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

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 (84) hide show
  1. package/README.md +61 -49
  2. package/README.zh-CN.md +40 -47
  3. package/dist/commands/app/down.js +259 -0
  4. package/dist/commands/app/logs.js +98 -0
  5. package/dist/commands/app/restart.js +75 -0
  6. package/dist/commands/app/start.js +252 -0
  7. package/dist/commands/app/stop.js +98 -0
  8. package/dist/commands/app/upgrade.js +579 -0
  9. package/dist/commands/build.js +3 -48
  10. package/dist/commands/config/delete.js +30 -0
  11. package/dist/commands/config/get.js +29 -0
  12. package/dist/commands/config/index.js +20 -0
  13. package/dist/commands/config/list.js +29 -0
  14. package/dist/commands/config/set.js +35 -0
  15. package/dist/commands/db/check.js +230 -0
  16. package/dist/commands/db/shared.js +1 -1
  17. package/dist/commands/dev.js +3 -147
  18. package/dist/commands/down.js +3 -188
  19. package/dist/commands/download.js +4 -856
  20. package/dist/commands/env/add.js +28 -23
  21. package/dist/commands/env/info.js +152 -0
  22. package/dist/commands/env/list.js +23 -9
  23. package/dist/commands/env/shared.js +158 -0
  24. package/dist/commands/{prompts-stages.js → examples/prompts-stages.js} +3 -3
  25. package/dist/commands/{prompts-test.js → examples/prompts-test.js} +3 -3
  26. package/dist/commands/init.js +83 -6
  27. package/dist/commands/install.js +361 -82
  28. package/dist/commands/license/activate.js +357 -0
  29. package/dist/commands/license/env.js +94 -0
  30. package/dist/commands/license/generate-id.js +107 -0
  31. package/dist/commands/license/id.js +52 -0
  32. package/dist/commands/license/index.js +20 -0
  33. package/dist/commands/license/plugins/clean.js +98 -0
  34. package/dist/commands/license/plugins/index.js +20 -0
  35. package/dist/commands/license/plugins/list.js +50 -0
  36. package/dist/commands/license/plugins/shared.js +325 -0
  37. package/dist/commands/license/plugins/sync.js +267 -0
  38. package/dist/commands/license/shared.js +411 -0
  39. package/dist/commands/license/status.js +50 -0
  40. package/dist/commands/logs.js +3 -88
  41. package/dist/commands/plugin/disable.js +64 -0
  42. package/dist/commands/plugin/enable.js +64 -0
  43. package/dist/commands/plugin/list.js +62 -0
  44. package/dist/commands/pm/disable.js +3 -54
  45. package/dist/commands/pm/enable.js +3 -54
  46. package/dist/commands/pm/list.js +3 -52
  47. package/dist/commands/restart.js +3 -65
  48. package/dist/commands/scaffold/migration.js +1 -1
  49. package/dist/commands/scaffold/plugin.js +1 -1
  50. package/dist/commands/skills/remove.js +71 -0
  51. package/dist/commands/skills/update.js +7 -0
  52. package/dist/commands/source/build.js +58 -0
  53. package/dist/commands/source/dev.js +157 -0
  54. package/dist/commands/source/download.js +866 -0
  55. package/dist/commands/source/test.js +467 -0
  56. package/dist/commands/start.js +3 -209
  57. package/dist/commands/stop.js +3 -88
  58. package/dist/commands/test.js +3 -457
  59. package/dist/commands/upgrade.js +3 -585
  60. package/dist/help/runtime-help.js +3 -0
  61. package/dist/lib/api-client.js +94 -9
  62. package/dist/lib/app-health.js +126 -0
  63. package/dist/lib/app-managed-resources.js +264 -0
  64. package/dist/lib/app-runtime.js +26 -10
  65. package/dist/lib/auth-store.js +29 -63
  66. package/dist/lib/build-config.js +8 -0
  67. package/dist/lib/cli-config.js +176 -0
  68. package/dist/lib/cli-home.js +12 -26
  69. package/dist/lib/cli-locale.js +15 -1
  70. package/dist/lib/db-connection-check.js +178 -0
  71. package/dist/lib/env-config.js +80 -0
  72. package/dist/lib/generated-command.js +23 -3
  73. package/dist/lib/plugin-storage.js +127 -0
  74. package/dist/lib/prompt-validators.js +4 -4
  75. package/dist/lib/prompt-web-ui.js +13 -6
  76. package/dist/lib/runtime-generator.js +89 -10
  77. package/dist/lib/self-manager.js +57 -2
  78. package/dist/lib/skills-manager.js +34 -7
  79. package/dist/lib/startup-update.js +85 -7
  80. package/dist/locale/en-US.json +16 -13
  81. package/dist/locale/zh-CN.json +16 -13
  82. package/nocobase-ctl.config.json +82 -0
  83. package/package.json +41 -6
  84. package/dist/commands/ps.js +0 -119
@@ -9,6 +9,7 @@
9
9
  import { Args, Command, Flags } from '@oclif/core';
10
10
  import { upsertEnv } from '../../lib/auth-store.js';
11
11
  import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
12
+ import { buildStoredEnvConfig, } from '../../lib/env-config.js';
12
13
  import { runPromptCatalog, } from '../../lib/prompt-catalog.js';
13
14
  import { applyCliLocale, CLI_LOCALE_FLAG_DESCRIPTION, CLI_LOCALE_FLAG_OPTIONS, localeText, } from '../../lib/cli-locale.js';
14
15
  import { validateApiBaseUrl } from '../../lib/prompt-validators.js';
@@ -33,6 +34,10 @@ const ENV_RUNTIME_FLAG_MAP = {
33
34
  'db-database': 'dbDatabase',
34
35
  'db-user': 'dbUser',
35
36
  'db-password': 'dbPassword',
37
+ 'root-username': 'rootUsername',
38
+ 'root-email': 'rootEmail',
39
+ 'root-password': 'rootPassword',
40
+ 'root-nickname': 'rootNickname',
36
41
  };
37
42
  const ENV_BOOLEAN_RUNTIME_FLAG_MAP = {
38
43
  'builtin-db': 'builtinDb',
@@ -185,6 +190,22 @@ export default class EnvAdd extends Command {
185
190
  hidden: true,
186
191
  description: 'Database password saved with this env',
187
192
  }),
193
+ 'root-username': Flags.string({
194
+ hidden: true,
195
+ description: 'Initial root username saved with this env',
196
+ }),
197
+ 'root-email': Flags.string({
198
+ hidden: true,
199
+ description: 'Initial root email saved with this env',
200
+ }),
201
+ 'root-password': Flags.string({
202
+ hidden: true,
203
+ description: 'Initial root password saved with this env',
204
+ }),
205
+ 'root-nickname': Flags.string({
206
+ hidden: true,
207
+ description: 'Initial root nickname saved with this env',
208
+ }),
188
209
  };
189
210
  static prompts = {
190
211
  name: {
@@ -250,36 +271,20 @@ export default class EnvAdd extends Command {
250
271
  return initialValues;
251
272
  }
252
273
  buildEnvConfig(results, flags) {
253
- const source = String(flags.source ?? '').trim();
254
- const appRootPath = String(flags['app-root-path'] ?? '').trim();
255
- const kind = source === 'docker'
256
- ? 'docker'
257
- : source === 'npm' || source === 'git' || source === 'local' || appRootPath
258
- ? 'local'
259
- : 'http';
260
- const envConfig = {
261
- kind,
262
- apiBaseUrl: String(results.apiBaseUrl ?? ''),
274
+ const envConfigInput = {
275
+ apiBaseUrl: results.apiBaseUrl,
276
+ authType: results.authType,
277
+ accessToken: results.accessToken,
263
278
  };
264
279
  for (const [flagName, configKey] of Object.entries(ENV_RUNTIME_FLAG_MAP)) {
265
280
  const value = flags[flagName];
266
- if (typeof value === 'string' && value.trim() !== '') {
267
- envConfig[configKey] = value.trim();
268
- }
281
+ envConfigInput[configKey] = value;
269
282
  }
270
283
  for (const [flagName, configKey] of Object.entries(ENV_BOOLEAN_RUNTIME_FLAG_MAP)) {
271
284
  const value = flags[flagName];
272
- if (typeof value === 'boolean') {
273
- envConfig[configKey] = value;
274
- }
275
- }
276
- if (flags['builtin-db'] === false) {
277
- envConfig.builtinDbImage = undefined;
278
- }
279
- if (results.authType === 'token' && results.accessToken != null) {
280
- envConfig.accessToken = String(results.accessToken);
285
+ envConfigInput[configKey] = value;
281
286
  }
282
- return envConfig;
287
+ return buildStoredEnvConfig(envConfigInput);
283
288
  }
284
289
  async run() {
285
290
  const { args, flags } = await this.parse(EnvAdd);
@@ -0,0 +1,152 @@
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 { Args, Command, Flags } from '@oclif/core';
10
+ import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime } from '../../lib/app-runtime.js';
11
+ import { renderTable } from '../../lib/ui.js';
12
+ import { appRootPath, dbStatus, runtimeStatus, storagePath } from './shared.js';
13
+ function normalizeJsonValue(value) {
14
+ if (value === undefined || value === null || value === '') {
15
+ return '-';
16
+ }
17
+ if (typeof value === 'boolean' || typeof value === 'number') {
18
+ return value;
19
+ }
20
+ return String(value);
21
+ }
22
+ function normalizeValue(value) {
23
+ if (value === undefined || value === null || value === '') {
24
+ return '-';
25
+ }
26
+ if (typeof value === 'boolean') {
27
+ return value ? 'true' : 'false';
28
+ }
29
+ return String(value);
30
+ }
31
+ function maskSecret(value, showSecrets) {
32
+ const normalized = normalizeValue(value);
33
+ if (normalized === '-') {
34
+ return normalized;
35
+ }
36
+ return showSecrets ? normalized : '********';
37
+ }
38
+ function createGroupTable(title, values) {
39
+ const rows = Object.entries(values).map(([field, value]) => [field, normalizeValue(value)]);
40
+ return `${title}\n${renderTable(['Field', 'Value'], rows)}`;
41
+ }
42
+ function serializeGroup(values) {
43
+ return Object.fromEntries(Object.entries(values).map(([field, value]) => [field, normalizeJsonValue(value)]));
44
+ }
45
+ export default class EnvInfo extends Command {
46
+ static hidden = false;
47
+ static description = 'Show grouped details for the selected NocoBase env, including app, database, API, and auth settings.';
48
+ static examples = [
49
+ '<%= config.bin %> <%= command.id %> app1',
50
+ '<%= config.bin %> <%= command.id %> app1 --json',
51
+ '<%= config.bin %> <%= command.id %> app1 --show-secrets',
52
+ '<%= config.bin %> <%= command.id %> --env app1',
53
+ ];
54
+ static args = {
55
+ name: Args.string({
56
+ description: 'CLI env name to inspect. Defaults to the current env when omitted',
57
+ required: false,
58
+ }),
59
+ };
60
+ static flags = {
61
+ env: Flags.string({
62
+ char: 'e',
63
+ description: 'CLI env name to inspect. Defaults to the current env when omitted',
64
+ }),
65
+ json: Flags.boolean({
66
+ description: 'Output the result as JSON',
67
+ default: false,
68
+ }),
69
+ 'show-secrets': Flags.boolean({
70
+ description: 'Show secret values in plain text',
71
+ default: false,
72
+ }),
73
+ };
74
+ async run() {
75
+ const { args, flags } = await this.parse(EnvInfo);
76
+ const envNameArg = args.name?.trim() || undefined;
77
+ const envNameFlag = flags.env?.trim() || undefined;
78
+ if (envNameArg && envNameFlag && envNameArg !== envNameFlag) {
79
+ this.error(`Environment name was provided both as the argument ("${envNameArg}") and as --env ("${envNameFlag}"). Please use only one.`);
80
+ }
81
+ const requestedEnv = envNameArg || envNameFlag;
82
+ const showSecrets = flags['show-secrets'];
83
+ const runtime = await resolveManagedAppRuntime(requestedEnv);
84
+ if (!runtime) {
85
+ this.error(formatMissingManagedAppEnvMessage(requestedEnv));
86
+ }
87
+ const auth = runtime.env.auth;
88
+ const appGroup = {
89
+ appRootPath: appRootPath(runtime),
90
+ storagePath: storagePath(runtime),
91
+ appPort: runtime.env.config.appPort,
92
+ appStatus: await runtimeStatus(runtime),
93
+ source: runtime.source,
94
+ downloadVersion: runtime.env.config.downloadVersion,
95
+ dockerRegistry: runtime.env.config.dockerRegistry,
96
+ dockerPlatform: runtime.env.config.dockerPlatform,
97
+ timezone: runtime.env.config.timezone,
98
+ };
99
+ const dbGroup = {
100
+ databaseStatus: await dbStatus(runtime),
101
+ builtinDb: runtime.env.config.builtinDb,
102
+ dbDialect: runtime.env.config.dbDialect,
103
+ builtinDbImage: runtime.env.config.builtinDbImage,
104
+ dbHost: runtime.env.config.dbHost,
105
+ dbPort: runtime.env.config.dbPort,
106
+ dbDatabase: runtime.env.config.dbDatabase,
107
+ dbUser: runtime.env.config.dbUser,
108
+ dbPassword: maskSecret(runtime.env.config.dbPassword, showSecrets),
109
+ };
110
+ const authGroup = {
111
+ type: auth?.type,
112
+ expiresAt: auth?.type === 'oauth' ? auth.expiresAt : undefined,
113
+ scope: auth?.type === 'oauth' ? auth.scope : undefined,
114
+ issuer: auth?.type === 'oauth' ? auth.issuer : undefined,
115
+ clientId: auth?.type === 'oauth' ? auth.clientId : undefined,
116
+ resource: auth?.type === 'oauth' ? auth.resource : undefined,
117
+ accessToken: maskSecret(auth?.accessToken, showSecrets),
118
+ refreshToken: maskSecret(auth?.type === 'oauth' ? auth.refreshToken : undefined, showSecrets),
119
+ };
120
+ const apiGroup = {
121
+ apiBaseUrl: runtime.env.apiBaseUrl,
122
+ 'auth.type': authGroup.type,
123
+ 'auth.expiresAt': authGroup.expiresAt,
124
+ 'auth.scope': authGroup.scope,
125
+ 'auth.issuer': authGroup.issuer,
126
+ 'auth.clientId': authGroup.clientId,
127
+ 'auth.resource': authGroup.resource,
128
+ 'auth.accessToken': authGroup.accessToken,
129
+ 'auth.refreshToken': authGroup.refreshToken,
130
+ };
131
+ const output = {
132
+ ok: true,
133
+ env: runtime.envName,
134
+ kind: runtime.kind,
135
+ app: serializeGroup(appGroup),
136
+ db: serializeGroup(dbGroup),
137
+ api: {
138
+ apiBaseUrl: normalizeJsonValue(runtime.env.apiBaseUrl),
139
+ auth: serializeGroup(authGroup),
140
+ },
141
+ };
142
+ if (flags.json) {
143
+ this.log(JSON.stringify(output, null, 2));
144
+ return;
145
+ }
146
+ this.log([
147
+ createGroupTable('App', appGroup),
148
+ createGroupTable('DB', dbGroup),
149
+ createGroupTable('API', apiGroup),
150
+ ].join('\n\n'));
151
+ }
152
+ }
@@ -7,30 +7,44 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
  import { Command } from '@oclif/core';
10
+ import { resolveManagedAppRuntime } from '../../lib/app-runtime.js';
10
11
  import { listEnvs } from '../../lib/auth-store.js';
11
12
  import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
12
13
  import { renderTable } from '../../lib/ui.js';
13
- function resolveApiBaseUrl(config) {
14
- return String(config.apiBaseUrl ?? config.baseUrl ?? config.apibaseUrl ?? '').trim();
15
- }
14
+ import { apiStatus, appUrl, resolveApiBaseUrl } from './shared.js';
16
15
  export default class EnvList extends Command {
17
- static summary = 'List configured environments';
16
+ static summary = 'List configured environments and API auth status';
18
17
  static examples = [
19
18
  '<%= config.bin %> <%= command.id %>',
20
19
  ];
21
20
  async run() {
22
21
  await this.parse(EnvList);
23
- const { currentEnv, envs } = await listEnvs({ scope: resolveDefaultConfigScope() });
22
+ const scope = resolveDefaultConfigScope();
23
+ const { currentEnv, envs } = await listEnvs({ scope });
24
24
  const names = Object.keys(envs).sort();
25
25
  if (!names.length) {
26
26
  this.log('No envs configured.');
27
27
  this.log('Run `nb env add <name> --api-base-url <url>` to add one.');
28
28
  return;
29
29
  }
30
- const rows = names.map((name) => {
30
+ const rows = [];
31
+ for (const name of names) {
31
32
  const env = envs[name];
32
- return [name === currentEnv ? '*' : '', name, resolveApiBaseUrl(env), env.auth?.type ?? '', env.runtime?.version ?? ''];
33
- });
34
- this.log(renderTable(['Current', 'Name', 'Base URL', 'Auth', 'Runtime'], rows));
33
+ const runtime = await resolveManagedAppRuntime(name);
34
+ const statusConfig = {
35
+ ...env,
36
+ ...(runtime?.env.config ?? {}),
37
+ };
38
+ rows.push([
39
+ name === currentEnv ? '*' : '',
40
+ name,
41
+ runtime?.kind ?? env.kind ?? '-',
42
+ await apiStatus(name, statusConfig, { scope }),
43
+ runtime ? appUrl(runtime) : resolveApiBaseUrl(env),
44
+ env.auth?.type ?? '',
45
+ env.runtime?.version ?? '',
46
+ ]);
47
+ }
48
+ this.log(renderTable(['Current', 'Name', 'Kind', 'App Status', 'URL', 'Auth', 'Runtime'], rows));
35
49
  }
36
50
  }
@@ -0,0 +1,158 @@
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 { buildDockerDbContainerName, dockerContainerExists, dockerContainerIsRunning, } from '../../lib/app-runtime.js';
10
+ import { executeRawApiRequest } from '../../lib/api-client.js';
11
+ export function resolveApiBaseUrl(config) {
12
+ return String(config.apiBaseUrl ?? config.baseUrl ?? config.apibaseUrl ?? '').trim();
13
+ }
14
+ export function appUrl(runtime) {
15
+ const port = String(runtime.env.config.appPort ?? '').trim();
16
+ if (port) {
17
+ return `http://127.0.0.1:${port}`;
18
+ }
19
+ const baseUrl = resolveApiBaseUrl(runtime.env.config);
20
+ return baseUrl.replace(/\/api\/?$/, '');
21
+ }
22
+ export function appRootPath(runtime) {
23
+ if (runtime.kind === 'http' || runtime.kind === 'docker') {
24
+ return '-';
25
+ }
26
+ if (runtime.kind === 'local') {
27
+ return String(runtime.projectRoot ?? runtime.env.appRootPath ?? '').trim() || '-';
28
+ }
29
+ return String(runtime.env.config.appRootPath ?? '').trim() || '-';
30
+ }
31
+ export function storagePath(runtime) {
32
+ if (runtime.kind === 'http') {
33
+ return '-';
34
+ }
35
+ const value = String(runtime.env.storagePath ?? runtime.env.config.storagePath ?? '').trim();
36
+ return value || '-';
37
+ }
38
+ function collectErrorCodes(value) {
39
+ if (!value || typeof value !== 'object') {
40
+ return [];
41
+ }
42
+ if (Array.isArray(value)) {
43
+ return value.flatMap((item) => collectErrorCodes(item));
44
+ }
45
+ const out = [];
46
+ const record = value;
47
+ if (typeof record.code === 'string') {
48
+ out.push(record.code);
49
+ }
50
+ for (const key of ['data', 'error', 'errors']) {
51
+ out.push(...collectErrorCodes(record[key]));
52
+ }
53
+ return out;
54
+ }
55
+ function isAuthFailureData(value) {
56
+ const codes = collectErrorCodes(value);
57
+ return codes.some((code) => ['EMPTY_TOKEN', 'INVALID_TOKEN', 'EXPIRED_TOKEN', 'BLOCKED_TOKEN', 'EXPIRED_SESSION', 'NOT_EXIST_USER'].includes(code));
58
+ }
59
+ function isNetworkFailure(error) {
60
+ if (!(error instanceof Error)) {
61
+ return false;
62
+ }
63
+ return (error.name === 'AbortError'
64
+ || /fetch failed|network|timeout|timed out|abort|ECONNREFUSED|ECONNRESET|ENOTFOUND|ETIMEDOUT|EAI_AGAIN|ENETUNREACH/i.test(error.message));
65
+ }
66
+ function isUnconfiguredFailure(error) {
67
+ return error instanceof Error && /missing (a )?base url|missing base URL/i.test(error.message);
68
+ }
69
+ function isAuthFailure(error) {
70
+ return (error instanceof Error
71
+ && /EMPTY_TOKEN|INVALID_TOKEN|EXPIRED_TOKEN|BLOCKED_TOKEN|EXPIRED_SESSION|NOT_EXIST_USER|invalid_grant|sign in|signin|authentication failed/i.test(error.message));
72
+ }
73
+ export async function apiStatus(envName, config, options = {}) {
74
+ if (!resolveApiBaseUrl(config)) {
75
+ return 'unconfigured';
76
+ }
77
+ try {
78
+ const response = await executeRawApiRequest({
79
+ envName,
80
+ scope: options.scope,
81
+ method: 'GET',
82
+ path: '/auth:check',
83
+ timeoutMs: options.timeoutMs ?? 2000,
84
+ });
85
+ if (response.ok) {
86
+ return 'ok';
87
+ }
88
+ if (response.status === 401 || response.status === 403 || isAuthFailureData(response.data)) {
89
+ return 'auth failed';
90
+ }
91
+ return 'error';
92
+ }
93
+ catch (error) {
94
+ if (isUnconfiguredFailure(error)) {
95
+ return 'unconfigured';
96
+ }
97
+ if (isAuthFailure(error)) {
98
+ return 'auth failed';
99
+ }
100
+ if (isNetworkFailure(error)) {
101
+ return 'unreachable';
102
+ }
103
+ return 'error';
104
+ }
105
+ }
106
+ async function isLocalAppHealthy(runtime) {
107
+ const port = String(runtime.env.config.appPort ?? '').trim();
108
+ if (!port) {
109
+ return false;
110
+ }
111
+ const controller = new AbortController();
112
+ const timeout = setTimeout(() => controller.abort(), 1500);
113
+ try {
114
+ const response = await fetch(`http://127.0.0.1:${port}/api/__health_check`, {
115
+ signal: controller.signal,
116
+ });
117
+ const text = await response.text();
118
+ return response.ok && text.trim().toLowerCase() === 'ok';
119
+ }
120
+ catch (_error) {
121
+ return false;
122
+ }
123
+ finally {
124
+ clearTimeout(timeout);
125
+ }
126
+ }
127
+ async function dockerStatus(containerName) {
128
+ if (!(await dockerContainerExists(containerName))) {
129
+ return 'missing';
130
+ }
131
+ return await dockerContainerIsRunning(containerName) ? 'running' : 'stopped';
132
+ }
133
+ export async function dbStatus(runtime) {
134
+ if (!runtime.env.config.builtinDb) {
135
+ return runtime.kind === 'http' ? 'external' : '-';
136
+ }
137
+ if (runtime.kind === 'http') {
138
+ return 'external';
139
+ }
140
+ if (runtime.kind === 'ssh') {
141
+ return '-';
142
+ }
143
+ const dbDialect = String(runtime.env.config.dbDialect ?? 'postgres').trim() || 'postgres';
144
+ const containerName = buildDockerDbContainerName(runtime.envName, dbDialect, runtime.dockerContainerPrefix || runtime.workspaceName);
145
+ return await dockerStatus(containerName);
146
+ }
147
+ export async function runtimeStatus(runtime) {
148
+ if (runtime.kind === 'http') {
149
+ return 'http';
150
+ }
151
+ if (runtime.kind === 'ssh') {
152
+ return 'ssh';
153
+ }
154
+ if (runtime.kind === 'docker') {
155
+ return await dockerStatus(runtime.containerName);
156
+ }
157
+ return await isLocalAppHealthy(runtime) ? 'running' : 'stopped';
158
+ }
@@ -7,9 +7,9 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
  import { Args, Command, Flags } from '@oclif/core';
10
- import { runPromptCatalog, } from "../lib/prompt-catalog.js";
11
- import { applyCliLocale, CLI_LOCALE_FLAG_DESCRIPTION, CLI_LOCALE_FLAG_OPTIONS, } from "../lib/cli-locale.js";
12
- import { runPromptCatalogWebUI } from "../lib/prompt-web-ui.js";
10
+ import { runPromptCatalog, } from "../../lib/prompt-catalog.js";
11
+ import { applyCliLocale, CLI_LOCALE_FLAG_DESCRIPTION, CLI_LOCALE_FLAG_OPTIONS, } from "../../lib/cli-locale.js";
12
+ import { runPromptCatalogWebUI } from "../../lib/prompt-web-ui.js";
13
13
  import PromptsTest from "./prompts-test.js";
14
14
  function buildWebUiStagesFromTestPrompts(c) {
15
15
  return [
@@ -8,9 +8,9 @@
8
8
  */
9
9
  import * as p from '@clack/prompts';
10
10
  import { Args, Command, Flags } from '@oclif/core';
11
- import { runPromptCatalog, } from "../lib/prompt-catalog.js";
12
- import { applyCliLocale, CLI_LOCALE_FLAG_DESCRIPTION, CLI_LOCALE_FLAG_OPTIONS, } from "../lib/cli-locale.js";
13
- import { runPromptCatalogWebUI, } from "../lib/prompt-web-ui.js";
11
+ import { runPromptCatalog, } from "../../lib/prompt-catalog.js";
12
+ import { applyCliLocale, CLI_LOCALE_FLAG_DESCRIPTION, CLI_LOCALE_FLAG_OPTIONS, } from "../../lib/cli-locale.js";
13
+ import { runPromptCatalogWebUI, } from "../../lib/prompt-web-ui.js";
14
14
  export default class PromptsTest extends Command {
15
15
  static hidden = true;
16
16
  static args = {
@@ -139,6 +139,12 @@ function logInitUiReady(command, url) {
139
139
  function logInitUiBrowserOpenFallback() {
140
140
  p.log.warn(translateCli('commands.init.messages.uiOpenBrowserFallback'));
141
141
  }
142
+ function formatBrowserOpenError(error) {
143
+ if (error instanceof Error) {
144
+ return error.message;
145
+ }
146
+ return String(error);
147
+ }
142
148
  export default class Init extends Command {
143
149
  static summary = 'Set up NocoBase so coding agents can connect and work with it';
144
150
  static description = `Set up NocoBase for coding agents in the current workspace.
@@ -171,6 +177,26 @@ Prompt modes:
171
177
  '<%= config.bin %> <%= command.id %> --ui --ui-port 3000',
172
178
  ];
173
179
  static prompts = {
180
+ seedResume: {
181
+ type: 'run',
182
+ run: (values, command) => {
183
+ const record = values;
184
+ if (record.resume === undefined) {
185
+ const flags = command?.parsedFlagsForPromptSeeds;
186
+ record.resume = Boolean(flags?.resume);
187
+ }
188
+ },
189
+ },
190
+ seedEnvName: {
191
+ type: 'run',
192
+ run: (values) => {
193
+ const record = values;
194
+ const appName = String(record.appName ?? '').trim();
195
+ if (appName && record.env === undefined) {
196
+ record.env = appName;
197
+ }
198
+ },
199
+ },
174
200
  appName: {
175
201
  type: 'text',
176
202
  message: initText('prompts.appName.message'),
@@ -256,6 +282,7 @@ Prompt modes:
256
282
  rootPassword: newInstallOnly(Install.rootUserPrompts.rootPassword),
257
283
  rootNickname: newInstallOnly(Install.rootUserPrompts.rootNickname),
258
284
  };
285
+ parsedFlagsForPromptSeeds;
259
286
  static flags = {
260
287
  yes: Flags.boolean({
261
288
  char: 'y',
@@ -274,6 +301,10 @@ Prompt modes:
274
301
  description: 'Show detailed command output',
275
302
  default: false,
276
303
  }),
304
+ 'skip-skills': Flags.boolean({
305
+ description: 'Skip installing or updating NocoBase AI coding skills during init',
306
+ default: false,
307
+ }),
277
308
  'ui-host': Flags.string({
278
309
  description: 'Host for the local --ui setup server (default: 127.0.0.1)',
279
310
  }),
@@ -290,6 +321,9 @@ Prompt modes:
290
321
  applyCliLocale(parsedResult.flags.locale);
291
322
  const flags = parsedResult.flags;
292
323
  const normalizedFlags = { ...flags };
324
+ this.parsedFlagsForPromptSeeds = {
325
+ resume: Boolean(normalizedFlags.resume),
326
+ };
293
327
  if (normalizedFlags.ui && normalizedFlags.yes) {
294
328
  this.error('--ui cannot be used with --yes.');
295
329
  }
@@ -307,7 +341,9 @@ Prompt modes:
307
341
  this.exit(1);
308
342
  }
309
343
  p.intro(initTitle());
310
- await this.syncNocoBaseSkills();
344
+ await this.syncNocoBaseSkills({
345
+ skip: Boolean(normalizedFlags['skip-skills']),
346
+ });
311
347
  try {
312
348
  await this.config.runCommand('install', this.buildResumeInstallArgv(normalizedFlags));
313
349
  }
@@ -321,6 +357,24 @@ Prompt modes:
321
357
  const interactive = Boolean(stdinStream.isTTY && stdoutStream.isTTY);
322
358
  const useBrowserUi = Boolean(normalizedFlags.ui);
323
359
  let presetValues = this.buildPresetValuesFromFlags(normalizedFlags);
360
+ if (normalizedFlags.resume) {
361
+ const resumeEnvName = String(normalizedFlags.env ?? '').trim();
362
+ if (resumeEnvName) {
363
+ const resumeEnv = await getEnv(resumeEnvName, {
364
+ scope: resolveDefaultConfigScope(),
365
+ });
366
+ if (resumeEnv) {
367
+ const savedAppPort = String(resumeEnv.config.appPort ?? '').trim();
368
+ const savedDbPort = String(resumeEnv.config.dbPort ?? '').trim();
369
+ if (savedAppPort) {
370
+ presetValues.resumeSavedAppPort = savedAppPort;
371
+ }
372
+ if (savedDbPort) {
373
+ presetValues.resumeSavedDbPort = savedDbPort;
374
+ }
375
+ }
376
+ }
377
+ }
324
378
  if (normalizedFlags.yes && !String(presetValues.appName ?? '').trim()) {
325
379
  const formatted = formatSkippedAppNameRequiredMessage();
326
380
  p.log.error(highlightInitValidationMessage(formatted));
@@ -356,8 +410,9 @@ Prompt modes:
356
410
  onServerStart: ({ url }) => {
357
411
  logInitUiReady(this, url);
358
412
  },
359
- onOpenBrowserError: (_url, _err) => {
413
+ onOpenBrowserError: (_url, err) => {
360
414
  logInitUiBrowserOpenFallback();
415
+ p.log.info(`Browser open error: ${formatBrowserOpenError(err)}`);
361
416
  },
362
417
  });
363
418
  }
@@ -385,7 +440,9 @@ Prompt modes:
385
440
  if (existingEnv && Boolean(normalizedFlags.force)) {
386
441
  p.log.warn(`Reconfiguring existing env ${pc.cyan(pc.bold(`"${existingEnv.name}"`))} from the global config because ${pc.bold('--force')} was set. The env config will be updated before install starts, then refreshed again after install succeeds.`);
387
442
  }
388
- await this.syncNocoBaseSkills();
443
+ await this.syncNocoBaseSkills({
444
+ skip: Boolean(normalizedFlags['skip-skills']),
445
+ });
389
446
  let managedInstallResults;
390
447
  try {
391
448
  // oclif explicit registry keys use `:` (e.g. `env:add`); users still type `nb env add`.
@@ -397,7 +454,7 @@ Prompt modes:
397
454
  p.log.step('Saving the local env config');
398
455
  await this.persistManagedEnvConfig(results, normalizedFlags);
399
456
  managedInstallResults = results;
400
- p.log.step('Running nb install');
457
+ p.log.step('Running nb init');
401
458
  await this.config.runCommand('install', this.buildInstallArgv(results, normalizedFlags));
402
459
  }
403
460
  }
@@ -647,7 +704,11 @@ Prompt modes:
647
704
  hasAgentsDirInCwd() {
648
705
  return existsSync(path.resolve(process.cwd(), '.agents'));
649
706
  }
650
- async syncNocoBaseSkills() {
707
+ async syncNocoBaseSkills(options) {
708
+ if (options?.skip) {
709
+ p.log.step('Skipped NocoBase agent skills sync.');
710
+ return;
711
+ }
651
712
  try {
652
713
  const status = await inspectSkillsStatus();
653
714
  if (!status.installed) {
@@ -682,6 +743,9 @@ Prompt modes:
682
743
  const dbDatabase = String(results.dbDatabase ?? '').trim();
683
744
  const dbUser = String(results.dbUser ?? '').trim();
684
745
  const dbPassword = String(results.dbPassword ?? '');
746
+ const apiBaseUrl = String(results.apiBaseUrl ?? '').trim();
747
+ const authType = String(results.authType ?? '').trim() || 'oauth';
748
+ const accessToken = String(results.accessToken ?? '');
685
749
  const builtinDb = explicitDbHostFlag(flags)
686
750
  ? false
687
751
  : results.builtinDb === undefined
@@ -695,7 +759,8 @@ Prompt modes:
695
759
  : appPort
696
760
  ? { kind: 'http' }
697
761
  : {}),
698
- ...(appPort ? { apiBaseUrl: `http://127.0.0.1:${appPort}/api` } : {}),
762
+ ...(apiBaseUrl ? { apiBaseUrl } : appPort ? { apiBaseUrl: `http://127.0.0.1:${appPort}/api` } : {}),
763
+ ...(authType === 'token' && accessToken ? { accessToken } : {}),
699
764
  ...(source ? { source } : {}),
700
765
  ...(version ? { downloadVersion: version } : {}),
701
766
  ...(dockerRegistry ? { dockerRegistry } : {}),
@@ -736,6 +801,9 @@ Prompt modes:
736
801
  const processArgv = process.argv.slice(2);
737
802
  const envName = String(results.appName ?? DEFAULT_INIT_APP_NAME).trim() || DEFAULT_INIT_APP_NAME;
738
803
  const source = String(results.source ?? '').trim();
804
+ const apiBaseUrl = String(results.apiBaseUrl ?? '').trim();
805
+ const authType = String(results.authType ?? '').trim();
806
+ const accessToken = String(results.accessToken ?? '');
739
807
  argv.push('--env', envName);
740
808
  if (options?.resume) {
741
809
  argv.push('--resume');
@@ -743,6 +811,15 @@ Prompt modes:
743
811
  if (Boolean(flags.verbose)) {
744
812
  argv.push('--verbose');
745
813
  }
814
+ if (apiBaseUrl) {
815
+ argv.push('--api-base-url', apiBaseUrl);
816
+ }
817
+ if (authType) {
818
+ argv.push('--auth-type', authType);
819
+ }
820
+ if (authType === 'token' && accessToken) {
821
+ argv.push('--access-token', accessToken);
822
+ }
746
823
  const lang = String(results.lang ?? '').trim();
747
824
  if (lang) {
748
825
  argv.push('--lang', lang);