@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
|
@@ -2655,6 +2655,8 @@ const aggregateCheckJob_1 = __nccwpck_require__(82145);
|
|
|
2655
2655
|
const shippedCatci_1 = __nccwpck_require__(27967);
|
|
2656
2656
|
const githubReleaseJobs_1 = __nccwpck_require__(25073);
|
|
2657
2657
|
const scriptFiles_1 = __nccwpck_require__(61071);
|
|
2658
|
+
const reviewApps_1 = __nccwpck_require__(67071);
|
|
2659
|
+
const reviewDeployWorkflow_1 = __nccwpck_require__(73671);
|
|
2658
2660
|
const WORKFLOWS_FOLDER = ".github/workflows";
|
|
2659
2661
|
/**
|
|
2660
2662
|
* generated workflow files are prefixed so that cleanup never touches
|
|
@@ -2750,6 +2752,7 @@ class GithubBackend {
|
|
|
2750
2752
|
...(0, ciVariables_1.getGithubInjectedWorkflowEnv)(images.registryImageRef()),
|
|
2751
2753
|
...(0, index_1.getPipelineOptions)(config, this.type).runnerVariables,
|
|
2752
2754
|
};
|
|
2755
|
+
const reviewApps = (0, reviewApps_1.getReviewAppsConfig)(config);
|
|
2753
2756
|
const reviewStopJobs = {};
|
|
2754
2757
|
// jobs routed to the per-kind workflow_dispatch workflows (deploy /
|
|
2755
2758
|
// stop / rollback). A manual job and its build closure live there
|
|
@@ -2792,6 +2795,39 @@ class GithubBackend {
|
|
|
2792
2795
|
triggerJobs.delete(id);
|
|
2793
2796
|
}
|
|
2794
2797
|
}
|
|
2798
|
+
// review delivery chains behind the per-PR deploy switch move to
|
|
2799
|
+
// their own workflow (reviewApps config, or manual review deploys)
|
|
2800
|
+
// When the label/draft policy already decides at the start of the
|
|
2801
|
+
// review run, the chains stay in it behind that condition (one
|
|
2802
|
+
// run, one app build); the review deploy workflow handles whatever
|
|
2803
|
+
// changes later. Manual review deploys only live there.
|
|
2804
|
+
let inRunDeliveryIds = [];
|
|
2805
|
+
const inRunCondition = (0, reviewApps_1.isReviewDeployGated)(reviewApps)
|
|
2806
|
+
? (0, reviewDeployWorkflow_1.getInRunDeployCondition)(reviewApps)
|
|
2807
|
+
: undefined;
|
|
2808
|
+
if (trigger === "mr") {
|
|
2809
|
+
const deliveryIds = (0, reviewDeployWorkflow_1.getGatedReviewDeliveryJobIds)(triggerJobs, reviewApps);
|
|
2810
|
+
if (deliveryIds.length > 0) {
|
|
2811
|
+
workflows[reviewDeployWorkflow_1.REVIEW_DEPLOY_WORKFLOW_FILE] = (0, reviewDeployWorkflow_1.makeReviewDeployWorkflow)({
|
|
2812
|
+
deliveryIds,
|
|
2813
|
+
triggerJobs,
|
|
2814
|
+
reviewApps,
|
|
2815
|
+
images,
|
|
2816
|
+
workflowEnv,
|
|
2817
|
+
permissions: workflowPermissions(images).permissions,
|
|
2818
|
+
});
|
|
2819
|
+
if (inRunCondition) {
|
|
2820
|
+
inRunDeliveryIds = deliveryIds;
|
|
2821
|
+
for (const id of deliveryIds) {
|
|
2822
|
+
const entry = triggerJobs.get(id);
|
|
2823
|
+
entry.githubJob = (0, reviewDeployWorkflow_1.withInRunCondition)(entry.githubJob, inRunCondition);
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
else {
|
|
2827
|
+
deliveryIds.forEach((id) => triggerJobs.delete(id));
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2795
2831
|
// leaf manual jobs: an explicit manual gate (e.g. a prod deploy) or
|
|
2796
2832
|
// a stop/rollback task with no automatic trigger. Each becomes a
|
|
2797
2833
|
// dispatch choice.
|
|
@@ -2895,7 +2931,7 @@ class GithubBackend {
|
|
|
2895
2931
|
}
|
|
2896
2932
|
if (Object.keys(jobs).length > 0) {
|
|
2897
2933
|
const workflowJobs = { ...(0, ensureImageJobs_1.makeEnsureImageGithubJobs)(images), ...jobs };
|
|
2898
|
-
|
|
2934
|
+
const workflow = {
|
|
2899
2935
|
...getTriggerWorkflow(trigger),
|
|
2900
2936
|
...workflowPermissions(images),
|
|
2901
2937
|
env: workflowEnv,
|
|
@@ -2907,13 +2943,30 @@ class GithubBackend {
|
|
|
2907
2943
|
// required. Added at finalization so its needs cover the
|
|
2908
2944
|
// image-build jobs too.
|
|
2909
2945
|
jobs: trigger === "mr"
|
|
2910
|
-
? {
|
|
2946
|
+
? {
|
|
2947
|
+
...workflowJobs,
|
|
2948
|
+
...(0, aggregateCheckJob_1.makeAggregateCheckJob)(workflowJobs, inRunDeliveryIds),
|
|
2949
|
+
// the stable review app result of an in-run deploy
|
|
2950
|
+
...(inRunCondition && inRunDeliveryIds.length > 0
|
|
2951
|
+
? (0, reviewDeployWorkflow_1.makeReviewAppAggregateJob)(inRunDeliveryIds, inRunCondition)
|
|
2952
|
+
: {}),
|
|
2953
|
+
// once green, hand over to the review deploy workflow
|
|
2954
|
+
// when the PR deploys but this run didn't
|
|
2955
|
+
...(workflows[reviewDeployWorkflow_1.REVIEW_DEPLOY_WORKFLOW_FILE] &&
|
|
2956
|
+
(0, reviewApps_1.isReviewDeployGated)(reviewApps)
|
|
2957
|
+
? (0, reviewDeployWorkflow_1.makeReviewDeployTriggerJob)(reviewApps, inRunCondition)
|
|
2958
|
+
: {}),
|
|
2959
|
+
}
|
|
2911
2960
|
: workflowJobs,
|
|
2912
2961
|
};
|
|
2962
|
+
workflows[`${GENERATED_FILE_PREFIX}${workflowFileName(trigger)}`] =
|
|
2963
|
+
trigger === "mr" && reviewApps.drafts === "skip"
|
|
2964
|
+
? (0, reviewDeployWorkflow_1.withDraftsSkipped)(workflow)
|
|
2965
|
+
: workflow;
|
|
2913
2966
|
}
|
|
2914
2967
|
}
|
|
2915
2968
|
if (Object.keys(reviewStopJobs).length > 0) {
|
|
2916
|
-
|
|
2969
|
+
const reviewStopWorkflow = {
|
|
2917
2970
|
name: "🛑 catladder stop review app",
|
|
2918
2971
|
on: {
|
|
2919
2972
|
pull_request: { types: ["closed"] },
|
|
@@ -2940,6 +2993,10 @@ class GithubBackend {
|
|
|
2940
2993
|
},
|
|
2941
2994
|
jobs: { ...(0, ensureImageJobs_1.makeEnsureImageGithubJobs)(images), ...reviewStopJobs },
|
|
2942
2995
|
};
|
|
2996
|
+
workflows[`${GENERATED_FILE_PREFIX}review-stop.yml`] = workflows[reviewDeployWorkflow_1.REVIEW_DEPLOY_WORKFLOW_FILE]
|
|
2997
|
+
? (0, reviewDeployWorkflow_1.withReviewStopSwitch)(reviewStopWorkflow, reviewApps, (0, reviewApps_1.isReviewDeployGated)(reviewApps) &&
|
|
2998
|
+
(0, reviewDeployWorkflow_1.getInRunDeployCondition)(reviewApps) !== undefined)
|
|
2999
|
+
: reviewStopWorkflow;
|
|
2943
3000
|
}
|
|
2944
3001
|
for (const [kind, bucket] of Object.entries(manualKindBuckets)) {
|
|
2945
3002
|
if (Object.keys(bucket.jobs).length === 0) {
|
|
@@ -3074,7 +3131,12 @@ exports.AGGREGATE_CHECK_JOB_NAME = "catladder ✅";
|
|
|
3074
3131
|
* `success` to `needs` even when red, so they stay non-blocking here
|
|
3075
3132
|
* by the same mechanism that keeps their workflow green.
|
|
3076
3133
|
*/
|
|
3077
|
-
const makeAggregateCheckJob = (reviewJobs
|
|
3134
|
+
const makeAggregateCheckJob = (reviewJobs,
|
|
3135
|
+
/**
|
|
3136
|
+
* jobs that may be skipped on purpose (review delivery chains behind
|
|
3137
|
+
* the reviewApps switch) — their skip is no failure, their failure is
|
|
3138
|
+
*/
|
|
3139
|
+
skippable = []) => ({
|
|
3078
3140
|
[exports.AGGREGATE_CHECK_JOB_ID]: {
|
|
3079
3141
|
name: exports.AGGREGATE_CHECK_JOB_NAME,
|
|
3080
3142
|
"runs-on": "ubuntu-latest",
|
|
@@ -3085,13 +3147,22 @@ const makeAggregateCheckJob = (reviewJobs) => ({
|
|
|
3085
3147
|
name: exports.AGGREGATE_CHECK_JOB_NAME,
|
|
3086
3148
|
// the needs context only interpolates inside the workflow file,
|
|
3087
3149
|
// so this script stays inline
|
|
3088
|
-
run:
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3150
|
+
run: (skippable.length === 0
|
|
3151
|
+
? [
|
|
3152
|
+
`results='\${{ toJSON(needs) }}'`,
|
|
3153
|
+
`echo "$results"`,
|
|
3154
|
+
`if echo "$results" | grep -qE '"result": "(failure|cancelled|skipped)"'; then`,
|
|
3155
|
+
` echo "a required job did not succeed"; exit 1`,
|
|
3156
|
+
`fi`,
|
|
3157
|
+
]
|
|
3158
|
+
: [
|
|
3159
|
+
`results='\${{ toJSON(needs) }}'`,
|
|
3160
|
+
`echo "$results"`,
|
|
3161
|
+
`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')`,
|
|
3162
|
+
`if [ -n "$failed" ]; then`,
|
|
3163
|
+
` echo "required jobs did not succeed: $failed"; exit 1`,
|
|
3164
|
+
`fi`,
|
|
3165
|
+
]).join("\n"),
|
|
3095
3166
|
shell: "bash",
|
|
3096
3167
|
},
|
|
3097
3168
|
],
|
|
@@ -3102,6 +3173,97 @@ exports.makeAggregateCheckJob = makeAggregateCheckJob;
|
|
|
3102
3173
|
|
|
3103
3174
|
/***/ }),
|
|
3104
3175
|
|
|
3176
|
+
/***/ 75125:
|
|
3177
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
3178
|
+
|
|
3179
|
+
"use strict";
|
|
3180
|
+
|
|
3181
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
3182
|
+
exports.createArtifactDownloadSteps = exports.createArtifactUploadSteps = exports.artifactArchiveFile = void 0;
|
|
3183
|
+
/**
|
|
3184
|
+
* Artifacts are handed between github jobs as ONE tar archive instead
|
|
3185
|
+
* of letting upload-artifact walk the paths itself.
|
|
3186
|
+
*
|
|
3187
|
+
* upload-artifact does not preserve symlinks (it uploads the target's
|
|
3188
|
+
* content) nor file modes, and it strips the paths' common ancestor.
|
|
3189
|
+
* Gitlab artifacts keep all of that, and builds rely on it: next.js
|
|
3190
|
+
* with turbopack writes `.next/node_modules/<pkg>-<hash>` as relative
|
|
3191
|
+
* symlinks into the pnpm virtual store. Copied as plain directories,
|
|
3192
|
+
* node resolves the package's own dependencies from the wrong place
|
|
3193
|
+
* and the app crashes with ERR_MODULE_NOT_FOUND at runtime. tar keeps
|
|
3194
|
+
* symlinks, modes and the paths relative to the repo root (github's
|
|
3195
|
+
* own docs recommend it for exactly this).
|
|
3196
|
+
*
|
|
3197
|
+
* The archive is not hidden (upload-artifact skips hidden files) and
|
|
3198
|
+
* lives in the workspace, the one path run steps and actions agree on
|
|
3199
|
+
* inside job containers too.
|
|
3200
|
+
*/
|
|
3201
|
+
const artifactArchiveFile = (jobId) => `catladder-artifacts-${jobId}.tar`;
|
|
3202
|
+
exports.artifactArchiveFile = artifactArchiveFile;
|
|
3203
|
+
const createArtifactUploadSteps = (jobId, paths, condition) => {
|
|
3204
|
+
const archive = (0, exports.artifactArchiveFile)(jobId);
|
|
3205
|
+
const ifCondition = condition ? { if: condition } : {};
|
|
3206
|
+
return [
|
|
3207
|
+
{
|
|
3208
|
+
name: "Archive artifacts",
|
|
3209
|
+
...ifCondition,
|
|
3210
|
+
run: [
|
|
3211
|
+
"shopt -s nullglob globstar",
|
|
3212
|
+
"paths=()",
|
|
3213
|
+
// unquoted on purpose: artifact paths may be globs. Non-matching
|
|
3214
|
+
// globs vanish (nullglob), missing literal paths are skipped —
|
|
3215
|
+
// gitlab only warns about both, so do we
|
|
3216
|
+
`for path in ${paths.join(" ")}; do`,
|
|
3217
|
+
' if [ -e "$path" ] || [ -L "$path" ]; then paths+=("$path"); else echo "::warning::artifact path not found: $path"; fi',
|
|
3218
|
+
"done",
|
|
3219
|
+
"if [ ${#paths[@]} -eq 0 ]; then",
|
|
3220
|
+
// an empty archive keeps downstream downloads from failing.
|
|
3221
|
+
// Written by hand: busybox tar (alpine job images) refuses to
|
|
3222
|
+
// create one, and zero blocks are what an empty tar is
|
|
3223
|
+
` head -c 10240 /dev/zero > ${archive}`,
|
|
3224
|
+
"else",
|
|
3225
|
+
` tar -cf ${archive} -- "\${paths[@]}"`,
|
|
3226
|
+
"fi",
|
|
3227
|
+
].join("\n"),
|
|
3228
|
+
shell: "bash",
|
|
3229
|
+
},
|
|
3230
|
+
{
|
|
3231
|
+
name: "Upload artifacts",
|
|
3232
|
+
...ifCondition,
|
|
3233
|
+
uses: "actions/upload-artifact@v4",
|
|
3234
|
+
with: {
|
|
3235
|
+
name: jobId,
|
|
3236
|
+
path: archive,
|
|
3237
|
+
"if-no-files-found": "error",
|
|
3238
|
+
},
|
|
3239
|
+
},
|
|
3240
|
+
];
|
|
3241
|
+
};
|
|
3242
|
+
exports.createArtifactUploadSteps = createArtifactUploadSteps;
|
|
3243
|
+
const createArtifactDownloadSteps = (providerIds) => providerIds.length === 0
|
|
3244
|
+
? []
|
|
3245
|
+
: [
|
|
3246
|
+
...providerIds.map((id) => ({
|
|
3247
|
+
name: `Download artifacts from ${id}`,
|
|
3248
|
+
uses: "actions/download-artifact@v4",
|
|
3249
|
+
with: { name: id },
|
|
3250
|
+
})),
|
|
3251
|
+
{
|
|
3252
|
+
name: "Extract artifacts",
|
|
3253
|
+
run: providerIds
|
|
3254
|
+
.flatMap((id) => [
|
|
3255
|
+
`tar -xf ${(0, exports.artifactArchiveFile)(id)}`,
|
|
3256
|
+
`rm ${(0, exports.artifactArchiveFile)(id)}`,
|
|
3257
|
+
])
|
|
3258
|
+
.join("\n"),
|
|
3259
|
+
shell: "bash",
|
|
3260
|
+
},
|
|
3261
|
+
];
|
|
3262
|
+
exports.createArtifactDownloadSteps = createArtifactDownloadSteps;
|
|
3263
|
+
//# sourceMappingURL=artifactArchive.js.map
|
|
3264
|
+
|
|
3265
|
+
/***/ }),
|
|
3266
|
+
|
|
3105
3267
|
/***/ 17300:
|
|
3106
3268
|
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
3107
3269
|
|
|
@@ -3171,6 +3333,7 @@ const utils_1 = __nccwpck_require__(49401);
|
|
|
3171
3333
|
const gitlab_1 = __nccwpck_require__(86962);
|
|
3172
3334
|
const scriptFunctions_1 = __nccwpck_require__(85873);
|
|
3173
3335
|
const ciVariables_1 = __nccwpck_require__(17300);
|
|
3336
|
+
const artifactArchive_1 = __nccwpck_require__(75125);
|
|
3174
3337
|
/**
|
|
3175
3338
|
* runner variables that only make sense on gitlab runners and must not
|
|
3176
3339
|
* leak into github jobs (docker-in-docker daemon config, kubernetes
|
|
@@ -3381,7 +3544,7 @@ const getReferencedSecretsEnv = (script, kinds) => Object.fromEntries([...script
|
|
|
3381
3544
|
.filter((name) => !ciVariables_1.GITHUB_INJECTED_WORKFLOW_ENV_NAMES.has(name))
|
|
3382
3545
|
.map((name) => [name, vaultValueExpression(name, kinds)]));
|
|
3383
3546
|
const makeGithubJob = (context, job, allJobs, uploadProviderIds, images, scripts, secretKinds = new Map()) => {
|
|
3384
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
3547
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
3385
3548
|
const id = (0, exports.githubJobId)(context, job.name);
|
|
3386
3549
|
const isComponent = context.type === "component";
|
|
3387
3550
|
const createsJobEnv = !!job.environment &&
|
|
@@ -3526,13 +3689,7 @@ const makeGithubJob = (context, job, allJobs, uploadProviderIds, images, scripts
|
|
|
3526
3689
|
? []
|
|
3527
3690
|
: [{ name: "Checkout", uses: "actions/checkout@v4" }]),
|
|
3528
3691
|
...createCacheSteps(context, job),
|
|
3529
|
-
...needs
|
|
3530
|
-
.filter((n) => n.artifacts)
|
|
3531
|
-
.map((n) => ({
|
|
3532
|
-
name: `Download artifacts from ${n.id}`,
|
|
3533
|
-
uses: "actions/download-artifact@v4",
|
|
3534
|
-
with: { name: n.id },
|
|
3535
|
-
})),
|
|
3692
|
+
...(0, artifactArchive_1.createArtifactDownloadSteps)(needs.filter((n) => n.artifacts).map((n) => n.id)),
|
|
3536
3693
|
{
|
|
3537
3694
|
name: job.name,
|
|
3538
3695
|
id: "main",
|
|
@@ -3553,27 +3710,15 @@ const makeGithubJob = (context, job, allJobs, uploadProviderIds, images, scripts
|
|
|
3553
3710
|
},
|
|
3554
3711
|
]
|
|
3555
3712
|
: []),
|
|
3556
|
-
...(pagesPublishDir
|
|
3557
|
-
?
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
name: id,
|
|
3566
|
-
path: artifactPaths.join("\n"),
|
|
3567
|
-
"if-no-files-found": "warn",
|
|
3568
|
-
// gitlab artifacts include everything; upload-artifact
|
|
3569
|
-
// silently drops hidden files by default — which is
|
|
3570
|
-
// exactly where next.js builds live (.next)
|
|
3571
|
-
"include-hidden-files": true,
|
|
3572
|
-
},
|
|
3573
|
-
},
|
|
3574
|
-
]
|
|
3575
|
-
: []
|
|
3576
|
-
: []),
|
|
3713
|
+
...(!pagesPublishDir && artifactPaths.length > 0
|
|
3714
|
+
? (0, artifactArchive_1.createArtifactUploadSteps)(id, artifactPaths,
|
|
3715
|
+
// gitlab's `artifacts.when`: steps only run on success by default
|
|
3716
|
+
((_k = job.artifacts) === null || _k === void 0 ? void 0 : _k.when) === "always"
|
|
3717
|
+
? "always()"
|
|
3718
|
+
: ((_l = job.artifacts) === null || _l === void 0 ? void 0 : _l.when) === "on_failure"
|
|
3719
|
+
? "failure()"
|
|
3720
|
+
: undefined)
|
|
3721
|
+
: []),
|
|
3577
3722
|
],
|
|
3578
3723
|
};
|
|
3579
3724
|
return [id, githubJob];
|
|
@@ -4062,6 +4207,523 @@ exports.resolveGithubRegistryImage = resolveGithubRegistryImage;
|
|
|
4062
4207
|
|
|
4063
4208
|
/***/ }),
|
|
4064
4209
|
|
|
4210
|
+
/***/ 73671:
|
|
4211
|
+
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
4212
|
+
|
|
4213
|
+
"use strict";
|
|
4214
|
+
|
|
4215
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
4216
|
+
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;
|
|
4217
|
+
const shippedCatci_1 = __nccwpck_require__(27967);
|
|
4218
|
+
const resolveRequirements_1 = __nccwpck_require__(81710);
|
|
4219
|
+
const reviewApps_1 = __nccwpck_require__(67071);
|
|
4220
|
+
const aggregateCheckJob_1 = __nccwpck_require__(82145);
|
|
4221
|
+
const ensureImageJobs_1 = __nccwpck_require__(77590);
|
|
4222
|
+
const CATCI = `${shippedCatci_1.GENERATED_CATCI_FOLDER}/index.js`;
|
|
4223
|
+
/**
|
|
4224
|
+
* the generated review deploy workflow. Keep in sync with
|
|
4225
|
+
* GITHUB_REVIEW_DEPLOY_WORKFLOW in apps/cli/src/reviewApps/github.ts.
|
|
4226
|
+
*/
|
|
4227
|
+
exports.REVIEW_DEPLOY_WORKFLOW_FILE = "catladder-deploy-review.yml";
|
|
4228
|
+
const GUARD_JOB_ID = "catladder-review-deploy-guard";
|
|
4229
|
+
const TRIGGER_JOB_ID = "catladder-review-deploy-trigger";
|
|
4230
|
+
/** the PR of the run — from the event, or the dispatch input */
|
|
4231
|
+
const PR_NUMBER_EXPRESSION = "github.event.number || inputs.pr";
|
|
4232
|
+
/** quote a value for a github expression string literal */
|
|
4233
|
+
const literal = (value) => `'${value.replaceAll("'", "''")}'`;
|
|
4234
|
+
/** combine a job's existing `if` with another condition */
|
|
4235
|
+
const andIf = (existing, condition) => {
|
|
4236
|
+
if (!existing) {
|
|
4237
|
+
return `\${{ ${condition} }}`;
|
|
4238
|
+
}
|
|
4239
|
+
const unwrapped = existing.replace(/^\$\{\{\s*([\s\S]*?)\s*\}\}$/, "$1");
|
|
4240
|
+
return `\${{ (${unwrapped}) && (${condition}) }}`;
|
|
4241
|
+
};
|
|
4242
|
+
/**
|
|
4243
|
+
* the condition under which the review workflow deploys the review apps
|
|
4244
|
+
* ITSELF, decided from the pull request event at the start of the run
|
|
4245
|
+
* (labels, draft) — the common case then needs one run and one app
|
|
4246
|
+
* build, like gitlab's switch job. Whatever changes later (a label set
|
|
4247
|
+
* during or after the run, a draft marked ready, an explicit request) is
|
|
4248
|
+
* handled by the review deploy workflow. `deploy: "manual"` never
|
|
4249
|
+
* deploys on its own: undefined.
|
|
4250
|
+
*/
|
|
4251
|
+
const getInRunDeployCondition = (reviewApps) => {
|
|
4252
|
+
const labels = "github.event.pull_request.labels.*.name";
|
|
4253
|
+
const policy = reviewApps.deploy === "optIn"
|
|
4254
|
+
? `contains(${labels}, ${literal(reviewApps.label)})`
|
|
4255
|
+
: reviewApps.deploy === "optOut"
|
|
4256
|
+
? `!contains(${labels}, ${literal(reviewApps.skipLabel)})`
|
|
4257
|
+
: reviewApps.deploy === "auto"
|
|
4258
|
+
? undefined
|
|
4259
|
+
: null;
|
|
4260
|
+
if (policy === null) {
|
|
4261
|
+
return undefined;
|
|
4262
|
+
}
|
|
4263
|
+
const notDraft = reviewApps.drafts !== "likeReady"
|
|
4264
|
+
? "!github.event.pull_request.draft"
|
|
4265
|
+
: undefined;
|
|
4266
|
+
return [policy, notDraft].filter(Boolean).join(" && ") || "true";
|
|
4267
|
+
};
|
|
4268
|
+
exports.getInRunDeployCondition = getInRunDeployCondition;
|
|
4269
|
+
/** combine a job's `if` with the in-run deploy condition */
|
|
4270
|
+
const withInRunCondition = (job, condition) => ({
|
|
4271
|
+
...job,
|
|
4272
|
+
if: andIf(job.if, condition),
|
|
4273
|
+
});
|
|
4274
|
+
exports.withInRunCondition = withInRunCondition;
|
|
4275
|
+
/**
|
|
4276
|
+
* the review delivery jobs (docker → deploy → verify) that move out of
|
|
4277
|
+
* the review workflow into the review deploy workflow:
|
|
4278
|
+
* - all of them when the reviewApps config gates review deploys
|
|
4279
|
+
* - otherwise those of components whose review deploy is manual
|
|
4280
|
+
* (`deploy.when: "manual"`) — github has no manual jobs inside a run,
|
|
4281
|
+
* and the generic manual-task dispatch can't carry the pull request
|
|
4282
|
+
*/
|
|
4283
|
+
const getGatedReviewDeliveryJobIds = (triggerJobs, reviewApps) => {
|
|
4284
|
+
var _a;
|
|
4285
|
+
const gatedByConfig = (0, reviewApps_1.isReviewDeployGated)(reviewApps);
|
|
4286
|
+
const manualComponents = new Set([...triggerJobs.values()]
|
|
4287
|
+
.filter(({ job, context }) => (0, reviewApps_1.isReviewDeliveryJob)(context, job) &&
|
|
4288
|
+
job.stage === "deploy" &&
|
|
4289
|
+
job.gate === "manual")
|
|
4290
|
+
.map(({ context }) => context.name));
|
|
4291
|
+
const ids = new Set([...triggerJobs]
|
|
4292
|
+
.filter(([, { job, context }]) => (0, reviewApps_1.isReviewDeliveryJob)(context, job) &&
|
|
4293
|
+
(gatedByConfig || manualComponents.has(context.name)))
|
|
4294
|
+
.map(([id]) => id));
|
|
4295
|
+
// jobs depending on a moved job (e.g. a custom e2e job waiting for a
|
|
4296
|
+
// review deploy) can't stay behind: they move along
|
|
4297
|
+
let grown = ids.size > 0;
|
|
4298
|
+
while (grown) {
|
|
4299
|
+
grown = false;
|
|
4300
|
+
for (const [id, { githubJob }] of triggerJobs) {
|
|
4301
|
+
if (!ids.has(id) && ((_a = githubJob.needs) === null || _a === void 0 ? void 0 : _a.some((need) => ids.has(need)))) {
|
|
4302
|
+
ids.add(id);
|
|
4303
|
+
grown = true;
|
|
4304
|
+
}
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
4307
|
+
return [...ids];
|
|
4308
|
+
};
|
|
4309
|
+
exports.getGatedReviewDeliveryJobIds = getGatedReviewDeliveryJobIds;
|
|
4310
|
+
/**
|
|
4311
|
+
* the review deploy workflow (`▶️ catladder deploy review`): the review
|
|
4312
|
+
* delivery chains of a pull request, behind a guard that decides
|
|
4313
|
+
* whether this run deploys (see `catci review-app github-guard`).
|
|
4314
|
+
*
|
|
4315
|
+
* Started by
|
|
4316
|
+
* - the review workflow, once it is green and the label/draft policy
|
|
4317
|
+
* says the PR deploys (`source: ci`)
|
|
4318
|
+
* - switching the label on (opt-in: adding it, opt-out: removing the
|
|
4319
|
+
* skip label) or marking a draft ready (`drafts: "checksOnly"`)
|
|
4320
|
+
* - a human or agent dispatching it with the PR number — explicit
|
|
4321
|
+
* requests always deploy, provided the PR's CI is green
|
|
4322
|
+
*
|
|
4323
|
+
* Artifacts are scoped to a single run, so the run rebuilds what the
|
|
4324
|
+
* chains need (the app build); tests are not rerun — the guard checks
|
|
4325
|
+
* the PR's `catladder ✅` instead.
|
|
4326
|
+
*/
|
|
4327
|
+
const makeReviewDeployWorkflow = ({ deliveryIds, triggerJobs, reviewApps, images, workflowEnv, permissions, }) => {
|
|
4328
|
+
var _a, _b;
|
|
4329
|
+
const delivery = new Set(deliveryIds);
|
|
4330
|
+
const isQualityJob = (id) => {
|
|
4331
|
+
const entry = triggerJobs.get(id);
|
|
4332
|
+
return !!entry && (0, resolveRequirements_1.providesCapability)(entry.job, "qualityGate");
|
|
4333
|
+
};
|
|
4334
|
+
// the delivery jobs plus everything upstream they need from the
|
|
4335
|
+
// trigger (rebuilt in this run), except the quality jobs
|
|
4336
|
+
const included = new Set();
|
|
4337
|
+
const stack = [...deliveryIds];
|
|
4338
|
+
let current;
|
|
4339
|
+
while ((current = stack.pop()) !== undefined) {
|
|
4340
|
+
if (included.has(current))
|
|
4341
|
+
continue;
|
|
4342
|
+
included.add(current);
|
|
4343
|
+
for (const need of (_b = (_a = triggerJobs.get(current)) === null || _a === void 0 ? void 0 : _a.githubJob.needs) !== null && _b !== void 0 ? _b : []) {
|
|
4344
|
+
if (triggerJobs.has(need) && !isQualityJob(need))
|
|
4345
|
+
stack.push(need);
|
|
4346
|
+
}
|
|
4347
|
+
}
|
|
4348
|
+
// the guard runs first, every other job only when it says deploy
|
|
4349
|
+
const gatedJob = (job) => {
|
|
4350
|
+
var _a;
|
|
4351
|
+
return ({
|
|
4352
|
+
...job,
|
|
4353
|
+
needs: [GUARD_JOB_ID, ...((_a = job.needs) !== null && _a !== void 0 ? _a : [])],
|
|
4354
|
+
if: andIf(job.if, `needs.${GUARD_JOB_ID}.outputs.deploy == 'true'`),
|
|
4355
|
+
});
|
|
4356
|
+
};
|
|
4357
|
+
const checkoutPrHead = (step) => {
|
|
4358
|
+
var _a;
|
|
4359
|
+
return ((_a = step.uses) === null || _a === void 0 ? void 0 : _a.startsWith("actions/checkout@"))
|
|
4360
|
+
? {
|
|
4361
|
+
...step,
|
|
4362
|
+
// the PR head — a label event's default is the merge ref
|
|
4363
|
+
with: {
|
|
4364
|
+
...step.with,
|
|
4365
|
+
ref: "${{ github.event.pull_request.head.sha || github.sha }}",
|
|
4366
|
+
},
|
|
4367
|
+
}
|
|
4368
|
+
: step;
|
|
4369
|
+
};
|
|
4370
|
+
const chainJobs = Object.fromEntries([...included].sort().map((id) => {
|
|
4371
|
+
const { githubJob } = triggerJobs.get(id);
|
|
4372
|
+
return [
|
|
4373
|
+
id,
|
|
4374
|
+
gatedJob({
|
|
4375
|
+
...githubJob,
|
|
4376
|
+
// the quality jobs stay in the review workflow — the guard
|
|
4377
|
+
// checks their aggregate instead
|
|
4378
|
+
...(githubJob.needs
|
|
4379
|
+
? {
|
|
4380
|
+
needs: githubJob.needs.filter((need) => !isQualityJob(need) || delivery.has(need)),
|
|
4381
|
+
}
|
|
4382
|
+
: {}),
|
|
4383
|
+
steps: githubJob.steps.map(checkoutPrHead),
|
|
4384
|
+
}),
|
|
4385
|
+
];
|
|
4386
|
+
}));
|
|
4387
|
+
// only the images the chains run in (not e.g. the release image)
|
|
4388
|
+
const neededIds = new Set(Object.values(chainJobs).flatMap((job) => { var _a; return (_a = job.needs) !== null && _a !== void 0 ? _a : []; }));
|
|
4389
|
+
const imageJobs = Object.fromEntries(Object.entries((0, ensureImageJobs_1.makeEnsureImageGithubJobs)(images))
|
|
4390
|
+
.filter(([id]) => neededIds.has(id))
|
|
4391
|
+
.map(([id, job]) => [id, gatedJob(job)]));
|
|
4392
|
+
const switchLabel = (0, reviewApps_1.getReviewAppsSwitchLabel)(reviewApps);
|
|
4393
|
+
const pullRequestTypes = [
|
|
4394
|
+
...(switchLabel ? [switchLabel.deployOn] : []),
|
|
4395
|
+
...(reviewApps.drafts === "checksOnly" ? ["ready_for_review"] : []),
|
|
4396
|
+
];
|
|
4397
|
+
// pull request events that concern the review apps — other label
|
|
4398
|
+
// changes must neither run the guard nor cancel a running deploy
|
|
4399
|
+
const relevantEvent = [
|
|
4400
|
+
"github.event_name == 'workflow_dispatch'",
|
|
4401
|
+
...(switchLabel
|
|
4402
|
+
? [
|
|
4403
|
+
`(github.event.action == ${literal(switchLabel.deployOn)} && github.event.label.name == ${literal(switchLabel.label)})`,
|
|
4404
|
+
]
|
|
4405
|
+
: []),
|
|
4406
|
+
...(reviewApps.drafts === "checksOnly"
|
|
4407
|
+
? ["github.event.action == 'ready_for_review'"]
|
|
4408
|
+
: []),
|
|
4409
|
+
].join(" || ");
|
|
4410
|
+
return {
|
|
4411
|
+
name: "▶️ catladder deploy review",
|
|
4412
|
+
on: {
|
|
4413
|
+
workflow_dispatch: {
|
|
4414
|
+
inputs: {
|
|
4415
|
+
pr: {
|
|
4416
|
+
description: "number of the pull request whose review apps to deploy",
|
|
4417
|
+
required: true,
|
|
4418
|
+
type: "string",
|
|
4419
|
+
},
|
|
4420
|
+
source: {
|
|
4421
|
+
description: "who requests the deploy: manual (always deploys) or ci (applies the label/draft policy)",
|
|
4422
|
+
required: false,
|
|
4423
|
+
type: "choice",
|
|
4424
|
+
options: ["manual", "ci"],
|
|
4425
|
+
default: "manual",
|
|
4426
|
+
},
|
|
4427
|
+
},
|
|
4428
|
+
},
|
|
4429
|
+
...(pullRequestTypes.length > 0
|
|
4430
|
+
? { pull_request: { types: pullRequestTypes } }
|
|
4431
|
+
: {}),
|
|
4432
|
+
},
|
|
4433
|
+
concurrency: reviewDeployConcurrency(relevantEvent),
|
|
4434
|
+
permissions,
|
|
4435
|
+
env: { ...workflowEnv, CL_PR_NUMBER: `\${{ ${PR_NUMBER_EXPRESSION} }}` },
|
|
4436
|
+
jobs: {
|
|
4437
|
+
[GUARD_JOB_ID]: {
|
|
4438
|
+
name: "🚦 review deploy guard",
|
|
4439
|
+
"runs-on": "ubuntu-latest",
|
|
4440
|
+
...(pullRequestTypes.length > 0
|
|
4441
|
+
? { if: `\${{ ${relevantEvent} }}` }
|
|
4442
|
+
: {}),
|
|
4443
|
+
permissions: {
|
|
4444
|
+
contents: "read",
|
|
4445
|
+
"pull-requests": "read",
|
|
4446
|
+
checks: "read",
|
|
4447
|
+
statuses: "write",
|
|
4448
|
+
},
|
|
4449
|
+
env: {
|
|
4450
|
+
GITHUB_TOKEN: "${{ github.token }}",
|
|
4451
|
+
CL_REVIEW_APPS: JSON.stringify(reviewApps),
|
|
4452
|
+
CL_DEPLOY_SOURCE: "${{ github.event_name == 'workflow_dispatch' && (inputs.source || 'manual') || 'label' }}",
|
|
4453
|
+
CL_HEAD_SHA: "${{ github.event.pull_request.head.sha || github.sha }}",
|
|
4454
|
+
},
|
|
4455
|
+
outputs: { deploy: "${{ steps.main.outputs.deploy }}" },
|
|
4456
|
+
steps: [
|
|
4457
|
+
catciCheckout(),
|
|
4458
|
+
{
|
|
4459
|
+
name: "🚦 review deploy guard",
|
|
4460
|
+
id: "main",
|
|
4461
|
+
run: `node ${CATCI} review-app github-guard`,
|
|
4462
|
+
shell: "bash",
|
|
4463
|
+
},
|
|
4464
|
+
{
|
|
4465
|
+
// the pull request shows the deploy in flight
|
|
4466
|
+
name: "report pending review app",
|
|
4467
|
+
if: "${{ steps.main.outputs.deploy == 'true' }}",
|
|
4468
|
+
env: { GH_TOKEN: "${{ github.token }}" },
|
|
4469
|
+
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`,
|
|
4470
|
+
shell: "bash",
|
|
4471
|
+
},
|
|
4472
|
+
],
|
|
4473
|
+
},
|
|
4474
|
+
...imageJobs,
|
|
4475
|
+
...chainJobs,
|
|
4476
|
+
...(0, exports.makeReviewAppAggregateJob)([GUARD_JOB_ID, ...Object.keys(imageJobs), ...Object.keys(chainJobs)], `needs.${GUARD_JOB_ID}.outputs.deploy == 'true'`),
|
|
4477
|
+
},
|
|
4478
|
+
};
|
|
4479
|
+
};
|
|
4480
|
+
exports.makeReviewDeployWorkflow = makeReviewDeployWorkflow;
|
|
4481
|
+
exports.REVIEW_APP_AGGREGATE_JOB_ID = "catladder-review-app-ok";
|
|
4482
|
+
exports.REVIEW_APP_AGGREGATE_JOB_NAME = "catladder review app ✅";
|
|
4483
|
+
/** the commit status context the aggregate job reports */
|
|
4484
|
+
exports.REVIEW_APP_STATUS_CONTEXT = "catladder review app";
|
|
4485
|
+
/**
|
|
4486
|
+
* one job with a STABLE name that succeeds exactly when the review apps
|
|
4487
|
+
* of this run deployed and verified — the result a merge gate (a bot,
|
|
4488
|
+
* or a branch rule for projects that deploy every PR) waits for,
|
|
4489
|
+
* regardless of the project's components.
|
|
4490
|
+
*
|
|
4491
|
+
* It also reports a commit status on the PR head: runs started by a
|
|
4492
|
+
* dispatch (the trigger job, or by hand) are attached to the commit but
|
|
4493
|
+
* not shown on the pull request, a commit status always is.
|
|
4494
|
+
*
|
|
4495
|
+
* Runs only when the run deploys (the guard decided so, or the review
|
|
4496
|
+
* workflow's start condition held) — a run that doesn't deploy says
|
|
4497
|
+
* nothing about the review app (its check is skipped, the commit status
|
|
4498
|
+
* untouched). Neither does a cancelled run (superseded by a newer
|
|
4499
|
+
* deploy, or stopped): a gate keeps waiting instead of seeing red.
|
|
4500
|
+
*/
|
|
4501
|
+
const makeReviewAppAggregateJob = (needs,
|
|
4502
|
+
/** when the run deploys the review apps (a github expression) */
|
|
4503
|
+
deploysWhen) => ({
|
|
4504
|
+
[exports.REVIEW_APP_AGGREGATE_JOB_ID]: {
|
|
4505
|
+
name: exports.REVIEW_APP_AGGREGATE_JOB_NAME,
|
|
4506
|
+
"runs-on": "ubuntu-latest",
|
|
4507
|
+
needs: [...needs].sort(),
|
|
4508
|
+
if: `\${{ always() && (${deploysWhen}) }}`,
|
|
4509
|
+
permissions: { statuses: "write" },
|
|
4510
|
+
env: {
|
|
4511
|
+
GH_TOKEN: "${{ github.token }}",
|
|
4512
|
+
HEAD_SHA: "${{ github.event.pull_request.head.sha || github.sha }}",
|
|
4513
|
+
RUN_URL: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}",
|
|
4514
|
+
},
|
|
4515
|
+
steps: [
|
|
4516
|
+
{
|
|
4517
|
+
name: exports.REVIEW_APP_AGGREGATE_JOB_NAME,
|
|
4518
|
+
// the needs context only interpolates inside the workflow file,
|
|
4519
|
+
// so this script stays inline
|
|
4520
|
+
run: [
|
|
4521
|
+
`results='\${{ toJSON(needs) }}'`,
|
|
4522
|
+
`echo "$results"`,
|
|
4523
|
+
`if echo "$results" | grep -qE '"result": "failure"'; then`,
|
|
4524
|
+
` state=failure; description="the review apps did not deploy and verify"`,
|
|
4525
|
+
`elif echo "$results" | grep -qE '"result": "cancelled"'; then`,
|
|
4526
|
+
` # superseded by a newer deploy, or stopped: no result, not red`,
|
|
4527
|
+
` echo "cancelled — no review app result reported"; exit 0`,
|
|
4528
|
+
`elif echo "$results" | grep -qE '"result": "skipped"'; then`,
|
|
4529
|
+
` state=failure; description="the review apps did not deploy and verify"`,
|
|
4530
|
+
`else`,
|
|
4531
|
+
` state=success; description="review apps deployed and verified"`,
|
|
4532
|
+
`fi`,
|
|
4533
|
+
`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`,
|
|
4534
|
+
`echo "$description"`,
|
|
4535
|
+
`[ "$state" = success ]`,
|
|
4536
|
+
].join("\n"),
|
|
4537
|
+
shell: "bash",
|
|
4538
|
+
},
|
|
4539
|
+
],
|
|
4540
|
+
},
|
|
4541
|
+
});
|
|
4542
|
+
exports.makeReviewAppAggregateJob = makeReviewAppAggregateJob;
|
|
4543
|
+
/** a checkout of just the materialized catci */
|
|
4544
|
+
const catciCheckout = () => ({
|
|
4545
|
+
name: "Checkout",
|
|
4546
|
+
uses: "actions/checkout@v4",
|
|
4547
|
+
with: { "sparse-checkout": shippedCatci_1.GENERATED_CATCI_FOLDER },
|
|
4548
|
+
});
|
|
4549
|
+
/**
|
|
4550
|
+
* one deploy/stop of a PR's review apps at a time, the newest wins —
|
|
4551
|
+
* shared by the deploy and the stop workflow, so closing a PR or
|
|
4552
|
+
* switching the label off cancels a deploy in flight before tearing
|
|
4553
|
+
* down. Events that don't concern the review apps get a group of
|
|
4554
|
+
* their own, so they never cancel anything.
|
|
4555
|
+
*/
|
|
4556
|
+
const reviewDeployConcurrency = (relevantEvent) => ({
|
|
4557
|
+
group: `catladder-review-deploy-\${{ ${PR_NUMBER_EXPRESSION} }}\${{ !(${relevantEvent}) && format('-ignored-{0}', github.run_id) || '' }}`,
|
|
4558
|
+
"cancel-in-progress": true,
|
|
4559
|
+
});
|
|
4560
|
+
/**
|
|
4561
|
+
* the last job of the review workflow: once it is green, dispatch the
|
|
4562
|
+
* review deploy workflow when the label/draft policy says this PR
|
|
4563
|
+
* deploys (read fresh from the PR, so a label set while CI ran counts).
|
|
4564
|
+
* Only for policies that deploy on their own — `deploy: "manual"` waits
|
|
4565
|
+
* for an explicit dispatch.
|
|
4566
|
+
*/
|
|
4567
|
+
const makeReviewDeployTriggerJob = (reviewApps,
|
|
4568
|
+
/** the review workflow deployed itself when this held at its start */
|
|
4569
|
+
inRunCondition) => reviewApps.deploy === "manual"
|
|
4570
|
+
? {}
|
|
4571
|
+
: {
|
|
4572
|
+
[TRIGGER_JOB_ID]: {
|
|
4573
|
+
name: "▶️ trigger review deploy",
|
|
4574
|
+
"runs-on": "ubuntu-latest",
|
|
4575
|
+
needs: [aggregateCheckJob_1.AGGREGATE_CHECK_JOB_ID],
|
|
4576
|
+
...(inRunCondition
|
|
4577
|
+
? {
|
|
4578
|
+
// NOT success(): it also checks the transitive needs,
|
|
4579
|
+
// and the chains this run skipped on purpose would make
|
|
4580
|
+
// it false — exactly when the trigger is needed
|
|
4581
|
+
if: `\${{ !cancelled() && needs.${aggregateCheckJob_1.AGGREGATE_CHECK_JOB_ID}.result == 'success' && !(${inRunCondition}) }}`,
|
|
4582
|
+
}
|
|
4583
|
+
: {}),
|
|
4584
|
+
permissions: {
|
|
4585
|
+
contents: "read",
|
|
4586
|
+
"pull-requests": "read",
|
|
4587
|
+
actions: "write",
|
|
4588
|
+
},
|
|
4589
|
+
env: {
|
|
4590
|
+
GITHUB_TOKEN: "${{ github.token }}",
|
|
4591
|
+
CL_REVIEW_APPS: JSON.stringify(reviewApps),
|
|
4592
|
+
CL_HEAD_REF: "${{ github.head_ref }}",
|
|
4593
|
+
CL_HEAD_SHA: "${{ github.event.pull_request.head.sha }}",
|
|
4594
|
+
},
|
|
4595
|
+
steps: [
|
|
4596
|
+
catciCheckout(),
|
|
4597
|
+
{
|
|
4598
|
+
name: "▶️ trigger review deploy",
|
|
4599
|
+
run: `node ${CATCI} review-app github-trigger`,
|
|
4600
|
+
shell: "bash",
|
|
4601
|
+
},
|
|
4602
|
+
],
|
|
4603
|
+
},
|
|
4604
|
+
};
|
|
4605
|
+
exports.makeReviewDeployTriggerJob = makeReviewDeployTriggerJob;
|
|
4606
|
+
/**
|
|
4607
|
+
* the review stop workflow under a label policy: switching the label
|
|
4608
|
+
* off (opt-in: removing it, opt-out: adding the skip label) stops the
|
|
4609
|
+
* review apps, like closing the PR does. It shares the deploy
|
|
4610
|
+
* workflow's concurrency group, so a deploy in flight is cancelled
|
|
4611
|
+
* first.
|
|
4612
|
+
*/
|
|
4613
|
+
const withReviewStopSwitch = (workflow, reviewApps,
|
|
4614
|
+
/** the review workflow deploys itself (see getInRunDeployCondition) */
|
|
4615
|
+
reviewRunDeploys = false) => {
|
|
4616
|
+
const switchLabel = (0, reviewApps_1.getReviewAppsSwitchLabel)(reviewApps);
|
|
4617
|
+
const relevantEvent = [
|
|
4618
|
+
"github.event_name == 'workflow_dispatch'",
|
|
4619
|
+
"github.event.action == 'closed'",
|
|
4620
|
+
...(switchLabel
|
|
4621
|
+
? [
|
|
4622
|
+
`(github.event.action == ${literal(switchLabel.stopOn)} && github.event.label.name == ${literal(switchLabel.label)})`,
|
|
4623
|
+
]
|
|
4624
|
+
: []),
|
|
4625
|
+
].join(" || ");
|
|
4626
|
+
return {
|
|
4627
|
+
...workflow,
|
|
4628
|
+
on: {
|
|
4629
|
+
...workflow.on,
|
|
4630
|
+
pull_request: {
|
|
4631
|
+
types: ["closed", ...(switchLabel ? [switchLabel.stopOn] : [])],
|
|
4632
|
+
},
|
|
4633
|
+
},
|
|
4634
|
+
concurrency: reviewDeployConcurrency(relevantEvent),
|
|
4635
|
+
jobs: Object.fromEntries(Object.entries({
|
|
4636
|
+
...(reviewRunDeploys ? makeCancelReviewRunsJob() : {}),
|
|
4637
|
+
...Object.fromEntries(Object.entries(workflow.jobs).map(([id, job]) => {
|
|
4638
|
+
var _a;
|
|
4639
|
+
return [
|
|
4640
|
+
id,
|
|
4641
|
+
// the teardown waits until no review run deploys anymore
|
|
4642
|
+
reviewRunDeploys && !id.startsWith("catladder-image-")
|
|
4643
|
+
? {
|
|
4644
|
+
...job,
|
|
4645
|
+
needs: [CANCEL_REVIEW_RUNS_JOB_ID, ...((_a = job.needs) !== null && _a !== void 0 ? _a : [])],
|
|
4646
|
+
}
|
|
4647
|
+
: job,
|
|
4648
|
+
];
|
|
4649
|
+
})),
|
|
4650
|
+
}).map(([id, job]) => [
|
|
4651
|
+
id,
|
|
4652
|
+
switchLabel ? { ...job, if: andIf(job.if, relevantEvent) } : job,
|
|
4653
|
+
])),
|
|
4654
|
+
};
|
|
4655
|
+
};
|
|
4656
|
+
exports.withReviewStopSwitch = withReviewStopSwitch;
|
|
4657
|
+
const CANCEL_REVIEW_RUNS_JOB_ID = "catladder-wait-review-runs";
|
|
4658
|
+
/**
|
|
4659
|
+
* the review workflow deploys review apps itself, outside the deploy /
|
|
4660
|
+
* stop concurrency group — a teardown would race a deploy still running
|
|
4661
|
+
* there (the deploy finishing after the stop leaves the app and its
|
|
4662
|
+
* database behind). So the stop first waits for the PR's unfinished
|
|
4663
|
+
* review runs to end — cancelling them when the PR was closed, letting
|
|
4664
|
+
* them finish when only the label was switched off (their CI still
|
|
4665
|
+
* counts). Never fails: a stuck run must not keep the teardown from
|
|
4666
|
+
* running (it gives up after 30 minutes).
|
|
4667
|
+
*/
|
|
4668
|
+
const makeCancelReviewRunsJob = () => ({
|
|
4669
|
+
[CANCEL_REVIEW_RUNS_JOB_ID]: {
|
|
4670
|
+
name: "wait for running review deploys",
|
|
4671
|
+
"runs-on": "ubuntu-latest",
|
|
4672
|
+
permissions: { actions: "write", "pull-requests": "read" },
|
|
4673
|
+
env: { GH_TOKEN: "${{ github.token }}" },
|
|
4674
|
+
steps: [
|
|
4675
|
+
{
|
|
4676
|
+
name: "wait for running review deploys",
|
|
4677
|
+
run: [
|
|
4678
|
+
`branch=$(gh pr view "$CL_PR_NUMBER" -R "$GITHUB_REPOSITORY" --json headRefName -q .headRefName) || exit 0`,
|
|
4679
|
+
`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`,
|
|
4680
|
+
`# a closed PR's CI is worthless: cancel it. Otherwise let the`,
|
|
4681
|
+
`# PR's CI finish (cancelling it would leave catladder ✅ red)`,
|
|
4682
|
+
`if [ "$(jq -r '.action // ""' "$GITHUB_EVENT_PATH")" = closed ]; then`,
|
|
4683
|
+
` for id in $runs; do echo "cancelling review run $id"; gh run cancel "$id" -R "$GITHUB_REPOSITORY" || true; done`,
|
|
4684
|
+
`fi`,
|
|
4685
|
+
`for id in $runs; do`,
|
|
4686
|
+
` echo "waiting for review run $id"`,
|
|
4687
|
+
` for _ in $(seq 1 360); do`,
|
|
4688
|
+
` [ "$(gh run view "$id" -R "$GITHUB_REPOSITORY" --json status -q .status)" = completed ] && break`,
|
|
4689
|
+
` sleep 5`,
|
|
4690
|
+
` done`,
|
|
4691
|
+
`done`,
|
|
4692
|
+
`exit 0`,
|
|
4693
|
+
].join("\n"),
|
|
4694
|
+
shell: "bash",
|
|
4695
|
+
},
|
|
4696
|
+
],
|
|
4697
|
+
},
|
|
4698
|
+
});
|
|
4699
|
+
/**
|
|
4700
|
+
* `drafts: "skip"`: draft PRs run nothing — every job of the review
|
|
4701
|
+
* workflow skips them, and marking the PR ready starts the run. A
|
|
4702
|
+
* skipped `catladder ✅` can't let a draft through: drafts can't be
|
|
4703
|
+
* merged.
|
|
4704
|
+
*/
|
|
4705
|
+
const withDraftsSkipped = (workflow) => {
|
|
4706
|
+
const on = workflow.on;
|
|
4707
|
+
return {
|
|
4708
|
+
...workflow,
|
|
4709
|
+
on: {
|
|
4710
|
+
...workflow.on,
|
|
4711
|
+
pull_request: {
|
|
4712
|
+
...on.pull_request,
|
|
4713
|
+
types: [...on.pull_request.types, "ready_for_review"],
|
|
4714
|
+
},
|
|
4715
|
+
},
|
|
4716
|
+
jobs: Object.fromEntries(Object.entries(workflow.jobs).map(([id, job]) => [
|
|
4717
|
+
id,
|
|
4718
|
+
{ ...job, if: andIf(job.if, "!github.event.pull_request.draft") },
|
|
4719
|
+
])),
|
|
4720
|
+
};
|
|
4721
|
+
};
|
|
4722
|
+
exports.withDraftsSkipped = withDraftsSkipped;
|
|
4723
|
+
//# sourceMappingURL=reviewDeployWorkflow.js.map
|
|
4724
|
+
|
|
4725
|
+
/***/ }),
|
|
4726
|
+
|
|
4065
4727
|
/***/ 61071:
|
|
4066
4728
|
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
4067
4729
|
|
|
@@ -4166,6 +4828,8 @@ const rules_1 = __nccwpck_require__(78503);
|
|
|
4166
4828
|
const config_1 = __nccwpck_require__(75591);
|
|
4167
4829
|
const configruedEnvs_1 = __nccwpck_require__(38866);
|
|
4168
4830
|
const autoStop_1 = __nccwpck_require__(72792);
|
|
4831
|
+
const reviewApps_1 = __nccwpck_require__(67071);
|
|
4832
|
+
const reviewDeployGate_1 = __nccwpck_require__(3485);
|
|
4169
4833
|
const createAllJobs_1 = __nccwpck_require__(83477);
|
|
4170
4834
|
const jobImagesPlan_1 = __nccwpck_require__(59623);
|
|
4171
4835
|
const shippedCatci_1 = __nccwpck_require__(27967);
|
|
@@ -4266,7 +4930,6 @@ class GitlabBackend {
|
|
|
4266
4930
|
return [mainInclude, ...componentIncludes];
|
|
4267
4931
|
}
|
|
4268
4932
|
async createPipeline(config, images) {
|
|
4269
|
-
const stages = (0, getPipelineStages_1.getPipelineStages)(config);
|
|
4270
4933
|
// register the release image first: the per-trigger job creation
|
|
4271
4934
|
// below emits the build jobs of all images registered so far. The
|
|
4272
4935
|
// release jobs themselves are created afterwards — they need the
|
|
@@ -4280,11 +4943,36 @@ class GitlabBackend {
|
|
|
4280
4943
|
...config_1.ALL_PIPELINE_TRIGGERS,
|
|
4281
4944
|
...branchTriggers,
|
|
4282
4945
|
];
|
|
4283
|
-
const
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4946
|
+
const reviewApps = (0, reviewApps_1.getReviewAppsConfig)(config);
|
|
4947
|
+
const jobsPerTrigger = await Promise.all(allTriggers.map(async (trigger) => {
|
|
4948
|
+
const catladderJobs = await (0, createAllJobs_1.createAllJobs)({
|
|
4949
|
+
config,
|
|
4950
|
+
trigger,
|
|
4951
|
+
pipelineType: this.type,
|
|
4952
|
+
});
|
|
4953
|
+
// review deploys behind one switch per MR (reviewApps config)
|
|
4954
|
+
const reviewDeployGate = trigger === "mr"
|
|
4955
|
+
? (0, reviewDeployGate_1.addGitlabReviewDeployGate)(catladderJobs, reviewApps)
|
|
4956
|
+
: undefined;
|
|
4957
|
+
const jobs = await (0, createGitlabJobs_1.createGitlabJobs)(catladderJobs, images, getGitlabRulesForTrigger(trigger));
|
|
4958
|
+
return {
|
|
4959
|
+
trigger,
|
|
4960
|
+
jobs: reviewDeployGate
|
|
4961
|
+
? [
|
|
4962
|
+
{
|
|
4963
|
+
name: reviewDeployGate_1.REVIEW_DEPLOY_GATE_JOB_NAME,
|
|
4964
|
+
gitlabJob: reviewDeployGate,
|
|
4965
|
+
context: null,
|
|
4966
|
+
},
|
|
4967
|
+
...jobs,
|
|
4968
|
+
]
|
|
4969
|
+
: jobs,
|
|
4970
|
+
};
|
|
4971
|
+
}));
|
|
4287
4972
|
const allJobsPerTrigger = jobsPerTrigger.flatMap(({ jobs }) => jobs);
|
|
4973
|
+
const stages = (0, getPipelineStages_1.getPipelineStages)(config, new Set(allJobsPerTrigger
|
|
4974
|
+
.map(({ gitlabJob }) => gitlabJob.stage)
|
|
4975
|
+
.filter((stage) => typeof stage === "string")));
|
|
4288
4976
|
// the jobs the queued-release executor waits for: everything that
|
|
4289
4977
|
// runs automatically in a main-branch pipeline. Manual jobs (stop,
|
|
4290
4978
|
// rollback, gates) are excluded — gitlab skips a job whose needs
|
|
@@ -4360,6 +5048,7 @@ class GitlabBackend {
|
|
|
4360
5048
|
},
|
|
4361
5049
|
branchPipelines: branchTriggers.map(({ branch }) => branch),
|
|
4362
5050
|
autoStop: (0, autoStop_1.getAutoStopConfig)(config),
|
|
5051
|
+
reviewApps,
|
|
4363
5052
|
});
|
|
4364
5053
|
}
|
|
4365
5054
|
}
|
|
@@ -4814,21 +5503,23 @@ function getGitlabNeedsForWorkspaceJob(context, { needs }, allJobs) {
|
|
|
4814
5503
|
"use strict";
|
|
4815
5504
|
|
|
4816
5505
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
4817
|
-
exports.createGitlabPipelineWithDefaults = void 0;
|
|
5506
|
+
exports.createGitlabPipelineWithDefaults = exports.DEFAULT_PIPELINE_IMAGE = void 0;
|
|
4818
5507
|
const bash_1 = __nccwpck_require__(94143);
|
|
4819
5508
|
const autoStop_1 = __nccwpck_require__(72792);
|
|
5509
|
+
const reviewApps_1 = __nccwpck_require__(67071);
|
|
4820
5510
|
const rules_1 = __nccwpck_require__(78503);
|
|
4821
5511
|
/**
|
|
4822
5512
|
* gitlab requires a top-level default image for jobs that don't set their
|
|
4823
5513
|
* own. Every catladder-generated job sets an explicit image, so this
|
|
4824
|
-
* fallback is never
|
|
5514
|
+
* fallback is never pulled for them — it just needs to be a valid,
|
|
4825
5515
|
* build-free image (a catladder job image would drag in a build job).
|
|
5516
|
+
* Trivial jobs (the review deploy switch) reuse it explicitly.
|
|
4826
5517
|
*/
|
|
4827
|
-
|
|
4828
|
-
const createGitlabPipelineWithDefaults = ({ image, variables, before_script, branchPipelines = [], autoStop, ...config }) => {
|
|
5518
|
+
exports.DEFAULT_PIPELINE_IMAGE = "node:22";
|
|
5519
|
+
const createGitlabPipelineWithDefaults = ({ image, variables, before_script, branchPipelines = [], autoStop, reviewApps, ...config }) => {
|
|
4829
5520
|
const pinLabel = autoStop && autoStop.pinLabel;
|
|
4830
5521
|
return {
|
|
4831
|
-
image: image !== null && image !== void 0 ? image : DEFAULT_PIPELINE_IMAGE,
|
|
5522
|
+
image: image !== null && image !== void 0 ? image : exports.DEFAULT_PIPELINE_IMAGE,
|
|
4832
5523
|
variables: {
|
|
4833
5524
|
FF_USE_FASTZIP: "true", // enable fastzip - a faster zip implementation that also supports level configuration.
|
|
4834
5525
|
ARTIFACT_COMPRESSION_LEVEL: "fast", // we value speed over compression
|
|
@@ -4866,6 +5557,16 @@ const createGitlabPipelineWithDefaults = ({ image, variables, before_script, bra
|
|
|
4866
5557
|
PIPELINE_NAME: "Thinking...",
|
|
4867
5558
|
},
|
|
4868
5559
|
},
|
|
5560
|
+
// reviewApps.drafts: "skip" — draft MRs get no pipeline (rules
|
|
5561
|
+
// stop at the first match, so this must come first)
|
|
5562
|
+
...((reviewApps === null || reviewApps === void 0 ? void 0 : reviewApps.drafts) === "skip"
|
|
5563
|
+
? [
|
|
5564
|
+
{
|
|
5565
|
+
if: `${rules_1.RULE_IS_MERGE_REQUEST.if} && ${reviewApps_1.GITLAB_DRAFT_MR_CONDITION}`,
|
|
5566
|
+
when: "never",
|
|
5567
|
+
},
|
|
5568
|
+
]
|
|
5569
|
+
: []),
|
|
4869
5570
|
// pinned MRs (rules stop at the first match, so this must come
|
|
4870
5571
|
// before the plain merge-request rule)
|
|
4871
5572
|
...(pinLabel
|
|
@@ -4939,14 +5640,23 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
|
4939
5640
|
exports.getPipelineStages = void 0;
|
|
4940
5641
|
const config_1 = __nccwpck_require__(11646);
|
|
4941
5642
|
const jobs_1 = __nccwpck_require__(98521);
|
|
5643
|
+
/**
|
|
5644
|
+
* stages that only appear in the pipeline when a job uses them, so that
|
|
5645
|
+
* projects not using the feature keep their generated files unchanged
|
|
5646
|
+
*/
|
|
5647
|
+
const OPTIONAL_STAGES = ["post-build"];
|
|
4942
5648
|
/**
|
|
4943
5649
|
* while technically not required, we group different envs in its own stage
|
|
4944
5650
|
* each job from `createJobs` that is defined as `envMode: "stagePerEnv"` will have `deploy dev`, etc. instead of just `deploy`
|
|
4945
5651
|
* this is just so that it looks nicer in gitlab and makes running mutliple manual tasks more easy to use
|
|
5652
|
+
*
|
|
5653
|
+
* `usedStages` are the stages of all generated jobs; optional stages are
|
|
5654
|
+
* dropped when none of them is used.
|
|
4946
5655
|
*/
|
|
4947
|
-
function getPipelineStages(config) {
|
|
5656
|
+
function getPipelineStages(config, usedStages = new Set()) {
|
|
4948
5657
|
const allEnvs = (0, config_1.getAllEnvsInAllComponents)(config).filter((e) => e !== "local");
|
|
4949
|
-
const
|
|
5658
|
+
const isUsed = (baseStage) => [...usedStages].some((stage) => stage === baseStage || stage.startsWith(`${baseStage} `));
|
|
5659
|
+
const stages = jobs_1.BASE_STAGES.filter((baseStage) => !OPTIONAL_STAGES.includes(baseStage) || isUsed(baseStage)).reduce((acc, baseStage) => [
|
|
4950
5660
|
...acc,
|
|
4951
5661
|
baseStage,
|
|
4952
5662
|
...allEnvs.map((e) => `${baseStage} ${e}`),
|
|
@@ -5224,6 +5934,107 @@ exports.getReleaseGateJobNames = getReleaseGateJobNames;
|
|
|
5224
5934
|
|
|
5225
5935
|
/***/ }),
|
|
5226
5936
|
|
|
5937
|
+
/***/ 3485:
|
|
5938
|
+
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
5939
|
+
|
|
5940
|
+
"use strict";
|
|
5941
|
+
|
|
5942
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
5943
|
+
exports.addGitlabReviewDeployGate = exports.REVIEW_DEPLOY_GATE_JOB_NAME = void 0;
|
|
5944
|
+
const reviewApps_1 = __nccwpck_require__(67071);
|
|
5945
|
+
const rules_1 = __nccwpck_require__(78503);
|
|
5946
|
+
const createGitlabPipeline_1 = __nccwpck_require__(32060);
|
|
5947
|
+
exports.REVIEW_DEPLOY_GATE_JOB_NAME = "🚀 deploy review";
|
|
5948
|
+
/**
|
|
5949
|
+
* the review deploy switch of a merge request (see reviewApps.ts): ONE
|
|
5950
|
+
* job per MR pipeline that unlocks the delivery chains (docker → deploy
|
|
5951
|
+
* → verify) of all components' review apps, instead of a manual job per
|
|
5952
|
+
* component — or a docker build per component in every pipeline.
|
|
5953
|
+
*
|
|
5954
|
+
* The chain heads `needs` the switch. It is a no-op job with
|
|
5955
|
+
* `allow_failure: true`: while it is unplayed, gitlab marks its
|
|
5956
|
+
* dependents skipped and the pipeline still passes ("Pipelines must
|
|
5957
|
+
* succeed" keeps working); playing it resets them to created, so it
|
|
5958
|
+
* can be played any time — early clicks wait for the builds and tests
|
|
5959
|
+
* like the chains always did (the same mechanism as the release
|
|
5960
|
+
* button).
|
|
5961
|
+
*
|
|
5962
|
+
* The label/draft policy decides whether it starts on its own. Label
|
|
5963
|
+
* changes only apply to the next pipeline (gitlab doesn't start one for
|
|
5964
|
+
* them) — `catladder mr review-app-on` sets the label and triggers one.
|
|
5965
|
+
*
|
|
5966
|
+
* Mutates the needs of the gated jobs; returns the switch job, or
|
|
5967
|
+
* nothing when review deploys aren't gated (or nothing deploys).
|
|
5968
|
+
*/
|
|
5969
|
+
const addGitlabReviewDeployGate = (allJobs, reviewApps) => {
|
|
5970
|
+
var _a;
|
|
5971
|
+
if (!(0, reviewApps_1.isReviewDeployGated)(reviewApps)) {
|
|
5972
|
+
return undefined;
|
|
5973
|
+
}
|
|
5974
|
+
let gated = false;
|
|
5975
|
+
for (const { context, jobs } of allJobs.components) {
|
|
5976
|
+
for (const job of jobs) {
|
|
5977
|
+
// the chain heads: verify follows its deploy anyway
|
|
5978
|
+
if ((0, reviewApps_1.isReviewDeliveryJob)(context, job) && job.stage !== "verify") {
|
|
5979
|
+
job.needs = [
|
|
5980
|
+
...((_a = job.needs) !== null && _a !== void 0 ? _a : []),
|
|
5981
|
+
{ job: exports.REVIEW_DEPLOY_GATE_JOB_NAME, artifacts: false, global: true },
|
|
5982
|
+
];
|
|
5983
|
+
gated = true;
|
|
5984
|
+
}
|
|
5985
|
+
}
|
|
5986
|
+
}
|
|
5987
|
+
if (!gated) {
|
|
5988
|
+
return undefined;
|
|
5989
|
+
}
|
|
5990
|
+
const isMr = rules_1.RULE_IS_MERGE_REQUEST.if;
|
|
5991
|
+
const manual = { when: "manual", allow_failure: true };
|
|
5992
|
+
const auto = { when: "on_success" };
|
|
5993
|
+
const policyRules = reviewApps.deploy === "optIn"
|
|
5994
|
+
? [
|
|
5995
|
+
{
|
|
5996
|
+
if: `${isMr} && $CI_MERGE_REQUEST_LABELS =~ ${(0, reviewApps_1.getGitlabLabelRegex)(reviewApps.label)}`,
|
|
5997
|
+
...auto,
|
|
5998
|
+
},
|
|
5999
|
+
{ if: isMr, ...manual },
|
|
6000
|
+
]
|
|
6001
|
+
: reviewApps.deploy === "optOut"
|
|
6002
|
+
? [
|
|
6003
|
+
{
|
|
6004
|
+
if: `${isMr} && $CI_MERGE_REQUEST_LABELS =~ ${(0, reviewApps_1.getGitlabLabelRegex)(reviewApps.skipLabel)}`,
|
|
6005
|
+
...manual,
|
|
6006
|
+
},
|
|
6007
|
+
{ if: isMr, ...auto },
|
|
6008
|
+
]
|
|
6009
|
+
: reviewApps.deploy === "manual"
|
|
6010
|
+
? [{ if: isMr, ...manual }]
|
|
6011
|
+
: [{ if: isMr, ...auto }];
|
|
6012
|
+
return {
|
|
6013
|
+
stage: "setup",
|
|
6014
|
+
image: createGitlabPipeline_1.DEFAULT_PIPELINE_IMAGE,
|
|
6015
|
+
variables: { GIT_STRATEGY: "none" },
|
|
6016
|
+
needs: [],
|
|
6017
|
+
interruptible: true,
|
|
6018
|
+
allow_failure: true,
|
|
6019
|
+
script: [
|
|
6020
|
+
`echo "review apps unlocked — the docker → deploy → verify chains of all components run as soon as their builds and tests succeeded"`,
|
|
6021
|
+
],
|
|
6022
|
+
rules: [
|
|
6023
|
+
rules_1.RULE_NEVER_ON_AGENT_TRIGGER,
|
|
6024
|
+
// drafts never deploy on their own (drafts: "skip" has no
|
|
6025
|
+
// pipeline at all, see the workflow rules)
|
|
6026
|
+
...(reviewApps.drafts !== "likeReady"
|
|
6027
|
+
? [{ if: `${isMr} && ${reviewApps_1.GITLAB_DRAFT_MR_CONDITION}`, ...manual }]
|
|
6028
|
+
: []),
|
|
6029
|
+
...policyRules,
|
|
6030
|
+
],
|
|
6031
|
+
};
|
|
6032
|
+
};
|
|
6033
|
+
exports.addGitlabReviewDeployGate = addGitlabReviewDeployGate;
|
|
6034
|
+
//# sourceMappingURL=reviewDeployGate.js.map
|
|
6035
|
+
|
|
6036
|
+
/***/ }),
|
|
6037
|
+
|
|
5227
6038
|
/***/ 21444:
|
|
5228
6039
|
/***/ ((__unused_webpack_module, exports) => {
|
|
5229
6040
|
|
|
@@ -8948,6 +9759,7 @@ const jobs_1 = __nccwpck_require__(98521);
|
|
|
8948
9759
|
const utils_1 = __nccwpck_require__(71286);
|
|
8949
9760
|
const autoStop_1 = __nccwpck_require__(72792);
|
|
8950
9761
|
const stop_1 = __nccwpck_require__(35937);
|
|
9762
|
+
const reviewApps_1 = __nccwpck_require__(67071);
|
|
8951
9763
|
const variables_1 = __nccwpck_require__(54768);
|
|
8952
9764
|
exports.DEPLOY_JOB_NAME = "🚀 Deploy";
|
|
8953
9765
|
class DeployJob extends jobs_1.CatladderJob {
|
|
@@ -8984,9 +9796,15 @@ class DeployJob extends jobs_1.CatladderJob {
|
|
|
8984
9796
|
: ((_b = context.componentConfig.env) === null || _b === void 0 ? void 0 : _b.stage) === false
|
|
8985
9797
|
? "auto" // is prod, but no staging, auto deploy
|
|
8986
9798
|
: "manual"; // manually deploy
|
|
8987
|
-
|
|
8988
|
-
|
|
8989
|
-
|
|
9799
|
+
// gated review apps: one switch per MR/PR unlocks the whole delivery
|
|
9800
|
+
// chain (see reviewApps.ts), the deploy itself then runs on its own
|
|
9801
|
+
const isGatedReviewApp = context.environment.instance.type === "review" &&
|
|
9802
|
+
(0, reviewApps_1.isReviewDeployGated)((0, reviewApps_1.getReviewAppsConfig)(context.fullConfig));
|
|
9803
|
+
const whenDeploy = isGatedReviewApp
|
|
9804
|
+
? "auto"
|
|
9805
|
+
: whenDeployDefined
|
|
9806
|
+
? whenDeployDefined
|
|
9807
|
+
: whenDeployDefault;
|
|
8990
9808
|
// wait for the deployment of other components first
|
|
8991
9809
|
const waitForRequirements = (_d = (_c = deployConfig === null || deployConfig === void 0 ? void 0 : deployConfig.waitFor) === null || _c === void 0 ? void 0 : _c.map((c) => ({
|
|
8992
9810
|
capability: "deployment",
|
|
@@ -9030,14 +9848,20 @@ class DeployJob extends jobs_1.CatladderJob {
|
|
|
9030
9848
|
: []),
|
|
9031
9849
|
];
|
|
9032
9850
|
// we don't want to deploy when there is a broken test
|
|
9033
|
-
const
|
|
9034
|
-
|
|
9035
|
-
artifacts: false,
|
|
9036
|
-
//
|
|
9037
|
-
|
|
9038
|
-
?
|
|
9039
|
-
|
|
9040
|
-
|
|
9851
|
+
const qualityGateRequirements = [
|
|
9852
|
+
// the component's own gates (e.g. post-build tests, custom test jobs)
|
|
9853
|
+
{ capability: "qualityGate", artifacts: false },
|
|
9854
|
+
// ...and the gates of the workspace build it is built in
|
|
9855
|
+
...((0, context_1.componentContextHasWorkspaceBuild)(context)
|
|
9856
|
+
? [
|
|
9857
|
+
{
|
|
9858
|
+
capability: "qualityGate",
|
|
9859
|
+
artifacts: false,
|
|
9860
|
+
from: { workspace: context.build.workspaceName },
|
|
9861
|
+
},
|
|
9862
|
+
]
|
|
9863
|
+
: []),
|
|
9864
|
+
];
|
|
9041
9865
|
super({
|
|
9042
9866
|
name: exports.DEPLOY_JOB_NAME,
|
|
9043
9867
|
script: jobDefinition.script,
|
|
@@ -9054,7 +9878,7 @@ class DeployJob extends jobs_1.CatladderJob {
|
|
|
9054
9878
|
...waitForRequirements,
|
|
9055
9879
|
// if the build is disabled, we don't need to wait for it
|
|
9056
9880
|
...(context.build.type !== "disabled"
|
|
9057
|
-
? [...buildRequirements,
|
|
9881
|
+
? [...buildRequirements, ...qualityGateRequirements]
|
|
9058
9882
|
: []),
|
|
9059
9883
|
],
|
|
9060
9884
|
gate: whenDeploy,
|
|
@@ -11818,6 +12642,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
11818
12642
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
11819
12643
|
__exportStar(__nccwpck_require__(46734), exports);
|
|
11820
12644
|
__exportStar(__nccwpck_require__(72792), exports);
|
|
12645
|
+
__exportStar(__nccwpck_require__(67071), exports);
|
|
11821
12646
|
__exportStar(__nccwpck_require__(62853), exports);
|
|
11822
12647
|
__exportStar(__nccwpck_require__(63557), exports);
|
|
11823
12648
|
__exportStar(__nccwpck_require__(78503), exports);
|
|
@@ -11830,6 +12655,7 @@ __exportStar(__nccwpck_require__(15707), exports);
|
|
|
11830
12655
|
__exportStar(__nccwpck_require__(45492), exports);
|
|
11831
12656
|
__exportStar(__nccwpck_require__(3659), exports);
|
|
11832
12657
|
__exportStar(__nccwpck_require__(50713), exports);
|
|
12658
|
+
__exportStar(__nccwpck_require__(28458), exports);
|
|
11833
12659
|
__exportStar(__nccwpck_require__(26041), exports);
|
|
11834
12660
|
__exportStar(__nccwpck_require__(47663), exports);
|
|
11835
12661
|
__exportStar(__nccwpck_require__(91804), exports);
|
|
@@ -12427,6 +13253,7 @@ exports.createJobsForComponentContext = void 0;
|
|
|
12427
13253
|
const build_1 = __nccwpck_require__(45492);
|
|
12428
13254
|
const deploy_1 = __nccwpck_require__(3659);
|
|
12429
13255
|
const jobs_1 = __nccwpck_require__(98521);
|
|
13256
|
+
const createPostBuildTestJobs_1 = __nccwpck_require__(61844);
|
|
12430
13257
|
const createVerifyJobs_1 = __nccwpck_require__(66900);
|
|
12431
13258
|
const injectDefaultVarsInCustomJobs = (context, jobs) => jobs.map(({ variables, ...job }) => {
|
|
12432
13259
|
var _a;
|
|
@@ -12446,17 +13273,26 @@ const getCustomJobs = (context) => {
|
|
|
12446
13273
|
return injectDefaultVarsInCustomJobs(context, rawJobs);
|
|
12447
13274
|
};
|
|
12448
13275
|
const createJobsForComponentContext = async (context) => {
|
|
12449
|
-
const [buildJobs, deployJobs, verifyJobs] = await Promise.all([
|
|
13276
|
+
const [buildJobs, postBuildTestJobs, deployJobs, verifyJobs] = await Promise.all([
|
|
12450
13277
|
context.build.type !== "disabled"
|
|
12451
13278
|
? build_1.BUILD_TYPES[context.build.buildType].jobs(context)
|
|
12452
13279
|
: [],
|
|
13280
|
+
(0, createPostBuildTestJobs_1.createPostBuildTestJobs)(context),
|
|
12453
13281
|
context.componentConfig.deploy !== false
|
|
12454
13282
|
? deploy_1.DEPLOY_TYPES[context.componentConfig.deploy.type].jobs(context)
|
|
12455
13283
|
: [],
|
|
12456
13284
|
(0, createVerifyJobs_1.createVerifyJobs)(context),
|
|
12457
13285
|
]);
|
|
12458
13286
|
const customJobs = getCustomJobs(context);
|
|
12459
|
-
|
|
13287
|
+
const otherJobs = [...buildJobs, ...deployJobs, ...verifyJobs, ...customJobs];
|
|
13288
|
+
(0, createPostBuildTestJobs_1.assertUniquePostBuildTestNames)(context, postBuildTestJobs, otherJobs);
|
|
13289
|
+
return [
|
|
13290
|
+
...buildJobs,
|
|
13291
|
+
...postBuildTestJobs,
|
|
13292
|
+
...deployJobs,
|
|
13293
|
+
...verifyJobs,
|
|
13294
|
+
...customJobs,
|
|
13295
|
+
];
|
|
12460
13296
|
};
|
|
12461
13297
|
exports.createJobsForComponentContext = createJobsForComponentContext;
|
|
12462
13298
|
//# sourceMappingURL=createJobsForComponent.js.map
|
|
@@ -12471,9 +13307,14 @@ exports.createJobsForComponentContext = createJobsForComponentContext;
|
|
|
12471
13307
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
12472
13308
|
exports.createJobsForWorkspace = void 0;
|
|
12473
13309
|
const build_1 = __nccwpck_require__(45492);
|
|
13310
|
+
const createPostBuildTestJobs_1 = __nccwpck_require__(61844);
|
|
12474
13311
|
const createJobsForWorkspace = async (context) => {
|
|
12475
|
-
const buildJobs = await
|
|
12476
|
-
|
|
13312
|
+
const [buildJobs, postBuildTestJobs] = await Promise.all([
|
|
13313
|
+
build_1.WORKSPACE_BUILD_TYPES[context.build.buildType].jobs(context),
|
|
13314
|
+
(0, createPostBuildTestJobs_1.createPostBuildTestJobs)(context),
|
|
13315
|
+
]);
|
|
13316
|
+
(0, createPostBuildTestJobs_1.assertUniquePostBuildTestNames)(context, postBuildTestJobs, buildJobs);
|
|
13317
|
+
return [...buildJobs, ...postBuildTestJobs];
|
|
12477
13318
|
};
|
|
12478
13319
|
exports.createJobsForWorkspace = createJobsForWorkspace;
|
|
12479
13320
|
//# sourceMappingURL=createJobsForWorkspace.js.map
|
|
@@ -12933,7 +13774,7 @@ exports.projectFileExists = projectFileExists;
|
|
|
12933
13774
|
"use strict";
|
|
12934
13775
|
|
|
12935
13776
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
12936
|
-
exports.resolveRequirements = void 0;
|
|
13777
|
+
exports.resolveRequirements = exports.providesCapability = void 0;
|
|
12937
13778
|
/**
|
|
12938
13779
|
* jobs that don't declare `provides` get a capability derived from
|
|
12939
13780
|
* their stage. This keeps e.g. custom jobs in the test stage acting
|
|
@@ -12942,6 +13783,7 @@ exports.resolveRequirements = void 0;
|
|
|
12942
13783
|
const DEFAULT_STAGE_CAPABILITIES = {
|
|
12943
13784
|
build: "build",
|
|
12944
13785
|
test: "qualityGate",
|
|
13786
|
+
"post-build": "qualityGate",
|
|
12945
13787
|
deploy: "deployment",
|
|
12946
13788
|
};
|
|
12947
13789
|
/**
|
|
@@ -12963,6 +13805,7 @@ const providesCapability = (job, capability) => {
|
|
|
12963
13805
|
((_a = CAPABILITY_IMPLICATIONS[c]) !== null && _a !== void 0 ? _a : []).includes(capability);
|
|
12964
13806
|
});
|
|
12965
13807
|
};
|
|
13808
|
+
exports.providesCapability = providesCapability;
|
|
12966
13809
|
/**
|
|
12967
13810
|
* the planner: resolves the semantic `requires` declarations of all jobs
|
|
12968
13811
|
* to concrete job dependencies (`needs`), now that all jobs of the
|
|
@@ -13004,7 +13847,7 @@ const expandRequirement = (context, job, requirement, allJobs) => {
|
|
|
13004
13847
|
c.context.env === env;
|
|
13005
13848
|
})
|
|
13006
13849
|
.flatMap((c) => c.jobs);
|
|
13007
|
-
const providers = candidates.filter((candidate) => candidate !== job && providesCapability(candidate, capability));
|
|
13850
|
+
const providers = candidates.filter((candidate) => candidate !== job && (0, exports.providesCapability)(candidate, capability));
|
|
13008
13851
|
// consuming artifacts from nobody is almost always a broken config
|
|
13009
13852
|
const isStrict = (_b = strict !== null && strict !== void 0 ? strict : artifacts) !== null && _b !== void 0 ? _b : false;
|
|
13010
13853
|
if (isStrict && providers.length === 0) {
|
|
@@ -13083,6 +13926,216 @@ exports.getWorkspaceDependencies = getWorkspaceDependencies;
|
|
|
13083
13926
|
|
|
13084
13927
|
/***/ }),
|
|
13085
13928
|
|
|
13929
|
+
/***/ 61844:
|
|
13930
|
+
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
13931
|
+
|
|
13932
|
+
"use strict";
|
|
13933
|
+
|
|
13934
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
13935
|
+
exports.assertUniquePostBuildTestNames = exports.createPostBuildTestJobs = exports.postBuildTestJobName = exports.POST_BUILD_TEST_STAGE = void 0;
|
|
13936
|
+
const createArtifactsConfig_1 = __nccwpck_require__(14149);
|
|
13937
|
+
const cache_1 = __nccwpck_require__(74559);
|
|
13938
|
+
const constants_1 = __nccwpck_require__(5576);
|
|
13939
|
+
const packageManagerInstall_1 = __nccwpck_require__(28419);
|
|
13940
|
+
const types_1 = __nccwpck_require__(35249);
|
|
13941
|
+
const runner_1 = __nccwpck_require__(92146);
|
|
13942
|
+
const jobs_1 = __nccwpck_require__(98521);
|
|
13943
|
+
const utils_1 = __nccwpck_require__(49401);
|
|
13944
|
+
exports.POST_BUILD_TEST_STAGE = "post-build";
|
|
13945
|
+
const postBuildTestJobName = (name) => `🔬 ${name}`;
|
|
13946
|
+
exports.postBuildTestJobName = postBuildTestJobName;
|
|
13947
|
+
const getPostBuildTestsConfig = (context) => {
|
|
13948
|
+
if (context.type === "workspace") {
|
|
13949
|
+
return context.build.config.postBuildTests;
|
|
13950
|
+
}
|
|
13951
|
+
if (context.build.type === "disabled") {
|
|
13952
|
+
return undefined;
|
|
13953
|
+
}
|
|
13954
|
+
return context.build.config.postBuildTests;
|
|
13955
|
+
};
|
|
13956
|
+
/**
|
|
13957
|
+
* whether the build output is a node project, so the job has to install
|
|
13958
|
+
* the dependencies before running the tests
|
|
13959
|
+
*/
|
|
13960
|
+
const isNodeBuild = (context) => {
|
|
13961
|
+
if (context.type === "workspace") {
|
|
13962
|
+
return true; // workspace builds are node builds
|
|
13963
|
+
}
|
|
13964
|
+
if (context.build.type === "fromWorkspace") {
|
|
13965
|
+
return true;
|
|
13966
|
+
}
|
|
13967
|
+
if (context.build.type === "standalone") {
|
|
13968
|
+
return (0, types_1.isOfBuildType)(context.build.config, "node", "node-static", "storybook", "meteor");
|
|
13969
|
+
}
|
|
13970
|
+
return false;
|
|
13971
|
+
};
|
|
13972
|
+
const hasBuildCommand = (buildCommand) => buildCommand !== null && buildCommand !== false;
|
|
13973
|
+
/**
|
|
13974
|
+
* whether the build creates an app build job with artifacts. Rails builds
|
|
13975
|
+
* and builds with `buildCommand: false` only build the docker image.
|
|
13976
|
+
*/
|
|
13977
|
+
const hasBuildArtifacts = (context) => {
|
|
13978
|
+
if (context.type === "workspace") {
|
|
13979
|
+
return hasBuildCommand(context.build.config.buildCommand);
|
|
13980
|
+
}
|
|
13981
|
+
if (context.build.type === "fromWorkspace") {
|
|
13982
|
+
return hasBuildCommand(context.build.workspaceBuildConfig.buildCommand);
|
|
13983
|
+
}
|
|
13984
|
+
if (context.build.type === "standalone") {
|
|
13985
|
+
return (!(0, types_1.isOfBuildType)(context.build.config, "rails") &&
|
|
13986
|
+
hasBuildCommand(context.build.config.buildCommand));
|
|
13987
|
+
}
|
|
13988
|
+
return false;
|
|
13989
|
+
};
|
|
13990
|
+
const getDefaultImage = (context) => {
|
|
13991
|
+
if (context.type === "component" &&
|
|
13992
|
+
context.build.type === "standalone" &&
|
|
13993
|
+
(0, types_1.isOfBuildType)(context.build.config, "custom")) {
|
|
13994
|
+
// custom builds bring their own toolchain image
|
|
13995
|
+
return context.build.config.jobImage;
|
|
13996
|
+
}
|
|
13997
|
+
return (0, runner_1.getRunnerImage)("jobs-default");
|
|
13998
|
+
};
|
|
13999
|
+
/**
|
|
14000
|
+
* jobs for `postBuildTests`: they run after the build in the `post-build`
|
|
14001
|
+
* stage, against the build artifacts, and act as quality gates, so a
|
|
14002
|
+
* failing post-build test blocks the deploy.
|
|
14003
|
+
*/
|
|
14004
|
+
const createPostBuildTestJobs = async (context) => {
|
|
14005
|
+
var _a;
|
|
14006
|
+
// like lint and test: tagged releases deploy what was already tested
|
|
14007
|
+
if (context.trigger === "taggedRelease") {
|
|
14008
|
+
return [];
|
|
14009
|
+
}
|
|
14010
|
+
const tests = Object.entries((_a = getPostBuildTestsConfig(context)) !== null && _a !== void 0 ? _a : {}).filter((entry) => entry[1] !== false);
|
|
14011
|
+
if (tests.length === 0) {
|
|
14012
|
+
return [];
|
|
14013
|
+
}
|
|
14014
|
+
const nodeBuild = isNodeBuild(context);
|
|
14015
|
+
const [packageManagerInstall, nodeCache] = nodeBuild
|
|
14016
|
+
? await Promise.all([
|
|
14017
|
+
(0, packageManagerInstall_1.getPackageManagerInstall)(context),
|
|
14018
|
+
// pull-only: the build job is the designated cache writer
|
|
14019
|
+
(0, cache_1.getNodeCache)(context, "pull"),
|
|
14020
|
+
])
|
|
14021
|
+
: [null, null];
|
|
14022
|
+
// components built in a workspace test the workspace build output
|
|
14023
|
+
const from = context.type === "component" && context.build.type === "fromWorkspace"
|
|
14024
|
+
? { workspace: context.build.workspaceName }
|
|
14025
|
+
: undefined;
|
|
14026
|
+
const buildRequirement = hasBuildArtifacts(context)
|
|
14027
|
+
? { capability: "buildArtifacts", artifacts: true, strict: true, from }
|
|
14028
|
+
: // no build artifacts (e.g. rails): run after the build (docker image)
|
|
14029
|
+
{ capability: "build", artifacts: false, from };
|
|
14030
|
+
return tests.map(([name, test]) => {
|
|
14031
|
+
var _a, _b, _c;
|
|
14032
|
+
const artifactsConfig = (0, createArtifactsConfig_1.createArtifactsConfig)(context.build.dir, test.artifactsReports, test.artifacts);
|
|
14033
|
+
return new jobs_1.CatladderJob({
|
|
14034
|
+
name: (0, exports.postBuildTestJobName)(name),
|
|
14035
|
+
stage: exports.POST_BUILD_TEST_STAGE,
|
|
14036
|
+
envMode: "stagePerEnv",
|
|
14037
|
+
provides: ["qualityGate"],
|
|
14038
|
+
needs: [],
|
|
14039
|
+
requires: [buildRequirement],
|
|
14040
|
+
image: (_a = test.jobImage) !== null && _a !== void 0 ? _a : getDefaultImage(context),
|
|
14041
|
+
services: test.services,
|
|
14042
|
+
// neutral cache declarations, lowered by each backend
|
|
14043
|
+
caches: nodeCache !== null && nodeCache !== void 0 ? nodeCache : undefined,
|
|
14044
|
+
variables: {
|
|
14045
|
+
APP_PATH: context.build.dir,
|
|
14046
|
+
...(context.type === "component"
|
|
14047
|
+
? context.environment.jobOnlyVars.build.envVars
|
|
14048
|
+
: {}),
|
|
14049
|
+
...((_b = test.vars) !== null && _b !== void 0 ? _b : {}),
|
|
14050
|
+
},
|
|
14051
|
+
runnerVariables: {
|
|
14052
|
+
...(nodeBuild ? constants_1.NODE_RUNNER_BUILD_VARIABLES : {}),
|
|
14053
|
+
...((_c = test.runnerVariables) !== null && _c !== void 0 ? _c : {}),
|
|
14054
|
+
},
|
|
14055
|
+
script: [
|
|
14056
|
+
...(nodeBuild ? (0, packageManagerInstall_1.ensureNodeVersion)(context) : []),
|
|
14057
|
+
`cd ${context.build.dir}`,
|
|
14058
|
+
...(packageManagerInstall !== null && packageManagerInstall !== void 0 ? packageManagerInstall : []),
|
|
14059
|
+
...(0, utils_1.ensureArray)(test.command),
|
|
14060
|
+
],
|
|
14061
|
+
allow_failure: test.allowFailure,
|
|
14062
|
+
// reports and screenshots are most needed when the tests fail
|
|
14063
|
+
...(artifactsConfig
|
|
14064
|
+
? {
|
|
14065
|
+
artifacts: {
|
|
14066
|
+
when: "always",
|
|
14067
|
+
...artifactsConfig.artifacts,
|
|
14068
|
+
},
|
|
14069
|
+
}
|
|
14070
|
+
: {}),
|
|
14071
|
+
});
|
|
14072
|
+
});
|
|
14073
|
+
};
|
|
14074
|
+
exports.createPostBuildTestJobs = createPostBuildTestJobs;
|
|
14075
|
+
const jobIdOf = (jobName) => jobName
|
|
14076
|
+
.toLowerCase()
|
|
14077
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
14078
|
+
.replace(/^-+|-+$/g, "");
|
|
14079
|
+
/**
|
|
14080
|
+
* post-build test names are chosen by the user. Github job ids drop the
|
|
14081
|
+
* emoji of the job name, so e.g. a post-build test named `test` would
|
|
14082
|
+
* clash with the `🧪 test` job.
|
|
14083
|
+
*/
|
|
14084
|
+
const assertUniquePostBuildTestNames = (context, postBuildTestJobs, otherJobs) => {
|
|
14085
|
+
const seenIds = new Set(otherJobs.map((job) => jobIdOf(job.name)));
|
|
14086
|
+
// also catches two post-build tests that only differ in case or
|
|
14087
|
+
// punctuation (e.g. `e2e` and `E2E`)
|
|
14088
|
+
const clash = postBuildTestJobs.find((job) => {
|
|
14089
|
+
const id = jobIdOf(job.name);
|
|
14090
|
+
const isClash = seenIds.has(id);
|
|
14091
|
+
seenIds.add(id);
|
|
14092
|
+
return isClash;
|
|
14093
|
+
});
|
|
14094
|
+
if (clash) {
|
|
14095
|
+
throw new Error(`${context.name}: post-build test '${clash.name}' clashes with another job of the same name, please rename it in postBuildTests`);
|
|
14096
|
+
}
|
|
14097
|
+
};
|
|
14098
|
+
exports.assertUniquePostBuildTestNames = assertUniquePostBuildTestNames;
|
|
14099
|
+
//# sourceMappingURL=createPostBuildTestJobs.js.map
|
|
14100
|
+
|
|
14101
|
+
/***/ }),
|
|
14102
|
+
|
|
14103
|
+
/***/ 28458:
|
|
14104
|
+
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
|
14105
|
+
|
|
14106
|
+
"use strict";
|
|
14107
|
+
|
|
14108
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14109
|
+
if (k2 === undefined) k2 = k;
|
|
14110
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14111
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14112
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14113
|
+
}
|
|
14114
|
+
Object.defineProperty(o, k2, desc);
|
|
14115
|
+
}) : (function(o, m, k, k2) {
|
|
14116
|
+
if (k2 === undefined) k2 = k;
|
|
14117
|
+
o[k2] = m[k];
|
|
14118
|
+
}));
|
|
14119
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14120
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
14121
|
+
};
|
|
14122
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
14123
|
+
__exportStar(__nccwpck_require__(92967), exports);
|
|
14124
|
+
__exportStar(__nccwpck_require__(61844), exports);
|
|
14125
|
+
//# sourceMappingURL=index.js.map
|
|
14126
|
+
|
|
14127
|
+
/***/ }),
|
|
14128
|
+
|
|
14129
|
+
/***/ 92967:
|
|
14130
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
14131
|
+
|
|
14132
|
+
"use strict";
|
|
14133
|
+
|
|
14134
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
14135
|
+
//# sourceMappingURL=types.js.map
|
|
14136
|
+
|
|
14137
|
+
/***/ }),
|
|
14138
|
+
|
|
13086
14139
|
/***/ 26041:
|
|
13087
14140
|
/***/ ((__unused_webpack_module, exports) => {
|
|
13088
14141
|
|
|
@@ -13108,6 +14161,143 @@ exports.getReleaseMethod = getReleaseMethod;
|
|
|
13108
14161
|
|
|
13109
14162
|
/***/ }),
|
|
13110
14163
|
|
|
14164
|
+
/***/ 67071:
|
|
14165
|
+
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
|
14166
|
+
|
|
14167
|
+
"use strict";
|
|
14168
|
+
|
|
14169
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14170
|
+
if (k2 === undefined) k2 = k;
|
|
14171
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14172
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14173
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14174
|
+
}
|
|
14175
|
+
Object.defineProperty(o, k2, desc);
|
|
14176
|
+
}) : (function(o, m, k, k2) {
|
|
14177
|
+
if (k2 === undefined) k2 = k;
|
|
14178
|
+
o[k2] = m[k];
|
|
14179
|
+
}));
|
|
14180
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14181
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
14182
|
+
};
|
|
14183
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
14184
|
+
exports.isReviewDeliveryJob = exports.GITLAB_DRAFT_MR_CONDITION = exports.getGitlabLabelRegex = exports.getReviewAppsSwitchLabel = exports.isReviewDeployGated = exports.getReviewAppsConfig = void 0;
|
|
14185
|
+
const autoStop_1 = __nccwpck_require__(72792);
|
|
14186
|
+
const reviewAppsPolicy_1 = __nccwpck_require__(49821);
|
|
14187
|
+
__exportStar(__nccwpck_require__(49821), exports);
|
|
14188
|
+
const getReviewAppsConfig = (config) => {
|
|
14189
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
14190
|
+
return ({
|
|
14191
|
+
deploy: (_b = (_a = config.reviewApps) === null || _a === void 0 ? void 0 : _a.deploy) !== null && _b !== void 0 ? _b : "auto",
|
|
14192
|
+
// single colon on purpose: gitlab treats `a::b` labels as scoped and
|
|
14193
|
+
// mutually exclusive, which would make this label and the
|
|
14194
|
+
// `catladder::pin-review` pin label displace each other
|
|
14195
|
+
label: (_d = (_c = config.reviewApps) === null || _c === void 0 ? void 0 : _c.label) !== null && _d !== void 0 ? _d : reviewAppsPolicy_1.DEFAULT_REVIEW_APP_LABEL,
|
|
14196
|
+
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,
|
|
14197
|
+
drafts: (_h = (_g = config.reviewApps) === null || _g === void 0 ? void 0 : _g.drafts) !== null && _h !== void 0 ? _h : "likeReady",
|
|
14198
|
+
});
|
|
14199
|
+
};
|
|
14200
|
+
exports.getReviewAppsConfig = getReviewAppsConfig;
|
|
14201
|
+
/**
|
|
14202
|
+
* whether the review deploys sit behind the per-MR/PR deploy switch
|
|
14203
|
+
* instead of running in every pipeline
|
|
14204
|
+
*/
|
|
14205
|
+
const isReviewDeployGated = (config) => config.deploy !== "auto" || config.drafts === "checksOnly";
|
|
14206
|
+
exports.isReviewDeployGated = isReviewDeployGated;
|
|
14207
|
+
/**
|
|
14208
|
+
* the label whose change switches the review apps on (`labeled` on
|
|
14209
|
+
* opt-in, `unlabeled` on opt-out) or off (the inverse), if the mode is
|
|
14210
|
+
* label-driven at all
|
|
14211
|
+
*/
|
|
14212
|
+
const getReviewAppsSwitchLabel = (config) => {
|
|
14213
|
+
switch (config.deploy) {
|
|
14214
|
+
case "optIn":
|
|
14215
|
+
return { label: config.label, deployOn: "labeled", stopOn: "unlabeled" };
|
|
14216
|
+
case "optOut":
|
|
14217
|
+
return {
|
|
14218
|
+
label: config.skipLabel,
|
|
14219
|
+
deployOn: "unlabeled",
|
|
14220
|
+
stopOn: "labeled",
|
|
14221
|
+
};
|
|
14222
|
+
default:
|
|
14223
|
+
return undefined;
|
|
14224
|
+
}
|
|
14225
|
+
};
|
|
14226
|
+
exports.getReviewAppsSwitchLabel = getReviewAppsSwitchLabel;
|
|
14227
|
+
/**
|
|
14228
|
+
* gitlab: matches a label inside `$CI_MERGE_REQUEST_LABELS`
|
|
14229
|
+
* (comma-separated) — anchored so `x` never matches `no-x`
|
|
14230
|
+
*/
|
|
14231
|
+
const getGitlabLabelRegex = (label) => `/${(0, autoStop_1.getPinLabelRegexSource)(label)}/`;
|
|
14232
|
+
exports.getGitlabLabelRegex = getGitlabLabelRegex;
|
|
14233
|
+
/**
|
|
14234
|
+
* gitlab: a draft MR, by its title prefix — the only draft signal every
|
|
14235
|
+
* gitlab version exposes to rules
|
|
14236
|
+
*/
|
|
14237
|
+
exports.GITLAB_DRAFT_MR_CONDITION = "$CI_MERGE_REQUEST_TITLE =~ /^\\s*(\\[draft\\]|\\(draft\\)|draft:)/i";
|
|
14238
|
+
/**
|
|
14239
|
+
* whether a job belongs to the delivery chain of a review app — the
|
|
14240
|
+
* docker image, the deploy and the post-deploy verification. These jobs
|
|
14241
|
+
* sit behind the review deploy switch; quality jobs (lint, test, audit)
|
|
14242
|
+
* and the app build never do.
|
|
14243
|
+
*/
|
|
14244
|
+
const isReviewDeliveryJob = (context, job) => {
|
|
14245
|
+
var _a;
|
|
14246
|
+
return context.type === "component" &&
|
|
14247
|
+
context.environment.instance.type === "review" &&
|
|
14248
|
+
(job.stage === "deploy" ||
|
|
14249
|
+
job.stage === "verify" ||
|
|
14250
|
+
((_a = job.provides) !== null && _a !== void 0 ? _a : []).includes("dockerImage"));
|
|
14251
|
+
};
|
|
14252
|
+
exports.isReviewDeliveryJob = isReviewDeliveryJob;
|
|
14253
|
+
//# sourceMappingURL=reviewApps.js.map
|
|
14254
|
+
|
|
14255
|
+
/***/ }),
|
|
14256
|
+
|
|
14257
|
+
/***/ 49821:
|
|
14258
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
14259
|
+
|
|
14260
|
+
"use strict";
|
|
14261
|
+
|
|
14262
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
14263
|
+
exports.shouldAutoDeployReviewApps = exports.DEFAULT_NO_REVIEW_APP_LABEL = exports.DEFAULT_REVIEW_APP_LABEL = void 0;
|
|
14264
|
+
exports.DEFAULT_REVIEW_APP_LABEL = "catladder:review-app";
|
|
14265
|
+
exports.DEFAULT_NO_REVIEW_APP_LABEL = "catladder:no-review-app";
|
|
14266
|
+
/**
|
|
14267
|
+
* whether the review apps of an MR/PR in the given state deploy
|
|
14268
|
+
* automatically. An explicit request (playing the gate job, dispatching
|
|
14269
|
+
* the deploy workflow) always deploys and doesn't consult this.
|
|
14270
|
+
*/
|
|
14271
|
+
const shouldAutoDeployReviewApps = (config, { labels, draft }) => {
|
|
14272
|
+
if (draft && config.drafts !== "likeReady") {
|
|
14273
|
+
return { deploy: false, reason: "drafts don't deploy review apps" };
|
|
14274
|
+
}
|
|
14275
|
+
switch (config.deploy) {
|
|
14276
|
+
case "auto":
|
|
14277
|
+
return { deploy: true, reason: "review apps deploy automatically" };
|
|
14278
|
+
case "manual":
|
|
14279
|
+
return {
|
|
14280
|
+
deploy: false,
|
|
14281
|
+
reason: "review apps only deploy on request (deploy: manual)",
|
|
14282
|
+
};
|
|
14283
|
+
case "optIn":
|
|
14284
|
+
return labels.includes(config.label)
|
|
14285
|
+
? { deploy: true, reason: `the '${config.label}' label is set` }
|
|
14286
|
+
: { deploy: false, reason: `the '${config.label}' label is not set` };
|
|
14287
|
+
case "optOut":
|
|
14288
|
+
return labels.includes(config.skipLabel)
|
|
14289
|
+
? { deploy: false, reason: `the '${config.skipLabel}' label is set` }
|
|
14290
|
+
: {
|
|
14291
|
+
deploy: true,
|
|
14292
|
+
reason: `the '${config.skipLabel}' label is not set`,
|
|
14293
|
+
};
|
|
14294
|
+
}
|
|
14295
|
+
};
|
|
14296
|
+
exports.shouldAutoDeployReviewApps = shouldAutoDeployReviewApps;
|
|
14297
|
+
//# sourceMappingURL=reviewAppsPolicy.js.map
|
|
14298
|
+
|
|
14299
|
+
/***/ }),
|
|
14300
|
+
|
|
13111
14301
|
/***/ 78503:
|
|
13112
14302
|
/***/ ((__unused_webpack_module, exports) => {
|
|
13113
14303
|
|
|
@@ -13416,6 +14606,7 @@ exports.BASE_STAGES = [
|
|
|
13416
14606
|
"setup",
|
|
13417
14607
|
"test",
|
|
13418
14608
|
"build",
|
|
14609
|
+
"post-build",
|
|
13419
14610
|
"deploy",
|
|
13420
14611
|
"verify",
|
|
13421
14612
|
"agents",
|