@ait-co/console-cli 0.1.35 → 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/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)) return {};
692
- return raw;
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({
@@ -4527,7 +4528,7 @@ const bundlesCommand = defineCommand({
4527
4528
  "test-links": defineCommand({
4528
4529
  meta: {
4529
4530
  name: "test-links",
4530
- description: "Show per-device test URLs for the mini-app."
4531
+ description: "Show the test deep-link URL for a specific bundle."
4531
4532
  },
4532
4533
  args: {
4533
4534
  id: {
@@ -4535,6 +4536,10 @@ const bundlesCommand = defineCommand({
4535
4536
  description: "Mini-app ID. Optional when `aitcc.yaml` provides `miniAppId`.",
4536
4537
  required: false
4537
4538
  },
4539
+ "deployment-id": {
4540
+ type: "string",
4541
+ description: "deploymentId of the bundle to fetch the test link for."
4542
+ },
4538
4543
  workspace: {
4539
4544
  type: "string",
4540
4545
  description: "Workspace ID. Defaults to the selected workspace."
@@ -4546,6 +4551,15 @@ const bundlesCommand = defineCommand({
4546
4551
  }
4547
4552
  },
4548
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
+ }
4549
4563
  const ctx = await resolveAppOrFail({
4550
4564
  json: args.json,
4551
4565
  appIdRaw: args.id,
@@ -4557,26 +4571,22 @@ const bundlesCommand = defineCommand({
4557
4571
  printContextHeader(ctx, { json: args.json });
4558
4572
  const { session, workspaceId } = ctx;
4559
4573
  try {
4560
- const links = await fetchBundleTestLinks(workspaceId, appId, session.cookies);
4574
+ const { linkUri } = await fetchBundleTestLinks(workspaceId, appId, deploymentId, session.cookies);
4561
4575
  if (args.json) {
4562
4576
  emitJson({
4563
4577
  ok: true,
4564
4578
  workspaceId,
4565
4579
  appId,
4566
- links
4580
+ deploymentId,
4581
+ linkUri
4567
4582
  });
4568
4583
  return exitAfterFlush(ExitCode.Ok);
4569
4584
  }
4570
- const keys = Object.keys(links);
4571
- if (keys.length === 0) {
4572
- 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`);
4573
4587
  return exitAfterFlush(ExitCode.Ok);
4574
4588
  }
4575
- process.stdout.write(`App ${appId} (ws ${workspaceId}):\n`);
4576
- for (const k of keys) {
4577
- const v = links[k];
4578
- process.stdout.write(` ${k}\t${typeof v === "string" ? v : JSON.stringify(v)}\n`);
4579
- }
4589
+ process.stdout.write(`App ${appId} (ws ${workspaceId}):\n ${linkUri}\n`);
4580
4590
  return exitAfterFlush(ExitCode.Ok);
4581
4591
  } catch (err) {
4582
4592
  return emitFailureFromError(args.json, err);
@@ -6975,6 +6985,81 @@ const completionCommand = defineCommand({
6975
6985
  }
6976
6986
  });
6977
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
6978
7063
  //#region src/api/api-keys.ts
6979
7064
  const BASE$2 = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole";
6980
7065
  async function fetchApiKeys(workspaceId, cookies, opts = {}) {
@@ -7130,6 +7215,10 @@ const createCommand = defineCommand({
7130
7215
  type: "string",
7131
7216
  description: "Workspace ID. Defaults to the selected workspace."
7132
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
+ },
7133
7222
  json: {
7134
7223
  type: "boolean",
7135
7224
  description: "Emit machine-readable JSON to stdout.",
@@ -7179,6 +7268,14 @@ const createCommand = defineCommand({
7179
7268
  name,
7180
7269
  target
7181
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
+ }
7182
7279
  if (args.json) {
7183
7280
  emitJson({
7184
7281
  ok: true,
@@ -7189,12 +7286,16 @@ const createCommand = defineCommand({
7189
7286
  isAll: target.isAll,
7190
7287
  appNames: [...target.appNames]
7191
7288
  },
7289
+ ...savedProfile !== void 0 ? { savedProfile } : {},
7290
+ ...saveProfileWarning !== void 0 ? { saveProfileWarning } : {},
7192
7291
  extra: result.extra
7193
7292
  });
7194
7293
  return exitAfterFlush(ExitCode.Ok);
7195
7294
  }
7196
7295
  process.stdout.write(`${result.apiKey}\n`);
7197
- process.stderr.write("⚠️ This key is shown only once. Save it to a secret manager now — it cannot be retrieved later.\n");
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`);
7198
7299
  return exitAfterFlush(ExitCode.Ok);
7199
7300
  } catch (err) {
7200
7301
  return emitFailureFromError(args.json, err);
@@ -9237,7 +9338,7 @@ function resolveVersion() {
9237
9338
  if (typeof injected === "string" && injected.length > 0) return injected;
9238
9339
  } catch {}
9239
9340
  try {
9240
- return "0.1.35";
9341
+ return "0.1.36";
9241
9342
  } catch {}
9242
9343
  return "0.0.0-dev";
9243
9344
  }