@geonosis/release 1.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/LICENSE +202 -0
- package/README.md +233 -0
- package/bin/geonosis-release.mjs +27 -0
- package/dist/chunk-2JLVQK7Y.js +941 -0
- package/dist/index.d.ts +252 -0
- package/dist/index.js +62 -0
- package/dist/release-cli.js +160 -0
- package/dist/stub-runner.js +31 -0
- package/package.json +51 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/** The dialects the gate can route to. A dialect is never sniffed: it is what the repo declared. */
|
|
2
|
+
type Dialect = 'mikro-orm-ts' | 'postgres' | 'sqlite';
|
|
3
|
+
type Phase = 'down' | 'up';
|
|
4
|
+
type MigrationDir = {
|
|
5
|
+
dialect: Dialect;
|
|
6
|
+
dir: string;
|
|
7
|
+
phases?: Phase[];
|
|
8
|
+
squawk?: {
|
|
9
|
+
exclude?: string[];
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
type ProofConfig = {
|
|
13
|
+
mustAssertVersion?: boolean;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Law 6: every directory, dialect, secret name and worker is an OPTION. The defaults are empty, and
|
|
17
|
+
* a command that needs one it has not got refuses with 2 rather than measuring nothing and
|
|
18
|
+
* reporting green.
|
|
19
|
+
*/
|
|
20
|
+
type ReleaseConfig = {
|
|
21
|
+
migrations?: MigrationDir[];
|
|
22
|
+
proof?: ProofConfig;
|
|
23
|
+
secrets?: string[];
|
|
24
|
+
steps?: string[];
|
|
25
|
+
workers?: string[];
|
|
26
|
+
wrangler?: string[];
|
|
27
|
+
wranglerEnv?: string;
|
|
28
|
+
};
|
|
29
|
+
type Refusal = {
|
|
30
|
+
line: number;
|
|
31
|
+
path: string;
|
|
32
|
+
verb: string;
|
|
33
|
+
};
|
|
34
|
+
/** A marker the gate could not believe, and the sentence saying which part it could not believe. */
|
|
35
|
+
type MarkerRefusal = {
|
|
36
|
+
line: number;
|
|
37
|
+
path: string;
|
|
38
|
+
why: string;
|
|
39
|
+
};
|
|
40
|
+
type MigrationsReport = {
|
|
41
|
+
linted: number;
|
|
42
|
+
markers: MarkerRefusal[];
|
|
43
|
+
ok: boolean;
|
|
44
|
+
read: number;
|
|
45
|
+
refusals: Refusal[];
|
|
46
|
+
squawk: {
|
|
47
|
+
findings: string;
|
|
48
|
+
path: string;
|
|
49
|
+
}[];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
declare const parseReleaseConfig: (raw: unknown) => ReleaseConfig;
|
|
53
|
+
/** geonosis.json is where a repo says what it has. Its absence is a repo that has said nothing. */
|
|
54
|
+
declare const readReleaseConfig: (root: string) => ReleaseConfig;
|
|
55
|
+
|
|
56
|
+
type Declared = {
|
|
57
|
+
bindings: string[];
|
|
58
|
+
crons: string[];
|
|
59
|
+
routes: string[];
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* JSON with comments and trailing commas — which is what `wrangler.jsonc` is, and the format both
|
|
63
|
+
* consumers actually use. Strings are walked over rather than regexed around, because a `//` inside
|
|
64
|
+
* a URL is not a comment and a repo whose route was silently truncated would read as no route.
|
|
65
|
+
*/
|
|
66
|
+
declare const parseJsonc: (source: string) => unknown;
|
|
67
|
+
/**
|
|
68
|
+
* Enough TOML for the keys a deployment drift check reads: `[table]`, `[[array of tables]]`,
|
|
69
|
+
* `key = value` with strings, numbers, booleans, arrays (multi-line included) and inline tables.
|
|
70
|
+
* It is deliberately not a TOML implementation — it exists because `wrangler.toml` is wrangler's
|
|
71
|
+
* default and refusing it outright would make this command unusable for most repos.
|
|
72
|
+
*/
|
|
73
|
+
declare const parseToml: (source: string) => Record<string, unknown>;
|
|
74
|
+
declare const declaredIn: (config: Record<string, unknown>) => Declared;
|
|
75
|
+
declare const readWrangler: (root: string, relative: string, env?: string) => Declared;
|
|
76
|
+
|
|
77
|
+
/** Where a consumer's pipeline writes what it actually put in front of a user, after promote. */
|
|
78
|
+
declare const DEPLOYED_FILE = ".geonosis/deployed.json";
|
|
79
|
+
declare const 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";
|
|
80
|
+
type Deployed = {
|
|
81
|
+
bindings: string[];
|
|
82
|
+
crons: string[];
|
|
83
|
+
routes: string[];
|
|
84
|
+
secrets: string[];
|
|
85
|
+
};
|
|
86
|
+
type Drift = {
|
|
87
|
+
kind: string;
|
|
88
|
+
missing: string[];
|
|
89
|
+
extra: string[];
|
|
90
|
+
};
|
|
91
|
+
type DeployedReport = {
|
|
92
|
+
at?: string;
|
|
93
|
+
drift: Drift[];
|
|
94
|
+
ok: boolean;
|
|
95
|
+
};
|
|
96
|
+
declare const readDeployed: (root: string) => {
|
|
97
|
+
at?: string;
|
|
98
|
+
deployed: Deployed;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* The comparison itself, over two plain shapes. Ten lines, deliberately — the doctor asks the same
|
|
102
|
+
* question of the same file, and it asks it by reading the file rather than by importing this
|
|
103
|
+
* package, so a repo can install either one alone.
|
|
104
|
+
*/
|
|
105
|
+
declare const driftBetween: (declared: Declared & {
|
|
106
|
+
secrets: string[];
|
|
107
|
+
}, deployed: Deployed) => Drift[];
|
|
108
|
+
declare const runDeployed: (input: {
|
|
109
|
+
root: string;
|
|
110
|
+
}) => DeployedReport;
|
|
111
|
+
declare const formatDeployed: (report: DeployedReport) => string;
|
|
112
|
+
|
|
113
|
+
type MigrationsInput = {
|
|
114
|
+
dialect?: Dialect;
|
|
115
|
+
root: string;
|
|
116
|
+
since: string;
|
|
117
|
+
};
|
|
118
|
+
declare const runMigrations: (input: MigrationsInput) => MigrationsReport;
|
|
119
|
+
declare const formatMigrations: (report: MigrationsReport) => string;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The contract, in the only order that means anything.
|
|
123
|
+
*
|
|
124
|
+
* `upload` moves no traffic. `park` puts the new version in the deployment at 0 %, which is what
|
|
125
|
+
* makes it reachable by an override at all. `prove` reaches it and asks it who it is. `promote`
|
|
126
|
+
* swaps the pointer. Promote before prove is the whole failure this package was extracted from;
|
|
127
|
+
* park before upload is nothing.
|
|
128
|
+
*/
|
|
129
|
+
declare const STEPS: readonly ["upload", "park", "prove", "promote"];
|
|
130
|
+
/**
|
|
131
|
+
* C1.2 of the review this was extracted from. Cloudflare documents that an override which was not
|
|
132
|
+
* applied — propagation, a malformed dictionary, a version not in the deployment — causes the
|
|
133
|
+
* request to be routed by the configured PERCENTAGES instead, silently.
|
|
134
|
+
*/
|
|
135
|
+
declare const WHY_VERSION_IDENTITY = "An override that was not applied routes by the configured percentages, to the OLD version \u2014 so a smoke that does not assert which version answered can pass by testing the code it was replacing.";
|
|
136
|
+
type PlanReport = {
|
|
137
|
+
accepted: boolean;
|
|
138
|
+
ok: boolean;
|
|
139
|
+
problems: string[];
|
|
140
|
+
steps: string[];
|
|
141
|
+
};
|
|
142
|
+
declare const runPlan: (input: {
|
|
143
|
+
accept: boolean;
|
|
144
|
+
root: string;
|
|
145
|
+
}) => PlanReport;
|
|
146
|
+
declare const formatPlan: (report: PlanReport) => string;
|
|
147
|
+
|
|
148
|
+
type Verdict = {
|
|
149
|
+
ok: boolean;
|
|
150
|
+
steps: string[];
|
|
151
|
+
why?: string;
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* One release, over one runner. The gate never invokes a platform; it asks, reads the answer, and
|
|
155
|
+
* REFUSES — and a refusal means the promote request is never written, which is the only reason the
|
|
156
|
+
* park step exists.
|
|
157
|
+
*/
|
|
158
|
+
declare const proveOver: (input: {
|
|
159
|
+
args?: readonly string[];
|
|
160
|
+
command: string;
|
|
161
|
+
cwd: string;
|
|
162
|
+
timeoutMs?: number;
|
|
163
|
+
}) => Promise<Verdict>;
|
|
164
|
+
/** The three plants, and the honest runner that must still get through. */
|
|
165
|
+
declare const PLANTS: readonly [{
|
|
166
|
+
readonly name: "answered";
|
|
167
|
+
readonly says: "a smoke that answered a version other than the one it overrode to is refused";
|
|
168
|
+
}, {
|
|
169
|
+
readonly name: "failed";
|
|
170
|
+
readonly says: "a smoke that did not pass is refused";
|
|
171
|
+
}, {
|
|
172
|
+
readonly name: "promoted";
|
|
173
|
+
readonly says: "a promote of a version nothing proved is refused";
|
|
174
|
+
}];
|
|
175
|
+
type ProveOutcome = {
|
|
176
|
+
lines: string[];
|
|
177
|
+
ok: boolean;
|
|
178
|
+
};
|
|
179
|
+
/**
|
|
180
|
+
* A gate whose failure has never been observed is a claim, not a gate. With no runner named, each
|
|
181
|
+
* refusal is planted into the package's own stub and required to be caught — and the honest runner
|
|
182
|
+
* is required to pass, because a gate that refused everything would be as useless as one that
|
|
183
|
+
* refused nothing.
|
|
184
|
+
*/
|
|
185
|
+
declare const prove: (cwd: string, stub?: string) => Promise<ProveOutcome>;
|
|
186
|
+
declare const formatProve: (outcome: ProveOutcome) => string;
|
|
187
|
+
declare const formatVerdict: (verdict: Verdict) => string;
|
|
188
|
+
|
|
189
|
+
type Request = {
|
|
190
|
+
step: 'promote';
|
|
191
|
+
versionId: string;
|
|
192
|
+
} | {
|
|
193
|
+
step: 'smoke';
|
|
194
|
+
versionId: string;
|
|
195
|
+
} | {
|
|
196
|
+
step: 'upload';
|
|
197
|
+
};
|
|
198
|
+
type Reply = Record<string, unknown>;
|
|
199
|
+
/**
|
|
200
|
+
* A consumer executable speaking JSON on stdio, one request per line and one reply per line.
|
|
201
|
+
*
|
|
202
|
+
* This is the whole seam. Every platform call — `wrangler versions upload`, the override header,
|
|
203
|
+
* `versions deploy` — lives on the far side of it, in the consumer's repo, where the credentials
|
|
204
|
+
* and the knowledge of which workers exist already are. Nothing on this side has ever heard of
|
|
205
|
+
* Cloudflare.
|
|
206
|
+
*/
|
|
207
|
+
declare class Runner {
|
|
208
|
+
private buffered;
|
|
209
|
+
private closed;
|
|
210
|
+
private readonly child;
|
|
211
|
+
private readonly waiting;
|
|
212
|
+
constructor(command: string, args: readonly string[], cwd: string);
|
|
213
|
+
ask(request: Request, timeoutMs?: number): Promise<Reply>;
|
|
214
|
+
close(): void;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
type Extracted = {
|
|
218
|
+
line: number;
|
|
219
|
+
sql: string;
|
|
220
|
+
};
|
|
221
|
+
/**
|
|
222
|
+
* Every string argument of `addSql(` inside the requested phases. Handles a template literal, a
|
|
223
|
+
* quoted string, either of them opening on the line after the call, a literal that itself spans
|
|
224
|
+
* lines, and adjacent literals joined by `+`. Everything else — an identifier holding the SQL, an
|
|
225
|
+
* interpolation — is refused by name: resolving one is a TypeScript parser, and this package will
|
|
226
|
+
* not grow one.
|
|
227
|
+
*/
|
|
228
|
+
declare const addSqlIn: (source: string, phases: readonly Phase[]) => Extracted[];
|
|
229
|
+
|
|
230
|
+
type Marker = {
|
|
231
|
+
line: number;
|
|
232
|
+
reason: string;
|
|
233
|
+
since?: string;
|
|
234
|
+
};
|
|
235
|
+
declare const markersIn: (source: string) => Marker[];
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Narrowing verbs. Each removes something a version that is still serving — or one a rollback could
|
|
239
|
+
* put back — may still be reading or writing. The schema is live from the instant the migration
|
|
240
|
+
* runs, minutes before any code has been proven and for ever if the release is rolled back.
|
|
241
|
+
*/
|
|
242
|
+
declare const NARROWING: readonly (readonly [string, RegExp])[];
|
|
243
|
+
/**
|
|
244
|
+
* Comments are where the marker lives, so they are not stripped; strings are, because a verb inside
|
|
245
|
+
* one is data, not a statement. The replacement keeps the same length AND the same newlines, so a
|
|
246
|
+
* line number counted afterwards is still the line number in the file somebody has open.
|
|
247
|
+
*/
|
|
248
|
+
declare const statementsOf: (sql: string) => string;
|
|
249
|
+
/** One refusal per verb per file, at the line of that verb's first occurrence. */
|
|
250
|
+
declare const refusalsIn: (path: string, body: string, firstLine?: number) => Refusal[];
|
|
251
|
+
|
|
252
|
+
export { DEPLOYED_FILE, type Declared, type Deployed, type DeployedReport, type Dialect, type Drift, type MigrationDir, type MigrationsReport, NARROWING, NOTHING_SAYS, PLANTS, type Phase, type PlanReport, type ProveOutcome, type Refusal, type ReleaseConfig, Runner, STEPS, type Verdict, WHY_VERSION_IDENTITY, addSqlIn, declaredIn, driftBetween, formatDeployed, formatMigrations, formatPlan, formatProve, formatVerdict, markersIn, parseJsonc, parseReleaseConfig, parseToml, prove, proveOver, readDeployed, readReleaseConfig, readWrangler, refusalsIn, runDeployed, runMigrations, runPlan, statementsOf };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEPLOYED_FILE,
|
|
3
|
+
NARROWING,
|
|
4
|
+
NOTHING_SAYS,
|
|
5
|
+
PLANTS,
|
|
6
|
+
Runner,
|
|
7
|
+
STEPS,
|
|
8
|
+
WHY_VERSION_IDENTITY,
|
|
9
|
+
addSqlIn,
|
|
10
|
+
declaredIn,
|
|
11
|
+
driftBetween,
|
|
12
|
+
formatDeployed,
|
|
13
|
+
formatMigrations,
|
|
14
|
+
formatPlan,
|
|
15
|
+
formatProve,
|
|
16
|
+
formatVerdict,
|
|
17
|
+
markersIn,
|
|
18
|
+
parseJsonc,
|
|
19
|
+
parseReleaseConfig,
|
|
20
|
+
parseToml,
|
|
21
|
+
prove,
|
|
22
|
+
proveOver,
|
|
23
|
+
readDeployed,
|
|
24
|
+
readReleaseConfig,
|
|
25
|
+
readWrangler,
|
|
26
|
+
refusalsIn,
|
|
27
|
+
runDeployed,
|
|
28
|
+
runMigrations,
|
|
29
|
+
runPlan,
|
|
30
|
+
statementsOf
|
|
31
|
+
} from "./chunk-2JLVQK7Y.js";
|
|
32
|
+
export {
|
|
33
|
+
DEPLOYED_FILE,
|
|
34
|
+
NARROWING,
|
|
35
|
+
NOTHING_SAYS,
|
|
36
|
+
PLANTS,
|
|
37
|
+
Runner,
|
|
38
|
+
STEPS,
|
|
39
|
+
WHY_VERSION_IDENTITY,
|
|
40
|
+
addSqlIn,
|
|
41
|
+
declaredIn,
|
|
42
|
+
driftBetween,
|
|
43
|
+
formatDeployed,
|
|
44
|
+
formatMigrations,
|
|
45
|
+
formatPlan,
|
|
46
|
+
formatProve,
|
|
47
|
+
formatVerdict,
|
|
48
|
+
markersIn,
|
|
49
|
+
parseJsonc,
|
|
50
|
+
parseReleaseConfig,
|
|
51
|
+
parseToml,
|
|
52
|
+
prove,
|
|
53
|
+
proveOver,
|
|
54
|
+
readDeployed,
|
|
55
|
+
readReleaseConfig,
|
|
56
|
+
readWrangler,
|
|
57
|
+
refusalsIn,
|
|
58
|
+
runDeployed,
|
|
59
|
+
runMigrations,
|
|
60
|
+
runPlan,
|
|
61
|
+
statementsOf
|
|
62
|
+
};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CannotRun,
|
|
3
|
+
formatDeployed,
|
|
4
|
+
formatMigrations,
|
|
5
|
+
formatPlan,
|
|
6
|
+
formatProve,
|
|
7
|
+
formatVerdict,
|
|
8
|
+
prove,
|
|
9
|
+
proveOver,
|
|
10
|
+
runDeployed,
|
|
11
|
+
runMigrations,
|
|
12
|
+
runPlan
|
|
13
|
+
} from "./chunk-2JLVQK7Y.js";
|
|
14
|
+
|
|
15
|
+
// src/release-cli.ts
|
|
16
|
+
import { resolve } from "path";
|
|
17
|
+
var USAGE = `geonosis-release <command> [options]
|
|
18
|
+
|
|
19
|
+
migrations --since <ref> [--dialect <name>] [--root <dir>] [--json]
|
|
20
|
+
|
|
21
|
+
Refuse a schema change the version that is still serving cannot survive. The schema
|
|
22
|
+
serves 100 % of traffic the instant the migration runs \u2014 before any code has been
|
|
23
|
+
proven, and for ever if the release is rolled back \u2014 so a release may only WIDEN.
|
|
24
|
+
|
|
25
|
+
Dialects are routed by geonosis.json \u2192 release.migrations[], never sniffed:
|
|
26
|
+
sqlite the narrowing-verb check (D1 is not Postgres and squawk cannot
|
|
27
|
+
parse it)
|
|
28
|
+
postgres squawk-cli, its exit code and output surfaced verbatim
|
|
29
|
+
mikro-orm-ts the SQL extracted from addSql(\u2026) in up(), read both ways
|
|
30
|
+
|
|
31
|
+
Exit 0 clean, 1 refusals, 2 the run could not be made.
|
|
32
|
+
|
|
33
|
+
plan [--root <dir>] [--i-accept-an-unproven-smoke] [--json]
|
|
34
|
+
|
|
35
|
+
The contract as data \u2014 geonosis.json \u2192 release.steps and release.proof \u2014 validated
|
|
36
|
+
and printed. upload \u2192 park \u2192 prove \u2192 promote, in that order and no other.
|
|
37
|
+
|
|
38
|
+
prove [--runner <script>] [--root <dir>] [--json]
|
|
39
|
+
|
|
40
|
+
With a runner: one release over it, refusing a smoke that answered a version other
|
|
41
|
+
than the one it overrode to, a smoke that failed, and a promote of a version nothing
|
|
42
|
+
proved. With no runner: each of those three planted into this package's own stub and
|
|
43
|
+
required to be caught.
|
|
44
|
+
|
|
45
|
+
The runner is a consumer executable speaking JSON on stdio, one request per line:
|
|
46
|
+
{"step":"upload"} -> {"versionId":"\u2026"}
|
|
47
|
+
{"step":"smoke","versionId":"\u2026"} -> {"ok":true,"answeredVersionId":"\u2026"}
|
|
48
|
+
{"step":"promote","versionId":"\u2026"} -> {"ok":true}
|
|
49
|
+
|
|
50
|
+
deployed --check [--root <dir>] [--json]
|
|
51
|
+
|
|
52
|
+
What the pipeline REPORTED deploying, in .geonosis/deployed.json, against what the
|
|
53
|
+
tree declares in the wrangler configs geonosis.json names and release.secrets.
|
|
54
|
+
\`versions upload\` applies no triggers, and a secret can be put outside the gate
|
|
55
|
+
entirely \u2014 so declared and deployed drift apart silently, with a green pipeline.
|
|
56
|
+
`;
|
|
57
|
+
var VALUED = /* @__PURE__ */ new Set(["--dialect", "--root", "--runner", "--since"]);
|
|
58
|
+
var FLAGS = /* @__PURE__ */ new Set(["--check", "--i-accept-an-unproven-smoke", "--json"]);
|
|
59
|
+
var DIALECTS = /* @__PURE__ */ new Set(["mikro-orm-ts", "postgres", "sqlite"]);
|
|
60
|
+
var parseArgs = (argv) => {
|
|
61
|
+
const read = {};
|
|
62
|
+
const flags = /* @__PURE__ */ new Set();
|
|
63
|
+
let json = false;
|
|
64
|
+
for (let index = 1; index < argv.length; index += 1) {
|
|
65
|
+
const flag = argv[index] ?? "";
|
|
66
|
+
if (FLAGS.has(flag)) {
|
|
67
|
+
if (flag === "--json") json = true;
|
|
68
|
+
flags.add(flag);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (!VALUED.has(flag)) throw new CannotRun(`unknown argument "${flag}"`);
|
|
72
|
+
const value = argv[index + 1];
|
|
73
|
+
if (value === void 0 || value.startsWith("--")) throw new CannotRun(`${flag} needs a value`);
|
|
74
|
+
read[flag] = value;
|
|
75
|
+
index += 1;
|
|
76
|
+
}
|
|
77
|
+
return { command: argv[0] ?? "", flags, json, read };
|
|
78
|
+
};
|
|
79
|
+
var rootOf = (parsed, cwd) => resolve(cwd, parsed.read["--root"] ?? cwd);
|
|
80
|
+
var migrations = (parsed, cwd) => {
|
|
81
|
+
const since = parsed.read["--since"];
|
|
82
|
+
if (since === void 0) throw new CannotRun("migrations needs --since <ref>");
|
|
83
|
+
const dialect = parsed.read["--dialect"];
|
|
84
|
+
if (dialect !== void 0 && !DIALECTS.has(dialect)) {
|
|
85
|
+
throw new CannotRun(`--dialect must be one of ${[...DIALECTS].toSorted().join(", ")}`);
|
|
86
|
+
}
|
|
87
|
+
const report = runMigrations({
|
|
88
|
+
...dialect === void 0 ? {} : { dialect },
|
|
89
|
+
root: resolve(cwd, parsed.read["--root"] ?? cwd),
|
|
90
|
+
since
|
|
91
|
+
});
|
|
92
|
+
process.stdout.write(
|
|
93
|
+
parsed.json ? `${JSON.stringify(report, void 0, 2)}
|
|
94
|
+
` : formatMigrations(report)
|
|
95
|
+
);
|
|
96
|
+
return report.ok ? 0 : 1;
|
|
97
|
+
};
|
|
98
|
+
var plan = (parsed, cwd) => {
|
|
99
|
+
const report = runPlan({
|
|
100
|
+
accept: parsed.flags.has("--i-accept-an-unproven-smoke"),
|
|
101
|
+
root: rootOf(parsed, cwd)
|
|
102
|
+
});
|
|
103
|
+
process.stdout.write(
|
|
104
|
+
parsed.json ? `${JSON.stringify(report, void 0, 2)}
|
|
105
|
+
` : formatPlan(report)
|
|
106
|
+
);
|
|
107
|
+
return report.ok ? 0 : 1;
|
|
108
|
+
};
|
|
109
|
+
var proving = async (parsed, cwd) => {
|
|
110
|
+
const root = rootOf(parsed, cwd);
|
|
111
|
+
const named = parsed.read["--runner"];
|
|
112
|
+
if (named === void 0) {
|
|
113
|
+
const outcome = await prove(root);
|
|
114
|
+
process.stdout.write(
|
|
115
|
+
parsed.json ? `${JSON.stringify(outcome, void 0, 2)}
|
|
116
|
+
` : formatProve(outcome)
|
|
117
|
+
);
|
|
118
|
+
return outcome.ok ? 0 : 1;
|
|
119
|
+
}
|
|
120
|
+
const verdict = await proveOver({ command: resolve(cwd, named), cwd: root });
|
|
121
|
+
process.stdout.write(
|
|
122
|
+
parsed.json ? `${JSON.stringify(verdict, void 0, 2)}
|
|
123
|
+
` : formatVerdict(verdict)
|
|
124
|
+
);
|
|
125
|
+
return verdict.ok ? 0 : 1;
|
|
126
|
+
};
|
|
127
|
+
var deployed = (parsed, cwd) => {
|
|
128
|
+
if (!parsed.flags.has("--check")) throw new CannotRun("deployed needs --check");
|
|
129
|
+
const report = runDeployed({ root: rootOf(parsed, cwd) });
|
|
130
|
+
process.stdout.write(
|
|
131
|
+
parsed.json ? `${JSON.stringify(report, void 0, 2)}
|
|
132
|
+
` : formatDeployed(report)
|
|
133
|
+
);
|
|
134
|
+
return report.ok ? 0 : 1;
|
|
135
|
+
};
|
|
136
|
+
var main = async () => {
|
|
137
|
+
const argv = process.argv.slice(2);
|
|
138
|
+
if (argv.length === 0 || argv.includes("--help") || argv.includes("-h")) {
|
|
139
|
+
process.stdout.write(USAGE);
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
|
142
|
+
const parsed = parseArgs(argv);
|
|
143
|
+
if (parsed.command === "migrations") return migrations(parsed, process.cwd());
|
|
144
|
+
if (parsed.command === "plan") return plan(parsed, process.cwd());
|
|
145
|
+
if (parsed.command === "prove") return proving(parsed, process.cwd());
|
|
146
|
+
if (parsed.command === "deployed") return deployed(parsed, process.cwd());
|
|
147
|
+
throw new CannotRun(
|
|
148
|
+
`no command called "${parsed.command}" \u2014 this bin has migrations, plan, prove, deployed`
|
|
149
|
+
);
|
|
150
|
+
};
|
|
151
|
+
try {
|
|
152
|
+
process.exit(await main());
|
|
153
|
+
} catch (error) {
|
|
154
|
+
process.stderr.write(`geonosis-release: ${error.message}
|
|
155
|
+
`);
|
|
156
|
+
process.exit(2);
|
|
157
|
+
}
|
|
158
|
+
export {
|
|
159
|
+
parseArgs
|
|
160
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// src/stub-runner.ts
|
|
2
|
+
import { createInterface } from "readline";
|
|
3
|
+
var plant = (process.argv.find((one) => one.startsWith("--plant=")) ?? "--plant=good").slice(
|
|
4
|
+
"--plant=".length
|
|
5
|
+
);
|
|
6
|
+
var reply = (answer) => {
|
|
7
|
+
process.stdout.write(`${JSON.stringify(answer)}
|
|
8
|
+
`);
|
|
9
|
+
};
|
|
10
|
+
createInterface({ input: process.stdin }).on("line", (line) => {
|
|
11
|
+
const request = JSON.parse(line);
|
|
12
|
+
if (request.step === "upload") {
|
|
13
|
+
reply({ versionId: "version-under-test" });
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (request.step === "smoke") {
|
|
17
|
+
reply({
|
|
18
|
+
answeredVersionId: plant === "answered" ? "the-version-that-was-already-serving" : "version-under-test",
|
|
19
|
+
ok: plant !== "failed"
|
|
20
|
+
});
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (request.step === "promote") {
|
|
24
|
+
reply({
|
|
25
|
+
ok: true,
|
|
26
|
+
promotedVersionId: plant === "promoted" ? "a-version-nothing-proved" : "version-under-test"
|
|
27
|
+
});
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
reply({ ok: false });
|
|
31
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@geonosis/release",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"types": "./dist/index.d.ts",
|
|
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
|
+
"keywords": [
|
|
7
|
+
"release",
|
|
8
|
+
"migrations",
|
|
9
|
+
"expand-contract",
|
|
10
|
+
"squawk",
|
|
11
|
+
"zero-downtime",
|
|
12
|
+
"gate",
|
|
13
|
+
"ci",
|
|
14
|
+
"drift"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://github.com/microcompanies/geonosis/tree/main/packages/release",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/microcompanies/geonosis.git",
|
|
20
|
+
"directory": "packages/release"
|
|
21
|
+
},
|
|
22
|
+
"license": "Apache-2.0",
|
|
23
|
+
"type": "module",
|
|
24
|
+
"main": "dist/index.js",
|
|
25
|
+
"bin": {
|
|
26
|
+
"geonosis-release": "bin/geonosis-release.mjs"
|
|
27
|
+
},
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"default": "./dist/index.js"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"bin",
|
|
36
|
+
"dist"
|
|
37
|
+
],
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"squawk-cli": "2.63.0"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=22"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsup",
|
|
49
|
+
"typecheck": "tsc --noEmit"
|
|
50
|
+
}
|
|
51
|
+
}
|