@nocobase/cli 2.1.0-beta.20 → 2.1.0-beta.21
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 +4 -4
- package/README.zh-CN.md +2 -2
- package/bin/run.js +15 -0
- package/dist/commands/db/shared.js +19 -5
- package/dist/commands/dev.js +8 -1
- package/dist/commands/down.js +10 -6
- package/dist/commands/env/add.js +14 -34
- package/dist/commands/env/auth.js +6 -13
- package/dist/commands/env/list.js +10 -15
- package/dist/commands/env/remove.js +4 -10
- package/dist/commands/env/update.js +7 -13
- package/dist/commands/env/use.js +5 -13
- package/dist/commands/init.js +190 -62
- package/dist/commands/install.js +65 -26
- package/dist/commands/logs.js +8 -1
- package/dist/commands/pm/list.js +8 -1
- package/dist/commands/ps.js +18 -15
- package/dist/commands/self/check.js +1 -1
- package/dist/commands/self/update.js +13 -3
- package/dist/commands/skills/check.js +11 -5
- package/dist/commands/skills/index.js +1 -1
- package/dist/commands/skills/install.js +20 -7
- package/dist/commands/skills/update.js +20 -7
- package/dist/commands/start.js +8 -1
- package/dist/commands/stop.js +8 -1
- package/dist/commands/upgrade.js +12 -1
- package/dist/lib/api-client.js +3 -2
- package/dist/lib/app-runtime.js +16 -5
- package/dist/lib/auth-store.js +159 -43
- package/dist/lib/bootstrap.js +13 -12
- package/dist/lib/cli-home.js +33 -2
- package/dist/lib/env-auth.js +3 -3
- package/dist/lib/generated-command.js +10 -2
- package/dist/lib/http-request.js +49 -0
- package/dist/lib/resource-command.js +10 -2
- package/dist/lib/runtime-generator.js +1 -1
- package/dist/lib/self-manager.js +1 -1
- package/dist/lib/skills-manager.js +140 -73
- package/dist/lib/startup-update.js +203 -0
- package/dist/locale/en-US.json +4 -1
- package/dist/locale/zh-CN.json +4 -1
- package/package.json +2 -2
package/dist/commands/stop.js
CHANGED
|
@@ -50,13 +50,20 @@ export default class Stop extends Command {
|
|
|
50
50
|
if (!runtime) {
|
|
51
51
|
this.error(formatMissingManagedAppEnvMessage(requestedEnv));
|
|
52
52
|
}
|
|
53
|
-
if (runtime.kind === '
|
|
53
|
+
if (runtime.kind === 'http') {
|
|
54
54
|
this.error([
|
|
55
55
|
`Can't stop "${runtime.envName}" from this machine.`,
|
|
56
56
|
'This env only has an API connection, so there is no saved local app or Docker runtime to stop here.',
|
|
57
57
|
'If the app is running on a server, stop it there or reconnect this env to a local runtime first.',
|
|
58
58
|
].join('\n'));
|
|
59
59
|
}
|
|
60
|
+
if (runtime.kind === 'ssh') {
|
|
61
|
+
this.error([
|
|
62
|
+
`Can't stop "${runtime.envName}" yet.`,
|
|
63
|
+
'SSH env support is reserved but not implemented yet.',
|
|
64
|
+
'Use a local or Docker env if you need CLI-managed stop right now.',
|
|
65
|
+
].join('\n'));
|
|
66
|
+
}
|
|
60
67
|
if (runtime.kind === 'docker') {
|
|
61
68
|
startTask(`Stopping NocoBase for "${runtime.envName}"...`);
|
|
62
69
|
try {
|
package/dist/commands/upgrade.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Command, Flags } from '@oclif/core';
|
|
10
10
|
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runLocalNocoBaseCommand, startDockerContainer, stopDockerContainer, } from '../lib/app-runtime.js';
|
|
11
|
+
import { resolveConfiguredEnvPath } from '../lib/cli-home.js';
|
|
11
12
|
import { commandOutput, commandSucceeds, run } from '../lib/run-npm.js';
|
|
12
13
|
import { failTask, printInfo, startTask, stopTask, succeedTask, updateTask } from '../lib/ui.js';
|
|
13
14
|
const DEFAULT_DOCKER_REGISTRY = 'nocobase/nocobase';
|
|
@@ -123,6 +124,9 @@ function normalizeDockerPlatform(value) {
|
|
|
123
124
|
return undefined;
|
|
124
125
|
}
|
|
125
126
|
function readEnvValue(env, key) {
|
|
127
|
+
if (key === 'appRootPath' || key === 'storagePath') {
|
|
128
|
+
return trimValue(resolveConfiguredEnvPath(env.config[key]));
|
|
129
|
+
}
|
|
126
130
|
return trimValue(env.config[key]);
|
|
127
131
|
}
|
|
128
132
|
async function sleep(ms) {
|
|
@@ -560,13 +564,20 @@ export default class Upgrade extends Command {
|
|
|
560
564
|
if (!runtime) {
|
|
561
565
|
this.error(formatMissingManagedAppEnvMessage(requestedEnv));
|
|
562
566
|
}
|
|
563
|
-
if (runtime.kind === '
|
|
567
|
+
if (runtime.kind === 'http') {
|
|
564
568
|
this.error([
|
|
565
569
|
`Can't upgrade "${runtime.envName}" from this machine.`,
|
|
566
570
|
'This env only has an API connection, so there is no saved local app or Docker runtime to upgrade here.',
|
|
567
571
|
'If you want a local NocoBase AI environment that the CLI can upgrade, run `nb init` first.',
|
|
568
572
|
].join('\n'));
|
|
569
573
|
}
|
|
574
|
+
if (runtime.kind === 'ssh') {
|
|
575
|
+
this.error([
|
|
576
|
+
`Can't upgrade "${runtime.envName}" yet.`,
|
|
577
|
+
'SSH env support is reserved but not implemented yet.',
|
|
578
|
+
'Use a local or Docker env if you need CLI-managed upgrades right now.',
|
|
579
|
+
].join('\n'));
|
|
580
|
+
}
|
|
570
581
|
try {
|
|
571
582
|
const runCommand = this.config.runCommand.bind(this.config);
|
|
572
583
|
if (runtime.kind === 'docker') {
|
package/dist/lib/api-client.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { promises as fs } from 'node:fs';
|
|
10
10
|
import { resolveServerRequestTarget } from './env-auth.js';
|
|
11
|
+
import { fetchWithPreservedAuthRedirect } from './http-request.js';
|
|
11
12
|
const CLI_REQUEST_SOURCE_HEADER = 'x-request-source';
|
|
12
13
|
const CLI_REQUEST_SOURCE_VALUE = 'cli';
|
|
13
14
|
function stripUtf8Bom(text) {
|
|
@@ -195,7 +196,7 @@ export async function executeApiRequest(options) {
|
|
|
195
196
|
}
|
|
196
197
|
const url = new URL(`${normalizeBaseUrl(baseUrl)}${requestPath}`);
|
|
197
198
|
query.forEach((value, key) => url.searchParams.append(key, value));
|
|
198
|
-
const response = await
|
|
199
|
+
const response = await fetchWithPreservedAuthRedirect(url.toString(), {
|
|
199
200
|
method: options.operation.method.toUpperCase(),
|
|
200
201
|
headers,
|
|
201
202
|
body: body === undefined ? undefined : JSON.stringify(body),
|
|
@@ -234,7 +235,7 @@ export async function executeRawApiRequest(options) {
|
|
|
234
235
|
}
|
|
235
236
|
url.searchParams.set(key, typeof value === 'object' ? JSON.stringify(value) : String(value));
|
|
236
237
|
}
|
|
237
|
-
const response = await
|
|
238
|
+
const response = await fetchWithPreservedAuthRedirect(url.toString(), {
|
|
238
239
|
method: options.method.toUpperCase(),
|
|
239
240
|
headers,
|
|
240
241
|
body: options.body === undefined ? undefined : JSON.stringify(options.body),
|
package/dist/lib/app-runtime.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
import path from 'node:path';
|
|
10
|
+
import { resolveEnvKind } from './auth-store.js';
|
|
10
11
|
import { getEnv, loadAuthConfig } from './auth-store.js';
|
|
11
12
|
import { commandOutput, commandSucceeds, run, runNocoBaseCommand } from './run-npm.js';
|
|
12
13
|
const DOCKER_APP_WORKDIR = '/app/nocobase';
|
|
@@ -36,7 +37,8 @@ function normalizeEnvSource(env) {
|
|
|
36
37
|
if (source === 'docker' || source === 'npm' || source === 'git') {
|
|
37
38
|
return source;
|
|
38
39
|
}
|
|
39
|
-
|
|
40
|
+
const kind = resolveEnvKind(env.config);
|
|
41
|
+
if (kind === 'local') {
|
|
40
42
|
return 'local';
|
|
41
43
|
}
|
|
42
44
|
return undefined;
|
|
@@ -50,17 +52,18 @@ export async function resolveManagedAppRuntime(envName) {
|
|
|
50
52
|
const resolvedName = env.name || envName?.trim() || config.currentEnv || 'default';
|
|
51
53
|
const source = normalizeEnvSource(env);
|
|
52
54
|
const workspaceName = config.name?.trim() || defaultWorkspaceName();
|
|
53
|
-
|
|
55
|
+
const kind = env.kind ?? resolveEnvKind(env.config);
|
|
56
|
+
if (kind === 'docker') {
|
|
54
57
|
return {
|
|
55
58
|
kind: 'docker',
|
|
56
59
|
env,
|
|
57
60
|
envName: resolvedName,
|
|
58
|
-
source,
|
|
61
|
+
source: 'docker',
|
|
59
62
|
workspaceName,
|
|
60
63
|
containerName: buildDockerAppContainerName(resolvedName, workspaceName),
|
|
61
64
|
};
|
|
62
65
|
}
|
|
63
|
-
if (
|
|
66
|
+
if (kind === 'local') {
|
|
64
67
|
return {
|
|
65
68
|
kind: 'local',
|
|
66
69
|
env,
|
|
@@ -70,8 +73,16 @@ export async function resolveManagedAppRuntime(envName) {
|
|
|
70
73
|
workspaceName,
|
|
71
74
|
};
|
|
72
75
|
}
|
|
76
|
+
if (kind === 'ssh') {
|
|
77
|
+
return {
|
|
78
|
+
kind: 'ssh',
|
|
79
|
+
env,
|
|
80
|
+
envName: resolvedName,
|
|
81
|
+
source,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
73
84
|
return {
|
|
74
|
-
kind: '
|
|
85
|
+
kind: 'http',
|
|
75
86
|
env,
|
|
76
87
|
envName: resolvedName,
|
|
77
88
|
source,
|
package/dist/lib/auth-store.js
CHANGED
|
@@ -7,33 +7,126 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
import { promises as fs } from 'node:fs';
|
|
10
|
-
import path
|
|
11
|
-
import { resolveCliHomeDir } from './cli-home.js';
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { resolveCliHomeDir, resolveConfiguredEnvPath, resolveDefaultConfigScope, resolveEnvRelativePath, } from './cli-home.js';
|
|
12
|
+
function normalizeStoredEnvKind(value) {
|
|
13
|
+
const kind = String(value ?? '').trim();
|
|
14
|
+
if (kind === 'remote') {
|
|
15
|
+
return 'http';
|
|
16
|
+
}
|
|
17
|
+
if (kind === 'local' || kind === 'http' || kind === 'docker' || kind === 'ssh') {
|
|
18
|
+
return kind;
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
function normalizeOptionalString(value) {
|
|
23
|
+
const normalized = String(value ?? '').trim();
|
|
24
|
+
return normalized || undefined;
|
|
25
|
+
}
|
|
26
|
+
export function readEnvApiBaseUrl(config) {
|
|
27
|
+
if (!config) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
return (normalizeOptionalString(config.apiBaseUrl)
|
|
31
|
+
?? normalizeOptionalString(config.baseUrl)
|
|
32
|
+
?? normalizeOptionalString(config.apibaseUrl));
|
|
33
|
+
}
|
|
34
|
+
export function resolveEnvKind(config) {
|
|
35
|
+
if (!config) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
const explicitKind = normalizeStoredEnvKind(config.kind);
|
|
39
|
+
if (explicitKind) {
|
|
40
|
+
return explicitKind;
|
|
41
|
+
}
|
|
42
|
+
const source = String(config.source ?? '').trim();
|
|
43
|
+
if (source === 'docker') {
|
|
44
|
+
return 'docker';
|
|
45
|
+
}
|
|
46
|
+
if (source === 'npm' || source === 'git' || source === 'local') {
|
|
47
|
+
return 'local';
|
|
48
|
+
}
|
|
49
|
+
if (String(config.appRootPath ?? '').trim()) {
|
|
50
|
+
return 'local';
|
|
51
|
+
}
|
|
52
|
+
if (readEnvApiBaseUrl(config) || config.auth) {
|
|
53
|
+
return 'http';
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
function normalizeEnvConfigEntry(entry) {
|
|
58
|
+
if (!entry) {
|
|
59
|
+
return entry;
|
|
60
|
+
}
|
|
61
|
+
const { kind: _kind, apiBaseUrl: _apiBaseUrl, baseUrl: _baseUrl, apibaseUrl: _legacyApiBaseUrl, ...rest } = entry;
|
|
62
|
+
const normalizedKind = resolveEnvKind(entry);
|
|
63
|
+
const apiBaseUrl = readEnvApiBaseUrl(entry);
|
|
64
|
+
return {
|
|
65
|
+
...rest,
|
|
66
|
+
...(normalizedKind ? { kind: normalizedKind } : {}),
|
|
67
|
+
...(apiBaseUrl !== undefined ? { apiBaseUrl } : {}),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function normalizeAuthConfig(config) {
|
|
71
|
+
return {
|
|
72
|
+
name: config.name || config.dockerResourcePrefix,
|
|
73
|
+
currentEnv: config.currentEnv || 'default',
|
|
74
|
+
envs: Object.fromEntries(Object.entries(config.envs || {}).map(([envName, entry]) => [envName, normalizeEnvConfigEntry(entry) ?? {}])),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
16
77
|
function getConfigFile(options = {}) {
|
|
17
78
|
return path.join(resolveCliHomeDir(options.scope), 'config.json');
|
|
18
79
|
}
|
|
19
|
-
|
|
80
|
+
function createDefaultConfig() {
|
|
81
|
+
return {
|
|
82
|
+
currentEnv: 'default',
|
|
83
|
+
envs: {},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function hasConfiguredEnvs(config) {
|
|
87
|
+
return Object.keys(config.envs).length > 0;
|
|
88
|
+
}
|
|
89
|
+
function shouldFallbackToLegacyProjectScope(options = {}) {
|
|
90
|
+
const requestedScope = options.scope ?? resolveDefaultConfigScope();
|
|
91
|
+
return requestedScope === 'global';
|
|
92
|
+
}
|
|
93
|
+
async function loadExactAuthConfig(options = {}) {
|
|
20
94
|
try {
|
|
21
95
|
const content = await fs.readFile(getConfigFile(options), 'utf8');
|
|
22
96
|
const parsed = JSON.parse(content);
|
|
23
|
-
return
|
|
24
|
-
name: parsed.name || parsed.dockerResourcePrefix,
|
|
25
|
-
currentEnv: parsed.currentEnv || 'default',
|
|
26
|
-
envs: parsed.envs || {},
|
|
27
|
-
};
|
|
97
|
+
return normalizeAuthConfig(parsed);
|
|
28
98
|
}
|
|
29
99
|
catch (_error) {
|
|
30
|
-
return
|
|
100
|
+
return createDefaultConfig();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async function resolveEnvStorageScope(envName, options = {}) {
|
|
104
|
+
const requestedScope = options.scope ?? resolveDefaultConfigScope();
|
|
105
|
+
if (requestedScope !== 'global') {
|
|
106
|
+
return { ...options, scope: requestedScope };
|
|
107
|
+
}
|
|
108
|
+
const globalConfig = await loadExactAuthConfig({ scope: 'global' });
|
|
109
|
+
if (globalConfig.envs[envName]) {
|
|
110
|
+
return { ...options, scope: 'global' };
|
|
111
|
+
}
|
|
112
|
+
const projectConfig = await loadExactAuthConfig({ scope: 'project' });
|
|
113
|
+
if (projectConfig.envs[envName]) {
|
|
114
|
+
return { ...options, scope: 'project' };
|
|
31
115
|
}
|
|
116
|
+
return { ...options, scope: 'global' };
|
|
117
|
+
}
|
|
118
|
+
export async function loadAuthConfig(options = {}) {
|
|
119
|
+
const config = await loadExactAuthConfig(options);
|
|
120
|
+
if (!shouldFallbackToLegacyProjectScope(options) || hasConfiguredEnvs(config)) {
|
|
121
|
+
return config;
|
|
122
|
+
}
|
|
123
|
+
const legacyProjectConfig = await loadExactAuthConfig({ scope: 'project' });
|
|
124
|
+
return hasConfiguredEnvs(legacyProjectConfig) ? legacyProjectConfig : config;
|
|
32
125
|
}
|
|
33
126
|
export async function saveAuthConfig(config, options = {}) {
|
|
34
127
|
const filePath = getConfigFile(options);
|
|
35
128
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
36
|
-
await fs.writeFile(filePath, JSON.stringify(config, null, 2));
|
|
129
|
+
await fs.writeFile(filePath, JSON.stringify(normalizeAuthConfig(config), null, 2));
|
|
37
130
|
}
|
|
38
131
|
export async function listEnvs(options = {}) {
|
|
39
132
|
const config = await loadAuthConfig(options);
|
|
@@ -47,15 +140,16 @@ export async function getCurrentEnvName(options = {}) {
|
|
|
47
140
|
return config.currentEnv || 'default';
|
|
48
141
|
}
|
|
49
142
|
export async function setCurrentEnv(envName, options = {}) {
|
|
50
|
-
const
|
|
143
|
+
const writeOptions = await resolveEnvStorageScope(envName, options);
|
|
144
|
+
const config = await loadExactAuthConfig(writeOptions);
|
|
51
145
|
if (!config.envs[envName]) {
|
|
52
146
|
throw new Error(`Env "${envName}" is not configured`);
|
|
53
147
|
}
|
|
54
148
|
config.currentEnv = envName;
|
|
55
|
-
await saveAuthConfig(config,
|
|
149
|
+
await saveAuthConfig(config, writeOptions);
|
|
56
150
|
}
|
|
57
151
|
export async function ensureWorkspaceName(defaultName, options = {}) {
|
|
58
|
-
const config = await
|
|
152
|
+
const config = await loadExactAuthConfig(options);
|
|
59
153
|
const existing = config.name?.trim();
|
|
60
154
|
if (existing) {
|
|
61
155
|
return existing;
|
|
@@ -74,7 +168,10 @@ export class Env {
|
|
|
74
168
|
return this.config.name;
|
|
75
169
|
}
|
|
76
170
|
get baseUrl() {
|
|
77
|
-
return this.config
|
|
171
|
+
return readEnvApiBaseUrl(this.config);
|
|
172
|
+
}
|
|
173
|
+
get apiBaseUrl() {
|
|
174
|
+
return readEnvApiBaseUrl(this.config);
|
|
78
175
|
}
|
|
79
176
|
get auth() {
|
|
80
177
|
return this.config.auth;
|
|
@@ -82,22 +179,26 @@ export class Env {
|
|
|
82
179
|
get runtime() {
|
|
83
180
|
return this.config.runtime;
|
|
84
181
|
}
|
|
182
|
+
get kind() {
|
|
183
|
+
return resolveEnvKind(this.config);
|
|
184
|
+
}
|
|
85
185
|
get appRootPath() {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return appRootPath;
|
|
186
|
+
if (this.kind === 'ssh') {
|
|
187
|
+
const configuredPath = String(this.config.appRootPath ?? '').trim();
|
|
188
|
+
if (configuredPath) {
|
|
189
|
+
return configuredPath;
|
|
190
|
+
}
|
|
92
191
|
}
|
|
93
|
-
return
|
|
192
|
+
return resolveConfiguredEnvPath(this.config.appRootPath) ?? resolveEnvRelativePath('.');
|
|
94
193
|
}
|
|
95
194
|
get storagePath() {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
195
|
+
if (this.kind === 'ssh') {
|
|
196
|
+
const configuredPath = String(this.config.storagePath ?? '').trim();
|
|
197
|
+
if (configuredPath) {
|
|
198
|
+
return configuredPath;
|
|
199
|
+
}
|
|
99
200
|
}
|
|
100
|
-
return
|
|
201
|
+
return resolveConfiguredEnvPath(this.config.storagePath) ?? resolveEnvRelativePath('.');
|
|
101
202
|
}
|
|
102
203
|
get appPort() {
|
|
103
204
|
return this.config.appPort;
|
|
@@ -130,9 +231,18 @@ export async function getEnv(envName, options = {}) {
|
|
|
130
231
|
const resolved = envName?.trim() || config.currentEnv || 'default';
|
|
131
232
|
const envConfig = config.envs[resolved];
|
|
132
233
|
if (!envConfig) {
|
|
133
|
-
|
|
234
|
+
if (!shouldFallbackToLegacyProjectScope(loadOptions)) {
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
const legacyProjectConfig = await loadExactAuthConfig({ scope: 'project' });
|
|
238
|
+
const legacyResolved = envName?.trim() || legacyProjectConfig.currentEnv || 'default';
|
|
239
|
+
const legacyEnvConfig = legacyProjectConfig.envs[legacyResolved];
|
|
240
|
+
if (!legacyEnvConfig) {
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
243
|
+
return new Env({ ...(normalizeEnvConfigEntry(legacyEnvConfig) ?? {}), name: legacyResolved });
|
|
134
244
|
}
|
|
135
|
-
return new Env({ ...envConfig, name: resolved });
|
|
245
|
+
return new Env({ ...(normalizeEnvConfigEntry(envConfig) ?? {}), name: resolved });
|
|
136
246
|
}
|
|
137
247
|
function areAuthConfigsEquivalent(left, right) {
|
|
138
248
|
if (!left && !right) {
|
|
@@ -156,16 +266,19 @@ function areAuthConfigsEquivalent(left, right) {
|
|
|
156
266
|
return false;
|
|
157
267
|
}
|
|
158
268
|
async function writeEnv(envName, updater, options = {}) {
|
|
159
|
-
const
|
|
269
|
+
const writeOptions = await resolveEnvStorageScope(envName, options);
|
|
270
|
+
const config = await loadExactAuthConfig(writeOptions);
|
|
160
271
|
const previous = config.envs[envName];
|
|
161
272
|
config.envs[envName] = updater(previous);
|
|
162
273
|
config.currentEnv = envName;
|
|
163
|
-
await saveAuthConfig(config,
|
|
274
|
+
await saveAuthConfig(config, writeOptions);
|
|
164
275
|
}
|
|
165
276
|
export async function upsertEnv(envName, config, options = {}) {
|
|
166
277
|
await writeEnv(envName, (previous) => {
|
|
167
|
-
const { baseUrl, accessToken, ...rest } = config;
|
|
168
|
-
const
|
|
278
|
+
const { apiBaseUrl: _apiBaseUrl, baseUrl: _baseUrl, apibaseUrl: _legacyApiBaseUrl, accessToken, ...rest } = config;
|
|
279
|
+
const nextApiBaseUrl = readEnvApiBaseUrl(config);
|
|
280
|
+
const previousApiBaseUrl = readEnvApiBaseUrl(previous);
|
|
281
|
+
const baseUrlChanged = previousApiBaseUrl !== nextApiBaseUrl;
|
|
169
282
|
const nextAuth = accessToken
|
|
170
283
|
? {
|
|
171
284
|
type: 'token',
|
|
@@ -177,7 +290,7 @@ export async function upsertEnv(envName, config, options = {}) {
|
|
|
177
290
|
const authChanged = !areAuthConfigsEquivalent(previous?.auth, nextAuth);
|
|
178
291
|
return {
|
|
179
292
|
...previous,
|
|
180
|
-
|
|
293
|
+
apiBaseUrl: nextApiBaseUrl,
|
|
181
294
|
auth: nextAuth,
|
|
182
295
|
...rest,
|
|
183
296
|
runtime: baseUrlChanged || authChanged ? undefined : previous?.runtime,
|
|
@@ -186,8 +299,9 @@ export async function upsertEnv(envName, config, options = {}) {
|
|
|
186
299
|
}
|
|
187
300
|
export async function updateEnvConnection(envName, updates, options = {}) {
|
|
188
301
|
await writeEnv(envName, (previous) => {
|
|
189
|
-
const
|
|
190
|
-
const
|
|
302
|
+
const nextApiBaseUrl = readEnvApiBaseUrl(updates) ?? readEnvApiBaseUrl(previous);
|
|
303
|
+
const previousApiBaseUrl = readEnvApiBaseUrl(previous);
|
|
304
|
+
const baseUrlChanged = previousApiBaseUrl !== nextApiBaseUrl;
|
|
191
305
|
const nextAuth = updates.accessToken
|
|
192
306
|
? {
|
|
193
307
|
type: 'token',
|
|
@@ -199,7 +313,7 @@ export async function updateEnvConnection(envName, updates, options = {}) {
|
|
|
199
313
|
const authChanged = !areAuthConfigsEquivalent(previous?.auth, nextAuth);
|
|
200
314
|
return {
|
|
201
315
|
...previous,
|
|
202
|
-
...(
|
|
316
|
+
...(nextApiBaseUrl !== undefined ? { apiBaseUrl: nextApiBaseUrl } : {}),
|
|
203
317
|
auth: nextAuth,
|
|
204
318
|
runtime: baseUrlChanged || authChanged ? undefined : previous?.runtime,
|
|
205
319
|
};
|
|
@@ -213,17 +327,19 @@ export async function setEnvOauthSession(envName, auth, options = {}) {
|
|
|
213
327
|
}), options);
|
|
214
328
|
}
|
|
215
329
|
export async function setEnvRuntime(envName, runtime, options = {}) {
|
|
216
|
-
const
|
|
330
|
+
const writeOptions = await resolveEnvStorageScope(envName, options);
|
|
331
|
+
const config = await loadExactAuthConfig(writeOptions);
|
|
217
332
|
const current = config.envs[envName] ?? {};
|
|
218
333
|
config.envs[envName] = {
|
|
219
334
|
...current,
|
|
220
335
|
runtime,
|
|
221
336
|
};
|
|
222
337
|
config.currentEnv = envName;
|
|
223
|
-
await saveAuthConfig(config,
|
|
338
|
+
await saveAuthConfig(config, writeOptions);
|
|
224
339
|
}
|
|
225
340
|
export async function removeEnv(envName, options = {}) {
|
|
226
|
-
const
|
|
341
|
+
const writeOptions = await resolveEnvStorageScope(envName, options);
|
|
342
|
+
const config = await loadExactAuthConfig(writeOptions);
|
|
227
343
|
if (!config.envs[envName]) {
|
|
228
344
|
throw new Error(`Env "${envName}" is not configured`);
|
|
229
345
|
}
|
|
@@ -232,7 +348,7 @@ export async function removeEnv(envName, options = {}) {
|
|
|
232
348
|
const nextEnv = Object.keys(config.envs).sort()[0];
|
|
233
349
|
config.currentEnv = nextEnv ?? 'default';
|
|
234
350
|
}
|
|
235
|
-
await saveAuthConfig(config,
|
|
351
|
+
await saveAuthConfig(config, writeOptions);
|
|
236
352
|
return {
|
|
237
353
|
removed: envName,
|
|
238
354
|
currentEnv: config.currentEnv || 'default',
|
package/dist/lib/bootstrap.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { getCurrentEnvName, getEnv, setEnvRuntime, updateEnvConnection } from './auth-store.js';
|
|
10
10
|
import { resolveAccessToken } from './env-auth.js';
|
|
11
|
+
import { fetchWithPreservedAuthRedirect } from './http-request.js';
|
|
11
12
|
import { generateRuntime } from './runtime-generator.js';
|
|
12
13
|
import { hasRuntimeSync, saveRuntime } from './runtime-store.js';
|
|
13
14
|
import { confirmAction, printInfo, printVerbose, printWarningBlock, setVerboseMode, stopTask, updateTask } from './ui.js';
|
|
@@ -92,7 +93,7 @@ async function requestJson(url, options) {
|
|
|
92
93
|
}
|
|
93
94
|
let response;
|
|
94
95
|
try {
|
|
95
|
-
response = await
|
|
96
|
+
response = await fetchWithPreservedAuthRedirect(url, {
|
|
96
97
|
method: options.method ?? 'GET',
|
|
97
98
|
headers,
|
|
98
99
|
});
|
|
@@ -144,7 +145,7 @@ async function waitForServiceReady(baseUrl, token, role) {
|
|
|
144
145
|
const startedAt = Date.now();
|
|
145
146
|
let notified = false;
|
|
146
147
|
while (Date.now() - startedAt < APP_RETRY_TIMEOUT) {
|
|
147
|
-
const response = await
|
|
148
|
+
const response = await fetchWithPreservedAuthRedirect(healthCheckUrl, {
|
|
148
149
|
method: 'GET',
|
|
149
150
|
headers: token || role
|
|
150
151
|
? {
|
|
@@ -228,14 +229,14 @@ function collectErrorEntries(data) {
|
|
|
228
229
|
}
|
|
229
230
|
return [];
|
|
230
231
|
}
|
|
231
|
-
function
|
|
232
|
-
return collectErrorEntries(data).some((entry) => entry?.code === 'INVALID_TOKEN');
|
|
232
|
+
function hasAuthenticationError(data) {
|
|
233
|
+
return collectErrorEntries(data).some((entry) => entry?.code === 'INVALID_TOKEN' || entry?.code === 'EMPTY_TOKEN');
|
|
233
234
|
}
|
|
234
235
|
function isNetworkFetchFailure(response) {
|
|
235
236
|
return response.status === 0;
|
|
236
237
|
}
|
|
237
238
|
export function formatSwaggerSchemaError(response, context) {
|
|
238
|
-
if (
|
|
239
|
+
if (hasAuthenticationError(response.data)) {
|
|
239
240
|
const entries = collectErrorEntries(response.data);
|
|
240
241
|
const details = entries
|
|
241
242
|
.map((entry) => {
|
|
@@ -251,7 +252,7 @@ export function formatSwaggerSchemaError(response, context) {
|
|
|
251
252
|
`Authentication failed while loading the command runtime from \`swagger:get\`${envLabel}.`,
|
|
252
253
|
`Base URL: ${context.baseUrl}`,
|
|
253
254
|
details,
|
|
254
|
-
'Update the API key with `nb env add <name> --base-url <url> --auth-type token --token <api-key>`, log in with `nb env auth <name>`, or rerun the command with `--token <api-key>`.',
|
|
255
|
+
'Update the API key with `nb env add <name> --api-base-url <url> --auth-type token --token <api-key>`, log in with `nb env auth <name>`, or rerun the command with `--token <api-key>`.',
|
|
255
256
|
commandHint,
|
|
256
257
|
].join('\n');
|
|
257
258
|
}
|
|
@@ -262,7 +263,7 @@ export function formatSwaggerSchemaError(response, context) {
|
|
|
262
263
|
`Base URL: ${context.baseUrl}`,
|
|
263
264
|
`Network error: ${rawMessage}`,
|
|
264
265
|
'Check that the NocoBase app is running, the base URL is correct, and the server is reachable from this machine.',
|
|
265
|
-
'If you recently changed the server address, update it with `nb env add <name> --base-url <url>` and retry `nb env update`.',
|
|
266
|
+
'If you recently changed the server address, update it with `nb env add <name> --api-base-url <url>` and retry `nb env update`.',
|
|
266
267
|
'Use `nb env list` to inspect the current env configuration.',
|
|
267
268
|
].join('\n');
|
|
268
269
|
}
|
|
@@ -272,7 +273,7 @@ export function formatMissingRuntimeEnvError(commandToken) {
|
|
|
272
273
|
if (!commandToken) {
|
|
273
274
|
return [
|
|
274
275
|
'No env is configured for runtime commands.',
|
|
275
|
-
'Run `nb env add <name> --base-url <url>` first.',
|
|
276
|
+
'Run `nb env add <name> --api-base-url <url>` first.',
|
|
276
277
|
'If you configure multiple environments later, switch with `nb env use <name>`.',
|
|
277
278
|
].join('\n');
|
|
278
279
|
}
|
|
@@ -280,7 +281,7 @@ export function formatMissingRuntimeEnvError(commandToken) {
|
|
|
280
281
|
`Unable to resolve runtime command \`${commandToken}\`.`,
|
|
281
282
|
'No env is configured, so the CLI cannot load runtime commands from `swagger:get`.',
|
|
282
283
|
'If this is a built-in command or a typo, run `nb --help` to inspect available commands.',
|
|
283
|
-
'If this should be an application runtime command, run `nb env add <name> --base-url <url>` and then `nb env update`.',
|
|
284
|
+
'If this should be an application runtime command, run `nb env add <name> --api-base-url <url>` and then `nb env update`.',
|
|
284
285
|
].join('\n');
|
|
285
286
|
}
|
|
286
287
|
export async function ensureRuntimeFromArgv(argv, options) {
|
|
@@ -294,7 +295,7 @@ export async function ensureRuntimeFromArgv(argv, options) {
|
|
|
294
295
|
try {
|
|
295
296
|
const envName = readFlag(argv, 'env') ?? (await getCurrentEnvName());
|
|
296
297
|
const env = await getEnv(envName);
|
|
297
|
-
const baseUrl = readFlag(argv, 'base-url') ?? env?.baseUrl;
|
|
298
|
+
const baseUrl = readFlag(argv, 'api-base-url') ?? env?.baseUrl;
|
|
298
299
|
const role = readFlag(argv, 'role');
|
|
299
300
|
const token = await resolveAccessToken({
|
|
300
301
|
envName,
|
|
@@ -357,7 +358,7 @@ export async function updateEnvRuntime(options) {
|
|
|
357
358
|
env
|
|
358
359
|
? `Env "${envName}" is missing a base URL.`
|
|
359
360
|
: `Env "${envName}" is not configured. Run \`nb env add ${envName}\` first.`,
|
|
360
|
-
env ? 'Update it with `nb env add <name> --base-url <url>` first.' : '',
|
|
361
|
+
env ? 'Update it with `nb env add <name> --api-base-url <url>` first.' : '',
|
|
361
362
|
]
|
|
362
363
|
.filter(Boolean)
|
|
363
364
|
.join('\n'));
|
|
@@ -370,7 +371,7 @@ export async function updateEnvRuntime(options) {
|
|
|
370
371
|
await saveRuntime(runtime, { scope: options.scope });
|
|
371
372
|
if (options.baseUrl !== undefined || options.token !== undefined) {
|
|
372
373
|
await updateEnvConnection(envName, {
|
|
373
|
-
|
|
374
|
+
apiBaseUrl: options.baseUrl,
|
|
374
375
|
accessToken: options.token,
|
|
375
376
|
}, { scope: options.scope });
|
|
376
377
|
}
|
package/dist/lib/cli-home.js
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
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
|
+
*/
|
|
1
9
|
import fs from 'node:fs';
|
|
2
10
|
import os from 'node:os';
|
|
3
11
|
import path from 'node:path';
|
|
4
12
|
export const CLI_HOME_DIRNAME = '.nocobase';
|
|
13
|
+
export const NB_CONFIG_SCOPE_ENV = 'NB_CONFIG_SCOPE';
|
|
14
|
+
export const NB_ENV_ROOT_ENV = 'NB_ENV_ROOT';
|
|
15
|
+
export function resolveDefaultConfigScope() {
|
|
16
|
+
const raw = String(process.env[NB_CONFIG_SCOPE_ENV] ?? '').trim().toLowerCase();
|
|
17
|
+
return raw === 'project' ? 'project' : 'global';
|
|
18
|
+
}
|
|
5
19
|
function resolveGlobalCliHomeRoot() {
|
|
6
20
|
if (process.env.NOCOBASE_CTL_HOME) {
|
|
7
21
|
return process.env.NOCOBASE_CTL_HOME;
|
|
8
22
|
}
|
|
9
23
|
return os.homedir();
|
|
10
24
|
}
|
|
11
|
-
export function resolveCliHomeRoot(scope =
|
|
25
|
+
export function resolveCliHomeRoot(scope = resolveDefaultConfigScope()) {
|
|
12
26
|
const cwdRoot = process.cwd();
|
|
13
27
|
if (scope === 'project') {
|
|
14
28
|
return cwdRoot;
|
|
@@ -22,9 +36,26 @@ export function resolveCliHomeRoot(scope = 'auto') {
|
|
|
22
36
|
}
|
|
23
37
|
return resolveGlobalCliHomeRoot();
|
|
24
38
|
}
|
|
25
|
-
export function resolveCliHomeDir(scope =
|
|
39
|
+
export function resolveCliHomeDir(scope = resolveDefaultConfigScope()) {
|
|
26
40
|
return path.join(resolveCliHomeRoot(scope), CLI_HOME_DIRNAME);
|
|
27
41
|
}
|
|
42
|
+
export function resolveEnvRoot(scope = resolveDefaultConfigScope()) {
|
|
43
|
+
const envRoot = String(process.env[NB_ENV_ROOT_ENV] ?? '').trim();
|
|
44
|
+
if (envRoot) {
|
|
45
|
+
return path.resolve(envRoot);
|
|
46
|
+
}
|
|
47
|
+
return resolveCliHomeRoot(scope);
|
|
48
|
+
}
|
|
49
|
+
export function resolveEnvRelativePath(relativePath, scope = resolveDefaultConfigScope()) {
|
|
50
|
+
return path.resolve(resolveEnvRoot(scope), relativePath);
|
|
51
|
+
}
|
|
52
|
+
export function resolveConfiguredEnvPath(value, scope = resolveDefaultConfigScope()) {
|
|
53
|
+
const text = String(value ?? '').trim();
|
|
54
|
+
if (!text) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
return path.isAbsolute(text) ? text : resolveEnvRelativePath(text, scope);
|
|
58
|
+
}
|
|
28
59
|
export function formatCliHomeScope(scope) {
|
|
29
60
|
return scope === 'project' ? 'project' : 'global';
|
|
30
61
|
}
|
package/dist/lib/env-auth.js
CHANGED
|
@@ -750,7 +750,7 @@ export async function resolveAccessToken(options) {
|
|
|
750
750
|
}
|
|
751
751
|
const baseUrl = options.baseUrl ?? env.baseUrl;
|
|
752
752
|
if (!baseUrl) {
|
|
753
|
-
throw new Error(`Env "${envName}" is missing a base URL. Run \`nb env add ${envName} --base-url <url>\`.`);
|
|
753
|
+
throw new Error(`Env "${envName}" is missing a base URL. Run \`nb env add ${envName} --api-base-url <url>\`.`);
|
|
754
754
|
}
|
|
755
755
|
printVerbose(`Refreshing OAuth session for env "${envName}"`);
|
|
756
756
|
return refreshOauthAccessToken({
|
|
@@ -771,7 +771,7 @@ export async function resolveServerRequestTarget(options) {
|
|
|
771
771
|
scope: options.scope,
|
|
772
772
|
});
|
|
773
773
|
if (!baseUrl) {
|
|
774
|
-
throw new Error('Missing base URL. Use --base-url or configure one with `nb env add`.');
|
|
774
|
+
throw new Error('Missing base URL. Use --api-base-url or configure one with `nb env add`.');
|
|
775
775
|
}
|
|
776
776
|
return { baseUrl, token };
|
|
777
777
|
}
|
|
@@ -785,7 +785,7 @@ export async function authenticateEnvWithOauth(options) {
|
|
|
785
785
|
? `Environment "${envName}" does not have an API base URL yet.`
|
|
786
786
|
: `Environment "${envName}" has not been set up yet.`,
|
|
787
787
|
env
|
|
788
|
-
? `Run \`nb env add ${envName} --base-url <url>\` to finish setting it up.`
|
|
788
|
+
? `Run \`nb env add ${envName} --api-base-url <url>\` to finish setting it up.`
|
|
789
789
|
: `Run \`nb env add ${envName}\` first.`,
|
|
790
790
|
]
|
|
791
791
|
.filter(Boolean)
|