@ait-co/console-cli 0.1.12 → 0.1.15
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 +1229 -37
- package/dist/cli.mjs.map +1 -1
- package/package.json +2 -1
package/dist/cli.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { defineCommand, runMain } from "citty";
|
|
|
3
3
|
import { access, chmod, mkdir, mkdtemp, readFile, rename, rm, unlink, writeFile } from "node:fs/promises";
|
|
4
4
|
import { basename, dirname, isAbsolute, join, resolve, win32 } from "node:path";
|
|
5
5
|
import { homedir, tmpdir } from "node:os";
|
|
6
|
+
import { unzipSync } from "fflate";
|
|
6
7
|
import { parse } from "yaml";
|
|
7
8
|
import { imageSize } from "image-size";
|
|
8
9
|
import { spawn } from "node:child_process";
|
|
@@ -516,6 +517,138 @@ async function fetchDeployedBundle(workspaceId, miniAppId, cookies, opts = {}) {
|
|
|
516
517
|
if (typeof raw !== "object" || Array.isArray(raw)) throw new Error(`Unexpected deployed-bundle shape for app=${miniAppId}`);
|
|
517
518
|
return raw;
|
|
518
519
|
}
|
|
520
|
+
async function postDeploymentsInitialize(workspaceId, miniAppId, deploymentId, cookies, opts = {}) {
|
|
521
|
+
const raw = await requestConsoleApi({
|
|
522
|
+
method: "POST",
|
|
523
|
+
url: `${BASE$4}/workspaces/${workspaceId}/mini-app/${miniAppId}/deployments/initialize`,
|
|
524
|
+
body: { deploymentId },
|
|
525
|
+
cookies,
|
|
526
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
527
|
+
});
|
|
528
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throw new Error(`Unexpected deployments/initialize shape for app=${miniAppId}`);
|
|
529
|
+
const data = raw;
|
|
530
|
+
const uploadUrl = data.uploadUrl;
|
|
531
|
+
if (typeof uploadUrl !== "string") throw new Error(`Unexpected deployments/initialize shape for app=${miniAppId}: missing uploadUrl`);
|
|
532
|
+
const deployment = data.deployment && typeof data.deployment === "object" ? data.deployment : {};
|
|
533
|
+
return {
|
|
534
|
+
uploadUrl,
|
|
535
|
+
deployment,
|
|
536
|
+
reviewStatus: typeof deployment.reviewStatus === "string" ? deployment.reviewStatus : "UNKNOWN"
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
async function postDeploymentsComplete(workspaceId, miniAppId, deploymentId, cookies, opts = {}) {
|
|
540
|
+
const raw = await requestConsoleApi({
|
|
541
|
+
method: "POST",
|
|
542
|
+
url: `${BASE$4}/workspaces/${workspaceId}/mini-app/${miniAppId}/deployments/complete`,
|
|
543
|
+
body: { deploymentId },
|
|
544
|
+
cookies,
|
|
545
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
546
|
+
});
|
|
547
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
548
|
+
return raw;
|
|
549
|
+
}
|
|
550
|
+
async function postBundleMemo(workspaceId, miniAppId, deploymentId, memo, cookies, opts = {}) {
|
|
551
|
+
const raw = await requestConsoleApi({
|
|
552
|
+
method: "POST",
|
|
553
|
+
url: `${BASE$4}/workspaces/${workspaceId}/mini-app/${miniAppId}/bundles/memos`,
|
|
554
|
+
body: {
|
|
555
|
+
deploymentId,
|
|
556
|
+
memo
|
|
557
|
+
},
|
|
558
|
+
cookies,
|
|
559
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
560
|
+
});
|
|
561
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
562
|
+
return raw;
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* PUT the raw .ait bytes to the S3 presigned URL returned by
|
|
566
|
+
* `postDeploymentsInitialize`. This is a direct-to-S3 call with NO Toss
|
|
567
|
+
* envelope — the response is empty on success (HTTP 200). Any cookies are
|
|
568
|
+
* intentionally NOT sent because S3 would reject the signed request if
|
|
569
|
+
* extra auth headers contradict the signature.
|
|
570
|
+
*/
|
|
571
|
+
async function putBundleToUploadUrl(uploadUrl, body, opts = {}) {
|
|
572
|
+
const impl = opts.fetchImpl ?? ((i, init) => fetch(i, init));
|
|
573
|
+
const view = new Uint8Array(body.buffer, body.byteOffset, body.byteLength);
|
|
574
|
+
let res;
|
|
575
|
+
try {
|
|
576
|
+
res = await impl(uploadUrl, {
|
|
577
|
+
method: "PUT",
|
|
578
|
+
headers: { "Content-Type": "application/zip" },
|
|
579
|
+
body: view
|
|
580
|
+
});
|
|
581
|
+
} catch (err) {
|
|
582
|
+
throw new Error(`PUT to upload URL failed: ${err.message}`);
|
|
583
|
+
}
|
|
584
|
+
if (!res.ok) {
|
|
585
|
+
const preview = await res.text().catch(() => "");
|
|
586
|
+
throw new Error(`PUT to upload URL returned HTTP ${res.status}: ${preview.slice(0, 200)}`);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
async function postBundleReview(params, cookies, opts = {}) {
|
|
590
|
+
const url = `${BASE$4}/workspaces/${params.workspaceId}/mini-app/${params.miniAppId}/bundles/reviews`;
|
|
591
|
+
const body = {
|
|
592
|
+
deploymentId: params.deploymentId,
|
|
593
|
+
releaseNotes: params.releaseNotes
|
|
594
|
+
};
|
|
595
|
+
if (params.featureList !== void 0) body.featureList = params.featureList;
|
|
596
|
+
if (params.screenshotImagePaths !== void 0) body.screenshotImagePaths = params.screenshotImagePaths;
|
|
597
|
+
const raw = await requestConsoleApi({
|
|
598
|
+
method: "POST",
|
|
599
|
+
url,
|
|
600
|
+
body,
|
|
601
|
+
cookies,
|
|
602
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
603
|
+
});
|
|
604
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
605
|
+
return raw;
|
|
606
|
+
}
|
|
607
|
+
async function postBundleReviewWithdrawal(workspaceId, miniAppId, deploymentId, cookies, opts = {}) {
|
|
608
|
+
const raw = await requestConsoleApi({
|
|
609
|
+
method: "POST",
|
|
610
|
+
url: `${BASE$4}/workspaces/${workspaceId}/mini-app/${miniAppId}/bundles/reviews/withdrawal`,
|
|
611
|
+
body: { deploymentId },
|
|
612
|
+
cookies,
|
|
613
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
614
|
+
});
|
|
615
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
616
|
+
return raw;
|
|
617
|
+
}
|
|
618
|
+
async function postBundleRelease(params, cookies, opts = {}) {
|
|
619
|
+
const url = `${BASE$4}/workspaces/${params.workspaceId}/mini-app/${params.miniAppId}/bundles/release`;
|
|
620
|
+
const body = { deploymentId: params.deploymentId };
|
|
621
|
+
if (params.contentImages !== void 0) body.contentImages = params.contentImages;
|
|
622
|
+
const raw = await requestConsoleApi({
|
|
623
|
+
method: "POST",
|
|
624
|
+
url,
|
|
625
|
+
body,
|
|
626
|
+
cookies,
|
|
627
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
628
|
+
});
|
|
629
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
630
|
+
return raw;
|
|
631
|
+
}
|
|
632
|
+
async function postBundleTestPush(workspaceId, miniAppId, deploymentId, cookies, opts = {}) {
|
|
633
|
+
const raw = await requestConsoleApi({
|
|
634
|
+
method: "POST",
|
|
635
|
+
url: `${BASE$4}/workspaces/${workspaceId}/mini-app/${miniAppId}/bundles/test-push`,
|
|
636
|
+
body: { deploymentId },
|
|
637
|
+
cookies,
|
|
638
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
639
|
+
});
|
|
640
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
641
|
+
return raw;
|
|
642
|
+
}
|
|
643
|
+
async function fetchBundleTestLinks(workspaceId, miniAppId, cookies, opts = {}) {
|
|
644
|
+
const raw = await requestConsoleApi({
|
|
645
|
+
url: `${BASE$4}/workspaces/${workspaceId}/mini-app/${miniAppId}/bundles/test-links`,
|
|
646
|
+
cookies,
|
|
647
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
648
|
+
});
|
|
649
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
650
|
+
return raw;
|
|
651
|
+
}
|
|
519
652
|
async function createMiniApp(workspaceId, payload, cookies, opts = {}) {
|
|
520
653
|
return normalizeCreateResult(await requestConsoleApi({
|
|
521
654
|
url: `${BASE$4}/workspaces/${workspaceId}/mini-app/review`,
|
|
@@ -873,6 +1006,341 @@ async function requireSession(json) {
|
|
|
873
1006
|
return session;
|
|
874
1007
|
}
|
|
875
1008
|
//#endregion
|
|
1009
|
+
//#region src/config/ait-bundle.ts
|
|
1010
|
+
var AitBundleError = class extends Error {
|
|
1011
|
+
path;
|
|
1012
|
+
reason;
|
|
1013
|
+
constructor(args) {
|
|
1014
|
+
super(args.message);
|
|
1015
|
+
this.name = "AitBundleError";
|
|
1016
|
+
this.path = args.path;
|
|
1017
|
+
this.reason = args.reason;
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
/**
|
|
1021
|
+
* Read the `.ait` at `path`, extract `app.json`, and pull out
|
|
1022
|
+
* `_metadata.deploymentId`. Returns both the id and the raw bytes so the
|
|
1023
|
+
* caller can forward them to the S3 upload without re-reading the file.
|
|
1024
|
+
*
|
|
1025
|
+
* Errors are all surfaced as `AitBundleError` with a structured `reason`
|
|
1026
|
+
* so the command layer can render a typed `--json` failure.
|
|
1027
|
+
*/
|
|
1028
|
+
async function readAitBundle(path) {
|
|
1029
|
+
let buf;
|
|
1030
|
+
try {
|
|
1031
|
+
buf = await readFile(path);
|
|
1032
|
+
} catch (err) {
|
|
1033
|
+
throw new AitBundleError({
|
|
1034
|
+
path,
|
|
1035
|
+
reason: "file-unreadable",
|
|
1036
|
+
message: err.message
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
const bytes = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
1040
|
+
return {
|
|
1041
|
+
deploymentId: deploymentIdFromBundleBytes(bytes, path),
|
|
1042
|
+
bytes
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Pure helper split out so tests can feed raw zip bytes without a tmp
|
|
1047
|
+
* file. Throws `AitBundleError` on any parse failure.
|
|
1048
|
+
*/
|
|
1049
|
+
function deploymentIdFromBundleBytes(bytes, pathForError) {
|
|
1050
|
+
let entries;
|
|
1051
|
+
try {
|
|
1052
|
+
entries = unzipSync(bytes, { filter: (file) => file.name === "app.json" });
|
|
1053
|
+
} catch (err) {
|
|
1054
|
+
throw new AitBundleError({
|
|
1055
|
+
path: pathForError,
|
|
1056
|
+
reason: "invalid-zip",
|
|
1057
|
+
message: `not a valid zip: ${err.message}`
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
const entry = entries["app.json"];
|
|
1061
|
+
if (!entry) throw new AitBundleError({
|
|
1062
|
+
path: pathForError,
|
|
1063
|
+
reason: "missing-app-json",
|
|
1064
|
+
message: "app.json is not present at the root of the bundle"
|
|
1065
|
+
});
|
|
1066
|
+
let parsed;
|
|
1067
|
+
try {
|
|
1068
|
+
parsed = JSON.parse(new TextDecoder().decode(entry));
|
|
1069
|
+
} catch (err) {
|
|
1070
|
+
throw new AitBundleError({
|
|
1071
|
+
path: pathForError,
|
|
1072
|
+
reason: "invalid-app-json",
|
|
1073
|
+
message: `app.json is not valid JSON: ${err.message}`
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new AitBundleError({
|
|
1077
|
+
path: pathForError,
|
|
1078
|
+
reason: "invalid-app-json",
|
|
1079
|
+
message: "app.json is not a JSON object"
|
|
1080
|
+
});
|
|
1081
|
+
const metadata = parsed._metadata;
|
|
1082
|
+
if (metadata === null || typeof metadata !== "object" || Array.isArray(metadata)) throw new AitBundleError({
|
|
1083
|
+
path: pathForError,
|
|
1084
|
+
reason: "missing-deployment-id",
|
|
1085
|
+
message: "app.json._metadata is missing; is your build outputting the modern app.json schema?"
|
|
1086
|
+
});
|
|
1087
|
+
const deploymentId = metadata.deploymentId;
|
|
1088
|
+
if (typeof deploymentId !== "string" || deploymentId === "") throw new AitBundleError({
|
|
1089
|
+
path: pathForError,
|
|
1090
|
+
reason: "missing-deployment-id",
|
|
1091
|
+
message: "app.json._metadata.deploymentId is missing or empty; is your build outputting the modern app.json schema?"
|
|
1092
|
+
});
|
|
1093
|
+
return deploymentId;
|
|
1094
|
+
}
|
|
1095
|
+
//#endregion
|
|
1096
|
+
//#region src/commands/app-deploy.ts
|
|
1097
|
+
function parseAppIdStrict(raw) {
|
|
1098
|
+
if (raw === "") return null;
|
|
1099
|
+
if (!/^[1-9]\d*$/.test(raw)) return null;
|
|
1100
|
+
const n = Number.parseInt(raw, 10);
|
|
1101
|
+
return Number.isSafeInteger(n) ? n : null;
|
|
1102
|
+
}
|
|
1103
|
+
async function runDeploy(args, deps = {}) {
|
|
1104
|
+
if (typeof args.app !== "string" || args.app === "") {
|
|
1105
|
+
if (args.json) emitJson({
|
|
1106
|
+
ok: false,
|
|
1107
|
+
reason: "missing-app-id",
|
|
1108
|
+
message: "--app <id> is required"
|
|
1109
|
+
});
|
|
1110
|
+
else process.stderr.write("app deploy: --app <id> is required.\n");
|
|
1111
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
1112
|
+
}
|
|
1113
|
+
const appId = parseAppIdStrict(args.app);
|
|
1114
|
+
if (appId === null) {
|
|
1115
|
+
if (args.json) emitJson({
|
|
1116
|
+
ok: false,
|
|
1117
|
+
reason: "invalid-id",
|
|
1118
|
+
message: `--app must be a positive integer (got ${JSON.stringify(args.app)})`
|
|
1119
|
+
});
|
|
1120
|
+
else process.stderr.write(`app deploy: invalid --app ${JSON.stringify(args.app)}\n`);
|
|
1121
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
1122
|
+
}
|
|
1123
|
+
if (typeof args.path !== "string" || args.path === "") {
|
|
1124
|
+
if (args.json) emitJson({
|
|
1125
|
+
ok: false,
|
|
1126
|
+
reason: "missing-path",
|
|
1127
|
+
message: "path to .ait bundle is required"
|
|
1128
|
+
});
|
|
1129
|
+
else process.stderr.write("app deploy: path to .ait bundle is required.\n");
|
|
1130
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
1131
|
+
}
|
|
1132
|
+
const requestReview = Boolean(args.requestReview);
|
|
1133
|
+
const release = Boolean(args.release);
|
|
1134
|
+
const confirm = Boolean(args.confirm);
|
|
1135
|
+
const releaseNotes = typeof args.releaseNotes === "string" ? args.releaseNotes : void 0;
|
|
1136
|
+
if (requestReview && releaseNotes === void 0) {
|
|
1137
|
+
if (args.json) emitJson({
|
|
1138
|
+
ok: false,
|
|
1139
|
+
reason: "missing-release-notes",
|
|
1140
|
+
message: "--release-notes <text> is required with --request-review"
|
|
1141
|
+
});
|
|
1142
|
+
else process.stderr.write("app deploy: --release-notes <text> is required with --request-review.\n");
|
|
1143
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
1144
|
+
}
|
|
1145
|
+
if (release && !confirm) {
|
|
1146
|
+
if (args.json) emitJson({
|
|
1147
|
+
ok: false,
|
|
1148
|
+
reason: "not-confirmed",
|
|
1149
|
+
message: "--release is destructive; pass --confirm to proceed"
|
|
1150
|
+
});
|
|
1151
|
+
else process.stderr.write("app deploy: --release publishes the bundle to end users.\n Re-run with --confirm to proceed.\n");
|
|
1152
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
1153
|
+
}
|
|
1154
|
+
const readBundle = deps.readBundleImpl ?? readAitBundle;
|
|
1155
|
+
let bundleInfo;
|
|
1156
|
+
try {
|
|
1157
|
+
bundleInfo = await readBundle(args.path);
|
|
1158
|
+
} catch (err) {
|
|
1159
|
+
if (err instanceof AitBundleError) {
|
|
1160
|
+
const reason = err.reason === "file-unreadable" ? "file-unreadable" : "invalid-bundle";
|
|
1161
|
+
if (args.json) emitJson({
|
|
1162
|
+
ok: false,
|
|
1163
|
+
reason,
|
|
1164
|
+
path: err.path,
|
|
1165
|
+
bundleReason: err.reason,
|
|
1166
|
+
message: err.message
|
|
1167
|
+
});
|
|
1168
|
+
else process.stderr.write(`app deploy: ${err.message}\n`);
|
|
1169
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
1170
|
+
}
|
|
1171
|
+
throw err;
|
|
1172
|
+
}
|
|
1173
|
+
const deploymentId = typeof args.deploymentId === "string" && args.deploymentId !== "" ? args.deploymentId : bundleInfo.deploymentId;
|
|
1174
|
+
if (deploymentId === "") {
|
|
1175
|
+
if (args.json) emitJson({
|
|
1176
|
+
ok: false,
|
|
1177
|
+
reason: "invalid-bundle",
|
|
1178
|
+
path: args.path,
|
|
1179
|
+
message: "deploymentId is empty"
|
|
1180
|
+
});
|
|
1181
|
+
else process.stderr.write("app deploy: deploymentId is empty.\n");
|
|
1182
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
1183
|
+
}
|
|
1184
|
+
const ctx = await resolveWorkspaceContext(args);
|
|
1185
|
+
if (!ctx) return;
|
|
1186
|
+
const { session, workspaceId } = ctx;
|
|
1187
|
+
const memo = typeof args.memo === "string" && args.memo.length > 0 ? args.memo : void 0;
|
|
1188
|
+
const steps = ["upload"];
|
|
1189
|
+
if (requestReview) steps.push("review");
|
|
1190
|
+
if (release) steps.push("release");
|
|
1191
|
+
if (args.dryRun) {
|
|
1192
|
+
if (args.json) emitJson({
|
|
1193
|
+
ok: true,
|
|
1194
|
+
dryRun: true,
|
|
1195
|
+
workspaceId,
|
|
1196
|
+
appId,
|
|
1197
|
+
deploymentId,
|
|
1198
|
+
bytes: bundleInfo.bytes.byteLength,
|
|
1199
|
+
steps,
|
|
1200
|
+
memo: memo ?? null,
|
|
1201
|
+
releaseNotes: releaseNotes ?? null,
|
|
1202
|
+
confirmed: confirm
|
|
1203
|
+
});
|
|
1204
|
+
else {
|
|
1205
|
+
const stepsLine = steps.map((s) => {
|
|
1206
|
+
if (s === "review") return `review (releaseNotes: ${JSON.stringify(releaseNotes ?? "")})`;
|
|
1207
|
+
if (s === "release") return `release (${confirm ? "confirmed" : "NOT confirmed"})`;
|
|
1208
|
+
return s;
|
|
1209
|
+
}).join(" → ");
|
|
1210
|
+
process.stdout.write(`DRY RUN\n app ${appId}\n workspace ${workspaceId}\n bundle ${args.path} (${bundleInfo.bytes.byteLength} bytes)\n deploymentId ${deploymentId}\n memo ${memo ?? "(none)"}\n steps ${stepsLine}\n`);
|
|
1211
|
+
}
|
|
1212
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
1213
|
+
}
|
|
1214
|
+
const apiOpts = deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {};
|
|
1215
|
+
let uploaded = false;
|
|
1216
|
+
let bundleRecord = null;
|
|
1217
|
+
let reviewed = false;
|
|
1218
|
+
let reviewResult = null;
|
|
1219
|
+
try {
|
|
1220
|
+
const init = await postDeploymentsInitialize(workspaceId, appId, deploymentId, session.cookies, apiOpts);
|
|
1221
|
+
if (init.reviewStatus !== "PREPARE") {
|
|
1222
|
+
if (args.json) emitJson({
|
|
1223
|
+
ok: false,
|
|
1224
|
+
reason: "bundle-not-prepare",
|
|
1225
|
+
workspaceId,
|
|
1226
|
+
appId,
|
|
1227
|
+
deploymentId,
|
|
1228
|
+
reviewStatus: init.reviewStatus,
|
|
1229
|
+
message: "이미 존재하는 버전이에요."
|
|
1230
|
+
});
|
|
1231
|
+
else process.stderr.write(`app deploy: deployment ${deploymentId} is already in state ${init.reviewStatus}; upload refused.\n`);
|
|
1232
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
1233
|
+
}
|
|
1234
|
+
await putBundleToUploadUrl(init.uploadUrl, bundleInfo.bytes, apiOpts);
|
|
1235
|
+
bundleRecord = await postDeploymentsComplete(workspaceId, appId, deploymentId, session.cookies, apiOpts);
|
|
1236
|
+
if (memo !== void 0) await postBundleMemo(workspaceId, appId, deploymentId, memo, session.cookies, apiOpts);
|
|
1237
|
+
uploaded = true;
|
|
1238
|
+
} catch (err) {
|
|
1239
|
+
return emitFailureFromError(args.json, err);
|
|
1240
|
+
}
|
|
1241
|
+
if (requestReview) try {
|
|
1242
|
+
reviewResult = await postBundleReview({
|
|
1243
|
+
workspaceId,
|
|
1244
|
+
miniAppId: appId,
|
|
1245
|
+
deploymentId,
|
|
1246
|
+
releaseNotes: releaseNotes ?? ""
|
|
1247
|
+
}, session.cookies, apiOpts);
|
|
1248
|
+
reviewed = true;
|
|
1249
|
+
} catch (err) {
|
|
1250
|
+
return emitPartialFailure(args.json, err, {
|
|
1251
|
+
workspaceId,
|
|
1252
|
+
appId,
|
|
1253
|
+
deploymentId,
|
|
1254
|
+
uploaded: true,
|
|
1255
|
+
reviewed: false,
|
|
1256
|
+
released: false
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
let releaseResult = null;
|
|
1260
|
+
if (release) try {
|
|
1261
|
+
releaseResult = await postBundleRelease({
|
|
1262
|
+
workspaceId,
|
|
1263
|
+
miniAppId: appId,
|
|
1264
|
+
deploymentId
|
|
1265
|
+
}, session.cookies, apiOpts);
|
|
1266
|
+
} catch (err) {
|
|
1267
|
+
return emitPartialFailure(args.json, err, {
|
|
1268
|
+
workspaceId,
|
|
1269
|
+
appId,
|
|
1270
|
+
deploymentId,
|
|
1271
|
+
uploaded: true,
|
|
1272
|
+
reviewed,
|
|
1273
|
+
released: false
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
if (args.json) {
|
|
1277
|
+
emitJson({
|
|
1278
|
+
ok: true,
|
|
1279
|
+
workspaceId,
|
|
1280
|
+
appId,
|
|
1281
|
+
deploymentId,
|
|
1282
|
+
uploaded,
|
|
1283
|
+
reviewed,
|
|
1284
|
+
released: release,
|
|
1285
|
+
bundle: bundleRecord,
|
|
1286
|
+
reviewResult,
|
|
1287
|
+
releaseResult
|
|
1288
|
+
});
|
|
1289
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
1290
|
+
}
|
|
1291
|
+
process.stdout.write(`Deployed bundle for app ${appId} (ws ${workspaceId})\n deploymentId ${deploymentId}\n bytes ${bundleInfo.bytes.byteLength}\n steps ${steps.join(" → ")}\n`);
|
|
1292
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
1293
|
+
}
|
|
1294
|
+
/**
|
|
1295
|
+
* Partial-failure emitter. The upload succeeded (so the user does NOT
|
|
1296
|
+
* need to re-upload on retry) but a downstream step failed. Keeping the
|
|
1297
|
+
* `uploaded: true` bit in the JSON lets agent-plugin skip to the
|
|
1298
|
+
* specific failing step on retry instead of re-running the whole
|
|
1299
|
+
* pipeline.
|
|
1300
|
+
*/
|
|
1301
|
+
async function emitPartialFailure(json, err, progress) {
|
|
1302
|
+
if (err instanceof TossApiError && err.isAuthError) {
|
|
1303
|
+
if (json) emitJson({
|
|
1304
|
+
ok: true,
|
|
1305
|
+
authenticated: false,
|
|
1306
|
+
reason: "session-expired",
|
|
1307
|
+
...progress
|
|
1308
|
+
});
|
|
1309
|
+
else process.stderr.write("Session is no longer valid. Run `aitcc login` again.\n");
|
|
1310
|
+
return exitAfterFlush(ExitCode.NotAuthenticated);
|
|
1311
|
+
}
|
|
1312
|
+
if (err instanceof TossApiError) {
|
|
1313
|
+
if (json) emitJson({
|
|
1314
|
+
ok: false,
|
|
1315
|
+
reason: "api-error",
|
|
1316
|
+
status: err.status,
|
|
1317
|
+
...err.errorCode !== void 0 ? { errorCode: err.errorCode } : {},
|
|
1318
|
+
message: err.message,
|
|
1319
|
+
...progress
|
|
1320
|
+
});
|
|
1321
|
+
else process.stderr.write(`Unexpected error: ${err.message}\n`);
|
|
1322
|
+
return exitAfterFlush(ExitCode.ApiError);
|
|
1323
|
+
}
|
|
1324
|
+
if (err instanceof NetworkError) {
|
|
1325
|
+
if (json) emitJson({
|
|
1326
|
+
ok: false,
|
|
1327
|
+
reason: "network-error",
|
|
1328
|
+
message: err.message,
|
|
1329
|
+
...progress
|
|
1330
|
+
});
|
|
1331
|
+
else process.stderr.write(`Network error reaching the console API: ${err.message}.\n`);
|
|
1332
|
+
return exitAfterFlush(ExitCode.NetworkError);
|
|
1333
|
+
}
|
|
1334
|
+
if (json) emitJson({
|
|
1335
|
+
ok: false,
|
|
1336
|
+
reason: "api-error",
|
|
1337
|
+
message: err.message,
|
|
1338
|
+
...progress
|
|
1339
|
+
});
|
|
1340
|
+
else process.stderr.write(`Unexpected error: ${err.message}\n`);
|
|
1341
|
+
return exitAfterFlush(ExitCode.ApiError);
|
|
1342
|
+
}
|
|
1343
|
+
//#endregion
|
|
876
1344
|
//#region src/config/app-manifest.ts
|
|
877
1345
|
var ManifestError = class extends Error {
|
|
878
1346
|
kind;
|
|
@@ -1275,6 +1743,10 @@ async function uploadOne(uploadImpl, input) {
|
|
|
1275
1743
|
}
|
|
1276
1744
|
});
|
|
1277
1745
|
}
|
|
1746
|
+
function categoryHintFor(err) {
|
|
1747
|
+
if ((err.field ?? "") === "categoryIds" || /categoryIds/.test(err.message)) return "Tip: run `aitcc app categories --selectable` to list valid category ids.";
|
|
1748
|
+
return null;
|
|
1749
|
+
}
|
|
1278
1750
|
function emitManifestError(json, err) {
|
|
1279
1751
|
if (json) if (err.kind === "missing-required-field") emitJson({
|
|
1280
1752
|
ok: false,
|
|
@@ -1287,7 +1759,11 @@ function emitManifestError(json, err) {
|
|
|
1287
1759
|
reason: "invalid-config",
|
|
1288
1760
|
message: err.message
|
|
1289
1761
|
});
|
|
1290
|
-
else
|
|
1762
|
+
else {
|
|
1763
|
+
process.stderr.write(`${err.message}\n`);
|
|
1764
|
+
const hint = categoryHintFor(err);
|
|
1765
|
+
if (hint) process.stderr.write(`${hint}\n`);
|
|
1766
|
+
}
|
|
1291
1767
|
}
|
|
1292
1768
|
function emitImageDimensionError(json, err) {
|
|
1293
1769
|
if (json) if (err.reason === "mismatch") emitJson({
|
|
@@ -1489,6 +1965,7 @@ const showCommand$2 = defineCommand({
|
|
|
1489
1965
|
try {
|
|
1490
1966
|
const envelope = await fetchMiniAppWithDraft(workspaceId, appId, session.cookies);
|
|
1491
1967
|
const miniApp = pickMiniAppView(envelope, view);
|
|
1968
|
+
if (miniApp === null && view === "current" && envelope.draft !== null) process.stderr.write(`App ${appId} has no \`current\` view yet (not reviewed). Re-run with \`--view draft\` to see the pending record.\n`);
|
|
1492
1969
|
if (args.json) {
|
|
1493
1970
|
emitJson({
|
|
1494
1971
|
ok: true,
|
|
@@ -1500,7 +1977,7 @@ const showCommand$2 = defineCommand({
|
|
|
1500
1977
|
return exitAfterFlush(ExitCode.Ok);
|
|
1501
1978
|
}
|
|
1502
1979
|
if (miniApp === null) {
|
|
1503
|
-
if (view === "current" && envelope.draft !== null) process.stdout.write(`App ${appId} has no \`current\` view yet
|
|
1980
|
+
if (view === "current" && envelope.draft !== null) process.stdout.write(`App ${appId} has no \`current\` view yet.\n`);
|
|
1504
1981
|
else process.stdout.write(`App ${appId} has no data for view=${view}.\n`);
|
|
1505
1982
|
return exitAfterFlush(ExitCode.Ok);
|
|
1506
1983
|
}
|
|
@@ -1627,29 +2104,40 @@ const statusCommand = defineCommand({
|
|
|
1627
2104
|
const ctx = await resolveWorkspaceContext(args);
|
|
1628
2105
|
if (!ctx) return;
|
|
1629
2106
|
const { session, workspaceId } = ctx;
|
|
1630
|
-
const emit = (status) => {
|
|
2107
|
+
const emit = (status, service) => {
|
|
1631
2108
|
if (args.json) emitJson({
|
|
1632
2109
|
ok: true,
|
|
1633
2110
|
workspaceId,
|
|
1634
2111
|
appId,
|
|
1635
|
-
...status
|
|
2112
|
+
...status,
|
|
2113
|
+
serviceStatus: service?.serviceStatus ?? null,
|
|
2114
|
+
shutdownCandidateStatus: service?.shutdownCandidateStatus ?? null,
|
|
2115
|
+
scheduledShutdownAt: service?.scheduledShutdownAt ?? null
|
|
1636
2116
|
});
|
|
1637
|
-
else
|
|
2117
|
+
else {
|
|
2118
|
+
const svc = service ? ` [${service.serviceStatus}]` : "";
|
|
2119
|
+
process.stdout.write(`App ${appId} (ws ${workspaceId}): ${status.state}${svc}` + (status.rejectedMessage ? `\n reason: ${status.rejectedMessage}` : "") + (service?.scheduledShutdownAt ? `\n scheduled shutdown: ${service.scheduledShutdownAt}` : "") + "\n");
|
|
2120
|
+
}
|
|
1638
2121
|
};
|
|
1639
2122
|
try {
|
|
1640
2123
|
const once = async () => {
|
|
1641
|
-
|
|
2124
|
+
const [env, service] = await Promise.all([fetchMiniAppWithDraft(workspaceId, appId, session.cookies), fetchAppServiceStatus(workspaceId, appId, session.cookies).catch(() => null)]);
|
|
2125
|
+
return [deriveReviewState(env), service];
|
|
1642
2126
|
};
|
|
1643
2127
|
if (!args.watch) {
|
|
1644
|
-
|
|
2128
|
+
const [status, service] = await once();
|
|
2129
|
+
emit(status, service);
|
|
1645
2130
|
return exitAfterFlush(ExitCode.Ok);
|
|
1646
2131
|
}
|
|
1647
2132
|
let lastState = null;
|
|
2133
|
+
let lastServiceStatus = null;
|
|
1648
2134
|
while (true) {
|
|
1649
|
-
const status = await once();
|
|
1650
|
-
|
|
1651
|
-
|
|
2135
|
+
const [status, service] = await once();
|
|
2136
|
+
const svc = service?.serviceStatus ?? null;
|
|
2137
|
+
if (args.json) emit(status, service);
|
|
2138
|
+
else if (status.state !== lastState || svc !== lastServiceStatus) emit(status, service);
|
|
1652
2139
|
lastState = status.state;
|
|
2140
|
+
lastServiceStatus = svc;
|
|
1653
2141
|
if (status.state !== "under-review") return exitAfterFlush(ExitCode.Ok);
|
|
1654
2142
|
await new Promise((resolve) => setTimeout(resolve, intervalSec * 1e3));
|
|
1655
2143
|
}
|
|
@@ -1925,7 +2413,7 @@ const reportsCommand = defineCommand({
|
|
|
1925
2413
|
const bundlesCommand = defineCommand({
|
|
1926
2414
|
meta: {
|
|
1927
2415
|
name: "bundles",
|
|
1928
|
-
description: "Inspect upload bundles for a mini-app."
|
|
2416
|
+
description: "Inspect and manage upload bundles for a mini-app."
|
|
1929
2417
|
},
|
|
1930
2418
|
subCommands: {
|
|
1931
2419
|
ls: defineCommand({
|
|
@@ -2103,32 +2591,490 @@ const bundlesCommand = defineCommand({
|
|
|
2103
2591
|
return emitFailureFromError(args.json, err);
|
|
2104
2592
|
}
|
|
2105
2593
|
}
|
|
2106
|
-
})
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
name: "certs",
|
|
2112
|
-
description: "Inspect mTLS certificates for a mini-app."
|
|
2113
|
-
},
|
|
2114
|
-
subCommands: { ls: defineCommand({
|
|
2115
|
-
meta: {
|
|
2116
|
-
name: "ls",
|
|
2117
|
-
description: "List mTLS certificates issued for a mini-app."
|
|
2118
|
-
},
|
|
2119
|
-
args: {
|
|
2120
|
-
id: {
|
|
2121
|
-
type: "positional",
|
|
2122
|
-
description: "Mini-app ID.",
|
|
2123
|
-
required: true
|
|
2594
|
+
}),
|
|
2595
|
+
upload: defineCommand({
|
|
2596
|
+
meta: {
|
|
2597
|
+
name: "upload",
|
|
2598
|
+
description: "Upload an .ait bundle (initialize → PUT → complete [+ memo])."
|
|
2124
2599
|
},
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2600
|
+
args: {
|
|
2601
|
+
id: {
|
|
2602
|
+
type: "positional",
|
|
2603
|
+
description: "Mini-app ID.",
|
|
2604
|
+
required: true
|
|
2605
|
+
},
|
|
2606
|
+
path: {
|
|
2607
|
+
type: "positional",
|
|
2608
|
+
description: "Path to the .ait bundle file.",
|
|
2609
|
+
required: true
|
|
2610
|
+
},
|
|
2611
|
+
"deployment-id": {
|
|
2612
|
+
type: "string",
|
|
2613
|
+
description: "deploymentId embedded in the bundle (from app.json._metadata.deploymentId)."
|
|
2614
|
+
},
|
|
2615
|
+
memo: {
|
|
2616
|
+
type: "string",
|
|
2617
|
+
description: "Optional memo attached to this bundle version."
|
|
2618
|
+
},
|
|
2619
|
+
workspace: {
|
|
2620
|
+
type: "string",
|
|
2621
|
+
description: "Workspace ID. Defaults to the selected workspace."
|
|
2622
|
+
},
|
|
2623
|
+
"dry-run": {
|
|
2624
|
+
type: "boolean",
|
|
2625
|
+
description: "Validate inputs and show what would be sent, without touching the server.",
|
|
2626
|
+
default: false
|
|
2627
|
+
},
|
|
2628
|
+
json: {
|
|
2629
|
+
type: "boolean",
|
|
2630
|
+
description: "Emit machine-readable JSON.",
|
|
2631
|
+
default: false
|
|
2632
|
+
}
|
|
2128
2633
|
},
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2634
|
+
async run({ args }) {
|
|
2635
|
+
const appId = parseAppId(args.id);
|
|
2636
|
+
if (appId === null) {
|
|
2637
|
+
if (args.json) emitJson({
|
|
2638
|
+
ok: false,
|
|
2639
|
+
reason: "invalid-id",
|
|
2640
|
+
message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
|
|
2641
|
+
});
|
|
2642
|
+
else process.stderr.write(`app bundles upload: invalid id ${JSON.stringify(args.id)}\n`);
|
|
2643
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
2644
|
+
}
|
|
2645
|
+
const deploymentId = typeof args["deployment-id"] === "string" ? args["deployment-id"] : "";
|
|
2646
|
+
if (deploymentId === "") {
|
|
2647
|
+
if (args.json) emitJson({
|
|
2648
|
+
ok: false,
|
|
2649
|
+
reason: "missing-deployment-id",
|
|
2650
|
+
message: "--deployment-id is required; read app.json._metadata.deploymentId from inside the .ait"
|
|
2651
|
+
});
|
|
2652
|
+
else process.stderr.write("app bundles upload: --deployment-id <uuid> is required.\n The .ait bundle is a zip; read app.json inside and copy _metadata.deploymentId.\n");
|
|
2653
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
2654
|
+
}
|
|
2655
|
+
const filePath = typeof args.path === "string" ? args.path : "";
|
|
2656
|
+
let bytes;
|
|
2657
|
+
try {
|
|
2658
|
+
const { readFile } = await import("node:fs/promises");
|
|
2659
|
+
const buf = await readFile(filePath);
|
|
2660
|
+
bytes = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
2661
|
+
} catch (err) {
|
|
2662
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2663
|
+
if (args.json) emitJson({
|
|
2664
|
+
ok: false,
|
|
2665
|
+
reason: "file-unreadable",
|
|
2666
|
+
path: filePath,
|
|
2667
|
+
message
|
|
2668
|
+
});
|
|
2669
|
+
else process.stderr.write(`app bundles upload: cannot read ${filePath}: ${message}\n`);
|
|
2670
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
2671
|
+
}
|
|
2672
|
+
const ctx = await resolveWorkspaceContext(args);
|
|
2673
|
+
if (!ctx) return;
|
|
2674
|
+
const { session, workspaceId } = ctx;
|
|
2675
|
+
const memo = typeof args.memo === "string" && args.memo.length > 0 ? args.memo : void 0;
|
|
2676
|
+
if (args["dry-run"]) {
|
|
2677
|
+
if (args.json) emitJson({
|
|
2678
|
+
ok: true,
|
|
2679
|
+
dryRun: true,
|
|
2680
|
+
workspaceId,
|
|
2681
|
+
appId,
|
|
2682
|
+
deploymentId,
|
|
2683
|
+
bytes: bytes.byteLength,
|
|
2684
|
+
memo: memo ?? null
|
|
2685
|
+
});
|
|
2686
|
+
else process.stdout.write(`DRY RUN\n workspace ${workspaceId}\n appId ${appId}\n deploymentId ${deploymentId}\n bytes ${bytes.byteLength}\n memo ${memo ?? "(none)"}\n`);
|
|
2687
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
2688
|
+
}
|
|
2689
|
+
try {
|
|
2690
|
+
const init = await postDeploymentsInitialize(workspaceId, appId, deploymentId, session.cookies);
|
|
2691
|
+
if (init.reviewStatus !== "PREPARE") {
|
|
2692
|
+
if (args.json) emitJson({
|
|
2693
|
+
ok: false,
|
|
2694
|
+
reason: "bundle-not-prepare",
|
|
2695
|
+
workspaceId,
|
|
2696
|
+
appId,
|
|
2697
|
+
deploymentId,
|
|
2698
|
+
reviewStatus: init.reviewStatus,
|
|
2699
|
+
message: "이미 존재하는 버전이에요."
|
|
2700
|
+
});
|
|
2701
|
+
else process.stderr.write(`app bundles upload: deployment ${deploymentId} is already in state ${init.reviewStatus}; bundle upload refused.\n`);
|
|
2702
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
2703
|
+
}
|
|
2704
|
+
await putBundleToUploadUrl(init.uploadUrl, bytes);
|
|
2705
|
+
const bundle = await postDeploymentsComplete(workspaceId, appId, deploymentId, session.cookies);
|
|
2706
|
+
let memoApplied = false;
|
|
2707
|
+
if (memo !== void 0) {
|
|
2708
|
+
await postBundleMemo(workspaceId, appId, deploymentId, memo, session.cookies);
|
|
2709
|
+
memoApplied = true;
|
|
2710
|
+
}
|
|
2711
|
+
if (args.json) {
|
|
2712
|
+
emitJson({
|
|
2713
|
+
ok: true,
|
|
2714
|
+
workspaceId,
|
|
2715
|
+
appId,
|
|
2716
|
+
deploymentId,
|
|
2717
|
+
reviewStatus: init.reviewStatus,
|
|
2718
|
+
bundle,
|
|
2719
|
+
memoApplied
|
|
2720
|
+
});
|
|
2721
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
2722
|
+
}
|
|
2723
|
+
process.stdout.write(`Uploaded bundle for app ${appId} (ws ${workspaceId})\n deploymentId ${deploymentId}\n bytes ${bytes.byteLength}\n memo ${memoApplied ? "applied" : "(none)"}\n`);
|
|
2724
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
2725
|
+
} catch (err) {
|
|
2726
|
+
return emitFailureFromError(args.json, err);
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
}),
|
|
2730
|
+
review: defineCommand({
|
|
2731
|
+
meta: {
|
|
2732
|
+
name: "review",
|
|
2733
|
+
description: "Submit (or withdraw) an uploaded bundle for review."
|
|
2734
|
+
},
|
|
2735
|
+
args: {
|
|
2736
|
+
id: {
|
|
2737
|
+
type: "positional",
|
|
2738
|
+
description: "Mini-app ID.",
|
|
2739
|
+
required: true
|
|
2740
|
+
},
|
|
2741
|
+
"deployment-id": {
|
|
2742
|
+
type: "string",
|
|
2743
|
+
description: "deploymentId of the uploaded bundle."
|
|
2744
|
+
},
|
|
2745
|
+
"release-notes": {
|
|
2746
|
+
type: "string",
|
|
2747
|
+
description: "Release notes shown to the reviewer. Ignored with --withdraw."
|
|
2748
|
+
},
|
|
2749
|
+
withdraw: {
|
|
2750
|
+
type: "boolean",
|
|
2751
|
+
description: "Withdraw the existing review request instead of submitting a new one.",
|
|
2752
|
+
default: false
|
|
2753
|
+
},
|
|
2754
|
+
workspace: {
|
|
2755
|
+
type: "string",
|
|
2756
|
+
description: "Workspace ID. Defaults to the selected workspace."
|
|
2757
|
+
},
|
|
2758
|
+
json: {
|
|
2759
|
+
type: "boolean",
|
|
2760
|
+
description: "Emit machine-readable JSON.",
|
|
2761
|
+
default: false
|
|
2762
|
+
}
|
|
2763
|
+
},
|
|
2764
|
+
async run({ args }) {
|
|
2765
|
+
const appId = parseAppId(args.id);
|
|
2766
|
+
if (appId === null) {
|
|
2767
|
+
if (args.json) emitJson({
|
|
2768
|
+
ok: false,
|
|
2769
|
+
reason: "invalid-id",
|
|
2770
|
+
message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
|
|
2771
|
+
});
|
|
2772
|
+
else process.stderr.write(`app bundles review: invalid id ${JSON.stringify(args.id)}\n`);
|
|
2773
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
2774
|
+
}
|
|
2775
|
+
const deploymentId = typeof args["deployment-id"] === "string" ? args["deployment-id"] : "";
|
|
2776
|
+
if (deploymentId === "") {
|
|
2777
|
+
if (args.json) emitJson({
|
|
2778
|
+
ok: false,
|
|
2779
|
+
reason: "missing-deployment-id"
|
|
2780
|
+
});
|
|
2781
|
+
else process.stderr.write("app bundles review: --deployment-id <uuid> is required.\n");
|
|
2782
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
2783
|
+
}
|
|
2784
|
+
const withdraw = Boolean(args.withdraw);
|
|
2785
|
+
const releaseNotes = typeof args["release-notes"] === "string" ? args["release-notes"] : void 0;
|
|
2786
|
+
if (!withdraw && releaseNotes === void 0) {
|
|
2787
|
+
if (args.json) emitJson({
|
|
2788
|
+
ok: false,
|
|
2789
|
+
reason: "missing-release-notes"
|
|
2790
|
+
});
|
|
2791
|
+
else process.stderr.write("app bundles review: --release-notes <text> is required to submit for review.\n");
|
|
2792
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
2793
|
+
}
|
|
2794
|
+
const ctx = await resolveWorkspaceContext(args);
|
|
2795
|
+
if (!ctx) return;
|
|
2796
|
+
const { session, workspaceId } = ctx;
|
|
2797
|
+
try {
|
|
2798
|
+
if (withdraw) {
|
|
2799
|
+
const result = await postBundleReviewWithdrawal(workspaceId, appId, deploymentId, session.cookies);
|
|
2800
|
+
if (args.json) {
|
|
2801
|
+
emitJson({
|
|
2802
|
+
ok: true,
|
|
2803
|
+
workspaceId,
|
|
2804
|
+
appId,
|
|
2805
|
+
deploymentId,
|
|
2806
|
+
action: "withdraw",
|
|
2807
|
+
result
|
|
2808
|
+
});
|
|
2809
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
2810
|
+
}
|
|
2811
|
+
process.stdout.write(`Withdrew review for bundle ${deploymentId} (app ${appId}, ws ${workspaceId})\n`);
|
|
2812
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
2813
|
+
}
|
|
2814
|
+
const result = await postBundleReview({
|
|
2815
|
+
workspaceId,
|
|
2816
|
+
miniAppId: appId,
|
|
2817
|
+
deploymentId,
|
|
2818
|
+
releaseNotes: releaseNotes ?? ""
|
|
2819
|
+
}, session.cookies);
|
|
2820
|
+
if (args.json) {
|
|
2821
|
+
emitJson({
|
|
2822
|
+
ok: true,
|
|
2823
|
+
workspaceId,
|
|
2824
|
+
appId,
|
|
2825
|
+
deploymentId,
|
|
2826
|
+
action: "submit",
|
|
2827
|
+
result
|
|
2828
|
+
});
|
|
2829
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
2830
|
+
}
|
|
2831
|
+
const versionName = typeof result.versionName === "string" ? result.versionName : "";
|
|
2832
|
+
process.stdout.write(`Submitted bundle ${deploymentId} for review (app ${appId}, ws ${workspaceId})` + (versionName ? ` — version ${versionName}` : "") + "\n");
|
|
2833
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
2834
|
+
} catch (err) {
|
|
2835
|
+
return emitFailureFromError(args.json, err);
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
}),
|
|
2839
|
+
release: defineCommand({
|
|
2840
|
+
meta: {
|
|
2841
|
+
name: "release",
|
|
2842
|
+
description: "Release (publish) an APPROVED bundle to end users."
|
|
2843
|
+
},
|
|
2844
|
+
args: {
|
|
2845
|
+
id: {
|
|
2846
|
+
type: "positional",
|
|
2847
|
+
description: "Mini-app ID.",
|
|
2848
|
+
required: true
|
|
2849
|
+
},
|
|
2850
|
+
"deployment-id": {
|
|
2851
|
+
type: "string",
|
|
2852
|
+
description: "deploymentId of the APPROVED bundle to publish."
|
|
2853
|
+
},
|
|
2854
|
+
confirm: {
|
|
2855
|
+
type: "boolean",
|
|
2856
|
+
description: "Required to actually release — without it, the command refuses.",
|
|
2857
|
+
default: false
|
|
2858
|
+
},
|
|
2859
|
+
workspace: {
|
|
2860
|
+
type: "string",
|
|
2861
|
+
description: "Workspace ID. Defaults to the selected workspace."
|
|
2862
|
+
},
|
|
2863
|
+
json: {
|
|
2864
|
+
type: "boolean",
|
|
2865
|
+
description: "Emit machine-readable JSON.",
|
|
2866
|
+
default: false
|
|
2867
|
+
}
|
|
2868
|
+
},
|
|
2869
|
+
async run({ args }) {
|
|
2870
|
+
const appId = parseAppId(args.id);
|
|
2871
|
+
if (appId === null) {
|
|
2872
|
+
if (args.json) emitJson({
|
|
2873
|
+
ok: false,
|
|
2874
|
+
reason: "invalid-id",
|
|
2875
|
+
message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
|
|
2876
|
+
});
|
|
2877
|
+
else process.stderr.write(`app bundles release: invalid id ${JSON.stringify(args.id)}\n`);
|
|
2878
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
2879
|
+
}
|
|
2880
|
+
const deploymentId = typeof args["deployment-id"] === "string" ? args["deployment-id"] : "";
|
|
2881
|
+
if (deploymentId === "") {
|
|
2882
|
+
if (args.json) emitJson({
|
|
2883
|
+
ok: false,
|
|
2884
|
+
reason: "missing-deployment-id"
|
|
2885
|
+
});
|
|
2886
|
+
else process.stderr.write("app bundles release: --deployment-id <uuid> is required.\n");
|
|
2887
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
2888
|
+
}
|
|
2889
|
+
if (!args.confirm) {
|
|
2890
|
+
if (args.json) emitJson({
|
|
2891
|
+
ok: false,
|
|
2892
|
+
reason: "not-confirmed",
|
|
2893
|
+
message: "release is destructive; pass --confirm to proceed"
|
|
2894
|
+
});
|
|
2895
|
+
else process.stderr.write("app bundles release: this publishes the bundle to end users.\n Re-run with --confirm to proceed.\n");
|
|
2896
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
2897
|
+
}
|
|
2898
|
+
const ctx = await resolveWorkspaceContext(args);
|
|
2899
|
+
if (!ctx) return;
|
|
2900
|
+
const { session, workspaceId } = ctx;
|
|
2901
|
+
try {
|
|
2902
|
+
const result = await postBundleRelease({
|
|
2903
|
+
workspaceId,
|
|
2904
|
+
miniAppId: appId,
|
|
2905
|
+
deploymentId
|
|
2906
|
+
}, session.cookies);
|
|
2907
|
+
if (args.json) {
|
|
2908
|
+
emitJson({
|
|
2909
|
+
ok: true,
|
|
2910
|
+
workspaceId,
|
|
2911
|
+
appId,
|
|
2912
|
+
deploymentId,
|
|
2913
|
+
result
|
|
2914
|
+
});
|
|
2915
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
2916
|
+
}
|
|
2917
|
+
process.stdout.write(`Released bundle ${deploymentId} for app ${appId} (ws ${workspaceId})\n`);
|
|
2918
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
2919
|
+
} catch (err) {
|
|
2920
|
+
return emitFailureFromError(args.json, err);
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
}),
|
|
2924
|
+
"test-push": defineCommand({
|
|
2925
|
+
meta: {
|
|
2926
|
+
name: "test-push",
|
|
2927
|
+
description: "Send a test push so the uploader can open this bundle on their device."
|
|
2928
|
+
},
|
|
2929
|
+
args: {
|
|
2930
|
+
id: {
|
|
2931
|
+
type: "positional",
|
|
2932
|
+
description: "Mini-app ID.",
|
|
2933
|
+
required: true
|
|
2934
|
+
},
|
|
2935
|
+
"deployment-id": {
|
|
2936
|
+
type: "string",
|
|
2937
|
+
description: "deploymentId of the bundle to test."
|
|
2938
|
+
},
|
|
2939
|
+
workspace: {
|
|
2940
|
+
type: "string",
|
|
2941
|
+
description: "Workspace ID. Defaults to the selected workspace."
|
|
2942
|
+
},
|
|
2943
|
+
json: {
|
|
2944
|
+
type: "boolean",
|
|
2945
|
+
description: "Emit machine-readable JSON.",
|
|
2946
|
+
default: false
|
|
2947
|
+
}
|
|
2948
|
+
},
|
|
2949
|
+
async run({ args }) {
|
|
2950
|
+
const appId = parseAppId(args.id);
|
|
2951
|
+
if (appId === null) {
|
|
2952
|
+
if (args.json) emitJson({
|
|
2953
|
+
ok: false,
|
|
2954
|
+
reason: "invalid-id",
|
|
2955
|
+
message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
|
|
2956
|
+
});
|
|
2957
|
+
else process.stderr.write(`app bundles test-push: invalid id ${JSON.stringify(args.id)}\n`);
|
|
2958
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
2959
|
+
}
|
|
2960
|
+
const deploymentId = typeof args["deployment-id"] === "string" ? args["deployment-id"] : "";
|
|
2961
|
+
if (deploymentId === "") {
|
|
2962
|
+
if (args.json) emitJson({
|
|
2963
|
+
ok: false,
|
|
2964
|
+
reason: "missing-deployment-id"
|
|
2965
|
+
});
|
|
2966
|
+
else process.stderr.write("app bundles test-push: --deployment-id <uuid> is required.\n");
|
|
2967
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
2968
|
+
}
|
|
2969
|
+
const ctx = await resolveWorkspaceContext(args);
|
|
2970
|
+
if (!ctx) return;
|
|
2971
|
+
const { session, workspaceId } = ctx;
|
|
2972
|
+
try {
|
|
2973
|
+
const result = await postBundleTestPush(workspaceId, appId, deploymentId, session.cookies);
|
|
2974
|
+
if (args.json) {
|
|
2975
|
+
emitJson({
|
|
2976
|
+
ok: true,
|
|
2977
|
+
workspaceId,
|
|
2978
|
+
appId,
|
|
2979
|
+
deploymentId,
|
|
2980
|
+
result
|
|
2981
|
+
});
|
|
2982
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
2983
|
+
}
|
|
2984
|
+
process.stdout.write(`Sent test push for bundle ${deploymentId} (app ${appId})\n`);
|
|
2985
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
2986
|
+
} catch (err) {
|
|
2987
|
+
return emitFailureFromError(args.json, err);
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
}),
|
|
2991
|
+
"test-links": defineCommand({
|
|
2992
|
+
meta: {
|
|
2993
|
+
name: "test-links",
|
|
2994
|
+
description: "Show per-device test URLs for the mini-app."
|
|
2995
|
+
},
|
|
2996
|
+
args: {
|
|
2997
|
+
id: {
|
|
2998
|
+
type: "positional",
|
|
2999
|
+
description: "Mini-app ID.",
|
|
3000
|
+
required: true
|
|
3001
|
+
},
|
|
3002
|
+
workspace: {
|
|
3003
|
+
type: "string",
|
|
3004
|
+
description: "Workspace ID. Defaults to the selected workspace."
|
|
3005
|
+
},
|
|
3006
|
+
json: {
|
|
3007
|
+
type: "boolean",
|
|
3008
|
+
description: "Emit machine-readable JSON.",
|
|
3009
|
+
default: false
|
|
3010
|
+
}
|
|
3011
|
+
},
|
|
3012
|
+
async run({ args }) {
|
|
3013
|
+
const appId = parseAppId(args.id);
|
|
3014
|
+
if (appId === null) {
|
|
3015
|
+
if (args.json) emitJson({
|
|
3016
|
+
ok: false,
|
|
3017
|
+
reason: "invalid-id",
|
|
3018
|
+
message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
|
|
3019
|
+
});
|
|
3020
|
+
else process.stderr.write(`app bundles test-links: invalid id ${JSON.stringify(args.id)}\n`);
|
|
3021
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
3022
|
+
}
|
|
3023
|
+
const ctx = await resolveWorkspaceContext(args);
|
|
3024
|
+
if (!ctx) return;
|
|
3025
|
+
const { session, workspaceId } = ctx;
|
|
3026
|
+
try {
|
|
3027
|
+
const links = await fetchBundleTestLinks(workspaceId, appId, session.cookies);
|
|
3028
|
+
if (args.json) {
|
|
3029
|
+
emitJson({
|
|
3030
|
+
ok: true,
|
|
3031
|
+
workspaceId,
|
|
3032
|
+
appId,
|
|
3033
|
+
links
|
|
3034
|
+
});
|
|
3035
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
3036
|
+
}
|
|
3037
|
+
const keys = Object.keys(links);
|
|
3038
|
+
if (keys.length === 0) {
|
|
3039
|
+
process.stdout.write(`App ${appId} (ws ${workspaceId}): no test links available\n`);
|
|
3040
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
3041
|
+
}
|
|
3042
|
+
process.stdout.write(`App ${appId} (ws ${workspaceId}):\n`);
|
|
3043
|
+
for (const k of keys) {
|
|
3044
|
+
const v = links[k];
|
|
3045
|
+
process.stdout.write(` ${k}\t${typeof v === "string" ? v : JSON.stringify(v)}\n`);
|
|
3046
|
+
}
|
|
3047
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
3048
|
+
} catch (err) {
|
|
3049
|
+
return emitFailureFromError(args.json, err);
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
})
|
|
3053
|
+
}
|
|
3054
|
+
});
|
|
3055
|
+
const certsCommand = defineCommand({
|
|
3056
|
+
meta: {
|
|
3057
|
+
name: "certs",
|
|
3058
|
+
description: "Inspect mTLS certificates for a mini-app."
|
|
3059
|
+
},
|
|
3060
|
+
subCommands: { ls: defineCommand({
|
|
3061
|
+
meta: {
|
|
3062
|
+
name: "ls",
|
|
3063
|
+
description: "List mTLS certificates issued for a mini-app."
|
|
3064
|
+
},
|
|
3065
|
+
args: {
|
|
3066
|
+
id: {
|
|
3067
|
+
type: "positional",
|
|
3068
|
+
description: "Mini-app ID.",
|
|
3069
|
+
required: true
|
|
3070
|
+
},
|
|
3071
|
+
workspace: {
|
|
3072
|
+
type: "string",
|
|
3073
|
+
description: "Workspace ID. Defaults to the selected workspace."
|
|
3074
|
+
},
|
|
3075
|
+
json: {
|
|
3076
|
+
type: "boolean",
|
|
3077
|
+
description: "Emit machine-readable JSON.",
|
|
2132
3078
|
default: false
|
|
2133
3079
|
}
|
|
2134
3080
|
},
|
|
@@ -2945,10 +3891,255 @@ const appCommand = defineCommand({
|
|
|
2945
3891
|
...args.config !== void 0 ? { config: args.config } : {}
|
|
2946
3892
|
});
|
|
2947
3893
|
}
|
|
3894
|
+
}),
|
|
3895
|
+
deploy: defineCommand({
|
|
3896
|
+
meta: {
|
|
3897
|
+
name: "deploy",
|
|
3898
|
+
description: "Upload a bundle, optionally request review, optionally release. Auto-detects deploymentId from the .ait if --deployment-id is omitted."
|
|
3899
|
+
},
|
|
3900
|
+
args: {
|
|
3901
|
+
path: {
|
|
3902
|
+
type: "positional",
|
|
3903
|
+
description: "Path to the .ait bundle file.",
|
|
3904
|
+
required: true
|
|
3905
|
+
},
|
|
3906
|
+
app: {
|
|
3907
|
+
type: "string",
|
|
3908
|
+
description: "Mini-app ID. Required — no top-level \"selected app\" concept yet."
|
|
3909
|
+
},
|
|
3910
|
+
"deployment-id": {
|
|
3911
|
+
type: "string",
|
|
3912
|
+
description: "deploymentId of the bundle. Defaults to app.json._metadata.deploymentId inside the .ait."
|
|
3913
|
+
},
|
|
3914
|
+
memo: {
|
|
3915
|
+
type: "string",
|
|
3916
|
+
description: "Optional memo attached to the uploaded bundle."
|
|
3917
|
+
},
|
|
3918
|
+
"request-review": {
|
|
3919
|
+
type: "boolean",
|
|
3920
|
+
description: "After upload, submit the bundle for review.",
|
|
3921
|
+
default: false
|
|
3922
|
+
},
|
|
3923
|
+
"release-notes": {
|
|
3924
|
+
type: "string",
|
|
3925
|
+
description: "Release notes for the review request. Required with --request-review."
|
|
3926
|
+
},
|
|
3927
|
+
release: {
|
|
3928
|
+
type: "boolean",
|
|
3929
|
+
description: "After review submit, publish the bundle. Requires the bundle to already be APPROVED; typically used on a second `app deploy` run.",
|
|
3930
|
+
default: false
|
|
3931
|
+
},
|
|
3932
|
+
confirm: {
|
|
3933
|
+
type: "boolean",
|
|
3934
|
+
description: "Required with --release — confirms the destructive publish step.",
|
|
3935
|
+
default: false
|
|
3936
|
+
},
|
|
3937
|
+
workspace: {
|
|
3938
|
+
type: "string",
|
|
3939
|
+
description: "Workspace ID. Defaults to the selected workspace."
|
|
3940
|
+
},
|
|
3941
|
+
"dry-run": {
|
|
3942
|
+
type: "boolean",
|
|
3943
|
+
description: "Print the planned pipeline without touching the server.",
|
|
3944
|
+
default: false
|
|
3945
|
+
},
|
|
3946
|
+
json: {
|
|
3947
|
+
type: "boolean",
|
|
3948
|
+
description: "Emit machine-readable JSON.",
|
|
3949
|
+
default: false
|
|
3950
|
+
}
|
|
3951
|
+
},
|
|
3952
|
+
async run({ args }) {
|
|
3953
|
+
await runDeploy({
|
|
3954
|
+
path: typeof args.path === "string" ? args.path : "",
|
|
3955
|
+
app: typeof args.app === "string" ? args.app : void 0,
|
|
3956
|
+
json: args.json,
|
|
3957
|
+
dryRun: args["dry-run"],
|
|
3958
|
+
requestReview: args["request-review"],
|
|
3959
|
+
release: args.release,
|
|
3960
|
+
confirm: args.confirm,
|
|
3961
|
+
...args["deployment-id"] !== void 0 ? { deploymentId: args["deployment-id"] } : {},
|
|
3962
|
+
...args.memo !== void 0 ? { memo: args.memo } : {},
|
|
3963
|
+
...args["release-notes"] !== void 0 ? { releaseNotes: args["release-notes"] } : {},
|
|
3964
|
+
...args.workspace !== void 0 ? { workspace: args.workspace } : {}
|
|
3965
|
+
});
|
|
3966
|
+
}
|
|
2948
3967
|
})
|
|
2949
3968
|
}
|
|
2950
3969
|
});
|
|
2951
3970
|
//#endregion
|
|
3971
|
+
//#region src/commands/completion.ts
|
|
3972
|
+
const TOP_LEVEL = [
|
|
3973
|
+
"whoami",
|
|
3974
|
+
"login",
|
|
3975
|
+
"logout",
|
|
3976
|
+
"upgrade",
|
|
3977
|
+
"workspace",
|
|
3978
|
+
"app",
|
|
3979
|
+
"members",
|
|
3980
|
+
"keys",
|
|
3981
|
+
"notices",
|
|
3982
|
+
"me",
|
|
3983
|
+
"completion"
|
|
3984
|
+
];
|
|
3985
|
+
const SUB_COMMANDS = {
|
|
3986
|
+
workspace: [
|
|
3987
|
+
"ls",
|
|
3988
|
+
"partner",
|
|
3989
|
+
"segments",
|
|
3990
|
+
"show",
|
|
3991
|
+
"terms",
|
|
3992
|
+
"use"
|
|
3993
|
+
],
|
|
3994
|
+
app: [
|
|
3995
|
+
"bundles",
|
|
3996
|
+
"categories",
|
|
3997
|
+
"certs",
|
|
3998
|
+
"events",
|
|
3999
|
+
"ls",
|
|
4000
|
+
"messages",
|
|
4001
|
+
"metrics",
|
|
4002
|
+
"ratings",
|
|
4003
|
+
"register",
|
|
4004
|
+
"reports",
|
|
4005
|
+
"service-status",
|
|
4006
|
+
"share-rewards",
|
|
4007
|
+
"show",
|
|
4008
|
+
"status",
|
|
4009
|
+
"templates"
|
|
4010
|
+
],
|
|
4011
|
+
notices: [
|
|
4012
|
+
"categories",
|
|
4013
|
+
"ls",
|
|
4014
|
+
"show"
|
|
4015
|
+
],
|
|
4016
|
+
me: ["terms"],
|
|
4017
|
+
completion: [
|
|
4018
|
+
"bash",
|
|
4019
|
+
"zsh",
|
|
4020
|
+
"fish"
|
|
4021
|
+
]
|
|
4022
|
+
};
|
|
4023
|
+
function emitBash() {
|
|
4024
|
+
const top = TOP_LEVEL.join(" ");
|
|
4025
|
+
const cases = [];
|
|
4026
|
+
for (const [ns, subs] of Object.entries(SUB_COMMANDS)) {
|
|
4027
|
+
cases.push(` ${ns})`);
|
|
4028
|
+
cases.push(` COMPREPLY=( $(compgen -W "${subs.join(" ")}" -- "$cur") )`);
|
|
4029
|
+
cases.push(" return 0 ;;");
|
|
4030
|
+
}
|
|
4031
|
+
return `# bash completion for aitcc. Generated by \`aitcc completion bash\`.
|
|
4032
|
+
# Install:
|
|
4033
|
+
# aitcc completion bash > /etc/bash_completion.d/aitcc
|
|
4034
|
+
# # or add to ~/.bashrc:
|
|
4035
|
+
# source <(aitcc completion bash)
|
|
4036
|
+
|
|
4037
|
+
_aitcc_completion() {
|
|
4038
|
+
local cur prev
|
|
4039
|
+
COMPREPLY=()
|
|
4040
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
4041
|
+
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
4042
|
+
|
|
4043
|
+
if [[ $COMP_CWORD -eq 1 ]]; then
|
|
4044
|
+
COMPREPLY=( $(compgen -W "${top}" -- "$cur") )
|
|
4045
|
+
return 0
|
|
4046
|
+
fi
|
|
4047
|
+
|
|
4048
|
+
case "\${COMP_WORDS[1]}" in
|
|
4049
|
+
${cases.join("\n")}
|
|
4050
|
+
esac
|
|
4051
|
+
|
|
4052
|
+
return 0
|
|
4053
|
+
}
|
|
4054
|
+
|
|
4055
|
+
complete -F _aitcc_completion aitcc
|
|
4056
|
+
`;
|
|
4057
|
+
}
|
|
4058
|
+
function emitZsh() {
|
|
4059
|
+
const top = TOP_LEVEL.join(" ");
|
|
4060
|
+
const nsClauses = [];
|
|
4061
|
+
for (const [ns, subs] of Object.entries(SUB_COMMANDS)) nsClauses.push(` ${ns}) _values 'subcommand' ${subs.map((s) => `'${s}'`).join(" ")} ;;`);
|
|
4062
|
+
return `#compdef aitcc
|
|
4063
|
+
# zsh completion for aitcc. Generated by \`aitcc completion zsh\`.
|
|
4064
|
+
# Install:
|
|
4065
|
+
# aitcc completion zsh > "\${fpath[1]}/_aitcc"
|
|
4066
|
+
# # then in a fresh shell (or run \`autoload -U compinit && compinit\`)
|
|
4067
|
+
|
|
4068
|
+
_aitcc() {
|
|
4069
|
+
local -a commands
|
|
4070
|
+
commands=(${TOP_LEVEL.map((c) => `'${c}'`).join(" ")})
|
|
4071
|
+
|
|
4072
|
+
if (( CURRENT == 2 )); then
|
|
4073
|
+
_values 'command' ${TOP_LEVEL.map((c) => `'${c}'`).join(" ")}
|
|
4074
|
+
return
|
|
4075
|
+
fi
|
|
4076
|
+
|
|
4077
|
+
case "\${words[2]}" in
|
|
4078
|
+
${nsClauses.join("\n")}
|
|
4079
|
+
esac
|
|
4080
|
+
}
|
|
4081
|
+
|
|
4082
|
+
_aitcc "$@"
|
|
4083
|
+
# ${top}
|
|
4084
|
+
`;
|
|
4085
|
+
}
|
|
4086
|
+
function emitFish() {
|
|
4087
|
+
const lines = [
|
|
4088
|
+
"# fish completion for aitcc. Generated by `aitcc completion fish`.",
|
|
4089
|
+
"# Install:",
|
|
4090
|
+
"# aitcc completion fish > ~/.config/fish/completions/aitcc.fish",
|
|
4091
|
+
""
|
|
4092
|
+
];
|
|
4093
|
+
for (const c of TOP_LEVEL) lines.push(`complete -c aitcc -n "__fish_use_subcommand" -a "${c}" -f`);
|
|
4094
|
+
for (const [ns, subs] of Object.entries(SUB_COMMANDS)) for (const s of subs) lines.push(`complete -c aitcc -n "__fish_seen_subcommand_from ${ns}" -a "${s}" -f`);
|
|
4095
|
+
return `${lines.join("\n")}\n`;
|
|
4096
|
+
}
|
|
4097
|
+
const completionCommand = defineCommand({
|
|
4098
|
+
meta: {
|
|
4099
|
+
name: "completion",
|
|
4100
|
+
description: "Emit a shell completion script for bash, zsh, or fish."
|
|
4101
|
+
},
|
|
4102
|
+
args: {
|
|
4103
|
+
shell: {
|
|
4104
|
+
type: "positional",
|
|
4105
|
+
description: "Target shell: bash, zsh, or fish.",
|
|
4106
|
+
required: false
|
|
4107
|
+
},
|
|
4108
|
+
json: {
|
|
4109
|
+
type: "boolean",
|
|
4110
|
+
description: "Emit { ok: false, reason: 'invalid-shell' } on bad input.",
|
|
4111
|
+
default: false
|
|
4112
|
+
}
|
|
4113
|
+
},
|
|
4114
|
+
async run({ args }) {
|
|
4115
|
+
const raw = args.shell === void 0 ? "" : String(args.shell).toLowerCase();
|
|
4116
|
+
if (raw === "bash") {
|
|
4117
|
+
process.stdout.write(emitBash());
|
|
4118
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
4119
|
+
}
|
|
4120
|
+
if (raw === "zsh") {
|
|
4121
|
+
process.stdout.write(emitZsh());
|
|
4122
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
4123
|
+
}
|
|
4124
|
+
if (raw === "fish") {
|
|
4125
|
+
process.stdout.write(emitFish());
|
|
4126
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
4127
|
+
}
|
|
4128
|
+
if (args.json) emitJson({
|
|
4129
|
+
ok: false,
|
|
4130
|
+
reason: "invalid-shell",
|
|
4131
|
+
allowed: [
|
|
4132
|
+
"bash",
|
|
4133
|
+
"zsh",
|
|
4134
|
+
"fish"
|
|
4135
|
+
],
|
|
4136
|
+
message: `completion shell must be one of: bash, zsh, fish (got ${JSON.stringify(raw)})`
|
|
4137
|
+
});
|
|
4138
|
+
else process.stderr.write("Usage: aitcc completion <bash|zsh|fish>\n\nExamples:\n aitcc completion bash > /etc/bash_completion.d/aitcc\n aitcc completion zsh > \"${fpath[1]}/_aitcc\"\n aitcc completion fish > ~/.config/fish/completions/aitcc.fish\n");
|
|
4139
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
4140
|
+
}
|
|
4141
|
+
});
|
|
4142
|
+
//#endregion
|
|
2952
4143
|
//#region src/api/api-keys.ts
|
|
2953
4144
|
const BASE$3 = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole";
|
|
2954
4145
|
async function fetchApiKeys(workspaceId, cookies, opts = {}) {
|
|
@@ -4219,7 +5410,7 @@ function resolveVersion() {
|
|
|
4219
5410
|
if (typeof injected === "string" && injected.length > 0) return injected;
|
|
4220
5411
|
} catch {}
|
|
4221
5412
|
try {
|
|
4222
|
-
return "0.1.
|
|
5413
|
+
return "0.1.15";
|
|
4223
5414
|
} catch {}
|
|
4224
5415
|
return "0.0.0-dev";
|
|
4225
5416
|
}
|
|
@@ -5126,7 +6317,8 @@ runMain(defineCommand({
|
|
|
5126
6317
|
members: membersCommand,
|
|
5127
6318
|
keys: keysCommand,
|
|
5128
6319
|
notices: noticesCommand,
|
|
5129
|
-
me: meCommand
|
|
6320
|
+
me: meCommand,
|
|
6321
|
+
completion: completionCommand
|
|
5130
6322
|
}
|
|
5131
6323
|
}));
|
|
5132
6324
|
//#endregion
|