@nocobase/cli 2.1.0-alpha.20 → 2.1.0-alpha.21
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 +256 -89
- package/README.zh-CN.md +332 -0
- package/bin/run.js +21 -2
- package/dist/commands/build.js +7 -1
- package/dist/commands/db/logs.js +85 -0
- package/dist/commands/db/ps.js +60 -0
- package/dist/commands/db/shared.js +81 -0
- package/dist/commands/db/start.js +55 -7
- package/dist/commands/db/stop.js +70 -0
- package/dist/commands/dev.js +112 -21
- package/dist/commands/down.js +193 -0
- package/dist/commands/download.js +622 -183
- package/dist/commands/env/add.js +233 -131
- package/dist/commands/env/auth.js +9 -8
- package/dist/commands/init.js +696 -103
- package/dist/commands/install.js +1588 -566
- package/dist/commands/logs.js +90 -0
- package/dist/commands/pm/disable.js +35 -3
- package/dist/commands/pm/enable.js +35 -3
- package/dist/commands/pm/list.js +37 -4
- package/dist/commands/prompts-stages.js +144 -0
- package/dist/commands/prompts-test.js +175 -0
- package/dist/commands/ps.js +116 -0
- package/dist/commands/start.js +171 -15
- package/dist/commands/stop.js +90 -0
- package/dist/commands/upgrade.js +559 -11
- package/dist/lib/app-runtime.js +142 -0
- package/dist/lib/auth-store.js +44 -3
- package/dist/lib/bootstrap.js +7 -3
- package/dist/lib/env-auth.js +427 -82
- package/dist/lib/prompt-catalog.js +552 -0
- package/dist/lib/prompt-validators.js +184 -0
- package/dist/lib/prompt-web-ui.js +2027 -0
- package/dist/lib/run-npm.js +71 -7
- package/package.json +3 -3
- package/dist/commands/restart.js +0 -32
- package/dist/lib/init-browser-wizard.js +0 -431
|
@@ -0,0 +1,184 @@
|
|
|
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 { spawn } from 'node:child_process';
|
|
10
|
+
import net from 'node:net';
|
|
11
|
+
const API_BASE_URL_EXAMPLE = 'http://localhost:13000/api';
|
|
12
|
+
const ENV_KEY_PATTERN = /^[A-Za-z0-9]+$/;
|
|
13
|
+
const TCP_PORT_EXAMPLE = '13000';
|
|
14
|
+
export function validateApiBaseUrl(value) {
|
|
15
|
+
const raw = String(value ?? '').trim();
|
|
16
|
+
if (raw === '') {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
let url;
|
|
20
|
+
try {
|
|
21
|
+
url = new URL(raw);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return `Enter a valid URL, for example ${API_BASE_URL_EXAMPLE}.`;
|
|
25
|
+
}
|
|
26
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
27
|
+
return `URL must start with http:// or https://, for example ${API_BASE_URL_EXAMPLE}.`;
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
export function validateEnvKey(value) {
|
|
32
|
+
const raw = String(value ?? '').trim();
|
|
33
|
+
if (raw === '') {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
if (!ENV_KEY_PATTERN.test(raw)) {
|
|
37
|
+
return 'Use letters and numbers only.';
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
function parseTcpPort(value) {
|
|
42
|
+
const raw = String(value ?? '').trim();
|
|
43
|
+
if (raw === '') {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
if (!/^\d+$/.test(raw)) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
const port = Number(raw);
|
|
50
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
return port;
|
|
54
|
+
}
|
|
55
|
+
export function validateTcpPort(value) {
|
|
56
|
+
const raw = String(value ?? '').trim();
|
|
57
|
+
if (raw === '') {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
const port = parseTcpPort(raw);
|
|
61
|
+
if (port === undefined) {
|
|
62
|
+
return `Enter a valid TCP port between 1 and 65535, for example ${TCP_PORT_EXAMPLE}.`;
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
async function canListenOnTcpPort(port) {
|
|
67
|
+
return await new Promise((resolve) => {
|
|
68
|
+
const server = net.createServer();
|
|
69
|
+
const resolveAndCleanup = (result) => {
|
|
70
|
+
server.removeAllListeners();
|
|
71
|
+
if (server.listening) {
|
|
72
|
+
server.close(() => resolve(result));
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
resolve(result);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
server.once('error', () => {
|
|
79
|
+
resolveAndCleanup(false);
|
|
80
|
+
});
|
|
81
|
+
server.once('listening', () => {
|
|
82
|
+
resolveAndCleanup(true);
|
|
83
|
+
});
|
|
84
|
+
server.listen(port, '127.0.0.1');
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function parseDockerPublishedTcpPorts(output) {
|
|
88
|
+
const ports = new Set();
|
|
89
|
+
for (const line of output.split(/\r?\n/)) {
|
|
90
|
+
for (const segment of line.split(',')) {
|
|
91
|
+
const match = segment.trim().match(/^(?:.+:)?(\d+)->\d+(?:-\d+)?\/tcp$/i);
|
|
92
|
+
if (!match) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const port = Number.parseInt(match[1], 10);
|
|
96
|
+
if (Number.isInteger(port) && port >= 1 && port <= 65535) {
|
|
97
|
+
ports.add(port);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return ports;
|
|
102
|
+
}
|
|
103
|
+
async function getDockerPublishedTcpPorts() {
|
|
104
|
+
return await new Promise((resolve) => {
|
|
105
|
+
let settled = false;
|
|
106
|
+
const stdoutChunks = [];
|
|
107
|
+
const finish = (ports) => {
|
|
108
|
+
if (settled) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
settled = true;
|
|
112
|
+
resolve(ports);
|
|
113
|
+
};
|
|
114
|
+
const child = spawn('docker', ['ps', '--format', '{{.Ports}}']);
|
|
115
|
+
child.stdout?.on('data', (chunk) => {
|
|
116
|
+
stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
|
|
117
|
+
});
|
|
118
|
+
child.once('error', () => {
|
|
119
|
+
finish(new Set());
|
|
120
|
+
});
|
|
121
|
+
child.once('close', (code) => {
|
|
122
|
+
if (code !== 0) {
|
|
123
|
+
finish(new Set());
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
finish(parseDockerPublishedTcpPorts(Buffer.concat(stdoutChunks).toString('utf8')));
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
async function allocateAvailableTcpPort() {
|
|
131
|
+
return await new Promise((resolve, reject) => {
|
|
132
|
+
const server = net.createServer();
|
|
133
|
+
server.once('error', reject);
|
|
134
|
+
server.listen(0, '127.0.0.1', () => {
|
|
135
|
+
const address = server.address();
|
|
136
|
+
const port = address && typeof address === 'object'
|
|
137
|
+
? address.port
|
|
138
|
+
: undefined;
|
|
139
|
+
if (!port) {
|
|
140
|
+
server.close(() => {
|
|
141
|
+
reject(new Error('Failed to allocate an available TCP port.'));
|
|
142
|
+
});
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
server.close((error) => {
|
|
146
|
+
if (error) {
|
|
147
|
+
reject(error);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
resolve(String(port));
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
export async function findAvailableTcpPort() {
|
|
156
|
+
const dockerPorts = await getDockerPublishedTcpPorts();
|
|
157
|
+
for (let attempt = 0; attempt < 10; attempt += 1) {
|
|
158
|
+
const candidate = await allocateAvailableTcpPort();
|
|
159
|
+
if (!dockerPorts.has(Number.parseInt(candidate, 10))) {
|
|
160
|
+
return candidate;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
throw new Error('Failed to allocate an available TCP port that is not already published by Docker.');
|
|
164
|
+
}
|
|
165
|
+
export async function validateAvailableTcpPort(value) {
|
|
166
|
+
const raw = String(value ?? '').trim();
|
|
167
|
+
if (raw === '') {
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
const formatError = validateTcpPort(raw);
|
|
171
|
+
if (formatError) {
|
|
172
|
+
return formatError;
|
|
173
|
+
}
|
|
174
|
+
const port = parseTcpPort(raw);
|
|
175
|
+
const available = await canListenOnTcpPort(port);
|
|
176
|
+
if (!available) {
|
|
177
|
+
return `Port ${port} is already in use. Choose another port.`;
|
|
178
|
+
}
|
|
179
|
+
const dockerPorts = await getDockerPublishedTcpPorts();
|
|
180
|
+
if (dockerPorts.has(port)) {
|
|
181
|
+
return `Port ${port} is already in use by a Docker container. Choose another port.`;
|
|
182
|
+
}
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|