mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-17 08:36:25 +03:00
Generate /skills index + 21 auto-rendered skill detail pages
Ships the first new sub-page section. Every user-invocable skill now has
its own page at /skills/{id}, with the canonical SKILL.md body rendered
via marked. The index at /skills lists all 21 skills grouped by category.
Editorial wrappers are opt-in: if content/site/skills/{id}.md exists, it
renders above the canonical body (with a "The skill itself" divider).
All 21 pages currently ship with the auto-rendered body only; hand-written
wrappers land in the next few commits.
- scripts/lib/sub-pages-data.js: builds the data model. Reuses
readSourceFiles() from lib/utils.js for skill content; parses the
ANTIPATTERNS array out of src/detect-antipatterns.mjs; reads optional
editorial wrappers from content/site/skills/*.md; validates that every
user-invocable skill has a category entry (build fails loudly if not).
- scripts/build-sub-pages.js: orchestrator. Writes generated HTML into
public/skills/*.html (gitignored). Called from both scripts/build.js
(before buildStaticSite) and server/index.js (at module load) so dev
and prod share the same generation code path.
- scripts/lib/render-page.js: new assetDepth parameter so generated
pages one level deep under public/ use relative paths (../favicon.svg,
../css/sub-pages.css) that Bun's HTML loader can resolve on disk.
- scripts/build.js: pass generated files into Bun.build entrypoints;
post-process to flatten build/public/* → build/* (Bun preserves the
public/ prefix when entrypoints span multiple depths).
- server/index.js: generateSubPages() runs at module load; new routes
/skills, /skills/:id, /anti-patterns, /tutorials, /tutorials/:slug
serve the pre-generated files via Bun.file().
- public/css/sub-pages.css: adds sub-page layout shell, skills index
grouped-list styling, skill detail header/meta chips/divider, collapsed
<details> reference sections, and a .prose block for rendered markdown
with editorial typography, code blocks, and inline code.
Verified: bun run build produces 26 HTML files (4 hand-authored + 22
generated), all flat under build/. Dev server returns 200 on /skills,
/skills/polish, /skills/impeccable, /skills/critique. Tests pass.
This commit is contained in:
+27
-5
@@ -20,6 +20,7 @@ import { fileURLToPath } from 'url';
|
||||
import { readSourceFiles, readPatterns } from './lib/utils.js';
|
||||
import { createTransformer, PROVIDERS } from './lib/transformers/index.js';
|
||||
import { createAllZips } from './lib/zip.js';
|
||||
import { generateSubPages } from './build-sub-pages.js';
|
||||
|
||||
/**
|
||||
* Generate authoritative counts from source data and write to public/js/generated/counts.js.
|
||||
@@ -218,16 +219,17 @@ const DIST_DIR = path.join(ROOT_DIR, 'dist');
|
||||
* Build static site using Bun's HTML bundler
|
||||
* Bun's HTML loader resolves <link rel="stylesheet"> and inlines CSS @imports.
|
||||
*/
|
||||
async function buildStaticSite() {
|
||||
async function buildStaticSite(extraEntrypoints = []) {
|
||||
const entrypoints = [
|
||||
path.join(ROOT_DIR, 'public', 'index.html'),
|
||||
path.join(ROOT_DIR, 'public', 'cheatsheet.html'),
|
||||
path.join(ROOT_DIR, 'public', 'gallery.html'),
|
||||
path.join(ROOT_DIR, 'public', 'privacy.html'),
|
||||
...extraEntrypoints,
|
||||
];
|
||||
const outdir = path.join(ROOT_DIR, 'build');
|
||||
|
||||
console.log('📦 Building static site with Bun...');
|
||||
console.log(`📦 Building static site with Bun (${entrypoints.length} HTML entries)...`);
|
||||
|
||||
try {
|
||||
const result = await Bun.build({
|
||||
@@ -250,11 +252,26 @@ async function buildStaticSite() {
|
||||
|
||||
// Calculate total size
|
||||
const totalSize = result.outputs.reduce((sum, o) => sum + o.size, 0);
|
||||
const htmlFiles = result.outputs.filter(o => o.path.endsWith('.html'));
|
||||
const jsFiles = result.outputs.filter(o => o.path.endsWith('.js'));
|
||||
const cssFiles = result.outputs.filter(o => o.path.endsWith('.css'));
|
||||
|
||||
// When entrypoints span multiple depths under public/ (e.g. public/index.html
|
||||
// + public/skills/polish.html), Bun's HTML loader preserves the full public/
|
||||
// prefix in the output tree. Flatten build/public/* up to build/*.
|
||||
const nestedPublic = path.join(outdir, 'public');
|
||||
if (fs.existsSync(nestedPublic)) {
|
||||
for (const entry of fs.readdirSync(nestedPublic, { withFileTypes: true })) {
|
||||
const from = path.join(nestedPublic, entry.name);
|
||||
const to = path.join(outdir, entry.name);
|
||||
if (fs.existsSync(to)) fs.rmSync(to, { recursive: true, force: true });
|
||||
fs.renameSync(from, to);
|
||||
}
|
||||
fs.rmdirSync(nestedPublic);
|
||||
}
|
||||
|
||||
console.log(`✓ Static site built to ./build/`);
|
||||
console.log(` HTML: 1 file`);
|
||||
console.log(` HTML: ${htmlFiles.length} file(s)`);
|
||||
console.log(` JS: ${jsFiles.length} file(s) (${(jsFiles.reduce((s, f) => s + f.size, 0) / 1024).toFixed(1)} KB)`);
|
||||
console.log(` CSS: ${cssFiles.length} file(s) (${(cssFiles.reduce((s, f) => s + f.size, 0) / 1024).toFixed(1)} KB)`);
|
||||
console.log(` Total: ${(totalSize / 1024).toFixed(1)} KB\n`);
|
||||
@@ -453,8 +470,13 @@ function generateCFConfig(buildDir) {
|
||||
async function build() {
|
||||
console.log('🔨 Building cross-provider design skills...\n');
|
||||
|
||||
// Bundle HTML, JS, and CSS with Bun
|
||||
await buildStaticSite();
|
||||
// Pre-generate sub-pages (skills, anti-patterns, tutorials) from source
|
||||
console.log('📝 Generating sub-pages...');
|
||||
const { files: subPageFiles } = await generateSubPages(ROOT_DIR);
|
||||
console.log(`✓ Generated ${subPageFiles.length} sub-page(s)\n`);
|
||||
|
||||
// Bundle HTML, JS, and CSS with Bun (including generated sub-pages)
|
||||
await buildStaticSite(subPageFiles);
|
||||
|
||||
// Copy root-level static assets that need stable (unhashed) URLs
|
||||
const staticAssets = ['og-image.jpg', 'robots.txt', 'sitemap.xml', 'favicon.svg', 'apple-touch-icon.png'];
|
||||
|
||||
Reference in New Issue
Block a user