create-goodfellow 0.1.0 → 0.2.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/README.md +2 -2
- package/dist/cli.js +36 -3
- package/dist/index.d.ts +6 -79
- package/dist/index.js +2 -2
- package/dist/scaffold-BGxzPdN1.js +150 -0
- package/package.json +6 -6
- package/templates/next/.github/workflows/deploy.yml +37 -2
- package/templates/next/.gitlab-ci.yml +12 -2
- package/templates/next/README.md +14 -7
- package/templates/next/app/admin/layout.tsx +2 -2
- package/templates/parish/.github/workflows/deploy.yml +37 -2
- package/templates/parish/.gitlab-ci.yml +12 -2
- package/templates/parish/README.md +14 -7
- package/templates/parish/content/collections/events/_collection.json +19 -20
- package/templates/parish/content/collections/events/advent-penance-service.json +5 -3
- package/templates/parish/content/collections/events/all-souls.json +5 -3
- package/templates/parish/content/collections/events/christmas-concert.json +5 -3
- package/templates/parish/content/collections/events/fall-festival.json +5 -3
- package/templates/parish/content/collections/events/youth-group.json +23 -0
- package/templates/parish/content/collections/news/_collection.json +24 -0
- package/templates/parish/content/collections/news/bell-tower-repairs.json +13 -0
- package/templates/parish/content/collections/news/choir-new-voices.json +14 -0
- package/templates/parish/content/collections/news/food-drive.json +4 -1
- package/templates/parish/content/collections/news/new-website.json +4 -1
- package/templates/parish/content/collections/news/winter-coats.json +13 -0
- package/templates/parish/content/collections/news/youth-group.json +4 -1
- package/templates/parish/content/collections/news/youth-service-day.json +14 -0
- package/templates/parish/content/menus.json +4 -0
- package/templates/parish/content/pages/about.json +48 -5
- package/templates/parish/content/pages/calendar.json +59 -0
- package/templates/parish/content/pages/events.json +27 -11
- package/templates/parish/content/pages/index.json +4 -37
- package/templates/parish/content/pages/news.json +14 -1
- package/templates/parish/content/site.json +1 -0
- package/templates/parish/vercel.ts +65 -0
- package/templates/registry/custom-html.json +1 -1
- package/templates/registry/registry.json +11 -11
- package/templates/registry/shadcn-call-to-action.json +1 -1
- package/templates/registry/shadcn-cards.json +1 -1
- package/templates/registry/shadcn-carousel.json +1 -1
- package/templates/registry/shadcn-faq.json +1 -1
- package/templates/registry/shadcn-hero.json +1 -1
- package/templates/registry/shadcn-notice.json +1 -1
- package/templates/registry/shadcn-pricing.json +1 -1
- package/templates/registry/shadcn-tabs.json +1 -1
- package/templates/registry/shadcn-testimonials.json +1 -1
- package/templates/starter/.github/workflows/deploy.yml +37 -2
- package/templates/starter/.gitlab-ci.yml +12 -2
- package/templates/starter/README.md +14 -7
- package/templates/starter/vercel.ts +65 -0
- package/templates/versions.json +11 -11
- package/dist/scaffold-4nOF_fOJ.js +0 -207
- package/templates/next/tsconfig.tsbuildinfo +0 -1
- package/templates/parish/vercel.json +0 -7
- package/templates/starter/vercel.json +0 -7
|
@@ -257,7 +257,7 @@
|
|
|
257
257
|
"className": "",
|
|
258
258
|
"collection": "events",
|
|
259
259
|
"columns": "3",
|
|
260
|
-
"dateField": "
|
|
260
|
+
"dateField": "when",
|
|
261
261
|
"emptyText": "Nothing on the calendar right now. Check back soon.",
|
|
262
262
|
"imageField": "image",
|
|
263
263
|
"layout": "cards",
|
|
@@ -280,42 +280,6 @@
|
|
|
280
280
|
"backgroundImage": "",
|
|
281
281
|
"className": "",
|
|
282
282
|
"content": [
|
|
283
|
-
{
|
|
284
|
-
"type": "Flex",
|
|
285
|
-
"props": {
|
|
286
|
-
"id": "Flex-news",
|
|
287
|
-
"align": "center",
|
|
288
|
-
"className": "mb-8",
|
|
289
|
-
"direction": "row",
|
|
290
|
-
"gap": "md",
|
|
291
|
-
"items": [
|
|
292
|
-
{
|
|
293
|
-
"type": "Heading",
|
|
294
|
-
"props": {
|
|
295
|
-
"id": "Heading-news",
|
|
296
|
-
"align": "left",
|
|
297
|
-
"className": "",
|
|
298
|
-
"level": "h2",
|
|
299
|
-
"text": "Parish news"
|
|
300
|
-
}
|
|
301
|
-
},
|
|
302
|
-
{
|
|
303
|
-
"type": "Button",
|
|
304
|
-
"props": {
|
|
305
|
-
"id": "Button-news",
|
|
306
|
-
"className": "",
|
|
307
|
-
"href": "/news",
|
|
308
|
-
"label": "All news",
|
|
309
|
-
"newTab": false,
|
|
310
|
-
"size": "md",
|
|
311
|
-
"variant": "outline"
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
],
|
|
315
|
-
"justify": "between",
|
|
316
|
-
"wrap": true
|
|
317
|
-
}
|
|
318
|
-
},
|
|
319
283
|
{
|
|
320
284
|
"type": "CollectionList",
|
|
321
285
|
"props": {
|
|
@@ -325,9 +289,12 @@
|
|
|
325
289
|
"columns": "3",
|
|
326
290
|
"dateField": "date",
|
|
327
291
|
"emptyText": "No news yet.",
|
|
292
|
+
"heading": "Parish news",
|
|
328
293
|
"imageField": "image",
|
|
329
294
|
"layout": "list",
|
|
330
295
|
"limit": 3,
|
|
296
|
+
"moreHref": "/news",
|
|
297
|
+
"moreLabel": "All news",
|
|
331
298
|
"order": "newest",
|
|
332
299
|
"show": "all",
|
|
333
300
|
"summaryField": "summary"
|
|
@@ -24,16 +24,29 @@
|
|
|
24
24
|
"type": "CollectionList",
|
|
25
25
|
"props": {
|
|
26
26
|
"id": "CollectionList-news",
|
|
27
|
+
"allLabel": "All news",
|
|
28
|
+
"choicePages": "topics",
|
|
27
29
|
"className": "mt-8",
|
|
28
30
|
"collection": "news",
|
|
29
31
|
"columns": "3",
|
|
30
32
|
"dateField": "date",
|
|
31
33
|
"emptyText": "No news yet.",
|
|
34
|
+
"filterField": "",
|
|
35
|
+
"filterValue": "",
|
|
36
|
+
"heading": "",
|
|
32
37
|
"imageField": "image",
|
|
33
38
|
"layout": "cards",
|
|
34
|
-
"limit":
|
|
39
|
+
"limit": 6,
|
|
40
|
+
"moreHref": "",
|
|
41
|
+
"moreLabel": "",
|
|
42
|
+
"nextLabel": "Older",
|
|
35
43
|
"order": "default",
|
|
44
|
+
"pageTitle": "Page {number}",
|
|
45
|
+
"pagesLabel": "More news",
|
|
46
|
+
"paginate": true,
|
|
47
|
+
"previousLabel": "Newer",
|
|
36
48
|
"show": "all",
|
|
49
|
+
"sortField": "",
|
|
37
50
|
"summaryField": "summary"
|
|
38
51
|
}
|
|
39
52
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Vercel's settings for this site, which Vercel reads when it builds the site.
|
|
2
|
+
//
|
|
3
|
+
// Vercel also sends the redirects from Site Settings → Redirects (content/redirects.json) itself,
|
|
4
|
+
// so visitors go straight to the new address. Like the pages the build writes at old addresses
|
|
5
|
+
// for other hosts, a redirect from an address a page has is left out: pages always win.
|
|
6
|
+
// This file uses only Node's own modules, since Vercel may read it before installing packages.
|
|
7
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
|
|
10
|
+
export const config = {
|
|
11
|
+
framework: null,
|
|
12
|
+
installCommand: "npm ci",
|
|
13
|
+
buildCommand: "npx goodfellow build",
|
|
14
|
+
outputDirectory: "dist",
|
|
15
|
+
redirects: redirects(),
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
interface Redirect {
|
|
19
|
+
from: string;
|
|
20
|
+
to: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function readJson(file: string): Record<string, unknown> | undefined {
|
|
24
|
+
return existsSync(file) ? JSON.parse(readFileSync(file, "utf8")) : undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Every file in a folder and its folders, as paths inside it. */
|
|
28
|
+
function filesIn(dir: string): string[] {
|
|
29
|
+
if (!existsSync(dir)) return [];
|
|
30
|
+
return readdirSync(dir, { recursive: true, encoding: "utf8" }).map((file) => file.split("\\").join("/"));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The addresses that have pages: `content/pages/about.json` is /about, and each collection item has its own. */
|
|
34
|
+
function pageAddresses(): Set<string> {
|
|
35
|
+
const addresses = new Set<string>();
|
|
36
|
+
for (const file of filesIn("content/pages")) {
|
|
37
|
+
if (file.endsWith(".json")) addresses.add(`/${file.slice(0, -".json".length)}`.replace(/\/?index$/, "") || "/");
|
|
38
|
+
}
|
|
39
|
+
for (const id of filesIn("content/collections").filter((file) => file.endsWith("/_collection.json"))) {
|
|
40
|
+
const folder = id.slice(0, -"/_collection.json".length);
|
|
41
|
+
const pattern = readJson(join("content/collections", id))?.path;
|
|
42
|
+
if (typeof pattern !== "string") continue;
|
|
43
|
+
for (const file of readdirSync(join("content/collections", folder))) {
|
|
44
|
+
const slug = /^(.+)\.(json|md)$/.exec(file)?.[1];
|
|
45
|
+
if (slug && file !== "_collection.json") addresses.add(pattern.replace("{slug}", slug).replace(/\/+$/, ""));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return addresses;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** An address as Vercel matches it, with the characters its patterns use escaped. */
|
|
52
|
+
function source(path: string): string {
|
|
53
|
+
return encodeURI(path).replace(/[()[\]{}+*?:!]/g, "\\$&");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function redirects() {
|
|
57
|
+
const list = (readJson("content/redirects.json")?.redirects ?? []) as Redirect[];
|
|
58
|
+
const pages = pageAddresses();
|
|
59
|
+
return list.flatMap(({ from, to }) => {
|
|
60
|
+
const path = from.replace(/\/+$/, "");
|
|
61
|
+
if (pages.has(path)) return [];
|
|
62
|
+
// With and without a trailing slash, as the other hosts serve the page there.
|
|
63
|
+
return [path, `${path}/`].map((address) => ({ source: source(address), destination: to, permanent: true }));
|
|
64
|
+
});
|
|
65
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://ui.shadcn.com/schema/registry.json",
|
|
3
3
|
"name": "goodfellow",
|
|
4
|
-
"homepage": "https://github.com/
|
|
4
|
+
"homepage": "https://github.com/goodfellow-cms/goodfellow-cms",
|
|
5
5
|
"items": [
|
|
6
6
|
{
|
|
7
7
|
"name": "shadcn-hero",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"goodfellow": {
|
|
22
22
|
"category": "Page sections",
|
|
23
23
|
"recommended": true,
|
|
24
|
-
"version": "0.
|
|
24
|
+
"version": "0.2.0"
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
"dependencies": []
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"goodfellow": {
|
|
46
46
|
"category": "Page sections",
|
|
47
47
|
"recommended": true,
|
|
48
|
-
"version": "0.
|
|
48
|
+
"version": "0.2.0"
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
"dependencies": []
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"goodfellow": {
|
|
70
70
|
"category": "Page sections",
|
|
71
71
|
"recommended": true,
|
|
72
|
-
"version": "0.
|
|
72
|
+
"version": "0.2.0"
|
|
73
73
|
}
|
|
74
74
|
},
|
|
75
75
|
"dependencies": []
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"goodfellow": {
|
|
93
93
|
"category": "Page sections",
|
|
94
94
|
"recommended": true,
|
|
95
|
-
"version": "0.
|
|
95
|
+
"version": "0.2.0"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"dependencies": []
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
"meta": {
|
|
120
120
|
"goodfellow": {
|
|
121
121
|
"category": "Page sections",
|
|
122
|
-
"version": "0.
|
|
122
|
+
"version": "0.2.0"
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
125
|
},
|
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
"goodfellow": {
|
|
145
145
|
"category": "Page sections",
|
|
146
146
|
"recommended": true,
|
|
147
|
-
"version": "0.
|
|
147
|
+
"version": "0.2.0"
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
},
|
|
@@ -170,7 +170,7 @@
|
|
|
170
170
|
"meta": {
|
|
171
171
|
"goodfellow": {
|
|
172
172
|
"category": "Advanced",
|
|
173
|
-
"version": "0.
|
|
173
|
+
"version": "0.2.0"
|
|
174
174
|
}
|
|
175
175
|
},
|
|
176
176
|
"dependencies": []
|
|
@@ -197,7 +197,7 @@
|
|
|
197
197
|
"goodfellow": {
|
|
198
198
|
"category": "Interactive",
|
|
199
199
|
"recommended": true,
|
|
200
|
-
"version": "0.
|
|
200
|
+
"version": "0.2.0"
|
|
201
201
|
}
|
|
202
202
|
},
|
|
203
203
|
"dependencies": []
|
|
@@ -224,7 +224,7 @@
|
|
|
224
224
|
"goodfellow": {
|
|
225
225
|
"category": "Interactive",
|
|
226
226
|
"recommended": true,
|
|
227
|
-
"version": "0.
|
|
227
|
+
"version": "0.2.0"
|
|
228
228
|
}
|
|
229
229
|
},
|
|
230
230
|
"dependencies": []
|
|
@@ -250,7 +250,7 @@
|
|
|
250
250
|
"meta": {
|
|
251
251
|
"goodfellow": {
|
|
252
252
|
"category": "Interactive",
|
|
253
|
-
"version": "0.
|
|
253
|
+
"version": "0.2.0"
|
|
254
254
|
}
|
|
255
255
|
},
|
|
256
256
|
"dependencies": []
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# Builds the site and publishes it with GitHub Pages whenever the main branch
|
|
2
2
|
# changes (including every Publish in the admin panel), and once a night so
|
|
3
|
-
# date-based content stays current.
|
|
4
|
-
#
|
|
3
|
+
# date-based content stays current. Each night it first installs Goodfellow's
|
|
4
|
+
# newest fixes, and publishes them only if the site still builds with them,
|
|
5
|
+
# unless content/updates.json turns automatic updates off.
|
|
6
|
+
# In the repository's Settings → Pages, set "Source" to "GitHub Actions".
|
|
7
|
+
# See README.md.
|
|
5
8
|
name: Deploy to GitHub Pages
|
|
6
9
|
|
|
7
10
|
on:
|
|
@@ -10,6 +13,11 @@ on:
|
|
|
10
13
|
schedule:
|
|
11
14
|
- cron: "17 4 * * *"
|
|
12
15
|
workflow_dispatch:
|
|
16
|
+
inputs:
|
|
17
|
+
update:
|
|
18
|
+
description: "Update Goodfellow first: fixes, latest, or a release such as 0.5.0 (an older one goes back to it). Leave empty to only build."
|
|
19
|
+
required: false
|
|
20
|
+
default: ""
|
|
13
21
|
|
|
14
22
|
permissions:
|
|
15
23
|
contents: read
|
|
@@ -21,10 +29,36 @@ concurrency:
|
|
|
21
29
|
cancel-in-progress: false
|
|
22
30
|
|
|
23
31
|
jobs:
|
|
32
|
+
update:
|
|
33
|
+
# Each night, and when the admin panel's Updates screen asks.
|
|
34
|
+
if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.update != '')
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
permissions:
|
|
37
|
+
contents: write
|
|
38
|
+
outputs:
|
|
39
|
+
sha: ${{ steps.update.outputs.sha }}
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v5
|
|
42
|
+
- uses: actions/setup-node@v5
|
|
43
|
+
with:
|
|
44
|
+
node-version: 22
|
|
45
|
+
cache: npm
|
|
46
|
+
- run: npm ci
|
|
47
|
+
- id: update
|
|
48
|
+
env:
|
|
49
|
+
TO: ${{ inputs.update || 'automatic' }}
|
|
50
|
+
run: npx goodfellow update --publish --to "$TO"
|
|
51
|
+
|
|
24
52
|
build:
|
|
53
|
+
needs: update
|
|
54
|
+
# Also when there was no update to run, or it didn't work: the site is built either way.
|
|
55
|
+
if: ${{ !cancelled() }}
|
|
25
56
|
runs-on: ubuntu-latest
|
|
26
57
|
steps:
|
|
27
58
|
- uses: actions/checkout@v5
|
|
59
|
+
with:
|
|
60
|
+
# The update's commit, when it published one.
|
|
61
|
+
ref: ${{ needs.update.outputs.sha || github.sha }}
|
|
28
62
|
- uses: actions/setup-node@v5
|
|
29
63
|
with:
|
|
30
64
|
node-version: 22
|
|
@@ -40,6 +74,7 @@ jobs:
|
|
|
40
74
|
|
|
41
75
|
deploy:
|
|
42
76
|
needs: build
|
|
77
|
+
if: ${{ !cancelled() && needs.build.result == 'success' }}
|
|
43
78
|
runs-on: ubuntu-latest
|
|
44
79
|
environment:
|
|
45
80
|
name: github-pages
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# Builds the site and publishes it with GitLab Pages whenever the default
|
|
2
|
-
# branch changes (including every Publish in the admin panel).
|
|
3
|
-
#
|
|
2
|
+
# branch changes (including every Publish in the admin panel). On scheduled
|
|
3
|
+
# pipelines, which the admin panel's Updates screen sets up to run each night,
|
|
4
|
+
# it first installs Goodfellow's newest fixes, unless content/updates.json turns
|
|
5
|
+
# automatic updates off (or the release GOODFELLOW_UPDATE names), and publishes
|
|
6
|
+
# them only if the site still builds with them. That
|
|
7
|
+
# needs "Allow Git push requests to the repository" under Settings → CI/CD →
|
|
8
|
+
# Job token permissions, which the Updates screen turns on. See README.md.
|
|
4
9
|
pages:
|
|
5
10
|
image: node:22
|
|
6
11
|
cache:
|
|
@@ -9,6 +14,11 @@ pages:
|
|
|
9
14
|
paths: [.npm/]
|
|
10
15
|
script:
|
|
11
16
|
- npm ci --cache .npm --prefer-offline
|
|
17
|
+
- |
|
|
18
|
+
if [ "$CI_PIPELINE_SOURCE" = "schedule" ] || [ -n "$GOODFELLOW_UPDATE" ]; then
|
|
19
|
+
git remote set-url origin "https://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_FQDN:-$CI_SERVER_HOST}/${CI_PROJECT_PATH}.git"
|
|
20
|
+
npx goodfellow update --publish --to "${GOODFELLOW_UPDATE:-automatic}" || echo "The update wasn't published; building the site as it is."
|
|
21
|
+
fi
|
|
12
22
|
# Pages may serve the site from a subfolder, such as https://group.gitlab.io/site/.
|
|
13
23
|
- export GOODFELLOW_BASE="$(node -e 'console.log(new URL(process.env.CI_PAGES_URL).pathname)')"
|
|
14
24
|
- npx goodfellow build
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# My site
|
|
2
2
|
|
|
3
|
-
A website built with [Goodfellow](https://github.com/
|
|
3
|
+
A website built with [Goodfellow](https://github.com/goodfellow-cms/goodfellow-cms). Pages are files in `content/`, edited in the admin panel at `/admin`, and published to a free static host.
|
|
4
4
|
|
|
5
5
|
## Working on the site
|
|
6
6
|
|
|
@@ -17,9 +17,9 @@ npm run preview # serves dist/
|
|
|
17
17
|
|
|
18
18
|
On your own computer, the admin panel saves straight to the files in `content/`.
|
|
19
19
|
|
|
20
|
-
More blocks, such as an FAQ, tabs and a pricing table, can be added on the admin panel's **Blocks** screen, which publishes their code into `blocks/installed/` and the shadcn components they use into `components/ui/`. See [
|
|
20
|
+
More blocks, such as an FAQ, tabs and a pricing table, can be added on the admin panel's **Blocks** screen, which publishes their code into `blocks/installed/` and the shadcn components they use into `components/ui/`. See [Add blocks](https://goodfellow-cms.github.io/goodfellow-cms/docs/add-blocks).
|
|
21
21
|
|
|
22
|
-
To add blocks of your own, write React components and add them to `blocks` in `goodfellow.config.tsx`. Parts that respond to visitors, such as a button that shows more, go in a file starting with `"use client"`, and only pages that use them load JavaScript: see [Interactive blocks](https://github.
|
|
22
|
+
To add blocks of your own, write React components and add them to `blocks` in `goodfellow.config.tsx`. Parts that respond to visitors, such as a button that shows more, go in a file starting with `"use client"`, and only pages that use them load JavaScript: see [Interactive blocks](https://goodfellow-cms.github.io/goodfellow-cms/docs/interactive-blocks).
|
|
23
23
|
|
|
24
24
|
The News page and its stories are an example of a collection: a group of similar items that share one page design. Change it or delete it under **Collections** in the admin panel.
|
|
25
25
|
|
|
@@ -29,7 +29,14 @@ Setup takes four steps. It's done once, usually by whoever set up the site.
|
|
|
29
29
|
|
|
30
30
|
### 1. Store the site on GitHub or GitLab
|
|
31
31
|
|
|
32
|
-
Create
|
|
32
|
+
Create an empty repository on GitHub or GitLab, without a README, and push this folder to it:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
git remote add origin https://github.com/your-name/your-site.git # unless origin is already set
|
|
36
|
+
git push -u origin main
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`create-goodfellow` makes the new site a git repository on the branch `main`, with everything, `package-lock.json` included, in its first commit, and sets `origin` when it's given the repository. Commit any changes since (`git add -A` and `git commit`) before pushing.
|
|
33
40
|
|
|
34
41
|
### 2. Tell Goodfellow where the site is stored
|
|
35
42
|
|
|
@@ -51,7 +58,7 @@ Also set the site's address under **Site settings → General → Site address**
|
|
|
51
58
|
|
|
52
59
|
### 3. Choose a host
|
|
53
60
|
|
|
54
|
-
Each host's free plan has its own rules about business use. If the site is for a business, sells anything, shows ads, or is built or looked after by someone who's paid for it, check those rules first. As of October 2026, GitHub Pages and Vercel's free plan don't allow most of these, and GitLab Pages has no rule against them that we know of. See [
|
|
61
|
+
Each host's free plan has its own rules about business use. If the site is for a business, sells anything, shows ads, or is built or looked after by someone who's paid for it, check those rules first. As of October 2026, GitHub Pages and Vercel's free plan don't allow most of these, and GitLab Pages has no rule against them that we know of. See [Hosts and business sites](https://goodfellow-cms.github.io/goodfellow-cms/docs/hosts) in Goodfellow's documentation.
|
|
55
62
|
|
|
56
63
|
**GitHub Pages** (free for public repositories; not for online businesses or shops)
|
|
57
64
|
|
|
@@ -66,7 +73,7 @@ Each host's free plan has its own rules about business use. If the site is for a
|
|
|
66
73
|
|
|
67
74
|
**Vercel** (works with GitHub and GitLab; the free plan is for non-commercial sites only)
|
|
68
75
|
|
|
69
|
-
1. Import the repository in Vercel. `vercel.
|
|
76
|
+
1. Import the repository in Vercel. `vercel.ts` already has the right settings, and has Vercel send the site's redirects itself.
|
|
70
77
|
2. For nightly rebuilds, create a deploy hook under **Settings → Git → Deploy Hooks**, and call it from a scheduled GitHub Action or GitLab pipeline schedule.
|
|
71
78
|
|
|
72
79
|
Delete the setup files for the hosts you don't use.
|
|
@@ -94,7 +101,7 @@ Upload images and files on the admin panel's **Media** screen, or with the **Cho
|
|
|
94
101
|
|
|
95
102
|
## Writing with AI
|
|
96
103
|
|
|
97
|
-
The admin panel's **AI** tab can draft pages, rewrite blocks and fill in news stories. Each editor picks an AI service there. Claude and some others need the editor's own API key and charge for use; there are free options, including copying the request into a chat app such as Claude.ai. To offer only some services, or to turn the assistant off, see `ai` in [
|
|
104
|
+
The admin panel's **AI** tab can draft pages, rewrite blocks and fill in news stories. Each editor picks an AI service there. Claude and some others need the editor's own API key and charge for use; there are free options, including copying the request into a chat app such as Claude.ai. To offer only some services, or to turn the assistant off, see `ai` in [Configuration](https://goodfellow-cms.github.io/goodfellow-cms/docs/configuration).
|
|
98
105
|
|
|
99
106
|
## Publishing
|
|
100
107
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Vercel's settings for this site, which Vercel reads when it builds the site.
|
|
2
|
+
//
|
|
3
|
+
// Vercel also sends the redirects from Site Settings → Redirects (content/redirects.json) itself,
|
|
4
|
+
// so visitors go straight to the new address. Like the pages the build writes at old addresses
|
|
5
|
+
// for other hosts, a redirect from an address a page has is left out: pages always win.
|
|
6
|
+
// This file uses only Node's own modules, since Vercel may read it before installing packages.
|
|
7
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
|
|
10
|
+
export const config = {
|
|
11
|
+
framework: null,
|
|
12
|
+
installCommand: "npm ci",
|
|
13
|
+
buildCommand: "npx goodfellow build",
|
|
14
|
+
outputDirectory: "dist",
|
|
15
|
+
redirects: redirects(),
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
interface Redirect {
|
|
19
|
+
from: string;
|
|
20
|
+
to: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function readJson(file: string): Record<string, unknown> | undefined {
|
|
24
|
+
return existsSync(file) ? JSON.parse(readFileSync(file, "utf8")) : undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Every file in a folder and its folders, as paths inside it. */
|
|
28
|
+
function filesIn(dir: string): string[] {
|
|
29
|
+
if (!existsSync(dir)) return [];
|
|
30
|
+
return readdirSync(dir, { recursive: true, encoding: "utf8" }).map((file) => file.split("\\").join("/"));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The addresses that have pages: `content/pages/about.json` is /about, and each collection item has its own. */
|
|
34
|
+
function pageAddresses(): Set<string> {
|
|
35
|
+
const addresses = new Set<string>();
|
|
36
|
+
for (const file of filesIn("content/pages")) {
|
|
37
|
+
if (file.endsWith(".json")) addresses.add(`/${file.slice(0, -".json".length)}`.replace(/\/?index$/, "") || "/");
|
|
38
|
+
}
|
|
39
|
+
for (const id of filesIn("content/collections").filter((file) => file.endsWith("/_collection.json"))) {
|
|
40
|
+
const folder = id.slice(0, -"/_collection.json".length);
|
|
41
|
+
const pattern = readJson(join("content/collections", id))?.path;
|
|
42
|
+
if (typeof pattern !== "string") continue;
|
|
43
|
+
for (const file of readdirSync(join("content/collections", folder))) {
|
|
44
|
+
const slug = /^(.+)\.(json|md)$/.exec(file)?.[1];
|
|
45
|
+
if (slug && file !== "_collection.json") addresses.add(pattern.replace("{slug}", slug).replace(/\/+$/, ""));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return addresses;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** An address as Vercel matches it, with the characters its patterns use escaped. */
|
|
52
|
+
function source(path: string): string {
|
|
53
|
+
return encodeURI(path).replace(/[()[\]{}+*?:!]/g, "\\$&");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function redirects() {
|
|
57
|
+
const list = (readJson("content/redirects.json")?.redirects ?? []) as Redirect[];
|
|
58
|
+
const pages = pageAddresses();
|
|
59
|
+
return list.flatMap(({ from, to }) => {
|
|
60
|
+
const path = from.replace(/\/+$/, "");
|
|
61
|
+
if (pages.has(path)) return [];
|
|
62
|
+
// With and without a trailing slash, as the other hosts serve the page there.
|
|
63
|
+
return [path, `${path}/`].map((address) => ({ source: source(address), destination: to, permanent: true }));
|
|
64
|
+
});
|
|
65
|
+
}
|
package/templates/versions.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"@goodfellow-cms/admin": "0.
|
|
3
|
-
"@goodfellow-cms/ai": "0.
|
|
4
|
-
"@goodfellow-cms/blocks": "0.
|
|
5
|
-
"goodfellow": "0.
|
|
6
|
-
"@goodfellow-cms/core": "0.
|
|
7
|
-
"create-goodfellow": "0.
|
|
8
|
-
"@goodfellow-cms/github": "0.
|
|
9
|
-
"@goodfellow-cms/gitlab": "0.
|
|
10
|
-
"@goodfellow-cms/next": "0.
|
|
11
|
-
"@goodfellow-cms/react": "0.
|
|
12
|
-
"@goodfellow-cms/registry": "0.
|
|
2
|
+
"@goodfellow-cms/admin": "0.2.0",
|
|
3
|
+
"@goodfellow-cms/ai": "0.2.0",
|
|
4
|
+
"@goodfellow-cms/blocks": "0.2.0",
|
|
5
|
+
"goodfellow": "0.2.0",
|
|
6
|
+
"@goodfellow-cms/core": "0.2.0",
|
|
7
|
+
"create-goodfellow": "0.2.0",
|
|
8
|
+
"@goodfellow-cms/github": "0.2.0",
|
|
9
|
+
"@goodfellow-cms/gitlab": "0.2.0",
|
|
10
|
+
"@goodfellow-cms/next": "0.2.0",
|
|
11
|
+
"@goodfellow-cms/react": "0.2.0",
|
|
12
|
+
"@goodfellow-cms/registry": "0.2.0"
|
|
13
13
|
}
|