@nocobase/cli 2.1.0-alpha.26 → 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 (43) hide show
  1. package/README.md +24 -0
  2. package/README.zh-CN.md +4 -0
  3. package/dist/commands/app/down.js +2 -3
  4. package/dist/commands/app/upgrade.js +112 -128
  5. package/dist/commands/config/delete.js +30 -0
  6. package/dist/commands/config/get.js +29 -0
  7. package/dist/commands/config/index.js +20 -0
  8. package/dist/commands/config/list.js +29 -0
  9. package/dist/commands/config/set.js +35 -0
  10. package/dist/commands/db/check.js +230 -0
  11. package/dist/commands/db/shared.js +1 -1
  12. package/dist/commands/env/shared.js +1 -1
  13. package/dist/commands/init.js +0 -1
  14. package/dist/commands/install.js +87 -35
  15. package/dist/commands/license/activate.js +357 -0
  16. package/dist/commands/license/env.js +94 -0
  17. package/dist/commands/license/generate-id.js +107 -0
  18. package/dist/commands/license/id.js +52 -0
  19. package/dist/commands/license/index.js +20 -0
  20. package/dist/commands/license/plugins/clean.js +98 -0
  21. package/dist/commands/license/plugins/index.js +20 -0
  22. package/dist/commands/license/plugins/list.js +50 -0
  23. package/dist/commands/license/plugins/shared.js +325 -0
  24. package/dist/commands/license/plugins/sync.js +267 -0
  25. package/dist/commands/license/shared.js +411 -0
  26. package/dist/commands/license/status.js +50 -0
  27. package/dist/lib/api-client.js +74 -3
  28. package/dist/lib/app-runtime.js +26 -10
  29. package/dist/lib/auth-store.js +29 -66
  30. package/dist/lib/build-config.js +8 -0
  31. package/dist/lib/cli-config.js +176 -0
  32. package/dist/lib/cli-home.js +6 -21
  33. package/dist/lib/db-connection-check.js +178 -0
  34. package/dist/lib/generated-command.js +23 -3
  35. package/dist/lib/plugin-storage.js +127 -0
  36. package/dist/lib/prompt-validators.js +4 -4
  37. package/dist/lib/runtime-generator.js +89 -10
  38. package/dist/lib/self-manager.js +57 -2
  39. package/dist/lib/startup-update.js +85 -7
  40. package/dist/locale/en-US.json +16 -13
  41. package/dist/locale/zh-CN.json +16 -13
  42. package/nocobase-ctl.config.json +82 -0
  43. package/package.json +16 -4
@@ -0,0 +1,50 @@
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 { ensureInstanceId, licenseEnvFlag, licenseJsonFlag, requireLicenseRuntime } from './shared.js';
11
+ export default class LicenseStatus extends Command {
12
+ static summary = 'Show commercial license status for the selected env';
13
+ static description = 'Inspect the selected env and show the current commercial licensing status. Use `--doctor` for extra diagnostic checks once the license backend wiring is implemented.';
14
+ static examples = [
15
+ '<%= config.bin %> <%= command.id %>',
16
+ '<%= config.bin %> <%= command.id %> --env app1',
17
+ '<%= config.bin %> <%= command.id %> --env app1 --doctor',
18
+ '<%= config.bin %> <%= command.id %> --env app1 --json',
19
+ ];
20
+ static flags = {
21
+ env: licenseEnvFlag,
22
+ json: licenseJsonFlag,
23
+ doctor: Flags.boolean({
24
+ description: 'Run extra diagnostic checks and suggestions',
25
+ default: false,
26
+ }),
27
+ };
28
+ async run() {
29
+ const { flags } = await this.parse(LicenseStatus);
30
+ const runtime = await requireLicenseRuntime(flags.env);
31
+ const payload = {
32
+ ok: true,
33
+ env: runtime.envName,
34
+ kind: runtime.kind,
35
+ instanceId: await ensureInstanceId(runtime),
36
+ licensed: false,
37
+ doctor: Boolean(flags.doctor),
38
+ implemented: false,
39
+ message: 'Commercial license status is not implemented yet in the new CLI.',
40
+ };
41
+ if (flags.json) {
42
+ this.log(JSON.stringify(payload, null, 2));
43
+ return;
44
+ }
45
+ this.log(`License status for env "${runtime.envName}": not implemented yet`);
46
+ if (flags.doctor) {
47
+ this.log('Diagnostic checks for commercial licensing are not implemented yet in the new CLI.');
48
+ }
49
+ }
50
+ }
@@ -6,7 +6,19 @@
6
6
  * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
+ /**
10
+ * This file is part of the NocoBase (R) project.
11
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
12
+ * Authors: NocoBase Team.
13
+ *
14
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
15
+ * For more information, please refer to: https://www.nocobase.com/agreement.
16
+ */
17
+ import { createWriteStream } from 'node:fs';
9
18
  import { promises as fs } from 'node:fs';
19
+ import { basename, dirname } from 'node:path';
20
+ import { Readable } from 'node:stream';
21
+ import { pipeline } from 'node:stream/promises';
10
22
  import { resolveServerRequestTarget } from './env-auth.js';
11
23
  import { fetchWithPreservedAuthRedirect } from './http-request.js';
12
24
  const CLI_REQUEST_SOURCE_HEADER = 'x-request-source';
@@ -43,6 +55,20 @@ async function parseResponse(response) {
43
55
  data,
44
56
  };
45
57
  }
58
+ async function parseBinaryResponse(response, outputPath) {
59
+ if (response.ok && response.body) {
60
+ await fs.mkdir(dirname(outputPath), { recursive: true }).catch(() => undefined);
61
+ await pipeline(Readable.fromWeb(response.body), createWriteStream(outputPath));
62
+ return {
63
+ ok: response.ok,
64
+ status: response.status,
65
+ data: {
66
+ output: outputPath,
67
+ },
68
+ };
69
+ }
70
+ return parseResponse(response);
71
+ }
46
72
  function parseScalarValue(value, type) {
47
73
  if (value === undefined) {
48
74
  return undefined;
@@ -105,6 +131,9 @@ function parseBodyFieldValue(rawValue, parameter) {
105
131
  return parseScalarValue(rawValue, parameter.type);
106
132
  }
107
133
  export async function parseBody(flags, operation) {
134
+ if (operation.requestContentType === 'multipart/form-data') {
135
+ return undefined;
136
+ }
108
137
  const inlineBody = flags.body;
109
138
  const bodyFile = flags['body-file'];
110
139
  const bodyParameters = operation.parameters.filter((parameter) => parameter.in === 'body');
@@ -143,6 +172,39 @@ export async function parseBody(flags, operation) {
143
172
  }
144
173
  return undefined;
145
174
  }
175
+ async function createMultipartBody(flags, operation) {
176
+ const bodyParameters = operation.parameters.filter((parameter) => parameter.in === 'body');
177
+ const formData = new FormData();
178
+ let hasValues = false;
179
+ for (const parameter of bodyParameters) {
180
+ const rawValue = flags[parameter.flagName];
181
+ const hasValue = hasParameterValue(flags, parameter);
182
+ if (parameter.required && !hasValue) {
183
+ throw new Error(`Missing required body field --${parameter.flagName}`);
184
+ }
185
+ if (!hasValue) {
186
+ continue;
187
+ }
188
+ if (parameter.isFile) {
189
+ const filePath = String(rawValue);
190
+ const content = await fs.readFile(filePath);
191
+ const arrayBuffer = content.buffer.slice(content.byteOffset, content.byteOffset + content.byteLength);
192
+ formData.append(parameter.name, new Blob([arrayBuffer]), basename(filePath));
193
+ hasValues = true;
194
+ continue;
195
+ }
196
+ const value = parseBodyFieldValue(rawValue, parameter);
197
+ if (value === undefined) {
198
+ continue;
199
+ }
200
+ formData.append(parameter.name, typeof value === 'object' ? JSON.stringify(value) : String(value));
201
+ hasValues = true;
202
+ }
203
+ if (!hasValues && operation.bodyRequired) {
204
+ throw new Error('Missing multipart request body.');
205
+ }
206
+ return hasValues ? formData : undefined;
207
+ }
146
208
  export async function executeApiRequest(options) {
147
209
  const { baseUrl, token } = await resolveServerRequestTarget(options);
148
210
  const headers = new Headers();
@@ -190,8 +252,10 @@ export async function executeApiRequest(options) {
190
252
  continue;
191
253
  }
192
254
  }
193
- const body = await parseBody(options.flags, options.operation);
194
- if (body !== undefined) {
255
+ const body = options.operation.requestContentType === 'multipart/form-data'
256
+ ? await createMultipartBody(options.flags, options.operation)
257
+ : await parseBody(options.flags, options.operation);
258
+ if (body !== undefined && options.operation.requestContentType !== 'multipart/form-data') {
195
259
  headers.set('content-type', 'application/json');
196
260
  }
197
261
  const url = new URL(`${normalizeBaseUrl(baseUrl)}${requestPath}`);
@@ -199,8 +263,15 @@ export async function executeApiRequest(options) {
199
263
  const response = await fetchWithPreservedAuthRedirect(url.toString(), {
200
264
  method: options.operation.method.toUpperCase(),
201
265
  headers,
202
- body: body === undefined ? undefined : JSON.stringify(body),
266
+ body: body === undefined ? undefined : body instanceof FormData ? body : JSON.stringify(body),
203
267
  });
268
+ if (options.operation.responseType === 'binary') {
269
+ const outputPath = options.flags.output;
270
+ if (!outputPath) {
271
+ throw new Error('Missing required output path --output');
272
+ }
273
+ return parseBinaryResponse(response, outputPath);
274
+ }
204
275
  return parseResponse(response);
205
276
  }
206
277
  export async function executeRawApiRequest(options) {
@@ -9,6 +9,7 @@
9
9
  import path from 'node:path';
10
10
  import { resolveEnvKind } from './auth-store.js';
11
11
  import { getEnv, loadAuthConfig } from './auth-store.js';
12
+ import { DEFAULT_DOCKER_CONTAINER_PREFIX, DEFAULT_DOCKER_NETWORK, getEffectiveCliConfigValue, } from './cli-config.js';
12
13
  import { commandOutput, commandSucceeds, run, runNocoBaseCommand } from './run-npm.js';
13
14
  const DOCKER_APP_WORKDIR = '/app/nocobase';
14
15
  function sanitizeDockerResourceName(value) {
@@ -23,14 +24,24 @@ function sanitizeDockerResourceName(value) {
23
24
  export function defaultWorkspaceName(cwd = process.cwd()) {
24
25
  return sanitizeDockerResourceName(`nb-${path.basename(cwd)}`);
25
26
  }
26
- export function buildDockerAppContainerName(envName, workspaceName) {
27
- const workspace = workspaceName?.trim() || defaultWorkspaceName();
28
- return sanitizeDockerResourceName(`${workspace}-${envName}-app`);
27
+ export function defaultDockerContainerPrefix(cwd = process.cwd()) {
28
+ const configured = String(DEFAULT_DOCKER_CONTAINER_PREFIX ?? '').trim();
29
+ if (configured) {
30
+ return sanitizeDockerResourceName(configured);
31
+ }
32
+ return defaultWorkspaceName(cwd);
33
+ }
34
+ export function defaultDockerNetworkName() {
35
+ return sanitizeDockerResourceName(DEFAULT_DOCKER_NETWORK);
36
+ }
37
+ export function buildDockerAppContainerName(envName, containerPrefix) {
38
+ const prefix = containerPrefix?.trim() || defaultDockerContainerPrefix();
39
+ return sanitizeDockerResourceName(`${prefix}-${envName}-app`);
29
40
  }
30
- export function buildDockerDbContainerName(envName, dbDialect, workspaceName) {
31
- const workspace = workspaceName?.trim() || defaultWorkspaceName();
41
+ export function buildDockerDbContainerName(envName, dbDialect, containerPrefix) {
42
+ const prefix = containerPrefix?.trim() || defaultDockerContainerPrefix();
32
43
  const dialect = dbDialect.trim() || 'postgres';
33
- return sanitizeDockerResourceName(`${workspace}-${envName}-${dialect}`);
44
+ return sanitizeDockerResourceName(`${prefix}-${envName}-${dialect}`);
34
45
  }
35
46
  function normalizeEnvSource(env) {
36
47
  const source = String(env.config.source ?? '').trim();
@@ -51,7 +62,8 @@ export async function resolveManagedAppRuntime(envName) {
51
62
  }
52
63
  const resolvedName = env.name || envName?.trim() || config.currentEnv || 'default';
53
64
  const source = normalizeEnvSource(env);
54
- const workspaceName = config.name?.trim() || defaultWorkspaceName();
65
+ const dockerNetworkName = sanitizeDockerResourceName(getEffectiveCliConfigValue(config, 'docker.network') || defaultDockerNetworkName());
66
+ const dockerContainerPrefix = sanitizeDockerResourceName(getEffectiveCliConfigValue(config, 'docker.container-prefix') || defaultDockerContainerPrefix());
55
67
  const kind = env.kind ?? resolveEnvKind(env.config);
56
68
  if (kind === 'docker') {
57
69
  return {
@@ -59,8 +71,10 @@ export async function resolveManagedAppRuntime(envName) {
59
71
  env,
60
72
  envName: resolvedName,
61
73
  source: 'docker',
62
- workspaceName,
63
- containerName: buildDockerAppContainerName(resolvedName, workspaceName),
74
+ dockerNetworkName,
75
+ dockerContainerPrefix,
76
+ workspaceName: dockerNetworkName,
77
+ containerName: buildDockerAppContainerName(resolvedName, dockerContainerPrefix),
64
78
  };
65
79
  }
66
80
  if (kind === 'local') {
@@ -70,7 +84,9 @@ export async function resolveManagedAppRuntime(envName) {
70
84
  envName: resolvedName,
71
85
  source: source === 'git' ? 'git' : source === 'npm' ? 'npm' : 'local',
72
86
  projectRoot: env.appRootPath,
73
- workspaceName,
87
+ dockerNetworkName,
88
+ dockerContainerPrefix,
89
+ workspaceName: dockerNetworkName,
74
90
  };
75
91
  }
76
92
  if (kind === 'ssh') {
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { promises as fs } from 'node:fs';
10
10
  import path from 'node:path';
11
- import { NB_CLI_ROOT_ENV, resolveCliHomeDir, resolveConfiguredEnvPath, resolveDefaultConfigScope, resolveEnvRelativePath, } from './cli-home.js';
11
+ import { resolveCliHomeDir, resolveConfiguredEnvPath, resolveEnvRelativePath, } from './cli-home.js';
12
12
  function normalizeStoredEnvKind(value) {
13
13
  const kind = String(value ?? '').trim();
14
14
  if (kind === 'remote') {
@@ -68,8 +68,22 @@ function normalizeEnvConfigEntry(entry) {
68
68
  };
69
69
  }
70
70
  function normalizeAuthConfig(config) {
71
+ const settings = config.settings ?? {};
71
72
  return {
72
73
  name: config.name || config.dockerResourcePrefix,
74
+ settings: {
75
+ ...(settings.license?.pkgUrl ? { license: { pkgUrl: normalizeOptionalString(settings.license.pkgUrl) } } : {}),
76
+ ...(settings.docker?.network || settings.docker?.containerPrefix
77
+ ? {
78
+ docker: {
79
+ ...(settings.docker?.network ? { network: normalizeOptionalString(settings.docker.network) } : {}),
80
+ ...(settings.docker?.containerPrefix
81
+ ? { containerPrefix: normalizeOptionalString(settings.docker.containerPrefix) }
82
+ : {}),
83
+ },
84
+ }
85
+ : {}),
86
+ },
73
87
  currentEnv: config.currentEnv || 'default',
74
88
  envs: Object.fromEntries(Object.entries(config.envs || {}).map(([envName, entry]) => [envName, normalizeEnvConfigEntry(entry) ?? {}])),
75
89
  };
@@ -83,48 +97,21 @@ function createDefaultConfig() {
83
97
  envs: {},
84
98
  };
85
99
  }
86
- function hasConfiguredEnvs(config) {
87
- return Object.keys(config.envs).length > 0;
88
- }
89
- function shouldFallbackToLegacyProjectScope(options = {}) {
90
- const requestedScope = options.scope ?? resolveDefaultConfigScope();
91
- if (requestedScope !== 'global') {
92
- return false;
93
- }
94
- return !process.env[NB_CLI_ROOT_ENV];
95
- }
96
- async function loadExactAuthConfig(options = {}) {
100
+ async function readStoredAuthConfig(filePath) {
97
101
  try {
98
- const content = await fs.readFile(getConfigFile(options), 'utf8');
102
+ const content = await fs.readFile(filePath, 'utf8');
99
103
  const parsed = JSON.parse(content);
100
104
  return normalizeAuthConfig(parsed);
101
105
  }
102
106
  catch (_error) {
103
- return createDefaultConfig();
107
+ return undefined;
104
108
  }
105
109
  }
106
- async function resolveEnvStorageScope(envName, options = {}) {
107
- const requestedScope = options.scope ?? resolveDefaultConfigScope();
108
- if (requestedScope !== 'global') {
109
- return { ...options, scope: requestedScope };
110
- }
111
- const globalConfig = await loadExactAuthConfig({ scope: 'global' });
112
- if (globalConfig.envs[envName]) {
113
- return { ...options, scope: 'global' };
114
- }
115
- const projectConfig = await loadExactAuthConfig({ scope: 'project' });
116
- if (projectConfig.envs[envName]) {
117
- return { ...options, scope: 'project' };
118
- }
119
- return { ...options, scope: 'global' };
110
+ export async function loadExactAuthConfig(options = {}) {
111
+ return (await readStoredAuthConfig(getConfigFile(options))) ?? createDefaultConfig();
120
112
  }
121
113
  export async function loadAuthConfig(options = {}) {
122
- const config = await loadExactAuthConfig(options);
123
- if (!shouldFallbackToLegacyProjectScope(options) || hasConfiguredEnvs(config)) {
124
- return config;
125
- }
126
- const legacyProjectConfig = await loadExactAuthConfig({ scope: 'project' });
127
- return hasConfiguredEnvs(legacyProjectConfig) ? legacyProjectConfig : config;
114
+ return await loadExactAuthConfig(options);
128
115
  }
129
116
  export async function saveAuthConfig(config, options = {}) {
130
117
  const filePath = getConfigFile(options);
@@ -143,24 +130,12 @@ export async function getCurrentEnvName(options = {}) {
143
130
  return config.currentEnv || 'default';
144
131
  }
145
132
  export async function setCurrentEnv(envName, options = {}) {
146
- const writeOptions = await resolveEnvStorageScope(envName, options);
147
- const config = await loadExactAuthConfig(writeOptions);
133
+ const config = await loadExactAuthConfig(options);
148
134
  if (!config.envs[envName]) {
149
135
  throw new Error(`Env "${envName}" is not configured`);
150
136
  }
151
137
  config.currentEnv = envName;
152
- await saveAuthConfig(config, writeOptions);
153
- }
154
- export async function ensureWorkspaceName(defaultName, options = {}) {
155
- const config = await loadExactAuthConfig(options);
156
- const existing = config.name?.trim();
157
- if (existing) {
158
- return existing;
159
- }
160
- const next = defaultName.trim();
161
- config.name = next;
162
138
  await saveAuthConfig(config, options);
163
- return next;
164
139
  }
165
140
  export class Env {
166
141
  config;
@@ -234,16 +209,7 @@ export async function getEnv(envName, options = {}) {
234
209
  const resolved = envName?.trim() || config.currentEnv || 'default';
235
210
  const envConfig = config.envs[resolved];
236
211
  if (!envConfig) {
237
- if (!shouldFallbackToLegacyProjectScope(loadOptions)) {
238
- return undefined;
239
- }
240
- const legacyProjectConfig = await loadExactAuthConfig({ scope: 'project' });
241
- const legacyResolved = envName?.trim() || legacyProjectConfig.currentEnv || 'default';
242
- const legacyEnvConfig = legacyProjectConfig.envs[legacyResolved];
243
- if (!legacyEnvConfig) {
244
- return undefined;
245
- }
246
- return new Env({ ...(normalizeEnvConfigEntry(legacyEnvConfig) ?? {}), name: legacyResolved });
212
+ return undefined;
247
213
  }
248
214
  return new Env({ ...(normalizeEnvConfigEntry(envConfig) ?? {}), name: resolved });
249
215
  }
@@ -269,12 +235,11 @@ function areAuthConfigsEquivalent(left, right) {
269
235
  return false;
270
236
  }
271
237
  async function writeEnv(envName, updater, options = {}) {
272
- const writeOptions = await resolveEnvStorageScope(envName, options);
273
- const config = await loadExactAuthConfig(writeOptions);
238
+ const config = await loadExactAuthConfig(options);
274
239
  const previous = config.envs[envName];
275
240
  config.envs[envName] = updater(previous);
276
241
  config.currentEnv = envName;
277
- await saveAuthConfig(config, writeOptions);
242
+ await saveAuthConfig(config, options);
278
243
  }
279
244
  export async function upsertEnv(envName, config, options = {}) {
280
245
  await writeEnv(envName, (previous) => {
@@ -330,19 +295,17 @@ export async function setEnvOauthSession(envName, auth, options = {}) {
330
295
  }), options);
331
296
  }
332
297
  export async function setEnvRuntime(envName, runtime, options = {}) {
333
- const writeOptions = await resolveEnvStorageScope(envName, options);
334
- const config = await loadExactAuthConfig(writeOptions);
298
+ const config = await loadExactAuthConfig(options);
335
299
  const current = config.envs[envName] ?? {};
336
300
  config.envs[envName] = {
337
301
  ...current,
338
302
  runtime,
339
303
  };
340
304
  config.currentEnv = envName;
341
- await saveAuthConfig(config, writeOptions);
305
+ await saveAuthConfig(config, options);
342
306
  }
343
307
  export async function removeEnv(envName, options = {}) {
344
- const writeOptions = await resolveEnvStorageScope(envName, options);
345
- const config = await loadExactAuthConfig(writeOptions);
308
+ const config = await loadExactAuthConfig(options);
346
309
  if (!config.envs[envName]) {
347
310
  throw new Error(`Env "${envName}" is not configured`);
348
311
  }
@@ -351,7 +314,7 @@ export async function removeEnv(envName, options = {}) {
351
314
  const nextEnv = Object.keys(config.envs).sort()[0];
352
315
  config.currentEnv = nextEnv ?? 'default';
353
316
  }
354
- await saveAuthConfig(config, writeOptions);
317
+ await saveAuthConfig(config, options);
355
318
  return {
356
319
  removed: envName,
357
320
  currentEnv: config.currentEnv || 'default',
@@ -1,3 +1,11 @@
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
+ */
1
9
  import { promises as fs } from 'node:fs';
2
10
  export async function loadBuildConfig(filePath) {
3
11
  try {
@@ -0,0 +1,176 @@
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 { loadExactAuthConfig, saveAuthConfig } from './auth-store.js';
10
+ import { resolveDefaultConfigScope } from './cli-home.js';
11
+ export const DEFAULT_LICENSE_PKG_URL = 'https://pkg.nocobase.com/';
12
+ export const DEFAULT_DOCKER_NETWORK = 'nocobase';
13
+ export const DEFAULT_DOCKER_CONTAINER_PREFIX = 'nb';
14
+ export const SUPPORTED_CLI_CONFIG_KEYS = [
15
+ 'license.pkg-url',
16
+ 'docker.network',
17
+ 'docker.container-prefix',
18
+ ];
19
+ function trimValue(value) {
20
+ const text = String(value ?? '').trim();
21
+ return text || undefined;
22
+ }
23
+ function resolveScope(options = {}) {
24
+ return {
25
+ scope: options.scope ?? resolveDefaultConfigScope(),
26
+ };
27
+ }
28
+ export function isSupportedCliConfigKey(value) {
29
+ return SUPPORTED_CLI_CONFIG_KEYS.includes(value);
30
+ }
31
+ export function assertSupportedCliConfigKey(value) {
32
+ if (!isSupportedCliConfigKey(value)) {
33
+ throw new Error(`Unsupported config key "${value}". Supported keys: ${SUPPORTED_CLI_CONFIG_KEYS.join(', ')}`);
34
+ }
35
+ return value;
36
+ }
37
+ function cloneSettings(config) {
38
+ return {
39
+ license: config.settings?.license ? { ...config.settings.license } : undefined,
40
+ docker: config.settings?.docker ? { ...config.settings.docker } : undefined,
41
+ };
42
+ }
43
+ function pruneSettings(config) {
44
+ const license = config.settings?.license;
45
+ if (license && !trimValue(license.pkgUrl)) {
46
+ delete config.settings?.license;
47
+ }
48
+ const docker = config.settings?.docker;
49
+ if (docker && !trimValue(docker.network) && !trimValue(docker.containerPrefix)) {
50
+ delete config.settings?.docker;
51
+ }
52
+ if (config.settings
53
+ && !config.settings.license
54
+ && !config.settings.docker) {
55
+ delete config.settings;
56
+ }
57
+ }
58
+ export function getExplicitCliConfigValue(config, key) {
59
+ switch (key) {
60
+ case 'license.pkg-url':
61
+ return trimValue(config.settings?.license?.pkgUrl);
62
+ case 'docker.network':
63
+ return trimValue(config.settings?.docker?.network);
64
+ case 'docker.container-prefix':
65
+ return trimValue(config.settings?.docker?.containerPrefix);
66
+ }
67
+ }
68
+ export function getEffectiveCliConfigValue(config, key) {
69
+ const explicit = getExplicitCliConfigValue(config, key);
70
+ if (explicit) {
71
+ return explicit;
72
+ }
73
+ switch (key) {
74
+ case 'license.pkg-url':
75
+ return DEFAULT_LICENSE_PKG_URL;
76
+ case 'docker.network':
77
+ return trimValue(config.name) || DEFAULT_DOCKER_NETWORK;
78
+ case 'docker.container-prefix':
79
+ return trimValue(config.name) || DEFAULT_DOCKER_CONTAINER_PREFIX;
80
+ }
81
+ }
82
+ export function normalizeCliConfigValue(key, value) {
83
+ const normalized = value.trim();
84
+ if (!normalized) {
85
+ throw new Error(`Config key "${key}" requires a non-empty value.`);
86
+ }
87
+ if (key === 'license.pkg-url') {
88
+ return normalized.replace(/\/+$/, '') + '/';
89
+ }
90
+ return normalized;
91
+ }
92
+ export async function loadCliConfig(options = {}) {
93
+ return await loadExactAuthConfig(resolveScope(options));
94
+ }
95
+ export async function getCliConfigValue(key, options = {}) {
96
+ const config = await loadCliConfig(options);
97
+ return getEffectiveCliConfigValue(config, key);
98
+ }
99
+ export async function listExplicitCliConfigValues(options = {}) {
100
+ const config = await loadCliConfig(options);
101
+ const out = {};
102
+ for (const key of SUPPORTED_CLI_CONFIG_KEYS) {
103
+ const value = getExplicitCliConfigValue(config, key);
104
+ if (value) {
105
+ out[key] = value;
106
+ }
107
+ }
108
+ return out;
109
+ }
110
+ export async function setCliConfigValue(key, value, options = {}) {
111
+ const scope = resolveScope(options);
112
+ const config = await loadExactAuthConfig(scope);
113
+ const normalized = normalizeCliConfigValue(key, value);
114
+ config.settings = cloneSettings(config);
115
+ switch (key) {
116
+ case 'license.pkg-url':
117
+ config.settings.license = {
118
+ ...(config.settings.license ?? {}),
119
+ pkgUrl: normalized,
120
+ };
121
+ break;
122
+ case 'docker.network':
123
+ config.settings.docker = {
124
+ ...(config.settings.docker ?? {}),
125
+ network: normalized,
126
+ };
127
+ break;
128
+ case 'docker.container-prefix':
129
+ config.settings.docker = {
130
+ ...(config.settings.docker ?? {}),
131
+ containerPrefix: normalized,
132
+ };
133
+ break;
134
+ }
135
+ pruneSettings(config);
136
+ await saveAuthConfig(config, scope);
137
+ return normalized;
138
+ }
139
+ export async function deleteCliConfigValue(key, options = {}) {
140
+ const scope = resolveScope(options);
141
+ const config = await loadExactAuthConfig(scope);
142
+ const hadValue = Boolean(getExplicitCliConfigValue(config, key));
143
+ if (!hadValue) {
144
+ return false;
145
+ }
146
+ config.settings = cloneSettings(config);
147
+ switch (key) {
148
+ case 'license.pkg-url':
149
+ if (config.settings.license) {
150
+ delete config.settings.license.pkgUrl;
151
+ }
152
+ break;
153
+ case 'docker.network':
154
+ if (config.settings.docker) {
155
+ delete config.settings.docker.network;
156
+ }
157
+ break;
158
+ case 'docker.container-prefix':
159
+ if (config.settings.docker) {
160
+ delete config.settings.docker.containerPrefix;
161
+ }
162
+ break;
163
+ }
164
+ pruneSettings(config);
165
+ await saveAuthConfig(config, scope);
166
+ return true;
167
+ }
168
+ export async function resolveDockerNetworkName(options = {}) {
169
+ return await getCliConfigValue('docker.network', options);
170
+ }
171
+ export async function resolveDockerContainerPrefix(options = {}) {
172
+ return await getCliConfigValue('docker.container-prefix', options);
173
+ }
174
+ export async function resolveLicensePkgUrlFromConfig(options = {}) {
175
+ return await getCliConfigValue('license.pkg-url', options);
176
+ }
@@ -6,15 +6,12 @@
6
6
  * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
- import fs from 'node:fs';
10
9
  import os from 'node:os';
11
10
  import path from 'node:path';
12
11
  export const CLI_HOME_DIRNAME = '.nocobase';
13
- export const NB_CONFIG_SCOPE_ENV = 'NB_CONFIG_SCOPE';
14
12
  export const NB_CLI_ROOT_ENV = 'NB_CLI_ROOT';
15
13
  export function resolveDefaultConfigScope() {
16
- const raw = String(process.env[NB_CONFIG_SCOPE_ENV] ?? '').trim().toLowerCase();
17
- return raw === 'project' ? 'project' : 'global';
14
+ return 'global';
18
15
  }
19
16
  function readConfiguredPath(name) {
20
17
  const value = String(process.env[name] ?? '').trim();
@@ -24,28 +21,15 @@ function resolveGlobalCliHomeRoot() {
24
21
  return readConfiguredPath(NB_CLI_ROOT_ENV) ?? os.homedir();
25
22
  }
26
23
  export function resolveCliHomeRoot(scope = resolveDefaultConfigScope()) {
27
- const cwdRoot = process.cwd();
28
- if (scope === 'project') {
29
- return cwdRoot;
30
- }
31
- if (scope === 'global') {
32
- return resolveGlobalCliHomeRoot();
33
- }
34
- const cwdCliHome = path.join(cwdRoot, CLI_HOME_DIRNAME);
35
- if (fs.existsSync(cwdCliHome)) {
36
- return cwdRoot;
37
- }
24
+ void scope;
38
25
  return resolveGlobalCliHomeRoot();
39
26
  }
40
27
  export function resolveCliHomeDir(scope = resolveDefaultConfigScope()) {
41
28
  return path.join(resolveCliHomeRoot(scope), CLI_HOME_DIRNAME);
42
29
  }
43
30
  export function resolveEnvRoot(scope = resolveDefaultConfigScope()) {
44
- const envRoot = readConfiguredPath(NB_CLI_ROOT_ENV);
45
- if (envRoot) {
46
- return path.resolve(envRoot);
47
- }
48
- return resolveCliHomeRoot(scope);
31
+ void scope;
32
+ return resolveCliHomeRoot();
49
33
  }
50
34
  export function resolveEnvRelativePath(relativePath, scope = resolveDefaultConfigScope()) {
51
35
  return path.resolve(resolveEnvRoot(scope), relativePath);
@@ -58,5 +42,6 @@ export function resolveConfiguredEnvPath(value, scope = resolveDefaultConfigScop
58
42
  return path.isAbsolute(text) ? text : resolveEnvRelativePath(text, scope);
59
43
  }
60
44
  export function formatCliHomeScope(scope) {
61
- return scope === 'project' ? 'project' : 'global';
45
+ void scope;
46
+ return 'global';
62
47
  }