@catladder/cli 5.0.1 → 5.1.1

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 (131) hide show
  1. package/dist/apps/cli/src/apps/cli/commands/project/doctor/checkGithub.js +3 -3
  2. package/dist/apps/cli/src/apps/cli/commands/project/doctor/checkGithub.js.map +1 -1
  3. package/dist/apps/cli/src/apps/cli/commands/project/doctor/checkGithubImageCasing.d.ts +15 -0
  4. package/dist/apps/cli/src/apps/cli/commands/project/doctor/checkGithubImageCasing.js +60 -0
  5. package/dist/apps/cli/src/apps/cli/commands/project/doctor/checkGithubImageCasing.js.map +1 -0
  6. package/dist/apps/cli/src/apps/cli/commands/project/doctor/index.js +2 -0
  7. package/dist/apps/cli/src/apps/cli/commands/project/doctor/index.js.map +1 -1
  8. package/dist/apps/cli/src/apps/cli/commands/project/setup/setupAccessTokens.js +2 -2
  9. package/dist/apps/cli/src/apps/cli/commands/project/setup/setupAccessTokens.js.map +1 -1
  10. package/dist/apps/cli/src/apps/cli/commands/project/setup/setupGithub.js +2 -2
  11. package/dist/apps/cli/src/apps/cli/commands/project/setup/setupGithub.js.map +1 -1
  12. package/dist/apps/cli/src/apps/cli/commands/project/setup/setupKubernetes.js +23 -7
  13. package/dist/apps/cli/src/apps/cli/commands/project/setup/setupKubernetes.js.map +1 -1
  14. package/dist/apps/cli/src/commands/index.d.ts +1 -0
  15. package/dist/apps/cli/src/commands/index.js +4 -1
  16. package/dist/apps/cli/src/commands/index.js.map +1 -1
  17. package/dist/apps/cli/src/commands/project/commandConfigSecrets.js +2 -1
  18. package/dist/apps/cli/src/commands/project/commandConfigSecrets.js.map +1 -1
  19. package/dist/apps/cli/src/commands/project/commandMrPin.d.ts +21 -0
  20. package/dist/apps/cli/src/commands/project/commandMrPin.js +119 -0
  21. package/dist/apps/cli/src/commands/project/commandMrPin.js.map +1 -0
  22. package/dist/apps/cli/src/commands/project/commandSecretsSyncGithub.d.ts +1 -42
  23. package/dist/apps/cli/src/commands/project/commandSecretsSyncGithub.js +5 -124
  24. package/dist/apps/cli/src/commands/project/commandSecretsSyncGithub.js.map +1 -1
  25. package/dist/apps/cli/src/commands/project/secrets/commandSecretsPush.js +2 -1
  26. package/dist/apps/cli/src/commands/project/secrets/commandSecretsPush.js.map +1 -1
  27. package/dist/apps/cli/src/commands/project/secrets/commandSecretsSet.js +2 -2
  28. package/dist/apps/cli/src/commands/project/secrets/commandSecretsSet.js.map +1 -1
  29. package/dist/apps/cli/src/commands/project/secrets/document.d.ts +1 -12
  30. package/dist/apps/cli/src/commands/project/secrets/document.js +1 -79
  31. package/dist/apps/cli/src/commands/project/secrets/document.js.map +1 -1
  32. package/dist/apps/cli/src/gcloud/serviceAccounts.js +15 -4
  33. package/dist/apps/cli/src/gcloud/serviceAccounts.js.map +1 -1
  34. package/dist/apps/cli/src/secrets/github.d.ts +41 -0
  35. package/dist/apps/cli/src/secrets/github.js +127 -0
  36. package/dist/apps/cli/src/secrets/github.js.map +1 -0
  37. package/dist/apps/cli/src/secrets/index.d.ts +2 -0
  38. package/dist/apps/cli/src/secrets/index.js +19 -0
  39. package/dist/apps/cli/src/secrets/index.js.map +1 -0
  40. package/dist/apps/cli/src/secrets/writeSecrets.d.ts +18 -0
  41. package/dist/apps/cli/src/secrets/writeSecrets.js +88 -0
  42. package/dist/apps/cli/src/secrets/writeSecrets.js.map +1 -0
  43. package/dist/apps/cli/src/utils/github.js +2 -2
  44. package/dist/apps/cli/src/utils/github.js.map +1 -1
  45. package/dist/apps/cli/src/utils/gitlab.d.ts +10 -0
  46. package/dist/apps/cli/src/utils/gitlab.js +30 -5
  47. package/dist/apps/cli/src/utils/gitlab.js.map +1 -1
  48. package/dist/apps/cli/src/vault/BitwardenVault.d.ts +6 -0
  49. package/dist/apps/cli/src/vault/BitwardenVault.js +11 -1
  50. package/dist/apps/cli/src/vault/BitwardenVault.js.map +1 -1
  51. package/dist/apps/cli/src/vault/GitlabVault.d.ts +2 -2
  52. package/dist/apps/cli/src/vault/GitlabVault.js +3 -2
  53. package/dist/apps/cli/src/vault/GitlabVault.js.map +1 -1
  54. package/dist/apps/cli/src/vault/VaultManager.d.ts +2 -2
  55. package/dist/apps/cli/src/vault/VaultManager.js +2 -2
  56. package/dist/apps/cli/src/vault/VaultManager.js.map +1 -1
  57. package/dist/apps/cli/src/vault/types.d.ts +16 -2
  58. package/dist/bundles/catenv/index.js +265 -28
  59. package/dist/bundles/cli/index.js +851 -329
  60. package/dist/bundles/skills/catladder-cli/references/commands.md +13 -0
  61. package/dist/bundles/skills/catladder-config/SKILL.md +3 -1
  62. package/dist/bundles/skills/catladder-deploys/SKILL.md +7 -0
  63. package/dist/bundles/skills/catladder-migrate-ci-backend/SKILL.md +18 -2
  64. package/dist/bundles/skills/catladder-pipelines/SKILL.md +51 -1
  65. package/dist/bundles/skills/catladder-secrets/SKILL.md +16 -0
  66. package/dist/packages/pipeline/src/autoStop.d.ts +22 -0
  67. package/dist/packages/pipeline/src/autoStop.js +30 -0
  68. package/dist/packages/pipeline/src/autoStop.js.map +1 -0
  69. package/dist/packages/pipeline/src/backends/github/GithubBackend.js +11 -5
  70. package/dist/packages/pipeline/src/backends/github/GithubBackend.js.map +1 -1
  71. package/dist/packages/pipeline/src/backends/github/ciVariables.d.ts +13 -2
  72. package/dist/packages/pipeline/src/backends/github/ciVariables.js +18 -5
  73. package/dist/packages/pipeline/src/backends/github/ciVariables.js.map +1 -1
  74. package/dist/packages/pipeline/src/backends/github/createGithubJobs.js +1 -1
  75. package/dist/packages/pipeline/src/backends/github/createGithubJobs.js.map +1 -1
  76. package/dist/packages/pipeline/src/backends/github/ghcr.d.ts +29 -0
  77. package/dist/packages/pipeline/src/backends/github/ghcr.js +40 -0
  78. package/dist/packages/pipeline/src/backends/github/ghcr.js.map +1 -0
  79. package/dist/packages/pipeline/src/backends/github/index.d.ts +2 -0
  80. package/dist/packages/pipeline/src/backends/github/index.js +2 -0
  81. package/dist/packages/pipeline/src/backends/github/index.js.map +1 -1
  82. package/dist/packages/pipeline/src/backends/github/registryImage.d.ts +20 -0
  83. package/dist/packages/pipeline/src/backends/github/registryImage.js +49 -0
  84. package/dist/packages/pipeline/src/backends/github/registryImage.js.map +1 -0
  85. package/dist/packages/pipeline/src/backends/gitlab/GitlabBackend.js +2 -0
  86. package/dist/packages/pipeline/src/backends/gitlab/GitlabBackend.js.map +1 -1
  87. package/dist/packages/pipeline/src/backends/gitlab/createGitlabPipeline.d.ts +4 -1
  88. package/dist/packages/pipeline/src/backends/gitlab/createGitlabPipeline.js +21 -1
  89. package/dist/packages/pipeline/src/backends/gitlab/createGitlabPipeline.js.map +1 -1
  90. package/dist/packages/pipeline/src/customImages/jobImagesPlan.d.ts +15 -3
  91. package/dist/packages/pipeline/src/customImages/jobImagesPlan.js +14 -3
  92. package/dist/packages/pipeline/src/customImages/jobImagesPlan.js.map +1 -1
  93. package/dist/packages/pipeline/src/deploy/base/deploy.js +8 -2
  94. package/dist/packages/pipeline/src/deploy/base/deploy.js.map +1 -1
  95. package/dist/packages/pipeline/src/index.d.ts +1 -0
  96. package/dist/packages/pipeline/src/index.js +1 -0
  97. package/dist/packages/pipeline/src/index.js.map +1 -1
  98. package/dist/packages/pipeline/src/types/config.d.ts +44 -0
  99. package/dist/skills/catladder-cli/references/commands.md +13 -0
  100. package/dist/skills/catladder-config/SKILL.md +3 -1
  101. package/dist/skills/catladder-deploys/SKILL.md +7 -0
  102. package/dist/skills/catladder-migrate-ci-backend/SKILL.md +18 -2
  103. package/dist/skills/catladder-pipelines/SKILL.md +51 -1
  104. package/dist/skills/catladder-secrets/SKILL.md +16 -0
  105. package/dist/tsconfig.tsbuildinfo +1 -1
  106. package/package.json +1 -1
  107. package/src/apps/cli/commands/project/doctor/checkGithub.ts +1 -1
  108. package/src/apps/cli/commands/project/doctor/checkGithubImageCasing.ts +87 -0
  109. package/src/apps/cli/commands/project/doctor/index.ts +2 -0
  110. package/src/apps/cli/commands/project/setup/setupAccessTokens.ts +3 -2
  111. package/src/apps/cli/commands/project/setup/setupGithub.ts +1 -1
  112. package/src/apps/cli/commands/project/setup/setupKubernetes.ts +31 -20
  113. package/src/commands/index.ts +1 -0
  114. package/src/commands/project/commandConfigSecrets.ts +1 -1
  115. package/src/commands/project/commandMrPin.ts +184 -0
  116. package/src/commands/project/commandSecretsSyncGithub.ts +7 -154
  117. package/src/commands/project/secrets/commandSecretsPush.ts +2 -5
  118. package/src/commands/project/secrets/commandSecretsSet.ts +2 -2
  119. package/src/commands/project/secrets/document.ts +2 -127
  120. package/src/gcloud/serviceAccounts.ts +16 -7
  121. package/src/secrets/__tests__/writeSecrets.test.ts +147 -0
  122. package/src/secrets/github.ts +157 -0
  123. package/src/secrets/index.ts +2 -0
  124. package/src/secrets/writeSecrets.ts +138 -0
  125. package/src/utils/github.ts +2 -4
  126. package/src/utils/gitlab.ts +34 -7
  127. package/src/vault/BitwardenVault.ts +10 -1
  128. package/src/vault/GitlabVault.ts +9 -2
  129. package/src/vault/VaultManager.ts +3 -2
  130. package/src/vault/__tests__/BitwardenVault.test.ts +88 -0
  131. package/src/vault/types.ts +17 -1
@@ -616,7 +616,7 @@ exports.checkCloudRun = checkCloudRun;
616
616
  Object.defineProperty(exports, "__esModule", ({ value: true }));
617
617
  exports.checkGithub = void 0;
618
618
  const pipeline_1 = __nccwpck_require__(53695);
619
- const commandSecretsSyncGithub_1 = __nccwpck_require__(27565);
619
+ const secrets_1 = __nccwpck_require__(85200);
620
620
  const concurrency_1 = __nccwpck_require__(59723);
621
621
  const github_1 = __nccwpck_require__(41353);
622
622
  const githubMergeGating_1 = __nccwpck_require__(14966);
@@ -637,14 +637,14 @@ const checkGithub = async (report, config) => {
637
637
  report.warn("github cli (gh) not authenticated — github checks skipped", "run: gh auth login");
638
638
  return;
639
639
  }
640
- const repo = await (0, commandSecretsSyncGithub_1.getConfiguredGithubRepo)();
640
+ const repo = await (0, secrets_1.getConfiguredGithubRepo)();
641
641
  if (!repo) {
642
642
  report.fail(`no github repository found on the '${(0, pipeline_1.getPipelineGitRemote)(config, "github")}' remote`);
643
643
  return;
644
644
  }
645
645
  let targets;
646
646
  try {
647
- targets = await (0, commandSecretsSyncGithub_1.collectSecretTargets)(config);
647
+ targets = await (0, secrets_1.collectSecretTargets)(config);
648
648
  }
649
649
  catch (e) {
650
650
  report.warn(`could not generate the github workflows to derive secret references (${e.message})`);
@@ -802,6 +802,73 @@ const checkGithubMergeGating = async (report, repo) => {
802
802
 
803
803
  /***/ }),
804
804
 
805
+ /***/ 12668:
806
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
807
+
808
+ "use strict";
809
+
810
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
811
+ exports.checkGithubImageCasing = void 0;
812
+ const promises_1 = __nccwpck_require__(91943);
813
+ const path_1 = __nccwpck_require__(16928);
814
+ const pipeline_1 = __nccwpck_require__(53695);
815
+ const secrets_1 = __nccwpck_require__(85200);
816
+ const WORKFLOWS_FOLDER = ".github/workflows";
817
+ const GENERATED_FILE_PREFIX = "catladder-";
818
+ /**
819
+ * the generated workflow files, or an empty list when none exist yet
820
+ */
821
+ const readGeneratedWorkflows = async () => {
822
+ const entries = await (0, promises_1.readdir)(WORKFLOWS_FOLDER).catch(() => []);
823
+ return Promise.all(entries
824
+ .filter((file) => file.startsWith(GENERATED_FILE_PREFIX))
825
+ .map((file) => (0, promises_1.readFile)((0, path_1.join)(WORKFLOWS_FOLDER, file), "utf8")));
826
+ };
827
+ /**
828
+ * ghcr image paths must be lowercase, but `${{ github.repository }}`
829
+ * interpolates github's display casing — so a repository whose owner or
830
+ * name contains an uppercase character needs a lowercased literal in the
831
+ * generated workflows. Generation bakes that in, which means committed
832
+ * workflows generated by an older catladder (or hand-edited ones) still
833
+ * carry the expression and fail every docker job with
834
+ * `repository name must be lowercase`.
835
+ *
836
+ * Unlike the rest of the github checks this needs no `gh` login — only
837
+ * the git remote and the committed files.
838
+ */
839
+ const checkGithubImageCasing = async (report, config) => {
840
+ if (!(0, pipeline_1.getEnabledPipelineTypes)(config).includes("github"))
841
+ return;
842
+ const repo = await (0, secrets_1.getConfiguredGithubRepo)();
843
+ if (!repo || repo === repo.toLowerCase()) {
844
+ // an all-lowercase repository keeps the expression on purpose (it
845
+ // stays correct in forks), and an unresolvable remote is reported
846
+ // by checkGithub already
847
+ return;
848
+ }
849
+ report.section("github image names");
850
+ const workflows = await readGeneratedWorkflows();
851
+ if (workflows.length === 0) {
852
+ report.warn(`no generated workflows in ${WORKFLOWS_FOLDER} to check`, "run: catenv");
853
+ return;
854
+ }
855
+ const stale = workflows.filter((content) => content.includes(pipeline_1.GHCR_REPOSITORY_EXPRESSION));
856
+ if (stale.length > 0) {
857
+ report.fail(`'${repo}' is mixed-case, but ${stale.length}/${workflows.length} generated workflows still use ${pipeline_1.GHCR_REPOSITORY_EXPRESSION} — ghcr rejects uppercase image paths, so every docker job fails with "repository name must be lowercase"`, "run: catenv (and commit the regenerated workflows)");
858
+ return;
859
+ }
860
+ const uppercase = workflows.flatMap((content) => [...content.matchAll(/ghcr\.io\/\S*[A-Z]\S*/g)].map(([match]) => match));
861
+ if (uppercase.length > 0) {
862
+ report.fail(`uppercase ghcr image paths in the generated workflows: ${[...new Set(uppercase)].join(", ")}`, `run: catenv — if it persists, set pipelines.github.repository (the '${(0, pipeline_1.getPipelineGitRemote)(config, "github")}' remote resolves to '${repo}')`);
863
+ return;
864
+ }
865
+ report.ok(`ghcr image paths lowercased for '${repo}'`);
866
+ };
867
+ exports.checkGithubImageCasing = checkGithubImageCasing;
868
+ //# sourceMappingURL=checkGithubImageCasing.js.map
869
+
870
+ /***/ }),
871
+
805
872
  /***/ 26904:
806
873
  /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
807
874
 
@@ -946,6 +1013,7 @@ const pipeline_1 = __nccwpck_require__(53695);
946
1013
  const getProjectConfig_1 = __nccwpck_require__(70478);
947
1014
  const checkCloudRun_1 = __nccwpck_require__(55869);
948
1015
  const checkGithub_1 = __nccwpck_require__(88480);
1016
+ const checkGithubImageCasing_1 = __nccwpck_require__(12668);
949
1017
  const checkGitlab_1 = __nccwpck_require__(26904);
950
1018
  const checkStore_1 = __nccwpck_require__(62878);
951
1019
  const DoctorReport_1 = __nccwpck_require__(87354);
@@ -981,6 +1049,7 @@ const doctorProject = async (instance, onlyComponents) => {
981
1049
  await (0, checkGitlab_1.checkGitlab)(instance, report, contexts);
982
1050
  }
983
1051
  await (0, checkCloudRun_1.checkCloudRun)(report, contexts);
1052
+ await (0, checkGithubImageCasing_1.checkGithubImageCasing)(report, config);
984
1053
  await (0, checkGithub_1.checkGithub)(report, config);
985
1054
  report.summarize();
986
1055
  };
@@ -1260,10 +1329,10 @@ const setupAccessTokens = async (instance) => {
1260
1329
  await (0, gitlab_1.doGitlabRequest)(instance, `projects/${projectId}/variables/GL_TOKEN`, { value: newToken.token }, "PUT");
1261
1330
  }
1262
1331
  catch (e) {
1263
- if (e.message !== "not found") {
1332
+ if (!(e instanceof gitlab_1.GitlabNotFoundError)) {
1264
1333
  throw e;
1265
1334
  }
1266
- // not found
1335
+ // the variable does not exist yet: create it
1267
1336
  await (0, gitlab_1.doGitlabRequest)(instance, `projects/${projectId}/variables`, {
1268
1337
  key: "GL_TOKEN",
1269
1338
  value: newToken.token,
@@ -1566,7 +1635,7 @@ const promises_1 = __nccwpck_require__(91943);
1566
1635
  const os_1 = __nccwpck_require__(70857);
1567
1636
  const path_1 = __nccwpck_require__(16928);
1568
1637
  const util_1 = __nccwpck_require__(39023);
1569
- const commandSecretsSyncGithub_1 = __nccwpck_require__(27565);
1638
+ const secrets_1 = __nccwpck_require__(85200);
1570
1639
  const github_1 = __nccwpck_require__(41353);
1571
1640
  const githubMergeGating_1 = __nccwpck_require__(14966);
1572
1641
  const execFile = (0, util_1.promisify)(child_process_1.execFile);
@@ -1600,7 +1669,7 @@ const setupGithub = async (instance, config) => {
1600
1669
  instance.log("⚠️ github cli (gh) not authenticated — skipping github merge-gating setup (run: gh auth login)");
1601
1670
  return;
1602
1671
  }
1603
- const repo = await (0, commandSecretsSyncGithub_1.getConfiguredGithubRepo)();
1672
+ const repo = await (0, secrets_1.getConfiguredGithubRepo)();
1604
1673
  if (!repo) {
1605
1674
  instance.log(`⚠️ no github repository found on the '${(0, pipeline_1.getPipelineGitRemote)(config, "github")}' remote — skipping github merge-gating setup`);
1606
1675
  return;
@@ -1770,15 +1839,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
1770
1839
  Object.defineProperty(exports, "__esModule", ({ value: true }));
1771
1840
  exports.setupKubernetes = void 0;
1772
1841
  const pipeline_1 = __nccwpck_require__(53695);
1842
+ const getProjectConfig_1 = __nccwpck_require__(70478);
1773
1843
  const child_process_promise_1 = __nccwpck_require__(11542);
1844
+ const secrets_1 = __nccwpck_require__(85200);
1774
1845
  const cluster_1 = __nccwpck_require__(8034);
1775
1846
  const gitlab_1 = __nccwpck_require__(17697);
1776
1847
  const ensureNamespace_1 = __importDefault(__nccwpck_require__(308));
1777
- const setupKubernetes = async (instance, context) => {
1778
- var _a;
1779
- const deployConfig = (_a = context.deploy) === null || _a === void 0 ? void 0 : _a.config;
1780
- if (!(0, pipeline_1.isOfDeployType)(deployConfig, "kubernetes")) {
1781
- throw new Error("cannot run setupKubernetes on non-kubernetes deployments");
1848
+ /**
1849
+ * the cluster pulls the images from the gitlab container registry, and
1850
+ * a deploy token is how it authenticates there. Nothing to do when the
1851
+ * project has no gitlab pipeline (the images live elsewhere then).
1852
+ */
1853
+ const ensureGitlabDeployToken = async (instance, config) => {
1854
+ if (!config || !(0, pipeline_1.getEnabledPipelineTypes)(config).includes("gitlab")) {
1855
+ return;
1782
1856
  }
1783
1857
  const { id: projectId } = await (0, gitlab_1.getProjectInfo)(instance);
1784
1858
  const deploy_tokens = await (0, gitlab_1.doGitlabRequest)(instance, `projects/${projectId}/deploy_tokens`);
@@ -1790,6 +1864,14 @@ const setupKubernetes = async (instance, context) => {
1790
1864
  scopes: ["read_registry"],
1791
1865
  }, "POST");
1792
1866
  }
1867
+ };
1868
+ const setupKubernetes = async (instance, context) => {
1869
+ var _a;
1870
+ const deployConfig = (_a = context.deploy) === null || _a === void 0 ? void 0 : _a.config;
1871
+ if (!(0, pipeline_1.isOfDeployType)(deployConfig, "kubernetes")) {
1872
+ throw new Error("cannot run setupKubernetes on non-kubernetes deployments");
1873
+ }
1874
+ await ensureGitlabDeployToken(instance, await (0, getProjectConfig_1.getProjectConfig)());
1793
1875
  const fullName = (0, pipeline_1.getFullKubernetesClusterName)(deployConfig.cluster);
1794
1876
  instance.log(`cluster: ${fullName}`);
1795
1877
  await (0, cluster_1.connectToCluster)(fullName);
@@ -1872,8 +1954,11 @@ EOF
1872
1954
  }
1873
1955
  instance.log("service accounts created / updated!");
1874
1956
  instance.log("");
1875
- instance.log("pusing secrets to gitlab...");
1876
- await (0, gitlab_1.upsertAllVariables)(instance, vars, context.env, context.name, false);
1957
+ instance.log("storing the cluster credentials...");
1958
+ await (0, secrets_1.writeSecretsAndMirror)(instance, [{ env: context.env, componentName: context.name, secrets: vars }],
1959
+ // the service account token is recreated on every run — the
1960
+ // previous one is worth nothing
1961
+ { backup: false });
1877
1962
  instance.log("done!");
1878
1963
  };
1879
1964
  exports.setupKubernetes = setupKubernetes;
@@ -2719,7 +2804,7 @@ exports.commandKubeStopPortforward = (0, defineCommand_1.defineCommand)({
2719
2804
  "use strict";
2720
2805
 
2721
2806
  Object.defineProperty(exports, "__esModule", ({ value: true }));
2722
- 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.commandDoctor = exports.commandSetup = exports.commandGetMyTotalWorktime = exports.commandRenewToken = exports.commandTriggerCronjobGeneral = exports.commandOpenLogs = exports.commandSecretsSyncGithub = exports.commandSecretsClearBackups = exports.commandSecretsList = exports.commandSecretsSet = exports.commandSecretsPush = exports.commandSecretsPull = 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;
2807
+ 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.commandMrUnpin = exports.commandMrPin = exports.commandSecurityEvaluate = exports.commandProjectRestoreDb = exports.commandCiJobLog = exports.commandCiJobOpen = exports.commandDoctor = exports.commandSetup = exports.commandGetMyTotalWorktime = exports.commandRenewToken = exports.commandTriggerCronjobGeneral = exports.commandOpenLogs = exports.commandSecretsSyncGithub = exports.commandSecretsClearBackups = exports.commandSecretsList = exports.commandSecretsSet = exports.commandSecretsPush = exports.commandSecretsPull = 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;
2723
2808
  // General commands
2724
2809
  var commandKubeCurrentContext_1 = __nccwpck_require__(78034);
2725
2810
  Object.defineProperty(exports, "commandKubeCurrentContext", ({ enumerable: true, get: function () { return commandKubeCurrentContext_1.commandKubeCurrentContext; } }));
@@ -2779,6 +2864,9 @@ var commandProjectRestoreDb_1 = __nccwpck_require__(69233);
2779
2864
  Object.defineProperty(exports, "commandProjectRestoreDb", ({ enumerable: true, get: function () { return commandProjectRestoreDb_1.commandProjectRestoreDb; } }));
2780
2865
  var commandSecurityEvaluate_1 = __nccwpck_require__(86851);
2781
2866
  Object.defineProperty(exports, "commandSecurityEvaluate", ({ enumerable: true, get: function () { return commandSecurityEvaluate_1.commandSecurityEvaluate; } }));
2867
+ var commandMrPin_1 = __nccwpck_require__(31318);
2868
+ Object.defineProperty(exports, "commandMrPin", ({ enumerable: true, get: function () { return commandMrPin_1.commandMrPin; } }));
2869
+ Object.defineProperty(exports, "commandMrUnpin", ({ enumerable: true, get: function () { return commandMrPin_1.commandMrUnpin; } }));
2782
2870
  // Project k8s commands
2783
2871
  var commandNamespace_1 = __nccwpck_require__(25918);
2784
2872
  Object.defineProperty(exports, "commandNamespace", ({ enumerable: true, get: function () { return commandNamespace_1.commandNamespace; } }));
@@ -3175,6 +3263,7 @@ const common_tags_1 = __nccwpck_require__(110);
3175
3263
  const defineCommand_1 = __nccwpck_require__(19096);
3176
3264
  const editAsFile_1 = __nccwpck_require__(3072);
3177
3265
  const promise_1 = __nccwpck_require__(23161);
3266
+ const secrets_1 = __nccwpck_require__(85200);
3178
3267
  const document_1 = __nccwpck_require__(96716);
3179
3268
  const scope_1 = __nccwpck_require__(8513);
3180
3269
  const parseKeysInput_1 = __nccwpck_require__(93560);
@@ -3237,7 +3326,7 @@ exports.commandConfigSecrets = (0, defineCommand_1.defineCommand)({
3237
3326
  ctx.log("⏭️ these secrets are still unset and will not be written:");
3238
3327
  skipped.forEach((entry) => ctx.log(` - ${entry}`));
3239
3328
  }
3240
- await (0, document_1.writeSecretsAndMirror)(ctx, writes);
3329
+ await (0, secrets_1.writeSecretsAndMirror)(ctx, writes);
3241
3330
  ctx.log("done! 😻");
3242
3331
  ctx.log("");
3243
3332
  },
@@ -3442,6 +3531,132 @@ exports.commandCiJobLog = (0, defineCommand_1.defineCommand)({
3442
3531
 
3443
3532
  /***/ }),
3444
3533
 
3534
+ /***/ 31318:
3535
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
3536
+
3537
+ "use strict";
3538
+
3539
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
3540
+ exports.commandMrUnpin = exports.commandMrPin = void 0;
3541
+ const child_process_promise_1 = __nccwpck_require__(11542);
3542
+ const pipeline_1 = __nccwpck_require__(53695);
3543
+ const getProjectConfig_1 = __nccwpck_require__(70478);
3544
+ const defineCommand_1 = __nccwpck_require__(19096);
3545
+ const gitlab_1 = __nccwpck_require__(17697);
3546
+ const PIN_LABEL_COLOR = "#428fdc";
3547
+ /**
3548
+ * the configured pin label — errors when the project cannot pin
3549
+ * (github-only, or pinning disabled)
3550
+ */
3551
+ const getPinLabel = async () => {
3552
+ const config = await (0, getProjectConfig_1.getProjectConfig)();
3553
+ if (!config) {
3554
+ throw new Error("no catladder config found");
3555
+ }
3556
+ if (!(0, pipeline_1.getEnabledPipelineTypes)(config).includes("gitlab")) {
3557
+ throw new Error("pinning review apps needs the gitlab pipeline — github has no auto-stop, review apps live until the pull request is closed");
3558
+ }
3559
+ const { pinLabel } = (0, pipeline_1.getAutoStopConfig)(config);
3560
+ if (pinLabel === false) {
3561
+ throw new Error("review-app pinning is disabled in this project (autoStop.pinLabel: false)");
3562
+ }
3563
+ return pinLabel;
3564
+ };
3565
+ const findMr = async (io, projectId, mrIid) => {
3566
+ if (mrIid) {
3567
+ return (0, gitlab_1.doGitlabRequest)(io, `projects/${projectId}/merge_requests/${mrIid}`);
3568
+ }
3569
+ const branch = (await (0, child_process_promise_1.exec)("git rev-parse --abbrev-ref HEAD")).stdout.trim();
3570
+ const mrs = await (0, gitlab_1.doGitlabRequest)(io, `projects/${projectId}/merge_requests?state=opened&source_branch=${encodeURIComponent(branch)}`);
3571
+ if (!(mrs === null || mrs === void 0 ? void 0 : mrs.length)) {
3572
+ throw new Error(`no open merge request found for branch '${branch}' — pass one with --mr <iid>`);
3573
+ }
3574
+ return mrs[0];
3575
+ };
3576
+ const ensurePinLabelExists = async (io, projectId, pinLabel) => {
3577
+ const existing = await (0, gitlab_1.doGitlabRequest)(io, `projects/${projectId}/labels?search=${encodeURIComponent(pinLabel)}`);
3578
+ if (existing === null || existing === void 0 ? void 0 : existing.some((label) => label.name === pinLabel)) {
3579
+ return;
3580
+ }
3581
+ await (0, gitlab_1.doGitlabRequest)(io, `projects/${projectId}/labels`, {
3582
+ name: pinLabel,
3583
+ color: PIN_LABEL_COLOR,
3584
+ description: "review apps of this MR are pinned — deploys use auto_stop_in: never (managed by catladder)",
3585
+ }, "POST");
3586
+ io.log(`created the '${pinLabel}' label in the project`);
3587
+ };
3588
+ exports.commandMrPin = (0, defineCommand_1.defineCommand)({
3589
+ name: "mr pin",
3590
+ description: "pin the review apps of a merge request (disable their auto-stop) by adding the pin label; merging or closing the MR still stops them",
3591
+ group: "mr",
3592
+ inputs: {
3593
+ mr: {
3594
+ type: "number",
3595
+ message: "merge request IID (defaults to the open MR of the current branch)",
3596
+ positional: true,
3597
+ required: false,
3598
+ },
3599
+ pipeline: {
3600
+ type: "boolean",
3601
+ message: "trigger a merge-request pipeline so the pin takes effect immediately",
3602
+ required: false,
3603
+ },
3604
+ },
3605
+ execute: async (ctx) => {
3606
+ var _a, _b;
3607
+ const pinLabel = await getPinLabel();
3608
+ const { id: projectId } = await (0, gitlab_1.getProjectInfo)(ctx);
3609
+ const mr = await findMr(ctx, projectId, await ctx.get("mr"));
3610
+ await ensurePinLabelExists(ctx, projectId, pinLabel);
3611
+ if ((_a = mr.labels) === null || _a === void 0 ? void 0 : _a.includes(pinLabel)) {
3612
+ ctx.log(`already pinned: mr!${mr.iid} carries the '${pinLabel}' label`);
3613
+ }
3614
+ else {
3615
+ await (0, gitlab_1.doGitlabRequest)(ctx, `projects/${projectId}/merge_requests/${mr.iid}`, { add_labels: pinLabel }, "PUT");
3616
+ ctx.log(`📌 pinned mr!${mr.iid} "${mr.title}" — label '${pinLabel}' added`);
3617
+ }
3618
+ // the label only affects pipelines created after it was set, so
3619
+ // trigger one — otherwise the currently deployed review apps keep
3620
+ // their old auto-stop timer until the next push
3621
+ if ((_b = (await ctx.get("pipeline"))) !== null && _b !== void 0 ? _b : true) {
3622
+ const pipeline = await (0, gitlab_1.doGitlabRequest)(ctx, `projects/${projectId}/merge_requests/${mr.iid}/pipelines`, {}, "POST");
3623
+ ctx.log(`🚀 triggered a pipeline so the pin takes effect now: ${pipeline.web_url}`);
3624
+ }
3625
+ else {
3626
+ ctx.log("the pin takes effect with the next pipeline of the MR");
3627
+ }
3628
+ },
3629
+ });
3630
+ exports.commandMrUnpin = (0, defineCommand_1.defineCommand)({
3631
+ name: "mr unpin",
3632
+ description: "unpin the review apps of a merge request — the auto-stop timer re-arms with the next deploy",
3633
+ group: "mr",
3634
+ inputs: {
3635
+ mr: {
3636
+ type: "number",
3637
+ message: "merge request IID (defaults to the open MR of the current branch)",
3638
+ positional: true,
3639
+ required: false,
3640
+ },
3641
+ },
3642
+ execute: async (ctx) => {
3643
+ var _a;
3644
+ const pinLabel = await getPinLabel();
3645
+ const { id: projectId } = await (0, gitlab_1.getProjectInfo)(ctx);
3646
+ const mr = await findMr(ctx, projectId, await ctx.get("mr"));
3647
+ if (!((_a = mr.labels) === null || _a === void 0 ? void 0 : _a.includes(pinLabel))) {
3648
+ ctx.log(`mr!${mr.iid} is not pinned (no '${pinLabel}' label)`);
3649
+ return;
3650
+ }
3651
+ await (0, gitlab_1.doGitlabRequest)(ctx, `projects/${projectId}/merge_requests/${mr.iid}`, { remove_labels: pinLabel }, "PUT");
3652
+ ctx.log(`unpinned mr!${mr.iid} — label '${pinLabel}' removed`);
3653
+ ctx.log("the deployed review apps stay pinned until the next deploy re-arms their auto-stop timer");
3654
+ },
3655
+ });
3656
+ //# sourceMappingURL=commandMrPin.js.map
3657
+
3658
+ /***/ }),
3659
+
3445
3660
  /***/ 30119:
3446
3661
  /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
3447
3662
 
@@ -3874,131 +4089,12 @@ exports.commandSecretsClearBackups = (0, defineCommand_1.defineCommand)({
3874
4089
  "use strict";
3875
4090
 
3876
4091
  Object.defineProperty(exports, "__esModule", ({ value: true }));
3877
- exports.commandSecretsSyncGithub = exports.getConfiguredGithubRepo = exports.pushSecretsToGithub = exports.collectSecretTargets = exports.collectSecretsFromVault = void 0;
4092
+ exports.commandSecretsSyncGithub = void 0;
3878
4093
  const pipeline_1 = __nccwpck_require__(53695);
3879
4094
  const getProjectConfig_1 = __nccwpck_require__(70478);
3880
4095
  const defineCommand_1 = __nccwpck_require__(19096);
4096
+ const secrets_1 = __nccwpck_require__(85200);
3881
4097
  const github_1 = __nccwpck_require__(41353);
3882
- /**
3883
- * all secrets declared in the config, with their values read from the
3884
- * vault (github secrets are write-only and can never act as a source)
3885
- */
3886
- const collectSecretsFromVault = async (io, envFilter) => {
3887
- const envAndComponents = await (0, getProjectConfig_1.getAllComponentsWithAllEnvsHierarchical)();
3888
- const kinds = new Map();
3889
- for (const [componentName, envs] of Object.entries(envAndComponents)) {
3890
- for (const env of envs) {
3891
- if (envFilter && !envFilter.includes(env)) {
3892
- continue;
3893
- }
3894
- const { secretEnvVarKeys, jobOnlyVars } = await (0, getProjectConfig_1.getEnvironment)(env, componentName);
3895
- const keys = [
3896
- ...secretEnvVarKeys,
3897
- ...jobOnlyVars.build.secretEnvVarKeys,
3898
- ...jobOnlyVars.deploy.secretEnvVarKeys,
3899
- ];
3900
- keys.forEach(({ key, kind }) => kinds.set((0, pipeline_1.getSecretVarName)(env, componentName, key), kind !== null && kind !== void 0 ? kind : "secret"));
3901
- }
3902
- }
3903
- const names = [...kinds.keys()];
3904
- const values = await (await io.getVaultManager()).readSecrets(names, io);
3905
- const secrets = names
3906
- .filter((name) => values[name] !== undefined)
3907
- .map((name) => {
3908
- var _a;
3909
- return ({
3910
- name,
3911
- value: values[name],
3912
- kind: (_a = kinds.get(name)) !== null && _a !== void 0 ? _a : "secret",
3913
- });
3914
- });
3915
- const missing = names.filter((name) => values[name] === undefined);
3916
- return { secrets, missing };
3917
- };
3918
- exports.collectSecretsFromVault = collectSecretsFromVault;
3919
- /** marker for repo-level (environment-less) targets */
3920
- const REPO_LEVEL = "";
3921
- /**
3922
- * derives which github environment(s) each secret/variable must live in
3923
- * by scanning the GENERATED workflows: a `${{ secrets.X }}` / `${{
3924
- * vars.X }}` reference in a job's env targets that job's environment
3925
- * (or the repo level when the job has none). Deriving from the real
3926
- * output means the sync can never drift from what the workflows expect.
3927
- *
3928
- * Environment secrets are the answer to github's cap of 100 secrets
3929
- * per REPO (a multi-env project easily exceeds it): the cap applies
3930
- * per environment instead.
3931
- */
3932
- const collectSecretTargets = async (config) => {
3933
- var _a;
3934
- const workflows = await new pipeline_1.GithubBackend().createWorkflows(config);
3935
- const targets = new Map();
3936
- for (const workflow of Object.values(workflows)) {
3937
- for (const job of Object.values(workflow.jobs)) {
3938
- const environment = typeof job.environment === "object"
3939
- ? job.environment.name
3940
- : job.environment;
3941
- for (const value of Object.values((_a = job.env) !== null && _a !== void 0 ? _a : {})) {
3942
- if (typeof value !== "string")
3943
- continue;
3944
- for (const match of value.matchAll(/\$\{\{\s*(?:secrets|vars)\.([A-Za-z0-9_]+)\s*\}\}/g)) {
3945
- const name = match[1];
3946
- if (!targets.has(name)) {
3947
- targets.set(name, new Set());
3948
- }
3949
- targets.get(name).add(environment !== null && environment !== void 0 ? environment : REPO_LEVEL);
3950
- }
3951
- }
3952
- }
3953
- }
3954
- return targets;
3955
- };
3956
- exports.collectSecretTargets = collectSecretTargets;
3957
- /**
3958
- * pushes vault values to github via the gh cli: sensitive ones as
3959
- * secrets (masked), "variable"-kind ones as variables (plain) — each
3960
- * into the environment(s) whose jobs reference them
3961
- */
3962
- const pushSecretsToGithub = async (io, repo, secrets, targets) => {
3963
- const environments = [
3964
- ...new Set([...targets.values()].flatMap((envs) => [...envs].filter((env) => env !== REPO_LEVEL))),
3965
- ];
3966
- for (const environment of environments) {
3967
- await (0, github_1.ensureGithubEnvironment)(repo, environment);
3968
- io.log(`🌍 environment ${environment}`);
3969
- }
3970
- for (const { name, value, kind } of secrets) {
3971
- const envs = targets.get(name);
3972
- if (!envs) {
3973
- io.log(`⏭️ ${name} (not referenced by any generated workflow)`);
3974
- continue;
3975
- }
3976
- for (const environment of envs) {
3977
- const suffix = environment === REPO_LEVEL ? "" : ` → ${environment}`;
3978
- if (kind === "variable") {
3979
- await (0, github_1.setGithubVariable)(repo, name, value, environment || undefined);
3980
- io.log(`📖 ${name} (variable)${suffix}`);
3981
- }
3982
- else {
3983
- await (0, github_1.setGithubSecret)(repo, name, value, environment || undefined);
3984
- io.log(`✅ ${name}${suffix}`);
3985
- }
3986
- }
3987
- }
3988
- };
3989
- exports.pushSecretsToGithub = pushSecretsToGithub;
3990
- /**
3991
- * resolves the github repository from the configured git remote
3992
- * (`pipelines.github.gitRemote`, defaults to origin)
3993
- */
3994
- const getConfiguredGithubRepo = async () => {
3995
- const config = await (0, getProjectConfig_1.getProjectConfig)();
3996
- if (!config) {
3997
- return undefined;
3998
- }
3999
- return (0, github_1.getGithubRepoFromRemote)((0, pipeline_1.getPipelineGitRemote)(config, "github"));
4000
- };
4001
- exports.getConfiguredGithubRepo = getConfiguredGithubRepo;
4002
4098
  /**
4003
4099
  * copies the catladder secrets from the vault to github actions
4004
4100
  * repository secrets, keeping the exact same names, so the github
@@ -4035,12 +4131,12 @@ exports.commandSecretsSyncGithub = (0, defineCommand_1.defineCommand)({
4035
4131
  if (!(await (0, github_1.isGhAuthenticated)())) {
4036
4132
  throw new Error("the github cli (gh) is not installed or not authenticated — run `gh auth login` first");
4037
4133
  }
4038
- const repo = (_a = (await ctx.get("repo"))) !== null && _a !== void 0 ? _a : (await (0, exports.getConfiguredGithubRepo)());
4134
+ const repo = (_a = (await ctx.get("repo"))) !== null && _a !== void 0 ? _a : (await (0, secrets_1.getConfiguredGithubRepo)());
4039
4135
  if (!repo) {
4040
4136
  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`);
4041
4137
  }
4042
4138
  const envFilter = (_b = (await ctx.get("env"))) === null || _b === void 0 ? void 0 : _b.split(",").map((e) => e.trim());
4043
- const { secrets, missing } = await (0, exports.collectSecretsFromVault)(ctx, envFilter);
4139
+ const { secrets, missing } = await (0, secrets_1.collectSecretsFromVault)(ctx, envFilter);
4044
4140
  if (missing.length > 0) {
4045
4141
  ctx.log(`⚠️ ${missing.length} secrets are not set in the vault and will be skipped:`);
4046
4142
  missing.forEach((name) => ctx.log(` - ${name}`));
@@ -4057,7 +4153,7 @@ exports.commandSecretsSyncGithub = (0, defineCommand_1.defineCommand)({
4057
4153
  if (!confirmed) {
4058
4154
  throw new Error("abort");
4059
4155
  }
4060
- await (0, exports.pushSecretsToGithub)(ctx, repo, secrets, await (0, exports.collectSecretTargets)(config));
4156
+ await (0, secrets_1.pushSecretsToGithub)(ctx, repo, secrets, await (0, secrets_1.collectSecretTargets)(config));
4061
4157
  ctx.log("");
4062
4158
  ctx.log("done! 😻 the github pipeline can use them as ${{ secrets.<NAME> }}");
4063
4159
  },
@@ -4621,6 +4717,7 @@ const fs_extra_1 = __nccwpck_require__(20925);
4621
4717
  const yaml_1 = __nccwpck_require__(99292);
4622
4718
  const defineCommand_1 = __nccwpck_require__(19096);
4623
4719
  const readStdin_1 = __nccwpck_require__(89973);
4720
+ const secrets_1 = __nccwpck_require__(85200);
4624
4721
  const document_1 = __nccwpck_require__(96716);
4625
4722
  const scope_1 = __nccwpck_require__(8513);
4626
4723
  const parseKeysInput_1 = __nccwpck_require__(93560);
@@ -4683,7 +4780,7 @@ exports.commandSecretsPush = (0, defineCommand_1.defineCommand)({
4683
4780
  if (!confirmed) {
4684
4781
  throw new Error("abort");
4685
4782
  }
4686
- await (0, document_1.writeSecretsAndMirror)(ctx, writes);
4783
+ await (0, secrets_1.writeSecretsAndMirror)(ctx, writes);
4687
4784
  ctx.log("done! 😻");
4688
4785
  },
4689
4786
  });
@@ -4701,7 +4798,7 @@ exports.commandSecretsSet = void 0;
4701
4798
  const fs_extra_1 = __nccwpck_require__(20925);
4702
4799
  const defineCommand_1 = __nccwpck_require__(19096);
4703
4800
  const readStdin_1 = __nccwpck_require__(89973);
4704
- const document_1 = __nccwpck_require__(96716);
4801
+ const secrets_1 = __nccwpck_require__(85200);
4705
4802
  const scope_1 = __nccwpck_require__(8513);
4706
4803
  const resolveValue = async (io, value, valueFile) => {
4707
4804
  if (valueFile) {
@@ -4772,7 +4869,7 @@ exports.commandSecretsSet = (0, defineCommand_1.defineCommand)({
4772
4869
  componentName,
4773
4870
  secrets: { [key]: value },
4774
4871
  }));
4775
- await (0, document_1.writeSecretsAndMirror)(ctx, writes);
4872
+ await (0, secrets_1.writeSecretsAndMirror)(ctx, writes);
4776
4873
  ctx.log("done! 😻");
4777
4874
  },
4778
4875
  });
@@ -4786,12 +4883,8 @@ exports.commandSecretsSet = (0, defineCommand_1.defineCommand)({
4786
4883
  "use strict";
4787
4884
 
4788
4885
  Object.defineProperty(exports, "__esModule", ({ value: true }));
4789
- exports.writeSecretsAndMirror = exports.collectSecretsWrites = exports.checkSecretsDocument = exports.buildSecretsDocument = exports.FILL_ME = void 0;
4886
+ exports.collectSecretsWrites = exports.checkSecretsDocument = exports.buildSecretsDocument = exports.FILL_ME = void 0;
4790
4887
  const pipeline_1 = __nccwpck_require__(53695);
4791
- const getProjectConfig_1 = __nccwpck_require__(70478);
4792
- const github_1 = __nccwpck_require__(41353);
4793
- const gitlab_1 = __nccwpck_require__(17697);
4794
- const commandSecretsSyncGithub_1 = __nccwpck_require__(27565);
4795
4888
  /** placeholder for secrets that have no value in the vault yet */
4796
4889
  exports.FILL_ME = "🚨 FILL ME";
4797
4890
  /* for convenience, parse json values. that makes it easier to edit secrets that are objects */
@@ -4889,80 +4982,6 @@ const collectSecretsWrites = (document, scoped) => {
4889
4982
  return { writes, skipped };
4890
4983
  };
4891
4984
  exports.collectSecretsWrites = collectSecretsWrites;
4892
- /**
4893
- * writes secrets to the vault (the source of truth) and mirrors them
4894
- * to every enabled CI backend — which backends exist is invisible to
4895
- * the caller
4896
- */
4897
- const writeSecretsAndMirror = async (io, writes) => {
4898
- const config = await (0, getProjectConfig_1.getProjectConfig)();
4899
- if (!config) {
4900
- throw new Error("no catladder config found");
4901
- }
4902
- io.log("");
4903
- io.log("writing secrets to the vault, please wait...");
4904
- io.log("");
4905
- for (const { env, componentName, secrets } of writes) {
4906
- io.log("writing " + env + ":" + componentName + "...\n");
4907
- await (await io.getVaultManager()).writeSecrets(env, componentName, secrets);
4908
- io.log("");
4909
- io.log("✅ " + env + ":" + componentName);
4910
- io.log("--------------------------------\n");
4911
- }
4912
- await mirrorSecretsToCiBackends(io, config, writes);
4913
- };
4914
- exports.writeSecretsAndMirror = writeSecretsAndMirror;
4915
- /**
4916
- * the vault is the source of truth; every enabled CI backend receives
4917
- * a mirrored copy of the written secrets (gitlab variables, github
4918
- * secrets), so all CI systems stay in sync
4919
- */
4920
- const mirrorSecretsToCiBackends = async (io, config, writes) => {
4921
- const enabled = (0, pipeline_1.getEnabledPipelineTypes)(config);
4922
- // gitlab mirror (unless gitlab is the vault itself, then it is
4923
- // already written)
4924
- if ((0, pipeline_1.getVaultConfig)(config).type !== "gitlab" && enabled.includes("gitlab")) {
4925
- for (const { env, componentName, secrets } of writes) {
4926
- await (0, gitlab_1.upsertAllVariables)(io, secrets, env, componentName);
4927
- }
4928
- io.log("✅ mirrored to gitlab");
4929
- }
4930
- if (!enabled.includes("github")) {
4931
- return;
4932
- }
4933
- const kinds = new Map();
4934
- for (const { env, componentName } of writes) {
4935
- const { secretEnvVarKeys, jobOnlyVars } = await (0, getProjectConfig_1.getEnvironment)(env, componentName);
4936
- [
4937
- ...secretEnvVarKeys,
4938
- ...jobOnlyVars.build.secretEnvVarKeys,
4939
- ...jobOnlyVars.deploy.secretEnvVarKeys,
4940
- ].forEach(({ key, kind }) => kinds.set((0, pipeline_1.getSecretVarName)(env, componentName, key), kind !== null && kind !== void 0 ? kind : "secret"));
4941
- }
4942
- const secrets = writes.flatMap(({ env, componentName, secrets }) => Object.entries(secrets).map(([key, value]) => {
4943
- var _a;
4944
- const name = (0, pipeline_1.getSecretVarName)(env, componentName, key);
4945
- return {
4946
- name,
4947
- value: typeof value === "string" ? value : JSON.stringify(value !== null && value !== void 0 ? value : ""),
4948
- kind: (_a = kinds.get(name)) !== null && _a !== void 0 ? _a : "secret",
4949
- };
4950
- }));
4951
- if (secrets.length === 0) {
4952
- return;
4953
- }
4954
- if (!(await (0, github_1.isGhAuthenticated)())) {
4955
- io.log("⚠️ github pipeline is enabled, but the github cli (gh) is not authenticated — run `project secrets-sync-github` later to mirror the secrets");
4956
- return;
4957
- }
4958
- const repo = await (0, commandSecretsSyncGithub_1.getConfiguredGithubRepo)();
4959
- if (!repo) {
4960
- io.log("⚠️ github pipeline is enabled, but no github repository was found — run `project secrets-sync-github` later to mirror the secrets");
4961
- return;
4962
- }
4963
- io.log(`mirroring ${secrets.length} secrets to github ('${repo}')...`);
4964
- await (0, commandSecretsSyncGithub_1.pushSecretsToGithub)(io, repo, secrets, await (0, commandSecretsSyncGithub_1.collectSecretTargets)(config));
4965
- };
4966
4985
  //# sourceMappingURL=document.js.map
4967
4986
 
4968
4987
  /***/ }),
@@ -6060,7 +6079,7 @@ const hash = (str, length) => {
6060
6079
  Object.defineProperty(exports, "__esModule", ({ value: true }));
6061
6080
  exports.upsertGcloudServiceAccountAndSaveSecret = exports.accountExists = void 0;
6062
6081
  const child_process_promise_1 = __nccwpck_require__(11542);
6063
- const gitlab_1 = __nccwpck_require__(17697);
6082
+ const secrets_1 = __nccwpck_require__(85200);
6064
6083
  const serviceAccountNames_1 = __nccwpck_require__(67851);
6065
6084
  const accountExists = async (fullIdentifier) => {
6066
6085
  try {
@@ -6097,9 +6116,20 @@ const upsertGcloudServiceAccount = async (context, account) => {
6097
6116
  const upsertGcloudServiceAccountAndSaveSecret = async (instance, context, account, secretName) => {
6098
6117
  instance.log("upserting service account " + account.name + "...");
6099
6118
  const key = await upsertGcloudServiceAccount(context, account);
6100
- await (0, gitlab_1.upsertAllVariables)(instance, {
6101
- [secretName]: key,
6102
- }, context.env, context.name);
6119
+ // through the vault (and from there to every enabled ci backend)
6120
+ // the key is a secret like any other, the deploy jobs read it under
6121
+ // its declared name. Writing it to gitlab directly would break every
6122
+ // project that has no gitlab.
6123
+ await (0, secrets_1.writeSecretsAndMirror)(instance, [
6124
+ {
6125
+ env: context.env,
6126
+ componentName: context.name,
6127
+ secrets: { [secretName]: key },
6128
+ },
6129
+ ],
6130
+ // a fresh key is created on every run, the previous one is deleted
6131
+ // at google — a backup copy of it would only be a dead secret
6132
+ { backup: false });
6103
6133
  instance.log("done!");
6104
6134
  };
6105
6135
  exports.upsertGcloudServiceAccountAndSaveSecret = upsertGcloudServiceAccountAndSaveSecret;
@@ -6279,97 +6309,352 @@ const getProjectPvcs = async (envComponent) => {
6279
6309
  const res = await k8sApi.listNamespacedPersistentVolumeClaim(namespace);
6280
6310
  return res.body.items;
6281
6311
  };
6282
- exports.getProjectPvcs = getProjectPvcs;
6283
- const getProjectPodNames = async (envComponent) => {
6284
- const pods = await (0, exports.getProjectPods)(envComponent);
6285
- return pods.map((n) => n.metadata.name);
6312
+ exports.getProjectPvcs = getProjectPvcs;
6313
+ const getProjectPodNames = async (envComponent) => {
6314
+ const pods = await (0, exports.getProjectPods)(envComponent);
6315
+ return pods.map((n) => n.metadata.name);
6316
+ };
6317
+ exports.getProjectPodNames = getProjectPodNames;
6318
+ //# sourceMappingURL=index.js.map
6319
+
6320
+ /***/ }),
6321
+
6322
+ /***/ 14178:
6323
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
6324
+
6325
+ "use strict";
6326
+
6327
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
6328
+ exports.openGoogleCloudKubernetesDashboard = exports.openGoogleCloudLogs = void 0;
6329
+ const pipeline_1 = __nccwpck_require__(53695);
6330
+ const openDashboard_1 = __nccwpck_require__(74241);
6331
+ const openGoogleCloudLogs = async (instance, context) => {
6332
+ var _a;
6333
+ const deployConfig = (_a = context.deploy) === null || _a === void 0 ? void 0 : _a.config;
6334
+ if (!(0, pipeline_1.isOfDeployType)(deployConfig, "kubernetes")) {
6335
+ throw new Error("context is not of type kubernetes");
6336
+ }
6337
+ if (!deployConfig.cluster || deployConfig.cluster.type !== "gcloud") {
6338
+ throw new Error("no gcloud custer configured");
6339
+ }
6340
+ const namespace = context.environment.envVars.KUBE_NAMESPACE;
6341
+ const cluster = deployConfig.cluster;
6342
+ const resource = `k8s_container/cluster_name/${cluster.name}${namespace ? `/namespace_name/${namespace}` : ""}`;
6343
+ await (0, openDashboard_1.openGoogleCloudDashboard)(instance, "logs/viewer", {
6344
+ project: cluster.projectId,
6345
+ resource: resource,
6346
+ });
6347
+ };
6348
+ exports.openGoogleCloudLogs = openGoogleCloudLogs;
6349
+ const openGoogleCloudKubernetesDashboard = async (instance, context) => {
6350
+ var _a;
6351
+ const deployConfig = (_a = context.deploy) === null || _a === void 0 ? void 0 : _a.config;
6352
+ if (!(0, pipeline_1.isOfDeployType)(deployConfig, "kubernetes")) {
6353
+ throw new Error("context is not of type kubernetes");
6354
+ }
6355
+ if (!deployConfig.cluster || deployConfig.cluster.type !== "gcloud") {
6356
+ throw new Error("no gcloud custer configured");
6357
+ }
6358
+ const namespace = context.environment.envVars.KUBE_NAMESPACE;
6359
+ const cluster = deployConfig.cluster;
6360
+ const pageState = `("savedViews":("c":["gke/${cluster.region}/${cluster.name}"],"n":["${namespace}"],"i":"4e42e0b9cd6147f8a4fba7516752ec48"))`;
6361
+ await (0, openDashboard_1.openGoogleCloudDashboard)(instance, "kubernetes/workload", {
6362
+ project: cluster.projectId,
6363
+ pageState: pageState,
6364
+ });
6365
+ };
6366
+ exports.openGoogleCloudKubernetesDashboard = openGoogleCloudKubernetesDashboard;
6367
+ //# sourceMappingURL=openKubernetesDashboards.js.map
6368
+
6369
+ /***/ }),
6370
+
6371
+ /***/ 76775:
6372
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
6373
+
6374
+ "use strict";
6375
+
6376
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
6377
+ exports.startKubePortForward = void 0;
6378
+ const portForwards_1 = __nccwpck_require__(5368);
6379
+ const startKubePortForward = async (podname, localPort, remotePort, namespace) => {
6380
+ const name = `kube/${namespace}/${podname}/${localPort}:${remotePort}`;
6381
+ await (0, portForwards_1.startPortForwardCommand)(name, `kubectl port-forward ${podname} ${localPort}:${remotePort} -n ${namespace}`);
6382
+ };
6383
+ exports.startKubePortForward = startKubePortForward;
6384
+ //# sourceMappingURL=portForward.js.map
6385
+
6386
+ /***/ }),
6387
+
6388
+ /***/ 55689:
6389
+ /***/ ((__unused_webpack_module, exports) => {
6390
+
6391
+ "use strict";
6392
+
6393
+ // we can't import the package.json directly, because the bundling process will inline it
6394
+ // but the version is only set during deployement, when its already bundled
6395
+ // instead we need to use require.main.path to get the path of the main module, which is actually the file in the bin
6396
+ // this will point to the root package.json
6397
+ var _a;
6398
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
6399
+ const path = ((_a = require.main) === null || _a === void 0 ? void 0 : _a.path) + "/../package.json";
6400
+ const ownPkg = require(path);
6401
+ exports["default"] = ownPkg;
6402
+ //# sourceMappingURL=packageInfos.js.map
6403
+
6404
+ /***/ }),
6405
+
6406
+ /***/ 97245:
6407
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
6408
+
6409
+ "use strict";
6410
+
6411
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
6412
+ exports.getConfiguredGithubRepo = exports.pushSecretsToGithub = exports.collectSecretTargets = exports.collectSecretsFromVault = void 0;
6413
+ const pipeline_1 = __nccwpck_require__(53695);
6414
+ const getProjectConfig_1 = __nccwpck_require__(70478);
6415
+ const github_1 = __nccwpck_require__(41353);
6416
+ /**
6417
+ * all secrets declared in the config, with their values read from the
6418
+ * vault (github secrets are write-only and can never act as a source)
6419
+ */
6420
+ const collectSecretsFromVault = async (io, envFilter) => {
6421
+ const envAndComponents = await (0, getProjectConfig_1.getAllComponentsWithAllEnvsHierarchical)();
6422
+ const kinds = new Map();
6423
+ for (const [componentName, envs] of Object.entries(envAndComponents)) {
6424
+ for (const env of envs) {
6425
+ if (envFilter && !envFilter.includes(env)) {
6426
+ continue;
6427
+ }
6428
+ const { secretEnvVarKeys, jobOnlyVars } = await (0, getProjectConfig_1.getEnvironment)(env, componentName);
6429
+ const keys = [
6430
+ ...secretEnvVarKeys,
6431
+ ...jobOnlyVars.build.secretEnvVarKeys,
6432
+ ...jobOnlyVars.deploy.secretEnvVarKeys,
6433
+ ];
6434
+ keys.forEach(({ key, kind }) => kinds.set((0, pipeline_1.getSecretVarName)(env, componentName, key), kind !== null && kind !== void 0 ? kind : "secret"));
6435
+ }
6436
+ }
6437
+ const names = [...kinds.keys()];
6438
+ const values = await (await io.getVaultManager()).readSecrets(names, io);
6439
+ const secrets = names
6440
+ .filter((name) => values[name] !== undefined)
6441
+ .map((name) => {
6442
+ var _a;
6443
+ return ({
6444
+ name,
6445
+ value: values[name],
6446
+ kind: (_a = kinds.get(name)) !== null && _a !== void 0 ? _a : "secret",
6447
+ });
6448
+ });
6449
+ const missing = names.filter((name) => values[name] === undefined);
6450
+ return { secrets, missing };
6451
+ };
6452
+ exports.collectSecretsFromVault = collectSecretsFromVault;
6453
+ /** marker for repo-level (environment-less) targets */
6454
+ const REPO_LEVEL = "";
6455
+ /**
6456
+ * derives which github environment(s) each secret/variable must live in
6457
+ * by scanning the GENERATED workflows: a `${{ secrets.X }}` / `${{
6458
+ * vars.X }}` reference in a job's env targets that job's environment
6459
+ * (or the repo level when the job has none). Deriving from the real
6460
+ * output means the sync can never drift from what the workflows expect.
6461
+ *
6462
+ * Environment secrets are the answer to github's cap of 100 secrets
6463
+ * per REPO (a multi-env project easily exceeds it): the cap applies
6464
+ * per environment instead.
6465
+ */
6466
+ const collectSecretTargets = async (config) => {
6467
+ var _a;
6468
+ const workflows = await new pipeline_1.GithubBackend().createWorkflows(config);
6469
+ const targets = new Map();
6470
+ for (const workflow of Object.values(workflows)) {
6471
+ for (const job of Object.values(workflow.jobs)) {
6472
+ const environment = typeof job.environment === "object"
6473
+ ? job.environment.name
6474
+ : job.environment;
6475
+ for (const value of Object.values((_a = job.env) !== null && _a !== void 0 ? _a : {})) {
6476
+ if (typeof value !== "string")
6477
+ continue;
6478
+ for (const match of value.matchAll(/\$\{\{\s*(?:secrets|vars)\.([A-Za-z0-9_]+)\s*\}\}/g)) {
6479
+ const name = match[1];
6480
+ if (!targets.has(name)) {
6481
+ targets.set(name, new Set());
6482
+ }
6483
+ targets.get(name).add(environment !== null && environment !== void 0 ? environment : REPO_LEVEL);
6484
+ }
6485
+ }
6486
+ }
6487
+ }
6488
+ return targets;
6489
+ };
6490
+ exports.collectSecretTargets = collectSecretTargets;
6491
+ /**
6492
+ * pushes vault values to github via the gh cli: sensitive ones as
6493
+ * secrets (masked), "variable"-kind ones as variables (plain) — each
6494
+ * into the environment(s) whose jobs reference them
6495
+ */
6496
+ const pushSecretsToGithub = async (io, repo, secrets, targets) => {
6497
+ const environments = [
6498
+ ...new Set([...targets.values()].flatMap((envs) => [...envs].filter((env) => env !== REPO_LEVEL))),
6499
+ ];
6500
+ for (const environment of environments) {
6501
+ await (0, github_1.ensureGithubEnvironment)(repo, environment);
6502
+ io.log(`🌍 environment ${environment}`);
6503
+ }
6504
+ for (const { name, value, kind } of secrets) {
6505
+ const envs = targets.get(name);
6506
+ if (!envs) {
6507
+ io.log(`⏭️ ${name} (not referenced by any generated workflow)`);
6508
+ continue;
6509
+ }
6510
+ for (const environment of envs) {
6511
+ const suffix = environment === REPO_LEVEL ? "" : ` → ${environment}`;
6512
+ if (kind === "variable") {
6513
+ await (0, github_1.setGithubVariable)(repo, name, value, environment || undefined);
6514
+ io.log(`📖 ${name} (variable)${suffix}`);
6515
+ }
6516
+ else {
6517
+ await (0, github_1.setGithubSecret)(repo, name, value, environment || undefined);
6518
+ io.log(`✅ ${name}${suffix}`);
6519
+ }
6520
+ }
6521
+ }
6522
+ };
6523
+ exports.pushSecretsToGithub = pushSecretsToGithub;
6524
+ /**
6525
+ * resolves the github repository from the configured git remote
6526
+ * (`pipelines.github.gitRemote`, defaults to origin)
6527
+ */
6528
+ const getConfiguredGithubRepo = async () => {
6529
+ const config = await (0, getProjectConfig_1.getProjectConfig)();
6530
+ if (!config) {
6531
+ return undefined;
6532
+ }
6533
+ return (0, github_1.getGithubRepoFromRemote)((0, pipeline_1.getPipelineGitRemote)(config, "github"));
6534
+ };
6535
+ exports.getConfiguredGithubRepo = getConfiguredGithubRepo;
6536
+ //# sourceMappingURL=github.js.map
6537
+
6538
+ /***/ }),
6539
+
6540
+ /***/ 85200:
6541
+ /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
6542
+
6543
+ "use strict";
6544
+
6545
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6546
+ if (k2 === undefined) k2 = k;
6547
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6548
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6549
+ desc = { enumerable: true, get: function() { return m[k]; } };
6550
+ }
6551
+ Object.defineProperty(o, k2, desc);
6552
+ }) : (function(o, m, k, k2) {
6553
+ if (k2 === undefined) k2 = k;
6554
+ o[k2] = m[k];
6555
+ }));
6556
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
6557
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
6286
6558
  };
6287
- exports.getProjectPodNames = getProjectPodNames;
6559
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
6560
+ __exportStar(__nccwpck_require__(97245), exports);
6561
+ __exportStar(__nccwpck_require__(31298), exports);
6288
6562
  //# sourceMappingURL=index.js.map
6289
6563
 
6290
6564
  /***/ }),
6291
6565
 
6292
- /***/ 14178:
6566
+ /***/ 31298:
6293
6567
  /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
6294
6568
 
6295
6569
  "use strict";
6296
6570
 
6297
6571
  Object.defineProperty(exports, "__esModule", ({ value: true }));
6298
- exports.openGoogleCloudKubernetesDashboard = exports.openGoogleCloudLogs = void 0;
6572
+ exports.writeSecretsAndMirror = void 0;
6299
6573
  const pipeline_1 = __nccwpck_require__(53695);
6300
- const openDashboard_1 = __nccwpck_require__(74241);
6301
- const openGoogleCloudLogs = async (instance, context) => {
6302
- var _a;
6303
- const deployConfig = (_a = context.deploy) === null || _a === void 0 ? void 0 : _a.config;
6304
- if (!(0, pipeline_1.isOfDeployType)(deployConfig, "kubernetes")) {
6305
- throw new Error("context is not of type kubernetes");
6574
+ const getProjectConfig_1 = __nccwpck_require__(70478);
6575
+ const github_1 = __nccwpck_require__(41353);
6576
+ const gitlab_1 = __nccwpck_require__(17697);
6577
+ const github_2 = __nccwpck_require__(97245);
6578
+ /**
6579
+ * the one door for writing secret values: they go into the vault (the
6580
+ * source of truth) and are mirrored to every enabled CI backend —
6581
+ * which vault and which backends exist is invisible to the caller.
6582
+ *
6583
+ * Everything that produces a secret value must come through here: the
6584
+ * secrets commands, but also the credentials `project setup`
6585
+ * provisions (gcloud service account keys, kubernetes tokens).
6586
+ */
6587
+ const writeSecretsAndMirror = async (io, writes, options = {}) => {
6588
+ const config = await (0, getProjectConfig_1.getProjectConfig)();
6589
+ if (!config) {
6590
+ throw new Error("no catladder config found");
6306
6591
  }
6307
- if (!deployConfig.cluster || deployConfig.cluster.type !== "gcloud") {
6308
- throw new Error("no gcloud custer configured");
6592
+ io.log("");
6593
+ io.log("writing secrets to the vault, please wait...");
6594
+ io.log("");
6595
+ for (const { env, componentName, secrets } of writes) {
6596
+ io.log("writing " + env + ":" + componentName + "...\n");
6597
+ await (await io.getVaultManager()).writeSecrets(env, componentName, secrets, io, options);
6598
+ io.log("");
6599
+ io.log("✅ " + env + ":" + componentName);
6600
+ io.log("--------------------------------\n");
6309
6601
  }
6310
- const namespace = context.environment.envVars.KUBE_NAMESPACE;
6311
- const cluster = deployConfig.cluster;
6312
- const resource = `k8s_container/cluster_name/${cluster.name}${namespace ? `/namespace_name/${namespace}` : ""}`;
6313
- await (0, openDashboard_1.openGoogleCloudDashboard)(instance, "logs/viewer", {
6314
- project: cluster.projectId,
6315
- resource: resource,
6316
- });
6602
+ await mirrorSecretsToCiBackends(io, config, writes, options);
6317
6603
  };
6318
- exports.openGoogleCloudLogs = openGoogleCloudLogs;
6319
- const openGoogleCloudKubernetesDashboard = async (instance, context) => {
6604
+ exports.writeSecretsAndMirror = writeSecretsAndMirror;
6605
+ /**
6606
+ * the vault is the source of truth; every enabled CI backend receives
6607
+ * a mirrored copy of the written secrets (gitlab variables, github
6608
+ * secrets), so all CI systems stay in sync
6609
+ */
6610
+ const mirrorSecretsToCiBackends = async (io, config, writes, options) => {
6320
6611
  var _a;
6321
- const deployConfig = (_a = context.deploy) === null || _a === void 0 ? void 0 : _a.config;
6322
- if (!(0, pipeline_1.isOfDeployType)(deployConfig, "kubernetes")) {
6323
- throw new Error("context is not of type kubernetes");
6612
+ const enabled = (0, pipeline_1.getEnabledPipelineTypes)(config);
6613
+ // gitlab mirror (unless gitlab is the vault itself, then it is
6614
+ // already written)
6615
+ if ((0, pipeline_1.getVaultConfig)(config).type !== "gitlab" && enabled.includes("gitlab")) {
6616
+ for (const { env, componentName, secrets } of writes) {
6617
+ await (0, gitlab_1.upsertAllVariables)(io, secrets, env, componentName, (_a = options.backup) !== null && _a !== void 0 ? _a : true);
6618
+ }
6619
+ io.log("✅ mirrored to gitlab");
6324
6620
  }
6325
- if (!deployConfig.cluster || deployConfig.cluster.type !== "gcloud") {
6326
- throw new Error("no gcloud custer configured");
6621
+ if (!enabled.includes("github")) {
6622
+ return;
6327
6623
  }
6328
- const namespace = context.environment.envVars.KUBE_NAMESPACE;
6329
- const cluster = deployConfig.cluster;
6330
- const pageState = `("savedViews":("c":["gke/${cluster.region}/${cluster.name}"],"n":["${namespace}"],"i":"4e42e0b9cd6147f8a4fba7516752ec48"))`;
6331
- await (0, openDashboard_1.openGoogleCloudDashboard)(instance, "kubernetes/workload", {
6332
- project: cluster.projectId,
6333
- pageState: pageState,
6334
- });
6335
- };
6336
- exports.openGoogleCloudKubernetesDashboard = openGoogleCloudKubernetesDashboard;
6337
- //# sourceMappingURL=openKubernetesDashboards.js.map
6338
-
6339
- /***/ }),
6340
-
6341
- /***/ 76775:
6342
- /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
6343
-
6344
- "use strict";
6345
-
6346
- Object.defineProperty(exports, "__esModule", ({ value: true }));
6347
- exports.startKubePortForward = void 0;
6348
- const portForwards_1 = __nccwpck_require__(5368);
6349
- const startKubePortForward = async (podname, localPort, remotePort, namespace) => {
6350
- const name = `kube/${namespace}/${podname}/${localPort}:${remotePort}`;
6351
- await (0, portForwards_1.startPortForwardCommand)(name, `kubectl port-forward ${podname} ${localPort}:${remotePort} -n ${namespace}`);
6624
+ const kinds = new Map();
6625
+ for (const { env, componentName } of writes) {
6626
+ const { secretEnvVarKeys, jobOnlyVars } = await (0, getProjectConfig_1.getEnvironment)(env, componentName);
6627
+ [
6628
+ ...secretEnvVarKeys,
6629
+ ...jobOnlyVars.build.secretEnvVarKeys,
6630
+ ...jobOnlyVars.deploy.secretEnvVarKeys,
6631
+ ].forEach(({ key, kind }) => kinds.set((0, pipeline_1.getSecretVarName)(env, componentName, key), kind !== null && kind !== void 0 ? kind : "secret"));
6632
+ }
6633
+ const secrets = writes.flatMap(({ env, componentName, secrets }) => Object.entries(secrets).map(([key, value]) => {
6634
+ var _a;
6635
+ const name = (0, pipeline_1.getSecretVarName)(env, componentName, key);
6636
+ return {
6637
+ name,
6638
+ value: typeof value === "string" ? value : JSON.stringify(value !== null && value !== void 0 ? value : ""),
6639
+ kind: (_a = kinds.get(name)) !== null && _a !== void 0 ? _a : "secret",
6640
+ };
6641
+ }));
6642
+ if (secrets.length === 0) {
6643
+ return;
6644
+ }
6645
+ if (!(await (0, github_1.isGhAuthenticated)())) {
6646
+ io.log("⚠️ github pipeline is enabled, but the github cli (gh) is not authenticated — run `project secrets-sync-github` later to mirror the secrets");
6647
+ return;
6648
+ }
6649
+ const repo = await (0, github_2.getConfiguredGithubRepo)();
6650
+ if (!repo) {
6651
+ io.log("⚠️ github pipeline is enabled, but no github repository was found — run `project secrets-sync-github` later to mirror the secrets");
6652
+ return;
6653
+ }
6654
+ io.log(`mirroring ${secrets.length} secrets to github ('${repo}')...`);
6655
+ await (0, github_2.pushSecretsToGithub)(io, repo, secrets, await (0, github_2.collectSecretTargets)(config));
6352
6656
  };
6353
- exports.startKubePortForward = startKubePortForward;
6354
- //# sourceMappingURL=portForward.js.map
6355
-
6356
- /***/ }),
6357
-
6358
- /***/ 55689:
6359
- /***/ ((__unused_webpack_module, exports) => {
6360
-
6361
- "use strict";
6362
-
6363
- // we can't import the package.json directly, because the bundling process will inline it
6364
- // but the version is only set during deployement, when its already bundled
6365
- // instead we need to use require.main.path to get the path of the main module, which is actually the file in the bin
6366
- // this will point to the root package.json
6367
- var _a;
6368
- Object.defineProperty(exports, "__esModule", ({ value: true }));
6369
- const path = ((_a = require.main) === null || _a === void 0 ? void 0 : _a.path) + "/../package.json";
6370
- const ownPkg = require(path);
6371
- exports["default"] = ownPkg;
6372
- //# sourceMappingURL=packageInfos.js.map
6657
+ //# sourceMappingURL=writeSecrets.js.map
6373
6658
 
6374
6659
  /***/ }),
6375
6660
 
@@ -6766,6 +7051,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
6766
7051
  exports.ghApiJson = exports.ensureGithubEnvironment = exports.setGithubVariable = exports.setGithubSecret = exports.listGithubVariableNames = exports.listGithubSecretNames = exports.listGithubEnvironments = exports.getGithubRepoFromRemote = exports.isGhAuthenticated = void 0;
6767
7052
  const child_process_1 = __nccwpck_require__(35317);
6768
7053
  const util_1 = __nccwpck_require__(39023);
7054
+ const pipeline_1 = __nccwpck_require__(53695);
6769
7055
  const execFile = (0, util_1.promisify)(child_process_1.execFile);
6770
7056
  /**
6771
7057
  * whether the github cli (gh) is installed and authenticated
@@ -6788,8 +7074,7 @@ exports.isGhAuthenticated = isGhAuthenticated;
6788
7074
  const getGithubRepoFromRemote = async (remoteName) => {
6789
7075
  try {
6790
7076
  const { stdout } = await execFile("git", ["remote", "get-url", remoteName]);
6791
- const match = stdout.match(/github\.com[:/]([\w.-]+\/[\w.-]+?)(\.git)?\s*$/);
6792
- return match === null || match === void 0 ? void 0 : match[1];
7077
+ return (0, pipeline_1.parseGithubRepoFromRemoteUrl)(stdout);
6793
7078
  }
6794
7079
  catch (_a) {
6795
7080
  return undefined;
@@ -6910,7 +7195,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
6910
7195
  return (mod && mod.__esModule) ? mod : { "default": mod };
6911
7196
  };
6912
7197
  Object.defineProperty(exports, "__esModule", ({ value: true }));
6913
- exports.upsertAllVariables = exports.clearBackups = exports.getVariableValueByRawName = exports.getAllVariables = exports.getProjectInfo = exports.doGitlabRequestAllPages = exports.doGitlabRequest = exports.getGitlabToken = exports.setupGitlabToken = exports.hasGitlabToken = void 0;
7198
+ exports.upsertAllVariables = exports.clearBackups = exports.getVariableValueByRawName = exports.getAllVariables = exports.getProjectInfo = exports.doGitlabRequestAllPages = exports.doGitlabRequest = exports.GitlabNotFoundError = exports.getGitlabToken = exports.setupGitlabToken = exports.hasGitlabToken = void 0;
6914
7199
  const pipeline_1 = __nccwpck_require__(53695);
6915
7200
  const lodash_es_1 = __nccwpck_require__(50210);
6916
7201
  const memoizee_1 = __importDefault(__nccwpck_require__(6575));
@@ -6962,6 +7247,20 @@ const getGitlabToken = async (io) => {
6962
7247
  return (0, preferences_1.getPreference)(TOKEN_KEY);
6963
7248
  };
6964
7249
  exports.getGitlabToken = getGitlabToken;
7250
+ /**
7251
+ * a gitlab api call that came back 404. Carries the path so a caller
7252
+ * that wants to treat "does not exist yet" as normal can say so
7253
+ * (`instanceof`) instead of matching on a message.
7254
+ */
7255
+ class GitlabNotFoundError extends Error {
7256
+ constructor(path, host) {
7257
+ super(`gitlab api: ${path} not found on ${host}`);
7258
+ this.path = path;
7259
+ this.host = host;
7260
+ this.name = "GitlabNotFoundError";
7261
+ }
7262
+ }
7263
+ exports.GitlabNotFoundError = GitlabNotFoundError;
6965
7264
  const doGitlabRequest = async (io, path, data = undefined, method = "GET") => {
6966
7265
  const [rootToken, { gitRemoteHost }] = await Promise.all([
6967
7266
  (0, exports.getGitlabToken)(io),
@@ -6983,7 +7282,7 @@ const doGitlabRequest = async (io, path, data = undefined, method = "GET") => {
6983
7282
  return null;
6984
7283
  }
6985
7284
  if (result.status === 404) {
6986
- throw new Error("not found");
7285
+ throw new GitlabNotFoundError(path, gitRemoteHost);
6987
7286
  }
6988
7287
  throw new Error(`Could not send request to gitlab api ${path}: ${result.status} "${result.statusText}".\nResponse: ${JSON.stringify(await result.json(), null, 2)}`);
6989
7288
  };
@@ -7009,9 +7308,20 @@ const doGitlabRequestAllPages = async (io, path) => {
7009
7308
  };
7010
7309
  exports.doGitlabRequestAllPages = doGitlabRequestAllPages;
7011
7310
  const getProjectInfo = async (io) => {
7012
- const { gitRemotePath } = await (0, gitProjectInformation_1.getGitRemoteHostAndPath)();
7013
- const project = await (0, exports.doGitlabRequest)(io, `projects/${encodeURIComponent(gitRemotePath)}`);
7014
- return project;
7311
+ const { gitRemoteHost, gitRemotePath } = await (0, gitProjectInformation_1.getGitRemoteHostAndPath)();
7312
+ try {
7313
+ return await (0, exports.doGitlabRequest)(io, `projects/${encodeURIComponent(gitRemotePath)}`);
7314
+ }
7315
+ catch (e) {
7316
+ if (e instanceof GitlabNotFoundError) {
7317
+ // the classic: the remote is not a gitlab at all (a github one,
7318
+ // say), so the request goes to a host without /api/v4 and every
7319
+ // path answers 404
7320
+ throw new Error(`no gitlab project '${gitRemotePath}' on '${gitRemoteHost}'. ` +
7321
+ "Check the git remote and `pipelines` in catladder.ts — this command talks to the gitlab api.");
7322
+ }
7323
+ throw e;
7324
+ }
7015
7325
  };
7016
7326
  exports.getProjectInfo = getProjectInfo;
7017
7327
  exports.getAllVariables = (0, memoizee_1.default)(async (io, n = 5) => {
@@ -7566,7 +7876,14 @@ class BitwardenVault {
7566
7876
  }
7567
7877
  return secrets;
7568
7878
  }
7879
+ /**
7880
+ * upserts into the item's yaml note. The note is rewritten as a
7881
+ * whole, so the values that are not part of this write have to be
7882
+ * carried over — a single-key write (`secrets-set`, a credential
7883
+ * provisioned by `project setup`) must not drop the rest.
7884
+ */
7569
7885
  async writeSecrets(io, env, componentName, secrets) {
7886
+ var _a;
7570
7887
  const { config, collectionName } = this;
7571
7888
  const bwSession = await this.session(io);
7572
7889
  const collection = await findCollection(bwSession, collectionName);
@@ -7579,7 +7896,10 @@ class BitwardenVault {
7579
7896
  "--search",
7580
7897
  name,
7581
7898
  ])).find((item) => item.name === name);
7582
- const notes = (0, yaml_1.stringify)(secrets);
7899
+ const previous = (existing === null || existing === void 0 ? void 0 : existing.notes)
7900
+ ? ((_a = (0, yaml_1.parse)(existing.notes)) !== null && _a !== void 0 ? _a : {})
7901
+ : {};
7902
+ const notes = (0, yaml_1.stringify)({ ...previous, ...secrets });
7583
7903
  if (existing) {
7584
7904
  await (0, bw_1.runBw)(bwSession, [
7585
7905
  "edit",
@@ -7633,8 +7953,9 @@ class GitlabVault {
7633
7953
  const variables = await (0, gitlab_1.getAllVariables)(io);
7634
7954
  return Object.fromEntries(variables.map((v) => [v.key, v.value]));
7635
7955
  }
7636
- async writeSecrets(io, env, componentName, secrets) {
7637
- await (0, gitlab_1.upsertAllVariables)(io, secrets, env, componentName);
7956
+ async writeSecrets(io, env, componentName, secrets, options = {}) {
7957
+ var _a;
7958
+ await (0, gitlab_1.upsertAllVariables)(io, secrets, env, componentName, (_a = options.backup) !== null && _a !== void 0 ? _a : true);
7638
7959
  }
7639
7960
  }
7640
7961
  exports.GitlabVault = GitlabVault;
@@ -7720,11 +8041,11 @@ class VaultManager {
7720
8041
  * writes secrets of one env/component to the vault and updates the
7721
8042
  * local cache (write-through, "local" env only)
7722
8043
  */
7723
- async writeSecrets(env, componentName, secrets, io = this.io) {
8044
+ async writeSecrets(env, componentName, secrets, io = this.io, options = {}) {
7724
8045
  if (!io) {
7725
8046
  throw new Error("writing to the vault requires an interactive context");
7726
8047
  }
7727
- await this.vault.writeSecrets(io, env, componentName, secrets);
8048
+ await this.vault.writeSecrets(io, env, componentName, secrets, options);
7728
8049
  if (env === CACHED_ENV) {
7729
8050
  await (0, cache_1.updateSecretsCache)(this.vault.id, env, componentName, Object.fromEntries(Object.entries(secrets).map(([key, value]) => [
7730
8051
  key,
@@ -8723,6 +9044,43 @@ exports.getAgentSkillTargets = getAgentSkillTargets;
8723
9044
 
8724
9045
  /***/ }),
8725
9046
 
9047
+ /***/ 72792:
9048
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
9049
+
9050
+ "use strict";
9051
+
9052
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
9053
+ exports.getPinLabelRegexSource = exports.getAutoStopConfig = exports.DEFAULT_PIN_LABEL = exports.DEFAULT_DEV_AUTO_STOP = exports.DEFAULT_REVIEW_AUTO_STOP = exports.REVIEW_AUTO_STOP_VARIABLE = void 0;
9054
+ const lodash_es_1 = __nccwpck_require__(50210);
9055
+ /**
9056
+ * pipeline-level variable holding the `auto_stop_in` of review
9057
+ * environments. Review deploy jobs reference it instead of a literal,
9058
+ * so the pin label's workflow rule can override it to `never` for the
9059
+ * whole pipeline (all components at once).
9060
+ */
9061
+ exports.REVIEW_AUTO_STOP_VARIABLE = "CL_REVIEW_AUTO_STOP";
9062
+ exports.DEFAULT_REVIEW_AUTO_STOP = "1 week";
9063
+ exports.DEFAULT_DEV_AUTO_STOP = "4 weeks";
9064
+ exports.DEFAULT_PIN_LABEL = "catladder::pin-review";
9065
+ const getAutoStopConfig = (config) => {
9066
+ var _a, _b, _c, _d, _e, _f;
9067
+ return ({
9068
+ review: (_b = (_a = config.autoStop) === null || _a === void 0 ? void 0 : _a.review) !== null && _b !== void 0 ? _b : exports.DEFAULT_REVIEW_AUTO_STOP,
9069
+ dev: (_d = (_c = config.autoStop) === null || _c === void 0 ? void 0 : _c.dev) !== null && _d !== void 0 ? _d : exports.DEFAULT_DEV_AUTO_STOP,
9070
+ pinLabel: (_f = (_e = config.autoStop) === null || _e === void 0 ? void 0 : _e.pinLabel) !== null && _f !== void 0 ? _f : exports.DEFAULT_PIN_LABEL,
9071
+ });
9072
+ };
9073
+ exports.getAutoStopConfig = getAutoStopConfig;
9074
+ /**
9075
+ * matches the pin label inside `$CI_MERGE_REQUEST_LABELS`
9076
+ * (comma-separated) — anchored so `pin` never matches `no-pin`
9077
+ */
9078
+ const getPinLabelRegexSource = (pinLabel) => `(^|,)${(0, lodash_es_1.escapeRegExp)(pinLabel).replaceAll("/", "\\/")}(,|$)`;
9079
+ exports.getPinLabelRegexSource = getPinLabelRegexSource;
9080
+ //# sourceMappingURL=autoStop.js.map
9081
+
9082
+ /***/ }),
9083
+
8726
9084
  /***/ 48476:
8727
9085
  /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
8728
9086
 
@@ -8735,6 +9093,7 @@ const path_1 = __nccwpck_require__(16928);
8735
9093
  const createAllJobs_1 = __nccwpck_require__(83477);
8736
9094
  const config_1 = __nccwpck_require__(75591);
8737
9095
  const ciVariables_1 = __nccwpck_require__(17300);
9096
+ const registryImage_1 = __nccwpck_require__(24333);
8738
9097
  const index_1 = __nccwpck_require__(62853);
8739
9098
  const createGithubJobs_1 = __nccwpck_require__(73474);
8740
9099
  const jobImagesPlan_1 = __nccwpck_require__(59623);
@@ -8788,7 +9147,10 @@ class GithubBackend {
8788
9147
  await (0, promises_1.rm)(scriptFiles_1.GITHUB_SCRIPTS_FOLDER, { recursive: true, force: true });
8789
9148
  }
8790
9149
  async createFiles(config) {
8791
- const images = this.createImagesPlan(config);
9150
+ // ghcr rejects uppercase image paths, and `${{ github.repository }}`
9151
+ // interpolates github's display casing — a mixed-case owner or repo
9152
+ // needs a lowercased literal instead (see ./ghcr)
9153
+ const images = this.createImagesPlan(config, await (0, registryImage_1.resolveGithubRegistryImage)(config));
8792
9154
  const scripts = new scriptFiles_1.GithubScriptFiles();
8793
9155
  const workflows = await this.createWorkflows(config, images, scripts);
8794
9156
  return [
@@ -8803,8 +9165,8 @@ class GithubBackend {
8803
9165
  ...(0, shippedCatci_1.getCatciGeneratedFiles)(),
8804
9166
  ];
8805
9167
  }
8806
- createImagesPlan(config) {
8807
- return new jobImagesPlan_1.JobImagesPlan(this.type, config.images);
9168
+ createImagesPlan(config, registryImage) {
9169
+ return new jobImagesPlan_1.JobImagesPlan(this.type, config.images, registryImage);
8808
9170
  }
8809
9171
  /**
8810
9172
  * all workflows keyed by file name, mainly for testing purposes
@@ -8813,9 +9175,11 @@ class GithubBackend {
8813
9175
  var _a, _b, _c, _d;
8814
9176
  const workflows = {};
8815
9177
  // per-pipeline-type variables (pipelines.github.runnerVariables);
8816
- // workflow-level env, so job-level runnerVariables take precedence
9178
+ // workflow-level env, so job-level runnerVariables take precedence.
9179
+ // The images plan is the single source of the ghcr namespace, so the
9180
+ // workflow env and the container images can never disagree.
8817
9181
  const workflowEnv = {
8818
- ...ciVariables_1.GITHUB_INJECTED_WORKFLOW_ENV,
9182
+ ...(0, ciVariables_1.getGithubInjectedWorkflowEnv)(images.registryImageRef()),
8819
9183
  ...(0, index_1.getPipelineOptions)(config, this.type).runnerVariables,
8820
9184
  };
8821
9185
  const reviewStopJobs = {};
@@ -9158,12 +9522,13 @@ exports.makeAggregateCheckJob = makeAggregateCheckJob;
9158
9522
  /***/ }),
9159
9523
 
9160
9524
  /***/ 17300:
9161
- /***/ ((__unused_webpack_module, exports) => {
9525
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
9162
9526
 
9163
9527
  "use strict";
9164
9528
 
9165
9529
  Object.defineProperty(exports, "__esModule", ({ value: true }));
9166
- exports.GITHUB_INJECTED_WORKFLOW_ENV = exports.GITHUB_CI_VARIABLES = void 0;
9530
+ exports.GITHUB_INJECTED_WORKFLOW_ENV_NAMES = exports.getGithubInjectedWorkflowEnv = exports.GITHUB_CI_VARIABLES = void 0;
9531
+ const ghcr_1 = __nccwpck_require__(59011);
9167
9532
  /**
9168
9533
  * how the github backend names the predefined CI variables.
9169
9534
  *
@@ -9184,16 +9549,28 @@ exports.GITHUB_CI_VARIABLES = {
9184
9549
  };
9185
9550
  /**
9186
9551
  * the workflow-level env the backend injects to provide the CL_*
9187
- * variables referenced by {@link GITHUB_CI_VARIABLES}
9552
+ * variables referenced by {@link GITHUB_CI_VARIABLES}.
9553
+ *
9554
+ * `registryImage` is the ghcr namespace to push to — the actions
9555
+ * expression for an all-lowercase repository, a lowercased literal for
9556
+ * a mixed-case one (see `./ghcr`).
9188
9557
  */
9189
- exports.GITHUB_INJECTED_WORKFLOW_ENV = {
9558
+ const getGithubInjectedWorkflowEnv = (registryImage = ghcr_1.GHCR_REPOSITORY_EXPRESSION) => ({
9190
9559
  CL_JOB_TOKEN: "${{ github.token }}",
9191
9560
  CL_REGISTRY: "ghcr.io",
9192
- CL_REGISTRY_IMAGE: "ghcr.io/${{ github.repository }}",
9561
+ CL_REGISTRY_IMAGE: registryImage,
9193
9562
  CL_REGISTRY_USER: "${{ github.actor }}",
9194
9563
  // empty outside of pull request events
9195
9564
  CL_PR_NUMBER: "${{ github.event.number }}",
9196
- };
9565
+ });
9566
+ exports.getGithubInjectedWorkflowEnv = getGithubInjectedWorkflowEnv;
9567
+ /**
9568
+ * the names of the injected workflow-level env — the values differ per
9569
+ * repository (see {@link getGithubInjectedWorkflowEnv}), the names never
9570
+ * do, so lookups that only ask "does the workflow already provide this?"
9571
+ * use this instead.
9572
+ */
9573
+ exports.GITHUB_INJECTED_WORKFLOW_ENV_NAMES = new Set(Object.keys((0, exports.getGithubInjectedWorkflowEnv)()));
9197
9574
  //# sourceMappingURL=ciVariables.js.map
9198
9575
 
9199
9576
  /***/ }),
@@ -9420,7 +9797,7 @@ const getSecretsEnv = (context, kinds) => {
9420
9797
  */
9421
9798
  const getReferencedSecretsEnv = (script, kinds) => Object.fromEntries([...script.matchAll(/\$\{?(CL_[A-Za-z0-9_]+)\}?/g)]
9422
9799
  .map((match) => match[1])
9423
- .filter((name) => !(name in ciVariables_1.GITHUB_INJECTED_WORKFLOW_ENV))
9800
+ .filter((name) => !ciVariables_1.GITHUB_INJECTED_WORKFLOW_ENV_NAMES.has(name))
9424
9801
  .map((name) => [name, vaultValueExpression(name, kinds)]));
9425
9802
  const makeGithubJob = (context, job, allJobs, uploadProviderIds, images, scripts, secretKinds = new Map()) => {
9426
9803
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
@@ -9684,6 +10061,53 @@ exports.makeEnsureImageGithubJobs = makeEnsureImageGithubJobs;
9684
10061
 
9685
10062
  /***/ }),
9686
10063
 
10064
+ /***/ 59011:
10065
+ /***/ ((__unused_webpack_module, exports) => {
10066
+
10067
+ "use strict";
10068
+
10069
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
10070
+ exports.toGhcrRegistryImage = exports.parseGithubRepoFromRemoteUrl = exports.GHCR_REPOSITORY_EXPRESSION = void 0;
10071
+ /**
10072
+ * the ghcr namespace expressed through the actions context — portable
10073
+ * across forks, and what every all-lowercase repository keeps using
10074
+ */
10075
+ exports.GHCR_REPOSITORY_EXPRESSION = "ghcr.io/${{ github.repository }}";
10076
+ /**
10077
+ * parses `owner/name` out of a github remote url (ssh or https, with or
10078
+ * without the `.git` suffix).
10079
+ *
10080
+ * Shared with the cli (`apps/cli/src/utils/github.ts`) so the two can't
10081
+ * drift apart.
10082
+ */
10083
+ const parseGithubRepoFromRemoteUrl = (url) => { var _a; return (_a = url.match(/github\.com[:/]([\w.-]+\/[\w.-]+?)(\.git)?\s*$/)) === null || _a === void 0 ? void 0 : _a[1]; };
10084
+ exports.parseGithubRepoFromRemoteUrl = parseGithubRepoFromRemoteUrl;
10085
+ /**
10086
+ * the ghcr image prefix for an `owner/name` repository.
10087
+ *
10088
+ * The OCI distribution spec only allows `[a-z0-9]` plus `.`, `_` and `-`
10089
+ * separators in a repository name, so a mixed-case owner or repo (very
10090
+ * common for org logins: `AcmeCorp`, …) makes the docker client reject
10091
+ * every tag before it even reaches the network. Lowercasing is not a
10092
+ * workaround: the org `AcmeCorp` owns the namespace `ghcr.io/acmecorp`,
10093
+ * so the lowercased path is the correct address.
10094
+ *
10095
+ * Returns the `${{ github.repository }}` expression when lowercasing
10096
+ * would change nothing — it is equivalent for those repositories, and
10097
+ * unlike a literal it keeps working in a fork (whose token can only
10098
+ * push to the fork's own namespace).
10099
+ */
10100
+ const toGhcrRegistryImage = (repository) => {
10101
+ const lowercased = repository.toLowerCase();
10102
+ return lowercased === repository
10103
+ ? exports.GHCR_REPOSITORY_EXPRESSION
10104
+ : `ghcr.io/${lowercased}`;
10105
+ };
10106
+ exports.toGhcrRegistryImage = toGhcrRegistryImage;
10107
+ //# sourceMappingURL=ghcr.js.map
10108
+
10109
+ /***/ }),
10110
+
9687
10111
  /***/ 25073:
9688
10112
  /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
9689
10113
 
@@ -9993,12 +10417,70 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
9993
10417
  Object.defineProperty(exports, "__esModule", ({ value: true }));
9994
10418
  __exportStar(__nccwpck_require__(48476), exports);
9995
10419
  __exportStar(__nccwpck_require__(17300), exports);
10420
+ __exportStar(__nccwpck_require__(59011), exports);
10421
+ __exportStar(__nccwpck_require__(24333), exports);
9996
10422
  __exportStar(__nccwpck_require__(61071), exports);
9997
10423
  __exportStar(__nccwpck_require__(82145), exports);
9998
10424
  //# sourceMappingURL=index.js.map
9999
10425
 
10000
10426
  /***/ }),
10001
10427
 
10428
+ /***/ 24333:
10429
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
10430
+
10431
+ "use strict";
10432
+
10433
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
10434
+ exports.resolveGithubRegistryImage = exports.githubRegistryImageFromConfig = void 0;
10435
+ const child_process_1 = __nccwpck_require__(35317);
10436
+ const util_1 = __nccwpck_require__(39023);
10437
+ const index_1 = __nccwpck_require__(62853);
10438
+ const ghcr_1 = __nccwpck_require__(59011);
10439
+ const execFile = (0, util_1.promisify)(child_process_1.execFile);
10440
+ /**
10441
+ * the ghcr image prefix derived from the config alone
10442
+ * (`pipelines.github.repository`), without touching git.
10443
+ *
10444
+ * Used where generation must stay pure and hermetic (tests, examples);
10445
+ * {@link resolveGithubRegistryImage} adds the git remote fallback.
10446
+ */
10447
+ const githubRegistryImageFromConfig = (config) => {
10448
+ const { repository } = (0, index_1.getPipelineOptions)(config, "github");
10449
+ return repository ? (0, ghcr_1.toGhcrRegistryImage)(repository) : undefined;
10450
+ };
10451
+ exports.githubRegistryImageFromConfig = githubRegistryImageFromConfig;
10452
+ /**
10453
+ * the ghcr image prefix to generate with: the configured repository, or
10454
+ * the one the github git remote points at.
10455
+ *
10456
+ * Falls back to the `${{ github.repository }}` expression when the
10457
+ * remote is unavailable (a checkout without the remote, a tarball, …).
10458
+ * That is the pre-existing behavior and correct for every all-lowercase
10459
+ * repository; `project doctor` catches the mixed-case case it is not
10460
+ * correct for.
10461
+ */
10462
+ const resolveGithubRegistryImage = async (config) => {
10463
+ const fromConfig = (0, exports.githubRegistryImageFromConfig)(config);
10464
+ if (fromConfig) {
10465
+ return fromConfig;
10466
+ }
10467
+ const remote = (0, index_1.getPipelineGitRemote)(config, "github");
10468
+ try {
10469
+ const { stdout } = await execFile("git", ["remote", "get-url", remote]);
10470
+ const repository = (0, ghcr_1.parseGithubRepoFromRemoteUrl)(stdout);
10471
+ return repository
10472
+ ? (0, ghcr_1.toGhcrRegistryImage)(repository)
10473
+ : ghcr_1.GHCR_REPOSITORY_EXPRESSION;
10474
+ }
10475
+ catch (_a) {
10476
+ return ghcr_1.GHCR_REPOSITORY_EXPRESSION;
10477
+ }
10478
+ };
10479
+ exports.resolveGithubRegistryImage = resolveGithubRegistryImage;
10480
+ //# sourceMappingURL=registryImage.js.map
10481
+
10482
+ /***/ }),
10483
+
10002
10484
  /***/ 61071:
10003
10485
  /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
10004
10486
 
@@ -10101,6 +10583,7 @@ exports.GitlabBackend = void 0;
10101
10583
  const promises_1 = __nccwpck_require__(91943);
10102
10584
  const rules_1 = __nccwpck_require__(78503);
10103
10585
  const config_1 = __nccwpck_require__(75591);
10586
+ const autoStop_1 = __nccwpck_require__(72792);
10104
10587
  const createAllJobs_1 = __nccwpck_require__(83477);
10105
10588
  const jobImagesPlan_1 = __nccwpck_require__(59623);
10106
10589
  const shippedCatci_1 = __nccwpck_require__(27967);
@@ -10286,6 +10769,7 @@ class GitlabBackend {
10286
10769
  // per-pipeline-type variables (pipelines.gitlab.runnerVariables)
10287
10770
  ...(0, index_1.getPipelineOptions)(config, this.type).runnerVariables,
10288
10771
  },
10772
+ autoStop: (0, autoStop_1.getAutoStopConfig)(config),
10289
10773
  });
10290
10774
  }
10291
10775
  }
@@ -10730,6 +11214,7 @@ function getGitlabNeedsForWorkspaceJob(context, { needs }, allJobs) {
10730
11214
  Object.defineProperty(exports, "__esModule", ({ value: true }));
10731
11215
  exports.createGitlabPipelineWithDefaults = void 0;
10732
11216
  const bash_1 = __nccwpck_require__(94143);
11217
+ const autoStop_1 = __nccwpck_require__(72792);
10733
11218
  const rules_1 = __nccwpck_require__(78503);
10734
11219
  /**
10735
11220
  * gitlab requires a top-level default image for jobs that don't set their
@@ -10738,7 +11223,8 @@ const rules_1 = __nccwpck_require__(78503);
10738
11223
  * build-free image (a catladder job image would drag in a build job).
10739
11224
  */
10740
11225
  const DEFAULT_PIPELINE_IMAGE = "node:22";
10741
- const createGitlabPipelineWithDefaults = ({ image, variables, before_script, ...config }) => {
11226
+ const createGitlabPipelineWithDefaults = ({ image, variables, before_script, autoStop, ...config }) => {
11227
+ const pinLabel = autoStop && autoStop.pinLabel;
10742
11228
  return {
10743
11229
  image: image !== null && image !== void 0 ? image : DEFAULT_PIPELINE_IMAGE,
10744
11230
  variables: {
@@ -10747,6 +11233,10 @@ const createGitlabPipelineWithDefaults = ({ image, variables, before_script, ...
10747
11233
  CACHE_COMPRESSION_LEVEL: "fast", // same as above, but for caches
10748
11234
  TRANSFER_METER_FREQUENCY: "5s", // how often we should update the transfer meter for cache upload/download
10749
11235
  GIT_DEPTH: "1", // no need the full depth
11236
+ // review env lifetime — the pin-label workflow rule below
11237
+ // overrides this to "never" (workflow rule variables take
11238
+ // precedence over pipeline-level variables)
11239
+ ...(pinLabel ? { [autoStop_1.REVIEW_AUTO_STOP_VARIABLE]: autoStop.review } : {}),
10750
11240
  ...(variables !== null && variables !== void 0 ? variables : {}),
10751
11241
  },
10752
11242
  before_script: [
@@ -10763,6 +11253,20 @@ const createGitlabPipelineWithDefaults = ({ image, variables, before_script, ...
10763
11253
  PIPELINE_NAME: "Thinking...",
10764
11254
  },
10765
11255
  },
11256
+ // pinned MRs (rules stop at the first match, so this must come
11257
+ // before the plain merge-request rule)
11258
+ ...(pinLabel
11259
+ ? [
11260
+ {
11261
+ if: `${rules_1.RULE_IS_MERGE_REQUEST.if} && $CI_MERGE_REQUEST_LABELS =~ /${(0, autoStop_1.getPinLabelRegexSource)(pinLabel)}/`,
11262
+ variables: {
11263
+ PIPELINE_ICON: "🐱📌",
11264
+ PIPELINE_NAME: "mr$CI_MERGE_REQUEST_IID - $CI_MERGE_REQUEST_TITLE",
11265
+ [autoStop_1.REVIEW_AUTO_STOP_VARIABLE]: "never",
11266
+ },
11267
+ },
11268
+ ]
11269
+ : []),
10766
11270
  {
10767
11271
  if: rules_1.RULE_IS_MERGE_REQUEST.if,
10768
11272
  variables: {
@@ -14086,6 +14590,7 @@ exports.JobImagesPlan = exports.PROJECT_IMAGES_FOLDER = exports.GENERATED_IMAGES
14086
14590
  const fs_1 = __nccwpck_require__(79896);
14087
14591
  const path_1 = __nccwpck_require__(16928);
14088
14592
  const ciVariables_1 = __nccwpck_require__(91215);
14593
+ const ghcr_1 = __nccwpck_require__(59011);
14089
14594
  const docker_1 = __nccwpck_require__(12354);
14090
14595
  const runner_1 = __nccwpck_require__(92146);
14091
14596
  const gitlab_1 = __nccwpck_require__(86962);
@@ -14146,9 +14651,16 @@ exports.PROJECT_IMAGES_FOLDER = `${exports.GENERATED_IMAGES_FOLDER}/project`;
14146
14651
  * Reference-driven: only images actually used by a job are built.
14147
14652
  */
14148
14653
  class JobImagesPlan {
14149
- constructor(pipelineType, projectImages = {}) {
14654
+ constructor(pipelineType, projectImages = {},
14655
+ /**
14656
+ * the registry image prefix to generate with, overriding the
14657
+ * pipeline type's default. On github this carries the lowercased
14658
+ * ghcr namespace (see `resolveGithubRegistryImage`).
14659
+ */
14660
+ registryImage) {
14150
14661
  this.pipelineType = pipelineType;
14151
14662
  this.projectImages = projectImages;
14663
+ this.registryImage = registryImage;
14152
14664
  this.used = new Map();
14153
14665
  this.usedProject = new Map();
14154
14666
  }
@@ -14173,11 +14685,14 @@ class JobImagesPlan {
14173
14685
  }
14174
14686
  /**
14175
14687
  * the registry image prefix. On github, container images can't use
14176
- * runtime env vars — the expression form is required.
14688
+ * runtime env vars — a literal or an actions expression is required.
14177
14689
  */
14178
14690
  registryImageRef() {
14691
+ if (this.registryImage) {
14692
+ return this.registryImage;
14693
+ }
14179
14694
  return this.pipelineType === "github"
14180
- ? "ghcr.io/${{ github.repository }}"
14695
+ ? ghcr_1.GHCR_REPOSITORY_EXPRESSION
14181
14696
  : `${(0, ciVariables_1.getCiVariable)({ pipelineType: this.pipelineType }, "registryImage")}`;
14182
14697
  }
14183
14698
  useProject(name) {
@@ -14477,6 +14992,7 @@ const docker_1 = __nccwpck_require__(12354);
14477
14992
  const context_1 = __nccwpck_require__(73938);
14478
14993
  const jobs_1 = __nccwpck_require__(98521);
14479
14994
  const utils_1 = __nccwpck_require__(71286);
14995
+ const autoStop_1 = __nccwpck_require__(72792);
14480
14996
  const stop_1 = __nccwpck_require__(35937);
14481
14997
  const variables_1 = __nccwpck_require__(54768);
14482
14998
  exports.DEPLOY_JOB_NAME = "🚀 Deploy";
@@ -14485,10 +15001,15 @@ class DeployJob extends jobs_1.CatladderJob {
14485
15001
  var _a, _b, _c, _d, _e, _f, _g;
14486
15002
  const hasDocker = (0, docker_1.requiresDockerBuild)(context);
14487
15003
  const isStoppable = (0, utils_1.contextIsStoppable)(context);
15004
+ const autoStopConfig = (0, autoStop_1.getAutoStopConfig)(context.fullConfig);
14488
15005
  const autoStop = context.environment.envType === "review"
14489
- ? "1 week"
15006
+ ? // review envs read the lifetime from a pipeline variable, so
15007
+ // the pin label's workflow rule can override it to "never"
15008
+ autoStopConfig.pinLabel === false
15009
+ ? autoStopConfig.review
15010
+ : `$${autoStop_1.REVIEW_AUTO_STOP_VARIABLE}`
14490
15011
  : context.environment.envType === "dev"
14491
- ? "4 weeks"
15012
+ ? autoStopConfig.dev
14492
15013
  : undefined;
14493
15014
  const deployConfig = (_a = context.deploy) === null || _a === void 0 ? void 0 : _a.config;
14494
15015
  // if auto or manual is configured explicitly, use that
@@ -17317,6 +17838,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
17317
17838
  };
17318
17839
  Object.defineProperty(exports, "__esModule", ({ value: true }));
17319
17840
  __exportStar(__nccwpck_require__(46734), exports);
17841
+ __exportStar(__nccwpck_require__(72792), exports);
17320
17842
  __exportStar(__nccwpck_require__(62853), exports);
17321
17843
  __exportStar(__nccwpck_require__(63557), exports);
17322
17844
  __exportStar(__nccwpck_require__(78503), exports);