@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
|
@@ -8314,7 +8314,7 @@ class GithubBackend {
|
|
|
8314
8314
|
await (0, promises_1.rm)(scriptFiles_1.GITHUB_SCRIPTS_FOLDER, { recursive: true, force: true });
|
|
8315
8315
|
}
|
|
8316
8316
|
async createFiles(config) {
|
|
8317
|
-
const images = this.createImagesPlan();
|
|
8317
|
+
const images = this.createImagesPlan(config);
|
|
8318
8318
|
const scripts = new scriptFiles_1.GithubScriptFiles();
|
|
8319
8319
|
const workflows = await this.createWorkflows(config, images, scripts);
|
|
8320
8320
|
return [
|
|
@@ -8329,13 +8329,13 @@ class GithubBackend {
|
|
|
8329
8329
|
...(0, shippedCatci_1.getCatciGeneratedFiles)(),
|
|
8330
8330
|
];
|
|
8331
8331
|
}
|
|
8332
|
-
createImagesPlan() {
|
|
8333
|
-
return new jobImagesPlan_1.JobImagesPlan(this.type);
|
|
8332
|
+
createImagesPlan(config) {
|
|
8333
|
+
return new jobImagesPlan_1.JobImagesPlan(this.type, config.images);
|
|
8334
8334
|
}
|
|
8335
8335
|
/**
|
|
8336
8336
|
* all workflows keyed by file name, mainly for testing purposes
|
|
8337
8337
|
*/
|
|
8338
|
-
async createWorkflows(config, images = this.createImagesPlan(), scripts = new scriptFiles_1.GithubScriptFiles()) {
|
|
8338
|
+
async createWorkflows(config, images = this.createImagesPlan(config), scripts = new scriptFiles_1.GithubScriptFiles()) {
|
|
8339
8339
|
var _a, _b, _c, _d;
|
|
8340
8340
|
const workflows = {};
|
|
8341
8341
|
// per-pipeline-type variables (pipelines.github.runnerVariables);
|
|
@@ -8456,6 +8456,13 @@ class GithubBackend {
|
|
|
8456
8456
|
manualJobConditions[id] = `inputs.job == '${id}'`;
|
|
8457
8457
|
manualDispatchOptions.push(id);
|
|
8458
8458
|
}
|
|
8459
|
+
// manual release mode: the workflow that runs a queued release
|
|
8460
|
+
// once the main workflow completed green
|
|
8461
|
+
const releaseOnGreen = (0, githubReleaseJobs_1.getGithubReleaseOnGreenWorkflow)(config, images, TRIGGER_WORKFLOWS.mainBranch.name, workflowEnv);
|
|
8462
|
+
if (releaseOnGreen) {
|
|
8463
|
+
workflows[`${GENERATED_FILE_PREFIX}release-on-green.yml`] =
|
|
8464
|
+
releaseOnGreen;
|
|
8465
|
+
}
|
|
8459
8466
|
}
|
|
8460
8467
|
if (Object.keys(jobs).length > 0) {
|
|
8461
8468
|
workflows[`${GENERATED_FILE_PREFIX}${workflowFileName(trigger)}`] = {
|
|
@@ -9000,16 +9007,21 @@ exports.getUploadProviderIds = getUploadProviderIds;
|
|
|
9000
9007
|
"use strict";
|
|
9001
9008
|
|
|
9002
9009
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
9003
|
-
exports.getGithubReleaseCheckJobs = exports.getGithubReleaseJobs = void 0;
|
|
9010
|
+
exports.getGithubReleaseCheckJobs = exports.getGithubReleaseOnGreenWorkflow = exports.getGithubReleaseJobs = void 0;
|
|
9004
9011
|
const createGithubJobs_1 = __nccwpck_require__(57877);
|
|
9005
9012
|
const release_1 = __nccwpck_require__(38904);
|
|
9006
|
-
const
|
|
9013
|
+
const shippedCatci_1 = __nccwpck_require__(79024);
|
|
9014
|
+
const CATCI = `${shippedCatci_1.GENERATED_CATCI_FOLDER}/index.js`;
|
|
9015
|
+
const createReleaseJob = (config, images, options = {}) => {
|
|
9016
|
+
var _a;
|
|
9007
9017
|
const method = (0, release_1.getReleaseMethod)(config);
|
|
9008
9018
|
const resolved = images.resolveRef({ catladderImage: method.image });
|
|
9009
|
-
const imageNeeds =
|
|
9019
|
+
const imageNeeds = !options.skipImageNeeds &&
|
|
9020
|
+
resolved.need &&
|
|
9021
|
+
typeof resolved.need === "object"
|
|
9010
9022
|
? [(0, createGithubJobs_1.githubGlobalJobId)(resolved.need.job)]
|
|
9011
9023
|
: [];
|
|
9012
|
-
const allNeeds = [...imageNeeds, ...(needs !== null &&
|
|
9024
|
+
const allNeeds = [...imageNeeds, ...((_a = options.needs) !== null && _a !== void 0 ? _a : [])];
|
|
9013
9025
|
return {
|
|
9014
9026
|
name: "create release",
|
|
9015
9027
|
"runs-on": "ubuntu-latest",
|
|
@@ -9038,7 +9050,7 @@ const createReleaseJob = (config, images, needs, extraEnv) => {
|
|
|
9038
9050
|
...(allNeeds.length > 0 ? { needs: allNeeds } : {}),
|
|
9039
9051
|
env: {
|
|
9040
9052
|
GITHUB_TOKEN: "${{ github.token }}",
|
|
9041
|
-
...extraEnv,
|
|
9053
|
+
...options.extraEnv,
|
|
9042
9054
|
},
|
|
9043
9055
|
steps: [
|
|
9044
9056
|
{
|
|
@@ -9048,8 +9060,21 @@ const createReleaseJob = (config, images, needs, extraEnv) => {
|
|
|
9048
9060
|
// tags since the last release) to determine the version
|
|
9049
9061
|
with: { "fetch-depth": 0 },
|
|
9050
9062
|
},
|
|
9063
|
+
...(options.queueCheck
|
|
9064
|
+
? [
|
|
9065
|
+
{
|
|
9066
|
+
name: "check main workflow",
|
|
9067
|
+
id: "queue",
|
|
9068
|
+
run: `node ${CATCI} release github-queue-check`,
|
|
9069
|
+
shell: "bash",
|
|
9070
|
+
},
|
|
9071
|
+
]
|
|
9072
|
+
: []),
|
|
9051
9073
|
{
|
|
9052
9074
|
name: "create release",
|
|
9075
|
+
...(options.queueCheck
|
|
9076
|
+
? { if: "steps.queue.outputs.queued != 'true'" }
|
|
9077
|
+
: {}),
|
|
9053
9078
|
run: method.script,
|
|
9054
9079
|
shell: "bash",
|
|
9055
9080
|
},
|
|
@@ -9063,7 +9088,10 @@ const createReleaseJob = (config, images, needs, extraEnv) => {
|
|
|
9063
9088
|
* workflow after all other jobs; a force variant goes to the manual
|
|
9064
9089
|
* tasks workflow
|
|
9065
9090
|
* - otherwise (manual, the default): the release job goes to the
|
|
9066
|
-
* manual tasks workflow
|
|
9091
|
+
* manual tasks workflow. Its queue-check step releases right away on
|
|
9092
|
+
* a green main run and otherwise queues the release for the
|
|
9093
|
+
* release-on-green workflow (see getGithubReleaseOnGreenWorkflow) —
|
|
9094
|
+
* the counterpart of gitlab's queue button + executor pair.
|
|
9067
9095
|
*/
|
|
9068
9096
|
const getGithubReleaseJobs = (config, mainWorkflowJobIds, images) => {
|
|
9069
9097
|
var _a;
|
|
@@ -9071,28 +9099,89 @@ const getGithubReleaseJobs = (config, mainWorkflowJobIds, images) => {
|
|
|
9071
9099
|
if (((_a = config.releases) === null || _a === void 0 ? void 0 : _a.when) === "auto") {
|
|
9072
9100
|
return {
|
|
9073
9101
|
main: {
|
|
9074
|
-
"create-release": createReleaseJob(config, images,
|
|
9102
|
+
"create-release": createReleaseJob(config, images, {
|
|
9103
|
+
needs: mainWorkflowJobIds,
|
|
9104
|
+
}),
|
|
9075
9105
|
},
|
|
9076
9106
|
manual: {
|
|
9077
|
-
"force-create-release": createReleaseJob(config, images,
|
|
9107
|
+
"force-create-release": createReleaseJob(config, images, {
|
|
9108
|
+
extraEnv: method.forceReleaseVariables,
|
|
9109
|
+
}),
|
|
9078
9110
|
},
|
|
9079
9111
|
};
|
|
9080
9112
|
}
|
|
9081
9113
|
return {
|
|
9082
9114
|
main: {},
|
|
9083
9115
|
manual: {
|
|
9084
|
-
"create-release": createReleaseJob(config, images),
|
|
9116
|
+
"create-release": createReleaseJob(config, images, { queueCheck: true }),
|
|
9085
9117
|
// an explicit force option wherever the method distinguishes it
|
|
9086
9118
|
// (e.g. changesets: release a patch bump without changesets)
|
|
9087
9119
|
...(method.forceReleaseVariables
|
|
9088
9120
|
? {
|
|
9089
|
-
"force-create-release": createReleaseJob(config, images,
|
|
9121
|
+
"force-create-release": createReleaseJob(config, images, {
|
|
9122
|
+
extraEnv: method.forceReleaseVariables,
|
|
9123
|
+
}),
|
|
9090
9124
|
}
|
|
9091
9125
|
: {}),
|
|
9092
9126
|
},
|
|
9093
9127
|
};
|
|
9094
9128
|
};
|
|
9095
9129
|
exports.getGithubReleaseJobs = getGithubReleaseJobs;
|
|
9130
|
+
/**
|
|
9131
|
+
* the release-on-green workflow (manual release mode only): runs on
|
|
9132
|
+
* every completed main workflow run. A lightweight host job checks the
|
|
9133
|
+
* queue marker (written by the create-release task while the main
|
|
9134
|
+
* workflow was still running) — most runs have nothing queued and end
|
|
9135
|
+
* after that single quick job. When the marker matches the completed
|
|
9136
|
+
* run, the containerized release job creates the release.
|
|
9137
|
+
*
|
|
9138
|
+
* No image-build jobs here: the release image was ensured by the main
|
|
9139
|
+
* workflow run that just completed (same commit, same content-hashed
|
|
9140
|
+
* tag).
|
|
9141
|
+
*/
|
|
9142
|
+
const getGithubReleaseOnGreenWorkflow = (config, images, mainWorkflowName, workflowEnv) => {
|
|
9143
|
+
var _a;
|
|
9144
|
+
if (((_a = config.releases) === null || _a === void 0 ? void 0 : _a.when) === "auto") {
|
|
9145
|
+
return null;
|
|
9146
|
+
}
|
|
9147
|
+
return {
|
|
9148
|
+
name: "catladder release on green",
|
|
9149
|
+
on: {
|
|
9150
|
+
workflow_run: {
|
|
9151
|
+
workflows: [mainWorkflowName],
|
|
9152
|
+
types: ["completed"],
|
|
9153
|
+
},
|
|
9154
|
+
},
|
|
9155
|
+
env: workflowEnv,
|
|
9156
|
+
jobs: {
|
|
9157
|
+
guard: {
|
|
9158
|
+
name: "check queued release",
|
|
9159
|
+
"runs-on": "ubuntu-latest",
|
|
9160
|
+
// consuming the queue marker is a ref deletion
|
|
9161
|
+
permissions: { contents: "write" },
|
|
9162
|
+
env: { GITHUB_TOKEN: "${{ github.token }}" },
|
|
9163
|
+
outputs: { release: "${{ steps.guard.outputs.release }}" },
|
|
9164
|
+
steps: [
|
|
9165
|
+
{ name: "Checkout", uses: "actions/checkout@v4" },
|
|
9166
|
+
{
|
|
9167
|
+
name: "check queued release",
|
|
9168
|
+
id: "guard",
|
|
9169
|
+
run: `node ${CATCI} release github-queued-guard "\${{ github.event.workflow_run.head_sha }}" "\${{ github.event.workflow_run.conclusion }}"`,
|
|
9170
|
+
shell: "bash",
|
|
9171
|
+
},
|
|
9172
|
+
],
|
|
9173
|
+
},
|
|
9174
|
+
"create-release": {
|
|
9175
|
+
...createReleaseJob(config, images, {
|
|
9176
|
+
needs: ["guard"],
|
|
9177
|
+
skipImageNeeds: true,
|
|
9178
|
+
}),
|
|
9179
|
+
if: "${{ needs.guard.outputs.release == 'true' }}",
|
|
9180
|
+
},
|
|
9181
|
+
},
|
|
9182
|
+
};
|
|
9183
|
+
};
|
|
9184
|
+
exports.getGithubReleaseOnGreenWorkflow = getGithubReleaseOnGreenWorkflow;
|
|
9096
9185
|
/**
|
|
9097
9186
|
* the informational MR/PR check job of the release method (e.g. the
|
|
9098
9187
|
* changeset check), added to the mr workflow. continue-on-error keeps
|
|
@@ -9287,6 +9376,7 @@ const config_1 = __nccwpck_require__(17860);
|
|
|
9287
9376
|
const createAllJobs_1 = __nccwpck_require__(54114);
|
|
9288
9377
|
const jobImagesPlan_1 = __nccwpck_require__(47864);
|
|
9289
9378
|
const shippedCatci_1 = __nccwpck_require__(79024);
|
|
9379
|
+
const release_1 = __nccwpck_require__(38904);
|
|
9290
9380
|
const index_1 = __nccwpck_require__(68906);
|
|
9291
9381
|
const createGitlabJobs_1 = __nccwpck_require__(41509);
|
|
9292
9382
|
const createGitlabPipeline_1 = __nccwpck_require__(94411);
|
|
@@ -9304,7 +9394,7 @@ class GitlabBackend {
|
|
|
9304
9394
|
await (0, promises_1.rm)(this.generatedFolder, { force: true, recursive: true });
|
|
9305
9395
|
}
|
|
9306
9396
|
async createFiles(config) {
|
|
9307
|
-
const images = this.createImagesPlan();
|
|
9397
|
+
const images = this.createImagesPlan(config);
|
|
9308
9398
|
const includes = await this.createIncludes(config, images);
|
|
9309
9399
|
const mainFile = {
|
|
9310
9400
|
path: ".gitlab-ci.yml",
|
|
@@ -9321,15 +9411,15 @@ class GitlabBackend {
|
|
|
9321
9411
|
...(0, shippedCatci_1.getCatciGeneratedFiles)(),
|
|
9322
9412
|
];
|
|
9323
9413
|
}
|
|
9324
|
-
createImagesPlan() {
|
|
9325
|
-
return new jobImagesPlan_1.JobImagesPlan(this.type);
|
|
9414
|
+
createImagesPlan(config) {
|
|
9415
|
+
return new jobImagesPlan_1.JobImagesPlan(this.type, config.images);
|
|
9326
9416
|
}
|
|
9327
9417
|
/**
|
|
9328
9418
|
* the complete pipeline as one object (all includes merged into one),
|
|
9329
9419
|
* mainly for testing purposes
|
|
9330
9420
|
*/
|
|
9331
9421
|
async createCompletePipeline(config) {
|
|
9332
|
-
const includes = await this.createIncludes(config, this.createImagesPlan());
|
|
9422
|
+
const includes = await this.createIncludes(config, this.createImagesPlan(config));
|
|
9333
9423
|
return includes.reduce((acc, { content }) => {
|
|
9334
9424
|
return {
|
|
9335
9425
|
...acc,
|
|
@@ -9373,12 +9463,29 @@ class GitlabBackend {
|
|
|
9373
9463
|
}
|
|
9374
9464
|
async createPipeline(config, images) {
|
|
9375
9465
|
const stages = (0, getPipelineStages_1.getPipelineStages)(config);
|
|
9376
|
-
// release
|
|
9377
|
-
//
|
|
9378
|
-
//
|
|
9379
|
-
|
|
9466
|
+
// register the release image first: the per-trigger job creation
|
|
9467
|
+
// below emits the build jobs of all images registered so far. The
|
|
9468
|
+
// release jobs themselves are created afterwards — they need the
|
|
9469
|
+
// main-branch job list for the queued-release executor's needs.
|
|
9470
|
+
images.resolveRef({ catladderImage: (0, release_1.getReleaseMethod)(config).image });
|
|
9380
9471
|
// for all triggers create jobs and add base rules
|
|
9381
|
-
const
|
|
9472
|
+
const jobsPerTrigger = await Promise.all(config_1.ALL_PIPELINE_TRIGGERS.map(async (trigger) => ({
|
|
9473
|
+
trigger,
|
|
9474
|
+
jobs: await (0, createGitlabJobs_1.createGitlabJobs)(await (0, createAllJobs_1.createAllJobs)({ config, trigger, pipelineType: this.type }), images, getGitlabRulesForTrigger(trigger)),
|
|
9475
|
+
})));
|
|
9476
|
+
const allJobsPerTrigger = jobsPerTrigger.flatMap(({ jobs }) => jobs);
|
|
9477
|
+
// the jobs the queued-release executor waits for: everything that
|
|
9478
|
+
// runs automatically in a main-branch pipeline. Manual jobs (stop,
|
|
9479
|
+
// rollback, gates) are excluded — gitlab skips a job whose needs
|
|
9480
|
+
// include an unplayed manual job.
|
|
9481
|
+
const mainBranchJobNames = [
|
|
9482
|
+
...new Set(jobsPerTrigger
|
|
9483
|
+
.filter(({ trigger }) => trigger === "mainBranch")
|
|
9484
|
+
.flatMap(({ jobs }) => jobs)
|
|
9485
|
+
.filter(({ gitlabJob }) => !isManualGitlabJob(gitlabJob))
|
|
9486
|
+
.map(({ name }) => name)),
|
|
9487
|
+
];
|
|
9488
|
+
const releaseJobs = (0, gitlabReleaseJobs_1.getGitlabReleaseJobs)(config, images, mainBranchJobNames);
|
|
9382
9489
|
const allWorkspaceJobs = allJobsPerTrigger
|
|
9383
9490
|
.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
|
|
9384
9491
|
// this is purely for better readability in git diffs when you add new components
|
|
@@ -9444,6 +9551,14 @@ class GitlabBackend {
|
|
|
9444
9551
|
}
|
|
9445
9552
|
}
|
|
9446
9553
|
exports.GitlabBackend = GitlabBackend;
|
|
9554
|
+
/**
|
|
9555
|
+
* whether a job can end up waiting for a human in a main-branch
|
|
9556
|
+
* pipeline (catladder expresses manual-ness only through rules)
|
|
9557
|
+
*/
|
|
9558
|
+
function isManualGitlabJob(job) {
|
|
9559
|
+
var _a;
|
|
9560
|
+
return ((_a = job.rules) !== null && _a !== void 0 ? _a : []).some((rule) => rule.when === "manual");
|
|
9561
|
+
}
|
|
9447
9562
|
function getGitlabRulesForTrigger(trigger) {
|
|
9448
9563
|
// mainBranch: on push to main branch except it's a release commit
|
|
9449
9564
|
// mr: on merge request
|
|
@@ -9976,91 +10091,179 @@ exports.getPipelineStages = getPipelineStages;
|
|
|
9976
10091
|
"use strict";
|
|
9977
10092
|
|
|
9978
10093
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
9979
|
-
exports.getGitlabReleaseJobs = void 0;
|
|
10094
|
+
exports.getGitlabReleaseJobs = exports.RELEASE_EXECUTOR_JOB_NAME = exports.RELEASE_BUTTON_JOB_NAME = void 0;
|
|
9980
10095
|
const rules_1 = __nccwpck_require__(35086);
|
|
9981
10096
|
const release_1 = __nccwpck_require__(38904);
|
|
9982
10097
|
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.'";
|
|
10098
|
+
/**
|
|
10099
|
+
* gitlab rejects jobs with more than 50 `needs:` entries (default
|
|
10100
|
+
* limit). Pipelines beyond that fall back to the legacy manual release
|
|
10101
|
+
* job, which is only playable once all prior stages finished.
|
|
10102
|
+
*/
|
|
10103
|
+
const GITLAB_MAX_NEEDS = 50;
|
|
10104
|
+
exports.RELEASE_BUTTON_JOB_NAME = "create release";
|
|
10105
|
+
exports.RELEASE_EXECUTOR_JOB_NAME = "🚀 release once pipeline succeeds";
|
|
9983
10106
|
const baseReleaseRules = [
|
|
9984
10107
|
rules_1.RULE_NEVER_ON_RELEASE_COMMIT,
|
|
9985
10108
|
rules_1.RULE_NEVER_ON_AGENT_TRIGGER,
|
|
9986
10109
|
rules_1.RULE_NEVER_ON_SCHEDULE,
|
|
9987
10110
|
];
|
|
9988
|
-
|
|
10111
|
+
// NOTE: with `rules:`, when: manual defaults to allow_failure: false,
|
|
10112
|
+
// which turns every unclicked pipeline into status "blocked" — the
|
|
10113
|
+
// manual release jobs therefore carry allow_failure: true so pipelines
|
|
10114
|
+
// end "passed" when nobody releases
|
|
10115
|
+
const manualReleaseRules = [
|
|
10116
|
+
...baseReleaseRules,
|
|
10117
|
+
{
|
|
10118
|
+
if: rules_1.RULE_CONDITION_MAIN_BRANCH,
|
|
10119
|
+
when: "manual",
|
|
10120
|
+
},
|
|
10121
|
+
{
|
|
10122
|
+
if: rules_1.RULE_CONDITION_HOTFIX_BRANCH,
|
|
10123
|
+
when: "manual",
|
|
10124
|
+
},
|
|
10125
|
+
];
|
|
10126
|
+
const getGitlabReleaseJobs = (config, images,
|
|
10127
|
+
/**
|
|
10128
|
+
* names of the jobs that run automatically in main-branch pipelines
|
|
10129
|
+
* (manual jobs excluded: a needed-but-unplayed manual job would make
|
|
10130
|
+
* gitlab skip the release executor)
|
|
10131
|
+
*/
|
|
10132
|
+
mainBranchJobNames) => {
|
|
9989
10133
|
var _a;
|
|
9990
10134
|
const method = (0, release_1.getReleaseMethod)(config);
|
|
9991
10135
|
// NOTE: only the resolved image url is used, not the `need`: release
|
|
9992
|
-
// jobs have no `needs` on purpose —
|
|
9993
|
-
//
|
|
9994
|
-
//
|
|
9995
|
-
// longer wait for the build/deploy stages.
|
|
10136
|
+
// jobs have no `needs` on the whole pipeline on purpose — where they
|
|
10137
|
+
// must wait for it, the wait is expressed by enumerating the jobs
|
|
10138
|
+
// explicitly (see the executor below).
|
|
9996
10139
|
const releaseImage = images.resolveRef({
|
|
9997
10140
|
catladderImage: method.image,
|
|
9998
10141
|
}).image;
|
|
9999
|
-
|
|
10000
|
-
|
|
10001
|
-
|
|
10002
|
-
|
|
10003
|
-
|
|
10004
|
-
|
|
10005
|
-
|
|
10006
|
-
...baseReleaseRules,
|
|
10007
|
-
{
|
|
10008
|
-
if: rules_1.RULE_CONDITION_MAIN_BRANCH,
|
|
10009
|
-
when: ((_a = config.releases) === null || _a === void 0 ? void 0 : _a.when) === "auto" ? "on_success" : "manual",
|
|
10010
|
-
},
|
|
10011
|
-
{
|
|
10012
|
-
if: rules_1.RULE_CONDITION_HOTFIX_BRANCH,
|
|
10013
|
-
when: "manual",
|
|
10014
|
-
},
|
|
10015
|
-
],
|
|
10016
|
-
},
|
|
10142
|
+
const releaseJobBase = {
|
|
10143
|
+
stage: "release",
|
|
10144
|
+
image: releaseImage,
|
|
10145
|
+
script: [method.script],
|
|
10146
|
+
after_script: [EXPIRED_TOKEN_HELP],
|
|
10147
|
+
};
|
|
10148
|
+
const forceReleaseJob = {
|
|
10017
10149
|
["⚠️ force create release"]: {
|
|
10018
|
-
|
|
10019
|
-
image: releaseImage,
|
|
10020
|
-
script: [method.script],
|
|
10021
|
-
after_script: [EXPIRED_TOKEN_HELP],
|
|
10150
|
+
...releaseJobBase,
|
|
10022
10151
|
needs: [],
|
|
10152
|
+
allow_failure: true,
|
|
10023
10153
|
// force semantics of the method's script (e.g. changesets:
|
|
10024
10154
|
// release a patch bump even without pending changesets)
|
|
10025
10155
|
...(method.forceReleaseVariables
|
|
10026
10156
|
? { variables: method.forceReleaseVariables }
|
|
10027
10157
|
: {}),
|
|
10158
|
+
rules: manualReleaseRules,
|
|
10159
|
+
},
|
|
10160
|
+
};
|
|
10161
|
+
// informational MR check of the release method (e.g. the changeset
|
|
10162
|
+
// check): warns via allow_failure, the report lands in the MR
|
|
10163
|
+
// widget as an exposed artifact
|
|
10164
|
+
const checkJob = method.checkScript
|
|
10165
|
+
? {
|
|
10166
|
+
["🦋 changeset check"]: {
|
|
10167
|
+
stage: "test",
|
|
10168
|
+
image: releaseImage,
|
|
10169
|
+
script: [method.checkScript],
|
|
10170
|
+
allow_failure: true,
|
|
10171
|
+
artifacts: {
|
|
10172
|
+
paths: ["changeset-report.md"],
|
|
10173
|
+
expose_as: "changeset report",
|
|
10174
|
+
// the report must survive the warning exit
|
|
10175
|
+
when: "always",
|
|
10176
|
+
},
|
|
10177
|
+
rules: [
|
|
10178
|
+
rules_1.RULE_NEVER_ON_AGENT_TRIGGER,
|
|
10179
|
+
rules_1.RULE_NEVER_ON_SCHEDULE,
|
|
10180
|
+
rules_1.RULE_IS_MERGE_REQUEST,
|
|
10181
|
+
],
|
|
10182
|
+
},
|
|
10183
|
+
}
|
|
10184
|
+
: {};
|
|
10185
|
+
if (((_a = config.releases) === null || _a === void 0 ? void 0 : _a.when) === "auto") {
|
|
10186
|
+
return {
|
|
10187
|
+
[exports.RELEASE_BUTTON_JOB_NAME]: {
|
|
10188
|
+
...releaseJobBase,
|
|
10189
|
+
rules: [
|
|
10190
|
+
...baseReleaseRules,
|
|
10191
|
+
{
|
|
10192
|
+
if: rules_1.RULE_CONDITION_MAIN_BRANCH,
|
|
10193
|
+
when: "on_success",
|
|
10194
|
+
},
|
|
10195
|
+
{
|
|
10196
|
+
if: rules_1.RULE_CONDITION_HOTFIX_BRANCH,
|
|
10197
|
+
when: "manual",
|
|
10198
|
+
// per-rule: only the unclicked hotfix button must not
|
|
10199
|
+
// block — a failed automatic release stays loud
|
|
10200
|
+
allow_failure: true,
|
|
10201
|
+
},
|
|
10202
|
+
],
|
|
10203
|
+
},
|
|
10204
|
+
...forceReleaseJob,
|
|
10205
|
+
...checkJob,
|
|
10206
|
+
};
|
|
10207
|
+
}
|
|
10208
|
+
const executorNeeds = [
|
|
10209
|
+
{ job: exports.RELEASE_BUTTON_JOB_NAME },
|
|
10210
|
+
// optional: hotfix pipelines (and rules-filtered jobs) contain a
|
|
10211
|
+
// different job subset
|
|
10212
|
+
...mainBranchJobNames.map((name) => ({ job: name, optional: true })),
|
|
10213
|
+
];
|
|
10214
|
+
if (executorNeeds.length > GITLAB_MAX_NEEDS) {
|
|
10215
|
+
console.warn(`⚠️ the main-branch pipeline has more than ${GITLAB_MAX_NEEDS} jobs — ` +
|
|
10216
|
+
`the "${exports.RELEASE_BUTTON_JOB_NAME}" job falls back to being playable ` +
|
|
10217
|
+
`only once the pipeline finished (gitlab caps needs: at ${GITLAB_MAX_NEEDS})`);
|
|
10218
|
+
return {
|
|
10219
|
+
[exports.RELEASE_BUTTON_JOB_NAME]: {
|
|
10220
|
+
...releaseJobBase,
|
|
10221
|
+
allow_failure: true,
|
|
10222
|
+
rules: manualReleaseRules,
|
|
10223
|
+
},
|
|
10224
|
+
...forceReleaseJob,
|
|
10225
|
+
...checkJob,
|
|
10226
|
+
};
|
|
10227
|
+
}
|
|
10228
|
+
return {
|
|
10229
|
+
// the queue button: playable from the first second of the pipeline.
|
|
10230
|
+
// It only records the intent to release — the executor below does
|
|
10231
|
+
// the actual work once every other job succeeded. Clicking after the
|
|
10232
|
+
// pipeline finished works the same (the executor starts right away).
|
|
10233
|
+
[exports.RELEASE_BUTTON_JOB_NAME]: {
|
|
10234
|
+
stage: "release",
|
|
10235
|
+
image: releaseImage,
|
|
10236
|
+
script: [
|
|
10237
|
+
`echo "release queued — '${exports.RELEASE_EXECUTOR_JOB_NAME}' runs it as soon as every other job in this pipeline succeeded"`,
|
|
10238
|
+
],
|
|
10239
|
+
needs: [],
|
|
10240
|
+
// allow_failure keeps the unclicked button from blocking the
|
|
10241
|
+
// pipeline AND makes the executor's needs treat it as optional
|
|
10242
|
+
// (skipped executor instead of a forever-waiting pipeline)
|
|
10243
|
+
allow_failure: true,
|
|
10244
|
+
rules: manualReleaseRules,
|
|
10245
|
+
},
|
|
10246
|
+
// the executor: never played manually. Skipped when the button was
|
|
10247
|
+
// not clicked; otherwise starts automatically as soon as the button
|
|
10248
|
+
// and every other (non-manual) job of the pipeline succeeded, and
|
|
10249
|
+
// is skipped when any of them failed.
|
|
10250
|
+
[exports.RELEASE_EXECUTOR_JOB_NAME]: {
|
|
10251
|
+
...releaseJobBase,
|
|
10252
|
+
needs: executorNeeds,
|
|
10028
10253
|
rules: [
|
|
10029
10254
|
...baseReleaseRules,
|
|
10030
10255
|
{
|
|
10031
10256
|
if: rules_1.RULE_CONDITION_MAIN_BRANCH,
|
|
10032
|
-
when: "
|
|
10257
|
+
when: "on_success",
|
|
10033
10258
|
},
|
|
10034
10259
|
{
|
|
10035
10260
|
if: rules_1.RULE_CONDITION_HOTFIX_BRANCH,
|
|
10036
|
-
when: "
|
|
10261
|
+
when: "on_success",
|
|
10037
10262
|
},
|
|
10038
10263
|
],
|
|
10039
10264
|
},
|
|
10040
|
-
|
|
10041
|
-
|
|
10042
|
-
// widget as an exposed artifact
|
|
10043
|
-
...(method.checkScript
|
|
10044
|
-
? {
|
|
10045
|
-
["🦋 changeset check"]: {
|
|
10046
|
-
stage: "test",
|
|
10047
|
-
image: releaseImage,
|
|
10048
|
-
script: [method.checkScript],
|
|
10049
|
-
allow_failure: true,
|
|
10050
|
-
artifacts: {
|
|
10051
|
-
paths: ["changeset-report.md"],
|
|
10052
|
-
expose_as: "changeset report",
|
|
10053
|
-
// the report must survive the warning exit
|
|
10054
|
-
when: "always",
|
|
10055
|
-
},
|
|
10056
|
-
rules: [
|
|
10057
|
-
rules_1.RULE_NEVER_ON_AGENT_TRIGGER,
|
|
10058
|
-
rules_1.RULE_NEVER_ON_SCHEDULE,
|
|
10059
|
-
rules_1.RULE_IS_MERGE_REQUEST,
|
|
10060
|
-
],
|
|
10061
|
-
},
|
|
10062
|
-
}
|
|
10063
|
-
: {}),
|
|
10265
|
+
...forceReleaseJob,
|
|
10266
|
+
...checkJob,
|
|
10064
10267
|
};
|
|
10065
10268
|
};
|
|
10066
10269
|
exports.getGitlabReleaseJobs = getGitlabReleaseJobs;
|
|
@@ -12974,7 +13177,7 @@ exports.makeSecretEnvVarMapping = makeSecretEnvVarMapping;
|
|
|
12974
13177
|
"use strict";
|
|
12975
13178
|
|
|
12976
13179
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
12977
|
-
exports.computeCustomImageHash = void 0;
|
|
13180
|
+
exports.computeInlineImageHash = exports.computeCustomImageHash = void 0;
|
|
12978
13181
|
const crypto_1 = __nccwpck_require__(76982);
|
|
12979
13182
|
const fs_1 = __nccwpck_require__(79896);
|
|
12980
13183
|
const path_1 = __nccwpck_require__(16928);
|
|
@@ -12995,6 +13198,29 @@ function listFilesRecursive(dir) {
|
|
|
12995
13198
|
})
|
|
12996
13199
|
.sort();
|
|
12997
13200
|
}
|
|
13201
|
+
/**
|
|
13202
|
+
* the parts every image definition hashes on top of its content:
|
|
13203
|
+
* files outside the definition, and the build args
|
|
13204
|
+
*/
|
|
13205
|
+
function hashExtras(hash, config) {
|
|
13206
|
+
const watchedPaths = [];
|
|
13207
|
+
// Hash extra paths (for files outside the dir)
|
|
13208
|
+
if (config.hashExtraPaths) {
|
|
13209
|
+
// Extra paths are literal file paths (not globs) for simplicity
|
|
13210
|
+
const extraFiles = [...config.hashExtraPaths].sort();
|
|
13211
|
+
for (const file of extraFiles) {
|
|
13212
|
+
hash.update(file);
|
|
13213
|
+
hash.update((0, fs_1.readFileSync)(file));
|
|
13214
|
+
}
|
|
13215
|
+
watchedPaths.push(...config.hashExtraPaths);
|
|
13216
|
+
}
|
|
13217
|
+
// Hash build args (sorted keys for determinism)
|
|
13218
|
+
if (config.buildArgs) {
|
|
13219
|
+
const sorted = Object.entries(config.buildArgs).sort(([a], [b]) => a.localeCompare(b));
|
|
13220
|
+
hash.update(JSON.stringify(sorted));
|
|
13221
|
+
}
|
|
13222
|
+
return watchedPaths;
|
|
13223
|
+
}
|
|
12998
13224
|
function computeCustomImageHash(config) {
|
|
12999
13225
|
var _a;
|
|
13000
13226
|
const hash = (0, crypto_1.createHash)("sha256");
|
|
@@ -13015,27 +13241,29 @@ function computeCustomImageHash(config) {
|
|
|
13015
13241
|
}
|
|
13016
13242
|
watchedPaths.push(`${extraDir}/**/*`);
|
|
13017
13243
|
}
|
|
13018
|
-
|
|
13019
|
-
if (config.hashExtraPaths) {
|
|
13020
|
-
// Extra paths are literal file paths (not globs) for simplicity
|
|
13021
|
-
const extraFiles = [...config.hashExtraPaths].sort();
|
|
13022
|
-
for (const file of extraFiles) {
|
|
13023
|
-
hash.update(file);
|
|
13024
|
-
hash.update((0, fs_1.readFileSync)(file));
|
|
13025
|
-
}
|
|
13026
|
-
watchedPaths.push(...config.hashExtraPaths);
|
|
13027
|
-
}
|
|
13028
|
-
// Hash build args (sorted keys for determinism)
|
|
13029
|
-
if (config.buildArgs) {
|
|
13030
|
-
const sorted = Object.entries(config.buildArgs).sort(([a], [b]) => a.localeCompare(b));
|
|
13031
|
-
hash.update(JSON.stringify(sorted));
|
|
13032
|
-
}
|
|
13244
|
+
watchedPaths.push(...hashExtras(hash, config));
|
|
13033
13245
|
return {
|
|
13034
13246
|
hash: hash.digest("hex").slice(0, 12),
|
|
13035
13247
|
watchedPaths,
|
|
13036
13248
|
};
|
|
13037
13249
|
}
|
|
13038
13250
|
exports.computeCustomImageHash = computeCustomImageHash;
|
|
13251
|
+
/**
|
|
13252
|
+
* the hash of an image defined by an inline Dockerfile: its content
|
|
13253
|
+
* stands in for the definition dir. The build context is deliberately
|
|
13254
|
+
* NOT hashed (it may be the whole repository) — `hashExtraPaths` is how
|
|
13255
|
+
* context files opt into the hash.
|
|
13256
|
+
*/
|
|
13257
|
+
function computeInlineImageHash(config) {
|
|
13258
|
+
const hash = (0, crypto_1.createHash)("sha256");
|
|
13259
|
+
hash.update(config.dockerfileContent);
|
|
13260
|
+
const watchedPaths = hashExtras(hash, config);
|
|
13261
|
+
return {
|
|
13262
|
+
hash: hash.digest("hex").slice(0, 12),
|
|
13263
|
+
watchedPaths,
|
|
13264
|
+
};
|
|
13265
|
+
}
|
|
13266
|
+
exports.computeInlineImageHash = computeInlineImageHash;
|
|
13039
13267
|
//# sourceMappingURL=hash.js.map
|
|
13040
13268
|
|
|
13041
13269
|
/***/ }),
|
|
@@ -13046,7 +13274,7 @@ exports.computeCustomImageHash = computeCustomImageHash;
|
|
|
13046
13274
|
"use strict";
|
|
13047
13275
|
|
|
13048
13276
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
13049
|
-
exports.JobImagesPlan = exports.GENERATED_IMAGES_FOLDER = void 0;
|
|
13277
|
+
exports.JobImagesPlan = exports.PROJECT_IMAGES_FOLDER = exports.GENERATED_IMAGES_FOLDER = void 0;
|
|
13050
13278
|
const fs_1 = __nccwpck_require__(79896);
|
|
13051
13279
|
const path_1 = __nccwpck_require__(16928);
|
|
13052
13280
|
const ciVariables_1 = __nccwpck_require__(72700);
|
|
@@ -13054,6 +13282,7 @@ const docker_1 = __nccwpck_require__(83777);
|
|
|
13054
13282
|
const runner_1 = __nccwpck_require__(71065);
|
|
13055
13283
|
const gitlab_1 = __nccwpck_require__(37393);
|
|
13056
13284
|
const hash_1 = __nccwpck_require__(34092);
|
|
13285
|
+
const projectImages_1 = __nccwpck_require__(55537);
|
|
13057
13286
|
const shippedImages_1 = __nccwpck_require__(28687);
|
|
13058
13287
|
exports.GENERATED_IMAGES_FOLDER = ".catladder-generated/images";
|
|
13059
13288
|
/**
|
|
@@ -13061,7 +13290,44 @@ exports.GENERATED_IMAGES_FOLDER = ".catladder-generated/images";
|
|
|
13061
13290
|
* catladder image, so building images never depends on one (bootstrap)
|
|
13062
13291
|
*/
|
|
13063
13292
|
const BUILDER_IMAGE = "docker.io/docker:29.5.1";
|
|
13064
|
-
|
|
13293
|
+
/**
|
|
13294
|
+
* every planned image is consumed the same way: its url, plus an
|
|
13295
|
+
* optional dependency on its build job
|
|
13296
|
+
*/
|
|
13297
|
+
const toResolvedJobImage = ({ imageRef, jobName, }) => ({
|
|
13298
|
+
image: imageRef,
|
|
13299
|
+
fromRepoRegistry: true,
|
|
13300
|
+
need: {
|
|
13301
|
+
job: jobName,
|
|
13302
|
+
artifacts: false,
|
|
13303
|
+
optional: true,
|
|
13304
|
+
global: true,
|
|
13305
|
+
},
|
|
13306
|
+
});
|
|
13307
|
+
/**
|
|
13308
|
+
* materializes an image definition directory into the generated files
|
|
13309
|
+
* (used for the definitions catladder ships)
|
|
13310
|
+
*/
|
|
13311
|
+
const materializeDir = (sourceDir, targetDir) => listFilesRecursive(sourceDir).map((file) => ({
|
|
13312
|
+
path: (0, path_1.join)(targetDir, file),
|
|
13313
|
+
content: (0, fs_1.readFileSync)((0, path_1.join)(sourceDir, file), "utf-8"),
|
|
13314
|
+
}));
|
|
13315
|
+
// built-in (catladder-shipped) images carry the catladder marker in
|
|
13316
|
+
// their job name; project-declared images get the short plain name
|
|
13317
|
+
const ensureJobName = (name) => `🐳 catladder image ${name}`;
|
|
13318
|
+
const projectEnsureJobName = (name) => `🐳 image ${name}`;
|
|
13319
|
+
/**
|
|
13320
|
+
* the config file whose content feeds the project image hashes
|
|
13321
|
+
* (`buildArgs`, `hashExtraPaths`): the gitlab change detection has to
|
|
13322
|
+
* watch it, because a config-only change alters the image tag without
|
|
13323
|
+
* touching the image dir
|
|
13324
|
+
*/
|
|
13325
|
+
const CONFIG_FILE = "catladder.ts";
|
|
13326
|
+
/**
|
|
13327
|
+
* where inline project Dockerfiles are materialized — a subfolder, so a
|
|
13328
|
+
* project image can carry the same name as a shipped one
|
|
13329
|
+
*/
|
|
13330
|
+
exports.PROJECT_IMAGES_FOLDER = `${exports.GENERATED_IMAGES_FOLDER}/project`;
|
|
13065
13331
|
/**
|
|
13066
13332
|
* plans the catladder job images of one backend run: resolves image
|
|
13067
13333
|
* references, collects the image build jobs, and the image definitions
|
|
@@ -13072,33 +13338,30 @@ const ensureJobName = (name) => `🐳 job image ${name}`;
|
|
|
13072
13338
|
* Reference-driven: only images actually used by a job are built.
|
|
13073
13339
|
*/
|
|
13074
13340
|
class JobImagesPlan {
|
|
13075
|
-
constructor(pipelineType) {
|
|
13341
|
+
constructor(pipelineType, projectImages = {}) {
|
|
13076
13342
|
this.pipelineType = pipelineType;
|
|
13343
|
+
this.projectImages = projectImages;
|
|
13077
13344
|
this.used = new Map();
|
|
13345
|
+
this.usedProject = new Map();
|
|
13078
13346
|
}
|
|
13079
13347
|
/**
|
|
13080
13348
|
* resolves a job image (marker or concrete) to a concrete image url
|
|
13081
13349
|
* plus the build job dependency
|
|
13082
13350
|
*/
|
|
13083
13351
|
resolve(image) {
|
|
13084
|
-
if (
|
|
13085
|
-
return
|
|
13352
|
+
if ((0, runner_1.isCatladderImageRef)(image)) {
|
|
13353
|
+
return this.resolveRef(image);
|
|
13086
13354
|
}
|
|
13087
|
-
|
|
13355
|
+
if ((0, projectImages_1.isProjectImageRef)(image)) {
|
|
13356
|
+
return this.resolveProjectRef(image);
|
|
13357
|
+
}
|
|
13358
|
+
return { image };
|
|
13088
13359
|
}
|
|
13089
13360
|
resolveRef(ref) {
|
|
13090
|
-
|
|
13091
|
-
|
|
13092
|
-
|
|
13093
|
-
|
|
13094
|
-
fromRepoRegistry: true,
|
|
13095
|
-
need: {
|
|
13096
|
-
job: ensureJobName(name),
|
|
13097
|
-
artifacts: false,
|
|
13098
|
-
optional: true,
|
|
13099
|
-
global: true,
|
|
13100
|
-
},
|
|
13101
|
-
};
|
|
13361
|
+
return toResolvedJobImage(this.use(ref.catladderImage));
|
|
13362
|
+
}
|
|
13363
|
+
resolveProjectRef(ref) {
|
|
13364
|
+
return toResolvedJobImage(this.useProject(ref.image));
|
|
13102
13365
|
}
|
|
13103
13366
|
/**
|
|
13104
13367
|
* the registry image prefix. On github, container images can't use
|
|
@@ -13109,6 +13372,85 @@ class JobImagesPlan {
|
|
|
13109
13372
|
? "ghcr.io/${{ github.repository }}"
|
|
13110
13373
|
: `${(0, ciVariables_1.getCiVariable)({ pipelineType: this.pipelineType }, "registryImage")}`;
|
|
13111
13374
|
}
|
|
13375
|
+
useProject(name) {
|
|
13376
|
+
const existing = this.usedProject.get(name);
|
|
13377
|
+
if (existing) {
|
|
13378
|
+
return existing;
|
|
13379
|
+
}
|
|
13380
|
+
const imageConfig = this.projectImages[name];
|
|
13381
|
+
if (!imageConfig) {
|
|
13382
|
+
const declared = Object.keys(this.projectImages);
|
|
13383
|
+
throw new Error(`unknown job image "${name}" — ${declared.length > 0
|
|
13384
|
+
? `images declared in config.images: ${declared.join(", ")}`
|
|
13385
|
+
: "no images are declared in config.images"}`);
|
|
13386
|
+
}
|
|
13387
|
+
const source = this.planProjectSource(name, imageConfig);
|
|
13388
|
+
const planned = {
|
|
13389
|
+
...source,
|
|
13390
|
+
jobName: projectEnsureJobName(name),
|
|
13391
|
+
buildArgs: imageConfig.buildArgs,
|
|
13392
|
+
// `job-images/` keeps the project's job images apart from its
|
|
13393
|
+
// deployable component images (registry root) and build caches
|
|
13394
|
+
imageRef: `${this.registryImageRef()}/job-images/${name}:${source.hash}`,
|
|
13395
|
+
};
|
|
13396
|
+
this.usedProject.set(name, planned);
|
|
13397
|
+
return planned;
|
|
13398
|
+
}
|
|
13399
|
+
/**
|
|
13400
|
+
* the source-dependent half of a project image: an inline Dockerfile
|
|
13401
|
+
* is materialized into the generated files and hashed by its content,
|
|
13402
|
+
* a directory is hashed and watched in place
|
|
13403
|
+
*/
|
|
13404
|
+
planProjectSource(name, imageConfig) {
|
|
13405
|
+
var _a, _b;
|
|
13406
|
+
if (imageConfig.dockerfile !== undefined) {
|
|
13407
|
+
const content = toDockerfileContent(imageConfig.dockerfile);
|
|
13408
|
+
const dockerfilePath = `${exports.PROJECT_IMAGES_FOLDER}/${name}/Dockerfile`;
|
|
13409
|
+
const { hash, watchedPaths } = (0, hash_1.computeInlineImageHash)({
|
|
13410
|
+
dockerfileContent: content,
|
|
13411
|
+
hashExtraPaths: imageConfig.hashExtraPaths,
|
|
13412
|
+
buildArgs: imageConfig.buildArgs,
|
|
13413
|
+
});
|
|
13414
|
+
return {
|
|
13415
|
+
hash,
|
|
13416
|
+
dockerfilePath,
|
|
13417
|
+
// an inline definition carries no directory of its own — the
|
|
13418
|
+
// repository root is the sensible default context
|
|
13419
|
+
contextDir: (_a = imageConfig.context) !== null && _a !== void 0 ? _a : ".",
|
|
13420
|
+
watchedPaths: [
|
|
13421
|
+
`${exports.PROJECT_IMAGES_FOLDER}/${name}/**/*`,
|
|
13422
|
+
...watchedPaths,
|
|
13423
|
+
CONFIG_FILE,
|
|
13424
|
+
],
|
|
13425
|
+
files: [{ path: dockerfilePath, content }],
|
|
13426
|
+
};
|
|
13427
|
+
}
|
|
13428
|
+
const dir = imageConfig.dir;
|
|
13429
|
+
// fail at generation time with the offending image named: an
|
|
13430
|
+
// unreadable dir would otherwise surface as a bare fs ENOENT, and a
|
|
13431
|
+
// missing Dockerfile only as a docker build failure in CI
|
|
13432
|
+
if (!(0, fs_1.existsSync)(dir)) {
|
|
13433
|
+
throw new Error(`job image "${name}": the directory "${dir}" does not exist (config.images.${name}.dir, relative to the repository root)`);
|
|
13434
|
+
}
|
|
13435
|
+
if (!(0, fs_1.existsSync)((0, path_1.join)(dir, "Dockerfile"))) {
|
|
13436
|
+
throw new Error(`job image "${name}": no Dockerfile in "${dir}" (config.images.${name}.dir)`);
|
|
13437
|
+
}
|
|
13438
|
+
const { hash, watchedPaths } = (0, hash_1.computeCustomImageHash)({
|
|
13439
|
+
dir,
|
|
13440
|
+
hashExtraPaths: imageConfig.hashExtraPaths,
|
|
13441
|
+
buildArgs: imageConfig.buildArgs,
|
|
13442
|
+
});
|
|
13443
|
+
return {
|
|
13444
|
+
hash,
|
|
13445
|
+
dockerfilePath: `${dir}/Dockerfile`,
|
|
13446
|
+
contextDir: (_b = imageConfig.context) !== null && _b !== void 0 ? _b : dir,
|
|
13447
|
+
// the dir is committed in the project — watched in place, nothing
|
|
13448
|
+
// is materialized. The config file is watched too: buildArgs /
|
|
13449
|
+
// hashExtraPaths changes alter the tag without touching the dir
|
|
13450
|
+
watchedPaths: [...watchedPaths, CONFIG_FILE],
|
|
13451
|
+
files: [],
|
|
13452
|
+
};
|
|
13453
|
+
}
|
|
13112
13454
|
use(name) {
|
|
13113
13455
|
var _a;
|
|
13114
13456
|
const existing = this.used.get(name);
|
|
@@ -13123,27 +13465,43 @@ class JobImagesPlan {
|
|
|
13123
13465
|
dir: (0, shippedImages_1.getShippedImageDir)(name),
|
|
13124
13466
|
extraDirs: dependencies.map((dependency) => (0, shippedImages_1.getShippedImageDir)(dependency)),
|
|
13125
13467
|
});
|
|
13126
|
-
const
|
|
13127
|
-
|
|
13128
|
-
|
|
13468
|
+
const targetDir = `${exports.GENERATED_IMAGES_FOLDER}/${name}`;
|
|
13469
|
+
const planned = {
|
|
13470
|
+
hash,
|
|
13471
|
+
jobName: ensureJobName(name),
|
|
13472
|
+
imageRef: `${this.registryImageRef()}/catladder/${name}:${hash}`,
|
|
13473
|
+
dockerfilePath: `${targetDir}/Dockerfile`,
|
|
13474
|
+
// the jobs family INCLUDEs sibling images, so it builds from the
|
|
13475
|
+
// images root; the others from their own dir
|
|
13476
|
+
contextDir: shippedImages_1.RUNNER_IMAGE_BUILD_CONTEXT[name] === "root"
|
|
13477
|
+
? exports.GENERATED_IMAGES_FOLDER
|
|
13478
|
+
: targetDir,
|
|
13479
|
+
watchedPaths: [name, ...dependencies].map((dir) => `${exports.GENERATED_IMAGES_FOLDER}/${dir}/**/*`),
|
|
13480
|
+
// pipelines build from committed, reviewable files (and gitlab's
|
|
13481
|
+
// rules:changes can watch them)
|
|
13482
|
+
files: materializeDir((0, shippedImages_1.getShippedImageDir)(name), targetDir),
|
|
13483
|
+
};
|
|
13484
|
+
this.used.set(name, planned);
|
|
13485
|
+
return planned;
|
|
13129
13486
|
}
|
|
13130
13487
|
/**
|
|
13131
13488
|
* the image build jobs for all used images
|
|
13132
13489
|
*/
|
|
13133
13490
|
getEnsureJobs() {
|
|
13134
|
-
return [...this.used.
|
|
13491
|
+
return [...this.used.values(), ...this.usedProject.values()].map((planned) => this.createImageBuildJob(planned));
|
|
13135
13492
|
}
|
|
13136
|
-
|
|
13137
|
-
|
|
13493
|
+
createImageBuildJob({ jobName, imageRef, watchedPaths, dockerfilePath, contextDir, buildArgs, }) {
|
|
13494
|
+
const buildArgFlags = Object.entries(buildArgs !== null && buildArgs !== void 0 ? buildArgs : {})
|
|
13495
|
+
.map(([key, value]) => ` --build-arg '${key}=${value}'`)
|
|
13496
|
+
.join("");
|
|
13497
|
+
const buildCommand = `DOCKER_BUILDKIT=1 docker build -t ${imageRef}${buildArgFlags} -f ${dockerfilePath} ${contextDir}`;
|
|
13138
13498
|
const ctx = { pipelineType: this.pipelineType };
|
|
13139
13499
|
const registryUser = (0, ciVariables_1.getCiVariable)(ctx, "registryUser");
|
|
13140
13500
|
const jobToken = (0, ciVariables_1.getCiVariable)(ctx, "jobToken");
|
|
13141
13501
|
const registry = (0, ciVariables_1.getCiVariable)(ctx, "registry");
|
|
13142
|
-
const imageRef = `${this.registryImageRef()}/catladder/${name}:${hash}`;
|
|
13143
|
-
const watchedDirs = [name, ...((_a = shippedImages_1.RUNNER_IMAGE_DEPENDENCIES[name]) !== null && _a !== void 0 ? _a : [])];
|
|
13144
13502
|
const { services, runnerVariables } = (0, docker_1.getDockerJobBaseProps)();
|
|
13145
13503
|
return {
|
|
13146
|
-
name:
|
|
13504
|
+
name: jobName,
|
|
13147
13505
|
stage: "setup",
|
|
13148
13506
|
image: BUILDER_IMAGE,
|
|
13149
13507
|
services,
|
|
@@ -13153,7 +13511,9 @@ class JobImagesPlan {
|
|
|
13153
13511
|
// no equivalent and always runs (the existence check is fast)
|
|
13154
13512
|
rules: [
|
|
13155
13513
|
{
|
|
13156
|
-
|
|
13514
|
+
// a fresh array per job: the yaml serializer anchors repeated
|
|
13515
|
+
// objects, and a shared reference would reshuffle anchors
|
|
13516
|
+
changes: [...watchedPaths],
|
|
13157
13517
|
},
|
|
13158
13518
|
],
|
|
13159
13519
|
variables: {},
|
|
@@ -13167,31 +13527,33 @@ class JobImagesPlan {
|
|
|
13167
13527
|
` exit 0`,
|
|
13168
13528
|
`fi`,
|
|
13169
13529
|
]),
|
|
13170
|
-
...(0, gitlab_1.collapseableSection)("docker-build", "Building image")([
|
|
13171
|
-
`DOCKER_BUILDKIT=1 docker build -t ${imageRef} -f ${exports.GENERATED_IMAGES_FOLDER}/${name}/Dockerfile ${shippedImages_1.RUNNER_IMAGE_BUILD_CONTEXT[name] === "root"
|
|
13172
|
-
? exports.GENERATED_IMAGES_FOLDER
|
|
13173
|
-
: `${exports.GENERATED_IMAGES_FOLDER}/${name}`}`,
|
|
13174
|
-
]),
|
|
13530
|
+
...(0, gitlab_1.collapseableSection)("docker-build", "Building image")([buildCommand]),
|
|
13175
13531
|
...(0, gitlab_1.collapseableSection)("docker-push", "Pushing image")([`docker push ${imageRef}`]),
|
|
13176
13532
|
],
|
|
13177
13533
|
};
|
|
13178
13534
|
}
|
|
13179
13535
|
/**
|
|
13180
|
-
* the image definitions
|
|
13181
|
-
*
|
|
13182
|
-
*
|
|
13536
|
+
* the image definitions to materialize into the generated files (so
|
|
13537
|
+
* pipelines build from committed, reviewable files and gitlab's
|
|
13538
|
+
* rules:changes can watch them): the definitions catladder ships and
|
|
13539
|
+
* the project's inline Dockerfiles. A project image directory is
|
|
13540
|
+
* already committed and contributes nothing.
|
|
13183
13541
|
*/
|
|
13184
13542
|
getGeneratedFiles() {
|
|
13185
|
-
return [...this.used.
|
|
13186
|
-
const dir = (0, shippedImages_1.getShippedImageDir)(name);
|
|
13187
|
-
return listFilesRecursive(dir).map((file) => ({
|
|
13188
|
-
path: (0, path_1.join)(exports.GENERATED_IMAGES_FOLDER, name, file),
|
|
13189
|
-
content: (0, fs_1.readFileSync)((0, path_1.join)(dir, file), "utf-8"),
|
|
13190
|
-
}));
|
|
13191
|
-
});
|
|
13543
|
+
return [...this.used.values(), ...this.usedProject.values()].flatMap(({ files }) => files);
|
|
13192
13544
|
}
|
|
13193
13545
|
}
|
|
13194
13546
|
exports.JobImagesPlan = JobImagesPlan;
|
|
13547
|
+
/**
|
|
13548
|
+
* an inline Dockerfile is written as given — as one string, or as lines
|
|
13549
|
+
* joined with newlines (always newline-terminated)
|
|
13550
|
+
*/
|
|
13551
|
+
const toDockerfileContent = (dockerfile) => {
|
|
13552
|
+
const content = Array.isArray(dockerfile)
|
|
13553
|
+
? dockerfile.join("\n")
|
|
13554
|
+
: dockerfile;
|
|
13555
|
+
return content.endsWith("\n") ? content : `${content}\n`;
|
|
13556
|
+
};
|
|
13195
13557
|
const IGNORED_FILES = new Set([".DS_Store", "Thumbs.db"]);
|
|
13196
13558
|
const listFilesRecursive = (dir) => (0, fs_1.readdirSync)(dir, { recursive: true, encoding: "utf-8" })
|
|
13197
13559
|
.filter((entry) => {
|
|
@@ -13205,6 +13567,22 @@ const listFilesRecursive = (dir) => (0, fs_1.readdirSync)(dir, { recursive: true
|
|
|
13205
13567
|
|
|
13206
13568
|
/***/ }),
|
|
13207
13569
|
|
|
13570
|
+
/***/ 55537:
|
|
13571
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
13572
|
+
|
|
13573
|
+
"use strict";
|
|
13574
|
+
|
|
13575
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
13576
|
+
exports.isProjectImageRef = void 0;
|
|
13577
|
+
const isProjectImageRef = (value) => typeof value === "object" &&
|
|
13578
|
+
value !== null &&
|
|
13579
|
+
"image" in value &&
|
|
13580
|
+
typeof value.image === "string";
|
|
13581
|
+
exports.isProjectImageRef = isProjectImageRef;
|
|
13582
|
+
//# sourceMappingURL=projectImages.js.map
|
|
13583
|
+
|
|
13584
|
+
/***/ }),
|
|
13585
|
+
|
|
13208
13586
|
/***/ 28687:
|
|
13209
13587
|
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
13210
13588
|
|
|
@@ -15918,6 +16296,8 @@ __exportStar(__nccwpck_require__(68906), exports);
|
|
|
15918
16296
|
__exportStar(__nccwpck_require__(23040), exports);
|
|
15919
16297
|
__exportStar(__nccwpck_require__(35086), exports);
|
|
15920
16298
|
__exportStar(__nccwpck_require__(71065), exports);
|
|
16299
|
+
__exportStar(__nccwpck_require__(55537), exports);
|
|
16300
|
+
__exportStar(__nccwpck_require__(47864), exports);
|
|
15921
16301
|
__exportStar(__nccwpck_require__(80769), exports);
|
|
15922
16302
|
__exportStar(__nccwpck_require__(7732), exports);
|
|
15923
16303
|
__exportStar(__nccwpck_require__(66526), exports);
|