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 GitHub
parent 17781fd5d3
commit fabf053def
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 411 additions and 66 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,7 +17,7 @@
},
"license": "MIT",
"dependencies": {
"npm-to-yarn": "^1.2.1",
"npm-to-yarn": "^2.0.0",
"tslib": "^2.4.1",
"unist-util-visit": "^2.0.3"
},

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

@ -6,45 +6,67 @@
*/
import visit from 'unist-util-visit';
// @ts-expect-error: this package provides CJS
import npmToYarn from 'npm-to-yarn';
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[];
};
@ -61,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;
@ -74,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;