@openfairygui/cli 0.1.0 → 0.2.0-alpha.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/dist/cli.mjs +3222 -2988
- package/package.json +4 -1
- package/src/cli.ts +216 -108
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openfairygui/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-alpha.0",
|
|
4
4
|
"description": "FairyGUI Headless Authoring SDK — command-line interface.",
|
|
5
5
|
"author": "OpenFairyGUI Contributors",
|
|
6
6
|
"license": "MIT",
|
|
@@ -39,6 +39,9 @@
|
|
|
39
39
|
"@openfairygui/core": "workspace:*",
|
|
40
40
|
"@openfairygui/functions": "workspace:*"
|
|
41
41
|
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@openfairygui/backend": "workspace:*"
|
|
44
|
+
},
|
|
42
45
|
"optionalDependencies": {
|
|
43
46
|
"sharp": ">=0.33.0"
|
|
44
47
|
}
|
package/src/cli.ts
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
|
+
import { BackendRuntime } from '@openfairygui/backend';
|
|
1
2
|
import {
|
|
2
3
|
NodeIO,
|
|
3
4
|
ProjectType,
|
|
5
|
+
} from '@openfairygui/core';
|
|
6
|
+
import {
|
|
7
|
+
inspect,
|
|
8
|
+
publish,
|
|
9
|
+
resolvePublishOptions,
|
|
10
|
+
restore,
|
|
11
|
+
type InspectReport,
|
|
12
|
+
type PublishOptions,
|
|
13
|
+
type RestoreFileSystem,
|
|
4
14
|
type RestoreImageCropInput,
|
|
5
15
|
type RestoreImageCropper,
|
|
6
16
|
type RestoreImageExtractInput,
|
|
7
17
|
type RestoreImageExtractor,
|
|
8
|
-
} from '@openfairygui/
|
|
9
|
-
import { inspect, publish, resolvePublishOptions, type InspectReport, type PublishOptions } from '@openfairygui/functions';
|
|
18
|
+
} from '@openfairygui/functions';
|
|
10
19
|
import fs from 'node:fs/promises';
|
|
11
20
|
import path from 'node:path';
|
|
12
21
|
import { parseArgs } from 'node:util';
|
|
13
|
-
|
|
22
|
+
|
|
14
23
|
const HELP = `
|
|
15
24
|
ofgui — FairyGUI Headless Authoring CLI
|
|
16
25
|
|
|
@@ -21,6 +30,7 @@ Commands:
|
|
|
21
30
|
inspect <project-dir> Show project contents report
|
|
22
31
|
publish <project-dir> --output <dir> [options] Publish project to binary outputs and configured generated code
|
|
23
32
|
restore <release-dir> --output <dir> [options] Restore a FairyGUI project from published binaries
|
|
33
|
+
backend-capabilities <project-dir> Open a backend session, print runtime capabilities, then close it
|
|
24
34
|
|
|
25
35
|
Publish options:
|
|
26
36
|
--output, -o <dir> Output directory (required)
|
|
@@ -36,69 +46,72 @@ Restore options:
|
|
|
36
46
|
--project-type <name|id> Override restored project type; default is unity
|
|
37
47
|
|
|
38
48
|
Options:
|
|
39
|
-
--help, -h Show this help
|
|
40
|
-
--version, -v Show version
|
|
41
|
-
|
|
42
|
-
Input can be a .fairy file or a project root directory (auto-discovers .fairy file).
|
|
43
|
-
File extension and binary format are read from project settings.
|
|
44
|
-
`;
|
|
45
|
-
|
|
46
|
-
/** Resolve input to a .fairy file path. Accepts a directory or a .fairy file. */
|
|
47
|
-
async function resolveFairyPath(input: string): Promise<string> {
|
|
48
|
-
const resolved = path.resolve(input);
|
|
49
|
-
const stat = await fs.stat(resolved);
|
|
50
|
-
|
|
51
|
-
if (stat.isFile() && resolved.endsWith('.fairy')) {
|
|
52
|
-
return resolved;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (stat.isDirectory()) {
|
|
56
|
-
// Scan for *.fairy in the directory
|
|
57
|
-
const entries = await fs.readdir(resolved);
|
|
58
|
-
const fairyFiles = entries.filter((e) => e.endsWith('.fairy'));
|
|
59
|
-
if (fairyFiles.length === 1) {
|
|
60
|
-
return path.join(resolved, fairyFiles[0]);
|
|
61
|
-
}
|
|
62
|
-
if (fairyFiles.length > 1) {
|
|
63
|
-
throw new Error(`Multiple .fairy files found in ${resolved}: ${fairyFiles.join(', ')}. Please specify one.`);
|
|
64
|
-
}
|
|
65
|
-
throw new Error(`No .fairy file found in ${resolved}`);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
throw new Error(`Input is not a .fairy file or directory: ${resolved}`);
|
|
69
|
-
}
|
|
70
|
-
|
|
49
|
+
--help, -h Show this help
|
|
50
|
+
--version, -v Show version
|
|
51
|
+
|
|
52
|
+
Input can be a .fairy file or a project root directory (auto-discovers .fairy file).
|
|
53
|
+
File extension and binary format are read from project settings.
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
/** Resolve input to a .fairy file path. Accepts a directory or a .fairy file. */
|
|
57
|
+
async function resolveFairyPath(input: string): Promise<string> {
|
|
58
|
+
const resolved = path.resolve(input);
|
|
59
|
+
const stat = await fs.stat(resolved);
|
|
60
|
+
|
|
61
|
+
if (stat.isFile() && resolved.endsWith('.fairy')) {
|
|
62
|
+
return resolved;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (stat.isDirectory()) {
|
|
66
|
+
// Scan for *.fairy in the directory
|
|
67
|
+
const entries = await fs.readdir(resolved);
|
|
68
|
+
const fairyFiles = entries.filter((e) => e.endsWith('.fairy'));
|
|
69
|
+
if (fairyFiles.length === 1) {
|
|
70
|
+
return path.join(resolved, fairyFiles[0]);
|
|
71
|
+
}
|
|
72
|
+
if (fairyFiles.length > 1) {
|
|
73
|
+
throw new Error(`Multiple .fairy files found in ${resolved}: ${fairyFiles.join(', ')}. Please specify one.`);
|
|
74
|
+
}
|
|
75
|
+
throw new Error(`No .fairy file found in ${resolved}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
throw new Error(`Input is not a .fairy file or directory: ${resolved}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
71
81
|
async function main(): Promise<void> {
|
|
72
|
-
const args = process.argv.slice(2);
|
|
73
|
-
|
|
74
|
-
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
75
|
-
console.log(HELP);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (args.includes('--version') || args.includes('-v')) {
|
|
80
|
-
console.log('0.1.0');
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const command = args[0];
|
|
85
|
-
const rest = args.slice(1);
|
|
86
|
-
|
|
87
|
-
switch (command) {
|
|
88
|
-
case 'inspect':
|
|
89
|
-
await cmdInspect(rest);
|
|
90
|
-
break;
|
|
82
|
+
const args = process.argv.slice(2);
|
|
83
|
+
|
|
84
|
+
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
85
|
+
console.log(HELP);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
90
|
+
console.log('0.1.0');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const command = args[0];
|
|
95
|
+
const rest = args.slice(1);
|
|
96
|
+
|
|
97
|
+
switch (command) {
|
|
98
|
+
case 'inspect':
|
|
99
|
+
await cmdInspect(rest);
|
|
100
|
+
break;
|
|
91
101
|
case 'publish':
|
|
92
102
|
await cmdPublish(rest);
|
|
93
103
|
break;
|
|
94
104
|
case 'restore':
|
|
95
105
|
await cmdRestore(rest);
|
|
96
106
|
break;
|
|
107
|
+
case 'backend-capabilities':
|
|
108
|
+
await cmdBackendCapabilities(rest);
|
|
109
|
+
break;
|
|
97
110
|
default:
|
|
98
|
-
console.error(`Unknown command: ${command}\n`);
|
|
99
|
-
console.log(HELP);
|
|
100
|
-
process.exit(1);
|
|
101
|
-
}
|
|
111
|
+
console.error(`Unknown command: ${command}\n`);
|
|
112
|
+
console.log(HELP);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
102
115
|
}
|
|
103
116
|
|
|
104
117
|
interface RestoreImageProcessors {
|
|
@@ -106,6 +119,63 @@ interface RestoreImageProcessors {
|
|
|
106
119
|
extractImage: RestoreImageExtractor;
|
|
107
120
|
}
|
|
108
121
|
|
|
122
|
+
function createNodeRestoreFs(): RestoreFileSystem {
|
|
123
|
+
return {
|
|
124
|
+
async readFile(filePath: string): Promise<string> {
|
|
125
|
+
return fs.readFile(filePath, 'utf-8');
|
|
126
|
+
},
|
|
127
|
+
async readFileRaw(filePath: string): Promise<Uint8Array> {
|
|
128
|
+
const buf = await fs.readFile(filePath);
|
|
129
|
+
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
130
|
+
},
|
|
131
|
+
async writeFile(filePath: string, content: string): Promise<void> {
|
|
132
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
133
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
134
|
+
},
|
|
135
|
+
async writeFileRaw(filePath: string, data: Uint8Array): Promise<void> {
|
|
136
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
137
|
+
await fs.writeFile(filePath, data);
|
|
138
|
+
},
|
|
139
|
+
async mkdir(dirPath: string): Promise<void> {
|
|
140
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
141
|
+
},
|
|
142
|
+
async readdir(dirPath: string): Promise<string[]> {
|
|
143
|
+
return fs.readdir(dirPath);
|
|
144
|
+
},
|
|
145
|
+
async exists(filePath: string): Promise<boolean> {
|
|
146
|
+
try {
|
|
147
|
+
await fs.access(filePath);
|
|
148
|
+
return true;
|
|
149
|
+
} catch {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
async isFile(filePath: string): Promise<boolean> {
|
|
154
|
+
try {
|
|
155
|
+
return (await fs.stat(filePath)).isFile();
|
|
156
|
+
} catch {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
async resolvePath(filePath: string): Promise<string> {
|
|
161
|
+
try {
|
|
162
|
+
return await fs.realpath(filePath);
|
|
163
|
+
} catch {
|
|
164
|
+
return path.resolve(filePath);
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
async rm(targetPath: string, options?: { recursive?: boolean; force?: boolean }): Promise<void> {
|
|
168
|
+
await fs.rm(targetPath, { recursive: options?.recursive ?? false, force: options?.force ?? false });
|
|
169
|
+
},
|
|
170
|
+
join(...paths: string[]): string {
|
|
171
|
+
return path.join(...paths);
|
|
172
|
+
},
|
|
173
|
+
dirname(filePath: string): string {
|
|
174
|
+
return path.dirname(filePath);
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
109
179
|
async function createRestoreImageProcessors(): Promise<RestoreImageProcessors> {
|
|
110
180
|
let sharp: any;
|
|
111
181
|
try {
|
|
@@ -140,7 +210,7 @@ async function createRestoreImageProcessors(): Promise<RestoreImageProcessors> {
|
|
|
140
210
|
|| input.offsetY + info.height > input.expectedHeight
|
|
141
211
|
) {
|
|
142
212
|
throw new Error(
|
|
143
|
-
`restore: Cropped image does not fit original canvas for ${
|
|
213
|
+
`restore: Cropped image does not fit original canvas for ${targetPath}: `
|
|
144
214
|
+ `crop ${info.width}x${info.height} at ${input.offsetX},${input.offsetY}, `
|
|
145
215
|
+ `canvas ${input.expectedWidth}x${input.expectedHeight}`,
|
|
146
216
|
);
|
|
@@ -190,41 +260,41 @@ async function createRestoreImageProcessors(): Promise<RestoreImageProcessors> {
|
|
|
190
260
|
},
|
|
191
261
|
};
|
|
192
262
|
}
|
|
193
|
-
|
|
263
|
+
|
|
194
264
|
async function cmdInspect(args: string[]): Promise<void> {
|
|
195
265
|
if (args.length === 0) {
|
|
196
266
|
console.error('Usage: ofgui inspect <project-dir>');
|
|
197
267
|
process.exit(1);
|
|
198
268
|
}
|
|
199
|
-
|
|
200
|
-
const fairyPath = await resolveFairyPath(args[0]);
|
|
201
|
-
console.log(`Project: ${fairyPath}\n`);
|
|
202
|
-
|
|
203
|
-
const io = new NodeIO();
|
|
204
|
-
const doc = await io.readProject(fairyPath);
|
|
205
|
-
const report = inspect(doc);
|
|
206
|
-
|
|
207
|
-
printReport(report);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function printReport(report: InspectReport): void {
|
|
211
|
-
console.log(`ID: ${report.projectId}`);
|
|
212
|
-
console.log(`Type: ${report.projectType}, Version: ${report.version}`);
|
|
213
|
-
console.log(`\nPackages: ${report.totals.packages}`);
|
|
214
|
-
console.log(` Images: ${report.totals.images}`);
|
|
215
|
-
console.log(` Sounds: ${report.totals.sounds}`);
|
|
216
|
-
console.log(` Fonts: ${report.totals.fonts}`);
|
|
217
|
-
console.log(` MovieClips: ${report.totals.movieClips}`);
|
|
218
|
-
console.log(` Components: ${report.totals.components}`);
|
|
219
|
-
console.log(` DisplayObjs: ${report.totals.displayObjects}`);
|
|
220
|
-
console.log(` Gears: ${report.totals.gears}`);
|
|
221
|
-
console.log(` Controllers: ${report.totals.controllers}`);
|
|
222
|
-
console.log(` Transitions: ${report.totals.transitions}`);
|
|
223
|
-
|
|
224
|
-
console.log('\nPackage details:');
|
|
225
|
-
for (const pkg of report.packages) {
|
|
226
|
-
const res = pkg.resources;
|
|
227
|
-
console.log(` ${pkg.name} (${pkg.id}): ${res.images.count} img, ${res.sounds.count} snd, ${res.fonts.count} font, ${res.components.count} comp`);
|
|
269
|
+
|
|
270
|
+
const fairyPath = await resolveFairyPath(args[0]);
|
|
271
|
+
console.log(`Project: ${fairyPath}\n`);
|
|
272
|
+
|
|
273
|
+
const io = new NodeIO();
|
|
274
|
+
const doc = await io.readProject(fairyPath);
|
|
275
|
+
const report = inspect(doc);
|
|
276
|
+
|
|
277
|
+
printReport(report);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function printReport(report: InspectReport): void {
|
|
281
|
+
console.log(`ID: ${report.projectId}`);
|
|
282
|
+
console.log(`Type: ${report.projectType}, Version: ${report.version}`);
|
|
283
|
+
console.log(`\nPackages: ${report.totals.packages}`);
|
|
284
|
+
console.log(` Images: ${report.totals.images}`);
|
|
285
|
+
console.log(` Sounds: ${report.totals.sounds}`);
|
|
286
|
+
console.log(` Fonts: ${report.totals.fonts}`);
|
|
287
|
+
console.log(` MovieClips: ${report.totals.movieClips}`);
|
|
288
|
+
console.log(` Components: ${report.totals.components}`);
|
|
289
|
+
console.log(` DisplayObjs: ${report.totals.displayObjects}`);
|
|
290
|
+
console.log(` Gears: ${report.totals.gears}`);
|
|
291
|
+
console.log(` Controllers: ${report.totals.controllers}`);
|
|
292
|
+
console.log(` Transitions: ${report.totals.transitions}`);
|
|
293
|
+
|
|
294
|
+
console.log('\nPackage details:');
|
|
295
|
+
for (const pkg of report.packages) {
|
|
296
|
+
const res = pkg.resources;
|
|
297
|
+
console.log(` ${pkg.name} (${pkg.id}): ${res.images.count} img, ${res.sounds.count} snd, ${res.fonts.count} font, ${res.components.count} comp`);
|
|
228
298
|
}
|
|
229
299
|
}
|
|
230
300
|
|
|
@@ -282,8 +352,10 @@ async function cmdRestore(args: string[]): Promise<void> {
|
|
|
282
352
|
const { cropImage, extractImage } = await createRestoreImageProcessors();
|
|
283
353
|
|
|
284
354
|
console.log(`Restoring published FairyGUI project: ${releaseDir}`);
|
|
285
|
-
const
|
|
286
|
-
|
|
355
|
+
const result = await restore({
|
|
356
|
+
inputDir: releaseDir,
|
|
357
|
+
output: outputDir,
|
|
358
|
+
fs: createNodeRestoreFs(),
|
|
287
359
|
packages: pkgFilter,
|
|
288
360
|
force: values.force,
|
|
289
361
|
projectType,
|
|
@@ -311,16 +383,16 @@ async function cmdPublish(args: string[]): Promise<void> {
|
|
|
311
383
|
},
|
|
312
384
|
allowPositionals: true,
|
|
313
385
|
});
|
|
314
|
-
|
|
386
|
+
|
|
315
387
|
if (positionals.length === 0 || !values.output) {
|
|
316
388
|
console.error('Usage: ofgui publish <project-dir> --output <dir> [--compressed] [--packages a,b,c] [--branch name]');
|
|
317
389
|
process.exit(1);
|
|
318
390
|
}
|
|
319
|
-
|
|
320
|
-
const fairyPath = await resolveFairyPath(positionals[0]);
|
|
321
|
-
const projectDir = path.dirname(fairyPath);
|
|
322
|
-
const outputDir = path.resolve(values.output);
|
|
323
|
-
|
|
391
|
+
|
|
392
|
+
const fairyPath = await resolveFairyPath(positionals[0]);
|
|
393
|
+
const projectDir = path.dirname(fairyPath);
|
|
394
|
+
const outputDir = path.resolve(values.output);
|
|
395
|
+
|
|
324
396
|
console.log(`Reading project: ${fairyPath}`);
|
|
325
397
|
const io = new NodeIO();
|
|
326
398
|
const doc = await io.readProject(fairyPath);
|
|
@@ -353,7 +425,7 @@ async function cmdPublish(args: string[]): Promise<void> {
|
|
|
353
425
|
try {
|
|
354
426
|
const sharp = await import('sharp');
|
|
355
427
|
encoder = sharp.default ?? sharp;
|
|
356
|
-
console.log('Sharp loaded — atlas PNGs will be generated.');
|
|
428
|
+
console.log('Sharp loaded — atlas PNGs will be generated.');
|
|
357
429
|
} catch {
|
|
358
430
|
console.log('Sharp not available — atlas PNGs will NOT be generated (layout only).');
|
|
359
431
|
console.log(' Install sharp to enable: pnpm add sharp');
|
|
@@ -367,7 +439,7 @@ async function cmdPublish(args: string[]): Promise<void> {
|
|
|
367
439
|
async writeFileRaw(filePath: string, data: Uint8Array): Promise<void> {
|
|
368
440
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
369
441
|
await fs.writeFile(filePath, data);
|
|
370
|
-
},
|
|
442
|
+
},
|
|
371
443
|
async mkdir(dirPath: string): Promise<void> {
|
|
372
444
|
await fs.mkdir(dirPath, { recursive: true });
|
|
373
445
|
},
|
|
@@ -381,7 +453,7 @@ async function cmdPublish(args: string[]): Promise<void> {
|
|
|
381
453
|
return path.join(...paths);
|
|
382
454
|
},
|
|
383
455
|
};
|
|
384
|
-
|
|
456
|
+
|
|
385
457
|
await doc.transform(publish({
|
|
386
458
|
output: outputDir,
|
|
387
459
|
compressed: resolved.compressed,
|
|
@@ -393,11 +465,47 @@ async function cmdPublish(args: string[]): Promise<void> {
|
|
|
393
465
|
atlas: atlasConfig,
|
|
394
466
|
branch: values.branch,
|
|
395
467
|
}));
|
|
396
|
-
|
|
397
|
-
console.log(`\nDone! Output: ${outputDir}`);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
468
|
+
|
|
469
|
+
console.log(`\nDone! Output: ${outputDir}`);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
async function cmdBackendCapabilities(args: string[]): Promise<void> {
|
|
473
|
+
if (args.length === 0) {
|
|
474
|
+
console.error('Usage: ofgui backend-capabilities <project-dir>');
|
|
475
|
+
process.exit(1);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const runtime = new BackendRuntime();
|
|
479
|
+
const opened = await runtime.openSession({ projectPath: path.resolve(args[0]) });
|
|
480
|
+
if (!opened.ok) {
|
|
481
|
+
const failure = opened as Extract<typeof opened, { ok: false }>;
|
|
482
|
+
console.error(`backend-capabilities: ${failure.error.message}`);
|
|
483
|
+
process.exit(1);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const capabilities = runtime.getCapabilities();
|
|
487
|
+
if (!capabilities.ok) {
|
|
488
|
+
console.error('backend-capabilities: failed to read capabilities');
|
|
489
|
+
await runtime.closeSession({ sessionId: opened.data.sessionId });
|
|
490
|
+
process.exit(1);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
console.log(`Session: ${opened.data.sessionId}`);
|
|
494
|
+
console.log(`Project: ${opened.data.canonicalProjectPath}`);
|
|
495
|
+
console.log(`Revision: ${opened.data.revision}`);
|
|
496
|
+
console.log(`Runtime owner: ${capabilities.data.runtimeOwner}`);
|
|
497
|
+
console.log(`Transaction owner: ${capabilities.data.transactionKernelOwner}`);
|
|
498
|
+
console.log(`App seam owner: ${capabilities.data.appSeamOwner}`);
|
|
499
|
+
|
|
500
|
+
const closed = await runtime.closeSession({ sessionId: opened.data.sessionId });
|
|
501
|
+
if (!closed.ok) {
|
|
502
|
+
const failure = closed as Extract<typeof closed, { ok: false }>;
|
|
503
|
+
console.error(`backend-capabilities: ${failure.error.message}`);
|
|
504
|
+
process.exit(1);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
main().catch((err) => {
|
|
509
|
+
console.error(err);
|
|
510
|
+
process.exit(1);
|
|
511
|
+
});
|