@catladder/cli 0.0.0-next-71c49b28 → 0.0.0-next-5dc1f907

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 (85) hide show
  1. package/dist/bundles/catenv/index.js +75 -12
  2. package/dist/bundles/cli/index.js +847 -51
  3. package/dist/cli/src/apps/cli/commands/project/doctor/DoctorReport.d.ts +22 -0
  4. package/dist/cli/src/apps/cli/commands/project/doctor/DoctorReport.js +75 -0
  5. package/dist/cli/src/apps/cli/commands/project/doctor/DoctorReport.js.map +1 -0
  6. package/dist/cli/src/apps/cli/commands/project/doctor/GcloudProjectInspector.d.ts +15 -0
  7. package/dist/cli/src/apps/cli/commands/project/doctor/GcloudProjectInspector.js +59 -0
  8. package/dist/cli/src/apps/cli/commands/project/doctor/GcloudProjectInspector.js.map +1 -0
  9. package/dist/cli/src/apps/cli/commands/project/doctor/checkCloudRun.d.ts +13 -0
  10. package/dist/cli/src/apps/cli/commands/project/doctor/checkCloudRun.js +120 -0
  11. package/dist/cli/src/apps/cli/commands/project/doctor/checkCloudRun.js.map +1 -0
  12. package/dist/cli/src/apps/cli/commands/project/doctor/checkGithub.d.ts +10 -0
  13. package/dist/cli/src/apps/cli/commands/project/doctor/checkGithub.js +100 -0
  14. package/dist/cli/src/apps/cli/commands/project/doctor/checkGithub.js.map +1 -0
  15. package/dist/cli/src/apps/cli/commands/project/doctor/checkGitlab.d.ts +13 -0
  16. package/dist/cli/src/apps/cli/commands/project/doctor/checkGitlab.js +79 -0
  17. package/dist/cli/src/apps/cli/commands/project/doctor/checkGitlab.js.map +1 -0
  18. package/dist/cli/src/apps/cli/commands/project/doctor/checkStore.d.ts +8 -0
  19. package/dist/cli/src/apps/cli/commands/project/doctor/checkStore.js +44 -0
  20. package/dist/cli/src/apps/cli/commands/project/doctor/checkStore.js.map +1 -0
  21. package/dist/cli/src/apps/cli/commands/project/doctor/index.d.ts +14 -0
  22. package/dist/cli/src/apps/cli/commands/project/doctor/index.js +48 -0
  23. package/dist/cli/src/apps/cli/commands/project/doctor/index.js.map +1 -0
  24. package/dist/cli/src/apps/cli/commands/project/setup/setupAccessTokens.js +4 -1
  25. package/dist/cli/src/apps/cli/commands/project/setup/setupAccessTokens.js.map +1 -1
  26. package/dist/cli/src/apps/cli/commands/project/setup/setupCloudRun.d.ts +21 -0
  27. package/dist/cli/src/apps/cli/commands/project/setup/setupCloudRun.js +31 -11
  28. package/dist/cli/src/apps/cli/commands/project/setup/setupCloudRun.js.map +1 -1
  29. package/dist/cli/src/commands/index.d.ts +1 -0
  30. package/dist/cli/src/commands/index.js +8 -1
  31. package/dist/cli/src/commands/index.js.map +1 -1
  32. package/dist/cli/src/commands/project/commandDoctor.d.ts +8 -0
  33. package/dist/cli/src/commands/project/commandDoctor.js +25 -0
  34. package/dist/cli/src/commands/project/commandDoctor.js.map +1 -0
  35. package/dist/cli/src/gcloud/artifactsRegistry.d.ts +1 -0
  36. package/dist/cli/src/gcloud/artifactsRegistry.js +3 -2
  37. package/dist/cli/src/gcloud/artifactsRegistry.js.map +1 -1
  38. package/dist/cli/src/gcloud/serviceAccountNames.d.ts +28 -0
  39. package/dist/cli/src/gcloud/serviceAccountNames.js +51 -0
  40. package/dist/cli/src/gcloud/serviceAccountNames.js.map +1 -0
  41. package/dist/cli/src/gcloud/serviceAccounts.js +6 -27
  42. package/dist/cli/src/gcloud/serviceAccounts.js.map +1 -1
  43. package/dist/cli/src/utils/concurrency.d.ts +7 -0
  44. package/dist/cli/src/utils/concurrency.js +27 -0
  45. package/dist/cli/src/utils/concurrency.js.map +1 -0
  46. package/dist/cli/src/utils/github.d.ts +10 -0
  47. package/dist/cli/src/utils/github.js +26 -1
  48. package/dist/cli/src/utils/github.js.map +1 -1
  49. package/dist/cli/src/utils/gitlab.d.ts +6 -0
  50. package/dist/cli/src/utils/gitlab.js +19 -1
  51. package/dist/cli/src/utils/gitlab.js.map +1 -1
  52. package/dist/pipeline/src/backends/github/createGithubJobs.js +24 -2
  53. package/dist/pipeline/src/backends/github/createGithubJobs.js.map +1 -1
  54. package/dist/pipeline/src/backends/gitlab/createGitlabJobs.js +8 -7
  55. package/dist/pipeline/src/backends/gitlab/createGitlabJobs.js.map +1 -1
  56. package/dist/pipeline/src/build/cache/createJobCache.js +8 -0
  57. package/dist/pipeline/src/build/cache/createJobCache.js.map +1 -1
  58. package/dist/pipeline/src/build/node/cache.js +15 -1
  59. package/dist/pipeline/src/build/node/cache.js.map +1 -1
  60. package/dist/pipeline/src/build/types.d.ts +19 -0
  61. package/dist/pipeline/src/build/types.js.map +1 -1
  62. package/dist/pipeline/src/constants.js +1 -1
  63. package/dist/pipeline/src/types/jobs.d.ts +6 -0
  64. package/dist/pipeline/src/types/jobs.js.map +1 -1
  65. package/dist/tsconfig.tsbuildinfo +1 -1
  66. package/package.json +1 -1
  67. package/src/apps/cli/commands/project/doctor/DoctorReport.ts +72 -0
  68. package/src/apps/cli/commands/project/doctor/GcloudProjectInspector.ts +77 -0
  69. package/src/apps/cli/commands/project/doctor/checkCloudRun.ts +148 -0
  70. package/src/apps/cli/commands/project/doctor/checkGithub.ts +156 -0
  71. package/src/apps/cli/commands/project/doctor/checkGitlab.ts +133 -0
  72. package/src/apps/cli/commands/project/doctor/checkStore.ts +43 -0
  73. package/src/apps/cli/commands/project/doctor/index.ts +59 -0
  74. package/src/apps/cli/commands/project/setup/setupAccessTokens.ts +9 -2
  75. package/src/apps/cli/commands/project/setup/setupCloudRun.ts +42 -21
  76. package/src/commands/index.ts +1 -0
  77. package/src/commands/project/commandDoctor.ts +21 -0
  78. package/src/gcloud/__tests__/serviceAccountNames.test.ts +63 -0
  79. package/src/gcloud/artifactsRegistry.ts +3 -1
  80. package/src/gcloud/serviceAccountNames.ts +65 -0
  81. package/src/gcloud/serviceAccounts.ts +6 -38
  82. package/src/utils/concurrency.ts +25 -0
  83. package/src/utils/github.ts +48 -0
  84. package/src/utils/gitlab.ts +24 -0
  85. package/tsconfig.json +2 -1
@@ -0,0 +1,22 @@
1
+ import type { IO } from "../../../../../core/types";
2
+ /**
3
+ * collects the results of all doctor checks: each check logs
4
+ * immediately (progressive output like `project setup`) and problems
5
+ * are repeated in the final summary together with the command that
6
+ * heals them. The report itself never mutates anything — doctor
7
+ * diagnoses, setup heals.
8
+ */
9
+ export declare class DoctorReport {
10
+ private readonly io;
11
+ private passed;
12
+ private readonly warnings;
13
+ private readonly problems;
14
+ private currentSection;
15
+ constructor(io: IO);
16
+ section(title: string): void;
17
+ ok(message: string): void;
18
+ warn(message: string, remediation?: string): void;
19
+ fail(message: string, remediation?: string): void;
20
+ get hasProblems(): boolean;
21
+ summarize(): void;
22
+ }
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.DoctorReport = void 0;
7
+ /**
8
+ * collects the results of all doctor checks: each check logs
9
+ * immediately (progressive output like `project setup`) and problems
10
+ * are repeated in the final summary together with the command that
11
+ * heals them. The report itself never mutates anything — doctor
12
+ * diagnoses, setup heals.
13
+ */
14
+ class DoctorReport {
15
+ constructor(io) {
16
+ this.io = io;
17
+ this.passed = 0;
18
+ this.warnings = [];
19
+ this.problems = [];
20
+ this.currentSection = "";
21
+ }
22
+ section(title) {
23
+ this.currentSection = title;
24
+ this.io.log("");
25
+ this.io.log("🩺 " + title);
26
+ }
27
+ ok(message) {
28
+ this.passed++;
29
+ this.io.log(" ✅ " + message);
30
+ }
31
+ warn(message, remediation) {
32
+ this.warnings.push({
33
+ section: this.currentSection,
34
+ message,
35
+ remediation
36
+ });
37
+ this.io.log(" ⚠️ " + message);
38
+ if (remediation) this.io.log(" ↳ " + remediation);
39
+ }
40
+ fail(message, remediation) {
41
+ this.problems.push({
42
+ section: this.currentSection,
43
+ message,
44
+ remediation
45
+ });
46
+ this.io.log(" ❌ " + message);
47
+ if (remediation) this.io.log(" ↳ " + remediation);
48
+ }
49
+ get hasProblems() {
50
+ return this.problems.length > 0;
51
+ }
52
+ summarize() {
53
+ const {
54
+ io
55
+ } = this;
56
+ io.log("");
57
+ io.log("==================================================================================");
58
+ io.log(`🩺 doctor summary: ${this.passed} checks passed, ` + `${this.warnings.length} warnings, ${this.problems.length} problems`);
59
+ if (!this.hasProblems) {
60
+ io.log("");
61
+ io.log("everything looks healthy 😻");
62
+ return;
63
+ }
64
+ io.log("");
65
+ for (const {
66
+ section,
67
+ message,
68
+ remediation
69
+ } of this.problems) {
70
+ io.log(`❌ [${section}] ${message}`);
71
+ if (remediation) io.log(` ↳ ${remediation}`);
72
+ }
73
+ }
74
+ }
75
+ exports.DoctorReport = DoctorReport;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DoctorReport.js","sourceRoot":"","sources":["../../../../../../../../src/apps/cli/commands/project/doctor/DoctorReport.ts"],"names":[],"mappings":";;;AAQA;;;;;;GAMG;AACH,MAAa,YAAY;IAMvB,YAA6B,EAAM;QAAN,OAAE,GAAF,EAAE,CAAI;QAL3B,WAAM,GAAG,CAAC,CAAC;QACF,aAAQ,GAAc,EAAE,CAAC;QACzB,aAAQ,GAAc,EAAE,CAAC;QAClC,mBAAc,GAAG,EAAE,CAAC;IAEU,CAAC;IAEvC,OAAO,CAAC,KAAa;QACnB,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC;QAC5B,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC;IAC7B,CAAC;IAED,EAAE,CAAC,OAAe;QAChB,IAAI,CAAC,MAAM,EAAE,CAAC;QACd,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,GAAG,OAAO,CAAC,CAAC;IAChC,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,WAAoB;QACxC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC;QAC3E,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,OAAO,GAAG,OAAO,CAAC,CAAC;QAC/B,IAAI,WAAW;YAAE,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,SAAS,GAAG,WAAW,CAAC,CAAC;IACxD,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,WAAoB;QACxC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC;QAC3E,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,GAAG,OAAO,CAAC,CAAC;QAC9B,IAAI,WAAW;YAAE,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,SAAS,GAAG,WAAW,CAAC,CAAC;IACxD,CAAC;IAED,IAAI,WAAW;QACb,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;IAClC,CAAC;IAED,SAAS;QACP,MAAM,EAAE,EAAE,EAAE,GAAG,IAAI,CAAC;QACpB,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACX,EAAE,CAAC,GAAG,CACJ,oFAAoF,CACrF,CAAC;QACF,EAAE,CAAC,GAAG,CACJ,sBAAsB,IAAI,CAAC,MAAM,kBAAkB;YACjD,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,cAAc,IAAI,CAAC,QAAQ,CAAC,MAAM,WAAW,CACvE,CAAC;QACF,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACX,EAAE,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;YACtC,OAAO;QACT,CAAC;QACD,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACX,KAAK,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC9D,EAAE,CAAC,GAAG,CAAC,MAAM,OAAO,KAAK,OAAO,EAAE,CAAC,CAAC;YACpC,IAAI,WAAW;gBAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,WAAW,EAAE,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;CACF;AAxDD,oCAwDC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * read-only gcloud queries for the doctor, cached per project: checking
3
+ * many component/env contexts of the same gcloud project costs one
4
+ * `get-iam-policy` / `services list` call, not one per context
5
+ */
6
+ export declare class GcloudProjectInspector {
7
+ private readonly iamBindings;
8
+ private readonly enabledServices;
9
+ isAuthenticated(): Promise<boolean>;
10
+ private getIamBindings;
11
+ /** all roles bound to the given member (e.g. "serviceAccount:<email>") */
12
+ getRolesOfMember(projectId: string, member: string): Promise<string[]>;
13
+ getEnabledServices(projectId: string): Promise<Set<string>>;
14
+ hasArtifactsRegistry(projectId: string, region: string): Promise<boolean>;
15
+ }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.GcloudProjectInspector = void 0;
7
+ const child_process_promise_1 = require("child-process-promise");
8
+ const artifactsRegistry_1 = require("../../../../../gcloud/artifactsRegistry");
9
+ /**
10
+ * read-only gcloud queries for the doctor, cached per project: checking
11
+ * many component/env contexts of the same gcloud project costs one
12
+ * `get-iam-policy` / `services list` call, not one per context
13
+ */
14
+ class GcloudProjectInspector {
15
+ constructor() {
16
+ this.iamBindings = new Map();
17
+ this.enabledServices = new Map();
18
+ }
19
+ async isAuthenticated() {
20
+ try {
21
+ await (0, child_process_promise_1.exec)("gcloud auth print-access-token");
22
+ return true;
23
+ } catch (_a) {
24
+ return false;
25
+ }
26
+ }
27
+ getIamBindings(projectId) {
28
+ if (!this.iamBindings.has(projectId)) {
29
+ this.iamBindings.set(projectId, (0, child_process_promise_1.exec)(`gcloud projects get-iam-policy ${projectId} --format=json`).then(result => {
30
+ var _a;
31
+ return (_a = JSON.parse(result.stdout).bindings) !== null && _a !== void 0 ? _a : [];
32
+ }));
33
+ }
34
+ return this.iamBindings.get(projectId);
35
+ }
36
+ /** all roles bound to the given member (e.g. "serviceAccount:<email>") */
37
+ async getRolesOfMember(projectId, member) {
38
+ const bindings = await this.getIamBindings(projectId);
39
+ return bindings.filter(binding => {
40
+ var _a;
41
+ return (_a = binding.members) === null || _a === void 0 ? void 0 : _a.includes(member);
42
+ }).map(binding => binding.role);
43
+ }
44
+ getEnabledServices(projectId) {
45
+ if (!this.enabledServices.has(projectId)) {
46
+ this.enabledServices.set(projectId, (0, child_process_promise_1.exec)(`gcloud services list --enabled --project=${projectId} --format="value(config.name)"`).then(result => new Set(result.stdout.split("\n").map(line => line.trim()).filter(Boolean))));
47
+ }
48
+ return this.enabledServices.get(projectId);
49
+ }
50
+ async hasArtifactsRegistry(projectId, region) {
51
+ try {
52
+ await (0, child_process_promise_1.exec)(`gcloud artifacts repositories describe ${artifactsRegistry_1.CATLADDER_REGISTRY_NAME} --project="${projectId}" --location=${region}`);
53
+ return true;
54
+ } catch (_a) {
55
+ return false;
56
+ }
57
+ }
58
+ }
59
+ exports.GcloudProjectInspector = GcloudProjectInspector;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GcloudProjectInspector.js","sourceRoot":"","sources":["../../../../../../../../src/apps/cli/commands/project/doctor/GcloudProjectInspector.ts"],"names":[],"mappings":";;;AAAA,iEAA6C;AAC7C,+EAAkF;AAIlF;;;;GAIG;AACH,MAAa,sBAAsB;IAAnC;QACmB,gBAAW,GAAG,IAAI,GAAG,EAAiC,CAAC;QACvD,oBAAe,GAAG,IAAI,GAAG,EAAgC,CAAC;IAgE7E,CAAC;IA9DC,KAAK,CAAC,eAAe;QACnB,IAAI,CAAC;YACH,MAAM,IAAA,4BAAI,EAAC,gCAAgC,CAAC,CAAC;YAC7C,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,WAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAEO,cAAc,CAAC,SAAiB;QACtC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YACrC,IAAI,CAAC,WAAW,CAAC,GAAG,CAClB,SAAS,EACT,IAAA,4BAAI,EAAC,kCAAkC,SAAS,gBAAgB,CAAC,CAAC,IAAI,CACpE,CAAC,MAAM,EAAE,EAAE,WAAC,OAAA,MAAA,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,mCAAI,EAAE,CAAA,EAAA,CACrD,CACF,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,SAAS,CAAE,CAAC;IAC1C,CAAC;IAED,0EAA0E;IAC1E,KAAK,CAAC,gBAAgB,CAAC,SAAiB,EAAE,MAAc;QACtD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;QACtD,OAAO,QAAQ;aACZ,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,WAAC,OAAA,MAAA,OAAO,CAAC,OAAO,0CAAE,QAAQ,CAAC,MAAM,CAAC,CAAA,EAAA,CAAC;aACtD,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;IAED,kBAAkB,CAAC,SAAiB;QAClC,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YACzC,IAAI,CAAC,eAAe,CAAC,GAAG,CACtB,SAAS,EACT,IAAA,4BAAI,EACF,4CAA4C,SAAS,gCAAgC,CACtF,CAAC,IAAI,CACJ,CAAC,MAAM,EAAE,EAAE,CACT,IAAI,GAAG,CACL,MAAM,CAAC,MAAM;iBACV,KAAK,CAAC,IAAI,CAAC;iBACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;iBAC1B,MAAM,CAAC,OAAO,CAAC,CACnB,CACJ,CACF,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,SAAS,CAAE,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,oBAAoB,CACxB,SAAiB,EACjB,MAAc;QAEd,IAAI,CAAC;YACH,MAAM,IAAA,4BAAI,EACR,0CAA0C,2CAAuB,eAAe,SAAS,gBAAgB,MAAM,EAAE,CAClH,CAAC;YACF,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,WAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;CACF;AAlED,wDAkEC"}
@@ -0,0 +1,13 @@
1
+ import type { ComponentContext } from "../../../../../../../pipeline/src/index.js";
2
+ import type { DoctorReport } from "./DoctorReport";
3
+ /**
4
+ * compares the gcloud state `setupCloudRun` would provision for the
5
+ * CURRENT config against what is actually there: enabled services and
6
+ * artifacts registry per project, deploy service account existence and
7
+ * IAM roles per component/env.
8
+ *
9
+ * The motivating case (#70): a component gets `cloudSql` after its
10
+ * setup ran — deploys keep working and only the review teardown hits
11
+ * the missing `roles/cloudsql.admin` as a 403 in ci.
12
+ */
13
+ export declare const checkCloudRun: (report: DoctorReport, contexts: ComponentContext[]) => Promise<void>;
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.checkCloudRun = void 0;
7
+ const pipeline_1 = require("../../../../../../../pipeline/src/index.js");
8
+ const artifactsRegistry_1 = require("../../../../../gcloud/artifactsRegistry");
9
+ const serviceAccountNames_1 = require("../../../../../gcloud/serviceAccountNames");
10
+ const serviceAccounts_1 = require("../../../../../gcloud/serviceAccounts");
11
+ const concurrency_1 = require("../../../../../utils/concurrency");
12
+ const setupCloudRun_1 = require("../setup/setupCloudRun");
13
+ const GcloudProjectInspector_1 = require("./GcloudProjectInspector");
14
+ const getCloudRunConfig = context => {
15
+ var _a;
16
+ const config = (_a = context.deploy) === null || _a === void 0 ? void 0 : _a.config;
17
+ if (!(0, pipeline_1.isOfDeployType)(config, "google-cloudrun")) {
18
+ throw new Error("not a cloud run context");
19
+ }
20
+ return config;
21
+ };
22
+ /**
23
+ * compares the gcloud state `setupCloudRun` would provision for the
24
+ * CURRENT config against what is actually there: enabled services and
25
+ * artifacts registry per project, deploy service account existence and
26
+ * IAM roles per component/env.
27
+ *
28
+ * The motivating case (#70): a component gets `cloudSql` after its
29
+ * setup ran — deploys keep working and only the review teardown hits
30
+ * the missing `roles/cloudsql.admin` as a 403 in ci.
31
+ */
32
+ const checkCloudRun = async (report, contexts) => {
33
+ var _a;
34
+ const cloudRunContexts = contexts.filter(context => {
35
+ var _a;
36
+ return (0, pipeline_1.isOfDeployType)((_a = context.deploy) === null || _a === void 0 ? void 0 : _a.config, "google-cloudrun");
37
+ });
38
+ if (cloudRunContexts.length === 0) return;
39
+ report.section("google cloud run infrastructure");
40
+ const inspector = new GcloudProjectInspector_1.GcloudProjectInspector();
41
+ if (!(await inspector.isAuthenticated())) {
42
+ report.fail("gcloud is not authenticated — all google cloud checks skipped", "run: gcloud auth login");
43
+ return;
44
+ }
45
+ // project-level: enabled services + artifacts registry. The required
46
+ // services are the union over all components deploying to the project
47
+ // (cloudSql on any of them requires the sql services).
48
+ const byProject = new Map();
49
+ for (const context of cloudRunContexts) {
50
+ const {
51
+ projectId
52
+ } = getCloudRunConfig(context);
53
+ byProject.set(projectId, [...((_a = byProject.get(projectId)) !== null && _a !== void 0 ? _a : []), context]);
54
+ }
55
+ for (const [projectId, projectContexts] of byProject) {
56
+ const requiredServices = new Set(projectContexts.flatMap(context => (0, setupCloudRun_1.getCloudRunRequiredServices)(getCloudRunConfig(context))));
57
+ try {
58
+ const enabled = await inspector.getEnabledServices(projectId);
59
+ const missing = [...requiredServices].filter(service => !enabled.has(service));
60
+ if (missing.length === 0) {
61
+ report.ok(`${projectId}: all ${requiredServices.size} required services enabled`);
62
+ } else {
63
+ report.fail(`${projectId}: services not enabled: ${missing.join(", ")}`, "run: catladder project setup");
64
+ }
65
+ } catch (e) {
66
+ report.warn(`${projectId}: could not list enabled services (${e.message})`);
67
+ }
68
+ const regions = new Set(projectContexts.map(context => getCloudRunConfig(context).region));
69
+ for (const region of regions) {
70
+ if (await inspector.hasArtifactsRegistry(projectId, region)) {
71
+ report.ok(`${projectId}: artifacts registry '${artifactsRegistry_1.CATLADDER_REGISTRY_NAME}' exists in ${region}`);
72
+ } else {
73
+ report.fail(`${projectId}: artifacts registry '${artifactsRegistry_1.CATLADDER_REGISTRY_NAME}' missing in ${region}`, "run: catladder project setup");
74
+ }
75
+ }
76
+ }
77
+ // per component/env: deploy service account exists and carries every
78
+ // role the current config implies
79
+ const results = await (0, concurrency_1.mapWithConcurrency)(cloudRunContexts, 5, async context => {
80
+ const config = getCloudRunConfig(context);
81
+ const {
82
+ fullIdentifier
83
+ } = (0, serviceAccountNames_1.getGcloudServiceAccountNames)(context, {
84
+ name: setupCloudRun_1.CLOUD_RUN_DEPLOY_SA_NAME,
85
+ projectId: config.projectId
86
+ });
87
+ if (!(await (0, serviceAccounts_1.accountExists)(fullIdentifier))) {
88
+ return {
89
+ context,
90
+ fullIdentifier,
91
+ exists: false,
92
+ missingRoles: []
93
+ };
94
+ }
95
+ const boundRoles = await inspector.getRolesOfMember(config.projectId, `serviceAccount:${fullIdentifier}`);
96
+ const missingRoles = (0, setupCloudRun_1.getCloudRunDeployRoles)(config).filter(role => !boundRoles.includes(role));
97
+ return {
98
+ context,
99
+ fullIdentifier,
100
+ exists: true,
101
+ missingRoles
102
+ };
103
+ });
104
+ for (const {
105
+ context,
106
+ fullIdentifier,
107
+ exists,
108
+ missingRoles
109
+ } of results) {
110
+ const label = `${context.env}:${context.name}`;
111
+ if (!exists) {
112
+ report.fail(`${label}: deploy service account ${fullIdentifier} does not exist`, `run: catladder project setup ${context.name}`);
113
+ } else if (missingRoles.length > 0) {
114
+ report.fail(`${label}: service account is missing roles: ${missingRoles.join(", ")}`, `run: catladder project setup ${context.name}`);
115
+ } else {
116
+ report.ok(`${label}: service account exists with all config-implied roles`);
117
+ }
118
+ }
119
+ };
120
+ exports.checkCloudRun = checkCloudRun;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkCloudRun.js","sourceRoot":"","sources":["../../../../../../../../src/apps/cli/commands/project/doctor/checkCloudRun.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { Config } from "../../../../../../../pipeline/src/index.js";
2
+ import type { DoctorReport } from "./DoctorReport";
3
+ /**
4
+ * compares the secret/variable NAMES the generated github workflows
5
+ * reference against what is actually set — per github environment, the
6
+ * same mapping `project secrets-sync-github` writes. Values cannot be
7
+ * compared: github secrets are write-only by design (the vault stays
8
+ * the source of truth).
9
+ */
10
+ export declare const checkGithub: (report: DoctorReport, config: Config) => Promise<void>;
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.checkGithub = void 0;
7
+ const pipeline_1 = require("../../../../../../../pipeline/src/index.js");
8
+ const commandSecretsSyncGithub_1 = require("../../../../../commands/project/commandSecretsSyncGithub");
9
+ const concurrency_1 = require("../../../../../utils/concurrency");
10
+ const github_1 = require("../../../../../utils/github");
11
+ /** marker for repo-level (environment-less) targets, see collectSecretTargets */
12
+ const REPO_LEVEL = "";
13
+ /**
14
+ * compares the secret/variable NAMES the generated github workflows
15
+ * reference against what is actually set — per github environment, the
16
+ * same mapping `project secrets-sync-github` writes. Values cannot be
17
+ * compared: github secrets are write-only by design (the vault stays
18
+ * the source of truth).
19
+ */
20
+ const checkGithub = async (report, config) => {
21
+ if (!(0, pipeline_1.getEnabledPipelineTypes)(config).includes("github")) return;
22
+ report.section("github environments & secrets");
23
+ if (!(await (0, github_1.isGhAuthenticated)())) {
24
+ report.warn("github cli (gh) not authenticated — github checks skipped", "run: gh auth login");
25
+ return;
26
+ }
27
+ const repo = await (0, commandSecretsSyncGithub_1.getConfiguredGithubRepo)();
28
+ if (!repo) {
29
+ report.fail(`no github repository found on the '${(0, pipeline_1.getPipelineGitRemote)(config, "github")}' remote`);
30
+ return;
31
+ }
32
+ let targets;
33
+ try {
34
+ targets = await (0, commandSecretsSyncGithub_1.collectSecretTargets)(config);
35
+ } catch (e) {
36
+ report.warn(`could not generate the github workflows to derive secret references (${e.message})`);
37
+ return;
38
+ }
39
+ const referencedNamesOf = environment => [...targets.entries()].filter(([, envs]) => envs.has(environment)).map(([name]) => name);
40
+ const environmentsNeeded = [...new Set([...targets.values()].flatMap(envs => [...envs]))].filter(environment => environment !== REPO_LEVEL).sort();
41
+ let existingEnvironments;
42
+ try {
43
+ existingEnvironments = new Set(await (0, github_1.listGithubEnvironments)(repo));
44
+ } catch (e) {
45
+ report.warn(`could not list environments of ${repo} (${e.message})`);
46
+ return;
47
+ }
48
+ const perEnvironment = await (0, concurrency_1.mapWithConcurrency)(environmentsNeeded, 8, async environment => {
49
+ const referenced = referencedNamesOf(environment);
50
+ if (!existingEnvironments.has(environment)) {
51
+ return {
52
+ environment,
53
+ referenced,
54
+ exists: false
55
+ };
56
+ }
57
+ const [secrets, variables] = await Promise.all([(0, github_1.listGithubSecretNames)(repo, environment), (0, github_1.listGithubVariableNames)(repo, environment)]);
58
+ // github secret names are case-insensitive and the api returns
59
+ // them uppercased — compare accordingly
60
+ const present = new Set([...secrets, ...variables].map(name => name.toUpperCase()));
61
+ const referencedUpper = new Set(referenced.map(name => name.toUpperCase()));
62
+ return {
63
+ environment,
64
+ referenced,
65
+ exists: true,
66
+ missing: referenced.filter(name => !present.has(name.toUpperCase())),
67
+ stale: [...present].filter(name => !referencedUpper.has(name))
68
+ };
69
+ });
70
+ for (const result of perEnvironment) {
71
+ const {
72
+ environment,
73
+ referenced
74
+ } = result;
75
+ if (!result.exists) {
76
+ report.fail(`environment '${environment}' does not exist (${referenced.length} referenced secrets have nowhere to live)`, "run: catladder project secrets-sync-github");
77
+ continue;
78
+ }
79
+ if (result.missing.length > 0) {
80
+ report.fail(`${environment}: missing ${result.missing.length}/${referenced.length} referenced secrets: ${result.missing.join(", ")}`, "run: catladder project secrets-sync-github");
81
+ } else {
82
+ report.ok(`${environment}: all ${referenced.length} referenced secrets/variables present`);
83
+ }
84
+ if (result.stale.length > 0) {
85
+ report.warn(`${environment}: ${result.stale.length} set but not referenced by any workflow (stale?): ${result.stale.join(", ")}`);
86
+ }
87
+ }
88
+ const repoLevelReferenced = referencedNamesOf(REPO_LEVEL);
89
+ if (repoLevelReferenced.length > 0) {
90
+ const [secrets, variables] = await Promise.all([(0, github_1.listGithubSecretNames)(repo), (0, github_1.listGithubVariableNames)(repo)]);
91
+ const present = new Set([...secrets, ...variables].map(name => name.toUpperCase()));
92
+ const missing = repoLevelReferenced.filter(name => !present.has(name.toUpperCase()));
93
+ if (missing.length > 0) {
94
+ report.fail(`repo level: missing ${missing.length}/${repoLevelReferenced.length} referenced secrets: ${missing.join(", ")}`, "run: catladder project secrets-sync-github");
95
+ } else {
96
+ report.ok(`repo level: all ${repoLevelReferenced.length} referenced secrets/variables present`);
97
+ }
98
+ }
99
+ };
100
+ exports.checkGithub = checkGithub;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkGithub.js","sourceRoot":"","sources":["../../../../../../../../src/apps/cli/commands/project/doctor/checkGithub.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { ComponentContext } from "../../../../../../../pipeline/src/index.js";
2
+ import type { IO } from "../../../../../core/types";
3
+ import type { DoctorReport } from "./DoctorReport";
4
+ /**
5
+ * the gitlab-side outputs of `project setup`: the semantic-release
6
+ * access token (+ its GL_TOKEN ci variable), the catladder topic and —
7
+ * for kubernetes deploys — the registry deploy token.
8
+ *
9
+ * Kubernetes namespace/rbac checks are deliberately skipped: they would
10
+ * require connecting kubectl to every cluster, which switches the local
11
+ * kubeconfig context — doctor must not change anything.
12
+ */
13
+ export declare const checkGitlab: (instance: IO, report: DoctorReport, contexts: ComponentContext[]) => Promise<void>;
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.checkGitlab = void 0;
7
+ const pipeline_1 = require("../../../../../../../pipeline/src/index.js");
8
+ const date_fns_1 = require("date-fns");
9
+ const gitlab_1 = require("../../../../../utils/gitlab");
10
+ const EXPIRY_WARNING_DAYS = 30;
11
+ /**
12
+ * the gitlab-side outputs of `project setup`: the semantic-release
13
+ * access token (+ its GL_TOKEN ci variable), the catladder topic and —
14
+ * for kubernetes deploys — the registry deploy token.
15
+ *
16
+ * Kubernetes namespace/rbac checks are deliberately skipped: they would
17
+ * require connecting kubectl to every cluster, which switches the local
18
+ * kubeconfig context — doctor must not change anything.
19
+ */
20
+ const checkGitlab = async (instance, report, contexts) => {
21
+ report.section("gitlab project setup");
22
+ let projectId;
23
+ try {
24
+ projectId = (await (0, gitlab_1.getProjectInfo)(instance)).id;
25
+ } catch (e) {
26
+ report.warn(`could not reach the gitlab api — gitlab checks skipped (${e.message})`);
27
+ return;
28
+ }
29
+ try {
30
+ const tokens = await (0, gitlab_1.doGitlabRequestAllPages)(instance, `projects/${projectId}/access_tokens`);
31
+ const activeTokens = tokens.filter(t => t.name === "semantic-release" && t.active === true);
32
+ // the GL_TOKEN ci variable holds exactly one of them — judge by the
33
+ // one that lives longest
34
+ const token = activeTokens.sort((a, b) => String(b.expires_at).localeCompare(String(a.expires_at)))[0];
35
+ if (activeTokens.length > 1) {
36
+ report.warn(`${activeTokens.length} active 'semantic-release' tokens exist — duplicates from earlier setups, consider revoking the old ones`);
37
+ }
38
+ if (!token) {
39
+ report.fail("no active 'semantic-release' project access token", "run: catladder project setup");
40
+ } else {
41
+ const daysLeft = (0, date_fns_1.differenceInCalendarDays)(new Date(token.expires_at), new Date());
42
+ if (daysLeft < 0) {
43
+ report.fail(`'semantic-release' token expired on ${token.expires_at}`, "run: catladder project setup (rotates the token)");
44
+ } else if (daysLeft <= EXPIRY_WARNING_DAYS) {
45
+ report.warn(`'semantic-release' token expires in ${daysLeft} days (${token.expires_at})`, "run: catladder project setup (rotates the token)");
46
+ } else {
47
+ report.ok(`'semantic-release' token active (expires ${token.expires_at})`);
48
+ }
49
+ }
50
+ try {
51
+ await (0, gitlab_1.doGitlabRequest)(instance, `projects/${projectId}/variables/GL_TOKEN`);
52
+ report.ok("GL_TOKEN ci variable present");
53
+ } catch (_a) {
54
+ report.fail("GL_TOKEN ci variable missing", "run: catladder project setup");
55
+ }
56
+ const {
57
+ topics
58
+ } = await (0, gitlab_1.doGitlabRequest)(instance, `projects/${projectId}`);
59
+ if (topics === null || topics === void 0 ? void 0 : topics.includes("catladder")) {
60
+ report.ok("'catladder' project topic set");
61
+ } else {
62
+ report.fail("'catladder' project topic missing", "run: catladder project setup");
63
+ }
64
+ if (contexts.some(context => {
65
+ var _a;
66
+ return (0, pipeline_1.isOfDeployType)((_a = context.deploy) === null || _a === void 0 ? void 0 : _a.config, "kubernetes");
67
+ })) {
68
+ const deployTokens = await (0, gitlab_1.doGitlabRequestAllPages)(instance, `projects/${projectId}/deploy_tokens`);
69
+ if (deployTokens.find(t => t.name === "gitlab-deploy-token")) {
70
+ report.ok("'gitlab-deploy-token' registry deploy token present");
71
+ } else {
72
+ report.fail("'gitlab-deploy-token' registry deploy token missing (kubernetes cannot pull images)", "run: catladder project setup");
73
+ }
74
+ }
75
+ } catch (e) {
76
+ report.warn(`gitlab checks aborted (${e.message})`);
77
+ }
78
+ };
79
+ exports.checkGitlab = checkGitlab;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkGitlab.js","sourceRoot":"","sources":["../../../../../../../../src/apps/cli/commands/project/doctor/checkGitlab.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { DoctorReport } from "./DoctorReport";
2
+ /**
3
+ * every google-cloudrun project in the config needs its project number
4
+ * in the committed store — the deterministic cloud run urls
5
+ * (<service>-<projectNumber>.<region>.run.app) are computed from it at
6
+ * generation time. This check is fully offline.
7
+ */
8
+ export declare const checkStore: (report: DoctorReport) => Promise<void>;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.checkStore = void 0;
7
+ const pipeline_1 = require("../../../../../../../pipeline/src/index.js");
8
+ const getProjectConfig_1 = require("../../../../../config/getProjectConfig");
9
+ const projects_1 = require("../../../../../utils/projects");
10
+ /**
11
+ * every google-cloudrun project in the config needs its project number
12
+ * in the committed store — the deterministic cloud run urls
13
+ * (<service>-<projectNumber>.<region>.run.app) are computed from it at
14
+ * generation time. This check is fully offline.
15
+ */
16
+ const checkStore = async report => {
17
+ var _a, _b, _c;
18
+ const config = await (0, getProjectConfig_1.getProjectConfig)();
19
+ const projectIds = new Set();
20
+ for (const {
21
+ componentName,
22
+ env
23
+ } of await (0, getProjectConfig_1.getAllComponentsWithAllEnvsFlat)()) {
24
+ const {
25
+ deploy
26
+ } = (0, pipeline_1.getEnvConfig)(config, componentName, env);
27
+ if (deploy && deploy.type === "google-cloudrun") {
28
+ projectIds.add(deploy.projectId);
29
+ }
30
+ }
31
+ if (projectIds.size === 0) return;
32
+ report.section("catladder store (.catladder-store/store.yml)");
33
+ const root = (_a = await (0, projects_1.getGitRoot)()) !== null && _a !== void 0 ? _a : process.cwd();
34
+ const store = (0, pipeline_1.readCatladderStore)(root);
35
+ for (const projectId of projectIds) {
36
+ const projectNumber = (_c = (_b = store.gcloudProjects) === null || _b === void 0 ? void 0 : _b[projectId]) === null || _c === void 0 ? void 0 : _c.projectNumber;
37
+ if (projectNumber) {
38
+ report.ok(`project number for ${projectId}: ${projectNumber}`);
39
+ } else {
40
+ report.fail(`missing project number for ${projectId}`, "run: catladder project setup");
41
+ }
42
+ }
43
+ };
44
+ exports.checkStore = checkStore;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkStore.js","sourceRoot":"","sources":["../../../../../../../../src/apps/cli/commands/project/doctor/checkStore.ts"],"names":[],"mappings":";;;AAAA,kDAAuE;AACvE,6EAGgD;AAChD,4DAA2D;AAG3D;;;;;GAKG;AACI,MAAM,UAAU,GAAG,KAAK,EAAE,MAAoB,EAAE,EAAE;;IACvD,MAAM,MAAM,GAAG,MAAM,IAAA,mCAAgB,GAAE,CAAC;IACxC,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IACrC,KAAK,MAAM,EACT,aAAa,EACb,GAAG,GACJ,IAAI,MAAM,IAAA,kDAA+B,GAAE,EAAE,CAAC;QAC7C,MAAM,EAAE,MAAM,EAAE,GAAG,IAAA,uBAAY,EAAC,MAAM,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;QAC5D,IAAI,MAAM,IAAI,MAAM,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;YAChD,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IACD,IAAI,UAAU,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO;IAElC,MAAM,CAAC,OAAO,CAAC,8CAA8C,CAAC,CAAC;IAC/D,MAAM,IAAI,GAAG,MAAA,CAAC,MAAM,IAAA,qBAAU,GAAE,CAAC,mCAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACnD,MAAM,KAAK,GAAG,IAAA,6BAAkB,EAAC,IAAI,CAAC,CAAC;IACvC,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,aAAa,GAAG,MAAA,MAAA,KAAK,CAAC,cAAc,0CAAG,SAAS,CAAC,0CAAE,aAAa,CAAC;QACvE,IAAI,aAAa,EAAE,CAAC;YAClB,MAAM,CAAC,EAAE,CAAC,sBAAsB,SAAS,KAAK,aAAa,EAAE,CAAC,CAAC;QACjE,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CACT,8BAA8B,SAAS,EAAE,EACzC,8BAA8B,CAC/B,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC,CAAC;AA5BW,QAAA,UAAU,cA4BrB"}
@@ -0,0 +1,14 @@
1
+ import type { IO } from "../../../../../core/types";
2
+ /**
3
+ * read-only health check: compares the state `project setup` (and
4
+ * `project secrets-sync-github`) would provision for the CURRENT config
5
+ * against what is actually there, and reports drift together with the
6
+ * command that heals it. `project setup` is a point-in-time
7
+ * provisioner, so config edits that change required infrastructure
8
+ * (IAM roles, services, secrets) drift silently until something breaks
9
+ * in ci — the doctor surfaces that before it does.
10
+ *
11
+ * The component filter only narrows the per-component checks; the
12
+ * cheap project-global checks (store, gitlab, github) always run.
13
+ */
14
+ export declare const doctorProject: (instance: IO, onlyComponents?: string | string[]) => Promise<void>;