@lark-apaas/miaoda-cli 0.1.1 → 0.1.2-alpha.08ca9fb

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.
Files changed (68) hide show
  1. package/README.md +8 -7
  2. package/dist/api/app/api.js +25 -0
  3. package/dist/api/app/index.js +15 -0
  4. package/dist/api/app/schemas.js +79 -0
  5. package/dist/api/app/types.js +58 -0
  6. package/dist/api/db/api.js +83 -6
  7. package/dist/api/db/client.js +40 -29
  8. package/dist/api/db/parsers.js +33 -20
  9. package/dist/api/db/sql-keywords.js +123 -0
  10. package/dist/api/deploy/api.js +60 -0
  11. package/dist/api/deploy/index.js +16 -0
  12. package/dist/api/deploy/schemas.js +105 -0
  13. package/dist/api/deploy/types.js +22 -0
  14. package/dist/api/file/api.js +78 -24
  15. package/dist/api/file/client.js +1 -5
  16. package/dist/api/file/parsers.js +1 -5
  17. package/dist/api/index.js +7 -1
  18. package/dist/api/observability/api.js +52 -0
  19. package/dist/api/observability/index.js +16 -0
  20. package/dist/api/observability/schemas.js +60 -0
  21. package/dist/api/observability/types.js +27 -0
  22. package/dist/api/plugin/api.js +8 -3
  23. package/dist/cli/commands/app/index.js +62 -0
  24. package/dist/cli/commands/db/index.js +1 -0
  25. package/dist/cli/commands/deploy/index.js +148 -0
  26. package/dist/cli/commands/index.js +10 -6
  27. package/dist/cli/commands/observability/index.js +240 -0
  28. package/dist/cli/commands/plugin/index.js +18 -6
  29. package/dist/cli/commands/shared.js +82 -6
  30. package/dist/cli/handlers/app/get.js +48 -0
  31. package/dist/cli/handlers/app/index.js +7 -0
  32. package/dist/cli/handlers/app/update.js +59 -0
  33. package/dist/cli/handlers/db/data.js +22 -2
  34. package/dist/cli/handlers/db/schema.js +22 -8
  35. package/dist/cli/handlers/db/sql.js +304 -16
  36. package/dist/cli/handlers/deploy/deploy.js +84 -0
  37. package/dist/cli/handlers/deploy/error-log.js +60 -0
  38. package/dist/cli/handlers/deploy/format.js +39 -0
  39. package/dist/cli/handlers/deploy/get.js +71 -0
  40. package/dist/cli/handlers/deploy/helpers.js +41 -0
  41. package/dist/cli/handlers/deploy/history.js +71 -0
  42. package/dist/cli/handlers/deploy/index.js +14 -0
  43. package/dist/cli/handlers/deploy/polling.js +162 -0
  44. package/dist/cli/handlers/file/cp.js +39 -17
  45. package/dist/cli/handlers/file/ls.js +1 -3
  46. package/dist/cli/handlers/file/rm.js +4 -3
  47. package/dist/cli/handlers/observability/analytics.js +212 -0
  48. package/dist/cli/handlers/observability/helpers.js +66 -0
  49. package/dist/cli/handlers/observability/index.js +12 -0
  50. package/dist/cli/handlers/observability/log.js +95 -0
  51. package/dist/cli/handlers/observability/metric.js +208 -0
  52. package/dist/cli/handlers/observability/trace.js +102 -0
  53. package/dist/cli/handlers/plugin/plugin-local.js +23 -9
  54. package/dist/cli/handlers/plugin/plugin.js +21 -7
  55. package/dist/cli/help.js +5 -2
  56. package/dist/cli/version.js +15 -0
  57. package/dist/main.js +27 -7
  58. package/dist/utils/colors.js +98 -0
  59. package/dist/utils/devops-error.js +28 -0
  60. package/dist/utils/error.js +11 -0
  61. package/dist/utils/fuzzy-match.js +91 -0
  62. package/dist/utils/git.js +29 -0
  63. package/dist/utils/http.js +118 -0
  64. package/dist/utils/index.js +13 -1
  65. package/dist/utils/output.js +397 -12
  66. package/dist/utils/render.js +61 -41
  67. package/dist/utils/time.js +203 -0
  68. package/package.json +16 -6
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.handleDeployErrorLog = handleDeployErrorLog;
37
+ const api = __importStar(require("../../../api/index"));
38
+ const output_1 = require("../../../utils/output");
39
+ const shared_1 = require("../../../cli/commands/shared");
40
+ const index_1 = require("../../../api/deploy/index");
41
+ const helpers_1 = require("./helpers");
42
+ /** miaoda deploy error-log <deploy-id> */
43
+ async function handleDeployErrorLog(opts) {
44
+ if (!opts.deployId)
45
+ (0, shared_1.failArgs)("<deploy-id> 必填");
46
+ const appID = (0, shared_1.resolveAppId)({ appId: opts.appId });
47
+ (0, helpers_1.parseDeployId)(opts.deployId); // 仅校验数字形式;URL 直接用原字符串
48
+ const instanceID = opts.deployId;
49
+ const resp = await api.deploy.getErrorLog({ appID, instanceID });
50
+ (0, output_1.emit)({
51
+ data: resp.errorJobs,
52
+ next_cursor: null,
53
+ has_more: false,
54
+ }, index_1.errorJobSchema);
55
+ // pretty 模式额外打一行整体状态。
56
+ if (!process.stdout.isTTY)
57
+ return;
58
+ const statusText = (0, index_1.nodeStatusText)(resp.status) ?? String(resp.status);
59
+ process.stdout.write(`\n— pipeline status: ${statusText}\n`);
60
+ }
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatStatusMetadata = formatStatusMetadata;
4
+ exports.formatSimpleInstance = formatSimpleInstance;
5
+ exports.formatPipelineInstance = formatPipelineInstance;
6
+ const index_1 = require("../../../api/deploy/index");
7
+ function formatStatusMetadata(statusCode) {
8
+ return {
9
+ status: (0, index_1.nodeStatusText)(statusCode) ?? `unknown(${String(statusCode)})`,
10
+ status_code: statusCode,
11
+ };
12
+ }
13
+ function formatSimpleInstance(instance) {
14
+ if (instance.status === undefined)
15
+ return { ...instance };
16
+ return {
17
+ ...instance,
18
+ ...formatStatusMetadata(instance.status),
19
+ };
20
+ }
21
+ function formatPipelineInstance(detail) {
22
+ const stages = Array.isArray(detail.stages) ? detail.stages : [];
23
+ return {
24
+ ...detail,
25
+ ...formatStatusMetadata(detail.status),
26
+ stages: stages.map((stage) => ({
27
+ ...stage,
28
+ ...formatStatusMetadata(stage.status),
29
+ jobs: stage.jobs.map((group) => group.map((job) => ({
30
+ ...job,
31
+ ...formatStatusMetadata(job.status),
32
+ steps: job.steps?.map((step) => ({
33
+ ...step,
34
+ ...formatStatusMetadata(step.status),
35
+ })),
36
+ }))),
37
+ })),
38
+ };
39
+ }
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.handleDeployGet = handleDeployGet;
37
+ const api = __importStar(require("../../../api/index"));
38
+ const output_1 = require("../../../utils/output");
39
+ const shared_1 = require("../../../cli/commands/shared");
40
+ const error_1 = require("../../../utils/error");
41
+ const index_1 = require("../../../api/deploy/index");
42
+ const helpers_1 = require("./helpers");
43
+ const format_1 = require("./format");
44
+ /**
45
+ * 单页拉取上限:BAM listPipelineInstances 限制 1~500,取 500 让 deploy get
46
+ * 在最近 500 条记录里按 ID 命中。
47
+ */
48
+ const DEPLOY_GET_LOOKUP_LIMIT = 500;
49
+ /**
50
+ * miaoda deploy get <deploy-id>
51
+ *
52
+ * BAM 没有 by-ID 的查询接口,复用 listPipelineInstances 在最近窗口里按 ID 筛一条。
53
+ * 命中失败时抛 DEPLOY_NOT_FOUND,提示用户用 history 查窗口。
54
+ */
55
+ async function handleDeployGet(opts) {
56
+ if (!opts.deployId)
57
+ (0, shared_1.failArgs)("<deploy-id> 必填");
58
+ const appID = (0, shared_1.resolveAppId)({ appId: opts.appId });
59
+ const deployId = (0, helpers_1.parseDeployId)(opts.deployId);
60
+ const resp = await api.deploy.listPipelineInstances({
61
+ appID,
62
+ limit: DEPLOY_GET_LOOKUP_LIMIT,
63
+ });
64
+ const instance = resp.instances?.find((it) => Number(it.ID) === deployId);
65
+ if (!instance) {
66
+ throw new error_1.AppError("DEPLOY_NOT_FOUND", `未在最近 ${String(DEPLOY_GET_LOOKUP_LIMIT)} 条记录中找到 deploy-id=${opts.deployId}`, {
67
+ next_actions: ["运行 `miaoda deploy history` 确认 deploy-id 是否在最近窗口内"],
68
+ });
69
+ }
70
+ (0, output_1.emit)({ data: (0, format_1.formatSimpleInstance)(instance), next_cursor: null, has_more: false }, index_1.deployGetSchema);
71
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseStatusFlag = parseStatusFlag;
4
+ exports.validateLimit = validateLimit;
5
+ exports.parseDeployId = parseDeployId;
6
+ const shared_1 = require("../../../cli/commands/shared");
7
+ const index_1 = require("../../../api/deploy/index");
8
+ /**
9
+ * 把 CLI --status 字符串映射成 BAM NodeStatus 数字;非法值抛 ARGS_INVALID。
10
+ *
11
+ * 历史接口(CliListPipelineInstances)返回 SimpleInstance,状态语义是 pipeline 节点状态,
12
+ * 取值 todo / running / success / failed / canceled / hold_on。
13
+ */
14
+ function parseStatusFlag(text) {
15
+ const value = (0, index_1.nodeStatusFromText)(text);
16
+ if (value === undefined) {
17
+ (0, shared_1.failArgs)(`--status 无效:'${text}'。可选值:todo | running | success | failed | canceled | hold_on`);
18
+ }
19
+ return value;
20
+ }
21
+ /** limit/pageSize 校验(API 限制 1~100) */
22
+ function validateLimit(limit, defaultLimit = 50) {
23
+ const n = Number.isFinite(limit) ? limit : defaultLimit;
24
+ if (n <= 0 || n > 100) {
25
+ (0, shared_1.failArgs)(`--limit 必须在 1~100 之间,收到 ${String(n)}`);
26
+ }
27
+ return n;
28
+ }
29
+ /**
30
+ * 把 CLI <deploy-id> 字符串转成 i64 number。
31
+ *
32
+ * 语义上 deploy-id 即 pipelineTaskID(pipeline 实例 ID);BAM CLIGetReleaseLogs
33
+ * IDL 历史命名为 releaseID,但传入的就是这同一个值。
34
+ */
35
+ function parseDeployId(text) {
36
+ const n = Number(text);
37
+ if (!Number.isFinite(n) || !/^\d+$/.test(text)) {
38
+ (0, shared_1.failArgs)(`<deploy-id> 必须是数字 ID,收到 '${text}'`);
39
+ }
40
+ return n;
41
+ }
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.handleDeployHistory = handleDeployHistory;
37
+ const api = __importStar(require("../../../api/index"));
38
+ const output_1 = require("../../../utils/output");
39
+ const shared_1 = require("../../../cli/commands/shared");
40
+ const time_1 = require("../../../utils/time");
41
+ const index_1 = require("../../../api/deploy/index");
42
+ const helpers_1 = require("./helpers");
43
+ const format_1 = require("./format");
44
+ /** miaoda deploy history */
45
+ async function handleDeployHistory(opts) {
46
+ const appID = (0, shared_1.resolveAppId)({ appId: opts.appId });
47
+ const limit = (0, helpers_1.validateLimit)(opts.limit ?? 50);
48
+ const req = {
49
+ appID,
50
+ limit,
51
+ pageToken: opts.cursor,
52
+ };
53
+ if (opts.status)
54
+ req.status = [(0, helpers_1.parseStatusFlag)(opts.status)];
55
+ // BAM startTimestamp / endTimestamp 单位是秒(IDL desc:单位:秒)
56
+ if (opts.since !== undefined)
57
+ req.startTimestamp = msToSec((0, time_1.parseToMs)(opts.since));
58
+ if (opts.until !== undefined)
59
+ req.endTimestamp = msToSec((0, time_1.parseToMs)(opts.until));
60
+ const resp = await api.deploy.listPipelineInstances(req);
61
+ (0, output_1.emit)({
62
+ data: (resp.instances ?? []).map(format_1.formatSimpleInstance),
63
+ next_cursor: resp.hasMore ? (resp.nextPageToken ?? null) : null,
64
+ has_more: resp.hasMore ?? false,
65
+ }, index_1.deployHistorySchema);
66
+ }
67
+ function msToSec(ms) {
68
+ if (ms === undefined)
69
+ return undefined;
70
+ return Math.floor(ms / 1000);
71
+ }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_POLL_INTERVAL_MS = exports.waitForPipeline = exports.handleDeployErrorLog = exports.handleDeployHistory = exports.handleDeployGet = exports.handleDeploy = void 0;
4
+ var deploy_1 = require("./deploy");
5
+ Object.defineProperty(exports, "handleDeploy", { enumerable: true, get: function () { return deploy_1.handleDeploy; } });
6
+ var get_1 = require("./get");
7
+ Object.defineProperty(exports, "handleDeployGet", { enumerable: true, get: function () { return get_1.handleDeployGet; } });
8
+ var history_1 = require("./history");
9
+ Object.defineProperty(exports, "handleDeployHistory", { enumerable: true, get: function () { return history_1.handleDeployHistory; } });
10
+ var error_log_1 = require("./error-log");
11
+ Object.defineProperty(exports, "handleDeployErrorLog", { enumerable: true, get: function () { return error_log_1.handleDeployErrorLog; } });
12
+ var polling_1 = require("./polling");
13
+ Object.defineProperty(exports, "waitForPipeline", { enumerable: true, get: function () { return polling_1.waitForPipeline; } });
14
+ Object.defineProperty(exports, "DEFAULT_POLL_INTERVAL_MS", { enumerable: true, get: function () { return polling_1.DEFAULT_POLL_INTERVAL_MS; } });
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.DEFAULT_POLL_INTERVAL_MS = void 0;
40
+ exports.waitForPipeline = waitForPipeline;
41
+ const ora_1 = __importDefault(require("ora"));
42
+ const api = __importStar(require("../../../api/index"));
43
+ const error_1 = require("../../../utils/error");
44
+ const output_1 = require("../../../utils/output");
45
+ const index_1 = require("../../../api/deploy/index");
46
+ /** 默认轮询间隔(毫秒) */
47
+ exports.DEFAULT_POLL_INTERVAL_MS = 2_000;
48
+ const TERMINAL_STATUSES = [
49
+ index_1.NodeStatus.SUCCESS,
50
+ index_1.NodeStatus.FAILED,
51
+ index_1.NodeStatus.CANCELED,
52
+ ];
53
+ function defaultSleep(ms) {
54
+ return new Promise((res) => setTimeout(res, ms));
55
+ }
56
+ /**
57
+ * 轮询 pipeline 状态直到终态或超时。
58
+ *
59
+ * - JSON 模式:轮询期间静默;终态后由 caller 输出最终信封。
60
+ * - Pretty 非 TTY:同 JSON。
61
+ * - Pretty TTY:底部行用 braille spinner 显示当前正在跑的 stage(每 80ms 跳一帧),
62
+ * stage 转终态时把 ✓ stage(12.3s) 打到 spinner 上方然后清掉 spinner 行。
63
+ *
64
+ * 终态返回最终 PipelineInstance,调用方再决定怎么 emit。
65
+ */
66
+ async function waitForPipeline(opts) {
67
+ const interval = opts.intervalMs ?? exports.DEFAULT_POLL_INTERVAL_MS;
68
+ const timeoutMs = opts.timeoutSec * 1000;
69
+ const sleep = opts.sleep ?? defaultSleep;
70
+ const now = opts.now ?? Date.now;
71
+ const start = now();
72
+ // ora 自身在非 TTY 下会降级为不动画,但我们额外加 isJsonMode 闸门:JSON / 字段选择
73
+ // 模式下完全静默。
74
+ const showProgress = !(0, output_1.isJsonMode)() && process.stdout.isTTY;
75
+ const printedStages = new Set();
76
+ const spinner = showProgress
77
+ ? (0, ora_1.default)({ text: "等待调度", spinner: "dots", stream: process.stdout }).start()
78
+ : null;
79
+ try {
80
+ for (;;) {
81
+ const resp = await api.deploy.queryPipelineInstance({
82
+ appID: opts.appID,
83
+ instanceID: opts.pipelineTaskID,
84
+ });
85
+ const detail = normalizePipelineInstance(resp);
86
+ if (spinner)
87
+ advanceProgress(detail, printedStages, spinner);
88
+ if (TERMINAL_STATUSES.includes(detail.status)) {
89
+ return detail;
90
+ }
91
+ if (now() - start >= timeoutMs) {
92
+ throw new error_1.AppError("DEPLOY_TIMEOUT", `轮询发布状态超时(${String(opts.timeoutSec)}s)`, {
93
+ retryable: true,
94
+ next_actions: [
95
+ `Run \`miaoda deploy get <deploy-id>\` 查看最新状态`,
96
+ `加大 --timeout 后重试`,
97
+ ],
98
+ });
99
+ }
100
+ await sleep(interval);
101
+ }
102
+ }
103
+ finally {
104
+ spinner?.stop();
105
+ }
106
+ }
107
+ /**
108
+ * 每轮 poll 的进度推进:刚转终态的 stage 用 stopAndPersist 落定为永久行,
109
+ * 再把 spinner 文本切到当前 running stage;没有 running 时显示"等待调度"。
110
+ */
111
+ function advanceProgress(detail, printed, spinner) {
112
+ const stages = Array.isArray(detail.stages) ? detail.stages : [];
113
+ for (const stage of stages) {
114
+ if (printed.has(stage.id))
115
+ continue;
116
+ if (!TERMINAL_STATUSES.includes(stage.status))
117
+ continue;
118
+ spinner.stopAndPersist({
119
+ symbol: stageSymbol(stage),
120
+ text: formatStageBody(stage),
121
+ });
122
+ printed.add(stage.id);
123
+ }
124
+ const running = stages.find((s) => s.status === index_1.NodeStatus.RUNNING);
125
+ const text = running ? `${running.name} 执行中` : "等待调度";
126
+ if (spinner.isSpinning) {
127
+ spinner.text = text;
128
+ }
129
+ else {
130
+ spinner.start(text);
131
+ }
132
+ }
133
+ function normalizePipelineInstance(resp) {
134
+ const keyedDetail = readPipelineDetail(resp, "pipelineInstanceDetail") ?? readPipelineDetail(resp, "detail");
135
+ if (keyedDetail)
136
+ return keyedDetail;
137
+ if (isPipelineInstance(resp))
138
+ return resp;
139
+ throw new error_1.AppError("DEPLOY_RESPONSE_INVALID", "发布状态响应缺少 pipeline detail", {
140
+ next_actions: ["使用 --verbose 重试以查看请求 logid,并联系平台排查响应结构"],
141
+ });
142
+ }
143
+ function readPipelineDetail(resp, key) {
144
+ if (!(key in resp))
145
+ return undefined;
146
+ const detail = resp[key];
147
+ return isPipelineInstance(detail) ? detail : undefined;
148
+ }
149
+ function isPipelineInstance(value) {
150
+ if (typeof value !== "object" || value === null)
151
+ return false;
152
+ const item = value;
153
+ return typeof item.status === "number";
154
+ }
155
+ function stageSymbol(stage) {
156
+ return stage.status === index_1.NodeStatus.SUCCESS ? "✓" : stage.status === index_1.NodeStatus.FAILED ? "✗" : "•";
157
+ }
158
+ function formatStageBody(stage) {
159
+ const cost = stage.costTime >= 0 ? `(${(stage.costTime / 1000).toFixed(1)}s)` : "";
160
+ const status = (0, index_1.nodeStatusText)(stage.status) ?? String(stage.status);
161
+ return `${stage.name}${cost ? ` ${cost}` : ""} ${status}`.trim();
162
+ }
@@ -45,15 +45,23 @@ const output_1 = require("../../../utils/output");
45
45
  const render_1 = require("../../../utils/render");
46
46
  const error_1 = require("../../../utils/error");
47
47
  const shared_1 = require("../../../cli/commands/shared");
48
+ const colors_1 = require("../../../utils/colors");
48
49
  const MAX_UPLOAD_BYTES = 100 * 1024 * 1024;
49
50
  /**
50
- * 判断 src 是本地文件还是远程引用:
51
- * - src 本地 fs 可访问 upload(dst remote)
52
- * - 其他 → download(src 是 /path 或 file_name,由 resolveRemotePath 解析)
53
- *
54
- * `cp` 语义要求 src 必须存在;本地不存在就认为用户指向远程。不需要给 dst 猜方向。
51
+ * 判断 src 是本地路径还是远程引用——按 PRD `miaoda file cp` 规则的优先级:
52
+ * 1. `./` / `../` / `~/` 开头 + 裸文件名(不含 `/`)→ 本地路径(即使不存在)
53
+ * handleUpload 把"不存在"准确抛成 FILE_SRC_NOT_FOUND,
54
+ * 避免回退到远程 download 输出 `FILE_NOT_FOUND` + "Run miaoda file ls"
55
+ * 这种与用户意图(上传)背离的引导。
56
+ * 2. `/` 开头:fs.existsSync 兜底——存在即本地(绝对路径上传),
57
+ * 不存在即远程 path(download,由 resolveRemotePath 处理)。
55
58
  */
56
59
  function isLocalSrc(src) {
60
+ if (src.startsWith("./") || src.startsWith("../") || src.startsWith("~/"))
61
+ return true;
62
+ if (!src.includes("/"))
63
+ return true;
64
+ // `/` 开头:可能是本地绝对路径,也可能是远程 path;交给 fs 探测。
57
65
  const expanded = expandHome(src);
58
66
  try {
59
67
  return node_fs_1.default.existsSync(expanded);
@@ -104,23 +112,37 @@ async function resolveRemotePath(appId, input) {
104
112
  async function handleUpload(appId, localRaw, remoteRaw, rename) {
105
113
  const localPath = expandHome(localRaw);
106
114
  if (!node_fs_1.default.existsSync(localPath) || !node_fs_1.default.statSync(localPath).isFile()) {
107
- throw new error_1.AppError("FILE_SRC_NOT_FOUND", `Local file '${localRaw}' does not exist`);
115
+ throw new error_1.AppError("FILE_SRC_NOT_FOUND", `Local file '${localRaw}' does not exist`, {
116
+ // 引导本地路径自检;远程下载请用 `/path` 形态,避免裸名/相对路径误入上传分支
117
+ next_actions: ["Check the local file path; use `/path` prefix for remote download."],
118
+ });
108
119
  }
109
120
  const stat = node_fs_1.default.statSync(localPath);
110
121
  if (stat.size > MAX_UPLOAD_BYTES) {
111
122
  throw new error_1.AppError("FILE_SIZE_EXCEEDED", `File size ${(0, render_1.formatSize)(stat.size)} exceeds the 100 MB upload limit`, {
112
- next_actions: [
113
- "Split the file, or use the web console for large uploads.",
114
- ],
123
+ next_actions: ["Split the file, or use the web console for large uploads."],
115
124
  });
116
125
  }
117
- const fileName = rename ?? node_path_1.default.basename(localPath);
118
- let remotePath = remoteRaw;
119
- if (remoteRaw.endsWith("/")) {
120
- remotePath = remoteRaw + fileName;
126
+ // PRD:path 末段**始终**由平台生成 16 ID 确保唯一,不受 dst 形态或
127
+ // --rename 影响;用户指定的 file_name 仅作为显示名(同目录下允许重名)。
128
+ // 因此 CLI 端把 dst 拆成"目录前缀 + file_name"两段:
129
+ // - dst / 结尾或为空 → 整段当目录前缀,file_name 用 --rename 或本地 basename
130
+ // 例:cp ./logo.png /imgs/ → path=/imgs/<GID>.png, file_name=logo.png
131
+ // - dst 不以 / 结尾 → 末段当 file_name,前段(含尾 /)当目录前缀
132
+ // 例:cp ./1.jpg /post-covers/cover.jpg → path=/post-covers/<GID>.jpg, file_name=cover.jpg
133
+ // 这样无论用户怎么写 dst,都走服务端目录前缀模式(PreUpload `filePath`
134
+ // 末尾带 /),保证 path 全局唯一性,不会因用户指定了完整路径而退化成
135
+ // 完整对象 key 模式。--rename 始终覆盖 file_name 推断结果(PRD 优先级)。
136
+ let fileName;
137
+ let remotePath;
138
+ if (remoteRaw === "" || remoteRaw.endsWith("/")) {
139
+ fileName = rename ?? node_path_1.default.basename(localPath);
140
+ remotePath = remoteRaw;
121
141
  }
122
- else if (remoteRaw === "/") {
123
- remotePath = "/" + fileName;
142
+ else {
143
+ const lastSlash = remoteRaw.lastIndexOf("/");
144
+ fileName = rename ?? remoteRaw.slice(lastSlash + 1);
145
+ remotePath = remoteRaw.slice(0, lastSlash + 1);
124
146
  }
125
147
  const contentType = detectMime(localPath);
126
148
  const result = await api.file.uploadFile({
@@ -147,7 +169,7 @@ async function handleUpload(appId, localRaw, remoteRaw, rename) {
147
169
  const tty = (0, render_1.isStdoutTty)();
148
170
  const lines = [];
149
171
  if (tty) {
150
- lines.push(`✓ Uploaded ${node_path_1.default.basename(localPath)} → ${result.path}`);
172
+ lines.push(colors_1.c.success(`✓ Uploaded ${node_path_1.default.basename(localPath)} → ${result.path}`));
151
173
  lines.push(` file_name: ${result.file_name}`);
152
174
  lines.push(` path: ${result.path}`);
153
175
  lines.push(` size: ${(0, render_1.formatSize)(result.size)} (${String(result.size)} bytes)`);
@@ -204,7 +226,7 @@ async function handleDownload(appId, remoteRaw, localRaw) {
204
226
  }
205
227
  const tty = (0, render_1.isStdoutTty)();
206
228
  if (tty) {
207
- (0, output_1.emit)(`✓ Downloaded ${baseName} → ${localTarget} (${(0, render_1.formatSize)(writtenBytes)})`);
229
+ (0, output_1.emit)(colors_1.c.success(`✓ Downloaded ${baseName} → ${localTarget} (${(0, render_1.formatSize)(writtenBytes)})`));
208
230
  }
209
231
  else {
210
232
  (0, output_1.emit)(`OK Downloaded ${baseName} -> ${localTarget} (${String(writtenBytes)} bytes)`);
@@ -101,9 +101,7 @@ async function handleFileLs(opts) {
101
101
  info.type,
102
102
  (0, render_1.formatTime)(info.uploaded_at, tty),
103
103
  ]);
104
- const table = tty
105
- ? (0, render_1.renderAlignedTable)(headers, rows)
106
- : (0, render_1.renderTsv)(headers, rows);
104
+ const table = tty ? (0, render_1.renderAlignedTable)(headers, rows) : (0, render_1.renderTsv)(headers, rows);
107
105
  const hint = result.has_more && result.next_cursor
108
106
  ? `\n— ${String(result.items.length)} results. Next: --cursor ${result.next_cursor}`
109
107
  : "";
@@ -43,6 +43,7 @@ const error_1 = require("../../../utils/error");
43
43
  const shared_1 = require("../../../cli/commands/shared");
44
44
  const index_1 = require("../../../api/file/index");
45
45
  const render_1 = require("../../../utils/render");
46
+ const colors_1 = require("../../../utils/colors");
46
47
  const node_readline_1 = __importDefault(require("node:readline"));
47
48
  const MAX_BATCH = 100;
48
49
  /**
@@ -169,7 +170,7 @@ async function handleFileRm(paths, opts) {
169
170
  results.push({
170
171
  status: "ok",
171
172
  input: entry?.input ?? p,
172
- file_name: entry?.file_name ?? (p.split("/").pop() ?? p),
173
+ file_name: entry?.file_name ?? p.split("/").pop() ?? p,
173
174
  path: p,
174
175
  });
175
176
  }
@@ -228,9 +229,9 @@ async function handleFileRm(paths, opts) {
228
229
  const lines = [];
229
230
  for (const r of results) {
230
231
  if (r.status === "ok")
231
- lines.push(`✓ Deleted ${r.input}`);
232
+ lines.push(colors_1.c.success(`✓ Deleted ${r.input}`));
232
233
  else
233
- lines.push(`✗ ${r.input}: ${r.error.message}`);
234
+ lines.push(colors_1.c.fail(`✗ ${r.input}: ${r.error.message}`));
234
235
  }
235
236
  if (failCount === 0) {
236
237
  lines.push(`Deleted ${String(okCount)} of ${String(totalCount)} files`);