49 lines
3.4 KiB
Python
49 lines
3.4 KiB
Python
"""Build production sources in QA, retaining the approved package and active bundle."""
|
|
from pathlib import Path
|
|
from datetime import datetime, timezone
|
|
import hashlib, json, os, subprocess
|
|
ROOT=Path(__file__).resolve().parent.parent
|
|
PRIVATE=ROOT/'storage/app/private/investmoz-v2/portal-audit'
|
|
read=lambda p:json.loads(Path(p).read_text(encoding='utf-8-sig'))
|
|
sha=lambda p:hashlib.sha256(Path(p).read_bytes()).hexdigest()
|
|
plan=read(PRIVATE/'portal-stability-latest.json');source=Path(plan['source'])
|
|
active={p.relative_to(ROOT/'public/build').as_posix():sha(p) for p in (ROOT/'public/build').rglob('*') if p.is_file()}
|
|
# Route generation bootstraps the same guarded QA configuration and local class map.
|
|
(source/'artisan').write_text('''#!/usr/bin/env php
|
|
<?php
|
|
if (($argv[1] ?? '') !== 'wayfinder:generate') throw new RuntimeException('Only isolated route generation is allowed');
|
|
define('LARAVEL_START', microtime(true));
|
|
$app = require __DIR__.'/tools/portal-stability-runtime.php';
|
|
$status = $app->handleCommand(new Symfony\\Component\\Console\\Input\\ArgvInput);
|
|
exit($status);
|
|
''',encoding='utf-8')
|
|
# The shared bootstrap must return the app when invoked through the guarded artisan.
|
|
runtime=source/'tools/portal-stability-runtime.php'
|
|
text=runtime.read_text(encoding='utf-8')
|
|
text=text.replace("if (PHP_SAPI !== 'cli') { return $app; }", "if (PHP_SAPI !== 'cli' || basename($_SERVER['SCRIPT_FILENAME'] ?? '') === 'artisan') { return $app; }")
|
|
runtime.write_text(text,encoding='utf-8')
|
|
inputs={p.relative_to(source).as_posix():sha(p) for folder in ['app','config','routes','resources','database/migrations','bootstrap']
|
|
for p in (source/folder).rglob('*') if p.is_file() and 'cache' not in p.relative_to(source).parts}
|
|
for name in ['composer.json','composer.lock','package.json','package-lock.json','tsconfig.json','vite.config.ts']:
|
|
inputs[name]=sha(source/name)
|
|
env=dict(os.environ);env.pop('INVESTMOZ_QA_BUILD',None)
|
|
env['PATH']='C:/laragon/bin/php/php-8.5.11-Win32-vs17-x64'+os.pathsep+env['PATH']
|
|
env['VITE_APP_NAME']='InvestMoz'
|
|
log=PRIVATE/'portal-stability-production-build.log'
|
|
with log.open('w',encoding='utf-8') as stream:
|
|
result=subprocess.run(['npm.cmd','run','build'],cwd=source,env=env,stdout=stream,stderr=subprocess.STDOUT,shell=True)
|
|
assert result.returncode==0,'Build failed; inspect the private build log'
|
|
assets={p.relative_to(source/'public/build').as_posix():sha(p) for p in (source/'public/build').rglob('*') if p.is_file()}
|
|
assert 'manifest.json' in assets
|
|
manifest=read(source/'public/build/manifest.json')
|
|
for item in manifest.values():
|
|
for name in ['file','css','assets']:
|
|
values=item.get(name,[]);values=[values] if isinstance(values,str) else values
|
|
assert all((source/'public/build'/p).is_file() for p in values)
|
|
assert active=={p.relative_to(ROOT/'public/build').as_posix():sha(p) for p in (ROOT/'public/build').rglob('*') if p.is_file()}
|
|
proof={'result':'passed','at':datetime.now(timezone.utc).isoformat(),'mode':'production','inputs':inputs,
|
|
'assets':assets,'build_manifest_sha256':assets['manifest.json'],'active_assets_preserved':True,
|
|
'active_writes':0,'source':str(source),'input_hash':hashlib.sha256(json.dumps(inputs,sort_keys=True,separators=(',',':')).encode()).hexdigest()}
|
|
(PRIVATE/'portal-stability-production.json').write_text(json.dumps(proof,indent=2),encoding='utf-8')
|
|
print(json.dumps({k:proof[k] for k in ['result','mode','build_manifest_sha256','input_hash','active_assets_preserved','active_writes']}))
|