@catladder/cli 5.1.0 → 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 (40) hide show
  1. package/dist/apps/cli/src/apps/cli/commands/project/doctor/checkGithubImageCasing.d.ts +15 -0
  2. package/dist/apps/cli/src/apps/cli/commands/project/doctor/checkGithubImageCasing.js +60 -0
  3. package/dist/apps/cli/src/apps/cli/commands/project/doctor/checkGithubImageCasing.js.map +1 -0
  4. package/dist/apps/cli/src/apps/cli/commands/project/doctor/index.js +2 -0
  5. package/dist/apps/cli/src/apps/cli/commands/project/doctor/index.js.map +1 -1
  6. package/dist/apps/cli/src/utils/github.js +2 -2
  7. package/dist/apps/cli/src/utils/github.js.map +1 -1
  8. package/dist/bundles/catenv/index.js +150 -15
  9. package/dist/bundles/cli/index.js +221 -17
  10. package/dist/bundles/skills/catladder-config/SKILL.md +3 -1
  11. package/dist/bundles/skills/catladder-migrate-ci-backend/SKILL.md +11 -0
  12. package/dist/bundles/skills/catladder-pipelines/SKILL.md +24 -0
  13. package/dist/packages/pipeline/src/backends/github/GithubBackend.js +11 -5
  14. package/dist/packages/pipeline/src/backends/github/GithubBackend.js.map +1 -1
  15. package/dist/packages/pipeline/src/backends/github/ciVariables.d.ts +13 -2
  16. package/dist/packages/pipeline/src/backends/github/ciVariables.js +18 -5
  17. package/dist/packages/pipeline/src/backends/github/ciVariables.js.map +1 -1
  18. package/dist/packages/pipeline/src/backends/github/createGithubJobs.js +1 -1
  19. package/dist/packages/pipeline/src/backends/github/createGithubJobs.js.map +1 -1
  20. package/dist/packages/pipeline/src/backends/github/ghcr.d.ts +29 -0
  21. package/dist/packages/pipeline/src/backends/github/ghcr.js +40 -0
  22. package/dist/packages/pipeline/src/backends/github/ghcr.js.map +1 -0
  23. package/dist/packages/pipeline/src/backends/github/index.d.ts +2 -0
  24. package/dist/packages/pipeline/src/backends/github/index.js +2 -0
  25. package/dist/packages/pipeline/src/backends/github/index.js.map +1 -1
  26. package/dist/packages/pipeline/src/backends/github/registryImage.d.ts +20 -0
  27. package/dist/packages/pipeline/src/backends/github/registryImage.js +49 -0
  28. package/dist/packages/pipeline/src/backends/github/registryImage.js.map +1 -0
  29. package/dist/packages/pipeline/src/customImages/jobImagesPlan.d.ts +15 -3
  30. package/dist/packages/pipeline/src/customImages/jobImagesPlan.js +14 -3
  31. package/dist/packages/pipeline/src/customImages/jobImagesPlan.js.map +1 -1
  32. package/dist/packages/pipeline/src/types/config.d.ts +12 -0
  33. package/dist/skills/catladder-config/SKILL.md +3 -1
  34. package/dist/skills/catladder-migrate-ci-backend/SKILL.md +11 -0
  35. package/dist/skills/catladder-pipelines/SKILL.md +24 -0
  36. package/dist/tsconfig.tsbuildinfo +1 -1
  37. package/package.json +1 -1
  38. package/src/apps/cli/commands/project/doctor/checkGithubImageCasing.ts +87 -0
  39. package/src/apps/cli/commands/project/doctor/index.ts +2 -0
  40. package/src/utils/github.ts +2 -4
package/package.json CHANGED
@@ -53,7 +53,7 @@
53
53
  }
54
54
  ],
55
55
  "license": "MIT",
56
- "version": "5.1.0",
56
+ "version": "5.1.1",
57
57
  "scripts": {
58
58
  "lint": "eslint \"src/**/*.ts\"",
59
59
  "lint:fix": "eslint \"src/**/*.ts\" --fix",
@@ -0,0 +1,87 @@
1
+ import { readdir, readFile } from "fs/promises";
2
+ import { join } from "path";
3
+ import type { Config } from "@catladder/pipeline";
4
+ import {
5
+ GHCR_REPOSITORY_EXPRESSION,
6
+ getEnabledPipelineTypes,
7
+ getPipelineGitRemote,
8
+ } from "@catladder/pipeline";
9
+ import { getConfiguredGithubRepo } from "../../../../../secrets";
10
+ import type { DoctorReport } from "./DoctorReport";
11
+
12
+ const WORKFLOWS_FOLDER = ".github/workflows";
13
+ const GENERATED_FILE_PREFIX = "catladder-";
14
+
15
+ /**
16
+ * the generated workflow files, or an empty list when none exist yet
17
+ */
18
+ const readGeneratedWorkflows = async (): Promise<string[]> => {
19
+ const entries = await readdir(WORKFLOWS_FOLDER).catch(() => []);
20
+ return Promise.all(
21
+ entries
22
+ .filter((file) => file.startsWith(GENERATED_FILE_PREFIX))
23
+ .map((file) => readFile(join(WORKFLOWS_FOLDER, file), "utf8")),
24
+ );
25
+ };
26
+
27
+ /**
28
+ * ghcr image paths must be lowercase, but `${{ github.repository }}`
29
+ * interpolates github's display casing — so a repository whose owner or
30
+ * name contains an uppercase character needs a lowercased literal in the
31
+ * generated workflows. Generation bakes that in, which means committed
32
+ * workflows generated by an older catladder (or hand-edited ones) still
33
+ * carry the expression and fail every docker job with
34
+ * `repository name must be lowercase`.
35
+ *
36
+ * Unlike the rest of the github checks this needs no `gh` login — only
37
+ * the git remote and the committed files.
38
+ */
39
+ export const checkGithubImageCasing = async (
40
+ report: DoctorReport,
41
+ config: Config,
42
+ ) => {
43
+ if (!getEnabledPipelineTypes(config).includes("github")) return;
44
+
45
+ const repo = await getConfiguredGithubRepo();
46
+ if (!repo || repo === repo.toLowerCase()) {
47
+ // an all-lowercase repository keeps the expression on purpose (it
48
+ // stays correct in forks), and an unresolvable remote is reported
49
+ // by checkGithub already
50
+ return;
51
+ }
52
+
53
+ report.section("github image names");
54
+
55
+ const workflows = await readGeneratedWorkflows();
56
+ if (workflows.length === 0) {
57
+ report.warn(
58
+ `no generated workflows in ${WORKFLOWS_FOLDER} to check`,
59
+ "run: catenv",
60
+ );
61
+ return;
62
+ }
63
+
64
+ const stale = workflows.filter((content) =>
65
+ content.includes(GHCR_REPOSITORY_EXPRESSION),
66
+ );
67
+ if (stale.length > 0) {
68
+ report.fail(
69
+ `'${repo}' is mixed-case, but ${stale.length}/${workflows.length} generated workflows still use ${GHCR_REPOSITORY_EXPRESSION} — ghcr rejects uppercase image paths, so every docker job fails with "repository name must be lowercase"`,
70
+ "run: catenv (and commit the regenerated workflows)",
71
+ );
72
+ return;
73
+ }
74
+
75
+ const uppercase = workflows.flatMap((content) =>
76
+ [...content.matchAll(/ghcr\.io\/\S*[A-Z]\S*/g)].map(([match]) => match),
77
+ );
78
+ if (uppercase.length > 0) {
79
+ report.fail(
80
+ `uppercase ghcr image paths in the generated workflows: ${[...new Set(uppercase)].join(", ")}`,
81
+ `run: catenv — if it persists, set pipelines.github.repository (the '${getPipelineGitRemote(config, "github")}' remote resolves to '${repo}')`,
82
+ );
83
+ return;
84
+ }
85
+
86
+ report.ok(`ghcr image paths lowercased for '${repo}'`);
87
+ };
@@ -7,6 +7,7 @@ import {
7
7
  import type { IO } from "../../../../../core/types";
8
8
  import { checkCloudRun } from "./checkCloudRun";
9
9
  import { checkGithub } from "./checkGithub";
10
+ import { checkGithubImageCasing } from "./checkGithubImageCasing";
10
11
  import { checkGitlab } from "./checkGitlab";
11
12
  import { checkStore } from "./checkStore";
12
13
  import { DoctorReport } from "./DoctorReport";
@@ -53,6 +54,7 @@ export const doctorProject = async (
53
54
  await checkGitlab(instance, report, contexts);
54
55
  }
55
56
  await checkCloudRun(report, contexts);
57
+ await checkGithubImageCasing(report, config);
56
58
  await checkGithub(report, config);
57
59
 
58
60
  report.summarize();
@@ -1,5 +1,6 @@
1
1
  import { execFile as execFileCb, spawn } from "child_process";
2
2
  import { promisify } from "util";
3
+ import { parseGithubRepoFromRemoteUrl } from "@catladder/pipeline";
3
4
 
4
5
  const execFile = promisify(execFileCb);
5
6
 
@@ -25,10 +26,7 @@ export const getGithubRepoFromRemote = async (
25
26
  ): Promise<string | undefined> => {
26
27
  try {
27
28
  const { stdout } = await execFile("git", ["remote", "get-url", remoteName]);
28
- const match = stdout.match(
29
- /github\.com[:/]([\w.-]+\/[\w.-]+?)(\.git)?\s*$/,
30
- );
31
- return match?.[1];
29
+ return parseGithubRepoFromRemoteUrl(stdout);
32
30
  } catch {
33
31
  return undefined;
34
32
  }