@ait-co/console-cli 0.1.14 → 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 +1026 -27
- 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;
|
|
@@ -1945,7 +2413,7 @@ const reportsCommand = defineCommand({
|
|
|
1945
2413
|
const bundlesCommand = defineCommand({
|
|
1946
2414
|
meta: {
|
|
1947
2415
|
name: "bundles",
|
|
1948
|
-
description: "Inspect upload bundles for a mini-app."
|
|
2416
|
+
description: "Inspect and manage upload bundles for a mini-app."
|
|
1949
2417
|
},
|
|
1950
2418
|
subCommands: {
|
|
1951
2419
|
ls: defineCommand({
|
|
@@ -2123,33 +2591,491 @@ const bundlesCommand = defineCommand({
|
|
|
2123
2591
|
return emitFailureFromError(args.json, err);
|
|
2124
2592
|
}
|
|
2125
2593
|
}
|
|
2126
|
-
})
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
name: "certs",
|
|
2132
|
-
description: "Inspect mTLS certificates for a mini-app."
|
|
2133
|
-
},
|
|
2134
|
-
subCommands: { ls: defineCommand({
|
|
2135
|
-
meta: {
|
|
2136
|
-
name: "ls",
|
|
2137
|
-
description: "List mTLS certificates issued for a mini-app."
|
|
2138
|
-
},
|
|
2139
|
-
args: {
|
|
2140
|
-
id: {
|
|
2141
|
-
type: "positional",
|
|
2142
|
-
description: "Mini-app ID.",
|
|
2143
|
-
required: true
|
|
2594
|
+
}),
|
|
2595
|
+
upload: defineCommand({
|
|
2596
|
+
meta: {
|
|
2597
|
+
name: "upload",
|
|
2598
|
+
description: "Upload an .ait bundle (initialize → PUT → complete [+ memo])."
|
|
2144
2599
|
},
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
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
|
+
}
|
|
2148
2633
|
},
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
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.",
|
|
3078
|
+
default: false
|
|
2153
3079
|
}
|
|
2154
3080
|
},
|
|
2155
3081
|
async run({ args }) {
|
|
@@ -2965,6 +3891,79 @@ const appCommand = defineCommand({
|
|
|
2965
3891
|
...args.config !== void 0 ? { config: args.config } : {}
|
|
2966
3892
|
});
|
|
2967
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
|
+
}
|
|
2968
3967
|
})
|
|
2969
3968
|
}
|
|
2970
3969
|
});
|
|
@@ -4411,7 +5410,7 @@ function resolveVersion() {
|
|
|
4411
5410
|
if (typeof injected === "string" && injected.length > 0) return injected;
|
|
4412
5411
|
} catch {}
|
|
4413
5412
|
try {
|
|
4414
|
-
return "0.1.
|
|
5413
|
+
return "0.1.15";
|
|
4415
5414
|
} catch {}
|
|
4416
5415
|
return "0.0.0-dev";
|
|
4417
5416
|
}
|