@nocobase/app 3.0.0-alpha.3 → 3.0.0-alpha.4

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.
@@ -76,19 +76,19 @@ function runBrowserChecker(options: RunBrowserCheckerOptions) {
76
76
 
77
77
  describe('v2 browser checker', () => {
78
78
  it.each([undefined, 'modern-default', 'modern-only'] as const)(
79
- 'redirects the modern client root to Settings for entry mode %s',
79
+ 'lets the modern client handle its root for entry mode %s',
80
80
  (appClientEntryMode) => {
81
81
  expect(
82
82
  runBrowserChecker({
83
83
  appClientEntryMode,
84
84
  pathname: '/v/',
85
85
  }).replacements,
86
- ).toEqual(['https://example.test/settings']);
86
+ ).toEqual([]);
87
87
  },
88
88
  );
89
89
 
90
90
  it.each([undefined, 'modern-default', 'modern-only'] as const)(
91
- 'redirects scoped modern client roots to scoped Settings for entry mode %s',
91
+ 'lets scoped modern clients handle their roots for entry mode %s',
92
92
  (appClientEntryMode) => {
93
93
  expect(
94
94
  runBrowserChecker({
@@ -97,7 +97,7 @@ describe('v2 browser checker', () => {
97
97
  search: '?from=admin',
98
98
  hash: '#portal',
99
99
  }).replacements,
100
- ).toEqual(['https://example.test/settings/apps/demo?from=admin#portal']);
100
+ ).toEqual([]);
101
101
  },
102
102
  );
103
103
 
@@ -0,0 +1,34 @@
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
+ import { describe, expect, it } from 'vitest';
11
+ import { isClientDevProxyPath, rewriteClientDevProxyRootPath } from '../clientDevProxy';
12
+
13
+ describe('client dev proxy', () => {
14
+ it.each([
15
+ ['/x', '/x/', true],
16
+ ['/x/', '/x/', true],
17
+ ['/x/test2/assets/index.js', '/x/', true],
18
+ ['/x-other', '/x/', false],
19
+ ['/console/v?from=portal', '/console/v/', true],
20
+ ['/v', '/console/v/', false],
21
+ ])('matches complete client base paths: %s', (url, basePath, expected) => {
22
+ expect(isClientDevProxyPath(url, basePath)).toBe(expected);
23
+ });
24
+
25
+ it.each([
26
+ ['/v', '/v/', '/v/'],
27
+ ['/v?from=portal', '/v/', '/v/?from=portal'],
28
+ ['/console/v#section', '/console/v/', '/console/v/#section'],
29
+ ['/v/admin', '/v/', '/v/admin'],
30
+ ['/v/', '/v/', '/v/'],
31
+ ])('normalizes only a slashless client root: %s', (url, basePath, expected) => {
32
+ expect(rewriteClientDevProxyRootPath(url, basePath)).toBe(expected);
33
+ });
34
+ });
@@ -16,6 +16,7 @@ import { pluginNodePolyfill } from '@rsbuild/plugin-node-polyfill';
16
16
  import { pluginReact } from '@rsbuild/plugin-react';
17
17
  import { pluginSvgr } from '@rsbuild/plugin-svgr';
18
18
  import { generatePlugins, getRsbuildBrowserAlias } from '@nocobase/devtools/rsbuildConfig';
19
+ import { isClientDevProxyPath, rewriteClientDevProxyRootPath } from '../clientDevProxy';
19
20
  import { createSettingsDevProxyOptions } from '../settingsDevProxy';
20
21
 
21
22
  const __filename = fileURLToPath(import.meta.url);
@@ -259,17 +260,15 @@ export default defineConfig(({ command }) => {
259
260
  xfwd: true,
260
261
  },
261
262
  {
262
- context: v2BasePath,
263
+ context: (pathname) => isClientDevProxyPath(pathname, v2BasePath),
263
264
  target: `http://127.0.0.1:${v2Port}`,
264
265
  changeOrigin: true,
265
266
  ws: true,
266
- pathRewrite: {
267
- [`^${v2BasePath}`]: v2BasePath,
268
- },
267
+ pathRewrite: (pathname) => rewriteClientDevProxyRootPath(pathname, v2BasePath),
269
268
  xfwd: true,
270
269
  },
271
270
  {
272
- context: portalBasePath,
271
+ context: (pathname) => isClientDevProxyPath(pathname, portalBasePath),
273
272
  target: proxyTargetUrl,
274
273
  changeOrigin: true,
275
274
  ws: true,
@@ -17,46 +17,6 @@ function normalizePublicPath(value) {
17
17
  return ensureTrailingSlash(normalized);
18
18
  }
19
19
 
20
- function normalizePathname(value) {
21
- const normalized = ensureLeadingSlash(String(value || '/').trim() || '/').replace(/\/{2,}/g, '/');
22
- return normalized === '/' ? normalized : normalized.replace(/\/+$/g, '');
23
- }
24
-
25
- function resolveSettingsRootPath(publicPath, modernPrefix, pathname) {
26
- const normalizedPublicPath = normalizePublicPath(publicPath);
27
- const normalizedModernPrefix =
28
- String(modernPrefix || 'v')
29
- .trim()
30
- .replace(/^\/+|\/+$/g, '') || 'v';
31
- const modernPublicPathSuffix = `/${normalizedModernPrefix}/`;
32
- if (!normalizedPublicPath.endsWith(modernPublicPathSuffix)) {
33
- return null;
34
- }
35
-
36
- const modernRootPath = normalizePathname(normalizedPublicPath);
37
- const normalizedPathname = normalizePathname(pathname);
38
- let relativePath = '';
39
- if (normalizedPathname !== modernRootPath) {
40
- if (!normalizedPathname.startsWith(`${modernRootPath}/`)) {
41
- return null;
42
- }
43
- relativePath = normalizedPathname.slice(modernRootPath.length + 1);
44
- }
45
-
46
- let appScope = '';
47
- if (relativePath) {
48
- const match = /^(apps|_app)\/([^/]+)$/.exec(relativePath);
49
- if (!match) {
50
- return null;
51
- }
52
- appScope = `/${match[1]}/${match[2]}`;
53
- }
54
-
55
- const rootPublicPath = normalizedPublicPath.slice(0, -(normalizedModernPrefix.length + 1));
56
- const rootPrefix = rootPublicPath === '/' ? '' : rootPublicPath.replace(/\/+$/g, '');
57
- return `${rootPrefix}/settings${appScope}`;
58
- }
59
-
60
20
  function isSettingsBrowserCheckerScript(script) {
61
21
  const source = String((script && script.src) || '').split(/[?#]/)[0];
62
22
  return source.endsWith('/settings/browser-checker.js');
@@ -81,15 +41,11 @@ function resolveScopedSettingsRootPath(publicPath, pathname) {
81
41
  const basename = normalizePublicPath(window['__nocobase_public_path__'] || '/');
82
42
  const currentPath = ensureLeadingSlash(String(window.location.pathname || '/').trim() || '/').replace(/\/{2,}/g, '/');
83
43
  const basenameWithoutTrailingSlash = basename === '/' ? '/' : basename.replace(/\/+$/, '');
84
- const settingsRootPath = resolveSettingsRootPath(basename, window['__nocobase_modern_client_prefix__'], currentPath);
85
44
  const scopedSettingsRootPath = isSettingsBrowserCheckerScript(document.currentScript)
86
45
  ? resolveScopedSettingsRootPath(basename, currentPath)
87
46
  : null;
88
47
 
89
- if (settingsRootPath) {
90
- const newUrl = `${window.location.origin}${settingsRootPath}${window.location.search}${window.location.hash}`;
91
- window.location.replace(newUrl);
92
- } else if (scopedSettingsRootPath) {
48
+ if (scopedSettingsRootPath) {
93
49
  const newUrl = `${window.location.origin}${scopedSettingsRootPath}${window.location.search}${window.location.hash}`;
94
50
  window.location.replace(newUrl);
95
51
  } else if (basename !== '/' && currentPath === basenameWithoutTrailingSlash) {
@@ -15,6 +15,7 @@ import { pluginNodePolyfill } from '@rsbuild/plugin-node-polyfill';
15
15
  import { pluginReact } from '@rsbuild/plugin-react';
16
16
  import { pluginSvgr } from '@rsbuild/plugin-svgr';
17
17
  import { generateV2Plugins, getRsbuildBrowserAlias } from '@nocobase/devtools/rsbuildConfig';
18
+ import { isClientDevProxyPath, rewriteClientDevProxyRootPath } from '../clientDevProxy';
18
19
  import { createSettingsDevProxyOptions, isSettingsDevPath } from '../settingsDevProxy';
19
20
 
20
21
  const __filename = fileURLToPath(import.meta.url);
@@ -300,7 +301,7 @@ export default defineConfig(({ command }) => {
300
301
  changeOrigin: true,
301
302
  },
302
303
  {
303
- context: portalBasePath,
304
+ context: (pathname) => isClientDevProxyPath(pathname, portalBasePath),
304
305
  target: proxyTargetUrl,
305
306
  changeOrigin: true,
306
307
  ws: true,
@@ -324,11 +325,12 @@ export default defineConfig(({ command }) => {
324
325
  lazyCompilation: false,
325
326
  setupMiddlewares: [
326
327
  (middlewares) => {
327
- if (appClientEntryMode !== 'modern-only') {
328
- return;
329
- }
330
-
331
328
  middlewares.unshift((req, res, next) => {
329
+ req.url = rewriteClientDevProxyRootPath(String(req.url || '/'), v2PublicPath);
330
+ if (appClientEntryMode !== 'modern-only') {
331
+ next();
332
+ return;
333
+ }
332
334
  const [rawPathname = '/', query = ''] = String(req.url || '/').split('?');
333
335
  const pathname = normalizePathname(rawPathname);
334
336
  if (
@@ -337,7 +339,7 @@ export default defineConfig(({ command }) => {
337
339
  pathname.startsWith(apiBasePath) ||
338
340
  pathname.startsWith(wsBasePath) ||
339
341
  pathname.startsWith(localStorageBasePath) ||
340
- pathname.startsWith(portalBasePath) ||
342
+ isClientDevProxyPath(pathname, portalBasePath) ||
341
343
  pathname.startsWith(staticBasePath) ||
342
344
  isSettingsDevPath(pathname, appPublicPath)
343
345
  ) {
@@ -0,0 +1,32 @@
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
+ function normalizeBasePath(value: string) {
11
+ let normalized = value || '/';
12
+ if (!normalized.startsWith('/')) {
13
+ normalized = `/${normalized}`;
14
+ }
15
+ normalized = normalized.replace(/\/{2,}/g, '/');
16
+ return normalized === '/' ? normalized : normalized.replace(/\/+$/g, '');
17
+ }
18
+
19
+ export function isClientDevProxyPath(url: string, basePath: string) {
20
+ const [pathname] = String(url || '/').split(/[?#]/, 1);
21
+ const normalizedBasePath = normalizeBasePath(basePath);
22
+ return pathname === normalizedBasePath || pathname.startsWith(`${normalizedBasePath}/`);
23
+ }
24
+
25
+ export function rewriteClientDevProxyRootPath(url: string, basePath: string) {
26
+ const normalizedBasePath = normalizeBasePath(basePath);
27
+ const [pathname] = String(url || '/').split(/[?#]/, 1);
28
+ if (pathname !== normalizedBasePath || normalizedBasePath === '/') {
29
+ return url;
30
+ }
31
+ return `${normalizedBasePath}/${url.slice(pathname.length)}`;
32
+ }