@nocobase/cli 2.1.0-alpha.25 → 2.1.0-alpha.27

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 (84) hide show
  1. package/README.md +61 -49
  2. package/README.zh-CN.md +40 -47
  3. package/dist/commands/app/down.js +259 -0
  4. package/dist/commands/app/logs.js +98 -0
  5. package/dist/commands/app/restart.js +75 -0
  6. package/dist/commands/app/start.js +252 -0
  7. package/dist/commands/app/stop.js +98 -0
  8. package/dist/commands/app/upgrade.js +579 -0
  9. package/dist/commands/build.js +3 -48
  10. package/dist/commands/config/delete.js +30 -0
  11. package/dist/commands/config/get.js +29 -0
  12. package/dist/commands/config/index.js +20 -0
  13. package/dist/commands/config/list.js +29 -0
  14. package/dist/commands/config/set.js +35 -0
  15. package/dist/commands/db/check.js +230 -0
  16. package/dist/commands/db/shared.js +1 -1
  17. package/dist/commands/dev.js +3 -147
  18. package/dist/commands/down.js +3 -188
  19. package/dist/commands/download.js +4 -856
  20. package/dist/commands/env/add.js +28 -23
  21. package/dist/commands/env/info.js +152 -0
  22. package/dist/commands/env/list.js +23 -9
  23. package/dist/commands/env/shared.js +158 -0
  24. package/dist/commands/{prompts-stages.js → examples/prompts-stages.js} +3 -3
  25. package/dist/commands/{prompts-test.js → examples/prompts-test.js} +3 -3
  26. package/dist/commands/init.js +83 -6
  27. package/dist/commands/install.js +361 -82
  28. package/dist/commands/license/activate.js +357 -0
  29. package/dist/commands/license/env.js +94 -0
  30. package/dist/commands/license/generate-id.js +107 -0
  31. package/dist/commands/license/id.js +52 -0
  32. package/dist/commands/license/index.js +20 -0
  33. package/dist/commands/license/plugins/clean.js +98 -0
  34. package/dist/commands/license/plugins/index.js +20 -0
  35. package/dist/commands/license/plugins/list.js +50 -0
  36. package/dist/commands/license/plugins/shared.js +325 -0
  37. package/dist/commands/license/plugins/sync.js +267 -0
  38. package/dist/commands/license/shared.js +411 -0
  39. package/dist/commands/license/status.js +50 -0
  40. package/dist/commands/logs.js +3 -88
  41. package/dist/commands/plugin/disable.js +64 -0
  42. package/dist/commands/plugin/enable.js +64 -0
  43. package/dist/commands/plugin/list.js +62 -0
  44. package/dist/commands/pm/disable.js +3 -54
  45. package/dist/commands/pm/enable.js +3 -54
  46. package/dist/commands/pm/list.js +3 -52
  47. package/dist/commands/restart.js +3 -65
  48. package/dist/commands/scaffold/migration.js +1 -1
  49. package/dist/commands/scaffold/plugin.js +1 -1
  50. package/dist/commands/skills/remove.js +71 -0
  51. package/dist/commands/skills/update.js +7 -0
  52. package/dist/commands/source/build.js +58 -0
  53. package/dist/commands/source/dev.js +157 -0
  54. package/dist/commands/source/download.js +866 -0
  55. package/dist/commands/source/test.js +467 -0
  56. package/dist/commands/start.js +3 -209
  57. package/dist/commands/stop.js +3 -88
  58. package/dist/commands/test.js +3 -457
  59. package/dist/commands/upgrade.js +3 -585
  60. package/dist/help/runtime-help.js +3 -0
  61. package/dist/lib/api-client.js +94 -9
  62. package/dist/lib/app-health.js +126 -0
  63. package/dist/lib/app-managed-resources.js +264 -0
  64. package/dist/lib/app-runtime.js +26 -10
  65. package/dist/lib/auth-store.js +29 -63
  66. package/dist/lib/build-config.js +8 -0
  67. package/dist/lib/cli-config.js +176 -0
  68. package/dist/lib/cli-home.js +12 -26
  69. package/dist/lib/cli-locale.js +15 -1
  70. package/dist/lib/db-connection-check.js +178 -0
  71. package/dist/lib/env-config.js +80 -0
  72. package/dist/lib/generated-command.js +23 -3
  73. package/dist/lib/plugin-storage.js +127 -0
  74. package/dist/lib/prompt-validators.js +4 -4
  75. package/dist/lib/prompt-web-ui.js +13 -6
  76. package/dist/lib/runtime-generator.js +89 -10
  77. package/dist/lib/self-manager.js +57 -2
  78. package/dist/lib/skills-manager.js +34 -7
  79. package/dist/lib/startup-update.js +85 -7
  80. package/dist/locale/en-US.json +16 -13
  81. package/dist/locale/zh-CN.json +16 -13
  82. package/nocobase-ctl.config.json +82 -0
  83. package/package.json +41 -6
  84. package/dist/commands/ps.js +0 -119
@@ -0,0 +1,357 @@
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 * as p from '@clack/prompts';
10
+ import { Command, Flags } from '@oclif/core';
11
+ import { readFile } from 'node:fs/promises';
12
+ import { ensureInstanceId, licenseEnvFlag, licenseJsonFlag, licensePkgUrlFlag, redactLicenseKey, requireLicenseRuntime, resolveLicenseKeyFile, resolveLicenseServiceUrl, saveLicenseKey, sanitizeLicenseOutput, validateLicenseKey, } from './shared.js';
13
+ import { isInteractiveTerminal } from '../../lib/ui.js';
14
+ import { appUrl } from '../env/shared.js';
15
+ function resolveOnlineInputValue(value) {
16
+ return String(value ?? '').trim();
17
+ }
18
+ async function promptActivationMode() {
19
+ const answer = await p.select({
20
+ message: 'How do you want to activate the license?',
21
+ options: [
22
+ { value: 'key', label: 'Use an existing license key' },
23
+ { value: 'online', label: 'Request and activate a license online' },
24
+ { value: 'cancel', label: 'Cancel' },
25
+ ],
26
+ initialValue: 'key',
27
+ });
28
+ if (p.isCancel(answer)) {
29
+ p.cancel('License activation cancelled.');
30
+ return 'cancel';
31
+ }
32
+ return answer;
33
+ }
34
+ async function promptLicenseKeyInput() {
35
+ const answer = await p.select({
36
+ message: 'How do you want to provide the license key?',
37
+ options: [
38
+ { value: 'key', label: 'Paste the license key' },
39
+ { value: 'file', label: 'Read the key from a file' },
40
+ ],
41
+ initialValue: 'key',
42
+ });
43
+ if (p.isCancel(answer)) {
44
+ p.cancel('License activation cancelled.');
45
+ return {};
46
+ }
47
+ if (answer === 'key') {
48
+ const key = await p.text({
49
+ message: 'License key',
50
+ validate: (value) => String(value ?? '').trim() ? undefined : 'License key is required.',
51
+ });
52
+ if (p.isCancel(key)) {
53
+ p.cancel('License activation cancelled.');
54
+ return {};
55
+ }
56
+ return { key: String(key ?? '').trim() || undefined };
57
+ }
58
+ const keyFile = await p.text({
59
+ message: 'Path to the license key file',
60
+ validate: (value) => String(value ?? '').trim() ? undefined : 'License key file path is required.',
61
+ });
62
+ if (p.isCancel(keyFile)) {
63
+ p.cancel('License activation cancelled.');
64
+ return {};
65
+ }
66
+ return { keyFile: String(keyFile ?? '').trim() || undefined };
67
+ }
68
+ async function promptOnlineActivationInput(initial) {
69
+ let account = String(initial.account ?? '').trim();
70
+ if (!account) {
71
+ const answer = await p.text({
72
+ message: 'Service account',
73
+ validate: (value) => String(value ?? '').trim() ? undefined : 'Service account is required.',
74
+ });
75
+ if (p.isCancel(answer)) {
76
+ p.cancel('License activation cancelled.');
77
+ return;
78
+ }
79
+ account = String(answer ?? '').trim();
80
+ }
81
+ if (!account) {
82
+ p.cancel('License activation cancelled.');
83
+ return;
84
+ }
85
+ let password = String(initial.password ?? '').trim();
86
+ if (!password) {
87
+ const answer = await p.password({
88
+ message: 'Service password',
89
+ validate: (value) => String(value ?? '').trim() ? undefined : 'Service password is required.',
90
+ });
91
+ if (p.isCancel(answer)) {
92
+ p.cancel('License activation cancelled.');
93
+ return;
94
+ }
95
+ password = String(answer ?? '').trim();
96
+ }
97
+ if (!password) {
98
+ p.cancel('License activation cancelled.');
99
+ return;
100
+ }
101
+ let appName = String(initial.appName ?? '').trim();
102
+ if (!appName) {
103
+ const answer = await p.text({
104
+ message: 'Application name',
105
+ validate: (value) => String(value ?? '').trim() ? undefined : 'Application name is required.',
106
+ });
107
+ if (p.isCancel(answer)) {
108
+ p.cancel('License activation cancelled.');
109
+ return;
110
+ }
111
+ appName = String(answer ?? '').trim();
112
+ }
113
+ if (!appName) {
114
+ p.cancel('License activation cancelled.');
115
+ return;
116
+ }
117
+ const confirmedAnswer = typeof initial.confirmed === 'boolean'
118
+ ? initial.confirmed
119
+ : await p.confirm({
120
+ message: 'Confirm that the submitted license information is true and accurate?',
121
+ initialValue: false,
122
+ });
123
+ if (p.isCancel(confirmedAnswer)) {
124
+ p.cancel('License activation cancelled.');
125
+ return;
126
+ }
127
+ return {
128
+ account,
129
+ password,
130
+ appName,
131
+ confirmed: Boolean(confirmedAnswer),
132
+ serviceUrl: await resolveLicenseServiceUrl(initial.serviceUrl),
133
+ };
134
+ }
135
+ function resolveAppUrlOrThrow(runtime) {
136
+ const currentAppUrl = appUrl(runtime);
137
+ if (!currentAppUrl) {
138
+ throw new Error(`Env "${runtime.envName}" does not have an app URL or app port configured.`);
139
+ }
140
+ try {
141
+ return new URL(currentAppUrl).toString();
142
+ }
143
+ catch {
144
+ throw new Error(`Env "${runtime.envName}" has an invalid app URL: ${currentAppUrl}`);
145
+ }
146
+ }
147
+ async function requestOnlineLicenseKey(serviceUrl, account, password, payload) {
148
+ const response = await fetch(`${serviceUrl}/license-key`, {
149
+ method: 'POST',
150
+ headers: {
151
+ 'content-type': 'application/json',
152
+ },
153
+ body: JSON.stringify({
154
+ account,
155
+ password,
156
+ appUrl: payload.appUrl,
157
+ appName: payload.appName,
158
+ instanceId: payload.instanceId,
159
+ type: payload.type,
160
+ }),
161
+ });
162
+ if (!response.ok) {
163
+ throw new Error(`License service request failed with status ${response.status}.`);
164
+ }
165
+ const data = await response.json();
166
+ const key = String(data?.data?.key ?? '').trim();
167
+ if (!key) {
168
+ throw new Error('License service did not return a license key.');
169
+ }
170
+ return key;
171
+ }
172
+ export default class LicenseActivate extends Command {
173
+ static summary = 'Activate commercial licensing for the selected env';
174
+ static description = 'Activate a commercial license for the selected env. Provide an existing license key directly, or use `--online` to request and activate one from the online license service.';
175
+ static examples = [
176
+ '<%= config.bin %> <%= command.id %> --env app1 --key <licenseKey>',
177
+ '<%= config.bin %> <%= command.id %> --env app1 --key-file ./license.txt',
178
+ '<%= config.bin %> <%= command.id %> --env app1 --online',
179
+ '<%= config.bin %> <%= command.id %> --env app1 --online --account aa --password bb --desc test24 --yes',
180
+ '<%= config.bin %> <%= command.id %> --env app1 --json --key-file ./license.txt',
181
+ ];
182
+ static flags = {
183
+ env: licenseEnvFlag,
184
+ json: licenseJsonFlag,
185
+ key: Flags.string({
186
+ description: 'Existing license key to activate',
187
+ }),
188
+ 'key-file': Flags.string({
189
+ description: 'Path to a file containing the license key to activate',
190
+ }),
191
+ online: Flags.boolean({
192
+ description: 'Request a license online and activate it',
193
+ default: false,
194
+ }),
195
+ account: Flags.string({
196
+ description: 'License service account for online activation',
197
+ }),
198
+ password: Flags.string({
199
+ description: 'License service password for online activation',
200
+ }),
201
+ desc: Flags.string({
202
+ description: 'Application name for online activation',
203
+ }),
204
+ 'pkg-url': licensePkgUrlFlag,
205
+ yes: Flags.boolean({
206
+ description: 'Confirm that the submitted application information is true and accurate',
207
+ default: false,
208
+ }),
209
+ };
210
+ async run() {
211
+ const { flags } = await this.parse(LicenseActivate);
212
+ const runtime = await requireLicenseRuntime(flags.env);
213
+ let key = String(flags.key ?? '').trim();
214
+ let keyFile = String(flags['key-file'] ?? '').trim();
215
+ let online = Boolean(flags.online);
216
+ if (!key && !keyFile && !online) {
217
+ if (!isInteractiveTerminal()) {
218
+ this.error('Provide --key, --key-file, or --online to continue.');
219
+ }
220
+ const mode = await promptActivationMode();
221
+ if (mode === 'cancel') {
222
+ this.log('Cancelled license activation.');
223
+ return;
224
+ }
225
+ if (mode === 'online') {
226
+ online = true;
227
+ }
228
+ else {
229
+ const prompted = await promptLicenseKeyInput();
230
+ key = String(prompted.key ?? '').trim();
231
+ keyFile = String(prompted.keyFile ?? '').trim();
232
+ if (!key && !keyFile) {
233
+ this.error('License key input was empty.');
234
+ }
235
+ }
236
+ }
237
+ if ((key || keyFile) && online) {
238
+ this.error('Use either an existing key (--key / --key-file) or --online, not both.');
239
+ }
240
+ if (online) {
241
+ const resolvedServiceUrl = await resolveLicenseServiceUrl(flags['pkg-url']);
242
+ const initialOnline = {
243
+ account: resolveOnlineInputValue(flags.account),
244
+ password: resolveOnlineInputValue(flags.password),
245
+ appName: resolveOnlineInputValue(flags.desc),
246
+ confirmed: flags.yes ? true : undefined,
247
+ serviceUrl: resolvedServiceUrl,
248
+ };
249
+ let onlineInput = initialOnline;
250
+ if (!onlineInput.account
251
+ || !onlineInput.password
252
+ || !onlineInput.appName
253
+ || !onlineInput.confirmed) {
254
+ if (!isInteractiveTerminal()) {
255
+ this.error('Online activation requires --account, --password, --desc, and --yes when not using a TTY.');
256
+ }
257
+ const prompted = await promptOnlineActivationInput(initialOnline);
258
+ if (!prompted) {
259
+ this.log('Cancelled license activation.');
260
+ return;
261
+ }
262
+ onlineInput = prompted;
263
+ }
264
+ if (!onlineInput.confirmed) {
265
+ this.error('Online activation requires confirmation that the submitted application information is true and accurate.');
266
+ }
267
+ const instanceId = await ensureInstanceId(runtime);
268
+ const resolvedAppUrl = resolveAppUrlOrThrow(runtime);
269
+ const resolvedKey = await requestOnlineLicenseKey(onlineInput.serviceUrl, onlineInput.account, onlineInput.password, {
270
+ appUrl: resolvedAppUrl,
271
+ appName: onlineInput.appName,
272
+ instanceId,
273
+ type: 'internal',
274
+ });
275
+ const validation = await validateLicenseKey(runtime, resolvedKey);
276
+ const ok = !validation.keyStatus
277
+ && validation.envMatch
278
+ && validation.domainMatch
279
+ && validation.licenseStatus === 'active';
280
+ const licenseKeyPath = ok ? await saveLicenseKey(runtime, resolvedKey) : resolveLicenseKeyFile(runtime);
281
+ const payload = {
282
+ ok,
283
+ env: runtime.envName,
284
+ kind: runtime.kind,
285
+ instanceId,
286
+ mode: 'online',
287
+ serviceUrl: onlineInput.serviceUrl,
288
+ appUrl: resolvedAppUrl,
289
+ appName: onlineInput.appName,
290
+ key: redactLicenseKey(resolvedKey),
291
+ licenseKeyPath,
292
+ validation: sanitizeLicenseOutput(validation),
293
+ };
294
+ if (flags.json) {
295
+ this.log(JSON.stringify(payload, null, 2));
296
+ if (!ok) {
297
+ this.exit(1);
298
+ }
299
+ return;
300
+ }
301
+ if (!ok) {
302
+ const reason = validation.keyStatus
303
+ ? `license key is ${validation.keyStatus}`
304
+ : !validation.envMatch
305
+ ? 'license key does not match the current instance environment'
306
+ : !validation.domainMatch
307
+ ? 'license key does not match the current app domain'
308
+ : validation.licenseStatus !== 'active'
309
+ ? `license status is ${validation.licenseStatus}`
310
+ : 'license validation failed';
311
+ this.error(`Failed to activate the online license for env "${runtime.envName}": ${reason}.`);
312
+ }
313
+ this.log(`Activated the online license for env "${runtime.envName}".`);
314
+ this.log(`Saved license key at ${licenseKeyPath}`);
315
+ return;
316
+ }
317
+ const resolvedKey = key || String(await readFile(keyFile, 'utf8')).trim();
318
+ const validation = await validateLicenseKey(runtime, resolvedKey);
319
+ const ok = !validation.keyStatus
320
+ && validation.envMatch
321
+ && validation.domainMatch
322
+ && validation.licenseStatus === 'active';
323
+ const licenseKeyPath = ok ? await saveLicenseKey(runtime, resolvedKey) : resolveLicenseKeyFile(runtime);
324
+ const payload = {
325
+ ok,
326
+ env: runtime.envName,
327
+ kind: runtime.kind,
328
+ instanceId: await ensureInstanceId(runtime),
329
+ mode: 'key',
330
+ key: redactLicenseKey(resolvedKey),
331
+ keyFile: keyFile || undefined,
332
+ licenseKeyPath,
333
+ validation: sanitizeLicenseOutput(validation),
334
+ };
335
+ if (flags.json) {
336
+ this.log(JSON.stringify(payload, null, 2));
337
+ if (!ok) {
338
+ this.exit(1);
339
+ }
340
+ return;
341
+ }
342
+ if (!ok) {
343
+ const reason = validation.keyStatus
344
+ ? `license key is ${validation.keyStatus}`
345
+ : !validation.envMatch
346
+ ? 'license key does not match the current instance environment'
347
+ : !validation.domainMatch
348
+ ? 'license key does not match the current app domain'
349
+ : validation.licenseStatus !== 'active'
350
+ ? `license status is ${validation.licenseStatus}`
351
+ : 'license validation failed';
352
+ this.error(`Failed to activate the license for env "${runtime.envName}": ${reason}.`);
353
+ }
354
+ this.log(`Activated the license for env "${runtime.envName}".`);
355
+ this.log(`Saved license key at ${licenseKeyPath}`);
356
+ }
357
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import { Command, Flags } from '@oclif/core';
10
+ import { getEnvAsync } from '@nocobase/license-kit';
11
+ import { validateTcpPort } from "../../lib/prompt-validators.js";
12
+ import { licenseJsonFlag, withLicenseEnvVars } from './shared.js';
13
+ function trimValue(value) {
14
+ const text = String(value ?? '').trim();
15
+ return text || undefined;
16
+ }
17
+ function formatMissingFieldsMessage(missing) {
18
+ return [
19
+ 'Missing database settings for license environment inspection.',
20
+ `Required: ${missing.join(', ')}.`,
21
+ 'Pass all required `--db-*` flags explicitly.',
22
+ ].join('\n');
23
+ }
24
+ export default class LicenseEnv extends Command {
25
+ static hidden = true;
26
+ static flags = {
27
+ 'db-dialect': Flags.string({
28
+ description: 'Database dialect: postgres, kingbase, mysql, or mariadb.',
29
+ options: ['postgres', 'kingbase', 'mysql', 'mariadb'],
30
+ }),
31
+ 'db-host': Flags.string({
32
+ description: 'Database host name or IP address.',
33
+ }),
34
+ 'db-port': Flags.string({
35
+ description: 'Database TCP port.',
36
+ }),
37
+ 'db-database': Flags.string({
38
+ description: 'Database name.',
39
+ }),
40
+ 'db-user': Flags.string({
41
+ description: 'Database username.',
42
+ }),
43
+ 'db-password': Flags.string({
44
+ description: 'Database password.',
45
+ }),
46
+ json: licenseJsonFlag,
47
+ };
48
+ async run() {
49
+ const { flags } = await this.parse(LicenseEnv);
50
+ const envVars = {
51
+ DB_DIALECT: trimValue(flags['db-dialect']),
52
+ DB_HOST: trimValue(flags['db-host']),
53
+ DB_PORT: trimValue(flags['db-port']),
54
+ DB_DATABASE: trimValue(flags['db-database']),
55
+ DB_USER: trimValue(flags['db-user']),
56
+ DB_PASSWORD: flags['db-password'] !== undefined ? String(flags['db-password']) : undefined,
57
+ };
58
+ const missing = [];
59
+ if (!envVars.DB_DIALECT) {
60
+ missing.push('--db-dialect');
61
+ }
62
+ if (!envVars.DB_HOST) {
63
+ missing.push('--db-host');
64
+ }
65
+ if (!envVars.DB_PORT) {
66
+ missing.push('--db-port');
67
+ }
68
+ if (!envVars.DB_DATABASE) {
69
+ missing.push('--db-database');
70
+ }
71
+ if (!envVars.DB_USER) {
72
+ missing.push('--db-user');
73
+ }
74
+ if (!envVars.DB_PASSWORD) {
75
+ missing.push('--db-password');
76
+ }
77
+ if (missing.length > 0) {
78
+ this.error(formatMissingFieldsMessage(missing));
79
+ }
80
+ const portError = validateTcpPort(envVars.DB_PORT);
81
+ if (portError) {
82
+ this.error(portError);
83
+ }
84
+ const env = await withLicenseEnvVars(envVars, async () => await getEnvAsync());
85
+ if (flags.json) {
86
+ this.log(JSON.stringify({
87
+ ok: true,
88
+ env,
89
+ }, null, 2));
90
+ return;
91
+ }
92
+ this.log(JSON.stringify(env, null, 2));
93
+ }
94
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import { Command, Flags } from '@oclif/core';
10
+ import { validateTcpPort } from "../../lib/prompt-validators.js";
11
+ import { generateValidatedInstanceIdFromEnvVars, licenseJsonFlag } from './shared.js';
12
+ function trimValue(value) {
13
+ const text = String(value ?? '').trim();
14
+ return text || undefined;
15
+ }
16
+ function formatMissingFieldsMessage(missing) {
17
+ return [
18
+ 'Missing database settings for instance ID generation.',
19
+ `Required: ${missing.join(', ')}.`,
20
+ 'Pass all required `--db-*` flags explicitly.',
21
+ ].join('\n');
22
+ }
23
+ export default class LicenseGenerateId extends Command {
24
+ static summary = 'Generate a commercial license instance ID from explicit database settings';
25
+ static description = 'Generate the commercial licensing instance ID from explicit `--db-*` flags. This command only prints the generated ID and does not save it.';
26
+ static examples = [
27
+ '<%= config.bin %> <%= command.id %> --db-dialect postgres --db-host 127.0.0.1 --db-port 5432 --db-database nocobase --db-user nocobase --db-password secret',
28
+ '<%= config.bin %> <%= command.id %> --db-dialect postgres --db-host 127.0.0.1 --db-port 5432 --db-database nocobase --db-user nocobase --db-password secret --json',
29
+ ];
30
+ static flags = {
31
+ 'db-dialect': Flags.string({
32
+ description: 'Database dialect: postgres, kingbase, mysql, or mariadb.',
33
+ options: ['postgres', 'kingbase', 'mysql', 'mariadb'],
34
+ required: false,
35
+ }),
36
+ 'db-host': Flags.string({
37
+ description: 'Database host name or IP address.',
38
+ }),
39
+ 'db-port': Flags.string({
40
+ description: 'Database TCP port.',
41
+ }),
42
+ 'db-database': Flags.string({
43
+ description: 'Database name.',
44
+ }),
45
+ 'db-user': Flags.string({
46
+ description: 'Database username.',
47
+ }),
48
+ 'db-password': Flags.string({
49
+ description: 'Database password.',
50
+ }),
51
+ json: licenseJsonFlag,
52
+ };
53
+ async run() {
54
+ const { flags } = await this.parse(LicenseGenerateId);
55
+ const dbConfig = {
56
+ dbDialect: trimValue(flags['db-dialect']),
57
+ dbHost: trimValue(flags['db-host']),
58
+ dbPort: trimValue(flags['db-port']),
59
+ dbDatabase: trimValue(flags['db-database']),
60
+ dbUser: trimValue(flags['db-user']),
61
+ dbPassword: flags['db-password'] !== undefined ? String(flags['db-password']) : undefined,
62
+ };
63
+ const missing = [];
64
+ if (!dbConfig.dbDialect) {
65
+ missing.push('--db-dialect');
66
+ }
67
+ if (!dbConfig.dbHost) {
68
+ missing.push('--db-host');
69
+ }
70
+ if (!dbConfig.dbPort) {
71
+ missing.push('--db-port');
72
+ }
73
+ if (!dbConfig.dbDatabase) {
74
+ missing.push('--db-database');
75
+ }
76
+ if (!dbConfig.dbUser) {
77
+ missing.push('--db-user');
78
+ }
79
+ if (!dbConfig.dbPassword) {
80
+ missing.push('--db-password');
81
+ }
82
+ if (missing.length > 0) {
83
+ this.error(formatMissingFieldsMessage(missing));
84
+ }
85
+ const portError = validateTcpPort(dbConfig.dbPort);
86
+ if (portError) {
87
+ this.error(portError);
88
+ }
89
+ const envVars = {
90
+ DB_DIALECT: dbConfig.dbDialect,
91
+ DB_HOST: dbConfig.dbHost,
92
+ DB_PORT: dbConfig.dbPort,
93
+ DB_DATABASE: dbConfig.dbDatabase,
94
+ DB_USER: dbConfig.dbUser,
95
+ DB_PASSWORD: dbConfig.dbPassword,
96
+ };
97
+ const instanceId = await generateValidatedInstanceIdFromEnvVars(envVars);
98
+ if (flags.json) {
99
+ this.log(JSON.stringify({
100
+ ok: true,
101
+ instanceId,
102
+ }, null, 2));
103
+ return;
104
+ }
105
+ this.log(instanceId);
106
+ }
107
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import { Command, Flags } from '@oclif/core';
10
+ import { generateAndSaveInstanceId, licenseEnvFlag, licenseJsonFlag, readSavedInstanceId, requireLicenseRuntime, resolveInstanceIdFile, } from './shared.js';
11
+ export default class LicenseId extends Command {
12
+ static summary = 'Show the instance ID for the selected env';
13
+ static description = 'Show the commercial licensing instance ID for the selected env, generating and saving it if needed.';
14
+ static examples = [
15
+ '<%= config.bin %> <%= command.id %>',
16
+ '<%= config.bin %> <%= command.id %> --env app1',
17
+ '<%= config.bin %> <%= command.id %> --env app1 --force',
18
+ '<%= config.bin %> <%= command.id %> --env app1 --json',
19
+ ];
20
+ static flags = {
21
+ env: licenseEnvFlag,
22
+ json: licenseJsonFlag,
23
+ force: Flags.boolean({
24
+ description: 'Force regenerate the instance ID even if one is already saved',
25
+ default: false,
26
+ }),
27
+ };
28
+ async run() {
29
+ const { flags } = await this.parse(LicenseId);
30
+ const runtime = await requireLicenseRuntime(flags.env);
31
+ const savedBefore = await readSavedInstanceId(runtime);
32
+ const shouldGenerate = Boolean(flags.force) || !savedBefore;
33
+ const instanceId = shouldGenerate
34
+ ? await generateAndSaveInstanceId(runtime)
35
+ : savedBefore;
36
+ const filePath = resolveInstanceIdFile(runtime);
37
+ const generated = shouldGenerate;
38
+ if (flags.json) {
39
+ this.log(JSON.stringify({
40
+ ok: true,
41
+ env: runtime.envName,
42
+ kind: runtime.kind,
43
+ instanceId,
44
+ filePath,
45
+ generated,
46
+ }, null, 2));
47
+ return;
48
+ }
49
+ this.log(`Instance ID for env "${runtime.envName}": ${instanceId}`);
50
+ this.log(`${generated ? 'Saved' : 'Loaded'} instance ID at ${filePath}`);
51
+ }
52
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import { Command, loadHelpClass } from '@oclif/core';
10
+ export default class License extends Command {
11
+ static summary = 'Manage NocoBase commercial licensing';
12
+ async run() {
13
+ await this.parse(License);
14
+ const Help = await loadHelpClass(this.config);
15
+ await new Help(this.config, this.config.pjson.oclif.helpOptions ?? this.config.pjson.helpOptions).showHelp([
16
+ this.id ?? 'license',
17
+ ...this.argv,
18
+ ]);
19
+ }
20
+ }