@catladder/cli 5.5.0 → 5.7.0
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/apps/cli/src/catci.js +22 -0
- package/dist/apps/cli/src/catci.js.map +1 -1
- package/dist/apps/cli/src/commands/index.d.ts +1 -0
- package/dist/apps/cli/src/commands/index.js +4 -1
- package/dist/apps/cli/src/commands/index.js.map +1 -1
- package/dist/apps/cli/src/commands/project/commandMrPin.d.ts +9 -0
- package/dist/apps/cli/src/commands/project/commandMrPin.js +11 -13
- package/dist/apps/cli/src/commands/project/commandMrPin.js.map +1 -1
- package/dist/apps/cli/src/commands/project/commandMrReviewApp.d.ts +21 -0
- package/dist/apps/cli/src/commands/project/commandMrReviewApp.js +102 -0
- package/dist/apps/cli/src/commands/project/commandMrReviewApp.js.map +1 -0
- package/dist/apps/cli/src/reviewApps/github.d.ts +10 -0
- package/dist/apps/cli/src/reviewApps/github.js +196 -0
- package/dist/apps/cli/src/reviewApps/github.js.map +1 -0
- package/dist/bundles/catci/index.js +4 -4
- package/dist/bundles/catenv/index.js +1261 -70
- package/dist/bundles/catenv/xdg-open +0 -0
- package/dist/bundles/cli/clipboard_i686.exe +0 -0
- package/dist/bundles/cli/clipboard_x86_64.exe +0 -0
- package/dist/bundles/cli/index.js +1387 -86
- package/dist/bundles/cli/xdg-open +0 -0
- package/dist/bundles/cli/xsel +0 -0
- package/dist/bundles/runner-images/changesets/scripts/changesetsRelease +0 -0
- package/dist/bundles/runner-images/kubernetes/scripts/kubernetesCreateSecret +0 -0
- package/dist/bundles/runner-images/kubernetes/scripts/kubernetesDelete +0 -0
- package/dist/bundles/runner-images/kubernetes/scripts/kubernetesDeploy +0 -0
- package/dist/bundles/runner-images/kubernetes/scripts/kubernetesRollback +0 -0
- package/dist/bundles/skills/catladder-builds/SKILL.md +50 -2
- package/dist/bundles/skills/catladder-builds/references/build-types.md +10 -2
- package/dist/bundles/skills/catladder-cli/references/commands.md +13 -0
- package/dist/bundles/skills/catladder-config/SKILL.md +4 -0
- package/dist/bundles/skills/catladder-deploys/SKILL.md +6 -0
- package/dist/bundles/skills/catladder-pipelines/SKILL.md +97 -3
- package/dist/packages/pipeline/src/backends/github/GithubBackend.js +60 -3
- package/dist/packages/pipeline/src/backends/github/GithubBackend.js.map +1 -1
- package/dist/packages/pipeline/src/backends/github/aggregateCheckJob.d.ts +1 -1
- package/dist/packages/pipeline/src/backends/github/aggregateCheckJob.js +22 -8
- package/dist/packages/pipeline/src/backends/github/aggregateCheckJob.js.map +1 -1
- package/dist/packages/pipeline/src/backends/github/artifactArchive.d.ts +22 -0
- package/dist/packages/pipeline/src/backends/github/artifactArchive.js +84 -0
- package/dist/packages/pipeline/src/backends/github/artifactArchive.js.map +1 -0
- package/dist/packages/pipeline/src/backends/github/createGithubJobs.js +12 -29
- package/dist/packages/pipeline/src/backends/github/createGithubJobs.js.map +1 -1
- package/dist/packages/pipeline/src/backends/github/reviewDeployWorkflow.d.ts +128 -0
- package/dist/packages/pipeline/src/backends/github/reviewDeployWorkflow.js +510 -0
- package/dist/packages/pipeline/src/backends/github/reviewDeployWorkflow.js.map +1 -0
- package/dist/packages/pipeline/src/backends/gitlab/GitlabBackend.js +32 -5
- package/dist/packages/pipeline/src/backends/gitlab/GitlabBackend.js.map +1 -1
- package/dist/packages/pipeline/src/backends/gitlab/createGitlabPipeline.d.ts +11 -1
- package/dist/packages/pipeline/src/backends/gitlab/createGitlabPipeline.js +17 -5
- package/dist/packages/pipeline/src/backends/gitlab/createGitlabPipeline.js.map +1 -1
- package/dist/packages/pipeline/src/backends/gitlab/getPipelineStages.d.ts +4 -1
- package/dist/packages/pipeline/src/backends/gitlab/getPipelineStages.js +11 -2
- package/dist/packages/pipeline/src/backends/gitlab/getPipelineStages.js.map +1 -1
- package/dist/packages/pipeline/src/backends/gitlab/reviewDeployGate.d.ts +26 -0
- package/dist/packages/pipeline/src/backends/gitlab/reviewDeployGate.js +94 -0
- package/dist/packages/pipeline/src/backends/gitlab/reviewDeployGate.js.map +1 -0
- package/dist/packages/pipeline/src/build/types.d.ts +4 -3
- package/dist/packages/pipeline/src/build/types.js.map +1 -1
- package/dist/packages/pipeline/src/deploy/base/deploy.js +25 -12
- package/dist/packages/pipeline/src/deploy/base/deploy.js.map +1 -1
- package/dist/packages/pipeline/src/index.d.ts +2 -0
- package/dist/packages/pipeline/src/index.js +2 -0
- package/dist/packages/pipeline/src/index.js.map +1 -1
- package/dist/packages/pipeline/src/pipeline/createJobsForComponent.js +12 -2
- package/dist/packages/pipeline/src/pipeline/createJobsForComponent.js.map +1 -1
- package/dist/packages/pipeline/src/pipeline/createJobsForWorkspace.js +7 -2
- package/dist/packages/pipeline/src/pipeline/createJobsForWorkspace.js.map +1 -1
- package/dist/packages/pipeline/src/pipeline/resolveRequirements.d.ts +2 -0
- package/dist/packages/pipeline/src/pipeline/resolveRequirements.js +4 -2
- package/dist/packages/pipeline/src/pipeline/resolveRequirements.js.map +1 -1
- package/dist/packages/pipeline/src/postBuildTest/createPostBuildTestJobs.d.ts +16 -0
- package/dist/packages/pipeline/src/postBuildTest/createPostBuildTestJobs.js +167 -0
- package/dist/packages/pipeline/src/postBuildTest/createPostBuildTestJobs.js.map +1 -0
- package/dist/packages/pipeline/src/postBuildTest/index.d.ts +2 -0
- package/dist/packages/pipeline/src/postBuildTest/index.js +19 -0
- package/dist/packages/pipeline/src/postBuildTest/index.js.map +1 -0
- package/dist/packages/pipeline/src/postBuildTest/types.d.ts +61 -0
- package/dist/packages/pipeline/src/postBuildTest/types.js +3 -0
- package/dist/packages/pipeline/src/postBuildTest/types.js.map +1 -0
- package/dist/packages/pipeline/src/reviewApps.d.ts +38 -0
- package/dist/packages/pipeline/src/reviewApps.js +86 -0
- package/dist/packages/pipeline/src/reviewApps.js.map +1 -0
- package/dist/packages/pipeline/src/reviewAppsPolicy.d.ts +46 -0
- package/dist/packages/pipeline/src/reviewAppsPolicy.js +37 -0
- package/dist/packages/pipeline/src/reviewAppsPolicy.js.map +1 -0
- package/dist/packages/pipeline/src/types/config.d.ts +55 -0
- package/dist/packages/pipeline/src/types/config.js.map +1 -1
- package/dist/packages/pipeline/src/types/jobs.d.ts +2 -2
- package/dist/packages/pipeline/src/types/jobs.js +1 -0
- package/dist/packages/pipeline/src/types/jobs.js.map +1 -1
- package/dist/runner-images/changesets/scripts/changesetsRelease +0 -0
- package/dist/runner-images/kubernetes/scripts/kubernetesCreateSecret +0 -0
- package/dist/runner-images/kubernetes/scripts/kubernetesDelete +0 -0
- package/dist/runner-images/kubernetes/scripts/kubernetesDeploy +0 -0
- package/dist/runner-images/kubernetes/scripts/kubernetesRollback +0 -0
- package/dist/skills/catladder-builds/SKILL.md +50 -2
- package/dist/skills/catladder-builds/references/build-types.md +10 -2
- package/dist/skills/catladder-cli/references/commands.md +13 -0
- package/dist/skills/catladder-config/SKILL.md +4 -0
- package/dist/skills/catladder-deploys/SKILL.md +6 -0
- package/dist/skills/catladder-pipelines/SKILL.md +97 -3
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/catci.ts +29 -0
- package/src/commands/index.ts +4 -0
- package/src/commands/project/commandMrPin.ts +15 -14
- package/src/commands/project/commandMrReviewApp.ts +143 -0
- package/src/reviewApps/github.ts +253 -0
|
@@ -2905,7 +2905,7 @@ exports.commandKubeStopPortforward = (0, defineCommand_1.defineCommand)({
|
|
|
2905
2905
|
"use strict";
|
|
2906
2906
|
|
|
2907
2907
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
2908
|
-
exports.commandStarwars = exports.commandDadjoke = exports.commandSecurityAuditCreate = exports.commandSecurityAuditEvaluate = exports.commandSecurityAuditCiJob = exports.commandRestoreDb = exports.commandMongoDestroyMember = exports.commandMongoPortForward = exports.commandMongoGetShell = exports.commandTriggerCronjobProject = exports.commandDeleteProject = exports.commandPauseProject = exports.commandDeletePods = exports.commandGetShell = exports.commandListPods = exports.commandNamespace = exports.commandMrUnpin = exports.commandMrPin = exports.commandSecurityEvaluate = exports.commandProjectRestoreDb = exports.commandCiJobLog = exports.commandCiJobOpen = exports.commandDoctor = exports.commandSetup = exports.commandGetMyTotalWorktime = exports.commandRenewToken = exports.commandTriggerCronjobGeneral = exports.commandOpenLogs = exports.commandSecretsSyncGithub = exports.commandSecretsClearBackups = exports.commandSecretsList = exports.commandSecretsSet = exports.commandSecretsPush = exports.commandSecretsPull = exports.commandConfigSecrets = exports.commandCloudSqlProxy = exports.commandOpenGit = exports.commandOpenEnv = exports.commandPortForward = exports.commandEnvVars = exports.commandKubePortForward = exports.commandKubeGetShell = exports.commandKubeStopPortforward = exports.commandKubeListPods = exports.commandKubeListSecrets = exports.commandKubeListNamespaces = exports.commandKubeCurrentContext = void 0;
|
|
2908
|
+
exports.commandStarwars = exports.commandDadjoke = exports.commandSecurityAuditCreate = exports.commandSecurityAuditEvaluate = exports.commandSecurityAuditCiJob = exports.commandRestoreDb = exports.commandMongoDestroyMember = exports.commandMongoPortForward = exports.commandMongoGetShell = exports.commandTriggerCronjobProject = exports.commandDeleteProject = exports.commandPauseProject = exports.commandDeletePods = exports.commandGetShell = exports.commandListPods = exports.commandNamespace = exports.commandMrReviewAppOff = exports.commandMrReviewAppOn = exports.commandMrUnpin = exports.commandMrPin = exports.commandSecurityEvaluate = exports.commandProjectRestoreDb = exports.commandCiJobLog = exports.commandCiJobOpen = exports.commandDoctor = exports.commandSetup = exports.commandGetMyTotalWorktime = exports.commandRenewToken = exports.commandTriggerCronjobGeneral = exports.commandOpenLogs = exports.commandSecretsSyncGithub = exports.commandSecretsClearBackups = exports.commandSecretsList = exports.commandSecretsSet = exports.commandSecretsPush = exports.commandSecretsPull = exports.commandConfigSecrets = exports.commandCloudSqlProxy = exports.commandOpenGit = exports.commandOpenEnv = exports.commandPortForward = exports.commandEnvVars = exports.commandKubePortForward = exports.commandKubeGetShell = exports.commandKubeStopPortforward = exports.commandKubeListPods = exports.commandKubeListSecrets = exports.commandKubeListNamespaces = exports.commandKubeCurrentContext = void 0;
|
|
2909
2909
|
// General commands
|
|
2910
2910
|
var commandKubeCurrentContext_1 = __nccwpck_require__(78034);
|
|
2911
2911
|
Object.defineProperty(exports, "commandKubeCurrentContext", ({ enumerable: true, get: function () { return commandKubeCurrentContext_1.commandKubeCurrentContext; } }));
|
|
@@ -2968,6 +2968,9 @@ Object.defineProperty(exports, "commandSecurityEvaluate", ({ enumerable: true, g
|
|
|
2968
2968
|
var commandMrPin_1 = __nccwpck_require__(31318);
|
|
2969
2969
|
Object.defineProperty(exports, "commandMrPin", ({ enumerable: true, get: function () { return commandMrPin_1.commandMrPin; } }));
|
|
2970
2970
|
Object.defineProperty(exports, "commandMrUnpin", ({ enumerable: true, get: function () { return commandMrPin_1.commandMrUnpin; } }));
|
|
2971
|
+
var commandMrReviewApp_1 = __nccwpck_require__(94390);
|
|
2972
|
+
Object.defineProperty(exports, "commandMrReviewAppOn", ({ enumerable: true, get: function () { return commandMrReviewApp_1.commandMrReviewAppOn; } }));
|
|
2973
|
+
Object.defineProperty(exports, "commandMrReviewAppOff", ({ enumerable: true, get: function () { return commandMrReviewApp_1.commandMrReviewAppOff; } }));
|
|
2971
2974
|
// Project k8s commands
|
|
2972
2975
|
var commandNamespace_1 = __nccwpck_require__(25918);
|
|
2973
2976
|
Object.defineProperty(exports, "commandNamespace", ({ enumerable: true, get: function () { return commandNamespace_1.commandNamespace; } }));
|
|
@@ -3638,7 +3641,7 @@ exports.commandCiJobLog = (0, defineCommand_1.defineCommand)({
|
|
|
3638
3641
|
"use strict";
|
|
3639
3642
|
|
|
3640
3643
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
3641
|
-
exports.commandMrUnpin = exports.commandMrPin = void 0;
|
|
3644
|
+
exports.commandMrUnpin = exports.commandMrPin = exports.ensureLabelExists = exports.findMr = void 0;
|
|
3642
3645
|
const child_process_promise_1 = __nccwpck_require__(11542);
|
|
3643
3646
|
const pipeline_1 = __nccwpck_require__(53695);
|
|
3644
3647
|
const getProjectConfig_1 = __nccwpck_require__(70478);
|
|
@@ -3674,18 +3677,16 @@ const findMr = async (io, projectId, mrIid) => {
|
|
|
3674
3677
|
}
|
|
3675
3678
|
return mrs[0];
|
|
3676
3679
|
};
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
+
exports.findMr = findMr;
|
|
3681
|
+
const ensureLabelExists = async (io, projectId, name, description) => {
|
|
3682
|
+
const existing = await (0, gitlab_1.doGitlabRequest)(io, `projects/${projectId}/labels?search=${encodeURIComponent(name)}`);
|
|
3683
|
+
if (existing === null || existing === void 0 ? void 0 : existing.some((label) => label.name === name)) {
|
|
3680
3684
|
return;
|
|
3681
3685
|
}
|
|
3682
|
-
await (0, gitlab_1.doGitlabRequest)(io, `projects/${projectId}/labels`, {
|
|
3683
|
-
|
|
3684
|
-
color: PIN_LABEL_COLOR,
|
|
3685
|
-
description: "review apps of this MR are pinned — deploys use auto_stop_in: never (managed by catladder)",
|
|
3686
|
-
}, "POST");
|
|
3687
|
-
io.log(`created the '${pinLabel}' label in the project`);
|
|
3686
|
+
await (0, gitlab_1.doGitlabRequest)(io, `projects/${projectId}/labels`, { name, color: PIN_LABEL_COLOR, description }, "POST");
|
|
3687
|
+
io.log(`created the '${name}' label in the project`);
|
|
3688
3688
|
};
|
|
3689
|
+
exports.ensureLabelExists = ensureLabelExists;
|
|
3689
3690
|
exports.commandMrPin = (0, defineCommand_1.defineCommand)({
|
|
3690
3691
|
name: "mr pin",
|
|
3691
3692
|
description: "pin the review apps of a merge request (disable their auto-stop) by adding the pin label; merging or closing the MR still stops them",
|
|
@@ -3707,8 +3708,8 @@ exports.commandMrPin = (0, defineCommand_1.defineCommand)({
|
|
|
3707
3708
|
var _a, _b;
|
|
3708
3709
|
const pinLabel = await getPinLabel();
|
|
3709
3710
|
const { id: projectId } = await (0, gitlab_1.getProjectInfo)(ctx);
|
|
3710
|
-
const mr = await findMr(ctx, projectId, await ctx.get("mr"));
|
|
3711
|
-
await
|
|
3711
|
+
const mr = await (0, exports.findMr)(ctx, projectId, await ctx.get("mr"));
|
|
3712
|
+
await (0, exports.ensureLabelExists)(ctx, projectId, pinLabel, "review apps of this MR are pinned — deploys use auto_stop_in: never (managed by catladder)");
|
|
3712
3713
|
if ((_a = mr.labels) === null || _a === void 0 ? void 0 : _a.includes(pinLabel)) {
|
|
3713
3714
|
ctx.log(`already pinned: mr!${mr.iid} carries the '${pinLabel}' label`);
|
|
3714
3715
|
}
|
|
@@ -3744,7 +3745,7 @@ exports.commandMrUnpin = (0, defineCommand_1.defineCommand)({
|
|
|
3744
3745
|
var _a;
|
|
3745
3746
|
const pinLabel = await getPinLabel();
|
|
3746
3747
|
const { id: projectId } = await (0, gitlab_1.getProjectInfo)(ctx);
|
|
3747
|
-
const mr = await findMr(ctx, projectId, await ctx.get("mr"));
|
|
3748
|
+
const mr = await (0, exports.findMr)(ctx, projectId, await ctx.get("mr"));
|
|
3748
3749
|
if (!((_a = mr.labels) === null || _a === void 0 ? void 0 : _a.includes(pinLabel))) {
|
|
3749
3750
|
ctx.log(`mr!${mr.iid} is not pinned (no '${pinLabel}' label)`);
|
|
3750
3751
|
return;
|
|
@@ -3758,6 +3759,115 @@ exports.commandMrUnpin = (0, defineCommand_1.defineCommand)({
|
|
|
3758
3759
|
|
|
3759
3760
|
/***/ }),
|
|
3760
3761
|
|
|
3762
|
+
/***/ 94390:
|
|
3763
|
+
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
3764
|
+
|
|
3765
|
+
"use strict";
|
|
3766
|
+
|
|
3767
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
3768
|
+
exports.commandMrReviewAppOff = exports.commandMrReviewAppOn = void 0;
|
|
3769
|
+
const pipeline_1 = __nccwpck_require__(53695);
|
|
3770
|
+
const getProjectConfig_1 = __nccwpck_require__(70478);
|
|
3771
|
+
const defineCommand_1 = __nccwpck_require__(19096);
|
|
3772
|
+
const gitlab_1 = __nccwpck_require__(17697);
|
|
3773
|
+
const commandMrPin_1 = __nccwpck_require__(31318);
|
|
3774
|
+
/**
|
|
3775
|
+
* the label switch of the project's review apps — errors when the
|
|
3776
|
+
* project has none (github, or a policy without labels)
|
|
3777
|
+
*/
|
|
3778
|
+
const getSwitch = async () => {
|
|
3779
|
+
const config = await (0, getProjectConfig_1.getProjectConfig)();
|
|
3780
|
+
if (!config) {
|
|
3781
|
+
throw new Error("no catladder config found");
|
|
3782
|
+
}
|
|
3783
|
+
const reviewApps = (0, pipeline_1.getReviewAppsConfig)(config);
|
|
3784
|
+
const switchLabel = (0, pipeline_1.getReviewAppsSwitchLabel)(reviewApps);
|
|
3785
|
+
if (!switchLabel) {
|
|
3786
|
+
throw new Error(reviewApps.deploy === "manual"
|
|
3787
|
+
? "review apps deploy on request in this project (reviewApps.deploy: manual) — play the '🚀 deploy review' job of the MR pipeline"
|
|
3788
|
+
: "review apps of this project are not switched by label (set reviewApps.deploy to optIn or optOut)");
|
|
3789
|
+
}
|
|
3790
|
+
if (!(0, pipeline_1.getEnabledPipelineTypes)(config).includes("gitlab")) {
|
|
3791
|
+
throw new Error(`this command switches gitlab merge requests — on github, set the label on the pull request instead (gh pr edit --${switchLabel.deployOn === "labeled" ? "add" : "remove"}-label ${switchLabel.label}); the label change deploys by itself`);
|
|
3792
|
+
}
|
|
3793
|
+
return switchLabel;
|
|
3794
|
+
};
|
|
3795
|
+
const mrInput = {
|
|
3796
|
+
mr: {
|
|
3797
|
+
type: "number",
|
|
3798
|
+
message: "merge request IID (defaults to the open MR of the current branch)",
|
|
3799
|
+
positional: true,
|
|
3800
|
+
required: false,
|
|
3801
|
+
},
|
|
3802
|
+
};
|
|
3803
|
+
exports.commandMrReviewAppOn = (0, defineCommand_1.defineCommand)({
|
|
3804
|
+
name: "mr review-app-on",
|
|
3805
|
+
description: "deploy the review apps of a merge request: switches the reviewApps label on (opt-in: adds it, opt-out: removes the skip label) and triggers a pipeline, since gitlab doesn't start one for label changes",
|
|
3806
|
+
group: "mr",
|
|
3807
|
+
inputs: {
|
|
3808
|
+
...mrInput,
|
|
3809
|
+
pipeline: {
|
|
3810
|
+
type: "boolean",
|
|
3811
|
+
message: "trigger a merge-request pipeline so the review apps deploy now",
|
|
3812
|
+
required: false,
|
|
3813
|
+
},
|
|
3814
|
+
},
|
|
3815
|
+
execute: async (ctx) => {
|
|
3816
|
+
var _a, _b;
|
|
3817
|
+
const switchLabel = await getSwitch();
|
|
3818
|
+
const { id: projectId } = await (0, gitlab_1.getProjectInfo)(ctx);
|
|
3819
|
+
const mr = await (0, commandMrPin_1.findMr)(ctx, projectId, await ctx.get("mr"));
|
|
3820
|
+
if (switchLabel.deployOn === "labeled") {
|
|
3821
|
+
await (0, commandMrPin_1.ensureLabelExists)(ctx, projectId, switchLabel.label, "review apps of this MR deploy (managed by catladder)");
|
|
3822
|
+
}
|
|
3823
|
+
const hasLabel = (_a = mr.labels) === null || _a === void 0 ? void 0 : _a.includes(switchLabel.label);
|
|
3824
|
+
if (hasLabel === (switchLabel.deployOn === "labeled")) {
|
|
3825
|
+
ctx.log(`review apps of mr!${mr.iid} are already switched on`);
|
|
3826
|
+
}
|
|
3827
|
+
else {
|
|
3828
|
+
await (0, gitlab_1.doGitlabRequest)(ctx, `projects/${projectId}/merge_requests/${mr.iid}`, switchLabel.deployOn === "labeled"
|
|
3829
|
+
? { add_labels: switchLabel.label }
|
|
3830
|
+
: { remove_labels: switchLabel.label }, "PUT");
|
|
3831
|
+
ctx.log(`review apps of mr!${mr.iid} "${mr.title}" switched on — label '${switchLabel.label}' ${switchLabel.deployOn === "labeled" ? "added" : "removed"}`);
|
|
3832
|
+
}
|
|
3833
|
+
if ((_b = (await ctx.get("pipeline"))) !== null && _b !== void 0 ? _b : true) {
|
|
3834
|
+
const pipeline = await (0, gitlab_1.doGitlabRequest)(ctx, `projects/${projectId}/merge_requests/${mr.iid}/pipelines`, {}, "POST");
|
|
3835
|
+
ctx.log(`🚀 triggered a pipeline that deploys them: ${pipeline.web_url}`);
|
|
3836
|
+
}
|
|
3837
|
+
else {
|
|
3838
|
+
ctx.log("the review apps deploy with the next pipeline of the MR");
|
|
3839
|
+
}
|
|
3840
|
+
},
|
|
3841
|
+
});
|
|
3842
|
+
exports.commandMrReviewAppOff = (0, defineCommand_1.defineCommand)({
|
|
3843
|
+
name: "mr review-app-off",
|
|
3844
|
+
description: "stop deploying the review apps of a merge request: switches the reviewApps label off (opt-in: removes it, opt-out: adds the skip label). Running review apps stay until the MR closes or they auto-stop",
|
|
3845
|
+
group: "mr",
|
|
3846
|
+
inputs: mrInput,
|
|
3847
|
+
execute: async (ctx) => {
|
|
3848
|
+
var _a;
|
|
3849
|
+
const switchLabel = await getSwitch();
|
|
3850
|
+
const { id: projectId } = await (0, gitlab_1.getProjectInfo)(ctx);
|
|
3851
|
+
const mr = await (0, commandMrPin_1.findMr)(ctx, projectId, await ctx.get("mr"));
|
|
3852
|
+
if (switchLabel.stopOn === "labeled") {
|
|
3853
|
+
await (0, commandMrPin_1.ensureLabelExists)(ctx, projectId, switchLabel.label, "review apps of this MR don't deploy (managed by catladder)");
|
|
3854
|
+
}
|
|
3855
|
+
const hasLabel = (_a = mr.labels) === null || _a === void 0 ? void 0 : _a.includes(switchLabel.label);
|
|
3856
|
+
if (hasLabel === (switchLabel.stopOn === "labeled")) {
|
|
3857
|
+
ctx.log(`review apps of mr!${mr.iid} are already switched off`);
|
|
3858
|
+
return;
|
|
3859
|
+
}
|
|
3860
|
+
await (0, gitlab_1.doGitlabRequest)(ctx, `projects/${projectId}/merge_requests/${mr.iid}`, switchLabel.stopOn === "labeled"
|
|
3861
|
+
? { add_labels: switchLabel.label }
|
|
3862
|
+
: { remove_labels: switchLabel.label }, "PUT");
|
|
3863
|
+
ctx.log(`review apps of mr!${mr.iid} switched off — label '${switchLabel.label}' ${switchLabel.stopOn === "labeled" ? "added" : "removed"}`);
|
|
3864
|
+
ctx.log("already running review apps stay until the MR is closed or they auto-stop — stop them earlier with the stop job of the MR pipeline");
|
|
3865
|
+
},
|
|
3866
|
+
});
|
|
3867
|
+
//# sourceMappingURL=commandMrReviewApp.js.map
|
|
3868
|
+
|
|
3869
|
+
/***/ }),
|
|
3870
|
+
|
|
3761
3871
|
/***/ 30119:
|
|
3762
3872
|
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
|
3763
3873
|
|
|
@@ -9232,6 +9342,8 @@ const aggregateCheckJob_1 = __nccwpck_require__(82145);
|
|
|
9232
9342
|
const shippedCatci_1 = __nccwpck_require__(27967);
|
|
9233
9343
|
const githubReleaseJobs_1 = __nccwpck_require__(25073);
|
|
9234
9344
|
const scriptFiles_1 = __nccwpck_require__(61071);
|
|
9345
|
+
const reviewApps_1 = __nccwpck_require__(67071);
|
|
9346
|
+
const reviewDeployWorkflow_1 = __nccwpck_require__(73671);
|
|
9235
9347
|
const WORKFLOWS_FOLDER = ".github/workflows";
|
|
9236
9348
|
/**
|
|
9237
9349
|
* generated workflow files are prefixed so that cleanup never touches
|
|
@@ -9327,6 +9439,7 @@ class GithubBackend {
|
|
|
9327
9439
|
...(0, ciVariables_1.getGithubInjectedWorkflowEnv)(images.registryImageRef()),
|
|
9328
9440
|
...(0, index_1.getPipelineOptions)(config, this.type).runnerVariables,
|
|
9329
9441
|
};
|
|
9442
|
+
const reviewApps = (0, reviewApps_1.getReviewAppsConfig)(config);
|
|
9330
9443
|
const reviewStopJobs = {};
|
|
9331
9444
|
// jobs routed to the per-kind workflow_dispatch workflows (deploy /
|
|
9332
9445
|
// stop / rollback). A manual job and its build closure live there
|
|
@@ -9369,6 +9482,39 @@ class GithubBackend {
|
|
|
9369
9482
|
triggerJobs.delete(id);
|
|
9370
9483
|
}
|
|
9371
9484
|
}
|
|
9485
|
+
// review delivery chains behind the per-PR deploy switch move to
|
|
9486
|
+
// their own workflow (reviewApps config, or manual review deploys)
|
|
9487
|
+
// When the label/draft policy already decides at the start of the
|
|
9488
|
+
// review run, the chains stay in it behind that condition (one
|
|
9489
|
+
// run, one app build); the review deploy workflow handles whatever
|
|
9490
|
+
// changes later. Manual review deploys only live there.
|
|
9491
|
+
let inRunDeliveryIds = [];
|
|
9492
|
+
const inRunCondition = (0, reviewApps_1.isReviewDeployGated)(reviewApps)
|
|
9493
|
+
? (0, reviewDeployWorkflow_1.getInRunDeployCondition)(reviewApps)
|
|
9494
|
+
: undefined;
|
|
9495
|
+
if (trigger === "mr") {
|
|
9496
|
+
const deliveryIds = (0, reviewDeployWorkflow_1.getGatedReviewDeliveryJobIds)(triggerJobs, reviewApps);
|
|
9497
|
+
if (deliveryIds.length > 0) {
|
|
9498
|
+
workflows[reviewDeployWorkflow_1.REVIEW_DEPLOY_WORKFLOW_FILE] = (0, reviewDeployWorkflow_1.makeReviewDeployWorkflow)({
|
|
9499
|
+
deliveryIds,
|
|
9500
|
+
triggerJobs,
|
|
9501
|
+
reviewApps,
|
|
9502
|
+
images,
|
|
9503
|
+
workflowEnv,
|
|
9504
|
+
permissions: workflowPermissions(images).permissions,
|
|
9505
|
+
});
|
|
9506
|
+
if (inRunCondition) {
|
|
9507
|
+
inRunDeliveryIds = deliveryIds;
|
|
9508
|
+
for (const id of deliveryIds) {
|
|
9509
|
+
const entry = triggerJobs.get(id);
|
|
9510
|
+
entry.githubJob = (0, reviewDeployWorkflow_1.withInRunCondition)(entry.githubJob, inRunCondition);
|
|
9511
|
+
}
|
|
9512
|
+
}
|
|
9513
|
+
else {
|
|
9514
|
+
deliveryIds.forEach((id) => triggerJobs.delete(id));
|
|
9515
|
+
}
|
|
9516
|
+
}
|
|
9517
|
+
}
|
|
9372
9518
|
// leaf manual jobs: an explicit manual gate (e.g. a prod deploy) or
|
|
9373
9519
|
// a stop/rollback task with no automatic trigger. Each becomes a
|
|
9374
9520
|
// dispatch choice.
|
|
@@ -9472,7 +9618,7 @@ class GithubBackend {
|
|
|
9472
9618
|
}
|
|
9473
9619
|
if (Object.keys(jobs).length > 0) {
|
|
9474
9620
|
const workflowJobs = { ...(0, ensureImageJobs_1.makeEnsureImageGithubJobs)(images), ...jobs };
|
|
9475
|
-
|
|
9621
|
+
const workflow = {
|
|
9476
9622
|
...getTriggerWorkflow(trigger),
|
|
9477
9623
|
...workflowPermissions(images),
|
|
9478
9624
|
env: workflowEnv,
|
|
@@ -9484,13 +9630,30 @@ class GithubBackend {
|
|
|
9484
9630
|
// required. Added at finalization so its needs cover the
|
|
9485
9631
|
// image-build jobs too.
|
|
9486
9632
|
jobs: trigger === "mr"
|
|
9487
|
-
? {
|
|
9633
|
+
? {
|
|
9634
|
+
...workflowJobs,
|
|
9635
|
+
...(0, aggregateCheckJob_1.makeAggregateCheckJob)(workflowJobs, inRunDeliveryIds),
|
|
9636
|
+
// the stable review app result of an in-run deploy
|
|
9637
|
+
...(inRunCondition && inRunDeliveryIds.length > 0
|
|
9638
|
+
? (0, reviewDeployWorkflow_1.makeReviewAppAggregateJob)(inRunDeliveryIds, inRunCondition)
|
|
9639
|
+
: {}),
|
|
9640
|
+
// once green, hand over to the review deploy workflow
|
|
9641
|
+
// when the PR deploys but this run didn't
|
|
9642
|
+
...(workflows[reviewDeployWorkflow_1.REVIEW_DEPLOY_WORKFLOW_FILE] &&
|
|
9643
|
+
(0, reviewApps_1.isReviewDeployGated)(reviewApps)
|
|
9644
|
+
? (0, reviewDeployWorkflow_1.makeReviewDeployTriggerJob)(reviewApps, inRunCondition)
|
|
9645
|
+
: {}),
|
|
9646
|
+
}
|
|
9488
9647
|
: workflowJobs,
|
|
9489
9648
|
};
|
|
9649
|
+
workflows[`${GENERATED_FILE_PREFIX}${workflowFileName(trigger)}`] =
|
|
9650
|
+
trigger === "mr" && reviewApps.drafts === "skip"
|
|
9651
|
+
? (0, reviewDeployWorkflow_1.withDraftsSkipped)(workflow)
|
|
9652
|
+
: workflow;
|
|
9490
9653
|
}
|
|
9491
9654
|
}
|
|
9492
9655
|
if (Object.keys(reviewStopJobs).length > 0) {
|
|
9493
|
-
|
|
9656
|
+
const reviewStopWorkflow = {
|
|
9494
9657
|
name: "🛑 catladder stop review app",
|
|
9495
9658
|
on: {
|
|
9496
9659
|
pull_request: { types: ["closed"] },
|
|
@@ -9517,6 +9680,10 @@ class GithubBackend {
|
|
|
9517
9680
|
},
|
|
9518
9681
|
jobs: { ...(0, ensureImageJobs_1.makeEnsureImageGithubJobs)(images), ...reviewStopJobs },
|
|
9519
9682
|
};
|
|
9683
|
+
workflows[`${GENERATED_FILE_PREFIX}review-stop.yml`] = workflows[reviewDeployWorkflow_1.REVIEW_DEPLOY_WORKFLOW_FILE]
|
|
9684
|
+
? (0, reviewDeployWorkflow_1.withReviewStopSwitch)(reviewStopWorkflow, reviewApps, (0, reviewApps_1.isReviewDeployGated)(reviewApps) &&
|
|
9685
|
+
(0, reviewDeployWorkflow_1.getInRunDeployCondition)(reviewApps) !== undefined)
|
|
9686
|
+
: reviewStopWorkflow;
|
|
9520
9687
|
}
|
|
9521
9688
|
for (const [kind, bucket] of Object.entries(manualKindBuckets)) {
|
|
9522
9689
|
if (Object.keys(bucket.jobs).length === 0) {
|
|
@@ -9651,7 +9818,12 @@ exports.AGGREGATE_CHECK_JOB_NAME = "catladder ✅";
|
|
|
9651
9818
|
* `success` to `needs` even when red, so they stay non-blocking here
|
|
9652
9819
|
* by the same mechanism that keeps their workflow green.
|
|
9653
9820
|
*/
|
|
9654
|
-
const makeAggregateCheckJob = (reviewJobs
|
|
9821
|
+
const makeAggregateCheckJob = (reviewJobs,
|
|
9822
|
+
/**
|
|
9823
|
+
* jobs that may be skipped on purpose (review delivery chains behind
|
|
9824
|
+
* the reviewApps switch) — their skip is no failure, their failure is
|
|
9825
|
+
*/
|
|
9826
|
+
skippable = []) => ({
|
|
9655
9827
|
[exports.AGGREGATE_CHECK_JOB_ID]: {
|
|
9656
9828
|
name: exports.AGGREGATE_CHECK_JOB_NAME,
|
|
9657
9829
|
"runs-on": "ubuntu-latest",
|
|
@@ -9662,13 +9834,22 @@ const makeAggregateCheckJob = (reviewJobs) => ({
|
|
|
9662
9834
|
name: exports.AGGREGATE_CHECK_JOB_NAME,
|
|
9663
9835
|
// the needs context only interpolates inside the workflow file,
|
|
9664
9836
|
// so this script stays inline
|
|
9665
|
-
run:
|
|
9666
|
-
|
|
9667
|
-
|
|
9668
|
-
|
|
9669
|
-
|
|
9670
|
-
|
|
9671
|
-
|
|
9837
|
+
run: (skippable.length === 0
|
|
9838
|
+
? [
|
|
9839
|
+
`results='\${{ toJSON(needs) }}'`,
|
|
9840
|
+
`echo "$results"`,
|
|
9841
|
+
`if echo "$results" | grep -qE '"result": "(failure|cancelled|skipped)"'; then`,
|
|
9842
|
+
` echo "a required job did not succeed"; exit 1`,
|
|
9843
|
+
`fi`,
|
|
9844
|
+
]
|
|
9845
|
+
: [
|
|
9846
|
+
`results='\${{ toJSON(needs) }}'`,
|
|
9847
|
+
`echo "$results"`,
|
|
9848
|
+
`failed=$(echo "$results" | jq -r --arg skippable ' ${[...skippable].sort().join(" ")} ' 'to_entries[] | .key as $job | select(.value.result == "failure" or .value.result == "cancelled" or (.value.result == "skipped" and ($skippable | contains(" " + $job + " ") | not))) | $job')`,
|
|
9849
|
+
`if [ -n "$failed" ]; then`,
|
|
9850
|
+
` echo "required jobs did not succeed: $failed"; exit 1`,
|
|
9851
|
+
`fi`,
|
|
9852
|
+
]).join("\n"),
|
|
9672
9853
|
shell: "bash",
|
|
9673
9854
|
},
|
|
9674
9855
|
],
|
|
@@ -9679,6 +9860,97 @@ exports.makeAggregateCheckJob = makeAggregateCheckJob;
|
|
|
9679
9860
|
|
|
9680
9861
|
/***/ }),
|
|
9681
9862
|
|
|
9863
|
+
/***/ 75125:
|
|
9864
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
9865
|
+
|
|
9866
|
+
"use strict";
|
|
9867
|
+
|
|
9868
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
9869
|
+
exports.createArtifactDownloadSteps = exports.createArtifactUploadSteps = exports.artifactArchiveFile = void 0;
|
|
9870
|
+
/**
|
|
9871
|
+
* Artifacts are handed between github jobs as ONE tar archive instead
|
|
9872
|
+
* of letting upload-artifact walk the paths itself.
|
|
9873
|
+
*
|
|
9874
|
+
* upload-artifact does not preserve symlinks (it uploads the target's
|
|
9875
|
+
* content) nor file modes, and it strips the paths' common ancestor.
|
|
9876
|
+
* Gitlab artifacts keep all of that, and builds rely on it: next.js
|
|
9877
|
+
* with turbopack writes `.next/node_modules/<pkg>-<hash>` as relative
|
|
9878
|
+
* symlinks into the pnpm virtual store. Copied as plain directories,
|
|
9879
|
+
* node resolves the package's own dependencies from the wrong place
|
|
9880
|
+
* and the app crashes with ERR_MODULE_NOT_FOUND at runtime. tar keeps
|
|
9881
|
+
* symlinks, modes and the paths relative to the repo root (github's
|
|
9882
|
+
* own docs recommend it for exactly this).
|
|
9883
|
+
*
|
|
9884
|
+
* The archive is not hidden (upload-artifact skips hidden files) and
|
|
9885
|
+
* lives in the workspace, the one path run steps and actions agree on
|
|
9886
|
+
* inside job containers too.
|
|
9887
|
+
*/
|
|
9888
|
+
const artifactArchiveFile = (jobId) => `catladder-artifacts-${jobId}.tar`;
|
|
9889
|
+
exports.artifactArchiveFile = artifactArchiveFile;
|
|
9890
|
+
const createArtifactUploadSteps = (jobId, paths, condition) => {
|
|
9891
|
+
const archive = (0, exports.artifactArchiveFile)(jobId);
|
|
9892
|
+
const ifCondition = condition ? { if: condition } : {};
|
|
9893
|
+
return [
|
|
9894
|
+
{
|
|
9895
|
+
name: "Archive artifacts",
|
|
9896
|
+
...ifCondition,
|
|
9897
|
+
run: [
|
|
9898
|
+
"shopt -s nullglob globstar",
|
|
9899
|
+
"paths=()",
|
|
9900
|
+
// unquoted on purpose: artifact paths may be globs. Non-matching
|
|
9901
|
+
// globs vanish (nullglob), missing literal paths are skipped —
|
|
9902
|
+
// gitlab only warns about both, so do we
|
|
9903
|
+
`for path in ${paths.join(" ")}; do`,
|
|
9904
|
+
' if [ -e "$path" ] || [ -L "$path" ]; then paths+=("$path"); else echo "::warning::artifact path not found: $path"; fi',
|
|
9905
|
+
"done",
|
|
9906
|
+
"if [ ${#paths[@]} -eq 0 ]; then",
|
|
9907
|
+
// an empty archive keeps downstream downloads from failing.
|
|
9908
|
+
// Written by hand: busybox tar (alpine job images) refuses to
|
|
9909
|
+
// create one, and zero blocks are what an empty tar is
|
|
9910
|
+
` head -c 10240 /dev/zero > ${archive}`,
|
|
9911
|
+
"else",
|
|
9912
|
+
` tar -cf ${archive} -- "\${paths[@]}"`,
|
|
9913
|
+
"fi",
|
|
9914
|
+
].join("\n"),
|
|
9915
|
+
shell: "bash",
|
|
9916
|
+
},
|
|
9917
|
+
{
|
|
9918
|
+
name: "Upload artifacts",
|
|
9919
|
+
...ifCondition,
|
|
9920
|
+
uses: "actions/upload-artifact@v4",
|
|
9921
|
+
with: {
|
|
9922
|
+
name: jobId,
|
|
9923
|
+
path: archive,
|
|
9924
|
+
"if-no-files-found": "error",
|
|
9925
|
+
},
|
|
9926
|
+
},
|
|
9927
|
+
];
|
|
9928
|
+
};
|
|
9929
|
+
exports.createArtifactUploadSteps = createArtifactUploadSteps;
|
|
9930
|
+
const createArtifactDownloadSteps = (providerIds) => providerIds.length === 0
|
|
9931
|
+
? []
|
|
9932
|
+
: [
|
|
9933
|
+
...providerIds.map((id) => ({
|
|
9934
|
+
name: `Download artifacts from ${id}`,
|
|
9935
|
+
uses: "actions/download-artifact@v4",
|
|
9936
|
+
with: { name: id },
|
|
9937
|
+
})),
|
|
9938
|
+
{
|
|
9939
|
+
name: "Extract artifacts",
|
|
9940
|
+
run: providerIds
|
|
9941
|
+
.flatMap((id) => [
|
|
9942
|
+
`tar -xf ${(0, exports.artifactArchiveFile)(id)}`,
|
|
9943
|
+
`rm ${(0, exports.artifactArchiveFile)(id)}`,
|
|
9944
|
+
])
|
|
9945
|
+
.join("\n"),
|
|
9946
|
+
shell: "bash",
|
|
9947
|
+
},
|
|
9948
|
+
];
|
|
9949
|
+
exports.createArtifactDownloadSteps = createArtifactDownloadSteps;
|
|
9950
|
+
//# sourceMappingURL=artifactArchive.js.map
|
|
9951
|
+
|
|
9952
|
+
/***/ }),
|
|
9953
|
+
|
|
9682
9954
|
/***/ 17300:
|
|
9683
9955
|
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
9684
9956
|
|
|
@@ -9748,6 +10020,7 @@ const utils_1 = __nccwpck_require__(49401);
|
|
|
9748
10020
|
const gitlab_1 = __nccwpck_require__(86962);
|
|
9749
10021
|
const scriptFunctions_1 = __nccwpck_require__(85873);
|
|
9750
10022
|
const ciVariables_1 = __nccwpck_require__(17300);
|
|
10023
|
+
const artifactArchive_1 = __nccwpck_require__(75125);
|
|
9751
10024
|
/**
|
|
9752
10025
|
* runner variables that only make sense on gitlab runners and must not
|
|
9753
10026
|
* leak into github jobs (docker-in-docker daemon config, kubernetes
|
|
@@ -9958,7 +10231,7 @@ const getReferencedSecretsEnv = (script, kinds) => Object.fromEntries([...script
|
|
|
9958
10231
|
.filter((name) => !ciVariables_1.GITHUB_INJECTED_WORKFLOW_ENV_NAMES.has(name))
|
|
9959
10232
|
.map((name) => [name, vaultValueExpression(name, kinds)]));
|
|
9960
10233
|
const makeGithubJob = (context, job, allJobs, uploadProviderIds, images, scripts, secretKinds = new Map()) => {
|
|
9961
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
10234
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
9962
10235
|
const id = (0, exports.githubJobId)(context, job.name);
|
|
9963
10236
|
const isComponent = context.type === "component";
|
|
9964
10237
|
const createsJobEnv = !!job.environment &&
|
|
@@ -10103,13 +10376,7 @@ const makeGithubJob = (context, job, allJobs, uploadProviderIds, images, scripts
|
|
|
10103
10376
|
? []
|
|
10104
10377
|
: [{ name: "Checkout", uses: "actions/checkout@v4" }]),
|
|
10105
10378
|
...createCacheSteps(context, job),
|
|
10106
|
-
...needs
|
|
10107
|
-
.filter((n) => n.artifacts)
|
|
10108
|
-
.map((n) => ({
|
|
10109
|
-
name: `Download artifacts from ${n.id}`,
|
|
10110
|
-
uses: "actions/download-artifact@v4",
|
|
10111
|
-
with: { name: n.id },
|
|
10112
|
-
})),
|
|
10379
|
+
...(0, artifactArchive_1.createArtifactDownloadSteps)(needs.filter((n) => n.artifacts).map((n) => n.id)),
|
|
10113
10380
|
{
|
|
10114
10381
|
name: job.name,
|
|
10115
10382
|
id: "main",
|
|
@@ -10130,27 +10397,15 @@ const makeGithubJob = (context, job, allJobs, uploadProviderIds, images, scripts
|
|
|
10130
10397
|
},
|
|
10131
10398
|
]
|
|
10132
10399
|
: []),
|
|
10133
|
-
...(pagesPublishDir
|
|
10134
|
-
?
|
|
10135
|
-
|
|
10136
|
-
|
|
10137
|
-
|
|
10138
|
-
|
|
10139
|
-
|
|
10140
|
-
|
|
10141
|
-
|
|
10142
|
-
name: id,
|
|
10143
|
-
path: artifactPaths.join("\n"),
|
|
10144
|
-
"if-no-files-found": "warn",
|
|
10145
|
-
// gitlab artifacts include everything; upload-artifact
|
|
10146
|
-
// silently drops hidden files by default — which is
|
|
10147
|
-
// exactly where next.js builds live (.next)
|
|
10148
|
-
"include-hidden-files": true,
|
|
10149
|
-
},
|
|
10150
|
-
},
|
|
10151
|
-
]
|
|
10152
|
-
: []
|
|
10153
|
-
: []),
|
|
10400
|
+
...(!pagesPublishDir && artifactPaths.length > 0
|
|
10401
|
+
? (0, artifactArchive_1.createArtifactUploadSteps)(id, artifactPaths,
|
|
10402
|
+
// gitlab's `artifacts.when`: steps only run on success by default
|
|
10403
|
+
((_k = job.artifacts) === null || _k === void 0 ? void 0 : _k.when) === "always"
|
|
10404
|
+
? "always()"
|
|
10405
|
+
: ((_l = job.artifacts) === null || _l === void 0 ? void 0 : _l.when) === "on_failure"
|
|
10406
|
+
? "failure()"
|
|
10407
|
+
: undefined)
|
|
10408
|
+
: []),
|
|
10154
10409
|
],
|
|
10155
10410
|
};
|
|
10156
10411
|
return [id, githubJob];
|
|
@@ -10634,8 +10889,525 @@ const resolveGithubRegistryImage = async (config) => {
|
|
|
10634
10889
|
return ghcr_1.GHCR_REPOSITORY_EXPRESSION;
|
|
10635
10890
|
}
|
|
10636
10891
|
};
|
|
10637
|
-
exports.resolveGithubRegistryImage = resolveGithubRegistryImage;
|
|
10638
|
-
//# sourceMappingURL=registryImage.js.map
|
|
10892
|
+
exports.resolveGithubRegistryImage = resolveGithubRegistryImage;
|
|
10893
|
+
//# sourceMappingURL=registryImage.js.map
|
|
10894
|
+
|
|
10895
|
+
/***/ }),
|
|
10896
|
+
|
|
10897
|
+
/***/ 73671:
|
|
10898
|
+
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
10899
|
+
|
|
10900
|
+
"use strict";
|
|
10901
|
+
|
|
10902
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
10903
|
+
exports.withDraftsSkipped = exports.withReviewStopSwitch = exports.makeReviewDeployTriggerJob = exports.makeReviewAppAggregateJob = exports.REVIEW_APP_STATUS_CONTEXT = exports.REVIEW_APP_AGGREGATE_JOB_NAME = exports.REVIEW_APP_AGGREGATE_JOB_ID = exports.makeReviewDeployWorkflow = exports.getGatedReviewDeliveryJobIds = exports.withInRunCondition = exports.getInRunDeployCondition = exports.REVIEW_DEPLOY_WORKFLOW_FILE = void 0;
|
|
10904
|
+
const shippedCatci_1 = __nccwpck_require__(27967);
|
|
10905
|
+
const resolveRequirements_1 = __nccwpck_require__(81710);
|
|
10906
|
+
const reviewApps_1 = __nccwpck_require__(67071);
|
|
10907
|
+
const aggregateCheckJob_1 = __nccwpck_require__(82145);
|
|
10908
|
+
const ensureImageJobs_1 = __nccwpck_require__(77590);
|
|
10909
|
+
const CATCI = `${shippedCatci_1.GENERATED_CATCI_FOLDER}/index.js`;
|
|
10910
|
+
/**
|
|
10911
|
+
* the generated review deploy workflow. Keep in sync with
|
|
10912
|
+
* GITHUB_REVIEW_DEPLOY_WORKFLOW in apps/cli/src/reviewApps/github.ts.
|
|
10913
|
+
*/
|
|
10914
|
+
exports.REVIEW_DEPLOY_WORKFLOW_FILE = "catladder-deploy-review.yml";
|
|
10915
|
+
const GUARD_JOB_ID = "catladder-review-deploy-guard";
|
|
10916
|
+
const TRIGGER_JOB_ID = "catladder-review-deploy-trigger";
|
|
10917
|
+
/** the PR of the run — from the event, or the dispatch input */
|
|
10918
|
+
const PR_NUMBER_EXPRESSION = "github.event.number || inputs.pr";
|
|
10919
|
+
/** quote a value for a github expression string literal */
|
|
10920
|
+
const literal = (value) => `'${value.replaceAll("'", "''")}'`;
|
|
10921
|
+
/** combine a job's existing `if` with another condition */
|
|
10922
|
+
const andIf = (existing, condition) => {
|
|
10923
|
+
if (!existing) {
|
|
10924
|
+
return `\${{ ${condition} }}`;
|
|
10925
|
+
}
|
|
10926
|
+
const unwrapped = existing.replace(/^\$\{\{\s*([\s\S]*?)\s*\}\}$/, "$1");
|
|
10927
|
+
return `\${{ (${unwrapped}) && (${condition}) }}`;
|
|
10928
|
+
};
|
|
10929
|
+
/**
|
|
10930
|
+
* the condition under which the review workflow deploys the review apps
|
|
10931
|
+
* ITSELF, decided from the pull request event at the start of the run
|
|
10932
|
+
* (labels, draft) — the common case then needs one run and one app
|
|
10933
|
+
* build, like gitlab's switch job. Whatever changes later (a label set
|
|
10934
|
+
* during or after the run, a draft marked ready, an explicit request) is
|
|
10935
|
+
* handled by the review deploy workflow. `deploy: "manual"` never
|
|
10936
|
+
* deploys on its own: undefined.
|
|
10937
|
+
*/
|
|
10938
|
+
const getInRunDeployCondition = (reviewApps) => {
|
|
10939
|
+
const labels = "github.event.pull_request.labels.*.name";
|
|
10940
|
+
const policy = reviewApps.deploy === "optIn"
|
|
10941
|
+
? `contains(${labels}, ${literal(reviewApps.label)})`
|
|
10942
|
+
: reviewApps.deploy === "optOut"
|
|
10943
|
+
? `!contains(${labels}, ${literal(reviewApps.skipLabel)})`
|
|
10944
|
+
: reviewApps.deploy === "auto"
|
|
10945
|
+
? undefined
|
|
10946
|
+
: null;
|
|
10947
|
+
if (policy === null) {
|
|
10948
|
+
return undefined;
|
|
10949
|
+
}
|
|
10950
|
+
const notDraft = reviewApps.drafts !== "likeReady"
|
|
10951
|
+
? "!github.event.pull_request.draft"
|
|
10952
|
+
: undefined;
|
|
10953
|
+
return [policy, notDraft].filter(Boolean).join(" && ") || "true";
|
|
10954
|
+
};
|
|
10955
|
+
exports.getInRunDeployCondition = getInRunDeployCondition;
|
|
10956
|
+
/** combine a job's `if` with the in-run deploy condition */
|
|
10957
|
+
const withInRunCondition = (job, condition) => ({
|
|
10958
|
+
...job,
|
|
10959
|
+
if: andIf(job.if, condition),
|
|
10960
|
+
});
|
|
10961
|
+
exports.withInRunCondition = withInRunCondition;
|
|
10962
|
+
/**
|
|
10963
|
+
* the review delivery jobs (docker → deploy → verify) that move out of
|
|
10964
|
+
* the review workflow into the review deploy workflow:
|
|
10965
|
+
* - all of them when the reviewApps config gates review deploys
|
|
10966
|
+
* - otherwise those of components whose review deploy is manual
|
|
10967
|
+
* (`deploy.when: "manual"`) — github has no manual jobs inside a run,
|
|
10968
|
+
* and the generic manual-task dispatch can't carry the pull request
|
|
10969
|
+
*/
|
|
10970
|
+
const getGatedReviewDeliveryJobIds = (triggerJobs, reviewApps) => {
|
|
10971
|
+
var _a;
|
|
10972
|
+
const gatedByConfig = (0, reviewApps_1.isReviewDeployGated)(reviewApps);
|
|
10973
|
+
const manualComponents = new Set([...triggerJobs.values()]
|
|
10974
|
+
.filter(({ job, context }) => (0, reviewApps_1.isReviewDeliveryJob)(context, job) &&
|
|
10975
|
+
job.stage === "deploy" &&
|
|
10976
|
+
job.gate === "manual")
|
|
10977
|
+
.map(({ context }) => context.name));
|
|
10978
|
+
const ids = new Set([...triggerJobs]
|
|
10979
|
+
.filter(([, { job, context }]) => (0, reviewApps_1.isReviewDeliveryJob)(context, job) &&
|
|
10980
|
+
(gatedByConfig || manualComponents.has(context.name)))
|
|
10981
|
+
.map(([id]) => id));
|
|
10982
|
+
// jobs depending on a moved job (e.g. a custom e2e job waiting for a
|
|
10983
|
+
// review deploy) can't stay behind: they move along
|
|
10984
|
+
let grown = ids.size > 0;
|
|
10985
|
+
while (grown) {
|
|
10986
|
+
grown = false;
|
|
10987
|
+
for (const [id, { githubJob }] of triggerJobs) {
|
|
10988
|
+
if (!ids.has(id) && ((_a = githubJob.needs) === null || _a === void 0 ? void 0 : _a.some((need) => ids.has(need)))) {
|
|
10989
|
+
ids.add(id);
|
|
10990
|
+
grown = true;
|
|
10991
|
+
}
|
|
10992
|
+
}
|
|
10993
|
+
}
|
|
10994
|
+
return [...ids];
|
|
10995
|
+
};
|
|
10996
|
+
exports.getGatedReviewDeliveryJobIds = getGatedReviewDeliveryJobIds;
|
|
10997
|
+
/**
|
|
10998
|
+
* the review deploy workflow (`▶️ catladder deploy review`): the review
|
|
10999
|
+
* delivery chains of a pull request, behind a guard that decides
|
|
11000
|
+
* whether this run deploys (see `catci review-app github-guard`).
|
|
11001
|
+
*
|
|
11002
|
+
* Started by
|
|
11003
|
+
* - the review workflow, once it is green and the label/draft policy
|
|
11004
|
+
* says the PR deploys (`source: ci`)
|
|
11005
|
+
* - switching the label on (opt-in: adding it, opt-out: removing the
|
|
11006
|
+
* skip label) or marking a draft ready (`drafts: "checksOnly"`)
|
|
11007
|
+
* - a human or agent dispatching it with the PR number — explicit
|
|
11008
|
+
* requests always deploy, provided the PR's CI is green
|
|
11009
|
+
*
|
|
11010
|
+
* Artifacts are scoped to a single run, so the run rebuilds what the
|
|
11011
|
+
* chains need (the app build); tests are not rerun — the guard checks
|
|
11012
|
+
* the PR's `catladder ✅` instead.
|
|
11013
|
+
*/
|
|
11014
|
+
const makeReviewDeployWorkflow = ({ deliveryIds, triggerJobs, reviewApps, images, workflowEnv, permissions, }) => {
|
|
11015
|
+
var _a, _b;
|
|
11016
|
+
const delivery = new Set(deliveryIds);
|
|
11017
|
+
const isQualityJob = (id) => {
|
|
11018
|
+
const entry = triggerJobs.get(id);
|
|
11019
|
+
return !!entry && (0, resolveRequirements_1.providesCapability)(entry.job, "qualityGate");
|
|
11020
|
+
};
|
|
11021
|
+
// the delivery jobs plus everything upstream they need from the
|
|
11022
|
+
// trigger (rebuilt in this run), except the quality jobs
|
|
11023
|
+
const included = new Set();
|
|
11024
|
+
const stack = [...deliveryIds];
|
|
11025
|
+
let current;
|
|
11026
|
+
while ((current = stack.pop()) !== undefined) {
|
|
11027
|
+
if (included.has(current))
|
|
11028
|
+
continue;
|
|
11029
|
+
included.add(current);
|
|
11030
|
+
for (const need of (_b = (_a = triggerJobs.get(current)) === null || _a === void 0 ? void 0 : _a.githubJob.needs) !== null && _b !== void 0 ? _b : []) {
|
|
11031
|
+
if (triggerJobs.has(need) && !isQualityJob(need))
|
|
11032
|
+
stack.push(need);
|
|
11033
|
+
}
|
|
11034
|
+
}
|
|
11035
|
+
// the guard runs first, every other job only when it says deploy
|
|
11036
|
+
const gatedJob = (job) => {
|
|
11037
|
+
var _a;
|
|
11038
|
+
return ({
|
|
11039
|
+
...job,
|
|
11040
|
+
needs: [GUARD_JOB_ID, ...((_a = job.needs) !== null && _a !== void 0 ? _a : [])],
|
|
11041
|
+
if: andIf(job.if, `needs.${GUARD_JOB_ID}.outputs.deploy == 'true'`),
|
|
11042
|
+
});
|
|
11043
|
+
};
|
|
11044
|
+
const checkoutPrHead = (step) => {
|
|
11045
|
+
var _a;
|
|
11046
|
+
return ((_a = step.uses) === null || _a === void 0 ? void 0 : _a.startsWith("actions/checkout@"))
|
|
11047
|
+
? {
|
|
11048
|
+
...step,
|
|
11049
|
+
// the PR head — a label event's default is the merge ref
|
|
11050
|
+
with: {
|
|
11051
|
+
...step.with,
|
|
11052
|
+
ref: "${{ github.event.pull_request.head.sha || github.sha }}",
|
|
11053
|
+
},
|
|
11054
|
+
}
|
|
11055
|
+
: step;
|
|
11056
|
+
};
|
|
11057
|
+
const chainJobs = Object.fromEntries([...included].sort().map((id) => {
|
|
11058
|
+
const { githubJob } = triggerJobs.get(id);
|
|
11059
|
+
return [
|
|
11060
|
+
id,
|
|
11061
|
+
gatedJob({
|
|
11062
|
+
...githubJob,
|
|
11063
|
+
// the quality jobs stay in the review workflow — the guard
|
|
11064
|
+
// checks their aggregate instead
|
|
11065
|
+
...(githubJob.needs
|
|
11066
|
+
? {
|
|
11067
|
+
needs: githubJob.needs.filter((need) => !isQualityJob(need) || delivery.has(need)),
|
|
11068
|
+
}
|
|
11069
|
+
: {}),
|
|
11070
|
+
steps: githubJob.steps.map(checkoutPrHead),
|
|
11071
|
+
}),
|
|
11072
|
+
];
|
|
11073
|
+
}));
|
|
11074
|
+
// only the images the chains run in (not e.g. the release image)
|
|
11075
|
+
const neededIds = new Set(Object.values(chainJobs).flatMap((job) => { var _a; return (_a = job.needs) !== null && _a !== void 0 ? _a : []; }));
|
|
11076
|
+
const imageJobs = Object.fromEntries(Object.entries((0, ensureImageJobs_1.makeEnsureImageGithubJobs)(images))
|
|
11077
|
+
.filter(([id]) => neededIds.has(id))
|
|
11078
|
+
.map(([id, job]) => [id, gatedJob(job)]));
|
|
11079
|
+
const switchLabel = (0, reviewApps_1.getReviewAppsSwitchLabel)(reviewApps);
|
|
11080
|
+
const pullRequestTypes = [
|
|
11081
|
+
...(switchLabel ? [switchLabel.deployOn] : []),
|
|
11082
|
+
...(reviewApps.drafts === "checksOnly" ? ["ready_for_review"] : []),
|
|
11083
|
+
];
|
|
11084
|
+
// pull request events that concern the review apps — other label
|
|
11085
|
+
// changes must neither run the guard nor cancel a running deploy
|
|
11086
|
+
const relevantEvent = [
|
|
11087
|
+
"github.event_name == 'workflow_dispatch'",
|
|
11088
|
+
...(switchLabel
|
|
11089
|
+
? [
|
|
11090
|
+
`(github.event.action == ${literal(switchLabel.deployOn)} && github.event.label.name == ${literal(switchLabel.label)})`,
|
|
11091
|
+
]
|
|
11092
|
+
: []),
|
|
11093
|
+
...(reviewApps.drafts === "checksOnly"
|
|
11094
|
+
? ["github.event.action == 'ready_for_review'"]
|
|
11095
|
+
: []),
|
|
11096
|
+
].join(" || ");
|
|
11097
|
+
return {
|
|
11098
|
+
name: "▶️ catladder deploy review",
|
|
11099
|
+
on: {
|
|
11100
|
+
workflow_dispatch: {
|
|
11101
|
+
inputs: {
|
|
11102
|
+
pr: {
|
|
11103
|
+
description: "number of the pull request whose review apps to deploy",
|
|
11104
|
+
required: true,
|
|
11105
|
+
type: "string",
|
|
11106
|
+
},
|
|
11107
|
+
source: {
|
|
11108
|
+
description: "who requests the deploy: manual (always deploys) or ci (applies the label/draft policy)",
|
|
11109
|
+
required: false,
|
|
11110
|
+
type: "choice",
|
|
11111
|
+
options: ["manual", "ci"],
|
|
11112
|
+
default: "manual",
|
|
11113
|
+
},
|
|
11114
|
+
},
|
|
11115
|
+
},
|
|
11116
|
+
...(pullRequestTypes.length > 0
|
|
11117
|
+
? { pull_request: { types: pullRequestTypes } }
|
|
11118
|
+
: {}),
|
|
11119
|
+
},
|
|
11120
|
+
concurrency: reviewDeployConcurrency(relevantEvent),
|
|
11121
|
+
permissions,
|
|
11122
|
+
env: { ...workflowEnv, CL_PR_NUMBER: `\${{ ${PR_NUMBER_EXPRESSION} }}` },
|
|
11123
|
+
jobs: {
|
|
11124
|
+
[GUARD_JOB_ID]: {
|
|
11125
|
+
name: "🚦 review deploy guard",
|
|
11126
|
+
"runs-on": "ubuntu-latest",
|
|
11127
|
+
...(pullRequestTypes.length > 0
|
|
11128
|
+
? { if: `\${{ ${relevantEvent} }}` }
|
|
11129
|
+
: {}),
|
|
11130
|
+
permissions: {
|
|
11131
|
+
contents: "read",
|
|
11132
|
+
"pull-requests": "read",
|
|
11133
|
+
checks: "read",
|
|
11134
|
+
statuses: "write",
|
|
11135
|
+
},
|
|
11136
|
+
env: {
|
|
11137
|
+
GITHUB_TOKEN: "${{ github.token }}",
|
|
11138
|
+
CL_REVIEW_APPS: JSON.stringify(reviewApps),
|
|
11139
|
+
CL_DEPLOY_SOURCE: "${{ github.event_name == 'workflow_dispatch' && (inputs.source || 'manual') || 'label' }}",
|
|
11140
|
+
CL_HEAD_SHA: "${{ github.event.pull_request.head.sha || github.sha }}",
|
|
11141
|
+
},
|
|
11142
|
+
outputs: { deploy: "${{ steps.main.outputs.deploy }}" },
|
|
11143
|
+
steps: [
|
|
11144
|
+
catciCheckout(),
|
|
11145
|
+
{
|
|
11146
|
+
name: "🚦 review deploy guard",
|
|
11147
|
+
id: "main",
|
|
11148
|
+
run: `node ${CATCI} review-app github-guard`,
|
|
11149
|
+
shell: "bash",
|
|
11150
|
+
},
|
|
11151
|
+
{
|
|
11152
|
+
// the pull request shows the deploy in flight
|
|
11153
|
+
name: "report pending review app",
|
|
11154
|
+
if: "${{ steps.main.outputs.deploy == 'true' }}",
|
|
11155
|
+
env: { GH_TOKEN: "${{ github.token }}" },
|
|
11156
|
+
run: `gh api "repos/$GITHUB_REPOSITORY/statuses/$CL_HEAD_SHA" -f state=pending -f context="${exports.REVIEW_APP_STATUS_CONTEXT}" -f description="deploying the review apps" -f target_url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" > /dev/null`,
|
|
11157
|
+
shell: "bash",
|
|
11158
|
+
},
|
|
11159
|
+
],
|
|
11160
|
+
},
|
|
11161
|
+
...imageJobs,
|
|
11162
|
+
...chainJobs,
|
|
11163
|
+
...(0, exports.makeReviewAppAggregateJob)([GUARD_JOB_ID, ...Object.keys(imageJobs), ...Object.keys(chainJobs)], `needs.${GUARD_JOB_ID}.outputs.deploy == 'true'`),
|
|
11164
|
+
},
|
|
11165
|
+
};
|
|
11166
|
+
};
|
|
11167
|
+
exports.makeReviewDeployWorkflow = makeReviewDeployWorkflow;
|
|
11168
|
+
exports.REVIEW_APP_AGGREGATE_JOB_ID = "catladder-review-app-ok";
|
|
11169
|
+
exports.REVIEW_APP_AGGREGATE_JOB_NAME = "catladder review app ✅";
|
|
11170
|
+
/** the commit status context the aggregate job reports */
|
|
11171
|
+
exports.REVIEW_APP_STATUS_CONTEXT = "catladder review app";
|
|
11172
|
+
/**
|
|
11173
|
+
* one job with a STABLE name that succeeds exactly when the review apps
|
|
11174
|
+
* of this run deployed and verified — the result a merge gate (a bot,
|
|
11175
|
+
* or a branch rule for projects that deploy every PR) waits for,
|
|
11176
|
+
* regardless of the project's components.
|
|
11177
|
+
*
|
|
11178
|
+
* It also reports a commit status on the PR head: runs started by a
|
|
11179
|
+
* dispatch (the trigger job, or by hand) are attached to the commit but
|
|
11180
|
+
* not shown on the pull request, a commit status always is.
|
|
11181
|
+
*
|
|
11182
|
+
* Runs only when the run deploys (the guard decided so, or the review
|
|
11183
|
+
* workflow's start condition held) — a run that doesn't deploy says
|
|
11184
|
+
* nothing about the review app (its check is skipped, the commit status
|
|
11185
|
+
* untouched). Neither does a cancelled run (superseded by a newer
|
|
11186
|
+
* deploy, or stopped): a gate keeps waiting instead of seeing red.
|
|
11187
|
+
*/
|
|
11188
|
+
const makeReviewAppAggregateJob = (needs,
|
|
11189
|
+
/** when the run deploys the review apps (a github expression) */
|
|
11190
|
+
deploysWhen) => ({
|
|
11191
|
+
[exports.REVIEW_APP_AGGREGATE_JOB_ID]: {
|
|
11192
|
+
name: exports.REVIEW_APP_AGGREGATE_JOB_NAME,
|
|
11193
|
+
"runs-on": "ubuntu-latest",
|
|
11194
|
+
needs: [...needs].sort(),
|
|
11195
|
+
if: `\${{ always() && (${deploysWhen}) }}`,
|
|
11196
|
+
permissions: { statuses: "write" },
|
|
11197
|
+
env: {
|
|
11198
|
+
GH_TOKEN: "${{ github.token }}",
|
|
11199
|
+
HEAD_SHA: "${{ github.event.pull_request.head.sha || github.sha }}",
|
|
11200
|
+
RUN_URL: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}",
|
|
11201
|
+
},
|
|
11202
|
+
steps: [
|
|
11203
|
+
{
|
|
11204
|
+
name: exports.REVIEW_APP_AGGREGATE_JOB_NAME,
|
|
11205
|
+
// the needs context only interpolates inside the workflow file,
|
|
11206
|
+
// so this script stays inline
|
|
11207
|
+
run: [
|
|
11208
|
+
`results='\${{ toJSON(needs) }}'`,
|
|
11209
|
+
`echo "$results"`,
|
|
11210
|
+
`if echo "$results" | grep -qE '"result": "failure"'; then`,
|
|
11211
|
+
` state=failure; description="the review apps did not deploy and verify"`,
|
|
11212
|
+
`elif echo "$results" | grep -qE '"result": "cancelled"'; then`,
|
|
11213
|
+
` # superseded by a newer deploy, or stopped: no result, not red`,
|
|
11214
|
+
` echo "cancelled — no review app result reported"; exit 0`,
|
|
11215
|
+
`elif echo "$results" | grep -qE '"result": "skipped"'; then`,
|
|
11216
|
+
` state=failure; description="the review apps did not deploy and verify"`,
|
|
11217
|
+
`else`,
|
|
11218
|
+
` state=success; description="review apps deployed and verified"`,
|
|
11219
|
+
`fi`,
|
|
11220
|
+
`gh api "repos/$GITHUB_REPOSITORY/statuses/$HEAD_SHA" -f state="$state" -f context="${exports.REVIEW_APP_STATUS_CONTEXT}" -f description="$description" -f target_url="$RUN_URL" > /dev/null`,
|
|
11221
|
+
`echo "$description"`,
|
|
11222
|
+
`[ "$state" = success ]`,
|
|
11223
|
+
].join("\n"),
|
|
11224
|
+
shell: "bash",
|
|
11225
|
+
},
|
|
11226
|
+
],
|
|
11227
|
+
},
|
|
11228
|
+
});
|
|
11229
|
+
exports.makeReviewAppAggregateJob = makeReviewAppAggregateJob;
|
|
11230
|
+
/** a checkout of just the materialized catci */
|
|
11231
|
+
const catciCheckout = () => ({
|
|
11232
|
+
name: "Checkout",
|
|
11233
|
+
uses: "actions/checkout@v4",
|
|
11234
|
+
with: { "sparse-checkout": shippedCatci_1.GENERATED_CATCI_FOLDER },
|
|
11235
|
+
});
|
|
11236
|
+
/**
|
|
11237
|
+
* one deploy/stop of a PR's review apps at a time, the newest wins —
|
|
11238
|
+
* shared by the deploy and the stop workflow, so closing a PR or
|
|
11239
|
+
* switching the label off cancels a deploy in flight before tearing
|
|
11240
|
+
* down. Events that don't concern the review apps get a group of
|
|
11241
|
+
* their own, so they never cancel anything.
|
|
11242
|
+
*/
|
|
11243
|
+
const reviewDeployConcurrency = (relevantEvent) => ({
|
|
11244
|
+
group: `catladder-review-deploy-\${{ ${PR_NUMBER_EXPRESSION} }}\${{ !(${relevantEvent}) && format('-ignored-{0}', github.run_id) || '' }}`,
|
|
11245
|
+
"cancel-in-progress": true,
|
|
11246
|
+
});
|
|
11247
|
+
/**
|
|
11248
|
+
* the last job of the review workflow: once it is green, dispatch the
|
|
11249
|
+
* review deploy workflow when the label/draft policy says this PR
|
|
11250
|
+
* deploys (read fresh from the PR, so a label set while CI ran counts).
|
|
11251
|
+
* Only for policies that deploy on their own — `deploy: "manual"` waits
|
|
11252
|
+
* for an explicit dispatch.
|
|
11253
|
+
*/
|
|
11254
|
+
const makeReviewDeployTriggerJob = (reviewApps,
|
|
11255
|
+
/** the review workflow deployed itself when this held at its start */
|
|
11256
|
+
inRunCondition) => reviewApps.deploy === "manual"
|
|
11257
|
+
? {}
|
|
11258
|
+
: {
|
|
11259
|
+
[TRIGGER_JOB_ID]: {
|
|
11260
|
+
name: "▶️ trigger review deploy",
|
|
11261
|
+
"runs-on": "ubuntu-latest",
|
|
11262
|
+
needs: [aggregateCheckJob_1.AGGREGATE_CHECK_JOB_ID],
|
|
11263
|
+
...(inRunCondition
|
|
11264
|
+
? {
|
|
11265
|
+
// NOT success(): it also checks the transitive needs,
|
|
11266
|
+
// and the chains this run skipped on purpose would make
|
|
11267
|
+
// it false — exactly when the trigger is needed
|
|
11268
|
+
if: `\${{ !cancelled() && needs.${aggregateCheckJob_1.AGGREGATE_CHECK_JOB_ID}.result == 'success' && !(${inRunCondition}) }}`,
|
|
11269
|
+
}
|
|
11270
|
+
: {}),
|
|
11271
|
+
permissions: {
|
|
11272
|
+
contents: "read",
|
|
11273
|
+
"pull-requests": "read",
|
|
11274
|
+
actions: "write",
|
|
11275
|
+
},
|
|
11276
|
+
env: {
|
|
11277
|
+
GITHUB_TOKEN: "${{ github.token }}",
|
|
11278
|
+
CL_REVIEW_APPS: JSON.stringify(reviewApps),
|
|
11279
|
+
CL_HEAD_REF: "${{ github.head_ref }}",
|
|
11280
|
+
CL_HEAD_SHA: "${{ github.event.pull_request.head.sha }}",
|
|
11281
|
+
},
|
|
11282
|
+
steps: [
|
|
11283
|
+
catciCheckout(),
|
|
11284
|
+
{
|
|
11285
|
+
name: "▶️ trigger review deploy",
|
|
11286
|
+
run: `node ${CATCI} review-app github-trigger`,
|
|
11287
|
+
shell: "bash",
|
|
11288
|
+
},
|
|
11289
|
+
],
|
|
11290
|
+
},
|
|
11291
|
+
};
|
|
11292
|
+
exports.makeReviewDeployTriggerJob = makeReviewDeployTriggerJob;
|
|
11293
|
+
/**
|
|
11294
|
+
* the review stop workflow under a label policy: switching the label
|
|
11295
|
+
* off (opt-in: removing it, opt-out: adding the skip label) stops the
|
|
11296
|
+
* review apps, like closing the PR does. It shares the deploy
|
|
11297
|
+
* workflow's concurrency group, so a deploy in flight is cancelled
|
|
11298
|
+
* first.
|
|
11299
|
+
*/
|
|
11300
|
+
const withReviewStopSwitch = (workflow, reviewApps,
|
|
11301
|
+
/** the review workflow deploys itself (see getInRunDeployCondition) */
|
|
11302
|
+
reviewRunDeploys = false) => {
|
|
11303
|
+
const switchLabel = (0, reviewApps_1.getReviewAppsSwitchLabel)(reviewApps);
|
|
11304
|
+
const relevantEvent = [
|
|
11305
|
+
"github.event_name == 'workflow_dispatch'",
|
|
11306
|
+
"github.event.action == 'closed'",
|
|
11307
|
+
...(switchLabel
|
|
11308
|
+
? [
|
|
11309
|
+
`(github.event.action == ${literal(switchLabel.stopOn)} && github.event.label.name == ${literal(switchLabel.label)})`,
|
|
11310
|
+
]
|
|
11311
|
+
: []),
|
|
11312
|
+
].join(" || ");
|
|
11313
|
+
return {
|
|
11314
|
+
...workflow,
|
|
11315
|
+
on: {
|
|
11316
|
+
...workflow.on,
|
|
11317
|
+
pull_request: {
|
|
11318
|
+
types: ["closed", ...(switchLabel ? [switchLabel.stopOn] : [])],
|
|
11319
|
+
},
|
|
11320
|
+
},
|
|
11321
|
+
concurrency: reviewDeployConcurrency(relevantEvent),
|
|
11322
|
+
jobs: Object.fromEntries(Object.entries({
|
|
11323
|
+
...(reviewRunDeploys ? makeCancelReviewRunsJob() : {}),
|
|
11324
|
+
...Object.fromEntries(Object.entries(workflow.jobs).map(([id, job]) => {
|
|
11325
|
+
var _a;
|
|
11326
|
+
return [
|
|
11327
|
+
id,
|
|
11328
|
+
// the teardown waits until no review run deploys anymore
|
|
11329
|
+
reviewRunDeploys && !id.startsWith("catladder-image-")
|
|
11330
|
+
? {
|
|
11331
|
+
...job,
|
|
11332
|
+
needs: [CANCEL_REVIEW_RUNS_JOB_ID, ...((_a = job.needs) !== null && _a !== void 0 ? _a : [])],
|
|
11333
|
+
}
|
|
11334
|
+
: job,
|
|
11335
|
+
];
|
|
11336
|
+
})),
|
|
11337
|
+
}).map(([id, job]) => [
|
|
11338
|
+
id,
|
|
11339
|
+
switchLabel ? { ...job, if: andIf(job.if, relevantEvent) } : job,
|
|
11340
|
+
])),
|
|
11341
|
+
};
|
|
11342
|
+
};
|
|
11343
|
+
exports.withReviewStopSwitch = withReviewStopSwitch;
|
|
11344
|
+
const CANCEL_REVIEW_RUNS_JOB_ID = "catladder-wait-review-runs";
|
|
11345
|
+
/**
|
|
11346
|
+
* the review workflow deploys review apps itself, outside the deploy /
|
|
11347
|
+
* stop concurrency group — a teardown would race a deploy still running
|
|
11348
|
+
* there (the deploy finishing after the stop leaves the app and its
|
|
11349
|
+
* database behind). So the stop first waits for the PR's unfinished
|
|
11350
|
+
* review runs to end — cancelling them when the PR was closed, letting
|
|
11351
|
+
* them finish when only the label was switched off (their CI still
|
|
11352
|
+
* counts). Never fails: a stuck run must not keep the teardown from
|
|
11353
|
+
* running (it gives up after 30 minutes).
|
|
11354
|
+
*/
|
|
11355
|
+
const makeCancelReviewRunsJob = () => ({
|
|
11356
|
+
[CANCEL_REVIEW_RUNS_JOB_ID]: {
|
|
11357
|
+
name: "wait for running review deploys",
|
|
11358
|
+
"runs-on": "ubuntu-latest",
|
|
11359
|
+
permissions: { actions: "write", "pull-requests": "read" },
|
|
11360
|
+
env: { GH_TOKEN: "${{ github.token }}" },
|
|
11361
|
+
steps: [
|
|
11362
|
+
{
|
|
11363
|
+
name: "wait for running review deploys",
|
|
11364
|
+
run: [
|
|
11365
|
+
`branch=$(gh pr view "$CL_PR_NUMBER" -R "$GITHUB_REPOSITORY" --json headRefName -q .headRefName) || exit 0`,
|
|
11366
|
+
`runs=$(gh run list -R "$GITHUB_REPOSITORY" --workflow catladder-review.yml --branch "$branch" --limit 20 --json databaseId,status -q '.[] | select(.status != "completed") | .databaseId') || exit 0`,
|
|
11367
|
+
`# a closed PR's CI is worthless: cancel it. Otherwise let the`,
|
|
11368
|
+
`# PR's CI finish (cancelling it would leave catladder ✅ red)`,
|
|
11369
|
+
`if [ "$(jq -r '.action // ""' "$GITHUB_EVENT_PATH")" = closed ]; then`,
|
|
11370
|
+
` for id in $runs; do echo "cancelling review run $id"; gh run cancel "$id" -R "$GITHUB_REPOSITORY" || true; done`,
|
|
11371
|
+
`fi`,
|
|
11372
|
+
`for id in $runs; do`,
|
|
11373
|
+
` echo "waiting for review run $id"`,
|
|
11374
|
+
` for _ in $(seq 1 360); do`,
|
|
11375
|
+
` [ "$(gh run view "$id" -R "$GITHUB_REPOSITORY" --json status -q .status)" = completed ] && break`,
|
|
11376
|
+
` sleep 5`,
|
|
11377
|
+
` done`,
|
|
11378
|
+
`done`,
|
|
11379
|
+
`exit 0`,
|
|
11380
|
+
].join("\n"),
|
|
11381
|
+
shell: "bash",
|
|
11382
|
+
},
|
|
11383
|
+
],
|
|
11384
|
+
},
|
|
11385
|
+
});
|
|
11386
|
+
/**
|
|
11387
|
+
* `drafts: "skip"`: draft PRs run nothing — every job of the review
|
|
11388
|
+
* workflow skips them, and marking the PR ready starts the run. A
|
|
11389
|
+
* skipped `catladder ✅` can't let a draft through: drafts can't be
|
|
11390
|
+
* merged.
|
|
11391
|
+
*/
|
|
11392
|
+
const withDraftsSkipped = (workflow) => {
|
|
11393
|
+
const on = workflow.on;
|
|
11394
|
+
return {
|
|
11395
|
+
...workflow,
|
|
11396
|
+
on: {
|
|
11397
|
+
...workflow.on,
|
|
11398
|
+
pull_request: {
|
|
11399
|
+
...on.pull_request,
|
|
11400
|
+
types: [...on.pull_request.types, "ready_for_review"],
|
|
11401
|
+
},
|
|
11402
|
+
},
|
|
11403
|
+
jobs: Object.fromEntries(Object.entries(workflow.jobs).map(([id, job]) => [
|
|
11404
|
+
id,
|
|
11405
|
+
{ ...job, if: andIf(job.if, "!github.event.pull_request.draft") },
|
|
11406
|
+
])),
|
|
11407
|
+
};
|
|
11408
|
+
};
|
|
11409
|
+
exports.withDraftsSkipped = withDraftsSkipped;
|
|
11410
|
+
//# sourceMappingURL=reviewDeployWorkflow.js.map
|
|
10639
11411
|
|
|
10640
11412
|
/***/ }),
|
|
10641
11413
|
|
|
@@ -10743,6 +11515,8 @@ const rules_1 = __nccwpck_require__(78503);
|
|
|
10743
11515
|
const config_1 = __nccwpck_require__(75591);
|
|
10744
11516
|
const configruedEnvs_1 = __nccwpck_require__(38866);
|
|
10745
11517
|
const autoStop_1 = __nccwpck_require__(72792);
|
|
11518
|
+
const reviewApps_1 = __nccwpck_require__(67071);
|
|
11519
|
+
const reviewDeployGate_1 = __nccwpck_require__(3485);
|
|
10746
11520
|
const createAllJobs_1 = __nccwpck_require__(83477);
|
|
10747
11521
|
const jobImagesPlan_1 = __nccwpck_require__(59623);
|
|
10748
11522
|
const shippedCatci_1 = __nccwpck_require__(27967);
|
|
@@ -10843,7 +11617,6 @@ class GitlabBackend {
|
|
|
10843
11617
|
return [mainInclude, ...componentIncludes];
|
|
10844
11618
|
}
|
|
10845
11619
|
async createPipeline(config, images) {
|
|
10846
|
-
const stages = (0, getPipelineStages_1.getPipelineStages)(config);
|
|
10847
11620
|
// register the release image first: the per-trigger job creation
|
|
10848
11621
|
// below emits the build jobs of all images registered so far. The
|
|
10849
11622
|
// release jobs themselves are created afterwards — they need the
|
|
@@ -10857,11 +11630,36 @@ class GitlabBackend {
|
|
|
10857
11630
|
...config_1.ALL_PIPELINE_TRIGGERS,
|
|
10858
11631
|
...branchTriggers,
|
|
10859
11632
|
];
|
|
10860
|
-
const
|
|
10861
|
-
|
|
10862
|
-
|
|
10863
|
-
|
|
11633
|
+
const reviewApps = (0, reviewApps_1.getReviewAppsConfig)(config);
|
|
11634
|
+
const jobsPerTrigger = await Promise.all(allTriggers.map(async (trigger) => {
|
|
11635
|
+
const catladderJobs = await (0, createAllJobs_1.createAllJobs)({
|
|
11636
|
+
config,
|
|
11637
|
+
trigger,
|
|
11638
|
+
pipelineType: this.type,
|
|
11639
|
+
});
|
|
11640
|
+
// review deploys behind one switch per MR (reviewApps config)
|
|
11641
|
+
const reviewDeployGate = trigger === "mr"
|
|
11642
|
+
? (0, reviewDeployGate_1.addGitlabReviewDeployGate)(catladderJobs, reviewApps)
|
|
11643
|
+
: undefined;
|
|
11644
|
+
const jobs = await (0, createGitlabJobs_1.createGitlabJobs)(catladderJobs, images, getGitlabRulesForTrigger(trigger));
|
|
11645
|
+
return {
|
|
11646
|
+
trigger,
|
|
11647
|
+
jobs: reviewDeployGate
|
|
11648
|
+
? [
|
|
11649
|
+
{
|
|
11650
|
+
name: reviewDeployGate_1.REVIEW_DEPLOY_GATE_JOB_NAME,
|
|
11651
|
+
gitlabJob: reviewDeployGate,
|
|
11652
|
+
context: null,
|
|
11653
|
+
},
|
|
11654
|
+
...jobs,
|
|
11655
|
+
]
|
|
11656
|
+
: jobs,
|
|
11657
|
+
};
|
|
11658
|
+
}));
|
|
10864
11659
|
const allJobsPerTrigger = jobsPerTrigger.flatMap(({ jobs }) => jobs);
|
|
11660
|
+
const stages = (0, getPipelineStages_1.getPipelineStages)(config, new Set(allJobsPerTrigger
|
|
11661
|
+
.map(({ gitlabJob }) => gitlabJob.stage)
|
|
11662
|
+
.filter((stage) => typeof stage === "string")));
|
|
10865
11663
|
// the jobs the queued-release executor waits for: everything that
|
|
10866
11664
|
// runs automatically in a main-branch pipeline. Manual jobs (stop,
|
|
10867
11665
|
// rollback, gates) are excluded — gitlab skips a job whose needs
|
|
@@ -10937,6 +11735,7 @@ class GitlabBackend {
|
|
|
10937
11735
|
},
|
|
10938
11736
|
branchPipelines: branchTriggers.map(({ branch }) => branch),
|
|
10939
11737
|
autoStop: (0, autoStop_1.getAutoStopConfig)(config),
|
|
11738
|
+
reviewApps,
|
|
10940
11739
|
});
|
|
10941
11740
|
}
|
|
10942
11741
|
}
|
|
@@ -11391,21 +12190,23 @@ function getGitlabNeedsForWorkspaceJob(context, { needs }, allJobs) {
|
|
|
11391
12190
|
"use strict";
|
|
11392
12191
|
|
|
11393
12192
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
11394
|
-
exports.createGitlabPipelineWithDefaults = void 0;
|
|
12193
|
+
exports.createGitlabPipelineWithDefaults = exports.DEFAULT_PIPELINE_IMAGE = void 0;
|
|
11395
12194
|
const bash_1 = __nccwpck_require__(94143);
|
|
11396
12195
|
const autoStop_1 = __nccwpck_require__(72792);
|
|
12196
|
+
const reviewApps_1 = __nccwpck_require__(67071);
|
|
11397
12197
|
const rules_1 = __nccwpck_require__(78503);
|
|
11398
12198
|
/**
|
|
11399
12199
|
* gitlab requires a top-level default image for jobs that don't set their
|
|
11400
12200
|
* own. Every catladder-generated job sets an explicit image, so this
|
|
11401
|
-
* fallback is never
|
|
12201
|
+
* fallback is never pulled for them — it just needs to be a valid,
|
|
11402
12202
|
* build-free image (a catladder job image would drag in a build job).
|
|
12203
|
+
* Trivial jobs (the review deploy switch) reuse it explicitly.
|
|
11403
12204
|
*/
|
|
11404
|
-
|
|
11405
|
-
const createGitlabPipelineWithDefaults = ({ image, variables, before_script, branchPipelines = [], autoStop, ...config }) => {
|
|
12205
|
+
exports.DEFAULT_PIPELINE_IMAGE = "node:22";
|
|
12206
|
+
const createGitlabPipelineWithDefaults = ({ image, variables, before_script, branchPipelines = [], autoStop, reviewApps, ...config }) => {
|
|
11406
12207
|
const pinLabel = autoStop && autoStop.pinLabel;
|
|
11407
12208
|
return {
|
|
11408
|
-
image: image !== null && image !== void 0 ? image : DEFAULT_PIPELINE_IMAGE,
|
|
12209
|
+
image: image !== null && image !== void 0 ? image : exports.DEFAULT_PIPELINE_IMAGE,
|
|
11409
12210
|
variables: {
|
|
11410
12211
|
FF_USE_FASTZIP: "true", // enable fastzip - a faster zip implementation that also supports level configuration.
|
|
11411
12212
|
ARTIFACT_COMPRESSION_LEVEL: "fast", // we value speed over compression
|
|
@@ -11443,6 +12244,16 @@ const createGitlabPipelineWithDefaults = ({ image, variables, before_script, bra
|
|
|
11443
12244
|
PIPELINE_NAME: "Thinking...",
|
|
11444
12245
|
},
|
|
11445
12246
|
},
|
|
12247
|
+
// reviewApps.drafts: "skip" — draft MRs get no pipeline (rules
|
|
12248
|
+
// stop at the first match, so this must come first)
|
|
12249
|
+
...((reviewApps === null || reviewApps === void 0 ? void 0 : reviewApps.drafts) === "skip"
|
|
12250
|
+
? [
|
|
12251
|
+
{
|
|
12252
|
+
if: `${rules_1.RULE_IS_MERGE_REQUEST.if} && ${reviewApps_1.GITLAB_DRAFT_MR_CONDITION}`,
|
|
12253
|
+
when: "never",
|
|
12254
|
+
},
|
|
12255
|
+
]
|
|
12256
|
+
: []),
|
|
11446
12257
|
// pinned MRs (rules stop at the first match, so this must come
|
|
11447
12258
|
// before the plain merge-request rule)
|
|
11448
12259
|
...(pinLabel
|
|
@@ -11516,14 +12327,23 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
|
11516
12327
|
exports.getPipelineStages = void 0;
|
|
11517
12328
|
const config_1 = __nccwpck_require__(11646);
|
|
11518
12329
|
const jobs_1 = __nccwpck_require__(98521);
|
|
12330
|
+
/**
|
|
12331
|
+
* stages that only appear in the pipeline when a job uses them, so that
|
|
12332
|
+
* projects not using the feature keep their generated files unchanged
|
|
12333
|
+
*/
|
|
12334
|
+
const OPTIONAL_STAGES = ["post-build"];
|
|
11519
12335
|
/**
|
|
11520
12336
|
* while technically not required, we group different envs in its own stage
|
|
11521
12337
|
* each job from `createJobs` that is defined as `envMode: "stagePerEnv"` will have `deploy dev`, etc. instead of just `deploy`
|
|
11522
12338
|
* this is just so that it looks nicer in gitlab and makes running mutliple manual tasks more easy to use
|
|
12339
|
+
*
|
|
12340
|
+
* `usedStages` are the stages of all generated jobs; optional stages are
|
|
12341
|
+
* dropped when none of them is used.
|
|
11523
12342
|
*/
|
|
11524
|
-
function getPipelineStages(config) {
|
|
12343
|
+
function getPipelineStages(config, usedStages = new Set()) {
|
|
11525
12344
|
const allEnvs = (0, config_1.getAllEnvsInAllComponents)(config).filter((e) => e !== "local");
|
|
11526
|
-
const
|
|
12345
|
+
const isUsed = (baseStage) => [...usedStages].some((stage) => stage === baseStage || stage.startsWith(`${baseStage} `));
|
|
12346
|
+
const stages = jobs_1.BASE_STAGES.filter((baseStage) => !OPTIONAL_STAGES.includes(baseStage) || isUsed(baseStage)).reduce((acc, baseStage) => [
|
|
11527
12347
|
...acc,
|
|
11528
12348
|
baseStage,
|
|
11529
12349
|
...allEnvs.map((e) => `${baseStage} ${e}`),
|
|
@@ -11801,6 +12621,107 @@ exports.getReleaseGateJobNames = getReleaseGateJobNames;
|
|
|
11801
12621
|
|
|
11802
12622
|
/***/ }),
|
|
11803
12623
|
|
|
12624
|
+
/***/ 3485:
|
|
12625
|
+
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
12626
|
+
|
|
12627
|
+
"use strict";
|
|
12628
|
+
|
|
12629
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
12630
|
+
exports.addGitlabReviewDeployGate = exports.REVIEW_DEPLOY_GATE_JOB_NAME = void 0;
|
|
12631
|
+
const reviewApps_1 = __nccwpck_require__(67071);
|
|
12632
|
+
const rules_1 = __nccwpck_require__(78503);
|
|
12633
|
+
const createGitlabPipeline_1 = __nccwpck_require__(32060);
|
|
12634
|
+
exports.REVIEW_DEPLOY_GATE_JOB_NAME = "🚀 deploy review";
|
|
12635
|
+
/**
|
|
12636
|
+
* the review deploy switch of a merge request (see reviewApps.ts): ONE
|
|
12637
|
+
* job per MR pipeline that unlocks the delivery chains (docker → deploy
|
|
12638
|
+
* → verify) of all components' review apps, instead of a manual job per
|
|
12639
|
+
* component — or a docker build per component in every pipeline.
|
|
12640
|
+
*
|
|
12641
|
+
* The chain heads `needs` the switch. It is a no-op job with
|
|
12642
|
+
* `allow_failure: true`: while it is unplayed, gitlab marks its
|
|
12643
|
+
* dependents skipped and the pipeline still passes ("Pipelines must
|
|
12644
|
+
* succeed" keeps working); playing it resets them to created, so it
|
|
12645
|
+
* can be played any time — early clicks wait for the builds and tests
|
|
12646
|
+
* like the chains always did (the same mechanism as the release
|
|
12647
|
+
* button).
|
|
12648
|
+
*
|
|
12649
|
+
* The label/draft policy decides whether it starts on its own. Label
|
|
12650
|
+
* changes only apply to the next pipeline (gitlab doesn't start one for
|
|
12651
|
+
* them) — `catladder mr review-app-on` sets the label and triggers one.
|
|
12652
|
+
*
|
|
12653
|
+
* Mutates the needs of the gated jobs; returns the switch job, or
|
|
12654
|
+
* nothing when review deploys aren't gated (or nothing deploys).
|
|
12655
|
+
*/
|
|
12656
|
+
const addGitlabReviewDeployGate = (allJobs, reviewApps) => {
|
|
12657
|
+
var _a;
|
|
12658
|
+
if (!(0, reviewApps_1.isReviewDeployGated)(reviewApps)) {
|
|
12659
|
+
return undefined;
|
|
12660
|
+
}
|
|
12661
|
+
let gated = false;
|
|
12662
|
+
for (const { context, jobs } of allJobs.components) {
|
|
12663
|
+
for (const job of jobs) {
|
|
12664
|
+
// the chain heads: verify follows its deploy anyway
|
|
12665
|
+
if ((0, reviewApps_1.isReviewDeliveryJob)(context, job) && job.stage !== "verify") {
|
|
12666
|
+
job.needs = [
|
|
12667
|
+
...((_a = job.needs) !== null && _a !== void 0 ? _a : []),
|
|
12668
|
+
{ job: exports.REVIEW_DEPLOY_GATE_JOB_NAME, artifacts: false, global: true },
|
|
12669
|
+
];
|
|
12670
|
+
gated = true;
|
|
12671
|
+
}
|
|
12672
|
+
}
|
|
12673
|
+
}
|
|
12674
|
+
if (!gated) {
|
|
12675
|
+
return undefined;
|
|
12676
|
+
}
|
|
12677
|
+
const isMr = rules_1.RULE_IS_MERGE_REQUEST.if;
|
|
12678
|
+
const manual = { when: "manual", allow_failure: true };
|
|
12679
|
+
const auto = { when: "on_success" };
|
|
12680
|
+
const policyRules = reviewApps.deploy === "optIn"
|
|
12681
|
+
? [
|
|
12682
|
+
{
|
|
12683
|
+
if: `${isMr} && $CI_MERGE_REQUEST_LABELS =~ ${(0, reviewApps_1.getGitlabLabelRegex)(reviewApps.label)}`,
|
|
12684
|
+
...auto,
|
|
12685
|
+
},
|
|
12686
|
+
{ if: isMr, ...manual },
|
|
12687
|
+
]
|
|
12688
|
+
: reviewApps.deploy === "optOut"
|
|
12689
|
+
? [
|
|
12690
|
+
{
|
|
12691
|
+
if: `${isMr} && $CI_MERGE_REQUEST_LABELS =~ ${(0, reviewApps_1.getGitlabLabelRegex)(reviewApps.skipLabel)}`,
|
|
12692
|
+
...manual,
|
|
12693
|
+
},
|
|
12694
|
+
{ if: isMr, ...auto },
|
|
12695
|
+
]
|
|
12696
|
+
: reviewApps.deploy === "manual"
|
|
12697
|
+
? [{ if: isMr, ...manual }]
|
|
12698
|
+
: [{ if: isMr, ...auto }];
|
|
12699
|
+
return {
|
|
12700
|
+
stage: "setup",
|
|
12701
|
+
image: createGitlabPipeline_1.DEFAULT_PIPELINE_IMAGE,
|
|
12702
|
+
variables: { GIT_STRATEGY: "none" },
|
|
12703
|
+
needs: [],
|
|
12704
|
+
interruptible: true,
|
|
12705
|
+
allow_failure: true,
|
|
12706
|
+
script: [
|
|
12707
|
+
`echo "review apps unlocked — the docker → deploy → verify chains of all components run as soon as their builds and tests succeeded"`,
|
|
12708
|
+
],
|
|
12709
|
+
rules: [
|
|
12710
|
+
rules_1.RULE_NEVER_ON_AGENT_TRIGGER,
|
|
12711
|
+
// drafts never deploy on their own (drafts: "skip" has no
|
|
12712
|
+
// pipeline at all, see the workflow rules)
|
|
12713
|
+
...(reviewApps.drafts !== "likeReady"
|
|
12714
|
+
? [{ if: `${isMr} && ${reviewApps_1.GITLAB_DRAFT_MR_CONDITION}`, ...manual }]
|
|
12715
|
+
: []),
|
|
12716
|
+
...policyRules,
|
|
12717
|
+
],
|
|
12718
|
+
};
|
|
12719
|
+
};
|
|
12720
|
+
exports.addGitlabReviewDeployGate = addGitlabReviewDeployGate;
|
|
12721
|
+
//# sourceMappingURL=reviewDeployGate.js.map
|
|
12722
|
+
|
|
12723
|
+
/***/ }),
|
|
12724
|
+
|
|
11804
12725
|
/***/ 21444:
|
|
11805
12726
|
/***/ ((__unused_webpack_module, exports) => {
|
|
11806
12727
|
|
|
@@ -15525,6 +16446,7 @@ const jobs_1 = __nccwpck_require__(98521);
|
|
|
15525
16446
|
const utils_1 = __nccwpck_require__(71286);
|
|
15526
16447
|
const autoStop_1 = __nccwpck_require__(72792);
|
|
15527
16448
|
const stop_1 = __nccwpck_require__(35937);
|
|
16449
|
+
const reviewApps_1 = __nccwpck_require__(67071);
|
|
15528
16450
|
const variables_1 = __nccwpck_require__(54768);
|
|
15529
16451
|
exports.DEPLOY_JOB_NAME = "🚀 Deploy";
|
|
15530
16452
|
class DeployJob extends jobs_1.CatladderJob {
|
|
@@ -15561,9 +16483,15 @@ class DeployJob extends jobs_1.CatladderJob {
|
|
|
15561
16483
|
: ((_b = context.componentConfig.env) === null || _b === void 0 ? void 0 : _b.stage) === false
|
|
15562
16484
|
? "auto" // is prod, but no staging, auto deploy
|
|
15563
16485
|
: "manual"; // manually deploy
|
|
15564
|
-
|
|
15565
|
-
|
|
15566
|
-
|
|
16486
|
+
// gated review apps: one switch per MR/PR unlocks the whole delivery
|
|
16487
|
+
// chain (see reviewApps.ts), the deploy itself then runs on its own
|
|
16488
|
+
const isGatedReviewApp = context.environment.instance.type === "review" &&
|
|
16489
|
+
(0, reviewApps_1.isReviewDeployGated)((0, reviewApps_1.getReviewAppsConfig)(context.fullConfig));
|
|
16490
|
+
const whenDeploy = isGatedReviewApp
|
|
16491
|
+
? "auto"
|
|
16492
|
+
: whenDeployDefined
|
|
16493
|
+
? whenDeployDefined
|
|
16494
|
+
: whenDeployDefault;
|
|
15567
16495
|
// wait for the deployment of other components first
|
|
15568
16496
|
const waitForRequirements = (_d = (_c = deployConfig === null || deployConfig === void 0 ? void 0 : deployConfig.waitFor) === null || _c === void 0 ? void 0 : _c.map((c) => ({
|
|
15569
16497
|
capability: "deployment",
|
|
@@ -15607,14 +16535,20 @@ class DeployJob extends jobs_1.CatladderJob {
|
|
|
15607
16535
|
: []),
|
|
15608
16536
|
];
|
|
15609
16537
|
// we don't want to deploy when there is a broken test
|
|
15610
|
-
const
|
|
15611
|
-
|
|
15612
|
-
artifacts: false,
|
|
15613
|
-
//
|
|
15614
|
-
|
|
15615
|
-
?
|
|
15616
|
-
|
|
15617
|
-
|
|
16538
|
+
const qualityGateRequirements = [
|
|
16539
|
+
// the component's own gates (e.g. post-build tests, custom test jobs)
|
|
16540
|
+
{ capability: "qualityGate", artifacts: false },
|
|
16541
|
+
// ...and the gates of the workspace build it is built in
|
|
16542
|
+
...((0, context_1.componentContextHasWorkspaceBuild)(context)
|
|
16543
|
+
? [
|
|
16544
|
+
{
|
|
16545
|
+
capability: "qualityGate",
|
|
16546
|
+
artifacts: false,
|
|
16547
|
+
from: { workspace: context.build.workspaceName },
|
|
16548
|
+
},
|
|
16549
|
+
]
|
|
16550
|
+
: []),
|
|
16551
|
+
];
|
|
15618
16552
|
super({
|
|
15619
16553
|
name: exports.DEPLOY_JOB_NAME,
|
|
15620
16554
|
script: jobDefinition.script,
|
|
@@ -15631,7 +16565,7 @@ class DeployJob extends jobs_1.CatladderJob {
|
|
|
15631
16565
|
...waitForRequirements,
|
|
15632
16566
|
// if the build is disabled, we don't need to wait for it
|
|
15633
16567
|
...(context.build.type !== "disabled"
|
|
15634
|
-
? [...buildRequirements,
|
|
16568
|
+
? [...buildRequirements, ...qualityGateRequirements]
|
|
15635
16569
|
: []),
|
|
15636
16570
|
],
|
|
15637
16571
|
gate: whenDeploy,
|
|
@@ -18395,6 +19329,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
18395
19329
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
18396
19330
|
__exportStar(__nccwpck_require__(46734), exports);
|
|
18397
19331
|
__exportStar(__nccwpck_require__(72792), exports);
|
|
19332
|
+
__exportStar(__nccwpck_require__(67071), exports);
|
|
18398
19333
|
__exportStar(__nccwpck_require__(62853), exports);
|
|
18399
19334
|
__exportStar(__nccwpck_require__(63557), exports);
|
|
18400
19335
|
__exportStar(__nccwpck_require__(78503), exports);
|
|
@@ -18407,6 +19342,7 @@ __exportStar(__nccwpck_require__(15707), exports);
|
|
|
18407
19342
|
__exportStar(__nccwpck_require__(45492), exports);
|
|
18408
19343
|
__exportStar(__nccwpck_require__(3659), exports);
|
|
18409
19344
|
__exportStar(__nccwpck_require__(50713), exports);
|
|
19345
|
+
__exportStar(__nccwpck_require__(28458), exports);
|
|
18410
19346
|
__exportStar(__nccwpck_require__(26041), exports);
|
|
18411
19347
|
__exportStar(__nccwpck_require__(47663), exports);
|
|
18412
19348
|
__exportStar(__nccwpck_require__(91804), exports);
|
|
@@ -19004,6 +19940,7 @@ exports.createJobsForComponentContext = void 0;
|
|
|
19004
19940
|
const build_1 = __nccwpck_require__(45492);
|
|
19005
19941
|
const deploy_1 = __nccwpck_require__(3659);
|
|
19006
19942
|
const jobs_1 = __nccwpck_require__(98521);
|
|
19943
|
+
const createPostBuildTestJobs_1 = __nccwpck_require__(61844);
|
|
19007
19944
|
const createVerifyJobs_1 = __nccwpck_require__(66900);
|
|
19008
19945
|
const injectDefaultVarsInCustomJobs = (context, jobs) => jobs.map(({ variables, ...job }) => {
|
|
19009
19946
|
var _a;
|
|
@@ -19023,17 +19960,26 @@ const getCustomJobs = (context) => {
|
|
|
19023
19960
|
return injectDefaultVarsInCustomJobs(context, rawJobs);
|
|
19024
19961
|
};
|
|
19025
19962
|
const createJobsForComponentContext = async (context) => {
|
|
19026
|
-
const [buildJobs, deployJobs, verifyJobs] = await Promise.all([
|
|
19963
|
+
const [buildJobs, postBuildTestJobs, deployJobs, verifyJobs] = await Promise.all([
|
|
19027
19964
|
context.build.type !== "disabled"
|
|
19028
19965
|
? build_1.BUILD_TYPES[context.build.buildType].jobs(context)
|
|
19029
19966
|
: [],
|
|
19967
|
+
(0, createPostBuildTestJobs_1.createPostBuildTestJobs)(context),
|
|
19030
19968
|
context.componentConfig.deploy !== false
|
|
19031
19969
|
? deploy_1.DEPLOY_TYPES[context.componentConfig.deploy.type].jobs(context)
|
|
19032
19970
|
: [],
|
|
19033
19971
|
(0, createVerifyJobs_1.createVerifyJobs)(context),
|
|
19034
19972
|
]);
|
|
19035
19973
|
const customJobs = getCustomJobs(context);
|
|
19036
|
-
|
|
19974
|
+
const otherJobs = [...buildJobs, ...deployJobs, ...verifyJobs, ...customJobs];
|
|
19975
|
+
(0, createPostBuildTestJobs_1.assertUniquePostBuildTestNames)(context, postBuildTestJobs, otherJobs);
|
|
19976
|
+
return [
|
|
19977
|
+
...buildJobs,
|
|
19978
|
+
...postBuildTestJobs,
|
|
19979
|
+
...deployJobs,
|
|
19980
|
+
...verifyJobs,
|
|
19981
|
+
...customJobs,
|
|
19982
|
+
];
|
|
19037
19983
|
};
|
|
19038
19984
|
exports.createJobsForComponentContext = createJobsForComponentContext;
|
|
19039
19985
|
//# sourceMappingURL=createJobsForComponent.js.map
|
|
@@ -19048,9 +19994,14 @@ exports.createJobsForComponentContext = createJobsForComponentContext;
|
|
|
19048
19994
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
19049
19995
|
exports.createJobsForWorkspace = void 0;
|
|
19050
19996
|
const build_1 = __nccwpck_require__(45492);
|
|
19997
|
+
const createPostBuildTestJobs_1 = __nccwpck_require__(61844);
|
|
19051
19998
|
const createJobsForWorkspace = async (context) => {
|
|
19052
|
-
const buildJobs = await
|
|
19053
|
-
|
|
19999
|
+
const [buildJobs, postBuildTestJobs] = await Promise.all([
|
|
20000
|
+
build_1.WORKSPACE_BUILD_TYPES[context.build.buildType].jobs(context),
|
|
20001
|
+
(0, createPostBuildTestJobs_1.createPostBuildTestJobs)(context),
|
|
20002
|
+
]);
|
|
20003
|
+
(0, createPostBuildTestJobs_1.assertUniquePostBuildTestNames)(context, postBuildTestJobs, buildJobs);
|
|
20004
|
+
return [...buildJobs, ...postBuildTestJobs];
|
|
19054
20005
|
};
|
|
19055
20006
|
exports.createJobsForWorkspace = createJobsForWorkspace;
|
|
19056
20007
|
//# sourceMappingURL=createJobsForWorkspace.js.map
|
|
@@ -19510,7 +20461,7 @@ exports.projectFileExists = projectFileExists;
|
|
|
19510
20461
|
"use strict";
|
|
19511
20462
|
|
|
19512
20463
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
19513
|
-
exports.resolveRequirements = void 0;
|
|
20464
|
+
exports.resolveRequirements = exports.providesCapability = void 0;
|
|
19514
20465
|
/**
|
|
19515
20466
|
* jobs that don't declare `provides` get a capability derived from
|
|
19516
20467
|
* their stage. This keeps e.g. custom jobs in the test stage acting
|
|
@@ -19519,6 +20470,7 @@ exports.resolveRequirements = void 0;
|
|
|
19519
20470
|
const DEFAULT_STAGE_CAPABILITIES = {
|
|
19520
20471
|
build: "build",
|
|
19521
20472
|
test: "qualityGate",
|
|
20473
|
+
"post-build": "qualityGate",
|
|
19522
20474
|
deploy: "deployment",
|
|
19523
20475
|
};
|
|
19524
20476
|
/**
|
|
@@ -19540,6 +20492,7 @@ const providesCapability = (job, capability) => {
|
|
|
19540
20492
|
((_a = CAPABILITY_IMPLICATIONS[c]) !== null && _a !== void 0 ? _a : []).includes(capability);
|
|
19541
20493
|
});
|
|
19542
20494
|
};
|
|
20495
|
+
exports.providesCapability = providesCapability;
|
|
19543
20496
|
/**
|
|
19544
20497
|
* the planner: resolves the semantic `requires` declarations of all jobs
|
|
19545
20498
|
* to concrete job dependencies (`needs`), now that all jobs of the
|
|
@@ -19581,7 +20534,7 @@ const expandRequirement = (context, job, requirement, allJobs) => {
|
|
|
19581
20534
|
c.context.env === env;
|
|
19582
20535
|
})
|
|
19583
20536
|
.flatMap((c) => c.jobs);
|
|
19584
|
-
const providers = candidates.filter((candidate) => candidate !== job && providesCapability(candidate, capability));
|
|
20537
|
+
const providers = candidates.filter((candidate) => candidate !== job && (0, exports.providesCapability)(candidate, capability));
|
|
19585
20538
|
// consuming artifacts from nobody is almost always a broken config
|
|
19586
20539
|
const isStrict = (_b = strict !== null && strict !== void 0 ? strict : artifacts) !== null && _b !== void 0 ? _b : false;
|
|
19587
20540
|
if (isStrict && providers.length === 0) {
|
|
@@ -19660,6 +20613,216 @@ exports.getWorkspaceDependencies = getWorkspaceDependencies;
|
|
|
19660
20613
|
|
|
19661
20614
|
/***/ }),
|
|
19662
20615
|
|
|
20616
|
+
/***/ 61844:
|
|
20617
|
+
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
20618
|
+
|
|
20619
|
+
"use strict";
|
|
20620
|
+
|
|
20621
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
20622
|
+
exports.assertUniquePostBuildTestNames = exports.createPostBuildTestJobs = exports.postBuildTestJobName = exports.POST_BUILD_TEST_STAGE = void 0;
|
|
20623
|
+
const createArtifactsConfig_1 = __nccwpck_require__(14149);
|
|
20624
|
+
const cache_1 = __nccwpck_require__(74559);
|
|
20625
|
+
const constants_1 = __nccwpck_require__(5576);
|
|
20626
|
+
const packageManagerInstall_1 = __nccwpck_require__(28419);
|
|
20627
|
+
const types_1 = __nccwpck_require__(35249);
|
|
20628
|
+
const runner_1 = __nccwpck_require__(92146);
|
|
20629
|
+
const jobs_1 = __nccwpck_require__(98521);
|
|
20630
|
+
const utils_1 = __nccwpck_require__(49401);
|
|
20631
|
+
exports.POST_BUILD_TEST_STAGE = "post-build";
|
|
20632
|
+
const postBuildTestJobName = (name) => `🔬 ${name}`;
|
|
20633
|
+
exports.postBuildTestJobName = postBuildTestJobName;
|
|
20634
|
+
const getPostBuildTestsConfig = (context) => {
|
|
20635
|
+
if (context.type === "workspace") {
|
|
20636
|
+
return context.build.config.postBuildTests;
|
|
20637
|
+
}
|
|
20638
|
+
if (context.build.type === "disabled") {
|
|
20639
|
+
return undefined;
|
|
20640
|
+
}
|
|
20641
|
+
return context.build.config.postBuildTests;
|
|
20642
|
+
};
|
|
20643
|
+
/**
|
|
20644
|
+
* whether the build output is a node project, so the job has to install
|
|
20645
|
+
* the dependencies before running the tests
|
|
20646
|
+
*/
|
|
20647
|
+
const isNodeBuild = (context) => {
|
|
20648
|
+
if (context.type === "workspace") {
|
|
20649
|
+
return true; // workspace builds are node builds
|
|
20650
|
+
}
|
|
20651
|
+
if (context.build.type === "fromWorkspace") {
|
|
20652
|
+
return true;
|
|
20653
|
+
}
|
|
20654
|
+
if (context.build.type === "standalone") {
|
|
20655
|
+
return (0, types_1.isOfBuildType)(context.build.config, "node", "node-static", "storybook", "meteor");
|
|
20656
|
+
}
|
|
20657
|
+
return false;
|
|
20658
|
+
};
|
|
20659
|
+
const hasBuildCommand = (buildCommand) => buildCommand !== null && buildCommand !== false;
|
|
20660
|
+
/**
|
|
20661
|
+
* whether the build creates an app build job with artifacts. Rails builds
|
|
20662
|
+
* and builds with `buildCommand: false` only build the docker image.
|
|
20663
|
+
*/
|
|
20664
|
+
const hasBuildArtifacts = (context) => {
|
|
20665
|
+
if (context.type === "workspace") {
|
|
20666
|
+
return hasBuildCommand(context.build.config.buildCommand);
|
|
20667
|
+
}
|
|
20668
|
+
if (context.build.type === "fromWorkspace") {
|
|
20669
|
+
return hasBuildCommand(context.build.workspaceBuildConfig.buildCommand);
|
|
20670
|
+
}
|
|
20671
|
+
if (context.build.type === "standalone") {
|
|
20672
|
+
return (!(0, types_1.isOfBuildType)(context.build.config, "rails") &&
|
|
20673
|
+
hasBuildCommand(context.build.config.buildCommand));
|
|
20674
|
+
}
|
|
20675
|
+
return false;
|
|
20676
|
+
};
|
|
20677
|
+
const getDefaultImage = (context) => {
|
|
20678
|
+
if (context.type === "component" &&
|
|
20679
|
+
context.build.type === "standalone" &&
|
|
20680
|
+
(0, types_1.isOfBuildType)(context.build.config, "custom")) {
|
|
20681
|
+
// custom builds bring their own toolchain image
|
|
20682
|
+
return context.build.config.jobImage;
|
|
20683
|
+
}
|
|
20684
|
+
return (0, runner_1.getRunnerImage)("jobs-default");
|
|
20685
|
+
};
|
|
20686
|
+
/**
|
|
20687
|
+
* jobs for `postBuildTests`: they run after the build in the `post-build`
|
|
20688
|
+
* stage, against the build artifacts, and act as quality gates, so a
|
|
20689
|
+
* failing post-build test blocks the deploy.
|
|
20690
|
+
*/
|
|
20691
|
+
const createPostBuildTestJobs = async (context) => {
|
|
20692
|
+
var _a;
|
|
20693
|
+
// like lint and test: tagged releases deploy what was already tested
|
|
20694
|
+
if (context.trigger === "taggedRelease") {
|
|
20695
|
+
return [];
|
|
20696
|
+
}
|
|
20697
|
+
const tests = Object.entries((_a = getPostBuildTestsConfig(context)) !== null && _a !== void 0 ? _a : {}).filter((entry) => entry[1] !== false);
|
|
20698
|
+
if (tests.length === 0) {
|
|
20699
|
+
return [];
|
|
20700
|
+
}
|
|
20701
|
+
const nodeBuild = isNodeBuild(context);
|
|
20702
|
+
const [packageManagerInstall, nodeCache] = nodeBuild
|
|
20703
|
+
? await Promise.all([
|
|
20704
|
+
(0, packageManagerInstall_1.getPackageManagerInstall)(context),
|
|
20705
|
+
// pull-only: the build job is the designated cache writer
|
|
20706
|
+
(0, cache_1.getNodeCache)(context, "pull"),
|
|
20707
|
+
])
|
|
20708
|
+
: [null, null];
|
|
20709
|
+
// components built in a workspace test the workspace build output
|
|
20710
|
+
const from = context.type === "component" && context.build.type === "fromWorkspace"
|
|
20711
|
+
? { workspace: context.build.workspaceName }
|
|
20712
|
+
: undefined;
|
|
20713
|
+
const buildRequirement = hasBuildArtifacts(context)
|
|
20714
|
+
? { capability: "buildArtifacts", artifacts: true, strict: true, from }
|
|
20715
|
+
: // no build artifacts (e.g. rails): run after the build (docker image)
|
|
20716
|
+
{ capability: "build", artifacts: false, from };
|
|
20717
|
+
return tests.map(([name, test]) => {
|
|
20718
|
+
var _a, _b, _c;
|
|
20719
|
+
const artifactsConfig = (0, createArtifactsConfig_1.createArtifactsConfig)(context.build.dir, test.artifactsReports, test.artifacts);
|
|
20720
|
+
return new jobs_1.CatladderJob({
|
|
20721
|
+
name: (0, exports.postBuildTestJobName)(name),
|
|
20722
|
+
stage: exports.POST_BUILD_TEST_STAGE,
|
|
20723
|
+
envMode: "stagePerEnv",
|
|
20724
|
+
provides: ["qualityGate"],
|
|
20725
|
+
needs: [],
|
|
20726
|
+
requires: [buildRequirement],
|
|
20727
|
+
image: (_a = test.jobImage) !== null && _a !== void 0 ? _a : getDefaultImage(context),
|
|
20728
|
+
services: test.services,
|
|
20729
|
+
// neutral cache declarations, lowered by each backend
|
|
20730
|
+
caches: nodeCache !== null && nodeCache !== void 0 ? nodeCache : undefined,
|
|
20731
|
+
variables: {
|
|
20732
|
+
APP_PATH: context.build.dir,
|
|
20733
|
+
...(context.type === "component"
|
|
20734
|
+
? context.environment.jobOnlyVars.build.envVars
|
|
20735
|
+
: {}),
|
|
20736
|
+
...((_b = test.vars) !== null && _b !== void 0 ? _b : {}),
|
|
20737
|
+
},
|
|
20738
|
+
runnerVariables: {
|
|
20739
|
+
...(nodeBuild ? constants_1.NODE_RUNNER_BUILD_VARIABLES : {}),
|
|
20740
|
+
...((_c = test.runnerVariables) !== null && _c !== void 0 ? _c : {}),
|
|
20741
|
+
},
|
|
20742
|
+
script: [
|
|
20743
|
+
...(nodeBuild ? (0, packageManagerInstall_1.ensureNodeVersion)(context) : []),
|
|
20744
|
+
`cd ${context.build.dir}`,
|
|
20745
|
+
...(packageManagerInstall !== null && packageManagerInstall !== void 0 ? packageManagerInstall : []),
|
|
20746
|
+
...(0, utils_1.ensureArray)(test.command),
|
|
20747
|
+
],
|
|
20748
|
+
allow_failure: test.allowFailure,
|
|
20749
|
+
// reports and screenshots are most needed when the tests fail
|
|
20750
|
+
...(artifactsConfig
|
|
20751
|
+
? {
|
|
20752
|
+
artifacts: {
|
|
20753
|
+
when: "always",
|
|
20754
|
+
...artifactsConfig.artifacts,
|
|
20755
|
+
},
|
|
20756
|
+
}
|
|
20757
|
+
: {}),
|
|
20758
|
+
});
|
|
20759
|
+
});
|
|
20760
|
+
};
|
|
20761
|
+
exports.createPostBuildTestJobs = createPostBuildTestJobs;
|
|
20762
|
+
const jobIdOf = (jobName) => jobName
|
|
20763
|
+
.toLowerCase()
|
|
20764
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
20765
|
+
.replace(/^-+|-+$/g, "");
|
|
20766
|
+
/**
|
|
20767
|
+
* post-build test names are chosen by the user. Github job ids drop the
|
|
20768
|
+
* emoji of the job name, so e.g. a post-build test named `test` would
|
|
20769
|
+
* clash with the `🧪 test` job.
|
|
20770
|
+
*/
|
|
20771
|
+
const assertUniquePostBuildTestNames = (context, postBuildTestJobs, otherJobs) => {
|
|
20772
|
+
const seenIds = new Set(otherJobs.map((job) => jobIdOf(job.name)));
|
|
20773
|
+
// also catches two post-build tests that only differ in case or
|
|
20774
|
+
// punctuation (e.g. `e2e` and `E2E`)
|
|
20775
|
+
const clash = postBuildTestJobs.find((job) => {
|
|
20776
|
+
const id = jobIdOf(job.name);
|
|
20777
|
+
const isClash = seenIds.has(id);
|
|
20778
|
+
seenIds.add(id);
|
|
20779
|
+
return isClash;
|
|
20780
|
+
});
|
|
20781
|
+
if (clash) {
|
|
20782
|
+
throw new Error(`${context.name}: post-build test '${clash.name}' clashes with another job of the same name, please rename it in postBuildTests`);
|
|
20783
|
+
}
|
|
20784
|
+
};
|
|
20785
|
+
exports.assertUniquePostBuildTestNames = assertUniquePostBuildTestNames;
|
|
20786
|
+
//# sourceMappingURL=createPostBuildTestJobs.js.map
|
|
20787
|
+
|
|
20788
|
+
/***/ }),
|
|
20789
|
+
|
|
20790
|
+
/***/ 28458:
|
|
20791
|
+
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
|
20792
|
+
|
|
20793
|
+
"use strict";
|
|
20794
|
+
|
|
20795
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
20796
|
+
if (k2 === undefined) k2 = k;
|
|
20797
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
20798
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
20799
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
20800
|
+
}
|
|
20801
|
+
Object.defineProperty(o, k2, desc);
|
|
20802
|
+
}) : (function(o, m, k, k2) {
|
|
20803
|
+
if (k2 === undefined) k2 = k;
|
|
20804
|
+
o[k2] = m[k];
|
|
20805
|
+
}));
|
|
20806
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
20807
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
20808
|
+
};
|
|
20809
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
20810
|
+
__exportStar(__nccwpck_require__(92967), exports);
|
|
20811
|
+
__exportStar(__nccwpck_require__(61844), exports);
|
|
20812
|
+
//# sourceMappingURL=index.js.map
|
|
20813
|
+
|
|
20814
|
+
/***/ }),
|
|
20815
|
+
|
|
20816
|
+
/***/ 92967:
|
|
20817
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
20818
|
+
|
|
20819
|
+
"use strict";
|
|
20820
|
+
|
|
20821
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
20822
|
+
//# sourceMappingURL=types.js.map
|
|
20823
|
+
|
|
20824
|
+
/***/ }),
|
|
20825
|
+
|
|
19663
20826
|
/***/ 26041:
|
|
19664
20827
|
/***/ ((__unused_webpack_module, exports) => {
|
|
19665
20828
|
|
|
@@ -19685,6 +20848,143 @@ exports.getReleaseMethod = getReleaseMethod;
|
|
|
19685
20848
|
|
|
19686
20849
|
/***/ }),
|
|
19687
20850
|
|
|
20851
|
+
/***/ 67071:
|
|
20852
|
+
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
|
20853
|
+
|
|
20854
|
+
"use strict";
|
|
20855
|
+
|
|
20856
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
20857
|
+
if (k2 === undefined) k2 = k;
|
|
20858
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
20859
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
20860
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
20861
|
+
}
|
|
20862
|
+
Object.defineProperty(o, k2, desc);
|
|
20863
|
+
}) : (function(o, m, k, k2) {
|
|
20864
|
+
if (k2 === undefined) k2 = k;
|
|
20865
|
+
o[k2] = m[k];
|
|
20866
|
+
}));
|
|
20867
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
20868
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
20869
|
+
};
|
|
20870
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
20871
|
+
exports.isReviewDeliveryJob = exports.GITLAB_DRAFT_MR_CONDITION = exports.getGitlabLabelRegex = exports.getReviewAppsSwitchLabel = exports.isReviewDeployGated = exports.getReviewAppsConfig = void 0;
|
|
20872
|
+
const autoStop_1 = __nccwpck_require__(72792);
|
|
20873
|
+
const reviewAppsPolicy_1 = __nccwpck_require__(49821);
|
|
20874
|
+
__exportStar(__nccwpck_require__(49821), exports);
|
|
20875
|
+
const getReviewAppsConfig = (config) => {
|
|
20876
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
20877
|
+
return ({
|
|
20878
|
+
deploy: (_b = (_a = config.reviewApps) === null || _a === void 0 ? void 0 : _a.deploy) !== null && _b !== void 0 ? _b : "auto",
|
|
20879
|
+
// single colon on purpose: gitlab treats `a::b` labels as scoped and
|
|
20880
|
+
// mutually exclusive, which would make this label and the
|
|
20881
|
+
// `catladder::pin-review` pin label displace each other
|
|
20882
|
+
label: (_d = (_c = config.reviewApps) === null || _c === void 0 ? void 0 : _c.label) !== null && _d !== void 0 ? _d : reviewAppsPolicy_1.DEFAULT_REVIEW_APP_LABEL,
|
|
20883
|
+
skipLabel: (_f = (_e = config.reviewApps) === null || _e === void 0 ? void 0 : _e.skipLabel) !== null && _f !== void 0 ? _f : reviewAppsPolicy_1.DEFAULT_NO_REVIEW_APP_LABEL,
|
|
20884
|
+
drafts: (_h = (_g = config.reviewApps) === null || _g === void 0 ? void 0 : _g.drafts) !== null && _h !== void 0 ? _h : "likeReady",
|
|
20885
|
+
});
|
|
20886
|
+
};
|
|
20887
|
+
exports.getReviewAppsConfig = getReviewAppsConfig;
|
|
20888
|
+
/**
|
|
20889
|
+
* whether the review deploys sit behind the per-MR/PR deploy switch
|
|
20890
|
+
* instead of running in every pipeline
|
|
20891
|
+
*/
|
|
20892
|
+
const isReviewDeployGated = (config) => config.deploy !== "auto" || config.drafts === "checksOnly";
|
|
20893
|
+
exports.isReviewDeployGated = isReviewDeployGated;
|
|
20894
|
+
/**
|
|
20895
|
+
* the label whose change switches the review apps on (`labeled` on
|
|
20896
|
+
* opt-in, `unlabeled` on opt-out) or off (the inverse), if the mode is
|
|
20897
|
+
* label-driven at all
|
|
20898
|
+
*/
|
|
20899
|
+
const getReviewAppsSwitchLabel = (config) => {
|
|
20900
|
+
switch (config.deploy) {
|
|
20901
|
+
case "optIn":
|
|
20902
|
+
return { label: config.label, deployOn: "labeled", stopOn: "unlabeled" };
|
|
20903
|
+
case "optOut":
|
|
20904
|
+
return {
|
|
20905
|
+
label: config.skipLabel,
|
|
20906
|
+
deployOn: "unlabeled",
|
|
20907
|
+
stopOn: "labeled",
|
|
20908
|
+
};
|
|
20909
|
+
default:
|
|
20910
|
+
return undefined;
|
|
20911
|
+
}
|
|
20912
|
+
};
|
|
20913
|
+
exports.getReviewAppsSwitchLabel = getReviewAppsSwitchLabel;
|
|
20914
|
+
/**
|
|
20915
|
+
* gitlab: matches a label inside `$CI_MERGE_REQUEST_LABELS`
|
|
20916
|
+
* (comma-separated) — anchored so `x` never matches `no-x`
|
|
20917
|
+
*/
|
|
20918
|
+
const getGitlabLabelRegex = (label) => `/${(0, autoStop_1.getPinLabelRegexSource)(label)}/`;
|
|
20919
|
+
exports.getGitlabLabelRegex = getGitlabLabelRegex;
|
|
20920
|
+
/**
|
|
20921
|
+
* gitlab: a draft MR, by its title prefix — the only draft signal every
|
|
20922
|
+
* gitlab version exposes to rules
|
|
20923
|
+
*/
|
|
20924
|
+
exports.GITLAB_DRAFT_MR_CONDITION = "$CI_MERGE_REQUEST_TITLE =~ /^\\s*(\\[draft\\]|\\(draft\\)|draft:)/i";
|
|
20925
|
+
/**
|
|
20926
|
+
* whether a job belongs to the delivery chain of a review app — the
|
|
20927
|
+
* docker image, the deploy and the post-deploy verification. These jobs
|
|
20928
|
+
* sit behind the review deploy switch; quality jobs (lint, test, audit)
|
|
20929
|
+
* and the app build never do.
|
|
20930
|
+
*/
|
|
20931
|
+
const isReviewDeliveryJob = (context, job) => {
|
|
20932
|
+
var _a;
|
|
20933
|
+
return context.type === "component" &&
|
|
20934
|
+
context.environment.instance.type === "review" &&
|
|
20935
|
+
(job.stage === "deploy" ||
|
|
20936
|
+
job.stage === "verify" ||
|
|
20937
|
+
((_a = job.provides) !== null && _a !== void 0 ? _a : []).includes("dockerImage"));
|
|
20938
|
+
};
|
|
20939
|
+
exports.isReviewDeliveryJob = isReviewDeliveryJob;
|
|
20940
|
+
//# sourceMappingURL=reviewApps.js.map
|
|
20941
|
+
|
|
20942
|
+
/***/ }),
|
|
20943
|
+
|
|
20944
|
+
/***/ 49821:
|
|
20945
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
20946
|
+
|
|
20947
|
+
"use strict";
|
|
20948
|
+
|
|
20949
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
20950
|
+
exports.shouldAutoDeployReviewApps = exports.DEFAULT_NO_REVIEW_APP_LABEL = exports.DEFAULT_REVIEW_APP_LABEL = void 0;
|
|
20951
|
+
exports.DEFAULT_REVIEW_APP_LABEL = "catladder:review-app";
|
|
20952
|
+
exports.DEFAULT_NO_REVIEW_APP_LABEL = "catladder:no-review-app";
|
|
20953
|
+
/**
|
|
20954
|
+
* whether the review apps of an MR/PR in the given state deploy
|
|
20955
|
+
* automatically. An explicit request (playing the gate job, dispatching
|
|
20956
|
+
* the deploy workflow) always deploys and doesn't consult this.
|
|
20957
|
+
*/
|
|
20958
|
+
const shouldAutoDeployReviewApps = (config, { labels, draft }) => {
|
|
20959
|
+
if (draft && config.drafts !== "likeReady") {
|
|
20960
|
+
return { deploy: false, reason: "drafts don't deploy review apps" };
|
|
20961
|
+
}
|
|
20962
|
+
switch (config.deploy) {
|
|
20963
|
+
case "auto":
|
|
20964
|
+
return { deploy: true, reason: "review apps deploy automatically" };
|
|
20965
|
+
case "manual":
|
|
20966
|
+
return {
|
|
20967
|
+
deploy: false,
|
|
20968
|
+
reason: "review apps only deploy on request (deploy: manual)",
|
|
20969
|
+
};
|
|
20970
|
+
case "optIn":
|
|
20971
|
+
return labels.includes(config.label)
|
|
20972
|
+
? { deploy: true, reason: `the '${config.label}' label is set` }
|
|
20973
|
+
: { deploy: false, reason: `the '${config.label}' label is not set` };
|
|
20974
|
+
case "optOut":
|
|
20975
|
+
return labels.includes(config.skipLabel)
|
|
20976
|
+
? { deploy: false, reason: `the '${config.skipLabel}' label is set` }
|
|
20977
|
+
: {
|
|
20978
|
+
deploy: true,
|
|
20979
|
+
reason: `the '${config.skipLabel}' label is not set`,
|
|
20980
|
+
};
|
|
20981
|
+
}
|
|
20982
|
+
};
|
|
20983
|
+
exports.shouldAutoDeployReviewApps = shouldAutoDeployReviewApps;
|
|
20984
|
+
//# sourceMappingURL=reviewAppsPolicy.js.map
|
|
20985
|
+
|
|
20986
|
+
/***/ }),
|
|
20987
|
+
|
|
19688
20988
|
/***/ 78503:
|
|
19689
20989
|
/***/ ((__unused_webpack_module, exports) => {
|
|
19690
20990
|
|
|
@@ -19993,6 +21293,7 @@ exports.BASE_STAGES = [
|
|
|
19993
21293
|
"setup",
|
|
19994
21294
|
"test",
|
|
19995
21295
|
"build",
|
|
21296
|
+
"post-build",
|
|
19996
21297
|
"deploy",
|
|
19997
21298
|
"verify",
|
|
19998
21299
|
"agents",
|