@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/dist/lib/auth-store.js
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { promises as fs } from 'node:fs';
|
|
10
10
|
import path from 'node:path';
|
|
11
|
-
import { resolveCliHomeDir, resolveConfiguredEnvPath, resolveEnvRelativePath
|
|
11
|
+
import { resolveCliHomeDir, resolveConfiguredEnvPath, resolveEnvRelativePath } from './cli-home.js';
|
|
12
|
+
import { normalizeCliLocale } from './cli-locale.js';
|
|
12
13
|
import { cleanupCurrentSessionAfterEnvRemoval, resolveEffectiveCurrentEnv, setSessionCurrentEnv, } from './session-store.js';
|
|
13
14
|
function normalizeStoredEnvKind(value) {
|
|
14
15
|
const kind = String(value ?? '').trim();
|
|
@@ -24,13 +25,20 @@ function normalizeOptionalString(value) {
|
|
|
24
25
|
const normalized = String(value ?? '').trim();
|
|
25
26
|
return normalized || undefined;
|
|
26
27
|
}
|
|
28
|
+
function normalizeOptionalCliLocale(value) {
|
|
29
|
+
const normalized = normalizeOptionalString(value);
|
|
30
|
+
if (!normalized) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
return normalizeCliLocale(normalized);
|
|
34
|
+
}
|
|
27
35
|
export function readEnvApiBaseUrl(config) {
|
|
28
36
|
if (!config) {
|
|
29
37
|
return undefined;
|
|
30
38
|
}
|
|
31
|
-
return (normalizeOptionalString(config.apiBaseUrl)
|
|
32
|
-
|
|
33
|
-
|
|
39
|
+
return (normalizeOptionalString(config.apiBaseUrl) ??
|
|
40
|
+
normalizeOptionalString(config.baseUrl) ??
|
|
41
|
+
normalizeOptionalString(config.apibaseUrl));
|
|
34
42
|
}
|
|
35
43
|
export function resolveEnvKind(config) {
|
|
36
44
|
if (!config) {
|
|
@@ -70,9 +78,11 @@ function normalizeEnvConfigEntry(entry) {
|
|
|
70
78
|
}
|
|
71
79
|
function normalizeAuthConfig(config) {
|
|
72
80
|
const settings = config.settings ?? {};
|
|
81
|
+
const locale = normalizeOptionalCliLocale(settings.locale);
|
|
73
82
|
return {
|
|
74
83
|
name: config.name || config.dockerResourcePrefix,
|
|
75
84
|
settings: {
|
|
85
|
+
...(locale ? { locale } : {}),
|
|
76
86
|
...(settings.license?.pkgUrl ? { license: { pkgUrl: normalizeOptionalString(settings.license.pkgUrl) } } : {}),
|
|
77
87
|
...(settings.docker?.network || settings.docker?.containerPrefix
|
|
78
88
|
? {
|
|
@@ -84,8 +94,19 @@ function normalizeAuthConfig(config) {
|
|
|
84
94
|
},
|
|
85
95
|
}
|
|
86
96
|
: {}),
|
|
97
|
+
...(settings.bin?.docker || settings.bin?.git || settings.bin?.yarn
|
|
98
|
+
? {
|
|
99
|
+
bin: {
|
|
100
|
+
...(settings.bin?.docker ? { docker: normalizeOptionalString(settings.bin.docker) } : {}),
|
|
101
|
+
...(settings.bin?.git ? { git: normalizeOptionalString(settings.bin.git) } : {}),
|
|
102
|
+
...(settings.bin?.yarn ? { yarn: normalizeOptionalString(settings.bin.yarn) } : {}),
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
: {}),
|
|
87
106
|
},
|
|
88
|
-
lastEnv: config.lastEnv ||
|
|
107
|
+
lastEnv: config.lastEnv ||
|
|
108
|
+
config.currentEnv ||
|
|
109
|
+
'default',
|
|
89
110
|
envs: Object.fromEntries(Object.entries(config.envs || {}).map(([envName, entry]) => [envName, normalizeEnvConfigEntry(entry) ?? {}])),
|
|
90
111
|
};
|
|
91
112
|
}
|
|
@@ -222,10 +243,11 @@ export class Env {
|
|
|
222
243
|
export async function getEnv(envName, options = {}) {
|
|
223
244
|
const { config: snapshot, ...loadOptions } = options;
|
|
224
245
|
const config = snapshot ?? (await loadAuthConfig(loadOptions));
|
|
225
|
-
const resolved = envName?.trim() ||
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
246
|
+
const resolved = envName?.trim() ||
|
|
247
|
+
(await resolveEffectiveCurrentEnv(Object.keys(config.envs).sort(), {
|
|
248
|
+
scope: loadOptions.scope,
|
|
249
|
+
lastEnv: config.lastEnv,
|
|
250
|
+
}));
|
|
229
251
|
const envConfig = config.envs[resolved];
|
|
230
252
|
if (!envConfig) {
|
|
231
253
|
return undefined;
|
|
@@ -260,37 +282,40 @@ async function writeEnv(envName, updater, options = {}) {
|
|
|
260
282
|
await saveAuthConfig(config, options);
|
|
261
283
|
}
|
|
262
284
|
function normalizeConfiguredAuthType(value) {
|
|
263
|
-
return value === 'token' || value === 'oauth' ? value : undefined;
|
|
285
|
+
return value === 'basic' || value === 'token' || value === 'oauth' ? value : undefined;
|
|
264
286
|
}
|
|
265
287
|
export function resolveConfiguredAuthType(config) {
|
|
266
288
|
return normalizeConfiguredAuthType(config?.authType) ?? normalizeConfiguredAuthType(config?.auth?.type);
|
|
267
289
|
}
|
|
268
290
|
export async function upsertEnv(envName, config, options = {}) {
|
|
269
291
|
await writeEnv(envName, (previous) => {
|
|
270
|
-
const { apiBaseUrl: _apiBaseUrl, baseUrl: _baseUrl, apibaseUrl: _legacyApiBaseUrl, accessToken, authType, ...rest } = config;
|
|
292
|
+
const { apiBaseUrl: _apiBaseUrl, baseUrl: _baseUrl, apibaseUrl: _legacyApiBaseUrl, accessToken, authType, authUsername, ...rest } = config;
|
|
271
293
|
const nextApiBaseUrl = readEnvApiBaseUrl(config);
|
|
272
294
|
const previousApiBaseUrl = readEnvApiBaseUrl(previous);
|
|
273
295
|
const baseUrlChanged = previousApiBaseUrl !== nextApiBaseUrl;
|
|
274
296
|
const previousAuthType = resolveConfiguredAuthType(previous);
|
|
275
297
|
const requestedAuthType = normalizeConfiguredAuthType(authType);
|
|
276
298
|
const nextAuthType = requestedAuthType ?? (accessToken ? 'token' : previousAuthType);
|
|
299
|
+
const nextAuthUsername = nextAuthType === 'basic' ? normalizeOptionalString(authUsername) ?? previous?.authUsername : undefined;
|
|
277
300
|
const nextAuth = accessToken
|
|
278
301
|
? {
|
|
279
302
|
type: 'token',
|
|
280
303
|
accessToken,
|
|
281
304
|
}
|
|
282
|
-
: nextAuthType === '
|
|
283
|
-
?
|
|
284
|
-
:
|
|
305
|
+
: nextAuthType === 'oauth' && !baseUrlChanged && previous?.auth?.type === 'oauth'
|
|
306
|
+
? previous.auth
|
|
307
|
+
: undefined;
|
|
285
308
|
const authChanged = !areAuthConfigsEquivalent(previous?.auth, nextAuth);
|
|
286
309
|
const authTypeChanged = previousAuthType !== nextAuthType;
|
|
310
|
+
const authUsernameChanged = previous?.authUsername !== nextAuthUsername;
|
|
287
311
|
return {
|
|
288
312
|
...previous,
|
|
289
313
|
apiBaseUrl: nextApiBaseUrl,
|
|
290
314
|
authType: nextAuthType,
|
|
315
|
+
authUsername: nextAuthUsername,
|
|
291
316
|
auth: nextAuth,
|
|
292
317
|
...rest,
|
|
293
|
-
runtime: baseUrlChanged || authChanged || authTypeChanged ? undefined : previous?.runtime,
|
|
318
|
+
runtime: baseUrlChanged || authChanged || authTypeChanged || authUsernameChanged ? undefined : previous?.runtime,
|
|
294
319
|
};
|
|
295
320
|
}, options);
|
|
296
321
|
}
|
|
@@ -302,22 +327,25 @@ export async function updateEnvConnection(envName, updates, options = {}) {
|
|
|
302
327
|
const previousAuthType = resolveConfiguredAuthType(previous);
|
|
303
328
|
const requestedAuthType = normalizeConfiguredAuthType(updates.authType);
|
|
304
329
|
const nextAuthType = requestedAuthType ?? (updates.accessToken ? 'token' : previousAuthType);
|
|
330
|
+
const nextAuthUsername = nextAuthType === 'basic' ? normalizeOptionalString(updates.authUsername) ?? previous?.authUsername : undefined;
|
|
305
331
|
const nextAuth = updates.accessToken
|
|
306
332
|
? {
|
|
307
333
|
type: 'token',
|
|
308
334
|
accessToken: updates.accessToken,
|
|
309
335
|
}
|
|
310
|
-
: nextAuthType === '
|
|
311
|
-
?
|
|
312
|
-
:
|
|
336
|
+
: nextAuthType === 'oauth' && !baseUrlChanged && previous?.auth?.type === 'oauth'
|
|
337
|
+
? previous.auth
|
|
338
|
+
: undefined;
|
|
313
339
|
const authChanged = !areAuthConfigsEquivalent(previous?.auth, nextAuth);
|
|
314
340
|
const authTypeChanged = previousAuthType !== nextAuthType;
|
|
341
|
+
const authUsernameChanged = previous?.authUsername !== nextAuthUsername;
|
|
315
342
|
return {
|
|
316
343
|
...previous,
|
|
317
344
|
...(nextApiBaseUrl !== undefined ? { apiBaseUrl: nextApiBaseUrl } : {}),
|
|
318
345
|
authType: nextAuthType,
|
|
346
|
+
authUsername: nextAuthUsername,
|
|
319
347
|
auth: nextAuth,
|
|
320
|
-
runtime: baseUrlChanged || authChanged || authTypeChanged ? undefined : previous?.runtime,
|
|
348
|
+
runtime: baseUrlChanged || authChanged || authTypeChanged || authUsernameChanged ? undefined : previous?.runtime,
|
|
321
349
|
};
|
|
322
350
|
}, options);
|
|
323
351
|
}
|
package/dist/lib/cli-config.js
CHANGED
|
@@ -8,13 +8,21 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { loadExactAuthConfig, saveAuthConfig } from './auth-store.js';
|
|
10
10
|
import { resolveDefaultConfigScope } from './cli-home.js';
|
|
11
|
+
import { CLI_LOCALE_FLAG_OPTIONS, normalizeCliLocale, resolveCliLocale } from './cli-locale.js';
|
|
11
12
|
export const DEFAULT_LICENSE_PKG_URL = 'https://pkg.nocobase.com/';
|
|
12
13
|
export const DEFAULT_DOCKER_NETWORK = 'nocobase';
|
|
13
14
|
export const DEFAULT_DOCKER_CONTAINER_PREFIX = 'nb';
|
|
15
|
+
export const DEFAULT_DOCKER_BIN = 'docker';
|
|
16
|
+
export const DEFAULT_GIT_BIN = 'git';
|
|
17
|
+
export const DEFAULT_YARN_BIN = 'yarn';
|
|
14
18
|
export const SUPPORTED_CLI_CONFIG_KEYS = [
|
|
19
|
+
'locale',
|
|
15
20
|
'license.pkg-url',
|
|
16
21
|
'docker.network',
|
|
17
22
|
'docker.container-prefix',
|
|
23
|
+
'bin.docker',
|
|
24
|
+
'bin.git',
|
|
25
|
+
'bin.yarn',
|
|
18
26
|
];
|
|
19
27
|
function trimValue(value) {
|
|
20
28
|
const text = String(value ?? '').trim();
|
|
@@ -36,11 +44,16 @@ export function assertSupportedCliConfigKey(value) {
|
|
|
36
44
|
}
|
|
37
45
|
function cloneSettings(config) {
|
|
38
46
|
return {
|
|
47
|
+
...(config.settings?.locale ? { locale: trimValue(config.settings.locale) } : {}),
|
|
39
48
|
license: config.settings?.license ? { ...config.settings.license } : undefined,
|
|
40
49
|
docker: config.settings?.docker ? { ...config.settings.docker } : undefined,
|
|
50
|
+
bin: config.settings?.bin ? { ...config.settings.bin } : undefined,
|
|
41
51
|
};
|
|
42
52
|
}
|
|
43
53
|
function pruneSettings(config) {
|
|
54
|
+
if (config.settings && !trimValue(config.settings.locale)) {
|
|
55
|
+
delete config.settings.locale;
|
|
56
|
+
}
|
|
44
57
|
const license = config.settings?.license;
|
|
45
58
|
if (license && !trimValue(license.pkgUrl)) {
|
|
46
59
|
delete config.settings?.license;
|
|
@@ -49,34 +62,56 @@ function pruneSettings(config) {
|
|
|
49
62
|
if (docker && !trimValue(docker.network) && !trimValue(docker.containerPrefix)) {
|
|
50
63
|
delete config.settings?.docker;
|
|
51
64
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
65
|
+
const bin = config.settings?.bin;
|
|
66
|
+
if (bin && !trimValue(bin.docker) && !trimValue(bin.git) && !trimValue(bin.yarn)) {
|
|
67
|
+
delete config.settings?.bin;
|
|
68
|
+
}
|
|
69
|
+
if (config.settings &&
|
|
70
|
+
!config.settings.locale &&
|
|
71
|
+
!config.settings.license &&
|
|
72
|
+
!config.settings.docker &&
|
|
73
|
+
!config.settings.bin) {
|
|
55
74
|
delete config.settings;
|
|
56
75
|
}
|
|
57
76
|
}
|
|
58
77
|
export function getExplicitCliConfigValue(config, key) {
|
|
59
78
|
switch (key) {
|
|
79
|
+
case 'locale':
|
|
80
|
+
return trimValue(config.settings?.locale);
|
|
60
81
|
case 'license.pkg-url':
|
|
61
82
|
return trimValue(config.settings?.license?.pkgUrl);
|
|
62
83
|
case 'docker.network':
|
|
63
84
|
return trimValue(config.settings?.docker?.network);
|
|
64
85
|
case 'docker.container-prefix':
|
|
65
86
|
return trimValue(config.settings?.docker?.containerPrefix);
|
|
87
|
+
case 'bin.docker':
|
|
88
|
+
return trimValue(config.settings?.bin?.docker);
|
|
89
|
+
case 'bin.git':
|
|
90
|
+
return trimValue(config.settings?.bin?.git);
|
|
91
|
+
case 'bin.yarn':
|
|
92
|
+
return trimValue(config.settings?.bin?.yarn);
|
|
66
93
|
}
|
|
67
94
|
}
|
|
68
95
|
export function getEffectiveCliConfigValue(config, key) {
|
|
69
96
|
const explicit = getExplicitCliConfigValue(config, key);
|
|
70
|
-
if (explicit) {
|
|
97
|
+
if (explicit && key !== 'locale') {
|
|
71
98
|
return explicit;
|
|
72
99
|
}
|
|
73
100
|
switch (key) {
|
|
101
|
+
case 'locale':
|
|
102
|
+
return resolveCliLocale(undefined, { configuredLocale: trimValue(config.settings?.locale) });
|
|
74
103
|
case 'license.pkg-url':
|
|
75
104
|
return DEFAULT_LICENSE_PKG_URL;
|
|
76
105
|
case 'docker.network':
|
|
77
106
|
return trimValue(config.name) || DEFAULT_DOCKER_NETWORK;
|
|
78
107
|
case 'docker.container-prefix':
|
|
79
108
|
return trimValue(config.name) || DEFAULT_DOCKER_CONTAINER_PREFIX;
|
|
109
|
+
case 'bin.docker':
|
|
110
|
+
return DEFAULT_DOCKER_BIN;
|
|
111
|
+
case 'bin.git':
|
|
112
|
+
return DEFAULT_GIT_BIN;
|
|
113
|
+
case 'bin.yarn':
|
|
114
|
+
return DEFAULT_YARN_BIN;
|
|
80
115
|
}
|
|
81
116
|
}
|
|
82
117
|
export function normalizeCliConfigValue(key, value) {
|
|
@@ -87,6 +122,13 @@ export function normalizeCliConfigValue(key, value) {
|
|
|
87
122
|
if (key === 'license.pkg-url') {
|
|
88
123
|
return normalized.replace(/\/+$/, '') + '/';
|
|
89
124
|
}
|
|
125
|
+
if (key === 'locale') {
|
|
126
|
+
const locale = normalizeCliLocale(normalized);
|
|
127
|
+
if (!locale) {
|
|
128
|
+
throw new Error(`Config key "${key}" must be one of: ${CLI_LOCALE_FLAG_OPTIONS.join(', ')}`);
|
|
129
|
+
}
|
|
130
|
+
return locale;
|
|
131
|
+
}
|
|
90
132
|
return normalized;
|
|
91
133
|
}
|
|
92
134
|
export async function loadCliConfig(options = {}) {
|
|
@@ -113,6 +155,9 @@ export async function setCliConfigValue(key, value, options = {}) {
|
|
|
113
155
|
const normalized = normalizeCliConfigValue(key, value);
|
|
114
156
|
config.settings = cloneSettings(config);
|
|
115
157
|
switch (key) {
|
|
158
|
+
case 'locale':
|
|
159
|
+
config.settings.locale = normalized;
|
|
160
|
+
break;
|
|
116
161
|
case 'license.pkg-url':
|
|
117
162
|
config.settings.license = {
|
|
118
163
|
...(config.settings.license ?? {}),
|
|
@@ -131,6 +176,24 @@ export async function setCliConfigValue(key, value, options = {}) {
|
|
|
131
176
|
containerPrefix: normalized,
|
|
132
177
|
};
|
|
133
178
|
break;
|
|
179
|
+
case 'bin.docker':
|
|
180
|
+
config.settings.bin = {
|
|
181
|
+
...(config.settings.bin ?? {}),
|
|
182
|
+
docker: normalized,
|
|
183
|
+
};
|
|
184
|
+
break;
|
|
185
|
+
case 'bin.git':
|
|
186
|
+
config.settings.bin = {
|
|
187
|
+
...(config.settings.bin ?? {}),
|
|
188
|
+
git: normalized,
|
|
189
|
+
};
|
|
190
|
+
break;
|
|
191
|
+
case 'bin.yarn':
|
|
192
|
+
config.settings.bin = {
|
|
193
|
+
...(config.settings.bin ?? {}),
|
|
194
|
+
yarn: normalized,
|
|
195
|
+
};
|
|
196
|
+
break;
|
|
134
197
|
}
|
|
135
198
|
pruneSettings(config);
|
|
136
199
|
await saveAuthConfig(config, scope);
|
|
@@ -145,6 +208,9 @@ export async function deleteCliConfigValue(key, options = {}) {
|
|
|
145
208
|
}
|
|
146
209
|
config.settings = cloneSettings(config);
|
|
147
210
|
switch (key) {
|
|
211
|
+
case 'locale':
|
|
212
|
+
delete config.settings.locale;
|
|
213
|
+
break;
|
|
148
214
|
case 'license.pkg-url':
|
|
149
215
|
if (config.settings.license) {
|
|
150
216
|
delete config.settings.license.pkgUrl;
|
|
@@ -160,6 +226,21 @@ export async function deleteCliConfigValue(key, options = {}) {
|
|
|
160
226
|
delete config.settings.docker.containerPrefix;
|
|
161
227
|
}
|
|
162
228
|
break;
|
|
229
|
+
case 'bin.docker':
|
|
230
|
+
if (config.settings.bin) {
|
|
231
|
+
delete config.settings.bin.docker;
|
|
232
|
+
}
|
|
233
|
+
break;
|
|
234
|
+
case 'bin.git':
|
|
235
|
+
if (config.settings.bin) {
|
|
236
|
+
delete config.settings.bin.git;
|
|
237
|
+
}
|
|
238
|
+
break;
|
|
239
|
+
case 'bin.yarn':
|
|
240
|
+
if (config.settings.bin) {
|
|
241
|
+
delete config.settings.bin.yarn;
|
|
242
|
+
}
|
|
243
|
+
break;
|
|
163
244
|
}
|
|
164
245
|
pruneSettings(config);
|
|
165
246
|
await saveAuthConfig(config, scope);
|
|
@@ -174,3 +255,17 @@ export async function resolveDockerContainerPrefix(options = {}) {
|
|
|
174
255
|
export async function resolveLicensePkgUrlFromConfig(options = {}) {
|
|
175
256
|
return await getCliConfigValue('license.pkg-url', options);
|
|
176
257
|
}
|
|
258
|
+
const CONFIGURABLE_COMMAND_KEYS = {
|
|
259
|
+
docker: 'bin.docker',
|
|
260
|
+
git: 'bin.git',
|
|
261
|
+
yarn: 'bin.yarn',
|
|
262
|
+
};
|
|
263
|
+
export function isConfigurableCommandName(value) {
|
|
264
|
+
return Object.prototype.hasOwnProperty.call(CONFIGURABLE_COMMAND_KEYS, value);
|
|
265
|
+
}
|
|
266
|
+
export async function resolveConfiguredCommandName(commandName, options = {}) {
|
|
267
|
+
if (!isConfigurableCommandName(commandName)) {
|
|
268
|
+
return commandName;
|
|
269
|
+
}
|
|
270
|
+
return await getCliConfigValue(CONFIGURABLE_COMMAND_KEYS[commandName], options);
|
|
271
|
+
}
|
package/dist/lib/cli-locale.js
CHANGED
|
@@ -9,12 +9,13 @@
|
|
|
9
9
|
import { readFileSync } from 'node:fs';
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import { resolveCliHomeDir } from './cli-home.js';
|
|
12
13
|
export const SUPPORTED_CLI_LOCALES = ['en-US', 'zh-CN'];
|
|
13
14
|
export const CLI_LOCALE_FLAG_OPTIONS = [...SUPPORTED_CLI_LOCALES];
|
|
14
15
|
export const CLI_LOCALE_FLAG_DESCRIPTION = 'Language for CLI prompts and the local setup UI.';
|
|
15
16
|
const DEFAULT_CLI_LOCALE = 'en-US';
|
|
16
17
|
const localeCache = {};
|
|
17
|
-
function normalizeCliLocale(value) {
|
|
18
|
+
export function normalizeCliLocale(value) {
|
|
18
19
|
const raw = String(value ?? '').trim();
|
|
19
20
|
if (!raw) {
|
|
20
21
|
return undefined;
|
|
@@ -28,6 +29,17 @@ function normalizeCliLocale(value) {
|
|
|
28
29
|
}
|
|
29
30
|
return undefined;
|
|
30
31
|
}
|
|
32
|
+
function readConfiguredCliLocale() {
|
|
33
|
+
try {
|
|
34
|
+
const configPath = path.join(resolveCliHomeDir(), 'config.json');
|
|
35
|
+
const content = readFileSync(configPath, 'utf8');
|
|
36
|
+
const parsed = JSON.parse(content);
|
|
37
|
+
return normalizeCliLocale(parsed.settings?.locale === undefined ? undefined : String(parsed.settings.locale));
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
31
43
|
function loadLocaleMessages(locale) {
|
|
32
44
|
if (localeCache[locale]) {
|
|
33
45
|
return localeCache[locale];
|
|
@@ -65,9 +77,11 @@ function interpolateTemplate(template, values) {
|
|
|
65
77
|
return value === undefined || value === null ? '' : String(value);
|
|
66
78
|
});
|
|
67
79
|
}
|
|
68
|
-
export function detectCliLocale() {
|
|
80
|
+
export function detectCliLocale(configuredLocale) {
|
|
81
|
+
const resolvedConfiguredLocale = configuredLocale ?? readConfiguredCliLocale();
|
|
69
82
|
const candidates = [
|
|
70
83
|
process.env.NB_LOCALE,
|
|
84
|
+
resolvedConfiguredLocale,
|
|
71
85
|
process.env.LC_ALL,
|
|
72
86
|
process.env.LC_MESSAGES,
|
|
73
87
|
process.env.LANG,
|
|
@@ -81,8 +95,8 @@ export function detectCliLocale() {
|
|
|
81
95
|
}
|
|
82
96
|
return DEFAULT_CLI_LOCALE;
|
|
83
97
|
}
|
|
84
|
-
export function resolveCliLocale(preferred) {
|
|
85
|
-
return normalizeCliLocale(preferred) ?? detectCliLocale();
|
|
98
|
+
export function resolveCliLocale(preferred, options) {
|
|
99
|
+
return normalizeCliLocale(preferred) ?? detectCliLocale(options?.configuredLocale);
|
|
86
100
|
}
|
|
87
101
|
export function applyCliLocale(preferred) {
|
|
88
102
|
const locale = resolveCliLocale(preferred);
|
|
@@ -111,9 +125,7 @@ export function localeText(key, values, fallback) {
|
|
|
111
125
|
};
|
|
112
126
|
}
|
|
113
127
|
export function isLocalizedTextDef(value) {
|
|
114
|
-
return Boolean(value
|
|
115
|
-
&& typeof value === 'object'
|
|
116
|
-
&& typeof value.key === 'string');
|
|
128
|
+
return Boolean(value && typeof value === 'object' && typeof value.key === 'string');
|
|
117
129
|
}
|
|
118
130
|
export function resolveLocalizedText(text, options) {
|
|
119
131
|
if (text === undefined) {
|
|
@@ -10,6 +10,7 @@ import { translateCli } from "./cli-locale.js";
|
|
|
10
10
|
import { validateTcpPort } from "./prompt-validators.js";
|
|
11
11
|
const DB_CONNECTION_TIMEOUT_MS = 5_000;
|
|
12
12
|
const externalDbValidationCache = new Map();
|
|
13
|
+
const mysqlLowerCaseTableNamesCache = new Map();
|
|
13
14
|
function trimPromptValue(value) {
|
|
14
15
|
return String(value ?? '').trim();
|
|
15
16
|
}
|
|
@@ -117,6 +118,36 @@ async function checkMysqlFamilyConnection(config) {
|
|
|
117
118
|
await Promise.resolve(connection.end()).catch(() => undefined);
|
|
118
119
|
}
|
|
119
120
|
}
|
|
121
|
+
async function readMysqlFamilyLowerCaseTableNames(config) {
|
|
122
|
+
const { default: mysql } = await import('mysql2/promise');
|
|
123
|
+
const connection = await mysql.createConnection({
|
|
124
|
+
host: config.host,
|
|
125
|
+
port: config.port,
|
|
126
|
+
user: config.user,
|
|
127
|
+
password: config.password,
|
|
128
|
+
database: config.database,
|
|
129
|
+
connectTimeout: DB_CONNECTION_TIMEOUT_MS,
|
|
130
|
+
});
|
|
131
|
+
try {
|
|
132
|
+
const [rows] = await connection.query(`SHOW VARIABLES LIKE 'lower_case_table_names'`);
|
|
133
|
+
if (!Array.isArray(rows)) {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
for (const row of rows) {
|
|
137
|
+
if (!row || typeof row !== 'object') {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const value = String(row.Value ?? '').trim();
|
|
141
|
+
if (value === '0' || value === '1' || value === '2') {
|
|
142
|
+
return value;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
await Promise.resolve(connection.end()).catch(() => undefined);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
120
151
|
async function performExternalDbConnectionCheck(config) {
|
|
121
152
|
try {
|
|
122
153
|
switch (config.dialect) {
|
|
@@ -146,6 +177,19 @@ export async function checkExternalDbConnection(config) {
|
|
|
146
177
|
externalDbValidationCache.set(cacheKey, pending);
|
|
147
178
|
return await pending;
|
|
148
179
|
}
|
|
180
|
+
async function readMysqlLowerCaseTableNamesMode(config) {
|
|
181
|
+
if (config.dialect !== 'mysql' && config.dialect !== 'mariadb') {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
const cacheKey = buildValidationCacheKey(config);
|
|
185
|
+
const cached = mysqlLowerCaseTableNamesCache.get(cacheKey);
|
|
186
|
+
if (cached) {
|
|
187
|
+
return await cached;
|
|
188
|
+
}
|
|
189
|
+
const pending = readMysqlFamilyLowerCaseTableNames(config);
|
|
190
|
+
mysqlLowerCaseTableNamesCache.set(cacheKey, pending);
|
|
191
|
+
return await pending;
|
|
192
|
+
}
|
|
149
193
|
export async function validateExternalDbConfig(values) {
|
|
150
194
|
const config = readExternalDbConnectionConfig(values);
|
|
151
195
|
if (!config) {
|
|
@@ -153,6 +197,23 @@ export async function validateExternalDbConfig(values) {
|
|
|
153
197
|
}
|
|
154
198
|
return await checkExternalDbConnection(config);
|
|
155
199
|
}
|
|
200
|
+
export async function validateMysqlLowerCaseTableNamesCompatibility(values) {
|
|
201
|
+
const config = readExternalDbConnectionConfig(values);
|
|
202
|
+
if (!config || (config.dialect !== 'mysql' && config.dialect !== 'mariadb')) {
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
const mode = await readMysqlLowerCaseTableNamesMode(config);
|
|
207
|
+
if (mode === '1' && values.dbUnderscored !== true) {
|
|
208
|
+
return translateCli('validators.dbConnection.lowerCaseTableNamesRequiresUnderscored');
|
|
209
|
+
}
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
return formatDbConnectionError(config, error);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
156
216
|
export function clearExternalDbValidationCache() {
|
|
157
217
|
externalDbValidationCache.clear();
|
|
218
|
+
mysqlLowerCaseTableNamesCache.clear();
|
|
158
219
|
}
|
package/dist/lib/env-auth.js
CHANGED
|
@@ -34,6 +34,28 @@ export function getOauthResource(issuerOrBaseUrl) {
|
|
|
34
34
|
export function getDefaultOauthScope() {
|
|
35
35
|
return DEFAULT_OAUTH_SCOPE;
|
|
36
36
|
}
|
|
37
|
+
function formatApiAuthError(prefix, data, fallbackStatus) {
|
|
38
|
+
if (typeof data === 'string' && data.trim()) {
|
|
39
|
+
return `${prefix}: ${data}`;
|
|
40
|
+
}
|
|
41
|
+
const errors = Array.isArray(data?.errors) ? data.errors : [];
|
|
42
|
+
if (errors.length > 0) {
|
|
43
|
+
const details = errors
|
|
44
|
+
.map((entry) => {
|
|
45
|
+
const code = entry?.code ? `[${entry.code}] ` : '';
|
|
46
|
+
return `${code}${entry?.message ?? 'Authentication failed.'}`;
|
|
47
|
+
})
|
|
48
|
+
.join('; ');
|
|
49
|
+
return `${prefix}: ${details}`;
|
|
50
|
+
}
|
|
51
|
+
if (typeof data?.error?.message === 'string' && data.error.message.trim()) {
|
|
52
|
+
return `${prefix}: ${data.error.message}`;
|
|
53
|
+
}
|
|
54
|
+
if (typeof fallbackStatus === 'number') {
|
|
55
|
+
return `${prefix}: HTTP ${fallbackStatus}`;
|
|
56
|
+
}
|
|
57
|
+
return prefix;
|
|
58
|
+
}
|
|
37
59
|
export function isOauthAccessTokenExpired(auth, now = Date.now()) {
|
|
38
60
|
if (!auth.expiresAt) {
|
|
39
61
|
return false;
|
|
@@ -775,6 +797,63 @@ export async function resolveServerRequestTarget(options) {
|
|
|
775
797
|
}
|
|
776
798
|
return { baseUrl, token };
|
|
777
799
|
}
|
|
800
|
+
export async function authenticateEnvWithBasic(options) {
|
|
801
|
+
const envName = options.envName ?? (await getCurrentEnvName({ scope: options.scope }));
|
|
802
|
+
const env = await getEnv(envName, { scope: options.scope });
|
|
803
|
+
const baseUrl = env?.baseUrl;
|
|
804
|
+
if (!baseUrl) {
|
|
805
|
+
throw new Error([
|
|
806
|
+
env
|
|
807
|
+
? `Environment "${envName}" does not have an API base URL yet.`
|
|
808
|
+
: `Environment "${envName}" has not been set up yet.`,
|
|
809
|
+
env
|
|
810
|
+
? `Run \`nb env add ${envName} --api-base-url <url>\` to finish setting it up.`
|
|
811
|
+
: `Run \`nb env add ${envName}\` first.`,
|
|
812
|
+
]
|
|
813
|
+
.filter(Boolean)
|
|
814
|
+
.join('\n'));
|
|
815
|
+
}
|
|
816
|
+
const loginUrl = `${normalizeBaseUrl(baseUrl)}/auth:signIn`;
|
|
817
|
+
let response;
|
|
818
|
+
try {
|
|
819
|
+
response = await fetchWithOauthRetry(loginUrl, {
|
|
820
|
+
method: 'POST',
|
|
821
|
+
headers: {
|
|
822
|
+
accept: 'application/json',
|
|
823
|
+
'content-type': 'application/json',
|
|
824
|
+
'x-authenticator': 'basic',
|
|
825
|
+
},
|
|
826
|
+
body: JSON.stringify({
|
|
827
|
+
account: options.username,
|
|
828
|
+
password: options.password,
|
|
829
|
+
}),
|
|
830
|
+
}, {
|
|
831
|
+
operation: 'Signing in with basic credentials',
|
|
832
|
+
onRetry: (message) => updateTask(message),
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
catch (error) {
|
|
836
|
+
throw new Error(formatOauthFetchFailure('Failed to sign in with basic credentials.', {
|
|
837
|
+
envName,
|
|
838
|
+
baseUrl,
|
|
839
|
+
url: loginUrl,
|
|
840
|
+
rawMessage: error?.message,
|
|
841
|
+
}));
|
|
842
|
+
}
|
|
843
|
+
const data = await parseJsonResponse(response);
|
|
844
|
+
if (!response.ok) {
|
|
845
|
+
throw new Error(formatApiAuthError('Basic sign-in failed', data, response.status));
|
|
846
|
+
}
|
|
847
|
+
const token = typeof data?.data?.token === 'string' && data.data.token.trim()
|
|
848
|
+
? data.data.token.trim()
|
|
849
|
+
: typeof data?.token === 'string' && data.token.trim()
|
|
850
|
+
? data.token.trim()
|
|
851
|
+
: '';
|
|
852
|
+
if (!token) {
|
|
853
|
+
throw new Error('Basic sign-in succeeded but no token was returned.');
|
|
854
|
+
}
|
|
855
|
+
return token;
|
|
856
|
+
}
|
|
778
857
|
export async function authenticateEnvWithOauth(options) {
|
|
779
858
|
const envName = options.envName ?? (await getCurrentEnvName({ scope: options.scope }));
|
|
780
859
|
const env = await getEnv(envName, { scope: options.scope });
|
package/dist/lib/env-config.js
CHANGED
|
@@ -19,6 +19,7 @@ const STRING_ENV_CONFIG_KEYS = [
|
|
|
19
19
|
'appPort',
|
|
20
20
|
'appKey',
|
|
21
21
|
'timezone',
|
|
22
|
+
'authUsername',
|
|
22
23
|
'dbDialect',
|
|
23
24
|
'builtinDbImage',
|
|
24
25
|
'dbHost',
|
|
@@ -83,11 +84,11 @@ export function buildStoredEnvConfig(input) {
|
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
const authType = trimConfigValue(input.authType);
|
|
86
|
-
if (authType === 'token' || authType === 'oauth') {
|
|
87
|
+
if (authType === 'basic' || authType === 'token' || authType === 'oauth') {
|
|
87
88
|
envConfig.authType = authType;
|
|
88
89
|
}
|
|
89
90
|
const accessToken = trimConfigValue(input.accessToken);
|
|
90
|
-
if (authType === 'token' && accessToken) {
|
|
91
|
+
if ((authType === 'basic' || authType === 'token') && accessToken) {
|
|
91
92
|
envConfig.accessToken = accessToken;
|
|
92
93
|
}
|
|
93
94
|
return envConfig;
|