@geonosis/release 2.0.0 → 2.1.0
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/{chunk-KQIWTXQP.js → chunk-MZWAN6WT.js} +1221 -919
- package/dist/index.d.ts +87 -10
- package/dist/index.js +17 -1
- package/dist/release-cli.js +68 -9
- package/package.json +2 -2
|
@@ -95,12 +95,41 @@ var parseSchema = (value2) => {
|
|
|
95
95
|
};
|
|
96
96
|
};
|
|
97
97
|
var SMOKE_KEYS = "expected { exclude?, snapshots? }";
|
|
98
|
+
var SNAPSHOT_KEYS = "expected { commands?, install?, name }";
|
|
99
|
+
var SMOKE_PHASES = ["doctor", "lint", "typecheck"];
|
|
100
|
+
var parseCommands = (value2, at) => {
|
|
101
|
+
if (value2 === void 0) return {};
|
|
102
|
+
if (!isRecord(value2)) {
|
|
103
|
+
throw new CannotRun(
|
|
104
|
+
`${at}.commands must be an object \u2014 expected { doctor?, lint?, typecheck? }`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
const unknown = Object.keys(value2).find(
|
|
108
|
+
(key) => !SMOKE_PHASES.includes(key)
|
|
109
|
+
);
|
|
110
|
+
if (unknown !== void 0) {
|
|
111
|
+
throw new CannotRun(
|
|
112
|
+
`${at}.commands.${unknown} is not a phase the smoke runs \u2014 it runs ${SMOKE_PHASES.join(", ")}`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
const commands = {};
|
|
116
|
+
for (const phase of SMOKE_PHASES) {
|
|
117
|
+
const command = value2[phase];
|
|
118
|
+
if (command === void 0) continue;
|
|
119
|
+
if (typeof command !== "string" || command === "") {
|
|
120
|
+
throw new CannotRun(`${at}.commands.${phase} must be the command that runs their ${phase}`);
|
|
121
|
+
}
|
|
122
|
+
commands[phase] = command;
|
|
123
|
+
}
|
|
124
|
+
return commands;
|
|
125
|
+
};
|
|
98
126
|
var oneSnapshot = (value2, index) => {
|
|
99
127
|
const at = `release.smoke.snapshots[${index}]`;
|
|
100
|
-
if (!isRecord(value2)) throw new CannotRun(`${at} must be an object \u2014
|
|
101
|
-
const
|
|
128
|
+
if (!isRecord(value2)) throw new CannotRun(`${at} must be an object \u2014 ${SNAPSHOT_KEYS}`);
|
|
129
|
+
const known = /* @__PURE__ */ new Set(["commands", "install", "name"]);
|
|
130
|
+
const unknown = Object.keys(value2).find((key) => !known.has(key));
|
|
102
131
|
if (unknown !== void 0) {
|
|
103
|
-
throw new CannotRun(`${at}.${unknown} is not a key it takes \u2014
|
|
132
|
+
throw new CannotRun(`${at}.${unknown} is not a key it takes \u2014 ${SNAPSHOT_KEYS}`);
|
|
104
133
|
}
|
|
105
134
|
const name = value2["name"];
|
|
106
135
|
if (typeof name !== "string" || name === "") {
|
|
@@ -110,7 +139,11 @@ var oneSnapshot = (value2, index) => {
|
|
|
110
139
|
if (install !== void 0 && typeof install !== "string") {
|
|
111
140
|
throw new CannotRun(`${at}.install must be the command that installs that tree`);
|
|
112
141
|
}
|
|
113
|
-
return {
|
|
142
|
+
return {
|
|
143
|
+
commands: parseCommands(value2["commands"], at),
|
|
144
|
+
...install === void 0 ? {} : { install },
|
|
145
|
+
name
|
|
146
|
+
};
|
|
114
147
|
};
|
|
115
148
|
var parseSmoke = (value2) => {
|
|
116
149
|
if (value2 === void 0) return { exclude: [], snapshots: [] };
|
|
@@ -121,7 +154,7 @@ var parseSmoke = (value2) => {
|
|
|
121
154
|
}
|
|
122
155
|
const snapshots = value2["snapshots"];
|
|
123
156
|
if (snapshots !== void 0 && !Array.isArray(snapshots)) {
|
|
124
|
-
throw new CannotRun(
|
|
157
|
+
throw new CannotRun(`release.smoke.snapshots must be a list \u2014 ${SNAPSHOT_KEYS}, per entry`);
|
|
125
158
|
}
|
|
126
159
|
return {
|
|
127
160
|
exclude: strings(value2["exclude"], "release.smoke.exclude"),
|
|
@@ -175,238 +208,905 @@ var readReleaseConfig = (root) => {
|
|
|
175
208
|
}
|
|
176
209
|
};
|
|
177
210
|
|
|
178
|
-
// src/
|
|
179
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
180
|
-
import {
|
|
181
|
-
|
|
182
|
-
var
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
index += 1;
|
|
189
|
-
for (; index < source.length; index += 1) {
|
|
190
|
-
if (source[index] === "\\") {
|
|
191
|
-
index += 1;
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
|
-
if (source[index] === '"') break;
|
|
195
|
-
}
|
|
196
|
-
out += source.slice(start, index + 1);
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
if (char === "/" && source[index + 1] === "/") {
|
|
200
|
-
const end = source.indexOf("\n", index);
|
|
201
|
-
index = end === -1 ? source.length : end - 1;
|
|
202
|
-
continue;
|
|
203
|
-
}
|
|
204
|
-
if (char === "/" && source[index + 1] === "*") {
|
|
205
|
-
const end = source.indexOf("*/", index + 2);
|
|
206
|
-
index = end === -1 ? source.length : end + 1;
|
|
207
|
-
continue;
|
|
208
|
-
}
|
|
209
|
-
out += char;
|
|
210
|
-
}
|
|
211
|
-
return JSON.parse(out.replaceAll(/,(\s*[\]}])/g, "$1"));
|
|
212
|
-
};
|
|
213
|
-
var literal = (text) => {
|
|
214
|
-
const value2 = text.trim();
|
|
215
|
-
if (value2.startsWith('"') || value2.startsWith("'")) return value2.slice(1, -1);
|
|
216
|
-
if (value2 === "true") return true;
|
|
217
|
-
if (value2 === "false") return false;
|
|
218
|
-
const number = Number(value2);
|
|
219
|
-
return Number.isNaN(number) ? value2 : number;
|
|
220
|
-
};
|
|
221
|
-
var split = (text, delimiter) => {
|
|
222
|
-
const parts = [];
|
|
223
|
-
let depth = 0;
|
|
224
|
-
let quote = "";
|
|
225
|
-
let current = "";
|
|
226
|
-
for (const char of text) {
|
|
227
|
-
if (quote !== "") {
|
|
228
|
-
current += char;
|
|
229
|
-
if (char === quote) quote = "";
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
if (char === '"' || char === "'") quote = char;
|
|
233
|
-
if (char === "[" || char === "{") depth += 1;
|
|
234
|
-
if (char === "]" || char === "}") depth -= 1;
|
|
235
|
-
if (char === delimiter && depth === 0) {
|
|
236
|
-
parts.push(current);
|
|
237
|
-
current = "";
|
|
238
|
-
continue;
|
|
239
|
-
}
|
|
240
|
-
current += char;
|
|
211
|
+
// src/envelope.ts
|
|
212
|
+
import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
213
|
+
import { dirname, join } from "path";
|
|
214
|
+
import { fileURLToPath } from "url";
|
|
215
|
+
var ENVELOPES_DIR = ".geonosis/envelopes";
|
|
216
|
+
var envelopePath = (root, tool) => join(root, ENVELOPES_DIR, `${tool}.json`);
|
|
217
|
+
var UnbalancedEnvelope = class extends Error {
|
|
218
|
+
constructor(message) {
|
|
219
|
+
super(message);
|
|
220
|
+
this.name = "UnbalancedEnvelope";
|
|
241
221
|
}
|
|
242
|
-
parts.push(current);
|
|
243
|
-
return parts.filter((one) => one.trim() !== "");
|
|
244
222
|
};
|
|
245
|
-
var
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
223
|
+
var isCount = (value2) => Number.isSafeInteger(value2) && value2 >= 0;
|
|
224
|
+
var unbalancedMessage = (envelope, next) => `${envelope.tool}: considered ${envelope.considered} but accounts for ${envelope.read + envelope.refused.length + envelope.excused.length} \u2014 ${envelope.read} read + ${envelope.refused.length} refused + ${envelope.excused.length} excused. A run that has lost count of its own inputs cannot say what it measured, so no verdict was rendered and no envelope was written. Next: ${next}`;
|
|
225
|
+
var writeEnvelope = ({
|
|
226
|
+
envelope,
|
|
227
|
+
next,
|
|
228
|
+
root
|
|
229
|
+
}) => {
|
|
230
|
+
if (envelope.tool.trim() === "") {
|
|
231
|
+
throw new UnbalancedEnvelope(
|
|
232
|
+
`an envelope with no tool name cannot be filed or reported against. Next: ${next}`
|
|
254
233
|
);
|
|
255
234
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
for (const key of path.slice(0, -1)) {
|
|
261
|
-
if (!isRecord2(here[key])) here[key] = {};
|
|
262
|
-
here = here[key];
|
|
263
|
-
}
|
|
264
|
-
here[path.at(-1) ?? ""] = leaf;
|
|
265
|
-
};
|
|
266
|
-
var table = (into, path) => {
|
|
267
|
-
let here = into;
|
|
268
|
-
for (const key of path) {
|
|
269
|
-
if (!isRecord2(here[key])) here[key] = {};
|
|
270
|
-
here = here[key];
|
|
235
|
+
if (envelope.version.trim() === "") {
|
|
236
|
+
throw new UnbalancedEnvelope(
|
|
237
|
+
`${envelope.tool}: an envelope that cannot name the build that wrote it dates nothing, and a stale one reads exactly like a fresh one. Next: ${next}`
|
|
238
|
+
);
|
|
271
239
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
for (const key of path.slice(0, -1)) {
|
|
277
|
-
if (!isRecord2(here[key])) here[key] = {};
|
|
278
|
-
here = here[key];
|
|
240
|
+
if (!isCount(envelope.considered) || !isCount(envelope.read)) {
|
|
241
|
+
throw new UnbalancedEnvelope(
|
|
242
|
+
`${envelope.tool}: considered ${envelope.considered} and read ${envelope.read} \u2014 a census is a whole number of things, and arithmetic over anything else balances by accident. Next: ${next}`
|
|
243
|
+
);
|
|
279
244
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const list = here[last];
|
|
283
|
-
const entry = {};
|
|
284
|
-
list.push(entry);
|
|
285
|
-
return entry;
|
|
286
|
-
};
|
|
287
|
-
var parseToml = (source) => {
|
|
288
|
-
const out = {};
|
|
289
|
-
let here = out;
|
|
290
|
-
const lines2 = source.split("\n");
|
|
291
|
-
for (let index = 0; index < lines2.length; index += 1) {
|
|
292
|
-
const line = (lines2[index] ?? "").split("#")[0]?.trim() ?? "";
|
|
293
|
-
if (line === "") continue;
|
|
294
|
-
if (line.startsWith("[[") && line.endsWith("]]")) {
|
|
295
|
-
here = arrayTable(out, line.slice(2, -2).trim().split("."));
|
|
296
|
-
continue;
|
|
297
|
-
}
|
|
298
|
-
if (line.startsWith("[") && line.endsWith("]")) {
|
|
299
|
-
here = table(out, line.slice(1, -1).trim().split("."));
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
const at = line.indexOf("=");
|
|
303
|
-
if (at === -1) continue;
|
|
304
|
-
let text = line.slice(at + 1);
|
|
305
|
-
while ([...text].filter((one) => one === "[").length > [...text].filter((one) => one === "]").length) {
|
|
306
|
-
index += 1;
|
|
307
|
-
if (index >= lines2.length) throw new CannotRun("an unterminated array in the TOML config");
|
|
308
|
-
text += `
|
|
309
|
-
${(lines2[index] ?? "").split("#")[0] ?? ""}`;
|
|
310
|
-
}
|
|
311
|
-
put(here, line.slice(0, at).trim().split("."), value(text));
|
|
245
|
+
if (envelope.considered !== envelope.read + envelope.refused.length + envelope.excused.length) {
|
|
246
|
+
throw new UnbalancedEnvelope(unbalancedMessage(envelope, next));
|
|
312
247
|
}
|
|
313
|
-
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
"browser",
|
|
319
|
-
"d1_databases",
|
|
320
|
-
"dispatch_namespaces",
|
|
321
|
-
"durable_objects",
|
|
322
|
-
"hyperdrive",
|
|
323
|
-
"kv_namespaces",
|
|
324
|
-
"mtls_certificates",
|
|
325
|
-
"queues",
|
|
326
|
-
"r2_buckets",
|
|
327
|
-
"send_email",
|
|
328
|
-
"services",
|
|
329
|
-
"vectorize",
|
|
330
|
-
"version_metadata",
|
|
331
|
-
"workflows"
|
|
332
|
-
];
|
|
333
|
-
var bindingsOf = (found) => {
|
|
334
|
-
if (Array.isArray(found)) return found.flatMap(bindingsOf);
|
|
335
|
-
if (!isRecord2(found)) return [];
|
|
336
|
-
const named = found["binding"] ?? found["name"];
|
|
337
|
-
const here = typeof named === "string" ? [named] : [];
|
|
338
|
-
const nested = Object.entries(found).filter(([key]) => key === "bindings" || key === "producers" || key === "consumers").flatMap(([, one]) => bindingsOf(one));
|
|
339
|
-
return [...here, ...nested];
|
|
340
|
-
};
|
|
341
|
-
var declaredIn = (config) => {
|
|
342
|
-
const triggers = config["triggers"];
|
|
343
|
-
const crons = isRecord2(triggers) && Array.isArray(triggers["crons"]) ? triggers["crons"] : [];
|
|
344
|
-
const routes = config["routes"];
|
|
345
|
-
const one = config["route"];
|
|
346
|
-
const listed = [
|
|
347
|
-
...Array.isArray(routes) ? routes : [],
|
|
348
|
-
...typeof one === "string" ? [one] : []
|
|
349
|
-
];
|
|
350
|
-
return {
|
|
351
|
-
bindings: BINDING_LISTS.flatMap((key) => bindingsOf(config[key])).toSorted(),
|
|
352
|
-
crons: crons.filter((cron) => typeof cron === "string").toSorted(),
|
|
353
|
-
routes: listed.map((route) => isRecord2(route) ? route["pattern"] : route).filter((pattern) => typeof pattern === "string").toSorted()
|
|
354
|
-
};
|
|
248
|
+
const at = envelopePath(root, envelope.tool);
|
|
249
|
+
mkdirSync(dirname(at), { recursive: true });
|
|
250
|
+
writeFileSync(at, `${JSON.stringify(envelope, void 0, 2)}
|
|
251
|
+
`);
|
|
252
|
+
return at;
|
|
355
253
|
};
|
|
356
|
-
var
|
|
357
|
-
|
|
358
|
-
let parsed;
|
|
254
|
+
var UNKNOWN = "unknown";
|
|
255
|
+
var versionIn = (dir) => {
|
|
359
256
|
try {
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
} catch
|
|
363
|
-
|
|
257
|
+
const manifest = JSON.parse(readFileSync2(join(dir, "package.json"), "utf8"));
|
|
258
|
+
return typeof manifest.version === "string" ? manifest.version : void 0;
|
|
259
|
+
} catch {
|
|
260
|
+
return void 0;
|
|
364
261
|
}
|
|
365
|
-
if (!isRecord2(parsed)) throw new CannotRun(`${relative5} is not a wrangler configuration`);
|
|
366
|
-
const environments = parsed["env"];
|
|
367
|
-
const block = env !== void 0 && isRecord2(environments) && isRecord2(environments[env]) ? environments[env] : parsed;
|
|
368
|
-
return declaredIn(block);
|
|
369
262
|
};
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
var readDeployed = (root) => {
|
|
379
|
-
const path = resolve3(root, DEPLOYED_FILE);
|
|
380
|
-
if (!existsSync2(path)) throw new CannotRun(NOTHING_SAYS);
|
|
381
|
-
let parsed;
|
|
382
|
-
try {
|
|
383
|
-
parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
384
|
-
} catch (error) {
|
|
385
|
-
throw new CannotRun(`${DEPLOYED_FILE} is not readable JSON: ${error.message}`);
|
|
263
|
+
var versionOf = (moduleUrl) => {
|
|
264
|
+
let dir = dirname(fileURLToPath(moduleUrl));
|
|
265
|
+
for (; ; ) {
|
|
266
|
+
const found = versionIn(dir);
|
|
267
|
+
if (found !== void 0) return found;
|
|
268
|
+
const up = dirname(dir);
|
|
269
|
+
if (up === dir) return UNKNOWN;
|
|
270
|
+
dir = up;
|
|
386
271
|
}
|
|
387
|
-
if (!isRecord3(parsed)) throw new CannotRun(`${DEPLOYED_FILE} is not an object`);
|
|
388
|
-
const triggers = isRecord3(parsed["triggers"]) ? parsed["triggers"] : {};
|
|
389
|
-
return {
|
|
390
|
-
...typeof parsed["at"] === "string" ? { at: parsed["at"] } : {},
|
|
391
|
-
deployed: {
|
|
392
|
-
bindings: listOf(parsed["bindings"]),
|
|
393
|
-
crons: listOf(triggers["crons"]),
|
|
394
|
-
routes: listOf(triggers["routes"]),
|
|
395
|
-
secrets: listOf(parsed["secrets"])
|
|
396
|
-
}
|
|
397
|
-
};
|
|
398
|
-
};
|
|
399
|
-
var missingBetween = (kind, declared, deployed) => {
|
|
400
|
-
const missing = declared.filter((one) => !deployed.includes(one));
|
|
401
|
-
const extra = deployed.filter((one) => !declared.includes(one));
|
|
402
|
-
return missing.length === 0 && extra.length === 0 ? [] : [{ extra, kind, missing }];
|
|
403
272
|
};
|
|
404
|
-
var
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
273
|
+
var MIGRATIONS_TOOL = "release-migrations";
|
|
274
|
+
var MIGRATIONS_NEXT = "geonosis-release migrations --since <ref> --json and compare `files` against `unreadable` \u2014 every added file leaves by exactly one door, and a file in one list and not the other is the accounting bug this refuses over";
|
|
275
|
+
var migrationsEnvelope = (report, durationMs) => ({
|
|
276
|
+
considered: report.files.length,
|
|
277
|
+
durationMs,
|
|
278
|
+
excused: report.files.filter((one) => one.state === "excused").map((one) => ({
|
|
279
|
+
path: one.path,
|
|
280
|
+
reason: "a contract-migration marker this gate could believe"
|
|
281
|
+
})),
|
|
282
|
+
findings: [...report.refusals, ...report.markers, ...report.squawk],
|
|
283
|
+
read: report.files.filter((one) => one.state !== "excused" && one.state !== "unreadable").length,
|
|
284
|
+
refused: report.unreadable.map((one) => ({ path: one.path, reason: one.why })),
|
|
285
|
+
tool: MIGRATIONS_TOOL,
|
|
286
|
+
version: versionOf(import.meta.url)
|
|
287
|
+
});
|
|
288
|
+
var SCHEMA_TOOL = "release-schema";
|
|
289
|
+
var SCHEMA_NEXT = "geonosis-release schema --json and compare `files` against `unreadable` \u2014 the denominator is every migration under every declared domain directory, and a file in neither list is one nothing read (#168)";
|
|
290
|
+
var schemaEnvelope = (report, durationMs) => ({
|
|
291
|
+
considered: report.files.length,
|
|
292
|
+
durationMs,
|
|
293
|
+
excused: [],
|
|
294
|
+
findings: report.findings,
|
|
295
|
+
read: report.read,
|
|
296
|
+
refused: report.unreadable.map((one) => ({ path: one.path, reason: one.why })),
|
|
297
|
+
tool: SCHEMA_TOOL,
|
|
298
|
+
version: versionOf(import.meta.url)
|
|
299
|
+
});
|
|
300
|
+
var PUBLISHED_TOOL = "release-published";
|
|
301
|
+
var PUBLISHED_NEXT = "geonosis-release published --group --json and compare `considered` against `lines` + `excused` \u2014 every manifest in the tree leaves by exactly one door, and a group announced off a sample is the failure this counts against (#147)";
|
|
302
|
+
var publishedEnvelope = (report, durationMs) => ({
|
|
303
|
+
considered: report.considered,
|
|
304
|
+
durationMs,
|
|
305
|
+
excused: report.excused,
|
|
306
|
+
findings: report.lines.filter((one) => one.verdict !== "MATCH"),
|
|
307
|
+
read: report.lines.filter((one) => one.verdict !== "UNREACHABLE").length,
|
|
308
|
+
refused: report.lines.filter((one) => one.verdict === "UNREACHABLE").map((one) => ({ path: one.at, reason: one.why })),
|
|
309
|
+
tool: PUBLISHED_TOOL,
|
|
310
|
+
version: versionOf(import.meta.url)
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// src/registry.ts
|
|
314
|
+
var DEFAULT_REGISTRY = "https://registry.npmjs.org";
|
|
315
|
+
var TIMEOUT_MS = 15e3;
|
|
316
|
+
var registryPathOf = (name) => name.startsWith("@") ? `@${encodeURIComponent(name.slice(1))}` : encodeURIComponent(name);
|
|
317
|
+
var versionsIn = (body) => {
|
|
318
|
+
const versions = body.versions;
|
|
319
|
+
return typeof versions === "object" && versions !== null ? Object.keys(versions) : [];
|
|
320
|
+
};
|
|
321
|
+
var latestIn = (body) => {
|
|
322
|
+
const tags = body["dist-tags"];
|
|
323
|
+
const latest = typeof tags === "object" && tags !== null ? tags.latest : void 0;
|
|
324
|
+
return typeof latest === "string" ? latest : void 0;
|
|
325
|
+
};
|
|
326
|
+
var askRegistry = async ({
|
|
327
|
+
name,
|
|
328
|
+
registry = DEFAULT_REGISTRY,
|
|
329
|
+
timeoutMs = TIMEOUT_MS
|
|
330
|
+
}) => {
|
|
331
|
+
const url = `${registry.replace(/\/+$/, "")}/${registryPathOf(name)}`;
|
|
332
|
+
let response;
|
|
333
|
+
try {
|
|
334
|
+
response = await fetch(url, {
|
|
335
|
+
headers: { accept: "application/json" },
|
|
336
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
337
|
+
});
|
|
338
|
+
} catch (error) {
|
|
339
|
+
return { kind: "unreachable", name, why: `GET ${url} \u2014 ${error.message}` };
|
|
340
|
+
}
|
|
341
|
+
if (response.status === 404) return { kind: "absent", name };
|
|
342
|
+
if (!response.ok) {
|
|
343
|
+
return { kind: "unreachable", name, why: `GET ${url} \u2014 HTTP ${response.status}` };
|
|
344
|
+
}
|
|
345
|
+
let body;
|
|
346
|
+
try {
|
|
347
|
+
body = await response.json();
|
|
348
|
+
} catch (error) {
|
|
349
|
+
return {
|
|
350
|
+
kind: "unreachable",
|
|
351
|
+
name,
|
|
352
|
+
why: `GET ${url} answered ${response.status} with something that is not JSON \u2014 ${error.message}`
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
return { kind: "present", latest: latestIn(body), name, versions: versionsIn(body) };
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
// src/published.ts
|
|
359
|
+
import { readdirSync, readFileSync as readFileSync3 } from "fs";
|
|
360
|
+
import { join as join2, relative, sep } from "path";
|
|
361
|
+
var NEVER_WALKED = /* @__PURE__ */ new Set(["build", "coverage", "dist", "node_modules", "storybook-static"]);
|
|
362
|
+
var pathOf = (root, path) => relative(root, path).split(sep).join("/");
|
|
363
|
+
var manifestsUnder = (root) => {
|
|
364
|
+
const found = [];
|
|
365
|
+
const walk = (dir) => {
|
|
366
|
+
let entries;
|
|
367
|
+
try {
|
|
368
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
369
|
+
} catch {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
for (const entry of entries) {
|
|
373
|
+
if (entry.isDirectory()) {
|
|
374
|
+
if (!entry.name.startsWith(".") && !NEVER_WALKED.has(entry.name))
|
|
375
|
+
walk(join2(dir, entry.name));
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
if (entry.name === "package.json") found.push(join2(dir, entry.name));
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
walk(root);
|
|
382
|
+
return found;
|
|
383
|
+
};
|
|
384
|
+
var censusOf = (root) => {
|
|
385
|
+
const excused = [];
|
|
386
|
+
const locals = [];
|
|
387
|
+
for (const path of manifestsUnder(root)) {
|
|
388
|
+
const at = pathOf(root, path);
|
|
389
|
+
let manifest;
|
|
390
|
+
try {
|
|
391
|
+
manifest = JSON.parse(readFileSync3(path, "utf8"));
|
|
392
|
+
} catch (error) {
|
|
393
|
+
excused.push({ path: at, reason: `it does not parse: ${error.message}` });
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
if (manifest.private === true) {
|
|
397
|
+
excused.push({ path: at, reason: "private: true \u2014 nothing publishes it" });
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
if (typeof manifest.name !== "string" || manifest.name === "") {
|
|
401
|
+
excused.push({ path: at, reason: "it names no package" });
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
if (typeof manifest.version !== "string" || manifest.version === "") {
|
|
405
|
+
excused.push({ path: at, reason: "it declares no version" });
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
locals.push({ at, name: manifest.name, version: manifest.version });
|
|
409
|
+
}
|
|
410
|
+
return { excused, locals: locals.toSorted((a, b) => a.name.localeCompare(b.name)) };
|
|
411
|
+
};
|
|
412
|
+
var EXISTS_BUT = "exists but no version matching";
|
|
413
|
+
var lineFor = async (local, registry) => {
|
|
414
|
+
const answer = await askRegistry({ name: local.name, registry });
|
|
415
|
+
const base = { at: local.at, local: local.version, name: local.name };
|
|
416
|
+
if (answer.kind === "unreachable") {
|
|
417
|
+
return { ...base, latest: void 0, verdict: "UNREACHABLE", why: answer.why };
|
|
418
|
+
}
|
|
419
|
+
if (answer.kind === "absent") {
|
|
420
|
+
return {
|
|
421
|
+
...base,
|
|
422
|
+
latest: void 0,
|
|
423
|
+
verdict: "ABSENT",
|
|
424
|
+
why: "npm has never heard of this name \u2014 a name published moments ago 404s for a few minutes; --wait <seconds> polls through that window"
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
if (answer.latest === local.version) {
|
|
428
|
+
return { ...base, latest: answer.latest, verdict: "MATCH", why: "on npm" };
|
|
429
|
+
}
|
|
430
|
+
return {
|
|
431
|
+
...base,
|
|
432
|
+
latest: answer.latest,
|
|
433
|
+
verdict: "BEHIND",
|
|
434
|
+
why: answer.versions.includes(local.version) ? `npm has ${local.version}, and its latest tag is ${answer.latest ?? "nothing"} \u2014 the group has not landed on one version` : `the package ${EXISTS_BUT} ${local.version}; npm answers ${answer.latest ?? "nothing"}. Minutes after a publish, suspect the package manager's manifest cache before the registry`
|
|
435
|
+
};
|
|
436
|
+
};
|
|
437
|
+
var sleep = (ms) => new Promise((done) => {
|
|
438
|
+
setTimeout(done, ms);
|
|
439
|
+
});
|
|
440
|
+
var POLL_MS = 5e3;
|
|
441
|
+
var runPublished = async ({
|
|
442
|
+
pollMs = POLL_MS,
|
|
443
|
+
registry = DEFAULT_REGISTRY,
|
|
444
|
+
root,
|
|
445
|
+
waitSeconds = 0
|
|
446
|
+
}) => {
|
|
447
|
+
const { excused, locals } = censusOf(root);
|
|
448
|
+
const startedAt = Date.now();
|
|
449
|
+
const budgetMs = Math.max(0, waitSeconds * 1e3);
|
|
450
|
+
let lines2 = [];
|
|
451
|
+
let sweeps = 0;
|
|
452
|
+
for (; ; ) {
|
|
453
|
+
lines2 = [];
|
|
454
|
+
for (const local of locals) lines2.push(await lineFor(local, registry));
|
|
455
|
+
sweeps += 1;
|
|
456
|
+
const settled = lines2.every((one) => one.verdict === "MATCH");
|
|
457
|
+
const stuck = lines2.some((one) => one.verdict === "UNREACHABLE");
|
|
458
|
+
const left = budgetMs - (Date.now() - startedAt);
|
|
459
|
+
if (settled || stuck || left <= 0) break;
|
|
460
|
+
await sleep(Math.min(pollMs, left));
|
|
461
|
+
}
|
|
462
|
+
const unreachable = lines2.filter((one) => one.verdict === "UNREACHABLE").length;
|
|
463
|
+
return {
|
|
464
|
+
considered: lines2.length + excused.length,
|
|
465
|
+
excused: excused.toSorted((a, b) => a.path.localeCompare(b.path)),
|
|
466
|
+
lines: lines2,
|
|
467
|
+
ok: lines2.length > 0 && lines2.every((one) => one.verdict === "MATCH"),
|
|
468
|
+
registry,
|
|
469
|
+
sweeps,
|
|
470
|
+
unreachable,
|
|
471
|
+
waitedMs: Date.now() - startedAt
|
|
472
|
+
};
|
|
473
|
+
};
|
|
474
|
+
var NOTHING_TO_CHECK = "published FAIL \u2014 no workspace in this tree declares a package a publish could be about, and a check that considered nothing has not passed";
|
|
475
|
+
var formatPublished = (report) => {
|
|
476
|
+
const width = Math.max(1, ...report.lines.map((one) => one.name.length));
|
|
477
|
+
const body = report.lines.map((one) => ` ${one.verdict.padEnd(11)} ${one.name.padEnd(width)} ${one.local} ${one.why}`).join("\n");
|
|
478
|
+
const head = report.lines.length === 0 ? NOTHING_TO_CHECK : `published ${report.ok ? "PASS" : "FAIL"} \u2014 ${report.lines.filter((one) => one.verdict === "MATCH").length}/${report.lines.length} of the group on ${report.registry}`;
|
|
479
|
+
const tail = [
|
|
480
|
+
`considered ${report.considered} manifests: ${report.lines.length} read, ${report.excused.length} excused`,
|
|
481
|
+
report.sweeps > 1 ? `swept ${report.sweeps} times over ${report.waitedMs} ms` : ""
|
|
482
|
+
].filter((one) => one !== "");
|
|
483
|
+
return `${[head, body, ...tail].filter((one) => one !== "").join("\n")}
|
|
484
|
+
`;
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
// src/smoke.ts
|
|
488
|
+
import { spawnSync } from "child_process";
|
|
489
|
+
import {
|
|
490
|
+
cpSync,
|
|
491
|
+
existsSync as existsSync2,
|
|
492
|
+
mkdirSync as mkdirSync2,
|
|
493
|
+
readdirSync as readdirSync2,
|
|
494
|
+
readFileSync as readFileSync4,
|
|
495
|
+
rmSync,
|
|
496
|
+
statSync,
|
|
497
|
+
writeFileSync as writeFileSync2
|
|
498
|
+
} from "fs";
|
|
499
|
+
import { join as join3, relative as relative2, resolve as resolve2 } from "path";
|
|
500
|
+
var SNAPSHOTS_DIR = ".geonosis/consumer-snapshots";
|
|
501
|
+
var LOCKFILES = [
|
|
502
|
+
{ file: "bun.lock", manager: "bun" },
|
|
503
|
+
{ file: "bun.lockb", manager: "bun" },
|
|
504
|
+
{ file: "pnpm-lock.yaml", manager: "pnpm" }
|
|
505
|
+
];
|
|
506
|
+
var UNMEASURED = [
|
|
507
|
+
{ file: "package-lock.json", manager: "npm" },
|
|
508
|
+
{ file: "yarn.lock", manager: "yarn" }
|
|
509
|
+
];
|
|
510
|
+
var EXCLUDED_DIRS = [
|
|
511
|
+
".cache",
|
|
512
|
+
".geonosis",
|
|
513
|
+
".git",
|
|
514
|
+
".next",
|
|
515
|
+
".turbo",
|
|
516
|
+
".wrangler",
|
|
517
|
+
"coverage",
|
|
518
|
+
"dist",
|
|
519
|
+
"node_modules",
|
|
520
|
+
"storybook-static"
|
|
521
|
+
];
|
|
522
|
+
var PHASES = ["typecheck", "lint", "doctor"];
|
|
523
|
+
var headOf = (from) => {
|
|
524
|
+
const done = spawnSync("git", ["-C", from, "rev-parse", "HEAD"], { encoding: "utf8" });
|
|
525
|
+
if (done.error !== void 0) {
|
|
526
|
+
return { why: `git could not be run here: ${done.error.message}` };
|
|
527
|
+
}
|
|
528
|
+
if (done.status !== 0) {
|
|
529
|
+
return { why: `git says nothing about ${from}: ${done.stderr.trim()}` };
|
|
530
|
+
}
|
|
531
|
+
return { sha: done.stdout.trim() };
|
|
532
|
+
};
|
|
533
|
+
var DOCTOR_PACKAGE = "@geonosis/doctor";
|
|
534
|
+
var DOCTOR_BIN = "geonosis-doctor";
|
|
535
|
+
var DOCTOR_DOORS = ["@geonosis/cli", "geonosis"];
|
|
536
|
+
var DOOR_BIN = "geonosis doctor";
|
|
537
|
+
var isRecord2 = (value2) => typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
|
|
538
|
+
var readManifest = (path) => {
|
|
539
|
+
try {
|
|
540
|
+
const parsed = JSON.parse(readFileSync4(path, "utf8"));
|
|
541
|
+
return isRecord2(parsed) ? parsed : {};
|
|
542
|
+
} catch (error) {
|
|
543
|
+
throw new CannotRun(`${path} is not readable JSON: ${error.message}`);
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
var versionsIn2 = (manifest) => {
|
|
547
|
+
const found = {};
|
|
548
|
+
for (const key of ["dependencies", "devDependencies"]) {
|
|
549
|
+
const block = manifest[key];
|
|
550
|
+
if (!isRecord2(block)) continue;
|
|
551
|
+
for (const [name, range] of Object.entries(block)) {
|
|
552
|
+
if (typeof range === "string") found[name] = range;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return Object.fromEntries(Object.entries(found).toSorted(([a], [b]) => a.localeCompare(b)));
|
|
556
|
+
};
|
|
557
|
+
var scriptsIn = (manifest) => {
|
|
558
|
+
const scripts = manifest["scripts"];
|
|
559
|
+
if (!isRecord2(scripts)) return {};
|
|
560
|
+
return Object.fromEntries(
|
|
561
|
+
Object.entries(scripts).filter((one) => typeof one[1] === "string")
|
|
562
|
+
);
|
|
563
|
+
};
|
|
564
|
+
var copyInto = (from, to, excluded) => {
|
|
565
|
+
let bytes = 0;
|
|
566
|
+
let files = 0;
|
|
567
|
+
const walk = (dir, into) => {
|
|
568
|
+
mkdirSync2(into, { recursive: true });
|
|
569
|
+
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
570
|
+
if (excluded.has(entry.name)) continue;
|
|
571
|
+
const at = join3(dir, entry.name);
|
|
572
|
+
if (entry.isDirectory()) {
|
|
573
|
+
walk(at, join3(into, entry.name));
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
if (!entry.isFile()) continue;
|
|
577
|
+
cpSync(at, join3(into, entry.name));
|
|
578
|
+
bytes += statSync(at).size;
|
|
579
|
+
files += 1;
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
walk(from, to);
|
|
583
|
+
return { bytes, files };
|
|
584
|
+
};
|
|
585
|
+
var manifestsUnder2 = (root) => {
|
|
586
|
+
const found = [];
|
|
587
|
+
const walk = (dir) => {
|
|
588
|
+
for (const entry of readdirSync2(dir, { withFileTypes: true }).toSorted(
|
|
589
|
+
(a, b) => a.name.localeCompare(b.name)
|
|
590
|
+
)) {
|
|
591
|
+
const at = join3(dir, entry.name);
|
|
592
|
+
if (entry.isDirectory()) {
|
|
593
|
+
walk(at);
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
if (entry.name !== "package.json") continue;
|
|
597
|
+
found.push({ path: relative2(root, at), versions: versionsIn2(readManifest(at)) });
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
walk(root);
|
|
601
|
+
return found.toSorted((a, b) => a.path.localeCompare(b.path));
|
|
602
|
+
};
|
|
603
|
+
var managerOf = (from) => {
|
|
604
|
+
const found = LOCKFILES.find((one) => existsSync2(join3(from, one.file)));
|
|
605
|
+
if (found !== void 0) return { lockfile: found.file, manager: found.manager };
|
|
606
|
+
const unmeasured = UNMEASURED.find((one) => existsSync2(join3(from, one.file)));
|
|
607
|
+
if (unmeasured !== void 0) {
|
|
608
|
+
throw new CannotRun(
|
|
609
|
+
`${from} is a ${unmeasured.manager} tree (${unmeasured.file}), and the pack \u2192 rewrite \u2192 install path has never been measured under ${unmeasured.manager} here. It reads ${LOCKFILES.map((one) => one.file).join(", ")}.`
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
throw new CannotRun(
|
|
613
|
+
`nothing in ${from} says which runtime installs it \u2014 looked for ${[...LOCKFILES, ...UNMEASURED].map((one) => one.file).join(", ")}. The smoke installs the way the consumer installs, so the manager is read, never assumed.`
|
|
614
|
+
);
|
|
615
|
+
};
|
|
616
|
+
var commandsFor = (manifest, manifests, named) => {
|
|
617
|
+
const scripts = scriptsIn(manifest);
|
|
618
|
+
const installs = (name) => manifests.some((one) => one.versions[name] !== void 0);
|
|
619
|
+
const doctorCommand = installs(DOCTOR_PACKAGE) ? { exec: DOCTOR_BIN } : DOCTOR_DOORS.some(installs) ? { exec: DOOR_BIN } : {
|
|
620
|
+
why: `no manifest in this tree installs ${DOCTOR_PACKAGE} or a door that brings it (${DOCTOR_DOORS.join(", ")}), so nothing here can run ${DOCTOR_BIN}`
|
|
621
|
+
};
|
|
622
|
+
const phase = (name, fallback) => {
|
|
623
|
+
const override = named[name];
|
|
624
|
+
if (override !== void 0) return { exec: override };
|
|
625
|
+
if (scripts[name] !== void 0) return { run: name };
|
|
626
|
+
return fallback;
|
|
627
|
+
};
|
|
628
|
+
return {
|
|
629
|
+
doctor: phase("doctor", doctorCommand),
|
|
630
|
+
lint: phase("lint", { why: 'the tree has no "lint" script and no --lint command was named' }),
|
|
631
|
+
typecheck: phase("typecheck", {
|
|
632
|
+
why: 'the tree has no "typecheck" script and no --typecheck command was named'
|
|
633
|
+
})
|
|
634
|
+
};
|
|
635
|
+
};
|
|
636
|
+
var snapshotDir = (root, name) => join3(root, SNAPSHOTS_DIR, name);
|
|
637
|
+
var readSnapshot = (root, name) => {
|
|
638
|
+
const at = join3(snapshotDir(root, name), "snapshot.json");
|
|
639
|
+
if (!existsSync2(at)) {
|
|
640
|
+
throw new CannotRun(
|
|
641
|
+
`there is no snapshot called "${name}" here \u2014 ${at} does not exist. Record one with: geonosis-release smoke snapshot ${name} --from <their tree>`
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
return JSON.parse(readFileSync4(at, "utf8"));
|
|
645
|
+
};
|
|
646
|
+
var wouldBeCommitted = (root, path) => {
|
|
647
|
+
const inside = spawnSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd: root });
|
|
648
|
+
if (inside.error !== void 0 || inside.status !== 0) return false;
|
|
649
|
+
return spawnSync("git", ["check-ignore", "-q", path], { cwd: root }).status !== 0;
|
|
650
|
+
};
|
|
651
|
+
var NAME_SHAPE = /^[a-z0-9][\w.-]*$/i;
|
|
652
|
+
var runSnapshot = (input) => {
|
|
653
|
+
if (!NAME_SHAPE.test(input.name)) {
|
|
654
|
+
throw new CannotRun(
|
|
655
|
+
`"${input.name}" is not a snapshot name \u2014 a name is letters, digits, dots, dashes and underscores, so that ${SNAPSHOTS_DIR}/<name> is the only place the bytes can land`
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
const from = resolve2(input.from);
|
|
659
|
+
if (!existsSync2(join3(from, "package.json"))) {
|
|
660
|
+
throw new CannotRun(`${from} has no package.json \u2014 that is not a tree a consumer installs`);
|
|
661
|
+
}
|
|
662
|
+
const at = snapshotDir(input.root, input.name);
|
|
663
|
+
if (wouldBeCommitted(input.root, join3(SNAPSHOTS_DIR, input.name))) {
|
|
664
|
+
throw new CannotRun(
|
|
665
|
+
`git here does not ignore ${SNAPSHOTS_DIR}/ \u2014 a consumer's tree is private (D-048) and a copy that reaches a commit cannot be taken back. Add ".geonosis/" to .gitignore and run this again.`
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
if (existsSync2(at) && !input.replace) {
|
|
669
|
+
throw new CannotRun(
|
|
670
|
+
`${at} is already a snapshot, and a baseline recorded against other bytes is worse than none \u2014 pass --replace to overwrite it`
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
const { lockfile, manager } = managerOf(from);
|
|
674
|
+
const excluded = [.../* @__PURE__ */ new Set([...EXCLUDED_DIRS, ...input.exclude ?? []])].toSorted();
|
|
675
|
+
rmSync(at, { force: true, recursive: true });
|
|
676
|
+
const tree = join3(at, "tree");
|
|
677
|
+
const { bytes, files } = copyInto(from, tree, new Set(excluded));
|
|
678
|
+
const manifests = manifestsUnder2(tree);
|
|
679
|
+
const record = {
|
|
680
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
681
|
+
bytes,
|
|
682
|
+
commit: headOf(from),
|
|
683
|
+
commands: commandsFor(readManifest(join3(tree, "package.json")), manifests, input.named),
|
|
684
|
+
excluded,
|
|
685
|
+
files,
|
|
686
|
+
from,
|
|
687
|
+
lockfile,
|
|
688
|
+
manager,
|
|
689
|
+
manifests,
|
|
690
|
+
name: input.name
|
|
691
|
+
};
|
|
692
|
+
writeFileSync2(join3(at, "snapshot.json"), `${JSON.stringify(record, void 0, 2)}
|
|
693
|
+
`);
|
|
694
|
+
return record;
|
|
695
|
+
};
|
|
696
|
+
var describeCommand = (command) => {
|
|
697
|
+
if ("run" in command) return `run ${command.run}`;
|
|
698
|
+
if ("exec" in command) return command.exec;
|
|
699
|
+
return `nothing \u2014 ${command.why}`;
|
|
700
|
+
};
|
|
701
|
+
var describeCommit = (commit) => {
|
|
702
|
+
if (commit === void 0) return "nothing recorded its commit";
|
|
703
|
+
if ("sha" in commit) return commit.sha;
|
|
704
|
+
return commit.why;
|
|
705
|
+
};
|
|
706
|
+
var formatSnapshot = (record) => [
|
|
707
|
+
`OK snapshot ${record.name}: ${record.files} files, ${record.bytes} bytes, ${record.manifests.length} manifests
|
|
708
|
+
`,
|
|
709
|
+
` from ${record.from} \u2014 ${record.manager} (${record.lockfile})
|
|
710
|
+
`,
|
|
711
|
+
` at ${describeCommit(record.commit)}
|
|
712
|
+
`,
|
|
713
|
+
...PHASES.map((phase) => ` ${phase}: ${describeCommand(record.commands[phase])}
|
|
714
|
+
`)
|
|
715
|
+
].join("");
|
|
716
|
+
|
|
717
|
+
// src/adoption.ts
|
|
718
|
+
import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
|
|
719
|
+
import { join as join4 } from "path";
|
|
720
|
+
var groupsOf = (root) => {
|
|
721
|
+
const at = join4(root, ".changeset/config.json");
|
|
722
|
+
if (!existsSync3(at)) return [];
|
|
723
|
+
let parsed;
|
|
724
|
+
try {
|
|
725
|
+
parsed = JSON.parse(readFileSync5(at, "utf8"));
|
|
726
|
+
} catch (error) {
|
|
727
|
+
throw new CannotRun(`${at} is not readable JSON: ${error.message}`);
|
|
728
|
+
}
|
|
729
|
+
const fixed = parsed.fixed;
|
|
730
|
+
return Array.isArray(fixed) ? fixed.filter((one) => Array.isArray(one) && one.every(isString)) : [];
|
|
731
|
+
};
|
|
732
|
+
var isString = (value2) => typeof value2 === "string";
|
|
733
|
+
var RANGE_PREFIX = /^[\^~>=<v\s]+/;
|
|
734
|
+
var RELEASE_NUMBER = /^\d+\.\d+\.\d+(?:[-+][\w.-]+)?$/;
|
|
735
|
+
var numberIn = (spec) => {
|
|
736
|
+
const bare = spec.replace(RANGE_PREFIX, "").trim();
|
|
737
|
+
return RELEASE_NUMBER.test(bare) ? bare : void 0;
|
|
738
|
+
};
|
|
739
|
+
var declarationsIn = (record, kit) => record.manifests.flatMap(
|
|
740
|
+
(manifest) => Object.entries(manifest.versions).filter(([name]) => kit.has(name)).map(([name, spec]) => ({ name, path: manifest.path, spec }))
|
|
741
|
+
);
|
|
742
|
+
var groupName = (index, total) => total > 1 ? `fixed group ${index + 1}` : "the fixed group";
|
|
743
|
+
var incoherence = (consumer, groups, declarations) => groups.flatMap((group, index) => {
|
|
744
|
+
const members = new Set(group);
|
|
745
|
+
const versions = [
|
|
746
|
+
...new Set(
|
|
747
|
+
declarations.filter((one) => members.has(one.name)).flatMap((one) => {
|
|
748
|
+
const number = numberIn(one.spec);
|
|
749
|
+
return number === void 0 ? [] : [number];
|
|
750
|
+
})
|
|
751
|
+
)
|
|
752
|
+
].toSorted();
|
|
753
|
+
if (versions.length < 2) return [];
|
|
754
|
+
const name = groupName(index, groups.length);
|
|
755
|
+
return [
|
|
756
|
+
{
|
|
757
|
+
consumer,
|
|
758
|
+
detail: `${name} at ${versions.join(", ")} \u2014 they are published as one number, so this tree is a bump that landed halfway`,
|
|
759
|
+
name,
|
|
760
|
+
verdict: "INCOHERENT"
|
|
761
|
+
}
|
|
762
|
+
];
|
|
763
|
+
});
|
|
764
|
+
var distance = (consumer, declarations, current) => declarations.flatMap((one) => {
|
|
765
|
+
const now = current.get(one.name);
|
|
766
|
+
if (now === void 0) return [];
|
|
767
|
+
const declared = numberIn(one.spec);
|
|
768
|
+
if (declared === void 0) {
|
|
769
|
+
return [
|
|
770
|
+
{
|
|
771
|
+
consumer,
|
|
772
|
+
detail: `${one.path} names it "${one.spec}", which is not a release number this can compare`,
|
|
773
|
+
name: one.name,
|
|
774
|
+
verdict: "UNJUDGED"
|
|
775
|
+
}
|
|
776
|
+
];
|
|
777
|
+
}
|
|
778
|
+
if (declared === now) return [];
|
|
779
|
+
return [{ consumer, detail: `${declared} \u2192 ${now}`, name: one.name, verdict: "BEHIND" }];
|
|
780
|
+
});
|
|
781
|
+
var undeclaredFloors = (consumer, floors, declarations) => {
|
|
782
|
+
const declared = new Set(declarations.map((one) => one.name));
|
|
783
|
+
return floors.filter((name) => !declared.has(name)).map((name) => ({
|
|
784
|
+
consumer,
|
|
785
|
+
detail: "nothing in this tree declares it \u2014 informational, a floor is opted into",
|
|
786
|
+
name,
|
|
787
|
+
verdict: "FLOOR UNDECLARED"
|
|
788
|
+
}));
|
|
789
|
+
};
|
|
790
|
+
var currentVersions = async (root, registry) => {
|
|
791
|
+
const locals = censusOf(root).locals;
|
|
792
|
+
const current = new Map(locals.map((one) => [one.name, one.version]));
|
|
793
|
+
if (registry === void 0) return current;
|
|
794
|
+
for (const one of locals) {
|
|
795
|
+
const answer = await askRegistry({ name: one.name, registry });
|
|
796
|
+
if (answer.kind === "present" && answer.latest !== void 0)
|
|
797
|
+
current.set(one.name, answer.latest);
|
|
798
|
+
}
|
|
799
|
+
return current;
|
|
800
|
+
};
|
|
801
|
+
var runAdoption = async ({
|
|
802
|
+
declared,
|
|
803
|
+
registry,
|
|
804
|
+
root
|
|
805
|
+
}) => {
|
|
806
|
+
if (declared.length === 0) {
|
|
807
|
+
throw new CannotRun(
|
|
808
|
+
"nothing here names a consumer to measure \u2014 geonosis.json \u2192 release.smoke.snapshots names them. An adoption sweep over zero consumers is not a scoreboard."
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
const current = await currentVersions(root, registry);
|
|
812
|
+
const groups = groupsOf(root);
|
|
813
|
+
const grouped = new Set(groups.flat());
|
|
814
|
+
const floors = [...current.keys()].filter((name) => !grouped.has(name)).toSorted();
|
|
815
|
+
const consumers = [];
|
|
816
|
+
const excused = [];
|
|
817
|
+
for (const one of declared) {
|
|
818
|
+
if (!existsSync3(join4(snapshotDir(root, one.name), "snapshot.json"))) {
|
|
819
|
+
excused.push({
|
|
820
|
+
path: one.name,
|
|
821
|
+
reason: `no recording of it here \u2014 record one with: geonosis-release smoke snapshot ${one.name} --from <their tree>`
|
|
822
|
+
});
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
const declarations = declarationsIn(readSnapshot(root, one.name), new Set(current.keys()));
|
|
826
|
+
consumers.push({
|
|
827
|
+
declared: declarations.length,
|
|
828
|
+
findings: [
|
|
829
|
+
...distance(one.name, declarations, current),
|
|
830
|
+
...incoherence(one.name, groups, declarations),
|
|
831
|
+
...undeclaredFloors(one.name, floors, declarations)
|
|
832
|
+
],
|
|
833
|
+
name: one.name
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
return {
|
|
837
|
+
considered: declared.length,
|
|
838
|
+
consumers,
|
|
839
|
+
excused,
|
|
840
|
+
registry,
|
|
841
|
+
versions: Object.fromEntries([...current.entries()].toSorted(([a], [b]) => a.localeCompare(b)))
|
|
842
|
+
};
|
|
843
|
+
};
|
|
844
|
+
var NOT_A_FAULT = /* @__PURE__ */ new Set(["FLOOR UNDECLARED"]);
|
|
845
|
+
var lineOf = (finding) => {
|
|
846
|
+
if (finding.verdict === "INCOHERENT") return ` INCOHERENT ${finding.consumer}: ${finding.detail}`;
|
|
847
|
+
if (finding.verdict === "FLOOR UNDECLARED") {
|
|
848
|
+
return ` FLOOR UNDECLARED ${finding.consumer} ${finding.name}`;
|
|
849
|
+
}
|
|
850
|
+
return ` ${finding.verdict} ${finding.consumer} ${finding.name} ${finding.detail}`;
|
|
851
|
+
};
|
|
852
|
+
var blockFor = (consumer) => {
|
|
853
|
+
const faults = consumer.findings.filter((one) => !NOT_A_FAULT.has(one.verdict));
|
|
854
|
+
const head = faults.length === 0 ? [
|
|
855
|
+
` MATCH ${consumer.name} \u2014 ${consumer.declared} spec(s) on the versions this release is cut at`
|
|
856
|
+
] : [];
|
|
857
|
+
return [...head, ...new Set(consumer.findings.map(lineOf))];
|
|
858
|
+
};
|
|
859
|
+
var formatAdoption = (report) => [
|
|
860
|
+
`adoption \u2014 ${report.considered} declared, ${report.consumers.length} read, ${report.excused.length} excused; against ${report.registry ?? "this workspace"}`,
|
|
861
|
+
...report.consumers.flatMap(blockFor),
|
|
862
|
+
...report.excused.map((one) => ` SKIP ${one.path}: ${one.reason}`),
|
|
863
|
+
""
|
|
864
|
+
].join("\n");
|
|
865
|
+
var ADOPTION_TOOL = "release-adoption";
|
|
866
|
+
var ADOPTION_NEXT = "geonosis-release adoption --json and read `consumers` \u2014 every declared consumer leaves by exactly one door, read or excused, and a release review carries one row per consumer from it (D-061)";
|
|
867
|
+
var adoptionEnvelope = (report, durationMs) => ({
|
|
868
|
+
considered: report.considered,
|
|
869
|
+
durationMs,
|
|
870
|
+
excused: report.excused,
|
|
871
|
+
findings: report.consumers.flatMap((one) => one.findings),
|
|
872
|
+
read: report.consumers.length,
|
|
873
|
+
refused: [],
|
|
874
|
+
tool: ADOPTION_TOOL,
|
|
875
|
+
version: versionOf(import.meta.url)
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
// src/wrangler.ts
|
|
879
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
880
|
+
import { resolve as resolve3 } from "path";
|
|
881
|
+
var isRecord3 = (value2) => typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
|
|
882
|
+
var parseJsonc = (source) => {
|
|
883
|
+
let out = "";
|
|
884
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
885
|
+
const char = source[index] ?? "";
|
|
886
|
+
if (char === '"') {
|
|
887
|
+
const start = index;
|
|
888
|
+
index += 1;
|
|
889
|
+
for (; index < source.length; index += 1) {
|
|
890
|
+
if (source[index] === "\\") {
|
|
891
|
+
index += 1;
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
if (source[index] === '"') break;
|
|
895
|
+
}
|
|
896
|
+
out += source.slice(start, index + 1);
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
if (char === "/" && source[index + 1] === "/") {
|
|
900
|
+
const end = source.indexOf("\n", index);
|
|
901
|
+
index = end === -1 ? source.length : end - 1;
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
904
|
+
if (char === "/" && source[index + 1] === "*") {
|
|
905
|
+
const end = source.indexOf("*/", index + 2);
|
|
906
|
+
index = end === -1 ? source.length : end + 1;
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
out += char;
|
|
910
|
+
}
|
|
911
|
+
return JSON.parse(out.replaceAll(/,(\s*[\]}])/g, "$1"));
|
|
912
|
+
};
|
|
913
|
+
var literal = (text) => {
|
|
914
|
+
const value2 = text.trim();
|
|
915
|
+
if (value2.startsWith('"') || value2.startsWith("'")) return value2.slice(1, -1);
|
|
916
|
+
if (value2 === "true") return true;
|
|
917
|
+
if (value2 === "false") return false;
|
|
918
|
+
const number = Number(value2);
|
|
919
|
+
return Number.isNaN(number) ? value2 : number;
|
|
920
|
+
};
|
|
921
|
+
var split = (text, delimiter) => {
|
|
922
|
+
const parts = [];
|
|
923
|
+
let depth = 0;
|
|
924
|
+
let quote = "";
|
|
925
|
+
let current = "";
|
|
926
|
+
for (const char of text) {
|
|
927
|
+
if (quote !== "") {
|
|
928
|
+
current += char;
|
|
929
|
+
if (char === quote) quote = "";
|
|
930
|
+
continue;
|
|
931
|
+
}
|
|
932
|
+
if (char === '"' || char === "'") quote = char;
|
|
933
|
+
if (char === "[" || char === "{") depth += 1;
|
|
934
|
+
if (char === "]" || char === "}") depth -= 1;
|
|
935
|
+
if (char === delimiter && depth === 0) {
|
|
936
|
+
parts.push(current);
|
|
937
|
+
current = "";
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
current += char;
|
|
941
|
+
}
|
|
942
|
+
parts.push(current);
|
|
943
|
+
return parts.filter((one) => one.trim() !== "");
|
|
944
|
+
};
|
|
945
|
+
var value = (text) => {
|
|
946
|
+
const trimmed = text.trim();
|
|
947
|
+
if (trimmed.startsWith("[")) return split(trimmed.slice(1, -1), ",").map(value);
|
|
948
|
+
if (trimmed.startsWith("{")) {
|
|
949
|
+
return Object.fromEntries(
|
|
950
|
+
split(trimmed.slice(1, -1), ",").map((pair) => {
|
|
951
|
+
const at = pair.indexOf("=");
|
|
952
|
+
return [pair.slice(0, at).trim(), value(pair.slice(at + 1))];
|
|
953
|
+
})
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
return literal(trimmed);
|
|
957
|
+
};
|
|
958
|
+
var put = (into, path, leaf) => {
|
|
959
|
+
let here = into;
|
|
960
|
+
for (const key of path.slice(0, -1)) {
|
|
961
|
+
if (!isRecord3(here[key])) here[key] = {};
|
|
962
|
+
here = here[key];
|
|
963
|
+
}
|
|
964
|
+
here[path.at(-1) ?? ""] = leaf;
|
|
965
|
+
};
|
|
966
|
+
var table = (into, path) => {
|
|
967
|
+
let here = into;
|
|
968
|
+
for (const key of path) {
|
|
969
|
+
if (!isRecord3(here[key])) here[key] = {};
|
|
970
|
+
here = here[key];
|
|
971
|
+
}
|
|
972
|
+
return here;
|
|
973
|
+
};
|
|
974
|
+
var arrayTable = (into, path) => {
|
|
975
|
+
let here = into;
|
|
976
|
+
for (const key of path.slice(0, -1)) {
|
|
977
|
+
if (!isRecord3(here[key])) here[key] = {};
|
|
978
|
+
here = here[key];
|
|
979
|
+
}
|
|
980
|
+
const last = path.at(-1) ?? "";
|
|
981
|
+
if (!Array.isArray(here[last])) here[last] = [];
|
|
982
|
+
const list = here[last];
|
|
983
|
+
const entry = {};
|
|
984
|
+
list.push(entry);
|
|
985
|
+
return entry;
|
|
986
|
+
};
|
|
987
|
+
var parseToml = (source) => {
|
|
988
|
+
const out = {};
|
|
989
|
+
let here = out;
|
|
990
|
+
const lines2 = source.split("\n");
|
|
991
|
+
for (let index = 0; index < lines2.length; index += 1) {
|
|
992
|
+
const line = (lines2[index] ?? "").split("#")[0]?.trim() ?? "";
|
|
993
|
+
if (line === "") continue;
|
|
994
|
+
if (line.startsWith("[[") && line.endsWith("]]")) {
|
|
995
|
+
here = arrayTable(out, line.slice(2, -2).trim().split("."));
|
|
996
|
+
continue;
|
|
997
|
+
}
|
|
998
|
+
if (line.startsWith("[") && line.endsWith("]")) {
|
|
999
|
+
here = table(out, line.slice(1, -1).trim().split("."));
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
1002
|
+
const at = line.indexOf("=");
|
|
1003
|
+
if (at === -1) continue;
|
|
1004
|
+
let text = line.slice(at + 1);
|
|
1005
|
+
while ([...text].filter((one) => one === "[").length > [...text].filter((one) => one === "]").length) {
|
|
1006
|
+
index += 1;
|
|
1007
|
+
if (index >= lines2.length) throw new CannotRun("an unterminated array in the TOML config");
|
|
1008
|
+
text += `
|
|
1009
|
+
${(lines2[index] ?? "").split("#")[0] ?? ""}`;
|
|
1010
|
+
}
|
|
1011
|
+
put(here, line.slice(0, at).trim().split("."), value(text));
|
|
1012
|
+
}
|
|
1013
|
+
return out;
|
|
1014
|
+
};
|
|
1015
|
+
var BINDING_LISTS = [
|
|
1016
|
+
"ai",
|
|
1017
|
+
"analytics_engine_datasets",
|
|
1018
|
+
"browser",
|
|
1019
|
+
"d1_databases",
|
|
1020
|
+
"dispatch_namespaces",
|
|
1021
|
+
"durable_objects",
|
|
1022
|
+
"hyperdrive",
|
|
1023
|
+
"kv_namespaces",
|
|
1024
|
+
"mtls_certificates",
|
|
1025
|
+
"queues",
|
|
1026
|
+
"r2_buckets",
|
|
1027
|
+
"send_email",
|
|
1028
|
+
"services",
|
|
1029
|
+
"vectorize",
|
|
1030
|
+
"version_metadata",
|
|
1031
|
+
"workflows"
|
|
1032
|
+
];
|
|
1033
|
+
var bindingsOf = (found) => {
|
|
1034
|
+
if (Array.isArray(found)) return found.flatMap(bindingsOf);
|
|
1035
|
+
if (!isRecord3(found)) return [];
|
|
1036
|
+
const named = found["binding"] ?? found["name"];
|
|
1037
|
+
const here = typeof named === "string" ? [named] : [];
|
|
1038
|
+
const nested = Object.entries(found).filter(([key]) => key === "bindings" || key === "producers" || key === "consumers").flatMap(([, one]) => bindingsOf(one));
|
|
1039
|
+
return [...here, ...nested];
|
|
1040
|
+
};
|
|
1041
|
+
var declaredIn = (config) => {
|
|
1042
|
+
const triggers = config["triggers"];
|
|
1043
|
+
const crons = isRecord3(triggers) && Array.isArray(triggers["crons"]) ? triggers["crons"] : [];
|
|
1044
|
+
const routes = config["routes"];
|
|
1045
|
+
const one = config["route"];
|
|
1046
|
+
const listed = [
|
|
1047
|
+
...Array.isArray(routes) ? routes : [],
|
|
1048
|
+
...typeof one === "string" ? [one] : []
|
|
1049
|
+
];
|
|
1050
|
+
return {
|
|
1051
|
+
bindings: BINDING_LISTS.flatMap((key) => bindingsOf(config[key])).toSorted(),
|
|
1052
|
+
crons: crons.filter((cron) => typeof cron === "string").toSorted(),
|
|
1053
|
+
routes: listed.map((route) => isRecord3(route) ? route["pattern"] : route).filter((pattern) => typeof pattern === "string").toSorted()
|
|
1054
|
+
};
|
|
1055
|
+
};
|
|
1056
|
+
var readWrangler = (root, relative5, env) => {
|
|
1057
|
+
const path = resolve3(root, relative5);
|
|
1058
|
+
let parsed;
|
|
1059
|
+
try {
|
|
1060
|
+
const source = readFileSync6(path, "utf8");
|
|
1061
|
+
parsed = relative5.endsWith(".toml") ? parseToml(source) : parseJsonc(source);
|
|
1062
|
+
} catch (error) {
|
|
1063
|
+
throw new CannotRun(`${relative5} could not be read: ${error.message}`);
|
|
1064
|
+
}
|
|
1065
|
+
if (!isRecord3(parsed)) throw new CannotRun(`${relative5} is not a wrangler configuration`);
|
|
1066
|
+
const environments = parsed["env"];
|
|
1067
|
+
const block = env !== void 0 && isRecord3(environments) && isRecord3(environments[env]) ? environments[env] : parsed;
|
|
1068
|
+
return declaredIn(block);
|
|
1069
|
+
};
|
|
1070
|
+
|
|
1071
|
+
// src/deployed.ts
|
|
1072
|
+
import { existsSync as existsSync4, readFileSync as readFileSync7 } from "fs";
|
|
1073
|
+
import { resolve as resolve4 } from "path";
|
|
1074
|
+
var DEPLOYED_FILE = ".geonosis/deployed.json";
|
|
1075
|
+
var NOTHING_SAYS = "nothing here says what is deployed \u2014 .geonosis/deployed.json is written by the pipeline after promote, and its absence is not a pass";
|
|
1076
|
+
var listOf = (found) => Array.isArray(found) ? found.filter((one) => typeof one === "string") : [];
|
|
1077
|
+
var isRecord4 = (value2) => typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
|
|
1078
|
+
var readDeployed = (root) => {
|
|
1079
|
+
const path = resolve4(root, DEPLOYED_FILE);
|
|
1080
|
+
if (!existsSync4(path)) throw new CannotRun(NOTHING_SAYS);
|
|
1081
|
+
let parsed;
|
|
1082
|
+
try {
|
|
1083
|
+
parsed = JSON.parse(readFileSync7(path, "utf8"));
|
|
1084
|
+
} catch (error) {
|
|
1085
|
+
throw new CannotRun(`${DEPLOYED_FILE} is not readable JSON: ${error.message}`);
|
|
1086
|
+
}
|
|
1087
|
+
if (!isRecord4(parsed)) throw new CannotRun(`${DEPLOYED_FILE} is not an object`);
|
|
1088
|
+
const triggers = isRecord4(parsed["triggers"]) ? parsed["triggers"] : {};
|
|
1089
|
+
return {
|
|
1090
|
+
...typeof parsed["at"] === "string" ? { at: parsed["at"] } : {},
|
|
1091
|
+
deployed: {
|
|
1092
|
+
bindings: listOf(parsed["bindings"]),
|
|
1093
|
+
crons: listOf(triggers["crons"]),
|
|
1094
|
+
routes: listOf(triggers["routes"]),
|
|
1095
|
+
secrets: listOf(parsed["secrets"])
|
|
1096
|
+
}
|
|
1097
|
+
};
|
|
1098
|
+
};
|
|
1099
|
+
var missingBetween = (kind, declared, deployed) => {
|
|
1100
|
+
const missing = declared.filter((one) => !deployed.includes(one));
|
|
1101
|
+
const extra = deployed.filter((one) => !declared.includes(one));
|
|
1102
|
+
return missing.length === 0 && extra.length === 0 ? [] : [{ extra, kind, missing }];
|
|
1103
|
+
};
|
|
1104
|
+
var driftBetween = (declared, deployed) => [
|
|
1105
|
+
...missingBetween("crons", declared.crons, deployed.crons),
|
|
1106
|
+
...missingBetween("routes", declared.routes, deployed.routes),
|
|
1107
|
+
...missingBetween("bindings", declared.bindings, deployed.bindings),
|
|
1108
|
+
...missingBetween("secrets", declared.secrets, deployed.secrets)
|
|
1109
|
+
];
|
|
410
1110
|
var runDeployed = (input) => {
|
|
411
1111
|
const config = readReleaseConfig(input.root);
|
|
412
1112
|
const configs = config.wrangler ?? [];
|
|
@@ -441,7 +1141,7 @@ var formatDeployed = (report) => {
|
|
|
441
1141
|
};
|
|
442
1142
|
|
|
443
1143
|
// src/added.ts
|
|
444
|
-
import { execFileSync, spawnSync } from "child_process";
|
|
1144
|
+
import { execFileSync, spawnSync as spawnSync2 } from "child_process";
|
|
445
1145
|
var git = (root, args) => {
|
|
446
1146
|
try {
|
|
447
1147
|
return execFileSync("git", [...args], { cwd: root, encoding: "utf8", stdio: "pipe" });
|
|
@@ -457,7 +1157,7 @@ var addedSince = (root, since) => {
|
|
|
457
1157
|
return [.../* @__PURE__ */ new Set([...committed, ...staged, ...untracked])].toSorted();
|
|
458
1158
|
};
|
|
459
1159
|
var gitOk = (root, args) => {
|
|
460
|
-
const run =
|
|
1160
|
+
const run = spawnSync2("git", [...args], { cwd: root, encoding: "utf8" });
|
|
461
1161
|
return run.error === void 0 && run.status === 0;
|
|
462
1162
|
};
|
|
463
1163
|
|
|
@@ -647,32 +1347,32 @@ var NARROWING = [
|
|
|
647
1347
|
["SET NOT NULL", /\bSET\s+NOT\s+NULL\b/i]
|
|
648
1348
|
];
|
|
649
1349
|
var statementsOf = (sql) => sql.replaceAll(/'[^']*'/g, (found) => `'${found.slice(1, -1).replaceAll(/[^\n]/g, " ")}'`);
|
|
650
|
-
var
|
|
1350
|
+
var lineOf2 = (body, index) => body.slice(0, index).split("\n").length;
|
|
651
1351
|
var refusalsIn = (path, body, firstLine = 1) => {
|
|
652
1352
|
const stripped = statementsOf(body);
|
|
653
1353
|
return NARROWING.flatMap(([verb, pattern]) => {
|
|
654
1354
|
const found = pattern.exec(stripped);
|
|
655
|
-
return found === null ? [] : [{ line:
|
|
1355
|
+
return found === null ? [] : [{ line: lineOf2(stripped, found.index) + firstLine - 1, path, verb }];
|
|
656
1356
|
});
|
|
657
1357
|
};
|
|
658
1358
|
|
|
659
1359
|
// src/migrations.ts
|
|
660
|
-
import { mkdtempSync, readFileSync as
|
|
1360
|
+
import { mkdtempSync, readFileSync as readFileSync8, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
661
1361
|
import { tmpdir } from "os";
|
|
662
|
-
import { basename, join, resolve as
|
|
1362
|
+
import { basename, join as join5, resolve as resolve6 } from "path";
|
|
663
1363
|
|
|
664
1364
|
// src/squawk.ts
|
|
665
|
-
import { spawnSync as
|
|
666
|
-
import { existsSync as
|
|
667
|
-
import { dirname, resolve as
|
|
668
|
-
import { fileURLToPath } from "url";
|
|
669
|
-
var HERE =
|
|
1365
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
1366
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1367
|
+
import { dirname as dirname2, resolve as resolve5 } from "path";
|
|
1368
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1369
|
+
var HERE = dirname2(fileURLToPath2(import.meta.url));
|
|
670
1370
|
var PINNED = "2.63.0";
|
|
671
1371
|
var findSquawk = (from = HERE) => {
|
|
672
|
-
for (let dir = from; ; dir =
|
|
673
|
-
const candidate =
|
|
674
|
-
if (
|
|
675
|
-
if (
|
|
1372
|
+
for (let dir = from; ; dir = dirname2(dir)) {
|
|
1373
|
+
const candidate = resolve5(dir, "node_modules/.bin/squawk");
|
|
1374
|
+
if (existsSync5(candidate)) return candidate;
|
|
1375
|
+
if (dirname2(dir) === dir) break;
|
|
676
1376
|
}
|
|
677
1377
|
throw new CannotRun(
|
|
678
1378
|
`squawk-cli is not installed beside this package, and the postgres and mikro-orm-ts dialects cannot be measured without it \u2014 a gate that cannot measure has not passed. Install it:
|
|
@@ -685,7 +1385,7 @@ It is an optional peer: a repo whose release.migrations names only the sqlite di
|
|
|
685
1385
|
var squawkOn = (input) => {
|
|
686
1386
|
const { cwd, exclude, files } = input;
|
|
687
1387
|
if (files.length === 0) return { findings: "", ok: true };
|
|
688
|
-
const run =
|
|
1388
|
+
const run = spawnSync3(
|
|
689
1389
|
input.bin ?? findSquawk(),
|
|
690
1390
|
[...exclude.length === 0 ? [] : [`--exclude=${exclude.join(",")}`], ...files],
|
|
691
1391
|
{ cwd, encoding: "utf8" }
|
|
@@ -752,7 +1452,7 @@ var runMigrations = (input) => {
|
|
|
752
1452
|
const forSquawk = [];
|
|
753
1453
|
const states = /* @__PURE__ */ new Map();
|
|
754
1454
|
for (const { entry, file } of matched) {
|
|
755
|
-
const source =
|
|
1455
|
+
const source = readFileSync8(resolve6(input.root, file), "utf8");
|
|
756
1456
|
const found = markersIn(source);
|
|
757
1457
|
const bad = found.flatMap((one) => {
|
|
758
1458
|
const why = judge(one, input.root, input.since);
|
|
@@ -777,11 +1477,11 @@ var runMigrations = (input) => {
|
|
|
777
1477
|
}
|
|
778
1478
|
let clean = true;
|
|
779
1479
|
if (forSquawk.length > 0) {
|
|
780
|
-
const staging = mkdtempSync(
|
|
1480
|
+
const staging = mkdtempSync(join5(tmpdir(), "geonosis-release-squawk-"));
|
|
781
1481
|
try {
|
|
782
1482
|
for (const one of forSquawk) {
|
|
783
|
-
const path = one.entry.dialect === "mikro-orm-ts" ?
|
|
784
|
-
if (one.entry.dialect === "mikro-orm-ts")
|
|
1483
|
+
const path = one.entry.dialect === "mikro-orm-ts" ? join5(staging, `${basename(one.file, ".ts")}.sql`) : resolve6(input.root, one.file);
|
|
1484
|
+
if (one.entry.dialect === "mikro-orm-ts") writeFileSync3(path, `${one.sql}
|
|
785
1485
|
`);
|
|
786
1486
|
const found = squawkOn({
|
|
787
1487
|
cwd: input.root,
|
|
@@ -795,7 +1495,7 @@ var runMigrations = (input) => {
|
|
|
795
1495
|
}
|
|
796
1496
|
}
|
|
797
1497
|
} finally {
|
|
798
|
-
|
|
1498
|
+
rmSync2(staging, { force: true, recursive: true });
|
|
799
1499
|
}
|
|
800
1500
|
}
|
|
801
1501
|
return {
|
|
@@ -959,8 +1659,8 @@ var Runner = class {
|
|
|
959
1659
|
};
|
|
960
1660
|
|
|
961
1661
|
// src/prove.ts
|
|
962
|
-
import { existsSync as
|
|
963
|
-
import { fileURLToPath as
|
|
1662
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1663
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
964
1664
|
var stringAt = (reply, key) => typeof reply[key] === "string" ? reply[key] : void 0;
|
|
965
1665
|
var proveOver = async (input) => {
|
|
966
1666
|
const runner = new Runner(input.command, input.args ?? [], input.cwd);
|
|
@@ -988,242 +1688,68 @@ var proveOver = async (input) => {
|
|
|
988
1688
|
steps.push("promote");
|
|
989
1689
|
const promoted = await runner.ask({ step: "promote", versionId }, input.timeoutMs);
|
|
990
1690
|
if (promoted["ok"] !== true) return refuse(`the promote of ${versionId} did not pass`);
|
|
991
|
-
const went = stringAt(promoted, "promotedVersionId");
|
|
992
|
-
if (went !== void 0 && went !== versionId) {
|
|
993
|
-
return refuse(`${versionId} was proved and ${went} was promoted \u2014 nothing proved ${went}`);
|
|
994
|
-
}
|
|
995
|
-
return { ok: true, steps };
|
|
996
|
-
} finally {
|
|
997
|
-
runner.close();
|
|
998
|
-
}
|
|
999
|
-
};
|
|
1000
|
-
var PLANTS = [
|
|
1001
|
-
{
|
|
1002
|
-
name: "answered",
|
|
1003
|
-
says: "a smoke that answered a version other than the one it overrode to is refused"
|
|
1004
|
-
},
|
|
1005
|
-
{ name: "failed", says: "a smoke that did not pass is refused" },
|
|
1006
|
-
{ name: "promoted", says: "a promote of a version nothing proved is refused" }
|
|
1007
|
-
];
|
|
1008
|
-
var stubPath = () => {
|
|
1009
|
-
const path = fileURLToPath2(new URL("./stub-runner.js", import.meta.url));
|
|
1010
|
-
if (!existsSync4(path)) throw new CannotRun(`${path} is missing \u2014 run pnpm build first`);
|
|
1011
|
-
return path;
|
|
1012
|
-
};
|
|
1013
|
-
var prove = async (cwd, stub = stubPath()) => {
|
|
1014
|
-
const lines2 = [];
|
|
1015
|
-
let ok = true;
|
|
1016
|
-
for (const plant of PLANTS) {
|
|
1017
|
-
const verdict = await proveOver({
|
|
1018
|
-
args: [stub, `--plant=${plant.name}`],
|
|
1019
|
-
command: process.execPath,
|
|
1020
|
-
cwd,
|
|
1021
|
-
timeoutMs: 3e4
|
|
1022
|
-
});
|
|
1023
|
-
if (verdict.ok) ok = false;
|
|
1024
|
-
lines2.push(` ${verdict.ok ? "MISSED" : "PROVEN"} ${plant.says}`);
|
|
1025
|
-
if (plant.name !== "promoted" && verdict.steps.includes("promote")) {
|
|
1026
|
-
ok = false;
|
|
1027
|
-
lines2.push(" MISSED a refused smoke was followed by a promote request");
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
const honest = await proveOver({
|
|
1031
|
-
args: [stub, "--plant=good"],
|
|
1032
|
-
command: process.execPath,
|
|
1033
|
-
cwd,
|
|
1034
|
-
timeoutMs: 3e4
|
|
1035
|
-
});
|
|
1036
|
-
if (!honest.ok) ok = false;
|
|
1037
|
-
lines2.push(
|
|
1038
|
-
` ${honest.ok ? "PROVEN" : "MISSED"} a smoke that answered the version it overrode to is not refused`
|
|
1039
|
-
);
|
|
1040
|
-
return { lines: lines2, ok };
|
|
1041
|
-
};
|
|
1042
|
-
var formatProve = (outcome) => `${outcome.lines.join("\n")}
|
|
1043
|
-
|
|
1044
|
-
prove ${outcome.ok ? "PASS" : "FAIL"} \u2014 ${outcome.ok ? "every plant was refused, and the honest runner was not" : "a plant went through"}.
|
|
1045
|
-
`;
|
|
1046
|
-
var formatVerdict = (verdict) => verdict.ok ? `OK prove: ${verdict.steps.join(" \u2192 ")}
|
|
1047
|
-
` : ` REFUSED after ${verdict.steps.join(" \u2192 ")}: ${verdict.why ?? ""}
|
|
1048
|
-
`;
|
|
1049
|
-
|
|
1050
|
-
// src/registry.ts
|
|
1051
|
-
var DEFAULT_REGISTRY = "https://registry.npmjs.org";
|
|
1052
|
-
var TIMEOUT_MS = 15e3;
|
|
1053
|
-
var registryPathOf = (name) => name.startsWith("@") ? `@${encodeURIComponent(name.slice(1))}` : encodeURIComponent(name);
|
|
1054
|
-
var versionsIn = (body) => {
|
|
1055
|
-
const versions = body.versions;
|
|
1056
|
-
return typeof versions === "object" && versions !== null ? Object.keys(versions) : [];
|
|
1057
|
-
};
|
|
1058
|
-
var latestIn = (body) => {
|
|
1059
|
-
const tags = body["dist-tags"];
|
|
1060
|
-
const latest = typeof tags === "object" && tags !== null ? tags.latest : void 0;
|
|
1061
|
-
return typeof latest === "string" ? latest : void 0;
|
|
1062
|
-
};
|
|
1063
|
-
var askRegistry = async ({
|
|
1064
|
-
name,
|
|
1065
|
-
registry = DEFAULT_REGISTRY,
|
|
1066
|
-
timeoutMs = TIMEOUT_MS
|
|
1067
|
-
}) => {
|
|
1068
|
-
const url = `${registry.replace(/\/+$/, "")}/${registryPathOf(name)}`;
|
|
1069
|
-
let response;
|
|
1070
|
-
try {
|
|
1071
|
-
response = await fetch(url, {
|
|
1072
|
-
headers: { accept: "application/json" },
|
|
1073
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
1074
|
-
});
|
|
1075
|
-
} catch (error) {
|
|
1076
|
-
return { kind: "unreachable", name, why: `GET ${url} \u2014 ${error.message}` };
|
|
1077
|
-
}
|
|
1078
|
-
if (response.status === 404) return { kind: "absent", name };
|
|
1079
|
-
if (!response.ok) {
|
|
1080
|
-
return { kind: "unreachable", name, why: `GET ${url} \u2014 HTTP ${response.status}` };
|
|
1081
|
-
}
|
|
1082
|
-
let body;
|
|
1083
|
-
try {
|
|
1084
|
-
body = await response.json();
|
|
1085
|
-
} catch (error) {
|
|
1086
|
-
return {
|
|
1087
|
-
kind: "unreachable",
|
|
1088
|
-
name,
|
|
1089
|
-
why: `GET ${url} answered ${response.status} with something that is not JSON \u2014 ${error.message}`
|
|
1090
|
-
};
|
|
1091
|
-
}
|
|
1092
|
-
return { kind: "present", latest: latestIn(body), name, versions: versionsIn(body) };
|
|
1093
|
-
};
|
|
1094
|
-
|
|
1095
|
-
// src/published.ts
|
|
1096
|
-
import { readdirSync, readFileSync as readFileSync5 } from "fs";
|
|
1097
|
-
import { join as join2, relative, sep } from "path";
|
|
1098
|
-
var NEVER_WALKED = /* @__PURE__ */ new Set(["build", "coverage", "dist", "node_modules", "storybook-static"]);
|
|
1099
|
-
var pathOf = (root, path) => relative(root, path).split(sep).join("/");
|
|
1100
|
-
var manifestsUnder = (root) => {
|
|
1101
|
-
const found = [];
|
|
1102
|
-
const walk = (dir) => {
|
|
1103
|
-
let entries;
|
|
1104
|
-
try {
|
|
1105
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
1106
|
-
} catch {
|
|
1107
|
-
return;
|
|
1108
|
-
}
|
|
1109
|
-
for (const entry of entries) {
|
|
1110
|
-
if (entry.isDirectory()) {
|
|
1111
|
-
if (!entry.name.startsWith(".") && !NEVER_WALKED.has(entry.name))
|
|
1112
|
-
walk(join2(dir, entry.name));
|
|
1113
|
-
continue;
|
|
1114
|
-
}
|
|
1115
|
-
if (entry.name === "package.json") found.push(join2(dir, entry.name));
|
|
1116
|
-
}
|
|
1117
|
-
};
|
|
1118
|
-
walk(root);
|
|
1119
|
-
return found;
|
|
1120
|
-
};
|
|
1121
|
-
var censusOf = (root) => {
|
|
1122
|
-
const excused = [];
|
|
1123
|
-
const locals = [];
|
|
1124
|
-
for (const path of manifestsUnder(root)) {
|
|
1125
|
-
const at = pathOf(root, path);
|
|
1126
|
-
let manifest;
|
|
1127
|
-
try {
|
|
1128
|
-
manifest = JSON.parse(readFileSync5(path, "utf8"));
|
|
1129
|
-
} catch (error) {
|
|
1130
|
-
excused.push({ path: at, reason: `it does not parse: ${error.message}` });
|
|
1131
|
-
continue;
|
|
1132
|
-
}
|
|
1133
|
-
if (manifest.private === true) {
|
|
1134
|
-
excused.push({ path: at, reason: "private: true \u2014 nothing publishes it" });
|
|
1135
|
-
continue;
|
|
1136
|
-
}
|
|
1137
|
-
if (typeof manifest.name !== "string" || manifest.name === "") {
|
|
1138
|
-
excused.push({ path: at, reason: "it names no package" });
|
|
1139
|
-
continue;
|
|
1140
|
-
}
|
|
1141
|
-
if (typeof manifest.version !== "string" || manifest.version === "") {
|
|
1142
|
-
excused.push({ path: at, reason: "it declares no version" });
|
|
1143
|
-
continue;
|
|
1144
|
-
}
|
|
1145
|
-
locals.push({ at, name: manifest.name, version: manifest.version });
|
|
1146
|
-
}
|
|
1147
|
-
return { excused, locals: locals.toSorted((a, b) => a.name.localeCompare(b.name)) };
|
|
1148
|
-
};
|
|
1149
|
-
var EXISTS_BUT = "exists but no version matching";
|
|
1150
|
-
var lineFor = async (local, registry) => {
|
|
1151
|
-
const answer = await askRegistry({ name: local.name, registry });
|
|
1152
|
-
const base = { at: local.at, local: local.version, name: local.name };
|
|
1153
|
-
if (answer.kind === "unreachable") {
|
|
1154
|
-
return { ...base, latest: void 0, verdict: "UNREACHABLE", why: answer.why };
|
|
1155
|
-
}
|
|
1156
|
-
if (answer.kind === "absent") {
|
|
1157
|
-
return {
|
|
1158
|
-
...base,
|
|
1159
|
-
latest: void 0,
|
|
1160
|
-
verdict: "ABSENT",
|
|
1161
|
-
why: "npm has never heard of this name \u2014 a name published moments ago 404s for a few minutes; --wait <seconds> polls through that window"
|
|
1162
|
-
};
|
|
1163
|
-
}
|
|
1164
|
-
if (answer.latest === local.version) {
|
|
1165
|
-
return { ...base, latest: answer.latest, verdict: "MATCH", why: "on npm" };
|
|
1166
|
-
}
|
|
1167
|
-
return {
|
|
1168
|
-
...base,
|
|
1169
|
-
latest: answer.latest,
|
|
1170
|
-
verdict: "BEHIND",
|
|
1171
|
-
why: answer.versions.includes(local.version) ? `npm has ${local.version}, and its latest tag is ${answer.latest ?? "nothing"} \u2014 the group has not landed on one version` : `the package ${EXISTS_BUT} ${local.version}; npm answers ${answer.latest ?? "nothing"}. Minutes after a publish, suspect the package manager's manifest cache before the registry`
|
|
1172
|
-
};
|
|
1173
|
-
};
|
|
1174
|
-
var sleep = (ms) => new Promise((done) => {
|
|
1175
|
-
setTimeout(done, ms);
|
|
1176
|
-
});
|
|
1177
|
-
var POLL_MS = 5e3;
|
|
1178
|
-
var runPublished = async ({
|
|
1179
|
-
pollMs = POLL_MS,
|
|
1180
|
-
registry = DEFAULT_REGISTRY,
|
|
1181
|
-
root,
|
|
1182
|
-
waitSeconds = 0
|
|
1183
|
-
}) => {
|
|
1184
|
-
const { excused, locals } = censusOf(root);
|
|
1185
|
-
const startedAt = Date.now();
|
|
1186
|
-
const budgetMs = Math.max(0, waitSeconds * 1e3);
|
|
1187
|
-
let lines2 = [];
|
|
1188
|
-
let sweeps = 0;
|
|
1189
|
-
for (; ; ) {
|
|
1190
|
-
lines2 = [];
|
|
1191
|
-
for (const local of locals) lines2.push(await lineFor(local, registry));
|
|
1192
|
-
sweeps += 1;
|
|
1193
|
-
const settled = lines2.every((one) => one.verdict === "MATCH");
|
|
1194
|
-
const stuck = lines2.some((one) => one.verdict === "UNREACHABLE");
|
|
1195
|
-
const left = budgetMs - (Date.now() - startedAt);
|
|
1196
|
-
if (settled || stuck || left <= 0) break;
|
|
1197
|
-
await sleep(Math.min(pollMs, left));
|
|
1691
|
+
const went = stringAt(promoted, "promotedVersionId");
|
|
1692
|
+
if (went !== void 0 && went !== versionId) {
|
|
1693
|
+
return refuse(`${versionId} was proved and ${went} was promoted \u2014 nothing proved ${went}`);
|
|
1694
|
+
}
|
|
1695
|
+
return { ok: true, steps };
|
|
1696
|
+
} finally {
|
|
1697
|
+
runner.close();
|
|
1198
1698
|
}
|
|
1199
|
-
const unreachable = lines2.filter((one) => one.verdict === "UNREACHABLE").length;
|
|
1200
|
-
return {
|
|
1201
|
-
considered: lines2.length + excused.length,
|
|
1202
|
-
excused: excused.toSorted((a, b) => a.path.localeCompare(b.path)),
|
|
1203
|
-
lines: lines2,
|
|
1204
|
-
ok: lines2.length > 0 && lines2.every((one) => one.verdict === "MATCH"),
|
|
1205
|
-
registry,
|
|
1206
|
-
sweeps,
|
|
1207
|
-
unreachable,
|
|
1208
|
-
waitedMs: Date.now() - startedAt
|
|
1209
|
-
};
|
|
1210
1699
|
};
|
|
1211
|
-
var
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1700
|
+
var PLANTS = [
|
|
1701
|
+
{
|
|
1702
|
+
name: "answered",
|
|
1703
|
+
says: "a smoke that answered a version other than the one it overrode to is refused"
|
|
1704
|
+
},
|
|
1705
|
+
{ name: "failed", says: "a smoke that did not pass is refused" },
|
|
1706
|
+
{ name: "promoted", says: "a promote of a version nothing proved is refused" }
|
|
1707
|
+
];
|
|
1708
|
+
var stubPath = () => {
|
|
1709
|
+
const path = fileURLToPath3(new URL("./stub-runner.js", import.meta.url));
|
|
1710
|
+
if (!existsSync6(path)) throw new CannotRun(`${path} is missing \u2014 run pnpm build first`);
|
|
1711
|
+
return path;
|
|
1712
|
+
};
|
|
1713
|
+
var prove = async (cwd, stub = stubPath()) => {
|
|
1714
|
+
const lines2 = [];
|
|
1715
|
+
let ok = true;
|
|
1716
|
+
for (const plant of PLANTS) {
|
|
1717
|
+
const verdict = await proveOver({
|
|
1718
|
+
args: [stub, `--plant=${plant.name}`],
|
|
1719
|
+
command: process.execPath,
|
|
1720
|
+
cwd,
|
|
1721
|
+
timeoutMs: 3e4
|
|
1722
|
+
});
|
|
1723
|
+
if (verdict.ok) ok = false;
|
|
1724
|
+
lines2.push(` ${verdict.ok ? "MISSED" : "PROVEN"} ${plant.says}`);
|
|
1725
|
+
if (plant.name !== "promoted" && verdict.steps.includes("promote")) {
|
|
1726
|
+
ok = false;
|
|
1727
|
+
lines2.push(" MISSED a refused smoke was followed by a promote request");
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
const honest = await proveOver({
|
|
1731
|
+
args: [stub, "--plant=good"],
|
|
1732
|
+
command: process.execPath,
|
|
1733
|
+
cwd,
|
|
1734
|
+
timeoutMs: 3e4
|
|
1735
|
+
});
|
|
1736
|
+
if (!honest.ok) ok = false;
|
|
1737
|
+
lines2.push(
|
|
1738
|
+
` ${honest.ok ? "PROVEN" : "MISSED"} a smoke that answered the version it overrode to is not refused`
|
|
1739
|
+
);
|
|
1740
|
+
return { lines: lines2, ok };
|
|
1222
1741
|
};
|
|
1742
|
+
var formatProve = (outcome) => `${outcome.lines.join("\n")}
|
|
1743
|
+
|
|
1744
|
+
prove ${outcome.ok ? "PASS" : "FAIL"} \u2014 ${outcome.ok ? "every plant was refused, and the honest runner was not" : "a plant went through"}.
|
|
1745
|
+
`;
|
|
1746
|
+
var formatVerdict = (verdict) => verdict.ok ? `OK prove: ${verdict.steps.join(" \u2192 ")}
|
|
1747
|
+
` : ` REFUSED after ${verdict.steps.join(" \u2192 ")}: ${verdict.why ?? ""}
|
|
1748
|
+
`;
|
|
1223
1749
|
|
|
1224
1750
|
// src/schema-walls.ts
|
|
1225
|
-
import { readdirSync as
|
|
1226
|
-
import { join as
|
|
1751
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync9 } from "fs";
|
|
1752
|
+
import { join as join6, relative as relative3, resolve as resolve7, sep as sep2 } from "path";
|
|
1227
1753
|
var MIGRATION_FILE = /\.(?:sql|ts)$/;
|
|
1228
1754
|
var CURRENT_SETTING = /current_setting\s*\(\s*'([^']+)'/gi;
|
|
1229
1755
|
var filesUnder = (root, dir) => {
|
|
@@ -1231,20 +1757,20 @@ var filesUnder = (root, dir) => {
|
|
|
1231
1757
|
const walk = (at) => {
|
|
1232
1758
|
let entries;
|
|
1233
1759
|
try {
|
|
1234
|
-
entries =
|
|
1760
|
+
entries = readdirSync3(at, { withFileTypes: true });
|
|
1235
1761
|
} catch {
|
|
1236
1762
|
return;
|
|
1237
1763
|
}
|
|
1238
1764
|
for (const entry of entries) {
|
|
1239
|
-
const path =
|
|
1765
|
+
const path = join6(at, entry.name);
|
|
1240
1766
|
if (entry.isDirectory()) walk(path);
|
|
1241
1767
|
else if (MIGRATION_FILE.test(entry.name)) found.push(path);
|
|
1242
1768
|
}
|
|
1243
1769
|
};
|
|
1244
|
-
walk(
|
|
1245
|
-
return found.map((path) =>
|
|
1770
|
+
walk(resolve7(root, dir));
|
|
1771
|
+
return found.map((path) => relative3(root, path).split(sep2).join("/")).toSorted();
|
|
1246
1772
|
};
|
|
1247
|
-
var
|
|
1773
|
+
var lineOf3 = (body, index) => body.slice(0, index).split("\n").length;
|
|
1248
1774
|
var wordFor = (table2) => new RegExp(
|
|
1249
1775
|
String.raw`(?<![\w.])${table2.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`)}\b`,
|
|
1250
1776
|
"i"
|
|
@@ -1258,7 +1784,7 @@ var foreignTables = (path, sql, schema) => {
|
|
|
1258
1784
|
const found = wordFor(table2).exec(sql);
|
|
1259
1785
|
return found === null ? [] : [
|
|
1260
1786
|
{
|
|
1261
|
-
line:
|
|
1787
|
+
line: lineOf3(sql, found.index),
|
|
1262
1788
|
path,
|
|
1263
1789
|
why: `"${mine.name}" names "${table2}", which "${other.name}" declares. A domain's migrations may only touch its own tables \u2014 cross-domain talk goes through events, and a foreign key here is a coupling no import graph shows`
|
|
1264
1790
|
}
|
|
@@ -1274,404 +1800,98 @@ var undeclaredSettings = (path, source, schema) => {
|
|
|
1274
1800
|
const name = match[1] ?? "";
|
|
1275
1801
|
if (allowed.has(name)) continue;
|
|
1276
1802
|
found.push({
|
|
1277
|
-
line:
|
|
1803
|
+
line: lineOf3(source, match.index),
|
|
1278
1804
|
path,
|
|
1279
1805
|
why: `current_setting('${name}') names a session variable this repo has not declared. release.schema.sessionVariables holds ${schema.sessionVariables.join(", ")} \u2014 add it there, or take the name as an option so the DDL is not written for one consumer's session`
|
|
1280
|
-
});
|
|
1281
|
-
}
|
|
1282
|
-
return found;
|
|
1283
|
-
};
|
|
1284
|
-
var runSchema = ({ root }) => {
|
|
1285
|
-
const schema = readReleaseConfig(root).schema;
|
|
1286
|
-
if (schema === void 0) {
|
|
1287
|
-
throw new CannotRun(
|
|
1288
|
-
"geonosis.json names no release.schema \u2014 nothing here says which tables belong to which domain, or which session variables the DDL may name (#168)"
|
|
1289
|
-
);
|
|
1290
|
-
}
|
|
1291
|
-
if (schema.domains.length === 0 && schema.sessionVariables.length === 0) {
|
|
1292
|
-
throw new CannotRun(
|
|
1293
|
-
"release.schema is declared and holds neither domains nor sessionVariables \u2014 say which tables belong to which domain and which session variables the DDL may name, or remove the block: a check with nothing to compare against reads exactly like a clean schema"
|
|
1294
|
-
);
|
|
1295
|
-
}
|
|
1296
|
-
const dirs = schema.domains.map((one) => one.dir);
|
|
1297
|
-
const files = [...new Set(dirs.flatMap((dir) => filesUnder(root, dir)))].toSorted();
|
|
1298
|
-
const findings = [];
|
|
1299
|
-
const unreadable = [];
|
|
1300
|
-
for (const path of files) {
|
|
1301
|
-
const source = readFileSync6(resolve6(root, path), "utf8");
|
|
1302
|
-
let sql;
|
|
1303
|
-
try {
|
|
1304
|
-
sql = sqlOf(path, source);
|
|
1305
|
-
} catch (error) {
|
|
1306
|
-
if (!(error instanceof Unreadable)) throw error;
|
|
1307
|
-
unreadable.push({ path, why: `${path} holds ${error.message} \u2014 this reader cannot read it` });
|
|
1308
|
-
continue;
|
|
1309
|
-
}
|
|
1310
|
-
findings.push(...foreignTables(path, sql, schema), ...undeclaredSettings(path, source, schema));
|
|
1311
|
-
}
|
|
1312
|
-
return {
|
|
1313
|
-
dirs,
|
|
1314
|
-
files,
|
|
1315
|
-
findings: findings.toSorted((a, b) => a.path.localeCompare(b.path) || a.line - b.line),
|
|
1316
|
-
ok: findings.length === 0 && unreadable.length === 0,
|
|
1317
|
-
read: files.length - unreadable.length,
|
|
1318
|
-
unreadable
|
|
1319
|
-
};
|
|
1320
|
-
};
|
|
1321
|
-
var formatSchema = (report) => {
|
|
1322
|
-
if (report.files.length === 0) {
|
|
1323
|
-
return `NONE schema: no migration file under ${report.dirs.join(", ")}; nothing was read
|
|
1324
|
-
`;
|
|
1325
|
-
}
|
|
1326
|
-
if (report.ok) {
|
|
1327
|
-
return `OK schema: ${report.read} migration(s) read under ${report.dirs.length} declared domain(s), no wall crossed
|
|
1328
|
-
`;
|
|
1329
|
-
}
|
|
1330
|
-
const said = report.findings.map((one) => ` ${one.path}:${one.line}: ${one.why}
|
|
1331
|
-
`);
|
|
1332
|
-
const cannot = report.unreadable.map((one) => ` ${one.why}
|
|
1333
|
-
`);
|
|
1334
|
-
return `${said.join("")}${cannot.join("")}schema: ${report.read} of ${report.files.length} files read \u2014 ${report.findings.length} finding(s)${report.unreadable.length === 0 ? "" : `, ${report.unreadable.length} unreadable`}
|
|
1335
|
-
`;
|
|
1336
|
-
};
|
|
1337
|
-
|
|
1338
|
-
// src/smoke.ts
|
|
1339
|
-
import { spawnSync as spawnSync3 } from "child_process";
|
|
1340
|
-
import {
|
|
1341
|
-
cpSync,
|
|
1342
|
-
existsSync as existsSync5,
|
|
1343
|
-
mkdirSync,
|
|
1344
|
-
readdirSync as readdirSync3,
|
|
1345
|
-
readFileSync as readFileSync7,
|
|
1346
|
-
rmSync as rmSync2,
|
|
1347
|
-
statSync,
|
|
1348
|
-
writeFileSync as writeFileSync2
|
|
1349
|
-
} from "fs";
|
|
1350
|
-
import { join as join4, relative as relative3, resolve as resolve7 } from "path";
|
|
1351
|
-
var SNAPSHOTS_DIR = ".geonosis/consumer-snapshots";
|
|
1352
|
-
var LOCKFILES = [
|
|
1353
|
-
{ file: "bun.lock", manager: "bun" },
|
|
1354
|
-
{ file: "bun.lockb", manager: "bun" },
|
|
1355
|
-
{ file: "pnpm-lock.yaml", manager: "pnpm" }
|
|
1356
|
-
];
|
|
1357
|
-
var UNMEASURED = [
|
|
1358
|
-
{ file: "package-lock.json", manager: "npm" },
|
|
1359
|
-
{ file: "yarn.lock", manager: "yarn" }
|
|
1360
|
-
];
|
|
1361
|
-
var EXCLUDED_DIRS = [
|
|
1362
|
-
".cache",
|
|
1363
|
-
".geonosis",
|
|
1364
|
-
".git",
|
|
1365
|
-
".next",
|
|
1366
|
-
".turbo",
|
|
1367
|
-
".wrangler",
|
|
1368
|
-
"coverage",
|
|
1369
|
-
"dist",
|
|
1370
|
-
"node_modules",
|
|
1371
|
-
"storybook-static"
|
|
1372
|
-
];
|
|
1373
|
-
var PHASES = ["typecheck", "lint", "doctor"];
|
|
1374
|
-
var DOCTOR_PACKAGE = "@geonosis/doctor";
|
|
1375
|
-
var DOCTOR_BIN = "geonosis-doctor";
|
|
1376
|
-
var DOCTOR_DOORS = ["@geonosis/cli", "geonosis"];
|
|
1377
|
-
var DOOR_BIN = "geonosis doctor";
|
|
1378
|
-
var isRecord4 = (value2) => typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
|
|
1379
|
-
var readManifest = (path) => {
|
|
1380
|
-
try {
|
|
1381
|
-
const parsed = JSON.parse(readFileSync7(path, "utf8"));
|
|
1382
|
-
return isRecord4(parsed) ? parsed : {};
|
|
1383
|
-
} catch (error) {
|
|
1384
|
-
throw new CannotRun(`${path} is not readable JSON: ${error.message}`);
|
|
1385
|
-
}
|
|
1386
|
-
};
|
|
1387
|
-
var versionsIn2 = (manifest) => {
|
|
1388
|
-
const found = {};
|
|
1389
|
-
for (const key of ["dependencies", "devDependencies"]) {
|
|
1390
|
-
const block = manifest[key];
|
|
1391
|
-
if (!isRecord4(block)) continue;
|
|
1392
|
-
for (const [name, range] of Object.entries(block)) {
|
|
1393
|
-
if (typeof range === "string") found[name] = range;
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
return Object.fromEntries(Object.entries(found).toSorted(([a], [b]) => a.localeCompare(b)));
|
|
1397
|
-
};
|
|
1398
|
-
var scriptsIn = (manifest) => {
|
|
1399
|
-
const scripts = manifest["scripts"];
|
|
1400
|
-
if (!isRecord4(scripts)) return {};
|
|
1401
|
-
return Object.fromEntries(
|
|
1402
|
-
Object.entries(scripts).filter((one) => typeof one[1] === "string")
|
|
1403
|
-
);
|
|
1404
|
-
};
|
|
1405
|
-
var copyInto = (from, to, excluded) => {
|
|
1406
|
-
let bytes = 0;
|
|
1407
|
-
let files = 0;
|
|
1408
|
-
const walk = (dir, into) => {
|
|
1409
|
-
mkdirSync(into, { recursive: true });
|
|
1410
|
-
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
1411
|
-
if (excluded.has(entry.name)) continue;
|
|
1412
|
-
const at = join4(dir, entry.name);
|
|
1413
|
-
if (entry.isDirectory()) {
|
|
1414
|
-
walk(at, join4(into, entry.name));
|
|
1415
|
-
continue;
|
|
1416
|
-
}
|
|
1417
|
-
if (!entry.isFile()) continue;
|
|
1418
|
-
cpSync(at, join4(into, entry.name));
|
|
1419
|
-
bytes += statSync(at).size;
|
|
1420
|
-
files += 1;
|
|
1421
|
-
}
|
|
1422
|
-
};
|
|
1423
|
-
walk(from, to);
|
|
1424
|
-
return { bytes, files };
|
|
1425
|
-
};
|
|
1426
|
-
var manifestsUnder2 = (root) => {
|
|
1427
|
-
const found = [];
|
|
1428
|
-
const walk = (dir) => {
|
|
1429
|
-
for (const entry of readdirSync3(dir, { withFileTypes: true }).toSorted(
|
|
1430
|
-
(a, b) => a.name.localeCompare(b.name)
|
|
1431
|
-
)) {
|
|
1432
|
-
const at = join4(dir, entry.name);
|
|
1433
|
-
if (entry.isDirectory()) {
|
|
1434
|
-
walk(at);
|
|
1435
|
-
continue;
|
|
1436
|
-
}
|
|
1437
|
-
if (entry.name !== "package.json") continue;
|
|
1438
|
-
found.push({ path: relative3(root, at), versions: versionsIn2(readManifest(at)) });
|
|
1439
|
-
}
|
|
1440
|
-
};
|
|
1441
|
-
walk(root);
|
|
1442
|
-
return found.toSorted((a, b) => a.path.localeCompare(b.path));
|
|
1443
|
-
};
|
|
1444
|
-
var managerOf = (from) => {
|
|
1445
|
-
const found = LOCKFILES.find((one) => existsSync5(join4(from, one.file)));
|
|
1446
|
-
if (found !== void 0) return { lockfile: found.file, manager: found.manager };
|
|
1447
|
-
const unmeasured = UNMEASURED.find((one) => existsSync5(join4(from, one.file)));
|
|
1448
|
-
if (unmeasured !== void 0) {
|
|
1449
|
-
throw new CannotRun(
|
|
1450
|
-
`${from} is a ${unmeasured.manager} tree (${unmeasured.file}), and the pack \u2192 rewrite \u2192 install path has never been measured under ${unmeasured.manager} here. It reads ${LOCKFILES.map((one) => one.file).join(", ")}.`
|
|
1451
|
-
);
|
|
1452
|
-
}
|
|
1453
|
-
throw new CannotRun(
|
|
1454
|
-
`nothing in ${from} says which runtime installs it \u2014 looked for ${[...LOCKFILES, ...UNMEASURED].map((one) => one.file).join(", ")}. The smoke installs the way the consumer installs, so the manager is read, never assumed.`
|
|
1455
|
-
);
|
|
1456
|
-
};
|
|
1457
|
-
var commandsFor = (manifest, manifests, named) => {
|
|
1458
|
-
const scripts = scriptsIn(manifest);
|
|
1459
|
-
const installs = (name) => manifests.some((one) => one.versions[name] !== void 0);
|
|
1460
|
-
const doctorCommand = installs(DOCTOR_PACKAGE) ? { exec: DOCTOR_BIN } : DOCTOR_DOORS.some(installs) ? { exec: DOOR_BIN } : {
|
|
1461
|
-
why: `no manifest in this tree installs ${DOCTOR_PACKAGE} or a door that brings it (${DOCTOR_DOORS.join(", ")}), so nothing here can run ${DOCTOR_BIN}`
|
|
1462
|
-
};
|
|
1463
|
-
const phase = (name, fallback) => {
|
|
1464
|
-
const override = named[name];
|
|
1465
|
-
if (override !== void 0) return { exec: override };
|
|
1466
|
-
if (scripts[name] !== void 0) return { run: name };
|
|
1467
|
-
return fallback;
|
|
1468
|
-
};
|
|
1469
|
-
return {
|
|
1470
|
-
doctor: phase("doctor", doctorCommand),
|
|
1471
|
-
lint: phase("lint", { why: 'the tree has no "lint" script and no --lint command was named' }),
|
|
1472
|
-
typecheck: phase("typecheck", {
|
|
1473
|
-
why: 'the tree has no "typecheck" script and no --typecheck command was named'
|
|
1474
|
-
})
|
|
1475
|
-
};
|
|
1476
|
-
};
|
|
1477
|
-
var snapshotDir = (root, name) => join4(root, SNAPSHOTS_DIR, name);
|
|
1478
|
-
var readSnapshot = (root, name) => {
|
|
1479
|
-
const at = join4(snapshotDir(root, name), "snapshot.json");
|
|
1480
|
-
if (!existsSync5(at)) {
|
|
1481
|
-
throw new CannotRun(
|
|
1482
|
-
`there is no snapshot called "${name}" here \u2014 ${at} does not exist. Record one with: geonosis-release smoke snapshot ${name} --from <their tree>`
|
|
1483
|
-
);
|
|
1806
|
+
});
|
|
1484
1807
|
}
|
|
1485
|
-
return
|
|
1486
|
-
};
|
|
1487
|
-
var wouldBeCommitted = (root, path) => {
|
|
1488
|
-
const inside = spawnSync3("git", ["rev-parse", "--is-inside-work-tree"], { cwd: root });
|
|
1489
|
-
if (inside.error !== void 0 || inside.status !== 0) return false;
|
|
1490
|
-
return spawnSync3("git", ["check-ignore", "-q", path], { cwd: root }).status !== 0;
|
|
1808
|
+
return found;
|
|
1491
1809
|
};
|
|
1492
|
-
var
|
|
1493
|
-
|
|
1494
|
-
if (
|
|
1810
|
+
var runSchema = ({ root }) => {
|
|
1811
|
+
const schema = readReleaseConfig(root).schema;
|
|
1812
|
+
if (schema === void 0) {
|
|
1495
1813
|
throw new CannotRun(
|
|
1496
|
-
|
|
1814
|
+
"geonosis.json names no release.schema \u2014 nothing here says which tables belong to which domain, or which session variables the DDL may name (#168)"
|
|
1497
1815
|
);
|
|
1498
1816
|
}
|
|
1499
|
-
|
|
1500
|
-
if (!existsSync5(join4(from, "package.json"))) {
|
|
1501
|
-
throw new CannotRun(`${from} has no package.json \u2014 that is not a tree a consumer installs`);
|
|
1502
|
-
}
|
|
1503
|
-
const at = snapshotDir(input.root, input.name);
|
|
1504
|
-
if (wouldBeCommitted(input.root, join4(SNAPSHOTS_DIR, input.name))) {
|
|
1817
|
+
if (schema.domains.length === 0 && schema.sessionVariables.length === 0) {
|
|
1505
1818
|
throw new CannotRun(
|
|
1506
|
-
|
|
1819
|
+
"release.schema is declared and holds neither domains nor sessionVariables \u2014 say which tables belong to which domain and which session variables the DDL may name, or remove the block: a check with nothing to compare against reads exactly like a clean schema"
|
|
1507
1820
|
);
|
|
1508
1821
|
}
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1822
|
+
const dirs = schema.domains.map((one) => one.dir);
|
|
1823
|
+
const files = [...new Set(dirs.flatMap((dir) => filesUnder(root, dir)))].toSorted();
|
|
1824
|
+
const findings = [];
|
|
1825
|
+
const unreadable = [];
|
|
1826
|
+
for (const path of files) {
|
|
1827
|
+
const source = readFileSync9(resolve7(root, path), "utf8");
|
|
1828
|
+
let sql;
|
|
1829
|
+
try {
|
|
1830
|
+
sql = sqlOf(path, source);
|
|
1831
|
+
} catch (error) {
|
|
1832
|
+
if (!(error instanceof Unreadable)) throw error;
|
|
1833
|
+
unreadable.push({ path, why: `${path} holds ${error.message} \u2014 this reader cannot read it` });
|
|
1834
|
+
continue;
|
|
1835
|
+
}
|
|
1836
|
+
findings.push(...foreignTables(path, sql, schema), ...undeclaredSettings(path, source, schema));
|
|
1513
1837
|
}
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
rmSync2(at, { force: true, recursive: true });
|
|
1517
|
-
const tree = join4(at, "tree");
|
|
1518
|
-
const { bytes, files } = copyInto(from, tree, new Set(excluded));
|
|
1519
|
-
const manifests = manifestsUnder2(tree);
|
|
1520
|
-
const record = {
|
|
1521
|
-
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1522
|
-
bytes,
|
|
1523
|
-
commands: commandsFor(readManifest(join4(tree, "package.json")), manifests, input.named),
|
|
1524
|
-
excluded,
|
|
1838
|
+
return {
|
|
1839
|
+
dirs,
|
|
1525
1840
|
files,
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
name: input.name
|
|
1841
|
+
findings: findings.toSorted((a, b) => a.path.localeCompare(b.path) || a.line - b.line),
|
|
1842
|
+
ok: findings.length === 0 && unreadable.length === 0,
|
|
1843
|
+
read: files.length - unreadable.length,
|
|
1844
|
+
unreadable
|
|
1531
1845
|
};
|
|
1532
|
-
writeFileSync2(join4(at, "snapshot.json"), `${JSON.stringify(record, void 0, 2)}
|
|
1533
|
-
`);
|
|
1534
|
-
return record;
|
|
1535
|
-
};
|
|
1536
|
-
var describeCommand = (command) => {
|
|
1537
|
-
if ("run" in command) return `run ${command.run}`;
|
|
1538
|
-
if ("exec" in command) return command.exec;
|
|
1539
|
-
return `nothing \u2014 ${command.why}`;
|
|
1540
|
-
};
|
|
1541
|
-
var formatSnapshot = (record) => [
|
|
1542
|
-
`OK snapshot ${record.name}: ${record.files} files, ${record.bytes} bytes, ${record.manifests.length} manifests
|
|
1543
|
-
`,
|
|
1544
|
-
` from ${record.from} \u2014 ${record.manager} (${record.lockfile})
|
|
1545
|
-
`,
|
|
1546
|
-
...PHASES.map((phase) => ` ${phase}: ${describeCommand(record.commands[phase])}
|
|
1547
|
-
`)
|
|
1548
|
-
].join("");
|
|
1549
|
-
|
|
1550
|
-
// src/envelope.ts
|
|
1551
|
-
import { mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
|
|
1552
|
-
import { dirname as dirname2, join as join5 } from "path";
|
|
1553
|
-
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1554
|
-
var ENVELOPES_DIR = ".geonosis/envelopes";
|
|
1555
|
-
var envelopePath = (root, tool) => join5(root, ENVELOPES_DIR, `${tool}.json`);
|
|
1556
|
-
var UnbalancedEnvelope = class extends Error {
|
|
1557
|
-
constructor(message) {
|
|
1558
|
-
super(message);
|
|
1559
|
-
this.name = "UnbalancedEnvelope";
|
|
1560
|
-
}
|
|
1561
1846
|
};
|
|
1562
|
-
var
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
next,
|
|
1567
|
-
root
|
|
1568
|
-
}) => {
|
|
1569
|
-
if (envelope.tool.trim() === "") {
|
|
1570
|
-
throw new UnbalancedEnvelope(
|
|
1571
|
-
`an envelope with no tool name cannot be filed or reported against. Next: ${next}`
|
|
1572
|
-
);
|
|
1573
|
-
}
|
|
1574
|
-
if (envelope.version.trim() === "") {
|
|
1575
|
-
throw new UnbalancedEnvelope(
|
|
1576
|
-
`${envelope.tool}: an envelope that cannot name the build that wrote it dates nothing, and a stale one reads exactly like a fresh one. Next: ${next}`
|
|
1577
|
-
);
|
|
1578
|
-
}
|
|
1579
|
-
if (!isCount(envelope.considered) || !isCount(envelope.read)) {
|
|
1580
|
-
throw new UnbalancedEnvelope(
|
|
1581
|
-
`${envelope.tool}: considered ${envelope.considered} and read ${envelope.read} \u2014 a census is a whole number of things, and arithmetic over anything else balances by accident. Next: ${next}`
|
|
1582
|
-
);
|
|
1847
|
+
var formatSchema = (report) => {
|
|
1848
|
+
if (report.files.length === 0) {
|
|
1849
|
+
return `NONE schema: no migration file under ${report.dirs.join(", ")}; nothing was read
|
|
1850
|
+
`;
|
|
1583
1851
|
}
|
|
1584
|
-
if (
|
|
1585
|
-
|
|
1852
|
+
if (report.ok) {
|
|
1853
|
+
return `OK schema: ${report.read} migration(s) read under ${report.dirs.length} declared domain(s), no wall crossed
|
|
1854
|
+
`;
|
|
1586
1855
|
}
|
|
1587
|
-
const
|
|
1588
|
-
mkdirSync2(dirname2(at), { recursive: true });
|
|
1589
|
-
writeFileSync3(at, `${JSON.stringify(envelope, void 0, 2)}
|
|
1856
|
+
const said = report.findings.map((one) => ` ${one.path}:${one.line}: ${one.why}
|
|
1590
1857
|
`);
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
try {
|
|
1596
|
-
const manifest = JSON.parse(readFileSync8(join5(dir, "package.json"), "utf8"));
|
|
1597
|
-
return typeof manifest.version === "string" ? manifest.version : void 0;
|
|
1598
|
-
} catch {
|
|
1599
|
-
return void 0;
|
|
1600
|
-
}
|
|
1601
|
-
};
|
|
1602
|
-
var versionOf = (moduleUrl) => {
|
|
1603
|
-
let dir = dirname2(fileURLToPath3(moduleUrl));
|
|
1604
|
-
for (; ; ) {
|
|
1605
|
-
const found = versionIn(dir);
|
|
1606
|
-
if (found !== void 0) return found;
|
|
1607
|
-
const up = dirname2(dir);
|
|
1608
|
-
if (up === dir) return UNKNOWN;
|
|
1609
|
-
dir = up;
|
|
1610
|
-
}
|
|
1858
|
+
const cannot = report.unreadable.map((one) => ` ${one.why}
|
|
1859
|
+
`);
|
|
1860
|
+
return `${said.join("")}${cannot.join("")}schema: ${report.read} of ${report.files.length} files read \u2014 ${report.findings.length} finding(s)${report.unreadable.length === 0 ? "" : `, ${report.unreadable.length} unreadable`}
|
|
1861
|
+
`;
|
|
1611
1862
|
};
|
|
1612
|
-
var MIGRATIONS_TOOL = "release-migrations";
|
|
1613
|
-
var MIGRATIONS_NEXT = "geonosis-release migrations --since <ref> --json and compare `files` against `unreadable` \u2014 every added file leaves by exactly one door, and a file in one list and not the other is the accounting bug this refuses over";
|
|
1614
|
-
var migrationsEnvelope = (report, durationMs) => ({
|
|
1615
|
-
considered: report.files.length,
|
|
1616
|
-
durationMs,
|
|
1617
|
-
excused: report.files.filter((one) => one.state === "excused").map((one) => ({
|
|
1618
|
-
path: one.path,
|
|
1619
|
-
reason: "a contract-migration marker this gate could believe"
|
|
1620
|
-
})),
|
|
1621
|
-
findings: [...report.refusals, ...report.markers, ...report.squawk],
|
|
1622
|
-
read: report.files.filter((one) => one.state !== "excused" && one.state !== "unreadable").length,
|
|
1623
|
-
refused: report.unreadable.map((one) => ({ path: one.path, reason: one.why })),
|
|
1624
|
-
tool: MIGRATIONS_TOOL,
|
|
1625
|
-
version: versionOf(import.meta.url)
|
|
1626
|
-
});
|
|
1627
|
-
var SCHEMA_TOOL = "release-schema";
|
|
1628
|
-
var SCHEMA_NEXT = "geonosis-release schema --json and compare `files` against `unreadable` \u2014 the denominator is every migration under every declared domain directory, and a file in neither list is one nothing read (#168)";
|
|
1629
|
-
var schemaEnvelope = (report, durationMs) => ({
|
|
1630
|
-
considered: report.files.length,
|
|
1631
|
-
durationMs,
|
|
1632
|
-
excused: [],
|
|
1633
|
-
findings: report.findings,
|
|
1634
|
-
read: report.read,
|
|
1635
|
-
refused: report.unreadable.map((one) => ({ path: one.path, reason: one.why })),
|
|
1636
|
-
tool: SCHEMA_TOOL,
|
|
1637
|
-
version: versionOf(import.meta.url)
|
|
1638
|
-
});
|
|
1639
|
-
var PUBLISHED_TOOL = "release-published";
|
|
1640
|
-
var PUBLISHED_NEXT = "geonosis-release published --group --json and compare `considered` against `lines` + `excused` \u2014 every manifest in the tree leaves by exactly one door, and a group announced off a sample is the failure this counts against (#147)";
|
|
1641
|
-
var publishedEnvelope = (report, durationMs) => ({
|
|
1642
|
-
considered: report.considered,
|
|
1643
|
-
durationMs,
|
|
1644
|
-
excused: report.excused,
|
|
1645
|
-
findings: report.lines.filter((one) => one.verdict !== "MATCH"),
|
|
1646
|
-
read: report.lines.filter((one) => one.verdict !== "UNREACHABLE").length,
|
|
1647
|
-
refused: report.lines.filter((one) => one.verdict === "UNREACHABLE").map((one) => ({ path: one.at, reason: one.why })),
|
|
1648
|
-
tool: PUBLISHED_TOOL,
|
|
1649
|
-
version: versionOf(import.meta.url)
|
|
1650
|
-
});
|
|
1651
1863
|
|
|
1652
1864
|
// src/smoke-run.ts
|
|
1653
1865
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
1654
|
-
import { existsSync as
|
|
1655
|
-
import { join as
|
|
1866
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync10, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
1867
|
+
import { join as join7, relative as relative4, resolve as resolve8 } from "path";
|
|
1656
1868
|
var BASELINE_FILE = "baseline.json";
|
|
1657
1869
|
var INSTALL = {
|
|
1658
1870
|
bun: ["install"],
|
|
1659
1871
|
pnpm: ["install", "--no-frozen-lockfile"]
|
|
1660
1872
|
};
|
|
1873
|
+
var MANAGER_CONFIG = [".npmrc", "bunfig.toml", "pnpm-workspace.yaml"];
|
|
1874
|
+
var configBytesOf = (work) => Object.fromEntries(
|
|
1875
|
+
MANAGER_CONFIG.filter((file) => existsSync7(join7(work, file))).map((file) => [
|
|
1876
|
+
file,
|
|
1877
|
+
readFileSync10(join7(work, file), "utf8")
|
|
1878
|
+
])
|
|
1879
|
+
);
|
|
1880
|
+
var editedByInstall = (before, after) => [.../* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])].filter((file) => before[file] !== after[file]).toSorted();
|
|
1661
1881
|
var isRecord5 = (value2) => typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
|
|
1662
1882
|
var readJson = (path) => {
|
|
1663
1883
|
try {
|
|
1664
|
-
return JSON.parse(
|
|
1884
|
+
return JSON.parse(readFileSync10(path, "utf8"));
|
|
1665
1885
|
} catch (error) {
|
|
1666
1886
|
throw new CannotRun(`${path} is not readable JSON: ${error.message}`);
|
|
1667
1887
|
}
|
|
1668
1888
|
};
|
|
1669
1889
|
var packRc = (rc, into) => {
|
|
1670
|
-
if (!
|
|
1890
|
+
if (!existsSync7(join7(rc, "package.json"))) {
|
|
1671
1891
|
throw new CannotRun(`${rc} has no package.json \u2014 that is not a workspace to pack`);
|
|
1672
1892
|
}
|
|
1673
1893
|
mkdirSync3(into, { recursive: true });
|
|
1674
|
-
const root = readJson(
|
|
1894
|
+
const root = readJson(join7(rc, "package.json"));
|
|
1675
1895
|
const isPrivateRoot = isRecord5(root) && root["private"] === true;
|
|
1676
1896
|
const done = spawnSync4(
|
|
1677
1897
|
"pnpm",
|
|
@@ -1721,11 +1941,11 @@ var sourceManifests = (rc) => {
|
|
|
1721
1941
|
for (const entry of readdirSync4(dir, { withFileTypes: true })) {
|
|
1722
1942
|
if (excluded.has(entry.name)) continue;
|
|
1723
1943
|
if (entry.isDirectory()) {
|
|
1724
|
-
walk(
|
|
1944
|
+
walk(join7(dir, entry.name));
|
|
1725
1945
|
continue;
|
|
1726
1946
|
}
|
|
1727
1947
|
if (entry.name !== "package.json") continue;
|
|
1728
|
-
const manifest = readJson(
|
|
1948
|
+
const manifest = readJson(join7(dir, entry.name));
|
|
1729
1949
|
if (!isRecord5(manifest) || typeof manifest["name"] !== "string") continue;
|
|
1730
1950
|
found.push({ manifest, name: manifest["name"] });
|
|
1731
1951
|
}
|
|
@@ -1747,9 +1967,9 @@ var OVERRIDES = "overrides:";
|
|
|
1747
1967
|
var TOP_LEVEL = /^\S/;
|
|
1748
1968
|
var KEYED = /^\s+["']?(.+?)["']?\s*:/;
|
|
1749
1969
|
var pinsInWorkspaceYaml = (work, pins) => {
|
|
1750
|
-
const at =
|
|
1970
|
+
const at = join7(work, "pnpm-workspace.yaml");
|
|
1751
1971
|
const written = Object.entries(pins).map(([name, spec]) => ` '${name}': '${spec}'`);
|
|
1752
|
-
const lines2 =
|
|
1972
|
+
const lines2 = existsSync7(at) ? readFileSync10(at, "utf8").split("\n") : [];
|
|
1753
1973
|
const kept = [];
|
|
1754
1974
|
let inside = false;
|
|
1755
1975
|
let found = false;
|
|
@@ -1768,8 +1988,8 @@ var pinsInWorkspaceYaml = (work, pins) => {
|
|
|
1768
1988
|
writeFileSync4(at, body.join("\n"));
|
|
1769
1989
|
};
|
|
1770
1990
|
var pinsInManifest = (work, pins) => {
|
|
1771
|
-
const at =
|
|
1772
|
-
if (!
|
|
1991
|
+
const at = join7(work, "package.json");
|
|
1992
|
+
if (!existsSync7(at)) return;
|
|
1773
1993
|
const manifest = readJson(at);
|
|
1774
1994
|
if (!isRecord5(manifest)) return;
|
|
1775
1995
|
manifest["overrides"] = {
|
|
@@ -1789,8 +2009,8 @@ var rewriteManifests = (tree, manifests, packed) => {
|
|
|
1789
2009
|
const byName = new Map(packed.map((one) => [one.name, one.tarball]));
|
|
1790
2010
|
const swapped = [];
|
|
1791
2011
|
for (const path of manifests) {
|
|
1792
|
-
const at =
|
|
1793
|
-
if (!
|
|
2012
|
+
const at = join7(tree, path);
|
|
2013
|
+
if (!existsSync7(at)) continue;
|
|
1794
2014
|
const manifest = readJson(at);
|
|
1795
2015
|
if (!isRecord5(manifest)) continue;
|
|
1796
2016
|
let touched = false;
|
|
@@ -1826,19 +2046,25 @@ var runPhase = (phase, command, cwd, manager) => {
|
|
|
1826
2046
|
const line = commandLine(command, manager);
|
|
1827
2047
|
const env = {
|
|
1828
2048
|
...process.env,
|
|
1829
|
-
PATH: `${
|
|
2049
|
+
PATH: `${join7(cwd, "node_modules/.bin")}:${process.env["PATH"] ?? ""}`
|
|
1830
2050
|
};
|
|
1831
2051
|
const { code, output } = shellRun(line, cwd, env);
|
|
1832
2052
|
return { code, command: line, lines: quotable(output), ok: code === 0, phase };
|
|
1833
2053
|
};
|
|
2054
|
+
var commandsToRun = (record, declared) => Object.fromEntries(
|
|
2055
|
+
PHASES.map((phase) => {
|
|
2056
|
+
const named = declared[phase];
|
|
2057
|
+
return [phase, named === void 0 ? record.commands[phase] : { exec: named }];
|
|
2058
|
+
})
|
|
2059
|
+
);
|
|
1834
2060
|
var smokeOver = (input) => {
|
|
1835
2061
|
const record = readSnapshot(input.root, input.name);
|
|
1836
2062
|
const at = snapshotDir(input.root, input.name);
|
|
1837
|
-
const work =
|
|
2063
|
+
const work = join7(at, "run");
|
|
1838
2064
|
rmSync3(work, { force: true, recursive: true });
|
|
1839
|
-
copyInto(
|
|
2065
|
+
copyInto(join7(at, "tree"), work, new Set(EXCLUDED_DIRS));
|
|
1840
2066
|
ownRepository(work);
|
|
1841
|
-
const packed = input.rc === void 0 ? [] : packRc(resolve8(input.rc),
|
|
2067
|
+
const packed = input.rc === void 0 ? [] : packRc(resolve8(input.rc), join7(at, "rc"));
|
|
1842
2068
|
const findings = input.rc === void 0 ? [] : promisedButNotPacked(resolve8(input.rc), packed);
|
|
1843
2069
|
const swapped = rewriteManifests(
|
|
1844
2070
|
work,
|
|
@@ -1852,6 +2078,7 @@ var smokeOver = (input) => {
|
|
|
1852
2078
|
}
|
|
1853
2079
|
overrideEveryCopy(work, record.manager, packed);
|
|
1854
2080
|
const install = input.install ?? `${record.manager} ${INSTALL[record.manager].join(" ")}`;
|
|
2081
|
+
const configBefore = configBytesOf(work);
|
|
1855
2082
|
const installed = shellRun(install, work, process.env);
|
|
1856
2083
|
if (installed.code !== 0) {
|
|
1857
2084
|
throw new CannotRun(
|
|
@@ -1859,9 +2086,15 @@ var smokeOver = (input) => {
|
|
|
1859
2086
|
${quotable(installed.output).join("\n")}`
|
|
1860
2087
|
);
|
|
1861
2088
|
}
|
|
2089
|
+
for (const file of editedByInstall(configBefore, configBytesOf(work))) {
|
|
2090
|
+
findings.push(
|
|
2091
|
+
`\`${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`
|
|
2092
|
+
);
|
|
2093
|
+
}
|
|
2094
|
+
const commands = commandsToRun(record, input.commands ?? {});
|
|
1862
2095
|
return {
|
|
1863
2096
|
findings,
|
|
1864
|
-
outcomes: PHASES.map((phase) => runPhase(phase,
|
|
2097
|
+
outcomes: PHASES.map((phase) => runPhase(phase, commands[phase], work, record.manager)),
|
|
1865
2098
|
packed
|
|
1866
2099
|
};
|
|
1867
2100
|
};
|
|
@@ -1873,7 +2106,7 @@ var ownRepository = (work) => {
|
|
|
1873
2106
|
);
|
|
1874
2107
|
}
|
|
1875
2108
|
};
|
|
1876
|
-
var baselinePath = (root, name) =>
|
|
2109
|
+
var baselinePath = (root, name) => join7(snapshotDir(root, name), BASELINE_FILE);
|
|
1877
2110
|
var recordBaseline = (input) => {
|
|
1878
2111
|
const { outcomes, packed } = smokeOver(input);
|
|
1879
2112
|
const baseline = {
|
|
@@ -1888,7 +2121,7 @@ var recordBaseline = (input) => {
|
|
|
1888
2121
|
};
|
|
1889
2122
|
var readBaseline = (root, name) => {
|
|
1890
2123
|
const at = baselinePath(root, name);
|
|
1891
|
-
if (!
|
|
2124
|
+
if (!existsSync7(at)) {
|
|
1892
2125
|
throw new CannotRun(
|
|
1893
2126
|
`"${name}" has no baseline \u2014 a run that took its own answer as the standard would certify anything. Record one against the versions this consumer is on: geonosis-release smoke baseline --snapshot ${name}`
|
|
1894
2127
|
);
|
|
@@ -1942,57 +2175,118 @@ var runSmoke = (input) => {
|
|
|
1942
2175
|
const { findings, outcomes } = smokeOver(input);
|
|
1943
2176
|
return compareToBaseline(input.name, baseline, outcomes, findings);
|
|
1944
2177
|
};
|
|
2178
|
+
var CONSUMER_TREES_FILE = ".geonosis/consumer-trees.local.json";
|
|
2179
|
+
var localTrees = (root) => {
|
|
2180
|
+
const at = join7(root, CONSUMER_TREES_FILE);
|
|
2181
|
+
if (!existsSync7(at)) return {};
|
|
2182
|
+
const parsed = readJson(at);
|
|
2183
|
+
if (!isRecord5(parsed)) {
|
|
2184
|
+
throw new CannotRun(`${at} must be an object of { "<snapshot name>": "<absolute path>" }`);
|
|
2185
|
+
}
|
|
2186
|
+
return Object.fromEntries(
|
|
2187
|
+
Object.entries(parsed).filter((one) => typeof one[1] === "string")
|
|
2188
|
+
);
|
|
2189
|
+
};
|
|
2190
|
+
var recordedAlready = (root, name) => existsSync7(join7(snapshotDir(root, name), "snapshot.json"));
|
|
2191
|
+
var unrecordedButHere = (root, wanted, absent) => {
|
|
2192
|
+
const trees = localTrees(root);
|
|
2193
|
+
return wanted.filter((one) => !absent.includes(one.name) && !recordedAlready(root, one.name)).flatMap((one) => {
|
|
2194
|
+
const path = trees[one.name];
|
|
2195
|
+
if (path === void 0 || !existsSync7(path)) return [];
|
|
2196
|
+
return [
|
|
2197
|
+
`${one.name} is declared and its tree is here (${path}) \u2014 record it: geonosis-release smoke snapshot ${one.name} --from ${path}`
|
|
2198
|
+
];
|
|
2199
|
+
});
|
|
2200
|
+
};
|
|
2201
|
+
var dating = (root, name) => {
|
|
2202
|
+
const record = readSnapshot(root, name);
|
|
2203
|
+
const commit = record.commit;
|
|
2204
|
+
if (commit === void 0) {
|
|
2205
|
+
return {
|
|
2206
|
+
undated: `UNDATED ${name}: recorded before source commits were kept \u2014 re-record it: geonosis-release smoke snapshot ${name} --from ${record.from}`
|
|
2207
|
+
};
|
|
2208
|
+
}
|
|
2209
|
+
if (!("sha" in commit) || !existsSync7(record.from)) return void 0;
|
|
2210
|
+
const now = headOf(record.from);
|
|
2211
|
+
if (!("sha" in now) || now.sha === commit.sha) return void 0;
|
|
2212
|
+
return { stale: `STALE ${name}: recorded at ${commit.sha}, the tree is at ${now.sha}` };
|
|
2213
|
+
};
|
|
1945
2214
|
var sweepSmoke = (input) => {
|
|
1946
|
-
const
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
if (
|
|
1950
|
-
|
|
2215
|
+
const absent = input.absent ?? [];
|
|
2216
|
+
if (input.skipMissing) {
|
|
2217
|
+
const owed = unrecordedButHere(input.root, input.wanted, absent);
|
|
2218
|
+
if (owed.length > 0) throw new CannotRun(owed.join("\n"));
|
|
2219
|
+
}
|
|
2220
|
+
const entries = input.wanted.map((one) => {
|
|
2221
|
+
if (absent.includes(one.name)) {
|
|
2222
|
+
return {
|
|
2223
|
+
name: one.name,
|
|
2224
|
+
why: "--absent named it, so this release was not read against it"
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
if (input.skipMissing && !recordedAlready(input.root, one.name)) {
|
|
2228
|
+
return {
|
|
1951
2229
|
name: one.name,
|
|
1952
2230
|
why: `no snapshot of it here \u2014 record one with: geonosis-release smoke snapshot ${one.name} --from <their tree>`
|
|
1953
|
-
}
|
|
1954
|
-
continue;
|
|
2231
|
+
};
|
|
1955
2232
|
}
|
|
1956
|
-
|
|
1957
|
-
|
|
2233
|
+
const note = dating(input.root, one.name);
|
|
2234
|
+
return {
|
|
2235
|
+
comparison: runSmoke({
|
|
2236
|
+
...one.commands === void 0 ? {} : { commands: one.commands },
|
|
1958
2237
|
...one.install === void 0 ? {} : { install: one.install },
|
|
1959
2238
|
name: one.name,
|
|
1960
2239
|
...input.rc === void 0 ? {} : { rc: input.rc },
|
|
1961
2240
|
root: input.root
|
|
1962
|
-
})
|
|
1963
|
-
|
|
2241
|
+
}),
|
|
2242
|
+
name: one.name,
|
|
2243
|
+
...note === void 0 ? {} : { note }
|
|
2244
|
+
};
|
|
2245
|
+
});
|
|
2246
|
+
return { entries };
|
|
2247
|
+
};
|
|
2248
|
+
var comparisonsIn = (sweep) => sweep.entries.flatMap((one) => "comparison" in one ? [one.comparison] : []);
|
|
2249
|
+
var EXCUSED = "EXCUSED";
|
|
2250
|
+
var noteLine = (note) => "stale" in note ? note.stale : note.undated;
|
|
2251
|
+
var entryBlock = (entry) => {
|
|
2252
|
+
if (!("comparison" in entry)) {
|
|
2253
|
+
const head = entry.why.startsWith("--absent") ? EXCUSED : "SKIP ";
|
|
2254
|
+
return `${head} smoke ${entry.name}: ${entry.why}
|
|
2255
|
+
`;
|
|
1964
2256
|
}
|
|
1965
|
-
return {
|
|
2257
|
+
return `${formatSmoke(entry.comparison)}${entry.note === void 0 ? "" : `${noteLine(entry.note)}
|
|
2258
|
+
`}`;
|
|
1966
2259
|
};
|
|
1967
|
-
var formatSweep = (sweep) =>
|
|
1968
|
-
...sweep.comparisons.map(formatSmoke),
|
|
1969
|
-
...sweep.skipped.map((one) => `SKIP smoke ${one.name}: ${one.why}
|
|
1970
|
-
`)
|
|
1971
|
-
].join("");
|
|
2260
|
+
var formatSweep = (sweep) => sweep.entries.map(entryBlock).join("");
|
|
1972
2261
|
var sweepEnvelope = (sweep, durationMs) => {
|
|
1973
|
-
const
|
|
2262
|
+
const excused = [];
|
|
2263
|
+
const findings = [];
|
|
2264
|
+
const refused = [];
|
|
2265
|
+
let considered = 0;
|
|
2266
|
+
let read = 0;
|
|
2267
|
+
for (const entry of sweep.entries) {
|
|
2268
|
+
if (!("comparison" in entry)) {
|
|
2269
|
+
considered += PHASES.length;
|
|
2270
|
+
excused.push(
|
|
2271
|
+
...PHASES.map((phase) => ({ path: `${entry.name}/${phase}`, reason: entry.why }))
|
|
2272
|
+
);
|
|
2273
|
+
continue;
|
|
2274
|
+
}
|
|
2275
|
+
const part = smokeEnvelope(entry.comparison, 0);
|
|
2276
|
+
considered += part.considered;
|
|
2277
|
+
read += part.read;
|
|
2278
|
+
excused.push(...part.excused.map((one) => ({ ...one, path: `${entry.name}/${one.path}` })));
|
|
2279
|
+
refused.push(...part.refused.map((one) => ({ ...one, path: `${entry.name}/${one.path}` })));
|
|
2280
|
+
findings.push(...part.findings);
|
|
2281
|
+
if (entry.note !== void 0) findings.push(entry.note);
|
|
2282
|
+
}
|
|
1974
2283
|
return {
|
|
1975
|
-
considered
|
|
2284
|
+
considered,
|
|
1976
2285
|
durationMs,
|
|
1977
|
-
excused
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
reason: entry.reason
|
|
1982
|
-
}))
|
|
1983
|
-
),
|
|
1984
|
-
...sweep.skipped.flatMap(
|
|
1985
|
-
(one) => PHASES.map((phase) => ({ path: `${one.name}/${phase}`, reason: one.why }))
|
|
1986
|
-
)
|
|
1987
|
-
],
|
|
1988
|
-
findings: parts.flatMap((one) => one.findings),
|
|
1989
|
-
read: parts.reduce((sum, one) => sum + one.read, 0),
|
|
1990
|
-
refused: parts.flatMap(
|
|
1991
|
-
(one, index) => one.refused.map((entry) => ({
|
|
1992
|
-
path: `${sweep.comparisons[index]?.name ?? ""}/${entry.path}`,
|
|
1993
|
-
reason: entry.reason
|
|
1994
|
-
}))
|
|
1995
|
-
),
|
|
2286
|
+
excused,
|
|
2287
|
+
findings,
|
|
2288
|
+
read,
|
|
2289
|
+
refused,
|
|
1996
2290
|
tool: SMOKE_TOOL,
|
|
1997
2291
|
version: versionOf(import.meta.url)
|
|
1998
2292
|
};
|
|
@@ -2065,6 +2359,33 @@ export {
|
|
|
2065
2359
|
CannotRun,
|
|
2066
2360
|
parseReleaseConfig,
|
|
2067
2361
|
readReleaseConfig,
|
|
2362
|
+
writeEnvelope,
|
|
2363
|
+
MIGRATIONS_NEXT,
|
|
2364
|
+
migrationsEnvelope,
|
|
2365
|
+
SCHEMA_NEXT,
|
|
2366
|
+
schemaEnvelope,
|
|
2367
|
+
PUBLISHED_NEXT,
|
|
2368
|
+
publishedEnvelope,
|
|
2369
|
+
DEFAULT_REGISTRY,
|
|
2370
|
+
registryPathOf,
|
|
2371
|
+
askRegistry,
|
|
2372
|
+
censusOf,
|
|
2373
|
+
EXISTS_BUT,
|
|
2374
|
+
runPublished,
|
|
2375
|
+
formatPublished,
|
|
2376
|
+
SNAPSHOTS_DIR,
|
|
2377
|
+
EXCLUDED_DIRS,
|
|
2378
|
+
PHASES,
|
|
2379
|
+
headOf,
|
|
2380
|
+
snapshotDir,
|
|
2381
|
+
readSnapshot,
|
|
2382
|
+
runSnapshot,
|
|
2383
|
+
formatSnapshot,
|
|
2384
|
+
runAdoption,
|
|
2385
|
+
formatAdoption,
|
|
2386
|
+
ADOPTION_TOOL,
|
|
2387
|
+
ADOPTION_NEXT,
|
|
2388
|
+
adoptionEnvelope,
|
|
2068
2389
|
parseJsonc,
|
|
2069
2390
|
parseToml,
|
|
2070
2391
|
declaredIn,
|
|
@@ -2092,29 +2413,8 @@ export {
|
|
|
2092
2413
|
prove,
|
|
2093
2414
|
formatProve,
|
|
2094
2415
|
formatVerdict,
|
|
2095
|
-
DEFAULT_REGISTRY,
|
|
2096
|
-
registryPathOf,
|
|
2097
|
-
askRegistry,
|
|
2098
|
-
censusOf,
|
|
2099
|
-
EXISTS_BUT,
|
|
2100
|
-
runPublished,
|
|
2101
|
-
formatPublished,
|
|
2102
2416
|
runSchema,
|
|
2103
2417
|
formatSchema,
|
|
2104
|
-
SNAPSHOTS_DIR,
|
|
2105
|
-
EXCLUDED_DIRS,
|
|
2106
|
-
PHASES,
|
|
2107
|
-
snapshotDir,
|
|
2108
|
-
readSnapshot,
|
|
2109
|
-
runSnapshot,
|
|
2110
|
-
formatSnapshot,
|
|
2111
|
-
writeEnvelope,
|
|
2112
|
-
MIGRATIONS_NEXT,
|
|
2113
|
-
migrationsEnvelope,
|
|
2114
|
-
SCHEMA_NEXT,
|
|
2115
|
-
schemaEnvelope,
|
|
2116
|
-
PUBLISHED_NEXT,
|
|
2117
|
-
publishedEnvelope,
|
|
2118
2418
|
BASELINE_FILE,
|
|
2119
2419
|
packRc,
|
|
2120
2420
|
promisedButNotPacked,
|
|
@@ -2123,7 +2423,9 @@ export {
|
|
|
2123
2423
|
readBaseline,
|
|
2124
2424
|
compareToBaseline,
|
|
2125
2425
|
runSmoke,
|
|
2426
|
+
CONSUMER_TREES_FILE,
|
|
2126
2427
|
sweepSmoke,
|
|
2428
|
+
comparisonsIn,
|
|
2127
2429
|
formatSweep,
|
|
2128
2430
|
sweepEnvelope,
|
|
2129
2431
|
formatSmoke,
|