@catladder/cli 0.0.0-next-dccc0a36 → 0.0.0-next-89bce6d0

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 (45) hide show
  1. package/dist/apps/cli/src/catci.js +23 -0
  2. package/dist/apps/cli/src/catci.js.map +1 -1
  3. package/dist/apps/cli/src/release/githubQueuedRelease.d.ts +14 -0
  4. package/dist/apps/cli/src/release/githubQueuedRelease.js +152 -0
  5. package/dist/apps/cli/src/release/githubQueuedRelease.js.map +1 -0
  6. package/dist/bundles/catci/index.js +3 -3
  7. package/dist/bundles/catenv/index.js +340 -109
  8. package/dist/bundles/cli/index.js +340 -109
  9. package/dist/bundles/skills/catladder-deploys/references/deploy-types.md +1 -1
  10. package/dist/bundles/skills/catladder-releases/SKILL.md +13 -2
  11. package/dist/packages/bash/src/variables/VariableValueContainingReferences.d.ts +6 -0
  12. package/dist/packages/bash/src/variables/VariableValueContainingReferences.js +8 -0
  13. package/dist/packages/bash/src/variables/VariableValueContainingReferences.js.map +1 -1
  14. package/dist/packages/bash/src/variables/resolveAllReferences.d.ts +1 -2
  15. package/dist/packages/bash/src/variables/resolveAllReferences.js +4 -2
  16. package/dist/packages/bash/src/variables/resolveAllReferences.js.map +1 -1
  17. package/dist/packages/bash/src/variables/resolveAllReferencesOnce.d.ts +1 -4
  18. package/dist/packages/bash/src/variables/resolveAllReferencesOnce.js +4 -2
  19. package/dist/packages/bash/src/variables/resolveAllReferencesOnce.js.map +1 -1
  20. package/dist/packages/pipeline/src/backends/github/GithubBackend.js +7 -0
  21. package/dist/packages/pipeline/src/backends/github/GithubBackend.js.map +1 -1
  22. package/dist/packages/pipeline/src/backends/github/githubReleaseJobs.d.ts +18 -2
  23. package/dist/packages/pipeline/src/backends/github/githubReleaseJobs.js +92 -10
  24. package/dist/packages/pipeline/src/backends/github/githubReleaseJobs.js.map +1 -1
  25. package/dist/packages/pipeline/src/backends/gitlab/GitlabBackend.js +31 -5
  26. package/dist/packages/pipeline/src/backends/gitlab/GitlabBackend.js.map +1 -1
  27. package/dist/packages/pipeline/src/backends/gitlab/gitlabReleaseJobs.d.ts +285 -3
  28. package/dist/packages/pipeline/src/backends/gitlab/gitlabReleaseJobs.js +142 -54
  29. package/dist/packages/pipeline/src/backends/gitlab/gitlabReleaseJobs.js.map +1 -1
  30. package/dist/packages/pipeline/src/context/getEnvironmentVariables.js +17 -9
  31. package/dist/packages/pipeline/src/context/getEnvironmentVariables.js.map +1 -1
  32. package/dist/packages/pipeline/src/deploy/cloudRun/index.js +2 -2
  33. package/dist/packages/pipeline/src/deploy/cloudRun/index.js.map +1 -1
  34. package/dist/packages/pipeline/src/deploy/cloudRun/utils/database.d.ts +7 -9
  35. package/dist/packages/pipeline/src/deploy/cloudRun/utils/database.js +32 -24
  36. package/dist/packages/pipeline/src/deploy/cloudRun/utils/database.js.map +1 -1
  37. package/dist/packages/pipeline/src/deploy/index.d.ts +2 -2
  38. package/dist/packages/pipeline/src/types/github-types.d.ts +1 -0
  39. package/dist/packages/pipeline/src/types/release.d.ts +7 -1
  40. package/dist/skills/catladder-deploys/references/deploy-types.md +1 -1
  41. package/dist/skills/catladder-releases/SKILL.md +13 -2
  42. package/dist/tsconfig.tsbuildinfo +1 -1
  43. package/package.json +1 -1
  44. package/src/catci.ts +30 -0
  45. package/src/release/githubQueuedRelease.ts +173 -0
package/package.json CHANGED
@@ -53,7 +53,7 @@
53
53
  }
54
54
  ],
55
55
  "license": "MIT",
56
- "version": "0.0.0-next-dccc0a36",
56
+ "version": "0.0.0-next-89bce6d0",
57
57
  "scripts": {
58
58
  "lint": "eslint \"src/**/*.ts\"",
59
59
  "lint:fix": "eslint \"src/**/*.ts\" --fix",
package/src/catci.ts CHANGED
@@ -24,6 +24,10 @@ import {
24
24
  dispatchTaggedReleaseWorkflow,
25
25
  } from "./release/changesetsReleaseJob";
26
26
  import { changesetCheckJob } from "./release/changesetCheckJob";
27
+ import {
28
+ githubQueueCheckJob,
29
+ githubQueuedGuardJob,
30
+ } from "./release/githubQueuedRelease";
27
31
  import { npmPublishJob, parseNpmPublishArgs } from "./publish/npmPublishJob";
28
32
 
29
33
  const GITLAB_HOST = "https://git.panter.ch";
@@ -55,6 +59,18 @@ usage:
55
59
  github only: dispatches the generated taggedRelease workflow for
56
60
  the tag (tags pushed with the job token don't trigger it)
57
61
 
62
+ catci release github-queue-check
63
+ github only, first step of the manual create-release task:
64
+ releases right away when the main workflow run for HEAD is green
65
+ (queued=false step output), queues the release when it is still
66
+ running (queued=true, marker ref for the release-on-green
67
+ workflow), fails when it concluded red
68
+
69
+ catci release github-queued-guard <head-sha> <conclusion>
70
+ github only, guard of the release-on-green workflow: consumes the
71
+ queue marker when it matches the completed main run and decides
72
+ whether the release step runs (release=true step output)
73
+
58
74
  catci publish npm --dir <dir> --env-type <envType> [--access <access>] [--registry <url>] [--dist-tag <tag>]
59
75
  npmPackage deploy job: derives version + dist-tag from the
60
76
  pipeline trigger (tagged release -> tag version @latest; branch/MR
@@ -160,6 +176,20 @@ const main = async () => {
160
176
  ) {
161
177
  return dispatchTaggedReleaseWorkflow(args[0]);
162
178
  }
179
+ if (
180
+ group === "release" &&
181
+ command === "github-queue-check" &&
182
+ args.length === 0
183
+ ) {
184
+ return githubQueueCheckJob();
185
+ }
186
+ if (
187
+ group === "release" &&
188
+ command === "github-queued-guard" &&
189
+ args.length === 2
190
+ ) {
191
+ return githubQueuedGuardJob(args[0], args[1]);
192
+ }
163
193
  if (group === "publish" && command === "npm") {
164
194
  const options = parseNpmPublishArgs(args);
165
195
  if (options) {
@@ -0,0 +1,173 @@
1
+ /**
2
+ * "release once the pipeline succeeds" on github.
3
+ *
4
+ * gitlab expresses this natively inside the pipeline (the `create
5
+ * release` button job plus an executor with `needs` on the whole
6
+ * pipeline); github has no cross-workflow needs, so catladder uses a
7
+ * marker ref plus a workflow_run-triggered workflow instead:
8
+ *
9
+ * - the manual `create-release` dispatch runs `github-queue-check`:
10
+ * - the main workflow run for HEAD completed green (or never ran) →
11
+ * release right away (queued=false, the job's release step runs)
12
+ * - still running → push the marker ref and stop (queued=true skips
13
+ * the release step); the release-on-green workflow takes over
14
+ * - completed red → fail with a pointer to the force task
15
+ * - the release-on-green workflow (`on: workflow_run` of the main
16
+ * workflow) runs `github-queued-guard` on every completed main run:
17
+ * it consumes the marker when it matches the run's head sha and
18
+ * releases only when the run concluded green
19
+ */
20
+ import { appendFileSync } from "fs";
21
+ import { git } from "./releaseGit";
22
+
23
+ /**
24
+ * the generated main-branch workflow whose runs gate a queued release.
25
+ * Keep in sync with `workflowFileName` / GENERATED_FILE_PREFIX in
26
+ * pipeline/src/backends/github/GithubBackend.ts.
27
+ */
28
+ const GITHUB_MAIN_WORKFLOW = "catladder-main.yml";
29
+
30
+ /**
31
+ * where a queued release is recorded: a ref outside refs/heads and
32
+ * refs/tags, pointing at the commit the release was queued for.
33
+ * Writable with the job token (`permissions: contents: write`).
34
+ */
35
+ const QUEUE_MARKER_REF = "refs/catladder/release-queued";
36
+
37
+ /**
38
+ * github container jobs: the workspace volume is owned by the host
39
+ * runner user while catci runs as the container user — git refuses the
40
+ * repo ("dubious ownership") without a safe.directory entry. The
41
+ * release entrypoint scripts do the same, but the queue commands run
42
+ * before them.
43
+ */
44
+ const ensureGitSafeDirectory = async () => {
45
+ await git("config", "--global", "--add", "safe.directory", process.cwd());
46
+ };
47
+
48
+ const requireEnv = (name: string): string => {
49
+ const value = process.env[name];
50
+ if (!value) {
51
+ throw new Error(`${name} is not set`);
52
+ }
53
+ return value;
54
+ };
55
+
56
+ const fail = (message: string): never => {
57
+ console.error(message);
58
+ process.exit(1);
59
+ };
60
+
61
+ /**
62
+ * step outputs of the github-actions step running the command
63
+ * (`$GITHUB_OUTPUT` is always set in workflow steps)
64
+ */
65
+ const setStepOutput = (name: string, value: string) => {
66
+ const file = process.env.GITHUB_OUTPUT;
67
+ if (!file) {
68
+ return;
69
+ }
70
+ appendFileSync(file, `${name}=${value}\n`);
71
+ };
72
+
73
+ const getMainWorkflowRun = async (
74
+ sha: string,
75
+ ): Promise<{ status: string; conclusion: string | null } | null> => {
76
+ const apiUrl = process.env.GITHUB_API_URL ?? "https://api.github.com";
77
+ const repository = requireEnv("GITHUB_REPOSITORY");
78
+ const token = requireEnv("GITHUB_TOKEN");
79
+ const response = await fetch(
80
+ `${apiUrl}/repos/${repository}/actions/workflows/${GITHUB_MAIN_WORKFLOW}/runs?head_sha=${sha}&per_page=5`,
81
+ {
82
+ headers: {
83
+ authorization: `Bearer ${token}`,
84
+ accept: "application/vnd.github+json",
85
+ },
86
+ },
87
+ );
88
+ if (!response.ok) {
89
+ throw new Error(
90
+ `listing ${GITHUB_MAIN_WORKFLOW} runs failed: ${response.status} ${await response.text()}`,
91
+ );
92
+ }
93
+ const data = (await response.json()) as {
94
+ workflow_runs?: Array<{ status: string; conclusion: string | null }>;
95
+ };
96
+ // newest first — re-runs replace older attempts
97
+ return data.workflow_runs?.[0] ?? null;
98
+ };
99
+
100
+ /**
101
+ * `catci release github-queue-check` — first step of the manual
102
+ * create-release dispatch job. Decides between releasing right away
103
+ * (queued=false) and queueing for the running main workflow
104
+ * (queued=true).
105
+ */
106
+ export const githubQueueCheckJob = async () => {
107
+ await ensureGitSafeDirectory();
108
+ const sha = await git("rev-parse", "HEAD");
109
+ const run = await getMainWorkflowRun(sha);
110
+ if (!run) {
111
+ console.log(`no main workflow run found for ${sha} — releasing right away`);
112
+ setStepOutput("queued", "false");
113
+ return;
114
+ }
115
+ if (run.status === "completed") {
116
+ if (run.conclusion === "success") {
117
+ console.log(`main workflow for ${sha} succeeded — releasing right away`);
118
+ setStepOutput("queued", "false");
119
+ return;
120
+ }
121
+ fail(
122
+ `the main workflow for ${sha} concluded '${run.conclusion}' — ` +
123
+ `fix the pipeline, or use the force-create-release task to release anyway`,
124
+ );
125
+ }
126
+ // queued / in_progress / waiting: record the intent and let the
127
+ // release-on-green workflow do the release. Force-push: a re-queue
128
+ // (or a stale marker of an older commit) just moves the marker.
129
+ await git("push", "origin", `+${sha}:${QUEUE_MARKER_REF}`);
130
+ console.log(
131
+ `main workflow for ${sha} is ${run.status} — release queued: ` +
132
+ `the 'release on green' workflow creates the release as soon as the run succeeds`,
133
+ );
134
+ setStepOutput("queued", "true");
135
+ };
136
+
137
+ /**
138
+ * `catci release github-queued-guard <head-sha> <conclusion>` — guard
139
+ * step of the release-on-green workflow, running on every completed
140
+ * main workflow run. Consumes the marker when it matches the run and
141
+ * decides whether the release step runs (release=true).
142
+ */
143
+ export const githubQueuedGuardJob = async (
144
+ headSha: string,
145
+ conclusion: string,
146
+ ) => {
147
+ await ensureGitSafeDirectory();
148
+ const markerLine = await git("ls-remote", "origin", QUEUE_MARKER_REF);
149
+ const marker = markerLine.split(/\s+/)[0] ?? "";
150
+ if (!marker) {
151
+ console.log("no release queued — nothing to do");
152
+ setStepOutput("release", "false");
153
+ return;
154
+ }
155
+ if (marker !== headSha) {
156
+ console.log(
157
+ `queued release is for ${marker}, this run is for ${headSha} — leaving the queue untouched`,
158
+ );
159
+ setStepOutput("release", "false");
160
+ return;
161
+ }
162
+ // consume the marker either way: the queued intent applied to exactly
163
+ // this run, whatever its outcome
164
+ await git("push", "origin", `:${QUEUE_MARKER_REF}`);
165
+ if (conclusion !== "success") {
166
+ fail(
167
+ `queued release not executed: the main workflow concluded '${conclusion}' — ` +
168
+ `fix the pipeline and queue again (create-release task)`,
169
+ );
170
+ }
171
+ console.log(`main workflow for ${headSha} succeeded — releasing`);
172
+ setStepOutput("release", "true");
173
+ };