@nocobase/cli 2.1.0-alpha.45 → 2.1.0-alpha.47

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 (87) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +63 -380
  3. package/assets/env-proxy/nginx/app.conf.tpl +23 -0
  4. package/assets/env-proxy/nginx/nocobase.conf.tpl +5 -0
  5. package/assets/env-proxy/nginx/snippets/dist-location.conf +5 -0
  6. package/assets/env-proxy/nginx/snippets/gzip.conf +17 -0
  7. package/assets/env-proxy/nginx/snippets/log-format-http.conf +13 -0
  8. package/assets/env-proxy/nginx/snippets/maps-http.conf +14 -0
  9. package/assets/env-proxy/nginx/snippets/mime-types.conf +98 -0
  10. package/assets/env-proxy/nginx/snippets/proxy-location.conf +17 -0
  11. package/assets/env-proxy/nginx/snippets/spa-location.conf +6 -0
  12. package/assets/env-proxy/nginx/snippets/uploads-location.conf +21 -0
  13. package/bin/run.js +40 -32
  14. package/dist/commands/app/autostart/disable.js +55 -0
  15. package/dist/commands/app/autostart/enable.js +55 -0
  16. package/dist/commands/app/autostart/list.js +37 -0
  17. package/dist/commands/app/autostart/run.js +84 -0
  18. package/dist/commands/app/autostart/shared.js +49 -0
  19. package/dist/commands/app/destroy.js +21 -12
  20. package/dist/commands/app/down.js +2 -2
  21. package/dist/commands/app/logs.js +2 -1
  22. package/dist/commands/app/restart.js +92 -20
  23. package/dist/commands/app/shared.js +3 -2
  24. package/dist/commands/app/start.js +143 -38
  25. package/dist/commands/app/stop.js +31 -2
  26. package/dist/commands/app/upgrade.js +11 -7
  27. package/dist/commands/config/delete.js +10 -2
  28. package/dist/commands/config/get.js +7 -1
  29. package/dist/commands/config/set.js +12 -2
  30. package/dist/commands/db/ps.js +5 -18
  31. package/dist/commands/env/add.js +24 -44
  32. package/dist/commands/env/auth.js +1 -1
  33. package/dist/commands/env/info.js +3 -2
  34. package/dist/commands/env/list.js +4 -5
  35. package/dist/commands/env/proxy/caddy.js +28 -0
  36. package/dist/commands/env/proxy/index.js +353 -0
  37. package/dist/commands/env/proxy/nginx.js +28 -0
  38. package/dist/commands/env/remove.js +112 -22
  39. package/dist/commands/env/shared.js +17 -9
  40. package/dist/commands/env/status.js +10 -7
  41. package/dist/commands/env/update.js +385 -21
  42. package/dist/commands/init.js +368 -98
  43. package/dist/commands/install.js +271 -104
  44. package/dist/commands/license/activate.js +63 -244
  45. package/dist/commands/license/plugins/shared.js +64 -13
  46. package/dist/commands/plugin/import.js +108 -0
  47. package/dist/commands/revision/create.js +89 -0
  48. package/dist/commands/self/update.js +58 -1
  49. package/dist/commands/source/dev.js +5 -5
  50. package/dist/commands/source/test.js +13 -14
  51. package/dist/lib/app-health.js +39 -32
  52. package/dist/lib/app-managed-resources.js +25 -9
  53. package/dist/lib/app-public-path.js +80 -0
  54. package/dist/lib/app-runtime.js +12 -3
  55. package/dist/lib/auth-store.js +128 -4
  56. package/dist/lib/backup.js +3 -3
  57. package/dist/lib/bootstrap.js +14 -8
  58. package/dist/lib/cli-config.js +229 -3
  59. package/dist/lib/cli-entry-error.js +52 -0
  60. package/dist/lib/command-log.js +284 -0
  61. package/dist/lib/docker-env-file.js +10 -2
  62. package/dist/lib/docker-log-stream.js +45 -0
  63. package/dist/lib/env-auth.js +25 -14
  64. package/dist/lib/env-command-config.js +45 -0
  65. package/dist/lib/env-config.js +7 -2
  66. package/dist/lib/env-guard.js +9 -10
  67. package/dist/lib/env-paths.js +101 -0
  68. package/dist/lib/env-proxy.js +1295 -0
  69. package/dist/lib/inquirer.js +4 -5
  70. package/dist/lib/managed-env-file.js +98 -0
  71. package/dist/lib/plugin-import.js +279 -0
  72. package/dist/lib/prompt-catalog-core.js +3 -2
  73. package/dist/lib/prompt-validators.js +14 -0
  74. package/dist/lib/prompt-web-ui.js +8 -1
  75. package/dist/lib/run-npm.js +56 -12
  76. package/dist/lib/skills-manager.js +3 -1
  77. package/dist/lib/source-publish.js +22 -2
  78. package/dist/lib/startup-update.js +74 -50
  79. package/dist/lib/ui.js +2 -1
  80. package/dist/locale/en-US.json +122 -22
  81. package/dist/locale/zh-CN.json +119 -19
  82. package/package.json +5 -7
  83. package/scripts/build.mjs +34 -0
  84. package/scripts/clean.mjs +9 -0
  85. package/tsconfig.json +19 -0
  86. package/LICENSE.txt +0 -107
  87. package/README.zh-CN.md +0 -355
@@ -8,8 +8,10 @@
8
8
  */
9
9
  import { promises as fs } from 'node:fs';
10
10
  import path from 'node:path';
11
+ import { resolveAppPublicPath } from './app-public-path.js';
11
12
  import { resolveCliHomeDir, resolveConfiguredEnvPath, resolveEnvRelativePath } from './cli-home.js';
12
13
  import { normalizeCliLocale } from './cli-locale.js';
14
+ import { inferConfiguredAppPathFromLegacyConfig, resolveConfiguredAppPath, resolveConfiguredSourcePath, resolveConfiguredStoragePath, } from './env-paths.js';
13
15
  import { cleanupCurrentSessionAfterEnvRemoval, resolveEffectiveCurrentEnv, setSessionCurrentEnv, } from './session-store.js';
14
16
  function normalizeStoredEnvKind(value) {
15
17
  const kind = String(value ?? '').trim();
@@ -32,6 +34,13 @@ function normalizeOptionalCliLocale(value) {
32
34
  }
33
35
  return normalizeCliLocale(normalized);
34
36
  }
37
+ function normalizeOptionalCliUpdatePolicy(value) {
38
+ const normalized = normalizeOptionalString(value);
39
+ if (normalized === 'prompt' || normalized === 'auto' || normalized === 'off') {
40
+ return normalized;
41
+ }
42
+ return undefined;
43
+ }
35
44
  export function readEnvApiBaseUrl(config) {
36
45
  if (!config) {
37
46
  return undefined;
@@ -55,7 +64,7 @@ export function resolveEnvKind(config) {
55
64
  if (source === 'npm' || source === 'git' || source === 'local') {
56
65
  return 'local';
57
66
  }
58
- if (String(config.appRootPath ?? '').trim()) {
67
+ if (String(config.appPath ?? '').trim() || String(config.appRootPath ?? '').trim()) {
59
68
  return 'local';
60
69
  }
61
70
  if (readEnvApiBaseUrl(config) || config.auth) {
@@ -74,15 +83,32 @@ function normalizeEnvConfigEntry(entry) {
74
83
  ...rest,
75
84
  ...(normalizedKind ? { kind: normalizedKind } : {}),
76
85
  ...(apiBaseUrl !== undefined ? { apiBaseUrl } : {}),
86
+ ...(normalizeOptionalString(entry.appPublicPath) ? { appPublicPath: resolveAppPublicPath(entry.appPublicPath) } : {}),
77
87
  };
78
88
  }
79
89
  function normalizeAuthConfig(config) {
80
90
  const settings = config.settings ?? {};
81
91
  const locale = normalizeOptionalCliLocale(settings.locale);
92
+ const defaultUiHost = normalizeOptionalString(settings.init?.defaultUiHost);
93
+ const defaultApiHost = normalizeOptionalString(settings.init?.defaultApiHost);
94
+ const updatePolicy = normalizeOptionalCliUpdatePolicy(settings.update?.policy);
95
+ const logRetentionDays = typeof settings.log?.retentionDays === 'number' && Number.isInteger(settings.log.retentionDays)
96
+ ? settings.log.retentionDays
97
+ : undefined;
98
+ const logEnabled = typeof settings.log?.enabled === 'boolean' ? settings.log.enabled : undefined;
82
99
  return {
83
100
  name: config.name || config.dockerResourcePrefix,
84
101
  settings: {
85
102
  ...(locale ? { locale } : {}),
103
+ ...(defaultUiHost || defaultApiHost
104
+ ? {
105
+ init: {
106
+ ...(defaultUiHost ? { defaultUiHost } : {}),
107
+ ...(defaultApiHost ? { defaultApiHost } : {}),
108
+ },
109
+ }
110
+ : {}),
111
+ ...(updatePolicy ? { update: { policy: updatePolicy } } : {}),
86
112
  ...(settings.license?.pkgUrl ? { license: { pkgUrl: normalizeOptionalString(settings.license.pkgUrl) } } : {}),
87
113
  ...(settings.docker?.network || settings.docker?.containerPrefix
88
114
  ? {
@@ -94,15 +120,39 @@ function normalizeAuthConfig(config) {
94
120
  },
95
121
  }
96
122
  : {}),
97
- ...(settings.bin?.docker || settings.bin?.git || settings.bin?.yarn
123
+ ...(settings.bin?.docker || settings.bin?.caddy || settings.bin?.git || settings.bin?.nginx || settings.bin?.yarn
98
124
  ? {
99
125
  bin: {
100
126
  ...(settings.bin?.docker ? { docker: normalizeOptionalString(settings.bin.docker) } : {}),
127
+ ...(settings.bin?.caddy ? { caddy: normalizeOptionalString(settings.bin.caddy) } : {}),
101
128
  ...(settings.bin?.git ? { git: normalizeOptionalString(settings.bin.git) } : {}),
129
+ ...(settings.bin?.nginx ? { nginx: normalizeOptionalString(settings.bin.nginx) } : {}),
102
130
  ...(settings.bin?.yarn ? { yarn: normalizeOptionalString(settings.bin.yarn) } : {}),
103
131
  },
104
132
  }
105
133
  : {}),
134
+ ...(settings.proxy?.nbCliRoot ||
135
+ settings.proxy?.upstreamHost ||
136
+ settings.proxy?.host
137
+ ? {
138
+ proxy: {
139
+ ...(settings.proxy?.nbCliRoot ? { nbCliRoot: normalizeOptionalString(settings.proxy.nbCliRoot) } : {}),
140
+ ...(settings.proxy?.upstreamHost || settings.proxy?.host
141
+ ? {
142
+ upstreamHost: normalizeOptionalString(settings.proxy?.upstreamHost ?? settings.proxy.host),
143
+ }
144
+ : {}),
145
+ },
146
+ }
147
+ : {}),
148
+ ...(logEnabled !== undefined || logRetentionDays !== undefined
149
+ ? {
150
+ log: {
151
+ ...(logEnabled !== undefined ? { enabled: logEnabled } : {}),
152
+ ...(logRetentionDays !== undefined ? { retentionDays: logRetentionDays } : {}),
153
+ },
154
+ }
155
+ : {}),
106
156
  },
107
157
  lastEnv: config.lastEnv ||
108
158
  config.currentEnv ||
@@ -196,7 +246,29 @@ export class Env {
196
246
  return configuredPath;
197
247
  }
198
248
  }
199
- return resolveConfiguredEnvPath(this.config.appRootPath) ?? resolveEnvRelativePath('.');
249
+ const legacyPath = resolveConfiguredEnvPath(this.config.appRootPath);
250
+ if (legacyPath) {
251
+ return legacyPath;
252
+ }
253
+ return this.kind === 'local' ? this.sourcePath : resolveEnvRelativePath('.');
254
+ }
255
+ get appPath() {
256
+ if (this.kind === 'ssh') {
257
+ const configuredPath = String(this.config.appPath ?? inferConfiguredAppPathFromLegacyConfig(this.config) ?? '').trim();
258
+ if (configuredPath) {
259
+ return configuredPath;
260
+ }
261
+ }
262
+ return resolveConfiguredAppPath(this.config) ?? resolveEnvRelativePath('.');
263
+ }
264
+ get sourcePath() {
265
+ if (this.kind === 'ssh') {
266
+ const configuredPath = String(this.config.appRootPath ?? '').trim();
267
+ if (configuredPath) {
268
+ return configuredPath;
269
+ }
270
+ }
271
+ return resolveConfiguredSourcePath(this.config) ?? path.join(this.appPath, 'source');
200
272
  }
201
273
  get storagePath() {
202
274
  if (this.kind === 'ssh') {
@@ -205,7 +277,13 @@ export class Env {
205
277
  return configuredPath;
206
278
  }
207
279
  }
208
- return resolveConfiguredEnvPath(this.config.storagePath) ?? resolveEnvRelativePath('.');
280
+ const resolvedStoragePath = resolveConfiguredStoragePath(this.config);
281
+ if (resolvedStoragePath) {
282
+ return resolvedStoragePath;
283
+ }
284
+ return this.kind === 'local' || this.kind === 'docker'
285
+ ? path.join(this.appPath, 'storage')
286
+ : resolveEnvRelativePath('.');
209
287
  }
210
288
  get appPort() {
211
289
  return this.config.appPort;
@@ -221,6 +299,8 @@ export class Env {
221
299
  out[key] = String(value);
222
300
  };
223
301
  put('APP_PORT', this.appPort);
302
+ put('APP_PUBLIC_PATH', this.config.appPublicPath ? resolveAppPublicPath(this.config.appPublicPath) : undefined);
303
+ put('CDN_BASE_URL', this.config.cdnBaseUrl);
224
304
  put('APP_KEY', this.config.appKey);
225
305
  put('TZ', this.config.timezone);
226
306
  put('DB_DIALECT', this.config.dbDialect);
@@ -349,6 +429,39 @@ export async function updateEnvConnection(envName, updates, options = {}) {
349
429
  };
350
430
  }, options);
351
431
  }
432
+ export async function replaceEnvConfig(envName, config, options = {}) {
433
+ await writeEnv(envName, (previous) => {
434
+ if (!previous) {
435
+ throw new Error(`Env "${envName}" is not configured`);
436
+ }
437
+ const { apiBaseUrl: _apiBaseUrl, baseUrl: _baseUrl, apibaseUrl: _legacyApiBaseUrl, accessToken, authType, authUsername, ...rest } = config;
438
+ const nextApiBaseUrl = readEnvApiBaseUrl(config);
439
+ const previousApiBaseUrl = readEnvApiBaseUrl(previous);
440
+ const baseUrlChanged = previousApiBaseUrl !== nextApiBaseUrl;
441
+ const previousAuthType = resolveConfiguredAuthType(previous);
442
+ const nextAuthType = normalizeConfiguredAuthType(authType) ?? (accessToken ? 'token' : undefined);
443
+ const nextAuthUsername = nextAuthType === 'basic' ? normalizeOptionalString(authUsername) : undefined;
444
+ const nextAuth = accessToken
445
+ ? {
446
+ type: 'token',
447
+ accessToken,
448
+ }
449
+ : nextAuthType === 'oauth' && !baseUrlChanged && previous?.auth?.type === 'oauth'
450
+ ? previous.auth
451
+ : undefined;
452
+ const authChanged = !areAuthConfigsEquivalent(previous?.auth, nextAuth);
453
+ const authTypeChanged = previousAuthType !== nextAuthType;
454
+ const authUsernameChanged = previous?.authUsername !== nextAuthUsername;
455
+ return {
456
+ ...rest,
457
+ ...(nextApiBaseUrl !== undefined ? { apiBaseUrl: nextApiBaseUrl } : {}),
458
+ ...(nextAuthType ? { authType: nextAuthType } : {}),
459
+ ...(nextAuthUsername ? { authUsername: nextAuthUsername } : {}),
460
+ ...(nextAuth ? { auth: nextAuth } : {}),
461
+ runtime: baseUrlChanged || authChanged || authTypeChanged || authUsernameChanged ? undefined : previous?.runtime,
462
+ };
463
+ }, options);
464
+ }
352
465
  export async function setEnvOauthSession(envName, auth, options = {}) {
353
466
  await writeEnv(envName, (previous) => ({
354
467
  ...previous,
@@ -366,6 +479,17 @@ export async function setEnvRuntime(envName, runtime, options = {}) {
366
479
  };
367
480
  await saveAuthConfig(config, options);
368
481
  }
482
+ export async function clearEnvRootSetup(envName, options = {}) {
483
+ const config = await loadExactAuthConfig(options);
484
+ const current = config.envs[envName];
485
+ if (!current) {
486
+ return false;
487
+ }
488
+ const { rootUsername: _rootUsername, rootEmail: _rootEmail, rootPassword: _rootPassword, rootNickname: _rootNickname, ...rest } = current;
489
+ config.envs[envName] = rest;
490
+ await saveAuthConfig(config, options);
491
+ return true;
492
+ }
369
493
  export async function removeEnv(envName, options = {}) {
370
494
  const config = await loadExactAuthConfig(options);
371
495
  if (!config.envs[envName]) {
@@ -62,14 +62,14 @@ export async function resolveBackupTargetEnv(requestedEnv) {
62
62
  const { envs } = await listEnvs({ scope });
63
63
  const configuredEnvNames = Object.keys(envs);
64
64
  if (!configuredEnvNames.length) {
65
- throw new Error('No env is configured. Run `nb env add <name> --api-base-url <url>` first.');
65
+ throw new Error('No env is configured. Run `nb init --ui` first.');
66
66
  }
67
67
  if (requestedEnv?.trim()) {
68
- throw new Error(`Env "${envName}" is not configured. Run \`nb env add ${envName} --api-base-url <url>\` first.`);
68
+ throw new Error(`Env "${envName}" is not configured. Run \`nb init --ui --env ${envName}\` first.`);
69
69
  }
70
70
  throw new Error([
71
71
  `Current env "${envName}" is not configured.`,
72
- 'Switch to an existing env with `nb env use <name>`, or add one with `nb env add <name> --api-base-url <url>`.',
72
+ `Switch to an existing env with \`nb env use <name>\`, or run \`nb init --ui --env ${envName}\` to create or connect it.`,
73
73
  ].join('\n'));
74
74
  }
75
75
  export async function ensureBackupRuntimeCommands(params) {
@@ -254,25 +254,31 @@ export function formatSwaggerSchemaError(response, context) {
254
254
  })
255
255
  .join('\n');
256
256
  const envLabel = context.envName ? ` for env "${context.envName}"` : '';
257
+ const tokenHint = context.envName
258
+ ? `Update the API key with \`nb env update ${context.envName} --token <api-key>\`, log in with \`nb env auth ${context.envName}\`, or rerun the command with \`--access-token <api-key>\`.`
259
+ : 'Update the API key with `nb env update <name> --token <api-key>`, log in with `nb env auth <name>`, or rerun the command with `--access-token <api-key>`.';
257
260
  const commandHint = context.commandToken
258
- ? `If \`${context.commandToken}\` is a runtime command, refresh the runtime after updating the token with \`nb env update\`. If it is a typo, run \`nb --help\` to inspect available commands.`
259
- : 'Run `nb --help` to inspect built-in commands, then refresh runtime commands with `nb env update` after updating the token.';
261
+ ? `If \`${context.commandToken}\` is a runtime command, retry it after updating the token. If it is a typo, run \`nb --help\` to inspect available commands.`
262
+ : 'Run `nb --help` to inspect built-in commands, then retry the original command after updating the token.';
260
263
  return [
261
264
  `Authentication failed while loading the command runtime from \`swagger:get\`${envLabel}.`,
262
265
  `Base URL: ${context.baseUrl}`,
263
266
  details,
264
- 'Update the API key with `nb env add <name> --api-base-url <url> --auth-type token --access-token <api-key>`, log in with `nb env auth <name>`, or rerun the command with `--access-token <api-key>`.',
267
+ tokenHint,
265
268
  commandHint,
266
269
  ].join('\n');
267
270
  }
268
271
  if (isNetworkFetchFailure(response)) {
269
272
  const rawMessage = response.data?.error?.message || 'fetch failed';
273
+ const updateConnectionHint = context.envName
274
+ ? `If you recently changed the server address, update env "${context.envName}" with \`nb env update ${context.envName} --api-base-url <url>\` and retry the original command.`
275
+ : 'If you recently changed the server address, update the saved env connection with `nb env update <name> --api-base-url <url>` and retry the original command.';
270
276
  return [
271
277
  'Failed to reach the NocoBase server while loading the command runtime from `swagger:get`.',
272
278
  `Base URL: ${context.baseUrl}`,
273
279
  `Network error: ${rawMessage}`,
274
280
  'Check that the NocoBase app is running, the base URL is correct, and the server is reachable from this machine.',
275
- 'If you recently changed the server address, update it with `nb env add <name> --api-base-url <url>` and retry `nb env update`.',
281
+ updateConnectionHint,
276
282
  'Use `nb env list` to inspect the current env configuration.',
277
283
  ].join('\n');
278
284
  }
@@ -282,7 +288,7 @@ export function formatMissingRuntimeEnvError(commandToken) {
282
288
  if (!commandToken) {
283
289
  return [
284
290
  'No env is configured for runtime commands.',
285
- 'Run `nb env add <name> --api-base-url <url>` first.',
291
+ 'Run `nb init --ui` first.',
286
292
  'If you configure multiple environments later, switch with `nb env use <name>`.',
287
293
  ].join('\n');
288
294
  }
@@ -290,7 +296,7 @@ export function formatMissingRuntimeEnvError(commandToken) {
290
296
  `Unable to resolve runtime command \`${commandToken}\`.`,
291
297
  'No env is configured, so the CLI cannot load runtime commands from `swagger:get`.',
292
298
  'If this is a built-in command or a typo, run `nb --help` to inspect available commands.',
293
- 'If this should be an application runtime command, run `nb env add <name> --api-base-url <url>` and then `nb env update`.',
299
+ 'If this should be an application runtime command, run `nb init --ui` to create or connect a NocoBase app first.',
294
300
  ].join('\n');
295
301
  }
296
302
  export async function ensureRuntimeFromArgv(argv, options) {
@@ -366,8 +372,8 @@ export async function updateEnvRuntime(options) {
366
372
  throw new Error([
367
373
  env
368
374
  ? `Env "${envName}" is missing a base URL.`
369
- : `Env "${envName}" is not configured. Run \`nb env add ${envName}\` first.`,
370
- env ? 'Update it with `nb env add <name> --api-base-url <url>` first.' : '',
375
+ : `Env "${envName}" is not configured. Run \`nb init --ui --env ${envName}\` first.`,
376
+ env ? `Update env "${envName}" with \`nb env update ${envName} --api-base-url <url>\` first.` : '',
371
377
  ]
372
378
  .filter(Boolean)
373
379
  .join('\n'));