@nocobase/cli-v1 2.1.0-alpha.37 → 2.1.0-alpha.39

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/cli-v1",
3
- "version": "2.1.0-alpha.37",
3
+ "version": "2.1.0-alpha.39",
4
4
  "description": "",
5
5
  "license": "Apache-2.0",
6
6
  "main": "./src/index.js",
@@ -8,9 +8,9 @@
8
8
  "nocobase-v1": "./bin/index.js"
9
9
  },
10
10
  "dependencies": {
11
- "@nocobase/cli": "2.1.0-alpha.37",
11
+ "@nocobase/cli": "2.1.0-alpha.39",
12
12
  "@nocobase/license-kit": "^0.3.8",
13
- "@nocobase/utils": "2.1.0-alpha.37",
13
+ "@nocobase/utils": "2.1.0-alpha.39",
14
14
  "chalk": "^4.1.1",
15
15
  "commander": "^9.2.0",
16
16
  "deepmerge": "^4.3.1",
@@ -25,7 +25,7 @@
25
25
  "tree-kill": "^1.2.2"
26
26
  },
27
27
  "devDependencies": {
28
- "@nocobase/devtools": "2.1.0-alpha.37",
28
+ "@nocobase/devtools": "2.1.0-alpha.39",
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": "8b45f4586ea5b386b376188cfc1012ec12e9bc8b"
36
+ "gitHead": "d06ed6b97030866c00e7ce40c9e1bcc773ebf12c"
37
37
  }
@@ -0,0 +1,121 @@
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 {
16
+ buildAppDevServerArgs,
17
+ createPluginClientExternals,
18
+ getPluginClientModuleIds,
19
+ shouldUseAppDevServerSource,
20
+ toPosixPath,
21
+ } = require('../commands/app-dev-utils');
22
+
23
+ describe('cli-v1 app-dev utils', () => {
24
+ test('toPosixPath normalizes Windows paths for generated browser imports', () => {
25
+ expect(toPosixPath('C:\\Users\\tester\\app\\packages\\plugins\\demo\\src\\client\\index.tsx')).toBe(
26
+ 'C:/Users/tester/app/packages/plugins/demo/src/client/index.tsx',
27
+ );
28
+ });
29
+
30
+ test('toPosixPath keeps POSIX paths unchanged', () => {
31
+ expect(toPosixPath('/Users/tester/app/packages/plugins/demo/src/client/index.tsx')).toBe(
32
+ '/Users/tester/app/packages/plugins/demo/src/client/index.tsx',
33
+ );
34
+ });
35
+
36
+ test('shouldUseAppDevServerSource requires app-dev and the app source entry', () => {
37
+ const appRoot = path.resolve('/app');
38
+ const appSourceEntry = path.resolve(appRoot, 'storage/.app-dev/src/index.ts');
39
+ const existsSync = (file) => file === appSourceEntry;
40
+
41
+ expect(
42
+ shouldUseAppDevServerSource({
43
+ cwd: appRoot,
44
+ env: {
45
+ APP_ENV: 'development',
46
+ APP_PACKAGE_ROOT: 'storage/.app-dev',
47
+ NOCOBASE_APP_DEV: 'true',
48
+ },
49
+ existsSync,
50
+ }),
51
+ ).toBe(true);
52
+ expect(
53
+ shouldUseAppDevServerSource({
54
+ cwd: appRoot,
55
+ env: {
56
+ APP_ENV: 'production',
57
+ APP_PACKAGE_ROOT: 'storage/.app-dev',
58
+ NOCOBASE_APP_DEV: 'true',
59
+ },
60
+ existsSync,
61
+ }),
62
+ ).toBe(false);
63
+ expect(
64
+ shouldUseAppDevServerSource({
65
+ cwd: appRoot,
66
+ env: {
67
+ APP_ENV: 'development',
68
+ APP_PACKAGE_ROOT: 'storage/.app-dev',
69
+ NOCOBASE_APP_DEV: '',
70
+ },
71
+ existsSync,
72
+ }),
73
+ ).toBe(false);
74
+ });
75
+
76
+ test('buildAppDevServerArgs runs the app source through tsx watch', () => {
77
+ expect(
78
+ buildAppDevServerArgs({
79
+ appPackageRoot: 'storage/.app-dev',
80
+ argv: ['node', 'nocobase-v1', 'start', '--launch-mode', 'direct'],
81
+ serverTsconfigPath: './tsconfig.server.json',
82
+ }),
83
+ ).toEqual([
84
+ 'watch',
85
+ `--ignore=${path.resolve(process.cwd(), 'storage/plugins')}/**`,
86
+ '--tsconfig',
87
+ './tsconfig.server.json',
88
+ '-r',
89
+ 'tsconfig-paths/register',
90
+ path.join('storage/.app-dev', 'src/index.ts'),
91
+ 'start',
92
+ '--launch-mode',
93
+ 'direct',
94
+ ]);
95
+ });
96
+
97
+ test('getPluginClientModuleIds discovers plugin client marker files', () => {
98
+ const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'nocobase-app-dev-modules-'));
99
+ const localPluginDir = path.join(cwd, 'packages/plugins/@nocobase/plugin-local');
100
+ const remotePluginDir = path.join(cwd, 'node_modules/@nocobase/plugin-remote');
101
+ fs.ensureDirSync(localPluginDir);
102
+ fs.ensureDirSync(remotePluginDir);
103
+ fs.writeJsonSync(path.join(localPluginDir, 'package.json'), { name: '@nocobase/plugin-local' });
104
+ fs.writeFileSync(path.join(localPluginDir, 'client.js'), '');
105
+ fs.writeJsonSync(path.join(remotePluginDir, 'package.json'), { name: '@nocobase/plugin-remote' });
106
+ fs.writeFileSync(path.join(remotePluginDir, 'client-v2.js'), '');
107
+
108
+ expect(getPluginClientModuleIds({ cwd }).sort()).toEqual([
109
+ '@nocobase/plugin-local/client',
110
+ '@nocobase/plugin-remote/client-v2',
111
+ ]);
112
+ fs.removeSync(cwd);
113
+ });
114
+
115
+ test('createPluginClientExternals maps plugin client imports to app-dev modules', () => {
116
+ expect(createPluginClientExternals(['@nocobase/plugin-demo/client'])).toEqual({
117
+ '@nocobase/plugin-demo/client':
118
+ 'window.__nocobase_app_dev_plugins__ && window.__nocobase_app_dev_plugins__["@nocobase/plugin-demo/client"]',
119
+ });
120
+ });
121
+ });
@@ -0,0 +1,83 @@
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 { buildIndexHtml } = require('../util');
16
+
17
+ function createAppPackageRoot() {
18
+ const appRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'nocobase-app-html-'));
19
+ const clientDir = path.join(appRoot, 'dist/client');
20
+ fs.ensureDirSync(clientDir);
21
+ fs.writeFileSync(path.join(clientDir, 'index.html'), '<html></html>', 'utf-8');
22
+ fs.writeFileSync(
23
+ path.join(clientDir, 'index.html.tpl'),
24
+ [
25
+ "window['__nocobase_app_dev__'] = {{env.NOCOBASE_APP_DEV}};",
26
+ "window['__nocobase_public_path__'] = '{{env.APP_PUBLIC_PATH}}';",
27
+ ].join('\n'),
28
+ 'utf-8',
29
+ );
30
+ return appRoot;
31
+ }
32
+
33
+ describe('cli-v1 buildIndexHtml', () => {
34
+ const originalArgv = process.argv.slice();
35
+ const originalEnv = { ...process.env };
36
+
37
+ afterEach(() => {
38
+ process.argv = originalArgv.slice();
39
+ process.env = { ...originalEnv };
40
+ });
41
+
42
+ test('marks app-dev HTML when running the app-dev command before child env is injected', () => {
43
+ const appRoot = createAppPackageRoot();
44
+ process.argv = ['node', 'nocobase-v1', 'app-dev'];
45
+ process.env.APP_PACKAGE_ROOT = appRoot;
46
+ process.env.APP_PUBLIC_PATH = '/';
47
+ process.env.NOCOBASE_APP_DEV = '';
48
+
49
+ buildIndexHtml();
50
+
51
+ const html = fs.readFileSync(path.join(appRoot, 'dist/client/index.html'), 'utf-8');
52
+ expect(html).toContain("window['__nocobase_app_dev__'] = true;");
53
+ fs.removeSync(appRoot);
54
+ });
55
+
56
+ test('marks app-dev HTML when running a child command with app-dev env', () => {
57
+ const appRoot = createAppPackageRoot();
58
+ process.argv = ['node', 'nocobase-v1', 'start'];
59
+ process.env.APP_PACKAGE_ROOT = appRoot;
60
+ process.env.APP_PUBLIC_PATH = '/';
61
+ process.env.NOCOBASE_APP_DEV = 'true';
62
+
63
+ buildIndexHtml();
64
+
65
+ const html = fs.readFileSync(path.join(appRoot, 'dist/client/index.html'), 'utf-8');
66
+ expect(html).toContain("window['__nocobase_app_dev__'] = true;");
67
+ fs.removeSync(appRoot);
68
+ });
69
+
70
+ test('does not mark normal start HTML as app-dev', () => {
71
+ const appRoot = createAppPackageRoot();
72
+ process.argv = ['node', 'nocobase-v1', 'start'];
73
+ process.env.APP_PACKAGE_ROOT = appRoot;
74
+ process.env.APP_PUBLIC_PATH = '/';
75
+ process.env.NOCOBASE_APP_DEV = '';
76
+
77
+ buildIndexHtml();
78
+
79
+ const html = fs.readFileSync(path.join(appRoot, 'dist/client/index.html'), 'utf-8');
80
+ expect(html).toContain("window['__nocobase_app_dev__'] = false;");
81
+ fs.removeSync(appRoot);
82
+ });
83
+ });
@@ -0,0 +1,181 @@
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
+ const path = require('path');
11
+ const { createRsbuild } = require('@rsbuild/core');
12
+ const { pluginLess } = require('@rsbuild/plugin-less');
13
+ const { pluginReact } = require('@rsbuild/plugin-react');
14
+ const { pluginSvgr } = require('@rsbuild/plugin-svgr');
15
+ const {
16
+ createPluginClientExternals,
17
+ discoverLocalPluginEntries,
18
+ getPluginClientModuleIds,
19
+ writePluginDevEntryFiles,
20
+ } = require('./app-dev-utils');
21
+ const { storagePathJoin } = require('../util');
22
+
23
+ const appDevExternalDeps = [
24
+ 'react',
25
+ 'react-dom',
26
+ 'react/jsx-runtime',
27
+ 'react-router',
28
+ 'react-router-dom',
29
+ 'antd',
30
+ 'antd-style',
31
+ '@ant-design/icons',
32
+ '@ant-design/cssinjs',
33
+ 'i18next',
34
+ 'react-i18next',
35
+ '@formily/antd-v5',
36
+ '@formily/core',
37
+ '@formily/json-schema',
38
+ '@formily/path',
39
+ '@formily/react',
40
+ '@formily/reactive',
41
+ '@formily/reactive-react',
42
+ '@formily/shared',
43
+ '@formily/validator',
44
+ '@dnd-kit/core',
45
+ '@dnd-kit/sortable',
46
+ '@nocobase/client',
47
+ '@nocobase/client/client',
48
+ '@nocobase/client-v2',
49
+ '@nocobase/client-v2/client-v2',
50
+ '@nocobase/evaluators',
51
+ '@nocobase/evaluators/client',
52
+ '@nocobase/flow-engine',
53
+ '@nocobase/sdk',
54
+ '@nocobase/utils',
55
+ '@nocobase/utils/client',
56
+ '@emotion/css',
57
+ 'ahooks',
58
+ 'axios',
59
+ 'dayjs',
60
+ 'file-saver',
61
+ 'lodash',
62
+ ];
63
+
64
+ function createExternals() {
65
+ return appDevExternalDeps.reduce((memo, dep) => {
66
+ memo[dep] = `window.__nocobase_app_dev_deps__ && window.__nocobase_app_dev_deps__[${JSON.stringify(dep)}]`;
67
+ return memo;
68
+ }, {});
69
+ }
70
+
71
+ async function main() {
72
+ const cwd = process.cwd();
73
+ const port = Number(process.env.NOCOBASE_APP_DEV_PLUGIN_PORT || 14100);
74
+ const entryDir = storagePathJoin('.app-dev', 'plugin-dev', 'entries');
75
+ const outDir = storagePathJoin('.app-dev', 'plugin-dev', 'dist');
76
+ const entries = discoverLocalPluginEntries({ cwd, port });
77
+ const externals = {
78
+ ...createExternals(),
79
+ ...createPluginClientExternals(getPluginClientModuleIds({ cwd })),
80
+ };
81
+
82
+ if (!entries.length) {
83
+ console.log('[app-dev] no local plugin client entries found');
84
+ await new Promise(() => {});
85
+ return;
86
+ }
87
+
88
+ const rsbuildEntries = await writePluginDevEntryFiles(entries, entryDir);
89
+ const rsbuild = await createRsbuild({
90
+ cwd,
91
+ rsbuildConfig: {
92
+ plugins: [pluginReact(), pluginLess(), pluginSvgr()],
93
+ source: {
94
+ entry: Object.fromEntries(
95
+ Object.entries(rsbuildEntries).map(([name, entryFile]) => [
96
+ name,
97
+ {
98
+ import: entryFile,
99
+ html: false,
100
+ },
101
+ ]),
102
+ ),
103
+ tsconfigPath: path.resolve(cwd, 'tsconfig.json'),
104
+ decorators: {
105
+ version: 'legacy',
106
+ },
107
+ },
108
+ output: {
109
+ target: 'web',
110
+ distPath: {
111
+ root: outDir,
112
+ js: '.',
113
+ jsAsync: '.',
114
+ css: '.',
115
+ cssAsync: '.',
116
+ svg: '.',
117
+ font: '.',
118
+ image: '.',
119
+ media: '.',
120
+ assets: '.',
121
+ },
122
+ filename: {
123
+ js: '[name].js',
124
+ css: '[name].css',
125
+ svg: '[name][ext][query]',
126
+ font: '[name][ext][query]',
127
+ image: '[name][ext][query]',
128
+ media: '[name][ext][query]',
129
+ assets: '[name][ext][query]',
130
+ },
131
+ assetPrefix: `http://localhost:${port}/`,
132
+ cleanDistPath: true,
133
+ sourceMap: {
134
+ js: 'eval-cheap-module-source-map',
135
+ css: false,
136
+ },
137
+ },
138
+ server: {
139
+ host: '0.0.0.0',
140
+ port,
141
+ cors: true,
142
+ },
143
+ dev: {
144
+ hmr: false,
145
+ liveReload: true,
146
+ assetPrefix: `http://localhost:${port}/`,
147
+ lazyCompilation: false,
148
+ client: {
149
+ port,
150
+ protocol: 'ws',
151
+ },
152
+ },
153
+ tools: {
154
+ rspack(config) {
155
+ config.output = config.output || {};
156
+ config.output.library = { type: 'module' };
157
+ config.output.module = true;
158
+ config.output.chunkFormat = 'module';
159
+ config.output.chunkLoading = 'import';
160
+ config.output.workerChunkLoading = 'import';
161
+ config.experiments = {
162
+ ...config.experiments,
163
+ outputModule: true,
164
+ };
165
+ config.externalsType = 'var';
166
+ config.externals = {
167
+ ...(config.externals || {}),
168
+ ...externals,
169
+ };
170
+ },
171
+ },
172
+ },
173
+ });
174
+
175
+ await rsbuild.startDevServer();
176
+ }
177
+
178
+ main().catch((error) => {
179
+ console.error(error);
180
+ process.exit(1);
181
+ });
@@ -0,0 +1,191 @@
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
+ const fs = require('fs-extra');
11
+ const fg = require('fast-glob');
12
+ const path = require('path');
13
+ const { resolvePluginStoragePath } = require('../util');
14
+
15
+ const pluginClientLanes = {
16
+ client: {
17
+ rootEntryFile: 'client.js',
18
+ sourceDir: 'client',
19
+ },
20
+ 'client-v2': {
21
+ rootEntryFile: 'client-v2.js',
22
+ sourceDir: 'client-v2',
23
+ },
24
+ };
25
+
26
+ function toPosixPath(filePath) {
27
+ return filePath.replace(/\\/g, '/');
28
+ }
29
+
30
+ function getEntryName(packageName, lane) {
31
+ return `${packageName.replace(/[^a-zA-Z0-9_]/g, '_')}__${lane.replace(/[^a-zA-Z0-9_]/g, '_')}`;
32
+ }
33
+
34
+ function readPackageName(packageJsonPath) {
35
+ try {
36
+ return fs.readJsonSync(packageJsonPath).name;
37
+ } catch (error) {
38
+ return '';
39
+ }
40
+ }
41
+
42
+ function findLocalPluginPackageJsons(cwd = process.cwd()) {
43
+ return fg.sync(['packages/plugins/*/package.json', 'packages/plugins/@*/*/package.json'], {
44
+ cwd,
45
+ absolute: true,
46
+ onlyFiles: true,
47
+ });
48
+ }
49
+
50
+ function findPluginClientPackageJsons(cwd = process.cwd()) {
51
+ return fg.sync(
52
+ [
53
+ 'packages/plugins/*/package.json',
54
+ 'packages/plugins/@*/*/package.json',
55
+ 'node_modules/@nocobase/plugin-*/package.json',
56
+ 'node_modules/@nocobase/preset-*/package.json',
57
+ ],
58
+ {
59
+ cwd,
60
+ absolute: true,
61
+ onlyFiles: true,
62
+ },
63
+ );
64
+ }
65
+
66
+ function getPluginClientModuleIds({ cwd = process.cwd(), packageJsonPaths = findPluginClientPackageJsons(cwd) } = {}) {
67
+ const moduleIds = new Set();
68
+
69
+ for (const packageJsonPath of packageJsonPaths) {
70
+ const pluginDir = path.dirname(packageJsonPath);
71
+ const packageName = readPackageName(packageJsonPath);
72
+ if (!packageName) {
73
+ continue;
74
+ }
75
+
76
+ for (const [lane, config] of Object.entries(pluginClientLanes)) {
77
+ if (fs.existsSync(path.join(pluginDir, config.rootEntryFile))) {
78
+ moduleIds.add(`${packageName}/${lane}`);
79
+ }
80
+ }
81
+ }
82
+
83
+ return [...moduleIds];
84
+ }
85
+
86
+ function createPluginClientExternals(moduleIds) {
87
+ return moduleIds.reduce((memo, moduleId) => {
88
+ memo[moduleId] = `window.__nocobase_app_dev_plugins__ && window.__nocobase_app_dev_plugins__[${JSON.stringify(
89
+ moduleId,
90
+ )}]`;
91
+ return memo;
92
+ }, {});
93
+ }
94
+
95
+ function discoverLocalPluginEntries({ cwd = process.cwd(), port } = {}) {
96
+ const entries = [];
97
+
98
+ for (const packageJsonPath of findLocalPluginPackageJsons(cwd)) {
99
+ const pluginDir = path.dirname(packageJsonPath);
100
+ const packageJson = fs.readJsonSync(packageJsonPath);
101
+ const packageName = packageJson.name;
102
+ if (!packageName) {
103
+ continue;
104
+ }
105
+
106
+ for (const [lane, config] of Object.entries(pluginClientLanes)) {
107
+ const rootEntry = path.join(pluginDir, config.rootEntryFile);
108
+ const sourceEntry = fg.sync('index.{ts,tsx,js,jsx}', {
109
+ cwd: path.join(pluginDir, 'src', config.sourceDir),
110
+ absolute: true,
111
+ onlyFiles: true,
112
+ })[0];
113
+
114
+ if (!fs.existsSync(rootEntry) || !sourceEntry) {
115
+ continue;
116
+ }
117
+
118
+ const entryName = getEntryName(packageName, lane);
119
+ entries.push({
120
+ packageName,
121
+ lane,
122
+ entryName,
123
+ sourceEntry,
124
+ url: port ? `http://localhost:${port}/${entryName}.js` : '',
125
+ });
126
+ }
127
+ }
128
+
129
+ return entries;
130
+ }
131
+
132
+ function buildPluginDevUrlMap(entries) {
133
+ return entries.reduce((memo, item) => {
134
+ memo[item.packageName] = memo[item.packageName] || {};
135
+ memo[item.packageName][item.lane] = item.url;
136
+ return memo;
137
+ }, {});
138
+ }
139
+
140
+ function shouldUseAppDevServerSource({ env = process.env, cwd = process.cwd(), existsSync = fs.existsSync } = {}) {
141
+ if (env.NOCOBASE_APP_DEV !== 'true' || env.APP_ENV === 'production' || !env.APP_PACKAGE_ROOT) {
142
+ return false;
143
+ }
144
+
145
+ return existsSync(path.resolve(cwd, env.APP_PACKAGE_ROOT, 'src/index.ts'));
146
+ }
147
+
148
+ function buildAppDevServerArgs({
149
+ appPackageRoot = process.env.APP_PACKAGE_ROOT,
150
+ argv = process.argv,
151
+ serverTsconfigPath = process.env.SERVER_TSCONFIG_PATH,
152
+ } = {}) {
153
+ const args = ['watch', `--ignore=${resolvePluginStoragePath()}/**`];
154
+
155
+ if (serverTsconfigPath) {
156
+ args.push('--tsconfig', serverTsconfigPath);
157
+ }
158
+
159
+ args.push('-r', 'tsconfig-paths/register', path.join(appPackageRoot, 'src/index.ts'), ...argv.slice(2));
160
+ return args;
161
+ }
162
+
163
+ async function writePluginDevEntryFiles(entries, entryDir) {
164
+ await fs.ensureDir(entryDir);
165
+ await fs.emptyDir(entryDir);
166
+
167
+ const rsbuildEntries = {};
168
+ for (const entry of entries) {
169
+ const entryFile = path.join(entryDir, `${entry.entryName}.tsx`);
170
+ const sourceEntry = toPosixPath(entry.sourceEntry);
171
+ await fs.writeFile(
172
+ entryFile,
173
+ [`export { default } from '${sourceEntry}';`, `export * from '${sourceEntry}';`, ''].join('\n'),
174
+ 'utf-8',
175
+ );
176
+ rsbuildEntries[entry.entryName] = entryFile;
177
+ }
178
+
179
+ return rsbuildEntries;
180
+ }
181
+
182
+ module.exports = {
183
+ buildAppDevServerArgs,
184
+ buildPluginDevUrlMap,
185
+ createPluginClientExternals,
186
+ discoverLocalPluginEntries,
187
+ getPluginClientModuleIds,
188
+ shouldUseAppDevServerSource,
189
+ toPosixPath,
190
+ writePluginDevEntryFiles,
191
+ };
@@ -0,0 +1,93 @@
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
+ const path = require('path');
11
+ const { getPortPromise } = require('portfinder');
12
+ const { buildPluginDevUrlMap, discoverLocalPluginEntries } = require('./app-dev-utils');
13
+ const { run, runWithPrefix } = require('../util');
14
+
15
+ function hasArg(args, name) {
16
+ return args.some((item) => item === name || item.startsWith(`${name}=`));
17
+ }
18
+
19
+ function omitOptionWithValue(args, name) {
20
+ const nextArgs = [];
21
+ for (let index = 0; index < args.length; index++) {
22
+ const item = args[index];
23
+ if (item === name) {
24
+ index += 1;
25
+ continue;
26
+ }
27
+ if (item.startsWith(`${name}=`)) {
28
+ continue;
29
+ }
30
+ nextArgs.push(item);
31
+ }
32
+ return nextArgs;
33
+ }
34
+
35
+ /**
36
+ *
37
+ * @param {import('commander').Command} cli
38
+ */
39
+ module.exports = (cli) => {
40
+ cli
41
+ .command('app-dev')
42
+ .description('Run the published app shell with local plugin development support')
43
+ .option('--plugin-port [port]', 'local plugin dev server port')
44
+ .allowUnknownOption()
45
+ .action(async (opts) => {
46
+ const passthroughArgs = omitOptionWithValue(process.argv.slice(3), '--plugin-port');
47
+ const pluginPort = opts.pluginPort
48
+ ? Number(opts.pluginPort)
49
+ : await getPortPromise({ port: Number(process.env.NOCOBASE_APP_DEV_PLUGIN_PORT || 14100) });
50
+ const entries = discoverLocalPluginEntries({ cwd: process.cwd(), port: pluginPort });
51
+ const pluginDevUrlMap = buildPluginDevUrlMap(entries);
52
+ let pluginDevServer;
53
+
54
+ const sharedEnv = {
55
+ NOCOBASE_APP_DEV: 'true',
56
+ NOCOBASE_APP_DEV_PLUGIN_PORT: `${pluginPort}`,
57
+ NOCOBASE_APP_DEV_PLUGIN_URLS: JSON.stringify(pluginDevUrlMap),
58
+ };
59
+
60
+ if (entries.length) {
61
+ pluginDevServer = runWithPrefix('node', [path.resolve(__dirname, './app-dev-plugin-server.js')], {
62
+ prefix: 'plugin-dev',
63
+ color: 'green',
64
+ env: sharedEnv,
65
+ });
66
+ }
67
+
68
+ const cleanup = () => {
69
+ pluginDevServer?.kill('SIGTERM');
70
+ };
71
+ process.once('exit', cleanup);
72
+ process.once('SIGINT', () => {
73
+ cleanup();
74
+ process.exit(130);
75
+ });
76
+ process.once('SIGTERM', () => {
77
+ cleanup();
78
+ process.exit(143);
79
+ });
80
+
81
+ const startArgs = ['start'];
82
+ if (!hasArg(passthroughArgs, '--launch-mode')) {
83
+ startArgs.push('--launch-mode', 'direct');
84
+ }
85
+ startArgs.push(...passthroughArgs);
86
+
87
+ await run('nocobase-v1', startArgs, {
88
+ env: {
89
+ ...sharedEnv,
90
+ },
91
+ });
92
+ });
93
+ };
@@ -7,34 +7,10 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
- const { existsSync } = require('fs');
11
10
  const { resolve } = require('path');
12
11
  const { Command } = require('commander');
13
12
  const { run, nodeCheck, isPackageValid, buildIndexHtml } = require('../util');
14
13
 
15
- async function buildClientV2() {
16
- const configPath = resolve(process.env.APP_PACKAGE_ROOT, 'client-v2/rsbuild.config.ts');
17
- if (!existsSync(configPath)) {
18
- console.log(`client-v2 config not found: ${configPath}`);
19
- return;
20
- }
21
- await run('rsbuild', ['build', '--config', configPath], {
22
- env: {
23
- ...process.env,
24
- APP_ENV: 'production',
25
- NODE_ENV: 'production',
26
- API_BASE_URL: process.env.API_BASE_URL || process.env.API_BASE_PATH,
27
- API_CLIENT_STORAGE_PREFIX: process.env.API_CLIENT_STORAGE_PREFIX,
28
- API_CLIENT_STORAGE_TYPE: process.env.API_CLIENT_STORAGE_TYPE,
29
- API_CLIENT_SHARE_TOKEN: process.env.API_CLIENT_SHARE_TOKEN || 'false',
30
- WEBSOCKET_URL: process.env.WEBSOCKET_URL || '',
31
- WS_PATH: process.env.WS_PATH,
32
- ESM_CDN_BASE_URL: process.env.ESM_CDN_BASE_URL || 'https://esm.sh',
33
- ESM_CDN_SUFFIX: process.env.ESM_CDN_SUFFIX || '',
34
- },
35
- });
36
- }
37
-
38
14
  /**
39
15
  *
40
16
  * @param {Command} cli
@@ -50,17 +26,11 @@ module.exports = (cli) => {
50
26
  .option('-w, --watch', 'watch compile the @nocobase/build package')
51
27
  .option('-s, --sourcemap', 'generate sourcemap')
52
28
  .option('--no-dts', 'not generate dts')
53
- .option('--client-v2-only', 'build client-v2 shell only')
54
29
  .action(async (pkgs, options) => {
55
30
  nodeCheck();
56
31
  process.env['VITE_CJS_IGNORE_WARNING'] = 'true';
57
32
  process.env.APP_ENV = 'production';
58
33
 
59
- if (options.clientV2Only) {
60
- await buildClientV2();
61
- return;
62
- }
63
-
64
34
  if (pkgs.length === 0) {
65
35
  await run('nocobase-v1', ['clean', '--dist']);
66
36
  }
@@ -80,8 +50,5 @@ module.exports = (cli) => {
80
50
  options.retry ? '--retry' : '',
81
51
  ]);
82
52
  buildIndexHtml(true);
83
- if (!pkgs.includes('@nocobase/app')) {
84
- await buildClientV2();
85
- }
86
53
  });
87
54
  };
@@ -9,7 +9,7 @@
9
9
 
10
10
  const chalk = require('chalk');
11
11
  const { Command } = require('commander');
12
- const { run, isDev } = require('../util');
12
+ const { run, isDev, storagePathJoin } = require('../util');
13
13
 
14
14
  /**
15
15
  *
@@ -34,7 +34,7 @@ module.exports = (cli) => {
34
34
  run('rimraf', ['-rf', ...distDirs]);
35
35
  }
36
36
  } else {
37
- run('rimraf', ['-rf', './storage/app-dev']);
37
+ run('rimraf', ['-rf', storagePathJoin('.app-dev')]);
38
38
  run('rimraf', ['-rf', 'packages/*/*/{lib,esm,es,dist,node_modules}']);
39
39
  run('rimraf', ['-rf', 'packages/*/@*/*/{lib,esm,es,dist,node_modules}']);
40
40
  }
@@ -11,6 +11,7 @@ const chalk = require('chalk');
11
11
  const { Command } = require('commander');
12
12
  const fs = require('fs-extra');
13
13
  const { resolve } = require('path');
14
+ const { storagePathJoin } = require('../util');
14
15
 
15
16
  /**
16
17
  * 复制主应用客户端文件
@@ -110,7 +111,7 @@ module.exports = (cli) => {
110
111
  .allowUnknownOption()
111
112
  .action(async () => {
112
113
  const version = require('../../package.json').version;
113
- const target = resolve(process.cwd(), 'storage/dist-client', version);
114
+ const target = storagePathJoin('dist-client', version);
114
115
  const mainClientSource = resolve(process.cwd(), 'node_modules/@nocobase/app/dist/client');
115
116
  await copyMainClient(mainClientSource, target);
116
117
  await copyPluginClients('packages/plugins', '@nocobase', target);
@@ -123,7 +124,7 @@ module.exports = (cli) => {
123
124
  .allowUnknownOption()
124
125
  .action(async () => {
125
126
  const version = require('../../package.json').version;
126
- const target = resolve(process.cwd(), 'storage/dist-client', version);
127
+ const target = storagePathJoin('dist-client', version);
127
128
 
128
129
  // 检查必要的环境变量
129
130
  if (
@@ -24,6 +24,7 @@ const chokidar = require('chokidar');
24
24
  const { uid } = require('@formily/shared');
25
25
  const path = require('path');
26
26
  const fs = require('fs');
27
+ const { resolvePluginStoragePath } = require('@nocobase/utils/plugin-symlink');
27
28
 
28
29
  function sleep(ms = 1000) {
29
30
  return new Promise((resolve) => {
@@ -198,7 +199,7 @@ module.exports = (cli) => {
198
199
  };
199
200
 
200
201
  if (shouldRunClient) {
201
- const storagePluginPath = path.resolve(process.cwd(), 'storage/plugins');
202
+ const storagePluginPath = resolvePluginStoragePath();
202
203
  const watcher = chokidar.watch(`${storagePluginPath}/**/*`, {
203
204
  cwd: process.cwd(),
204
205
  ignored: /(^|[\/\\])\../, // 忽略隐藏文件
@@ -246,7 +247,7 @@ module.exports = (cli) => {
246
247
  const argv = [
247
248
  'watch',
248
249
  ...(inspect ? [`--inspect=${inspect === true ? 9229 : inspect}`] : []),
249
- '--ignore=./storage/plugins/**',
250
+ `--ignore=${resolvePluginStoragePath()}/**`,
250
251
  '--tsconfig',
251
252
  SERVER_TSCONFIG_PATH,
252
253
  '-r',
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  const { Command } = require('commander');
11
- const { run, isPortReachable, checkDBDialect } = require('../util');
11
+ const { run, isPortReachable, checkDBDialect, storagePathJoin } = require('../util');
12
12
  const { execSync } = require('node:child_process');
13
13
  const axios = require('axios');
14
14
  const { pTest } = require('./p-test');
@@ -119,17 +119,15 @@ const commonConfig = {
119
119
  };
120
120
 
121
121
  const runCodegenSync = () => {
122
+ const authFile = storagePathJoin('playwright', '.auth', 'codegen.auth.json');
122
123
  try {
123
124
  execSync(
124
- `npx playwright codegen --load-storage=storage/playwright/.auth/codegen.auth.json ${process.env.APP_BASE_URL} --save-storage=storage/playwright/.auth/codegen.auth.json`,
125
+ `npx playwright codegen --load-storage=${authFile} ${process.env.APP_BASE_URL} --save-storage=${authFile}`,
125
126
  commonConfig,
126
127
  );
127
128
  } catch (err) {
128
129
  if (err.message.includes('auth.json')) {
129
- execSync(
130
- `npx playwright codegen ${process.env.APP_BASE_URL} --save-storage=storage/playwright/.auth/codegen.auth.json`,
131
- commonConfig,
132
- );
130
+ execSync(`npx playwright codegen ${process.env.APP_BASE_URL} --save-storage=${authFile}`, commonConfig);
133
131
  } else {
134
132
  console.error(err);
135
133
  }
@@ -21,6 +21,7 @@ module.exports = (cli) => {
21
21
  require('./locale')(cli);
22
22
  require('./build')(cli);
23
23
  require('./tar')(cli);
24
+ require('./app-dev')(cli);
24
25
  require('./dev')(cli);
25
26
  require('./start')(cli);
26
27
  require('./e2e')(cli);
@@ -9,9 +9,8 @@
9
9
 
10
10
  const chalk = require('chalk');
11
11
  const { Command } = require('commander');
12
- const { run, isDev } = require('../util');
12
+ const { run, isDev, storagePathJoin } = require('../util');
13
13
  const { getInstanceIdAsync } = require('@nocobase/license-kit');
14
- const path = require('path');
15
14
  const fs = require('fs');
16
15
  const { logger } = require('../logger');
17
16
 
@@ -26,8 +25,8 @@ module.exports = (cli) => {
26
25
  .option('--force', 'Force generate InstanceID')
27
26
  .action(async (options) => {
28
27
  logger.info('Generating InstanceID...');
29
- const dir = path.resolve(process.cwd(), 'storage/.license');
30
- const filePath = path.resolve(dir, 'instance-id');
28
+ const dir = storagePathJoin('.license');
29
+ const filePath = storagePathJoin('.license', 'instance-id');
31
30
  if (fs.existsSync(filePath) && !options.force) {
32
31
  logger.info('InstanceID already exists at ' + filePath);
33
32
  return;
@@ -15,6 +15,7 @@ const _ = require('lodash');
15
15
  const deepmerge = require('deepmerge');
16
16
  const { getCronstrueLocale } = require('./locale/cronstrue');
17
17
  const { getReactJsCron } = require('./locale/react-js-cron');
18
+ const { storagePathJoin } = require('../util');
18
19
 
19
20
  function sortJSON(json) {
20
21
  if (Array.isArray(json)) {
@@ -41,8 +42,9 @@ module.exports = (cli) => {
41
42
  const files = await fg('./*/src/locale/*.json', {
42
43
  cwd,
43
44
  });
45
+ const localeDir = storagePathJoin('locales');
44
46
  let locales = {};
45
- await fs.mkdirp(path.resolve(process.cwd(), 'storage/locales'));
47
+ await fs.mkdirp(localeDir);
46
48
  for (const file of files) {
47
49
  const locale = path.basename(file, '.json');
48
50
  const pkg = path.basename(path.dirname(path.dirname(path.dirname(file))));
@@ -70,10 +72,7 @@ module.exports = (cli) => {
70
72
  }
71
73
  locales = sortJSON(locales);
72
74
  for (const locale of Object.keys(locales)) {
73
- await fs.writeFile(
74
- path.resolve(process.cwd(), 'storage/locales', `${locale}.json`),
75
- JSON.stringify(sortJSON(locales[locale]), null, 2),
76
- );
75
+ await fs.writeFile(path.resolve(localeDir, `${locale}.json`), JSON.stringify(sortJSON(locales[locale]), null, 2));
77
76
  }
78
77
  });
79
78
 
@@ -14,6 +14,7 @@ const dotenv = require('dotenv');
14
14
  const fs = require('fs');
15
15
  const glob = require('glob');
16
16
  const _ = require('lodash');
17
+ const { storagePathJoin } = require('../util');
17
18
 
18
19
  let ENV_FILE = resolve(process.cwd(), '.env.e2e');
19
20
 
@@ -55,16 +56,16 @@ async function runApp(dir, index = 0) {
55
56
  APP_ENV: 'production',
56
57
  APP_PORT: 20000 + index,
57
58
  DB_DATABASE: `nocobase${index}`,
58
- SOCKET_PATH: `storage/e2e/gateway-e2e-${index}.sock`,
59
- PM2_HOME: resolve(process.cwd(), `storage/e2e/.pm2-${index}`),
60
- PLAYWRIGHT_AUTH_FILE: resolve(process.cwd(), `storage/playwright/.auth/admin-${index}.json`),
59
+ SOCKET_PATH: storagePathJoin('e2e', `gateway-e2e-${index}.sock`),
60
+ PM2_HOME: storagePathJoin('e2e', `.pm2-${index}`),
61
+ PLAYWRIGHT_AUTH_FILE: storagePathJoin('playwright', '.auth', `admin-${index}.json`),
61
62
  E2E_JOB_ID: index,
62
63
  },
63
64
  });
64
65
  }
65
66
 
66
67
  exports.pTest = async (options) => {
67
- const dir = resolve(process.cwd(), 'storage/e2e');
68
+ const dir = storagePathJoin('e2e');
68
69
 
69
70
  if (!fs.existsSync(dir)) {
70
71
  fs.mkdirSync(dir, { recursive: true });
@@ -22,7 +22,7 @@ class Package {
22
22
  constructor(packageName, packageManager) {
23
23
  this.packageName = packageName;
24
24
  this.packageManager = packageManager;
25
- this.outputDir = path.resolve(process.cwd(), `storage/plugins/${this.packageName}`);
25
+ this.outputDir = path.resolve(resolvePluginStoragePath(), this.packageName);
26
26
  }
27
27
 
28
28
  get token() {
@@ -13,6 +13,8 @@ const { existsSync, rmSync } = require('fs');
13
13
  const { resolve, isAbsolute } = require('path');
14
14
  const chalk = require('chalk');
15
15
  const chokidar = require('chokidar');
16
+ const { buildAppDevServerArgs, shouldUseAppDevServerSource } = require('./app-dev-utils');
17
+ const { resolvePluginStoragePath } = require('@nocobase/utils/plugin-symlink');
16
18
 
17
19
  function getSocketPath() {
18
20
  const { SOCKET_PATH } = process.env;
@@ -52,6 +54,7 @@ module.exports = (cli) => {
52
54
  .allowUnknownOption()
53
55
  .action(async (opts) => {
54
56
  checkDBDialect();
57
+ const useAppDevServerSource = shouldUseAppDevServerSource();
55
58
  if (opts.quickstart) {
56
59
  await downloadPro();
57
60
  }
@@ -62,7 +65,7 @@ module.exports = (cli) => {
62
65
  await run('yarn', ['nocobase', 'pm2-restart']);
63
66
  }, 500);
64
67
 
65
- const watcher = chokidar.watch('./storage/plugins/**/*', {
68
+ const watcher = chokidar.watch(`${resolvePluginStoragePath()}/**/*`, {
66
69
  cwd: process.cwd(),
67
70
  ignoreInitial: true,
68
71
  ignored: /(^|[\/\\])\../, // 忽略隐藏文件
@@ -99,7 +102,7 @@ module.exports = (cli) => {
99
102
  ]);
100
103
  return;
101
104
  }
102
- if (!existsSync(resolve(process.cwd(), `${APP_PACKAGE_ROOT}/lib/index.js`))) {
105
+ if (!useAppDevServerSource && !existsSync(resolve(process.cwd(), `${APP_PACKAGE_ROOT}/lib/index.js`))) {
103
106
  console.log('The code is not compiled, please execute it first');
104
107
  console.log(chalk.yellow('$ yarn build'));
105
108
  console.log('If you want to run in development mode, please execute');
@@ -136,12 +139,21 @@ module.exports = (cli) => {
136
139
  ].filter(Boolean),
137
140
  );
138
141
  } else {
139
- run(
140
- 'node',
141
- [`${APP_PACKAGE_ROOT}/lib/index.js`, ...(NODE_ARGS || '').split(' '), ...process.argv.slice(2)].filter(
142
- Boolean,
143
- ),
144
- );
142
+ if (useAppDevServerSource) {
143
+ promptForTs();
144
+ run('tsx', buildAppDevServerArgs(), {
145
+ env: {
146
+ IS_DEV_CMD: 'true',
147
+ },
148
+ });
149
+ } else {
150
+ run(
151
+ 'node',
152
+ [`${APP_PACKAGE_ROOT}/lib/index.js`, ...(NODE_ARGS || '').split(' '), ...process.argv.slice(2)].filter(
153
+ Boolean,
154
+ ),
155
+ );
156
+ }
145
157
  }
146
158
  }
147
159
  });
@@ -10,14 +10,23 @@
10
10
  const chalk = require('chalk');
11
11
  const { Command } = require('commander');
12
12
  const { resolve } = require('path');
13
- const { run, promptForTs, runAppCommand, hasCorePackages, downloadPro, hasTsNode, checkDBDialect } = require('../util');
13
+ const {
14
+ run,
15
+ promptForTs,
16
+ runAppCommand,
17
+ hasCorePackages,
18
+ downloadPro,
19
+ hasTsNode,
20
+ checkDBDialect,
21
+ storagePathJoin,
22
+ } = require('../util');
14
23
  const { existsSync, rmSync } = require('fs');
15
24
  const { readJSON, writeJSON } = require('fs-extra');
16
25
  const deepmerge = require('deepmerge');
17
26
 
18
27
  const rmAppDir = () => {
19
28
  // If ts-node is not installed, do not do the following
20
- const appDevDir = resolve(process.cwd(), './storage/.app-dev');
29
+ const appDevDir = storagePathJoin('.app-dev');
21
30
  if (existsSync(appDevDir)) {
22
31
  rmSync(appDevDir, { recursive: true, force: true });
23
32
  }
@@ -10,8 +10,8 @@
10
10
  const chalk = require('chalk');
11
11
  const { Command } = require('commander');
12
12
  const { keyDecrypt } = require('@nocobase/license-kit');
13
- const path = require('path');
14
13
  const fs = require('fs');
14
+ const { storagePathJoin } = require('../util');
15
15
 
16
16
  /**
17
17
  *
@@ -22,8 +22,7 @@ module.exports = (cli) => {
22
22
  .command('view-license-key')
23
23
  .description('View License Key')
24
24
  .action(async (options) => {
25
- const dir = path.resolve(process.cwd(), 'storage/.license');
26
- const filePath = path.resolve(dir, 'license-key');
25
+ const filePath = storagePathJoin('.license', 'license-key');
27
26
  if (!fs.existsSync(filePath)) {
28
27
  console.log('License key not found at ' + filePath);
29
28
  return;
package/src/license.js CHANGED
@@ -8,15 +8,15 @@
8
8
  */
9
9
 
10
10
  const chalk = require('chalk');
11
- const { resolve } = require('path');
12
11
  const fs = require('fs-extra');
13
12
  const { keyDecrypt, getEnvAsync } = require('@nocobase/license-kit');
14
13
  const { isEnvMatch } = require('@nocobase/plugin-license/utils/env');
15
14
  const { logger } = require('./logger');
15
+ const { storagePathJoin } = require('./util');
16
16
  const { pick } = require('lodash');
17
17
 
18
18
  exports.getAccessKeyPair = async function () {
19
- const keyFile = resolve(process.cwd(), 'storage/.license/license-key');
19
+ const keyFile = storagePathJoin('.license', 'license-key');
20
20
  if (!fs.existsSync(keyFile)) {
21
21
  logger.info('License key not found');
22
22
  return {};
package/src/util.js CHANGED
@@ -261,7 +261,7 @@ function normalizeAppDevFiles(appDevDir) {
261
261
 
262
262
  exports.generateAppDir = function generateAppDir() {
263
263
  const appPkgPath = dirname(dirname(require.resolve('@nocobase/app/src/index.ts')));
264
- const appDevDir = resolve(process.cwd(), './storage/.app-dev');
264
+ const appDevDir = storagePathJoin('.app-dev');
265
265
  if (exports.isDev() && !exports.hasCorePackages() && appPkgPath.includes('node_modules')) {
266
266
  if (!existsSync(appDevDir)) {
267
267
  mkdirSync(appDevDir, { force: true, recursive: true });
@@ -374,6 +374,10 @@ function resolveV2PublicPath(appPublicPath = '/') {
374
374
 
375
375
  exports.resolveV2PublicPath = resolveV2PublicPath;
376
376
 
377
+ function isAppDevHtml() {
378
+ return process.argv[2] === 'app-dev' || process.env.NOCOBASE_APP_DEV === 'true';
379
+ }
380
+
377
381
  function buildIndexHtml(force = false) {
378
382
  const file = `${process.env.APP_PACKAGE_ROOT}/dist/client/index.html`;
379
383
  if (!fs.existsSync(file)) {
@@ -396,6 +400,7 @@ function buildIndexHtml(force = false) {
396
400
  .replace(/\{\{env.API_BASE_URL\}\}/g, process.env.API_BASE_URL || process.env.API_BASE_PATH)
397
401
  .replace(/\{\{env.WS_URL\}\}/g, process.env.WEBSOCKET_URL || '')
398
402
  .replace(/\{\{env.WS_PATH\}\}/g, process.env.WS_PATH)
403
+ .replace(/\{\{env.NOCOBASE_APP_DEV\}\}/g, isAppDevHtml() ? 'true' : 'false')
399
404
  .replace(/\{\{env.ESM_CDN_BASE_URL\}\}/g, process.env.ESM_CDN_BASE_URL || '')
400
405
  .replace(/\{\{env.ESM_CDN_SUFFIX\}\}/g, process.env.ESM_CDN_SUFFIX || '')
401
406
  .replace(/((?:src|href)=")(?:\.\/)?assets\//g, `$1${process.env.APP_PUBLIC_PATH}assets/`)
@@ -455,6 +460,7 @@ function storagePathJoin(...segments) {
455
460
 
456
461
  exports.resolveStorageRoot = resolveStorageRoot;
457
462
  exports.storagePathJoin = storagePathJoin;
463
+ exports.resolvePluginStoragePath = resolvePluginStoragePath;
458
464
  /** @deprecated Use resolveStorageRoot — kept for backward compatibility */
459
465
  exports.generateStoragePath = resolveStorageRoot;
460
466
 
@@ -485,6 +491,17 @@ function generatePm2Home() {
485
491
  }
486
492
 
487
493
  exports.initEnv = function initEnv() {
494
+ const preserveSymlinksFlag = '--preserve-symlinks';
495
+ const currentNodeOptions = String(process.env.NODE_OPTIONS || '').trim();
496
+ const hasPreserveSymlinksFlag = currentNodeOptions
497
+ ? currentNodeOptions.split(/\s+/).includes(preserveSymlinksFlag)
498
+ : false;
499
+ if (!hasPreserveSymlinksFlag) {
500
+ process.env.NODE_OPTIONS = currentNodeOptions
501
+ ? `${currentNodeOptions} ${preserveSymlinksFlag}`
502
+ : preserveSymlinksFlag;
503
+ }
504
+
488
505
  const env = {
489
506
  APP_ENV: 'development',
490
507
  APP_KEY: 'test-jwt-secret',
@@ -509,6 +526,7 @@ exports.initEnv = function initEnv() {
509
526
  CACHE_DEFAULT_STORE: 'memory',
510
527
  CACHE_MEMORY_MAX: 2000,
511
528
  BROWSERSLIST_IGNORE_OLD_DATA: true,
529
+ NOCOBASE_DEV_LOCAL_PLUGINS_ONLY: 'true',
512
530
  PLUGIN_STATICS_PATH: '/static/plugins/',
513
531
  APP_SERVER_BASE_URL: '',
514
532
  APP_BASE_URL: '',