@nocobase/cli 2.2.0-alpha.1 → 2.2.0-alpha.2

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 (50) hide show
  1. package/bin/early-locale.js +89 -0
  2. package/bin/node-version.js +35 -0
  3. package/bin/run.js +9 -0
  4. package/bin/windows-admin.js +60 -0
  5. package/dist/commands/app/destroy.js +4 -3
  6. package/dist/commands/app/restart.js +38 -0
  7. package/dist/commands/app/shared.js +49 -3
  8. package/dist/commands/app/start.js +92 -0
  9. package/dist/commands/app/upgrade.js +11 -0
  10. package/dist/commands/examples/prompts-stages.js +2 -2
  11. package/dist/commands/examples/prompts-test.js +2 -2
  12. package/dist/commands/init.js +22 -10
  13. package/dist/commands/install.js +135 -4
  14. package/dist/commands/license/activate.js +4 -1
  15. package/dist/commands/license/shared.js +24 -15
  16. package/dist/commands/self/check.js +1 -1
  17. package/dist/commands/self/update.js +4 -4
  18. package/dist/commands/skills/check.js +4 -5
  19. package/dist/commands/skills/install.js +18 -1
  20. package/dist/commands/skills/update.js +19 -4
  21. package/dist/commands/source/dev.js +9 -5
  22. package/dist/commands/source/download.js +67 -2
  23. package/dist/lib/api-command-compat.js +51 -8
  24. package/dist/lib/app-managed-resources.js +101 -1
  25. package/dist/lib/auth-store.js +34 -12
  26. package/dist/lib/cli-config.js +19 -0
  27. package/dist/lib/env-config.js +6 -0
  28. package/dist/lib/hook-script.js +160 -0
  29. package/dist/lib/prompt-web-ui.js +7 -11
  30. package/dist/lib/run-npm.js +4 -0
  31. package/dist/lib/self-manager.js +254 -46
  32. package/dist/lib/skills-manager.js +116 -23
  33. package/dist/lib/source-publish.js +2 -2
  34. package/dist/lib/startup-update.js +1 -1
  35. package/dist/locale/en-US.json +10 -4
  36. package/dist/locale/zh-CN.json +10 -4
  37. package/package.json +7 -2
  38. package/assets/env-proxy/nginx/app.conf.tpl +0 -23
  39. package/assets/env-proxy/nginx/nocobase.conf.tpl +0 -5
  40. package/assets/env-proxy/nginx/snippets/dist-location.conf +0 -5
  41. package/assets/env-proxy/nginx/snippets/gzip.conf +0 -17
  42. package/assets/env-proxy/nginx/snippets/log-format-http.conf +0 -13
  43. package/assets/env-proxy/nginx/snippets/maps-http.conf +0 -14
  44. package/assets/env-proxy/nginx/snippets/mime-types.conf +0 -98
  45. package/assets/env-proxy/nginx/snippets/proxy-location.conf +0 -17
  46. package/assets/env-proxy/nginx/snippets/spa-location.conf +0 -6
  47. package/assets/env-proxy/nginx/snippets/uploads-location.conf +0 -21
  48. package/scripts/build.mjs +0 -34
  49. package/scripts/clean.mjs +0 -9
  50. package/tsconfig.json +0 -19
@@ -15,7 +15,7 @@ import { stdin as stdinStream, stdout as stdoutStream } from 'node:process';
15
15
  import { getEnv, upsertEnv } from "../lib/auth-store.js";
16
16
  import { runPromptCatalog, } from "../lib/prompt-catalog.js";
17
17
  import { applyCliLocale, localeText, translateCli } from "../lib/cli-locale.js";
18
- import { resolveDefaultConfigScope } from '../lib/cli-home.js';
18
+ import { resolveConfiguredEnvPath, resolveDefaultConfigScope, resolveEnvRelativePath } from '../lib/cli-home.js';
19
19
  import { resolveDefaultApiHost, resolveDefaultUiHost } from '../lib/cli-config.js';
20
20
  import { areConfiguredPathsEquivalent, deriveConfiguredSourcePath, deriveConfiguredStoragePath, inferConfiguredAppPathFromLegacyConfig, } from '../lib/env-paths.js';
21
21
  import { formatMissingManagedAppEnvMessage } from '../lib/app-runtime.js';
@@ -23,7 +23,9 @@ import { runPromptCatalogWebUI } from "../lib/prompt-web-ui.js";
23
23
  import { validateApiBaseUrl, validateEnvKey } from "../lib/prompt-validators.js";
24
24
  import { installNocoBaseSkills, isNpmRegistryUnavailable } from '../lib/skills-manager.js';
25
25
  import { omitKeys, pickKeys } from "../lib/object-utils.js";
26
+ import { ENV_CONFIG_SCHEMA_VERSION } from '../lib/env-config.js';
26
27
  import { printInfo, printStage, printVerbose, printWarning } from '../lib/ui.js';
28
+ import { persistHookScript } from '../lib/hook-script.js';
27
29
  import Download from "./download.js";
28
30
  import EnvAdd from "./env/add.js";
29
31
  import Install, { defaultDbPortForDialect } from "./install.js";
@@ -246,16 +248,12 @@ function formatBrowserOpenError(error) {
246
248
  return String(error);
247
249
  }
248
250
  export default class Init extends Command {
249
- static summary = 'Set up NocoBase so coding agents can connect and work with it';
250
- static description = `Set up NocoBase for coding agents in the current workspace.
251
+ static summary = 'Set up or connect a NocoBase environment in the current workspace';
252
+ static description = `Set up NocoBase in the current workspace.
251
253
 
252
- \`nb init\` prepares a NocoBase environment that coding agents can use. It supports three setup paths:
254
+ \`nb init\` helps you install a new NocoBase app, take over managing one that already exists on this machine, or connect a remote NocoBase app and save it as a CLI env.
253
255
 
254
- - Install a new NocoBase app, then save it as a CLI env.
255
- - Take over managing an app that already exists on this machine by reusing its database.
256
- - Connect a remote NocoBase app and save it as a CLI env.
257
-
258
- It can also install NocoBase AI coding skills (\`nocobase/skills\`) so agents get the project-specific workflow guidance.
256
+ You can use the saved environment directly, or let a coding agent access it later. It can also install NocoBase AI coding skills (\`nocobase/skills\`) when you want agent-specific workflow guidance.
259
257
 
260
258
  If setup was interrupted earlier, use \`--resume\` with an existing env name to continue from the saved workspace config.
261
259
 
@@ -475,7 +473,7 @@ Prompt modes:
475
473
  default: false,
476
474
  }),
477
475
  'ui-host': Flags.string({
478
- description: 'Host for the local --ui setup server (default: 127.0.0.1)',
476
+ description: 'Browser-accessible host for the --ui setup page URL (default: 127.0.0.1)',
479
477
  }),
480
478
  'ui-port': Flags.integer({
481
479
  description: 'Port for the local --ui setup server; 0 lets the OS choose an available port',
@@ -1032,6 +1030,14 @@ Prompt modes:
1032
1030
  const skipDownload = results.skipDownload === true;
1033
1031
  const appKey = resolveManagedAppKey(results.appKey ?? existingEnv?.config.appKey);
1034
1032
  const timeZone = resolveManagedTimeZone(results.timeZone ?? existingEnv?.config.timezone);
1033
+ const hookScriptInput = String(flags['hook-script'] ?? '').trim();
1034
+ const hookAppPath = appPath || `./${envName}/`;
1035
+ const hookScript = hookScriptInput
1036
+ ? await persistHookScript({
1037
+ sourcePath: hookScriptInput,
1038
+ appPath: resolveConfiguredEnvPath(hookAppPath) ?? resolveEnvRelativePath(hookAppPath),
1039
+ })
1040
+ : String(results.hookScript ?? '').trim();
1035
1041
  const builtinDb = explicitDbHostFlag(flags)
1036
1042
  ? false
1037
1043
  : results.builtinDb === undefined
@@ -1040,6 +1046,7 @@ Prompt modes:
1040
1046
  results.appKey = appKey;
1041
1047
  results.timeZone = timeZone;
1042
1048
  await upsertEnv(envName, {
1049
+ schemaVersion: ENV_CONFIG_SCHEMA_VERSION,
1043
1050
  ...(source === 'docker'
1044
1051
  ? { kind: 'docker' }
1045
1052
  : source || appPath || appRootPath
@@ -1057,6 +1064,7 @@ Prompt modes:
1057
1064
  ...(dockerPlatform ? { dockerPlatform } : {}),
1058
1065
  ...(gitUrl ? { gitUrl } : {}),
1059
1066
  ...(npmRegistry ? { npmRegistry } : {}),
1067
+ ...(hookScript ? { hookScript } : {}),
1060
1068
  ...(appPath ? { appPath } : {}),
1061
1069
  ...(appRootPath && !areConfiguredPathsEquivalent(appRootPath, derivedAppRootPath) ? { appRootPath } : {}),
1062
1070
  ...(storagePath && !areConfiguredPathsEquivalent(storagePath, derivedStoragePath) ? { storagePath } : {}),
@@ -1132,6 +1140,10 @@ Prompt modes:
1132
1140
  if (flags['prepare-only']) {
1133
1141
  argv.push('--prepare-only');
1134
1142
  }
1143
+ const hookScript = String(results.hookScript ?? flags['hook-script'] ?? '').trim();
1144
+ if (hookScript) {
1145
+ argv.push('--hook-script', hookScript);
1146
+ }
1135
1147
  if (flags.verbose) {
1136
1148
  argv.push('--verbose');
1137
1149
  }
@@ -15,7 +15,7 @@ import { appendAppPublicPath } from '../lib/app-public-path.js';
15
15
  import { runPromptCatalog, } from "../lib/prompt-catalog.js";
16
16
  import { applyCliLocale, localeText, resolveCliLocale, translateCli } from "../lib/cli-locale.js";
17
17
  import { resolveConfiguredEnvPath, resolveDefaultConfigScope, resolveEnvRoot, resolveEnvRelativePath, } from '../lib/cli-home.js';
18
- import { defaultDockerContainerPrefix, defaultDockerNetworkName } from '../lib/app-runtime.js';
18
+ import { defaultDockerContainerPrefix, defaultDockerNetworkName, managedAppLifecycleEnvVars, } from '../lib/app-runtime.js';
19
19
  import { resolveDefaultApiHost, resolveDockerContainerPrefix, resolveDockerNetworkName } from '../lib/cli-config.js';
20
20
  import { DEFAULT_DOCKER_VERSION, resolveDockerImageRef } from "../lib/docker-image.js";
21
21
  import { findAvailableTcpPort, validateAppPublicPath, validateAvailableTcpPort, validateTcpPort, validateEnvKey, } from "../lib/prompt-validators.js";
@@ -29,6 +29,7 @@ import { buildStoredEnvConfig } from '../lib/env-config.js';
29
29
  import { resolveDockerEnvFileArg } from "../lib/docker-env-file.js";
30
30
  import { startDockerLogFollower } from '../lib/docker-log-stream.js';
31
31
  import { buildInitAppEnvVarsFromConfig } from '../lib/managed-init-env.js';
32
+ import { buildHookContext, persistHookScript, resolveHookScriptPath, runHookScriptHook, } from '../lib/hook-script.js';
32
33
  import { areConfiguredPathsEquivalent, deriveConfiguredSourcePath, deriveConfiguredStoragePath, inferConfiguredAppPathFromLegacyConfig, } from '../lib/env-paths.js';
33
34
  import Download, { defaultDockerRegistryForLang } from './download.js';
34
35
  import EnvAdd from "./env/add.js";
@@ -472,7 +473,10 @@ export default class Install extends Command {
472
473
  description: 'Skip the download step and reuse an existing local app directory or Docker image',
473
474
  default: false,
474
475
  }),
475
- ...omitKeys(Download.flags, ['yes']),
476
+ 'hook-script': Flags.string({
477
+ description: 'Hook module copied to <appPath>/.nb/hooks.mjs and run before npm/git dependency installation.',
478
+ }),
479
+ ...omitKeys(Download.flags, ['yes', 'hook-script']),
476
480
  };
477
481
  /** Environment name only: run before {@link Install.prompts} (see `run`). */
478
482
  static envPrompts = {
@@ -1095,6 +1099,7 @@ export default class Install extends Command {
1095
1099
  const appPort = Install.toOptionalPromptString(config.appPort);
1096
1100
  const storagePath = Install.toOptionalPromptString(config.storagePath);
1097
1101
  const appPublicPath = Install.toOptionalPromptString(config.appPublicPath);
1102
+ const hookScript = Install.toOptionalPromptString(config.hookScript);
1098
1103
  const apiBaseUrl = Install.toOptionalPromptString(config.apiBaseUrl);
1099
1104
  const downloadVersion = Install.toOptionalPromptString(config.downloadVersion);
1100
1105
  const dockerRegistry = Install.toOptionalPromptString(config.dockerRegistry);
@@ -1125,6 +1130,7 @@ export default class Install extends Command {
1125
1130
  ...(appPort ? { appPort } : {}),
1126
1131
  ...(storagePath ? { storagePath } : {}),
1127
1132
  ...(appPublicPath ? { appPublicPath } : {}),
1133
+ ...(hookScript ? { hookScript } : {}),
1128
1134
  };
1129
1135
  const downloadPreset = {
1130
1136
  ...(source ? { source } : {}),
@@ -1916,6 +1922,12 @@ export default class Install extends Command {
1916
1922
  if (results.buildDts) {
1917
1923
  argv.push('--build-dts');
1918
1924
  }
1925
+ Install.pushDownloadArgIfValue(argv, '--hook-script', results.hookScript);
1926
+ Install.pushDownloadArgIfValue(argv, '--hook-phase', results.hookPhase);
1927
+ Install.pushDownloadArgIfValue(argv, '--hook-command', results.hookCommand);
1928
+ Install.pushDownloadArgIfValue(argv, '--hook-env-name', results.hookEnvName);
1929
+ Install.pushDownloadArgIfValue(argv, '--hook-app-path', results.hookAppPath);
1930
+ Install.pushDownloadArgIfValue(argv, '--hook-storage-path', results.hookStoragePath);
1919
1931
  return argv;
1920
1932
  }
1921
1933
  static resolveLocalProjectRoot(params) {
@@ -1942,6 +1954,40 @@ export default class Install extends Command {
1942
1954
  buildDts: false,
1943
1955
  };
1944
1956
  }
1957
+ static resolveAbsoluteAppPath(envName, appResults) {
1958
+ const configuredAppPath = resolveConfiguredAppPathValue(appResults) ?? defaultInstallAppPath(envName);
1959
+ return resolveConfiguredEnvPath(configuredAppPath) ?? resolveEnvRelativePath(defaultInstallAppPath(envName));
1960
+ }
1961
+ static resolveAbsoluteStoragePath(envName, appResults) {
1962
+ const configuredStoragePath = resolveConfiguredStoragePathValue(appResults, envName);
1963
+ return (resolveConfiguredEnvPath(configuredStoragePath) ?? resolveEnvRelativePath(defaultInstallStoragePath(envName)));
1964
+ }
1965
+ async prepareHookScriptForInstall(params) {
1966
+ const appPath = Install.resolveAbsoluteAppPath(params.envName, params.appResults);
1967
+ const storagePath = Install.resolveAbsoluteStoragePath(params.envName, params.appResults);
1968
+ const inputHookScript = Install.toOptionalPromptString(params.hookScript);
1969
+ if (inputHookScript) {
1970
+ params.appResults.hookScript = await persistHookScript({
1971
+ sourcePath: inputHookScript,
1972
+ appPath,
1973
+ });
1974
+ printInfo(`Saved hook script to ${path.join(appPath, String(params.appResults.hookScript))}.`);
1975
+ }
1976
+ const savedHookScript = Install.toOptionalPromptString(params.appResults.hookScript);
1977
+ const hookScriptPath = resolveHookScriptPath({
1978
+ appPath,
1979
+ hookScript: savedHookScript,
1980
+ });
1981
+ if (!hookScriptPath) {
1982
+ return;
1983
+ }
1984
+ params.downloadResults.hookScript = hookScriptPath;
1985
+ params.downloadResults.hookPhase = 'init';
1986
+ params.downloadResults.hookCommand = 'init';
1987
+ params.downloadResults.hookEnvName = params.envName;
1988
+ params.downloadResults.hookAppPath = appPath;
1989
+ params.downloadResults.hookStoragePath = storagePath;
1990
+ }
1945
1991
  commandStdio(verbose) {
1946
1992
  return verbose ? 'inherit' : 'ignore';
1947
1993
  }
@@ -2011,7 +2057,9 @@ export default class Install extends Command {
2011
2057
  const dbDialect = String(params.dbResults.dbDialect ?? 'postgres').trim() || 'postgres';
2012
2058
  const appKey = Install.resolveManagedAppKey(params.appResults.appKey);
2013
2059
  const timeZone = Install.resolveManagedTimeZone(params.appResults.timeZone);
2060
+ const lifecycleEnvVars = managedAppLifecycleEnvVars();
2014
2061
  const env = {
2062
+ ...lifecycleEnvVars,
2015
2063
  STORAGE_PATH: storagePath,
2016
2064
  APP_PORT: String(params.appResults.appPort ?? DEFAULT_INSTALL_APP_PORT).trim() || DEFAULT_INSTALL_APP_PORT,
2017
2065
  APP_KEY: appKey,
@@ -2216,6 +2264,44 @@ export default class Install extends Command {
2216
2264
  }
2217
2265
  await this.config.runCommand('env:update', [params.envName]);
2218
2266
  }
2267
+ async runInstallHookIfNeeded(params) {
2268
+ const appPath = Install.resolveAbsoluteAppPath(params.envName, params.appResults);
2269
+ const savedHookScript = Install.toOptionalPromptString(params.appResults.hookScript);
2270
+ const hookScriptPath = resolveHookScriptPath({
2271
+ appPath,
2272
+ hookScript: savedHookScript,
2273
+ });
2274
+ if (!hookScriptPath) {
2275
+ return;
2276
+ }
2277
+ const storagePath = Install.resolveAbsoluteStoragePath(params.envName, params.appResults);
2278
+ const sourcePath = params.projectRoot ??
2279
+ resolveConfiguredEnvPath(resolveConfiguredSourcePathValue(params.appResults, params.envName)) ??
2280
+ path.join(appPath, 'source');
2281
+ const context = buildHookContext({
2282
+ phase: 'init',
2283
+ command: 'init',
2284
+ envName: params.envName,
2285
+ source: params.source,
2286
+ version: downloadResultsValue(params.downloadResults, 'version'),
2287
+ appPath,
2288
+ sourcePath,
2289
+ storagePath,
2290
+ hookScript: savedHookScript,
2291
+ envConfig: Install.buildSavedEnvConfig(params, {
2292
+ defaultApiHost: params.defaultApiHost,
2293
+ }),
2294
+ });
2295
+ if (!context) {
2296
+ return;
2297
+ }
2298
+ this.log(`Running hook ${params.hookName}: ${hookScriptPath}`);
2299
+ await runHookScriptHook({
2300
+ hookScriptPath,
2301
+ hookName: params.hookName,
2302
+ context,
2303
+ });
2304
+ }
2219
2305
  static buildSavedEnvConfig(params, options = {}) {
2220
2306
  const appPath = resolveConfiguredAppPathValue(params.appResults);
2221
2307
  const appRootPath = Install.toOptionalPromptString(params.appResults.appRootPath);
@@ -2247,6 +2333,7 @@ export default class Install extends Command {
2247
2333
  devDependencies: downloadResultsValue(params.downloadResults, 'devDependencies'),
2248
2334
  build: downloadResultsValue(params.downloadResults, 'build'),
2249
2335
  buildDts: downloadResultsValue(params.downloadResults, 'buildDts'),
2336
+ hookScript: params.appResults.hookScript,
2250
2337
  ...(appPath ? { appPath } : {}),
2251
2338
  ...(appRootPath && !areConfiguredPathsEquivalent(appRootPath, derivedAppRootPath) ? { appRootPath } : {}),
2252
2339
  appPort,
@@ -2311,6 +2398,9 @@ export default class Install extends Command {
2311
2398
  yesInitialValues: { resume: parsed.resume },
2312
2399
  yes,
2313
2400
  });
2401
+ if (resumePreset?.appPreset?.hookScript !== undefined && appResults.hookScript === undefined) {
2402
+ appResults.hookScript = resumePreset.appPreset.hookScript;
2403
+ }
2314
2404
  const downloadOpts = Install.buildDownloadPromptOptionsForInstall(appResults, envName);
2315
2405
  downloadOpts.values = {
2316
2406
  ...(resumePreset?.downloadPreset ?? {}),
@@ -2440,6 +2530,12 @@ export default class Install extends Command {
2440
2530
  appResults,
2441
2531
  });
2442
2532
  appResults.setupState = 'prepared';
2533
+ await this.prepareHookScriptForInstall({
2534
+ envName,
2535
+ appResults,
2536
+ downloadResults,
2537
+ hookScript: parsed['hook-script'],
2538
+ });
2443
2539
  const source = String(downloadResultsValue(downloadResults, 'source') ?? '').trim();
2444
2540
  const usesDockerResources = Boolean(dbResults.builtinDb) || source === 'docker';
2445
2541
  const dockerNetworkName = usesDockerResources
@@ -2505,6 +2601,17 @@ export default class Install extends Command {
2505
2601
  printInfo('Application image ready.');
2506
2602
  }
2507
2603
  if (!parsed['prepare-only']) {
2604
+ await this.runInstallHookIfNeeded({
2605
+ hookName: 'beforeAppInstall',
2606
+ envName,
2607
+ source,
2608
+ appResults,
2609
+ downloadResults,
2610
+ dbResults,
2611
+ rootResults,
2612
+ envAddResults,
2613
+ defaultApiHost,
2614
+ });
2508
2615
  dockerAppPlan = await this.installDockerApp({
2509
2616
  envName,
2510
2617
  dockerNetworkName,
@@ -2523,7 +2630,7 @@ export default class Install extends Command {
2523
2630
  }
2524
2631
  else if (source === 'npm' || source === 'git') {
2525
2632
  const localSource = source === 'npm' ? 'npm' : 'git';
2526
- const projectRoot = parsed['skip-download']
2633
+ const projectRoot = parsed['skip-download'] || parsed['prepare-only']
2527
2634
  ? Install.resolveLocalProjectRoot({
2528
2635
  envName,
2529
2636
  appResults,
@@ -2535,10 +2642,22 @@ export default class Install extends Command {
2535
2642
  downloadResults,
2536
2643
  verbose: parsed.verbose,
2537
2644
  });
2538
- if (!parsed['skip-download']) {
2645
+ if (!parsed['skip-download'] && !parsed['prepare-only']) {
2539
2646
  printInfo('Application files ready.');
2540
2647
  }
2541
2648
  if (!parsed['prepare-only']) {
2649
+ await this.runInstallHookIfNeeded({
2650
+ hookName: 'beforeAppInstall',
2651
+ envName,
2652
+ source,
2653
+ appResults,
2654
+ downloadResults,
2655
+ dbResults,
2656
+ rootResults,
2657
+ envAddResults,
2658
+ projectRoot,
2659
+ defaultApiHost,
2660
+ });
2542
2661
  localAppPlan = await this.startLocalApp({
2543
2662
  envName,
2544
2663
  source: localSource,
@@ -2573,6 +2692,18 @@ export default class Install extends Command {
2573
2692
  });
2574
2693
  printInfo(`NocoBase is ready at ${formatInstallDisplayUrl(displayApiBaseUrl)}`);
2575
2694
  appResults.setupState = 'installed';
2695
+ await this.runInstallHookIfNeeded({
2696
+ hookName: 'afterAppStart',
2697
+ envName,
2698
+ source,
2699
+ appResults,
2700
+ downloadResults,
2701
+ dbResults,
2702
+ rootResults,
2703
+ envAddResults,
2704
+ projectRoot: localAppPlan?.projectRoot,
2705
+ defaultApiHost,
2706
+ });
2576
2707
  }
2577
2708
  if (dockerAppPlan || localAppPlan || builtinDbPlan) {
2578
2709
  await this.saveInstalledEnv({
@@ -143,11 +143,14 @@ export default class LicenseActivate extends Command {
143
143
  const validation = await validateLicenseKey(runtime, resolvedKey);
144
144
  const ok = !validation.keyStatus && validation.envMatch && validation.domainMatch && validation.licenseStatus === 'active';
145
145
  const licenseKeyPath = ok ? await saveLicenseKey(runtime, resolvedKey) : resolveLicenseKeyFile(runtime);
146
+ const shouldResolveInstanceId = Boolean(interactiveKeyFlowInstanceId || ok || (flags.json && !validation.keyStatus));
146
147
  const payload = {
147
148
  ok,
148
149
  env: runtime.envName,
149
150
  kind: runtime.kind,
150
- instanceId: interactiveKeyFlowInstanceId ?? (await ensureInstanceId(runtime)),
151
+ instanceId: shouldResolveInstanceId
152
+ ? interactiveKeyFlowInstanceId ?? (await ensureInstanceId(runtime))
153
+ : undefined,
151
154
  mode: 'key',
152
155
  key: redactLicenseKey(resolvedKey),
153
156
  keyFile: keyFile || undefined,
@@ -10,8 +10,8 @@ import { Flags } from '@oclif/core';
10
10
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
11
11
  import path from 'node:path';
12
12
  import { getEnvAsync, getInstanceIdAsync, keyDecrypt } from '@nocobase/license-kit';
13
- import { checkExternalDbConnection, readExternalDbConnectionConfig, } from "../../lib/db-connection-check.js";
14
- import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_VERSION, resolveDockerImageRef, } from "../../lib/docker-image.js";
13
+ import { checkExternalDbConnection, readExternalDbConnectionConfig } from "../../lib/db-connection-check.js";
14
+ import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_VERSION, resolveDockerImageRef } from "../../lib/docker-image.js";
15
15
  import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime } from '../../lib/app-runtime.js';
16
16
  import { buildRuntimeEnvVars } from '../../lib/runtime-env-vars.js';
17
17
  import { resolveLicensePkgUrlFromConfig } from '../../lib/cli-config.js';
@@ -102,12 +102,7 @@ function buildDockerLicenseDbFlagArgs(envVars) {
102
102
  ];
103
103
  }
104
104
  async function runDockerLicenseJsonCommand(runtime, commandArgs) {
105
- const args = [
106
- 'run',
107
- '--rm',
108
- '--network',
109
- runtime.dockerNetworkName || runtime.workspaceName,
110
- ];
105
+ const args = ['run', '--rm', '--network', runtime.dockerNetworkName || runtime.workspaceName];
111
106
  const dockerPlatform = normalizeDockerPlatform(runtime.env.config?.dockerPlatform);
112
107
  if (dockerPlatform) {
113
108
  args.push('--platform', dockerPlatform);
@@ -193,11 +188,11 @@ export async function generateValidatedInstanceIdFromEnvVars(envVars) {
193
188
  }
194
189
  async function generateInstanceIdForDockerRuntime(runtime) {
195
190
  const envVars = await buildRuntimeEnvVars(runtime);
196
- const payload = await runDockerLicenseJsonCommand(runtime, [
191
+ const payload = (await runDockerLicenseJsonCommand(runtime, [
197
192
  'license',
198
193
  'generate-id',
199
194
  ...buildDockerLicenseDbFlagArgs(envVars),
200
- ]);
195
+ ]));
201
196
  const instanceId = trimValue(payload.instanceId);
202
197
  if (!instanceId) {
203
198
  throw new Error('Docker instance ID generation did not return an instance ID.');
@@ -337,7 +332,7 @@ export async function getLicenseStatus(keyData) {
337
332
  }),
338
333
  signal: controller.signal,
339
334
  });
340
- const payload = await response.json();
335
+ const payload = (await response.json());
341
336
  return payload?.data?.status === 'active' ? 'active' : 'invalid';
342
337
  }
343
338
  catch {
@@ -356,6 +351,22 @@ export async function validateLicenseKey(runtime, key) {
356
351
  catch {
357
352
  keyStatus = 'invalid';
358
353
  }
354
+ if (keyStatus) {
355
+ const currentDomain = appUrl(runtime);
356
+ return {
357
+ current: {
358
+ env: undefined,
359
+ domain: currentDomain ? new URL(currentDomain).host : '',
360
+ },
361
+ keyData,
362
+ keyStatus,
363
+ dbMatch: false,
364
+ sysMatch: false,
365
+ envMatch: false,
366
+ domainMatch: false,
367
+ licenseStatus: 'invalid',
368
+ };
369
+ }
359
370
  const currentEnv = await getCurrentLicenseEnv(runtime);
360
371
  const currentDomain = appUrl(runtime);
361
372
  const dbMatch = isDbMatch(currentEnv, keyData);
@@ -391,7 +402,7 @@ export async function resolveLicenseServiceUrl(value) {
391
402
  return (await resolveLicensePkgUrl(value)).replace(/\/+$/, '');
392
403
  }
393
404
  export async function resolveLicensePkgUrl(value) {
394
- const normalized = String(value ?? '').trim() || await resolveLicensePkgUrlFromConfig();
405
+ const normalized = String(value ?? '').trim() || (await resolveLicensePkgUrlFromConfig());
395
406
  return normalized.replace(/\/+$/, '') + '/';
396
407
  }
397
408
  function shouldRedactOutputKey(key) {
@@ -414,9 +425,7 @@ export function sanitizeLicenseOutput(value) {
414
425
  if (value && typeof value === 'object') {
415
426
  return Object.fromEntries(Object.entries(value).map(([key, nestedValue]) => [
416
427
  key,
417
- shouldRedactOutputKey(key)
418
- ? redactOutputValue(String(nestedValue ?? ''))
419
- : sanitizeLicenseOutput(nestedValue),
428
+ shouldRedactOutputKey(key) ? redactOutputValue(String(nestedValue ?? '')) : sanitizeLicenseOutput(nestedValue),
420
429
  ]));
421
430
  }
422
431
  return value;
@@ -20,7 +20,7 @@ export default class SelfCheck extends Command {
20
20
  static flags = {
21
21
  channel: Flags.string({
22
22
  description: 'Release channel to compare against. Defaults to the current CLI channel.',
23
- options: ['auto', 'latest', 'beta', 'alpha'],
23
+ options: ['auto', 'latest', 'test', 'beta', 'alpha'],
24
24
  default: 'auto',
25
25
  }),
26
26
  json: Flags.boolean({
@@ -26,17 +26,17 @@ function formatSkillsUpdateMessage(result, verbose) {
26
26
  }
27
27
  export default class SelfUpdate extends Command {
28
28
  static summary = 'Update the globally installed NocoBase CLI';
29
- static description = 'Update the current NocoBase CLI install when it is managed by a standard global npm install.';
29
+ static description = 'Update the current NocoBase CLI install when it is managed by a standard global npm, pnpm, or yarn install.';
30
30
  static examples = [
31
31
  '<%= config.bin %> <%= command.id %>',
32
32
  '<%= config.bin %> <%= command.id %> --yes',
33
33
  '<%= config.bin %> <%= command.id %> --skills',
34
- '<%= config.bin %> <%= command.id %> --channel alpha --json',
34
+ '<%= config.bin %> <%= command.id %> --channel test --json',
35
35
  ];
36
36
  static flags = {
37
37
  channel: Flags.string({
38
38
  description: 'Release channel to update to. Defaults to the current CLI channel.',
39
- options: ['auto', 'latest', 'beta', 'alpha'],
39
+ options: ['auto', 'latest', 'test', 'beta', 'alpha'],
40
40
  default: 'auto',
41
41
  }),
42
42
  yes: Flags.boolean({
@@ -77,7 +77,7 @@ export default class SelfUpdate extends Command {
77
77
  message: flags.skills
78
78
  ? `Update ${status.packageName} from ${status.currentVersion} to ${status.latestVersion} and refresh the globally installed NocoBase AI coding skills?`
79
79
  : `Update ${status.packageName} from ${status.currentVersion} to ${status.latestVersion}?`,
80
- default: false,
80
+ default: true,
81
81
  });
82
82
  }
83
83
  catch {
@@ -12,10 +12,7 @@ import { printInfo, renderTable } from '../../lib/ui.js';
12
12
  export default class SkillsCheck extends Command {
13
13
  static summary = 'Check the globally installed NocoBase AI coding skills';
14
14
  static description = 'Inspect the global NocoBase AI coding skills and report whether they are managed by the CLI and whether an update is available.';
15
- static examples = [
16
- '<%= config.bin %> <%= command.id %>',
17
- '<%= config.bin %> <%= command.id %> --json',
18
- ];
15
+ static examples = ['<%= config.bin %> <%= command.id %>', '<%= config.bin %> <%= command.id %> --json'];
19
16
  static flags = {
20
17
  json: Flags.boolean({
21
18
  description: 'Output the result as JSON',
@@ -25,6 +22,7 @@ export default class SkillsCheck extends Command {
25
22
  async run() {
26
23
  const { flags } = await this.parse(SkillsCheck);
27
24
  const status = await inspectSkillsStatus();
25
+ const displaySkillNames = status.packageSkillNames.length ? status.packageSkillNames : status.installedSkillNames;
28
26
  if (flags.json) {
29
27
  this.log(JSON.stringify({
30
28
  ok: true,
@@ -35,6 +33,7 @@ export default class SkillsCheck extends Command {
35
33
  managedByNb: status.managedByNb,
36
34
  sourcePackage: status.sourcePackage,
37
35
  npmPackageName: status.npmPackageName,
36
+ packageSkillNames: status.packageSkillNames,
38
37
  installedSkillNames: status.installedSkillNames,
39
38
  installedVersion: status.installedVersion,
40
39
  latestVersion: status.latestVersion,
@@ -50,7 +49,7 @@ export default class SkillsCheck extends Command {
50
49
  ['Skills home', status.globalRoot],
51
50
  ['Installed', status.installed ? 'yes' : 'no'],
52
51
  ['Managed by nb', status.managedByNb ? 'yes' : 'no'],
53
- ['Installed skills', status.installedSkillNames.length ? status.installedSkillNames.join(', ') : '(none)'],
52
+ ['Installed skills', displaySkillNames.length ? displaySkillNames.join(', ') : '(none)'],
54
53
  ['Installed version', status.installedVersion ?? '(unknown)'],
55
54
  ['Latest version', status.latestVersion ?? '(unknown)'],
56
55
  ['Update available', status.updateAvailable === null ? 'unknown' : status.updateAvailable ? 'yes' : 'no'],
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { Command, Flags } from '@oclif/core';
10
10
  import { confirm } from "../../lib/inquirer.js";
11
- import { setVerboseMode } from '../../lib/ui.js';
11
+ import { setVerboseMode, startTask, stopTask, updateTask } from '../../lib/ui.js';
12
12
  import { installNocoBaseSkills } from '../../lib/skills-manager.js';
13
13
  export default class SkillsInstall extends Command {
14
14
  static summary = 'Install the NocoBase AI coding skills globally';
@@ -16,6 +16,8 @@ export default class SkillsInstall extends Command {
16
16
  static examples = [
17
17
  '<%= config.bin %> <%= command.id %>',
18
18
  '<%= config.bin %> <%= command.id %> --yes',
19
+ '<%= config.bin %> <%= command.id %> --version 1.0.4',
20
+ '<%= config.bin %> <%= command.id %> --verbose',
19
21
  '<%= config.bin %> <%= command.id %> --json',
20
22
  ];
21
23
  static flags = {
@@ -32,6 +34,9 @@ export default class SkillsInstall extends Command {
32
34
  description: 'Show detailed install output',
33
35
  default: false,
34
36
  }),
37
+ version: Flags.string({
38
+ description: 'Install a specific @nocobase/skills version',
39
+ }),
35
40
  };
36
41
  async run() {
37
42
  const { flags } = await this.parse(SkillsInstall);
@@ -51,8 +56,20 @@ export default class SkillsInstall extends Command {
51
56
  return;
52
57
  }
53
58
  }
59
+ const shouldShowLoading = !flags.json && !flags.verbose;
60
+ if (shouldShowLoading) {
61
+ startTask(flags.version
62
+ ? `Installing NocoBase AI coding skills ${flags.version}...`
63
+ : 'Installing NocoBase AI coding skills...');
64
+ }
54
65
  const result = await installNocoBaseSkills({
66
+ targetVersion: flags.version,
55
67
  verbose: flags.verbose,
68
+ onProgress: shouldShowLoading ? updateTask : undefined,
69
+ }).finally(() => {
70
+ if (shouldShowLoading) {
71
+ stopTask();
72
+ }
56
73
  });
57
74
  if (flags.json) {
58
75
  this.log(JSON.stringify({
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { Command, Flags } from '@oclif/core';
10
10
  import { confirm } from "../../lib/inquirer.js";
11
- import { setVerboseMode } from '../../lib/ui.js';
11
+ import { setVerboseMode, startTask, stopTask, updateTask } from '../../lib/ui.js';
12
12
  import { updateNocoBaseSkills } from '../../lib/skills-manager.js';
13
13
  export default class SkillsUpdate extends Command {
14
14
  static summary = 'Update the globally installed NocoBase AI coding skills';
@@ -16,6 +16,8 @@ export default class SkillsUpdate extends Command {
16
16
  static examples = [
17
17
  '<%= config.bin %> <%= command.id %>',
18
18
  '<%= config.bin %> <%= command.id %> --yes',
19
+ '<%= config.bin %> <%= command.id %> --version 1.0.4',
20
+ '<%= config.bin %> <%= command.id %> --verbose',
19
21
  '<%= config.bin %> <%= command.id %> --json',
20
22
  ];
21
23
  static flags = {
@@ -32,6 +34,9 @@ export default class SkillsUpdate extends Command {
32
34
  description: 'Show detailed update output',
33
35
  default: false,
34
36
  }),
37
+ version: Flags.string({
38
+ description: 'Sync to a specific @nocobase/skills version',
39
+ }),
35
40
  };
36
41
  async run() {
37
42
  const { flags } = await this.parse(SkillsUpdate);
@@ -51,8 +56,20 @@ export default class SkillsUpdate extends Command {
51
56
  return;
52
57
  }
53
58
  }
59
+ const shouldShowLoading = !flags.json && !flags.verbose;
60
+ if (shouldShowLoading) {
61
+ startTask(flags.version
62
+ ? `Syncing NocoBase AI coding skills to ${flags.version}...`
63
+ : 'Updating NocoBase AI coding skills...');
64
+ }
54
65
  const result = await updateNocoBaseSkills({
66
+ targetVersion: flags.version,
55
67
  verbose: flags.verbose,
68
+ onProgress: shouldShowLoading ? updateTask : undefined,
69
+ }).finally(() => {
70
+ if (shouldShowLoading) {
71
+ stopTask();
72
+ }
56
73
  });
57
74
  if (flags.json) {
58
75
  this.log(JSON.stringify({
@@ -80,8 +97,6 @@ export default class SkillsUpdate extends Command {
80
97
  : 'NocoBase AI coding skills are up to date.');
81
98
  return;
82
99
  }
83
- this.log(flags.verbose
84
- ? 'Updated the global NocoBase AI coding skills.'
85
- : 'Updated NocoBase AI coding skills globally.');
100
+ this.log(flags.verbose ? 'Updated the global NocoBase AI coding skills.' : 'Updated NocoBase AI coding skills globally.');
86
101
  }
87
102
  }