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 |
| --- | --- | --- | --- |
| `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",
"dependencies": {
"npm-to-yarn": "^1.0.1",
"tslib": "^2.4.0",
"npm-to-yarn": "^2.0.0",
"tslib": "^2.4.1",
"unist-util-visit": "^2.0.3"
},
"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';
<Tabs>
<TabItem value="npm">
\`\`\`bash
@ -13,19 +14,30 @@ import TabItem from '@theme/TabItem';
\`\`\`
</TabItem>
<TabItem value="yarn" label="Yarn">
\`\`\`bash
$ yarn add --global docusaurus
$ yarn global add docusaurus
\`\`\`
</TabItem>
<TabItem value="pnpm" label="pnpm">
\`\`\`bash
$ pnpm add --global docusaurus
\`\`\`
</TabItem>
</Tabs>
"
`;
exports[`npm2yarn plugin does not re-import tabs components when already imported below 1`] = `
"<Tabs>
<TabItem value="npm">
\`\`\`bash
@ -33,13 +45,23 @@ exports[`npm2yarn plugin does not re-import tabs components when already importe
\`\`\`
</TabItem>
<TabItem value="yarn" label="Yarn">
\`\`\`bash
$ yarn add --global docusaurus
$ yarn global add docusaurus
\`\`\`
</TabItem>
<TabItem value="pnpm" label="pnpm">
\`\`\`bash
$ pnpm add --global docusaurus
\`\`\`
</TabItem>
</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`] = `
"import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
<Tabs>
<TabItem value="npm">
\`\`\`bash
@ -75,13 +188,23 @@ import TabItem from '@theme/TabItem';
\`\`\`
</TabItem>
<TabItem value="yarn" label="Yarn">
\`\`\`bash
$ yarn add --global docusaurus
$ yarn global add docusaurus
\`\`\`
</TabItem>
<TabItem value="pnpm" label="pnpm">
\`\`\`bash
$ pnpm add --global docusaurus
\`\`\`
</TabItem>
</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.
<Tabs>
<TabItem value="npm">
\`\`\`bash
@ -102,6 +226,7 @@ npm install --save docusaurus-plugin-name
\`\`\`
</TabItem>
<TabItem value="yarn" label="Yarn">
\`\`\`bash
@ -109,6 +234,136 @@ yarn add docusaurus-plugin-name
\`\`\`
</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>
"
`;
@ -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.
<Tabs groupId="npm2yarn">
<TabItem value="npm">
\`\`\`bash
@ -129,6 +385,7 @@ npm install --save docusaurus-plugin-name
\`\`\`
</TabItem>
<TabItem value="yarn" label="Yarn">
\`\`\`bash
@ -136,6 +393,15 @@ yarn add docusaurus-plugin-name
\`\`\`
</TabItem>
<TabItem value="pnpm" label="pnpm">
\`\`\`bash
pnpm add docusaurus-plugin-name
\`\`\`
</TabItem>
</Tabs>
"
`;

View file

@ -11,7 +11,10 @@ import mdx from 'remark-mdx';
import remark from 'remark';
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 file = await vfile.read(filePath);
const result = await remark().use(mdx).use(npm2yarn, options).process(file);
@ -32,6 +35,12 @@ describe('npm2yarn plugin', () => {
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 () => {
const result = await processFixture('plugin', {sync: true});
@ -55,4 +64,24 @@ describe('npm2yarn plugin', () => {
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 {Node, Parent} from 'unist';
type CustomConverter = [name: string, cb: (npmCode: string) => string];
type PluginOptions = {
sync?: boolean;
converters?: (CustomConverter | 'yarn' | 'pnpm')[];
};
// E.g. global install: 'npm i' -> 'yarn'
const convertNpmToYarn = (npmCode: string) => npmToYarn(npmCode, 'yarn');
const transformNode = (node: Code, isSync: boolean) => {
const groupIdProp = isSync ? ' groupId="npm2yarn"' : '';
const npmCode = node.value;
const yarnCode = convertNpmToYarn(node.value);
function createTabItem(
code: string,
node: Code,
value: string,
label?: string,
) {
return [
{
type: 'jsx',
value: `<Tabs${groupIdProp}>\n<TabItem value="npm">`,
value: `<TabItem value="${value}"${label ? ` label="${label}"` : ''}>`,
},
{
type: node.type,
lang: node.lang,
value: npmCode,
value: code,
},
{
type: 'jsx',
value: '</TabItem>\n<TabItem value="yarn" label="Yarn">',
},
{
type: node.type,
lang: node.lang,
value: yarnCode,
value: '</TabItem>',
},
] as Content[];
}
const transformNode = (
node: Code,
isSync: boolean,
converters: (CustomConverter | 'yarn' | 'pnpm')[],
) => {
const groupIdProp = isSync ? ' groupId="npm2yarn"' : '';
const npmCode = node.value;
return [
{
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[];
};
@ -60,7 +83,7 @@ const nodeForImport: Literal = {
};
const plugin: Plugin<[PluginOptions?]> = (options = {}) => {
const {sync = false} = options;
const {sync = false, converters = ['yarn', 'pnpm']} = options;
return (root) => {
let transformed = false as boolean;
let alreadyImported = false as boolean;
@ -73,7 +96,7 @@ const plugin: Plugin<[PluginOptions?]> = (options = {}) => {
while (index < node.children.length) {
const child = node.children[index]!;
if (matchNode(child)) {
const result = transformNode(child, sync);
const result = transformNode(child, sync, converters);
node.children.splice(index, 1, ...result);
index += result.length;
transformed = true;

View file

@ -770,6 +770,14 @@ module.exports = {
remarkPlugins: [require('@docusaurus/remark-plugin-npm2yarn')],
},
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}

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:
```mdx-code-block
<Tabs>
<TabItem value="npm">
```
```bash
```bash npm2yarn
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>
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:
```bash npm2yarn
```bash
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.
```bash
```bash npm2yarn
cd ./v2-website
yarn install
yarn start
npm install
npm start
```
:::danger

View file

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

View file

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