@patronage/factory-ci 1.0.0-alpha.19 → 1.0.0-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +17 -4
- package/dist/index.js +36 -11
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/pr-status-hud-workflow.ts +17 -0
- package/src/preview-proof-inventory.ts +64 -12
package/dist/index.d.ts
CHANGED
|
@@ -307,9 +307,11 @@ interface PreviewProofInventoryTransport {
|
|
|
307
307
|
/** Per-request timeout; defaults to five seconds. */
|
|
308
308
|
timeoutMs?: number;
|
|
309
309
|
/**
|
|
310
|
-
* Bearer token. `list` accepts a workflow `GITHUB_TOKEN` (read)
|
|
311
|
-
*
|
|
312
|
-
*
|
|
310
|
+
* Bearer token. `list` accepts a workflow `GITHUB_TOKEN` (read) and needs
|
|
311
|
+
* commit (contents) read for the force-push timeline; the contents-less
|
|
312
|
+
* Factory App token cannot serve it (#825). `persist` and `recordCleanup`
|
|
313
|
+
* must use a Factory App installation token: GitHub refuses GITHUB_TOKEN
|
|
314
|
+
* writes to App-owned check-runs.
|
|
313
315
|
*/
|
|
314
316
|
token: string;
|
|
315
317
|
}
|
|
@@ -919,6 +921,16 @@ interface FactoryPrStatusHudWorkflowOptions {
|
|
|
919
921
|
readonly cli?: string;
|
|
920
922
|
readonly createGithubAppToken: PinnedAction;
|
|
921
923
|
}
|
|
924
|
+
/**
|
|
925
|
+
* Least privilege for the job that runs the present step. A `permissions`
|
|
926
|
+
* block zeroes every unlisted GITHUB_TOKEN scope, and the inventory list
|
|
927
|
+
* reads pull requests, commits, and check-runs with that token (#825).
|
|
928
|
+
*/
|
|
929
|
+
declare const FACTORY_PR_STATUS_HUD_PERMISSIONS: Readonly<{
|
|
930
|
+
readonly checks: "read";
|
|
931
|
+
readonly contents: "read";
|
|
932
|
+
readonly "pull-requests": "read";
|
|
933
|
+
}>;
|
|
922
934
|
declare const FACTORY_PR_STATUS_HUD_CONCURRENCY: Readonly<{
|
|
923
935
|
cancelInProgress: false;
|
|
924
936
|
group: "status-hud-${{ github.repository }}-${{ github.event.pull_request.number }}";
|
|
@@ -928,6 +940,7 @@ interface FactoryPrStatusHudWorkflow {
|
|
|
928
940
|
readonly if: typeof FACTORY_PR_STATUS_HUD_IF;
|
|
929
941
|
readonly jobId: typeof FACTORY_PR_STATUS_HUD_JOB_ID;
|
|
930
942
|
readonly jobName: typeof FACTORY_PR_STATUS_HUD_JOB_NAME;
|
|
943
|
+
readonly permissions: typeof FACTORY_PR_STATUS_HUD_PERMISSIONS;
|
|
931
944
|
readonly presentSteps: readonly WorkflowStep[];
|
|
932
945
|
}
|
|
933
946
|
declare const factoryPrStatusHudWorkflow: (options: FactoryPrStatusHudWorkflowOptions) => FactoryPrStatusHudWorkflow;
|
|
@@ -1329,4 +1342,4 @@ declare const assertWorkflowShellParses: (yaml: string, options: {
|
|
|
1329
1342
|
readonly source: string;
|
|
1330
1343
|
}) => void;
|
|
1331
1344
|
//#endregion
|
|
1332
|
-
export { type BundleAlchemyEntryOptions, type CheckoutStepOptions, EXECUTE_ALCHEMY_ENTRY_MAX_BUFFER, type ExecuteAlchemyEntryOptions, type ExecuteAlchemyEntryResult, FACTORY_CANDIDATE_IMPACT_BASIS_OUTPUT, FACTORY_CANDIDATE_IMPACT_DECISION_OUTPUT, FACTORY_CANDIDATE_IMPACT_INERT_OUTPUT, FACTORY_CANDIDATE_IMPACT_STEP_ID, FACTORY_CANDIDATE_IMPACT_UNSUBSCRIBED_OUTPUT, FACTORY_CANDIDATE_PULL_REQUEST_TYPES, FACTORY_LIFECYCLE_CONTRACT_JOB_ID, FACTORY_LIFECYCLE_CONTRACT_JOB_NAME, FACTORY_PREVIEW_CLEANUP_AUDIT_JOB_ID, FACTORY_PREVIEW_CLEANUP_AUDIT_JOB_NAME, FACTORY_PRODUCTION_IMPACT_BASIS_OUTPUT, FACTORY_PRODUCTION_IMPACT_DECISION_OUTPUT, FACTORY_PRODUCTION_IMPACT_STEP_ID, FACTORY_PRODUCTION_IMPACT_UNSUBSCRIBED_OUTPUT, FACTORY_PROOF_GATE_APP_ID, FACTORY_PROOF_GATE_CHECK_NAME, FACTORY_PROOF_GATE_GUARD, FACTORY_PROOF_GATE_IF, FACTORY_PROOF_GATE_MODE_OUTPUT, FACTORY_PROOF_GATE_OUTPUT, FACTORY_PROOF_GATE_REASONS, FACTORY_PROOF_GATE_REASON_OUTPUT, FACTORY_PROOF_GATE_SHELL, FACTORY_PROOF_GATE_SOURCE_URL_OUTPUT, FACTORY_PROOF_GATE_STEP_ID, FACTORY_PROOF_GATE_STEP_NAME, FACTORY_PR_STATUS_HUD_APP_TOKEN_STEP_ID, FACTORY_PR_STATUS_HUD_CONCURRENCY, FACTORY_PR_STATUS_HUD_IF, FACTORY_PR_STATUS_HUD_JOB_ID, FACTORY_PR_STATUS_HUD_JOB_NAME, FACTORY_PR_STATUS_HUD_PLAN_PATH, FACTORY_PUSH_IDENTITY_SCHEMA_VERSION, type FactoryCandidateImpactWorkflow, type FactoryCandidateImpactWorkflowOptions, type FactoryLifecycleContractLane, type FactoryLifecycleContractLaneOptions, type FactoryPrStatusHudWorkflow, type FactoryPrStatusHudWorkflowOptions, type FactoryPreviewCleanupAuditJob, type FactoryPreviewCleanupDestroyJob, type FactoryPreviewCleanupTopology, type FactoryPreviewCleanupTopologyOptions, type FactoryProductionImpactWorkflow, type FactoryProductionImpactWorkflowOptions, type FactoryProofGateOptions, type FactoryProofGateReason, type FactoryProofGateStep, type FactoryProofReusePresentationOptions, type FactoryProofReuseSummaryStep, type FactoryProofTimingStartStep, type FactoryPushIdentityConsumer, type FactoryPushIdentityConsumerOptions, type FactoryPushIdentityDisposition, type FactoryPushIdentityEnvelope, type FactoryPushIdentityProducer, type FactoryPushIdentityProducerOptions, type FactoryWorkflowArtifact, type FactoryWorkflowOptions, type FactoryWorkflowSetupOptions, GitHubApiError, type GithubAppCredentials, type GithubAppTokenOptions, type InstallStepOptions, type LocalPreviewStage, type LocalPreviewStageOptions, NODE_PNPM_ACTION_FAMILY_NODE24, type NodePnpmActionFamily, PREVIEW_PROOF_INVENTORY_CHECK_NAME, type ParseLocalPreviewStageExpected, type ParsedLocalPreviewStage, type PinnedAction, type PreviewProofCandidate, type PreviewProofCleanupOutcome, type PreviewProofCleanupStatus, type PreviewProofEnvelopeEvidence, type PreviewProofInventoryAccess, type PreviewProofInventoryCleanupInput, type PreviewProofInventoryListInput, type PreviewProofInventoryPersistInput, type PreviewProofInventoryStore, type PreviewProofInventoryTransport, type PreviewProofLifecycleEvent, type PreviewProofLifecycleState, type PreviewProofRegistration, type PreviewProofResolveQuery, type ProofReuseCommand, type ProofReuseCoverageInput, type ProofReuseCoverageReport, type SetupNodeStepOptions, VITEST_PROFILE_SCHEMA_VERSION, VITEST_PROFILE_TOOL, type VitestJsonReport, type VitestProfile, type VitestProfileDependencies, type VitestProfileDurationSummary, type VitestProfileEnvironment, VitestProfileError, type VitestProfileOptions, type VitestProfileReadResult, type VitestProfileSample, type VitestProfileSampleExecution, type VitestTestStatus, type WorkflowShellParseFailure, type WorkflowStep, assertProofReuseCoverage, assertWorkflowShellParses, bundleAlchemyEntry, captureVitestProfileEnvironment, executeAlchemyEntry, factoryCandidateImpactWorkflow, factoryCandidateOrPushCondition, factoryLifecycleContractLane, factoryPrStatusHudWorkflow, factoryPreviewCleanupTopology, factoryProductionImpactWorkflow, factoryProofGateScript, factoryProofGateStep, factoryProofReuseSummaryStep, factoryProofTimingStartStep, factoryPushIdentityConsumer, factoryPushIdentityProducer, factoryWorkflow, githubAppJwt, isLocalPreviewStage, localPreviewStage, mintInstallationToken, normalizeVitestProfileSample, parseLocalPreviewStage, previewCleanupDestroyJobId, previewProofInventory, previewProofLifecycle, productionImpactTargetOutput, proofReuseCoverage, proofReuseRequiredCommands, readVitestProfileDocument, resolveProofReuseCommands, runVitestProfile, workflowRunBlocks, workflowShellParseFailures, writeVitestProfile };
|
|
1345
|
+
export { type BundleAlchemyEntryOptions, type CheckoutStepOptions, EXECUTE_ALCHEMY_ENTRY_MAX_BUFFER, type ExecuteAlchemyEntryOptions, type ExecuteAlchemyEntryResult, FACTORY_CANDIDATE_IMPACT_BASIS_OUTPUT, FACTORY_CANDIDATE_IMPACT_DECISION_OUTPUT, FACTORY_CANDIDATE_IMPACT_INERT_OUTPUT, FACTORY_CANDIDATE_IMPACT_STEP_ID, FACTORY_CANDIDATE_IMPACT_UNSUBSCRIBED_OUTPUT, FACTORY_CANDIDATE_PULL_REQUEST_TYPES, FACTORY_LIFECYCLE_CONTRACT_JOB_ID, FACTORY_LIFECYCLE_CONTRACT_JOB_NAME, FACTORY_PREVIEW_CLEANUP_AUDIT_JOB_ID, FACTORY_PREVIEW_CLEANUP_AUDIT_JOB_NAME, FACTORY_PRODUCTION_IMPACT_BASIS_OUTPUT, FACTORY_PRODUCTION_IMPACT_DECISION_OUTPUT, FACTORY_PRODUCTION_IMPACT_STEP_ID, FACTORY_PRODUCTION_IMPACT_UNSUBSCRIBED_OUTPUT, FACTORY_PROOF_GATE_APP_ID, FACTORY_PROOF_GATE_CHECK_NAME, FACTORY_PROOF_GATE_GUARD, FACTORY_PROOF_GATE_IF, FACTORY_PROOF_GATE_MODE_OUTPUT, FACTORY_PROOF_GATE_OUTPUT, FACTORY_PROOF_GATE_REASONS, FACTORY_PROOF_GATE_REASON_OUTPUT, FACTORY_PROOF_GATE_SHELL, FACTORY_PROOF_GATE_SOURCE_URL_OUTPUT, FACTORY_PROOF_GATE_STEP_ID, FACTORY_PROOF_GATE_STEP_NAME, FACTORY_PR_STATUS_HUD_APP_TOKEN_STEP_ID, FACTORY_PR_STATUS_HUD_CONCURRENCY, FACTORY_PR_STATUS_HUD_IF, FACTORY_PR_STATUS_HUD_JOB_ID, FACTORY_PR_STATUS_HUD_JOB_NAME, FACTORY_PR_STATUS_HUD_PERMISSIONS, FACTORY_PR_STATUS_HUD_PLAN_PATH, FACTORY_PUSH_IDENTITY_SCHEMA_VERSION, type FactoryCandidateImpactWorkflow, type FactoryCandidateImpactWorkflowOptions, type FactoryLifecycleContractLane, type FactoryLifecycleContractLaneOptions, type FactoryPrStatusHudWorkflow, type FactoryPrStatusHudWorkflowOptions, type FactoryPreviewCleanupAuditJob, type FactoryPreviewCleanupDestroyJob, type FactoryPreviewCleanupTopology, type FactoryPreviewCleanupTopologyOptions, type FactoryProductionImpactWorkflow, type FactoryProductionImpactWorkflowOptions, type FactoryProofGateOptions, type FactoryProofGateReason, type FactoryProofGateStep, type FactoryProofReusePresentationOptions, type FactoryProofReuseSummaryStep, type FactoryProofTimingStartStep, type FactoryPushIdentityConsumer, type FactoryPushIdentityConsumerOptions, type FactoryPushIdentityDisposition, type FactoryPushIdentityEnvelope, type FactoryPushIdentityProducer, type FactoryPushIdentityProducerOptions, type FactoryWorkflowArtifact, type FactoryWorkflowOptions, type FactoryWorkflowSetupOptions, GitHubApiError, type GithubAppCredentials, type GithubAppTokenOptions, type InstallStepOptions, type LocalPreviewStage, type LocalPreviewStageOptions, NODE_PNPM_ACTION_FAMILY_NODE24, type NodePnpmActionFamily, PREVIEW_PROOF_INVENTORY_CHECK_NAME, type ParseLocalPreviewStageExpected, type ParsedLocalPreviewStage, type PinnedAction, type PreviewProofCandidate, type PreviewProofCleanupOutcome, type PreviewProofCleanupStatus, type PreviewProofEnvelopeEvidence, type PreviewProofInventoryAccess, type PreviewProofInventoryCleanupInput, type PreviewProofInventoryListInput, type PreviewProofInventoryPersistInput, type PreviewProofInventoryStore, type PreviewProofInventoryTransport, type PreviewProofLifecycleEvent, type PreviewProofLifecycleState, type PreviewProofRegistration, type PreviewProofResolveQuery, type ProofReuseCommand, type ProofReuseCoverageInput, type ProofReuseCoverageReport, type SetupNodeStepOptions, VITEST_PROFILE_SCHEMA_VERSION, VITEST_PROFILE_TOOL, type VitestJsonReport, type VitestProfile, type VitestProfileDependencies, type VitestProfileDurationSummary, type VitestProfileEnvironment, VitestProfileError, type VitestProfileOptions, type VitestProfileReadResult, type VitestProfileSample, type VitestProfileSampleExecution, type VitestTestStatus, type WorkflowShellParseFailure, type WorkflowStep, assertProofReuseCoverage, assertWorkflowShellParses, bundleAlchemyEntry, captureVitestProfileEnvironment, executeAlchemyEntry, factoryCandidateImpactWorkflow, factoryCandidateOrPushCondition, factoryLifecycleContractLane, factoryPrStatusHudWorkflow, factoryPreviewCleanupTopology, factoryProductionImpactWorkflow, factoryProofGateScript, factoryProofGateStep, factoryProofReuseSummaryStep, factoryProofTimingStartStep, factoryPushIdentityConsumer, factoryPushIdentityProducer, factoryWorkflow, githubAppJwt, isLocalPreviewStage, localPreviewStage, mintInstallationToken, normalizeVitestProfileSample, parseLocalPreviewStage, previewCleanupDestroyJobId, previewProofInventory, previewProofLifecycle, productionImpactTargetOutput, proofReuseCoverage, proofReuseRequiredCommands, readVitestProfileDocument, resolveProofReuseCommands, runVitestProfile, workflowRunBlocks, workflowShellParseFailures, writeVitestProfile };
|
package/dist/index.js
CHANGED
|
@@ -1333,7 +1333,10 @@ const githubGraphql = async (request, token, timeoutMs, query, variables) => {
|
|
|
1333
1333
|
method: "POST"
|
|
1334
1334
|
}, timeoutMs);
|
|
1335
1335
|
if (!isRecord$1(served)) throw new TypeError("GitHub GraphQL response was not an object");
|
|
1336
|
-
if (served.errors !== void 0)
|
|
1336
|
+
if (served.errors !== void 0) {
|
|
1337
|
+
const details = Array.isArray(served.errors) ? served.errors.flatMap((entry) => isRecord$1(entry) ? [[entry.type, entry.message].filter((part) => typeof part === "string").join(": ")] : []).filter((detail) => detail.length > 0).join("; ") : "";
|
|
1338
|
+
throw new Error(details.length > 0 ? `Preview proof inventory GraphQL query failed: ${details}` : "Preview proof inventory GraphQL query failed.");
|
|
1339
|
+
}
|
|
1337
1340
|
return served.data;
|
|
1338
1341
|
};
|
|
1339
1342
|
const repoApi = (owner, repo) => `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`;
|
|
@@ -1447,27 +1450,37 @@ const listPullRequestCommitShas = (request, token, timeoutMs, owner, repo, pr) =
|
|
|
1447
1450
|
if (!Array.isArray(commits)) throw new TypeError("GitHub pull request commits response was not an array");
|
|
1448
1451
|
return commits.flatMap((commit) => isRecord$1(commit) && typeof commit.sha === "string" ? [commit.sha] : []);
|
|
1449
1452
|
}, COMMITS_PER_PAGE, MAX_COMMIT_PAGES, "Preview proof inventory commit listing exhausted pagination before GitHub returned a final page.");
|
|
1453
|
+
const readPullBaseSha = (pull) => {
|
|
1454
|
+
if (!isRecord$1(pull.base) || typeof pull.base.sha !== "string") throw new TypeError("GitHub pull request response omitted base.sha");
|
|
1455
|
+
return pull.base.sha;
|
|
1456
|
+
};
|
|
1450
1457
|
/**
|
|
1451
|
-
* Current head, replaced heads from force-push,
|
|
1452
|
-
* pull-commits endpoint silently stops at 250 and
|
|
1453
|
-
* head, so those two cases use compare pagination
|
|
1458
|
+
* Current head, replaced heads from force-push, the commits each replaced head
|
|
1459
|
+
* carried, and PR commits. The pull-commits endpoint silently stops at 250 and
|
|
1460
|
+
* never lists a rebased-away head, so those two cases use compare pagination
|
|
1461
|
+
* and the force-push timeline. A persist, then a fast-forward, then a rebase
|
|
1462
|
+
* leaves the persist SHA behind the replaced head, so each replaced head is
|
|
1463
|
+
* also walked back to the base (#796).
|
|
1454
1464
|
*/
|
|
1455
1465
|
const listCommitShasForPull = async (request, token, timeoutMs, owner, repo, pr) => {
|
|
1456
1466
|
const pull = await githubJson(request, `${repoApi(owner, repo)}/pulls/${pr}`, token, { method: "GET" }, timeoutMs);
|
|
1457
1467
|
if (!isRecord$1(pull) || !isRecord$1(pull.head) || typeof pull.head.sha !== "string") throw new TypeError("GitHub pull request response omitted head.sha");
|
|
1458
1468
|
const pullCommits = await listPullRequestCommitShas(request, token, timeoutMs, owner, repo, pr);
|
|
1459
1469
|
let older = pullCommits;
|
|
1460
|
-
if (pullCommits.length >= GITHUB_PULL_COMMITS_CAP)
|
|
1461
|
-
if (!isRecord$1(pull.base) || typeof pull.base.sha !== "string") throw new TypeError("GitHub pull request response omitted base.sha");
|
|
1462
|
-
older = await listCompareCommitShas(request, token, timeoutMs, owner, repo, pull.base.sha, pull.head.sha);
|
|
1463
|
-
}
|
|
1470
|
+
if (pullCommits.length >= GITHUB_PULL_COMMITS_CAP) older = await listCompareCommitShas(request, token, timeoutMs, owner, repo, readPullBaseSha(pull), pull.head.sha);
|
|
1464
1471
|
const previousHeads = await listForcePushBeforeShas(request, token, timeoutMs, owner, repo, pr);
|
|
1472
|
+
let replaced = [];
|
|
1473
|
+
if (previousHeads.length > 0) {
|
|
1474
|
+
const baseSha = readPullBaseSha(pull);
|
|
1475
|
+
replaced = (await mapPool(previousHeads, 8, (previousHead) => listCompareCommitShas(request, token, timeoutMs, owner, repo, baseSha, previousHead))).flatMap((history) => [...history].toReversed());
|
|
1476
|
+
}
|
|
1465
1477
|
const shas = [];
|
|
1466
1478
|
const seen = /* @__PURE__ */ new Set();
|
|
1467
1479
|
for (const sha of [
|
|
1468
1480
|
pull.head.sha,
|
|
1469
1481
|
...previousHeads,
|
|
1470
|
-
...older.toReversed()
|
|
1482
|
+
...older.toReversed(),
|
|
1483
|
+
...replaced
|
|
1471
1484
|
]) if (!seen.has(sha)) {
|
|
1472
1485
|
seen.add(sha);
|
|
1473
1486
|
shas.push(sha);
|
|
@@ -2017,6 +2030,16 @@ const FACTORY_PR_STATUS_HUD_PLAN_PATH = ".factory-memory/preview-plan.json";
|
|
|
2017
2030
|
* skipped or withdrawn deploy still publishes Skipped rows.
|
|
2018
2031
|
*/
|
|
2019
2032
|
const FACTORY_PR_STATUS_HUD_IF = "always() && github.event_name == 'pull_request' && github.event.pull_request.draft != true";
|
|
2033
|
+
/**
|
|
2034
|
+
* Least privilege for the job that runs the present step. A `permissions`
|
|
2035
|
+
* block zeroes every unlisted GITHUB_TOKEN scope, and the inventory list
|
|
2036
|
+
* reads pull requests, commits, and check-runs with that token (#825).
|
|
2037
|
+
*/
|
|
2038
|
+
const FACTORY_PR_STATUS_HUD_PERMISSIONS = Object.freeze({
|
|
2039
|
+
checks: "read",
|
|
2040
|
+
contents: "read",
|
|
2041
|
+
"pull-requests": "read"
|
|
2042
|
+
});
|
|
2020
2043
|
const FACTORY_PR_STATUS_HUD_CONCURRENCY = Object.freeze({
|
|
2021
2044
|
cancelInProgress: false,
|
|
2022
2045
|
group: `status-hud-\${{ github.repository }}-\${{ github.event.pull_request.number }}`
|
|
@@ -2040,7 +2063,8 @@ const factoryPrStatusHudWorkflow = (options) => {
|
|
|
2040
2063
|
FACTORY_HEAD_SHA: `\${{ github.event.pull_request.head.sha }}`,
|
|
2041
2064
|
FACTORY_OWNER: `\${{ github.repository_owner }}`,
|
|
2042
2065
|
FACTORY_PR: `\${{ github.event.pull_request.number }}`,
|
|
2043
|
-
FACTORY_REPO: `\${{ github.event.repository.name }}
|
|
2066
|
+
FACTORY_REPO: `\${{ github.event.repository.name }}`,
|
|
2067
|
+
GITHUB_TOKEN: `\${{ github.token }}`
|
|
2044
2068
|
}),
|
|
2045
2069
|
name: "Present the PR status HUD",
|
|
2046
2070
|
run: `${cli} pr:status-hud --head "$FACTORY_HEAD_SHA" --owner "$FACTORY_OWNER" --repo "$FACTORY_REPO" --pr "$FACTORY_PR"`
|
|
@@ -2050,6 +2074,7 @@ const factoryPrStatusHudWorkflow = (options) => {
|
|
|
2050
2074
|
if: FACTORY_PR_STATUS_HUD_IF,
|
|
2051
2075
|
jobId: FACTORY_PR_STATUS_HUD_JOB_ID,
|
|
2052
2076
|
jobName: FACTORY_PR_STATUS_HUD_JOB_NAME,
|
|
2077
|
+
permissions: FACTORY_PR_STATUS_HUD_PERMISSIONS,
|
|
2053
2078
|
presentSteps: Object.freeze([mintStep, presentStep])
|
|
2054
2079
|
});
|
|
2055
2080
|
};
|
|
@@ -3174,4 +3199,4 @@ const assertWorkflowShellParses = (yaml, options) => {
|
|
|
3174
3199
|
throw new Error(`${options.source} emits shell bash cannot parse; the runner would treat it as a no-op:\n${detail}`);
|
|
3175
3200
|
};
|
|
3176
3201
|
//#endregion
|
|
3177
|
-
export { EXECUTE_ALCHEMY_ENTRY_MAX_BUFFER, FACTORY_CANDIDATE_IMPACT_BASIS_OUTPUT, FACTORY_CANDIDATE_IMPACT_DECISION_OUTPUT, FACTORY_CANDIDATE_IMPACT_INERT_OUTPUT, FACTORY_CANDIDATE_IMPACT_STEP_ID, FACTORY_CANDIDATE_IMPACT_UNSUBSCRIBED_OUTPUT, FACTORY_CANDIDATE_PULL_REQUEST_TYPES, FACTORY_LIFECYCLE_CONTRACT_JOB_ID, FACTORY_LIFECYCLE_CONTRACT_JOB_NAME, FACTORY_PREVIEW_CLEANUP_AUDIT_JOB_ID, FACTORY_PREVIEW_CLEANUP_AUDIT_JOB_NAME, FACTORY_PRODUCTION_IMPACT_BASIS_OUTPUT, FACTORY_PRODUCTION_IMPACT_DECISION_OUTPUT, FACTORY_PRODUCTION_IMPACT_STEP_ID, FACTORY_PRODUCTION_IMPACT_UNSUBSCRIBED_OUTPUT, FACTORY_PROOF_GATE_APP_ID, FACTORY_PROOF_GATE_CHECK_NAME, FACTORY_PROOF_GATE_GUARD, FACTORY_PROOF_GATE_IF, FACTORY_PROOF_GATE_MODE_OUTPUT, FACTORY_PROOF_GATE_OUTPUT, FACTORY_PROOF_GATE_REASONS, FACTORY_PROOF_GATE_REASON_OUTPUT, FACTORY_PROOF_GATE_SHELL, FACTORY_PROOF_GATE_SOURCE_URL_OUTPUT, FACTORY_PROOF_GATE_STEP_ID, FACTORY_PROOF_GATE_STEP_NAME, FACTORY_PR_STATUS_HUD_APP_TOKEN_STEP_ID, FACTORY_PR_STATUS_HUD_CONCURRENCY, FACTORY_PR_STATUS_HUD_IF, FACTORY_PR_STATUS_HUD_JOB_ID, FACTORY_PR_STATUS_HUD_JOB_NAME, FACTORY_PR_STATUS_HUD_PLAN_PATH, FACTORY_PUSH_IDENTITY_SCHEMA_VERSION, GitHubApiError, NODE_PNPM_ACTION_FAMILY_NODE24, PREVIEW_PROOF_INVENTORY_CHECK_NAME, VITEST_PROFILE_SCHEMA_VERSION, VITEST_PROFILE_TOOL, VitestProfileError, assertProofReuseCoverage, assertWorkflowShellParses, bundleAlchemyEntry, captureVitestProfileEnvironment, executeAlchemyEntry, factoryCandidateImpactWorkflow, factoryCandidateOrPushCondition, factoryLifecycleContractLane, factoryPrStatusHudWorkflow, factoryPreviewCleanupTopology, factoryProductionImpactWorkflow, factoryProofGateScript, factoryProofGateStep, factoryProofReuseSummaryStep, factoryProofTimingStartStep, factoryPushIdentityConsumer, factoryPushIdentityProducer, factoryWorkflow, githubAppJwt, isLocalPreviewStage, localPreviewStage, mintInstallationToken, normalizeVitestProfileSample, parseLocalPreviewStage, previewCleanupDestroyJobId, previewProofInventory, previewProofLifecycle, productionImpactTargetOutput, proofReuseCoverage, proofReuseRequiredCommands, readVitestProfileDocument, resolveProofReuseCommands, runVitestProfile, workflowRunBlocks, workflowShellParseFailures, writeVitestProfile };
|
|
3202
|
+
export { EXECUTE_ALCHEMY_ENTRY_MAX_BUFFER, FACTORY_CANDIDATE_IMPACT_BASIS_OUTPUT, FACTORY_CANDIDATE_IMPACT_DECISION_OUTPUT, FACTORY_CANDIDATE_IMPACT_INERT_OUTPUT, FACTORY_CANDIDATE_IMPACT_STEP_ID, FACTORY_CANDIDATE_IMPACT_UNSUBSCRIBED_OUTPUT, FACTORY_CANDIDATE_PULL_REQUEST_TYPES, FACTORY_LIFECYCLE_CONTRACT_JOB_ID, FACTORY_LIFECYCLE_CONTRACT_JOB_NAME, FACTORY_PREVIEW_CLEANUP_AUDIT_JOB_ID, FACTORY_PREVIEW_CLEANUP_AUDIT_JOB_NAME, FACTORY_PRODUCTION_IMPACT_BASIS_OUTPUT, FACTORY_PRODUCTION_IMPACT_DECISION_OUTPUT, FACTORY_PRODUCTION_IMPACT_STEP_ID, FACTORY_PRODUCTION_IMPACT_UNSUBSCRIBED_OUTPUT, FACTORY_PROOF_GATE_APP_ID, FACTORY_PROOF_GATE_CHECK_NAME, FACTORY_PROOF_GATE_GUARD, FACTORY_PROOF_GATE_IF, FACTORY_PROOF_GATE_MODE_OUTPUT, FACTORY_PROOF_GATE_OUTPUT, FACTORY_PROOF_GATE_REASONS, FACTORY_PROOF_GATE_REASON_OUTPUT, FACTORY_PROOF_GATE_SHELL, FACTORY_PROOF_GATE_SOURCE_URL_OUTPUT, FACTORY_PROOF_GATE_STEP_ID, FACTORY_PROOF_GATE_STEP_NAME, FACTORY_PR_STATUS_HUD_APP_TOKEN_STEP_ID, FACTORY_PR_STATUS_HUD_CONCURRENCY, FACTORY_PR_STATUS_HUD_IF, FACTORY_PR_STATUS_HUD_JOB_ID, FACTORY_PR_STATUS_HUD_JOB_NAME, FACTORY_PR_STATUS_HUD_PERMISSIONS, FACTORY_PR_STATUS_HUD_PLAN_PATH, FACTORY_PUSH_IDENTITY_SCHEMA_VERSION, GitHubApiError, NODE_PNPM_ACTION_FAMILY_NODE24, PREVIEW_PROOF_INVENTORY_CHECK_NAME, VITEST_PROFILE_SCHEMA_VERSION, VITEST_PROFILE_TOOL, VitestProfileError, assertProofReuseCoverage, assertWorkflowShellParses, bundleAlchemyEntry, captureVitestProfileEnvironment, executeAlchemyEntry, factoryCandidateImpactWorkflow, factoryCandidateOrPushCondition, factoryLifecycleContractLane, factoryPrStatusHudWorkflow, factoryPreviewCleanupTopology, factoryProductionImpactWorkflow, factoryProofGateScript, factoryProofGateStep, factoryProofReuseSummaryStep, factoryProofTimingStartStep, factoryPushIdentityConsumer, factoryPushIdentityProducer, factoryWorkflow, githubAppJwt, isLocalPreviewStage, localPreviewStage, mintInstallationToken, normalizeVitestProfileSample, parseLocalPreviewStage, previewCleanupDestroyJobId, previewProofInventory, previewProofLifecycle, productionImpactTargetOutput, proofReuseCoverage, proofReuseRequiredCommands, readVitestProfileDocument, resolveProofReuseCommands, runVitestProfile, workflowRunBlocks, workflowShellParseFailures, writeVitestProfile };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@patronage/factory-ci",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.20",
|
|
4
4
|
"description": "Deep CI and deploy building blocks for Patronage factory projects: workflow source artifacts, hosted diff classification, Alchemy entry execution, and disposable-stage semantics",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"alchemy",
|
package/src/index.ts
CHANGED
|
@@ -130,6 +130,7 @@ export {
|
|
|
130
130
|
FACTORY_PR_STATUS_HUD_IF,
|
|
131
131
|
FACTORY_PR_STATUS_HUD_JOB_ID,
|
|
132
132
|
FACTORY_PR_STATUS_HUD_JOB_NAME,
|
|
133
|
+
FACTORY_PR_STATUS_HUD_PERMISSIONS,
|
|
133
134
|
FACTORY_PR_STATUS_HUD_PLAN_PATH,
|
|
134
135
|
type FactoryPrStatusHudWorkflow,
|
|
135
136
|
type FactoryPrStatusHudWorkflowOptions,
|
|
@@ -22,6 +22,17 @@ export interface FactoryPrStatusHudWorkflowOptions {
|
|
|
22
22
|
readonly createGithubAppToken: PinnedAction;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Least privilege for the job that runs the present step. A `permissions`
|
|
27
|
+
* block zeroes every unlisted GITHUB_TOKEN scope, and the inventory list
|
|
28
|
+
* reads pull requests, commits, and check-runs with that token (#825).
|
|
29
|
+
*/
|
|
30
|
+
export const FACTORY_PR_STATUS_HUD_PERMISSIONS = Object.freeze({
|
|
31
|
+
checks: "read",
|
|
32
|
+
contents: "read",
|
|
33
|
+
"pull-requests": "read",
|
|
34
|
+
} as const);
|
|
35
|
+
|
|
25
36
|
export const FACTORY_PR_STATUS_HUD_CONCURRENCY = Object.freeze({
|
|
26
37
|
cancelInProgress: false,
|
|
27
38
|
group: `status-hud-\${{ github.repository }}-\${{ github.event.pull_request.number }}`,
|
|
@@ -32,6 +43,7 @@ export interface FactoryPrStatusHudWorkflow {
|
|
|
32
43
|
readonly if: typeof FACTORY_PR_STATUS_HUD_IF;
|
|
33
44
|
readonly jobId: typeof FACTORY_PR_STATUS_HUD_JOB_ID;
|
|
34
45
|
readonly jobName: typeof FACTORY_PR_STATUS_HUD_JOB_NAME;
|
|
46
|
+
readonly permissions: typeof FACTORY_PR_STATUS_HUD_PERMISSIONS;
|
|
35
47
|
readonly presentSteps: readonly WorkflowStep[];
|
|
36
48
|
}
|
|
37
49
|
|
|
@@ -57,6 +69,9 @@ export const factoryPrStatusHudWorkflow = (
|
|
|
57
69
|
"private-key": `\${{ secrets.FACTORY_GITHUB_APP_PRIVATE_KEY }}`,
|
|
58
70
|
}),
|
|
59
71
|
});
|
|
72
|
+
// The App token owns the HUD comment but has no contents permission, so
|
|
73
|
+
// the force-push timeline read (#819) is FORBIDDEN for it. The workflow
|
|
74
|
+
// token carries the job's `contents: read` and serves that read (#825).
|
|
60
75
|
const presentStep: WorkflowStep = Object.freeze({
|
|
61
76
|
env: Object.freeze({
|
|
62
77
|
FACTORY_GITHUB_APP_TOKEN: `\${{ steps.${FACTORY_PR_STATUS_HUD_APP_TOKEN_STEP_ID}.outputs.token }}`,
|
|
@@ -64,6 +79,7 @@ export const factoryPrStatusHudWorkflow = (
|
|
|
64
79
|
FACTORY_OWNER: `\${{ github.repository_owner }}`,
|
|
65
80
|
FACTORY_PR: `\${{ github.event.pull_request.number }}`,
|
|
66
81
|
FACTORY_REPO: `\${{ github.event.repository.name }}`,
|
|
82
|
+
GITHUB_TOKEN: `\${{ github.token }}`,
|
|
67
83
|
}),
|
|
68
84
|
name: "Present the PR status HUD",
|
|
69
85
|
run: `${cli} pr:status-hud --head "$FACTORY_HEAD_SHA" --owner "$FACTORY_OWNER" --repo "$FACTORY_REPO" --pr "$FACTORY_PR"`,
|
|
@@ -74,6 +90,7 @@ export const factoryPrStatusHudWorkflow = (
|
|
|
74
90
|
if: FACTORY_PR_STATUS_HUD_IF,
|
|
75
91
|
jobId: FACTORY_PR_STATUS_HUD_JOB_ID,
|
|
76
92
|
jobName: FACTORY_PR_STATUS_HUD_JOB_NAME,
|
|
93
|
+
permissions: FACTORY_PR_STATUS_HUD_PERMISSIONS,
|
|
77
94
|
presentSteps: Object.freeze([mintStep, presentStep]),
|
|
78
95
|
});
|
|
79
96
|
};
|
|
@@ -74,9 +74,11 @@ export interface PreviewProofInventoryTransport {
|
|
|
74
74
|
/** Per-request timeout; defaults to five seconds. */
|
|
75
75
|
timeoutMs?: number;
|
|
76
76
|
/**
|
|
77
|
-
* Bearer token. `list` accepts a workflow `GITHUB_TOKEN` (read)
|
|
78
|
-
*
|
|
79
|
-
*
|
|
77
|
+
* Bearer token. `list` accepts a workflow `GITHUB_TOKEN` (read) and needs
|
|
78
|
+
* commit (contents) read for the force-push timeline; the contents-less
|
|
79
|
+
* Factory App token cannot serve it (#825). `persist` and `recordCleanup`
|
|
80
|
+
* must use a Factory App installation token: GitHub refuses GITHUB_TOKEN
|
|
81
|
+
* writes to App-owned check-runs.
|
|
80
82
|
*/
|
|
81
83
|
token: string;
|
|
82
84
|
}
|
|
@@ -361,7 +363,25 @@ const githubGraphql = async (
|
|
|
361
363
|
throw new TypeError("GitHub GraphQL response was not an object");
|
|
362
364
|
}
|
|
363
365
|
if (served.errors !== undefined) {
|
|
364
|
-
|
|
366
|
+
const details = Array.isArray(served.errors)
|
|
367
|
+
? served.errors
|
|
368
|
+
.flatMap((entry) =>
|
|
369
|
+
isRecord(entry)
|
|
370
|
+
? [
|
|
371
|
+
[entry.type, entry.message]
|
|
372
|
+
.filter((part) => typeof part === "string")
|
|
373
|
+
.join(": "),
|
|
374
|
+
]
|
|
375
|
+
: []
|
|
376
|
+
)
|
|
377
|
+
.filter((detail) => detail.length > 0)
|
|
378
|
+
.join("; ")
|
|
379
|
+
: "";
|
|
380
|
+
throw new Error(
|
|
381
|
+
details.length > 0
|
|
382
|
+
? `Preview proof inventory GraphQL query failed: ${details}`
|
|
383
|
+
: "Preview proof inventory GraphQL query failed."
|
|
384
|
+
);
|
|
365
385
|
}
|
|
366
386
|
return served.data;
|
|
367
387
|
};
|
|
@@ -636,10 +656,20 @@ const listPullRequestCommitShas = (
|
|
|
636
656
|
"Preview proof inventory commit listing exhausted pagination before GitHub returned a final page."
|
|
637
657
|
);
|
|
638
658
|
|
|
659
|
+
const readPullBaseSha = (pull: Record<string, unknown>): string => {
|
|
660
|
+
if (!isRecord(pull.base) || typeof pull.base.sha !== "string") {
|
|
661
|
+
throw new TypeError("GitHub pull request response omitted base.sha");
|
|
662
|
+
}
|
|
663
|
+
return pull.base.sha;
|
|
664
|
+
};
|
|
665
|
+
|
|
639
666
|
/**
|
|
640
|
-
* Current head, replaced heads from force-push,
|
|
641
|
-
* pull-commits endpoint silently stops at 250 and
|
|
642
|
-
* head, so those two cases use compare pagination
|
|
667
|
+
* Current head, replaced heads from force-push, the commits each replaced head
|
|
668
|
+
* carried, and PR commits. The pull-commits endpoint silently stops at 250 and
|
|
669
|
+
* never lists a rebased-away head, so those two cases use compare pagination
|
|
670
|
+
* and the force-push timeline. A persist, then a fast-forward, then a rebase
|
|
671
|
+
* leaves the persist SHA behind the replaced head, so each replaced head is
|
|
672
|
+
* also walked back to the base (#796).
|
|
643
673
|
*/
|
|
644
674
|
const listCommitShasForPull = async (
|
|
645
675
|
request: typeof fetch,
|
|
@@ -674,16 +704,13 @@ const listCommitShasForPull = async (
|
|
|
674
704
|
);
|
|
675
705
|
let older = pullCommits;
|
|
676
706
|
if (pullCommits.length >= GITHUB_PULL_COMMITS_CAP) {
|
|
677
|
-
if (!isRecord(pull.base) || typeof pull.base.sha !== "string") {
|
|
678
|
-
throw new TypeError("GitHub pull request response omitted base.sha");
|
|
679
|
-
}
|
|
680
707
|
older = await listCompareCommitShas(
|
|
681
708
|
request,
|
|
682
709
|
token,
|
|
683
710
|
timeoutMs,
|
|
684
711
|
owner,
|
|
685
712
|
repo,
|
|
686
|
-
pull
|
|
713
|
+
readPullBaseSha(pull),
|
|
687
714
|
pull.head.sha
|
|
688
715
|
);
|
|
689
716
|
}
|
|
@@ -697,9 +724,34 @@ const listCommitShasForPull = async (
|
|
|
697
724
|
pr
|
|
698
725
|
);
|
|
699
726
|
|
|
727
|
+
let replaced: readonly string[] = [];
|
|
728
|
+
if (previousHeads.length > 0) {
|
|
729
|
+
const baseSha = readPullBaseSha(pull);
|
|
730
|
+
const histories = await mapPool(
|
|
731
|
+
previousHeads,
|
|
732
|
+
PREVIEW_PROOF_LIST_CONCURRENCY,
|
|
733
|
+
(previousHead) =>
|
|
734
|
+
listCompareCommitShas(
|
|
735
|
+
request,
|
|
736
|
+
token,
|
|
737
|
+
timeoutMs,
|
|
738
|
+
owner,
|
|
739
|
+
repo,
|
|
740
|
+
baseSha,
|
|
741
|
+
previousHead
|
|
742
|
+
)
|
|
743
|
+
);
|
|
744
|
+
replaced = histories.flatMap((history) => [...history].toReversed());
|
|
745
|
+
}
|
|
746
|
+
|
|
700
747
|
const shas: string[] = [];
|
|
701
748
|
const seen = new Set<string>();
|
|
702
|
-
for (const sha of [
|
|
749
|
+
for (const sha of [
|
|
750
|
+
pull.head.sha,
|
|
751
|
+
...previousHeads,
|
|
752
|
+
...older.toReversed(),
|
|
753
|
+
...replaced,
|
|
754
|
+
]) {
|
|
703
755
|
if (!seen.has(sha)) {
|
|
704
756
|
seen.add(sha);
|
|
705
757
|
shas.push(sha);
|