You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
walkthrough(filenames): rename part files to title-word slugs
Each part now emits <filename>.html instead of walkthrough-part-<n>.html
(e.g. anatomy.html, building.html, parsing.html, validation.html,
conversion.html, ecosystems.html, comparison.html, security.html).
Public URLs on GH Pages are now short and speakable —
/socket-packageurl-js/anatomy.html instead of
/socket-packageurl-js/walkthrough-part-1.html.
The filename lives in walkthrough.json per part. A validator at the
top of the generate pipeline enforces that every part has a filename,
matches [a-z]+, and is unique — failing with all violations reported
in one pass per the ERROR MESSAGES doctrine in CLAUDE.md.
applyBasePath() and routeToFile() both consult the same part-id →
filename map, so prod builds rewrite /<slug>/part/<n> hrefs to the
flat file, and the local dev server translates the same URL shape
back to the renamed file on disk.
`${configPath}: part ${p.id} ("${p.title}") is missing "filename". Add a single-word lowercase filename (e.g. "anatomy") to this part — one per part is required to route /<slug>/part/${p.id} at publish time.`,
135
+
)
136
+
continue
137
+
}
138
+
if(!/^[a-z]+$/.test(p.filename)){
139
+
errors.push(
140
+
`${configPath}: part ${p.id} ("${p.title}") has filename "${p.filename}" but filenames must match [a-z]+ (lowercase ASCII letters only — no digits, hyphens, or dots). Rewrite "${p.filename}" as a single lowercase word.`,
141
+
)
142
+
continue
143
+
}
144
+
constprior=seen.get(p.filename)
145
+
if(prior!==undefined){
146
+
errors.push(
147
+
`${configPath}: filename "${p.filename}" is used by both part ${prior} and part ${p.id} ("${p.title}"). Filenames must be unique — rename one of the two to a distinct single-word lowercase filename.`,
148
+
)
149
+
continue
150
+
}
151
+
seen.set(p.filename,p.id)
152
+
}
153
+
if(errors.length>0){
154
+
thrownewError(
155
+
`walkthrough.json has ${errors.length} invalid part filename(s):\n - ${errors.join('\n - ')}`,
156
+
)
157
+
}
158
+
constmap=newMap<number,string>()
159
+
for(constpofparts){
160
+
map.set(p.id,p.filename!)
161
+
}
162
+
returnmap
163
+
}
164
+
107
165
/**
108
166
* Rewrite a generated HTML file for hosting under `basePath`. Two
109
167
* categories of URL get prefixed:
@@ -112,24 +170,36 @@ function normalizeBasePath(raw: string): string {
`walkthrough/${entry}: no filename configured for part ${n}. Add "filename" to part ${n} in walkthrough.json (e.g. "anatomy") — the validator should have caught this, so meander may have rendered a part that isn't in walkthrough.json.`,
"objective": "Per-ecosystem normalize/validate/encode rules across all 41 supported package ecosystems.",
71
77
"keywords": [
72
78
"ecosystem",
@@ -127,6 +133,7 @@
127
133
{
128
134
"id": 7,
129
135
"title": "Comparison, Matching & Existence",
136
+
"filename": "comparison",
130
137
"objective": "Compare + equal + match PURLs with wildcard patterns (ReDoS-protected); registry existence checks (npmExists, pypiExists, and friends).",
131
138
"keywords": [
132
139
"compare",
@@ -146,6 +153,7 @@
146
153
{
147
154
"id": 8,
148
155
"title": "Security Primitives & VERS",
156
+
"filename": "security",
149
157
"objective": "Injection-character detection for safe PURL handling (containsInjectionCharacters, findInjectionCharCode), safe object freezing, and VERS — the pre-standard version-range specifier slated for Ecma submission in late 2026.",
0 commit comments