@nocobase/cli 2.1.0-alpha.3 → 2.1.0-alpha.30

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.
Files changed (182) hide show
  1. package/LICENSE.txt +107 -0
  2. package/README.md +379 -19
  3. package/README.zh-CN.md +329 -0
  4. package/bin/run.cmd +3 -0
  5. package/bin/run.js +131 -0
  6. package/dist/commands/api/resource/create.js +15 -0
  7. package/dist/commands/api/resource/destroy.js +15 -0
  8. package/dist/commands/api/resource/get.js +15 -0
  9. package/dist/commands/api/resource/index.js +20 -0
  10. package/dist/commands/api/resource/list.js +16 -0
  11. package/dist/commands/api/resource/query.js +15 -0
  12. package/dist/commands/api/resource/update.js +15 -0
  13. package/dist/commands/app/down.js +266 -0
  14. package/dist/commands/app/logs.js +98 -0
  15. package/dist/commands/app/restart.js +75 -0
  16. package/dist/commands/app/start.js +253 -0
  17. package/dist/commands/app/stop.js +99 -0
  18. package/dist/commands/app/upgrade.js +582 -0
  19. package/{src/cli.js → dist/commands/build.js} +4 -11
  20. package/dist/commands/config/delete.js +30 -0
  21. package/dist/commands/config/get.js +29 -0
  22. package/dist/commands/config/index.js +20 -0
  23. package/dist/commands/config/list.js +29 -0
  24. package/dist/commands/config/set.js +35 -0
  25. package/dist/commands/db/check.js +238 -0
  26. package/dist/commands/db/logs.js +85 -0
  27. package/dist/commands/db/ps.js +60 -0
  28. package/dist/commands/db/shared.js +96 -0
  29. package/dist/commands/db/start.js +71 -0
  30. package/dist/commands/db/stop.js +71 -0
  31. package/{templates/plugin/src/client/models/index.ts → dist/commands/dev.js} +4 -4
  32. package/{src/index.js → dist/commands/down.js} +4 -6
  33. package/{src/commands/locale/react-js-cron/index.js → dist/commands/download.js} +4 -8
  34. package/dist/commands/env/add.js +312 -0
  35. package/dist/commands/env/auth.js +55 -0
  36. package/dist/commands/env/info.js +156 -0
  37. package/dist/commands/env/list.js +50 -0
  38. package/dist/commands/env/remove.js +59 -0
  39. package/dist/commands/env/shared.js +158 -0
  40. package/dist/commands/env/update.js +67 -0
  41. package/dist/commands/env/use.js +28 -0
  42. package/dist/commands/examples/prompts-stages.js +150 -0
  43. package/dist/commands/examples/prompts-test.js +181 -0
  44. package/dist/commands/init.js +1027 -0
  45. package/dist/commands/install.js +2206 -0
  46. package/dist/commands/license/activate.js +360 -0
  47. package/dist/commands/license/env.js +94 -0
  48. package/dist/commands/license/generate-id.js +108 -0
  49. package/dist/commands/license/id.js +56 -0
  50. package/dist/commands/license/index.js +20 -0
  51. package/dist/commands/license/plugins/clean.js +101 -0
  52. package/dist/commands/license/plugins/index.js +20 -0
  53. package/dist/commands/license/plugins/list.js +50 -0
  54. package/dist/commands/license/plugins/shared.js +325 -0
  55. package/dist/commands/license/plugins/sync.js +269 -0
  56. package/dist/commands/license/shared.js +414 -0
  57. package/dist/commands/license/status.js +50 -0
  58. package/dist/commands/logs.js +12 -0
  59. package/dist/commands/plugin/disable.js +66 -0
  60. package/dist/commands/plugin/enable.js +66 -0
  61. package/dist/commands/plugin/list.js +62 -0
  62. package/dist/commands/pm/disable.js +12 -0
  63. package/dist/commands/pm/enable.js +12 -0
  64. package/dist/commands/pm/list.js +12 -0
  65. package/dist/commands/restart.js +12 -0
  66. package/dist/commands/scaffold/migration.js +38 -0
  67. package/dist/commands/scaffold/plugin.js +37 -0
  68. package/dist/commands/self/check.js +71 -0
  69. package/dist/commands/self/index.js +20 -0
  70. package/dist/commands/self/update.js +86 -0
  71. package/dist/commands/skills/check.js +69 -0
  72. package/dist/commands/skills/index.js +20 -0
  73. package/dist/commands/skills/install.js +71 -0
  74. package/dist/commands/skills/remove.js +71 -0
  75. package/dist/commands/skills/update.js +78 -0
  76. package/dist/commands/source/build.js +58 -0
  77. package/dist/commands/source/dev.js +158 -0
  78. package/dist/commands/source/download.js +866 -0
  79. package/dist/commands/source/test.js +467 -0
  80. package/dist/commands/start.js +12 -0
  81. package/dist/commands/stop.js +12 -0
  82. package/dist/commands/test.js +12 -0
  83. package/dist/commands/upgrade.js +12 -0
  84. package/dist/generated/command-registry.js +133 -0
  85. package/dist/help/runtime-help.js +23 -0
  86. package/dist/lib/api-client.js +329 -0
  87. package/dist/lib/app-health.js +126 -0
  88. package/dist/lib/app-managed-resources.js +268 -0
  89. package/dist/lib/app-runtime.js +171 -0
  90. package/dist/lib/auth-store.js +328 -0
  91. package/dist/lib/bootstrap.js +384 -0
  92. package/dist/lib/build-config.js +18 -0
  93. package/dist/lib/builtin-db.js +86 -0
  94. package/dist/lib/cli-config.js +176 -0
  95. package/dist/lib/cli-home.js +47 -0
  96. package/dist/lib/cli-locale.js +129 -0
  97. package/dist/lib/command-discovery.js +39 -0
  98. package/dist/lib/db-connection-check.js +178 -0
  99. package/dist/lib/env-auth.js +872 -0
  100. package/dist/lib/env-config.js +87 -0
  101. package/dist/lib/generated-command.js +171 -0
  102. package/dist/lib/http-request.js +49 -0
  103. package/dist/lib/naming.js +70 -0
  104. package/dist/lib/openapi.js +62 -0
  105. package/dist/lib/plugin-storage.js +127 -0
  106. package/dist/lib/post-processors.js +23 -0
  107. package/dist/lib/prompt-catalog.js +581 -0
  108. package/dist/lib/prompt-validators.js +185 -0
  109. package/dist/lib/prompt-web-ui.js +2103 -0
  110. package/dist/lib/resource-command.js +343 -0
  111. package/dist/lib/resource-request.js +104 -0
  112. package/dist/lib/run-npm.js +250 -0
  113. package/dist/lib/runtime-env-vars.js +32 -0
  114. package/dist/lib/runtime-generator.js +498 -0
  115. package/dist/lib/runtime-store.js +56 -0
  116. package/dist/lib/self-manager.js +301 -0
  117. package/dist/lib/skills-manager.js +296 -0
  118. package/dist/lib/startup-update.js +281 -0
  119. package/dist/lib/ui.js +178 -0
  120. package/dist/locale/en-US.json +339 -0
  121. package/dist/locale/zh-CN.json +339 -0
  122. package/dist/post-processors/data-modeling.js +66 -0
  123. package/dist/post-processors/data-source-manager.js +114 -0
  124. package/dist/post-processors/index.js +19 -0
  125. package/nocobase-ctl.config.json +369 -0
  126. package/package.json +95 -26
  127. package/LICENSE +0 -661
  128. package/bin/index.js +0 -39
  129. package/nocobase.conf.tpl +0 -95
  130. package/src/commands/benchmark.js +0 -73
  131. package/src/commands/build.js +0 -49
  132. package/src/commands/clean.js +0 -30
  133. package/src/commands/client.js +0 -166
  134. package/src/commands/create-nginx-conf.js +0 -37
  135. package/src/commands/create-plugin.js +0 -33
  136. package/src/commands/dev.js +0 -200
  137. package/src/commands/doc.js +0 -76
  138. package/src/commands/e2e.js +0 -265
  139. package/src/commands/global.js +0 -43
  140. package/src/commands/index.js +0 -45
  141. package/src/commands/instance-id.js +0 -47
  142. package/src/commands/locale/cronstrue.js +0 -122
  143. package/src/commands/locale/react-js-cron/en-US.json +0 -75
  144. package/src/commands/locale/react-js-cron/zh-CN.json +0 -33
  145. package/src/commands/locale/react-js-cron/zh-TW.json +0 -33
  146. package/src/commands/locale.js +0 -81
  147. package/src/commands/p-test.js +0 -88
  148. package/src/commands/perf.js +0 -63
  149. package/src/commands/pkg.js +0 -321
  150. package/src/commands/pm2.js +0 -37
  151. package/src/commands/postinstall.js +0 -88
  152. package/src/commands/start.js +0 -148
  153. package/src/commands/tar.js +0 -36
  154. package/src/commands/test-coverage.js +0 -55
  155. package/src/commands/test.js +0 -107
  156. package/src/commands/umi.js +0 -33
  157. package/src/commands/update-deps.js +0 -72
  158. package/src/commands/upgrade.js +0 -47
  159. package/src/commands/view-license-key.js +0 -44
  160. package/src/license.js +0 -76
  161. package/src/logger.js +0 -75
  162. package/src/plugin-generator.js +0 -80
  163. package/src/util.js +0 -517
  164. package/templates/bundle-status.html +0 -338
  165. package/templates/create-app-package.json +0 -39
  166. package/templates/plugin/.npmignore.tpl +0 -2
  167. package/templates/plugin/README.md.tpl +0 -1
  168. package/templates/plugin/client.d.ts +0 -2
  169. package/templates/plugin/client.js +0 -1
  170. package/templates/plugin/package.json.tpl +0 -11
  171. package/templates/plugin/server.d.ts +0 -2
  172. package/templates/plugin/server.js +0 -1
  173. package/templates/plugin/src/client/client.d.ts +0 -249
  174. package/templates/plugin/src/client/index.tsx.tpl +0 -1
  175. package/templates/plugin/src/client/locale.ts +0 -21
  176. package/templates/plugin/src/client/plugin.tsx.tpl +0 -10
  177. package/templates/plugin/src/index.ts +0 -2
  178. package/templates/plugin/src/locale/en-US.json +0 -1
  179. package/templates/plugin/src/locale/zh-CN.json +0 -1
  180. package/templates/plugin/src/server/collections/.gitkeep +0 -0
  181. package/templates/plugin/src/server/index.ts.tpl +0 -1
  182. package/templates/plugin/src/server/plugin.ts.tpl +0 -19
@@ -0,0 +1,467 @@
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 SourceTest extends Command {
284
+ static hidden = false;
285
+ static args = {
286
+ paths: Args.string({
287
+ description: 'test file paths or globs to pass through',
288
+ multiple: true,
289
+ required: false,
290
+ }),
291
+ };
292
+ 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.';
293
+ static examples = [
294
+ '<%= config.bin %> <%= command.id %>',
295
+ '<%= config.bin %> <%= command.id %> --cwd /path/to/app',
296
+ '<%= config.bin %> <%= command.id %> packages/core/server/src/__tests__/foo.test.ts',
297
+ '<%= config.bin %> <%= command.id %> --server --coverage',
298
+ '<%= config.bin %> <%= command.id %> --db-port 5433',
299
+ ];
300
+ static flags = {
301
+ cwd: Flags.string({
302
+ char: 'c',
303
+ description: 'App directory to run tests from. Defaults to the current working directory',
304
+ required: false,
305
+ }),
306
+ watch: Flags.boolean({
307
+ char: 'w',
308
+ description: 'Run Vitest in watch mode',
309
+ default: false,
310
+ }),
311
+ run: Flags.boolean({
312
+ description: 'Run once without watch mode',
313
+ default: false,
314
+ }),
315
+ allowOnly: Flags.boolean({
316
+ description: 'Allow `.only` tests',
317
+ default: false,
318
+ }),
319
+ bail: Flags.boolean({
320
+ description: 'Stop after the first failure',
321
+ default: false,
322
+ }),
323
+ coverage: Flags.boolean({
324
+ description: 'Enable coverage reporting',
325
+ default: false,
326
+ }),
327
+ 'single-thread': Flags.string({
328
+ description: 'Forward single-thread mode to the underlying test runner',
329
+ required: false,
330
+ }),
331
+ server: Flags.boolean({
332
+ description: 'Force server-side test mode',
333
+ default: false,
334
+ }),
335
+ client: Flags.boolean({
336
+ description: 'Force client-side test mode',
337
+ default: false,
338
+ }),
339
+ 'db-clean': Flags.boolean({
340
+ char: 'd',
341
+ description: 'Clean the database before tests when supported by the underlying app command',
342
+ default: false,
343
+ }),
344
+ 'db-dialect': Flags.string({
345
+ description: 'Built-in test database dialect to start',
346
+ options: ['postgres', 'mysql', 'mariadb', 'kingbase'],
347
+ required: false,
348
+ }),
349
+ 'db-image': Flags.string({
350
+ description: 'Built-in test database Docker image to start',
351
+ aliases: ['builtin-db-image'],
352
+ required: false,
353
+ }),
354
+ 'db-port': Flags.string({
355
+ description: 'Host TCP port to publish for the built-in test database',
356
+ required: false,
357
+ }),
358
+ 'db-database': Flags.string({
359
+ description: 'Database name to inject for tests',
360
+ required: false,
361
+ }),
362
+ 'db-user': Flags.string({
363
+ description: 'Database user to inject for tests',
364
+ required: false,
365
+ }),
366
+ 'db-password': Flags.string({
367
+ description: 'Database password to inject for tests',
368
+ required: false,
369
+ }),
370
+ verbose: Flags.boolean({
371
+ description: 'Show raw Docker and test runner output',
372
+ default: false,
373
+ }),
374
+ };
375
+ async run() {
376
+ const { args, flags } = await this.parse(SourceTest);
377
+ setVerboseMode(flags.verbose);
378
+ if (flags.server && flags.client) {
379
+ this.error('Cannot use `--server` and `--client` together.');
380
+ }
381
+ const cwd = resolveProjectCwd(flags.cwd);
382
+ const commandArgs = ['test', ...(args.paths ?? [])];
383
+ if (flags.watch) {
384
+ commandArgs.push('--watch');
385
+ }
386
+ if (flags.run || !flags.watch) {
387
+ commandArgs.push('--run');
388
+ }
389
+ if (flags.allowOnly) {
390
+ commandArgs.push('--allowOnly');
391
+ }
392
+ if (flags.bail) {
393
+ commandArgs.push('--bail');
394
+ }
395
+ if (flags.coverage) {
396
+ commandArgs.push('--coverage');
397
+ }
398
+ if (flags.server) {
399
+ commandArgs.push('--server');
400
+ }
401
+ else if (flags.client) {
402
+ commandArgs.push('--client');
403
+ }
404
+ if (flags['db-clean']) {
405
+ commandArgs.push('--db-clean');
406
+ }
407
+ if (flags['single-thread'] !== undefined) {
408
+ commandArgs.push(`--single-thread=${flags['single-thread']}`);
409
+ }
410
+ else if (!flags.client && !flags.server && inferTestEnv(args.paths ?? []) === 'server-side') {
411
+ commandArgs.push('--single-thread=true');
412
+ }
413
+ startTask('Recreating the built-in test database...');
414
+ let testDbConfig;
415
+ let testDbDistributor;
416
+ try {
417
+ testDbConfig = await prepareTestDatabase(buildTestDbConfig({
418
+ cwd,
419
+ dbDialect: flags['db-dialect'],
420
+ builtinDbImage: flags['db-image'],
421
+ dbPort: flags['db-port'],
422
+ dbDatabase: flags['db-database'],
423
+ dbUser: flags['db-user'],
424
+ dbPassword: flags['db-password'],
425
+ }), {
426
+ stdio: flags.verbose ? 'inherit' : 'ignore',
427
+ dbPortExplicit: Boolean(flags['db-port']),
428
+ });
429
+ if (shouldRunServerTests({
430
+ server: flags.server,
431
+ client: flags.client,
432
+ paths: args.paths ?? [],
433
+ })
434
+ && supportsTestDbDistributor(testDbConfig.env.DB_DIALECT)) {
435
+ testDbDistributor = await startTestDbDistributor({
436
+ cwd,
437
+ env: testDbConfig.env,
438
+ stdio: flags.verbose ? 'inherit' : 'ignore',
439
+ });
440
+ testDbConfig.env.DB_TEST_DISTRIBUTOR_PORT = testDbDistributor.port;
441
+ testDbConfig.env.DB_TEST_PREFIX = testDbDistributor.prefix;
442
+ }
443
+ succeedTask(`The built-in test database is ready at ${testDbConfig.env.DB_HOST}:${testDbConfig.env.DB_PORT}.`);
444
+ 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}`);
445
+ }
446
+ catch (error) {
447
+ const message = error instanceof Error ? error.message : String(error);
448
+ failTask('Failed to recreate the built-in test database.');
449
+ this.error(formatDbBootstrapFailure(message));
450
+ return;
451
+ }
452
+ try {
453
+ await runNocoBaseCommand(commandArgs, {
454
+ cwd,
455
+ stdio: flags.verbose ? 'inherit' : 'ignore',
456
+ env: testDbConfig.env,
457
+ });
458
+ }
459
+ catch (error) {
460
+ const message = error instanceof Error ? error.message : String(error);
461
+ this.error(message);
462
+ }
463
+ finally {
464
+ await testDbDistributor?.stop();
465
+ }
466
+ }
467
+ }
@@ -0,0 +1,12 @@
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 AppStart from './app/start.js';
10
+ export default class Start extends AppStart {
11
+ static hidden = true;
12
+ }
@@ -0,0 +1,12 @@
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 AppStop from './app/stop.js';
10
+ export default class Stop extends AppStop {
11
+ static hidden = true;
12
+ }
@@ -0,0 +1,12 @@
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 SourceTest from './source/test.js';
10
+ export default class Test extends SourceTest {
11
+ static hidden = true;
12
+ }
@@ -0,0 +1,12 @@
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 AppUpgrade from './app/upgrade.js';
10
+ export default class Upgrade extends AppUpgrade {
11
+ static hidden = true;
12
+ }
@@ -0,0 +1,133 @@
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
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
10
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
11
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
12
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
13
+ });
14
+ }
15
+ return path;
16
+ };
17
+ import { Command, loadHelpClass } from '@oclif/core';
18
+ import { dirname, join, relative } from 'node:path';
19
+ import { fileURLToPath, pathToFileURL } from 'node:url';
20
+ import { collectCommandModulePaths, commandRelativePathToRegistryKey, } from "../lib/command-discovery.js";
21
+ import { getCurrentEnvName, getEnv } from "../lib/auth-store.js";
22
+ import { createGeneratedFlags, GeneratedApiCommand } from "../lib/generated-command.js";
23
+ import { toKebabCase } from "../lib/naming.js";
24
+ import { loadRuntimeSync } from "../lib/runtime-store.js";
25
+ const registryFilePath = fileURLToPath(import.meta.url);
26
+ const commandsRoot = join(dirname(registryFilePath), '../commands');
27
+ const commandModuleExtension = registryFilePath.endsWith('.ts') ? '.ts' : '.js';
28
+ async function loadCommandsFromDirectory() {
29
+ const absolutePaths = await collectCommandModulePaths(commandsRoot, commandModuleExtension);
30
+ const entries = await Promise.all(absolutePaths.map(async (absolutePath) => {
31
+ const rel = relative(commandsRoot, absolutePath).replace(/\\/g, '/');
32
+ const key = commandRelativePathToRegistryKey(rel);
33
+ const mod = await import(__rewriteRelativeImportExtension(pathToFileURL(absolutePath).href));
34
+ return [key, mod.default];
35
+ }));
36
+ return Object.fromEntries(entries);
37
+ }
38
+ function readEnvName(argv) {
39
+ for (let index = 0; index < argv.length; index += 1) {
40
+ const token = argv[index];
41
+ if (token === '--env') {
42
+ return argv[index + 1];
43
+ }
44
+ if (token === '-e') {
45
+ return argv[index + 1];
46
+ }
47
+ if (token.startsWith('--env=')) {
48
+ return token.slice('--env='.length);
49
+ }
50
+ }
51
+ return undefined;
52
+ }
53
+ function createRuntimeCommand(operation) {
54
+ return class RuntimeCommand extends GeneratedApiCommand {
55
+ static summary = operation.summary;
56
+ static description = operation.description;
57
+ static examples = operation.examples;
58
+ static flags = createGeneratedFlags(operation);
59
+ static operation = operation;
60
+ };
61
+ }
62
+ function createRuntimeIndexCommand(commandId, metadata) {
63
+ const summary = metadata.summary || `Work with ${commandId}`;
64
+ const description = metadata.description && metadata.description !== summary ? metadata.description : undefined;
65
+ return class RuntimeIndexCommand extends Command {
66
+ static summary = summary;
67
+ static description = description;
68
+ async run() {
69
+ await this.parse(RuntimeIndexCommand);
70
+ const Help = await loadHelpClass(this.config);
71
+ await new Help(this.config, this.config.pjson.oclif.helpOptions ?? this.config.pjson.helpOptions).showHelp([
72
+ this.id ?? commandId.replaceAll(' ', ':'),
73
+ ...this.argv,
74
+ ]);
75
+ }
76
+ };
77
+ }
78
+ function getRuntimeTopicEntries(operation) {
79
+ const commandSegments = operation.commandId.split(' ');
80
+ const topLevelCommandId = commandSegments[0];
81
+ const modulePrefix = toKebabCase(operation.moduleDisplayName || operation.moduleName || '');
82
+ const isTopLevelResource = Boolean(topLevelCommandId && modulePrefix && topLevelCommandId !== modulePrefix);
83
+ const entries = [];
84
+ if (!topLevelCommandId) {
85
+ return entries;
86
+ }
87
+ if (isTopLevelResource) {
88
+ entries.push([
89
+ topLevelCommandId,
90
+ {
91
+ summary: operation.resourceDescription || operation.resourceDisplayName,
92
+ description: operation.resourceDescription,
93
+ },
94
+ ]);
95
+ return entries;
96
+ }
97
+ entries.push([
98
+ topLevelCommandId,
99
+ {
100
+ summary: operation.moduleDescription || operation.moduleDisplayName || operation.moduleName,
101
+ description: operation.moduleDescription,
102
+ },
103
+ ]);
104
+ const resourceCommandId = commandSegments.slice(0, 2).join(' ');
105
+ if (commandSegments[1]) {
106
+ entries.push([
107
+ resourceCommandId,
108
+ {
109
+ summary: operation.resourceDescription || operation.resourceDisplayName,
110
+ description: operation.resourceDescription,
111
+ },
112
+ ]);
113
+ }
114
+ return entries;
115
+ }
116
+ const registry = {
117
+ ...(await loadCommandsFromDirectory()),
118
+ };
119
+ const envName = readEnvName(process.argv.slice(2)) ?? (await getCurrentEnvName());
120
+ const env = await getEnv(envName);
121
+ const runtime = loadRuntimeSync(env?.runtime?.version);
122
+ for (const operation of runtime?.commands ?? []) {
123
+ const commandSegments = operation.commandId.split(' ');
124
+ const commandKey = commandSegments.join(':');
125
+ registry[`api:${commandKey}`] = createRuntimeCommand(operation);
126
+ for (const [topicCommandId, metadata] of getRuntimeTopicEntries(operation)) {
127
+ const topicKey = `api:${topicCommandId.split(' ').join(':')}`;
128
+ if (!registry[topicKey]) {
129
+ registry[topicKey] = createRuntimeIndexCommand(`api ${topicCommandId}`, metadata);
130
+ }
131
+ }
132
+ }
133
+ export default registry;
@@ -0,0 +1,23 @@
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 { Help } from '@oclif/core';
10
+ export function isTopicIndexCommand(commandId, topics) {
11
+ if (!commandId) {
12
+ return false;
13
+ }
14
+ return topics.some((topic) => topic.name.startsWith(`${commandId}:`));
15
+ }
16
+ export default class RuntimeHelp extends Help {
17
+ get sortedCommands() {
18
+ return super.sortedCommands.filter((command) => !isTopicIndexCommand(command.id, this.config.topics));
19
+ }
20
+ get sortedTopics() {
21
+ return super.sortedTopics.filter((topic) => !topic.hidden);
22
+ }
23
+ }