@ait-co/console-cli 0.1.5 → 0.1.6
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 +671 -115
- package/dist/cli.mjs.map +1 -1
- package/package.json +4 -2
package/dist/cli.mjs
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { defineCommand, runMain } from "citty";
|
|
3
|
-
import { chmod, mkdir, mkdtemp, readFile, rename, rm, unlink, writeFile } from "node:fs/promises";
|
|
4
|
-
import { basename, dirname, join, win32 } from "node:path";
|
|
3
|
+
import { access, chmod, mkdir, mkdtemp, readFile, rename, rm, unlink, writeFile } from "node:fs/promises";
|
|
4
|
+
import { basename, dirname, isAbsolute, join, resolve, win32 } from "node:path";
|
|
5
5
|
import { homedir, tmpdir } from "node:os";
|
|
6
|
+
import { parse } from "yaml";
|
|
7
|
+
import { imageSize } from "image-size";
|
|
6
8
|
import { spawn } from "node:child_process";
|
|
7
9
|
import { constants } from "node:fs";
|
|
8
10
|
//#region src/api/http.ts
|
|
@@ -119,10 +121,25 @@ async function requestConsoleApi(options) {
|
|
|
119
121
|
headers["Content-Type"] = "application/json";
|
|
120
122
|
init.body = JSON.stringify(options.body);
|
|
121
123
|
}
|
|
122
|
-
|
|
124
|
+
return executeAndUnwrap(url, init, options.fetchImpl);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Send a pre-built `RequestInit` against the console API and unwrap the
|
|
128
|
+
* Toss envelope. Use this when the caller needs to build the body itself
|
|
129
|
+
* (multipart uploads, binary requests, anything that can't live under
|
|
130
|
+
* `requestConsoleApi`'s JSON-body assumption). Cookie header composition
|
|
131
|
+
* and any additional headers remain the caller's responsibility.
|
|
132
|
+
*
|
|
133
|
+
* Exists so `uploadMiniAppResource` doesn't have to re-implement the
|
|
134
|
+
* text→JSON→envelope→error branch in `requestConsoleApi`; drift between
|
|
135
|
+
* the two paths has bitten us once (cf. the refactor in the
|
|
136
|
+
* `app register` review).
|
|
137
|
+
*/
|
|
138
|
+
async function executeAndUnwrap(url, init, fetchImpl) {
|
|
139
|
+
const impl = fetchImpl ?? ((input, i) => fetch(input, i));
|
|
123
140
|
let res;
|
|
124
141
|
try {
|
|
125
|
-
res = await
|
|
142
|
+
res = await impl(url, init);
|
|
126
143
|
} catch (err) {
|
|
127
144
|
throw new NetworkError(url.toString(), err);
|
|
128
145
|
}
|
|
@@ -187,6 +204,60 @@ async function fetchReviewStatus(workspaceId, cookies, opts = {}) {
|
|
|
187
204
|
})
|
|
188
205
|
};
|
|
189
206
|
}
|
|
207
|
+
async function createMiniApp(workspaceId, payload, cookies, opts = {}) {
|
|
208
|
+
return normalizeCreateResult(await requestConsoleApi({
|
|
209
|
+
url: `${BASE$2}/workspaces/${workspaceId}/mini-app/review`,
|
|
210
|
+
method: "POST",
|
|
211
|
+
cookies,
|
|
212
|
+
body: payload,
|
|
213
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
214
|
+
}));
|
|
215
|
+
}
|
|
216
|
+
function normalizeCreateResult(raw) {
|
|
217
|
+
if (raw === null || typeof raw !== "object") return {
|
|
218
|
+
miniAppId: void 0,
|
|
219
|
+
reviewState: void 0,
|
|
220
|
+
extra: {}
|
|
221
|
+
};
|
|
222
|
+
const rec = raw;
|
|
223
|
+
const rawId = rec.miniAppId ?? rec.id ?? rec.appId;
|
|
224
|
+
const miniAppId = typeof rawId === "string" || typeof rawId === "number" ? rawId : void 0;
|
|
225
|
+
const rawState = rec.reviewState ?? rec.status;
|
|
226
|
+
return {
|
|
227
|
+
miniAppId,
|
|
228
|
+
reviewState: typeof rawState === "string" ? rawState : void 0,
|
|
229
|
+
extra: rec
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Upload an image to `/resource/:wid/upload?validWidth=W&validHeight=H`
|
|
234
|
+
* and return the CDN URL the server hands back. The endpoint is a
|
|
235
|
+
* multipart/form-data POST; we build a FormData with a single `resource`
|
|
236
|
+
* field because that matches the bundle analysis for the console's
|
|
237
|
+
* uploader, which pairs a `fileName` string field with a `resource`
|
|
238
|
+
* Blob (see VALIDATION-RULES.md → iconUri). Dog-food #23 may reveal that
|
|
239
|
+
* the field name is actually `file` — if so, swap it in one place here.
|
|
240
|
+
*/
|
|
241
|
+
async function uploadMiniAppResource(params, opts = {}) {
|
|
242
|
+
const url = new URL(`${BASE$2}/resource/${params.workspaceId}/upload`);
|
|
243
|
+
url.searchParams.set("validWidth", String(params.validWidth));
|
|
244
|
+
url.searchParams.set("validHeight", String(params.validHeight));
|
|
245
|
+
const form = new FormData();
|
|
246
|
+
const view = new Uint8Array(params.file.buffer.buffer, params.file.buffer.byteOffset, params.file.buffer.byteLength);
|
|
247
|
+
const blob = new Blob([view], { type: params.file.contentType });
|
|
248
|
+
form.append("resource", blob, params.file.fileName);
|
|
249
|
+
form.append("fileName", params.file.fileName);
|
|
250
|
+
const cookieHeader = cookieHeaderFor(url, params.cookies);
|
|
251
|
+
const headers = { Accept: "application/json, text/plain, */*" };
|
|
252
|
+
if (cookieHeader) headers.Cookie = cookieHeader;
|
|
253
|
+
const imageUrl = await executeAndUnwrap(url, {
|
|
254
|
+
method: "POST",
|
|
255
|
+
headers,
|
|
256
|
+
body: form
|
|
257
|
+
}, opts.fetchImpl);
|
|
258
|
+
if (typeof imageUrl !== "string") throw new MalformedResponseError(url.toString(), 200, `expected string imageUrl, got ${typeof imageUrl}`);
|
|
259
|
+
return imageUrl;
|
|
260
|
+
}
|
|
190
261
|
//#endregion
|
|
191
262
|
//#region src/exit.ts
|
|
192
263
|
const ExitCode = {
|
|
@@ -376,14 +447,48 @@ function emitNetworkError(json, message) {
|
|
|
376
447
|
});
|
|
377
448
|
else process.stderr.write(`Network error reaching the console API: ${message}.\n`);
|
|
378
449
|
}
|
|
379
|
-
function emitApiError(json, message) {
|
|
450
|
+
function emitApiError(json, message, details) {
|
|
380
451
|
if (json) emitJson({
|
|
381
452
|
ok: false,
|
|
382
453
|
reason: "api-error",
|
|
454
|
+
...details?.status !== void 0 ? { status: details.status } : {},
|
|
455
|
+
...details?.errorCode !== void 0 ? { errorCode: details.errorCode } : {},
|
|
383
456
|
message
|
|
384
457
|
});
|
|
385
458
|
else process.stderr.write(`Unexpected error: ${message}\n`);
|
|
386
459
|
}
|
|
460
|
+
/**
|
|
461
|
+
* Shared auth/network/api dispatch. Every session-scoped command's
|
|
462
|
+
* `catch (err)` block boils down to the same sequence: TossApiError
|
|
463
|
+
* (auth → exit 10, otherwise → exit 17 with status + errorCode),
|
|
464
|
+
* NetworkError (exit 11), fallback (exit 17 with just a message).
|
|
465
|
+
* Exists so we get a single source of truth for the api-error JSON
|
|
466
|
+
* shape — previously each command duplicated the if/else ladder and
|
|
467
|
+
* `register` diverged (it exposed `status`/`errorCode` that the others
|
|
468
|
+
* didn't) until this extraction lined them up.
|
|
469
|
+
*
|
|
470
|
+
* Returns `Promise<void>` but never returns at runtime: every branch
|
|
471
|
+
* awaits `exitAfterFlush` which calls `process.exit`.
|
|
472
|
+
*/
|
|
473
|
+
async function emitFailureFromError(json, err) {
|
|
474
|
+
if (err instanceof TossApiError && err.isAuthError) {
|
|
475
|
+
emitNotAuthenticated(json, "session-expired");
|
|
476
|
+
return exitAfterFlush(ExitCode.NotAuthenticated);
|
|
477
|
+
}
|
|
478
|
+
if (err instanceof TossApiError) {
|
|
479
|
+
emitApiError(json, err.message, {
|
|
480
|
+
status: err.status,
|
|
481
|
+
errorCode: err.errorCode
|
|
482
|
+
});
|
|
483
|
+
return exitAfterFlush(ExitCode.ApiError);
|
|
484
|
+
}
|
|
485
|
+
if (err instanceof NetworkError) {
|
|
486
|
+
emitNetworkError(json, err.message);
|
|
487
|
+
return exitAfterFlush(ExitCode.NetworkError);
|
|
488
|
+
}
|
|
489
|
+
emitApiError(json, err.message);
|
|
490
|
+
return exitAfterFlush(ExitCode.ApiError);
|
|
491
|
+
}
|
|
387
492
|
function parsePositiveInt(raw) {
|
|
388
493
|
if (!/^[1-9]\d*$/.test(raw)) return null;
|
|
389
494
|
const n = Number.parseInt(raw, 10);
|
|
@@ -441,6 +546,469 @@ async function resolveWorkspaceContext(args) {
|
|
|
441
546
|
};
|
|
442
547
|
}
|
|
443
548
|
//#endregion
|
|
549
|
+
//#region src/config/app-manifest.ts
|
|
550
|
+
var ManifestError = class extends Error {
|
|
551
|
+
kind;
|
|
552
|
+
field;
|
|
553
|
+
constructor(kind, message, field) {
|
|
554
|
+
super(message);
|
|
555
|
+
this.name = "ManifestError";
|
|
556
|
+
this.kind = kind;
|
|
557
|
+
this.field = field;
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
const DEFAULT_NAMES = ["aitcc.app.yaml", "aitcc.app.json"];
|
|
561
|
+
async function fileExists(path) {
|
|
562
|
+
try {
|
|
563
|
+
await access(path);
|
|
564
|
+
return true;
|
|
565
|
+
} catch {
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Resolve the manifest file path. When `explicit` is provided, we use it
|
|
571
|
+
* verbatim (resolved against `cwd`) and require it to exist. Otherwise we
|
|
572
|
+
* auto-detect `aitcc.app.yaml` then `aitcc.app.json` under `cwd`.
|
|
573
|
+
*/
|
|
574
|
+
async function resolveManifestPath(explicit, cwd) {
|
|
575
|
+
if (explicit) {
|
|
576
|
+
const abs = isAbsolute(explicit) ? explicit : resolve(cwd, explicit);
|
|
577
|
+
if (!await fileExists(abs)) throw new ManifestError("invalid-config", `manifest file not found at ${abs}`);
|
|
578
|
+
return abs;
|
|
579
|
+
}
|
|
580
|
+
for (const name of DEFAULT_NAMES) {
|
|
581
|
+
const abs = resolve(cwd, name);
|
|
582
|
+
if (await fileExists(abs)) return abs;
|
|
583
|
+
}
|
|
584
|
+
throw new ManifestError("invalid-config", `no app manifest found (looked for ${DEFAULT_NAMES.join(", ")} in ${cwd})`);
|
|
585
|
+
}
|
|
586
|
+
async function loadAppManifest(path) {
|
|
587
|
+
return validateManifest(parseManifestFile(path, await readFile(path, "utf8")), dirname(path));
|
|
588
|
+
}
|
|
589
|
+
function parseManifestFile(path, raw) {
|
|
590
|
+
const isJson = path.toLowerCase().endsWith(".json");
|
|
591
|
+
try {
|
|
592
|
+
const out = isJson ? JSON.parse(raw) : parse(raw);
|
|
593
|
+
if (out === null || typeof out !== "object" || Array.isArray(out)) throw new ManifestError("invalid-config", `manifest at ${path} is not a mapping`);
|
|
594
|
+
return out;
|
|
595
|
+
} catch (err) {
|
|
596
|
+
if (err instanceof ManifestError) throw err;
|
|
597
|
+
const msg = err.message;
|
|
598
|
+
throw new ManifestError("invalid-config", `failed to parse manifest at ${path}: ${msg}`);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
function requireString(input, key) {
|
|
602
|
+
const v = input[key];
|
|
603
|
+
if (v === void 0 || v === null) throw new ManifestError("missing-required-field", `${key} is required`, key);
|
|
604
|
+
if (typeof v !== "string") throw new ManifestError("invalid-config", `${key} must be a string`, key);
|
|
605
|
+
if (v.trim().length === 0) throw new ManifestError("missing-required-field", `${key} is required`, key);
|
|
606
|
+
return v;
|
|
607
|
+
}
|
|
608
|
+
function optionalString(input, key) {
|
|
609
|
+
const v = input[key];
|
|
610
|
+
if (v === void 0 || v === null) return void 0;
|
|
611
|
+
if (typeof v !== "string") throw new ManifestError("invalid-config", `${key} must be a string when provided`, key);
|
|
612
|
+
return v;
|
|
613
|
+
}
|
|
614
|
+
function requirePath(input, key, configDir) {
|
|
615
|
+
const rel = requireString(input, key);
|
|
616
|
+
return isAbsolute(rel) ? rel : resolve(configDir, rel);
|
|
617
|
+
}
|
|
618
|
+
function optionalPath(input, key, configDir) {
|
|
619
|
+
const rel = optionalString(input, key);
|
|
620
|
+
if (rel === void 0) return void 0;
|
|
621
|
+
return isAbsolute(rel) ? rel : resolve(configDir, rel);
|
|
622
|
+
}
|
|
623
|
+
function requireNumberArray(input, key, { min }) {
|
|
624
|
+
const v = input[key];
|
|
625
|
+
if (!Array.isArray(v)) throw new ManifestError("invalid-config", `${key} must be an array of numbers`, key);
|
|
626
|
+
if (v.length < min) throw new ManifestError("invalid-config", `${key} must contain at least ${min} item(s)`, key);
|
|
627
|
+
return v.map((item, idx) => {
|
|
628
|
+
if (typeof item !== "number" || !Number.isInteger(item)) throw new ManifestError("invalid-config", `${key}[${idx}] must be an integer`, key);
|
|
629
|
+
return item;
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
function optionalStringArray(input, key, { max } = {}) {
|
|
633
|
+
const v = input[key];
|
|
634
|
+
if (v === void 0 || v === null) return [];
|
|
635
|
+
if (!Array.isArray(v)) throw new ManifestError("invalid-config", `${key} must be an array of strings`, key);
|
|
636
|
+
if (max !== void 0 && v.length > max) throw new ManifestError("invalid-config", `${key} accepts at most ${max} entries (got ${v.length})`, key);
|
|
637
|
+
return v.map((item, idx) => {
|
|
638
|
+
if (typeof item !== "string") throw new ManifestError("invalid-config", `${key}[${idx}] must be a string`, key);
|
|
639
|
+
return item;
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
function requirePathArray(input, key, configDir, { min }) {
|
|
643
|
+
const v = input[key];
|
|
644
|
+
if (!Array.isArray(v)) throw new ManifestError("invalid-config", `${key} must be an array of paths`, key);
|
|
645
|
+
if (v.length < min) throw new ManifestError("invalid-config", `${key} must contain at least ${min} item(s)`, key);
|
|
646
|
+
return v.map((item, idx) => {
|
|
647
|
+
if (typeof item !== "string" || item.trim().length === 0) throw new ManifestError("invalid-config", `${key}[${idx}] must be a non-empty string`, key);
|
|
648
|
+
return isAbsolute(item) ? item : resolve(configDir, item);
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
function optionalPathArray(input, key, configDir) {
|
|
652
|
+
const v = input[key];
|
|
653
|
+
if (v === void 0 || v === null) return [];
|
|
654
|
+
if (!Array.isArray(v)) throw new ManifestError("invalid-config", `${key} must be an array of paths`, key);
|
|
655
|
+
return v.map((item, idx) => {
|
|
656
|
+
if (typeof item !== "string" || item.trim().length === 0) throw new ManifestError("invalid-config", `${key}[${idx}] must be a non-empty string`, key);
|
|
657
|
+
return isAbsolute(item) ? item : resolve(configDir, item);
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
const EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
|
661
|
+
function isValidEmail(v) {
|
|
662
|
+
return EMAIL_REGEX.test(v.toLowerCase());
|
|
663
|
+
}
|
|
664
|
+
function isValidHttpUrl(v) {
|
|
665
|
+
try {
|
|
666
|
+
const parsed = new URL(v);
|
|
667
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
668
|
+
} catch {
|
|
669
|
+
return false;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
function validateManifest(raw, configDir) {
|
|
673
|
+
const titleKo = requireString(raw, "titleKo");
|
|
674
|
+
const titleEn = requireString(raw, "titleEn");
|
|
675
|
+
const appName = requireString(raw, "appName");
|
|
676
|
+
const csEmail = requireString(raw, "csEmail");
|
|
677
|
+
if (!isValidEmail(csEmail)) throw new ManifestError("invalid-config", `csEmail is not a valid email address (got ${csEmail})`, "csEmail");
|
|
678
|
+
const subtitle = requireString(raw, "subtitle");
|
|
679
|
+
if (subtitle.length > 20) throw new ManifestError("invalid-config", `subtitle must be 20 characters or fewer (got ${subtitle.length})`, "subtitle");
|
|
680
|
+
const description = requireString(raw, "description");
|
|
681
|
+
const homePageUri = optionalString(raw, "homePageUri");
|
|
682
|
+
if (homePageUri !== void 0 && !isValidHttpUrl(homePageUri)) throw new ManifestError("invalid-config", `homePageUri must be a http(s) URL (got ${homePageUri})`, "homePageUri");
|
|
683
|
+
return {
|
|
684
|
+
titleKo,
|
|
685
|
+
titleEn,
|
|
686
|
+
appName,
|
|
687
|
+
homePageUri,
|
|
688
|
+
csEmail,
|
|
689
|
+
logo: requirePath(raw, "logo", configDir),
|
|
690
|
+
logoDarkMode: optionalPath(raw, "logoDarkMode", configDir),
|
|
691
|
+
horizontalThumbnail: requirePath(raw, "horizontalThumbnail", configDir),
|
|
692
|
+
categoryIds: requireNumberArray(raw, "categoryIds", { min: 1 }),
|
|
693
|
+
subtitle,
|
|
694
|
+
description,
|
|
695
|
+
keywords: optionalStringArray(raw, "keywords", { max: 10 }),
|
|
696
|
+
verticalScreenshots: requirePathArray(raw, "verticalScreenshots", configDir, { min: 3 }),
|
|
697
|
+
horizontalScreenshots: optionalPathArray(raw, "horizontalScreenshots", configDir)
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
//#endregion
|
|
701
|
+
//#region src/config/image-validator.ts
|
|
702
|
+
var ImageDimensionError = class extends Error {
|
|
703
|
+
path;
|
|
704
|
+
expected;
|
|
705
|
+
actual;
|
|
706
|
+
reason;
|
|
707
|
+
constructor(args) {
|
|
708
|
+
super(args.message);
|
|
709
|
+
this.name = "ImageDimensionError";
|
|
710
|
+
this.path = args.path;
|
|
711
|
+
this.expected = args.expected;
|
|
712
|
+
this.actual = args.actual;
|
|
713
|
+
this.reason = args.reason;
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
function format(dim) {
|
|
717
|
+
return `${dim.width}x${dim.height}`;
|
|
718
|
+
}
|
|
719
|
+
async function validateImageDimensions(path, expected) {
|
|
720
|
+
let buffer;
|
|
721
|
+
try {
|
|
722
|
+
buffer = await readFile(path);
|
|
723
|
+
} catch (err) {
|
|
724
|
+
throw new ImageDimensionError({
|
|
725
|
+
path,
|
|
726
|
+
expected: format(expected),
|
|
727
|
+
actual: void 0,
|
|
728
|
+
reason: "unreadable",
|
|
729
|
+
message: `could not read image at ${path}: ${err.message}`
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
let dims;
|
|
733
|
+
try {
|
|
734
|
+
dims = imageSize(buffer);
|
|
735
|
+
} catch (err) {
|
|
736
|
+
throw new ImageDimensionError({
|
|
737
|
+
path,
|
|
738
|
+
expected: format(expected),
|
|
739
|
+
actual: void 0,
|
|
740
|
+
reason: "unreadable",
|
|
741
|
+
message: `could not decode image header at ${path}: ${err.message}`
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
if (typeof dims.width !== "number" || typeof dims.height !== "number") throw new ImageDimensionError({
|
|
745
|
+
path,
|
|
746
|
+
expected: format(expected),
|
|
747
|
+
actual: void 0,
|
|
748
|
+
reason: "unreadable",
|
|
749
|
+
message: `image header at ${path} did not expose width/height`
|
|
750
|
+
});
|
|
751
|
+
if (dims.width !== expected.width || dims.height !== expected.height) {
|
|
752
|
+
const actual = `${dims.width}x${dims.height}`;
|
|
753
|
+
throw new ImageDimensionError({
|
|
754
|
+
path,
|
|
755
|
+
expected: format(expected),
|
|
756
|
+
actual,
|
|
757
|
+
reason: "mismatch",
|
|
758
|
+
message: `image ${path} has dimensions ${actual}; expected ${format(expected)}`
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
const DIMENSIONS = {
|
|
763
|
+
logo: {
|
|
764
|
+
width: 600,
|
|
765
|
+
height: 600
|
|
766
|
+
},
|
|
767
|
+
horizontalThumbnail: {
|
|
768
|
+
width: 1932,
|
|
769
|
+
height: 828
|
|
770
|
+
},
|
|
771
|
+
verticalScreenshot: {
|
|
772
|
+
width: 636,
|
|
773
|
+
height: 1048
|
|
774
|
+
},
|
|
775
|
+
horizontalScreenshot: {
|
|
776
|
+
width: 1504,
|
|
777
|
+
height: 741
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
//#endregion
|
|
781
|
+
//#region src/commands/register-payload.ts
|
|
782
|
+
function buildSubmitPayload(manifest, urls) {
|
|
783
|
+
const images = [
|
|
784
|
+
{
|
|
785
|
+
imageUrl: urls.horizontalThumbnail,
|
|
786
|
+
imageType: "THUMBNAIL",
|
|
787
|
+
orientation: "HORIZONTAL"
|
|
788
|
+
},
|
|
789
|
+
...urls.verticalScreenshots.map((u) => ({
|
|
790
|
+
imageUrl: u,
|
|
791
|
+
imageType: "PREVIEW",
|
|
792
|
+
orientation: "VERTICAL"
|
|
793
|
+
})),
|
|
794
|
+
...urls.horizontalScreenshots.map((u) => ({
|
|
795
|
+
imageUrl: u,
|
|
796
|
+
imageType: "PREVIEW",
|
|
797
|
+
orientation: "HORIZONTAL"
|
|
798
|
+
}))
|
|
799
|
+
];
|
|
800
|
+
return {
|
|
801
|
+
miniApp: {
|
|
802
|
+
title: manifest.titleKo,
|
|
803
|
+
titleEn: manifest.titleEn,
|
|
804
|
+
appName: manifest.appName,
|
|
805
|
+
iconUri: urls.logo,
|
|
806
|
+
status: "PREPARE",
|
|
807
|
+
csEmail: manifest.csEmail,
|
|
808
|
+
description: manifest.subtitle,
|
|
809
|
+
detailDescription: manifest.description,
|
|
810
|
+
images,
|
|
811
|
+
...urls.logoDarkMode !== void 0 ? { darkModeIconUri: urls.logoDarkMode } : {},
|
|
812
|
+
...manifest.homePageUri !== void 0 ? { homePageUri: manifest.homePageUri } : {}
|
|
813
|
+
},
|
|
814
|
+
impression: {
|
|
815
|
+
keywordList: manifest.keywords,
|
|
816
|
+
categoryIds: manifest.categoryIds
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
//#endregion
|
|
821
|
+
//#region src/commands/register.ts
|
|
822
|
+
async function runRegister(args, deps = {}) {
|
|
823
|
+
const ctx = await resolveWorkspaceContext({
|
|
824
|
+
json: args.json,
|
|
825
|
+
...args.workspace !== void 0 ? { workspace: args.workspace } : {}
|
|
826
|
+
});
|
|
827
|
+
if (!ctx) return;
|
|
828
|
+
const { session, workspaceId } = ctx;
|
|
829
|
+
const manifest = await loadAndValidateManifest(args, deps);
|
|
830
|
+
if (!manifest) return;
|
|
831
|
+
if (!args.dryRun && !args.acceptTerms) {
|
|
832
|
+
emitTermsNotAccepted(args.json);
|
|
833
|
+
await exitAfterFlush(ExitCode.Usage);
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
try {
|
|
837
|
+
if (args.dryRun) {
|
|
838
|
+
const payload = buildSubmitPayload(manifest, {
|
|
839
|
+
logo: "<dry-run:logo>",
|
|
840
|
+
logoDarkMode: manifest.logoDarkMode !== void 0 ? "<dry-run:logoDarkMode>" : void 0,
|
|
841
|
+
horizontalThumbnail: "<dry-run:horizontalThumbnail>",
|
|
842
|
+
verticalScreenshots: manifest.verticalScreenshots.map((_, i) => `<dry-run:verticalScreenshots[${i}]>`),
|
|
843
|
+
horizontalScreenshots: manifest.horizontalScreenshots.map((_, i) => `<dry-run:horizontalScreenshots[${i}]>`)
|
|
844
|
+
});
|
|
845
|
+
emitDryRun(args.json, workspaceId, payload);
|
|
846
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
847
|
+
}
|
|
848
|
+
const payload = buildSubmitPayload(manifest, await uploadAllImages(workspaceId, manifest, session.cookies, deps));
|
|
849
|
+
const result = await (deps.submitImpl ?? ((wid, p, c) => createMiniApp(wid, p, c)))(workspaceId, payload, session.cookies);
|
|
850
|
+
emitSuccess(args.json, workspaceId, result);
|
|
851
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
852
|
+
} catch (err) {
|
|
853
|
+
return emitFailureAndExit(args.json, err);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
async function loadAndValidateManifest(args, deps) {
|
|
857
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
858
|
+
let manifest;
|
|
859
|
+
try {
|
|
860
|
+
manifest = await loadAppManifest(await resolveManifestPath(args.config, cwd));
|
|
861
|
+
} catch (err) {
|
|
862
|
+
if (err instanceof ManifestError) {
|
|
863
|
+
emitManifestError(args.json, err);
|
|
864
|
+
await exitAfterFlush(ExitCode.Usage);
|
|
865
|
+
return null;
|
|
866
|
+
}
|
|
867
|
+
throw err;
|
|
868
|
+
}
|
|
869
|
+
try {
|
|
870
|
+
await validateImageDimensions(manifest.logo, DIMENSIONS.logo);
|
|
871
|
+
if (manifest.logoDarkMode !== void 0) await validateImageDimensions(manifest.logoDarkMode, DIMENSIONS.logo);
|
|
872
|
+
await validateImageDimensions(manifest.horizontalThumbnail, DIMENSIONS.horizontalThumbnail);
|
|
873
|
+
for (const p of manifest.verticalScreenshots) await validateImageDimensions(p, DIMENSIONS.verticalScreenshot);
|
|
874
|
+
for (const p of manifest.horizontalScreenshots) await validateImageDimensions(p, DIMENSIONS.horizontalScreenshot);
|
|
875
|
+
} catch (err) {
|
|
876
|
+
if (err instanceof ImageDimensionError) {
|
|
877
|
+
emitImageDimensionError(args.json, err);
|
|
878
|
+
await exitAfterFlush(ExitCode.Usage);
|
|
879
|
+
return null;
|
|
880
|
+
}
|
|
881
|
+
throw err;
|
|
882
|
+
}
|
|
883
|
+
return manifest;
|
|
884
|
+
}
|
|
885
|
+
async function uploadAllImages(workspaceId, manifest, cookies, deps) {
|
|
886
|
+
const uploadImpl = deps.uploadImpl ?? ((p) => uploadMiniAppResource(p));
|
|
887
|
+
const logo = await uploadOne(uploadImpl, {
|
|
888
|
+
workspaceId,
|
|
889
|
+
validWidth: DIMENSIONS.logo.width,
|
|
890
|
+
validHeight: DIMENSIONS.logo.height,
|
|
891
|
+
cookies,
|
|
892
|
+
path: manifest.logo
|
|
893
|
+
});
|
|
894
|
+
const logoDarkMode = manifest.logoDarkMode !== void 0 ? await uploadOne(uploadImpl, {
|
|
895
|
+
workspaceId,
|
|
896
|
+
validWidth: DIMENSIONS.logo.width,
|
|
897
|
+
validHeight: DIMENSIONS.logo.height,
|
|
898
|
+
cookies,
|
|
899
|
+
path: manifest.logoDarkMode
|
|
900
|
+
}) : void 0;
|
|
901
|
+
const horizontalThumbnail = await uploadOne(uploadImpl, {
|
|
902
|
+
workspaceId,
|
|
903
|
+
validWidth: DIMENSIONS.horizontalThumbnail.width,
|
|
904
|
+
validHeight: DIMENSIONS.horizontalThumbnail.height,
|
|
905
|
+
cookies,
|
|
906
|
+
path: manifest.horizontalThumbnail
|
|
907
|
+
});
|
|
908
|
+
const verticalScreenshots = [];
|
|
909
|
+
for (const p of manifest.verticalScreenshots) verticalScreenshots.push(await uploadOne(uploadImpl, {
|
|
910
|
+
workspaceId,
|
|
911
|
+
validWidth: DIMENSIONS.verticalScreenshot.width,
|
|
912
|
+
validHeight: DIMENSIONS.verticalScreenshot.height,
|
|
913
|
+
cookies,
|
|
914
|
+
path: p
|
|
915
|
+
}));
|
|
916
|
+
const horizontalScreenshots = [];
|
|
917
|
+
for (const p of manifest.horizontalScreenshots) horizontalScreenshots.push(await uploadOne(uploadImpl, {
|
|
918
|
+
workspaceId,
|
|
919
|
+
validWidth: DIMENSIONS.horizontalScreenshot.width,
|
|
920
|
+
validHeight: DIMENSIONS.horizontalScreenshot.height,
|
|
921
|
+
cookies,
|
|
922
|
+
path: p
|
|
923
|
+
}));
|
|
924
|
+
return {
|
|
925
|
+
logo,
|
|
926
|
+
logoDarkMode,
|
|
927
|
+
horizontalThumbnail,
|
|
928
|
+
verticalScreenshots,
|
|
929
|
+
horizontalScreenshots
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
async function uploadOne(uploadImpl, input) {
|
|
933
|
+
const buffer = await readFile(input.path);
|
|
934
|
+
return uploadImpl({
|
|
935
|
+
workspaceId: input.workspaceId,
|
|
936
|
+
validWidth: input.validWidth,
|
|
937
|
+
validHeight: input.validHeight,
|
|
938
|
+
cookies: input.cookies,
|
|
939
|
+
file: {
|
|
940
|
+
buffer,
|
|
941
|
+
fileName: basename(input.path),
|
|
942
|
+
contentType: "image/png"
|
|
943
|
+
}
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
function emitManifestError(json, err) {
|
|
947
|
+
if (json) if (err.kind === "missing-required-field") emitJson({
|
|
948
|
+
ok: false,
|
|
949
|
+
reason: "missing-required-field",
|
|
950
|
+
field: err.field ?? null,
|
|
951
|
+
message: err.message
|
|
952
|
+
});
|
|
953
|
+
else emitJson({
|
|
954
|
+
ok: false,
|
|
955
|
+
reason: "invalid-config",
|
|
956
|
+
message: err.message
|
|
957
|
+
});
|
|
958
|
+
else process.stderr.write(`${err.message}\n`);
|
|
959
|
+
}
|
|
960
|
+
function emitImageDimensionError(json, err) {
|
|
961
|
+
if (json) if (err.reason === "mismatch") emitJson({
|
|
962
|
+
ok: false,
|
|
963
|
+
reason: "image-dimension-mismatch",
|
|
964
|
+
path: err.path,
|
|
965
|
+
expected: err.expected,
|
|
966
|
+
actual: err.actual ?? null,
|
|
967
|
+
message: err.message
|
|
968
|
+
});
|
|
969
|
+
else emitJson({
|
|
970
|
+
ok: false,
|
|
971
|
+
reason: "image-unreadable",
|
|
972
|
+
path: err.path,
|
|
973
|
+
message: err.message
|
|
974
|
+
});
|
|
975
|
+
else process.stderr.write(`${err.message}\n`);
|
|
976
|
+
}
|
|
977
|
+
function emitTermsNotAccepted(json) {
|
|
978
|
+
const message = "The console requires several legal-agreement checkboxes before submitting a mini-app for review. Re-run with --accept-terms to attest that you have read and agree to each of them (see VALIDATION-RULES.md or the console UI), or use --dry-run to preview the payload without submitting.";
|
|
979
|
+
if (json) emitJson({
|
|
980
|
+
ok: false,
|
|
981
|
+
reason: "terms-not-accepted",
|
|
982
|
+
message
|
|
983
|
+
});
|
|
984
|
+
else process.stderr.write(`${message}\n`);
|
|
985
|
+
}
|
|
986
|
+
function emitDryRun(json, workspaceId, payload) {
|
|
987
|
+
if (json) emitJson({
|
|
988
|
+
ok: true,
|
|
989
|
+
dryRun: true,
|
|
990
|
+
workspaceId,
|
|
991
|
+
payload
|
|
992
|
+
});
|
|
993
|
+
else {
|
|
994
|
+
process.stdout.write("[dry-run] Would POST to ");
|
|
995
|
+
process.stdout.write(`https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole/workspaces/${workspaceId}/mini-app/review\n`);
|
|
996
|
+
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
function emitSuccess(json, workspaceId, result) {
|
|
1000
|
+
if (json) emitJson({
|
|
1001
|
+
ok: true,
|
|
1002
|
+
workspaceId,
|
|
1003
|
+
appId: result.miniAppId ?? null,
|
|
1004
|
+
reviewState: result.reviewState ?? null
|
|
1005
|
+
});
|
|
1006
|
+
else process.stdout.write(`Registered mini-app ${result.miniAppId ?? "(id unknown)"} in workspace ${workspaceId} (reviewState=${result.reviewState ?? "unknown"}).\n`);
|
|
1007
|
+
}
|
|
1008
|
+
async function emitFailureAndExit(json, err) {
|
|
1009
|
+
return emitFailureFromError(json, err);
|
|
1010
|
+
}
|
|
1011
|
+
//#endregion
|
|
444
1012
|
//#region src/commands/app.ts
|
|
445
1013
|
function findReviewEntry(reviewEntries, appId) {
|
|
446
1014
|
const target = String(appId);
|
|
@@ -460,72 +1028,105 @@ const appCommand = defineCommand({
|
|
|
460
1028
|
name: "app",
|
|
461
1029
|
description: "Inspect mini-apps in a workspace."
|
|
462
1030
|
},
|
|
463
|
-
subCommands: {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
args: {
|
|
469
|
-
workspace: {
|
|
470
|
-
type: "string",
|
|
471
|
-
description: "Workspace ID. Defaults to the selected workspace (`aitcc workspace use`)."
|
|
1031
|
+
subCommands: {
|
|
1032
|
+
ls: defineCommand({
|
|
1033
|
+
meta: {
|
|
1034
|
+
name: "ls",
|
|
1035
|
+
description: "List mini-apps in the selected workspace."
|
|
472
1036
|
},
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
const { session, workspaceId } = ctx;
|
|
483
|
-
try {
|
|
484
|
-
const [apps, review] = await Promise.all([fetchMiniApps(workspaceId, session.cookies), fetchReviewStatus(workspaceId, session.cookies)]);
|
|
485
|
-
if (args.json) {
|
|
486
|
-
const joined = apps.map((app) => {
|
|
487
|
-
const reviewState = reviewStateFor(findReviewEntry(review.miniApps, app.id));
|
|
488
|
-
return {
|
|
489
|
-
id: app.id,
|
|
490
|
-
name: app.name ?? null,
|
|
491
|
-
...reviewState !== void 0 ? { reviewState } : {},
|
|
492
|
-
extra: app.extra
|
|
493
|
-
};
|
|
494
|
-
});
|
|
495
|
-
emitJson({
|
|
496
|
-
ok: true,
|
|
497
|
-
workspaceId,
|
|
498
|
-
hasPolicyViolation: review.hasPolicyViolation,
|
|
499
|
-
apps: joined
|
|
500
|
-
});
|
|
501
|
-
return exitAfterFlush(ExitCode.Ok);
|
|
1037
|
+
args: {
|
|
1038
|
+
workspace: {
|
|
1039
|
+
type: "string",
|
|
1040
|
+
description: "Workspace ID. Defaults to the selected workspace (`aitcc workspace use`)."
|
|
1041
|
+
},
|
|
1042
|
+
json: {
|
|
1043
|
+
type: "boolean",
|
|
1044
|
+
description: "Emit machine-readable JSON to stdout.",
|
|
1045
|
+
default: false
|
|
502
1046
|
}
|
|
503
|
-
|
|
504
|
-
|
|
1047
|
+
},
|
|
1048
|
+
async run({ args }) {
|
|
1049
|
+
const ctx = await resolveWorkspaceContext(args);
|
|
1050
|
+
if (!ctx) return;
|
|
1051
|
+
const { session, workspaceId } = ctx;
|
|
1052
|
+
try {
|
|
1053
|
+
const [apps, review] = await Promise.all([fetchMiniApps(workspaceId, session.cookies), fetchReviewStatus(workspaceId, session.cookies)]);
|
|
1054
|
+
if (args.json) {
|
|
1055
|
+
const joined = apps.map((app) => {
|
|
1056
|
+
const reviewState = reviewStateFor(findReviewEntry(review.miniApps, app.id));
|
|
1057
|
+
return {
|
|
1058
|
+
id: app.id,
|
|
1059
|
+
name: app.name ?? null,
|
|
1060
|
+
...reviewState !== void 0 ? { reviewState } : {},
|
|
1061
|
+
extra: app.extra
|
|
1062
|
+
};
|
|
1063
|
+
});
|
|
1064
|
+
emitJson({
|
|
1065
|
+
ok: true,
|
|
1066
|
+
workspaceId,
|
|
1067
|
+
hasPolicyViolation: review.hasPolicyViolation,
|
|
1068
|
+
apps: joined
|
|
1069
|
+
});
|
|
1070
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
1071
|
+
}
|
|
1072
|
+
if (apps.length === 0) {
|
|
1073
|
+
process.stdout.write(`No apps in workspace ${workspaceId}.\n`);
|
|
1074
|
+
if (review.hasPolicyViolation) process.stderr.write("Note: workspace-wide policy violation flag is set.\n");
|
|
1075
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
1076
|
+
}
|
|
1077
|
+
for (const app of apps) {
|
|
1078
|
+
const reviewState = reviewStateFor(findReviewEntry(review.miniApps, app.id)) ?? "-";
|
|
1079
|
+
const name = app.name ?? "(unnamed)";
|
|
1080
|
+
process.stdout.write(`${app.id}\t${name}\t${reviewState}\n`);
|
|
1081
|
+
}
|
|
505
1082
|
if (review.hasPolicyViolation) process.stderr.write("Note: workspace-wide policy violation flag is set.\n");
|
|
506
1083
|
return exitAfterFlush(ExitCode.Ok);
|
|
1084
|
+
} catch (err) {
|
|
1085
|
+
return emitFailureFromError(args.json, err);
|
|
507
1086
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
1087
|
+
}
|
|
1088
|
+
}),
|
|
1089
|
+
register: defineCommand({
|
|
1090
|
+
meta: {
|
|
1091
|
+
name: "register",
|
|
1092
|
+
description: "Register a mini-app in the selected workspace from a YAML/JSON manifest. Uploads logo/thumbnail/screenshots, then submits the create payload."
|
|
1093
|
+
},
|
|
1094
|
+
args: {
|
|
1095
|
+
workspace: {
|
|
1096
|
+
type: "string",
|
|
1097
|
+
description: "Workspace ID. Defaults to the selected workspace (`aitcc workspace use`)."
|
|
1098
|
+
},
|
|
1099
|
+
config: {
|
|
1100
|
+
type: "string",
|
|
1101
|
+
description: "Path to the app manifest. Defaults to `./aitcc.app.yaml`, then `./aitcc.app.json`."
|
|
1102
|
+
},
|
|
1103
|
+
"dry-run": {
|
|
1104
|
+
type: "boolean",
|
|
1105
|
+
description: "Validate manifest + images and print the inferred submit payload; no uploads.",
|
|
1106
|
+
default: false
|
|
1107
|
+
},
|
|
1108
|
+
"accept-terms": {
|
|
1109
|
+
type: "boolean",
|
|
1110
|
+
description: "Attest to the required console legal-agreement checkboxes (see VALIDATION-RULES.md). Required for real submits.",
|
|
1111
|
+
default: false
|
|
1112
|
+
},
|
|
1113
|
+
json: {
|
|
1114
|
+
type: "boolean",
|
|
1115
|
+
description: "Emit machine-readable JSON to stdout.",
|
|
1116
|
+
default: false
|
|
523
1117
|
}
|
|
524
|
-
|
|
525
|
-
|
|
1118
|
+
},
|
|
1119
|
+
async run({ args }) {
|
|
1120
|
+
await runRegister({
|
|
1121
|
+
json: args.json,
|
|
1122
|
+
dryRun: args["dry-run"],
|
|
1123
|
+
acceptTerms: args["accept-terms"],
|
|
1124
|
+
...args.workspace !== void 0 ? { workspace: args.workspace } : {},
|
|
1125
|
+
...args.config !== void 0 ? { config: args.config } : {}
|
|
1126
|
+
});
|
|
526
1127
|
}
|
|
527
|
-
}
|
|
528
|
-
}
|
|
1128
|
+
})
|
|
1129
|
+
}
|
|
529
1130
|
});
|
|
530
1131
|
//#endregion
|
|
531
1132
|
//#region src/api/api-keys.ts
|
|
@@ -605,16 +1206,7 @@ const keysCommand = defineCommand({
|
|
|
605
1206
|
}
|
|
606
1207
|
return exitAfterFlush(ExitCode.Ok);
|
|
607
1208
|
} catch (err) {
|
|
608
|
-
|
|
609
|
-
emitNotAuthenticated(args.json, "session-expired");
|
|
610
|
-
return exitAfterFlush(ExitCode.NotAuthenticated);
|
|
611
|
-
}
|
|
612
|
-
if (err instanceof NetworkError) {
|
|
613
|
-
emitNetworkError(args.json, err.message);
|
|
614
|
-
return exitAfterFlush(ExitCode.NetworkError);
|
|
615
|
-
}
|
|
616
|
-
emitApiError(args.json, err.message);
|
|
617
|
-
return exitAfterFlush(ExitCode.ApiError);
|
|
1209
|
+
return emitFailureFromError(args.json, err);
|
|
618
1210
|
}
|
|
619
1211
|
}
|
|
620
1212
|
}) }
|
|
@@ -1367,16 +1959,7 @@ const membersCommand = defineCommand({
|
|
|
1367
1959
|
for (const m of members) process.stdout.write(`${m.bizUserNo}\t${m.name}\t${m.email}\t${m.role}\t${m.status}\n`);
|
|
1368
1960
|
return exitAfterFlush(ExitCode.Ok);
|
|
1369
1961
|
} catch (err) {
|
|
1370
|
-
|
|
1371
|
-
emitNotAuthenticated(args.json, "session-expired");
|
|
1372
|
-
return exitAfterFlush(ExitCode.NotAuthenticated);
|
|
1373
|
-
}
|
|
1374
|
-
if (err instanceof NetworkError) {
|
|
1375
|
-
emitNetworkError(args.json, err.message);
|
|
1376
|
-
return exitAfterFlush(ExitCode.NetworkError);
|
|
1377
|
-
}
|
|
1378
|
-
emitApiError(args.json, err.message);
|
|
1379
|
-
return exitAfterFlush(ExitCode.ApiError);
|
|
1962
|
+
return emitFailureFromError(args.json, err);
|
|
1380
1963
|
}
|
|
1381
1964
|
}
|
|
1382
1965
|
}) }
|
|
@@ -1494,7 +2077,7 @@ function resolveVersion() {
|
|
|
1494
2077
|
if (typeof injected === "string" && injected.length > 0) return injected;
|
|
1495
2078
|
} catch {}
|
|
1496
2079
|
try {
|
|
1497
|
-
return "0.1.
|
|
2080
|
+
return "0.1.6";
|
|
1498
2081
|
} catch {}
|
|
1499
2082
|
return "0.0.0-dev";
|
|
1500
2083
|
}
|
|
@@ -1928,16 +2511,7 @@ const workspaceCommand = defineCommand({
|
|
|
1928
2511
|
if (current === void 0) process.stderr.write("No workspace selected. Run `aitcc workspace use <id>`.\n");
|
|
1929
2512
|
return exitAfterFlush(ExitCode.Ok);
|
|
1930
2513
|
} catch (err) {
|
|
1931
|
-
|
|
1932
|
-
emitNotAuthenticated(args.json, "session-expired");
|
|
1933
|
-
return exitAfterFlush(ExitCode.NotAuthenticated);
|
|
1934
|
-
}
|
|
1935
|
-
if (err instanceof NetworkError) {
|
|
1936
|
-
emitNetworkError(args.json, err.message);
|
|
1937
|
-
return exitAfterFlush(ExitCode.NetworkError);
|
|
1938
|
-
}
|
|
1939
|
-
emitApiError(args.json, err.message);
|
|
1940
|
-
return exitAfterFlush(ExitCode.ApiError);
|
|
2514
|
+
return emitFailureFromError(args.json, err);
|
|
1941
2515
|
}
|
|
1942
2516
|
}
|
|
1943
2517
|
}),
|
|
@@ -1999,16 +2573,7 @@ const workspaceCommand = defineCommand({
|
|
|
1999
2573
|
else process.stdout.write(`Using workspace ${match.workspaceId} (${match.workspaceName}).\n`);
|
|
2000
2574
|
return exitAfterFlush(ExitCode.Ok);
|
|
2001
2575
|
} catch (err) {
|
|
2002
|
-
|
|
2003
|
-
emitNotAuthenticated(args.json, "session-expired");
|
|
2004
|
-
return exitAfterFlush(ExitCode.NotAuthenticated);
|
|
2005
|
-
}
|
|
2006
|
-
if (err instanceof NetworkError) {
|
|
2007
|
-
emitNetworkError(args.json, err.message);
|
|
2008
|
-
return exitAfterFlush(ExitCode.NetworkError);
|
|
2009
|
-
}
|
|
2010
|
-
emitApiError(args.json, err.message);
|
|
2011
|
-
return exitAfterFlush(ExitCode.ApiError);
|
|
2576
|
+
return emitFailureFromError(args.json, err);
|
|
2012
2577
|
}
|
|
2013
2578
|
}
|
|
2014
2579
|
}),
|
|
@@ -2073,16 +2638,7 @@ const workspaceCommand = defineCommand({
|
|
|
2073
2638
|
if (detail.extra) for (const [k, v] of Object.entries(detail.extra)) process.stdout.write(` ${k}: ${formatScalar(v)}\n`);
|
|
2074
2639
|
return exitAfterFlush(ExitCode.Ok);
|
|
2075
2640
|
} catch (err) {
|
|
2076
|
-
|
|
2077
|
-
emitNotAuthenticated(args.json, "session-expired");
|
|
2078
|
-
return exitAfterFlush(ExitCode.NotAuthenticated);
|
|
2079
|
-
}
|
|
2080
|
-
if (err instanceof NetworkError) {
|
|
2081
|
-
emitNetworkError(args.json, err.message);
|
|
2082
|
-
return exitAfterFlush(ExitCode.NetworkError);
|
|
2083
|
-
}
|
|
2084
|
-
emitApiError(args.json, err.message);
|
|
2085
|
-
return exitAfterFlush(ExitCode.ApiError);
|
|
2641
|
+
return emitFailureFromError(args.json, err);
|
|
2086
2642
|
}
|
|
2087
2643
|
}
|
|
2088
2644
|
})
|