ufutx_love_mp/tools/local-acceptance/build.cjs

62 lines
8.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict'
const fs=require('fs'), path=require('path'), cp=require('child_process')
const repo=path.resolve(__dirname,'../..'), workspace=path.dirname(repo)
const apiMode=process.argv.includes('--api')
const appidArg=process.argv.find(arg=>arg.startsWith('--appid='))
const localAppid=appidArg && appidArg.slice(8)
if(appidArg && !/^wx[0-9a-f]{16}$/.test(localAppid)) throw Error('Invalid local AppID')
const root=path.join(workspace,'.local-acceptance'), build=path.join(root,apiMode?'mp-local-api-build':'mp-mock-build')
const modules=path.join(root,'mp-build','node_modules')
if(!fs.existsSync(path.join(modules,'@wepy/cli/bin/wepy.js'))) throw Error('Run the isolated dependency installation first')
fs.mkdirSync(build,{recursive:true})
// Only remove this tool's generated directories, never source or another dependency tree.
for(const name of ['src','weapp']) {const target=path.resolve(build,name);if(!target.startsWith(build+path.sep))throw Error('Unsafe target');fs.rmSync(target,{recursive:true,force:true})}
fs.cpSync(path.join(repo,'src'),path.join(build,'src'),{recursive:true})
for(const name of ['package.json','package-lock.json','wepy.config.js'])fs.copyFileSync(path.join(repo,name),path.join(build,name))
if(!fs.existsSync(path.join(build,'node_modules')))fs.symlinkSync(modules,path.join(build,'node_modules'),'junction')
function walk(dir,fn){for(const entry of fs.readdirSync(dir,{withFileTypes:true})){const p=path.join(dir,entry.name);if(entry.isDirectory())walk(p,fn);else fn(p)}}
// Static media is local. No remote pictures, fonts, embedded web pages or SDK destinations remain.
const urlPattern=/(?:https?|wss?):\/\/[^\s"'`<>\\)]+/gi
let replaced=0
walk(path.join(build,'src'),p=>{if(!/\.(js|wpy|wxss|less|json|wxml)$/.test(p))return;let text=fs.readFileSync(p,'utf8');text=text.replace(urlPattern,()=>{replaced++;return '/images/tabBar/my.png'});text=text.replace(/<web-view\b[^>]*>(?:<\/web-view>)?/g,'<view>本地模拟:外部网页已禁用</view>');fs.writeFileSync(p,text)})
// Replace just the two host assignments in the copied config, with a hard assertion on source shape.
const config=path.join(build,'src/config.js');let text=fs.readFileSync(config,'utf8');let hosts=0
text=text.replace(/^(host|hostGo) = '[^']*'.*$/gm,(_,name)=>{hosts++;return name+" = 'http://127.0.0.1:18765/go/api'"})
if(hosts!==2)throw Error('Expected exactly two active API host assignments')
fs.writeFileSync(config,text)
// The legacy error branch throws after rejecting. In the acceptance copy, unsupported mocks
// should show the existing modal and reject normally, not crash the simulator callback.
const requests=path.join(build,'src/mixins/https.js')
fs.writeFileSync(requests,fs.readFileSync(requests,'utf8').replace(/throw errorMessage/g,'return'))
fs.copyFileSync(path.join(__dirname,'im.js'),path.join(build,'src/utils/im.js'))
fs.writeFileSync(path.join(build,'src/mp_ecard_sdk/main.js'),"export function initEid() {}\nexport function startEid() { wx.showModal({title:'本地模拟',content:'实名认证未执行,认证状态不会改变',showCancel:false}) }\n")
const result=cp.spawnSync(process.execPath,[path.join(modules,'@wepy/cli/bin/wepy.js'),'build','--no-cache'],{cwd:build,env:{...process.env,NODE_ENV:'production'},encoding:'utf8',maxBuffer:20*1024*1024})
fs.mkdirSync(path.join(root,'logs'),{recursive:true});fs.writeFileSync(path.join(root,'logs/mp-mock-build.log'),(result.stdout||'')+(result.stderr||''))
if(result.status!==0)throw Error('WePY build failed: '+(result.error?result.error.code:result.status)+'; inspect local log (redact before sharing)')
const output=path.join(build,'weapp')
const fixTemplates=require('./fix-template-expressions.cjs')
walk(output,p=>{if(p.endsWith('.wxml'))fs.writeFileSync(p,fixTemplates(fs.readFileSync(p,'utf8')))})
fs.copyFileSync(path.join(__dirname,'runtime.js'),path.join(output,'acceptance-runtime.js'))
const appFile=path.join(output,'app.js');fs.writeFileSync(appFile,"require('./acceptance-runtime').install(wx"+(apiMode?", {apiOrigin:'http://127.0.0.1:18766'}":"")+");\n"+fs.readFileSync(appFile,'utf8'))
const appPath=path.join(output,'app.json');const app=JSON.parse(fs.readFileSync(appPath));app.pages.unshift('local-acceptance/index');app.window.navigationBarTitleText='本地模拟验收';fs.writeFileSync(appPath,JSON.stringify(app,null,2))
const panel=path.join(output,'local-acceptance');fs.mkdirSync(panel,{recursive:true})
fs.writeFileSync(path.join(panel,'index.json'),JSON.stringify({navigationBarTitleText:'本地模拟验收'}))
fs.writeFileSync(path.join(panel,'index.wxml'),'<view style="padding:40rpx"><view style="font-size:40rpx;font-weight:bold">福恋 · 本地模拟验收</view><view style="margin:30rpx 0">合成数据,不访问后端或第三方服务。远程图片已替换,外部字体可能与正式版不同。支付、认证、上传不属于本轮可验收范围。</view><button bindtap="enter">进入首页</button><button bindtap="membership">会员购买关闭验收</button><button bindtap="edit">编辑测试资料</button><button bindtap="publish">发布测试动态</button><button bindtap="reset">重置合成数据</button><button bindtap="failure">切换请求失败场景:{{failed}}</button><view>重新编译回到此入口。测试注册验证码:000000。不要输入真实个人资料。</view></view>')
fs.writeFileSync(path.join(panel,'index.js'),`Page({data:{failed:false},onShow(){this.setData({failed:wx.__loveAcceptance.snapshot().failure})},enter(){wx.switchTab({url:'/pages/tabBar/home'})},membership(){wx.navigateTo({url:'/pages/users/upgradeVIP'})},edit(){wx.navigateTo({url:'/pages/users/selfTextarea?text=自我描述'})},publish(){wx.navigateTo({url:'/pages/dynamic/issue'})},reset(){wx.__loveAcceptance.reset();wx.reLaunch({url:'/local-acceptance/index'})},failure(){wx.__loveAcceptance.scenario('failure',!this.data.failed);this.onShow()}})`)
// Permanent visual marker on every generated page, including subpackages.
walk(output,p=>{if(p.endsWith('.wxml')){let markup=fs.readFileSync(p,'utf8').replace(/open-type="[^"]*"/g,'disabled="true" data-local-disabled="true"');fs.writeFileSync(p,markup+'\n<view style="position:fixed;right:8rpx;top:8rpx;z-index:2147483647;background:#fff1b8;color:#613400;padding:4rpx 12rpx;font-size:20rpx;pointer-events:none">本地模拟 · 合成数据</view>\n')}})
const project=JSON.parse(fs.readFileSync(path.join(repo,'project.config.json')))
if(!process.argv.includes('--source-appid')) project.appid='touristappid';if(localAppid)project.appid=localAppid;project.projectname=apiMode?'福恋-本地API验收':'福恋-本地模拟验收';project.setting={...project.setting,urlCheck:!apiMode};delete project.packOptions
fs.writeFileSync(path.join(build,'project.config.json'),JSON.stringify(project,null,2))
const testPackage=JSON.parse(fs.readFileSync(path.join(build,'package.json')))
testPackage.private=true
testPackage.scripts={build:'node ../../ufutx_love_mp/tools/local-acceptance/build.cjs'+(apiMode?' --api':'')+(process.argv.includes('--source-appid')?' --source-appid':'')+(localAppid?' --appid='+localAppid:''),test:'node ../../ufutx_love_mp/tools/local-acceptance/test.cjs'}
fs.writeFileSync(path.join(build,'package.json'),JSON.stringify(testPackage,null,2))
// Built vendor code can contain dormant SDK URLs. Scrub them too; runtime denies all native transport.
walk(output,p=>{if(!/\.(js|wxml|wxss|json)$/.test(p)||p.endsWith('acceptance-runtime.js'))return;let t=fs.readFileSync(p,'utf8');t=t.replace(urlPattern,url=>url.startsWith('http://127.0.0.1:18765/')||url==='http://127.0.0.1:18766'?url:'/images/tabBar/my.png');fs.writeFileSync(p,t)})
if(apiMode){const p=path.join(panel,'index.wxml');fs.writeFileSync(p,fs.readFileSync(p,'utf8').replace('福恋 · 本地模拟验收','福恋 · 本地 API 验收').replace('合成数据,不访问后端或第三方服务。','登录、资料读取/保存与 DMA 走本机真实 Go API;其他流程仍为 mock。').replace('重置合成数据','重置 mock 数据(不重置数据库)'))}
fs.writeFileSync(path.join(build,'ACCEPTANCE-ONLY.txt'),'LOCAL MOCK ONLY. Never upload or publish this project. Generated from the current working tree. See repository tools/local-acceptance/README.txt.\n')
const crypto=require('crypto')
fs.writeFileSync(path.join(build,'acceptance-manifest.json'),JSON.stringify({builtAt:new Date().toISOString(),node:process.version,mode:apiMode?'local-api-with-mock-fallbacks':'offline-in-process-mock',source:repo,privateGoModulesUsed:apiMode,lockSha256:crypto.createHash('sha256').update(fs.readFileSync(path.join(repo,'package-lock.json'))).digest('hex'),runtimeSha256:crypto.createHash('sha256').update(fs.readFileSync(path.join(__dirname,'runtime.js'))).digest('hex')},null,2))
console.log(JSON.stringify({build,staticUrlsReplaced:replaced,mode:apiMode?'local-api-with-mock-fallbacks':'offline-in-process-mock',privateGoModulesUsed:apiMode}))