sektamarketingcommitse06f344
Analyzing diff...
.oxygit/ci.yml+5 −0
index 73ddebc..a06bdb5 100644 @@ -6,5 +6,10 @@ publish_pages: script: - mkdir -p public - 'cp "Курс для беременных 2026/Маркетинговые материалы/recepty---25a9d15c-9cda-432f-9444-d94255eb206e.html" public/index.html' + - 'cp "Курс для беременных 2026/Маркетинговые материалы/"recipe-*.png public/' + - python3 scripts/validate_static.py public --manifest static-manifest.json + artifacts: + paths: [static-manifest.json] + expire_in: never pages: path: public
scripts/validate_static.py+55 −0
new file mode 100644 index 0000000..260846a @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +"""Validate a static bundle without network access; emit a reproducible byte manifest.""" +import argparse, hashlib, json, re, sys +from html.parser import HTMLParser +from pathlib import Path +from urllib.parse import unquote, urlsplit + +class References(HTMLParser): + def __init__(self): + super().__init__(); self.refs=[] + def handle_starttag(self, tag, attrs): + a=dict(attrs) + for k in ('src','poster'): + if a.get(k): self.refs.append(a[k]) + if tag=='link' and a.get('href'): self.refs.append(a['href']) + if a.get('srcset') and not a['srcset'].startswith('data:'): + self.refs.extend(x.strip().split()[0] for x in a['srcset'].split(',') if x.strip()) + if a.get('style'): self.refs.extend(css_refs(a['style'])) + +def css_refs(s): + return re.findall(r'url\(\s*[\"\']?([^\"\')]+)',s)+re.findall(r'@import\s+[\"\']([^\"\']+)',s) + +def validate(root): + root=root.resolve(); errors=[]; files=[] + for p in sorted(root.rglob('*')): + if p.is_symlink(): errors.append(f'symlink: {p.relative_to(root)}'); continue + if not p.is_file(): continue + rel=p.relative_to(root).as_posix(); data=p.read_bytes() + if p.suffix.lower()=='.png' and not data.startswith(b'\x89PNG\r\n\x1a\n'): + errors.append(f'not PNG: {rel}') + if p.suffix.lower() in ('.js','.css','.png','.jpg','.jpeg','.webp','.pdf') and data.lstrip().lower().startswith((b'<!doctype html',b'<html')): + errors.append(f'HTML masquerading as asset: {rel}') + refs=[] + if p.suffix.lower() in ('.html','.htm'): + parser=References(); txt=data.decode('utf-8'); parser.feed(txt); refs=parser.refs+css_refs(txt) + elif p.suffix.lower()=='.css': refs=css_refs(data.decode('utf-8')) + for ref in refs: + u=urlsplit(ref.strip()) + if u.scheme or u.netloc or not u.path: continue + # Root-relative routes belong to the serving origin, not this bundle. + if u.path.startswith('/'): errors.append(f'root-relative resource needs explicit mapping: {rel}: {ref}'); continue + target=(p.parent/unquote(u.path)).resolve() + if not target.is_relative_to(root): errors.append(f'path escapes bundle: {rel}: {ref}') + elif not target.is_file(): errors.append(f'missing resource: {rel}: {ref}') + files.append({'path':rel,'bytes':len(data),'sha256':hashlib.sha256(data).hexdigest()}) + if not any(x['path'].endswith(('.html','.htm')) for x in files): errors.append('no HTML entry') + return files, sorted(set(errors)) + +if __name__=='__main__': + p=argparse.ArgumentParser(); p.add_argument('root',type=Path); p.add_argument('--manifest',type=Path); a=p.parse_args() + files, errors=validate(a.root) + if errors: + print('\n'.join(errors),file=sys.stderr); sys.exit(1) + if a.manifest: a.manifest.write_text(json.dumps({'schema':1,'files':files},ensure_ascii=False,indent=2)+'\n') + print(f'Validated {len(files)} files')
diff --git "a/\320\232\321\203\321\200\321\201 \320\264\320\273\321\217 \320\261\320\265\321\200\320\265\320\274\320\265\320\275\320\275\321\213\321\205 2026/\320\234\320\260\321\200\320\272\320\265\321\202\320\270\320\275\320\263\320\276\320\262\321\213\320\265 \320\274\320\260\321\202\320\265\321\200\320\270\320\260\320\273\321\213/recipe-01-omelette.png" "b/\320\232\321\203\321\200\321\201 \320\264\320\273\321\217 \320\261\320\265\321\200\320\265\320\274\320\265\320\275\320\275\321\213\321\205 2026/\320\234\320\260\321\200\320\272\320\265\321\202\320\270\320\275\320\263\320\276\320\262\321\213\320\265 \320\274\320\260\321\202\320\265\321\200\320\270\320\260\320\273\321\213/recipe-01-omelette.png"+0 −0
new file mode 100644 index 0000000..f7fe0e2 Binary files /dev/null and "b/\320\232\321\203\321\200\321\201 \320\264\320\273\321\217 \320\261\320\265\321\200\320\265\320\274\320\265\320\275\320\275\321\213\321\205 2026/\320\234\320\260\321\200\320\272\320\265\321\202\320\270\320\275\320\263\320\276\320\262\321\213\320\265 \320\274\320\260\321\202\320\265\321\200\320\270\320\260\320\273\321\213/recipe-01-omelette.png" differ
diff --git "a/\320\232\321\203\321\200\321\201 \320\264\320\273\321\217 \320\261\320\265\321\200\320\265\320\274\320\265\320\275\320\275\321\213\321\205 2026/\320\234\320\260\321\200\320\272\320\265\321\202\320\270\320\275\320\263\320\276\320\262\321\213\320\265 \320\274\320\260\321\202\320\265\321\200\320\270\320\260\320\273\321\213/recipe-02-turkey.png" "b/\320\232\321\203\321\200\321\201 \320\264\320\273\321\217 \320\261\320\265\321\200\320\265\320\274\320\265\320\275\320\275\321\213\321\205 2026/\320\234\320\260\321\200\320\272\320\265\321\202\320\270\320\275\320\263\320\276\320\262\321\213\320\265 \320\274\320\260\321\202\320\265\321\200\320\270\320\260\320\273\321\213/recipe-02-turkey.png"+0 −0
new file mode 100644 index 0000000..dfbcf2a Binary files /dev/null and "b/\320\232\321\203\321\200\321\201 \320\264\320\273\321\217 \320\261\320\265\321\200\320\265\320\274\320\265\320\275\320\275\321\213\321\205 2026/\320\234\320\260\321\200\320\272\320\265\321\202\320\270\320\275\320\263\320\276\320\262\321\213\320\265 \320\274\320\260\321\202\320\265\321\200\320\270\320\260\320\273\321\213/recipe-02-turkey.png" differ
diff --git "a/\320\232\321\203\321\200\321\201 \320\264\320\273\321\217 \320\261\320\265\321\200\320\265\320\274\320\265\320\275\320\275\321\213\321\205 2026/\320\234\320\260\321\200\320\272\320\265\321\202\320\270\320\275\320\263\320\276\320\262\321\213\320\265 \320\274\320\260\321\202\320\265\321\200\320\270\320\260\320\273\321\213/recipe-03-salmon.png" "b/\320\232\321\203\321\200\321\201 \320\264\320\273\321\217 \320\261\320\265\321\200\320\265\320\274\320\265\320\275\320\275\321\213\321\205 2026/\320\234\320\260\321\200\320\272\320\265\321\202\320\270\320\275\320\263\320\276\320\262\321\213\320\265 \320\274\320\260\321\202\320\265\321\200\320\270\320\260\320\273\321\213/recipe-03-salmon.png"+0 −0
new file mode 100644 index 0000000..d5a6a15 Binary files /dev/null and "b/\320\232\321\203\321\200\321\201 \320\264\320\273\321\217 \320\261\320\265\321\200\320\265\320\274\320\265\320\275\320\275\321\213\321\205 2026/\320\234\320\260\321\200\320\272\320\265\321\202\320\270\320\275\320\263\320\276\320\262\321\213\320\265 \320\274\320\260\321\202\320\265\321\200\320\270\320\260\320\273\321\213/recipe-03-salmon.png" differ
diff --git "a/\320\232\321\203\321\200\321\201 \320\264\320\273\321\217 \320\261\320\265\321\200\320\265\320\274\320\265\320\275\320\275\321\213\321\205 2026/\320\234\320\260\321\200\320\272\320\265\321\202\320\270\320\275\320\263\320\276\320\262\321\213\320\265 \320\274\320\260\321\202\320\265\321\200\320\270\320\260\320\273\321\213/recipe-04-cottage.png" "b/\320\232\321\203\321\200\321\201 \320\264\320\273\321\217 \320\261\320\265\321\200\320\265\320\274\320\265\320\275\320\275\321\213\321\205 2026/\320\234\320\260\321\200\320\272\320\265\321\202\320\270\320\275\320\263\320\276\320\262\321\213\320\265 \320\274\320\260\321\202\320\265\321\200\320\270\320\260\320\273\321\213/recipe-04-cottage.png"+0 −0
new file mode 100644 index 0000000..8ddf60b Binary files /dev/null and "b/\320\232\321\203\321\200\321\201 \320\264\320\273\321\217 \320\261\320\265\321\200\320\265\320\274\320\265\320\275\320\275\321\213\321\205 2026/\320\234\320\260\321\200\320\272\320\265\321\202\320\270\320\275\320\263\320\276\320\262\321\213\320\265 \320\274\320\260\321\202\320\265\321\200\320\270\320\260\320\273\321\213/recipe-04-cottage.png" differ
diff --git "a/\320\232\321\203\321\200\321\201 \320\264\320\273\321\217 \320\261\320\265\321\200\320\265\320\274\320\265\320\275\320\275\321\213\321\205 2026/\320\234\320\260\321\200\320\272\320\265\321\202\320\270\320\275\320\263\320\276\320\262\321\213\320\265 \320\274\320\260\321\202\320\265\321\200\320\270\320\260\320\273\321\213/recipe-05-hummus.png" "b/\320\232\321\203\321\200\321\201 \320\264\320\273\321\217 \320\261\320\265\321\200\320\265\320\274\320\265\320\275\320\275\321\213\321\205 2026/\320\234\320\260\321\200\320\272\320\265\321\202\320\270\320\275\320\263\320\276\320\262\321\213\320\265 \320\274\320\260\321\202\320\265\321\200\320\270\320\260\320\273\321\213/recipe-05-hummus.png"+0 −0
new file mode 100644 index 0000000..d8f1a59 Binary files /dev/null and "b/\320\232\321\203\321\200\321\201 \320\264\320\273\321\217 \320\261\320\265\321\200\320\265\320\274\320\265\320\275\320\275\321\213\321\205 2026/\320\234\320\260\321\200\320\272\320\265\321\202\320\270\320\275\320\263\320\276\320\262\321\213\320\265 \320\274\320\260\321\202\320\265\321\200\320\270\320\260\320\273\321\213/recipe-05-hummus.png" differ
diff --git "a/\320\232\321\203\321\200\321\201 \320\264\320\273\321\217 \320\261\320\265\321\200\320\265\320\274\320\265\320\275\320\275\321\213\321\205 2026/\320\234\320\260\321\200\320\272\320\265\321\202\320\270\320\275\320\263\320\276\320\262\321\213\320\265 \320\274\320\260\321\202\320\265\321\200\320\270\320\260\320\273\321\213/recipe-06-toast.png" "b/\320\232\321\203\321\200\321\201 \320\264\320\273\321\217 \320\261\320\265\321\200\320\265\320\274\320\265\320\275\320\275\321\213\321\205 2026/\320\234\320\260\321\200\320\272\320\265\321\202\320\270\320\275\320\263\320\276\320\262\321\213\320\265 \320\274\320\260\321\202\320\265\321\200\320\270\320\260\320\273\321\213/recipe-06-toast.png"+0 −0
new file mode 100644 index 0000000..3ea6fdc Binary files /dev/null and "b/\320\232\321\203\321\200\321\201 \320\264\320\273\321\217 \320\261\320\265\321\200\320\265\320\274\320\265\320\275\320\275\321\213\321\205 2026/\320\234\320\260\321\200\320\272\320\265\321\202\320\270\320\275\320\263\320\276\320\262\321\213\320\265 \320\274\320\260\321\202\320\265\321\200\320\270\320\260\320\273\321\213/recipe-06-toast.png" differ