@patronage/factory-ci 1.0.0-alpha.18 → 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 +45 -4
- package/dist/index.js +83 -10
- package/package.json +1 -1
- package/src/index.ts +12 -0
- package/src/pr-status-hud-workflow.ts +96 -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
|
}
|
|
@@ -904,6 +906,45 @@ interface FactoryLifecycleContractLane {
|
|
|
904
906
|
*/
|
|
905
907
|
declare const factoryLifecycleContractLane: (declaration?: FactoryLifecycleContractLaneOptions) => FactoryLifecycleContractLane | undefined;
|
|
906
908
|
//#endregion
|
|
909
|
+
//#region src/pr-status-hud-workflow.d.ts
|
|
910
|
+
declare const FACTORY_PR_STATUS_HUD_JOB_ID = "status-hud";
|
|
911
|
+
declare const FACTORY_PR_STATUS_HUD_JOB_NAME = "Present PR status HUD";
|
|
912
|
+
declare const FACTORY_PR_STATUS_HUD_APP_TOKEN_STEP_ID = "factory-app-token";
|
|
913
|
+
declare const FACTORY_PR_STATUS_HUD_PLAN_PATH = ".factory-memory/preview-plan.json";
|
|
914
|
+
/**
|
|
915
|
+
* Presentation is independent of deploy demand. Callers who `needs` the jobs
|
|
916
|
+
* that settle HUD sources (verify, deploy) must keep this `always()` so a
|
|
917
|
+
* skipped or withdrawn deploy still publishes Skipped rows.
|
|
918
|
+
*/
|
|
919
|
+
declare const FACTORY_PR_STATUS_HUD_IF = "always() && github.event_name == 'pull_request' && github.event.pull_request.draft != true";
|
|
920
|
+
interface FactoryPrStatusHudWorkflowOptions {
|
|
921
|
+
readonly cli?: string;
|
|
922
|
+
readonly createGithubAppToken: PinnedAction;
|
|
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
|
+
}>;
|
|
934
|
+
declare const FACTORY_PR_STATUS_HUD_CONCURRENCY: Readonly<{
|
|
935
|
+
cancelInProgress: false;
|
|
936
|
+
group: "status-hud-${{ github.repository }}-${{ github.event.pull_request.number }}";
|
|
937
|
+
}>;
|
|
938
|
+
interface FactoryPrStatusHudWorkflow {
|
|
939
|
+
readonly concurrency: typeof FACTORY_PR_STATUS_HUD_CONCURRENCY;
|
|
940
|
+
readonly if: typeof FACTORY_PR_STATUS_HUD_IF;
|
|
941
|
+
readonly jobId: typeof FACTORY_PR_STATUS_HUD_JOB_ID;
|
|
942
|
+
readonly jobName: typeof FACTORY_PR_STATUS_HUD_JOB_NAME;
|
|
943
|
+
readonly permissions: typeof FACTORY_PR_STATUS_HUD_PERMISSIONS;
|
|
944
|
+
readonly presentSteps: readonly WorkflowStep[];
|
|
945
|
+
}
|
|
946
|
+
declare const factoryPrStatusHudWorkflow: (options: FactoryPrStatusHudWorkflowOptions) => FactoryPrStatusHudWorkflow;
|
|
947
|
+
//#endregion
|
|
907
948
|
//#region src/preview-cleanup-topology.d.ts
|
|
908
949
|
declare const FACTORY_PREVIEW_CLEANUP_AUDIT_JOB_ID = "stage-residue-audit";
|
|
909
950
|
declare const FACTORY_PREVIEW_CLEANUP_AUDIT_JOB_NAME = "Stage residue audit";
|
|
@@ -1301,4 +1342,4 @@ declare const assertWorkflowShellParses: (yaml: string, options: {
|
|
|
1301
1342
|
readonly source: string;
|
|
1302
1343
|
}) => void;
|
|
1303
1344
|
//#endregion
|
|
1304
|
-
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_PUSH_IDENTITY_SCHEMA_VERSION, type FactoryCandidateImpactWorkflow, type FactoryCandidateImpactWorkflowOptions, type FactoryLifecycleContractLane, type FactoryLifecycleContractLaneOptions, 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, 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);
|
|
@@ -2006,6 +2019,66 @@ const factoryLifecycleContractLane = (declaration) => {
|
|
|
2006
2019
|
});
|
|
2007
2020
|
};
|
|
2008
2021
|
//#endregion
|
|
2022
|
+
//#region src/pr-status-hud-workflow.ts
|
|
2023
|
+
const FACTORY_PR_STATUS_HUD_JOB_ID = "status-hud";
|
|
2024
|
+
const FACTORY_PR_STATUS_HUD_JOB_NAME = "Present PR status HUD";
|
|
2025
|
+
const FACTORY_PR_STATUS_HUD_APP_TOKEN_STEP_ID = "factory-app-token";
|
|
2026
|
+
const FACTORY_PR_STATUS_HUD_PLAN_PATH = ".factory-memory/preview-plan.json";
|
|
2027
|
+
/**
|
|
2028
|
+
* Presentation is independent of deploy demand. Callers who `needs` the jobs
|
|
2029
|
+
* that settle HUD sources (verify, deploy) must keep this `always()` so a
|
|
2030
|
+
* skipped or withdrawn deploy still publishes Skipped rows.
|
|
2031
|
+
*/
|
|
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
|
+
});
|
|
2043
|
+
const FACTORY_PR_STATUS_HUD_CONCURRENCY = Object.freeze({
|
|
2044
|
+
cancelInProgress: false,
|
|
2045
|
+
group: `status-hud-\${{ github.repository }}-\${{ github.event.pull_request.number }}`
|
|
2046
|
+
});
|
|
2047
|
+
const defaultCli = "psf";
|
|
2048
|
+
const factoryPrStatusHudWorkflow = (options) => {
|
|
2049
|
+
assertPinnedAction("createGithubAppToken", options.createGithubAppToken, "actions/create-github-app-token");
|
|
2050
|
+
const cli = options.cli || defaultCli;
|
|
2051
|
+
const mintStep = Object.freeze({
|
|
2052
|
+
id: FACTORY_PR_STATUS_HUD_APP_TOKEN_STEP_ID,
|
|
2053
|
+
name: "Mint the factory App token",
|
|
2054
|
+
uses: options.createGithubAppToken.uses,
|
|
2055
|
+
with: Object.freeze({
|
|
2056
|
+
"client-id": FACTORY_PROOF_GATE_APP_ID,
|
|
2057
|
+
"private-key": `\${{ secrets.FACTORY_GITHUB_APP_PRIVATE_KEY }}`
|
|
2058
|
+
})
|
|
2059
|
+
});
|
|
2060
|
+
const presentStep = Object.freeze({
|
|
2061
|
+
env: Object.freeze({
|
|
2062
|
+
FACTORY_GITHUB_APP_TOKEN: `\${{ steps.${FACTORY_PR_STATUS_HUD_APP_TOKEN_STEP_ID}.outputs.token }}`,
|
|
2063
|
+
FACTORY_HEAD_SHA: `\${{ github.event.pull_request.head.sha }}`,
|
|
2064
|
+
FACTORY_OWNER: `\${{ github.repository_owner }}`,
|
|
2065
|
+
FACTORY_PR: `\${{ github.event.pull_request.number }}`,
|
|
2066
|
+
FACTORY_REPO: `\${{ github.event.repository.name }}`,
|
|
2067
|
+
GITHUB_TOKEN: `\${{ github.token }}`
|
|
2068
|
+
}),
|
|
2069
|
+
name: "Present the PR status HUD",
|
|
2070
|
+
run: `${cli} pr:status-hud --head "$FACTORY_HEAD_SHA" --owner "$FACTORY_OWNER" --repo "$FACTORY_REPO" --pr "$FACTORY_PR"`
|
|
2071
|
+
});
|
|
2072
|
+
return Object.freeze({
|
|
2073
|
+
concurrency: FACTORY_PR_STATUS_HUD_CONCURRENCY,
|
|
2074
|
+
if: FACTORY_PR_STATUS_HUD_IF,
|
|
2075
|
+
jobId: FACTORY_PR_STATUS_HUD_JOB_ID,
|
|
2076
|
+
jobName: FACTORY_PR_STATUS_HUD_JOB_NAME,
|
|
2077
|
+
permissions: FACTORY_PR_STATUS_HUD_PERMISSIONS,
|
|
2078
|
+
presentSteps: Object.freeze([mintStep, presentStep])
|
|
2079
|
+
});
|
|
2080
|
+
};
|
|
2081
|
+
//#endregion
|
|
2009
2082
|
//#region src/preview-cleanup-topology.ts
|
|
2010
2083
|
const FACTORY_PREVIEW_CLEANUP_AUDIT_JOB_ID = "stage-residue-audit";
|
|
2011
2084
|
const FACTORY_PREVIEW_CLEANUP_AUDIT_JOB_NAME = "Stage residue audit";
|
|
@@ -3126,4 +3199,4 @@ const assertWorkflowShellParses = (yaml, options) => {
|
|
|
3126
3199
|
throw new Error(`${options.source} emits shell bash cannot parse; the runner would treat it as a no-op:\n${detail}`);
|
|
3127
3200
|
};
|
|
3128
3201
|
//#endregion
|
|
3129
|
-
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_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, 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
|
@@ -124,6 +124,18 @@ export {
|
|
|
124
124
|
type FactoryLifecycleContractLaneOptions,
|
|
125
125
|
factoryLifecycleContractLane,
|
|
126
126
|
} from "./lifecycle-contract-lane.ts";
|
|
127
|
+
export {
|
|
128
|
+
FACTORY_PR_STATUS_HUD_APP_TOKEN_STEP_ID,
|
|
129
|
+
FACTORY_PR_STATUS_HUD_CONCURRENCY,
|
|
130
|
+
FACTORY_PR_STATUS_HUD_IF,
|
|
131
|
+
FACTORY_PR_STATUS_HUD_JOB_ID,
|
|
132
|
+
FACTORY_PR_STATUS_HUD_JOB_NAME,
|
|
133
|
+
FACTORY_PR_STATUS_HUD_PERMISSIONS,
|
|
134
|
+
FACTORY_PR_STATUS_HUD_PLAN_PATH,
|
|
135
|
+
type FactoryPrStatusHudWorkflow,
|
|
136
|
+
type FactoryPrStatusHudWorkflowOptions,
|
|
137
|
+
factoryPrStatusHudWorkflow,
|
|
138
|
+
} from "./pr-status-hud-workflow.ts";
|
|
127
139
|
export {
|
|
128
140
|
FACTORY_PREVIEW_CLEANUP_AUDIT_JOB_ID,
|
|
129
141
|
FACTORY_PREVIEW_CLEANUP_AUDIT_JOB_NAME,
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { PinnedAction } from "./actions.ts";
|
|
2
|
+
import type { WorkflowStep } from "./factory-workflow.ts";
|
|
3
|
+
import { assertPinnedAction } from "./pinned-action.ts";
|
|
4
|
+
import { FACTORY_PROOF_GATE_APP_ID } from "./proof-reuse-gate.ts";
|
|
5
|
+
|
|
6
|
+
export const FACTORY_PR_STATUS_HUD_JOB_ID = "status-hud";
|
|
7
|
+
export const FACTORY_PR_STATUS_HUD_JOB_NAME = "Present PR status HUD";
|
|
8
|
+
export const FACTORY_PR_STATUS_HUD_APP_TOKEN_STEP_ID = "factory-app-token";
|
|
9
|
+
export const FACTORY_PR_STATUS_HUD_PLAN_PATH =
|
|
10
|
+
".factory-memory/preview-plan.json";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Presentation is independent of deploy demand. Callers who `needs` the jobs
|
|
14
|
+
* that settle HUD sources (verify, deploy) must keep this `always()` so a
|
|
15
|
+
* skipped or withdrawn deploy still publishes Skipped rows.
|
|
16
|
+
*/
|
|
17
|
+
export const FACTORY_PR_STATUS_HUD_IF =
|
|
18
|
+
"always() && github.event_name == 'pull_request' && github.event.pull_request.draft != true";
|
|
19
|
+
|
|
20
|
+
export interface FactoryPrStatusHudWorkflowOptions {
|
|
21
|
+
readonly cli?: string;
|
|
22
|
+
readonly createGithubAppToken: PinnedAction;
|
|
23
|
+
}
|
|
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
|
+
|
|
36
|
+
export const FACTORY_PR_STATUS_HUD_CONCURRENCY = Object.freeze({
|
|
37
|
+
cancelInProgress: false,
|
|
38
|
+
group: `status-hud-\${{ github.repository }}-\${{ github.event.pull_request.number }}`,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export interface FactoryPrStatusHudWorkflow {
|
|
42
|
+
readonly concurrency: typeof FACTORY_PR_STATUS_HUD_CONCURRENCY;
|
|
43
|
+
readonly if: typeof FACTORY_PR_STATUS_HUD_IF;
|
|
44
|
+
readonly jobId: typeof FACTORY_PR_STATUS_HUD_JOB_ID;
|
|
45
|
+
readonly jobName: typeof FACTORY_PR_STATUS_HUD_JOB_NAME;
|
|
46
|
+
readonly permissions: typeof FACTORY_PR_STATUS_HUD_PERMISSIONS;
|
|
47
|
+
readonly presentSteps: readonly WorkflowStep[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const defaultCli = "psf";
|
|
51
|
+
|
|
52
|
+
export const factoryPrStatusHudWorkflow = (
|
|
53
|
+
options: FactoryPrStatusHudWorkflowOptions
|
|
54
|
+
): FactoryPrStatusHudWorkflow => {
|
|
55
|
+
assertPinnedAction(
|
|
56
|
+
"createGithubAppToken",
|
|
57
|
+
options.createGithubAppToken,
|
|
58
|
+
"actions/create-github-app-token"
|
|
59
|
+
);
|
|
60
|
+
const cli = options.cli || defaultCli;
|
|
61
|
+
// Same unscoped mint as the freeze job. A subset (checks/issues/PRs)
|
|
62
|
+
// 422s: those permissions are not granted to this installation.
|
|
63
|
+
const mintStep: WorkflowStep = Object.freeze({
|
|
64
|
+
id: FACTORY_PR_STATUS_HUD_APP_TOKEN_STEP_ID,
|
|
65
|
+
name: "Mint the factory App token",
|
|
66
|
+
uses: options.createGithubAppToken.uses,
|
|
67
|
+
with: Object.freeze({
|
|
68
|
+
"client-id": FACTORY_PROOF_GATE_APP_ID,
|
|
69
|
+
"private-key": `\${{ secrets.FACTORY_GITHUB_APP_PRIVATE_KEY }}`,
|
|
70
|
+
}),
|
|
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).
|
|
75
|
+
const presentStep: WorkflowStep = Object.freeze({
|
|
76
|
+
env: Object.freeze({
|
|
77
|
+
FACTORY_GITHUB_APP_TOKEN: `\${{ steps.${FACTORY_PR_STATUS_HUD_APP_TOKEN_STEP_ID}.outputs.token }}`,
|
|
78
|
+
FACTORY_HEAD_SHA: `\${{ github.event.pull_request.head.sha }}`,
|
|
79
|
+
FACTORY_OWNER: `\${{ github.repository_owner }}`,
|
|
80
|
+
FACTORY_PR: `\${{ github.event.pull_request.number }}`,
|
|
81
|
+
FACTORY_REPO: `\${{ github.event.repository.name }}`,
|
|
82
|
+
GITHUB_TOKEN: `\${{ github.token }}`,
|
|
83
|
+
}),
|
|
84
|
+
name: "Present the PR status HUD",
|
|
85
|
+
run: `${cli} pr:status-hud --head "$FACTORY_HEAD_SHA" --owner "$FACTORY_OWNER" --repo "$FACTORY_REPO" --pr "$FACTORY_PR"`,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return Object.freeze({
|
|
89
|
+
concurrency: FACTORY_PR_STATUS_HUD_CONCURRENCY,
|
|
90
|
+
if: FACTORY_PR_STATUS_HUD_IF,
|
|
91
|
+
jobId: FACTORY_PR_STATUS_HUD_JOB_ID,
|
|
92
|
+
jobName: FACTORY_PR_STATUS_HUD_JOB_NAME,
|
|
93
|
+
permissions: FACTORY_PR_STATUS_HUD_PERMISSIONS,
|
|
94
|
+
presentSteps: Object.freeze([mintStep, presentStep]),
|
|
95
|
+
});
|
|
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);
|