62 lines
8.7 KiB
JavaScript
62 lines
8.7 KiB
JavaScript
'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}))
|