@catladder/cli 0.0.0-next-4e7c6848 → 0.0.0-next-8b9ad5ca

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 (103) hide show
  1. package/dist/bundles/catenv/index.js +617 -19
  2. package/dist/bundles/cli/index.js +811 -18
  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 +52 -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 +37 -0
  36. package/dist/cli/src/commands/project/commandSecretsSyncGithub.js +127 -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 +15 -0
  48. package/dist/cli/src/utils/github.js +54 -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/index.d.ts +17 -1
  72. package/dist/pipeline/src/backends/index.js +33 -1
  73. package/dist/pipeline/src/backends/index.js.map +1 -1
  74. package/dist/pipeline/src/constants.js +1 -1
  75. package/dist/pipeline/src/deploy/cloudRun/index.js +11 -4
  76. package/dist/pipeline/src/deploy/cloudRun/index.js.map +1 -1
  77. package/dist/pipeline/src/types/config.d.ts +37 -3
  78. package/dist/tsconfig.tsbuildinfo +1 -1
  79. package/package.json +1 -1
  80. package/src/adapters/baseContext.ts +10 -0
  81. package/src/adapters/nonInteractive.ts +30 -0
  82. package/src/adapters/programmatic.ts +7 -1
  83. package/src/adapters/terminal.ts +9 -1
  84. package/src/adapters/vaultManagerAccess.ts +25 -0
  85. package/src/apps/catenv/catenv.ts +5 -1
  86. package/src/apps/catenv/printVariables.ts +16 -6
  87. package/src/apps/catenv/types.ts +9 -0
  88. package/src/apps/catenv/writeDotEnvFiles.ts +3 -3
  89. package/src/apps/cli/commands/project/commandConfigSecrets.ts +94 -9
  90. package/src/catenv.ts +22 -2
  91. package/src/commands/index.ts +1 -0
  92. package/src/commands/project/commandSecretsSyncGithub.ts +149 -0
  93. package/src/config/getProjectConfig.ts +21 -7
  94. package/src/core/types.ts +23 -0
  95. package/src/git/gitProjectInformation.ts +5 -3
  96. package/src/utils/github.ts +59 -0
  97. package/src/vault/BitwardenVault.ts +161 -0
  98. package/src/vault/GitlabVault.ts +25 -0
  99. package/src/vault/VaultManager.ts +211 -0
  100. package/src/vault/bw.ts +110 -0
  101. package/src/vault/cache.ts +80 -0
  102. package/src/vault/index.ts +5 -0
  103. package/src/vault/types.ts +42 -0
@@ -1,6 +1,78 @@
1
1
  /******/ (() => { // webpackBootstrap
2
2
  /******/ var __webpack_modules__ = ({
3
3
 
4
+ /***/ 1917:
5
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
6
+
7
+ "use strict";
8
+
9
+
10
+ Object.defineProperty(exports, "__esModule", ({
11
+ value: true
12
+ }));
13
+ exports.createNonInteractiveIO = void 0;
14
+ const types_1 = __nccwpck_require__(2284);
15
+ const vaultManagerAccess_1 = __nccwpck_require__(7881);
16
+ /**
17
+ * the context of non-interactive runs (catenv via direnv): logging
18
+ * goes to stderr, any required interaction fails with a clear error.
19
+ */
20
+ function createNonInteractiveIO(options = {}) {
21
+ const io = {
22
+ interactive: false,
23
+ getVaultManager: (0, vaultManagerAccess_1.createVaultManagerGetter)(() => io, {
24
+ mode: options.vaultMode
25
+ }),
26
+ log(message) {
27
+ // stderr: stdout may be evaluated (direnv)
28
+ console.error(message);
29
+ },
30
+ async confirm(message) {
31
+ throw new types_1.NonInteractiveError(`confirmation ("${message}")`);
32
+ },
33
+ async promptDirect(spec) {
34
+ throw new types_1.NonInteractiveError(`input "${spec.name}"`);
35
+ }
36
+ };
37
+ return io;
38
+ }
39
+ exports.createNonInteractiveIO = createNonInteractiveIO;
40
+
41
+ /***/ }),
42
+
43
+ /***/ 7881:
44
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
45
+
46
+ "use strict";
47
+
48
+
49
+ Object.defineProperty(exports, "__esModule", ({
50
+ value: true
51
+ }));
52
+ exports.createVaultManagerGetter = void 0;
53
+ const getProjectConfig_1 = __nccwpck_require__(1);
54
+ const VaultManager_1 = __nccwpck_require__(5771);
55
+ /**
56
+ * lazily creates the vault manager of a context (once per context),
57
+ * loading the project config on first use
58
+ */
59
+ const createVaultManagerGetter = (io, options = {}) => {
60
+ let managerPromise;
61
+ return () => managerPromise !== null && managerPromise !== void 0 ? managerPromise : managerPromise = (async () => {
62
+ const config = await (0, getProjectConfig_1.getProjectConfig)();
63
+ if (!config) {
64
+ throw new Error("no catladder config found — the secrets vault needs a project config");
65
+ }
66
+ return new VaultManager_1.VaultManager(config, {
67
+ ...options,
68
+ io: io()
69
+ });
70
+ })();
71
+ };
72
+ exports.createVaultManagerGetter = createVaultManagerGetter;
73
+
74
+ /***/ }),
75
+
4
76
  /***/ 6057:
5
77
  /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
6
78
 
@@ -16,12 +88,19 @@ const verboseBanner_1 = __nccwpck_require__(5472);
16
88
  const writeDotEnvFiles_1 = __nccwpck_require__(5614);
17
89
  const writeEnvDTs_1 = __nccwpck_require__(8832);
18
90
  const pipeline_1 = __nccwpck_require__(241);
91
+ const nonInteractive_1 = __nccwpck_require__(1917);
19
92
  exports["default"] = async (choice, options) => {
20
93
  const config = await (0, getProjectConfig_1.getProjectConfig)();
21
94
  if (!config) {
22
95
  return;
23
96
  }
24
- const context = (0, pipeline_1.createCatenvContext)(config);
97
+ const io = (0, nonInteractive_1.createNonInteractiveIO)({
98
+ vaultMode: options === null || options === void 0 ? void 0 : options.vaultMode
99
+ });
100
+ const context = {
101
+ ...(0, pipeline_1.createCatenvContext)(config),
102
+ io
103
+ };
25
104
  if (options === null || options === void 0 ? void 0 : options.verbose) {
26
105
  (0, verboseBanner_1.printVerboseBanner)();
27
106
  }
@@ -42,19 +121,19 @@ Object.defineProperty(exports, "__esModule", ({
42
121
  exports.printVariables = void 0;
43
122
  const getProjectConfig_1 = __nccwpck_require__(1);
44
123
  const utils_1 = __nccwpck_require__(1909);
45
- const getAllVariablesToPrint = async (config, choice) => {
124
+ const getAllVariablesToPrint = async (io, config, choice) => {
46
125
  const {
47
126
  env,
48
127
  currentComponent
49
128
  } = await (0, utils_1.getCurrentComponentAndEnvFromChoice)(config, choice);
50
129
  if (currentComponent) {
51
- return await (0, getProjectConfig_1.getEnvVarsResolved)(null, env, currentComponent);
130
+ return await (0, getProjectConfig_1.getEnvVarsResolved)(io, env, currentComponent);
52
131
  } else {
53
132
  // when in a monorep and not in a subapp, merge all env vars.
54
133
  // this is not 100% correct, but better than not exporting any vars at all
55
134
  // so we also add prefixed variants
56
135
  return await Object.keys(config.components).reduce(async (acc, componentName) => {
57
- const subappvars = await (0, getProjectConfig_1.getEnvVarsResolved)(null, env, componentName);
136
+ const subappvars = await (0, getProjectConfig_1.getEnvVarsResolved)(io, env, componentName);
58
137
  delete subappvars["_ALL_ENV_VAR_KEYS"];
59
138
  return {
60
139
  ...(await acc),
@@ -66,7 +145,7 @@ const getAllVariablesToPrint = async (config, choice) => {
66
145
  }
67
146
  };
68
147
  const printVariables = async (context, choice) => {
69
- const variables = await getAllVariablesToPrint(context.config, choice);
148
+ const variables = await getAllVariablesToPrint(context.io, context.config, choice);
70
149
  console.log(makeExportKeyValuestring(variables));
71
150
  };
72
151
  exports.printVariables = printVariables;
@@ -185,7 +264,7 @@ const writeDotEnvFiles = async (context, choice) => {
185
264
  const componentsToActuallyWriteDotEnvNow = currentComponent ? componentsWithEnabledDotEnvWrite.includes(currentComponent) ? [currentComponent] : [] : componentsWithEnabledDotEnvWrite;
186
265
  const gitRoot = await (0, projects_1.getGitRoot)();
187
266
  for (const componentName of componentsToActuallyWriteDotEnvNow) {
188
- const variables = await (0, getProjectConfig_1.getEnvVarsResolved)(null, env, componentName);
267
+ const variables = await (0, getProjectConfig_1.getEnvVarsResolved)(context.io, env, componentName);
189
268
  delete variables["_ALL_ENV_VAR_KEYS"];
190
269
  const componentDir = (0, utils_1.getComponentFullPath)(gitRoot, context.config, componentName);
191
270
  const filePath = (0, path_1.join)(componentDir, ".env");
@@ -279,11 +358,12 @@ const catenv_1 = __importDefault(__nccwpck_require__(6057));
279
358
  const parseChoice_1 = __nccwpck_require__(4272);
280
359
  const packageInfos_1 = __importDefault(__nccwpck_require__(3568));
281
360
  const program = new commander_1.Command();
282
- program.name("catenv").description("Environment variable and pipeline generation via direnv").version(packageInfos_1.default.version).argument("[envComponent]", "env or env:component").option("-v, --verbose", "verbose output").option("-p, --print-variables", "print variables").action((envComponent, opts) => {
361
+ program.name("catenv").description("Environment variable and pipeline generation via direnv").version(packageInfos_1.default.version).argument("[envComponent]", "env or env:component").option("-v, --verbose", "verbose output").option("-p, --print-variables", "print variables").addOption(new commander_1.Option("--vault-mode <mode>", ["how to interact with the secrets vault:", "auto: contact the vault when cached secrets are missing (may prompt to unlock)", "no-prompt: like auto, but fail instead of prompting", "offline: never contact the vault, use only locally cached secrets", "refresh: ignore the local cache once and refresh it from the vault"].join("\n")).choices(["auto", "no-prompt", "offline", "refresh"]).default("auto")).action((envComponent, opts) => {
283
362
  var _a, _b;
284
363
  (0, catenv_1.default)(envComponent ? (0, parseChoice_1.parseChoice)(envComponent) : null, {
285
364
  verbose: (_a = opts.verbose) !== null && _a !== void 0 ? _a : false,
286
- printVariables: (_b = opts.printVariables) !== null && _b !== void 0 ? _b : false
365
+ printVariables: (_b = opts.printVariables) !== null && _b !== void 0 ? _b : false,
366
+ vaultMode: opts.vaultMode
287
367
  }).then(() => {
288
368
  // we have to exit manually, because we have some file watches
289
369
  process.exit();
@@ -403,14 +483,24 @@ const getGitlabVar = async (io, env, componentName, variableName) => {
403
483
  };
404
484
  exports.getGitlabVar = getGitlabVar;
405
485
  const resolveSecrets = async (io, varSets) => {
406
- const allVariablesInGitlab = await (0, gitlab_1.getAllVariables)(io);
486
+ // the secret variables actually referenced by the env var values —
487
+ // when they are all in the local cache, the vault is not contacted
488
+ const referencedNames = [...new Set(varSets.flatMap(({
489
+ envVars
490
+ }) => Object.values(envVars).flatMap(value => {
491
+ var _a;
492
+ return (_a = `${value}`.match(/\$CL_\w+/g)) !== null && _a !== void 0 ? _a : [];
493
+ })))].map(reference => reference.slice(1));
494
+ const vaultManager = await io.getVaultManager();
495
+ const secrets = await vaultManager.readSecrets(referencedNames, io);
496
+ const secretEntries = Object.entries(secrets);
407
497
  return Object.fromEntries(varSets.flatMap(({
408
498
  envVars,
409
499
  secretEnvVarKeys
410
500
  }) => Object.entries(envVars).filter(([key, value]) => {
411
501
  var _a;
412
502
  return value !== undefined && value !== null && !((_a = secretEnvVarKeys.find(k => k.key === key)) === null || _a === void 0 ? void 0 : _a.hidden);
413
- }).map(([key, value]) => [key, allVariablesInGitlab.reduce((acc, curr) => acc.replace(new RegExp("\\$" + curr.key, "g"), curr.value), `${value}`)])));
503
+ }).map(([key, value]) => [key, secretEntries.reduce((acc, [name, secretValue]) => acc.replace(new RegExp("\\$" + name, "g"), secretValue), `${value}`)])));
414
504
  };
415
505
  const getEnvVarsResolved = async (io, env, componentName) => {
416
506
  if (!componentName) {
@@ -464,6 +554,36 @@ exports.parseChoice = parseChoice;
464
554
 
465
555
  /***/ }),
466
556
 
557
+ /***/ 2284:
558
+ /***/ ((__unused_webpack_module, exports) => {
559
+
560
+ "use strict";
561
+
562
+
563
+ Object.defineProperty(exports, "__esModule", ({
564
+ value: true
565
+ }));
566
+ exports.MissingInputError = exports.NonInteractiveError = void 0;
567
+ // ─── Errors ──────────────────────────────────────────────────────────
568
+ class NonInteractiveError extends Error {
569
+ constructor(interaction) {
570
+ super(`${interaction} requires an interactive terminal, but this context is not interactive`);
571
+ this.name = "NonInteractiveError";
572
+ }
573
+ }
574
+ exports.NonInteractiveError = NonInteractiveError;
575
+ class MissingInputError extends Error {
576
+ constructor(inputName, promptMessage) {
577
+ super(`Missing required input: "${inputName}" (${promptMessage}). ` + `Provide it via --${inputName}=<value> or --inputs='{"${inputName}": ...}'`);
578
+ this.inputName = inputName;
579
+ this.promptMessage = promptMessage;
580
+ this.name = "MissingInputError";
581
+ }
582
+ }
583
+ exports.MissingInputError = MissingInputError;
584
+
585
+ /***/ }),
586
+
467
587
  /***/ 6636:
468
588
  /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
469
589
 
@@ -518,14 +638,14 @@ exports.getProjectRootPath = getProjectRootPath;
518
638
  /**
519
639
  * Get the git remote hostname and project path from the git config.
520
640
  */
521
- const getGitRemoteHostAndPath = async () => {
641
+ const getGitRemoteHostAndPath = async (remoteName = "origin") => {
522
642
  var _a;
523
- const remoteUrl = await (0, gitConfig_1.gitConfigGet)("remote.origin.url");
643
+ const remoteUrl = await (0, gitConfig_1.gitConfigGet)(`remote.${remoteName}.url`);
524
644
  const remoteReg = /(https:\/\/|git@)([^:/]+)[:/]([^.]*)(\.git)?/;
525
645
  const match = (_a = remoteUrl.match(remoteReg)) !== null && _a !== void 0 ? _a : [];
526
646
  const [,, gitRemoteHost, gitRemotePath] = match;
527
647
  if (!(gitRemoteHost === null || gitRemoteHost === void 0 ? void 0 : gitRemoteHost.length) || !(gitRemotePath === null || gitRemotePath === void 0 ? void 0 : gitRemotePath.length)) {
528
- throw new Error(`Failed to parse git remote hostname and path from git configs remote.origin.url! ${remoteUrl}`);
648
+ throw new Error(`Failed to parse git remote hostname and path from git configs remote.${remoteName}.url! ${remoteUrl}`);
529
649
  }
530
650
  return {
531
651
  gitRemoteHost,
@@ -947,6 +1067,445 @@ exports.getProjectNamespace = getProjectNamespace;
947
1067
 
948
1068
  /***/ }),
949
1069
 
1070
+ /***/ 3510:
1071
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
1072
+
1073
+ "use strict";
1074
+
1075
+
1076
+ Object.defineProperty(exports, "__esModule", ({
1077
+ value: true
1078
+ }));
1079
+ exports.BitwardenVault = void 0;
1080
+ const pipeline_1 = __nccwpck_require__(241);
1081
+ const yaml_1 = __nccwpck_require__(6637);
1082
+ const bw_1 = __nccwpck_require__(2211);
1083
+ const DEFAULT_COLLECTION = "catladder";
1084
+ const SECURE_NOTE_TYPE = 2;
1085
+ /**
1086
+ * one yaml note per env and component:
1087
+ * `<customerName>/<appName>/<env>/<componentName>/secrets.yml`
1088
+ */
1089
+ const itemName = (config, env, componentName) => `${config.customerName}/${config.appName}/${env}/${componentName}/secrets.yml`;
1090
+ const itemPrefix = config => `${config.customerName}/${config.appName}/`;
1091
+ /**
1092
+ * parses env and componentName back out of an item name
1093
+ */
1094
+ const parseItemName = (config, name) => {
1095
+ const match = name.slice(itemPrefix(config).length).match(/^([^/]+)\/([^/]+)\/secrets\.yml$/);
1096
+ return match ? {
1097
+ env: match[1],
1098
+ componentName: match[2]
1099
+ } : null;
1100
+ };
1101
+ const findCollection = async (session, collectionName) => {
1102
+ const collections = await (0, bw_1.runBwJson)(session, ["list", "collections", "--search", collectionName]);
1103
+ const collection = collections.find(c => c.name === collectionName);
1104
+ if (!collection) {
1105
+ throw new Error(`bitwarden collection '${collectionName}' not found — create it and make sure you have access`);
1106
+ }
1107
+ return collection;
1108
+ };
1109
+ const encodePayload = payload => Buffer.from(JSON.stringify(payload), "utf-8").toString("base64");
1110
+ class BitwardenVault {
1111
+ constructor(config, options,
1112
+ /**
1113
+ * whether a locked bitwarden may be unlocked interactively
1114
+ * (false with catenv --vault-mode no-prompt)
1115
+ */
1116
+ allowPrompt) {
1117
+ var _a;
1118
+ this.config = config;
1119
+ this.allowPrompt = allowPrompt;
1120
+ this.collectionName = (_a = options.collection) !== null && _a !== void 0 ? _a : DEFAULT_COLLECTION;
1121
+ this.id = `bitwarden:${this.collectionName}`;
1122
+ }
1123
+ session(io) {
1124
+ return (0, bw_1.getBwSession)(io, {
1125
+ allowPrompt: this.allowPrompt
1126
+ });
1127
+ }
1128
+ async readAllSecrets(io) {
1129
+ const {
1130
+ config,
1131
+ collectionName
1132
+ } = this;
1133
+ const bwSession = await this.session(io);
1134
+ const collection = await findCollection(bwSession, collectionName);
1135
+ const items = await (0, bw_1.runBwJson)(bwSession, ["list", "items", "--collectionid", collection.id]);
1136
+ const secrets = {};
1137
+ for (const item of items) {
1138
+ const parsed = parseItemName(config, item.name);
1139
+ if (!parsed || !item.notes) {
1140
+ continue;
1141
+ }
1142
+ const values = (0, yaml_1.parse)(item.notes);
1143
+ Object.entries(values !== null && values !== void 0 ? values : {}).forEach(([key, value]) => {
1144
+ secrets[(0, pipeline_1.getSecretVarName)(parsed.env, parsed.componentName, key)] = typeof value === "string" ? value : JSON.stringify(value);
1145
+ });
1146
+ }
1147
+ return secrets;
1148
+ }
1149
+ async writeSecrets(io, env, componentName, secrets) {
1150
+ const {
1151
+ config,
1152
+ collectionName
1153
+ } = this;
1154
+ const bwSession = await this.session(io);
1155
+ const collection = await findCollection(bwSession, collectionName);
1156
+ const name = itemName(config, env, componentName);
1157
+ const existing = (await (0, bw_1.runBwJson)(bwSession, ["list", "items", "--collectionid", collection.id, "--search", name])).find(item => item.name === name);
1158
+ const notes = (0, yaml_1.stringify)(secrets);
1159
+ if (existing) {
1160
+ await (0, bw_1.runBw)(bwSession, ["edit", "item", existing.id, encodePayload({
1161
+ ...existing,
1162
+ notes
1163
+ })]);
1164
+ } else {
1165
+ await (0, bw_1.runBw)(bwSession, ["create", "item", encodePayload({
1166
+ type: SECURE_NOTE_TYPE,
1167
+ secureNote: {
1168
+ type: 0
1169
+ },
1170
+ name,
1171
+ notes,
1172
+ ...(collection.organizationId ? {
1173
+ organizationId: collection.organizationId,
1174
+ collectionIds: [collection.id]
1175
+ } : {})
1176
+ })]);
1177
+ }
1178
+ }
1179
+ }
1180
+ exports.BitwardenVault = BitwardenVault;
1181
+
1182
+ /***/ }),
1183
+
1184
+ /***/ 1107:
1185
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
1186
+
1187
+ "use strict";
1188
+
1189
+
1190
+ Object.defineProperty(exports, "__esModule", ({
1191
+ value: true
1192
+ }));
1193
+ exports.GitlabVault = void 0;
1194
+ const gitlab_1 = __nccwpck_require__(4508);
1195
+ /**
1196
+ * the legacy (and default) vault: the gitlab project variables double
1197
+ * as the secret store
1198
+ */
1199
+ class GitlabVault {
1200
+ constructor() {
1201
+ this.id = "gitlab";
1202
+ }
1203
+ async readAllSecrets(io) {
1204
+ const variables = await (0, gitlab_1.getAllVariables)(io);
1205
+ return Object.fromEntries(variables.map(v => [v.key, v.value]));
1206
+ }
1207
+ async writeSecrets(io, env, componentName, secrets) {
1208
+ await (0, gitlab_1.upsertAllVariables)(io, secrets, env, componentName);
1209
+ }
1210
+ }
1211
+ exports.GitlabVault = GitlabVault;
1212
+
1213
+ /***/ }),
1214
+
1215
+ /***/ 5771:
1216
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
1217
+
1218
+ "use strict";
1219
+
1220
+
1221
+ Object.defineProperty(exports, "__esModule", ({
1222
+ value: true
1223
+ }));
1224
+ exports.VaultManager = void 0;
1225
+ const pipeline_1 = __nccwpck_require__(241);
1226
+ const BitwardenVault_1 = __nccwpck_require__(3510);
1227
+ const cache_1 = __nccwpck_require__(4236);
1228
+ const GitlabVault_1 = __nccwpck_require__(1107);
1229
+ /**
1230
+ * only secrets of this env are cached locally — the other envs'
1231
+ * secrets have no business sitting on developer machines
1232
+ */
1233
+ const CACHED_ENV = "local";
1234
+ /**
1235
+ * owns the configured vault, the secrets mode and the local cache.
1236
+ * The vault is the readable source of truth for secrets; CI backends
1237
+ * only receive mirrored copies.
1238
+ */
1239
+ class VaultManager {
1240
+ constructor(config, options = {}) {
1241
+ var _a, _b;
1242
+ this.config = config;
1243
+ /** refresh mode bypasses the cache only once per manager */
1244
+ this.refreshed = false;
1245
+ this.mode = (_a = options.mode) !== null && _a !== void 0 ? _a : "auto";
1246
+ this.io = (_b = options.io) !== null && _b !== void 0 ? _b : null;
1247
+ this.vault = this.createVault();
1248
+ }
1249
+ createVault() {
1250
+ const vaultConfig = (0, pipeline_1.getVaultConfig)(this.config);
1251
+ switch (vaultConfig.type) {
1252
+ case "gitlab":
1253
+ return new GitlabVault_1.GitlabVault();
1254
+ case "bitwarden":
1255
+ return new BitwardenVault_1.BitwardenVault(this.config, vaultConfig, this.mode === "auto" || this.mode === "refresh");
1256
+ }
1257
+ }
1258
+ /**
1259
+ * the vault secrets, served from the local cache when it contains
1260
+ * all required names; otherwise (depending on the mode) refreshed
1261
+ * from the vault. Only the "local" env is (re-)cached.
1262
+ */
1263
+ async readSecrets(requiredNames, io = this.io) {
1264
+ const bypassCache = this.mode === "refresh" && !this.refreshed;
1265
+ const cache = bypassCache ? null : await (0, cache_1.readSecretsCache)();
1266
+ // known entries include recorded misses (null values), so declared
1267
+ // but intentionally unset secrets don't force a vault refresh
1268
+ const known = (cache === null || cache === void 0 ? void 0 : cache.vaultId) === this.vault.id ? flattenSecrets(cache.secrets) : undefined;
1269
+ const missing = requiredNames.filter(name => !(name in (known !== null && known !== void 0 ? known : {})));
1270
+ // nothing missing (or nothing required at all): don't touch the vault
1271
+ if (missing.length === 0 && (known || requiredNames.length === 0)) {
1272
+ return withoutMisses(known !== null && known !== void 0 ? known : {});
1273
+ }
1274
+ if (this.mode === "offline") {
1275
+ if (missing.length > 0) {
1276
+ io === null || io === void 0 ? void 0 : io.log(`⚠️ vault is offline (--vault-mode offline) and ${missing.length} secrets are not in the local cache: ${missing.join(", ")}`);
1277
+ }
1278
+ return withoutMisses(known !== null && known !== void 0 ? known : {});
1279
+ }
1280
+ const secrets = await this.vault.readAllSecrets(io);
1281
+ this.refreshed = true;
1282
+ await (0, cache_1.writeSecretsCache)(this.vault.id, await this.cacheableSubset(secrets));
1283
+ return secrets;
1284
+ }
1285
+ /**
1286
+ * writes secrets of one env/component to the vault and updates the
1287
+ * local cache (write-through, "local" env only)
1288
+ */
1289
+ async writeSecrets(env, componentName, secrets, io = this.io) {
1290
+ if (!io) {
1291
+ throw new Error("writing to the vault requires an interactive context");
1292
+ }
1293
+ await this.vault.writeSecrets(io, env, componentName, secrets);
1294
+ if (env === CACHED_ENV) {
1295
+ await (0, cache_1.updateSecretsCache)(this.vault.id, env, componentName, Object.fromEntries(Object.entries(secrets).map(([key, value]) => [key, typeof value === "string" ? value : JSON.stringify(value)])));
1296
+ }
1297
+ }
1298
+ /**
1299
+ * the subset of the vault secrets that may be cached: the declared
1300
+ * secrets of the "local" env, per component, as a hierarchy
1301
+ */
1302
+ async cacheableSubset(flatSecrets) {
1303
+ const local = {};
1304
+ for (const componentName of Object.keys(this.config.components)) {
1305
+ const keys = await this.declaredSecretKeys(componentName);
1306
+ const values = Object.fromEntries(keys.map(key => {
1307
+ var _a;
1308
+ return [key,
1309
+ // null records "absent in the vault" (a known miss)
1310
+ (_a = flatSecrets[(0, pipeline_1.getSecretVarName)(CACHED_ENV, componentName, key)]) !== null && _a !== void 0 ? _a : null];
1311
+ }));
1312
+ if (Object.keys(values).length > 0) {
1313
+ local[componentName] = values;
1314
+ }
1315
+ }
1316
+ return Object.keys(local).length > 0 ? {
1317
+ [CACHED_ENV]: local
1318
+ } : {};
1319
+ }
1320
+ async declaredSecretKeys(componentName) {
1321
+ try {
1322
+ const {
1323
+ secretEnvVarKeys,
1324
+ jobOnlyVars
1325
+ } = await (0, pipeline_1.getEnvironment)({
1326
+ config: this.config,
1327
+ componentName,
1328
+ env: CACHED_ENV
1329
+ });
1330
+ return [...secretEnvVarKeys, ...jobOnlyVars.build.secretEnvVarKeys, ...jobOnlyVars.deploy.secretEnvVarKeys].map(({
1331
+ key
1332
+ }) => key);
1333
+ } catch (_a) {
1334
+ // components without a local env simply have nothing to cache
1335
+ return [];
1336
+ }
1337
+ }
1338
+ }
1339
+ exports.VaultManager = VaultManager;
1340
+ /**
1341
+ * the cache hierarchy as a flat map of raw secret variable names
1342
+ * (null values = recorded misses)
1343
+ */
1344
+ const flattenSecrets = secrets => Object.fromEntries(Object.entries(secrets).flatMap(([env, components]) => Object.entries(components).flatMap(([componentName, values]) => Object.entries(values).map(([key, value]) => [(0, pipeline_1.getSecretVarName)(env, componentName, key), value]))));
1345
+ /**
1346
+ * only actual values — recorded misses stay unresolved, like any other
1347
+ * unset variable
1348
+ */
1349
+ const withoutMisses = known => Object.fromEntries(Object.entries(known).filter(entry => entry[1] !== null));
1350
+
1351
+ /***/ }),
1352
+
1353
+ /***/ 2211:
1354
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
1355
+
1356
+ "use strict";
1357
+
1358
+
1359
+ Object.defineProperty(exports, "__esModule", ({
1360
+ value: true
1361
+ }));
1362
+ exports.runBwJson = exports.runBw = exports.getBwSession = void 0;
1363
+ const child_process_1 = __nccwpck_require__(5317);
1364
+ const util_1 = __nccwpck_require__(9023);
1365
+ const preferences_1 = __nccwpck_require__(6261);
1366
+ const execFile = (0, util_1.promisify)(child_process_1.execFile);
1367
+ const SESSION_PREFERENCE = "bwsession";
1368
+ class BitwardenLockedError extends Error {
1369
+ constructor() {
1370
+ super("bitwarden is locked and prompting is disabled — run `bw unlock` or catenv without --vault-mode no-prompt");
1371
+ }
1372
+ }
1373
+ /**
1374
+ * runs an interactive bw command that prints a session key on stdout
1375
+ * (login/unlock) while the user types the password on the terminal
1376
+ */
1377
+ const interactiveSessionCommand = args => new Promise((resolve, reject) => {
1378
+ var _a;
1379
+ const child = (0, child_process_1.spawn)("bw", [...args, "--raw"], {
1380
+ stdio: ["inherit", "pipe", "inherit"]
1381
+ });
1382
+ let session = "";
1383
+ (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on("data", data => session += data.toString("utf-8"));
1384
+ child.on("error", reject);
1385
+ child.on("close", code => code === 0 && session.trim() ? resolve(session.trim()) : reject(new Error(`bw ${args[0]} failed`)));
1386
+ });
1387
+ const getBwStatus = async session => {
1388
+ try {
1389
+ const {
1390
+ stdout
1391
+ } = await execFile("bw", ["status"], {
1392
+ env: {
1393
+ ...process.env,
1394
+ ...(session ? {
1395
+ BW_SESSION: session
1396
+ } : {})
1397
+ }
1398
+ });
1399
+ return JSON.parse(stdout).status;
1400
+ } catch (_a) {
1401
+ return "unauthenticated";
1402
+ }
1403
+ };
1404
+ /**
1405
+ * a working bw session: reuses the cached one, otherwise unlocks (or
1406
+ * logs in) interactively — unless prompting is disabled
1407
+ */
1408
+ const getBwSession = async (io, {
1409
+ allowPrompt
1410
+ }) => {
1411
+ const cached = (await (0, preferences_1.hasPreference)(SESSION_PREFERENCE)) ? String(await (0, preferences_1.getPreference)(SESSION_PREFERENCE)) : null;
1412
+ const status = await getBwStatus(cached);
1413
+ if (status === "unlocked" && cached) {
1414
+ return cached;
1415
+ }
1416
+ if (!allowPrompt) {
1417
+ throw new BitwardenLockedError();
1418
+ }
1419
+ io === null || io === void 0 ? void 0 : io.log("");
1420
+ io === null || io === void 0 ? void 0 : io.log(status === "unauthenticated" ? "# Please login to Bitwarden:" : "# Bitwarden is locked, please unlock:");
1421
+ io === null || io === void 0 ? void 0 : io.log("");
1422
+ const session = await interactiveSessionCommand([status === "unauthenticated" ? "login" : "unlock"]);
1423
+ await (0, preferences_1.setPreference)(SESSION_PREFERENCE, session);
1424
+ await (0, exports.runBw)(session, ["sync"]);
1425
+ return session;
1426
+ };
1427
+ exports.getBwSession = getBwSession;
1428
+ /**
1429
+ * runs a bw command with the given session and returns stdout
1430
+ */
1431
+ const runBw = async (session, args) => {
1432
+ const {
1433
+ stdout
1434
+ } = await execFile("bw", [...args, "--raw", "--nointeraction"], {
1435
+ env: {
1436
+ ...process.env,
1437
+ BW_SESSION: session
1438
+ },
1439
+ maxBuffer: 50 * 1024 * 1024
1440
+ });
1441
+ return stdout;
1442
+ };
1443
+ exports.runBw = runBw;
1444
+ const runBwJson = async (session, args) => JSON.parse(await (0, exports.runBw)(session, args));
1445
+ exports.runBwJson = runBwJson;
1446
+
1447
+ /***/ }),
1448
+
1449
+ /***/ 4236:
1450
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
1451
+
1452
+ "use strict";
1453
+
1454
+
1455
+ Object.defineProperty(exports, "__esModule", ({
1456
+ value: true
1457
+ }));
1458
+ exports.updateSecretsCache = exports.writeSecretsCache = exports.readSecretsCache = void 0;
1459
+ const promises_1 = __nccwpck_require__(1943);
1460
+ const path_1 = __nccwpck_require__(6928);
1461
+ const gitProjectInformation_1 = __nccwpck_require__(1111);
1462
+ const getCacheFolder = async () => (0, path_1.join)(await (0, gitProjectInformation_1.getProjectRootPath)(), ".catladder");
1463
+ const getCachePath = async () => (0, path_1.join)(await getCacheFolder(), "secrets.json");
1464
+ const readSecretsCache = async () => {
1465
+ try {
1466
+ return JSON.parse(await (0, promises_1.readFile)(await getCachePath(), "utf-8"));
1467
+ } catch (_a) {
1468
+ return null;
1469
+ }
1470
+ };
1471
+ exports.readSecretsCache = readSecretsCache;
1472
+ const writeSecretsCache = async (vaultId, secrets) => {
1473
+ const folder = await getCacheFolder();
1474
+ await (0, promises_1.mkdir)(folder, {
1475
+ recursive: true
1476
+ });
1477
+ // the folder ignores itself, so the cache can never be committed
1478
+ await (0, promises_1.writeFile)((0, path_1.join)(folder, ".gitignore"), "*\n");
1479
+ const cache = {
1480
+ vaultId,
1481
+ updatedAt: new Date().toISOString(),
1482
+ secrets
1483
+ };
1484
+ await (0, promises_1.writeFile)(await getCachePath(), JSON.stringify(cache, null, 2));
1485
+ };
1486
+ exports.writeSecretsCache = writeSecretsCache;
1487
+ /**
1488
+ * merges freshly written secrets of one env/component into the cache
1489
+ * (write-through from config-secrets), so the next catenv run doesn't
1490
+ * need the vault
1491
+ */
1492
+ const updateSecretsCache = async (vaultId, env, componentName, values) => {
1493
+ var _a;
1494
+ const existing = await (0, exports.readSecretsCache)();
1495
+ const secrets = (existing === null || existing === void 0 ? void 0 : existing.vaultId) === vaultId ? existing.secrets : {};
1496
+ secrets[env] = {
1497
+ ...secrets[env],
1498
+ [componentName]: {
1499
+ ...((_a = secrets[env]) === null || _a === void 0 ? void 0 : _a[componentName]),
1500
+ ...values
1501
+ }
1502
+ };
1503
+ await (0, exports.writeSecretsCache)(vaultId, secrets);
1504
+ };
1505
+ exports.updateSecretsCache = updateSecretsCache;
1506
+
1507
+ /***/ }),
1508
+
950
1509
  /***/ 4038:
951
1510
  /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
952
1511
 
@@ -2265,7 +2824,7 @@ var __exportStar = this && this.__exportStar || function (m, exports) {
2265
2824
  Object.defineProperty(exports, "__esModule", ({
2266
2825
  value: true
2267
2826
  }));
2268
- exports.getEnabledPipelineTypes = exports.getPipelineBackend = void 0;
2827
+ exports.getEnabledPipelineTypes = exports.getVaultConfig = exports.getPipelineGitRemote = exports.getPipelineOptions = exports.getPipelineBackend = void 0;
2269
2828
  const github_1 = __nccwpck_require__(9635);
2270
2829
  const gitlab_1 = __nccwpck_require__(8475);
2271
2830
  __exportStar(__nccwpck_require__(2474), exports);
@@ -2283,6 +2842,38 @@ const getPipelineBackend = type => {
2283
2842
  return createBackend();
2284
2843
  };
2285
2844
  exports.getPipelineBackend = getPipelineBackend;
2845
+ /**
2846
+ * the options of an enabled pipeline type (empty object when enabled
2847
+ * with `true`)
2848
+ */
2849
+ const getPipelineOptions = (config, type) => {
2850
+ var _a;
2851
+ const value = (_a = config.pipelines) === null || _a === void 0 ? void 0 : _a[type];
2852
+ return typeof value === "object" ? value : {};
2853
+ };
2854
+ exports.getPipelineOptions = getPipelineOptions;
2855
+ /**
2856
+ * the git remote pointing to the repository of this pipeline type's CI
2857
+ * system (e.g. during a migration, gitlab may be `origin` and github a
2858
+ * second remote)
2859
+ */
2860
+ const getPipelineGitRemote = (config, type) => {
2861
+ var _a;
2862
+ return (_a = (0, exports.getPipelineOptions)(config, type).gitRemote) !== null && _a !== void 0 ? _a : "origin";
2863
+ };
2864
+ exports.getPipelineGitRemote = getPipelineGitRemote;
2865
+ /**
2866
+ * the normalized secrets vault of a config (string shorthands resolved
2867
+ * to their object form; defaults to the legacy gitlab store)
2868
+ */
2869
+ const getVaultConfig = config => {
2870
+ var _a, _b;
2871
+ const vault = (_b = (_a = config.secrets) === null || _a === void 0 ? void 0 : _a.vault) !== null && _b !== void 0 ? _b : "gitlab";
2872
+ return typeof vault === "string" ? {
2873
+ type: vault
2874
+ } : vault;
2875
+ };
2876
+ exports.getVaultConfig = getVaultConfig;
2286
2877
  /**
2287
2878
  * the pipeline types enabled for this config. Multiple types can be
2288
2879
  * enabled at once (parallel generation, e.g. during a CI migration).
@@ -4249,7 +4840,7 @@ Object.defineProperty(exports, "__esModule", ({
4249
4840
  value: true
4250
4841
  }));
4251
4842
  exports.DOCKER_REGISTRY = exports.PIPELINE_IMAGE_TAG = void 0;
4252
- exports.PIPELINE_IMAGE_TAG = "next-4e7c6848" || 0;
4843
+ exports.PIPELINE_IMAGE_TAG = "next-8b9ad5ca" || 0;
4253
4844
  exports.DOCKER_REGISTRY = "git.panter.ch:5001/catladder/catladder" || 0;
4254
4845
 
4255
4846
  /***/ }),
@@ -6154,13 +6745,15 @@ exports.GCLOUD_RUN_DEPLOY_TYPE = {
6154
6745
  }
6155
6746
  return {};
6156
6747
  },
6157
- additionalSecretKeys: ctx => [{
6748
+ additionalSecretKeys: ctx => [
6749
+ // deploy-time credentials don't exist for the local env
6750
+ ...(ctx.envType !== "local" ? [{
6158
6751
  key: exports.GCLOUD_DEPLOY_CREDENTIALS_KEY,
6159
6752
  hidden: true
6160
6753
  }, {
6161
6754
  key: exports.GCLOUD_RUN_CANONICAL_HOST_SUFFIX,
6162
6755
  hidden: true
6163
- }, ...(ctx.deployConfigRaw && ctx.deployConfigRaw.cloudSql ? [{
6756
+ }] : []), ...(ctx.deployConfigRaw && ctx.deployConfigRaw.cloudSql ? [{
6164
6757
  key: "DB_PASSWORD"
6165
6758
  }] : [])],
6166
6759
  getAdditionalEnvVars: ctx => {
@@ -6168,7 +6761,8 @@ exports.GCLOUD_RUN_DEPLOY_TYPE = {
6168
6761
  fullName,
6169
6762
  env,
6170
6763
  componentName,
6171
- deployConfigRaw
6764
+ deployConfigRaw,
6765
+ envType
6172
6766
  } = ctx;
6173
6767
  const HOSTNAME_INTERNAL = (0, BashExpression_1.joinBashExpressions)([fullName, (0, BashExpression_1.getBashVariable)((0, context_1.getSecretVarName)(env, componentName, exports.GCLOUD_RUN_CANONICAL_HOST_SUFFIX))], "-").toLowerCase();
6174
6768
  const jobTriggers = deployConfigRaw && deployConfigRaw.jobs ? Object.fromEntries(Object.entries(deployConfigRaw.jobs).filter(([name, job]) => Boolean(job)).map(([name, job]) => ["CLOUD_RUN_JOB_TRIGGER_URL_" + (0, gitlab_1.sanitizeForBashVariable)(name), (0, cloudRunExecutionUrl_1.getCloudRunJobExecuteUrl)(name, {
@@ -6177,7 +6771,11 @@ exports.GCLOUD_RUN_DEPLOY_TYPE = {
6177
6771
  region: deployConfigRaw.region
6178
6772
  })])) : {};
6179
6773
  return {
6180
- HOSTNAME_INTERNAL,
6774
+ // references the canonical host suffix secret, which doesn't
6775
+ // exist for the local env
6776
+ ...(envType !== "local" ? {
6777
+ HOSTNAME_INTERNAL
6778
+ } : {}),
6181
6779
  ...getCloudSqlVariables(ctx),
6182
6780
  ...jobTriggers,
6183
6781
  DEPLOY_CLOUD_RUN_SERVICE_NAME: (0, getServiceName_1.getServiceNameForEnvContext)(ctx),