@nocobase/cli-v1 2.1.0-beta.44 → 2.1.0-beta.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/nocobase.conf.tpl CHANGED
@@ -58,6 +58,14 @@ server {
58
58
  }
59
59
  }
60
60
 
61
+ location ^~ {{distPath}} {
62
+ alias {{cwd}}/storage/dist-client/;
63
+ expires 365d;
64
+ add_header Cache-Control "public";
65
+ access_log off;
66
+ autoindex off;
67
+ }
68
+
61
69
  location {{publicPath}}static/plugins/ {
62
70
  alias {{cwd}}/node_modules/;
63
71
  expires 365d;
@@ -91,7 +99,7 @@ server {
91
99
  }
92
100
 
93
101
  location {{v2PublicPath}}assets/ {
94
- alias {{cwd}}/node_modules/@nocobase/app/dist/client/v2/assets/;
102
+ alias {{cwd}}/node_modules/@nocobase/app/dist/client/v/assets/;
95
103
  expires 365d;
96
104
  add_header Cache-Control "public";
97
105
  access_log off;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/cli-v1",
3
- "version": "2.1.0-beta.44",
3
+ "version": "2.1.0-beta.46",
4
4
  "description": "",
5
5
  "license": "Apache-2.0",
6
6
  "main": "./src/index.js",
@@ -8,9 +8,9 @@
8
8
  "nocobase-v1": "./bin/index.js"
9
9
  },
10
10
  "dependencies": {
11
- "@nocobase/cli": "2.1.0-beta.44",
11
+ "@nocobase/cli": "2.1.0-beta.46",
12
12
  "@nocobase/license-kit": "^0.3.8",
13
- "@nocobase/utils": "2.1.0-beta.44",
13
+ "@nocobase/utils": "2.1.0-beta.46",
14
14
  "chalk": "^4.1.1",
15
15
  "commander": "^9.2.0",
16
16
  "deepmerge": "^4.3.1",
@@ -25,7 +25,7 @@
25
25
  "tree-kill": "^1.2.2"
26
26
  },
27
27
  "devDependencies": {
28
- "@nocobase/devtools": "2.1.0-beta.44",
28
+ "@nocobase/devtools": "2.1.0-beta.46",
29
29
  "@types/fs-extra": "^11.0.1"
30
30
  },
31
31
  "repository": {
@@ -33,5 +33,5 @@
33
33
  "url": "git+https://github.com/nocobase/nocobase.git",
34
34
  "directory": "packages/core/cli"
35
35
  },
36
- "gitHead": "540d4897b1696cc24c0754521b0b766978b4933c"
36
+ "gitHead": "91fd3ab238a5ff58735bbfca04a8cb05d233b0af"
37
37
  }
@@ -0,0 +1,45 @@
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
+
10
+ /* eslint-env jest */
11
+
12
+ const fs = require('fs-extra');
13
+ const os = require('os');
14
+ const path = require('path');
15
+ const { resolveDefaultCdnBaseUrlFromActiveVersion } = require('../util');
16
+
17
+ describe('cli-v1 default CDN_BASE_URL', () => {
18
+ const originalEnv = { ...process.env };
19
+
20
+ afterEach(() => {
21
+ process.env = { ...originalEnv };
22
+ });
23
+
24
+ test('prefers the extracted dist-client active version when available', async () => {
25
+ const storagePath = await fs.mkdtemp(path.join(os.tmpdir(), 'nocobase-dist-client-'));
26
+ const distClientRoot = path.join(storagePath, 'dist-client');
27
+ await fs.ensureDir(distClientRoot);
28
+ await fs.writeFile(path.join(distClientRoot, 'active-version'), '2.1.0-beta.45\n', 'utf8');
29
+ process.env.STORAGE_PATH = storagePath;
30
+
31
+ expect(resolveDefaultCdnBaseUrlFromActiveVersion('/console/')).toBe('/console/dist/2.1.0-beta.45/');
32
+ expect(resolveDefaultCdnBaseUrlFromActiveVersion('/')).toBe('/dist/2.1.0-beta.45/');
33
+
34
+ await fs.remove(storagePath);
35
+ });
36
+
37
+ test('returns undefined when the active version file is missing', async () => {
38
+ const storagePath = await fs.mkdtemp(path.join(os.tmpdir(), 'nocobase-dist-client-empty-'));
39
+ process.env.STORAGE_PATH = storagePath;
40
+
41
+ expect(resolveDefaultCdnBaseUrlFromActiveVersion('/console/')).toBeUndefined();
42
+
43
+ await fs.remove(storagePath);
44
+ });
45
+ });
@@ -8,9 +8,9 @@
8
8
  */
9
9
 
10
10
  const chalk = require('chalk');
11
- const { Command } = require('commander');
12
11
  const fs = require('fs-extra');
13
12
  const { resolve } = require('path');
13
+ const { discoverPluginPackages } = require('@nocobase/utils/plugin-package');
14
14
  const { storagePathJoin } = require('../util');
15
15
 
16
16
  /**
@@ -20,39 +20,47 @@ const { storagePathJoin } = require('../util');
20
20
  */
21
21
  async function copyMainClient(source, target) {
22
22
  if (!(await fs.exists(source))) {
23
- console.log(chalk.yellow(`Source directory does not exist: ${source}`));
24
- return;
23
+ console.warn(chalk.yellow(`Source directory does not exist: ${source}`));
24
+ return false;
25
25
  }
26
26
  // 确保目标目录存在且为空
27
27
  await fs.ensureDir(target);
28
28
  await fs.emptyDir(target);
29
29
  await fs.copy(source, target, { recursive: true });
30
- console.log(chalk.green(`Copied main client files from ${source} to ${target}`));
30
+ return true;
31
31
  }
32
32
 
33
33
  /**
34
34
  * 复制插件客户端文件
35
- * @param {string} pluginsBaseDir - 插件基础目录路径
36
- * @param {string} namespace - 命名空间(如 '@nocobase' 或 '@nocobase-example')
35
+ * @param {Array<{ packageName: string, resolvedPath: string }>} plugins - 插件清单
37
36
  * @param {string} target - 目标目录
38
37
  */
39
- async function copyPluginClients(pluginsBaseDir, namespace, target) {
40
- const pluginsDir = resolve(process.cwd(), pluginsBaseDir, namespace);
41
- if (await fs.exists(pluginsDir)) {
42
- const pluginNames = await fs.readdir(pluginsDir);
43
- for (const pluginName of pluginNames) {
44
- const pluginPath = resolve(pluginsDir, pluginName);
45
- for (const lane of ['client', 'client-v2']) {
46
- const pluginDistClient = resolve(pluginPath, `dist/${lane}`);
47
- if (await fs.exists(pluginDistClient)) {
48
- const pluginTarget = resolve(target, 'static/plugins', namespace, pluginName, 'dist', lane);
49
- await fs.mkdir(resolve(pluginTarget, '..'), { recursive: true });
50
- await fs.copy(pluginDistClient, pluginTarget, { recursive: true });
51
- console.log(chalk.green(`Copied ${namespace}/${pluginName} ${lane} files`));
52
- }
38
+ async function copyPluginClients(plugins, target) {
39
+ let copiedCount = 0;
40
+ for (const plugin of plugins) {
41
+ for (const lane of ['client', 'client-v2']) {
42
+ const pluginDistClient = resolve(plugin.resolvedPath, `dist/${lane}`);
43
+ if (await fs.exists(pluginDistClient)) {
44
+ const pluginTarget = resolve(target, 'static/plugins', plugin.packageName, 'dist', lane);
45
+ await fs.mkdir(resolve(pluginTarget, '..'), { recursive: true });
46
+ await fs.copy(pluginDistClient, pluginTarget, { recursive: true });
47
+ copiedCount++;
53
48
  }
54
49
  }
55
50
  }
51
+ return copiedCount;
52
+ }
53
+
54
+ async function writeActiveVersion(version) {
55
+ const distClientRoot = storagePathJoin('dist-client');
56
+ const activeVersionFile = resolve(distClientRoot, 'active-version');
57
+ const tempFile = resolve(distClientRoot, `.active-version.${process.pid}.${Date.now()}.tmp`);
58
+
59
+ await fs.ensureDir(distClientRoot);
60
+ await fs.writeFile(tempFile, `${version}\n`, 'utf8');
61
+ await fs.move(tempFile, activeVersionFile, { overwrite: true });
62
+
63
+ return activeVersionFile;
56
64
  }
57
65
 
58
66
  /**
@@ -108,15 +116,38 @@ async function uploadDirectoryToOSS(client, localDir, ossPrefix = '') {
108
116
  module.exports = (cli) => {
109
117
  cli
110
118
  .command('client:extract')
119
+ .option('--json', 'Output machine-readable JSON')
111
120
  .allowUnknownOption()
112
- .action(async () => {
121
+ .action(async function () {
122
+ const json = this.opts().json === true;
113
123
  const version = require('../../package.json').version;
114
124
  const target = storagePathJoin('dist-client', version);
115
125
  const mainClientSource = resolve(process.cwd(), 'node_modules/@nocobase/app/dist/client');
116
- await copyMainClient(mainClientSource, target);
117
- await copyPluginClients('packages/plugins', '@nocobase', target);
118
- await copyPluginClients('packages/plugins', '@nocobase-example', target);
119
- await copyPluginClients('packages/pro-plugins', '@nocobase', target);
126
+ const plugins = await discoverPluginPackages({
127
+ nodeModulesPath: resolve(process.cwd(), 'node_modules'),
128
+ });
129
+ const copiedMainClient = await copyMainClient(mainClientSource, target);
130
+ const copiedPluginBundles = await copyPluginClients(plugins, target);
131
+ const activeVersionFile = await writeActiveVersion(version);
132
+
133
+ if (json) {
134
+ process.stdout.write(
135
+ JSON.stringify({
136
+ version,
137
+ target,
138
+ activeVersionFile,
139
+ }),
140
+ );
141
+ return;
142
+ }
143
+
144
+ console.log(
145
+ chalk.green(
146
+ `Extracted client assets ${version} to ${target} (main client: ${
147
+ copiedMainClient ? 'yes' : 'no'
148
+ }, plugin bundles: ${copiedPluginBundles}).`,
149
+ ),
150
+ );
120
151
  });
121
152
 
122
153
  cli
@@ -8,8 +8,7 @@
8
8
  */
9
9
 
10
10
  const { resolve, posix } = require('path');
11
- const { storagePathJoin, resolvePublicPath, resolveV2PublicPath } = require('../util');
12
- const { Command } = require('commander');
11
+ const { storagePathJoin, resolvePublicPath, resolveV2PublicPath, normalizeModernClientPrefix } = require('../util');
13
12
  const { readFileSync, writeFileSync } = require('fs');
14
13
 
15
14
  /**
@@ -20,18 +19,22 @@ module.exports = (cli) => {
20
19
  cli.command('create-nginx-conf').action(async (name, options) => {
21
20
  const rawAppPublicPath = process.env.APP_PUBLIC_PATH || '/';
22
21
  const appPublicPath = resolvePublicPath(rawAppPublicPath);
22
+ const distPath = `${appPublicPath.replace(/\/$/, '')}/dist/`;
23
23
  const v2PublicPath = resolveV2PublicPath(rawAppPublicPath);
24
+ const modernClientPrefix = normalizeModernClientPrefix(process.env.APP_MODERN_CLIENT_PREFIX);
24
25
  const appPublicPathWithoutTrailingSlash = appPublicPath.replace(/\/$/, '');
25
26
  const v2PublicPathWithoutTrailingSlash = v2PublicPath.replace(/\/$/, '');
26
27
  const file = resolve(__dirname, '../../nocobase.conf.tpl');
27
28
  const data = readFileSync(file, 'utf-8');
28
29
  let otherLocation = '';
29
30
  if (appPublicPath !== '/') {
30
- otherLocation = `location = /v2 {
31
+ // When the app is mounted under a sub-path, redirect the root-level
32
+ // `/<prefix>` and `/<prefix>/` to the real (sub-path-prefixed) location.
33
+ otherLocation = `location = /${modernClientPrefix} {
31
34
  return 302 ${v2PublicPath}$is_args$args;
32
35
  }
33
36
 
34
- location /v2/ {
37
+ location /${modernClientPrefix}/ {
35
38
  return 302 ${appPublicPathWithoutTrailingSlash}$uri$is_args$args;
36
39
  }
37
40
 
@@ -43,6 +46,7 @@ module.exports = (cli) => {
43
46
  const replaced = data
44
47
  .replace(/\{\{cwd\}\}/g, posix.resolve(process.cwd()))
45
48
  .replace(/\{\{publicPath\}\}/g, appPublicPath)
49
+ .replace(/\{\{distPath\}\}/g, distPath)
46
50
  .replace(/\{\{v2PublicPath\}\}/g, v2PublicPath)
47
51
  .replace(/\{\{v2PublicPathNoTrailingSlash\}\}/g, v2PublicPathWithoutTrailingSlash)
48
52
  .replace(/\{\{apiPort\}\}/g, process.env.APP_PORT)
package/src/util.js CHANGED
@@ -367,9 +367,64 @@ function resolvePublicPath(appPublicPath = '/') {
367
367
 
368
368
  exports.resolvePublicPath = resolvePublicPath;
369
369
 
370
+ function resolveDistPublicPath(appPublicPath = '/') {
371
+ const publicPath = resolvePublicPath(appPublicPath).replace(/\/+$/, '');
372
+ return `${publicPath}/dist/`;
373
+ }
374
+
375
+ function buildDefaultCdnBaseUrl(appPublicPath, version) {
376
+ const normalizedVersion = String(version || '')
377
+ .trim()
378
+ .replace(/^\/+|\/+$/g, '');
379
+ if (!normalizedVersion) {
380
+ return undefined;
381
+ }
382
+ return `${resolveDistPublicPath(appPublicPath)}${normalizedVersion}/`;
383
+ }
384
+
385
+ exports.buildDefaultCdnBaseUrl = buildDefaultCdnBaseUrl;
386
+
387
+ function resolveDefaultCdnBaseUrlFromActiveVersion(appPublicPath = '/') {
388
+ try {
389
+ const activeVersionFile = storagePathJoin('dist-client', 'active-version');
390
+ if (!fs.existsSync(activeVersionFile)) {
391
+ return undefined;
392
+ }
393
+ const activeVersion = String(fs.readFileSync(activeVersionFile, 'utf8') || '').trim();
394
+ return buildDefaultCdnBaseUrl(appPublicPath, activeVersion);
395
+ } catch (_error) {
396
+ return undefined;
397
+ }
398
+ }
399
+
400
+ exports.resolveDefaultCdnBaseUrlFromActiveVersion = resolveDefaultCdnBaseUrlFromActiveVersion;
401
+
402
+ // Default URL segment under which the modern (v2) client is served.
403
+ // Kept local here so the CLI bootstrap (bin/index.js -> initEnv) stays lightweight
404
+ // and does not have to require heavier packages. A second copy of the fixed
405
+ // build-output directory name lives in:
406
+ // - packages/core/app/client-v2/rsbuild.config.ts (output.distPath)
407
+ // - packages/core/server/src/gateway/index.ts (MODERN_CLIENT_DIST_DIR)
408
+ // Keep them in sync. See docs/adr/0001-modern-client-prefix.md.
409
+ const DEFAULT_MODERN_CLIENT_PREFIX = 'v';
410
+
411
+ exports.DEFAULT_MODERN_CLIENT_PREFIX = DEFAULT_MODERN_CLIENT_PREFIX;
412
+
413
+ // Normalize APP_MODERN_CLIENT_PREFIX (accepts `v`, `/v`, `/v/`)
414
+ // down to a bare segment like `v`.
415
+ function normalizeModernClientPrefix(value) {
416
+ const segment = String(value || '')
417
+ .trim()
418
+ .replace(/^\/+|\/+$/g, '');
419
+ return segment || DEFAULT_MODERN_CLIENT_PREFIX;
420
+ }
421
+
422
+ exports.normalizeModernClientPrefix = normalizeModernClientPrefix;
423
+
370
424
  function resolveV2PublicPath(appPublicPath = '/') {
371
425
  const publicPath = resolvePublicPath(appPublicPath);
372
- return `${publicPath.replace(/\/$/, '')}/v2/`;
426
+ const prefix = normalizeModernClientPrefix(process.env.APP_MODERN_CLIENT_PREFIX);
427
+ return `${publicPath.replace(/\/$/, '')}/${prefix}/`;
373
428
  }
374
429
 
375
430
  exports.resolveV2PublicPath = resolveV2PublicPath;
@@ -533,6 +588,7 @@ exports.initEnv = function initEnv() {
533
588
  APP_BASE_URL: '',
534
589
  CDN_BASE_URL: '',
535
590
  APP_PUBLIC_PATH: '/',
591
+ APP_MODERN_CLIENT_PREFIX: DEFAULT_MODERN_CLIENT_PREFIX,
536
592
  ESM_CDN_BASE_URL: 'https://esm.sh',
537
593
  ESM_CDN_SUFFIX: '',
538
594
  };
@@ -598,8 +654,10 @@ exports.initEnv = function initEnv() {
598
654
  process.env.__env_modified__ = true;
599
655
  }
600
656
 
601
- if (!process.env.CDN_BASE_URL && process.env.APP_PUBLIC_PATH !== '/') {
602
- process.env.CDN_BASE_URL = process.env.APP_PUBLIC_PATH;
657
+ if (!process.env.CDN_BASE_URL) {
658
+ process.env.CDN_BASE_URL =
659
+ resolveDefaultCdnBaseUrlFromActiveVersion(process.env.APP_PUBLIC_PATH) ||
660
+ (process.env.APP_PUBLIC_PATH !== '/' ? process.env.APP_PUBLIC_PATH : '');
603
661
  }
604
662
 
605
663
  if (process.env.CDN_BASE_URL.includes('http') && process.env.CDN_VERSION === 'auto') {