@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
@@ -2,7 +2,7 @@
2
2
  /******/ var __webpack_modules__ = ({
3
3
 
4
4
  /***/ 48618:
5
- /***/ ((__unused_webpack_module, exports) => {
5
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
6
6
 
7
7
  "use strict";
8
8
 
@@ -11,6 +11,7 @@ Object.defineProperty(exports, "__esModule", ({
11
11
  value: true
12
12
  }));
13
13
  exports.BaseContext = void 0;
14
+ const vaultManagerAccess_1 = __nccwpck_require__(37881);
14
15
  /**
15
16
  * Base class for CommandContext implementations.
16
17
  * Handles caching, the `get()` method, and `promptDirect` delegation.
@@ -20,6 +21,10 @@ class BaseContext {
20
21
  constructor(command) {
21
22
  this.command = command;
22
23
  this.cache = new Map();
24
+ this.vaultManagerGetter = (0, vaultManagerAccess_1.createVaultManagerGetter)(() => this);
25
+ }
26
+ getVaultManager() {
27
+ return this.vaultManagerGetter();
23
28
  }
24
29
  get(name) {
25
30
  if (!this.cache.has(name)) {
@@ -91,6 +96,7 @@ Object.defineProperty(exports, "__esModule", ({
91
96
  exports.createTerminalIO = exports.createTerminalContext = void 0;
92
97
  const types_1 = __nccwpck_require__(92284);
93
98
  const baseContext_1 = __nccwpck_require__(48618);
99
+ const vaultManagerAccess_1 = __nccwpck_require__(37881);
94
100
  /**
95
101
  * Resolve choices from a choices function.
96
102
  */
@@ -155,6 +161,10 @@ class TerminalContext extends baseContext_1.BaseContext {
155
161
  super(command);
156
162
  this.options = options;
157
163
  }
164
+ get interactive() {
165
+ var _a;
166
+ return (_a = this.options.interactive) !== null && _a !== void 0 ? _a : process.stdin.isTTY === true;
167
+ }
158
168
  log(message) {
159
169
  console.log(message);
160
170
  }
@@ -229,7 +239,10 @@ exports.createTerminalContext = createTerminalContext;
229
239
  * Create a bare IO instance for terminal helper functions.
230
240
  */
231
241
  function createTerminalIO(options = {}) {
232
- return {
242
+ var _a;
243
+ const io = {
244
+ interactive: (_a = options.interactive) !== null && _a !== void 0 ? _a : process.stdin.isTTY === true,
245
+ getVaultManager: (0, vaultManagerAccess_1.createVaultManagerGetter)(() => io),
233
246
  log(message) {
234
247
  console.log(message);
235
248
  },
@@ -255,11 +268,45 @@ function createTerminalIO(options = {}) {
255
268
  throw new types_1.MissingInputError(spec.name, spec.message);
256
269
  }
257
270
  };
271
+ return io;
258
272
  }
259
273
  exports.createTerminalIO = createTerminalIO;
260
274
 
261
275
  /***/ }),
262
276
 
277
+ /***/ 37881:
278
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
279
+
280
+ "use strict";
281
+
282
+
283
+ Object.defineProperty(exports, "__esModule", ({
284
+ value: true
285
+ }));
286
+ exports.createVaultManagerGetter = void 0;
287
+ const getProjectConfig_1 = __nccwpck_require__(50001);
288
+ const VaultManager_1 = __nccwpck_require__(55771);
289
+ /**
290
+ * lazily creates the vault manager of a context (once per context),
291
+ * loading the project config on first use
292
+ */
293
+ const createVaultManagerGetter = (io, options = {}) => {
294
+ let managerPromise;
295
+ return () => managerPromise !== null && managerPromise !== void 0 ? managerPromise : managerPromise = (async () => {
296
+ const config = await (0, getProjectConfig_1.getProjectConfig)();
297
+ if (!config) {
298
+ throw new Error("no catladder config found — the secrets vault needs a project config");
299
+ }
300
+ return new VaultManager_1.VaultManager(config, {
301
+ ...options,
302
+ io: io()
303
+ });
304
+ })();
305
+ };
306
+ exports.createVaultManagerGetter = createVaultManagerGetter;
307
+
308
+ /***/ }),
309
+
263
310
  /***/ 38365:
264
311
  /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
265
312
 
@@ -355,7 +402,9 @@ const pipeline_1 = __nccwpck_require__(60241);
355
402
  const common_tags_1 = __nccwpck_require__(63779);
356
403
  const lodash_es_1 = __nccwpck_require__(64284);
357
404
  const getProjectConfig_1 = __nccwpck_require__(50001);
405
+ const commandSecretsSyncGithub_1 = __nccwpck_require__(50962);
358
406
  const editAsFile_1 = __nccwpck_require__(82805);
407
+ const github_1 = __nccwpck_require__(18036);
359
408
  const gitlab_1 = __nccwpck_require__(4508);
360
409
  const promise_1 = __nccwpck_require__(51762);
361
410
  /* for convenience, parse json objects. that makes it easier to edit secrets that are object */
@@ -433,21 +482,86 @@ const doItFor = async (instance, envAndComponents) => {
433
482
  }
434
483
  }
435
484
  }
485
+ const config = await (0, getProjectConfig_1.getProjectConfig)();
486
+ if (!config) {
487
+ throw new Error("no catladder config found");
488
+ }
436
489
  instance.log("");
437
- instance.log("upserting all variables, please wait...");
490
+ instance.log("writing all secrets to the vault, please wait...");
438
491
  instance.log("");
439
492
  for (const [componentName, envs] of Object.entries(envAndComponents)) {
440
493
  for (const env of envs) {
441
- instance.log("upserting " + env + ":" + componentName + "...\n");
442
- await (0, gitlab_1.upsertAllVariables)(instance, valuesToEdit[componentName][env], env, componentName);
494
+ instance.log("writing " + env + ":" + componentName + "...\n");
495
+ await (await instance.getVaultManager()).writeSecrets(env, componentName, valuesToEdit[componentName][env]);
443
496
  instance.log("");
444
497
  instance.log("✅ " + env + ":" + componentName);
445
498
  instance.log("--------------------------------\n");
446
499
  }
447
500
  }
501
+ await mirrorSecretsToCiBackends(instance, config, envAndComponents, valuesToEdit);
448
502
  instance.log("done! 😻");
449
503
  instance.log("");
450
504
  };
505
+ /**
506
+ * the vault is the source of truth; every enabled CI backend receives
507
+ * a mirrored copy of the edited secrets (gitlab variables, github
508
+ * secrets), so all CI systems stay in sync
509
+ */
510
+ const mirrorSecretsToCiBackends = async (instance, config, envAndComponents, valuesToEdit) => {
511
+ const enabled = (0, pipeline_1.getEnabledPipelineTypes)(config);
512
+ // gitlab mirror (unless gitlab is the vault itself, then it is
513
+ // already written)
514
+ if ((0, pipeline_1.getVaultConfig)(config).type !== "gitlab" && enabled.includes("gitlab")) {
515
+ for (const [componentName, envs] of Object.entries(envAndComponents)) {
516
+ for (const env of envs) {
517
+ await (0, gitlab_1.upsertAllVariables)(instance, valuesToEdit[componentName][env], env, componentName);
518
+ }
519
+ }
520
+ instance.log("✅ mirrored to gitlab");
521
+ }
522
+ if (!enabled.includes("github")) {
523
+ return;
524
+ }
525
+ const kinds = new Map();
526
+ for (const [componentName, envs] of Object.entries(envAndComponents)) {
527
+ for (const env of envs) {
528
+ const {
529
+ secretEnvVarKeys,
530
+ jobOnlyVars
531
+ } = await (0, getProjectConfig_1.getEnvironment)(env, componentName);
532
+ [...secretEnvVarKeys, ...jobOnlyVars.build.secretEnvVarKeys, ...jobOnlyVars.deploy.secretEnvVarKeys].forEach(({
533
+ key,
534
+ kind
535
+ }) => kinds.set((0, pipeline_1.getSecretVarName)(env, componentName, key), kind !== null && kind !== void 0 ? kind : "secret"));
536
+ }
537
+ }
538
+ const secrets = Object.entries(envAndComponents).flatMap(([componentName, envs]) => envs.flatMap(env => {
539
+ var _a;
540
+ return Object.entries((_a = valuesToEdit[componentName][env]) !== null && _a !== void 0 ? _a : {}).map(([key, value]) => {
541
+ var _a;
542
+ const name = (0, pipeline_1.getSecretVarName)(env, componentName, key);
543
+ return {
544
+ name,
545
+ value: typeof value === "string" ? value : JSON.stringify(value !== null && value !== void 0 ? value : ""),
546
+ kind: (_a = kinds.get(name)) !== null && _a !== void 0 ? _a : "secret"
547
+ };
548
+ });
549
+ }));
550
+ if (secrets.length === 0) {
551
+ return;
552
+ }
553
+ if (!(await (0, github_1.isGhAuthenticated)())) {
554
+ instance.log("⚠️ github pipeline is enabled, but the github cli (gh) is not authenticated — run `project secrets-sync-github` later to mirror the secrets");
555
+ return;
556
+ }
557
+ const repo = await (0, commandSecretsSyncGithub_1.getConfiguredGithubRepo)();
558
+ if (!repo) {
559
+ instance.log("⚠️ github pipeline is enabled, but no github repository was found — run `project secrets-sync-github` later to mirror the secrets");
560
+ return;
561
+ }
562
+ instance.log(`mirroring ${secrets.length} secrets to github ('${repo}')...`);
563
+ await (0, commandSecretsSyncGithub_1.pushSecretsToGithub)(instance, repo, secrets);
564
+ };
451
565
  const projectConfigSecrets = async (io, envComponent) => {
452
566
  if (!envComponent) {
453
567
  const allEnvAndcomponents = await (0, getProjectConfig_1.getAllComponentsWithAllEnvsHierarchical)();
@@ -1874,7 +1988,7 @@ exports.commandKubeStopPortforward = (0, defineCommand_1.defineCommand)({
1874
1988
  Object.defineProperty(exports, "__esModule", ({
1875
1989
  value: true
1876
1990
  }));
1877
- exports.commandStarwars = exports.commandDadjoke = exports.commandSecurityAuditCreate = exports.commandSecurityAuditEvaluate = exports.commandSecurityAuditCiJob = exports.commandRestoreDb = exports.commandMongoDestroyMember = exports.commandMongoPortForward = exports.commandMongoGetShell = exports.commandTriggerCronjobProject = exports.commandDeleteProject = exports.commandPauseProject = exports.commandDeletePods = exports.commandGetShell = exports.commandListPods = exports.commandNamespace = exports.commandSecurityEvaluate = exports.commandProjectRestoreDb = exports.commandCiJobLog = exports.commandCiJobOpen = exports.commandSetup = exports.commandGetMyTotalWorktime = exports.commandRenewToken = exports.commandTriggerCronjobGeneral = exports.commandOpenLogs = exports.commandSecretsClearBackups = exports.commandConfigSecrets = exports.commandCloudSqlProxy = exports.commandOpenGit = exports.commandOpenEnv = exports.commandPortForward = exports.commandEnvVars = exports.commandKubePortForward = exports.commandKubeGetShell = exports.commandKubeStopPortforward = exports.commandKubeListPods = exports.commandKubeListSecrets = exports.commandKubeListNamespaces = exports.commandKubeCurrentContext = void 0;
1991
+ exports.commandStarwars = exports.commandDadjoke = exports.commandSecurityAuditCreate = exports.commandSecurityAuditEvaluate = exports.commandSecurityAuditCiJob = exports.commandRestoreDb = exports.commandMongoDestroyMember = exports.commandMongoPortForward = exports.commandMongoGetShell = exports.commandTriggerCronjobProject = exports.commandDeleteProject = exports.commandPauseProject = exports.commandDeletePods = exports.commandGetShell = exports.commandListPods = exports.commandNamespace = exports.commandSecurityEvaluate = exports.commandProjectRestoreDb = exports.commandCiJobLog = exports.commandCiJobOpen = exports.commandSetup = exports.commandGetMyTotalWorktime = exports.commandRenewToken = exports.commandTriggerCronjobGeneral = exports.commandOpenLogs = exports.commandSecretsSyncGithub = exports.commandSecretsClearBackups = exports.commandConfigSecrets = exports.commandCloudSqlProxy = exports.commandOpenGit = exports.commandOpenEnv = exports.commandPortForward = exports.commandEnvVars = exports.commandKubePortForward = exports.commandKubeGetShell = exports.commandKubeStopPortforward = exports.commandKubeListPods = exports.commandKubeListSecrets = exports.commandKubeListNamespaces = exports.commandKubeCurrentContext = void 0;
1878
1992
  // General commands
1879
1993
  var commandKubeCurrentContext_1 = __nccwpck_require__(70243);
1880
1994
  Object.defineProperty(exports, "commandKubeCurrentContext", ({
@@ -1975,6 +2089,13 @@ Object.defineProperty(exports, "commandSecretsClearBackups", ({
1975
2089
  return commandSecretsClearBackups_1.commandSecretsClearBackups;
1976
2090
  }
1977
2091
  }));
2092
+ var commandSecretsSyncGithub_1 = __nccwpck_require__(50962);
2093
+ Object.defineProperty(exports, "commandSecretsSyncGithub", ({
2094
+ enumerable: true,
2095
+ get: function () {
2096
+ return commandSecretsSyncGithub_1.commandSecretsSyncGithub;
2097
+ }
2098
+ }));
1978
2099
  var commandOpenLogs_1 = __nccwpck_require__(98720);
1979
2100
  Object.defineProperty(exports, "commandOpenLogs", ({
1980
2101
  enumerable: true,
@@ -3186,6 +3307,163 @@ exports.commandSecretsClearBackups = (0, defineCommand_1.defineCommand)({
3186
3307
 
3187
3308
  /***/ }),
3188
3309
 
3310
+ /***/ 50962:
3311
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
3312
+
3313
+ "use strict";
3314
+
3315
+
3316
+ Object.defineProperty(exports, "__esModule", ({
3317
+ value: true
3318
+ }));
3319
+ exports.commandSecretsSyncGithub = exports.getConfiguredGithubRepo = exports.pushSecretsToGithub = exports.collectSecretsFromVault = void 0;
3320
+ const pipeline_1 = __nccwpck_require__(60241);
3321
+ const getProjectConfig_1 = __nccwpck_require__(50001);
3322
+ const defineCommand_1 = __nccwpck_require__(39489);
3323
+ const github_1 = __nccwpck_require__(18036);
3324
+ /**
3325
+ * all secrets declared in the config, with their values read from the
3326
+ * vault (github secrets are write-only and can never act as a source)
3327
+ */
3328
+ const collectSecretsFromVault = async (io, envFilter) => {
3329
+ const envAndComponents = await (0, getProjectConfig_1.getAllComponentsWithAllEnvsHierarchical)();
3330
+ const kinds = new Map();
3331
+ for (const [componentName, envs] of Object.entries(envAndComponents)) {
3332
+ for (const env of envs) {
3333
+ if (envFilter && !envFilter.includes(env)) {
3334
+ continue;
3335
+ }
3336
+ const {
3337
+ secretEnvVarKeys,
3338
+ jobOnlyVars
3339
+ } = await (0, getProjectConfig_1.getEnvironment)(env, componentName);
3340
+ const keys = [...secretEnvVarKeys, ...jobOnlyVars.build.secretEnvVarKeys, ...jobOnlyVars.deploy.secretEnvVarKeys];
3341
+ keys.forEach(({
3342
+ key,
3343
+ kind
3344
+ }) => kinds.set((0, pipeline_1.getSecretVarName)(env, componentName, key), kind !== null && kind !== void 0 ? kind : "secret"));
3345
+ }
3346
+ }
3347
+ const names = [...kinds.keys()];
3348
+ const values = await (await io.getVaultManager()).readSecrets(names, io);
3349
+ const secrets = names.filter(name => values[name] !== undefined).map(name => {
3350
+ var _a;
3351
+ return {
3352
+ name,
3353
+ value: values[name],
3354
+ kind: (_a = kinds.get(name)) !== null && _a !== void 0 ? _a : "secret"
3355
+ };
3356
+ });
3357
+ const missing = names.filter(name => values[name] === undefined);
3358
+ return {
3359
+ secrets,
3360
+ missing
3361
+ };
3362
+ };
3363
+ exports.collectSecretsFromVault = collectSecretsFromVault;
3364
+ /**
3365
+ * pushes vault values to github via the gh cli: sensitive ones as repo
3366
+ * secrets (masked), "variable"-kind ones as repo variables (plain)
3367
+ */
3368
+ const pushSecretsToGithub = async (io, repo, secrets) => {
3369
+ for (const {
3370
+ name,
3371
+ value,
3372
+ kind
3373
+ } of secrets) {
3374
+ if (kind === "variable") {
3375
+ await (0, github_1.setGithubVariable)(repo, name, value);
3376
+ io.log(`📖 ${name} (variable)`);
3377
+ } else {
3378
+ await (0, github_1.setGithubSecret)(repo, name, value);
3379
+ io.log(`✅ ${name}`);
3380
+ }
3381
+ }
3382
+ };
3383
+ exports.pushSecretsToGithub = pushSecretsToGithub;
3384
+ /**
3385
+ * resolves the github repository from the configured git remote
3386
+ * (`pipelines.github.gitRemote`, defaults to origin)
3387
+ */
3388
+ const getConfiguredGithubRepo = async () => {
3389
+ const config = await (0, getProjectConfig_1.getProjectConfig)();
3390
+ if (!config) {
3391
+ return undefined;
3392
+ }
3393
+ return (0, github_1.getGithubRepoFromRemote)((0, pipeline_1.getPipelineGitRemote)(config, "github"));
3394
+ };
3395
+ exports.getConfiguredGithubRepo = getConfiguredGithubRepo;
3396
+ /**
3397
+ * copies the catladder secrets from the vault to github actions
3398
+ * repository secrets, keeping the exact same names, so the github
3399
+ * pipeline can reference them as ${{ secrets.<NAME> }}.
3400
+ */
3401
+ exports.commandSecretsSyncGithub = (0, defineCommand_1.defineCommand)({
3402
+ name: "project secrets-sync-github",
3403
+ description: "copies all secrets from the vault to github repo secrets (same names), for the github pipeline",
3404
+ group: "project",
3405
+ inputs: {
3406
+ repo: {
3407
+ type: "string",
3408
+ message: "github repository (owner/name)",
3409
+ positional: true,
3410
+ required: false
3411
+ },
3412
+ env: {
3413
+ type: "string",
3414
+ message: "only sync these environments (comma-separated, e.g. review,dev) — github allows at most 100 repo secrets",
3415
+ positional: true,
3416
+ required: false
3417
+ }
3418
+ },
3419
+ execute: async ctx => {
3420
+ var _a, _b;
3421
+ const config = await (0, getProjectConfig_1.getProjectConfig)();
3422
+ if (!config) {
3423
+ throw new Error("no catladder config found");
3424
+ }
3425
+ const enabled = (0, pipeline_1.getEnabledPipelineTypes)(config);
3426
+ if (!enabled.includes("github")) {
3427
+ throw new Error("the github pipeline is not enabled in `pipelines` — nothing to sync to");
3428
+ }
3429
+ if (!(await (0, github_1.isGhAuthenticated)())) {
3430
+ throw new Error("the github cli (gh) is not installed or not authenticated — run `gh auth login` first");
3431
+ }
3432
+ const repo = (_a = await ctx.get("repo")) !== null && _a !== void 0 ? _a : await (0, exports.getConfiguredGithubRepo)();
3433
+ if (!repo) {
3434
+ throw new Error(`no github repository found on the '${(0, pipeline_1.getPipelineGitRemote)(config, "github")}' remote — pass it as owner/name or configure pipelines.github.gitRemote`);
3435
+ }
3436
+ const envFilter = (_b = await ctx.get("env")) === null || _b === void 0 ? void 0 : _b.split(",").map(e => e.trim());
3437
+ const {
3438
+ secrets,
3439
+ missing
3440
+ } = await (0, exports.collectSecretsFromVault)(ctx, envFilter);
3441
+ if (missing.length > 0) {
3442
+ ctx.log(`⚠️ ${missing.length} secrets are not set in the vault and will be skipped:`);
3443
+ missing.forEach(name => ctx.log(` - ${name}`));
3444
+ ctx.log("");
3445
+ }
3446
+ if (secrets.length === 0) {
3447
+ ctx.log("no secrets to sync 🤷");
3448
+ return;
3449
+ }
3450
+ ctx.log(`about to set ${secrets.length} secrets on '${repo}':`);
3451
+ secrets.forEach(({
3452
+ name
3453
+ }) => ctx.log(` - ${name}`));
3454
+ ctx.log("");
3455
+ const confirmed = await ctx.confirm("continue? 🤔");
3456
+ if (!confirmed) {
3457
+ throw new Error("abort");
3458
+ }
3459
+ await (0, exports.pushSecretsToGithub)(ctx, repo, secrets);
3460
+ ctx.log("");
3461
+ ctx.log("done! 😻 the github pipeline can use them as ${{ secrets.<NAME> }}");
3462
+ }
3463
+ });
3464
+
3465
+ /***/ }),
3466
+
3189
3467
  /***/ 92494:
3190
3468
  /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
3191
3469
 
@@ -4179,14 +4457,24 @@ const getGitlabVar = async (io, env, componentName, variableName) => {
4179
4457
  };
4180
4458
  exports.getGitlabVar = getGitlabVar;
4181
4459
  const resolveSecrets = async (io, varSets) => {
4182
- const allVariablesInGitlab = await (0, gitlab_1.getAllVariables)(io);
4460
+ // the secret variables actually referenced by the env var values —
4461
+ // when they are all in the local cache, the vault is not contacted
4462
+ const referencedNames = [...new Set(varSets.flatMap(({
4463
+ envVars
4464
+ }) => Object.values(envVars).flatMap(value => {
4465
+ var _a;
4466
+ return (_a = `${value}`.match(/\$CL_\w+/g)) !== null && _a !== void 0 ? _a : [];
4467
+ })))].map(reference => reference.slice(1));
4468
+ const vaultManager = await io.getVaultManager();
4469
+ const secrets = await vaultManager.readSecrets(referencedNames, io);
4470
+ const secretEntries = Object.entries(secrets);
4183
4471
  return Object.fromEntries(varSets.flatMap(({
4184
4472
  envVars,
4185
4473
  secretEnvVarKeys
4186
4474
  }) => Object.entries(envVars).filter(([key, value]) => {
4187
4475
  var _a;
4188
4476
  return value !== undefined && value !== null && !((_a = secretEnvVarKeys.find(k => k.key === key)) === null || _a === void 0 ? void 0 : _a.hidden);
4189
- }).map(([key, value]) => [key, allVariablesInGitlab.reduce((acc, curr) => acc.replace(new RegExp("\\$" + curr.key, "g"), curr.value), `${value}`)])));
4477
+ }).map(([key, value]) => [key, secretEntries.reduce((acc, [name, secretValue]) => acc.replace(new RegExp("\\$" + name, "g"), secretValue), `${value}`)])));
4190
4478
  };
4191
4479
  const getEnvVarsResolved = async (io, env, componentName) => {
4192
4480
  if (!componentName) {
@@ -4291,8 +4579,15 @@ exports.defineCommand = defineCommand;
4291
4579
  Object.defineProperty(exports, "__esModule", ({
4292
4580
  value: true
4293
4581
  }));
4294
- exports.MissingInputError = void 0;
4582
+ exports.MissingInputError = exports.NonInteractiveError = void 0;
4295
4583
  // ─── Errors ──────────────────────────────────────────────────────────
4584
+ class NonInteractiveError extends Error {
4585
+ constructor(interaction) {
4586
+ super(`${interaction} requires an interactive terminal, but this context is not interactive`);
4587
+ this.name = "NonInteractiveError";
4588
+ }
4589
+ }
4590
+ exports.NonInteractiveError = NonInteractiveError;
4296
4591
  class MissingInputError extends Error {
4297
4592
  constructor(inputName, promptMessage) {
4298
4593
  super(`Missing required input: "${inputName}" (${promptMessage}). ` + `Provide it via --${inputName}=<value> or --inputs='{"${inputName}": ...}'`);
@@ -4742,14 +5037,14 @@ exports.getProjectRootPath = getProjectRootPath;
4742
5037
  /**
4743
5038
  * Get the git remote hostname and project path from the git config.
4744
5039
  */
4745
- const getGitRemoteHostAndPath = async () => {
5040
+ const getGitRemoteHostAndPath = async (remoteName = "origin") => {
4746
5041
  var _a;
4747
- const remoteUrl = await (0, gitConfig_1.gitConfigGet)("remote.origin.url");
5042
+ const remoteUrl = await (0, gitConfig_1.gitConfigGet)(`remote.${remoteName}.url`);
4748
5043
  const remoteReg = /(https:\/\/|git@)([^:/]+)[:/]([^.]*)(\.git)?/;
4749
5044
  const match = (_a = remoteUrl.match(remoteReg)) !== null && _a !== void 0 ? _a : [];
4750
5045
  const [,, gitRemoteHost, gitRemotePath] = match;
4751
5046
  if (!(gitRemoteHost === null || gitRemoteHost === void 0 ? void 0 : gitRemoteHost.length) || !(gitRemotePath === null || gitRemotePath === void 0 ? void 0 : gitRemotePath.length)) {
4752
- throw new Error(`Failed to parse git remote hostname and path from git configs remote.origin.url! ${remoteUrl}`);
5047
+ throw new Error(`Failed to parse git remote hostname and path from git configs remote.${remoteName}.url! ${remoteUrl}`);
4753
5048
  }
4754
5049
  return {
4755
5050
  gitRemoteHost,
@@ -5306,6 +5601,74 @@ exports["default"] = async () => {
5306
5601
 
5307
5602
  /***/ }),
5308
5603
 
5604
+ /***/ 18036:
5605
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
5606
+
5607
+ "use strict";
5608
+
5609
+
5610
+ Object.defineProperty(exports, "__esModule", ({
5611
+ value: true
5612
+ }));
5613
+ exports.setGithubVariable = exports.setGithubSecret = exports.getGithubRepoFromRemote = exports.isGhAuthenticated = void 0;
5614
+ const child_process_1 = __nccwpck_require__(35317);
5615
+ const util_1 = __nccwpck_require__(39023);
5616
+ const execFile = (0, util_1.promisify)(child_process_1.execFile);
5617
+ /**
5618
+ * whether the github cli (gh) is installed and authenticated
5619
+ */
5620
+ const isGhAuthenticated = async () => {
5621
+ try {
5622
+ await execFile("gh", ["auth", "status"]);
5623
+ return true;
5624
+ } catch (_a) {
5625
+ return false;
5626
+ }
5627
+ };
5628
+ exports.isGhAuthenticated = isGhAuthenticated;
5629
+ /**
5630
+ * the github repository (owner/name) a git remote points to.
5631
+ * Which remote belongs to github is configured per pipeline type
5632
+ * (`pipelines.github.gitRemote`, defaults to origin).
5633
+ */
5634
+ const getGithubRepoFromRemote = async remoteName => {
5635
+ try {
5636
+ const {
5637
+ stdout
5638
+ } = await execFile("git", ["remote", "get-url", remoteName]);
5639
+ const match = stdout.match(/github\.com[:/]([\w.-]+\/[\w.-]+?)(\.git)?\s*$/);
5640
+ return match === null || match === void 0 ? void 0 : match[1];
5641
+ } catch (_a) {
5642
+ return undefined;
5643
+ }
5644
+ };
5645
+ exports.getGithubRepoFromRemote = getGithubRepoFromRemote;
5646
+ const ghSetViaStdin = (kind, repo, name, value) => new Promise((resolve, reject) => {
5647
+ var _a, _b;
5648
+ const child = (0, child_process_1.spawn)("gh", [kind, "set", name, "--repo", repo], {
5649
+ stdio: ["pipe", "ignore", "pipe"]
5650
+ });
5651
+ let stderr = "";
5652
+ (_a = child.stderr) === null || _a === void 0 ? void 0 : _a.on("data", data => stderr += data);
5653
+ child.on("error", reject);
5654
+ child.on("close", code => code === 0 ? resolve() : reject(new Error(`gh ${kind} set ${name} failed: ${stderr.trim()}`)));
5655
+ (_b = child.stdin) === null || _b === void 0 ? void 0 : _b.end(value);
5656
+ });
5657
+ /**
5658
+ * sets a github actions repository secret via the gh cli.
5659
+ * The value is passed via stdin so it never shows up in a process list.
5660
+ */
5661
+ const setGithubSecret = async (repo, name, value) => ghSetViaStdin("secret", repo, name, value);
5662
+ exports.setGithubSecret = setGithubSecret;
5663
+ /**
5664
+ * sets a github actions repository variable (plain, unmasked) via the
5665
+ * gh cli — for vault values classified as not sensitive
5666
+ */
5667
+ const setGithubVariable = async (repo, name, value) => ghSetViaStdin("variable", repo, name, value);
5668
+ exports.setGithubVariable = setGithubVariable;
5669
+
5670
+ /***/ }),
5671
+
5309
5672
  /***/ 4508:
5310
5673
  /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
5311
5674
 
@@ -5845,6 +6208,445 @@ exports.getShell = getShell;
5845
6208
 
5846
6209
  /***/ }),
5847
6210
 
6211
+ /***/ 63510:
6212
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
6213
+
6214
+ "use strict";
6215
+
6216
+
6217
+ Object.defineProperty(exports, "__esModule", ({
6218
+ value: true
6219
+ }));
6220
+ exports.BitwardenVault = void 0;
6221
+ const pipeline_1 = __nccwpck_require__(60241);
6222
+ const yaml_1 = __nccwpck_require__(6637);
6223
+ const bw_1 = __nccwpck_require__(52211);
6224
+ const DEFAULT_COLLECTION = "catladder";
6225
+ const SECURE_NOTE_TYPE = 2;
6226
+ /**
6227
+ * one yaml note per env and component:
6228
+ * `<customerName>/<appName>/<env>/<componentName>/secrets.yml`
6229
+ */
6230
+ const itemName = (config, env, componentName) => `${config.customerName}/${config.appName}/${env}/${componentName}/secrets.yml`;
6231
+ const itemPrefix = config => `${config.customerName}/${config.appName}/`;
6232
+ /**
6233
+ * parses env and componentName back out of an item name
6234
+ */
6235
+ const parseItemName = (config, name) => {
6236
+ const match = name.slice(itemPrefix(config).length).match(/^([^/]+)\/([^/]+)\/secrets\.yml$/);
6237
+ return match ? {
6238
+ env: match[1],
6239
+ componentName: match[2]
6240
+ } : null;
6241
+ };
6242
+ const findCollection = async (session, collectionName) => {
6243
+ const collections = await (0, bw_1.runBwJson)(session, ["list", "collections", "--search", collectionName]);
6244
+ const collection = collections.find(c => c.name === collectionName);
6245
+ if (!collection) {
6246
+ throw new Error(`bitwarden collection '${collectionName}' not found — create it and make sure you have access`);
6247
+ }
6248
+ return collection;
6249
+ };
6250
+ const encodePayload = payload => Buffer.from(JSON.stringify(payload), "utf-8").toString("base64");
6251
+ class BitwardenVault {
6252
+ constructor(config, options,
6253
+ /**
6254
+ * whether a locked bitwarden may be unlocked interactively
6255
+ * (false with catenv --vault-mode no-prompt)
6256
+ */
6257
+ allowPrompt) {
6258
+ var _a;
6259
+ this.config = config;
6260
+ this.allowPrompt = allowPrompt;
6261
+ this.collectionName = (_a = options.collection) !== null && _a !== void 0 ? _a : DEFAULT_COLLECTION;
6262
+ this.id = `bitwarden:${this.collectionName}`;
6263
+ }
6264
+ session(io) {
6265
+ return (0, bw_1.getBwSession)(io, {
6266
+ allowPrompt: this.allowPrompt
6267
+ });
6268
+ }
6269
+ async readAllSecrets(io) {
6270
+ const {
6271
+ config,
6272
+ collectionName
6273
+ } = this;
6274
+ const bwSession = await this.session(io);
6275
+ const collection = await findCollection(bwSession, collectionName);
6276
+ const items = await (0, bw_1.runBwJson)(bwSession, ["list", "items", "--collectionid", collection.id]);
6277
+ const secrets = {};
6278
+ for (const item of items) {
6279
+ const parsed = parseItemName(config, item.name);
6280
+ if (!parsed || !item.notes) {
6281
+ continue;
6282
+ }
6283
+ const values = (0, yaml_1.parse)(item.notes);
6284
+ Object.entries(values !== null && values !== void 0 ? values : {}).forEach(([key, value]) => {
6285
+ secrets[(0, pipeline_1.getSecretVarName)(parsed.env, parsed.componentName, key)] = typeof value === "string" ? value : JSON.stringify(value);
6286
+ });
6287
+ }
6288
+ return secrets;
6289
+ }
6290
+ async writeSecrets(io, env, componentName, secrets) {
6291
+ const {
6292
+ config,
6293
+ collectionName
6294
+ } = this;
6295
+ const bwSession = await this.session(io);
6296
+ const collection = await findCollection(bwSession, collectionName);
6297
+ const name = itemName(config, env, componentName);
6298
+ const existing = (await (0, bw_1.runBwJson)(bwSession, ["list", "items", "--collectionid", collection.id, "--search", name])).find(item => item.name === name);
6299
+ const notes = (0, yaml_1.stringify)(secrets);
6300
+ if (existing) {
6301
+ await (0, bw_1.runBw)(bwSession, ["edit", "item", existing.id, encodePayload({
6302
+ ...existing,
6303
+ notes
6304
+ })]);
6305
+ } else {
6306
+ await (0, bw_1.runBw)(bwSession, ["create", "item", encodePayload({
6307
+ type: SECURE_NOTE_TYPE,
6308
+ secureNote: {
6309
+ type: 0
6310
+ },
6311
+ name,
6312
+ notes,
6313
+ ...(collection.organizationId ? {
6314
+ organizationId: collection.organizationId,
6315
+ collectionIds: [collection.id]
6316
+ } : {})
6317
+ })]);
6318
+ }
6319
+ }
6320
+ }
6321
+ exports.BitwardenVault = BitwardenVault;
6322
+
6323
+ /***/ }),
6324
+
6325
+ /***/ 31107:
6326
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
6327
+
6328
+ "use strict";
6329
+
6330
+
6331
+ Object.defineProperty(exports, "__esModule", ({
6332
+ value: true
6333
+ }));
6334
+ exports.GitlabVault = void 0;
6335
+ const gitlab_1 = __nccwpck_require__(4508);
6336
+ /**
6337
+ * the legacy (and default) vault: the gitlab project variables double
6338
+ * as the secret store
6339
+ */
6340
+ class GitlabVault {
6341
+ constructor() {
6342
+ this.id = "gitlab";
6343
+ }
6344
+ async readAllSecrets(io) {
6345
+ const variables = await (0, gitlab_1.getAllVariables)(io);
6346
+ return Object.fromEntries(variables.map(v => [v.key, v.value]));
6347
+ }
6348
+ async writeSecrets(io, env, componentName, secrets) {
6349
+ await (0, gitlab_1.upsertAllVariables)(io, secrets, env, componentName);
6350
+ }
6351
+ }
6352
+ exports.GitlabVault = GitlabVault;
6353
+
6354
+ /***/ }),
6355
+
6356
+ /***/ 55771:
6357
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
6358
+
6359
+ "use strict";
6360
+
6361
+
6362
+ Object.defineProperty(exports, "__esModule", ({
6363
+ value: true
6364
+ }));
6365
+ exports.VaultManager = void 0;
6366
+ const pipeline_1 = __nccwpck_require__(60241);
6367
+ const BitwardenVault_1 = __nccwpck_require__(63510);
6368
+ const cache_1 = __nccwpck_require__(14236);
6369
+ const GitlabVault_1 = __nccwpck_require__(31107);
6370
+ /**
6371
+ * only secrets of this env are cached locally — the other envs'
6372
+ * secrets have no business sitting on developer machines
6373
+ */
6374
+ const CACHED_ENV = "local";
6375
+ /**
6376
+ * owns the configured vault, the secrets mode and the local cache.
6377
+ * The vault is the readable source of truth for secrets; CI backends
6378
+ * only receive mirrored copies.
6379
+ */
6380
+ class VaultManager {
6381
+ constructor(config, options = {}) {
6382
+ var _a, _b;
6383
+ this.config = config;
6384
+ /** refresh mode bypasses the cache only once per manager */
6385
+ this.refreshed = false;
6386
+ this.mode = (_a = options.mode) !== null && _a !== void 0 ? _a : "auto";
6387
+ this.io = (_b = options.io) !== null && _b !== void 0 ? _b : null;
6388
+ this.vault = this.createVault();
6389
+ }
6390
+ createVault() {
6391
+ const vaultConfig = (0, pipeline_1.getVaultConfig)(this.config);
6392
+ switch (vaultConfig.type) {
6393
+ case "gitlab":
6394
+ return new GitlabVault_1.GitlabVault();
6395
+ case "bitwarden":
6396
+ return new BitwardenVault_1.BitwardenVault(this.config, vaultConfig, this.mode === "auto" || this.mode === "refresh");
6397
+ }
6398
+ }
6399
+ /**
6400
+ * the vault secrets, served from the local cache when it contains
6401
+ * all required names; otherwise (depending on the mode) refreshed
6402
+ * from the vault. Only the "local" env is (re-)cached.
6403
+ */
6404
+ async readSecrets(requiredNames, io = this.io) {
6405
+ const bypassCache = this.mode === "refresh" && !this.refreshed;
6406
+ const cache = bypassCache ? null : await (0, cache_1.readSecretsCache)();
6407
+ // known entries include recorded misses (null values), so declared
6408
+ // but intentionally unset secrets don't force a vault refresh
6409
+ const known = (cache === null || cache === void 0 ? void 0 : cache.vaultId) === this.vault.id ? flattenSecrets(cache.secrets) : undefined;
6410
+ const missing = requiredNames.filter(name => !(name in (known !== null && known !== void 0 ? known : {})));
6411
+ // nothing missing (or nothing required at all): don't touch the vault
6412
+ if (missing.length === 0 && (known || requiredNames.length === 0)) {
6413
+ return withoutMisses(known !== null && known !== void 0 ? known : {});
6414
+ }
6415
+ if (this.mode === "offline") {
6416
+ if (missing.length > 0) {
6417
+ 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(", ")}`);
6418
+ }
6419
+ return withoutMisses(known !== null && known !== void 0 ? known : {});
6420
+ }
6421
+ const secrets = await this.vault.readAllSecrets(io);
6422
+ this.refreshed = true;
6423
+ await (0, cache_1.writeSecretsCache)(this.vault.id, await this.cacheableSubset(secrets));
6424
+ return secrets;
6425
+ }
6426
+ /**
6427
+ * writes secrets of one env/component to the vault and updates the
6428
+ * local cache (write-through, "local" env only)
6429
+ */
6430
+ async writeSecrets(env, componentName, secrets, io = this.io) {
6431
+ if (!io) {
6432
+ throw new Error("writing to the vault requires an interactive context");
6433
+ }
6434
+ await this.vault.writeSecrets(io, env, componentName, secrets);
6435
+ if (env === CACHED_ENV) {
6436
+ 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)])));
6437
+ }
6438
+ }
6439
+ /**
6440
+ * the subset of the vault secrets that may be cached: the declared
6441
+ * secrets of the "local" env, per component, as a hierarchy
6442
+ */
6443
+ async cacheableSubset(flatSecrets) {
6444
+ const local = {};
6445
+ for (const componentName of Object.keys(this.config.components)) {
6446
+ const keys = await this.declaredSecretKeys(componentName);
6447
+ const values = Object.fromEntries(keys.map(key => {
6448
+ var _a;
6449
+ return [key,
6450
+ // null records "absent in the vault" (a known miss)
6451
+ (_a = flatSecrets[(0, pipeline_1.getSecretVarName)(CACHED_ENV, componentName, key)]) !== null && _a !== void 0 ? _a : null];
6452
+ }));
6453
+ if (Object.keys(values).length > 0) {
6454
+ local[componentName] = values;
6455
+ }
6456
+ }
6457
+ return Object.keys(local).length > 0 ? {
6458
+ [CACHED_ENV]: local
6459
+ } : {};
6460
+ }
6461
+ async declaredSecretKeys(componentName) {
6462
+ try {
6463
+ const {
6464
+ secretEnvVarKeys,
6465
+ jobOnlyVars
6466
+ } = await (0, pipeline_1.getEnvironment)({
6467
+ config: this.config,
6468
+ componentName,
6469
+ env: CACHED_ENV
6470
+ });
6471
+ return [...secretEnvVarKeys, ...jobOnlyVars.build.secretEnvVarKeys, ...jobOnlyVars.deploy.secretEnvVarKeys].map(({
6472
+ key
6473
+ }) => key);
6474
+ } catch (_a) {
6475
+ // components without a local env simply have nothing to cache
6476
+ return [];
6477
+ }
6478
+ }
6479
+ }
6480
+ exports.VaultManager = VaultManager;
6481
+ /**
6482
+ * the cache hierarchy as a flat map of raw secret variable names
6483
+ * (null values = recorded misses)
6484
+ */
6485
+ 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]))));
6486
+ /**
6487
+ * only actual values — recorded misses stay unresolved, like any other
6488
+ * unset variable
6489
+ */
6490
+ const withoutMisses = known => Object.fromEntries(Object.entries(known).filter(entry => entry[1] !== null));
6491
+
6492
+ /***/ }),
6493
+
6494
+ /***/ 52211:
6495
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
6496
+
6497
+ "use strict";
6498
+
6499
+
6500
+ Object.defineProperty(exports, "__esModule", ({
6501
+ value: true
6502
+ }));
6503
+ exports.runBwJson = exports.runBw = exports.getBwSession = void 0;
6504
+ const child_process_1 = __nccwpck_require__(35317);
6505
+ const util_1 = __nccwpck_require__(39023);
6506
+ const preferences_1 = __nccwpck_require__(66261);
6507
+ const execFile = (0, util_1.promisify)(child_process_1.execFile);
6508
+ const SESSION_PREFERENCE = "bwsession";
6509
+ class BitwardenLockedError extends Error {
6510
+ constructor() {
6511
+ super("bitwarden is locked and prompting is disabled — run `bw unlock` or catenv without --vault-mode no-prompt");
6512
+ }
6513
+ }
6514
+ /**
6515
+ * runs an interactive bw command that prints a session key on stdout
6516
+ * (login/unlock) while the user types the password on the terminal
6517
+ */
6518
+ const interactiveSessionCommand = args => new Promise((resolve, reject) => {
6519
+ var _a;
6520
+ const child = (0, child_process_1.spawn)("bw", [...args, "--raw"], {
6521
+ stdio: ["inherit", "pipe", "inherit"]
6522
+ });
6523
+ let session = "";
6524
+ (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on("data", data => session += data.toString("utf-8"));
6525
+ child.on("error", reject);
6526
+ child.on("close", code => code === 0 && session.trim() ? resolve(session.trim()) : reject(new Error(`bw ${args[0]} failed`)));
6527
+ });
6528
+ const getBwStatus = async session => {
6529
+ try {
6530
+ const {
6531
+ stdout
6532
+ } = await execFile("bw", ["status"], {
6533
+ env: {
6534
+ ...process.env,
6535
+ ...(session ? {
6536
+ BW_SESSION: session
6537
+ } : {})
6538
+ }
6539
+ });
6540
+ return JSON.parse(stdout).status;
6541
+ } catch (_a) {
6542
+ return "unauthenticated";
6543
+ }
6544
+ };
6545
+ /**
6546
+ * a working bw session: reuses the cached one, otherwise unlocks (or
6547
+ * logs in) interactively — unless prompting is disabled
6548
+ */
6549
+ const getBwSession = async (io, {
6550
+ allowPrompt
6551
+ }) => {
6552
+ const cached = (await (0, preferences_1.hasPreference)(SESSION_PREFERENCE)) ? String(await (0, preferences_1.getPreference)(SESSION_PREFERENCE)) : null;
6553
+ const status = await getBwStatus(cached);
6554
+ if (status === "unlocked" && cached) {
6555
+ return cached;
6556
+ }
6557
+ if (!allowPrompt) {
6558
+ throw new BitwardenLockedError();
6559
+ }
6560
+ io === null || io === void 0 ? void 0 : io.log("");
6561
+ io === null || io === void 0 ? void 0 : io.log(status === "unauthenticated" ? "# Please login to Bitwarden:" : "# Bitwarden is locked, please unlock:");
6562
+ io === null || io === void 0 ? void 0 : io.log("");
6563
+ const session = await interactiveSessionCommand([status === "unauthenticated" ? "login" : "unlock"]);
6564
+ await (0, preferences_1.setPreference)(SESSION_PREFERENCE, session);
6565
+ await (0, exports.runBw)(session, ["sync"]);
6566
+ return session;
6567
+ };
6568
+ exports.getBwSession = getBwSession;
6569
+ /**
6570
+ * runs a bw command with the given session and returns stdout
6571
+ */
6572
+ const runBw = async (session, args) => {
6573
+ const {
6574
+ stdout
6575
+ } = await execFile("bw", [...args, "--raw", "--nointeraction"], {
6576
+ env: {
6577
+ ...process.env,
6578
+ BW_SESSION: session
6579
+ },
6580
+ maxBuffer: 50 * 1024 * 1024
6581
+ });
6582
+ return stdout;
6583
+ };
6584
+ exports.runBw = runBw;
6585
+ const runBwJson = async (session, args) => JSON.parse(await (0, exports.runBw)(session, args));
6586
+ exports.runBwJson = runBwJson;
6587
+
6588
+ /***/ }),
6589
+
6590
+ /***/ 14236:
6591
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
6592
+
6593
+ "use strict";
6594
+
6595
+
6596
+ Object.defineProperty(exports, "__esModule", ({
6597
+ value: true
6598
+ }));
6599
+ exports.updateSecretsCache = exports.writeSecretsCache = exports.readSecretsCache = void 0;
6600
+ const promises_1 = __nccwpck_require__(91943);
6601
+ const path_1 = __nccwpck_require__(16928);
6602
+ const gitProjectInformation_1 = __nccwpck_require__(21111);
6603
+ const getCacheFolder = async () => (0, path_1.join)(await (0, gitProjectInformation_1.getProjectRootPath)(), ".catladder");
6604
+ const getCachePath = async () => (0, path_1.join)(await getCacheFolder(), "secrets.json");
6605
+ const readSecretsCache = async () => {
6606
+ try {
6607
+ return JSON.parse(await (0, promises_1.readFile)(await getCachePath(), "utf-8"));
6608
+ } catch (_a) {
6609
+ return null;
6610
+ }
6611
+ };
6612
+ exports.readSecretsCache = readSecretsCache;
6613
+ const writeSecretsCache = async (vaultId, secrets) => {
6614
+ const folder = await getCacheFolder();
6615
+ await (0, promises_1.mkdir)(folder, {
6616
+ recursive: true
6617
+ });
6618
+ // the folder ignores itself, so the cache can never be committed
6619
+ await (0, promises_1.writeFile)((0, path_1.join)(folder, ".gitignore"), "*\n");
6620
+ const cache = {
6621
+ vaultId,
6622
+ updatedAt: new Date().toISOString(),
6623
+ secrets
6624
+ };
6625
+ await (0, promises_1.writeFile)(await getCachePath(), JSON.stringify(cache, null, 2));
6626
+ };
6627
+ exports.writeSecretsCache = writeSecretsCache;
6628
+ /**
6629
+ * merges freshly written secrets of one env/component into the cache
6630
+ * (write-through from config-secrets), so the next catenv run doesn't
6631
+ * need the vault
6632
+ */
6633
+ const updateSecretsCache = async (vaultId, env, componentName, values) => {
6634
+ var _a;
6635
+ const existing = await (0, exports.readSecretsCache)();
6636
+ const secrets = (existing === null || existing === void 0 ? void 0 : existing.vaultId) === vaultId ? existing.secrets : {};
6637
+ secrets[env] = {
6638
+ ...secrets[env],
6639
+ [componentName]: {
6640
+ ...((_a = secrets[env]) === null || _a === void 0 ? void 0 : _a[componentName]),
6641
+ ...values
6642
+ }
6643
+ };
6644
+ await (0, exports.writeSecretsCache)(vaultId, secrets);
6645
+ };
6646
+ exports.updateSecretsCache = updateSecretsCache;
6647
+
6648
+ /***/ }),
6649
+
5848
6650
  /***/ 54038:
5849
6651
  /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
5850
6652
 
@@ -5861,7 +6663,11 @@ const createAllJobs_1 = __nccwpck_require__(83671);
5861
6663
  const config_1 = __nccwpck_require__(96925);
5862
6664
  const ciVariables_1 = __nccwpck_require__(25474);
5863
6665
  const createGithubJobs_1 = __nccwpck_require__(14016);
6666
+ const jobImagesPlan_1 = __nccwpck_require__(11585);
6667
+ const scriptFunctions_1 = __nccwpck_require__(75759);
6668
+ const utils_1 = __nccwpck_require__(20595);
5864
6669
  const githubReleaseJobs_1 = __nccwpck_require__(55611);
6670
+ const scriptFiles_1 = __nccwpck_require__(59189);
5865
6671
  const WORKFLOWS_FOLDER = ".github/workflows";
5866
6672
  /**
5867
6673
  * generated workflow files are prefixed so that cleanup never touches
@@ -5906,18 +6712,29 @@ class GithubBackend {
5906
6712
  async cleanup() {
5907
6713
  const entries = await (0, promises_1.readdir)(WORKFLOWS_FOLDER).catch(() => []);
5908
6714
  await Promise.all(entries.filter(file => file.startsWith(GENERATED_FILE_PREFIX)).map(file => (0, promises_1.unlink)((0, path_1.join)(WORKFLOWS_FOLDER, file))));
6715
+ await (0, promises_1.rm)(scriptFiles_1.GITHUB_SCRIPTS_FOLDER, {
6716
+ recursive: true,
6717
+ force: true
6718
+ });
5909
6719
  }
5910
6720
  async createFiles(config) {
5911
- const workflows = await this.createWorkflows(config);
5912
- return Object.entries(workflows).map(([fileName, workflow]) => ({
6721
+ const images = this.createImagesPlan(config);
6722
+ const scripts = new scriptFiles_1.GithubScriptFiles();
6723
+ const workflows = await this.createWorkflows(config, images, scripts);
6724
+ return [...Object.entries(workflows).map(([fileName, workflow]) => ({
5913
6725
  path: (0, path_1.join)(WORKFLOWS_FOLDER, fileName),
5914
6726
  content: workflow
5915
- }));
6727
+ })),
6728
+ // materialized job scripts and image definitions (repo mode)
6729
+ ...scripts.getGeneratedFiles(), ...images.getGeneratedFiles()];
6730
+ }
6731
+ createImagesPlan(config) {
6732
+ return new jobImagesPlan_1.JobImagesPlan((0, jobImagesPlan_1.getJobImagesMode)(config, this.type), this.type);
5916
6733
  }
5917
6734
  /**
5918
6735
  * all workflows keyed by file name, mainly for testing purposes
5919
6736
  */
5920
- async createWorkflows(config) {
6737
+ async createWorkflows(config, images = this.createImagesPlan(config), scripts = new scriptFiles_1.GithubScriptFiles()) {
5921
6738
  const workflows = {};
5922
6739
  const reviewStopJobs = {};
5923
6740
  const manualJobs = {};
@@ -5928,13 +6745,14 @@ class GithubBackend {
5928
6745
  pipelineType: this.type
5929
6746
  });
5930
6747
  const uploadProviderIds = (0, createGithubJobs_1.getUploadProviderIds)(allJobs);
6748
+ const secretKinds = (0, createGithubJobs_1.collectSecretKinds)(allJobs);
5931
6749
  const jobs = {};
5932
6750
  // NOTE: agent jobs are gitlab-only and skipped on github
5933
6751
  [...allJobs.workspaces, ...allJobs.components].forEach(({
5934
6752
  context,
5935
6753
  jobs: contextJobs
5936
6754
  }) => contextJobs.forEach(job => {
5937
- const [id, githubJob] = (0, createGithubJobs_1.makeGithubJob)(context, job, allJobs, uploadProviderIds);
6755
+ const [id, githubJob] = (0, createGithubJobs_1.makeGithubJob)(context, job, allJobs, uploadProviderIds, images, scripts, secretKinds);
5938
6756
  if (isReviewStopJob(context, job)) {
5939
6757
  reviewStopJobs[id] = githubJob;
5940
6758
  } else if (isManualTaskJob(job)) {
@@ -5944,15 +6762,19 @@ class GithubBackend {
5944
6762
  }
5945
6763
  }));
5946
6764
  if (trigger === "mainBranch") {
5947
- const releaseJobs = (0, githubReleaseJobs_1.getGithubReleaseJobs)(config, Object.keys(jobs));
6765
+ const releaseJobs = (0, githubReleaseJobs_1.getGithubReleaseJobs)(config, Object.keys(jobs), images);
5948
6766
  Object.assign(jobs, releaseJobs.main);
5949
6767
  Object.assign(manualJobs, releaseJobs.manual);
5950
6768
  }
5951
6769
  if (Object.keys(jobs).length > 0) {
5952
6770
  workflows[`${GENERATED_FILE_PREFIX}${workflowFileName(trigger)}`] = {
5953
6771
  ...TRIGGER_WORKFLOWS[trigger],
6772
+ ...workflowPermissions(images),
5954
6773
  env: ciVariables_1.GITHUB_INJECTED_WORKFLOW_ENV,
5955
- jobs
6774
+ jobs: {
6775
+ ...makeEnsureImageGithubJobs(images),
6776
+ ...jobs
6777
+ }
5956
6778
  };
5957
6779
  }
5958
6780
  }
@@ -5964,8 +6786,12 @@ class GithubBackend {
5964
6786
  types: ["closed"]
5965
6787
  }
5966
6788
  },
6789
+ ...workflowPermissions(images),
5967
6790
  env: ciVariables_1.GITHUB_INJECTED_WORKFLOW_ENV,
5968
- jobs: reviewStopJobs
6791
+ jobs: {
6792
+ ...makeEnsureImageGithubJobs(images),
6793
+ ...reviewStopJobs
6794
+ }
5969
6795
  };
5970
6796
  }
5971
6797
  if (Object.keys(manualJobs).length > 0) {
@@ -5983,17 +6809,51 @@ class GithubBackend {
5983
6809
  }
5984
6810
  }
5985
6811
  },
6812
+ ...workflowPermissions(images),
5986
6813
  env: ciVariables_1.GITHUB_INJECTED_WORKFLOW_ENV,
5987
- jobs: Object.fromEntries(Object.entries(manualJobs).map(([id, job]) => [id, {
5988
- ...job,
5989
- if: `\${{ inputs.job == '${id}' }}`
5990
- }]))
6814
+ jobs: {
6815
+ ...makeEnsureImageGithubJobs(images),
6816
+ ...Object.fromEntries(Object.entries(manualJobs).map(([id, job]) => [id, {
6817
+ ...job,
6818
+ if: `\${{ inputs.job == '${id}' }}`
6819
+ }]))
6820
+ }
5991
6821
  };
5992
6822
  }
5993
6823
  return workflows;
5994
6824
  }
5995
6825
  }
5996
6826
  exports.GithubBackend = GithubBackend;
6827
+ /**
6828
+ * repo mode needs packages:write for pushing job images to ghcr
6829
+ */
6830
+ const workflowPermissions = images => images.mode === "repo" ? {
6831
+ permissions: {
6832
+ contents: "read",
6833
+ packages: "write"
6834
+ }
6835
+ } : {};
6836
+ /**
6837
+ * the image build jobs: always run (github has no server-side
6838
+ * changes-filter), the existence check makes unchanged runs fast.
6839
+ * Docker is native on github runners — no container, no dind.
6840
+ */
6841
+ const makeEnsureImageGithubJobs = images => Object.fromEntries(images.getEnsureJobs().map(job => {
6842
+ var _a, _b;
6843
+ return [(0, createGithubJobs_1.githubGlobalJobId)(job.name), {
6844
+ name: job.name,
6845
+ "runs-on": "ubuntu-latest",
6846
+ steps: [{
6847
+ name: "Checkout",
6848
+ uses: "actions/checkout@v4"
6849
+ }, {
6850
+ name: job.name,
6851
+ id: "main",
6852
+ 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"),
6853
+ shell: "bash"
6854
+ }]
6855
+ }];
6856
+ }));
5997
6857
  const workflowFileName = trigger => {
5998
6858
  switch (trigger) {
5999
6859
  case "mr":
@@ -6044,7 +6904,8 @@ exports.GITHUB_CI_VARIABLES = {
6044
6904
  jobToken: "CL_JOB_TOKEN",
6045
6905
  jobImage: "CL_JOB_IMAGE",
6046
6906
  registry: "CL_REGISTRY",
6047
- registryImage: "CL_REGISTRY_IMAGE"
6907
+ registryImage: "CL_REGISTRY_IMAGE",
6908
+ registryUser: "CL_REGISTRY_USER"
6048
6909
  };
6049
6910
  /**
6050
6911
  * the workflow-level env the backend injects to provide the CL_*
@@ -6054,6 +6915,7 @@ exports.GITHUB_INJECTED_WORKFLOW_ENV = {
6054
6915
  CL_JOB_TOKEN: "${{ github.token }}",
6055
6916
  CL_REGISTRY: "ghcr.io",
6056
6917
  CL_REGISTRY_IMAGE: "ghcr.io/${{ github.repository }}",
6918
+ CL_REGISTRY_USER: "${{ github.actor }}",
6057
6919
  // empty outside of pull request events
6058
6920
  CL_PR_NUMBER: "${{ github.event.number }}"
6059
6921
  };
@@ -6069,7 +6931,7 @@ exports.GITHUB_INJECTED_WORKFLOW_ENV = {
6069
6931
  Object.defineProperty(exports, "__esModule", ({
6070
6932
  value: true
6071
6933
  }));
6072
- exports.getUploadProviderIds = exports.makeGithubJob = exports.resolveGithubNeeds = exports.githubJobId = void 0;
6934
+ exports.getUploadProviderIds = exports.makeGithubJob = exports.collectSecretKinds = exports.resolveGithubNeeds = exports.githubGlobalJobId = exports.githubJobId = void 0;
6073
6935
  const lodash_es_1 = __nccwpck_require__(64284);
6074
6936
  const getInjectVarsScript_1 = __nccwpck_require__(90436);
6075
6937
  const createJobCache_1 = __nccwpck_require__(65644);
@@ -6077,6 +6939,7 @@ const getEnvironmentVariables_1 = __nccwpck_require__(85855);
6077
6939
  const utils_1 = __nccwpck_require__(20595);
6078
6940
  const gitlab_1 = __nccwpck_require__(13468);
6079
6941
  const scriptFunctions_1 = __nccwpck_require__(75759);
6942
+ const ciVariables_1 = __nccwpck_require__(25474);
6080
6943
  /**
6081
6944
  * runner variables that only make sense on gitlab runners and must not
6082
6945
  * leak into github jobs (docker-in-docker daemon config, kubernetes
@@ -6095,6 +6958,9 @@ const slug = value => {
6095
6958
  };
6096
6959
  const githubJobId = (context, jobName) => slug(`${context.type === "workspace" ? "ws-" : ""}${context.name}-${jobName}-${context.env}`);
6097
6960
  exports.githubJobId = githubJobId;
6961
+ /** id of a job without component/workspace context (e.g. image builds) */
6962
+ const githubGlobalJobId = jobName => slug(jobName);
6963
+ exports.githubGlobalJobId = githubGlobalJobId;
6098
6964
  const githubJobName = (context, jobName) => `${context.name} ${jobName} | ${context.env}`;
6099
6965
  const resolveNeed = (context, need, allJobs) => {
6100
6966
  var _a;
@@ -6116,6 +6982,12 @@ const resolveNeed = (context, need, allJobs) => {
6116
6982
  return (0, exports.githubJobId)(provider.context, jobName);
6117
6983
  };
6118
6984
  if ((0, lodash_es_1.isObject)(need)) {
6985
+ if ("global" in need && need.global) {
6986
+ return {
6987
+ id: (0, exports.githubGlobalJobId)(need.job),
6988
+ artifacts: need.artifacts
6989
+ };
6990
+ }
6119
6991
  if ("workspaceName" in need && need.workspaceName) {
6120
6992
  return {
6121
6993
  id: findWorkspaceJob(need.workspaceName, need.job),
@@ -6133,9 +7005,9 @@ const resolveNeed = (context, need, allJobs) => {
6133
7005
  artifacts: true
6134
7006
  };
6135
7007
  };
6136
- const resolveGithubNeeds = (context, job, allJobs) => {
7008
+ const resolveGithubNeeds = (context, job, allJobs, extraNeeds = []) => {
6137
7009
  var _a;
6138
- const resolved = ((_a = job.needs) !== null && _a !== void 0 ? _a : []).map(need => resolveNeed(context, need, allJobs));
7010
+ const resolved = [...extraNeeds, ...((_a = job.needs) !== null && _a !== void 0 ? _a : [])].map(need => resolveNeed(context, need, allJobs));
6139
7011
  // deduplicate by provider id, explicit entries win (they come last)
6140
7012
  return [...new Map(resolved.map(n => [n.id, n])).values()].sort((a, b) => a.id.localeCompare(b.id));
6141
7013
  };
@@ -6176,29 +7048,73 @@ const createCacheSteps = (context, job) => {
6176
7048
  };
6177
7049
  });
6178
7050
  };
6179
- const getSecretsEnv = context => {
7051
+ /**
7052
+ * how a vault-stored value reaches the job env: masked github secret,
7053
+ * or plain (unmasked) github variable for non-sensitive values — those
7054
+ * stay readable in logs and usable in environment urls
7055
+ */
7056
+ const vaultValueExpression = (name, kinds) => kinds.get(name) === "variable" ? `\${{ vars.${name} }}` : `\${{ secrets.${name} }}`;
7057
+ /**
7058
+ * the kind of every vault-stored var of all components, keyed by its
7059
+ * CL_* name — the lookup for cross-component references
7060
+ */
7061
+ const collectSecretKinds = allJobs => {
7062
+ const kinds = new Map();
7063
+ allJobs.components.forEach(({
7064
+ context
7065
+ }) => {
7066
+ var _a, _b;
7067
+ 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 : [])];
7068
+ entries.forEach(({
7069
+ key,
7070
+ kind
7071
+ }) => kinds.set((0, getEnvironmentVariables_1.getSecretVarName)(context.env, context.name, key), kind !== null && kind !== void 0 ? kind : "secret"));
7072
+ });
7073
+ return kinds;
7074
+ };
7075
+ exports.collectSecretKinds = collectSecretKinds;
7076
+ const getSecretsEnv = (context, kinds) => {
6180
7077
  var _a, _b;
6181
7078
  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 : [])];
6182
7079
  return Object.fromEntries(keys.map(({
6183
7080
  key
6184
7081
  }) => {
6185
7082
  const name = (0, getEnvironmentVariables_1.getSecretVarName)(context.env, context.name, key);
6186
- return [name, `\${{ secrets.${name} }}`];
7083
+ return [name, vaultValueExpression(name, kinds)];
6187
7084
  }));
6188
7085
  };
6189
- const makeGithubJob = (context, job, allJobs, uploadProviderIds) => {
7086
+ /**
7087
+ * every CL_* variable the job script reads must exist in the job env.
7088
+ * On gitlab all project variables are implicitly available in every
7089
+ * job — cross-component references (`${other-component:KEY}`) rely on
7090
+ * that. Github jobs only see what is injected explicitly, so the
7091
+ * script is scanned for the names it actually references.
7092
+ */
7093
+ 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)]));
7094
+ const makeGithubJob = (context, job, allJobs, uploadProviderIds, images, scripts, secretKinds = new Map()) => {
6190
7095
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
6191
7096
  const id = (0, exports.githubJobId)(context, job.name);
6192
7097
  const isComponent = context.type === "component";
6193
7098
  const createsJobEnv = !!job.environment && isComponent && (!job.environment.action || job.environment.action === "start");
6194
7099
  const skipCheckout = ((_a = job.runnerVariables) === null || _a === void 0 ? void 0 : _a.GIT_STRATEGY) === "none";
6195
- const needs = (0, exports.resolveGithubNeeds)(context, job, allJobs);
6196
- 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;
7100
+ const resolved = images.resolve(job.image);
7101
+ const needs = (0, exports.resolveGithubNeeds)(context, job, allJobs, resolved.need ? [resolved.need] : []);
7102
+ 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;
6197
7103
  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");
7104
+ // scripts live in committed files to keep workflows under github's
7105
+ // 512 KB limit. Two classes of jobs must keep their script inline:
7106
+ // jobs without a checkout have no repo files, and `${{ }}` expressions
7107
+ // are only interpolated inside the workflow file itself.
7108
+ const run = skipCheckout || runScript.includes("${{") ? runScript : scripts.add(id, runScript);
6198
7109
  const artifactPaths = (_g = (_f = job.artifacts) === null || _f === void 0 ? void 0 : _f.paths) !== null && _g !== void 0 ? _g : [];
6199
- const services = Object.fromEntries(((_h = job.services) !== null && _h !== void 0 ? _h : []).map(service => typeof service === "string" ? {
7110
+ const declaredServices = ((_h = job.services) !== null && _h !== void 0 ? _h : []).map(service => typeof service === "string" ? {
6200
7111
  name: service
6201
- } : service).filter(service => !isDindService(service.name)).map(service => [slug("alias" in service && service.alias ? service.alias : service.name), {
7112
+ } : service);
7113
+ // docker runs natively on github runners, so gitlab's docker-in-docker
7114
+ // services are dropped. The job container stays: the runner mounts the
7115
+ // host's docker socket into it, and images like docker-build carry the
7116
+ // helper scripts (ensure*Dockerfile etc.) the job scripts rely on.
7117
+ const services = Object.fromEntries(declaredServices.filter(service => !isDindService(service.name)).map(service => [slug("alias" in service && service.alias ? service.alias : service.name), {
6202
7118
  image: service.name,
6203
7119
  ...("variables" in service && service.variables ? {
6204
7120
  env: service.variables
@@ -6206,7 +7122,8 @@ const makeGithubJob = (context, job, allJobs, uploadProviderIds) => {
6206
7122
  }]));
6207
7123
  const env = {
6208
7124
  ...Object.fromEntries(Object.entries((_j = job.runnerVariables) !== null && _j !== void 0 ? _j : {}).filter(([key]) => !GITLAB_ONLY_RUNNER_VARIABLES.has(key))),
6209
- ...(isComponent ? getSecretsEnv(context) : {}),
7125
+ ...(isComponent ? getSecretsEnv(context, secretKinds) : {}),
7126
+ ...getReferencedSecretsEnv(runScript, secretKinds),
6210
7127
  CL_JOB_IMAGE: image !== null && image !== void 0 ? image : "runner"
6211
7128
  };
6212
7129
  const githubJob = {
@@ -6217,7 +7134,14 @@ const makeGithubJob = (context, job, allJobs, uploadProviderIds) => {
6217
7134
  } : {}),
6218
7135
  ...(image ? {
6219
7136
  container: {
6220
- image
7137
+ image,
7138
+ // private images in the repo's registry need credentials
7139
+ ...(resolved.fromRepoRegistry ? {
7140
+ credentials: {
7141
+ username: "${{ github.actor }}",
7142
+ password: "${{ github.token }}"
7143
+ }
7144
+ } : {})
6221
7145
  }
6222
7146
  } : {}),
6223
7147
  ...(Object.keys(services).length > 0 ? {
@@ -6244,7 +7168,7 @@ const makeGithubJob = (context, job, allJobs, uploadProviderIds) => {
6244
7168
  })), {
6245
7169
  name: job.name,
6246
7170
  id: "main",
6247
- run: runScript,
7171
+ run,
6248
7172
  shell: "bash"
6249
7173
  }, ...(uploadProviderIds.has(id) || artifactPaths.length > 0 ? artifactPaths.length > 0 ? [{
6250
7174
  name: "Upload artifacts",
@@ -6252,7 +7176,11 @@ const makeGithubJob = (context, job, allJobs, uploadProviderIds) => {
6252
7176
  with: {
6253
7177
  name: id,
6254
7178
  path: artifactPaths.join("\n"),
6255
- "if-no-files-found": "warn"
7179
+ "if-no-files-found": "warn",
7180
+ // gitlab artifacts include everything; upload-artifact
7181
+ // silently drops hidden files by default — which is
7182
+ // exactly where next.js builds live (.next)
7183
+ "include-hidden-files": true
6256
7184
  }
6257
7185
  }] : [] : [])]
6258
7186
  };
@@ -6285,36 +7213,49 @@ Object.defineProperty(exports, "__esModule", ({
6285
7213
  value: true
6286
7214
  }));
6287
7215
  exports.getGithubReleaseJobs = void 0;
6288
- const runner_1 = __nccwpck_require__(92348);
6289
- const createReleaseJob = needs => ({
6290
- name: "create release",
6291
- "runs-on": "ubuntu-latest",
6292
- container: {
6293
- image: (0, runner_1.getRunnerImage)("semantic-release")
6294
- },
6295
- // semantic-release pushes the version tag and release commit
6296
- permissions: {
6297
- contents: "write"
6298
- },
6299
- ...(needs && needs.length > 0 ? {
6300
- needs
6301
- } : {}),
6302
- env: {
6303
- GITHUB_TOKEN: "${{ github.token }}"
6304
- },
6305
- steps: [{
6306
- name: "Checkout",
6307
- uses: "actions/checkout@v4",
6308
- // semantic-release needs the full history to determine the version
6309
- with: {
6310
- "fetch-depth": 0
6311
- }
6312
- }, {
7216
+ const createGithubJobs_1 = __nccwpck_require__(14016);
7217
+ const createReleaseJob = (images, needs) => {
7218
+ const resolved = images.resolveRef({
7219
+ catladderImage: "semantic-release"
7220
+ });
7221
+ const imageNeeds = resolved.need && typeof resolved.need === "object" ? [(0, createGithubJobs_1.githubGlobalJobId)(resolved.need.job)] : [];
7222
+ const allNeeds = [...imageNeeds, ...(needs !== null && needs !== void 0 ? needs : [])];
7223
+ return {
6313
7224
  name: "create release",
6314
- run: "semanticRelease",
6315
- shell: "bash"
6316
- }]
6317
- });
7225
+ "runs-on": "ubuntu-latest",
7226
+ container: {
7227
+ image: resolved.image,
7228
+ ...(resolved.fromRepoRegistry ? {
7229
+ credentials: {
7230
+ username: "${{ github.actor }}",
7231
+ password: "${{ github.token }}"
7232
+ }
7233
+ } : {})
7234
+ },
7235
+ // semantic-release pushes the version tag and release commit
7236
+ permissions: {
7237
+ contents: "write"
7238
+ },
7239
+ ...(allNeeds.length > 0 ? {
7240
+ needs: allNeeds
7241
+ } : {}),
7242
+ env: {
7243
+ GITHUB_TOKEN: "${{ github.token }}"
7244
+ },
7245
+ steps: [{
7246
+ name: "Checkout",
7247
+ uses: "actions/checkout@v4",
7248
+ // semantic-release needs the full history to determine the version
7249
+ with: {
7250
+ "fetch-depth": 0
7251
+ }
7252
+ }, {
7253
+ name: "create release",
7254
+ run: "semanticRelease",
7255
+ shell: "bash"
7256
+ }]
7257
+ };
7258
+ };
6318
7259
  /**
6319
7260
  * the release jobs for the github backend (the counterpart of the
6320
7261
  * gitlab release jobs):
@@ -6324,22 +7265,22 @@ const createReleaseJob = needs => ({
6324
7265
  * - otherwise (manual, the default): the release job goes to the
6325
7266
  * manual tasks workflow
6326
7267
  */
6327
- const getGithubReleaseJobs = (config, mainWorkflowJobIds) => {
7268
+ const getGithubReleaseJobs = (config, mainWorkflowJobIds, images) => {
6328
7269
  var _a;
6329
7270
  if (((_a = config.releases) === null || _a === void 0 ? void 0 : _a.when) === "auto") {
6330
7271
  return {
6331
7272
  main: {
6332
- "create-release": createReleaseJob(mainWorkflowJobIds)
7273
+ "create-release": createReleaseJob(images, mainWorkflowJobIds)
6333
7274
  },
6334
7275
  manual: {
6335
- "force-create-release": createReleaseJob()
7276
+ "force-create-release": createReleaseJob(images)
6336
7277
  }
6337
7278
  };
6338
7279
  }
6339
7280
  return {
6340
7281
  main: {},
6341
7282
  manual: {
6342
- "create-release": createReleaseJob()
7283
+ "create-release": createReleaseJob(images)
6343
7284
  }
6344
7285
  };
6345
7286
  };
@@ -6377,6 +7318,64 @@ Object.defineProperty(exports, "__esModule", ({
6377
7318
  }));
6378
7319
  __exportStar(__nccwpck_require__(54038), exports);
6379
7320
  __exportStar(__nccwpck_require__(25474), exports);
7321
+ __exportStar(__nccwpck_require__(59189), exports);
7322
+
7323
+ /***/ }),
7324
+
7325
+ /***/ 59189:
7326
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
7327
+
7328
+ "use strict";
7329
+
7330
+
7331
+ Object.defineProperty(exports, "__esModule", ({
7332
+ value: true
7333
+ }));
7334
+ exports.GithubScriptFiles = exports.GITHUB_SCRIPTS_FOLDER = void 0;
7335
+ const path_1 = __nccwpck_require__(16928);
7336
+ /**
7337
+ * folder for job scripts materialized into the repository (committed,
7338
+ * like the image definitions). Owned entirely by the github backend.
7339
+ */
7340
+ exports.GITHUB_SCRIPTS_FOLDER = ".catladder-generated/github/scripts";
7341
+ /**
7342
+ * github silently ignores workflow files larger than 512 KB — no run,
7343
+ * no check suite, no error. Inlining every job's bash script blows past
7344
+ * that limit on bigger projects, so job scripts are materialized into
7345
+ * committed files and the workflow step only invokes them.
7346
+ *
7347
+ * Scripts containing `${{ ... }}` expressions must stay inline: the
7348
+ * runner only interpolates expressions in the workflow file itself,
7349
+ * never in files read at runtime.
7350
+ */
7351
+ class GithubScriptFiles {
7352
+ constructor() {
7353
+ this.files = new Map();
7354
+ }
7355
+ /**
7356
+ * materializes a job script into a committed file and returns the
7357
+ * step command invoking it
7358
+ */
7359
+ add(jobId, script) {
7360
+ const path = (0, path_1.join)(exports.GITHUB_SCRIPTS_FOLDER, `${jobId}.sh`);
7361
+ // `shell: bash` steps run with `bash -eo pipefail`; the script file
7362
+ // must fail exactly like the inline version did
7363
+ const content = `#!/usr/bin/env bash\nset -eo pipefail\n\n${script}\n`;
7364
+ const existing = this.files.get(path);
7365
+ if (existing !== undefined && existing !== content) {
7366
+ throw new Error(`conflicting scripts for job id '${jobId}'`);
7367
+ }
7368
+ this.files.set(path, content);
7369
+ return `bash ${path}`;
7370
+ }
7371
+ getGeneratedFiles() {
7372
+ return [...this.files.entries()].map(([path, content]) => ({
7373
+ path,
7374
+ content
7375
+ }));
7376
+ }
7377
+ }
7378
+ exports.GithubScriptFiles = GithubScriptFiles;
6380
7379
 
6381
7380
  /***/ }),
6382
7381
 
@@ -6431,6 +7430,7 @@ const promises_1 = __nccwpck_require__(91943);
6431
7430
  const rules_1 = __nccwpck_require__(44565);
6432
7431
  const config_1 = __nccwpck_require__(96925);
6433
7432
  const createAllJobs_1 = __nccwpck_require__(83671);
7433
+ const jobImagesPlan_1 = __nccwpck_require__(11585);
6434
7434
  const createGitlabJobs_1 = __nccwpck_require__(80512);
6435
7435
  const createGitlabPipeline_1 = __nccwpck_require__(61862);
6436
7436
  const gitlabReleaseJobs_1 = __nccwpck_require__(35531);
@@ -6450,31 +7450,38 @@ class GitlabBackend {
6450
7450
  });
6451
7451
  }
6452
7452
  async createFiles(config) {
6453
- const includes = await this.createIncludes(config);
7453
+ const images = this.createImagesPlan(config);
7454
+ const includes = await this.createIncludes(config, images);
6454
7455
  const mainFile = {
6455
7456
  path: ".gitlab-ci.yml",
6456
7457
  content: {
6457
7458
  include: includes.map(i => i.path)
6458
7459
  }
6459
7460
  };
6460
- return [mainFile, ...includes];
7461
+ return [mainFile, ...includes,
7462
+ // materialized image definitions (repo mode)
7463
+ ...images.getGeneratedFiles()];
7464
+ }
7465
+ createImagesPlan(config) {
7466
+ return new jobImagesPlan_1.JobImagesPlan((0, jobImagesPlan_1.getJobImagesMode)(config, this.type), this.type);
6461
7467
  }
6462
7468
  /**
6463
7469
  * the complete pipeline as one object (all includes merged into one),
6464
7470
  * mainly for testing purposes
6465
7471
  */
6466
7472
  async createCompletePipeline(config) {
6467
- const includes = await this.createIncludes(config);
7473
+ const includes = await this.createIncludes(config, this.createImagesPlan(config));
6468
7474
  return includes.reduce((acc, {
6469
7475
  content
6470
7476
  }) => {
6471
7477
  return {
6472
7478
  ...acc,
7479
+ // includes are always yaml objects
6473
7480
  ...content // merge all includes into one object
6474
7481
  };
6475
7482
  }, {});
6476
7483
  }
6477
- async createIncludes(config) {
7484
+ async createIncludes(config, images) {
6478
7485
  const {
6479
7486
  jobs,
6480
7487
  image,
@@ -6482,7 +7489,7 @@ class GitlabBackend {
6482
7489
  variables,
6483
7490
  workflow,
6484
7491
  ...pipelineRest
6485
- } = await this.createPipeline(config);
7492
+ } = await this.createPipeline(config, images);
6486
7493
  // we will create 1 include per component or workspace
6487
7494
  // this is for better readability in git diffs and to avoid problems with yaml files beeing too large
6488
7495
  // group by context
@@ -6515,14 +7522,14 @@ class GitlabBackend {
6515
7522
  };
6516
7523
  return [mainInclude, ...componentIncludes];
6517
7524
  }
6518
- async createPipeline(config) {
7525
+ async createPipeline(config, images) {
6519
7526
  const stages = (0, getPipelineStages_1.getPipelineStages)(config);
6520
7527
  // for all triggers create jobs and add base rules
6521
7528
  const allJobsPerTrigger = await Promise.all(config_1.ALL_PIPELINE_TRIGGERS.map(async trigger => await (0, createGitlabJobs_1.createGitlabJobs)(await (0, createAllJobs_1.createAllJobs)({
6522
7529
  config,
6523
7530
  trigger,
6524
7531
  pipelineType: this.type
6525
- }), getGitlabRulesForTrigger(trigger)))).then(j => j.flat());
7532
+ }), images, getGitlabRulesForTrigger(trigger)))).then(j => j.flat());
6526
7533
  const allWorkspaceJobs = allJobsPerTrigger.filter(j => {
6527
7534
  var _a;
6528
7535
  return ((_a = j.context) === null || _a === void 0 ? void 0 : _a.type) === "workspace";
@@ -6551,7 +7558,9 @@ class GitlabBackend {
6551
7558
  var _a;
6552
7559
  return ((_a = j.context) === null || _a === void 0 ? void 0 : _a.type) === "agent";
6553
7560
  });
6554
- const allJobs = [...allWorkspaceJobs, ...allComponentJobs, ...allGlobalJobs].reduce((acc, {
7561
+ // context-less jobs (image build jobs)
7562
+ const allContextlessJobs = allJobsPerTrigger.filter(j => j.context === null);
7563
+ const allJobs = [...allContextlessJobs, ...allWorkspaceJobs, ...allComponentJobs, ...allGlobalJobs].reduce((acc, {
6555
7564
  gitlabJob,
6556
7565
  name,
6557
7566
  context
@@ -6665,7 +7674,8 @@ exports.GITLAB_CI_VARIABLES = {
6665
7674
  jobToken: "CI_JOB_TOKEN",
6666
7675
  jobImage: "CI_JOB_IMAGE",
6667
7676
  registry: "CI_REGISTRY",
6668
- registryImage: "CI_REGISTRY_IMAGE"
7677
+ registryImage: "CI_REGISTRY_IMAGE",
7678
+ registryUser: "CI_REGISTRY_USER"
6669
7679
  };
6670
7680
 
6671
7681
  /***/ }),
@@ -6734,7 +7744,7 @@ const getFullReferencedJobNameFromWorkspace = (referencedJobName, workspaceName,
6734
7744
  });
6735
7745
  };
6736
7746
  const getJobName = need => (0, lodash_es_1.isObject)(need) ? need.job : need;
6737
- const makeGitlabJob = (context, job, allJobs, baseRules) => {
7747
+ const makeGitlabJob = (context, job, allJobs, images, baseRules) => {
6738
7748
  var _a, _b;
6739
7749
  const {
6740
7750
  environment,
@@ -6752,8 +7762,14 @@ const makeGitlabJob = (context, job, allJobs, baseRules) => {
6752
7762
  allow_failure: jobAllowFailure,
6753
7763
  cache: jobCache,
6754
7764
  caches,
7765
+ image: jobImage,
6755
7766
  ...rest
6756
7767
  } = job;
7768
+ // catladder image references resolve according to the jobImages mode;
7769
+ // in repo mode the job additionally depends on the image build job
7770
+ const resolvedImage = images.resolve(jobImage);
7771
+ const image = resolvedImage.image;
7772
+ const imageNeeds = resolvedImage.need ? [resolvedImage.need] : [];
6757
7773
  // the neutral `gate` translates to gitlab's when/allow_failure;
6758
7774
  // explicitly set gitlab fields take precedence
6759
7775
  const when = jobWhen !== null && jobWhen !== void 0 ? jobWhen : gate ? gate === "manual" ? "manual" : "on_success" : undefined;
@@ -6762,7 +7778,7 @@ const makeGitlabJob = (context, job, allJobs, baseRules) => {
6762
7778
  // an explicitly set gitlab `cache` takes precedence
6763
7779
  const cache = jobCache !== null && jobCache !== void 0 ? jobCache : caches && context.type !== "agent" ? (0, createJobCache_1.createJobCacheFromCacheConfigs)(context, caches) : undefined;
6764
7780
  const stage = envMode === "stagePerEnv" && context.type !== "agent" ? `${job.stage} ${context.env}` : job.stage;
6765
- const deduplicatedGitlabNeeds = getGitlabNeeds(context, job, allJobs);
7781
+ const deduplicatedGitlabNeeds = getGitlabNeeds(context, job, allJobs, imageNeeds);
6766
7782
  const fullJobName = getFullJobName({
6767
7783
  type: context.type,
6768
7784
  name,
@@ -6791,6 +7807,7 @@ const makeGitlabJob = (context, job, allJobs, baseRules) => {
6791
7807
  retry: defaults_1.BASE_RETRY,
6792
7808
  interruptible: true,
6793
7809
  ...rest,
7810
+ image,
6794
7811
  allow_failure,
6795
7812
  cache: cache ? (0, cache_1.addCacheFallback)(cache, context) : undefined,
6796
7813
  rules: rules.length > 0 ? rules : undefined,
@@ -6866,13 +7883,13 @@ const addGitlabEnvironment = (context, catladderJobEnvironment, job, allJobs) =>
6866
7883
  script: [...((_b = job.script) !== null && _b !== void 0 ? _b : []), ...(createsJobEnv ? scriptToAdd : [])]
6867
7884
  };
6868
7885
  };
6869
- const createGitlabJobs = async (allJobs, baseRules) => {
6870
- return [...allJobs.workspaces, ...allJobs.components, ...allJobs.agents].flatMap(({
7886
+ const createGitlabJobs = async (allJobs, images, baseRules) => {
7887
+ const contextualJobs = [...allJobs.workspaces, ...allJobs.components, ...allJobs.agents].flatMap(({
6871
7888
  context,
6872
7889
  jobs
6873
7890
  }) => {
6874
7891
  return jobs.map(job => {
6875
- const [fullJobName, gitlabJob] = (0, exports.makeGitlabJob)(context, job, allJobs, baseRules);
7892
+ const [fullJobName, gitlabJob] = (0, exports.makeGitlabJob)(context, job, allJobs, images, baseRules);
6876
7893
  return {
6877
7894
  name: fullJobName,
6878
7895
  gitlabJob,
@@ -6880,13 +7897,66 @@ const createGitlabJobs = async (allJobs, baseRules) => {
6880
7897
  };
6881
7898
  });
6882
7899
  });
7900
+ // image build jobs (repo mode): no context, plain names, their
7901
+ // rules:changes combined with the trigger rules
7902
+ const imageJobs = images.getEnsureJobs().map(job => ({
7903
+ name: job.name,
7904
+ gitlabJob: makeEnsureImageGitlabJob(job, baseRules),
7905
+ context: null
7906
+ }));
7907
+ return [...imageJobs, ...contextualJobs];
6883
7908
  };
6884
7909
  exports.createGitlabJobs = createGitlabJobs;
6885
- function getGitlabNeeds(context, job, allJobs) {
7910
+ /**
7911
+ * lowers an image build job: only runs when the (generated) image
7912
+ * definition changed, within the trigger's base rules
7913
+ */
7914
+ const makeEnsureImageGitlabJob = (job, baseRules) => {
7915
+ var _a, _b, _c, _d;
7916
+ const changes = (_b = (_a = job.rules) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.changes;
7917
+ const rules = (baseRules !== null && baseRules !== void 0 ? baseRules : []).map(baseRule => baseRule.when === "never" ? baseRule : {
7918
+ ...baseRule,
7919
+ changes
7920
+ });
7921
+ return (0, removeUndefined_1.removeUndefined)({
7922
+ retry: defaults_1.BASE_RETRY,
7923
+ interruptible: true,
7924
+ image: job.image,
7925
+ services: job.services,
7926
+ rules: rules.length > 0 ? rules : [{
7927
+ changes
7928
+ }],
7929
+ variables: job.runnerVariables,
7930
+ script: (_d = (_c = job.script) === null || _c === void 0 ? void 0 : _c.filter(utils_1.notNil)) !== null && _d !== void 0 ? _d : [],
7931
+ stage: job.stage,
7932
+ needs: []
7933
+ });
7934
+ };
7935
+ function getGitlabNeeds(context, job, allJobs, extraNeeds = []) {
6886
7936
  var _a;
6887
- 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);
7937
+ const jobWithExtraNeeds = extraNeeds.length > 0 ? {
7938
+ ...job,
7939
+ needs: [...extraNeeds, ...((_a = job.needs) !== null && _a !== void 0 ? _a : [])]
7940
+ } : job;
7941
+ const needs = context.type === "workspace" ? getGitlabNeedsForWorkspaceJob(context, jobWithExtraNeeds, allJobs) : context.type === "agent" ? mapAgentNeeds(jobWithExtraNeeds.needs) : getGitlabNeedsForComponentJob(context, jobWithExtraNeeds, allJobs);
6888
7942
  return needs ? deduplicateNeeds(needs) : undefined;
6889
7943
  }
7944
+ /**
7945
+ * agent jobs use their needs as-is; global needs (image build jobs)
7946
+ * keep their plain name and optional flag
7947
+ */
7948
+ function mapAgentNeeds(needs) {
7949
+ if (!needs) {
7950
+ return null;
7951
+ }
7952
+ return needs.map(n => (0, lodash_es_1.isObject)(n) && "global" in n && n.global ? {
7953
+ job: n.job,
7954
+ artifacts: n.artifacts,
7955
+ ...(n.optional ? {
7956
+ optional: true
7957
+ } : {})
7958
+ } : n);
7959
+ }
6890
7960
  function deduplicateNeeds(needs) {
6891
7961
  return needs ? [...new Map(needs.map(n => [(0, lodash_es_1.isObject)(n) ? n.job : n, n])).values()] : undefined;
6892
7962
  }
@@ -6895,11 +7965,19 @@ function getGitlabNeedsForComponentJob(context, {
6895
7965
  }, allJobs) {
6896
7966
  return (needs !== null && needs !== void 0 ? needs : []).map(n => {
6897
7967
  var _a;
6898
- return (0, lodash_es_1.isObject)(n) ? "workspaceName" in n ? {
7968
+ return (0, lodash_es_1.isObject)(n) ? "global" in n && n.global ?
7969
+ // global jobs (e.g. image builds) keep their plain name
7970
+ {
7971
+ job: n.job,
7972
+ artifacts: n.artifacts,
7973
+ ...(n.optional ? {
7974
+ optional: true
7975
+ } : {})
7976
+ } : "workspaceName" in n && n.workspaceName ? {
6899
7977
  job: getFullReferencedJobNameFromWorkspace(n.job, n.workspaceName, context.env, allJobs),
6900
7978
  artifacts: n.artifacts
6901
7979
  } : {
6902
- job: getFullReferencedJobNameFromComponent(n.job, (_a = n.componentName) !== null && _a !== void 0 ? _a : context.name, context.env, allJobs),
7980
+ job: getFullReferencedJobNameFromComponent(n.job, "componentName" in n ? (_a = n.componentName) !== null && _a !== void 0 ? _a : context.name : context.name, context.env, allJobs),
6903
7981
  artifacts: n.artifacts
6904
7982
  } : getFullReferencedJobNameFromComponent(n, context.name, context.env, allJobs);
6905
7983
  }) // sort in a predictable manner for snapshot tests
@@ -6912,7 +7990,13 @@ function getGitlabNeedsForComponentJob(context, {
6912
7990
  function getGitlabNeedsForWorkspaceJob(context, {
6913
7991
  needs
6914
7992
  }, allJobs) {
6915
- return (needs !== null && needs !== void 0 ? needs : []).map(n => (0, lodash_es_1.isObject)(n) ? {
7993
+ return (needs !== null && needs !== void 0 ? needs : []).map(n => (0, lodash_es_1.isObject)(n) ? "global" in n && n.global ? {
7994
+ job: n.job,
7995
+ artifacts: n.artifacts,
7996
+ ...(n.optional ? {
7997
+ optional: true
7998
+ } : {})
7999
+ } : {
6916
8000
  job: getFullReferencedJobNameFromWorkspace(n.job, "workspaceName" in n && n.workspaceName ? n.workspaceName : context.name, context.env, allJobs),
6917
8001
  artifacts: n.artifacts
6918
8002
  } : getFullReferencedJobNameFromWorkspace(n, context.name, context.env, allJobs)) // sort in a predictable manner for snapshot tests
@@ -6941,7 +8025,7 @@ const createGitlabPipelineWithDefaults = ({
6941
8025
  ...config
6942
8026
  }) => {
6943
8027
  return {
6944
- image: image !== null && image !== void 0 ? image : (0, runner_1.getRunnerImage)("jobs-default"),
8028
+ image: image !== null && image !== void 0 ? image : (0, runner_1.getCentralRunnerImageUrl)("jobs-default"),
6945
8029
  // default image
6946
8030
  variables: {
6947
8031
  FF_USE_FASTZIP: "true",
@@ -7044,7 +8128,7 @@ const getGitlabReleaseJobs = config => {
7044
8128
  return {
7045
8129
  ["create release"]: {
7046
8130
  stage: "release",
7047
- image: (0, runner_1.getRunnerImage)("semantic-release"),
8131
+ image: (0, runner_1.getCentralRunnerImageUrl)("semantic-release"),
7048
8132
  script: ["semanticRelease"],
7049
8133
  after_script: [EXPIRED_TOKEN_HELP],
7050
8134
  rules: [...baseReleaseRules, {
@@ -7057,7 +8141,7 @@ const getGitlabReleaseJobs = config => {
7057
8141
  },
7058
8142
  ["⚠️ force create release"]: {
7059
8143
  stage: "release",
7060
- image: (0, runner_1.getRunnerImage)("semantic-release"),
8144
+ image: (0, runner_1.getCentralRunnerImageUrl)("semantic-release"),
7061
8145
  script: ["semanticRelease"],
7062
8146
  after_script: [EXPIRED_TOKEN_HELP],
7063
8147
  needs: [],
@@ -7163,7 +8247,7 @@ var __exportStar = this && this.__exportStar || function (m, exports) {
7163
8247
  Object.defineProperty(exports, "__esModule", ({
7164
8248
  value: true
7165
8249
  }));
7166
- exports.getEnabledPipelineTypes = exports.getPipelineBackend = void 0;
8250
+ exports.getEnabledPipelineTypes = exports.getVaultConfig = exports.getPipelineGitRemote = exports.getPipelineOptions = exports.getPipelineBackend = void 0;
7167
8251
  const github_1 = __nccwpck_require__(99635);
7168
8252
  const gitlab_1 = __nccwpck_require__(8475);
7169
8253
  __exportStar(__nccwpck_require__(92474), exports);
@@ -7181,6 +8265,38 @@ const getPipelineBackend = type => {
7181
8265
  return createBackend();
7182
8266
  };
7183
8267
  exports.getPipelineBackend = getPipelineBackend;
8268
+ /**
8269
+ * the options of an enabled pipeline type (empty object when enabled
8270
+ * with `true`)
8271
+ */
8272
+ const getPipelineOptions = (config, type) => {
8273
+ var _a;
8274
+ const value = (_a = config.pipelines) === null || _a === void 0 ? void 0 : _a[type];
8275
+ return typeof value === "object" ? value : {};
8276
+ };
8277
+ exports.getPipelineOptions = getPipelineOptions;
8278
+ /**
8279
+ * the git remote pointing to the repository of this pipeline type's CI
8280
+ * system (e.g. during a migration, gitlab may be `origin` and github a
8281
+ * second remote)
8282
+ */
8283
+ const getPipelineGitRemote = (config, type) => {
8284
+ var _a;
8285
+ return (_a = (0, exports.getPipelineOptions)(config, type).gitRemote) !== null && _a !== void 0 ? _a : "origin";
8286
+ };
8287
+ exports.getPipelineGitRemote = getPipelineGitRemote;
8288
+ /**
8289
+ * the normalized secrets vault of a config (string shorthands resolved
8290
+ * to their object form; defaults to the legacy gitlab store)
8291
+ */
8292
+ const getVaultConfig = config => {
8293
+ var _a, _b;
8294
+ const vault = (_b = (_a = config.secrets) === null || _a === void 0 ? void 0 : _a.vault) !== null && _b !== void 0 ? _b : "gitlab";
8295
+ return typeof vault === "string" ? {
8296
+ type: vault
8297
+ } : vault;
8298
+ };
8299
+ exports.getVaultConfig = getVaultConfig;
7184
8300
  /**
7185
8301
  * the pipeline types enabled for this config. Multiple types can be
7186
8302
  * enabled at once (parallel generation, e.g. during a CI migration).
@@ -8768,7 +9884,10 @@ const getYarnInstallCommand = async context => {
8768
9884
  // inline builds make debugging easier as it prints it out in the logs, instead of writing it in temp files
8769
9885
  return `yarn install --immutable --inline-builds`;
8770
9886
  };
8771
- 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"]);
9887
+ const ensureNodeVersion = context => (0, gitlab_1.collapseableSection)("nodeinstall", "Ensure node version")([
9888
+ // github actions overrides HOME in container jobs, so also look
9889
+ // for nvm where the catladder images install it (/root/.nvm)
9890
+ '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"]);
8772
9891
  exports.ensureNodeVersion = ensureNodeVersion;
8773
9892
  const getYarnInstall = async (context, options) => {
8774
9893
  const postInstall = context.type !== "workspace" && context.build.type !== "disabled" && "postInstall" in context.build.config ? context.build.config.postInstall : null;
@@ -9147,7 +10266,7 @@ Object.defineProperty(exports, "__esModule", ({
9147
10266
  value: true
9148
10267
  }));
9149
10268
  exports.DOCKER_REGISTRY = exports.PIPELINE_IMAGE_TAG = void 0;
9150
- exports.PIPELINE_IMAGE_TAG = "next-4e7c6848" || 0;
10269
+ exports.PIPELINE_IMAGE_TAG = "next-6a4450e0" || 0;
9151
10270
  exports.DOCKER_REGISTRY = "git.panter.ch:5001/catladder/catladder" || 0;
9152
10271
 
9153
10272
  /***/ }),
@@ -9559,7 +10678,7 @@ const getBasePredefinedVariables = ctx => {
9559
10678
  };
9560
10679
  };
9561
10680
  const getEnvironmentVariables = async (ctx, options = {}) => {
9562
- var _a, _b, _c, _d, _e, _f, _g, _h;
10681
+ var _a, _b, _c, _d, _e, _f, _g;
9563
10682
  const environmentContext = (0, getEnvironmentContext_1.getEnvironmentContext)(ctx);
9564
10683
  const {
9565
10684
  config,
@@ -9616,13 +10735,13 @@ const getEnvironmentVariables = async (ctx, options = {}) => {
9616
10735
  }
9617
10736
  const publicEnvVarsRaw = (_e = (_d = envConfigRaw.vars) === null || _d === void 0 ? void 0 : _d.public) !== null && _e !== void 0 ? _e : {};
9618
10737
  const additionalSecretKeys = deployConfigRaw ? deploy_1.DEPLOY_TYPES[deployConfigRaw.type].additionalSecretKeys(environmentContext) : [];
9619
- const secretEnvVarKeys = [...(0, envVars_1.stringListToSecreteEnvVarList)((_g = (_f = envConfigRaw.vars) === null || _f === void 0 ? void 0 : _f.secret) !== null && _g !== void 0 ? _g : []), ...additionalSecretKeys];
10738
+ const secretEnvVarKeys = [...(0, envVars_1.normalizeSecretVarsConfig)((_f = envConfigRaw.vars) === null || _f === void 0 ? void 0 : _f.secret), ...additionalSecretKeys];
9620
10739
  const secretEnvVars = (0, envVars_1.makeSecretEnvVarMapping)(env, componentName, secretEnvVarKeys);
9621
10740
  const publicEnvVarsRawSanitized = (0, envVars_1.stringifyValues)(publicEnvVarsRaw);
9622
10741
  const publicEnvVarsUnresolved = Object.fromEntries(Object.entries(publicEnvVarsRawSanitized).map(([key, value]) => [key, (0, VariableValueContainingReferences_1.createVariableValueContainingReferencesFromString)(value, {
9623
10742
  componentName: ctx.componentName
9624
10743
  })]));
9625
- const publicEnvVars = ((_h = options.shouldResolveReferences) !== null && _h !== void 0 ? _h : true) ? await (0, resolveAllReferences_1.resolveAllReferences)(publicEnvVarsUnresolved, async otherComponentName => {
10744
+ const publicEnvVars = ((_g = options.shouldResolveReferences) !== null && _g !== void 0 ? _g : true) ? await (0, resolveAllReferences_1.resolveAllReferences)(publicEnvVarsUnresolved, async otherComponentName => {
9626
10745
  const {
9627
10746
  envVars: otherEnvVars
9628
10747
  } = await (0, exports.getEnvironmentVariables)({
@@ -9782,7 +10901,7 @@ const envVars_1 = __nccwpck_require__(94946);
9782
10901
  * @returns
9783
10902
  */
9784
10903
  const transformJobOnlyVars = async (env, componentName, vars) => {
9785
- var _a, _b;
10904
+ var _a;
9786
10905
  if (!vars) {
9787
10906
  return {
9788
10907
  envVars: {},
@@ -9790,7 +10909,7 @@ const transformJobOnlyVars = async (env, componentName, vars) => {
9790
10909
  };
9791
10910
  }
9792
10911
  const publicVars = (0, envVars_1.stringifyValues)((_a = vars === null || vars === void 0 ? void 0 : vars.public) !== null && _a !== void 0 ? _a : {});
9793
- const secretEnvVarKeys = (0, envVars_1.stringListToSecreteEnvVarList)((_b = vars === null || vars === void 0 ? void 0 : vars.secret) !== null && _b !== void 0 ? _b : []);
10912
+ const secretEnvVarKeys = (0, envVars_1.normalizeSecretVarsConfig)(vars === null || vars === void 0 ? void 0 : vars.secret);
9794
10913
  const deployJobOnlySecretEnvVars = (vars === null || vars === void 0 ? void 0 : vars.secret) ? (0, envVars_1.makeSecretEnvVarMapping)(env, componentName, secretEnvVarKeys) : {};
9795
10914
  const envVars = {
9796
10915
  ...deployJobOnlySecretEnvVars,
@@ -9814,7 +10933,7 @@ exports.transformJobOnlyVars = transformJobOnlyVars;
9814
10933
  Object.defineProperty(exports, "__esModule", ({
9815
10934
  value: true
9816
10935
  }));
9817
- exports.makeSecretEnvVarMapping = exports.stringListToSecreteEnvVarList = exports.stringifyValues = void 0;
10936
+ exports.makeSecretEnvVarMapping = exports.normalizeSecretVarsConfig = exports.stringListToSecreteEnvVarList = exports.stringifyValues = void 0;
9818
10937
  const lodash_es_1 = __nccwpck_require__(64284);
9819
10938
  const getEnvironmentVariables_1 = __nccwpck_require__(85855);
9820
10939
  const BashExpression_1 = __nccwpck_require__(25354);
@@ -9824,6 +10943,27 @@ const stringListToSecreteEnvVarList = keys => keys.map(key => ({
9824
10943
  key
9825
10944
  }));
9826
10945
  exports.stringListToSecreteEnvVarList = stringListToSecreteEnvVarList;
10946
+ /**
10947
+ * normalizes both config forms of vault-stored env vars: the deprecated
10948
+ * plain string[] (all keys secret) and the record form carrying a kind
10949
+ * (or config object) per key
10950
+ */
10951
+ const normalizeSecretVarsConfig = secret => {
10952
+ if (!secret) {
10953
+ return [];
10954
+ }
10955
+ if (Array.isArray(secret)) {
10956
+ return (0, exports.stringListToSecreteEnvVarList)(secret);
10957
+ }
10958
+ return Object.entries(secret).map(([key, config]) => typeof config === "string" ? {
10959
+ key,
10960
+ kind: config
10961
+ } : {
10962
+ key,
10963
+ ...config
10964
+ });
10965
+ };
10966
+ exports.normalizeSecretVarsConfig = normalizeSecretVarsConfig;
9827
10967
  const makeSecretEnvVarMapping = (env, componentName, secretEnvVars) => {
9828
10968
  return Object.fromEntries(secretEnvVars.map(({
9829
10969
  key
@@ -9833,6 +10973,330 @@ exports.makeSecretEnvVarMapping = makeSecretEnvVarMapping;
9833
10973
 
9834
10974
  /***/ }),
9835
10975
 
10976
+ /***/ 53231:
10977
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
10978
+
10979
+ "use strict";
10980
+
10981
+
10982
+ Object.defineProperty(exports, "__esModule", ({
10983
+ value: true
10984
+ }));
10985
+ exports.computeCustomImageHash = void 0;
10986
+ const crypto_1 = __nccwpck_require__(76982);
10987
+ const fs_1 = __nccwpck_require__(79896);
10988
+ const path_1 = __nccwpck_require__(16928);
10989
+ const IGNORED_FILES = new Set([".DS_Store", "Thumbs.db"]);
10990
+ const isIgnoredFile = path => {
10991
+ var _a;
10992
+ return IGNORED_FILES.has((_a = path.split("/").pop()) !== null && _a !== void 0 ? _a : "");
10993
+ };
10994
+ function listFilesRecursive(dir) {
10995
+ const resolved = (0, path_1.resolve)(dir);
10996
+ const entries = (0, fs_1.readdirSync)(resolved, {
10997
+ recursive: true,
10998
+ encoding: "utf-8"
10999
+ });
11000
+ return entries.filter(entry => {
11001
+ if (isIgnoredFile(entry)) {
11002
+ // OS junk files exist locally but not in CI checkouts — they
11003
+ // must never influence the content hash
11004
+ return false;
11005
+ }
11006
+ const fullPath = (0, path_1.join)(resolved, entry);
11007
+ return (0, fs_1.statSync)(fullPath).isFile();
11008
+ }).sort();
11009
+ }
11010
+ function computeCustomImageHash(config) {
11011
+ var _a;
11012
+ const hash = (0, crypto_1.createHash)("sha256");
11013
+ const watchedPaths = [];
11014
+ // Hash all files in the dir (includes the Dockerfile and any context files)
11015
+ const dirFiles = listFilesRecursive(config.dir);
11016
+ for (const file of dirFiles) {
11017
+ const fullPath = (0, path_1.join)((0, path_1.resolve)(config.dir), file);
11018
+ hash.update(file); // include relative path for disambiguation
11019
+ hash.update((0, fs_1.readFileSync)(fullPath));
11020
+ }
11021
+ watchedPaths.push(`${config.dir}/**/*`);
11022
+ // extra dirs (e.g. included base image definitions)
11023
+ for (const extraDir of (_a = config.extraDirs) !== null && _a !== void 0 ? _a : []) {
11024
+ for (const file of listFilesRecursive(extraDir)) {
11025
+ hash.update(file);
11026
+ hash.update((0, fs_1.readFileSync)((0, path_1.join)((0, path_1.resolve)(extraDir), file)));
11027
+ }
11028
+ watchedPaths.push(`${extraDir}/**/*`);
11029
+ }
11030
+ // Hash extra paths (for files outside the dir)
11031
+ if (config.hashExtraPaths) {
11032
+ // Extra paths are literal file paths (not globs) for simplicity
11033
+ const extraFiles = [...config.hashExtraPaths].sort();
11034
+ for (const file of extraFiles) {
11035
+ hash.update(file);
11036
+ hash.update((0, fs_1.readFileSync)(file));
11037
+ }
11038
+ watchedPaths.push(...config.hashExtraPaths);
11039
+ }
11040
+ // Hash build args (sorted keys for determinism)
11041
+ if (config.buildArgs) {
11042
+ const sorted = Object.entries(config.buildArgs).sort(([a], [b]) => a.localeCompare(b));
11043
+ hash.update(JSON.stringify(sorted));
11044
+ }
11045
+ return {
11046
+ hash: hash.digest("hex").slice(0, 12),
11047
+ watchedPaths
11048
+ };
11049
+ }
11050
+ exports.computeCustomImageHash = computeCustomImageHash;
11051
+
11052
+ /***/ }),
11053
+
11054
+ /***/ 11585:
11055
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
11056
+
11057
+ "use strict";
11058
+
11059
+
11060
+ Object.defineProperty(exports, "__esModule", ({
11061
+ value: true
11062
+ }));
11063
+ exports.JobImagesPlan = exports.getJobImagesMode = exports.GENERATED_IMAGES_FOLDER = void 0;
11064
+ const fs_1 = __nccwpck_require__(79896);
11065
+ const path_1 = __nccwpck_require__(16928);
11066
+ const ciVariables_1 = __nccwpck_require__(14489);
11067
+ const docker_1 = __nccwpck_require__(12764);
11068
+ const constants_1 = __nccwpck_require__(43480);
11069
+ const runner_1 = __nccwpck_require__(92348);
11070
+ const gitlab_1 = __nccwpck_require__(13468);
11071
+ const hash_1 = __nccwpck_require__(53231);
11072
+ const shippedImages_1 = __nccwpck_require__(77734);
11073
+ exports.GENERATED_IMAGES_FOLDER = ".catladder-generated/images";
11074
+ /**
11075
+ * the official docker cli image used to build the job images — NOT a
11076
+ * catladder image, so building images never depends on one (bootstrap)
11077
+ */
11078
+ const BUILDER_IMAGE = "docker.io/docker:29.5.1";
11079
+ const getJobImagesMode = (config, pipelineType) => {
11080
+ var _a;
11081
+ // the central registry is not reachable from github runners
11082
+ if (pipelineType === "github" && config.jobImages === "central") {
11083
+ 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.`);
11084
+ }
11085
+ return (_a = config.jobImages) !== null && _a !== void 0 ? _a : pipelineType === "github" ? "repo" : "central";
11086
+ };
11087
+ exports.getJobImagesMode = getJobImagesMode;
11088
+ const ensureJobName = name => `🐳 job image ${name}`;
11089
+ /**
11090
+ * plans the catladder job images of one backend run: resolves image
11091
+ * references, and (in repo mode) collects the image build jobs and the
11092
+ * image definitions to materialize into the generated files.
11093
+ *
11094
+ * Reference-driven: only images actually used by a job are built.
11095
+ */
11096
+ class JobImagesPlan {
11097
+ constructor(mode, pipelineType) {
11098
+ this.mode = mode;
11099
+ this.pipelineType = pipelineType;
11100
+ this.used = new Map();
11101
+ }
11102
+ /**
11103
+ * resolves a job image (marker or concrete) to a concrete image url
11104
+ * plus the build job dependency in repo mode
11105
+ */
11106
+ resolve(image) {
11107
+ if (!(0, runner_1.isCatladderImageRef)(image)) {
11108
+ return {
11109
+ image
11110
+ };
11111
+ }
11112
+ return this.resolveRef(image);
11113
+ }
11114
+ resolveRef(ref) {
11115
+ const name = ref.catladderImage;
11116
+ if (this.mode === "central" || shippedImages_1.CENTRAL_ONLY_IMAGES.has(name)) {
11117
+ return {
11118
+ image: (0, runner_1.getCentralRunnerImageUrl)(name)
11119
+ };
11120
+ }
11121
+ const {
11122
+ hash
11123
+ } = this.use(name);
11124
+ return {
11125
+ image: `${this.registryImageRef()}/catladder/${name}:${hash}`,
11126
+ fromRepoRegistry: true,
11127
+ need: {
11128
+ job: ensureJobName(name),
11129
+ artifacts: false,
11130
+ optional: true,
11131
+ global: true
11132
+ }
11133
+ };
11134
+ }
11135
+ /**
11136
+ * the registry image prefix. On github, container images can't use
11137
+ * runtime env vars — the expression form is required.
11138
+ */
11139
+ registryImageRef() {
11140
+ return this.pipelineType === "github" ? "ghcr.io/${{ github.repository }}" : `${(0, ciVariables_1.getCiVariable)({
11141
+ pipelineType: this.pipelineType
11142
+ }, "registryImage")}`;
11143
+ }
11144
+ use(name) {
11145
+ var _a;
11146
+ const existing = this.used.get(name);
11147
+ if (existing) {
11148
+ return existing;
11149
+ }
11150
+ const dependencies = (_a = shippedImages_1.RUNNER_IMAGE_DEPENDENCIES[name]) !== null && _a !== void 0 ? _a : [];
11151
+ // dependencies are materialized (and hashed) as part of this image,
11152
+ // but also usable standalone
11153
+ dependencies.forEach(dependency => this.use(dependency));
11154
+ const {
11155
+ hash
11156
+ } = (0, hash_1.computeCustomImageHash)({
11157
+ dir: (0, shippedImages_1.getShippedImageDir)(name),
11158
+ extraDirs: dependencies.map(dependency => (0, shippedImages_1.getShippedImageDir)(dependency))
11159
+ });
11160
+ const entry = {
11161
+ hash
11162
+ };
11163
+ this.used.set(name, entry);
11164
+ return entry;
11165
+ }
11166
+ /**
11167
+ * the image build jobs for all used images (repo mode; empty in
11168
+ * central mode)
11169
+ */
11170
+ getEnsureJobs() {
11171
+ return [...this.used.entries()].map(([name, {
11172
+ hash
11173
+ }]) => this.createEnsureJob(name, hash));
11174
+ }
11175
+ createEnsureJob(name, hash) {
11176
+ var _a;
11177
+ const ctx = {
11178
+ pipelineType: this.pipelineType
11179
+ };
11180
+ const registryUser = (0, ciVariables_1.getCiVariable)(ctx, "registryUser");
11181
+ const jobToken = (0, ciVariables_1.getCiVariable)(ctx, "jobToken");
11182
+ const registry = (0, ciVariables_1.getCiVariable)(ctx, "registry");
11183
+ const imageRef = `${this.registryImageRef()}/catladder/${name}:${hash}`;
11184
+ const watchedDirs = [name, ...((_a = shippedImages_1.RUNNER_IMAGE_DEPENDENCIES[name]) !== null && _a !== void 0 ? _a : [])];
11185
+ const {
11186
+ services,
11187
+ runnerVariables
11188
+ } = (0, docker_1.getDockerJobBaseProps)();
11189
+ return {
11190
+ name: ensureJobName(name),
11191
+ stage: "setup",
11192
+ image: BUILDER_IMAGE,
11193
+ services,
11194
+ runnerVariables,
11195
+ needs: [],
11196
+ // gitlab: only run when the image definition changed; github has
11197
+ // no equivalent and always runs (the existence check is fast)
11198
+ rules: [{
11199
+ changes: watchedDirs.map(dir => `${exports.GENERATED_IMAGES_FOLDER}/${dir}/**/*`)
11200
+ }],
11201
+ variables: {},
11202
+ 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}`])]
11203
+ };
11204
+ }
11205
+ /**
11206
+ * the image definitions of all used images, materialized into the
11207
+ * generated files (so pipelines build from committed, reviewable
11208
+ * files and gitlab's rules:changes can watch them)
11209
+ */
11210
+ getGeneratedFiles() {
11211
+ return [...this.used.keys()].flatMap(name => {
11212
+ const dir = (0, shippedImages_1.getShippedImageDir)(name);
11213
+ return listFilesRecursive(dir).map(file => ({
11214
+ path: (0, path_1.join)(exports.GENERATED_IMAGES_FOLDER, name, file),
11215
+ content: (0, fs_1.readFileSync)((0, path_1.join)(dir, file), "utf-8")
11216
+ }));
11217
+ });
11218
+ }
11219
+ }
11220
+ exports.JobImagesPlan = JobImagesPlan;
11221
+ const IGNORED_FILES = new Set([".DS_Store", "Thumbs.db"]);
11222
+ const listFilesRecursive = dir => (0, fs_1.readdirSync)(dir, {
11223
+ recursive: true,
11224
+ encoding: "utf-8"
11225
+ }).filter(entry => {
11226
+ var _a;
11227
+ // OS junk files exist locally but not in CI checkouts
11228
+ return !IGNORED_FILES.has((_a = entry.split("/").pop()) !== null && _a !== void 0 ? _a : "") && (0, fs_1.statSync)((0, path_1.join)(dir, entry)).isFile();
11229
+ }).sort();
11230
+
11231
+ /***/ }),
11232
+
11233
+ /***/ 77734:
11234
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
11235
+
11236
+ "use strict";
11237
+
11238
+
11239
+ Object.defineProperty(exports, "__esModule", ({
11240
+ value: true
11241
+ }));
11242
+ exports.getShippedImageDir = exports.getShippedImagesDir = exports.RUNNER_IMAGE_BUILD_CONTEXT = exports.CENTRAL_ONLY_IMAGES = exports.RUNNER_IMAGE_DEPENDENCIES = void 0;
11243
+ const fs_1 = __nccwpck_require__(79896);
11244
+ const path_1 = __nccwpck_require__(16928);
11245
+ /**
11246
+ * images whose Dockerfile INCLUDEs another image's Dockerfile
11247
+ * (dockerfile-x/dockerfile-plus, resolved against the images root as
11248
+ * build context)
11249
+ */
11250
+ exports.RUNNER_IMAGE_DEPENDENCIES = {
11251
+ "jobs-meteor": ["jobs-default"],
11252
+ "jobs-testing-chrome": ["jobs-default"]
11253
+ };
11254
+ /**
11255
+ * the docker build context of each image, mirroring the central image
11256
+ * builds: the jobs family builds from the images root (their
11257
+ * Dockerfiles INCLUDE sibling images), the others from their own dir
11258
+ * (their COPY paths are relative to it)
11259
+ */
11260
+ /**
11261
+ * images that cannot be built from the shipped definitions and always
11262
+ * come from the central registry: semantic-release bakes catladder's
11263
+ * own cli sources into the image (repo-root build context).
11264
+ * TODO: make the semantic-release image self-contained.
11265
+ */
11266
+ exports.CENTRAL_ONLY_IMAGES = new Set(["semantic-release"]);
11267
+ exports.RUNNER_IMAGE_BUILD_CONTEXT = {
11268
+ "jobs-default": "root",
11269
+ "jobs-meteor": "root",
11270
+ "jobs-testing-chrome": "root",
11271
+ kubernetes: "self",
11272
+ "docker-build": "self",
11273
+ gcloud: "self",
11274
+ "semantic-release": "self",
11275
+ "agent-claude": "self"
11276
+ };
11277
+ /**
11278
+ * the directory containing the image definitions shipped with the
11279
+ * package (copied into dist at build time; the repository layout is
11280
+ * the fallback for development and tests)
11281
+ */
11282
+ const getShippedImagesDir = () => {
11283
+ const candidates = [
11284
+ // built package: dist/customImages -> dist/runner-images
11285
+ (0, path_1.join)(__dirname, "..", "runner-images"),
11286
+ // repository: pipeline/src/customImages -> <root>/runner-images
11287
+ (0, path_1.join)(__dirname, "..", "..", "..", "runner-images")];
11288
+ const found = candidates.find(candidate => (0, fs_1.existsSync)(candidate));
11289
+ if (!found) {
11290
+ throw new Error("shipped job image definitions not found");
11291
+ }
11292
+ return found;
11293
+ };
11294
+ exports.getShippedImagesDir = getShippedImagesDir;
11295
+ const getShippedImageDir = name => (0, path_1.join)((0, exports.getShippedImagesDir)(), name);
11296
+ exports.getShippedImageDir = getShippedImageDir;
11297
+
11298
+ /***/ }),
11299
+
9836
11300
  /***/ 84628:
9837
11301
  /***/ ((__unused_webpack_module, exports) => {
9838
11302
 
@@ -11052,13 +12516,18 @@ exports.GCLOUD_RUN_DEPLOY_TYPE = {
11052
12516
  }
11053
12517
  return {};
11054
12518
  },
11055
- additionalSecretKeys: ctx => [{
12519
+ additionalSecretKeys: ctx => [
12520
+ // deploy-time credentials don't exist for the local env
12521
+ ...(ctx.envType !== "local" ? [{
11056
12522
  key: exports.GCLOUD_DEPLOY_CREDENTIALS_KEY,
11057
12523
  hidden: true
11058
12524
  }, {
11059
12525
  key: exports.GCLOUD_RUN_CANONICAL_HOST_SUFFIX,
11060
- hidden: true
11061
- }, ...(ctx.deployConfigRaw && ctx.deployConfigRaw.cloudSql ? [{
12526
+ hidden: true,
12527
+ // a public url fragment, not sensitive: masking it would
12528
+ // suppress environment urls on github and break log links
12529
+ kind: "variable"
12530
+ }] : []), ...(ctx.deployConfigRaw && ctx.deployConfigRaw.cloudSql ? [{
11062
12531
  key: "DB_PASSWORD"
11063
12532
  }] : [])],
11064
12533
  getAdditionalEnvVars: ctx => {
@@ -11066,7 +12535,8 @@ exports.GCLOUD_RUN_DEPLOY_TYPE = {
11066
12535
  fullName,
11067
12536
  env,
11068
12537
  componentName,
11069
- deployConfigRaw
12538
+ deployConfigRaw,
12539
+ envType
11070
12540
  } = ctx;
11071
12541
  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();
11072
12542
  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, {
@@ -11075,7 +12545,11 @@ exports.GCLOUD_RUN_DEPLOY_TYPE = {
11075
12545
  region: deployConfigRaw.region
11076
12546
  })])) : {};
11077
12547
  return {
11078
- HOSTNAME_INTERNAL,
12548
+ // references the canonical host suffix secret, which doesn't
12549
+ // exist for the local env
12550
+ ...(envType !== "local" ? {
12551
+ HOSTNAME_INTERNAL
12552
+ } : {}),
11079
12553
  ...getCloudSqlVariables(ctx),
11080
12554
  ...jobTriggers,
11081
12555
  DEPLOY_CLOUD_RUN_SERVICE_NAME: (0, getServiceName_1.getServiceNameForEnvContext)(ctx),
@@ -11168,7 +12642,10 @@ const getDatabaseDeleteScript = (context, deployConfig) => {
11168
12642
  // unfortunatly the database cannot be deleted right after the service has stopped, as it might still have connections
11169
12643
  // we have to repeate this process
11170
12644
  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}`, {
11171
- pauseInSeconds: 10
12645
+ pauseInSeconds: 10,
12646
+ // connections normally clear within 3-5min; anything beyond is
12647
+ // a permanent error (missing permission, wrong instance, ...)
12648
+ maxAttempts: 30
11172
12649
  })];
11173
12650
  };
11174
12651
  exports.getDatabaseDeleteScript = getDatabaseDeleteScript;
@@ -12892,6 +14369,7 @@ const promises_1 = __nccwpck_require__(91943);
12892
14369
  const path_1 = __nccwpck_require__(16928);
12893
14370
  const backends_1 = __nccwpck_require__(93243);
12894
14371
  const gitlab_1 = __nccwpck_require__(8475);
14372
+ const jobImagesPlan_1 = __nccwpck_require__(11585);
12895
14373
  /**
12896
14374
  * generates the pipeline files of all pipeline types enabled in the
12897
14375
  * config (see `pipelines`). Pass an explicit `pipelineType` to generate
@@ -12899,6 +14377,16 @@ const gitlab_1 = __nccwpck_require__(8475);
12899
14377
  */
12900
14378
  async function generatePipelineFiles(context, pipelineType) {
12901
14379
  const pipelineTypes = pipelineType ? [pipelineType] : (0, backends_1.getEnabledPipelineTypes)(context.config);
14380
+ if (!pipelineType) {
14381
+ // the materialized image definitions are a union across all enabled
14382
+ // backends and no single backend may wipe them — cleaned once per
14383
+ // full generation, so images that fell out of use don't linger (and
14384
+ // keep triggering gitlab's rules:changes)
14385
+ await (0, promises_1.rm)(jobImagesPlan_1.GENERATED_IMAGES_FOLDER, {
14386
+ recursive: true,
14387
+ force: true
14388
+ });
14389
+ }
12902
14390
  for (const type of pipelineTypes) {
12903
14391
  await generatePipelineFilesForBackend(context, (0, backends_1.getPipelineBackend)(type));
12904
14392
  }
@@ -12916,7 +14404,11 @@ async function generatePipelineFilesForBackend(context, backend) {
12916
14404
  await (0, promises_1.mkdir)((0, path_1.dirname)(path), {
12917
14405
  recursive: true
12918
14406
  });
12919
- await context.fileWriter.writeYamlfile(path, content);
14407
+ if (typeof content === "string") {
14408
+ await context.fileWriter.writeGeneratedFileRaw(path, content);
14409
+ } else {
14410
+ await context.fileWriter.writeYamlfile(path, content);
14411
+ }
12920
14412
  }));
12921
14413
  }
12922
14414
  /**
@@ -13272,10 +14764,23 @@ exports.RULE_CONDITION_HOTFIX_BRANCH = "$CI_COMMIT_BRANCH =~ /^[0-9]+.([0-9]+|x)
13272
14764
  Object.defineProperty(exports, "__esModule", ({
13273
14765
  value: true
13274
14766
  }));
13275
- exports.getRunnerImage = void 0;
14767
+ exports.getCentralRunnerImageUrl = exports.getRunnerImage = exports.isCatladderImageRef = void 0;
13276
14768
  const constants_1 = __nccwpck_require__(43480);
13277
- const getRunnerImage = imageName => constants_1.DOCKER_REGISTRY + "/" + imageName + ":" + constants_1.PIPELINE_IMAGE_TAG;
14769
+ const isCatladderImageRef = value => typeof value === "object" && value !== null && "catladderImage" in value && typeof value.catladderImage === "string";
14770
+ exports.isCatladderImageRef = isCatladderImageRef;
14771
+ /**
14772
+ * job creators use this to reference a catladder-provided job image;
14773
+ * the backends resolve it to a concrete image url
14774
+ */
14775
+ const getRunnerImage = imageName => ({
14776
+ catladderImage: imageName
14777
+ });
13278
14778
  exports.getRunnerImage = getRunnerImage;
14779
+ /**
14780
+ * the image url in catladder's central registry (the classic behavior)
14781
+ */
14782
+ const getCentralRunnerImageUrl = imageName => constants_1.DOCKER_REGISTRY + "/" + imageName + ":" + constants_1.PIPELINE_IMAGE_TAG;
14783
+ exports.getCentralRunnerImageUrl = getCentralRunnerImageUrl;
13279
14784
 
13280
14785
  /***/ }),
13281
14786
 
@@ -13490,8 +14995,14 @@ const sanitizeForBashVariable = name => name.replace(/-/g, "_");
13490
14995
  exports.sanitizeForBashVariable = sanitizeForBashVariable;
13491
14996
  const repeatOnFailure = (command, options) => {
13492
14997
  return `
14998
+ _attempt=0
13493
14999
  until ${command}
13494
15000
  do
15001
+ _attempt=$((_attempt+1))
15002
+ if [ "$_attempt" -ge ${options.maxAttempts} ]; then
15003
+ echo "giving up after ${options.maxAttempts} attempts"
15004
+ exit 1
15005
+ fi
13495
15006
  echo "Trying again."
13496
15007
  sleep ${options.pauseInSeconds}
13497
15008
  done
@@ -13679,6 +15190,14 @@ class FileWriter {
13679
15190
  extension: (_b = path.split(".").pop()) !== null && _b !== void 0 ? _b : ""
13680
15191
  };
13681
15192
  }
15193
+ /**
15194
+ * writes content verbatim (no auto-generated header) — e.g. for
15195
+ * materialized dockerfiles, where a leading comment would break
15196
+ * parser directives like `# syntax = ...`
15197
+ */
15198
+ async writeGeneratedFileRaw(path, content) {
15199
+ await this.writeTheFile(path, content);
15200
+ }
13682
15201
  async writeGeneratedFile(path, content, options) {
13683
15202
  await this.writeTheFile(path, [this.getAutoGeneratedHeader(options.commentChar), content].join("\n"));
13684
15203
  }