@ibalzam/codejitsu-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +67 -0
- package/LICENSE +21 -0
- package/MIGRATIONS/README.md +30 -0
- package/README.md +35 -0
- package/checklist/bin/run.mjs +189 -0
- package/checklist/core.md +55 -0
- package/modules/blog/CLAUDE.md +87 -0
- package/modules/blog/checklist.md +36 -0
- package/modules/blog/src/components/index.ts +1 -0
- package/modules/blog/src/index.ts +201 -0
- package/modules/blog/templates/content/_sample-post.md +18 -0
- package/modules/blog/templates/lib/blog.ts +17 -0
- package/modules/blog/templates/pages/blog/[...slug].astro +53 -0
- package/modules/blog/templates/pages/blog/category/[category].astro +40 -0
- package/modules/blog/templates/pages/blog/index.astro +30 -0
- package/modules/blog/templates/pages/blog/tag/[tag].astro +35 -0
- package/modules/deploy/CLAUDE.md +58 -0
- package/modules/deploy/checklist.md +9 -0
- package/modules/deploy/templates/daily-deploy.yml +29 -0
- package/modules/deploy/templates/wrangler.toml +2 -0
- package/modules/images/CLAUDE.md +77 -0
- package/modules/images/bin/optimize.mjs +44 -0
- package/modules/images/checklist.md +23 -0
- package/modules/images/src/index.ts +21 -0
- package/modules/images/src/optimize.mjs +113 -0
- package/modules/images/templates/codejitsu-images.config.mjs +18 -0
- package/modules/llms/CLAUDE.md +57 -0
- package/modules/llms/bin/generate.mjs +35 -0
- package/modules/llms/checklist.md +10 -0
- package/modules/llms/src/generate.mjs +179 -0
- package/modules/llms/templates/codejitsu-llms.config.mjs +39 -0
- package/modules/seo/CLAUDE.md +95 -0
- package/modules/seo/checklist.md +30 -0
- package/modules/seo/src/index.ts +2 -0
- package/modules/seo/src/schema.ts +203 -0
- package/modules/seo/src/sitemap.ts +109 -0
- package/modules/seo/templates/Head.astro +53 -0
- package/modules/seo/templates/robots.txt +5 -0
- package/package.json +73 -0
- package/src/index.ts +1 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Master instructions — @ibalzam/codejitsu-core
|
|
2
|
+
|
|
3
|
+
This is the shared core for all Codejitsu sites. When a Codejitsu site depends on this package and the user invokes a module by name (e.g. **"implement codejitsu/core/blog"**, **"add codejitsu/core/seo"**), this file is your starting point.
|
|
4
|
+
|
|
5
|
+
## How to act on a module request
|
|
6
|
+
|
|
7
|
+
1. Open `node_modules/@ibalzam/codejitsu-core/modules/<name>/CLAUDE.md` — it tells you what to do for that module specifically.
|
|
8
|
+
2. Import code from the matching subpath (e.g. `@ibalzam/codejitsu-core/blog`). Do **not** copy-paste the source into the site.
|
|
9
|
+
3. If the module has a `templates/` directory, copy those files into the site at the locations the module's CLAUDE.md specifies. Templates are starting points — adapt them for the site's content.
|
|
10
|
+
4. After the change, run the module's `checklist.md` mentally, plus `checklist/core.md` (sitewide).
|
|
11
|
+
|
|
12
|
+
## Principles that apply to every Codejitsu site
|
|
13
|
+
|
|
14
|
+
These are non-negotiable unless the user explicitly opts out:
|
|
15
|
+
|
|
16
|
+
### Stack
|
|
17
|
+
- **Astro** (latest stable). Pure static output (`output: 'static'`).
|
|
18
|
+
- **Tailwind v4** via `@tailwindcss/vite`. Theme via CSS variables on `:root`, not hardcoded color palettes.
|
|
19
|
+
- **TypeScript** everywhere except config files where Astro/Vite expects `.mjs`.
|
|
20
|
+
- **React** integration only if the site needs interactive client islands (Framer, charts). Otherwise pure Astro.
|
|
21
|
+
|
|
22
|
+
### Deploy
|
|
23
|
+
- **Cloudflare Pages**, static deploy. `wrangler.toml` at site root.
|
|
24
|
+
- `npm run build && npx wrangler pages deploy dist` is the deploy command (or git-integration on Pages).
|
|
25
|
+
- Daily GH Action (`.github/workflows/daily-deploy.yml`) pings a Cloudflare deploy hook to publish scheduled content. See `modules/deploy/`.
|
|
26
|
+
|
|
27
|
+
### URLs + routing
|
|
28
|
+
- `trailingSlash: 'always'` in Astro config. Every internal link ends with `/`.
|
|
29
|
+
- Canonical URLs are absolute and trailing-slashed.
|
|
30
|
+
|
|
31
|
+
### Images
|
|
32
|
+
- Source images in `public/images/` (or `src/assets/` for Astro-processed). Originals can be PNG/JPG.
|
|
33
|
+
- Every shipped image must be available as WebP. The Astro sharp service handles `<Image>` references automatically (`image.defaults: { quality: 82, format: 'webp' }`). For images referenced by URL in HTML/CSS, run the pre-pass: `npx codejitsu-optimize-images`.
|
|
34
|
+
- No raw PNGs referenced from production HTML when a WebP equivalent exists.
|
|
35
|
+
|
|
36
|
+
### SEO (must be on every page)
|
|
37
|
+
- `<title>` and `<meta name="description">` set per page.
|
|
38
|
+
- Canonical URL `<link rel="canonical">`.
|
|
39
|
+
- OG meta (`og:title`, `og:description`, `og:image`, `og:url`, `og:type`).
|
|
40
|
+
- Twitter card meta.
|
|
41
|
+
- JSON-LD schema.org appropriate to the page type (Organization on home, LocalBusiness if applicable, BlogPosting on blog posts, FAQPage if FAQs present). Use builders from `@ibalzam/codejitsu-core/seo/schema`.
|
|
42
|
+
- `sitemap.xml` generated via `@astrojs/sitemap` with helpers from `@ibalzam/codejitsu-core/seo/sitemap`.
|
|
43
|
+
- `robots.txt` at site root.
|
|
44
|
+
- `/llms.txt` + `/llms-full.txt` generated via `npx codejitsu-llms` in prebuild.
|
|
45
|
+
|
|
46
|
+
### Content
|
|
47
|
+
- Blog posts as Markdown in `content/blog/` with frontmatter (see `modules/blog/CLAUDE.md`).
|
|
48
|
+
- Future-dated posts are hidden from public pages and sitemap but kept addressable for OG meta scrapers.
|
|
49
|
+
- All copy in English unless the site uses the i18n module (workzen-only currently).
|
|
50
|
+
|
|
51
|
+
### What NOT to do
|
|
52
|
+
- Don't reinvent modules that exist here. If you're tempted to write a blog loader, image optimizer, sitemap config, schema builder, or daily-deploy workflow, **import or copy from this package instead**.
|
|
53
|
+
- Don't hardcode brand colors in component files — always via Tailwind theme tokens / CSS variables.
|
|
54
|
+
- Don't add a runtime database, server-rendered routes, or anything that breaks static export. These sites deploy as plain HTML.
|
|
55
|
+
- Don't add page-level redirects in code; use Cloudflare Pages `_redirects` or `_headers`.
|
|
56
|
+
- Don't introduce a new heavy dependency without checking if existing modules already cover it.
|
|
57
|
+
|
|
58
|
+
## After any non-trivial change
|
|
59
|
+
|
|
60
|
+
Run through `checklist/core.md` before reporting work as done. For UI changes, build the site and view the pages in a browser — type-checking is not visual verification.
|
|
61
|
+
|
|
62
|
+
## Updating to a new version of `@ibalzam/codejitsu-core`
|
|
63
|
+
|
|
64
|
+
1. `npm update @ibalzam/codejitsu-core`
|
|
65
|
+
2. Read `node_modules/@ibalzam/codejitsu-core/MIGRATIONS/` for any version notes newer than the previous installed version.
|
|
66
|
+
3. Apply migration steps in order. They're prose, not codemods — judgment is fine; ask the user when ambiguous.
|
|
67
|
+
4. Run `checklist/core.md` to verify nothing regressed.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ika Balzam
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Migrations
|
|
2
|
+
|
|
3
|
+
When `@ibalzam/codejitsu-core` ships a change that requires touching site-owned files (page routes, config, content), the change is described here as prose Claude reads and applies.
|
|
4
|
+
|
|
5
|
+
## Format
|
|
6
|
+
|
|
7
|
+
One file per version: `MIGRATIONS/<version>.md`. Example: `MIGRATIONS/0.2.0.md`.
|
|
8
|
+
|
|
9
|
+
Each file follows this structure:
|
|
10
|
+
|
|
11
|
+
```markdown
|
|
12
|
+
# 0.2.0
|
|
13
|
+
|
|
14
|
+
## Summary
|
|
15
|
+
One paragraph: what changed and why.
|
|
16
|
+
|
|
17
|
+
## Required actions
|
|
18
|
+
Numbered list of concrete things to do in the site. Reference file paths and code snippets. Be specific enough that Claude can apply them without ambiguity.
|
|
19
|
+
|
|
20
|
+
## Verify
|
|
21
|
+
What to check after applying.
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Why prose, not codemods
|
|
25
|
+
|
|
26
|
+
We do migrations through Claude reading the notes, not jscodeshift. Trade-off: prose is more flexible (handles edge cases, branding variations, half-applied previous migrations) but requires Claude to actually be in the loop on the upgrade. That matches how this package is meant to be used — every Codejitsu site is maintained with Claude, so Claude can read and apply.
|
|
27
|
+
|
|
28
|
+
## When NOT to write a migration
|
|
29
|
+
|
|
30
|
+
Most upgrades don't need one. If a change can live entirely in library code (a new component, a fixed function, an Astro integration hook that injects something automatically), ship it as a package update and let `npm update` propagate. Only write a migration when site-owned files must change.
|
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# @ibalzam/codejitsu-core
|
|
2
|
+
|
|
3
|
+
Shared core for all Codejitsu sites. Two channels per module:
|
|
4
|
+
|
|
5
|
+
- **Code** — importable via `@ibalzam/codejitsu-core/<module>` (Astro/TS).
|
|
6
|
+
- **Instructions** — a `CLAUDE.md` per module that tells Claude how to wire it into a site, what to do, what to avoid.
|
|
7
|
+
|
|
8
|
+
Sites stay thin: configuration + content + brand. Everything else lives here.
|
|
9
|
+
|
|
10
|
+
## Modules
|
|
11
|
+
|
|
12
|
+
| Module | Subpath | What it provides |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| `blog` | `@ibalzam/codejitsu-core/blog` | Markdown-based blog system with scheduled publishing, FAQs, tags, categories |
|
|
15
|
+
| `seo` | `@ibalzam/codejitsu-core/seo` | Sitemap helpers, schema.org JSON-LD builders, meta tag patterns |
|
|
16
|
+
| `images` | `@ibalzam/codejitsu-core/images` + `codejitsu-optimize-images` CLI | PNG/JPG→WebP pre-pass + Astro sharp defaults |
|
|
17
|
+
| `deploy` | (templates only) | GH Action daily-deploy + Cloudflare wrangler templates |
|
|
18
|
+
| `llms` | `codejitsu-llms` CLI | Generates `/llms.txt` + `/llms-full.txt` from site content |
|
|
19
|
+
|
|
20
|
+
Plus `checklist/` — sitewide invariants Claude verifies after any non-trivial change.
|
|
21
|
+
|
|
22
|
+
## How Claude uses this package
|
|
23
|
+
|
|
24
|
+
In a site that depends on `@ibalzam/codejitsu-core`, when the user says **"implement codejitsu/core/blog"** (or animations, or deploy, etc.), Claude reads `node_modules/@ibalzam/codejitsu-core/modules/<name>/CLAUDE.md` for instructions, imports code from the matching subpath, copies any templates, and runs the module's `checklist.md` to verify the result.
|
|
25
|
+
|
|
26
|
+
Start at `CLAUDE.md` (the master entry).
|
|
27
|
+
|
|
28
|
+
## Updating a site after a core release
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm update @ibalzam/codejitsu-core
|
|
32
|
+
# Then tell Claude: "we just upgraded codejitsu/core, check MIGRATIONS for anything that needs applying"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Migration notes live in `MIGRATIONS/<version>.md` as prose Claude reads and applies (not jscodeshift codemods — Claude does the work).
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Smoke checker for Codejitsu sites. Run from a site repo root after
|
|
4
|
+
* `npm run build`. Verifies sitewide invariants from `checklist/core.md`
|
|
5
|
+
* that can be checked programmatically.
|
|
6
|
+
*
|
|
7
|
+
* Exit code 0 = all checks pass.
|
|
8
|
+
* Exit code 1 = at least one check failed (warnings still exit 0).
|
|
9
|
+
*
|
|
10
|
+
* Not exhaustive — visual/UX/content checks need human + Claude review.
|
|
11
|
+
*/
|
|
12
|
+
import fs from 'fs';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
|
|
15
|
+
const cwd = process.cwd();
|
|
16
|
+
const distDir = path.join(cwd, 'dist');
|
|
17
|
+
|
|
18
|
+
const checks = [];
|
|
19
|
+
|
|
20
|
+
function pass(name) {
|
|
21
|
+
checks.push({ status: 'pass', name });
|
|
22
|
+
}
|
|
23
|
+
function warn(name, detail) {
|
|
24
|
+
checks.push({ status: 'warn', name, detail });
|
|
25
|
+
}
|
|
26
|
+
function fail(name, detail) {
|
|
27
|
+
checks.push({ status: 'fail', name, detail });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function exists(p) {
|
|
31
|
+
return fs.existsSync(path.join(cwd, p));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function distHtmlFiles() {
|
|
35
|
+
if (!fs.existsSync(distDir)) return [];
|
|
36
|
+
const out = [];
|
|
37
|
+
(function walk(dir) {
|
|
38
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
39
|
+
const full = path.join(dir, entry.name);
|
|
40
|
+
if (entry.isDirectory()) walk(full);
|
|
41
|
+
else if (entry.name.endsWith('.html')) out.push(full);
|
|
42
|
+
}
|
|
43
|
+
})(distDir);
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function readFile(p) {
|
|
48
|
+
return fs.readFileSync(p, 'utf8');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ─── Pre-build files ─────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
if (!exists('wrangler.toml')) fail('wrangler.toml present');
|
|
54
|
+
else pass('wrangler.toml present');
|
|
55
|
+
|
|
56
|
+
if (!exists('.github/workflows/daily-deploy.yml'))
|
|
57
|
+
warn('.github/workflows/daily-deploy.yml present', 'Skip only if site has no scheduled content.');
|
|
58
|
+
else pass('.github/workflows/daily-deploy.yml present');
|
|
59
|
+
|
|
60
|
+
if (!exists('astro.config.mjs') && !exists('astro.config.ts'))
|
|
61
|
+
fail('astro.config.{mjs,ts} present');
|
|
62
|
+
else {
|
|
63
|
+
const cfg = readFile(
|
|
64
|
+
path.join(cwd, exists('astro.config.ts') ? 'astro.config.ts' : 'astro.config.mjs')
|
|
65
|
+
);
|
|
66
|
+
if (!/trailingSlash:\s*['"]always['"]/.test(cfg))
|
|
67
|
+
fail("trailingSlash: 'always' in astro.config", 'Required for canonical URL policy.');
|
|
68
|
+
else pass("trailingSlash: 'always' in astro.config");
|
|
69
|
+
|
|
70
|
+
if (!/output:\s*['"]static['"]/.test(cfg))
|
|
71
|
+
fail("output: 'static' in astro.config");
|
|
72
|
+
else pass("output: 'static' in astro.config");
|
|
73
|
+
|
|
74
|
+
if (!/format:\s*['"]webp['"]/.test(cfg))
|
|
75
|
+
warn("image.defaults.format: 'webp' in astro.config");
|
|
76
|
+
else pass("image.defaults.format: 'webp' in astro.config");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ─── Build artifacts ─────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
if (!fs.existsSync(distDir)) {
|
|
82
|
+
fail('dist/ exists', 'Run `npm run build` first.');
|
|
83
|
+
printAndExit();
|
|
84
|
+
}
|
|
85
|
+
pass('dist/ exists');
|
|
86
|
+
|
|
87
|
+
const htmlFiles = distHtmlFiles();
|
|
88
|
+
if (htmlFiles.length === 0) fail('dist/ contains HTML files');
|
|
89
|
+
else pass(`dist/ contains ${htmlFiles.length} HTML files`);
|
|
90
|
+
|
|
91
|
+
const hasSitemap = fs.existsSync(path.join(distDir, 'sitemap-index.xml')) ||
|
|
92
|
+
fs.existsSync(path.join(distDir, 'sitemap-0.xml'));
|
|
93
|
+
if (!hasSitemap) fail('sitemap-(index|0).xml in dist/');
|
|
94
|
+
else pass('sitemap in dist/');
|
|
95
|
+
|
|
96
|
+
if (!fs.existsSync(path.join(distDir, 'robots.txt'))) fail('dist/robots.txt');
|
|
97
|
+
else pass('dist/robots.txt');
|
|
98
|
+
|
|
99
|
+
if (!fs.existsSync(path.join(distDir, 'llms.txt'))) warn('dist/llms.txt');
|
|
100
|
+
else pass('dist/llms.txt');
|
|
101
|
+
|
|
102
|
+
if (!fs.existsSync(path.join(distDir, 'llms-full.txt'))) warn('dist/llms-full.txt');
|
|
103
|
+
else pass('dist/llms-full.txt');
|
|
104
|
+
|
|
105
|
+
// ─── Per-page checks ─────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
const missingTitle = [];
|
|
108
|
+
const missingDescription = [];
|
|
109
|
+
const missingCanonical = [];
|
|
110
|
+
const missingOgImage = [];
|
|
111
|
+
const missingJsonLd = [];
|
|
112
|
+
const pngWhereWebp = [];
|
|
113
|
+
const placeholderText = [];
|
|
114
|
+
|
|
115
|
+
const webpSet = new Set();
|
|
116
|
+
(function walkAssets(dir) {
|
|
117
|
+
if (!fs.existsSync(dir)) return;
|
|
118
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
119
|
+
const full = path.join(dir, entry.name);
|
|
120
|
+
if (entry.isDirectory()) walkAssets(full);
|
|
121
|
+
else if (entry.name.toLowerCase().endsWith('.webp')) {
|
|
122
|
+
webpSet.add(path.relative(distDir, full).replace(/\.webp$/i, ''));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
})(distDir);
|
|
126
|
+
|
|
127
|
+
const PLACEHOLDER_RE = /\b(lorem ipsum|TODO|FIXME|XXX:|placeholder)\b/i;
|
|
128
|
+
|
|
129
|
+
for (const file of htmlFiles) {
|
|
130
|
+
const rel = path.relative(distDir, file);
|
|
131
|
+
const html = readFile(file);
|
|
132
|
+
|
|
133
|
+
if (!/<title>[^<]+<\/title>/.test(html)) missingTitle.push(rel);
|
|
134
|
+
if (!/<meta\s+name=["']description["']\s+content=["'][^"']+["']/i.test(html))
|
|
135
|
+
missingDescription.push(rel);
|
|
136
|
+
if (!/<link\s+rel=["']canonical["']\s+href=["'][^"']+["']/i.test(html))
|
|
137
|
+
missingCanonical.push(rel);
|
|
138
|
+
if (!/<meta\s+property=["']og:image["']\s+content=["'][^"']+["']/i.test(html))
|
|
139
|
+
missingOgImage.push(rel);
|
|
140
|
+
if (!/<script\s+type=["']application\/ld\+json["']/i.test(html))
|
|
141
|
+
missingJsonLd.push(rel);
|
|
142
|
+
|
|
143
|
+
// Find <img src="*.png|*.jpg"> and check if a .webp equivalent exists.
|
|
144
|
+
for (const m of html.matchAll(/<img[^>]+src=["']([^"']+\.(?:png|jpe?g))["']/gi)) {
|
|
145
|
+
const src = m[1].replace(/^\//, '');
|
|
146
|
+
const noExt = src.replace(/\.(?:png|jpe?g)$/i, '');
|
|
147
|
+
if (webpSet.has(noExt)) pngWhereWebp.push(`${rel} → ${m[1]}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (PLACEHOLDER_RE.test(html)) placeholderText.push(rel);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function reportList(name, list, severity = 'fail') {
|
|
154
|
+
if (list.length === 0) {
|
|
155
|
+
pass(name);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const detail =
|
|
159
|
+
list.length <= 5
|
|
160
|
+
? list.join(', ')
|
|
161
|
+
: `${list.slice(0, 5).join(', ')} … (+${list.length - 5} more)`;
|
|
162
|
+
(severity === 'fail' ? fail : warn)(name, detail);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
reportList('Every page has <title>', missingTitle);
|
|
166
|
+
reportList('Every page has <meta description>', missingDescription);
|
|
167
|
+
reportList('Every page has canonical link', missingCanonical);
|
|
168
|
+
reportList('Every page has og:image', missingOgImage, 'warn');
|
|
169
|
+
reportList('Every page has JSON-LD schema', missingJsonLd);
|
|
170
|
+
reportList('No <img> references raw PNG/JPG where WebP exists', pngWhereWebp);
|
|
171
|
+
reportList('No placeholder text in production HTML', placeholderText);
|
|
172
|
+
|
|
173
|
+
// ─── Output ──────────────────────────────────────────────────────────────
|
|
174
|
+
|
|
175
|
+
printAndExit();
|
|
176
|
+
|
|
177
|
+
function printAndExit() {
|
|
178
|
+
const passes = checks.filter((c) => c.status === 'pass').length;
|
|
179
|
+
const warns = checks.filter((c) => c.status === 'warn').length;
|
|
180
|
+
const fails = checks.filter((c) => c.status === 'fail').length;
|
|
181
|
+
|
|
182
|
+
for (const c of checks) {
|
|
183
|
+
const icon = c.status === 'pass' ? '✓' : c.status === 'warn' ? '!' : '✗';
|
|
184
|
+
const line = `${icon} ${c.name}`;
|
|
185
|
+
console.log(c.detail ? `${line}\n ${c.detail}` : line);
|
|
186
|
+
}
|
|
187
|
+
console.log(`\n${passes} pass · ${warns} warn · ${fails} fail`);
|
|
188
|
+
process.exit(fails > 0 ? 1 : 0);
|
|
189
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Sitewide checklist — every Codejitsu site
|
|
2
|
+
|
|
3
|
+
Walk through this list after any non-trivial change. Programmatic checks are runnable via `npx codejitsu-check` (runs from the site repo root after `npm run build`).
|
|
4
|
+
|
|
5
|
+
The runner covers the easy stuff (file presence, meta tags, canonical, schema script tags, PNG-where-WebP-exists, placeholder text). The rest needs human + Claude judgment.
|
|
6
|
+
|
|
7
|
+
## Build + deploy
|
|
8
|
+
|
|
9
|
+
- [ ] `npm run build` exits 0 with no warnings about missing pages, unresolved imports, or broken links.
|
|
10
|
+
- [ ] `dist/` contains static HTML for every expected route. No `.html` route is missing.
|
|
11
|
+
- [ ] `wrangler.toml` is present and points to `dist`.
|
|
12
|
+
- [ ] `.github/workflows/daily-deploy.yml` exists; the `CLOUDFLARE_DEPLOY_HOOK_URL` secret is set in the repo (skip if site has no scheduled content).
|
|
13
|
+
|
|
14
|
+
## URLs + routing
|
|
15
|
+
|
|
16
|
+
- [ ] Astro config has `trailingSlash: 'always'` and `output: 'static'`.
|
|
17
|
+
- [ ] No internal link in any `.astro`/`.tsx` file points to a URL without a trailing slash.
|
|
18
|
+
- [ ] Canonical URL appears on every page in `<head>` and matches the trailing-slash policy.
|
|
19
|
+
|
|
20
|
+
## SEO
|
|
21
|
+
|
|
22
|
+
- [ ] Every page has `<title>` and `<meta name="description">`. No duplicates across pages.
|
|
23
|
+
- [ ] OG meta on every page: `og:title`, `og:description`, `og:image`, `og:url`, `og:type`.
|
|
24
|
+
- [ ] Twitter card meta on every page.
|
|
25
|
+
- [ ] JSON-LD schema appropriate per page type (Organization, LocalBusiness, BlogPosting, FAQPage). Use `@ibalzam/codejitsu-core/seo/schema` builders.
|
|
26
|
+
- [ ] `sitemap-index.xml` generated and lists every public page. Future-dated blog posts are excluded.
|
|
27
|
+
- [ ] `robots.txt` at root and references the sitemap.
|
|
28
|
+
- [ ] `llms.txt` and `llms-full.txt` at root, regenerated this build.
|
|
29
|
+
|
|
30
|
+
## Images
|
|
31
|
+
|
|
32
|
+
- [ ] No `<img src="*.png">` or `<img src="*.jpg">` references in built HTML where a WebP equivalent exists.
|
|
33
|
+
- [ ] Astro `image.defaults` includes `format: 'webp'`.
|
|
34
|
+
- [ ] All images in `public/images/` over 500KB have been run through `codejitsu-optimize-images` or have a documented exception.
|
|
35
|
+
|
|
36
|
+
## Performance
|
|
37
|
+
|
|
38
|
+
- [ ] No client-side JS on pages that don't need it (Astro should ship zero JS by default).
|
|
39
|
+
- [ ] `inlineStylesheets: 'always'` in build config (or justified opt-out).
|
|
40
|
+
- [ ] Hero image uses `loading="eager"` + `fetchpriority="high"`; everything else lazy-loaded.
|
|
41
|
+
|
|
42
|
+
## Accessibility
|
|
43
|
+
|
|
44
|
+
- [ ] Every `<img>` has alt text (decorative images: `alt=""`).
|
|
45
|
+
- [ ] Every interactive element is keyboard-reachable and has a visible focus style.
|
|
46
|
+
- [ ] Color contrast meets WCAG AA on body text.
|
|
47
|
+
|
|
48
|
+
## Content
|
|
49
|
+
|
|
50
|
+
- [ ] No placeholder text (`Lorem ipsum`, `TODO`, `FIXME`) in shipped routes.
|
|
51
|
+
- [ ] Every blog post in `content/blog/` has required frontmatter (see `modules/blog/checklist.md`).
|
|
52
|
+
|
|
53
|
+
## When something on this list changes
|
|
54
|
+
|
|
55
|
+
If a new invariant is added to a Codejitsu site, add it here AND bump the package version with a `MIGRATIONS/<version>.md` note so existing sites can adopt it.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Blog module — instructions for Claude
|
|
2
|
+
|
|
3
|
+
When the user asks to **implement codejitsu/core/blog** (or "add the blog system"), do the following.
|
|
4
|
+
|
|
5
|
+
## What this module provides
|
|
6
|
+
|
|
7
|
+
A markdown-based blog with:
|
|
8
|
+
- File-based posts in `content/blog/*.md` (gray-matter frontmatter)
|
|
9
|
+
- Scheduled publishing — future-dated posts are hidden from public pages and sitemap, but their slugs stay buildable so OG meta scrapers (Hootsuite, etc.) can hit them
|
|
10
|
+
- Dual-slug resolution — filename slug (`2026-02-08-foo`) and canonical frontmatter slug (`foo`) both resolve to the same post; the frontmatter slug is canonical for SEO
|
|
11
|
+
- Reading time, tags, FAQs, categories
|
|
12
|
+
- Listing, tag, category pages
|
|
13
|
+
|
|
14
|
+
## Wiring it into a site
|
|
15
|
+
|
|
16
|
+
### 1. Install peer deps in the site (one-time)
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install gray-matter reading-time
|
|
20
|
+
```
|
|
21
|
+
(They're transitive deps of `@ibalzam/codejitsu-core`, but Astro's bundler resolves them from the site's `node_modules`.)
|
|
22
|
+
|
|
23
|
+
### 2. Create the site's blog instance
|
|
24
|
+
|
|
25
|
+
Copy `templates/lib/blog.ts` → `src/lib/blog.ts` in the site. Edit the config to set the site's default author and (optionally) its category list. The whole file is ~10 lines.
|
|
26
|
+
|
|
27
|
+
### 3. Add page routes
|
|
28
|
+
|
|
29
|
+
Copy these from `templates/pages/` → `src/pages/` in the site:
|
|
30
|
+
- `blog/index.astro` — listing
|
|
31
|
+
- `blog/[...slug].astro` — detail (handles both filename and canonical slug forms)
|
|
32
|
+
- `blog/tag/[tag].astro` — tag pages
|
|
33
|
+
- `blog/category/[category].astro` — category pages (skip if site has no categories)
|
|
34
|
+
|
|
35
|
+
Adapt the markup to the site's design system. The page logic (data fetching, getStaticPaths) is the part that must stay correct — styling is the site's job.
|
|
36
|
+
|
|
37
|
+
### 4. Add the first post
|
|
38
|
+
|
|
39
|
+
Copy `templates/content/_sample-post.md` → `content/blog/<today>-<slug>.md` and edit.
|
|
40
|
+
|
|
41
|
+
### 5. Wire scheduled-post filter into the sitemap
|
|
42
|
+
|
|
43
|
+
In `astro.config.mjs`, import the site's blog instance and exclude future-dated slugs from the sitemap:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { blog } from './src/lib/blog';
|
|
47
|
+
const futureSlugs = await blog.getFutureBlogSlugs();
|
|
48
|
+
|
|
49
|
+
// in sitemap integration:
|
|
50
|
+
filter: (page) => {
|
|
51
|
+
const m = page.match(/\/blog\/([^/]+)\/?$/);
|
|
52
|
+
return !(m && futureSlugs.includes(m[1]));
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 6. Wire the daily-deploy GH Action
|
|
57
|
+
|
|
58
|
+
See `modules/deploy/CLAUDE.md`. The cron rebuilds the site so scheduled posts graduate from hidden to public on their publish date.
|
|
59
|
+
|
|
60
|
+
## Post frontmatter shape
|
|
61
|
+
|
|
62
|
+
```yaml
|
|
63
|
+
---
|
|
64
|
+
title: "How to size a furnace" # required
|
|
65
|
+
description: "Quick guide to BTU sizing" # required (used as meta description)
|
|
66
|
+
date: 2026-03-15 # required; future date = hidden until that day
|
|
67
|
+
slug: how-to-size-a-furnace # optional; if set, this is the canonical URL
|
|
68
|
+
author: "Pearl Remodeling" # optional; falls back to defaultAuthor in config
|
|
69
|
+
image: /images/blog/furnace-sizing.webp # optional; used for OG + listing card
|
|
70
|
+
tags: [HVAC, Heating, Guides] # optional
|
|
71
|
+
faqs: # optional; rendered as FAQ schema + section
|
|
72
|
+
- question: "What BTU do I need?"
|
|
73
|
+
answer: "Roughly 30 BTU per sq ft as a starting point..."
|
|
74
|
+
---
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## What must NOT be done
|
|
78
|
+
|
|
79
|
+
- **Don't reimplement the loader.** Always import from `@ibalzam/codejitsu-core/blog` via the site's `src/lib/blog.ts`.
|
|
80
|
+
- **Don't bypass `getAllPosts()` for the listing.** It's already filtering future-dated posts; bypassing means drafts leak.
|
|
81
|
+
- **Don't add `getStaticPaths` that calls `getAllPosts()` alone for the detail page** — it'll exclude future-dated posts and break OG scraping for scheduled releases. Use `getAllPostSlugs()` for path generation.
|
|
82
|
+
- **Don't put `.mdx` files in `content/blog/`.** The loader is `.md` only. If MDX support is needed, raise it as a feature request — it's a deliberate scope decision.
|
|
83
|
+
- **Don't change the dual-slug resolution.** Old date-prefixed URLs must keep working alongside short canonical slugs.
|
|
84
|
+
|
|
85
|
+
## Verify
|
|
86
|
+
|
|
87
|
+
Run `modules/blog/checklist.md` after wiring. Run `checklist/core.md` (sitewide).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Blog module — checklist
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
- [ ] `src/lib/blog.ts` exists and calls `createBlog(...)` from `@ibalzam/codejitsu-core/blog`.
|
|
6
|
+
- [ ] `content/blog/` directory exists; at least one `.md` post is present.
|
|
7
|
+
- [ ] Page routes exist: `src/pages/blog/index.astro`, `src/pages/blog/[...slug].astro`.
|
|
8
|
+
- [ ] If site uses tags: `src/pages/blog/tag/[tag].astro` exists.
|
|
9
|
+
- [ ] If site uses categories: `src/pages/blog/category/[category].astro` exists and `categories` is passed to `createBlog`.
|
|
10
|
+
|
|
11
|
+
## Post frontmatter
|
|
12
|
+
|
|
13
|
+
For every post in `content/blog/`:
|
|
14
|
+
- [ ] `title`, `description`, `date` are present.
|
|
15
|
+
- [ ] `description` is < 160 chars (meta description budget).
|
|
16
|
+
- [ ] `date` is ISO `YYYY-MM-DD` format.
|
|
17
|
+
- [ ] If `image` is set, the file exists in `public/`.
|
|
18
|
+
- [ ] If `faqs` is set, every entry has both `question` and `answer`.
|
|
19
|
+
|
|
20
|
+
## Build behaviour
|
|
21
|
+
|
|
22
|
+
- [ ] Future-dated posts are absent from `dist/blog/index.html` (listing).
|
|
23
|
+
- [ ] Future-dated posts ARE built at `dist/blog/<slug>/index.html` (so OG scrapers can reach them).
|
|
24
|
+
- [ ] `sitemap-index.xml` does NOT contain URLs for future-dated posts.
|
|
25
|
+
|
|
26
|
+
## SEO
|
|
27
|
+
|
|
28
|
+
- [ ] Each blog post page has `BlogPosting` JSON-LD schema (use `@ibalzam/codejitsu-core/seo/schema`).
|
|
29
|
+
- [ ] If post has `faqs`, the page also has `FAQPage` JSON-LD.
|
|
30
|
+
- [ ] Post pages have OG image set to `frontmatter.image` (absolute URL).
|
|
31
|
+
- [ ] Canonical URL on a post points to the canonical (frontmatter) slug, not the filename slug.
|
|
32
|
+
|
|
33
|
+
## Daily deploy
|
|
34
|
+
|
|
35
|
+
- [ ] `.github/workflows/daily-deploy.yml` is present (see `modules/deploy/`).
|
|
36
|
+
- [ ] `CLOUDFLARE_DEPLOY_HOOK_URL` secret is configured in the repo.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { BlogPost, BlogPostMetadata, BlogCategory, FAQItem } from '../index.js';
|