@catladder/cli 0.0.0-main-a6b45abe → 0.0.0-main-fd9c5f51
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.
- package/dist/apps/cli/src/apps/cli/commands/project/doctor/checkGithub.js +48 -9
- package/dist/apps/cli/src/apps/cli/commands/project/doctor/checkGithub.js.map +1 -1
- package/dist/apps/cli/src/apps/cli/commands/project/githubMergeGating.d.ts +83 -0
- package/dist/apps/cli/src/apps/cli/commands/project/githubMergeGating.js +80 -0
- package/dist/apps/cli/src/apps/cli/commands/project/githubMergeGating.js.map +1 -0
- package/dist/apps/cli/src/apps/cli/commands/project/setup/index.js +4 -3
- package/dist/apps/cli/src/apps/cli/commands/project/setup/index.js.map +1 -1
- package/dist/apps/cli/src/apps/cli/commands/project/setup/setupGithub.d.ts +12 -7
- package/dist/apps/cli/src/apps/cli/commands/project/setup/setupGithub.js +138 -31
- package/dist/apps/cli/src/apps/cli/commands/project/setup/setupGithub.js.map +1 -1
- package/dist/apps/cli/src/release/changesetsReleaseJob.js +64 -4
- package/dist/apps/cli/src/release/changesetsReleaseJob.js.map +1 -1
- package/dist/apps/cli/src/release/releaseGit.d.ts +2 -0
- package/dist/apps/cli/src/release/releaseGit.js +9 -1
- package/dist/apps/cli/src/release/releaseGit.js.map +1 -1
- package/dist/apps/cli/src/utils/github.d.ts +1 -1
- package/dist/apps/cli/src/utils/github.js.map +1 -1
- package/dist/bundles/catci/index.js +5 -5
- package/dist/bundles/catenv/index.js +5 -0
- package/dist/bundles/cli/index.js +282 -43
- package/dist/bundles/runner-images/changesets/Dockerfile +2 -1
- package/dist/bundles/skills/catladder-migrate-ci-backend/SKILL.md +15 -7
- package/dist/packages/pipeline/src/backends/github/githubReleaseJobs.js +5 -0
- package/dist/packages/pipeline/src/backends/github/githubReleaseJobs.js.map +1 -1
- package/dist/runner-images/changesets/Dockerfile +2 -1
- package/dist/skills/catladder-migrate-ci-backend/SKILL.md +15 -7
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/apps/cli/commands/project/__tests__/githubMergeGating.test.ts +123 -0
- package/src/apps/cli/commands/project/doctor/checkGithub.ts +74 -8
- package/src/apps/cli/commands/project/githubMergeGating.ts +101 -0
- package/src/apps/cli/commands/project/setup/index.ts +4 -3
- package/src/apps/cli/commands/project/setup/setupGithub.ts +190 -27
- package/src/release/changesetsReleaseJob.ts +81 -6
- package/src/release/releaseGit.ts +11 -0
- package/src/utils/github.ts +1 -1
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* pushes — the pushed tag triggers the taggedRelease pipeline.
|
|
7
7
|
*/
|
|
8
8
|
import { existsSync } from "fs";
|
|
9
|
-
import { readFile, readdir, rm, writeFile } from "fs/promises";
|
|
9
|
+
import { mkdtemp, readFile, readdir, rm, writeFile } from "fs/promises";
|
|
10
|
+
import { tmpdir } from "os";
|
|
10
11
|
import { join } from "path";
|
|
11
12
|
import type { Changeset } from "./changesets";
|
|
12
13
|
import {
|
|
@@ -16,7 +17,12 @@ import {
|
|
|
16
17
|
prependToChangelog,
|
|
17
18
|
renderChangelogEntries,
|
|
18
19
|
} from "./changesets";
|
|
19
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
ensureReleaseHistory,
|
|
22
|
+
getLastReleaseTag,
|
|
23
|
+
git,
|
|
24
|
+
gitWithEnv,
|
|
25
|
+
} from "./releaseGit";
|
|
20
26
|
import { appendStepSummary } from "./stepSummary";
|
|
21
27
|
|
|
22
28
|
const CHANGESET_DIR = ".changeset";
|
|
@@ -75,8 +81,12 @@ const requireEnv = (name: string): string => {
|
|
|
75
81
|
* - gitlab: CI checkouts have a read-only origin, so push via an
|
|
76
82
|
* authenticated url with GL_TOKEN (the same project access token
|
|
77
83
|
* semantic-release uses)
|
|
78
|
-
* - github:
|
|
79
|
-
*
|
|
84
|
+
* - github: over ssh with the release deploy key
|
|
85
|
+
* (CATLADDER_RELEASE_KEY, provisioned by `project setup`) — merge
|
|
86
|
+
* gating requires the `catladder ✅` check on the default branch,
|
|
87
|
+
* and only a deploy key can bypass that (github deliberately never
|
|
88
|
+
* lets the workflow token bypass rulesets). Without the secret the
|
|
89
|
+
* push falls back to origin, which works on repos without gating.
|
|
80
90
|
*/
|
|
81
91
|
const pushCommitAndTag = async (tag: string) => {
|
|
82
92
|
// --atomic: all refs or none — a rejected tag must not leave the
|
|
@@ -84,7 +94,29 @@ const pushCommitAndTag = async (tag: string) => {
|
|
|
84
94
|
// rederived an existing version)
|
|
85
95
|
if (process.env.GITHUB_ACTIONS === "true") {
|
|
86
96
|
const branch = requireEnv("GITHUB_REF_NAME");
|
|
87
|
-
|
|
97
|
+
const refs = [`HEAD:refs/heads/${branch}`, tag];
|
|
98
|
+
const deployKey = process.env.CATLADDER_RELEASE_KEY;
|
|
99
|
+
if (deployKey) {
|
|
100
|
+
await pushWithDeployKey(deployKey, refs);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
await git("push", "--atomic", "origin", ...refs);
|
|
105
|
+
} catch (e) {
|
|
106
|
+
// GH006: a required status check rejects the fresh release
|
|
107
|
+
// commit (it cannot have a passing check yet) — the push needs
|
|
108
|
+
// the release deploy key, which `project setup` provisions
|
|
109
|
+
if (/GH006|protected branch/i.test(`${e?.message ?? e}`)) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`pushing the release to '${branch}' was rejected by its branch protection:\n` +
|
|
112
|
+
`${e.message}\n` +
|
|
113
|
+
`The workflow token cannot bypass a required status check on '${branch}'. ` +
|
|
114
|
+
`Run \`catladder project setup\` to provision the release deploy key ` +
|
|
115
|
+
`(${RELEASE_KEY_ENV} secret + ruleset bypass), then rerun this job.`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
throw e;
|
|
119
|
+
}
|
|
88
120
|
return;
|
|
89
121
|
}
|
|
90
122
|
const token = requireEnv("GL_TOKEN");
|
|
@@ -95,6 +127,41 @@ const pushCommitAndTag = async (tag: string) => {
|
|
|
95
127
|
await git("push", "--atomic", remote, `HEAD:refs/heads/${branch}`, tag);
|
|
96
128
|
};
|
|
97
129
|
|
|
130
|
+
const RELEASE_KEY_ENV = "CATLADDER_RELEASE_KEY";
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* pushes over ssh with the release deploy key — the only actor that
|
|
134
|
+
* can bypass the merge-gating ruleset on the default branch
|
|
135
|
+
*/
|
|
136
|
+
const pushWithDeployKey = async (privateKey: string, refs: string[]) => {
|
|
137
|
+
const serverUrl = process.env.GITHUB_SERVER_URL ?? "https://github.com";
|
|
138
|
+
const host = new URL(serverUrl).host;
|
|
139
|
+
const repository = requireEnv("GITHUB_REPOSITORY");
|
|
140
|
+
const dir = await mkdtemp(join(tmpdir(), "release-key-"));
|
|
141
|
+
try {
|
|
142
|
+
const keyFile = join(dir, "id_ed25519");
|
|
143
|
+
// a key file without a trailing newline is rejected by openssh
|
|
144
|
+
await writeFile(
|
|
145
|
+
keyFile,
|
|
146
|
+
privateKey.endsWith("\n") ? privateKey : `${privateKey}\n`,
|
|
147
|
+
{
|
|
148
|
+
mode: 0o600,
|
|
149
|
+
},
|
|
150
|
+
);
|
|
151
|
+
await gitWithEnv(
|
|
152
|
+
{
|
|
153
|
+
GIT_SSH_COMMAND: `ssh -i ${keyFile} -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new`,
|
|
154
|
+
},
|
|
155
|
+
"push",
|
|
156
|
+
"--atomic",
|
|
157
|
+
`git@${host}:${repository}.git`,
|
|
158
|
+
...refs,
|
|
159
|
+
);
|
|
160
|
+
} finally {
|
|
161
|
+
await rm(dir, { recursive: true, force: true });
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
98
165
|
/**
|
|
99
166
|
* tags pushed with the default GITHUB_TOKEN do not trigger `on: push:
|
|
100
167
|
* tags` workflows (github's recursion guard), so the taggedRelease
|
|
@@ -196,7 +263,15 @@ export const changesetsReleaseJob = async () => {
|
|
|
196
263
|
} else {
|
|
197
264
|
await git("add", CHANGELOG_FILE, CHANGESET_DIR);
|
|
198
265
|
}
|
|
199
|
-
|
|
266
|
+
// [skip ci] on github only: deploy-key pushes trigger workflows
|
|
267
|
+
// (unlike GITHUB_TOKEN pushes), and neither the release commit nor
|
|
268
|
+
// the tag needs a push-triggered run — the taggedRelease workflow is
|
|
269
|
+
// dispatched explicitly below, [skip ci] keeps it the only run. On
|
|
270
|
+
// gitlab the tag pipeline MUST fire natively, so no marker there
|
|
271
|
+
// (release-commit branch pipelines are already filtered by rules).
|
|
272
|
+
const onGithub = process.env.GITHUB_ACTIONS === "true";
|
|
273
|
+
const subject = `chore(release): ${version}${onGithub ? " [skip ci]" : ""}`;
|
|
274
|
+
await git("commit", "-m", `${subject}\n\n${entries}`);
|
|
200
275
|
await git("tag", tag);
|
|
201
276
|
await pushCommitAndTag(tag);
|
|
202
277
|
console.log(`released ${tag}`);
|
|
@@ -12,6 +12,17 @@ export const git = async (...args: string[]): Promise<string> => {
|
|
|
12
12
|
return stdout.trim();
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
+
/** like `git`, but with extra environment variables (e.g. GIT_SSH_COMMAND) */
|
|
16
|
+
export const gitWithEnv = async (
|
|
17
|
+
env: Record<string, string>,
|
|
18
|
+
...args: string[]
|
|
19
|
+
): Promise<string> => {
|
|
20
|
+
const { stdout } = await execFile("git", args, {
|
|
21
|
+
env: { ...process.env, ...env },
|
|
22
|
+
});
|
|
23
|
+
return stdout.trim();
|
|
24
|
+
};
|
|
25
|
+
|
|
15
26
|
/**
|
|
16
27
|
* CI checkouts are usually shallow and tagless (gitlab clones with
|
|
17
28
|
* depth 1; github's checkout needs fetch-depth: 0) — the local history
|
package/src/utils/github.ts
CHANGED
|
@@ -170,7 +170,7 @@ export const ensureGithubEnvironment = async (
|
|
|
170
170
|
|
|
171
171
|
/** runs `gh api` with an optional JSON body and returns the parsed response */
|
|
172
172
|
export const ghApiJson = async <T = any>(
|
|
173
|
-
method: "GET" | "PATCH" | "PUT",
|
|
173
|
+
method: "GET" | "POST" | "PATCH" | "PUT" | "DELETE",
|
|
174
174
|
path: string,
|
|
175
175
|
body?: unknown,
|
|
176
176
|
): Promise<T> => {
|