@nocobase/cli-v1 2.2.0-alpha.1 → 2.2.0-alpha.10
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 +70 -1
- package/package.json +5 -5
- package/src/__tests__/app-dev-utils.test.js +1 -0
- package/src/__tests__/build-index-html.test.js +34 -0
- package/src/__tests__/dev-command.test.js +70 -0
- package/src/__tests__/util.test.js +33 -0
- package/src/commands/app-dev-utils.js +1 -1
- package/src/commands/create-nginx-conf.js +15 -0
- package/src/commands/dev.js +55 -11
- package/src/commands/update-deps.js +8 -0
- package/src/util.js +74 -5
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,7 +167,72 @@ 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
|
+
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
location ^~ {{publicPath}}x/apps/ {
|
|
190
|
+
absolute_redirect off;
|
|
191
|
+
|
|
192
|
+
if ($uri ~ ^{{publicPath}}x/apps/(?<subapp>[A-Za-z0-9_-]+)/(?<portal>[A-Za-z0-9_-]+)$) {
|
|
193
|
+
return 308 {{publicPath}}x/apps/$subapp/$portal/$is_args$args;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if ($uri !~ ^{{publicPath}}x/apps/(?<subapp>[A-Za-z0-9_-]+)/(?<portal>[A-Za-z0-9_-]+)/(?<portal_path>.*)$) {
|
|
197
|
+
return 404;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
root {{cwd}}/storage;
|
|
201
|
+
|
|
202
|
+
if ($portal_path = "") {
|
|
203
|
+
rewrite ^ /portals/$subapp/$portal/dist/index.html break;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
try_files
|
|
207
|
+
/portals/$subapp/$portal/dist/$portal_path
|
|
208
|
+
/portals/$subapp/$portal/dist/$portal_path/
|
|
209
|
+
/portals/$subapp/$portal/dist/index.html
|
|
210
|
+
=404;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
location ^~ {{publicPath}}x/ {
|
|
214
|
+
absolute_redirect off;
|
|
166
215
|
|
|
216
|
+
if ($uri ~ ^{{publicPath}}x/(?<portal>[A-Za-z0-9_-]+)$) {
|
|
217
|
+
return 308 {{publicPath}}x/$portal/$is_args$args;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if ($uri !~ ^{{publicPath}}x/(?<portal>[A-Za-z0-9_-]+)/(?<portal_path>.*)$) {
|
|
221
|
+
return 404;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
root {{cwd}}/storage;
|
|
225
|
+
|
|
226
|
+
if ($portal_path = "") {
|
|
227
|
+
rewrite ^ /portals/main/$portal/dist/index.html break;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
try_files
|
|
231
|
+
/portals/main/$portal/dist/$portal_path
|
|
232
|
+
/portals/main/$portal/dist/$portal_path/
|
|
233
|
+
/portals/main/$portal/dist/index.html
|
|
234
|
+
=404;
|
|
235
|
+
}
|
|
167
236
|
|
|
168
237
|
location {{publicPath}} {
|
|
169
238
|
alias {{cwd}}/node_modules/@nocobase/app/dist/client/;
|
package/package.json
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/cli-v1",
|
|
3
|
-
"version": "2.2.0-alpha.
|
|
3
|
+
"version": "2.2.0-alpha.10",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"bin": {
|
|
8
|
+
"nocobase": "./bin/index.js",
|
|
8
9
|
"nocobase-v1": "./bin/index.js"
|
|
9
10
|
},
|
|
10
11
|
"dependencies": {
|
|
11
|
-
"@nocobase/cli": "2.2.0-alpha.
|
|
12
|
+
"@nocobase/cli": "2.2.0-alpha.10",
|
|
12
13
|
"@nocobase/license-kit": "^0.3.8",
|
|
13
|
-
"@nocobase/utils": "2.2.0-alpha.
|
|
14
|
+
"@nocobase/utils": "2.2.0-alpha.10",
|
|
14
15
|
"chalk": "^4.1.1",
|
|
15
16
|
"commander": "^9.2.0",
|
|
16
17
|
"deepmerge": "^4.3.1",
|
|
@@ -25,7 +26,6 @@
|
|
|
25
26
|
"tree-kill": "^1.2.2"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
28
|
-
"@nocobase/devtools": "2.2.0-alpha.1",
|
|
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": "
|
|
36
|
+
"gitHead": "b5a3f131bb0a4a2fbfd96852896bccadc933d834"
|
|
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
|
});
|
|
@@ -0,0 +1,70 @@
|
|
|
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 { buildAppDevForwardArgs, forwardDevToAppDev, resolveDevRuntimeMode } = require('../commands/dev')._test;
|
|
13
|
+
|
|
14
|
+
describe('cli-v1 dev command', () => {
|
|
15
|
+
test('buildAppDevForwardArgs rewrites dev argv to app-dev while preserving extra args', () => {
|
|
16
|
+
expect(
|
|
17
|
+
buildAppDevForwardArgs([
|
|
18
|
+
'node',
|
|
19
|
+
'nocobase-v1',
|
|
20
|
+
'dev',
|
|
21
|
+
'--rsbuild',
|
|
22
|
+
'--quickstart',
|
|
23
|
+
'--port',
|
|
24
|
+
'13000',
|
|
25
|
+
'--inspect=9229',
|
|
26
|
+
]),
|
|
27
|
+
).toEqual(['app-dev', '--rsbuild', '--quickstart', '--port', '13000', '--inspect=9229']);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('forwardDevToAppDev delegates to nocobase-v1 app-dev for create-app projects', async () => {
|
|
31
|
+
const calls = [];
|
|
32
|
+
const runCommand = async (...args) => {
|
|
33
|
+
calls.push(args);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
await forwardDevToAppDev({
|
|
37
|
+
argv: ['node', 'nocobase-v1', 'dev', '--port', '13000', '--db-sync'],
|
|
38
|
+
runCommand,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
expect(calls).toEqual([['nocobase-v1', ['app-dev', '--port', '13000', '--db-sync']]]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('resolveDevRuntimeMode keeps both clients in legacy-default', () => {
|
|
45
|
+
expect(resolveDevRuntimeMode({ appClientEntryMode: 'legacy-default' })).toMatchObject({
|
|
46
|
+
useModernOnlyEntryMode: false,
|
|
47
|
+
shouldRunClient: true,
|
|
48
|
+
shouldRunClientV2: true,
|
|
49
|
+
shouldRunServer: true,
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('resolveDevRuntimeMode only runs v2 client and server in modern-only', () => {
|
|
54
|
+
expect(resolveDevRuntimeMode({ appClientEntryMode: 'modern-only' })).toMatchObject({
|
|
55
|
+
useModernOnlyEntryMode: true,
|
|
56
|
+
shouldRunClient: false,
|
|
57
|
+
shouldRunClientV2: true,
|
|
58
|
+
shouldRunServer: true,
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('resolveDevRuntimeMode preserves explicit client-v2-only flag behavior', () => {
|
|
63
|
+
expect(resolveDevRuntimeMode({ clientV2Only: true, appClientEntryMode: 'legacy-default' })).toMatchObject({
|
|
64
|
+
useModernOnlyEntryMode: false,
|
|
65
|
+
shouldRunClient: false,
|
|
66
|
+
shouldRunClientV2: true,
|
|
67
|
+
shouldRunServer: false,
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
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 { colorizedDevLogEnv, createRunWithPrefixLabel } = require('../util')._test;
|
|
13
|
+
|
|
14
|
+
describe('cli-v1 util helpers', () => {
|
|
15
|
+
test('colorizedDevLogEnv enables color for dev child output by default', () => {
|
|
16
|
+
expect(colorizedDevLogEnv({})).toEqual({ FORCE_COLOR: '1' });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('colorizedDevLogEnv keeps explicit FORCE_COLOR', () => {
|
|
20
|
+
expect(colorizedDevLogEnv({ FORCE_COLOR: '3' })).toEqual({ FORCE_COLOR: '3' });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('colorizedDevLogEnv removes inherited NO_COLOR so dev logs stay colored', () => {
|
|
24
|
+
expect(colorizedDevLogEnv({ NO_COLOR: '1', TERM: 'dumb' })).toEqual({
|
|
25
|
+
FORCE_COLOR: '1',
|
|
26
|
+
TERM: 'dumb',
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('createRunWithPrefixLabel colorizes prefixed output even when the parent disables colors', () => {
|
|
31
|
+
expect(createRunWithPrefixLabel('client', 'cyan')).toBe('\u001b[36m[client]\u001b[39m');
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -150,7 +150,7 @@ function buildAppDevServerArgs({
|
|
|
150
150
|
argv = process.argv,
|
|
151
151
|
serverTsconfigPath = process.env.SERVER_TSCONFIG_PATH,
|
|
152
152
|
} = {}) {
|
|
153
|
-
const args = ['watch', `--ignore=${resolvePluginStoragePath()}/**`];
|
|
153
|
+
const args = ['watch', '--clear-screen=false', `--ignore=${resolvePluginStoragePath()}/**`];
|
|
154
154
|
|
|
155
155
|
if (serverTsconfigPath) {
|
|
156
156
|
args.push('--tsconfig', serverTsconfigPath);
|
|
@@ -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;
|
package/src/commands/dev.js
CHANGED
|
@@ -10,14 +10,17 @@ const _ = require('lodash');
|
|
|
10
10
|
const { Command } = require('commander');
|
|
11
11
|
const {
|
|
12
12
|
generatePlugins,
|
|
13
|
+
hasCorePackages,
|
|
13
14
|
run,
|
|
14
15
|
runWithPrefix,
|
|
16
|
+
colorizedDevLogEnv,
|
|
15
17
|
postCheck,
|
|
16
18
|
nodeCheck,
|
|
17
19
|
promptForTs,
|
|
18
20
|
isPortReachable,
|
|
19
21
|
buildWSURL,
|
|
20
22
|
checkDBDialect,
|
|
23
|
+
resolveAppClientEntryMode,
|
|
21
24
|
} = require('../util');
|
|
22
25
|
const { getPortPromise } = require('portfinder');
|
|
23
26
|
const chokidar = require('chokidar');
|
|
@@ -41,6 +44,33 @@ async function buildBundleStatusHtml() {
|
|
|
41
44
|
);
|
|
42
45
|
}
|
|
43
46
|
|
|
47
|
+
function buildAppDevForwardArgs(argv = process.argv) {
|
|
48
|
+
return ['app-dev', ...argv.slice(3)];
|
|
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
|
+
|
|
70
|
+
async function forwardDevToAppDev({ argv = process.argv, runCommand = run } = {}) {
|
|
71
|
+
await runCommand('nocobase-v1', buildAppDevForwardArgs(argv));
|
|
72
|
+
}
|
|
73
|
+
|
|
44
74
|
/**
|
|
45
75
|
*
|
|
46
76
|
* @param {Command} cli
|
|
@@ -53,11 +83,17 @@ module.exports = (cli) => {
|
|
|
53
83
|
.option('-c, --client')
|
|
54
84
|
.option('-s, --server')
|
|
55
85
|
.option('--db-sync')
|
|
86
|
+
.option('--quickstart')
|
|
56
87
|
.option('--rsbuild')
|
|
57
88
|
.option('--client-v2-only')
|
|
58
89
|
.option('-i, --inspect [port]')
|
|
59
90
|
.allowUnknownOption()
|
|
60
91
|
.action(async (opts) => {
|
|
92
|
+
if (!hasCorePackages()) {
|
|
93
|
+
await forwardDevToAppDev();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
61
97
|
checkDBDialect();
|
|
62
98
|
await buildBundleStatusHtml();
|
|
63
99
|
|
|
@@ -92,9 +128,9 @@ module.exports = (cli) => {
|
|
|
92
128
|
nodeCheck();
|
|
93
129
|
await postCheck(opts);
|
|
94
130
|
|
|
95
|
-
const shouldRunClientV2
|
|
96
|
-
|
|
97
|
-
|
|
131
|
+
const { useModernOnlyEntryMode, shouldRunClientV2, shouldRunClient, shouldRunServer } = resolveDevRuntimeMode(
|
|
132
|
+
opts,
|
|
133
|
+
);
|
|
98
134
|
const shouldRunClientWithRsbuild = shouldRunClient && !!rsbuild;
|
|
99
135
|
|
|
100
136
|
if (shouldRunServer && server) {
|
|
@@ -105,12 +141,16 @@ module.exports = (cli) => {
|
|
|
105
141
|
});
|
|
106
142
|
}
|
|
107
143
|
|
|
108
|
-
if (shouldRunClientV2 && !clientV2Only) {
|
|
144
|
+
if (shouldRunClientV2 && !clientV2Only && !useModernOnlyEntryMode) {
|
|
109
145
|
clientV2Port = await getPortPromise({
|
|
110
146
|
port: 1 * clientPort + 2,
|
|
111
147
|
});
|
|
112
148
|
}
|
|
113
149
|
|
|
150
|
+
if (useModernOnlyEntryMode) {
|
|
151
|
+
clientV2Port = APP_PORT;
|
|
152
|
+
}
|
|
153
|
+
|
|
114
154
|
let subprocessClient;
|
|
115
155
|
let subprocessClientV2;
|
|
116
156
|
|
|
@@ -184,16 +224,13 @@ module.exports = (cli) => {
|
|
|
184
224
|
}
|
|
185
225
|
subprocessRef.cancel();
|
|
186
226
|
let i = 0;
|
|
187
|
-
while (
|
|
227
|
+
while (i <= 10) {
|
|
188
228
|
++i;
|
|
189
229
|
const result = await isPortReachable(port);
|
|
190
230
|
if (!result) {
|
|
191
231
|
break;
|
|
192
232
|
}
|
|
193
233
|
await sleep(500);
|
|
194
|
-
if (i > 10) {
|
|
195
|
-
break;
|
|
196
|
-
}
|
|
197
234
|
}
|
|
198
235
|
start();
|
|
199
236
|
};
|
|
@@ -202,7 +239,7 @@ module.exports = (cli) => {
|
|
|
202
239
|
const storagePluginPath = resolvePluginStoragePath();
|
|
203
240
|
const watcher = chokidar.watch(`${storagePluginPath}/**/*`, {
|
|
204
241
|
cwd: process.cwd(),
|
|
205
|
-
ignored: /(^|[
|
|
242
|
+
ignored: /(^|[/\\])\../, // 忽略隐藏文件
|
|
206
243
|
persistent: true,
|
|
207
244
|
depth: 1, // 只监听第一层目录
|
|
208
245
|
});
|
|
@@ -246,6 +283,7 @@ module.exports = (cli) => {
|
|
|
246
283
|
|
|
247
284
|
const argv = [
|
|
248
285
|
'watch',
|
|
286
|
+
'--clear-screen=false',
|
|
249
287
|
...(inspect ? [`--inspect=${inspect === true ? 9229 : inspect}`] : []),
|
|
250
288
|
`--ignore=${resolvePluginStoragePath()}/**`,
|
|
251
289
|
'--tsconfig',
|
|
@@ -264,9 +302,9 @@ module.exports = (cli) => {
|
|
|
264
302
|
|
|
265
303
|
const runDevServer = () => {
|
|
266
304
|
run('tsx', argv, {
|
|
267
|
-
env: {
|
|
305
|
+
env: colorizedDevLogEnv(process.env, {
|
|
268
306
|
APP_PORT: serverPort,
|
|
269
|
-
},
|
|
307
|
+
}),
|
|
270
308
|
}).catch((err) => {
|
|
271
309
|
if (err.exitCode == 100) {
|
|
272
310
|
console.log('Restarting server...');
|
|
@@ -289,3 +327,9 @@ module.exports = (cli) => {
|
|
|
289
327
|
}
|
|
290
328
|
});
|
|
291
329
|
};
|
|
330
|
+
|
|
331
|
+
module.exports._test = {
|
|
332
|
+
buildAppDevForwardArgs,
|
|
333
|
+
forwardDevToAppDev,
|
|
334
|
+
resolveDevRuntimeMode,
|
|
335
|
+
};
|
|
@@ -68,6 +68,14 @@ module.exports = (cli) => {
|
|
|
68
68
|
if (descJson['dependencies']?.['@nocobase/app']) {
|
|
69
69
|
descJson['dependencies']['@nocobase/app'] = stdout;
|
|
70
70
|
}
|
|
71
|
+
descJson['scripts']['dev'] = 'nocobase app-dev';
|
|
72
|
+
if (descJson['dependencies']?.['@nocobase/cli']) {
|
|
73
|
+
descJson['dependencies']['@nocobase/app'] = stdout;
|
|
74
|
+
console.log(
|
|
75
|
+
chalk.yellow('The @nocobase/cli package is no longer needed, it will be removed from dependencies.'),
|
|
76
|
+
);
|
|
77
|
+
delete descJson['dependencies']['@nocobase/cli'];
|
|
78
|
+
}
|
|
71
79
|
if (descJson['devDependencies']?.['@nocobase/devtools']) {
|
|
72
80
|
descJson['devDependencies']['@nocobase/devtools'] = stdout;
|
|
73
81
|
}
|
package/src/util.js
CHANGED
|
@@ -84,6 +84,24 @@ exports.run = (command, args, options = {}) => {
|
|
|
84
84
|
});
|
|
85
85
|
};
|
|
86
86
|
|
|
87
|
+
function colorizedDevLogEnv(env = process.env, overrides = {}) {
|
|
88
|
+
const nextEnv = {
|
|
89
|
+
...env,
|
|
90
|
+
...overrides,
|
|
91
|
+
};
|
|
92
|
+
delete nextEnv.NO_COLOR;
|
|
93
|
+
if (!nextEnv.FORCE_COLOR) {
|
|
94
|
+
nextEnv.FORCE_COLOR = '1';
|
|
95
|
+
}
|
|
96
|
+
return nextEnv;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function createRunWithPrefixLabel(prefix, color) {
|
|
100
|
+
const forcedChalk = new chalk.Instance({ level: 1 });
|
|
101
|
+
const colorize = forcedChalk[color] || forcedChalk.cyan;
|
|
102
|
+
return colorize(`[${prefix}]`);
|
|
103
|
+
}
|
|
104
|
+
|
|
87
105
|
exports.runWithPrefix = (command, args, options = {}) => {
|
|
88
106
|
if (command === 'tsx') {
|
|
89
107
|
command = 'node';
|
|
@@ -92,15 +110,12 @@ exports.runWithPrefix = (command, args, options = {}) => {
|
|
|
92
110
|
|
|
93
111
|
const prefix = options.prefix || 'process';
|
|
94
112
|
const color = options.color || 'cyan';
|
|
95
|
-
const label =
|
|
113
|
+
const label = createRunWithPrefixLabel(prefix, color);
|
|
96
114
|
const subprocess = execa(command, args, {
|
|
97
115
|
shell: true,
|
|
98
116
|
stdio: 'pipe',
|
|
99
117
|
...options,
|
|
100
|
-
env:
|
|
101
|
-
...process.env,
|
|
102
|
-
...options.env,
|
|
103
|
-
},
|
|
118
|
+
env: colorizedDevLogEnv(process.env, options.env),
|
|
104
119
|
});
|
|
105
120
|
|
|
106
121
|
const writePrefixed = (chunk, writer) => {
|
|
@@ -122,6 +137,13 @@ exports.runWithPrefix = (command, args, options = {}) => {
|
|
|
122
137
|
return subprocess;
|
|
123
138
|
};
|
|
124
139
|
|
|
140
|
+
exports._test = {
|
|
141
|
+
createRunWithPrefixLabel,
|
|
142
|
+
colorizedDevLogEnv,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
exports.colorizedDevLogEnv = colorizedDevLogEnv;
|
|
146
|
+
|
|
125
147
|
exports.isPortReachable = async (port, { timeout = 1000, host } = {}) => {
|
|
126
148
|
const promise = new Promise((resolve, reject) => {
|
|
127
149
|
const socket = new net.Socket();
|
|
@@ -410,6 +432,30 @@ const DEFAULT_MODERN_CLIENT_PREFIX = 'v';
|
|
|
410
432
|
|
|
411
433
|
exports.DEFAULT_MODERN_CLIENT_PREFIX = DEFAULT_MODERN_CLIENT_PREFIX;
|
|
412
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
|
+
|
|
413
459
|
// Normalize APP_MODERN_CLIENT_PREFIX (accepts `v`, `/v`, `/v/`)
|
|
414
460
|
// down to a bare segment like `v`.
|
|
415
461
|
function normalizeModernClientPrefix(value) {
|
|
@@ -433,7 +479,17 @@ function isAppDevHtml() {
|
|
|
433
479
|
return process.argv[2] === 'app-dev' || process.env.NOCOBASE_APP_DEV === 'true';
|
|
434
480
|
}
|
|
435
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
|
+
|
|
436
489
|
function buildIndexHtml(force = false) {
|
|
490
|
+
if (process.env.NOCOBASE_EXTRACT_CLIENT_ASSETS === 'true') {
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
437
493
|
const file = `${process.env.APP_PACKAGE_ROOT}/dist/client/index.html`;
|
|
438
494
|
if (!fs.existsSync(file)) {
|
|
439
495
|
return;
|
|
@@ -444,11 +500,15 @@ function buildIndexHtml(force = false) {
|
|
|
444
500
|
}
|
|
445
501
|
if (!fs.existsSync(tpl)) {
|
|
446
502
|
fs.copyFileSync(file, tpl);
|
|
503
|
+
} else if (shouldRefreshLegacyIndexTemplate(fs.readFileSync(tpl, 'utf-8'))) {
|
|
504
|
+
fs.copyFileSync(file, tpl);
|
|
447
505
|
}
|
|
448
506
|
const data = fs.readFileSync(tpl, 'utf-8');
|
|
449
507
|
let replacedData = data
|
|
450
508
|
.replace(/\{\{env.CDN_BASE_URL\}\}/g, process.env.CDN_BASE_URL)
|
|
451
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())
|
|
452
512
|
.replace(/\{\{env.API_CLIENT_SHARE_TOKEN\}\}/g, process.env.API_CLIENT_SHARE_TOKEN || 'false')
|
|
453
513
|
.replace(/\{\{env.API_CLIENT_STORAGE_TYPE\}\}/g, process.env.API_CLIENT_STORAGE_TYPE)
|
|
454
514
|
.replace(/\{\{env.API_CLIENT_STORAGE_PREFIX\}\}/g, process.env.API_CLIENT_STORAGE_PREFIX)
|
|
@@ -589,6 +649,7 @@ exports.initEnv = function initEnv() {
|
|
|
589
649
|
CDN_BASE_URL: '',
|
|
590
650
|
APP_PUBLIC_PATH: '/',
|
|
591
651
|
APP_MODERN_CLIENT_PREFIX: DEFAULT_MODERN_CLIENT_PREFIX,
|
|
652
|
+
APP_CLIENT_ENTRY_MODE: DEFAULT_APP_CLIENT_ENTRY_MODE,
|
|
592
653
|
ESM_CDN_BASE_URL: 'https://esm.sh',
|
|
593
654
|
ESM_CDN_SUFFIX: '',
|
|
594
655
|
};
|
|
@@ -640,6 +701,14 @@ exports.initEnv = function initEnv() {
|
|
|
640
701
|
}
|
|
641
702
|
}
|
|
642
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
|
+
|
|
643
712
|
if (!process.env.__env_modified__ && process.env.APP_PUBLIC_PATH) {
|
|
644
713
|
const publicPath = process.env.APP_PUBLIC_PATH.replace(/\/$/g, '');
|
|
645
714
|
const keys = ['API_BASE_PATH', 'WS_PATH', 'PLUGIN_STATICS_PATH'];
|