@catladder/cli 0.0.0-next-dccc0a36 → 0.0.0-next-89bce6d0

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 (45) 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 +340 -109
  8. package/dist/bundles/cli/index.js +340 -109
  9. package/dist/bundles/skills/catladder-deploys/references/deploy-types.md +1 -1
  10. package/dist/bundles/skills/catladder-releases/SKILL.md +13 -2
  11. package/dist/packages/bash/src/variables/VariableValueContainingReferences.d.ts +6 -0
  12. package/dist/packages/bash/src/variables/VariableValueContainingReferences.js +8 -0
  13. package/dist/packages/bash/src/variables/VariableValueContainingReferences.js.map +1 -1
  14. package/dist/packages/bash/src/variables/resolveAllReferences.d.ts +1 -2
  15. package/dist/packages/bash/src/variables/resolveAllReferences.js +4 -2
  16. package/dist/packages/bash/src/variables/resolveAllReferences.js.map +1 -1
  17. package/dist/packages/bash/src/variables/resolveAllReferencesOnce.d.ts +1 -4
  18. package/dist/packages/bash/src/variables/resolveAllReferencesOnce.js +4 -2
  19. package/dist/packages/bash/src/variables/resolveAllReferencesOnce.js.map +1 -1
  20. package/dist/packages/pipeline/src/backends/github/GithubBackend.js +7 -0
  21. package/dist/packages/pipeline/src/backends/github/GithubBackend.js.map +1 -1
  22. package/dist/packages/pipeline/src/backends/github/githubReleaseJobs.d.ts +18 -2
  23. package/dist/packages/pipeline/src/backends/github/githubReleaseJobs.js +92 -10
  24. package/dist/packages/pipeline/src/backends/github/githubReleaseJobs.js.map +1 -1
  25. package/dist/packages/pipeline/src/backends/gitlab/GitlabBackend.js +31 -5
  26. package/dist/packages/pipeline/src/backends/gitlab/GitlabBackend.js.map +1 -1
  27. package/dist/packages/pipeline/src/backends/gitlab/gitlabReleaseJobs.d.ts +285 -3
  28. package/dist/packages/pipeline/src/backends/gitlab/gitlabReleaseJobs.js +142 -54
  29. package/dist/packages/pipeline/src/backends/gitlab/gitlabReleaseJobs.js.map +1 -1
  30. package/dist/packages/pipeline/src/context/getEnvironmentVariables.js +17 -9
  31. package/dist/packages/pipeline/src/context/getEnvironmentVariables.js.map +1 -1
  32. package/dist/packages/pipeline/src/deploy/cloudRun/index.js +2 -2
  33. package/dist/packages/pipeline/src/deploy/cloudRun/index.js.map +1 -1
  34. package/dist/packages/pipeline/src/deploy/cloudRun/utils/database.d.ts +7 -9
  35. package/dist/packages/pipeline/src/deploy/cloudRun/utils/database.js +32 -24
  36. package/dist/packages/pipeline/src/deploy/cloudRun/utils/database.js.map +1 -1
  37. package/dist/packages/pipeline/src/deploy/index.d.ts +2 -2
  38. package/dist/packages/pipeline/src/types/github-types.d.ts +1 -0
  39. package/dist/packages/pipeline/src/types/release.d.ts +7 -1
  40. package/dist/skills/catladder-deploys/references/deploy-types.md +1 -1
  41. package/dist/skills/catladder-releases/SKILL.md +13 -2
  42. package/dist/tsconfig.tsbuildinfo +1 -1
  43. package/package.json +1 -1
  44. package/src/catci.ts +30 -0
  45. package/src/release/githubQueuedRelease.ts +173 -0
@@ -1902,6 +1902,14 @@ class VariableValueContainingReferences {
1902
1902
  ? []
1903
1903
  : [part]);
1904
1904
  }
1905
+ /**
1906
+ * concats values to this one and returns a new VariableValueContainingReferences.
1907
+ * mirrors String.prototype.concat / BashExpression.concat so a VariableValue
1908
+ * can be extended regardless of its concrete type
1909
+ */
1910
+ concat(...values) {
1911
+ return new VariableValueContainingReferences([...this.parts, ...values]);
1912
+ }
1905
1913
  toString(options = {
1906
1914
  quotes: false,
1907
1915
  }) {
@@ -1951,16 +1959,18 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
1951
1959
  exports.resolveAllReferences = void 0;
1952
1960
  const VariableValueContainingReferences_1 = __nccwpck_require__(70795);
1953
1961
  const resolveAllReferencesOnce_1 = __nccwpck_require__(98690);
1962
+ const hasUnresolvedReferences = (value) => value instanceof VariableValueContainingReferences_1.VariableValueContainingReferences &&
1963
+ value.parts.some((part) => part instanceof VariableValueContainingReferences_1.VariableReference);
1954
1964
  const resolveAllReferences = async (values, getEnvVars) => {
1955
1965
  // replace until there aren't any references left
1956
1966
  let result = values;
1957
1967
  let i = 0;
1958
- while (Object.values(result).some((value) => value.parts.some((part) => part instanceof VariableValueContainingReferences_1.VariableReference))) {
1968
+ while (Object.values(result).some(hasUnresolvedReferences)) {
1959
1969
  const replaced = await (0, resolveAllReferencesOnce_1.resolveAllReferencesOnce)(result, getEnvVars);
1960
1970
  result = replaced;
1961
1971
  i++;
1962
1972
  if (i > 1000) {
1963
- const unresolved = Object.entries(result).filter(([key, value]) => value.parts.some((part) => part instanceof VariableValueContainingReferences_1.VariableReference));
1973
+ const unresolved = Object.entries(result).filter(([key, value]) => hasUnresolvedReferences(value));
1964
1974
  throw new Error("Infinite loop detected in these variables: " +
1965
1975
  unresolved
1966
1976
  .map(([key, value]) => `${key} (last reference: ${value.parts.find((part) => part instanceof VariableValueContainingReferences_1.VariableReference)})`)
@@ -1984,7 +1994,9 @@ exports.resolveAllReferencesOnce = void 0;
1984
1994
  const VariableValueContainingReferences_1 = __nccwpck_require__(70795);
1985
1995
  const resolveReferencesOnce_1 = __nccwpck_require__(29767);
1986
1996
  const resolveAllReferencesOnce = async (values, getEnvVars) => {
1987
- const allReferences = Object.values(values).flatMap((value) => value === null || value === void 0 ? void 0 : value.parts.filter((part) => part instanceof VariableValueContainingReferences_1.VariableReference));
1997
+ const allReferences = Object.values(values).flatMap((value) => value instanceof VariableValueContainingReferences_1.VariableValueContainingReferences
1998
+ ? value.parts.filter((part) => part instanceof VariableValueContainingReferences_1.VariableReference)
1999
+ : []);
1988
2000
  const allComponentsUnique = Array.from(new Set(allReferences.map((reference) => reference.componentName)));
1989
2001
  const allEnvVarsInComponents = Object.fromEntries(await Promise.all(allComponentsUnique.map(async (componentName) => [
1990
2002
  componentName,
@@ -1992,7 +2004,7 @@ const resolveAllReferencesOnce = async (values, getEnvVars) => {
1992
2004
  ])));
1993
2005
  return Object.fromEntries(Object.entries(values).map(([key, value]) => [
1994
2006
  key,
1995
- value !== null && value !== undefined
2007
+ value instanceof VariableValueContainingReferences_1.VariableValueContainingReferences
1996
2008
  ? (0, resolveReferencesOnce_1.resolveReferencesOnce)(value, ({ componentName, variableName }) => {
1997
2009
  return allEnvVarsInComponents[componentName][variableName];
1998
2010
  })
@@ -2463,6 +2475,13 @@ class GithubBackend {
2463
2475
  manualJobConditions[id] = `inputs.job == '${id}'`;
2464
2476
  manualDispatchOptions.push(id);
2465
2477
  }
2478
+ // manual release mode: the workflow that runs a queued release
2479
+ // once the main workflow completed green
2480
+ const releaseOnGreen = (0, githubReleaseJobs_1.getGithubReleaseOnGreenWorkflow)(config, images, TRIGGER_WORKFLOWS.mainBranch.name, workflowEnv);
2481
+ if (releaseOnGreen) {
2482
+ workflows[`${GENERATED_FILE_PREFIX}release-on-green.yml`] =
2483
+ releaseOnGreen;
2484
+ }
2466
2485
  }
2467
2486
  if (Object.keys(jobs).length > 0) {
2468
2487
  workflows[`${GENERATED_FILE_PREFIX}${workflowFileName(trigger)}`] = {
@@ -3007,16 +3026,21 @@ exports.getUploadProviderIds = getUploadProviderIds;
3007
3026
  "use strict";
3008
3027
 
3009
3028
  Object.defineProperty(exports, "__esModule", ({ value: true }));
3010
- exports.getGithubReleaseCheckJobs = exports.getGithubReleaseJobs = void 0;
3029
+ exports.getGithubReleaseCheckJobs = exports.getGithubReleaseOnGreenWorkflow = exports.getGithubReleaseJobs = void 0;
3011
3030
  const createGithubJobs_1 = __nccwpck_require__(57877);
3012
3031
  const release_1 = __nccwpck_require__(38904);
3013
- const createReleaseJob = (config, images, needs, extraEnv) => {
3032
+ const shippedCatci_1 = __nccwpck_require__(79024);
3033
+ const CATCI = `${shippedCatci_1.GENERATED_CATCI_FOLDER}/index.js`;
3034
+ const createReleaseJob = (config, images, options = {}) => {
3035
+ var _a;
3014
3036
  const method = (0, release_1.getReleaseMethod)(config);
3015
3037
  const resolved = images.resolveRef({ catladderImage: method.image });
3016
- const imageNeeds = resolved.need && typeof resolved.need === "object"
3038
+ const imageNeeds = !options.skipImageNeeds &&
3039
+ resolved.need &&
3040
+ typeof resolved.need === "object"
3017
3041
  ? [(0, createGithubJobs_1.githubGlobalJobId)(resolved.need.job)]
3018
3042
  : [];
3019
- const allNeeds = [...imageNeeds, ...(needs !== null && needs !== void 0 ? needs : [])];
3043
+ const allNeeds = [...imageNeeds, ...((_a = options.needs) !== null && _a !== void 0 ? _a : [])];
3020
3044
  return {
3021
3045
  name: "create release",
3022
3046
  "runs-on": "ubuntu-latest",
@@ -3045,7 +3069,7 @@ const createReleaseJob = (config, images, needs, extraEnv) => {
3045
3069
  ...(allNeeds.length > 0 ? { needs: allNeeds } : {}),
3046
3070
  env: {
3047
3071
  GITHUB_TOKEN: "${{ github.token }}",
3048
- ...extraEnv,
3072
+ ...options.extraEnv,
3049
3073
  },
3050
3074
  steps: [
3051
3075
  {
@@ -3055,8 +3079,21 @@ const createReleaseJob = (config, images, needs, extraEnv) => {
3055
3079
  // tags since the last release) to determine the version
3056
3080
  with: { "fetch-depth": 0 },
3057
3081
  },
3082
+ ...(options.queueCheck
3083
+ ? [
3084
+ {
3085
+ name: "check main workflow",
3086
+ id: "queue",
3087
+ run: `node ${CATCI} release github-queue-check`,
3088
+ shell: "bash",
3089
+ },
3090
+ ]
3091
+ : []),
3058
3092
  {
3059
3093
  name: "create release",
3094
+ ...(options.queueCheck
3095
+ ? { if: "steps.queue.outputs.queued != 'true'" }
3096
+ : {}),
3060
3097
  run: method.script,
3061
3098
  shell: "bash",
3062
3099
  },
@@ -3070,7 +3107,10 @@ const createReleaseJob = (config, images, needs, extraEnv) => {
3070
3107
  * workflow after all other jobs; a force variant goes to the manual
3071
3108
  * tasks workflow
3072
3109
  * - otherwise (manual, the default): the release job goes to the
3073
- * manual tasks workflow
3110
+ * manual tasks workflow. Its queue-check step releases right away on
3111
+ * a green main run and otherwise queues the release for the
3112
+ * release-on-green workflow (see getGithubReleaseOnGreenWorkflow) —
3113
+ * the counterpart of gitlab's queue button + executor pair.
3074
3114
  */
3075
3115
  const getGithubReleaseJobs = (config, mainWorkflowJobIds, images) => {
3076
3116
  var _a;
@@ -3078,28 +3118,89 @@ const getGithubReleaseJobs = (config, mainWorkflowJobIds, images) => {
3078
3118
  if (((_a = config.releases) === null || _a === void 0 ? void 0 : _a.when) === "auto") {
3079
3119
  return {
3080
3120
  main: {
3081
- "create-release": createReleaseJob(config, images, mainWorkflowJobIds),
3121
+ "create-release": createReleaseJob(config, images, {
3122
+ needs: mainWorkflowJobIds,
3123
+ }),
3082
3124
  },
3083
3125
  manual: {
3084
- "force-create-release": createReleaseJob(config, images, undefined, method.forceReleaseVariables),
3126
+ "force-create-release": createReleaseJob(config, images, {
3127
+ extraEnv: method.forceReleaseVariables,
3128
+ }),
3085
3129
  },
3086
3130
  };
3087
3131
  }
3088
3132
  return {
3089
3133
  main: {},
3090
3134
  manual: {
3091
- "create-release": createReleaseJob(config, images),
3135
+ "create-release": createReleaseJob(config, images, { queueCheck: true }),
3092
3136
  // an explicit force option wherever the method distinguishes it
3093
3137
  // (e.g. changesets: release a patch bump without changesets)
3094
3138
  ...(method.forceReleaseVariables
3095
3139
  ? {
3096
- "force-create-release": createReleaseJob(config, images, undefined, method.forceReleaseVariables),
3140
+ "force-create-release": createReleaseJob(config, images, {
3141
+ extraEnv: method.forceReleaseVariables,
3142
+ }),
3097
3143
  }
3098
3144
  : {}),
3099
3145
  },
3100
3146
  };
3101
3147
  };
3102
3148
  exports.getGithubReleaseJobs = getGithubReleaseJobs;
3149
+ /**
3150
+ * the release-on-green workflow (manual release mode only): runs on
3151
+ * every completed main workflow run. A lightweight host job checks the
3152
+ * queue marker (written by the create-release task while the main
3153
+ * workflow was still running) — most runs have nothing queued and end
3154
+ * after that single quick job. When the marker matches the completed
3155
+ * run, the containerized release job creates the release.
3156
+ *
3157
+ * No image-build jobs here: the release image was ensured by the main
3158
+ * workflow run that just completed (same commit, same content-hashed
3159
+ * tag).
3160
+ */
3161
+ const getGithubReleaseOnGreenWorkflow = (config, images, mainWorkflowName, workflowEnv) => {
3162
+ var _a;
3163
+ if (((_a = config.releases) === null || _a === void 0 ? void 0 : _a.when) === "auto") {
3164
+ return null;
3165
+ }
3166
+ return {
3167
+ name: "catladder release on green",
3168
+ on: {
3169
+ workflow_run: {
3170
+ workflows: [mainWorkflowName],
3171
+ types: ["completed"],
3172
+ },
3173
+ },
3174
+ env: workflowEnv,
3175
+ jobs: {
3176
+ guard: {
3177
+ name: "check queued release",
3178
+ "runs-on": "ubuntu-latest",
3179
+ // consuming the queue marker is a ref deletion
3180
+ permissions: { contents: "write" },
3181
+ env: { GITHUB_TOKEN: "${{ github.token }}" },
3182
+ outputs: { release: "${{ steps.guard.outputs.release }}" },
3183
+ steps: [
3184
+ { name: "Checkout", uses: "actions/checkout@v4" },
3185
+ {
3186
+ name: "check queued release",
3187
+ id: "guard",
3188
+ run: `node ${CATCI} release github-queued-guard "\${{ github.event.workflow_run.head_sha }}" "\${{ github.event.workflow_run.conclusion }}"`,
3189
+ shell: "bash",
3190
+ },
3191
+ ],
3192
+ },
3193
+ "create-release": {
3194
+ ...createReleaseJob(config, images, {
3195
+ needs: ["guard"],
3196
+ skipImageNeeds: true,
3197
+ }),
3198
+ if: "${{ needs.guard.outputs.release == 'true' }}",
3199
+ },
3200
+ },
3201
+ };
3202
+ };
3203
+ exports.getGithubReleaseOnGreenWorkflow = getGithubReleaseOnGreenWorkflow;
3103
3204
  /**
3104
3205
  * the informational MR/PR check job of the release method (e.g. the
3105
3206
  * changeset check), added to the mr workflow. continue-on-error keeps
@@ -3294,6 +3395,7 @@ const config_1 = __nccwpck_require__(17860);
3294
3395
  const createAllJobs_1 = __nccwpck_require__(54114);
3295
3396
  const jobImagesPlan_1 = __nccwpck_require__(47864);
3296
3397
  const shippedCatci_1 = __nccwpck_require__(79024);
3398
+ const release_1 = __nccwpck_require__(38904);
3297
3399
  const index_1 = __nccwpck_require__(68906);
3298
3400
  const createGitlabJobs_1 = __nccwpck_require__(41509);
3299
3401
  const createGitlabPipeline_1 = __nccwpck_require__(94411);
@@ -3380,12 +3482,29 @@ class GitlabBackend {
3380
3482
  }
3381
3483
  async createPipeline(config, images) {
3382
3484
  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);
3485
+ // register the release image first: the per-trigger job creation
3486
+ // below emits the build jobs of all images registered so far. The
3487
+ // release jobs themselves are created afterwards — they need the
3488
+ // main-branch job list for the queued-release executor's needs.
3489
+ images.resolveRef({ catladderImage: (0, release_1.getReleaseMethod)(config).image });
3387
3490
  // 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());
3491
+ const jobsPerTrigger = await Promise.all(config_1.ALL_PIPELINE_TRIGGERS.map(async (trigger) => ({
3492
+ trigger,
3493
+ jobs: await (0, createGitlabJobs_1.createGitlabJobs)(await (0, createAllJobs_1.createAllJobs)({ config, trigger, pipelineType: this.type }), images, getGitlabRulesForTrigger(trigger)),
3494
+ })));
3495
+ const allJobsPerTrigger = jobsPerTrigger.flatMap(({ jobs }) => jobs);
3496
+ // the jobs the queued-release executor waits for: everything that
3497
+ // runs automatically in a main-branch pipeline. Manual jobs (stop,
3498
+ // rollback, gates) are excluded — gitlab skips a job whose needs
3499
+ // include an unplayed manual job.
3500
+ const mainBranchJobNames = [
3501
+ ...new Set(jobsPerTrigger
3502
+ .filter(({ trigger }) => trigger === "mainBranch")
3503
+ .flatMap(({ jobs }) => jobs)
3504
+ .filter(({ gitlabJob }) => !isManualGitlabJob(gitlabJob))
3505
+ .map(({ name }) => name)),
3506
+ ];
3507
+ const releaseJobs = (0, gitlabReleaseJobs_1.getGitlabReleaseJobs)(config, images, mainBranchJobNames);
3389
3508
  const allWorkspaceJobs = allJobsPerTrigger
3390
3509
  .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
3510
  // this is purely for better readability in git diffs when you add new components
@@ -3451,6 +3570,14 @@ class GitlabBackend {
3451
3570
  }
3452
3571
  }
3453
3572
  exports.GitlabBackend = GitlabBackend;
3573
+ /**
3574
+ * whether a job can end up waiting for a human in a main-branch
3575
+ * pipeline (catladder expresses manual-ness only through rules)
3576
+ */
3577
+ function isManualGitlabJob(job) {
3578
+ var _a;
3579
+ return ((_a = job.rules) !== null && _a !== void 0 ? _a : []).some((rule) => rule.when === "manual");
3580
+ }
3454
3581
  function getGitlabRulesForTrigger(trigger) {
3455
3582
  // mainBranch: on push to main branch except it's a release commit
3456
3583
  // mr: on merge request
@@ -3983,91 +4110,179 @@ exports.getPipelineStages = getPipelineStages;
3983
4110
  "use strict";
3984
4111
 
3985
4112
  Object.defineProperty(exports, "__esModule", ({ value: true }));
3986
- exports.getGitlabReleaseJobs = void 0;
4113
+ exports.getGitlabReleaseJobs = exports.RELEASE_EXECUTOR_JOB_NAME = exports.RELEASE_BUTTON_JOB_NAME = void 0;
3987
4114
  const rules_1 = __nccwpck_require__(35086);
3988
4115
  const release_1 = __nccwpck_require__(38904);
3989
4116
  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.'";
4117
+ /**
4118
+ * gitlab rejects jobs with more than 50 `needs:` entries (default
4119
+ * limit). Pipelines beyond that fall back to the legacy manual release
4120
+ * job, which is only playable once all prior stages finished.
4121
+ */
4122
+ const GITLAB_MAX_NEEDS = 50;
4123
+ exports.RELEASE_BUTTON_JOB_NAME = "create release";
4124
+ exports.RELEASE_EXECUTOR_JOB_NAME = "🚀 release once pipeline succeeds";
3990
4125
  const baseReleaseRules = [
3991
4126
  rules_1.RULE_NEVER_ON_RELEASE_COMMIT,
3992
4127
  rules_1.RULE_NEVER_ON_AGENT_TRIGGER,
3993
4128
  rules_1.RULE_NEVER_ON_SCHEDULE,
3994
4129
  ];
3995
- const getGitlabReleaseJobs = (config, images) => {
4130
+ // NOTE: with `rules:`, when: manual defaults to allow_failure: false,
4131
+ // which turns every unclicked pipeline into status "blocked" — the
4132
+ // manual release jobs therefore carry allow_failure: true so pipelines
4133
+ // end "passed" when nobody releases
4134
+ const manualReleaseRules = [
4135
+ ...baseReleaseRules,
4136
+ {
4137
+ if: rules_1.RULE_CONDITION_MAIN_BRANCH,
4138
+ when: "manual",
4139
+ },
4140
+ {
4141
+ if: rules_1.RULE_CONDITION_HOTFIX_BRANCH,
4142
+ when: "manual",
4143
+ },
4144
+ ];
4145
+ const getGitlabReleaseJobs = (config, images,
4146
+ /**
4147
+ * names of the jobs that run automatically in main-branch pipelines
4148
+ * (manual jobs excluded: a needed-but-unplayed manual job would make
4149
+ * gitlab skip the release executor)
4150
+ */
4151
+ mainBranchJobNames) => {
3996
4152
  var _a;
3997
4153
  const method = (0, release_1.getReleaseMethod)(config);
3998
4154
  // 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.
4155
+ // jobs have no `needs` on the whole pipeline on purpose — where they
4156
+ // must wait for it, the wait is expressed by enumerating the jobs
4157
+ // explicitly (see the executor below).
4003
4158
  const releaseImage = images.resolveRef({
4004
4159
  catladderImage: method.image,
4005
4160
  }).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
- },
4161
+ const releaseJobBase = {
4162
+ stage: "release",
4163
+ image: releaseImage,
4164
+ script: [method.script],
4165
+ after_script: [EXPIRED_TOKEN_HELP],
4166
+ };
4167
+ const forceReleaseJob = {
4024
4168
  ["⚠️ force create release"]: {
4025
- stage: "release",
4026
- image: releaseImage,
4027
- script: [method.script],
4028
- after_script: [EXPIRED_TOKEN_HELP],
4169
+ ...releaseJobBase,
4029
4170
  needs: [],
4171
+ allow_failure: true,
4030
4172
  // force semantics of the method's script (e.g. changesets:
4031
4173
  // release a patch bump even without pending changesets)
4032
4174
  ...(method.forceReleaseVariables
4033
4175
  ? { variables: method.forceReleaseVariables }
4034
4176
  : {}),
4177
+ rules: manualReleaseRules,
4178
+ },
4179
+ };
4180
+ // informational MR check of the release method (e.g. the changeset
4181
+ // check): warns via allow_failure, the report lands in the MR
4182
+ // widget as an exposed artifact
4183
+ const checkJob = method.checkScript
4184
+ ? {
4185
+ ["🦋 changeset check"]: {
4186
+ stage: "test",
4187
+ image: releaseImage,
4188
+ script: [method.checkScript],
4189
+ allow_failure: true,
4190
+ artifacts: {
4191
+ paths: ["changeset-report.md"],
4192
+ expose_as: "changeset report",
4193
+ // the report must survive the warning exit
4194
+ when: "always",
4195
+ },
4196
+ rules: [
4197
+ rules_1.RULE_NEVER_ON_AGENT_TRIGGER,
4198
+ rules_1.RULE_NEVER_ON_SCHEDULE,
4199
+ rules_1.RULE_IS_MERGE_REQUEST,
4200
+ ],
4201
+ },
4202
+ }
4203
+ : {};
4204
+ if (((_a = config.releases) === null || _a === void 0 ? void 0 : _a.when) === "auto") {
4205
+ return {
4206
+ [exports.RELEASE_BUTTON_JOB_NAME]: {
4207
+ ...releaseJobBase,
4208
+ rules: [
4209
+ ...baseReleaseRules,
4210
+ {
4211
+ if: rules_1.RULE_CONDITION_MAIN_BRANCH,
4212
+ when: "on_success",
4213
+ },
4214
+ {
4215
+ if: rules_1.RULE_CONDITION_HOTFIX_BRANCH,
4216
+ when: "manual",
4217
+ // per-rule: only the unclicked hotfix button must not
4218
+ // block — a failed automatic release stays loud
4219
+ allow_failure: true,
4220
+ },
4221
+ ],
4222
+ },
4223
+ ...forceReleaseJob,
4224
+ ...checkJob,
4225
+ };
4226
+ }
4227
+ const executorNeeds = [
4228
+ { job: exports.RELEASE_BUTTON_JOB_NAME },
4229
+ // optional: hotfix pipelines (and rules-filtered jobs) contain a
4230
+ // different job subset
4231
+ ...mainBranchJobNames.map((name) => ({ job: name, optional: true })),
4232
+ ];
4233
+ if (executorNeeds.length > GITLAB_MAX_NEEDS) {
4234
+ console.warn(`⚠️ the main-branch pipeline has more than ${GITLAB_MAX_NEEDS} jobs — ` +
4235
+ `the "${exports.RELEASE_BUTTON_JOB_NAME}" job falls back to being playable ` +
4236
+ `only once the pipeline finished (gitlab caps needs: at ${GITLAB_MAX_NEEDS})`);
4237
+ return {
4238
+ [exports.RELEASE_BUTTON_JOB_NAME]: {
4239
+ ...releaseJobBase,
4240
+ allow_failure: true,
4241
+ rules: manualReleaseRules,
4242
+ },
4243
+ ...forceReleaseJob,
4244
+ ...checkJob,
4245
+ };
4246
+ }
4247
+ return {
4248
+ // the queue button: playable from the first second of the pipeline.
4249
+ // It only records the intent to release — the executor below does
4250
+ // the actual work once every other job succeeded. Clicking after the
4251
+ // pipeline finished works the same (the executor starts right away).
4252
+ [exports.RELEASE_BUTTON_JOB_NAME]: {
4253
+ stage: "release",
4254
+ image: releaseImage,
4255
+ script: [
4256
+ `echo "release queued — '${exports.RELEASE_EXECUTOR_JOB_NAME}' runs it as soon as every other job in this pipeline succeeded"`,
4257
+ ],
4258
+ needs: [],
4259
+ // allow_failure keeps the unclicked button from blocking the
4260
+ // pipeline AND makes the executor's needs treat it as optional
4261
+ // (skipped executor instead of a forever-waiting pipeline)
4262
+ allow_failure: true,
4263
+ rules: manualReleaseRules,
4264
+ },
4265
+ // the executor: never played manually. Skipped when the button was
4266
+ // not clicked; otherwise starts automatically as soon as the button
4267
+ // and every other (non-manual) job of the pipeline succeeded, and
4268
+ // is skipped when any of them failed.
4269
+ [exports.RELEASE_EXECUTOR_JOB_NAME]: {
4270
+ ...releaseJobBase,
4271
+ needs: executorNeeds,
4035
4272
  rules: [
4036
4273
  ...baseReleaseRules,
4037
4274
  {
4038
4275
  if: rules_1.RULE_CONDITION_MAIN_BRANCH,
4039
- when: "manual",
4276
+ when: "on_success",
4040
4277
  },
4041
4278
  {
4042
4279
  if: rules_1.RULE_CONDITION_HOTFIX_BRANCH,
4043
- when: "manual",
4280
+ when: "on_success",
4044
4281
  },
4045
4282
  ],
4046
4283
  },
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
- : {}),
4284
+ ...forceReleaseJob,
4285
+ ...checkJob,
4071
4286
  };
4072
4287
  };
4073
4288
  exports.getGitlabReleaseJobs = getGitlabReleaseJobs;
@@ -6726,7 +6941,9 @@ const getEnvironmentVariables = async (ctx, options = {}) => {
6726
6941
  const additionalEnvVars = deployConfigRaw
6727
6942
  ? deploy_1.DEPLOY_TYPES[deployConfigRaw.type].getAdditionalEnvVars(environmentContext)
6728
6943
  : {};
6729
- const HOSTNAME_INTERNAL = (_b = additionalEnvVars.HOSTNAME_INTERNAL) !== null && _b !== void 0 ? _b : "unknown-host.example.com";
6944
+ // deploy types provide HOSTNAME_INTERNAL as a plain string or bash
6945
+ // expression (never containing references)
6946
+ const HOSTNAME_INTERNAL = ((_b = additionalEnvVars.HOSTNAME_INTERNAL) !== null && _b !== void 0 ? _b : "unknown-host.example.com");
6730
6947
  host = (_c = envConfigRaw === null || envConfigRaw === void 0 ? void 0 : envConfigRaw.host) !== null && _c !== void 0 ? _c : HOSTNAME_INTERNAL;
6731
6948
  url = (0, bash_1.joinBashExpressions)(["https://", host]);
6732
6949
  predefinedVariables = {
@@ -6754,8 +6971,18 @@ const getEnvironmentVariables = async (ctx, options = {}) => {
6754
6971
  componentName: ctx.componentName,
6755
6972
  }),
6756
6973
  ]));
6757
- const publicEnvVars = ((_g = options.shouldResolveReferences) !== null && _g !== void 0 ? _g : true)
6758
- ? await (0, bash_3.resolveAllReferences)(publicEnvVarsUnresolved, async (otherComponentName) => {
6974
+ // predefined variables may contain references too (e.g. the embedded
6975
+ // database connection string references the component's own DB_* vars), so
6976
+ // we resolve references over the merged map. This way public var overrides
6977
+ // (like DB_PASSWORD: "${otherComponent:DB_PASSWORD}") are picked up by the
6978
+ // predefined variables that reference them
6979
+ const mergedEnvVars = {
6980
+ ...predefinedVariables,
6981
+ ...secretEnvVars,
6982
+ ...publicEnvVarsUnresolved,
6983
+ };
6984
+ const resolvedEnvVars = ((_g = options.shouldResolveReferences) !== null && _g !== void 0 ? _g : true)
6985
+ ? await (0, bash_3.resolveAllReferences)(mergedEnvVars, async (otherComponentName) => {
6759
6986
  const { envVars: otherEnvVars } = await (0, exports.getEnvironmentVariables)({
6760
6987
  ...ctx,
6761
6988
  componentName: otherComponentName,
@@ -6764,12 +6991,8 @@ const getEnvironmentVariables = async (ctx, options = {}) => {
6764
6991
  });
6765
6992
  return otherEnvVars;
6766
6993
  })
6767
- : publicEnvVarsUnresolved;
6768
- const envVars = addIndexVar({
6769
- ...predefinedVariables,
6770
- ...secretEnvVars,
6771
- ...publicEnvVars,
6772
- });
6994
+ : mergedEnvVars;
6995
+ const envVars = addIndexVar(resolvedEnvVars);
6773
6996
  return {
6774
6997
  envVars,
6775
6998
  secretEnvVarKeys,
@@ -8760,8 +8983,8 @@ const getCloudSqlVariables = ({ deployConfigRaw, environmentSlugPrefix, env, com
8760
8983
  };
8761
8984
  return {
8762
8985
  ...dbVars,
8763
- DATABASE_URL: (0, database_1.getDatabaseConnectionString)(deployConfigRaw.cloudSql, dbVars).concat(additionalQueryParamsString),
8764
- DATABASE_JDBC_URL: (0, database_1.getDatabaseJdbcUrl)(dbVars, (_c = deployConfigRaw.cloudSql.dbConnectionStringVariablesMode) !== null && _c !== void 0 ? _c : database_1.DEFAULT_DB_VARIABLES_MODE),
8986
+ DATABASE_URL: (0, database_1.getDatabaseConnectionString)(deployConfigRaw.cloudSql, dbVars, componentName).concat(additionalQueryParamsString),
8987
+ DATABASE_JDBC_URL: (0, database_1.getDatabaseJdbcUrl)(dbVars, (_c = deployConfigRaw.cloudSql.dbConnectionStringVariablesMode) !== null && _c !== void 0 ? _c : database_1.DEFAULT_DB_VARIABLES_MODE, componentName),
8765
8988
  };
8766
8989
  }
8767
8990
  return {};
@@ -8953,64 +9176,72 @@ const getDatabaseCreateScript = (context, deployConfig) => {
8953
9176
  ]);
8954
9177
  };
8955
9178
  exports.getDatabaseCreateScript = getDatabaseCreateScript;
8956
- exports.DEFAULT_DB_VARIABLES_MODE = "legacy";
8957
- const getVariableOrValue = (key, variables, mode) => {
8958
- return mode === "legacy" ? `$${key}` : variables[key];
8959
- };
8960
- const getDatabaseJdbcUrl = (variables, mode) => {
9179
+ exports.DEFAULT_DB_VARIABLES_MODE = "embedded";
9180
+ const getVariableOrValue = (key, mode, componentName) => {
9181
+ // in embedded mode we reference the component's own env var, which gets
9182
+ // resolved to its final value (including vars.public overrides) after all
9183
+ // env vars have been merged
9184
+ return mode === "legacy"
9185
+ ? `$${key}`
9186
+ : new bash_1.VariableReference(componentName, key);
9187
+ };
9188
+ const joinDbUrlParts = (parts, mode) => mode === "legacy"
9189
+ ? (0, bash_1.joinBashExpressions)(parts)
9190
+ : new bash_1.VariableValueContainingReferences(parts);
9191
+ const getDatabaseJdbcUrl = (variables, mode, componentName) => {
8961
9192
  const parts = [
8962
9193
  "jdbc:postgresql:///",
8963
- getVariableOrValue("DB_NAME", variables, mode),
9194
+ getVariableOrValue("DB_NAME", mode, componentName),
8964
9195
  "?cloudSqlInstance=",
8965
- getVariableOrValue("CLOUD_SQL_INSTANCE_CONNECTION_NAME", variables, mode),
9196
+ getVariableOrValue("CLOUD_SQL_INSTANCE_CONNECTION_NAME", mode, componentName),
8966
9197
  "&socketFactory=com.google.cloud.sql.postgres.SocketFactory&user=",
8967
- getVariableOrValue("DB_USER", variables, mode),
9198
+ getVariableOrValue("DB_USER", mode, componentName),
8968
9199
  "&password=",
8969
- getVariableOrValue("DB_PASSWORD", variables, mode),
9200
+ getVariableOrValue("DB_PASSWORD", mode, componentName),
8970
9201
  ];
8971
- return (0, bash_1.joinBashExpressions)(parts);
9202
+ return joinDbUrlParts(parts, mode);
8972
9203
  };
8973
9204
  exports.getDatabaseJdbcUrl = getDatabaseJdbcUrl;
8974
- const getRailsDatabaseConnectionString = (variables, mode) => {
9205
+ const getRailsDatabaseConnectionString = (variables, mode, componentName) => {
8975
9206
  const parts = [
8976
9207
  "postgresql://",
8977
- getVariableOrValue("DB_USER", variables, mode),
9208
+ getVariableOrValue("DB_USER", mode, componentName),
8978
9209
  ":",
8979
- getVariableOrValue("DB_PASSWORD", variables, mode),
9210
+ getVariableOrValue("DB_PASSWORD", mode, componentName),
8980
9211
  "@",
8981
9212
  encodeURIComponent(`/cloudsql/${variables.CLOUD_SQL_INSTANCE_CONNECTION_NAME}`),
8982
9213
  "/",
8983
- getVariableOrValue("DB_NAME", variables, mode),
9214
+ getVariableOrValue("DB_NAME", mode, componentName),
8984
9215
  "?",
8985
9216
  ];
8986
- return (0, bash_1.joinBashExpressions)(parts);
9217
+ return joinDbUrlParts(parts, mode);
8987
9218
  };
8988
9219
  exports.getRailsDatabaseConnectionString = getRailsDatabaseConnectionString;
8989
- const getPrismaDatabaseConnectionString = (variables, mode) => {
9220
+ const getPrismaDatabaseConnectionString = (variables, mode, componentName) => {
8990
9221
  const parts = [
8991
9222
  "postgresql://",
8992
- getVariableOrValue("DB_USER", variables, mode),
9223
+ getVariableOrValue("DB_USER", mode, componentName),
8993
9224
  ":",
8994
- getVariableOrValue("DB_PASSWORD", variables, mode),
9225
+ getVariableOrValue("DB_PASSWORD", mode, componentName),
8995
9226
  "@localhost/",
8996
- getVariableOrValue("DB_NAME", variables, mode),
9227
+ getVariableOrValue("DB_NAME", mode, componentName),
8997
9228
  "?host=/cloudsql/",
8998
- getVariableOrValue("CLOUD_SQL_INSTANCE_CONNECTION_NAME", variables, mode),
9229
+ getVariableOrValue("CLOUD_SQL_INSTANCE_CONNECTION_NAME", mode, componentName),
8999
9230
  ];
9000
- return (0, bash_1.joinBashExpressions)(parts);
9231
+ return joinDbUrlParts(parts, mode);
9001
9232
  };
9002
9233
  exports.getPrismaDatabaseConnectionString = getPrismaDatabaseConnectionString;
9003
- const getDatabaseConnectionString = (config, variables) => {
9234
+ const getDatabaseConnectionString = (config, variables, componentName) => {
9004
9235
  var _a;
9005
9236
  const mode = (_a = config.dbConnectionStringVariablesMode) !== null && _a !== void 0 ? _a : exports.DEFAULT_DB_VARIABLES_MODE;
9006
9237
  switch (config.dbConnectionStringFormat) {
9007
9238
  case "jdbc":
9008
- return (0, exports.getDatabaseJdbcUrl)(variables, mode);
9239
+ return (0, exports.getDatabaseJdbcUrl)(variables, mode, componentName);
9009
9240
  case "rails":
9010
- return (0, exports.getRailsDatabaseConnectionString)(variables, mode);
9241
+ return (0, exports.getRailsDatabaseConnectionString)(variables, mode, componentName);
9011
9242
  default:
9012
9243
  // prisma
9013
- return (0, exports.getPrismaDatabaseConnectionString)(variables, mode);
9244
+ return (0, exports.getPrismaDatabaseConnectionString)(variables, mode, componentName);
9014
9245
  }
9015
9246
  };
9016
9247
  exports.getDatabaseConnectionString = getDatabaseConnectionString;