@kitecd/cli 1.1.0 → 1.2.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 (80) 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 +201 -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 +14578 -5647
  10. package/dist/upload.js +9 -3
  11. package/dist/verify.js +161 -0
  12. package/dist/web/assets/AuditLog-BFmJfgzL.js +1 -0
  13. package/dist/web/assets/ConfirmDialog-CJ8lJeUc.js +1 -0
  14. package/dist/web/assets/ConfirmDialog-D8avT8FJ.css +1 -0
  15. package/dist/web/assets/Dashboard-BTliTkq1.js +1 -0
  16. package/dist/web/assets/DefaultLayout-BG_y85yG.js +1 -0
  17. package/dist/web/assets/DefaultLayout-CZENO67n.css +1 -0
  18. package/dist/web/assets/FileExplorer-Bf32_MMS.js +1 -0
  19. package/dist/web/assets/LogBoard-0so-XiW3.css +1 -0
  20. package/dist/web/assets/LogBoard-CDz4n0DY.js +6 -0
  21. package/dist/web/assets/Login-C3zfObzP.js +1 -0
  22. package/dist/web/assets/Migration-BaKlcCkB.js +1 -0
  23. package/dist/web/assets/ProjectDetail-BL_D0OJY.js +1 -0
  24. package/dist/web/assets/ProjectDetail-ia6-z1kZ.css +1 -0
  25. package/dist/web/assets/ProjectList-7AnhOS7h.css +1 -0
  26. package/dist/web/assets/ProjectList-C4KuZEq4.js +1 -0
  27. package/dist/web/assets/Settings-CzWG9312.js +1 -0
  28. package/dist/web/assets/Storage-BCao_e7Y.js +1 -0
  29. package/dist/web/assets/Storage-DNadqpUy.css +1 -0
  30. package/dist/web/assets/{activity-DItEGOtI.js → activity-Ba-YPfmn.js} +1 -1
  31. package/dist/web/assets/archive-CJ5gDBKY.js +1 -0
  32. package/dist/web/assets/arrow-left-WEOMLXIi.js +1 -0
  33. package/dist/web/assets/chevron-right-DLiDJVJl.js +1 -0
  34. package/dist/web/assets/{circle-alert-Bfrn_ovD.js → circle-alert-DQzM-U4P.js} +1 -1
  35. package/dist/web/assets/clock-IwxBKgP4.js +1 -0
  36. package/dist/web/assets/constants-Ch47JPs3.js +1 -0
  37. package/dist/web/assets/copy-C1rADgcQ.js +1 -0
  38. package/dist/web/assets/createLucideIcon-CE-ry2oA.js +1 -0
  39. package/dist/web/assets/database-CI6acTSY.js +1 -0
  40. package/dist/web/assets/eye-Cas8HsmK.js +1 -0
  41. package/dist/web/assets/eye-off-4PD31MPV.js +1 -0
  42. package/dist/web/assets/file-text-C6nzo1us.js +1 -0
  43. package/dist/web/assets/folder-D9pvA6oI.js +1 -0
  44. package/dist/web/assets/folder-open-CGeIri0U.js +1 -0
  45. package/dist/web/assets/hard-drive-CGWwV4Ei.js +1 -0
  46. package/dist/web/assets/house-8ZvvlaEh.js +1 -0
  47. package/dist/web/assets/index-BFE6PEIL.js +2 -0
  48. package/dist/web/assets/index-XrzJwjrk.css +1 -0
  49. package/dist/web/assets/loader-circle-uiC7GaCG.js +1 -0
  50. package/dist/web/assets/plus-CfMi1Jv1.js +1 -0
  51. package/dist/web/assets/refresh-cw-0yb8DZDc.js +1 -0
  52. package/dist/web/assets/rotate-ccw-YevaWXw9.js +1 -0
  53. package/dist/web/assets/save-BuzCP3T1.js +1 -0
  54. package/dist/web/assets/scroll-text-BIYMFNN5.js +1 -0
  55. package/dist/web/assets/{server-C33taHNn.js → server-B31hj0g7.js} +1 -1
  56. package/dist/web/assets/{square-terminal-C8toRwjx.js → square-terminal-DT6aoXm0.js} +1 -1
  57. package/dist/web/assets/sun-BaEbKNDV.js +1 -0
  58. package/dist/web/assets/trash-2-BWqtqkeW.js +1 -0
  59. package/dist/web/index.html +3 -3
  60. package/package.json +2 -2
  61. package/dist/web/assets/Dashboard-7wBCpwzp.js +0 -1
  62. package/dist/web/assets/DefaultLayout-Bj8fPWym.css +0 -1
  63. package/dist/web/assets/DefaultLayout-LaPhlICp.js +0 -1
  64. package/dist/web/assets/FileExplorer-DvzkxXvZ.js +0 -1
  65. package/dist/web/assets/LogBoard-BWAPesBz.js +0 -6
  66. package/dist/web/assets/LogBoard-DzW-cEqH.css +0 -1
  67. package/dist/web/assets/Login-BhNdUJs0.js +0 -1
  68. package/dist/web/assets/ProjectDetail-DZwMOEto.js +0 -1
  69. package/dist/web/assets/ProjectList-Czr-438J.js +0 -1
  70. package/dist/web/assets/Settings-OmPvcQbD.js +0 -1
  71. package/dist/web/assets/clock-BPXGSCIV.js +0 -1
  72. package/dist/web/assets/constants-iI5LEC2F.js +0 -1
  73. package/dist/web/assets/createLucideIcon-Cgv1AIRL.js +0 -1
  74. package/dist/web/assets/folder-open-jX-_Q7bA.js +0 -1
  75. package/dist/web/assets/index-C9LiRc31.css +0 -1
  76. package/dist/web/assets/index-eyx6wNyQ.js +0 -2
  77. package/dist/web/assets/project-BFuaDcvV.js +0 -1
  78. package/dist/web/assets/refresh-cw-DWmqwQRn.js +0 -1
  79. package/dist/web/assets/save-BkiMrL9q.js +0 -1
  80. package/dist/web/assets/settings-CrCWmNyB.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}`));
@@ -538,7 +558,10 @@ cli.command('push', 'Push and deploy project')
538
558
  .option('--post <script>', 'Post-deploy script (Server side)')
539
559
  .option('--command <script>', 'Deploy command alias, same as --post')
540
560
  .option('--set-env <vars>', 'Environment variables as JSON or KEY=VALUE (overrides config)')
561
+ .option('--ignore <patterns>', 'Extra ignore patterns (comma separated, may repeat)')
562
+ .option('--no-ignore-builtin', 'Disable built-in ignore patterns (node_modules, .git, .env*, etc.)')
541
563
  .action(async (options) => {
564
+ const pushStartedAt = new Date().toISOString();
542
565
  try {
543
566
  // 1. 解析环境配置
544
567
  const allEnvs = listProjectEnvs();
@@ -624,9 +647,21 @@ cli.command('push', 'Push and deploy project')
624
647
  process.exit(1);
625
648
  }
626
649
  // 5. 打包文件
650
+ const cliIgnore = parseIgnoreOption(options.ignore);
651
+ const ignore = cliIgnore.length > 0
652
+ ? cliIgnore
653
+ : (Array.isArray(projectConfig.ignore) ? projectConfig.ignore : []);
654
+ // cac: --no-ignore-builtin 时 options.ignoreBuiltin === false;未传时 true
655
+ const ignoreBuiltin = options.ignoreBuiltin === false
656
+ ? true
657
+ : (projectConfig.ignoreBuiltin === true);
627
658
  const spinner = ora('Packing files...').start();
628
659
  const zipFilePath = path.resolve(process.cwd(), '.deploy-archive.zip');
629
- const packResult = await packProject(sourceDir, zipFilePath, files.length > 0 ? files : undefined);
660
+ const packResult = await packProject(sourceDir, zipFilePath, {
661
+ files: files.length > 0 ? files : undefined,
662
+ ignore,
663
+ ignoreBuiltin,
664
+ });
630
665
  spinner.succeed(chalk.green('Packed successfully'));
631
666
  displayPackResult(packResult);
632
667
  // 6. 上传与部署
@@ -639,13 +674,20 @@ cli.command('push', 'Push and deploy project')
639
674
  projectId,
640
675
  preDeploy,
641
676
  postDeploy,
642
- env: deployEnv
677
+ env: deployEnv,
678
+ startedAt: pushStartedAt
643
679
  });
644
680
  if (result.success) {
645
681
  console.log(chalk.green(`\nDeployed successfully! (${result.duration})`));
682
+ if (result.traceId) {
683
+ console.log(chalk.gray(` trace: ${result.traceId}`));
684
+ }
646
685
  }
647
686
  else {
648
687
  console.error(chalk.red('\nDeployment failed'));
688
+ if (result.traceId) {
689
+ console.error(chalk.gray(` trace: ${result.traceId}`));
690
+ }
649
691
  process.exit(1);
650
692
  }
651
693
  }
@@ -661,6 +703,162 @@ cli.command('push', 'Push and deploy project')
661
703
  }
662
704
  }
663
705
  });
706
+ // ==========================
707
+ // Migration commands: export / import
708
+ // ==========================
709
+ cli.command('export', 'Export Kite database (and optional artifacts) to a portable archive')
710
+ .option('--out <file>', 'Output file path (default: kite-export-<timestamp>.zip)')
711
+ .option('--no-include-artifacts', 'Skip packing each project deployPath contents (default: include)')
712
+ .option('--no-include-logs', 'Skip deployment history (default: include)')
713
+ .option('--projects <ids>', 'Comma separated project ids to include (default: all)')
714
+ .option('--ignore <patterns>', 'Extra ignore patterns for artifacts (comma separated, may repeat)')
715
+ .option('--no-ignore-builtin', 'Disable built-in ignore patterns when packing artifacts')
716
+ .action(async (options) => {
717
+ try {
718
+ const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
719
+ const ignoreBuiltin = options.ignoreBuiltin === false;
720
+ await runExport({
721
+ out: options.out,
722
+ includeArtifacts: options.includeArtifacts !== false,
723
+ includeLogs: options.includeLogs !== false,
724
+ projects: options.projects,
725
+ ignore: options.ignore,
726
+ ignoreBuiltin,
727
+ }, pkg.version || '0.0.0');
728
+ }
729
+ catch (error) {
730
+ console.error(chalk.red(`\nExport failed: ${error.message}`));
731
+ process.exit(1);
732
+ }
733
+ });
734
+ cli.command('import <file>', 'Import Kite database from an export archive')
735
+ .option('--strategy <mode>', 'Conflict strategy: merge | overwrite | skip-existing', { default: 'skip-existing' })
736
+ .option('--no-restore-artifacts', 'Skip restoring each project deployPath from the archive (default: restore when archive contains artifacts)')
737
+ .option('--dry-run', 'Show summary without writing')
738
+ .option('--yes', 'Confirm destructive --strategy overwrite')
739
+ .action(async (file, options) => {
740
+ try {
741
+ await runImport(file, {
742
+ strategy: options.strategy,
743
+ restoreArtifacts: options.restoreArtifacts !== false,
744
+ dryRun: !!options.dryRun,
745
+ yes: !!options.yes,
746
+ });
747
+ }
748
+ catch (error) {
749
+ console.error(chalk.red(`\nImport failed: ${error.message}`));
750
+ process.exit(1);
751
+ }
752
+ });
753
+ cli.command('verify', 'Verify ~/.kite migration integrity (db, deploy paths, tokens, optional server health)')
754
+ .option('--check-server', 'Also probe configured serverUrl with HTTP GET')
755
+ .option('--timeout <ms>', 'Server probe timeout in ms', { default: 5000 })
756
+ .action(async (options) => {
757
+ try {
758
+ await runVerify({
759
+ checkServer: !!options.checkServer,
760
+ timeout: Number(options.timeout) || 5000,
761
+ });
762
+ }
763
+ catch (error) {
764
+ console.error(chalk.red(`\nVerify failed: ${error.message}`));
765
+ process.exit(1);
766
+ }
767
+ });
768
+ cli.command('doctor', 'Run local + remote health diagnostics')
769
+ .option('--server <url>', 'Override server URL (defaults to global config / KITE_SERVER_URL)')
770
+ .option('--token <token>', 'Override admin token (defaults to global config / KITE_TOKEN)')
771
+ .action(async (options) => {
772
+ try {
773
+ const code = await runDoctor({ server: options.server, token: options.token });
774
+ process.exit(code);
775
+ }
776
+ catch (error) {
777
+ console.error(chalk.red(`\nDoctor failed: ${error.message}`));
778
+ process.exit(1);
779
+ }
780
+ });
781
+ cli.command('list', 'List projects on Kite server')
782
+ .option('--server <url>', 'Override server URL')
783
+ .option('--token <token>', 'Override admin token')
784
+ .option('--env <name>', 'Filter by project env')
785
+ .option('--json', 'Output JSON (no colors)')
786
+ .action(async (options) => {
787
+ try {
788
+ const code = await runList({ server: options.server, token: options.token, env: options.env, json: options.json });
789
+ process.exit(code);
790
+ }
791
+ catch (error) {
792
+ console.error(chalk.red(`\nList failed: ${error.message}`));
793
+ process.exit(1);
794
+ }
795
+ });
796
+ cli.command('status [projectId]', 'Show recent deployments of a project')
797
+ .option('--server <url>', 'Override server URL')
798
+ .option('--token <token>', 'Override admin token')
799
+ .option('--env <name>', 'Pick kite.config.<env>.json when no projectId given')
800
+ .option('--limit <n>', 'Number of deployments to show (default 5, max 50)')
801
+ .option('--json', 'Output JSON')
802
+ .action(async (projectId, options) => {
803
+ try {
804
+ const code = await runStatus(projectId, {
805
+ server: options.server,
806
+ token: options.token,
807
+ env: options.env,
808
+ limit: options.limit ? Number(options.limit) : undefined,
809
+ json: options.json,
810
+ });
811
+ process.exit(code);
812
+ }
813
+ catch (error) {
814
+ console.error(chalk.red(`\nStatus failed: ${error.message}`));
815
+ process.exit(1);
816
+ }
817
+ });
818
+ cli.command('logs <deployId>', 'Print deployment logs (or follow live with -f)')
819
+ .option('--server <url>', 'Override server URL')
820
+ .option('--token <token>', 'Override admin token')
821
+ .option('-f, --follow', 'Stream live logs via SSE until the deployment finishes')
822
+ .option('--json', 'Output JSON (only without --follow)')
823
+ .action(async (deployId, options) => {
824
+ try {
825
+ const code = await runLogs(deployId, {
826
+ server: options.server,
827
+ token: options.token,
828
+ follow: options.follow,
829
+ json: options.json,
830
+ });
831
+ process.exit(code);
832
+ }
833
+ catch (error) {
834
+ console.error(chalk.red(`\nLogs failed: ${error.message}`));
835
+ process.exit(1);
836
+ }
837
+ });
838
+ cli.command('rollback [projectId]', 'Rollback a project to a previous successful deployment')
839
+ .option('--server <url>', 'Override server URL')
840
+ .option('--token <token>', 'Override admin token (admin required)')
841
+ .option('--env <name>', 'Pick kite.config.<env>.json when no projectId given')
842
+ .option('--to <deployId>', 'Target deployment to roll back to (default: previous success)')
843
+ .option('--yes', 'Skip interactive confirmation (required in non-TTY)')
844
+ .option('--json', 'Output JSON on success')
845
+ .action(async (projectId, options) => {
846
+ try {
847
+ const code = await runRollback(projectId, {
848
+ server: options.server,
849
+ token: options.token,
850
+ env: options.env,
851
+ to: options.to,
852
+ yes: options.yes,
853
+ json: options.json,
854
+ });
855
+ process.exit(code);
856
+ }
857
+ catch (error) {
858
+ console.error(chalk.red(`\nRollback failed: ${error.message}`));
859
+ process.exit(1);
860
+ }
861
+ });
664
862
  cli.help();
665
863
  const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
666
864
  cli.version(pkg.version);