@catladder/cli 0.0.0-next-dccc0a36 → 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.
Files changed (26) hide show
  1. package/dist/apps/cli/src/catci.js +23 -0
  2. package/dist/apps/cli/src/catci.js.map +1 -1
  3. package/dist/apps/cli/src/release/githubQueuedRelease.d.ts +14 -0
  4. package/dist/apps/cli/src/release/githubQueuedRelease.js +152 -0
  5. package/dist/apps/cli/src/release/githubQueuedRelease.js.map +1 -0
  6. package/dist/bundles/catci/index.js +3 -3
  7. package/dist/bundles/catenv/index.js +272 -69
  8. package/dist/bundles/cli/index.js +272 -69
  9. package/dist/bundles/skills/catladder-releases/SKILL.md +13 -2
  10. package/dist/packages/pipeline/src/backends/github/GithubBackend.js +7 -0
  11. package/dist/packages/pipeline/src/backends/github/GithubBackend.js.map +1 -1
  12. package/dist/packages/pipeline/src/backends/github/githubReleaseJobs.d.ts +18 -2
  13. package/dist/packages/pipeline/src/backends/github/githubReleaseJobs.js +92 -10
  14. package/dist/packages/pipeline/src/backends/github/githubReleaseJobs.js.map +1 -1
  15. package/dist/packages/pipeline/src/backends/gitlab/GitlabBackend.js +31 -5
  16. package/dist/packages/pipeline/src/backends/gitlab/GitlabBackend.js.map +1 -1
  17. package/dist/packages/pipeline/src/backends/gitlab/gitlabReleaseJobs.d.ts +285 -3
  18. package/dist/packages/pipeline/src/backends/gitlab/gitlabReleaseJobs.js +142 -54
  19. package/dist/packages/pipeline/src/backends/gitlab/gitlabReleaseJobs.js.map +1 -1
  20. package/dist/packages/pipeline/src/types/github-types.d.ts +1 -0
  21. package/dist/packages/pipeline/src/types/release.d.ts +7 -1
  22. package/dist/skills/catladder-releases/SKILL.md +13 -2
  23. package/dist/tsconfig.tsbuildinfo +1 -1
  24. package/package.json +1 -1
  25. package/src/catci.ts +30 -0
  26. package/src/release/githubQueuedRelease.ts +173 -0
@@ -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 createReleaseJob = (config, images, needs, extraEnv) => {
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 = resolved.need && typeof resolved.need === "object"
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 && needs !== void 0 ? needs : [])];
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, mainWorkflowJobIds),
3109
+ "create-release": createReleaseJob(config, images, {
3110
+ needs: mainWorkflowJobIds,
3111
+ }),
3082
3112
  },
3083
3113
  manual: {
3084
- "force-create-release": createReleaseJob(config, images, undefined, method.forceReleaseVariables),
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, undefined, method.forceReleaseVariables),
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);
@@ -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 jobs first: they register their image on the plan, and the
3384
- // per-trigger job creation below emits the build jobs of all images
3385
- // registered so far
3386
- const releaseJobs = (0, gitlabReleaseJobs_1.getGitlabReleaseJobs)(config, images);
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 allJobsPerTrigger = await Promise.all(config_1.ALL_PIPELINE_TRIGGERS.map(async (trigger) => await (0, createGitlabJobs_1.createGitlabJobs)(await (0, createAllJobs_1.createAllJobs)({ config, trigger, pipelineType: this.type }), images, getGitlabRulesForTrigger(trigger)))).then((j) => j.flat());
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
- const getGitlabReleaseJobs = (config, images) => {
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 — their stage ordering already
4000
- // guarantees the image build job (setup stage) ran, and adding a
4001
- // `needs` would switch gitlab to DAG mode, so the release job would no
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
- return {
4007
- ["create release"]: {
4008
- stage: "release",
4009
- image: releaseImage,
4010
- script: [method.script],
4011
- after_script: [EXPIRED_TOKEN_HELP],
4012
- rules: [
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
- stage: "release",
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: "manual",
4264
+ when: "on_success",
4040
4265
  },
4041
4266
  {
4042
4267
  if: rules_1.RULE_CONDITION_HOTFIX_BRANCH,
4043
- when: "manual",
4268
+ when: "on_success",
4044
4269
  },
4045
4270
  ],
4046
4271
  },
4047
- // informational MR check of the release method (e.g. the changeset
4048
- // check): warns via allow_failure, the report lands in the MR
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;