@nocobase/cli 2.1.0-alpha.45 → 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 -32
- 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 +92 -20
- package/dist/commands/app/shared.js +3 -2
- package/dist/commands/app/start.js +143 -38
- package/dist/commands/app/stop.js +31 -2
- package/dist/commands/app/upgrade.js +11 -7
- package/dist/commands/config/delete.js +10 -2
- package/dist/commands/config/get.js +7 -1
- package/dist/commands/config/set.js +12 -2
- package/dist/commands/db/ps.js +5 -18
- package/dist/commands/env/add.js +24 -44
- package/dist/commands/env/auth.js +1 -1
- package/dist/commands/env/info.js +3 -2
- package/dist/commands/env/list.js +4 -5
- 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/status.js +10 -7
- package/dist/commands/env/update.js +385 -21
- package/dist/commands/init.js +368 -98
- package/dist/commands/install.js +271 -104
- 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/self/update.js +58 -1
- package/dist/commands/source/dev.js +5 -5
- package/dist/commands/source/test.js +13 -14
- package/dist/lib/app-health.js +39 -32
- package/dist/lib/app-managed-resources.js +25 -9
- package/dist/lib/app-public-path.js +80 -0
- package/dist/lib/app-runtime.js +12 -3
- package/dist/lib/auth-store.js +128 -4
- package/dist/lib/backup.js +3 -3
- package/dist/lib/bootstrap.js +14 -8
- package/dist/lib/cli-config.js +229 -3
- package/dist/lib/cli-entry-error.js +52 -0
- package/dist/lib/command-log.js +284 -0
- package/dist/lib/docker-env-file.js +10 -2
- package/dist/lib/docker-log-stream.js +45 -0
- package/dist/lib/env-auth.js +25 -14
- 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 +22 -2
- package/dist/lib/startup-update.js +74 -50
- package/dist/lib/ui.js +2 -1
- package/dist/locale/en-US.json +122 -22
- package/dist/locale/zh-CN.json +119 -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
|
+
}
|
|
@@ -8,14 +8,29 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Command, Flags } from '@oclif/core';
|
|
10
10
|
import { confirm } from "../../lib/inquirer.js";
|
|
11
|
+
import { updateNocoBaseSkills } from '../../lib/skills-manager.js';
|
|
11
12
|
import { setVerboseMode } from '../../lib/ui.js';
|
|
12
13
|
import { formatSelfUpdateUnavailableMessage, formatUnsupportedSelfUpdateMessage, inspectSelfStatus, updateSelf, } from '../../lib/self-manager.js';
|
|
14
|
+
function formatSkillsUpdateMessage(result, verbose) {
|
|
15
|
+
if (result.action === 'noop') {
|
|
16
|
+
if (result.reason === 'not-installed') {
|
|
17
|
+
return verbose
|
|
18
|
+
? 'NocoBase AI coding skills are not installed globally. Run `nb skills install` first.'
|
|
19
|
+
: 'Skipped skills update because NocoBase AI coding skills are not installed.';
|
|
20
|
+
}
|
|
21
|
+
return verbose
|
|
22
|
+
? 'NocoBase AI coding skills are already up to date globally.'
|
|
23
|
+
: 'NocoBase AI coding skills are up to date.';
|
|
24
|
+
}
|
|
25
|
+
return verbose ? 'Updated the global NocoBase AI coding skills.' : 'Updated NocoBase AI coding skills globally.';
|
|
26
|
+
}
|
|
13
27
|
export default class SelfUpdate extends Command {
|
|
14
28
|
static summary = 'Update the globally installed NocoBase CLI';
|
|
15
29
|
static description = 'Update the current NocoBase CLI install when it is managed by a standard global npm install.';
|
|
16
30
|
static examples = [
|
|
17
31
|
'<%= config.bin %> <%= command.id %>',
|
|
18
32
|
'<%= config.bin %> <%= command.id %> --yes',
|
|
33
|
+
'<%= config.bin %> <%= command.id %> --skills',
|
|
19
34
|
'<%= config.bin %> <%= command.id %> --channel alpha --json',
|
|
20
35
|
];
|
|
21
36
|
static flags = {
|
|
@@ -33,6 +48,10 @@ export default class SelfUpdate extends Command {
|
|
|
33
48
|
description: 'Output the result as JSON',
|
|
34
49
|
default: false,
|
|
35
50
|
}),
|
|
51
|
+
skills: Flags.boolean({
|
|
52
|
+
description: 'Also update the globally installed NocoBase AI coding skills',
|
|
53
|
+
default: false,
|
|
54
|
+
}),
|
|
36
55
|
verbose: Flags.boolean({
|
|
37
56
|
description: 'Show detailed update output',
|
|
38
57
|
default: false,
|
|
@@ -50,11 +69,14 @@ export default class SelfUpdate extends Command {
|
|
|
50
69
|
if (!status.latestVersion && status.registryError) {
|
|
51
70
|
this.error(formatSelfUpdateUnavailableMessage(status));
|
|
52
71
|
}
|
|
72
|
+
let shouldUpdateSkills = flags.skills;
|
|
53
73
|
if (!flags.yes && status.updateAvailable) {
|
|
54
74
|
let confirmed = false;
|
|
55
75
|
try {
|
|
56
76
|
confirmed = await confirm({
|
|
57
|
-
message:
|
|
77
|
+
message: flags.skills
|
|
78
|
+
? `Update ${status.packageName} from ${status.currentVersion} to ${status.latestVersion} and refresh the globally installed NocoBase AI coding skills?`
|
|
79
|
+
: `Update ${status.packageName} from ${status.currentVersion} to ${status.latestVersion}?`,
|
|
58
80
|
default: false,
|
|
59
81
|
});
|
|
60
82
|
}
|
|
@@ -69,6 +91,24 @@ export default class SelfUpdate extends Command {
|
|
|
69
91
|
channel: flags.channel,
|
|
70
92
|
verbose: flags.verbose,
|
|
71
93
|
});
|
|
94
|
+
if (flags.skills && !flags.yes && !status.updateAvailable) {
|
|
95
|
+
let confirmed = false;
|
|
96
|
+
try {
|
|
97
|
+
confirmed = await confirm({
|
|
98
|
+
message: 'Update the globally installed NocoBase AI coding skills?',
|
|
99
|
+
default: true,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
shouldUpdateSkills = confirmed;
|
|
106
|
+
}
|
|
107
|
+
const skillsResult = shouldUpdateSkills
|
|
108
|
+
? await updateNocoBaseSkills({
|
|
109
|
+
verbose: flags.verbose,
|
|
110
|
+
})
|
|
111
|
+
: undefined;
|
|
72
112
|
if (flags.json) {
|
|
73
113
|
this.log(JSON.stringify({
|
|
74
114
|
ok: true,
|
|
@@ -79,6 +119,17 @@ export default class SelfUpdate extends Command {
|
|
|
79
119
|
channel: result.status.channel,
|
|
80
120
|
fromVersion: result.status.currentVersion,
|
|
81
121
|
toVersion: result.targetVersion,
|
|
122
|
+
skills: skillsResult
|
|
123
|
+
? {
|
|
124
|
+
action: skillsResult.action,
|
|
125
|
+
reason: skillsResult.action === 'noop' ? skillsResult.reason : undefined,
|
|
126
|
+
globalRoot: skillsResult.status.globalRoot,
|
|
127
|
+
workspaceRoot: skillsResult.status.workspaceRoot,
|
|
128
|
+
installedSkillNames: skillsResult.status.installedSkillNames,
|
|
129
|
+
installedVersion: skillsResult.status.installedVersion,
|
|
130
|
+
installedRef: skillsResult.status.installedRef,
|
|
131
|
+
}
|
|
132
|
+
: undefined,
|
|
82
133
|
}, null, 2));
|
|
83
134
|
return;
|
|
84
135
|
}
|
|
@@ -86,10 +137,16 @@ export default class SelfUpdate extends Command {
|
|
|
86
137
|
this.log(flags.verbose
|
|
87
138
|
? `NocoBase CLI is already up to date at ${result.status.currentVersion}.`
|
|
88
139
|
: `NocoBase CLI is up to date: ${result.status.currentVersion}.`);
|
|
140
|
+
if (skillsResult) {
|
|
141
|
+
this.log(formatSkillsUpdateMessage(skillsResult, flags.verbose));
|
|
142
|
+
}
|
|
89
143
|
return;
|
|
90
144
|
}
|
|
91
145
|
this.log(flags.verbose
|
|
92
146
|
? `Updated NocoBase CLI from ${result.status.currentVersion} using ${result.packageSpec}${result.targetVersion ? ` (latest ${result.status.channel} resolves to ${result.targetVersion})` : ''}.`
|
|
93
147
|
: `Updated NocoBase CLI: ${result.status.currentVersion} -> ${result.targetVersion}.`);
|
|
148
|
+
if (skillsResult) {
|
|
149
|
+
this.log(formatSkillsUpdateMessage(skillsResult, flags.verbose));
|
|
150
|
+
}
|
|
94
151
|
}
|
|
95
152
|
}
|
|
@@ -16,20 +16,20 @@ function formatUnsupportedRuntimeMessage(kind, envName) {
|
|
|
16
16
|
return [
|
|
17
17
|
`Can't run dev mode for "${envName}".`,
|
|
18
18
|
'This env is managed by Docker, but `nb source dev` requires a local npm or Git source directory.',
|
|
19
|
-
`Use \`nb app logs --env ${envName}\` to inspect the Docker app, or create a source-based env with \`nb init --env ${envName} --source git\`.`,
|
|
19
|
+
`Use \`nb app logs --env ${envName}\` to inspect the Docker app, or create a source-based env with \`nb init --ui --env ${envName} --source git\`.`,
|
|
20
20
|
].join('\n');
|
|
21
21
|
}
|
|
22
22
|
if (kind === 'ssh') {
|
|
23
23
|
return [
|
|
24
24
|
`Can't run dev mode for "${envName}" yet.`,
|
|
25
25
|
'SSH env support is reserved but not implemented yet.',
|
|
26
|
-
`Create a source-based env with \`nb init --env ${envName} --source git\` if you want local development mode right now.`,
|
|
26
|
+
`Create a source-based env with \`nb init --ui --env ${envName} --source git\` if you want local development mode right now.`,
|
|
27
27
|
].join('\n');
|
|
28
28
|
}
|
|
29
29
|
return [
|
|
30
30
|
`Can't run dev mode for "${envName}".`,
|
|
31
31
|
'This env only has an API connection, but `nb source dev` requires a local npm or Git source directory.',
|
|
32
|
-
`Create a source-based env with \`nb init --env ${envName} --source git\` if you want local development mode.`,
|
|
32
|
+
`Create a source-based env with \`nb init --ui --env ${envName} --source git\` if you want local development mode.`,
|
|
33
33
|
].join('\n');
|
|
34
34
|
}
|
|
35
35
|
function appUrlForPort(port) {
|
|
@@ -129,8 +129,8 @@ export default class SourceDev extends Command {
|
|
|
129
129
|
this.error(formatUnsupportedRuntimeMessage(runtime.kind, runtime.envName));
|
|
130
130
|
}
|
|
131
131
|
announceTargetEnv(runtime.envName);
|
|
132
|
-
const devPort = flags.port
|
|
133
|
-
|
|
132
|
+
const devPort = flags.port ||
|
|
133
|
+
(runtime.env.appPort !== undefined && runtime.env.appPort !== null
|
|
134
134
|
? String(runtime.env.appPort).trim()
|
|
135
135
|
: undefined);
|
|
136
136
|
const appUrl = appUrlForPort(devPort);
|
|
@@ -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,8 @@
|
|
|
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';
|
|
10
|
+
import { startDockerLogFollower } from './docker-log-stream.js';
|
|
9
11
|
import { printInfo } from './ui.js';
|
|
10
12
|
const APP_HEALTH_CHECK_INTERVAL_MS = 2_000;
|
|
11
13
|
const APP_HEALTH_CHECK_TIMEOUT_MS = 600_000;
|
|
@@ -55,14 +57,13 @@ async function requestAppHealthCheck(params) {
|
|
|
55
57
|
}
|
|
56
58
|
export class AppHealthCheckError extends Error {
|
|
57
59
|
}
|
|
58
|
-
export function formatAppUrl(port) {
|
|
59
|
-
|
|
60
|
-
return value ? `http://127.0.0.1:${value}` : undefined;
|
|
60
|
+
export function formatAppUrl(port, appPublicPath) {
|
|
61
|
+
return buildLocalAppUrl(port, appPublicPath);
|
|
61
62
|
}
|
|
62
63
|
export function resolveManagedAppApiBaseUrl(runtime, options) {
|
|
63
64
|
const override = trimValue(options?.portOverride);
|
|
64
65
|
if (override) {
|
|
65
|
-
return
|
|
66
|
+
return buildLocalApiBaseUrl(override, runtime.env.config?.appPublicPath);
|
|
66
67
|
}
|
|
67
68
|
const baseUrl = trimValue(runtime.env.baseUrl);
|
|
68
69
|
if (baseUrl) {
|
|
@@ -71,7 +72,7 @@ export function resolveManagedAppApiBaseUrl(runtime, options) {
|
|
|
71
72
|
const appPort = runtime.env.appPort === undefined || runtime.env.appPort === null
|
|
72
73
|
? ''
|
|
73
74
|
: trimValue(runtime.env.appPort);
|
|
74
|
-
return appPort
|
|
75
|
+
return buildLocalApiBaseUrl(appPort, runtime.env.config?.appPublicPath);
|
|
75
76
|
}
|
|
76
77
|
export async function isAppReady(apiBaseUrl, options) {
|
|
77
78
|
const baseUrl = trimValue(apiBaseUrl);
|
|
@@ -99,34 +100,40 @@ export async function waitForAppReady(params) {
|
|
|
99
100
|
let lastMessage = 'No response yet';
|
|
100
101
|
let nextProgressLogAt = startedAt + progressLogIntervalMs;
|
|
101
102
|
printInfo(`Waiting for NocoBase to become ready for "${params.envName}"...`);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const now = Date.now();
|
|
113
|
-
if (now >= nextProgressLogAt) {
|
|
114
|
-
const elapsedSeconds = Math.max(1, Math.floor((now - startedAt) / 1000));
|
|
115
|
-
printInfo(`Still waiting for "${params.envName}"... (${elapsedSeconds}s elapsed)`);
|
|
116
|
-
while (nextProgressLogAt <= now) {
|
|
117
|
-
nextProgressLogAt += progressLogIntervalMs;
|
|
103
|
+
const dockerLogFollower = params.verbose && params.containerName ? startDockerLogFollower(params.containerName) : undefined;
|
|
104
|
+
try {
|
|
105
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
106
|
+
const result = await requestAppHealthCheck({
|
|
107
|
+
healthCheckUrl,
|
|
108
|
+
fetchImpl: params.fetchImpl,
|
|
109
|
+
requestTimeoutMs: params.requestTimeoutMs,
|
|
110
|
+
});
|
|
111
|
+
if (result.ok) {
|
|
112
|
+
return;
|
|
118
113
|
}
|
|
114
|
+
lastMessage = result.message;
|
|
115
|
+
const now = Date.now();
|
|
116
|
+
if (now >= nextProgressLogAt) {
|
|
117
|
+
const elapsedSeconds = Math.max(1, Math.floor((now - startedAt) / 1000));
|
|
118
|
+
printInfo(`Still waiting for "${params.envName}"... (${elapsedSeconds}s elapsed)`);
|
|
119
|
+
while (nextProgressLogAt <= now) {
|
|
120
|
+
nextProgressLogAt += progressLogIntervalMs;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const remainingMs = timeoutMs - (Date.now() - startedAt);
|
|
124
|
+
if (remainingMs <= 0) {
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
await sleep(Math.min(intervalMs, remainingMs));
|
|
119
128
|
}
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
129
|
+
const hints = [
|
|
130
|
+
params.logHint,
|
|
131
|
+
params.containerName ? `docker logs ${params.containerName}` : undefined,
|
|
132
|
+
].filter(Boolean);
|
|
133
|
+
const hintText = hints.length > 0 ? ` ${hints.join(' ')}` : '';
|
|
134
|
+
throw new AppHealthCheckError(`NocoBase did not become ready in time for "${params.envName}". Expected \`${healthCheckUrl}\` to respond with \`ok\`, but the last status was: ${lastMessage}.${hintText}`);
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
await dockerLogFollower?.stop();
|
|
125
138
|
}
|
|
126
|
-
const hints = [
|
|
127
|
-
params.logHint,
|
|
128
|
-
params.containerName ? `docker logs ${params.containerName}` : undefined,
|
|
129
|
-
].filter(Boolean);
|
|
130
|
-
const hintText = hints.length > 0 ? ` ${hints.join(' ')}` : '';
|
|
131
|
-
throw new AppHealthCheckError(`NocoBase did not become ready in time for "${params.envName}". Expected \`${healthCheckUrl}\` to respond with \`ok\`, but the last status was: ${lastMessage}.${hintText}`);
|
|
132
139
|
}
|
|
@@ -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') {
|
|
@@ -82,7 +94,7 @@ function formatSavedDockerSettingsIncomplete(envName, missing) {
|
|
|
82
94
|
return [
|
|
83
95
|
`Can't start NocoBase for "${envName}" yet.`,
|
|
84
96
|
`The saved Docker settings for this env are incomplete. Missing: ${missing.join(', ')}.`,
|
|
85
|
-
|
|
97
|
+
`Re-run \`nb init --ui --env ${envName}\` to refresh this env config, then try again.`,
|
|
86
98
|
].join('\n');
|
|
87
99
|
}
|
|
88
100
|
function formatDockerAppRecreateFailure(envName, message) {
|
|
@@ -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,
|
|
@@ -110,17 +111,25 @@ export function formatMissingManagedAppEnvMessage(envName) {
|
|
|
110
111
|
if (requested) {
|
|
111
112
|
return [
|
|
112
113
|
`Env "${requested}" is not configured in this workspace.`,
|
|
113
|
-
`If you want to create a new NocoBase AI environment, run \`nb init --env ${requested}\` first.`,
|
|
114
|
+
`If you want to create a new NocoBase AI environment, run \`nb init --ui --env ${requested}\` first.`,
|
|
114
115
|
].join('\n');
|
|
115
116
|
}
|
|
116
|
-
return 'No NocoBase env is configured yet. Run `nb init` to create one first.';
|
|
117
|
+
return 'No NocoBase env is configured yet. Run `nb init --ui` to create one first.';
|
|
118
|
+
}
|
|
119
|
+
export function managedAppLifecycleEnvVars() {
|
|
120
|
+
return {
|
|
121
|
+
APP_ENV: 'production',
|
|
122
|
+
NODE_ENV: 'production',
|
|
123
|
+
};
|
|
117
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,
|