@catladder/cli 0.0.0-next-b3e567e8 → 0.0.0-next-78f04d3f
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 +23 -0
- package/dist/apps/cli/src/catci.js.map +1 -1
- package/dist/apps/cli/src/release/githubQueuedRelease.d.ts +14 -0
- package/dist/apps/cli/src/release/githubQueuedRelease.js +152 -0
- package/dist/apps/cli/src/release/githubQueuedRelease.js.map +1 -0
- package/dist/bundles/catci/index.js +3 -3
- package/dist/bundles/catenv/index.js +516 -136
- package/dist/bundles/cli/index.js +516 -136
- package/dist/bundles/skills/catladder-builds/SKILL.md +49 -2
- package/dist/bundles/skills/catladder-builds/references/build-types.md +2 -2
- package/dist/bundles/skills/catladder-pipelines/SKILL.md +13 -3
- package/dist/bundles/skills/catladder-releases/SKILL.md +13 -2
- package/dist/packages/pipeline/src/backends/github/GithubBackend.js +11 -4
- package/dist/packages/pipeline/src/backends/github/GithubBackend.js.map +1 -1
- package/dist/packages/pipeline/src/backends/github/githubReleaseJobs.d.ts +18 -2
- package/dist/packages/pipeline/src/backends/github/githubReleaseJobs.js +92 -10
- package/dist/packages/pipeline/src/backends/github/githubReleaseJobs.js.map +1 -1
- package/dist/packages/pipeline/src/backends/gitlab/GitlabBackend.js +35 -9
- package/dist/packages/pipeline/src/backends/gitlab/GitlabBackend.js.map +1 -1
- package/dist/packages/pipeline/src/backends/gitlab/gitlabReleaseJobs.d.ts +285 -3
- package/dist/packages/pipeline/src/backends/gitlab/gitlabReleaseJobs.js +142 -54
- package/dist/packages/pipeline/src/backends/gitlab/gitlabReleaseJobs.js.map +1 -1
- package/dist/packages/pipeline/src/build/types.d.ts +6 -5
- package/dist/packages/pipeline/src/build/types.js.map +1 -1
- package/dist/packages/pipeline/src/customImages/hash.d.ts +9 -0
- package/dist/packages/pipeline/src/customImages/hash.js +41 -16
- package/dist/packages/pipeline/src/customImages/hash.js.map +1 -1
- package/dist/packages/pipeline/src/customImages/jobImagesPlan.d.ts +23 -5
- package/dist/packages/pipeline/src/customImages/jobImagesPlan.js +177 -43
- package/dist/packages/pipeline/src/customImages/jobImagesPlan.js.map +1 -1
- package/dist/packages/pipeline/src/customImages/projectImages.d.ts +67 -0
- package/dist/packages/pipeline/src/customImages/projectImages.js +9 -0
- package/dist/packages/pipeline/src/customImages/projectImages.js.map +1 -0
- package/dist/packages/pipeline/src/deploy/types/custom.d.ts +2 -2
- package/dist/packages/pipeline/src/deploy/types/index.d.ts +4 -4
- package/dist/packages/pipeline/src/deploy/types/pages.d.ts +2 -2
- 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/types/config.d.ts +8 -0
- package/dist/packages/pipeline/src/types/config.js.map +1 -1
- package/dist/packages/pipeline/src/types/github-types.d.ts +1 -0
- package/dist/packages/pipeline/src/types/jobs.d.ts +2 -1
- package/dist/packages/pipeline/src/types/jobs.js.map +1 -1
- package/dist/packages/pipeline/src/types/release.d.ts +7 -1
- package/dist/skills/catladder-builds/SKILL.md +49 -2
- package/dist/skills/catladder-builds/references/build-types.md +2 -2
- package/dist/skills/catladder-pipelines/SKILL.md +13 -3
- package/dist/skills/catladder-releases/SKILL.md +13 -2
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/catci.ts +30 -0
- package/src/release/githubQueuedRelease.ts +173 -0
|
@@ -2321,7 +2321,7 @@ class GithubBackend {
|
|
|
2321
2321
|
await (0, promises_1.rm)(scriptFiles_1.GITHUB_SCRIPTS_FOLDER, { recursive: true, force: true });
|
|
2322
2322
|
}
|
|
2323
2323
|
async createFiles(config) {
|
|
2324
|
-
const images = this.createImagesPlan();
|
|
2324
|
+
const images = this.createImagesPlan(config);
|
|
2325
2325
|
const scripts = new scriptFiles_1.GithubScriptFiles();
|
|
2326
2326
|
const workflows = await this.createWorkflows(config, images, scripts);
|
|
2327
2327
|
return [
|
|
@@ -2336,13 +2336,13 @@ class GithubBackend {
|
|
|
2336
2336
|
...(0, shippedCatci_1.getCatciGeneratedFiles)(),
|
|
2337
2337
|
];
|
|
2338
2338
|
}
|
|
2339
|
-
createImagesPlan() {
|
|
2340
|
-
return new jobImagesPlan_1.JobImagesPlan(this.type);
|
|
2339
|
+
createImagesPlan(config) {
|
|
2340
|
+
return new jobImagesPlan_1.JobImagesPlan(this.type, config.images);
|
|
2341
2341
|
}
|
|
2342
2342
|
/**
|
|
2343
2343
|
* all workflows keyed by file name, mainly for testing purposes
|
|
2344
2344
|
*/
|
|
2345
|
-
async createWorkflows(config, images = this.createImagesPlan(), scripts = new scriptFiles_1.GithubScriptFiles()) {
|
|
2345
|
+
async createWorkflows(config, images = this.createImagesPlan(config), scripts = new scriptFiles_1.GithubScriptFiles()) {
|
|
2346
2346
|
var _a, _b, _c, _d;
|
|
2347
2347
|
const workflows = {};
|
|
2348
2348
|
// per-pipeline-type variables (pipelines.github.runnerVariables);
|
|
@@ -2463,6 +2463,13 @@ class GithubBackend {
|
|
|
2463
2463
|
manualJobConditions[id] = `inputs.job == '${id}'`;
|
|
2464
2464
|
manualDispatchOptions.push(id);
|
|
2465
2465
|
}
|
|
2466
|
+
// manual release mode: the workflow that runs a queued release
|
|
2467
|
+
// once the main workflow completed green
|
|
2468
|
+
const releaseOnGreen = (0, githubReleaseJobs_1.getGithubReleaseOnGreenWorkflow)(config, images, TRIGGER_WORKFLOWS.mainBranch.name, workflowEnv);
|
|
2469
|
+
if (releaseOnGreen) {
|
|
2470
|
+
workflows[`${GENERATED_FILE_PREFIX}release-on-green.yml`] =
|
|
2471
|
+
releaseOnGreen;
|
|
2472
|
+
}
|
|
2466
2473
|
}
|
|
2467
2474
|
if (Object.keys(jobs).length > 0) {
|
|
2468
2475
|
workflows[`${GENERATED_FILE_PREFIX}${workflowFileName(trigger)}`] = {
|
|
@@ -3007,16 +3014,21 @@ exports.getUploadProviderIds = getUploadProviderIds;
|
|
|
3007
3014
|
"use strict";
|
|
3008
3015
|
|
|
3009
3016
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
3010
|
-
exports.getGithubReleaseCheckJobs = exports.getGithubReleaseJobs = void 0;
|
|
3017
|
+
exports.getGithubReleaseCheckJobs = exports.getGithubReleaseOnGreenWorkflow = exports.getGithubReleaseJobs = void 0;
|
|
3011
3018
|
const createGithubJobs_1 = __nccwpck_require__(57877);
|
|
3012
3019
|
const release_1 = __nccwpck_require__(38904);
|
|
3013
|
-
const
|
|
3020
|
+
const shippedCatci_1 = __nccwpck_require__(79024);
|
|
3021
|
+
const CATCI = `${shippedCatci_1.GENERATED_CATCI_FOLDER}/index.js`;
|
|
3022
|
+
const createReleaseJob = (config, images, options = {}) => {
|
|
3023
|
+
var _a;
|
|
3014
3024
|
const method = (0, release_1.getReleaseMethod)(config);
|
|
3015
3025
|
const resolved = images.resolveRef({ catladderImage: method.image });
|
|
3016
|
-
const imageNeeds =
|
|
3026
|
+
const imageNeeds = !options.skipImageNeeds &&
|
|
3027
|
+
resolved.need &&
|
|
3028
|
+
typeof resolved.need === "object"
|
|
3017
3029
|
? [(0, createGithubJobs_1.githubGlobalJobId)(resolved.need.job)]
|
|
3018
3030
|
: [];
|
|
3019
|
-
const allNeeds = [...imageNeeds, ...(needs !== null &&
|
|
3031
|
+
const allNeeds = [...imageNeeds, ...((_a = options.needs) !== null && _a !== void 0 ? _a : [])];
|
|
3020
3032
|
return {
|
|
3021
3033
|
name: "create release",
|
|
3022
3034
|
"runs-on": "ubuntu-latest",
|
|
@@ -3045,7 +3057,7 @@ const createReleaseJob = (config, images, needs, extraEnv) => {
|
|
|
3045
3057
|
...(allNeeds.length > 0 ? { needs: allNeeds } : {}),
|
|
3046
3058
|
env: {
|
|
3047
3059
|
GITHUB_TOKEN: "${{ github.token }}",
|
|
3048
|
-
...extraEnv,
|
|
3060
|
+
...options.extraEnv,
|
|
3049
3061
|
},
|
|
3050
3062
|
steps: [
|
|
3051
3063
|
{
|
|
@@ -3055,8 +3067,21 @@ const createReleaseJob = (config, images, needs, extraEnv) => {
|
|
|
3055
3067
|
// tags since the last release) to determine the version
|
|
3056
3068
|
with: { "fetch-depth": 0 },
|
|
3057
3069
|
},
|
|
3070
|
+
...(options.queueCheck
|
|
3071
|
+
? [
|
|
3072
|
+
{
|
|
3073
|
+
name: "check main workflow",
|
|
3074
|
+
id: "queue",
|
|
3075
|
+
run: `node ${CATCI} release github-queue-check`,
|
|
3076
|
+
shell: "bash",
|
|
3077
|
+
},
|
|
3078
|
+
]
|
|
3079
|
+
: []),
|
|
3058
3080
|
{
|
|
3059
3081
|
name: "create release",
|
|
3082
|
+
...(options.queueCheck
|
|
3083
|
+
? { if: "steps.queue.outputs.queued != 'true'" }
|
|
3084
|
+
: {}),
|
|
3060
3085
|
run: method.script,
|
|
3061
3086
|
shell: "bash",
|
|
3062
3087
|
},
|
|
@@ -3070,7 +3095,10 @@ const createReleaseJob = (config, images, needs, extraEnv) => {
|
|
|
3070
3095
|
* workflow after all other jobs; a force variant goes to the manual
|
|
3071
3096
|
* tasks workflow
|
|
3072
3097
|
* - otherwise (manual, the default): the release job goes to the
|
|
3073
|
-
* manual tasks workflow
|
|
3098
|
+
* manual tasks workflow. Its queue-check step releases right away on
|
|
3099
|
+
* a green main run and otherwise queues the release for the
|
|
3100
|
+
* release-on-green workflow (see getGithubReleaseOnGreenWorkflow) —
|
|
3101
|
+
* the counterpart of gitlab's queue button + executor pair.
|
|
3074
3102
|
*/
|
|
3075
3103
|
const getGithubReleaseJobs = (config, mainWorkflowJobIds, images) => {
|
|
3076
3104
|
var _a;
|
|
@@ -3078,28 +3106,89 @@ const getGithubReleaseJobs = (config, mainWorkflowJobIds, images) => {
|
|
|
3078
3106
|
if (((_a = config.releases) === null || _a === void 0 ? void 0 : _a.when) === "auto") {
|
|
3079
3107
|
return {
|
|
3080
3108
|
main: {
|
|
3081
|
-
"create-release": createReleaseJob(config, images,
|
|
3109
|
+
"create-release": createReleaseJob(config, images, {
|
|
3110
|
+
needs: mainWorkflowJobIds,
|
|
3111
|
+
}),
|
|
3082
3112
|
},
|
|
3083
3113
|
manual: {
|
|
3084
|
-
"force-create-release": createReleaseJob(config, images,
|
|
3114
|
+
"force-create-release": createReleaseJob(config, images, {
|
|
3115
|
+
extraEnv: method.forceReleaseVariables,
|
|
3116
|
+
}),
|
|
3085
3117
|
},
|
|
3086
3118
|
};
|
|
3087
3119
|
}
|
|
3088
3120
|
return {
|
|
3089
3121
|
main: {},
|
|
3090
3122
|
manual: {
|
|
3091
|
-
"create-release": createReleaseJob(config, images),
|
|
3123
|
+
"create-release": createReleaseJob(config, images, { queueCheck: true }),
|
|
3092
3124
|
// an explicit force option wherever the method distinguishes it
|
|
3093
3125
|
// (e.g. changesets: release a patch bump without changesets)
|
|
3094
3126
|
...(method.forceReleaseVariables
|
|
3095
3127
|
? {
|
|
3096
|
-
"force-create-release": createReleaseJob(config, images,
|
|
3128
|
+
"force-create-release": createReleaseJob(config, images, {
|
|
3129
|
+
extraEnv: method.forceReleaseVariables,
|
|
3130
|
+
}),
|
|
3097
3131
|
}
|
|
3098
3132
|
: {}),
|
|
3099
3133
|
},
|
|
3100
3134
|
};
|
|
3101
3135
|
};
|
|
3102
3136
|
exports.getGithubReleaseJobs = getGithubReleaseJobs;
|
|
3137
|
+
/**
|
|
3138
|
+
* the release-on-green workflow (manual release mode only): runs on
|
|
3139
|
+
* every completed main workflow run. A lightweight host job checks the
|
|
3140
|
+
* queue marker (written by the create-release task while the main
|
|
3141
|
+
* workflow was still running) — most runs have nothing queued and end
|
|
3142
|
+
* after that single quick job. When the marker matches the completed
|
|
3143
|
+
* run, the containerized release job creates the release.
|
|
3144
|
+
*
|
|
3145
|
+
* No image-build jobs here: the release image was ensured by the main
|
|
3146
|
+
* workflow run that just completed (same commit, same content-hashed
|
|
3147
|
+
* tag).
|
|
3148
|
+
*/
|
|
3149
|
+
const getGithubReleaseOnGreenWorkflow = (config, images, mainWorkflowName, workflowEnv) => {
|
|
3150
|
+
var _a;
|
|
3151
|
+
if (((_a = config.releases) === null || _a === void 0 ? void 0 : _a.when) === "auto") {
|
|
3152
|
+
return null;
|
|
3153
|
+
}
|
|
3154
|
+
return {
|
|
3155
|
+
name: "catladder release on green",
|
|
3156
|
+
on: {
|
|
3157
|
+
workflow_run: {
|
|
3158
|
+
workflows: [mainWorkflowName],
|
|
3159
|
+
types: ["completed"],
|
|
3160
|
+
},
|
|
3161
|
+
},
|
|
3162
|
+
env: workflowEnv,
|
|
3163
|
+
jobs: {
|
|
3164
|
+
guard: {
|
|
3165
|
+
name: "check queued release",
|
|
3166
|
+
"runs-on": "ubuntu-latest",
|
|
3167
|
+
// consuming the queue marker is a ref deletion
|
|
3168
|
+
permissions: { contents: "write" },
|
|
3169
|
+
env: { GITHUB_TOKEN: "${{ github.token }}" },
|
|
3170
|
+
outputs: { release: "${{ steps.guard.outputs.release }}" },
|
|
3171
|
+
steps: [
|
|
3172
|
+
{ name: "Checkout", uses: "actions/checkout@v4" },
|
|
3173
|
+
{
|
|
3174
|
+
name: "check queued release",
|
|
3175
|
+
id: "guard",
|
|
3176
|
+
run: `node ${CATCI} release github-queued-guard "\${{ github.event.workflow_run.head_sha }}" "\${{ github.event.workflow_run.conclusion }}"`,
|
|
3177
|
+
shell: "bash",
|
|
3178
|
+
},
|
|
3179
|
+
],
|
|
3180
|
+
},
|
|
3181
|
+
"create-release": {
|
|
3182
|
+
...createReleaseJob(config, images, {
|
|
3183
|
+
needs: ["guard"],
|
|
3184
|
+
skipImageNeeds: true,
|
|
3185
|
+
}),
|
|
3186
|
+
if: "${{ needs.guard.outputs.release == 'true' }}",
|
|
3187
|
+
},
|
|
3188
|
+
},
|
|
3189
|
+
};
|
|
3190
|
+
};
|
|
3191
|
+
exports.getGithubReleaseOnGreenWorkflow = getGithubReleaseOnGreenWorkflow;
|
|
3103
3192
|
/**
|
|
3104
3193
|
* the informational MR/PR check job of the release method (e.g. the
|
|
3105
3194
|
* changeset check), added to the mr workflow. continue-on-error keeps
|
|
@@ -3294,6 +3383,7 @@ const config_1 = __nccwpck_require__(17860);
|
|
|
3294
3383
|
const createAllJobs_1 = __nccwpck_require__(54114);
|
|
3295
3384
|
const jobImagesPlan_1 = __nccwpck_require__(47864);
|
|
3296
3385
|
const shippedCatci_1 = __nccwpck_require__(79024);
|
|
3386
|
+
const release_1 = __nccwpck_require__(38904);
|
|
3297
3387
|
const index_1 = __nccwpck_require__(68906);
|
|
3298
3388
|
const createGitlabJobs_1 = __nccwpck_require__(41509);
|
|
3299
3389
|
const createGitlabPipeline_1 = __nccwpck_require__(94411);
|
|
@@ -3311,7 +3401,7 @@ class GitlabBackend {
|
|
|
3311
3401
|
await (0, promises_1.rm)(this.generatedFolder, { force: true, recursive: true });
|
|
3312
3402
|
}
|
|
3313
3403
|
async createFiles(config) {
|
|
3314
|
-
const images = this.createImagesPlan();
|
|
3404
|
+
const images = this.createImagesPlan(config);
|
|
3315
3405
|
const includes = await this.createIncludes(config, images);
|
|
3316
3406
|
const mainFile = {
|
|
3317
3407
|
path: ".gitlab-ci.yml",
|
|
@@ -3328,15 +3418,15 @@ class GitlabBackend {
|
|
|
3328
3418
|
...(0, shippedCatci_1.getCatciGeneratedFiles)(),
|
|
3329
3419
|
];
|
|
3330
3420
|
}
|
|
3331
|
-
createImagesPlan() {
|
|
3332
|
-
return new jobImagesPlan_1.JobImagesPlan(this.type);
|
|
3421
|
+
createImagesPlan(config) {
|
|
3422
|
+
return new jobImagesPlan_1.JobImagesPlan(this.type, config.images);
|
|
3333
3423
|
}
|
|
3334
3424
|
/**
|
|
3335
3425
|
* the complete pipeline as one object (all includes merged into one),
|
|
3336
3426
|
* mainly for testing purposes
|
|
3337
3427
|
*/
|
|
3338
3428
|
async createCompletePipeline(config) {
|
|
3339
|
-
const includes = await this.createIncludes(config, this.createImagesPlan());
|
|
3429
|
+
const includes = await this.createIncludes(config, this.createImagesPlan(config));
|
|
3340
3430
|
return includes.reduce((acc, { content }) => {
|
|
3341
3431
|
return {
|
|
3342
3432
|
...acc,
|
|
@@ -3380,12 +3470,29 @@ class GitlabBackend {
|
|
|
3380
3470
|
}
|
|
3381
3471
|
async createPipeline(config, images) {
|
|
3382
3472
|
const stages = (0, getPipelineStages_1.getPipelineStages)(config);
|
|
3383
|
-
// release
|
|
3384
|
-
//
|
|
3385
|
-
//
|
|
3386
|
-
|
|
3473
|
+
// register the release image first: the per-trigger job creation
|
|
3474
|
+
// below emits the build jobs of all images registered so far. The
|
|
3475
|
+
// release jobs themselves are created afterwards — they need the
|
|
3476
|
+
// main-branch job list for the queued-release executor's needs.
|
|
3477
|
+
images.resolveRef({ catladderImage: (0, release_1.getReleaseMethod)(config).image });
|
|
3387
3478
|
// for all triggers create jobs and add base rules
|
|
3388
|
-
const
|
|
3479
|
+
const jobsPerTrigger = await Promise.all(config_1.ALL_PIPELINE_TRIGGERS.map(async (trigger) => ({
|
|
3480
|
+
trigger,
|
|
3481
|
+
jobs: await (0, createGitlabJobs_1.createGitlabJobs)(await (0, createAllJobs_1.createAllJobs)({ config, trigger, pipelineType: this.type }), images, getGitlabRulesForTrigger(trigger)),
|
|
3482
|
+
})));
|
|
3483
|
+
const allJobsPerTrigger = jobsPerTrigger.flatMap(({ jobs }) => jobs);
|
|
3484
|
+
// the jobs the queued-release executor waits for: everything that
|
|
3485
|
+
// runs automatically in a main-branch pipeline. Manual jobs (stop,
|
|
3486
|
+
// rollback, gates) are excluded — gitlab skips a job whose needs
|
|
3487
|
+
// include an unplayed manual job.
|
|
3488
|
+
const mainBranchJobNames = [
|
|
3489
|
+
...new Set(jobsPerTrigger
|
|
3490
|
+
.filter(({ trigger }) => trigger === "mainBranch")
|
|
3491
|
+
.flatMap(({ jobs }) => jobs)
|
|
3492
|
+
.filter(({ gitlabJob }) => !isManualGitlabJob(gitlabJob))
|
|
3493
|
+
.map(({ name }) => name)),
|
|
3494
|
+
];
|
|
3495
|
+
const releaseJobs = (0, gitlabReleaseJobs_1.getGitlabReleaseJobs)(config, images, mainBranchJobNames);
|
|
3389
3496
|
const allWorkspaceJobs = allJobsPerTrigger
|
|
3390
3497
|
.filter((j) => { var _a; return ((_a = j.context) === null || _a === void 0 ? void 0 : _a.type) === "workspace"; }) // sort by componentName in the same order as they appear in the config
|
|
3391
3498
|
// this is purely for better readability in git diffs when you add new components
|
|
@@ -3451,6 +3558,14 @@ class GitlabBackend {
|
|
|
3451
3558
|
}
|
|
3452
3559
|
}
|
|
3453
3560
|
exports.GitlabBackend = GitlabBackend;
|
|
3561
|
+
/**
|
|
3562
|
+
* whether a job can end up waiting for a human in a main-branch
|
|
3563
|
+
* pipeline (catladder expresses manual-ness only through rules)
|
|
3564
|
+
*/
|
|
3565
|
+
function isManualGitlabJob(job) {
|
|
3566
|
+
var _a;
|
|
3567
|
+
return ((_a = job.rules) !== null && _a !== void 0 ? _a : []).some((rule) => rule.when === "manual");
|
|
3568
|
+
}
|
|
3454
3569
|
function getGitlabRulesForTrigger(trigger) {
|
|
3455
3570
|
// mainBranch: on push to main branch except it's a release commit
|
|
3456
3571
|
// mr: on merge request
|
|
@@ -3983,91 +4098,179 @@ exports.getPipelineStages = getPipelineStages;
|
|
|
3983
4098
|
"use strict";
|
|
3984
4099
|
|
|
3985
4100
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
3986
|
-
exports.getGitlabReleaseJobs = void 0;
|
|
4101
|
+
exports.getGitlabReleaseJobs = exports.RELEASE_EXECUTOR_JOB_NAME = exports.RELEASE_BUTTON_JOB_NAME = void 0;
|
|
3987
4102
|
const rules_1 = __nccwpck_require__(35086);
|
|
3988
4103
|
const release_1 = __nccwpck_require__(38904);
|
|
3989
4104
|
const EXPIRED_TOKEN_HELP = "echo '👉 If this job failed with access denied, the project access token might be invald - run `project-renew-token` in catladder CLI to fix.'";
|
|
4105
|
+
/**
|
|
4106
|
+
* gitlab rejects jobs with more than 50 `needs:` entries (default
|
|
4107
|
+
* limit). Pipelines beyond that fall back to the legacy manual release
|
|
4108
|
+
* job, which is only playable once all prior stages finished.
|
|
4109
|
+
*/
|
|
4110
|
+
const GITLAB_MAX_NEEDS = 50;
|
|
4111
|
+
exports.RELEASE_BUTTON_JOB_NAME = "create release";
|
|
4112
|
+
exports.RELEASE_EXECUTOR_JOB_NAME = "🚀 release once pipeline succeeds";
|
|
3990
4113
|
const baseReleaseRules = [
|
|
3991
4114
|
rules_1.RULE_NEVER_ON_RELEASE_COMMIT,
|
|
3992
4115
|
rules_1.RULE_NEVER_ON_AGENT_TRIGGER,
|
|
3993
4116
|
rules_1.RULE_NEVER_ON_SCHEDULE,
|
|
3994
4117
|
];
|
|
3995
|
-
|
|
4118
|
+
// NOTE: with `rules:`, when: manual defaults to allow_failure: false,
|
|
4119
|
+
// which turns every unclicked pipeline into status "blocked" — the
|
|
4120
|
+
// manual release jobs therefore carry allow_failure: true so pipelines
|
|
4121
|
+
// end "passed" when nobody releases
|
|
4122
|
+
const manualReleaseRules = [
|
|
4123
|
+
...baseReleaseRules,
|
|
4124
|
+
{
|
|
4125
|
+
if: rules_1.RULE_CONDITION_MAIN_BRANCH,
|
|
4126
|
+
when: "manual",
|
|
4127
|
+
},
|
|
4128
|
+
{
|
|
4129
|
+
if: rules_1.RULE_CONDITION_HOTFIX_BRANCH,
|
|
4130
|
+
when: "manual",
|
|
4131
|
+
},
|
|
4132
|
+
];
|
|
4133
|
+
const getGitlabReleaseJobs = (config, images,
|
|
4134
|
+
/**
|
|
4135
|
+
* names of the jobs that run automatically in main-branch pipelines
|
|
4136
|
+
* (manual jobs excluded: a needed-but-unplayed manual job would make
|
|
4137
|
+
* gitlab skip the release executor)
|
|
4138
|
+
*/
|
|
4139
|
+
mainBranchJobNames) => {
|
|
3996
4140
|
var _a;
|
|
3997
4141
|
const method = (0, release_1.getReleaseMethod)(config);
|
|
3998
4142
|
// NOTE: only the resolved image url is used, not the `need`: release
|
|
3999
|
-
// jobs have no `needs` on purpose —
|
|
4000
|
-
//
|
|
4001
|
-
//
|
|
4002
|
-
// longer wait for the build/deploy stages.
|
|
4143
|
+
// jobs have no `needs` on the whole pipeline on purpose — where they
|
|
4144
|
+
// must wait for it, the wait is expressed by enumerating the jobs
|
|
4145
|
+
// explicitly (see the executor below).
|
|
4003
4146
|
const releaseImage = images.resolveRef({
|
|
4004
4147
|
catladderImage: method.image,
|
|
4005
4148
|
}).image;
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
...baseReleaseRules,
|
|
4014
|
-
{
|
|
4015
|
-
if: rules_1.RULE_CONDITION_MAIN_BRANCH,
|
|
4016
|
-
when: ((_a = config.releases) === null || _a === void 0 ? void 0 : _a.when) === "auto" ? "on_success" : "manual",
|
|
4017
|
-
},
|
|
4018
|
-
{
|
|
4019
|
-
if: rules_1.RULE_CONDITION_HOTFIX_BRANCH,
|
|
4020
|
-
when: "manual",
|
|
4021
|
-
},
|
|
4022
|
-
],
|
|
4023
|
-
},
|
|
4149
|
+
const releaseJobBase = {
|
|
4150
|
+
stage: "release",
|
|
4151
|
+
image: releaseImage,
|
|
4152
|
+
script: [method.script],
|
|
4153
|
+
after_script: [EXPIRED_TOKEN_HELP],
|
|
4154
|
+
};
|
|
4155
|
+
const forceReleaseJob = {
|
|
4024
4156
|
["⚠️ force create release"]: {
|
|
4025
|
-
|
|
4026
|
-
image: releaseImage,
|
|
4027
|
-
script: [method.script],
|
|
4028
|
-
after_script: [EXPIRED_TOKEN_HELP],
|
|
4157
|
+
...releaseJobBase,
|
|
4029
4158
|
needs: [],
|
|
4159
|
+
allow_failure: true,
|
|
4030
4160
|
// force semantics of the method's script (e.g. changesets:
|
|
4031
4161
|
// release a patch bump even without pending changesets)
|
|
4032
4162
|
...(method.forceReleaseVariables
|
|
4033
4163
|
? { variables: method.forceReleaseVariables }
|
|
4034
4164
|
: {}),
|
|
4165
|
+
rules: manualReleaseRules,
|
|
4166
|
+
},
|
|
4167
|
+
};
|
|
4168
|
+
// informational MR check of the release method (e.g. the changeset
|
|
4169
|
+
// check): warns via allow_failure, the report lands in the MR
|
|
4170
|
+
// widget as an exposed artifact
|
|
4171
|
+
const checkJob = method.checkScript
|
|
4172
|
+
? {
|
|
4173
|
+
["🦋 changeset check"]: {
|
|
4174
|
+
stage: "test",
|
|
4175
|
+
image: releaseImage,
|
|
4176
|
+
script: [method.checkScript],
|
|
4177
|
+
allow_failure: true,
|
|
4178
|
+
artifacts: {
|
|
4179
|
+
paths: ["changeset-report.md"],
|
|
4180
|
+
expose_as: "changeset report",
|
|
4181
|
+
// the report must survive the warning exit
|
|
4182
|
+
when: "always",
|
|
4183
|
+
},
|
|
4184
|
+
rules: [
|
|
4185
|
+
rules_1.RULE_NEVER_ON_AGENT_TRIGGER,
|
|
4186
|
+
rules_1.RULE_NEVER_ON_SCHEDULE,
|
|
4187
|
+
rules_1.RULE_IS_MERGE_REQUEST,
|
|
4188
|
+
],
|
|
4189
|
+
},
|
|
4190
|
+
}
|
|
4191
|
+
: {};
|
|
4192
|
+
if (((_a = config.releases) === null || _a === void 0 ? void 0 : _a.when) === "auto") {
|
|
4193
|
+
return {
|
|
4194
|
+
[exports.RELEASE_BUTTON_JOB_NAME]: {
|
|
4195
|
+
...releaseJobBase,
|
|
4196
|
+
rules: [
|
|
4197
|
+
...baseReleaseRules,
|
|
4198
|
+
{
|
|
4199
|
+
if: rules_1.RULE_CONDITION_MAIN_BRANCH,
|
|
4200
|
+
when: "on_success",
|
|
4201
|
+
},
|
|
4202
|
+
{
|
|
4203
|
+
if: rules_1.RULE_CONDITION_HOTFIX_BRANCH,
|
|
4204
|
+
when: "manual",
|
|
4205
|
+
// per-rule: only the unclicked hotfix button must not
|
|
4206
|
+
// block — a failed automatic release stays loud
|
|
4207
|
+
allow_failure: true,
|
|
4208
|
+
},
|
|
4209
|
+
],
|
|
4210
|
+
},
|
|
4211
|
+
...forceReleaseJob,
|
|
4212
|
+
...checkJob,
|
|
4213
|
+
};
|
|
4214
|
+
}
|
|
4215
|
+
const executorNeeds = [
|
|
4216
|
+
{ job: exports.RELEASE_BUTTON_JOB_NAME },
|
|
4217
|
+
// optional: hotfix pipelines (and rules-filtered jobs) contain a
|
|
4218
|
+
// different job subset
|
|
4219
|
+
...mainBranchJobNames.map((name) => ({ job: name, optional: true })),
|
|
4220
|
+
];
|
|
4221
|
+
if (executorNeeds.length > GITLAB_MAX_NEEDS) {
|
|
4222
|
+
console.warn(`⚠️ the main-branch pipeline has more than ${GITLAB_MAX_NEEDS} jobs — ` +
|
|
4223
|
+
`the "${exports.RELEASE_BUTTON_JOB_NAME}" job falls back to being playable ` +
|
|
4224
|
+
`only once the pipeline finished (gitlab caps needs: at ${GITLAB_MAX_NEEDS})`);
|
|
4225
|
+
return {
|
|
4226
|
+
[exports.RELEASE_BUTTON_JOB_NAME]: {
|
|
4227
|
+
...releaseJobBase,
|
|
4228
|
+
allow_failure: true,
|
|
4229
|
+
rules: manualReleaseRules,
|
|
4230
|
+
},
|
|
4231
|
+
...forceReleaseJob,
|
|
4232
|
+
...checkJob,
|
|
4233
|
+
};
|
|
4234
|
+
}
|
|
4235
|
+
return {
|
|
4236
|
+
// the queue button: playable from the first second of the pipeline.
|
|
4237
|
+
// It only records the intent to release — the executor below does
|
|
4238
|
+
// the actual work once every other job succeeded. Clicking after the
|
|
4239
|
+
// pipeline finished works the same (the executor starts right away).
|
|
4240
|
+
[exports.RELEASE_BUTTON_JOB_NAME]: {
|
|
4241
|
+
stage: "release",
|
|
4242
|
+
image: releaseImage,
|
|
4243
|
+
script: [
|
|
4244
|
+
`echo "release queued — '${exports.RELEASE_EXECUTOR_JOB_NAME}' runs it as soon as every other job in this pipeline succeeded"`,
|
|
4245
|
+
],
|
|
4246
|
+
needs: [],
|
|
4247
|
+
// allow_failure keeps the unclicked button from blocking the
|
|
4248
|
+
// pipeline AND makes the executor's needs treat it as optional
|
|
4249
|
+
// (skipped executor instead of a forever-waiting pipeline)
|
|
4250
|
+
allow_failure: true,
|
|
4251
|
+
rules: manualReleaseRules,
|
|
4252
|
+
},
|
|
4253
|
+
// the executor: never played manually. Skipped when the button was
|
|
4254
|
+
// not clicked; otherwise starts automatically as soon as the button
|
|
4255
|
+
// and every other (non-manual) job of the pipeline succeeded, and
|
|
4256
|
+
// is skipped when any of them failed.
|
|
4257
|
+
[exports.RELEASE_EXECUTOR_JOB_NAME]: {
|
|
4258
|
+
...releaseJobBase,
|
|
4259
|
+
needs: executorNeeds,
|
|
4035
4260
|
rules: [
|
|
4036
4261
|
...baseReleaseRules,
|
|
4037
4262
|
{
|
|
4038
4263
|
if: rules_1.RULE_CONDITION_MAIN_BRANCH,
|
|
4039
|
-
when: "
|
|
4264
|
+
when: "on_success",
|
|
4040
4265
|
},
|
|
4041
4266
|
{
|
|
4042
4267
|
if: rules_1.RULE_CONDITION_HOTFIX_BRANCH,
|
|
4043
|
-
when: "
|
|
4268
|
+
when: "on_success",
|
|
4044
4269
|
},
|
|
4045
4270
|
],
|
|
4046
4271
|
},
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
// widget as an exposed artifact
|
|
4050
|
-
...(method.checkScript
|
|
4051
|
-
? {
|
|
4052
|
-
["🦋 changeset check"]: {
|
|
4053
|
-
stage: "test",
|
|
4054
|
-
image: releaseImage,
|
|
4055
|
-
script: [method.checkScript],
|
|
4056
|
-
allow_failure: true,
|
|
4057
|
-
artifacts: {
|
|
4058
|
-
paths: ["changeset-report.md"],
|
|
4059
|
-
expose_as: "changeset report",
|
|
4060
|
-
// the report must survive the warning exit
|
|
4061
|
-
when: "always",
|
|
4062
|
-
},
|
|
4063
|
-
rules: [
|
|
4064
|
-
rules_1.RULE_NEVER_ON_AGENT_TRIGGER,
|
|
4065
|
-
rules_1.RULE_NEVER_ON_SCHEDULE,
|
|
4066
|
-
rules_1.RULE_IS_MERGE_REQUEST,
|
|
4067
|
-
],
|
|
4068
|
-
},
|
|
4069
|
-
}
|
|
4070
|
-
: {}),
|
|
4272
|
+
...forceReleaseJob,
|
|
4273
|
+
...checkJob,
|
|
4071
4274
|
};
|
|
4072
4275
|
};
|
|
4073
4276
|
exports.getGitlabReleaseJobs = getGitlabReleaseJobs;
|
|
@@ -6981,7 +7184,7 @@ exports.makeSecretEnvVarMapping = makeSecretEnvVarMapping;
|
|
|
6981
7184
|
"use strict";
|
|
6982
7185
|
|
|
6983
7186
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
6984
|
-
exports.computeCustomImageHash = void 0;
|
|
7187
|
+
exports.computeInlineImageHash = exports.computeCustomImageHash = void 0;
|
|
6985
7188
|
const crypto_1 = __nccwpck_require__(76982);
|
|
6986
7189
|
const fs_1 = __nccwpck_require__(79896);
|
|
6987
7190
|
const path_1 = __nccwpck_require__(16928);
|
|
@@ -7002,6 +7205,29 @@ function listFilesRecursive(dir) {
|
|
|
7002
7205
|
})
|
|
7003
7206
|
.sort();
|
|
7004
7207
|
}
|
|
7208
|
+
/**
|
|
7209
|
+
* the parts every image definition hashes on top of its content:
|
|
7210
|
+
* files outside the definition, and the build args
|
|
7211
|
+
*/
|
|
7212
|
+
function hashExtras(hash, config) {
|
|
7213
|
+
const watchedPaths = [];
|
|
7214
|
+
// Hash extra paths (for files outside the dir)
|
|
7215
|
+
if (config.hashExtraPaths) {
|
|
7216
|
+
// Extra paths are literal file paths (not globs) for simplicity
|
|
7217
|
+
const extraFiles = [...config.hashExtraPaths].sort();
|
|
7218
|
+
for (const file of extraFiles) {
|
|
7219
|
+
hash.update(file);
|
|
7220
|
+
hash.update((0, fs_1.readFileSync)(file));
|
|
7221
|
+
}
|
|
7222
|
+
watchedPaths.push(...config.hashExtraPaths);
|
|
7223
|
+
}
|
|
7224
|
+
// Hash build args (sorted keys for determinism)
|
|
7225
|
+
if (config.buildArgs) {
|
|
7226
|
+
const sorted = Object.entries(config.buildArgs).sort(([a], [b]) => a.localeCompare(b));
|
|
7227
|
+
hash.update(JSON.stringify(sorted));
|
|
7228
|
+
}
|
|
7229
|
+
return watchedPaths;
|
|
7230
|
+
}
|
|
7005
7231
|
function computeCustomImageHash(config) {
|
|
7006
7232
|
var _a;
|
|
7007
7233
|
const hash = (0, crypto_1.createHash)("sha256");
|
|
@@ -7022,27 +7248,29 @@ function computeCustomImageHash(config) {
|
|
|
7022
7248
|
}
|
|
7023
7249
|
watchedPaths.push(`${extraDir}/**/*`);
|
|
7024
7250
|
}
|
|
7025
|
-
|
|
7026
|
-
if (config.hashExtraPaths) {
|
|
7027
|
-
// Extra paths are literal file paths (not globs) for simplicity
|
|
7028
|
-
const extraFiles = [...config.hashExtraPaths].sort();
|
|
7029
|
-
for (const file of extraFiles) {
|
|
7030
|
-
hash.update(file);
|
|
7031
|
-
hash.update((0, fs_1.readFileSync)(file));
|
|
7032
|
-
}
|
|
7033
|
-
watchedPaths.push(...config.hashExtraPaths);
|
|
7034
|
-
}
|
|
7035
|
-
// Hash build args (sorted keys for determinism)
|
|
7036
|
-
if (config.buildArgs) {
|
|
7037
|
-
const sorted = Object.entries(config.buildArgs).sort(([a], [b]) => a.localeCompare(b));
|
|
7038
|
-
hash.update(JSON.stringify(sorted));
|
|
7039
|
-
}
|
|
7251
|
+
watchedPaths.push(...hashExtras(hash, config));
|
|
7040
7252
|
return {
|
|
7041
7253
|
hash: hash.digest("hex").slice(0, 12),
|
|
7042
7254
|
watchedPaths,
|
|
7043
7255
|
};
|
|
7044
7256
|
}
|
|
7045
7257
|
exports.computeCustomImageHash = computeCustomImageHash;
|
|
7258
|
+
/**
|
|
7259
|
+
* the hash of an image defined by an inline Dockerfile: its content
|
|
7260
|
+
* stands in for the definition dir. The build context is deliberately
|
|
7261
|
+
* NOT hashed (it may be the whole repository) — `hashExtraPaths` is how
|
|
7262
|
+
* context files opt into the hash.
|
|
7263
|
+
*/
|
|
7264
|
+
function computeInlineImageHash(config) {
|
|
7265
|
+
const hash = (0, crypto_1.createHash)("sha256");
|
|
7266
|
+
hash.update(config.dockerfileContent);
|
|
7267
|
+
const watchedPaths = hashExtras(hash, config);
|
|
7268
|
+
return {
|
|
7269
|
+
hash: hash.digest("hex").slice(0, 12),
|
|
7270
|
+
watchedPaths,
|
|
7271
|
+
};
|
|
7272
|
+
}
|
|
7273
|
+
exports.computeInlineImageHash = computeInlineImageHash;
|
|
7046
7274
|
//# sourceMappingURL=hash.js.map
|
|
7047
7275
|
|
|
7048
7276
|
/***/ }),
|
|
@@ -7053,7 +7281,7 @@ exports.computeCustomImageHash = computeCustomImageHash;
|
|
|
7053
7281
|
"use strict";
|
|
7054
7282
|
|
|
7055
7283
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
7056
|
-
exports.JobImagesPlan = exports.GENERATED_IMAGES_FOLDER = void 0;
|
|
7284
|
+
exports.JobImagesPlan = exports.PROJECT_IMAGES_FOLDER = exports.GENERATED_IMAGES_FOLDER = void 0;
|
|
7057
7285
|
const fs_1 = __nccwpck_require__(79896);
|
|
7058
7286
|
const path_1 = __nccwpck_require__(16928);
|
|
7059
7287
|
const ciVariables_1 = __nccwpck_require__(72700);
|
|
@@ -7061,6 +7289,7 @@ const docker_1 = __nccwpck_require__(83777);
|
|
|
7061
7289
|
const runner_1 = __nccwpck_require__(71065);
|
|
7062
7290
|
const gitlab_1 = __nccwpck_require__(37393);
|
|
7063
7291
|
const hash_1 = __nccwpck_require__(34092);
|
|
7292
|
+
const projectImages_1 = __nccwpck_require__(55537);
|
|
7064
7293
|
const shippedImages_1 = __nccwpck_require__(28687);
|
|
7065
7294
|
exports.GENERATED_IMAGES_FOLDER = ".catladder-generated/images";
|
|
7066
7295
|
/**
|
|
@@ -7068,7 +7297,44 @@ exports.GENERATED_IMAGES_FOLDER = ".catladder-generated/images";
|
|
|
7068
7297
|
* catladder image, so building images never depends on one (bootstrap)
|
|
7069
7298
|
*/
|
|
7070
7299
|
const BUILDER_IMAGE = "docker.io/docker:29.5.1";
|
|
7071
|
-
|
|
7300
|
+
/**
|
|
7301
|
+
* every planned image is consumed the same way: its url, plus an
|
|
7302
|
+
* optional dependency on its build job
|
|
7303
|
+
*/
|
|
7304
|
+
const toResolvedJobImage = ({ imageRef, jobName, }) => ({
|
|
7305
|
+
image: imageRef,
|
|
7306
|
+
fromRepoRegistry: true,
|
|
7307
|
+
need: {
|
|
7308
|
+
job: jobName,
|
|
7309
|
+
artifacts: false,
|
|
7310
|
+
optional: true,
|
|
7311
|
+
global: true,
|
|
7312
|
+
},
|
|
7313
|
+
});
|
|
7314
|
+
/**
|
|
7315
|
+
* materializes an image definition directory into the generated files
|
|
7316
|
+
* (used for the definitions catladder ships)
|
|
7317
|
+
*/
|
|
7318
|
+
const materializeDir = (sourceDir, targetDir) => listFilesRecursive(sourceDir).map((file) => ({
|
|
7319
|
+
path: (0, path_1.join)(targetDir, file),
|
|
7320
|
+
content: (0, fs_1.readFileSync)((0, path_1.join)(sourceDir, file), "utf-8"),
|
|
7321
|
+
}));
|
|
7322
|
+
// built-in (catladder-shipped) images carry the catladder marker in
|
|
7323
|
+
// their job name; project-declared images get the short plain name
|
|
7324
|
+
const ensureJobName = (name) => `🐳 catladder image ${name}`;
|
|
7325
|
+
const projectEnsureJobName = (name) => `🐳 image ${name}`;
|
|
7326
|
+
/**
|
|
7327
|
+
* the config file whose content feeds the project image hashes
|
|
7328
|
+
* (`buildArgs`, `hashExtraPaths`): the gitlab change detection has to
|
|
7329
|
+
* watch it, because a config-only change alters the image tag without
|
|
7330
|
+
* touching the image dir
|
|
7331
|
+
*/
|
|
7332
|
+
const CONFIG_FILE = "catladder.ts";
|
|
7333
|
+
/**
|
|
7334
|
+
* where inline project Dockerfiles are materialized — a subfolder, so a
|
|
7335
|
+
* project image can carry the same name as a shipped one
|
|
7336
|
+
*/
|
|
7337
|
+
exports.PROJECT_IMAGES_FOLDER = `${exports.GENERATED_IMAGES_FOLDER}/project`;
|
|
7072
7338
|
/**
|
|
7073
7339
|
* plans the catladder job images of one backend run: resolves image
|
|
7074
7340
|
* references, collects the image build jobs, and the image definitions
|
|
@@ -7079,33 +7345,30 @@ const ensureJobName = (name) => `🐳 job image ${name}`;
|
|
|
7079
7345
|
* Reference-driven: only images actually used by a job are built.
|
|
7080
7346
|
*/
|
|
7081
7347
|
class JobImagesPlan {
|
|
7082
|
-
constructor(pipelineType) {
|
|
7348
|
+
constructor(pipelineType, projectImages = {}) {
|
|
7083
7349
|
this.pipelineType = pipelineType;
|
|
7350
|
+
this.projectImages = projectImages;
|
|
7084
7351
|
this.used = new Map();
|
|
7352
|
+
this.usedProject = new Map();
|
|
7085
7353
|
}
|
|
7086
7354
|
/**
|
|
7087
7355
|
* resolves a job image (marker or concrete) to a concrete image url
|
|
7088
7356
|
* plus the build job dependency
|
|
7089
7357
|
*/
|
|
7090
7358
|
resolve(image) {
|
|
7091
|
-
if (
|
|
7092
|
-
return
|
|
7359
|
+
if ((0, runner_1.isCatladderImageRef)(image)) {
|
|
7360
|
+
return this.resolveRef(image);
|
|
7093
7361
|
}
|
|
7094
|
-
|
|
7362
|
+
if ((0, projectImages_1.isProjectImageRef)(image)) {
|
|
7363
|
+
return this.resolveProjectRef(image);
|
|
7364
|
+
}
|
|
7365
|
+
return { image };
|
|
7095
7366
|
}
|
|
7096
7367
|
resolveRef(ref) {
|
|
7097
|
-
|
|
7098
|
-
|
|
7099
|
-
|
|
7100
|
-
|
|
7101
|
-
fromRepoRegistry: true,
|
|
7102
|
-
need: {
|
|
7103
|
-
job: ensureJobName(name),
|
|
7104
|
-
artifacts: false,
|
|
7105
|
-
optional: true,
|
|
7106
|
-
global: true,
|
|
7107
|
-
},
|
|
7108
|
-
};
|
|
7368
|
+
return toResolvedJobImage(this.use(ref.catladderImage));
|
|
7369
|
+
}
|
|
7370
|
+
resolveProjectRef(ref) {
|
|
7371
|
+
return toResolvedJobImage(this.useProject(ref.image));
|
|
7109
7372
|
}
|
|
7110
7373
|
/**
|
|
7111
7374
|
* the registry image prefix. On github, container images can't use
|
|
@@ -7116,6 +7379,85 @@ class JobImagesPlan {
|
|
|
7116
7379
|
? "ghcr.io/${{ github.repository }}"
|
|
7117
7380
|
: `${(0, ciVariables_1.getCiVariable)({ pipelineType: this.pipelineType }, "registryImage")}`;
|
|
7118
7381
|
}
|
|
7382
|
+
useProject(name) {
|
|
7383
|
+
const existing = this.usedProject.get(name);
|
|
7384
|
+
if (existing) {
|
|
7385
|
+
return existing;
|
|
7386
|
+
}
|
|
7387
|
+
const imageConfig = this.projectImages[name];
|
|
7388
|
+
if (!imageConfig) {
|
|
7389
|
+
const declared = Object.keys(this.projectImages);
|
|
7390
|
+
throw new Error(`unknown job image "${name}" — ${declared.length > 0
|
|
7391
|
+
? `images declared in config.images: ${declared.join(", ")}`
|
|
7392
|
+
: "no images are declared in config.images"}`);
|
|
7393
|
+
}
|
|
7394
|
+
const source = this.planProjectSource(name, imageConfig);
|
|
7395
|
+
const planned = {
|
|
7396
|
+
...source,
|
|
7397
|
+
jobName: projectEnsureJobName(name),
|
|
7398
|
+
buildArgs: imageConfig.buildArgs,
|
|
7399
|
+
// `job-images/` keeps the project's job images apart from its
|
|
7400
|
+
// deployable component images (registry root) and build caches
|
|
7401
|
+
imageRef: `${this.registryImageRef()}/job-images/${name}:${source.hash}`,
|
|
7402
|
+
};
|
|
7403
|
+
this.usedProject.set(name, planned);
|
|
7404
|
+
return planned;
|
|
7405
|
+
}
|
|
7406
|
+
/**
|
|
7407
|
+
* the source-dependent half of a project image: an inline Dockerfile
|
|
7408
|
+
* is materialized into the generated files and hashed by its content,
|
|
7409
|
+
* a directory is hashed and watched in place
|
|
7410
|
+
*/
|
|
7411
|
+
planProjectSource(name, imageConfig) {
|
|
7412
|
+
var _a, _b;
|
|
7413
|
+
if (imageConfig.dockerfile !== undefined) {
|
|
7414
|
+
const content = toDockerfileContent(imageConfig.dockerfile);
|
|
7415
|
+
const dockerfilePath = `${exports.PROJECT_IMAGES_FOLDER}/${name}/Dockerfile`;
|
|
7416
|
+
const { hash, watchedPaths } = (0, hash_1.computeInlineImageHash)({
|
|
7417
|
+
dockerfileContent: content,
|
|
7418
|
+
hashExtraPaths: imageConfig.hashExtraPaths,
|
|
7419
|
+
buildArgs: imageConfig.buildArgs,
|
|
7420
|
+
});
|
|
7421
|
+
return {
|
|
7422
|
+
hash,
|
|
7423
|
+
dockerfilePath,
|
|
7424
|
+
// an inline definition carries no directory of its own — the
|
|
7425
|
+
// repository root is the sensible default context
|
|
7426
|
+
contextDir: (_a = imageConfig.context) !== null && _a !== void 0 ? _a : ".",
|
|
7427
|
+
watchedPaths: [
|
|
7428
|
+
`${exports.PROJECT_IMAGES_FOLDER}/${name}/**/*`,
|
|
7429
|
+
...watchedPaths,
|
|
7430
|
+
CONFIG_FILE,
|
|
7431
|
+
],
|
|
7432
|
+
files: [{ path: dockerfilePath, content }],
|
|
7433
|
+
};
|
|
7434
|
+
}
|
|
7435
|
+
const dir = imageConfig.dir;
|
|
7436
|
+
// fail at generation time with the offending image named: an
|
|
7437
|
+
// unreadable dir would otherwise surface as a bare fs ENOENT, and a
|
|
7438
|
+
// missing Dockerfile only as a docker build failure in CI
|
|
7439
|
+
if (!(0, fs_1.existsSync)(dir)) {
|
|
7440
|
+
throw new Error(`job image "${name}": the directory "${dir}" does not exist (config.images.${name}.dir, relative to the repository root)`);
|
|
7441
|
+
}
|
|
7442
|
+
if (!(0, fs_1.existsSync)((0, path_1.join)(dir, "Dockerfile"))) {
|
|
7443
|
+
throw new Error(`job image "${name}": no Dockerfile in "${dir}" (config.images.${name}.dir)`);
|
|
7444
|
+
}
|
|
7445
|
+
const { hash, watchedPaths } = (0, hash_1.computeCustomImageHash)({
|
|
7446
|
+
dir,
|
|
7447
|
+
hashExtraPaths: imageConfig.hashExtraPaths,
|
|
7448
|
+
buildArgs: imageConfig.buildArgs,
|
|
7449
|
+
});
|
|
7450
|
+
return {
|
|
7451
|
+
hash,
|
|
7452
|
+
dockerfilePath: `${dir}/Dockerfile`,
|
|
7453
|
+
contextDir: (_b = imageConfig.context) !== null && _b !== void 0 ? _b : dir,
|
|
7454
|
+
// the dir is committed in the project — watched in place, nothing
|
|
7455
|
+
// is materialized. The config file is watched too: buildArgs /
|
|
7456
|
+
// hashExtraPaths changes alter the tag without touching the dir
|
|
7457
|
+
watchedPaths: [...watchedPaths, CONFIG_FILE],
|
|
7458
|
+
files: [],
|
|
7459
|
+
};
|
|
7460
|
+
}
|
|
7119
7461
|
use(name) {
|
|
7120
7462
|
var _a;
|
|
7121
7463
|
const existing = this.used.get(name);
|
|
@@ -7130,27 +7472,43 @@ class JobImagesPlan {
|
|
|
7130
7472
|
dir: (0, shippedImages_1.getShippedImageDir)(name),
|
|
7131
7473
|
extraDirs: dependencies.map((dependency) => (0, shippedImages_1.getShippedImageDir)(dependency)),
|
|
7132
7474
|
});
|
|
7133
|
-
const
|
|
7134
|
-
|
|
7135
|
-
|
|
7475
|
+
const targetDir = `${exports.GENERATED_IMAGES_FOLDER}/${name}`;
|
|
7476
|
+
const planned = {
|
|
7477
|
+
hash,
|
|
7478
|
+
jobName: ensureJobName(name),
|
|
7479
|
+
imageRef: `${this.registryImageRef()}/catladder/${name}:${hash}`,
|
|
7480
|
+
dockerfilePath: `${targetDir}/Dockerfile`,
|
|
7481
|
+
// the jobs family INCLUDEs sibling images, so it builds from the
|
|
7482
|
+
// images root; the others from their own dir
|
|
7483
|
+
contextDir: shippedImages_1.RUNNER_IMAGE_BUILD_CONTEXT[name] === "root"
|
|
7484
|
+
? exports.GENERATED_IMAGES_FOLDER
|
|
7485
|
+
: targetDir,
|
|
7486
|
+
watchedPaths: [name, ...dependencies].map((dir) => `${exports.GENERATED_IMAGES_FOLDER}/${dir}/**/*`),
|
|
7487
|
+
// pipelines build from committed, reviewable files (and gitlab's
|
|
7488
|
+
// rules:changes can watch them)
|
|
7489
|
+
files: materializeDir((0, shippedImages_1.getShippedImageDir)(name), targetDir),
|
|
7490
|
+
};
|
|
7491
|
+
this.used.set(name, planned);
|
|
7492
|
+
return planned;
|
|
7136
7493
|
}
|
|
7137
7494
|
/**
|
|
7138
7495
|
* the image build jobs for all used images
|
|
7139
7496
|
*/
|
|
7140
7497
|
getEnsureJobs() {
|
|
7141
|
-
return [...this.used.
|
|
7498
|
+
return [...this.used.values(), ...this.usedProject.values()].map((planned) => this.createImageBuildJob(planned));
|
|
7142
7499
|
}
|
|
7143
|
-
|
|
7144
|
-
|
|
7500
|
+
createImageBuildJob({ jobName, imageRef, watchedPaths, dockerfilePath, contextDir, buildArgs, }) {
|
|
7501
|
+
const buildArgFlags = Object.entries(buildArgs !== null && buildArgs !== void 0 ? buildArgs : {})
|
|
7502
|
+
.map(([key, value]) => ` --build-arg '${key}=${value}'`)
|
|
7503
|
+
.join("");
|
|
7504
|
+
const buildCommand = `DOCKER_BUILDKIT=1 docker build -t ${imageRef}${buildArgFlags} -f ${dockerfilePath} ${contextDir}`;
|
|
7145
7505
|
const ctx = { pipelineType: this.pipelineType };
|
|
7146
7506
|
const registryUser = (0, ciVariables_1.getCiVariable)(ctx, "registryUser");
|
|
7147
7507
|
const jobToken = (0, ciVariables_1.getCiVariable)(ctx, "jobToken");
|
|
7148
7508
|
const registry = (0, ciVariables_1.getCiVariable)(ctx, "registry");
|
|
7149
|
-
const imageRef = `${this.registryImageRef()}/catladder/${name}:${hash}`;
|
|
7150
|
-
const watchedDirs = [name, ...((_a = shippedImages_1.RUNNER_IMAGE_DEPENDENCIES[name]) !== null && _a !== void 0 ? _a : [])];
|
|
7151
7509
|
const { services, runnerVariables } = (0, docker_1.getDockerJobBaseProps)();
|
|
7152
7510
|
return {
|
|
7153
|
-
name:
|
|
7511
|
+
name: jobName,
|
|
7154
7512
|
stage: "setup",
|
|
7155
7513
|
image: BUILDER_IMAGE,
|
|
7156
7514
|
services,
|
|
@@ -7160,7 +7518,9 @@ class JobImagesPlan {
|
|
|
7160
7518
|
// no equivalent and always runs (the existence check is fast)
|
|
7161
7519
|
rules: [
|
|
7162
7520
|
{
|
|
7163
|
-
|
|
7521
|
+
// a fresh array per job: the yaml serializer anchors repeated
|
|
7522
|
+
// objects, and a shared reference would reshuffle anchors
|
|
7523
|
+
changes: [...watchedPaths],
|
|
7164
7524
|
},
|
|
7165
7525
|
],
|
|
7166
7526
|
variables: {},
|
|
@@ -7174,31 +7534,33 @@ class JobImagesPlan {
|
|
|
7174
7534
|
` exit 0`,
|
|
7175
7535
|
`fi`,
|
|
7176
7536
|
]),
|
|
7177
|
-
...(0, gitlab_1.collapseableSection)("docker-build", "Building image")([
|
|
7178
|
-
`DOCKER_BUILDKIT=1 docker build -t ${imageRef} -f ${exports.GENERATED_IMAGES_FOLDER}/${name}/Dockerfile ${shippedImages_1.RUNNER_IMAGE_BUILD_CONTEXT[name] === "root"
|
|
7179
|
-
? exports.GENERATED_IMAGES_FOLDER
|
|
7180
|
-
: `${exports.GENERATED_IMAGES_FOLDER}/${name}`}`,
|
|
7181
|
-
]),
|
|
7537
|
+
...(0, gitlab_1.collapseableSection)("docker-build", "Building image")([buildCommand]),
|
|
7182
7538
|
...(0, gitlab_1.collapseableSection)("docker-push", "Pushing image")([`docker push ${imageRef}`]),
|
|
7183
7539
|
],
|
|
7184
7540
|
};
|
|
7185
7541
|
}
|
|
7186
7542
|
/**
|
|
7187
|
-
* the image definitions
|
|
7188
|
-
*
|
|
7189
|
-
*
|
|
7543
|
+
* the image definitions to materialize into the generated files (so
|
|
7544
|
+
* pipelines build from committed, reviewable files and gitlab's
|
|
7545
|
+
* rules:changes can watch them): the definitions catladder ships and
|
|
7546
|
+
* the project's inline Dockerfiles. A project image directory is
|
|
7547
|
+
* already committed and contributes nothing.
|
|
7190
7548
|
*/
|
|
7191
7549
|
getGeneratedFiles() {
|
|
7192
|
-
return [...this.used.
|
|
7193
|
-
const dir = (0, shippedImages_1.getShippedImageDir)(name);
|
|
7194
|
-
return listFilesRecursive(dir).map((file) => ({
|
|
7195
|
-
path: (0, path_1.join)(exports.GENERATED_IMAGES_FOLDER, name, file),
|
|
7196
|
-
content: (0, fs_1.readFileSync)((0, path_1.join)(dir, file), "utf-8"),
|
|
7197
|
-
}));
|
|
7198
|
-
});
|
|
7550
|
+
return [...this.used.values(), ...this.usedProject.values()].flatMap(({ files }) => files);
|
|
7199
7551
|
}
|
|
7200
7552
|
}
|
|
7201
7553
|
exports.JobImagesPlan = JobImagesPlan;
|
|
7554
|
+
/**
|
|
7555
|
+
* an inline Dockerfile is written as given — as one string, or as lines
|
|
7556
|
+
* joined with newlines (always newline-terminated)
|
|
7557
|
+
*/
|
|
7558
|
+
const toDockerfileContent = (dockerfile) => {
|
|
7559
|
+
const content = Array.isArray(dockerfile)
|
|
7560
|
+
? dockerfile.join("\n")
|
|
7561
|
+
: dockerfile;
|
|
7562
|
+
return content.endsWith("\n") ? content : `${content}\n`;
|
|
7563
|
+
};
|
|
7202
7564
|
const IGNORED_FILES = new Set([".DS_Store", "Thumbs.db"]);
|
|
7203
7565
|
const listFilesRecursive = (dir) => (0, fs_1.readdirSync)(dir, { recursive: true, encoding: "utf-8" })
|
|
7204
7566
|
.filter((entry) => {
|
|
@@ -7212,6 +7574,22 @@ const listFilesRecursive = (dir) => (0, fs_1.readdirSync)(dir, { recursive: true
|
|
|
7212
7574
|
|
|
7213
7575
|
/***/ }),
|
|
7214
7576
|
|
|
7577
|
+
/***/ 55537:
|
|
7578
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
7579
|
+
|
|
7580
|
+
"use strict";
|
|
7581
|
+
|
|
7582
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
7583
|
+
exports.isProjectImageRef = void 0;
|
|
7584
|
+
const isProjectImageRef = (value) => typeof value === "object" &&
|
|
7585
|
+
value !== null &&
|
|
7586
|
+
"image" in value &&
|
|
7587
|
+
typeof value.image === "string";
|
|
7588
|
+
exports.isProjectImageRef = isProjectImageRef;
|
|
7589
|
+
//# sourceMappingURL=projectImages.js.map
|
|
7590
|
+
|
|
7591
|
+
/***/ }),
|
|
7592
|
+
|
|
7215
7593
|
/***/ 28687:
|
|
7216
7594
|
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
7217
7595
|
|
|
@@ -9925,6 +10303,8 @@ __exportStar(__nccwpck_require__(68906), exports);
|
|
|
9925
10303
|
__exportStar(__nccwpck_require__(23040), exports);
|
|
9926
10304
|
__exportStar(__nccwpck_require__(35086), exports);
|
|
9927
10305
|
__exportStar(__nccwpck_require__(71065), exports);
|
|
10306
|
+
__exportStar(__nccwpck_require__(55537), exports);
|
|
10307
|
+
__exportStar(__nccwpck_require__(47864), exports);
|
|
9928
10308
|
__exportStar(__nccwpck_require__(80769), exports);
|
|
9929
10309
|
__exportStar(__nccwpck_require__(7732), exports);
|
|
9930
10310
|
__exportStar(__nccwpck_require__(66526), exports);
|