@qse/edu-scripts 1.15.0 → 2.0.1

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.
Files changed (94) hide show
  1. package/CHANGELOG.md +4 -489
  2. package/asset/rspack-dev-server-client.js +534 -0
  3. package/{src/asset → asset}/template/edu-scripts.override.js.tpl +2 -2
  4. package/{lib/asset → asset}/template/tailwind.config.js.tpl +1 -1
  5. package/babel.config.json +6 -0
  6. package/dist/cli.d.mts +1 -0
  7. package/dist/cli.mjs +1432 -0
  8. package/dist/index.d.mts +83 -0
  9. package/dist/index.mjs +13 -0
  10. package/eslint.config.mjs +3 -0
  11. package/{jest.config.js → jest.config.mjs} +8 -4
  12. package/package.json +67 -67
  13. package/pnpm-workspace.yaml +2 -0
  14. package/src/{auto-refactor.js → auto-refactor.ts} +51 -59
  15. package/src/{build.js → build.ts} +22 -16
  16. package/src/{cli.js → cli.ts} +26 -21
  17. package/src/{commit-dist.js → commit-dist.ts} +28 -21
  18. package/src/config/paths.ts +52 -0
  19. package/src/config/plugins/mock-server/{index.js → index.ts} +30 -41
  20. package/src/config/plugins/{postcss-safe-area.js → postcss-safe-area.ts} +4 -2
  21. package/src/config/{webpackConfig.js → webpackConfig.ts} +157 -126
  22. package/src/config/{webpackDevServerConfig.js → webpackDevServerConfig.ts} +18 -18
  23. package/src/{deploy.js → deploy.ts} +36 -25
  24. package/src/{generator.js → generator.ts} +5 -14
  25. package/src/start.ts +52 -0
  26. package/src/utils/FileSizeReporter.ts +166 -0
  27. package/src/utils/{appConfig.js → appConfig.ts} +5 -4
  28. package/src/utils/{beforeStart.js → beforeStart.ts} +19 -7
  29. package/src/utils/{changeDeployVersion.js → changeDeployVersion.ts} +43 -48
  30. package/src/utils/defineConfig.ts +19 -36
  31. package/src/utils/esm-register.ts +6 -0
  32. package/src/utils/{exec.js → exec.ts} +3 -3
  33. package/src/utils/{getConfig.js → getConfig.ts} +7 -5
  34. package/src/utils/getOverride.ts +32 -0
  35. package/src/utils/resolveModule.ts +3 -0
  36. package/tsconfig.json +3 -15
  37. package/tsdown.config.ts +5 -0
  38. package/docs/changelog.md +0 -5
  39. package/docs/debug.md +0 -17
  40. package/docs/deploy.md +0 -54
  41. package/docs/faq.md +0 -144
  42. package/docs/feat.md +0 -167
  43. package/docs/grayscale.md +0 -31
  44. package/docs/index.md +0 -5
  45. package/docs/mode.md +0 -42
  46. package/docs/override.md +0 -193
  47. package/docs/refactor-react-16.md +0 -37
  48. package/docs/refactor.md +0 -67
  49. package/docs/static.md +0 -24
  50. package/lib/asset/template/edu-scripts.override.js.tpl +0 -7
  51. package/lib/auto-refactor.js +0 -151
  52. package/lib/build.js +0 -59
  53. package/lib/cli.js +0 -66
  54. package/lib/commit-dist.js +0 -79
  55. package/lib/config/babel.dependencies.js +0 -79
  56. package/lib/config/babel.js +0 -107
  57. package/lib/config/paths.js +0 -36
  58. package/lib/config/plugins/babel-plugin-add-webpack-chunk-name.js +0 -31
  59. package/lib/config/plugins/mock-server/defineMock.d.ts +0 -6
  60. package/lib/config/plugins/mock-server/defineMock.js +0 -31
  61. package/lib/config/plugins/mock-server/index.js +0 -122
  62. package/lib/config/plugins/postcss-safe-area.js +0 -19
  63. package/lib/config/plugins/ws-utils-createSocketURL.js +0 -118
  64. package/lib/config/webpackConfig.js +0 -462
  65. package/lib/config/webpackDevServerConfig.js +0 -72
  66. package/lib/deploy.js +0 -143
  67. package/lib/generator.js +0 -50
  68. package/lib/index.d.ts +0 -2
  69. package/lib/index.js +0 -32
  70. package/lib/start.js +0 -36
  71. package/lib/utils/FileSizeReporter.js +0 -107
  72. package/lib/utils/appConfig.js +0 -32
  73. package/lib/utils/beforeStart.js +0 -50
  74. package/lib/utils/changeDeployVersion.js +0 -89
  75. package/lib/utils/defineConfig.d.ts +0 -93
  76. package/lib/utils/defineConfig.js +0 -31
  77. package/lib/utils/exec.js +0 -7
  78. package/lib/utils/getConfig.js +0 -20
  79. package/lib/utils/getOverride.js +0 -61
  80. package/src/asset/dll/libcommon3-manifest.json +0 -181
  81. package/src/asset/template/edu-app-env.d.ts.tpl +0 -20
  82. package/src/asset/template/tailwind.config.js.tpl +0 -11
  83. package/src/asset/template/tsconfig.json.tpl +0 -24
  84. package/src/config/babel.dependencies.js +0 -66
  85. package/src/config/babel.js +0 -94
  86. package/src/config/paths.js +0 -38
  87. package/src/config/plugins/babel-plugin-add-webpack-chunk-name.js +0 -55
  88. package/src/config/plugins/ws-utils-createSocketURL.js +0 -140
  89. package/src/start.js +0 -44
  90. package/src/utils/FileSizeReporter.js +0 -151
  91. package/src/utils/getOverride.js +0 -48
  92. /package/{lib/asset → asset}/dll/libcommon3-manifest.json +0 -0
  93. /package/{lib/asset → asset}/template/edu-app-env.d.ts.tpl +0 -0
  94. /package/{lib/asset → asset}/template/tsconfig.json.tpl +0 -0
package/dist/cli.mjs ADDED
@@ -0,0 +1,1432 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ import fs from "fs-extra";
4
+ import path from "path";
5
+ import { globby, globbySync } from "globby";
6
+ import { fileURLToPath } from "node:url";
7
+ import chalk from "chalk";
8
+ import semver from "semver";
9
+ import yargs from "yargs";
10
+ import { rspack } from "@rspack/core";
11
+ import { RspackDevServer } from "@rspack/dev-server";
12
+ import { RsdoctorRspackPlugin } from "@rsdoctor/rspack-plugin";
13
+ import ReactRefreshPlugin from "@rspack/plugin-react-refresh";
14
+ import HtmlWebpackPlugin from "html-webpack-plugin";
15
+ import path$1 from "node:path";
16
+ import chokidar from "chokidar";
17
+ import { debounce, memoize } from "es-toolkit";
18
+ import express from "express";
19
+ import cookieParser from "cookie-parser";
20
+ import multer from "multer";
21
+ import { pathToRegexp } from "path-to-regexp";
22
+ import filesize from "filesize";
23
+ import recursive from "recursive-readdir";
24
+ import stripAnsi from "strip-ansi";
25
+ import { gzipSizeSync } from "gzip-size";
26
+ import { sshSftp } from "@qse/ssh-sftp";
27
+ import { parse, transformFromAstSync, traverse, types } from "@babel/core";
28
+ import ora from "ora";
29
+ import { format } from "prettier";
30
+ import { checkbox, input, select } from "@inquirer/prompts";
31
+ import cp from "child_process";
32
+ import tmp from "tmp";
33
+
34
+ //#region src/utils/esm-register.ts
35
+ const { register } = createRequire(import.meta.url)("@swc-node/register/register");
36
+ register();
37
+
38
+ //#endregion
39
+ //#region src/config/paths.ts
40
+ function resolveApp(...filePath) {
41
+ return path.resolve(process.cwd(), ...filePath);
42
+ }
43
+ function resolveOwn(...filePath) {
44
+ let current = path.dirname(fileURLToPath(import.meta.url));
45
+ while (!fs.existsSync(path.join(current, "package.json"))) {
46
+ const parent = path.dirname(current);
47
+ if (parent === current) throw new Error("package.json not found");
48
+ current = parent;
49
+ }
50
+ return path.resolve(current, ...filePath);
51
+ }
52
+ function getExistPath(...paths) {
53
+ for (const path of paths) if (fs.existsSync(path)) return path;
54
+ return paths[0];
55
+ }
56
+ const paths = {
57
+ resolveApp,
58
+ resolveOwn,
59
+ eduAppEnv: resolveApp("src", "edu-app-env.d.ts"),
60
+ dist: resolveApp("dist"),
61
+ sshSftp: resolveApp(".sftprc.json"),
62
+ nodeModules: resolveApp("node_modules"),
63
+ tsconfig: resolveApp("tsconfig.json"),
64
+ jsconfig: resolveApp("jsconfig.json"),
65
+ package: resolveApp("package.json"),
66
+ tailwind: resolveApp("tailwind.config.js"),
67
+ pages: resolveApp("src", "pages"),
68
+ override: getExistPath(resolveApp("edu-scripts.override.js"), resolveApp("edu-scripts.override.ts")),
69
+ indexHTML: globbySync("./public/*.html", { absolute: true }),
70
+ src: resolveApp("src"),
71
+ public: resolveApp("public"),
72
+ static: resolveApp("public", "static"),
73
+ theme: getExistPath(resolveApp("theme.json"), resolveApp("theme.js")),
74
+ mock: resolveApp("mock")
75
+ };
76
+
77
+ //#endregion
78
+ //#region src/utils/appConfig.ts
79
+ const appPkg$3 = fs.readJsonSync(paths.package);
80
+ const edu = appPkg$3.edu || {};
81
+ if (edu.single || edu.mainProject) {
82
+ if (edu.single) {
83
+ edu.mode = "single";
84
+ delete edu.single;
85
+ }
86
+ if (edu.mainProject) {
87
+ edu.mode = "main";
88
+ delete edu.mainProject;
89
+ }
90
+ fs.writeFileSync(paths.package, JSON.stringify(appPkg$3, null, 2), "utf-8");
91
+ }
92
+ const appConfig = {
93
+ get mode() {
94
+ return edu.mode;
95
+ },
96
+ get mainProject() {
97
+ return this.mode === "main";
98
+ },
99
+ get single() {
100
+ return this.mode === "single";
101
+ },
102
+ get grayscale() {
103
+ return !!edu.grayscale;
104
+ }
105
+ };
106
+
107
+ //#endregion
108
+ //#region src/utils/beforeStart.ts
109
+ const pkg$2 = fs.readJsonSync(paths.resolveOwn("package.json"));
110
+ const appPkg$2 = fs.readJsonSync(paths.package);
111
+ if (semver.valid(appPkg$2.version) === null) {
112
+ console.log(chalk.red(`package.version 不符合 semver 规范 https://docs.npmjs.com/about-semantic-versioning`));
113
+ process.exit(1);
114
+ }
115
+ switch (appConfig.mode) {
116
+ case "main":
117
+ console.log(chalk.bgMagenta("正在使用教育主工程模式"));
118
+ break;
119
+ case "single":
120
+ console.log(chalk.bgMagenta("正在使用独立项目模式"));
121
+ break;
122
+ default:
123
+ console.log(chalk.bgMagenta("正在使用教育集成模式"));
124
+ break;
125
+ }
126
+ if (appConfig.grayscale) console.log(chalk.bgYellow("正在使用灰度测试模式"));
127
+ if (!(appConfig.single || appConfig.mainProject)) {
128
+ if (fs.existsSync(paths.static)) {
129
+ console.log(chalk.bgYellow("教育集成工程不能含有 public/static, 现已自动删除"));
130
+ try {
131
+ fs.rmSync(paths.static, { recursive: true });
132
+ } catch (e) {}
133
+ }
134
+ }
135
+ if (!fs.existsSync(paths.public) && !process.argv.includes("auto-refactor")) {
136
+ console.log(chalk.red(`public 文件夹不存在,请先按文档改造项目`));
137
+ console.log(`文档: ${chalk.underline(pkg$2.homepage)}`);
138
+ console.log(`\n使用 ${chalk.green("npx edu-scripts auto-refactor")} 自动改造\n`);
139
+ process.exit(0);
140
+ }
141
+ if (appPkg$2.browserslist) {
142
+ console.log(chalk.yellow("已删除 package.json 中 browserslist,该值由内部自动控制\n"));
143
+ delete appPkg$2.browserslist;
144
+ fs.writeFileSync(paths.package, JSON.stringify(appPkg$2, null, 2), "utf-8");
145
+ }
146
+ if (appPkg$2.eslintConfig) {
147
+ console.log(chalk.yellow("package.json 中 eslintConfig 已废弃,请改用 @qse/eslint-config 进行配置"));
148
+ console.log(chalk.yellow("npm i eslint @qse/eslint-config -D"));
149
+ console.log();
150
+ console.log(chalk.gray("新建 eslint.config.mjs 文件,内容如下:"));
151
+ console.log(chalk.yellow("import config from \"@qse/eslint-config\""));
152
+ console.log(chalk.yellow("export default config"));
153
+ }
154
+
155
+ //#endregion
156
+ //#region src/utils/resolveModule.ts
157
+ function resolveModule(mod) {
158
+ return mod.default ? mod.default : mod;
159
+ }
160
+
161
+ //#endregion
162
+ //#region src/config/plugins/postcss-safe-area.ts
163
+ const expr = new RegExp(`env\\(\\s*(${[
164
+ "safe-area-inset-top",
165
+ "safe-area-inset-bottom",
166
+ "safe-area-inset-left",
167
+ "safe-area-inset-right"
168
+ ].join("|")})\\s*,?\\s*([^)]+)?\\s*\\)`, "g");
169
+ const PostcssSafeAreaPlugin = () => {
170
+ return {
171
+ postcssPlugin: "postcss-safe-area",
172
+ Declaration(decl) {
173
+ const fallback = decl.value.replace(expr, (match, param, defaultValue) => defaultValue || "0");
174
+ if (fallback !== decl.value) decl.cloneBefore({ value: fallback });
175
+ }
176
+ };
177
+ };
178
+
179
+ //#endregion
180
+ //#region src/config/webpackConfig.ts
181
+ const require$2 = createRequire(import.meta.url);
182
+ const appPkg$1 = fs.readJsonSync(paths.package);
183
+ const jsMainPath = appConfig.grayscale ? `${appPkg$1.name}/beta/${appPkg$1.name}` : `${appPkg$1.name}/${appPkg$1.name}`;
184
+ const assetPath = appConfig.grayscale ? `${appPkg$1.name}/beta/${appPkg$1.version}` : `${appPkg$1.name}/${appPkg$1.version}`;
185
+ const cssRegex = /\.css$/;
186
+ const cssModuleRegex = /\.module\.css$/;
187
+ const lessRegex = /\.less$/;
188
+ const lessModuleRegex = /\.module\.less$/;
189
+ const imageInlineSizeLimit = 10 * 1024;
190
+ const qseCDN = (() => {
191
+ const contents = paths.indexHTML.map((url) => fs.readFileSync(url, "utf-8"));
192
+ function include(pattern) {
193
+ const regexp = new RegExp(pattern);
194
+ return contents.some((content) => regexp.test(content));
195
+ }
196
+ return {
197
+ isUseCommon: include(/react16.14.*_common31?.js|react-dev-preset.js/),
198
+ isUseAxios: include(/react16.14.*_axios0.21.1|react-dev-preset.js/),
199
+ isUseMoment: include("moment2.29.1.js"),
200
+ isUseAntd: include("antd3.26.20.js"),
201
+ isUseQsbAntd: include("qsb-antd.min.js"),
202
+ isUseQsbSchemeRender: include("qsb-scheme-render.min.js")
203
+ };
204
+ })();
205
+ function getWebpackConfig(args, override) {
206
+ const isDev = process.env.NODE_ENV === "development";
207
+ const isProd = process.env.NODE_ENV === "production";
208
+ const getStyleLoaders = (cssOptions, preProcessor) => {
209
+ const loaders = [
210
+ {
211
+ loader: require$2.resolve("style-loader"),
212
+ options: { attributes: {
213
+ "data-module": appPkg$1.name,
214
+ "data-version": appPkg$1.version
215
+ } }
216
+ },
217
+ {
218
+ loader: require$2.resolve("css-loader"),
219
+ options: cssOptions
220
+ },
221
+ {
222
+ loader: require$2.resolve("postcss-loader"),
223
+ options: {
224
+ postcssOptions: {
225
+ ident: "postcss",
226
+ config: false,
227
+ plugins: [
228
+ isProd && require$2("cssnano")({ preset: "default" }),
229
+ fs.existsSync(paths.tailwind) && require$2.resolve("tailwindcss"),
230
+ require$2.resolve("postcss-flexbugs-fixes"),
231
+ [require$2.resolve("postcss-preset-env"), { autoprefixer: { flexbox: "no-2009" } }],
232
+ isProd && PostcssSafeAreaPlugin(),
233
+ isProd && [require$2.resolve("postcss-momentum-scrolling"), ["scroll", "auto"]],
234
+ require$2.resolve("postcss-normalize"),
235
+ ...override.extraPostCSSPlugins || []
236
+ ].filter(Boolean)
237
+ },
238
+ sourceMap: isDev
239
+ }
240
+ }
241
+ ];
242
+ if (preProcessor === "less-loader") loaders.push({
243
+ loader: require$2.resolve("less-loader"),
244
+ options: {
245
+ lessOptions: {
246
+ javascriptEnabled: true,
247
+ modifyVars: fs.existsSync(paths.theme) ? resolveModule(require$2(paths.theme)) : void 0
248
+ },
249
+ sourceMap: true
250
+ }
251
+ });
252
+ return loaders;
253
+ };
254
+ return {
255
+ context: process.cwd(),
256
+ mode: process.env.NODE_ENV,
257
+ entry: "./src/index",
258
+ target: "browserslist",
259
+ output: {
260
+ filename: appConfig.single ? `js/${jsMainPath}_${appPkg$1.version}.[contenthash:6].js` : `js/${jsMainPath}_${appPkg$1.version}.js`,
261
+ chunkFilename: `js/${assetPath}/[name].[chunkhash:8].js`,
262
+ assetModuleFilename: `images/${assetPath}/[name].[hash:6][ext]`,
263
+ uniqueName: appPkg$1.name,
264
+ publicPath: ""
265
+ },
266
+ externals: Object.assign({}, qseCDN.isUseCommon && {
267
+ react: "React",
268
+ "react-dom": "ReactDOM",
269
+ "natty-fetch": "nattyFetch",
270
+ "natty-storage": "nattyStorage",
271
+ "common-utils": "CommonUtils",
272
+ "@qse/common-utils": "CommonUtils"
273
+ }, qseCDN.isUseAxios && { axios: "axios" }, qseCDN.isUseMoment && { moment: "moment" }, qseCDN.isUseAntd && Object.assign({
274
+ react: "React",
275
+ "react-dom": "ReactDOM",
276
+ moment: "moment",
277
+ antd: "antd"
278
+ }, qseCDN.isUseQsbAntd && {
279
+ "@qse/antd": "qsbAntd",
280
+ "@qsb/antd": "qsbAntd"
281
+ }, qseCDN.isUseQsbSchemeRender && {
282
+ "@qse/scheme-render": "qsbSchemeRender",
283
+ "@qsb/scheme-render": "qsbSchemeRender"
284
+ }), !appConfig.single && Object.assign({
285
+ react: "React",
286
+ "react-dom": "ReactDOM",
287
+ "natty-fetch": "nattyFetch",
288
+ "natty-storage": "nattyStorage",
289
+ "common-utils": "CommonUtils",
290
+ "@qse/common-utils": "CommonUtils",
291
+ moment: "moment",
292
+ antd: "antd"
293
+ }, isProd && {
294
+ "@qse/antd": "qsbAntd",
295
+ "@qsb/antd": "qsbAntd",
296
+ "@qse/scheme-render": "qsbSchemeRender",
297
+ "@qsb/scheme-render": "qsbSchemeRender"
298
+ }), override.externals),
299
+ resolve: {
300
+ alias: {
301
+ "@": paths.src,
302
+ "@swc/helpers": path$1.dirname(require$2.resolve("@swc/helpers/package.json")),
303
+ ...override.alias
304
+ },
305
+ extensions: [
306
+ ".web.js",
307
+ ".web.mjs",
308
+ ".js",
309
+ ".mjs",
310
+ ".jsx",
311
+ ".ts",
312
+ ".tsx",
313
+ ".json",
314
+ ".wasm"
315
+ ],
316
+ tsConfig: {
317
+ configFile: paths.tsconfig,
318
+ references: "auto"
319
+ }
320
+ },
321
+ stats: false,
322
+ devtool: isDev ? "cheap-module-source-map" : false,
323
+ module: { rules: [{ oneOf: [
324
+ {
325
+ resourceQuery: /raw/,
326
+ type: "asset/source"
327
+ },
328
+ {
329
+ test: /\.[cm]?[jt]sx?$/,
330
+ exclude: /node_modules/,
331
+ use: [{
332
+ loader: "builtin:swc-loader",
333
+ options: {
334
+ env: { targets: process.env.BROWSERSLIST },
335
+ rspackExperiments: { import: [{
336
+ libraryName: "lodash",
337
+ libraryDirectory: "",
338
+ camelToDashComponentName: false
339
+ }, ...override.import || []] },
340
+ isModule: "unknown",
341
+ jsc: {
342
+ parser: {
343
+ syntax: "typescript",
344
+ tsx: true,
345
+ decorators: override.decorators
346
+ },
347
+ externalHelpers: true,
348
+ transform: {
349
+ legacyDecorator: override.decorators,
350
+ react: {
351
+ runtime: "automatic",
352
+ development: isDev,
353
+ refresh: isDev
354
+ }
355
+ }
356
+ }
357
+ }
358
+ }]
359
+ },
360
+ {
361
+ test: /\.[cm]?jsx?$/,
362
+ exclude: /node_modules[\\/](core-js|@swc[\\/]helpers)([\\/]|$)/,
363
+ use: [{
364
+ loader: "builtin:swc-loader",
365
+ options: {
366
+ env: { targets: process.env.BROWSERSLIST },
367
+ isModule: "unknown",
368
+ jsc: {
369
+ parser: { syntax: "ecmascript" },
370
+ externalHelpers: true
371
+ }
372
+ }
373
+ }]
374
+ },
375
+ {
376
+ test: cssRegex,
377
+ exclude: cssModuleRegex,
378
+ use: getStyleLoaders({
379
+ importLoaders: 1,
380
+ sourceMap: isDev,
381
+ modules: {
382
+ mode: "global",
383
+ localIdentName: "[local]--[hash:base64:6]"
384
+ }
385
+ }),
386
+ sideEffects: true
387
+ },
388
+ {
389
+ test: cssModuleRegex,
390
+ use: getStyleLoaders({
391
+ importLoaders: 1,
392
+ sourceMap: isDev,
393
+ modules: {
394
+ mode: "local",
395
+ localIdentName: "[local]--[hash:base64:6]"
396
+ }
397
+ })
398
+ },
399
+ {
400
+ test: lessRegex,
401
+ exclude: lessModuleRegex,
402
+ use: getStyleLoaders({
403
+ importLoaders: 2,
404
+ sourceMap: isDev,
405
+ modules: {
406
+ mode: "global",
407
+ localIdentName: "[local]--[hash:base64:6]"
408
+ }
409
+ }, "less-loader"),
410
+ sideEffects: true
411
+ },
412
+ {
413
+ test: lessModuleRegex,
414
+ use: getStyleLoaders({
415
+ importLoaders: 2,
416
+ sourceMap: isDev,
417
+ modules: {
418
+ mode: "local",
419
+ localIdentName: "[local]--[hash:base64:6]"
420
+ }
421
+ }, "less-loader")
422
+ },
423
+ {
424
+ test: /\.(bmp|png|jpe?g|gif|webp)$/,
425
+ type: "asset",
426
+ parser: { dataUrlCondition: { maxSize: imageInlineSizeLimit } }
427
+ },
428
+ {
429
+ test: /\.svg$/,
430
+ type: "asset",
431
+ parser: { dataUrlCondition: { maxSize: imageInlineSizeLimit } },
432
+ issuer: { and: [/\.(css|less)$/] }
433
+ },
434
+ {
435
+ test: /\.svg$/,
436
+ use: [{
437
+ loader: require$2.resolve("@svgr/webpack"),
438
+ options: {
439
+ prettier: false,
440
+ svgo: false,
441
+ svgoConfig: { plugins: [{ removeViewBox: false }] },
442
+ titleProp: true,
443
+ ref: false
444
+ }
445
+ }, {
446
+ loader: require$2.resolve("url-loader"),
447
+ options: { limit: imageInlineSizeLimit }
448
+ }],
449
+ issuer: { and: [/\.(ts|tsx|js|jsx|md|mdx)$/] }
450
+ },
451
+ {
452
+ test: /\.md$/,
453
+ type: "asset/source"
454
+ },
455
+ {
456
+ test: /\.(?!(?:js|mjs|jsx|ts|tsx|html|json)$)[^.]+$/,
457
+ type: "asset/resource"
458
+ }
459
+ ].filter(Boolean) }] },
460
+ plugins: [
461
+ qseCDN.isUseCommon && new rspack.DllReferencePlugin({ manifest: fs.readJsonSync(paths.resolveOwn("asset", "dll", "libcommon3-manifest.json")) }),
462
+ new rspack.NormalModuleReplacementPlugin(/@rspack\/dev-server\/client\/index\.js/, (resource) => {
463
+ const myClientPath = paths.resolveOwn("asset", "rspack-dev-server-client.js");
464
+ resource.request = resource.request.replace(/.*dev-server\/client\/index\.js/, myClientPath);
465
+ }),
466
+ new rspack.IgnorePlugin({
467
+ resourceRegExp: /^\.\/locale$/,
468
+ contextRegExp: /moment$/
469
+ }),
470
+ new rspack.DefinePlugin({
471
+ "process.env.APP_NAME": JSON.stringify(appPkg$1.name),
472
+ "process.env.APP_VERSION": JSON.stringify(appPkg$1.version),
473
+ "process.env.BABEL_ENV": JSON.stringify(process.env.BABEL_ENV),
474
+ "process.env.BROWSERSLIST": JSON.stringify(process.env.BROWSERSLIST),
475
+ ...override.define
476
+ }),
477
+ new rspack.ProgressPlugin(),
478
+ isDev && new rspack.CaseSensitivePlugin(),
479
+ isDev && new ReactRefreshPlugin({ overlay: false }),
480
+ ...isDev || process.env.OUTPUT_HTML || appConfig.single || appConfig.mainProject ? paths.indexHTML.map((template) => new HtmlWebpackPlugin({
481
+ template,
482
+ filename: template.split("/").pop(),
483
+ inject: false,
484
+ minify: {
485
+ removeComments: true,
486
+ collapseWhitespace: true,
487
+ removeRedundantAttributes: true,
488
+ useShortDoctype: true,
489
+ removeEmptyAttributes: true,
490
+ removeStyleLinkTypeAttributes: true,
491
+ keepClosingSlash: true,
492
+ minifyJS: true,
493
+ minifyCSS: true,
494
+ minifyURLs: true
495
+ }
496
+ })) : [],
497
+ process.env.ANALYZE && isProd && new RsdoctorRspackPlugin(),
498
+ isDev && ((compiler) => {
499
+ let isFirst = true;
500
+ compiler.hooks.afterDone.tap("edu-scripts-startup", (stats) => {
501
+ if (!isFirst) console.clear();
502
+ isFirst = false;
503
+ if (override.startup) {
504
+ const logger = compiler.getInfrastructureLogger("edu-scripts");
505
+ override.startup({
506
+ logger,
507
+ chalk,
508
+ compiler
509
+ });
510
+ }
511
+ console.log(stats.toString({
512
+ preset: "errors-warnings",
513
+ timings: true,
514
+ colors: true
515
+ }));
516
+ });
517
+ })
518
+ ].filter(Boolean),
519
+ optimization: {
520
+ minimize: isProd && override.minify !== false,
521
+ minimizer: [new rspack.SwcJsMinimizerRspackPlugin({ minimizerOptions: {
522
+ ecma: 5,
523
+ compress: {
524
+ pure_funcs: override.pure_funcs,
525
+ drop_debugger: true,
526
+ ecma: 5,
527
+ comparisons: false,
528
+ inline: 2
529
+ }
530
+ } })],
531
+ splitChunks: { minChunks: 2 }
532
+ },
533
+ performance: {
534
+ maxEntrypointSize: appConfig.single ? 1024 * 1024 : 30 * 1024,
535
+ maxAssetSize: 2 * 1024 * 1024
536
+ }
537
+ };
538
+ }
539
+
540
+ //#endregion
541
+ //#region src/config/plugins/mock-server/index.ts
542
+ const require$1 = createRequire(import.meta.url);
543
+ let mockCache = {};
544
+ let isSetup = false;
545
+ const setupMock = debounce(function setupMock() {
546
+ mockCache = {};
547
+ const files = globbySync(paths.mock, { expandDirectories: { extensions: ["js", "ts"] } });
548
+ if (isSetup) console.log(chalk.green("Mock files changed, reloaded"));
549
+ else isSetup = true;
550
+ for (const file of files) {
551
+ delete require$1.cache[require$1.resolve(file)];
552
+ try {
553
+ const mock = resolveModule(require$1(file));
554
+ for (const key in mock) {
555
+ const [method, path] = key.split(" ");
556
+ mockCache[key] = {
557
+ method,
558
+ path,
559
+ handler: mock[key]
560
+ };
561
+ }
562
+ } catch (e) {
563
+ console.error(chalk.red(`Mock file ${file} error: ${e.message}`));
564
+ }
565
+ }
566
+ }, 100);
567
+ const getPathReAndKeys = memoize((path) => {
568
+ const keys = [];
569
+ return {
570
+ re: pathToRegexp(path, keys),
571
+ keys
572
+ };
573
+ });
574
+ function decodeParam(val) {
575
+ if (typeof val !== "string" || val.length === 0) return val;
576
+ try {
577
+ return decodeURIComponent(val);
578
+ } catch (err) {
579
+ if (err instanceof URIError) {
580
+ err.message = `Failed to decode param ' ${val} '`;
581
+ err.status = 400;
582
+ err.statusCode = 400;
583
+ }
584
+ throw err;
585
+ }
586
+ }
587
+ /**
588
+ * @type {import('express').RequestHandler}
589
+ */
590
+ const mockMiddlewave = function mockMiddlewave(req, res, next) {
591
+ const { method, path } = req;
592
+ for (const key in mockCache) {
593
+ const mock = mockCache[key];
594
+ if (mock.method !== method) continue;
595
+ const { keys, re } = getPathReAndKeys(mock.path);
596
+ const m = re.exec(path);
597
+ if (m) {
598
+ console.log(chalk.green(`Mock: ${key}`));
599
+ res.setHeader("X-Mock", key);
600
+ if (typeof mock.handler === "function") {
601
+ const params = {};
602
+ for (let i = 1; i < m.length; i += 1) {
603
+ const prop = keys[i - 1].name;
604
+ const val = decodeParam(m[i]);
605
+ if (val !== void 0) params[prop] = val;
606
+ }
607
+ req.params = params;
608
+ const middelwaves = [
609
+ express.urlencoded({
610
+ limit: "5mb",
611
+ extended: true
612
+ }),
613
+ express.json({
614
+ limit: "5mb",
615
+ strict: false
616
+ }),
617
+ multer().any(),
618
+ cookieParser(),
619
+ mock.handler
620
+ ];
621
+ const throwNext = () => {
622
+ console.log(chalk.red(`Mock: ${key} don't use next()`));
623
+ res.sendStatus(500);
624
+ };
625
+ const mwNext = () => {
626
+ middelwaves.shift()(req, res, middelwaves.length ? mwNext : throwNext);
627
+ };
628
+ mwNext();
629
+ } else res.json(mock.handler);
630
+ return;
631
+ }
632
+ }
633
+ next();
634
+ };
635
+ function setupMockServer(middelwaves, _devServer) {
636
+ if (!fs.existsSync(paths.mock)) return;
637
+ middelwaves.unshift({
638
+ name: "edu-scripts-mock-middelwave",
639
+ middleware: mockMiddlewave
640
+ });
641
+ console.log(`<i> ${chalk.green.bold("[edu-scripts] Mock server created:")} ${chalk.cyan.bold(paths.mock)}`);
642
+ chokidar.watch(paths.mock).on("all", setupMock);
643
+ return middelwaves;
644
+ }
645
+
646
+ //#endregion
647
+ //#region src/config/webpackDevServerConfig.ts
648
+ function createProxy(context, target, origin) {
649
+ const url = new URL(origin || target);
650
+ return {
651
+ context: [context],
652
+ target,
653
+ changeOrigin: true,
654
+ onProxyReq: (proxyReq) => {
655
+ proxyReq.setHeader("host", url.host);
656
+ proxyReq.setHeader("origin", url.origin);
657
+ proxyReq.removeHeader("referer");
658
+ }
659
+ };
660
+ }
661
+ function getWebpackDevServerConfig(args, override) {
662
+ const host = process.env.HOST || "0.0.0.0";
663
+ const devServer = {
664
+ allowedHosts: "all",
665
+ historyApiFallback: true,
666
+ port: args.port,
667
+ host,
668
+ client: {
669
+ webSocketURL: "auto://0.0.0.0:0/ws",
670
+ overlay: {
671
+ runtimeErrors: false,
672
+ errors: true,
673
+ warnings: false
674
+ }
675
+ },
676
+ headers: {
677
+ "Access-Control-Allow-Origin": "*",
678
+ "Access-Control-Allow-Methods": "*",
679
+ "Access-Control-Allow-Headers": "*"
680
+ },
681
+ setupMiddlewares: (middlewares, devServer) => {
682
+ if (override.mock !== false) setupMockServer(middlewares, devServer);
683
+ return middlewares;
684
+ },
685
+ proxy: [createProxy("/api", "http://192.168.10.19:3339/qsxxwapdev", "http://www.zhidianbao.cn")],
686
+ compress: true
687
+ };
688
+ if (override.proxy) {
689
+ if (Array.isArray(override.proxy)) devServer.proxy = [...override.proxy, ...devServer.proxy || []];
690
+ else if (typeof override.proxy === "object") devServer.proxy = [...Object.entries(override.proxy).map(([context, target]) => {
691
+ return createProxy(context, target);
692
+ }), ...devServer.proxy || []];
693
+ else throw new Error("proxy 必须是数组或对象");
694
+ const proxyMap = /* @__PURE__ */ new Map();
695
+ devServer.proxy = devServer.proxy.filter((item) => {
696
+ const key = JSON.stringify([...item.context].sort());
697
+ if (!proxyMap.has(key)) {
698
+ proxyMap.set(key, true);
699
+ return true;
700
+ }
701
+ return false;
702
+ });
703
+ }
704
+ return devServer;
705
+ }
706
+
707
+ //#endregion
708
+ //#region src/utils/getOverride.ts
709
+ const require = createRequire(import.meta.url);
710
+ const defaultOverride = {
711
+ minify: true,
712
+ proxy: [],
713
+ extraPostCSSPlugins: [],
714
+ import: [],
715
+ pure_funcs: ["console.log"]
716
+ };
717
+ let override = null;
718
+ function getOverride() {
719
+ if (override) return override;
720
+ override = Object.assign({}, defaultOverride);
721
+ if (fs.existsSync(paths.override)) {
722
+ const userOverride = resolveModule(require(paths.override));
723
+ if (typeof userOverride !== "object") throw new Error("格式错误,请使用 npx edu g override 生成文件");
724
+ Object.assign(override, userOverride);
725
+ }
726
+ return override;
727
+ }
728
+
729
+ //#endregion
730
+ //#region src/utils/getConfig.ts
731
+ function getConfig(args) {
732
+ const override = getOverride();
733
+ let webpackConfig = getWebpackConfig(args, override);
734
+ if (override.webpack) webpackConfig = override.webpack(webpackConfig) || webpackConfig;
735
+ if (process.env.NODE_ENV === "development") {
736
+ let devServerConfig = getWebpackDevServerConfig(args, override);
737
+ if (override.devServer) devServerConfig = override.devServer(devServerConfig) || devServerConfig;
738
+ webpackConfig.devServer = devServerConfig;
739
+ }
740
+ return webpackConfig;
741
+ }
742
+
743
+ //#endregion
744
+ //#region src/start.ts
745
+ async function start(args) {
746
+ process.env.NODE_ENV = "development";
747
+ process.env.BABEL_ENV = "development";
748
+ process.env.BROWSERSLIST = "chrome >= 70";
749
+ process.env.WEBPACK_DEV_SERVER_BASE_PORT = "3000";
750
+ process.env.BROWSERSLIST_IGNORE_OLD_DATA = "true";
751
+ const basePort = process.env.WEBPACK_DEV_SERVER_BASE_PORT;
752
+ const port = await RspackDevServer.getFreePort(args.port || process.env.PORT);
753
+ if (!(args.port || process.env.PORT) && +port !== +basePort) console.log(chalk.bgYellow(`${basePort} 端口已被占用,现切换到 ${port} 端口运行`));
754
+ args.port = port;
755
+ process.env.PORT = port;
756
+ const config = getConfig(args);
757
+ const compiler = rspack(config);
758
+ const middleware = rspack.lazyCompilationMiddleware(compiler);
759
+ const oldSetupMiddlewares = config.devServer.setupMiddlewares;
760
+ config.devServer.setupMiddlewares = (middlewares, devServer) => {
761
+ if (oldSetupMiddlewares) middlewares = oldSetupMiddlewares(middlewares, devServer);
762
+ middlewares.unshift(middleware);
763
+ return middlewares;
764
+ };
765
+ const devServer = new RspackDevServer(config.devServer, compiler);
766
+ devServer.start();
767
+ ["SIGINT", "SIGTERM"].forEach(function(sig) {
768
+ process.on(sig, function() {
769
+ devServer.stop();
770
+ process.exit();
771
+ });
772
+ });
773
+ if (process.env.CI !== "true") process.stdin.on("end", function() {
774
+ devServer.stop();
775
+ process.exit();
776
+ });
777
+ }
778
+
779
+ //#endregion
780
+ //#region src/utils/FileSizeReporter.ts
781
+ /**
782
+ * Copyright (c) 2015-present, Facebook, Inc.
783
+ *
784
+ * This source code is licensed under the MIT license found in the
785
+ * LICENSE file in the root directory of this source tree.
786
+ */
787
+ function canReadAsset(asset) {
788
+ return /\.(js|css)$/.test(asset) && !/service-worker\.js/.test(asset) && !/precache-manifest\.[0-9a-f]+\.js/.test(asset);
789
+ }
790
+ function printFileSizesAfterBuild(webpackStats, previousSizeMap, buildFolder, maxBundleGzipSize, maxChunkGzipSize) {
791
+ const root = previousSizeMap.root;
792
+ const sizes = previousSizeMap.sizes;
793
+ const assets = (webpackStats.stats || [webpackStats]).map((stats) => stats.toJson({
794
+ all: false,
795
+ assets: true
796
+ }).assets.filter((asset) => canReadAsset(asset.name)).map((asset) => {
797
+ const size = gzipSizeSync(fs.readFileSync(path.join(root, asset.name)));
798
+ const previousSize = sizes[removeFileNameHash(root, asset.name)];
799
+ const difference = getDifferenceLabel(size, previousSize);
800
+ return {
801
+ folder: path.join(path.basename(buildFolder), path.dirname(asset.name)),
802
+ name: path.basename(asset.name),
803
+ size,
804
+ sizeLabel: filesize(size) + (difference ? " (" + difference + ")" : "")
805
+ };
806
+ })).reduce((single, all) => all.concat(single), []);
807
+ if (assets.length === 0) return;
808
+ console.log("\ngzip 后文件大小:\n");
809
+ assets.sort((a, b) => b.size - a.size);
810
+ const mainAssetIdx = assets.findIndex((asset) => /_\d+\.\d+\.\d+/.test(asset.name));
811
+ assets.unshift(assets.splice(mainAssetIdx, 1)[0]);
812
+ const longestSizeLabelLength = Math.max.apply(null, assets.map((a) => stripAnsi(a.sizeLabel).length));
813
+ let suggestBundleSplitting = false;
814
+ assets.forEach((asset) => {
815
+ let sizeLabel = asset.sizeLabel;
816
+ const sizeLength = stripAnsi(sizeLabel).length;
817
+ if (sizeLength < longestSizeLabelLength) {
818
+ const rightPadding = " ".repeat(longestSizeLabelLength - sizeLength);
819
+ sizeLabel += rightPadding;
820
+ }
821
+ const isMainBundle = /_\d+\.\d+\.\d+/.test(asset.name);
822
+ const maxRecommendedSize = isMainBundle ? maxBundleGzipSize : maxChunkGzipSize;
823
+ const isLarge = maxRecommendedSize && asset.size > maxRecommendedSize;
824
+ if (isLarge && path.extname(asset.name) === ".js") suggestBundleSplitting = true;
825
+ console.log(" " + (isLarge ? chalk.yellow(sizeLabel) : sizeLabel) + " " + chalk.dim(asset.folder + path.sep) + chalk.cyan(asset.name));
826
+ if (isMainBundle) console.log("");
827
+ });
828
+ if (suggestBundleSplitting) {
829
+ console.log();
830
+ console.log(chalk.yellow(`产物大小明显大于推荐的大小 (主文件 ${filesize(maxBundleGzipSize)}, chunk ${filesize(maxChunkGzipSize)}, 黄色标注为偏大)`));
831
+ console.log(chalk.yellow("考虑下使用代码分割解决"));
832
+ console.log(chalk.yellow("也可以使用 npm run analyze 命令分析产物"));
833
+ }
834
+ console.log();
835
+ }
836
+ function removeFileNameHash(buildFolder, fileName) {
837
+ return fileName.replace(buildFolder, "").replace(/\\/g, "/").replace(/\/\d+\.\d+\.\d+\//, "/").replace(/\/?(.*)(\.[0-9a-f]+)(\.chunk)?(\.js|\.css)/, (match, p1, p2, p3, p4) => p1 + p4);
838
+ }
839
+ function getDifferenceLabel(currentSize, previousSize) {
840
+ const FIFTY_KILOBYTES = 1024 * 50;
841
+ const difference = currentSize - previousSize;
842
+ const fileSize = !Number.isNaN(difference) ? filesize(difference) : 0;
843
+ if (difference >= FIFTY_KILOBYTES) return chalk.red("+" + fileSize);
844
+ else if (difference < FIFTY_KILOBYTES && difference > 0) return chalk.yellow("+" + fileSize);
845
+ else if (difference < 0) return chalk.green(fileSize);
846
+ else return "";
847
+ }
848
+ function measureFileSizesBeforeBuild(buildFolder) {
849
+ return new Promise((resolve) => {
850
+ recursive(buildFolder, (err, fileNames) => {
851
+ let sizes;
852
+ if (!err && fileNames) sizes = fileNames.filter(canReadAsset).reduce((memo, fileName) => {
853
+ const contents = fs.readFileSync(fileName);
854
+ const key = removeFileNameHash(buildFolder, fileName);
855
+ memo[key] = gzipSizeSync(contents);
856
+ return memo;
857
+ }, {});
858
+ resolve({
859
+ root: buildFolder,
860
+ sizes: sizes || {}
861
+ });
862
+ });
863
+ });
864
+ }
865
+
866
+ //#endregion
867
+ //#region src/build.ts
868
+ const WARN_AFTER_BUNDLE_GZIP_SIZE = appConfig.single ? 1024 * 1024 : 30 * 1024;
869
+ const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;
870
+ async function build(args) {
871
+ process.env.NODE_ENV = "production";
872
+ process.env.BABEL_ENV = "production";
873
+ process.env.BROWSERSLIST = ">0.2%, iOS>=9, ie 11, chrome>=49, not op_mini all";
874
+ process.env.BROWSERSLIST_IGNORE_OLD_DATA = "true";
875
+ if (args.analyze) process.env.ANALYZE = "1";
876
+ if (args.outputHtml) process.env.OUTPUT_HTML = "1";
877
+ const previousSizeMap = await measureFileSizesBeforeBuild(paths.dist);
878
+ fs.emptyDirSync(paths.dist);
879
+ if (appConfig.single) fs.copySync(paths.public, paths.resolveApp("dist"));
880
+ if (appConfig.mainProject && fs.existsSync(paths.static)) fs.copySync(paths.static, paths.resolveApp("dist", "static"));
881
+ rspack(getConfig(args), (error, stats) => {
882
+ if (error) {
883
+ console.log(chalk.red("编译失败"));
884
+ console.log(chalk.red(error.message || error));
885
+ process.exit(1);
886
+ }
887
+ if (!stats) return;
888
+ if (stats.compilation.errors.length) {
889
+ console.log(chalk.red("编译失败"));
890
+ console.log(stats.toString({
891
+ all: false,
892
+ errors: true,
893
+ colors: true
894
+ }));
895
+ process.exit(1);
896
+ }
897
+ console.log(stats.toString({
898
+ colors: true,
899
+ preset: "errors-warnings",
900
+ timings: true
901
+ }));
902
+ printFileSizesAfterBuild(stats, previousSizeMap, paths.dist, WARN_AFTER_BUNDLE_GZIP_SIZE, WARN_AFTER_CHUNK_GZIP_SIZE);
903
+ if (appConfig.single) console.log(`打包完成,可以使用 ${chalk.green("@qse/ssh-sftp")} 自动部署代码到 v1`);
904
+ else console.log(`打包完成,可以运行 ${chalk.green("npx edu-scripts deploy")} 部署代码到 v1`);
905
+ console.log();
906
+ });
907
+ }
908
+
909
+ //#endregion
910
+ //#region src/utils/changeDeployVersion.ts
911
+ const TARGET_IDENTIFIER_NAME = "project_apiArr";
912
+ const MODULE_IDENTIFIER_NAME = "module";
913
+ function changeDeployVersion(code, pkg) {
914
+ const { name, version, grayscale } = pkg;
915
+ let ast;
916
+ try {
917
+ ast = parse(code, { filename: "ver.js" });
918
+ } catch (error) {
919
+ throw new Error(`代码解析错误: ${error.message}`, { cause: error });
920
+ }
921
+ const keyName = grayscale ? "grayscale" : "main";
922
+ function findTargetDeclarator(ast) {
923
+ let res;
924
+ traverse(ast, { VariableDeclarator(path) {
925
+ if (types.isIdentifier(path.node.id, { name: TARGET_IDENTIFIER_NAME }) && types.isArrayExpression(path.node.init)) res = path;
926
+ } });
927
+ return res;
928
+ }
929
+ function findModuleObject(path) {
930
+ let res;
931
+ path.traverse({ ObjectExpression(path) {
932
+ if (path.node.properties.some((node) => types.isObjectProperty(node) && types.isIdentifier(node.key, { name: MODULE_IDENTIFIER_NAME }) && types.isLiteral(node.value, { value: name }))) res = path;
933
+ } });
934
+ return res;
935
+ }
936
+ function modifyModuleVersion(path) {
937
+ let hasModify = false;
938
+ path.traverse({ Property(path) {
939
+ if (types.isObjectProperty(path.node) && types.isIdentifier(path.node.key, { name: keyName })) {
940
+ if (types.isStringLiteral(path.node.value)) path.node.value.value = version;
941
+ hasModify = true;
942
+ }
943
+ } });
944
+ if (!hasModify) path.node.properties.push(types.objectProperty(types.identifier(keyName), types.stringLiteral(version)));
945
+ }
946
+ function deleteModuleComments(path) {
947
+ path.traverse({ ObjectExpression(path) {
948
+ delete path.node.leadingComments;
949
+ } });
950
+ }
951
+ function addModuleToTarget(path) {
952
+ if (types.isArrayExpression(path.node.init)) {
953
+ const elements = path.node.init.elements;
954
+ elements.splice(elements.length - 2, 0, types.objectExpression([types.objectProperty(types.identifier(MODULE_IDENTIFIER_NAME), types.stringLiteral(name)), types.objectProperty(types.identifier(keyName), types.stringLiteral(version))]));
955
+ }
956
+ }
957
+ const targetPath = findTargetDeclarator(ast);
958
+ if (!targetPath) throw new Error(`ver.js 不合规范,未找到参数 ${TARGET_IDENTIFIER_NAME}`);
959
+ const moduleObjPath = findModuleObject(targetPath);
960
+ if (moduleObjPath) modifyModuleVersion(moduleObjPath);
961
+ else addModuleToTarget(targetPath);
962
+ deleteModuleComments(targetPath);
963
+ const result = transformFromAstSync(ast, void 0, {
964
+ filename: "ver.js",
965
+ minified: true
966
+ });
967
+ if (!result || !result.code) throw new Error("代码转换失败");
968
+ return result.code;
969
+ }
970
+
971
+ //#endregion
972
+ //#region src/deploy.ts
973
+ const appPkg = fs.readJsonSync(paths.package);
974
+ const baseConfig = {
975
+ localPath: "dist",
976
+ ignore: [],
977
+ cleanRemoteFiles: false,
978
+ securityLock: false,
979
+ keepAlive: true,
980
+ noWarn: true
981
+ };
982
+ async function normalDeploy(args) {
983
+ const resolve = paths.resolveApp;
984
+ /**
985
+ * 生成路径
986
+ * @name remoteFile 远程文件
987
+ * @name tmpFile 本地文件
988
+ * @name tmpDir 本地临时文件夹 `tmpFile`存在的文件夹
989
+ * @name tmpBase 本地临时文件夹 到`__tmp__`层
990
+ * @param {string} remoteFilePath 远程文件相对`opts.remotePath`地址
991
+ */
992
+ function getLocalAndRemoteFilePath(remoteFilePath, opts) {
993
+ const splited = remoteFilePath.split("/");
994
+ const fileName = splited[splited.length - 1];
995
+ const tmpBase = resolve(opts.localPath, "__tmp__");
996
+ const tmpDir = resolve(opts.localPath, "__tmp__", ...splited.slice(0, -1));
997
+ return {
998
+ tmpDir,
999
+ tmpFile: resolve(tmpDir, fileName),
1000
+ remoteFile: [opts.remotePath, remoteFilePath].join("/"),
1001
+ tmpBase
1002
+ };
1003
+ }
1004
+ function dateTime() {
1005
+ let date = /* @__PURE__ */ new Date();
1006
+ date = /* @__PURE__ */ new Date(date.getTime() - date.getTimezoneOffset() * 6e4);
1007
+ return date.toISOString().replace(/T/, " ").replace(/\..+/, "");
1008
+ }
1009
+ function updateLogContent(content, info) {
1010
+ const lines = content.trim().split("\n");
1011
+ lines.push(`[${dateTime()}] ${JSON.stringify(info)}\n`);
1012
+ return lines.slice(-50).join("\n");
1013
+ }
1014
+ async function upload(opts) {
1015
+ const { sftp, opts: fullOpts } = await sshSftp(opts);
1016
+ const spinner = ora("自动更新 ver.js 版本配置").start();
1017
+ const { remoteFile, tmpDir, tmpFile, tmpBase } = getLocalAndRemoteFilePath("js/ver.js", fullOpts);
1018
+ try {
1019
+ fs.mkdirSync(tmpDir, { recursive: true });
1020
+ const info = {
1021
+ name: appPkg.name,
1022
+ version: appPkg.version,
1023
+ grayscale: appConfig.grayscale
1024
+ };
1025
+ {
1026
+ await sftp.fastGet(remoteFile, tmpFile);
1027
+ let code = await fs.readFile(tmpFile, { encoding: "utf-8" });
1028
+ code = await changeDeployVersion(code, info);
1029
+ code = await format(code, {
1030
+ parser: "babel",
1031
+ printWidth: 120
1032
+ });
1033
+ await sftp.fastPut(tmpFile, remoteFile + ".bak");
1034
+ await fs.writeFile(tmpFile, code);
1035
+ await sftp.fastPut(tmpFile, remoteFile);
1036
+ }
1037
+ {
1038
+ const remoteLogFile = remoteFile + ".log";
1039
+ const tmpLogFile = tmpFile + ".log";
1040
+ let content = "";
1041
+ try {
1042
+ await sftp.fastGet(remoteLogFile, tmpLogFile);
1043
+ content = await fs.readFile(tmpLogFile, "utf-8");
1044
+ } catch {}
1045
+ content = updateLogContent(content, info);
1046
+ await fs.writeFile(tmpLogFile, content);
1047
+ await sftp.fastPut(tmpLogFile, remoteLogFile);
1048
+ }
1049
+ spinner.succeed("已更新 ver.js 版本配置");
1050
+ } catch (e) {
1051
+ spinner.fail(`自动修改 ver.js 失败,请手动修改`);
1052
+ console.log(chalk.bgRed(e.message));
1053
+ } finally {
1054
+ await sftp.end();
1055
+ fs.removeSync(tmpBase);
1056
+ }
1057
+ }
1058
+ const presetConfig = {
1059
+ s: { preset: {
1060
+ context: "eduwebngv1",
1061
+ folder: "userportal"
1062
+ } },
1063
+ b: { preset: {
1064
+ context: "eduwebngv1",
1065
+ folder: "bureaupc"
1066
+ } },
1067
+ d: { preset: {
1068
+ context: "eduwebngv1",
1069
+ folder: "documentshelves"
1070
+ } },
1071
+ c: { preset: {
1072
+ context: "eduwebngv1",
1073
+ folder: "compositionshelves"
1074
+ } },
1075
+ cd: {
1076
+ preset: { server: "19" },
1077
+ remotePath: "/erp/edumaven/dingcorrection-page-dev/compositionshelves"
1078
+ }
1079
+ };
1080
+ const uploadSftpConfigs = [];
1081
+ if (args.bureau) uploadSftpConfigs.push(presetConfig.b);
1082
+ if (args.school) uploadSftpConfigs.push(presetConfig.s);
1083
+ if (args.documentshelves) uploadSftpConfigs.push(presetConfig.d);
1084
+ if (args.compositionshelves) uploadSftpConfigs.push(presetConfig.c);
1085
+ if (args.compositionshelvesDingtalk) uploadSftpConfigs.push(presetConfig.cd);
1086
+ if (uploadSftpConfigs.length === 0) {
1087
+ console.log(`
1088
+ ${chalk.red("指定 deploy 部署范围")}
1089
+ 执行 ${chalk.green("npx edu-scripts deploy -h")} 查看具体用法
1090
+ `);
1091
+ process.exit();
1092
+ }
1093
+ const uploadConfig = {
1094
+ ...baseConfig,
1095
+ ignore: [...baseConfig.ignore, "js/ver.js"]
1096
+ };
1097
+ if (!appConfig.mainProject) uploadConfig.ignore = [...uploadConfig.ignore, "!(js|images)"];
1098
+ for (const config of uploadSftpConfigs) await upload({
1099
+ ...uploadConfig,
1100
+ ...config
1101
+ });
1102
+ }
1103
+ async function singleDeploy() {
1104
+ sshSftp(fs.existsSync(paths.sshSftp) ? fs.readJsonSync(paths.sshSftp) : {
1105
+ ...baseConfig,
1106
+ preset: { context: "qsxxwapdev" },
1107
+ cleanRemoteFiles: true,
1108
+ securityLock: true,
1109
+ keepAlive: false,
1110
+ noWarn: false
1111
+ });
1112
+ }
1113
+ function deploy(args) {
1114
+ if (appConfig.single) singleDeploy();
1115
+ else normalDeploy(args);
1116
+ }
1117
+
1118
+ //#endregion
1119
+ //#region src/auto-refactor.ts
1120
+ const pkg$1 = fs.readJsonSync(paths.resolveOwn("package.json"));
1121
+ async function step(msg, callback) {
1122
+ const spinner = ora(msg).start();
1123
+ try {
1124
+ await callback(spinner);
1125
+ spinner.succeed();
1126
+ } catch (error) {
1127
+ spinner.fail();
1128
+ throw error;
1129
+ }
1130
+ }
1131
+ async function autoRefactor() {
1132
+ if (await fs.pathExists(paths.public)) {
1133
+ console.log(chalk.green("已完成改造,不需要重复执行,如果运行报错请查看文档"));
1134
+ console.log(`文档: ${chalk.underline(pkg$1.homepage)}`);
1135
+ process.exit(0);
1136
+ }
1137
+ const appPkg = fs.readJsonSync(paths.package);
1138
+ const name = await input({
1139
+ message: "请输入模块名称,要求唯一,不能与其他工程相同",
1140
+ default: appPkg.name,
1141
+ validate: (v) => /^[a-z][a-z0-9_-]+$/.test(v) || "请按格式输入 /^[a-z][a-z0-9_-]+$/ 只能使用小写字母、连字符、下划线"
1142
+ });
1143
+ const version = await input({
1144
+ message: "版本号",
1145
+ default: "1.0.0",
1146
+ validate: (v) => /^\d+\.\d+\.\d+$/.test(v) || "请按格式输入 /^\\d+\\.\\d+\\.\\d+$/ 例如: 1.0.0"
1147
+ });
1148
+ const mode = await select({
1149
+ message: "项目使用的哪种模式",
1150
+ choices: [
1151
+ {
1152
+ name: "教育子工程模式",
1153
+ value: ""
1154
+ },
1155
+ {
1156
+ name: "教育主工程模式",
1157
+ value: "main"
1158
+ },
1159
+ {
1160
+ name: "独立模式",
1161
+ value: "single"
1162
+ }
1163
+ ]
1164
+ });
1165
+ let deploy = [];
1166
+ if (mode !== "single") deploy = await checkbox({
1167
+ message: "项目需要部署到哪里",
1168
+ choices: [
1169
+ {
1170
+ name: "校端",
1171
+ value: "-s"
1172
+ },
1173
+ {
1174
+ name: "局端",
1175
+ value: "-b"
1176
+ },
1177
+ {
1178
+ name: "公文端",
1179
+ value: "-d"
1180
+ }
1181
+ ],
1182
+ validate: (v) => !!v && v.length > 0 || "必须选一个"
1183
+ });
1184
+ const answers = {
1185
+ name,
1186
+ version,
1187
+ mode,
1188
+ deploy
1189
+ };
1190
+ appPkg.name = answers.name;
1191
+ appPkg.version = answers.version;
1192
+ await step("创建 public 文件夹", async () => {
1193
+ await fs.mkdir(paths.public);
1194
+ });
1195
+ await step("移动 js 文件夹", async () => {
1196
+ if (await fs.pathExists(path.resolve(paths.src, "js"))) await fs.move(path.resolve(paths.src, "js"), path.resolve(paths.public, "js"));
1197
+ });
1198
+ await step("移动 html 文件", async () => {
1199
+ const HTMLFiles = await globby("*.html", { cwd: paths.src });
1200
+ for (const file of HTMLFiles) await fs.move(path.resolve(paths.src, file), path.resolve(paths.public, file));
1201
+ });
1202
+ await step("删除 dll 文件夹", async () => {
1203
+ if (await fs.pathExists(path.resolve(paths.src, "dll"))) await fs.remove(path.resolve(paths.src, "dll"));
1204
+ });
1205
+ await step("删除没用的 babel 和 webpack 配置", async () => {
1206
+ const deleteFiles = [
1207
+ ...await globby("{.,*}babel*"),
1208
+ ...await globby("*webpack*"),
1209
+ ...await globby("package-lock.json"),
1210
+ paths.nodeModules,
1211
+ paths.sshSftp
1212
+ ];
1213
+ for (const filePath of deleteFiles) await fs.remove(filePath);
1214
+ });
1215
+ await step("设置项目模式", () => {
1216
+ if (answers.mode) appPkg.edu = { mode: answers.mode };
1217
+ });
1218
+ await step("修改 package.json 的 scripts", () => {
1219
+ const scripts = appPkg.scripts;
1220
+ scripts.start = "edu-scripts start";
1221
+ scripts.build = "edu-scripts build";
1222
+ scripts.analyze = "edu-scripts build --analyze";
1223
+ if (answers.mode !== "single") scripts.deploy = `edu-scripts deploy ${answers.deploy.join(" ")}`;
1224
+ else {
1225
+ scripts.deploy = `edu-scripts deploy`;
1226
+ scripts["commit-dist"] = "edu-scripts commit-dist --rm-local";
1227
+ }
1228
+ scripts["one-key-deploy"] = "npm version patch";
1229
+ scripts.postversion = "npm run build && npm run deploy";
1230
+ });
1231
+ await step("删除 babel/webpack 相关依赖", (spinner) => {
1232
+ const deleteRe = /(babel|autoprefixer|webpack|loader|less|css|sass|hmr|ssh-sftp|regenerator-runtime|nowa|prettier)/i;
1233
+ const deletePkgs = {
1234
+ dependencies: [],
1235
+ devDependencies: []
1236
+ };
1237
+ Object.entries(deletePkgs).forEach(([scope, pkgs]) => {
1238
+ for (const key in appPkg[scope]) if (Object.hasOwnProperty.call(appPkg[scope], key)) {
1239
+ if (deleteRe.test(key)) {
1240
+ pkgs.push(key);
1241
+ delete appPkg[scope][key];
1242
+ }
1243
+ }
1244
+ });
1245
+ spinner.clear();
1246
+ console.log(`删除的pkgs\n${chalk.green(JSON.stringify(deletePkgs, null, 2))}\n`);
1247
+ appPkg.devDependencies["@qse/edu-scripts"] = "^" + pkg$1.version;
1248
+ });
1249
+ await fs.writeFile(paths.package, JSON.stringify(appPkg, null, 2), "utf-8");
1250
+ console.log(chalk.green(`
1251
+ 改造还未完成,剩余步骤请查看文档
1252
+ ${pkg$1.homepage}#/refactor
1253
+
1254
+ 运行 npm i 安装依赖
1255
+ 运行 edu-scripts start 启动服务
1256
+ `));
1257
+ }
1258
+
1259
+ //#endregion
1260
+ //#region src/commit-dist.ts
1261
+ const exec = (cmd, opts) => cp.execSync(cmd, {
1262
+ encoding: "utf-8",
1263
+ stdio: "pipe",
1264
+ ...opts
1265
+ });
1266
+ function validateSVNRoot(root) {
1267
+ const ls = exec(`svn ls ${root}`);
1268
+ return ["trunk", "branches"].every((s) => ls.includes(s));
1269
+ }
1270
+ function getWorkingCopyInfo() {
1271
+ exec(`svn up`);
1272
+ const url = exec(`svn info --show-item url`).trim();
1273
+ const revision = exec(`svn info --show-item last-changed-revision`).trim();
1274
+ const author = exec(`svn info --show-item last-changed-author`).trim();
1275
+ let branch = "trunk";
1276
+ let root = url.replace(/\/trunk$/, "");
1277
+ if (url.includes("/branches/")) {
1278
+ branch = url.split("/").pop();
1279
+ root = url.replace(/\/branches\/[^/]+$/, "");
1280
+ }
1281
+ let distBranchURL = root + "/branches/dist";
1282
+ let distBranchDirURL = distBranchURL + "/" + branch;
1283
+ if (!validateSVNRoot(root)) {
1284
+ console.log(chalk.red("SVN目录不符合规则,必须包含 trunk branches"));
1285
+ process.exit(1);
1286
+ }
1287
+ return {
1288
+ url,
1289
+ branch,
1290
+ revision,
1291
+ author,
1292
+ distBranchURL,
1293
+ distBranchDirURL,
1294
+ root
1295
+ };
1296
+ }
1297
+ function copyDistToRepo(info) {
1298
+ const tmpdir = tmp.dirSync().name;
1299
+ try {
1300
+ exec(`svn ls ${info.distBranchDirURL} --depth empty`);
1301
+ } catch (error) {
1302
+ if (error.message.includes("non-existent")) exec(`svn mkdir ${info.distBranchDirURL} --parents -m "[edu-scripts] create ${info.branch} dist"`);
1303
+ else throw error;
1304
+ }
1305
+ exec(`svn co ${info.distBranchDirURL} ${tmpdir}`);
1306
+ try {
1307
+ exec(`svn rm * --force -q`, { cwd: tmpdir });
1308
+ } catch {}
1309
+ fs.copySync(paths.dist, tmpdir);
1310
+ exec(`svn add * --force --auto-props --parents --depth infinity -q`, { cwd: tmpdir });
1311
+ exec(`svn ci -m "${`[edu-scripts] commit ${info.branch} dist #${info.revision} @${info.author}`}"`, { cwd: tmpdir });
1312
+ fs.removeSync(tmpdir);
1313
+ }
1314
+ async function commitDist(args) {
1315
+ if (!fs.existsSync(paths.dist)) {
1316
+ console.log(chalk.red("未找到 dist 文件夹,请先 edu-scpirts build"));
1317
+ process.exit(1);
1318
+ }
1319
+ if (exec("svn st").trim().length) {
1320
+ console.log(chalk.red("似乎存在未提交的代码,请提交后重试。运行 svn st 查看具体信息"));
1321
+ process.exit(1);
1322
+ }
1323
+ const info = getWorkingCopyInfo();
1324
+ console.log(chalk.green([
1325
+ `分支: ${info.branch}`,
1326
+ `版本: ${info.revision}`,
1327
+ `作者: ${info.author}`,
1328
+ `地址: ${info.distBranchDirURL}`
1329
+ ].join("\n")));
1330
+ copyDistToRepo(info);
1331
+ if (args.rmLocal) fs.removeSync(paths.dist);
1332
+ console.log(chalk.green("提交完成"));
1333
+ }
1334
+
1335
+ //#endregion
1336
+ //#region src/generator.ts
1337
+ const getTmpPath = (...args) => paths.resolveOwn("asset", "template", ...args);
1338
+ async function generatorOverride() {
1339
+ if (fs.existsSync(paths.override)) {
1340
+ console.log(chalk.red(`文件已存在 ${paths.override}`));
1341
+ process.exit(0);
1342
+ }
1343
+ fs.copySync(getTmpPath("edu-scripts.override.js.tpl"), paths.override);
1344
+ console.log(chalk.green(`成功生成 ${paths.override}`));
1345
+ }
1346
+ async function generatorTsconfig() {
1347
+ if (fs.existsSync(paths.tsconfig)) {
1348
+ console.log(chalk.red(`文件已存在 ${paths.tsconfig}`));
1349
+ process.exit(0);
1350
+ }
1351
+ fs.copySync(getTmpPath("tsconfig.json.tpl"), paths.tsconfig);
1352
+ fs.copySync(getTmpPath("edu-app-env.d.ts.tpl"), paths.eduAppEnv);
1353
+ console.log(chalk.green(`成功生成 ${paths.tsconfig}`));
1354
+ }
1355
+ async function generatorTailwind() {
1356
+ if (fs.existsSync(paths.tailwind)) {
1357
+ console.log(chalk.red(`文件已存在 ${paths.tailwind}`));
1358
+ process.exit(0);
1359
+ }
1360
+ fs.copySync(getTmpPath("tailwind.config.js.tpl"), paths.tailwind);
1361
+ const code = [
1362
+ "@tailwind base;",
1363
+ "@tailwind components;",
1364
+ "@tailwind utilities;"
1365
+ ].join("\n");
1366
+ const globalLessFile = paths.resolveApp("src", "index.less");
1367
+ if (fs.existsSync(globalLessFile)) {
1368
+ const content = fs.readFileSync(globalLessFile, "utf-8");
1369
+ if (!content.includes("@tailwind base")) fs.writeFileSync(globalLessFile, [code, content].join("\n"));
1370
+ console.log(chalk.green(`成功生成 ${paths.tailwind}`));
1371
+ return;
1372
+ }
1373
+ console.log(chalk.green([
1374
+ `成功生成 ${paths.tailwind}`,
1375
+ "",
1376
+ "添加以下代码到入口处的 index.less 中",
1377
+ code
1378
+ ].join("\n")));
1379
+ }
1380
+
1381
+ //#endregion
1382
+ //#region src/cli.ts
1383
+ const pkg = fs.readJsonSync(paths.resolveOwn("package.json"));
1384
+ yargs(process.argv.slice(2)).usage(`教育工程化 webpack5 基础框架\n文档: ${pkg.homepage}`).command("start", "开发", (yargs) => yargs.option("port", {
1385
+ alias: "p",
1386
+ desc: "指定端口",
1387
+ string: true
1388
+ }), (args) => start(args)).command("build", "打包", (yargs) => yargs.option("analyze", {
1389
+ alias: "a",
1390
+ desc: "分析代码",
1391
+ default: false,
1392
+ boolean: true
1393
+ }).option("output-html", {
1394
+ alias: "o",
1395
+ desc: "输出 html 文件",
1396
+ default: false,
1397
+ boolean: true
1398
+ }), (args) => build(args)).command("deploy", "自动部署 dist 到 v1 服务器", (yargs) => yargs.option("school", {
1399
+ alias: "s",
1400
+ desc: "上传到校端",
1401
+ default: false,
1402
+ boolean: true
1403
+ }).option("bureau", {
1404
+ alias: "b",
1405
+ desc: "上传到局端",
1406
+ default: false,
1407
+ boolean: true
1408
+ }).option("documentshelves", {
1409
+ alias: "d",
1410
+ desc: "上传到公文",
1411
+ default: false,
1412
+ boolean: true
1413
+ }).option("compositionshelves", {
1414
+ alias: "c",
1415
+ desc: "上传到文曲智阅",
1416
+ default: false,
1417
+ boolean: true
1418
+ }).option("compositionshelves-dingtalk", {
1419
+ alias: "cd",
1420
+ desc: "上传到文曲智阅(钉钉一方化)",
1421
+ default: false,
1422
+ boolean: true
1423
+ }), (args) => deploy(args)).command(["generator", "g"], "自动生成代码", (yargs) => yargs.command("override", "创建 override 文件", {}, () => generatorOverride()).command("tailwind", "创建 tailwind 文件", {}, () => generatorTailwind()).command("ts", "创建 tsconfig 文件", {}, () => generatorTsconfig()).showHelpOnFail(true).demandCommand(1, "")).command("auto-refactor", "自动改造项目", {}, () => autoRefactor()).command("commit-dist", "提交 dist 目录到 dist 分支", (yargs) => yargs.option("rm-local", {
1424
+ desc: "提交完后删除本地 dist",
1425
+ type: "boolean"
1426
+ }), (args) => commitDist(args)).showHelpOnFail(true).demandCommand(1, "").alias({
1427
+ v: "version",
1428
+ h: "help"
1429
+ }).argv;
1430
+
1431
+ //#endregion
1432
+ export { };