@qse/edu-scripts 0.0.0-beta.0

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 (81) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +107 -0
  3. package/app.d.ts +73 -0
  4. package/babel.config.json +3 -0
  5. package/docs/.vitepress/config.ts +35 -0
  6. package/docs/changelog.md +1 -0
  7. package/docs/debug.md +17 -0
  8. package/docs/deploy.md +54 -0
  9. package/docs/faq.md +144 -0
  10. package/docs/feat.md +167 -0
  11. package/docs/grayscale.md +31 -0
  12. package/docs/index.md +15 -0
  13. package/docs/install.md +1 -0
  14. package/docs/mode.md +42 -0
  15. package/docs/override.md +193 -0
  16. package/docs/refactor-react-16.md +37 -0
  17. package/docs/refactor.md +67 -0
  18. package/docs/static.md +24 -0
  19. package/es/asset/dll/libcommon3-manifest.json +181 -0
  20. package/es/asset/template/edu-app-env.d.ts.tpl +20 -0
  21. package/es/asset/template/edu-scripts.override.js.tpl +7 -0
  22. package/es/asset/template/tailwind.config.js.tpl +11 -0
  23. package/es/asset/template/tsconfig.json.tpl +24 -0
  24. package/es/auto-refactor.js +153 -0
  25. package/es/build.js +58 -0
  26. package/es/cli.js +65 -0
  27. package/es/commit-dist.js +83 -0
  28. package/es/config/paths.js +39 -0
  29. package/es/config/plugins/mock-server/defineMock.d.ts +6 -0
  30. package/es/config/plugins/mock-server/defineMock.js +7 -0
  31. package/es/config/plugins/mock-server/index.js +122 -0
  32. package/es/config/plugins/postcss-safe-area.js +22 -0
  33. package/es/config/plugins/ws-utils-createSocketURL.js +98 -0
  34. package/es/config/webpackConfig.js +418 -0
  35. package/es/config/webpackDevServerConfig.js +73 -0
  36. package/es/deploy.js +148 -0
  37. package/es/generator.js +52 -0
  38. package/es/index.d.ts +2 -0
  39. package/es/index.js +7 -0
  40. package/es/start.js +36 -0
  41. package/es/utils/FileSizeReporter.js +107 -0
  42. package/es/utils/appConfig.js +35 -0
  43. package/es/utils/beforeStart.js +51 -0
  44. package/es/utils/changeDeployVersion.js +85 -0
  45. package/es/utils/defineConfig.d.ts +76 -0
  46. package/es/utils/defineConfig.js +7 -0
  47. package/es/utils/exec.js +10 -0
  48. package/es/utils/getConfig.js +23 -0
  49. package/es/utils/getOverride.js +28 -0
  50. package/eslint.config.mjs +3 -0
  51. package/jest.config.mjs +199 -0
  52. package/package.json +95 -0
  53. package/src/asset/dll/libcommon3-manifest.json +181 -0
  54. package/src/asset/template/edu-app-env.d.ts.tpl +20 -0
  55. package/src/asset/template/edu-scripts.override.js.tpl +7 -0
  56. package/src/asset/template/tailwind.config.js.tpl +11 -0
  57. package/src/asset/template/tsconfig.json.tpl +24 -0
  58. package/src/auto-refactor.js +170 -0
  59. package/src/build.js +64 -0
  60. package/src/cli.js +88 -0
  61. package/src/commit-dist.js +103 -0
  62. package/src/config/paths.js +38 -0
  63. package/src/config/plugins/mock-server/defineMock.ts +12 -0
  64. package/src/config/plugins/mock-server/index.js +150 -0
  65. package/src/config/plugins/postcss-safe-area.js +21 -0
  66. package/src/config/plugins/ws-utils-createSocketURL.js +140 -0
  67. package/src/config/webpackConfig.js +444 -0
  68. package/src/config/webpackDevServerConfig.js +83 -0
  69. package/src/deploy.js +182 -0
  70. package/src/generator.js +67 -0
  71. package/src/index.ts +2 -0
  72. package/src/start.js +37 -0
  73. package/src/utils/FileSizeReporter.js +148 -0
  74. package/src/utils/appConfig.js +36 -0
  75. package/src/utils/beforeStart.js +55 -0
  76. package/src/utils/changeDeployVersion.js +119 -0
  77. package/src/utils/defineConfig.ts +81 -0
  78. package/src/utils/exec.js +7 -0
  79. package/src/utils/getConfig.js +26 -0
  80. package/src/utils/getOverride.js +33 -0
  81. package/tsconfig.json +21 -0
@@ -0,0 +1,444 @@
1
+ import fs from 'fs-extra'
2
+ import { rspack } from '@rspack/core'
3
+ import HtmlWebpackPlugin from 'html-webpack-plugin'
4
+ import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'
5
+ import ReactRefreshPlugin from '@rspack/plugin-react-refresh'
6
+ import paths from './paths.js'
7
+ import appConfig from '../utils/appConfig.js'
8
+ import { once } from 'lodash-es'
9
+ import { createRequire } from 'node:module'
10
+
11
+ const require = createRequire(import.meta.url)
12
+ const appPkg = fs.readJsonSync(paths.package)
13
+
14
+ const jsMainPath = appConfig.grayscale
15
+ ? `${appPkg.name}/beta/${appPkg.name}`
16
+ : `${appPkg.name}/${appPkg.name}`
17
+ const assetPath = appConfig.grayscale
18
+ ? `${appPkg.name}/beta/${appPkg.version}`
19
+ : `${appPkg.name}/${appPkg.version}`
20
+
21
+ const cssRegex = /\.css$/
22
+ const cssModuleRegex = /\.module\.css$/
23
+ const lessRegex = /\.less$/
24
+ const lessModuleRegex = /\.module\.less$/
25
+
26
+ const imageInlineSizeLimit = 10 * 1024
27
+
28
+ const qseCDN = (() => {
29
+ const contents = paths.indexHTML.map((url) => fs.readFileSync(url, 'utf-8'))
30
+
31
+ function include(pattern) {
32
+ const regexp = new RegExp(pattern)
33
+ return contents.some((content) => regexp.test(content))
34
+ }
35
+
36
+ return {
37
+ isUseCommon: include(/react16.14.*_common31?.js|react-dev-preset.js/),
38
+ isUseAxios: include(/react16.14.*_axios0.21.1|react-dev-preset.js/),
39
+ isUseMoment: include('moment2.29.1.js'),
40
+ isUseAntd: include('antd3.26.20.js'),
41
+ isUseQsbAntd: include('qsb-antd.min.js'),
42
+ isUseQsbSchemeRender: include('qsb-scheme-render.min.js'),
43
+ }
44
+ })()
45
+
46
+ /**
47
+ * @param {*} args
48
+ * @param {import('../utils/defineConfig').Config} override
49
+ */
50
+ export default function getWebpackConfig(args, override) {
51
+ const isDev = process.env.NODE_ENV === 'development'
52
+ const isProd = process.env.NODE_ENV === 'production'
53
+
54
+ // common function to get style loaders
55
+ const getStyleLoaders = (cssOptions, preProcessor) => {
56
+ const loaders = [
57
+ {
58
+ loader: require.resolve('style-loader'),
59
+ options: { attributes: { 'data-module': appPkg.name, 'data-version': appPkg.version } },
60
+ },
61
+ {
62
+ loader: require.resolve('css-loader'),
63
+ options: cssOptions,
64
+ },
65
+ {
66
+ // Options for PostCSS as we reference these options twice
67
+ // Adds vendor prefixing based on your specified browser support in
68
+ // package.json
69
+ loader: require.resolve('postcss-loader'),
70
+ options: {
71
+ postcssOptions: {
72
+ // Necessary for external CSS imports to work
73
+ // https://github.com/facebook/create-react-app/issues/2677
74
+ ident: 'postcss',
75
+ config: false,
76
+ plugins: [
77
+ isProd && require('cssnano')({ preset: 'default' }),
78
+ fs.existsSync(paths.tailwind) && require.resolve('tailwindcss'),
79
+ require.resolve('postcss-flexbugs-fixes'),
80
+ [
81
+ require.resolve('postcss-preset-env'),
82
+ {
83
+ autoprefixer: {
84
+ flexbox: 'no-2009',
85
+ },
86
+ // https://preset-env.cssdb.org/features/#stage-2
87
+ },
88
+ ],
89
+ isProd && require('./plugins/postcss-safe-area').default(),
90
+ isProd && [require.resolve('postcss-momentum-scrolling'), ['scroll', 'auto']],
91
+ require.resolve('postcss-normalize'),
92
+ ...override.extraPostCSSPlugins,
93
+ ].filter(Boolean),
94
+ },
95
+ sourceMap: isDev,
96
+ },
97
+ },
98
+ ]
99
+ if (preProcessor === 'less-loader') {
100
+ loaders.push({
101
+ loader: require.resolve('less-loader'),
102
+ options: {
103
+ lessOptions: {
104
+ javascriptEnabled: true,
105
+ modifyVars: fs.existsSync(paths.theme) ? require(paths.theme) : undefined,
106
+ },
107
+ sourceMap: true,
108
+ },
109
+ })
110
+ }
111
+ return loaders
112
+ }
113
+
114
+ /** @type {import('@rspack/core').Configuration} */
115
+ const config = {
116
+ context: process.cwd(),
117
+ mode: process.env.NODE_ENV,
118
+ entry: './src/index',
119
+ target: 'browserslist',
120
+ output: {
121
+ filename: appConfig.single
122
+ ? `js/${jsMainPath}_${appPkg.version}.[contenthash:6].js`
123
+ : `js/${jsMainPath}_${appPkg.version}.js`,
124
+ chunkFilename: `js/${assetPath}/[name].[chunkhash:8].js`,
125
+ assetModuleFilename: `images/${assetPath}/[name].[hash:6][ext]`,
126
+ uniqueName: appPkg.name,
127
+ publicPath: '',
128
+ },
129
+ externals: Object.assign(
130
+ {},
131
+ qseCDN.isUseCommon && {
132
+ react: 'React',
133
+ 'react-dom': 'ReactDOM',
134
+ 'natty-fetch': 'nattyFetch',
135
+ 'natty-storage': 'nattyStorage',
136
+ 'common-utils': 'CommonUtils',
137
+ '@qse/common-utils': 'CommonUtils',
138
+ },
139
+ qseCDN.isUseAxios && { axios: 'axios' },
140
+ qseCDN.isUseMoment && { moment: 'moment' },
141
+ qseCDN.isUseAntd &&
142
+ Object.assign(
143
+ {
144
+ react: 'React',
145
+ 'react-dom': 'ReactDOM',
146
+ moment: 'moment',
147
+ antd: 'antd',
148
+ },
149
+ qseCDN.isUseQsbAntd && {
150
+ '@qse/antd': 'qsbAntd',
151
+ '@qsb/antd': 'qsbAntd',
152
+ },
153
+ qseCDN.isUseQsbSchemeRender && {
154
+ '@qse/scheme-render': 'qsbSchemeRender',
155
+ '@qsb/scheme-render': 'qsbSchemeRender',
156
+ }
157
+ ),
158
+
159
+ // 教育工程这些一定都需要 external
160
+ !appConfig.single &&
161
+ Object.assign(
162
+ {
163
+ react: 'React',
164
+ 'react-dom': 'ReactDOM',
165
+ 'natty-fetch': 'nattyFetch',
166
+ 'natty-storage': 'nattyStorage',
167
+ 'common-utils': 'CommonUtils',
168
+ '@qse/common-utils': 'CommonUtils',
169
+ moment: 'moment',
170
+ antd: 'antd',
171
+ },
172
+ isProd && {
173
+ '@qse/antd': 'qsbAntd',
174
+ '@qsb/antd': 'qsbAntd',
175
+ '@qse/scheme-render': 'qsbSchemeRender',
176
+ '@qsb/scheme-render': 'qsbSchemeRender',
177
+ }
178
+ ),
179
+ override.externals
180
+ ),
181
+ resolve: {
182
+ alias: {
183
+ '@': paths.src,
184
+ ...override.alias,
185
+ },
186
+ extensions: ['.web.js', '.web.mjs', '.js', '.mjs', '.jsx', '.ts', '.tsx', '.json', '.wasm'],
187
+ },
188
+ stats: isDev ? { preset: 'errors-warnings', timings: true } : undefined,
189
+ devtool: isDev ? 'cheap-module-source-map' : false,
190
+ module: {
191
+ rules: [
192
+ {
193
+ oneOf: [
194
+ {
195
+ resourceQuery: /raw/,
196
+ type: 'asset/source',
197
+ },
198
+ {
199
+ test: /\.[jt]sx?$/,
200
+ use: [
201
+ {
202
+ loader: 'builtin:swc-loader',
203
+ options: {
204
+ rspackExperiments: {
205
+ import: [
206
+ {
207
+ libraryName: 'lodash',
208
+ libraryDirectory: '',
209
+ camel2DashComponentName: false,
210
+ },
211
+ ...override.import,
212
+ ],
213
+ },
214
+ jsc: {
215
+ parser: {
216
+ syntax: 'typescript',
217
+ tsx: true,
218
+ decorators: override.decorators,
219
+ },
220
+ // externalHelpers: true,
221
+ transform: {
222
+ react: {
223
+ runtime: 'automatic',
224
+ development: isDev,
225
+ refresh: isDev,
226
+ },
227
+ },
228
+ },
229
+ },
230
+ },
231
+ ],
232
+ },
233
+ {
234
+ test: cssRegex,
235
+ exclude: cssModuleRegex,
236
+ use: getStyleLoaders({
237
+ importLoaders: 1,
238
+ sourceMap: isDev,
239
+ modules: {
240
+ mode: 'global',
241
+ localIdentName: '[local]--[hash:base64:6]',
242
+ },
243
+ }),
244
+ sideEffects: true,
245
+ },
246
+ {
247
+ test: cssModuleRegex,
248
+ use: getStyleLoaders({
249
+ importLoaders: 1,
250
+ sourceMap: isDev,
251
+ modules: {
252
+ mode: 'local',
253
+ localIdentName: '[local]--[hash:base64:6]',
254
+ },
255
+ }),
256
+ },
257
+ {
258
+ test: lessRegex,
259
+ exclude: lessModuleRegex,
260
+ use: getStyleLoaders(
261
+ {
262
+ importLoaders: 2,
263
+ sourceMap: isDev,
264
+ modules: {
265
+ mode: 'global',
266
+ localIdentName: '[local]--[hash:base64:6]',
267
+ },
268
+ },
269
+ 'less-loader'
270
+ ),
271
+ sideEffects: true,
272
+ },
273
+ {
274
+ test: lessModuleRegex,
275
+ use: getStyleLoaders(
276
+ {
277
+ importLoaders: 2,
278
+ sourceMap: isDev,
279
+ modules: {
280
+ mode: 'local',
281
+ localIdentName: '[local]--[hash:base64:6]',
282
+ },
283
+ },
284
+ 'less-loader'
285
+ ),
286
+ },
287
+ {
288
+ test: /\.(bmp|png|jpe?g|gif|webp)$/,
289
+ type: 'asset',
290
+ parser: {
291
+ dataUrlCondition: {
292
+ maxSize: imageInlineSizeLimit, // 10kb
293
+ },
294
+ },
295
+ },
296
+ {
297
+ test: /\.svg$/,
298
+ type: 'asset',
299
+ parser: {
300
+ dataUrlCondition: {
301
+ maxSize: imageInlineSizeLimit, // 10kb
302
+ },
303
+ },
304
+ issuer: {
305
+ and: [/\.(css|less)$/],
306
+ },
307
+ },
308
+ {
309
+ test: /\.svg$/,
310
+ use: [
311
+ {
312
+ loader: require.resolve('@svgr/webpack'),
313
+ options: {
314
+ prettier: false,
315
+ svgo: false,
316
+ svgoConfig: {
317
+ plugins: [{ removeViewBox: false }],
318
+ },
319
+ titleProp: true,
320
+ ref: false,
321
+ },
322
+ },
323
+ {
324
+ loader: require.resolve('url-loader'),
325
+ options: {
326
+ limit: imageInlineSizeLimit,
327
+ },
328
+ },
329
+ ],
330
+ issuer: {
331
+ and: [/\.(ts|tsx|js|jsx|md|mdx)$/],
332
+ },
333
+ },
334
+ {
335
+ test: /\.md$/,
336
+ type: 'asset/source',
337
+ },
338
+ {
339
+ // Exclude `js` files to keep "css" loader working as it injects
340
+ // its runtime that would otherwise be processed through "file" loader.
341
+ // Also exclude `html` and `json` extensions so they get processed
342
+ // by webpacks internal loaders.
343
+ test: /\.(?!(?:js|mjs|jsx|ts|tsx|html|json)$)[^.]+$/,
344
+ type: 'asset/resource',
345
+ },
346
+ ].filter(Boolean),
347
+ },
348
+ ],
349
+ },
350
+ plugins: [
351
+ qseCDN.isUseCommon &&
352
+ new rspack.DllReferencePlugin({
353
+ manifest: require('../asset/dll/libcommon3-manifest.json'),
354
+ }),
355
+ new rspack.NormalModuleReplacementPlugin(/createSocketURL\.js$/, (resource) => {
356
+ if (resource.context.includes('webpack-dev-server')) {
357
+ resource.request = require.resolve('./plugins/ws-utils-createSocketURL')
358
+ }
359
+ }),
360
+ new rspack.IgnorePlugin({
361
+ resourceRegExp: /^\.\/locale$/,
362
+ contextRegExp: /moment$/,
363
+ }),
364
+ new rspack.DefinePlugin({
365
+ 'process.env.APP_NAME': JSON.stringify(appPkg.name),
366
+ 'process.env.APP_VERSION': JSON.stringify(appPkg.version),
367
+ 'process.env.BABEL_ENV': JSON.stringify(process.env.BABEL_ENV),
368
+ 'process.env.BROWSERSLIST': JSON.stringify(process.env.BROWSERSLIST),
369
+ ...override.define,
370
+ }),
371
+ new rspack.ProgressPlugin(),
372
+ isDev && new rspack.CaseSensitivePlugin(),
373
+ isDev && new ReactRefreshPlugin({ overlay: false }),
374
+ ...(isDev || process.env.OUTPUT_HTML || appConfig.single || appConfig.mainProject
375
+ ? paths.indexHTML.map(
376
+ (template) =>
377
+ new HtmlWebpackPlugin({
378
+ template: template,
379
+ filename: template.split('/').pop(),
380
+ inject: false,
381
+ minify: {
382
+ removeComments: true,
383
+ collapseWhitespace: true,
384
+ removeRedundantAttributes: true,
385
+ useShortDoctype: true,
386
+ removeEmptyAttributes: true,
387
+ removeStyleLinkTypeAttributes: true,
388
+ keepClosingSlash: true,
389
+ minifyJS: true,
390
+ minifyCSS: true,
391
+ minifyURLs: true,
392
+ },
393
+ })
394
+ )
395
+ : []),
396
+ process.env.ANALYZE && isProd && new BundleAnalyzerPlugin(),
397
+ isDev &&
398
+ !!override.startup &&
399
+ ((compiler) => {
400
+ compiler.hooks.afterDone.tap(
401
+ 'edu-scripts-startup',
402
+ once(() => {
403
+ const logger = compiler.getInfrastructureLogger('edu-scripts')
404
+ override.startup({ logger, chalk: require('chalk'), compiler })
405
+ })
406
+ )
407
+ }),
408
+ ].filter(Boolean),
409
+ optimization: {
410
+ minimize: isProd && override.minify !== false,
411
+ minimizer: [
412
+ new rspack.SwcJsMinimizerRspackPlugin({
413
+ minimizerOptions: {
414
+ ecma: 5,
415
+ compress: {
416
+ pure_funcs: override.pure_funcs,
417
+ drop_debugger: true,
418
+ ecma: 5,
419
+ // Disabled because of an issue with Uglify breaking seemingly valid code:
420
+ // https://github.com/facebook/create-react-app/issues/2376
421
+ // Pending further investigation:
422
+ // https://github.com/mishoo/UglifyJS2/issues/2011
423
+ comparisons: false,
424
+ // Disabled because of an issue with Terser breaking valid code:
425
+ // https://github.com/facebook/create-react-app/issues/5250
426
+ // Pending further investigation:
427
+ // https://github.com/terser-js/terser/issues/120
428
+ inline: 2,
429
+ },
430
+ },
431
+ }),
432
+ ],
433
+ splitChunks: {
434
+ minChunks: 2,
435
+ },
436
+ },
437
+ performance: {
438
+ maxEntrypointSize: appConfig.single ? 1024 * 1024 : 30 * 1024,
439
+ maxAssetSize: 2 * 1024 * 1024,
440
+ },
441
+ }
442
+
443
+ return config
444
+ }
@@ -0,0 +1,83 @@
1
+ import setupMockServer from './plugins/mock-server/index.js'
2
+
3
+ function createProxy(context, target, origin) {
4
+ const url = new URL(origin || target)
5
+
6
+ return {
7
+ context: [context],
8
+ target: target,
9
+ changeOrigin: true,
10
+ onProxyReq: (proxyReq) => {
11
+ proxyReq.setHeader('host', url.host)
12
+ proxyReq.setHeader('origin', url.origin)
13
+ proxyReq.removeHeader('referer')
14
+ },
15
+ }
16
+ }
17
+
18
+ /**
19
+ * @param {*} args
20
+ * @param {import('../utils/defineConfig').Config} override
21
+ */
22
+ export default function getWebpackDevServerConfig(args, override) {
23
+ const host = process.env.HOST || '0.0.0.0'
24
+
25
+ /** @type {import('@rspack/dev-server').Configuration} */
26
+ const devServer = {
27
+ allowedHosts: 'all',
28
+ historyApiFallback: true,
29
+ port: args.port,
30
+ open: args.open,
31
+ host,
32
+ client: {
33
+ webSocketURL: 'auto://0.0.0.0:0/ws',
34
+ overlay: {
35
+ runtimeErrors: false,
36
+ errors: true,
37
+ warnings: false,
38
+ },
39
+ },
40
+ headers: {
41
+ 'Access-Control-Allow-Origin': '*',
42
+ 'Access-Control-Allow-Methods': '*',
43
+ 'Access-Control-Allow-Headers': '*',
44
+ },
45
+ setupMiddlewares: (middlewares, devServer) => {
46
+ if (override.mock !== false) {
47
+ setupMockServer(middlewares, devServer)
48
+ }
49
+
50
+ return middlewares
51
+ },
52
+ proxy: [
53
+ createProxy('/api', 'http://192.168.10.19:3339/qsxxwapdev', 'http://www.zhidianbao.cn'),
54
+ ],
55
+ compress: true,
56
+ }
57
+
58
+ if (override.proxy) {
59
+ if (Array.isArray(override.proxy)) {
60
+ devServer.proxy = [...override.proxy, ...devServer.proxy]
61
+ } else if (typeof override.proxy === 'object') {
62
+ const proxies = Object.entries(override.proxy).map(([context, target]) => {
63
+ return createProxy(context, target)
64
+ })
65
+ devServer.proxy = [...proxies, ...devServer.proxy]
66
+ } else {
67
+ throw new Error('proxy 必须是数组或对象')
68
+ }
69
+
70
+ // 清理重复的代理配置
71
+ const proxyMap = new Map()
72
+ devServer.proxy = devServer.proxy.filter((item) => {
73
+ const key = JSON.stringify([...item.context].sort())
74
+ if (!proxyMap.has(key)) {
75
+ proxyMap.set(key, true)
76
+ return true
77
+ }
78
+ return false
79
+ })
80
+ }
81
+
82
+ return devServer
83
+ }
package/src/deploy.js ADDED
@@ -0,0 +1,182 @@
1
+ import { sshSftp } from '@qse/ssh-sftp'
2
+ import path from 'path'
3
+ import paths from './config/paths.js'
4
+ import chalk from 'chalk'
5
+ import fs from 'fs-extra'
6
+ import changeDeployVersion from './utils/changeDeployVersion.js'
7
+ import ora from 'ora'
8
+ import appConfig from './utils/appConfig.js'
9
+ import { format } from 'prettier'
10
+
11
+ const appPkg = fs.readJsonSync(paths.package)
12
+
13
+ const baseConfig = {
14
+ localPath: 'dist',
15
+ ignore: [],
16
+ cleanRemoteFiles: false,
17
+ securityLock: false,
18
+ keepAlive: true,
19
+ noWarn: true,
20
+ }
21
+
22
+ async function normalDeploy(args) {
23
+ const resolve = (...pathSegments) => path.resolve(process.cwd(), ...pathSegments)
24
+
25
+ /**
26
+ * 生成路径
27
+ * @name remoteFile 远程文件
28
+ * @name tmpFile 本地文件
29
+ * @name tmpDir 本地临时文件夹 `tmpFile`存在的文件夹
30
+ * @name tmpBase 本地临时文件夹 到`__tmp__`层
31
+ * @param {string} remoteFilePath 远程文件相对`opts.remotePath`地址
32
+ */
33
+ function getLocalAndRemoteFilePath(remoteFilePath, opts) {
34
+ const splited = remoteFilePath.split('/')
35
+ const fileName = splited[splited.length - 1]
36
+ const tmpBase = resolve(opts.localPath, '__tmp__')
37
+ const tmpDir = resolve(opts.localPath, '__tmp__', ...splited.slice(0, -1))
38
+ const tmpFile = resolve(tmpDir, fileName)
39
+
40
+ const remoteFile = [opts.remotePath, remoteFilePath].join('/')
41
+
42
+ return { tmpDir, tmpFile, remoteFile, tmpBase }
43
+ }
44
+
45
+ function dateTime() {
46
+ let date = new Date()
47
+ date = new Date(date.getTime() - date.getTimezoneOffset() * 60000)
48
+ return date.toISOString().replace(/T/, ' ').replace(/\..+/, '')
49
+ }
50
+ function updateLogContent(content, info) {
51
+ const lines = content.trim().split('\n')
52
+ lines.push(`[${dateTime()}] ${JSON.stringify(info)}\n`)
53
+ return lines.slice(-50).join('\n')
54
+ }
55
+
56
+ async function upload(opts) {
57
+ // 上传dist文件
58
+ const { sftp, opts: fullOpts } = await sshSftp(opts)
59
+
60
+ const spinner = ora('自动更新 ver.js 版本配置').start()
61
+ // 指定远程需要修改的文件
62
+ const fileName = 'js/ver.js'
63
+ // 生成各种路径
64
+ const { remoteFile, tmpDir, tmpFile, tmpBase } = getLocalAndRemoteFilePath(fileName, fullOpts)
65
+
66
+ try {
67
+ // 创建本地临时文件夹,位置在 `opts.localPath` 下的 `__tmp__` 文件夹
68
+ fs.mkdirSync(tmpDir, { recursive: true })
69
+
70
+ const info = {
71
+ name: appPkg.name,
72
+ version: appPkg.version,
73
+ grayscale: appConfig.grayscale,
74
+ }
75
+
76
+ {
77
+ // 拉取远程文件到本地
78
+ await sftp.fastGet(remoteFile, tmpFile)
79
+
80
+ // 读取本地文件内容
81
+ let code = await fs.readFile(tmpFile, { encoding: 'utf-8' })
82
+ code = await changeDeployVersion(code, info)
83
+ code = await format(code, { parser: 'babel', printWidth: 120 })
84
+
85
+ await sftp.fastPut(tmpFile, remoteFile + '.bak')
86
+ await fs.writeFile(tmpFile, code)
87
+ // 将修改完的内容传回服务器
88
+ await sftp.fastPut(tmpFile, remoteFile)
89
+ }
90
+
91
+ {
92
+ const remoteLogFile = remoteFile + '.log'
93
+ const tmpLogFile = tmpFile + '.log'
94
+ let content = ''
95
+ try {
96
+ await sftp.fastGet(remoteLogFile, tmpLogFile)
97
+ content = await fs.readFile(tmpLogFile, 'utf-8')
98
+ } catch (error) {}
99
+ content = updateLogContent(content, info)
100
+ await fs.writeFile(tmpLogFile, content)
101
+ await sftp.fastPut(tmpLogFile, remoteLogFile)
102
+ }
103
+
104
+ spinner.succeed('已更新 ver.js 版本配置')
105
+ } catch (e) {
106
+ spinner.fail(`自动修改 ver.js 失败,请手动修改`)
107
+ console.log(chalk.bgRed(e.message))
108
+ } finally {
109
+ await sftp.end()
110
+ // 清除临时文件夹
111
+ fs.removeSync(tmpBase)
112
+ }
113
+ }
114
+
115
+ const presetConfig = {
116
+ s: { preset: { context: 'eduwebngv1', folder: 'userportal' } },
117
+ b: { preset: { context: 'eduwebngv1', folder: 'bureaupc' } },
118
+ d: { preset: { context: 'eduwebngv1', folder: 'documentshelves' } },
119
+ c: { preset: { context: 'eduwebngv1', folder: 'compositionshelves' } },
120
+ cd: {
121
+ preset: { server: '19' },
122
+ remotePath: '/erp/edumaven/dingcorrection-page-dev/compositionshelves',
123
+ },
124
+ }
125
+ const uploadSftpConfigs = []
126
+ if (args.b) {
127
+ uploadSftpConfigs.push(presetConfig.b)
128
+ }
129
+ if (args.s) {
130
+ uploadSftpConfigs.push(presetConfig.s)
131
+ }
132
+ if (args.d) {
133
+ uploadSftpConfigs.push(presetConfig.d)
134
+ }
135
+ if (args.c) {
136
+ uploadSftpConfigs.push(presetConfig.c)
137
+ }
138
+ if (args.cd) {
139
+ uploadSftpConfigs.push(presetConfig.cd)
140
+ }
141
+ if (uploadSftpConfigs.length === 0) {
142
+ console.log(
143
+ `
144
+ ${chalk.red('指定 deploy 部署范围')}
145
+ 执行 ${chalk.green('npx edu-scripts deploy -h')} 查看具体用法
146
+ `
147
+ )
148
+ process.exit()
149
+ }
150
+
151
+ const uploadConfig = { ...baseConfig, ignore: [...baseConfig.ignore, 'js/ver.js'] }
152
+ if (!appConfig.mainProject) {
153
+ uploadConfig.ignore = [...uploadConfig.ignore, '!(js|images)']
154
+ }
155
+
156
+ for (const config of uploadSftpConfigs) {
157
+ await upload({ ...uploadConfig, ...config })
158
+ }
159
+ }
160
+
161
+ async function singleDeploy() {
162
+ const config = fs.existsSync(paths.sshSftp)
163
+ ? fs.readJsonSync(paths.sshSftp)
164
+ : {
165
+ ...baseConfig,
166
+ preset: { context: 'qsxxwapdev' },
167
+ cleanRemoteFiles: true,
168
+ securityLock: true,
169
+ keepAlive: false,
170
+ noWarn: false,
171
+ }
172
+
173
+ sshSftp(config)
174
+ }
175
+
176
+ export default function deploy(args) {
177
+ if (appConfig.single) {
178
+ singleDeploy()
179
+ } else {
180
+ normalDeploy(args)
181
+ }
182
+ }