Skip to content

Commit 601be00

Browse files
authored
feat: support template-specific package managers (#177)
1 parent 5dd462f commit 601be00

3 files changed

Lines changed: 58 additions & 12 deletions

File tree

‎README.md‎

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,21 @@ Omit the option to enable every built-in tool. Pass an array such as
4444

4545
### Package Manager Configuration
4646

47-
When a local template contains `pnpm-workspace.yaml`, the file is only copied
48-
if the project is created with pnpm. Templates loaded from third-party npm
49-
packages are copied without this filtering.
50-
51-
The toolkit automatically passes the resolved `skipFiles` list to custom
52-
`extraTools` actions. When an action uses `copyFolder` to copy a local tool
53-
template, forward the received list so the same package-manager filtering is
54-
applied:
47+
Use `getPackageManager` to specify a package manager for a template:
48+
49+
```ts
50+
create({
51+
getPackageManager: ({ templateName }) =>
52+
templateName === 'turborepo' ? 'pnpm' : undefined,
53+
// ...other options
54+
});
55+
```
56+
57+
Return `undefined` to use the package manager detected from the user agent, falling back to npm.
58+
59+
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.
60+
61+
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:
5562

5663
```ts
5764
import { copyFolder, create } from '@rstackjs/create-toolkit';

‎src/index.ts‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,7 @@ type ExtraTool = {
463463
*/
464464
action?: (context: {
465465
templateName: string;
466+
packageManager: string;
466467
distFolder: string;
467468
skipFiles: string[];
468469
addAgentsMdSearchDirs: (dir: string) => void;
@@ -678,6 +679,7 @@ export async function create({
678679
templates,
679680
skipFiles,
680681
getTemplateName,
682+
getPackageManager,
681683
mapESLintTemplate,
682684
mapRslintTemplate,
683685
version,
@@ -694,6 +696,12 @@ export async function create({
694696
skipFiles?: string[];
695697
templates: string[];
696698
getTemplateName: (argv: Argv) => Promise<string>;
699+
/**
700+
* Specify the package manager for the selected template.
701+
* Return undefined to use the package manager detected from the user agent,
702+
* falling back to npm when no user agent is available.
703+
*/
704+
getPackageManager?: (context: { templateName: string }) => string | undefined;
697705
/**
698706
* Map the template name to the ESLint template name.
699707
* If not provided, defaults to 'vanilla-ts' for all templates.
@@ -754,14 +762,13 @@ export async function create({
754762
logger.greet(`\n◆ Create ${upperFirst(name)} Project`);
755763

756764
const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent);
757-
const packageManager = pkgInfo ? pkgInfo.name : 'npm';
758-
const templateParameters = { packageManager };
765+
const detectedPackageManager = pkgInfo ? pkgInfo.name : 'npm';
759766

760767
const { isAgent } = await determineAgent();
761768
if (isAgent) {
762769
console.log('');
763770
logger.info(
764-
`To create a project non-interactively, run: ${getAgentCreateCommand(name, packageManager)} <DIR> --template <TEMPLATE>`,
771+
`To create a project non-interactively, run: ${getAgentCreateCommand(name, detectedPackageManager)} <DIR> --template <TEMPLATE>`,
765772
);
766773
}
767774

@@ -813,6 +820,9 @@ export async function create({
813820
}
814821

815822
const templateName = await getTemplateName(argv);
823+
const packageManager =
824+
getPackageManager?.({ templateName }) ?? detectedPackageManager;
825+
const templateParameters = { packageManager };
816826

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

@@ -932,6 +942,7 @@ export async function create({
932942
if (matchedTool.action) {
933943
await matchedTool.action({
934944
templateName,
945+
packageManager,
935946
distFolder,
936947
skipFiles: [...localSkipFiles],
937948
addAgentsMdSearchDirs: (dir: string) =>

‎test/package-manager-files.test.ts‎

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,16 @@ beforeEach(() => {
2525
};
2626
});
2727

28-
async function createProject(projectDir: string) {
28+
async function createProject(
29+
projectDir: string,
30+
getPackageManager?: Parameters<typeof create>[0]['getPackageManager'],
31+
) {
2932
await create({
3033
name: 'test',
3134
root: fixturesDir,
3235
templates: ['vanilla'],
3336
getTemplateName: async () => 'vanilla',
37+
getPackageManager,
3438
git: false,
3539
builtinTools: [],
3640
argv: ['node', 'test', '--dir', projectDir, '--template', 'vanilla'],
@@ -94,6 +98,30 @@ test('should skip pnpm-workspace.yaml for other package managers', async () => {
9498
);
9599
});
96100

101+
test('should override the detected package manager for a template', async () => {
102+
const projectDir = path.join(testDir, 'override');
103+
rs.stubEnv('npm_config_user_agent', 'npm/11.0.0');
104+
105+
await createProject(projectDir, ({ templateName }) =>
106+
templateName === 'vanilla' ? 'pnpm' : undefined,
107+
);
108+
109+
expect(fs.existsSync(path.join(projectDir, 'pnpm-workspace.yaml'))).toBe(
110+
true,
111+
);
112+
});
113+
114+
test('should keep the detected package manager when returning undefined', async () => {
115+
const projectDir = path.join(testDir, 'fallback');
116+
rs.stubEnv('npm_config_user_agent', 'npm/11.0.0');
117+
118+
await createProject(projectDir, () => undefined);
119+
120+
expect(fs.existsSync(path.join(projectDir, 'pnpm-workspace.yaml'))).toBe(
121+
false,
122+
);
123+
});
124+
97125
test('should copy pnpm-workspace.yaml from an extra tool for pnpm', async () => {
98126
const projectDir = path.join(testDir, 'extra-tool-pnpm');
99127
rs.stubEnv('npm_config_user_agent', 'pnpm/11.20.0');

0 commit comments

Comments
 (0)