@lark-apaas/miaoda-cli 0.1.3 → 0.1.4
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/api/app/api.js +3 -3
- package/dist/api/app/schemas.js +43 -43
- package/dist/api/db/api.js +398 -55
- package/dist/api/db/client.js +155 -28
- package/dist/api/db/index.js +12 -1
- package/dist/api/db/parsers.js +20 -20
- package/dist/api/db/sql-keywords.js +87 -87
- package/dist/api/deploy/api.js +5 -5
- package/dist/api/deploy/schemas.js +32 -32
- 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/observability/api.js +6 -6
- package/dist/api/observability/schemas.js +14 -14
- package/dist/api/plugin/api.js +31 -31
- package/dist/cli/commands/app/index.js +12 -12
- package/dist/cli/commands/db/index.js +602 -54
- package/dist/cli/commands/deploy/index.js +28 -28
- package/dist/cli/commands/file/index.js +85 -58
- package/dist/cli/commands/observability/index.js +69 -69
- package/dist/cli/commands/plugin/index.js +27 -27
- package/dist/cli/commands/shared.js +10 -10
- package/dist/cli/handlers/app/update.js +2 -2
- package/dist/cli/handlers/db/_destructive.js +67 -0
- package/dist/cli/handlers/db/_env.js +26 -0
- package/dist/cli/handlers/db/_operator.js +35 -0
- package/dist/cli/handlers/db/audit.js +383 -0
- package/dist/cli/handlers/db/changelog.js +160 -0
- package/dist/cli/handlers/db/data.js +32 -31
- package/dist/cli/handlers/db/index.js +17 -1
- package/dist/cli/handlers/db/migration.js +234 -0
- package/dist/cli/handlers/db/quota.js +68 -0
- package/dist/cli/handlers/db/recovery.js +413 -0
- package/dist/cli/handlers/db/schema.js +33 -33
- package/dist/cli/handlers/db/sql.js +69 -69
- package/dist/cli/handlers/deploy/deploy.js +4 -4
- package/dist/cli/handlers/deploy/error-log.js +1 -1
- package/dist/cli/handlers/deploy/get.js +3 -3
- package/dist/cli/handlers/deploy/polling.js +11 -11
- package/dist/cli/handlers/file/cp.js +30 -30
- package/dist/cli/handlers/file/index.js +3 -1
- package/dist/cli/handlers/file/ls.js +5 -5
- package/dist/cli/handlers/file/quota.js +66 -0
- package/dist/cli/handlers/file/rm.js +32 -30
- package/dist/cli/handlers/file/sign.js +3 -3
- package/dist/cli/handlers/file/stat.js +10 -9
- package/dist/cli/handlers/observability/analytics.js +47 -47
- package/dist/cli/handlers/observability/helpers.js +2 -2
- package/dist/cli/handlers/observability/log.js +9 -9
- package/dist/cli/handlers/observability/metric.js +26 -26
- package/dist/cli/handlers/observability/trace.js +5 -5
- 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 +12 -12
- package/dist/utils/args.js +1 -1
- package/dist/utils/colors.js +2 -2
- package/dist/utils/config.js +2 -2
- package/dist/utils/devops-error.js +9 -9
- package/dist/utils/error.js +2 -2
- package/dist/utils/git.js +4 -4
- package/dist/utils/http.js +19 -19
- package/dist/utils/index.js +3 -1
- package/dist/utils/output.js +67 -45
- package/dist/utils/poll.js +35 -0
- package/dist/utils/render.js +27 -27
- package/dist/utils/spinner.js +46 -0
- package/dist/utils/time.js +47 -42
- package/package.json +1 -1
|
@@ -38,39 +38,40 @@ exports.handleDbDataExport = handleDbDataExport;
|
|
|
38
38
|
const fs = __importStar(require("node:fs/promises"));
|
|
39
39
|
const path = __importStar(require("node:path"));
|
|
40
40
|
const api = __importStar(require("../../../api/index"));
|
|
41
|
+
const colors_1 = require("../../../utils/colors");
|
|
41
42
|
const error_1 = require("../../../utils/error");
|
|
42
43
|
const output_1 = require("../../../utils/output");
|
|
43
44
|
const render_1 = require("../../../utils/render");
|
|
44
|
-
//
|
|
45
|
+
// import / export 体积上限
|
|
45
46
|
const MAX_SIZE_BYTES = 1 * 1024 * 1024; // 1 MB
|
|
46
47
|
const MAX_ROWS = 5000;
|
|
47
48
|
async function handleDbDataImport(file, opts) {
|
|
48
49
|
const appId = opts.appId;
|
|
49
50
|
const ext = path.extname(file).toLowerCase();
|
|
50
|
-
const format = resolveFormat(opts.format, ext,
|
|
51
|
+
const format = resolveFormat(opts.format, ext, 'import');
|
|
51
52
|
let body;
|
|
52
53
|
try {
|
|
53
54
|
body = await fs.readFile(file);
|
|
54
55
|
}
|
|
55
56
|
catch (err) {
|
|
56
57
|
const code = err.code;
|
|
57
|
-
if (code ===
|
|
58
|
-
throw new error_1.AppError(
|
|
59
|
-
next_actions: [
|
|
58
|
+
if (code === 'ENOENT') {
|
|
59
|
+
throw new error_1.AppError('IMPORT_FILE_NOT_FOUND', `Local file '${file}' does not exist`, {
|
|
60
|
+
next_actions: ['Check the file path.'],
|
|
60
61
|
});
|
|
61
62
|
}
|
|
62
63
|
throw err;
|
|
63
64
|
}
|
|
64
65
|
if (body.length > MAX_SIZE_BYTES) {
|
|
65
|
-
throw new error_1.AppError(
|
|
66
|
+
throw new error_1.AppError('IMPORT_SIZE_EXCEEDED', `Import exceeds 1 MB limit (file is ${String(body.length)} bytes)`, { next_actions: ['Split the file into chunks of ≤ 5000 rows / 1 MB and import separately.'] });
|
|
66
67
|
}
|
|
67
68
|
const rowCount = countRows(body, format);
|
|
68
69
|
if (rowCount > MAX_ROWS) {
|
|
69
|
-
throw new error_1.AppError(
|
|
70
|
+
throw new error_1.AppError('IMPORT_ROWS_EXCEEDED', `Import exceeds 5000 rows limit (file has ${String(rowCount)} rows)`, { next_actions: ['Split the file into chunks of ≤ 5000 rows / 1 MB and import separately.'] });
|
|
70
71
|
}
|
|
71
72
|
const tableName = opts.table ?? path.basename(file, ext);
|
|
72
73
|
if (!tableName) {
|
|
73
|
-
throw new error_1.AppError(
|
|
74
|
+
throw new error_1.AppError('ARGS_INVALID', 'Cannot infer target table from file name; specify --table');
|
|
74
75
|
}
|
|
75
76
|
const result = await api.db.importData({
|
|
76
77
|
appId,
|
|
@@ -90,28 +91,28 @@ async function handleDbDataImport(file, opts) {
|
|
|
90
91
|
}
|
|
91
92
|
const tty = (0, render_1.isStdoutTty)();
|
|
92
93
|
(0, output_1.emit)(tty
|
|
93
|
-
? `✓ Imported ${file} → table '${result.tableName}' (${String(result.recordCount)} rows)`
|
|
94
|
+
? colors_1.c.success(`✓ Imported ${file} → table '${result.tableName}' (${String(result.recordCount)} rows)`)
|
|
94
95
|
: `OK Imported ${file} -> table '${result.tableName}' (${String(result.recordCount)} rows)`);
|
|
95
96
|
}
|
|
96
97
|
async function handleDbDataExport(table, opts) {
|
|
97
98
|
const appId = opts.appId;
|
|
98
|
-
const format = resolveFormat(opts.format, undefined,
|
|
99
|
+
const format = resolveFormat(opts.format, undefined, 'export', 'csv');
|
|
99
100
|
const outputPath = opts.file ?? `${table}.${format}`;
|
|
100
101
|
const limit = opts.limit ? Number(opts.limit) : MAX_ROWS;
|
|
101
102
|
if (!Number.isInteger(limit) || limit <= 0 || limit > MAX_ROWS) {
|
|
102
|
-
throw new error_1.AppError(
|
|
103
|
+
throw new error_1.AppError('ARGS_INVALID', `--limit must be a positive integer ≤ ${String(MAX_ROWS)}`);
|
|
103
104
|
}
|
|
104
105
|
if (!opts.force) {
|
|
105
106
|
try {
|
|
106
107
|
await fs.access(outputPath);
|
|
107
|
-
throw new error_1.AppError(
|
|
108
|
-
next_actions: [
|
|
108
|
+
throw new error_1.AppError('FILE_ALREADY_EXISTS', `Output file '${outputPath}' already exists`, {
|
|
109
|
+
next_actions: ['Use -f to specify a different path, or --force to overwrite.'],
|
|
109
110
|
});
|
|
110
111
|
}
|
|
111
112
|
catch (err) {
|
|
112
113
|
if (err instanceof error_1.AppError)
|
|
113
114
|
throw err;
|
|
114
|
-
if (err.code !==
|
|
115
|
+
if (err.code !== 'ENOENT')
|
|
115
116
|
throw err;
|
|
116
117
|
}
|
|
117
118
|
}
|
|
@@ -122,7 +123,7 @@ async function handleDbDataExport(table, opts) {
|
|
|
122
123
|
limit,
|
|
123
124
|
});
|
|
124
125
|
if (result.body.length > MAX_SIZE_BYTES) {
|
|
125
|
-
throw new error_1.AppError(
|
|
126
|
+
throw new error_1.AppError('EXPORT_SIZE_EXCEEDED', `Export exceeds 1 MB limit (body is ${String(result.body.length)} bytes)`, {
|
|
126
127
|
next_actions: [
|
|
127
128
|
`Filter the table with "miaoda db sql" (e.g. WHERE/LIMIT) and export smaller subsets.`,
|
|
128
129
|
],
|
|
@@ -144,33 +145,33 @@ async function handleDbDataExport(table, opts) {
|
|
|
144
145
|
}
|
|
145
146
|
const tty = (0, render_1.isStdoutTty)();
|
|
146
147
|
(0, output_1.emit)(tty
|
|
147
|
-
? `✓ Exported ${table} → ${outputPath} (${String(rows)} rows)`
|
|
148
|
+
? colors_1.c.success(`✓ Exported ${table} → ${outputPath} (${String(rows)} rows)`)
|
|
148
149
|
: `OK Exported ${table} -> ${outputPath} (${String(rows)} rows)`);
|
|
149
150
|
}
|
|
150
151
|
// ── 共用辅助 ──
|
|
151
152
|
function resolveFormat(explicit, ext, scope, fallback) {
|
|
152
|
-
const raw = (explicit ?? ext ?? fallback ??
|
|
153
|
-
if (raw ===
|
|
154
|
-
return
|
|
155
|
-
if (raw ===
|
|
156
|
-
return
|
|
153
|
+
const raw = (explicit ?? ext ?? fallback ?? '').replace(/^\./, '').toLowerCase();
|
|
154
|
+
if (raw === 'csv')
|
|
155
|
+
return 'csv';
|
|
156
|
+
if (raw === 'json')
|
|
157
|
+
return 'json';
|
|
157
158
|
// sql 仅 export 路径接受 —— import 端后端仍只支持 csv/json。
|
|
158
|
-
if (raw ===
|
|
159
|
-
return
|
|
160
|
-
const code = scope ===
|
|
161
|
-
throw new error_1.AppError(code, `Unrecognized format '${raw ||
|
|
159
|
+
if (raw === 'sql' && scope === 'export')
|
|
160
|
+
return 'sql';
|
|
161
|
+
const code = scope === 'import' ? 'IMPORT_FORMAT_UNSUPPORTED' : 'EXPORT_FORMAT_UNSUPPORTED';
|
|
162
|
+
throw new error_1.AppError(code, `Unrecognized format '${raw || '(unspecified)'}'`, {
|
|
162
163
|
next_actions: [
|
|
163
|
-
scope ===
|
|
164
|
-
?
|
|
165
|
-
:
|
|
164
|
+
scope === 'import'
|
|
165
|
+
? 'Supported formats: .csv, .json. Convert the file first, or rename with the correct extension.'
|
|
166
|
+
: 'Supported formats: csv, json, sql. Pass --format csv|json|sql.',
|
|
166
167
|
],
|
|
167
168
|
});
|
|
168
169
|
}
|
|
169
170
|
function countRows(body, format) {
|
|
170
|
-
if (format ===
|
|
171
|
+
if (format === 'csv') {
|
|
171
172
|
// 粗估:非空行数 - 表头 1 行
|
|
172
173
|
let lines = 0;
|
|
173
|
-
const text = body.toString(
|
|
174
|
+
const text = body.toString('utf8');
|
|
174
175
|
for (const line of text.split(/\r?\n/)) {
|
|
175
176
|
if (line.length > 0)
|
|
176
177
|
lines += 1;
|
|
@@ -179,7 +180,7 @@ function countRows(body, format) {
|
|
|
179
180
|
}
|
|
180
181
|
// JSON:期望是顶层数组
|
|
181
182
|
try {
|
|
182
|
-
const parsed = JSON.parse(body.toString(
|
|
183
|
+
const parsed = JSON.parse(body.toString('utf8'));
|
|
183
184
|
if (Array.isArray(parsed))
|
|
184
185
|
return parsed.length;
|
|
185
186
|
return 1;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.handleDbDataExport = exports.handleDbDataImport = exports.handleDbSchemaGet = exports.handleDbSchemaList = exports.handleDbSql = void 0;
|
|
3
|
+
exports.handleDbQuota = exports.handleDbRecoveryApply = exports.handleDbRecoveryDiff = exports.handleDbMigrationApply = exports.handleDbMigrationDiff = exports.handleDbMigrationInit = exports.handleDbAuditList = exports.handleDbAuditDisable = exports.handleDbAuditEnable = exports.handleDbAuditStatus = exports.handleDbChangelog = exports.handleDbDataExport = exports.handleDbDataImport = exports.handleDbSchemaGet = exports.handleDbSchemaList = exports.handleDbSql = void 0;
|
|
4
4
|
var sql_1 = require("./sql");
|
|
5
5
|
Object.defineProperty(exports, "handleDbSql", { enumerable: true, get: function () { return sql_1.handleDbSql; } });
|
|
6
6
|
var schema_1 = require("./schema");
|
|
@@ -9,3 +9,19 @@ Object.defineProperty(exports, "handleDbSchemaGet", { enumerable: true, get: fun
|
|
|
9
9
|
var data_1 = require("./data");
|
|
10
10
|
Object.defineProperty(exports, "handleDbDataImport", { enumerable: true, get: function () { return data_1.handleDbDataImport; } });
|
|
11
11
|
Object.defineProperty(exports, "handleDbDataExport", { enumerable: true, get: function () { return data_1.handleDbDataExport; } });
|
|
12
|
+
var changelog_1 = require("./changelog");
|
|
13
|
+
Object.defineProperty(exports, "handleDbChangelog", { enumerable: true, get: function () { return changelog_1.handleDbChangelog; } });
|
|
14
|
+
var audit_1 = require("./audit");
|
|
15
|
+
Object.defineProperty(exports, "handleDbAuditStatus", { enumerable: true, get: function () { return audit_1.handleDbAuditStatus; } });
|
|
16
|
+
Object.defineProperty(exports, "handleDbAuditEnable", { enumerable: true, get: function () { return audit_1.handleDbAuditEnable; } });
|
|
17
|
+
Object.defineProperty(exports, "handleDbAuditDisable", { enumerable: true, get: function () { return audit_1.handleDbAuditDisable; } });
|
|
18
|
+
Object.defineProperty(exports, "handleDbAuditList", { enumerable: true, get: function () { return audit_1.handleDbAuditList; } });
|
|
19
|
+
var migration_1 = require("./migration");
|
|
20
|
+
Object.defineProperty(exports, "handleDbMigrationInit", { enumerable: true, get: function () { return migration_1.handleDbMigrationInit; } });
|
|
21
|
+
Object.defineProperty(exports, "handleDbMigrationDiff", { enumerable: true, get: function () { return migration_1.handleDbMigrationDiff; } });
|
|
22
|
+
Object.defineProperty(exports, "handleDbMigrationApply", { enumerable: true, get: function () { return migration_1.handleDbMigrationApply; } });
|
|
23
|
+
var recovery_1 = require("./recovery");
|
|
24
|
+
Object.defineProperty(exports, "handleDbRecoveryDiff", { enumerable: true, get: function () { return recovery_1.handleDbRecoveryDiff; } });
|
|
25
|
+
Object.defineProperty(exports, "handleDbRecoveryApply", { enumerable: true, get: function () { return recovery_1.handleDbRecoveryApply; } });
|
|
26
|
+
var quota_1 = require("./quota");
|
|
27
|
+
Object.defineProperty(exports, "handleDbQuota", { enumerable: true, get: function () { return quota_1.handleDbQuota; } });
|
|
@@ -0,0 +1,234 @@
|
|
|
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.handleDbMigrationInit = handleDbMigrationInit;
|
|
37
|
+
exports.handleDbMigrationDiff = handleDbMigrationDiff;
|
|
38
|
+
exports.handleDbMigrationApply = handleDbMigrationApply;
|
|
39
|
+
const api = __importStar(require("../../../api/index"));
|
|
40
|
+
const shared_1 = require("../../../cli/commands/shared");
|
|
41
|
+
const colors_1 = require("../../../utils/colors");
|
|
42
|
+
const error_1 = require("../../../utils/error");
|
|
43
|
+
const output_1 = require("../../../utils/output");
|
|
44
|
+
const poll_1 = require("../../../utils/poll");
|
|
45
|
+
const render_1 = require("../../../utils/render");
|
|
46
|
+
const spinner_1 = require("../../../utils/spinner");
|
|
47
|
+
const _destructive_1 = require("../../../cli/handlers/db/_destructive");
|
|
48
|
+
async function handleDbMigrationInit(opts) {
|
|
49
|
+
const appId = (0, shared_1.resolveAppId)(opts);
|
|
50
|
+
// 不可逆操作:--yes 直接放行;非 TTY 抛 DESTRUCTIVE_REQUIRES_CONFIRM;TTY 交互确认。
|
|
51
|
+
// 不再用 isJsonMode() 做门 —— 见 _destructive.ts 注释。
|
|
52
|
+
const suffix = opts.syncData ? ' (existing data will be copied to dev)' : '';
|
|
53
|
+
const ok = await (0, _destructive_1.confirmDestructive)(`? This action is irreversible. Initialize multi-env (dev / online)${suffix}? (y/N) `, opts.yes);
|
|
54
|
+
if (!ok) {
|
|
55
|
+
(0, output_1.emit)('Aborted.');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
let result;
|
|
59
|
+
try {
|
|
60
|
+
result = await api.db.migrationInit({ appId, syncData: opts.syncData });
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
// PRD: 重复 init 报错带 hint,引导用户去 diff 看待发布变更
|
|
64
|
+
if (err instanceof error_1.AppError && err.code === 'DB_API_k_dl_1300034') {
|
|
65
|
+
throw new error_1.AppError(err.code, err.message, {
|
|
66
|
+
next_actions: ['Run `miaoda db migration diff` to view pending changes.'],
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
throw err;
|
|
70
|
+
}
|
|
71
|
+
if ((0, output_1.isJsonMode)()) {
|
|
72
|
+
(0, output_1.emitOk)((0, output_1.snakeCaseKeys)(result));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
// PRD 单行格式(不用 key:value 表格):
|
|
76
|
+
// 默认 ✓ / OK: "Multi-env initialized (dev / online)"
|
|
77
|
+
// --sync-data: "Multi-env initialized, data synced to dev"
|
|
78
|
+
const tty = (0, render_1.isStdoutTty)();
|
|
79
|
+
const body = result.dataSynced
|
|
80
|
+
? 'Multi-env initialized, data synced to dev'
|
|
81
|
+
: `Multi-env initialized (${result.environments.join(' / ')})`;
|
|
82
|
+
(0, output_1.emit)(tty ? colors_1.c.success(`✓ ${body}`) : `OK ${body}`);
|
|
83
|
+
}
|
|
84
|
+
async function handleDbMigrationDiff(opts) {
|
|
85
|
+
const appId = (0, shared_1.resolveAppId)(opts);
|
|
86
|
+
let result;
|
|
87
|
+
const stopSpinner = (0, spinner_1.startSpinner)('Previewing migration diff (dev → online)');
|
|
88
|
+
try {
|
|
89
|
+
result = await api.db.migrate({ appId, dryRun: true });
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
throw decorateMigrationError(err);
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
stopSpinner();
|
|
96
|
+
}
|
|
97
|
+
// PRD: diff 在无待发布变更时报错带 hint,而不是渲染空列表
|
|
98
|
+
if (result.changes.length === 0) {
|
|
99
|
+
throw new error_1.AppError('DB_API_k_dl_1300035', `No pending changes between ${result.from} and ${result.to}`, {
|
|
100
|
+
next_actions: [
|
|
101
|
+
'Make schema changes in dev first (e.g. `miaoda db sql "ALTER TABLE ..." --env dev`)',
|
|
102
|
+
],
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
renderDiff(result);
|
|
106
|
+
}
|
|
107
|
+
async function handleDbMigrationApply(opts) {
|
|
108
|
+
const appId = (0, shared_1.resolveAppId)(opts);
|
|
109
|
+
// --yes 跳过预览 + 确认;否则 TTY 拉 diff 给用户审,非 TTY 直接拒(避免无意义 dry-run RPC)
|
|
110
|
+
if (opts.yes !== true) {
|
|
111
|
+
(0, _destructive_1.assertDestructiveAllowedInTty)(opts.yes);
|
|
112
|
+
let preview;
|
|
113
|
+
const stopSpinner = (0, spinner_1.startSpinner)('Previewing migration diff (dev → online)');
|
|
114
|
+
try {
|
|
115
|
+
preview = await api.db.migrate({ appId, dryRun: true });
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
throw decorateMigrationError(err);
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
stopSpinner();
|
|
122
|
+
}
|
|
123
|
+
if (preview.changes.length === 0) {
|
|
124
|
+
// PRD 文案 + hint
|
|
125
|
+
throw new error_1.AppError('DB_API_k_dl_1300035', `No pending changes between ${preview.from} and ${preview.to}`, {
|
|
126
|
+
next_actions: [
|
|
127
|
+
'Make schema changes in dev first (e.g. `miaoda db sql "ALTER TABLE ..." --env dev`)',
|
|
128
|
+
],
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
// --json 模式跳过 pretty diff 渲染(污染 stdout envelope),但仍要求 confirm
|
|
132
|
+
if (!(0, output_1.isJsonMode)())
|
|
133
|
+
renderDiff(preview);
|
|
134
|
+
const ok = await (0, _destructive_1.askYesNo)(`? Apply ${String(preview.changes.length)} change(s) to ${preview.to}? (y/N) `);
|
|
135
|
+
if (!ok) {
|
|
136
|
+
(0, output_1.emit)('Aborted.');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
let result;
|
|
141
|
+
try {
|
|
142
|
+
result = await api.db.migrate({ appId, dryRun: false });
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
throw decorateMigrationError(err);
|
|
146
|
+
}
|
|
147
|
+
// dataloom 立即返 taskId(apply 实际是异步流水线)。CLI 自己 poll 直到 success/failed,
|
|
148
|
+
// 避免单次 HTTP 长连接 30s+ 被网关 / SDK 中断。
|
|
149
|
+
if (result.taskId === undefined || result.taskId === '') {
|
|
150
|
+
throw new error_1.AppError('INTERNAL_DB_ERROR', 'migration apply did not return taskId');
|
|
151
|
+
}
|
|
152
|
+
const taskId = result.taskId;
|
|
153
|
+
const final = await (0, poll_1.pollUntilDone)({
|
|
154
|
+
label: 'migration apply',
|
|
155
|
+
spinnerLabel: 'Applying migration (dev → online)',
|
|
156
|
+
intervalMs: 1000,
|
|
157
|
+
fetch: () => api.db.getMigrationStatus({ appId, taskId }),
|
|
158
|
+
isDone: (cur) => {
|
|
159
|
+
// 同 recovery preview,dataloom 上下游枚举大小写不完全统一,客户端归一兜底。
|
|
160
|
+
const status = cur.status.toLowerCase();
|
|
161
|
+
if (status === 'success')
|
|
162
|
+
return { done: true, value: cur };
|
|
163
|
+
if (status === 'failed') {
|
|
164
|
+
throw new error_1.AppError('DB_API_k_dl_1300030', cur.errorMessage ?? `migration apply failed (taskId=${taskId})`);
|
|
165
|
+
}
|
|
166
|
+
return { done: false };
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
const appliedCount = final.changesApplied ?? result.changes.length;
|
|
170
|
+
if ((0, output_1.isJsonMode)()) {
|
|
171
|
+
// PRD:{"status": "applied", "from": "dev", "to": "online", "changes_applied": 2}
|
|
172
|
+
(0, output_1.emitOk)((0, output_1.snakeCaseKeys)({
|
|
173
|
+
status: 'applied',
|
|
174
|
+
from: result.from,
|
|
175
|
+
to: result.to,
|
|
176
|
+
changesApplied: appliedCount,
|
|
177
|
+
}));
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const tty = (0, render_1.isStdoutTty)();
|
|
181
|
+
const arrow = tty ? '→' : '->';
|
|
182
|
+
const body = `Applied ${result.from} ${arrow} ${result.to} (${String(appliedCount)} changes)`;
|
|
183
|
+
(0, output_1.emit)(tty ? colors_1.c.success(`✓ ${body}`) : `OK ${body}`);
|
|
184
|
+
}
|
|
185
|
+
// ── helpers ──
|
|
186
|
+
// PRD diff 输出:
|
|
187
|
+
// dev → online (2 changes):
|
|
188
|
+
//
|
|
189
|
+
// ALTER TABLE users ADD COLUMN avatar_url text;
|
|
190
|
+
// CREATE INDEX idx_users_avatar ON users(avatar_url);
|
|
191
|
+
function renderDiff(result) {
|
|
192
|
+
if ((0, output_1.isJsonMode)()) {
|
|
193
|
+
(0, output_1.emitOk)((0, output_1.snakeCaseKeys)({
|
|
194
|
+
from: result.from,
|
|
195
|
+
to: result.to,
|
|
196
|
+
changes: result.changes.map((c) => ({
|
|
197
|
+
type: c.type,
|
|
198
|
+
table: c.table,
|
|
199
|
+
statement: c.statement,
|
|
200
|
+
})),
|
|
201
|
+
}));
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const tty = (0, render_1.isStdoutTty)();
|
|
205
|
+
if (result.changes.length === 0) {
|
|
206
|
+
(0, output_1.emit)(`No pending changes from ${result.from} to ${result.to}.`);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const arrow = tty ? '→' : '->';
|
|
210
|
+
(0, output_1.emit)(`${result.from} ${arrow} ${result.to} (${String(result.changes.length)} changes):\n\n` +
|
|
211
|
+
result.changes.map((c) => ` ${c.statement}`).join('\n'));
|
|
212
|
+
}
|
|
213
|
+
// decorateMigrationError 给 migration / recovery 路径上的几个错误码补 PRD 规定的 hint。
|
|
214
|
+
// dataloom 后端只返 message + code,hint 由 CLI 端按错误码映射;其它错误码原样透传。
|
|
215
|
+
function decorateMigrationError(err) {
|
|
216
|
+
if (!(err instanceof error_1.AppError))
|
|
217
|
+
return err;
|
|
218
|
+
switch (err.code) {
|
|
219
|
+
case 'DB_API_k_dl_1300039':
|
|
220
|
+
// 多环境未初始化:引导先 init
|
|
221
|
+
return new error_1.AppError(err.code, err.message, {
|
|
222
|
+
next_actions: ['Run `miaoda db migration init` to set up multi-env first.'],
|
|
223
|
+
});
|
|
224
|
+
case 'DB_API_k_dl_1300035':
|
|
225
|
+
// 无待发布变更:引导先在 dev 改 schema
|
|
226
|
+
return new error_1.AppError(err.code, err.message, {
|
|
227
|
+
next_actions: [
|
|
228
|
+
'Make schema changes in dev first (e.g. `miaoda db sql "ALTER TABLE ..." --env dev`)',
|
|
229
|
+
],
|
|
230
|
+
});
|
|
231
|
+
default:
|
|
232
|
+
return err;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
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.handleDbQuota = handleDbQuota;
|
|
37
|
+
const api = __importStar(require("../../../api/index"));
|
|
38
|
+
const shared_1 = require("../../../cli/commands/shared");
|
|
39
|
+
const output_1 = require("../../../utils/output");
|
|
40
|
+
const render_1 = require("../../../utils/render");
|
|
41
|
+
async function handleDbQuota(opts) {
|
|
42
|
+
const appId = (0, shared_1.resolveAppId)(opts);
|
|
43
|
+
const data = await api.db.getDbQuota({ appId, dbBranch: opts.env });
|
|
44
|
+
if ((0, output_1.isJsonMode)()) {
|
|
45
|
+
// 配额未对接(storageQuotaBytes=0)时,quota / usage_percent 字段都不输出
|
|
46
|
+
const out = {
|
|
47
|
+
storageUsedBytes: data.storageUsedBytes,
|
|
48
|
+
tables: data.tables,
|
|
49
|
+
views: data.views,
|
|
50
|
+
};
|
|
51
|
+
if (data.storageQuotaBytes > 0) {
|
|
52
|
+
out.storageQuotaBytes = data.storageQuotaBytes;
|
|
53
|
+
out.usagePercent = data.usagePercent;
|
|
54
|
+
}
|
|
55
|
+
(0, output_1.emitOk)((0, output_1.snakeCaseKeys)(out));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
// PRD:单行 "Storage: 14.9 MB / 1 GB (1.5%)";配额未对接时只显示 used
|
|
59
|
+
const tty = (0, render_1.isStdoutTty)();
|
|
60
|
+
const storageLine = data.storageQuotaBytes > 0
|
|
61
|
+
? `${(0, render_1.formatSize)(data.storageUsedBytes)} / ${(0, render_1.formatSize)(data.storageQuotaBytes)} (${data.usagePercent.toFixed(1)}%)`
|
|
62
|
+
: (0, render_1.formatSize)(data.storageUsedBytes);
|
|
63
|
+
(0, output_1.emit)((0, render_1.renderKeyValue)([
|
|
64
|
+
['Storage', storageLine],
|
|
65
|
+
['Tables', String(data.tables)],
|
|
66
|
+
['Views', String(data.views)],
|
|
67
|
+
], tty));
|
|
68
|
+
}
|