docs: add npx2yarn component (#14512)
* initial * initial * update tests * remove unused import * allow passing with no tests * vale fixes
This commit is contained in:
@@ -29,6 +29,7 @@ export type CodeBlockMetaFields = {
|
||||
title?: string
|
||||
hasTabs?: boolean
|
||||
npm2yarn?: boolean
|
||||
npx2yarn?: boolean
|
||||
highlights?: string[][]
|
||||
apiTesting?: boolean
|
||||
testApiMethod?: ApiMethod
|
||||
|
||||
@@ -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 <Npm2YarnCode npmCode={codeContent} {...rest} />
|
||||
} else if (rest.npx2yarn) {
|
||||
return <Npx2YarnCode npxCode={codeContent} {...rest} />
|
||||
} else if (match[1] === "mermaid") {
|
||||
return <MermaidDiagram diagramContent={codeContent} />
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}) => (
|
||||
<div data-testid="code-tabs" data-group={group}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock("@/components/CodeTabs/Item", () => ({
|
||||
CodeTab: ({
|
||||
children,
|
||||
label,
|
||||
value,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
label: string
|
||||
value: string
|
||||
}) => (
|
||||
<div data-testid="code-tab" data-label={label} data-value={value}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock("@/components/CodeBlock", () => ({
|
||||
CodeBlock: ({
|
||||
source,
|
||||
lang,
|
||||
title,
|
||||
}: {
|
||||
source: string
|
||||
lang: string
|
||||
title?: string
|
||||
}) => (
|
||||
<div data-testid="code-block" data-lang={lang} data-title={title}>
|
||||
{source}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
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(
|
||||
<Npx2YarnCode npxCode="npx medusa db:migrate" />
|
||||
)
|
||||
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(
|
||||
<Npx2YarnCode
|
||||
npxCode="npx medusa db:migrate"
|
||||
title="Custom Title"
|
||||
/>
|
||||
)
|
||||
expect(container).toBeInTheDocument()
|
||||
const codeBlock = container.querySelector("[data-testid='code-block']")
|
||||
expect(codeBlock).toBeInTheDocument()
|
||||
expect(codeBlock).toHaveAttribute("data-title", "Custom Title")
|
||||
})
|
||||
})
|
||||
@@ -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<CodeBlockMetaFields, "npx2yarn">
|
||||
|
||||
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
|
||||
<CodeTabs group="npm2yarn">
|
||||
{tabs.map((tab, index) => (
|
||||
<CodeTab label={tab.label} value={tab.value} key={index}>
|
||||
<CodeBlock {...tab.code} />
|
||||
</CodeTab>
|
||||
))}
|
||||
</CodeTabs>
|
||||
)
|
||||
}
|
||||
@@ -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")
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user