package-product-images.py 4.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. """Package reviewed front-end files only; record the observed production baseline."""
  2. import hashlib
  3. import json
  4. from pathlib import Path
  5. import urllib.request
  6. import zipfile
  7. ROOT = Path(__file__).resolve().parents[1]
  8. OUT = ROOT / 'artifacts/商品图片展示-20261106-r1'
  9. WORK = ROOT / 'work/product-media/frontend-package'
  10. DOCS = ROOT / 'docs/industry-product-images-20261106'
  11. FILES = [
  12. 'src/core/library.js', 'src/core/library-comparison.js',
  13. 'src/services/library-service.js', 'src/services/library-images.js', 'src/services/library-comparison.js',
  14. 'src/pages/view-library.js', 'src/pages/library-styles.js',
  15. 'src/pages/library-product-image.js', 'src/pages/library-product-compare.js',
  16. ]
  17. sha = lambda body: hashlib.sha256(body).hexdigest()
  18. normalized = lambda body: sha(body.replace(b'\r\n', b'\n'))
  19. def main():
  20. WORK.mkdir(parents=True, exist_ok=True)
  21. OUT.mkdir(parents=True, exist_ok=True)
  22. target = OUT / 'ksf-product-images-20261106-r1.zip'
  23. if target.exists():
  24. raise RuntimeError('RELEASE_ALREADY_EXISTS')
  25. observed = []
  26. for path in FILES + ['src/pages/library-product-detail.js']:
  27. req = urllib.request.Request('https://kangshifu.fmode.cn/' + path, headers={'Cache-Control': 'no-cache'})
  28. with urllib.request.urlopen(req, timeout=25) as response:
  29. before, mime = response.read(), response.headers.get('Content-Type', '')
  30. absent = 'text/html' in mime
  31. after = (ROOT / path).read_bytes()
  32. backup = WORK / 'production-before' / path
  33. backup.parent.mkdir(parents=True, exist_ok=True)
  34. backup.write_bytes(before)
  35. observed.append({'path': path, 'sha256': sha(after), 'normalizedSha256': normalized(after),
  36. 'observedProductionSha256': sha(before), 'priorNormalizedSha256': [] if absent else [normalized(before)],
  37. 'allowMissing': absent, 'dependencyOnly': path not in FILES,
  38. 'productionMatchesLocal': not absent and normalized(before) == normalized(after)})
  39. manifest = {'schema': 'ksf.product-images.frontend-release.v1', 'origin': 'https://kangshifu.fmode.cn',
  40. 'files': [row for row in observed if not row['dependencyOnly']],
  41. 'dependencies': [row for row in observed if row['dependencyOnly']],
  42. 'databaseImageCount': 4674, 'databasePublished': True, 'frontendPublished': False,
  43. 'instructions': ['Use the existing static-site publishing channel.',
  44. 'Compare the observed production baseline before replacing any file; stop on mismatch.',
  45. 'Resolve the product detail dependency with the main workspace owner; it is not included for replacement.',
  46. 'Upload new modules before importing pages; retain backups and verify the module graph and browser afterward.']}
  47. raw_manifest = (json.dumps(manifest, ensure_ascii=False, indent=2) + '\n').encode()
  48. (DOCS / 'frontend-release.json').write_bytes(raw_manifest)
  49. with zipfile.ZipFile(target, 'x', compression=zipfile.ZIP_DEFLATED) as archive:
  50. for path in FILES:
  51. archive.write(ROOT / path, 'payload/' + path)
  52. archive.writestr('deploy/frontend-release.json', raw_manifest)
  53. for name in ['README.md', 'database-verification.json', 'browser-acceptance.json', 'module-check.json', 'comparison-regression.json']:
  54. archive.write(DOCS / name, 'docs/' + name)
  55. for path in ['test/library-images.test.js', 'scripts/import-product-images.py', 'scripts/test_import_product_images.py', 'scripts/verify-product-images.py']:
  56. archive.write(ROOT / path, 'verification/' + path)
  57. receipt = {'ok': True, 'file': str(target.relative_to(ROOT)), 'bytes': target.stat().st_size,
  58. 'sha256': sha(target.read_bytes()), 'frontendFiles': len(FILES), 'frontendPublished': False,
  59. 'productionDependenciesRequiringIntegration': [r['path'] for r in observed if r['dependencyOnly'] and not r['productionMatchesLocal']]}
  60. (DOCS / 'package-receipt.json').write_text(json.dumps(receipt, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
  61. print(json.dumps(receipt, ensure_ascii=False))
  62. if __name__ == '__main__':
  63. main()