diff --git a/www/apps/book/app/learn/build/page.mdx b/www/apps/book/app/learn/build/page.mdx index 387035f362..5337d40163 100644 --- a/www/apps/book/app/learn/build/page.mdx +++ b/www/apps/book/app/learn/build/page.mdx @@ -61,7 +61,7 @@ When `NODE_ENV=production`, the Medusa application loads the environment variabl 3. Set `NODE_ENV` to `production` in the system environment variable: -```bash npm2yarn title=".medusa/server" +```bash title=".medusa/server" export NODE_ENV=production ``` diff --git a/www/apps/cloud/app/deployments/page.mdx b/www/apps/cloud/app/deployments/page.mdx index 7b81b1e145..91ea646654 100644 --- a/www/apps/cloud/app/deployments/page.mdx +++ b/www/apps/cloud/app/deployments/page.mdx @@ -56,7 +56,7 @@ You can replace `npm run other-build-steps` with the appropriate command for you If you're deploying both a Medusa application and a storefront on Cloud, Medusa will run the: 1. The `build` command defined in the backend's `package.json` file, which must run the `medusa build` command. -2. The build command relevant to the storefront, depending on the framework you're using. For example, if you're using Next.js for your storefront, Medusa will run the `next build` command in the storefront's directory. +2. The build command relevant to the storefront, depending on the frontend framework you're using. For example, if you're using Next.js for your storefront, Medusa will run the `next build` command in the storefront's directory. - Medusa currently doesn't support custom build scripts for storefronts. ### What Gets Deployed in the Medusa Application? diff --git a/www/apps/cloud/app/projects/page.mdx b/www/apps/cloud/app/projects/page.mdx index 451fee0daf..dbf0aaa348 100644 --- a/www/apps/cloud/app/projects/page.mdx +++ b/www/apps/cloud/app/projects/page.mdx @@ -49,7 +49,7 @@ Medusa provides you with the following starters that you can use to quickly set - **DTC Starter**: Standard Medusa application with fully-fledged commerce features. - **B2B Starter**: Medusa application with powerful B2B and commerce features. -Both starters come with a pre-configured Medusa server, admin dashboard, and Next.js storefront. +Both starters come with a pre-configured Medusa server, admin dashboard, and the Next.js Starter Storefront. To create a project from either of these starters: diff --git a/www/apps/cloud/app/projects/prerequisites/page.mdx b/www/apps/cloud/app/projects/prerequisites/page.mdx index 4608f7d581..b6c2235ef5 100644 --- a/www/apps/cloud/app/projects/prerequisites/page.mdx +++ b/www/apps/cloud/app/projects/prerequisites/page.mdx @@ -4,13 +4,13 @@ export const metadata = { # {metadata.title} -In this guide, learn about the prerequisites for your Medusa application and storefront before deploying it to Medusa Cloud in a new project. +In this guide, learn about the prerequisites for your Medusa application and storefront before deploying it to Cloud in a new project. Alternatively, you can create a project from a starter, as explained in the [Create Projects](../page.mdx) guide. ## Who is this guide for? -This guide is intended for developers and teams deploying their local Medusa applications to Medusa Cloud. +This guide is intended for developers and teams deploying their local Medusa applications to Cloud. You'll learn what setup steps are necessary for: @@ -25,7 +25,7 @@ This section covers the prerequisites for deploying your Medusa application (ser If you're also deploying a storefront with your backend, check the [next section](#prerequisites-for-medusa-application-with-storefront) for additional prerequisites. -### Configurations Managed by Medusa Cloud +### Configurations Managed in Cloud Your existing Medusa application (server and admin dashboard) doesn't need specific configurations to be deployed to Cloud. Medusa automatically: diff --git a/www/packages/docs-ui/src/components/CodeBlock/index.tsx b/www/packages/docs-ui/src/components/CodeBlock/index.tsx index 3c4d5112d0..4e27f20140 100644 --- a/www/packages/docs-ui/src/components/CodeBlock/index.tsx +++ b/www/packages/docs-ui/src/components/CodeBlock/index.tsx @@ -29,6 +29,7 @@ export type CodeBlockMetaFields = { title?: string hasTabs?: boolean npm2yarn?: boolean + npx2yarn?: boolean highlights?: string[][] apiTesting?: boolean testApiMethod?: ApiMethod diff --git a/www/packages/docs-ui/src/components/CodeMdx/index.tsx b/www/packages/docs-ui/src/components/CodeMdx/index.tsx index 7d96d57f73..d386f3e2eb 100644 --- a/www/packages/docs-ui/src/components/CodeMdx/index.tsx +++ b/www/packages/docs-ui/src/components/CodeMdx/index.tsx @@ -7,6 +7,7 @@ import { import { InlineCode, InlineCodeProps } from "@/components/InlineCode" import { MermaidDiagram } from "@/components/MermaidDiagram" import { Npm2YarnCode } from "../Npm2YarnCode" +import { Npx2YarnCode } from "../Npx2YarnCode" export type CodeMdxProps = { className?: string @@ -39,6 +40,8 @@ export const CodeMdx = ({ if (match) { if (rest.npm2yarn) { return + } else if (rest.npx2yarn) { + return } else if (match[1] === "mermaid") { return } diff --git a/www/packages/docs-ui/src/components/Npx2YarnCode/__tests__/index.test.tsx b/www/packages/docs-ui/src/components/Npx2YarnCode/__tests__/index.test.tsx new file mode 100644 index 0000000000..b6a9e8aa2c --- /dev/null +++ b/www/packages/docs-ui/src/components/Npx2YarnCode/__tests__/index.test.tsx @@ -0,0 +1,112 @@ +import React from "react" +import { describe, expect, test, vi } from "vitest" +import { render } from "@testing-library/react" + +// mock functions +const npxToYarnMock = vi.fn((code: string, packageManager: "yarn" | "pnpm") => code) + +// mock components +vi.mock("@/components/CodeTabs", () => ({ + CodeTabs: ({ + children, + group, + }: { + children: React.ReactNode + group: string + }) => ( +
+ {children} +
+ ), +})) + +vi.mock("@/components/CodeTabs/Item", () => ({ + CodeTab: ({ + children, + label, + value, + }: { + children: React.ReactNode + label: string + value: string + }) => ( +
+ {children} +
+ ), +})) + +vi.mock("@/components/CodeBlock", () => ({ + CodeBlock: ({ + source, + lang, + title, + }: { + source: string + lang: string + title?: string + }) => ( +
+ {source} +
+ ), +})) + +vi.mock("@/utils/npx-to-yarn", () => ({ + npxToYarn: (code: string, packageManager: "yarn" | "pnpm") => + npxToYarnMock(code, packageManager), +})) + +import { Npx2YarnCode } from "../index" + +describe("render", () => { + test("renders npm2yarn code", () => { + const { container } = render( + + ) + expect(npxToYarnMock).toHaveBeenCalledTimes(2) + expect(container).toBeInTheDocument() + const codeTabs = container.querySelector("[data-testid='code-tabs']") + expect(codeTabs).toBeInTheDocument() + expect(codeTabs).toHaveAttribute("data-group", "npm2yarn") + const codeTabsChildren = codeTabs?.querySelectorAll( + "[data-testid='code-tab']" + ) + expect(codeTabsChildren).toHaveLength(3) + expect(codeTabsChildren![0]).toHaveAttribute("data-label", "npx") + expect(codeTabsChildren![0]).toHaveAttribute("data-value", "npm") + const npxCodeBlock = codeTabsChildren![0].querySelector( + "[data-testid='code-block']" + ) + expect(npxCodeBlock).toBeInTheDocument() + expect(npxCodeBlock).toHaveAttribute("data-lang", "bash") + expect(npxCodeBlock).toHaveTextContent("npx medusa db:migrate") + expect(codeTabsChildren![1]).toHaveAttribute("data-label", "yarn") + expect(codeTabsChildren![1]).toHaveAttribute("data-value", "yarn") + const yarnCodeBlock = codeTabsChildren![1].querySelector( + "[data-testid='code-block']" + ) + expect(yarnCodeBlock).toBeInTheDocument() + expect(yarnCodeBlock).toHaveAttribute("data-lang", "bash") + expect(codeTabsChildren![2]).toHaveAttribute("data-label", "pnpm") + expect(codeTabsChildren![2]).toHaveAttribute("data-value", "pnpm") + const pnpmCodeBlock = codeTabsChildren![2].querySelector( + "[data-testid='code-block']" + ) + expect(pnpmCodeBlock).toBeInTheDocument() + expect(pnpmCodeBlock).toHaveAttribute("data-lang", "bash") + }) + + test("renders npm2yarn code with custom code options", () => { + const { container } = render( + + ) + expect(container).toBeInTheDocument() + const codeBlock = container.querySelector("[data-testid='code-block']") + expect(codeBlock).toBeInTheDocument() + expect(codeBlock).toHaveAttribute("data-title", "Custom Title") + }) +}) diff --git a/www/packages/docs-ui/src/components/Npx2YarnCode/index.tsx b/www/packages/docs-ui/src/components/Npx2YarnCode/index.tsx new file mode 100644 index 0000000000..8a1c43d9e1 --- /dev/null +++ b/www/packages/docs-ui/src/components/Npx2YarnCode/index.tsx @@ -0,0 +1,63 @@ +import React from "react" +import { CodeBlock, CodeBlockMetaFields } from "@/components/CodeBlock" +import { CodeTabs } from "@/components/CodeTabs" +import { CodeTab } from "@/components/CodeTabs/Item" +import { npxToYarn } from "@/utils/npx-to-yarn" + +type Npx2YarnCodeProps = { + npxCode: string +} & Omit + +export const Npx2YarnCode = ({ + npxCode, + ...codeOptions +}: Npx2YarnCodeProps) => { + // convert npx code + const yarnCode = npxToYarn(npxCode, "yarn") + const pnpmCode = npxToYarn(npxCode, "pnpm") + const lang = "bash" + + codeOptions.hasTabs = true + + const tabs = [ + { + label: "npx", + // keep it npm so it matches the tab name in Npm2YarnCode + value: "npm", + code: { + source: npxCode, + lang, + ...codeOptions, + }, + }, + { + label: "yarn", + value: "yarn", + code: { + source: yarnCode, + lang, + ...codeOptions, + }, + }, + { + label: "pnpm", + value: "pnpm", + code: { + source: pnpmCode, + lang, + ...codeOptions, + }, + }, + ] + + return ( + // Keep the group name same as Npm2YarnCode, value selection will be synced across both components + + {tabs.map((tab, index) => ( + + + + ))} + + ) +} diff --git a/www/packages/docs-ui/src/utils/__tests__/npx-to-yarn.test.ts b/www/packages/docs-ui/src/utils/__tests__/npx-to-yarn.test.ts new file mode 100644 index 0000000000..a7e2d1a8c2 --- /dev/null +++ b/www/packages/docs-ui/src/utils/__tests__/npx-to-yarn.test.ts @@ -0,0 +1,70 @@ +import { describe, it, expect } from "vitest" +import { npxToYarn } from "../npx-to-yarn.js" + +describe("npxToYarn", () => { + describe("yarn conversion", () => { + it("should convert basic npx command to yarn", () => { + const result = npxToYarn("npx medusa db:migrate", "yarn") + expect(result).toBe("yarn medusa db:migrate") + }) + + it("should convert npx command with multiple arguments", () => { + const result = npxToYarn("npx medusa develop --port 9000", "yarn") + expect(result).toBe("yarn medusa develop --port 9000") + }) + + it("should convert npx command with flags", () => { + const result = npxToYarn("npx medusa user --email admin@test.com", "yarn") + expect(result).toBe("yarn medusa user --email admin@test.com") + }) + + it("should handle npx command with leading/trailing whitespace", () => { + const result = npxToYarn(" npx medusa db:migrate ", "yarn") + expect(result).toBe("yarn medusa db:migrate") + }) + }) + + describe("pnpm conversion", () => { + it("should convert basic npx command to pnpm", () => { + const result = npxToYarn("npx medusa db:migrate", "pnpm") + expect(result).toBe("pnpm medusa db:migrate") + }) + + it("should convert npx command with multiple arguments", () => { + const result = npxToYarn("npx medusa develop --port 9000", "pnpm") + expect(result).toBe("pnpm medusa develop --port 9000") + }) + + it("should convert npx command with flags", () => { + const result = npxToYarn("npx medusa user --email admin@test.com", "pnpm") + expect(result).toBe("pnpm medusa user --email admin@test.com") + }) + + it("should handle npx command with leading/trailing whitespace", () => { + const result = npxToYarn(" npx medusa db:migrate ", "pnpm") + expect(result).toBe("pnpm medusa db:migrate") + }) + }) + + describe("edge cases", () => { + it("should return original command if it does not start with npx", () => { + const result = npxToYarn("npm install medusa", "yarn") + expect(result).toBe("npm install medusa") + }) + + it("should handle command with only npx and package name", () => { + const result = npxToYarn("npx medusa", "yarn") + expect(result).toBe("yarn medusa") + }) + + it("should preserve command structure with special characters", () => { + const result = npxToYarn("npx medusa db:seed --file=./data.json", "pnpm") + expect(result).toBe("pnpm medusa db:seed --file=./data.json") + }) + + it("should handle command with path separators", () => { + const result = npxToYarn("npx @medusajs/medusa-cli develop", "yarn") + expect(result).toBe("yarn @medusajs/medusa-cli develop") + }) + }) +}) diff --git a/www/packages/docs-ui/src/utils/npx-to-yarn.ts b/www/packages/docs-ui/src/utils/npx-to-yarn.ts new file mode 100644 index 0000000000..ea4862aacb --- /dev/null +++ b/www/packages/docs-ui/src/utils/npx-to-yarn.ts @@ -0,0 +1,34 @@ +/** + * Converts an npx command to its yarn or pnpm equivalent + * Assumes the package is installed locally in node_modules + * @param npxCommand - The npx command to convert (e.g., "npx medusa db:migrate") + * @param packageManager - The target package manager ("yarn" or "pnpm") + * @returns The converted command + * + * @example + * npxToYarn("npx medusa db:migrate", "yarn") // "yarn medusa db:migrate" + * npxToYarn("npx medusa db:migrate", "pnpm") // "pnpm medusa db:migrate" + */ +export function npxToYarn( + npxCommand: string, + packageManager: "yarn" | "pnpm" +): string { + // Remove leading/trailing whitespace + const trimmed = npxCommand.trim() + + // Check if command starts with npx + if (!trimmed.startsWith("npx ")) { + return trimmed + } + + // Remove "npx " prefix and replace with the target package manager + const command = trimmed.slice(4) + + if (packageManager === "yarn") { + return `yarn ${command}` + } else if (packageManager === "pnpm") { + return `pnpm ${command}` + } + + return trimmed +} diff --git a/www/packages/docs-utils/package.json b/www/packages/docs-utils/package.json index 4a843203e5..cbcfca8bef 100644 --- a/www/packages/docs-utils/package.json +++ b/www/packages/docs-utils/package.json @@ -25,7 +25,8 @@ "scripts": { "build": "yarn clean && tsc", "clean": "rimraf dist", - "watch": "tsc --watch" + "watch": "tsc --watch", + "test": "vitest --passWithNoTests" }, "dependencies": { "@mdx-js/mdx": "^3.1.0", @@ -44,7 +45,9 @@ "rimraf": "^5.0.5", "tsconfig": "*", "types": "*", - "typescript": "^5.3.3" + "typescript": "^5.3.3", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^2.1.8" }, "engines": { "node": ">=18.17.0" diff --git a/www/packages/docs-utils/tsconfig.json b/www/packages/docs-utils/tsconfig.json index ce3e8d1889..21960ea72b 100644 --- a/www/packages/docs-utils/tsconfig.json +++ b/www/packages/docs-utils/tsconfig.json @@ -16,5 +16,8 @@ "skipLibCheck": true, "resolveJsonModule": true }, - "include": ["src"] + "include": ["src"], + "exclude": [ + "**/__tests__" + ] } diff --git a/www/packages/docs-utils/tsconfig.tests.json b/www/packages/docs-utils/tsconfig.tests.json new file mode 100644 index 0000000000..c201492c62 --- /dev/null +++ b/www/packages/docs-utils/tsconfig.tests.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "include": [ + "src/**/*", + "__tests__/**/*", + "__mocks__/**/*" + ], + "exclude": [] +} \ No newline at end of file diff --git a/www/packages/docs-utils/vitest.config.mts b/www/packages/docs-utils/vitest.config.mts new file mode 100644 index 0000000000..8c8c5f61a3 --- /dev/null +++ b/www/packages/docs-utils/vitest.config.mts @@ -0,0 +1,18 @@ +import { defineConfig } from 'vitest/config' +import react from '@vitejs/plugin-react' +import tsconfigPaths from 'vite-tsconfig-paths' +import { resolve } from 'path' + +export default defineConfig({ + plugins: [ + tsconfigPaths({ + configNames: ["tsconfig.tests.json"] + }), + react() + ], + test: { + environment: 'jsdom', + setupFiles: [resolve(__dirname, '../../vitest.setup.ts')], + }, +}) + diff --git a/www/vale/styles/docs/Tooling.yml b/www/vale/styles/docs/Tooling.yml index fe7f26ad79..fcec53a8c1 100644 --- a/www/vale/styles/docs/Tooling.yml +++ b/www/vale/styles/docs/Tooling.yml @@ -38,4 +38,5 @@ exceptions: - 'Frontend''s Framework' - 'Frontend''s framework' - 'your framework' - - 'Log in with Medusa Cloud' \ No newline at end of file + - 'Log in with Medusa Cloud' + - 'different framework' \ No newline at end of file diff --git a/www/yarn.lock b/www/yarn.lock index dfe6fd6a52..ed3788ad59 100644 --- a/www/yarn.lock +++ b/www/yarn.lock @@ -8238,6 +8238,8 @@ __metadata: typescript: ^5.3.3 unified: ^11.0.4 vfile-matter: ^5.0.0 + vite-tsconfig-paths: ^5.1.4 + vitest: ^2.1.8 languageName: unknown linkType: soft