diff --git a/__tests__/integration/dependabot.test.js b/__tests__/integration/dependabot.test.js index f30c9b1..5802cb3 100644 --- a/__tests__/integration/dependabot.test.js +++ b/__tests__/integration/dependabot.test.js @@ -80,7 +80,7 @@ describe('dependabot', () => { 'owner', 'repo', '.github/dependabot.yml', - yaml.dump(depConfig), + yaml.dump(depConfig, { noRefs: true }), 'Add/Update Dependabot configuration' ); expect(createConfigurationPR).not.toHaveBeenCalled(); @@ -96,12 +96,35 @@ describe('dependabot', () => { 'owner', 'repo', '.github/dependabot.yml', - yaml.dump(depConfig), + yaml.dump(depConfig, { noRefs: true }), 'Add/Update Dependabot configuration' ); expect(createConfigurationPR).not.toHaveBeenCalled(); }); + it('emits dependabot.yml without YAML anchors even when labels are shared across updates', async () => { + // Regression: js-yaml's default dump deduplicates repeated structures with + // anchors (&ref_0 / *ref_0). GitHub's dependabot.yml parser rejects + // anchors. The fix is yaml.dump(cfg, { noRefs: true }). + const sharedLabels = ['dependencies']; + const sharedConfig = { + version: 2, + updates: [ + { 'package-ecosystem': 'npm', directory: '/', schedule: { interval: 'weekly' }, labels: sharedLabels }, + { 'package-ecosystem': 'docker', directory: '/', schedule: { interval: 'weekly' }, labels: sharedLabels } + ] + }; + _setConfigForTesting({}); + + await applyDependabotConfig(octokit, 'owner', 'repo', sharedConfig); + + const dumpedContent = upsertRepoFile.mock.calls[0][4]; + expect(dumpedContent).not.toMatch(/&ref_/); + expect(dumpedContent).not.toMatch(/\*ref_/); + // Both updates should still carry the labels inline + expect(dumpedContent.match(/labels:/g) || []).toHaveLength(2); + }); + it('applies config via PR when use_pull_requests is true', async () => { _setConfigForTesting({ change_strategy: { use_pull_requests: true } @@ -114,7 +137,7 @@ describe('dependabot', () => { 'owner', 'repo', '.github/dependabot.yml', - yaml.dump(depConfig), + yaml.dump(depConfig, { noRefs: true }), 'Update Dependabot configuration' ); expect(upsertRepoFile).not.toHaveBeenCalled(); diff --git a/src/app.js b/src/app.js index 0024f76..7d9ef92 100644 --- a/src/app.js +++ b/src/app.js @@ -462,7 +462,7 @@ function registerApp(app, options = {}) { const yaml = (await import('js-yaml')).default; let body = `## Generated Dependabot Configuration for ${owner}/${repo}\n\n`; body += `${result.report}\n\n`; - body += '```yaml\n' + yaml.dump(result.config) + '```\n\n'; + body += '```yaml\n' + yaml.dump(result.config, { noRefs: true }) + '```\n\n'; body += 'Applying configuration...'; await context.octokit.issues.createComment({ diff --git a/src/dependabot.js b/src/dependabot.js index 5ca763c..88174b4 100644 --- a/src/dependabot.js +++ b/src/dependabot.js @@ -23,7 +23,10 @@ async function applyDependabotConfig(octokit, owner, repo, dependabotConfig) { getLogger().info(`Applying Dependabot configuration to ${owner}/${repo}`); const usePR = config.change_strategy?.use_pull_requests || false; - const yamlContent = yaml.dump(dependabotConfig); + // noRefs: true inlines repeated structures (e.g. shared `labels` arrays) + // instead of emitting YAML anchors (`&ref_0` / `*ref_0`) that GitHub's + // dependabot.yml parser rejects. + const yamlContent = yaml.dump(dependabotConfig, { noRefs: true }); if (usePR) { await createConfigurationPR(