@nocobase/cli 2.1.0-alpha.46 → 2.1.0-alpha.47
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/LICENSE +201 -0
- package/README.md +63 -380
- package/assets/env-proxy/nginx/app.conf.tpl +23 -0
- package/assets/env-proxy/nginx/nocobase.conf.tpl +5 -0
- package/assets/env-proxy/nginx/snippets/dist-location.conf +5 -0
- package/assets/env-proxy/nginx/snippets/gzip.conf +17 -0
- package/assets/env-proxy/nginx/snippets/log-format-http.conf +13 -0
- package/assets/env-proxy/nginx/snippets/maps-http.conf +14 -0
- package/assets/env-proxy/nginx/snippets/mime-types.conf +98 -0
- package/assets/env-proxy/nginx/snippets/proxy-location.conf +17 -0
- package/assets/env-proxy/nginx/snippets/spa-location.conf +6 -0
- package/assets/env-proxy/nginx/snippets/uploads-location.conf +21 -0
- package/bin/run.js +40 -3
- package/dist/commands/app/autostart/disable.js +55 -0
- package/dist/commands/app/autostart/enable.js +55 -0
- package/dist/commands/app/autostart/list.js +37 -0
- package/dist/commands/app/autostart/run.js +84 -0
- package/dist/commands/app/autostart/shared.js +49 -0
- package/dist/commands/app/destroy.js +21 -12
- package/dist/commands/app/down.js +2 -2
- package/dist/commands/app/logs.js +2 -1
- package/dist/commands/app/restart.js +89 -24
- package/dist/commands/app/shared.js +3 -2
- package/dist/commands/app/start.js +142 -38
- package/dist/commands/app/stop.js +31 -2
- package/dist/commands/app/upgrade.js +9 -5
- package/dist/commands/config/delete.js +6 -1
- package/dist/commands/config/get.js +6 -1
- package/dist/commands/config/set.js +7 -2
- package/dist/commands/env/add.js +24 -44
- package/dist/commands/env/info.js +3 -2
- package/dist/commands/env/proxy/caddy.js +28 -0
- package/dist/commands/env/proxy/index.js +353 -0
- package/dist/commands/env/proxy/nginx.js +28 -0
- package/dist/commands/env/remove.js +112 -22
- package/dist/commands/env/shared.js +17 -9
- package/dist/commands/env/update.js +385 -21
- package/dist/commands/init.js +350 -98
- package/dist/commands/install.js +218 -75
- package/dist/commands/license/activate.js +63 -244
- package/dist/commands/license/plugins/shared.js +64 -13
- package/dist/commands/plugin/import.js +108 -0
- package/dist/commands/revision/create.js +89 -0
- package/dist/commands/source/test.js +13 -14
- package/dist/lib/app-health.js +5 -5
- package/dist/lib/app-managed-resources.js +24 -8
- package/dist/lib/app-public-path.js +80 -0
- package/dist/lib/app-runtime.js +10 -1
- package/dist/lib/auth-store.js +119 -4
- package/dist/lib/cli-config.js +191 -3
- package/dist/lib/cli-entry-error.js +8 -0
- package/dist/lib/command-log.js +284 -0
- package/dist/lib/docker-env-file.js +10 -2
- package/dist/lib/env-auth.js +4 -1
- package/dist/lib/env-command-config.js +45 -0
- package/dist/lib/env-config.js +7 -2
- package/dist/lib/env-guard.js +9 -10
- package/dist/lib/env-paths.js +101 -0
- package/dist/lib/env-proxy.js +1295 -0
- package/dist/lib/inquirer.js +4 -5
- package/dist/lib/managed-env-file.js +98 -0
- package/dist/lib/plugin-import.js +279 -0
- package/dist/lib/prompt-catalog-core.js +3 -2
- package/dist/lib/prompt-validators.js +14 -0
- package/dist/lib/prompt-web-ui.js +8 -1
- package/dist/lib/run-npm.js +56 -12
- package/dist/lib/skills-manager.js +3 -1
- package/dist/lib/source-publish.js +18 -1
- package/dist/lib/ui.js +2 -1
- package/dist/locale/en-US.json +116 -22
- package/dist/locale/zh-CN.json +113 -19
- package/package.json +5 -7
- package/scripts/build.mjs +34 -0
- package/scripts/clean.mjs +9 -0
- package/tsconfig.json +19 -0
- package/LICENSE.txt +0 -107
- package/README.zh-CN.md +0 -355
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
10
|
+
import { executeRawApiRequest } from '../../lib/api-client.js';
|
|
11
|
+
import { ensureCrossEnvConfirmed } from '../../lib/env-guard.js';
|
|
12
|
+
export default class RevisionCreate extends Command {
|
|
13
|
+
static summary = 'Save the current NocoBase build as a restorable revision';
|
|
14
|
+
static examples = [
|
|
15
|
+
'<%= config.bin %> <%= command.id %> "description here"',
|
|
16
|
+
'<%= config.bin %> <%= command.id %> --env app1 "description here"',
|
|
17
|
+
];
|
|
18
|
+
static args = {
|
|
19
|
+
description: Args.string({
|
|
20
|
+
required: true,
|
|
21
|
+
description: 'Revision description, up to 2000 characters.',
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
static flags = {
|
|
25
|
+
'api-base-url': Flags.string({
|
|
26
|
+
description: 'NocoBase API base URL, for example http://localhost:13000/api',
|
|
27
|
+
}),
|
|
28
|
+
env: Flags.string({
|
|
29
|
+
char: 'e',
|
|
30
|
+
description: 'Environment name',
|
|
31
|
+
}),
|
|
32
|
+
role: Flags.string({
|
|
33
|
+
description: 'Role override, sent as X-Role',
|
|
34
|
+
}),
|
|
35
|
+
token: Flags.string({
|
|
36
|
+
char: 't',
|
|
37
|
+
description: 'API key override',
|
|
38
|
+
}),
|
|
39
|
+
yes: Flags.boolean({
|
|
40
|
+
char: 'y',
|
|
41
|
+
description: 'Confirm using --env when it targets a different env than the current env',
|
|
42
|
+
default: false,
|
|
43
|
+
}),
|
|
44
|
+
'json-output': Flags.boolean({
|
|
45
|
+
char: 'j',
|
|
46
|
+
description: 'Print raw JSON response',
|
|
47
|
+
default: true,
|
|
48
|
+
allowNo: true,
|
|
49
|
+
}),
|
|
50
|
+
};
|
|
51
|
+
async run() {
|
|
52
|
+
const { args, flags } = await this.parse(RevisionCreate);
|
|
53
|
+
const description = args.description;
|
|
54
|
+
if (description.length > 2000) {
|
|
55
|
+
this.error('Description length should not be over 2000');
|
|
56
|
+
}
|
|
57
|
+
const confirmed = await ensureCrossEnvConfirmed({
|
|
58
|
+
command: this,
|
|
59
|
+
requestedEnv: flags.env,
|
|
60
|
+
yes: flags.yes,
|
|
61
|
+
});
|
|
62
|
+
if (!confirmed) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const response = await executeRawApiRequest({
|
|
66
|
+
envName: flags.env,
|
|
67
|
+
baseUrl: flags['api-base-url'],
|
|
68
|
+
role: flags.role,
|
|
69
|
+
token: flags.token,
|
|
70
|
+
method: 'POST',
|
|
71
|
+
path: '/app:publishEvent',
|
|
72
|
+
body: {
|
|
73
|
+
plugin: 'plugin-version-control',
|
|
74
|
+
command: 'revision:create',
|
|
75
|
+
payload: {
|
|
76
|
+
description,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
this.error(`Request failed with status ${response.status}\n${JSON.stringify(response.data, null, 2)}`);
|
|
82
|
+
}
|
|
83
|
+
if (flags['json-output']) {
|
|
84
|
+
this.log(JSON.stringify(response.data, null, 2));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
this.log(`HTTP ${response.status}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -14,7 +14,7 @@ import Install from '../install.js';
|
|
|
14
14
|
import { defaultWorkspaceName } from '../../lib/app-runtime.js';
|
|
15
15
|
import { resolveCliLocale } from '../../lib/cli-locale.js';
|
|
16
16
|
import { findAvailableTcpPort, validateAvailableTcpPort } from '../../lib/prompt-validators.js';
|
|
17
|
-
import { commandSucceeds, resolveProjectCwd, run, runNocoBaseCommand } from '../../lib/run-npm.js';
|
|
17
|
+
import { commandSucceeds, ensureDockerDaemonRunning, resolveProjectCwd, run, runNocoBaseCommand, } from '../../lib/run-npm.js';
|
|
18
18
|
import { failTask, printInfo, setVerboseMode, startTask, succeedTask } from '../../lib/ui.js';
|
|
19
19
|
const DEFAULT_DB_HOST = '127.0.0.1';
|
|
20
20
|
const DEFAULT_DB_DATABASE = 'nocobase-test';
|
|
@@ -48,11 +48,11 @@ const DEFAULT_DB_PORTS = {
|
|
|
48
48
|
};
|
|
49
49
|
const TCP_PORT_READY_SCRIPT = [
|
|
50
50
|
"const net = require('node:net');",
|
|
51
|
-
|
|
51
|
+
'const port = Number(process.argv.at(-1));',
|
|
52
52
|
"const socket = net.createConnection({ host: '127.0.0.1', port });",
|
|
53
53
|
"socket.once('connect', () => { socket.end(); process.exit(0); });",
|
|
54
54
|
"socket.once('error', () => process.exit(1));",
|
|
55
|
-
|
|
55
|
+
'setTimeout(() => { socket.destroy(); process.exit(1); }, 200).unref();',
|
|
56
56
|
].join('\n');
|
|
57
57
|
function inferTestEnv(paths) {
|
|
58
58
|
const first = String(paths[0] ?? '').trim();
|
|
@@ -60,9 +60,7 @@ function inferTestEnv(paths) {
|
|
|
60
60
|
return undefined;
|
|
61
61
|
}
|
|
62
62
|
const normalized = first.split('\\').join('/');
|
|
63
|
-
if (normalized.includes('/client/')
|
|
64
|
-
|| normalized.includes('/client-v2/')
|
|
65
|
-
|| normalized.includes('/flow-engine/')) {
|
|
63
|
+
if (normalized.includes('/client/') || normalized.includes('/client-v2/') || normalized.includes('/flow-engine/')) {
|
|
66
64
|
return 'client-side';
|
|
67
65
|
}
|
|
68
66
|
return 'server-side';
|
|
@@ -77,9 +75,7 @@ function defaultTestDbPort(dbDialect) {
|
|
|
77
75
|
return String(DEFAULT_DB_PORTS[dbDialect] ?? DEFAULT_DB_PORTS.postgres);
|
|
78
76
|
}
|
|
79
77
|
function defaultTestDbImage(dbDialect) {
|
|
80
|
-
const defaults = resolveCliLocale(process.env.NB_LOCALE) === 'zh-CN'
|
|
81
|
-
? DEFAULT_TEST_DB_IMAGES_ZH_CN
|
|
82
|
-
: DEFAULT_TEST_DB_IMAGES;
|
|
78
|
+
const defaults = resolveCliLocale(process.env.NB_LOCALE) === 'zh-CN' ? DEFAULT_TEST_DB_IMAGES_ZH_CN : DEFAULT_TEST_DB_IMAGES;
|
|
83
79
|
return defaults[dbDialect] ?? defaults.postgres;
|
|
84
80
|
}
|
|
85
81
|
function delay(ms) {
|
|
@@ -174,9 +170,11 @@ async function startTestDbDistributor(params) {
|
|
|
174
170
|
if (code === 0) {
|
|
175
171
|
return;
|
|
176
172
|
}
|
|
177
|
-
childError =
|
|
178
|
-
|
|
179
|
-
|
|
173
|
+
childError =
|
|
174
|
+
childError ??
|
|
175
|
+
new Error(signal
|
|
176
|
+
? `test DB distributor exited due to signal ${signal}`
|
|
177
|
+
: `test DB distributor exited with code ${code ?? 'unknown'}`);
|
|
180
178
|
});
|
|
181
179
|
try {
|
|
182
180
|
await waitForTcpPortReady(port);
|
|
@@ -194,6 +192,7 @@ async function startTestDbDistributor(params) {
|
|
|
194
192
|
};
|
|
195
193
|
}
|
|
196
194
|
async function ensureDockerNetwork(networkName, options) {
|
|
195
|
+
await ensureDockerDaemonRunning('prepare Docker resources for the built-in test database');
|
|
197
196
|
if (await commandSucceeds('docker', ['network', 'inspect', networkName])) {
|
|
198
197
|
return;
|
|
199
198
|
}
|
|
@@ -440,8 +439,8 @@ export default class SourceTest extends Command {
|
|
|
440
439
|
server: flags.server,
|
|
441
440
|
client: flags.client,
|
|
442
441
|
paths: args.paths ?? [],
|
|
443
|
-
})
|
|
444
|
-
|
|
442
|
+
}) &&
|
|
443
|
+
supportsTestDbDistributor(testDbConfig.env.DB_DIALECT)) {
|
|
445
444
|
testDbDistributor = await startTestDbDistributor({
|
|
446
445
|
cwd,
|
|
447
446
|
env: testDbConfig.env,
|
package/dist/lib/app-health.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
|
+
import { buildLocalApiBaseUrl, buildLocalAppUrl } from './app-public-path.js';
|
|
9
10
|
import { startDockerLogFollower } from './docker-log-stream.js';
|
|
10
11
|
import { printInfo } from './ui.js';
|
|
11
12
|
const APP_HEALTH_CHECK_INTERVAL_MS = 2_000;
|
|
@@ -56,14 +57,13 @@ async function requestAppHealthCheck(params) {
|
|
|
56
57
|
}
|
|
57
58
|
export class AppHealthCheckError extends Error {
|
|
58
59
|
}
|
|
59
|
-
export function formatAppUrl(port) {
|
|
60
|
-
|
|
61
|
-
return value ? `http://127.0.0.1:${value}` : undefined;
|
|
60
|
+
export function formatAppUrl(port, appPublicPath) {
|
|
61
|
+
return buildLocalAppUrl(port, appPublicPath);
|
|
62
62
|
}
|
|
63
63
|
export function resolveManagedAppApiBaseUrl(runtime, options) {
|
|
64
64
|
const override = trimValue(options?.portOverride);
|
|
65
65
|
if (override) {
|
|
66
|
-
return
|
|
66
|
+
return buildLocalApiBaseUrl(override, runtime.env.config?.appPublicPath);
|
|
67
67
|
}
|
|
68
68
|
const baseUrl = trimValue(runtime.env.baseUrl);
|
|
69
69
|
if (baseUrl) {
|
|
@@ -72,7 +72,7 @@ export function resolveManagedAppApiBaseUrl(runtime, options) {
|
|
|
72
72
|
const appPort = runtime.env.appPort === undefined || runtime.env.appPort === null
|
|
73
73
|
? ''
|
|
74
74
|
: trimValue(runtime.env.appPort);
|
|
75
|
-
return appPort
|
|
75
|
+
return buildLocalApiBaseUrl(appPort, runtime.env.config?.appPublicPath);
|
|
76
76
|
}
|
|
77
77
|
export async function isAppReady(apiBaseUrl, options) {
|
|
78
78
|
const baseUrl = trimValue(apiBaseUrl);
|
|
@@ -7,18 +7,20 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
import { mkdir, readdir } from 'node:fs/promises';
|
|
10
|
-
import {
|
|
10
|
+
import { resolveAppPublicPath } from './app-public-path.js';
|
|
11
|
+
import { dockerContainerExists, managedAppLifecycleEnvVars, runLocalNocoBaseCommand, startDockerContainer, } from './app-runtime.js';
|
|
11
12
|
import { deriveBuiltinDbConnection, resolveBuiltinDbConnection } from './builtin-db.js';
|
|
12
|
-
import {
|
|
13
|
+
import { resolveConfiguredStoragePath } from './env-paths.js';
|
|
13
14
|
import { resolveDockerEnvFileArg } from "./docker-env-file.js";
|
|
14
15
|
import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_VERSION, resolveDockerImageRef, } from "./docker-image.js";
|
|
15
|
-
import { commandSucceeds, run } from './run-npm.js';
|
|
16
|
+
import { commandSucceeds, ensureDockerDaemonRunning, run } from './run-npm.js';
|
|
16
17
|
import Install from '../commands/install.js';
|
|
17
18
|
const DOCKER_APP_STORAGE_DESTINATION = '/app/nocobase/storage';
|
|
18
19
|
function commandStdio(verbose) {
|
|
19
20
|
return verbose ? 'inherit' : 'ignore';
|
|
20
21
|
}
|
|
21
22
|
async function ensureDockerNetwork(networkName) {
|
|
23
|
+
await ensureDockerDaemonRunning('prepare Docker resources for this environment');
|
|
22
24
|
if (await commandSucceeds('docker', ['network', 'inspect', networkName])) {
|
|
23
25
|
return;
|
|
24
26
|
}
|
|
@@ -44,6 +46,16 @@ function pushOptionalEnvArg(args, key, value) {
|
|
|
44
46
|
args.push('-e', `${key}=${String(value)}`);
|
|
45
47
|
}
|
|
46
48
|
}
|
|
49
|
+
function resolveDockerClientAssetsExtractEnabled(envValue) {
|
|
50
|
+
const text = trimValue(envValue).toLowerCase();
|
|
51
|
+
if (!text) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
if (['0', 'false', 'no', 'off'].includes(text)) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
47
59
|
function normalizeDockerPlatform(value) {
|
|
48
60
|
const text = trimValue(value);
|
|
49
61
|
if (!text || text === 'auto') {
|
|
@@ -104,15 +116,13 @@ async function localProjectHasFiles(projectRoot) {
|
|
|
104
116
|
}
|
|
105
117
|
export async function buildSavedDockerRunArgs(runtime) {
|
|
106
118
|
const config = runtime.env.config ?? {};
|
|
107
|
-
const
|
|
108
|
-
const storagePath = configuredStoragePath
|
|
109
|
-
? trimValue(resolveConfiguredEnvPath(configuredStoragePath))
|
|
110
|
-
: '';
|
|
119
|
+
const storagePath = trimValue(resolveConfiguredStoragePath(config));
|
|
111
120
|
const envFile = await resolveDockerEnvFileArg(runtime.envName, config);
|
|
112
121
|
const appPort = runtime.env.appPort === undefined || runtime.env.appPort === null
|
|
113
122
|
? ''
|
|
114
123
|
: trimValue(runtime.env.appPort);
|
|
115
124
|
const appKey = trimValue(config.appKey);
|
|
125
|
+
const appPublicPath = trimValue(config.appPublicPath);
|
|
116
126
|
const timeZone = trimValue(config.timezone) || Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
|
|
117
127
|
const builtinDbConnection = config.builtinDb ? deriveBuiltinDbConnection(runtime) : undefined;
|
|
118
128
|
const dbDialect = builtinDbConnection?.dbDialect || trimValue(config.dbDialect);
|
|
@@ -124,6 +134,7 @@ export async function buildSavedDockerRunArgs(runtime) {
|
|
|
124
134
|
const dbSchema = trimValue(config.dbSchema);
|
|
125
135
|
const dbTablePrefix = trimValue(config.dbTablePrefix);
|
|
126
136
|
const dbUnderscored = typeof config.dbUnderscored === 'boolean' ? config.dbUnderscored : undefined;
|
|
137
|
+
const extractClientAssets = resolveDockerClientAssetsExtractEnabled(process.env.NOCOBASE_EXTRACT_CLIENT_ASSETS);
|
|
127
138
|
const dockerRegistry = trimValue(config.dockerRegistry) || DEFAULT_DOCKER_REGISTRY;
|
|
128
139
|
const version = trimValue(config.downloadVersion) || DEFAULT_DOCKER_VERSION;
|
|
129
140
|
const imageRef = resolveDockerImageRef(dockerRegistry, version, {
|
|
@@ -178,10 +189,13 @@ export async function buildSavedDockerRunArgs(runtime) {
|
|
|
178
189
|
if (envFile) {
|
|
179
190
|
args.push('--env-file', envFile);
|
|
180
191
|
}
|
|
181
|
-
|
|
192
|
+
const lifecycleEnvVars = managedAppLifecycleEnvVars();
|
|
193
|
+
args.push('-e', `APP_ENV=${lifecycleEnvVars.APP_ENV}`, '-e', `NODE_ENV=${lifecycleEnvVars.NODE_ENV}`, '-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}`);
|
|
194
|
+
pushOptionalEnvArg(args, 'APP_PUBLIC_PATH', appPublicPath ? resolveAppPublicPath(appPublicPath) : undefined);
|
|
182
195
|
pushOptionalEnvArg(args, 'DB_SCHEMA', dbSchema || undefined);
|
|
183
196
|
pushOptionalEnvArg(args, 'DB_TABLE_PREFIX', dbTablePrefix || undefined);
|
|
184
197
|
pushOptionalEnvArg(args, 'DB_UNDERSCORED', dbUnderscored);
|
|
198
|
+
pushOptionalEnvArg(args, 'NOCOBASE_EXTRACT_CLIENT_ASSETS', extractClientAssets);
|
|
185
199
|
args.push(imageRef);
|
|
186
200
|
return {
|
|
187
201
|
appPort: appPort || undefined,
|
|
@@ -220,6 +234,7 @@ export async function ensureBuiltinDbReady(runtime, options) {
|
|
|
220
234
|
envName: runtime.envName,
|
|
221
235
|
workspaceName: runtime.workspaceName,
|
|
222
236
|
dockerContainerPrefix: runtime.dockerContainerPrefix,
|
|
237
|
+
appPath: config.appPath,
|
|
223
238
|
storagePath: config.storagePath,
|
|
224
239
|
source: runtime.source,
|
|
225
240
|
dbDialect: builtinDbConnection.dbDialect,
|
|
@@ -305,6 +320,7 @@ export async function ensureLocalPostinstall(runtime, options) {
|
|
|
305
320
|
options?.onStartTask?.(`Running local postinstall for "${runtime.envName}"...`);
|
|
306
321
|
try {
|
|
307
322
|
await runLocalNocoBaseCommand(runtime, ['postinstall'], {
|
|
323
|
+
env: options?.env,
|
|
308
324
|
stdio: commandStdio(options?.verbose),
|
|
309
325
|
});
|
|
310
326
|
options?.onSucceedTask?.(`Local postinstall finished for "${runtime.envName}".`);
|
|
@@ -0,0 +1,80 @@
|
|
|
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 { readFile } from 'node:fs/promises';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
const DEFAULT_APP_PUBLIC_PATH = '/';
|
|
12
|
+
const DEFAULT_DIST_SEGMENT = 'dist';
|
|
13
|
+
const DIST_CLIENT_DIR = 'dist-client';
|
|
14
|
+
const DIST_CLIENT_ACTIVE_VERSION_FILE = 'active-version';
|
|
15
|
+
function trimValue(value) {
|
|
16
|
+
const text = String(value ?? '').trim();
|
|
17
|
+
return text || undefined;
|
|
18
|
+
}
|
|
19
|
+
export function resolveAppPublicPath(appPublicPath = DEFAULT_APP_PUBLIC_PATH) {
|
|
20
|
+
const normalized = String(appPublicPath || DEFAULT_APP_PUBLIC_PATH)
|
|
21
|
+
.trim()
|
|
22
|
+
.replace(/\/+/g, '/')
|
|
23
|
+
.replace(/\/?$/, '/');
|
|
24
|
+
const withLeadingSlash = normalized.startsWith('/') ? normalized : `/${normalized}`;
|
|
25
|
+
return withLeadingSlash || DEFAULT_APP_PUBLIC_PATH;
|
|
26
|
+
}
|
|
27
|
+
export function appendAppPublicPath(appPublicPath, segment, options) {
|
|
28
|
+
const publicPath = resolveAppPublicPath(appPublicPath).replace(/\/+$/, '');
|
|
29
|
+
const normalizedSegment = String(segment || '')
|
|
30
|
+
.trim()
|
|
31
|
+
.replace(/^\/+/, '')
|
|
32
|
+
.replace(options?.trailingSlash ? /\/+$/ : /\/*$/, '');
|
|
33
|
+
const joinedPath = normalizedSegment ? `${publicPath}/${normalizedSegment}` : publicPath || '/';
|
|
34
|
+
if (options?.trailingSlash) {
|
|
35
|
+
return joinedPath.endsWith('/') ? joinedPath : `${joinedPath}/`;
|
|
36
|
+
}
|
|
37
|
+
return joinedPath || '/';
|
|
38
|
+
}
|
|
39
|
+
export function resolveDistPublicPath(appPublicPath) {
|
|
40
|
+
return appendAppPublicPath(appPublicPath, DEFAULT_DIST_SEGMENT, { trailingSlash: true });
|
|
41
|
+
}
|
|
42
|
+
export function buildDefaultCdnBaseUrl(appPublicPath, version) {
|
|
43
|
+
const normalizedVersion = String(version || '')
|
|
44
|
+
.trim()
|
|
45
|
+
.replace(/^\/+|\/+$/g, '');
|
|
46
|
+
return `${resolveDistPublicPath(appPublicPath)}${normalizedVersion}/`;
|
|
47
|
+
}
|
|
48
|
+
export function buildLocalAppUrl(port, appPublicPath) {
|
|
49
|
+
const value = trimValue(port);
|
|
50
|
+
if (!value) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
const publicPath = resolveAppPublicPath(appPublicPath);
|
|
54
|
+
return publicPath === '/' ? `http://127.0.0.1:${value}` : `http://127.0.0.1:${value}${publicPath}`;
|
|
55
|
+
}
|
|
56
|
+
export function buildLocalApiBaseUrl(port, appPublicPath) {
|
|
57
|
+
const value = trimValue(port);
|
|
58
|
+
if (!value) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
return `http://127.0.0.1:${value}${appendAppPublicPath(appPublicPath, 'api', { trailingSlash: false })}`;
|
|
62
|
+
}
|
|
63
|
+
export function resolveDistClientRoot(storagePath) {
|
|
64
|
+
return path.join(storagePath, DIST_CLIENT_DIR);
|
|
65
|
+
}
|
|
66
|
+
export function resolveDistClientVersionRoot(storagePath, version) {
|
|
67
|
+
return path.join(resolveDistClientRoot(storagePath), version);
|
|
68
|
+
}
|
|
69
|
+
export function resolveDistClientActiveVersionFile(storagePath) {
|
|
70
|
+
return path.join(resolveDistClientRoot(storagePath), DIST_CLIENT_ACTIVE_VERSION_FILE);
|
|
71
|
+
}
|
|
72
|
+
export async function readDistClientActiveVersion(storagePath) {
|
|
73
|
+
try {
|
|
74
|
+
const content = await readFile(resolveDistClientActiveVersionFile(storagePath), 'utf8');
|
|
75
|
+
return trimValue(content);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
}
|
package/dist/lib/app-runtime.js
CHANGED
|
@@ -10,6 +10,7 @@ import path from 'node:path';
|
|
|
10
10
|
import { resolveEnvKind } from './auth-store.js';
|
|
11
11
|
import { getEnv, loadAuthConfig } from './auth-store.js';
|
|
12
12
|
import { DEFAULT_DOCKER_CONTAINER_PREFIX, DEFAULT_DOCKER_NETWORK, getEffectiveCliConfigValue, } from './cli-config.js';
|
|
13
|
+
import { resolveManagedLocalEnvFilePath } from './managed-env-file.js';
|
|
13
14
|
import { commandOutput, commandSucceeds, run, runNocoBaseCommand } from './run-npm.js';
|
|
14
15
|
import { buildRuntimeEnvVars } from './runtime-env-vars.js';
|
|
15
16
|
const DOCKER_APP_WORKDIR = '/app/nocobase';
|
|
@@ -84,7 +85,7 @@ export async function resolveManagedAppRuntime(envName) {
|
|
|
84
85
|
env,
|
|
85
86
|
envName: resolvedName,
|
|
86
87
|
source: source === 'git' ? 'git' : source === 'npm' ? 'npm' : 'local',
|
|
87
|
-
projectRoot: env.
|
|
88
|
+
projectRoot: env.sourcePath,
|
|
88
89
|
dockerNetworkName,
|
|
89
90
|
dockerContainerPrefix,
|
|
90
91
|
workspaceName: dockerNetworkName,
|
|
@@ -115,12 +116,20 @@ export function formatMissingManagedAppEnvMessage(envName) {
|
|
|
115
116
|
}
|
|
116
117
|
return 'No NocoBase env is configured yet. Run `nb init --ui` to create one first.';
|
|
117
118
|
}
|
|
119
|
+
export function managedAppLifecycleEnvVars() {
|
|
120
|
+
return {
|
|
121
|
+
APP_ENV: 'production',
|
|
122
|
+
NODE_ENV: 'production',
|
|
123
|
+
};
|
|
124
|
+
}
|
|
118
125
|
export async function runLocalNocoBaseCommand(runtime, args, options) {
|
|
119
126
|
const envVars = await buildRuntimeEnvVars(runtime);
|
|
127
|
+
const appEnvPath = resolveManagedLocalEnvFilePath(runtime);
|
|
120
128
|
await runNocoBaseCommand(args, {
|
|
121
129
|
cwd: runtime.projectRoot,
|
|
122
130
|
env: {
|
|
123
131
|
...envVars,
|
|
132
|
+
APP_ENV_PATH: appEnvPath,
|
|
124
133
|
...options?.env,
|
|
125
134
|
},
|
|
126
135
|
stdio: options?.stdio,
|
package/dist/lib/auth-store.js
CHANGED
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { promises as fs } from 'node:fs';
|
|
10
10
|
import path from 'node:path';
|
|
11
|
+
import { resolveAppPublicPath } from './app-public-path.js';
|
|
11
12
|
import { resolveCliHomeDir, resolveConfiguredEnvPath, resolveEnvRelativePath } from './cli-home.js';
|
|
12
13
|
import { normalizeCliLocale } from './cli-locale.js';
|
|
14
|
+
import { inferConfiguredAppPathFromLegacyConfig, resolveConfiguredAppPath, resolveConfiguredSourcePath, resolveConfiguredStoragePath, } from './env-paths.js';
|
|
13
15
|
import { cleanupCurrentSessionAfterEnvRemoval, resolveEffectiveCurrentEnv, setSessionCurrentEnv, } from './session-store.js';
|
|
14
16
|
function normalizeStoredEnvKind(value) {
|
|
15
17
|
const kind = String(value ?? '').trim();
|
|
@@ -62,7 +64,7 @@ export function resolveEnvKind(config) {
|
|
|
62
64
|
if (source === 'npm' || source === 'git' || source === 'local') {
|
|
63
65
|
return 'local';
|
|
64
66
|
}
|
|
65
|
-
if (String(config.appRootPath ?? '').trim()) {
|
|
67
|
+
if (String(config.appPath ?? '').trim() || String(config.appRootPath ?? '').trim()) {
|
|
66
68
|
return 'local';
|
|
67
69
|
}
|
|
68
70
|
if (readEnvApiBaseUrl(config) || config.auth) {
|
|
@@ -81,16 +83,31 @@ function normalizeEnvConfigEntry(entry) {
|
|
|
81
83
|
...rest,
|
|
82
84
|
...(normalizedKind ? { kind: normalizedKind } : {}),
|
|
83
85
|
...(apiBaseUrl !== undefined ? { apiBaseUrl } : {}),
|
|
86
|
+
...(normalizeOptionalString(entry.appPublicPath) ? { appPublicPath: resolveAppPublicPath(entry.appPublicPath) } : {}),
|
|
84
87
|
};
|
|
85
88
|
}
|
|
86
89
|
function normalizeAuthConfig(config) {
|
|
87
90
|
const settings = config.settings ?? {};
|
|
88
91
|
const locale = normalizeOptionalCliLocale(settings.locale);
|
|
92
|
+
const defaultUiHost = normalizeOptionalString(settings.init?.defaultUiHost);
|
|
93
|
+
const defaultApiHost = normalizeOptionalString(settings.init?.defaultApiHost);
|
|
89
94
|
const updatePolicy = normalizeOptionalCliUpdatePolicy(settings.update?.policy);
|
|
95
|
+
const logRetentionDays = typeof settings.log?.retentionDays === 'number' && Number.isInteger(settings.log.retentionDays)
|
|
96
|
+
? settings.log.retentionDays
|
|
97
|
+
: undefined;
|
|
98
|
+
const logEnabled = typeof settings.log?.enabled === 'boolean' ? settings.log.enabled : undefined;
|
|
90
99
|
return {
|
|
91
100
|
name: config.name || config.dockerResourcePrefix,
|
|
92
101
|
settings: {
|
|
93
102
|
...(locale ? { locale } : {}),
|
|
103
|
+
...(defaultUiHost || defaultApiHost
|
|
104
|
+
? {
|
|
105
|
+
init: {
|
|
106
|
+
...(defaultUiHost ? { defaultUiHost } : {}),
|
|
107
|
+
...(defaultApiHost ? { defaultApiHost } : {}),
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
: {}),
|
|
94
111
|
...(updatePolicy ? { update: { policy: updatePolicy } } : {}),
|
|
95
112
|
...(settings.license?.pkgUrl ? { license: { pkgUrl: normalizeOptionalString(settings.license.pkgUrl) } } : {}),
|
|
96
113
|
...(settings.docker?.network || settings.docker?.containerPrefix
|
|
@@ -103,15 +120,39 @@ function normalizeAuthConfig(config) {
|
|
|
103
120
|
},
|
|
104
121
|
}
|
|
105
122
|
: {}),
|
|
106
|
-
...(settings.bin?.docker || settings.bin?.git || settings.bin?.yarn
|
|
123
|
+
...(settings.bin?.docker || settings.bin?.caddy || settings.bin?.git || settings.bin?.nginx || settings.bin?.yarn
|
|
107
124
|
? {
|
|
108
125
|
bin: {
|
|
109
126
|
...(settings.bin?.docker ? { docker: normalizeOptionalString(settings.bin.docker) } : {}),
|
|
127
|
+
...(settings.bin?.caddy ? { caddy: normalizeOptionalString(settings.bin.caddy) } : {}),
|
|
110
128
|
...(settings.bin?.git ? { git: normalizeOptionalString(settings.bin.git) } : {}),
|
|
129
|
+
...(settings.bin?.nginx ? { nginx: normalizeOptionalString(settings.bin.nginx) } : {}),
|
|
111
130
|
...(settings.bin?.yarn ? { yarn: normalizeOptionalString(settings.bin.yarn) } : {}),
|
|
112
131
|
},
|
|
113
132
|
}
|
|
114
133
|
: {}),
|
|
134
|
+
...(settings.proxy?.nbCliRoot ||
|
|
135
|
+
settings.proxy?.upstreamHost ||
|
|
136
|
+
settings.proxy?.host
|
|
137
|
+
? {
|
|
138
|
+
proxy: {
|
|
139
|
+
...(settings.proxy?.nbCliRoot ? { nbCliRoot: normalizeOptionalString(settings.proxy.nbCliRoot) } : {}),
|
|
140
|
+
...(settings.proxy?.upstreamHost || settings.proxy?.host
|
|
141
|
+
? {
|
|
142
|
+
upstreamHost: normalizeOptionalString(settings.proxy?.upstreamHost ?? settings.proxy.host),
|
|
143
|
+
}
|
|
144
|
+
: {}),
|
|
145
|
+
},
|
|
146
|
+
}
|
|
147
|
+
: {}),
|
|
148
|
+
...(logEnabled !== undefined || logRetentionDays !== undefined
|
|
149
|
+
? {
|
|
150
|
+
log: {
|
|
151
|
+
...(logEnabled !== undefined ? { enabled: logEnabled } : {}),
|
|
152
|
+
...(logRetentionDays !== undefined ? { retentionDays: logRetentionDays } : {}),
|
|
153
|
+
},
|
|
154
|
+
}
|
|
155
|
+
: {}),
|
|
115
156
|
},
|
|
116
157
|
lastEnv: config.lastEnv ||
|
|
117
158
|
config.currentEnv ||
|
|
@@ -205,7 +246,29 @@ export class Env {
|
|
|
205
246
|
return configuredPath;
|
|
206
247
|
}
|
|
207
248
|
}
|
|
208
|
-
|
|
249
|
+
const legacyPath = resolveConfiguredEnvPath(this.config.appRootPath);
|
|
250
|
+
if (legacyPath) {
|
|
251
|
+
return legacyPath;
|
|
252
|
+
}
|
|
253
|
+
return this.kind === 'local' ? this.sourcePath : resolveEnvRelativePath('.');
|
|
254
|
+
}
|
|
255
|
+
get appPath() {
|
|
256
|
+
if (this.kind === 'ssh') {
|
|
257
|
+
const configuredPath = String(this.config.appPath ?? inferConfiguredAppPathFromLegacyConfig(this.config) ?? '').trim();
|
|
258
|
+
if (configuredPath) {
|
|
259
|
+
return configuredPath;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return resolveConfiguredAppPath(this.config) ?? resolveEnvRelativePath('.');
|
|
263
|
+
}
|
|
264
|
+
get sourcePath() {
|
|
265
|
+
if (this.kind === 'ssh') {
|
|
266
|
+
const configuredPath = String(this.config.appRootPath ?? '').trim();
|
|
267
|
+
if (configuredPath) {
|
|
268
|
+
return configuredPath;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return resolveConfiguredSourcePath(this.config) ?? path.join(this.appPath, 'source');
|
|
209
272
|
}
|
|
210
273
|
get storagePath() {
|
|
211
274
|
if (this.kind === 'ssh') {
|
|
@@ -214,7 +277,13 @@ export class Env {
|
|
|
214
277
|
return configuredPath;
|
|
215
278
|
}
|
|
216
279
|
}
|
|
217
|
-
|
|
280
|
+
const resolvedStoragePath = resolveConfiguredStoragePath(this.config);
|
|
281
|
+
if (resolvedStoragePath) {
|
|
282
|
+
return resolvedStoragePath;
|
|
283
|
+
}
|
|
284
|
+
return this.kind === 'local' || this.kind === 'docker'
|
|
285
|
+
? path.join(this.appPath, 'storage')
|
|
286
|
+
: resolveEnvRelativePath('.');
|
|
218
287
|
}
|
|
219
288
|
get appPort() {
|
|
220
289
|
return this.config.appPort;
|
|
@@ -230,6 +299,8 @@ export class Env {
|
|
|
230
299
|
out[key] = String(value);
|
|
231
300
|
};
|
|
232
301
|
put('APP_PORT', this.appPort);
|
|
302
|
+
put('APP_PUBLIC_PATH', this.config.appPublicPath ? resolveAppPublicPath(this.config.appPublicPath) : undefined);
|
|
303
|
+
put('CDN_BASE_URL', this.config.cdnBaseUrl);
|
|
233
304
|
put('APP_KEY', this.config.appKey);
|
|
234
305
|
put('TZ', this.config.timezone);
|
|
235
306
|
put('DB_DIALECT', this.config.dbDialect);
|
|
@@ -358,6 +429,39 @@ export async function updateEnvConnection(envName, updates, options = {}) {
|
|
|
358
429
|
};
|
|
359
430
|
}, options);
|
|
360
431
|
}
|
|
432
|
+
export async function replaceEnvConfig(envName, config, options = {}) {
|
|
433
|
+
await writeEnv(envName, (previous) => {
|
|
434
|
+
if (!previous) {
|
|
435
|
+
throw new Error(`Env "${envName}" is not configured`);
|
|
436
|
+
}
|
|
437
|
+
const { apiBaseUrl: _apiBaseUrl, baseUrl: _baseUrl, apibaseUrl: _legacyApiBaseUrl, accessToken, authType, authUsername, ...rest } = config;
|
|
438
|
+
const nextApiBaseUrl = readEnvApiBaseUrl(config);
|
|
439
|
+
const previousApiBaseUrl = readEnvApiBaseUrl(previous);
|
|
440
|
+
const baseUrlChanged = previousApiBaseUrl !== nextApiBaseUrl;
|
|
441
|
+
const previousAuthType = resolveConfiguredAuthType(previous);
|
|
442
|
+
const nextAuthType = normalizeConfiguredAuthType(authType) ?? (accessToken ? 'token' : undefined);
|
|
443
|
+
const nextAuthUsername = nextAuthType === 'basic' ? normalizeOptionalString(authUsername) : undefined;
|
|
444
|
+
const nextAuth = accessToken
|
|
445
|
+
? {
|
|
446
|
+
type: 'token',
|
|
447
|
+
accessToken,
|
|
448
|
+
}
|
|
449
|
+
: nextAuthType === 'oauth' && !baseUrlChanged && previous?.auth?.type === 'oauth'
|
|
450
|
+
? previous.auth
|
|
451
|
+
: undefined;
|
|
452
|
+
const authChanged = !areAuthConfigsEquivalent(previous?.auth, nextAuth);
|
|
453
|
+
const authTypeChanged = previousAuthType !== nextAuthType;
|
|
454
|
+
const authUsernameChanged = previous?.authUsername !== nextAuthUsername;
|
|
455
|
+
return {
|
|
456
|
+
...rest,
|
|
457
|
+
...(nextApiBaseUrl !== undefined ? { apiBaseUrl: nextApiBaseUrl } : {}),
|
|
458
|
+
...(nextAuthType ? { authType: nextAuthType } : {}),
|
|
459
|
+
...(nextAuthUsername ? { authUsername: nextAuthUsername } : {}),
|
|
460
|
+
...(nextAuth ? { auth: nextAuth } : {}),
|
|
461
|
+
runtime: baseUrlChanged || authChanged || authTypeChanged || authUsernameChanged ? undefined : previous?.runtime,
|
|
462
|
+
};
|
|
463
|
+
}, options);
|
|
464
|
+
}
|
|
361
465
|
export async function setEnvOauthSession(envName, auth, options = {}) {
|
|
362
466
|
await writeEnv(envName, (previous) => ({
|
|
363
467
|
...previous,
|
|
@@ -375,6 +479,17 @@ export async function setEnvRuntime(envName, runtime, options = {}) {
|
|
|
375
479
|
};
|
|
376
480
|
await saveAuthConfig(config, options);
|
|
377
481
|
}
|
|
482
|
+
export async function clearEnvRootSetup(envName, options = {}) {
|
|
483
|
+
const config = await loadExactAuthConfig(options);
|
|
484
|
+
const current = config.envs[envName];
|
|
485
|
+
if (!current) {
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
const { rootUsername: _rootUsername, rootEmail: _rootEmail, rootPassword: _rootPassword, rootNickname: _rootNickname, ...rest } = current;
|
|
489
|
+
config.envs[envName] = rest;
|
|
490
|
+
await saveAuthConfig(config, options);
|
|
491
|
+
return true;
|
|
492
|
+
}
|
|
378
493
|
export async function removeEnv(envName, options = {}) {
|
|
379
494
|
const config = await loadExactAuthConfig(options);
|
|
380
495
|
if (!config.envs[envName]) {
|