@ibalzam/codejitsu-core 0.9.0 → 0.11.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 +4 -0
- package/SETUP.md +533 -0
- package/bin/codejitsu.mjs +3 -0
- package/modules/blog-writer/BLOG_BATCH.md +142 -0
- package/modules/blog-writer/BLOG_IMAGES.md +127 -0
- package/modules/blog-writer/BLOG_WRITING.md +198 -0
- package/modules/blog-writer/CLAUDE.md +125 -0
- package/modules/blog-writer/templates/.claude/commands/blog-batch.md +10 -0
- package/modules/blog-writer/templates/.claude/commands/blog-images.md +12 -0
- package/modules/blog-writer/templates/.claude/commands/blog.md +10 -0
- package/modules/cli/src/blog-init.mjs +59 -0
- package/modules/config/src/types.d.ts +49 -0
- package/modules/config/src/types.ts +41 -0
- package/package.json +3 -1
package/CLAUDE.md
CHANGED
|
@@ -10,6 +10,10 @@ Shared core for every Codejitsu site. When the user invokes a module by name (e.
|
|
|
10
10
|
4. If the module has a `templates/` directory, copy those files into the site at the locations the module's CLAUDE.md specifies. Adapt templates for the site's brand and content.
|
|
11
11
|
5. After the change, walk through the module's `checklist.md` plus `checklist/core.md` (sitewide). Run `npx codejitsu-check` from the site root.
|
|
12
12
|
|
|
13
|
+
## How to act on a SETUP request
|
|
14
|
+
|
|
15
|
+
If the user asks to **set up codejitsu**, **init**, **migrate \<site\> to codejitsu**, or **start a new Codejitsu site** — open `node_modules/@ibalzam/codejitsu-core/SETUP.md` and follow the step-by-step playbook. It covers detection, install, config generation, module wiring, deploy, and verify for both greenfield and migration scenarios.
|
|
16
|
+
|
|
13
17
|
## Module subpaths
|
|
14
18
|
|
|
15
19
|
| Subpath | Provides |
|
package/SETUP.md
ADDED
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
# Codejitsu Setup — Claude playbook
|
|
2
|
+
|
|
3
|
+
End-to-end procedure for adding `@ibalzam/codejitsu-core` to any project.
|
|
4
|
+
Works for both **greenfield** (brand new site) and **migration** (existing
|
|
5
|
+
Astro site with its own scaffolding).
|
|
6
|
+
|
|
7
|
+
> **For Claude**: This file is the cookbook. When the user says any of:
|
|
8
|
+
> "set up codejitsu", "add codejitsu", "init codejitsu", "migrate <site> to
|
|
9
|
+
> codejitsu", "start a new Codejitsu site" — open this file and follow the
|
|
10
|
+
> steps top-down. Don't improvise from training-data memory.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Pre-flight: never trust version memory
|
|
15
|
+
|
|
16
|
+
Before any step that mentions a package version:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm view @ibalzam/codejitsu-core version # what's actually latest right now
|
|
20
|
+
node --version # must be ≥ 20 LTS
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Use `@latest` in install commands rather than typing a version number you
|
|
24
|
+
think is current. If the project already has the package, run:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx codejitsu doctor
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Surface anything outdated to the user **before** doing setup. Fixing drift
|
|
31
|
+
on a stale codebase is worse than starting from current.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Step 1 — Detect current state
|
|
36
|
+
|
|
37
|
+
Don't ask the user what's there. Look:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
cat package.json | head -40 # what's installed + scripts
|
|
41
|
+
cat astro.config.mjs # framework + integrations
|
|
42
|
+
ls src/content.config.ts 2>/dev/null && cat src/content.config.ts
|
|
43
|
+
ls src/content/ 2>/dev/null # collections present?
|
|
44
|
+
ls src/pages/ # route shape
|
|
45
|
+
cat .git/config | grep url # github remote
|
|
46
|
+
cat wrangler.toml 2>/dev/null # cloudflare project name
|
|
47
|
+
ls codejitsu.config.ts codejitsu.config.mjs 2>/dev/null # already set up?
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Build a one-screen summary for the user:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
Detected:
|
|
54
|
+
Astro: 6.3.5
|
|
55
|
+
Has blog: Yes (Content Collections, dateField: pubDate, draftField: draft)
|
|
56
|
+
Has services: Yes (src/content/services/, 9 .md files)
|
|
57
|
+
Has locations: Yes (src/content/locations/, 7 .md files)
|
|
58
|
+
GitHub: ikanc/example
|
|
59
|
+
Cloudflare: wrangler.toml present, project: example
|
|
60
|
+
Codejitsu: Not installed yet
|
|
61
|
+
|
|
62
|
+
Greenfield or migration: MIGRATION
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
State this back to the user **before making changes**. Ask one question:
|
|
66
|
+
|
|
67
|
+
> "Above is what I detected — anything missing or wrong before I proceed?"
|
|
68
|
+
|
|
69
|
+
If they say "go" → continue. If they correct something → update your model.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Step 2 — Install the package
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npm install @ibalzam/codejitsu-core@latest
|
|
77
|
+
npm install -D jiti # only if codejitsu.config.ts (TS) will be used
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Then immediately:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npx codejitsu doctor
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Confirm:
|
|
87
|
+
- `✓ @ibalzam/codejitsu-core ✓ On latest`
|
|
88
|
+
- No critical outdated deps (Astro / TypeScript / React / Tailwind etc.)
|
|
89
|
+
|
|
90
|
+
If critical deps are outdated, **stop and surface to user**. Major bumps
|
|
91
|
+
get their own sessions. Don't bundle a codejitsu setup with an Astro 5→6
|
|
92
|
+
upgrade.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Step 3 — Generate `codejitsu.config.ts`
|
|
97
|
+
|
|
98
|
+
At the project root. Use detected values; prompt only for unknowns.
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import { defineConfig } from '@ibalzam/codejitsu-core/config';
|
|
102
|
+
|
|
103
|
+
export default defineConfig({
|
|
104
|
+
site: {
|
|
105
|
+
url: '<from astro.config.mjs site:>',
|
|
106
|
+
name: '<ask user>',
|
|
107
|
+
titleSuffix: ' | <site name>',
|
|
108
|
+
defaultAuthor: '<ask user; default "editor">',
|
|
109
|
+
defaultOgImage: '/assets/images/og-image.webp',
|
|
110
|
+
locale: 'en_US',
|
|
111
|
+
business: { // optional — fill if it's a local business
|
|
112
|
+
legalName: '...',
|
|
113
|
+
telephone: '...',
|
|
114
|
+
email: '...',
|
|
115
|
+
address: { addressLocality: '...', addressRegion: '...', addressCountry: 'US' },
|
|
116
|
+
areaServed: ['...'],
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
blog: { // omit entirely if no blog
|
|
121
|
+
mode: 'collection',
|
|
122
|
+
collectionName: 'blog',
|
|
123
|
+
dateField: '<detected from content config; usually pubDate>',
|
|
124
|
+
draftField: '<detected; usually draft or null>',
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
seo: {
|
|
128
|
+
sitemap: {
|
|
129
|
+
excludePatterns: [/* /\/lp\//, etc. — anything to hide from search */],
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
images: {
|
|
134
|
+
sourceDir: '<usually public/assets/images>',
|
|
135
|
+
defaultQuality: 82,
|
|
136
|
+
defaultMaxSize: 1376,
|
|
137
|
+
// autoBlogImages — add only if site has a "blog source images live outside public/" workflow
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
llms: {
|
|
141
|
+
mode: 'content-scan', // sites with services/locations content; use 'config' for simpler sites
|
|
142
|
+
tagline: '<ask user>',
|
|
143
|
+
about: '<ask user>',
|
|
144
|
+
aboutFull: '<ask user, longer version>',
|
|
145
|
+
aiGuidance: '<ask user, "When referencing us..." block>',
|
|
146
|
+
blogDir: 'src/content/blog',
|
|
147
|
+
contentScan: {
|
|
148
|
+
servicesDir: 'src/content/services',
|
|
149
|
+
locationsDir: 'src/content/locations',
|
|
150
|
+
pagesDir: 'src/pages',
|
|
151
|
+
dynamicRoutes: [
|
|
152
|
+
// expand based on detected page routes
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
deploy: {
|
|
158
|
+
cloudflarePagesName: '<from wrangler.toml, or ask>',
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
contact: { // omit if no contact form
|
|
162
|
+
emailjs: {
|
|
163
|
+
serviceId: '<ask user, or read from existing modal if migrating>',
|
|
164
|
+
templateId: '<ask user>',
|
|
165
|
+
publicKey: '<ask user>',
|
|
166
|
+
},
|
|
167
|
+
recaptcha: { // optional
|
|
168
|
+
siteKey: '<ask user>',
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Strategy:
|
|
175
|
+
- Auto-fill from existing state (site URL, blog field names, Cloudflare name).
|
|
176
|
+
- Tagline / about / aiGuidance / EmailJS keys — **ask the user**, don't invent.
|
|
177
|
+
- For migrations, read existing components (e.g. pearl's `QuoteModal.astro`)
|
|
178
|
+
to extract EmailJS keys already in use.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Step 4 — Wire `astro.config.mjs`
|
|
183
|
+
|
|
184
|
+
For an **existing** config, preserve everything custom (integrations,
|
|
185
|
+
vite config, output settings). Only refactor:
|
|
186
|
+
|
|
187
|
+
### 4a. Replace inline future-blog scanner
|
|
188
|
+
|
|
189
|
+
If the existing config has a `readdirSync` loop scanning blog frontmatter
|
|
190
|
+
for future dates, replace it:
|
|
191
|
+
|
|
192
|
+
```js
|
|
193
|
+
import { createBlog } from '@ibalzam/codejitsu-core/blog';
|
|
194
|
+
|
|
195
|
+
const fsBlog = createBlog({
|
|
196
|
+
contentDir: 'src/content/blog',
|
|
197
|
+
dateField: 'pubDate', // match the codejitsu.config
|
|
198
|
+
draftField: 'draft',
|
|
199
|
+
});
|
|
200
|
+
const futureSlugs = await fsBlog.getFutureBlogSlugs();
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### 4b. Sitemap → package helpers
|
|
204
|
+
|
|
205
|
+
Replace the existing `@astrojs/sitemap` `serialize`/`filter` with:
|
|
206
|
+
|
|
207
|
+
```js
|
|
208
|
+
import {
|
|
209
|
+
defaultPriorityRules,
|
|
210
|
+
excludeFuturePosts,
|
|
211
|
+
composeFilters,
|
|
212
|
+
excludePatterns,
|
|
213
|
+
} from '@ibalzam/codejitsu-core/seo';
|
|
214
|
+
|
|
215
|
+
sitemap({
|
|
216
|
+
filter: composeFilters(
|
|
217
|
+
excludePatterns([/\/lp\//]),
|
|
218
|
+
excludeFuturePosts(futureSlugs),
|
|
219
|
+
),
|
|
220
|
+
serialize: defaultPriorityRules(SITE, {
|
|
221
|
+
rules: [
|
|
222
|
+
// any site-specific priority overrides
|
|
223
|
+
],
|
|
224
|
+
}),
|
|
225
|
+
})
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### 4c. Add rehype trailing-slash plugin
|
|
229
|
+
|
|
230
|
+
```js
|
|
231
|
+
import rehypeTrailingSlash from '@ibalzam/codejitsu-core/rehype/trailing-slash';
|
|
232
|
+
|
|
233
|
+
// inside defineConfig({}):
|
|
234
|
+
markdown: {
|
|
235
|
+
rehypePlugins: [rehypeTrailingSlash],
|
|
236
|
+
},
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### 4d. Verify defaults
|
|
240
|
+
|
|
241
|
+
```js
|
|
242
|
+
output: 'static',
|
|
243
|
+
trailingSlash: 'always',
|
|
244
|
+
image: {
|
|
245
|
+
service: { entrypoint: 'astro/assets/services/sharp' },
|
|
246
|
+
defaults: { quality: 82, format: 'webp' },
|
|
247
|
+
},
|
|
248
|
+
build: { assets: 'assets', inlineStylesheets: 'always' },
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
For migrations, these often already exist. For greenfield, add them.
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Step 5 — Wire the blog (if site has one)
|
|
256
|
+
|
|
257
|
+
### 5a. Content Collection schema
|
|
258
|
+
|
|
259
|
+
`src/content.config.ts` — copy from
|
|
260
|
+
`node_modules/@ibalzam/codejitsu-core/modules/blog/templates/content.config.ts`.
|
|
261
|
+
Adapt to the site's existing frontmatter shape.
|
|
262
|
+
|
|
263
|
+
**On Astro 6+**: import `z` from `'astro/zod'`, NOT `'astro:content'`
|
|
264
|
+
(deprecated). `import { defineCollection } from 'astro:content';`
|
|
265
|
+
plus `import { z } from 'astro/zod';`.
|
|
266
|
+
|
|
267
|
+
### 5b. Loader
|
|
268
|
+
|
|
269
|
+
`src/lib/blog.ts`:
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
import type { CollectionEntry } from 'astro:content';
|
|
273
|
+
import { createBlogFromCollection } from '@ibalzam/codejitsu-core/blog/collection';
|
|
274
|
+
|
|
275
|
+
export const blog = createBlogFromCollection<CollectionEntry<'blog'>>({
|
|
276
|
+
collectionName: 'blog',
|
|
277
|
+
dateField: 'pubDate',
|
|
278
|
+
draftField: 'draft',
|
|
279
|
+
defaultAuthor: 'editor',
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Backward-compat exports if migrating existing pages:
|
|
283
|
+
export const getPublishedPosts = () => blog.getPublishedEntries();
|
|
284
|
+
export const getAllPosts = () => blog.getAllEntries();
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
If migrating, page routes like `src/pages/blog/[slug].astro` should already
|
|
288
|
+
import these names — keep the exports.
|
|
289
|
+
|
|
290
|
+
### 5c. Page routes
|
|
291
|
+
|
|
292
|
+
For **greenfield**, copy from `node_modules/@ibalzam/codejitsu-core/modules/blog/templates/pages/blog/`:
|
|
293
|
+
- `[...slug].astro` — detail
|
|
294
|
+
- `index.astro` — listing
|
|
295
|
+
- `tag/[tag].astro` (optional)
|
|
296
|
+
- `category/[category].astro` (optional)
|
|
297
|
+
|
|
298
|
+
For **migrations**, leave existing page routes — they already work via the
|
|
299
|
+
backward-compat exports.
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## Step 6 — Wire the SEO Head
|
|
304
|
+
|
|
305
|
+
### 6a. SiteHead wrapper
|
|
306
|
+
|
|
307
|
+
`src/components/SiteHead.astro` — pulls site defaults from
|
|
308
|
+
`codejitsu.config.ts` and forwards to the package's `Head.astro`. Template
|
|
309
|
+
in `modules/seo/CLAUDE.md`.
|
|
310
|
+
|
|
311
|
+
### 6b. Use it on every page
|
|
312
|
+
|
|
313
|
+
Every layout's `<head>` should have:
|
|
314
|
+
|
|
315
|
+
```astro
|
|
316
|
+
<SiteHead title="..." description="..." schema={[...]} />
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
For **greenfield**: create one BaseLayout with SiteHead.
|
|
320
|
+
For **migrations**: the existing site likely has its own `Head.astro`.
|
|
321
|
+
Don't replace it wholesale — instead:
|
|
322
|
+
1. Import `jsonLd` from `@ibalzam/codejitsu-core/seo` and use it for JSON-LD injection (fixes XSS risk).
|
|
323
|
+
2. Import schema builders (`blogPosting`, `breadcrumbList`, `service`, etc.) from `@ibalzam/codejitsu-core/seo` and delegate.
|
|
324
|
+
3. Pages stay calling the existing Head; only the implementation gets thinner.
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## Step 7 — Wire contact (if site has one)
|
|
329
|
+
|
|
330
|
+
If `contact` is in the config:
|
|
331
|
+
|
|
332
|
+
In a layout that wraps every page (e.g. BaseLayout), near `</body>`:
|
|
333
|
+
|
|
334
|
+
```astro
|
|
335
|
+
---
|
|
336
|
+
import ContactModal from '@ibalzam/codejitsu-core/contact/ContactModal.astro';
|
|
337
|
+
import cjConfig from '../../codejitsu.config';
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
<ContactModal
|
|
341
|
+
title="Get a Quote"
|
|
342
|
+
image={{ src: '/assets/images/contact.webp', alt: '...' }}
|
|
343
|
+
fields={{
|
|
344
|
+
name: { required: true },
|
|
345
|
+
email: { required: true },
|
|
346
|
+
phone: { required: true },
|
|
347
|
+
message: { required: false },
|
|
348
|
+
}}
|
|
349
|
+
submitText="Submit"
|
|
350
|
+
thankYouMessage="Thanks! We'll be in touch."
|
|
351
|
+
emailjs={cjConfig.contact.emailjs}
|
|
352
|
+
recaptcha={cjConfig.contact.recaptcha}
|
|
353
|
+
/>
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Trigger from anywhere: `<button data-codejitsu-contact-trigger>...</button>`.
|
|
357
|
+
|
|
358
|
+
For **migrations**, the existing site has its own modal. Either:
|
|
359
|
+
- (a) replace it with the package modal + remove the old, OR
|
|
360
|
+
- (b) leave the existing modal and skip the contact module.
|
|
361
|
+
|
|
362
|
+
Migrating: update trigger classes/data attributes to match the new pattern.
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## Step 8 — Wire package scripts
|
|
367
|
+
|
|
368
|
+
Update `package.json`:
|
|
369
|
+
|
|
370
|
+
```json
|
|
371
|
+
{
|
|
372
|
+
"scripts": {
|
|
373
|
+
"dev": "astro dev",
|
|
374
|
+
"prebuild": "codejitsu-optimize-images && codejitsu-llms",
|
|
375
|
+
"build": "astro check && astro build",
|
|
376
|
+
"preview": "astro preview"
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
For **migrations**, replace any existing `generate-llms-txt`, `optimize-images`,
|
|
382
|
+
or similar scripts. Delete the old script files (`scripts/optimize-images.js`,
|
|
383
|
+
`scripts/generate-llms-txt.mjs`, etc.) — they're now superseded.
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## Step 9 — Set up the daily deploy
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
npx codejitsu deploy:setup
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Interactive wizard:
|
|
394
|
+
- Copies `.github/workflows/daily-deploy.yml` + `wrangler.toml` from
|
|
395
|
+
package templates if missing.
|
|
396
|
+
- Prompts for the Cloudflare deploy hook URL.
|
|
397
|
+
- Stores it as a GH Actions secret via `gh secret set`.
|
|
398
|
+
- Optionally triggers a test run.
|
|
399
|
+
|
|
400
|
+
If the user doesn't have `gh` CLI authed yet, they need:
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
gh auth login
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
Tell them this **before** running `deploy:setup` — it'll fail otherwise.
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## Step 10 — Verify
|
|
411
|
+
|
|
412
|
+
In sequence:
|
|
413
|
+
|
|
414
|
+
```bash
|
|
415
|
+
npm run build
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
Must complete with 0 errors. Note warnings (e.g. deprecation warnings from
|
|
419
|
+
upstream deps) — surface to user if they need attention.
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
npx codejitsu audit
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
Read the output. Pearl's baseline: 50 pass · ~8 warn · 0 fail · ~10 info.
|
|
426
|
+
Comparable shape on a fresh site = healthy. Treat any `✗ fail` as a blocker.
|
|
427
|
+
|
|
428
|
+
For a more thorough check (production-ready):
|
|
429
|
+
|
|
430
|
+
```bash
|
|
431
|
+
npx codejitsu audit --live https://<deployed-url> --a11y
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
(Use the production URL or a local preview server. Skip --a11y if axe-core
|
|
435
|
+
not desired.)
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## Step 11 — Commit + push
|
|
440
|
+
|
|
441
|
+
`git status` to see what changed. Common files:
|
|
442
|
+
|
|
443
|
+
```
|
|
444
|
+
M astro.config.mjs
|
|
445
|
+
M package.json
|
|
446
|
+
M package-lock.json
|
|
447
|
+
M src/components/Head.astro (if migration)
|
|
448
|
+
M src/data/schemas.ts (if migration)
|
|
449
|
+
M src/lib/blog.ts (if migration; or new)
|
|
450
|
+
A codejitsu.config.ts (new)
|
|
451
|
+
A src/components/SiteHead.astro (greenfield)
|
|
452
|
+
A src/components/Footer.astro (greenfield)
|
|
453
|
+
A .github/workflows/daily-deploy.yml (from deploy:setup)
|
|
454
|
+
A wrangler.toml (from deploy:setup)
|
|
455
|
+
D scripts/generate-llms-txt.mjs (deleted; superseded)
|
|
456
|
+
D scripts/optimize-images.js (deleted; superseded)
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
Commit message structure (single commit for the migration):
|
|
460
|
+
|
|
461
|
+
```
|
|
462
|
+
Adopt @ibalzam/codejitsu-core for shared primitives
|
|
463
|
+
|
|
464
|
+
- codejitsu.config.ts: site info, blog/seo/images/llms/deploy/contact config
|
|
465
|
+
- astro.config.mjs: sitemap helpers, rehype trailing-slash plugin, blog
|
|
466
|
+
future-slug filter via the package
|
|
467
|
+
- src/lib/blog.ts: createBlogFromCollection (preserves API for existing pages)
|
|
468
|
+
- src/data/schemas.ts: delegate to package schema builders; jsonLd safe injection
|
|
469
|
+
- package.json: codejitsu-optimize-images + codejitsu-llms in prebuild
|
|
470
|
+
- Remove redundant scripts: generate-llms-txt, optimize-images
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
**Don't push without the user's explicit "yes, push"** — pushing to main
|
|
474
|
+
triggers production deploy via Cloudflare Pages.
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## Anti-checklist — common mistakes
|
|
479
|
+
|
|
480
|
+
- **Don't** invent version numbers for packages from memory. Always `@latest`.
|
|
481
|
+
- **Don't** bundle major-version bumps (Astro N→N+1, TypeScript major)
|
|
482
|
+
into a codejitsu setup commit. Separate concerns.
|
|
483
|
+
- **Don't** wholesale-replace a site's existing `Head.astro` if it has
|
|
484
|
+
custom site-specific things (analytics, fonts, etc.). Selectively migrate
|
|
485
|
+
the JSON-LD injection + schema builder calls.
|
|
486
|
+
- **Don't** modify `src/pages/*` unless necessary. The backward-compat
|
|
487
|
+
exports in `src/lib/blog.ts` mean page code usually doesn't need changes.
|
|
488
|
+
- **Don't** commit the deploy hook URL to git. Use `gh secret set`.
|
|
489
|
+
- **Don't** set `recaptcha` in the contact config unless EmailJS's
|
|
490
|
+
server-side verification is enabled — otherwise it's just friction.
|
|
491
|
+
See `modules/contact/CLAUDE.md`.
|
|
492
|
+
- **Don't** push without explicit user permission. Per the user's global rule.
|
|
493
|
+
|
|
494
|
+
---
|
|
495
|
+
|
|
496
|
+
## Greenfield-specific shortcuts
|
|
497
|
+
|
|
498
|
+
For brand-new sites, after Step 2 (install):
|
|
499
|
+
|
|
500
|
+
```bash
|
|
501
|
+
npm create astro@latest --template minimal --typescript strict --yes
|
|
502
|
+
# Then steps 3 through 11
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
The greenfield path is simpler because there's nothing to migrate — every
|
|
506
|
+
file is new from a template.
|
|
507
|
+
|
|
508
|
+
For greenfield blog setup specifically:
|
|
509
|
+
|
|
510
|
+
```bash
|
|
511
|
+
# Astro CC schema
|
|
512
|
+
cp node_modules/@ibalzam/codejitsu-core/modules/blog/templates/content.config.ts src/
|
|
513
|
+
|
|
514
|
+
# Page routes
|
|
515
|
+
mkdir -p src/pages/blog
|
|
516
|
+
cp -r node_modules/@ibalzam/codejitsu-core/modules/blog/templates/pages/blog/ src/pages/
|
|
517
|
+
|
|
518
|
+
# Sample post
|
|
519
|
+
cp node_modules/@ibalzam/codejitsu-core/modules/blog/templates/content/_sample-post.md src/content/blog/welcome.md
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
---
|
|
523
|
+
|
|
524
|
+
## When done
|
|
525
|
+
|
|
526
|
+
The site should pass:
|
|
527
|
+
- `npm run build` → 0 errors
|
|
528
|
+
- `npx codejitsu doctor` → all deps current
|
|
529
|
+
- `npx codejitsu audit` → 0 fail
|
|
530
|
+
- Browser smoke test: open `npm run dev`, click through home + blog + contact
|
|
531
|
+
|
|
532
|
+
Hand back to the user with the audit + doctor summaries. Don't push until
|
|
533
|
+
they say "push".
|
package/bin/codejitsu.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { parseArgs } from 'node:util';
|
|
|
3
3
|
import { runBlog } from '../modules/cli/src/blog.mjs';
|
|
4
4
|
import { runDeploySetup, runDeployTrigger } from '../modules/cli/src/deploy.mjs';
|
|
5
5
|
import { runDoctor } from '../modules/cli/src/doctor.mjs';
|
|
6
|
+
import { runBlogInit } from '../modules/cli/src/blog-init.mjs';
|
|
6
7
|
import { runAudit } from '../modules/audit/src/run.mjs';
|
|
7
8
|
|
|
8
9
|
const subcommand = process.argv[2];
|
|
@@ -11,6 +12,7 @@ const rest = process.argv.slice(3);
|
|
|
11
12
|
const COMMANDS = {
|
|
12
13
|
'blog:list': () => runBlog('blog:list'),
|
|
13
14
|
'blog:drafts': () => runBlog('blog:drafts'),
|
|
15
|
+
'blog:init': () => runBlogInit(),
|
|
14
16
|
'deploy:setup': () => runDeploySetup(),
|
|
15
17
|
'deploy:run': () => runDeployTrigger(),
|
|
16
18
|
doctor: () => runDoctor(),
|
|
@@ -56,6 +58,7 @@ function printHelp() {
|
|
|
56
58
|
console.log(`Subcommands:`);
|
|
57
59
|
console.log(` blog:list List every non-draft post with URL + image check`);
|
|
58
60
|
console.log(` blog:drafts List future-dated (pending) posts only`);
|
|
61
|
+
console.log(` blog:init Install /blog, /blog-batch, /blog-images slash commands`);
|
|
59
62
|
console.log(``);
|
|
60
63
|
console.log(` deploy:setup Wire up daily Cloudflare deploy (prompts for hook URL)`);
|
|
61
64
|
console.log(` deploy:run Trigger the Daily Deploy workflow once now`);
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Blog batch generation — Claude playbook
|
|
2
|
+
|
|
3
|
+
> Triggered when the user runs `/blog-batch <N>` in a Codejitsu site
|
|
4
|
+
> (default N = 20).
|
|
5
|
+
>
|
|
6
|
+
> Generates a **schedule + outline** for N future-dated posts. It does NOT
|
|
7
|
+
> write the full body of every post — that's `/blog` per row. The goal here
|
|
8
|
+
> is to produce a publishable roadmap that the user reviews + edits before
|
|
9
|
+
> any prose is committed.
|
|
10
|
+
|
|
11
|
+
## Step 0 — Read site config
|
|
12
|
+
|
|
13
|
+
Same as `BLOG_WRITING.md` Step 0: load `codejitsu.config.ts.blogWriter`.
|
|
14
|
+
|
|
15
|
+
Also read:
|
|
16
|
+
- `src/content/blog/` — existing posts (find the latest `pubDate`)
|
|
17
|
+
- `src/content.config.ts` — confirm the schema shape
|
|
18
|
+
|
|
19
|
+
## Step 1 — Decide cadence
|
|
20
|
+
|
|
21
|
+
Default cadence is **4 days between posts** (matches workzen / pearl /
|
|
22
|
+
veteran). If existing posts have a different rhythm, infer from the last
|
|
23
|
+
10 and match it.
|
|
24
|
+
|
|
25
|
+
Starting date = max(`latest existing pubDate`, today) + cadence.
|
|
26
|
+
|
|
27
|
+
## Step 2 — Generate N topics
|
|
28
|
+
|
|
29
|
+
Brainstorm `N` topics, balancing across:
|
|
30
|
+
|
|
31
|
+
- **Service × location** combinations — cycle through `services` and
|
|
32
|
+
`locations` from config so coverage is even
|
|
33
|
+
- **Topic format** mix — roughly:
|
|
34
|
+
- 30% comparison / "X vs Y" posts
|
|
35
|
+
- 30% how-to / guides
|
|
36
|
+
- 20% troubleshooting
|
|
37
|
+
- 20% seasonal / news
|
|
38
|
+
- **Season awareness** — apply `seasonalRules` from config. A post landing
|
|
39
|
+
in July shouldn't be about "winterize"; one in December shouldn't be
|
|
40
|
+
about "pre-summer".
|
|
41
|
+
- **Unique titles** — no two posts in the batch should target the same slug
|
|
42
|
+
or near-duplicate keyword. Read all existing post titles before
|
|
43
|
+
brainstorming to avoid collisions.
|
|
44
|
+
- **Approved tag balance** — distribute tags so no single tag is over 40% of
|
|
45
|
+
the batch.
|
|
46
|
+
|
|
47
|
+
For each topic, derive:
|
|
48
|
+
- `slug` — kebab-case, includes the city when applicable
|
|
49
|
+
- `title` — natural, clickable, ≤70 chars
|
|
50
|
+
- `pubDate` — assigned by cadence walk
|
|
51
|
+
- `tags` — 2-3 from `approvedTags`, primary first
|
|
52
|
+
- `service` + `city` — for internal-link planning
|
|
53
|
+
|
|
54
|
+
## Step 3 — Produce the schedule table
|
|
55
|
+
|
|
56
|
+
Write to `Blog/BLOG_SCHEDULE.md` (create the `Blog/` directory if needed).
|
|
57
|
+
Append to it if it already exists — never overwrite without asking.
|
|
58
|
+
|
|
59
|
+
Format:
|
|
60
|
+
|
|
61
|
+
```markdown
|
|
62
|
+
# <Site Name> — Blog Schedule (<start month> - <end month> <year>)
|
|
63
|
+
|
|
64
|
+
Offline publishing calendar. N posts on a <cadence>-day cadence,
|
|
65
|
+
<start date> through <end date>.
|
|
66
|
+
|
|
67
|
+
## How to use
|
|
68
|
+
|
|
69
|
+
1. Pick a row. Slug = filename → `src/content/blog/<slug>.md`.
|
|
70
|
+
2. `pubDate: <date>` in frontmatter controls visibility.
|
|
71
|
+
3. Run `/blog "<title>"` to flesh out the post body.
|
|
72
|
+
4. Run `/blog-images` once a batch of posts is drafted.
|
|
73
|
+
|
|
74
|
+
## Schedule
|
|
75
|
+
|
|
76
|
+
| # | Date | Category | City | Slug | Title |
|
|
77
|
+
|---|------------|----------------|-----------|------|-------|
|
|
78
|
+
| 1 | YYYY-MM-DD | <primary tag> | <city> | `<slug>` | <Title> |
|
|
79
|
+
| 2 | ... | ... | ... | ... | ... |
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Step 4 — Produce per-post outlines
|
|
83
|
+
|
|
84
|
+
In the same file or a sibling `Blog/BLOG_OUTLINES.md`, expand each row into
|
|
85
|
+
an outline:
|
|
86
|
+
|
|
87
|
+
```markdown
|
|
88
|
+
## <Slug>
|
|
89
|
+
|
|
90
|
+
**pubDate**: YYYY-MM-DD
|
|
91
|
+
**target words**: <from config.wordCount.default>
|
|
92
|
+
**tags**: [primary, secondary]
|
|
93
|
+
**service**: <slug>
|
|
94
|
+
**city**: <slug or 'general'>
|
|
95
|
+
|
|
96
|
+
**Angle / hook**: <one-sentence framing of the reader's pain>
|
|
97
|
+
|
|
98
|
+
**Outline**:
|
|
99
|
+
- Intro — 1-2 paragraphs hooking <hook>
|
|
100
|
+
- ## <H2 #1>
|
|
101
|
+
- 2-3 paragraphs covering <points>
|
|
102
|
+
- ## <H2 #2>
|
|
103
|
+
- Comparison table of <X vs Y>
|
|
104
|
+
- ## <H2 #3>
|
|
105
|
+
- ...
|
|
106
|
+
- Closing CTA + 5-8 FAQs
|
|
107
|
+
|
|
108
|
+
**FAQ seeds**: list of 5-8 question stems
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Step 5 — Show + approve
|
|
112
|
+
|
|
113
|
+
Present the full schedule + outline file to the user. Ask:
|
|
114
|
+
|
|
115
|
+
> "Here's the schedule. Anything to adjust before we lock it in?
|
|
116
|
+
> Want me to flesh out the first N posts now, or stop here for review?"
|
|
117
|
+
|
|
118
|
+
If the user says go: run `/blog` for each row (one post at a time). If they
|
|
119
|
+
say stop: leave the file and exit. They can run `/blog` row by row whenever.
|
|
120
|
+
|
|
121
|
+
## Hard rules
|
|
122
|
+
|
|
123
|
+
- **Don't write the full prose for all N in one shot.** Too long, too
|
|
124
|
+
expensive, hard to review. Schedule + outlines first; prose per row on
|
|
125
|
+
demand.
|
|
126
|
+
- **Don't push or commit.** The user reviews + commits.
|
|
127
|
+
- **Respect existing posts.** If `Blog/BLOG_SCHEDULE.md` already has rows,
|
|
128
|
+
append the new batch and don't renumber.
|
|
129
|
+
- **Don't invent services or locations.** Use only what's in
|
|
130
|
+
`blogWriter.services` and `blogWriter.locations`.
|
|
131
|
+
- **Don't invent tags.** Only from `approvedTags`.
|
|
132
|
+
- **Don't bunch up topic types.** Reject your own first draft if it has 5
|
|
133
|
+
comparison posts in a row.
|
|
134
|
+
|
|
135
|
+
## Verify
|
|
136
|
+
|
|
137
|
+
- [ ] N rows, all unique slugs
|
|
138
|
+
- [ ] Dates respect cadence; no collisions with existing posts
|
|
139
|
+
- [ ] Tags balanced across `approvedTags`
|
|
140
|
+
- [ ] Service + city distribution covers most of the config lists
|
|
141
|
+
- [ ] Season fits each `pubDate`'s month
|
|
142
|
+
- [ ] No duplicate or near-duplicate topics
|