@nocobase/cli 2.1.0-alpha.23 → 2.1.0-alpha.25

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 (46) hide show
  1. package/README.md +37 -2
  2. package/README.zh-CN.md +6 -2
  3. package/bin/run.js +28 -11
  4. package/dist/commands/db/shared.js +19 -5
  5. package/dist/commands/dev.js +8 -1
  6. package/dist/commands/down.js +10 -6
  7. package/dist/commands/env/add.js +14 -34
  8. package/dist/commands/env/auth.js +6 -13
  9. package/dist/commands/env/list.js +10 -15
  10. package/dist/commands/env/remove.js +4 -10
  11. package/dist/commands/env/update.js +7 -13
  12. package/dist/commands/env/use.js +5 -13
  13. package/dist/commands/init.js +190 -62
  14. package/dist/commands/install.js +101 -33
  15. package/dist/commands/logs.js +8 -1
  16. package/dist/commands/pm/list.js +8 -1
  17. package/dist/commands/ps.js +18 -15
  18. package/dist/commands/restart.js +74 -0
  19. package/dist/commands/self/check.js +71 -0
  20. package/dist/commands/self/index.js +20 -0
  21. package/dist/commands/self/update.js +86 -0
  22. package/dist/commands/skills/check.js +69 -0
  23. package/dist/commands/skills/index.js +20 -0
  24. package/dist/commands/skills/install.js +71 -0
  25. package/dist/commands/skills/update.js +71 -0
  26. package/dist/commands/start.js +8 -1
  27. package/dist/commands/stop.js +8 -1
  28. package/dist/commands/test.js +466 -0
  29. package/dist/commands/upgrade.js +12 -1
  30. package/dist/lib/api-client.js +3 -2
  31. package/dist/lib/app-runtime.js +16 -5
  32. package/dist/lib/auth-store.js +159 -43
  33. package/dist/lib/bootstrap.js +13 -12
  34. package/dist/lib/cli-home.js +33 -2
  35. package/dist/lib/env-auth.js +3 -3
  36. package/dist/lib/generated-command.js +10 -2
  37. package/dist/lib/http-request.js +49 -0
  38. package/dist/lib/resource-command.js +10 -2
  39. package/dist/lib/run-npm.js +82 -8
  40. package/dist/lib/runtime-generator.js +1 -1
  41. package/dist/lib/self-manager.js +246 -0
  42. package/dist/lib/skills-manager.js +269 -0
  43. package/dist/lib/startup-update.js +203 -0
  44. package/dist/locale/en-US.json +4 -1
  45. package/dist/locale/zh-CN.json +4 -1
  46. package/package.json +10 -4
@@ -16,6 +16,7 @@ import path from 'node:path';
16
16
  import { exit } from 'node:process';
17
17
  import { runPromptCatalog, } from "../lib/prompt-catalog.js";
18
18
  import { applyCliLocale, localeText, resolveCliLocale, translateCli, } from "../lib/cli-locale.js";
19
+ import { resolveConfiguredEnvPath, resolveDefaultConfigScope, resolveEnvRelativePath, } from '../lib/cli-home.js';
19
20
  import { findAvailableTcpPort, validateAvailableTcpPort, validateTcpPort, validateEnvKey, } from "../lib/prompt-validators.js";
20
21
  import { formatMissingManagedAppEnvMessage } from '../lib/app-runtime.js';
21
22
  import { run, runNocoBaseCommand } from '../lib/run-npm.js';
@@ -53,7 +54,6 @@ const DEFAULT_INSTALL_ROOT_USERNAME = 'nocobase';
53
54
  const DEFAULT_INSTALL_ROOT_EMAIL = 'admin@nocobase.com';
54
55
  const DEFAULT_INSTALL_ROOT_PASSWORD = 'admin123';
55
56
  const DEFAULT_INSTALL_ROOT_NICKNAME = 'Super Admin';
56
- const CONFIG_SCOPE = 'project';
57
57
  const APP_HEALTH_CHECK_INTERVAL_MS = 2_000;
58
58
  const APP_HEALTH_CHECK_TIMEOUT_MS = 600_000;
59
59
  const APP_HEALTH_CHECK_REQUEST_TIMEOUT_MS = 5_000;
@@ -137,6 +137,11 @@ function argvHasToken(argv, tokens) {
137
137
  function isInstallDbDialect(value) {
138
138
  return INSTALL_DB_DIALECTS.includes(value);
139
139
  }
140
+ function downloadVersionPromptValue(version) {
141
+ return version === 'latest' || version === 'beta' || version === 'alpha'
142
+ ? version
143
+ : 'other';
144
+ }
140
145
  function supportsBuiltinDbDialect(value) {
141
146
  const dialect = String(value ?? '').trim();
142
147
  return Object.prototype.hasOwnProperty.call(DEFAULT_INSTALL_BUILTIN_DB_IMAGES, dialect);
@@ -303,6 +308,7 @@ export default class Install extends Command {
303
308
  required: false,
304
309
  }),
305
310
  'builtin-db': Flags.boolean({
311
+ allowNo: true,
306
312
  description: 'Create and connect a CLI-managed built-in database for the app',
307
313
  default: false,
308
314
  }),
@@ -569,7 +575,7 @@ export default class Install extends Command {
569
575
  if (argvHasToken(argv, ['--fetch-source'])) {
570
576
  preset.fetchSource = flags['fetch-source'];
571
577
  }
572
- if (argvHasToken(argv, ['--builtin-db'])) {
578
+ if (argvHasToken(argv, ['--builtin-db', '--no-builtin-db'])) {
573
579
  preset.builtinDb = flags['builtin-db'];
574
580
  }
575
581
  if (flags['db-dialect'] !== undefined) {
@@ -588,6 +594,7 @@ export default class Install extends Command {
588
594
  const v = String(flags['db-host'] ?? '').trim();
589
595
  if (v) {
590
596
  preset.dbHost = v;
597
+ preset.builtinDb = false;
591
598
  }
592
599
  }
593
600
  if (flags['db-port'] !== undefined) {
@@ -682,7 +689,14 @@ export default class Install extends Command {
682
689
  };
683
690
  const downloadPreset = {
684
691
  ...(source ? { source } : {}),
685
- ...(downloadVersion ? { version: downloadVersion } : {}),
692
+ ...(downloadVersion
693
+ ? {
694
+ version: downloadVersionPromptValue(downloadVersion),
695
+ ...(downloadVersionPromptValue(downloadVersion) === 'other'
696
+ ? { otherVersion: downloadVersion }
697
+ : {}),
698
+ }
699
+ : {}),
686
700
  ...(dockerRegistry ? { dockerRegistry } : {}),
687
701
  ...(dockerPlatform ? { dockerPlatform } : {}),
688
702
  ...(gitUrl ? { gitUrl } : {}),
@@ -746,7 +760,7 @@ export default class Install extends Command {
746
760
  if (!parsed.resume) {
747
761
  return undefined;
748
762
  }
749
- const env = await getEnv(parsed.env, { scope: CONFIG_SCOPE });
763
+ const env = await getEnv(parsed.env, { scope: resolveDefaultConfigScope() });
750
764
  if (!env) {
751
765
  throw new Error(formatMissingManagedAppEnvMessage(parsed.env));
752
766
  }
@@ -861,7 +875,11 @@ export default class Install extends Command {
861
875
  preset.source = String(flags.source).trim();
862
876
  }
863
877
  if (flags.version !== undefined) {
864
- preset.version = String(flags.version).trim() || 'latest';
878
+ const version = String(flags.version).trim() || 'latest';
879
+ preset.version = downloadVersionPromptValue(version);
880
+ if (preset.version === 'other') {
881
+ preset.otherVersion = version;
882
+ }
865
883
  }
866
884
  if (flags['docker-registry'] !== undefined) {
867
885
  const value = String(flags['docker-registry'] ?? '').trim();
@@ -891,7 +909,10 @@ export default class Install extends Command {
891
909
  preset.npmRegistry =
892
910
  typeof flags['npm-registry'] === 'string' ? flags['npm-registry'] : '';
893
911
  }
894
- if (argvHasToken(argv, ['--replace', '-r'])) {
912
+ if (flags.resume && !argvHasToken(argv, ['--replace', '-r'])) {
913
+ preset.replace = true;
914
+ }
915
+ else if (argvHasToken(argv, ['--replace', '-r'])) {
895
916
  preset.replace = flags.replace;
896
917
  }
897
918
  if (argvHasToken(argv, ['--dev-dependencies', '--no-dev-dependencies', '-D'])) {
@@ -933,7 +954,7 @@ export default class Install extends Command {
933
954
  : Install.defaultWorkspaceName();
934
955
  }
935
956
  static async ensureWorkspaceName() {
936
- return await ensureWorkspaceName(Install.defaultWorkspaceName(), { scope: CONFIG_SCOPE });
957
+ return await ensureWorkspaceName(Install.defaultWorkspaceName(), { scope: resolveDefaultConfigScope() });
937
958
  }
938
959
  static buildBuiltinDbNetworkName(envName, workspaceName) {
939
960
  return Install.buildBuiltinDbResourcePrefix(envName, workspaceName);
@@ -982,9 +1003,11 @@ export default class Install extends Command {
982
1003
  && dbHostInput !== 'localhost'
983
1004
  ? dbHostInput
984
1005
  : containerName);
1006
+ const storagePath = resolveConfiguredEnvPath(params.storagePath)
1007
+ ?? resolveEnvRelativePath(defaultInstallStoragePath(params.envName));
985
1008
  if (dbDialect === 'postgres') {
986
1009
  const image = String(params.builtinDbImage ?? '').trim() || defaultBuiltinDbImageForDialect(dbDialect);
987
- const dataDir = path.resolve(params.storagePath, 'db', 'postgres');
1010
+ const dataDir = path.resolve(storagePath, 'db', 'postgres');
988
1011
  const args = [
989
1012
  'run',
990
1013
  '-d',
@@ -1028,7 +1051,7 @@ export default class Install extends Command {
1028
1051
  }
1029
1052
  if (dbDialect === 'mysql') {
1030
1053
  const image = String(params.builtinDbImage ?? '').trim() || defaultBuiltinDbImageForDialect(dbDialect);
1031
- const dataDir = path.resolve(params.storagePath, 'db', 'mysql');
1054
+ const dataDir = path.resolve(storagePath, 'db', 'mysql');
1032
1055
  const dbUser = String(params.dbUser ?? DEFAULT_INSTALL_DB_USER).trim() || DEFAULT_INSTALL_DB_USER;
1033
1056
  const dbDatabase = String(params.dbDatabase ?? defaultDbDatabase).trim() || defaultDbDatabase;
1034
1057
  const dbPassword = String(params.dbPassword ?? DEFAULT_INSTALL_DB_PASSWORD) || DEFAULT_INSTALL_DB_PASSWORD;
@@ -1074,7 +1097,7 @@ export default class Install extends Command {
1074
1097
  }
1075
1098
  if (dbDialect === 'mariadb') {
1076
1099
  const image = String(params.builtinDbImage ?? '').trim() || defaultBuiltinDbImageForDialect(dbDialect);
1077
- const dataDir = path.resolve(params.storagePath, 'db', 'mariadb');
1100
+ const dataDir = path.resolve(storagePath, 'db', 'mariadb');
1078
1101
  const dbUser = String(params.dbUser ?? DEFAULT_INSTALL_DB_USER).trim() || DEFAULT_INSTALL_DB_USER;
1079
1102
  const dbDatabase = String(params.dbDatabase ?? defaultDbDatabase).trim() || defaultDbDatabase;
1080
1103
  const dbPassword = String(params.dbPassword ?? DEFAULT_INSTALL_DB_PASSWORD) || DEFAULT_INSTALL_DB_PASSWORD;
@@ -1120,7 +1143,7 @@ export default class Install extends Command {
1120
1143
  }
1121
1144
  if (dbDialect === 'kingbase') {
1122
1145
  const image = String(params.builtinDbImage ?? '').trim() || defaultBuiltinDbImageForDialect(dbDialect);
1123
- const dataDir = path.resolve(params.storagePath, 'db', 'kingbase');
1146
+ const dataDir = path.resolve(storagePath, 'db', 'kingbase');
1124
1147
  const dbUser = String(params.dbUser ?? DEFAULT_INSTALL_DB_USER).trim() || DEFAULT_INSTALL_DB_USER;
1125
1148
  const dbDatabase = String(params.dbDatabase ?? defaultDbDatabase).trim() || defaultDbDatabase;
1126
1149
  const dbPassword = String(params.dbPassword ?? DEFAULT_INSTALL_DB_PASSWORD) || DEFAULT_INSTALL_DB_PASSWORD;
@@ -1207,6 +1230,7 @@ export default class Install extends Command {
1207
1230
  async removeDockerContainer(name) {
1208
1231
  await run('docker', ['rm', '-f', name], {
1209
1232
  errorName: 'docker rm',
1233
+ stdio: 'ignore',
1210
1234
  });
1211
1235
  }
1212
1236
  async removeDockerContainerIfForced(params) {
@@ -1238,7 +1262,7 @@ export default class Install extends Command {
1238
1262
  }
1239
1263
  return env;
1240
1264
  }
1241
- async ensureBuiltinDbContainer(plan) {
1265
+ async ensureBuiltinDbContainer(plan, options) {
1242
1266
  const exists = await this.dockerContainerExists(plan.containerName);
1243
1267
  if (exists) {
1244
1268
  p.log.info(`Built-in ${plan.dbDialect} container already exists: ${plan.containerName}`);
@@ -1247,6 +1271,7 @@ export default class Install extends Command {
1247
1271
  await mkdir(plan.dataDir, { recursive: true });
1248
1272
  await run('docker', plan.args, {
1249
1273
  errorName: 'docker run',
1274
+ stdio: options?.stdio ?? 'ignore',
1250
1275
  });
1251
1276
  }
1252
1277
  async startBuiltinDb(params) {
@@ -1267,18 +1292,20 @@ export default class Install extends Command {
1267
1292
  });
1268
1293
  p.log.step(`Preparing built-in ${plan.dbDialect} database`);
1269
1294
  await this.ensureDockerNetwork(plan.networkName);
1270
- await this.removeDockerContainerIfForced({
1295
+ const existingContainerKept = await this.removeDockerContainerIfForced({
1271
1296
  containerName: plan.containerName,
1272
1297
  displayName: `built-in ${plan.dbDialect} container`,
1273
1298
  force: params.force,
1274
1299
  });
1275
- if (Install.shouldPublishBuiltinDbPort(params.downloadResults.source)) {
1300
+ if (!existingContainerKept && Install.shouldPublishBuiltinDbPort(params.downloadResults.source)) {
1276
1301
  const portError = await validateAvailableTcpPort(plan.dbPort);
1277
1302
  if (portError) {
1278
1303
  throw new Error(`Built-in ${plan.dbDialect} needs host port ${plan.dbPort}, but ${portError}`);
1279
1304
  }
1280
1305
  }
1281
- await this.ensureBuiltinDbContainer(plan);
1306
+ await this.ensureBuiltinDbContainer(plan, {
1307
+ stdio: params.commandStdio ?? 'ignore',
1308
+ });
1282
1309
  p.log.info(`Built-in ${plan.dbDialect} database is ready at ${plan.dbHost}:${plan.dbPort}`);
1283
1310
  return plan;
1284
1311
  }
@@ -1287,8 +1314,9 @@ export default class Install extends Command {
1287
1314
  || defaultDockerRegistryForLang(process.env.NB_LOCALE);
1288
1315
  const version = String(downloadResultsValue(params.downloadResults, 'version') ?? '').trim() || 'latest';
1289
1316
  const appPort = String(params.appResults.appPort ?? DEFAULT_INSTALL_APP_PORT).trim() || DEFAULT_INSTALL_APP_PORT;
1290
- const storagePath = path.resolve(String(params.appResults.storagePath ?? '').trim()
1291
- || defaultInstallStoragePath(params.envName));
1317
+ const storagePath = resolveConfiguredEnvPath(String(params.appResults.storagePath ?? '').trim()
1318
+ || defaultInstallStoragePath(params.envName))
1319
+ ?? resolveEnvRelativePath(defaultInstallStoragePath(params.envName));
1292
1320
  const dbDialect = String(params.dbResults.dbDialect ?? 'postgres').trim() || 'postgres';
1293
1321
  const dbHost = String(params.dbResults.dbHost ?? DEFAULT_INSTALL_DB_HOST).trim() || DEFAULT_INSTALL_DB_HOST;
1294
1322
  const dbPort = String(params.dbResults.dbPort ?? defaultDbPortForDialect(dbDialect)).trim()
@@ -1332,7 +1360,7 @@ export default class Install extends Command {
1332
1360
  args,
1333
1361
  };
1334
1362
  }
1335
- async ensureDockerAppContainer(plan) {
1363
+ async ensureDockerAppContainer(plan, options) {
1336
1364
  const exists = await this.dockerContainerExists(plan.containerName);
1337
1365
  if (exists) {
1338
1366
  p.log.info(`App container already exists: ${plan.containerName}`);
@@ -1341,6 +1369,7 @@ export default class Install extends Command {
1341
1369
  await mkdir(plan.storagePath, { recursive: true });
1342
1370
  await run('docker', plan.args, {
1343
1371
  errorName: 'docker run',
1372
+ stdio: options?.stdio ?? 'ignore',
1344
1373
  });
1345
1374
  return 'created';
1346
1375
  }
@@ -1363,7 +1392,9 @@ export default class Install extends Command {
1363
1392
  displayName: 'app container',
1364
1393
  force: params.force,
1365
1394
  });
1366
- const containerState = await this.ensureDockerAppContainer(plan);
1395
+ const containerState = await this.ensureDockerAppContainer(plan, {
1396
+ stdio: params.commandStdio ?? 'ignore',
1397
+ });
1367
1398
  if (containerState === 'existing') {
1368
1399
  const env = await this.inspectDockerContainerEnv(plan.containerName);
1369
1400
  plan.appKey = env.APP_KEY || plan.appKey;
@@ -1380,12 +1411,16 @@ export default class Install extends Command {
1380
1411
  }
1381
1412
  static buildDownloadArgvFromResults(results, options) {
1382
1413
  const argv = ['-y', '--no-intro'];
1414
+ const source = String(results.source ?? '').trim();
1383
1415
  if (options?.verbose) {
1384
1416
  argv.push('--verbose');
1385
1417
  }
1386
1418
  Install.pushDownloadArgIfValue(argv, '--source', results.source);
1387
- Install.pushDownloadArgIfValue(argv, '--version', results.version);
1388
- Install.pushDownloadArgIfValue(argv, '--output-dir', results.outputDir);
1419
+ Install.pushDownloadArgIfValue(argv, '--version', downloadResultsValue(results, 'version'));
1420
+ Install.pushDownloadArgIfValue(argv, '--output-dir', source === 'npm' || source === 'git'
1421
+ ? (resolveConfiguredEnvPath(results.outputDir)
1422
+ ?? resolveConfiguredEnvPath(String(results.outputDir ?? '').trim() || defaultInstallAppRootPath(results.env)))
1423
+ : results.outputDir);
1389
1424
  Install.pushDownloadArgIfValue(argv, '--git-url', results.gitUrl);
1390
1425
  Install.pushDownloadArgIfValue(argv, '--docker-registry', results.dockerRegistry);
1391
1426
  Install.pushDownloadArgIfValue(argv, '--docker-platform', results.dockerPlatform);
@@ -1415,26 +1450,49 @@ export default class Install extends Command {
1415
1450
  const outputDir = String(params.downloadResults.outputDir ?? '').trim()
1416
1451
  || String(params.appResults.appRootPath ?? '').trim()
1417
1452
  || defaultInstallAppRootPath(params.envName);
1418
- return path.resolve(process.cwd(), outputDir);
1453
+ return resolveConfiguredEnvPath(outputDir) ?? resolveEnvRelativePath(defaultInstallAppRootPath(params.envName));
1419
1454
  }
1420
- async downloadLocalApp(params) {
1455
+ static resolveLocalProjectConfigPath(params) {
1456
+ return (String(params.downloadResults.outputDir ?? '').trim()
1457
+ || String(params.appResults.appRootPath ?? '').trim()
1458
+ || defaultInstallAppRootPath(params.envName));
1459
+ }
1460
+ commandStdio(verbose) {
1461
+ return verbose ? 'inherit' : 'ignore';
1462
+ }
1463
+ async downloadManagedSource(params) {
1421
1464
  const argv = Install.buildDownloadArgvFromResults(params.downloadResults, {
1422
1465
  verbose: params.verbose,
1423
1466
  });
1424
- p.log.step('Downloading local NocoBase app files');
1425
- const result = await this.config.runCommand('download', argv);
1426
- const projectRoot = Install.resolveLocalProjectRoot({
1467
+ const source = String(params.downloadResults.source ?? '').trim();
1468
+ p.log.step(source === 'docker'
1469
+ ? 'Downloading Docker image'
1470
+ : 'Downloading local NocoBase app files');
1471
+ return await this.config.runCommand('download', argv);
1472
+ }
1473
+ async downloadLocalApp(params) {
1474
+ const result = await this.downloadManagedSource({
1475
+ downloadResults: params.downloadResults,
1476
+ verbose: params.verbose,
1477
+ });
1478
+ const downloadedProjectRoot = Install.resolveLocalProjectRoot({
1427
1479
  envName: params.envName,
1428
1480
  appResults: params.appResults,
1429
1481
  downloadResults: params.downloadResults,
1430
1482
  downloadCommandResult: result,
1431
1483
  });
1432
- params.appResults.appRootPath = projectRoot;
1433
- return projectRoot;
1484
+ params.appResults.appRootPath = Install.resolveLocalProjectConfigPath({
1485
+ envName: params.envName,
1486
+ appResults: params.appResults,
1487
+ downloadResults: params.downloadResults,
1488
+ });
1489
+ return downloadedProjectRoot;
1434
1490
  }
1435
1491
  static buildLocalAppEnvVars(params) {
1436
- const storagePath = path.resolve(String(params.appResults.storagePath ?? '').trim()
1437
- || defaultInstallStoragePath(params.envName));
1492
+ const configuredStoragePath = String(params.appResults.storagePath ?? '').trim()
1493
+ || defaultInstallStoragePath(params.envName);
1494
+ const storagePath = resolveConfiguredEnvPath(configuredStoragePath)
1495
+ ?? resolveEnvRelativePath(defaultInstallStoragePath(params.envName));
1438
1496
  const dbDialect = String(params.dbResults.dbDialect ?? 'postgres').trim()
1439
1497
  || 'postgres';
1440
1498
  const appKey = crypto.randomBytes(32).toString('hex');
@@ -1476,6 +1534,7 @@ export default class Install extends Command {
1476
1534
  await runNocoBaseCommand(['pm2', 'kill'], {
1477
1535
  cwd: params.projectRoot,
1478
1536
  env,
1537
+ stdio: params.commandStdio ?? 'ignore',
1479
1538
  });
1480
1539
  }
1481
1540
  catch (error) {
@@ -1486,6 +1545,7 @@ export default class Install extends Command {
1486
1545
  await runNocoBaseCommand(args, {
1487
1546
  cwd: params.projectRoot,
1488
1547
  env,
1548
+ stdio: params.commandStdio ?? 'ignore',
1489
1549
  });
1490
1550
  p.log.info(`Local app is starting at http://127.0.0.1:${env.APP_PORT}`);
1491
1551
  return {
@@ -1625,8 +1685,6 @@ export default class Install extends Command {
1625
1685
  const argv = [
1626
1686
  params.envName,
1627
1687
  '--no-intro',
1628
- '--scope',
1629
- CONFIG_SCOPE,
1630
1688
  '--api-base-url',
1631
1689
  apiBaseUrl,
1632
1690
  '--auth-type',
@@ -1739,7 +1797,6 @@ export default class Install extends Command {
1739
1797
  },
1740
1798
  values: {
1741
1799
  name: envName,
1742
- scope: 'project',
1743
1800
  ...(resumePreset?.envAddPreset ?? {}),
1744
1801
  },
1745
1802
  yes,
@@ -1761,6 +1818,7 @@ export default class Install extends Command {
1761
1818
  const parsed = {
1762
1819
  ...flags,
1763
1820
  };
1821
+ const commandStdio = this.commandStdio(parsed.verbose);
1764
1822
  if (!parsed['no-intro']) {
1765
1823
  p.intro('Set Up NocoBase');
1766
1824
  }
@@ -1787,6 +1845,7 @@ export default class Install extends Command {
1787
1845
  downloadResults,
1788
1846
  dbResults,
1789
1847
  force: parsed.force,
1848
+ commandStdio,
1790
1849
  });
1791
1850
  dbResults.dbHost = builtinDbPlan.dbHost;
1792
1851
  dbResults.dbPort = builtinDbPlan.dbPort;
@@ -1799,6 +1858,10 @@ export default class Install extends Command {
1799
1858
  let localAppPlan;
1800
1859
  if (Boolean(appResults.fetchSource)) {
1801
1860
  if (source === 'docker') {
1861
+ await this.downloadManagedSource({
1862
+ downloadResults,
1863
+ verbose: parsed.verbose,
1864
+ });
1802
1865
  dockerAppPlan = await this.installDockerApp({
1803
1866
  envName,
1804
1867
  workspaceName,
@@ -1808,6 +1871,7 @@ export default class Install extends Command {
1808
1871
  rootResults,
1809
1872
  builtinDbPlan,
1810
1873
  force: parsed.force,
1874
+ commandStdio,
1811
1875
  });
1812
1876
  appResults.appKey = dockerAppPlan.appKey;
1813
1877
  appResults.timeZone = dockerAppPlan.timeZone;
@@ -1826,6 +1890,7 @@ export default class Install extends Command {
1826
1890
  appResults,
1827
1891
  dbResults,
1828
1892
  rootResults,
1893
+ commandStdio,
1829
1894
  });
1830
1895
  appResults.appKey = localAppPlan.appKey;
1831
1896
  appResults.timeZone = localAppPlan.timeZone;
@@ -1855,5 +1920,8 @@ export default class Install extends Command {
1855
1920
  }
1856
1921
  }
1857
1922
  function downloadResultsValue(downloadResults, key) {
1923
+ if (key === 'version' && String(downloadResults.version ?? '').trim() === 'other') {
1924
+ return downloadResults.otherVersion;
1925
+ }
1858
1926
  return downloadResults[key];
1859
1927
  }
@@ -49,13 +49,20 @@ export default class Logs extends Command {
49
49
  if (!runtime) {
50
50
  this.error(formatMissingManagedAppEnvMessage(requestedEnv));
51
51
  }
52
- if (runtime.kind === 'remote') {
52
+ if (runtime.kind === 'http') {
53
53
  this.error([
54
54
  `Can't show runtime logs for "${runtime.envName}" from this machine.`,
55
55
  'This env only has an API connection, so there is no saved local app or Docker container to read logs from.',
56
56
  'Connect it to a local checkout or reinstall it with npm, git, or Docker if you want CLI-managed logs.',
57
57
  ].join('\n'));
58
58
  }
59
+ if (runtime.kind === 'ssh') {
60
+ this.error([
61
+ `Can't show runtime logs for "${runtime.envName}" yet.`,
62
+ 'SSH env support is reserved but not implemented yet.',
63
+ 'Use a local or Docker env for CLI-managed logs for now.',
64
+ ].join('\n'));
65
+ }
59
66
  const tail = String(flags.tail ?? 100);
60
67
  const follow = flags.follow !== false;
61
68
  printInfo(follow
@@ -11,7 +11,7 @@ import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runDockerN
11
11
  export default class PmList extends Command {
12
12
  static args = {};
13
13
  static summary = 'List plugins for the selected env';
14
- static description = 'List installed plugins in the selected env (npm/git runs locally, Docker runs inside the saved app container, remote envs fall back to the API)';
14
+ static description = 'List installed plugins in the selected env (npm/git runs locally, Docker runs inside the saved app container, HTTP envs fall back to the API)';
15
15
  static examples = [
16
16
  '<%= config.bin %> <%= command.id %>',
17
17
  '<%= config.bin %> <%= command.id %> -e local',
@@ -49,6 +49,13 @@ export default class PmList extends Command {
49
49
  }
50
50
  return;
51
51
  }
52
+ if (runtime.kind === 'ssh') {
53
+ this.error([
54
+ `Can't list plugins for "${runtime.envName}" yet.`,
55
+ 'SSH env support is reserved but not implemented yet.',
56
+ 'Use a local, Docker, or HTTP env for plugin inspection right now.',
57
+ ].join('\n'));
58
+ }
52
59
  await this.config.runCommand('api:pm:list', ['--mode=summary']);
53
60
  }
54
61
  }
@@ -10,12 +10,15 @@ import { Command, Flags } from '@oclif/core';
10
10
  import { buildDockerDbContainerName, dockerContainerExists, dockerContainerIsRunning, formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, } from '../lib/app-runtime.js';
11
11
  import { listEnvs } from '../lib/auth-store.js';
12
12
  import { renderTable } from '../lib/ui.js';
13
+ function resolveApiBaseUrl(config) {
14
+ return String(config.apiBaseUrl ?? config.baseUrl ?? config.apibaseUrl ?? '').trim();
15
+ }
13
16
  function appUrl(runtime) {
14
17
  const port = String(runtime.env.config.appPort ?? '').trim();
15
18
  if (port) {
16
19
  return `http://127.0.0.1:${port}`;
17
20
  }
18
- const baseUrl = String(runtime.env.config.baseUrl ?? '').trim();
21
+ const baseUrl = resolveApiBaseUrl(runtime.env.config);
19
22
  return baseUrl.replace(/\/api\/?$/, '');
20
23
  }
21
24
  async function isLocalAppHealthy(runtime) {
@@ -47,30 +50,30 @@ async function dockerStatus(containerName) {
47
50
  }
48
51
  async function dbStatus(runtime) {
49
52
  if (!runtime.env.config.builtinDb) {
50
- return runtime.kind === 'remote' ? 'external' : '-';
53
+ return runtime.kind === 'http' ? 'external' : '-';
51
54
  }
52
- if (runtime.kind === 'remote') {
55
+ if (runtime.kind === 'http') {
53
56
  return 'external';
54
57
  }
58
+ if (runtime.kind === 'ssh') {
59
+ return '-';
60
+ }
55
61
  const dbDialect = String(runtime.env.config.dbDialect ?? 'postgres').trim() || 'postgres';
56
62
  const containerName = buildDockerDbContainerName(runtime.envName, dbDialect, runtime.workspaceName);
57
63
  return await dockerStatus(containerName);
58
64
  }
59
- async function appStatus(runtime) {
60
- if (runtime.kind === 'remote') {
61
- return 'remote';
65
+ async function runtimeStatus(runtime) {
66
+ if (runtime.kind === 'http') {
67
+ return 'http';
68
+ }
69
+ if (runtime.kind === 'ssh') {
70
+ return 'ssh';
62
71
  }
63
72
  if (runtime.kind === 'docker') {
64
73
  return await dockerStatus(runtime.containerName);
65
74
  }
66
75
  return await isLocalAppHealthy(runtime) ? 'running' : 'stopped';
67
76
  }
68
- function sourceLabel(runtime) {
69
- if (runtime.kind === 'remote') {
70
- return 'remote';
71
- }
72
- return runtime.source;
73
- }
74
77
  export default class Ps extends Command {
75
78
  static description = 'Show NocoBase runtime status for configured envs without starting or stopping anything.';
76
79
  static examples = [
@@ -105,12 +108,12 @@ export default class Ps extends Command {
105
108
  }
106
109
  rows.push([
107
110
  runtime.envName,
108
- sourceLabel(runtime),
109
- await appStatus(runtime),
111
+ runtime.kind,
112
+ await runtimeStatus(runtime),
110
113
  await dbStatus(runtime),
111
114
  appUrl(runtime),
112
115
  ]);
113
116
  }
114
- this.log(renderTable(['Env', 'Source', 'App', 'Database', 'URL'], rows));
117
+ this.log(renderTable(['Env', 'Kind', 'Status', 'Database', 'URL'], rows));
115
118
  }
116
119
  }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import { Command, Flags } from '@oclif/core';
10
+ function argvHasToken(argv, tokens) {
11
+ return tokens.some((token) => argv.includes(token));
12
+ }
13
+ function pushFlag(argv, flag, value) {
14
+ if (value !== undefined) {
15
+ argv.push(flag, String(value));
16
+ }
17
+ }
18
+ export default class Restart extends Command {
19
+ static description = 'Restart NocoBase for the selected env by stopping it first, then starting it again.';
20
+ static examples = [
21
+ '<%= config.bin %> <%= command.id %>',
22
+ '<%= config.bin %> <%= command.id %> --env local',
23
+ '<%= config.bin %> <%= command.id %> --env local --quickstart',
24
+ '<%= config.bin %> <%= command.id %> --env local --port 12000',
25
+ '<%= config.bin %> <%= command.id %> --env local --daemon',
26
+ '<%= config.bin %> <%= command.id %> --env local --no-daemon',
27
+ '<%= config.bin %> <%= command.id %> --env local --instances 2',
28
+ '<%= config.bin %> <%= command.id %> --env local --launch-mode pm2',
29
+ '<%= config.bin %> <%= command.id %> --env local --verbose',
30
+ '<%= config.bin %> <%= command.id %> --env local-docker',
31
+ ];
32
+ static flags = {
33
+ env: Flags.string({
34
+ char: 'e',
35
+ description: 'CLI env name to restart. Defaults to the current env when omitted',
36
+ }),
37
+ quickstart: Flags.boolean({ description: 'Quickstart the application after stopping it', required: false }),
38
+ port: Flags.string({ description: 'Port (overrides appPort from env config when set)', char: 'p', required: false }),
39
+ daemon: Flags.boolean({
40
+ description: 'Run the application as a daemon after stopping it (default: true; use --no-daemon to stay in the foreground)',
41
+ char: 'd',
42
+ required: false,
43
+ default: true,
44
+ allowNo: true,
45
+ }),
46
+ instances: Flags.integer({ description: 'Number of instances to run after stopping it', char: 'i', required: false }),
47
+ 'launch-mode': Flags.string({ description: 'Launch Mode', required: false, options: ['pm2', 'node'] }),
48
+ verbose: Flags.boolean({
49
+ description: 'Show raw shutdown/startup output from the underlying local or Docker command',
50
+ default: false,
51
+ }),
52
+ };
53
+ async run() {
54
+ const { flags } = await this.parse(Restart);
55
+ const stopArgv = [];
56
+ const daemonFlagWasProvided = argvHasToken(this.argv, ['--daemon', '--no-daemon']);
57
+ pushFlag(stopArgv, '--env', flags.env?.trim() || undefined);
58
+ if (flags.verbose) {
59
+ stopArgv.push('--verbose');
60
+ }
61
+ await this.config.runCommand('stop', stopArgv);
62
+ const startArgv = [...stopArgv];
63
+ if (flags.quickstart) {
64
+ startArgv.push('--quickstart');
65
+ }
66
+ pushFlag(startArgv, '--port', flags.port);
67
+ if (daemonFlagWasProvided) {
68
+ startArgv.push(flags.daemon === false ? '--no-daemon' : '--daemon');
69
+ }
70
+ pushFlag(startArgv, '--instances', flags.instances);
71
+ pushFlag(startArgv, '--launch-mode', flags['launch-mode']);
72
+ await this.config.runCommand('start', startArgv);
73
+ }
74
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import { Command, Flags } from '@oclif/core';
10
+ import { getRecommendedSelfUpdateCommand, inspectSelfStatus, } from '../../lib/self-manager.js';
11
+ import { printInfo, renderTable } from '../../lib/ui.js';
12
+ export default class SelfCheck extends Command {
13
+ static summary = 'Check the installed NocoBase CLI version and self-update support';
14
+ static description = 'Inspect the current NocoBase CLI install, resolve the latest version for the selected channel, and report whether automatic self-update is supported.';
15
+ static examples = [
16
+ '<%= config.bin %> <%= command.id %>',
17
+ '<%= config.bin %> <%= command.id %> --channel beta',
18
+ '<%= config.bin %> <%= command.id %> --json',
19
+ ];
20
+ static flags = {
21
+ channel: Flags.string({
22
+ description: 'Release channel to compare against. Defaults to the current CLI channel.',
23
+ options: ['auto', 'latest', 'beta', 'alpha'],
24
+ default: 'auto',
25
+ }),
26
+ json: Flags.boolean({
27
+ description: 'Output the result as JSON',
28
+ default: false,
29
+ }),
30
+ };
31
+ async run() {
32
+ const { flags } = await this.parse(SelfCheck);
33
+ const status = await inspectSelfStatus({
34
+ channel: flags.channel,
35
+ });
36
+ if (flags.json) {
37
+ this.log(JSON.stringify({
38
+ ok: true,
39
+ kind: 'self',
40
+ packageName: status.packageName,
41
+ currentVersion: status.currentVersion,
42
+ latestVersion: status.latestVersion,
43
+ channel: status.channel,
44
+ updateAvailable: status.updateAvailable,
45
+ installMethod: status.installMethod,
46
+ updatable: status.updatable,
47
+ updateBlockedReason: status.updateBlockedReason,
48
+ recommendedCommand: getRecommendedSelfUpdateCommand(status),
49
+ registryError: status.registryError,
50
+ }, null, 2));
51
+ return;
52
+ }
53
+ this.log(renderTable(['Field', 'Value'], [
54
+ ['Current version', status.currentVersion || 'unknown'],
55
+ ['Latest version', status.latestVersion || 'unknown'],
56
+ ['Channel', status.channel],
57
+ ['Install method', status.installMethod],
58
+ ['Auto-update', status.updatable ? 'supported' : 'not supported'],
59
+ ['Update available', status.updateAvailable ? 'yes' : 'no'],
60
+ ]));
61
+ if (status.updateAvailable && status.updatable) {
62
+ printInfo('Run `nb self update`.');
63
+ }
64
+ else if (status.updateAvailable && status.updateBlockedReason) {
65
+ printInfo(status.updateBlockedReason);
66
+ }
67
+ if (status.registryError) {
68
+ printInfo(`Version check warning: ${status.registryError}`);
69
+ }
70
+ }
71
+ }