Skip to content

Commit 57c56d4

Browse files
committed
Release 1.0.0-beta.30 (from d85811b)
verify-static: a long attribute value or a link out of the site no longer crashes the identity proof.
1 parent a8b4b8b commit 57c56d4

7 files changed

Lines changed: 41 additions & 10 deletions

File tree

‎.claude-plugin/marketplace.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"source": "./",
1515
"displayName": "html2wp — HTML to WordPress",
1616
"description": "BETA. Convert a static HTML site into a standalone WordPress block theme, verified against the original at three viewport widths — and reviewed by you before handover. The conversion runs through a verified Astro 5 build, so the Astro project is a real output too — compared pixel by pixel against the source before anything WordPress-shaped is generated.",
17-
"version": "1.0.0-beta.29",
17+
"version": "1.0.0-beta.30",
1818
"author": {
1919
"name": "BELNEM s.r.o.",
2020
"email": "hello@html2wp.dev",

‎.claude-plugin/plugin.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "html2wp",
33
"displayName": "html2wp — HTML to WordPress",
44
"description": "BETA. Convert a static HTML site — or anything that builds to one (Lovable, Bolt, v0, Next export) — into a standalone WordPress block theme: verified pixel by pixel, pages, blog, forms, menus, SEO and redirects, with no plugin required and verification gates at every stage. Every conversion ends in your own page-by-page review; the gates catch most fidelity drift, not all of it.",
5-
"version": "1.0.0-beta.29",
5+
"version": "1.0.0-beta.30",
66
"author": {
77
"name": "BELNEM s.r.o.",
88
"email": "hello@html2wp.dev",

‎.codex-plugin/plugin.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "html2wp",
3-
"version": "1.0.0-beta.29",
3+
"version": "1.0.0-beta.30",
44
"description": "Convert static HTML or buildable web projects into verified, editable WordPress block themes.",
55
"author": {
66
"name": "BELNEM s.r.o.",

‎VERSION‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.0.0-beta.29
1+
1.0.0-beta.30

‎skills/html2wp/VERSION‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.0.0-beta.29
1+
1.0.0-beta.30

‎skills/html2wp/assets/scripts/test-verify-static-identity.py‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,31 @@ def test_a_path_only_a_script_names_counts(self):
103103
self.assertNotIn('identicalByHash', report['pages']['index.html'])
104104
self.assertIn('diffRatio', report['pages']['index.html']['desktop'])
105105

106+
def test_an_attribute_value_longer_than_a_file_name_is_no_file(self):
107+
# Every whole attribute value is a candidate path. An SVG path or a
108+
# style-like data-* value over 255 bytes is a name no file system
109+
# holds: a 404 on both sides alike, not a crash of the whole gate.
110+
d = 'M0,0' + 'L1.25,2.5' * 60
111+
offsets = '; '.join(f'--offset-{side}: 24px' for side in ('top', 'right', 'bottom', 'left') * 6)
112+
extra = f'<svg width="10" height="10"><path d="{d}"/></svg><section data-offsets="{offsets};"></section>'
113+
for tree in (self.orig, self.dist):
114+
(tree / 'index.html').write_text(PAGE.format(t='Home', link='about.html', extra=extra))
115+
report, run = self.gate()
116+
self.assertTrue(report['passed'], run.stdout + run.stderr)
117+
self.assertEqual(self.proven(report), ['about.html', 'index.html'])
118+
119+
def test_a_file_linked_out_of_the_tree_disables_the_proof(self):
120+
# The gate's server refuses a link out of the site, so nothing is
121+
# known about its bytes: every page is rasterised, nothing crashes.
122+
outside = Path(self.temp.name) / 'outside.css'
123+
outside.write_text(CSS)
124+
for tree in (self.orig, self.dist):
125+
(tree / 'css/linked.css').symlink_to(outside)
126+
report, run = self.gate()
127+
self.assertEqual(self.proven(report), [], run.stdout + run.stderr)
128+
self.assertIn('css/linked.css', report['identity']['disabled'])
129+
self.assertTrue(report['passed'])
130+
106131
def test_an_original_remote_run_is_never_proven(self):
107132
(self.out.parent / 'remote.json').write_text(json.dumps({'localized': [{'url': 'https://img.example/a.png', 'finalUrl': 'https://img.example/a.png'}]}))
108133
report, _ = self.gate('--original-remote', str(self.out.parent / 'remote.json'))

‎skills/html2wp/assets/scripts/verify-static.py‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -508,15 +508,18 @@ def _resolve(candidate, base):
508508

509509
def _file(root, rel, cache):
510510
"""(sha256, text or None) of `rel` as the gate's server would hand it to
511-
the browser (a directory is its index.html; a refused path is a 404), or
512-
None. Only a file the scan reads again keeps its text."""
511+
the browser (a directory is its index.html; a refused path, or a name no
512+
file system can hold, is a 404), or None. Only a file the scan reads
513+
again keeps its text."""
513514
key = (root, rel)
514515
if key not in cache:
515516
target = root / rel
516-
if target.is_dir():
517-
target = target / "index.html"
518517
found = None
519518
try:
519+
# Candidates include whole attribute values (an SVG path, a long
520+
# alt): stat() of such a name raises ENAMETOOLONG, not False.
521+
if target.is_dir():
522+
target = target / "index.html"
520523
if (target.is_file() and target.resolve().is_relative_to(root)
521524
and not refuse_request_path("/" + target.relative_to(root).as_posix(), root)):
522525
data = target.read_bytes()
@@ -582,8 +585,11 @@ def servable(root):
582585
return {rel for rel in (p.relative_to(root).as_posix() for p in root.rglob("*") if p.is_file())
583586
if not refuse_request_path("/" + rel, root)}
584587
orig, dist = servable(ORIG), servable(DIST)
588+
def digest(root, rel):
589+
found = _file(root, rel, cache) # None: unreadable, or a link out of the tree
590+
return found[0] if found else None
585591
differing = sorted(rel for rel in orig & dist if Path(rel).suffix.lower() not in (".html", ".htm")
586-
and _file(ORIG, rel, cache)[0] != _file(DIST, rel, cache)[0])
592+
and (digest(ORIG, rel) is None or digest(ORIG, rel) != digest(DIST, rel)))
587593
summary["distOnlyFiles"] = len(dist - orig)
588594
if differing:
589595
summary["disabled"] = "files differ between the two sides: " + ", ".join(differing[:5])

0 commit comments

Comments
 (0)