@atcute/lex-cli 1.0.2 → 1.0.3

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,572 @@
1
+ import { readFile } from 'node:fs/promises';
2
+
3
+ import {
4
+ documentSchema,
5
+ type DocumentSchema,
6
+ type RefVariantSchema,
7
+ type UserTypeSchema,
8
+ type XrpcParametersSchema,
9
+ } from './schema.js';
10
+
11
+ const toUpperCache: Record<string, string> = Object.create(null);
12
+ const toNamespaceCache: Record<string, string> = Object.create(null);
13
+
14
+ const toUpper = (s: string) => {
15
+ return (toUpperCache[s] ??= s[0].toUpperCase() + s.slice(1));
16
+ };
17
+ const toNamespace = (s: string) => {
18
+ return (toNamespaceCache[s] ??= s.replace(/^\w|\.\w/g, (m) => m[m.length === 1 ? 0 : 1].toUpperCase()));
19
+ };
20
+
21
+ const sortName: (a: string, b: string) => number = (() => {
22
+ const collator = new Intl.Collator('en-US');
23
+ return (a, b) => collator.compare(a, b);
24
+ })();
25
+
26
+ const sortDefinition = (a: string, b: string) => {
27
+ const aIsMain = a === 'main';
28
+ const bIsMain = b === 'main';
29
+
30
+ if (aIsMain === bIsMain) {
31
+ return sortName(a, b);
32
+ }
33
+
34
+ return +bIsMain - +aIsMain;
35
+ };
36
+
37
+ const writeJsdoc = (descriptions: string[]) => {
38
+ const len = descriptions.length;
39
+
40
+ if (len === 0) {
41
+ return '';
42
+ }
43
+
44
+ if (len === 1) {
45
+ return `\n/** ${descriptions[0]} */\n`;
46
+ }
47
+
48
+ let jsdoc = '\n/**';
49
+
50
+ for (let idx = 0; idx < len; idx++) {
51
+ const suffix = idx !== len - 1 && descriptions[idx + 1][0] !== '@';
52
+ jsdoc += `\n * ${descriptions[idx]}${suffix ? ` \\` : ''}`;
53
+ }
54
+
55
+ jsdoc += `\n */\n`;
56
+ return jsdoc;
57
+ };
58
+
59
+ const resolveType = (
60
+ nsid: string,
61
+ def: UserTypeSchema | RefVariantSchema | XrpcParametersSchema,
62
+ ): { value: string; descriptions: string[] } => {
63
+ const type = def.type;
64
+
65
+ /** @type {string[]} */
66
+ let descs = [];
67
+ let val = 'unknown';
68
+
69
+ if (def.description) {
70
+ descs.push(def.description);
71
+
72
+ if (def.description.toLowerCase().startsWith('deprecated')) {
73
+ descs.push(`@deprecated`);
74
+ }
75
+ }
76
+
77
+ if (type === 'unknown') {
78
+ val = 'unknown';
79
+ } else if (type === 'cid-link') {
80
+ val = 'At.CIDLink';
81
+ } else if (type === 'integer') {
82
+ val = 'number';
83
+
84
+ if (def.minimum !== undefined) {
85
+ descs.push(`Minimum: ${def.minimum}`);
86
+ }
87
+
88
+ if (def.maximum !== undefined) {
89
+ descs.push(`Maximum: ${def.maximum}`);
90
+ }
91
+
92
+ if (def.default !== undefined) {
93
+ descs.push(`@default ${def.default}`);
94
+ }
95
+ } else if (type === 'boolean') {
96
+ val = 'boolean';
97
+
98
+ if (def.default !== undefined) {
99
+ descs.push(`@default ${def.default}`);
100
+ }
101
+ } else if (type === 'string') {
102
+ const enums = def.enum;
103
+ const known = def.knownValues;
104
+ const format = def.format;
105
+
106
+ if (format !== undefined) {
107
+ if (format === 'did') {
108
+ val = 'At.DID';
109
+ } else if (format === 'cid') {
110
+ val = 'At.CID';
111
+ } else if (format === 'handle') {
112
+ val = 'At.Handle';
113
+ } else if (format === 'at-uri') {
114
+ val = 'At.Uri';
115
+ } else if (
116
+ format === 'at-identifier' ||
117
+ format === 'datetime' ||
118
+ format === 'language' ||
119
+ format === 'nsid' ||
120
+ format === 'uri'
121
+ ) {
122
+ // deliberately ignored
123
+ val = 'string';
124
+ } else {
125
+ console.warn(`${nsid}: unknown format ${format}`);
126
+ val = 'string';
127
+ }
128
+ } else {
129
+ if (def.minLength !== undefined) {
130
+ descs.push(`Minimum string length: ${def.minLength}`);
131
+ }
132
+
133
+ if (def.maxLength !== undefined) {
134
+ descs.push(`Maximum string length: ${def.maxLength}`);
135
+ }
136
+
137
+ if (def.maxGraphemes !== undefined) {
138
+ descs.push(`Maximum grapheme length: ${def.maxGraphemes}`);
139
+ }
140
+
141
+ if (def.default !== undefined) {
142
+ descs.push(`@default ${JSON.stringify(def.default)}`);
143
+ }
144
+
145
+ if (enums) {
146
+ val = enums.map((val) => JSON.stringify(val)).join('|');
147
+ } else if (known) {
148
+ val = `${known
149
+ .toSorted(sortName)
150
+ .map((val) => JSON.stringify(val))
151
+ .join('|')} | (string & {})`;
152
+ } else {
153
+ val = 'string';
154
+ }
155
+ }
156
+ } else if (type === 'array') {
157
+ const { value, descriptions } = resolveType(`${nsid}/0`, def.items);
158
+
159
+ if (def.minLength !== undefined) {
160
+ descs.push(`Minimum array length: ${def.minLength}`);
161
+ }
162
+
163
+ if (def.maxLength !== undefined) {
164
+ descs.push(`Maximum array length: ${def.maxLength}`);
165
+ }
166
+
167
+ val = `(${value})[]`;
168
+ descs = descs.concat(descriptions);
169
+ } else if (type === 'blob') {
170
+ // const accept = def.accept?.map((mime) => `\`${mime.replaceAll('*', '${string}')}\``);
171
+ // val = `At.Blob${accept ? `<${accept.join('|')}>` : ''}`;
172
+
173
+ val = `At.Blob`;
174
+ } else if (type === 'ref') {
175
+ const [ns, ref] = def.ref.split('#');
176
+ val = (ns ? toNamespace(ns) + '.' : '') + (ref ? toUpper(ref) : 'Main');
177
+ } else if (type === 'union') {
178
+ const refs = def.refs.toSorted(sortName).map((raw) => {
179
+ const [ns, ref] = raw.split('#');
180
+ return (ns ? toNamespace(ns) + '.' : '') + (ref ? toUpper(ref) : 'Main');
181
+ });
182
+
183
+ val = `Brand.Union<${refs.join('|')}>`;
184
+ } else if (type === 'object' || type === 'params') {
185
+ const required = def.required;
186
+ const nullable = type === 'object' ? def.nullable : [];
187
+ const properties = def.properties;
188
+
189
+ const propKeys = Object.keys(properties).sort((a, b) => {
190
+ const aIsOptional = !required || !required.includes(a);
191
+ const bIsOptional = !required || !required.includes(b);
192
+
193
+ if (aIsOptional === bIsOptional) {
194
+ return sortName(a, b);
195
+ }
196
+
197
+ return +aIsOptional - +bIsOptional;
198
+ });
199
+
200
+ let chunk = '{';
201
+
202
+ for (const prop of propKeys) {
203
+ const isOptional = !required || !required.includes(prop);
204
+ const isNullable = nullable !== undefined && nullable.includes(prop);
205
+ const { value, descriptions } = resolveType(`${nsid}/${prop}`, properties[prop]);
206
+
207
+ chunk += writeJsdoc(descriptions);
208
+ chunk += `${prop}${isOptional ? `?` : ``}:${value}${isNullable ? `| null` : ``};`;
209
+ }
210
+
211
+ chunk += '}';
212
+ val = chunk;
213
+ } else if (type === 'bytes') {
214
+ val = `At.Bytes`;
215
+ } else {
216
+ console.log(`${nsid}: unknown type ${type}`);
217
+ }
218
+
219
+ return { value: val, descriptions: descs };
220
+ };
221
+
222
+ export interface GenerateDefinitionsOptions {
223
+ files: string[];
224
+ main: boolean;
225
+ banner?: string;
226
+ description?: string;
227
+ }
228
+
229
+ const mainPrelude = `type ObjectOmit<T, K extends keyof any> = Omit<T, K>;
230
+
231
+ /** Handles type branding in objects */
232
+ export declare namespace Brand {
233
+ /** Symbol used to brand objects, this does not actually exist in runtime */
234
+ const Type: unique symbol;
235
+
236
+ /** Get the intended \`$type\` field */
237
+ type GetType<T extends { [Type]?: string }> = NonNullable<T[typeof Type]>;
238
+
239
+ /** Creates a union of objects where it's discriminated by \`$type\` field. */
240
+ type Union<T extends { [Type]?: string }> = T extends any ? T & { $type: GetType<T> } : never;
241
+
242
+ /** Omits the type branding from object */
243
+ type Omit<T extends { [Type]?: string }> = ObjectOmit<T, typeof Type>;
244
+ }
245
+
246
+ /** Base AT Protocol schema types */
247
+ export declare namespace At {
248
+ /** CID string */
249
+ type CID = string;
250
+
251
+ /** DID of a user */
252
+ type DID = \`did:\${string}\`;
253
+
254
+ /** User handle */
255
+ type Handle = string;
256
+
257
+ /** URI string */
258
+ type Uri = string;
259
+
260
+ /** Object containing a CID string */
261
+ interface CIDLink {
262
+ $link: CID;
263
+ }
264
+
265
+ /** Object containing a base64-encoded bytes */
266
+ interface Bytes {
267
+ $bytes: string;
268
+ }
269
+
270
+ /** Blob interface */
271
+ interface Blob<T extends string = string> {
272
+ $type: 'blob';
273
+ mimeType: T;
274
+ ref: {
275
+ $link: string;
276
+ };
277
+ size: number;
278
+ }
279
+ }`;
280
+
281
+ export const generateDefinitions = async (opts: GenerateDefinitionsOptions) => {
282
+ const { files, main, banner, description } = opts;
283
+
284
+ let queries = '';
285
+ let procedures = '';
286
+ let records = '';
287
+
288
+ let code = `/* eslint-disable */
289
+ // This file is automatically generated, do not edit!`;
290
+
291
+ if (description) {
292
+ code += `\n\n/**
293
+ * @module
294
+ * ${description}
295
+ */`;
296
+ }
297
+
298
+ if (main) {
299
+ code += `\n\n${banner ?? ''}\n${mainPrelude}`;
300
+ } else {
301
+ code += `\n\nimport "@atcute/client/lexicons";${banner ?? ''}
302
+
303
+ declare module "@atcute/client/lexicons" {`;
304
+ }
305
+
306
+ for await (const filename of files.sort(sortName)) {
307
+ let document: DocumentSchema;
308
+
309
+ try {
310
+ const jsonString = await readFile(filename, 'utf8');
311
+ document = documentSchema.parse(JSON.parse(jsonString), { mode: 'passthrough' });
312
+ } catch (err) {
313
+ throw new Error(`failed to read ${filename}`, { cause: err });
314
+ }
315
+
316
+ const ns = document.id;
317
+ const tsNamespace = toNamespace(ns);
318
+
319
+ const descs = [];
320
+
321
+ let chunk = '';
322
+
323
+ const definitions = document.defs;
324
+ const keys = Object.keys(definitions).sort(sortDefinition);
325
+
326
+ for (const key of keys) {
327
+ const def = definitions[key];
328
+ const type = def.type;
329
+
330
+ const nsid = `${ns}${key !== 'main' ? `#${key}` : ''}`;
331
+ const typeName = key[0].toUpperCase() + key.slice(1);
332
+
333
+ if (type === 'string') {
334
+ const { value, descriptions } = resolveType(nsid, def);
335
+
336
+ chunk += writeJsdoc(descriptions);
337
+ chunk += `type ${typeName} = ${value};`;
338
+ } else if (type === 'token') {
339
+ chunk += `type ${typeName} = '${nsid}';`;
340
+ } else if (type === 'object') {
341
+ const required = def.required;
342
+ const nullable = def.nullable;
343
+ const properties = def.properties;
344
+
345
+ const propKeys = Object.keys(properties).sort((a, b) => {
346
+ const aIsOptional = !required || !required.includes(a);
347
+ const bIsOptional = !required || !required.includes(b);
348
+
349
+ if (aIsOptional === bIsOptional) {
350
+ return sortName(a, b);
351
+ }
352
+
353
+ return +aIsOptional - +bIsOptional;
354
+ });
355
+
356
+ const descs = [];
357
+
358
+ if (def.description) {
359
+ descs.push(def.description);
360
+
361
+ if (def.description.toLowerCase().startsWith('deprecated')) {
362
+ descs.push(`@deprecated`);
363
+ }
364
+ }
365
+
366
+ chunk += writeJsdoc(descs);
367
+ chunk += `interface ${typeName} {`;
368
+ chunk += `[Brand.Type]?: '${nsid}';`;
369
+
370
+ for (const prop of propKeys) {
371
+ const isOptional = !required || !required.includes(prop);
372
+ const isNullable = nullable !== undefined && nullable.includes(prop);
373
+ const { value, descriptions } = resolveType(`${nsid}/${prop}`, properties[prop]);
374
+
375
+ chunk += writeJsdoc(descriptions);
376
+ chunk += `${prop}${isOptional ? `?` : ``}:${value}${isNullable ? `| null` : ``};`;
377
+ }
378
+
379
+ chunk += '}';
380
+ } else if (type === 'array') {
381
+ const { value, descriptions } = resolveType(nsid, def.items);
382
+ const descs = [];
383
+
384
+ if (def.maxLength !== undefined) {
385
+ descs.push(`Maximum array length: ${def.maxLength}`);
386
+ }
387
+
388
+ if (def.minLength !== undefined) {
389
+ descs.push(`Minimum array length: ${def.minLength}`);
390
+ }
391
+
392
+ chunk += writeJsdoc(descs.concat(descriptions));
393
+ chunk += `type ${typeName} = (${value})[];`;
394
+ } else if (type === 'record') {
395
+ const obj = def.record;
396
+
397
+ const required = obj.required;
398
+ const nullable = obj.nullable;
399
+ const properties = obj.properties;
400
+
401
+ const propKeys = Object.keys(properties).sort((a, b) => {
402
+ const aIsOptional = !required || !required.includes(a);
403
+ const bIsOptional = !required || !required.includes(b);
404
+
405
+ if (aIsOptional === bIsOptional) {
406
+ return sortName(a, b);
407
+ }
408
+
409
+ return +aIsOptional - +bIsOptional;
410
+ });
411
+
412
+ const descs = [];
413
+
414
+ if (def.description) {
415
+ descs.push(def.description);
416
+
417
+ if (def.description.toLowerCase().startsWith('deprecated')) {
418
+ descs.push(`@deprecated`);
419
+ }
420
+ }
421
+
422
+ chunk += writeJsdoc(descs);
423
+ chunk += `interface Record {`;
424
+ chunk += `$type: '${nsid}';`;
425
+
426
+ for (const prop of propKeys) {
427
+ const isOptional = !required || !required.includes(prop);
428
+ const isNullable = nullable !== undefined && nullable.includes(prop);
429
+ const { value, descriptions } = resolveType(`${nsid}/${prop}`, properties[prop]);
430
+
431
+ chunk += writeJsdoc(descriptions);
432
+ chunk += `${prop}${isOptional ? `?` : ``}:${value}${isNullable ? `| null` : ``};`;
433
+ }
434
+
435
+ chunk += '}';
436
+
437
+ records += `\n'${nsid}': ${tsNamespace}.Record;`;
438
+ } else if (type === 'query' || type === 'procedure') {
439
+ let parameters = def.parameters;
440
+
441
+ const input = type === 'procedure' ? def.input : undefined;
442
+ const output = def.output;
443
+ const errors = def.errors;
444
+
445
+ if (def.description) {
446
+ descs.push(def.description);
447
+
448
+ if (def.description.toLowerCase().startsWith('deprecated')) {
449
+ descs.push(`@deprecated`);
450
+ }
451
+ }
452
+
453
+ if (parameters) {
454
+ if (Object.values(parameters.properties).length === 0) {
455
+ parameters = undefined;
456
+ } else {
457
+ const { value, descriptions } = resolveType(nsid, parameters);
458
+
459
+ chunk += writeJsdoc(descriptions);
460
+ chunk += `interface Params ${value}`;
461
+ }
462
+ } else {
463
+ chunk += `interface Params {}`;
464
+ }
465
+
466
+ if (input) {
467
+ if (input.encoding === 'application/json') {
468
+ const { value, descriptions } = resolveType(nsid, input.schema!);
469
+
470
+ chunk += writeJsdoc(descriptions);
471
+
472
+ if (input.schema!.type === 'object') {
473
+ chunk += `interface Input ${value}`;
474
+ } else {
475
+ chunk += `type Input = ${value};`;
476
+ }
477
+ } else {
478
+ chunk += `type Input = Blob | ArrayBufferView;`;
479
+ }
480
+ } else {
481
+ chunk += `type Input = undefined;`;
482
+ }
483
+
484
+ if (output) {
485
+ if (output.encoding === 'application/json') {
486
+ const { value, descriptions } = resolveType(nsid, output.schema!);
487
+
488
+ chunk += writeJsdoc(descriptions);
489
+
490
+ if (output.schema!.type === 'object') {
491
+ chunk += `interface Output ${value}`;
492
+ } else {
493
+ chunk += `type Output = ${value};`;
494
+ }
495
+ } else {
496
+ chunk += `type Output = Uint8Array;`;
497
+ }
498
+ } else {
499
+ chunk += `type Output = undefined;`;
500
+ }
501
+
502
+ if (errors) {
503
+ chunk += `interface Errors {`;
504
+
505
+ for (const error of errors) {
506
+ chunk += `${error.name}: {};`;
507
+ }
508
+
509
+ chunk += '}';
510
+ }
511
+
512
+ {
513
+ let rc = `'${ns}':{\n`;
514
+
515
+ if (parameters) {
516
+ rc += `params: ${tsNamespace}.Params;`;
517
+ }
518
+ if (input) {
519
+ rc += `input: ${tsNamespace}.Input;`;
520
+ }
521
+ if (output) {
522
+ rc += `output: ${tsNamespace}.Output;`;
523
+ }
524
+
525
+ rc += '};';
526
+
527
+ if (type === 'query') {
528
+ queries += rc;
529
+ } else if (type === 'procedure') {
530
+ procedures += rc;
531
+ }
532
+ }
533
+ } else if (type === 'blob') {
534
+ const { value, descriptions } = resolveType(nsid, def);
535
+
536
+ chunk += writeJsdoc(descriptions);
537
+ chunk += `type ${typeName} = ${value};`;
538
+ } else if (type === 'bytes') {
539
+ const { value, descriptions } = resolveType(nsid, def);
540
+
541
+ chunk += writeJsdoc(descriptions);
542
+ chunk += `type ${typeName} = ${value};`;
543
+ } else {
544
+ console.log(`${nsid}: unhandled type ${type}`);
545
+ }
546
+ }
547
+
548
+ code += writeJsdoc(descs);
549
+
550
+ if (main) {
551
+ code += `export declare namespace ${tsNamespace} {`;
552
+ } else {
553
+ code += `namespace ${tsNamespace} {`;
554
+ }
555
+
556
+ code += chunk;
557
+ code += `}\n\n`;
558
+ }
559
+
560
+ if (main) {
561
+ code += `export declare interface Records {${records}}\n\n`;
562
+ code += `export declare interface Queries {${queries}}\n\n`;
563
+ code += `export declare interface Procedures {${procedures}}\n\n`;
564
+ } else {
565
+ code += `interface Records {${records}}\n\n`;
566
+ code += `interface Queries {${queries}}\n\n`;
567
+ code += `interface Procedures {${procedures}}\n\n`;
568
+ code += '}';
569
+ }
570
+
571
+ return code;
572
+ };
package/src/index.ts ADDED
@@ -0,0 +1,152 @@
1
+ import { writeFile } from 'node:fs/promises';
2
+ import { basename } from 'node:path';
3
+
4
+ import * as v from '@badrap/valita';
5
+ import { Builtins, Command, Option, Program } from '@externdefs/collider';
6
+ import pc from 'picocolors';
7
+ import prettier from 'prettier';
8
+
9
+ import { generateDefinitions } from './generator.js';
10
+
11
+ const program = new Program({ binaryName: 'lex-cli' });
12
+
13
+ program.register(Builtins.HelpCommand);
14
+
15
+ program.register(
16
+ class GenerateMainLexicons extends Command {
17
+ static override paths = [['generate-main']];
18
+
19
+ static override usage = Command.Usage({
20
+ description: `Generates the main type definition file`,
21
+ });
22
+
23
+ output = Option.String(['-o', '--output'], {
24
+ required: false,
25
+ description: 'Where to save the resulting type definition file, defaults to stdout if not passed',
26
+ validator: v
27
+ .string()
28
+ .assert((v) => basename(v).endsWith('.ts'), `expected output file to end with .ts extension`),
29
+ });
30
+
31
+ desc = Option.String(['--description'], {
32
+ required: false,
33
+ description: 'Module description',
34
+ });
35
+
36
+ banner = Option.String(['--banner'], {
37
+ required: false,
38
+ description: 'Insert an arbitrary string at the beginning of the module',
39
+ });
40
+
41
+ files = Option.Rest({
42
+ required: 1,
43
+ name: 'files',
44
+ });
45
+
46
+ async execute(): Promise<number | void> {
47
+ let code: string;
48
+
49
+ try {
50
+ code = await generateDefinitions({
51
+ files: this.files,
52
+ main: true,
53
+ banner: this.banner,
54
+ description: this.desc,
55
+ });
56
+ } catch (err) {
57
+ if (err instanceof Error) {
58
+ console.error(pc.bold(`${pc.red(`error:`)} ${err.message}`));
59
+
60
+ if (err.cause instanceof Error) {
61
+ console.error(` ${pc.gray(`caused by:`)} ${err.cause.message}`);
62
+ }
63
+ } else {
64
+ console.error(pc.bold(pc.red(`unknown error occured:`)));
65
+ console.error(err);
66
+ }
67
+
68
+ return 1;
69
+ }
70
+
71
+ const config = await prettier.resolveConfig(this.output || process.cwd(), { editorconfig: true });
72
+ const formatted = await prettier.format(code, { ...config, parser: 'typescript' });
73
+
74
+ if (this.output) {
75
+ await writeFile(this.output, formatted);
76
+ } else {
77
+ console.log(formatted);
78
+ }
79
+ }
80
+ },
81
+ );
82
+
83
+ program.register(
84
+ class GenerateLexicons extends Command {
85
+ static override paths = [['generate']];
86
+
87
+ static override usage = Command.Usage({
88
+ description: `Generates a type definition file`,
89
+ });
90
+
91
+ output = Option.String(['-o', '--output'], {
92
+ required: false,
93
+ description: 'Where to save the resulting type definition file, defaults to stdout if not passed',
94
+ validator: v
95
+ .string()
96
+ .assert((v) => basename(v).endsWith('.ts'), `expected output file to end with .ts extension`),
97
+ });
98
+
99
+ desc = Option.String(['--description'], {
100
+ required: false,
101
+ description: 'Module description',
102
+ });
103
+
104
+ banner = Option.String(['--banner'], {
105
+ required: false,
106
+ description: 'Insert an arbitrary string at the beginning of the module',
107
+ });
108
+
109
+ files = Option.Rest({
110
+ required: 1,
111
+ name: 'files',
112
+ });
113
+
114
+ async execute(): Promise<number | void> {
115
+ let code: string;
116
+
117
+ try {
118
+ code = await generateDefinitions({
119
+ files: this.files,
120
+ main: false,
121
+ banner: this.banner,
122
+ description: this.desc,
123
+ });
124
+ } catch (err) {
125
+ if (err instanceof Error) {
126
+ console.error(pc.bold(`${pc.red(`error: `)}: ${err.message}`));
127
+
128
+ if (err.cause instanceof Error) {
129
+ console.error(` ${pc.gray(`caused by:`)}: ${err.cause.message}`);
130
+ }
131
+ } else {
132
+ console.error(pc.bold(pc.red(`unknown error occured:`)));
133
+ console.error(err);
134
+ }
135
+
136
+ return 1;
137
+ }
138
+
139
+ const config = await prettier.resolveConfig(this.output || process.cwd(), { editorconfig: true });
140
+ const formatted = await prettier.format(code, { ...config, parser: 'typescript' });
141
+
142
+ if (this.output) {
143
+ await writeFile(this.output, formatted);
144
+ } else {
145
+ console.log(formatted);
146
+ }
147
+ }
148
+ },
149
+ );
150
+
151
+ const exitCode = await program.run(process.argv.slice(2));
152
+ process.exitCode = exitCode;