Animate equipment artwork and publish optimized motion release, agent=codex
This commit is contained in:
+58
-3
@@ -118,7 +118,7 @@ def audit():
|
||||
sftp.close(); ssh.close()
|
||||
|
||||
|
||||
def put_atomic(sftp, local, remote, replace=False):
|
||||
def put_atomic(sftp, local, remote, replace=False, backup_dir=REMOTE_BACKUP):
|
||||
mkdirs(sftp, posixpath.dirname(remote))
|
||||
temp = remote + '.codex-upload-' + uuid.uuid4().hex[:10]
|
||||
sftp.put(str(local), temp, confirm=True)
|
||||
@@ -128,7 +128,9 @@ def put_atomic(sftp, local, remote, replace=False):
|
||||
try:
|
||||
sftp.posix_rename(temp, remote)
|
||||
except OSError:
|
||||
shifted = REMOTE_BACKUP + '/' + posixpath.basename(remote) + '.shifted'
|
||||
relative = posixpath.relpath(remote, WEBROOT)
|
||||
shifted = backup_dir + '/' + relative + '.shifted'
|
||||
mkdirs(sftp, posixpath.dirname(shifted))
|
||||
sftp.rename(remote, shifted)
|
||||
try:
|
||||
sftp.rename(temp, remote)
|
||||
@@ -198,6 +200,58 @@ def verify():
|
||||
sftp.close(); ssh.close()
|
||||
|
||||
|
||||
def update_motion():
|
||||
"""Publish the illustration motion release only after an exact live baseline check."""
|
||||
previous = ROOT.parent / 'site-servaki-online-git' / 'public'
|
||||
if not previous.is_dir():
|
||||
raise RuntimeError('Previous Gitea release is required for the baseline check')
|
||||
old = {p.relative_to(previous).as_posix(): digest(p.read_bytes())
|
||||
for p in previous.rglob('*') if p.is_file()}
|
||||
new = manifest()
|
||||
removed = set(old) - set(new)
|
||||
changed = {rel for rel in new if new[rel] != old.get(rel)}
|
||||
html = {p.relative_to(ROOT / 'public').as_posix()
|
||||
for p in (ROOT / 'public').rglob('*.html')}
|
||||
allowed = html | {'assets/editorial/motion.css', 'assets/editorial/motion.js'}
|
||||
if removed or changed != allowed:
|
||||
raise RuntimeError(f'Unexpected release scope: removed={sorted(removed)}, changed={sorted(changed ^ allowed)}')
|
||||
local_backup = ROOT / 'release-backups' / '2026-09-28-before-motion'
|
||||
remote_backup = SITE + '/backup-2026-09-28-before-motion'
|
||||
ssh, sftp = connect()
|
||||
try:
|
||||
for rel, expected in old.items():
|
||||
target = WEBROOT + '/' + rel
|
||||
if not exists(sftp, target) or digest(remote_bytes(sftp, target)) != expected:
|
||||
raise RuntimeError('Published baseline changed; stopped before writing: ' + rel)
|
||||
for rel in changed - set(old):
|
||||
if exists(sftp, WEBROOT + '/' + rel):
|
||||
raise RuntimeError('New motion asset already exists: ' + rel)
|
||||
mkdirs(sftp, remote_backup)
|
||||
for rel in sorted(changed & set(old)):
|
||||
data = remote_bytes(sftp, WEBROOT + '/' + rel)
|
||||
destination = local_backup / rel
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
if destination.exists() and destination.read_bytes() != data:
|
||||
raise RuntimeError('Local backup conflict: ' + rel)
|
||||
destination.write_bytes(data)
|
||||
remote_copy = remote_backup + '/' + rel
|
||||
if exists(sftp, remote_copy):
|
||||
if digest(remote_bytes(sftp, remote_copy)) != old[rel]:
|
||||
raise RuntimeError('Remote backup conflict: ' + rel)
|
||||
else:
|
||||
put_atomic(sftp, destination, remote_copy)
|
||||
for rel in sorted(changed, key=lambda item: (item == 'index.html', not item.startswith('assets/'), item)):
|
||||
put_atomic(sftp, ROOT / 'public' / rel, WEBROOT + '/' + rel,
|
||||
replace=rel in old, backup_dir=remote_backup)
|
||||
for rel in changed:
|
||||
if digest(remote_bytes(sftp, WEBROOT + '/' + rel)) != new[rel]:
|
||||
raise RuntimeError('Published motion file failed checksum: ' + rel)
|
||||
print('Published animation update; changed files:', len(changed),
|
||||
'verified previous files:', len(old), 'backup:', remote_backup)
|
||||
finally:
|
||||
sftp.close(); ssh.close()
|
||||
|
||||
|
||||
def reprice():
|
||||
"""Replace only the published homepage after verifying the six price tokens."""
|
||||
before = json.loads((BACKUP / 'deployed.json').read_text(encoding='utf-8'))
|
||||
@@ -249,4 +303,5 @@ def reprice():
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
{'audit': audit, 'deploy': deploy, 'verify': verify, 'reprice': reprice}[sys.argv[1]]()
|
||||
{'audit': audit, 'deploy': deploy, 'verify': verify,
|
||||
'reprice': reprice, 'update-motion': update_motion}[sys.argv[1]]()
|
||||
|
||||
Reference in New Issue
Block a user