From 5371e978f58a38769e6195e837d64d5e43b32061 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 06:53:19 -0700 Subject: [PATCH 1/5] test(supply-chain): require supported dependency update automation --- src/dependencyUpdateAutomation.test.ts | 43 ++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/dependencyUpdateAutomation.test.ts diff --git a/src/dependencyUpdateAutomation.test.ts b/src/dependencyUpdateAutomation.test.ts new file mode 100644 index 00000000..a4e15865 --- /dev/null +++ b/src/dependencyUpdateAutomation.test.ts @@ -0,0 +1,43 @@ +import { readFileSync } from 'node:fs'; + +import { describe, expect, it } from 'vitest'; + +const CONFIG_PATH = '.github/dependabot.yml'; + +function normalizeYamlText(source: string): string { + return source.replaceAll('"', ''); +} + +function escapeRegExp(source: string): string { + return source.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&'); +} + +describe('dependency update automation', () => { + it('keeps supported Office and GitHub Actions dependencies on a bounded weekly update path', () => { + const config = normalizeYamlText(readFileSync(CONFIG_PATH, 'utf8')); + + expect(config).toMatch(/^version:\s*2\s*$/mu); + expect((config.match(/^-\s+package-ecosystem:/gmu) ?? []).length).toBe(2); + + for (const [ecosystem, directory] of [ + ['github-actions', '/'], + ['pip', '/office'], + ] as const) { + expect(config).toMatch( + new RegExp( + String.raw`^-\s+package-ecosystem:\s*${escapeRegExp(ecosystem)}\s*$[\s\S]*?^\s+directory:\s*${escapeRegExp(directory)}\s*$[\s\S]*?^\s+schedule:\s*$\n^\s+interval:\s*weekly\s*$`, + 'mu', + ), + ); + } + }); + + it('does not add unsupported pnpm 11 or credential-bearing registry configuration', () => { + const config = normalizeYamlText(readFileSync(CONFIG_PATH, 'utf8')); + + expect(config).not.toMatch(/package-ecosystem:\s*npm\b/u); + expect(config).not.toMatch(/^registries:/mu); + expect(config).not.toContain('${{ secrets.'); + expect(config).not.toMatch(/^\s+(?:username|password|token):/mu); + }); +}); From 366f0793867370a8c7dab31f8152a4d9a1a5348e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 07:00:46 -0700 Subject: [PATCH 2/5] test(supply-chain): make dependency automation RED executable --- src/dependencyUpdateAutomation.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dependencyUpdateAutomation.test.ts b/src/dependencyUpdateAutomation.test.ts index a4e15865..c5f9b72a 100644 --- a/src/dependencyUpdateAutomation.test.ts +++ b/src/dependencyUpdateAutomation.test.ts @@ -5,7 +5,7 @@ import { describe, expect, it } from 'vitest'; const CONFIG_PATH = '.github/dependabot.yml'; function normalizeYamlText(source: string): string { - return source.replaceAll('"', ''); + return source.replace(/"/gu, ''); } function escapeRegExp(source: string): string { From d67b316566bb6f5231cfa3ccc79cddaa3bbe1331 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 07:03:35 -0700 Subject: [PATCH 3/5] chore(supply-chain): enable supported dependency updates --- .github/dependabot.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..4f847204 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 + +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + + - package-ecosystem: pip + directory: /office + schedule: + interval: weekly From 642d4a40551d725adb0a5f49e9073506a9085c25 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 29 Aug 2026 07:07:29 -0700 Subject: [PATCH 4/5] test(supply-chain): match indented Dependabot entries --- src/dependencyUpdateAutomation.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dependencyUpdateAutomation.test.ts b/src/dependencyUpdateAutomation.test.ts index c5f9b72a..b806dede 100644 --- a/src/dependencyUpdateAutomation.test.ts +++ b/src/dependencyUpdateAutomation.test.ts @@ -17,7 +17,7 @@ describe('dependency update automation', () => { const config = normalizeYamlText(readFileSync(CONFIG_PATH, 'utf8')); expect(config).toMatch(/^version:\s*2\s*$/mu); - expect((config.match(/^-\s+package-ecosystem:/gmu) ?? []).length).toBe(2); + expect((config.match(/^\s*-\s+package-ecosystem:/gmu) ?? []).length).toBe(2); for (const [ecosystem, directory] of [ ['github-actions', '/'], @@ -25,7 +25,7 @@ describe('dependency update automation', () => { ] as const) { expect(config).toMatch( new RegExp( - String.raw`^-\s+package-ecosystem:\s*${escapeRegExp(ecosystem)}\s*$[\s\S]*?^\s+directory:\s*${escapeRegExp(directory)}\s*$[\s\S]*?^\s+schedule:\s*$\n^\s+interval:\s*weekly\s*$`, + String.raw`^\s*-\s+package-ecosystem:\s*${escapeRegExp(ecosystem)}\s*$[\s\S]*?^\s+directory:\s*${escapeRegExp(directory)}\s*$[\s\S]*?^\s+schedule:\s*$\n^\s+interval:\s*weekly\s*$`, 'mu', ), ); From aa287adab5d1d04fb4f3c1a001c707078eaa69cf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 15:54:01 +0900 Subject: [PATCH 5/5] test: isolate dependency update schedule assertions Keep each ecosystem assertion inside its own update entry. Negative controls reject monthly and daily Actions schedules even when the following Office update entry still declares a weekly schedule. Signed-off-by: Seongho Bae --- src/dependencyUpdateAutomation.test.ts | 40 ++++++++++++++++---------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/src/dependencyUpdateAutomation.test.ts b/src/dependencyUpdateAutomation.test.ts index b806dede..bbb9211b 100644 --- a/src/dependencyUpdateAutomation.test.ts +++ b/src/dependencyUpdateAutomation.test.ts @@ -12,24 +12,34 @@ function escapeRegExp(source: string): string { return source.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&'); } +function assertSupportedUpdateEntries(config: string): void { + expect(config).toMatch(/^version:\s*2\s*$/mu); + const updates = config.split(/(?=^[ \t]*-[ \t]+package-ecosystem:)/mu).slice(1); + expect(updates).toHaveLength(2); + + for (const [ecosystem, directory] of [ + ['github-actions', '/'], + ['pip', '/office'], + ] as const) { + expect(updates).toEqual(expect.arrayContaining([ + expect.stringMatching(new RegExp( + String.raw`^\s*-\s+package-ecosystem:\s*${escapeRegExp(ecosystem)}\s*$[\s\S]*?^\s+directory:\s*${escapeRegExp(directory)}\s*$[\s\S]*?^\s+schedule:\s*$\n^\s+interval:\s*weekly\s*$`, + 'mu', + )), + ])); + } +} + describe('dependency update automation', () => { it('keeps supported Office and GitHub Actions dependencies on a bounded weekly update path', () => { - const config = normalizeYamlText(readFileSync(CONFIG_PATH, 'utf8')); + assertSupportedUpdateEntries(normalizeYamlText(readFileSync(CONFIG_PATH, 'utf8'))); + }); + + it.each(['monthly', 'daily'])('rejects an Actions %s schedule even when Office remains weekly', (interval) => { + const config = normalizeYamlText(readFileSync(CONFIG_PATH, 'utf8')) + .replace('interval: weekly', `interval: ${interval}`); - expect(config).toMatch(/^version:\s*2\s*$/mu); - expect((config.match(/^\s*-\s+package-ecosystem:/gmu) ?? []).length).toBe(2); - - for (const [ecosystem, directory] of [ - ['github-actions', '/'], - ['pip', '/office'], - ] as const) { - expect(config).toMatch( - new RegExp( - String.raw`^\s*-\s+package-ecosystem:\s*${escapeRegExp(ecosystem)}\s*$[\s\S]*?^\s+directory:\s*${escapeRegExp(directory)}\s*$[\s\S]*?^\s+schedule:\s*$\n^\s+interval:\s*weekly\s*$`, - 'mu', - ), - ); - } + expect(() => assertSupportedUpdateEntries(config)).toThrow(); }); it('does not add unsupported pnpm 11 or credential-bearing registry configuration', () => {