@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
|
-
'
|
|
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
|
|
102
|
+
const hasSingular = secrets.includes('CLOUDFLARE_DEPLOY_HOOK_URL');
|
|
103
|
+
const hasPlural = secrets.includes('CLOUDFLARE_DEPLOY_HOOK_URLS');
|
|
99
104
|
|
|
100
|
-
if (
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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": [
|