@lark-apaas/miaoda-cli 0.1.2 → 0.1.3-alpha.2a09432
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.md +8 -7
- package/dist/api/app/api.js +25 -0
- package/dist/api/app/index.js +15 -0
- package/dist/api/app/schemas.js +79 -0
- package/dist/api/app/types.js +58 -0
- package/dist/api/db/api.js +361 -54
- package/dist/api/db/client.js +60 -24
- package/dist/api/db/index.js +11 -1
- package/dist/api/db/parsers.js +20 -20
- package/dist/api/db/sql-keywords.js +87 -87
- package/dist/api/deploy/api.js +60 -0
- package/dist/api/deploy/index.js +16 -0
- package/dist/api/deploy/schemas.js +105 -0
- package/dist/api/deploy/types.js +22 -0
- package/dist/api/file/api.js +89 -87
- package/dist/api/file/client.js +62 -22
- package/dist/api/file/detect.js +3 -3
- package/dist/api/file/index.js +2 -1
- package/dist/api/file/parsers.js +18 -7
- package/dist/api/index.js +7 -1
- package/dist/api/observability/api.js +52 -0
- package/dist/api/observability/index.js +16 -0
- package/dist/api/observability/schemas.js +60 -0
- package/dist/api/observability/types.js +27 -0
- package/dist/api/plugin/api.js +31 -31
- package/dist/cli/commands/app/index.js +62 -0
- package/dist/cli/commands/db/index.js +599 -59
- package/dist/cli/commands/deploy/index.js +155 -0
- package/dist/cli/commands/file/index.js +90 -62
- package/dist/cli/commands/index.js +10 -6
- package/dist/cli/commands/observability/index.js +240 -0
- package/dist/cli/commands/plugin/index.js +27 -27
- package/dist/cli/commands/shared.js +86 -10
- package/dist/cli/handlers/app/get.js +47 -0
- package/dist/cli/handlers/app/index.js +7 -0
- package/dist/cli/handlers/app/update.js +59 -0
- package/dist/cli/handlers/db/_operator.js +35 -0
- package/dist/cli/handlers/db/audit.js +303 -0
- package/dist/cli/handlers/db/changelog.js +132 -0
- package/dist/cli/handlers/db/data.js +34 -34
- package/dist/cli/handlers/db/index.js +17 -1
- package/dist/cli/handlers/db/migration.js +235 -0
- package/dist/cli/handlers/db/quota.js +68 -0
- package/dist/cli/handlers/db/recovery.js +357 -0
- package/dist/cli/handlers/db/schema.js +35 -36
- package/dist/cli/handlers/db/sql.js +70 -71
- package/dist/cli/handlers/deploy/deploy.js +84 -0
- package/dist/cli/handlers/deploy/error-log.js +60 -0
- package/dist/cli/handlers/deploy/format.js +39 -0
- package/dist/cli/handlers/deploy/get.js +71 -0
- package/dist/cli/handlers/deploy/helpers.js +41 -0
- package/dist/cli/handlers/deploy/history.js +70 -0
- package/dist/cli/handlers/deploy/index.js +14 -0
- package/dist/cli/handlers/deploy/polling.js +162 -0
- package/dist/cli/handlers/file/cp.js +31 -32
- package/dist/cli/handlers/file/index.js +3 -1
- package/dist/cli/handlers/file/ls.js +6 -7
- package/dist/cli/handlers/file/quota.js +66 -0
- package/dist/cli/handlers/file/rm.js +25 -26
- package/dist/cli/handlers/file/sign.js +4 -5
- package/dist/cli/handlers/file/stat.js +11 -11
- package/dist/cli/handlers/observability/analytics.js +212 -0
- package/dist/cli/handlers/observability/helpers.js +66 -0
- package/dist/cli/handlers/observability/index.js +12 -0
- package/dist/cli/handlers/observability/log.js +94 -0
- package/dist/cli/handlers/observability/metric.js +208 -0
- package/dist/cli/handlers/observability/trace.js +102 -0
- package/dist/cli/handlers/plugin/plugin-local.js +53 -53
- package/dist/cli/handlers/plugin/plugin.js +15 -15
- package/dist/cli/help.js +16 -16
- package/dist/main.js +13 -9
- package/dist/utils/args.js +8 -0
- package/dist/utils/colors.js +2 -2
- package/dist/utils/config.js +2 -2
- package/dist/utils/devops-error.js +28 -0
- package/dist/utils/error.js +2 -2
- package/dist/utils/git.js +29 -0
- package/dist/utils/http.js +119 -1
- package/dist/utils/index.js +15 -1
- package/dist/utils/output.js +373 -20
- package/dist/utils/poll.js +27 -0
- package/dist/utils/render.js +27 -27
- package/dist/utils/time.js +208 -0
- package/package.json +7 -5
|
@@ -39,15 +39,14 @@ const api = __importStar(require("../../../api/index"));
|
|
|
39
39
|
const error_1 = require("../../../utils/error");
|
|
40
40
|
const output_1 = require("../../../utils/output");
|
|
41
41
|
const render_1 = require("../../../utils/render");
|
|
42
|
-
const shared_1 = require("../../../cli/commands/shared");
|
|
43
42
|
const colors_1 = require("../../../utils/colors");
|
|
44
43
|
const index_1 = require("../../../api/db/index");
|
|
45
44
|
// ── schema list ──
|
|
46
45
|
async function handleDbSchemaList(opts) {
|
|
47
|
-
const appId =
|
|
46
|
+
const appId = opts.appId;
|
|
48
47
|
const resp = await api.db.getSchema({
|
|
49
48
|
appId,
|
|
50
|
-
format:
|
|
49
|
+
format: 'schema',
|
|
51
50
|
includeStats: true,
|
|
52
51
|
dbBranch: opts.env,
|
|
53
52
|
});
|
|
@@ -57,30 +56,30 @@ async function handleDbSchemaList(opts) {
|
|
|
57
56
|
return;
|
|
58
57
|
}
|
|
59
58
|
if (tables.length === 0) {
|
|
60
|
-
(0, output_1.emit)(
|
|
59
|
+
(0, output_1.emit)('No tables found.');
|
|
61
60
|
return;
|
|
62
61
|
}
|
|
63
62
|
const tty = (0, render_1.isStdoutTty)();
|
|
64
63
|
// PRD 对齐:TTY 表头用 `size`(友好格式),non-TTY 用 `size_bytes`(原始整数)。
|
|
65
64
|
// updated_at 暂时不展示——PG pg_catalog 不存真实表时间,详见 renderDetail 注释。
|
|
66
65
|
const headers = [
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
tty ?
|
|
71
|
-
|
|
66
|
+
'name',
|
|
67
|
+
'description',
|
|
68
|
+
'estimated_row_count',
|
|
69
|
+
tty ? 'size' : 'size_bytes',
|
|
70
|
+
'columns',
|
|
72
71
|
];
|
|
73
72
|
const rows = tables.map((t) => [
|
|
74
73
|
t.name,
|
|
75
|
-
t.description ??
|
|
76
|
-
t.estimated_row_count === null ?
|
|
77
|
-
t.size_bytes === null ?
|
|
74
|
+
t.description ?? '—',
|
|
75
|
+
t.estimated_row_count === null ? '—' : String(t.estimated_row_count),
|
|
76
|
+
t.size_bytes === null ? '—' : tty ? (0, render_1.formatSize)(t.size_bytes) : String(t.size_bytes),
|
|
78
77
|
String(t.columns),
|
|
79
78
|
]);
|
|
80
79
|
(0, output_1.emit)(tty ? (0, render_1.renderAlignedTable)(headers, rows) : (0, render_1.renderTsv)(headers, rows));
|
|
81
80
|
}
|
|
82
81
|
async function handleDbSchemaGet(table, opts) {
|
|
83
|
-
const appId =
|
|
82
|
+
const appId = opts.appId;
|
|
84
83
|
const tty = (0, render_1.isStdoutTty)();
|
|
85
84
|
const forceDdl = Boolean(opts.ddl);
|
|
86
85
|
const wantsStructured = (0, output_1.isJsonMode)() || (tty && !forceDdl);
|
|
@@ -88,13 +87,13 @@ async function handleDbSchemaGet(table, opts) {
|
|
|
88
87
|
// non-TTY 或 --ddl:直接取完整 DDL 输出
|
|
89
88
|
const ddlResp = await api.db.getSchema({
|
|
90
89
|
appId,
|
|
91
|
-
format:
|
|
90
|
+
format: 'ddl',
|
|
92
91
|
tableNames: table,
|
|
93
92
|
dbBranch: opts.env,
|
|
94
93
|
});
|
|
95
94
|
const sql = ddlResp.ddl?.[table];
|
|
96
95
|
if (!sql) {
|
|
97
|
-
throw new error_1.AppError(
|
|
96
|
+
throw new error_1.AppError('TABLE_NOT_FOUND', `Table '${table}' does not exist`, {
|
|
98
97
|
next_actions: [
|
|
99
98
|
`Did you mean another table? Run "miaoda db schema list" to see all tables.`,
|
|
100
99
|
],
|
|
@@ -106,14 +105,14 @@ async function handleDbSchemaGet(table, opts) {
|
|
|
106
105
|
// TTY / JSON:取结构化
|
|
107
106
|
const resp = await api.db.getSchema({
|
|
108
107
|
appId,
|
|
109
|
-
format:
|
|
108
|
+
format: 'schema',
|
|
110
109
|
tableNames: table,
|
|
111
110
|
includeStats: true,
|
|
112
111
|
dbBranch: opts.env,
|
|
113
112
|
});
|
|
114
113
|
const detail = (0, index_1.pickTableDetail)(resp.schema, table);
|
|
115
114
|
if (!detail) {
|
|
116
|
-
throw new error_1.AppError(
|
|
115
|
+
throw new error_1.AppError('TABLE_NOT_FOUND', `Table '${table}' does not exist`, {
|
|
117
116
|
next_actions: [`Did you mean another table? Run "miaoda db schema list" to see all tables.`],
|
|
118
117
|
});
|
|
119
118
|
}
|
|
@@ -124,54 +123,54 @@ async function handleDbSchemaGet(table, opts) {
|
|
|
124
123
|
(0, output_1.emit)(renderDetail(detail, tty));
|
|
125
124
|
}
|
|
126
125
|
function renderDetail(d, tty) {
|
|
127
|
-
const systemFields = d.columns.filter((c) => c.name.startsWith(
|
|
128
|
-
const userFields = d.columns.filter((c) => !c.name.startsWith(
|
|
126
|
+
const systemFields = d.columns.filter((c) => c.name.startsWith('_'));
|
|
127
|
+
const userFields = d.columns.filter((c) => !c.name.startsWith('_'));
|
|
129
128
|
// header 布局:Name / Description / Columns(含"+ N system") / Estimated Rows / Size。
|
|
130
129
|
// 不展示 Created / Updated:PG pg_catalog 不存表创建时间,dataloom 用 OID
|
|
131
130
|
// 构造的伪时间戳(baseTime=2020-01-01 + OID 秒偏移),仅保排序意义、绝对值
|
|
132
131
|
// 误导性强,先去掉。后续如果接 ddl_change_log 取真实时间再加回。
|
|
133
132
|
const header = [
|
|
134
133
|
// 表名做 cyan 强调(spec:spec 里的"命令名/表名"类强调值都走 highlight)
|
|
135
|
-
[
|
|
136
|
-
[
|
|
134
|
+
['Name', tty ? colors_1.c.highlight(d.name) : d.name],
|
|
135
|
+
['Description', d.description ?? '—'],
|
|
137
136
|
[
|
|
138
|
-
|
|
137
|
+
'Columns',
|
|
139
138
|
systemFields.length > 0
|
|
140
139
|
? `${String(userFields.length)} (+ ${String(systemFields.length)} system)`
|
|
141
140
|
: String(userFields.length),
|
|
142
141
|
],
|
|
143
|
-
[
|
|
144
|
-
[
|
|
142
|
+
['Estimated Rows', d.estimated_row_count === null ? '—' : String(d.estimated_row_count)],
|
|
143
|
+
['Size', d.size_bytes === null ? '—' : (0, render_1.formatSize)(d.size_bytes)],
|
|
145
144
|
];
|
|
146
|
-
const colHeaders = [
|
|
145
|
+
const colHeaders = ['column', 'type', 'nullable', 'default', 'comment'];
|
|
147
146
|
const colRows = userFields.map((c) => [
|
|
148
147
|
c.name,
|
|
149
148
|
c.type,
|
|
150
|
-
c.nullable ?
|
|
151
|
-
c.default ??
|
|
152
|
-
c.comment ??
|
|
149
|
+
c.nullable ? 'yes' : 'no',
|
|
150
|
+
c.default ?? '—',
|
|
151
|
+
c.comment ?? '—',
|
|
153
152
|
]);
|
|
154
153
|
const parts = [];
|
|
155
154
|
parts.push((0, render_1.renderKeyValue)(header, tty));
|
|
156
|
-
parts.push(
|
|
155
|
+
parts.push('');
|
|
157
156
|
parts.push(tty ? (0, render_1.renderAlignedTable)(colHeaders, colRows) : (0, render_1.renderTsv)(colHeaders, colRows));
|
|
158
157
|
// Constraints 段(PRIMARY KEY / UNIQUE):表级约束独立成段,与普通索引分离展示
|
|
159
158
|
if (d.constraints.length > 0) {
|
|
160
|
-
parts.push(
|
|
161
|
-
parts.push(tty ?
|
|
159
|
+
parts.push('');
|
|
160
|
+
parts.push(tty ? ' Constraints:' : 'Constraints:');
|
|
162
161
|
for (const c of d.constraints) {
|
|
163
|
-
const line = `${c.type} (${c.columns.join(
|
|
162
|
+
const line = `${c.type} (${c.columns.join(', ')})`;
|
|
164
163
|
parts.push(tty ? ` ${line}` : line);
|
|
165
164
|
}
|
|
166
165
|
}
|
|
167
166
|
// Indexes 段:普通索引,格式 "<name> ON <col1, col2> USING <method>"
|
|
168
167
|
if (d.indexes.length > 0) {
|
|
169
|
-
parts.push(
|
|
170
|
-
parts.push(tty ?
|
|
168
|
+
parts.push('');
|
|
169
|
+
parts.push(tty ? ' Indexes:' : 'Indexes:');
|
|
171
170
|
for (const idx of d.indexes) {
|
|
172
|
-
const line = `${idx.name} ON ${idx.columns.join(
|
|
171
|
+
const line = `${idx.name} ON ${idx.columns.join(', ')} USING ${idx.method}`;
|
|
173
172
|
parts.push(tty ? ` ${line}` : line);
|
|
174
173
|
}
|
|
175
174
|
}
|
|
176
|
-
return parts.join(
|
|
175
|
+
return parts.join('\n');
|
|
177
176
|
}
|
|
@@ -42,7 +42,6 @@ const error_1 = require("../../../utils/error");
|
|
|
42
42
|
const output_1 = require("../../../utils/output");
|
|
43
43
|
const config_1 = require("../../../utils/config");
|
|
44
44
|
const logger_1 = require("../../../utils/logger");
|
|
45
|
-
const shared_1 = require("../../../cli/commands/shared");
|
|
46
45
|
const render_1 = require("../../../utils/render");
|
|
47
46
|
const colors_1 = require("../../../utils/colors");
|
|
48
47
|
const fuzzy_match_1 = require("../../../utils/fuzzy-match");
|
|
@@ -62,10 +61,10 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
62
61
|
* 末尾汇总,--json 输出 data 数组。
|
|
63
62
|
*/
|
|
64
63
|
async function handleDbSql(query, opts) {
|
|
65
|
-
const appId =
|
|
64
|
+
const appId = opts.appId;
|
|
66
65
|
const sql = await readSql(query);
|
|
67
66
|
if (!sql.trim()) {
|
|
68
|
-
throw new error_1.AppError(
|
|
67
|
+
throw new error_1.AppError('ARGS_INVALID', 'Empty SQL (no inline query and stdin is empty)');
|
|
69
68
|
}
|
|
70
69
|
let results;
|
|
71
70
|
try {
|
|
@@ -87,7 +86,7 @@ async function handleDbSql(query, opts) {
|
|
|
87
86
|
(0, output_1.emit)({ data: [] });
|
|
88
87
|
return;
|
|
89
88
|
}
|
|
90
|
-
(0, output_1.emit)(
|
|
89
|
+
(0, output_1.emit)((0, render_1.isStdoutTty)() ? colors_1.c.success('✓ No results') : 'OK No results');
|
|
91
90
|
return;
|
|
92
91
|
}
|
|
93
92
|
if (results.length === 1) {
|
|
@@ -122,7 +121,7 @@ async function handleDbSql(query, opts) {
|
|
|
122
121
|
* statement 都执行成功,无需再校验逐条状态。
|
|
123
122
|
*/
|
|
124
123
|
/** agent runtime 中固定的项目根目录;放在此处便于以后调整。 */
|
|
125
|
-
const AGENT_PROJECT_ROOT =
|
|
124
|
+
const AGENT_PROJECT_ROOT = '/home/gem/workspace/code';
|
|
126
125
|
/** drain 子进程 pipe stream:默认静默模式下消费 chunk 防止缓冲区反压。 */
|
|
127
126
|
function drainChunk(_chunk) {
|
|
128
127
|
// 显式接 chunk 参数让 ESLint 不再当成 empty function;不做任何处理
|
|
@@ -132,7 +131,7 @@ async function maybeSyncAgentSchema(results) {
|
|
|
132
131
|
return;
|
|
133
132
|
const projectRoot = await resolveAgentProjectRoot();
|
|
134
133
|
if (!projectRoot) {
|
|
135
|
-
(0, logger_1.debug)(
|
|
134
|
+
(0, logger_1.debug)('[db sql] agent project root not found or missing gen:db-schema script, skip schema sync');
|
|
136
135
|
return;
|
|
137
136
|
}
|
|
138
137
|
const verbose = (0, config_1.getConfig)().verbose;
|
|
@@ -141,8 +140,8 @@ async function maybeSyncAgentSchema(results) {
|
|
|
141
140
|
(0, logger_1.debug)(`[db sql] DDL detected, running \`npm run gen:db-schema\` in ${projectRoot}`);
|
|
142
141
|
try {
|
|
143
142
|
await new Promise((resolve) => {
|
|
144
|
-
const proc = (0, node_child_process_1.spawn)(
|
|
145
|
-
stdio: [
|
|
143
|
+
const proc = (0, node_child_process_1.spawn)('npm', ['run', 'gen:db-schema'], {
|
|
144
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
146
145
|
cwd: projectRoot,
|
|
147
146
|
});
|
|
148
147
|
// --verbose:实时透传到 stderr 看进度;默认完全静默丢弃。
|
|
@@ -153,15 +152,15 @@ async function maybeSyncAgentSchema(results) {
|
|
|
153
152
|
proc.stderr.pipe(process.stderr);
|
|
154
153
|
}
|
|
155
154
|
else {
|
|
156
|
-
proc.stdout.on(
|
|
157
|
-
proc.stderr.on(
|
|
155
|
+
proc.stdout.on('data', drainChunk);
|
|
156
|
+
proc.stderr.on('data', drainChunk);
|
|
158
157
|
}
|
|
159
158
|
let timedOut = false;
|
|
160
159
|
const timer = setTimeout(() => {
|
|
161
160
|
timedOut = true;
|
|
162
|
-
proc.kill(
|
|
161
|
+
proc.kill('SIGKILL');
|
|
163
162
|
}, TIMEOUT_MS);
|
|
164
|
-
proc.on(
|
|
163
|
+
proc.on('close', (code) => {
|
|
165
164
|
clearTimeout(timer);
|
|
166
165
|
if (timedOut) {
|
|
167
166
|
(0, logger_1.debug)(`[db sql] gen:db-schema timed out after ${String(TIMEOUT_MS / 1000)}s, killed`);
|
|
@@ -170,11 +169,11 @@ async function maybeSyncAgentSchema(results) {
|
|
|
170
169
|
(0, logger_1.debug)(`[db sql] gen:db-schema exited with code ${String(code)}`);
|
|
171
170
|
}
|
|
172
171
|
else {
|
|
173
|
-
(0, logger_1.debug)(
|
|
172
|
+
(0, logger_1.debug)('[db sql] gen:db-schema completed');
|
|
174
173
|
}
|
|
175
174
|
resolve();
|
|
176
175
|
});
|
|
177
|
-
proc.on(
|
|
176
|
+
proc.on('error', (err) => {
|
|
178
177
|
clearTimeout(timer);
|
|
179
178
|
(0, logger_1.debug)(`[db sql] gen:db-schema spawn error: ${err.message}`);
|
|
180
179
|
resolve();
|
|
@@ -194,10 +193,10 @@ async function maybeSyncAgentSchema(results) {
|
|
|
194
193
|
*/
|
|
195
194
|
async function resolveAgentProjectRoot() {
|
|
196
195
|
try {
|
|
197
|
-
const pkgPath = node_path_1.default.join(AGENT_PROJECT_ROOT,
|
|
198
|
-
const raw = await node_fs_1.promises.readFile(pkgPath,
|
|
196
|
+
const pkgPath = node_path_1.default.join(AGENT_PROJECT_ROOT, 'package.json');
|
|
197
|
+
const raw = await node_fs_1.promises.readFile(pkgPath, 'utf8');
|
|
199
198
|
const pkg = JSON.parse(raw);
|
|
200
|
-
if (pkg.scripts?.[
|
|
199
|
+
if (pkg.scripts?.['gen:db-schema'])
|
|
201
200
|
return AGENT_PROJECT_ROOT;
|
|
202
201
|
}
|
|
203
202
|
catch {
|
|
@@ -207,7 +206,7 @@ async function resolveAgentProjectRoot() {
|
|
|
207
206
|
}
|
|
208
207
|
/** 是否有任意一条 statement 是 DDL(CREATE / ALTER / DROP / GRANT / TRUNCATE / COMMENT 等)。 */
|
|
209
208
|
function hasDdl(results) {
|
|
210
|
-
return results.some((r) => (0, index_1.parseSqlResult)(r).kind ===
|
|
209
|
+
return results.some((r) => (0, index_1.parseSqlResult)(r).kind === 'ddl');
|
|
211
210
|
}
|
|
212
211
|
/** 读取 stdin 并返回完整 SQL 文本(stdin 不是 TTY 即认为被 pipe)。 */
|
|
213
212
|
async function readSql(inline) {
|
|
@@ -215,17 +214,17 @@ async function readSql(inline) {
|
|
|
215
214
|
return inline;
|
|
216
215
|
const stdin = process.stdin;
|
|
217
216
|
if (stdin.isTTY)
|
|
218
|
-
return
|
|
217
|
+
return '';
|
|
219
218
|
return new Promise((resolve, reject) => {
|
|
220
|
-
let data =
|
|
221
|
-
process.stdin.setEncoding(
|
|
222
|
-
process.stdin.on(
|
|
219
|
+
let data = '';
|
|
220
|
+
process.stdin.setEncoding('utf8');
|
|
221
|
+
process.stdin.on('data', (chunk) => {
|
|
223
222
|
data += chunk;
|
|
224
223
|
});
|
|
225
|
-
process.stdin.on(
|
|
224
|
+
process.stdin.on('end', () => {
|
|
226
225
|
resolve(data);
|
|
227
226
|
});
|
|
228
|
-
process.stdin.on(
|
|
227
|
+
process.stdin.on('error', reject);
|
|
229
228
|
});
|
|
230
229
|
}
|
|
231
230
|
function renderSingle(raw) {
|
|
@@ -235,9 +234,9 @@ function renderSingle(raw) {
|
|
|
235
234
|
(0, output_1.emit)(toJson(parsed));
|
|
236
235
|
return;
|
|
237
236
|
}
|
|
238
|
-
if (parsed.kind ===
|
|
237
|
+
if (parsed.kind === 'select') {
|
|
239
238
|
if (parsed.rows.length === 0) {
|
|
240
|
-
(0, output_1.emit)(
|
|
239
|
+
(0, output_1.emit)('(0 rows)');
|
|
241
240
|
return;
|
|
242
241
|
}
|
|
243
242
|
const cols = collectColumns(parsed.rows);
|
|
@@ -245,22 +244,22 @@ function renderSingle(raw) {
|
|
|
245
244
|
(0, output_1.emit)(tty ? (0, render_1.renderAlignedTable)(cols, rows) : (0, render_1.renderTsv)(cols, rows));
|
|
246
245
|
return;
|
|
247
246
|
}
|
|
248
|
-
if (parsed.kind ===
|
|
247
|
+
if (parsed.kind === 'dml') {
|
|
249
248
|
const verb = dmlVerb(parsed.sqlType);
|
|
250
249
|
(0, output_1.emit)(tty
|
|
251
|
-
? colors_1.c.success(`✓ ${String(parsed.affectedRows)} row${parsed.affectedRows === 1 ?
|
|
250
|
+
? colors_1.c.success(`✓ ${String(parsed.affectedRows)} row${parsed.affectedRows === 1 ? '' : 's'} ${verb}`)
|
|
252
251
|
: `OK ${String(parsed.affectedRows)} rows ${verb}`);
|
|
253
252
|
return;
|
|
254
253
|
}
|
|
255
254
|
// DDL
|
|
256
|
-
(0, output_1.emit)(tty ? colors_1.c.success(
|
|
255
|
+
(0, output_1.emit)(tty ? colors_1.c.success('✓ DDL executed') : 'OK DDL executed');
|
|
257
256
|
}
|
|
258
257
|
function toJson(parsed) {
|
|
259
|
-
if (parsed.kind ===
|
|
258
|
+
if (parsed.kind === 'select') {
|
|
260
259
|
// PRD 单 SELECT:data 直接是行数组(按 --json 字段投影裁剪)
|
|
261
260
|
return { data: projectRows(parsed.rows) };
|
|
262
261
|
}
|
|
263
|
-
if (parsed.kind ===
|
|
262
|
+
if (parsed.kind === 'dml') {
|
|
264
263
|
// PRD 单 DML:data = {command, rows_affected}
|
|
265
264
|
return {
|
|
266
265
|
data: {
|
|
@@ -278,10 +277,10 @@ function toJson(parsed) {
|
|
|
278
277
|
* {command:"SELECT", rows:[...]}(PRD 约定,避免数组里嵌套数组造成歧义)。
|
|
279
278
|
*/
|
|
280
279
|
function toMultiElement(parsed) {
|
|
281
|
-
if (parsed.kind ===
|
|
282
|
-
return { command:
|
|
280
|
+
if (parsed.kind === 'select') {
|
|
281
|
+
return { command: 'SELECT', rows: projectRows(parsed.rows) };
|
|
283
282
|
}
|
|
284
|
-
if (parsed.kind ===
|
|
283
|
+
if (parsed.kind === 'dml') {
|
|
285
284
|
return { command: parsed.sqlType, rows_affected: parsed.affectedRows };
|
|
286
285
|
}
|
|
287
286
|
// DDL:用后端给的细粒度 command
|
|
@@ -307,10 +306,10 @@ function projectRows(rows) {
|
|
|
307
306
|
/** 读取 --json [fields]:返回字段列表;boolean true 或 undefined 返回 null(不裁剪)。 */
|
|
308
307
|
function parseJsonFields() {
|
|
309
308
|
const v = (0, config_1.getConfig)().json;
|
|
310
|
-
if (typeof v !==
|
|
309
|
+
if (typeof v !== 'string' || v === '')
|
|
311
310
|
return null;
|
|
312
311
|
return v
|
|
313
|
-
.split(
|
|
312
|
+
.split(',')
|
|
314
313
|
.map((s) => s.trim())
|
|
315
314
|
.filter((s) => s.length > 0);
|
|
316
315
|
}
|
|
@@ -321,9 +320,9 @@ function renderMultiPretty(results) {
|
|
|
321
320
|
for (let i = 0; i < results.length; i++) {
|
|
322
321
|
const parsed = (0, index_1.parseSqlResult)(results[i]);
|
|
323
322
|
const idx = i + 1;
|
|
324
|
-
if (parsed.kind ===
|
|
323
|
+
if (parsed.kind === 'select') {
|
|
325
324
|
const n = parsed.rows.length;
|
|
326
|
-
lines.push(`Statement ${String(idx)}: SELECT (${String(n)} row${n === 1 ?
|
|
325
|
+
lines.push(`Statement ${String(idx)}: SELECT (${String(n)} row${n === 1 ? '' : 's'})`);
|
|
327
326
|
if (n > 0) {
|
|
328
327
|
const cols = collectColumns(parsed.rows);
|
|
329
328
|
const tbl = parsed.rows.map((r) => cols.map((col) => formatCell(r[col], tty)));
|
|
@@ -331,37 +330,37 @@ function renderMultiPretty(results) {
|
|
|
331
330
|
}
|
|
332
331
|
// 块间空行(最后一条不留)
|
|
333
332
|
if (i < results.length - 1)
|
|
334
|
-
lines.push(
|
|
333
|
+
lines.push('');
|
|
335
334
|
continue;
|
|
336
335
|
}
|
|
337
|
-
if (parsed.kind ===
|
|
336
|
+
if (parsed.kind === 'dml') {
|
|
338
337
|
const verb = dmlVerb(parsed.sqlType);
|
|
339
338
|
const n = parsed.affectedRows;
|
|
340
|
-
const body = `${String(n)} row${n === 1 ?
|
|
341
|
-
lines.push(`Statement ${String(idx)}: ${tty ? colors_1.c.success(
|
|
339
|
+
const body = `${String(n)} row${n === 1 ? '' : 's'} ${verb}`;
|
|
340
|
+
lines.push(`Statement ${String(idx)}: ${tty ? colors_1.c.success('✓ ' + body) : body}`);
|
|
342
341
|
continue;
|
|
343
342
|
}
|
|
344
343
|
// DDL
|
|
345
|
-
lines.push(`Statement ${String(idx)}: ${tty ? colors_1.c.success(
|
|
344
|
+
lines.push(`Statement ${String(idx)}: ${tty ? colors_1.c.success('✓ DDL executed') : 'DDL executed'}`);
|
|
346
345
|
}
|
|
347
346
|
// 汇总行:所有 statement 都跑完了
|
|
348
347
|
lines.push(tty
|
|
349
348
|
? colors_1.c.success(`✓ ${String(results.length)} statements executed`)
|
|
350
349
|
: `OK ${String(results.length)} statements executed`);
|
|
351
|
-
(0, output_1.emit)(lines.join(
|
|
350
|
+
(0, output_1.emit)(lines.join('\n'));
|
|
352
351
|
}
|
|
353
352
|
function dmlVerb(type) {
|
|
354
353
|
switch (type) {
|
|
355
|
-
case
|
|
356
|
-
return
|
|
357
|
-
case
|
|
358
|
-
return
|
|
359
|
-
case
|
|
360
|
-
return
|
|
361
|
-
case
|
|
362
|
-
return
|
|
363
|
-
case
|
|
364
|
-
return
|
|
354
|
+
case 'INSERT':
|
|
355
|
+
return 'inserted';
|
|
356
|
+
case 'UPDATE':
|
|
357
|
+
return 'updated';
|
|
358
|
+
case 'DELETE':
|
|
359
|
+
return 'deleted';
|
|
360
|
+
case 'MERGE':
|
|
361
|
+
return 'merged';
|
|
362
|
+
case 'DML':
|
|
363
|
+
return 'affected'; // 未识别子类的兜底
|
|
365
364
|
}
|
|
366
365
|
}
|
|
367
366
|
/** 从第一行收集列顺序;缺失列保留空白(兼容稀疏行)。 */
|
|
@@ -381,9 +380,9 @@ function collectColumns(rows) {
|
|
|
381
380
|
*/
|
|
382
381
|
function formatCell(v, tty) {
|
|
383
382
|
if (v === null || v === undefined) {
|
|
384
|
-
return tty ? colors_1.c.muted(
|
|
383
|
+
return tty ? colors_1.c.muted('NULL') : 'NULL';
|
|
385
384
|
}
|
|
386
|
-
if (typeof v ===
|
|
385
|
+
if (typeof v === 'string') {
|
|
387
386
|
// TTY 下检测到 ISO 8601 时间字符串 → 转成相对时间("3h ago" / "2d ago" /
|
|
388
387
|
// "2026-03-15"),方便 _created_at / _updated_at 等列直观可读
|
|
389
388
|
if (tty && ISO_TIMESTAMP_RE.test(v) && !Number.isNaN(Date.parse(v))) {
|
|
@@ -391,7 +390,7 @@ function formatCell(v, tty) {
|
|
|
391
390
|
}
|
|
392
391
|
return v;
|
|
393
392
|
}
|
|
394
|
-
if (typeof v ===
|
|
393
|
+
if (typeof v === 'number' || typeof v === 'boolean')
|
|
395
394
|
return String(v);
|
|
396
395
|
// object / array → JSON
|
|
397
396
|
return JSON.stringify(v);
|
|
@@ -488,7 +487,7 @@ async function loadTableMap(ctx) {
|
|
|
488
487
|
try {
|
|
489
488
|
const resp = await api.db.getSchema({
|
|
490
489
|
appId: ctx.appId,
|
|
491
|
-
format:
|
|
490
|
+
format: 'schema',
|
|
492
491
|
dbBranch: ctx.env,
|
|
493
492
|
});
|
|
494
493
|
return extractTableFieldMap(resp.schema);
|
|
@@ -501,7 +500,7 @@ function enrichRelationNotExist(err, relation, tableMap) {
|
|
|
501
500
|
const guess = (0, fuzzy_match_1.suggest)(relation, Object.keys(tableMap));
|
|
502
501
|
err.next_actions.push(guess
|
|
503
502
|
? `Did you mean '${guess}'? Run \`miaoda db schema list\` to see all tables.`
|
|
504
|
-
:
|
|
503
|
+
: 'Run `miaoda db schema list` to see all tables.');
|
|
505
504
|
}
|
|
506
505
|
function enrichColumnNotExist(err, colMatch, tableMap) {
|
|
507
506
|
const colName = colMatch[1];
|
|
@@ -523,7 +522,7 @@ function enrichColumnNotExist(err, colMatch, tableMap) {
|
|
|
523
522
|
const guess = (0, fuzzy_match_1.suggest)(colName, allCols);
|
|
524
523
|
err.next_actions.push(guess
|
|
525
524
|
? `Did you mean '${guess}'? Run \`miaoda db schema get <table>\` to see columns.`
|
|
526
|
-
:
|
|
525
|
+
: 'Run `miaoda db schema get <table>` to see all columns.');
|
|
527
526
|
}
|
|
528
527
|
/**
|
|
529
528
|
* 扫整段 SQL 找一个看似"拼错的关键字"token:纯字母 + 长度 ≥ 3 + 不是已知 keyword
|
|
@@ -594,7 +593,7 @@ function enrichMultiStatementError(err, sql) {
|
|
|
594
593
|
}
|
|
595
594
|
// rolled_back=true 时追加 spec hint:"Transaction rolled back; no changes persisted."
|
|
596
595
|
if (err.rolled_back) {
|
|
597
|
-
err.next_actions.push(
|
|
596
|
+
err.next_actions.push('Transaction rolled back; no changes persisted.');
|
|
598
597
|
}
|
|
599
598
|
}
|
|
600
599
|
/**
|
|
@@ -606,9 +605,9 @@ function enrichMultiStatementError(err, sql) {
|
|
|
606
605
|
function inferRolledBack(completed) {
|
|
607
606
|
let depth = 0;
|
|
608
607
|
for (const e of completed) {
|
|
609
|
-
if (e.command ===
|
|
608
|
+
if (e.command === 'BEGIN')
|
|
610
609
|
depth++;
|
|
611
|
-
else if (e.command ===
|
|
610
|
+
else if (e.command === 'COMMIT' || e.command === 'ROLLBACK')
|
|
612
611
|
depth--;
|
|
613
612
|
}
|
|
614
613
|
return depth > 0;
|
|
@@ -631,25 +630,25 @@ function writeMultiStatementBreakdown(err, completed) {
|
|
|
631
630
|
const lines = [];
|
|
632
631
|
for (let i = 0; i < completed.length; i++) {
|
|
633
632
|
const e = completed[i];
|
|
634
|
-
const verb = e.command ===
|
|
633
|
+
const verb = e.command === 'BEGIN' || e.command === 'COMMIT' || e.command === 'ROLLBACK'
|
|
635
634
|
? e.command
|
|
636
635
|
: describeCompleted(e);
|
|
637
|
-
lines.push(`Statement ${String(i + 1)}: ${tty ? colors_1.c.success(
|
|
636
|
+
lines.push(`Statement ${String(i + 1)}: ${tty ? colors_1.c.success('✓ ') : '✓ '}${verb}`);
|
|
638
637
|
}
|
|
639
638
|
// 失败那条
|
|
640
639
|
const failedIdx = err.statement_index ?? completed.length;
|
|
641
|
-
lines.push(`Statement ${String(failedIdx + 1)}: ${tty ? colors_1.c.fail(
|
|
642
|
-
process.stderr.write(lines.join(
|
|
640
|
+
lines.push(`Statement ${String(failedIdx + 1)}: ${tty ? colors_1.c.fail('✗ ') : '✗ '}${err.message}`);
|
|
641
|
+
process.stderr.write(lines.join('\n') + '\n\n');
|
|
643
642
|
}
|
|
644
643
|
/** completed 单条结果的人类可读描述(用于 stderr breakdown 行尾)。 */
|
|
645
644
|
function describeCompleted(e) {
|
|
646
645
|
const r = e;
|
|
647
|
-
if (r.command ===
|
|
648
|
-
return `SELECT (${String(r.rows.length)} row${r.rows.length === 1 ?
|
|
646
|
+
if (r.command === 'SELECT' && Array.isArray(r.rows)) {
|
|
647
|
+
return `SELECT (${String(r.rows.length)} row${r.rows.length === 1 ? '' : 's'})`;
|
|
649
648
|
}
|
|
650
|
-
if (typeof r.rows_affected ===
|
|
649
|
+
if (typeof r.rows_affected === 'number') {
|
|
651
650
|
const verb = dmlVerb(r.command);
|
|
652
|
-
return `${String(r.rows_affected)} row${r.rows_affected === 1 ?
|
|
651
|
+
return `${String(r.rows_affected)} row${r.rows_affected === 1 ? '' : 's'} ${verb}`;
|
|
653
652
|
}
|
|
654
653
|
return `${r.command} executed`;
|
|
655
654
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
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.handleDeploy = handleDeploy;
|
|
37
|
+
const api = __importStar(require("../../../api/index"));
|
|
38
|
+
const output_1 = require("../../../utils/output");
|
|
39
|
+
const index_1 = require("../../../api/deploy/index");
|
|
40
|
+
const polling_1 = require("./polling");
|
|
41
|
+
const format_1 = require("./format");
|
|
42
|
+
/** miaoda deploy [--branch ...] [--wait] [--timeout 300] */
|
|
43
|
+
async function handleDeploy(opts) {
|
|
44
|
+
const appID = opts.appId;
|
|
45
|
+
const resp = await api.deploy.createRelease({ appID, branch: opts.branch });
|
|
46
|
+
const pipelineTaskID = resp.pipelineTaskID ?? '';
|
|
47
|
+
if (!opts.wait) {
|
|
48
|
+
if (!(0, output_1.isJsonMode)()) {
|
|
49
|
+
process.stdout.write(`✓ Deployment triggered. (deploy-id: ${pipelineTaskID})\n`);
|
|
50
|
+
process.stdout.write(` hint: No terminal status yet. Poll every 2s with \`miaoda deploy get ${pipelineTaskID} --json\`; if no deploy-id is visible, first get it from \`miaoda deploy history --limit 1 --json\`.\n`);
|
|
51
|
+
}
|
|
52
|
+
(0, output_1.emit)({
|
|
53
|
+
data: { pipelineTaskID },
|
|
54
|
+
next_cursor: null,
|
|
55
|
+
has_more: false,
|
|
56
|
+
});
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (!(0, output_1.isJsonMode)()) {
|
|
60
|
+
process.stdout.write(`Waiting for deployment to complete... (deploy-id: ${pipelineTaskID})\n`);
|
|
61
|
+
}
|
|
62
|
+
const detail = await (0, polling_1.waitForPipeline)({
|
|
63
|
+
appID,
|
|
64
|
+
pipelineTaskID,
|
|
65
|
+
timeoutSec: opts.timeout ?? 300,
|
|
66
|
+
});
|
|
67
|
+
if (!(0, output_1.isJsonMode)()) {
|
|
68
|
+
if (detail.status === index_1.NodeStatus.SUCCESS) {
|
|
69
|
+
process.stdout.write('✓ 发布成功\n');
|
|
70
|
+
}
|
|
71
|
+
else if (detail.status === index_1.NodeStatus.FAILED) {
|
|
72
|
+
process.stdout.write('✗ 发布失败\n');
|
|
73
|
+
process.stdout.write(` hint: Run \`miaoda deploy error-log ${pipelineTaskID}\` to view error logs\n`);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
process.stdout.write('• 发布已取消\n');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
(0, output_1.emit)({
|
|
80
|
+
data: { pipelineTaskID, detail: (0, format_1.formatPipelineInstance)(detail) },
|
|
81
|
+
next_cursor: null,
|
|
82
|
+
has_more: false,
|
|
83
|
+
});
|
|
84
|
+
}
|