@dereekb/dbx-cli 13.11.10 → 13.11.11

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.
@@ -2,7 +2,6 @@
2
2
 
3
3
  var tsMorph = require('ts-morph');
4
4
 
5
- // 'query' is accepted today even though `<Group>ModelCrudFunctionsConfig` in @dereekb/firebase does not yet permit `query:` keys (deferred follow-up). Once query support lands upstream, every query entry flows through here with no change.
6
5
  var SUPPORTED_VERBS = new Set([
7
6
  'create',
8
7
  'read',
@@ -1,6 +1,5 @@
1
1
  import { Project, Node } from 'ts-morph';
2
2
 
3
- // 'query' is accepted today even though `<Group>ModelCrudFunctionsConfig` in @dereekb/firebase does not yet permit `query:` keys (deferred follow-up). Once query support lands upstream, every query entry flows through here with no change.
4
3
  var SUPPORTED_VERBS = new Set([
5
4
  'create',
6
5
  'read',
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-cli/manifest-extract",
3
- "version": "13.11.10",
3
+ "version": "13.11.11",
4
4
  "sideEffects": false,
5
5
  "peerDependencies": {
6
6
  "ts-morph": "^21.0.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-cli",
3
- "version": "13.11.10",
3
+ "version": "13.11.11",
4
4
  "sideEffects": false,
5
5
  "bin": {
6
6
  "dbx-cli-generate-firebase-api-manifest": "firebase-api-manifest/main.js"
@@ -24,10 +24,10 @@
24
24
  }
25
25
  },
26
26
  "peerDependencies": {
27
- "@dereekb/date": "13.11.10",
28
- "@dereekb/firebase": "13.11.10",
29
- "@dereekb/nestjs": "13.11.10",
30
- "@dereekb/util": "13.11.10",
27
+ "@dereekb/date": "13.11.11",
28
+ "@dereekb/firebase": "13.11.11",
29
+ "@dereekb/nestjs": "13.11.11",
30
+ "@dereekb/util": "13.11.11",
31
31
  "arktype": "^2.2.0",
32
32
  "yargs": "^18.0.0"
33
33
  },
@@ -0,0 +1,80 @@
1
+ import type { Argv, CommandModule } from 'yargs';
2
+ import { type CliContext } from '../context/cli.context';
3
+ /**
4
+ * Specification for a composite action command surfaced under `<cli> action ...`.
5
+ *
6
+ * Actions are user-defined async lambdas that chain multiple {@link CliContext.callModel} /
7
+ * {@link CliContext.getModel} / {@link CliContext.getMultipleModels} calls in-process,
8
+ * letting consumers expose high-leverage workflows (paginate-and-aggregate, fan-out,
9
+ * derived projections) without spending a CLI round-trip per call.
10
+ *
11
+ * Each action runs after the standard auth middleware, so the {@link CliContext} is
12
+ * already populated by the time {@link ActionCommandSpec.handler} fires.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * const districtsForRegion: ActionCommandSpec = {
17
+ * command: 'districts <region>',
18
+ * describe: 'List every District in a Region.',
19
+ * model: 'region',
20
+ * builder: (y) => y.positional('region', { type: 'string' }),
21
+ * handler: async ({ context, argv }) => {
22
+ * const region = String(argv.region);
23
+ * return context.callModel({ modelType: 'district', call: 'query', data: { region } });
24
+ * }
25
+ * };
26
+ * ```
27
+ */
28
+ export interface ActionCommandSpec<TArgv = any, TResult = unknown> {
29
+ /**
30
+ * Yargs command string for the action's leaf — positionals come after the action name.
31
+ * Example: `'open-jobs-for-region <region>'` resolves to `<cli> action open-jobs-for-region <region>`
32
+ * (or `<cli> action <model> open-jobs-for-region <region>` when {@link model} is set).
33
+ */
34
+ readonly command: string;
35
+ /**
36
+ * Short one-line description shown in `<cli> action --help`.
37
+ */
38
+ readonly describe: string;
39
+ /**
40
+ * When provided, the action is grouped under `<cli> action <model> <action>` instead of
41
+ * the root `<cli> action <action>`. Use the model's persisted type (e.g. `'region'`,
42
+ * `'district'`) so the grouping mirrors `model <model>` from {@link buildManifestCommands}.
43
+ */
44
+ readonly model?: string;
45
+ /**
46
+ * Optional yargs builder. Use to declare positionals and options that the action consumes.
47
+ */
48
+ readonly builder?: (yargs: Argv) => Argv;
49
+ /**
50
+ * Optional epilogue rendered after `--help` for this action.
51
+ */
52
+ readonly helpEpilogue?: string;
53
+ /**
54
+ * The action body. Receives the live {@link CliContext} (auth already resolved) and the
55
+ * parsed argv. Throwing a {@link CliError} surfaces a structured failure envelope.
56
+ */
57
+ readonly handler: (input: {
58
+ readonly context: CliContext;
59
+ readonly argv: TArgv;
60
+ }) => Promise<TResult> | TResult;
61
+ /**
62
+ * Optional result transform applied before {@link outputResult} serialises the value.
63
+ * Use to strip noise (e.g. paging cursors) or shape the response for downstream callers.
64
+ */
65
+ readonly mapResult?: (result: TResult) => unknown;
66
+ }
67
+ /**
68
+ * Wraps an {@link ActionCommandSpec} as a yargs `CommandModule`.
69
+ *
70
+ * The returned command:
71
+ * - Calls {@link requireCliContext} so it fails loudly when auth middleware did not run.
72
+ * - Invokes `spec.handler({ context, argv })`.
73
+ * - Pipes the result through `spec.mapResult` when provided.
74
+ * - Emits the value via {@link outputResult}; emits {@link outputError} + `process.exit(1)` on failure.
75
+ *
76
+ * @param spec - The action specification.
77
+ * @returns The yargs command module ready to be registered under the `action` parent.
78
+ * @__NO_SIDE_EFFECTS__
79
+ */
80
+ export declare function createActionCommand<TArgv = any, TResult = unknown>(spec: ActionCommandSpec<TArgv, TResult>): CommandModule;
@@ -0,0 +1,30 @@
1
+ import type { CommandModule } from 'yargs';
2
+ import { type ActionCommandSpec } from './action.command.factory';
3
+ /**
4
+ * Default name of the parent command that groups all registered action specs.
5
+ * Surfaces as `<cli> action <action>` (root) and `<cli> action <model> <action>` (model-scoped).
6
+ */
7
+ export declare const DEFAULT_ACTION_COMMAND_NAME = "action";
8
+ /**
9
+ * Options accepted by {@link buildActionCommands}.
10
+ */
11
+ export interface BuildActionCommandsOptions {
12
+ /**
13
+ * Name of the parent command that groups all action specs. Defaults to
14
+ * {@link DEFAULT_ACTION_COMMAND_NAME} (`action`).
15
+ */
16
+ readonly actionCommandName?: string;
17
+ }
18
+ /**
19
+ * Assembles the parent `action` command tree from a list of {@link ActionCommandSpec}s.
20
+ *
21
+ * Specs without a `model` are appended as direct subcommands of `action`. Specs with a
22
+ * `model` are grouped under `action <model> <action>` (mirroring `buildManifestCommands`'
23
+ * `model <model> <action>` shape) so the model-scoped surface stays browseable.
24
+ *
25
+ * @param specs - The action specs registered by the app.
26
+ * @param options - Optional overrides (e.g. parent command name).
27
+ * @returns An array containing a single parent `action` command, or empty when no specs were provided.
28
+ * @__NO_SIDE_EFFECTS__
29
+ */
30
+ export declare function buildActionCommands(specs: readonly ActionCommandSpec[], options?: BuildActionCommandsOptions): CommandModule[];
@@ -0,0 +1,3 @@
1
+ export * from './action.command.factory';
2
+ export * from './build-action-commands';
3
+ export * from './iterate';
@@ -0,0 +1,231 @@
1
+ import type { OnCallFunctionType, OnCallQueryModelResult } from '@dereekb/firebase';
2
+ import { type Maybe, type PerformAsyncTasksConfig } from '@dereekb/util';
3
+ import { type CliContext } from '../context/cli.context';
4
+ /**
5
+ * State passed into {@link IterateDbxCliCallModelConfig.buildRequestData} when assembling
6
+ * the next page's request payload.
7
+ */
8
+ export interface IterateDbxCliCallModelPageState {
9
+ /**
10
+ * Zero-based index of the page about to be fetched.
11
+ */
12
+ readonly pageIndex: number;
13
+ /**
14
+ * Cursor key returned by the previous page, or undefined for the first page.
15
+ */
16
+ readonly cursorDocumentKey: Maybe<string>;
17
+ /**
18
+ * Total items visited prior to this page.
19
+ */
20
+ readonly visitedItems: number;
21
+ }
22
+ /**
23
+ * Per-item callback signature for {@link iterateDbxCliCallModel}.
24
+ *
25
+ * @typeParam TItem - The item type yielded by the page response.
26
+ * @typeParam TItemResult - The value produced by processing one item.
27
+ */
28
+ export type IterateDbxCliCallModelItemFn<TItem, TItemResult> = (input: {
29
+ readonly context: CliContext;
30
+ readonly item: TItem;
31
+ readonly key: string;
32
+ readonly pageIndex: number;
33
+ readonly itemIndex: number;
34
+ }) => Promise<TItemResult>;
35
+ /**
36
+ * Per-page callback signature for {@link iterateDbxCliCallModel}. Invoked after
37
+ * {@link IterateDbxCliCallModelItemFn} (when both are configured).
38
+ */
39
+ export type IterateDbxCliCallModelPageFn<TItem, TRaw, TItemResult, TPageResult> = (input: {
40
+ readonly context: CliContext;
41
+ readonly page: TRaw;
42
+ readonly items: ReadonlyArray<TItem>;
43
+ readonly keys: ReadonlyArray<string>;
44
+ readonly pageIndex: number;
45
+ readonly pageItemResults?: ReadonlyArray<TItemResult>;
46
+ }) => Promise<TPageResult>;
47
+ /**
48
+ * Adapter that maps an arbitrary `callModel` response shape into the pieces
49
+ * {@link iterateDbxCliCallModel} needs (items, cursor, has-more).
50
+ *
51
+ * Defaults to the {@link OnCallQueryModelResult} shape — supply a custom adapter
52
+ * when iterating responses from other call types (e.g. `getMultiple`-style calls
53
+ * or custom standalone calls that return an array of records).
54
+ */
55
+ export interface IterateDbxCliCallModelResponseAdapter<TRaw, TItem> {
56
+ /**
57
+ * Pulls the page's items out of the raw response.
58
+ */
59
+ readonly items: (raw: TRaw) => ReadonlyArray<TItem>;
60
+ /**
61
+ * Returns the keys aligned with {@link items}. Defaults to an empty array when omitted.
62
+ */
63
+ readonly keys?: (raw: TRaw) => ReadonlyArray<string>;
64
+ /**
65
+ * Returns the cursor key for the next page, or undefined to stop.
66
+ */
67
+ readonly cursorDocumentKey?: (raw: TRaw) => Maybe<string>;
68
+ /**
69
+ * Overrides the "keep going" check. Defaults to `!!cursorDocumentKey(raw)`.
70
+ */
71
+ readonly hasMore?: (raw: TRaw) => boolean;
72
+ }
73
+ /**
74
+ * Config for {@link iterateDbxCliCallModel}.
75
+ *
76
+ * Cursor-paginated iterator over a `callModel` endpoint that returns an array of
77
+ * items. The default {@link responseAdapter} reads {@link OnCallQueryModelResult}
78
+ * fields (`results`, `keys`, `cursorDocumentKey`, `hasMore`), so a typical
79
+ * `call: 'query'` flow only needs to specify `modelType` and `params`. Custom
80
+ * call shapes can override both {@link buildRequestData} and {@link responseAdapter}.
81
+ *
82
+ * @typeParam TParams - The request data shape passed to `callModel`.
83
+ * @typeParam TItem - The item type yielded by the page response.
84
+ * @typeParam TRaw - The full raw response shape. Defaults to {@link OnCallQueryModelResult}<TItem>.
85
+ * @typeParam TItemResult - Per-item processing result type.
86
+ * @typeParam TPageResult - Per-page processing result type.
87
+ */
88
+ export interface IterateDbxCliCallModelConfig<TParams, TItem, TRaw = OnCallQueryModelResult<TItem>, TItemResult = void, TPageResult = void> {
89
+ /**
90
+ * Active CLI context (provides callModel + auth).
91
+ */
92
+ readonly context: CliContext;
93
+ /**
94
+ * Target model type, e.g. `'guestbook'`.
95
+ */
96
+ readonly modelType: string;
97
+ /**
98
+ * Call type.
99
+ */
100
+ readonly call: OnCallFunctionType;
101
+ /**
102
+ * Optional specifier passed through to `OnCallTypedModelParams`.
103
+ */
104
+ readonly specifier?: string;
105
+ /**
106
+ * Base request data merged into each page request. The iterator owns the
107
+ * `cursorDocumentKey` field — anything else on the object (filters, parent
108
+ * keys, etc.) is forwarded verbatim.
109
+ */
110
+ readonly params: TParams;
111
+ /**
112
+ * Override how the per-page request data is assembled. Defaults to spreading
113
+ * `params` with `cursorDocumentKey` and (when {@link limitPerPage} or the
114
+ * remaining {@link totalItemsLimit} budget is set) `limit` injected.
115
+ */
116
+ readonly buildRequestData?: (params: TParams, state: IterateDbxCliCallModelPageState, limit: Maybe<number>) => TParams;
117
+ /**
118
+ * Adapter for non-{@link OnCallQueryModelResult} responses.
119
+ */
120
+ readonly responseAdapter?: IterateDbxCliCallModelResponseAdapter<TRaw, TItem>;
121
+ /**
122
+ * Per-page request limit. Forwarded into the default {@link buildRequestData}.
123
+ */
124
+ readonly limitPerPage?: number;
125
+ /**
126
+ * Stop once this many items have been visited across all pages.
127
+ */
128
+ readonly totalItemsLimit?: number;
129
+ /**
130
+ * Stop after this many pages.
131
+ */
132
+ readonly maxPages?: number;
133
+ /**
134
+ * Per-item processing callback. Parallelism controlled by {@link maxParallelPerPage}.
135
+ */
136
+ readonly iterateItem?: IterateDbxCliCallModelItemFn<TItem, TItemResult>;
137
+ /**
138
+ * Per-page processing callback. Runs after {@link iterateItem} when both are set.
139
+ */
140
+ readonly iteratePage?: IterateDbxCliCallModelPageFn<TItem, TRaw, TItemResult, TPageResult>;
141
+ /**
142
+ * Concurrency knobs forwarded to `performAsyncTasks` for {@link iterateItem}.
143
+ */
144
+ readonly itemPerformTasksConfig?: Partial<PerformAsyncTasksConfig<TItem>>;
145
+ /**
146
+ * Shorthand for `itemPerformTasksConfig.maxParallelTasks`.
147
+ */
148
+ readonly maxParallelPerPage?: number;
149
+ /**
150
+ * Collect items into the result array. Defaults to true.
151
+ */
152
+ readonly collectItems?: boolean;
153
+ /**
154
+ * Collect per-item results into the result array. Defaults to true when {@link iterateItem} is set.
155
+ */
156
+ readonly collectItemResults?: boolean;
157
+ /**
158
+ * Collect per-page results into the result array. Defaults to true when {@link iteratePage} is set.
159
+ */
160
+ readonly collectPageResults?: boolean;
161
+ }
162
+ /**
163
+ * Aggregate result returned by {@link iterateDbxCliCallModel}.
164
+ */
165
+ export interface IterateDbxCliCallModelResult<TItem, TItemResult, TPageResult> {
166
+ /**
167
+ * Number of pages fetched.
168
+ */
169
+ readonly totalPages: number;
170
+ /**
171
+ * Number of items visited across all pages.
172
+ */
173
+ readonly totalItems: number;
174
+ /**
175
+ * True when iteration stopped because of `totalItemsLimit` / `maxPages`.
176
+ */
177
+ readonly hitLimit: boolean;
178
+ /**
179
+ * Last cursor seen. Useful for callers that want to resume later.
180
+ */
181
+ readonly lastCursorDocumentKey: Maybe<string>;
182
+ /**
183
+ * Flat list of items across all pages. Present when `collectItems !== false`.
184
+ */
185
+ readonly items?: ReadonlyArray<TItem>;
186
+ /**
187
+ * Flat list of per-item processing results. Present when {@link iterateItem} ran and `collectItemResults !== false`.
188
+ */
189
+ readonly itemResults?: ReadonlyArray<TItemResult>;
190
+ /**
191
+ * Per-page processing results. Present when {@link iteratePage} ran and `collectPageResults !== false`.
192
+ */
193
+ readonly pageResults?: ReadonlyArray<TPageResult>;
194
+ }
195
+ /**
196
+ * Iterates a paginated `callModel` endpoint, exhausting (or stopping at the configured limit) all pages.
197
+ *
198
+ * Cursor-paginated analog of {@link iterateFirestoreDocumentSnapshots}, but speaking HTTP/`callModel`
199
+ * rather than direct Firestore. The default response adapter reads the canonical
200
+ * {@link OnCallQueryModelResult} shape — supply a custom adapter to iterate other array-returning
201
+ * call types (e.g. a `getMultiple`-style standalone call).
202
+ *
203
+ * Concurrency: pages are fetched serially (cursor dependency); items within a page can run
204
+ * in parallel via `maxParallelPerPage` (forwarded to `performAsyncTasks`).
205
+ *
206
+ * @example
207
+ * ```ts
208
+ * // Exhaust every published Guestbook entry for a single Guestbook.
209
+ * const { totalItems, items } = await iterateDbxCliCallModel<QueryGuestbookEntriesParams, GuestbookEntry>({
210
+ * context,
211
+ * modelType: 'guestbookEntry',
212
+ * params: { guestbook: 'gb/abc', published: true }
213
+ * });
214
+ * ```
215
+ *
216
+ * @example
217
+ * ```ts
218
+ * // Fan out to a child action per Guestbook, with 4-way parallelism per page.
219
+ * const { itemResults } = await iterateDbxCliCallModel<QueryGuestbooksParams, Guestbook, OnCallQueryModelResult<Guestbook>, EntriesForGuestbook>({
220
+ * context,
221
+ * modelType: 'guestbook',
222
+ * params: { published: true },
223
+ * maxParallelPerPage: 4,
224
+ * iterateItem: ({ context, key }) => queryGuestbookEntriesForGuestbook({ context, guestbook: key, published: true })
225
+ * });
226
+ * ```
227
+ *
228
+ * @param config - Iterator configuration.
229
+ * @returns The aggregate result.
230
+ */
231
+ export declare function iterateDbxCliCallModel<TParams, TItem, TRaw = OnCallQueryModelResult<TItem>, TItemResult = void, TPageResult = void>(config: IterateDbxCliCallModelConfig<TParams, TItem, TRaw, TItemResult, TPageResult>): Promise<IterateDbxCliCallModelResult<TItem, TItemResult, TPageResult>>;
@@ -1,3 +1,4 @@
1
+ export * from './action';
1
2
  export * from './api';
2
3
  export * from './auth';
3
4
  export * from './config';
@@ -1,5 +1,6 @@
1
1
  import type { MiddlewareFunction } from 'yargs';
2
2
  import { type CliEnvDefault } from '../config/env';
3
+ import { type CliContext } from '../context/cli.context';
3
4
  import { type CliModelManifest } from '../manifest/types';
4
5
  export interface CreateAuthMiddlewareInput {
5
6
  readonly cliName: string;
@@ -34,3 +35,17 @@ export interface CreateAuthMiddlewareInput {
34
35
  * @__NO_SIDE_EFFECTS__
35
36
  */
36
37
  export declare function createAuthMiddleware(input: CreateAuthMiddlewareInput): MiddlewareFunction;
38
+ /**
39
+ * Test-only middleware that skips OIDC discovery, disk token loading, and token refresh, attaching
40
+ * a pre-built {@link CliContext} directly via {@link setCliContext}.
41
+ *
42
+ * @internal Intended for use by `@dereekb/dbx-cli/test`. Not for production wiring.
43
+ *
44
+ * @param input - The pre-built context to attach on every invocation.
45
+ * @param input.cliContext - The {@link CliContext} that will be attached via {@link setCliContext}.
46
+ * @returns A yargs middleware function that always sets the provided context.
47
+ * @__NO_SIDE_EFFECTS__
48
+ */
49
+ export declare function createPassthroughAuthMiddleware(input: {
50
+ readonly cliContext: CliContext;
51
+ }): MiddlewareFunction;
@@ -1,5 +1,7 @@
1
1
  import { type Argv, type CommandModule } from 'yargs';
2
+ import { type ActionCommandSpec } from '../action/action.command.factory';
2
3
  import { type CliEnvDefault } from '../config/env';
4
+ import { type CliContext } from '../context/cli.context';
3
5
  import { type DoctorCheck } from '../doctor/doctor.command.factory';
4
6
  import { type CliModelManifest } from '../manifest/types';
5
7
  /**
@@ -26,6 +28,18 @@ export interface CreateCliInput {
26
28
  * it returns a single parent `model <model>` command so the top-level `--help` stays focused.
27
29
  */
28
30
  readonly apiCommands?: CommandModule[];
31
+ /**
32
+ * App-defined composite actions surfaced under `<cli> action <action>` (root) or
33
+ * `<cli> action <model> <action>` (model-scoped).
34
+ *
35
+ * Each action runs after the auth middleware so the handler receives the live
36
+ * {@link CliContext}. Use for high-leverage workflows that chain multiple
37
+ * `callModel` / `getModel` / `getMultipleModels` calls in-process so the caller
38
+ * does not pay one CLI round-trip per call.
39
+ *
40
+ * When omitted or empty, no `action` parent command is registered.
41
+ */
42
+ readonly actionCommands?: readonly ActionCommandSpec[];
29
43
  /**
30
44
  * Extra checks appended to the doctor's default check list.
31
45
  */
@@ -87,6 +101,17 @@ export interface CreateCliInput {
87
101
  * not want to surface the key-decode command itself.
88
102
  */
89
103
  readonly disableModelDecode?: boolean;
104
+ /**
105
+ * Test-only override that bypasses the auth middleware entirely and attaches the supplied
106
+ * {@link CliContext} on every command invocation.
107
+ *
108
+ * When set, the default auth middleware (OIDC discovery, disk token load, refresh, `process.exit`
109
+ * on failure) is replaced with a passthrough that just calls `setCliContext(testCliContext)`. The
110
+ * output middleware still runs.
111
+ *
112
+ * @internal Intended for use from `@dereekb/dbx-cli/test`. Not for production wiring.
113
+ */
114
+ readonly testCliContext?: CliContext;
90
115
  }
91
116
  /**
92
117
  * Top-level CLI builder.
@@ -0,0 +1 @@
1
+ export * from './lib';
@@ -0,0 +1,101 @@
1
+ import { type INestApplication } from '@nestjs/common';
2
+ import { type CliContext, type CliEnvConfig, type CliModelManifest, type CreateCliInput } from '@dereekb/dbx-cli';
3
+ /**
4
+ * Input for {@link buildTestCliContext}.
5
+ */
6
+ export interface BuildTestCliContextInput {
7
+ readonly cliName: string;
8
+ readonly envName: string;
9
+ readonly env: CliEnvConfig;
10
+ readonly accessToken: string;
11
+ readonly modelManifest?: CliModelManifest;
12
+ }
13
+ /**
14
+ * Builds a {@link CliContext} for use as `testCliContext` on {@link createCli}.
15
+ *
16
+ * Thin wrapper around {@link createCliContext} that exists so test code can import from a single
17
+ * test-only entry without pulling in production-only types.
18
+ *
19
+ * @param input - The context inputs (cliName, envName, env, accessToken, optional modelManifest).
20
+ * @returns The constructed {@link CliContext} that drives `callModel` / `getModel` / `getMultipleModels`
21
+ * against `input.env.apiBaseUrl` with `input.accessToken` as the Bearer token.
22
+ * @__NO_SIDE_EFFECTS__
23
+ */
24
+ export declare function buildTestCliContext(input: BuildTestCliContextInput): CliContext;
25
+ /**
26
+ * Result envelope returned by {@link runCliCommand}.
27
+ *
28
+ * Captures everything tests typically want to assert on: stdout/stderr chunks, the parsed argv,
29
+ * yargs's help output (if any), any handler error, and the exit code if the handler called
30
+ * `process.exit` (which is intercepted so it does not actually terminate vitest).
31
+ *
32
+ * {@link runCliCommand} always resolves — errors come back in {@link RunCliCommandResult.error}
33
+ * and exit attempts in {@link RunCliCommandResult.exitCode} so tests can assert on error envelopes
34
+ * without try/catch boilerplate.
35
+ */
36
+ export interface RunCliCommandResult {
37
+ readonly stdout: readonly string[];
38
+ readonly stderr: readonly string[];
39
+ readonly stdoutText: string;
40
+ readonly stderrText: string;
41
+ readonly argv: unknown;
42
+ readonly helpOutput: string;
43
+ readonly error?: Error;
44
+ /**
45
+ * Set when the CLI's handler called `process.exit(code)`. Captured (so the test process is not
46
+ * killed) and surfaced here so tests can assert on the intended exit status.
47
+ *
48
+ * Production CLI handlers exit with `1` on CliError (via `wrapCommandHandler`) and `4` on auth
49
+ * middleware failures. With the `testCliContext` override the auth middleware never exits, but
50
+ * handler errors still go through `wrapCommandHandler`.
51
+ */
52
+ readonly exitCode?: number;
53
+ }
54
+ /**
55
+ * Drives a CLI invocation in-process and captures all output.
56
+ *
57
+ * Creates a fresh yargs `Argv` per call (so middleware/option defaults can't leak across tests),
58
+ * parses `args`, and collects `process.stdout.write` / `process.stderr.write` / `console.log` /
59
+ * `console.error` output via `vi.spyOn`. The spies are always restored on completion.
60
+ *
61
+ * Always resolves — handler errors and yargs failures are surfaced in {@link RunCliCommandResult.error}
62
+ * instead of being thrown.
63
+ *
64
+ * @param input - The {@link CreateCliInput} used to build the CLI for this invocation. Pass a fresh
65
+ * object each call (or rely on the caller-side factory) — yargs `Argv` state is not reused across
66
+ * invocations.
67
+ * @param args - The argv vector to parse (e.g. `['get', 'p/abc']`).
68
+ * @returns The captured output envelope.
69
+ */
70
+ export declare function runCliCommand(input: CreateCliInput, args: readonly string[]): Promise<RunCliCommandResult>;
71
+ /**
72
+ * Idempotently binds the fixture's NestJS application to `127.0.0.1:0` (or the supplied host) and
73
+ * returns the live `apiBaseUrl` so the CLI's `fetch` calls have a real socket to hit.
74
+ *
75
+ * Safe to call multiple times against the same app — when `app.getHttpServer().listening` is true,
76
+ * skips re-binding and just resolves the current address.
77
+ *
78
+ * The caller does NOT need to `.close()` explicitly: the demoApi/firebase-admin-nest fixture closes
79
+ * the underlying NestJS app at the end of its describe block, which closes the HTTP listener.
80
+ *
81
+ * @param input - The fixture app + optional global route prefix (defaults to `'api'` to match
82
+ * demo-api's production prefix) and host (defaults to `127.0.0.1`).
83
+ * @returns The bound `apiBaseUrl` (e.g. `http://127.0.0.1:54321/api`) and the resolved port.
84
+ */
85
+ export declare function listenOnNestAppForTest(input: ListenOnNestAppForTestInput): Promise<ListenOnNestAppForTestResult>;
86
+ export interface ListenOnNestAppForTestInput {
87
+ readonly app: INestApplication;
88
+ /**
89
+ * Global route prefix the NestJS app is mounted under. Defaults to `'api'` to match the demo-api
90
+ * production configuration. Pass an empty string to skip the prefix.
91
+ */
92
+ readonly apiPrefix?: string;
93
+ /**
94
+ * Host to bind to. Defaults to `127.0.0.1`.
95
+ */
96
+ readonly host?: string;
97
+ }
98
+ export interface ListenOnNestAppForTestResult {
99
+ readonly apiBaseUrl: string;
100
+ readonly port: number;
101
+ }
@@ -0,0 +1 @@
1
+ export * from './cli-test';