@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
@@ -7895,6 +7895,14 @@ class VariableValueContainingReferences {
7895
7895
  ? []
7896
7896
  : [part]);
7897
7897
  }
7898
+ /**
7899
+ * concats values to this one and returns a new VariableValueContainingReferences.
7900
+ * mirrors String.prototype.concat / BashExpression.concat so a VariableValue
7901
+ * can be extended regardless of its concrete type
7902
+ */
7903
+ concat(...values) {
7904
+ return new VariableValueContainingReferences([...this.parts, ...values]);
7905
+ }
7898
7906
  toString(options = {
7899
7907
  quotes: false,
7900
7908
  }) {
@@ -7944,16 +7952,18 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
7944
7952
  exports.resolveAllReferences = void 0;
7945
7953
  const VariableValueContainingReferences_1 = __nccwpck_require__(70795);
7946
7954
  const resolveAllReferencesOnce_1 = __nccwpck_require__(98690);
7955
+ const hasUnresolvedReferences = (value) => value instanceof VariableValueContainingReferences_1.VariableValueContainingReferences &&
7956
+ value.parts.some((part) => part instanceof VariableValueContainingReferences_1.VariableReference);
7947
7957
  const resolveAllReferences = async (values, getEnvVars) => {
7948
7958
  // replace until there aren't any references left
7949
7959
  let result = values;
7950
7960
  let i = 0;
7951
- while (Object.values(result).some((value) => value.parts.some((part) => part instanceof VariableValueContainingReferences_1.VariableReference))) {
7961
+ while (Object.values(result).some(hasUnresolvedReferences)) {
7952
7962
  const replaced = await (0, resolveAllReferencesOnce_1.resolveAllReferencesOnce)(result, getEnvVars);
7953
7963
  result = replaced;
7954
7964
  i++;
7955
7965
  if (i > 1000) {
7956
- const unresolved = Object.entries(result).filter(([key, value]) => value.parts.some((part) => part instanceof VariableValueContainingReferences_1.VariableReference));
7966
+ const unresolved = Object.entries(result).filter(([key, value]) => hasUnresolvedReferences(value));
7957
7967
  throw new Error("Infinite loop detected in these variables: " +
7958
7968
  unresolved
7959
7969
  .map(([key, value]) => `${key} (last reference: ${value.parts.find((part) => part instanceof VariableValueContainingReferences_1.VariableReference)})`)
@@ -7977,7 +7987,9 @@ exports.resolveAllReferencesOnce = void 0;
7977
7987
  const VariableValueContainingReferences_1 = __nccwpck_require__(70795);
7978
7988
  const resolveReferencesOnce_1 = __nccwpck_require__(29767);
7979
7989
  const resolveAllReferencesOnce = async (values, getEnvVars) => {
7980
- const allReferences = Object.values(values).flatMap((value) => value === null || value === void 0 ? void 0 : value.parts.filter((part) => part instanceof VariableValueContainingReferences_1.VariableReference));
7990
+ const allReferences = Object.values(values).flatMap((value) => value instanceof VariableValueContainingReferences_1.VariableValueContainingReferences
7991
+ ? value.parts.filter((part) => part instanceof VariableValueContainingReferences_1.VariableReference)
7992
+ : []);
7981
7993
  const allComponentsUnique = Array.from(new Set(allReferences.map((reference) => reference.componentName)));
7982
7994
  const allEnvVarsInComponents = Object.fromEntries(await Promise.all(allComponentsUnique.map(async (componentName) => [
7983
7995
  componentName,
@@ -7985,7 +7997,7 @@ const resolveAllReferencesOnce = async (values, getEnvVars) => {
7985
7997
  ])));
7986
7998
  return Object.fromEntries(Object.entries(values).map(([key, value]) => [
7987
7999
  key,
7988
- value !== null && value !== undefined
8000
+ value instanceof VariableValueContainingReferences_1.VariableValueContainingReferences
7989
8001
  ? (0, resolveReferencesOnce_1.resolveReferencesOnce)(value, ({ componentName, variableName }) => {
7990
8002
  return allEnvVarsInComponents[componentName][variableName];
7991
8003
  })
@@ -8456,6 +8468,13 @@ class GithubBackend {
8456
8468
  manualJobConditions[id] = `inputs.job == '${id}'`;
8457
8469
  manualDispatchOptions.push(id);
8458
8470
  }
8471
+ // manual release mode: the workflow that runs a queued release
8472
+ // once the main workflow completed green
8473
+ const releaseOnGreen = (0, githubReleaseJobs_1.getGithubReleaseOnGreenWorkflow)(config, images, TRIGGER_WORKFLOWS.mainBranch.name, workflowEnv);
8474
+ if (releaseOnGreen) {
8475
+ workflows[`${GENERATED_FILE_PREFIX}release-on-green.yml`] =
8476
+ releaseOnGreen;
8477
+ }
8459
8478
  }
8460
8479
  if (Object.keys(jobs).length > 0) {
8461
8480
  workflows[`${GENERATED_FILE_PREFIX}${workflowFileName(trigger)}`] = {
@@ -9000,16 +9019,21 @@ exports.getUploadProviderIds = getUploadProviderIds;
9000
9019
  "use strict";
9001
9020
 
9002
9021
  Object.defineProperty(exports, "__esModule", ({ value: true }));
9003
- exports.getGithubReleaseCheckJobs = exports.getGithubReleaseJobs = void 0;
9022
+ exports.getGithubReleaseCheckJobs = exports.getGithubReleaseOnGreenWorkflow = exports.getGithubReleaseJobs = void 0;
9004
9023
  const createGithubJobs_1 = __nccwpck_require__(57877);
9005
9024
  const release_1 = __nccwpck_require__(38904);
9006
- const createReleaseJob = (config, images, needs, extraEnv) => {
9025
+ const shippedCatci_1 = __nccwpck_require__(79024);
9026
+ const CATCI = `${shippedCatci_1.GENERATED_CATCI_FOLDER}/index.js`;
9027
+ const createReleaseJob = (config, images, options = {}) => {
9028
+ var _a;
9007
9029
  const method = (0, release_1.getReleaseMethod)(config);
9008
9030
  const resolved = images.resolveRef({ catladderImage: method.image });
9009
- const imageNeeds = resolved.need && typeof resolved.need === "object"
9031
+ const imageNeeds = !options.skipImageNeeds &&
9032
+ resolved.need &&
9033
+ typeof resolved.need === "object"
9010
9034
  ? [(0, createGithubJobs_1.githubGlobalJobId)(resolved.need.job)]
9011
9035
  : [];
9012
- const allNeeds = [...imageNeeds, ...(needs !== null && needs !== void 0 ? needs : [])];
9036
+ const allNeeds = [...imageNeeds, ...((_a = options.needs) !== null && _a !== void 0 ? _a : [])];
9013
9037
  return {
9014
9038
  name: "create release",
9015
9039
  "runs-on": "ubuntu-latest",
@@ -9038,7 +9062,7 @@ const createReleaseJob = (config, images, needs, extraEnv) => {
9038
9062
  ...(allNeeds.length > 0 ? { needs: allNeeds } : {}),
9039
9063
  env: {
9040
9064
  GITHUB_TOKEN: "${{ github.token }}",
9041
- ...extraEnv,
9065
+ ...options.extraEnv,
9042
9066
  },
9043
9067
  steps: [
9044
9068
  {
@@ -9048,8 +9072,21 @@ const createReleaseJob = (config, images, needs, extraEnv) => {
9048
9072
  // tags since the last release) to determine the version
9049
9073
  with: { "fetch-depth": 0 },
9050
9074
  },
9075
+ ...(options.queueCheck
9076
+ ? [
9077
+ {
9078
+ name: "check main workflow",
9079
+ id: "queue",
9080
+ run: `node ${CATCI} release github-queue-check`,
9081
+ shell: "bash",
9082
+ },
9083
+ ]
9084
+ : []),
9051
9085
  {
9052
9086
  name: "create release",
9087
+ ...(options.queueCheck
9088
+ ? { if: "steps.queue.outputs.queued != 'true'" }
9089
+ : {}),
9053
9090
  run: method.script,
9054
9091
  shell: "bash",
9055
9092
  },
@@ -9063,7 +9100,10 @@ const createReleaseJob = (config, images, needs, extraEnv) => {
9063
9100
  * workflow after all other jobs; a force variant goes to the manual
9064
9101
  * tasks workflow
9065
9102
  * - otherwise (manual, the default): the release job goes to the
9066
- * manual tasks workflow
9103
+ * manual tasks workflow. Its queue-check step releases right away on
9104
+ * a green main run and otherwise queues the release for the
9105
+ * release-on-green workflow (see getGithubReleaseOnGreenWorkflow) —
9106
+ * the counterpart of gitlab's queue button + executor pair.
9067
9107
  */
9068
9108
  const getGithubReleaseJobs = (config, mainWorkflowJobIds, images) => {
9069
9109
  var _a;
@@ -9071,28 +9111,89 @@ const getGithubReleaseJobs = (config, mainWorkflowJobIds, images) => {
9071
9111
  if (((_a = config.releases) === null || _a === void 0 ? void 0 : _a.when) === "auto") {
9072
9112
  return {
9073
9113
  main: {
9074
- "create-release": createReleaseJob(config, images, mainWorkflowJobIds),
9114
+ "create-release": createReleaseJob(config, images, {
9115
+ needs: mainWorkflowJobIds,
9116
+ }),
9075
9117
  },
9076
9118
  manual: {
9077
- "force-create-release": createReleaseJob(config, images, undefined, method.forceReleaseVariables),
9119
+ "force-create-release": createReleaseJob(config, images, {
9120
+ extraEnv: method.forceReleaseVariables,
9121
+ }),
9078
9122
  },
9079
9123
  };
9080
9124
  }
9081
9125
  return {
9082
9126
  main: {},
9083
9127
  manual: {
9084
- "create-release": createReleaseJob(config, images),
9128
+ "create-release": createReleaseJob(config, images, { queueCheck: true }),
9085
9129
  // an explicit force option wherever the method distinguishes it
9086
9130
  // (e.g. changesets: release a patch bump without changesets)
9087
9131
  ...(method.forceReleaseVariables
9088
9132
  ? {
9089
- "force-create-release": createReleaseJob(config, images, undefined, method.forceReleaseVariables),
9133
+ "force-create-release": createReleaseJob(config, images, {
9134
+ extraEnv: method.forceReleaseVariables,
9135
+ }),
9090
9136
  }
9091
9137
  : {}),
9092
9138
  },
9093
9139
  };
9094
9140
  };
9095
9141
  exports.getGithubReleaseJobs = getGithubReleaseJobs;
9142
+ /**
9143
+ * the release-on-green workflow (manual release mode only): runs on
9144
+ * every completed main workflow run. A lightweight host job checks the
9145
+ * queue marker (written by the create-release task while the main
9146
+ * workflow was still running) — most runs have nothing queued and end
9147
+ * after that single quick job. When the marker matches the completed
9148
+ * run, the containerized release job creates the release.
9149
+ *
9150
+ * No image-build jobs here: the release image was ensured by the main
9151
+ * workflow run that just completed (same commit, same content-hashed
9152
+ * tag).
9153
+ */
9154
+ const getGithubReleaseOnGreenWorkflow = (config, images, mainWorkflowName, workflowEnv) => {
9155
+ var _a;
9156
+ if (((_a = config.releases) === null || _a === void 0 ? void 0 : _a.when) === "auto") {
9157
+ return null;
9158
+ }
9159
+ return {
9160
+ name: "catladder release on green",
9161
+ on: {
9162
+ workflow_run: {
9163
+ workflows: [mainWorkflowName],
9164
+ types: ["completed"],
9165
+ },
9166
+ },
9167
+ env: workflowEnv,
9168
+ jobs: {
9169
+ guard: {
9170
+ name: "check queued release",
9171
+ "runs-on": "ubuntu-latest",
9172
+ // consuming the queue marker is a ref deletion
9173
+ permissions: { contents: "write" },
9174
+ env: { GITHUB_TOKEN: "${{ github.token }}" },
9175
+ outputs: { release: "${{ steps.guard.outputs.release }}" },
9176
+ steps: [
9177
+ { name: "Checkout", uses: "actions/checkout@v4" },
9178
+ {
9179
+ name: "check queued release",
9180
+ id: "guard",
9181
+ run: `node ${CATCI} release github-queued-guard "\${{ github.event.workflow_run.head_sha }}" "\${{ github.event.workflow_run.conclusion }}"`,
9182
+ shell: "bash",
9183
+ },
9184
+ ],
9185
+ },
9186
+ "create-release": {
9187
+ ...createReleaseJob(config, images, {
9188
+ needs: ["guard"],
9189
+ skipImageNeeds: true,
9190
+ }),
9191
+ if: "${{ needs.guard.outputs.release == 'true' }}",
9192
+ },
9193
+ },
9194
+ };
9195
+ };
9196
+ exports.getGithubReleaseOnGreenWorkflow = getGithubReleaseOnGreenWorkflow;
9096
9197
  /**
9097
9198
  * the informational MR/PR check job of the release method (e.g. the
9098
9199
  * changeset check), added to the mr workflow. continue-on-error keeps
@@ -9287,6 +9388,7 @@ const config_1 = __nccwpck_require__(17860);
9287
9388
  const createAllJobs_1 = __nccwpck_require__(54114);
9288
9389
  const jobImagesPlan_1 = __nccwpck_require__(47864);
9289
9390
  const shippedCatci_1 = __nccwpck_require__(79024);
9391
+ const release_1 = __nccwpck_require__(38904);
9290
9392
  const index_1 = __nccwpck_require__(68906);
9291
9393
  const createGitlabJobs_1 = __nccwpck_require__(41509);
9292
9394
  const createGitlabPipeline_1 = __nccwpck_require__(94411);
@@ -9373,12 +9475,29 @@ class GitlabBackend {
9373
9475
  }
9374
9476
  async createPipeline(config, images) {
9375
9477
  const stages = (0, getPipelineStages_1.getPipelineStages)(config);
9376
- // release jobs first: they register their image on the plan, and the
9377
- // per-trigger job creation below emits the build jobs of all images
9378
- // registered so far
9379
- const releaseJobs = (0, gitlabReleaseJobs_1.getGitlabReleaseJobs)(config, images);
9478
+ // register the release image first: the per-trigger job creation
9479
+ // below emits the build jobs of all images registered so far. The
9480
+ // release jobs themselves are created afterwards — they need the
9481
+ // main-branch job list for the queued-release executor's needs.
9482
+ images.resolveRef({ catladderImage: (0, release_1.getReleaseMethod)(config).image });
9380
9483
  // for all triggers create jobs and add base rules
9381
- 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());
9484
+ const jobsPerTrigger = await Promise.all(config_1.ALL_PIPELINE_TRIGGERS.map(async (trigger) => ({
9485
+ trigger,
9486
+ jobs: await (0, createGitlabJobs_1.createGitlabJobs)(await (0, createAllJobs_1.createAllJobs)({ config, trigger, pipelineType: this.type }), images, getGitlabRulesForTrigger(trigger)),
9487
+ })));
9488
+ const allJobsPerTrigger = jobsPerTrigger.flatMap(({ jobs }) => jobs);
9489
+ // the jobs the queued-release executor waits for: everything that
9490
+ // runs automatically in a main-branch pipeline. Manual jobs (stop,
9491
+ // rollback, gates) are excluded — gitlab skips a job whose needs
9492
+ // include an unplayed manual job.
9493
+ const mainBranchJobNames = [
9494
+ ...new Set(jobsPerTrigger
9495
+ .filter(({ trigger }) => trigger === "mainBranch")
9496
+ .flatMap(({ jobs }) => jobs)
9497
+ .filter(({ gitlabJob }) => !isManualGitlabJob(gitlabJob))
9498
+ .map(({ name }) => name)),
9499
+ ];
9500
+ const releaseJobs = (0, gitlabReleaseJobs_1.getGitlabReleaseJobs)(config, images, mainBranchJobNames);
9382
9501
  const allWorkspaceJobs = allJobsPerTrigger
9383
9502
  .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
9503
  // this is purely for better readability in git diffs when you add new components
@@ -9444,6 +9563,14 @@ class GitlabBackend {
9444
9563
  }
9445
9564
  }
9446
9565
  exports.GitlabBackend = GitlabBackend;
9566
+ /**
9567
+ * whether a job can end up waiting for a human in a main-branch
9568
+ * pipeline (catladder expresses manual-ness only through rules)
9569
+ */
9570
+ function isManualGitlabJob(job) {
9571
+ var _a;
9572
+ return ((_a = job.rules) !== null && _a !== void 0 ? _a : []).some((rule) => rule.when === "manual");
9573
+ }
9447
9574
  function getGitlabRulesForTrigger(trigger) {
9448
9575
  // mainBranch: on push to main branch except it's a release commit
9449
9576
  // mr: on merge request
@@ -9976,91 +10103,179 @@ exports.getPipelineStages = getPipelineStages;
9976
10103
  "use strict";
9977
10104
 
9978
10105
  Object.defineProperty(exports, "__esModule", ({ value: true }));
9979
- exports.getGitlabReleaseJobs = void 0;
10106
+ exports.getGitlabReleaseJobs = exports.RELEASE_EXECUTOR_JOB_NAME = exports.RELEASE_BUTTON_JOB_NAME = void 0;
9980
10107
  const rules_1 = __nccwpck_require__(35086);
9981
10108
  const release_1 = __nccwpck_require__(38904);
9982
10109
  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.'";
10110
+ /**
10111
+ * gitlab rejects jobs with more than 50 `needs:` entries (default
10112
+ * limit). Pipelines beyond that fall back to the legacy manual release
10113
+ * job, which is only playable once all prior stages finished.
10114
+ */
10115
+ const GITLAB_MAX_NEEDS = 50;
10116
+ exports.RELEASE_BUTTON_JOB_NAME = "create release";
10117
+ exports.RELEASE_EXECUTOR_JOB_NAME = "🚀 release once pipeline succeeds";
9983
10118
  const baseReleaseRules = [
9984
10119
  rules_1.RULE_NEVER_ON_RELEASE_COMMIT,
9985
10120
  rules_1.RULE_NEVER_ON_AGENT_TRIGGER,
9986
10121
  rules_1.RULE_NEVER_ON_SCHEDULE,
9987
10122
  ];
9988
- const getGitlabReleaseJobs = (config, images) => {
10123
+ // NOTE: with `rules:`, when: manual defaults to allow_failure: false,
10124
+ // which turns every unclicked pipeline into status "blocked" — the
10125
+ // manual release jobs therefore carry allow_failure: true so pipelines
10126
+ // end "passed" when nobody releases
10127
+ const manualReleaseRules = [
10128
+ ...baseReleaseRules,
10129
+ {
10130
+ if: rules_1.RULE_CONDITION_MAIN_BRANCH,
10131
+ when: "manual",
10132
+ },
10133
+ {
10134
+ if: rules_1.RULE_CONDITION_HOTFIX_BRANCH,
10135
+ when: "manual",
10136
+ },
10137
+ ];
10138
+ const getGitlabReleaseJobs = (config, images,
10139
+ /**
10140
+ * names of the jobs that run automatically in main-branch pipelines
10141
+ * (manual jobs excluded: a needed-but-unplayed manual job would make
10142
+ * gitlab skip the release executor)
10143
+ */
10144
+ mainBranchJobNames) => {
9989
10145
  var _a;
9990
10146
  const method = (0, release_1.getReleaseMethod)(config);
9991
10147
  // NOTE: only the resolved image url is used, not the `need`: release
9992
- // jobs have no `needs` on purpose — their stage ordering already
9993
- // guarantees the image build job (setup stage) ran, and adding a
9994
- // `needs` would switch gitlab to DAG mode, so the release job would no
9995
- // longer wait for the build/deploy stages.
10148
+ // jobs have no `needs` on the whole pipeline on purpose — where they
10149
+ // must wait for it, the wait is expressed by enumerating the jobs
10150
+ // explicitly (see the executor below).
9996
10151
  const releaseImage = images.resolveRef({
9997
10152
  catladderImage: method.image,
9998
10153
  }).image;
9999
- return {
10000
- ["create release"]: {
10001
- stage: "release",
10002
- image: releaseImage,
10003
- script: [method.script],
10004
- after_script: [EXPIRED_TOKEN_HELP],
10005
- rules: [
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
- },
10154
+ const releaseJobBase = {
10155
+ stage: "release",
10156
+ image: releaseImage,
10157
+ script: [method.script],
10158
+ after_script: [EXPIRED_TOKEN_HELP],
10159
+ };
10160
+ const forceReleaseJob = {
10017
10161
  ["⚠️ force create release"]: {
10018
- stage: "release",
10019
- image: releaseImage,
10020
- script: [method.script],
10021
- after_script: [EXPIRED_TOKEN_HELP],
10162
+ ...releaseJobBase,
10022
10163
  needs: [],
10164
+ allow_failure: true,
10023
10165
  // force semantics of the method's script (e.g. changesets:
10024
10166
  // release a patch bump even without pending changesets)
10025
10167
  ...(method.forceReleaseVariables
10026
10168
  ? { variables: method.forceReleaseVariables }
10027
10169
  : {}),
10170
+ rules: manualReleaseRules,
10171
+ },
10172
+ };
10173
+ // informational MR check of the release method (e.g. the changeset
10174
+ // check): warns via allow_failure, the report lands in the MR
10175
+ // widget as an exposed artifact
10176
+ const checkJob = method.checkScript
10177
+ ? {
10178
+ ["🦋 changeset check"]: {
10179
+ stage: "test",
10180
+ image: releaseImage,
10181
+ script: [method.checkScript],
10182
+ allow_failure: true,
10183
+ artifacts: {
10184
+ paths: ["changeset-report.md"],
10185
+ expose_as: "changeset report",
10186
+ // the report must survive the warning exit
10187
+ when: "always",
10188
+ },
10189
+ rules: [
10190
+ rules_1.RULE_NEVER_ON_AGENT_TRIGGER,
10191
+ rules_1.RULE_NEVER_ON_SCHEDULE,
10192
+ rules_1.RULE_IS_MERGE_REQUEST,
10193
+ ],
10194
+ },
10195
+ }
10196
+ : {};
10197
+ if (((_a = config.releases) === null || _a === void 0 ? void 0 : _a.when) === "auto") {
10198
+ return {
10199
+ [exports.RELEASE_BUTTON_JOB_NAME]: {
10200
+ ...releaseJobBase,
10201
+ rules: [
10202
+ ...baseReleaseRules,
10203
+ {
10204
+ if: rules_1.RULE_CONDITION_MAIN_BRANCH,
10205
+ when: "on_success",
10206
+ },
10207
+ {
10208
+ if: rules_1.RULE_CONDITION_HOTFIX_BRANCH,
10209
+ when: "manual",
10210
+ // per-rule: only the unclicked hotfix button must not
10211
+ // block — a failed automatic release stays loud
10212
+ allow_failure: true,
10213
+ },
10214
+ ],
10215
+ },
10216
+ ...forceReleaseJob,
10217
+ ...checkJob,
10218
+ };
10219
+ }
10220
+ const executorNeeds = [
10221
+ { job: exports.RELEASE_BUTTON_JOB_NAME },
10222
+ // optional: hotfix pipelines (and rules-filtered jobs) contain a
10223
+ // different job subset
10224
+ ...mainBranchJobNames.map((name) => ({ job: name, optional: true })),
10225
+ ];
10226
+ if (executorNeeds.length > GITLAB_MAX_NEEDS) {
10227
+ console.warn(`⚠️ the main-branch pipeline has more than ${GITLAB_MAX_NEEDS} jobs — ` +
10228
+ `the "${exports.RELEASE_BUTTON_JOB_NAME}" job falls back to being playable ` +
10229
+ `only once the pipeline finished (gitlab caps needs: at ${GITLAB_MAX_NEEDS})`);
10230
+ return {
10231
+ [exports.RELEASE_BUTTON_JOB_NAME]: {
10232
+ ...releaseJobBase,
10233
+ allow_failure: true,
10234
+ rules: manualReleaseRules,
10235
+ },
10236
+ ...forceReleaseJob,
10237
+ ...checkJob,
10238
+ };
10239
+ }
10240
+ return {
10241
+ // the queue button: playable from the first second of the pipeline.
10242
+ // It only records the intent to release — the executor below does
10243
+ // the actual work once every other job succeeded. Clicking after the
10244
+ // pipeline finished works the same (the executor starts right away).
10245
+ [exports.RELEASE_BUTTON_JOB_NAME]: {
10246
+ stage: "release",
10247
+ image: releaseImage,
10248
+ script: [
10249
+ `echo "release queued — '${exports.RELEASE_EXECUTOR_JOB_NAME}' runs it as soon as every other job in this pipeline succeeded"`,
10250
+ ],
10251
+ needs: [],
10252
+ // allow_failure keeps the unclicked button from blocking the
10253
+ // pipeline AND makes the executor's needs treat it as optional
10254
+ // (skipped executor instead of a forever-waiting pipeline)
10255
+ allow_failure: true,
10256
+ rules: manualReleaseRules,
10257
+ },
10258
+ // the executor: never played manually. Skipped when the button was
10259
+ // not clicked; otherwise starts automatically as soon as the button
10260
+ // and every other (non-manual) job of the pipeline succeeded, and
10261
+ // is skipped when any of them failed.
10262
+ [exports.RELEASE_EXECUTOR_JOB_NAME]: {
10263
+ ...releaseJobBase,
10264
+ needs: executorNeeds,
10028
10265
  rules: [
10029
10266
  ...baseReleaseRules,
10030
10267
  {
10031
10268
  if: rules_1.RULE_CONDITION_MAIN_BRANCH,
10032
- when: "manual",
10269
+ when: "on_success",
10033
10270
  },
10034
10271
  {
10035
10272
  if: rules_1.RULE_CONDITION_HOTFIX_BRANCH,
10036
- when: "manual",
10273
+ when: "on_success",
10037
10274
  },
10038
10275
  ],
10039
10276
  },
10040
- // informational MR check of the release method (e.g. the changeset
10041
- // check): warns via allow_failure, the report lands in the MR
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
- : {}),
10277
+ ...forceReleaseJob,
10278
+ ...checkJob,
10064
10279
  };
10065
10280
  };
10066
10281
  exports.getGitlabReleaseJobs = getGitlabReleaseJobs;
@@ -12719,7 +12934,9 @@ const getEnvironmentVariables = async (ctx, options = {}) => {
12719
12934
  const additionalEnvVars = deployConfigRaw
12720
12935
  ? deploy_1.DEPLOY_TYPES[deployConfigRaw.type].getAdditionalEnvVars(environmentContext)
12721
12936
  : {};
12722
- const HOSTNAME_INTERNAL = (_b = additionalEnvVars.HOSTNAME_INTERNAL) !== null && _b !== void 0 ? _b : "unknown-host.example.com";
12937
+ // deploy types provide HOSTNAME_INTERNAL as a plain string or bash
12938
+ // expression (never containing references)
12939
+ const HOSTNAME_INTERNAL = ((_b = additionalEnvVars.HOSTNAME_INTERNAL) !== null && _b !== void 0 ? _b : "unknown-host.example.com");
12723
12940
  host = (_c = envConfigRaw === null || envConfigRaw === void 0 ? void 0 : envConfigRaw.host) !== null && _c !== void 0 ? _c : HOSTNAME_INTERNAL;
12724
12941
  url = (0, bash_1.joinBashExpressions)(["https://", host]);
12725
12942
  predefinedVariables = {
@@ -12747,8 +12964,18 @@ const getEnvironmentVariables = async (ctx, options = {}) => {
12747
12964
  componentName: ctx.componentName,
12748
12965
  }),
12749
12966
  ]));
12750
- const publicEnvVars = ((_g = options.shouldResolveReferences) !== null && _g !== void 0 ? _g : true)
12751
- ? await (0, bash_3.resolveAllReferences)(publicEnvVarsUnresolved, async (otherComponentName) => {
12967
+ // predefined variables may contain references too (e.g. the embedded
12968
+ // database connection string references the component's own DB_* vars), so
12969
+ // we resolve references over the merged map. This way public var overrides
12970
+ // (like DB_PASSWORD: "${otherComponent:DB_PASSWORD}") are picked up by the
12971
+ // predefined variables that reference them
12972
+ const mergedEnvVars = {
12973
+ ...predefinedVariables,
12974
+ ...secretEnvVars,
12975
+ ...publicEnvVarsUnresolved,
12976
+ };
12977
+ const resolvedEnvVars = ((_g = options.shouldResolveReferences) !== null && _g !== void 0 ? _g : true)
12978
+ ? await (0, bash_3.resolveAllReferences)(mergedEnvVars, async (otherComponentName) => {
12752
12979
  const { envVars: otherEnvVars } = await (0, exports.getEnvironmentVariables)({
12753
12980
  ...ctx,
12754
12981
  componentName: otherComponentName,
@@ -12757,12 +12984,8 @@ const getEnvironmentVariables = async (ctx, options = {}) => {
12757
12984
  });
12758
12985
  return otherEnvVars;
12759
12986
  })
12760
- : publicEnvVarsUnresolved;
12761
- const envVars = addIndexVar({
12762
- ...predefinedVariables,
12763
- ...secretEnvVars,
12764
- ...publicEnvVars,
12765
- });
12987
+ : mergedEnvVars;
12988
+ const envVars = addIndexVar(resolvedEnvVars);
12766
12989
  return {
12767
12990
  envVars,
12768
12991
  secretEnvVarKeys,
@@ -14753,8 +14976,8 @@ const getCloudSqlVariables = ({ deployConfigRaw, environmentSlugPrefix, env, com
14753
14976
  };
14754
14977
  return {
14755
14978
  ...dbVars,
14756
- DATABASE_URL: (0, database_1.getDatabaseConnectionString)(deployConfigRaw.cloudSql, dbVars).concat(additionalQueryParamsString),
14757
- DATABASE_JDBC_URL: (0, database_1.getDatabaseJdbcUrl)(dbVars, (_c = deployConfigRaw.cloudSql.dbConnectionStringVariablesMode) !== null && _c !== void 0 ? _c : database_1.DEFAULT_DB_VARIABLES_MODE),
14979
+ DATABASE_URL: (0, database_1.getDatabaseConnectionString)(deployConfigRaw.cloudSql, dbVars, componentName).concat(additionalQueryParamsString),
14980
+ DATABASE_JDBC_URL: (0, database_1.getDatabaseJdbcUrl)(dbVars, (_c = deployConfigRaw.cloudSql.dbConnectionStringVariablesMode) !== null && _c !== void 0 ? _c : database_1.DEFAULT_DB_VARIABLES_MODE, componentName),
14758
14981
  };
14759
14982
  }
14760
14983
  return {};
@@ -14946,64 +15169,72 @@ const getDatabaseCreateScript = (context, deployConfig) => {
14946
15169
  ]);
14947
15170
  };
14948
15171
  exports.getDatabaseCreateScript = getDatabaseCreateScript;
14949
- exports.DEFAULT_DB_VARIABLES_MODE = "legacy";
14950
- const getVariableOrValue = (key, variables, mode) => {
14951
- return mode === "legacy" ? `$${key}` : variables[key];
14952
- };
14953
- const getDatabaseJdbcUrl = (variables, mode) => {
15172
+ exports.DEFAULT_DB_VARIABLES_MODE = "embedded";
15173
+ const getVariableOrValue = (key, mode, componentName) => {
15174
+ // in embedded mode we reference the component's own env var, which gets
15175
+ // resolved to its final value (including vars.public overrides) after all
15176
+ // env vars have been merged
15177
+ return mode === "legacy"
15178
+ ? `$${key}`
15179
+ : new bash_1.VariableReference(componentName, key);
15180
+ };
15181
+ const joinDbUrlParts = (parts, mode) => mode === "legacy"
15182
+ ? (0, bash_1.joinBashExpressions)(parts)
15183
+ : new bash_1.VariableValueContainingReferences(parts);
15184
+ const getDatabaseJdbcUrl = (variables, mode, componentName) => {
14954
15185
  const parts = [
14955
15186
  "jdbc:postgresql:///",
14956
- getVariableOrValue("DB_NAME", variables, mode),
15187
+ getVariableOrValue("DB_NAME", mode, componentName),
14957
15188
  "?cloudSqlInstance=",
14958
- getVariableOrValue("CLOUD_SQL_INSTANCE_CONNECTION_NAME", variables, mode),
15189
+ getVariableOrValue("CLOUD_SQL_INSTANCE_CONNECTION_NAME", mode, componentName),
14959
15190
  "&socketFactory=com.google.cloud.sql.postgres.SocketFactory&user=",
14960
- getVariableOrValue("DB_USER", variables, mode),
15191
+ getVariableOrValue("DB_USER", mode, componentName),
14961
15192
  "&password=",
14962
- getVariableOrValue("DB_PASSWORD", variables, mode),
15193
+ getVariableOrValue("DB_PASSWORD", mode, componentName),
14963
15194
  ];
14964
- return (0, bash_1.joinBashExpressions)(parts);
15195
+ return joinDbUrlParts(parts, mode);
14965
15196
  };
14966
15197
  exports.getDatabaseJdbcUrl = getDatabaseJdbcUrl;
14967
- const getRailsDatabaseConnectionString = (variables, mode) => {
15198
+ const getRailsDatabaseConnectionString = (variables, mode, componentName) => {
14968
15199
  const parts = [
14969
15200
  "postgresql://",
14970
- getVariableOrValue("DB_USER", variables, mode),
15201
+ getVariableOrValue("DB_USER", mode, componentName),
14971
15202
  ":",
14972
- getVariableOrValue("DB_PASSWORD", variables, mode),
15203
+ getVariableOrValue("DB_PASSWORD", mode, componentName),
14973
15204
  "@",
14974
15205
  encodeURIComponent(`/cloudsql/${variables.CLOUD_SQL_INSTANCE_CONNECTION_NAME}`),
14975
15206
  "/",
14976
- getVariableOrValue("DB_NAME", variables, mode),
15207
+ getVariableOrValue("DB_NAME", mode, componentName),
14977
15208
  "?",
14978
15209
  ];
14979
- return (0, bash_1.joinBashExpressions)(parts);
15210
+ return joinDbUrlParts(parts, mode);
14980
15211
  };
14981
15212
  exports.getRailsDatabaseConnectionString = getRailsDatabaseConnectionString;
14982
- const getPrismaDatabaseConnectionString = (variables, mode) => {
15213
+ const getPrismaDatabaseConnectionString = (variables, mode, componentName) => {
14983
15214
  const parts = [
14984
15215
  "postgresql://",
14985
- getVariableOrValue("DB_USER", variables, mode),
15216
+ getVariableOrValue("DB_USER", mode, componentName),
14986
15217
  ":",
14987
- getVariableOrValue("DB_PASSWORD", variables, mode),
15218
+ getVariableOrValue("DB_PASSWORD", mode, componentName),
14988
15219
  "@localhost/",
14989
- getVariableOrValue("DB_NAME", variables, mode),
15220
+ getVariableOrValue("DB_NAME", mode, componentName),
14990
15221
  "?host=/cloudsql/",
14991
- getVariableOrValue("CLOUD_SQL_INSTANCE_CONNECTION_NAME", variables, mode),
15222
+ getVariableOrValue("CLOUD_SQL_INSTANCE_CONNECTION_NAME", mode, componentName),
14992
15223
  ];
14993
- return (0, bash_1.joinBashExpressions)(parts);
15224
+ return joinDbUrlParts(parts, mode);
14994
15225
  };
14995
15226
  exports.getPrismaDatabaseConnectionString = getPrismaDatabaseConnectionString;
14996
- const getDatabaseConnectionString = (config, variables) => {
15227
+ const getDatabaseConnectionString = (config, variables, componentName) => {
14997
15228
  var _a;
14998
15229
  const mode = (_a = config.dbConnectionStringVariablesMode) !== null && _a !== void 0 ? _a : exports.DEFAULT_DB_VARIABLES_MODE;
14999
15230
  switch (config.dbConnectionStringFormat) {
15000
15231
  case "jdbc":
15001
- return (0, exports.getDatabaseJdbcUrl)(variables, mode);
15232
+ return (0, exports.getDatabaseJdbcUrl)(variables, mode, componentName);
15002
15233
  case "rails":
15003
- return (0, exports.getRailsDatabaseConnectionString)(variables, mode);
15234
+ return (0, exports.getRailsDatabaseConnectionString)(variables, mode, componentName);
15004
15235
  default:
15005
15236
  // prisma
15006
- return (0, exports.getPrismaDatabaseConnectionString)(variables, mode);
15237
+ return (0, exports.getPrismaDatabaseConnectionString)(variables, mode, componentName);
15007
15238
  }
15008
15239
  };
15009
15240
  exports.getDatabaseConnectionString = getDatabaseConnectionString;