build-mp-weixin.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import { spawn } from 'child_process'
  2. import dotenv from 'dotenv'
  3. import { existsSync, readFileSync, writeFileSync } from 'fs'
  4. import { applyEdits, modify } from 'jsonc-parser'
  5. import path from 'path'
  6. type TargetEnvironment = 'luoxin' | 'pre' | 'prod'
  7. interface EnvironmentConfig {
  8. appid: string
  9. baseApi: string
  10. envFile: string
  11. label: string
  12. viteMode: string
  13. viteModeValue: string
  14. }
  15. const ROOT_DIR = path.resolve(__dirname, '..')
  16. const MANIFEST_PATH = path.resolve(ROOT_DIR, 'src/manifest.json')
  17. const UNI_CLI_PATH = path.resolve(ROOT_DIR, 'node_modules/@dcloudio/vite-plugin-uni/bin/uni.js')
  18. const BUILD_OUTPUT_DIR = path.resolve(ROOT_DIR, 'dist/build/mp-weixin')
  19. const ORIGINAL_APPID = 'wxd03398e1bff2b241'
  20. const LUOXIN_APPID = 'wx2a5a7f417981f250'
  21. const ENVIRONMENTS: Record<TargetEnvironment, EnvironmentConfig> = {
  22. luoxin: {
  23. appid: LUOXIN_APPID,
  24. baseApi: 'https://luoxin.yaoyi.net',
  25. envFile: '.env.production_LUOXIN',
  26. label: '罗欣',
  27. viteMode: 'production_LUOXIN',
  28. viteModeValue: 'production_luoxin',
  29. },
  30. pre: {
  31. appid: ORIGINAL_APPID,
  32. baseApi: 'https://pre1.yaoyi.net',
  33. envFile: '.env.pre',
  34. label: '预发布',
  35. viteMode: 'pre',
  36. viteModeValue: 'production_pre',
  37. },
  38. prod: {
  39. appid: ORIGINAL_APPID,
  40. baseApi: 'https://api.yaoyi.net',
  41. envFile: '.env.production',
  42. label: '正式',
  43. viteMode: 'production',
  44. viteModeValue: 'production',
  45. },
  46. }
  47. const TARGETS: TargetEnvironment[] = ['luoxin', 'pre', 'prod']
  48. const fail = (message: string): never => {
  49. throw new Error(message)
  50. }
  51. const parseTarget = (value: string | undefined): TargetEnvironment => {
  52. const target = TARGETS.find((item) => item === value)
  53. if (!target) {
  54. throw new Error(`环境必须是:${TARGETS.join(' / ')}`)
  55. }
  56. return target
  57. }
  58. const loadEnvironment = (target: TargetEnvironment): NodeJS.ProcessEnv => {
  59. const config = ENVIRONMENTS[target]
  60. const envPath = path.resolve(ROOT_DIR, config.envFile)
  61. if (!existsSync(envPath)) {
  62. fail(`环境文件不存在:${config.envFile}`)
  63. }
  64. const parsedEnv = dotenv.parse(readFileSync(envPath, 'utf8'))
  65. const expectedValues: Record<string, string> = {
  66. VITE_BASE_API: config.baseApi,
  67. VITE_MODE: config.viteModeValue,
  68. VITE_WX_APPID: config.appid,
  69. }
  70. for (const [key, expectedValue] of Object.entries(expectedValues)) {
  71. if (parsedEnv[key] !== expectedValue) {
  72. fail(
  73. `${config.envFile} 中的 ${key} 配置错误:期望 ${expectedValue},实际 ${parsedEnv[key] || '未配置'}`
  74. )
  75. }
  76. }
  77. return {
  78. ...process.env,
  79. ...parsedEnv,
  80. }
  81. }
  82. const injectManifestAppid = (manifestSource: string, appid: string): string => {
  83. const edits = modify(manifestSource, ['mp-weixin', 'appid'], appid, {
  84. formattingOptions: {
  85. insertSpaces: true,
  86. tabSize: 2,
  87. },
  88. })
  89. return applyEdits(manifestSource, edits)
  90. }
  91. const runUniBuild = async (
  92. target: TargetEnvironment,
  93. processEnv: NodeJS.ProcessEnv
  94. ): Promise<void> => {
  95. const config = ENVIRONMENTS[target]
  96. await new Promise<void>((resolve, reject) => {
  97. const childProcess = spawn(
  98. process.execPath,
  99. [UNI_CLI_PATH, 'build', '-p', 'mp-weixin', '--mode', config.viteMode],
  100. {
  101. cwd: ROOT_DIR,
  102. env: processEnv,
  103. stdio: 'inherit',
  104. }
  105. )
  106. childProcess.once('error', reject)
  107. childProcess.once('exit', (code, signal) => {
  108. if (code === 0) {
  109. resolve()
  110. return
  111. }
  112. reject(
  113. new Error(
  114. signal ? `uni-app 进程被信号 ${signal} 终止` : `uni-app 进程退出,错误码:${code}`
  115. )
  116. )
  117. })
  118. })
  119. }
  120. const validateBuildOutput = (expectedAppid: string): void => {
  121. const projectConfigPath = path.resolve(BUILD_OUTPUT_DIR, 'project.config.json')
  122. if (!existsSync(projectConfigPath)) {
  123. fail(`构建产物不存在:${projectConfigPath}`)
  124. }
  125. const projectConfig = JSON.parse(readFileSync(projectConfigPath, 'utf8')) as {
  126. appid?: string
  127. }
  128. if (projectConfig.appid !== expectedAppid) {
  129. fail(`构建产物 AppID 校验失败:期望 ${expectedAppid},实际 ${projectConfig.appid || '未配置'}`)
  130. }
  131. }
  132. const main = async (): Promise<void> => {
  133. const target = parseTarget(process.argv[2])
  134. const config = ENVIRONMENTS[target]
  135. const processEnv = loadEnvironment(target)
  136. const originalManifest = readFileSync(MANIFEST_PATH, 'utf8')
  137. console.log(`\n[环境] ${config.label} (${target})`)
  138. console.log(`[接口] ${config.baseApi}`)
  139. console.log(`[AppID] ${config.appid}`)
  140. writeFileSync(MANIFEST_PATH, injectManifestAppid(originalManifest, config.appid), 'utf8')
  141. try {
  142. await runUniBuild(target, processEnv)
  143. validateBuildOutput(config.appid)
  144. console.log(`[构建] 已生成 ${BUILD_OUTPUT_DIR}`)
  145. } finally {
  146. writeFileSync(MANIFEST_PATH, originalManifest, 'utf8')
  147. }
  148. }
  149. void main().catch((error: unknown) => {
  150. const message = error instanceof Error ? error.message : String(error)
  151. console.error(`\n[失败] ${message}`)
  152. process.exitCode = 1
  153. })