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
23 changes: 15 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,21 @@ Omit the option to enable every built-in tool. Pass an array such as

### Package Manager Configuration

When a local template contains `pnpm-workspace.yaml`, the file is only copied
if the project is created with pnpm. Templates loaded from third-party npm
packages are copied without this filtering.

The toolkit automatically passes the resolved `skipFiles` list to custom
`extraTools` actions. When an action uses `copyFolder` to copy a local tool
template, forward the received list so the same package-manager filtering is
applied:
Use `getPackageManager` to specify a package manager for a template:

```ts
create({
getPackageManager: ({ templateName }) =>
templateName === 'turborepo' ? 'pnpm' : undefined,
// ...other options
});
```

Return `undefined` to use the package manager detected from the user agent, falling back to npm.

When a local template contains `pnpm-workspace.yaml`, the file is only copied if the resolved package manager is pnpm. Templates loaded from third-party npm packages are copied without this filtering.

The toolkit automatically passes the resolved `skipFiles` list to custom `extraTools` actions. When an action uses `copyFolder` to copy a local tool template, forward the received list so the same package-manager filtering is applied:

```ts
import { copyFolder, create } from '@rstackjs/create-toolkit';
Expand Down
17 changes: 14 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,7 @@ type ExtraTool = {
*/
action?: (context: {
templateName: string;
packageManager: string;
distFolder: string;
skipFiles: string[];
addAgentsMdSearchDirs: (dir: string) => void;
Expand Down Expand Up @@ -678,6 +679,7 @@ export async function create({
templates,
skipFiles,
getTemplateName,
getPackageManager,
mapESLintTemplate,
mapRslintTemplate,
version,
Expand All @@ -694,6 +696,12 @@ export async function create({
skipFiles?: string[];
templates: string[];
getTemplateName: (argv: Argv) => Promise<string>;
/**
* Specify the package manager for the selected template.
* Return undefined to use the package manager detected from the user agent,
* falling back to npm when no user agent is available.
*/
getPackageManager?: (context: { templateName: string }) => string | undefined;
/**
* Map the template name to the ESLint template name.
* If not provided, defaults to 'vanilla-ts' for all templates.
Expand Down Expand Up @@ -754,14 +762,13 @@ export async function create({
logger.greet(`\n◆ Create ${upperFirst(name)} Project`);

const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent);
const packageManager = pkgInfo ? pkgInfo.name : 'npm';
const templateParameters = { packageManager };
const detectedPackageManager = pkgInfo ? pkgInfo.name : 'npm';

const { isAgent } = await determineAgent();
if (isAgent) {
console.log('');
logger.info(
`To create a project non-interactively, run: ${getAgentCreateCommand(name, packageManager)} <DIR> --template <TEMPLATE>`,
`To create a project non-interactively, run: ${getAgentCreateCommand(name, detectedPackageManager)} <DIR> --template <TEMPLATE>`,
);
}

Expand Down Expand Up @@ -813,6 +820,9 @@ export async function create({
}

const templateName = await getTemplateName(argv);
const packageManager =
getPackageManager?.({ templateName }) ?? detectedPackageManager;
const templateParameters = { packageManager };

const srcFolder = path.join(root, `template-${templateName}`);

Expand Down Expand Up @@ -932,6 +942,7 @@ export async function create({
if (matchedTool.action) {
await matchedTool.action({
templateName,
packageManager,
distFolder,
skipFiles: [...localSkipFiles],
addAgentsMdSearchDirs: (dir: string) =>
Expand Down
30 changes: 29 additions & 1 deletion test/package-manager-files.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,16 @@ beforeEach(() => {
};
});

async function createProject(projectDir: string) {
async function createProject(
projectDir: string,
getPackageManager?: Parameters<typeof create>[0]['getPackageManager'],
) {
await create({
name: 'test',
root: fixturesDir,
templates: ['vanilla'],
getTemplateName: async () => 'vanilla',
getPackageManager,
git: false,
builtinTools: [],
argv: ['node', 'test', '--dir', projectDir, '--template', 'vanilla'],
Expand Down Expand Up @@ -94,6 +98,30 @@ test('should skip pnpm-workspace.yaml for other package managers', async () => {
);
});

test('should override the detected package manager for a template', async () => {
const projectDir = path.join(testDir, 'override');
rs.stubEnv('npm_config_user_agent', 'npm/11.0.0');

await createProject(projectDir, ({ templateName }) =>
templateName === 'vanilla' ? 'pnpm' : undefined,
);

expect(fs.existsSync(path.join(projectDir, 'pnpm-workspace.yaml'))).toBe(
true,
);
});

test('should keep the detected package manager when returning undefined', async () => {
const projectDir = path.join(testDir, 'fallback');
rs.stubEnv('npm_config_user_agent', 'npm/11.0.0');

await createProject(projectDir, () => undefined);

expect(fs.existsSync(path.join(projectDir, 'pnpm-workspace.yaml'))).toBe(
false,
);
});

test('should copy pnpm-workspace.yaml from an extra tool for pnpm', async () => {
const projectDir = path.join(testDir, 'extra-tool-pnpm');
rs.stubEnv('npm_config_user_agent', 'pnpm/11.20.0');
Expand Down