@catladder/cli 0.0.0-next-4e7c6848 → 0.0.0-next-6a4450e0

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 (171) hide show
  1. package/dist/bundles/catenv/index.js +1392 -116
  2. package/dist/bundles/cli/index.js +1631 -112
  3. package/dist/cli/src/adapters/baseContext.d.ts +4 -0
  4. package/dist/cli/src/adapters/baseContext.js +5 -0
  5. package/dist/cli/src/adapters/baseContext.js.map +1 -1
  6. package/dist/cli/src/adapters/nonInteractive.d.ts +9 -0
  7. package/dist/cli/src/adapters/nonInteractive.js +32 -0
  8. package/dist/cli/src/adapters/nonInteractive.js.map +1 -0
  9. package/dist/cli/src/adapters/programmatic.d.ts +1 -0
  10. package/dist/cli/src/adapters/programmatic.js +6 -1
  11. package/dist/cli/src/adapters/programmatic.js.map +1 -1
  12. package/dist/cli/src/adapters/terminal.d.ts +1 -0
  13. package/dist/cli/src/adapters/terminal.js +10 -1
  14. package/dist/cli/src/adapters/terminal.js.map +1 -1
  15. package/dist/cli/src/adapters/vaultManagerAccess.d.ts +8 -0
  16. package/dist/cli/src/adapters/vaultManagerAccess.js +26 -0
  17. package/dist/cli/src/adapters/vaultManagerAccess.js.map +1 -0
  18. package/dist/cli/src/apps/catenv/catenv.d.ts +2 -0
  19. package/dist/cli/src/apps/catenv/catenv.js +8 -1
  20. package/dist/cli/src/apps/catenv/catenv.js.map +1 -1
  21. package/dist/cli/src/apps/catenv/printVariables.d.ts +2 -2
  22. package/dist/cli/src/apps/catenv/printVariables.js +4 -4
  23. package/dist/cli/src/apps/catenv/printVariables.js.map +1 -1
  24. package/dist/cli/src/apps/catenv/types.d.ts +9 -0
  25. package/dist/cli/src/apps/catenv/writeDotEnvFiles.d.ts +2 -2
  26. package/dist/cli/src/apps/catenv/writeDotEnvFiles.js +1 -1
  27. package/dist/cli/src/apps/catenv/writeDotEnvFiles.js.map +1 -1
  28. package/dist/cli/src/apps/cli/commands/project/commandConfigSecrets.js +70 -3
  29. package/dist/cli/src/apps/cli/commands/project/commandConfigSecrets.js.map +1 -1
  30. package/dist/cli/src/catenv.js +3 -2
  31. package/dist/cli/src/catenv.js.map +1 -1
  32. package/dist/cli/src/commands/index.d.ts +1 -0
  33. package/dist/cli/src/commands/index.js +8 -1
  34. package/dist/cli/src/commands/index.js.map +1 -1
  35. package/dist/cli/src/commands/project/commandSecretsSyncGithub.d.ts +46 -0
  36. package/dist/cli/src/commands/project/commandSecretsSyncGithub.js +150 -0
  37. package/dist/cli/src/commands/project/commandSecretsSyncGithub.js.map +1 -0
  38. package/dist/cli/src/config/getProjectConfig.d.ts +1 -1
  39. package/dist/cli/src/config/getProjectConfig.js +12 -2
  40. package/dist/cli/src/config/getProjectConfig.js.map +1 -1
  41. package/dist/cli/src/core/types.d.ts +15 -0
  42. package/dist/cli/src/core/types.js +8 -1
  43. package/dist/cli/src/core/types.js.map +1 -1
  44. package/dist/cli/src/git/gitProjectInformation.d.ts +1 -1
  45. package/dist/cli/src/git/gitProjectInformation.js +3 -3
  46. package/dist/cli/src/git/gitProjectInformation.js.map +1 -1
  47. package/dist/cli/src/utils/github.d.ts +20 -0
  48. package/dist/cli/src/utils/github.js +61 -0
  49. package/dist/cli/src/utils/github.js.map +1 -0
  50. package/dist/cli/src/vault/BitwardenVault.d.ts +24 -0
  51. package/dist/cli/src/vault/BitwardenVault.js +107 -0
  52. package/dist/cli/src/vault/BitwardenVault.js.map +1 -0
  53. package/dist/cli/src/vault/GitlabVault.d.ts +11 -0
  54. package/dist/cli/src/vault/GitlabVault.js +24 -0
  55. package/dist/cli/src/vault/GitlabVault.js.map +1 -0
  56. package/dist/cli/src/vault/VaultManager.d.ts +43 -0
  57. package/dist/cli/src/vault/VaultManager.js +131 -0
  58. package/dist/cli/src/vault/VaultManager.js.map +1 -0
  59. package/dist/cli/src/vault/bw.d.ts +15 -0
  60. package/dist/cli/src/vault/bw.js +89 -0
  61. package/dist/cli/src/vault/bw.js.map +1 -0
  62. package/dist/cli/src/vault/cache.d.ts +38 -0
  63. package/dist/cli/src/vault/cache.js +53 -0
  64. package/dist/cli/src/vault/cache.js.map +1 -0
  65. package/dist/cli/src/vault/index.d.ts +5 -0
  66. package/dist/cli/src/vault/index.js +48 -0
  67. package/dist/cli/src/vault/index.js.map +1 -0
  68. package/dist/cli/src/vault/types.d.ts +33 -0
  69. package/dist/cli/src/vault/types.js +5 -0
  70. package/dist/cli/src/vault/types.js.map +1 -0
  71. package/dist/pipeline/src/backends/github/GithubBackend.d.ts +4 -1
  72. package/dist/pipeline/src/backends/github/GithubBackend.js +70 -12
  73. package/dist/pipeline/src/backends/github/GithubBackend.js.map +1 -1
  74. package/dist/pipeline/src/backends/github/ciVariables.js +3 -1
  75. package/dist/pipeline/src/backends/github/ciVariables.js.map +1 -1
  76. package/dist/pipeline/src/backends/github/createGithubJobs.d.ts +13 -3
  77. package/dist/pipeline/src/backends/github/createGithubJobs.js +80 -14
  78. package/dist/pipeline/src/backends/github/createGithubJobs.js.map +1 -1
  79. package/dist/pipeline/src/backends/github/githubReleaseJobs.d.ts +2 -1
  80. package/dist/pipeline/src/backends/github/githubReleaseJobs.js +46 -33
  81. package/dist/pipeline/src/backends/github/githubReleaseJobs.js.map +1 -1
  82. package/dist/pipeline/src/backends/github/index.d.ts +2 -1
  83. package/dist/pipeline/src/backends/github/index.js +2 -1
  84. package/dist/pipeline/src/backends/github/index.js.map +1 -1
  85. package/dist/pipeline/src/backends/github/scriptFiles.d.ts +25 -0
  86. package/dist/pipeline/src/backends/github/scriptFiles.js +50 -0
  87. package/dist/pipeline/src/backends/github/scriptFiles.js.map +1 -0
  88. package/dist/pipeline/src/backends/gitlab/GitlabBackend.d.ts +1 -0
  89. package/dist/pipeline/src/backends/gitlab/GitlabBackend.js +18 -8
  90. package/dist/pipeline/src/backends/gitlab/GitlabBackend.js.map +1 -1
  91. package/dist/pipeline/src/backends/gitlab/ciVariables.js +2 -1
  92. package/dist/pipeline/src/backends/gitlab/ciVariables.js.map +1 -1
  93. package/dist/pipeline/src/backends/gitlab/createGitlabJobs.d.ts +3 -2
  94. package/dist/pipeline/src/backends/gitlab/createGitlabJobs.js +84 -10
  95. package/dist/pipeline/src/backends/gitlab/createGitlabJobs.js.map +1 -1
  96. package/dist/pipeline/src/backends/gitlab/createGitlabPipeline.js +1 -1
  97. package/dist/pipeline/src/backends/gitlab/createGitlabPipeline.js.map +1 -1
  98. package/dist/pipeline/src/backends/gitlab/gitlabReleaseJobs.js +2 -2
  99. package/dist/pipeline/src/backends/gitlab/gitlabReleaseJobs.js.map +1 -1
  100. package/dist/pipeline/src/backends/index.d.ts +17 -1
  101. package/dist/pipeline/src/backends/index.js +33 -1
  102. package/dist/pipeline/src/backends/index.js.map +1 -1
  103. package/dist/pipeline/src/backends/types.d.ts +2 -1
  104. package/dist/pipeline/src/bash/ciVariables.d.ts +1 -1
  105. package/dist/pipeline/src/bash/ciVariables.js.map +1 -1
  106. package/dist/pipeline/src/build/node/yarn.js +4 -1
  107. package/dist/pipeline/src/build/node/yarn.js.map +1 -1
  108. package/dist/pipeline/src/build/types.d.ts +1 -1
  109. package/dist/pipeline/src/constants.js +1 -1
  110. package/dist/pipeline/src/context/getEnvironmentVariables.d.ts +8 -0
  111. package/dist/pipeline/src/context/getEnvironmentVariables.js +3 -3
  112. package/dist/pipeline/src/context/getEnvironmentVariables.js.map +1 -1
  113. package/dist/pipeline/src/context/transformJobOnlyVars.js +2 -2
  114. package/dist/pipeline/src/context/transformJobOnlyVars.js.map +1 -1
  115. package/dist/pipeline/src/context/utils/envVars.d.ts +7 -0
  116. package/dist/pipeline/src/context/utils/envVars.js +22 -1
  117. package/dist/pipeline/src/context/utils/envVars.js.map +1 -1
  118. package/dist/pipeline/src/customImages/hash.d.ts +15 -0
  119. package/dist/pipeline/src/customImages/hash.js +71 -0
  120. package/dist/pipeline/src/customImages/hash.js.map +1 -0
  121. package/dist/pipeline/src/customImages/jobImagesPlan.d.ts +59 -0
  122. package/dist/pipeline/src/customImages/jobImagesPlan.js +172 -0
  123. package/dist/pipeline/src/customImages/jobImagesPlan.js.map +1 -0
  124. package/dist/pipeline/src/customImages/shippedImages.d.ts +28 -0
  125. package/dist/pipeline/src/customImages/shippedImages.js +60 -0
  126. package/dist/pipeline/src/customImages/shippedImages.js.map +1 -0
  127. package/dist/pipeline/src/deploy/cloudRun/index.js +15 -5
  128. package/dist/pipeline/src/deploy/cloudRun/index.js.map +1 -1
  129. package/dist/pipeline/src/deploy/cloudRun/utils/database.js +4 -1
  130. package/dist/pipeline/src/deploy/cloudRun/utils/database.js.map +1 -1
  131. package/dist/pipeline/src/pipeline/generatePipelineFiles.js +16 -1
  132. package/dist/pipeline/src/pipeline/generatePipelineFiles.js.map +1 -1
  133. package/dist/pipeline/src/runner/index.d.ts +20 -1
  134. package/dist/pipeline/src/runner/index.js +16 -3
  135. package/dist/pipeline/src/runner/index.js.map +1 -1
  136. package/dist/pipeline/src/types/config.d.ts +63 -5
  137. package/dist/pipeline/src/types/github-types.d.ts +5 -0
  138. package/dist/pipeline/src/types/jobs.d.ts +10 -2
  139. package/dist/pipeline/src/types/jobs.js.map +1 -1
  140. package/dist/pipeline/src/utils/gitlab.d.ts +5 -0
  141. package/dist/pipeline/src/utils/gitlab.js +6 -0
  142. package/dist/pipeline/src/utils/gitlab.js.map +1 -1
  143. package/dist/pipeline/src/utils/writeFiles.d.ts +6 -0
  144. package/dist/pipeline/src/utils/writeFiles.js +8 -0
  145. package/dist/pipeline/src/utils/writeFiles.js.map +1 -1
  146. package/dist/tsconfig.tsbuildinfo +1 -1
  147. package/package.json +1 -1
  148. package/src/adapters/baseContext.ts +10 -0
  149. package/src/adapters/nonInteractive.ts +30 -0
  150. package/src/adapters/programmatic.ts +7 -1
  151. package/src/adapters/terminal.ts +9 -1
  152. package/src/adapters/vaultManagerAccess.ts +25 -0
  153. package/src/apps/catenv/catenv.ts +5 -1
  154. package/src/apps/catenv/printVariables.ts +16 -6
  155. package/src/apps/catenv/types.ts +9 -0
  156. package/src/apps/catenv/writeDotEnvFiles.ts +3 -3
  157. package/src/apps/cli/commands/project/commandConfigSecrets.ts +114 -9
  158. package/src/catenv.ts +22 -2
  159. package/src/commands/index.ts +1 -0
  160. package/src/commands/project/commandSecretsSyncGithub.ts +172 -0
  161. package/src/config/getProjectConfig.ts +21 -7
  162. package/src/core/types.ts +23 -0
  163. package/src/git/gitProjectInformation.ts +5 -3
  164. package/src/utils/github.ts +76 -0
  165. package/src/vault/BitwardenVault.ts +161 -0
  166. package/src/vault/GitlabVault.ts +25 -0
  167. package/src/vault/VaultManager.ts +211 -0
  168. package/src/vault/bw.ts +110 -0
  169. package/src/vault/cache.ts +80 -0
  170. package/src/vault/index.ts +5 -0
  171. package/src/vault/types.ts +42 -0
@@ -1,6 +1,78 @@
1
1
  /******/ (() => { // webpackBootstrap
2
2
  /******/ var __webpack_modules__ = ({
3
3
 
4
+ /***/ 1917:
5
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
6
+
7
+ "use strict";
8
+
9
+
10
+ Object.defineProperty(exports, "__esModule", ({
11
+ value: true
12
+ }));
13
+ exports.createNonInteractiveIO = void 0;
14
+ const types_1 = __nccwpck_require__(2284);
15
+ const vaultManagerAccess_1 = __nccwpck_require__(7881);
16
+ /**
17
+ * the context of non-interactive runs (catenv via direnv): logging
18
+ * goes to stderr, any required interaction fails with a clear error.
19
+ */
20
+ function createNonInteractiveIO(options = {}) {
21
+ const io = {
22
+ interactive: false,
23
+ getVaultManager: (0, vaultManagerAccess_1.createVaultManagerGetter)(() => io, {
24
+ mode: options.vaultMode
25
+ }),
26
+ log(message) {
27
+ // stderr: stdout may be evaluated (direnv)
28
+ console.error(message);
29
+ },
30
+ async confirm(message) {
31
+ throw new types_1.NonInteractiveError(`confirmation ("${message}")`);
32
+ },
33
+ async promptDirect(spec) {
34
+ throw new types_1.NonInteractiveError(`input "${spec.name}"`);
35
+ }
36
+ };
37
+ return io;
38
+ }
39
+ exports.createNonInteractiveIO = createNonInteractiveIO;
40
+
41
+ /***/ }),
42
+
43
+ /***/ 7881:
44
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
45
+
46
+ "use strict";
47
+
48
+
49
+ Object.defineProperty(exports, "__esModule", ({
50
+ value: true
51
+ }));
52
+ exports.createVaultManagerGetter = void 0;
53
+ const getProjectConfig_1 = __nccwpck_require__(1);
54
+ const VaultManager_1 = __nccwpck_require__(5771);
55
+ /**
56
+ * lazily creates the vault manager of a context (once per context),
57
+ * loading the project config on first use
58
+ */
59
+ const createVaultManagerGetter = (io, options = {}) => {
60
+ let managerPromise;
61
+ return () => managerPromise !== null && managerPromise !== void 0 ? managerPromise : managerPromise = (async () => {
62
+ const config = await (0, getProjectConfig_1.getProjectConfig)();
63
+ if (!config) {
64
+ throw new Error("no catladder config found — the secrets vault needs a project config");
65
+ }
66
+ return new VaultManager_1.VaultManager(config, {
67
+ ...options,
68
+ io: io()
69
+ });
70
+ })();
71
+ };
72
+ exports.createVaultManagerGetter = createVaultManagerGetter;
73
+
74
+ /***/ }),
75
+
4
76
  /***/ 6057:
5
77
  /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
6
78
 
@@ -16,12 +88,19 @@ const verboseBanner_1 = __nccwpck_require__(5472);
16
88
  const writeDotEnvFiles_1 = __nccwpck_require__(5614);
17
89
  const writeEnvDTs_1 = __nccwpck_require__(8832);
18
90
  const pipeline_1 = __nccwpck_require__(241);
91
+ const nonInteractive_1 = __nccwpck_require__(1917);
19
92
  exports["default"] = async (choice, options) => {
20
93
  const config = await (0, getProjectConfig_1.getProjectConfig)();
21
94
  if (!config) {
22
95
  return;
23
96
  }
24
- const context = (0, pipeline_1.createCatenvContext)(config);
97
+ const io = (0, nonInteractive_1.createNonInteractiveIO)({
98
+ vaultMode: options === null || options === void 0 ? void 0 : options.vaultMode
99
+ });
100
+ const context = {
101
+ ...(0, pipeline_1.createCatenvContext)(config),
102
+ io
103
+ };
25
104
  if (options === null || options === void 0 ? void 0 : options.verbose) {
26
105
  (0, verboseBanner_1.printVerboseBanner)();
27
106
  }
@@ -42,19 +121,19 @@ Object.defineProperty(exports, "__esModule", ({
42
121
  exports.printVariables = void 0;
43
122
  const getProjectConfig_1 = __nccwpck_require__(1);
44
123
  const utils_1 = __nccwpck_require__(1909);
45
- const getAllVariablesToPrint = async (config, choice) => {
124
+ const getAllVariablesToPrint = async (io, config, choice) => {
46
125
  const {
47
126
  env,
48
127
  currentComponent
49
128
  } = await (0, utils_1.getCurrentComponentAndEnvFromChoice)(config, choice);
50
129
  if (currentComponent) {
51
- return await (0, getProjectConfig_1.getEnvVarsResolved)(null, env, currentComponent);
130
+ return await (0, getProjectConfig_1.getEnvVarsResolved)(io, env, currentComponent);
52
131
  } else {
53
132
  // when in a monorep and not in a subapp, merge all env vars.
54
133
  // this is not 100% correct, but better than not exporting any vars at all
55
134
  // so we also add prefixed variants
56
135
  return await Object.keys(config.components).reduce(async (acc, componentName) => {
57
- const subappvars = await (0, getProjectConfig_1.getEnvVarsResolved)(null, env, componentName);
136
+ const subappvars = await (0, getProjectConfig_1.getEnvVarsResolved)(io, env, componentName);
58
137
  delete subappvars["_ALL_ENV_VAR_KEYS"];
59
138
  return {
60
139
  ...(await acc),
@@ -66,7 +145,7 @@ const getAllVariablesToPrint = async (config, choice) => {
66
145
  }
67
146
  };
68
147
  const printVariables = async (context, choice) => {
69
- const variables = await getAllVariablesToPrint(context.config, choice);
148
+ const variables = await getAllVariablesToPrint(context.io, context.config, choice);
70
149
  console.log(makeExportKeyValuestring(variables));
71
150
  };
72
151
  exports.printVariables = printVariables;
@@ -185,7 +264,7 @@ const writeDotEnvFiles = async (context, choice) => {
185
264
  const componentsToActuallyWriteDotEnvNow = currentComponent ? componentsWithEnabledDotEnvWrite.includes(currentComponent) ? [currentComponent] : [] : componentsWithEnabledDotEnvWrite;
186
265
  const gitRoot = await (0, projects_1.getGitRoot)();
187
266
  for (const componentName of componentsToActuallyWriteDotEnvNow) {
188
- const variables = await (0, getProjectConfig_1.getEnvVarsResolved)(null, env, componentName);
267
+ const variables = await (0, getProjectConfig_1.getEnvVarsResolved)(context.io, env, componentName);
189
268
  delete variables["_ALL_ENV_VAR_KEYS"];
190
269
  const componentDir = (0, utils_1.getComponentFullPath)(gitRoot, context.config, componentName);
191
270
  const filePath = (0, path_1.join)(componentDir, ".env");
@@ -279,11 +358,12 @@ const catenv_1 = __importDefault(__nccwpck_require__(6057));
279
358
  const parseChoice_1 = __nccwpck_require__(4272);
280
359
  const packageInfos_1 = __importDefault(__nccwpck_require__(3568));
281
360
  const program = new commander_1.Command();
282
- program.name("catenv").description("Environment variable and pipeline generation via direnv").version(packageInfos_1.default.version).argument("[envComponent]", "env or env:component").option("-v, --verbose", "verbose output").option("-p, --print-variables", "print variables").action((envComponent, opts) => {
361
+ program.name("catenv").description("Environment variable and pipeline generation via direnv").version(packageInfos_1.default.version).argument("[envComponent]", "env or env:component").option("-v, --verbose", "verbose output").option("-p, --print-variables", "print variables").addOption(new commander_1.Option("--vault-mode <mode>", ["how to interact with the secrets vault:", "auto: contact the vault when cached secrets are missing (may prompt to unlock)", "no-prompt: like auto, but fail instead of prompting", "offline: never contact the vault, use only locally cached secrets", "refresh: ignore the local cache once and refresh it from the vault"].join("\n")).choices(["auto", "no-prompt", "offline", "refresh"]).default("auto")).action((envComponent, opts) => {
283
362
  var _a, _b;
284
363
  (0, catenv_1.default)(envComponent ? (0, parseChoice_1.parseChoice)(envComponent) : null, {
285
364
  verbose: (_a = opts.verbose) !== null && _a !== void 0 ? _a : false,
286
- printVariables: (_b = opts.printVariables) !== null && _b !== void 0 ? _b : false
365
+ printVariables: (_b = opts.printVariables) !== null && _b !== void 0 ? _b : false,
366
+ vaultMode: opts.vaultMode
287
367
  }).then(() => {
288
368
  // we have to exit manually, because we have some file watches
289
369
  process.exit();
@@ -403,14 +483,24 @@ const getGitlabVar = async (io, env, componentName, variableName) => {
403
483
  };
404
484
  exports.getGitlabVar = getGitlabVar;
405
485
  const resolveSecrets = async (io, varSets) => {
406
- const allVariablesInGitlab = await (0, gitlab_1.getAllVariables)(io);
486
+ // the secret variables actually referenced by the env var values —
487
+ // when they are all in the local cache, the vault is not contacted
488
+ const referencedNames = [...new Set(varSets.flatMap(({
489
+ envVars
490
+ }) => Object.values(envVars).flatMap(value => {
491
+ var _a;
492
+ return (_a = `${value}`.match(/\$CL_\w+/g)) !== null && _a !== void 0 ? _a : [];
493
+ })))].map(reference => reference.slice(1));
494
+ const vaultManager = await io.getVaultManager();
495
+ const secrets = await vaultManager.readSecrets(referencedNames, io);
496
+ const secretEntries = Object.entries(secrets);
407
497
  return Object.fromEntries(varSets.flatMap(({
408
498
  envVars,
409
499
  secretEnvVarKeys
410
500
  }) => Object.entries(envVars).filter(([key, value]) => {
411
501
  var _a;
412
502
  return value !== undefined && value !== null && !((_a = secretEnvVarKeys.find(k => k.key === key)) === null || _a === void 0 ? void 0 : _a.hidden);
413
- }).map(([key, value]) => [key, allVariablesInGitlab.reduce((acc, curr) => acc.replace(new RegExp("\\$" + curr.key, "g"), curr.value), `${value}`)])));
503
+ }).map(([key, value]) => [key, secretEntries.reduce((acc, [name, secretValue]) => acc.replace(new RegExp("\\$" + name, "g"), secretValue), `${value}`)])));
414
504
  };
415
505
  const getEnvVarsResolved = async (io, env, componentName) => {
416
506
  if (!componentName) {
@@ -464,6 +554,36 @@ exports.parseChoice = parseChoice;
464
554
 
465
555
  /***/ }),
466
556
 
557
+ /***/ 2284:
558
+ /***/ ((__unused_webpack_module, exports) => {
559
+
560
+ "use strict";
561
+
562
+
563
+ Object.defineProperty(exports, "__esModule", ({
564
+ value: true
565
+ }));
566
+ exports.MissingInputError = exports.NonInteractiveError = void 0;
567
+ // ─── Errors ──────────────────────────────────────────────────────────
568
+ class NonInteractiveError extends Error {
569
+ constructor(interaction) {
570
+ super(`${interaction} requires an interactive terminal, but this context is not interactive`);
571
+ this.name = "NonInteractiveError";
572
+ }
573
+ }
574
+ exports.NonInteractiveError = NonInteractiveError;
575
+ class MissingInputError extends Error {
576
+ constructor(inputName, promptMessage) {
577
+ super(`Missing required input: "${inputName}" (${promptMessage}). ` + `Provide it via --${inputName}=<value> or --inputs='{"${inputName}": ...}'`);
578
+ this.inputName = inputName;
579
+ this.promptMessage = promptMessage;
580
+ this.name = "MissingInputError";
581
+ }
582
+ }
583
+ exports.MissingInputError = MissingInputError;
584
+
585
+ /***/ }),
586
+
467
587
  /***/ 6636:
468
588
  /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
469
589
 
@@ -518,14 +638,14 @@ exports.getProjectRootPath = getProjectRootPath;
518
638
  /**
519
639
  * Get the git remote hostname and project path from the git config.
520
640
  */
521
- const getGitRemoteHostAndPath = async () => {
641
+ const getGitRemoteHostAndPath = async (remoteName = "origin") => {
522
642
  var _a;
523
- const remoteUrl = await (0, gitConfig_1.gitConfigGet)("remote.origin.url");
643
+ const remoteUrl = await (0, gitConfig_1.gitConfigGet)(`remote.${remoteName}.url`);
524
644
  const remoteReg = /(https:\/\/|git@)([^:/]+)[:/]([^.]*)(\.git)?/;
525
645
  const match = (_a = remoteUrl.match(remoteReg)) !== null && _a !== void 0 ? _a : [];
526
646
  const [,, gitRemoteHost, gitRemotePath] = match;
527
647
  if (!(gitRemoteHost === null || gitRemoteHost === void 0 ? void 0 : gitRemoteHost.length) || !(gitRemotePath === null || gitRemotePath === void 0 ? void 0 : gitRemotePath.length)) {
528
- throw new Error(`Failed to parse git remote hostname and path from git configs remote.origin.url! ${remoteUrl}`);
648
+ throw new Error(`Failed to parse git remote hostname and path from git configs remote.${remoteName}.url! ${remoteUrl}`);
529
649
  }
530
650
  return {
531
651
  gitRemoteHost,
@@ -947,6 +1067,445 @@ exports.getProjectNamespace = getProjectNamespace;
947
1067
 
948
1068
  /***/ }),
949
1069
 
1070
+ /***/ 3510:
1071
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
1072
+
1073
+ "use strict";
1074
+
1075
+
1076
+ Object.defineProperty(exports, "__esModule", ({
1077
+ value: true
1078
+ }));
1079
+ exports.BitwardenVault = void 0;
1080
+ const pipeline_1 = __nccwpck_require__(241);
1081
+ const yaml_1 = __nccwpck_require__(6637);
1082
+ const bw_1 = __nccwpck_require__(2211);
1083
+ const DEFAULT_COLLECTION = "catladder";
1084
+ const SECURE_NOTE_TYPE = 2;
1085
+ /**
1086
+ * one yaml note per env and component:
1087
+ * `<customerName>/<appName>/<env>/<componentName>/secrets.yml`
1088
+ */
1089
+ const itemName = (config, env, componentName) => `${config.customerName}/${config.appName}/${env}/${componentName}/secrets.yml`;
1090
+ const itemPrefix = config => `${config.customerName}/${config.appName}/`;
1091
+ /**
1092
+ * parses env and componentName back out of an item name
1093
+ */
1094
+ const parseItemName = (config, name) => {
1095
+ const match = name.slice(itemPrefix(config).length).match(/^([^/]+)\/([^/]+)\/secrets\.yml$/);
1096
+ return match ? {
1097
+ env: match[1],
1098
+ componentName: match[2]
1099
+ } : null;
1100
+ };
1101
+ const findCollection = async (session, collectionName) => {
1102
+ const collections = await (0, bw_1.runBwJson)(session, ["list", "collections", "--search", collectionName]);
1103
+ const collection = collections.find(c => c.name === collectionName);
1104
+ if (!collection) {
1105
+ throw new Error(`bitwarden collection '${collectionName}' not found — create it and make sure you have access`);
1106
+ }
1107
+ return collection;
1108
+ };
1109
+ const encodePayload = payload => Buffer.from(JSON.stringify(payload), "utf-8").toString("base64");
1110
+ class BitwardenVault {
1111
+ constructor(config, options,
1112
+ /**
1113
+ * whether a locked bitwarden may be unlocked interactively
1114
+ * (false with catenv --vault-mode no-prompt)
1115
+ */
1116
+ allowPrompt) {
1117
+ var _a;
1118
+ this.config = config;
1119
+ this.allowPrompt = allowPrompt;
1120
+ this.collectionName = (_a = options.collection) !== null && _a !== void 0 ? _a : DEFAULT_COLLECTION;
1121
+ this.id = `bitwarden:${this.collectionName}`;
1122
+ }
1123
+ session(io) {
1124
+ return (0, bw_1.getBwSession)(io, {
1125
+ allowPrompt: this.allowPrompt
1126
+ });
1127
+ }
1128
+ async readAllSecrets(io) {
1129
+ const {
1130
+ config,
1131
+ collectionName
1132
+ } = this;
1133
+ const bwSession = await this.session(io);
1134
+ const collection = await findCollection(bwSession, collectionName);
1135
+ const items = await (0, bw_1.runBwJson)(bwSession, ["list", "items", "--collectionid", collection.id]);
1136
+ const secrets = {};
1137
+ for (const item of items) {
1138
+ const parsed = parseItemName(config, item.name);
1139
+ if (!parsed || !item.notes) {
1140
+ continue;
1141
+ }
1142
+ const values = (0, yaml_1.parse)(item.notes);
1143
+ Object.entries(values !== null && values !== void 0 ? values : {}).forEach(([key, value]) => {
1144
+ secrets[(0, pipeline_1.getSecretVarName)(parsed.env, parsed.componentName, key)] = typeof value === "string" ? value : JSON.stringify(value);
1145
+ });
1146
+ }
1147
+ return secrets;
1148
+ }
1149
+ async writeSecrets(io, env, componentName, secrets) {
1150
+ const {
1151
+ config,
1152
+ collectionName
1153
+ } = this;
1154
+ const bwSession = await this.session(io);
1155
+ const collection = await findCollection(bwSession, collectionName);
1156
+ const name = itemName(config, env, componentName);
1157
+ const existing = (await (0, bw_1.runBwJson)(bwSession, ["list", "items", "--collectionid", collection.id, "--search", name])).find(item => item.name === name);
1158
+ const notes = (0, yaml_1.stringify)(secrets);
1159
+ if (existing) {
1160
+ await (0, bw_1.runBw)(bwSession, ["edit", "item", existing.id, encodePayload({
1161
+ ...existing,
1162
+ notes
1163
+ })]);
1164
+ } else {
1165
+ await (0, bw_1.runBw)(bwSession, ["create", "item", encodePayload({
1166
+ type: SECURE_NOTE_TYPE,
1167
+ secureNote: {
1168
+ type: 0
1169
+ },
1170
+ name,
1171
+ notes,
1172
+ ...(collection.organizationId ? {
1173
+ organizationId: collection.organizationId,
1174
+ collectionIds: [collection.id]
1175
+ } : {})
1176
+ })]);
1177
+ }
1178
+ }
1179
+ }
1180
+ exports.BitwardenVault = BitwardenVault;
1181
+
1182
+ /***/ }),
1183
+
1184
+ /***/ 1107:
1185
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
1186
+
1187
+ "use strict";
1188
+
1189
+
1190
+ Object.defineProperty(exports, "__esModule", ({
1191
+ value: true
1192
+ }));
1193
+ exports.GitlabVault = void 0;
1194
+ const gitlab_1 = __nccwpck_require__(4508);
1195
+ /**
1196
+ * the legacy (and default) vault: the gitlab project variables double
1197
+ * as the secret store
1198
+ */
1199
+ class GitlabVault {
1200
+ constructor() {
1201
+ this.id = "gitlab";
1202
+ }
1203
+ async readAllSecrets(io) {
1204
+ const variables = await (0, gitlab_1.getAllVariables)(io);
1205
+ return Object.fromEntries(variables.map(v => [v.key, v.value]));
1206
+ }
1207
+ async writeSecrets(io, env, componentName, secrets) {
1208
+ await (0, gitlab_1.upsertAllVariables)(io, secrets, env, componentName);
1209
+ }
1210
+ }
1211
+ exports.GitlabVault = GitlabVault;
1212
+
1213
+ /***/ }),
1214
+
1215
+ /***/ 5771:
1216
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
1217
+
1218
+ "use strict";
1219
+
1220
+
1221
+ Object.defineProperty(exports, "__esModule", ({
1222
+ value: true
1223
+ }));
1224
+ exports.VaultManager = void 0;
1225
+ const pipeline_1 = __nccwpck_require__(241);
1226
+ const BitwardenVault_1 = __nccwpck_require__(3510);
1227
+ const cache_1 = __nccwpck_require__(4236);
1228
+ const GitlabVault_1 = __nccwpck_require__(1107);
1229
+ /**
1230
+ * only secrets of this env are cached locally — the other envs'
1231
+ * secrets have no business sitting on developer machines
1232
+ */
1233
+ const CACHED_ENV = "local";
1234
+ /**
1235
+ * owns the configured vault, the secrets mode and the local cache.
1236
+ * The vault is the readable source of truth for secrets; CI backends
1237
+ * only receive mirrored copies.
1238
+ */
1239
+ class VaultManager {
1240
+ constructor(config, options = {}) {
1241
+ var _a, _b;
1242
+ this.config = config;
1243
+ /** refresh mode bypasses the cache only once per manager */
1244
+ this.refreshed = false;
1245
+ this.mode = (_a = options.mode) !== null && _a !== void 0 ? _a : "auto";
1246
+ this.io = (_b = options.io) !== null && _b !== void 0 ? _b : null;
1247
+ this.vault = this.createVault();
1248
+ }
1249
+ createVault() {
1250
+ const vaultConfig = (0, pipeline_1.getVaultConfig)(this.config);
1251
+ switch (vaultConfig.type) {
1252
+ case "gitlab":
1253
+ return new GitlabVault_1.GitlabVault();
1254
+ case "bitwarden":
1255
+ return new BitwardenVault_1.BitwardenVault(this.config, vaultConfig, this.mode === "auto" || this.mode === "refresh");
1256
+ }
1257
+ }
1258
+ /**
1259
+ * the vault secrets, served from the local cache when it contains
1260
+ * all required names; otherwise (depending on the mode) refreshed
1261
+ * from the vault. Only the "local" env is (re-)cached.
1262
+ */
1263
+ async readSecrets(requiredNames, io = this.io) {
1264
+ const bypassCache = this.mode === "refresh" && !this.refreshed;
1265
+ const cache = bypassCache ? null : await (0, cache_1.readSecretsCache)();
1266
+ // known entries include recorded misses (null values), so declared
1267
+ // but intentionally unset secrets don't force a vault refresh
1268
+ const known = (cache === null || cache === void 0 ? void 0 : cache.vaultId) === this.vault.id ? flattenSecrets(cache.secrets) : undefined;
1269
+ const missing = requiredNames.filter(name => !(name in (known !== null && known !== void 0 ? known : {})));
1270
+ // nothing missing (or nothing required at all): don't touch the vault
1271
+ if (missing.length === 0 && (known || requiredNames.length === 0)) {
1272
+ return withoutMisses(known !== null && known !== void 0 ? known : {});
1273
+ }
1274
+ if (this.mode === "offline") {
1275
+ if (missing.length > 0) {
1276
+ io === null || io === void 0 ? void 0 : io.log(`⚠️ vault is offline (--vault-mode offline) and ${missing.length} secrets are not in the local cache: ${missing.join(", ")}`);
1277
+ }
1278
+ return withoutMisses(known !== null && known !== void 0 ? known : {});
1279
+ }
1280
+ const secrets = await this.vault.readAllSecrets(io);
1281
+ this.refreshed = true;
1282
+ await (0, cache_1.writeSecretsCache)(this.vault.id, await this.cacheableSubset(secrets));
1283
+ return secrets;
1284
+ }
1285
+ /**
1286
+ * writes secrets of one env/component to the vault and updates the
1287
+ * local cache (write-through, "local" env only)
1288
+ */
1289
+ async writeSecrets(env, componentName, secrets, io = this.io) {
1290
+ if (!io) {
1291
+ throw new Error("writing to the vault requires an interactive context");
1292
+ }
1293
+ await this.vault.writeSecrets(io, env, componentName, secrets);
1294
+ if (env === CACHED_ENV) {
1295
+ await (0, cache_1.updateSecretsCache)(this.vault.id, env, componentName, Object.fromEntries(Object.entries(secrets).map(([key, value]) => [key, typeof value === "string" ? value : JSON.stringify(value)])));
1296
+ }
1297
+ }
1298
+ /**
1299
+ * the subset of the vault secrets that may be cached: the declared
1300
+ * secrets of the "local" env, per component, as a hierarchy
1301
+ */
1302
+ async cacheableSubset(flatSecrets) {
1303
+ const local = {};
1304
+ for (const componentName of Object.keys(this.config.components)) {
1305
+ const keys = await this.declaredSecretKeys(componentName);
1306
+ const values = Object.fromEntries(keys.map(key => {
1307
+ var _a;
1308
+ return [key,
1309
+ // null records "absent in the vault" (a known miss)
1310
+ (_a = flatSecrets[(0, pipeline_1.getSecretVarName)(CACHED_ENV, componentName, key)]) !== null && _a !== void 0 ? _a : null];
1311
+ }));
1312
+ if (Object.keys(values).length > 0) {
1313
+ local[componentName] = values;
1314
+ }
1315
+ }
1316
+ return Object.keys(local).length > 0 ? {
1317
+ [CACHED_ENV]: local
1318
+ } : {};
1319
+ }
1320
+ async declaredSecretKeys(componentName) {
1321
+ try {
1322
+ const {
1323
+ secretEnvVarKeys,
1324
+ jobOnlyVars
1325
+ } = await (0, pipeline_1.getEnvironment)({
1326
+ config: this.config,
1327
+ componentName,
1328
+ env: CACHED_ENV
1329
+ });
1330
+ return [...secretEnvVarKeys, ...jobOnlyVars.build.secretEnvVarKeys, ...jobOnlyVars.deploy.secretEnvVarKeys].map(({
1331
+ key
1332
+ }) => key);
1333
+ } catch (_a) {
1334
+ // components without a local env simply have nothing to cache
1335
+ return [];
1336
+ }
1337
+ }
1338
+ }
1339
+ exports.VaultManager = VaultManager;
1340
+ /**
1341
+ * the cache hierarchy as a flat map of raw secret variable names
1342
+ * (null values = recorded misses)
1343
+ */
1344
+ const flattenSecrets = secrets => Object.fromEntries(Object.entries(secrets).flatMap(([env, components]) => Object.entries(components).flatMap(([componentName, values]) => Object.entries(values).map(([key, value]) => [(0, pipeline_1.getSecretVarName)(env, componentName, key), value]))));
1345
+ /**
1346
+ * only actual values — recorded misses stay unresolved, like any other
1347
+ * unset variable
1348
+ */
1349
+ const withoutMisses = known => Object.fromEntries(Object.entries(known).filter(entry => entry[1] !== null));
1350
+
1351
+ /***/ }),
1352
+
1353
+ /***/ 2211:
1354
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
1355
+
1356
+ "use strict";
1357
+
1358
+
1359
+ Object.defineProperty(exports, "__esModule", ({
1360
+ value: true
1361
+ }));
1362
+ exports.runBwJson = exports.runBw = exports.getBwSession = void 0;
1363
+ const child_process_1 = __nccwpck_require__(5317);
1364
+ const util_1 = __nccwpck_require__(9023);
1365
+ const preferences_1 = __nccwpck_require__(6261);
1366
+ const execFile = (0, util_1.promisify)(child_process_1.execFile);
1367
+ const SESSION_PREFERENCE = "bwsession";
1368
+ class BitwardenLockedError extends Error {
1369
+ constructor() {
1370
+ super("bitwarden is locked and prompting is disabled — run `bw unlock` or catenv without --vault-mode no-prompt");
1371
+ }
1372
+ }
1373
+ /**
1374
+ * runs an interactive bw command that prints a session key on stdout
1375
+ * (login/unlock) while the user types the password on the terminal
1376
+ */
1377
+ const interactiveSessionCommand = args => new Promise((resolve, reject) => {
1378
+ var _a;
1379
+ const child = (0, child_process_1.spawn)("bw", [...args, "--raw"], {
1380
+ stdio: ["inherit", "pipe", "inherit"]
1381
+ });
1382
+ let session = "";
1383
+ (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on("data", data => session += data.toString("utf-8"));
1384
+ child.on("error", reject);
1385
+ child.on("close", code => code === 0 && session.trim() ? resolve(session.trim()) : reject(new Error(`bw ${args[0]} failed`)));
1386
+ });
1387
+ const getBwStatus = async session => {
1388
+ try {
1389
+ const {
1390
+ stdout
1391
+ } = await execFile("bw", ["status"], {
1392
+ env: {
1393
+ ...process.env,
1394
+ ...(session ? {
1395
+ BW_SESSION: session
1396
+ } : {})
1397
+ }
1398
+ });
1399
+ return JSON.parse(stdout).status;
1400
+ } catch (_a) {
1401
+ return "unauthenticated";
1402
+ }
1403
+ };
1404
+ /**
1405
+ * a working bw session: reuses the cached one, otherwise unlocks (or
1406
+ * logs in) interactively — unless prompting is disabled
1407
+ */
1408
+ const getBwSession = async (io, {
1409
+ allowPrompt
1410
+ }) => {
1411
+ const cached = (await (0, preferences_1.hasPreference)(SESSION_PREFERENCE)) ? String(await (0, preferences_1.getPreference)(SESSION_PREFERENCE)) : null;
1412
+ const status = await getBwStatus(cached);
1413
+ if (status === "unlocked" && cached) {
1414
+ return cached;
1415
+ }
1416
+ if (!allowPrompt) {
1417
+ throw new BitwardenLockedError();
1418
+ }
1419
+ io === null || io === void 0 ? void 0 : io.log("");
1420
+ io === null || io === void 0 ? void 0 : io.log(status === "unauthenticated" ? "# Please login to Bitwarden:" : "# Bitwarden is locked, please unlock:");
1421
+ io === null || io === void 0 ? void 0 : io.log("");
1422
+ const session = await interactiveSessionCommand([status === "unauthenticated" ? "login" : "unlock"]);
1423
+ await (0, preferences_1.setPreference)(SESSION_PREFERENCE, session);
1424
+ await (0, exports.runBw)(session, ["sync"]);
1425
+ return session;
1426
+ };
1427
+ exports.getBwSession = getBwSession;
1428
+ /**
1429
+ * runs a bw command with the given session and returns stdout
1430
+ */
1431
+ const runBw = async (session, args) => {
1432
+ const {
1433
+ stdout
1434
+ } = await execFile("bw", [...args, "--raw", "--nointeraction"], {
1435
+ env: {
1436
+ ...process.env,
1437
+ BW_SESSION: session
1438
+ },
1439
+ maxBuffer: 50 * 1024 * 1024
1440
+ });
1441
+ return stdout;
1442
+ };
1443
+ exports.runBw = runBw;
1444
+ const runBwJson = async (session, args) => JSON.parse(await (0, exports.runBw)(session, args));
1445
+ exports.runBwJson = runBwJson;
1446
+
1447
+ /***/ }),
1448
+
1449
+ /***/ 4236:
1450
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
1451
+
1452
+ "use strict";
1453
+
1454
+
1455
+ Object.defineProperty(exports, "__esModule", ({
1456
+ value: true
1457
+ }));
1458
+ exports.updateSecretsCache = exports.writeSecretsCache = exports.readSecretsCache = void 0;
1459
+ const promises_1 = __nccwpck_require__(1943);
1460
+ const path_1 = __nccwpck_require__(6928);
1461
+ const gitProjectInformation_1 = __nccwpck_require__(1111);
1462
+ const getCacheFolder = async () => (0, path_1.join)(await (0, gitProjectInformation_1.getProjectRootPath)(), ".catladder");
1463
+ const getCachePath = async () => (0, path_1.join)(await getCacheFolder(), "secrets.json");
1464
+ const readSecretsCache = async () => {
1465
+ try {
1466
+ return JSON.parse(await (0, promises_1.readFile)(await getCachePath(), "utf-8"));
1467
+ } catch (_a) {
1468
+ return null;
1469
+ }
1470
+ };
1471
+ exports.readSecretsCache = readSecretsCache;
1472
+ const writeSecretsCache = async (vaultId, secrets) => {
1473
+ const folder = await getCacheFolder();
1474
+ await (0, promises_1.mkdir)(folder, {
1475
+ recursive: true
1476
+ });
1477
+ // the folder ignores itself, so the cache can never be committed
1478
+ await (0, promises_1.writeFile)((0, path_1.join)(folder, ".gitignore"), "*\n");
1479
+ const cache = {
1480
+ vaultId,
1481
+ updatedAt: new Date().toISOString(),
1482
+ secrets
1483
+ };
1484
+ await (0, promises_1.writeFile)(await getCachePath(), JSON.stringify(cache, null, 2));
1485
+ };
1486
+ exports.writeSecretsCache = writeSecretsCache;
1487
+ /**
1488
+ * merges freshly written secrets of one env/component into the cache
1489
+ * (write-through from config-secrets), so the next catenv run doesn't
1490
+ * need the vault
1491
+ */
1492
+ const updateSecretsCache = async (vaultId, env, componentName, values) => {
1493
+ var _a;
1494
+ const existing = await (0, exports.readSecretsCache)();
1495
+ const secrets = (existing === null || existing === void 0 ? void 0 : existing.vaultId) === vaultId ? existing.secrets : {};
1496
+ secrets[env] = {
1497
+ ...secrets[env],
1498
+ [componentName]: {
1499
+ ...((_a = secrets[env]) === null || _a === void 0 ? void 0 : _a[componentName]),
1500
+ ...values
1501
+ }
1502
+ };
1503
+ await (0, exports.writeSecretsCache)(vaultId, secrets);
1504
+ };
1505
+ exports.updateSecretsCache = updateSecretsCache;
1506
+
1507
+ /***/ }),
1508
+
950
1509
  /***/ 4038:
951
1510
  /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
952
1511
 
@@ -963,7 +1522,11 @@ const createAllJobs_1 = __nccwpck_require__(3671);
963
1522
  const config_1 = __nccwpck_require__(6925);
964
1523
  const ciVariables_1 = __nccwpck_require__(3093);
965
1524
  const createGithubJobs_1 = __nccwpck_require__(4016);
1525
+ const jobImagesPlan_1 = __nccwpck_require__(1585);
1526
+ const scriptFunctions_1 = __nccwpck_require__(5759);
1527
+ const utils_1 = __nccwpck_require__(595);
966
1528
  const githubReleaseJobs_1 = __nccwpck_require__(5611);
1529
+ const scriptFiles_1 = __nccwpck_require__(9189);
967
1530
  const WORKFLOWS_FOLDER = ".github/workflows";
968
1531
  /**
969
1532
  * generated workflow files are prefixed so that cleanup never touches
@@ -1008,18 +1571,29 @@ class GithubBackend {
1008
1571
  async cleanup() {
1009
1572
  const entries = await (0, promises_1.readdir)(WORKFLOWS_FOLDER).catch(() => []);
1010
1573
  await Promise.all(entries.filter(file => file.startsWith(GENERATED_FILE_PREFIX)).map(file => (0, promises_1.unlink)((0, path_1.join)(WORKFLOWS_FOLDER, file))));
1574
+ await (0, promises_1.rm)(scriptFiles_1.GITHUB_SCRIPTS_FOLDER, {
1575
+ recursive: true,
1576
+ force: true
1577
+ });
1011
1578
  }
1012
1579
  async createFiles(config) {
1013
- const workflows = await this.createWorkflows(config);
1014
- return Object.entries(workflows).map(([fileName, workflow]) => ({
1580
+ const images = this.createImagesPlan(config);
1581
+ const scripts = new scriptFiles_1.GithubScriptFiles();
1582
+ const workflows = await this.createWorkflows(config, images, scripts);
1583
+ return [...Object.entries(workflows).map(([fileName, workflow]) => ({
1015
1584
  path: (0, path_1.join)(WORKFLOWS_FOLDER, fileName),
1016
1585
  content: workflow
1017
- }));
1586
+ })),
1587
+ // materialized job scripts and image definitions (repo mode)
1588
+ ...scripts.getGeneratedFiles(), ...images.getGeneratedFiles()];
1589
+ }
1590
+ createImagesPlan(config) {
1591
+ return new jobImagesPlan_1.JobImagesPlan((0, jobImagesPlan_1.getJobImagesMode)(config, this.type), this.type);
1018
1592
  }
1019
1593
  /**
1020
1594
  * all workflows keyed by file name, mainly for testing purposes
1021
1595
  */
1022
- async createWorkflows(config) {
1596
+ async createWorkflows(config, images = this.createImagesPlan(config), scripts = new scriptFiles_1.GithubScriptFiles()) {
1023
1597
  const workflows = {};
1024
1598
  const reviewStopJobs = {};
1025
1599
  const manualJobs = {};
@@ -1030,13 +1604,14 @@ class GithubBackend {
1030
1604
  pipelineType: this.type
1031
1605
  });
1032
1606
  const uploadProviderIds = (0, createGithubJobs_1.getUploadProviderIds)(allJobs);
1607
+ const secretKinds = (0, createGithubJobs_1.collectSecretKinds)(allJobs);
1033
1608
  const jobs = {};
1034
1609
  // NOTE: agent jobs are gitlab-only and skipped on github
1035
1610
  [...allJobs.workspaces, ...allJobs.components].forEach(({
1036
1611
  context,
1037
1612
  jobs: contextJobs
1038
1613
  }) => contextJobs.forEach(job => {
1039
- const [id, githubJob] = (0, createGithubJobs_1.makeGithubJob)(context, job, allJobs, uploadProviderIds);
1614
+ const [id, githubJob] = (0, createGithubJobs_1.makeGithubJob)(context, job, allJobs, uploadProviderIds, images, scripts, secretKinds);
1040
1615
  if (isReviewStopJob(context, job)) {
1041
1616
  reviewStopJobs[id] = githubJob;
1042
1617
  } else if (isManualTaskJob(job)) {
@@ -1046,15 +1621,19 @@ class GithubBackend {
1046
1621
  }
1047
1622
  }));
1048
1623
  if (trigger === "mainBranch") {
1049
- const releaseJobs = (0, githubReleaseJobs_1.getGithubReleaseJobs)(config, Object.keys(jobs));
1624
+ const releaseJobs = (0, githubReleaseJobs_1.getGithubReleaseJobs)(config, Object.keys(jobs), images);
1050
1625
  Object.assign(jobs, releaseJobs.main);
1051
1626
  Object.assign(manualJobs, releaseJobs.manual);
1052
1627
  }
1053
1628
  if (Object.keys(jobs).length > 0) {
1054
1629
  workflows[`${GENERATED_FILE_PREFIX}${workflowFileName(trigger)}`] = {
1055
1630
  ...TRIGGER_WORKFLOWS[trigger],
1631
+ ...workflowPermissions(images),
1056
1632
  env: ciVariables_1.GITHUB_INJECTED_WORKFLOW_ENV,
1057
- jobs
1633
+ jobs: {
1634
+ ...makeEnsureImageGithubJobs(images),
1635
+ ...jobs
1636
+ }
1058
1637
  };
1059
1638
  }
1060
1639
  }
@@ -1066,8 +1645,12 @@ class GithubBackend {
1066
1645
  types: ["closed"]
1067
1646
  }
1068
1647
  },
1648
+ ...workflowPermissions(images),
1069
1649
  env: ciVariables_1.GITHUB_INJECTED_WORKFLOW_ENV,
1070
- jobs: reviewStopJobs
1650
+ jobs: {
1651
+ ...makeEnsureImageGithubJobs(images),
1652
+ ...reviewStopJobs
1653
+ }
1071
1654
  };
1072
1655
  }
1073
1656
  if (Object.keys(manualJobs).length > 0) {
@@ -1085,17 +1668,51 @@ class GithubBackend {
1085
1668
  }
1086
1669
  }
1087
1670
  },
1671
+ ...workflowPermissions(images),
1088
1672
  env: ciVariables_1.GITHUB_INJECTED_WORKFLOW_ENV,
1089
- jobs: Object.fromEntries(Object.entries(manualJobs).map(([id, job]) => [id, {
1090
- ...job,
1091
- if: `\${{ inputs.job == '${id}' }}`
1092
- }]))
1673
+ jobs: {
1674
+ ...makeEnsureImageGithubJobs(images),
1675
+ ...Object.fromEntries(Object.entries(manualJobs).map(([id, job]) => [id, {
1676
+ ...job,
1677
+ if: `\${{ inputs.job == '${id}' }}`
1678
+ }]))
1679
+ }
1093
1680
  };
1094
1681
  }
1095
1682
  return workflows;
1096
1683
  }
1097
1684
  }
1098
1685
  exports.GithubBackend = GithubBackend;
1686
+ /**
1687
+ * repo mode needs packages:write for pushing job images to ghcr
1688
+ */
1689
+ const workflowPermissions = images => images.mode === "repo" ? {
1690
+ permissions: {
1691
+ contents: "read",
1692
+ packages: "write"
1693
+ }
1694
+ } : {};
1695
+ /**
1696
+ * the image build jobs: always run (github has no server-side
1697
+ * changes-filter), the existence check makes unchanged runs fast.
1698
+ * Docker is native on github runners — no container, no dind.
1699
+ */
1700
+ const makeEnsureImageGithubJobs = images => Object.fromEntries(images.getEnsureJobs().map(job => {
1701
+ var _a, _b;
1702
+ return [(0, createGithubJobs_1.githubGlobalJobId)(job.name), {
1703
+ name: job.name,
1704
+ "runs-on": "ubuntu-latest",
1705
+ steps: [{
1706
+ name: "Checkout",
1707
+ uses: "actions/checkout@v4"
1708
+ }, {
1709
+ name: job.name,
1710
+ id: "main",
1711
+ run: [...(0, scriptFunctions_1.getGithubScriptFunctionDefinitions)(), ...((_b = (_a = job.script) === null || _a === void 0 ? void 0 : _a.filter(utils_1.notNil)) !== null && _b !== void 0 ? _b : [])].join("\n"),
1712
+ shell: "bash"
1713
+ }]
1714
+ }];
1715
+ }));
1099
1716
  const workflowFileName = trigger => {
1100
1717
  switch (trigger) {
1101
1718
  case "mr":
@@ -1146,7 +1763,8 @@ exports.GITHUB_CI_VARIABLES = {
1146
1763
  jobToken: "CL_JOB_TOKEN",
1147
1764
  jobImage: "CL_JOB_IMAGE",
1148
1765
  registry: "CL_REGISTRY",
1149
- registryImage: "CL_REGISTRY_IMAGE"
1766
+ registryImage: "CL_REGISTRY_IMAGE",
1767
+ registryUser: "CL_REGISTRY_USER"
1150
1768
  };
1151
1769
  /**
1152
1770
  * the workflow-level env the backend injects to provide the CL_*
@@ -1156,6 +1774,7 @@ exports.GITHUB_INJECTED_WORKFLOW_ENV = {
1156
1774
  CL_JOB_TOKEN: "${{ github.token }}",
1157
1775
  CL_REGISTRY: "ghcr.io",
1158
1776
  CL_REGISTRY_IMAGE: "ghcr.io/${{ github.repository }}",
1777
+ CL_REGISTRY_USER: "${{ github.actor }}",
1159
1778
  // empty outside of pull request events
1160
1779
  CL_PR_NUMBER: "${{ github.event.number }}"
1161
1780
  };
@@ -1171,7 +1790,7 @@ exports.GITHUB_INJECTED_WORKFLOW_ENV = {
1171
1790
  Object.defineProperty(exports, "__esModule", ({
1172
1791
  value: true
1173
1792
  }));
1174
- exports.getUploadProviderIds = exports.makeGithubJob = exports.resolveGithubNeeds = exports.githubJobId = void 0;
1793
+ exports.getUploadProviderIds = exports.makeGithubJob = exports.collectSecretKinds = exports.resolveGithubNeeds = exports.githubGlobalJobId = exports.githubJobId = void 0;
1175
1794
  const lodash_es_1 = __nccwpck_require__(4284);
1176
1795
  const getInjectVarsScript_1 = __nccwpck_require__(436);
1177
1796
  const createJobCache_1 = __nccwpck_require__(5644);
@@ -1179,6 +1798,7 @@ const getEnvironmentVariables_1 = __nccwpck_require__(5855);
1179
1798
  const utils_1 = __nccwpck_require__(595);
1180
1799
  const gitlab_1 = __nccwpck_require__(3468);
1181
1800
  const scriptFunctions_1 = __nccwpck_require__(5759);
1801
+ const ciVariables_1 = __nccwpck_require__(3093);
1182
1802
  /**
1183
1803
  * runner variables that only make sense on gitlab runners and must not
1184
1804
  * leak into github jobs (docker-in-docker daemon config, kubernetes
@@ -1197,6 +1817,9 @@ const slug = value => {
1197
1817
  };
1198
1818
  const githubJobId = (context, jobName) => slug(`${context.type === "workspace" ? "ws-" : ""}${context.name}-${jobName}-${context.env}`);
1199
1819
  exports.githubJobId = githubJobId;
1820
+ /** id of a job without component/workspace context (e.g. image builds) */
1821
+ const githubGlobalJobId = jobName => slug(jobName);
1822
+ exports.githubGlobalJobId = githubGlobalJobId;
1200
1823
  const githubJobName = (context, jobName) => `${context.name} ${jobName} | ${context.env}`;
1201
1824
  const resolveNeed = (context, need, allJobs) => {
1202
1825
  var _a;
@@ -1215,9 +1838,15 @@ const resolveNeed = (context, need, allJobs) => {
1215
1838
  if (!provider || !job) {
1216
1839
  throw new Error(`unknown job referenced: '${jobName}' from workspace '${env}:${workspaceName}'`);
1217
1840
  }
1218
- return (0, exports.githubJobId)(provider.context, jobName);
1219
- };
1220
- if ((0, lodash_es_1.isObject)(need)) {
1841
+ return (0, exports.githubJobId)(provider.context, jobName);
1842
+ };
1843
+ if ((0, lodash_es_1.isObject)(need)) {
1844
+ if ("global" in need && need.global) {
1845
+ return {
1846
+ id: (0, exports.githubGlobalJobId)(need.job),
1847
+ artifacts: need.artifacts
1848
+ };
1849
+ }
1221
1850
  if ("workspaceName" in need && need.workspaceName) {
1222
1851
  return {
1223
1852
  id: findWorkspaceJob(need.workspaceName, need.job),
@@ -1235,9 +1864,9 @@ const resolveNeed = (context, need, allJobs) => {
1235
1864
  artifacts: true
1236
1865
  };
1237
1866
  };
1238
- const resolveGithubNeeds = (context, job, allJobs) => {
1867
+ const resolveGithubNeeds = (context, job, allJobs, extraNeeds = []) => {
1239
1868
  var _a;
1240
- const resolved = ((_a = job.needs) !== null && _a !== void 0 ? _a : []).map(need => resolveNeed(context, need, allJobs));
1869
+ const resolved = [...extraNeeds, ...((_a = job.needs) !== null && _a !== void 0 ? _a : [])].map(need => resolveNeed(context, need, allJobs));
1241
1870
  // deduplicate by provider id, explicit entries win (they come last)
1242
1871
  return [...new Map(resolved.map(n => [n.id, n])).values()].sort((a, b) => a.id.localeCompare(b.id));
1243
1872
  };
@@ -1278,29 +1907,73 @@ const createCacheSteps = (context, job) => {
1278
1907
  };
1279
1908
  });
1280
1909
  };
1281
- const getSecretsEnv = context => {
1910
+ /**
1911
+ * how a vault-stored value reaches the job env: masked github secret,
1912
+ * or plain (unmasked) github variable for non-sensitive values — those
1913
+ * stay readable in logs and usable in environment urls
1914
+ */
1915
+ const vaultValueExpression = (name, kinds) => kinds.get(name) === "variable" ? `\${{ vars.${name} }}` : `\${{ secrets.${name} }}`;
1916
+ /**
1917
+ * the kind of every vault-stored var of all components, keyed by its
1918
+ * CL_* name — the lookup for cross-component references
1919
+ */
1920
+ const collectSecretKinds = allJobs => {
1921
+ const kinds = new Map();
1922
+ allJobs.components.forEach(({
1923
+ context
1924
+ }) => {
1925
+ var _a, _b;
1926
+ const entries = [...context.environment.secretEnvVarKeys, ...((_a = context.environment.jobOnlyVars.build.secretEnvVarKeys) !== null && _a !== void 0 ? _a : []), ...((_b = context.environment.jobOnlyVars.deploy.secretEnvVarKeys) !== null && _b !== void 0 ? _b : [])];
1927
+ entries.forEach(({
1928
+ key,
1929
+ kind
1930
+ }) => kinds.set((0, getEnvironmentVariables_1.getSecretVarName)(context.env, context.name, key), kind !== null && kind !== void 0 ? kind : "secret"));
1931
+ });
1932
+ return kinds;
1933
+ };
1934
+ exports.collectSecretKinds = collectSecretKinds;
1935
+ const getSecretsEnv = (context, kinds) => {
1282
1936
  var _a, _b;
1283
1937
  const keys = [...context.environment.secretEnvVarKeys, ...((_a = context.environment.jobOnlyVars.build.secretEnvVarKeys) !== null && _a !== void 0 ? _a : []), ...((_b = context.environment.jobOnlyVars.deploy.secretEnvVarKeys) !== null && _b !== void 0 ? _b : [])];
1284
1938
  return Object.fromEntries(keys.map(({
1285
1939
  key
1286
1940
  }) => {
1287
1941
  const name = (0, getEnvironmentVariables_1.getSecretVarName)(context.env, context.name, key);
1288
- return [name, `\${{ secrets.${name} }}`];
1942
+ return [name, vaultValueExpression(name, kinds)];
1289
1943
  }));
1290
1944
  };
1291
- const makeGithubJob = (context, job, allJobs, uploadProviderIds) => {
1945
+ /**
1946
+ * every CL_* variable the job script reads must exist in the job env.
1947
+ * On gitlab all project variables are implicitly available in every
1948
+ * job — cross-component references (`${other-component:KEY}`) rely on
1949
+ * that. Github jobs only see what is injected explicitly, so the
1950
+ * script is scanned for the names it actually references.
1951
+ */
1952
+ const getReferencedSecretsEnv = (script, kinds) => Object.fromEntries([...script.matchAll(/\$\{?(CL_[A-Za-z0-9_]+)\}?/g)].map(match => match[1]).filter(name => !(name in ciVariables_1.GITHUB_INJECTED_WORKFLOW_ENV)).map(name => [name, vaultValueExpression(name, kinds)]));
1953
+ const makeGithubJob = (context, job, allJobs, uploadProviderIds, images, scripts, secretKinds = new Map()) => {
1292
1954
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
1293
1955
  const id = (0, exports.githubJobId)(context, job.name);
1294
1956
  const isComponent = context.type === "component";
1295
1957
  const createsJobEnv = !!job.environment && isComponent && (!job.environment.action || job.environment.action === "start");
1296
1958
  const skipCheckout = ((_a = job.runnerVariables) === null || _a === void 0 ? void 0 : _a.GIT_STRATEGY) === "none";
1297
- const needs = (0, exports.resolveGithubNeeds)(context, job, allJobs);
1298
- const image = typeof job.image === "string" ? job.image : Array.isArray(job.image) ? job.image[0] : (_b = job.image) === null || _b === void 0 ? void 0 : _b.name;
1959
+ const resolved = images.resolve(job.image);
1960
+ const needs = (0, exports.resolveGithubNeeds)(context, job, allJobs, resolved.need ? [resolved.need] : []);
1961
+ const image = typeof resolved.image === "string" ? resolved.image : Array.isArray(resolved.image) ? resolved.image[0] : (_b = resolved.image) === null || _b === void 0 ? void 0 : _b.name;
1299
1962
  const runScript = [...(0, scriptFunctions_1.getGithubScriptFunctionDefinitions)(), ...(0, gitlab_1.collapseableSection)("injectvars", "Injecting variables")((0, getInjectVarsScript_1.getInjectVarsScript)((_c = job.variables) !== null && _c !== void 0 ? _c : {})), ...((_e = (_d = job.script) === null || _d === void 0 ? void 0 : _d.filter(utils_1.notNil)) !== null && _e !== void 0 ? _e : []), ...(createsJobEnv ? [`echo "url=$ROOT_URL" >> "$GITHUB_OUTPUT"`] : [])].join("\n");
1963
+ // scripts live in committed files to keep workflows under github's
1964
+ // 512 KB limit. Two classes of jobs must keep their script inline:
1965
+ // jobs without a checkout have no repo files, and `${{ }}` expressions
1966
+ // are only interpolated inside the workflow file itself.
1967
+ const run = skipCheckout || runScript.includes("${{") ? runScript : scripts.add(id, runScript);
1300
1968
  const artifactPaths = (_g = (_f = job.artifacts) === null || _f === void 0 ? void 0 : _f.paths) !== null && _g !== void 0 ? _g : [];
1301
- const services = Object.fromEntries(((_h = job.services) !== null && _h !== void 0 ? _h : []).map(service => typeof service === "string" ? {
1969
+ const declaredServices = ((_h = job.services) !== null && _h !== void 0 ? _h : []).map(service => typeof service === "string" ? {
1302
1970
  name: service
1303
- } : service).filter(service => !isDindService(service.name)).map(service => [slug("alias" in service && service.alias ? service.alias : service.name), {
1971
+ } : service);
1972
+ // docker runs natively on github runners, so gitlab's docker-in-docker
1973
+ // services are dropped. The job container stays: the runner mounts the
1974
+ // host's docker socket into it, and images like docker-build carry the
1975
+ // helper scripts (ensure*Dockerfile etc.) the job scripts rely on.
1976
+ const services = Object.fromEntries(declaredServices.filter(service => !isDindService(service.name)).map(service => [slug("alias" in service && service.alias ? service.alias : service.name), {
1304
1977
  image: service.name,
1305
1978
  ...("variables" in service && service.variables ? {
1306
1979
  env: service.variables
@@ -1308,7 +1981,8 @@ const makeGithubJob = (context, job, allJobs, uploadProviderIds) => {
1308
1981
  }]));
1309
1982
  const env = {
1310
1983
  ...Object.fromEntries(Object.entries((_j = job.runnerVariables) !== null && _j !== void 0 ? _j : {}).filter(([key]) => !GITLAB_ONLY_RUNNER_VARIABLES.has(key))),
1311
- ...(isComponent ? getSecretsEnv(context) : {}),
1984
+ ...(isComponent ? getSecretsEnv(context, secretKinds) : {}),
1985
+ ...getReferencedSecretsEnv(runScript, secretKinds),
1312
1986
  CL_JOB_IMAGE: image !== null && image !== void 0 ? image : "runner"
1313
1987
  };
1314
1988
  const githubJob = {
@@ -1319,7 +1993,14 @@ const makeGithubJob = (context, job, allJobs, uploadProviderIds) => {
1319
1993
  } : {}),
1320
1994
  ...(image ? {
1321
1995
  container: {
1322
- image
1996
+ image,
1997
+ // private images in the repo's registry need credentials
1998
+ ...(resolved.fromRepoRegistry ? {
1999
+ credentials: {
2000
+ username: "${{ github.actor }}",
2001
+ password: "${{ github.token }}"
2002
+ }
2003
+ } : {})
1323
2004
  }
1324
2005
  } : {}),
1325
2006
  ...(Object.keys(services).length > 0 ? {
@@ -1346,7 +2027,7 @@ const makeGithubJob = (context, job, allJobs, uploadProviderIds) => {
1346
2027
  })), {
1347
2028
  name: job.name,
1348
2029
  id: "main",
1349
- run: runScript,
2030
+ run,
1350
2031
  shell: "bash"
1351
2032
  }, ...(uploadProviderIds.has(id) || artifactPaths.length > 0 ? artifactPaths.length > 0 ? [{
1352
2033
  name: "Upload artifacts",
@@ -1354,7 +2035,11 @@ const makeGithubJob = (context, job, allJobs, uploadProviderIds) => {
1354
2035
  with: {
1355
2036
  name: id,
1356
2037
  path: artifactPaths.join("\n"),
1357
- "if-no-files-found": "warn"
2038
+ "if-no-files-found": "warn",
2039
+ // gitlab artifacts include everything; upload-artifact
2040
+ // silently drops hidden files by default — which is
2041
+ // exactly where next.js builds live (.next)
2042
+ "include-hidden-files": true
1358
2043
  }
1359
2044
  }] : [] : [])]
1360
2045
  };
@@ -1387,36 +2072,49 @@ Object.defineProperty(exports, "__esModule", ({
1387
2072
  value: true
1388
2073
  }));
1389
2074
  exports.getGithubReleaseJobs = void 0;
1390
- const runner_1 = __nccwpck_require__(2348);
1391
- const createReleaseJob = needs => ({
1392
- name: "create release",
1393
- "runs-on": "ubuntu-latest",
1394
- container: {
1395
- image: (0, runner_1.getRunnerImage)("semantic-release")
1396
- },
1397
- // semantic-release pushes the version tag and release commit
1398
- permissions: {
1399
- contents: "write"
1400
- },
1401
- ...(needs && needs.length > 0 ? {
1402
- needs
1403
- } : {}),
1404
- env: {
1405
- GITHUB_TOKEN: "${{ github.token }}"
1406
- },
1407
- steps: [{
1408
- name: "Checkout",
1409
- uses: "actions/checkout@v4",
1410
- // semantic-release needs the full history to determine the version
1411
- with: {
1412
- "fetch-depth": 0
1413
- }
1414
- }, {
2075
+ const createGithubJobs_1 = __nccwpck_require__(4016);
2076
+ const createReleaseJob = (images, needs) => {
2077
+ const resolved = images.resolveRef({
2078
+ catladderImage: "semantic-release"
2079
+ });
2080
+ const imageNeeds = resolved.need && typeof resolved.need === "object" ? [(0, createGithubJobs_1.githubGlobalJobId)(resolved.need.job)] : [];
2081
+ const allNeeds = [...imageNeeds, ...(needs !== null && needs !== void 0 ? needs : [])];
2082
+ return {
1415
2083
  name: "create release",
1416
- run: "semanticRelease",
1417
- shell: "bash"
1418
- }]
1419
- });
2084
+ "runs-on": "ubuntu-latest",
2085
+ container: {
2086
+ image: resolved.image,
2087
+ ...(resolved.fromRepoRegistry ? {
2088
+ credentials: {
2089
+ username: "${{ github.actor }}",
2090
+ password: "${{ github.token }}"
2091
+ }
2092
+ } : {})
2093
+ },
2094
+ // semantic-release pushes the version tag and release commit
2095
+ permissions: {
2096
+ contents: "write"
2097
+ },
2098
+ ...(allNeeds.length > 0 ? {
2099
+ needs: allNeeds
2100
+ } : {}),
2101
+ env: {
2102
+ GITHUB_TOKEN: "${{ github.token }}"
2103
+ },
2104
+ steps: [{
2105
+ name: "Checkout",
2106
+ uses: "actions/checkout@v4",
2107
+ // semantic-release needs the full history to determine the version
2108
+ with: {
2109
+ "fetch-depth": 0
2110
+ }
2111
+ }, {
2112
+ name: "create release",
2113
+ run: "semanticRelease",
2114
+ shell: "bash"
2115
+ }]
2116
+ };
2117
+ };
1420
2118
  /**
1421
2119
  * the release jobs for the github backend (the counterpart of the
1422
2120
  * gitlab release jobs):
@@ -1426,22 +2124,22 @@ const createReleaseJob = needs => ({
1426
2124
  * - otherwise (manual, the default): the release job goes to the
1427
2125
  * manual tasks workflow
1428
2126
  */
1429
- const getGithubReleaseJobs = (config, mainWorkflowJobIds) => {
2127
+ const getGithubReleaseJobs = (config, mainWorkflowJobIds, images) => {
1430
2128
  var _a;
1431
2129
  if (((_a = config.releases) === null || _a === void 0 ? void 0 : _a.when) === "auto") {
1432
2130
  return {
1433
2131
  main: {
1434
- "create-release": createReleaseJob(mainWorkflowJobIds)
2132
+ "create-release": createReleaseJob(images, mainWorkflowJobIds)
1435
2133
  },
1436
2134
  manual: {
1437
- "force-create-release": createReleaseJob()
2135
+ "force-create-release": createReleaseJob(images)
1438
2136
  }
1439
2137
  };
1440
2138
  }
1441
2139
  return {
1442
2140
  main: {},
1443
2141
  manual: {
1444
- "create-release": createReleaseJob()
2142
+ "create-release": createReleaseJob(images)
1445
2143
  }
1446
2144
  };
1447
2145
  };
@@ -1479,6 +2177,64 @@ Object.defineProperty(exports, "__esModule", ({
1479
2177
  }));
1480
2178
  __exportStar(__nccwpck_require__(4038), exports);
1481
2179
  __exportStar(__nccwpck_require__(3093), exports);
2180
+ __exportStar(__nccwpck_require__(9189), exports);
2181
+
2182
+ /***/ }),
2183
+
2184
+ /***/ 9189:
2185
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
2186
+
2187
+ "use strict";
2188
+
2189
+
2190
+ Object.defineProperty(exports, "__esModule", ({
2191
+ value: true
2192
+ }));
2193
+ exports.GithubScriptFiles = exports.GITHUB_SCRIPTS_FOLDER = void 0;
2194
+ const path_1 = __nccwpck_require__(6928);
2195
+ /**
2196
+ * folder for job scripts materialized into the repository (committed,
2197
+ * like the image definitions). Owned entirely by the github backend.
2198
+ */
2199
+ exports.GITHUB_SCRIPTS_FOLDER = ".catladder-generated/github/scripts";
2200
+ /**
2201
+ * github silently ignores workflow files larger than 512 KB — no run,
2202
+ * no check suite, no error. Inlining every job's bash script blows past
2203
+ * that limit on bigger projects, so job scripts are materialized into
2204
+ * committed files and the workflow step only invokes them.
2205
+ *
2206
+ * Scripts containing `${{ ... }}` expressions must stay inline: the
2207
+ * runner only interpolates expressions in the workflow file itself,
2208
+ * never in files read at runtime.
2209
+ */
2210
+ class GithubScriptFiles {
2211
+ constructor() {
2212
+ this.files = new Map();
2213
+ }
2214
+ /**
2215
+ * materializes a job script into a committed file and returns the
2216
+ * step command invoking it
2217
+ */
2218
+ add(jobId, script) {
2219
+ const path = (0, path_1.join)(exports.GITHUB_SCRIPTS_FOLDER, `${jobId}.sh`);
2220
+ // `shell: bash` steps run with `bash -eo pipefail`; the script file
2221
+ // must fail exactly like the inline version did
2222
+ const content = `#!/usr/bin/env bash\nset -eo pipefail\n\n${script}\n`;
2223
+ const existing = this.files.get(path);
2224
+ if (existing !== undefined && existing !== content) {
2225
+ throw new Error(`conflicting scripts for job id '${jobId}'`);
2226
+ }
2227
+ this.files.set(path, content);
2228
+ return `bash ${path}`;
2229
+ }
2230
+ getGeneratedFiles() {
2231
+ return [...this.files.entries()].map(([path, content]) => ({
2232
+ path,
2233
+ content
2234
+ }));
2235
+ }
2236
+ }
2237
+ exports.GithubScriptFiles = GithubScriptFiles;
1482
2238
 
1483
2239
  /***/ }),
1484
2240
 
@@ -1533,6 +2289,7 @@ const promises_1 = __nccwpck_require__(1943);
1533
2289
  const rules_1 = __nccwpck_require__(4565);
1534
2290
  const config_1 = __nccwpck_require__(6925);
1535
2291
  const createAllJobs_1 = __nccwpck_require__(3671);
2292
+ const jobImagesPlan_1 = __nccwpck_require__(1585);
1536
2293
  const createGitlabJobs_1 = __nccwpck_require__(512);
1537
2294
  const createGitlabPipeline_1 = __nccwpck_require__(1862);
1538
2295
  const gitlabReleaseJobs_1 = __nccwpck_require__(5531);
@@ -1552,31 +2309,38 @@ class GitlabBackend {
1552
2309
  });
1553
2310
  }
1554
2311
  async createFiles(config) {
1555
- const includes = await this.createIncludes(config);
2312
+ const images = this.createImagesPlan(config);
2313
+ const includes = await this.createIncludes(config, images);
1556
2314
  const mainFile = {
1557
2315
  path: ".gitlab-ci.yml",
1558
2316
  content: {
1559
2317
  include: includes.map(i => i.path)
1560
2318
  }
1561
2319
  };
1562
- return [mainFile, ...includes];
2320
+ return [mainFile, ...includes,
2321
+ // materialized image definitions (repo mode)
2322
+ ...images.getGeneratedFiles()];
2323
+ }
2324
+ createImagesPlan(config) {
2325
+ return new jobImagesPlan_1.JobImagesPlan((0, jobImagesPlan_1.getJobImagesMode)(config, this.type), this.type);
1563
2326
  }
1564
2327
  /**
1565
2328
  * the complete pipeline as one object (all includes merged into one),
1566
2329
  * mainly for testing purposes
1567
2330
  */
1568
2331
  async createCompletePipeline(config) {
1569
- const includes = await this.createIncludes(config);
2332
+ const includes = await this.createIncludes(config, this.createImagesPlan(config));
1570
2333
  return includes.reduce((acc, {
1571
2334
  content
1572
2335
  }) => {
1573
2336
  return {
1574
2337
  ...acc,
2338
+ // includes are always yaml objects
1575
2339
  ...content // merge all includes into one object
1576
2340
  };
1577
2341
  }, {});
1578
2342
  }
1579
- async createIncludes(config) {
2343
+ async createIncludes(config, images) {
1580
2344
  const {
1581
2345
  jobs,
1582
2346
  image,
@@ -1584,7 +2348,7 @@ class GitlabBackend {
1584
2348
  variables,
1585
2349
  workflow,
1586
2350
  ...pipelineRest
1587
- } = await this.createPipeline(config);
2351
+ } = await this.createPipeline(config, images);
1588
2352
  // we will create 1 include per component or workspace
1589
2353
  // this is for better readability in git diffs and to avoid problems with yaml files beeing too large
1590
2354
  // group by context
@@ -1617,14 +2381,14 @@ class GitlabBackend {
1617
2381
  };
1618
2382
  return [mainInclude, ...componentIncludes];
1619
2383
  }
1620
- async createPipeline(config) {
2384
+ async createPipeline(config, images) {
1621
2385
  const stages = (0, getPipelineStages_1.getPipelineStages)(config);
1622
2386
  // for all triggers create jobs and add base rules
1623
2387
  const allJobsPerTrigger = await Promise.all(config_1.ALL_PIPELINE_TRIGGERS.map(async trigger => await (0, createGitlabJobs_1.createGitlabJobs)(await (0, createAllJobs_1.createAllJobs)({
1624
2388
  config,
1625
2389
  trigger,
1626
2390
  pipelineType: this.type
1627
- }), getGitlabRulesForTrigger(trigger)))).then(j => j.flat());
2391
+ }), images, getGitlabRulesForTrigger(trigger)))).then(j => j.flat());
1628
2392
  const allWorkspaceJobs = allJobsPerTrigger.filter(j => {
1629
2393
  var _a;
1630
2394
  return ((_a = j.context) === null || _a === void 0 ? void 0 : _a.type) === "workspace";
@@ -1653,7 +2417,9 @@ class GitlabBackend {
1653
2417
  var _a;
1654
2418
  return ((_a = j.context) === null || _a === void 0 ? void 0 : _a.type) === "agent";
1655
2419
  });
1656
- const allJobs = [...allWorkspaceJobs, ...allComponentJobs, ...allGlobalJobs].reduce((acc, {
2420
+ // context-less jobs (image build jobs)
2421
+ const allContextlessJobs = allJobsPerTrigger.filter(j => j.context === null);
2422
+ const allJobs = [...allContextlessJobs, ...allWorkspaceJobs, ...allComponentJobs, ...allGlobalJobs].reduce((acc, {
1657
2423
  gitlabJob,
1658
2424
  name,
1659
2425
  context
@@ -1767,7 +2533,8 @@ exports.GITLAB_CI_VARIABLES = {
1767
2533
  jobToken: "CI_JOB_TOKEN",
1768
2534
  jobImage: "CI_JOB_IMAGE",
1769
2535
  registry: "CI_REGISTRY",
1770
- registryImage: "CI_REGISTRY_IMAGE"
2536
+ registryImage: "CI_REGISTRY_IMAGE",
2537
+ registryUser: "CI_REGISTRY_USER"
1771
2538
  };
1772
2539
 
1773
2540
  /***/ }),
@@ -1836,7 +2603,7 @@ const getFullReferencedJobNameFromWorkspace = (referencedJobName, workspaceName,
1836
2603
  });
1837
2604
  };
1838
2605
  const getJobName = need => (0, lodash_es_1.isObject)(need) ? need.job : need;
1839
- const makeGitlabJob = (context, job, allJobs, baseRules) => {
2606
+ const makeGitlabJob = (context, job, allJobs, images, baseRules) => {
1840
2607
  var _a, _b;
1841
2608
  const {
1842
2609
  environment,
@@ -1854,8 +2621,14 @@ const makeGitlabJob = (context, job, allJobs, baseRules) => {
1854
2621
  allow_failure: jobAllowFailure,
1855
2622
  cache: jobCache,
1856
2623
  caches,
2624
+ image: jobImage,
1857
2625
  ...rest
1858
2626
  } = job;
2627
+ // catladder image references resolve according to the jobImages mode;
2628
+ // in repo mode the job additionally depends on the image build job
2629
+ const resolvedImage = images.resolve(jobImage);
2630
+ const image = resolvedImage.image;
2631
+ const imageNeeds = resolvedImage.need ? [resolvedImage.need] : [];
1859
2632
  // the neutral `gate` translates to gitlab's when/allow_failure;
1860
2633
  // explicitly set gitlab fields take precedence
1861
2634
  const when = jobWhen !== null && jobWhen !== void 0 ? jobWhen : gate ? gate === "manual" ? "manual" : "on_success" : undefined;
@@ -1864,7 +2637,7 @@ const makeGitlabJob = (context, job, allJobs, baseRules) => {
1864
2637
  // an explicitly set gitlab `cache` takes precedence
1865
2638
  const cache = jobCache !== null && jobCache !== void 0 ? jobCache : caches && context.type !== "agent" ? (0, createJobCache_1.createJobCacheFromCacheConfigs)(context, caches) : undefined;
1866
2639
  const stage = envMode === "stagePerEnv" && context.type !== "agent" ? `${job.stage} ${context.env}` : job.stage;
1867
- const deduplicatedGitlabNeeds = getGitlabNeeds(context, job, allJobs);
2640
+ const deduplicatedGitlabNeeds = getGitlabNeeds(context, job, allJobs, imageNeeds);
1868
2641
  const fullJobName = getFullJobName({
1869
2642
  type: context.type,
1870
2643
  name,
@@ -1893,6 +2666,7 @@ const makeGitlabJob = (context, job, allJobs, baseRules) => {
1893
2666
  retry: defaults_1.BASE_RETRY,
1894
2667
  interruptible: true,
1895
2668
  ...rest,
2669
+ image,
1896
2670
  allow_failure,
1897
2671
  cache: cache ? (0, cache_1.addCacheFallback)(cache, context) : undefined,
1898
2672
  rules: rules.length > 0 ? rules : undefined,
@@ -1968,13 +2742,13 @@ const addGitlabEnvironment = (context, catladderJobEnvironment, job, allJobs) =>
1968
2742
  script: [...((_b = job.script) !== null && _b !== void 0 ? _b : []), ...(createsJobEnv ? scriptToAdd : [])]
1969
2743
  };
1970
2744
  };
1971
- const createGitlabJobs = async (allJobs, baseRules) => {
1972
- return [...allJobs.workspaces, ...allJobs.components, ...allJobs.agents].flatMap(({
2745
+ const createGitlabJobs = async (allJobs, images, baseRules) => {
2746
+ const contextualJobs = [...allJobs.workspaces, ...allJobs.components, ...allJobs.agents].flatMap(({
1973
2747
  context,
1974
2748
  jobs
1975
2749
  }) => {
1976
2750
  return jobs.map(job => {
1977
- const [fullJobName, gitlabJob] = (0, exports.makeGitlabJob)(context, job, allJobs, baseRules);
2751
+ const [fullJobName, gitlabJob] = (0, exports.makeGitlabJob)(context, job, allJobs, images, baseRules);
1978
2752
  return {
1979
2753
  name: fullJobName,
1980
2754
  gitlabJob,
@@ -1982,13 +2756,66 @@ const createGitlabJobs = async (allJobs, baseRules) => {
1982
2756
  };
1983
2757
  });
1984
2758
  });
2759
+ // image build jobs (repo mode): no context, plain names, their
2760
+ // rules:changes combined with the trigger rules
2761
+ const imageJobs = images.getEnsureJobs().map(job => ({
2762
+ name: job.name,
2763
+ gitlabJob: makeEnsureImageGitlabJob(job, baseRules),
2764
+ context: null
2765
+ }));
2766
+ return [...imageJobs, ...contextualJobs];
1985
2767
  };
1986
2768
  exports.createGitlabJobs = createGitlabJobs;
1987
- function getGitlabNeeds(context, job, allJobs) {
2769
+ /**
2770
+ * lowers an image build job: only runs when the (generated) image
2771
+ * definition changed, within the trigger's base rules
2772
+ */
2773
+ const makeEnsureImageGitlabJob = (job, baseRules) => {
2774
+ var _a, _b, _c, _d;
2775
+ const changes = (_b = (_a = job.rules) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.changes;
2776
+ const rules = (baseRules !== null && baseRules !== void 0 ? baseRules : []).map(baseRule => baseRule.when === "never" ? baseRule : {
2777
+ ...baseRule,
2778
+ changes
2779
+ });
2780
+ return (0, removeUndefined_1.removeUndefined)({
2781
+ retry: defaults_1.BASE_RETRY,
2782
+ interruptible: true,
2783
+ image: job.image,
2784
+ services: job.services,
2785
+ rules: rules.length > 0 ? rules : [{
2786
+ changes
2787
+ }],
2788
+ variables: job.runnerVariables,
2789
+ script: (_d = (_c = job.script) === null || _c === void 0 ? void 0 : _c.filter(utils_1.notNil)) !== null && _d !== void 0 ? _d : [],
2790
+ stage: job.stage,
2791
+ needs: []
2792
+ });
2793
+ };
2794
+ function getGitlabNeeds(context, job, allJobs, extraNeeds = []) {
1988
2795
  var _a;
1989
- const needs = context.type === "workspace" ? getGitlabNeedsForWorkspaceJob(context, job, allJobs) : context.type === "agent" ? (_a = job.needs) !== null && _a !== void 0 ? _a : null : getGitlabNeedsForComponentJob(context, job, allJobs);
2796
+ const jobWithExtraNeeds = extraNeeds.length > 0 ? {
2797
+ ...job,
2798
+ needs: [...extraNeeds, ...((_a = job.needs) !== null && _a !== void 0 ? _a : [])]
2799
+ } : job;
2800
+ const needs = context.type === "workspace" ? getGitlabNeedsForWorkspaceJob(context, jobWithExtraNeeds, allJobs) : context.type === "agent" ? mapAgentNeeds(jobWithExtraNeeds.needs) : getGitlabNeedsForComponentJob(context, jobWithExtraNeeds, allJobs);
1990
2801
  return needs ? deduplicateNeeds(needs) : undefined;
1991
2802
  }
2803
+ /**
2804
+ * agent jobs use their needs as-is; global needs (image build jobs)
2805
+ * keep their plain name and optional flag
2806
+ */
2807
+ function mapAgentNeeds(needs) {
2808
+ if (!needs) {
2809
+ return null;
2810
+ }
2811
+ return needs.map(n => (0, lodash_es_1.isObject)(n) && "global" in n && n.global ? {
2812
+ job: n.job,
2813
+ artifacts: n.artifacts,
2814
+ ...(n.optional ? {
2815
+ optional: true
2816
+ } : {})
2817
+ } : n);
2818
+ }
1992
2819
  function deduplicateNeeds(needs) {
1993
2820
  return needs ? [...new Map(needs.map(n => [(0, lodash_es_1.isObject)(n) ? n.job : n, n])).values()] : undefined;
1994
2821
  }
@@ -1997,11 +2824,19 @@ function getGitlabNeedsForComponentJob(context, {
1997
2824
  }, allJobs) {
1998
2825
  return (needs !== null && needs !== void 0 ? needs : []).map(n => {
1999
2826
  var _a;
2000
- return (0, lodash_es_1.isObject)(n) ? "workspaceName" in n ? {
2827
+ return (0, lodash_es_1.isObject)(n) ? "global" in n && n.global ?
2828
+ // global jobs (e.g. image builds) keep their plain name
2829
+ {
2830
+ job: n.job,
2831
+ artifacts: n.artifacts,
2832
+ ...(n.optional ? {
2833
+ optional: true
2834
+ } : {})
2835
+ } : "workspaceName" in n && n.workspaceName ? {
2001
2836
  job: getFullReferencedJobNameFromWorkspace(n.job, n.workspaceName, context.env, allJobs),
2002
2837
  artifacts: n.artifacts
2003
2838
  } : {
2004
- job: getFullReferencedJobNameFromComponent(n.job, (_a = n.componentName) !== null && _a !== void 0 ? _a : context.name, context.env, allJobs),
2839
+ job: getFullReferencedJobNameFromComponent(n.job, "componentName" in n ? (_a = n.componentName) !== null && _a !== void 0 ? _a : context.name : context.name, context.env, allJobs),
2005
2840
  artifacts: n.artifacts
2006
2841
  } : getFullReferencedJobNameFromComponent(n, context.name, context.env, allJobs);
2007
2842
  }) // sort in a predictable manner for snapshot tests
@@ -2014,7 +2849,13 @@ function getGitlabNeedsForComponentJob(context, {
2014
2849
  function getGitlabNeedsForWorkspaceJob(context, {
2015
2850
  needs
2016
2851
  }, allJobs) {
2017
- return (needs !== null && needs !== void 0 ? needs : []).map(n => (0, lodash_es_1.isObject)(n) ? {
2852
+ return (needs !== null && needs !== void 0 ? needs : []).map(n => (0, lodash_es_1.isObject)(n) ? "global" in n && n.global ? {
2853
+ job: n.job,
2854
+ artifacts: n.artifacts,
2855
+ ...(n.optional ? {
2856
+ optional: true
2857
+ } : {})
2858
+ } : {
2018
2859
  job: getFullReferencedJobNameFromWorkspace(n.job, "workspaceName" in n && n.workspaceName ? n.workspaceName : context.name, context.env, allJobs),
2019
2860
  artifacts: n.artifacts
2020
2861
  } : getFullReferencedJobNameFromWorkspace(n, context.name, context.env, allJobs)) // sort in a predictable manner for snapshot tests
@@ -2043,7 +2884,7 @@ const createGitlabPipelineWithDefaults = ({
2043
2884
  ...config
2044
2885
  }) => {
2045
2886
  return {
2046
- image: image !== null && image !== void 0 ? image : (0, runner_1.getRunnerImage)("jobs-default"),
2887
+ image: image !== null && image !== void 0 ? image : (0, runner_1.getCentralRunnerImageUrl)("jobs-default"),
2047
2888
  // default image
2048
2889
  variables: {
2049
2890
  FF_USE_FASTZIP: "true",
@@ -2146,7 +2987,7 @@ const getGitlabReleaseJobs = config => {
2146
2987
  return {
2147
2988
  ["create release"]: {
2148
2989
  stage: "release",
2149
- image: (0, runner_1.getRunnerImage)("semantic-release"),
2990
+ image: (0, runner_1.getCentralRunnerImageUrl)("semantic-release"),
2150
2991
  script: ["semanticRelease"],
2151
2992
  after_script: [EXPIRED_TOKEN_HELP],
2152
2993
  rules: [...baseReleaseRules, {
@@ -2159,7 +3000,7 @@ const getGitlabReleaseJobs = config => {
2159
3000
  },
2160
3001
  ["⚠️ force create release"]: {
2161
3002
  stage: "release",
2162
- image: (0, runner_1.getRunnerImage)("semantic-release"),
3003
+ image: (0, runner_1.getCentralRunnerImageUrl)("semantic-release"),
2163
3004
  script: ["semanticRelease"],
2164
3005
  after_script: [EXPIRED_TOKEN_HELP],
2165
3006
  needs: [],
@@ -2265,7 +3106,7 @@ var __exportStar = this && this.__exportStar || function (m, exports) {
2265
3106
  Object.defineProperty(exports, "__esModule", ({
2266
3107
  value: true
2267
3108
  }));
2268
- exports.getEnabledPipelineTypes = exports.getPipelineBackend = void 0;
3109
+ exports.getEnabledPipelineTypes = exports.getVaultConfig = exports.getPipelineGitRemote = exports.getPipelineOptions = exports.getPipelineBackend = void 0;
2269
3110
  const github_1 = __nccwpck_require__(9635);
2270
3111
  const gitlab_1 = __nccwpck_require__(8475);
2271
3112
  __exportStar(__nccwpck_require__(2474), exports);
@@ -2283,6 +3124,38 @@ const getPipelineBackend = type => {
2283
3124
  return createBackend();
2284
3125
  };
2285
3126
  exports.getPipelineBackend = getPipelineBackend;
3127
+ /**
3128
+ * the options of an enabled pipeline type (empty object when enabled
3129
+ * with `true`)
3130
+ */
3131
+ const getPipelineOptions = (config, type) => {
3132
+ var _a;
3133
+ const value = (_a = config.pipelines) === null || _a === void 0 ? void 0 : _a[type];
3134
+ return typeof value === "object" ? value : {};
3135
+ };
3136
+ exports.getPipelineOptions = getPipelineOptions;
3137
+ /**
3138
+ * the git remote pointing to the repository of this pipeline type's CI
3139
+ * system (e.g. during a migration, gitlab may be `origin` and github a
3140
+ * second remote)
3141
+ */
3142
+ const getPipelineGitRemote = (config, type) => {
3143
+ var _a;
3144
+ return (_a = (0, exports.getPipelineOptions)(config, type).gitRemote) !== null && _a !== void 0 ? _a : "origin";
3145
+ };
3146
+ exports.getPipelineGitRemote = getPipelineGitRemote;
3147
+ /**
3148
+ * the normalized secrets vault of a config (string shorthands resolved
3149
+ * to their object form; defaults to the legacy gitlab store)
3150
+ */
3151
+ const getVaultConfig = config => {
3152
+ var _a, _b;
3153
+ const vault = (_b = (_a = config.secrets) === null || _a === void 0 ? void 0 : _a.vault) !== null && _b !== void 0 ? _b : "gitlab";
3154
+ return typeof vault === "string" ? {
3155
+ type: vault
3156
+ } : vault;
3157
+ };
3158
+ exports.getVaultConfig = getVaultConfig;
2286
3159
  /**
2287
3160
  * the pipeline types enabled for this config. Multiple types can be
2288
3161
  * enabled at once (parallel generation, e.g. during a CI migration).
@@ -3870,7 +4743,10 @@ const getYarnInstallCommand = async context => {
3870
4743
  // inline builds make debugging easier as it prints it out in the logs, instead of writing it in temp files
3871
4744
  return `yarn install --immutable --inline-builds`;
3872
4745
  };
3873
- const ensureNodeVersion = context => (0, gitlab_1.collapseableSection)("nodeinstall", "Ensure node version")(["if [ -f ~/.nvm/nvm.sh ]; then source ~/.nvm/nvm.sh; fi", "if command -v nvm &> /dev/null && [ -f ./.nvmrc ]; then nvm install; fi"]);
4746
+ const ensureNodeVersion = context => (0, gitlab_1.collapseableSection)("nodeinstall", "Ensure node version")([
4747
+ // github actions overrides HOME in container jobs, so also look
4748
+ // for nvm where the catladder images install it (/root/.nvm)
4749
+ 'if [ -f "$HOME/.nvm/nvm.sh" ]; then source "$HOME/.nvm/nvm.sh"; elif [ -f /root/.nvm/nvm.sh ]; then export NVM_DIR=/root/.nvm; source /root/.nvm/nvm.sh; fi', "if command -v nvm &> /dev/null && [ -f ./.nvmrc ]; then nvm install; fi"]);
3874
4750
  exports.ensureNodeVersion = ensureNodeVersion;
3875
4751
  const getYarnInstall = async (context, options) => {
3876
4752
  const postInstall = context.type !== "workspace" && context.build.type !== "disabled" && "postInstall" in context.build.config ? context.build.config.postInstall : null;
@@ -4249,7 +5125,7 @@ Object.defineProperty(exports, "__esModule", ({
4249
5125
  value: true
4250
5126
  }));
4251
5127
  exports.DOCKER_REGISTRY = exports.PIPELINE_IMAGE_TAG = void 0;
4252
- exports.PIPELINE_IMAGE_TAG = "next-4e7c6848" || 0;
5128
+ exports.PIPELINE_IMAGE_TAG = "next-6a4450e0" || 0;
4253
5129
  exports.DOCKER_REGISTRY = "git.panter.ch:5001/catladder/catladder" || 0;
4254
5130
 
4255
5131
  /***/ }),
@@ -4661,7 +5537,7 @@ const getBasePredefinedVariables = ctx => {
4661
5537
  };
4662
5538
  };
4663
5539
  const getEnvironmentVariables = async (ctx, options = {}) => {
4664
- var _a, _b, _c, _d, _e, _f, _g, _h;
5540
+ var _a, _b, _c, _d, _e, _f, _g;
4665
5541
  const environmentContext = (0, getEnvironmentContext_1.getEnvironmentContext)(ctx);
4666
5542
  const {
4667
5543
  config,
@@ -4718,13 +5594,13 @@ const getEnvironmentVariables = async (ctx, options = {}) => {
4718
5594
  }
4719
5595
  const publicEnvVarsRaw = (_e = (_d = envConfigRaw.vars) === null || _d === void 0 ? void 0 : _d.public) !== null && _e !== void 0 ? _e : {};
4720
5596
  const additionalSecretKeys = deployConfigRaw ? deploy_1.DEPLOY_TYPES[deployConfigRaw.type].additionalSecretKeys(environmentContext) : [];
4721
- const secretEnvVarKeys = [...(0, envVars_1.stringListToSecreteEnvVarList)((_g = (_f = envConfigRaw.vars) === null || _f === void 0 ? void 0 : _f.secret) !== null && _g !== void 0 ? _g : []), ...additionalSecretKeys];
5597
+ const secretEnvVarKeys = [...(0, envVars_1.normalizeSecretVarsConfig)((_f = envConfigRaw.vars) === null || _f === void 0 ? void 0 : _f.secret), ...additionalSecretKeys];
4722
5598
  const secretEnvVars = (0, envVars_1.makeSecretEnvVarMapping)(env, componentName, secretEnvVarKeys);
4723
5599
  const publicEnvVarsRawSanitized = (0, envVars_1.stringifyValues)(publicEnvVarsRaw);
4724
5600
  const publicEnvVarsUnresolved = Object.fromEntries(Object.entries(publicEnvVarsRawSanitized).map(([key, value]) => [key, (0, VariableValueContainingReferences_1.createVariableValueContainingReferencesFromString)(value, {
4725
5601
  componentName: ctx.componentName
4726
5602
  })]));
4727
- const publicEnvVars = ((_h = options.shouldResolveReferences) !== null && _h !== void 0 ? _h : true) ? await (0, resolveAllReferences_1.resolveAllReferences)(publicEnvVarsUnresolved, async otherComponentName => {
5603
+ const publicEnvVars = ((_g = options.shouldResolveReferences) !== null && _g !== void 0 ? _g : true) ? await (0, resolveAllReferences_1.resolveAllReferences)(publicEnvVarsUnresolved, async otherComponentName => {
4728
5604
  const {
4729
5605
  envVars: otherEnvVars
4730
5606
  } = await (0, exports.getEnvironmentVariables)({
@@ -4884,7 +5760,7 @@ const envVars_1 = __nccwpck_require__(4946);
4884
5760
  * @returns
4885
5761
  */
4886
5762
  const transformJobOnlyVars = async (env, componentName, vars) => {
4887
- var _a, _b;
5763
+ var _a;
4888
5764
  if (!vars) {
4889
5765
  return {
4890
5766
  envVars: {},
@@ -4892,7 +5768,7 @@ const transformJobOnlyVars = async (env, componentName, vars) => {
4892
5768
  };
4893
5769
  }
4894
5770
  const publicVars = (0, envVars_1.stringifyValues)((_a = vars === null || vars === void 0 ? void 0 : vars.public) !== null && _a !== void 0 ? _a : {});
4895
- const secretEnvVarKeys = (0, envVars_1.stringListToSecreteEnvVarList)((_b = vars === null || vars === void 0 ? void 0 : vars.secret) !== null && _b !== void 0 ? _b : []);
5771
+ const secretEnvVarKeys = (0, envVars_1.normalizeSecretVarsConfig)(vars === null || vars === void 0 ? void 0 : vars.secret);
4896
5772
  const deployJobOnlySecretEnvVars = (vars === null || vars === void 0 ? void 0 : vars.secret) ? (0, envVars_1.makeSecretEnvVarMapping)(env, componentName, secretEnvVarKeys) : {};
4897
5773
  const envVars = {
4898
5774
  ...deployJobOnlySecretEnvVars,
@@ -4916,7 +5792,7 @@ exports.transformJobOnlyVars = transformJobOnlyVars;
4916
5792
  Object.defineProperty(exports, "__esModule", ({
4917
5793
  value: true
4918
5794
  }));
4919
- exports.makeSecretEnvVarMapping = exports.stringListToSecreteEnvVarList = exports.stringifyValues = void 0;
5795
+ exports.makeSecretEnvVarMapping = exports.normalizeSecretVarsConfig = exports.stringListToSecreteEnvVarList = exports.stringifyValues = void 0;
4920
5796
  const lodash_es_1 = __nccwpck_require__(4284);
4921
5797
  const getEnvironmentVariables_1 = __nccwpck_require__(5855);
4922
5798
  const BashExpression_1 = __nccwpck_require__(5354);
@@ -4926,6 +5802,27 @@ const stringListToSecreteEnvVarList = keys => keys.map(key => ({
4926
5802
  key
4927
5803
  }));
4928
5804
  exports.stringListToSecreteEnvVarList = stringListToSecreteEnvVarList;
5805
+ /**
5806
+ * normalizes both config forms of vault-stored env vars: the deprecated
5807
+ * plain string[] (all keys secret) and the record form carrying a kind
5808
+ * (or config object) per key
5809
+ */
5810
+ const normalizeSecretVarsConfig = secret => {
5811
+ if (!secret) {
5812
+ return [];
5813
+ }
5814
+ if (Array.isArray(secret)) {
5815
+ return (0, exports.stringListToSecreteEnvVarList)(secret);
5816
+ }
5817
+ return Object.entries(secret).map(([key, config]) => typeof config === "string" ? {
5818
+ key,
5819
+ kind: config
5820
+ } : {
5821
+ key,
5822
+ ...config
5823
+ });
5824
+ };
5825
+ exports.normalizeSecretVarsConfig = normalizeSecretVarsConfig;
4929
5826
  const makeSecretEnvVarMapping = (env, componentName, secretEnvVars) => {
4930
5827
  return Object.fromEntries(secretEnvVars.map(({
4931
5828
  key
@@ -4935,6 +5832,330 @@ exports.makeSecretEnvVarMapping = makeSecretEnvVarMapping;
4935
5832
 
4936
5833
  /***/ }),
4937
5834
 
5835
+ /***/ 3231:
5836
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
5837
+
5838
+ "use strict";
5839
+
5840
+
5841
+ Object.defineProperty(exports, "__esModule", ({
5842
+ value: true
5843
+ }));
5844
+ exports.computeCustomImageHash = void 0;
5845
+ const crypto_1 = __nccwpck_require__(6982);
5846
+ const fs_1 = __nccwpck_require__(9896);
5847
+ const path_1 = __nccwpck_require__(6928);
5848
+ const IGNORED_FILES = new Set([".DS_Store", "Thumbs.db"]);
5849
+ const isIgnoredFile = path => {
5850
+ var _a;
5851
+ return IGNORED_FILES.has((_a = path.split("/").pop()) !== null && _a !== void 0 ? _a : "");
5852
+ };
5853
+ function listFilesRecursive(dir) {
5854
+ const resolved = (0, path_1.resolve)(dir);
5855
+ const entries = (0, fs_1.readdirSync)(resolved, {
5856
+ recursive: true,
5857
+ encoding: "utf-8"
5858
+ });
5859
+ return entries.filter(entry => {
5860
+ if (isIgnoredFile(entry)) {
5861
+ // OS junk files exist locally but not in CI checkouts — they
5862
+ // must never influence the content hash
5863
+ return false;
5864
+ }
5865
+ const fullPath = (0, path_1.join)(resolved, entry);
5866
+ return (0, fs_1.statSync)(fullPath).isFile();
5867
+ }).sort();
5868
+ }
5869
+ function computeCustomImageHash(config) {
5870
+ var _a;
5871
+ const hash = (0, crypto_1.createHash)("sha256");
5872
+ const watchedPaths = [];
5873
+ // Hash all files in the dir (includes the Dockerfile and any context files)
5874
+ const dirFiles = listFilesRecursive(config.dir);
5875
+ for (const file of dirFiles) {
5876
+ const fullPath = (0, path_1.join)((0, path_1.resolve)(config.dir), file);
5877
+ hash.update(file); // include relative path for disambiguation
5878
+ hash.update((0, fs_1.readFileSync)(fullPath));
5879
+ }
5880
+ watchedPaths.push(`${config.dir}/**/*`);
5881
+ // extra dirs (e.g. included base image definitions)
5882
+ for (const extraDir of (_a = config.extraDirs) !== null && _a !== void 0 ? _a : []) {
5883
+ for (const file of listFilesRecursive(extraDir)) {
5884
+ hash.update(file);
5885
+ hash.update((0, fs_1.readFileSync)((0, path_1.join)((0, path_1.resolve)(extraDir), file)));
5886
+ }
5887
+ watchedPaths.push(`${extraDir}/**/*`);
5888
+ }
5889
+ // Hash extra paths (for files outside the dir)
5890
+ if (config.hashExtraPaths) {
5891
+ // Extra paths are literal file paths (not globs) for simplicity
5892
+ const extraFiles = [...config.hashExtraPaths].sort();
5893
+ for (const file of extraFiles) {
5894
+ hash.update(file);
5895
+ hash.update((0, fs_1.readFileSync)(file));
5896
+ }
5897
+ watchedPaths.push(...config.hashExtraPaths);
5898
+ }
5899
+ // Hash build args (sorted keys for determinism)
5900
+ if (config.buildArgs) {
5901
+ const sorted = Object.entries(config.buildArgs).sort(([a], [b]) => a.localeCompare(b));
5902
+ hash.update(JSON.stringify(sorted));
5903
+ }
5904
+ return {
5905
+ hash: hash.digest("hex").slice(0, 12),
5906
+ watchedPaths
5907
+ };
5908
+ }
5909
+ exports.computeCustomImageHash = computeCustomImageHash;
5910
+
5911
+ /***/ }),
5912
+
5913
+ /***/ 1585:
5914
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
5915
+
5916
+ "use strict";
5917
+
5918
+
5919
+ Object.defineProperty(exports, "__esModule", ({
5920
+ value: true
5921
+ }));
5922
+ exports.JobImagesPlan = exports.getJobImagesMode = exports.GENERATED_IMAGES_FOLDER = void 0;
5923
+ const fs_1 = __nccwpck_require__(9896);
5924
+ const path_1 = __nccwpck_require__(6928);
5925
+ const ciVariables_1 = __nccwpck_require__(4489);
5926
+ const docker_1 = __nccwpck_require__(2764);
5927
+ const constants_1 = __nccwpck_require__(3480);
5928
+ const runner_1 = __nccwpck_require__(2348);
5929
+ const gitlab_1 = __nccwpck_require__(3468);
5930
+ const hash_1 = __nccwpck_require__(3231);
5931
+ const shippedImages_1 = __nccwpck_require__(7734);
5932
+ exports.GENERATED_IMAGES_FOLDER = ".catladder-generated/images";
5933
+ /**
5934
+ * the official docker cli image used to build the job images — NOT a
5935
+ * catladder image, so building images never depends on one (bootstrap)
5936
+ */
5937
+ const BUILDER_IMAGE = "docker.io/docker:29.5.1";
5938
+ const getJobImagesMode = (config, pipelineType) => {
5939
+ var _a;
5940
+ // the central registry is not reachable from github runners
5941
+ if (pipelineType === "github" && config.jobImages === "central") {
5942
+ throw new Error(`jobImages: "central" is not supported on github: catladder's central registry (${constants_1.DOCKER_REGISTRY}) is unreachable from github runners. Use "repo" (the default) instead.`);
5943
+ }
5944
+ return (_a = config.jobImages) !== null && _a !== void 0 ? _a : pipelineType === "github" ? "repo" : "central";
5945
+ };
5946
+ exports.getJobImagesMode = getJobImagesMode;
5947
+ const ensureJobName = name => `🐳 job image ${name}`;
5948
+ /**
5949
+ * plans the catladder job images of one backend run: resolves image
5950
+ * references, and (in repo mode) collects the image build jobs and the
5951
+ * image definitions to materialize into the generated files.
5952
+ *
5953
+ * Reference-driven: only images actually used by a job are built.
5954
+ */
5955
+ class JobImagesPlan {
5956
+ constructor(mode, pipelineType) {
5957
+ this.mode = mode;
5958
+ this.pipelineType = pipelineType;
5959
+ this.used = new Map();
5960
+ }
5961
+ /**
5962
+ * resolves a job image (marker or concrete) to a concrete image url
5963
+ * plus the build job dependency in repo mode
5964
+ */
5965
+ resolve(image) {
5966
+ if (!(0, runner_1.isCatladderImageRef)(image)) {
5967
+ return {
5968
+ image
5969
+ };
5970
+ }
5971
+ return this.resolveRef(image);
5972
+ }
5973
+ resolveRef(ref) {
5974
+ const name = ref.catladderImage;
5975
+ if (this.mode === "central" || shippedImages_1.CENTRAL_ONLY_IMAGES.has(name)) {
5976
+ return {
5977
+ image: (0, runner_1.getCentralRunnerImageUrl)(name)
5978
+ };
5979
+ }
5980
+ const {
5981
+ hash
5982
+ } = this.use(name);
5983
+ return {
5984
+ image: `${this.registryImageRef()}/catladder/${name}:${hash}`,
5985
+ fromRepoRegistry: true,
5986
+ need: {
5987
+ job: ensureJobName(name),
5988
+ artifacts: false,
5989
+ optional: true,
5990
+ global: true
5991
+ }
5992
+ };
5993
+ }
5994
+ /**
5995
+ * the registry image prefix. On github, container images can't use
5996
+ * runtime env vars — the expression form is required.
5997
+ */
5998
+ registryImageRef() {
5999
+ return this.pipelineType === "github" ? "ghcr.io/${{ github.repository }}" : `${(0, ciVariables_1.getCiVariable)({
6000
+ pipelineType: this.pipelineType
6001
+ }, "registryImage")}`;
6002
+ }
6003
+ use(name) {
6004
+ var _a;
6005
+ const existing = this.used.get(name);
6006
+ if (existing) {
6007
+ return existing;
6008
+ }
6009
+ const dependencies = (_a = shippedImages_1.RUNNER_IMAGE_DEPENDENCIES[name]) !== null && _a !== void 0 ? _a : [];
6010
+ // dependencies are materialized (and hashed) as part of this image,
6011
+ // but also usable standalone
6012
+ dependencies.forEach(dependency => this.use(dependency));
6013
+ const {
6014
+ hash
6015
+ } = (0, hash_1.computeCustomImageHash)({
6016
+ dir: (0, shippedImages_1.getShippedImageDir)(name),
6017
+ extraDirs: dependencies.map(dependency => (0, shippedImages_1.getShippedImageDir)(dependency))
6018
+ });
6019
+ const entry = {
6020
+ hash
6021
+ };
6022
+ this.used.set(name, entry);
6023
+ return entry;
6024
+ }
6025
+ /**
6026
+ * the image build jobs for all used images (repo mode; empty in
6027
+ * central mode)
6028
+ */
6029
+ getEnsureJobs() {
6030
+ return [...this.used.entries()].map(([name, {
6031
+ hash
6032
+ }]) => this.createEnsureJob(name, hash));
6033
+ }
6034
+ createEnsureJob(name, hash) {
6035
+ var _a;
6036
+ const ctx = {
6037
+ pipelineType: this.pipelineType
6038
+ };
6039
+ const registryUser = (0, ciVariables_1.getCiVariable)(ctx, "registryUser");
6040
+ const jobToken = (0, ciVariables_1.getCiVariable)(ctx, "jobToken");
6041
+ const registry = (0, ciVariables_1.getCiVariable)(ctx, "registry");
6042
+ const imageRef = `${this.registryImageRef()}/catladder/${name}:${hash}`;
6043
+ const watchedDirs = [name, ...((_a = shippedImages_1.RUNNER_IMAGE_DEPENDENCIES[name]) !== null && _a !== void 0 ? _a : [])];
6044
+ const {
6045
+ services,
6046
+ runnerVariables
6047
+ } = (0, docker_1.getDockerJobBaseProps)();
6048
+ return {
6049
+ name: ensureJobName(name),
6050
+ stage: "setup",
6051
+ image: BUILDER_IMAGE,
6052
+ services,
6053
+ runnerVariables,
6054
+ needs: [],
6055
+ // gitlab: only run when the image definition changed; github has
6056
+ // no equivalent and always runs (the existence check is fast)
6057
+ rules: [{
6058
+ changes: watchedDirs.map(dir => `${exports.GENERATED_IMAGES_FOLDER}/${dir}/**/*`)
6059
+ }],
6060
+ variables: {},
6061
+ script: [...(0, gitlab_1.collapseableSection)("docker-login", "Docker Login")([`docker login --username ${registryUser} --password ${jobToken} ${registry}`]), ...(0, gitlab_1.collapseableSection)("check-image", "Checking if image exists")([`if docker manifest inspect ${imageRef} > /dev/null 2>&1; then`, ` echo "Image ${imageRef} already exists, skipping build"`, ` exit 0`, `fi`]), ...(0, gitlab_1.collapseableSection)("docker-build", "Building image")([`DOCKER_BUILDKIT=1 docker build -t ${imageRef} -f ${exports.GENERATED_IMAGES_FOLDER}/${name}/Dockerfile ${shippedImages_1.RUNNER_IMAGE_BUILD_CONTEXT[name] === "root" ? exports.GENERATED_IMAGES_FOLDER : `${exports.GENERATED_IMAGES_FOLDER}/${name}`}`]), ...(0, gitlab_1.collapseableSection)("docker-push", "Pushing image")([`docker push ${imageRef}`])]
6062
+ };
6063
+ }
6064
+ /**
6065
+ * the image definitions of all used images, materialized into the
6066
+ * generated files (so pipelines build from committed, reviewable
6067
+ * files and gitlab's rules:changes can watch them)
6068
+ */
6069
+ getGeneratedFiles() {
6070
+ return [...this.used.keys()].flatMap(name => {
6071
+ const dir = (0, shippedImages_1.getShippedImageDir)(name);
6072
+ return listFilesRecursive(dir).map(file => ({
6073
+ path: (0, path_1.join)(exports.GENERATED_IMAGES_FOLDER, name, file),
6074
+ content: (0, fs_1.readFileSync)((0, path_1.join)(dir, file), "utf-8")
6075
+ }));
6076
+ });
6077
+ }
6078
+ }
6079
+ exports.JobImagesPlan = JobImagesPlan;
6080
+ const IGNORED_FILES = new Set([".DS_Store", "Thumbs.db"]);
6081
+ const listFilesRecursive = dir => (0, fs_1.readdirSync)(dir, {
6082
+ recursive: true,
6083
+ encoding: "utf-8"
6084
+ }).filter(entry => {
6085
+ var _a;
6086
+ // OS junk files exist locally but not in CI checkouts
6087
+ return !IGNORED_FILES.has((_a = entry.split("/").pop()) !== null && _a !== void 0 ? _a : "") && (0, fs_1.statSync)((0, path_1.join)(dir, entry)).isFile();
6088
+ }).sort();
6089
+
6090
+ /***/ }),
6091
+
6092
+ /***/ 7734:
6093
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
6094
+
6095
+ "use strict";
6096
+
6097
+
6098
+ Object.defineProperty(exports, "__esModule", ({
6099
+ value: true
6100
+ }));
6101
+ exports.getShippedImageDir = exports.getShippedImagesDir = exports.RUNNER_IMAGE_BUILD_CONTEXT = exports.CENTRAL_ONLY_IMAGES = exports.RUNNER_IMAGE_DEPENDENCIES = void 0;
6102
+ const fs_1 = __nccwpck_require__(9896);
6103
+ const path_1 = __nccwpck_require__(6928);
6104
+ /**
6105
+ * images whose Dockerfile INCLUDEs another image's Dockerfile
6106
+ * (dockerfile-x/dockerfile-plus, resolved against the images root as
6107
+ * build context)
6108
+ */
6109
+ exports.RUNNER_IMAGE_DEPENDENCIES = {
6110
+ "jobs-meteor": ["jobs-default"],
6111
+ "jobs-testing-chrome": ["jobs-default"]
6112
+ };
6113
+ /**
6114
+ * the docker build context of each image, mirroring the central image
6115
+ * builds: the jobs family builds from the images root (their
6116
+ * Dockerfiles INCLUDE sibling images), the others from their own dir
6117
+ * (their COPY paths are relative to it)
6118
+ */
6119
+ /**
6120
+ * images that cannot be built from the shipped definitions and always
6121
+ * come from the central registry: semantic-release bakes catladder's
6122
+ * own cli sources into the image (repo-root build context).
6123
+ * TODO: make the semantic-release image self-contained.
6124
+ */
6125
+ exports.CENTRAL_ONLY_IMAGES = new Set(["semantic-release"]);
6126
+ exports.RUNNER_IMAGE_BUILD_CONTEXT = {
6127
+ "jobs-default": "root",
6128
+ "jobs-meteor": "root",
6129
+ "jobs-testing-chrome": "root",
6130
+ kubernetes: "self",
6131
+ "docker-build": "self",
6132
+ gcloud: "self",
6133
+ "semantic-release": "self",
6134
+ "agent-claude": "self"
6135
+ };
6136
+ /**
6137
+ * the directory containing the image definitions shipped with the
6138
+ * package (copied into dist at build time; the repository layout is
6139
+ * the fallback for development and tests)
6140
+ */
6141
+ const getShippedImagesDir = () => {
6142
+ const candidates = [
6143
+ // built package: dist/customImages -> dist/runner-images
6144
+ (0, path_1.join)(__dirname, "..", "runner-images"),
6145
+ // repository: pipeline/src/customImages -> <root>/runner-images
6146
+ (0, path_1.join)(__dirname, "..", "..", "..", "runner-images")];
6147
+ const found = candidates.find(candidate => (0, fs_1.existsSync)(candidate));
6148
+ if (!found) {
6149
+ throw new Error("shipped job image definitions not found");
6150
+ }
6151
+ return found;
6152
+ };
6153
+ exports.getShippedImagesDir = getShippedImagesDir;
6154
+ const getShippedImageDir = name => (0, path_1.join)((0, exports.getShippedImagesDir)(), name);
6155
+ exports.getShippedImageDir = getShippedImageDir;
6156
+
6157
+ /***/ }),
6158
+
4938
6159
  /***/ 4628:
4939
6160
  /***/ ((__unused_webpack_module, exports) => {
4940
6161
 
@@ -6154,13 +7375,18 @@ exports.GCLOUD_RUN_DEPLOY_TYPE = {
6154
7375
  }
6155
7376
  return {};
6156
7377
  },
6157
- additionalSecretKeys: ctx => [{
7378
+ additionalSecretKeys: ctx => [
7379
+ // deploy-time credentials don't exist for the local env
7380
+ ...(ctx.envType !== "local" ? [{
6158
7381
  key: exports.GCLOUD_DEPLOY_CREDENTIALS_KEY,
6159
7382
  hidden: true
6160
7383
  }, {
6161
7384
  key: exports.GCLOUD_RUN_CANONICAL_HOST_SUFFIX,
6162
- hidden: true
6163
- }, ...(ctx.deployConfigRaw && ctx.deployConfigRaw.cloudSql ? [{
7385
+ hidden: true,
7386
+ // a public url fragment, not sensitive: masking it would
7387
+ // suppress environment urls on github and break log links
7388
+ kind: "variable"
7389
+ }] : []), ...(ctx.deployConfigRaw && ctx.deployConfigRaw.cloudSql ? [{
6164
7390
  key: "DB_PASSWORD"
6165
7391
  }] : [])],
6166
7392
  getAdditionalEnvVars: ctx => {
@@ -6168,7 +7394,8 @@ exports.GCLOUD_RUN_DEPLOY_TYPE = {
6168
7394
  fullName,
6169
7395
  env,
6170
7396
  componentName,
6171
- deployConfigRaw
7397
+ deployConfigRaw,
7398
+ envType
6172
7399
  } = ctx;
6173
7400
  const HOSTNAME_INTERNAL = (0, BashExpression_1.joinBashExpressions)([fullName, (0, BashExpression_1.getBashVariable)((0, context_1.getSecretVarName)(env, componentName, exports.GCLOUD_RUN_CANONICAL_HOST_SUFFIX))], "-").toLowerCase();
6174
7401
  const jobTriggers = deployConfigRaw && deployConfigRaw.jobs ? Object.fromEntries(Object.entries(deployConfigRaw.jobs).filter(([name, job]) => Boolean(job)).map(([name, job]) => ["CLOUD_RUN_JOB_TRIGGER_URL_" + (0, gitlab_1.sanitizeForBashVariable)(name), (0, cloudRunExecutionUrl_1.getCloudRunJobExecuteUrl)(name, {
@@ -6177,7 +7404,11 @@ exports.GCLOUD_RUN_DEPLOY_TYPE = {
6177
7404
  region: deployConfigRaw.region
6178
7405
  })])) : {};
6179
7406
  return {
6180
- HOSTNAME_INTERNAL,
7407
+ // references the canonical host suffix secret, which doesn't
7408
+ // exist for the local env
7409
+ ...(envType !== "local" ? {
7410
+ HOSTNAME_INTERNAL
7411
+ } : {}),
6181
7412
  ...getCloudSqlVariables(ctx),
6182
7413
  ...jobTriggers,
6183
7414
  DEPLOY_CLOUD_RUN_SERVICE_NAME: (0, getServiceName_1.getServiceNameForEnvContext)(ctx),
@@ -6270,7 +7501,10 @@ const getDatabaseDeleteScript = (context, deployConfig) => {
6270
7501
  // unfortunatly the database cannot be deleted right after the service has stopped, as it might still have connections
6271
7502
  // we have to repeate this process
6272
7503
  return [`echo "deleting database ${DB_NAME}..."`, `echo "👆 this can take multiple attemps (3-5min), because google cloud run may still have a connection to the database after the cloud run service is shut down"`, (0, gitlab_1.repeatOnFailure)(`gcloud sql databases delete ${DB_NAME} --instance=${instanceId} --project ${projectId}`, {
6273
- pauseInSeconds: 10
7504
+ pauseInSeconds: 10,
7505
+ // connections normally clear within 3-5min; anything beyond is
7506
+ // a permanent error (missing permission, wrong instance, ...)
7507
+ maxAttempts: 30
6274
7508
  })];
6275
7509
  };
6276
7510
  exports.getDatabaseDeleteScript = getDatabaseDeleteScript;
@@ -7994,6 +9228,7 @@ const promises_1 = __nccwpck_require__(1943);
7994
9228
  const path_1 = __nccwpck_require__(6928);
7995
9229
  const backends_1 = __nccwpck_require__(3243);
7996
9230
  const gitlab_1 = __nccwpck_require__(8475);
9231
+ const jobImagesPlan_1 = __nccwpck_require__(1585);
7997
9232
  /**
7998
9233
  * generates the pipeline files of all pipeline types enabled in the
7999
9234
  * config (see `pipelines`). Pass an explicit `pipelineType` to generate
@@ -8001,6 +9236,16 @@ const gitlab_1 = __nccwpck_require__(8475);
8001
9236
  */
8002
9237
  async function generatePipelineFiles(context, pipelineType) {
8003
9238
  const pipelineTypes = pipelineType ? [pipelineType] : (0, backends_1.getEnabledPipelineTypes)(context.config);
9239
+ if (!pipelineType) {
9240
+ // the materialized image definitions are a union across all enabled
9241
+ // backends and no single backend may wipe them — cleaned once per
9242
+ // full generation, so images that fell out of use don't linger (and
9243
+ // keep triggering gitlab's rules:changes)
9244
+ await (0, promises_1.rm)(jobImagesPlan_1.GENERATED_IMAGES_FOLDER, {
9245
+ recursive: true,
9246
+ force: true
9247
+ });
9248
+ }
8004
9249
  for (const type of pipelineTypes) {
8005
9250
  await generatePipelineFilesForBackend(context, (0, backends_1.getPipelineBackend)(type));
8006
9251
  }
@@ -8018,7 +9263,11 @@ async function generatePipelineFilesForBackend(context, backend) {
8018
9263
  await (0, promises_1.mkdir)((0, path_1.dirname)(path), {
8019
9264
  recursive: true
8020
9265
  });
8021
- await context.fileWriter.writeYamlfile(path, content);
9266
+ if (typeof content === "string") {
9267
+ await context.fileWriter.writeGeneratedFileRaw(path, content);
9268
+ } else {
9269
+ await context.fileWriter.writeYamlfile(path, content);
9270
+ }
8022
9271
  }));
8023
9272
  }
8024
9273
  /**
@@ -8374,10 +9623,23 @@ exports.RULE_CONDITION_HOTFIX_BRANCH = "$CI_COMMIT_BRANCH =~ /^[0-9]+.([0-9]+|x)
8374
9623
  Object.defineProperty(exports, "__esModule", ({
8375
9624
  value: true
8376
9625
  }));
8377
- exports.getRunnerImage = void 0;
9626
+ exports.getCentralRunnerImageUrl = exports.getRunnerImage = exports.isCatladderImageRef = void 0;
8378
9627
  const constants_1 = __nccwpck_require__(3480);
8379
- const getRunnerImage = imageName => constants_1.DOCKER_REGISTRY + "/" + imageName + ":" + constants_1.PIPELINE_IMAGE_TAG;
9628
+ const isCatladderImageRef = value => typeof value === "object" && value !== null && "catladderImage" in value && typeof value.catladderImage === "string";
9629
+ exports.isCatladderImageRef = isCatladderImageRef;
9630
+ /**
9631
+ * job creators use this to reference a catladder-provided job image;
9632
+ * the backends resolve it to a concrete image url
9633
+ */
9634
+ const getRunnerImage = imageName => ({
9635
+ catladderImage: imageName
9636
+ });
8380
9637
  exports.getRunnerImage = getRunnerImage;
9638
+ /**
9639
+ * the image url in catladder's central registry (the classic behavior)
9640
+ */
9641
+ const getCentralRunnerImageUrl = imageName => constants_1.DOCKER_REGISTRY + "/" + imageName + ":" + constants_1.PIPELINE_IMAGE_TAG;
9642
+ exports.getCentralRunnerImageUrl = getCentralRunnerImageUrl;
8381
9643
 
8382
9644
  /***/ }),
8383
9645
 
@@ -8592,8 +9854,14 @@ const sanitizeForBashVariable = name => name.replace(/-/g, "_");
8592
9854
  exports.sanitizeForBashVariable = sanitizeForBashVariable;
8593
9855
  const repeatOnFailure = (command, options) => {
8594
9856
  return `
9857
+ _attempt=0
8595
9858
  until ${command}
8596
9859
  do
9860
+ _attempt=$((_attempt+1))
9861
+ if [ "$_attempt" -ge ${options.maxAttempts} ]; then
9862
+ echo "giving up after ${options.maxAttempts} attempts"
9863
+ exit 1
9864
+ fi
8597
9865
  echo "Trying again."
8598
9866
  sleep ${options.pauseInSeconds}
8599
9867
  done
@@ -8781,6 +10049,14 @@ class FileWriter {
8781
10049
  extension: (_b = path.split(".").pop()) !== null && _b !== void 0 ? _b : ""
8782
10050
  };
8783
10051
  }
10052
+ /**
10053
+ * writes content verbatim (no auto-generated header) — e.g. for
10054
+ * materialized dockerfiles, where a leading comment would break
10055
+ * parser directives like `# syntax = ...`
10056
+ */
10057
+ async writeGeneratedFileRaw(path, content) {
10058
+ await this.writeTheFile(path, content);
10059
+ }
8784
10060
  async writeGeneratedFile(path, content, options) {
8785
10061
  await this.writeTheFile(path, [this.getAutoGeneratedHeader(options.commentChar), content].join("\n"));
8786
10062
  }