@nocobase/cli 2.1.0-beta.2 → 2.1.0-beta.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/LICENSE.txt +107 -0
- package/README.md +367 -19
- package/README.zh-CN.md +336 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +131 -0
- package/dist/commands/api/resource/create.js +15 -0
- package/dist/commands/api/resource/destroy.js +15 -0
- package/dist/commands/api/resource/get.js +15 -0
- package/dist/commands/api/resource/index.js +20 -0
- package/dist/commands/api/resource/list.js +16 -0
- package/dist/commands/api/resource/query.js +15 -0
- package/dist/commands/api/resource/update.js +15 -0
- package/dist/commands/build.js +57 -0
- package/dist/commands/db/logs.js +85 -0
- package/dist/commands/db/ps.js +60 -0
- package/dist/commands/db/shared.js +95 -0
- package/dist/commands/db/start.js +70 -0
- package/dist/commands/db/stop.js +70 -0
- package/dist/commands/dev.js +156 -0
- package/dist/commands/down.js +197 -0
- package/dist/commands/download.js +865 -0
- package/dist/commands/env/add.js +307 -0
- package/dist/commands/env/auth.js +55 -0
- package/dist/commands/env/list.js +36 -0
- package/dist/commands/env/remove.js +59 -0
- package/dist/commands/env/update.js +67 -0
- package/dist/commands/env/use.js +28 -0
- package/dist/commands/init.js +950 -0
- package/dist/commands/install.js +1927 -0
- package/dist/commands/logs.js +97 -0
- package/dist/commands/pm/disable.js +63 -0
- package/dist/commands/pm/enable.js +63 -0
- package/dist/commands/pm/list.js +61 -0
- package/dist/commands/prompts-stages.js +150 -0
- package/dist/commands/prompts-test.js +181 -0
- package/dist/commands/ps.js +119 -0
- package/dist/commands/restart.js +74 -0
- package/dist/commands/scaffold/migration.js +38 -0
- package/dist/commands/scaffold/plugin.js +37 -0
- package/dist/commands/self/check.js +71 -0
- package/dist/commands/self/index.js +20 -0
- package/dist/commands/self/update.js +86 -0
- package/dist/commands/skills/check.js +69 -0
- package/dist/commands/skills/index.js +20 -0
- package/dist/commands/skills/install.js +71 -0
- package/dist/commands/skills/update.js +71 -0
- package/dist/commands/start.js +218 -0
- package/dist/commands/stop.js +97 -0
- package/dist/commands/test.js +466 -0
- package/dist/commands/upgrade.js +594 -0
- package/dist/generated/command-registry.js +133 -0
- package/dist/help/runtime-help.js +20 -0
- package/dist/lib/api-client.js +244 -0
- package/dist/lib/app-runtime.js +153 -0
- package/dist/lib/auth-store.js +357 -0
- package/dist/lib/bootstrap.js +388 -0
- package/dist/lib/build-config.js +10 -0
- package/dist/lib/cli-home.js +61 -0
- package/dist/lib/cli-locale.js +115 -0
- package/dist/lib/command-discovery.js +39 -0
- package/dist/lib/env-auth.js +872 -0
- package/dist/lib/generated-command.js +150 -0
- package/dist/lib/http-request.js +49 -0
- package/dist/lib/naming.js +70 -0
- package/dist/lib/openapi.js +62 -0
- package/dist/lib/post-processors.js +23 -0
- package/dist/lib/prompt-catalog.js +581 -0
- package/dist/lib/prompt-validators.js +185 -0
- package/dist/lib/prompt-web-ui.js +2096 -0
- package/dist/lib/resource-command.js +343 -0
- package/dist/lib/resource-request.js +104 -0
- package/dist/lib/run-npm.js +197 -0
- package/dist/lib/runtime-generator.js +419 -0
- package/dist/lib/runtime-store.js +56 -0
- package/dist/lib/self-manager.js +246 -0
- package/dist/lib/skills-manager.js +269 -0
- package/dist/lib/startup-update.js +203 -0
- package/dist/lib/ui.js +175 -0
- package/dist/locale/en-US.json +336 -0
- package/dist/locale/zh-CN.json +336 -0
- package/dist/post-processors/data-modeling.js +66 -0
- package/dist/post-processors/data-source-manager.js +114 -0
- package/dist/post-processors/index.js +19 -0
- package/nocobase-ctl.config.json +287 -0
- package/package.json +60 -26
- package/LICENSE +0 -661
- package/bin/index.js +0 -39
- package/nocobase.conf.tpl +0 -95
- package/src/cli.js +0 -19
- package/src/commands/benchmark.js +0 -73
- package/src/commands/build.js +0 -49
- package/src/commands/clean.js +0 -30
- package/src/commands/client.js +0 -166
- package/src/commands/create-nginx-conf.js +0 -37
- package/src/commands/create-plugin.js +0 -33
- package/src/commands/dev.js +0 -200
- package/src/commands/doc.js +0 -76
- package/src/commands/e2e.js +0 -265
- package/src/commands/global.js +0 -43
- package/src/commands/index.js +0 -45
- package/src/commands/instance-id.js +0 -47
- package/src/commands/locale/cronstrue.js +0 -122
- package/src/commands/locale/react-js-cron/en-US.json +0 -75
- package/src/commands/locale/react-js-cron/index.js +0 -17
- package/src/commands/locale/react-js-cron/zh-CN.json +0 -33
- package/src/commands/locale/react-js-cron/zh-TW.json +0 -33
- package/src/commands/locale.js +0 -81
- package/src/commands/p-test.js +0 -88
- package/src/commands/perf.js +0 -63
- package/src/commands/pkg.js +0 -321
- package/src/commands/pm2.js +0 -37
- package/src/commands/postinstall.js +0 -88
- package/src/commands/start.js +0 -148
- package/src/commands/tar.js +0 -36
- package/src/commands/test-coverage.js +0 -55
- package/src/commands/test.js +0 -107
- package/src/commands/umi.js +0 -33
- package/src/commands/update-deps.js +0 -72
- package/src/commands/upgrade.js +0 -47
- package/src/commands/view-license-key.js +0 -44
- package/src/index.js +0 -14
- package/src/license.js +0 -76
- package/src/logger.js +0 -75
- package/src/plugin-generator.js +0 -80
- package/src/util.js +0 -517
- package/templates/bundle-status.html +0 -338
- package/templates/create-app-package.json +0 -39
- package/templates/plugin/.npmignore.tpl +0 -2
- package/templates/plugin/README.md.tpl +0 -1
- package/templates/plugin/client.d.ts +0 -2
- package/templates/plugin/client.js +0 -1
- package/templates/plugin/package.json.tpl +0 -11
- package/templates/plugin/server.d.ts +0 -2
- package/templates/plugin/server.js +0 -1
- package/templates/plugin/src/client/client.d.ts +0 -249
- package/templates/plugin/src/client/index.tsx.tpl +0 -1
- package/templates/plugin/src/client/locale.ts +0 -21
- package/templates/plugin/src/client/models/index.ts +0 -12
- package/templates/plugin/src/client/plugin.tsx.tpl +0 -10
- package/templates/plugin/src/index.ts +0 -2
- package/templates/plugin/src/locale/en-US.json +0 -1
- package/templates/plugin/src/locale/zh-CN.json +0 -1
- package/templates/plugin/src/server/collections/.gitkeep +0 -0
- package/templates/plugin/src/server/index.ts.tpl +0 -1
- package/templates/plugin/src/server/plugin.ts.tpl +0 -19
|
@@ -0,0 +1,466 @@
|
|
|
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 { spawn } from 'node:child_process';
|
|
11
|
+
import fsp from 'node:fs/promises';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import Install from './install.js';
|
|
14
|
+
import { defaultWorkspaceName } from '../lib/app-runtime.js';
|
|
15
|
+
import { findAvailableTcpPort, validateAvailableTcpPort } from '../lib/prompt-validators.js';
|
|
16
|
+
import { commandSucceeds, resolveProjectCwd, run, runNocoBaseCommand } from '../lib/run-npm.js';
|
|
17
|
+
import { failTask, printInfo, setVerboseMode, startTask, succeedTask } from '../lib/ui.js';
|
|
18
|
+
const DEFAULT_DB_HOST = '127.0.0.1';
|
|
19
|
+
const DEFAULT_DB_DATABASE = 'nocobase-test';
|
|
20
|
+
const DEFAULT_DB_USER = 'nocobase';
|
|
21
|
+
const DEFAULT_DB_PASSWORD = 'nocobase';
|
|
22
|
+
const DEFAULT_DB_DIALECT = 'postgres';
|
|
23
|
+
const DEFAULT_TEST_TIMEZONE = 'UTC';
|
|
24
|
+
const DEFAULT_TEST_DB_IMAGES = {
|
|
25
|
+
postgres: 'postgres:16',
|
|
26
|
+
mysql: 'mysql:8',
|
|
27
|
+
mariadb: 'mariadb:11',
|
|
28
|
+
kingbase: 'registry.cn-shanghai.aliyuncs.com/nocobase/kingbase:v009r001c001b0030_single_x86',
|
|
29
|
+
};
|
|
30
|
+
const DEFAULT_TEST_DB_DISTRIBUTOR_PORT = '23450';
|
|
31
|
+
const DEFAULT_TEST_DB_DISTRIBUTOR_PREFIX = {
|
|
32
|
+
postgres: 'test',
|
|
33
|
+
mysql: 'test_',
|
|
34
|
+
mariadb: 'test_',
|
|
35
|
+
};
|
|
36
|
+
const DEFAULT_DB_PORTS = {
|
|
37
|
+
postgres: 5433,
|
|
38
|
+
mysql: 3307,
|
|
39
|
+
mariadb: 3307,
|
|
40
|
+
kingbase: 54322,
|
|
41
|
+
};
|
|
42
|
+
const TCP_PORT_READY_SCRIPT = [
|
|
43
|
+
"const net = require('node:net');",
|
|
44
|
+
"const port = Number(process.argv.at(-1));",
|
|
45
|
+
"const socket = net.createConnection({ host: '127.0.0.1', port });",
|
|
46
|
+
"socket.once('connect', () => { socket.end(); process.exit(0); });",
|
|
47
|
+
"socket.once('error', () => process.exit(1));",
|
|
48
|
+
"setTimeout(() => { socket.destroy(); process.exit(1); }, 200).unref();",
|
|
49
|
+
].join('\n');
|
|
50
|
+
function inferTestEnv(paths) {
|
|
51
|
+
const first = String(paths[0] ?? '').trim();
|
|
52
|
+
if (!first) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
const normalized = first.split('\\').join('/');
|
|
56
|
+
if (normalized.includes('/client/')
|
|
57
|
+
|| normalized.includes('/client-v2/')
|
|
58
|
+
|| normalized.includes('/flow-engine/')) {
|
|
59
|
+
return 'client-side';
|
|
60
|
+
}
|
|
61
|
+
return 'server-side';
|
|
62
|
+
}
|
|
63
|
+
function trimValue(value) {
|
|
64
|
+
return String(value ?? '').trim();
|
|
65
|
+
}
|
|
66
|
+
function resolveWorkspaceName(cwd) {
|
|
67
|
+
return defaultWorkspaceName(cwd);
|
|
68
|
+
}
|
|
69
|
+
function defaultTestDbPort(dbDialect) {
|
|
70
|
+
return String(DEFAULT_DB_PORTS[dbDialect] ?? DEFAULT_DB_PORTS.postgres);
|
|
71
|
+
}
|
|
72
|
+
function defaultTestDbImage(dbDialect) {
|
|
73
|
+
return DEFAULT_TEST_DB_IMAGES[dbDialect] ?? DEFAULT_TEST_DB_IMAGES.postgres;
|
|
74
|
+
}
|
|
75
|
+
function delay(ms) {
|
|
76
|
+
return new Promise((resolve) => {
|
|
77
|
+
setTimeout(resolve, ms);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function shouldRunServerTests(params) {
|
|
81
|
+
if (params.server) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
if (params.client) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
return inferTestEnv(params.paths) !== 'client-side';
|
|
88
|
+
}
|
|
89
|
+
function defaultTestDbDistributorPrefix(dbDialect) {
|
|
90
|
+
return DEFAULT_TEST_DB_DISTRIBUTOR_PREFIX[dbDialect];
|
|
91
|
+
}
|
|
92
|
+
function supportsTestDbDistributor(dbDialect) {
|
|
93
|
+
return Boolean(defaultTestDbDistributorPrefix(dbDialect));
|
|
94
|
+
}
|
|
95
|
+
function buildTestDbDistributorEnv(env) {
|
|
96
|
+
if (env.DB_DIALECT === 'mysql' || env.DB_DIALECT === 'mariadb') {
|
|
97
|
+
return {
|
|
98
|
+
...env,
|
|
99
|
+
DB_APP_USER: env.DB_USER,
|
|
100
|
+
DB_USER: 'root',
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return env;
|
|
104
|
+
}
|
|
105
|
+
async function waitForTcpPortReady(port, timeoutMs = 5000) {
|
|
106
|
+
const deadline = Date.now() + timeoutMs;
|
|
107
|
+
while (Date.now() < deadline) {
|
|
108
|
+
if (await commandSucceeds(process.execPath, ['-e', TCP_PORT_READY_SCRIPT, port])) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
await delay(100);
|
|
112
|
+
}
|
|
113
|
+
throw new Error(`Timed out while waiting for the test DB distributor on 127.0.0.1:${port}.`);
|
|
114
|
+
}
|
|
115
|
+
async function stopBackgroundProcess(child) {
|
|
116
|
+
if (child.exitCode !== null || child.killed) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
await new Promise((resolve) => {
|
|
120
|
+
const finish = () => {
|
|
121
|
+
clearTimeout(timeout);
|
|
122
|
+
resolve();
|
|
123
|
+
};
|
|
124
|
+
const timeout = setTimeout(finish, 1000);
|
|
125
|
+
child.once('close', finish);
|
|
126
|
+
try {
|
|
127
|
+
child.kill();
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
finish();
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
async function startTestDbDistributor(params) {
|
|
135
|
+
const port = DEFAULT_TEST_DB_DISTRIBUTOR_PORT;
|
|
136
|
+
const prefix = defaultTestDbDistributorPrefix(params.env.DB_DIALECT);
|
|
137
|
+
if (!prefix) {
|
|
138
|
+
throw new Error(`The ${params.env.DB_DIALECT} test DB distributor is not supported.`);
|
|
139
|
+
}
|
|
140
|
+
const portError = await validateAvailableTcpPort(port);
|
|
141
|
+
if (portError) {
|
|
142
|
+
throw new Error(`Host port ${port} is unavailable for the test DB distributor. ${portError}`);
|
|
143
|
+
}
|
|
144
|
+
const distributorEnv = buildTestDbDistributorEnv(params.env);
|
|
145
|
+
const child = spawn(process.execPath, [
|
|
146
|
+
path.resolve(params.cwd, 'node_modules', 'tsx', 'dist', 'cli.mjs'),
|
|
147
|
+
path.resolve(params.cwd, 'packages', 'core', 'test', 'src', 'scripts', 'test-db-creator.ts'),
|
|
148
|
+
], {
|
|
149
|
+
cwd: params.cwd,
|
|
150
|
+
env: {
|
|
151
|
+
...process.env,
|
|
152
|
+
...distributorEnv,
|
|
153
|
+
DB_TEST_DISTRIBUTOR_PORT: port,
|
|
154
|
+
DB_TEST_PREFIX: prefix,
|
|
155
|
+
},
|
|
156
|
+
stdio: params.stdio,
|
|
157
|
+
windowsHide: process.platform === 'win32',
|
|
158
|
+
});
|
|
159
|
+
let childError;
|
|
160
|
+
child.once('error', (error) => {
|
|
161
|
+
childError = error;
|
|
162
|
+
});
|
|
163
|
+
child.once('close', (code, signal) => {
|
|
164
|
+
if (code === 0) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
childError = childError ?? new Error(signal
|
|
168
|
+
? `test DB distributor exited due to signal ${signal}`
|
|
169
|
+
: `test DB distributor exited with code ${code ?? 'unknown'}`);
|
|
170
|
+
});
|
|
171
|
+
try {
|
|
172
|
+
await waitForTcpPortReady(port);
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
await stopBackgroundProcess(child);
|
|
176
|
+
throw childError ?? error;
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
port,
|
|
180
|
+
prefix,
|
|
181
|
+
stop: async () => {
|
|
182
|
+
await stopBackgroundProcess(child);
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
async function ensureDockerNetwork(networkName, options) {
|
|
187
|
+
if (await commandSucceeds('docker', ['network', 'inspect', networkName])) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
await run('docker', ['network', 'create', networkName], {
|
|
191
|
+
errorName: 'docker network create',
|
|
192
|
+
stdio: options?.stdio ?? 'ignore',
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
async function removeDockerContainerIfExists(containerName, options) {
|
|
196
|
+
if (!(await commandSucceeds('docker', ['container', 'inspect', containerName]))) {
|
|
197
|
+
return 'missing';
|
|
198
|
+
}
|
|
199
|
+
await run('docker', ['rm', '-f', containerName], {
|
|
200
|
+
errorName: 'docker rm',
|
|
201
|
+
stdio: options?.stdio ?? 'ignore',
|
|
202
|
+
});
|
|
203
|
+
return 'removed';
|
|
204
|
+
}
|
|
205
|
+
function formatDbBootstrapFailure(message) {
|
|
206
|
+
return [
|
|
207
|
+
'Could not prepare the built-in test database.',
|
|
208
|
+
'The CLI was not able to recreate a clean Docker database for this test run.',
|
|
209
|
+
'Check Docker status, the selected port, and local storage permissions, then try again.',
|
|
210
|
+
`Details: ${message}`,
|
|
211
|
+
].join('\n');
|
|
212
|
+
}
|
|
213
|
+
function buildTestDbConfig(params) {
|
|
214
|
+
const dbDialect = trimValue(params.dbDialect) || DEFAULT_DB_DIALECT;
|
|
215
|
+
const workspaceName = resolveWorkspaceName(params.cwd);
|
|
216
|
+
const storagePath = path.join(params.cwd, 'storage', 'test');
|
|
217
|
+
const plan = Install.buildBuiltinDbPlan({
|
|
218
|
+
envName: 'test',
|
|
219
|
+
workspaceName,
|
|
220
|
+
storagePath,
|
|
221
|
+
source: 'test',
|
|
222
|
+
dbDialect,
|
|
223
|
+
dbHost: DEFAULT_DB_HOST,
|
|
224
|
+
dbPort: trimValue(params.dbPort) || defaultTestDbPort(dbDialect),
|
|
225
|
+
dbDatabase: trimValue(params.dbDatabase) || DEFAULT_DB_DATABASE,
|
|
226
|
+
dbUser: trimValue(params.dbUser) || DEFAULT_DB_USER,
|
|
227
|
+
dbPassword: trimValue(params.dbPassword) || DEFAULT_DB_PASSWORD,
|
|
228
|
+
builtinDbImage: trimValue(params.builtinDbImage) || defaultTestDbImage(dbDialect),
|
|
229
|
+
});
|
|
230
|
+
return {
|
|
231
|
+
storagePath,
|
|
232
|
+
containerName: plan.containerName,
|
|
233
|
+
networkName: plan.networkName,
|
|
234
|
+
dataDir: plan.dataDir,
|
|
235
|
+
args: plan.args,
|
|
236
|
+
env: {
|
|
237
|
+
APP_ENV_PATH: '.env',
|
|
238
|
+
STORAGE_PATH: storagePath,
|
|
239
|
+
TZ: DEFAULT_TEST_TIMEZONE,
|
|
240
|
+
DB_DIALECT: plan.dbDialect,
|
|
241
|
+
DB_HOST: plan.dbHost,
|
|
242
|
+
DB_PORT: plan.dbPort,
|
|
243
|
+
DB_DATABASE: plan.dbDatabase,
|
|
244
|
+
DB_USER: plan.dbUser,
|
|
245
|
+
DB_PASSWORD: plan.dbPassword,
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
async function prepareTestDatabase(config, options) {
|
|
250
|
+
let nextConfig = config;
|
|
251
|
+
await ensureDockerNetwork(nextConfig.networkName, {
|
|
252
|
+
stdio: options?.stdio,
|
|
253
|
+
});
|
|
254
|
+
await removeDockerContainerIfExists(nextConfig.containerName, {
|
|
255
|
+
stdio: options?.stdio,
|
|
256
|
+
});
|
|
257
|
+
await fsp.rm(nextConfig.storagePath, { recursive: true, force: true });
|
|
258
|
+
const portError = await validateAvailableTcpPort(nextConfig.env.DB_PORT);
|
|
259
|
+
if (portError) {
|
|
260
|
+
if (options?.dbPortExplicit) {
|
|
261
|
+
throw new Error(`Host port ${nextConfig.env.DB_PORT} is unavailable. ${portError}`);
|
|
262
|
+
}
|
|
263
|
+
const fallbackPort = await findAvailableTcpPort();
|
|
264
|
+
printInfo(`Host port ${nextConfig.env.DB_PORT} is unavailable for the test database, so the CLI will use ${fallbackPort} instead.`);
|
|
265
|
+
nextConfig = buildTestDbConfig({
|
|
266
|
+
cwd: path.dirname(path.dirname(nextConfig.storagePath)),
|
|
267
|
+
dbDialect: nextConfig.env.DB_DIALECT,
|
|
268
|
+
dbPort: fallbackPort,
|
|
269
|
+
dbDatabase: nextConfig.env.DB_DATABASE,
|
|
270
|
+
dbUser: nextConfig.env.DB_USER,
|
|
271
|
+
dbPassword: nextConfig.env.DB_PASSWORD,
|
|
272
|
+
builtinDbImage: undefined,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
await fsp.mkdir(nextConfig.dataDir, { recursive: true });
|
|
276
|
+
await run('docker', nextConfig.args, {
|
|
277
|
+
errorName: 'docker run',
|
|
278
|
+
stdio: options?.stdio ?? 'ignore',
|
|
279
|
+
});
|
|
280
|
+
await waitForTcpPortReady(nextConfig.env.DB_PORT, 30_000);
|
|
281
|
+
return nextConfig;
|
|
282
|
+
}
|
|
283
|
+
export default class Test extends Command {
|
|
284
|
+
static args = {
|
|
285
|
+
paths: Args.string({
|
|
286
|
+
description: 'test file paths or globs to pass through',
|
|
287
|
+
multiple: true,
|
|
288
|
+
required: false,
|
|
289
|
+
}),
|
|
290
|
+
};
|
|
291
|
+
static description = 'Run project tests from the selected app directory. Before running tests, the CLI recreates a built-in Docker test database and injects `DB_*` values internally.';
|
|
292
|
+
static examples = [
|
|
293
|
+
'<%= config.bin %> <%= command.id %>',
|
|
294
|
+
'<%= config.bin %> <%= command.id %> --cwd /path/to/app',
|
|
295
|
+
'<%= config.bin %> <%= command.id %> packages/core/server/src/__tests__/foo.test.ts',
|
|
296
|
+
'<%= config.bin %> <%= command.id %> --server --coverage',
|
|
297
|
+
'<%= config.bin %> <%= command.id %> --db-port 5433',
|
|
298
|
+
];
|
|
299
|
+
static flags = {
|
|
300
|
+
cwd: Flags.string({
|
|
301
|
+
char: 'c',
|
|
302
|
+
description: 'App directory to run tests from. Defaults to the current working directory',
|
|
303
|
+
required: false,
|
|
304
|
+
}),
|
|
305
|
+
watch: Flags.boolean({
|
|
306
|
+
char: 'w',
|
|
307
|
+
description: 'Run Vitest in watch mode',
|
|
308
|
+
default: false,
|
|
309
|
+
}),
|
|
310
|
+
run: Flags.boolean({
|
|
311
|
+
description: 'Run once without watch mode',
|
|
312
|
+
default: false,
|
|
313
|
+
}),
|
|
314
|
+
allowOnly: Flags.boolean({
|
|
315
|
+
description: 'Allow `.only` tests',
|
|
316
|
+
default: false,
|
|
317
|
+
}),
|
|
318
|
+
bail: Flags.boolean({
|
|
319
|
+
description: 'Stop after the first failure',
|
|
320
|
+
default: false,
|
|
321
|
+
}),
|
|
322
|
+
coverage: Flags.boolean({
|
|
323
|
+
description: 'Enable coverage reporting',
|
|
324
|
+
default: false,
|
|
325
|
+
}),
|
|
326
|
+
'single-thread': Flags.string({
|
|
327
|
+
description: 'Forward single-thread mode to the underlying test runner',
|
|
328
|
+
required: false,
|
|
329
|
+
}),
|
|
330
|
+
server: Flags.boolean({
|
|
331
|
+
description: 'Force server-side test mode',
|
|
332
|
+
default: false,
|
|
333
|
+
}),
|
|
334
|
+
client: Flags.boolean({
|
|
335
|
+
description: 'Force client-side test mode',
|
|
336
|
+
default: false,
|
|
337
|
+
}),
|
|
338
|
+
'db-clean': Flags.boolean({
|
|
339
|
+
char: 'd',
|
|
340
|
+
description: 'Clean the database before tests when supported by the underlying app command',
|
|
341
|
+
default: false,
|
|
342
|
+
}),
|
|
343
|
+
'db-dialect': Flags.string({
|
|
344
|
+
description: 'Built-in test database dialect to start',
|
|
345
|
+
options: ['postgres', 'mysql', 'mariadb', 'kingbase'],
|
|
346
|
+
required: false,
|
|
347
|
+
}),
|
|
348
|
+
'db-image': Flags.string({
|
|
349
|
+
description: 'Built-in test database Docker image to start',
|
|
350
|
+
aliases: ['builtin-db-image'],
|
|
351
|
+
required: false,
|
|
352
|
+
}),
|
|
353
|
+
'db-port': Flags.string({
|
|
354
|
+
description: 'Host TCP port to publish for the built-in test database',
|
|
355
|
+
required: false,
|
|
356
|
+
}),
|
|
357
|
+
'db-database': Flags.string({
|
|
358
|
+
description: 'Database name to inject for tests',
|
|
359
|
+
required: false,
|
|
360
|
+
}),
|
|
361
|
+
'db-user': Flags.string({
|
|
362
|
+
description: 'Database user to inject for tests',
|
|
363
|
+
required: false,
|
|
364
|
+
}),
|
|
365
|
+
'db-password': Flags.string({
|
|
366
|
+
description: 'Database password to inject for tests',
|
|
367
|
+
required: false,
|
|
368
|
+
}),
|
|
369
|
+
verbose: Flags.boolean({
|
|
370
|
+
description: 'Show raw Docker and test runner output',
|
|
371
|
+
default: false,
|
|
372
|
+
}),
|
|
373
|
+
};
|
|
374
|
+
async run() {
|
|
375
|
+
const { args, flags } = await this.parse(Test);
|
|
376
|
+
setVerboseMode(flags.verbose);
|
|
377
|
+
if (flags.server && flags.client) {
|
|
378
|
+
this.error('Cannot use `--server` and `--client` together.');
|
|
379
|
+
}
|
|
380
|
+
const cwd = resolveProjectCwd(flags.cwd);
|
|
381
|
+
const commandArgs = ['test', ...(args.paths ?? [])];
|
|
382
|
+
if (flags.watch) {
|
|
383
|
+
commandArgs.push('--watch');
|
|
384
|
+
}
|
|
385
|
+
if (flags.run || !flags.watch) {
|
|
386
|
+
commandArgs.push('--run');
|
|
387
|
+
}
|
|
388
|
+
if (flags.allowOnly) {
|
|
389
|
+
commandArgs.push('--allowOnly');
|
|
390
|
+
}
|
|
391
|
+
if (flags.bail) {
|
|
392
|
+
commandArgs.push('--bail');
|
|
393
|
+
}
|
|
394
|
+
if (flags.coverage) {
|
|
395
|
+
commandArgs.push('--coverage');
|
|
396
|
+
}
|
|
397
|
+
if (flags.server) {
|
|
398
|
+
commandArgs.push('--server');
|
|
399
|
+
}
|
|
400
|
+
else if (flags.client) {
|
|
401
|
+
commandArgs.push('--client');
|
|
402
|
+
}
|
|
403
|
+
if (flags['db-clean']) {
|
|
404
|
+
commandArgs.push('--db-clean');
|
|
405
|
+
}
|
|
406
|
+
if (flags['single-thread'] !== undefined) {
|
|
407
|
+
commandArgs.push(`--single-thread=${flags['single-thread']}`);
|
|
408
|
+
}
|
|
409
|
+
else if (!flags.client && !flags.server && inferTestEnv(args.paths ?? []) === 'server-side') {
|
|
410
|
+
commandArgs.push('--single-thread=true');
|
|
411
|
+
}
|
|
412
|
+
startTask('Recreating the built-in test database...');
|
|
413
|
+
let testDbConfig;
|
|
414
|
+
let testDbDistributor;
|
|
415
|
+
try {
|
|
416
|
+
testDbConfig = await prepareTestDatabase(buildTestDbConfig({
|
|
417
|
+
cwd,
|
|
418
|
+
dbDialect: flags['db-dialect'],
|
|
419
|
+
builtinDbImage: flags['db-image'],
|
|
420
|
+
dbPort: flags['db-port'],
|
|
421
|
+
dbDatabase: flags['db-database'],
|
|
422
|
+
dbUser: flags['db-user'],
|
|
423
|
+
dbPassword: flags['db-password'],
|
|
424
|
+
}), {
|
|
425
|
+
stdio: flags.verbose ? 'inherit' : 'ignore',
|
|
426
|
+
dbPortExplicit: Boolean(flags['db-port']),
|
|
427
|
+
});
|
|
428
|
+
if (shouldRunServerTests({
|
|
429
|
+
server: flags.server,
|
|
430
|
+
client: flags.client,
|
|
431
|
+
paths: args.paths ?? [],
|
|
432
|
+
})
|
|
433
|
+
&& supportsTestDbDistributor(testDbConfig.env.DB_DIALECT)) {
|
|
434
|
+
testDbDistributor = await startTestDbDistributor({
|
|
435
|
+
cwd,
|
|
436
|
+
env: testDbConfig.env,
|
|
437
|
+
stdio: flags.verbose ? 'inherit' : 'ignore',
|
|
438
|
+
});
|
|
439
|
+
testDbConfig.env.DB_TEST_DISTRIBUTOR_PORT = testDbDistributor.port;
|
|
440
|
+
testDbConfig.env.DB_TEST_PREFIX = testDbDistributor.prefix;
|
|
441
|
+
}
|
|
442
|
+
succeedTask(`The built-in test database is ready at ${testDbConfig.env.DB_HOST}:${testDbConfig.env.DB_PORT}.`);
|
|
443
|
+
printInfo(`Test DB settings: DB_DIALECT=${testDbConfig.env.DB_DIALECT} DB_HOST=${testDbConfig.env.DB_HOST} DB_PORT=${testDbConfig.env.DB_PORT} DB_DATABASE=${testDbConfig.env.DB_DATABASE} DB_USER=${testDbConfig.env.DB_USER}`);
|
|
444
|
+
}
|
|
445
|
+
catch (error) {
|
|
446
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
447
|
+
failTask('Failed to recreate the built-in test database.');
|
|
448
|
+
this.error(formatDbBootstrapFailure(message));
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
try {
|
|
452
|
+
await runNocoBaseCommand(commandArgs, {
|
|
453
|
+
cwd,
|
|
454
|
+
stdio: flags.verbose ? 'inherit' : 'ignore',
|
|
455
|
+
env: testDbConfig.env,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
catch (error) {
|
|
459
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
460
|
+
this.error(message);
|
|
461
|
+
}
|
|
462
|
+
finally {
|
|
463
|
+
await testDbDistributor?.stop();
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|