@nocobase/cli 2.1.0-beta.35 → 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/app/upgrade.js +38 -16
- package/dist/commands/backup/create.js +147 -0
- package/dist/commands/backup/index.js +20 -0
- package/dist/commands/backup/restore.js +105 -0
- 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 +129 -15
- package/dist/commands/env/auth.js +145 -12
- 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 +254 -136
- package/dist/commands/install.js +447 -272
- package/dist/commands/license/activate.js +6 -4
- package/dist/commands/source/publish.js +17 -0
- package/dist/commands/v1.js +210 -0
- package/dist/lib/app-managed-resources.js +20 -1
- package/dist/lib/app-runtime.js +13 -4
- package/dist/lib/auth-store.js +69 -18
- package/dist/lib/backup.js +171 -0
- package/dist/lib/bootstrap.js +23 -13
- 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 +8 -1
- package/dist/lib/prompt-validators.js +23 -5
- package/dist/lib/prompt-web-ui.js +143 -19
- package/dist/lib/run-npm.js +166 -30
- package/dist/lib/skills-manager.js +74 -4
- package/dist/lib/source-publish.js +20 -1
- package/dist/lib/source-registry.js +2 -2
- package/dist/locale/en-US.json +36 -5
- package/dist/locale/zh-CN.json +36 -5
- package/package.json +6 -3
|
@@ -21,11 +21,11 @@ async function promptActivationMode() {
|
|
|
21
21
|
return await select({
|
|
22
22
|
message: 'How do you want to activate the license?',
|
|
23
23
|
choices: [
|
|
24
|
-
{ value: 'key', name: 'Use an existing license key' },
|
|
25
24
|
{ value: 'online', name: 'Request and activate a license online' },
|
|
25
|
+
{ value: 'key', name: 'Use an existing license key' },
|
|
26
26
|
{ value: 'cancel', name: 'Cancel' },
|
|
27
27
|
],
|
|
28
|
-
default: '
|
|
28
|
+
default: 'online',
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
31
|
catch {
|
|
@@ -70,7 +70,7 @@ async function promptLicenseKeyInput() {
|
|
|
70
70
|
return {};
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
-
async function promptOnlineActivationInput(initial) {
|
|
73
|
+
async function promptOnlineActivationInput(initial, defaultAppName) {
|
|
74
74
|
let account = String(initial.account ?? '').trim();
|
|
75
75
|
if (!account) {
|
|
76
76
|
try {
|
|
@@ -107,8 +107,10 @@ async function promptOnlineActivationInput(initial) {
|
|
|
107
107
|
let appName = String(initial.appName ?? '').trim();
|
|
108
108
|
if (!appName) {
|
|
109
109
|
try {
|
|
110
|
+
const resolvedDefaultAppName = String(defaultAppName ?? '').trim();
|
|
110
111
|
const answer = await input({
|
|
111
112
|
message: 'Application name',
|
|
113
|
+
default: resolvedDefaultAppName || undefined,
|
|
112
114
|
validate: (value) => String(value ?? '').trim() ? true : 'Application name is required.',
|
|
113
115
|
});
|
|
114
116
|
appName = String(answer ?? '').trim();
|
|
@@ -259,7 +261,7 @@ export default class LicenseActivate extends Command {
|
|
|
259
261
|
if (!isInteractiveTerminal()) {
|
|
260
262
|
this.error('Online activation requires --account, --password, and --desc when not using a TTY.');
|
|
261
263
|
}
|
|
262
|
-
const prompted = await promptOnlineActivationInput(initialOnline);
|
|
264
|
+
const prompted = await promptOnlineActivationInput(initialOnline, runtime.envName);
|
|
263
265
|
if (!prompted) {
|
|
264
266
|
return;
|
|
265
267
|
}
|
|
@@ -10,6 +10,11 @@ import { Command, Flags } from '@oclif/core';
|
|
|
10
10
|
import { buildSuggestedInitCommand, publishSourceSnapshot } from '../../lib/source-publish.js';
|
|
11
11
|
import { failTask, printInfo, startTask, succeedTask } from '../../lib/ui.js';
|
|
12
12
|
function formatPublishFailure(message) {
|
|
13
|
+
if (message.includes('The specified --cwd does not exist:')
|
|
14
|
+
|| message.includes('The specified --cwd is not a directory:')
|
|
15
|
+
|| message.includes('Couldn\'t find a NocoBase source project from --cwd:')) {
|
|
16
|
+
return message;
|
|
17
|
+
}
|
|
13
18
|
return [
|
|
14
19
|
'Couldn\'t publish a source snapshot.',
|
|
15
20
|
'Check that Docker is running, the target npm registry is reachable, and the current directory is a NocoBase source repo.',
|
|
@@ -20,6 +25,8 @@ export default class SourcePublish extends Command {
|
|
|
20
25
|
static description = 'Publish the current NocoBase source repo as a snapshot version to an npm registry for install testing.';
|
|
21
26
|
static examples = [
|
|
22
27
|
'<%= config.bin %> <%= command.id %> --snapshot',
|
|
28
|
+
'<%= config.bin %> <%= command.id %> --snapshot --no-build',
|
|
29
|
+
'<%= config.bin %> <%= command.id %> --snapshot --build-dts',
|
|
23
30
|
'<%= config.bin %> <%= command.id %> --snapshot --cwd /path/to/nocobase/source',
|
|
24
31
|
'<%= config.bin %> <%= command.id %> --snapshot --npm-registry=http://127.0.0.1:4873',
|
|
25
32
|
'<%= config.bin %> <%= command.id %> --snapshot --json',
|
|
@@ -38,6 +45,14 @@ export default class SourcePublish extends Command {
|
|
|
38
45
|
description: 'Source repository path. Defaults to the nearest detected NocoBase source root from the current working directory',
|
|
39
46
|
required: false,
|
|
40
47
|
}),
|
|
48
|
+
'no-build': Flags.boolean({
|
|
49
|
+
description: 'Skip building the source repo before snapshot versioning and publish',
|
|
50
|
+
default: false,
|
|
51
|
+
}),
|
|
52
|
+
'build-dts': Flags.boolean({
|
|
53
|
+
description: 'Generate TypeScript declaration files during the source build',
|
|
54
|
+
default: false,
|
|
55
|
+
}),
|
|
41
56
|
json: Flags.boolean({
|
|
42
57
|
description: 'Print the publish result as JSON',
|
|
43
58
|
default: false,
|
|
@@ -59,6 +74,8 @@ export default class SourcePublish extends Command {
|
|
|
59
74
|
const result = await publishSourceSnapshot({
|
|
60
75
|
cwd: flags.cwd,
|
|
61
76
|
npmRegistry: flags['npm-registry'],
|
|
77
|
+
build: !flags['no-build'],
|
|
78
|
+
buildDts: flags['build-dts'],
|
|
62
79
|
verbose: flags.verbose,
|
|
63
80
|
});
|
|
64
81
|
if (flags.json) {
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
import { Command } from '@oclif/core';
|
|
10
|
+
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runDockerNocoBaseCommand, runLocalNocoBaseCommand, } from '../lib/app-runtime.js';
|
|
11
|
+
import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../lib/env-guard.js';
|
|
12
|
+
import { announceTargetEnv } from '../lib/ui.js';
|
|
13
|
+
const SILENT_LIKE_PASSTHROUGH_FLAGS = new Set(['--help', '-h', '--silent']);
|
|
14
|
+
const SILENT_RUNTIME_ENV_VARS = {
|
|
15
|
+
LOGGER_SILENT: 'true',
|
|
16
|
+
NODE_NO_WARNINGS: '1',
|
|
17
|
+
};
|
|
18
|
+
const SILENT_STDERR_FILTERS = [
|
|
19
|
+
/^\(node:\d+\) \[DEP0040\] DeprecationWarning: The `punycode` module is deprecated\..*$/,
|
|
20
|
+
/^\(Use `node --trace-deprecation .*$/,
|
|
21
|
+
/^About to overwrite ArrayBuffer\.prototype properties /,
|
|
22
|
+
];
|
|
23
|
+
function parseBridgeArgv(argv) {
|
|
24
|
+
let requestedEnv;
|
|
25
|
+
let yes = false;
|
|
26
|
+
const passthroughArgs = [];
|
|
27
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
28
|
+
const token = argv[index];
|
|
29
|
+
if (token === '--') {
|
|
30
|
+
passthroughArgs.push(...argv.slice(index + 1));
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
if (token === '--env') {
|
|
34
|
+
const value = argv[index + 1];
|
|
35
|
+
if (!value || value === '--') {
|
|
36
|
+
throw new Error('Missing value for `--env`.');
|
|
37
|
+
}
|
|
38
|
+
requestedEnv = value.trim() || undefined;
|
|
39
|
+
index += 1;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (token.startsWith('--env=')) {
|
|
43
|
+
requestedEnv = token.slice('--env='.length).trim() || undefined;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (token === '-e') {
|
|
47
|
+
const value = argv[index + 1];
|
|
48
|
+
if (!value || value === '--') {
|
|
49
|
+
throw new Error('Missing value for `-e`.');
|
|
50
|
+
}
|
|
51
|
+
requestedEnv = value.trim() || undefined;
|
|
52
|
+
index += 1;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (token.startsWith('-e') && token.length > 2) {
|
|
56
|
+
requestedEnv = token.slice(2).trim() || undefined;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (token === '--yes') {
|
|
60
|
+
yes = true;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
passthroughArgs.push(...argv.slice(index));
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
requestedEnv,
|
|
68
|
+
yes,
|
|
69
|
+
passthroughArgs,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function formatHttpEnvError(envName) {
|
|
73
|
+
return [
|
|
74
|
+
`Can't run \`nb v1\` for "${envName}" yet.`,
|
|
75
|
+
'This env only has an API connection, so the v1 bridge is not available here.',
|
|
76
|
+
'Use a local or Docker env instead.',
|
|
77
|
+
].join('\n');
|
|
78
|
+
}
|
|
79
|
+
function formatSshEnvError(envName) {
|
|
80
|
+
return [
|
|
81
|
+
`Can't run \`nb v1\` for "${envName}" yet.`,
|
|
82
|
+
'SSH env support is reserved but not implemented yet.',
|
|
83
|
+
'Use a local or Docker env right now.',
|
|
84
|
+
].join('\n');
|
|
85
|
+
}
|
|
86
|
+
function hasSilentLikePassthrough(args) {
|
|
87
|
+
return args.some((arg) => SILENT_LIKE_PASSTHROUGH_FLAGS.has(arg));
|
|
88
|
+
}
|
|
89
|
+
function shouldFilterSilentStderrLine(line) {
|
|
90
|
+
const normalized = line.replace(/\r$/, '');
|
|
91
|
+
return SILENT_STDERR_FILTERS.some((pattern) => pattern.test(normalized));
|
|
92
|
+
}
|
|
93
|
+
function createSilentBridgeOptions() {
|
|
94
|
+
let pendingStderr = '';
|
|
95
|
+
const flushBufferedStderr = (force) => {
|
|
96
|
+
while (true) {
|
|
97
|
+
const newlineIndex = pendingStderr.indexOf('\n');
|
|
98
|
+
if (newlineIndex === -1) {
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
const line = pendingStderr.slice(0, newlineIndex);
|
|
102
|
+
pendingStderr = pendingStderr.slice(newlineIndex + 1);
|
|
103
|
+
if (!shouldFilterSilentStderrLine(line)) {
|
|
104
|
+
process.stderr.write(`${line}\n`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (force && pendingStderr) {
|
|
108
|
+
if (!shouldFilterSilentStderrLine(pendingStderr)) {
|
|
109
|
+
process.stderr.write(pendingStderr);
|
|
110
|
+
}
|
|
111
|
+
pendingStderr = '';
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
return {
|
|
115
|
+
commandOptions: {
|
|
116
|
+
stdio: 'pipe',
|
|
117
|
+
env: {
|
|
118
|
+
...SILENT_RUNTIME_ENV_VARS,
|
|
119
|
+
},
|
|
120
|
+
onStdout: (chunk) => {
|
|
121
|
+
process.stdout.write(chunk);
|
|
122
|
+
},
|
|
123
|
+
onStderr: (chunk) => {
|
|
124
|
+
pendingStderr += chunk;
|
|
125
|
+
flushBufferedStderr(false);
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
flush: () => {
|
|
129
|
+
flushBufferedStderr(true);
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
export default class V1 extends Command {
|
|
134
|
+
static hidden = true;
|
|
135
|
+
static strict = false;
|
|
136
|
+
static summary = 'Forward commands to the selected env through the v1 bridge';
|
|
137
|
+
static description = 'Forward v1-compatible commands to the selected env. Defaults to the current env when `--env` is omitted. Local envs run `nocobase-v1`, and Docker envs run inside the saved app container. Bridge flags (`--env`, `--yes`) must appear before the forwarded command. Use `--` when the forwarded command needs the same flag names.';
|
|
138
|
+
static examples = [
|
|
139
|
+
'<%= config.bin %> <%= command.id %> build',
|
|
140
|
+
'<%= config.bin %> <%= command.id %> --env local pm list',
|
|
141
|
+
'<%= config.bin %> <%= command.id %> --env docker-local -- pm enable @nocobase/plugin-sample --yes',
|
|
142
|
+
];
|
|
143
|
+
async run() {
|
|
144
|
+
const originalArgv = [...this.argv];
|
|
145
|
+
await this.parse({ strict: false, flags: {}, args: {} }, []);
|
|
146
|
+
this.argv = originalArgv;
|
|
147
|
+
let parsed;
|
|
148
|
+
try {
|
|
149
|
+
parsed = parseBridgeArgv(this.argv);
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
153
|
+
this.error(message);
|
|
154
|
+
}
|
|
155
|
+
const { requestedEnv, yes, passthroughArgs } = parsed;
|
|
156
|
+
if (passthroughArgs.length === 0) {
|
|
157
|
+
this.error('Pass at least one v1 command to forward.');
|
|
158
|
+
}
|
|
159
|
+
const explicitEnvSelection = Boolean(requestedEnv && hasExplicitEnvSelection(this.argv));
|
|
160
|
+
if (explicitEnvSelection) {
|
|
161
|
+
const confirmed = await ensureCrossEnvConfirmed({
|
|
162
|
+
command: this,
|
|
163
|
+
requestedEnv,
|
|
164
|
+
yes,
|
|
165
|
+
});
|
|
166
|
+
if (!confirmed) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const runtime = await resolveManagedAppRuntime(requestedEnv);
|
|
171
|
+
if (!runtime) {
|
|
172
|
+
this.error(formatMissingManagedAppEnvMessage(requestedEnv));
|
|
173
|
+
}
|
|
174
|
+
const silentLike = hasSilentLikePassthrough(passthroughArgs);
|
|
175
|
+
const silentBridge = silentLike ? createSilentBridgeOptions() : undefined;
|
|
176
|
+
if (!silentLike) {
|
|
177
|
+
announceTargetEnv(runtime.envName);
|
|
178
|
+
}
|
|
179
|
+
if (runtime.kind === 'local') {
|
|
180
|
+
try {
|
|
181
|
+
await runLocalNocoBaseCommand(runtime, passthroughArgs, silentBridge?.commandOptions);
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
185
|
+
this.error(message);
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
silentBridge?.flush();
|
|
189
|
+
}
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (runtime.kind === 'docker') {
|
|
193
|
+
try {
|
|
194
|
+
await runDockerNocoBaseCommand(runtime.containerName, passthroughArgs, silentBridge?.commandOptions);
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
198
|
+
this.error(message);
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
silentBridge?.flush();
|
|
202
|
+
}
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (runtime.kind === 'http') {
|
|
206
|
+
this.error(formatHttpEnvError(runtime.envName));
|
|
207
|
+
}
|
|
208
|
+
this.error(formatSshEnvError(runtime.envName));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -32,6 +32,18 @@ function localSourceLabel(source) {
|
|
|
32
32
|
function trimValue(value) {
|
|
33
33
|
return String(value ?? '').trim();
|
|
34
34
|
}
|
|
35
|
+
function pushOptionalEnvArg(args, key, value) {
|
|
36
|
+
if (typeof value === 'string') {
|
|
37
|
+
if (!value) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
args.push('-e', `${key}=${value}`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (typeof value === 'boolean') {
|
|
44
|
+
args.push('-e', `${key}=${String(value)}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
35
47
|
function normalizeDockerPlatform(value) {
|
|
36
48
|
const text = trimValue(value);
|
|
37
49
|
if (!text || text === 'auto') {
|
|
@@ -109,6 +121,9 @@ export async function buildSavedDockerRunArgs(runtime) {
|
|
|
109
121
|
const dbDatabase = trimValue(config.dbDatabase);
|
|
110
122
|
const dbUser = trimValue(config.dbUser);
|
|
111
123
|
const dbPassword = trimValue(config.dbPassword);
|
|
124
|
+
const dbSchema = trimValue(config.dbSchema);
|
|
125
|
+
const dbTablePrefix = trimValue(config.dbTablePrefix);
|
|
126
|
+
const dbUnderscored = typeof config.dbUnderscored === 'boolean' ? config.dbUnderscored : undefined;
|
|
112
127
|
const dockerRegistry = trimValue(config.dockerRegistry) || DEFAULT_DOCKER_REGISTRY;
|
|
113
128
|
const version = trimValue(config.downloadVersion) || DEFAULT_DOCKER_VERSION;
|
|
114
129
|
const imageRef = resolveDockerImageRef(dockerRegistry, version, {
|
|
@@ -163,7 +178,11 @@ export async function buildSavedDockerRunArgs(runtime) {
|
|
|
163
178
|
if (envFile) {
|
|
164
179
|
args.push('--env-file', envFile);
|
|
165
180
|
}
|
|
166
|
-
args.push('-e', `APP_KEY=${appKey}`, '-e', `DB_DIALECT=${dbDialect}`, '-e', `DB_HOST=${dbHost}`, '-e', `DB_PORT=${dbPort}`, '-e', `DB_DATABASE=${dbDatabase}`, '-e', `DB_USER=${dbUser}`, '-e', `DB_PASSWORD=${dbPassword}`, '-e', `TZ=${timeZone}`, '-v', `${storagePath}:${DOCKER_APP_STORAGE_DESTINATION}
|
|
181
|
+
args.push('-e', `APP_KEY=${appKey}`, '-e', `DB_DIALECT=${dbDialect}`, '-e', `DB_HOST=${dbHost}`, '-e', `DB_PORT=${dbPort}`, '-e', `DB_DATABASE=${dbDatabase}`, '-e', `DB_USER=${dbUser}`, '-e', `DB_PASSWORD=${dbPassword}`, '-e', `TZ=${timeZone}`, '-v', `${storagePath}:${DOCKER_APP_STORAGE_DESTINATION}`);
|
|
182
|
+
pushOptionalEnvArg(args, 'DB_SCHEMA', dbSchema || undefined);
|
|
183
|
+
pushOptionalEnvArg(args, 'DB_TABLE_PREFIX', dbTablePrefix || undefined);
|
|
184
|
+
pushOptionalEnvArg(args, 'DB_UNDERSCORED', dbUnderscored);
|
|
185
|
+
args.push(imageRef);
|
|
167
186
|
return {
|
|
168
187
|
appPort: appPort || undefined,
|
|
169
188
|
storagePath,
|
package/dist/lib/app-runtime.js
CHANGED
|
@@ -119,8 +119,13 @@ export async function runLocalNocoBaseCommand(runtime, args, options) {
|
|
|
119
119
|
const envVars = await buildRuntimeEnvVars(runtime);
|
|
120
120
|
await runNocoBaseCommand(args, {
|
|
121
121
|
cwd: runtime.projectRoot,
|
|
122
|
-
env:
|
|
122
|
+
env: {
|
|
123
|
+
...envVars,
|
|
124
|
+
...options?.env,
|
|
125
|
+
},
|
|
123
126
|
stdio: options?.stdio,
|
|
127
|
+
onStdout: options?.onStdout,
|
|
128
|
+
onStderr: options?.onStderr,
|
|
124
129
|
});
|
|
125
130
|
}
|
|
126
131
|
export async function dockerContainerExists(containerName) {
|
|
@@ -163,9 +168,13 @@ export async function stopDockerContainer(containerName, options) {
|
|
|
163
168
|
});
|
|
164
169
|
return 'stopped';
|
|
165
170
|
}
|
|
166
|
-
export async function runDockerNocoBaseCommand(containerName, args) {
|
|
167
|
-
await startDockerContainer(containerName);
|
|
168
|
-
|
|
171
|
+
export async function runDockerNocoBaseCommand(containerName, args, options) {
|
|
172
|
+
await startDockerContainer(containerName, { stdio: options?.stdio });
|
|
173
|
+
const dockerEnvArgs = Object.entries(options?.env ?? {}).flatMap(([key, value]) => ['-e', `${key}=${value}`]);
|
|
174
|
+
await run('docker', ['exec', ...dockerEnvArgs, '-w', DOCKER_APP_WORKDIR, containerName, 'yarn', 'nocobase', ...args], {
|
|
169
175
|
errorName: 'docker exec',
|
|
176
|
+
stdio: options?.stdio,
|
|
177
|
+
onStdout: options?.onStdout,
|
|
178
|
+
onStderr: options?.onStderr,
|
|
170
179
|
});
|
|
171
180
|
}
|
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
|
}
|
|
@@ -159,6 +180,9 @@ export class Env {
|
|
|
159
180
|
get auth() {
|
|
160
181
|
return this.config.auth;
|
|
161
182
|
}
|
|
183
|
+
get authType() {
|
|
184
|
+
return resolveConfiguredAuthType(this.config);
|
|
185
|
+
}
|
|
162
186
|
get runtime() {
|
|
163
187
|
return this.config.runtime;
|
|
164
188
|
}
|
|
@@ -210,16 +234,20 @@ export class Env {
|
|
|
210
234
|
put('DB_DATABASE', this.config.dbDatabase);
|
|
211
235
|
put('DB_USER', this.config.dbUser);
|
|
212
236
|
put('DB_PASSWORD', this.config.dbPassword);
|
|
237
|
+
put('DB_SCHEMA', this.config.dbSchema);
|
|
238
|
+
put('DB_TABLE_PREFIX', this.config.dbTablePrefix);
|
|
239
|
+
put('DB_UNDERSCORED', this.config.dbUnderscored);
|
|
213
240
|
return out;
|
|
214
241
|
}
|
|
215
242
|
}
|
|
216
243
|
export async function getEnv(envName, options = {}) {
|
|
217
244
|
const { config: snapshot, ...loadOptions } = options;
|
|
218
245
|
const config = snapshot ?? (await loadAuthConfig(loadOptions));
|
|
219
|
-
const resolved = envName?.trim() ||
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
246
|
+
const resolved = envName?.trim() ||
|
|
247
|
+
(await resolveEffectiveCurrentEnv(Object.keys(config.envs).sort(), {
|
|
248
|
+
scope: loadOptions.scope,
|
|
249
|
+
lastEnv: config.lastEnv,
|
|
250
|
+
}));
|
|
223
251
|
const envConfig = config.envs[resolved];
|
|
224
252
|
if (!envConfig) {
|
|
225
253
|
return undefined;
|
|
@@ -253,27 +281,41 @@ async function writeEnv(envName, updater, options = {}) {
|
|
|
253
281
|
config.envs[envName] = updater(previous);
|
|
254
282
|
await saveAuthConfig(config, options);
|
|
255
283
|
}
|
|
284
|
+
function normalizeConfiguredAuthType(value) {
|
|
285
|
+
return value === 'basic' || value === 'token' || value === 'oauth' ? value : undefined;
|
|
286
|
+
}
|
|
287
|
+
export function resolveConfiguredAuthType(config) {
|
|
288
|
+
return normalizeConfiguredAuthType(config?.authType) ?? normalizeConfiguredAuthType(config?.auth?.type);
|
|
289
|
+
}
|
|
256
290
|
export async function upsertEnv(envName, config, options = {}) {
|
|
257
291
|
await writeEnv(envName, (previous) => {
|
|
258
|
-
const { apiBaseUrl: _apiBaseUrl, baseUrl: _baseUrl, apibaseUrl: _legacyApiBaseUrl, accessToken, ...rest } = config;
|
|
292
|
+
const { apiBaseUrl: _apiBaseUrl, baseUrl: _baseUrl, apibaseUrl: _legacyApiBaseUrl, accessToken, authType, authUsername, ...rest } = config;
|
|
259
293
|
const nextApiBaseUrl = readEnvApiBaseUrl(config);
|
|
260
294
|
const previousApiBaseUrl = readEnvApiBaseUrl(previous);
|
|
261
295
|
const baseUrlChanged = previousApiBaseUrl !== nextApiBaseUrl;
|
|
296
|
+
const previousAuthType = resolveConfiguredAuthType(previous);
|
|
297
|
+
const requestedAuthType = normalizeConfiguredAuthType(authType);
|
|
298
|
+
const nextAuthType = requestedAuthType ?? (accessToken ? 'token' : previousAuthType);
|
|
299
|
+
const nextAuthUsername = nextAuthType === 'basic' ? normalizeOptionalString(authUsername) ?? previous?.authUsername : undefined;
|
|
262
300
|
const nextAuth = accessToken
|
|
263
301
|
? {
|
|
264
302
|
type: 'token',
|
|
265
303
|
accessToken,
|
|
266
304
|
}
|
|
267
|
-
: baseUrlChanged
|
|
268
|
-
?
|
|
269
|
-
:
|
|
305
|
+
: nextAuthType === 'oauth' && !baseUrlChanged && previous?.auth?.type === 'oauth'
|
|
306
|
+
? previous.auth
|
|
307
|
+
: undefined;
|
|
270
308
|
const authChanged = !areAuthConfigsEquivalent(previous?.auth, nextAuth);
|
|
309
|
+
const authTypeChanged = previousAuthType !== nextAuthType;
|
|
310
|
+
const authUsernameChanged = previous?.authUsername !== nextAuthUsername;
|
|
271
311
|
return {
|
|
272
312
|
...previous,
|
|
273
313
|
apiBaseUrl: nextApiBaseUrl,
|
|
314
|
+
authType: nextAuthType,
|
|
315
|
+
authUsername: nextAuthUsername,
|
|
274
316
|
auth: nextAuth,
|
|
275
317
|
...rest,
|
|
276
|
-
runtime: baseUrlChanged || authChanged ? undefined : previous?.runtime,
|
|
318
|
+
runtime: baseUrlChanged || authChanged || authTypeChanged || authUsernameChanged ? undefined : previous?.runtime,
|
|
277
319
|
};
|
|
278
320
|
}, options);
|
|
279
321
|
}
|
|
@@ -282,26 +324,35 @@ export async function updateEnvConnection(envName, updates, options = {}) {
|
|
|
282
324
|
const nextApiBaseUrl = readEnvApiBaseUrl(updates) ?? readEnvApiBaseUrl(previous);
|
|
283
325
|
const previousApiBaseUrl = readEnvApiBaseUrl(previous);
|
|
284
326
|
const baseUrlChanged = previousApiBaseUrl !== nextApiBaseUrl;
|
|
327
|
+
const previousAuthType = resolveConfiguredAuthType(previous);
|
|
328
|
+
const requestedAuthType = normalizeConfiguredAuthType(updates.authType);
|
|
329
|
+
const nextAuthType = requestedAuthType ?? (updates.accessToken ? 'token' : previousAuthType);
|
|
330
|
+
const nextAuthUsername = nextAuthType === 'basic' ? normalizeOptionalString(updates.authUsername) ?? previous?.authUsername : undefined;
|
|
285
331
|
const nextAuth = updates.accessToken
|
|
286
332
|
? {
|
|
287
333
|
type: 'token',
|
|
288
334
|
accessToken: updates.accessToken,
|
|
289
335
|
}
|
|
290
|
-
: baseUrlChanged
|
|
291
|
-
?
|
|
292
|
-
:
|
|
336
|
+
: nextAuthType === 'oauth' && !baseUrlChanged && previous?.auth?.type === 'oauth'
|
|
337
|
+
? previous.auth
|
|
338
|
+
: undefined;
|
|
293
339
|
const authChanged = !areAuthConfigsEquivalent(previous?.auth, nextAuth);
|
|
340
|
+
const authTypeChanged = previousAuthType !== nextAuthType;
|
|
341
|
+
const authUsernameChanged = previous?.authUsername !== nextAuthUsername;
|
|
294
342
|
return {
|
|
295
343
|
...previous,
|
|
296
344
|
...(nextApiBaseUrl !== undefined ? { apiBaseUrl: nextApiBaseUrl } : {}),
|
|
345
|
+
authType: nextAuthType,
|
|
346
|
+
authUsername: nextAuthUsername,
|
|
297
347
|
auth: nextAuth,
|
|
298
|
-
runtime: baseUrlChanged || authChanged ? undefined : previous?.runtime,
|
|
348
|
+
runtime: baseUrlChanged || authChanged || authTypeChanged || authUsernameChanged ? undefined : previous?.runtime,
|
|
299
349
|
};
|
|
300
350
|
}, options);
|
|
301
351
|
}
|
|
302
352
|
export async function setEnvOauthSession(envName, auth, options = {}) {
|
|
303
353
|
await writeEnv(envName, (previous) => ({
|
|
304
354
|
...previous,
|
|
355
|
+
authType: 'oauth',
|
|
305
356
|
auth,
|
|
306
357
|
runtime: options.preserveRuntime ? previous?.runtime : undefined,
|
|
307
358
|
}), options);
|