@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.
Files changed (145) hide show
  1. package/LICENSE.txt +107 -0
  2. package/README.md +367 -19
  3. package/README.zh-CN.md +336 -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/build.js +57 -0
  14. package/dist/commands/db/logs.js +85 -0
  15. package/dist/commands/db/ps.js +60 -0
  16. package/dist/commands/db/shared.js +95 -0
  17. package/dist/commands/db/start.js +70 -0
  18. package/dist/commands/db/stop.js +70 -0
  19. package/dist/commands/dev.js +156 -0
  20. package/dist/commands/down.js +197 -0
  21. package/dist/commands/download.js +865 -0
  22. package/dist/commands/env/add.js +307 -0
  23. package/dist/commands/env/auth.js +55 -0
  24. package/dist/commands/env/list.js +36 -0
  25. package/dist/commands/env/remove.js +59 -0
  26. package/dist/commands/env/update.js +67 -0
  27. package/dist/commands/env/use.js +28 -0
  28. package/dist/commands/init.js +950 -0
  29. package/dist/commands/install.js +1927 -0
  30. package/dist/commands/logs.js +97 -0
  31. package/dist/commands/pm/disable.js +63 -0
  32. package/dist/commands/pm/enable.js +63 -0
  33. package/dist/commands/pm/list.js +61 -0
  34. package/dist/commands/prompts-stages.js +150 -0
  35. package/dist/commands/prompts-test.js +181 -0
  36. package/dist/commands/ps.js +119 -0
  37. package/dist/commands/restart.js +74 -0
  38. package/dist/commands/scaffold/migration.js +38 -0
  39. package/dist/commands/scaffold/plugin.js +37 -0
  40. package/dist/commands/self/check.js +71 -0
  41. package/dist/commands/self/index.js +20 -0
  42. package/dist/commands/self/update.js +86 -0
  43. package/dist/commands/skills/check.js +69 -0
  44. package/dist/commands/skills/index.js +20 -0
  45. package/dist/commands/skills/install.js +71 -0
  46. package/dist/commands/skills/update.js +71 -0
  47. package/dist/commands/start.js +218 -0
  48. package/dist/commands/stop.js +97 -0
  49. package/dist/commands/test.js +466 -0
  50. package/dist/commands/upgrade.js +594 -0
  51. package/dist/generated/command-registry.js +133 -0
  52. package/dist/help/runtime-help.js +20 -0
  53. package/dist/lib/api-client.js +244 -0
  54. package/dist/lib/app-runtime.js +153 -0
  55. package/dist/lib/auth-store.js +357 -0
  56. package/dist/lib/bootstrap.js +388 -0
  57. package/dist/lib/build-config.js +10 -0
  58. package/dist/lib/cli-home.js +61 -0
  59. package/dist/lib/cli-locale.js +115 -0
  60. package/dist/lib/command-discovery.js +39 -0
  61. package/dist/lib/env-auth.js +872 -0
  62. package/dist/lib/generated-command.js +150 -0
  63. package/dist/lib/http-request.js +49 -0
  64. package/dist/lib/naming.js +70 -0
  65. package/dist/lib/openapi.js +62 -0
  66. package/dist/lib/post-processors.js +23 -0
  67. package/dist/lib/prompt-catalog.js +581 -0
  68. package/dist/lib/prompt-validators.js +185 -0
  69. package/dist/lib/prompt-web-ui.js +2096 -0
  70. package/dist/lib/resource-command.js +343 -0
  71. package/dist/lib/resource-request.js +104 -0
  72. package/dist/lib/run-npm.js +197 -0
  73. package/dist/lib/runtime-generator.js +419 -0
  74. package/dist/lib/runtime-store.js +56 -0
  75. package/dist/lib/self-manager.js +246 -0
  76. package/dist/lib/skills-manager.js +269 -0
  77. package/dist/lib/startup-update.js +203 -0
  78. package/dist/lib/ui.js +175 -0
  79. package/dist/locale/en-US.json +336 -0
  80. package/dist/locale/zh-CN.json +336 -0
  81. package/dist/post-processors/data-modeling.js +66 -0
  82. package/dist/post-processors/data-source-manager.js +114 -0
  83. package/dist/post-processors/index.js +19 -0
  84. package/nocobase-ctl.config.json +287 -0
  85. package/package.json +60 -26
  86. package/LICENSE +0 -661
  87. package/bin/index.js +0 -39
  88. package/nocobase.conf.tpl +0 -95
  89. package/src/cli.js +0 -19
  90. package/src/commands/benchmark.js +0 -73
  91. package/src/commands/build.js +0 -49
  92. package/src/commands/clean.js +0 -30
  93. package/src/commands/client.js +0 -166
  94. package/src/commands/create-nginx-conf.js +0 -37
  95. package/src/commands/create-plugin.js +0 -33
  96. package/src/commands/dev.js +0 -200
  97. package/src/commands/doc.js +0 -76
  98. package/src/commands/e2e.js +0 -265
  99. package/src/commands/global.js +0 -43
  100. package/src/commands/index.js +0 -45
  101. package/src/commands/instance-id.js +0 -47
  102. package/src/commands/locale/cronstrue.js +0 -122
  103. package/src/commands/locale/react-js-cron/en-US.json +0 -75
  104. package/src/commands/locale/react-js-cron/index.js +0 -17
  105. package/src/commands/locale/react-js-cron/zh-CN.json +0 -33
  106. package/src/commands/locale/react-js-cron/zh-TW.json +0 -33
  107. package/src/commands/locale.js +0 -81
  108. package/src/commands/p-test.js +0 -88
  109. package/src/commands/perf.js +0 -63
  110. package/src/commands/pkg.js +0 -321
  111. package/src/commands/pm2.js +0 -37
  112. package/src/commands/postinstall.js +0 -88
  113. package/src/commands/start.js +0 -148
  114. package/src/commands/tar.js +0 -36
  115. package/src/commands/test-coverage.js +0 -55
  116. package/src/commands/test.js +0 -107
  117. package/src/commands/umi.js +0 -33
  118. package/src/commands/update-deps.js +0 -72
  119. package/src/commands/upgrade.js +0 -47
  120. package/src/commands/view-license-key.js +0 -44
  121. package/src/index.js +0 -14
  122. package/src/license.js +0 -76
  123. package/src/logger.js +0 -75
  124. package/src/plugin-generator.js +0 -80
  125. package/src/util.js +0 -517
  126. package/templates/bundle-status.html +0 -338
  127. package/templates/create-app-package.json +0 -39
  128. package/templates/plugin/.npmignore.tpl +0 -2
  129. package/templates/plugin/README.md.tpl +0 -1
  130. package/templates/plugin/client.d.ts +0 -2
  131. package/templates/plugin/client.js +0 -1
  132. package/templates/plugin/package.json.tpl +0 -11
  133. package/templates/plugin/server.d.ts +0 -2
  134. package/templates/plugin/server.js +0 -1
  135. package/templates/plugin/src/client/client.d.ts +0 -249
  136. package/templates/plugin/src/client/index.tsx.tpl +0 -1
  137. package/templates/plugin/src/client/locale.ts +0 -21
  138. package/templates/plugin/src/client/models/index.ts +0 -12
  139. package/templates/plugin/src/client/plugin.tsx.tpl +0 -10
  140. package/templates/plugin/src/index.ts +0 -2
  141. package/templates/plugin/src/locale/en-US.json +0 -1
  142. package/templates/plugin/src/locale/zh-CN.json +0 -1
  143. package/templates/plugin/src/server/collections/.gitkeep +0 -0
  144. package/templates/plugin/src/server/index.ts.tpl +0 -1
  145. package/templates/plugin/src/server/plugin.ts.tpl +0 -19
@@ -0,0 +1,388 @@
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 { getCurrentEnvName, getEnv, setEnvRuntime, updateEnvConnection } from './auth-store.js';
10
+ import { resolveAccessToken } from './env-auth.js';
11
+ import { fetchWithPreservedAuthRedirect } from './http-request.js';
12
+ import { generateRuntime } from './runtime-generator.js';
13
+ import { hasRuntimeSync, saveRuntime } from './runtime-store.js';
14
+ import { confirmAction, printInfo, printVerbose, printWarningBlock, setVerboseMode, stopTask, updateTask } from './ui.js';
15
+ const APP_RETRY_INTERVAL = 2000;
16
+ const APP_RETRY_TIMEOUT = 120000;
17
+ function readFlag(argv, name) {
18
+ const exact = `--${name}`;
19
+ const prefix = `--${name}=`;
20
+ const alias = name === 'env' ? '-e' : name === 'scope' ? '-s' : undefined;
21
+ for (let index = 0; index < argv.length; index += 1) {
22
+ const value = argv[index];
23
+ if (value === exact) {
24
+ return argv[index + 1];
25
+ }
26
+ if (alias && value === alias) {
27
+ return argv[index + 1];
28
+ }
29
+ if (value.startsWith(prefix)) {
30
+ return value.slice(prefix.length);
31
+ }
32
+ }
33
+ return undefined;
34
+ }
35
+ function hasBooleanFlag(argv, name) {
36
+ const exact = `--${name}`;
37
+ const negated = `--no-${name}`;
38
+ const prefix = `--${name}=`;
39
+ const alias = name === 'verbose' ? '-V' : undefined;
40
+ for (const value of argv) {
41
+ if (value === exact) {
42
+ return true;
43
+ }
44
+ if (alias && value === alias) {
45
+ return true;
46
+ }
47
+ if (value === negated) {
48
+ return false;
49
+ }
50
+ if (value.startsWith(prefix)) {
51
+ return value.slice(prefix.length) !== 'false';
52
+ }
53
+ }
54
+ return false;
55
+ }
56
+ function getCommandToken(argv) {
57
+ const tokens = [];
58
+ for (const token of argv) {
59
+ if (!token || token.startsWith('-')) {
60
+ continue;
61
+ }
62
+ tokens.push(token);
63
+ }
64
+ if (tokens[0] === 'api') {
65
+ return tokens[1] ?? tokens[0];
66
+ }
67
+ return tokens[0];
68
+ }
69
+ function hasHelpFlag(argv) {
70
+ return argv.includes('--help') || argv.includes('-h');
71
+ }
72
+ function hasVersionFlag(argv) {
73
+ return argv.includes('--version') || argv.includes('-v');
74
+ }
75
+ function isBuiltinCommand(argv) {
76
+ const commandTokens = argv.filter((token) => token && !token.startsWith('-'));
77
+ const [topic, subtopic] = commandTokens;
78
+ return topic === 'env' || topic === 'resource' || (topic === 'api' && subtopic === 'resource');
79
+ }
80
+ export function shouldSkipRuntimeBootstrap(argv) {
81
+ return hasVersionFlag(argv) || isBuiltinCommand(argv);
82
+ }
83
+ function shouldIgnoreBootstrapFailure(argv, commandToken) {
84
+ return !commandToken || hasHelpFlag(argv) || (commandToken === 'api' && argv.length === 1);
85
+ }
86
+ async function requestJson(url, options) {
87
+ const headers = new Headers();
88
+ if (options.token) {
89
+ headers.set('authorization', `Bearer ${options.token}`);
90
+ }
91
+ if (options.role) {
92
+ headers.set('x-role', options.role);
93
+ }
94
+ let response;
95
+ try {
96
+ response = await fetchWithPreservedAuthRedirect(url, {
97
+ method: options.method ?? 'GET',
98
+ headers,
99
+ });
100
+ }
101
+ catch (error) {
102
+ return {
103
+ status: 0,
104
+ ok: false,
105
+ data: {
106
+ error: {
107
+ message: error?.message ?? 'fetch failed',
108
+ },
109
+ },
110
+ };
111
+ }
112
+ const text = await response.text();
113
+ let data = undefined;
114
+ if (text) {
115
+ try {
116
+ data = JSON.parse(text);
117
+ }
118
+ catch (error) {
119
+ data = text;
120
+ }
121
+ }
122
+ return {
123
+ status: response.status,
124
+ ok: response.ok,
125
+ data,
126
+ };
127
+ }
128
+ function sleep(ms) {
129
+ return new Promise((resolve) => setTimeout(resolve, ms));
130
+ }
131
+ function isAppRestarting(response) {
132
+ return response.status === 503 && response.data?.error?.code === 'APP_COMMANDING';
133
+ }
134
+ function shouldRetryAppAvailability(response) {
135
+ return isAppRestarting(response) || response.status === 0;
136
+ }
137
+ function getSwaggerUrl(baseUrl) {
138
+ return `${baseUrl.replace(/\/+$/, '')}/swagger:get`;
139
+ }
140
+ function getHealthCheckUrl(baseUrl) {
141
+ return `${baseUrl.replace(/\/+$/, '')}/__health_check`;
142
+ }
143
+ async function waitForServiceReady(baseUrl, token, role) {
144
+ const healthCheckUrl = getHealthCheckUrl(baseUrl);
145
+ const startedAt = Date.now();
146
+ let notified = false;
147
+ while (Date.now() - startedAt < APP_RETRY_TIMEOUT) {
148
+ const response = await fetchWithPreservedAuthRedirect(healthCheckUrl, {
149
+ method: 'GET',
150
+ headers: token || role
151
+ ? {
152
+ ...(token ? { authorization: `Bearer ${token}` } : undefined),
153
+ ...(role ? { 'x-role': role } : undefined),
154
+ }
155
+ : undefined,
156
+ }).catch((error) => {
157
+ return {
158
+ ok: false,
159
+ status: 0,
160
+ text: async () => error?.message ?? 'fetch failed',
161
+ };
162
+ });
163
+ const text = await response.text();
164
+ if (response.ok && text.trim().toLowerCase() === 'ok') {
165
+ return;
166
+ }
167
+ if (!notified) {
168
+ printVerbose(`Waiting for health check: ${healthCheckUrl}`);
169
+ updateTask(`Waiting for application readiness (${healthCheckUrl})`);
170
+ notified = true;
171
+ }
172
+ await sleep(APP_RETRY_INTERVAL);
173
+ }
174
+ throw new Error(`The application did not become ready in time. Expected \`${healthCheckUrl}\` to respond with \`ok\`.`);
175
+ }
176
+ async function waitForSwaggerSchema(baseUrl, token, role) {
177
+ const swaggerUrl = getSwaggerUrl(baseUrl);
178
+ const startedAt = Date.now();
179
+ printVerbose(`Checking swagger schema: ${swaggerUrl}`);
180
+ while (Date.now() - startedAt < APP_RETRY_TIMEOUT) {
181
+ const response = await requestJson(swaggerUrl, { token, role });
182
+ if (response.ok) {
183
+ return response;
184
+ }
185
+ if (!shouldRetryAppAvailability(response)) {
186
+ return response;
187
+ }
188
+ await waitForServiceReady(baseUrl, token, role);
189
+ }
190
+ return await requestJson(swaggerUrl, { token, role });
191
+ }
192
+ async function confirmEnableApiDoc() {
193
+ return confirmAction('Enable the API documentation plugin now?', { defaultValue: false });
194
+ }
195
+ async function fetchSwaggerSchema(baseUrl, token, role, context = {}, options = {}) {
196
+ let response = options.retryAppAvailability === false
197
+ ? await requestJson(getSwaggerUrl(baseUrl), { token, role })
198
+ : await waitForSwaggerSchema(baseUrl, token, role);
199
+ if (response.status === 404) {
200
+ if (options.allowEnableApiDoc === false) {
201
+ throw new Error('`swagger:get` returned 404. Check the base URL and enable the `API documentation plugin` if needed.');
202
+ }
203
+ printInfo('The API documentation plugin is not enabled.');
204
+ const shouldEnable = await confirmEnableApiDoc();
205
+ if (!shouldEnable) {
206
+ throw new Error('`swagger:get` returned 404. Enable the `API documentation plugin` first.');
207
+ }
208
+ const enableUrl = `${baseUrl.replace(/\/+$/, '')}/pm:enable?filterByTk=api-doc`;
209
+ printVerbose(`Enabling API documentation plugin via ${enableUrl}`);
210
+ const enableResponse = await requestJson(enableUrl, { method: 'POST', token, role });
211
+ if (!enableResponse.ok) {
212
+ throw new Error(`Failed to enable the \`API documentation plugin\` via \`pm:enable\`.\n${JSON.stringify(enableResponse.data, null, 2)}`);
213
+ }
214
+ updateTask('Enabled the API documentation plugin. Waiting for application readiness...');
215
+ await waitForServiceReady(baseUrl, token, role);
216
+ response = await waitForSwaggerSchema(baseUrl, token, role);
217
+ }
218
+ if (!response.ok) {
219
+ throw new Error(formatSwaggerSchemaError(response, { baseUrl, token, ...context }));
220
+ }
221
+ return (response.data?.data ?? response.data);
222
+ }
223
+ function collectErrorEntries(data) {
224
+ if (Array.isArray(data?.errors)) {
225
+ return data.errors.filter(Boolean);
226
+ }
227
+ if (data?.error) {
228
+ return [data.error];
229
+ }
230
+ return [];
231
+ }
232
+ function hasAuthenticationError(data) {
233
+ return collectErrorEntries(data).some((entry) => entry?.code === 'INVALID_TOKEN' || entry?.code === 'EMPTY_TOKEN');
234
+ }
235
+ function isNetworkFetchFailure(response) {
236
+ return response.status === 0;
237
+ }
238
+ export function formatSwaggerSchemaError(response, context) {
239
+ if (hasAuthenticationError(response.data)) {
240
+ const entries = collectErrorEntries(response.data);
241
+ const details = entries
242
+ .map((entry) => {
243
+ const code = entry?.code ? `[${entry.code}] ` : '';
244
+ return `${code}${entry?.message ?? 'Authentication failed.'}`;
245
+ })
246
+ .join('\n');
247
+ const envLabel = context.envName ? ` for env "${context.envName}"` : '';
248
+ const commandHint = context.commandToken
249
+ ? `If \`${context.commandToken}\` is a runtime command, refresh the runtime after updating the token with \`nb env update\`. If it is a typo, run \`nb --help\` to inspect available commands.`
250
+ : 'Run `nb --help` to inspect built-in commands, then refresh runtime commands with `nb env update` after updating the token.';
251
+ return [
252
+ `Authentication failed while loading the command runtime from \`swagger:get\`${envLabel}.`,
253
+ `Base URL: ${context.baseUrl}`,
254
+ details,
255
+ 'Update the API key with `nb env add <name> --api-base-url <url> --auth-type token --token <api-key>`, log in with `nb env auth <name>`, or rerun the command with `--token <api-key>`.',
256
+ commandHint,
257
+ ].join('\n');
258
+ }
259
+ if (isNetworkFetchFailure(response)) {
260
+ const rawMessage = response.data?.error?.message || 'fetch failed';
261
+ return [
262
+ 'Failed to reach the NocoBase server while loading the command runtime from `swagger:get`.',
263
+ `Base URL: ${context.baseUrl}`,
264
+ `Network error: ${rawMessage}`,
265
+ 'Check that the NocoBase app is running, the base URL is correct, and the server is reachable from this machine.',
266
+ 'If you recently changed the server address, update it with `nb env add <name> --api-base-url <url>` and retry `nb env update`.',
267
+ 'Use `nb env list` to inspect the current env configuration.',
268
+ ].join('\n');
269
+ }
270
+ return `Failed to load swagger schema from \`swagger:get\`.\n${JSON.stringify(response.data, null, 2)}`;
271
+ }
272
+ export function formatMissingRuntimeEnvError(commandToken) {
273
+ if (!commandToken) {
274
+ return [
275
+ 'No env is configured for runtime commands.',
276
+ 'Run `nb env add <name> --api-base-url <url>` first.',
277
+ 'If you configure multiple environments later, switch with `nb env use <name>`.',
278
+ ].join('\n');
279
+ }
280
+ return [
281
+ `Unable to resolve runtime command \`${commandToken}\`.`,
282
+ 'No env is configured, so the CLI cannot load runtime commands from `swagger:get`.',
283
+ 'If this is a built-in command or a typo, run `nb --help` to inspect available commands.',
284
+ 'If this should be an application runtime command, run `nb env add <name> --api-base-url <url>` and then `nb env update`.',
285
+ ].join('\n');
286
+ }
287
+ export async function ensureRuntimeFromArgv(argv, options) {
288
+ const commandToken = getCommandToken(argv);
289
+ const isRootInvocation = !commandToken;
290
+ const canContinueWithoutRuntime = shouldIgnoreBootstrapFailure(argv, commandToken);
291
+ setVerboseMode(hasBooleanFlag(argv, 'verbose'));
292
+ if (shouldSkipRuntimeBootstrap(argv)) {
293
+ return;
294
+ }
295
+ try {
296
+ const envName = readFlag(argv, 'env') ?? (await getCurrentEnvName());
297
+ const env = await getEnv(envName);
298
+ const baseUrl = readFlag(argv, 'api-base-url') ?? env?.baseUrl;
299
+ const role = readFlag(argv, 'role');
300
+ const token = await resolveAccessToken({
301
+ envName,
302
+ baseUrl,
303
+ token: readFlag(argv, 'token'),
304
+ });
305
+ const runtimeVersion = env?.runtime?.version;
306
+ if (runtimeVersion && hasRuntimeSync(runtimeVersion)) {
307
+ return;
308
+ }
309
+ if (!baseUrl) {
310
+ if (isRootInvocation) {
311
+ return;
312
+ }
313
+ throw new Error(formatMissingRuntimeEnvError(commandToken));
314
+ }
315
+ updateTask('Loading command runtime...');
316
+ try {
317
+ printVerbose(`Runtime source: ${baseUrl}`);
318
+ const document = await fetchSwaggerSchema(baseUrl, token, role, { envName, commandToken }, isRootInvocation
319
+ ? {
320
+ allowEnableApiDoc: false,
321
+ retryAppAvailability: false,
322
+ }
323
+ : undefined);
324
+ const runtime = await generateRuntime(document, options.configFile, baseUrl);
325
+ await saveRuntime(runtime);
326
+ await setEnvRuntime(envName, {
327
+ version: runtime.version,
328
+ schemaHash: runtime.schemaHash,
329
+ generatedAt: runtime.generatedAt,
330
+ });
331
+ }
332
+ finally {
333
+ stopTask();
334
+ }
335
+ }
336
+ catch (error) {
337
+ if (!canContinueWithoutRuntime) {
338
+ throw error;
339
+ }
340
+ stopTask();
341
+ const message = error instanceof Error ? error.message : String(error);
342
+ printWarningBlock(`Unable to load runtime commands. Showing built-in help instead.\n\n${message}`);
343
+ }
344
+ }
345
+ export async function updateEnvRuntime(options) {
346
+ setVerboseMode(Boolean(options.verbose));
347
+ const envName = options.envName ?? (await getCurrentEnvName({ scope: options.scope }));
348
+ const env = await getEnv(envName, { scope: options.scope });
349
+ const baseUrl = options.baseUrl ?? env?.baseUrl;
350
+ const token = await resolveAccessToken({
351
+ envName,
352
+ baseUrl,
353
+ token: options.token,
354
+ scope: options.scope,
355
+ });
356
+ if (!baseUrl) {
357
+ throw new Error([
358
+ env
359
+ ? `Env "${envName}" is missing a base URL.`
360
+ : `Env "${envName}" is not configured. Run \`nb env add ${envName}\` first.`,
361
+ env ? 'Update it with `nb env add <name> --api-base-url <url>` first.' : '',
362
+ ]
363
+ .filter(Boolean)
364
+ .join('\n'));
365
+ }
366
+ updateTask('Loading command runtime...');
367
+ try {
368
+ printVerbose(`Runtime source: ${baseUrl}`);
369
+ const document = await fetchSwaggerSchema(baseUrl, token, options.role, { envName });
370
+ const runtime = await generateRuntime(document, options.configFile, baseUrl);
371
+ await saveRuntime(runtime, { scope: options.scope });
372
+ if (options.baseUrl !== undefined || options.token !== undefined) {
373
+ await updateEnvConnection(envName, {
374
+ apiBaseUrl: options.baseUrl,
375
+ accessToken: options.token,
376
+ }, { scope: options.scope });
377
+ }
378
+ await setEnvRuntime(envName, {
379
+ version: runtime.version,
380
+ schemaHash: runtime.schemaHash,
381
+ generatedAt: runtime.generatedAt,
382
+ }, { scope: options.scope });
383
+ return runtime;
384
+ }
385
+ finally {
386
+ stopTask();
387
+ }
388
+ }
@@ -0,0 +1,10 @@
1
+ import { promises as fs } from 'node:fs';
2
+ export async function loadBuildConfig(filePath) {
3
+ try {
4
+ const content = await fs.readFile(filePath, 'utf8');
5
+ return JSON.parse(content);
6
+ }
7
+ catch (error) {
8
+ return {};
9
+ }
10
+ }
@@ -0,0 +1,61 @@
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 fs from 'node:fs';
10
+ import os from 'node:os';
11
+ import path from 'node:path';
12
+ export const CLI_HOME_DIRNAME = '.nocobase';
13
+ export const NB_CONFIG_SCOPE_ENV = 'NB_CONFIG_SCOPE';
14
+ export const NB_ENV_ROOT_ENV = 'NB_ENV_ROOT';
15
+ export function resolveDefaultConfigScope() {
16
+ const raw = String(process.env[NB_CONFIG_SCOPE_ENV] ?? '').trim().toLowerCase();
17
+ return raw === 'project' ? 'project' : 'global';
18
+ }
19
+ function resolveGlobalCliHomeRoot() {
20
+ if (process.env.NOCOBASE_CTL_HOME) {
21
+ return process.env.NOCOBASE_CTL_HOME;
22
+ }
23
+ return os.homedir();
24
+ }
25
+ export function resolveCliHomeRoot(scope = resolveDefaultConfigScope()) {
26
+ const cwdRoot = process.cwd();
27
+ if (scope === 'project') {
28
+ return cwdRoot;
29
+ }
30
+ if (scope === 'global') {
31
+ return resolveGlobalCliHomeRoot();
32
+ }
33
+ const cwdCliHome = path.join(cwdRoot, CLI_HOME_DIRNAME);
34
+ if (fs.existsSync(cwdCliHome)) {
35
+ return cwdRoot;
36
+ }
37
+ return resolveGlobalCliHomeRoot();
38
+ }
39
+ export function resolveCliHomeDir(scope = resolveDefaultConfigScope()) {
40
+ return path.join(resolveCliHomeRoot(scope), CLI_HOME_DIRNAME);
41
+ }
42
+ export function resolveEnvRoot(scope = resolveDefaultConfigScope()) {
43
+ const envRoot = String(process.env[NB_ENV_ROOT_ENV] ?? '').trim();
44
+ if (envRoot) {
45
+ return path.resolve(envRoot);
46
+ }
47
+ return resolveCliHomeRoot(scope);
48
+ }
49
+ export function resolveEnvRelativePath(relativePath, scope = resolveDefaultConfigScope()) {
50
+ return path.resolve(resolveEnvRoot(scope), relativePath);
51
+ }
52
+ export function resolveConfiguredEnvPath(value, scope = resolveDefaultConfigScope()) {
53
+ const text = String(value ?? '').trim();
54
+ if (!text) {
55
+ return undefined;
56
+ }
57
+ return path.isAbsolute(text) ? text : resolveEnvRelativePath(text, scope);
58
+ }
59
+ export function formatCliHomeScope(scope) {
60
+ return scope === 'project' ? 'project' : 'global';
61
+ }
@@ -0,0 +1,115 @@
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 { readFileSync } from 'node:fs';
10
+ export const SUPPORTED_CLI_LOCALES = ['en-US', 'zh-CN'];
11
+ export const CLI_LOCALE_FLAG_OPTIONS = [...SUPPORTED_CLI_LOCALES];
12
+ export const CLI_LOCALE_FLAG_DESCRIPTION = 'Language for CLI prompts and the local setup UI.';
13
+ const DEFAULT_CLI_LOCALE = 'en-US';
14
+ const localeCache = {};
15
+ function normalizeCliLocale(value) {
16
+ const raw = String(value ?? '').trim();
17
+ if (!raw) {
18
+ return undefined;
19
+ }
20
+ const normalized = raw.replace(/\..*$/, '').replace(/_/g, '-').toLowerCase();
21
+ if (normalized === 'zh' || normalized.startsWith('zh-')) {
22
+ return 'zh-CN';
23
+ }
24
+ if (normalized === 'en' || normalized.startsWith('en-')) {
25
+ return 'en-US';
26
+ }
27
+ return undefined;
28
+ }
29
+ function loadLocaleMessages(locale) {
30
+ if (localeCache[locale]) {
31
+ return localeCache[locale];
32
+ }
33
+ const fileUrl = new URL(`../locale/${locale}.json`, import.meta.url);
34
+ const parsed = JSON.parse(readFileSync(fileUrl, 'utf8'));
35
+ localeCache[locale] = parsed;
36
+ return parsed;
37
+ }
38
+ function getPathValue(input, path) {
39
+ let current = input;
40
+ for (const part of path.split('.')) {
41
+ if (!current || typeof current !== 'object' || !Object.prototype.hasOwnProperty.call(current, part)) {
42
+ return undefined;
43
+ }
44
+ current = current[part];
45
+ }
46
+ return current;
47
+ }
48
+ function interpolateTemplate(template, values) {
49
+ return template.replace(/{{\s*([\w.]+)\s*}}/g, (_match, key) => {
50
+ const value = getPathValue(values, key);
51
+ return value === undefined || value === null ? '' : String(value);
52
+ });
53
+ }
54
+ export function detectCliLocale() {
55
+ const candidates = [
56
+ process.env.NB_LOCALE,
57
+ process.env.LC_ALL,
58
+ process.env.LC_MESSAGES,
59
+ process.env.LANG,
60
+ Intl.DateTimeFormat().resolvedOptions().locale,
61
+ ];
62
+ for (const candidate of candidates) {
63
+ const locale = normalizeCliLocale(candidate);
64
+ if (locale) {
65
+ return locale;
66
+ }
67
+ }
68
+ return DEFAULT_CLI_LOCALE;
69
+ }
70
+ export function resolveCliLocale(preferred) {
71
+ return normalizeCliLocale(preferred) ?? detectCliLocale();
72
+ }
73
+ export function applyCliLocale(preferred) {
74
+ const locale = resolveCliLocale(preferred);
75
+ process.env.NB_LOCALE = locale;
76
+ return locale;
77
+ }
78
+ export function createCliTranslate(preferred) {
79
+ const locale = resolveCliLocale(preferred);
80
+ return (key, values, fallback) => {
81
+ const messages = loadLocaleMessages(locale);
82
+ const template = getPathValue(messages, key);
83
+ if (typeof template !== 'string') {
84
+ return interpolateTemplate(fallback ?? key, values);
85
+ }
86
+ return interpolateTemplate(template, values);
87
+ };
88
+ }
89
+ export function translateCli(key, values, options) {
90
+ return createCliTranslate(options?.locale)(key, values, options?.fallback);
91
+ }
92
+ export function localeText(key, values, fallback) {
93
+ return {
94
+ key,
95
+ ...(values ? { values } : {}),
96
+ ...(fallback ? { fallback } : {}),
97
+ };
98
+ }
99
+ export function isLocalizedTextDef(value) {
100
+ return Boolean(value
101
+ && typeof value === 'object'
102
+ && typeof value.key === 'string');
103
+ }
104
+ export function resolveLocalizedText(text, options) {
105
+ if (text === undefined) {
106
+ return options?.fallback ?? '';
107
+ }
108
+ if (typeof text === 'string') {
109
+ return text;
110
+ }
111
+ return translateCli(text.key, text.values, {
112
+ locale: options?.locale,
113
+ fallback: text.fallback ?? options?.fallback,
114
+ });
115
+ }
@@ -0,0 +1,39 @@
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 { readdir } from 'node:fs/promises';
10
+ import { join } from 'node:path';
11
+ /**
12
+ * Recursively collect command module paths under `commandsRoot` (e.g. `dist/commands` → `.js`, `src/commands` → `.ts`).
13
+ */
14
+ export async function collectCommandModulePaths(commandsRoot, extension) {
15
+ const entries = await readdir(commandsRoot, { withFileTypes: true });
16
+ const files = [];
17
+ for (const ent of entries) {
18
+ const full = join(commandsRoot, ent.name);
19
+ if (ent.isDirectory()) {
20
+ files.push(...(await collectCommandModulePaths(full, extension)));
21
+ }
22
+ else if (ent.isFile() && ent.name.endsWith(extension)) {
23
+ files.push(full);
24
+ }
25
+ }
26
+ return files.sort();
27
+ }
28
+ /**
29
+ * Map a path relative to `commands/` with `.js` / `.ts` to an oclif explicit-registry key.
30
+ * `api/resource/foo.js` → `api:resource:foo`; trailing `index` maps to the parent command.
31
+ */
32
+ export function commandRelativePathToRegistryKey(relativePath) {
33
+ const normalized = relativePath.replace(/\\/g, '/').replace(/\.(js|ts)$/i, '');
34
+ const segments = normalized.split('/').filter(Boolean);
35
+ if (segments.at(-1) === 'index') {
36
+ segments.pop();
37
+ }
38
+ return segments.join(':');
39
+ }