@geonosis/release 2.5.0 → 2.5.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.
- package/CHANGELOG.md +40 -0
- package/bin/geonosis-release.mjs +11 -3
- package/dist/{chunk-NNGZ47I5.js → chunk-RQOZEZSB.js} +239 -105
- package/dist/index.d.ts +13 -3
- package/dist/index.js +1 -1
- package/dist/release-cli.js +138 -25
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# @geonosis/release
|
|
2
2
|
|
|
3
|
+
## 2.5.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 7f24da6: `geonosis-release smoke baseline`/`smoke run --rc-dir` now write, beside the tarballs `packRc`
|
|
8
|
+
makes, a `candidate/` scratch package pinned to the candidate's own `@geonosis/cli` tarball (an
|
|
9
|
+
override for every packed name, in `package.json` and — pnpm actually reads it from —
|
|
10
|
+
`pnpm-workspace.yaml`), and print `candidate CLI: (cd <dir>/candidate && pnpm install
|
|
11
|
+
--ignore-scripts && pnpm exec geonosis update --registry <dir> --to <version> …)` (#389). A
|
|
12
|
+
consumer at 2.4.3 cannot run `update --registry <dir>` at all (it parses the directory as a URL),
|
|
13
|
+
and a bare tarball install cannot resolve its own siblings either (#387) — the adoption story had
|
|
14
|
+
no first step. `docs/releasing.md`'s RC section carries the line as that first step.
|
|
15
|
+
- 7f24da6: A machine-wide heavy budget, two slots, held by every tool that spends one (#385): measured load 48
|
|
16
|
+
with during.day's Stop-hook tier, this kit's Stop-hook tier, its ratchet and an engineer's suite all
|
|
17
|
+
running at once — nothing held the two-slot budget across the two repos, and three kit tests read
|
|
18
|
+
red under it that were green alone. `@geonosis/ratchet` ships `takeSlot`/`heavySlotDir` (a lock
|
|
19
|
+
directory under `os.tmpdir()/geonosis-heavy`, two slot files each holding `{ pid, command, cwd,
|
|
20
|
+
startedAt }`, a dead holder reaped on the next take, a taker that finds both held waiting once
|
|
21
|
+
before refusing naming them); `geonosis-verify` (each tier run) and `geonosis-ratchet` (the run, not
|
|
22
|
+
`--prove`'s inner unit) take a slot for the run's duration. `@geonosis/release`'s `smoke
|
|
23
|
+
baseline`/`smoke run` take one too, from their own copy (this layer imports nothing, same posture as
|
|
24
|
+
`envelope.ts`). The plugin's Stop hook reads a fresh green `gate-report.<tier>.json` — its own
|
|
25
|
+
recorded commit equal to the current `HEAD` with a clean tree — and reports PASS without re-running;
|
|
26
|
+
otherwise it peeks the same two slots (never claims one itself, or the verify child it is about to
|
|
27
|
+
spawn would be racing against its own reservation) and blocks naming both holders rather than
|
|
28
|
+
running a third tier.
|
|
29
|
+
- 7f24da6: The smoke's run copy sits where nothing is above it (#373's true root): it used to land at
|
|
30
|
+
`<root>/.geonosis/consumer-snapshots/<name>/run`, an ancestor of which (`<root>` itself) carries
|
|
31
|
+
this kit's own `node_modules` — so a package a consumer never installed resolved to the kit's own
|
|
32
|
+
workspace copy, two directories up, and every tool run inside the copy (the doctor included) read
|
|
33
|
+
it as theirs. The copy now lands under the OS temp directory instead, as `packages/verify`'s prove
|
|
34
|
+
copy does; its path is printed as `run copy: <path>` and it is removed on a passing run and kept
|
|
35
|
+
(for reading) on a failing one.
|
|
36
|
+
- 7f24da6: The smoke runner owns its child's lifetime (#368): two `pnpm install` processes from a 2026-09-03
|
|
37
|
+
smoke were still alive two days later, holding pnpm's store lock every other install on the machine
|
|
38
|
+
then waits on. Every install and phase now spawns in its own process group with a timeout (default
|
|
39
|
+
20 minutes, `release.smoke.timeoutMs` overrides it) and is killed by the WHOLE group — SIGTERM, then
|
|
40
|
+
SIGKILL after a grace period — on timeout; a timed-out phase is recorded as a fail with the tail
|
|
41
|
+
`timed out after <n> ms — killed`.
|
|
42
|
+
|
|
3
43
|
## 2.5.0
|
|
4
44
|
|
|
5
45
|
### Patch Changes
|
package/bin/geonosis-release.mjs
CHANGED
|
@@ -9,11 +9,19 @@ import { dirname, join } from 'node:path'
|
|
|
9
9
|
import { fileURLToPath } from 'node:url'
|
|
10
10
|
|
|
11
11
|
const here = dirname(fileURLToPath(import.meta.url))
|
|
12
|
-
|
|
12
|
+
// tsup never reads a test file, so one counting as source made every bin refuse a build that had
|
|
13
|
+
// not gone stale (#399).
|
|
14
|
+
const NOT_SOURCE = (name) =>
|
|
15
|
+
name.endsWith('.test.ts') ||
|
|
16
|
+
name.endsWith('.test.tsx') ||
|
|
17
|
+
name === '__tests__' ||
|
|
18
|
+
name === '__fixtures__'
|
|
19
|
+
const newest = (dir, skip) =>
|
|
13
20
|
existsSync(dir)
|
|
14
21
|
? readdirSync(dir, { withFileTypes: true }).reduce((most, entry) => {
|
|
22
|
+
if (skip !== undefined && skip(entry.name)) return most
|
|
15
23
|
const at = join(dir, entry.name)
|
|
16
|
-
return Math.max(most, entry.isDirectory() ? newest(at) : statSync(at).mtimeMs)
|
|
24
|
+
return Math.max(most, entry.isDirectory() ? newest(at, skip) : statSync(at).mtimeMs)
|
|
17
25
|
}, 0)
|
|
18
26
|
: 0
|
|
19
27
|
const src = join(here, '..', 'src')
|
|
@@ -30,7 +38,7 @@ if (existsSync(src)) {
|
|
|
30
38
|
)
|
|
31
39
|
process.exit(2)
|
|
32
40
|
}
|
|
33
|
-
if (newest(src) > built) {
|
|
41
|
+
if (newest(src, NOT_SOURCE) > built) {
|
|
34
42
|
process.stderr.write(
|
|
35
43
|
'geonosis-release: dist is older than src — run pnpm build before trusting this bin.\n',
|
|
36
44
|
)
|
|
@@ -97,7 +97,7 @@ var parseSchema = (value2) => {
|
|
|
97
97
|
sessionVariables: strings(value2["sessionVariables"], "release.schema.sessionVariables")
|
|
98
98
|
};
|
|
99
99
|
};
|
|
100
|
-
var SMOKE_KEYS = "expected { exclude?, snapshots? }";
|
|
100
|
+
var SMOKE_KEYS = "expected { exclude?, snapshots?, timeoutMs? }";
|
|
101
101
|
var SNAPSHOT_KEYS = "expected { commands?, install?, name }";
|
|
102
102
|
var SMOKE_PHASES = PHASES;
|
|
103
103
|
var parseCommands = (value2, at) => {
|
|
@@ -146,10 +146,11 @@ var oneSnapshot = (value2, index) => {
|
|
|
146
146
|
name
|
|
147
147
|
};
|
|
148
148
|
};
|
|
149
|
+
var KNOWN_SMOKE_KEYS = /* @__PURE__ */ new Set(["exclude", "snapshots", "timeoutMs"]);
|
|
149
150
|
var parseSmoke = (value2) => {
|
|
150
151
|
if (value2 === void 0) return { exclude: [], snapshots: [] };
|
|
151
152
|
if (!isRecord(value2)) throw new CannotRun(`release.smoke must be an object \u2014 ${SMOKE_KEYS}`);
|
|
152
|
-
const unknown = Object.keys(value2).find((key) => key
|
|
153
|
+
const unknown = Object.keys(value2).find((key) => !KNOWN_SMOKE_KEYS.has(key));
|
|
153
154
|
if (unknown !== void 0) {
|
|
154
155
|
throw new CannotRun(`release.smoke.${unknown} is not a key it reads \u2014 ${SMOKE_KEYS}`);
|
|
155
156
|
}
|
|
@@ -157,9 +158,14 @@ var parseSmoke = (value2) => {
|
|
|
157
158
|
if (snapshots !== void 0 && !Array.isArray(snapshots)) {
|
|
158
159
|
throw new CannotRun(`release.smoke.snapshots must be a list \u2014 ${SNAPSHOT_KEYS}, per entry`);
|
|
159
160
|
}
|
|
161
|
+
const timeoutMs = value2["timeoutMs"];
|
|
162
|
+
if (timeoutMs !== void 0 && (typeof timeoutMs !== "number" || timeoutMs <= 0)) {
|
|
163
|
+
throw new CannotRun("release.smoke.timeoutMs must be a positive number of milliseconds");
|
|
164
|
+
}
|
|
160
165
|
return {
|
|
161
166
|
exclude: strings(value2["exclude"], "release.smoke.exclude"),
|
|
162
|
-
snapshots: (snapshots ?? []).map(oneSnapshot)
|
|
167
|
+
snapshots: (snapshots ?? []).map(oneSnapshot),
|
|
168
|
+
...timeoutMs === void 0 ? {} : { timeoutMs }
|
|
163
169
|
};
|
|
164
170
|
};
|
|
165
171
|
var parseReleaseConfig = (raw) => {
|
|
@@ -366,9 +372,18 @@ var askRegistry = async ({
|
|
|
366
372
|
};
|
|
367
373
|
|
|
368
374
|
// src/published.ts
|
|
369
|
-
import {
|
|
375
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
376
|
+
|
|
377
|
+
// src/manifests.ts
|
|
378
|
+
import { readdirSync } from "fs";
|
|
370
379
|
import { join as join2, relative, sep } from "path";
|
|
371
|
-
var NEVER_WALKED = /* @__PURE__ */ new Set([
|
|
380
|
+
var NEVER_WALKED = /* @__PURE__ */ new Set([
|
|
381
|
+
"build",
|
|
382
|
+
"coverage",
|
|
383
|
+
"dist",
|
|
384
|
+
"node_modules",
|
|
385
|
+
"storybook-static"
|
|
386
|
+
]);
|
|
372
387
|
var pathOf = (root, path) => relative(root, path).split(sep).join("/");
|
|
373
388
|
var manifestsUnder = (root) => {
|
|
374
389
|
const found = [];
|
|
@@ -391,6 +406,8 @@ var manifestsUnder = (root) => {
|
|
|
391
406
|
walk(root);
|
|
392
407
|
return found;
|
|
393
408
|
};
|
|
409
|
+
|
|
410
|
+
// src/published.ts
|
|
394
411
|
var censusOf = (root) => {
|
|
395
412
|
const excused = [];
|
|
396
413
|
const locals = [];
|
|
@@ -1178,16 +1195,16 @@ var declaredIn = (config) => {
|
|
|
1178
1195
|
routes: listed.map((route) => isRecord3(route) ? route["pattern"] : route).filter((pattern) => typeof pattern === "string").toSorted()
|
|
1179
1196
|
};
|
|
1180
1197
|
};
|
|
1181
|
-
var readWrangler = (root,
|
|
1182
|
-
const path = resolve4(root,
|
|
1198
|
+
var readWrangler = (root, relative4, env) => {
|
|
1199
|
+
const path = resolve4(root, relative4);
|
|
1183
1200
|
let parsed;
|
|
1184
1201
|
try {
|
|
1185
1202
|
const source = readFileSync6(path, "utf8");
|
|
1186
|
-
parsed =
|
|
1203
|
+
parsed = relative4.endsWith(".toml") ? parseToml(source) : parseJsonc(source);
|
|
1187
1204
|
} catch (error) {
|
|
1188
|
-
throw new CannotRun(`${
|
|
1205
|
+
throw new CannotRun(`${relative4} could not be read: ${error.message}`);
|
|
1189
1206
|
}
|
|
1190
|
-
if (!isRecord3(parsed)) throw new CannotRun(`${
|
|
1207
|
+
if (!isRecord3(parsed)) throw new CannotRun(`${relative4} is not a wrangler configuration`);
|
|
1191
1208
|
const environments = parsed["env"];
|
|
1192
1209
|
const block = env !== void 0 && isRecord3(environments) && isRecord3(environments[env]) ? environments[env] : parsed;
|
|
1193
1210
|
return declaredIn(block);
|
|
@@ -1987,9 +2004,18 @@ var formatSchema = (report) => {
|
|
|
1987
2004
|
};
|
|
1988
2005
|
|
|
1989
2006
|
// src/smoke-run.ts
|
|
1990
|
-
import { spawnSync as spawnSync4 } from "child_process";
|
|
1991
|
-
import {
|
|
1992
|
-
|
|
2007
|
+
import { spawn as spawn2, spawnSync as spawnSync4 } from "child_process";
|
|
2008
|
+
import {
|
|
2009
|
+
existsSync as existsSync7,
|
|
2010
|
+
mkdirSync as mkdirSync3,
|
|
2011
|
+
mkdtempSync as mkdtempSync2,
|
|
2012
|
+
readdirSync as readdirSync5,
|
|
2013
|
+
readFileSync as readFileSync10,
|
|
2014
|
+
rmSync as rmSync3,
|
|
2015
|
+
writeFileSync as writeFileSync4
|
|
2016
|
+
} from "fs";
|
|
2017
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
2018
|
+
import { join as join7, resolve as resolve9 } from "path";
|
|
1993
2019
|
var BASELINE_FILE = "baseline.json";
|
|
1994
2020
|
var INSTALL = {
|
|
1995
2021
|
bun: ["install"],
|
|
@@ -2098,7 +2124,7 @@ var TOP_LEVEL = /^\S/;
|
|
|
2098
2124
|
var KEYED = /^\s+["']?(.+?)["']?\s*:/;
|
|
2099
2125
|
var pinsInWorkspaceYaml = (work, pins) => {
|
|
2100
2126
|
const at = join7(work, "pnpm-workspace.yaml");
|
|
2101
|
-
const written = Object.entries(pins).map(([name, spec]) => ` '${name}': '${spec}'`);
|
|
2127
|
+
const written = Object.entries(pins).filter((entry) => entry[1] !== void 0).map(([name, spec]) => ` '${name}': '${spec}'`);
|
|
2102
2128
|
const lines2 = existsSync7(at) ? readFileSync10(at, "utf8").split("\n") : [];
|
|
2103
2129
|
const kept = [];
|
|
2104
2130
|
let inside = false;
|
|
@@ -2111,10 +2137,10 @@ var pinsInWorkspaceYaml = (work, pins) => {
|
|
|
2111
2137
|
continue;
|
|
2112
2138
|
}
|
|
2113
2139
|
const key = inside ? KEYED.exec(line)?.[1] : void 0;
|
|
2114
|
-
if (key !== void 0 && pins
|
|
2140
|
+
if (key !== void 0 && Object.hasOwn(pins, key)) continue;
|
|
2115
2141
|
kept.push(line);
|
|
2116
2142
|
}
|
|
2117
|
-
const body = found ? kept : [...kept, OVERRIDES, ...written, ""];
|
|
2143
|
+
const body = found ? kept : written.length === 0 ? kept : [...kept, OVERRIDES, ...written, ""];
|
|
2118
2144
|
writeFileSync4(at, body.join("\n"));
|
|
2119
2145
|
};
|
|
2120
2146
|
var pinsInManifest = (work, pins) => {
|
|
@@ -2122,13 +2148,42 @@ var pinsInManifest = (work, pins) => {
|
|
|
2122
2148
|
if (!existsSync7(at)) return;
|
|
2123
2149
|
const manifest = readJson(at);
|
|
2124
2150
|
if (!isRecord5(manifest)) return;
|
|
2125
|
-
manifest["overrides"]
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2151
|
+
const next = isRecord5(manifest["overrides"]) ? { ...manifest["overrides"] } : {};
|
|
2152
|
+
for (const [name, spec] of Object.entries(pins)) {
|
|
2153
|
+
if (spec === void 0) delete next[name];
|
|
2154
|
+
else next[name] = spec;
|
|
2155
|
+
}
|
|
2156
|
+
manifest["overrides"] = next;
|
|
2129
2157
|
writeFileSync4(at, `${JSON.stringify(manifest, void 0, 2)}
|
|
2130
2158
|
`);
|
|
2131
2159
|
};
|
|
2160
|
+
var CANDIDATE_CLI = "@geonosis/cli";
|
|
2161
|
+
var candidateDirIn = (into, packed) => {
|
|
2162
|
+
const cli = packed.find((one) => one.name === CANDIDATE_CLI);
|
|
2163
|
+
if (cli === void 0) return void 0;
|
|
2164
|
+
const dir = join7(into, "candidate");
|
|
2165
|
+
mkdirSync3(dir, { recursive: true });
|
|
2166
|
+
const pins = Object.fromEntries(packed.map((one) => [one.name, `file:${one.tarball}`]));
|
|
2167
|
+
writeFileSync4(
|
|
2168
|
+
join7(dir, "package.json"),
|
|
2169
|
+
`${JSON.stringify(
|
|
2170
|
+
{
|
|
2171
|
+
devDependencies: { [CANDIDATE_CLI]: `file:${cli.tarball}` },
|
|
2172
|
+
name: "geonosis-candidate",
|
|
2173
|
+
overrides: pins,
|
|
2174
|
+
private: true
|
|
2175
|
+
},
|
|
2176
|
+
void 0,
|
|
2177
|
+
2
|
|
2178
|
+
)}
|
|
2179
|
+
`
|
|
2180
|
+
);
|
|
2181
|
+
pinsInWorkspaceYaml(dir, pins);
|
|
2182
|
+
return {
|
|
2183
|
+
line: `candidate CLI: (cd ${dir} && pnpm install --ignore-scripts && pnpm exec geonosis update --registry ${into} --to ${cli.version} \u2026)`,
|
|
2184
|
+
path: dir
|
|
2185
|
+
};
|
|
2186
|
+
};
|
|
2132
2187
|
var overrideEveryCopy = (work, manager, packed) => {
|
|
2133
2188
|
if (packed.length === 0) return;
|
|
2134
2189
|
const pins = Object.fromEntries(packed.map((one) => [one.name, `file:${one.tarball}`]));
|
|
@@ -2160,9 +2215,62 @@ var rewriteManifests = (tree, manifests, packed) => {
|
|
|
2160
2215
|
}
|
|
2161
2216
|
return swapped;
|
|
2162
2217
|
};
|
|
2163
|
-
var
|
|
2164
|
-
|
|
2165
|
-
|
|
2218
|
+
var DEFAULT_PHASE_TIMEOUT_MS = 20 * 6e4;
|
|
2219
|
+
var KILL_GRACE_MS = 5e3;
|
|
2220
|
+
var groupsThisProcessOwns = /* @__PURE__ */ new Set();
|
|
2221
|
+
var killGroup = (pid, signal) => {
|
|
2222
|
+
try {
|
|
2223
|
+
process.kill(-pid, signal);
|
|
2224
|
+
} catch {
|
|
2225
|
+
}
|
|
2226
|
+
};
|
|
2227
|
+
var ownsItsExit = false;
|
|
2228
|
+
var ownEveryGroupOnExit = () => {
|
|
2229
|
+
if (ownsItsExit) return;
|
|
2230
|
+
ownsItsExit = true;
|
|
2231
|
+
const sweep = () => {
|
|
2232
|
+
for (const pid of groupsThisProcessOwns) killGroup(pid, "SIGKILL");
|
|
2233
|
+
};
|
|
2234
|
+
process.once("exit", sweep);
|
|
2235
|
+
process.once("SIGINT", sweep);
|
|
2236
|
+
process.once("SIGTERM", sweep);
|
|
2237
|
+
};
|
|
2238
|
+
var shellRun = (command, cwd, env, timeoutMs = DEFAULT_PHASE_TIMEOUT_MS) => {
|
|
2239
|
+
ownEveryGroupOnExit();
|
|
2240
|
+
return new Promise((settle) => {
|
|
2241
|
+
const child = spawn2(command, {
|
|
2242
|
+
cwd,
|
|
2243
|
+
detached: true,
|
|
2244
|
+
env,
|
|
2245
|
+
shell: true,
|
|
2246
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2247
|
+
});
|
|
2248
|
+
const pid = child.pid;
|
|
2249
|
+
if (pid !== void 0) groupsThisProcessOwns.add(pid);
|
|
2250
|
+
let output = "";
|
|
2251
|
+
child.stdout?.on("data", (chunk) => {
|
|
2252
|
+
output += chunk.toString();
|
|
2253
|
+
});
|
|
2254
|
+
child.stderr?.on("data", (chunk) => {
|
|
2255
|
+
output += chunk.toString();
|
|
2256
|
+
});
|
|
2257
|
+
let timedOut = false;
|
|
2258
|
+
let graceTimer;
|
|
2259
|
+
const deadline = setTimeout(() => {
|
|
2260
|
+
timedOut = true;
|
|
2261
|
+
if (pid === void 0) return;
|
|
2262
|
+
killGroup(pid, "SIGTERM");
|
|
2263
|
+
graceTimer = setTimeout(() => killGroup(pid, "SIGKILL"), KILL_GRACE_MS);
|
|
2264
|
+
}, timeoutMs);
|
|
2265
|
+
const finish = (code) => {
|
|
2266
|
+
clearTimeout(deadline);
|
|
2267
|
+
if (graceTimer !== void 0) clearTimeout(graceTimer);
|
|
2268
|
+
if (pid !== void 0) groupsThisProcessOwns.delete(pid);
|
|
2269
|
+
settle({ code: code ?? -1, output, timedOut });
|
|
2270
|
+
};
|
|
2271
|
+
child.on("error", () => finish(-1));
|
|
2272
|
+
child.on("close", (code) => finish(code));
|
|
2273
|
+
});
|
|
2166
2274
|
};
|
|
2167
2275
|
var INTERESTING = /\b(error|Error|ERROR|FAIL|failed|refused|✗|✘)\b/;
|
|
2168
2276
|
var quotable = (output) => {
|
|
@@ -2175,14 +2283,23 @@ var GIT_REFUSAL = /fatal: not a git repository|Command failed: git |git .+ faile
|
|
|
2175
2283
|
var looksLikeAGitRefusal = (output) => output.split("\n").some((line) => GIT_REFUSAL.test(line));
|
|
2176
2284
|
var hasNoCommit = (dir) => spawnSync4("git", ["-C", dir, "rev-parse", "--verify", "-q", "HEAD"], { encoding: "utf8" }).status !== 0;
|
|
2177
2285
|
var NOT_MEASURED_REASON = "this gate asks git and a snapshot has none";
|
|
2178
|
-
var runPhase = (phase, command, cwd, manager) => {
|
|
2286
|
+
var runPhase = async (phase, command, cwd, manager, timeoutMs = DEFAULT_PHASE_TIMEOUT_MS) => {
|
|
2179
2287
|
if ("why" in command) return { phase, why: command.why };
|
|
2180
2288
|
const line = commandLine(command, manager);
|
|
2181
2289
|
const env = {
|
|
2182
2290
|
...process.env,
|
|
2183
2291
|
PATH: `${join7(cwd, "node_modules/.bin")}:${process.env["PATH"] ?? ""}`
|
|
2184
2292
|
};
|
|
2185
|
-
const { code, output } = shellRun(line, cwd, env);
|
|
2293
|
+
const { code, output, timedOut } = await shellRun(line, cwd, env, timeoutMs);
|
|
2294
|
+
if (timedOut) {
|
|
2295
|
+
return {
|
|
2296
|
+
code: 124,
|
|
2297
|
+
command: line,
|
|
2298
|
+
lines: [...quotable(output), `timed out after ${timeoutMs} ms \u2014 killed`],
|
|
2299
|
+
ok: false,
|
|
2300
|
+
phase
|
|
2301
|
+
};
|
|
2302
|
+
}
|
|
2186
2303
|
if (code !== 0 && looksLikeAGitRefusal(output) && hasNoCommit(cwd)) {
|
|
2187
2304
|
return { command: line, phase, unmeasurable: NOT_MEASURED_REASON };
|
|
2188
2305
|
}
|
|
@@ -2194,53 +2311,70 @@ var commandsToRun = (record, declared) => Object.fromEntries(
|
|
|
2194
2311
|
return [phase, named === void 0 ? record.commands[phase] : { exec: named }];
|
|
2195
2312
|
})
|
|
2196
2313
|
);
|
|
2197
|
-
var smokeOver = (input) => {
|
|
2314
|
+
var smokeOver = async (input) => {
|
|
2315
|
+
const timeoutMs = readReleaseConfig(input.root).smoke?.timeoutMs ?? DEFAULT_PHASE_TIMEOUT_MS;
|
|
2198
2316
|
const record = readSnapshot(input.root, input.name);
|
|
2199
2317
|
const at = snapshotDir(input.root, input.name);
|
|
2200
|
-
const work = join7(
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
throw new CannotRun(
|
|
2213
|
-
`no manifest in "${input.name}" depends on anything ${input.rc} packs \u2014 this run would have installed the published versions and called them the release candidate`
|
|
2318
|
+
const work = mkdtempSync2(join7(tmpdir2(), "geonosis-smoke-"));
|
|
2319
|
+
try {
|
|
2320
|
+
copyInto(join7(at, "tree"), work, new Set(EXCLUDED_DIRS));
|
|
2321
|
+
ownRepository(work);
|
|
2322
|
+
const rcDir = join7(at, "rc");
|
|
2323
|
+
const packed = input.rc === void 0 ? [] : packRc(resolve9(input.rc), rcDir);
|
|
2324
|
+
const candidate = input.rc === void 0 ? void 0 : candidateDirIn(rcDir, packed)?.line;
|
|
2325
|
+
const findings = input.rc === void 0 ? [] : promisedButNotPacked(resolve9(input.rc), packed);
|
|
2326
|
+
const swapped = rewriteManifests(
|
|
2327
|
+
work,
|
|
2328
|
+
record.manifests.map((one) => one.path),
|
|
2329
|
+
packed
|
|
2214
2330
|
);
|
|
2215
|
-
|
|
2216
|
-
overrideEveryCopy(work, record.manager, packed);
|
|
2217
|
-
const frozen = input.rc === void 0;
|
|
2218
|
-
const install = input.install ?? defaultInstallLine(record.manager, frozen);
|
|
2219
|
-
const configBefore = configBytesOf(work);
|
|
2220
|
-
const installed = shellRun(install, work, process.env);
|
|
2221
|
-
if (installed.code !== 0) {
|
|
2222
|
-
if (frozen) {
|
|
2331
|
+
if (input.rc !== void 0 && swapped.length === 0) {
|
|
2223
2332
|
throw new CannotRun(
|
|
2224
|
-
|
|
2333
|
+
`no manifest in "${input.name}" depends on anything ${input.rc} packs \u2014 this run would have installed the published versions and called them the release candidate`
|
|
2334
|
+
);
|
|
2335
|
+
}
|
|
2336
|
+
overrideEveryCopy(work, record.manager, packed);
|
|
2337
|
+
const frozen = input.rc === void 0;
|
|
2338
|
+
const install = input.install ?? defaultInstallLine(record.manager, frozen);
|
|
2339
|
+
const configBefore = configBytesOf(work);
|
|
2340
|
+
const installed = await shellRun(install, work, process.env, timeoutMs);
|
|
2341
|
+
if (installed.timedOut) {
|
|
2342
|
+
throw new CannotRun(
|
|
2343
|
+
`\`${install}\` timed out after ${timeoutMs} ms in the snapshot and was killed \u2014 nothing below it was measured:
|
|
2225
2344
|
${quotable(installed.output).join("\n")}`
|
|
2226
2345
|
);
|
|
2227
2346
|
}
|
|
2228
|
-
|
|
2229
|
-
|
|
2347
|
+
if (installed.code !== 0) {
|
|
2348
|
+
if (frozen) {
|
|
2349
|
+
throw new CannotRun(
|
|
2350
|
+
`\`${install}\` exited ${installed.code} in the snapshot \u2014 the baseline is the consumer's own tree and their lockfile does not describe it \u2014 record it after they install:
|
|
2230
2351
|
${quotable(installed.output).join("\n")}`
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2352
|
+
);
|
|
2353
|
+
}
|
|
2354
|
+
throw new CannotRun(
|
|
2355
|
+
`\`${install}\` exited ${installed.code} in the snapshot \u2014 the release was never installed, so nothing below it was measured:
|
|
2356
|
+
${quotable(installed.output).join("\n")}`
|
|
2357
|
+
);
|
|
2358
|
+
}
|
|
2359
|
+
for (const file of editedByInstall(configBefore, configBytesOf(work))) {
|
|
2360
|
+
findings.push(
|
|
2361
|
+
`\`${install}\` rewrote ${file} in the copy \u2014 every phase below ran against a tree that is no longer byte-for-byte "${input.name}", and the same install in their own tree would edit theirs`
|
|
2362
|
+
);
|
|
2363
|
+
}
|
|
2364
|
+
const commands = commandsToRun(record, input.commands ?? {});
|
|
2365
|
+
const outcomes = [];
|
|
2366
|
+
for (const phase of PHASES) {
|
|
2367
|
+
outcomes.push(await runPhase(phase, commands[phase], work, record.manager, timeoutMs));
|
|
2368
|
+
}
|
|
2369
|
+
if (outcomes.every((one) => !("ok" in one) || one.ok)) {
|
|
2370
|
+
rmSync3(work, { force: true, recursive: true });
|
|
2371
|
+
}
|
|
2372
|
+
return { ...candidate === void 0 ? {} : { candidate }, findings, outcomes, packed, work };
|
|
2373
|
+
} catch (error) {
|
|
2374
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2375
|
+
throw new CannotRun(`${message}
|
|
2376
|
+
run copy: ${work}`);
|
|
2237
2377
|
}
|
|
2238
|
-
const commands = commandsToRun(record, input.commands ?? {});
|
|
2239
|
-
return {
|
|
2240
|
-
findings,
|
|
2241
|
-
outcomes: PHASES.map((phase) => runPhase(phase, commands[phase], work, record.manager)),
|
|
2242
|
-
packed
|
|
2243
|
-
};
|
|
2244
2378
|
};
|
|
2245
2379
|
var ownRepository = (work) => {
|
|
2246
2380
|
const done = spawnSync4("git", ["init", "-q"], { cwd: work });
|
|
@@ -2251,13 +2385,15 @@ var ownRepository = (work) => {
|
|
|
2251
2385
|
}
|
|
2252
2386
|
};
|
|
2253
2387
|
var baselinePath = (root, name) => join7(snapshotDir(root, name), BASELINE_FILE);
|
|
2254
|
-
var recordBaseline = (input) => {
|
|
2255
|
-
const { outcomes, packed } = smokeOver(input);
|
|
2388
|
+
var recordBaseline = async (input) => {
|
|
2389
|
+
const { candidate, outcomes, packed, work } = await smokeOver(input);
|
|
2256
2390
|
const baseline = {
|
|
2257
2391
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2392
|
+
...candidate === void 0 ? {} : { candidate },
|
|
2258
2393
|
packed: packed.map((one) => ({ name: one.name, version: one.version })),
|
|
2259
2394
|
phases: outcomes,
|
|
2260
|
-
rc: input.rc === void 0 ? "the versions the tree names" : resolve9(input.rc)
|
|
2395
|
+
rc: input.rc === void 0 ? "the versions the tree names" : resolve9(input.rc),
|
|
2396
|
+
...existsSync7(work) ? { runCopy: work } : {}
|
|
2261
2397
|
};
|
|
2262
2398
|
writeFileSync4(baselinePath(input.root, input.name), `${JSON.stringify(baseline, void 0, 2)}
|
|
2263
2399
|
`);
|
|
@@ -2315,10 +2451,14 @@ var compareToBaseline = (name, baseline, outcomes, findings) => {
|
|
|
2315
2451
|
refused
|
|
2316
2452
|
};
|
|
2317
2453
|
};
|
|
2318
|
-
var runSmoke = (input) => {
|
|
2454
|
+
var runSmoke = async (input) => {
|
|
2319
2455
|
const baseline = readBaseline(input.root, input.name);
|
|
2320
|
-
const { findings, outcomes } = smokeOver(input);
|
|
2321
|
-
return
|
|
2456
|
+
const { candidate, findings, outcomes, work } = await smokeOver(input);
|
|
2457
|
+
return {
|
|
2458
|
+
...compareToBaseline(input.name, baseline, outcomes, findings),
|
|
2459
|
+
...candidate === void 0 ? {} : { candidate },
|
|
2460
|
+
...existsSync7(work) ? { runCopy: work } : {}
|
|
2461
|
+
};
|
|
2322
2462
|
};
|
|
2323
2463
|
var CONSUMER_TREES_FILE = ".geonosis/consumer-trees.local.json";
|
|
2324
2464
|
var localTrees = (root) => {
|
|
@@ -2356,28 +2496,31 @@ var dating = (root, name) => {
|
|
|
2356
2496
|
if (!("sha" in now) || now.sha === commit.sha) return void 0;
|
|
2357
2497
|
return { stale: `STALE ${name}: recorded at ${commit.sha}, the tree is at ${now.sha}` };
|
|
2358
2498
|
};
|
|
2359
|
-
var sweepSmoke = (input) => {
|
|
2499
|
+
var sweepSmoke = async (input) => {
|
|
2360
2500
|
const absent = input.absent ?? [];
|
|
2361
2501
|
if (input.skipMissing) {
|
|
2362
2502
|
const owed = unrecordedButHere(input.root, input.wanted, absent);
|
|
2363
2503
|
if (owed.length > 0) throw new CannotRun(owed.join("\n"));
|
|
2364
2504
|
}
|
|
2365
|
-
const entries =
|
|
2505
|
+
const entries = [];
|
|
2506
|
+
for (const one of input.wanted) {
|
|
2366
2507
|
if (absent.includes(one.name)) {
|
|
2367
|
-
|
|
2508
|
+
entries.push({
|
|
2368
2509
|
name: one.name,
|
|
2369
2510
|
why: "--absent named it, so this release was not read against it"
|
|
2370
|
-
};
|
|
2511
|
+
});
|
|
2512
|
+
continue;
|
|
2371
2513
|
}
|
|
2372
2514
|
if (input.skipMissing && !recordedAlready(input.root, one.name)) {
|
|
2373
|
-
|
|
2515
|
+
entries.push({
|
|
2374
2516
|
name: one.name,
|
|
2375
2517
|
why: `no snapshot of it here \u2014 record one with: geonosis-release smoke snapshot ${one.name} --from <their tree>`
|
|
2376
|
-
};
|
|
2518
|
+
});
|
|
2519
|
+
continue;
|
|
2377
2520
|
}
|
|
2378
2521
|
const note = dating(input.root, one.name);
|
|
2379
|
-
|
|
2380
|
-
comparison: runSmoke({
|
|
2522
|
+
entries.push({
|
|
2523
|
+
comparison: await runSmoke({
|
|
2381
2524
|
...one.commands === void 0 ? {} : { commands: one.commands },
|
|
2382
2525
|
...one.install === void 0 ? {} : { install: one.install },
|
|
2383
2526
|
name: one.name,
|
|
@@ -2386,8 +2529,8 @@ var sweepSmoke = (input) => {
|
|
|
2386
2529
|
}),
|
|
2387
2530
|
name: one.name,
|
|
2388
2531
|
...note === void 0 ? {} : { note }
|
|
2389
|
-
};
|
|
2390
|
-
}
|
|
2532
|
+
});
|
|
2533
|
+
}
|
|
2391
2534
|
return { entries };
|
|
2392
2535
|
};
|
|
2393
2536
|
var comparisonsIn = (sweep) => sweep.entries.flatMap((one) => "comparison" in one ? [one.comparison] : []);
|
|
@@ -2468,6 +2611,10 @@ var headline = (comparison) => {
|
|
|
2468
2611
|
};
|
|
2469
2612
|
var formatSmoke = (comparison) => [
|
|
2470
2613
|
headline(comparison),
|
|
2614
|
+
...comparison.candidate === void 0 ? [] : [` ${comparison.candidate}
|
|
2615
|
+
`],
|
|
2616
|
+
...comparison.runCopy === void 0 ? [] : [` run copy: ${comparison.runCopy}
|
|
2617
|
+
`],
|
|
2471
2618
|
...comparison.outcomes.map(outcomeLine),
|
|
2472
2619
|
...comparison.broken.flatMap((one) => [
|
|
2473
2620
|
` ${one.phase} passed at the versions they are on and fails under this release:
|
|
@@ -2491,6 +2638,10 @@ var formatSmoke = (comparison) => [
|
|
|
2491
2638
|
var formatBaseline = (name, baseline) => [
|
|
2492
2639
|
`OK baseline ${name}: recorded against ${baseline.rc}
|
|
2493
2640
|
`,
|
|
2641
|
+
...baseline.candidate === void 0 ? [] : [` ${baseline.candidate}
|
|
2642
|
+
`],
|
|
2643
|
+
...baseline.runCopy === void 0 ? [] : [` run copy: ${baseline.runCopy}
|
|
2644
|
+
`],
|
|
2494
2645
|
...baseline.phases.map(outcomeLine)
|
|
2495
2646
|
].join("");
|
|
2496
2647
|
var SMOKE_TOOL = "release-smoke";
|
|
@@ -2519,8 +2670,8 @@ var smokeEnvelope = (comparison, durationMs) => {
|
|
|
2519
2670
|
};
|
|
2520
2671
|
|
|
2521
2672
|
// src/changelog.ts
|
|
2522
|
-
import { existsSync as existsSync8,
|
|
2523
|
-
import {
|
|
2673
|
+
import { existsSync as existsSync8, readFileSync as readFileSync11 } from "fs";
|
|
2674
|
+
import { dirname as dirname3, join as join8, resolve as resolve10 } from "path";
|
|
2524
2675
|
var CHANGELOG = "CHANGELOG.md";
|
|
2525
2676
|
var VERSION_HEADING = /^##\s+v?(\d+\.\d+\.\d+.*)$/;
|
|
2526
2677
|
var emptyHeadingsIn = (body, at) => {
|
|
@@ -2569,35 +2720,18 @@ var formatChangelogs = (report) => [
|
|
|
2569
2720
|
),
|
|
2570
2721
|
report.empty.length === 0 ? `changelog PASS \u2014 ${report.versions} version heading(s) over ${report.read} file(s), every one of them says something` : `changelog FAIL \u2014 ${report.empty.length} of ${report.versions} version heading(s) over ${report.read} file(s) are empty`
|
|
2571
2722
|
].join("\n");
|
|
2572
|
-
var NEVER_WALKED3 = /* @__PURE__ */ new Set(["build", "coverage", "dist", "node_modules", "storybook-static"]);
|
|
2573
2723
|
var publishableDirs = (root) => {
|
|
2574
2724
|
const found = [];
|
|
2575
|
-
const
|
|
2576
|
-
let
|
|
2725
|
+
for (const path of manifestsUnder(root)) {
|
|
2726
|
+
let manifest;
|
|
2577
2727
|
try {
|
|
2578
|
-
|
|
2728
|
+
manifest = JSON.parse(readFileSync11(path, "utf8"));
|
|
2579
2729
|
} catch {
|
|
2580
|
-
|
|
2581
|
-
}
|
|
2582
|
-
for (const entry of entries) {
|
|
2583
|
-
if (entry.isDirectory()) {
|
|
2584
|
-
if (!entry.name.startsWith(".") && !NEVER_WALKED3.has(entry.name)) {
|
|
2585
|
-
walk(join8(dir, entry.name));
|
|
2586
|
-
}
|
|
2587
|
-
continue;
|
|
2588
|
-
}
|
|
2589
|
-
if (entry.name !== "package.json") continue;
|
|
2590
|
-
let manifest;
|
|
2591
|
-
try {
|
|
2592
|
-
manifest = JSON.parse(readFileSync11(join8(dir, entry.name), "utf8"));
|
|
2593
|
-
} catch {
|
|
2594
|
-
continue;
|
|
2595
|
-
}
|
|
2596
|
-
if (manifest.private === true || typeof manifest.name !== "string") continue;
|
|
2597
|
-
found.push(relative5(root, dir).split(sep4).join("/"));
|
|
2730
|
+
continue;
|
|
2598
2731
|
}
|
|
2599
|
-
|
|
2600
|
-
|
|
2732
|
+
if (manifest.private === true || typeof manifest.name !== "string") continue;
|
|
2733
|
+
found.push(pathOf(root, dirname3(path)));
|
|
2734
|
+
}
|
|
2601
2735
|
return found.filter((one) => one !== "").toSorted();
|
|
2602
2736
|
};
|
|
2603
2737
|
|
package/dist/index.d.ts
CHANGED
|
@@ -197,6 +197,8 @@ type SmokeSnapshotConfig = {
|
|
|
197
197
|
type SmokeConfig = {
|
|
198
198
|
exclude?: string[];
|
|
199
199
|
snapshots?: SmokeSnapshotConfig[];
|
|
200
|
+
/** How long an install or a phase may run before the runner kills its whole process group (#368). */
|
|
201
|
+
timeoutMs?: number;
|
|
200
202
|
};
|
|
201
203
|
/**
|
|
202
204
|
* Law 6: every directory, dialect, secret name and worker is an OPTION. The defaults are empty, and
|
|
@@ -540,18 +542,24 @@ type Packed = {
|
|
|
540
542
|
};
|
|
541
543
|
type SmokeBaseline = {
|
|
542
544
|
at: string;
|
|
545
|
+
/** The candidate's first step (#389) — absent when this run packed no `@geonosis/cli`. */
|
|
546
|
+
candidate?: string;
|
|
543
547
|
packed: {
|
|
544
548
|
name: string;
|
|
545
549
|
version: string;
|
|
546
550
|
}[];
|
|
547
551
|
phases: PhaseOutcome[];
|
|
548
552
|
rc: string;
|
|
553
|
+
/** Where a phase that failed left its copy — absent when every phase that ran passed. */
|
|
554
|
+
runCopy?: string;
|
|
549
555
|
};
|
|
550
556
|
type SmokeComparison = {
|
|
551
557
|
broken: {
|
|
552
558
|
lines: string[];
|
|
553
559
|
phase: SmokePhase;
|
|
554
560
|
}[];
|
|
561
|
+
/** The candidate's first step (#389) — absent when this run packed no `@geonosis/cli`. */
|
|
562
|
+
candidate?: string;
|
|
555
563
|
findings: string[];
|
|
556
564
|
known: SmokePhase[];
|
|
557
565
|
mended: SmokePhase[];
|
|
@@ -562,6 +570,8 @@ type SmokeComparison = {
|
|
|
562
570
|
phase: SmokePhase;
|
|
563
571
|
why: string;
|
|
564
572
|
}[];
|
|
573
|
+
/** Where a phase that failed left its copy — absent when every phase that ran passed. */
|
|
574
|
+
runCopy?: string;
|
|
565
575
|
};
|
|
566
576
|
declare const BASELINE_FILE = "baseline.json";
|
|
567
577
|
/**
|
|
@@ -591,10 +601,10 @@ type RunInput = {
|
|
|
591
601
|
rc?: string;
|
|
592
602
|
root: string;
|
|
593
603
|
};
|
|
594
|
-
declare const recordBaseline: (input: RunInput) => SmokeBaseline
|
|
604
|
+
declare const recordBaseline: (input: RunInput) => Promise<SmokeBaseline>;
|
|
595
605
|
declare const readBaseline: (root: string, name: string) => SmokeBaseline;
|
|
596
606
|
declare const compareToBaseline: (name: string, baseline: SmokeBaseline, outcomes: PhaseOutcome[], findings: string[]) => SmokeComparison;
|
|
597
|
-
declare const runSmoke: (input: RunInput) => SmokeComparison
|
|
607
|
+
declare const runSmoke: (input: RunInput) => Promise<SmokeComparison>;
|
|
598
608
|
/**
|
|
599
609
|
* What the sweep can say about WHEN a recording was taken: that its source has moved on, or that
|
|
600
610
|
* the question cannot be asked of it at all.
|
|
@@ -649,7 +659,7 @@ declare const sweepSmoke: (input: {
|
|
|
649
659
|
*/
|
|
650
660
|
skipMissing: boolean;
|
|
651
661
|
wanted: WantedSnapshot[];
|
|
652
|
-
}) => SmokeSweep
|
|
662
|
+
}) => Promise<SmokeSweep>;
|
|
653
663
|
declare const comparisonsIn: (sweep: SmokeSweep) => SmokeComparison[];
|
|
654
664
|
declare const formatSweep: (sweep: SmokeSweep) => string;
|
|
655
665
|
declare const sweepEnvelope: (sweep: SmokeSweep, durationMs: number) => ReportEnvelope;
|
package/dist/index.js
CHANGED
package/dist/release-cli.js
CHANGED
|
@@ -40,11 +40,111 @@ import {
|
|
|
40
40
|
sweepEnvelope,
|
|
41
41
|
sweepSmoke,
|
|
42
42
|
writeEnvelope
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-RQOZEZSB.js";
|
|
44
44
|
|
|
45
45
|
// src/release-cli.ts
|
|
46
46
|
import { existsSync } from "fs";
|
|
47
|
-
import { join, resolve } from "path";
|
|
47
|
+
import { join as join2, resolve } from "path";
|
|
48
|
+
|
|
49
|
+
// src/heavy-slot.ts
|
|
50
|
+
import { randomUUID } from "crypto";
|
|
51
|
+
import { linkSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
52
|
+
import { tmpdir } from "os";
|
|
53
|
+
import { dirname, join } from "path";
|
|
54
|
+
var HEAVY_SLOTS = 2;
|
|
55
|
+
var DEFAULT_WAIT_MS = 20 * 6e4;
|
|
56
|
+
var heavySlotDir = () => {
|
|
57
|
+
const named = process.env.GEONOSIS_HEAVY_SLOT_DIR;
|
|
58
|
+
if (named !== void 0 && named !== "") return named;
|
|
59
|
+
return join(tmpdir(), "geonosis-heavy");
|
|
60
|
+
};
|
|
61
|
+
var slotPath = (dir, at) => join(dir, `slot-${at}.json`);
|
|
62
|
+
var alive = (pid) => {
|
|
63
|
+
try {
|
|
64
|
+
process.kill(pid, 0);
|
|
65
|
+
return true;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
return error.code === "EPERM";
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var holderOf = (path) => {
|
|
71
|
+
try {
|
|
72
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
73
|
+
return typeof parsed.pid === "number" ? {
|
|
74
|
+
command: parsed.command ?? "unknown",
|
|
75
|
+
cwd: parsed.cwd ?? "somewhere",
|
|
76
|
+
pid: parsed.pid,
|
|
77
|
+
startedAt: parsed.startedAt ?? "unknown"
|
|
78
|
+
} : void 0;
|
|
79
|
+
} catch {
|
|
80
|
+
return void 0;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
var reapIfDead = (path) => {
|
|
84
|
+
const holder = holderOf(path);
|
|
85
|
+
if (holder !== void 0 && !alive(holder.pid)) rmSync(path, { force: true });
|
|
86
|
+
};
|
|
87
|
+
var claim = (path, holder) => {
|
|
88
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
89
|
+
const staging = `${path}.${holder.pid}.${randomUUID()}`;
|
|
90
|
+
try {
|
|
91
|
+
writeFileSync(staging, JSON.stringify(holder));
|
|
92
|
+
linkSync(staging, path);
|
|
93
|
+
return true;
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (error.code === "EEXIST") return false;
|
|
96
|
+
throw error;
|
|
97
|
+
} finally {
|
|
98
|
+
rmSync(staging, { force: true });
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
var sleep = (ms) => new Promise((done) => setTimeout(done, ms));
|
|
102
|
+
var namedHolders = (dir) => Array.from({ length: HEAVY_SLOTS }, (_unused, at) => holderOf(slotPath(dir, at))).filter(
|
|
103
|
+
(holder) => holder !== void 0
|
|
104
|
+
);
|
|
105
|
+
var describeHolders = (holders) => holders.map((one) => `${one.pid} ${one.command} since ${one.startedAt}`).join(", ");
|
|
106
|
+
var HeavySlotTimeout = class extends Error {
|
|
107
|
+
};
|
|
108
|
+
var takeSlot = async ({
|
|
109
|
+
command,
|
|
110
|
+
cwd = process.cwd(),
|
|
111
|
+
dir = heavySlotDir(),
|
|
112
|
+
pollMs = 250,
|
|
113
|
+
say = (line) => process.stderr.write(`${line}
|
|
114
|
+
`),
|
|
115
|
+
waitMs = Number(process.env.GEONOSIS_HEAVY_WAIT_MS ?? "") || DEFAULT_WAIT_MS
|
|
116
|
+
}) => {
|
|
117
|
+
const until = Date.now() + waitMs;
|
|
118
|
+
let told = false;
|
|
119
|
+
for (; ; ) {
|
|
120
|
+
for (let at = 0; at < HEAVY_SLOTS; at += 1) {
|
|
121
|
+
const path = slotPath(dir, at);
|
|
122
|
+
reapIfDead(path);
|
|
123
|
+
const mine = {
|
|
124
|
+
command,
|
|
125
|
+
cwd,
|
|
126
|
+
pid: process.pid,
|
|
127
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
128
|
+
};
|
|
129
|
+
if (claim(path, mine)) {
|
|
130
|
+
return () => {
|
|
131
|
+
if (holderOf(path)?.pid === process.pid) rmSync(path, { force: true });
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const holders = namedHolders(dir);
|
|
136
|
+
if (!told) {
|
|
137
|
+
told = true;
|
|
138
|
+
say(`waiting for a heavy slot: ${holders.length} held \u2014 ${describeHolders(holders)}`);
|
|
139
|
+
}
|
|
140
|
+
if (Date.now() >= until) {
|
|
141
|
+
throw new HeavySlotTimeout(
|
|
142
|
+
`waited ${waitMs} ms for a heavy slot: ${holders.length} held \u2014 ${describeHolders(holders)}`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
await sleep(pollMs);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
48
148
|
|
|
49
149
|
// src/since.ts
|
|
50
150
|
var MEANS = {
|
|
@@ -416,32 +516,37 @@ var wantedSnapshots = (parsed, root) => {
|
|
|
416
516
|
var rcOf = (parsed, root, cwd) => {
|
|
417
517
|
const named = parsed.read["--rc-dir"];
|
|
418
518
|
if (named !== void 0) return resolve(cwd, named);
|
|
419
|
-
if (existsSync(
|
|
519
|
+
if (existsSync(join2(root, "package.json"))) return root;
|
|
420
520
|
throw new CannotRun(
|
|
421
521
|
"nothing here is a release candidate \u2014 pass --rc-dir <the workspace whose packages this release publishes>, because a smoke with nothing packed compares a tree to itself"
|
|
422
522
|
);
|
|
423
523
|
};
|
|
424
|
-
var baseline = (parsed, root, cwd) => {
|
|
524
|
+
var baseline = async (parsed, root, cwd) => {
|
|
425
525
|
const wanted = wantedSnapshots(parsed, root);
|
|
426
526
|
const one = wanted[0];
|
|
427
527
|
if (parsed.read["--snapshot"] === void 0 || one === void 0) {
|
|
428
528
|
throw new CannotRun("smoke baseline needs --snapshot <name>");
|
|
429
529
|
}
|
|
430
|
-
const
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
530
|
+
const releaseSlot = await takeSlot({ command: "geonosis-release smoke baseline", cwd: root });
|
|
531
|
+
try {
|
|
532
|
+
const rc = parsed.read["--rc-dir"];
|
|
533
|
+
const recorded = await recordBaseline({
|
|
534
|
+
...one.commands === void 0 ? {} : { commands: one.commands },
|
|
535
|
+
...one.install === void 0 ? {} : { install: one.install },
|
|
536
|
+
name: one.name,
|
|
537
|
+
...rc === void 0 ? {} : { rc: resolve(cwd, rc) },
|
|
538
|
+
root
|
|
539
|
+
});
|
|
540
|
+
process.stdout.write(
|
|
541
|
+
parsed.json ? `${JSON.stringify(recorded, void 0, 2)}
|
|
440
542
|
` : formatBaseline(one.name, recorded)
|
|
441
|
-
|
|
442
|
-
|
|
543
|
+
);
|
|
544
|
+
return 0;
|
|
545
|
+
} finally {
|
|
546
|
+
releaseSlot();
|
|
547
|
+
}
|
|
443
548
|
};
|
|
444
|
-
var smokeRun = (parsed, root, cwd) => {
|
|
549
|
+
var smokeRun = async (parsed, root, cwd) => {
|
|
445
550
|
const startedAt = Date.now();
|
|
446
551
|
const wanted = wantedSnapshots(parsed, root);
|
|
447
552
|
if (wanted.length === 0) {
|
|
@@ -449,13 +554,19 @@ var smokeRun = (parsed, root, cwd) => {
|
|
|
449
554
|
"nothing here names a consumer to read this release against \u2014 geonosis.json \u2192 release.smoke.snapshots names them, or pass --snapshot <name>. A release check over zero consumers is not a green release check."
|
|
450
555
|
);
|
|
451
556
|
}
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
557
|
+
const releaseSlot = await takeSlot({ command: "geonosis-release smoke run", cwd: root });
|
|
558
|
+
let swept;
|
|
559
|
+
try {
|
|
560
|
+
swept = await sweepSmoke({
|
|
561
|
+
absent: parsed.many["--absent"] ?? [],
|
|
562
|
+
rc: rcOf(parsed, root, cwd),
|
|
563
|
+
root,
|
|
564
|
+
skipMissing: parsed.read["--snapshot"] === void 0,
|
|
565
|
+
wanted
|
|
566
|
+
});
|
|
567
|
+
} finally {
|
|
568
|
+
releaseSlot();
|
|
569
|
+
}
|
|
459
570
|
process.stdout.write(
|
|
460
571
|
parsed.json ? `${JSON.stringify(swept, void 0, 2)}
|
|
461
572
|
` : formatSweep(swept)
|
|
@@ -481,7 +592,7 @@ var smokeRun = (parsed, root, cwd) => {
|
|
|
481
592
|
}
|
|
482
593
|
return comparisons.every((one) => one.ok) ? 0 : 1;
|
|
483
594
|
};
|
|
484
|
-
var smoke = (parsed, cwd) => {
|
|
595
|
+
var smoke = async (parsed, cwd) => {
|
|
485
596
|
const root = rootOf(parsed, cwd);
|
|
486
597
|
const verb = parsed.rest[0];
|
|
487
598
|
if (verb === "snapshot") return snapshot(parsed, root);
|
|
@@ -507,6 +618,8 @@ geonosis.json \u2192 "release" every key optional; a repo that configured n
|
|
|
507
618
|
that tree is installed with when the manager's default is not it,
|
|
508
619
|
and { doctor?, lint?, typecheck? } when their gates are not the
|
|
509
620
|
scripts of those names
|
|
621
|
+
timeoutMs number? how long an install or a phase may run before the runner kills its
|
|
622
|
+
whole process group (default 1200000 \u2014 20 minutes)
|
|
510
623
|
secrets string[]? the secret names a deployment carries
|
|
511
624
|
steps string[]? the release steps
|
|
512
625
|
workers string[]? the workers it deploys
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geonosis/release",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.1",
|
|
4
4
|
"types": "./dist/index.d.ts",
|
|
5
5
|
"description": "The release contract with its proofs: an expand-only migration gate over three dialects, a smoke that must name the version that answered it, and declared ≠ deployed.",
|
|
6
6
|
"keywords": [
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"squawk-cli": "2.63.0",
|
|
49
|
-
"@geonosis/ratchet": "2.5.
|
|
49
|
+
"@geonosis/ratchet": "2.5.1"
|
|
50
50
|
},
|
|
51
51
|
"engines": {
|
|
52
52
|
"node": ">=22"
|