| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136 |
- """Prepare only the workshop board UI against the current public site, excluding Skill/manual/runtime."""
- from concurrent.futures import ThreadPoolExecutor
- import difflib
- import hashlib
- import json
- from pathlib import Path
- import posixpath
- import re
- from urllib.request import Request, urlopen
- from urllib.error import HTTPError
- ROOT = Path(__file__).resolve().parents[1]
- RELEASE = 'ksf-workshop-board-20261110-r1'
- OUT = ROOT / 'docs' / RELEASE
- PACK = OUT / 'package'
- WORK = ROOT / 'work' / RELEASE
- FILES = [
- 'src/core/kfs/build.js', 'src/core/library/reports.js', 'src/core/library/types.js',
- 'src/core/workshop-outputs.js', 'src/layout/kf-app.js', 'src/layout/kf-nav.js',
- 'src/pages/view-reports.js', 'src/pages/view-workshop-outputs.js', 'src/utils/router.js',
- ]
- def sha(raw):
- return hashlib.sha256(raw).hexdigest()
- def normal(raw):
- return raw.replace(b'\r\n', b'\n')
- def write_json(path, value):
- path.parent.mkdir(parents=True, exist_ok=True)
- path.write_text(json.dumps(value, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
- def capture(name):
- try:
- req = Request('https://kangshifu.fmode.cn/' + name, headers={'Cache-Control': 'no-cache'})
- with urlopen(req, timeout=40) as response:
- raw = response.read(3_000_001)
- assert len(raw) < 3_000_001
- content_type = response.headers.get('Content-Type', '')
- exists = name == 'index.html' or 'javascript' in content_type or 'text/css' in content_type
- if exists:
- target = WORK / 'baseline' / name
- target.parent.mkdir(parents=True, exist_ok=True)
- target.write_bytes(raw)
- return {'path': name, 'exists': exists, 'status': response.status, 'contentType': content_type,
- 'sha256': sha(raw) if exists else None,
- 'normalizedSha256': sha(normal(raw)) if exists else None}
- except HTTPError as e:
- assert e.code == 404, (name, e.code)
- return {'path': name, 'exists': False, 'status': 404, 'sha256': None, 'normalizedSha256': None}
- def dependencies(observed):
- visited, queue = set(), list(FILES)
- while queue:
- batch = sorted(set(queue) - visited)
- queue = []
- absent = [name for name in batch if name not in observed]
- with ThreadPoolExecutor(max_workers=8) as pool:
- for row in pool.map(capture, absent):
- observed[row['path']] = row
- for name in batch:
- visited.add(name)
- source = ROOT / name if name in FILES else WORK / 'baseline' / name
- assert source.exists(), 'MISSING_PRODUCTION_DEPENDENCY:' + name
- for relative in re.findall(r'(?:from\s*|import\s*)[\'\"]([.][^\'\"]+)[\'\"]', source.read_text('utf-8')):
- queue.append(posixpath.normpath(posixpath.join(posixpath.dirname(name), relative)))
- return visited - set(FILES)
- def main():
- assert not PACK.exists(), 'NEW_RELEASE_REQUIRED'
- with ThreadPoolExecutor(max_workers=8) as pool:
- rows = list(pool.map(capture, sorted(set(FILES) | {'index.html'})))
- observed = {r['path']: r for r in rows}
- dependencies_set = dependencies(observed)
- rows = [observed[name] for name in sorted(observed)]
- differences = [name for name in dependencies_set if not (ROOT / name).exists() or
- observed[name]['normalizedSha256'] != sha(normal((ROOT / name).read_bytes()))]
- write_json(WORK / 'baseline.json', {'files': rows, 'localDifferencesExcludedFromRelease': differences,
- 'dependencySource': 'existing-production-modules; not uncommitted unrelated changes'})
- patches, files = [], []
- for name in sorted(FILES):
- prior = observed[name]
- raw = normal((ROOT / name).read_bytes())
- old = normal((WORK / 'baseline' / name).read_bytes()) if prior['exists'] else b''
- dest = PACK / 'payload' / name
- dest.parent.mkdir(parents=True, exist_ok=True)
- dest.write_bytes(raw)
- files.append({'path': name, 'sha256': sha(raw), 'allowMissing': not prior['exists'],
- 'priorNormalizedSha256': [prior['normalizedSha256']] if prior['exists'] else [],
- 'observedProductionSha256': prior['sha256'], 'changed': old != raw})
- patches.extend(difflib.unified_diff(old.decode().splitlines(True), raw.decode().splitlines(True),
- fromfile='production/' + name, tofile=RELEASE + '/' + name))
- # Reuse the previously tested publisher, including 0644 writes and concurrent-change checks.
- publisher = (ROOT / 'artifacts/ksf-board-mobile-filters-20261108-r14/package/scripts/publish-industry-ui.mjs').read_text('utf-8')
- line = next(line for line in publisher.splitlines() if line.startswith('export const FILES='))
- publisher = publisher.replace(line, 'export const FILES=Object.freeze(' + json.dumps(sorted(FILES)) + ');')
- publisher = publisher.replace('ksf-board-mobile-filters-20261108-r14', RELEASE)
- target = PACK / 'scripts/publish-industry-ui.mjs'
- target.parent.mkdir(parents=True, exist_ok=True)
- target.write_text(publisher, encoding='utf-8', newline='\n')
- for name in ['rollback-ui.mjs', 'check-site-modes.mjs']:
- text = (ROOT / 'scripts/release-templates' / name).read_text('utf-8').replace('ksf-board-mobile-filters-20261108-r14', RELEASE)
- (PACK / 'scripts' / name).write_text(text, encoding='utf-8', newline='\n')
- write_json(PACK / 'deploy/industry-ui-files.json', {'version': 'ksf.industry-ui.publish.v1', 'release': RELEASE,
- 'homepageSha256': observed['index.html']['sha256'], 'files': files})
- write_json(PACK / 'evidence/production-baseline.json', {'files': rows})
- (PACK / 'evidence/ui.patch').write_text(''.join(patches), encoding='utf-8')
- for name in dependencies_set:
- dest = PACK / 'test-support/site' / name
- dest.parent.mkdir(parents=True, exist_ok=True)
- dest.write_bytes((WORK / 'baseline' / name).read_bytes())
- for name in FILES:
- dest = PACK / 'test-support/site' / name
- dest.parent.mkdir(parents=True, exist_ok=True)
- dest.write_bytes((PACK / 'payload' / name).read_bytes())
- test = (ROOT / 'test/workshop-outputs.test.js').read_text('utf-8').replace('../src/', '../test-support/site/src/')
- dest = PACK / 'test/workshop-outputs.test.js'
- dest.parent.mkdir(parents=True, exist_ok=True)
- dest.write_text(test, encoding='utf-8')
- write_json(PACK / 'package.json', {'type': 'module', 'private': True})
- write_json(OUT / 'preparation.json', {'release': RELEASE, 'staticFiles': len(files),
- 'dependencyFiles': len(dependencies_set), 'newFiles': sum(f['allowMissing'] for f in files),
- 'changedFiles': sum(f['changed'] for f in files), 'homepageSha256': observed['index.html']['sha256'],
- 'mainSkillIncluded': False, 'manualIncluded': False, 'productionDeployed': False})
- print(json.dumps({'ok': True, 'files': len(files), 'dependenciesVerified': len(dependencies_set),
- 'changedFiles': sum(f['changed'] for f in files), 'package': str(PACK)}, ensure_ascii=False))
- if __name__ == '__main__':
- main()
|