@nocobase/cli-v1 2.2.0-alpha.10 → 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 +28 -0
- package/package.json +4 -4
- package/src/__tests__/client-extract.test.js +55 -0
- package/src/__tests__/create-nginx-conf.test.js +68 -0
- package/src/__tests__/dev-command.test.js +39 -1
- package/src/__tests__/util.test.js +5 -0
- package/src/commands/client.js +98 -2
- package/src/commands/create-nginx-conf.js +8 -0
- package/src/commands/dev.js +96 -3
- package/src/util.js +5 -1
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.
|
|
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.
|
|
12
|
+
"@nocobase/cli": "2.2.0-alpha.12",
|
|
13
13
|
"@nocobase/license-kit": "^0.3.8",
|
|
14
|
-
"@nocobase/utils": "2.2.0-alpha.
|
|
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": "
|
|
36
|
+
"gitHead": "96ae69754a6398de63037ead3a4aad5e5d0ab918"
|
|
37
37
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
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 { renderExtractedClientIndexHtml } = require('../commands/client');
|
|
16
|
+
|
|
17
|
+
describe('cli-v1 client:extract', () => {
|
|
18
|
+
const originalEnv = { ...process.env };
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
process.env = { ...originalEnv };
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('renders extracted root index with the current client entry mode', () => {
|
|
25
|
+
const target = fs.mkdtempSync(path.join(os.tmpdir(), 'nocobase-client-extract-'));
|
|
26
|
+
fs.writeFileSync(
|
|
27
|
+
path.join(target, 'index.html.tpl'),
|
|
28
|
+
[
|
|
29
|
+
'<html><head>',
|
|
30
|
+
"<script>window['__webpack_public_path__'] = '{{env.CDN_BASE_URL}}';",
|
|
31
|
+
"window['__nocobase_public_path__'] = '{{env.APP_PUBLIC_PATH}}';",
|
|
32
|
+
"window['__nocobase_modern_client_prefix__'] = '{{env.APP_MODERN_CLIENT_PREFIX}}';",
|
|
33
|
+
"window['__nocobase_app_client_entry_mode__'] = '{{env.APP_CLIENT_ENTRY_MODE}}';</script>",
|
|
34
|
+
'<script src="{{env.APP_PUBLIC_PATH}}browser-checker.js?v=1"></script>',
|
|
35
|
+
'<script type="module" src="assets/runtime.js"></script>',
|
|
36
|
+
'</head><body></body></html>',
|
|
37
|
+
].join(''),
|
|
38
|
+
'utf-8',
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
process.env.APP_PUBLIC_PATH = '/';
|
|
42
|
+
process.env.APP_MODERN_CLIENT_PREFIX = 'v';
|
|
43
|
+
process.env.APP_CLIENT_ENTRY_MODE = 'modern-only';
|
|
44
|
+
delete process.env.CDN_BASE_URL;
|
|
45
|
+
|
|
46
|
+
expect(renderExtractedClientIndexHtml(target, '2.2.0-beta.15')).toBe(true);
|
|
47
|
+
|
|
48
|
+
const html = fs.readFileSync(path.join(target, 'index.html'), 'utf-8');
|
|
49
|
+
expect(html).toContain("window['__nocobase_app_client_entry_mode__'] = 'modern-only';");
|
|
50
|
+
expect(html).toContain("window['__webpack_public_path__'] = '/dist/2.2.0-beta.15/';");
|
|
51
|
+
expect(html).toContain('src="/dist/2.2.0-beta.15/browser-checker.js?v=1"');
|
|
52
|
+
expect(html).toContain('src="/dist/2.2.0-beta.15/assets/runtime.js"');
|
|
53
|
+
fs.removeSync(target);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -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 {
|
|
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
|
});
|
package/src/commands/client.js
CHANGED
|
@@ -11,7 +11,100 @@ const chalk = require('chalk');
|
|
|
11
11
|
const fs = require('fs-extra');
|
|
12
12
|
const { resolve } = require('path');
|
|
13
13
|
const { discoverPluginPackages } = require('@nocobase/utils/plugin-package');
|
|
14
|
-
const {
|
|
14
|
+
const {
|
|
15
|
+
normalizeModernClientPrefix,
|
|
16
|
+
resolveAppClientEntryMode,
|
|
17
|
+
resolvePublicPath,
|
|
18
|
+
storagePathJoin,
|
|
19
|
+
} = require('../util');
|
|
20
|
+
|
|
21
|
+
function replaceRuntimeEnvPlaceholders(data, values) {
|
|
22
|
+
return data
|
|
23
|
+
.replace(/\{\{env.CDN_BASE_URL\}\}/g, values.CDN_BASE_URL)
|
|
24
|
+
.replace(/\{\{env.APP_PUBLIC_PATH\}\}/g, values.APP_PUBLIC_PATH)
|
|
25
|
+
.replace(/\{\{env.APP_MODERN_CLIENT_PREFIX\}\}/g, values.APP_MODERN_CLIENT_PREFIX)
|
|
26
|
+
.replace(/\{\{env.APP_CLIENT_ENTRY_MODE\}\}/g, values.APP_CLIENT_ENTRY_MODE)
|
|
27
|
+
.replace(/\{\{env.API_CLIENT_SHARE_TOKEN\}\}/g, values.API_CLIENT_SHARE_TOKEN)
|
|
28
|
+
.replace(/\{\{env.API_CLIENT_STORAGE_TYPE\}\}/g, values.API_CLIENT_STORAGE_TYPE)
|
|
29
|
+
.replace(/\{\{env.API_CLIENT_STORAGE_PREFIX\}\}/g, values.API_CLIENT_STORAGE_PREFIX)
|
|
30
|
+
.replace(/\{\{env.API_BASE_URL\}\}/g, values.API_BASE_URL)
|
|
31
|
+
.replace(/\{\{env.WS_URL\}\}/g, values.WS_URL)
|
|
32
|
+
.replace(/\{\{env.WS_PATH\}\}/g, values.WS_PATH)
|
|
33
|
+
.replace(/\{\{env.NOCOBASE_APP_DEV\}\}/g, values.NOCOBASE_APP_DEV)
|
|
34
|
+
.replace(/\{\{env.ESM_CDN_BASE_URL\}\}/g, values.ESM_CDN_BASE_URL)
|
|
35
|
+
.replace(/\{\{env.ESM_CDN_SUFFIX\}\}/g, values.ESM_CDN_SUFFIX);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function replaceRuntimeAssignment(data, key, value) {
|
|
39
|
+
const literal =
|
|
40
|
+
typeof value === 'boolean' ? String(value) : `'${String(value).replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
|
|
41
|
+
const assignment = `window['${key}'] = ${literal};`;
|
|
42
|
+
const pattern = new RegExp(`window\\['${key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}'\\]\\s*=\\s*[^;]*;`);
|
|
43
|
+
|
|
44
|
+
return pattern.test(data) ? data.replace(pattern, assignment) : data;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function renderExtractedClientIndexHtml(target, version) {
|
|
48
|
+
const indexPath = resolve(target, 'index.html');
|
|
49
|
+
const tplPath = resolve(target, 'index.html.tpl');
|
|
50
|
+
if (!fs.existsSync(indexPath) && !fs.existsSync(tplPath)) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const appPublicPath = resolvePublicPath(process.env.APP_PUBLIC_PATH || '/');
|
|
55
|
+
const cdnBaseUrl =
|
|
56
|
+
process.env.CDN_BASE_URL || `${appPublicPath.replace(/\/$/, '')}/dist/${version}/`.replace(/^\/\//, '/');
|
|
57
|
+
const values = {
|
|
58
|
+
CDN_BASE_URL: cdnBaseUrl,
|
|
59
|
+
APP_PUBLIC_PATH: appPublicPath,
|
|
60
|
+
APP_MODERN_CLIENT_PREFIX: normalizeModernClientPrefix(process.env.APP_MODERN_CLIENT_PREFIX),
|
|
61
|
+
APP_CLIENT_ENTRY_MODE: resolveAppClientEntryMode(),
|
|
62
|
+
API_CLIENT_SHARE_TOKEN: process.env.API_CLIENT_SHARE_TOKEN || 'false',
|
|
63
|
+
API_CLIENT_STORAGE_TYPE: process.env.API_CLIENT_STORAGE_TYPE || 'localStorage',
|
|
64
|
+
API_CLIENT_STORAGE_PREFIX: process.env.API_CLIENT_STORAGE_PREFIX || 'NOCOBASE_',
|
|
65
|
+
API_BASE_URL: process.env.API_BASE_URL || process.env.API_BASE_PATH || '/api/',
|
|
66
|
+
WS_URL: process.env.WEBSOCKET_URL || '',
|
|
67
|
+
WS_PATH: process.env.WS_PATH || '/ws',
|
|
68
|
+
NOCOBASE_APP_DEV: 'false',
|
|
69
|
+
ESM_CDN_BASE_URL: process.env.ESM_CDN_BASE_URL || 'https://esm.sh',
|
|
70
|
+
ESM_CDN_SUFFIX: process.env.ESM_CDN_SUFFIX || '',
|
|
71
|
+
};
|
|
72
|
+
const sourcePath = fs.existsSync(tplPath) ? tplPath : indexPath;
|
|
73
|
+
let data = replaceRuntimeEnvPlaceholders(fs.readFileSync(sourcePath, 'utf-8'), values)
|
|
74
|
+
.replace(/((?:src|href)=")(?:\.\/)?assets\//g, `$1${values.APP_PUBLIC_PATH}assets/`)
|
|
75
|
+
.replace(/((?:src|href)=")\/assets\//g, `$1${values.APP_PUBLIC_PATH}assets/`)
|
|
76
|
+
.replace('src="/umi.', `src="${values.APP_PUBLIC_PATH}umi.`)
|
|
77
|
+
.replace(/((?:src|href)="[^"]*?)\/{2,}(assets\/)/g, '$1/$2');
|
|
78
|
+
|
|
79
|
+
if (values.CDN_BASE_URL) {
|
|
80
|
+
const appBaseUrl = values.CDN_BASE_URL.replace(/\/+$/, '');
|
|
81
|
+
const publicPath = values.APP_PUBLIC_PATH.replace(/\/+$/, '');
|
|
82
|
+
data = data
|
|
83
|
+
.replace(new RegExp(`src="${publicPath}/`, 'g'), `src="${appBaseUrl}/`)
|
|
84
|
+
.replace(new RegExp(`href="${publicPath}/`, 'g'), `href="${appBaseUrl}/`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
data = replaceRuntimeAssignment(data, '__webpack_public_path__', values.CDN_BASE_URL);
|
|
88
|
+
data = replaceRuntimeAssignment(data, '__nocobase_public_path__', values.APP_PUBLIC_PATH);
|
|
89
|
+
data = replaceRuntimeAssignment(data, '__nocobase_modern_client_prefix__', values.APP_MODERN_CLIENT_PREFIX);
|
|
90
|
+
data = replaceRuntimeAssignment(data, '__nocobase_app_client_entry_mode__', values.APP_CLIENT_ENTRY_MODE);
|
|
91
|
+
data = replaceRuntimeAssignment(data, '__nocobase_api_base_url__', values.API_BASE_URL);
|
|
92
|
+
data = replaceRuntimeAssignment(data, '__nocobase_api_client_storage_prefix__', values.API_CLIENT_STORAGE_PREFIX);
|
|
93
|
+
data = replaceRuntimeAssignment(data, '__nocobase_api_client_storage_type__', values.API_CLIENT_STORAGE_TYPE);
|
|
94
|
+
data = replaceRuntimeAssignment(
|
|
95
|
+
data,
|
|
96
|
+
'__nocobase_api_client_share_token__',
|
|
97
|
+
/^true$/i.test(values.API_CLIENT_SHARE_TOKEN),
|
|
98
|
+
);
|
|
99
|
+
data = replaceRuntimeAssignment(data, '__nocobase_ws_url__', values.WS_URL);
|
|
100
|
+
data = replaceRuntimeAssignment(data, '__nocobase_ws_path__', values.WS_PATH);
|
|
101
|
+
data = replaceRuntimeAssignment(data, '__nocobase_app_dev__', false);
|
|
102
|
+
data = replaceRuntimeAssignment(data, '__esm_cdn_base_url__', values.ESM_CDN_BASE_URL);
|
|
103
|
+
data = replaceRuntimeAssignment(data, '__esm_cdn_suffix__', values.ESM_CDN_SUFFIX);
|
|
104
|
+
|
|
105
|
+
fs.writeFileSync(indexPath, data, 'utf-8');
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
15
108
|
|
|
16
109
|
/**
|
|
17
110
|
* 复制主应用客户端文件
|
|
@@ -127,6 +220,7 @@ module.exports = (cli) => {
|
|
|
127
220
|
nodeModulesPath: resolve(process.cwd(), 'node_modules'),
|
|
128
221
|
});
|
|
129
222
|
const copiedMainClient = await copyMainClient(mainClientSource, target);
|
|
223
|
+
const renderedMainIndex = copiedMainClient ? renderExtractedClientIndexHtml(target, version) : false;
|
|
130
224
|
const copiedPluginBundles = await copyPluginClients(plugins, target);
|
|
131
225
|
const activeVersionFile = await writeActiveVersion(version);
|
|
132
226
|
|
|
@@ -145,7 +239,7 @@ module.exports = (cli) => {
|
|
|
145
239
|
chalk.green(
|
|
146
240
|
`Extracted client assets ${version} to ${target} (main client: ${
|
|
147
241
|
copiedMainClient ? 'yes' : 'no'
|
|
148
|
-
}, plugin bundles: ${copiedPluginBundles}).`,
|
|
242
|
+
}, rendered index: ${renderedMainIndex ? 'yes' : 'no'}, plugin bundles: ${copiedPluginBundles}).`,
|
|
149
243
|
),
|
|
150
244
|
);
|
|
151
245
|
});
|
|
@@ -198,3 +292,5 @@ module.exports = (cli) => {
|
|
|
198
292
|
}
|
|
199
293
|
});
|
|
200
294
|
};
|
|
295
|
+
|
|
296
|
+
module.exports.renderExtractedClientIndexHtml = renderExtractedClientIndexHtml;
|
|
@@ -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');
|
package/src/commands/dev.js
CHANGED
|
@@ -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 {
|
|
132
|
-
|
|
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
|
-
|
|
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;
|