@nocobase/cli 2.1.0-alpha.1 → 2.1.0-alpha.11

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.
@@ -8,7 +8,17 @@
8
8
  */
9
9
  const _ = require('lodash');
10
10
  const { Command } = require('commander');
11
- const { generatePlugins, run, postCheck, nodeCheck, promptForTs, isPortReachable, checkDBDialect } = require('../util');
11
+ const {
12
+ generatePlugins,
13
+ run,
14
+ runWithPrefix,
15
+ postCheck,
16
+ nodeCheck,
17
+ promptForTs,
18
+ isPortReachable,
19
+ buildWSURL,
20
+ checkDBDialect,
21
+ } = require('../util');
12
22
  const { getPortPromise } = require('portfinder');
13
23
  const chokidar = require('chokidar');
14
24
  const { uid } = require('@formily/shared');
@@ -21,6 +31,15 @@ function sleep(ms = 1000) {
21
31
  });
22
32
  }
23
33
 
34
+ async function buildBundleStatusHtml() {
35
+ const data = await fs.promises.readFile(path.resolve(__dirname, '../../templates/bundle-status.html'), 'utf-8');
36
+ await fs.promises.writeFile(
37
+ path.resolve(process.cwd(), 'node_modules/@umijs/preset-umi/assets/bundle-status.html'),
38
+ data,
39
+ 'utf-8',
40
+ );
41
+ }
42
+
24
43
  /**
25
44
  *
26
45
  * @param {Command} cli
@@ -33,73 +52,13 @@ module.exports = (cli) => {
33
52
  .option('-c, --client')
34
53
  .option('-s, --server')
35
54
  .option('--db-sync')
55
+ .option('--rsbuild')
56
+ .option('--client-v2-only')
36
57
  .option('-i, --inspect [port]')
37
58
  .allowUnknownOption()
38
59
  .action(async (opts) => {
39
60
  checkDBDialect();
40
- let subprocess;
41
- const runDevClient = () => {
42
- console.log('starting client', 1 * clientPort);
43
- subprocess = run('umi', ['dev'], {
44
- env: {
45
- ...process.env,
46
- stdio: 'inherit',
47
- shell: true,
48
- PORT: clientPort,
49
- APP_ROOT: `${APP_PACKAGE_ROOT}/client`,
50
- WEBSOCKET_URL:
51
- process.env.WEBSOCKET_URL ||
52
- (serverPort ? `ws://localhost:${serverPort}${process.env.WS_PATH}` : undefined),
53
- PROXY_TARGET_URL:
54
- process.env.PROXY_TARGET_URL || (serverPort ? `http://127.0.0.1:${serverPort}` : undefined),
55
- },
56
- });
57
- };
58
- const watcher = chokidar.watch('./storage/plugins/**/*', {
59
- cwd: process.cwd(),
60
- ignored: /(^|[\/\\])\../, // 忽略隐藏文件
61
- persistent: true,
62
- depth: 1, // 只监听第一层目录
63
- });
64
-
65
- await fs.promises.mkdir(path.dirname(process.env.WATCH_FILE), { recursive: true });
66
- let isReady = false;
67
-
68
- const restartClient = _.debounce(async () => {
69
- if (!isReady) return;
70
- generatePlugins();
71
- if (subprocess) {
72
- console.log('client restarting...');
73
- subprocess.cancel();
74
- let i = 0;
75
- while (true) {
76
- ++i;
77
- const result = await isPortReachable(clientPort);
78
- if (!result) {
79
- break;
80
- }
81
- await sleep(500);
82
- if (i > 10) {
83
- break;
84
- }
85
- }
86
- runDevClient();
87
- await fs.promises.writeFile(process.env.WATCH_FILE, `export const watchId = '${uid()}';`, 'utf-8');
88
- }
89
- }, 500);
90
-
91
- watcher
92
- .on('ready', () => {
93
- isReady = true;
94
- })
95
- .on('addDir', async (pathname) => {
96
- if (!isReady) return;
97
- restartClient();
98
- })
99
- .on('unlinkDir', async (pathname) => {
100
- if (!isReady) return;
101
- restartClient();
102
- });
61
+ await buildBundleStatusHtml();
103
62
 
104
63
  promptForTs();
105
64
  const { SERVER_TSCONFIG_PATH } = process.env;
@@ -117,30 +76,167 @@ module.exports = (cli) => {
117
76
  return;
118
77
  }
119
78
 
120
- const { port, client, server, inspect } = opts;
79
+ const { port, client, server, inspect, clientV2Only, rsbuild } = opts;
121
80
 
122
81
  if (port) {
123
82
  process.env.APP_PORT = opts.port;
124
83
  }
125
84
 
126
- const { APP_PORT } = process.env;
85
+ const APP_PORT = Number(process.env.APP_PORT);
127
86
 
128
87
  let clientPort = APP_PORT;
129
88
  let serverPort;
89
+ let clientV2Port = APP_PORT;
130
90
 
131
91
  nodeCheck();
132
-
133
92
  await postCheck(opts);
134
93
 
135
- if (server) {
94
+ const shouldRunClientV2 = clientV2Only || client || !server;
95
+ const shouldRunClient = !clientV2Only && (client || !server);
96
+ const shouldRunServer = !clientV2Only && (server || !client);
97
+ const shouldRunClientWithRsbuild = shouldRunClient && !!rsbuild;
98
+
99
+ if (shouldRunServer && server) {
136
100
  serverPort = APP_PORT;
137
- } else if (!server && !client) {
101
+ } else if (shouldRunServer) {
138
102
  serverPort = await getPortPromise({
139
103
  port: 1 * clientPort + 1,
140
104
  });
141
105
  }
142
106
 
143
- if (server || !client) {
107
+ if (shouldRunClientV2 && !clientV2Only) {
108
+ clientV2Port = await getPortPromise({
109
+ port: 1 * clientPort + 2,
110
+ });
111
+ }
112
+
113
+ let subprocessClient;
114
+ let subprocessClientV2;
115
+
116
+ const runDevClientV2 = () => {
117
+ console.log('starting client-v2', 1 * clientV2Port);
118
+ subprocessClientV2 = runWithPrefix(
119
+ 'rsbuild',
120
+ ['dev', '--config', `${APP_PACKAGE_ROOT}/client-v2/rsbuild.config.ts`],
121
+ {
122
+ prefix: 'client-v2',
123
+ color: 'magenta',
124
+ env: {
125
+ ...process.env,
126
+ APP_V2_PORT: `${clientV2Port}`,
127
+ NODE_ENV: 'development',
128
+ RSPACK_HMR_CLIENT_PORT: `${clientV2Only ? clientV2Port : clientPort}`,
129
+ API_BASE_URL: process.env.API_BASE_URL || process.env.API_BASE_PATH,
130
+ API_CLIENT_STORAGE_PREFIX: process.env.API_CLIENT_STORAGE_PREFIX,
131
+ API_CLIENT_STORAGE_TYPE: process.env.API_CLIENT_STORAGE_TYPE,
132
+ API_CLIENT_SHARE_TOKEN: process.env.API_CLIENT_SHARE_TOKEN || 'false',
133
+ WEBSOCKET_URL: process.env.WEBSOCKET_URL || buildWSURL(process.env.API_BASE_URL, serverPort),
134
+ WS_PATH: process.env.WS_PATH,
135
+ ESM_CDN_BASE_URL: process.env.ESM_CDN_BASE_URL || 'https://esm.sh',
136
+ ESM_CDN_SUFFIX: process.env.ESM_CDN_SUFFIX || '',
137
+ PROXY_TARGET_URL:
138
+ process.env.PROXY_TARGET_URL || (serverPort ? `http://127.0.0.1:${serverPort}` : undefined),
139
+ },
140
+ },
141
+ );
142
+ };
143
+
144
+ if (clientV2Only) {
145
+ runDevClientV2();
146
+ return;
147
+ }
148
+
149
+ const runDevClient = () => {
150
+ console.log('starting client', 1 * clientPort);
151
+ const command = shouldRunClientWithRsbuild ? 'rsbuild' : 'umi';
152
+ const args = shouldRunClientWithRsbuild
153
+ ? ['dev', '--config', `${APP_PACKAGE_ROOT}/client/rsbuild.config.ts`]
154
+ : ['dev'];
155
+ subprocessClient = runWithPrefix(command, args, {
156
+ prefix: 'client',
157
+ color: 'cyan',
158
+ env: {
159
+ ...process.env,
160
+ stdio: 'inherit',
161
+ shell: true,
162
+ PORT: clientPort,
163
+ APP_PORT: `${clientPort}`,
164
+ APP_ROOT: `${APP_PACKAGE_ROOT}/client`,
165
+ APP_V2_PORT: `${clientV2Port}`,
166
+ NODE_ENV: 'development',
167
+ RSPACK_HMR_CLIENT_PORT: `${clientPort}`,
168
+ API_BASE_URL: process.env.API_BASE_URL || process.env.API_BASE_PATH,
169
+ API_CLIENT_STORAGE_PREFIX: process.env.API_CLIENT_STORAGE_PREFIX,
170
+ API_CLIENT_STORAGE_TYPE: process.env.API_CLIENT_STORAGE_TYPE,
171
+ API_CLIENT_SHARE_TOKEN: process.env.API_CLIENT_SHARE_TOKEN || 'false',
172
+ WEBSOCKET_URL: process.env.WEBSOCKET_URL || buildWSURL(process.env.API_BASE_URL, serverPort),
173
+ PROXY_TARGET_URL:
174
+ process.env.PROXY_TARGET_URL || (serverPort ? `http://127.0.0.1:${serverPort}` : undefined),
175
+ },
176
+ });
177
+ };
178
+
179
+ const restartSubprocess = async (subprocessRef, port, start) => {
180
+ if (!subprocessRef) {
181
+ start();
182
+ return;
183
+ }
184
+ subprocessRef.cancel();
185
+ let i = 0;
186
+ while (true) {
187
+ ++i;
188
+ const result = await isPortReachable(port);
189
+ if (!result) {
190
+ break;
191
+ }
192
+ await sleep(500);
193
+ if (i > 10) {
194
+ break;
195
+ }
196
+ }
197
+ start();
198
+ };
199
+
200
+ if (shouldRunClient) {
201
+ const storagePluginPath = path.resolve(process.cwd(), 'storage/plugins');
202
+ const watcher = chokidar.watch(`${storagePluginPath}/**/*`, {
203
+ cwd: process.cwd(),
204
+ ignored: /(^|[\/\\])\../, // 忽略隐藏文件
205
+ persistent: true,
206
+ depth: 1, // 只监听第一层目录
207
+ });
208
+
209
+ await fs.promises.mkdir(path.dirname(process.env.WATCH_FILE), { recursive: true });
210
+ let isReady = false;
211
+
212
+ const restartClient = _.debounce(async () => {
213
+ if (!isReady) return;
214
+ generatePlugins();
215
+ if (shouldRunClient) {
216
+ await restartSubprocess(subprocessClient, clientPort, runDevClient);
217
+ }
218
+ if (shouldRunClientV2) {
219
+ await restartSubprocess(subprocessClientV2, clientV2Port, runDevClientV2);
220
+ }
221
+ await fs.promises.writeFile(process.env.WATCH_FILE, `export const watchId = '${uid()}';`, 'utf-8');
222
+ }, 500);
223
+
224
+ watcher
225
+ .on('ready', () => {
226
+ console.log('watching plugin folder changes...');
227
+ isReady = true;
228
+ })
229
+ .on('addDir', async () => {
230
+ if (!isReady) return;
231
+ restartClient();
232
+ })
233
+ .on('unlinkDir', async () => {
234
+ if (!isReady) return;
235
+ restartClient();
236
+ });
237
+ }
238
+
239
+ if (shouldRunServer) {
144
240
  console.log('starting server', serverPort);
145
241
 
146
242
  const filteredArgs = process.argv.filter(
@@ -183,8 +279,12 @@ module.exports = (cli) => {
183
279
  runDevServer();
184
280
  }
185
281
 
186
- if (client || !server) {
282
+ if (shouldRunClient) {
187
283
  runDevClient();
188
284
  }
285
+
286
+ if (shouldRunClientV2) {
287
+ runDevClientV2();
288
+ }
189
289
  });
190
290
  };
@@ -38,6 +38,7 @@ module.exports = (cli) => {
38
38
  require('./pkg')(cli);
39
39
  require('./instance-id')(cli);
40
40
  require('./view-license-key')(cli);
41
+ require('./client')(cli);
41
42
  if (isPackageValid('@umijs/utils')) {
42
43
  require('./create-plugin')(cli);
43
44
  }
@@ -208,7 +208,7 @@ class PackageManager {
208
208
  showLicenseInfo(LicenseKeyError.notValid);
209
209
  }
210
210
  logger.error(`Login failed: ${this.baseURL}`);
211
- logger.error(error?.message || error);
211
+ logger.error(error?.message, { error });
212
212
  }
213
213
  }
214
214
 
@@ -283,7 +283,12 @@ module.exports = (cli) => {
283
283
  NOCOBASE_PKG_URL = 'https://pkg.nocobase.com/',
284
284
  NOCOBASE_PKG_USERNAME,
285
285
  NOCOBASE_PKG_PASSWORD,
286
+ DISABLE_PKG_DOWNLOAD,
286
287
  } = process.env;
288
+ if (DISABLE_PKG_DOWNLOAD === 'true') {
289
+ logger.info('Package download is disabled.');
290
+ return;
291
+ }
287
292
  let accessKeyId;
288
293
  let accessKeySecret;
289
294
  try {
@@ -32,6 +32,6 @@ module.exports = (cli) => {
32
32
  .command('pm2-stop')
33
33
  .allowUnknownOption()
34
34
  .action(() => {
35
- run('pm2', ['stop', 'all']);
35
+ run('pm2', ['kill']);
36
36
  });
37
37
  };
@@ -62,6 +62,7 @@ module.exports = (cli) => {
62
62
  if (descJson['devDependencies']?.['@nocobase/devtools']) {
63
63
  descJson['devDependencies']['@nocobase/devtools'] = stdout;
64
64
  }
65
+ descJson['resolutions'] = sourceJson['resolutions'];
65
66
  const json = deepmerge(descJson, sourceJson);
66
67
  await writeJSON(descPath, json, { spaces: 2, encoding: 'utf8' });
67
68
  await run('yarn', ['install']);
package/src/license.js CHANGED
@@ -18,7 +18,7 @@ const { pick } = require('lodash');
18
18
  exports.getAccessKeyPair = async function () {
19
19
  const keyFile = resolve(process.cwd(), 'storage/.license/license-key');
20
20
  if (!fs.existsSync(keyFile)) {
21
- logger.error('License key not found');
21
+ logger.info('License key not found');
22
22
  return {};
23
23
  }
24
24
  logger.info('License key found');
package/src/util.js CHANGED
@@ -83,6 +83,44 @@ exports.run = (command, args, options = {}) => {
83
83
  });
84
84
  };
85
85
 
86
+ exports.runWithPrefix = (command, args, options = {}) => {
87
+ if (command === 'tsx') {
88
+ command = 'node';
89
+ args = ['./node_modules/tsx/dist/cli.mjs'].concat(args || []);
90
+ }
91
+
92
+ const prefix = options.prefix || 'process';
93
+ const color = options.color || 'cyan';
94
+ const label = chalk[color](`[${prefix}]`);
95
+ const subprocess = execa(command, args, {
96
+ shell: true,
97
+ stdio: 'pipe',
98
+ ...options,
99
+ env: {
100
+ ...process.env,
101
+ ...options.env,
102
+ },
103
+ });
104
+
105
+ const writePrefixed = (chunk, writer) => {
106
+ const text = chunk.toString();
107
+ const lines = text.split(/\r?\n/);
108
+ const trailingNewline = text.endsWith('\n') || text.endsWith('\r');
109
+
110
+ lines.forEach((line, index) => {
111
+ if (!line && index === lines.length - 1 && trailingNewline) {
112
+ return;
113
+ }
114
+ writer.write(`${label} ${line}\n`);
115
+ });
116
+ };
117
+
118
+ subprocess.stdout?.on('data', (chunk) => writePrefixed(chunk, process.stdout));
119
+ subprocess.stderr?.on('data', (chunk) => writePrefixed(chunk, process.stderr));
120
+
121
+ return subprocess;
122
+ };
123
+
86
124
  exports.isPortReachable = async (port, { timeout = 1000, host } = {}) => {
87
125
  const promise = new Promise((resolve, reject) => {
88
126
  const socket = new net.Socket();
@@ -232,6 +270,7 @@ exports.genTsConfigPaths = function genTsConfigPaths() {
232
270
  .split(sep)
233
271
  .join('/');
234
272
  paths[`${packageJsonName}/client`] = [`${relativePath}/src/client`];
273
+ paths[`${packageJsonName}/client-v2`] = [`${relativePath}/src/client-v2`];
235
274
  paths[`${packageJsonName}/package.json`] = [`${relativePath}/package.json`];
236
275
  paths[packageJsonName] = [`${relativePath}/src`];
237
276
  if (packageJsonName === '@nocobase/test') {
@@ -282,6 +321,21 @@ function parseEnv(name) {
282
321
  }
283
322
  }
284
323
 
324
+ function resolvePublicPath(appPublicPath = '/') {
325
+ const normalized = String(appPublicPath || '/').trim() || '/';
326
+ const withLeadingSlash = normalized.startsWith('/') ? normalized : `/${normalized}`;
327
+ return withLeadingSlash.endsWith('/') ? withLeadingSlash : `${withLeadingSlash}/`;
328
+ }
329
+
330
+ exports.resolvePublicPath = resolvePublicPath;
331
+
332
+ function resolveV2PublicPath(appPublicPath = '/') {
333
+ const publicPath = resolvePublicPath(appPublicPath);
334
+ return `${publicPath.replace(/\/$/, '')}/v2/`;
335
+ }
336
+
337
+ exports.resolveV2PublicPath = resolveV2PublicPath;
338
+
285
339
  function buildIndexHtml(force = false) {
286
340
  const file = `${process.env.APP_PACKAGE_ROOT}/dist/client/index.html`;
287
341
  if (!fs.existsSync(file)) {
@@ -295,14 +349,26 @@ function buildIndexHtml(force = false) {
295
349
  fs.copyFileSync(file, tpl);
296
350
  }
297
351
  const data = fs.readFileSync(tpl, 'utf-8');
298
- const replacedData = data
352
+ let replacedData = data
353
+ .replace(/\{\{env.CDN_BASE_URL\}\}/g, process.env.CDN_BASE_URL)
299
354
  .replace(/\{\{env.APP_PUBLIC_PATH\}\}/g, process.env.APP_PUBLIC_PATH)
355
+ .replace(/\{\{env.API_CLIENT_SHARE_TOKEN\}\}/g, process.env.API_CLIENT_SHARE_TOKEN || 'false')
300
356
  .replace(/\{\{env.API_CLIENT_STORAGE_TYPE\}\}/g, process.env.API_CLIENT_STORAGE_TYPE)
301
357
  .replace(/\{\{env.API_CLIENT_STORAGE_PREFIX\}\}/g, process.env.API_CLIENT_STORAGE_PREFIX)
302
358
  .replace(/\{\{env.API_BASE_URL\}\}/g, process.env.API_BASE_URL || process.env.API_BASE_PATH)
303
359
  .replace(/\{\{env.WS_URL\}\}/g, process.env.WEBSOCKET_URL || '')
304
360
  .replace(/\{\{env.WS_PATH\}\}/g, process.env.WS_PATH)
361
+ .replace(/\{\{env.ESM_CDN_BASE_URL\}\}/g, process.env.ESM_CDN_BASE_URL || '')
362
+ .replace(/\{\{env.ESM_CDN_SUFFIX\}\}/g, process.env.ESM_CDN_SUFFIX || '')
305
363
  .replace('src="/umi.', `src="${process.env.APP_PUBLIC_PATH}umi.`);
364
+
365
+ if (process.env.CDN_BASE_URL) {
366
+ const appBaseUrl = process.env.CDN_BASE_URL.replace(/\/+$/, '');
367
+ const appPublicPath = process.env.APP_PUBLIC_PATH.replace(/\/+$/, '');
368
+ const re1 = new RegExp(`src="${appPublicPath}/`, 'g');
369
+ const re2 = new RegExp(`href="${appPublicPath}/`, 'g');
370
+ replacedData = replacedData.replace(re1, `src="${appBaseUrl}/`).replace(re2, `href="${appBaseUrl}/`);
371
+ }
306
372
  fs.writeFileSync(file, replacedData, 'utf-8');
307
373
  }
308
374
 
@@ -360,6 +426,7 @@ exports.initEnv = function initEnv() {
360
426
  APP_PORT: 13000,
361
427
  API_BASE_PATH: '/api/',
362
428
  API_CLIENT_STORAGE_PREFIX: 'NOCOBASE_',
429
+ API_CLIENT_SHARE_TOKEN: 'false',
363
430
  API_CLIENT_STORAGE_TYPE: 'localStorage',
364
431
  // DB_DIALECT: 'sqlite',
365
432
  DB_STORAGE: 'storage/db/nocobase.sqlite',
@@ -383,8 +450,12 @@ exports.initEnv = function initEnv() {
383
450
  PLUGIN_STATICS_PATH: '/static/plugins/',
384
451
  LOGGER_BASE_PATH: 'storage/logs',
385
452
  APP_SERVER_BASE_URL: '',
453
+ APP_BASE_URL: '',
454
+ CDN_BASE_URL: '',
386
455
  APP_PUBLIC_PATH: '/',
387
456
  WATCH_FILE: resolve(process.cwd(), 'storage/app.watch.ts'),
457
+ ESM_CDN_BASE_URL: 'https://esm.sh',
458
+ ESM_CDN_SUFFIX: '',
388
459
  };
389
460
 
390
461
  if (
@@ -438,6 +509,16 @@ exports.initEnv = function initEnv() {
438
509
  process.env.__env_modified__ = true;
439
510
  }
440
511
 
512
+ if (!process.env.CDN_BASE_URL && process.env.APP_PUBLIC_PATH !== '/') {
513
+ process.env.CDN_BASE_URL = process.env.APP_PUBLIC_PATH;
514
+ }
515
+
516
+ if (process.env.CDN_BASE_URL.includes('http') && process.env.CDN_VERSION === 'auto') {
517
+ const version = require('../package.json').version;
518
+ process.env.CDN_BASE_URL = process.env.CDN_BASE_URL.replace(/\/+$/, '') + '/' + version + '/';
519
+ process.env.CDN_VERSION = '';
520
+ }
521
+
441
522
  if (!process.env.TZ) {
442
523
  const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
443
524
  process.env.TZ = getTimezonesByOffset(process.env.DB_TIMEZONE || timeZone);
@@ -481,10 +562,40 @@ exports.checkDBDialect = function () {
481
562
 
482
563
  exports.generatePlugins = function () {
483
564
  try {
484
- require.resolve('@nocobase/devtools/umiConfig');
485
- const { generatePlugins } = require('@nocobase/devtools/umiConfig');
486
- generatePlugins();
565
+ require.resolve('@nocobase/devtools/common');
566
+ const { generateAllPlugins, generatePlugins, generateV2Plugins } = require('@nocobase/devtools/common');
567
+ if (typeof generateAllPlugins === 'function') {
568
+ generateAllPlugins();
569
+ return;
570
+ }
571
+ generatePlugins?.();
572
+ generateV2Plugins?.();
487
573
  } catch (error) {
488
574
  return;
489
575
  }
490
576
  };
577
+
578
+ exports.isURL = function isURL(str) {
579
+ let url;
580
+
581
+ try {
582
+ url = new URL(str);
583
+ } catch (e) {
584
+ return false;
585
+ }
586
+
587
+ return url.protocol === 'http:' || url.protocol === 'https:';
588
+ };
589
+
590
+ exports.buildWSURL = function buildWSURL(urlString, serverPort) {
591
+ if (exports.isURL(urlString)) {
592
+ const parsedUrl = new URL(urlString);
593
+ parsedUrl.protocol = parsedUrl.protocol === 'https:' ? 'wss:' : 'ws:';
594
+ parsedUrl.pathname =
595
+ process.env.WS_PATH || (process.env.APP_PUBLIC_PATH ? process.env.APP_PUBLIC_PATH + 'ws' : '/ws');
596
+ const url = parsedUrl.toString();
597
+ return url;
598
+ }
599
+
600
+ return serverPort ? `ws://localhost:${serverPort}${process.env.WS_PATH}` : undefined;
601
+ };