@ait-co/console-cli 0.1.34 → 0.1.36
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/README.en.md +2 -2
- package/README.md +2 -2
- package/dist/cli.mjs +126 -21
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -22,7 +22,7 @@ The installer detects OS (`uname -s`) and arch (`uname -m`), downloads the match
|
|
|
22
22
|
Pin a specific version:
|
|
23
23
|
|
|
24
24
|
```sh
|
|
25
|
-
curl -fsSL https://raw.githubusercontent.com/apps-in-toss-community/console-cli/main/install.sh | AITCC_VERSION=v0.1.
|
|
25
|
+
curl -fsSL https://raw.githubusercontent.com/apps-in-toss-community/console-cli/main/install.sh | AITCC_VERSION=v0.1.34 sh
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
Override the install directory with `AITCC_INSTALL_DIR=/custom/path` (default `$HOME/.local/bin`).
|
|
@@ -128,7 +128,7 @@ For one-shot CI runs (e.g. `aitcc app deploy` from a workflow), seed the runner
|
|
|
128
128
|
|
|
129
129
|
```sh
|
|
130
130
|
# Desktop (already logged in):
|
|
131
|
-
aitcc auth export --format env >> $GITHUB_ENV # or
|
|
131
|
+
aitcc auth export --format env >> $GITHUB_ENV # or store as a secret
|
|
132
132
|
# CI (with the secret exposed as $AITCC_SESSION):
|
|
133
133
|
aitcc app deploy ./aitc-sdk-example.ait --json
|
|
134
134
|
```
|
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ installer가 OS(`uname -s`)와 아키텍처(`uname -m`)를 자동 감지해 최
|
|
|
22
22
|
특정 버전 고정:
|
|
23
23
|
|
|
24
24
|
```sh
|
|
25
|
-
curl -fsSL https://raw.githubusercontent.com/apps-in-toss-community/console-cli/main/install.sh | AITCC_VERSION=v0.1.
|
|
25
|
+
curl -fsSL https://raw.githubusercontent.com/apps-in-toss-community/console-cli/main/install.sh | AITCC_VERSION=v0.1.34 sh
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
설치 경로를 바꾸려면 `AITCC_INSTALL_DIR=/custom/path` (기본 `$HOME/.local/bin`).
|
|
@@ -124,7 +124,7 @@ OS 키체인 대신 plain `0600` 파일을 쓰는 이유는 [CLAUDE.md](./CLAUDE
|
|
|
124
124
|
|
|
125
125
|
## CI/CD 통합
|
|
126
126
|
|
|
127
|
-
워크플로의
|
|
127
|
+
워크플로의 일회성 실행(`aitcc app deploy` 등)을 위해 desktop에서 캡처한 세션을 runner에 주입합니다:
|
|
128
128
|
|
|
129
129
|
```sh
|
|
130
130
|
# Desktop (이미 로그인된 상태):
|
package/dist/cli.mjs
CHANGED
|
@@ -7,8 +7,8 @@ import { isMap, parse, parseDocument } from "yaml";
|
|
|
7
7
|
import { unzipSync } from "fflate";
|
|
8
8
|
import { checkbox, confirm, editor, input, password, select } from "@inquirer/prompts";
|
|
9
9
|
import { imageSize } from "image-size";
|
|
10
|
-
import { execFile, spawn } from "node:child_process";
|
|
11
|
-
import { constants, createReadStream, existsSync } from "node:fs";
|
|
10
|
+
import { execFile, execFileSync, spawn } from "node:child_process";
|
|
11
|
+
import { constants, createReadStream, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { createInterface } from "node:readline/promises";
|
|
13
13
|
import { promisify } from "node:util";
|
|
14
14
|
import { createHash } from "node:crypto";
|
|
@@ -682,14 +682,15 @@ async function postBundleTestPush(workspaceId, miniAppId, deploymentId, cookies,
|
|
|
682
682
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
683
683
|
return raw;
|
|
684
684
|
}
|
|
685
|
-
async function fetchBundleTestLinks(workspaceId, miniAppId, cookies, opts = {}) {
|
|
685
|
+
async function fetchBundleTestLinks(workspaceId, miniAppId, deploymentId, cookies, opts = {}) {
|
|
686
686
|
const raw = await requestConsoleApi({
|
|
687
|
-
url: `${BASE$4}/workspaces/${workspaceId}/mini-app/${miniAppId}/bundles/test-links`,
|
|
687
|
+
url: `${BASE$4}/workspaces/${workspaceId}/mini-app/${miniAppId}/bundles/test-links?deploymentId=${encodeURIComponent(deploymentId)}`,
|
|
688
688
|
cookies,
|
|
689
689
|
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
690
690
|
});
|
|
691
|
-
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
692
|
-
|
|
691
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throw new Error(`Unexpected test-links shape for app=${miniAppId}`);
|
|
692
|
+
const data = raw;
|
|
693
|
+
return { linkUri: typeof data.linkUri === "string" ? data.linkUri : "" };
|
|
693
694
|
}
|
|
694
695
|
async function createMiniApp(workspaceId, payload, cookies, opts = {}) {
|
|
695
696
|
return normalizeCreateResult(await requestConsoleApi({
|
|
@@ -3382,7 +3383,8 @@ function formatDiffValue(v) {
|
|
|
3382
3383
|
return String(v);
|
|
3383
3384
|
}
|
|
3384
3385
|
function describeServiceStatus(s) {
|
|
3385
|
-
if (s === "PREPARE") return "PREPARE (출시 전 —
|
|
3386
|
+
if (s === "PREPARE") return "PREPARE (출시 전 — 번들 출시 검수 미통과)";
|
|
3387
|
+
if (s === "OPENED") return "OPENED (출시 중)";
|
|
3386
3388
|
if (s === "RUNNING") return "RUNNING (출시 중)";
|
|
3387
3389
|
if (s === "STOPPED") return "STOPPED (운영 중지)";
|
|
3388
3390
|
return s;
|
|
@@ -3583,6 +3585,9 @@ function deriveReviewState(env) {
|
|
|
3583
3585
|
lockReason: locked ? "review-pending" : null
|
|
3584
3586
|
};
|
|
3585
3587
|
}
|
|
3588
|
+
function isInServiceStatus(serviceStatus) {
|
|
3589
|
+
return serviceStatus === "OPENED" || serviceStatus === "RUNNING";
|
|
3590
|
+
}
|
|
3586
3591
|
function deriveLsStatus(derived, serviceStatus) {
|
|
3587
3592
|
if (!derived) return {
|
|
3588
3593
|
status: "unknown",
|
|
@@ -3592,7 +3597,7 @@ function deriveLsStatus(derived, serviceStatus) {
|
|
|
3592
3597
|
const locked = derived.approvalType === "REVIEW";
|
|
3593
3598
|
const lockReason = locked ? "review-pending" : null;
|
|
3594
3599
|
let status = derived.state;
|
|
3595
|
-
if (status === "approved" && serviceStatus
|
|
3600
|
+
if (status === "approved" && isInServiceStatus(serviceStatus)) status = "in-service";
|
|
3596
3601
|
return {
|
|
3597
3602
|
status,
|
|
3598
3603
|
locked,
|
|
@@ -4523,7 +4528,7 @@ const bundlesCommand = defineCommand({
|
|
|
4523
4528
|
"test-links": defineCommand({
|
|
4524
4529
|
meta: {
|
|
4525
4530
|
name: "test-links",
|
|
4526
|
-
description: "Show
|
|
4531
|
+
description: "Show the test deep-link URL for a specific bundle."
|
|
4527
4532
|
},
|
|
4528
4533
|
args: {
|
|
4529
4534
|
id: {
|
|
@@ -4531,6 +4536,10 @@ const bundlesCommand = defineCommand({
|
|
|
4531
4536
|
description: "Mini-app ID. Optional when `aitcc.yaml` provides `miniAppId`.",
|
|
4532
4537
|
required: false
|
|
4533
4538
|
},
|
|
4539
|
+
"deployment-id": {
|
|
4540
|
+
type: "string",
|
|
4541
|
+
description: "deploymentId of the bundle to fetch the test link for."
|
|
4542
|
+
},
|
|
4534
4543
|
workspace: {
|
|
4535
4544
|
type: "string",
|
|
4536
4545
|
description: "Workspace ID. Defaults to the selected workspace."
|
|
@@ -4542,6 +4551,15 @@ const bundlesCommand = defineCommand({
|
|
|
4542
4551
|
}
|
|
4543
4552
|
},
|
|
4544
4553
|
async run({ args }) {
|
|
4554
|
+
const deploymentId = typeof args["deployment-id"] === "string" ? args["deployment-id"] : "";
|
|
4555
|
+
if (deploymentId === "") {
|
|
4556
|
+
if (args.json) emitJson({
|
|
4557
|
+
ok: false,
|
|
4558
|
+
reason: "missing-deployment-id"
|
|
4559
|
+
});
|
|
4560
|
+
else process.stderr.write("app bundles test-links: --deployment-id <uuid> is required.\n");
|
|
4561
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
4562
|
+
}
|
|
4545
4563
|
const ctx = await resolveAppOrFail({
|
|
4546
4564
|
json: args.json,
|
|
4547
4565
|
appIdRaw: args.id,
|
|
@@ -4553,26 +4571,22 @@ const bundlesCommand = defineCommand({
|
|
|
4553
4571
|
printContextHeader(ctx, { json: args.json });
|
|
4554
4572
|
const { session, workspaceId } = ctx;
|
|
4555
4573
|
try {
|
|
4556
|
-
const
|
|
4574
|
+
const { linkUri } = await fetchBundleTestLinks(workspaceId, appId, deploymentId, session.cookies);
|
|
4557
4575
|
if (args.json) {
|
|
4558
4576
|
emitJson({
|
|
4559
4577
|
ok: true,
|
|
4560
4578
|
workspaceId,
|
|
4561
4579
|
appId,
|
|
4562
|
-
|
|
4580
|
+
deploymentId,
|
|
4581
|
+
linkUri
|
|
4563
4582
|
});
|
|
4564
4583
|
return exitAfterFlush(ExitCode.Ok);
|
|
4565
4584
|
}
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
process.stdout.write(`App ${appId} (ws ${workspaceId}): no test links available\n`);
|
|
4585
|
+
if (linkUri === "") {
|
|
4586
|
+
process.stdout.write(`App ${appId} (ws ${workspaceId}): no test link available for deployment ${deploymentId}\n`);
|
|
4569
4587
|
return exitAfterFlush(ExitCode.Ok);
|
|
4570
4588
|
}
|
|
4571
|
-
process.stdout.write(`App ${appId} (ws ${workspaceId}):\n`);
|
|
4572
|
-
for (const k of keys) {
|
|
4573
|
-
const v = links[k];
|
|
4574
|
-
process.stdout.write(` ${k}\t${typeof v === "string" ? v : JSON.stringify(v)}\n`);
|
|
4575
|
-
}
|
|
4589
|
+
process.stdout.write(`App ${appId} (ws ${workspaceId}):\n ${linkUri}\n`);
|
|
4576
4590
|
return exitAfterFlush(ExitCode.Ok);
|
|
4577
4591
|
} catch (err) {
|
|
4578
4592
|
return emitFailureFromError(args.json, err);
|
|
@@ -6971,6 +6985,81 @@ const completionCommand = defineCommand({
|
|
|
6971
6985
|
}
|
|
6972
6986
|
});
|
|
6973
6987
|
//#endregion
|
|
6988
|
+
//#region src/ait-token-profile.ts
|
|
6989
|
+
const AIT_CREDENTIALS_PATH = join(homedir(), ".ait", "credentials");
|
|
6990
|
+
function credentialsPath() {
|
|
6991
|
+
const override = process.env._AIT_CREDENTIALS_PATH_OVERRIDE;
|
|
6992
|
+
if (override && override.length > 0) return override;
|
|
6993
|
+
return AIT_CREDENTIALS_PATH;
|
|
6994
|
+
}
|
|
6995
|
+
function readCredentials(path) {
|
|
6996
|
+
if (!existsSync(path)) return {};
|
|
6997
|
+
try {
|
|
6998
|
+
const raw = readFileSync(path, "utf8");
|
|
6999
|
+
const parsed = JSON.parse(raw);
|
|
7000
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
7001
|
+
return {};
|
|
7002
|
+
} catch {
|
|
7003
|
+
return {};
|
|
7004
|
+
}
|
|
7005
|
+
}
|
|
7006
|
+
function writeCredentials(path, map) {
|
|
7007
|
+
const dir = join(path, "..");
|
|
7008
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
7009
|
+
writeFileSync(path, JSON.stringify(map, null, 2) + "\n", {
|
|
7010
|
+
encoding: "utf8",
|
|
7011
|
+
mode: 384
|
|
7012
|
+
});
|
|
7013
|
+
}
|
|
7014
|
+
/**
|
|
7015
|
+
* Save a Deploy Key to the `ait` token profile store.
|
|
7016
|
+
*
|
|
7017
|
+
* Tries direct-write first. If that fails and `ait` is on PATH, falls back
|
|
7018
|
+
* to spawning `ait token add --api-key ... <profile>`.
|
|
7019
|
+
*
|
|
7020
|
+
* SECRET-HANDLING: `apiKey` must not appear in any log or thrown message.
|
|
7021
|
+
* The `detail` field in failure results contains only non-secret context.
|
|
7022
|
+
*/
|
|
7023
|
+
function saveAitTokenProfile(profile, apiKey) {
|
|
7024
|
+
const path = credentialsPath();
|
|
7025
|
+
try {
|
|
7026
|
+
const map = readCredentials(path);
|
|
7027
|
+
map[profile] = apiKey;
|
|
7028
|
+
writeCredentials(path, map);
|
|
7029
|
+
return {
|
|
7030
|
+
ok: true,
|
|
7031
|
+
method: "direct",
|
|
7032
|
+
profile
|
|
7033
|
+
};
|
|
7034
|
+
} catch (err) {
|
|
7035
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
7036
|
+
try {
|
|
7037
|
+
execFileSync("ait", [
|
|
7038
|
+
"token",
|
|
7039
|
+
"add",
|
|
7040
|
+
"--api-key",
|
|
7041
|
+
apiKey,
|
|
7042
|
+
profile
|
|
7043
|
+
], {
|
|
7044
|
+
stdio: "ignore",
|
|
7045
|
+
timeout: 1e4,
|
|
7046
|
+
env: { ...process.env }
|
|
7047
|
+
});
|
|
7048
|
+
return {
|
|
7049
|
+
ok: true,
|
|
7050
|
+
method: "spawn",
|
|
7051
|
+
profile
|
|
7052
|
+
};
|
|
7053
|
+
} catch {
|
|
7054
|
+
return {
|
|
7055
|
+
ok: false,
|
|
7056
|
+
reason: "write-failed",
|
|
7057
|
+
detail
|
|
7058
|
+
};
|
|
7059
|
+
}
|
|
7060
|
+
}
|
|
7061
|
+
}
|
|
7062
|
+
//#endregion
|
|
6974
7063
|
//#region src/api/api-keys.ts
|
|
6975
7064
|
const BASE$2 = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole";
|
|
6976
7065
|
async function fetchApiKeys(workspaceId, cookies, opts = {}) {
|
|
@@ -7126,6 +7215,10 @@ const createCommand = defineCommand({
|
|
|
7126
7215
|
type: "string",
|
|
7127
7216
|
description: "Workspace ID. Defaults to the selected workspace."
|
|
7128
7217
|
},
|
|
7218
|
+
"save-profile": {
|
|
7219
|
+
type: "string",
|
|
7220
|
+
description: "After issuing, save the key as an `ait` token profile (written to `~/.ait/credentials`). The named profile can then be used with `ait deploy --profile <name>` immediately. If omitted, the key is printed to stdout once and not persisted locally."
|
|
7221
|
+
},
|
|
7129
7222
|
json: {
|
|
7130
7223
|
type: "boolean",
|
|
7131
7224
|
description: "Emit machine-readable JSON to stdout.",
|
|
@@ -7175,6 +7268,14 @@ const createCommand = defineCommand({
|
|
|
7175
7268
|
name,
|
|
7176
7269
|
target
|
|
7177
7270
|
}, session.cookies);
|
|
7271
|
+
const saveProfileName = args["save-profile"] ? String(args["save-profile"]) : void 0;
|
|
7272
|
+
let savedProfile;
|
|
7273
|
+
let saveProfileWarning;
|
|
7274
|
+
if (saveProfileName !== void 0) {
|
|
7275
|
+
const saveResult = saveAitTokenProfile(saveProfileName, result.apiKey);
|
|
7276
|
+
if (saveResult.ok) savedProfile = saveResult.profile;
|
|
7277
|
+
else saveProfileWarning = `Could not save to ait profile "${saveProfileName}": ${saveResult.detail}. Save the key manually with \`ait token add --api-key <key> ` + saveProfileName + "`.";
|
|
7278
|
+
}
|
|
7178
7279
|
if (args.json) {
|
|
7179
7280
|
emitJson({
|
|
7180
7281
|
ok: true,
|
|
@@ -7185,12 +7286,16 @@ const createCommand = defineCommand({
|
|
|
7185
7286
|
isAll: target.isAll,
|
|
7186
7287
|
appNames: [...target.appNames]
|
|
7187
7288
|
},
|
|
7289
|
+
...savedProfile !== void 0 ? { savedProfile } : {},
|
|
7290
|
+
...saveProfileWarning !== void 0 ? { saveProfileWarning } : {},
|
|
7188
7291
|
extra: result.extra
|
|
7189
7292
|
});
|
|
7190
7293
|
return exitAfterFlush(ExitCode.Ok);
|
|
7191
7294
|
}
|
|
7192
7295
|
process.stdout.write(`${result.apiKey}\n`);
|
|
7193
|
-
process.stderr.write(
|
|
7296
|
+
if (savedProfile !== void 0) process.stderr.write(`Saved as ait profile "${savedProfile}". Run: ait deploy --profile ${savedProfile}\n`);
|
|
7297
|
+
else process.stderr.write("⚠️ This key is shown only once. Save it to a secret manager now — it cannot be retrieved later.\n");
|
|
7298
|
+
if (saveProfileWarning !== void 0) process.stderr.write(`Warning: ${saveProfileWarning}\n`);
|
|
7194
7299
|
return exitAfterFlush(ExitCode.Ok);
|
|
7195
7300
|
} catch (err) {
|
|
7196
7301
|
return emitFailureFromError(args.json, err);
|
|
@@ -9233,7 +9338,7 @@ function resolveVersion() {
|
|
|
9233
9338
|
if (typeof injected === "string" && injected.length > 0) return injected;
|
|
9234
9339
|
} catch {}
|
|
9235
9340
|
try {
|
|
9236
|
-
return "0.1.
|
|
9341
|
+
return "0.1.36";
|
|
9237
9342
|
} catch {}
|
|
9238
9343
|
return "0.0.0-dev";
|
|
9239
9344
|
}
|