@dereekb/dbx-cli 13.11.10 → 13.11.12

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.
@@ -0,0 +1,377 @@
1
+ import { vi } from 'vitest';
2
+ import { createCliContext, createCli } from '@dereekb/dbx-cli';
3
+
4
+ function _array_like_to_array(arr, len) {
5
+ if (len == null || len > arr.length) len = arr.length;
6
+ for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
7
+ return arr2;
8
+ }
9
+ function _array_without_holes(arr) {
10
+ if (Array.isArray(arr)) return _array_like_to_array(arr);
11
+ }
12
+ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
13
+ try {
14
+ var info = gen[key](arg);
15
+ var value = info.value;
16
+ } catch (error) {
17
+ reject(error);
18
+ return;
19
+ }
20
+ if (info.done) {
21
+ resolve(value);
22
+ } else {
23
+ Promise.resolve(value).then(_next, _throw);
24
+ }
25
+ }
26
+ function _async_to_generator(fn) {
27
+ return function() {
28
+ var self = this, args = arguments;
29
+ return new Promise(function(resolve, reject) {
30
+ var gen = fn.apply(self, args);
31
+ function _next(value) {
32
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
33
+ }
34
+ function _throw(err) {
35
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
36
+ }
37
+ _next(undefined);
38
+ });
39
+ };
40
+ }
41
+ function _instanceof(left, right) {
42
+ "@swc/helpers - instanceof";
43
+ if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
44
+ return !!right[Symbol.hasInstance](left);
45
+ } else {
46
+ return left instanceof right;
47
+ }
48
+ }
49
+ function _iterable_to_array(iter) {
50
+ if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
51
+ }
52
+ function _non_iterable_spread() {
53
+ throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
54
+ }
55
+ function _to_consumable_array(arr) {
56
+ return _array_without_holes(arr) || _iterable_to_array(arr) || _unsupported_iterable_to_array(arr) || _non_iterable_spread();
57
+ }
58
+ function _type_of(obj) {
59
+ "@swc/helpers - typeof";
60
+ return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
61
+ }
62
+ function _unsupported_iterable_to_array(o, minLen) {
63
+ if (!o) return;
64
+ if (typeof o === "string") return _array_like_to_array(o, minLen);
65
+ var n = Object.prototype.toString.call(o).slice(8, -1);
66
+ if (n === "Object" && o.constructor) n = o.constructor.name;
67
+ if (n === "Map" || n === "Set") return Array.from(n);
68
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
69
+ }
70
+ function _ts_generator(thisArg, body) {
71
+ var f, y, t, _ = {
72
+ label: 0,
73
+ sent: function() {
74
+ if (t[0] & 1) throw t[1];
75
+ return t[1];
76
+ },
77
+ trys: [],
78
+ ops: []
79
+ }, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype), d = Object.defineProperty;
80
+ return d(g, "next", {
81
+ value: verb(0)
82
+ }), d(g, "throw", {
83
+ value: verb(1)
84
+ }), d(g, "return", {
85
+ value: verb(2)
86
+ }), typeof Symbol === "function" && d(g, Symbol.iterator, {
87
+ value: function() {
88
+ return this;
89
+ }
90
+ }), g;
91
+ function verb(n) {
92
+ return function(v) {
93
+ return step([
94
+ n,
95
+ v
96
+ ]);
97
+ };
98
+ }
99
+ function step(op) {
100
+ if (f) throw new TypeError("Generator is already executing.");
101
+ while(g && (g = 0, op[0] && (_ = 0)), _)try {
102
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
103
+ if (y = 0, t) op = [
104
+ op[0] & 2,
105
+ t.value
106
+ ];
107
+ switch(op[0]){
108
+ case 0:
109
+ case 1:
110
+ t = op;
111
+ break;
112
+ case 4:
113
+ _.label++;
114
+ return {
115
+ value: op[1],
116
+ done: false
117
+ };
118
+ case 5:
119
+ _.label++;
120
+ y = op[1];
121
+ op = [
122
+ 0
123
+ ];
124
+ continue;
125
+ case 7:
126
+ op = _.ops.pop();
127
+ _.trys.pop();
128
+ continue;
129
+ default:
130
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
131
+ _ = 0;
132
+ continue;
133
+ }
134
+ if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
135
+ _.label = op[1];
136
+ break;
137
+ }
138
+ if (op[0] === 6 && _.label < t[1]) {
139
+ _.label = t[1];
140
+ t = op;
141
+ break;
142
+ }
143
+ if (t && _.label < t[2]) {
144
+ _.label = t[2];
145
+ _.ops.push(op);
146
+ break;
147
+ }
148
+ if (t[2]) _.ops.pop();
149
+ _.trys.pop();
150
+ continue;
151
+ }
152
+ op = body.call(thisArg, _);
153
+ } catch (e) {
154
+ op = [
155
+ 6,
156
+ e
157
+ ];
158
+ y = 0;
159
+ } finally{
160
+ f = t = 0;
161
+ }
162
+ if (op[0] & 5) throw op[1];
163
+ return {
164
+ value: op[0] ? op[1] : void 0,
165
+ done: true
166
+ };
167
+ }
168
+ }
169
+ /**
170
+ * Builds a {@link CliContext} for use as `testCliContext` on {@link createCli}.
171
+ *
172
+ * Thin wrapper around {@link createCliContext} that exists so test code can import from a single
173
+ * test-only entry without pulling in production-only types.
174
+ *
175
+ * @param input - The context inputs (cliName, envName, env, accessToken, optional modelManifest).
176
+ * @returns The constructed {@link CliContext} that drives `callModel` / `getModel` / `getMultipleModels`
177
+ * against `input.env.apiBaseUrl` with `input.accessToken` as the Bearer token.
178
+ * @__NO_SIDE_EFFECTS__
179
+ */ function buildTestCliContext(input) {
180
+ return createCliContext({
181
+ cliName: input.cliName,
182
+ envName: input.envName,
183
+ env: input.env,
184
+ accessToken: input.accessToken,
185
+ modelManifest: input.modelManifest
186
+ });
187
+ }
188
+ /**
189
+ * Drives a CLI invocation in-process and captures all output.
190
+ *
191
+ * Creates a fresh yargs `Argv` per call (so middleware/option defaults can't leak across tests),
192
+ * parses `args`, and collects `process.stdout.write` / `process.stderr.write` / `console.log` /
193
+ * `console.error` output via `vi.spyOn`. The spies are always restored on completion.
194
+ *
195
+ * Always resolves — handler errors and yargs failures are surfaced in {@link RunCliCommandResult.error}
196
+ * instead of being thrown.
197
+ *
198
+ * @param input - The {@link CreateCliInput} used to build the CLI for this invocation. Pass a fresh
199
+ * object each call (or rely on the caller-side factory) — yargs `Argv` state is not reused across
200
+ * invocations.
201
+ * @param args - The argv vector to parse (e.g. `['get', 'p/abc']`).
202
+ * @returns The captured output envelope.
203
+ */ function runCliCommand(input, args) {
204
+ return _async_to_generator(function() {
205
+ var stdoutChunks, stderrChunks, writeSpy, errSpy, logSpy, errLogSpy, capturedExitCode, exitSentinel, exitSpy, capturedArgv, capturedError, helpOutput, e, result;
206
+ return _ts_generator(this, function(_state) {
207
+ switch(_state.label){
208
+ case 0:
209
+ stdoutChunks = [];
210
+ stderrChunks = [];
211
+ writeSpy = vi.spyOn(process.stdout, 'write').mockImplementation(function(chunk) {
212
+ stdoutChunks.push(toChunkString(chunk));
213
+ return true;
214
+ });
215
+ errSpy = vi.spyOn(process.stderr, 'write').mockImplementation(function(chunk) {
216
+ stderrChunks.push(toChunkString(chunk));
217
+ return true;
218
+ });
219
+ logSpy = vi.spyOn(console, 'log').mockImplementation(function() {
220
+ for(var _len = arguments.length, parts = new Array(_len), _key = 0; _key < _len; _key++){
221
+ parts[_key] = arguments[_key];
222
+ }
223
+ stdoutChunks.push(parts.map(function(p) {
224
+ return stringifyConsolePart(p);
225
+ }).join(' ') + '\n');
226
+ });
227
+ errLogSpy = vi.spyOn(console, 'error').mockImplementation(function() {
228
+ for(var _len = arguments.length, parts = new Array(_len), _key = 0; _key < _len; _key++){
229
+ parts[_key] = arguments[_key];
230
+ }
231
+ stderrChunks.push(parts.map(function(p) {
232
+ return stringifyConsolePart(p);
233
+ }).join(' ') + '\n');
234
+ });
235
+ exitSentinel = new Error('__cli_test_process_exit__');
236
+ exitSpy = vi.spyOn(process, 'exit').mockImplementation(function(code) {
237
+ capturedExitCode = typeof code === 'number' ? code : 0;
238
+ throw exitSentinel;
239
+ });
240
+ helpOutput = '';
241
+ _state.label = 1;
242
+ case 1:
243
+ _state.trys.push([
244
+ 1,
245
+ 3,
246
+ 4,
247
+ 5
248
+ ]);
249
+ return [
250
+ 4,
251
+ createCli(input).exitProcess(false).parse(_to_consumable_array(args), function(err, argv, output) {
252
+ capturedArgv = argv;
253
+ if (err) capturedError = err;
254
+ helpOutput = output;
255
+ })
256
+ ];
257
+ case 2:
258
+ _state.sent();
259
+ return [
260
+ 3,
261
+ 5
262
+ ];
263
+ case 3:
264
+ e = _state.sent();
265
+ if (e !== exitSentinel) {
266
+ capturedError = _instanceof(e, Error) ? e : new Error(String(e));
267
+ }
268
+ return [
269
+ 3,
270
+ 5
271
+ ];
272
+ case 4:
273
+ writeSpy.mockRestore();
274
+ errSpy.mockRestore();
275
+ logSpy.mockRestore();
276
+ errLogSpy.mockRestore();
277
+ exitSpy.mockRestore();
278
+ return [
279
+ 7
280
+ ];
281
+ case 5:
282
+ result = {
283
+ stdout: stdoutChunks,
284
+ stderr: stderrChunks,
285
+ stdoutText: stdoutChunks.join(''),
286
+ stderrText: stderrChunks.join(''),
287
+ argv: capturedArgv,
288
+ helpOutput: helpOutput,
289
+ error: capturedError,
290
+ exitCode: capturedExitCode
291
+ };
292
+ return [
293
+ 2,
294
+ result
295
+ ];
296
+ }
297
+ });
298
+ })();
299
+ }
300
+ /**
301
+ * Idempotently binds the fixture's NestJS application to `127.0.0.1:0` (or the supplied host) and
302
+ * returns the live `apiBaseUrl` so the CLI's `fetch` calls have a real socket to hit.
303
+ *
304
+ * Safe to call multiple times against the same app — when `app.getHttpServer().listening` is true,
305
+ * skips re-binding and just resolves the current address.
306
+ *
307
+ * The caller does NOT need to `.close()` explicitly: the demoApi/firebase-admin-nest fixture closes
308
+ * the underlying NestJS app at the end of its describe block, which closes the HTTP listener.
309
+ *
310
+ * @param input - The fixture app + optional global route prefix (defaults to `'api'` to match
311
+ * demo-api's production prefix) and host (defaults to `127.0.0.1`).
312
+ * @returns The bound `apiBaseUrl` (e.g. `http://127.0.0.1:54321/api`) and the resolved port.
313
+ */ function listenOnNestAppForTest(input) {
314
+ return _async_to_generator(function() {
315
+ var _input_host, _input_apiPrefix, host, prefix, server, address, port, trimmedPrefix, apiBaseUrl;
316
+ return _ts_generator(this, function(_state) {
317
+ switch(_state.label){
318
+ case 0:
319
+ host = (_input_host = input.host) !== null && _input_host !== void 0 ? _input_host : '127.0.0.1';
320
+ prefix = (_input_apiPrefix = input.apiPrefix) !== null && _input_apiPrefix !== void 0 ? _input_apiPrefix : 'api';
321
+ server = input.app.getHttpServer();
322
+ if (!!server.listening) return [
323
+ 3,
324
+ 2
325
+ ];
326
+ return [
327
+ 4,
328
+ input.app.listen(0, host)
329
+ ];
330
+ case 1:
331
+ _state.sent();
332
+ _state.label = 2;
333
+ case 2:
334
+ address = server.address();
335
+ port = (typeof address === "undefined" ? "undefined" : _type_of(address)) === 'object' && address ? address.port : 0;
336
+ trimmedPrefix = prefix.replace(/^\/+|\/+$/g, '');
337
+ apiBaseUrl = trimmedPrefix.length > 0 ? "http://".concat(host, ":").concat(port, "/").concat(trimmedPrefix) : "http://".concat(host, ":").concat(port);
338
+ return [
339
+ 2,
340
+ {
341
+ apiBaseUrl: apiBaseUrl,
342
+ port: port
343
+ }
344
+ ];
345
+ }
346
+ });
347
+ })();
348
+ }
349
+ function toChunkString(chunk) {
350
+ var result;
351
+ if (typeof chunk === 'string') {
352
+ result = chunk;
353
+ } else if (_instanceof(chunk, Uint8Array)) {
354
+ result = Buffer.from(chunk).toString('utf8');
355
+ } else {
356
+ result = String(chunk);
357
+ }
358
+ return result;
359
+ }
360
+ function stringifyConsolePart(part) {
361
+ var result;
362
+ if (typeof part === 'string') {
363
+ result = part;
364
+ } else if (_instanceof(part, Error)) {
365
+ var _part_stack;
366
+ result = (_part_stack = part.stack) !== null && _part_stack !== void 0 ? _part_stack : part.message;
367
+ } else {
368
+ try {
369
+ result = JSON.stringify(part);
370
+ } catch (unused) {
371
+ result = String(part);
372
+ }
373
+ }
374
+ return result;
375
+ }
376
+
377
+ export { buildTestCliContext, listenOnNestAppForTest, runCliCommand };
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@dereekb/dbx-cli/test",
3
+ "version": "13.11.12",
4
+ "peerDependencies": {
5
+ "@dereekb/date": "13.11.12",
6
+ "@dereekb/dbx-cli": "13.11.12",
7
+ "@dereekb/firebase": "13.11.12",
8
+ "@dereekb/firebase-server/test": "13.11.12",
9
+ "@dereekb/model": "13.11.12",
10
+ "@dereekb/nestjs": "13.11.12",
11
+ "@dereekb/rxjs": "13.11.12",
12
+ "@dereekb/util": "13.11.12",
13
+ "@nestjs/common": "^11.1.19",
14
+ "arktype": "^2.2.0",
15
+ "yargs": "^18.0.0"
16
+ },
17
+ "devDependencies": {},
18
+ "exports": {
19
+ "./package.json": "./package.json",
20
+ ".": {
21
+ "module": "./index.esm.js",
22
+ "types": "./index.d.ts",
23
+ "import": "./index.cjs.mjs",
24
+ "default": "./index.cjs.js"
25
+ }
26
+ },
27
+ "module": "./index.esm.js",
28
+ "main": "./index.cjs.js",
29
+ "types": "./index.d.ts"
30
+ }
@@ -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';