@nocobase/cli-v1 2.2.0-beta.8 → 2.2.0

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
@@ -36,10 +36,11 @@ server {
36
36
  gzip on;
37
37
  gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
38
38
 
39
- location ~* ^{{publicPath}}storage/uploads/(.*\.(?:htm|html|svg|svgz|xhtml))$ {
39
+ location ~* ^{{publicPath}}storage/uploads/(.*\.(?:htm|html|pdf|svg|svgz|xht|xhtml|xml|xsl|xslt))$ {
40
40
  alias {{cwd}}/storage/uploads/$1;
41
41
  add_header Cache-Control "public";
42
42
  add_header Content-Disposition "attachment" always;
43
+ add_header Content-Security-Policy "sandbox" always;
43
44
  add_header X-Content-Type-Options "nosniff" always;
44
45
  access_log off;
45
46
  autoindex off;
@@ -48,6 +49,7 @@ server {
48
49
  location {{publicPath}}storage/uploads/ {
49
50
  alias {{cwd}}/storage/uploads/;
50
51
  add_header Cache-Control "public";
52
+ add_header Content-Security-Policy "sandbox" always;
51
53
  add_header X-Content-Type-Options "nosniff" always;
52
54
  access_log off;
53
55
  autoindex off;
@@ -55,6 +57,8 @@ server {
55
57
  location ~* \.md$ {
56
58
  default_type text/markdown;
57
59
  add_header Content-Disposition "inline";
60
+ add_header Content-Security-Policy "sandbox" always;
61
+ add_header X-Content-Type-Options "nosniff" always;
58
62
  }
59
63
  }
60
64
 
@@ -163,6 +167,23 @@ server {
163
167
  send_timeout 600;
164
168
  }
165
169
 
170
+ # File access URLs are application routes. Keep them above the SPA
171
+ # locations so Nginx forwards them to the NocoBase gateway.
172
+ location ^~ {{publicPath}}files/ {
173
+ proxy_pass http://127.0.0.1:{{apiPort}};
174
+ proxy_http_version 1.1;
175
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
176
+ proxy_set_header X-Forwarded-Proto $upstream_x_forwarded_proto;
177
+ proxy_set_header Host $final_host;
178
+ proxy_set_header Referer $http_referer;
179
+ proxy_set_header User-Agent $http_user_agent;
180
+ add_header Cache-Control 'no-cache, no-store';
181
+ proxy_connect_timeout 600;
182
+ proxy_send_timeout 600;
183
+ proxy_read_timeout 600;
184
+ send_timeout 600;
185
+ }
186
+
166
187
 
167
188
 
168
189
  location {{publicPath}} {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/cli-v1",
3
- "version": "2.2.0-beta.8",
3
+ "version": "2.2.0",
4
4
  "description": "",
5
5
  "license": "Apache-2.0",
6
6
  "main": "./src/index.js",
@@ -9,9 +9,9 @@
9
9
  "nocobase-v1": "./bin/index.js"
10
10
  },
11
11
  "dependencies": {
12
- "@nocobase/cli": "2.2.0-beta.8",
12
+ "@nocobase/cli": "2.2.0",
13
13
  "@nocobase/license-kit": "^0.3.8",
14
- "@nocobase/utils": "2.2.0-beta.8",
14
+ "@nocobase/utils": "2.2.0",
15
15
  "chalk": "^4.1.1",
16
16
  "commander": "^9.2.0",
17
17
  "deepmerge": "^4.3.1",
@@ -33,5 +33,5 @@
33
33
  "url": "git+https://github.com/nocobase/nocobase.git",
34
34
  "directory": "packages/core/cli"
35
35
  },
36
- "gitHead": "fa2502c1e9faf6d74b3f51b42dbc6546638d46af"
36
+ "gitHead": "d5052cae37177054c236f1bf7bba3aed6ef029d0"
37
37
  }
@@ -24,6 +24,8 @@ function createAppPackageRoot() {
24
24
  [
25
25
  "window['__nocobase_app_dev__'] = {{env.NOCOBASE_APP_DEV}};",
26
26
  "window['__nocobase_public_path__'] = '{{env.APP_PUBLIC_PATH}}';",
27
+ "window['__nocobase_modern_client_prefix__'] = '{{env.APP_MODERN_CLIENT_PREFIX}}';",
28
+ "window['__nocobase_app_client_entry_mode__'] = '{{env.APP_CLIENT_ENTRY_MODE}}';",
27
29
  ].join('\n'),
28
30
  'utf-8',
29
31
  );
@@ -73,11 +75,43 @@ describe('cli-v1 buildIndexHtml', () => {
73
75
  process.env.APP_PACKAGE_ROOT = appRoot;
74
76
  process.env.APP_PUBLIC_PATH = '/';
75
77
  process.env.NOCOBASE_APP_DEV = '';
78
+ process.env.APP_MODERN_CLIENT_PREFIX = 'console';
79
+ process.env.APP_CLIENT_ENTRY_MODE = 'modern-default';
76
80
 
77
81
  buildIndexHtml();
78
82
 
79
83
  const html = fs.readFileSync(path.join(appRoot, 'dist/client/index.html'), 'utf-8');
80
84
  expect(html).toContain("window['__nocobase_app_dev__'] = false;");
85
+ expect(html).toContain("window['__nocobase_modern_client_prefix__'] = 'console';");
86
+ expect(html).toContain("window['__nocobase_app_client_entry_mode__'] = 'modern-default';");
87
+ fs.removeSync(appRoot);
88
+ });
89
+
90
+ test('refreshes cached tpl when new runtime placeholders are missing', () => {
91
+ const appRoot = createAppPackageRoot();
92
+ const tplPath = path.join(appRoot, 'dist/client/index.html.tpl');
93
+ const indexPath = path.join(appRoot, 'dist/client/index.html');
94
+ fs.writeFileSync(tplPath, "window['__nocobase_public_path__'] = '{{env.APP_PUBLIC_PATH}}';", 'utf-8');
95
+ fs.writeFileSync(
96
+ indexPath,
97
+ [
98
+ "window['__nocobase_public_path__'] = '{{env.APP_PUBLIC_PATH}}';",
99
+ "window['__nocobase_modern_client_prefix__'] = '{{env.APP_MODERN_CLIENT_PREFIX}}';",
100
+ "window['__nocobase_app_client_entry_mode__'] = '{{env.APP_CLIENT_ENTRY_MODE}}';",
101
+ ].join('\n'),
102
+ 'utf-8',
103
+ );
104
+ process.argv = ['node', 'nocobase-v1', 'start'];
105
+ process.env.APP_PACKAGE_ROOT = appRoot;
106
+ process.env.APP_PUBLIC_PATH = '/';
107
+ process.env.APP_MODERN_CLIENT_PREFIX = 'v';
108
+ process.env.APP_CLIENT_ENTRY_MODE = 'modern-only';
109
+
110
+ buildIndexHtml();
111
+
112
+ const tpl = fs.readFileSync(tplPath, 'utf-8');
113
+ expect(tpl).toContain('__nocobase_modern_client_prefix__');
114
+ expect(tpl).toContain('__nocobase_app_client_entry_mode__');
81
115
  fs.removeSync(appRoot);
82
116
  });
83
117
  });
@@ -9,7 +9,7 @@
9
9
 
10
10
  /* eslint-env jest */
11
11
 
12
- const { buildAppDevForwardArgs, forwardDevToAppDev } = require('../commands/dev')._test;
12
+ const { buildAppDevForwardArgs, forwardDevToAppDev, resolveDevRuntimeMode } = require('../commands/dev')._test;
13
13
 
14
14
  describe('cli-v1 dev command', () => {
15
15
  test('buildAppDevForwardArgs rewrites dev argv to app-dev while preserving extra args', () => {
@@ -31,4 +31,31 @@ describe('cli-v1 dev command', () => {
31
31
 
32
32
  expect(calls).toEqual([['nocobase-v1', ['app-dev', '--port', '13000', '--db-sync']]]);
33
33
  });
34
+
35
+ test('resolveDevRuntimeMode keeps both clients in legacy-default', () => {
36
+ expect(resolveDevRuntimeMode({ appClientEntryMode: 'legacy-default' })).toMatchObject({
37
+ useModernOnlyEntryMode: false,
38
+ shouldRunClient: true,
39
+ shouldRunClientV2: true,
40
+ shouldRunServer: true,
41
+ });
42
+ });
43
+
44
+ test('resolveDevRuntimeMode only runs v2 client and server in modern-only', () => {
45
+ expect(resolveDevRuntimeMode({ appClientEntryMode: 'modern-only' })).toMatchObject({
46
+ useModernOnlyEntryMode: true,
47
+ shouldRunClient: false,
48
+ shouldRunClientV2: true,
49
+ shouldRunServer: true,
50
+ });
51
+ });
52
+
53
+ test('resolveDevRuntimeMode preserves explicit client-v2-only flag behavior', () => {
54
+ expect(resolveDevRuntimeMode({ clientV2Only: true, appClientEntryMode: 'legacy-default' })).toMatchObject({
55
+ useModernOnlyEntryMode: false,
56
+ shouldRunClient: false,
57
+ shouldRunClientV2: true,
58
+ shouldRunServer: false,
59
+ });
60
+ });
34
61
  });
@@ -38,6 +38,21 @@ module.exports = (cli) => {
38
38
  return 302 ${appPublicPathWithoutTrailingSlash}$uri$is_args$args;
39
39
  }
40
40
 
41
+ location ^~ /files/ {
42
+ proxy_pass http://127.0.0.1:${process.env.APP_PORT};
43
+ proxy_http_version 1.1;
44
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
45
+ proxy_set_header X-Forwarded-Proto $upstream_x_forwarded_proto;
46
+ proxy_set_header Host $final_host;
47
+ proxy_set_header Referer $http_referer;
48
+ proxy_set_header User-Agent $http_user_agent;
49
+ add_header Cache-Control 'no-cache, no-store';
50
+ proxy_connect_timeout 600;
51
+ proxy_send_timeout 600;
52
+ proxy_read_timeout 600;
53
+ send_timeout 600;
54
+ }
55
+
41
56
  location / {
42
57
  alias ${posix.resolve(process.cwd())}/node_modules/@nocobase/app/dist/client/;
43
58
  try_files $uri $uri/ /index.html;
@@ -20,6 +20,7 @@ const {
20
20
  isPortReachable,
21
21
  buildWSURL,
22
22
  checkDBDialect,
23
+ resolveAppClientEntryMode,
23
24
  } = require('../util');
24
25
  const { getPortPromise } = require('portfinder');
25
26
  const chokidar = require('chokidar');
@@ -47,6 +48,25 @@ function buildAppDevForwardArgs(argv = process.argv) {
47
48
  return ['app-dev', ...argv.slice(3)];
48
49
  }
49
50
 
51
+ function resolveDevRuntimeMode(opts = {}) {
52
+ const appClientEntryMode = opts.appClientEntryMode || resolveAppClientEntryMode();
53
+ const useModernOnlyEntryMode = appClientEntryMode === 'modern-only';
54
+ const clientV2Only = !!opts.clientV2Only;
55
+ const forceClient = !!opts.client;
56
+ const forceServer = !!opts.server;
57
+ const shouldRunClientV2 = clientV2Only || useModernOnlyEntryMode || forceClient || !forceServer;
58
+ const shouldRunClient = !clientV2Only && !useModernOnlyEntryMode && (forceClient || !forceServer);
59
+ const shouldRunServer = !clientV2Only && (forceServer || !forceClient || useModernOnlyEntryMode);
60
+
61
+ return {
62
+ appClientEntryMode,
63
+ useModernOnlyEntryMode,
64
+ shouldRunClientV2,
65
+ shouldRunClient,
66
+ shouldRunServer,
67
+ };
68
+ }
69
+
50
70
  async function forwardDevToAppDev({ argv = process.argv, runCommand = run } = {}) {
51
71
  await runCommand('nocobase-v1', buildAppDevForwardArgs(argv));
52
72
  }
@@ -107,9 +127,9 @@ module.exports = (cli) => {
107
127
  nodeCheck();
108
128
  await postCheck(opts);
109
129
 
110
- const shouldRunClientV2 = clientV2Only || client || !server;
111
- const shouldRunClient = !clientV2Only && (client || !server);
112
- const shouldRunServer = !clientV2Only && (server || !client);
130
+ const { useModernOnlyEntryMode, shouldRunClientV2, shouldRunClient, shouldRunServer } = resolveDevRuntimeMode(
131
+ opts,
132
+ );
113
133
  const shouldRunClientWithRsbuild = shouldRunClient && !!rsbuild;
114
134
 
115
135
  if (shouldRunServer && server) {
@@ -120,12 +140,16 @@ module.exports = (cli) => {
120
140
  });
121
141
  }
122
142
 
123
- if (shouldRunClientV2 && !clientV2Only) {
143
+ if (shouldRunClientV2 && !clientV2Only && !useModernOnlyEntryMode) {
124
144
  clientV2Port = await getPortPromise({
125
145
  port: 1 * clientPort + 2,
126
146
  });
127
147
  }
128
148
 
149
+ if (useModernOnlyEntryMode) {
150
+ clientV2Port = APP_PORT;
151
+ }
152
+
129
153
  let subprocessClient;
130
154
  let subprocessClientV2;
131
155
 
@@ -306,4 +330,5 @@ module.exports = (cli) => {
306
330
  module.exports._test = {
307
331
  buildAppDevForwardArgs,
308
332
  forwardDevToAppDev,
333
+ resolveDevRuntimeMode,
309
334
  };
package/src/util.js CHANGED
@@ -432,6 +432,30 @@ const DEFAULT_MODERN_CLIENT_PREFIX = 'v';
432
432
 
433
433
  exports.DEFAULT_MODERN_CLIENT_PREFIX = DEFAULT_MODERN_CLIENT_PREFIX;
434
434
 
435
+ const DEFAULT_APP_CLIENT_ENTRY_MODE = 'legacy-default';
436
+ const APP_CLIENT_ENTRY_MODES = new Set(['legacy-default', 'modern-default', 'modern-only']);
437
+
438
+ exports.DEFAULT_APP_CLIENT_ENTRY_MODE = DEFAULT_APP_CLIENT_ENTRY_MODE;
439
+
440
+ function isAppClientEntryMode(value) {
441
+ return APP_CLIENT_ENTRY_MODES.has(String(value || '').trim());
442
+ }
443
+
444
+ exports.isAppClientEntryMode = isAppClientEntryMode;
445
+
446
+ function normalizeAppClientEntryMode(value) {
447
+ const normalized = String(value || '').trim();
448
+ return isAppClientEntryMode(normalized) ? normalized : DEFAULT_APP_CLIENT_ENTRY_MODE;
449
+ }
450
+
451
+ exports.normalizeAppClientEntryMode = normalizeAppClientEntryMode;
452
+
453
+ function resolveAppClientEntryMode() {
454
+ return normalizeAppClientEntryMode(process.env.APP_CLIENT_ENTRY_MODE);
455
+ }
456
+
457
+ exports.resolveAppClientEntryMode = resolveAppClientEntryMode;
458
+
435
459
  // Normalize APP_MODERN_CLIENT_PREFIX (accepts `v`, `/v`, `/v/`)
436
460
  // down to a bare segment like `v`.
437
461
  function normalizeModernClientPrefix(value) {
@@ -455,7 +479,17 @@ function isAppDevHtml() {
455
479
  return process.argv[2] === 'app-dev' || process.env.NOCOBASE_APP_DEV === 'true';
456
480
  }
457
481
 
482
+ function shouldRefreshLegacyIndexTemplate(data = '') {
483
+ return (
484
+ !data.includes("window['__nocobase_modern_client_prefix__']") ||
485
+ !data.includes("window['__nocobase_app_client_entry_mode__']")
486
+ );
487
+ }
488
+
458
489
  function buildIndexHtml(force = false) {
490
+ if (process.env.NOCOBASE_EXTRACT_CLIENT_ASSETS === 'true') {
491
+ return;
492
+ }
459
493
  const file = `${process.env.APP_PACKAGE_ROOT}/dist/client/index.html`;
460
494
  if (!fs.existsSync(file)) {
461
495
  return;
@@ -466,11 +500,15 @@ function buildIndexHtml(force = false) {
466
500
  }
467
501
  if (!fs.existsSync(tpl)) {
468
502
  fs.copyFileSync(file, tpl);
503
+ } else if (shouldRefreshLegacyIndexTemplate(fs.readFileSync(tpl, 'utf-8'))) {
504
+ fs.copyFileSync(file, tpl);
469
505
  }
470
506
  const data = fs.readFileSync(tpl, 'utf-8');
471
507
  let replacedData = data
472
508
  .replace(/\{\{env.CDN_BASE_URL\}\}/g, process.env.CDN_BASE_URL)
473
509
  .replace(/\{\{env.APP_PUBLIC_PATH\}\}/g, process.env.APP_PUBLIC_PATH)
510
+ .replace(/\{\{env.APP_MODERN_CLIENT_PREFIX\}\}/g, normalizeModernClientPrefix(process.env.APP_MODERN_CLIENT_PREFIX))
511
+ .replace(/\{\{env.APP_CLIENT_ENTRY_MODE\}\}/g, resolveAppClientEntryMode())
474
512
  .replace(/\{\{env.API_CLIENT_SHARE_TOKEN\}\}/g, process.env.API_CLIENT_SHARE_TOKEN || 'false')
475
513
  .replace(/\{\{env.API_CLIENT_STORAGE_TYPE\}\}/g, process.env.API_CLIENT_STORAGE_TYPE)
476
514
  .replace(/\{\{env.API_CLIENT_STORAGE_PREFIX\}\}/g, process.env.API_CLIENT_STORAGE_PREFIX)
@@ -611,6 +649,7 @@ exports.initEnv = function initEnv() {
611
649
  CDN_BASE_URL: '',
612
650
  APP_PUBLIC_PATH: '/',
613
651
  APP_MODERN_CLIENT_PREFIX: DEFAULT_MODERN_CLIENT_PREFIX,
652
+ APP_CLIENT_ENTRY_MODE: DEFAULT_APP_CLIENT_ENTRY_MODE,
614
653
  ESM_CDN_BASE_URL: 'https://esm.sh',
615
654
  ESM_CDN_SUFFIX: '',
616
655
  };
@@ -662,6 +701,14 @@ exports.initEnv = function initEnv() {
662
701
  }
663
702
  }
664
703
 
704
+ const rawAppClientEntryMode = String(process.env.APP_CLIENT_ENTRY_MODE || '').trim();
705
+ if (rawAppClientEntryMode && !isAppClientEntryMode(rawAppClientEntryMode)) {
706
+ console.warn(
707
+ `Unknown APP_CLIENT_ENTRY_MODE "${rawAppClientEntryMode}", falling back to ${DEFAULT_APP_CLIENT_ENTRY_MODE}.`,
708
+ );
709
+ }
710
+ process.env.APP_CLIENT_ENTRY_MODE = normalizeAppClientEntryMode(rawAppClientEntryMode);
711
+
665
712
  if (!process.env.__env_modified__ && process.env.APP_PUBLIC_PATH) {
666
713
  const publicPath = process.env.APP_PUBLIC_PATH.replace(/\/$/g, '');
667
714
  const keys = ['API_BASE_PATH', 'WS_PATH', 'PLUGIN_STATICS_PATH'];