impel-cli 0.20.63-canary.1 → 0.20.63-canary.2
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/package.json
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
function main() {
|
|
8
|
+
const destination = process.env.RUNNER_TEMP;
|
|
9
|
+
if (!destination) throw new Error("RUNNER_TEMP is required");
|
|
10
|
+
const { version } = JSON.parse(fs.readFileSync("package.json", "utf8"));
|
|
11
|
+
execFileSync("pnpm", ["pack", "--pack-destination", destination], { stdio: "inherit" });
|
|
12
|
+
const tarball = path.join(destination, `impel-cli-${version}.tgz`);
|
|
13
|
+
if (!fs.existsSync(tarball)) throw new Error(`pnpm pack did not create ${tarball}`);
|
|
14
|
+
const prefix = path.join(destination, "impel-prefix");
|
|
15
|
+
execFileSync("npm", ["install", "--global", "--prefix", prefix, tarball], { stdio: "inherit" });
|
|
16
|
+
const executable = process.platform === "win32" ? path.join(prefix, "impel.cmd") : path.join(prefix, "bin", "impel");
|
|
17
|
+
const actual = execFileSync(executable, ["--version"], { encoding: "utf8" }).trim();
|
|
18
|
+
if (actual !== version) throw new Error(`packed CLI reports ${actual}, expected ${version}`);
|
|
19
|
+
console.log(`Verified packed impel-cli ${version}.`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
main();
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error(`pack-release: ${error?.message || error}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
import { compareSemver } from "./canary-version.mjs";
|
|
9
|
+
import { evaluateCheckRuns } from "./required-checks.mjs";
|
|
10
|
+
|
|
11
|
+
const DIST_TAGS_URL = "https://registry.npmjs.org/-/package/impel-cli/dist-tags";
|
|
12
|
+
|
|
13
|
+
function appendSummary(text) {
|
|
14
|
+
if (!process.env.GITHUB_STEP_SUMMARY) throw new Error("GITHUB_STEP_SUMMARY is not set");
|
|
15
|
+
fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, text.endsWith("\n") ? text : `${text}\n`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function fetchJson(url, { token, allowMissing = false } = {}) {
|
|
19
|
+
const response = await fetch(url, {
|
|
20
|
+
headers: {
|
|
21
|
+
Accept: "application/vnd.github+json",
|
|
22
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
23
|
+
"User-Agent": "UseImpel/impel-cli release gate",
|
|
24
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
25
|
+
},
|
|
26
|
+
signal: AbortSignal.timeout(30_000),
|
|
27
|
+
});
|
|
28
|
+
if (response.status === 404 && allowMissing) return {};
|
|
29
|
+
if (!response.ok) throw new Error(`request failed: HTTP ${response.status}`);
|
|
30
|
+
return response.json();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function checkRunsForCommit(repository, sha, token) {
|
|
34
|
+
const runs = [];
|
|
35
|
+
for (let page = 1; ; page += 1) {
|
|
36
|
+
const body = await fetchJson(
|
|
37
|
+
`https://api.github.com/repos/${repository}/commits/${sha}/check-runs?per_page=100&page=${page}`,
|
|
38
|
+
{ token },
|
|
39
|
+
);
|
|
40
|
+
runs.push(...body.check_runs.map(({ name, status, conclusion }) => ({ name, status, conclusion })));
|
|
41
|
+
if (body.check_runs.length < 100) return runs;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function manifest() {
|
|
46
|
+
return JSON.parse(fs.readFileSync("package.json", "utf8"));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function verifyTag({ tag, version }) {
|
|
50
|
+
const expected = `v${version}`;
|
|
51
|
+
if (tag !== expected) throw new Error(`release tag ${tag} does not match ${expected}`);
|
|
52
|
+
return expected;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function verifyMain({ sha, mainRef = "origin/main" }) {
|
|
56
|
+
try {
|
|
57
|
+
execFileSync("git", ["merge-base", "--is-ancestor", sha, mainRef], { stdio: "ignore" });
|
|
58
|
+
} catch {
|
|
59
|
+
throw new Error(`tagged commit ${sha} is not on ${mainRef}; merge to main before tagging`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function verifyChecks({ repository, sha, token }) {
|
|
64
|
+
const result = evaluateCheckRuns(await checkRunsForCommit(repository, sha, token));
|
|
65
|
+
const summary = result.summary;
|
|
66
|
+
appendSummary(summary);
|
|
67
|
+
if (result.state !== "success") {
|
|
68
|
+
throw new Error(`required checks on ${sha} are not all green: ${result.failures.join("; ") || result.pending.join(", ")}`);
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function verifySuccessor({ version, channel = version.includes("-") ? "next" : "latest" }) {
|
|
74
|
+
const tags = await fetchJson(DIST_TAGS_URL, { allowMissing: true });
|
|
75
|
+
const current = tags[channel] ?? (channel === "next" ? tags.latest : undefined);
|
|
76
|
+
const message = current === undefined
|
|
77
|
+
? `No existing \`${channel}\` dist-tag; ${version} is the channel bootstrap.`
|
|
78
|
+
: `${version} strictly succeeds \`${channel}\`=${current}. Current tags: ${JSON.stringify(tags)}`;
|
|
79
|
+
if (current !== undefined && compareSemver(version, current) <= 0) {
|
|
80
|
+
throw new Error(`version ${version} is not a strict semver successor of ${channel}=${current}`);
|
|
81
|
+
}
|
|
82
|
+
appendSummary(`## Release gate: dist-tag successor\n\n${message}\n`);
|
|
83
|
+
console.log(message);
|
|
84
|
+
return { channel, current, tags };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function selectDistTag(version) {
|
|
88
|
+
const tag = version.includes("-") ? "next" : "latest";
|
|
89
|
+
if (!process.env.GITHUB_ENV) throw new Error("GITHUB_ENV is not set");
|
|
90
|
+
fs.appendFileSync(process.env.GITHUB_ENV, `NPM_DIST_TAG=${tag}\n`);
|
|
91
|
+
console.log(`Publishing ${version} under npm dist-tag ${tag}.`);
|
|
92
|
+
return tag;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function main([command]) {
|
|
96
|
+
const pkg = manifest();
|
|
97
|
+
if (command === "verify-tag") {
|
|
98
|
+
verifyTag({ tag: process.env.RELEASE_TAG, version: pkg.version });
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (command === "verify-main") {
|
|
102
|
+
const sha = execFileSync("git", ["rev-parse", "HEAD"], { encoding: "utf8" }).trim();
|
|
103
|
+
verifyMain({ sha });
|
|
104
|
+
console.log(`Tagged commit ${sha} is on origin/main.`);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (command === "verify-checks") {
|
|
108
|
+
const sha = execFileSync("git", ["rev-parse", "HEAD"], { encoding: "utf8" }).trim();
|
|
109
|
+
await verifyChecks({
|
|
110
|
+
repository: process.env.GITHUB_REPOSITORY,
|
|
111
|
+
sha,
|
|
112
|
+
token: process.env.GH_TOKEN,
|
|
113
|
+
});
|
|
114
|
+
console.log(`All required checks are green on ${sha}.`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (command === "verify-successor") {
|
|
118
|
+
await verifySuccessor({ version: pkg.version });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (command === "select-dist-tag") {
|
|
122
|
+
selectDistTag(pkg.version);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
throw new Error("usage: node scripts/release-gate.mjs verify-tag|verify-main|verify-checks|verify-successor|select-dist-tag");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
129
|
+
main(process.argv.slice(2)).catch((error) => {
|
|
130
|
+
console.error(`release-gate: ${error?.message || error}`);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
|
|
6
|
+
function main() {
|
|
7
|
+
if (!process.env.GITHUB_OUTPUT) throw new Error("GITHUB_OUTPUT is not set");
|
|
8
|
+
const { version } = JSON.parse(fs.readFileSync("package.json", "utf8"));
|
|
9
|
+
const sourceCommit = execFileSync("git", ["rev-parse", "HEAD"], { encoding: "utf8" }).trim();
|
|
10
|
+
fs.appendFileSync(process.env.GITHUB_OUTPUT, `version=${version}\nsource_commit=${sourceCommit}\n`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
main();
|
|
15
|
+
} catch (error) {
|
|
16
|
+
console.error(`release-metadata: ${error?.message || error}`);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// The CI check names a publishable commit must carry (canary release cycle, U4).
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// `
|
|
7
|
-
// a
|
|
8
|
-
// release. Every push to `dev` and `main` runs these via `ci.yml`, so they
|
|
9
|
-
// must exist on a publishable SHA; names must match the job names exactly.
|
|
4
|
+
// Both release workflows consume this list through checked-in gate scripts.
|
|
5
|
+
// A renamed CI job that is not renamed here fails the suite, not a release.
|
|
6
|
+
// Every push to `dev` and `main` runs these via `ci.yml`, so they must exist on
|
|
7
|
+
// a publishable SHA; names must match the job names exactly.
|
|
10
8
|
//
|
|
11
9
|
// Usage:
|
|
12
10
|
// node scripts/required-checks.mjs <check-runs.ndjson>
|
|
@@ -81,7 +79,7 @@ export function evaluateCheckRuns(runs, { required = REQUIRED_CHECKS, conditiona
|
|
|
81
79
|
}
|
|
82
80
|
|
|
83
81
|
const state = failures.length > 0 ? "failure" : pending.length > 0 ? "pending" : "success";
|
|
84
|
-
const summary = ["##
|
|
82
|
+
const summary = ["## Release gate: checks on the commit", ""];
|
|
85
83
|
summary.push("| Check | Result |", "| --- | --- |");
|
|
86
84
|
for (const name of [...required, ...conditional]) summary.push(`| ${name} | ${describe(name)} |`);
|
|
87
85
|
if (absentConditional.length > 0) {
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
|
|
7
|
-
const TARGET = "https://api.github.com/repos/UseImpel/impel-apps/dispatches";
|
|
8
|
-
|
|
9
|
-
async function main([version]) {
|
|
10
|
-
const token = process.env.IMPEL_APPS_DISPATCH_TOKEN;
|
|
11
|
-
const commit = process.env.GITHUB_SHA;
|
|
12
|
-
if (!version || !commit) throw new Error("canary version and GITHUB_SHA are required");
|
|
13
|
-
if (!token) {
|
|
14
|
-
const message = `IMPEL_APPS_DISPATCH_TOKEN is not configured; impel-apps will pick up impel-cli@${version} on its next canary rebuild.`;
|
|
15
|
-
console.warn(message);
|
|
16
|
-
if (process.env.GITHUB_STEP_SUMMARY) {
|
|
17
|
-
fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, `${message}\n`);
|
|
18
|
-
}
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
const response = await fetch(TARGET, {
|
|
22
|
-
method: "POST",
|
|
23
|
-
headers: {
|
|
24
|
-
Accept: "application/vnd.github+json",
|
|
25
|
-
Authorization: `Bearer ${token}`,
|
|
26
|
-
"Content-Type": "application/json",
|
|
27
|
-
"User-Agent": "UseImpel/impel-cli canary publisher",
|
|
28
|
-
"X-GitHub-Api-Version": "2022-11-28",
|
|
29
|
-
},
|
|
30
|
-
body: JSON.stringify({
|
|
31
|
-
event_type: "impel-cli-canary",
|
|
32
|
-
client_payload: { version, source_commit: commit, dist_tag: "canary" },
|
|
33
|
-
}),
|
|
34
|
-
signal: AbortSignal.timeout(30_000),
|
|
35
|
-
});
|
|
36
|
-
if (!response.ok) throw new Error(`impel-apps dispatch failed: HTTP ${response.status}`);
|
|
37
|
-
console.log(`Dispatched impel-cli canary ${version} (${commit}) to UseImpel/impel-apps.`);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
41
|
-
main(process.argv.slice(2)).catch((error) => {
|
|
42
|
-
console.error(`notify-canary: ${error?.message || error}`);
|
|
43
|
-
process.exit(1);
|
|
44
|
-
});
|
|
45
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
|
|
6
|
-
import { evaluateCheckRuns } from "./required-checks.mjs";
|
|
7
|
-
|
|
8
|
-
const POLL_INTERVAL_MS = 30_000;
|
|
9
|
-
const TIMEOUT_MS = 60 * 60 * 1_000;
|
|
10
|
-
|
|
11
|
-
const delay = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
12
|
-
|
|
13
|
-
async function fetchCheckRuns(repository, sha, token) {
|
|
14
|
-
const runs = [];
|
|
15
|
-
for (let page = 1; ; page += 1) {
|
|
16
|
-
const response = await fetch(
|
|
17
|
-
`https://api.github.com/repos/${repository}/commits/${sha}/check-runs?per_page=100&page=${page}`,
|
|
18
|
-
{
|
|
19
|
-
headers: {
|
|
20
|
-
Accept: "application/vnd.github+json",
|
|
21
|
-
Authorization: `Bearer ${token}`,
|
|
22
|
-
"User-Agent": "UseImpel/impel-cli canary publisher",
|
|
23
|
-
"X-GitHub-Api-Version": "2022-11-28",
|
|
24
|
-
},
|
|
25
|
-
signal: AbortSignal.timeout(30_000),
|
|
26
|
-
},
|
|
27
|
-
);
|
|
28
|
-
if (!response.ok) throw new Error(`Checks API request failed: HTTP ${response.status}`);
|
|
29
|
-
const body = await response.json();
|
|
30
|
-
for (const { name, status, conclusion } of body.check_runs) {
|
|
31
|
-
runs.push({ name, status, conclusion });
|
|
32
|
-
}
|
|
33
|
-
if (body.check_runs.length < 100) return runs;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function writeSummary(summary) {
|
|
38
|
-
if (!process.env.GITHUB_STEP_SUMMARY) throw new Error("GITHUB_STEP_SUMMARY is not set");
|
|
39
|
-
fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, summary);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function main() {
|
|
43
|
-
const { GITHUB_REPOSITORY: repository, GITHUB_SHA: sha, GH_TOKEN: token } = process.env;
|
|
44
|
-
if (!repository || !sha || !token) throw new Error("GITHUB_REPOSITORY, GITHUB_SHA, and GH_TOKEN are required");
|
|
45
|
-
const deadline = Date.now() + TIMEOUT_MS;
|
|
46
|
-
let lastSummary = "";
|
|
47
|
-
while (Date.now() < deadline) {
|
|
48
|
-
try {
|
|
49
|
-
const result = evaluateCheckRuns(await fetchCheckRuns(repository, sha, token));
|
|
50
|
-
lastSummary = result.summary;
|
|
51
|
-
if (result.state === "success") {
|
|
52
|
-
writeSummary(result.summary);
|
|
53
|
-
console.log(`All required checks are green on ${sha}.`);
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
if (result.state === "failure") {
|
|
57
|
-
writeSummary(result.summary);
|
|
58
|
-
throw new Error(`A required check on ${sha} completed without success; nothing is published for this commit.`);
|
|
59
|
-
}
|
|
60
|
-
console.log(`Required checks are still pending on ${sha}; retrying in 30 seconds.`);
|
|
61
|
-
} catch (error) {
|
|
62
|
-
if (/completed without success/u.test(error?.message || "")) throw error;
|
|
63
|
-
console.warn(`Checks API request failed (${error?.message || error}); retrying in 30 seconds.`);
|
|
64
|
-
}
|
|
65
|
-
await delay(POLL_INTERVAL_MS);
|
|
66
|
-
}
|
|
67
|
-
if (lastSummary) writeSummary(lastSummary);
|
|
68
|
-
throw new Error(`Canary gate timed out after 60 minutes waiting for required checks on ${sha}.`);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
72
|
-
main().catch((error) => {
|
|
73
|
-
console.error(`wait-for-required-checks: ${error?.message || error}`);
|
|
74
|
-
process.exit(1);
|
|
75
|
-
});
|
|
76
|
-
}
|