@nocobase/cli-v1 2.2.0-alpha.11 → 2.2.0-alpha.12

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
@@ -128,6 +128,34 @@ server {
128
128
  send_timeout 600;
129
129
  }
130
130
 
131
+ location ^~ {{settingsAssetsPath}} {
132
+ alias {{cwd}}/node_modules/@nocobase/app/dist/client/settings/assets/;
133
+ expires 365d;
134
+ add_header Cache-Control "public";
135
+ access_log off;
136
+ autoindex off;
137
+ }
138
+
139
+ # The standalone Settings SPA is a Client V2 surface. Keep this matcher
140
+ # narrow so legacy /admin/settings routes continue to use the v1 HTML.
141
+ location ~ {{settingsDocumentPattern}} {
142
+ proxy_pass http://127.0.0.1:{{apiPort}};
143
+ proxy_http_version 1.1;
144
+ proxy_set_header Upgrade $http_upgrade;
145
+ proxy_set_header Connection 'upgrade';
146
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
147
+ proxy_set_header X-Forwarded-Proto $upstream_x_forwarded_proto;
148
+ proxy_set_header Host $final_host;
149
+ proxy_set_header Referer $http_referer;
150
+ proxy_set_header User-Agent $http_user_agent;
151
+ add_header Cache-Control 'no-cache, no-store';
152
+ proxy_cache_bypass $http_upgrade;
153
+ proxy_connect_timeout 600;
154
+ proxy_send_timeout 600;
155
+ proxy_read_timeout 600;
156
+ send_timeout 600;
157
+ }
158
+
131
159
  # RFC 8414 root-mounted discovery compatibility for path-based issuers/resources.
132
160
  location ~ ^/\.well-known/oauth-authorization-server/(.+)$ {
133
161
  rewrite ^/\.well-known/oauth-authorization-server/(.+)$ /$1/.well-known/oauth-authorization-server break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/cli-v1",
3
- "version": "2.2.0-alpha.11",
3
+ "version": "2.2.0-alpha.12",
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-alpha.11",
12
+ "@nocobase/cli": "2.2.0-alpha.12",
13
13
  "@nocobase/license-kit": "^0.3.8",
14
- "@nocobase/utils": "2.2.0-alpha.11",
14
+ "@nocobase/utils": "2.2.0-alpha.12",
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": "c9ff1f51e5c33bc6437b64eb779212be71b78f58"
36
+ "gitHead": "96ae69754a6398de63037ead3a4aad5e5d0ab918"
37
37
  }
@@ -0,0 +1,68 @@
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
+
16
+ const registerCreateNginxConf = require('../commands/create-nginx-conf');
17
+
18
+ describe('create-nginx-conf Settings SPA routing', () => {
19
+ const originalEnv = { ...process.env };
20
+ let storagePath;
21
+
22
+ afterEach(async () => {
23
+ process.env = { ...originalEnv };
24
+ if (storagePath) {
25
+ await fs.remove(storagePath);
26
+ storagePath = undefined;
27
+ }
28
+ });
29
+
30
+ async function renderConfig(appPublicPath) {
31
+ storagePath = await fs.mkdtemp(path.join(os.tmpdir(), 'nocobase-nginx-settings-'));
32
+ process.env.APP_PUBLIC_PATH = appPublicPath;
33
+ process.env.APP_MODERN_CLIENT_PREFIX = 'v';
34
+ process.env.APP_PORT = '13000';
35
+ process.env.STORAGE_PATH = storagePath;
36
+
37
+ let action;
38
+ registerCreateNginxConf({
39
+ command() {
40
+ return {
41
+ action(callback) {
42
+ action = callback;
43
+ },
44
+ };
45
+ },
46
+ });
47
+ await action();
48
+ return await fs.readFile(path.join(storagePath, 'nocobase.conf'), 'utf8');
49
+ }
50
+
51
+ test.each([
52
+ ['root mount', '/', '/settings/assets/', '^/settings(?:/|$)'],
53
+ ['custom public path', '/nocobase/', '/nocobase/settings/assets/', '^/nocobase/settings(?:/|$)'],
54
+ ])(
55
+ 'proxies Settings documents and caches Settings assets for %s',
56
+ async (_label, publicPath, assetsPath, routePattern) => {
57
+ const config = await renderConfig(publicPath);
58
+
59
+ expect(config).toContain(`location ^~ ${assetsPath} {`);
60
+ expect(config).toContain('dist/client/settings/assets/;');
61
+ expect(config).toContain('expires 365d;');
62
+ expect(config).toContain(`location ~ ${routePattern} {`);
63
+ expect(config).toContain('proxy_pass http://127.0.0.1:13000;');
64
+ expect(config).not.toContain('location ~ ^/admin/settings');
65
+ expect(config).toContain('try_files $uri $uri/ /index.html;');
66
+ },
67
+ );
68
+ });
@@ -9,7 +9,13 @@
9
9
 
10
10
  /* eslint-env jest */
11
11
 
12
- const { buildAppDevForwardArgs, forwardDevToAppDev, resolveDevRuntimeMode } = require('../commands/dev')._test;
12
+ const {
13
+ buildAppDevForwardArgs,
14
+ createSettingsDevProcessOptions,
15
+ forwardDevToAppDev,
16
+ resolveDevRuntimeMode,
17
+ resolveSettingsDevPort,
18
+ } = require('../commands/dev')._test;
13
19
 
14
20
  describe('cli-v1 dev command', () => {
15
21
  test('buildAppDevForwardArgs rewrites dev argv to app-dev while preserving extra args', () => {
@@ -46,6 +52,7 @@ describe('cli-v1 dev command', () => {
46
52
  useModernOnlyEntryMode: false,
47
53
  shouldRunClient: true,
48
54
  shouldRunClientV2: true,
55
+ shouldRunSettings: true,
49
56
  shouldRunServer: true,
50
57
  });
51
58
  });
@@ -55,6 +62,7 @@ describe('cli-v1 dev command', () => {
55
62
  useModernOnlyEntryMode: true,
56
63
  shouldRunClient: false,
57
64
  shouldRunClientV2: true,
65
+ shouldRunSettings: true,
58
66
  shouldRunServer: true,
59
67
  });
60
68
  });
@@ -64,7 +72,37 @@ describe('cli-v1 dev command', () => {
64
72
  useModernOnlyEntryMode: false,
65
73
  shouldRunClient: false,
66
74
  shouldRunClientV2: true,
75
+ shouldRunSettings: true,
67
76
  shouldRunServer: false,
68
77
  });
69
78
  });
79
+
80
+ test('resolveSettingsDevPort reserves APP_PORT + 3 by default', () => {
81
+ expect(resolveSettingsDevPort(13001)).toBe(13004);
82
+ });
83
+
84
+ test('createSettingsDevProcessOptions uses the standalone config and settings HMR path', () => {
85
+ expect(
86
+ createSettingsDevProcessOptions({
87
+ appPackageRoot: '/repo/packages/core/app',
88
+ appPort: 13001,
89
+ settingsPort: 13004,
90
+ browserPort: 13001,
91
+ appPublicPath: '/nocobase/',
92
+ processEnv: { API_BASE_URL: '/api/' },
93
+ }),
94
+ ).toMatchObject({
95
+ command: 'rsbuild',
96
+ args: ['dev', '--config', '/repo/packages/core/app/client-settings/rsbuild.config.ts'],
97
+ runOptions: {
98
+ prefix: 'client-settings',
99
+ env: {
100
+ APP_PORT: '13001',
101
+ APP_SETTINGS_PORT: '13004',
102
+ RSPACK_HMR_CLIENT_PORT: '13001',
103
+ RSPACK_HMR_PATH: '/nocobase/settings/__rspack_hmr',
104
+ },
105
+ },
106
+ });
107
+ });
70
108
  });
@@ -9,9 +9,14 @@
9
9
 
10
10
  /* eslint-env jest */
11
11
 
12
+ const { normalizeModernClientPrefix } = require('../util');
12
13
  const { colorizedDevLogEnv, createRunWithPrefixLabel } = require('../util')._test;
13
14
 
14
15
  describe('cli-v1 util helpers', () => {
16
+ test('normalizeModernClientPrefix reserves the Settings SPA path', () => {
17
+ expect(() => normalizeModernClientPrefix('/settings/')).toThrow('APP_MODERN_CLIENT_PREFIX "settings" is reserved');
18
+ });
19
+
15
20
  test('colorizedDevLogEnv enables color for dev child output by default', () => {
16
21
  expect(colorizedDevLogEnv({})).toEqual({ FORCE_COLOR: '1' });
17
22
  });
@@ -11,6 +11,10 @@ const { resolve, posix } = require('path');
11
11
  const { storagePathJoin, resolvePublicPath, resolveV2PublicPath, normalizeModernClientPrefix } = require('../util');
12
12
  const { readFileSync, writeFileSync } = require('fs');
13
13
 
14
+ function escapeRegExp(value) {
15
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
16
+ }
17
+
14
18
  /**
15
19
  *
16
20
  * @param {Command} cli
@@ -24,6 +28,8 @@ module.exports = (cli) => {
24
28
  const modernClientPrefix = normalizeModernClientPrefix(process.env.APP_MODERN_CLIENT_PREFIX);
25
29
  const appPublicPathWithoutTrailingSlash = appPublicPath.replace(/\/$/, '');
26
30
  const v2PublicPathWithoutTrailingSlash = v2PublicPath.replace(/\/$/, '');
31
+ const settingsAssetsPath = `${appPublicPath}settings/assets/`;
32
+ const settingsDocumentPattern = `^${escapeRegExp(appPublicPath)}settings(?:/|$)`;
27
33
  const file = resolve(__dirname, '../../nocobase.conf.tpl');
28
34
  const data = readFileSync(file, 'utf-8');
29
35
  let otherLocation = '';
@@ -64,6 +70,8 @@ module.exports = (cli) => {
64
70
  .replace(/\{\{distPath\}\}/g, distPath)
65
71
  .replace(/\{\{v2PublicPath\}\}/g, v2PublicPath)
66
72
  .replace(/\{\{v2PublicPathNoTrailingSlash\}\}/g, v2PublicPathWithoutTrailingSlash)
73
+ .replace(/\{\{settingsAssetsPath\}\}/g, settingsAssetsPath)
74
+ .replace(/\{\{settingsDocumentPattern\}\}/g, settingsDocumentPattern)
67
75
  .replace(/\{\{apiPort\}\}/g, process.env.APP_PORT)
68
76
  .replace(/\{\{otherLocation\}\}/g, otherLocation);
69
77
  const targetFile = storagePathJoin('nocobase.conf');
@@ -48,6 +48,48 @@ function buildAppDevForwardArgs(argv = process.argv) {
48
48
  return ['app-dev', ...argv.slice(3)];
49
49
  }
50
50
 
51
+ function resolveSettingsDevPort(appPort) {
52
+ return Number(appPort) + 3;
53
+ }
54
+
55
+ function normalizePublicPath(value) {
56
+ let normalized = value || '/';
57
+ if (!normalized.startsWith('/')) {
58
+ normalized = `/${normalized}`;
59
+ }
60
+ if (!normalized.endsWith('/')) {
61
+ normalized = `${normalized}/`;
62
+ }
63
+ return normalized.replace(/\/{2,}/g, '/');
64
+ }
65
+
66
+ function createSettingsDevProcessOptions({
67
+ appPackageRoot,
68
+ appPort,
69
+ settingsPort,
70
+ browserPort,
71
+ appPublicPath,
72
+ processEnv = process.env,
73
+ }) {
74
+ const settingsHmrPath = `${normalizePublicPath(appPublicPath)}settings/__rspack_hmr`;
75
+ return {
76
+ command: 'rsbuild',
77
+ args: ['dev', '--config', `${appPackageRoot}/client-settings/rsbuild.config.ts`],
78
+ runOptions: {
79
+ prefix: 'client-settings',
80
+ color: 'yellow',
81
+ env: {
82
+ ...processEnv,
83
+ APP_PORT: `${appPort}`,
84
+ APP_SETTINGS_PORT: `${settingsPort}`,
85
+ NODE_ENV: 'development',
86
+ RSPACK_HMR_CLIENT_PORT: `${browserPort}`,
87
+ RSPACK_HMR_PATH: settingsHmrPath,
88
+ },
89
+ },
90
+ };
91
+ }
92
+
51
93
  function resolveDevRuntimeMode(opts = {}) {
52
94
  const appClientEntryMode = opts.appClientEntryMode || resolveAppClientEntryMode();
53
95
  const useModernOnlyEntryMode = appClientEntryMode === 'modern-only';
@@ -57,6 +99,7 @@ function resolveDevRuntimeMode(opts = {}) {
57
99
  const shouldRunClientV2 = clientV2Only || useModernOnlyEntryMode || forceClient || !forceServer;
58
100
  const shouldRunClient = !clientV2Only && !useModernOnlyEntryMode && (forceClient || !forceServer);
59
101
  const shouldRunServer = !clientV2Only && (forceServer || !forceClient || useModernOnlyEntryMode);
102
+ const shouldRunSettings = shouldRunClientV2;
60
103
 
61
104
  return {
62
105
  appClientEntryMode,
@@ -64,6 +107,7 @@ function resolveDevRuntimeMode(opts = {}) {
64
107
  shouldRunClientV2,
65
108
  shouldRunClient,
66
109
  shouldRunServer,
110
+ shouldRunSettings,
67
111
  };
68
112
  }
69
113
 
@@ -124,13 +168,18 @@ module.exports = (cli) => {
124
168
  let clientPort = APP_PORT;
125
169
  let serverPort;
126
170
  let clientV2Port = APP_PORT;
171
+ let settingsPort = resolveSettingsDevPort(APP_PORT);
127
172
 
128
173
  nodeCheck();
129
174
  await postCheck(opts);
130
175
 
131
- const { useModernOnlyEntryMode, shouldRunClientV2, shouldRunClient, shouldRunServer } = resolveDevRuntimeMode(
132
- opts,
133
- );
176
+ const {
177
+ useModernOnlyEntryMode,
178
+ shouldRunClientV2,
179
+ shouldRunClient,
180
+ shouldRunServer,
181
+ shouldRunSettings,
182
+ } = resolveDevRuntimeMode(opts);
134
183
  const shouldRunClientWithRsbuild = shouldRunClient && !!rsbuild;
135
184
 
136
185
  if (shouldRunServer && server) {
@@ -151,8 +200,15 @@ module.exports = (cli) => {
151
200
  clientV2Port = APP_PORT;
152
201
  }
153
202
 
203
+ if (shouldRunSettings) {
204
+ settingsPort = await getPortPromise({
205
+ port: resolveSettingsDevPort(APP_PORT),
206
+ });
207
+ }
208
+
154
209
  let subprocessClient;
155
210
  let subprocessClientV2;
211
+ let subprocessSettings;
156
212
 
157
213
  const runDevClientV2 = () => {
158
214
  console.log('starting client-v2', 1 * clientV2Port);
@@ -165,6 +221,7 @@ module.exports = (cli) => {
165
221
  env: {
166
222
  ...process.env,
167
223
  APP_V2_PORT: `${clientV2Port}`,
224
+ APP_SETTINGS_PORT: `${settingsPort}`,
168
225
  NODE_ENV: 'development',
169
226
  RSPACK_HMR_CLIENT_PORT: `${clientV2Only ? clientV2Port : clientPort}`,
170
227
  API_BASE_URL: process.env.API_BASE_URL || process.env.API_BASE_PATH,
@@ -182,8 +239,34 @@ module.exports = (cli) => {
182
239
  );
183
240
  };
184
241
 
242
+ const runDevSettings = () => {
243
+ console.log('starting client-settings', 1 * settingsPort);
244
+ const { command, args, runOptions } = createSettingsDevProcessOptions({
245
+ appPackageRoot: APP_PACKAGE_ROOT,
246
+ appPort: APP_PORT,
247
+ settingsPort,
248
+ browserPort: clientV2Only ? clientV2Port : clientPort,
249
+ appPublicPath: process.env.APP_PUBLIC_PATH,
250
+ processEnv: {
251
+ ...process.env,
252
+ API_BASE_URL: process.env.API_BASE_URL || process.env.API_BASE_PATH,
253
+ API_CLIENT_STORAGE_PREFIX: process.env.API_CLIENT_STORAGE_PREFIX,
254
+ API_CLIENT_STORAGE_TYPE: process.env.API_CLIENT_STORAGE_TYPE,
255
+ API_CLIENT_SHARE_TOKEN: process.env.API_CLIENT_SHARE_TOKEN || 'false',
256
+ WEBSOCKET_URL: process.env.WEBSOCKET_URL || buildWSURL(process.env.API_BASE_URL, serverPort),
257
+ WS_PATH: process.env.WS_PATH,
258
+ ESM_CDN_BASE_URL: process.env.ESM_CDN_BASE_URL || 'https://esm.sh',
259
+ ESM_CDN_SUFFIX: process.env.ESM_CDN_SUFFIX || '',
260
+ PROXY_TARGET_URL:
261
+ process.env.PROXY_TARGET_URL || (serverPort ? `http://127.0.0.1:${serverPort}` : undefined),
262
+ },
263
+ });
264
+ subprocessSettings = runWithPrefix(command, args, runOptions);
265
+ };
266
+
185
267
  if (clientV2Only) {
186
268
  runDevClientV2();
269
+ runDevSettings();
187
270
  return;
188
271
  }
189
272
 
@@ -204,6 +287,7 @@ module.exports = (cli) => {
204
287
  APP_PORT: `${clientPort}`,
205
288
  APP_ROOT: `${APP_PACKAGE_ROOT}/client`,
206
289
  APP_V2_PORT: `${clientV2Port}`,
290
+ APP_SETTINGS_PORT: `${settingsPort}`,
207
291
  NODE_ENV: 'development',
208
292
  RSPACK_HMR_CLIENT_PORT: `${clientPort}`,
209
293
  API_BASE_URL: process.env.API_BASE_URL || process.env.API_BASE_PATH,
@@ -256,6 +340,9 @@ module.exports = (cli) => {
256
340
  if (shouldRunClientV2) {
257
341
  await restartSubprocess(subprocessClientV2, clientV2Port, runDevClientV2);
258
342
  }
343
+ if (shouldRunSettings) {
344
+ await restartSubprocess(subprocessSettings, settingsPort, runDevSettings);
345
+ }
259
346
  await fs.promises.writeFile(process.env.WATCH_FILE, `export const watchId = '${uid()}';`, 'utf-8');
260
347
  }, 500);
261
348
 
@@ -325,11 +412,17 @@ module.exports = (cli) => {
325
412
  if (shouldRunClientV2) {
326
413
  runDevClientV2();
327
414
  }
415
+
416
+ if (shouldRunSettings) {
417
+ runDevSettings();
418
+ }
328
419
  });
329
420
  };
330
421
 
331
422
  module.exports._test = {
332
423
  buildAppDevForwardArgs,
424
+ createSettingsDevProcessOptions,
333
425
  forwardDevToAppDev,
334
426
  resolveDevRuntimeMode,
427
+ resolveSettingsDevPort,
335
428
  };
package/src/util.js CHANGED
@@ -462,7 +462,11 @@ function normalizeModernClientPrefix(value) {
462
462
  const segment = String(value || '')
463
463
  .trim()
464
464
  .replace(/^\/+|\/+$/g, '');
465
- return segment || DEFAULT_MODERN_CLIENT_PREFIX;
465
+ const normalized = segment || DEFAULT_MODERN_CLIENT_PREFIX;
466
+ if (normalized === 'settings') {
467
+ throw new Error('APP_MODERN_CLIENT_PREFIX "settings" is reserved for the standalone Settings application.');
468
+ }
469
+ return normalized;
466
470
  }
467
471
 
468
472
  exports.normalizeModernClientPrefix = normalizeModernClientPrefix;