From d8a30cd46ddfc469027ed804d8a45ef8ec988f3e Mon Sep 17 00:00:00 2001 From: NiftyAndy Date: Mon, 3 Aug 2026 19:55:38 -0400 Subject: [PATCH] fix: validate license policy before sync --- src/commands/sync.mjs | 15 +++++++++------ test/cli.test.mjs | 8 ++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/commands/sync.mjs b/src/commands/sync.mjs index a33d639..23aad3c 100644 --- a/src/commands/sync.mjs +++ b/src/commands/sync.mjs @@ -65,6 +65,15 @@ export function syncRepository(options) { if (!Object.keys(existingConfig).length && !options.init) throw new Error('Missing .github/code-foundry.yml; run init first.') const defaults = createDefaultConfig(target, source) let config = { ...defaults, ...existingConfig } + // Resolve and validate the license policy before any sync writes occur + // (including config/default additions) so an unsupported policy fails + // fast without leaving partially generated files behind. + const license = configured(config.license, existsSync(join(target, 'LICENSE')) ? 'preserve' : 'gpl-3.0-or-later') + const licenseFile = licenseFiles[license] + if (license !== 'preserve' && license !== 'none' && !licenseFile) { + const supported = [...Object.keys(licenseFiles), 'preserve', 'none'].join(', ') + throw new Error(`Unsupported license: ${license}; use ${supported}.`) + } if (!Object.keys(existingConfig).length) { writeOrReport(configPath, renderConfig(config), dryRun) } else { @@ -95,7 +104,6 @@ export function syncRepository(options) { if (includesValue(features, 'release') && releaseMergeStrategy !== 'rebase') { throw new Error(`Unsupported release_merge_strategy: ${releaseMergeStrategy || '(unset)'}; release automation requires rebase for Release Please version pull requests and never defaults to merge.`) } - const license = configured(config.license, existsSync(join(target, 'LICENSE')) ? 'preserve' : 'gpl-3.0-or-later') const changed = [] // Keep normal semver pins current during sync while preserving intentional @@ -179,11 +187,6 @@ export function syncRepository(options) { } if (license !== 'preserve' && license !== 'none') { - const licenseFile = licenseFiles[license] - if (!licenseFile) { - const supported = [...Object.keys(licenseFiles), 'preserve', 'none'].join(', ') - throw new Error(`Unsupported license: ${license}; use ${supported}.`) - } const sourceLicense = join(source, '.github/licenses', licenseFile) if (!existsSync(sourceLicense)) throw new Error(`License template missing: ${sourceLicense}`) const licenseContent = readFileSync(sourceLicense) diff --git a/test/cli.test.mjs b/test/cli.test.mjs index 840d3b2..1ce3627 100644 --- a/test/cli.test.mjs +++ b/test/cli.test.mjs @@ -541,6 +541,14 @@ describe('code-foundry CLI', () => { () => syncRepository({ target: root, source: process.cwd() }), /Unsupported license: bsd-3-clause; use gpl-3\.0-or-later, agpl-3\.0-or-later, apache-2\.0, mit, preserve, none\./ ) + // Validation happens before any writes: the config must not gain default + // additions and no generated baseline files may exist. + assert.equal( + readFileSync(join(root, '.github/code-foundry.yml'), 'utf8'), + 'languages: none\npackage_manager: none\nlicense: bsd-3-clause\n' + ) + assert.deepEqual(readdirSync(root).sort(), ['.github']) + assert.deepEqual(readdirSync(join(root, '.github')).sort(), ['code-foundry.yml']) }) it('rejects unsafe Rust CodeQL parallelism configuration', () => {