@vypdev/copilot 3.5.1 → 3.5.3
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/build/cli/index.js
CHANGED
|
@@ -44015,7 +44015,7 @@ exports.ENGLISH_MERGE_QUEUE_MESSAGES = Object.freeze({
|
|
|
44015
44015
|
'mergeQueue.decision.queueNotExposed': 'The target does not expose a required merge queue.',
|
|
44016
44016
|
'mergeQueue.decision.autoMergeConfigured': 'Native auto-merge was explicitly configured.',
|
|
44017
44017
|
'mergeQueue.decision.autoMergeDisabled': 'Native auto-merge is disabled for this repository.',
|
|
44018
|
-
'mergeQueue.decision.immediatelyMergeable': 'GitHub reports the pull request ready;
|
|
44018
|
+
'mergeQueue.decision.immediatelyMergeable': 'GitHub reports the pull request ready; merge its verified head with branch protection enforced.',
|
|
44019
44019
|
'mergeQueue.decision.autoMergeAvailable': 'GitHub will merge after checks and reviews complete.',
|
|
44020
44020
|
'mergeQueue.decision.createOnlyRequired': 'Repository auto-merge is unavailable; maintainer merge is required.',
|
|
44021
44021
|
});
|
|
@@ -44040,7 +44040,7 @@ exports.SPANISH_MERGE_QUEUE_MESSAGES = Object.freeze({
|
|
|
44040
44040
|
'mergeQueue.decision.queueNotExposed': 'El destino no expone una merge queue obligatoria.',
|
|
44041
44041
|
'mergeQueue.decision.autoMergeConfigured': 'El auto-merge nativo se ha configurado explícitamente.',
|
|
44042
44042
|
'mergeQueue.decision.autoMergeDisabled': 'El auto-merge nativo está deshabilitado en este repositorio.',
|
|
44043
|
-
'mergeQueue.decision.immediatelyMergeable': 'GitHub indica que la pull request está lista;
|
|
44043
|
+
'mergeQueue.decision.immediatelyMergeable': 'GitHub indica que la pull request está lista; fusionar su head verificado respetando la protección de rama.',
|
|
44044
44044
|
'mergeQueue.decision.autoMergeAvailable': 'GitHub hará merge cuando terminen los checks y las revisiones.',
|
|
44045
44045
|
'mergeQueue.decision.createOnlyRequired': 'El auto-merge del repositorio no está disponible; se requiere el merge de una persona mantenedora.',
|
|
44046
44046
|
});
|
|
@@ -51946,7 +51946,14 @@ class DeploymentOrchestrationRuntime {
|
|
|
51946
51946
|
selectedPrMode: inspection.decision.mode,
|
|
51947
51947
|
};
|
|
51948
51948
|
await this.persist(context, managed);
|
|
51949
|
-
|
|
51949
|
+
try {
|
|
51950
|
+
await this.applyMergeBehavior(context, operation, pullRequest, inspection);
|
|
51951
|
+
}
|
|
51952
|
+
catch (cause) {
|
|
51953
|
+
const message = `GitHub could not configure automatic merge for PR #${pullRequest.number}. Check the PR and retry the stored ${category} phase.`;
|
|
51954
|
+
const error = new application_error_1.ApplicationError("provider.unavailable", message, { cause, retryable: true });
|
|
51955
|
+
return { kind: "blocked", result: await this.block(context, managed, category, message, true, error) };
|
|
51956
|
+
}
|
|
51950
51957
|
await this.publishDashboard(context, managed);
|
|
51951
51958
|
return { kind: "configured", operation: managed };
|
|
51952
51959
|
}
|
|
@@ -52014,7 +52021,7 @@ class DeploymentOrchestrationRuntime {
|
|
|
52014
52021
|
}
|
|
52015
52022
|
async applyAutoMerge(context, operation, pullRequest, capabilities) {
|
|
52016
52023
|
if (operation.prMode === "auto" && capabilities.immediatelyMergeable) {
|
|
52017
|
-
await this.dependencies.pullRequests.mergePullRequest(pullRequest.number);
|
|
52024
|
+
await this.dependencies.pullRequests.mergePullRequest(pullRequest.number, pullRequest.headSha);
|
|
52018
52025
|
return;
|
|
52019
52026
|
}
|
|
52020
52027
|
if (!pullRequest.autoMergeEnabled) {
|
|
@@ -52876,22 +52883,19 @@ const application_error_1 = __nccwpck_require__(75999);
|
|
|
52876
52883
|
const deployment_plan_policy_1 = __nccwpck_require__(8352);
|
|
52877
52884
|
const deployment_operation_1 = __nccwpck_require__(92730);
|
|
52878
52885
|
const deployment_orchestration_runtime_1 = __nccwpck_require__(77658);
|
|
52886
|
+
const deployment_continuation_guard_1 = __nccwpck_require__(1779);
|
|
52879
52887
|
class ConfirmPublicationHandler {
|
|
52880
52888
|
constructor(runtime, reconciliation) {
|
|
52881
52889
|
this.runtime = runtime;
|
|
52882
52890
|
this.reconciliation = reconciliation;
|
|
52883
52891
|
}
|
|
52884
52892
|
async invoke(context) {
|
|
52885
|
-
const
|
|
52886
|
-
|
|
52887
|
-
|
|
52888
|
-
|
|
52889
|
-
|
|
52890
|
-
|
|
52891
|
-
await this.runtime.publishDashboard(context, operation);
|
|
52892
|
-
return (0, deployment_orchestration_runtime_1.deploymentSuccess)(`Publication for ${operation.tag} was already verified; duplicate notification ignored.`);
|
|
52893
|
-
}
|
|
52894
|
-
assertPublicationPhase(operation);
|
|
52893
|
+
const current = (0, deployment_orchestration_runtime_1.requireDeploymentOperation)(context);
|
|
52894
|
+
const invalid = (0, deployment_continuation_guard_1.validateDeploymentContinuation)(current, context.singleAction.operationId, ["published", "publishing", "promoted", "reconciliation_pending", "completed"], context.singleAction.version);
|
|
52895
|
+
if (invalid)
|
|
52896
|
+
throw new application_error_1.ApplicationError("workflow.stale", invalid);
|
|
52897
|
+
// Verify external receipts before clearing a block or retrying any merge/cleanup effect.
|
|
52898
|
+
const operation = current;
|
|
52895
52899
|
const productionSha = requireProductionSha(operation);
|
|
52896
52900
|
const publication = await this.runtime.dependencies.publication.inspect({
|
|
52897
52901
|
tag: operation.tag,
|
|
@@ -52908,8 +52912,18 @@ class ConfirmPublicationHandler {
|
|
|
52908
52912
|
if (!reachable) {
|
|
52909
52913
|
return await this.runtime.block(context, operation, "publication", "Published SHA is not reachable from the stored production branch.", false);
|
|
52910
52914
|
}
|
|
52915
|
+
const resumed = await this.resumeRetryableBlock(context, operation);
|
|
52916
|
+
if (resumed.phase === "reconciliation_pending" && resumed.publicationVerified) {
|
|
52917
|
+
return await this.reconciliation.plan(context, resumed)
|
|
52918
|
+
?? (0, deployment_orchestration_runtime_1.deploymentSuccess)(`Publication for ${resumed.tag} was already verified; reconciliation state was recovered.`);
|
|
52919
|
+
}
|
|
52920
|
+
if (resumed.phase === "completed" && resumed.publicationVerified) {
|
|
52921
|
+
await this.runtime.publishDashboard(context, resumed);
|
|
52922
|
+
return (0, deployment_orchestration_runtime_1.deploymentSuccess)(`Publication for ${resumed.tag} was already verified; duplicate notification ignored.`);
|
|
52923
|
+
}
|
|
52924
|
+
assertPublicationPhase(resumed);
|
|
52911
52925
|
const published = {
|
|
52912
|
-
...
|
|
52926
|
+
...resumed,
|
|
52913
52927
|
phase: "published",
|
|
52914
52928
|
publicationVerified: true,
|
|
52915
52929
|
publicationReceipt: publication.receipt,
|
|
@@ -53228,6 +53242,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
|
53228
53242
|
exports.ReconciliationHandler = void 0;
|
|
53229
53243
|
const deployment_plan_policy_1 = __nccwpck_require__(8352);
|
|
53230
53244
|
const deployment_operation_1 = __nccwpck_require__(92730);
|
|
53245
|
+
const managed_pull_request_1 = __nccwpck_require__(95914);
|
|
53231
53246
|
const deployment_orchestration_runtime_1 = __nccwpck_require__(77658);
|
|
53232
53247
|
class ReconciliationHandler {
|
|
53233
53248
|
constructor(runtime) {
|
|
@@ -53264,10 +53279,12 @@ class ReconciliationHandler {
|
|
|
53264
53279
|
return await this.complete(context, updated, ` after reconciliation PR #${pullRequest.number}`);
|
|
53265
53280
|
}
|
|
53266
53281
|
async plan(context, operation) {
|
|
53267
|
-
const index = operation.reconciliationTargets.findIndex((target) => target.status === "pending"
|
|
53282
|
+
const index = operation.reconciliationTargets.findIndex((target) => target.status === "pending");
|
|
53268
53283
|
if (index < 0)
|
|
53269
53284
|
return await this.finishOrPresent(context, operation);
|
|
53270
53285
|
let target = operation.reconciliationTargets[index];
|
|
53286
|
+
if (target.pullRequest !== undefined)
|
|
53287
|
+
return await this.resumePullRequest(context, operation, target);
|
|
53271
53288
|
const targetRole = (0, deployment_orchestration_runtime_1.reconciliationTargetRole)(operation, target.targetBranch);
|
|
53272
53289
|
const preflight = await this.runtime.inspectMergeBehavior(context, operation, target.targetBranch, targetRole, target.sourceSha);
|
|
53273
53290
|
if (preflight.kind === "blocked") {
|
|
@@ -53324,6 +53341,26 @@ class ReconciliationHandler {
|
|
|
53324
53341
|
await this.runtime.publishMilestone(context, completed, { kind: "orchestration-complete", tag: completed.tag });
|
|
53325
53342
|
return (0, deployment_orchestration_runtime_1.deploymentSuccess)(`Deployment ${completed.tag} completed${completionContext}.`);
|
|
53326
53343
|
}
|
|
53344
|
+
async resumePullRequest(context, operation, target) {
|
|
53345
|
+
const pullRequest = await this.runtime.dependencies.pullRequests.getPullRequest(target.pullRequest);
|
|
53346
|
+
const identity = (0, managed_pull_request_1.parseManagedPullRequestMarker)(pullRequest.body);
|
|
53347
|
+
const expectedHead = target.syncBranch ? target.syncSha : target.sourceSha;
|
|
53348
|
+
if (!identity || identity.operationId !== operation.operationId || identity.phase !== "reconciliation"
|
|
53349
|
+
|| identity.issue !== context.singleAction.issue || pullRequest.number !== target.pullRequest
|
|
53350
|
+
|| pullRequest.repositoryFullName.toLowerCase() !== `${context.owner}/${context.repo}`.toLowerCase()
|
|
53351
|
+
|| pullRequest.baseBranch !== target.targetBranch
|
|
53352
|
+
|| pullRequest.headBranch !== (target.syncBranch ?? target.sourceBranch)
|
|
53353
|
+
|| !expectedHead || pullRequest.headSha !== expectedHead) {
|
|
53354
|
+
return await this.runtime.block(context, operation, "reconciliation", "The recorded reconciliation PR no longer matches its durable identity and head SHA.", false);
|
|
53355
|
+
}
|
|
53356
|
+
if (pullRequest.merged)
|
|
53357
|
+
return await this.accept(context, operation, pullRequest);
|
|
53358
|
+
if (pullRequest.state === "closed") {
|
|
53359
|
+
return await this.runtime.block(context, operation, "reconciliation", `Reconciliation PR #${pullRequest.number} was closed without merge.`, true);
|
|
53360
|
+
}
|
|
53361
|
+
const configured = await this.runtime.configureMergeBehavior(context, operation, pullRequest, "reconciliation", (0, deployment_orchestration_runtime_1.reconciliationTargetRole)(operation, target.targetBranch));
|
|
53362
|
+
return configured.kind === "blocked" ? configured.result : undefined;
|
|
53363
|
+
}
|
|
53327
53364
|
async finishOrPresent(context, operation) {
|
|
53328
53365
|
if (operation.reconciliationTargets.length > 0
|
|
53329
53366
|
&& operation.reconciliationTargets.every((target) => target.status === "completed")) {
|
|
@@ -79383,12 +79420,13 @@ class GithubManagedPullRequestRepository {
|
|
|
79383
79420
|
throw new Error("GitHub did not confirm that the pull request entered the merge queue.");
|
|
79384
79421
|
}
|
|
79385
79422
|
}
|
|
79386
|
-
async mergePullRequest(owner, repository, pullRequest, token) {
|
|
79423
|
+
async mergePullRequest(owner, repository, pullRequest, expectedHeadSha, token) {
|
|
79387
79424
|
const { data } = await this.clientProvider.getClient(token).rest.pulls.merge({
|
|
79388
79425
|
owner,
|
|
79389
79426
|
repo: repository,
|
|
79390
79427
|
pull_number: pullRequest,
|
|
79391
79428
|
merge_method: "merge",
|
|
79429
|
+
sha: expectedHeadSha,
|
|
79392
79430
|
});
|
|
79393
79431
|
if (!data.merged || !data.sha)
|
|
79394
79432
|
throw new Error(data.message ?? `Pull request #${pullRequest} was not merged.`);
|
|
@@ -79413,6 +79451,43 @@ function mapPullRequest(value, owner, repository) {
|
|
|
79413
79451
|
}
|
|
79414
79452
|
|
|
79415
79453
|
|
|
79454
|
+
/***/ }),
|
|
79455
|
+
|
|
79456
|
+
/***/ 33371:
|
|
79457
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
79458
|
+
|
|
79459
|
+
"use strict";
|
|
79460
|
+
|
|
79461
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
79462
|
+
exports.observePullRequestReadiness = observePullRequestReadiness;
|
|
79463
|
+
async function observePullRequestReadiness(client, owner, repository, pullRequest) {
|
|
79464
|
+
try {
|
|
79465
|
+
const response = await client.graphql(`query DeploymentPullRequestReadiness($owner: String!, $repository: String!, $pullRequest: Int!) {
|
|
79466
|
+
repository(owner: $owner, name: $repository) {
|
|
79467
|
+
pullRequest(number: $pullRequest) { headRefOid mergeable mergeStateStatus isDraft }
|
|
79468
|
+
}
|
|
79469
|
+
}`, { owner, repository, pullRequest });
|
|
79470
|
+
const state = response?.repository?.pullRequest;
|
|
79471
|
+
if (!state || typeof state.headRefOid !== "string" || !/^[a-f0-9]{40}$/i.test(state.headRefOid)
|
|
79472
|
+
|| typeof state.mergeable !== "string" || !["MERGEABLE", "CONFLICTING", "UNKNOWN"].includes(state.mergeable)
|
|
79473
|
+
|| typeof state.mergeStateStatus !== "string" || state.mergeStateStatus.length === 0
|
|
79474
|
+
|| typeof state.isDraft !== "boolean") {
|
|
79475
|
+
return { problem: { area: "workflow-contract", message: "GitHub returned incomplete pull-request merge readiness." } };
|
|
79476
|
+
}
|
|
79477
|
+
return {
|
|
79478
|
+
value: {
|
|
79479
|
+
headSha: state.headRefOid,
|
|
79480
|
+
immediatelyMergeable: !state.isDraft && state.mergeable === "MERGEABLE"
|
|
79481
|
+
&& ["CLEAN", "UNSTABLE"].includes(state.mergeStateStatus),
|
|
79482
|
+
},
|
|
79483
|
+
};
|
|
79484
|
+
}
|
|
79485
|
+
catch {
|
|
79486
|
+
return { problem: { area: "workflow-contract", message: "GitHub pull-request merge readiness could not be inspected." } };
|
|
79487
|
+
}
|
|
79488
|
+
}
|
|
79489
|
+
|
|
79490
|
+
|
|
79416
79491
|
/***/ }),
|
|
79417
79492
|
|
|
79418
79493
|
/***/ 55527:
|
|
@@ -79456,6 +79531,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
79456
79531
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
79457
79532
|
exports.GithubTargetMergeCapabilitiesInspector = void 0;
|
|
79458
79533
|
const yaml = __importStar(__nccwpck_require__(783));
|
|
79534
|
+
const github_pull_request_readiness_query_1 = __nccwpck_require__(33371);
|
|
79459
79535
|
class GithubTargetMergeCapabilitiesInspector {
|
|
79460
79536
|
constructor(clientProvider) {
|
|
79461
79537
|
this.clientProvider = clientProvider;
|
|
@@ -79468,8 +79544,8 @@ class GithubTargetMergeCapabilitiesInspector {
|
|
|
79468
79544
|
observeEffectiveRules(client, owner, repository, targetBranch),
|
|
79469
79545
|
observeBranchMergeQueue(client, owner, repository, targetBranch),
|
|
79470
79546
|
options.pullRequest === undefined
|
|
79471
|
-
? Promise.resolve(undefined)
|
|
79472
|
-
:
|
|
79547
|
+
? Promise.resolve({ value: undefined, problem: undefined })
|
|
79548
|
+
: (0, github_pull_request_readiness_query_1.observePullRequestReadiness)(client, owner, repository, options.pullRequest),
|
|
79473
79549
|
]);
|
|
79474
79550
|
const candidateProblem = options.candidateHeadSha !== undefined && !/^[a-f0-9]{40}$/i.test(options.candidateHeadSha)
|
|
79475
79551
|
? {
|
|
@@ -79479,25 +79555,26 @@ class GithubTargetMergeCapabilitiesInspector {
|
|
|
79479
79555
|
: undefined;
|
|
79480
79556
|
const candidateHeadChangedProblem = options.candidateHeadSha !== undefined
|
|
79481
79557
|
&& candidateProblem === undefined
|
|
79482
|
-
&& pullRequestState !== undefined
|
|
79483
|
-
&& pullRequestState.
|
|
79558
|
+
&& pullRequestState.value !== undefined
|
|
79559
|
+
&& pullRequestState.value.headSha !== options.candidateHeadSha
|
|
79484
79560
|
? {
|
|
79485
79561
|
area: "workflow-contract",
|
|
79486
79562
|
message: "The pull request head changed during readiness inspection, so the observed workflow contract is stale.",
|
|
79487
79563
|
}
|
|
79488
79564
|
: undefined;
|
|
79489
79565
|
const effective = normalizeEffectiveRules(classic.value, rulesets.value);
|
|
79490
|
-
const problems = [classic.problem, rulesets.problem, queue.problem,
|
|
79566
|
+
const problems = [classic.problem, rulesets.problem, queue.problem, pullRequestState.problem,
|
|
79567
|
+
candidateProblem, candidateHeadChangedProblem, ...effective.problems]
|
|
79491
79568
|
.filter((problem) => problem !== undefined);
|
|
79492
79569
|
const mergeQueueRequired = queue.value === true || effective.mergeQueueRequired;
|
|
79493
|
-
const candidateHeadSha = options.candidateHeadSha ?? pullRequestState?.
|
|
79570
|
+
const candidateHeadSha = options.candidateHeadSha ?? pullRequestState.value?.headSha;
|
|
79494
79571
|
const producerInspection = mergeQueueRequired
|
|
79495
79572
|
? await inspectMergeQueueProducers(client, owner, repository, repositoryData.id, targetBranch, candidateHeadSha, effective.requiredChecks, effective.requiredWorkflows)
|
|
79496
79573
|
: { producers: [], problems: [] };
|
|
79497
79574
|
return {
|
|
79498
79575
|
autoMergeAllowed: repositoryData.allow_auto_merge === true,
|
|
79499
79576
|
mergeQueueRequired,
|
|
79500
|
-
immediatelyMergeable:
|
|
79577
|
+
immediatelyMergeable: problems.length === 0 && pullRequestState.value?.immediatelyMergeable === true,
|
|
79501
79578
|
requiresStrictStatusChecks: effective.requiresStrictStatusChecks,
|
|
79502
79579
|
mergeQueueProducers: producerInspection.producers,
|
|
79503
79580
|
mergeQueueObservationProblems: [...problems, ...producerInspection.problems],
|
|
@@ -92399,7 +92476,7 @@ function bindManagedPullRequests(port, binding) {
|
|
|
92399
92476
|
enableAutoMerge: (pullRequestNodeId) => port.enableAutoMerge(binding.owner, binding.repository, pullRequestNodeId, binding.token),
|
|
92400
92477
|
isPullRequestQueued: (pullRequestNodeId) => port.isPullRequestQueued(binding.owner, binding.repository, pullRequestNodeId, binding.token),
|
|
92401
92478
|
enqueuePullRequest: (pullRequestNodeId, expectedHeadSha) => port.enqueuePullRequest(binding.owner, binding.repository, pullRequestNodeId, expectedHeadSha, binding.token),
|
|
92402
|
-
mergePullRequest: (pullRequest) => port.mergePullRequest(binding.owner, binding.repository, pullRequest, binding.token),
|
|
92479
|
+
mergePullRequest: (pullRequest, expectedHeadSha) => port.mergePullRequest(binding.owner, binding.repository, pullRequest, expectedHeadSha, binding.token),
|
|
92403
92480
|
});
|
|
92404
92481
|
}
|
|
92405
92482
|
function bindDeploymentTargetRules(port, binding) {
|
|
@@ -45553,7 +45553,7 @@ exports.ENGLISH_MERGE_QUEUE_MESSAGES = Object.freeze({
|
|
|
45553
45553
|
'mergeQueue.decision.queueNotExposed': 'The target does not expose a required merge queue.',
|
|
45554
45554
|
'mergeQueue.decision.autoMergeConfigured': 'Native auto-merge was explicitly configured.',
|
|
45555
45555
|
'mergeQueue.decision.autoMergeDisabled': 'Native auto-merge is disabled for this repository.',
|
|
45556
|
-
'mergeQueue.decision.immediatelyMergeable': 'GitHub reports the pull request ready;
|
|
45556
|
+
'mergeQueue.decision.immediatelyMergeable': 'GitHub reports the pull request ready; merge its verified head with branch protection enforced.',
|
|
45557
45557
|
'mergeQueue.decision.autoMergeAvailable': 'GitHub will merge after checks and reviews complete.',
|
|
45558
45558
|
'mergeQueue.decision.createOnlyRequired': 'Repository auto-merge is unavailable; maintainer merge is required.',
|
|
45559
45559
|
});
|
|
@@ -45578,7 +45578,7 @@ exports.SPANISH_MERGE_QUEUE_MESSAGES = Object.freeze({
|
|
|
45578
45578
|
'mergeQueue.decision.queueNotExposed': 'El destino no expone una merge queue obligatoria.',
|
|
45579
45579
|
'mergeQueue.decision.autoMergeConfigured': 'El auto-merge nativo se ha configurado explícitamente.',
|
|
45580
45580
|
'mergeQueue.decision.autoMergeDisabled': 'El auto-merge nativo está deshabilitado en este repositorio.',
|
|
45581
|
-
'mergeQueue.decision.immediatelyMergeable': 'GitHub indica que la pull request está lista;
|
|
45581
|
+
'mergeQueue.decision.immediatelyMergeable': 'GitHub indica que la pull request está lista; fusionar su head verificado respetando la protección de rama.',
|
|
45582
45582
|
'mergeQueue.decision.autoMergeAvailable': 'GitHub hará merge cuando terminen los checks y las revisiones.',
|
|
45583
45583
|
'mergeQueue.decision.createOnlyRequired': 'El auto-merge del repositorio no está disponible; se requiere el merge de una persona mantenedora.',
|
|
45584
45584
|
});
|
|
@@ -50260,7 +50260,14 @@ class DeploymentOrchestrationRuntime {
|
|
|
50260
50260
|
selectedPrMode: inspection.decision.mode,
|
|
50261
50261
|
};
|
|
50262
50262
|
await this.persist(context, managed);
|
|
50263
|
-
|
|
50263
|
+
try {
|
|
50264
|
+
await this.applyMergeBehavior(context, operation, pullRequest, inspection);
|
|
50265
|
+
}
|
|
50266
|
+
catch (cause) {
|
|
50267
|
+
const message = `GitHub could not configure automatic merge for PR #${pullRequest.number}. Check the PR and retry the stored ${category} phase.`;
|
|
50268
|
+
const error = new application_error_1.ApplicationError("provider.unavailable", message, { cause, retryable: true });
|
|
50269
|
+
return { kind: "blocked", result: await this.block(context, managed, category, message, true, error) };
|
|
50270
|
+
}
|
|
50264
50271
|
await this.publishDashboard(context, managed);
|
|
50265
50272
|
return { kind: "configured", operation: managed };
|
|
50266
50273
|
}
|
|
@@ -50328,7 +50335,7 @@ class DeploymentOrchestrationRuntime {
|
|
|
50328
50335
|
}
|
|
50329
50336
|
async applyAutoMerge(context, operation, pullRequest, capabilities) {
|
|
50330
50337
|
if (operation.prMode === "auto" && capabilities.immediatelyMergeable) {
|
|
50331
|
-
await this.dependencies.pullRequests.mergePullRequest(pullRequest.number);
|
|
50338
|
+
await this.dependencies.pullRequests.mergePullRequest(pullRequest.number, pullRequest.headSha);
|
|
50332
50339
|
return;
|
|
50333
50340
|
}
|
|
50334
50341
|
if (!pullRequest.autoMergeEnabled) {
|
|
@@ -51190,22 +51197,19 @@ const application_error_1 = __nccwpck_require__(75999);
|
|
|
51190
51197
|
const deployment_plan_policy_1 = __nccwpck_require__(8352);
|
|
51191
51198
|
const deployment_operation_1 = __nccwpck_require__(92730);
|
|
51192
51199
|
const deployment_orchestration_runtime_1 = __nccwpck_require__(77658);
|
|
51200
|
+
const deployment_continuation_guard_1 = __nccwpck_require__(1779);
|
|
51193
51201
|
class ConfirmPublicationHandler {
|
|
51194
51202
|
constructor(runtime, reconciliation) {
|
|
51195
51203
|
this.runtime = runtime;
|
|
51196
51204
|
this.reconciliation = reconciliation;
|
|
51197
51205
|
}
|
|
51198
51206
|
async invoke(context) {
|
|
51199
|
-
const
|
|
51200
|
-
|
|
51201
|
-
|
|
51202
|
-
|
|
51203
|
-
|
|
51204
|
-
|
|
51205
|
-
await this.runtime.publishDashboard(context, operation);
|
|
51206
|
-
return (0, deployment_orchestration_runtime_1.deploymentSuccess)(`Publication for ${operation.tag} was already verified; duplicate notification ignored.`);
|
|
51207
|
-
}
|
|
51208
|
-
assertPublicationPhase(operation);
|
|
51207
|
+
const current = (0, deployment_orchestration_runtime_1.requireDeploymentOperation)(context);
|
|
51208
|
+
const invalid = (0, deployment_continuation_guard_1.validateDeploymentContinuation)(current, context.singleAction.operationId, ["published", "publishing", "promoted", "reconciliation_pending", "completed"], context.singleAction.version);
|
|
51209
|
+
if (invalid)
|
|
51210
|
+
throw new application_error_1.ApplicationError("workflow.stale", invalid);
|
|
51211
|
+
// Verify external receipts before clearing a block or retrying any merge/cleanup effect.
|
|
51212
|
+
const operation = current;
|
|
51209
51213
|
const productionSha = requireProductionSha(operation);
|
|
51210
51214
|
const publication = await this.runtime.dependencies.publication.inspect({
|
|
51211
51215
|
tag: operation.tag,
|
|
@@ -51222,8 +51226,18 @@ class ConfirmPublicationHandler {
|
|
|
51222
51226
|
if (!reachable) {
|
|
51223
51227
|
return await this.runtime.block(context, operation, "publication", "Published SHA is not reachable from the stored production branch.", false);
|
|
51224
51228
|
}
|
|
51229
|
+
const resumed = await this.resumeRetryableBlock(context, operation);
|
|
51230
|
+
if (resumed.phase === "reconciliation_pending" && resumed.publicationVerified) {
|
|
51231
|
+
return await this.reconciliation.plan(context, resumed)
|
|
51232
|
+
?? (0, deployment_orchestration_runtime_1.deploymentSuccess)(`Publication for ${resumed.tag} was already verified; reconciliation state was recovered.`);
|
|
51233
|
+
}
|
|
51234
|
+
if (resumed.phase === "completed" && resumed.publicationVerified) {
|
|
51235
|
+
await this.runtime.publishDashboard(context, resumed);
|
|
51236
|
+
return (0, deployment_orchestration_runtime_1.deploymentSuccess)(`Publication for ${resumed.tag} was already verified; duplicate notification ignored.`);
|
|
51237
|
+
}
|
|
51238
|
+
assertPublicationPhase(resumed);
|
|
51225
51239
|
const published = {
|
|
51226
|
-
...
|
|
51240
|
+
...resumed,
|
|
51227
51241
|
phase: "published",
|
|
51228
51242
|
publicationVerified: true,
|
|
51229
51243
|
publicationReceipt: publication.receipt,
|
|
@@ -51542,6 +51556,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
|
51542
51556
|
exports.ReconciliationHandler = void 0;
|
|
51543
51557
|
const deployment_plan_policy_1 = __nccwpck_require__(8352);
|
|
51544
51558
|
const deployment_operation_1 = __nccwpck_require__(92730);
|
|
51559
|
+
const managed_pull_request_1 = __nccwpck_require__(95914);
|
|
51545
51560
|
const deployment_orchestration_runtime_1 = __nccwpck_require__(77658);
|
|
51546
51561
|
class ReconciliationHandler {
|
|
51547
51562
|
constructor(runtime) {
|
|
@@ -51578,10 +51593,12 @@ class ReconciliationHandler {
|
|
|
51578
51593
|
return await this.complete(context, updated, ` after reconciliation PR #${pullRequest.number}`);
|
|
51579
51594
|
}
|
|
51580
51595
|
async plan(context, operation) {
|
|
51581
|
-
const index = operation.reconciliationTargets.findIndex((target) => target.status === "pending"
|
|
51596
|
+
const index = operation.reconciliationTargets.findIndex((target) => target.status === "pending");
|
|
51582
51597
|
if (index < 0)
|
|
51583
51598
|
return await this.finishOrPresent(context, operation);
|
|
51584
51599
|
let target = operation.reconciliationTargets[index];
|
|
51600
|
+
if (target.pullRequest !== undefined)
|
|
51601
|
+
return await this.resumePullRequest(context, operation, target);
|
|
51585
51602
|
const targetRole = (0, deployment_orchestration_runtime_1.reconciliationTargetRole)(operation, target.targetBranch);
|
|
51586
51603
|
const preflight = await this.runtime.inspectMergeBehavior(context, operation, target.targetBranch, targetRole, target.sourceSha);
|
|
51587
51604
|
if (preflight.kind === "blocked") {
|
|
@@ -51638,6 +51655,26 @@ class ReconciliationHandler {
|
|
|
51638
51655
|
await this.runtime.publishMilestone(context, completed, { kind: "orchestration-complete", tag: completed.tag });
|
|
51639
51656
|
return (0, deployment_orchestration_runtime_1.deploymentSuccess)(`Deployment ${completed.tag} completed${completionContext}.`);
|
|
51640
51657
|
}
|
|
51658
|
+
async resumePullRequest(context, operation, target) {
|
|
51659
|
+
const pullRequest = await this.runtime.dependencies.pullRequests.getPullRequest(target.pullRequest);
|
|
51660
|
+
const identity = (0, managed_pull_request_1.parseManagedPullRequestMarker)(pullRequest.body);
|
|
51661
|
+
const expectedHead = target.syncBranch ? target.syncSha : target.sourceSha;
|
|
51662
|
+
if (!identity || identity.operationId !== operation.operationId || identity.phase !== "reconciliation"
|
|
51663
|
+
|| identity.issue !== context.singleAction.issue || pullRequest.number !== target.pullRequest
|
|
51664
|
+
|| pullRequest.repositoryFullName.toLowerCase() !== `${context.owner}/${context.repo}`.toLowerCase()
|
|
51665
|
+
|| pullRequest.baseBranch !== target.targetBranch
|
|
51666
|
+
|| pullRequest.headBranch !== (target.syncBranch ?? target.sourceBranch)
|
|
51667
|
+
|| !expectedHead || pullRequest.headSha !== expectedHead) {
|
|
51668
|
+
return await this.runtime.block(context, operation, "reconciliation", "The recorded reconciliation PR no longer matches its durable identity and head SHA.", false);
|
|
51669
|
+
}
|
|
51670
|
+
if (pullRequest.merged)
|
|
51671
|
+
return await this.accept(context, operation, pullRequest);
|
|
51672
|
+
if (pullRequest.state === "closed") {
|
|
51673
|
+
return await this.runtime.block(context, operation, "reconciliation", `Reconciliation PR #${pullRequest.number} was closed without merge.`, true);
|
|
51674
|
+
}
|
|
51675
|
+
const configured = await this.runtime.configureMergeBehavior(context, operation, pullRequest, "reconciliation", (0, deployment_orchestration_runtime_1.reconciliationTargetRole)(operation, target.targetBranch));
|
|
51676
|
+
return configured.kind === "blocked" ? configured.result : undefined;
|
|
51677
|
+
}
|
|
51641
51678
|
async finishOrPresent(context, operation) {
|
|
51642
51679
|
if (operation.reconciliationTargets.length > 0
|
|
51643
51680
|
&& operation.reconciliationTargets.every((target) => target.status === "completed")) {
|
|
@@ -71651,12 +71688,13 @@ class GithubManagedPullRequestRepository {
|
|
|
71651
71688
|
throw new Error("GitHub did not confirm that the pull request entered the merge queue.");
|
|
71652
71689
|
}
|
|
71653
71690
|
}
|
|
71654
|
-
async mergePullRequest(owner, repository, pullRequest, token) {
|
|
71691
|
+
async mergePullRequest(owner, repository, pullRequest, expectedHeadSha, token) {
|
|
71655
71692
|
const { data } = await this.clientProvider.getClient(token).rest.pulls.merge({
|
|
71656
71693
|
owner,
|
|
71657
71694
|
repo: repository,
|
|
71658
71695
|
pull_number: pullRequest,
|
|
71659
71696
|
merge_method: "merge",
|
|
71697
|
+
sha: expectedHeadSha,
|
|
71660
71698
|
});
|
|
71661
71699
|
if (!data.merged || !data.sha)
|
|
71662
71700
|
throw new Error(data.message ?? `Pull request #${pullRequest} was not merged.`);
|
|
@@ -71681,6 +71719,43 @@ function mapPullRequest(value, owner, repository) {
|
|
|
71681
71719
|
}
|
|
71682
71720
|
|
|
71683
71721
|
|
|
71722
|
+
/***/ }),
|
|
71723
|
+
|
|
71724
|
+
/***/ 33371:
|
|
71725
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
71726
|
+
|
|
71727
|
+
"use strict";
|
|
71728
|
+
|
|
71729
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
71730
|
+
exports.observePullRequestReadiness = observePullRequestReadiness;
|
|
71731
|
+
async function observePullRequestReadiness(client, owner, repository, pullRequest) {
|
|
71732
|
+
try {
|
|
71733
|
+
const response = await client.graphql(`query DeploymentPullRequestReadiness($owner: String!, $repository: String!, $pullRequest: Int!) {
|
|
71734
|
+
repository(owner: $owner, name: $repository) {
|
|
71735
|
+
pullRequest(number: $pullRequest) { headRefOid mergeable mergeStateStatus isDraft }
|
|
71736
|
+
}
|
|
71737
|
+
}`, { owner, repository, pullRequest });
|
|
71738
|
+
const state = response?.repository?.pullRequest;
|
|
71739
|
+
if (!state || typeof state.headRefOid !== "string" || !/^[a-f0-9]{40}$/i.test(state.headRefOid)
|
|
71740
|
+
|| typeof state.mergeable !== "string" || !["MERGEABLE", "CONFLICTING", "UNKNOWN"].includes(state.mergeable)
|
|
71741
|
+
|| typeof state.mergeStateStatus !== "string" || state.mergeStateStatus.length === 0
|
|
71742
|
+
|| typeof state.isDraft !== "boolean") {
|
|
71743
|
+
return { problem: { area: "workflow-contract", message: "GitHub returned incomplete pull-request merge readiness." } };
|
|
71744
|
+
}
|
|
71745
|
+
return {
|
|
71746
|
+
value: {
|
|
71747
|
+
headSha: state.headRefOid,
|
|
71748
|
+
immediatelyMergeable: !state.isDraft && state.mergeable === "MERGEABLE"
|
|
71749
|
+
&& ["CLEAN", "UNSTABLE"].includes(state.mergeStateStatus),
|
|
71750
|
+
},
|
|
71751
|
+
};
|
|
71752
|
+
}
|
|
71753
|
+
catch {
|
|
71754
|
+
return { problem: { area: "workflow-contract", message: "GitHub pull-request merge readiness could not be inspected." } };
|
|
71755
|
+
}
|
|
71756
|
+
}
|
|
71757
|
+
|
|
71758
|
+
|
|
71684
71759
|
/***/ }),
|
|
71685
71760
|
|
|
71686
71761
|
/***/ 55527:
|
|
@@ -71724,6 +71799,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
71724
71799
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
71725
71800
|
exports.GithubTargetMergeCapabilitiesInspector = void 0;
|
|
71726
71801
|
const yaml = __importStar(__nccwpck_require__(783));
|
|
71802
|
+
const github_pull_request_readiness_query_1 = __nccwpck_require__(33371);
|
|
71727
71803
|
class GithubTargetMergeCapabilitiesInspector {
|
|
71728
71804
|
constructor(clientProvider) {
|
|
71729
71805
|
this.clientProvider = clientProvider;
|
|
@@ -71736,8 +71812,8 @@ class GithubTargetMergeCapabilitiesInspector {
|
|
|
71736
71812
|
observeEffectiveRules(client, owner, repository, targetBranch),
|
|
71737
71813
|
observeBranchMergeQueue(client, owner, repository, targetBranch),
|
|
71738
71814
|
options.pullRequest === undefined
|
|
71739
|
-
? Promise.resolve(undefined)
|
|
71740
|
-
:
|
|
71815
|
+
? Promise.resolve({ value: undefined, problem: undefined })
|
|
71816
|
+
: (0, github_pull_request_readiness_query_1.observePullRequestReadiness)(client, owner, repository, options.pullRequest),
|
|
71741
71817
|
]);
|
|
71742
71818
|
const candidateProblem = options.candidateHeadSha !== undefined && !/^[a-f0-9]{40}$/i.test(options.candidateHeadSha)
|
|
71743
71819
|
? {
|
|
@@ -71747,25 +71823,26 @@ class GithubTargetMergeCapabilitiesInspector {
|
|
|
71747
71823
|
: undefined;
|
|
71748
71824
|
const candidateHeadChangedProblem = options.candidateHeadSha !== undefined
|
|
71749
71825
|
&& candidateProblem === undefined
|
|
71750
|
-
&& pullRequestState !== undefined
|
|
71751
|
-
&& pullRequestState.
|
|
71826
|
+
&& pullRequestState.value !== undefined
|
|
71827
|
+
&& pullRequestState.value.headSha !== options.candidateHeadSha
|
|
71752
71828
|
? {
|
|
71753
71829
|
area: "workflow-contract",
|
|
71754
71830
|
message: "The pull request head changed during readiness inspection, so the observed workflow contract is stale.",
|
|
71755
71831
|
}
|
|
71756
71832
|
: undefined;
|
|
71757
71833
|
const effective = normalizeEffectiveRules(classic.value, rulesets.value);
|
|
71758
|
-
const problems = [classic.problem, rulesets.problem, queue.problem,
|
|
71834
|
+
const problems = [classic.problem, rulesets.problem, queue.problem, pullRequestState.problem,
|
|
71835
|
+
candidateProblem, candidateHeadChangedProblem, ...effective.problems]
|
|
71759
71836
|
.filter((problem) => problem !== undefined);
|
|
71760
71837
|
const mergeQueueRequired = queue.value === true || effective.mergeQueueRequired;
|
|
71761
|
-
const candidateHeadSha = options.candidateHeadSha ?? pullRequestState?.
|
|
71838
|
+
const candidateHeadSha = options.candidateHeadSha ?? pullRequestState.value?.headSha;
|
|
71762
71839
|
const producerInspection = mergeQueueRequired
|
|
71763
71840
|
? await inspectMergeQueueProducers(client, owner, repository, repositoryData.id, targetBranch, candidateHeadSha, effective.requiredChecks, effective.requiredWorkflows)
|
|
71764
71841
|
: { producers: [], problems: [] };
|
|
71765
71842
|
return {
|
|
71766
71843
|
autoMergeAllowed: repositoryData.allow_auto_merge === true,
|
|
71767
71844
|
mergeQueueRequired,
|
|
71768
|
-
immediatelyMergeable:
|
|
71845
|
+
immediatelyMergeable: problems.length === 0 && pullRequestState.value?.immediatelyMergeable === true,
|
|
71769
71846
|
requiresStrictStatusChecks: effective.requiresStrictStatusChecks,
|
|
71770
71847
|
mergeQueueProducers: producerInspection.producers,
|
|
71771
71848
|
mergeQueueObservationProblems: [...problems, ...producerInspection.problems],
|
|
@@ -84833,7 +84910,7 @@ function bindManagedPullRequests(port, binding) {
|
|
|
84833
84910
|
enableAutoMerge: (pullRequestNodeId) => port.enableAutoMerge(binding.owner, binding.repository, pullRequestNodeId, binding.token),
|
|
84834
84911
|
isPullRequestQueued: (pullRequestNodeId) => port.isPullRequestQueued(binding.owner, binding.repository, pullRequestNodeId, binding.token),
|
|
84835
84912
|
enqueuePullRequest: (pullRequestNodeId, expectedHeadSha) => port.enqueuePullRequest(binding.owner, binding.repository, pullRequestNodeId, expectedHeadSha, binding.token),
|
|
84836
|
-
mergePullRequest: (pullRequest) => port.mergePullRequest(binding.owner, binding.repository, pullRequest, binding.token),
|
|
84913
|
+
mergePullRequest: (pullRequest, expectedHeadSha) => port.mergePullRequest(binding.owner, binding.repository, pullRequest, expectedHeadSha, binding.token),
|
|
84837
84914
|
});
|
|
84838
84915
|
}
|
|
84839
84916
|
function bindDeploymentTargetRules(port, binding) {
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@ on:
|
|
|
8
8
|
required: true
|
|
9
9
|
default: prepare
|
|
10
10
|
type: choice
|
|
11
|
-
options: [prepare, publish]
|
|
11
|
+
options: [prepare, publish, resume]
|
|
12
12
|
operation-id:
|
|
13
13
|
description: 'Internal durable operation identifier'
|
|
14
14
|
required: false
|
|
@@ -66,10 +66,10 @@ jobs:
|
|
|
66
66
|
if (operationId) errors.push('Operation ID must be empty in prepare mode.');
|
|
67
67
|
if (!title || title.length > 1000) errors.push('Title must contain 1-1000 characters.');
|
|
68
68
|
if (!changelog || changelog.length > 50000) errors.push('Changelog must contain 1-50000 characters.');
|
|
69
|
-
} else if (mode === 'publish') {
|
|
69
|
+
} else if (mode === 'publish' || mode === 'resume') {
|
|
70
70
|
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{7,127}$/.test(operationId)) errors.push('Operation ID is invalid.');
|
|
71
|
-
if (title || changelog) errors.push('Title and changelog must be empty in publish mode.');
|
|
72
|
-
} else errors.push('Mode must be prepare or
|
|
71
|
+
if (title || changelog) errors.push('Title and changelog must be empty in publish or resume mode.');
|
|
72
|
+
} else errors.push('Mode must be prepare, publish, or resume.');
|
|
73
73
|
if (errors.length) core.setFailed(errors.join(' '));
|
|
74
74
|
|
|
75
75
|
prepare-version-files:
|
|
@@ -185,17 +185,66 @@ jobs:
|
|
|
185
185
|
single-action-version: ${{ inputs.version }}
|
|
186
186
|
token: ${{ secrets.PAT }}
|
|
187
187
|
|
|
188
|
+
resume-reconciliation:
|
|
189
|
+
name: Resume published deployment
|
|
190
|
+
if: ${{ inputs.mode == 'resume' }}
|
|
191
|
+
runs-on: ubuntu-latest
|
|
192
|
+
needs: validate
|
|
193
|
+
timeout-minutes: 15
|
|
194
|
+
permissions:
|
|
195
|
+
issues: write
|
|
196
|
+
pull-requests: write
|
|
197
|
+
steps:
|
|
198
|
+
- name: Verify published operation before recovery
|
|
199
|
+
uses: actions/github-script@v9
|
|
200
|
+
env:
|
|
201
|
+
ISSUE: ${{ inputs.issue }}
|
|
202
|
+
OPERATION_ID: ${{ inputs.operation-id }}
|
|
203
|
+
VERSION: ${{ inputs.version }}
|
|
204
|
+
with:
|
|
205
|
+
script: |
|
|
206
|
+
const response = await github.rest.issues.get({ ...context.repo, issue_number: Number(process.env.ISSUE) });
|
|
207
|
+
if (response.data.pull_request) throw new Error('The launcher must be an issue.');
|
|
208
|
+
const body = response.data.body || '';
|
|
209
|
+
const start = '<!-- copilot-configuration-start';
|
|
210
|
+
const end = 'copilot-configuration-end -->';
|
|
211
|
+
const first = body.indexOf(start);
|
|
212
|
+
const last = body.indexOf(end, first + start.length);
|
|
213
|
+
if (first < 0 || last < 0 || body.indexOf(start, first + start.length) >= 0) throw new Error('A unique durable configuration is required.');
|
|
214
|
+
const payload = JSON.parse(body.slice(first + start.length, last));
|
|
215
|
+
const state = payload?.deploymentOrchestration;
|
|
216
|
+
const receipt = state?.publicationReceipt;
|
|
217
|
+
const phase = state?.phase === 'blocked' && state?.lastFailure?.retryable === true
|
|
218
|
+
? state.lastFailure.previousPhase : state?.phase;
|
|
219
|
+
if (payload?.schemaVersion !== 3 || state?.stateVersion !== 1
|
|
220
|
+
|| !Number.isSafeInteger(state?.revision) || state.revision < 1
|
|
221
|
+
|| state.operationId !== process.env.OPERATION_ID || state.version !== process.env.VERSION
|
|
222
|
+
|| !['reconciliation_pending', 'completed'].includes(phase) || state.publicationVerified !== true
|
|
223
|
+
|| !/^[a-f0-9]{40}$/i.test(state.productionSha || '') || state.tag !== `v${process.env.VERSION}`
|
|
224
|
+
|| receipt?.operationId !== state.operationId || receipt?.productionSha !== state.productionSha
|
|
225
|
+
|| receipt?.tag !== state.tag) throw new Error('Recovery requires the matching, already verified publication and reconciliation state.');
|
|
226
|
+
- name: Resume reconciliation from verified receipts
|
|
227
|
+
uses: vypdev/copilot@v3
|
|
228
|
+
with:
|
|
229
|
+
single-action: published_deployment_action
|
|
230
|
+
single-action-issue: ${{ inputs.issue }}
|
|
231
|
+
issue-workflow-profile: ${{ vars.COPILOT_ISSUE_WORKFLOW_PROFILE || '' }}
|
|
232
|
+
single-action-operation-id: ${{ inputs.operation-id }}
|
|
233
|
+
single-action-version: ${{ inputs.version }}
|
|
234
|
+
merge-queue-check-attestations: ${{ vars.MERGE_QUEUE_CHECK_ATTESTATIONS || '[]' }}
|
|
235
|
+
token: ${{ secrets.PAT }}
|
|
236
|
+
|
|
188
237
|
report-failure:
|
|
189
238
|
name: Report orchestration failure
|
|
190
239
|
runs-on: ubuntu-latest
|
|
191
240
|
timeout-minutes: 5
|
|
192
|
-
needs: [validate, prepare-version-files, promote, publish]
|
|
241
|
+
needs: [validate, prepare-version-files, promote, publish, resume-reconciliation]
|
|
193
242
|
if: ${{ failure() && needs.validate.result == 'success' }}
|
|
194
243
|
permissions:
|
|
195
244
|
issues: write
|
|
196
245
|
steps:
|
|
197
246
|
- name: Persist publication failure
|
|
198
|
-
if: ${{ inputs.mode == 'publish' }}
|
|
247
|
+
if: ${{ inputs.mode == 'publish' || inputs.mode == 'resume' }}
|
|
199
248
|
uses: vypdev/copilot@v3
|
|
200
249
|
with:
|
|
201
250
|
single-action: failed_deployment_action
|
|
@@ -8,7 +8,7 @@ on:
|
|
|
8
8
|
required: true
|
|
9
9
|
default: prepare
|
|
10
10
|
type: choice
|
|
11
|
-
options: [prepare, publish]
|
|
11
|
+
options: [prepare, publish, resume]
|
|
12
12
|
operation-id:
|
|
13
13
|
description: 'Internal durable operation identifier'
|
|
14
14
|
required: false
|
|
@@ -66,10 +66,10 @@ jobs:
|
|
|
66
66
|
if (operationId) errors.push('Operation ID must be empty in prepare mode.');
|
|
67
67
|
if (!title || title.length > 1000) errors.push('Title must contain 1-1000 characters.');
|
|
68
68
|
if (!changelog || changelog.length > 50000) errors.push('Changelog must contain 1-50000 characters.');
|
|
69
|
-
} else if (mode === 'publish') {
|
|
69
|
+
} else if (mode === 'publish' || mode === 'resume') {
|
|
70
70
|
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{7,127}$/.test(operationId)) errors.push('Operation ID is invalid.');
|
|
71
|
-
if (title || changelog) errors.push('Title and changelog must be empty in publish mode.');
|
|
72
|
-
} else errors.push('Mode must be prepare or
|
|
71
|
+
if (title || changelog) errors.push('Title and changelog must be empty in publish or resume mode.');
|
|
72
|
+
} else errors.push('Mode must be prepare, publish, or resume.');
|
|
73
73
|
if (errors.length) core.setFailed(errors.join(' '));
|
|
74
74
|
|
|
75
75
|
prepare-version-files:
|
|
@@ -185,17 +185,66 @@ jobs:
|
|
|
185
185
|
single-action-version: ${{ inputs.version }}
|
|
186
186
|
token: ${{ secrets.PAT }}
|
|
187
187
|
|
|
188
|
+
resume-reconciliation:
|
|
189
|
+
name: Resume published deployment
|
|
190
|
+
if: ${{ inputs.mode == 'resume' }}
|
|
191
|
+
runs-on: ubuntu-latest
|
|
192
|
+
needs: validate
|
|
193
|
+
timeout-minutes: 15
|
|
194
|
+
permissions:
|
|
195
|
+
issues: write
|
|
196
|
+
pull-requests: write
|
|
197
|
+
steps:
|
|
198
|
+
- name: Verify published operation before recovery
|
|
199
|
+
uses: actions/github-script@v9
|
|
200
|
+
env:
|
|
201
|
+
ISSUE: ${{ inputs.issue }}
|
|
202
|
+
OPERATION_ID: ${{ inputs.operation-id }}
|
|
203
|
+
VERSION: ${{ inputs.version }}
|
|
204
|
+
with:
|
|
205
|
+
script: |
|
|
206
|
+
const response = await github.rest.issues.get({ ...context.repo, issue_number: Number(process.env.ISSUE) });
|
|
207
|
+
if (response.data.pull_request) throw new Error('The launcher must be an issue.');
|
|
208
|
+
const body = response.data.body || '';
|
|
209
|
+
const start = '<!-- copilot-configuration-start';
|
|
210
|
+
const end = 'copilot-configuration-end -->';
|
|
211
|
+
const first = body.indexOf(start);
|
|
212
|
+
const last = body.indexOf(end, first + start.length);
|
|
213
|
+
if (first < 0 || last < 0 || body.indexOf(start, first + start.length) >= 0) throw new Error('A unique durable configuration is required.');
|
|
214
|
+
const payload = JSON.parse(body.slice(first + start.length, last));
|
|
215
|
+
const state = payload?.deploymentOrchestration;
|
|
216
|
+
const receipt = state?.publicationReceipt;
|
|
217
|
+
const phase = state?.phase === 'blocked' && state?.lastFailure?.retryable === true
|
|
218
|
+
? state.lastFailure.previousPhase : state?.phase;
|
|
219
|
+
if (payload?.schemaVersion !== 3 || state?.stateVersion !== 1
|
|
220
|
+
|| !Number.isSafeInteger(state?.revision) || state.revision < 1
|
|
221
|
+
|| state.operationId !== process.env.OPERATION_ID || state.version !== process.env.VERSION
|
|
222
|
+
|| !['reconciliation_pending', 'completed'].includes(phase) || state.publicationVerified !== true
|
|
223
|
+
|| !/^[a-f0-9]{40}$/i.test(state.productionSha || '') || state.tag !== `v${process.env.VERSION}`
|
|
224
|
+
|| receipt?.operationId !== state.operationId || receipt?.productionSha !== state.productionSha
|
|
225
|
+
|| receipt?.tag !== state.tag) throw new Error('Recovery requires the matching, already verified publication and reconciliation state.');
|
|
226
|
+
- name: Resume reconciliation from verified receipts
|
|
227
|
+
uses: vypdev/copilot@v3
|
|
228
|
+
with:
|
|
229
|
+
single-action: published_deployment_action
|
|
230
|
+
single-action-issue: ${{ inputs.issue }}
|
|
231
|
+
issue-workflow-profile: ${{ vars.COPILOT_ISSUE_WORKFLOW_PROFILE || '' }}
|
|
232
|
+
single-action-operation-id: ${{ inputs.operation-id }}
|
|
233
|
+
single-action-version: ${{ inputs.version }}
|
|
234
|
+
merge-queue-check-attestations: ${{ vars.MERGE_QUEUE_CHECK_ATTESTATIONS || '[]' }}
|
|
235
|
+
token: ${{ secrets.PAT }}
|
|
236
|
+
|
|
188
237
|
report-failure:
|
|
189
238
|
name: Report orchestration failure
|
|
190
239
|
runs-on: ubuntu-latest
|
|
191
240
|
timeout-minutes: 5
|
|
192
|
-
needs: [validate, prepare-version-files, promote, publish]
|
|
241
|
+
needs: [validate, prepare-version-files, promote, publish, resume-reconciliation]
|
|
193
242
|
if: ${{ failure() && needs.validate.result == 'success' }}
|
|
194
243
|
permissions:
|
|
195
244
|
issues: write
|
|
196
245
|
steps:
|
|
197
246
|
- name: Persist publication failure
|
|
198
|
-
if: ${{ inputs.mode == 'publish' }}
|
|
247
|
+
if: ${{ inputs.mode == 'publish' || inputs.mode == 'resume' }}
|
|
199
248
|
uses: vypdev/copilot@v3
|
|
200
249
|
with:
|
|
201
250
|
single-action: failed_deployment_action
|