@kitecd/cli 1.1.0 → 1.3.0

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 (91) hide show
  1. package/dist/doctor.js +181 -0
  2. package/dist/export.js +144 -0
  3. package/dist/ignore.js +38 -0
  4. package/dist/import.js +333 -0
  5. package/dist/index.js +213 -3
  6. package/dist/ops.js +338 -0
  7. package/dist/pack.js +15 -5
  8. package/dist/serve.js +27 -1
  9. package/dist/server/index.js +17231 -5610
  10. package/dist/upload.js +11 -3
  11. package/dist/verify.js +161 -0
  12. package/dist/web/assets/AuditLog-BO9BJdsk.js +1 -0
  13. package/dist/web/assets/ConfirmDialog-CikXU818.js +1 -0
  14. package/dist/web/assets/ConfirmDialog-D8avT8FJ.css +1 -0
  15. package/dist/web/assets/Dashboard-ho157_Gr.js +1 -0
  16. package/dist/web/assets/DefaultLayout-Y_852d55.js +1 -0
  17. package/dist/web/assets/DefaultLayout-lj5NNciV.css +1 -0
  18. package/dist/web/assets/FileExplorer-oxT6ZdHt.js +1 -0
  19. package/dist/web/assets/FolderPickerDialog-CDO-yrvE.css +1 -0
  20. package/dist/web/assets/FolderPickerDialog-CKohwBIP.js +1 -0
  21. package/dist/web/assets/LogBoard-DkvHTXcb.js +6 -0
  22. package/dist/web/assets/LogBoard-Dx8yNofc.css +1 -0
  23. package/dist/web/assets/LogTail-CuaBDDKf.js +9 -0
  24. package/dist/web/assets/Login-kcvq2T9U.js +1 -0
  25. package/dist/web/assets/Migration-C_M_Exzf.js +1 -0
  26. package/dist/web/assets/ProjectDetail-BTOfo71A.js +1 -0
  27. package/dist/web/assets/ProjectDetail-ia6-z1kZ.css +1 -0
  28. package/dist/web/assets/ProjectList-D_PoTDT9.js +1 -0
  29. package/dist/web/assets/ProjectList-YJlvRRNh.css +1 -0
  30. package/dist/web/assets/ProjectTagsEditor-zXN_5rwP.js +1 -0
  31. package/dist/web/assets/Settings-BL4hNZpU.js +1 -0
  32. package/dist/web/assets/Storage-B-pZjj08.js +1 -0
  33. package/dist/web/assets/Storage-CFbfZtA5.css +1 -0
  34. package/dist/web/assets/Terminal-Bh7bM6Bb.css +1 -0
  35. package/dist/web/assets/Terminal-fTZlKf2Y.js +7 -0
  36. package/dist/web/assets/{activity-DItEGOtI.js → activity-DZCOq6kQ.js} +1 -1
  37. package/dist/web/assets/archive-MB1JcYug.js +1 -0
  38. package/dist/web/assets/arrow-left-CsZYc3XK.js +1 -0
  39. package/dist/web/assets/{circle-alert-Bfrn_ovD.js → circle-alert-kmcvMchB.js} +1 -1
  40. package/dist/web/assets/clock-DIBzfDoY.js +1 -0
  41. package/dist/web/assets/constants-m1eFfRMw.js +1 -0
  42. package/dist/web/assets/copy-CNXWZJbC.js +1 -0
  43. package/dist/web/assets/createLucideIcon-BmsTm7z-.js +1 -0
  44. package/dist/web/assets/database-D6rC_24l.js +1 -0
  45. package/dist/web/assets/eye-off-BNUfzp8P.js +1 -0
  46. package/dist/web/assets/eye-vRDqRzR9.js +1 -0
  47. package/dist/web/assets/file-text-BxWt6is5.js +1 -0
  48. package/dist/web/assets/folder-CYPJgLMr.js +1 -0
  49. package/dist/web/assets/folder-open-DQz0XviI.js +1 -0
  50. package/dist/web/assets/hard-drive-DBiQxkNS.js +1 -0
  51. package/dist/web/assets/history-dkZbN_TB.js +1 -0
  52. package/dist/web/assets/house-D9JIOKIs.js +1 -0
  53. package/dist/web/assets/index-BZU6i5nw.js +2 -0
  54. package/dist/web/assets/index-BrlC5Hdt.css +1 -0
  55. package/dist/web/assets/loader-circle-OwtRa1dp.js +1 -0
  56. package/dist/web/assets/pencil-BTFuj0gA.js +1 -0
  57. package/dist/web/assets/plus-CcefsCv_.js +1 -0
  58. package/dist/web/assets/refresh-cw-OqaxwQhF.js +1 -0
  59. package/dist/web/assets/rotate-ccw-D8C0iiAw.js +1 -0
  60. package/dist/web/assets/save-D0NTjP8Q.js +1 -0
  61. package/dist/web/assets/scroll-text-6UwJwqap.js +1 -0
  62. package/dist/web/assets/{server-C33taHNn.js → server-DFHpqwVn.js} +1 -1
  63. package/dist/web/assets/square-terminal-C1oJyHEG.js +1 -0
  64. package/dist/web/assets/sun-Cg6PdQms.js +1 -0
  65. package/dist/web/assets/terminal-CUz5b_ol.js +1 -0
  66. package/dist/web/assets/trash-2-20ikd6fk.js +1 -0
  67. package/dist/web/assets/useIntervalRaf-CXWU2lqg.js +1 -0
  68. package/dist/web/index.html +3 -3
  69. package/package.json +11 -7
  70. package/scripts/postinstall.js +53 -0
  71. package/dist/web/assets/Dashboard-7wBCpwzp.js +0 -1
  72. package/dist/web/assets/DefaultLayout-Bj8fPWym.css +0 -1
  73. package/dist/web/assets/DefaultLayout-LaPhlICp.js +0 -1
  74. package/dist/web/assets/FileExplorer-DvzkxXvZ.js +0 -1
  75. package/dist/web/assets/LogBoard-BWAPesBz.js +0 -6
  76. package/dist/web/assets/LogBoard-DzW-cEqH.css +0 -1
  77. package/dist/web/assets/Login-BhNdUJs0.js +0 -1
  78. package/dist/web/assets/ProjectDetail-DZwMOEto.js +0 -1
  79. package/dist/web/assets/ProjectList-Czr-438J.js +0 -1
  80. package/dist/web/assets/Settings-OmPvcQbD.js +0 -1
  81. package/dist/web/assets/clock-BPXGSCIV.js +0 -1
  82. package/dist/web/assets/constants-iI5LEC2F.js +0 -1
  83. package/dist/web/assets/createLucideIcon-Cgv1AIRL.js +0 -1
  84. package/dist/web/assets/folder-open-jX-_Q7bA.js +0 -1
  85. package/dist/web/assets/index-C9LiRc31.css +0 -1
  86. package/dist/web/assets/index-eyx6wNyQ.js +0 -2
  87. package/dist/web/assets/project-BFuaDcvV.js +0 -1
  88. package/dist/web/assets/refresh-cw-DWmqwQRn.js +0 -1
  89. package/dist/web/assets/save-BkiMrL9q.js +0 -1
  90. package/dist/web/assets/settings-CrCWmNyB.js +0 -1
  91. package/dist/web/assets/square-terminal-C8toRwjx.js +0 -1
package/dist/import.js ADDED
@@ -0,0 +1,333 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import AdmZip from 'adm-zip';
4
+ import ora from 'ora';
5
+ import chalk from 'chalk';
6
+ import { createClient } from '@libsql/client';
7
+ import { ensureKiteHome, readGlobalConfig, writeGlobalConfig } from './home.js';
8
+ const DEFAULT_LOCAL_SERVER_URL = 'http://127.0.0.1:5431';
9
+ const SUPPORTED_SCHEMA = 1;
10
+ const PROJECT_COLUMNS = [
11
+ 'id', 'name', 'description', 'deploy_path', 'token',
12
+ 'pre_deploy_script', 'post_deploy_script', 'env', 'status',
13
+ 'created_at', 'updated_at',
14
+ ];
15
+ const DEPLOYMENT_COLUMNS = [
16
+ 'id', 'project_id', 'project_name', 'status', 'trigger_source',
17
+ 'duration', 'output', 'start_time', 'end_time',
18
+ ];
19
+ const CREATE_PROJECTS = `
20
+ CREATE TABLE IF NOT EXISTS projects (
21
+ id TEXT PRIMARY KEY,
22
+ name TEXT NOT NULL,
23
+ description TEXT,
24
+ deploy_path TEXT NOT NULL,
25
+ token TEXT NOT NULL UNIQUE,
26
+ pre_deploy_script TEXT,
27
+ post_deploy_script TEXT,
28
+ env TEXT,
29
+ status TEXT DEFAULT 'idle',
30
+ created_at TEXT NOT NULL,
31
+ updated_at TEXT NOT NULL
32
+ );
33
+ `;
34
+ const CREATE_SETTINGS = `
35
+ CREATE TABLE IF NOT EXISTS settings (
36
+ key TEXT PRIMARY KEY,
37
+ value TEXT NOT NULL
38
+ );
39
+ `;
40
+ const CREATE_DEPLOYMENTS = `
41
+ CREATE TABLE IF NOT EXISTS deployments (
42
+ id TEXT PRIMARY KEY,
43
+ project_id TEXT NOT NULL REFERENCES projects(id),
44
+ project_name TEXT NOT NULL,
45
+ status TEXT NOT NULL,
46
+ trigger_source TEXT NOT NULL,
47
+ duration TEXT,
48
+ output TEXT,
49
+ start_time TEXT NOT NULL,
50
+ end_time TEXT
51
+ );
52
+ `;
53
+ const ensureSchema = async (client) => {
54
+ await client.execute(CREATE_PROJECTS);
55
+ try {
56
+ await client.execute(`ALTER TABLE projects ADD COLUMN env TEXT`);
57
+ }
58
+ catch { /* exists */ }
59
+ await client.execute(CREATE_SETTINGS);
60
+ await client.execute(CREATE_DEPLOYMENTS);
61
+ };
62
+ const readJsonEntry = (zip, name) => {
63
+ const entry = zip.getEntry(name);
64
+ if (!entry)
65
+ return null;
66
+ return JSON.parse(entry.getData().toString('utf-8'));
67
+ };
68
+ export async function runImport(file, options) {
69
+ const strategy = options.strategy || 'skip-existing';
70
+ if (!['merge', 'overwrite', 'skip-existing'].includes(strategy)) {
71
+ console.error(chalk.red(`Invalid strategy: ${strategy}. Use merge | overwrite | skip-existing.`));
72
+ process.exit(1);
73
+ }
74
+ if (strategy === 'overwrite' && !options.yes && !options.dryRun) {
75
+ console.error(chalk.red('Strategy "overwrite" requires --yes to confirm destructive replacement.'));
76
+ process.exit(1);
77
+ }
78
+ const absFile = path.resolve(process.cwd(), file);
79
+ if (!fs.existsSync(absFile)) {
80
+ console.error(chalk.red(`Import file not found: ${absFile}`));
81
+ process.exit(1);
82
+ }
83
+ const home = ensureKiteHome();
84
+ const dbPath = path.join(home, 'kite.db');
85
+ const spinner = ora('Reading import package...').start();
86
+ const zip = new AdmZip(absFile);
87
+ const manifest = readJsonEntry(zip, 'kite-export/manifest.json');
88
+ if (!manifest) {
89
+ spinner.fail();
90
+ console.error(chalk.red('Invalid import package: kite-export/manifest.json not found.'));
91
+ process.exit(1);
92
+ }
93
+ if (manifest.schemaVersion !== SUPPORTED_SCHEMA) {
94
+ spinner.fail();
95
+ console.error(chalk.red(`Unsupported schemaVersion ${manifest.schemaVersion} (CLI supports ${SUPPORTED_SCHEMA}). Upgrade CLI and retry.`));
96
+ process.exit(1);
97
+ }
98
+ spinner.succeed(chalk.green(`Loaded export package (exported ${manifest.exportedAt}, kite ${manifest.kiteVersion})`));
99
+ const projects = readJsonEntry(zip, 'kite-export/projects.json') || [];
100
+ const settings = readJsonEntry(zip, 'kite-export/settings.json') || [];
101
+ const deployments = manifest.includes.deployments
102
+ ? (readJsonEntry(zip, 'kite-export/deployments.json') || [])
103
+ : [];
104
+ const client = createClient({ url: `file:${dbPath}` });
105
+ try {
106
+ await ensureSchema(client);
107
+ // 预扫现状用于摘要 + skip-existing 判断
108
+ const existingProjectIds = new Set();
109
+ const existingTokens = new Set();
110
+ for (const row of (await client.execute(`SELECT id, token FROM projects`)).rows) {
111
+ existingProjectIds.add(String(row.id));
112
+ existingTokens.add(String(row.token));
113
+ }
114
+ const existingSettingKeys = new Set();
115
+ for (const row of (await client.execute(`SELECT key FROM settings`)).rows) {
116
+ existingSettingKeys.add(String(row.key));
117
+ }
118
+ const existingDeploymentIds = new Set();
119
+ for (const row of (await client.execute(`SELECT id FROM deployments`)).rows) {
120
+ existingDeploymentIds.add(String(row.id));
121
+ }
122
+ const projectCounts = { added: 0, updated: 0, skipped: 0 };
123
+ const settingCounts = { added: 0, updated: 0, skipped: 0 };
124
+ const deploymentCounts = { added: 0, updated: 0, skipped: 0 };
125
+ const projectsToWrite = [];
126
+ const settingsToWrite = [];
127
+ const deploymentsToWrite = [];
128
+ for (const p of projects) {
129
+ const id = String(p.id);
130
+ const token = String(p.token);
131
+ const idExists = existingProjectIds.has(id);
132
+ const tokenConflict = !idExists && existingTokens.has(token);
133
+ if (idExists) {
134
+ if (strategy === 'overwrite') {
135
+ projectsToWrite.push(p);
136
+ projectCounts.updated++;
137
+ }
138
+ else {
139
+ projectCounts.skipped++;
140
+ }
141
+ }
142
+ else if (tokenConflict) {
143
+ // token unique constraint 会冲突,跳过避免报错
144
+ projectCounts.skipped++;
145
+ }
146
+ else {
147
+ projectsToWrite.push(p);
148
+ projectCounts.added++;
149
+ }
150
+ }
151
+ for (const s of settings) {
152
+ const key = String(s.key);
153
+ const exists = existingSettingKeys.has(key);
154
+ if (exists) {
155
+ if (strategy === 'overwrite') {
156
+ settingsToWrite.push(s);
157
+ settingCounts.updated++;
158
+ }
159
+ else {
160
+ settingCounts.skipped++;
161
+ }
162
+ }
163
+ else {
164
+ settingsToWrite.push(s);
165
+ settingCounts.added++;
166
+ }
167
+ }
168
+ for (const d of deployments) {
169
+ const id = String(d.id);
170
+ const exists = existingDeploymentIds.has(id);
171
+ if (exists) {
172
+ if (strategy === 'overwrite') {
173
+ deploymentsToWrite.push(d);
174
+ deploymentCounts.updated++;
175
+ }
176
+ else {
177
+ deploymentCounts.skipped++;
178
+ }
179
+ }
180
+ else {
181
+ deploymentsToWrite.push(d);
182
+ deploymentCounts.added++;
183
+ }
184
+ }
185
+ // 摘要
186
+ console.log(chalk.bold('\nImport summary:'));
187
+ console.log(` Strategy: ${strategy}`);
188
+ console.log(` DB path: ${dbPath}`);
189
+ console.log(` Projects: + ${projectCounts.added} new, ~ ${projectCounts.updated} update, - ${projectCounts.skipped} skip`);
190
+ console.log(` Settings: + ${settingCounts.added} new, ~ ${settingCounts.updated} update, - ${settingCounts.skipped} skip`);
191
+ if (manifest.includes.deployments) {
192
+ console.log(` Deployments: + ${deploymentCounts.added} new, ~ ${deploymentCounts.updated} update, - ${deploymentCounts.skipped} skip`);
193
+ }
194
+ if (manifest.includes.artifacts) {
195
+ const restoreCount = options.restoreArtifacts ? manifest.artifacts.filter(a => a.archive).length : 0;
196
+ console.log(` Artifacts: ${options.restoreArtifacts ? `restore ${restoreCount}` : 'available, not restored (pass --restore-artifacts)'}`);
197
+ }
198
+ console.log(` Global cfg: will write projectToken for ${projectsToWrite.length} projects to ~/.kite/config.json`);
199
+ if (options.dryRun) {
200
+ console.log(chalk.gray('\nDry-run complete. No changes written.'));
201
+ return;
202
+ }
203
+ const writeSpinner = ora('Writing to database...').start();
204
+ const placeholders = (cols) => cols.map(() => '?').join(', ');
205
+ const upsertProject = (cols) => `INSERT INTO projects (${cols.join(', ')}) VALUES (${placeholders(cols)}) ` +
206
+ `ON CONFLICT(id) DO UPDATE SET ${cols.filter(c => c !== 'id').map(c => `${c} = excluded.${c}`).join(', ')}`;
207
+ const upsertSetting = `INSERT INTO settings (key, value) VALUES (?, ?) ` +
208
+ `ON CONFLICT(key) DO UPDATE SET value = excluded.value`;
209
+ const upsertDeployment = (cols) => `INSERT INTO deployments (${cols.join(', ')}) VALUES (${placeholders(cols)}) ` +
210
+ `ON CONFLICT(id) DO UPDATE SET ${cols.filter(c => c !== 'id').map(c => `${c} = excluded.${c}`).join(', ')}`;
211
+ const tx = await client.transaction('write');
212
+ try {
213
+ for (const p of projectsToWrite) {
214
+ const args = PROJECT_COLUMNS.map(c => (p[c] ?? null));
215
+ await tx.execute({ sql: upsertProject(PROJECT_COLUMNS), args });
216
+ }
217
+ for (const s of settingsToWrite) {
218
+ await tx.execute({ sql: upsertSetting, args: [String(s.key), String(s.value ?? '')] });
219
+ }
220
+ for (const d of deploymentsToWrite) {
221
+ const args = DEPLOYMENT_COLUMNS.map(c => (d[c] ?? null));
222
+ await tx.execute({ sql: upsertDeployment(DEPLOYMENT_COLUMNS), args });
223
+ }
224
+ await tx.commit();
225
+ }
226
+ catch (err) {
227
+ await tx.rollback();
228
+ writeSpinner.fail();
229
+ throw err;
230
+ }
231
+ writeSpinner.succeed(chalk.green('Database updated.'));
232
+ // 写回 CLI 全局 config,方便目标机器直接 kite push
233
+ try {
234
+ const globalConfig = readGlobalConfig();
235
+ const nextProjectToken = { ...(globalConfig.projectToken || {}) };
236
+ let tokenAdded = 0;
237
+ let tokenUpdated = 0;
238
+ let tokenSkipped = 0;
239
+ for (const p of projectsToWrite) {
240
+ const projectId = String(p.id);
241
+ const token = p.token != null ? String(p.token) : '';
242
+ if (!projectId || !token)
243
+ continue;
244
+ const existing = nextProjectToken[projectId];
245
+ if (existing === undefined) {
246
+ nextProjectToken[projectId] = token;
247
+ tokenAdded++;
248
+ }
249
+ else if (strategy === 'overwrite') {
250
+ if (existing !== token) {
251
+ nextProjectToken[projectId] = token;
252
+ tokenUpdated++;
253
+ }
254
+ }
255
+ else {
256
+ tokenSkipped++;
257
+ }
258
+ }
259
+ const serverUrlMissing = !globalConfig.serverUrl;
260
+ const nextServerUrl = serverUrlMissing ? DEFAULT_LOCAL_SERVER_URL : globalConfig.serverUrl;
261
+ writeGlobalConfig({
262
+ ...globalConfig,
263
+ serverUrl: nextServerUrl,
264
+ projectToken: nextProjectToken,
265
+ });
266
+ console.log(chalk.green('Global config updated (~/.kite/config.json).'));
267
+ console.log(chalk.gray(` projectToken: + ${tokenAdded} new, ~ ${tokenUpdated} update, - ${tokenSkipped} skip`));
268
+ if (serverUrlMissing) {
269
+ console.log(chalk.gray(` serverUrl: set to ${DEFAULT_LOCAL_SERVER_URL} (override via \`kite config:set serverUrl <url>\`)`));
270
+ }
271
+ }
272
+ catch (err) {
273
+ console.log(chalk.yellow(` ! Failed to update global config: ${err.message}`));
274
+ }
275
+ if (manifest.includes.artifacts && options.restoreArtifacts) {
276
+ // 重新读取写入后 projects 表,获取最新 deploy_path(可能用户保留了已有项目的路径)
277
+ const deployPathMap = new Map();
278
+ for (const row of (await client.execute(`SELECT id, deploy_path FROM projects`)).rows) {
279
+ deployPathMap.set(String(row.id), String(row.deploy_path));
280
+ }
281
+ const artifactSpinner = ora('Restoring artifacts...').start();
282
+ let okCount = 0;
283
+ let warnCount = 0;
284
+ for (const a of manifest.artifacts) {
285
+ if (!a.archive)
286
+ continue;
287
+ const target = deployPathMap.get(a.projectId);
288
+ if (!target) {
289
+ console.log(chalk.yellow(` ! ${a.projectId}: project not in DB, skip restore`));
290
+ warnCount++;
291
+ continue;
292
+ }
293
+ const entry = zip.getEntry(`kite-export/${a.archive}`);
294
+ if (!entry) {
295
+ console.log(chalk.yellow(` ! ${a.projectId}: archive entry missing`));
296
+ warnCount++;
297
+ continue;
298
+ }
299
+ try {
300
+ fs.mkdirSync(target, { recursive: true });
301
+ const tmpZipPath = path.join(home, 'tmp', `restore-${a.projectId}-${Date.now()}.zip`);
302
+ fs.mkdirSync(path.dirname(tmpZipPath), { recursive: true });
303
+ fs.writeFileSync(tmpZipPath, entry.getData());
304
+ const innerZip = new AdmZip(tmpZipPath);
305
+ // 路径穿越校验
306
+ for (const e of innerZip.getEntries()) {
307
+ const dest = path.resolve(target, e.entryName);
308
+ if (!dest.startsWith(path.resolve(target) + path.sep) && dest !== path.resolve(target)) {
309
+ throw new Error(`Refusing to extract entry outside target: ${e.entryName}`);
310
+ }
311
+ }
312
+ innerZip.extractAllTo(target, true);
313
+ fs.unlinkSync(tmpZipPath);
314
+ okCount++;
315
+ }
316
+ catch (err) {
317
+ console.log(chalk.yellow(` ! ${a.projectId}: restore failed - ${err.message}`));
318
+ warnCount++;
319
+ }
320
+ }
321
+ artifactSpinner.succeed(chalk.green(`Artifacts restored: ${okCount} ok, ${warnCount} warnings`));
322
+ }
323
+ console.log(chalk.green('\nImport completed.'));
324
+ if (!manifest.includes.artifacts || !options.restoreArtifacts) {
325
+ console.log(chalk.gray('Tip: start `kite serve` to verify projects in the dashboard.'));
326
+ }
327
+ console.log(chalk.gray('Tip: `kite push` from your project directory now uses the restored token automatically.'));
328
+ console.log(chalk.gray('Tip: run `kite verify` to self-check migration integrity (db, deploy paths, tokens).'));
329
+ }
330
+ finally {
331
+ client.close();
332
+ }
333
+ }
package/dist/index.js CHANGED
@@ -10,6 +10,12 @@ import { uploadZip } from './upload.js';
10
10
  import { getConfigPath, getKiteHome, randomToken, readGlobalConfig, readLocalEnv, setGlobalConfig, writeGlobalConfig, writeLocalEnvValue, listProjectEnvs, resolveProjectConfig, envTokenKey } from './home.js';
11
11
  import { LocalStore } from './local-store.js';
12
12
  import { startServe } from './serve.js';
13
+ import { parseIgnoreOption } from './ignore.js';
14
+ import { runExport } from './export.js';
15
+ import { runImport } from './import.js';
16
+ import { runVerify } from './verify.js';
17
+ import { runDoctor } from './doctor.js';
18
+ import { runList, runStatus, runLogs, runRollback } from './ops.js';
13
19
  // @ts-ignore
14
20
  const cli = cac('kite');
15
21
  // ==========================
@@ -482,6 +488,8 @@ const displayPackResult = (result) => {
482
488
  cli.command('build', 'Pack project files and verify packaging (no upload)')
483
489
  .option('--env <name>', 'Environment name (selects kite.config.<name>.json)')
484
490
  .option('--out <dir>', 'Output directory to pack')
491
+ .option('--ignore <patterns>', 'Extra ignore patterns (comma separated, may repeat)')
492
+ .option('--no-ignore-builtin', 'Disable built-in ignore patterns (node_modules, .git, .env*, etc.)')
485
493
  .action(async (options) => {
486
494
  try {
487
495
  const allEnvs = listProjectEnvs();
@@ -513,9 +521,21 @@ cli.command('build', 'Pack project files and verify packaging (no upload)')
513
521
  console.error(chalk.red(`Error: Output directory not found: ${sourceDir}`));
514
522
  process.exit(1);
515
523
  }
524
+ const cliIgnore = parseIgnoreOption(options.ignore);
525
+ const ignore = cliIgnore.length > 0
526
+ ? cliIgnore
527
+ : (Array.isArray(projectConfig.ignore) ? projectConfig.ignore : []);
528
+ // cac: --no-ignore-builtin 时 options.ignoreBuiltin === false;未传时 true
529
+ const ignoreBuiltin = options.ignoreBuiltin === false
530
+ ? true
531
+ : (projectConfig.ignoreBuiltin === true);
516
532
  const spinner = ora('Packing files...').start();
517
533
  const zipFilePath = path.resolve(process.cwd(), '.deploy-archive.zip');
518
- const result = await packProject(sourceDir, zipFilePath, files.length > 0 ? files : undefined);
534
+ const result = await packProject(sourceDir, zipFilePath, {
535
+ files: files.length > 0 ? files : undefined,
536
+ ignore,
537
+ ignoreBuiltin,
538
+ });
519
539
  spinner.succeed(chalk.green('Pack successful!'));
520
540
  displayPackResult(result);
521
541
  console.log(chalk.gray(` Archive: ${zipFilePath}`));
@@ -537,8 +557,12 @@ cli.command('push', 'Push and deploy project')
537
557
  .option('--pre <script>', 'Pre-deploy script (Server side)')
538
558
  .option('--post <script>', 'Post-deploy script (Server side)')
539
559
  .option('--command <script>', 'Deploy command alias, same as --post')
560
+ .option('--post-deploy-async', 'Run postDeploy asynchronously (do not wait for it to finish)')
540
561
  .option('--set-env <vars>', 'Environment variables as JSON or KEY=VALUE (overrides config)')
562
+ .option('--ignore <patterns>', 'Extra ignore patterns (comma separated, may repeat)')
563
+ .option('--no-ignore-builtin', 'Disable built-in ignore patterns (node_modules, .git, .env*, etc.)')
541
564
  .action(async (options) => {
565
+ const pushStartedAt = new Date().toISOString();
542
566
  try {
543
567
  // 1. 解析环境配置
544
568
  const allEnvs = listProjectEnvs();
@@ -601,6 +625,16 @@ cli.command('push', 'Push and deploy project')
601
625
  const files = projectConfig.files || [];
602
626
  const preDeploy = options.pre || localEnv.KITE_PRE_DEPLOY || projectConfig.preDeploy;
603
627
  const postDeploy = options.command || options.post || localEnv.KITE_DEPLOY_COMMAND || localEnv.KITE_POST_DEPLOY || projectConfig.command || projectConfig.postDeploy;
628
+ // postDeployAsync: CLI flag > env > project config(undefined = 不覆盖项目设置)
629
+ let postDeployAsync;
630
+ if (options.postDeployAsync === true)
631
+ postDeployAsync = true;
632
+ else if (localEnv.KITE_POST_DEPLOY_ASYNC === 'true' || localEnv.KITE_POST_DEPLOY_ASYNC === '1')
633
+ postDeployAsync = true;
634
+ else if (localEnv.KITE_POST_DEPLOY_ASYNC === 'false' || localEnv.KITE_POST_DEPLOY_ASYNC === '0')
635
+ postDeployAsync = false;
636
+ else if (typeof projectConfig.postDeployAsync === 'boolean')
637
+ postDeployAsync = projectConfig.postDeployAsync;
604
638
  // 解析 env: 项目配置 + CLI --set-env 覆盖
605
639
  let deployEnv = projectConfig.env || undefined;
606
640
  if (options.setEnv) {
@@ -624,9 +658,21 @@ cli.command('push', 'Push and deploy project')
624
658
  process.exit(1);
625
659
  }
626
660
  // 5. 打包文件
661
+ const cliIgnore = parseIgnoreOption(options.ignore);
662
+ const ignore = cliIgnore.length > 0
663
+ ? cliIgnore
664
+ : (Array.isArray(projectConfig.ignore) ? projectConfig.ignore : []);
665
+ // cac: --no-ignore-builtin 时 options.ignoreBuiltin === false;未传时 true
666
+ const ignoreBuiltin = options.ignoreBuiltin === false
667
+ ? true
668
+ : (projectConfig.ignoreBuiltin === true);
627
669
  const spinner = ora('Packing files...').start();
628
670
  const zipFilePath = path.resolve(process.cwd(), '.deploy-archive.zip');
629
- const packResult = await packProject(sourceDir, zipFilePath, files.length > 0 ? files : undefined);
671
+ const packResult = await packProject(sourceDir, zipFilePath, {
672
+ files: files.length > 0 ? files : undefined,
673
+ ignore,
674
+ ignoreBuiltin,
675
+ });
630
676
  spinner.succeed(chalk.green('Packed successfully'));
631
677
  displayPackResult(packResult);
632
678
  // 6. 上传与部署
@@ -639,13 +685,21 @@ cli.command('push', 'Push and deploy project')
639
685
  projectId,
640
686
  preDeploy,
641
687
  postDeploy,
642
- env: deployEnv
688
+ postDeployAsync,
689
+ env: deployEnv,
690
+ startedAt: pushStartedAt
643
691
  });
644
692
  if (result.success) {
645
693
  console.log(chalk.green(`\nDeployed successfully! (${result.duration})`));
694
+ if (result.traceId) {
695
+ console.log(chalk.gray(` trace: ${result.traceId}`));
696
+ }
646
697
  }
647
698
  else {
648
699
  console.error(chalk.red('\nDeployment failed'));
700
+ if (result.traceId) {
701
+ console.error(chalk.gray(` trace: ${result.traceId}`));
702
+ }
649
703
  process.exit(1);
650
704
  }
651
705
  }
@@ -661,6 +715,162 @@ cli.command('push', 'Push and deploy project')
661
715
  }
662
716
  }
663
717
  });
718
+ // ==========================
719
+ // Migration commands: export / import
720
+ // ==========================
721
+ cli.command('export', 'Export Kite database (and optional artifacts) to a portable archive')
722
+ .option('--out <file>', 'Output file path (default: kite-export-<timestamp>.zip)')
723
+ .option('--no-include-artifacts', 'Skip packing each project deployPath contents (default: include)')
724
+ .option('--no-include-logs', 'Skip deployment history (default: include)')
725
+ .option('--projects <ids>', 'Comma separated project ids to include (default: all)')
726
+ .option('--ignore <patterns>', 'Extra ignore patterns for artifacts (comma separated, may repeat)')
727
+ .option('--no-ignore-builtin', 'Disable built-in ignore patterns when packing artifacts')
728
+ .action(async (options) => {
729
+ try {
730
+ const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
731
+ const ignoreBuiltin = options.ignoreBuiltin === false;
732
+ await runExport({
733
+ out: options.out,
734
+ includeArtifacts: options.includeArtifacts !== false,
735
+ includeLogs: options.includeLogs !== false,
736
+ projects: options.projects,
737
+ ignore: options.ignore,
738
+ ignoreBuiltin,
739
+ }, pkg.version || '0.0.0');
740
+ }
741
+ catch (error) {
742
+ console.error(chalk.red(`\nExport failed: ${error.message}`));
743
+ process.exit(1);
744
+ }
745
+ });
746
+ cli.command('import <file>', 'Import Kite database from an export archive')
747
+ .option('--strategy <mode>', 'Conflict strategy: merge | overwrite | skip-existing', { default: 'skip-existing' })
748
+ .option('--no-restore-artifacts', 'Skip restoring each project deployPath from the archive (default: restore when archive contains artifacts)')
749
+ .option('--dry-run', 'Show summary without writing')
750
+ .option('--yes', 'Confirm destructive --strategy overwrite')
751
+ .action(async (file, options) => {
752
+ try {
753
+ await runImport(file, {
754
+ strategy: options.strategy,
755
+ restoreArtifacts: options.restoreArtifacts !== false,
756
+ dryRun: !!options.dryRun,
757
+ yes: !!options.yes,
758
+ });
759
+ }
760
+ catch (error) {
761
+ console.error(chalk.red(`\nImport failed: ${error.message}`));
762
+ process.exit(1);
763
+ }
764
+ });
765
+ cli.command('verify', 'Verify ~/.kite migration integrity (db, deploy paths, tokens, optional server health)')
766
+ .option('--check-server', 'Also probe configured serverUrl with HTTP GET')
767
+ .option('--timeout <ms>', 'Server probe timeout in ms', { default: 5000 })
768
+ .action(async (options) => {
769
+ try {
770
+ await runVerify({
771
+ checkServer: !!options.checkServer,
772
+ timeout: Number(options.timeout) || 5000,
773
+ });
774
+ }
775
+ catch (error) {
776
+ console.error(chalk.red(`\nVerify failed: ${error.message}`));
777
+ process.exit(1);
778
+ }
779
+ });
780
+ cli.command('doctor', 'Run local + remote health diagnostics')
781
+ .option('--server <url>', 'Override server URL (defaults to global config / KITE_SERVER_URL)')
782
+ .option('--token <token>', 'Override admin token (defaults to global config / KITE_TOKEN)')
783
+ .action(async (options) => {
784
+ try {
785
+ const code = await runDoctor({ server: options.server, token: options.token });
786
+ process.exit(code);
787
+ }
788
+ catch (error) {
789
+ console.error(chalk.red(`\nDoctor failed: ${error.message}`));
790
+ process.exit(1);
791
+ }
792
+ });
793
+ cli.command('list', 'List projects on Kite server')
794
+ .option('--server <url>', 'Override server URL')
795
+ .option('--token <token>', 'Override admin token')
796
+ .option('--env <name>', 'Filter by project env')
797
+ .option('--json', 'Output JSON (no colors)')
798
+ .action(async (options) => {
799
+ try {
800
+ const code = await runList({ server: options.server, token: options.token, env: options.env, json: options.json });
801
+ process.exit(code);
802
+ }
803
+ catch (error) {
804
+ console.error(chalk.red(`\nList failed: ${error.message}`));
805
+ process.exit(1);
806
+ }
807
+ });
808
+ cli.command('status [projectId]', 'Show recent deployments of a project')
809
+ .option('--server <url>', 'Override server URL')
810
+ .option('--token <token>', 'Override admin token')
811
+ .option('--env <name>', 'Pick kite.config.<env>.json when no projectId given')
812
+ .option('--limit <n>', 'Number of deployments to show (default 5, max 50)')
813
+ .option('--json', 'Output JSON')
814
+ .action(async (projectId, options) => {
815
+ try {
816
+ const code = await runStatus(projectId, {
817
+ server: options.server,
818
+ token: options.token,
819
+ env: options.env,
820
+ limit: options.limit ? Number(options.limit) : undefined,
821
+ json: options.json,
822
+ });
823
+ process.exit(code);
824
+ }
825
+ catch (error) {
826
+ console.error(chalk.red(`\nStatus failed: ${error.message}`));
827
+ process.exit(1);
828
+ }
829
+ });
830
+ cli.command('logs <deployId>', 'Print deployment logs (or follow live with -f)')
831
+ .option('--server <url>', 'Override server URL')
832
+ .option('--token <token>', 'Override admin token')
833
+ .option('-f, --follow', 'Stream live logs via SSE until the deployment finishes')
834
+ .option('--json', 'Output JSON (only without --follow)')
835
+ .action(async (deployId, options) => {
836
+ try {
837
+ const code = await runLogs(deployId, {
838
+ server: options.server,
839
+ token: options.token,
840
+ follow: options.follow,
841
+ json: options.json,
842
+ });
843
+ process.exit(code);
844
+ }
845
+ catch (error) {
846
+ console.error(chalk.red(`\nLogs failed: ${error.message}`));
847
+ process.exit(1);
848
+ }
849
+ });
850
+ cli.command('rollback [projectId]', 'Rollback a project to a previous successful deployment')
851
+ .option('--server <url>', 'Override server URL')
852
+ .option('--token <token>', 'Override admin token (admin required)')
853
+ .option('--env <name>', 'Pick kite.config.<env>.json when no projectId given')
854
+ .option('--to <deployId>', 'Target deployment to roll back to (default: previous success)')
855
+ .option('--yes', 'Skip interactive confirmation (required in non-TTY)')
856
+ .option('--json', 'Output JSON on success')
857
+ .action(async (projectId, options) => {
858
+ try {
859
+ const code = await runRollback(projectId, {
860
+ server: options.server,
861
+ token: options.token,
862
+ env: options.env,
863
+ to: options.to,
864
+ yes: options.yes,
865
+ json: options.json,
866
+ });
867
+ process.exit(code);
868
+ }
869
+ catch (error) {
870
+ console.error(chalk.red(`\nRollback failed: ${error.message}`));
871
+ process.exit(1);
872
+ }
873
+ });
664
874
  cli.help();
665
875
  const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
666
876
  cli.version(pkg.version);