diff --git a/scripts/gen-llms-txt.ts b/scripts/gen-llms-txt.ts index 02e55d12d..3295f36ff 100644 --- a/scripts/gen-llms-txt.ts +++ b/scripts/gen-llms-txt.ts @@ -19,6 +19,7 @@ import * as fs from 'fs'; import * as path from 'path'; +import { mkdirpSync } from '../lib/fs-utils'; import { discoverTemplates } from './discover-skills'; import { COMMAND_DESCRIPTIONS as BROWSE_COMMANDS } from '../browse/src/commands'; @@ -224,7 +225,7 @@ export async function generateLlmsTxt(opts: GenerateOptions = {}): Promise { const result = await generateLlmsTxt(opts); const outputPath = opts.outputPath ?? OUTPUT; - fs.mkdirSync(path.dirname(outputPath), { recursive: true }); + mkdirpSync(path.dirname(outputPath)); fs.writeFileSync(outputPath, result.content, { encoding: 'utf-8' }); return result; } diff --git a/scripts/gen-skill-docs.ts b/scripts/gen-skill-docs.ts index 57e0e82ed..f2628367d 100644 --- a/scripts/gen-skill-docs.ts +++ b/scripts/gen-skill-docs.ts @@ -29,6 +29,7 @@ import type { HostConfig } from './host-config'; const ROOT = path.resolve(import.meta.dir, '..'); import { ALL_MODEL_NAMES, resolveModel, type Model } from './models'; import { resolveStateRoot } from '../lib/state-root'; +import { mkdirpSync } from '../lib/fs-utils'; type HostArg = Host | 'all'; @@ -967,7 +968,7 @@ export async function runGeneration(settings: GenerationOptions = {}): Promise fs.rmSync(tmp, { recursive: true, force: true })); + +const bun = (...args: string[]) => spawnSync(process.execPath, ['--preload', PRELOAD, ...args], { cwd: ROOT, encoding: 'utf8', timeout: 120_000 }); + +describe('#2329: generators tolerate existing output directories under Windows EEXIST semantics', () => { + test('gen-skill-docs renders twice into the same out-dir', () => { + const out = path.join(tmp, 'render'); + for (let run = 1; run <= 2; run++) { + const r = bun('scripts/gen-skill-docs.ts', '--host', 'codex', '--out-dir', out); + expect(`run ${run}: exit ${r.status}`, r.stderr).toBe(`run ${run}: exit 0`); + expect(r.stderr).not.toContain('EEXIST'); + } + expect(fs.existsSync(path.join(out, '.agents', 'skills', 'gstack-review', 'SKILL.md'))).toBe(true); + }); + + test('writeLlmsTxt writes into an existing directory', () => { + const target = path.join(tmp, 'llms', 'llms.txt'); + fs.mkdirSync(path.dirname(target), { recursive: true }); + const r = bun('-e', `const { writeLlmsTxt } = await import(${JSON.stringify(path.join(ROOT, 'scripts/gen-llms-txt.ts'))}); await writeLlmsTxt({ outputPath: ${JSON.stringify(target)} });`); + expect(r.status, r.stderr).toBe(0); + expect(fs.readFileSync(target, 'utf8')).toContain('gstack'); + }); +});