From 7b5d436000d632040e4b02208ca5847e9004c1f8 Mon Sep 17 00:00:00 2001 From: neverland Date: Sun, 18 Oct 2026 10:36:51 +0800 Subject: [PATCH 1/4] fix: reject unsupported custom template options --- README.md | 2 ++ src/index.ts | 14 ++++++++++++++ test/custom-templates.test.ts | 16 ++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/README.md b/README.md index 1ee2483..cbb942c 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,8 @@ create({ The callback replaces the built-in project setup, including file generation, tools, skills, Git initialization, and next-step messages. It may return a promise; errors propagate to the caller. String templates keep their existing behavior. +Custom templates reject `--tools`, `--skill`, and `--template-version`. Configure these through your custom setup instead. Project paths, `--package-name`, `--no-git`, and `--override` are passed to the callback through its context. + ### NPM Template Support `@rstackjs/create-toolkit` supports using npm packages as templates, allowing users to create projects from custom templates published to npm. diff --git a/src/index.ts b/src/index.ts index e4c7da1..444be91 100644 --- a/src/index.ts +++ b/src/index.ts @@ -846,6 +846,20 @@ export async function create({ typeof template !== 'string' && template.name === templateName, ); if (typeof customTemplate === 'object') { + const unsupportedOptions = Object.entries({ + '--tools': argv.tools, + '--skill': argv.skill, + '--template-version': argv.templateVersion ?? argv['template-version'], + }) + .filter(([, value]) => value !== undefined) + .map(([option]) => option); + + if (unsupportedOptions.length) { + throw new Error( + `Template "${templateName}" does not support ${unsupportedOptions.join(', ')}. Remove the unsupported options and use the template's setup flow instead.`, + ); + } + await customTemplate.create({ distFolder, packageName: diff --git a/test/custom-templates.test.ts b/test/custom-templates.test.ts index 614a11d..8c658c9 100644 --- a/test/custom-templates.test.ts +++ b/test/custom-templates.test.ts @@ -81,3 +81,19 @@ test('should propagate errors from a custom template', async () => { await expect(create(options)).rejects.toBe(error); }); + +test.each([ + { + args: ['--tools', '', '--skill', 'example-skill', '--template-version', ''], + unsupported: '--tools, --skill, --template-version', + }, + { args: ['--templateVersion', '1.0.0'], unsupported: '--template-version' }, +])( + 'should reject unsupported options: $args', + async ({ args, unsupported }) => { + await expect( + create({ ...options, argv: [...options.argv, ...args] }), + ).rejects.toThrow(`Template "external" does not support ${unsupported}.`); + expect(createTemplate).not.toHaveBeenCalled(); + }, +); From 05d714b03c56b4d7943f2514ec79b325029a7050 Mon Sep 17 00:00:00 2001 From: neverland Date: Sun, 18 Oct 2026 16:59:35 +0800 Subject: [PATCH 2/4] refactor: clarify unsupported option checks --- src/index.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/index.ts b/src/index.ts index 444be91..4c57979 100644 --- a/src/index.ts +++ b/src/index.ts @@ -846,13 +846,17 @@ export async function create({ typeof template !== 'string' && template.name === templateName, ); if (typeof customTemplate === 'object') { - const unsupportedOptions = Object.entries({ - '--tools': argv.tools, - '--skill': argv.skill, - '--template-version': argv.templateVersion ?? argv['template-version'], - }) - .filter(([, value]) => value !== undefined) - .map(([option]) => option); + const unsupportedOptions: string[] = []; + if (argv.tools !== undefined) { + unsupportedOptions.push('--tools'); + } + if (argv.skill !== undefined) { + unsupportedOptions.push('--skill'); + } + const templateVersion = argv.templateVersion ?? argv['template-version']; + if (templateVersion !== undefined) { + unsupportedOptions.push('--template-version'); + } if (unsupportedOptions.length) { throw new Error( From b2bfc248fd0e307900e30937993c445dc71c11a4 Mon Sep 17 00:00:00 2001 From: neverland Date: Sun, 18 Oct 2026 17:01:02 +0800 Subject: [PATCH 3/4] refactor: extract custom template option validation --- src/index.ts | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/index.ts b/src/index.ts index 4c57979..c240ee2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -470,6 +470,26 @@ const parseArgv = (processArgv: string[]) => { return argv; }; +function validateCustomTemplateOptions(argv: Argv, templateName: string) { + const unsupportedOptions: string[] = []; + if (argv.tools !== undefined) { + unsupportedOptions.push('--tools'); + } + if (argv.skill !== undefined) { + unsupportedOptions.push('--skill'); + } + const templateVersion = argv.templateVersion ?? argv['template-version']; + if (templateVersion !== undefined) { + unsupportedOptions.push('--template-version'); + } + + if (unsupportedOptions.length) { + throw new Error( + `Template "${templateName}" does not support ${unsupportedOptions.join(', ')}. Remove the unsupported options and use the template's setup flow instead.`, + ); + } +} + type ExtraTool = { /** * The value of the multiselect option. @@ -846,23 +866,7 @@ export async function create({ typeof template !== 'string' && template.name === templateName, ); if (typeof customTemplate === 'object') { - const unsupportedOptions: string[] = []; - if (argv.tools !== undefined) { - unsupportedOptions.push('--tools'); - } - if (argv.skill !== undefined) { - unsupportedOptions.push('--skill'); - } - const templateVersion = argv.templateVersion ?? argv['template-version']; - if (templateVersion !== undefined) { - unsupportedOptions.push('--template-version'); - } - - if (unsupportedOptions.length) { - throw new Error( - `Template "${templateName}" does not support ${unsupportedOptions.join(', ')}. Remove the unsupported options and use the template's setup flow instead.`, - ); - } + validateCustomTemplateOptions(argv, templateName); await customTemplate.create({ distFolder, From a43adac55110adfa41b65f896cb48aa7256c54dc Mon Sep 17 00:00:00 2001 From: neverland Date: Sun, 18 Oct 2026 17:28:55 +0800 Subject: [PATCH 4/4] docs: drop custom template option documentation --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index cbb942c..1ee2483 100644 --- a/README.md +++ b/README.md @@ -138,8 +138,6 @@ create({ The callback replaces the built-in project setup, including file generation, tools, skills, Git initialization, and next-step messages. It may return a promise; errors propagate to the caller. String templates keep their existing behavior. -Custom templates reject `--tools`, `--skill`, and `--template-version`. Configure these through your custom setup instead. Project paths, `--package-name`, `--no-git`, and `--override` are passed to the callback through its context. - ### NPM Template Support `@rstackjs/create-toolkit` supports using npm packages as templates, allowing users to create projects from custom templates published to npm.