@4399ywkf/core 5.0.2 → 5.0.5

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/dist/index.js CHANGED
@@ -136,6 +136,7 @@ function createPathResolver(cwd) {
136
136
  import { RsdoctorRspackPlugin } from "@rsdoctor/rspack-plugin";
137
137
 
138
138
  // src/rspack/dev.ts
139
+ import ReactRefreshPlugin from "@rspack/plugin-react-refresh";
139
140
  import { merge } from "webpack-merge";
140
141
  import { createRequire as createRequire2 } from "module";
141
142
 
@@ -447,7 +448,7 @@ export default routes;
447
448
  mkdirSync(outputDir, { recursive: true });
448
449
  }
449
450
  writeFileSync(join(outputDir, "routes.tsx"), code, "utf-8");
450
- console.log(`[ywkf] \u7EA6\u5B9A\u5F0F\u8DEF\u7531\u5DF2\u751F\u6210`);
451
+ if (process.env.DEBUG) console.log(`[ywkf] \u7EA6\u5B9A\u5F0F\u8DEF\u7531\u5DF2\u751F\u6210`);
451
452
  }
452
453
  };
453
454
  function generateConventionalRoutes(options) {
@@ -464,17 +465,33 @@ function generateEntry(config, injections = {}) {
464
465
  const topLevel = injections.topLevel || [];
465
466
  const exports = injections.exports || [];
466
467
  const hasPluginExports = exports.length > 0;
467
- const startupCode = hasPluginExports ? `// \u72EC\u7ACB\u8FD0\u884C\u6A21\u5F0F
468
- if (shouldRunIndependently !== false) {
469
- runApp();
470
- }` : `// \u542F\u52A8\u5E94\u7528
471
- runApp();`;
468
+ const hasAsyncTopLevel = topLevel.some((line) => line.includes("await "));
469
+ let startupBody;
470
+ if (hasPluginExports) {
471
+ startupBody = [
472
+ ...topLevel,
473
+ ...exports,
474
+ ``,
475
+ `if (shouldRunIndependently !== false) {`,
476
+ ` runApp();`,
477
+ `}`
478
+ ].join("\n");
479
+ } else if (hasAsyncTopLevel) {
480
+ startupBody = [
481
+ `(async () => {`,
482
+ ...topLevel.map((l) => ` ${l}`),
483
+ ` runApp();`,
484
+ `})();`
485
+ ].join("\n");
486
+ } else {
487
+ startupBody = topLevel.length > 0 ? [...topLevel, `runApp();`].join("\n") : `runApp();`;
488
+ }
472
489
  return `// \u6B64\u6587\u4EF6\u7531 @4399ywkf/core \u81EA\u52A8\u751F\u6210\uFF0C\u8BF7\u52FF\u624B\u52A8\u4FEE\u6539
473
490
  // Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
474
491
 
475
492
  ${imports.join("\n")}
476
493
 
477
- ${topLevel.length > 0 ? topLevel.join("\n") + "\n" : ""}${exports.length > 0 ? exports.join("\n") + "\n\n" : ""}${startupCode}
494
+ ${startupBody}
478
495
  `;
479
496
  }
480
497
 
@@ -523,12 +540,13 @@ async function getUserConfig(): Promise<Partial<AppConfig>> {
523
540
  * \u521B\u5EFA\u5E94\u7528\u914D\u7F6E
524
541
  */
525
542
  export function createAppConfig(userConfig: Partial<AppConfig> = {}): AppConfig {
526
- const router = createRouter(BASENAME);
543
+ const effectiveBasename = userConfig.basename || BASENAME;
544
+ const router = createRouter(effectiveBasename);
527
545
 
528
546
  const defaultConfig: AppConfig = {
529
547
  appName: APP_NAME,
530
548
  router,
531
- basename: BASENAME,
549
+ basename: effectiveBasename,
532
550
  rootId: APP_NAME,
533
551
  strictMode: true,
534
552
  antd: {
@@ -721,7 +739,7 @@ var YwkfGenerator = class {
721
739
  await hooks.afterGenerate(this.context);
722
740
  }
723
741
  }
724
- console.log(`[ywkf] \u5DF2\u751F\u6210 .ywkf \u76EE\u5F55`);
742
+ if (process.env.DEBUG) console.log(`[ywkf] \u5DF2\u751F\u6210 .ywkf \u76EE\u5F55`);
725
743
  }
726
744
  /**
727
745
  * 生成配置快照
@@ -947,13 +965,13 @@ var YwkfGeneratorPlugin = class {
947
965
  isProd: !isDev,
948
966
  config,
949
967
  logger: {
950
- info: (msg) => console.log(`[ywkf] ${msg}`),
968
+ info: (msg) => {
969
+ if (process.env.DEBUG) console.log(`[ywkf] ${msg}`);
970
+ },
951
971
  warn: (msg) => console.warn(`[ywkf] ${msg}`),
952
972
  error: (msg) => console.error(`[ywkf] ${msg}`),
953
973
  debug: (msg) => {
954
- if (process.env.DEBUG) {
955
- console.log(`[ywkf:debug] ${msg}`);
956
- }
974
+ if (process.env.DEBUG) console.log(`[ywkf:debug] ${msg}`);
957
975
  }
958
976
  }
959
977
  };
@@ -962,7 +980,7 @@ var YwkfGeneratorPlugin = class {
962
980
  if (plugin) {
963
981
  const hooks = await plugin.setup(context);
964
982
  this.pluginHooks.push(hooks);
965
- console.log(`[ywkf] \u63D2\u4EF6\u5DF2\u52A0\u8F7D: ${plugin.name}`);
983
+ if (process.env.DEBUG) console.log(`[ywkf] \u63D2\u4EF6\u5DF2\u52A0\u8F7D: ${plugin.name}`);
966
984
  }
967
985
  }
968
986
  this.generator = new YwkfGenerator(
@@ -1016,7 +1034,7 @@ var YwkfGeneratorPlugin = class {
1016
1034
  clearTimeout(debounceTimer);
1017
1035
  }
1018
1036
  debounceTimer = setTimeout(async () => {
1019
- console.log(`[ywkf] \u68C0\u6D4B\u5230\u9875\u9762\u53D8\u5316: ${filename}`);
1037
+ if (process.env.DEBUG) console.log(`[ywkf] \u68C0\u6D4B\u5230\u9875\u9762\u53D8\u5316: ${filename}`);
1020
1038
  if (this.generator) {
1021
1039
  await this.generator.generate();
1022
1040
  }
@@ -1033,7 +1051,8 @@ var YwkfGeneratorPlugin = class {
1033
1051
  var require2 = createRequire(import.meta.url);
1034
1052
  var __dirname = dirname(fileURLToPath(import.meta.url));
1035
1053
  var coreNodeModules = join5(__dirname, "../../node_modules");
1036
- function createBaseConfig(config, cwd) {
1054
+ function createBaseConfig(config, cwd, options = {}) {
1055
+ const isDev = options.isDev ?? process.env.NODE_ENV !== "production";
1037
1056
  const { resolveApp } = createPathResolver(cwd);
1038
1057
  const {
1039
1058
  appName,
@@ -1064,12 +1083,11 @@ function createBaseConfig(config, cwd) {
1064
1083
  entry: [join5(ywkfOutputDir, "index.tsx")],
1065
1084
  resolve: {
1066
1085
  extensions: [".ts", ".tsx", ".jsx", ".js", ".json", ".mjs"],
1067
- symlinks: false,
1086
+ symlinks: true,
1068
1087
  alias: {
1069
1088
  ...alias,
1070
1089
  "process/browser.js": require2.resolve("process/browser.js"),
1071
1090
  "process/browser": require2.resolve("process/browser.js"),
1072
- // 确保 React 系列包从用户项目的 node_modules 解析
1073
1091
  react: resolveApp("node_modules/react"),
1074
1092
  "react-dom": resolveApp("node_modules/react-dom"),
1075
1093
  "react-router": resolveApp("node_modules/react-router")
@@ -1127,7 +1145,9 @@ function createBaseConfig(config, cwd) {
1127
1145
  },
1128
1146
  transform: {
1129
1147
  react: {
1130
- runtime: "automatic"
1148
+ runtime: "automatic",
1149
+ development: isDev,
1150
+ refresh: isDev
1131
1151
  }
1132
1152
  },
1133
1153
  target: "es2015"
@@ -1241,7 +1261,7 @@ function convertProxyToArray(proxy) {
1241
1261
  });
1242
1262
  }
1243
1263
  function createDevConfig(config, cwd) {
1244
- const baseConfig = createBaseConfig(config, cwd);
1264
+ const baseConfig = createBaseConfig(config, cwd, { isDev: true });
1245
1265
  const { resolveApp } = createPathResolver(cwd);
1246
1266
  const { dev: dev2, style } = config;
1247
1267
  const styleRules = {
@@ -1425,6 +1445,9 @@ function createDevConfig(config, cwd) {
1425
1445
  clean: false
1426
1446
  },
1427
1447
  stats: "errors-only",
1448
+ plugins: [
1449
+ new ReactRefreshPlugin()
1450
+ ],
1428
1451
  devServer: {
1429
1452
  host: dev2.host,
1430
1453
  port: dev2.port,
@@ -1447,7 +1470,7 @@ function createDevConfig(config, cwd) {
1447
1470
  import { rspack as rspack2 } from "@rspack/core";
1448
1471
  import { merge as merge2 } from "webpack-merge";
1449
1472
  function createProdConfig(config, cwd) {
1450
- const baseConfig = createBaseConfig(config, cwd);
1473
+ const baseConfig = createBaseConfig(config, cwd, { isDev: false });
1451
1474
  const { resolveApp } = createPathResolver(cwd);
1452
1475
  const { style, performance, output } = config;
1453
1476
  const styleRules = {
@@ -1764,7 +1787,7 @@ var ConventionalRoutePlugin = class {
1764
1787
  clearTimeout(debounceTimer);
1765
1788
  }
1766
1789
  debounceTimer = setTimeout(() => {
1767
- console.log(`[ywkf] \u68C0\u6D4B\u5230\u9875\u9762\u53D8\u5316: ${filename}`);
1790
+ if (process.env.DEBUG) console.log(`[ywkf] \u68C0\u6D4B\u5230\u9875\u9762\u53D8\u5316: ${filename}`);
1768
1791
  this.generateRoutes();
1769
1792
  }, 500);
1770
1793
  }
@@ -2175,14 +2198,15 @@ function createPlugin(factory) {
2175
2198
  // src/plugin/manager.ts
2176
2199
  function createLogger(pluginName) {
2177
2200
  const prefix = `[${pluginName}]`;
2201
+ const verbose = !!process.env.DEBUG;
2178
2202
  return {
2179
- info: (msg) => console.log(`${prefix} ${msg}`),
2203
+ info: (msg) => {
2204
+ if (verbose) console.log(`${prefix} ${msg}`);
2205
+ },
2180
2206
  warn: (msg) => console.warn(`${prefix} ${msg}`),
2181
2207
  error: (msg) => console.error(`${prefix} ${msg}`),
2182
2208
  debug: (msg) => {
2183
- if (process.env.DEBUG) {
2184
- console.debug(`${prefix} ${msg}`);
2185
- }
2209
+ if (verbose) console.debug(`${prefix} ${msg}`);
2186
2210
  }
2187
2211
  };
2188
2212
  }
@@ -2436,6 +2460,15 @@ var garfishPlugin = createPlugin((options = {}) => ({
2436
2460
  globalObject: "window",
2437
2461
  chunkLoadingGlobal: `garfish_${finalAppName}`
2438
2462
  };
2463
+ rspackConfig.devServer = {
2464
+ ...rspackConfig.devServer,
2465
+ headers: {
2466
+ ...rspackConfig.devServer?.headers || {},
2467
+ "Access-Control-Allow-Origin": "*",
2468
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
2469
+ "Access-Control-Allow-Headers": "*"
2470
+ }
2471
+ };
2439
2472
  logger.info(`\u914D\u7F6E UMD \u8F93\u51FA: ${finalAppName}`);
2440
2473
  }
2441
2474
  return rspackConfig;
@@ -2450,44 +2483,58 @@ var garfishPlugin = createPlugin((options = {}) => ({
2450
2483
  },
2451
2484
  // ===== 代码生成阶段钩子 =====
2452
2485
  injectEntry(ctx) {
2453
- if (master) {
2454
- return {};
2486
+ if (master && apps.length > 0) {
2487
+ return {
2488
+ imports: [
2489
+ `import { initGarfishMaster } from "./bootstrap";`
2490
+ ],
2491
+ topLevel: [
2492
+ `// Garfish \u4E3B\u5E94\u7528\uFF1A\u521D\u59CB\u5316\u5FAE\u524D\u7AEF\u5E76\u6CE8\u518C\u5B50\u5E94\u7528`,
2493
+ `await initGarfishMaster();`
2494
+ ]
2495
+ };
2455
2496
  }
2456
- return {
2457
- imports: [
2458
- `import { microApp, isMicroAppEnv } from "./bootstrap";`
2459
- ],
2460
- exports: [
2461
- `// Garfish \u5FAE\u524D\u7AEF\u751F\u547D\u5468\u671F\u5BFC\u51FA`,
2462
- `export const { mount, unmount } = microApp;`,
2463
- `export const provider = microApp;`,
2464
- `export default microApp;`
2465
- ],
2466
- topLevel: [
2467
- `// Garfish \u5FAE\u524D\u7AEF\uFF1A\u72EC\u7ACB\u8FD0\u884C\u5224\u65AD`,
2468
- `const shouldRunIndependently = !isMicroAppEnv();`
2469
- ]
2470
- };
2497
+ if (!master) {
2498
+ return {
2499
+ imports: [
2500
+ `import { garfishProvider, isMicroAppEnv } from "./bootstrap";`
2501
+ ],
2502
+ exports: [
2503
+ `// Garfish \u5B50\u5E94\u7528\u751F\u547D\u5468\u671F\u5BFC\u51FA\uFF08provider \u51FD\u6570\u683C\u5F0F\uFF09`,
2504
+ `export const provider = garfishProvider;`
2505
+ ],
2506
+ topLevel: [
2507
+ `// Garfish \u5FAE\u524D\u7AEF\uFF1A\u72EC\u7ACB\u8FD0\u884C\u5224\u65AD`,
2508
+ `const shouldRunIndependently = !isMicroAppEnv();`
2509
+ ]
2510
+ };
2511
+ }
2512
+ return {};
2471
2513
  },
2472
2514
  injectBootstrap(ctx) {
2473
- const imports = [
2474
- `import { createMicroApp, isMicroAppEnv } from "@4399ywkf/core/runtime";`
2475
- ];
2476
- const exports = [
2477
- `/**`,
2478
- ` * Garfish \u5FAE\u524D\u7AEF\u5E94\u7528\u5B9E\u4F8B`,
2479
- ` */`,
2480
- `export const microApp = createMicroApp(createAppConfig);`,
2481
- ``,
2482
- `/**`,
2483
- ` * \u5224\u65AD\u662F\u5426\u5728\u5FAE\u524D\u7AEF\u73AF\u5883`,
2484
- ` */`,
2485
- `export { isMicroAppEnv };`
2486
- ];
2515
+ const imports = [];
2516
+ const exports = [];
2487
2517
  const topLevel = [];
2488
- if (master && apps.length > 0) {
2489
- imports.push(`import Garfish from "garfish";`);
2490
- topLevel.push(generateMasterInitCode(apps, config.router.basename));
2518
+ if (master) {
2519
+ if (apps.length > 0) {
2520
+ imports.push(`import Garfish from "garfish";`);
2521
+ topLevel.push(generateMasterInitCode(apps, config.router.basename));
2522
+ }
2523
+ } else {
2524
+ imports.push(
2525
+ `import { createGarfishProvider, isMicroAppEnv } from "@4399ywkf/core/runtime";`
2526
+ );
2527
+ exports.push(
2528
+ `/**`,
2529
+ ` * Garfish \u5B50\u5E94\u7528 provider\uFF08render/destroy \u683C\u5F0F\uFF09`,
2530
+ ` */`,
2531
+ `export const garfishProvider = createGarfishProvider(createAppConfig);`,
2532
+ ``,
2533
+ `/**`,
2534
+ ` * \u5224\u65AD\u662F\u5426\u5728\u5FAE\u524D\u7AEF\u73AF\u5883`,
2535
+ ` */`,
2536
+ `export { isMicroAppEnv };`
2537
+ );
2491
2538
  }
2492
2539
  return { imports, exports, topLevel };
2493
2540
  },
@@ -2532,6 +2579,7 @@ function generateMasterInitCode(apps, basename2) {
2532
2579
  export async function initGarfishMaster(): Promise<void> {
2533
2580
  await Garfish.run({
2534
2581
  basename: "${basename2 || "/"}",
2582
+ domGetter: "#subapp-container",
2535
2583
  apps: [
2536
2584
  ${appConfigs}
2537
2585
  ],
@@ -3099,20 +3147,32 @@ export default queryClient;
3099
3147
  }
3100
3148
  function buildRequestFile(opts) {
3101
3149
  const { baseURL, timeout } = opts;
3102
- return `// \u6B64\u6587\u4EF6\u7531 @4399ywkf/plugin-react-query \u81EA\u52A8\u751F\u6210
3103
- import axios, { type AxiosInstance, type AxiosRequestConfig, type InternalAxiosRequestConfig, type AxiosResponse, type AxiosError } from "axios";
3150
+ return `// \u6B64\u6587\u4EF6\u7531 @4399ywkf/plugin-react-query \u81EA\u52A8\u751F\u6210\uFF0C\u8BF7\u52FF\u624B\u52A8\u4FEE\u6539
3151
+ // \u5982\u9700\u5B9A\u5236\u62E6\u622A\u5668\uFF0C\u8BF7\u7F16\u8F91 src/request.ts
3152
+ import axios from "axios";
3153
+ import type { AxiosInstance, InternalAxiosRequestConfig, AxiosResponse, AxiosError } from "axios";
3154
+ import type { RequestConfig, Result } from "@4399ywkf/core/runtime";
3155
+
3156
+ // ============ \u52A0\u8F7D\u7528\u6237\u914D\u7F6E ============
3157
+
3158
+ let userConfig: RequestConfig = {};
3159
+
3160
+ try {
3161
+ const mod = require("@/request");
3162
+ userConfig = mod.default || mod;
3163
+ } catch {
3164
+ // src/request.ts \u4E0D\u5B58\u5728\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u914D\u7F6E
3165
+ }
3166
+
3167
+ // ============ \u521B\u5EFA Axios \u5B9E\u4F8B ============
3104
3168
 
3105
- /**
3106
- * Axios \u5B9E\u4F8B
3107
- *
3108
- * \u57FA\u7840\u914D\u7F6E\u7531\u63D2\u4EF6\u751F\u6210\uFF0C\u53EF\u5728 src/app.config.ts \u4E2D\u901A\u8FC7
3109
- * \`configureRequest(request)\` \u81EA\u5B9A\u4E49\u62E6\u622A\u5668\u3002
3110
- */
3111
3169
  export const request: AxiosInstance = axios.create({
3112
- baseURL: "${baseURL}" || import.meta.env.VITE_API_BASE_URL || "",
3113
- timeout: ${timeout},
3170
+ baseURL: userConfig.baseURL ?? ("${baseURL}" || ""),
3171
+ timeout: userConfig.timeout ?? ${timeout},
3172
+ withCredentials: userConfig.withCredentials ?? false,
3114
3173
  headers: {
3115
3174
  "Content-Type": "application/json",
3175
+ ...(userConfig.headers || {}),
3116
3176
  },
3117
3177
  });
3118
3178
 
@@ -3120,13 +3180,26 @@ export const request: AxiosInstance = axios.create({
3120
3180
 
3121
3181
  request.interceptors.request.use(
3122
3182
  (config: InternalAxiosRequestConfig) => {
3123
- // Token \u6CE8\u5165\uFF08\u4ECE localStorage \u8BFB\u53D6\uFF0C\u53EF\u6309\u9700\u66FF\u6362\uFF09
3124
- const token = typeof localStorage !== "undefined"
3125
- ? localStorage.getItem("token")
3126
- : null;
3183
+ // 1. Token \u6CE8\u5165
3184
+ if (userConfig.getToken) {
3185
+ const token = userConfig.getToken();
3186
+ if (token && config.headers) {
3187
+ const prefix = userConfig.tokenPrefix ?? "Bearer";
3188
+ config.headers.Authorization = prefix ? \`\${prefix} \${token}\` : token;
3189
+ }
3190
+ } else {
3191
+ // \u9ED8\u8BA4\uFF1A\u4ECE localStorage \u8BFB\u53D6
3192
+ const token = typeof localStorage !== "undefined"
3193
+ ? localStorage.getItem("token")
3194
+ : null;
3195
+ if (token && config.headers) {
3196
+ config.headers.Authorization = \`Bearer \${token}\`;
3197
+ }
3198
+ }
3127
3199
 
3128
- if (token && config.headers) {
3129
- config.headers.Authorization = \`Bearer \${token}\`;
3200
+ // 2. \u7528\u6237\u81EA\u5B9A\u4E49\u8BF7\u6C42\u62E6\u622A
3201
+ if (userConfig.requestInterceptor) {
3202
+ return userConfig.requestInterceptor(config as any) as any;
3130
3203
  }
3131
3204
 
3132
3205
  return config;
@@ -3138,10 +3211,21 @@ request.interceptors.request.use(
3138
3211
 
3139
3212
  request.interceptors.response.use(
3140
3213
  (response: AxiosResponse) => {
3141
- // \u76F4\u63A5\u8FD4\u56DE data \u5C42\uFF0C\u51CF\u5C11\u4E1A\u52A1\u4EE3\u7801\u89E3\u6784
3214
+ // \u7528\u6237\u81EA\u5B9A\u4E49\u54CD\u5E94\u62E6\u622A
3215
+ if (userConfig.responseInterceptor) {
3216
+ return userConfig.responseInterceptor(response as any);
3217
+ }
3218
+
3219
+ // \u9ED8\u8BA4\uFF1A\u76F4\u63A5\u8FD4\u56DE response.data
3142
3220
  return response.data;
3143
3221
  },
3144
3222
  (error: AxiosError) => {
3223
+ // \u7528\u6237\u81EA\u5B9A\u4E49\u9519\u8BEF\u5904\u7406
3224
+ if (userConfig.errorHandler) {
3225
+ return userConfig.errorHandler(error as any);
3226
+ }
3227
+
3228
+ // \u9ED8\u8BA4\u9519\u8BEF\u5904\u7406
3145
3229
  const status = error.response?.status;
3146
3230
 
3147
3231
  switch (status) {
@@ -3151,9 +3235,6 @@ request.interceptors.response.use(
3151
3235
  case 403:
3152
3236
  console.warn("[request] \u65E0\u8BBF\u95EE\u6743\u9650");
3153
3237
  break;
3154
- case 404:
3155
- console.warn("[request] \u8BF7\u6C42\u8D44\u6E90\u4E0D\u5B58\u5728");
3156
- break;
3157
3238
  case 500:
3158
3239
  console.error("[request] \u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF");
3159
3240
  break;
@@ -3167,6 +3248,9 @@ request.interceptors.response.use(
3167
3248
  }
3168
3249
  );
3169
3250
 
3251
+ // ============ \u5BFC\u51FA ============
3252
+
3253
+ export type { Result };
3170
3254
  export default request;
3171
3255
  `;
3172
3256
  }
@@ -3415,8 +3499,7 @@ export type { StateCreator, StoreApi } from "zustand";
3415
3499
  // src/cli/dev.ts
3416
3500
  import { rspack as rspack3 } from "@rspack/core";
3417
3501
  import { RspackDevServer } from "@rspack/dev-server";
3418
- import chalk from "chalk";
3419
- import ora from "ora";
3502
+ import chalk2 from "chalk";
3420
3503
 
3421
3504
  // src/cli/env.ts
3422
3505
  import { existsSync as existsSync11, readFileSync as readFileSync4 } from "fs";
@@ -3473,42 +3556,306 @@ function loadEnv(config, cwd, mode) {
3473
3556
  process.env.APP_PORT = process.env.APP_PORT || String(config.dev.port);
3474
3557
  }
3475
3558
 
3559
+ // src/cli/port.ts
3560
+ import net from "net";
3561
+ function isPortAvailable(port, host) {
3562
+ return new Promise((resolve4) => {
3563
+ const server = net.createServer();
3564
+ server.once("error", (err) => {
3565
+ if (err.code === "EADDRINUSE") {
3566
+ resolve4(false);
3567
+ } else {
3568
+ resolve4(false);
3569
+ }
3570
+ });
3571
+ server.once("listening", () => {
3572
+ server.close(() => resolve4(true));
3573
+ });
3574
+ server.listen(port, host);
3575
+ });
3576
+ }
3577
+ async function getAvailablePort(preferredPort, host) {
3578
+ const MAX_ATTEMPTS = 20;
3579
+ for (let i = 0; i < MAX_ATTEMPTS; i++) {
3580
+ const port = preferredPort + i;
3581
+ const available = await isPortAvailable(port, host);
3582
+ if (available) return port;
3583
+ }
3584
+ throw new Error(
3585
+ `No available port found in range ${preferredPort}-${preferredPort + MAX_ATTEMPTS - 1}`
3586
+ );
3587
+ }
3588
+
3589
+ // src/cli/printer.ts
3590
+ import chalk from "chalk";
3591
+ import os from "os";
3592
+ import { createRequire as createRequire3 } from "module";
3593
+ import { fileURLToPath as fileURLToPath2 } from "url";
3594
+ import { dirname as dirname2, join as join11 } from "path";
3595
+ var __filename = fileURLToPath2(import.meta.url);
3596
+ var __dirname2 = dirname2(__filename);
3597
+ var require4 = createRequire3(import.meta.url);
3598
+ var _version = null;
3599
+ function getFrameworkVersion() {
3600
+ if (_version) return _version;
3601
+ try {
3602
+ const pkgPath = join11(__dirname2, "../../package.json");
3603
+ const pkg = require4(pkgPath);
3604
+ _version = pkg.version || "0.0.0";
3605
+ } catch {
3606
+ _version = "0.0.0";
3607
+ }
3608
+ return _version;
3609
+ }
3610
+ function getNetworkAddress(port) {
3611
+ const interfaces = os.networkInterfaces();
3612
+ for (const entries of Object.values(interfaces)) {
3613
+ if (!entries) continue;
3614
+ for (const entry of entries) {
3615
+ if (entry.family === "IPv4" && !entry.internal) {
3616
+ return `http://${entry.address}:${port}/`;
3617
+ }
3618
+ }
3619
+ }
3620
+ return null;
3621
+ }
3622
+ var BAR_WIDTH = 30;
3623
+ function renderBar(percent) {
3624
+ const filled = Math.round(BAR_WIDTH * percent);
3625
+ const empty = BAR_WIDTH - filled;
3626
+ const bar = chalk.green("\u2501".repeat(filled)) + chalk.gray("\u2501".repeat(empty));
3627
+ return bar;
3628
+ }
3629
+ var DevPrinter = class {
3630
+ constructor(host, port, pluginNames) {
3631
+ this.host = host;
3632
+ this.port = port;
3633
+ this.pluginNames = pluginNames;
3634
+ }
3635
+ startTime = 0;
3636
+ lastProgressLine = "";
3637
+ firstCompileDone = false;
3638
+ /**
3639
+ * 打印框架 banner
3640
+ */
3641
+ printBanner() {
3642
+ const version = getFrameworkVersion();
3643
+ console.log();
3644
+ console.log(
3645
+ ` ${chalk.bold.cyan("@4399ywkf/core")} ${chalk.green(`Framework v${version}`)}`
3646
+ );
3647
+ console.log();
3648
+ }
3649
+ /**
3650
+ * 打印 "start build started..."
3651
+ */
3652
+ printBuildStart() {
3653
+ this.startTime = Date.now();
3654
+ this.compileDone = false;
3655
+ const label = chalk.gray("start");
3656
+ console.log(` ${label} build started...`);
3657
+ }
3658
+ /**
3659
+ * 更新编译进度条(原地覆写同一行)
3660
+ */
3661
+ updateProgress(percent, message) {
3662
+ const pct = Math.min(Math.round(percent * 100), 100);
3663
+ const bar = renderBar(percent);
3664
+ const status = message || "compiling";
3665
+ const line = ` ${chalk.yellow("\u25CF")} client ${bar} ${chalk.bold(`(${pct}%)`)} ${chalk.gray(status)}`;
3666
+ if (process.stdout.isTTY) {
3667
+ if (this.lastProgressLine) {
3668
+ process.stdout.write("\x1B[1A\x1B[2K");
3669
+ }
3670
+ process.stdout.write(line + "\n");
3671
+ } else if (!this.firstCompileDone && pct === 100) {
3672
+ console.log(line);
3673
+ }
3674
+ this.lastProgressLine = line;
3675
+ }
3676
+ /**
3677
+ * 编译完成
3678
+ */
3679
+ printBuildDone(hasErrors = false) {
3680
+ if (this.firstCompileDone) {
3681
+ if (!hasErrors) {
3682
+ const elapsed2 = ((Date.now() - this.startTime) / 1e3).toFixed(2);
3683
+ console.log(
3684
+ ` ${chalk.green("hmr")} update in ${chalk.bold(`${elapsed2} s`)}`
3685
+ );
3686
+ }
3687
+ return;
3688
+ }
3689
+ this.firstCompileDone = true;
3690
+ this.lastProgressLine = "";
3691
+ if (hasErrors) return;
3692
+ const elapsed = ((Date.now() - this.startTime) / 1e3).toFixed(2);
3693
+ const label = chalk.green("ready");
3694
+ console.log(` ${label} built in ${chalk.bold(`${elapsed} s`)}`);
3695
+ console.log();
3696
+ this.printServerInfo();
3697
+ }
3698
+ /**
3699
+ * 标记新一轮编译开始(HMR)
3700
+ */
3701
+ markRebuildStart() {
3702
+ this.startTime = Date.now();
3703
+ this.lastProgressLine = "";
3704
+ }
3705
+ /**
3706
+ * 打印服务器信息面板
3707
+ */
3708
+ printServerInfo() {
3709
+ const localUrl = `http://localhost:${this.port}/`;
3710
+ const networkUrl = getNetworkAddress(this.port);
3711
+ console.log(` ${chalk.bold(">")} Local: ${chalk.cyan(localUrl)}`);
3712
+ if (networkUrl) {
3713
+ console.log(` ${chalk.bold(">")} Network: ${chalk.cyan(networkUrl)}`);
3714
+ }
3715
+ console.log();
3716
+ if (this.pluginNames.length > 0) {
3717
+ console.log(` ${chalk.bold(">")} Plugins:`);
3718
+ for (const name of this.pluginNames) {
3719
+ const shortName = name.replace("@4399ywkf/plugin-", "").replace("@4399ywkf/", "");
3720
+ console.log(` ${chalk.green("\u2713")} ${shortName}`);
3721
+ }
3722
+ console.log();
3723
+ }
3724
+ console.log(
3725
+ ` ${chalk.bold(">")} press ${chalk.bold("h + enter")} to show shortcuts`
3726
+ );
3727
+ console.log();
3728
+ }
3729
+ /**
3730
+ * 打印快捷键帮助
3731
+ */
3732
+ printShortcuts() {
3733
+ console.log();
3734
+ console.log(` ${chalk.bold("Shortcuts:")}`);
3735
+ console.log(` ${chalk.bold("o + enter")} open in browser`);
3736
+ console.log(` ${chalk.bold("c + enter")} clear console`);
3737
+ console.log(` ${chalk.bold("q + enter")} quit`);
3738
+ console.log();
3739
+ }
3740
+ };
3741
+ function createProgressHandler(printer) {
3742
+ return (percent, message, ..._details) => {
3743
+ printer.updateProgress(percent, message);
3744
+ };
3745
+ }
3746
+ function registerShortcuts(printer, opts) {
3747
+ if (!process.stdin.isTTY) return;
3748
+ process.stdin.setEncoding("utf-8");
3749
+ process.stdin.setRawMode?.(false);
3750
+ let buffer = "";
3751
+ const handler = (data) => {
3752
+ buffer += data;
3753
+ if (!buffer.includes("\n")) return;
3754
+ const cmd = buffer.trim().toLowerCase();
3755
+ buffer = "";
3756
+ switch (cmd) {
3757
+ case "h":
3758
+ printer.printShortcuts();
3759
+ break;
3760
+ case "o": {
3761
+ const url = `http://localhost:${opts.port}/`;
3762
+ import("child_process").then((cp) => {
3763
+ const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
3764
+ cp.exec(`${command} ${url}`);
3765
+ });
3766
+ console.log(` ${chalk.green("\u279C")} Opening ${chalk.cyan(url)}...`);
3767
+ break;
3768
+ }
3769
+ case "c":
3770
+ console.clear();
3771
+ break;
3772
+ case "q":
3773
+ opts.onQuit();
3774
+ break;
3775
+ }
3776
+ };
3777
+ process.stdin.on("data", handler);
3778
+ process.stdin.resume();
3779
+ }
3780
+
3476
3781
  // src/cli/dev.ts
3477
3782
  async function dev(options = {}) {
3478
3783
  const cwd = options.cwd || process.cwd();
3479
- const spinner = ora("\u6B63\u5728\u542F\u52A8\u5F00\u53D1\u670D\u52A1\u5668...").start();
3480
3784
  try {
3481
3785
  preloadEnv(cwd, "development");
3482
- const { config, configPath } = await resolveConfig(cwd);
3483
- if (configPath) {
3484
- spinner.text = `\u5DF2\u52A0\u8F7D\u914D\u7F6E: ${configPath}`;
3485
- }
3786
+ const { config } = await resolveConfig(cwd);
3486
3787
  loadEnv(config, cwd, "development");
3487
3788
  const pluginManager = new PluginManager(config, cwd, true);
3488
3789
  if (config.plugins && config.plugins.length > 0) {
3489
- spinner.text = "\u52A0\u8F7D\u63D2\u4EF6...";
3490
3790
  await pluginManager.loadPlugins(config.plugins);
3491
3791
  }
3492
3792
  await pluginManager.runBeforeDevServer();
3493
3793
  let rspackConfig = createRspackConfig(config, cwd, { isDev: true });
3494
3794
  rspackConfig = await pluginManager.applyRspackConfigHooks(rspackConfig);
3795
+ const host = config.dev.host || "localhost";
3796
+ const preferredPort = config.dev.port || 3e3;
3797
+ const port = await getAvailablePort(preferredPort, host);
3798
+ const pluginNames = pluginManager.getPluginNames();
3799
+ if (port !== preferredPort) {
3800
+ console.log(
3801
+ chalk2.yellow(` Port ${preferredPort} is in use, using ${port} instead.
3802
+ `)
3803
+ );
3804
+ }
3805
+ const printer = new DevPrinter(host, port, pluginNames);
3806
+ printer.printBanner();
3807
+ printer.printBuildStart();
3808
+ printer.updateProgress(0, "preparing");
3809
+ rspackConfig.plugins = rspackConfig.plugins || [];
3810
+ rspackConfig.plugins.push(
3811
+ new rspack3.ProgressPlugin(createProgressHandler(printer))
3812
+ );
3813
+ rspackConfig.stats = "none";
3814
+ rspackConfig.infrastructureLogging = { level: "none" };
3815
+ if (rspackConfig.devServer) {
3816
+ const ds = rspackConfig.devServer;
3817
+ const existingClient = ds.client ?? {};
3818
+ ds.client = {
3819
+ ...existingClient,
3820
+ logging: "warn",
3821
+ overlay: false,
3822
+ progress: false
3823
+ };
3824
+ }
3495
3825
  const compiler = rspack3(rspackConfig);
3496
- const devServerOptions = rspackConfig.devServer || {};
3826
+ compiler.hooks.done.tap("ywkf-dev-printer", (stats) => {
3827
+ const hasErrors = stats.hasErrors();
3828
+ if (hasErrors) {
3829
+ const info = stats.toJson({ errors: true });
3830
+ console.log();
3831
+ console.log(chalk2.red(" Compile error:"));
3832
+ for (const err of info.errors || []) {
3833
+ console.log(chalk2.red(` ${err.message}`));
3834
+ }
3835
+ console.log();
3836
+ }
3837
+ printer.printBuildDone(hasErrors);
3838
+ });
3839
+ compiler.hooks.invalid.tap("ywkf-dev-printer", () => {
3840
+ printer.markRebuildStart();
3841
+ printer.updateProgress(0, "rebuilding");
3842
+ });
3843
+ const devServerOptions = {
3844
+ ...rspackConfig.devServer || {},
3845
+ host,
3846
+ port
3847
+ };
3497
3848
  const server = new RspackDevServer(devServerOptions, compiler);
3498
- const host = config.dev.host || "localhost";
3499
- const port = config.dev.port || 3e3;
3500
3849
  await server.start();
3501
3850
  await pluginManager.runAfterDevServer({ host, port });
3502
- spinner.succeed(chalk.green("\u5F00\u53D1\u670D\u52A1\u5668\u542F\u52A8\u6210\u529F!"));
3503
- console.log();
3504
- console.log(
3505
- ` ${chalk.bold("Local:")} ${chalk.cyan(`http://${host}:${port}`)}`
3506
- );
3507
- const pluginNames = pluginManager.getPluginNames();
3508
- if (pluginNames.length > 0) {
3509
- console.log(` ${chalk.bold("\u63D2\u4EF6:")} ${chalk.dim(pluginNames.join(", "))}`);
3510
- }
3511
- console.log();
3851
+ registerShortcuts(printer, {
3852
+ port,
3853
+ onQuit: async () => {
3854
+ console.log(chalk2.gray("\n Shutting down...\n"));
3855
+ await server.stop();
3856
+ process.exit(0);
3857
+ }
3858
+ });
3512
3859
  const signals = ["SIGINT", "SIGTERM"];
3513
3860
  for (const signal of signals) {
3514
3861
  process.on(signal, async () => {
@@ -3517,7 +3864,8 @@ async function dev(options = {}) {
3517
3864
  });
3518
3865
  }
3519
3866
  } catch (error) {
3520
- spinner.fail(chalk.red("\u5F00\u53D1\u670D\u52A1\u5668\u542F\u52A8\u5931\u8D25"));
3867
+ console.error();
3868
+ console.error(chalk2.red(" \u2716 Dev server failed to start"));
3521
3869
  console.error(error);
3522
3870
  process.exit(1);
3523
3871
  }
@@ -3525,8 +3873,7 @@ async function dev(options = {}) {
3525
3873
 
3526
3874
  // src/cli/build.ts
3527
3875
  import { rspack as rspack4 } from "@rspack/core";
3528
- import chalk2 from "chalk";
3529
- import ora2 from "ora";
3876
+ import chalk3 from "chalk";
3530
3877
  function formatSize(bytes) {
3531
3878
  if (bytes < 1024) return `${bytes} B`;
3532
3879
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`;
@@ -3539,57 +3886,57 @@ function printBuildResult(stats) {
3539
3886
  warnings: true
3540
3887
  });
3541
3888
  if (info.errors && info.errors.length > 0) {
3542
- console.log(chalk2.red("\n\u6784\u5EFA\u9519\u8BEF:"));
3889
+ console.log(chalk3.red("\n Compile errors:"));
3543
3890
  for (const error of info.errors) {
3544
- console.log(chalk2.red(` ${error.message}`));
3891
+ console.log(chalk3.red(` ${error.message}`));
3545
3892
  }
3546
3893
  return;
3547
3894
  }
3548
3895
  if (info.warnings && info.warnings.length > 0) {
3549
- console.log(chalk2.yellow("\n\u6784\u5EFA\u8B66\u544A:"));
3896
+ console.log(chalk3.yellow("\n Warnings:"));
3550
3897
  for (const warning of info.warnings) {
3551
- console.log(chalk2.yellow(` ${warning.message}`));
3898
+ console.log(chalk3.yellow(` ${warning.message}`));
3552
3899
  }
3553
3900
  }
3554
- console.log(chalk2.bold("\n\u6784\u5EFA\u4EA7\u7269:"));
3901
+ console.log(chalk3.bold("\n Build output:"));
3555
3902
  console.log();
3556
3903
  const assets = info.assets || [];
3557
3904
  const sortedAssets = assets.filter((asset) => !asset.name.endsWith(".map")).sort((a, b) => b.size - a.size);
3558
3905
  for (const asset of sortedAssets.slice(0, 15)) {
3559
- const sizeColor = asset.size > 500 * 1024 ? chalk2.yellow : chalk2.green;
3906
+ const sizeColor = asset.size > 500 * 1024 ? chalk3.yellow : chalk3.green;
3560
3907
  console.log(
3561
- ` ${chalk2.dim(asset.name.padEnd(50))} ${sizeColor(formatSize(asset.size))}`
3908
+ ` ${chalk3.dim(asset.name.padEnd(50))} ${sizeColor(formatSize(asset.size))}`
3562
3909
  );
3563
3910
  }
3564
3911
  if (sortedAssets.length > 15) {
3565
- console.log(chalk2.dim(` ... \u8FD8\u6709 ${sortedAssets.length - 15} \u4E2A\u6587\u4EF6`));
3912
+ console.log(chalk3.dim(` ... and ${sortedAssets.length - 15} more files`));
3566
3913
  }
3567
3914
  console.log();
3568
- console.log(
3569
- chalk2.green(
3570
- `\u2713 \u6784\u5EFA\u5B8C\u6210\uFF0C\u8017\u65F6 ${((info.time || 0) / 1e3).toFixed(2)}s`
3571
- )
3572
- );
3573
3915
  }
3574
3916
  async function build(options = {}) {
3575
3917
  const cwd = options.cwd || process.cwd();
3576
- const spinner = ora2("\u6B63\u5728\u6784\u5EFA\u751F\u4EA7\u73AF\u5883...").start();
3577
3918
  try {
3578
3919
  preloadEnv(cwd, "production");
3579
- const { config, configPath } = await resolveConfig(cwd);
3580
- if (configPath) {
3581
- spinner.text = `\u5DF2\u52A0\u8F7D\u914D\u7F6E: ${configPath}`;
3582
- }
3920
+ const { config } = await resolveConfig(cwd);
3583
3921
  loadEnv(config, cwd, "production");
3584
3922
  const pluginManager = new PluginManager(config, cwd, false);
3585
3923
  if (config.plugins && config.plugins.length > 0) {
3586
- spinner.text = "\u52A0\u8F7D\u63D2\u4EF6...";
3587
3924
  await pluginManager.loadPlugins(config.plugins);
3588
3925
  }
3589
3926
  await pluginManager.runBeforeBuild();
3590
3927
  let rspackConfig = createRspackConfig(config, cwd, { isDev: false });
3591
3928
  rspackConfig = await pluginManager.applyRspackConfigHooks(rspackConfig);
3592
- spinner.text = "\u6B63\u5728\u7F16\u8BD1...";
3929
+ const pluginNames = pluginManager.getPluginNames();
3930
+ const printer = new DevPrinter("localhost", 0, pluginNames);
3931
+ printer.printBanner();
3932
+ printer.printBuildStart();
3933
+ printer.updateProgress(0, "preparing");
3934
+ rspackConfig.plugins = rspackConfig.plugins || [];
3935
+ rspackConfig.plugins.push(
3936
+ new rspack4.ProgressPlugin(createProgressHandler(printer))
3937
+ );
3938
+ rspackConfig.stats = "none";
3939
+ rspackConfig.infrastructureLogging = { level: "none" };
3593
3940
  const compiler = rspack4(rspackConfig);
3594
3941
  const stats = await new Promise((resolve4, reject) => {
3595
3942
  compiler.run((err, stats2) => {
@@ -3598,7 +3945,7 @@ async function build(options = {}) {
3598
3945
  return;
3599
3946
  }
3600
3947
  if (!stats2) {
3601
- reject(new Error("\u6784\u5EFA\u5931\u8D25: \u65E0\u6CD5\u83B7\u53D6\u7EDF\u8BA1\u4FE1\u606F"));
3948
+ reject(new Error("Build failed: no stats available"));
3602
3949
  return;
3603
3950
  }
3604
3951
  resolve4(stats2);
@@ -3616,15 +3963,15 @@ async function build(options = {}) {
3616
3963
  success: !hasErrors,
3617
3964
  errors: statsInfo.errors?.map((e) => e.message)
3618
3965
  });
3966
+ printer.printBuildDone(hasErrors);
3619
3967
  if (hasErrors) {
3620
- spinner.fail(chalk2.red("\u6784\u5EFA\u5931\u8D25"));
3621
3968
  printBuildResult(stats);
3622
3969
  process.exit(1);
3623
3970
  }
3624
- spinner.succeed(chalk2.green("\u6784\u5EFA\u5B8C\u6210!"));
3625
3971
  printBuildResult(stats);
3626
3972
  } catch (error) {
3627
- spinner.fail(chalk2.red("\u6784\u5EFA\u5931\u8D25"));
3973
+ console.error();
3974
+ console.error(chalk3.red(" \u2716 Build failed"));
3628
3975
  console.error(error);
3629
3976
  process.exit(1);
3630
3977
  }