@geonosis/release 1.2.0 → 1.4.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/README.md +18 -4
- package/dist/{chunk-BOTEFN3W.js → chunk-D5IC766I.js} +34 -8
- package/dist/index.js +1 -1
- package/dist/release-cli.js +113 -2
- package/package.json +11 -2
package/README.md
CHANGED
|
@@ -101,11 +101,25 @@ An **identifier** argument (`this.addSql(dropOperationChecks)`) and a **template
|
|
|
101
101
|
are refused BY NAME with exit 2. Resolving either is name resolution — a TypeScript parser — and
|
|
102
102
|
this package will not grow one. A migration nobody could read is not a migration that passed.
|
|
103
103
|
|
|
104
|
-
### squawk
|
|
104
|
+
### squawk — an OPTIONAL peer
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
```bash
|
|
107
|
+
pnpm add -D squawk-cli@2.63.0 # only if release.migrations names postgres or mikro-orm-ts
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`squawk-cli` ships platform binaries, and as a hard dependency of this package it grew one
|
|
111
|
+
consumer's lockfile by **395 lines** for a Postgres linter that repo never configured. It is a peer
|
|
112
|
+
marked optional: nothing installs it for you.
|
|
113
|
+
|
|
114
|
+
- a `release.migrations` naming only the **`sqlite`** dialect never calls it, and needs none of it;
|
|
115
|
+
- a `postgres` or `mikro-orm-ts` dialect with squawk absent is **exit 2**, with the install line
|
|
116
|
+
above — the dialect cannot be measured, and a gate that cannot measure has not passed.
|
|
117
|
+
|
|
118
|
+
It is pinned exactly, because a squawk that renamed a rule would change what a release argues from,
|
|
119
|
+
and that is a decision rather than an install. It is spawned from the `node_modules/.bin` beside
|
|
120
|
+
this package: its bin is called `squawk`, and `squawk` on a public registry is an unrelated package
|
|
121
|
+
that exits 0 on anything — a gate that passes because it linted nothing. Nothing here asks a
|
|
122
|
+
resolver.
|
|
109
123
|
|
|
110
124
|
Exit 0 is clean and exit 1 is findings. **Anything else is exit 2**, one line naming it: a linter
|
|
111
125
|
that could not start has not measured, and a gate that cannot measure has not passed.
|
|
@@ -12,26 +12,33 @@ var strings = (value2, where) => {
|
|
|
12
12
|
}
|
|
13
13
|
return value2;
|
|
14
14
|
};
|
|
15
|
+
var ENTRY_KEYS = "expected { dir, dialect, phases?, squawk? }";
|
|
15
16
|
var oneDir = (value2, index) => {
|
|
16
|
-
|
|
17
|
+
const at = `release.migrations[${index}]`;
|
|
18
|
+
if (!isRecord(value2)) throw new CannotRun(`${at} must be an object \u2014 ${ENTRY_KEYS}`);
|
|
19
|
+
const known = /* @__PURE__ */ new Set(["dialect", "dir", "phases", "squawk"]);
|
|
20
|
+
const unknown = Object.keys(value2).find((key) => !known.has(key));
|
|
21
|
+
if (unknown !== void 0) {
|
|
22
|
+
throw new CannotRun(`${at}.${unknown} is not a key it takes \u2014 ${ENTRY_KEYS}`);
|
|
23
|
+
}
|
|
17
24
|
const dir = value2["dir"];
|
|
18
25
|
const dialect = value2["dialect"];
|
|
19
26
|
if (typeof dir !== "string" || dir === "") {
|
|
20
|
-
throw new CannotRun(
|
|
27
|
+
throw new CannotRun(`${at}.dir must name a directory \u2014 ${ENTRY_KEYS}`);
|
|
21
28
|
}
|
|
22
29
|
if (typeof dialect !== "string" || !DIALECTS.has(dialect)) {
|
|
23
30
|
throw new CannotRun(
|
|
24
|
-
|
|
31
|
+
`${at}.dialect must be one of ${[...DIALECTS].toSorted().join(", ")} \u2014 ${ENTRY_KEYS}`
|
|
25
32
|
);
|
|
26
33
|
}
|
|
27
|
-
const phases = strings(value2["phases"],
|
|
34
|
+
const phases = strings(value2["phases"], `${at}.phases`);
|
|
28
35
|
for (const phase of phases) {
|
|
29
36
|
if (phase !== "up" && phase !== "down") {
|
|
30
|
-
throw new CannotRun(
|
|
37
|
+
throw new CannotRun(`${at}.phases may only hold "up" and "down"`);
|
|
31
38
|
}
|
|
32
39
|
}
|
|
33
40
|
const squawk = value2["squawk"];
|
|
34
|
-
const exclude = isRecord(squawk) ? strings(squawk["exclude"],
|
|
41
|
+
const exclude = isRecord(squawk) ? strings(squawk["exclude"], `${at}.squawk.exclude`) : [];
|
|
35
42
|
return {
|
|
36
43
|
dialect,
|
|
37
44
|
dir,
|
|
@@ -49,13 +56,27 @@ var EMPTY = {
|
|
|
49
56
|
workers: [],
|
|
50
57
|
wrangler: []
|
|
51
58
|
};
|
|
59
|
+
var BLOCK_KEYS = "expected { migrations?, proof?, secrets?, steps?, workers?, wrangler?, wranglerEnv? }";
|
|
52
60
|
var parseReleaseConfig = (raw) => {
|
|
53
61
|
if (!isRecord(raw)) return EMPTY;
|
|
54
62
|
const release = raw["release"];
|
|
55
63
|
if (!isRecord(release)) return EMPTY;
|
|
64
|
+
const known = /* @__PURE__ */ new Set([
|
|
65
|
+
"migrations",
|
|
66
|
+
"proof",
|
|
67
|
+
"secrets",
|
|
68
|
+
"steps",
|
|
69
|
+
"workers",
|
|
70
|
+
"wrangler",
|
|
71
|
+
"wranglerEnv"
|
|
72
|
+
]);
|
|
73
|
+
const unknown = Object.keys(release).find((key) => !known.has(key));
|
|
74
|
+
if (unknown !== void 0) {
|
|
75
|
+
throw new CannotRun(`release.${unknown} is not a key it reads \u2014 ${BLOCK_KEYS}`);
|
|
76
|
+
}
|
|
56
77
|
const migrations = release["migrations"];
|
|
57
78
|
if (migrations !== void 0 && !Array.isArray(migrations)) {
|
|
58
|
-
throw new CannotRun(
|
|
79
|
+
throw new CannotRun(`release.migrations must be a list \u2014 ${ENTRY_KEYS}, per entry`);
|
|
59
80
|
}
|
|
60
81
|
const proof = release["proof"];
|
|
61
82
|
return {
|
|
@@ -571,6 +592,7 @@ import { existsSync as existsSync3 } from "fs";
|
|
|
571
592
|
import { dirname, resolve as resolve4 } from "path";
|
|
572
593
|
import { fileURLToPath } from "url";
|
|
573
594
|
var HERE = dirname(fileURLToPath(import.meta.url));
|
|
595
|
+
var PINNED = "2.63.0";
|
|
574
596
|
var findSquawk = (from = HERE) => {
|
|
575
597
|
for (let dir = from; ; dir = dirname(dir)) {
|
|
576
598
|
const candidate = resolve4(dir, "node_modules/.bin/squawk");
|
|
@@ -578,7 +600,11 @@ var findSquawk = (from = HERE) => {
|
|
|
578
600
|
if (dirname(dir) === dir) break;
|
|
579
601
|
}
|
|
580
602
|
throw new CannotRun(
|
|
581
|
-
|
|
603
|
+
`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:
|
|
604
|
+
|
|
605
|
+
pnpm add -D squawk-cli@${PINNED}
|
|
606
|
+
|
|
607
|
+
It is an optional peer: a repo whose release.migrations names only the sqlite dialect needs none of this.`
|
|
582
608
|
);
|
|
583
609
|
};
|
|
584
610
|
var squawkOn = (input) => {
|
package/dist/index.js
CHANGED
package/dist/release-cli.js
CHANGED
|
@@ -10,10 +10,90 @@ import {
|
|
|
10
10
|
runDeployed,
|
|
11
11
|
runMigrations,
|
|
12
12
|
runPlan
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-D5IC766I.js";
|
|
14
14
|
|
|
15
15
|
// src/release-cli.ts
|
|
16
16
|
import { resolve } from "path";
|
|
17
|
+
|
|
18
|
+
// src/envelope.ts
|
|
19
|
+
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
20
|
+
import { dirname, join } from "path";
|
|
21
|
+
import { fileURLToPath } from "url";
|
|
22
|
+
var ENVELOPES_DIR = ".geonosis/envelopes";
|
|
23
|
+
var envelopePath = (root, tool) => join(root, ENVELOPES_DIR, `${tool}.json`);
|
|
24
|
+
var UnbalancedEnvelope = class extends Error {
|
|
25
|
+
constructor(message) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = "UnbalancedEnvelope";
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
var isCount = (value) => Number.isSafeInteger(value) && value >= 0;
|
|
31
|
+
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}`;
|
|
32
|
+
var writeEnvelope = ({
|
|
33
|
+
envelope,
|
|
34
|
+
next,
|
|
35
|
+
root
|
|
36
|
+
}) => {
|
|
37
|
+
if (envelope.tool.trim() === "") {
|
|
38
|
+
throw new UnbalancedEnvelope(
|
|
39
|
+
`an envelope with no tool name cannot be filed or reported against. Next: ${next}`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
if (envelope.version.trim() === "") {
|
|
43
|
+
throw new UnbalancedEnvelope(
|
|
44
|
+
`${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}`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
if (!isCount(envelope.considered) || !isCount(envelope.read)) {
|
|
48
|
+
throw new UnbalancedEnvelope(
|
|
49
|
+
`${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}`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (envelope.considered !== envelope.read + envelope.refused.length + envelope.excused.length) {
|
|
53
|
+
throw new UnbalancedEnvelope(unbalancedMessage(envelope, next));
|
|
54
|
+
}
|
|
55
|
+
const at = envelopePath(root, envelope.tool);
|
|
56
|
+
mkdirSync(dirname(at), { recursive: true });
|
|
57
|
+
writeFileSync(at, `${JSON.stringify(envelope, void 0, 2)}
|
|
58
|
+
`);
|
|
59
|
+
return at;
|
|
60
|
+
};
|
|
61
|
+
var UNKNOWN = "unknown";
|
|
62
|
+
var versionIn = (dir) => {
|
|
63
|
+
try {
|
|
64
|
+
const manifest = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
65
|
+
return typeof manifest.version === "string" ? manifest.version : void 0;
|
|
66
|
+
} catch {
|
|
67
|
+
return void 0;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var versionOf = (moduleUrl) => {
|
|
71
|
+
let dir = dirname(fileURLToPath(moduleUrl));
|
|
72
|
+
for (; ; ) {
|
|
73
|
+
const found = versionIn(dir);
|
|
74
|
+
if (found !== void 0) return found;
|
|
75
|
+
const up = dirname(dir);
|
|
76
|
+
if (up === dir) return UNKNOWN;
|
|
77
|
+
dir = up;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
var MIGRATIONS_TOOL = "release-migrations";
|
|
81
|
+
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";
|
|
82
|
+
var migrationsEnvelope = (report, durationMs) => ({
|
|
83
|
+
considered: report.files.length,
|
|
84
|
+
durationMs,
|
|
85
|
+
excused: report.files.filter((one) => one.state === "excused").map((one) => ({
|
|
86
|
+
path: one.path,
|
|
87
|
+
reason: "a contract-migration marker this gate could believe"
|
|
88
|
+
})),
|
|
89
|
+
findings: [...report.refusals, ...report.markers, ...report.squawk],
|
|
90
|
+
read: report.files.filter((one) => one.state !== "excused" && one.state !== "unreadable").length,
|
|
91
|
+
refused: report.unreadable.map((one) => ({ path: one.path, reason: one.why })),
|
|
92
|
+
tool: MIGRATIONS_TOOL,
|
|
93
|
+
version: versionOf(import.meta.url)
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// src/release-cli.ts
|
|
17
97
|
var USAGE = `geonosis-release <command> [options]
|
|
18
98
|
|
|
19
99
|
migrations --since <ref> [--dialect <name>] [--root <dir>] [--json]
|
|
@@ -84,15 +164,24 @@ var migrations = (parsed, cwd) => {
|
|
|
84
164
|
if (dialect !== void 0 && !DIALECTS.has(dialect)) {
|
|
85
165
|
throw new CannotRun(`--dialect must be one of ${[...DIALECTS].toSorted().join(", ")}`);
|
|
86
166
|
}
|
|
167
|
+
const root = resolve(cwd, parsed.read["--root"] ?? cwd);
|
|
168
|
+
const startedAt = Date.now();
|
|
87
169
|
const report = runMigrations({
|
|
88
170
|
...dialect === void 0 ? {} : { dialect },
|
|
89
|
-
root
|
|
171
|
+
root,
|
|
90
172
|
since
|
|
91
173
|
});
|
|
92
174
|
process.stdout.write(
|
|
93
175
|
parsed.json ? `${JSON.stringify(report, void 0, 2)}
|
|
94
176
|
` : formatMigrations(report)
|
|
95
177
|
);
|
|
178
|
+
const at = writeEnvelope({
|
|
179
|
+
envelope: migrationsEnvelope(report, Date.now() - startedAt),
|
|
180
|
+
next: MIGRATIONS_NEXT,
|
|
181
|
+
root
|
|
182
|
+
});
|
|
183
|
+
if (!parsed.json) process.stdout.write(`envelope: ${at}
|
|
184
|
+
`);
|
|
96
185
|
if (report.unreadable.length > 0) return 2;
|
|
97
186
|
return report.ok ? 0 : 1;
|
|
98
187
|
};
|
|
@@ -134,8 +223,30 @@ var deployed = (parsed, cwd) => {
|
|
|
134
223
|
);
|
|
135
224
|
return report.ok ? 0 : 1;
|
|
136
225
|
};
|
|
226
|
+
var CONFIG_SHAPE = `geonosis-release reads geonosis.json
|
|
227
|
+
|
|
228
|
+
geonosis.json \u2192 "release" every key optional; a repo that configured nothing gets nothing
|
|
229
|
+
migrations Entry[]? the directories whose migrations a release examines
|
|
230
|
+
dir string required \u2014 the directory, relative to the root
|
|
231
|
+
dialect string required \u2014 one of mikro-orm-ts, postgres, sqlite
|
|
232
|
+
phases string[]? "up" and/or "down" (default ["up"])
|
|
233
|
+
squawk object? { exclude: string[] } \u2014 the rule ids to leave out
|
|
234
|
+
proof object? { mustAssertVersion: boolean }
|
|
235
|
+
secrets string[]? the secret names a deployment carries
|
|
236
|
+
steps string[]? the release steps
|
|
237
|
+
workers string[]? the workers it deploys
|
|
238
|
+
wrangler string[]? the wrangler configs it carries
|
|
239
|
+
wranglerEnv string? the named environment block to read out of them
|
|
240
|
+
|
|
241
|
+
A key outside either list is refused BY NAME, with the accepted set. This shape had to be read out
|
|
242
|
+
of a bundled chunk file by a consumer once (#75).`;
|
|
137
243
|
var main = async () => {
|
|
138
244
|
const argv = process.argv.slice(2);
|
|
245
|
+
if (argv.includes("--print-config-shape")) {
|
|
246
|
+
process.stdout.write(`${CONFIG_SHAPE}
|
|
247
|
+
`);
|
|
248
|
+
return 0;
|
|
249
|
+
}
|
|
139
250
|
if (argv.length === 0 || argv.includes("--help") || argv.includes("-h")) {
|
|
140
251
|
process.stdout.write(USAGE);
|
|
141
252
|
return 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geonosis/release",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"types": "./dist/index.d.ts",
|
|
5
5
|
"description": "The release contract with its proofs: an expand-only migration gate over three dialects, a smoke that must name the version that answered it, and declared ≠ deployed.",
|
|
6
6
|
"keywords": [
|
|
@@ -35,9 +35,18 @@
|
|
|
35
35
|
"bin",
|
|
36
36
|
"dist"
|
|
37
37
|
],
|
|
38
|
-
"
|
|
38
|
+
"peerDependencies": {
|
|
39
39
|
"squawk-cli": "2.63.0"
|
|
40
40
|
},
|
|
41
|
+
"peerDependenciesMeta": {
|
|
42
|
+
"squawk-cli": {
|
|
43
|
+
"optional": true
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"squawk-cli": "2.63.0",
|
|
48
|
+
"@geonosis/ratchet": "1.4.0"
|
|
49
|
+
},
|
|
41
50
|
"engines": {
|
|
42
51
|
"node": ">=22"
|
|
43
52
|
},
|