diff --git a/src/selfTest.ts b/src/selfTest.ts index 91f982f..ce4b2f3 100644 --- a/src/selfTest.ts +++ b/src/selfTest.ts @@ -470,29 +470,70 @@ export async function runSelfTest(): Promise { throw new Error(`NapCat upstream Jenkins template missing: ${expected}`); } } - const defaultUpstreamAuditIndex = upstreamSyncJenkinsTemplate.indexOf( - "run napcat-upstream-audit --", - ); - const firstUpstreamExecuteIndex = - upstreamSyncJenkinsTemplate.indexOf("--execute"); - if ( - defaultUpstreamAuditIndex === -1 || - (firstUpstreamExecuteIndex !== -1 && - defaultUpstreamAuditIndex > firstUpstreamExecuteIndex) - ) { - throw new Error( - "NapCat upstream Jenkins template must run dry-run audit before execute", + /** + * Verifies that the default upstream audit Jenkins call stays dry-run before the optional Codex execution stage. + * @param template - Full Jenkinsfile text to inspect. + * @returns Nothing; throws when the default audit call is unsafe. + */ + const assertUpstreamJenkinsDefaultAuditDryRun = (template: string): void => { + const defaultStageStart = template.indexOf( + "stage('ktWorkflow upstream audit dry-run')", + ); + const codexStageStart = template.indexOf( + "stage('ktWorkflow upstream audit with Codex')", + ); + if ( + defaultStageStart === -1 || + codexStageStart === -1 || + defaultStageStart >= codexStageStart + ) { + throw new Error( + "NapCat upstream Jenkins template default audit stage order self-check failed", + ); + } + const defaultUpstreamAuditStage = template.slice( + defaultStageStart, + codexStageStart, + ); + if (!defaultUpstreamAuditStage.includes("run napcat-upstream-audit --")) { + throw new Error( + "NapCat upstream Jenkins template default audit stage missing audit command", + ); + } + for (const forbiddenFlag of ["--execute", "--use-codex"]) { + if (defaultUpstreamAuditStage.includes(forbiddenFlag)) { + throw new Error( + `NapCat upstream Jenkins template default audit must not include ${forbiddenFlag}`, + ); + } + } + }; + assertUpstreamJenkinsDefaultAuditDryRun(upstreamSyncJenkinsTemplate); + const unsafeDefaultUpstreamAuditTemplate = [ + "stage('ktWorkflow upstream audit dry-run') {", + " pnpm --dir \"$KT_WORKFLOW_DIR\" run napcat-upstream-audit -- \\", + " --execute \\", + " --use-codex", + "}", + "stage('ktWorkflow upstream audit with Codex') {", + " pnpm --dir \"$KT_WORKFLOW_DIR\" run napcat-upstream-audit -- \\", + " --execute \\", + " --use-codex", + "}", + ].join("\n"); + let unsafeDefaultUpstreamAuditRejected = false; + try { + assertUpstreamJenkinsDefaultAuditDryRun( + unsafeDefaultUpstreamAuditTemplate, + ); + } catch (error) { + unsafeDefaultUpstreamAuditRejected = String(error).includes( + "NapCat upstream Jenkins template default audit must not include", ); } - const defaultUpstreamAuditCall = upstreamSyncJenkinsTemplate.slice( - defaultUpstreamAuditIndex, - firstUpstreamExecuteIndex === -1 - ? upstreamSyncJenkinsTemplate.length - : firstUpstreamExecuteIndex, - ); - if (defaultUpstreamAuditCall.includes("--use-codex")) { + if (!unsafeDefaultUpstreamAuditRejected) { throw new Error( - "NapCat upstream Jenkins template default audit must not use Codex", + "NapCat upstream Jenkins template unsafe default audit self-check failed", ); } for (const forbidden of ["git merge", "git push", "docker build", "kubectl"]) { @@ -517,6 +558,8 @@ export async function runSelfTest(): Promise { "string(name: 'API_PROMOTION_JOB'", "git merge", "git push", + "docker build", + "kubectl", ]) { if (runtimeReleaseJenkinsTemplate.includes(forbidden)) { throw new Error(