From 1cd8b0b8e00dd65dc20758ed57d0d09a48ae5d8b Mon Sep 17 00:00:00 2001 From: Pheidon Date: Fri, 8 May 2026 20:48:19 +0000 Subject: [PATCH 1/2] Add Dependabot policy rendering --- project.bootstrap.yaml | 15 +++++++ src/archetypes.ts | 52 +++++++++++++++++++++++++ src/manifest.ts | 40 +++++++++++++++++++ src/types.ts | 16 ++++++++ tests/__snapshots__/render.test.ts.snap | 16 ++++++++ tests/render.test.ts | 26 +++++++++++++ 6 files changed, 165 insertions(+) diff --git a/project.bootstrap.yaml b/project.bootstrap.yaml index 3b67fc9..144fa7d 100644 --- a/project.bootstrap.yaml +++ b/project.bootstrap.yaml @@ -155,6 +155,21 @@ ci: - release-readiness nightlyCron: 0 7 * * * additionalWorkflows: [] + dependabot: + enabled: true + securityUpdates: true + versionUpdates: true + ecosystems: + - packageEcosystem: npm + directory: / + interval: weekly + groupMinorAndPatch: true + ignoreMajorUpdates: true + - packageEcosystem: github-actions + directory: / + interval: weekly + groupMinorAndPatch: false + ignoreMajorUpdates: true aiAttestation: enabled: true artifactName: ai-attestation diff --git a/src/archetypes.ts b/src/archetypes.ts index 27c00ba..48e73b7 100644 --- a/src/archetypes.ts +++ b/src/archetypes.ts @@ -1154,6 +1154,49 @@ function setupSteps(manifest: BootstrapManifest): string { return lines.join("\n"); } +function dependabotConfig(manifest: BootstrapManifest): string { + const updates = manifest.ci.dependabot.ecosystems + .map((ecosystem) => { + const lines = [ + ` - package-ecosystem: "${ecosystem.packageEcosystem}"`, + ` directory: "${ecosystem.directory}"`, + " schedule:", + ` interval: "${ecosystem.interval}"` + ]; + + if (ecosystem.groupMinorAndPatch) { + lines.push( + " groups:", + ` ${ecosystem.packageEcosystem.replace(/[^a-z0-9]+/g, "-")}-minor-patch:`, + " update-types:", + " - \"minor\"", + " - \"patch\"" + ); + } + + if (ecosystem.ignoreMajorUpdates) { + lines.push( + " ignore:", + " - dependency-name: \"*\"", + " update-types:", + " - \"version-update:semver-major\"" + ); + } + + return lines.join("\n"); + }) + .join("\n\n"); + + return dedent` + # Generated by OMT Bootstrap. Keep dependency policy in project.bootstrap.yaml. + # Dependabot alerts + security updates are managed through GitHub security settings; + # this file governs routine scheduled version update PRs. + version: 2 + updates: +${updates} + `; +} + function aiAttestationCallerWorkflow(manifest: BootstrapManifest): string { const config = manifest.ci.aiAttestation; const reusableWorkflow = @@ -1691,6 +1734,15 @@ export function renderManagedFiles(manifest: BootstrapManifest): RenderedFile[] reason: "Extended validation workflow", contents: `${extendedWorkflow(manifest)}\n` }, + ...(manifest.ci.dependabot.enabled && manifest.ci.dependabot.versionUpdates + ? [ + { + path: ".github/dependabot.yml", + reason: "Dependabot scheduled version update policy", + contents: `${dependabotConfig(manifest)}\n` + } + ] + : []), ...(manifest.release.enabled ? [ { diff --git a/src/manifest.ts b/src/manifest.ts index 547e369..a6926f4 100644 --- a/src/manifest.ts +++ b/src/manifest.ts @@ -5,6 +5,7 @@ import { z } from "zod"; import { readTextIfExists } from "./lib/fs.js"; import type { AdditionalWorkflowConfig, + DependabotConfig, BootstrapManifest, CodeownerRule, DefaultRepositoryPermission, @@ -114,6 +115,21 @@ const additionalWorkflowSchema = z.object({ purpose: z.string().min(1) }); +const dependabotEcosystemSchema = z.object({ + packageEcosystem: z.enum(["npm", "github-actions", "docker"]), + directory: z.string().min(1).optional(), + interval: z.enum(["daily", "weekly", "monthly"]).optional(), + groupMinorAndPatch: z.boolean().optional(), + ignoreMajorUpdates: z.boolean().optional() +}); + +const dependabotSchema = z.object({ + enabled: z.boolean().optional(), + securityUpdates: z.boolean().optional(), + versionUpdates: z.boolean().optional(), + ecosystems: z.array(dependabotEcosystemSchema).optional() +}); + const manifestSchema = z.object({ version: z.literal(1).optional(), project: z.object({ @@ -172,6 +188,7 @@ const manifestSchema = z.object({ extendedChecks: z.array(z.string()).optional(), nightlyCron: z.string().optional(), additionalWorkflows: z.array(additionalWorkflowSchema).optional(), + dependabot: dependabotSchema.optional(), aiAttestation: z .object({ enabled: z.boolean().optional(), @@ -335,6 +352,28 @@ function normalizeAdditionalWorkflows( })); } +function normalizeDependabot( + dependabot: z.input | undefined +): DependabotConfig { + const ecosystems = dependabot?.ecosystems ?? [ + { packageEcosystem: "npm" as const, directory: "/", interval: "weekly" as const }, + { packageEcosystem: "github-actions" as const, directory: "/", interval: "weekly" as const } + ]; + + return { + enabled: dependabot?.enabled ?? true, + securityUpdates: dependabot?.securityUpdates ?? true, + versionUpdates: dependabot?.versionUpdates ?? true, + ecosystems: ecosystems.map((ecosystem) => ({ + packageEcosystem: ecosystem.packageEcosystem, + directory: (ecosystem.directory ?? "/").replace(/\\/g, "/"), + interval: ecosystem.interval ?? "weekly", + groupMinorAndPatch: ecosystem.groupMinorAndPatch ?? ecosystem.packageEcosystem === "npm", + ignoreMajorUpdates: ecosystem.ignoreMajorUpdates ?? true + })) + }; +} + export function normalizeManifest(raw: z.input): BootstrapManifest { const parsed = manifestSchema.parse(raw); const reviewers = (parsed.github?.reviewers ?? []).map((reviewer) => reviewer.replace(/^@/, "")); @@ -406,6 +445,7 @@ export function normalizeManifest(raw: z.input): Bootstra extendedChecks: parsed.ci?.extendedChecks ?? ["integration", "release-readiness"], nightlyCron: parsed.ci?.nightlyCron ?? "0 7 * * *", additionalWorkflows: normalizeAdditionalWorkflows(parsed.ci?.additionalWorkflows), + dependabot: normalizeDependabot(parsed.ci?.dependabot), aiAttestation: { enabled: parsed.ci?.aiAttestation?.enabled ?? false, artifactName: parsed.ci?.aiAttestation?.artifactName ?? "ai-attestation", diff --git a/src/types.ts b/src/types.ts index 294d795..d7b5ad2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -30,6 +30,21 @@ export interface AdditionalWorkflowConfig { purpose: string; } +export interface DependabotEcosystemConfig { + packageEcosystem: "npm" | "github-actions" | "docker"; + directory: string; + interval: "daily" | "weekly" | "monthly"; + groupMinorAndPatch: boolean; + ignoreMajorUpdates: boolean; +} + +export interface DependabotConfig { + enabled: boolean; + securityUpdates: boolean; + versionUpdates: boolean; + ecosystems: DependabotEcosystemConfig[]; +} + export interface OrganizationSecurityDefaults { dependabotAlerts: boolean; dependabotSecurityUpdates: boolean; @@ -97,6 +112,7 @@ export interface BootstrapManifest { extendedChecks: string[]; nightlyCron: string; additionalWorkflows: AdditionalWorkflowConfig[]; + dependabot: DependabotConfig; aiAttestation: { enabled: boolean; artifactName: string; diff --git a/tests/__snapshots__/render.test.ts.snap b/tests/__snapshots__/render.test.ts.snap index d82f17e..f2c005e 100644 --- a/tests/__snapshots__/render.test.ts.snap +++ b/tests/__snapshots__/render.test.ts.snap @@ -46,6 +46,10 @@ exports[`renderManagedFiles > renders a stable managed file set for generic-empt "executable": false, "path": ".github/workflows/extended-validation.yml", }, + { + "executable": false, + "path": ".github/dependabot.yml", + }, { "executable": false, "path": ".github/workflows/release-tag.yml", @@ -143,6 +147,10 @@ exports[`renderManagedFiles > renders a stable managed file set for nextjs-web 1 "executable": false, "path": ".github/workflows/extended-validation.yml", }, + { + "executable": false, + "path": ".github/dependabot.yml", + }, { "executable": false, "path": ".github/workflows/release-tag.yml", @@ -248,6 +256,10 @@ exports[`renderManagedFiles > renders a stable managed file set for node-ts-serv "executable": false, "path": ".github/workflows/extended-validation.yml", }, + { + "executable": false, + "path": ".github/dependabot.yml", + }, { "executable": false, "path": ".github/workflows/release-tag.yml", @@ -345,6 +357,10 @@ exports[`renderManagedFiles > renders a stable managed file set for python-servi "executable": false, "path": ".github/workflows/extended-validation.yml", }, + { + "executable": false, + "path": ".github/dependabot.yml", + }, { "executable": false, "path": ".github/workflows/release-tag.yml", diff --git a/tests/render.test.ts b/tests/render.test.ts index 33a236e..f3a74d3 100644 --- a/tests/render.test.ts +++ b/tests/render.test.ts @@ -37,12 +37,17 @@ describe("renderManagedFiles", () => { expect(prWorkflow?.contents).toContain("PR body must close/link an issue"); const prTemplate = files.find((file) => file.path === ".github/PULL_REQUEST_TEMPLATE.md"); + const dependabot = files.find((file) => file.path === ".github/dependabot.yml"); expect(prTemplate?.contents).toContain("## Summary"); expect(prTemplate?.contents).toContain("## Governing Issue"); expect(prTemplate?.contents).toContain("## Validation"); expect(prTemplate?.contents).toContain("## Bootstrap Governance"); expect(prTemplate?.contents).toContain("fallback merge-readiness policy applies"); expect(prTemplate?.contents).toContain("## Notes"); + expect(dependabot?.contents).toContain('package-ecosystem: "npm"'); + expect(dependabot?.contents).toContain('package-ecosystem: "github-actions"'); + expect(dependabot?.contents).toContain("npm-minor-patch"); + expect(dependabot?.contents).toContain("version-update:semver-major"); expect(files.some((file) => file.path === "CLAUDE.md")).toBe(false); expect(files.some((file) => file.path === ".github/workflows/claude.yml")).toBe(false); @@ -56,6 +61,27 @@ describe("renderManagedFiles", () => { }); } + it("can disable Dependabot version update rendering", () => { + const manifest = normalizeManifest({ + project: { + name: "quiet-deps", + owner: "acme" + }, + archetype: { + kind: "generic-empty" + }, + ci: { + dependabot: { + versionUpdates: false + } + } + }); + + const files = renderManagedFiles(manifest); + expect(files.some((file) => file.path === ".github/dependabot.yml")).toBe(false); + expect(manifest.ci.dependabot.securityUpdates).toBe(true); + }); + it("uses the primary required status check name in the generated PR workflow", () => { const manifest = normalizeManifest({ project: { From 96dd69df5c43d116a3721fea010f27975fc8f097 Mon Sep 17 00:00:00 2001 From: Pheidon Date: Fri, 8 May 2026 20:51:46 +0000 Subject: [PATCH 2/2] Refresh PR validation after body update