@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.
- package/README.md +61 -49
- package/README.zh-CN.md +40 -47
- package/dist/commands/app/down.js +259 -0
- package/dist/commands/app/logs.js +98 -0
- package/dist/commands/app/restart.js +75 -0
- package/dist/commands/app/start.js +252 -0
- package/dist/commands/app/stop.js +98 -0
- package/dist/commands/app/upgrade.js +579 -0
- package/dist/commands/build.js +3 -48
- package/dist/commands/config/delete.js +30 -0
- package/dist/commands/config/get.js +29 -0
- package/dist/commands/config/index.js +20 -0
- package/dist/commands/config/list.js +29 -0
- package/dist/commands/config/set.js +35 -0
- package/dist/commands/db/check.js +230 -0
- package/dist/commands/db/shared.js +1 -1
- package/dist/commands/dev.js +3 -147
- package/dist/commands/down.js +3 -188
- package/dist/commands/download.js +4 -856
- package/dist/commands/env/add.js +28 -23
- package/dist/commands/env/info.js +152 -0
- package/dist/commands/env/list.js +23 -9
- package/dist/commands/env/shared.js +158 -0
- package/dist/commands/{prompts-stages.js → examples/prompts-stages.js} +3 -3
- package/dist/commands/{prompts-test.js → examples/prompts-test.js} +3 -3
- package/dist/commands/init.js +83 -6
- package/dist/commands/install.js +361 -82
- package/dist/commands/license/activate.js +357 -0
- package/dist/commands/license/env.js +94 -0
- package/dist/commands/license/generate-id.js +107 -0
- package/dist/commands/license/id.js +52 -0
- package/dist/commands/license/index.js +20 -0
- package/dist/commands/license/plugins/clean.js +98 -0
- package/dist/commands/license/plugins/index.js +20 -0
- package/dist/commands/license/plugins/list.js +50 -0
- package/dist/commands/license/plugins/shared.js +325 -0
- package/dist/commands/license/plugins/sync.js +267 -0
- package/dist/commands/license/shared.js +411 -0
- package/dist/commands/license/status.js +50 -0
- package/dist/commands/logs.js +3 -88
- package/dist/commands/plugin/disable.js +64 -0
- package/dist/commands/plugin/enable.js +64 -0
- package/dist/commands/plugin/list.js +62 -0
- package/dist/commands/pm/disable.js +3 -54
- package/dist/commands/pm/enable.js +3 -54
- package/dist/commands/pm/list.js +3 -52
- package/dist/commands/restart.js +3 -65
- package/dist/commands/scaffold/migration.js +1 -1
- package/dist/commands/scaffold/plugin.js +1 -1
- package/dist/commands/skills/remove.js +71 -0
- package/dist/commands/skills/update.js +7 -0
- package/dist/commands/source/build.js +58 -0
- package/dist/commands/source/dev.js +157 -0
- package/dist/commands/source/download.js +866 -0
- package/dist/commands/source/test.js +467 -0
- package/dist/commands/start.js +3 -209
- package/dist/commands/stop.js +3 -88
- package/dist/commands/test.js +3 -457
- package/dist/commands/upgrade.js +3 -585
- package/dist/help/runtime-help.js +3 -0
- package/dist/lib/api-client.js +94 -9
- package/dist/lib/app-health.js +126 -0
- package/dist/lib/app-managed-resources.js +264 -0
- package/dist/lib/app-runtime.js +26 -10
- package/dist/lib/auth-store.js +29 -63
- package/dist/lib/build-config.js +8 -0
- package/dist/lib/cli-config.js +176 -0
- package/dist/lib/cli-home.js +12 -26
- package/dist/lib/cli-locale.js +15 -1
- package/dist/lib/db-connection-check.js +178 -0
- package/dist/lib/env-config.js +80 -0
- package/dist/lib/generated-command.js +23 -3
- package/dist/lib/plugin-storage.js +127 -0
- package/dist/lib/prompt-validators.js +4 -4
- package/dist/lib/prompt-web-ui.js +13 -6
- package/dist/lib/runtime-generator.js +89 -10
- package/dist/lib/self-manager.js +57 -2
- package/dist/lib/skills-manager.js +34 -7
- package/dist/lib/startup-update.js +85 -7
- package/dist/locale/en-US.json +16 -13
- package/dist/locale/zh-CN.json +16 -13
- package/nocobase-ctl.config.json +82 -0
- package/package.json +41 -6
- package/dist/commands/ps.js +0 -119
package/dist/commands/env/add.js
CHANGED
|
@@ -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
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
30
|
+
const rows = [];
|
|
31
|
+
for (const name of names) {
|
|
31
32
|
const env = envs[name];
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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 "
|
|
11
|
-
import { applyCliLocale, CLI_LOCALE_FLAG_DESCRIPTION, CLI_LOCALE_FLAG_OPTIONS, } from "
|
|
12
|
-
import { runPromptCatalogWebUI } from "
|
|
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 "
|
|
12
|
-
import { applyCliLocale, CLI_LOCALE_FLAG_DESCRIPTION, CLI_LOCALE_FLAG_OPTIONS, } from "
|
|
13
|
-
import { runPromptCatalogWebUI, } from "
|
|
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 = {
|
package/dist/commands/init.js
CHANGED
|
@@ -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,
|
|
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
|
|
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);
|