@norskvideo/ctl-commands 0.1.8 → 0.1.10

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/commands.d.ts CHANGED
@@ -3,6 +3,16 @@ import type { CertSource, HardwareProfile, NetworkMode, ProductNetworkMode, Rest
3
3
  export type LeafCmd<F extends (...args: never[]) => unknown> = F & {
4
4
  readonly __sig: string;
5
5
  };
6
+ /** What a leaf declares about itself, and all `toArgv` needs to render it. */
7
+ export interface CommandSpec {
8
+ readonly kind: string;
9
+ /** argv path segments, e.g. `["instance", "launch-template"]`. */
10
+ readonly path: readonly string[];
11
+ /** Command fields emitted as bare positionals, in argv order. */
12
+ readonly positionals: readonly string[];
13
+ }
14
+ /** Every leaf's spec, by kind. */
15
+ export declare const commandSpecs: ReadonlyMap<string, CommandSpec>;
6
16
  export interface RelaunchOpts {
7
17
  /** Raw `SPEC` / `SPEC@service` flag values; parsed into HostPort[] by the coercer. */
8
18
  hostPorts?: string[];
@@ -80,6 +90,16 @@ export interface SideloadFetchOpts {
80
90
  export interface TemplateImportOpts {
81
91
  name: string;
82
92
  }
93
+ export interface TemplateBuildOpts {
94
+ /** Registered product to build from; its `/api/product-template` renders the tar. */
95
+ product: string;
96
+ /** Path to a JSON file of the product's template inputs, forwarded verbatim. */
97
+ input: string;
98
+ /** Overwrite an existing template of this name instead of failing. What makes
99
+ * a re-run of a bring-up script idempotent without removing the only copy
100
+ * first. Refused while an instance references the template. */
101
+ replace?: boolean;
102
+ }
83
103
  export interface InstanceLaunchTemplateOpts {
84
104
  template: string;
85
105
  param?: string[];
@@ -103,148 +123,12 @@ export interface InstanceLaunchTemplateOpts {
103
123
  memoryLimit?: string;
104
124
  restartPolicy?: RestartPolicy;
105
125
  }
106
- export type GetResource = "versions" | "hardware" | "status" | "prerequisites";
107
- export type Command = {
108
- kind: "config.set";
109
- } | {
110
- kind: "config.show";
111
- } | {
112
- kind: "fetch.mkcert";
113
- } | {
114
- kind: "get";
115
- resource: GetResource;
116
- } | {
117
- kind: "init";
118
- opts: InitOpts;
119
- } | {
120
- kind: "inspect";
121
- } | {
122
- kind: "instance.delete";
123
- id: string;
124
- opts: InstanceDeleteOpts;
125
- } | {
126
- kind: "instance.describe";
127
- id: string;
128
- } | {
129
- kind: "instance.restart";
130
- id: string;
131
- } | {
132
- kind: "instance.stop";
133
- id: string;
134
- } | {
135
- kind: "instance.start";
136
- id: string;
137
- } | {
138
- kind: "instance.relaunch";
139
- id: string;
140
- opts: RelaunchOpts;
141
- } | {
142
- kind: "instance.list";
143
- } | {
144
- kind: "instance.exists";
145
- id: string;
146
- } | {
147
- kind: "instance.launchTemplate";
148
- id: string;
149
- opts: InstanceLaunchTemplateOpts;
150
- } | {
151
- kind: "orphaned-dirs.list";
152
- opts: OrphanListOpts;
153
- } | {
154
- kind: "orphaned-dirs.clear";
155
- id: string;
156
- } | {
157
- kind: "shm.list";
158
- } | {
159
- kind: "sideload.status";
160
- } | {
161
- kind: "sideload.fetch";
162
- opts: SideloadFetchOpts;
163
- } | {
164
- kind: "template.list";
165
- } | {
166
- kind: "template.show";
167
- name: string;
168
- } | {
169
- kind: "template.default";
170
- product: string;
171
- } | {
172
- kind: "template.import";
173
- file: string;
174
- opts: TemplateImportOpts;
175
- } | {
176
- kind: "template.refresh";
177
- name: string;
178
- } | {
179
- kind: "template.remove";
180
- name: string;
181
- } | {
182
- kind: "product.add";
183
- opts: ProductAddOpts;
184
- } | {
185
- kind: "product.list";
186
- } | {
187
- kind: "product.exists";
188
- name: string;
189
- } | {
190
- kind: "product.remove";
191
- name: string;
192
- } | {
193
- kind: "product.reload";
194
- name: string;
195
- } | {
196
- kind: "product.pull";
197
- name: string;
198
- opts: ProductPullOpts;
199
- } | {
200
- kind: "mcp";
201
- } | {
202
- kind: "proxy.pull";
203
- } | {
204
- kind: "proxy.start";
205
- } | {
206
- kind: "proxy.stop";
207
- } | {
208
- kind: "proxy.reload";
209
- } | {
210
- kind: "proxy.status";
211
- } | {
212
- kind: "proxy.logs";
213
- opts: ProxyLogsOpts;
214
- } | {
215
- kind: "serve";
216
- } | {
217
- kind: "source.start";
218
- instanceId: string;
219
- preset: string;
220
- opts: SourceStartOpts;
221
- } | {
222
- kind: "source.stop";
223
- instanceId: string;
224
- name: string;
225
- } | {
226
- kind: "source.list";
227
- } | {
228
- kind: "source.presets";
229
- } | {
230
- kind: "stats";
231
- opts: StatsOpts;
232
- } | {
233
- kind: "restart";
234
- } | {
235
- kind: "shutdown";
236
- opts: ShutdownOpts;
237
- } | {
238
- kind: "upgrade";
239
- opts: UpgradeOpts;
240
- } | {
241
- kind: "user.set";
242
- name: string;
243
- opts: UserSetOpts;
244
- } | {
245
- kind: "user.delete";
246
- name: string;
247
- };
126
+ /** Runtime source for the `get` vocabulary the CLI's yargs `choices` and
127
+ * help text derive from it, so the type and the accepted words cannot drift.
128
+ * (`versions` was removed when core's Studio version surface went away; its
129
+ * route no longer exists.) */
130
+ export declare const GET_RESOURCES: readonly ["hardware", "status", "prerequisites"];
131
+ export type GetResource = (typeof GET_RESOURCES)[number];
248
132
  export declare const commands: {
249
133
  readonly config: {
250
134
  readonly set: LeafCmd<() => {
@@ -261,7 +145,7 @@ export declare const commands: {
261
145
  };
262
146
  readonly get: LeafCmd<(resource: GetResource) => {
263
147
  readonly kind: "get";
264
- readonly resource: GetResource;
148
+ readonly resource: "hardware" | "status" | "prerequisites";
265
149
  }>;
266
150
  readonly init: LeafCmd<(opts: InitOpts) => {
267
151
  readonly kind: "init";
@@ -377,6 +261,11 @@ export declare const commands: {
377
261
  readonly file: string;
378
262
  readonly opts: TemplateImportOpts;
379
263
  }>;
264
+ readonly build: LeafCmd<(name: string, opts: TemplateBuildOpts) => {
265
+ readonly kind: "template.build";
266
+ readonly name: string;
267
+ readonly opts: TemplateBuildOpts;
268
+ }>;
380
269
  readonly refresh: LeafCmd<(name: string) => {
381
270
  readonly kind: "template.refresh";
382
271
  readonly name: string;
@@ -459,3 +348,15 @@ export declare const commands: {
459
348
  }>;
460
349
  };
461
350
  };
351
+ /** Every leaf in a factory subtree, as a union of its `LeafCmd` types. */
352
+ type LeavesOf<T> = T extends LeafCmd<(...args: never[]) => unknown> ? T : T extends object ? {
353
+ [K in keyof T]: LeavesOf<T[K]>;
354
+ }[keyof T] : never;
355
+ /** The constructors build `as const` objects, so their properties come back
356
+ * readonly; `Command` has always been the mutable view of them. */
357
+ type Mutable<T> = {
358
+ -readonly [K in keyof T]: T[K];
359
+ };
360
+ /** Everything the CLI can be asked to do, discriminated by `kind`. */
361
+ export type Command = Mutable<ReturnType<LeavesOf<typeof commands>>>;
362
+ export {};
package/commands.js CHANGED
@@ -3,92 +3,112 @@
3
3
  // of stringly-typed argv arrays. Renaming or removing a CLI verb is a
4
4
  // typecheck error at the call site, not a runtime "Unknown arguments" surprise.
5
5
  //
6
- // Each leaf factory carries a `__sig` field with the help-display signature
7
- // (e.g. "launch <id>"). `describeCommands` (in ./help.ts) reads these to build
8
- // HelpTrees, and the `DescriptionsFor<T>` type forces every per-module
9
- // description object to enumerate exactly the leaves of its factory subtree —
10
- // drift between this file and commands/<group>.ts is a typecheck error.
6
+ // A leaf is declared ONCE, by its kind, its constructor and its positional
7
+ // names. Everything else falls out: the argv path is the kind's dot-segments
8
+ // kebab-cased, the `__sig` help string is the last segment plus the
9
+ // positionals, the `Command` union is the union of the constructors' return
10
+ // types, and `format.toArgv` renders from the same spec. Adding a verb here
11
+ // leaves nothing else in this package to update.
12
+ //
13
+ // `describeCommands` (in norsk-ctl's cli/src/typed/help.ts) reads `__sig` to
14
+ // build HelpTrees, and the `DescriptionsFor<T>` type forces every per-module
15
+ // description object to enumerate exactly the leaves of its factory subtree.
11
16
  //
12
17
  // Pair with `format.toArgv(cmd)` to render to a string[] for execution and
13
18
  // `format.commandToString(cmd)` for human-readable error messages.
14
- function leaf(sig, fn) {
19
+ const specs = new Map();
20
+ /** Every leaf's spec, by kind. */
21
+ export const commandSpecs = specs;
22
+ const camelToKebab = (s) => s.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
23
+ function leaf(kind, fn, ...positionals) {
24
+ // `instance.launchTemplate` is the only kind whose argv differs from its
25
+ // segments, and it differs exactly by camelCase — the same transform the
26
+ // flag renderer applies.
27
+ const path = kind.split(".").map(camelToKebab);
28
+ const sig = [path[path.length - 1], ...positionals.map((p) => `<${String(p)}>`)].join(" ");
29
+ specs.set(kind, { kind, path, positionals: positionals.map(String) });
15
30
  return Object.assign(fn, { __sig: sig });
16
31
  }
32
+ /** Runtime source for the `get` vocabulary — the CLI's yargs `choices` and
33
+ * help text derive from it, so the type and the accepted words cannot drift.
34
+ * (`versions` was removed when core's Studio version surface went away; its
35
+ * route no longer exists.) */
36
+ export const GET_RESOURCES = ["hardware", "status", "prerequisites"];
17
37
  // ---- Factory tree -----------------------------------------------------------
18
- // Mirrors cli-builder.ts. Adding a new CLI verb requires a Command variant
19
- // here, a leaf entry below, and (via DescriptionsFor) a description in the
20
- // owning command module's helpEntries.
38
+ // Mirrors cli-builder.ts. Adding a new CLI verb is one leaf here, plus (via
39
+ // DescriptionsFor) a description in the owning command module's helpEntries.
21
40
  export const commands = {
22
41
  config: {
23
- set: leaf("set", () => ({ kind: "config.set" })),
24
- show: leaf("show", () => ({ kind: "config.show" })),
42
+ set: leaf("config.set", () => ({ kind: "config.set" })),
43
+ show: leaf("config.show", () => ({ kind: "config.show" })),
25
44
  },
26
45
  fetch: {
27
- mkcert: leaf("mkcert", () => ({ kind: "fetch.mkcert" })),
46
+ mkcert: leaf("fetch.mkcert", () => ({ kind: "fetch.mkcert" })),
28
47
  },
29
- get: leaf("get <resource>", (resource) => ({ kind: "get", resource })),
48
+ get: leaf("get", (resource) => ({ kind: "get", resource }), "resource"),
30
49
  init: leaf("init", (opts) => ({ kind: "init", opts })),
31
50
  inspect: leaf("inspect", () => ({ kind: "inspect" })),
32
51
  instance: {
33
- delete: leaf("delete <id>", (id, opts = {}) => ({ kind: "instance.delete", id, opts })),
34
- describe: leaf("describe <id>", (id) => ({ kind: "instance.describe", id })),
35
- restart: leaf("restart <id>", (id) => ({ kind: "instance.restart", id })),
36
- stop: leaf("stop <id>", (id) => ({ kind: "instance.stop", id })),
37
- start: leaf("start <id>", (id) => ({ kind: "instance.start", id })),
38
- relaunch: leaf("relaunch <id>", (id, opts = {}) => ({ kind: "instance.relaunch", id, opts })),
39
- list: leaf("list", () => ({ kind: "instance.list" })),
40
- exists: leaf("exists <id>", (id) => ({ kind: "instance.exists", id })),
41
- launchTemplate: leaf("launch-template <id>", (id, opts) => ({ kind: "instance.launchTemplate", id, opts })),
52
+ delete: leaf("instance.delete", (id, opts = {}) => ({ kind: "instance.delete", id, opts }), "id"),
53
+ describe: leaf("instance.describe", (id) => ({ kind: "instance.describe", id }), "id"),
54
+ restart: leaf("instance.restart", (id) => ({ kind: "instance.restart", id }), "id"),
55
+ stop: leaf("instance.stop", (id) => ({ kind: "instance.stop", id }), "id"),
56
+ start: leaf("instance.start", (id) => ({ kind: "instance.start", id }), "id"),
57
+ relaunch: leaf("instance.relaunch", (id, opts = {}) => ({ kind: "instance.relaunch", id, opts }), "id"),
58
+ list: leaf("instance.list", () => ({ kind: "instance.list" })),
59
+ exists: leaf("instance.exists", (id) => ({ kind: "instance.exists", id }), "id"),
60
+ launchTemplate: leaf("instance.launchTemplate", (id, opts) => ({ kind: "instance.launchTemplate", id, opts }), "id"),
42
61
  },
43
62
  orphanedDirs: {
44
- list: leaf("list", (opts = {}) => ({ kind: "orphaned-dirs.list", opts })),
45
- clear: leaf("clear <id>", (id) => ({ kind: "orphaned-dirs.clear", id })),
63
+ list: leaf("orphaned-dirs.list", (opts = {}) => ({ kind: "orphaned-dirs.list", opts })),
64
+ clear: leaf("orphaned-dirs.clear", (id) => ({ kind: "orphaned-dirs.clear", id }), "id"),
46
65
  },
47
66
  product: {
48
- add: leaf("add", (opts = {}) => ({ kind: "product.add", opts })),
49
- list: leaf("list", () => ({ kind: "product.list" })),
50
- exists: leaf("exists <name>", (name) => ({ kind: "product.exists", name })),
51
- remove: leaf("remove <name>", (name) => ({ kind: "product.remove", name })),
52
- reload: leaf("reload <name>", (name) => ({ kind: "product.reload", name })),
53
- pull: leaf("pull <name>", (name, opts = {}) => ({ kind: "product.pull", name, opts })),
67
+ add: leaf("product.add", (opts = {}) => ({ kind: "product.add", opts })),
68
+ list: leaf("product.list", () => ({ kind: "product.list" })),
69
+ exists: leaf("product.exists", (name) => ({ kind: "product.exists", name }), "name"),
70
+ remove: leaf("product.remove", (name) => ({ kind: "product.remove", name }), "name"),
71
+ reload: leaf("product.reload", (name) => ({ kind: "product.reload", name }), "name"),
72
+ pull: leaf("product.pull", (name, opts = {}) => ({ kind: "product.pull", name, opts }), "name"),
54
73
  },
55
74
  shm: {
56
- list: leaf("list", () => ({ kind: "shm.list" })),
75
+ list: leaf("shm.list", () => ({ kind: "shm.list" })),
57
76
  },
58
77
  sideload: {
59
- status: leaf("status", () => ({ kind: "sideload.status" })),
60
- fetch: leaf("fetch", (opts = {}) => ({ kind: "sideload.fetch", opts })),
78
+ status: leaf("sideload.status", () => ({ kind: "sideload.status" })),
79
+ fetch: leaf("sideload.fetch", (opts = {}) => ({ kind: "sideload.fetch", opts })),
61
80
  },
62
81
  template: {
63
- list: leaf("list", () => ({ kind: "template.list" })),
64
- show: leaf("show <name>", (name) => ({ kind: "template.show", name })),
65
- default: leaf("default <product>", (product) => ({ kind: "template.default", product })),
66
- import: leaf("import <file>", (file, opts) => ({ kind: "template.import", file, opts })),
67
- refresh: leaf("refresh <name>", (name) => ({ kind: "template.refresh", name })),
68
- remove: leaf("remove <name>", (name) => ({ kind: "template.remove", name })),
82
+ list: leaf("template.list", () => ({ kind: "template.list" })),
83
+ show: leaf("template.show", (name) => ({ kind: "template.show", name }), "name"),
84
+ default: leaf("template.default", (product) => ({ kind: "template.default", product }), "product"),
85
+ import: leaf("template.import", (file, opts) => ({ kind: "template.import", file, opts }), "file"),
86
+ build: leaf("template.build", (name, opts) => ({ kind: "template.build", name, opts }), "name"),
87
+ refresh: leaf("template.refresh", (name) => ({ kind: "template.refresh", name }), "name"),
88
+ remove: leaf("template.remove", (name) => ({ kind: "template.remove", name }), "name"),
69
89
  },
70
90
  proxy: {
71
- pull: leaf("pull", () => ({ kind: "proxy.pull" })),
72
- start: leaf("start", () => ({ kind: "proxy.start" })),
73
- stop: leaf("stop", () => ({ kind: "proxy.stop" })),
74
- reload: leaf("reload", () => ({ kind: "proxy.reload" })),
75
- status: leaf("status", () => ({ kind: "proxy.status" })),
76
- logs: leaf("logs", (opts = {}) => ({ kind: "proxy.logs", opts })),
91
+ pull: leaf("proxy.pull", () => ({ kind: "proxy.pull" })),
92
+ start: leaf("proxy.start", () => ({ kind: "proxy.start" })),
93
+ stop: leaf("proxy.stop", () => ({ kind: "proxy.stop" })),
94
+ reload: leaf("proxy.reload", () => ({ kind: "proxy.reload" })),
95
+ status: leaf("proxy.status", () => ({ kind: "proxy.status" })),
96
+ logs: leaf("proxy.logs", (opts = {}) => ({ kind: "proxy.logs", opts })),
77
97
  },
78
98
  mcp: leaf("mcp", () => ({ kind: "mcp" })),
79
99
  serve: leaf("serve", () => ({ kind: "serve" })),
80
100
  source: {
81
- start: leaf("start <instanceId> <preset>", (instanceId, preset, opts = {}) => ({ kind: "source.start", instanceId, preset, opts })),
82
- stop: leaf("stop <instanceId> <name>", (instanceId, name) => ({ kind: "source.stop", instanceId, name })),
83
- list: leaf("list", () => ({ kind: "source.list" })),
84
- presets: leaf("presets", () => ({ kind: "source.presets" })),
101
+ start: leaf("source.start", (instanceId, preset, opts = {}) => ({ kind: "source.start", instanceId, preset, opts }), "instanceId", "preset"),
102
+ stop: leaf("source.stop", (instanceId, name) => ({ kind: "source.stop", instanceId, name }), "instanceId", "name"),
103
+ list: leaf("source.list", () => ({ kind: "source.list" })),
104
+ presets: leaf("source.presets", () => ({ kind: "source.presets" })),
85
105
  },
86
106
  stats: leaf("stats", (opts = {}) => ({ kind: "stats", opts })),
87
107
  restart: leaf("restart", () => ({ kind: "restart" })),
88
108
  shutdown: leaf("shutdown", (opts = {}) => ({ kind: "shutdown", opts })),
89
109
  upgrade: leaf("upgrade", (opts = {}) => ({ kind: "upgrade", opts })),
90
110
  user: {
91
- set: leaf("set <name>", (name, opts) => ({ kind: "user.set", name, opts })),
92
- delete: leaf("delete <name>", (name) => ({ kind: "user.delete", name })),
111
+ set: leaf("user.set", (name, opts) => ({ kind: "user.set", name, opts }), "name"),
112
+ delete: leaf("user.delete", (name) => ({ kind: "user.delete", name }), "name"),
93
113
  },
94
114
  };
package/format.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import type { Command } from "./commands.js";
1
+ import { type Command } from "./commands.js";
2
2
  export declare function toArgv(cmd: Command): string[];
3
3
  export declare function commandToString(cmd: Command): string;
package/format.js CHANGED
@@ -3,11 +3,8 @@
3
3
  // docs). Mirrors the CLI's flag conventions: camelCase option keys become
4
4
  // --kebab-case flags; arrays become repeated --flag value pairs; boolean true
5
5
  // becomes a bare --flag, boolean false is omitted (yargs default).
6
+ import { commandSpecs } from "./commands.js";
6
7
  const camelToKebab = (s) => s.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
7
- /** Self-referential constraint: every key in T must map to an OptValue. Lets
8
- * callers pass typed interfaces (LaunchOpts, InitOpts, …) without polluting
9
- * them with index signatures, while still rejecting an opts object that
10
- * carries an unsupported field shape (e.g. a Date or nested object). */
11
8
  function formatOpts(opts) {
12
9
  if (!opts)
13
10
  return [];
@@ -31,108 +28,15 @@ function formatOpts(opts) {
31
28
  return out;
32
29
  }
33
30
  export function toArgv(cmd) {
34
- switch (cmd.kind) {
35
- case "config.set":
36
- return ["config", "set"];
37
- case "config.show":
38
- return ["config", "show"];
39
- case "fetch.mkcert":
40
- return ["fetch", "mkcert"];
41
- case "get":
42
- return ["get", cmd.resource];
43
- case "init":
44
- return ["init", ...formatOpts(cmd.opts)];
45
- case "inspect":
46
- return ["inspect"];
47
- case "instance.delete":
48
- return ["instance", "delete", cmd.id, ...formatOpts(cmd.opts)];
49
- case "instance.describe":
50
- return ["instance", "describe", cmd.id];
51
- case "instance.restart":
52
- return ["instance", "restart", cmd.id];
53
- case "instance.stop":
54
- return ["instance", "stop", cmd.id];
55
- case "instance.start":
56
- return ["instance", "start", cmd.id];
57
- case "instance.relaunch":
58
- return ["instance", "relaunch", cmd.id, ...formatOpts(cmd.opts)];
59
- case "instance.list":
60
- return ["instance", "list"];
61
- case "instance.exists":
62
- return ["instance", "exists", cmd.id];
63
- case "instance.launchTemplate":
64
- return ["instance", "launch-template", cmd.id, ...formatOpts(cmd.opts)];
65
- case "orphaned-dirs.list":
66
- return ["orphaned-dirs", "list", ...formatOpts(cmd.opts)];
67
- case "orphaned-dirs.clear":
68
- return ["orphaned-dirs", "clear", cmd.id];
69
- case "product.add":
70
- return ["product", "add", ...formatOpts(cmd.opts)];
71
- case "product.list":
72
- return ["product", "list"];
73
- case "product.exists":
74
- return ["product", "exists", cmd.name];
75
- case "product.remove":
76
- return ["product", "remove", cmd.name];
77
- case "product.reload":
78
- return ["product", "reload", cmd.name];
79
- case "product.pull":
80
- return ["product", "pull", cmd.name, ...formatOpts(cmd.opts)];
81
- case "shm.list":
82
- return ["shm", "list"];
83
- case "sideload.status":
84
- return ["sideload", "status"];
85
- case "sideload.fetch":
86
- return ["sideload", "fetch", ...formatOpts(cmd.opts)];
87
- case "template.list":
88
- return ["template", "list"];
89
- case "template.show":
90
- return ["template", "show", cmd.name];
91
- case "template.default":
92
- return ["template", "default", cmd.product];
93
- case "template.import":
94
- return ["template", "import", cmd.file, ...formatOpts(cmd.opts)];
95
- case "template.refresh":
96
- return ["template", "refresh", cmd.name];
97
- case "template.remove":
98
- return ["template", "remove", cmd.name];
99
- case "mcp":
100
- return ["mcp"];
101
- case "proxy.pull":
102
- return ["proxy", "pull"];
103
- case "proxy.start":
104
- return ["proxy", "start"];
105
- case "proxy.stop":
106
- return ["proxy", "stop"];
107
- case "proxy.reload":
108
- return ["proxy", "reload"];
109
- case "proxy.status":
110
- return ["proxy", "status"];
111
- case "proxy.logs":
112
- return ["proxy", "logs", ...formatOpts(cmd.opts)];
113
- case "serve":
114
- return ["serve"];
115
- case "source.start":
116
- return ["source", "start", cmd.instanceId, cmd.preset, ...formatOpts(cmd.opts)];
117
- case "source.stop":
118
- return ["source", "stop", cmd.instanceId, cmd.name];
119
- case "source.list":
120
- return ["source", "list"];
121
- case "source.presets":
122
- return ["source", "presets"];
123
- case "stats":
124
- return ["stats", ...formatOpts(cmd.opts)];
125
- case "restart":
126
- return ["restart"];
127
- case "shutdown":
128
- return ["shutdown", ...formatOpts(cmd.opts)];
129
- case "upgrade":
130
- return ["upgrade", ...formatOpts(cmd.opts)];
131
- case "user.set":
132
- return ["user", "set", cmd.name, ...formatOpts(cmd.opts)];
133
- case "user.delete":
134
- return ["user", "delete", cmd.name];
135
- }
31
+ const spec = commandSpecs.get(cmd.kind);
32
+ if (!spec)
33
+ throw new Error(`toArgv: '${cmd.kind}' is not a declared command kind`);
34
+ const fields = cmd;
35
+ return [
36
+ ...spec.path,
37
+ ...spec.positionals.map((name) => String(fields[name])),
38
+ ...formatOpts(fields.opts),
39
+ ];
136
40
  }
137
41
  export function commandToString(cmd) {
138
42
  return ["norsk-ctl", ...toArgv(cmd)].join(" ");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@norskvideo/ctl-commands",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {