@lark-apaas/miaoda-cli 0.1.2 → 0.1.3-alpha.09899c4
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 +390 -55
- package/dist/api/db/client.js +65 -25
- 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 +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 +600 -59
- package/dist/cli/commands/deploy/index.js +155 -0
- package/dist/cli/commands/file/index.js +91 -63
- 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 +383 -0
- package/dist/cli/handlers/db/changelog.js +160 -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 +245 -0
- package/dist/cli/handlers/db/quota.js +68 -0
- package/dist/cli/handlers/db/recovery.js +387 -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 +33 -32
- 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 +35 -0
- package/dist/utils/render.js +27 -27
- package/dist/utils/spinner.js +46 -0
- package/dist/utils/time.js +208 -0
- package/package.json +7 -5
|
@@ -38,40 +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
|
-
const shared_1 = require("../../../cli/commands/shared");
|
|
44
44
|
const render_1 = require("../../../utils/render");
|
|
45
|
-
//
|
|
45
|
+
// import / export 体积上限
|
|
46
46
|
const MAX_SIZE_BYTES = 1 * 1024 * 1024; // 1 MB
|
|
47
47
|
const MAX_ROWS = 5000;
|
|
48
48
|
async function handleDbDataImport(file, opts) {
|
|
49
|
-
const appId =
|
|
49
|
+
const appId = opts.appId;
|
|
50
50
|
const ext = path.extname(file).toLowerCase();
|
|
51
|
-
const format = resolveFormat(opts.format, ext,
|
|
51
|
+
const format = resolveFormat(opts.format, ext, 'import');
|
|
52
52
|
let body;
|
|
53
53
|
try {
|
|
54
54
|
body = await fs.readFile(file);
|
|
55
55
|
}
|
|
56
56
|
catch (err) {
|
|
57
57
|
const code = err.code;
|
|
58
|
-
if (code ===
|
|
59
|
-
throw new error_1.AppError(
|
|
60
|
-
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.'],
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
63
|
throw err;
|
|
64
64
|
}
|
|
65
65
|
if (body.length > MAX_SIZE_BYTES) {
|
|
66
|
-
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.'] });
|
|
67
67
|
}
|
|
68
68
|
const rowCount = countRows(body, format);
|
|
69
69
|
if (rowCount > MAX_ROWS) {
|
|
70
|
-
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.'] });
|
|
71
71
|
}
|
|
72
72
|
const tableName = opts.table ?? path.basename(file, ext);
|
|
73
73
|
if (!tableName) {
|
|
74
|
-
throw new error_1.AppError(
|
|
74
|
+
throw new error_1.AppError('ARGS_INVALID', 'Cannot infer target table from file name; specify --table');
|
|
75
75
|
}
|
|
76
76
|
const result = await api.db.importData({
|
|
77
77
|
appId,
|
|
@@ -91,28 +91,28 @@ async function handleDbDataImport(file, opts) {
|
|
|
91
91
|
}
|
|
92
92
|
const tty = (0, render_1.isStdoutTty)();
|
|
93
93
|
(0, output_1.emit)(tty
|
|
94
|
-
? `✓ Imported ${file} → table '${result.tableName}' (${String(result.recordCount)} rows)`
|
|
94
|
+
? colors_1.c.success(`✓ Imported ${file} → table '${result.tableName}' (${String(result.recordCount)} rows)`)
|
|
95
95
|
: `OK Imported ${file} -> table '${result.tableName}' (${String(result.recordCount)} rows)`);
|
|
96
96
|
}
|
|
97
97
|
async function handleDbDataExport(table, opts) {
|
|
98
|
-
const appId =
|
|
99
|
-
const format = resolveFormat(opts.format, undefined,
|
|
98
|
+
const appId = opts.appId;
|
|
99
|
+
const format = resolveFormat(opts.format, undefined, 'export', 'csv');
|
|
100
100
|
const outputPath = opts.file ?? `${table}.${format}`;
|
|
101
101
|
const limit = opts.limit ? Number(opts.limit) : MAX_ROWS;
|
|
102
102
|
if (!Number.isInteger(limit) || limit <= 0 || limit > MAX_ROWS) {
|
|
103
|
-
throw new error_1.AppError(
|
|
103
|
+
throw new error_1.AppError('ARGS_INVALID', `--limit must be a positive integer ≤ ${String(MAX_ROWS)}`);
|
|
104
104
|
}
|
|
105
105
|
if (!opts.force) {
|
|
106
106
|
try {
|
|
107
107
|
await fs.access(outputPath);
|
|
108
|
-
throw new error_1.AppError(
|
|
109
|
-
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.'],
|
|
110
110
|
});
|
|
111
111
|
}
|
|
112
112
|
catch (err) {
|
|
113
113
|
if (err instanceof error_1.AppError)
|
|
114
114
|
throw err;
|
|
115
|
-
if (err.code !==
|
|
115
|
+
if (err.code !== 'ENOENT')
|
|
116
116
|
throw err;
|
|
117
117
|
}
|
|
118
118
|
}
|
|
@@ -123,7 +123,7 @@ async function handleDbDataExport(table, opts) {
|
|
|
123
123
|
limit,
|
|
124
124
|
});
|
|
125
125
|
if (result.body.length > MAX_SIZE_BYTES) {
|
|
126
|
-
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)`, {
|
|
127
127
|
next_actions: [
|
|
128
128
|
`Filter the table with "miaoda db sql" (e.g. WHERE/LIMIT) and export smaller subsets.`,
|
|
129
129
|
],
|
|
@@ -145,33 +145,33 @@ async function handleDbDataExport(table, opts) {
|
|
|
145
145
|
}
|
|
146
146
|
const tty = (0, render_1.isStdoutTty)();
|
|
147
147
|
(0, output_1.emit)(tty
|
|
148
|
-
? `✓ Exported ${table} → ${outputPath} (${String(rows)} rows)`
|
|
148
|
+
? colors_1.c.success(`✓ Exported ${table} → ${outputPath} (${String(rows)} rows)`)
|
|
149
149
|
: `OK Exported ${table} -> ${outputPath} (${String(rows)} rows)`);
|
|
150
150
|
}
|
|
151
151
|
// ── 共用辅助 ──
|
|
152
152
|
function resolveFormat(explicit, ext, scope, fallback) {
|
|
153
|
-
const raw = (explicit ?? ext ?? fallback ??
|
|
154
|
-
if (raw ===
|
|
155
|
-
return
|
|
156
|
-
if (raw ===
|
|
157
|
-
return
|
|
153
|
+
const raw = (explicit ?? ext ?? fallback ?? '').replace(/^\./, '').toLowerCase();
|
|
154
|
+
if (raw === 'csv')
|
|
155
|
+
return 'csv';
|
|
156
|
+
if (raw === 'json')
|
|
157
|
+
return 'json';
|
|
158
158
|
// sql 仅 export 路径接受 —— import 端后端仍只支持 csv/json。
|
|
159
|
-
if (raw ===
|
|
160
|
-
return
|
|
161
|
-
const code = scope ===
|
|
162
|
-
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)'}'`, {
|
|
163
163
|
next_actions: [
|
|
164
|
-
scope ===
|
|
165
|
-
?
|
|
166
|
-
:
|
|
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.',
|
|
167
167
|
],
|
|
168
168
|
});
|
|
169
169
|
}
|
|
170
170
|
function countRows(body, format) {
|
|
171
|
-
if (format ===
|
|
171
|
+
if (format === 'csv') {
|
|
172
172
|
// 粗估:非空行数 - 表头 1 行
|
|
173
173
|
let lines = 0;
|
|
174
|
-
const text = body.toString(
|
|
174
|
+
const text = body.toString('utf8');
|
|
175
175
|
for (const line of text.split(/\r?\n/)) {
|
|
176
176
|
if (line.length > 0)
|
|
177
177
|
lines += 1;
|
|
@@ -180,7 +180,7 @@ function countRows(body, format) {
|
|
|
180
180
|
}
|
|
181
181
|
// JSON:期望是顶层数组
|
|
182
182
|
try {
|
|
183
|
-
const parsed = JSON.parse(body.toString(
|
|
183
|
+
const parsed = JSON.parse(body.toString('utf8'));
|
|
184
184
|
if (Array.isArray(parsed))
|
|
185
185
|
return parsed.length;
|
|
186
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,245 @@
|
|
|
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.handleDbMigrationInit = handleDbMigrationInit;
|
|
40
|
+
exports.handleDbMigrationDiff = handleDbMigrationDiff;
|
|
41
|
+
exports.handleDbMigrationApply = handleDbMigrationApply;
|
|
42
|
+
const node_readline_1 = __importDefault(require("node:readline"));
|
|
43
|
+
const api = __importStar(require("../../../api/index"));
|
|
44
|
+
const shared_1 = require("../../../cli/commands/shared");
|
|
45
|
+
const colors_1 = require("../../../utils/colors");
|
|
46
|
+
const error_1 = require("../../../utils/error");
|
|
47
|
+
const output_1 = require("../../../utils/output");
|
|
48
|
+
const poll_1 = require("../../../utils/poll");
|
|
49
|
+
const render_1 = require("../../../utils/render");
|
|
50
|
+
const spinner_1 = require("../../../utils/spinner");
|
|
51
|
+
async function handleDbMigrationInit(opts) {
|
|
52
|
+
const appId = (0, shared_1.resolveAppId)(opts);
|
|
53
|
+
// 不可逆操作,TTY 默认要求 y/N;--yes 跳过
|
|
54
|
+
if (!opts.yes && !(0, output_1.isJsonMode)()) {
|
|
55
|
+
// 文案对齐 PRD:先讲不可逆,再问是否初始化;syncData 时追加一句而不是塞进同一句话
|
|
56
|
+
const suffix = opts.syncData ? ' (existing data will be copied to dev)' : '';
|
|
57
|
+
const ok = await confirm(`? This action is irreversible. Initialize multi-env (dev / online)${suffix}? (y/N) `);
|
|
58
|
+
if (!ok) {
|
|
59
|
+
(0, output_1.emit)('Aborted.');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
let result;
|
|
64
|
+
try {
|
|
65
|
+
result = await api.db.migrationInit({ appId, syncData: opts.syncData });
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
// PRD: 重复 init 报错带 hint,引导用户去 diff 看待发布变更
|
|
69
|
+
if (err instanceof error_1.AppError && err.code === 'DB_API_k_dl_1300034') {
|
|
70
|
+
throw new error_1.AppError(err.code, err.message, {
|
|
71
|
+
next_actions: ['Run `miaoda db migration diff` to view pending changes.'],
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
if ((0, output_1.isJsonMode)()) {
|
|
77
|
+
(0, output_1.emitOk)((0, output_1.snakeCaseKeys)(result));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// PRD 单行格式(不用 key:value 表格):
|
|
81
|
+
// 默认 ✓ / OK: "Multi-env initialized (dev / online)"
|
|
82
|
+
// --sync-data: "Multi-env initialized, data synced to dev"
|
|
83
|
+
const tty = (0, render_1.isStdoutTty)();
|
|
84
|
+
const body = result.dataSynced
|
|
85
|
+
? 'Multi-env initialized, data synced to dev'
|
|
86
|
+
: `Multi-env initialized (${result.environments.join(' / ')})`;
|
|
87
|
+
(0, output_1.emit)(tty ? colors_1.c.success(`✓ ${body}`) : `OK ${body}`);
|
|
88
|
+
}
|
|
89
|
+
async function handleDbMigrationDiff(opts) {
|
|
90
|
+
const appId = (0, shared_1.resolveAppId)(opts);
|
|
91
|
+
let result;
|
|
92
|
+
const stopSpinner = (0, spinner_1.startSpinner)('Computing migration diff');
|
|
93
|
+
try {
|
|
94
|
+
result = await api.db.migrate({ appId, dryRun: true });
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
throw decorateMigrationError(err);
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
stopSpinner();
|
|
101
|
+
}
|
|
102
|
+
// PRD: diff 在无待发布变更时报错带 hint,而不是渲染空列表
|
|
103
|
+
if (result.changes.length === 0) {
|
|
104
|
+
throw new error_1.AppError('DB_API_k_dl_1300035', `No pending changes between ${result.from} and ${result.to}`, {
|
|
105
|
+
next_actions: [
|
|
106
|
+
'Make schema changes in dev first (e.g. `miaoda db sql "ALTER TABLE ..." --env dev`)',
|
|
107
|
+
],
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
renderDiff(result);
|
|
111
|
+
}
|
|
112
|
+
async function handleDbMigrationApply(opts) {
|
|
113
|
+
const appId = (0, shared_1.resolveAppId)(opts);
|
|
114
|
+
// TTY 下先 diff 给用户审;--yes 直接打到 online
|
|
115
|
+
if (!opts.yes && !(0, output_1.isJsonMode)()) {
|
|
116
|
+
let preview;
|
|
117
|
+
const stopSpinner = (0, spinner_1.startSpinner)('Computing migration diff');
|
|
118
|
+
try {
|
|
119
|
+
preview = await api.db.migrate({ appId, dryRun: true });
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
throw decorateMigrationError(err);
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
stopSpinner();
|
|
126
|
+
}
|
|
127
|
+
if (preview.changes.length === 0) {
|
|
128
|
+
// PRD 文案 + hint
|
|
129
|
+
throw new error_1.AppError('DB_API_k_dl_1300035', `No pending changes between ${preview.from} and ${preview.to}`, {
|
|
130
|
+
next_actions: [
|
|
131
|
+
'Make schema changes in dev first (e.g. `miaoda db sql "ALTER TABLE ..." --env dev`)',
|
|
132
|
+
],
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
renderDiff(preview);
|
|
136
|
+
const ok = await confirm(`? Apply ${String(preview.changes.length)} change(s) to ${preview.to}? (y/N) `);
|
|
137
|
+
if (!ok) {
|
|
138
|
+
(0, output_1.emit)('Aborted.');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
let result;
|
|
143
|
+
try {
|
|
144
|
+
result = await api.db.migrate({ appId, dryRun: false });
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
throw decorateMigrationError(err);
|
|
148
|
+
}
|
|
149
|
+
// dataloom 立即返 taskId(apply 实际是异步流水线)。CLI 自己 poll 直到 success/failed,
|
|
150
|
+
// 避免单次 HTTP 长连接 30s+ 被网关 / SDK 中断。
|
|
151
|
+
if (result.taskId === undefined || result.taskId === '') {
|
|
152
|
+
throw new error_1.AppError('INTERNAL_DB_ERROR', 'migration apply did not return taskId');
|
|
153
|
+
}
|
|
154
|
+
const taskId = result.taskId;
|
|
155
|
+
const final = await (0, poll_1.pollUntilDone)({
|
|
156
|
+
label: 'migration apply',
|
|
157
|
+
spinnerLabel: 'Applying migration to online',
|
|
158
|
+
intervalMs: 1000,
|
|
159
|
+
fetch: () => api.db.getMigrationStatus({ appId, taskId }),
|
|
160
|
+
isDone: (cur) => {
|
|
161
|
+
// 同 recovery preview,dataloom 上下游枚举大小写不完全统一,客户端归一兜底。
|
|
162
|
+
const status = cur.status.toLowerCase();
|
|
163
|
+
if (status === 'success')
|
|
164
|
+
return { done: true, value: cur };
|
|
165
|
+
if (status === 'failed') {
|
|
166
|
+
throw new error_1.AppError('DB_API_k_dl_1300030', cur.errorMessage ?? `migration apply failed (taskId=${taskId})`);
|
|
167
|
+
}
|
|
168
|
+
return { done: false };
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
const appliedCount = final.changesApplied ?? result.changes.length;
|
|
172
|
+
if ((0, output_1.isJsonMode)()) {
|
|
173
|
+
// PRD:{"status": "applied", "from": "dev", "to": "online", "changes_applied": 2}
|
|
174
|
+
(0, output_1.emitOk)((0, output_1.snakeCaseKeys)({
|
|
175
|
+
status: 'applied',
|
|
176
|
+
from: result.from,
|
|
177
|
+
to: result.to,
|
|
178
|
+
changesApplied: appliedCount,
|
|
179
|
+
}));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const tty = (0, render_1.isStdoutTty)();
|
|
183
|
+
const arrow = tty ? '→' : '->';
|
|
184
|
+
const body = `Applied ${result.from} ${arrow} ${result.to} (${String(appliedCount)} changes)`;
|
|
185
|
+
(0, output_1.emit)(tty ? colors_1.c.success(`✓ ${body}`) : `OK ${body}`);
|
|
186
|
+
}
|
|
187
|
+
// ── helpers ──
|
|
188
|
+
// PRD diff 输出:
|
|
189
|
+
// dev → online (2 changes):
|
|
190
|
+
//
|
|
191
|
+
// ALTER TABLE users ADD COLUMN avatar_url text;
|
|
192
|
+
// CREATE INDEX idx_users_avatar ON users(avatar_url);
|
|
193
|
+
function renderDiff(result) {
|
|
194
|
+
if ((0, output_1.isJsonMode)()) {
|
|
195
|
+
(0, output_1.emitOk)((0, output_1.snakeCaseKeys)({
|
|
196
|
+
from: result.from,
|
|
197
|
+
to: result.to,
|
|
198
|
+
changes: result.changes.map((c) => ({
|
|
199
|
+
type: c.type,
|
|
200
|
+
table: c.table,
|
|
201
|
+
statement: c.statement,
|
|
202
|
+
})),
|
|
203
|
+
}));
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const tty = (0, render_1.isStdoutTty)();
|
|
207
|
+
if (result.changes.length === 0) {
|
|
208
|
+
(0, output_1.emit)(`No pending changes from ${result.from} to ${result.to}.`);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const arrow = tty ? '→' : '->';
|
|
212
|
+
(0, output_1.emit)(`${result.from} ${arrow} ${result.to} (${String(result.changes.length)} changes):\n\n` +
|
|
213
|
+
result.changes.map((c) => ` ${c.statement}`).join('\n'));
|
|
214
|
+
}
|
|
215
|
+
// decorateMigrationError 给 migration / recovery 路径上的几个错误码补 PRD 规定的 hint。
|
|
216
|
+
// dataloom 后端只返 message + code,hint 由 CLI 端按错误码映射;其它错误码原样透传。
|
|
217
|
+
function decorateMigrationError(err) {
|
|
218
|
+
if (!(err instanceof error_1.AppError))
|
|
219
|
+
return err;
|
|
220
|
+
switch (err.code) {
|
|
221
|
+
case 'DB_API_k_dl_1300039':
|
|
222
|
+
// 多环境未初始化:引导先 init
|
|
223
|
+
return new error_1.AppError(err.code, err.message, {
|
|
224
|
+
next_actions: ['Run `miaoda db migration init` to set up multi-env first.'],
|
|
225
|
+
});
|
|
226
|
+
case 'DB_API_k_dl_1300035':
|
|
227
|
+
// 无待发布变更:引导先在 dev 改 schema
|
|
228
|
+
return new error_1.AppError(err.code, err.message, {
|
|
229
|
+
next_actions: [
|
|
230
|
+
'Make schema changes in dev first (e.g. `miaoda db sql "ALTER TABLE ..." --env dev`)',
|
|
231
|
+
],
|
|
232
|
+
});
|
|
233
|
+
default:
|
|
234
|
+
return err;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
async function confirm(prompt) {
|
|
238
|
+
const rl = node_readline_1.default.createInterface({ input: process.stdin, output: process.stderr });
|
|
239
|
+
return new Promise((resolve) => {
|
|
240
|
+
rl.question(prompt, (answer) => {
|
|
241
|
+
rl.close();
|
|
242
|
+
resolve(answer.trim().toLowerCase() === 'y');
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
}
|
|
@@ -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
|
+
}
|