Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
32 changes: 5 additions & 27 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ import deepmerge from 'deepmerge';
import minimist from 'minimist';
import { color, logger } from 'rslog';
import { x, xSync } from 'tinyexec';
import {
getAgentCreateCommand,
replaceCreateCommand,
} from './package-manager.js';
import { isNpmTemplate, resolveCustomTemplate } from './template-manager.js';

const __filename = fileURLToPath(import.meta.url);
Expand Down Expand Up @@ -82,12 +86,6 @@ function pkgFromUserAgent(userAgent: string | undefined) {
};
}

function getAgentCreateCommand(name: string, packageManager: string) {
return packageManager === 'nub'
? `nub create ${name}@latest`
: `npx -y create-${name}`;
}

const PACKAGE_MANAGER_FILES = [
{ file: 'pnpm-workspace.yaml', packageManager: 'pnpm' },
];
Expand Down Expand Up @@ -504,27 +502,7 @@ async function runCommand(
cwd: string,
packageManager: string,
) {
// Replace `npm create` with the equivalent command for the detected package manager
if (command.startsWith('npm create ')) {
const createReplacements: Record<string, string> = {
bun: 'bun create ',
nub: 'nub create ',
pnpm: 'pnpm create ',
yarn: 'yarn create ',
deno: 'deno run -A npm:create-',
};
const replacement = createReplacements[packageManager];
if (replacement) {
command = command
.replace('npm create ', replacement)
// other package managers don't need the extra `--`
.replace(' -- --', ' --');
}
// Yarn v1 does not support `@latest` tag
if (packageManager === 'yarn') {
command = command.replace('@latest', '');
}
}
command = replaceCreateCommand(command, packageManager);

const result = await x(command, [], {
nodeOptions: {
Expand Down
59 changes: 59 additions & 0 deletions src/package-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
const CREATE_COMMAND_REPLACEMENTS: Record<string, string> = {
bun: 'bun create ',
nub: 'nub create ',
pnpm: 'pnpm create ',
yarn: 'yarn create ',
deno: 'deno run -A npm:create-',
};

function addNpmYesFlag(command: string) {
const separatorIndex = command.indexOf(' -- ');
const npmCommand =
separatorIndex === -1 ? command : command.slice(0, separatorIndex);

if (/(?:^|\s)(?:-y|--yes(?:=\S+)?)(?=\s|$)/.test(npmCommand)) {
return command;
}

return separatorIndex === -1
? `${command} -y`
: `${npmCommand} -y${command.slice(separatorIndex)}`;
}

export function replaceCreateCommand(command: string, packageManager: string) {
if (!command.startsWith('npm create ')) {
return command;
}

if (packageManager === 'npm') {
return addNpmYesFlag(command);
}

const replacement = CREATE_COMMAND_REPLACEMENTS[packageManager];
if (!replacement) {
return command;
}

const replacedCommand = command
.replace('npm create ', replacement)
// Other package managers don't need the extra `--`.
.replace(' -- --', ' --');

// Yarn v1 does not support the `@latest` tag.
return packageManager === 'yarn'
? replacedCommand.replace('@latest', '')
: replacedCommand;
}

export function getAgentCreateCommand(name: string, packageManager: string) {
if (packageManager === 'npm') {
return `npx -y create-${name}@latest`;
}

const npmCreateCommand = `npm create ${name}@latest`;
const createCommand = replaceCreateCommand(npmCreateCommand, packageManager);

return createCommand === npmCreateCommand
? `npx -y create-${name}@latest`
: createCommand;
}
46 changes: 46 additions & 0 deletions test/package-manager.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { expect, test } from 'rstack/test';
import {
getAgentCreateCommand,
replaceCreateCommand,
} from '../src/package-manager';

test.each([
['npm', 'npx -y create-test@latest'],
['pnpm', 'pnpm create test@latest'],
['yarn', 'yarn create test'],
['bun', 'bun create test@latest'],
['deno', 'deno run -A npm:create-test@latest'],
['unknown', 'npx -y create-test@latest'],
])('should get the agent create command for %s', (packageManager, expected) => {
expect(getAgentCreateCommand('test', packageManager)).toBe(expected);
});

test.each([
['npm', 'npm create test@latest -y -- --template vanilla'],
['pnpm', 'pnpm create test@latest --template vanilla'],
['yarn', 'yarn create test --template vanilla'],
['bun', 'bun create test@latest --template vanilla'],
['deno', 'deno run -A npm:create-test@latest --template vanilla'],
['unknown', 'npm create test@latest -- --template vanilla'],
])('should replace the create command for %s', (packageManager, expected) => {
expect(
replaceCreateCommand(
'npm create test@latest -- --template vanilla',
packageManager,
),
).toBe(expected);
});

test.each([
'npm create test@latest -y -- --template vanilla',
'npm create test@latest --yes -- --template vanilla',
'npm create test@latest --yes=false -- --template vanilla',
])('should preserve an existing npm yes flag in %s', (command) => {
expect(replaceCreateCommand(command, 'npm')).toBe(command);
});

test('should distinguish an npm flag from a scaffolder argument', () => {
expect(replaceCreateCommand('npm create test@latest -- --yes', 'npm')).toBe(
'npm create test@latest -y -- --yes',
);
});