Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ const defaultManager = managers[0];

<script>
const STORAGE_KEY = 'expressjs:package-manager';
const MANAGERS = ['npm', 'yarn', 'pnpm', 'bun'];
const MANAGERS = ['npm', 'yarn', 'pnpm', 'bun', 'deno'];

function applyManager(pm: string) {
document.querySelectorAll<HTMLElement>('[data-pm-command]').forEach((root) => {
Expand Down
19 changes: 15 additions & 4 deletions src/utils/package-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
* Package manager helpers.
*
* Converts an `npm`/`npx` command into the equivalent command for the other
* supported package managers (Yarn, pnpm, Bun) so docs can show a single,
* supported package managers (Yarn, pnpm, Bun, Deno) so docs can show a single,
* authored npm command and let readers pick their tool of choice.
*
* Authors write the npm command they already know; everything else is derived.
* Commands that have no clean cross-manager equivalent (e.g. `npm install
* <pkg> --no-save`) should stay as a plain fenced code block instead.
*/

export const PACKAGE_MANAGERS = ['npm', 'yarn', 'pnpm', 'bun'] as const;
export const PACKAGE_MANAGERS = ['npm', 'yarn', 'pnpm', 'bun', 'deno'] as const;

export type PackageManager = (typeof PACKAGE_MANAGERS)[number];

Expand All @@ -19,6 +19,7 @@ export const PACKAGE_MANAGER_LABELS: Record<PackageManager, string> = {
yarn: 'yarn',
pnpm: 'pnpm',
bun: 'bun',
deno: 'deno',
};

/**
Expand All @@ -38,6 +39,7 @@ const sameForAll = (command: string): CommandMap => ({
yarn: command,
pnpm: command,
bun: command,
deno: command,
});

const join = (...parts: (string | false | undefined)[]): string =>
Expand All @@ -52,14 +54,15 @@ export function convertPackageManagerCommand(command: string): CommandMap {
const tokens = command.trim().split(/\s+/);
const [bin, sub, ...rest] = tokens;

// npx codemod@latest ... -> yarn/pnpm dlx, bunx
// npx codemod@latest ... -> yarn/pnpm dlx, bunx, deno x
if (bin === 'npx') {
const exec = tokens.slice(1).join(' ');
return {
npm: join('npx', exec),
yarn: join('yarn dlx', exec),
pnpm: join('pnpm dlx', exec),
bun: join('bunx', exec),
deno: join('deno x', exec),
};
}

Expand All @@ -73,7 +76,7 @@ export function convertPackageManagerCommand(command: string): CommandMap {
const packages = rest.filter((t) => !t.startsWith('-'));
const isDev = flags.some((f) => DEV_FLAGS.has(f));
const isGlobal = flags.some((f) => GLOBAL_FLAGS.has(f));
// `--no-save` only exists on npm and Bun; Yarn and pnpm have no equivalent.
// `--no-save` only exists on npm and Bun; Yarn, pnpm and Deno have no equivalent.
const hasNoSave = flags.includes('--no-save');
const extra = flags.filter(
(f) => !DEV_FLAGS.has(f) && !GLOBAL_FLAGS.has(f) && !SAVE_FLAGS.has(f)
Expand All @@ -88,6 +91,7 @@ export function convertPackageManagerCommand(command: string): CommandMap {
yarn: join('yarn install', extras),
pnpm: join('pnpm install', extras),
bun: join('bun install', extras),
deno: join('deno install', extras),
};
}

Expand All @@ -102,6 +106,9 @@ export function convertPackageManagerCommand(command: string): CommandMap {
? null
: join('pnpm add', isDev && '--save-dev', isGlobal && '--global', pkgs, extras),
bun: join('bun add', isDev && '--dev', isGlobal && '--global', pkgs, extras),
deno: hasNoSave
? null
: join(isGlobal ? 'deno install -g' : 'deno add', isDev && '--dev', pkgs, extras),
};
}

Expand All @@ -117,6 +124,7 @@ export function convertPackageManagerCommand(command: string): CommandMap {
yarn: isGlobal ? join('yarn global remove', pkgs) : join('yarn remove', pkgs),
pnpm: join('pnpm remove', isGlobal && '--global', pkgs),
bun: join('bun remove', isGlobal && '--global', pkgs),
deno: isGlobal ? null : join('deno remove', pkgs),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure that Deno allows uninstalling packages installed globally.

BTW Deno docs say that add is an alias of install and remove is an alias of uninstall, but aliases usually accept the same parameters.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the catch, actually it looks like deno remove doesn't accept --global. I'll fix it for Deno v2.9 and can open a follow up PR once it lands if that works for you.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks! Fixed β€” global removals now map to deno uninstall --global <pkg> (mirroring the yarn global remove branch) instead of being dropped. You're right that remove/uninstall are aliases, but only uninstall takes --global, so I used that for the global case.

};
}

Expand All @@ -130,13 +138,15 @@ export function convertPackageManagerCommand(command: string): CommandMap {
yarn: join('yarn init', args),
pnpm: join('pnpm init', args),
bun: join('bun init', args),
deno: join('deno init', args),
};
}
return {
npm: command,
yarn: join('yarn create', args),
pnpm: join('pnpm create', args),
bun: join('bun create', args),
deno: join('deno create --npm', args),
};
}

Expand All @@ -147,6 +157,7 @@ export function convertPackageManagerCommand(command: string): CommandMap {
yarn: join('yarn', args),
pnpm: join('pnpm', args),
bun: join('bun run', args),
deno: join('deno task', args),
};
}

Expand Down
Loading