@ibalzam/codejitsu-core 0.13.0 → 0.13.1

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.
@@ -0,0 +1,56 @@
1
+ # 0.13.0 — Daily deploy supports multi-site monorepos
2
+
3
+ ## Summary
4
+
5
+ The deploy module now drives **every** Cloudflare Pages project in a repo, not just one. Previously the `daily-deploy.yml` workflow pinged a single deploy hook (`CLOUDFLARE_DEPLOY_HOOK_URL`), so a monorepo that deploys several Pages projects (e.g. `sites/www`, `sites/kamloops`, `sites/kelowna`) only ever rebuilt one of them on the morning cron — the others never graduated their scheduled/future-dated posts.
6
+
7
+ A new secret, `CLOUDFLARE_DEPLOY_HOOK_URLS`, holds every project's deploy hook URL (comma-separated). The workflow prefers it and pings each URL. Single-site repos are unchanged: `CLOUDFLARE_DEPLOY_HOOK_URL` still works and is still what `deploy:setup` writes when you give it one URL.
8
+
9
+ This is backward compatible — **no action needed for single-site repos**. Action is only required if one git repo deploys to **more than one** Cloudflare Pages project.
10
+
11
+ ## Required actions
12
+
13
+ ### Single-site repo (one Pages project)
14
+
15
+ Nothing to do. If you re-copy the workflow template, the behaviour is identical.
16
+
17
+ ### Multi-site monorepo (one repo → several Pages projects)
18
+
19
+ This is the case the change exists for. Symptom: only one site publishes scheduled content each morning; the others look stale until something else triggers a build.
20
+
21
+ 1. **Update the workflow file.** Re-copy the template so it loops over multiple hooks:
22
+
23
+ ```
24
+ cp node_modules/@ibalzam/codejitsu-core/modules/deploy/templates/daily-deploy.yml \
25
+ .github/workflows/daily-deploy.yml
26
+ ```
27
+
28
+ (Or run `npx codejitsu deploy:setup`, which copies it for you if missing.)
29
+
30
+ 2. **Create one Cloudflare deploy hook per Pages project** — dashboard → each project → Settings → Builds & deployments → Deploy hooks, branch `main`.
31
+
32
+ 3. **Store all the hook URLs in `CLOUDFLARE_DEPLOY_HOOK_URLS`, comma-separated.** Easiest via the wizard:
33
+
34
+ ```
35
+ npx codejitsu deploy:setup
36
+ ```
37
+
38
+ When it prompts, paste every project's URL comma-separated. It writes the plural secret (and removes the now-redundant singular one). Or set it directly:
39
+
40
+ ```
41
+ gh secret set CLOUDFLARE_DEPLOY_HOOK_URLS \
42
+ --body "https://api.cloudflare.com/.../hook-a,https://api.cloudflare.com/.../hook-b,https://api.cloudflare.com/.../hook-c"
43
+ ```
44
+
45
+ 4. **Adding a site later:** create its deploy hook and append `,<new-url>` to `CLOUDFLARE_DEPLOY_HOOK_URLS`. No workflow edit needed.
46
+
47
+ ## Verify
48
+
49
+ ```bash
50
+ npm update @ibalzam/codejitsu-core
51
+ npx codejitsu deploy:run # dispatch the workflow once
52
+ ```
53
+
54
+ Then confirm a deployment kicks off in **every** Pages project, not just one. `npx codejitsu audit` now passes when either `CLOUDFLARE_DEPLOY_HOOK_URL` or `CLOUDFLARE_DEPLOY_HOOK_URLS` is set.
55
+
56
+ See `modules/deploy/CLAUDE.md` → "Multi-site monorepos" for the full reference.
package/bin/codejitsu.mjs CHANGED
@@ -73,7 +73,7 @@ function printHelp() {
73
73
  console.log(` blog:init Install /blog, /blog-batch, /blog-images slash commands`);
74
74
  console.log(` blog:selftest Cold-Claude write a throwaway post + grade it. Flags: --topic, --model`);
75
75
  console.log(``);
76
- console.log(` deploy:setup Wire up daily Cloudflare deploy (prompts for hook URL)`);
76
+ console.log(` deploy:setup Wire up daily Cloudflare deploy (prompts for hook URL(s); multi-site aware)`);
77
77
  console.log(` deploy:run Trigger the Daily Deploy workflow once now`);
78
78
  console.log(``);
79
79
  console.log(` doctor Check Node + dependency versions are current (run BEFORE upgrading or scaffolding)`);
@@ -75,12 +75,15 @@ export async function runStructure(ctx) {
75
75
  const secrets = await listSecrets(repo);
76
76
  if (secrets === null) {
77
77
  results.push(info('Skipped GH secret check (gh not authenticated for this repo)'));
78
+ } else if (secrets.includes('CLOUDFLARE_DEPLOY_HOOK_URLS')) {
79
+ results.push(pass(`CLOUDFLARE_DEPLOY_HOOK_URLS set in ${repo} (multi-site)`));
78
80
  } else if (secrets.includes('CLOUDFLARE_DEPLOY_HOOK_URL')) {
79
81
  results.push(pass(`CLOUDFLARE_DEPLOY_HOOK_URL set in ${repo}`));
80
82
  } else {
81
83
  results.push(warn(
82
- 'CLOUDFLARE_DEPLOY_HOOK_URL secret missing',
83
- 'Daily deploy workflow will fail. Run `npx codejitsu deploy:setup` to configure.'
84
+ 'Deploy hook secret missing (CLOUDFLARE_DEPLOY_HOOK_URL or _URLS)',
85
+ 'Daily deploy workflow will fail. Run `npx codejitsu deploy:setup` to configure. ' +
86
+ 'For a multi-site monorepo, set CLOUDFLARE_DEPLOY_HOOK_URLS (one hook per Pages project, comma-separated).'
84
87
  ));
85
88
  }
86
89
  }
@@ -93,13 +93,19 @@ export async function runDeploySetup() {
93
93
  }
94
94
 
95
95
  // ─── GitHub secret ───────────────────────────────────────────────────
96
+ // Single site → CLOUDFLARE_DEPLOY_HOOK_URL. Multi-site monorepo (one repo,
97
+ // several Pages projects) → CLOUDFLARE_DEPLOY_HOOK_URLS holding every
98
+ // project's hook, comma-separated. The daily-deploy workflow prefers the
99
+ // plural secret and pings each URL.
96
100
  console.log('\nChecking GitHub Actions secrets…');
97
101
  const secrets = await listSecrets(repo);
98
- const hasSecret = secrets.includes('CLOUDFLARE_DEPLOY_HOOK_URL');
102
+ const hasSingular = secrets.includes('CLOUDFLARE_DEPLOY_HOOK_URL');
103
+ const hasPlural = secrets.includes('CLOUDFLARE_DEPLOY_HOOK_URLS');
99
104
 
100
- if (hasSecret) {
101
- console.log(c.green('') + ' CLOUDFLARE_DEPLOY_HOOK_URL is set');
102
- const rotate = await prompt('\nRotate the deploy hook URL? [y/N]: ');
105
+ if (hasSingular || hasPlural) {
106
+ const which = hasPlural ? 'CLOUDFLARE_DEPLOY_HOOK_URLS' : 'CLOUDFLARE_DEPLOY_HOOK_URL';
107
+ console.log(c.green('✓') + ` ${which} is set`);
108
+ const rotate = await prompt('\nRotate / re-enter the deploy hook URL(s)? [y/N]: ');
103
109
  if (!/^y(es)?$/i.test(rotate.trim())) {
104
110
  console.log(c.gray('\nNothing to do. The daily deploy is configured.\n'));
105
111
  await offerTestRun(repo);
@@ -107,28 +113,51 @@ export async function runDeploySetup() {
107
113
  }
108
114
  }
109
115
 
110
- // Prompt for URL.
116
+ // Prompt for URL(s).
111
117
  console.log('');
112
- console.log('Get a deploy hook URL from Cloudflare Pages:');
118
+ console.log('Get a deploy hook URL from Cloudflare Pages (one per Pages project):');
113
119
  console.log(c.gray(' 1. Open Cloudflare dashboard → Pages → ' + (pagesName ?? 'your project') + ' → Settings'));
114
120
  console.log(c.gray(' 2. Builds & deployments → Deploy hooks → "Add deploy hook"'));
115
121
  console.log(c.gray(' 3. Name: "daily-scheduled-content" — Branch: main'));
116
122
  console.log(c.gray(' 4. Copy the URL'));
123
+ console.log(c.gray(' Multi-site monorepo: repeat for every Pages project and paste all URLs comma-separated.'));
117
124
  console.log('');
118
125
 
119
- const url = (await prompt('Paste the deploy hook URL: ')).trim();
120
- if (!url.startsWith('https://api.cloudflare.com/client/v4/pages/webhooks/deploy_hooks/')) {
121
- console.error(c.red('\n✗ That doesn\'t look like a Cloudflare Pages deploy hook URL.'));
122
- console.error(' Expected prefix: https://api.cloudflare.com/client/v4/pages/webhooks/deploy_hooks/');
126
+ const raw = (await prompt('Paste the deploy hook URL(s), comma-separated: ')).trim();
127
+ const urls = raw.split(',').map((u) => u.trim()).filter(Boolean);
128
+ const prefix = 'https://api.cloudflare.com/client/v4/pages/webhooks/deploy_hooks/';
129
+ if (urls.length === 0) {
130
+ console.error(c.red('\n✗ No deploy hook URL provided.'));
131
+ process.exit(1);
132
+ }
133
+ const bad = urls.filter((u) => !u.startsWith(prefix));
134
+ if (bad.length > 0) {
135
+ console.error(c.red('\n✗ These don\'t look like Cloudflare Pages deploy hook URLs:'));
136
+ bad.forEach((u) => console.error(' ' + u));
137
+ console.error(' Expected prefix: ' + prefix);
123
138
  process.exit(1);
124
139
  }
125
140
 
126
- const setOk = await setSecret(repo, 'CLOUDFLARE_DEPLOY_HOOK_URL', url);
141
+ // One URL singular secret (back-compat). Multiple → plural secret.
142
+ const secretName = urls.length > 1 ? 'CLOUDFLARE_DEPLOY_HOOK_URLS' : 'CLOUDFLARE_DEPLOY_HOOK_URL';
143
+ const setOk = await setSecret(repo, secretName, urls.join(','));
127
144
  if (!setOk) {
128
145
  console.error(c.red('\n✗ Failed to set GitHub secret.'));
129
146
  process.exit(1);
130
147
  }
131
- console.log(c.green('✓') + ` Secret CLOUDFLARE_DEPLOY_HOOK_URL set in ${repo}`);
148
+ console.log(
149
+ c.green('✓') + ` Secret ${secretName} set in ${repo}` +
150
+ (urls.length > 1 ? ` (${urls.length} hooks)` : '')
151
+ );
152
+
153
+ // Switching singular → plural: the stale singular secret is ignored by the
154
+ // workflow (plural wins) but is confusing. Remove it.
155
+ if (urls.length > 1 && hasSingular) {
156
+ const cleaned = await deleteSecret(repo, 'CLOUDFLARE_DEPLOY_HOOK_URL');
157
+ if (cleaned) {
158
+ console.log(c.gray(' Removed now-redundant CLOUDFLARE_DEPLOY_HOOK_URL (plural secret takes precedence).'));
159
+ }
160
+ }
132
161
 
133
162
  await offerTestRun(repo);
134
163
  }
@@ -236,6 +265,11 @@ async function setSecret(repo, name, value) {
236
265
  return r.code === 0;
237
266
  }
238
267
 
268
+ async function deleteSecret(repo, name) {
269
+ const r = await runGh(['secret', 'delete', name, '--repo', repo]);
270
+ return r.code === 0;
271
+ }
272
+
239
273
  function runGh(args) {
240
274
  return new Promise((resolve) => {
241
275
  const proc = spawn('gh', args, { stdio: ['ignore', 'pipe', 'pipe'] });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ibalzam/codejitsu-core",
3
- "version": "0.13.0",
3
+ "version": "0.13.1",
4
4
  "type": "module",
5
5
  "description": "Shared core for Codejitsu Astro sites — reusable code and Claude-facing instructions for blog, SEO, images, deploy, and llms.txt.",
6
6
  "keywords": [