@mason-data/cli 0.8.0 → 0.10.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 +19 -8
- package/dist/src/api/error.js +4 -0
- package/dist/src/api/postgres/catalog-worker.js +49 -2
- package/dist/src/api/postgres/catalog.js +166 -32
- package/dist/src/api/postgres/listener.js +11 -3
- package/dist/src/api/postgres/proxy.js +129 -0
- package/dist/src/api/postgres/schema.js +6 -0
- package/dist/src/api/postgres/server.js +527 -103
- package/dist/src/api/postgres/translate.js +47 -2
- package/dist/src/cli/background.js +18 -19
- package/dist/src/cli/bridge-stdin.js +101 -0
- package/dist/src/cli/command.js +337 -15
- package/dist/src/cli/commands.js +458 -169
- package/dist/src/cli/credentials.js +182 -0
- package/dist/src/cli/demo.js +2 -2
- package/dist/src/cli/deploy.js +680 -0
- package/dist/src/cli/dev.js +148 -27
- package/dist/src/cli/doctor.js +122 -36
- package/dist/src/cli/generated-files.js +143 -0
- package/dist/src/cli/guides.js +116 -16
- package/dist/src/cli/hosted.js +850 -0
- package/dist/src/cli/init.js +154 -33
- package/dist/src/cli/login.js +292 -0
- package/dist/src/cli/machine.js +125 -40
- package/dist/src/cli/main.js +59 -10
- package/dist/src/cli/mason.cjs +17 -0
- package/dist/src/cli/mcp.js +17 -14
- package/dist/src/cli/output-gate.js +176 -0
- package/dist/src/cli/page-share.js +96 -0
- package/dist/src/cli/pg-tls.js +1 -1
- package/dist/src/cli/pin.js +6 -2
- package/dist/src/cli/pool-size.cjs +85 -0
- package/dist/src/cli/ports.js +2 -0
- package/dist/src/cli/preflight.js +130 -39
- package/dist/src/cli/project.js +100 -1
- package/dist/src/cli/readiness.js +42 -6
- package/dist/src/cli/registry.js +89 -36
- package/dist/src/cli/replica-hosted.js +1324 -0
- package/dist/src/cli/replica.js +114 -0
- package/dist/src/cli/replication-mode.js +5 -0
- package/dist/src/cli/request-filter.js +16 -6
- package/dist/src/cli/roles.js +2 -1
- package/dist/src/cli/run-server.js +179 -9
- package/dist/src/cli/secret-input.js +193 -0
- package/dist/src/cli/security.js +57 -10
- package/dist/src/cli/serve.js +65 -9
- package/dist/src/cli/share-run.js +51 -8
- package/dist/src/cli/share.js +23 -18
- package/dist/src/cli/source-edit.js +10 -4
- package/dist/src/cli/source-role.js +343 -0
- package/dist/src/cli/sources.js +50 -23
- package/dist/src/cli/style.js +16 -1
- package/dist/src/cli/supervise.js +15 -23
- package/dist/src/cli/sync.js +17 -8
- package/dist/src/cli/token-hosted.js +343 -0
- package/dist/src/cli/tunnel.js +1 -1
- package/dist/src/cli/ui-hosted.js +163 -0
- package/dist/src/cli/ui-ssh.js +460 -0
- package/dist/src/cli/ui.js +162 -17
- package/dist/src/cli/usage.js +19 -15
- package/dist/src/cli/user-hosted.js +364 -0
- package/dist/src/cli/user.js +275 -105
- package/dist/src/demo/seed.js +26 -0
- package/dist/src/duck/analyze.js +36 -20
- package/dist/src/duck/collation.js +266 -0
- package/dist/src/duck/constant-key.js +94 -0
- package/dist/src/duck/distinct-key.js +283 -26
- package/dist/src/duck/events.js +39 -8
- package/dist/src/duck/executor-worker.js +2 -2
- package/dist/src/duck/executor.js +6 -5
- package/dist/src/duck/explain.js +4 -0
- package/dist/src/duck/functions.js +448 -49
- package/dist/src/duck/gaps.js +34 -16
- package/dist/src/duck/generated-plan.js +1070 -0
- package/dist/src/duck/generated.js +906 -0
- package/dist/src/duck/grants.js +1 -1
- package/dist/src/duck/held-types.js +64 -0
- package/dist/src/duck/history.js +4 -0
- package/dist/src/duck/jsonb-order.js +9 -12
- package/dist/src/duck/kept-route.js +6 -5
- package/dist/src/duck/kept.js +9 -7
- package/dist/src/duck/late.js +1469 -40
- package/dist/src/duck/libc-bound.js +341 -0
- package/dist/src/duck/libc-order.js +892 -126
- package/dist/src/duck/pg-eval-worker.js +7 -1
- package/dist/src/duck/pg-eval.js +6 -4
- package/dist/src/duck/pgtree.js +774 -23
- package/dist/src/duck/plan.js +275 -30
- package/dist/src/duck/publication.js +14 -0
- package/dist/src/duck/requests.js +9 -5
- package/dist/src/duck/resolved.js +117 -2
- package/dist/src/duck/rewrite.js +4567 -583
- package/dist/src/duck/row-filter.js +12 -6
- package/dist/src/duck/statement.js +19 -0
- package/dist/src/duck/store.js +294 -68
- package/dist/src/duck/stream.js +9 -5
- package/dist/src/duck/sync.js +602 -126
- package/dist/src/duck/token.js +4 -1
- package/dist/src/duck/types.js +63 -7
- package/dist/src/duck/usage.js +6 -4
- package/dist/src/duck/users.js +110 -9
- package/dist/src/duck/verified.js +159 -2
- package/dist/src/duck/views.js +4 -3
- package/dist/src/lib/cidr.js +177 -0
- package/dist/src/lib/database-url.js +55 -10
- package/dist/src/lib/instant.js +50 -0
- package/dist/src/lib/log-buffer.js +13 -4
- package/dist/src/lib/log-read.js +196 -0
- package/dist/src/lib/pg-numeric.js +387 -49
- package/dist/src/lib/secret-mask.js +142 -0
- package/dist/src/lib/table-sync.js +84 -19
- package/dist/src/web/app.js +9 -4
- package/dist/src/web/pages/status.js +178 -13
- package/dist/src/web/pages/syncs.js +1 -1
- package/dist/src/web/pages/tables.js +8 -3
- package/dist/src/web/pages/users.js +4 -3
- package/dist/src/web/shared.js +19 -3
- package/package.json +8 -7
|
@@ -55,19 +55,30 @@ export function copyProgress(source, copied, rowsEstimate, p) {
|
|
|
55
55
|
const c = copyingText(copied, rowsEstimate, p);
|
|
56
56
|
return `${source}: ${c.fraction !== null ? `syncing ${Math.floor(c.fraction * 100)}% · ` : /^\d/.test(c.text) ? "syncing " : ""}${c.text}`;
|
|
57
57
|
}
|
|
58
|
-
export function syncStateOf(c) {
|
|
58
|
+
export function syncStateOf(c, now = Date.now()) {
|
|
59
59
|
if (c === undefined || c.phase === "waiting")
|
|
60
60
|
return "waiting";
|
|
61
61
|
if (c.phase === "failed" || c.error !== null)
|
|
62
62
|
return "failed";
|
|
63
63
|
if (c.phase === "copying" || c.rebuilding !== undefined || c.behind !== undefined)
|
|
64
64
|
return "syncing";
|
|
65
|
-
return "synced";
|
|
65
|
+
return (behindOf(c.kept, now).behindBySeconds ?? 0) > 0 ? "behind" : "synced";
|
|
66
|
+
}
|
|
67
|
+
export function behindOf(k, now) {
|
|
68
|
+
if (k === undefined || k.by === "generated")
|
|
69
|
+
return { asOf: null, promisedSeconds: null, behindBySeconds: null, behindWhy: null };
|
|
70
|
+
const asOf = k.by === "recopy" ? k.syncedAt ?? k.copiedAt : k.syncedAt;
|
|
71
|
+
const promisedSeconds = k.promisedSeconds ?? null;
|
|
72
|
+
if (promisedSeconds === null || asOf === null)
|
|
73
|
+
return { asOf, promisedSeconds, behindBySeconds: null, behindWhy: null };
|
|
74
|
+
const over = (now - Date.parse(asOf)) / 1000 - promisedSeconds;
|
|
75
|
+
return over > 0 ? { asOf, promisedSeconds, behindBySeconds: Math.round(over * 10) / 10, behindWhy: k.lateWhy ?? "its next sync is late" }
|
|
76
|
+
: { asOf, promisedSeconds, behindBySeconds: 0, behindWhy: null };
|
|
66
77
|
}
|
|
67
78
|
export function tableSync(c, now) {
|
|
68
79
|
const s = c?.synced;
|
|
69
|
-
return { state: syncStateOf(c), syncedAt: s?.at ?? null, syncSeconds: s?.seconds ?? null, syncedBy: s?.by ?? null,
|
|
70
|
-
ageSeconds: s === undefined ? null : Math.max(0, Math.round((now - Date.parse(s.at)) / 100) / 10) };
|
|
80
|
+
return { state: syncStateOf(c, now), syncedAt: s?.at ?? null, syncSeconds: s?.seconds ?? null, syncedBy: s?.by ?? null,
|
|
81
|
+
ageSeconds: s === undefined ? null : Math.max(0, Math.round((now - Date.parse(s.at)) / 100) / 10), ...behindOf(c?.kept, now) };
|
|
71
82
|
}
|
|
72
83
|
export function syncTotals(added, copies) {
|
|
73
84
|
const copied = copies.filter((c) => c.phase === "copied");
|
|
@@ -95,8 +106,12 @@ export function readinessWord(r) {
|
|
|
95
106
|
export function readiness(s) {
|
|
96
107
|
const failed = [], waiting = [];
|
|
97
108
|
for (const t of s.tables.filter((t) => t.added)) {
|
|
98
|
-
|
|
99
|
-
|
|
109
|
+
// the table's state in one word (`syncStateOf`), never a second reading of its error or phase: a copied table whose
|
|
110
|
+
// poll, catch-up, stream or publication check failed is failed here as it is there (issues 100, 82). Behind is said
|
|
111
|
+
// on its line, never a failure: it is served and being kept. A run from before `sync` was said: its error and phase.
|
|
112
|
+
const state = t.sync?.state ?? (t.error ? "failed" : t.copy?.phase === "copying" ? "syncing" : !t.copy ? "waiting" : "synced");
|
|
113
|
+
if (state === "failed") {
|
|
114
|
+
failed.push(`${t.table}: ${firstLine(t.error ?? "its sync failed (mason status says why)")}`);
|
|
100
115
|
continue;
|
|
101
116
|
}
|
|
102
117
|
if (t.copy?.phase === "copying") {
|
|
@@ -108,3 +123,24 @@ export function readiness(s) {
|
|
|
108
123
|
}
|
|
109
124
|
return { ready: failed.length === 0 && waiting.length === 0, failed, waiting };
|
|
110
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* WHERE A GENERATED TABLE IS, for `mason wait <table>`: ready once it is served and holds every base change Mason had
|
|
128
|
+
* synced when the wait began (`since`, ms), across its chain — nothing behind (`behindSince` null), or only changes
|
|
129
|
+
* committed after it began. A last run that failed, or a parent that blocks it, is a failure, so an agent is never left
|
|
130
|
+
* waiting for a run that cannot come. The marks are the run's clock and `since` this machine's: `mason wait` reads a run
|
|
131
|
+
* on this machine.
|
|
132
|
+
*/
|
|
133
|
+
export function generatedReadiness(g, since) {
|
|
134
|
+
const caught = g.serving && (g.behindSince === null || Date.parse(g.behindSince) > since);
|
|
135
|
+
if (caught)
|
|
136
|
+
return { ready: true, failed: [], waiting: [] };
|
|
137
|
+
const fail = (why) => ({ ready: false, failed: [`${g.table}: ${why}`], waiting: [] });
|
|
138
|
+
if (g.error !== null)
|
|
139
|
+
return fail(`its last run failed: ${firstLine(g.error)}`);
|
|
140
|
+
if (g.state === "refused" && !g.serving)
|
|
141
|
+
return fail(`refused: ${g.refusals[0]?.message ?? "mason status says why"}`);
|
|
142
|
+
if (g.waitingFor !== null && /(failed its last run|is refused)$/.test(g.waitingFor))
|
|
143
|
+
return fail(`not run, because ${g.waitingFor}`);
|
|
144
|
+
return { ready: false, failed: [], waiting: [`${g.table}: ${!g.serving ? g.waitingFor === null ? "being built" : `waiting for ${g.waitingFor}` :
|
|
145
|
+
`${Math.round(g.behindSeconds)} s behind its bases; its next run brings in what was synced before the wait`}`] };
|
|
146
|
+
}
|
package/dist/src/cli/registry.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* command's summary, in this order — so a command cannot exist without being listed, and tests/cli-help.test.ts holds
|
|
4
4
|
* each one to src/cli/command.ts's rules (its own --help, an example, every option documented).
|
|
5
5
|
*/
|
|
6
|
-
import { failed, parseCommand, reportText, wantsJson } from "./command.js";
|
|
6
|
+
import { FAILURE_CODES, SPEC_NEXT, failed, jsonText, parseCommand, reportText, specAnswer, specJson, wantsJson } from "./command.js";
|
|
7
7
|
import { removedStartFlags } from "../lib/table-sync.js";
|
|
8
8
|
import { sourcesTop } from "./sources.js";
|
|
9
9
|
import { dev } from "./dev.js";
|
|
@@ -12,6 +12,7 @@ import { ADD, LOGS, REMOVE, STATUS, STOP, TABLES, WAIT, add, logs, remove, statu
|
|
|
12
12
|
import { USER, user } from "./user.js";
|
|
13
13
|
import { MCP, mcp } from "./mcp.js";
|
|
14
14
|
import { SHARE, share } from "./share.js";
|
|
15
|
+
import { DEFAULT_UI_PORT } from "./ports.js";
|
|
15
16
|
import { UI, ui } from "./ui.js";
|
|
16
17
|
import { GUIDES, guideText } from "./guides.js";
|
|
17
18
|
import { DOCTOR, NODE_MIN, doctor } from "./doctor.js";
|
|
@@ -21,6 +22,10 @@ import { SYNC, sync } from "./sync.js";
|
|
|
21
22
|
import { USAGE, usage } from "./usage.js";
|
|
22
23
|
import { CANCEL, cancel } from "./cancel.js";
|
|
23
24
|
import { SERVE, serve } from "./serve.js";
|
|
25
|
+
import { LOGIN, LOGOUT, login, logout } from "./login.js";
|
|
26
|
+
import { DEPLOY, deploy } from "./deploy.js";
|
|
27
|
+
import { REPLICA, replica } from "./replica-hosted.js";
|
|
28
|
+
import { TOKEN, token } from "./token-hosted.js";
|
|
24
29
|
import { PROJECT_LINE } from "./dev.js";
|
|
25
30
|
import { version } from "./version.js";
|
|
26
31
|
import { didYouMean } from "../lib/did-you-mean.js";
|
|
@@ -32,7 +37,7 @@ export const DEV = {
|
|
|
32
37
|
needs: "project",
|
|
33
38
|
name: "start",
|
|
34
39
|
summary: "start Mason for this folder's project, in the background (mason init makes the project)",
|
|
35
|
-
usage: `usage: mason start [--attach] [--self-signed-tls] [--port <n>] [--json]
|
|
40
|
+
usage: `usage: mason start [--attach] [--self-signed-tls] [--ui-port <n>] [--json]
|
|
36
41
|
|
|
37
42
|
Starts Mason for the project of this folder (mason init made it): one process, which syncs the tables you add
|
|
38
43
|
(mason add) from the project's database into its own file, keeps them current as the sources file says, and answers
|
|
@@ -40,6 +45,17 @@ export const DEV = {
|
|
|
40
45
|
its sync: says replication). It returns once Mason is up; run it again any time (it says so when Mason is
|
|
41
46
|
already running). The synced data and Mason's state stay between runs: the next start resumes.
|
|
42
47
|
|
|
48
|
+
mason start
|
|
49
|
+
mason start --attach in the foreground: Ctrl-C stops it
|
|
50
|
+
MASON_PG_HOST=0.0.0.0 mason start --self-signed-tls the Postgres API for other machines, over TLS
|
|
51
|
+
|
|
52
|
+
--attach stay in the foreground (Ctrl-C stops Mason)
|
|
53
|
+
--self-signed-tls the Postgres API requires TLS, from a certificate made for this project in .mason/ (kept and
|
|
54
|
+
served again by every later start with the flag); not with MASON_PG_TLS_CERT / _KEY
|
|
55
|
+
--ui-port <n> the port of its web page and of the mason commands, on 127.0.0.1 (default ${DEFAULT_UI_PORT}, or the
|
|
56
|
+
next free one); MASON_UI_PORT on a server, never the Postgres API's (MASON_PG_PORT)
|
|
57
|
+
--json as data, once it is up: { running, pid, postgres: { url, host, port, database, sslmode }, next }
|
|
58
|
+
|
|
43
59
|
After its first sync, each table is kept current as the sources file's sync: says — at the top for every table, or
|
|
44
60
|
on a table's entry: off (the default, which mason init writes) reads it again only when you run mason sync <table>;
|
|
45
61
|
poll <interval> (poll 5s, poll 5m, poll 1h) polls it with SELECTs at that interval; replication reads its changes
|
|
@@ -56,7 +72,8 @@ export const DEV = {
|
|
|
56
72
|
created, altered or dropped — Mason makes only its slot. A read replica needs one (it takes no CREATE PUBLICATION).
|
|
57
73
|
|
|
58
74
|
--replication, --poll and --publication are gone: each answers with the line to write at the top of the sources
|
|
59
|
-
file (sync: replication, sync: poll 5m, publication: <name>) and exit 2, and starts nothing.
|
|
75
|
+
file (sync: replication, sync: poll 5m, publication: <name>) and exit 2, and starts nothing. --port is now
|
|
76
|
+
--ui-port: it answers with the line to run and exit 2.
|
|
60
77
|
|
|
61
78
|
The Postgres API's port is the project's: the first start takes 5433, or the next port free on every address when a
|
|
62
79
|
second Postgres holds it, and records it; every later start listens on the same port, so a BI tool's saved connection
|
|
@@ -68,44 +85,41 @@ export const DEV = {
|
|
|
68
85
|
MASON_PG_TLS_KEY (PEM files), or --self-signed-tls, which makes a certificate for the project in .mason/ and serves
|
|
69
86
|
it again at every later start with the flag. Every client then connects with sslmode=require. A team whose own proxy
|
|
70
87
|
or load balancer terminates TLS keeps it on 127.0.0.1, or sets MASON_PG_PLAIN=1 for a private network.
|
|
88
|
+
Behind a TCP proxy that sends the PROXY protocol v2, MASON_PG_PROXY_FROM=<cidr>[,<cidr>…] names the proxy's own
|
|
89
|
+
addresses: a connection from one must begin with the header, whose client the request log names, and a header from
|
|
90
|
+
any other address is refused.
|
|
71
91
|
|
|
72
92
|
${PROJECT_LINE}
|
|
73
|
-
|
|
74
|
-
mason start
|
|
75
|
-
mason start --attach in the foreground: Ctrl-C stops it
|
|
76
|
-
MASON_PG_HOST=0.0.0.0 mason start --self-signed-tls the Postgres API for other machines, over TLS
|
|
77
|
-
|
|
78
|
-
--attach stay in the foreground (Ctrl-C stops Mason)
|
|
79
|
-
--self-signed-tls the Postgres API requires TLS, from a certificate made for this project in .mason/ (kept and
|
|
80
|
-
served again by every later start with the flag); not with MASON_PG_TLS_CERT / _KEY
|
|
81
|
-
--port <n> the port the mason commands reach it on (default 4039, or the next free one)
|
|
82
|
-
--json as data, once it is up: { running, pid, postgres: { url, host, port, database, sslmode }, next }
|
|
83
93
|
`,
|
|
84
94
|
options: {
|
|
85
|
-
port: { type: "string" }, attach: { type: "boolean", default: false }, json: { type: "boolean", default: false },
|
|
95
|
+
"ui-port": { type: "string" }, attach: { type: "boolean", default: false }, json: { type: "boolean", default: false },
|
|
86
96
|
"self-signed-tls": { type: "boolean", default: false },
|
|
87
97
|
},
|
|
88
98
|
json: ["running", "pid", "postgres", "next"],
|
|
89
99
|
};
|
|
90
100
|
async function runDev(argv) {
|
|
91
|
-
// --replication, --poll and --publication are gone (issue 33): each answered with the edit to make in the sources
|
|
101
|
+
// --replication, --poll and --publication are gone (issue 33), and --port is --ui-port: each answered with the edit to make in the sources
|
|
92
102
|
// file, exit 2, before the options are parsed (it would be an unknown option there) and before anything starts. The
|
|
93
103
|
// file is read only when one is given. A decision: `mason start --replication --help` answers the refusal, not the
|
|
94
104
|
// usage — the flag is wrong whatever else the line asks
|
|
95
105
|
const removed = removedStartFlags(argv, () => startSourcesTop(process.cwd()));
|
|
96
|
-
if (removed !== null)
|
|
97
|
-
|
|
106
|
+
if (removed !== null) {
|
|
107
|
+
const { start, sync, ...said } = removed;
|
|
108
|
+
const next = sync ? [[start, "each table kept current as its sync: says"], ["mason help production", "sync: and publication:, and what each needs"]]
|
|
109
|
+
: [[start, "Mason, its web page and the commands on that port"]];
|
|
110
|
+
return failed("start", { code: "usage", ...said, next }, { json: wantsJson(argv, DEV) });
|
|
111
|
+
}
|
|
98
112
|
const f = parseCommand(DEV, argv);
|
|
99
113
|
if (typeof f === "number")
|
|
100
114
|
return f;
|
|
101
115
|
const v = f.values;
|
|
102
116
|
if (f.positionals.length) {
|
|
103
117
|
// the database is the project's now: a URL here is the old habit, answered with the new way
|
|
104
|
-
return failed("start", { message: "it takes no database URL: the project has one", code: "usage", why: "mason init <url> made it; .mason/config.json holds it", usage: DEV.usage }, { json: v.json });
|
|
118
|
+
return failed("start", { message: "it takes no database URL: the project has one", code: "usage", why: "mason init <url> made it; .mason/config.json holds it", fix: "run mason start alone", usage: DEV.usage }, { json: v.json });
|
|
105
119
|
}
|
|
106
|
-
const port = v
|
|
120
|
+
const port = v["ui-port"] === undefined ? DEFAULT_UI_PORT : Number(v["ui-port"]);
|
|
107
121
|
if (!Number.isInteger(port) || port < 1 || port > 65_535)
|
|
108
|
-
return failed("start", { message: `--port must be a port number (1-65535), not ${JSON.stringify(v
|
|
122
|
+
return failed("start", { message: `--ui-port must be a port number (1-65535), not ${JSON.stringify(v["ui-port"])}`, code: "usage" }, { json: v.json });
|
|
109
123
|
if (!v.attach)
|
|
110
124
|
return devInBackground(argv, { json: v.json, selfSignedTls: v["self-signed-tls"] });
|
|
111
125
|
return dev({ port, version: version(), selfSignedTls: v["self-signed-tls"] });
|
|
@@ -119,7 +133,7 @@ function startSourcesTop(cwd) {
|
|
|
119
133
|
export const HELP = {
|
|
120
134
|
name: "help",
|
|
121
135
|
summary: "the commands by step, or one command's usage: mason help add",
|
|
122
|
-
usage: `usage: mason help [<command> | <guide>]
|
|
136
|
+
usage: `usage: mason help [<command> | <guide>] [--json]
|
|
123
137
|
|
|
124
138
|
The commands by step (mason help), how to use one (mason help add — the same as mason add --help), or a guide,
|
|
125
139
|
offline, short enough to read whole and listing exactly what Mason accepts:
|
|
@@ -127,35 +141,74 @@ export const HELP = {
|
|
|
127
141
|
mason help
|
|
128
142
|
mason help add
|
|
129
143
|
${Object.entries(GUIDES).map(([k, v]) => ` mason help ${k.padEnd(14)} ${v}`).join("\n")}
|
|
144
|
+
mason help --json every command's spec as data (mason help agent says its shape)
|
|
145
|
+
|
|
146
|
+
--json as data: mason help, { version, commands: [<spec>…], guides: [{ name, summary }], failureCodes:
|
|
147
|
+
[{ code, meaning }], next }; mason help <command> (the same as mason <command> --help --json), { command:
|
|
148
|
+
<spec>, next }; mason help <guide>, { guide: { name, summary, text }, next }
|
|
130
149
|
`,
|
|
131
|
-
options: {},
|
|
150
|
+
options: { json: { type: "boolean", default: false } },
|
|
151
|
+
json: ["version", "commands", "guides", "failureCodes", "command", "guide", "next"],
|
|
132
152
|
};
|
|
153
|
+
/** `mason version` (also `--version`, `-v`): the CLI's version, as text or with --json as data. Answered by main.ts
|
|
154
|
+
* before the registry, so not one of COMMANDS; described here so `mason help version` and its --json read one spec. */
|
|
155
|
+
export const VERSION = {
|
|
156
|
+
name: "version",
|
|
157
|
+
summary: "the CLI's version",
|
|
158
|
+
usage: `usage: mason version [--json]
|
|
159
|
+
|
|
160
|
+
The CLI's version (mason --version and mason -v are the same):
|
|
161
|
+
|
|
162
|
+
mason version
|
|
163
|
+
|
|
164
|
+
--json as data: { version, next }
|
|
165
|
+
`,
|
|
166
|
+
options: { json: { type: "boolean", default: false } },
|
|
167
|
+
json: ["version", "next"],
|
|
168
|
+
};
|
|
169
|
+
/** `mason version` (and `--version`, `-v`): its line read by VERSION, as every command's is by its spec, so what it
|
|
170
|
+
* accepts is what its spec as data says — `--help`, `--help --json`, a wrong option or an argument refused (exit 2). */
|
|
171
|
+
export function runVersion(argv) {
|
|
172
|
+
const f = parseCommand(VERSION, argv);
|
|
173
|
+
if (typeof f === "number")
|
|
174
|
+
return f;
|
|
175
|
+
const json = f.values.json === true;
|
|
176
|
+
if (f.positionals.length)
|
|
177
|
+
return failed("version", { message: `it takes no argument: ${f.positionals.join(" ")}`, code: "usage", usage: VERSION.usage }, { json });
|
|
178
|
+
process.stdout.write(`${json ? jsonText(VERSION, { version: version() }) : version()}\n`);
|
|
179
|
+
return 0;
|
|
180
|
+
}
|
|
133
181
|
async function runHelp(argv) {
|
|
134
182
|
const f = parseCommand(HELP, argv);
|
|
135
183
|
if (typeof f === "number")
|
|
136
184
|
return f;
|
|
185
|
+
const json = f.values.json === true;
|
|
137
186
|
const [asked, ...rest] = f.positionals;
|
|
138
187
|
if (rest.length)
|
|
139
|
-
return failed("help", { message: "one command or guide at a time", code: "usage", usage: HELP.usage }, { json
|
|
188
|
+
return failed("help", { message: "one command or guide at a time", code: "usage", usage: HELP.usage }, { json });
|
|
140
189
|
if (asked === undefined) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
process.stdout.write(
|
|
190
|
+
if (!json) {
|
|
191
|
+
process.stdout.write(topUsage());
|
|
192
|
+
return 0;
|
|
193
|
+
}
|
|
194
|
+
process.stdout.write(`${jsonText(HELP, {
|
|
195
|
+
version: version(), commands: [...COMMANDS, VERSION].map(specJson), guides: Object.entries(GUIDES).map(([name, summary]) => ({ name, summary })),
|
|
196
|
+
failureCodes: Object.entries(FAILURE_CODES).map(([code, meaning]) => ({ code, meaning })),
|
|
197
|
+
}, [["mason help agent", "driving Mason from a coding agent: these shapes, the failures and the exit codes"]])}\n`);
|
|
146
198
|
return 0;
|
|
147
199
|
}
|
|
148
200
|
if (Object.hasOwn(GUIDES, asked)) {
|
|
149
|
-
|
|
201
|
+
const text = await guideText(asked);
|
|
202
|
+
process.stdout.write(json ? `${jsonText(HELP, { guide: { name: asked, summary: GUIDES[asked], text } }, SPEC_NEXT)}\n` : text);
|
|
150
203
|
return 0;
|
|
151
204
|
}
|
|
152
|
-
const cmd = COMMANDS.find((c) => c.name === asked);
|
|
205
|
+
const cmd = asked === "version" ? VERSION : COMMANDS.find((c) => c.name === asked);
|
|
153
206
|
if (cmd) {
|
|
154
|
-
process.stdout.write(cmd.usage);
|
|
207
|
+
process.stdout.write(json ? `${specAnswer(cmd)}\n` : cmd.usage);
|
|
155
208
|
return 0;
|
|
156
209
|
}
|
|
157
210
|
return failed("help", { message: `there is no command or guide ${JSON.stringify(asked)}${didYouMean(asked, [...COMMANDS.map((c) => c.name), ...Object.keys(GUIDES)])}`, code: "usage",
|
|
158
|
-
why: `the guides: ${Object.keys(GUIDES).join(", ")}`, next: [["mason help", "every command"]] }, { json
|
|
211
|
+
why: `the guides: ${Object.keys(GUIDES).join(", ")}`, fix: "name a command or a guide mason help lists", next: [["mason help", "every command"]] }, { json });
|
|
159
212
|
}
|
|
160
213
|
/**
|
|
161
214
|
* EVERY COMMAND, IN THE ORDER A FIRST SESSION NEEDS THEM (listed in the order a developer will likely need them, the
|
|
@@ -169,7 +222,7 @@ export const STEPS = [
|
|
|
169
222
|
["Add your tables", [{ ...TABLES, run: tables }, { ...ADD, run: add }, { ...REMOVE, run: remove }, { ...WAIT, run: wait }]],
|
|
170
223
|
["Connect", [{ ...USER, run: user }, { ...MCP, run: mcp }, { ...SHARE, run: share }]],
|
|
171
224
|
["Run", [{ ...STATUS, run: status }, { ...UI, run: ui }, { ...SYNC, run: sync }, { ...LOGS, run: logs }, { ...CANCEL, run: cancel }, { ...USAGE, run: (argv) => usage(argv) }, { ...STOP, run: stop }, { ...DESTROY, run: (argv) => destroy(argv) }]],
|
|
172
|
-
["Go to production", [{ ...SERVE, run: serve }]],
|
|
225
|
+
["Go to production", [{ ...SERVE, run: serve }, { ...LOGIN, run: login }, { ...LOGOUT, run: logout }, { ...REPLICA, run: replica }, { ...TOKEN, run: token }, { ...DEPLOY, run: deploy }]],
|
|
173
226
|
];
|
|
174
227
|
/** Every command, in the order `mason --help` lists them. */
|
|
175
228
|
export const COMMANDS = STEPS.flatMap(([, commands]) => commands);
|
|
@@ -190,7 +243,7 @@ export async function orientation(cwd, s = styleOut()) {
|
|
|
190
243
|
? [["Project", `${project.config.name}, files in ${project.folder}`], ["Mason", run ? `running (pid ${run.pid})` : "not running"]]
|
|
191
244
|
: [["Project", "none here: mason init makes this folder one"]];
|
|
192
245
|
const next = !project
|
|
193
|
-
? [["mason init", "make this folder a project (MASON_DATABASE_URL or DATABASE_URL from .env, or mason init
|
|
246
|
+
? [["mason init", "make this folder a project (MASON_DATABASE_URL or DATABASE_URL from .env, or its URL on stdin: mason init -)"],
|
|
194
247
|
["mason init demo", "or try it on a demo database Mason makes itself: nothing to install"],
|
|
195
248
|
["mason doctor", "what this machine needs for Mason, and whether it has it"]]
|
|
196
249
|
: !run ? [["mason start", "start Mason for this project, in the background"], ["mason status", "the project and its tables"]]
|
|
@@ -230,10 +283,10 @@ export function topUsage(s = styleOut()) {
|
|
|
230
283
|
listed.map(([step, es]) => ` ${s.bold(step)}\n${block(es)}`).join("\n\n"),
|
|
231
284
|
"",
|
|
232
285
|
para("mason <command> --help (or mason help <command>) says how to use one, with examples; the guides are offline and short enough to read whole."),
|
|
233
|
-
para("A command means the project of the folder it runs in (or above it)."),
|
|
286
|
+
para("A command means the project of the folder it runs in (or above it); mason -C <folder> <command> runs it as if started in that folder."),
|
|
234
287
|
para("Every command but help and mcp (whose stdout is the MCP protocol's) takes --json: one JSON object for a program, always with next (what to run " +
|
|
235
288
|
"next); a failure is { error: { code, message, why, fix }, next }. The exit code says how it went: 0 done, 1 failed " +
|
|
236
|
-
"with the reason, 2 a wrong command line, 124
|
|
289
|
+
"with the reason, 2 a wrong command line, 124 a wait timed out (mason wait, mason deploy, mason replica create, mason replica delete). Driving Mason from a coding agent: mason help agent."),
|
|
237
290
|
"",
|
|
238
291
|
].join("\n");
|
|
239
292
|
}
|