-
Notifications
You must be signed in to change notification settings - Fork 1
chore: sync workflow templates #5819
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |
| const fs = require('fs'); | ||
| const path = require('path'); | ||
| const { execFileSync } = require('child_process'); | ||
| const vm = require('node:vm'); | ||
|
|
||
| const OUTPUT_NAMES = { | ||
| 'docs-only': 'is-docs-only', | ||
|
|
@@ -25,6 +26,16 @@ const DEFAULT_CATEGORIES = { | |
| 'test-only': { paths: ['tests/**', '**/test_*.py', '**/*.test.js'], requireAll: true }, | ||
| }; | ||
|
|
||
| const STABLE_SYNC_BRANCHES = new Set([ | ||
| 'sync/workflows-candidate', | ||
| 'sync/workflows-delivery', | ||
| ]); | ||
|
|
||
| function isStableDeliveryPullRequest(githubContext) { | ||
| const branch = githubContext?.event?.pull_request?.head?.ref || ''; | ||
| return githubContext?.event_name === 'pull_request' && STABLE_SYNC_BRANCHES.has(branch); | ||
| } | ||
|
|
||
| function normalizePath(value) { | ||
| return String(value || '').replace(/\\/g, '/').replace(/^\.\/+/, ''); | ||
| } | ||
|
|
@@ -182,6 +193,122 @@ function parseGithubContext() { | |
| } | ||
| } | ||
|
|
||
| function stableDeliverySealStatus(githubContext, { contract, now } = {}) { | ||
| const event = githubContext?.event || {}; | ||
| const pullRequest = event.pull_request; | ||
| if (!isStableDeliveryPullRequest(githubContext)) { | ||
| return { required: false, valid: true, reason: '' }; | ||
| } | ||
|
|
||
| const headRepository = pullRequest?.head?.repo?.full_name || ''; | ||
| const baseRepository = pullRequest?.base?.repo?.full_name || ''; | ||
| if (!headRepository || !baseRepository || headRepository !== baseRepository) { | ||
| return { | ||
| required: true, | ||
| valid: false, | ||
| reason: 'stable delivery must originate from the base repository', | ||
| }; | ||
| } | ||
| if (!contract) { | ||
| return { required: true, valid: false, reason: 'delivery contract is unavailable' }; | ||
| } | ||
|
|
||
| const record = contract.parseDeliveryRecord(pullRequest?.body || ''); | ||
| const eligibility = contract.mergeEligibility(record, { | ||
| now: now || new Date().toISOString(), | ||
| repository: baseRepository, | ||
| requireSealed: true, | ||
| headSha: pullRequest?.head?.sha || '', | ||
| }); | ||
| return { | ||
| required: true, | ||
| valid: Boolean(eligibility.eligible), | ||
| reason: eligibility.reason, | ||
| }; | ||
| } | ||
|
|
||
| function compileDeliveryContract(source, filename) { | ||
| const module = { exports: {} }; | ||
| const sandbox = { module, exports: module.exports }; | ||
| vm.runInNewContext(String(source), sandbox, { filename }); | ||
| return module.exports; | ||
| } | ||
|
|
||
| function readContractAtRef(ref, contractPath) { | ||
| return runGit(['show', `${ref}:${contractPath}`]); | ||
| } | ||
|
|
||
| function isAddOnlyContractDiff(diffText, contractPath) { | ||
| return String(diffText || '') | ||
| .split(/\r?\n/) | ||
| .some((line) => line === `A\t${contractPath}`); | ||
| } | ||
|
|
||
| function contractAddedBetweenRefs(baseSha, headSha, contractPath) { | ||
| const added = runGit([ | ||
| 'diff', | ||
| '--name-status', | ||
| '--diff-filter=A', | ||
| baseSha, | ||
| headSha, | ||
| '--', | ||
| contractPath, | ||
| ]); | ||
| return isAddOnlyContractDiff(added, contractPath); | ||
| } | ||
|
|
||
| function loadDeliveryContract( | ||
| githubContext = {}, | ||
| { | ||
| readTrustedContract = readContractAtRef, | ||
| readBootstrapContract = readContractAtRef, | ||
| isBootstrapAddition = contractAddedBetweenRefs, | ||
| } = {}, | ||
| ) { | ||
| const workspace = process.env.GITHUB_WORKSPACE || process.cwd(); | ||
| const relativeContractPath = '.github/scripts/sync_pr_lease_contract.js'; | ||
| const pullRequest = githubContext?.event?.pull_request; | ||
|
|
||
| if (isStableDeliveryPullRequest(githubContext)) { | ||
| const baseSha = pullRequest?.base?.sha || ''; | ||
| if (!baseSha) { | ||
| return null; | ||
| } | ||
| try { | ||
| const source = readTrustedContract(baseSha, relativeContractPath); | ||
| return compileDeliveryContract(source, `${baseSha}:${relativeContractPath}`); | ||
| } catch { | ||
| // A consumer's first stable-delivery rollout necessarily predates the | ||
| // lease contract on its base. Permit only that exact add-only bootstrap: | ||
| // same repository, exact observed head, and the contract path added (not | ||
| // modified or renamed) between base and head. Maint 71 remains the final | ||
| // boundary and independently requires the exact generated head to carry | ||
| // a valid GitHub-recognized signature before it can merge. | ||
| const headSha = pullRequest?.head?.sha || ''; | ||
| const headRepository = pullRequest?.head?.repo?.full_name || ''; | ||
| const baseRepository = pullRequest?.base?.repo?.full_name || ''; | ||
| if (!headSha || !headRepository || headRepository !== baseRepository) { | ||
| return null; | ||
| } | ||
| try { | ||
| if (!isBootstrapAddition(baseSha, headSha, relativeContractPath)) { | ||
| return null; | ||
| } | ||
| const source = readBootstrapContract(headSha, relativeContractPath); | ||
|
Comment on lines
+293
to
+297
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For the first stable-delivery rollout, the base lacks this contract, but the Useful? React with 👍 / 👎. |
||
| return compileDeliveryContract(source, `${headSha}:${relativeContractPath}`); | ||
| } catch { | ||
| return null; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| const contractPath = path.resolve(workspace, relativeContractPath); | ||
| if (!fs.existsSync(contractPath)) { | ||
| return null; | ||
| } | ||
| return require(contractPath); | ||
| } | ||
|
|
||
| function runGit(args) { | ||
| return execFileSync('git', args, { | ||
| cwd: process.env.GITHUB_WORKSPACE || process.cwd(), | ||
|
|
@@ -224,7 +351,13 @@ function fetchBaseRef(baseRef, githubContext) { | |
| } | ||
| } | ||
|
|
||
| function listChangedFiles({ baseRef, githubContext } = {}) { | ||
| function listChangedFiles({ | ||
| baseRef, | ||
| githubContext, | ||
| baseAlreadyFetched = false, | ||
| fetchBase = fetchBaseRef, | ||
| diffGit = tryGit, | ||
| } = {}) { | ||
| const envFiles = process.env.PATH_CLASSIFIER_FILES_JSON; | ||
| if (envFiles) { | ||
| const parsed = JSON.parse(envFiles); | ||
|
|
@@ -234,7 +367,9 @@ function listChangedFiles({ baseRef, githubContext } = {}) { | |
| return parsed.map(normalizePath).filter(Boolean); | ||
| } | ||
|
|
||
| fetchBaseRef(baseRef, githubContext); | ||
| if (!baseAlreadyFetched) { | ||
| fetchBase(baseRef, githubContext); | ||
| } | ||
| const head = githubContext.sha || 'HEAD'; | ||
| const ranges = []; | ||
| if (baseRef) { | ||
|
|
@@ -248,7 +383,7 @@ function listChangedFiles({ baseRef, githubContext } = {}) { | |
| } | ||
|
|
||
| for (const range of ranges) { | ||
| const output = tryGit(['diff', '--name-only', range]); | ||
| const output = diffGit(['diff', '--name-only', range]); | ||
| if (output) { | ||
| return output.split(/\r?\n/).map(normalizePath).filter(Boolean); | ||
| } | ||
|
|
@@ -312,15 +447,32 @@ function writeOutputs(outputs) { | |
|
|
||
| function main() { | ||
| const githubContext = parseGithubContext(); | ||
| const baseRef = resolveBaseRef(process.env.INPUT_BASE_REF || '', githubContext); | ||
| // The stable-delivery contract is loaded from the exact trusted base SHA. | ||
| // Fetch it before evaluating a stable delivery seal, then reuse that fetch | ||
| // for changed-file classification. Ordinary PRs defer the same fetch until | ||
| // classification so every run fetches the base at most once. | ||
| const baseAlreadyFetched = isStableDeliveryPullRequest(githubContext); | ||
| if (baseAlreadyFetched) { | ||
| fetchBaseRef(baseRef, githubContext); | ||
| } | ||
| const seal = stableDeliverySealStatus(githubContext, { | ||
| contract: loadDeliveryContract(githubContext), | ||
| }); | ||
| if (seal.required && !seal.valid) { | ||
| throw new Error( | ||
| `Mutable generated delivery is not mergeable: ${seal.reason}. ` + | ||
| 'Maint 71 must seal this exact head after bounded reviewer settlement.', | ||
| ); | ||
| } | ||
| const forceFull = String(process.env.INPUT_FORCE_FULL || '').toLowerCase() === 'true'; | ||
| const configPath = process.env.INPUT_CONFIG_PATH || '.github/path-classification.yml'; | ||
| const baseRef = resolveBaseRef(process.env.INPUT_BASE_REF || '', githubContext); | ||
| const config = loadConfig(configPath); | ||
| let files = []; | ||
| let conservativeFull = false; | ||
|
|
||
| try { | ||
| files = listChangedFiles({ baseRef, githubContext }); | ||
| files = listChangedFiles({ baseRef, githubContext, baseAlreadyFetched }); | ||
| } catch (error) { | ||
| conservativeFull = true; | ||
| console.warn(`::warning::Unable to list changed files; forcing full classification: ${error.message}`); | ||
|
|
@@ -341,8 +493,13 @@ module.exports = { | |
| OUTPUT_NAMES, | ||
| classifyFiles, | ||
| globToRegExp, | ||
| isAddOnlyContractDiff, | ||
| isStableDeliveryPullRequest, | ||
| listChangedFiles, | ||
| loadConfig, | ||
| loadDeliveryContract, | ||
| matchesAny, | ||
| normalizePath, | ||
| parseClassificationConfig, | ||
| stableDeliverySealStatus, | ||
| }; | ||
Uh oh!
There was an error while loading. Please reload this page.