@nocobase/cli 2.1.0-beta.21 → 2.1.0-beta.23
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 +37 -49
- package/README.zh-CN.md +36 -47
- package/dist/commands/app/down.js +260 -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 +595 -0
- package/dist/commands/build.js +3 -48
- 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 +84 -6
- package/dist/commands/install.js +288 -61
- 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 +20 -6
- package/dist/lib/app-health.js +126 -0
- package/dist/lib/app-managed-resources.js +264 -0
- package/dist/lib/auth-store.js +5 -2
- package/dist/lib/cli-home.js +7 -6
- package/dist/lib/cli-locale.js +15 -1
- package/dist/lib/env-config.js +80 -0
- package/dist/lib/prompt-web-ui.js +13 -6
- package/dist/lib/skills-manager.js +34 -7
- package/package.json +27 -4
- 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.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,11 @@ 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
|
+
hidden: true,
|
|
307
|
+
default: false,
|
|
308
|
+
}),
|
|
277
309
|
'ui-host': Flags.string({
|
|
278
310
|
description: 'Host for the local --ui setup server (default: 127.0.0.1)',
|
|
279
311
|
}),
|
|
@@ -290,6 +322,9 @@ Prompt modes:
|
|
|
290
322
|
applyCliLocale(parsedResult.flags.locale);
|
|
291
323
|
const flags = parsedResult.flags;
|
|
292
324
|
const normalizedFlags = { ...flags };
|
|
325
|
+
this.parsedFlagsForPromptSeeds = {
|
|
326
|
+
resume: Boolean(normalizedFlags.resume),
|
|
327
|
+
};
|
|
293
328
|
if (normalizedFlags.ui && normalizedFlags.yes) {
|
|
294
329
|
this.error('--ui cannot be used with --yes.');
|
|
295
330
|
}
|
|
@@ -307,7 +342,9 @@ Prompt modes:
|
|
|
307
342
|
this.exit(1);
|
|
308
343
|
}
|
|
309
344
|
p.intro(initTitle());
|
|
310
|
-
await this.syncNocoBaseSkills(
|
|
345
|
+
await this.syncNocoBaseSkills({
|
|
346
|
+
skip: Boolean(normalizedFlags['skip-skills']),
|
|
347
|
+
});
|
|
311
348
|
try {
|
|
312
349
|
await this.config.runCommand('install', this.buildResumeInstallArgv(normalizedFlags));
|
|
313
350
|
}
|
|
@@ -321,6 +358,24 @@ Prompt modes:
|
|
|
321
358
|
const interactive = Boolean(stdinStream.isTTY && stdoutStream.isTTY);
|
|
322
359
|
const useBrowserUi = Boolean(normalizedFlags.ui);
|
|
323
360
|
let presetValues = this.buildPresetValuesFromFlags(normalizedFlags);
|
|
361
|
+
if (normalizedFlags.resume) {
|
|
362
|
+
const resumeEnvName = String(normalizedFlags.env ?? '').trim();
|
|
363
|
+
if (resumeEnvName) {
|
|
364
|
+
const resumeEnv = await getEnv(resumeEnvName, {
|
|
365
|
+
scope: resolveDefaultConfigScope(),
|
|
366
|
+
});
|
|
367
|
+
if (resumeEnv) {
|
|
368
|
+
const savedAppPort = String(resumeEnv.config.appPort ?? '').trim();
|
|
369
|
+
const savedDbPort = String(resumeEnv.config.dbPort ?? '').trim();
|
|
370
|
+
if (savedAppPort) {
|
|
371
|
+
presetValues.resumeSavedAppPort = savedAppPort;
|
|
372
|
+
}
|
|
373
|
+
if (savedDbPort) {
|
|
374
|
+
presetValues.resumeSavedDbPort = savedDbPort;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
324
379
|
if (normalizedFlags.yes && !String(presetValues.appName ?? '').trim()) {
|
|
325
380
|
const formatted = formatSkippedAppNameRequiredMessage();
|
|
326
381
|
p.log.error(highlightInitValidationMessage(formatted));
|
|
@@ -356,8 +411,9 @@ Prompt modes:
|
|
|
356
411
|
onServerStart: ({ url }) => {
|
|
357
412
|
logInitUiReady(this, url);
|
|
358
413
|
},
|
|
359
|
-
onOpenBrowserError: (_url,
|
|
414
|
+
onOpenBrowserError: (_url, err) => {
|
|
360
415
|
logInitUiBrowserOpenFallback();
|
|
416
|
+
p.log.info(`Browser open error: ${formatBrowserOpenError(err)}`);
|
|
361
417
|
},
|
|
362
418
|
});
|
|
363
419
|
}
|
|
@@ -385,7 +441,9 @@ Prompt modes:
|
|
|
385
441
|
if (existingEnv && Boolean(normalizedFlags.force)) {
|
|
386
442
|
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
443
|
}
|
|
388
|
-
await this.syncNocoBaseSkills(
|
|
444
|
+
await this.syncNocoBaseSkills({
|
|
445
|
+
skip: Boolean(normalizedFlags['skip-skills']),
|
|
446
|
+
});
|
|
389
447
|
let managedInstallResults;
|
|
390
448
|
try {
|
|
391
449
|
// oclif explicit registry keys use `:` (e.g. `env:add`); users still type `nb env add`.
|
|
@@ -397,7 +455,7 @@ Prompt modes:
|
|
|
397
455
|
p.log.step('Saving the local env config');
|
|
398
456
|
await this.persistManagedEnvConfig(results, normalizedFlags);
|
|
399
457
|
managedInstallResults = results;
|
|
400
|
-
p.log.step('Running nb
|
|
458
|
+
p.log.step('Running nb init');
|
|
401
459
|
await this.config.runCommand('install', this.buildInstallArgv(results, normalizedFlags));
|
|
402
460
|
}
|
|
403
461
|
}
|
|
@@ -647,7 +705,11 @@ Prompt modes:
|
|
|
647
705
|
hasAgentsDirInCwd() {
|
|
648
706
|
return existsSync(path.resolve(process.cwd(), '.agents'));
|
|
649
707
|
}
|
|
650
|
-
async syncNocoBaseSkills() {
|
|
708
|
+
async syncNocoBaseSkills(options) {
|
|
709
|
+
if (options?.skip) {
|
|
710
|
+
p.log.step('Skipped NocoBase agent skills sync.');
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
651
713
|
try {
|
|
652
714
|
const status = await inspectSkillsStatus();
|
|
653
715
|
if (!status.installed) {
|
|
@@ -682,6 +744,9 @@ Prompt modes:
|
|
|
682
744
|
const dbDatabase = String(results.dbDatabase ?? '').trim();
|
|
683
745
|
const dbUser = String(results.dbUser ?? '').trim();
|
|
684
746
|
const dbPassword = String(results.dbPassword ?? '');
|
|
747
|
+
const apiBaseUrl = String(results.apiBaseUrl ?? '').trim();
|
|
748
|
+
const authType = String(results.authType ?? '').trim() || 'oauth';
|
|
749
|
+
const accessToken = String(results.accessToken ?? '');
|
|
685
750
|
const builtinDb = explicitDbHostFlag(flags)
|
|
686
751
|
? false
|
|
687
752
|
: results.builtinDb === undefined
|
|
@@ -695,7 +760,8 @@ Prompt modes:
|
|
|
695
760
|
: appPort
|
|
696
761
|
? { kind: 'http' }
|
|
697
762
|
: {}),
|
|
698
|
-
...(appPort ? { apiBaseUrl: `http://127.0.0.1:${appPort}/api` } : {}),
|
|
763
|
+
...(apiBaseUrl ? { apiBaseUrl } : appPort ? { apiBaseUrl: `http://127.0.0.1:${appPort}/api` } : {}),
|
|
764
|
+
...(authType === 'token' && accessToken ? { accessToken } : {}),
|
|
699
765
|
...(source ? { source } : {}),
|
|
700
766
|
...(version ? { downloadVersion: version } : {}),
|
|
701
767
|
...(dockerRegistry ? { dockerRegistry } : {}),
|
|
@@ -736,6 +802,9 @@ Prompt modes:
|
|
|
736
802
|
const processArgv = process.argv.slice(2);
|
|
737
803
|
const envName = String(results.appName ?? DEFAULT_INIT_APP_NAME).trim() || DEFAULT_INIT_APP_NAME;
|
|
738
804
|
const source = String(results.source ?? '').trim();
|
|
805
|
+
const apiBaseUrl = String(results.apiBaseUrl ?? '').trim();
|
|
806
|
+
const authType = String(results.authType ?? '').trim();
|
|
807
|
+
const accessToken = String(results.accessToken ?? '');
|
|
739
808
|
argv.push('--env', envName);
|
|
740
809
|
if (options?.resume) {
|
|
741
810
|
argv.push('--resume');
|
|
@@ -743,6 +812,15 @@ Prompt modes:
|
|
|
743
812
|
if (Boolean(flags.verbose)) {
|
|
744
813
|
argv.push('--verbose');
|
|
745
814
|
}
|
|
815
|
+
if (apiBaseUrl) {
|
|
816
|
+
argv.push('--api-base-url', apiBaseUrl);
|
|
817
|
+
}
|
|
818
|
+
if (authType) {
|
|
819
|
+
argv.push('--auth-type', authType);
|
|
820
|
+
}
|
|
821
|
+
if (authType === 'token' && accessToken) {
|
|
822
|
+
argv.push('--access-token', accessToken);
|
|
823
|
+
}
|
|
746
824
|
const lang = String(results.lang ?? '').trim();
|
|
747
825
|
if (lang) {
|
|
748
826
|
argv.push('--lang', lang);
|