feat(npm-to-yarn): add support for PnPm and custom converters (#8690)

Co-authored-by: Ben Gubler <nebrelbug@gmail.com>
Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
This commit is contained in:
Armano 2023-02-24 17:59:15 +01:00 committed by sebastienlorber
parent 3a73ce5ee1
commit a8ab309ec8
11 changed files with 416 additions and 70 deletions

View file

@ -63,3 +63,30 @@ module.exports = {
| Property | Type | Default | Description | | Property | Type | Default | Description |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `sync` | `boolean` | `false` | Syncing tab choices (Yarn and npm). See https://docusaurus.io/docs/markdown-features/#syncing-tab-choices for details. | | `sync` | `boolean` | `false` | Syncing tab choices (Yarn and npm). See https://docusaurus.io/docs/markdown-features/#syncing-tab-choices for details. |
| `converters` | `array` | `'yarn'`, `'pnpm'` | The list of converters to use. The order of the converters is important, as the first converter will be used as the default choice. |
## Custom converters
In case you want to convert npm commands to something else than `yarn` or `pnpm`, you can use custom converters:
```ts
type CustomConverter = [name: string, cb: (npmCode: string) => string];
```
```ts
{
remarkPlugins: [
[
require('@docusaurus/remark-plugin-npm2yarn'),
{
sync: true,
converters: [
'yarn',
'pnpm',
['Turbo', (code) => code.replace(/npm/g, 'turbo')],
],
},
],
];
}
```

View file

@ -17,8 +17,8 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"npm-to-yarn": "^1.0.1", "npm-to-yarn": "^2.0.0",
"tslib": "^2.4.0", "tslib": "^2.4.1",
"unist-util-visit": "^2.0.3" "unist-util-visit": "^2.0.3"
}, },
"devDependencies": { "devDependencies": {

View file

@ -0,0 +1,16 @@
```bash npm2yarn
npm run xxx -- --arg
```
```bash npm2yarn
npm install package
```
```bash npm2yarn
npm remove package-name
```
```bash npm2yarn
npm init docusaurus
npm init docusaurus@latest my-website classic
```

View file

@ -6,6 +6,7 @@ exports[`npm2yarn plugin does not re-import tabs components when already importe
import TabItem from '@theme/TabItem'; import TabItem from '@theme/TabItem';
<Tabs> <Tabs>
<TabItem value="npm"> <TabItem value="npm">
\`\`\`bash \`\`\`bash
@ -13,19 +14,30 @@ import TabItem from '@theme/TabItem';
\`\`\` \`\`\`
</TabItem> </TabItem>
<TabItem value="yarn" label="Yarn"> <TabItem value="yarn" label="Yarn">
\`\`\`bash \`\`\`bash
$ yarn add --global docusaurus $ yarn global add docusaurus
\`\`\` \`\`\`
</TabItem> </TabItem>
<TabItem value="pnpm" label="pnpm">
\`\`\`bash
$ pnpm add --global docusaurus
\`\`\`
</TabItem>
</Tabs> </Tabs>
" "
`; `;
exports[`npm2yarn plugin does not re-import tabs components when already imported below 1`] = ` exports[`npm2yarn plugin does not re-import tabs components when already imported below 1`] = `
"<Tabs> "<Tabs>
<TabItem value="npm"> <TabItem value="npm">
\`\`\`bash \`\`\`bash
@ -33,13 +45,23 @@ exports[`npm2yarn plugin does not re-import tabs components when already importe
\`\`\` \`\`\`
</TabItem> </TabItem>
<TabItem value="yarn" label="Yarn"> <TabItem value="yarn" label="Yarn">
\`\`\`bash \`\`\`bash
$ yarn add --global docusaurus $ yarn global add docusaurus
\`\`\` \`\`\`
</TabItem> </TabItem>
<TabItem value="pnpm" label="pnpm">
\`\`\`bash
$ pnpm add --global docusaurus
\`\`\`
</TabItem>
</Tabs> </Tabs>
import Tabs from '@theme/Tabs'; import Tabs from '@theme/Tabs';
@ -63,11 +85,102 @@ npm install --save docusaurus-plugin-name
" "
`; `;
exports[`npm2yarn plugin work with custom converter 1`] = `
"import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
## Installing a plugin
A plugin is usually a npm package, so you install them like other npm packages using npm.
<Tabs>
<TabItem value="npm">
\`\`\`bash
npm install --save docusaurus-plugin-name
\`\`\`
</TabItem>
<TabItem value="Turbo">
\`\`\`bash
turbo install --save docusaurus-plugin-name
\`\`\`
</TabItem>
</Tabs>
"
`;
exports[`npm2yarn plugin work with pnpm converter 1`] = `
"import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
## Installing a plugin
A plugin is usually a npm package, so you install them like other npm packages using npm.
<Tabs>
<TabItem value="npm">
\`\`\`bash
npm install --save docusaurus-plugin-name
\`\`\`
</TabItem>
<TabItem value="pnpm" label="pnpm">
\`\`\`bash
pnpm add docusaurus-plugin-name
\`\`\`
</TabItem>
</Tabs>
"
`;
exports[`npm2yarn plugin work with yarn converter 1`] = `
"import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
## Installing a plugin
A plugin is usually a npm package, so you install them like other npm packages using npm.
<Tabs>
<TabItem value="npm">
\`\`\`bash
npm install --save docusaurus-plugin-name
\`\`\`
</TabItem>
<TabItem value="yarn" label="Yarn">
\`\`\`bash
yarn add docusaurus-plugin-name
\`\`\`
</TabItem>
</Tabs>
"
`;
exports[`npm2yarn plugin works on installation file 1`] = ` exports[`npm2yarn plugin works on installation file 1`] = `
"import Tabs from '@theme/Tabs'; "import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem'; import TabItem from '@theme/TabItem';
<Tabs> <Tabs>
<TabItem value="npm"> <TabItem value="npm">
\`\`\`bash \`\`\`bash
@ -75,13 +188,23 @@ import TabItem from '@theme/TabItem';
\`\`\` \`\`\`
</TabItem> </TabItem>
<TabItem value="yarn" label="Yarn"> <TabItem value="yarn" label="Yarn">
\`\`\`bash \`\`\`bash
$ yarn add --global docusaurus $ yarn global add docusaurus
\`\`\` \`\`\`
</TabItem> </TabItem>
<TabItem value="pnpm" label="pnpm">
\`\`\`bash
$ pnpm add --global docusaurus
\`\`\`
</TabItem>
</Tabs> </Tabs>
" "
`; `;
@ -95,6 +218,7 @@ import TabItem from '@theme/TabItem';
A plugin is usually a npm package, so you install them like other npm packages using npm. A plugin is usually a npm package, so you install them like other npm packages using npm.
<Tabs> <Tabs>
<TabItem value="npm"> <TabItem value="npm">
\`\`\`bash \`\`\`bash
@ -102,6 +226,7 @@ npm install --save docusaurus-plugin-name
\`\`\` \`\`\`
</TabItem> </TabItem>
<TabItem value="yarn" label="Yarn"> <TabItem value="yarn" label="Yarn">
\`\`\`bash \`\`\`bash
@ -109,6 +234,136 @@ yarn add docusaurus-plugin-name
\`\`\` \`\`\`
</TabItem> </TabItem>
<TabItem value="pnpm" label="pnpm">
\`\`\`bash
pnpm add docusaurus-plugin-name
\`\`\`
</TabItem>
</Tabs>
"
`;
exports[`npm2yarn plugin works with common commands 1`] = `
"import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
<Tabs groupId="npm2yarn">
<TabItem value="npm">
\`\`\`bash
npm run xxx -- --arg
\`\`\`
</TabItem>
<TabItem value="yarn" label="Yarn">
\`\`\`bash
yarn xxx --arg
\`\`\`
</TabItem>
<TabItem value="pnpm" label="pnpm">
\`\`\`bash
pnpm run xxx -- --arg
\`\`\`
</TabItem>
</Tabs>
<Tabs groupId="npm2yarn">
<TabItem value="npm">
\`\`\`bash
npm install package
\`\`\`
</TabItem>
<TabItem value="yarn" label="Yarn">
\`\`\`bash
yarn add package
\`\`\`
</TabItem>
<TabItem value="pnpm" label="pnpm">
\`\`\`bash
pnpm add package
\`\`\`
</TabItem>
</Tabs>
<Tabs groupId="npm2yarn">
<TabItem value="npm">
\`\`\`bash
npm remove package-name
\`\`\`
</TabItem>
<TabItem value="yarn" label="Yarn">
\`\`\`bash
yarn remove package-name
\`\`\`
</TabItem>
<TabItem value="pnpm" label="pnpm">
\`\`\`bash
pnpm remove package-name
\`\`\`
</TabItem>
</Tabs>
<Tabs groupId="npm2yarn">
<TabItem value="npm">
\`\`\`bash
npm init docusaurus
npm init docusaurus@latest my-website classic
\`\`\`
</TabItem>
<TabItem value="yarn" label="Yarn">
\`\`\`bash
yarn create docusaurus
yarn create docusaurus@latest my-website classic
\`\`\`
</TabItem>
<TabItem value="pnpm" label="pnpm">
\`\`\`bash
pnpm create docusaurus
pnpm create docusaurus@latest my-website classic
\`\`\`
</TabItem>
</Tabs> </Tabs>
" "
`; `;
@ -122,6 +377,7 @@ import TabItem from '@theme/TabItem';
A plugin is usually a npm package, so you install them like other npm packages using npm. A plugin is usually a npm package, so you install them like other npm packages using npm.
<Tabs groupId="npm2yarn"> <Tabs groupId="npm2yarn">
<TabItem value="npm"> <TabItem value="npm">
\`\`\`bash \`\`\`bash
@ -129,6 +385,7 @@ npm install --save docusaurus-plugin-name
\`\`\` \`\`\`
</TabItem> </TabItem>
<TabItem value="yarn" label="Yarn"> <TabItem value="yarn" label="Yarn">
\`\`\`bash \`\`\`bash
@ -136,6 +393,15 @@ yarn add docusaurus-plugin-name
\`\`\` \`\`\`
</TabItem> </TabItem>
<TabItem value="pnpm" label="pnpm">
\`\`\`bash
pnpm add docusaurus-plugin-name
\`\`\`
</TabItem>
</Tabs> </Tabs>
" "
`; `;

View file

@ -11,7 +11,10 @@ import mdx from 'remark-mdx';
import remark from 'remark'; import remark from 'remark';
import npm2yarn from '../index'; import npm2yarn from '../index';
const processFixture = async (name: string, options?: {sync?: boolean}) => { const processFixture = async (
name: string,
options?: Parameters<typeof npm2yarn>[0],
) => {
const filePath = path.join(__dirname, '__fixtures__', `${name}.md`); const filePath = path.join(__dirname, '__fixtures__', `${name}.md`);
const file = await vfile.read(filePath); const file = await vfile.read(filePath);
const result = await remark().use(mdx).use(npm2yarn, options).process(file); const result = await remark().use(mdx).use(npm2yarn, options).process(file);
@ -32,6 +35,12 @@ describe('npm2yarn plugin', () => {
expect(result).toMatchSnapshot(); expect(result).toMatchSnapshot();
}); });
it('works with common commands', async () => {
const result = await processFixture('conversion-test', {sync: true});
expect(result).toMatchSnapshot();
});
it('works with sync option', async () => { it('works with sync option', async () => {
const result = await processFixture('plugin', {sync: true}); const result = await processFixture('plugin', {sync: true});
@ -55,4 +64,24 @@ describe('npm2yarn plugin', () => {
expect(result).toMatchSnapshot(); expect(result).toMatchSnapshot();
}); });
it('work with yarn converter', async () => {
const result = await processFixture('plugin', {converters: ['yarn']});
expect(result).toMatchSnapshot();
});
it('work with pnpm converter', async () => {
const result = await processFixture('plugin', {converters: ['pnpm']});
expect(result).toMatchSnapshot();
});
it('work with custom converter', async () => {
const result = await processFixture('plugin', {
converters: [['Turbo', (code) => code.replace(/npm/g, 'turbo')]],
});
expect(result).toMatchSnapshot();
});
}); });

View file

@ -11,39 +11,62 @@ import type {Code, Content, Literal} from 'mdast';
import type {Plugin} from 'unified'; import type {Plugin} from 'unified';
import type {Node, Parent} from 'unist'; import type {Node, Parent} from 'unist';
type CustomConverter = [name: string, cb: (npmCode: string) => string];
type PluginOptions = { type PluginOptions = {
sync?: boolean; sync?: boolean;
converters?: (CustomConverter | 'yarn' | 'pnpm')[];
}; };
// E.g. global install: 'npm i' -> 'yarn' function createTabItem(
const convertNpmToYarn = (npmCode: string) => npmToYarn(npmCode, 'yarn'); code: string,
node: Code,
const transformNode = (node: Code, isSync: boolean) => { value: string,
const groupIdProp = isSync ? ' groupId="npm2yarn"' : ''; label?: string,
const npmCode = node.value; ) {
const yarnCode = convertNpmToYarn(node.value);
return [ return [
{ {
type: 'jsx', type: 'jsx',
value: `<Tabs${groupIdProp}>\n<TabItem value="npm">`, value: `<TabItem value="${value}"${label ? ` label="${label}"` : ''}>`,
}, },
{ {
type: node.type, type: node.type,
lang: node.lang, lang: node.lang,
value: npmCode, value: code,
}, },
{ {
type: 'jsx', type: 'jsx',
value: '</TabItem>\n<TabItem value="yarn" label="Yarn">', value: '</TabItem>',
},
{
type: node.type,
lang: node.lang,
value: yarnCode,
}, },
] as Content[];
}
const transformNode = (
node: Code,
isSync: boolean,
converters: (CustomConverter | 'yarn' | 'pnpm')[],
) => {
const groupIdProp = isSync ? ' groupId="npm2yarn"' : '';
const npmCode = node.value;
return [
{ {
type: 'jsx', type: 'jsx',
value: '</TabItem>\n</Tabs>', value: `<Tabs${groupIdProp}>`,
},
...createTabItem(npmCode, node, 'npm'),
...converters.flatMap((converter) =>
typeof converter === 'string'
? createTabItem(
npmToYarn(npmCode, converter),
node,
converter,
converter === 'yarn' ? 'Yarn' : converter,
)
: createTabItem(converter[1](npmCode), node, converter[0]),
),
{
type: 'jsx',
value: '</Tabs>',
}, },
] as Content[]; ] as Content[];
}; };
@ -60,7 +83,7 @@ const nodeForImport: Literal = {
}; };
const plugin: Plugin<[PluginOptions?]> = (options = {}) => { const plugin: Plugin<[PluginOptions?]> = (options = {}) => {
const {sync = false} = options; const {sync = false, converters = ['yarn', 'pnpm']} = options;
return (root) => { return (root) => {
let transformed = false as boolean; let transformed = false as boolean;
let alreadyImported = false as boolean; let alreadyImported = false as boolean;
@ -73,7 +96,7 @@ const plugin: Plugin<[PluginOptions?]> = (options = {}) => {
while (index < node.children.length) { while (index < node.children.length) {
const child = node.children[index]!; const child = node.children[index]!;
if (matchNode(child)) { if (matchNode(child)) {
const result = transformNode(child, sync); const result = transformNode(child, sync, converters);
node.children.splice(index, 1, ...result); node.children.splice(index, 1, ...result);
index += result.length; index += result.length;
transformed = true; transformed = true;

View file

@ -770,6 +770,14 @@ module.exports = {
remarkPlugins: [require('@docusaurus/remark-plugin-npm2yarn')], remarkPlugins: [require('@docusaurus/remark-plugin-npm2yarn')],
}, },
blog: { blog: {
// highlight-start
remarkPlugins: [
[
require('@docusaurus/remark-plugin-npm2yarn'),
{converters: ['pnpm']},
],
],
// highlight-end
// ... // ...
}, },
}, },
@ -786,7 +794,12 @@ npm install @docusaurus/remark-plugin-npm2yarn
``` ```
```` ````
Using the `{sync: true}` option would make all tab choices synced. Because the choice is stored under the same namespace `npm2yarn`, different `npm2yarn` plugin instances would also sync their choices. #### Configuration {#npm2yarn-remark-plugin-configuration}
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `sync` | `boolean` | `false` | Whether to sync the selected converter across all code blocks. |
| `converters` | `array` | `'yarn'`, `'pnpm'` | The list of converters to use. The order of the converters is important, as the first converter will be used as the default choice. |
## Usage in JSX {#usage-in-jsx} ## Usage in JSX {#usage-in-jsx}

View file

@ -55,38 +55,10 @@ npx create-docusaurus@latest my-website facebook
You can also initialize a new project using your preferred project manager: You can also initialize a new project using your preferred project manager:
```mdx-code-block ```bash npm2yarn
<Tabs>
<TabItem value="npm">
```
```bash
npm init docusaurus npm init docusaurus
``` ```
```mdx-code-block
</TabItem>
<TabItem value="yarn">
```
```bash
yarn create docusaurus
```
```mdx-code-block
</TabItem>
<TabItem value="pnpm">
```
```bash
pnpm create docusaurus
```
```mdx-code-block
</TabItem>
</Tabs>
```
</details> </details>
Run `npx create-docusaurus@latest --help`, or check out its [API docs](./api/misc/create-docusaurus.md) for more information about all available flags. Run `npx create-docusaurus@latest --help`, or check out its [API docs](./api/misc/create-docusaurus.md) for more information about all available flags.
@ -193,7 +165,7 @@ npm install
To check that the update occurred successfully, run: To check that the update occurred successfully, run:
```bash npm2yarn ```bash
npx docusaurus --version npx docusaurus --version
``` ```

View file

@ -38,10 +38,10 @@ npx @docusaurus/migrate migrate ./v1-website ./v2-website
3. To view your new website locally, go into your v2 website's directory and start your development server. 3. To view your new website locally, go into your v2 website's directory and start your development server.
```bash ```bash npm2yarn
cd ./v2-website cd ./v2-website
yarn install npm install
yarn start npm start
``` ```
:::danger :::danger

View file

@ -622,13 +622,13 @@ my-project
Start the development server and fix any errors: Start the development server and fix any errors:
```bash ```bash npm2yarn
cd website cd website
yarn start npm start
``` ```
You can also try to build the site for production: You can also try to build the site for production:
```bash ```bash npm2yarn
yarn build npm run build
``` ```

View file

@ -11444,10 +11444,10 @@ npm-run-path@^5.1.0:
dependencies: dependencies:
path-key "^4.0.0" path-key "^4.0.0"
npm-to-yarn@^1.0.1: npm-to-yarn@^2.0.0:
version "1.0.1" version "2.0.0"
resolved "https://registry.yarnpkg.com/npm-to-yarn/-/npm-to-yarn-1.0.1.tgz#6cdb95114c4ff0be50a7a2381d4d16131a5f52df" resolved "https://registry.yarnpkg.com/npm-to-yarn/-/npm-to-yarn-2.0.0.tgz#59c9c615eca3ba8920308a0b418007b73ffc7492"
integrity sha512-bp8T8oNMfLW+N/fE0itFfSu7RReytwhqNd9skbkfHfzGYC+5CCdzS2HnaXz6JiG4AlK2eA0qlT6NJN1SoFvcWQ== integrity sha512-/IbjiJ7vqbxfxJxAZ+QI9CCRjnIbvGxn5KQcSY9xHh0lMKc/Sgqmm7yp7KPmd6TiTZX5/KiSBKlkGHo59ucZbg==
npmlog@^6.0.0, npmlog@^6.0.2: npmlog@^6.0.0, npmlog@^6.0.2:
version "6.0.2" version "6.0.2"
@ -15023,10 +15023,10 @@ tslib@^1.8.1, tslib@^1.9.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0: tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.4.1:
version "2.4.0" version "2.5.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
tsutils@^3.21.0: tsutils@^3.21.0:
version "3.21.0" version "3.21.0"