From db1803b6d7857351893df41fdd5f36f42ed137ae Mon Sep 17 00:00:00 2001 From: Yiming Li Date: Thu, 10 Sep 2026 22:51:37 +0800 Subject: [PATCH] fix: keep both scripts when two built-in tools write the same one ESLint and Rslint both write a lint script, and Biome and Prettier both write a format script. The tool applied later replaced the earlier one, so its config was installed but never run. Chain the two commands instead. --- src/index.ts | 36 +++++++++++++++++++++++++ test/tool-scripts.test.ts | 55 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 test/tool-scripts.test.ts diff --git a/src/index.ts b/src/index.ts index e226d7c..07260f5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -932,6 +932,8 @@ export async function create({ const packageRoot = path.resolve(__dirname, '..'); const agentsMdSearchDirs = [commonFolder, srcFolder]; + const builtinToolScripts = new Map(); + for (const tool of tools) { // Handle extra tools first if (extraTools) { @@ -980,6 +982,7 @@ export async function create({ isMergePackageJson: true, }); + keepBuiltinToolScripts(distFolder, subFolder, builtinToolScripts); agentsMdSearchDirs.push(toolFolder); agentsMdSearchDirs.push(subFolder); continue; @@ -1004,6 +1007,7 @@ export async function create({ isMergePackageJson: true, }); + keepBuiltinToolScripts(distFolder, subFolder, builtinToolScripts); agentsMdSearchDirs.push(toolFolder); agentsMdSearchDirs.push(subFolder); continue; @@ -1018,6 +1022,7 @@ export async function create({ isMergePackageJson: true, }); + keepBuiltinToolScripts(distFolder, toolFolder, builtinToolScripts); agentsMdSearchDirs.push(toolFolder); if (tool === 'biome') { @@ -1057,6 +1062,37 @@ function sortObjectKeys(obj: Record) { * @param targetPackage Path to the base package.json file * @param extraPackage Path to the extra package.json file to merge */ +function keepBuiltinToolScripts( + distFolder: string, + toolFolder: string, + builtinToolScripts: Map, +) { + const toolPackage = path.join(toolFolder, 'package.json'); + const targetPackage = path.join(distFolder, 'package.json'); + if (!fs.existsSync(toolPackage) || !fs.existsSync(targetPackage)) { + return; + } + + const toolScripts: Record = + JSON.parse(fs.readFileSync(toolPackage, 'utf-8')).scripts ?? {}; + const targetJson = JSON.parse(fs.readFileSync(targetPackage, 'utf-8')); + let changed = false; + + for (const name of Object.keys(toolScripts)) { + const previous = builtinToolScripts.get(name); + const command: string = targetJson.scripts[name]; + if (previous !== undefined && previous !== command) { + targetJson.scripts[name] = `${previous} && ${command}`; + changed = true; + } + builtinToolScripts.set(name, targetJson.scripts[name]); + } + + if (changed) { + fs.writeFileSync(targetPackage, `${JSON.stringify(targetJson, null, 2)}\n`); + } +} + export function mergePackageJson(targetPackage: string, extraPackage: string) { if (!fs.existsSync(targetPackage)) { return; diff --git a/test/tool-scripts.test.ts b/test/tool-scripts.test.ts new file mode 100644 index 0000000..2497bed --- /dev/null +++ b/test/tool-scripts.test.ts @@ -0,0 +1,55 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { beforeEach, expect, test } from 'rstack/test'; +import { create } from '../src'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const fixturesDir = path.join(__dirname, 'fixtures', 'agents-md'); +const testDir = path.join(fixturesDir, 'test-temp-output-tool-scripts'); + +beforeEach(() => { + fs.rmSync(testDir, { recursive: true, force: true }); + fs.mkdirSync(testDir, { recursive: true }); + return () => fs.rmSync(testDir, { recursive: true, force: true }); +}); + +async function createWithTools(name: string, tools: string) { + const projectDir = path.join(testDir, name); + + await create({ + name: 'test', + root: fixturesDir, + templates: ['vanilla'], + getTemplateName: async () => 'vanilla', + argv: [ + 'node', + 'test', + '--dir', + projectDir, + '--template', + 'vanilla', + '--tools', + tools, + ], + }); + + return JSON.parse( + fs.readFileSync(path.join(projectDir, 'package.json'), 'utf-8'), + ).scripts; +} + +test('should run both linters when ESLint and Rslint are selected', async () => { + const scripts = await createWithTools('eslint-rslint', 'eslint,rslint'); + expect(scripts.lint).toBe('eslint . && rslint'); +}); + +test('should run both formatters when Biome and Prettier are selected', async () => { + const scripts = await createWithTools('biome-prettier', 'biome,prettier'); + expect(scripts.format).toBe('biome format --write && prettier --write .'); +}); + +test('should keep a single tool script unchanged', async () => { + const scripts = await createWithTools('eslint', 'eslint'); + expect(scripts.lint).toBe('eslint .'); +});