from pathlib import Path
import os,shutil,json,subprocess,hashlib
base=Path('/workspace/foodsafe-release-20261020-c4070071');src=base/'src';kit=base/'kit';payload=kit/'payload'
payload.mkdir(exist_ok=False)
for rel in ['node_modules','services/gateway-service/node_modules','services/gateway-service/dist','libs']:
 target=payload/rel;target.parent.mkdir(parents=True,exist_ok=True);subprocess.run(['cp','-a',str(src/rel),str(target)],check=True)
service=payload/'services/gateway-service'
d=json.loads((src/'services/gateway-service/package.json').read_text());d['dependencies']['qrcode']='1.5.4';(service/'package.json').write_text(json.dumps(d,indent=2)+'\n')
for app in ['web-school','web-regulator','screen','mobile-device']:
 target=payload/'apps'/app/'dist';target.parent.mkdir(parents=True,exist_ok=True);subprocess.run(['cp','-a',str(src/'apps'/app/'dist'),str(target)],check=True)
 lib=target/'libs';lib.mkdir(exist_ok=True)
 for mod,name in [('hls.js','hls.min.js'),('flv.js','flv.min.js')]:
  resolved=subprocess.check_output(['/opt/foodsafe-node/bin/node','-e',f'console.log(require.resolve("{mod}/dist/{name}"))'],cwd=src/'apps/web-school',text=True).strip()
  shutil.copyfile(resolved,lib/name)
 (target/'runtime-config.js').write_text('window.FOODSAFE_RUNTIME={};window.SCHOOL_INTEGRATION_BASE="/integration";window.DEWO_MQTT_HOST="/api";\n')
# Replace the build-only absolute qrcode link with self-contained npm dependency tree.
q=payload/'node_modules/qrcode';q.unlink()
subprocess.run(['cp','-a',str(base/'extra/node_modules')+'/.',str(payload/'node_modules')+'/'],check=True)
for path in list(payload.rglob('.bin')):
 if path.is_symlink():path.unlink()
 elif path.is_dir():shutil.rmtree(path)
removed=[]
for f in list(payload.rglob('*')):
 if f.is_symlink() and not f.exists():
  if str(f.relative_to(payload)) not in ['node_modules/.pnpm/node_modules/stream-tester','node_modules/.pnpm/node_modules/@foodsafe/mobile-emergency']:raise RuntimeError('unexpected broken link '+str(f))
  removed.append(str(f.relative_to(payload)));f.unlink()
print('Removed unused workspace links',removed)
for root,dirs,files in os.walk(payload,followlinks=False):
 for name in files:
  p=Path(root)/name
  if name.startswith('.env') or name in ['.npmrc','.pnpmfile.cjs','.DS_Store','.modules.yaml']:
   if p.is_file() or p.is_symlink():p.unlink()
 for name in dirs+files:
  p=Path(root)/name
  if p.is_symlink():
   if os.path.isabs(os.readlink(p)):raise RuntimeError('absolute symlink '+str(p.relative_to(payload)))
   if not p.exists():raise RuntimeError('broken symlink '+str(p.relative_to(payload)))
   if payload.resolve() not in p.resolve().parents and p.resolve()!=payload.resolve():raise RuntimeError('external symlink')
# Public payload contains no private env; runtime user must be able to read it.
subprocess.run(['chmod','-R','a+rX',str(payload)],check=True)
# Include source SQL as a candidate only, never auto execute.
(kit/'sql-review').mkdir(exist_ok=True);shutil.copyfile(src/'db/upgrade/zywxb.sql',kit/'sql-review/zywxb-mysql8-foodsafe2.sql')
(kit/'sql-review/README.txt').write_text('此SQL为standard_wxb历史foodsafe2结构增量升级候选，不是达梦SQL。仅在备份恢复的MySQL8副本核对后使用；第一套独立比对，禁止自动执行。')
(kit/'provenance').mkdir(exist_ok=True)
shutil.copyfile(base/'extra/package-lock.json',kit/'provenance/qrcode-package-lock.json')
shutil.copyfile(src/'pnpm-lock.yaml',kit/'provenance/source-pnpm-lock.yaml')
patch=subprocess.check_output(['git','diff','--','apps','services/gateway-service/src'],cwd=src)
(kit/'provenance/release-overlay.patch').write_bytes(patch)
shutil.copyfile(src/'apps/web-school/src/utils/offline-release.ts',kit/'provenance/offline-release.ts')
manifest={'source_branch':'standard_wxb','source_commit':'c40700715f8c23eb33eb855beec44b28dd19ec5b','os':'Kylin V10 x86_64','glibc_build':'2.28','node_build':'20.19.5','pnpm_build':'10.21.0','dmdb':'1.0.52452','qrcode':'1.5.4','entry':'services/gateway-service/dist/services/gateway-service/src/main.js','cwd':'project root','release_overlay':True,'screen_build_base':'/','screen_base_fixed_in_source':True,'screen_build_command':'pnpm run build','database_migration_not_executed':True}
(kit/'RELEASE.json').write_text(json.dumps(manifest,indent=2)+'\n')
with (kit/'PAYLOAD.sha256').open('w') as out:
 for p in sorted(payload.rglob('*')):
  if p.is_file() and not p.is_symlink():
   h=hashlib.sha256()
   with p.open('rb') as f:
    for b in iter(lambda:f.read(1024*1024),b''):h.update(b)
   out.write(h.hexdigest()+'  '+str(p.relative_to(kit))+'\n')
print('ASSEMBLED',subprocess.check_output(['du','-sh',str(kit)],text=True).strip())
