@kitecd/cli 1.0.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 +271 -9
  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 +14562 -5621
  10. package/dist/upload.js +11 -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-pjIWWLub.js +0 -1
  62. package/dist/web/assets/DefaultLayout-Bj8fPWym.css +0 -1
  63. package/dist/web/assets/DefaultLayout-DelfwTTT.js +0 -1
  64. package/dist/web/assets/FileExplorer-xY5ejhhN.js +0 -1
  65. package/dist/web/assets/LogBoard-DzW-cEqH.css +0 -1
  66. package/dist/web/assets/LogBoard-tT61QjOx.js +0 -6
  67. package/dist/web/assets/Login-B4C149oC.js +0 -1
  68. package/dist/web/assets/ProjectDetail-Z8cZoqr5.js +0 -1
  69. package/dist/web/assets/ProjectList-9rbMuJeY.js +0 -1
  70. package/dist/web/assets/Settings-CtCNDUXY.js +0 -1
  71. package/dist/web/assets/clock-BPXGSCIV.js +0 -1
  72. package/dist/web/assets/constants-C4Zrkm2g.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-C615tnMi.js +0 -2
  76. package/dist/web/assets/index-C9LiRc31.css +0 -1
  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/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
  // ==========================
@@ -48,6 +54,32 @@ cli.command('config:set <key> <value>', 'Set global configuration')
48
54
  }
49
55
  console.log(chalk.yellow('No kite.config*.json found, setting as global token.'));
50
56
  }
57
+ // serverUrl: 优先写入项目配置
58
+ if (key === 'serverUrl' && !options.global) {
59
+ const allEnvs = listProjectEnvs();
60
+ let resolved = null;
61
+ if (options.env) {
62
+ resolved = resolveProjectConfig(options.env);
63
+ }
64
+ else if (allEnvs.length === 1) {
65
+ resolved = allEnvs[0];
66
+ }
67
+ else if (allEnvs.length > 1) {
68
+ if (!process.stdin.isTTY) {
69
+ console.error(chalk.red('Multiple kite.config*.json found. Pass --env to specify environment.'));
70
+ process.exit(1);
71
+ }
72
+ console.error(chalk.red('Multiple kite.config*.json found. Pass --env to specify environment.'));
73
+ process.exit(1);
74
+ }
75
+ if (resolved) {
76
+ resolved.config.serverUrl = value;
77
+ fs.writeFileSync(resolved.configPath, `${JSON.stringify(resolved.config, null, 2)}\n`);
78
+ console.log(chalk.green(`Set serverUrl in ${resolved.configPath}`));
79
+ return;
80
+ }
81
+ console.log(chalk.yellow('No kite.config*.json found, setting as global serverUrl.'));
82
+ }
51
83
  setGlobalConfig(key, value);
52
84
  console.log(chalk.green(`Set ${key} = ${value}`));
53
85
  });
@@ -114,7 +146,7 @@ cli.command('config', 'Show current effective configuration (merged from all sou
114
146
  const projectId = localEnv.KITE_PROJECT_ID || projectConfig.projectId;
115
147
  const tokenKey = projectId ? envTokenKey(projectId, envName) : '';
116
148
  const token = localEnv.KITE_DEPLOY_TOKEN || localEnv.KITE_TOKEN || globalConfig.projectToken?.[tokenKey] || (envName && projectId ? globalConfig.projectToken?.[projectId] : undefined) || globalConfig.token;
117
- const serverUrl = localEnv.KITE_SERVER_URL || globalConfig.serverUrl;
149
+ const serverUrl = localEnv.KITE_SERVER_URL || projectConfig.serverUrl || globalConfig.serverUrl;
118
150
  const outputDir = localEnv.KITE_OUTPUT_DIR || projectConfig.outputDir || './';
119
151
  const preDeploy = localEnv.KITE_PRE_DEPLOY || projectConfig.preDeploy;
120
152
  const postDeploy = localEnv.KITE_DEPLOY_COMMAND || localEnv.KITE_POST_DEPLOY || projectConfig.command || projectConfig.postDeploy;
@@ -131,6 +163,10 @@ cli.command('config', 'Show current effective configuration (merged from all sou
131
163
  if (projectConfig.files?.length) {
132
164
  console.log(` files: ${projectConfig.files.join(', ')}`);
133
165
  }
166
+ if (projectConfig.env && Object.keys(projectConfig.env).length > 0) {
167
+ const entries = Object.entries(projectConfig.env).map(([k, v]) => `${k}=${v}`).join(', ');
168
+ console.log(` env: ${entries}`);
169
+ }
134
170
  if (allEnvs.length > 1) {
135
171
  console.log(chalk.gray('\nAvailable environments:'));
136
172
  for (const e of allEnvs) {
@@ -350,6 +386,7 @@ cli.command('init', 'Create kite.config.json without writing token into source c
350
386
  .option('--pre <script>', 'Pre-deploy script')
351
387
  .option('--post <script>', 'Post-deploy script')
352
388
  .option('--command <script>', 'Deploy command alias, same as --post')
389
+ .option('--set-env <vars>', 'Environment variables as JSON or KEY=VALUE')
353
390
  .action(async (options) => {
354
391
  const projectId = options.project;
355
392
  if (!projectId) {
@@ -364,17 +401,31 @@ cli.command('init', 'Create kite.config.json without writing token into source c
364
401
  outputDir: options.out || './dist',
365
402
  files: String(options.files || '**/*').split(',').map(item => item.trim()).filter(Boolean)
366
403
  };
404
+ if (options.server)
405
+ projectConfig.serverUrl = options.server;
367
406
  if (options.pre)
368
407
  projectConfig.preDeploy = options.pre;
369
408
  if (options.command || options.post)
370
409
  projectConfig.postDeploy = options.command || options.post;
410
+ if (options.setEnv) {
411
+ const envVars = {};
412
+ const raw = options.setEnv;
413
+ if (raw.trimStart().startsWith('{')) {
414
+ Object.assign(envVars, JSON.parse(raw));
415
+ }
416
+ else {
417
+ for (const pair of raw.split(',')) {
418
+ const eq = pair.indexOf('=');
419
+ if (eq > 0)
420
+ envVars[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
421
+ }
422
+ }
423
+ if (Object.keys(envVars).length > 0)
424
+ projectConfig.env = envVars;
425
+ }
371
426
  fs.writeFileSync(configPath, `${JSON.stringify(projectConfig, null, 2)}\n`);
372
427
  console.log(chalk.green(`Created ${configPath}`));
373
428
  console.log(chalk.gray('Deploy token is intentionally not written to kite config file.'));
374
- if (options.server) {
375
- setGlobalConfig('serverUrl', options.server);
376
- console.log(chalk.green(`Saved serverUrl to ${getKiteHome()}/config.json`));
377
- }
378
429
  if (options.token) {
379
430
  const tokenStore = options.tokenStore || await askTokenStore();
380
431
  const tokenKey = envTokenKey(projectId, env);
@@ -437,6 +488,8 @@ const displayPackResult = (result) => {
437
488
  cli.command('build', 'Pack project files and verify packaging (no upload)')
438
489
  .option('--env <name>', 'Environment name (selects kite.config.<name>.json)')
439
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.)')
440
493
  .action(async (options) => {
441
494
  try {
442
495
  const allEnvs = listProjectEnvs();
@@ -468,9 +521,21 @@ cli.command('build', 'Pack project files and verify packaging (no upload)')
468
521
  console.error(chalk.red(`Error: Output directory not found: ${sourceDir}`));
469
522
  process.exit(1);
470
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);
471
532
  const spinner = ora('Packing files...').start();
472
533
  const zipFilePath = path.resolve(process.cwd(), '.deploy-archive.zip');
473
- 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
+ });
474
539
  spinner.succeed(chalk.green('Pack successful!'));
475
540
  displayPackResult(result);
476
541
  console.log(chalk.gray(` Archive: ${zipFilePath}`));
@@ -492,7 +557,11 @@ cli.command('push', 'Push and deploy project')
492
557
  .option('--pre <script>', 'Pre-deploy script (Server side)')
493
558
  .option('--post <script>', 'Post-deploy script (Server side)')
494
559
  .option('--command <script>', 'Deploy command alias, same as --post')
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.)')
495
563
  .action(async (options) => {
564
+ const pushStartedAt = new Date().toISOString();
496
565
  try {
497
566
  // 1. 解析环境配置
498
567
  const allEnvs = listProjectEnvs();
@@ -538,7 +607,7 @@ cli.command('push', 'Push and deploy project')
538
607
  || globalConfig?.projectToken?.[tokenKey]
539
608
  || (envName ? globalConfig?.projectToken?.[projectId] : undefined)
540
609
  || globalConfig?.token;
541
- const serverUrl = options.server || localEnv.KITE_SERVER_URL || globalConfig?.serverUrl;
610
+ const serverUrl = options.server || localEnv.KITE_SERVER_URL || projectConfig.serverUrl || globalConfig?.serverUrl;
542
611
  if (!token && !serverUrl) {
543
612
  console.error(chalk.red('Missing token and serverUrl. Run `kite config:set serverUrl <url>` and `kite config:set token <token>`.'));
544
613
  process.exit(1);
@@ -555,15 +624,44 @@ cli.command('push', 'Push and deploy project')
555
624
  const files = projectConfig.files || [];
556
625
  const preDeploy = options.pre || localEnv.KITE_PRE_DEPLOY || projectConfig.preDeploy;
557
626
  const postDeploy = options.command || options.post || localEnv.KITE_DEPLOY_COMMAND || localEnv.KITE_POST_DEPLOY || projectConfig.command || projectConfig.postDeploy;
627
+ // 解析 env: 项目配置 + CLI --set-env 覆盖
628
+ let deployEnv = projectConfig.env || undefined;
629
+ if (options.setEnv) {
630
+ const cliEnv = {};
631
+ const raw = options.setEnv;
632
+ if (raw.trimStart().startsWith('{')) {
633
+ Object.assign(cliEnv, JSON.parse(raw));
634
+ }
635
+ else {
636
+ for (const pair of raw.split(',')) {
637
+ const eq = pair.indexOf('=');
638
+ if (eq > 0)
639
+ cliEnv[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
640
+ }
641
+ }
642
+ deployEnv = { ...deployEnv, ...cliEnv };
643
+ }
558
644
  const sourceDir = path.resolve(process.cwd(), outputDir);
559
645
  if (!fs.existsSync(sourceDir)) {
560
646
  console.error(chalk.red(`Error: Output directory not found: ${sourceDir}`));
561
647
  process.exit(1);
562
648
  }
563
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);
564
658
  const spinner = ora('Packing files...').start();
565
659
  const zipFilePath = path.resolve(process.cwd(), '.deploy-archive.zip');
566
- 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
+ });
567
665
  spinner.succeed(chalk.green('Packed successfully'));
568
666
  displayPackResult(packResult);
569
667
  // 6. 上传与部署
@@ -575,13 +673,21 @@ cli.command('push', 'Push and deploy project')
575
673
  zipFilePath,
576
674
  projectId,
577
675
  preDeploy,
578
- postDeploy
676
+ postDeploy,
677
+ env: deployEnv,
678
+ startedAt: pushStartedAt
579
679
  });
580
680
  if (result.success) {
581
681
  console.log(chalk.green(`\nDeployed successfully! (${result.duration})`));
682
+ if (result.traceId) {
683
+ console.log(chalk.gray(` trace: ${result.traceId}`));
684
+ }
582
685
  }
583
686
  else {
584
687
  console.error(chalk.red('\nDeployment failed'));
688
+ if (result.traceId) {
689
+ console.error(chalk.gray(` trace: ${result.traceId}`));
690
+ }
585
691
  process.exit(1);
586
692
  }
587
693
  }
@@ -597,6 +703,162 @@ cli.command('push', 'Push and deploy project')
597
703
  }
598
704
  }
599
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
+ });
600
862
  cli.help();
601
863
  const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
602
864
  cli.version(pkg.version);