@ontrails/commander 0.2.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/src/surface.ts ADDED
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Surface helpers for wiring a topo to Commander.
3
+ */
4
+
5
+ import type {
6
+ BaseSurfaceOptions,
7
+ Layer,
8
+ OverlayEnvelopeLike,
9
+ ResourceOverrideMap,
10
+ Topo,
11
+ TrailContextInit,
12
+ } from '@ontrails/core';
13
+ import type {
14
+ ActionResultContext,
15
+ CliFlag,
16
+ InputResolver,
17
+ ResolveCliPermitFromToken,
18
+ } from '@ontrails/cli';
19
+ import { defaultOnResult, deriveCliCommands } from '@ontrails/cli';
20
+ import type { ToCommanderOptions } from './to-commander.js';
21
+ import { toCommander } from './to-commander.js';
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Options
25
+ // ---------------------------------------------------------------------------
26
+
27
+ /**
28
+ * Options for creating Commander CLI surfaces from a Trails topo.
29
+ */
30
+ export interface CreateProgramOptions extends BaseSurfaceOptions {
31
+ readonly createContext?:
32
+ | (() => TrailContextInit | Promise<TrailContextInit>)
33
+ | undefined;
34
+ readonly description?: string | undefined;
35
+ readonly layers?: readonly Layer[] | undefined;
36
+ readonly name?: string | undefined;
37
+ readonly onResult?: ((ctx: ActionResultContext) => Promise<void>) | undefined;
38
+ /**
39
+ * App-authored overlay envelopes (conventionally the app module's
40
+ * `trailsOverlays` export); the `surfaces` envelope's `cli` bindings
41
+ * render synonym and command-group routes onto the program.
42
+ */
43
+ readonly overlays?: readonly OverlayEnvelopeLike[] | undefined;
44
+ readonly presets?: CliFlag[][] | undefined;
45
+ readonly resources?: ResourceOverrideMap | undefined;
46
+ readonly resolveInput?: InputResolver | undefined;
47
+ readonly resolvePermitFromToken?: ResolveCliPermitFromToken | undefined;
48
+ readonly version?: string | undefined;
49
+ }
50
+
51
+ /**
52
+ * Result returned by running the Commander surface bootstrap.
53
+ */
54
+ export interface SurfaceCliResult {
55
+ readonly exitCode: number;
56
+ }
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // createProgram
60
+ // ---------------------------------------------------------------------------
61
+
62
+ const deriveCommanderOptions = (
63
+ graph: Topo,
64
+ options: CreateProgramOptions
65
+ ): ToCommanderOptions => {
66
+ const commanderOpts: ToCommanderOptions = {
67
+ name: options.name ?? graph.name,
68
+ topoName: graph.name,
69
+ };
70
+ if (options.version !== undefined || graph.version !== undefined) {
71
+ commanderOpts.version = options.version ?? graph.version;
72
+ }
73
+ if (options.description !== undefined || graph.description !== undefined) {
74
+ commanderOpts.description = options.description ?? graph.description;
75
+ }
76
+ return commanderOpts;
77
+ };
78
+
79
+ /**
80
+ * Create a Commander program from a topo without parsing argv.
81
+ *
82
+ * @remarks This is a host materialization boundary. Derivation failures are
83
+ * thrown for the caller's CLI bootstrap code after `deriveCliCommands` has
84
+ * already represented the framework error as a Result.
85
+ *
86
+ * @example
87
+ * ```ts
88
+ * import { createProgram } from '@ontrails/commander';
89
+ *
90
+ * const program = createProgram(graph, { name: 'demo' });
91
+ * program.parse();
92
+ * ```
93
+ */
94
+ export const createProgram = (
95
+ graph: Topo,
96
+ options: CreateProgramOptions = {}
97
+ ) => {
98
+ const commandsResult = deriveCliCommands(graph, {
99
+ configValues: options.configValues,
100
+ createContext: options.createContext,
101
+ exclude: options.exclude,
102
+ include: options.include,
103
+ intent: options.intent,
104
+ layers: options.layers,
105
+ onResult: options.onResult ?? defaultOnResult,
106
+ overlays: options.overlays,
107
+ presets: options.presets,
108
+ resolveInput: options.resolveInput,
109
+ resolvePermitFromToken: options.resolvePermitFromToken,
110
+ resources: options.resources,
111
+ validate: options.validate,
112
+ });
113
+
114
+ if (commandsResult.isErr()) {
115
+ throw commandsResult.error;
116
+ }
117
+
118
+ return toCommander(
119
+ commandsResult.value,
120
+ deriveCommanderOptions(graph, options)
121
+ );
122
+ };
123
+
124
+ // ---------------------------------------------------------------------------
125
+ // surface
126
+ // ---------------------------------------------------------------------------
127
+
128
+ /**
129
+ * Parse argv for a topo through Commander.
130
+ *
131
+ * Returns the process exit code without calling `process.exit()`, so callers
132
+ * can run cleanup before terminating. The CLI `surface()` entry point
133
+ * delegates here and lets the process exit naturally.
134
+ *
135
+ * @example
136
+ * ```ts
137
+ * import { surface } from '@ontrails/commander';
138
+ *
139
+ * const { exitCode } = await surface(graph, { name: 'demo' });
140
+ * ```
141
+ */
142
+ export const surface = async (
143
+ graph: Topo,
144
+ options: CreateProgramOptions = {}
145
+ ): Promise<SurfaceCliResult> => {
146
+ const program = createProgram(graph, options);
147
+ await program.parseAsync();
148
+ const { exitCode } = process;
149
+ return { exitCode: typeof exitCode === 'number' ? exitCode : 0 };
150
+ };