@nocobase/cli 2.1.0-beta.36 → 2.1.0-beta.37
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/bin/run.js +3 -2
- package/dist/commands/config/delete.js +4 -0
- package/dist/commands/config/get.js +4 -0
- package/dist/commands/config/set.js +5 -1
- package/dist/commands/env/add.js +66 -6
- package/dist/commands/env/auth.js +86 -27
- package/dist/commands/env/info.js +52 -8
- package/dist/commands/env/list.js +2 -2
- package/dist/commands/env/shared.js +41 -3
- package/dist/commands/init.js +196 -136
- package/dist/commands/install.js +311 -265
- package/dist/lib/auth-store.js +47 -19
- package/dist/lib/cli-config.js +99 -4
- package/dist/lib/cli-locale.js +19 -7
- package/dist/lib/db-connection-check.js +61 -0
- package/dist/lib/env-auth.js +79 -0
- package/dist/lib/env-config.js +3 -2
- package/dist/lib/prompt-validators.js +23 -5
- package/dist/lib/prompt-web-ui.js +143 -19
- package/dist/lib/run-npm.js +133 -23
- package/dist/lib/skills-manager.js +74 -4
- package/dist/locale/en-US.json +36 -5
- package/dist/locale/zh-CN.json +36 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -61,7 +61,7 @@ When creating a new app, it can also install NocoBase AI coding skills
|
|
|
61
61
|
(`nocobase/skills`) globally.
|
|
62
62
|
|
|
63
63
|
Use `--skip-skills` if the skills are managed separately, or when running in CI
|
|
64
|
-
or offline environments where `nb init` should not install
|
|
64
|
+
or offline environments where `nb init` should not install them.
|
|
65
65
|
|
|
66
66
|
### Non-Interactive Setup
|
|
67
67
|
|
package/README.zh-CN.md
CHANGED
|
@@ -55,7 +55,7 @@ nb init --ui
|
|
|
55
55
|
|
|
56
56
|
`nb init` 可以连接已有的 NocoBase 应用,也可以安装一个新的 NocoBase 应用。创建新应用时,还可以全局安装 NocoBase AI coding skills (`nocobase/skills`)。
|
|
57
57
|
|
|
58
|
-
如果已经自行管理 skills,或在 CI、离线环境中运行,不希望 `nb init`
|
|
58
|
+
如果已经自行管理 skills,或在 CI、离线环境中运行,不希望 `nb init` 安装 skills,可以传入 `--skip-skills`。
|
|
59
59
|
|
|
60
60
|
### 非交互式初始化
|
|
61
61
|
|
package/bin/run.js
CHANGED
|
@@ -4,6 +4,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import { createRequire } from 'node:module';
|
|
6
6
|
import path from 'node:path';
|
|
7
|
+
import pc from 'picocolors';
|
|
7
8
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
8
9
|
import { normalizeNodeOptions, normalizeSessionEnv } from './session-env.js';
|
|
9
10
|
|
|
@@ -130,6 +131,6 @@ try {
|
|
|
130
131
|
flush();
|
|
131
132
|
} catch (error) {
|
|
132
133
|
const message = formatCliEntryError(error, process.argv.slice(2));
|
|
133
|
-
console.error(message);
|
|
134
|
-
process.
|
|
134
|
+
console.error(pc.red(message));
|
|
135
|
+
process.exit(1);
|
|
135
136
|
}
|
|
@@ -11,9 +11,13 @@ import { assertSupportedCliConfigKey, deleteCliConfigValue } from '../../lib/cli
|
|
|
11
11
|
export default class ConfigDelete extends Command {
|
|
12
12
|
static summary = 'Delete an explicitly configured CLI setting';
|
|
13
13
|
static examples = [
|
|
14
|
+
'<%= config.bin %> <%= command.id %> locale',
|
|
14
15
|
'<%= config.bin %> <%= command.id %> license.pkg-url',
|
|
15
16
|
'<%= config.bin %> <%= command.id %> docker.network',
|
|
16
17
|
'<%= config.bin %> <%= command.id %> docker.container-prefix',
|
|
18
|
+
'<%= config.bin %> <%= command.id %> bin.docker',
|
|
19
|
+
'<%= config.bin %> <%= command.id %> bin.git',
|
|
20
|
+
'<%= config.bin %> <%= command.id %> bin.yarn',
|
|
17
21
|
];
|
|
18
22
|
static args = {
|
|
19
23
|
key: Args.string({
|
|
@@ -11,9 +11,13 @@ import { assertSupportedCliConfigKey, getCliConfigValue } from '../../lib/cli-co
|
|
|
11
11
|
export default class ConfigGet extends Command {
|
|
12
12
|
static summary = 'Get the effective CLI configuration value for a key';
|
|
13
13
|
static examples = [
|
|
14
|
+
'<%= config.bin %> <%= command.id %> locale',
|
|
14
15
|
'<%= config.bin %> <%= command.id %> license.pkg-url',
|
|
15
16
|
'<%= config.bin %> <%= command.id %> docker.network',
|
|
16
17
|
'<%= config.bin %> <%= command.id %> docker.container-prefix',
|
|
18
|
+
'<%= config.bin %> <%= command.id %> bin.docker',
|
|
19
|
+
'<%= config.bin %> <%= command.id %> bin.git',
|
|
20
|
+
'<%= config.bin %> <%= command.id %> bin.yarn',
|
|
17
21
|
];
|
|
18
22
|
static args = {
|
|
19
23
|
key: Args.string({
|
|
@@ -10,11 +10,15 @@ import { Args, Command } from '@oclif/core';
|
|
|
10
10
|
import { assertSupportedCliConfigKey, setCliConfigValue } from '../../lib/cli-config.js';
|
|
11
11
|
export default class ConfigSet extends Command {
|
|
12
12
|
static summary = 'Set a CLI configuration value';
|
|
13
|
-
static description = 'Set a supported CLI configuration key. Supported keys: license.pkg-url, docker.network, docker.container-prefix.';
|
|
13
|
+
static description = 'Set a supported CLI configuration key. Supported keys: locale, license.pkg-url, docker.network, docker.container-prefix, bin.docker, bin.git, bin.yarn.';
|
|
14
14
|
static examples = [
|
|
15
|
+
'<%= config.bin %> <%= command.id %> locale zh-CN',
|
|
15
16
|
'<%= config.bin %> <%= command.id %> license.pkg-url https://pkg.nocobase.com/',
|
|
16
17
|
'<%= config.bin %> <%= command.id %> docker.network nocobase',
|
|
17
18
|
'<%= config.bin %> <%= command.id %> docker.container-prefix nb',
|
|
19
|
+
'<%= config.bin %> <%= command.id %> bin.docker /usr/local/bin/docker',
|
|
20
|
+
'<%= config.bin %> <%= command.id %> bin.git /usr/bin/git',
|
|
21
|
+
'<%= config.bin %> <%= command.id %> bin.yarn yarn',
|
|
18
22
|
];
|
|
19
23
|
static args = {
|
|
20
24
|
key: Args.string({
|
package/dist/commands/env/add.js
CHANGED
|
@@ -54,9 +54,25 @@ const envAddAccessTokenPrompt = {
|
|
|
54
54
|
required: true,
|
|
55
55
|
hidden: (values) => values.authType !== 'token' || values.skipAuth === true,
|
|
56
56
|
};
|
|
57
|
+
const envAddUsernamePrompt = {
|
|
58
|
+
type: 'text',
|
|
59
|
+
message: envAddText('prompts.username.message'),
|
|
60
|
+
placeholder: envAddText('prompts.username.placeholder'),
|
|
61
|
+
required: true,
|
|
62
|
+
hidden: (values) => values.authType !== 'basic' || values.skipAuth === true,
|
|
63
|
+
};
|
|
64
|
+
const envAddPasswordPrompt = {
|
|
65
|
+
type: 'password',
|
|
66
|
+
message: envAddText('prompts.password.message'),
|
|
67
|
+
required: true,
|
|
68
|
+
hidden: (values) => values.authType !== 'basic' || values.skipAuth === true,
|
|
69
|
+
};
|
|
57
70
|
function formatDeferredAuthMessage(envName, authType) {
|
|
58
71
|
const normalizedAuthType = String(authType ?? '').trim();
|
|
59
72
|
const nextStep = `Authentication was skipped for env "${envName}". Run \`nb env auth ${envName}\` to finish setup.`;
|
|
73
|
+
if (normalizedAuthType === 'basic') {
|
|
74
|
+
return `${nextStep} You will be prompted for a username and password.`;
|
|
75
|
+
}
|
|
60
76
|
if (normalizedAuthType === 'token') {
|
|
61
77
|
return `${nextStep} You will be prompted for an access token.`;
|
|
62
78
|
}
|
|
@@ -66,7 +82,7 @@ function formatDeferredAuthMessage(envName, authType) {
|
|
|
66
82
|
return nextStep;
|
|
67
83
|
}
|
|
68
84
|
export default class EnvAdd extends Command {
|
|
69
|
-
static summary = 'Save a named NocoBase API endpoint (token or OAuth), then switch the CLI to use it';
|
|
85
|
+
static summary = 'Save a named NocoBase API endpoint (basic, token, or OAuth), then switch the CLI to use it';
|
|
70
86
|
static examples = [
|
|
71
87
|
'<%= config.bin %> <%= command.id %>',
|
|
72
88
|
'<%= config.bin %> <%= command.id %> local',
|
|
@@ -109,14 +125,20 @@ export default class EnvAdd extends Command {
|
|
|
109
125
|
}),
|
|
110
126
|
'auth-type': Flags.string({
|
|
111
127
|
char: 'a',
|
|
112
|
-
description: 'Authentication: token (API key) or oauth (browser login via `nb env auth`); prompted in a TTY when omitted',
|
|
113
|
-
options: ['token', 'oauth'],
|
|
128
|
+
description: 'Authentication: basic (username/password login), token (API key), or oauth (browser login via `nb env auth`); prompted in a TTY when omitted',
|
|
129
|
+
options: ['basic', 'token', 'oauth'],
|
|
114
130
|
}),
|
|
115
131
|
'access-token': Flags.string({
|
|
116
132
|
char: 't',
|
|
117
133
|
aliases: ['token'],
|
|
118
134
|
description: 'API key or access token when using --auth-type token (prompted in a TTY when omitted)',
|
|
119
135
|
}),
|
|
136
|
+
username: Flags.string({
|
|
137
|
+
description: 'Username when using --auth-type basic (prompted in a TTY when omitted)',
|
|
138
|
+
}),
|
|
139
|
+
password: Flags.string({
|
|
140
|
+
description: 'Password when using --auth-type basic (prompted in a TTY when omitted)',
|
|
141
|
+
}),
|
|
120
142
|
'skip-auth': Flags.boolean({
|
|
121
143
|
description: 'Save the env now and finish authentication later with `nb env auth`',
|
|
122
144
|
default: false,
|
|
@@ -261,6 +283,11 @@ export default class EnvAdd extends Command {
|
|
|
261
283
|
type: 'select',
|
|
262
284
|
message: envAddText('prompts.authType.message'),
|
|
263
285
|
options: [
|
|
286
|
+
{
|
|
287
|
+
value: 'basic',
|
|
288
|
+
label: envAddText('prompts.authType.basicLabel'),
|
|
289
|
+
hint: envAddText('prompts.authType.basicHint'),
|
|
290
|
+
},
|
|
264
291
|
{
|
|
265
292
|
value: 'oauth',
|
|
266
293
|
label: envAddText('prompts.authType.oauthLabel'),
|
|
@@ -271,6 +298,8 @@ export default class EnvAdd extends Command {
|
|
|
271
298
|
initialValue: 'oauth',
|
|
272
299
|
required: true,
|
|
273
300
|
},
|
|
301
|
+
username: envAddUsernamePrompt,
|
|
302
|
+
password: envAddPasswordPrompt,
|
|
274
303
|
accessToken: envAddAccessTokenPrompt,
|
|
275
304
|
};
|
|
276
305
|
buildPromptValues(nameArg, flags) {
|
|
@@ -293,6 +322,12 @@ export default class EnvAdd extends Command {
|
|
|
293
322
|
if (typeof token === 'string' && token !== '') {
|
|
294
323
|
values.accessToken = token;
|
|
295
324
|
}
|
|
325
|
+
if (flags.username !== undefined) {
|
|
326
|
+
values.username = String(flags.username ?? '').trim();
|
|
327
|
+
}
|
|
328
|
+
if (flags.password !== undefined) {
|
|
329
|
+
values.password = String(flags.password ?? '');
|
|
330
|
+
}
|
|
296
331
|
return values;
|
|
297
332
|
}
|
|
298
333
|
buildPromptInitialValues(flags) {
|
|
@@ -309,6 +344,14 @@ export default class EnvAdd extends Command {
|
|
|
309
344
|
}
|
|
310
345
|
return {
|
|
311
346
|
...EnvAdd.prompts,
|
|
347
|
+
username: {
|
|
348
|
+
...envAddUsernamePrompt,
|
|
349
|
+
hidden: () => true,
|
|
350
|
+
},
|
|
351
|
+
password: {
|
|
352
|
+
...envAddPasswordPrompt,
|
|
353
|
+
hidden: () => true,
|
|
354
|
+
},
|
|
312
355
|
accessToken: {
|
|
313
356
|
...envAddAccessTokenPrompt,
|
|
314
357
|
hidden: () => true,
|
|
@@ -316,9 +359,14 @@ export default class EnvAdd extends Command {
|
|
|
316
359
|
};
|
|
317
360
|
}
|
|
318
361
|
buildEnvConfig(results, flags) {
|
|
362
|
+
const authType = String(results.authType ?? '').trim();
|
|
363
|
+
const authUsername = authType === 'basic'
|
|
364
|
+
? String(results.username ?? flags.username ?? '').trim()
|
|
365
|
+
: '';
|
|
319
366
|
const envConfigInput = {
|
|
320
367
|
apiBaseUrl: results.apiBaseUrl,
|
|
321
|
-
authType
|
|
368
|
+
authType,
|
|
369
|
+
authUsername: authUsername || undefined,
|
|
322
370
|
accessToken: results.accessToken,
|
|
323
371
|
};
|
|
324
372
|
for (const [flagName, configKey] of Object.entries(ENV_RUNTIME_FLAG_MAP)) {
|
|
@@ -357,8 +405,20 @@ export default class EnvAdd extends Command {
|
|
|
357
405
|
printInfo(formatDeferredAuthMessage(envName, results.authType));
|
|
358
406
|
return;
|
|
359
407
|
}
|
|
360
|
-
if (results.authType === 'oauth') {
|
|
361
|
-
|
|
408
|
+
if (results.authType === 'oauth' || results.authType === 'basic') {
|
|
409
|
+
const authArgv = [envName];
|
|
410
|
+
if (results.authType === 'basic') {
|
|
411
|
+
authArgv.push('--auth-type', 'basic');
|
|
412
|
+
const username = String(results.username ?? '').trim();
|
|
413
|
+
const password = String(results.password ?? '');
|
|
414
|
+
if (username) {
|
|
415
|
+
authArgv.push('--username', username);
|
|
416
|
+
}
|
|
417
|
+
if (password) {
|
|
418
|
+
authArgv.push('--password', password);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
await this.config.runCommand('env:auth', authArgv);
|
|
362
422
|
}
|
|
363
423
|
await this.config.runCommand('env:update', [envName]);
|
|
364
424
|
printSuccess(`✔ Env "${envName}" is ready.`);
|
|
@@ -7,30 +7,30 @@
|
|
|
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 { getCurrentEnvName, getEnv, resolveConfiguredAuthType, updateEnvConnection
|
|
10
|
+
import { getCurrentEnvName, getEnv, resolveConfiguredAuthType, updateEnvConnection } from '../../lib/auth-store.js';
|
|
11
11
|
import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
|
|
12
|
-
import { authenticateEnvWithOauth } from '../../lib/env-auth.js';
|
|
12
|
+
import { authenticateEnvWithBasic, authenticateEnvWithOauth } from '../../lib/env-auth.js';
|
|
13
13
|
import { runPromptCatalog } from '../../lib/prompt-catalog.js';
|
|
14
|
-
import { failTask, printStage, startTask, stopTask, succeedTask } from '../../lib/ui.js';
|
|
14
|
+
import { failTask, isInteractiveTerminal, printStage, startTask, stopTask, succeedTask } from '../../lib/ui.js';
|
|
15
15
|
import EnvAdd from "./add.js";
|
|
16
16
|
const envAuthPrompts = {
|
|
17
17
|
authType: EnvAdd.prompts.authType,
|
|
18
|
+
username: EnvAdd.prompts.username,
|
|
19
|
+
password: EnvAdd.prompts.password,
|
|
18
20
|
accessToken: EnvAdd.prompts.accessToken,
|
|
19
21
|
};
|
|
20
22
|
function resolveExplicitAuthType(value) {
|
|
21
|
-
return value === 'token' || value === 'oauth' ? value : undefined;
|
|
23
|
+
return value === 'basic' || value === 'token' || value === 'oauth' ? value : undefined;
|
|
22
24
|
}
|
|
23
25
|
function formatMissingEnvMessage(envName) {
|
|
24
|
-
return [
|
|
25
|
-
`Env "${envName}" is not configured.`,
|
|
26
|
-
`Run \`nb env add ${envName} --api-base-url <url>\` first.`,
|
|
27
|
-
].join('\n');
|
|
26
|
+
return [`Env "${envName}" is not configured.`, `Run \`nb env add ${envName} --api-base-url <url>\` first.`].join('\n');
|
|
28
27
|
}
|
|
29
28
|
export default class EnvAuth extends Command {
|
|
30
|
-
static summary = 'Authenticate a saved NocoBase environment with a token or OAuth';
|
|
29
|
+
static summary = 'Authenticate a saved NocoBase environment with basic login, a token, or OAuth';
|
|
31
30
|
static examples = [
|
|
32
31
|
'<%= config.bin %> <%= command.id %>',
|
|
33
32
|
'<%= config.bin %> <%= command.id %> prod',
|
|
33
|
+
'<%= config.bin %> <%= command.id %> prod --auth-type basic --username admin --password secret',
|
|
34
34
|
'<%= config.bin %> <%= command.id %> prod --auth-type token --access-token <api-key>',
|
|
35
35
|
];
|
|
36
36
|
static args = {
|
|
@@ -48,13 +48,19 @@ export default class EnvAuth extends Command {
|
|
|
48
48
|
}),
|
|
49
49
|
'auth-type': Flags.string({
|
|
50
50
|
char: 'a',
|
|
51
|
-
description: 'Authentication: token (API key) or oauth (browser login)',
|
|
52
|
-
options: ['token', 'oauth'],
|
|
51
|
+
description: 'Authentication: basic (username/password login), token (API key), or oauth (browser login)',
|
|
52
|
+
options: ['basic', 'token', 'oauth'],
|
|
53
53
|
}),
|
|
54
54
|
'access-token': Flags.string({
|
|
55
55
|
char: 't',
|
|
56
56
|
description: 'API key or access token when using token authentication',
|
|
57
57
|
}),
|
|
58
|
+
username: Flags.string({
|
|
59
|
+
description: 'Username when using basic authentication (prompted in a TTY when omitted)',
|
|
60
|
+
}),
|
|
61
|
+
password: Flags.string({
|
|
62
|
+
description: 'Password when using basic authentication (prompted in a TTY when omitted)',
|
|
63
|
+
}),
|
|
58
64
|
};
|
|
59
65
|
async run() {
|
|
60
66
|
const { args, flags } = await this.parse(EnvAuth);
|
|
@@ -63,9 +69,6 @@ export default class EnvAuth extends Command {
|
|
|
63
69
|
if (nameArg && nameFlag && nameArg !== nameFlag) {
|
|
64
70
|
this.error(`Environment name was provided both as the argument ("${nameArg}") and as --env ("${nameFlag}"). Please use only one.`);
|
|
65
71
|
}
|
|
66
|
-
if (flags['auth-type'] === 'oauth' && flags['access-token'] !== undefined) {
|
|
67
|
-
this.error('--access-token cannot be used with --auth-type oauth.');
|
|
68
|
-
}
|
|
69
72
|
const envName = nameArg || nameFlag || (await getCurrentEnvName({ scope: resolveDefaultConfigScope() }));
|
|
70
73
|
const env = await getEnv(envName, { scope: resolveDefaultConfigScope() });
|
|
71
74
|
if (!env) {
|
|
@@ -73,32 +76,88 @@ export default class EnvAuth extends Command {
|
|
|
73
76
|
}
|
|
74
77
|
const tokenFromFlags = flags['access-token'];
|
|
75
78
|
const tokenFlagProvided = tokenFromFlags !== undefined;
|
|
76
|
-
const
|
|
79
|
+
const tokenValue = typeof tokenFromFlags === 'string' ? tokenFromFlags.trim() : '';
|
|
80
|
+
const tokenProvided = tokenValue !== '';
|
|
77
81
|
if (tokenFlagProvided && !tokenProvided) {
|
|
78
82
|
this.error('--access-token cannot be empty.');
|
|
79
83
|
}
|
|
84
|
+
const usernameFromFlags = flags.username;
|
|
85
|
+
const usernameFlagProvided = usernameFromFlags !== undefined;
|
|
86
|
+
const usernameProvided = typeof usernameFromFlags === 'string' && usernameFromFlags.trim() !== '';
|
|
87
|
+
if (usernameFlagProvided && !usernameProvided) {
|
|
88
|
+
this.error('--username cannot be empty.');
|
|
89
|
+
}
|
|
90
|
+
const passwordFromFlags = flags.password;
|
|
91
|
+
const passwordFlagProvided = passwordFromFlags !== undefined;
|
|
92
|
+
const passwordProvided = typeof passwordFromFlags === 'string' && passwordFromFlags.trim() !== '';
|
|
93
|
+
if (passwordFlagProvided && !passwordProvided) {
|
|
94
|
+
this.error('--password cannot be empty.');
|
|
95
|
+
}
|
|
80
96
|
const explicitAuthType = resolveExplicitAuthType(flags['auth-type']);
|
|
97
|
+
if (tokenFlagProvided && (usernameFlagProvided || passwordFlagProvided)) {
|
|
98
|
+
this.error('--access-token cannot be used with --username or --password.');
|
|
99
|
+
}
|
|
100
|
+
if (explicitAuthType === 'oauth' && (tokenFlagProvided || usernameFlagProvided || passwordFlagProvided)) {
|
|
101
|
+
this.error('--auth-type oauth cannot be used with --access-token, --username, or --password.');
|
|
102
|
+
}
|
|
103
|
+
if (explicitAuthType === 'token' && (usernameFlagProvided || passwordFlagProvided)) {
|
|
104
|
+
this.error('--auth-type token cannot be used with --username or --password.');
|
|
105
|
+
}
|
|
106
|
+
if (explicitAuthType === 'basic' && tokenFlagProvided) {
|
|
107
|
+
this.error('--auth-type basic cannot be used with --access-token.');
|
|
108
|
+
}
|
|
81
109
|
const savedAuthType = resolveConfiguredAuthType(env.config);
|
|
82
|
-
const resolvedAuthType = explicitAuthType ??
|
|
110
|
+
const resolvedAuthType = explicitAuthType ??
|
|
111
|
+
(tokenProvided ? 'token' : usernameFlagProvided || passwordFlagProvided ? 'basic' : savedAuthType);
|
|
112
|
+
if (resolvedAuthType === 'basic' && !usernameProvided && !isInteractiveTerminal()) {
|
|
113
|
+
this.error('--username is required when using basic authentication in non-interactive mode.');
|
|
114
|
+
}
|
|
115
|
+
if (resolvedAuthType === 'basic' && !passwordProvided && !isInteractiveTerminal()) {
|
|
116
|
+
this.error('--password is required when using basic authentication in non-interactive mode.');
|
|
117
|
+
}
|
|
83
118
|
const prompted = (resolvedAuthType === 'oauth'
|
|
84
119
|
? { authType: 'oauth' }
|
|
85
|
-
:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
|
|
120
|
+
: await runPromptCatalog(envAuthPrompts, {
|
|
121
|
+
values: {
|
|
122
|
+
...(resolvedAuthType ? { authType: resolvedAuthType } : {}),
|
|
123
|
+
...(usernameFlagProvided ? { username: String(usernameFromFlags ?? '').trim() } : {}),
|
|
124
|
+
...(passwordFlagProvided ? { password: String(passwordFromFlags ?? '') } : {}),
|
|
125
|
+
...(tokenFlagProvided ? { accessToken: tokenValue } : {}),
|
|
126
|
+
},
|
|
127
|
+
command: this,
|
|
128
|
+
})) ?? {};
|
|
93
129
|
const authType = resolveExplicitAuthType(prompted.authType ?? resolvedAuthType);
|
|
94
130
|
if (!authType) {
|
|
95
131
|
this.error('Choose an authentication type before continuing.');
|
|
96
132
|
}
|
|
97
133
|
printStage('Authenticating');
|
|
98
134
|
try {
|
|
99
|
-
if (authType === '
|
|
100
|
-
const
|
|
101
|
-
|
|
135
|
+
if (authType === 'basic') {
|
|
136
|
+
const username = String(prompted.username ?? usernameFromFlags ?? '').trim();
|
|
137
|
+
const password = String(prompted.password ?? passwordFromFlags ?? '');
|
|
138
|
+
if (!username) {
|
|
139
|
+
this.error('--username is required when using basic authentication.');
|
|
140
|
+
}
|
|
141
|
+
if (!password) {
|
|
142
|
+
this.error('--password cannot be empty.');
|
|
143
|
+
}
|
|
144
|
+
startTask(`Signing in with username and password for "${envName}"...`);
|
|
145
|
+
const accessToken = await authenticateEnvWithBasic({
|
|
146
|
+
envName,
|
|
147
|
+
username,
|
|
148
|
+
password,
|
|
149
|
+
scope: resolveDefaultConfigScope(),
|
|
150
|
+
});
|
|
151
|
+
await updateEnvConnection(envName, {
|
|
152
|
+
authType: 'basic',
|
|
153
|
+
authUsername: username,
|
|
154
|
+
accessToken,
|
|
155
|
+
}, { scope: resolveDefaultConfigScope() });
|
|
156
|
+
stopTask();
|
|
157
|
+
}
|
|
158
|
+
else if (authType === 'token') {
|
|
159
|
+
const accessToken = String(prompted.accessToken ?? tokenFromFlags ?? '').trim();
|
|
160
|
+
if (accessToken === '') {
|
|
102
161
|
this.error('--access-token cannot be empty.');
|
|
103
162
|
}
|
|
104
163
|
startTask(`Saving access token for "${envName}"...`);
|
|
@@ -10,7 +10,9 @@ import { Args, Command, Flags } from '@oclif/core';
|
|
|
10
10
|
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime } from '../../lib/app-runtime.js';
|
|
11
11
|
import { resolveBuiltinDbConnection } from '../../lib/builtin-db.js';
|
|
12
12
|
import { renderTable } from '../../lib/ui.js';
|
|
13
|
-
import { appRootPath, dbStatus, runtimeStatus, storagePath } from './shared.js';
|
|
13
|
+
import { appRootPath, appUrl, dbStatus, runtimeStatus, storagePath } from './shared.js';
|
|
14
|
+
const MISSING_FIELD = Symbol('missingField');
|
|
15
|
+
const FORBIDDEN_FIELD_PATH_SEGMENTS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
14
16
|
function normalizeJsonValue(value) {
|
|
15
17
|
if (value === undefined || value === null || value === '') {
|
|
16
18
|
return '-';
|
|
@@ -43,6 +45,26 @@ function createGroupTable(title, values) {
|
|
|
43
45
|
function serializeGroup(values) {
|
|
44
46
|
return Object.fromEntries(Object.entries(values).map(([field, value]) => [field, normalizeJsonValue(value)]));
|
|
45
47
|
}
|
|
48
|
+
function resolveFieldPath(value, path) {
|
|
49
|
+
const segments = path
|
|
50
|
+
.split('.')
|
|
51
|
+
.map((segment) => segment.trim())
|
|
52
|
+
.filter(Boolean);
|
|
53
|
+
if (segments.length === 0) {
|
|
54
|
+
return MISSING_FIELD;
|
|
55
|
+
}
|
|
56
|
+
let current = value;
|
|
57
|
+
for (const segment of segments) {
|
|
58
|
+
if (!current ||
|
|
59
|
+
typeof current !== 'object' ||
|
|
60
|
+
FORBIDDEN_FIELD_PATH_SEGMENTS.has(segment) ||
|
|
61
|
+
!Object.prototype.hasOwnProperty.call(current, segment)) {
|
|
62
|
+
return MISSING_FIELD;
|
|
63
|
+
}
|
|
64
|
+
current = current[segment];
|
|
65
|
+
}
|
|
66
|
+
return current;
|
|
67
|
+
}
|
|
46
68
|
export default class EnvInfo extends Command {
|
|
47
69
|
static hidden = false;
|
|
48
70
|
static description = 'Show grouped details for the selected NocoBase env, including app, database, API, and auth settings.';
|
|
@@ -50,6 +72,7 @@ export default class EnvInfo extends Command {
|
|
|
50
72
|
'<%= config.bin %> <%= command.id %> app1',
|
|
51
73
|
'<%= config.bin %> <%= command.id %> app1 --json',
|
|
52
74
|
'<%= config.bin %> <%= command.id %> app1 --show-secrets',
|
|
75
|
+
'<%= config.bin %> <%= command.id %> app1 --field app.url',
|
|
53
76
|
];
|
|
54
77
|
static args = {
|
|
55
78
|
name: Args.string({
|
|
@@ -68,6 +91,9 @@ export default class EnvInfo extends Command {
|
|
|
68
91
|
description: 'Output the result as JSON',
|
|
69
92
|
default: false,
|
|
70
93
|
}),
|
|
94
|
+
field: Flags.string({
|
|
95
|
+
description: 'Return only a single field using dot notation, for example app.url or api.auth.type',
|
|
96
|
+
}),
|
|
71
97
|
'show-secrets': Flags.boolean({
|
|
72
98
|
description: 'Show secret values in plain text',
|
|
73
99
|
default: false,
|
|
@@ -82,6 +108,7 @@ export default class EnvInfo extends Command {
|
|
|
82
108
|
}
|
|
83
109
|
const requestedEnv = envNameArg || envNameFlag;
|
|
84
110
|
const showSecrets = flags['show-secrets'];
|
|
111
|
+
const fieldPath = flags.field?.trim() || undefined;
|
|
85
112
|
const runtime = await resolveManagedAppRuntime(requestedEnv);
|
|
86
113
|
if (!runtime) {
|
|
87
114
|
this.error(formatMissingManagedAppEnvMessage(requestedEnv));
|
|
@@ -90,7 +117,9 @@ export default class EnvInfo extends Command {
|
|
|
90
117
|
const builtinDbConnection = (runtime.kind === 'local' || runtime.kind === 'docker') && runtime.env.config.builtinDb
|
|
91
118
|
? await resolveBuiltinDbConnection(runtime)
|
|
92
119
|
: undefined;
|
|
120
|
+
const dbDialect = builtinDbConnection?.dbDialect ?? runtime.env.config.dbDialect;
|
|
93
121
|
const appGroup = {
|
|
122
|
+
url: appUrl(runtime),
|
|
94
123
|
appRootPath: appRootPath(runtime),
|
|
95
124
|
storagePath: storagePath(runtime),
|
|
96
125
|
appPort: runtime.env.config.appPort,
|
|
@@ -104,16 +133,21 @@ export default class EnvInfo extends Command {
|
|
|
104
133
|
const dbGroup = {
|
|
105
134
|
databaseStatus: await dbStatus(runtime),
|
|
106
135
|
builtinDb: runtime.env.config.builtinDb,
|
|
107
|
-
dbDialect
|
|
136
|
+
dbDialect,
|
|
108
137
|
builtinDbImage: runtime.env.config.builtinDbImage,
|
|
109
138
|
dbHost: builtinDbConnection?.dbHost ?? runtime.env.config.dbHost,
|
|
110
139
|
dbPort: builtinDbConnection?.dbPort ?? runtime.env.config.dbPort,
|
|
111
140
|
dbDatabase: runtime.env.config.dbDatabase,
|
|
112
141
|
dbUser: runtime.env.config.dbUser,
|
|
113
142
|
dbPassword: maskSecret(runtime.env.config.dbPassword, showSecrets),
|
|
143
|
+
dbTablePrefix: runtime.env.config.dbTablePrefix,
|
|
144
|
+
dbUnderscored: runtime.env.config.dbUnderscored,
|
|
145
|
+
...(dbDialect === 'postgres' ? { dbSchema: runtime.env.config.dbSchema } : {}),
|
|
114
146
|
};
|
|
115
147
|
const authGroup = {
|
|
116
|
-
type: auth?.type,
|
|
148
|
+
type: runtime.env.authType ?? auth?.type,
|
|
149
|
+
sessionType: auth?.type,
|
|
150
|
+
username: runtime.env.config.authUsername,
|
|
117
151
|
expiresAt: auth?.type === 'oauth' ? auth.expiresAt : undefined,
|
|
118
152
|
scope: auth?.type === 'oauth' ? auth.scope : undefined,
|
|
119
153
|
issuer: auth?.type === 'oauth' ? auth.issuer : undefined,
|
|
@@ -125,6 +159,8 @@ export default class EnvInfo extends Command {
|
|
|
125
159
|
const apiGroup = {
|
|
126
160
|
apiBaseUrl: runtime.env.apiBaseUrl,
|
|
127
161
|
'auth.type': authGroup.type,
|
|
162
|
+
'auth.sessionType': authGroup.sessionType,
|
|
163
|
+
'auth.username': authGroup.username,
|
|
128
164
|
'auth.expiresAt': authGroup.expiresAt,
|
|
129
165
|
'auth.scope': authGroup.scope,
|
|
130
166
|
'auth.issuer': authGroup.issuer,
|
|
@@ -144,14 +180,22 @@ export default class EnvInfo extends Command {
|
|
|
144
180
|
auth: serializeGroup(authGroup),
|
|
145
181
|
},
|
|
146
182
|
};
|
|
183
|
+
if (fieldPath) {
|
|
184
|
+
const selected = resolveFieldPath(output, fieldPath);
|
|
185
|
+
if (selected === MISSING_FIELD) {
|
|
186
|
+
this.error(`Unknown field "${fieldPath}". Use dot notation like app.url, db.databaseStatus, or api.auth.type.`);
|
|
187
|
+
}
|
|
188
|
+
if (flags.json) {
|
|
189
|
+
this.log(JSON.stringify(selected, null, 2));
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
this.log(typeof selected === 'object' ? JSON.stringify(selected, null, 2) : String(selected));
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
147
195
|
if (flags.json) {
|
|
148
196
|
this.log(JSON.stringify(output, null, 2));
|
|
149
197
|
return;
|
|
150
198
|
}
|
|
151
|
-
this.log([
|
|
152
|
-
createGroupTable('App', appGroup),
|
|
153
|
-
createGroupTable('DB', dbGroup),
|
|
154
|
-
createGroupTable('API', apiGroup),
|
|
155
|
-
].join('\n\n'));
|
|
199
|
+
this.log([createGroupTable('App', appGroup), createGroupTable('DB', dbGroup), createGroupTable('API', apiGroup)].join('\n\n'));
|
|
156
200
|
}
|
|
157
201
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
import { Command } from '@oclif/core';
|
|
10
|
-
import { getCurrentEnvName, listEnvs } from '../../lib/auth-store.js';
|
|
10
|
+
import { getCurrentEnvName, listEnvs, resolveConfiguredAuthType } from '../../lib/auth-store.js';
|
|
11
11
|
import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
|
|
12
12
|
import { renderTable } from '../../lib/ui.js';
|
|
13
13
|
import { resolveApiBaseUrl } from './shared.js';
|
|
@@ -35,7 +35,7 @@ export default class EnvList extends Command {
|
|
|
35
35
|
name,
|
|
36
36
|
env.kind ?? '-',
|
|
37
37
|
resolveApiBaseUrl(env),
|
|
38
|
-
env.auth?.type ?? '',
|
|
38
|
+
resolveConfiguredAuthType(env) ?? env.auth?.type ?? '',
|
|
39
39
|
env.runtime?.version ?? '',
|
|
40
40
|
]);
|
|
41
41
|
}
|
|
@@ -11,13 +11,51 @@ import { executeRawApiRequest } from '../../lib/api-client.js';
|
|
|
11
11
|
export function resolveApiBaseUrl(config) {
|
|
12
12
|
return String(config.apiBaseUrl ?? config.baseUrl ?? config.apibaseUrl ?? '').trim();
|
|
13
13
|
}
|
|
14
|
+
function buildAppPath(publicPath, subapp) {
|
|
15
|
+
const normalizedPublicPath = publicPath.replace(/\/+$/, '');
|
|
16
|
+
if (!subapp) {
|
|
17
|
+
return normalizedPublicPath ? `${normalizedPublicPath}/` : '/';
|
|
18
|
+
}
|
|
19
|
+
const normalizedSubapp = subapp.replace(/^\/+|\/+$/g, '');
|
|
20
|
+
return `${normalizedPublicPath ? normalizedPublicPath : ''}/apps/${normalizedSubapp}/`;
|
|
21
|
+
}
|
|
22
|
+
export function resolveAppUrlFromApiBaseUrl(apiBaseUrl) {
|
|
23
|
+
const value = String(apiBaseUrl ?? '').trim();
|
|
24
|
+
if (!value) {
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const url = new URL(value);
|
|
29
|
+
const subappMatch = url.pathname.match(/^(.*)\/api\/__app\/([^/]+)\/?$/);
|
|
30
|
+
if (subappMatch) {
|
|
31
|
+
url.pathname = buildAppPath(subappMatch[1] ?? '', subappMatch[2]);
|
|
32
|
+
url.search = '';
|
|
33
|
+
url.hash = '';
|
|
34
|
+
return url.toString();
|
|
35
|
+
}
|
|
36
|
+
const appMatch = url.pathname.match(/^(.*)\/api\/?$/);
|
|
37
|
+
if (appMatch) {
|
|
38
|
+
url.pathname = buildAppPath(appMatch[1] ?? '');
|
|
39
|
+
url.search = '';
|
|
40
|
+
url.hash = '';
|
|
41
|
+
return url.toString();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
14
49
|
export function appUrl(runtime) {
|
|
50
|
+
const resolvedFromApiBaseUrl = resolveAppUrlFromApiBaseUrl(runtime.env.apiBaseUrl ?? resolveApiBaseUrl(runtime.env.config));
|
|
51
|
+
if (resolvedFromApiBaseUrl) {
|
|
52
|
+
return resolvedFromApiBaseUrl;
|
|
53
|
+
}
|
|
15
54
|
const port = String(runtime.env.config.appPort ?? '').trim();
|
|
16
55
|
if (port) {
|
|
17
|
-
return `http://127.0.0.1:${port}
|
|
56
|
+
return `http://127.0.0.1:${port}/`;
|
|
18
57
|
}
|
|
19
|
-
|
|
20
|
-
return baseUrl.replace(/\/api\/?$/, '');
|
|
58
|
+
return '';
|
|
21
59
|
}
|
|
22
60
|
export function appRootPath(runtime) {
|
|
23
61
|
if (runtime.kind === 'http' || runtime.kind === 'docker') {
|