gemi 0.51.0 → 0.52.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/app/index.js +1 -1
- package/dist/bin/gemi.js +30 -1
- package/dist/bin/gemi.js.map +4 -4
- package/dist/broadcasting/index.js +1 -1
- package/dist/bun/plugin.js +1 -1
- package/dist/bun/preload.js +1 -1
- package/dist/{chunk-5v36t9j3.js → chunk-092pj2j2.js} +3 -3
- package/dist/{chunk-5v36t9j3.js.map → chunk-092pj2j2.js.map} +1 -1
- package/dist/{chunk-zwpx6z5f.js → chunk-0h3jr9d9.js} +2 -2
- package/dist/{chunk-8zd6d5nd.js.map → chunk-0h3jr9d9.js.map} +1 -1
- package/dist/{chunk-98sj7tkf.js → chunk-0tbwn6er.js} +3 -3
- package/dist/{chunk-98sj7tkf.js.map → chunk-0tbwn6er.js.map} +1 -1
- package/dist/{chunk-hwwp6x2s.js → chunk-1ab1ptq5.js} +2 -2
- package/dist/{chunk-hwwp6x2s.js.map → chunk-1ab1ptq5.js.map} +1 -1
- package/dist/{chunk-eby61053.js → chunk-1ce7veh9.js} +3 -3
- package/dist/{chunk-eby61053.js.map → chunk-1ce7veh9.js.map} +1 -1
- package/dist/{chunk-dk01r6se.js → chunk-23g7qdpg.js} +2 -2
- package/dist/{chunk-dk01r6se.js.map → chunk-23g7qdpg.js.map} +1 -1
- package/dist/{chunk-fs091xz7.js → chunk-2gw6cyyj.js} +2 -2
- package/dist/{chunk-fs091xz7.js.map → chunk-2gw6cyyj.js.map} +1 -1
- package/dist/chunk-2qn472j4.js +5 -0
- package/dist/{chunk-8gb1w19q.js.map → chunk-2qn472j4.js.map} +1 -1
- package/dist/{chunk-pkzj7c5w.js → chunk-2sfz64xh.js} +2 -2
- package/dist/{chunk-pkzj7c5w.js.map → chunk-2sfz64xh.js.map} +1 -1
- package/dist/{chunk-q0xq0pt0.js → chunk-2yth3kk2.js} +4 -4
- package/dist/{chunk-q0xq0pt0.js.map → chunk-2yth3kk2.js.map} +1 -1
- package/dist/{chunk-gdegb5t0.js → chunk-38ma6gg3.js} +2 -2
- package/dist/{chunk-gdegb5t0.js.map → chunk-38ma6gg3.js.map} +1 -1
- package/dist/{chunk-8vnbvfmd.js → chunk-3y9nf09k.js} +2 -2
- package/dist/{chunk-8vnbvfmd.js.map → chunk-3y9nf09k.js.map} +1 -1
- package/dist/{chunk-8zd6d5nd.js → chunk-4e61qbp4.js} +2 -2
- package/dist/{chunk-zwpx6z5f.js.map → chunk-4e61qbp4.js.map} +1 -1
- package/dist/{chunk-nbx9mgmr.js → chunk-4t6a82fh.js} +2 -2
- package/dist/{chunk-nbx9mgmr.js.map → chunk-4t6a82fh.js.map} +1 -1
- package/dist/{chunk-mrf9tjev.js → chunk-53e48ce8.js} +2 -2
- package/dist/{chunk-mrf9tjev.js.map → chunk-53e48ce8.js.map} +1 -1
- package/dist/{chunk-j33n723q.js → chunk-5s45prj5.js} +3 -3
- package/dist/{chunk-j33n723q.js.map → chunk-5s45prj5.js.map} +1 -1
- package/dist/{chunk-16bnbtyn.js → chunk-68w225rv.js} +2 -2
- package/dist/{chunk-16bnbtyn.js.map → chunk-68w225rv.js.map} +1 -1
- package/dist/{chunk-rxeyhwqz.js → chunk-69w4egkg.js} +3 -3
- package/dist/{chunk-rxeyhwqz.js.map → chunk-69w4egkg.js.map} +1 -1
- package/dist/{chunk-8m3wq94b.js → chunk-6scjrbw7.js} +2 -2
- package/dist/{chunk-8m3wq94b.js.map → chunk-6scjrbw7.js.map} +1 -1
- package/dist/{chunk-asgfqcwd.js → chunk-88pq4gmf.js} +2 -2
- package/dist/{chunk-asgfqcwd.js.map → chunk-88pq4gmf.js.map} +1 -1
- package/dist/{chunk-p0z35s59.js → chunk-8p6rqegw.js} +1 -1
- package/dist/{chunk-5t2c9194.js → chunk-a0sy9f9x.js} +1 -1
- package/dist/{chunk-g4gjn481.js → chunk-cmq5t8p9.js} +3 -3
- package/dist/{chunk-g4gjn481.js.map → chunk-cmq5t8p9.js.map} +1 -1
- package/dist/{chunk-zznpmnp7.js → chunk-d1ahbza9.js} +3 -3
- package/dist/{chunk-zznpmnp7.js.map → chunk-d1ahbza9.js.map} +1 -1
- package/dist/{chunk-an6nq9yd.js → chunk-ebcgdv00.js} +3 -3
- package/dist/{chunk-an6nq9yd.js.map → chunk-ebcgdv00.js.map} +1 -1
- package/dist/{chunk-3z4nwzvp.js → chunk-et0j6jx8.js} +3 -3
- package/dist/{chunk-3z4nwzvp.js.map → chunk-et0j6jx8.js.map} +1 -1
- package/dist/chunk-f29vd5mc.js +5 -0
- package/dist/{chunk-45c8sqb7.js.map → chunk-f29vd5mc.js.map} +2 -2
- package/dist/{chunk-gvf1d636.js → chunk-faa987e4.js} +2 -2
- package/dist/{chunk-gvf1d636.js.map → chunk-faa987e4.js.map} +1 -1
- package/dist/{chunk-6wm9bsz9.js → chunk-h5g05qwb.js} +2 -2
- package/dist/{chunk-6wm9bsz9.js.map → chunk-h5g05qwb.js.map} +1 -1
- package/dist/{chunk-x80n922n.js → chunk-h7jv3ame.js} +2 -2
- package/dist/{chunk-x80n922n.js.map → chunk-h7jv3ame.js.map} +1 -1
- package/dist/chunk-ha7n1c94.js +7 -0
- package/dist/{chunk-bm6q0q1q.js.map → chunk-ha7n1c94.js.map} +3 -3
- package/dist/chunk-hbycemy5.js +6 -0
- package/dist/{chunk-ygpzyxr6.js.map → chunk-hbycemy5.js.map} +2 -2
- package/dist/{chunk-3fcgg506.js → chunk-j1knf8gv.js} +3 -3
- package/dist/{chunk-3fcgg506.js.map → chunk-j1knf8gv.js.map} +1 -1
- package/dist/chunk-jdfe4dmh.js +5 -0
- package/dist/chunk-jdfe4dmh.js.map +30 -0
- package/dist/{chunk-1sbm38g3.js → chunk-kaxvw75w.js} +3 -3
- package/dist/{chunk-1sbm38g3.js.map → chunk-kaxvw75w.js.map} +1 -1
- package/dist/{chunk-c4r1zh9q.js → chunk-km6x37ws.js} +4 -4
- package/dist/{chunk-c4r1zh9q.js.map → chunk-km6x37ws.js.map} +1 -1
- package/dist/{chunk-eyvtf769.js → chunk-kxzwem3g.js} +2 -2
- package/dist/{chunk-eyvtf769.js.map → chunk-kxzwem3g.js.map} +1 -1
- package/dist/{chunk-hnw1jy86.js → chunk-msz8rngs.js} +2 -2
- package/dist/{chunk-hnw1jy86.js.map → chunk-msz8rngs.js.map} +1 -1
- package/dist/chunk-mt2755rs.js +5 -0
- package/dist/{chunk-g0w87jtw.js.map → chunk-mt2755rs.js.map} +4 -3
- package/dist/chunk-p22rkf1g.js +8 -0
- package/dist/chunk-p22rkf1g.js.map +11 -0
- package/dist/{chunk-kv8p4h79.js → chunk-q09mmr05.js} +3 -3
- package/dist/{chunk-kv8p4h79.js.map → chunk-q09mmr05.js.map} +1 -1
- package/dist/chunk-qj4mmner.js +4 -0
- package/dist/{chunk-6x6gb7zg.js.map → chunk-qj4mmner.js.map} +3 -3
- package/dist/{chunk-d3tf618w.js → chunk-qx6sahe0.js} +2 -2
- package/dist/{chunk-d3tf618w.js.map → chunk-qx6sahe0.js.map} +1 -1
- package/dist/{chunk-w7fpm2pw.js → chunk-qyaqg07q.js} +2 -2
- package/dist/{chunk-w7fpm2pw.js.map → chunk-qyaqg07q.js.map} +1 -1
- package/dist/{chunk-a6w4tpwz.js → chunk-rq770c71.js} +2 -2
- package/dist/{chunk-a6w4tpwz.js.map → chunk-rq770c71.js.map} +1 -1
- package/dist/{chunk-zezdm4ep.js → chunk-t4n25km9.js} +3 -3
- package/dist/{chunk-zezdm4ep.js.map → chunk-t4n25km9.js.map} +1 -1
- package/dist/chunk-tmk4zfz9.js +33 -0
- package/dist/chunk-tmk4zfz9.js.map +18 -0
- package/dist/{chunk-k4mjcvsm.js → chunk-ttzkn1qd.js} +2 -2
- package/dist/{chunk-k4mjcvsm.js.map → chunk-ttzkn1qd.js.map} +1 -1
- package/dist/{chunk-94k8pjmf.js → chunk-tztqkat2.js} +3 -3
- package/dist/{chunk-94k8pjmf.js.map → chunk-tztqkat2.js.map} +1 -1
- package/dist/{chunk-ybqftmjx.js → chunk-vnjvd82s.js} +2 -2
- package/dist/{chunk-ybqftmjx.js.map → chunk-vnjvd82s.js.map} +1 -1
- package/dist/{chunk-0k5jhyry.js → chunk-xsmt86n3.js} +2 -2
- package/dist/{chunk-0k5jhyry.js.map → chunk-xsmt86n3.js.map} +1 -1
- package/dist/{chunk-ntr81h76.js → chunk-z4q1ty6w.js} +2 -2
- package/dist/{chunk-ntr81h76.js.map → chunk-z4q1ty6w.js.map} +1 -1
- package/dist/{chunk-53w1njqz.js → chunk-zhsaz9cr.js} +1 -1
- package/dist/config/index.js +1 -1
- package/dist/console/Command.d.ts +143 -0
- package/dist/console/Command.d.ts.map +1 -0
- package/dist/console/CommandRegistry.d.ts +46 -0
- package/dist/console/CommandRegistry.d.ts.map +1 -0
- package/dist/console/builder.d.ts +115 -0
- package/dist/console/builder.d.ts.map +1 -0
- package/dist/console/builder.test-d.d.ts +2 -0
- package/dist/console/builder.test-d.d.ts.map +1 -0
- package/dist/console/config.d.ts +40 -0
- package/dist/console/config.d.ts.map +1 -0
- package/dist/console/context.d.ts +90 -0
- package/dist/console/context.d.ts.map +1 -0
- package/dist/console/errors.d.ts +19 -0
- package/dist/console/errors.d.ts.map +1 -0
- package/dist/console/parse.d.ts +65 -0
- package/dist/console/parse.d.ts.map +1 -0
- package/dist/console/run.d.ts +2 -0
- package/dist/console/run.d.ts.map +1 -0
- package/dist/console/run.js +16 -0
- package/dist/console/run.js.map +14 -0
- package/dist/console/runner.d.ts +41 -0
- package/dist/console/runner.d.ts.map +1 -0
- package/dist/console/usage.d.ts +7 -0
- package/dist/console/usage.d.ts.map +1 -0
- package/dist/container/index.js +2 -2
- package/dist/container/index.js.map +1 -1
- package/dist/database/index.js +2 -2
- package/dist/database/index.js.map +1 -1
- package/dist/email/index.js +2 -2
- package/dist/email/index.js.map +2 -2
- package/dist/facades/index.js +2 -2
- package/dist/facades/index.js.map +1 -1
- package/dist/foundation/index.js +2 -2
- package/dist/foundation/index.js.map +1 -1
- package/dist/http/ViewRouter.d.ts.map +1 -1
- package/dist/http/index.js +2 -2
- package/dist/http/index.js.map +1 -1
- package/dist/i18n/index.js +2 -2
- package/dist/i18n/index.js.map +2 -2
- package/dist/kernel/Kernel.d.ts +29 -0
- package/dist/kernel/Kernel.d.ts.map +1 -1
- package/dist/kernel/index.js +3 -3
- package/dist/kernel/index.js.map +3 -3
- package/dist/orm/compile/nested-writes.d.ts +11 -3
- package/dist/orm/compile/nested-writes.d.ts.map +1 -1
- package/dist/orm/index.js +3 -3
- package/dist/orm/index.js.map +4 -4
- package/dist/server/index.js +2 -2
- package/dist/server/index.js.map +1 -1
- package/dist/services/cron/ScheduleServiceProvider.d.ts.map +1 -1
- package/dist/services/discovery.d.ts +15 -0
- package/dist/services/discovery.d.ts.map +1 -1
- package/dist/services/index.d.ts +8 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +11 -11
- package/dist/services/index.js.map +2 -2
- package/dist/support/Service.d.ts +90 -0
- package/dist/support/Service.d.ts.map +1 -0
- package/dist/support/discover.d.ts +9 -1
- package/dist/support/discover.d.ts.map +1 -1
- package/dist/support/index.d.ts +1 -0
- package/dist/support/index.d.ts.map +1 -1
- package/dist/support/index.js +2 -2
- package/dist/support/index.js.map +4 -3
- package/package.json +2 -1
- package/dist/chunk-45c8sqb7.js +0 -5
- package/dist/chunk-6fym5bfg.js +0 -23
- package/dist/chunk-6fym5bfg.js.map +0 -36
- package/dist/chunk-6x6gb7zg.js +0 -4
- package/dist/chunk-8gb1w19q.js +0 -5
- package/dist/chunk-bm6q0q1q.js +0 -7
- package/dist/chunk-g0w87jtw.js +0 -5
- package/dist/chunk-j4rkk01q.js +0 -5
- package/dist/chunk-j4rkk01q.js.map +0 -10
- package/dist/chunk-ygpzyxr6.js +0 -6
- /package/dist/{chunk-p0z35s59.js.map → chunk-8p6rqegw.js.map} +0 -0
- /package/dist/{chunk-5t2c9194.js.map → chunk-a0sy9f9x.js.map} +0 -0
- /package/dist/{chunk-53w1njqz.js.map → chunk-zhsaz9cr.js.map} +0 -0
package/dist/config/index.js
CHANGED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A command an operator runs by hand: `gemi run backfill-avatars --dry-run`.
|
|
3
|
+
*
|
|
4
|
+
* This file is the *runtime* shape — the base class discovery walks the
|
|
5
|
+
* prototype chain for, and the statics the runner reads. It is not the
|
|
6
|
+
* authoring surface. Applications write commands with `defineCommand` in
|
|
7
|
+
* `./builder`, which produces one of these; see that file for why the authoring
|
|
8
|
+
* surface is a builder and not a class body.
|
|
9
|
+
*
|
|
10
|
+
* ### Why the schema is descriptors and not a signature string
|
|
11
|
+
*
|
|
12
|
+
* Laravel spells this `protected $signature = 'mail:send {user} {--queue}'`, and
|
|
13
|
+
* the string is the whole schema. It reads beautifully and TypeScript cannot see
|
|
14
|
+
* a word of it: a brace in the wrong place is a parse error the first time
|
|
15
|
+
* somebody runs the command, months after it was written, and the parser is a
|
|
16
|
+
* piece of framework surface with its own failure modes. The descriptors below
|
|
17
|
+
* say the same thing in a shape the compiler checks — and, through the builder,
|
|
18
|
+
* in a shape that types the handler's own body.
|
|
19
|
+
*
|
|
20
|
+
* ### Why the name is declared and never falls back to the class name
|
|
21
|
+
*
|
|
22
|
+
* `Job` keys off `static name`, which shadows `Function.name`. That is the right
|
|
23
|
+
* trade there: a job's name *is* its class name, and the shadow is what makes it
|
|
24
|
+
* a string literal that survives minification. Here it is the wrong one twice
|
|
25
|
+
* over. A command's name is a CLI token (`backfill-avatars`, `db:seed`) with no
|
|
26
|
+
* relationship to the class name, so an implicit fallback would only ever be
|
|
27
|
+
* wrong; and shadowing `Function.name` costs the one error anybody will actually
|
|
28
|
+
* hit — "two commands claim `db:seed`" — the ability to say *which two*.
|
|
29
|
+
*
|
|
30
|
+
* ### The minification hazard does not reach here
|
|
31
|
+
*
|
|
32
|
+
* `services/discovery.ts` warns about a job that never declared `static name`,
|
|
33
|
+
* because two halves have to agree on one string across a bundler: the
|
|
34
|
+
* dispatcher, minified into `dist/server/server.mjs`, and discovery, reading the
|
|
35
|
+
* source. A command has one half — a human types the name and it is matched
|
|
36
|
+
* against discovery-on-source. Nothing in the server bundle names a command, and
|
|
37
|
+
* `gemi build` keeps `app/commands` out of the bundle anyway. So there is no
|
|
38
|
+
* `warnIfNameWillNotSurviveTheBuild` equivalent below, and adding one by analogy
|
|
39
|
+
* would be noise.
|
|
40
|
+
*
|
|
41
|
+
* If a `Command.call("db:seed")` reachable from a controller is ever added, that
|
|
42
|
+
* changes — and `commandName` being a declared string literal already covers it,
|
|
43
|
+
* which is one more reason not to fall back to the class binding.
|
|
44
|
+
*/
|
|
45
|
+
/** A positional argument. `variadic` collects the rest and must come last. */
|
|
46
|
+
export interface ArgSpec {
|
|
47
|
+
description?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Missing it is a usage error rather than an `undefined` in the handler.
|
|
50
|
+
* Mutually exclusive with `default` — an argument that is supplied when absent
|
|
51
|
+
* cannot also be absent, and declaring both is refused rather than silently
|
|
52
|
+
* resolved in one direction.
|
|
53
|
+
*/
|
|
54
|
+
required?: boolean;
|
|
55
|
+
/** Collects every remaining positional. Only legal on the last argument. */
|
|
56
|
+
variadic?: boolean;
|
|
57
|
+
/** Supplied when the argument is absent, which makes it always present. */
|
|
58
|
+
default?: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A flag. The `type` decides both how the value is parsed and what the handler
|
|
62
|
+
* sees, which is why it is required rather than defaulted to `string`: a
|
|
63
|
+
* `--dry-run` declared without a type would arrive as the string `""`, and every
|
|
64
|
+
* `if (options.dryRun)` over it would be wrong in the one direction nobody
|
|
65
|
+
* tests.
|
|
66
|
+
*/
|
|
67
|
+
export type OptionSpec = {
|
|
68
|
+
type: "boolean";
|
|
69
|
+
description?: string;
|
|
70
|
+
/** Single character, without the dash. */
|
|
71
|
+
alias?: string;
|
|
72
|
+
default?: boolean;
|
|
73
|
+
} | {
|
|
74
|
+
type: "string";
|
|
75
|
+
description?: string;
|
|
76
|
+
alias?: string;
|
|
77
|
+
default?: string;
|
|
78
|
+
required?: boolean;
|
|
79
|
+
} | {
|
|
80
|
+
type: "number";
|
|
81
|
+
description?: string;
|
|
82
|
+
alias?: string;
|
|
83
|
+
default?: number;
|
|
84
|
+
required?: boolean;
|
|
85
|
+
};
|
|
86
|
+
/** An argument as stored on the class: the spec, plus the name it was given. */
|
|
87
|
+
export type CommandArgument = ArgSpec & {
|
|
88
|
+
name: string;
|
|
89
|
+
};
|
|
90
|
+
/** An option as stored on the class: the spec, plus the name it was given. */
|
|
91
|
+
export type CommandOption = OptionSpec & {
|
|
92
|
+
name: string;
|
|
93
|
+
};
|
|
94
|
+
/** What a handler may return. `void` and `0` mean the same thing. */
|
|
95
|
+
export type CommandResult = Promise<number | void> | number | void;
|
|
96
|
+
/**
|
|
97
|
+
* A command that failed on purpose.
|
|
98
|
+
*
|
|
99
|
+
* The distinction `bin/check-models.ts` draws between `CheckModelsError` and
|
|
100
|
+
* everything else, in the one place an application can reach it: this is a
|
|
101
|
+
* sentence written for the terminal and is printed without a stack, while
|
|
102
|
+
* anything else that escapes a handler is a bug and keeps its stack.
|
|
103
|
+
*/
|
|
104
|
+
export declare class CommandFailed extends Error {
|
|
105
|
+
readonly code: number;
|
|
106
|
+
constructor(message: string, code?: number);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The base class. Discovery finds subclasses of this; the runner reads the
|
|
110
|
+
* statics and calls `run`.
|
|
111
|
+
*
|
|
112
|
+
* Subclassing it by hand works and is what `defineCommand` does under the hood,
|
|
113
|
+
* but it gives up the typing that is the whole point of the builder — `run`'s
|
|
114
|
+
* parameter cannot be inferred from statics declared on the subclass, because
|
|
115
|
+
* TypeScript does not contextually type an overriding method's parameters. Write
|
|
116
|
+
* commands with `defineCommand`.
|
|
117
|
+
*/
|
|
118
|
+
export declare class Command {
|
|
119
|
+
/**
|
|
120
|
+
* The name typed on the command line. Mandatory: there is no sensible default
|
|
121
|
+
* and the class name is not one.
|
|
122
|
+
*/
|
|
123
|
+
static commandName: string;
|
|
124
|
+
/** One line, printed by `gemi run` with no name and at the top of `--help`. */
|
|
125
|
+
static description: string;
|
|
126
|
+
/** Positional arguments, in order. A variadic one must be last. */
|
|
127
|
+
static args: CommandArgument[];
|
|
128
|
+
/** Flags. Declared in camelCase; `dryRun` is spelled `--dry-run` on the CLI. */
|
|
129
|
+
static options: CommandOption[];
|
|
130
|
+
/**
|
|
131
|
+
* The body.
|
|
132
|
+
*
|
|
133
|
+
* Runs inside `kernel.run()`, so `app(MailManager)` and a model read resolve
|
|
134
|
+
* exactly as they do in a request handler. Resolve services *here* rather than
|
|
135
|
+
* at module load: a command file is imported by discovery before the container
|
|
136
|
+
* has finished booting, and a service captured at the top of the file is
|
|
137
|
+
* captured from a container that is not ready.
|
|
138
|
+
*/
|
|
139
|
+
run(_ctx: any): CommandResult;
|
|
140
|
+
}
|
|
141
|
+
/** The type of a class `defineCommand(...).handle(...)` produces. */
|
|
142
|
+
export type CommandClass = typeof Command;
|
|
143
|
+
//# sourceMappingURL=Command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Command.d.ts","sourceRoot":"","sources":["../../console/Command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,8EAA8E;AAC9E,MAAM,WAAW,OAAO;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAClB;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEN,gFAAgF;AAChF,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzD,8EAA8E;AAC9E,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1D,qEAAqE;AACrE,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC;AAEnE;;;;;;;GAOG;AACH,qBAAa,aAAc,SAAQ,KAAK;IAGpC,QAAQ,CAAC,IAAI,EAAE,MAAM;gBADrB,OAAO,EAAE,MAAM,EACN,IAAI,GAAE,MAAU;CAK5B;AAED;;;;;;;;;GASG;AACH,qBAAa,OAAO;IAClB;;;OAGG;IACH,MAAM,CAAC,WAAW,SAAW;IAE7B,+EAA+E;IAC/E,MAAM,CAAC,WAAW,SAAM;IAExB,mEAAmE;IACnE,MAAM,CAAC,IAAI,EAAE,eAAe,EAAE,CAAM;IAEpC,gFAAgF;IAChF,MAAM,CAAC,OAAO,EAAE,aAAa,EAAE,CAAM;IAErC;;;;;;;;OAQG;IACH,GAAG,CAAC,IAAI,EAAE,GAAG,GAAG,aAAa;CAC9B;AAED,qEAAqE;AACrE,MAAM,MAAM,YAAY,GAAG,OAAO,OAAO,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { CommandClass } from "./Command";
|
|
2
|
+
/**
|
|
3
|
+
* The commands an application has, and the rules about what a command may be.
|
|
4
|
+
*
|
|
5
|
+
* A plain class, not a container service, and that is a decision rather than an
|
|
6
|
+
* omission. `QueueManager` and `Scheduler` are bound and populated during boot
|
|
7
|
+
* because the server needs them: a dispatch or a tick can arrive at any moment
|
|
8
|
+
* after boot finishes. A command registry has exactly one consumer — `gemi run`,
|
|
9
|
+
* which builds one, uses it, and exits. Making it a boot step would import every
|
|
10
|
+
* file under `app/commands` into every `gemi dev`, every `gemi start` and every
|
|
11
|
+
* deployed server process, on every start, for code no request will ever reach.
|
|
12
|
+
* That is the cost `bin/check-models.ts` argues against at length, and there is
|
|
13
|
+
* no benefit here to weigh against it.
|
|
14
|
+
*
|
|
15
|
+
* What the class is for is the rules below, and the `commands` getter — the same
|
|
16
|
+
* seam `QueueManager.registeredJobs` and `Scheduler.jobs` exist to give a test:
|
|
17
|
+
* what was actually taken, rather than what a walk would find if asked again.
|
|
18
|
+
*/
|
|
19
|
+
export declare class CommandRegistry {
|
|
20
|
+
private readonly byName;
|
|
21
|
+
/**
|
|
22
|
+
* @throws ConsoleError naming the offending class for a command that cannot
|
|
23
|
+
* be run.
|
|
24
|
+
*
|
|
25
|
+
* Every refusal here is loud and immediate. There is a human at a terminal,
|
|
26
|
+
* and every one of these is a mistake in the application's own source that
|
|
27
|
+
* they are better off seeing now than after the command they meant to run
|
|
28
|
+
* silently did not exist.
|
|
29
|
+
*
|
|
30
|
+
* `ConsoleError` rather than a plain `Error` so the runner prints the message
|
|
31
|
+
* alone. These are sentences with the fix named in them, and burying one under
|
|
32
|
+
* `at new CommandRegistry (…)` plus the runner's own frames points the reader
|
|
33
|
+
* at framework code when the mistake is in their `app/commands`.
|
|
34
|
+
*/
|
|
35
|
+
constructor(commands: CommandClass[]);
|
|
36
|
+
/** What was actually taken. */
|
|
37
|
+
get commands(): CommandClass[];
|
|
38
|
+
get(name: string): CommandClass | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* The nearest names to something that did not match, for the "did you mean"
|
|
41
|
+
* line. Prefix matches first, then substring, then names sharing a namespace —
|
|
42
|
+
* `db:sed` should find `db:seed`, and a typo in a namespace is the common case.
|
|
43
|
+
*/
|
|
44
|
+
suggest(name: string): string[];
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=CommandRegistry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CommandRegistry.d.ts","sourceRoot":"","sources":["../../console/CommandRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAG9C;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmC;IAE1D;;;;;;;;;;;;;OAaG;gBACS,QAAQ,EAAE,YAAY,EAAE;IAgCpC,+BAA+B;IAC/B,IAAI,QAAQ,IAAI,YAAY,EAAE,CAE7B;IAED,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAI3C;;;;OAIG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE;CAuBhC"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { type ArgSpec, type CommandClass, type CommandResult, type OptionSpec } from "./Command";
|
|
2
|
+
import type { CommandContext } from "./context";
|
|
3
|
+
/**
|
|
4
|
+
* Declaring a command, in the one shape that can type its own body.
|
|
5
|
+
*
|
|
6
|
+
* ### Why a builder and not a class
|
|
7
|
+
*
|
|
8
|
+
* The point of declaring arguments and options at all is that the handler then
|
|
9
|
+
* knows what it has. A class body cannot deliver that, and the reason is a hard
|
|
10
|
+
* TypeScript rule rather than a matter of arrangement: **TypeScript does not
|
|
11
|
+
* contextually type a method's parameters from the base-class method it
|
|
12
|
+
* overrides.** So both class-shaped designs fail in the same place:
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* class Backfill extends Command {
|
|
16
|
+
* static args = [{ name: "since", required: true }];
|
|
17
|
+
* async run(ctx) {} // implicit any
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* class Backfill extends Command.define({ since: { required: true } }) {
|
|
21
|
+
* async run(ctx) {} // still implicit any — the
|
|
22
|
+
* } // configured base's signature does
|
|
23
|
+
* // not flow into the override
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* The author's only way out is to restate the type by hand, which is the
|
|
27
|
+
* ceremony the descriptors existed to remove — and which nothing enforces, so a
|
|
28
|
+
* wrong restatement compiles.
|
|
29
|
+
*
|
|
30
|
+
* A **function expression passed as an argument is contextually typed**. That is
|
|
31
|
+
* the entire mechanism here, and it is why this is a builder rather than sugar:
|
|
32
|
+
* `.handle(fn)` carries the accumulated argument and option types into `fn`'s
|
|
33
|
+
* parameter with nothing written by hand and nothing to keep in step.
|
|
34
|
+
*
|
|
35
|
+
* ### What it produces
|
|
36
|
+
*
|
|
37
|
+
* A `Command` subclass, so nothing downstream knows the difference: discovery
|
|
38
|
+
* finds it by prototype chain, and the registry, parser and `--help` read the
|
|
39
|
+
* same statics they would read off a hand-written class. The builder is an
|
|
40
|
+
* authoring surface over one runtime shape, not a second mechanism.
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* Marks a builder that was never finished. `discoverCommands` looks for this on
|
|
44
|
+
* the exports it is about to discard — see the note on `handle` below.
|
|
45
|
+
*/
|
|
46
|
+
export declare const BUILDER_BRAND: unique symbol;
|
|
47
|
+
/** Flattens an accumulated intersection so editor hovers stay readable. */
|
|
48
|
+
type Simplify<T> = {
|
|
49
|
+
[K in keyof T]: T[K];
|
|
50
|
+
} & {};
|
|
51
|
+
/**
|
|
52
|
+
* What the handler sees for one argument.
|
|
53
|
+
*
|
|
54
|
+
* The order matters: `variadic` first because it wins over everything, then the
|
|
55
|
+
* two ways an argument is guaranteed present. Anything else can be absent and
|
|
56
|
+
* says so.
|
|
57
|
+
*/
|
|
58
|
+
export type ArgValue<S> = S extends {
|
|
59
|
+
variadic: true;
|
|
60
|
+
} ? string[] : S extends {
|
|
61
|
+
required: true;
|
|
62
|
+
} ? string : S extends {
|
|
63
|
+
default: string;
|
|
64
|
+
} ? string : string | undefined;
|
|
65
|
+
/** What the handler sees for one option. */
|
|
66
|
+
export type OptionValue<S> = S extends {
|
|
67
|
+
type: "boolean";
|
|
68
|
+
} ? boolean : S extends {
|
|
69
|
+
type: "number";
|
|
70
|
+
} ? S extends {
|
|
71
|
+
default: number;
|
|
72
|
+
} ? number : S extends {
|
|
73
|
+
required: true;
|
|
74
|
+
} ? number : number | undefined : S extends {
|
|
75
|
+
default: string;
|
|
76
|
+
} ? string : S extends {
|
|
77
|
+
required: true;
|
|
78
|
+
} ? string : string | undefined;
|
|
79
|
+
export interface CommandBuilder<A extends Record<string, unknown>, O extends Record<string, unknown>> {
|
|
80
|
+
/** One line, shown in the listing and at the top of `--help`. */
|
|
81
|
+
describe(text: string): CommandBuilder<A, O>;
|
|
82
|
+
/**
|
|
83
|
+
* A positional argument. Declared in the order it is read from the command
|
|
84
|
+
* line; a `variadic` one must be last.
|
|
85
|
+
*/
|
|
86
|
+
arg<N extends string, const S extends ArgSpec = {}>(name: N, spec?: S): CommandBuilder<A & {
|
|
87
|
+
[K in N]: ArgValue<S>;
|
|
88
|
+
}, O>;
|
|
89
|
+
/**
|
|
90
|
+
* A flag. Declared in camelCase and spelled in kebab-case on the command line,
|
|
91
|
+
* so `.option("dryRun", …)` is `--dry-run` for the operator and
|
|
92
|
+
* `options.dryRun` in the handler — no string key nobody can autocomplete, and
|
|
93
|
+
* no CLI flag with a capital letter in it.
|
|
94
|
+
*/
|
|
95
|
+
option<N extends string, const S extends OptionSpec>(name: N, spec: S): CommandBuilder<A, O & {
|
|
96
|
+
[K in N]: OptionValue<S>;
|
|
97
|
+
}>;
|
|
98
|
+
/**
|
|
99
|
+
* The body, and the terminal of the chain.
|
|
100
|
+
*
|
|
101
|
+
* A builder that never reaches here is not a class, so discovery would skip it
|
|
102
|
+
* and the command would simply vanish from `gemi run` with nothing raised —
|
|
103
|
+
* the silence #322 and #323 exist to remove, reintroduced by a new authoring
|
|
104
|
+
* surface. `discoverCommands` therefore looks for `BUILDER_BRAND` among the
|
|
105
|
+
* exports it discards and refuses the file by name.
|
|
106
|
+
*/
|
|
107
|
+
handle(fn: (ctx: CommandContext<Simplify<A>, Simplify<O>>) => CommandResult): CommandClass;
|
|
108
|
+
}
|
|
109
|
+
export declare function defineCommand(name: string): CommandBuilder<{}, {}>;
|
|
110
|
+
/** True for a builder that never called `.handle()`. */
|
|
111
|
+
export declare function isUnfinishedBuilder(value: unknown): value is {
|
|
112
|
+
[BUILDER_BRAND]: string;
|
|
113
|
+
};
|
|
114
|
+
export {};
|
|
115
|
+
//# sourceMappingURL=builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../console/builder.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,OAAO,EAEZ,KAAK,YAAY,EAEjB,KAAK,aAAa,EAClB,KAAK,UAAU,EAChB,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,OAAO,MAA2C,CAAC;AAE/E,2EAA2E;AAC3E,KAAK,QAAQ,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAE,GAAG,EAAE,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,CAAC,SAAS;IAAE,QAAQ,EAAE,IAAI,CAAA;CAAE,GAClD,MAAM,EAAE,GACR,CAAC,SAAS;IAAE,QAAQ,EAAE,IAAI,CAAA;CAAE,GAC1B,MAAM,GACN,CAAC,SAAS;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAC3B,MAAM,GACN,MAAM,GAAG,SAAS,CAAC;AAE3B,4CAA4C;AAC5C,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACtD,OAAO,GACP,CAAC,SAAS;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAC1B,CAAC,SAAS;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAC3B,MAAM,GACN,CAAC,SAAS;IAAE,QAAQ,EAAE,IAAI,CAAA;CAAE,GAC1B,MAAM,GACN,MAAM,GAAG,SAAS,GACtB,CAAC,SAAS;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAC3B,MAAM,GACN,CAAC,SAAS;IAAE,QAAQ,EAAE,IAAI,CAAA;CAAE,GAC1B,MAAM,GACN,MAAM,GAAG,SAAS,CAAC;AAE7B,MAAM,WAAW,cAAc,CAC7B,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAEjC,iEAAiE;IACjE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAE7C;;;OAGG;IACH,GAAG,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,CAAC,CAAC,SAAS,OAAO,GAAG,EAAE,EAChD,IAAI,EAAE,CAAC,EACP,IAAI,CAAC,EAAE,CAAC,GACP,cAAc,CAAC,CAAC,GAAG;SAAG,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;KAAE,EAAE,CAAC,CAAC,CAAC;IAEpD;;;;;OAKG;IACH,MAAM,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,CAAC,CAAC,SAAS,UAAU,EACjD,IAAI,EAAE,CAAC,EACP,IAAI,EAAE,CAAC,GACN,cAAc,CAAC,CAAC,EAAE,CAAC,GAAG;SAAG,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC;KAAE,CAAC,CAAC;IAEvD;;;;;;;;OAQG;IACH,MAAM,CACJ,EAAE,EAAE,CAAC,GAAG,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,aAAa,GACnE,YAAY,CAAC;CACjB;AA4BD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAAC,EAAE,EAAE,EAAE,CAAC,CAqIlE;AAED,wDAAwD;AACxD,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI;IAAE,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,CAMtC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builder.test-d.d.ts","sourceRoot":"","sources":["../../console/builder.test-d.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { CommandClass } from "./Command";
|
|
2
|
+
export interface CommandConfig {
|
|
3
|
+
/**
|
|
4
|
+
* The `Command` classes this application can run, or nothing.
|
|
5
|
+
*
|
|
6
|
+
* ### The rule, exactly
|
|
7
|
+
*
|
|
8
|
+
* **Declared wins, and `[]` is declared.** A `commands` that is present is
|
|
9
|
+
* used verbatim and no directory is read — the escape hatch for an app whose
|
|
10
|
+
* commands live somewhere the walk cannot reach, and for a deploy that ships
|
|
11
|
+
* no source. An empty array means an app with no commands and says so; it does
|
|
12
|
+
* not mean "find some".
|
|
13
|
+
*
|
|
14
|
+
* **Absent or `undefined` discovers.** `undefined` counts as absent for the
|
|
15
|
+
* same reason `withDefaults` treats it that way everywhere else: a key spread
|
|
16
|
+
* in from an optional value is an omission, not an instruction.
|
|
17
|
+
*
|
|
18
|
+
* ### One way this differs from `queue` and `schedule`
|
|
19
|
+
*
|
|
20
|
+
* There, the cost of the list and the directory disagreeing is silence — a
|
|
21
|
+
* dispatch dropped with a line on stderr nobody reads, a report that stops
|
|
22
|
+
* arriving. Here it is a person typing `gemi run thing` and being told, in
|
|
23
|
+
* those words, that there is no command called `thing`. The rule is kept for
|
|
24
|
+
* consistency and for the source-less-deploy case, not because the stakes are
|
|
25
|
+
* the same.
|
|
26
|
+
*/
|
|
27
|
+
commands?: CommandClass[];
|
|
28
|
+
/**
|
|
29
|
+
* Where to look when `commands` was not declared. Relative to the project
|
|
30
|
+
* root, or absolute.
|
|
31
|
+
*
|
|
32
|
+
* Every `.ts`/`.tsx` file underneath is imported when a command is run, so
|
|
33
|
+
* this wants to be a directory of command declarations rather than a directory
|
|
34
|
+
* that merely contains some.
|
|
35
|
+
*/
|
|
36
|
+
commandsDir?: string;
|
|
37
|
+
}
|
|
38
|
+
export declare function defineCommandConfig(config: CommandConfig): CommandConfig;
|
|
39
|
+
export declare function commandConfigDefaults(): Required<CommandConfig>;
|
|
40
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../console/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAU9C,MAAM,WAAW,aAAa;IAC5B;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;IAE1B;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa,CAExE;AAED,wBAAgB,qBAAqB,IAAI,QAAQ,CAAC,aAAa,CAAC,CAK/D"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single argument a command handler receives.
|
|
3
|
+
*
|
|
4
|
+
* ### Why the output helpers live here and not on `this`
|
|
5
|
+
*
|
|
6
|
+
* The authoring surface is a builder terminating in `.handle(fn)`, and `fn` is
|
|
7
|
+
* an arrow function with no useful `this`. That turns out to be the better
|
|
8
|
+
* arrangement rather than a cost to work around: the helpers are destructured on
|
|
9
|
+
* the first line of the handler, so a reader sees which streams a command writes
|
|
10
|
+
* to before reading any of it, and a test supplies its own writers by passing a
|
|
11
|
+
* context instead of subclassing.
|
|
12
|
+
*
|
|
13
|
+
* ### Why there are three of them
|
|
14
|
+
*
|
|
15
|
+
* The load-bearing thing a framework can own here is **stream discipline, not
|
|
16
|
+
* colour**. A diagnostic printed to stdout is what breaks
|
|
17
|
+
* `TOTAL=$(gemi run count-users)` and what makes a CI grep match a progress line
|
|
18
|
+
* instead of the answer, and that mistake is invisible until somebody pipes the
|
|
19
|
+
* command somewhere.
|
|
20
|
+
*
|
|
21
|
+
* So: `line` is the command's answer and goes to stdout, `error` is a diagnostic
|
|
22
|
+
* and goes to stderr, and `fail` ends the command. Deliberately absent are
|
|
23
|
+
* `info`/`success` (`line` with a colour, and colour is the application's
|
|
24
|
+
* business), `warn` (identical to `error`; two names for one stream is a coin
|
|
25
|
+
* flip at every call site), and tables or progress bars — a console formatting
|
|
26
|
+
* library is a project, not a feature.
|
|
27
|
+
*/
|
|
28
|
+
export interface CommandContext<A = Record<string, unknown>, O = Record<string, unknown>> {
|
|
29
|
+
/** The positional arguments, by the names `.arg()` declared. */
|
|
30
|
+
args: A;
|
|
31
|
+
/** The flags, by the names `.option()` declared. */
|
|
32
|
+
options: O;
|
|
33
|
+
/**
|
|
34
|
+
* Everything after the command name, unparsed.
|
|
35
|
+
*
|
|
36
|
+
* The escape hatch, for a command that wants to forward its tail to something
|
|
37
|
+
* else. Present even when parsing succeeded, so a command never has to choose
|
|
38
|
+
* between the declared schema and the raw truth.
|
|
39
|
+
*/
|
|
40
|
+
argv: readonly string[];
|
|
41
|
+
/** The command's answer, on stdout. */
|
|
42
|
+
line(message?: string): void;
|
|
43
|
+
/** A diagnostic, on stderr. Does not end the command. */
|
|
44
|
+
error(message: string): void;
|
|
45
|
+
/**
|
|
46
|
+
* Ends the command with a message and an exit code, and no stack.
|
|
47
|
+
*
|
|
48
|
+
* Throws rather than returning, so it type-checks as the last statement of any
|
|
49
|
+
* branch and a caller cannot forget to `return` after it.
|
|
50
|
+
*/
|
|
51
|
+
fail(message: string, code?: number): never;
|
|
52
|
+
}
|
|
53
|
+
/** Where a context writes. Injectable so tests can capture without spawning. */
|
|
54
|
+
export interface CommandWriters {
|
|
55
|
+
stdout: (chunk: string) => void;
|
|
56
|
+
stderr: (chunk: string) => void;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Writers onto this process's streams, and the means to know they landed.
|
|
60
|
+
*
|
|
61
|
+
* ### Why `flush` exists
|
|
62
|
+
*
|
|
63
|
+
* `process.stdout.write` is asynchronous when stdout is a pipe, and `gemi run`
|
|
64
|
+
* ends in an explicit `process.exit` — which it must, or a command holding a
|
|
65
|
+
* database pool open would finish its work and then sit there. Those two facts
|
|
66
|
+
* together truncate output at the pipe buffer: measured on Bun 1.3.14, a
|
|
67
|
+
* 2,000,000-byte write followed immediately by `process.exit(0)` delivers
|
|
68
|
+
* exactly 65,536 bytes through a pipe, and exits 0 while doing it. So
|
|
69
|
+
* `gemi run export-users | gzip > out.gz` silently stores a fragment — the exact
|
|
70
|
+
* idiom the three-stream design above exists to protect.
|
|
71
|
+
*
|
|
72
|
+
* The fence is the *last* write's completion callback. Chunks are flushed in
|
|
73
|
+
* order, so awaiting the newest one awaits all of them. The obvious alternatives
|
|
74
|
+
* do not work here: a zero-length `write("", cb)` used as a fence delivered only
|
|
75
|
+
* 1,310,720 of those bytes, and `writableLength` reports 0 while data is still
|
|
76
|
+
* pending, so a drain loop exits immediately. Both were measured rather than
|
|
77
|
+
* assumed.
|
|
78
|
+
*/
|
|
79
|
+
export interface ProcessWriters extends CommandWriters {
|
|
80
|
+
/** Resolves once everything written so far has reached the OS. */
|
|
81
|
+
flush(): Promise<void>;
|
|
82
|
+
}
|
|
83
|
+
export declare function processWriters(): ProcessWriters;
|
|
84
|
+
export declare function createContext<A, O>(params: {
|
|
85
|
+
args: A;
|
|
86
|
+
options: O;
|
|
87
|
+
argv: readonly string[];
|
|
88
|
+
writers?: CommandWriters;
|
|
89
|
+
}): CommandContext<A, O>;
|
|
90
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../console/context.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,WAAW,cAAc,CAC7B,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC3B,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAE3B,gEAAgE;IAChE,IAAI,EAAE,CAAC,CAAC;IAER,oDAAoD;IACpD,OAAO,EAAE,CAAC,CAAC;IAEX;;;;;;OAMG;IACH,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IAExB,uCAAuC;IACvC,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAE7B,yDAAyD;IACzD,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAE7B;;;;;OAKG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CAC7C;AAED,gFAAgF;AAChF,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,cAAe,SAAQ,cAAc;IACpD,kEAAkE;IAClE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,wBAAgB,cAAc,IAAI,cAAc,CAyB/C;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE;IAC1C,IAAI,EAAE,CAAC,CAAC;IACR,OAAO,EAAE,CAAC,CAAC;IACX,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B,GAAG,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,CAavB"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The failures `gemi run` prints as sentences rather than stack traces.
|
|
3
|
+
*
|
|
4
|
+
* Its own file rather than a declaration in `runner.ts`, because the classes
|
|
5
|
+
* that *raise* these are the ones the runner imports — `CommandRegistry` most of
|
|
6
|
+
* all — and a registry importing the runner to reach its error type is a cycle
|
|
7
|
+
* for no reason.
|
|
8
|
+
*
|
|
9
|
+
* The split this exists to serve is the one `bin/gemi.ts` already draws around
|
|
10
|
+
* `CheckModelsError`: a message written for the operator standing at the
|
|
11
|
+
* terminal, with the fix named in it, is printed alone; anything else is a bug
|
|
12
|
+
* in the framework and keeps its stack so it can be reported.
|
|
13
|
+
*/
|
|
14
|
+
export declare class ConsoleError extends Error {
|
|
15
|
+
constructor(message: string, options?: {
|
|
16
|
+
cause?: unknown;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../console/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAI3D"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { CommandArgument, CommandOption } from "./Command";
|
|
2
|
+
/**
|
|
3
|
+
* Turning what an operator typed into what a handler declared it wanted.
|
|
4
|
+
*
|
|
5
|
+
* A pure function over `(spec, argv)`, deliberately: this is where every
|
|
6
|
+
* off-by-one in argument handling lives, and a pure function is one a test can
|
|
7
|
+
* exercise a hundred ways without a project on disk, a spawned process or a
|
|
8
|
+
* booted container. `runner.ts` does the parts that need those.
|
|
9
|
+
*
|
|
10
|
+
* ### Why gemi parses this itself instead of handing it to commander
|
|
11
|
+
*
|
|
12
|
+
* commander is a dependency of this package and would resolve here, and an
|
|
13
|
+
* earlier draft used it. Two things argued it back out. The schema is already
|
|
14
|
+
* declared — named options with a `type`, not commander's `"-t, --tries <n>"`
|
|
15
|
+
* flag strings — so using commander means translating one declaration into
|
|
16
|
+
* another and then translating its output back, with the type information lost
|
|
17
|
+
* in the middle. And commander is currently a *CLI-side* concern: `bin/gemi.ts`
|
|
18
|
+
* uses it, and nothing that runs inside an application does. Keeping it that way
|
|
19
|
+
* means a command's parsing has no dependency at all, which matters more here
|
|
20
|
+
* than it looks — this code runs in the application's process, next to the
|
|
21
|
+
* application's own module graph.
|
|
22
|
+
*
|
|
23
|
+
* `usage.ts` renders `--help` from the same spec this reads, so the help and the
|
|
24
|
+
* parser cannot drift.
|
|
25
|
+
*/
|
|
26
|
+
/** `dryRun` -> `dry-run`. What the operator types. */
|
|
27
|
+
export declare function kebab(name: string): string;
|
|
28
|
+
export interface CommandSpec {
|
|
29
|
+
commandName: string;
|
|
30
|
+
args: CommandArgument[];
|
|
31
|
+
options: CommandOption[];
|
|
32
|
+
}
|
|
33
|
+
export type ParseResult = {
|
|
34
|
+
/**
|
|
35
|
+
* The invocation asked for help rather than for work.
|
|
36
|
+
*
|
|
37
|
+
* Reported by the parse rather than by a separate scan, because whether a
|
|
38
|
+
* `--help` is a request for help or the *value* of the option before it is a
|
|
39
|
+
* question only something tracking option arity can answer. A flat token scan
|
|
40
|
+
* reads `--message --help` as a request for help, prints usage and exits 0 —
|
|
41
|
+
* so the message is never sent and a cron wrapper branching on the exit code
|
|
42
|
+
* records a success for work that did not happen.
|
|
43
|
+
*
|
|
44
|
+
* Present on both branches, and the runner checks it first: `--help` has to
|
|
45
|
+
* work on an invocation that would otherwise be a usage error, which is
|
|
46
|
+
* precisely when somebody reaches for it.
|
|
47
|
+
*/
|
|
48
|
+
help: boolean;
|
|
49
|
+
} & ({
|
|
50
|
+
ok: true;
|
|
51
|
+
args: Record<string, string | string[] | undefined>;
|
|
52
|
+
options: Record<string, string | number | boolean | undefined>;
|
|
53
|
+
} | {
|
|
54
|
+
ok: false;
|
|
55
|
+
errors: string[];
|
|
56
|
+
});
|
|
57
|
+
/**
|
|
58
|
+
* Whether this invocation is asking for help rather than running.
|
|
59
|
+
*
|
|
60
|
+
* A thin read of the parse, so the two cannot disagree about what counts as a
|
|
61
|
+
* `--help`. See `ParseResult["help"]` for why that question needs a parse at all.
|
|
62
|
+
*/
|
|
63
|
+
export declare function wantsHelp(spec: CommandSpec, argv: readonly string[]): boolean;
|
|
64
|
+
export declare function parseArgv(spec: CommandSpec, argv: readonly string[]): ParseResult;
|
|
65
|
+
//# sourceMappingURL=parse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../console/parse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAEhE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,sDAAsD;AACtD,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK1C;AAED,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,eAAe,EAAE,CAAC;IACxB,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AAED,MAAM,MAAM,WAAW,GAAG;IACxB;;;;;;;;;;;;;OAaG;IACH,IAAI,EAAE,OAAO,CAAC;CACf,GAAG,CACA;IACE,EAAE,EAAE,IAAI,CAAC;IACT,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IACpD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC;CAChE,GACD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAClC,CAAC;AAkBF;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAE7E;AAuBD,wBAAgB,SAAS,CACvB,IAAI,EAAE,WAAW,EACjB,IAAI,EAAE,SAAS,MAAM,EAAE,GACtB,WAAW,CA2Rb"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../console/run.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import{Ba as A,Ca as l}from"../chunk-p22rkf1g.js";import{nb as q,qb as m,yb as p}from"../chunk-tmk4zfz9.js";import{Cb as c}from"../chunk-ha7n1c94.js";import{Ce as u}from"../chunk-3y9nf09k.js";import"../chunk-mt2755rs.js";import"../chunk-qyaqg07q.js";import"../chunk-0h3jr9d9.js";function F(){let z=Promise.resolve(),M=Promise.resolve(),X=(Q,G)=>new Promise(($)=>{Q.write(G,()=>$())});return{stdout:(Q)=>{z=X(process.stdout,Q)},stderr:(Q)=>{M=X(process.stderr,Q)},flush:async()=>{await Promise.all([z,M])}}}function b(z){let M=z.writers??F();return{args:z.args,options:z.options,argv:z.argv,line:(X="")=>M.stdout(`${X}
|
|
3
|
+
`),error:(X)=>M.stderr(`${X}
|
|
4
|
+
`),fail:(X,Q=1)=>{throw new q(X,Q)}}}import w from"path";function V(z){return z.replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/([A-Z])([A-Z][a-z])/g,"$1-$2").toLowerCase()}function t(z){let M=new Map,X=new Map;for(let Q of z){M.set(V(Q.name),Q),M.set(Q.name,Q);let G="alias"in Q?Q.alias:void 0;if(G)X.set(G,Q)}return{byName:M,byAlias:X}}function x(z){if(z==="--")return!0;if(!z.startsWith("-"))return!1;if(z==="-")return!1;if(/^-(\d|\.\d)/.test(z))return!1;return!0}function k(z,M){let{byName:X,byAlias:Q}=t(z.options),G=[],$=[],Z=new Map,S=!X.has("help"),C=!Q.has("h"),P=!1,D=!1,I=(J)=>G.push(`Unknown option ${J} for command "${z.commandName}".`),T=(J,Y)=>{if(Y==="--help"||Y==="-h")D=!0;G.push(Y===void 0?`Option ${J} needs a value.`:`Option ${J} needs a value, but the next token is ${JSON.stringify(Y)}, which is another option. If that really is the value, write ${J}=${Y}.`)},_=0;while(_<M.length){let J=M[_];if(J==="--"){$.push(...M.slice(_+1));break}if(J.startsWith("--")){let Y=J.slice(2),j=Y.indexOf("="),B=j===-1?Y:Y.slice(0,j),K=j===-1?void 0:Y.slice(j+1),H=X.get(B),E=!1;if(!H&&B.startsWith("no-")){let W=X.get(B.slice(3));if(W&&W.type==="boolean")H=W,E=!0}if(!H&&B==="help"&&S){P=!0,_+=1;continue}if(!H){I(`--${B}`),_+=1;continue}if(H.type==="boolean"){if(K!==void 0&&K!=="true"&&K!=="false")G.push(`Option --${V(H.name)} is a flag and takes no value, but got ${JSON.stringify(K)}.`);else{let W=K===void 0?!0:K==="true";Z.set(H.name,E?!W:W)}_+=1;continue}if(K!==void 0){Z.set(H.name,K),_+=1;continue}let U=M[_+1];if(U===void 0||x(U)){T(`--${V(H.name)}`,U),_+=1;continue}Z.set(H.name,U),_+=2;continue}if(J.startsWith("-")&&J.length>1){let Y=J.slice(1),j=!1;for(let B=0;B<Y.length;B+=1){let K=Q.get(Y[B]);if(!K){if(Y[B]==="h"&&C){P=!0;continue}I(`-${Y[B]}`);break}if(K.type==="boolean"){Z.set(K.name,!0);continue}let H=Y.slice(B+1),E=H.startsWith("=")?H.slice(1):H;if(E!==""){Z.set(K.name,E);break}let U=M[_+1];if(U===void 0||x(U)){T(`-${K.alias}`,U);break}Z.set(K.name,U),j=!0;break}_+=j?2:1;continue}$.push(J),_+=1}let L={},O=0;for(let J of z.args){if(J.variadic){let j=$.slice(O);if(O=$.length,L[J.name]=j,J.required&&j.length===0)G.push(`Missing required argument <${J.name}...>. Command "${z.commandName}" needs at least one.`);continue}let Y=$[O];if(O+=1,Y!==void 0){L[J.name]=Y;continue}if(J.default!==void 0){L[J.name]=J.default;continue}if(J.required)G.push(`Missing required argument <${J.name}>.`);L[J.name]=void 0}if(O<$.length){let J=$.slice(O);G.push(`Unexpected argument${J.length===1?"":"s"} ${J.map((Y)=>JSON.stringify(Y)).join(", ")}. Command "${z.commandName}" takes ${z.args.length} argument${z.args.length===1?"":"s"}.`)}let R={};for(let J of z.options){let Y=Z.get(J.name);if(J.type==="boolean"){R[J.name]=Y===void 0?J.default??!1:Boolean(Y);continue}if(Y===void 0){if(J.default!==void 0)R[J.name]=J.default;else if(J.required)G.push(`Missing required option --${V(J.name)}.`),R[J.name]=void 0;else R[J.name]=void 0;continue}let j=String(Y);if(J.type==="number"){let B=Number(j);if(j.trim()===""||!Number.isFinite(B))G.push(`Option --${V(J.name)} expects a number, but got ${JSON.stringify(j)}.`),R[J.name]=void 0;else R[J.name]=B;continue}R[J.name]=j}let N=P&&!D;if(G.length>0)return{ok:!1,help:N,errors:G};return{ok:!0,help:N,args:L,options:R}}function f(z,M=" "){let X=z.reduce((Q,[G])=>Math.max(Q,G.length),0);return z.map(([Q,G])=>G===""?`${M}${Q}`:`${M}${Q.padEnd(X)} ${G}`)}function g(z){let M=z.variadic?`${z.name}...`:z.name;return z.required?`<${M}>`:`[${M}]`}function s(z){let M=(G)=>z.options.some(G),X=M((G)=>G.name==="help"||V(G.name)==="help"),Q=M((G)=>("alias"in G)&&G.alias==="h");if(X&&Q)return[];if(X)return[["-h","Show this message"]];return[[`${Q?"":"-h, "}--help`,"Show this message"]]}function v(z){let M=[],X=z.args.map(g).join(" "),Q=["gemi run",z.commandName,z.options.length>0?"[options]":"",X].filter(Boolean).join(" ");if(M.push(`Usage: ${Q}`),z.description)M.push("",z.description);if(z.args.length>0)M.push("","Arguments:"),M.push(...f(z.args.map((G)=>{let $=[G.description??"",G.default!==void 0?`(default: ${JSON.stringify(G.default)})`:""].filter(Boolean).join(" ");return[g(G),$]})));return M.push("","Options:"),M.push(...f([...z.options.map((G)=>{let $="alias"in G&&G.alias?`-${G.alias}, `:"",Z=G.type==="boolean"?"":` <${G.type}>`,S=[G.description??"",G.default!==void 0?`(default: ${JSON.stringify(G.default)})`:"","required"in G&&G.required?"(required)":""].filter(Boolean).join(" ");return[`${$}--${V(G.name)}${Z}`,S]}),...s(z)])),M.join(`
|
|
5
|
+
`)}function y(z,M){let X=M.declared?`${z.length} command${z.length===1?"":"s"} declared in app/config/command.ts:`:`Commands in ${M.dir}:`;if(z.length===0)return M.declared?`app/config/command.ts declares no commands.
|
|
6
|
+
|
|
7
|
+
Remove the \`commands\` key from that slice to discover the classes under ${M.dir} instead \u2014 a declared list wins, and an empty one `+'means "none, and I mean it".':`No commands found in ${M.dir}.
|
|
8
|
+
|
|
9
|
+
A command is registered by existing: write a file there that exports a \`defineCommand(...).handle(...)\` chain and it will appear here.`;let Q=[...z].sort((G,$)=>G.commandName<$.commandName?-1:G.commandName>$.commandName?1:0);return[X,"",...f(Q.map((G)=>[G.commandName,G.description])),"","Run one with `gemi run <name>`, and see its arguments with `gemi run <name> --help`."].join(`
|
|
10
|
+
`)}async function d(z){let M=z.writers??F(),X=(Z)=>M.stdout(`${Z}
|
|
11
|
+
`),Q=(Z)=>M.stderr(`${Z}
|
|
12
|
+
`),G,$;try{G=await n(z.rootDir),G.boot();let Z=G.app.config.get("command",{}),S=u(m(),Z).commandsDir,C=w.isAbsolute(S)?S:w.join(z.rootDir,S),P=Z.commands!==void 0,D=P?Z.commands:await p(C),I=new l(D),T={declared:P,dir:S};if(z.name===void 0)return X(y(I.commands,T)),0;let _=I.get(z.name);if($=_,!_){let J=I.suggest(z.name);return Q(`No command named \`${z.name}\`.`+(J.length>0?` Did you mean ${J.map((Y)=>`\`${Y}\``).join(", or ")}?`:"")),Q(""),Q(y(I.commands,T)),1}let L={commandName:_.commandName,args:_.args,options:_.options},O=k(L,z.argv);if(O.help)return X(v(_)),0;if(O.ok===!1){for(let J of O.errors)Q(J);return Q(""),Q(v(_)),1}await G.waitForBoot();let R=b({args:O.args,options:O.options,argv:z.argv,writers:M}),N=await G.run(()=>Promise.resolve(new _().run(R)));return h(N,_,Q)}catch(Z){if(Z instanceof q)return Q(Z.message),h(Z.code,$,Q,"called fail() with");if(Z instanceof A||Z instanceof c)return Q(Z.message),1;return Q(String(Z?.stack??Z)),1}finally{G?.destroy()}}async function n(z){let M=w.resolve(z,"app/kernel/Kernel.ts"),X;try{X=await import(M)}catch(Q){throw new A(`Could not load ${M}:
|
|
13
|
+
${Q.message}
|
|
14
|
+
\`gemi run\` boots your application the way the server does, so it needs the Kernel. Run it from the root of a gemi project.`,{cause:Q})}if(typeof X.default!=="function")throw new A(`${M} has no default export. gemi expects the Kernel subclass to be the default export of that file.`);return new X.default}function h(z,M,X,Q="returned"){if(z===void 0||z===null)return 0;if(typeof z==="number"&&Number.isInteger(z)&&z>=0&&z<=255)return z;let G=M?`Command "${M.commandName}"`:"The command";return X(`${G} ${Q} ${JSON.stringify(z)}, which is not an exit `+"code \u2014 an exit code is an integer from 0 to 255. Treating this as a "+"failure."),1}var[a,...o]=process.argv.slice(2),i=F();d({rootDir:process.cwd(),name:a,argv:o,writers:i}).then(async(z)=>{await i.flush(),process.exit(z)});
|
|
15
|
+
|
|
16
|
+
//# debugId=3A559D4973E0E24564756E2164756E21
|