@nocobase/cli 2.1.0-alpha.25 → 2.1.0-alpha.26
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 +37 -49
- package/README.zh-CN.md +36 -47
- package/dist/commands/app/down.js +260 -0
- package/dist/commands/app/logs.js +98 -0
- package/dist/commands/app/restart.js +75 -0
- package/dist/commands/app/start.js +252 -0
- package/dist/commands/app/stop.js +98 -0
- package/dist/commands/app/upgrade.js +595 -0
- package/dist/commands/build.js +3 -48
- package/dist/commands/dev.js +3 -147
- package/dist/commands/down.js +3 -188
- package/dist/commands/download.js +4 -856
- package/dist/commands/env/add.js +28 -23
- package/dist/commands/env/info.js +152 -0
- package/dist/commands/env/list.js +23 -9
- package/dist/commands/env/shared.js +158 -0
- package/dist/commands/{prompts-stages.js → examples/prompts-stages.js} +3 -3
- package/dist/commands/{prompts-test.js → examples/prompts-test.js} +3 -3
- package/dist/commands/init.js +84 -6
- package/dist/commands/install.js +288 -61
- package/dist/commands/logs.js +3 -88
- package/dist/commands/plugin/disable.js +64 -0
- package/dist/commands/plugin/enable.js +64 -0
- package/dist/commands/plugin/list.js +62 -0
- package/dist/commands/pm/disable.js +3 -54
- package/dist/commands/pm/enable.js +3 -54
- package/dist/commands/pm/list.js +3 -52
- package/dist/commands/restart.js +3 -65
- package/dist/commands/scaffold/migration.js +1 -1
- package/dist/commands/scaffold/plugin.js +1 -1
- package/dist/commands/skills/remove.js +71 -0
- package/dist/commands/skills/update.js +7 -0
- package/dist/commands/source/build.js +58 -0
- package/dist/commands/source/dev.js +157 -0
- package/dist/commands/source/download.js +866 -0
- package/dist/commands/source/test.js +467 -0
- package/dist/commands/start.js +3 -209
- package/dist/commands/stop.js +3 -88
- package/dist/commands/test.js +3 -457
- package/dist/commands/upgrade.js +3 -585
- package/dist/help/runtime-help.js +3 -0
- package/dist/lib/api-client.js +20 -6
- package/dist/lib/app-health.js +126 -0
- package/dist/lib/app-managed-resources.js +264 -0
- package/dist/lib/auth-store.js +5 -2
- package/dist/lib/cli-home.js +7 -6
- package/dist/lib/cli-locale.js +15 -1
- package/dist/lib/env-config.js +80 -0
- package/dist/lib/prompt-web-ui.js +13 -6
- package/dist/lib/skills-manager.js +34 -7
- package/package.json +27 -4
- package/dist/commands/ps.js +0 -119
|
@@ -0,0 +1,98 @@
|
|
|
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, Flags } from '@oclif/core';
|
|
10
|
+
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runLocalNocoBaseCommand, } from '../../lib/app-runtime.js';
|
|
11
|
+
import { run } from '../../lib/run-npm.js';
|
|
12
|
+
import { printInfo } from '../../lib/ui.js';
|
|
13
|
+
function formatLogsFailure(envName, message) {
|
|
14
|
+
return [
|
|
15
|
+
`Couldn't show logs for "${envName}".`,
|
|
16
|
+
'Check that the saved local app or Docker container still exists on this machine, then try again.',
|
|
17
|
+
`Details: ${message}`,
|
|
18
|
+
].join('\n');
|
|
19
|
+
}
|
|
20
|
+
export default class AppLogs extends Command {
|
|
21
|
+
static hidden = false;
|
|
22
|
+
static description = 'Show NocoBase logs for the selected env. Local npm/git installs use pm2 logs, and Docker installs use docker logs.';
|
|
23
|
+
static examples = [
|
|
24
|
+
'<%= config.bin %> <%= command.id %>',
|
|
25
|
+
'<%= config.bin %> <%= command.id %> --env app1',
|
|
26
|
+
'<%= config.bin %> <%= command.id %> --env app1 --tail 200',
|
|
27
|
+
'<%= config.bin %> <%= command.id %> --env app1 --no-follow',
|
|
28
|
+
];
|
|
29
|
+
static flags = {
|
|
30
|
+
env: Flags.string({
|
|
31
|
+
char: 'e',
|
|
32
|
+
description: 'CLI env name to inspect logs for. Defaults to the current env when omitted',
|
|
33
|
+
}),
|
|
34
|
+
tail: Flags.integer({
|
|
35
|
+
description: 'Number of recent log lines to show before following',
|
|
36
|
+
default: 100,
|
|
37
|
+
min: 0,
|
|
38
|
+
}),
|
|
39
|
+
follow: Flags.boolean({
|
|
40
|
+
char: 'f',
|
|
41
|
+
description: 'Keep streaming new log lines',
|
|
42
|
+
default: true,
|
|
43
|
+
allowNo: true,
|
|
44
|
+
}),
|
|
45
|
+
};
|
|
46
|
+
async run() {
|
|
47
|
+
const { flags } = await this.parse(AppLogs);
|
|
48
|
+
const requestedEnv = flags.env?.trim() || undefined;
|
|
49
|
+
const runtime = await resolveManagedAppRuntime(requestedEnv);
|
|
50
|
+
if (!runtime) {
|
|
51
|
+
this.error(formatMissingManagedAppEnvMessage(requestedEnv));
|
|
52
|
+
}
|
|
53
|
+
if (runtime.kind === 'http') {
|
|
54
|
+
this.error([
|
|
55
|
+
`Can't show runtime logs for "${runtime.envName}" from this machine.`,
|
|
56
|
+
'This env only has an API connection, so there is no saved local app or Docker container to read logs from.',
|
|
57
|
+
'Connect it to a local checkout or reinstall it with npm, git, or Docker if you want CLI-managed logs.',
|
|
58
|
+
].join('\n'));
|
|
59
|
+
}
|
|
60
|
+
if (runtime.kind === 'ssh') {
|
|
61
|
+
this.error([
|
|
62
|
+
`Can't show runtime logs for "${runtime.envName}" yet.`,
|
|
63
|
+
'SSH env support is reserved but not implemented yet.',
|
|
64
|
+
'Use a local or Docker env for CLI-managed logs for now.',
|
|
65
|
+
].join('\n'));
|
|
66
|
+
}
|
|
67
|
+
const tail = String(flags.tail ?? 100);
|
|
68
|
+
const follow = flags.follow !== false;
|
|
69
|
+
printInfo(follow
|
|
70
|
+
? `Showing logs for "${runtime.envName}" (press Ctrl+C to stop).`
|
|
71
|
+
: `Showing recent logs for "${runtime.envName}".`);
|
|
72
|
+
try {
|
|
73
|
+
if (runtime.kind === 'docker') {
|
|
74
|
+
const dockerArgs = ['logs', '--tail', tail];
|
|
75
|
+
if (follow) {
|
|
76
|
+
dockerArgs.push('--follow');
|
|
77
|
+
}
|
|
78
|
+
dockerArgs.push(runtime.containerName);
|
|
79
|
+
await run('docker', dockerArgs, {
|
|
80
|
+
errorName: 'docker logs',
|
|
81
|
+
stdio: 'inherit',
|
|
82
|
+
});
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const localArgs = ['pm2', 'logs', '--lines', tail];
|
|
86
|
+
if (!follow) {
|
|
87
|
+
localArgs.push('--nostream');
|
|
88
|
+
}
|
|
89
|
+
await runLocalNocoBaseCommand(runtime, localArgs, {
|
|
90
|
+
stdio: 'inherit',
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
95
|
+
this.error(formatLogsFailure(runtime.envName, message));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
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, Flags } from '@oclif/core';
|
|
10
|
+
function argvHasToken(argv, tokens) {
|
|
11
|
+
return tokens.some((token) => argv.includes(token));
|
|
12
|
+
}
|
|
13
|
+
function pushFlag(argv, flag, value) {
|
|
14
|
+
if (value !== undefined) {
|
|
15
|
+
argv.push(flag, String(value));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export default class AppRestart extends Command {
|
|
19
|
+
static hidden = false;
|
|
20
|
+
static description = 'Restart NocoBase for the selected env by stopping it first, then starting it again.';
|
|
21
|
+
static examples = [
|
|
22
|
+
'<%= config.bin %> <%= command.id %>',
|
|
23
|
+
'<%= config.bin %> <%= command.id %> --env local',
|
|
24
|
+
'<%= config.bin %> <%= command.id %> --env local --quickstart',
|
|
25
|
+
'<%= config.bin %> <%= command.id %> --env local --port 12000',
|
|
26
|
+
'<%= config.bin %> <%= command.id %> --env local --daemon',
|
|
27
|
+
'<%= config.bin %> <%= command.id %> --env local --no-daemon',
|
|
28
|
+
'<%= config.bin %> <%= command.id %> --env local --instances 2',
|
|
29
|
+
'<%= config.bin %> <%= command.id %> --env local --launch-mode pm2',
|
|
30
|
+
'<%= config.bin %> <%= command.id %> --env local --verbose',
|
|
31
|
+
'<%= config.bin %> <%= command.id %> --env local-docker',
|
|
32
|
+
];
|
|
33
|
+
static flags = {
|
|
34
|
+
env: Flags.string({
|
|
35
|
+
char: 'e',
|
|
36
|
+
description: 'CLI env name to restart. Defaults to the current env when omitted',
|
|
37
|
+
}),
|
|
38
|
+
quickstart: Flags.boolean({ description: 'Quickstart the application after stopping it', required: false }),
|
|
39
|
+
port: Flags.string({ description: 'Port (overrides appPort from env config when set)', char: 'p', required: false }),
|
|
40
|
+
daemon: Flags.boolean({
|
|
41
|
+
description: 'Run the application as a daemon after stopping it (default: true; use --no-daemon to stay in the foreground)',
|
|
42
|
+
char: 'd',
|
|
43
|
+
required: false,
|
|
44
|
+
default: true,
|
|
45
|
+
allowNo: true,
|
|
46
|
+
}),
|
|
47
|
+
instances: Flags.integer({ description: 'Number of instances to run after stopping it', char: 'i', required: false }),
|
|
48
|
+
'launch-mode': Flags.string({ description: 'Launch Mode', required: false, options: ['pm2', 'node'] }),
|
|
49
|
+
verbose: Flags.boolean({
|
|
50
|
+
description: 'Show raw shutdown/startup output from the underlying local or Docker command',
|
|
51
|
+
default: false,
|
|
52
|
+
}),
|
|
53
|
+
};
|
|
54
|
+
async run() {
|
|
55
|
+
const { flags } = await this.parse(AppRestart);
|
|
56
|
+
const stopArgv = [];
|
|
57
|
+
const daemonFlagWasProvided = argvHasToken(this.argv, ['--daemon', '--no-daemon']);
|
|
58
|
+
pushFlag(stopArgv, '--env', flags.env?.trim() || undefined);
|
|
59
|
+
if (flags.verbose) {
|
|
60
|
+
stopArgv.push('--verbose');
|
|
61
|
+
}
|
|
62
|
+
await this.config.runCommand('app:stop', stopArgv);
|
|
63
|
+
const startArgv = [...stopArgv];
|
|
64
|
+
if (flags.quickstart) {
|
|
65
|
+
startArgv.push('--quickstart');
|
|
66
|
+
}
|
|
67
|
+
pushFlag(startArgv, '--port', flags.port);
|
|
68
|
+
if (daemonFlagWasProvided) {
|
|
69
|
+
startArgv.push(flags.daemon === false ? '--no-daemon' : '--daemon');
|
|
70
|
+
}
|
|
71
|
+
pushFlag(startArgv, '--instances', flags.instances);
|
|
72
|
+
pushFlag(startArgv, '--launch-mode', flags['launch-mode']);
|
|
73
|
+
await this.config.runCommand('app:start', startArgv);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
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, Flags } from '@oclif/core';
|
|
10
|
+
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runLocalNocoBaseCommand, startDockerContainer, } from '../../lib/app-runtime.js';
|
|
11
|
+
import { AppHealthCheckError, formatAppUrl, isAppReady, resolveManagedAppApiBaseUrl, waitForAppReady, } from '../../lib/app-health.js';
|
|
12
|
+
import { ensureBuiltinDbReady, ensureSavedLocalSource, recreateSavedDockerApp, } from '../../lib/app-managed-resources.js';
|
|
13
|
+
import { failTask, printInfo, startTask, succeedTask } from '../../lib/ui.js';
|
|
14
|
+
function argvHasToken(argv, tokens) {
|
|
15
|
+
return tokens.some((token) => argv.includes(token));
|
|
16
|
+
}
|
|
17
|
+
function formatDockerStartFailure(envName, message) {
|
|
18
|
+
return [
|
|
19
|
+
`Couldn't start NocoBase for "${envName}".`,
|
|
20
|
+
'Check that the Docker runtime for this env is still available, then try again.',
|
|
21
|
+
`Details: ${message}`,
|
|
22
|
+
].join('\n');
|
|
23
|
+
}
|
|
24
|
+
function formatLocalStartFailure(envName, options) {
|
|
25
|
+
const sourceLabel = options?.source === 'git'
|
|
26
|
+
? 'the local Git checkout'
|
|
27
|
+
: options?.source === 'npm'
|
|
28
|
+
? 'the local npm app'
|
|
29
|
+
: 'the local app';
|
|
30
|
+
const portHint = options?.port ? ` Expected app port: ${options.port}.` : '';
|
|
31
|
+
return [
|
|
32
|
+
`Couldn't start NocoBase for "${envName}".`,
|
|
33
|
+
`The CLI was not able to start ${sourceLabel} successfully.`,
|
|
34
|
+
`Check that the app dependencies, database connection, and local env settings are ready, then try again.${portHint}`,
|
|
35
|
+
].join('\n');
|
|
36
|
+
}
|
|
37
|
+
function formatLocalReadyFailure(envName, message, options) {
|
|
38
|
+
const sourceLabel = options?.source === 'git'
|
|
39
|
+
? 'the local Git checkout'
|
|
40
|
+
: options?.source === 'npm'
|
|
41
|
+
? 'the local npm app'
|
|
42
|
+
: 'the local app';
|
|
43
|
+
const portHint = options?.port ? ` Expected app port: ${options.port}.` : '';
|
|
44
|
+
return [
|
|
45
|
+
`NocoBase did not become ready for "${envName}".`,
|
|
46
|
+
`The CLI started ${sourceLabel}, but the app did not pass its health check in time.`,
|
|
47
|
+
`Check the startup logs, database connection, and local env settings, then try again.${portHint}`,
|
|
48
|
+
`Details: ${message}`,
|
|
49
|
+
].join('\n');
|
|
50
|
+
}
|
|
51
|
+
export default class AppStart extends Command {
|
|
52
|
+
static hidden = false;
|
|
53
|
+
static description = 'Start NocoBase for the selected env. Local npm/git installs run the app command, and Docker installs start the saved app container.';
|
|
54
|
+
static examples = [
|
|
55
|
+
'<%= config.bin %> <%= command.id %>',
|
|
56
|
+
'<%= config.bin %> <%= command.id %> --env local',
|
|
57
|
+
'<%= config.bin %> <%= command.id %> --env local --quickstart',
|
|
58
|
+
'<%= config.bin %> <%= command.id %> --env local --port 12000',
|
|
59
|
+
'<%= config.bin %> <%= command.id %> --env local --daemon',
|
|
60
|
+
'<%= config.bin %> <%= command.id %> --env local --no-daemon',
|
|
61
|
+
'<%= config.bin %> <%= command.id %> --env local --instances 2',
|
|
62
|
+
'<%= config.bin %> <%= command.id %> --env local --launch-mode pm2',
|
|
63
|
+
'<%= config.bin %> <%= command.id %> --env local --verbose',
|
|
64
|
+
'<%= config.bin %> <%= command.id %> --env local-docker',
|
|
65
|
+
];
|
|
66
|
+
static flags = {
|
|
67
|
+
env: Flags.string({
|
|
68
|
+
char: 'e',
|
|
69
|
+
description: 'CLI env name to start. Defaults to the current env when omitted',
|
|
70
|
+
}),
|
|
71
|
+
quickstart: Flags.boolean({ description: 'Quickstart the application', required: false }),
|
|
72
|
+
port: Flags.string({ description: 'Port (overrides appPort from env config when set)', char: 'p', required: false }),
|
|
73
|
+
daemon: Flags.boolean({
|
|
74
|
+
description: 'Run the application as a daemon (default: true; use --no-daemon to stay in the foreground)',
|
|
75
|
+
char: 'd',
|
|
76
|
+
required: false,
|
|
77
|
+
default: true,
|
|
78
|
+
allowNo: true,
|
|
79
|
+
}),
|
|
80
|
+
instances: Flags.integer({ description: 'Number of instances to run', char: 'i', required: false }),
|
|
81
|
+
'launch-mode': Flags.string({ description: 'Launch Mode', required: false, options: ['pm2', 'node'] }),
|
|
82
|
+
verbose: Flags.boolean({
|
|
83
|
+
description: 'Show raw startup output from the underlying local or Docker command',
|
|
84
|
+
default: false,
|
|
85
|
+
}),
|
|
86
|
+
};
|
|
87
|
+
async run() {
|
|
88
|
+
const { flags } = await this.parse(AppStart);
|
|
89
|
+
const requestedEnv = flags.env?.trim() || undefined;
|
|
90
|
+
const daemonFlagWasProvided = argvHasToken(this.argv, ['--daemon', '--no-daemon']);
|
|
91
|
+
const runtime = await resolveManagedAppRuntime(requestedEnv);
|
|
92
|
+
const commandStdio = flags.verbose ? 'inherit' : 'ignore';
|
|
93
|
+
if (!runtime) {
|
|
94
|
+
this.error(formatMissingManagedAppEnvMessage(requestedEnv));
|
|
95
|
+
}
|
|
96
|
+
if (runtime.kind === 'http') {
|
|
97
|
+
this.error([
|
|
98
|
+
`Can't start "${runtime.envName}" from this machine.`,
|
|
99
|
+
'This env only has an API connection, so there is no saved local app or Docker runtime to launch here.',
|
|
100
|
+
'Connect it to a local checkout or reinstall it with npm, git, or Docker if you want CLI-managed start and stop.',
|
|
101
|
+
].join('\n'));
|
|
102
|
+
}
|
|
103
|
+
if (runtime.kind === 'ssh') {
|
|
104
|
+
this.error([
|
|
105
|
+
`Can't start "${runtime.envName}" yet.`,
|
|
106
|
+
'SSH env support is reserved but not implemented yet.',
|
|
107
|
+
'Use a local or Docker env if you need CLI-managed start and stop right now.',
|
|
108
|
+
].join('\n'));
|
|
109
|
+
}
|
|
110
|
+
if (runtime.kind === 'docker') {
|
|
111
|
+
const unsupportedFlags = [
|
|
112
|
+
flags.quickstart ? '--quickstart' : undefined,
|
|
113
|
+
flags.port ? '--port' : undefined,
|
|
114
|
+
daemonFlagWasProvided ? (flags.daemon ? '--daemon' : '--no-daemon') : undefined,
|
|
115
|
+
flags.instances !== undefined ? '--instances' : undefined,
|
|
116
|
+
flags['launch-mode'] ? '--launch-mode' : undefined,
|
|
117
|
+
].filter(Boolean);
|
|
118
|
+
if (unsupportedFlags.length > 0) {
|
|
119
|
+
this.error([
|
|
120
|
+
`Can't apply ${unsupportedFlags.join(', ')} to "${runtime.envName}".`,
|
|
121
|
+
'This env is managed by Docker, so those options are only available for local npm/git installs.',
|
|
122
|
+
`Run \`nb app start --env ${runtime.envName}\` to start the saved container, or recreate the env if you need different runtime settings.`,
|
|
123
|
+
].join('\n'));
|
|
124
|
+
}
|
|
125
|
+
await ensureBuiltinDbReady(runtime, {
|
|
126
|
+
verbose: flags.verbose,
|
|
127
|
+
onStartTask: startTask,
|
|
128
|
+
onSucceedTask: succeedTask,
|
|
129
|
+
onFailTask: failTask,
|
|
130
|
+
});
|
|
131
|
+
const appUrl = formatAppUrl(runtime.env.appPort === undefined || runtime.env.appPort === null
|
|
132
|
+
? undefined
|
|
133
|
+
: String(runtime.env.appPort));
|
|
134
|
+
const apiBaseUrl = resolveManagedAppApiBaseUrl(runtime);
|
|
135
|
+
startTask(`Starting NocoBase for "${runtime.envName}"...`);
|
|
136
|
+
try {
|
|
137
|
+
const state = await startDockerContainer(runtime.containerName, {
|
|
138
|
+
stdio: commandStdio,
|
|
139
|
+
});
|
|
140
|
+
if (state === 'already-running' && await isAppReady(apiBaseUrl)) {
|
|
141
|
+
succeedTask(`NocoBase is already running for "${runtime.envName}"${appUrl ? ` at ${appUrl}` : ''}.`);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
147
|
+
if (/does not exist/i.test(message)) {
|
|
148
|
+
printInfo(`The saved Docker app container for "${runtime.envName}" is missing. Recreating it from the saved Docker env settings...`);
|
|
149
|
+
await recreateSavedDockerApp(runtime, {
|
|
150
|
+
verbose: flags.verbose,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
failTask(`Failed to start NocoBase for "${runtime.envName}".`);
|
|
155
|
+
this.error(formatDockerStartFailure(runtime.envName, message));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
await waitForAppReady({
|
|
159
|
+
envName: runtime.envName,
|
|
160
|
+
apiBaseUrl,
|
|
161
|
+
containerName: runtime.containerName,
|
|
162
|
+
logHint: `You can inspect startup logs with \`nb app logs --env ${runtime.envName}\`.`,
|
|
163
|
+
});
|
|
164
|
+
succeedTask(`NocoBase is running for "${runtime.envName}"${appUrl ? ` at ${appUrl}` : ''}.`);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
await ensureBuiltinDbReady(runtime, {
|
|
168
|
+
verbose: flags.verbose,
|
|
169
|
+
onStartTask: startTask,
|
|
170
|
+
onSucceedTask: succeedTask,
|
|
171
|
+
onFailTask: failTask,
|
|
172
|
+
});
|
|
173
|
+
if (runtime.source === 'npm' || runtime.source === 'git') {
|
|
174
|
+
const runCommand = this.config.runCommand.bind(this.config);
|
|
175
|
+
const downloadableRuntime = runtime;
|
|
176
|
+
await ensureSavedLocalSource(downloadableRuntime, runCommand, {
|
|
177
|
+
verbose: flags.verbose,
|
|
178
|
+
onStartTask: startTask,
|
|
179
|
+
onSucceedTask: succeedTask,
|
|
180
|
+
onFailTask: failTask,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
const npmArgs = ['start'];
|
|
184
|
+
if (flags.quickstart) {
|
|
185
|
+
npmArgs.push('--quickstart');
|
|
186
|
+
}
|
|
187
|
+
if (flags.port) {
|
|
188
|
+
npmArgs.push('--port', flags.port);
|
|
189
|
+
}
|
|
190
|
+
else if (runtime.env.appPort !== undefined && runtime.env.appPort !== null && String(runtime.env.appPort).trim() !== '') {
|
|
191
|
+
npmArgs.push('--port', String(runtime.env.appPort));
|
|
192
|
+
}
|
|
193
|
+
if (flags.daemon !== false) {
|
|
194
|
+
npmArgs.push('--daemon');
|
|
195
|
+
}
|
|
196
|
+
if (flags.instances !== undefined) {
|
|
197
|
+
npmArgs.push('--instances', flags.instances.toString());
|
|
198
|
+
}
|
|
199
|
+
if (flags['launch-mode']) {
|
|
200
|
+
npmArgs.push('--launch-mode', flags['launch-mode']);
|
|
201
|
+
}
|
|
202
|
+
const effectivePort = flags.port
|
|
203
|
+
|| (runtime.env.appPort !== undefined && runtime.env.appPort !== null
|
|
204
|
+
? String(runtime.env.appPort).trim()
|
|
205
|
+
: undefined);
|
|
206
|
+
const appUrl = formatAppUrl(effectivePort);
|
|
207
|
+
const apiBaseUrl = resolveManagedAppApiBaseUrl(runtime, {
|
|
208
|
+
portOverride: effectivePort,
|
|
209
|
+
});
|
|
210
|
+
if (await isAppReady(apiBaseUrl, { requestTimeoutMs: 1_500 })) {
|
|
211
|
+
if (flags.daemon === false) {
|
|
212
|
+
printInfo(`NocoBase is already running for "${runtime.envName}"${appUrl ? ` at ${appUrl}` : ''}. Use \`nb app stop --env ${runtime.envName}\` before starting it again in the foreground.`);
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
succeedTask(`NocoBase is already running for "${runtime.envName}"${appUrl ? ` at ${appUrl}` : ''}.`);
|
|
216
|
+
}
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (flags.daemon === false) {
|
|
220
|
+
printInfo(`Starting NocoBase for "${runtime.envName}" in the foreground${appUrl ? ` at ${appUrl}` : ''}. Press Ctrl+C to stop.`);
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
startTask(`Starting NocoBase for "${runtime.envName}" in the background...`);
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
await runLocalNocoBaseCommand(runtime, npmArgs, {
|
|
227
|
+
stdio: commandStdio,
|
|
228
|
+
});
|
|
229
|
+
if (flags.daemon !== false) {
|
|
230
|
+
await waitForAppReady({
|
|
231
|
+
envName: runtime.envName,
|
|
232
|
+
apiBaseUrl,
|
|
233
|
+
logHint: `You can inspect startup logs with \`nb app logs --env ${runtime.envName}\`.`,
|
|
234
|
+
});
|
|
235
|
+
succeedTask(`NocoBase is running for "${runtime.envName}"${appUrl ? ` at ${appUrl}` : ''}.`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
failTask(`Failed to start NocoBase for "${runtime.envName}".`);
|
|
240
|
+
if (error instanceof AppHealthCheckError) {
|
|
241
|
+
this.error(formatLocalReadyFailure(runtime.envName, error.message, {
|
|
242
|
+
port: effectivePort,
|
|
243
|
+
source: runtime.source,
|
|
244
|
+
}));
|
|
245
|
+
}
|
|
246
|
+
this.error(formatLocalStartFailure(runtime.envName, {
|
|
247
|
+
port: effectivePort,
|
|
248
|
+
source: runtime.source,
|
|
249
|
+
}));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
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, Flags } from '@oclif/core';
|
|
10
|
+
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runLocalNocoBaseCommand, stopDockerContainer, } from '../../lib/app-runtime.js';
|
|
11
|
+
import { failTask, startTask, succeedTask } from '../../lib/ui.js';
|
|
12
|
+
function formatStopFailure(envName, message) {
|
|
13
|
+
if (/does not exist/i.test(message)) {
|
|
14
|
+
return [
|
|
15
|
+
`Can't stop NocoBase for "${envName}" yet.`,
|
|
16
|
+
'The saved Docker app for this env could not be found on this machine.',
|
|
17
|
+
'If it was removed manually, reinstall or reconnect the env before trying again.',
|
|
18
|
+
`Details: ${message}`,
|
|
19
|
+
].join('\n');
|
|
20
|
+
}
|
|
21
|
+
return [
|
|
22
|
+
`Couldn't stop NocoBase for "${envName}".`,
|
|
23
|
+
'Check that the local app or Docker runtime is still available, then try again.',
|
|
24
|
+
`Details: ${message}`,
|
|
25
|
+
].join('\n');
|
|
26
|
+
}
|
|
27
|
+
export default class AppStop extends Command {
|
|
28
|
+
static hidden = false;
|
|
29
|
+
static description = 'Stop NocoBase for the selected env. Local npm/git installs stop the app process, and Docker installs stop the saved app container.';
|
|
30
|
+
static examples = [
|
|
31
|
+
'<%= config.bin %> <%= command.id %>',
|
|
32
|
+
'<%= config.bin %> <%= command.id %> --env local',
|
|
33
|
+
'<%= config.bin %> <%= command.id %> --env local --verbose',
|
|
34
|
+
'<%= config.bin %> <%= command.id %> --env local-docker',
|
|
35
|
+
];
|
|
36
|
+
static flags = {
|
|
37
|
+
env: Flags.string({
|
|
38
|
+
char: 'e',
|
|
39
|
+
description: 'CLI env name to stop. Defaults to the current env when omitted',
|
|
40
|
+
}),
|
|
41
|
+
verbose: Flags.boolean({
|
|
42
|
+
description: 'Show raw shutdown output from the underlying local or Docker command',
|
|
43
|
+
default: false,
|
|
44
|
+
}),
|
|
45
|
+
};
|
|
46
|
+
async run() {
|
|
47
|
+
const { flags } = await this.parse(AppStop);
|
|
48
|
+
const requestedEnv = flags.env?.trim() || undefined;
|
|
49
|
+
const runtime = await resolveManagedAppRuntime(requestedEnv);
|
|
50
|
+
const commandStdio = flags.verbose ? 'inherit' : 'ignore';
|
|
51
|
+
if (!runtime) {
|
|
52
|
+
this.error(formatMissingManagedAppEnvMessage(requestedEnv));
|
|
53
|
+
}
|
|
54
|
+
if (runtime.kind === 'http') {
|
|
55
|
+
this.error([
|
|
56
|
+
`Can't stop "${runtime.envName}" from this machine.`,
|
|
57
|
+
'This env only has an API connection, so there is no saved local app or Docker runtime to stop here.',
|
|
58
|
+
'If the app is running on a server, stop it there or reconnect this env to a local runtime first.',
|
|
59
|
+
].join('\n'));
|
|
60
|
+
}
|
|
61
|
+
if (runtime.kind === 'ssh') {
|
|
62
|
+
this.error([
|
|
63
|
+
`Can't stop "${runtime.envName}" yet.`,
|
|
64
|
+
'SSH env support is reserved but not implemented yet.',
|
|
65
|
+
'Use a local or Docker env if you need CLI-managed stop right now.',
|
|
66
|
+
].join('\n'));
|
|
67
|
+
}
|
|
68
|
+
if (runtime.kind === 'docker') {
|
|
69
|
+
startTask(`Stopping NocoBase for "${runtime.envName}"...`);
|
|
70
|
+
try {
|
|
71
|
+
const state = await stopDockerContainer(runtime.containerName, {
|
|
72
|
+
stdio: commandStdio,
|
|
73
|
+
});
|
|
74
|
+
succeedTask(state === 'already-stopped'
|
|
75
|
+
? `NocoBase is already stopped for "${runtime.envName}".`
|
|
76
|
+
: `NocoBase has stopped for "${runtime.envName}".`);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
80
|
+
failTask(`Failed to stop NocoBase for "${runtime.envName}".`);
|
|
81
|
+
this.error(formatStopFailure(runtime.envName, message));
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
startTask(`Stopping NocoBase for "${runtime.envName}"...`);
|
|
86
|
+
try {
|
|
87
|
+
await runLocalNocoBaseCommand(runtime, ['pm2', 'kill'], {
|
|
88
|
+
stdio: commandStdio,
|
|
89
|
+
});
|
|
90
|
+
succeedTask(`NocoBase has stopped for "${runtime.envName}".`);
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
94
|
+
failTask(`Failed to stop NocoBase for "${runtime.envName}".`);
|
|
95
|
+
this.error(formatStopFailure(runtime.envName, message));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|