create-goodfellow 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +15 -2
- package/bin/create-goodfellow.js +2 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +187 -0
- package/dist/index.d.ts +107 -0
- package/dist/index.js +2 -0
- package/dist/scaffold-4nOF_fOJ.js +207 -0
- package/package.json +55 -3
- package/templates/next/.github/workflows/deploy.yml +51 -0
- package/templates/next/.gitlab-ci.yml +18 -0
- package/templates/next/README.md +120 -0
- package/templates/next/_gitignore +5 -0
- package/templates/next/app/(site)/[[...path]]/page.tsx +8 -0
- package/templates/next/app/(site)/layout.tsx +5 -0
- package/templates/next/app/admin/demo-content.json/route.ts +5 -0
- package/templates/next/app/admin/layout.tsx +8 -0
- package/templates/next/app/admin/page.tsx +8 -0
- package/templates/next/app/layout.tsx +11 -0
- package/templates/next/app/not-found.tsx +4 -0
- package/templates/next/app/robots.ts +4 -0
- package/templates/next/app/site.css +11 -0
- package/templates/next/app/sitemap.ts +4 -0
- package/templates/next/blocks/installed/index.ts +7 -0
- package/templates/next/components.json +24 -0
- package/templates/next/content/collections/news/_collection.json +113 -0
- package/templates/next/content/collections/news/open-house.json +9 -0
- package/templates/next/content/collections/news/welcome.json +10 -0
- package/templates/next/content/layout/footer.json +55 -0
- package/templates/next/content/layout/header.json +54 -0
- package/templates/next/content/menus.json +25 -0
- package/templates/next/content/pages/404.json +73 -0
- package/templates/next/content/pages/about.json +59 -0
- package/templates/next/content/pages/index.json +215 -0
- package/templates/next/content/pages/news.json +55 -0
- package/templates/next/content/site.json +25 -0
- package/templates/next/content/styles/custom.css +4 -0
- package/templates/next/goodfellow.config.tsx +17 -0
- package/templates/next/lib/site.ts +5 -0
- package/templates/next/next.config.ts +7 -0
- package/templates/next/package.json +40 -0
- package/templates/next/postcss.config.mjs +3 -0
- package/templates/next/public/media/favicon.svg +1 -0
- package/templates/next/public/media/logo.svg +1 -0
- package/templates/next/public/media/placeholder.svg +1 -0
- package/templates/next/tsconfig.json +27 -0
- package/templates/next/tsconfig.tsbuildinfo +1 -0
- package/templates/parish/.github/workflows/deploy.yml +49 -0
- package/templates/parish/.gitlab-ci.yml +18 -0
- package/templates/parish/README.md +121 -0
- package/templates/parish/_gitignore +2 -0
- package/templates/parish/blocks/installed/index.ts +7 -0
- package/templates/parish/blocks/mass-times.tsx +59 -0
- package/templates/parish/blocks/notice.tsx +46 -0
- package/templates/parish/blocks/scripture.tsx +40 -0
- package/templates/parish/components.json +24 -0
- package/templates/parish/content/collections/bulletins/2026-09-27.json +8 -0
- package/templates/parish/content/collections/bulletins/2026-10-04.json +8 -0
- package/templates/parish/content/collections/bulletins/_collection.json +113 -0
- package/templates/parish/content/collections/events/_collection.json +157 -0
- package/templates/parish/content/collections/events/advent-penance-service.json +12 -0
- package/templates/parish/content/collections/events/all-souls.json +12 -0
- package/templates/parish/content/collections/events/christmas-concert.json +12 -0
- package/templates/parish/content/collections/events/fall-festival.json +12 -0
- package/templates/parish/content/collections/news/_collection.json +113 -0
- package/templates/parish/content/collections/news/food-drive.json +10 -0
- package/templates/parish/content/collections/news/new-website.json +10 -0
- package/templates/parish/content/collections/news/youth-group.json +10 -0
- package/templates/parish/content/collections/staff/_collection.json +34 -0
- package/templates/parish/content/collections/staff/david-okafor.json +9 -0
- package/templates/parish/content/collections/staff/fr-thomas-reed.json +9 -0
- package/templates/parish/content/collections/staff/maria-chen.json +9 -0
- package/templates/parish/content/collections/staff/sarah-lindqvist.json +9 -0
- package/templates/parish/content/layout/footer.json +142 -0
- package/templates/parish/content/layout/header.json +54 -0
- package/templates/parish/content/menus.json +63 -0
- package/templates/parish/content/pages/404.json +85 -0
- package/templates/parish/content/pages/about.json +110 -0
- package/templates/parish/content/pages/bulletins.json +64 -0
- package/templates/parish/content/pages/contact.json +180 -0
- package/templates/parish/content/pages/events.json +91 -0
- package/templates/parish/content/pages/index.json +423 -0
- package/templates/parish/content/pages/mass-times.json +168 -0
- package/templates/parish/content/pages/news.json +55 -0
- package/templates/parish/content/pages/sacraments.json +259 -0
- package/templates/parish/content/site.json +35 -0
- package/templates/parish/content/styles/custom.css +4 -0
- package/templates/parish/goodfellow.config.tsx +24 -0
- package/templates/parish/package.json +36 -0
- package/templates/parish/public/media/bulletin-example.pdf +32 -0
- package/templates/parish/public/media/candles.svg +22 -0
- package/templates/parish/public/media/church.svg +33 -0
- package/templates/parish/public/media/favicon.svg +5 -0
- package/templates/parish/public/media/gathering.svg +18 -0
- package/templates/parish/public/media/logo.svg +5 -0
- package/templates/parish/public/media/person.svg +5 -0
- package/templates/parish/public/media/placeholder.svg +5 -0
- package/templates/parish/src/styles.css +8 -0
- package/templates/parish/tsconfig.json +14 -0
- package/templates/parish/vercel.json +7 -0
- package/templates/registry/accordion.json +17 -0
- package/templates/registry/alert.json +16 -0
- package/templates/registry/badge.json +17 -0
- package/templates/registry/button.json +17 -0
- package/templates/registry/card.json +16 -0
- package/templates/registry/carousel.json +19 -0
- package/templates/registry/custom-html.json +31 -0
- package/templates/registry/registry.json +364 -0
- package/templates/registry/shadcn-call-to-action.json +26 -0
- package/templates/registry/shadcn-cards.json +26 -0
- package/templates/registry/shadcn-carousel.json +29 -0
- package/templates/registry/shadcn-faq.json +30 -0
- package/templates/registry/shadcn-hero.json +25 -0
- package/templates/registry/shadcn-notice.json +27 -0
- package/templates/registry/shadcn-pricing.json +28 -0
- package/templates/registry/shadcn-tabs.json +30 -0
- package/templates/registry/shadcn-testimonials.json +25 -0
- package/templates/registry/tabs.json +17 -0
- package/templates/starter/.github/workflows/deploy.yml +49 -0
- package/templates/starter/.gitlab-ci.yml +18 -0
- package/templates/starter/README.md +101 -0
- package/templates/starter/_gitignore +2 -0
- package/templates/starter/blocks/installed/index.ts +7 -0
- package/templates/starter/components.json +24 -0
- package/templates/starter/content/collections/news/_collection.json +113 -0
- package/templates/starter/content/collections/news/open-house.json +9 -0
- package/templates/starter/content/collections/news/welcome.json +10 -0
- package/templates/starter/content/layout/footer.json +55 -0
- package/templates/starter/content/layout/header.json +54 -0
- package/templates/starter/content/menus.json +25 -0
- package/templates/starter/content/pages/404.json +73 -0
- package/templates/starter/content/pages/about.json +59 -0
- package/templates/starter/content/pages/index.json +215 -0
- package/templates/starter/content/pages/news.json +55 -0
- package/templates/starter/content/site.json +25 -0
- package/templates/starter/content/styles/custom.css +4 -0
- package/templates/starter/goodfellow.config.tsx +17 -0
- package/templates/starter/package.json +36 -0
- package/templates/starter/public/media/favicon.svg +1 -0
- package/templates/starter/public/media/logo.svg +1 -0
- package/templates/starter/public/media/placeholder.svg +1 -0
- package/templates/starter/src/styles.css +8 -0
- package/templates/starter/tsconfig.json +14 -0
- package/templates/starter/vercel.json +7 -0
- package/templates/versions.json +13 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Goodfellow contributors
|
|
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.
|
package/README.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
-
#
|
|
1
|
+
# create-goodfellow
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Creates a new Goodfellow site from a starter, the parish example or the Next.js starter, and sets up where it's stored and where it's put online.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm create goodfellow@latest my-site
|
|
7
|
+
npm create goodfellow@latest my-site -- --template parish --host github-pages
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
[Goodfellow](https://github.com/chartung17/goodfellow-cms) is a website builder for people who aren't developers, with no server or database: pages are files in your own GitHub or GitLab repository, edited in a visual editor built on [Puck](https://puckeditor.com). To start a new site, run `npm create goodfellow@latest my-site`. See the [documentation](https://chartung17.github.io/goodfellow-cms/) for everything else.
|
|
11
|
+
|
|
12
|
+
More in the docs: [Create a site](https://chartung17.github.io/goodfellow-cms/docs/create-a-site).
|
|
13
|
+
|
|
14
|
+
## License
|
|
15
|
+
|
|
16
|
+
MIT
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { a as TEMPLATES, c as hostsFor, d as parseRepo, f as scaffold, i as ScaffoldError, r as HOSTS, t as BLOCK_CHOICES } from "./scaffold-4nOF_fOJ.js";
|
|
2
|
+
import { join, relative, resolve } from "node:path";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { stdin, stdout } from "node:process";
|
|
5
|
+
import { createInterface } from "node:readline/promises";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { parseArgs } from "node:util";
|
|
8
|
+
//#region src/cli.ts
|
|
9
|
+
const HELP = `Creates a new Goodfellow site.
|
|
10
|
+
|
|
11
|
+
Usage: npm create goodfellow@latest [folder] -- [options]
|
|
12
|
+
|
|
13
|
+
Options:
|
|
14
|
+
--template <name> starter (the default), parish, or next for a Next.js site
|
|
15
|
+
--github <repo> The GitHub repository the site will be stored in, such as your-name/your-site
|
|
16
|
+
--gitlab <project> The GitLab project the site will be stored in, such as your-group/your-site
|
|
17
|
+
--host <host> github-pages, gitlab-pages or vercel. Keeps only that host's setup file.
|
|
18
|
+
--blocks <choice> recommended (the default) to add the recommended blocks, or built-in for Goodfellow's own only
|
|
19
|
+
-y, --yes Don't ask anything: use the options given, and defaults for the rest
|
|
20
|
+
-h, --help Show this help
|
|
21
|
+
`;
|
|
22
|
+
/** Bundled into the package when it's built: see scripts/bundle-templates.mjs. */
|
|
23
|
+
const templatesDir = fileURLToPath(new URL("../templates/", import.meta.url));
|
|
24
|
+
const HOST_NAMES = {
|
|
25
|
+
github: "GitHub",
|
|
26
|
+
gitlab: "GitLab"
|
|
27
|
+
};
|
|
28
|
+
async function choose(rl, question, choices) {
|
|
29
|
+
stdout.write(`\n${question}\n`);
|
|
30
|
+
choices.forEach((choice, index) => {
|
|
31
|
+
stdout.write(` ${index + 1}. ${choice.label}${choice.note ? `\n ${choice.note}` : ""}\n`);
|
|
32
|
+
});
|
|
33
|
+
for (;;) {
|
|
34
|
+
const answer = (await rl.question(`Choose 1-${choices.length} (1): `)).trim();
|
|
35
|
+
const choice = choices[answer === "" ? 0 : Number(answer) - 1];
|
|
36
|
+
if (choice) return choice.value;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function askRepo(rl, host) {
|
|
40
|
+
const example = host === "github" ? "your-name/your-site" : "your-group/your-site";
|
|
41
|
+
for (;;) {
|
|
42
|
+
const answer = await rl.question(`\nThe ${HOST_NAMES[host]} ${host === "github" ? "repository" : "project"}, such as ${example} (leave empty if it doesn't exist yet): `);
|
|
43
|
+
if (answer.trim() === "") return void 0;
|
|
44
|
+
const repo = parseRepo(host, answer);
|
|
45
|
+
if (repo) return repo;
|
|
46
|
+
stdout.write(`That doesn't look like a ${HOST_NAMES[host]} address. Type it as ${example}.\n`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function isTemplate(name) {
|
|
50
|
+
return Object.hasOwn(TEMPLATES, name);
|
|
51
|
+
}
|
|
52
|
+
function isHost(name) {
|
|
53
|
+
return Object.hasOwn(HOSTS, name);
|
|
54
|
+
}
|
|
55
|
+
function backendFromOptions(github, gitlab) {
|
|
56
|
+
if (github !== void 0 && gitlab !== void 0) throw new ScaffoldError("Choose either --github or --gitlab, not both.");
|
|
57
|
+
const host = github !== void 0 ? "github" : gitlab !== void 0 ? "gitlab" : void 0;
|
|
58
|
+
if (!host) return void 0;
|
|
59
|
+
const repo = parseRepo(host, github ?? gitlab);
|
|
60
|
+
if (!repo) throw new ScaffoldError(host === "github" ? "--github takes a repository such as your-name/your-site." : "--gitlab takes a project such as your-group/your-site.");
|
|
61
|
+
return {
|
|
62
|
+
host,
|
|
63
|
+
repo
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
async function main() {
|
|
67
|
+
const { positionals, values } = parseArgs({
|
|
68
|
+
allowPositionals: true,
|
|
69
|
+
options: {
|
|
70
|
+
template: { type: "string" },
|
|
71
|
+
github: { type: "string" },
|
|
72
|
+
gitlab: { type: "string" },
|
|
73
|
+
host: { type: "string" },
|
|
74
|
+
blocks: { type: "string" },
|
|
75
|
+
yes: {
|
|
76
|
+
type: "boolean",
|
|
77
|
+
short: "y"
|
|
78
|
+
},
|
|
79
|
+
help: {
|
|
80
|
+
type: "boolean",
|
|
81
|
+
short: "h"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
if (values.help) {
|
|
86
|
+
stdout.write(HELP);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
let folder = positionals[0];
|
|
90
|
+
let template = values.template;
|
|
91
|
+
if (template !== void 0 && !isTemplate(template)) throw new ScaffoldError(`There's no template called "${template}". Choose ${Object.keys(TEMPLATES).join(" or ")}.`);
|
|
92
|
+
let backend = backendFromOptions(values.github, values.gitlab);
|
|
93
|
+
let host = values.host;
|
|
94
|
+
if (host !== void 0 && !isHost(host)) throw new ScaffoldError(`--host takes ${Object.keys(HOSTS).join(", ")}.`);
|
|
95
|
+
let blocks = values.blocks;
|
|
96
|
+
if (blocks !== void 0 && !(blocks in BLOCK_CHOICES)) throw new ScaffoldError(`--blocks takes ${Object.keys(BLOCK_CHOICES).join(" or ")}.`);
|
|
97
|
+
if (stdin.isTTY && !values.yes) {
|
|
98
|
+
const rl = createInterface({
|
|
99
|
+
input: stdin,
|
|
100
|
+
output: stdout
|
|
101
|
+
});
|
|
102
|
+
try {
|
|
103
|
+
if (!folder) folder = (await rl.question("Folder for the new site (my-site): ")).trim() || "my-site";
|
|
104
|
+
template ??= await choose(rl, "Start from:", Object.entries(TEMPLATES).map(([value, { label, description }]) => ({
|
|
105
|
+
value,
|
|
106
|
+
label,
|
|
107
|
+
note: description
|
|
108
|
+
})));
|
|
109
|
+
let storage = backend?.host;
|
|
110
|
+
if (!backend) {
|
|
111
|
+
storage = await choose(rl, "Where will the site be stored? Editors sign in there, and publishing saves to it.", [
|
|
112
|
+
{
|
|
113
|
+
value: "github",
|
|
114
|
+
label: "GitHub"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
value: "gitlab",
|
|
118
|
+
label: "GitLab"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
value: void 0,
|
|
122
|
+
label: "Not decided yet"
|
|
123
|
+
}
|
|
124
|
+
]);
|
|
125
|
+
const repo = storage && await askRepo(rl, storage);
|
|
126
|
+
if (storage && repo) backend = {
|
|
127
|
+
host: storage,
|
|
128
|
+
repo
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
host ??= await chooseHost(rl, storage);
|
|
132
|
+
blocks ??= await choose(rl, "Which blocks should the editor offer to start with?", Object.entries(BLOCK_CHOICES).map(([value, { label, description }]) => ({
|
|
133
|
+
value,
|
|
134
|
+
label,
|
|
135
|
+
note: description
|
|
136
|
+
})));
|
|
137
|
+
} finally {
|
|
138
|
+
rl.close();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (!folder) throw new ScaffoldError("Say which folder to create the site in, such as: npm create goodfellow@latest my-site");
|
|
142
|
+
if (host && backend && !hostsFor(backend.host).includes(host)) throw new ScaffoldError(`${HOSTS[host].label} can't serve a site stored on ${HOST_NAMES[backend.host]}.`);
|
|
143
|
+
const target = resolve(folder);
|
|
144
|
+
const versions = JSON.parse(readFileSync(join(templatesDir, "versions.json"), "utf8"));
|
|
145
|
+
await scaffold({
|
|
146
|
+
template: join(templatesDir, template ?? "starter"),
|
|
147
|
+
target,
|
|
148
|
+
versions,
|
|
149
|
+
...backend && { backend },
|
|
150
|
+
...host && { host },
|
|
151
|
+
...(blocks ?? "recommended") === "recommended" && { registryDir: join(templatesDir, "registry") }
|
|
152
|
+
});
|
|
153
|
+
const where = relative(process.cwd(), target) || ".";
|
|
154
|
+
const lines = [
|
|
155
|
+
"",
|
|
156
|
+
`Created a new site in ${where}.`,
|
|
157
|
+
"",
|
|
158
|
+
"Next:",
|
|
159
|
+
...where === "." ? [] : [` cd ${/\s/.test(where) ? JSON.stringify(where) : where}`],
|
|
160
|
+
" npm install",
|
|
161
|
+
" npm run dev",
|
|
162
|
+
"",
|
|
163
|
+
"Then open http://localhost:4321/admin to start editing. README.md explains how to put the site online."
|
|
164
|
+
];
|
|
165
|
+
if (!backend) lines.push("Before you do, say where the site is stored in goodfellow.config.tsx; README.md shows how.");
|
|
166
|
+
stdout.write(`${lines.join("\n")}\n`);
|
|
167
|
+
}
|
|
168
|
+
async function chooseHost(rl, storage) {
|
|
169
|
+
stdout.write("\nEach host's free plan has its own rules about business use. Check them if the site is for a business,");
|
|
170
|
+
stdout.write("\nsells anything, shows ads, or anyone is paid to build or look after it.\n");
|
|
171
|
+
return choose(rl, "Where will the site be hosted?", [...hostsFor(storage).map((value) => ({
|
|
172
|
+
value,
|
|
173
|
+
label: HOSTS[value].label,
|
|
174
|
+
note: HOSTS[value].note
|
|
175
|
+
})), {
|
|
176
|
+
value: void 0,
|
|
177
|
+
label: "Not decided yet",
|
|
178
|
+
note: "Keeps the setup files for every host."
|
|
179
|
+
}]);
|
|
180
|
+
}
|
|
181
|
+
main().catch((error) => {
|
|
182
|
+
if (error.code === "ABORT_ERR") process.exit(130);
|
|
183
|
+
console.error(error instanceof ScaffoldError ? error.message : error);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
});
|
|
186
|
+
//#endregion
|
|
187
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
//#region src/scaffold.d.ts
|
|
2
|
+
/** The sites a new site can start from. The keys are what `--template` takes. */
|
|
3
|
+
export declare const TEMPLATES: {
|
|
4
|
+
readonly starter: {
|
|
5
|
+
readonly label: "Starter";
|
|
6
|
+
readonly description: "A simple site with a few pages and a news section";
|
|
7
|
+
};
|
|
8
|
+
readonly parish: {
|
|
9
|
+
readonly label: "Parish example";
|
|
10
|
+
readonly description: "A made-up parish with Mass times, events, news, bulletins and staff, and blocks of its own";
|
|
11
|
+
};
|
|
12
|
+
readonly next: {
|
|
13
|
+
readonly label: "Starter for Next.js";
|
|
14
|
+
readonly description: "The starter as a Next.js site, for developers who want their own Next.js pages beside it";
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
type TemplateName = keyof typeof TEMPLATES;
|
|
18
|
+
/** Where the site's repository is stored, which the admin panel publishes to. */
|
|
19
|
+
interface Backend {
|
|
20
|
+
host: "github" | "gitlab";
|
|
21
|
+
/** `owner/name` on GitHub, or the project's path on GitLab, such as `group/subgroup/site`. */
|
|
22
|
+
repo: string;
|
|
23
|
+
}
|
|
24
|
+
/** Where the built site is served from. Each has its own setup file in the template. */
|
|
25
|
+
export declare const HOSTS: {
|
|
26
|
+
readonly "github-pages": {
|
|
27
|
+
readonly label: "GitHub Pages";
|
|
28
|
+
readonly files: readonly [".github/workflows/deploy.yml"];
|
|
29
|
+
readonly note: "Free for public repositories. Not allowed for online businesses or shops.";
|
|
30
|
+
};
|
|
31
|
+
readonly "gitlab-pages": {
|
|
32
|
+
readonly label: "GitLab Pages";
|
|
33
|
+
readonly files: readonly [".gitlab-ci.yml"];
|
|
34
|
+
readonly note: "Free, including for private projects. No rule against business sites that we know of.";
|
|
35
|
+
};
|
|
36
|
+
readonly vercel: {
|
|
37
|
+
readonly label: "Vercel";
|
|
38
|
+
readonly files: readonly ["vercel.json"];
|
|
39
|
+
readonly note: "Works with GitHub and GitLab. The free plan is for non-commercial sites only.";
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
type Host = keyof typeof HOSTS;
|
|
43
|
+
/** The hosts that can serve a site stored with `backend`: GitHub Pages only builds GitHub repositories, and so on. */
|
|
44
|
+
export declare function hostsFor(backend: Backend["host"] | undefined): Host[];
|
|
45
|
+
/** A problem the person creating the site can fix, described in plain words. */
|
|
46
|
+
export declare class ScaffoldError extends Error {
|
|
47
|
+
name: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* npm leaves `.gitignore` files out of published packages, so templates are
|
|
51
|
+
* bundled with `_gitignore` instead, and it's renamed back in the new site.
|
|
52
|
+
*/
|
|
53
|
+
export declare const BUNDLED_GITIGNORE = "_gitignore";
|
|
54
|
+
/**
|
|
55
|
+
* Copies a template's files, leaving out installed packages and build output.
|
|
56
|
+
* `bundling` renames `.gitignore` files for publishing; otherwise they're renamed back.
|
|
57
|
+
*/
|
|
58
|
+
export declare function copyTemplate(from: string, to: string, { bundling }?: {
|
|
59
|
+
bundling?: boolean | undefined;
|
|
60
|
+
}): Promise<void>;
|
|
61
|
+
/** A valid npm package name from a folder name, such as `My Parish` → `my-parish`. */
|
|
62
|
+
export declare function packageName(folder: string): string;
|
|
63
|
+
/**
|
|
64
|
+
* Reads a repository from what someone typed: `owner/name`, or its address on
|
|
65
|
+
* GitHub or GitLab. Returns `undefined` if it isn't one.
|
|
66
|
+
*/
|
|
67
|
+
export declare function parseRepo(host: Backend["host"], input: string): string | undefined;
|
|
68
|
+
/** The template's `package.json`, named for the new site and with workspace links replaced by published versions. */
|
|
69
|
+
export declare function sitePackageJson(source: string, name: string, versions: Record<string, string>): string;
|
|
70
|
+
/** Turns on one of the commented-out `backend` lines in a template's `goodfellow.config.tsx`. */
|
|
71
|
+
export declare function configureBackend(config: string, backend: Backend): string;
|
|
72
|
+
/** A site's README without the notes for people reading it in the Goodfellow repository. */
|
|
73
|
+
export declare function withoutRepositoryNotes(readme: string): string;
|
|
74
|
+
/** Which blocks a new site starts with, besides the built-in ones. */
|
|
75
|
+
export declare const BLOCK_CHOICES: {
|
|
76
|
+
readonly recommended: {
|
|
77
|
+
readonly label: "Recommended";
|
|
78
|
+
readonly description: "Adds the recommended blocks, such as Hero, Cards and FAQ. Others can be added in the admin panel.";
|
|
79
|
+
};
|
|
80
|
+
readonly "built-in": {
|
|
81
|
+
readonly label: "Built-in blocks only";
|
|
82
|
+
readonly description: "Starts with Goodfellow's own blocks. More can be added in the admin panel.";
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
type BlockChoice = keyof typeof BLOCK_CHOICES;
|
|
86
|
+
/**
|
|
87
|
+
* Installs the registry's recommended blocks into a new site, as the admin
|
|
88
|
+
* panel's Blocks screen would, so the site can add and remove them there later.
|
|
89
|
+
*/
|
|
90
|
+
export declare function installRecommendedBlocks(target: string, registryDir: string): Promise<string[]>;
|
|
91
|
+
interface ScaffoldOptions {
|
|
92
|
+
/** The template's folder. */
|
|
93
|
+
template: string;
|
|
94
|
+
/** The new site's folder. It must be empty or not exist yet. */
|
|
95
|
+
target: string;
|
|
96
|
+
/** Versions of Goodfellow's packages, by name, to replace the template's workspace links. */
|
|
97
|
+
versions: Record<string, string>;
|
|
98
|
+
backend?: Backend;
|
|
99
|
+
/** Keeps only this host's setup file. All are kept if it isn't set. */
|
|
100
|
+
host?: Host;
|
|
101
|
+
/** The built block registry to install recommended blocks from. Without it, the site has the built-in blocks only. */
|
|
102
|
+
registryDir?: string;
|
|
103
|
+
}
|
|
104
|
+
/** Creates a new site in `target` from a template. */
|
|
105
|
+
export declare function scaffold({ template, target, versions, backend, host, registryDir }: ScaffoldOptions): Promise<void>;
|
|
106
|
+
//#endregion
|
|
107
|
+
export type { Backend, BlockChoice, Host, ScaffoldOptions, TemplateName };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as TEMPLATES, c as hostsFor, d as parseRepo, f as scaffold, i as ScaffoldError, l as installRecommendedBlocks, m as withoutRepositoryNotes, n as BUNDLED_GITIGNORE, o as configureBackend, p as sitePackageJson, r as HOSTS, s as copyTemplate, t as BLOCK_CHOICES, u as packageName } from "./scaffold-4nOF_fOJ.js";
|
|
2
|
+
export { BLOCK_CHOICES, BUNDLED_GITIGNORE, HOSTS, ScaffoldError, TEMPLATES, configureBackend, copyTemplate, hostsFor, installRecommendedBlocks, packageName, parseRepo, scaffold, sitePackageJson, withoutRepositoryNotes };
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { cp, mkdir, readFile, readdir, rename, rm, rmdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { EMPTY_RECORD, GOODFELLOW_REGISTRY, availableBlocks, planInstall } from "@goodfellow-cms/core";
|
|
4
|
+
//#region src/scaffold.ts
|
|
5
|
+
/** The sites a new site can start from. The keys are what `--template` takes. */
|
|
6
|
+
const TEMPLATES = {
|
|
7
|
+
starter: {
|
|
8
|
+
label: "Starter",
|
|
9
|
+
description: "A simple site with a few pages and a news section"
|
|
10
|
+
},
|
|
11
|
+
parish: {
|
|
12
|
+
label: "Parish example",
|
|
13
|
+
description: "A made-up parish with Mass times, events, news, bulletins and staff, and blocks of its own"
|
|
14
|
+
},
|
|
15
|
+
next: {
|
|
16
|
+
label: "Starter for Next.js",
|
|
17
|
+
description: "The starter as a Next.js site, for developers who want their own Next.js pages beside it"
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
/** Where the built site is served from. Each has its own setup file in the template. */
|
|
21
|
+
const HOSTS = {
|
|
22
|
+
"github-pages": {
|
|
23
|
+
label: "GitHub Pages",
|
|
24
|
+
files: [".github/workflows/deploy.yml"],
|
|
25
|
+
note: "Free for public repositories. Not allowed for online businesses or shops."
|
|
26
|
+
},
|
|
27
|
+
"gitlab-pages": {
|
|
28
|
+
label: "GitLab Pages",
|
|
29
|
+
files: [".gitlab-ci.yml"],
|
|
30
|
+
note: "Free, including for private projects. No rule against business sites that we know of."
|
|
31
|
+
},
|
|
32
|
+
vercel: {
|
|
33
|
+
label: "Vercel",
|
|
34
|
+
files: ["vercel.json"],
|
|
35
|
+
note: "Works with GitHub and GitLab. The free plan is for non-commercial sites only."
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
/** The hosts that can serve a site stored with `backend`: GitHub Pages only builds GitHub repositories, and so on. */
|
|
39
|
+
function hostsFor(backend) {
|
|
40
|
+
if (backend === "github") return ["github-pages", "vercel"];
|
|
41
|
+
if (backend === "gitlab") return ["gitlab-pages", "vercel"];
|
|
42
|
+
return [
|
|
43
|
+
"github-pages",
|
|
44
|
+
"gitlab-pages",
|
|
45
|
+
"vercel"
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
/** A problem the person creating the site can fix, described in plain words. */
|
|
49
|
+
var ScaffoldError = class extends Error {
|
|
50
|
+
name = "ScaffoldError";
|
|
51
|
+
};
|
|
52
|
+
/** Never copied, wherever they are: installed packages and build caches. */
|
|
53
|
+
const SKIPPED = /* @__PURE__ */ new Set(["node_modules", ".turbo"]);
|
|
54
|
+
/** Never copied from the template's own folder: build output, and files only the Goodfellow repository uses. */
|
|
55
|
+
const SKIPPED_AT_ROOT = /* @__PURE__ */ new Set([
|
|
56
|
+
"dist",
|
|
57
|
+
"out",
|
|
58
|
+
".next",
|
|
59
|
+
"next-env.d.ts",
|
|
60
|
+
"turbo.json"
|
|
61
|
+
]);
|
|
62
|
+
/**
|
|
63
|
+
* npm leaves `.gitignore` files out of published packages, so templates are
|
|
64
|
+
* bundled with `_gitignore` instead, and it's renamed back in the new site.
|
|
65
|
+
*/
|
|
66
|
+
const BUNDLED_GITIGNORE = "_gitignore";
|
|
67
|
+
/**
|
|
68
|
+
* Copies a template's files, leaving out installed packages and build output.
|
|
69
|
+
* `bundling` renames `.gitignore` files for publishing; otherwise they're renamed back.
|
|
70
|
+
*/
|
|
71
|
+
async function copyTemplate(from, to, { bundling = false } = {}) {
|
|
72
|
+
await cp(from, to, {
|
|
73
|
+
recursive: true,
|
|
74
|
+
filter: (source) => {
|
|
75
|
+
const path = relative(from, source).split(sep);
|
|
76
|
+
return !path.some((part) => SKIPPED.has(part)) && !SKIPPED_AT_ROOT.has(path[0] ?? "");
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
const [find, replace] = bundling ? [".gitignore", BUNDLED_GITIGNORE] : [BUNDLED_GITIGNORE, ".gitignore"];
|
|
80
|
+
for (const file of await readdir(to, { recursive: true })) if (basename(file) === find) await rename(join(to, file), join(to, file.slice(0, -find.length) + replace));
|
|
81
|
+
}
|
|
82
|
+
/** A valid npm package name from a folder name, such as `My Parish` → `my-parish`. */
|
|
83
|
+
function packageName(folder) {
|
|
84
|
+
return basename(resolve(folder)).toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[._-]+|[-]+$/g, "").slice(0, 214) || "my-site";
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Reads a repository from what someone typed: `owner/name`, or its address on
|
|
88
|
+
* GitHub or GitLab. Returns `undefined` if it isn't one.
|
|
89
|
+
*/
|
|
90
|
+
function parseRepo(host, input) {
|
|
91
|
+
let path = input.trim().replace(/\.git$/, "").replace(/\/+$/, "");
|
|
92
|
+
const prefix = host === "github" ? /^(?:https?:\/\/)?(?:www\.)?github\.com\//i : /^(?:https?:\/\/)?gitlab\.com\//i;
|
|
93
|
+
path = path.replace(prefix, "");
|
|
94
|
+
return (host === "github" ? /^[A-Za-z0-9-]+\/[A-Za-z0-9._-]+$/ : /^[A-Za-z0-9][A-Za-z0-9._-]*(?:\/[A-Za-z0-9._-]+)+$/).test(path) ? path : void 0;
|
|
95
|
+
}
|
|
96
|
+
/** The template's `package.json`, named for the new site and with workspace links replaced by published versions. */
|
|
97
|
+
function sitePackageJson(source, name, versions) {
|
|
98
|
+
const pkg = JSON.parse(source);
|
|
99
|
+
const result = {
|
|
100
|
+
...pkg,
|
|
101
|
+
name
|
|
102
|
+
};
|
|
103
|
+
for (const key of ["dependencies", "devDependencies"]) {
|
|
104
|
+
const deps = pkg[key];
|
|
105
|
+
if (!deps) continue;
|
|
106
|
+
result[key] = Object.fromEntries(Object.entries(deps).map(([dep, range]) => {
|
|
107
|
+
if (!range.startsWith("workspace:")) return [dep, range];
|
|
108
|
+
const version = versions[dep];
|
|
109
|
+
if (!version) throw new Error(`No version of ${dep} to use in the new site.`);
|
|
110
|
+
return [dep, `^${version}`];
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
return `${JSON.stringify(result, null, 2)}\n`;
|
|
114
|
+
}
|
|
115
|
+
/** Turns on one of the commented-out `backend` lines in a template's `goodfellow.config.tsx`. */
|
|
116
|
+
function configureBackend(config, backend) {
|
|
117
|
+
const importLine = `// import { ${backend.host} } from "@goodfellow-cms/${backend.host}";`;
|
|
118
|
+
const backendLine = new RegExp(`^([ \\t]*)// backend: ${backend.host}\\(.*$`, "m");
|
|
119
|
+
if (!config.includes(importLine) || !backendLine.test(config)) throw new Error(`The template's goodfellow.config.tsx has no ${backend.host} line to turn on.`);
|
|
120
|
+
const call = backend.host === "github" ? `github({ repo: ${JSON.stringify(backend.repo)} })` : `gitlab({ project: ${JSON.stringify(backend.repo)} })`;
|
|
121
|
+
return config.replace(importLine, importLine.slice(3)).replace(backendLine, `$1backend: ${call},`);
|
|
122
|
+
}
|
|
123
|
+
/** Notes in a site's README for people reading it in the Goodfellow repository, which new sites don't need. */
|
|
124
|
+
const REPOSITORY_NOTE = /<!-- goodfellow-repository -->[\s\S]*?<!-- \/goodfellow-repository -->\n*/g;
|
|
125
|
+
/** A site's README without the notes for people reading it in the Goodfellow repository. */
|
|
126
|
+
function withoutRepositoryNotes(readme) {
|
|
127
|
+
return readme.replace(REPOSITORY_NOTE, "");
|
|
128
|
+
}
|
|
129
|
+
/** Which blocks a new site starts with, besides the built-in ones. */
|
|
130
|
+
const BLOCK_CHOICES = {
|
|
131
|
+
recommended: {
|
|
132
|
+
label: "Recommended",
|
|
133
|
+
description: "Adds the recommended blocks, such as Hero, Cards and FAQ. Others can be added in the admin panel."
|
|
134
|
+
},
|
|
135
|
+
"built-in": {
|
|
136
|
+
label: "Built-in blocks only",
|
|
137
|
+
description: "Starts with Goodfellow's own blocks. More can be added in the admin panel."
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
/** Reads a built registry from disk, as the admin panel would read it from its address. */
|
|
141
|
+
function localRegistry(dir) {
|
|
142
|
+
const base = "https://registry.invalid/r/";
|
|
143
|
+
return {
|
|
144
|
+
registries: { [GOODFELLOW_REGISTRY]: `${base}{name}.json` },
|
|
145
|
+
fetchJson: async (url) => JSON.parse(await readFile(join(dir, url.slice(27)), "utf8"))
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Installs the registry's recommended blocks into a new site, as the admin
|
|
150
|
+
* panel's Blocks screen would, so the site can add and remove them there later.
|
|
151
|
+
*/
|
|
152
|
+
async function installRecommendedBlocks(target, registryDir) {
|
|
153
|
+
const { registries, fetchJson } = localRegistry(registryDir);
|
|
154
|
+
const recommended = (await availableBlocks(registries, fetchJson)).filter((block) => block.recommended);
|
|
155
|
+
const readSiteFile = (path) => readFile(join(target, path), "utf8").catch(() => void 0);
|
|
156
|
+
let record = EMPTY_RECORD;
|
|
157
|
+
for (const block of recommended) {
|
|
158
|
+
const plan = await planInstall({
|
|
159
|
+
ref: block.ref,
|
|
160
|
+
registries,
|
|
161
|
+
fetchJson,
|
|
162
|
+
readFile: readSiteFile,
|
|
163
|
+
record,
|
|
164
|
+
blocks: []
|
|
165
|
+
});
|
|
166
|
+
for (const change of plan.changes) {
|
|
167
|
+
if (!("content" in change)) continue;
|
|
168
|
+
await mkdir(dirname(join(target, change.path)), { recursive: true });
|
|
169
|
+
await writeFile(join(target, change.path), change.content);
|
|
170
|
+
}
|
|
171
|
+
record = plan.record;
|
|
172
|
+
}
|
|
173
|
+
return recommended.map((block) => block.title);
|
|
174
|
+
}
|
|
175
|
+
async function isEmptyFolder(path) {
|
|
176
|
+
try {
|
|
177
|
+
return (await readdir(path)).length === 0;
|
|
178
|
+
} catch (error) {
|
|
179
|
+
if (error.code === "ENOENT") return true;
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/** Creates a new site in `target` from a template. */
|
|
184
|
+
async function scaffold({ template, target, versions, backend, host, registryDir }) {
|
|
185
|
+
if (!await isEmptyFolder(target)) throw new ScaffoldError(`The folder ${target} already has files in it. Choose a new folder for the site.`);
|
|
186
|
+
await mkdir(target, { recursive: true });
|
|
187
|
+
await copyTemplate(template, target);
|
|
188
|
+
const pkgPath = join(target, "package.json");
|
|
189
|
+
await writeFile(pkgPath, sitePackageJson(await readFile(pkgPath, "utf8"), packageName(target), versions));
|
|
190
|
+
const readmePath = join(target, "README.md");
|
|
191
|
+
const readme = await readFile(readmePath, "utf8").catch(() => void 0);
|
|
192
|
+
if (readme !== void 0) await writeFile(readmePath, withoutRepositoryNotes(readme));
|
|
193
|
+
if (registryDir) await installRecommendedBlocks(target, registryDir);
|
|
194
|
+
if (backend) {
|
|
195
|
+
const configPath = join(target, "goodfellow.config.tsx");
|
|
196
|
+
await writeFile(configPath, configureBackend(await readFile(configPath, "utf8"), backend));
|
|
197
|
+
}
|
|
198
|
+
if (host) {
|
|
199
|
+
for (const [name, { files }] of Object.entries(HOSTS)) {
|
|
200
|
+
if (name === host) continue;
|
|
201
|
+
for (const file of files) await rm(join(target, file), { force: true });
|
|
202
|
+
}
|
|
203
|
+
for (const dir of [".github/workflows", ".github"]) if (await isEmptyFolder(join(target, dir))) await rmdir(join(target, dir)).catch(() => void 0);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
//#endregion
|
|
207
|
+
export { TEMPLATES as a, hostsFor as c, parseRepo as d, scaffold as f, ScaffoldError as i, installRecommendedBlocks as l, withoutRepositoryNotes as m, BUNDLED_GITIGNORE as n, configureBackend as o, sitePackageJson as p, HOSTS as r, copyTemplate as s, BLOCK_CHOICES as t, packageName as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,58 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-goodfellow",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Creates a new Goodfellow site: npm create goodfellow@latest my-site",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"goodfellow",
|
|
7
|
+
"cms",
|
|
8
|
+
"website-builder",
|
|
9
|
+
"puck",
|
|
10
|
+
"create",
|
|
11
|
+
"starter",
|
|
12
|
+
"scaffold"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://chartung17.github.io/goodfellow-cms/docs/create-a-site",
|
|
15
|
+
"bugs": "https://github.com/chartung17/goodfellow-cms/issues",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/chartung17/goodfellow-cms.git",
|
|
20
|
+
"directory": "packages/create-goodfellow"
|
|
21
|
+
},
|
|
22
|
+
"type": "module",
|
|
23
|
+
"bin": {
|
|
24
|
+
"create-goodfellow": "./bin/create-goodfellow.js"
|
|
25
|
+
},
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"import": "./dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./package.json": "./package.json"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"bin",
|
|
35
|
+
"dist",
|
|
36
|
+
"templates"
|
|
37
|
+
],
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=22"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@goodfellow-cms/core": "0.1.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@goodfellow-cms/example-parish": "0.0.0",
|
|
46
|
+
"@goodfellow-cms/registry": "0.1.0",
|
|
47
|
+
"@goodfellow-cms/template-starter": "0.0.0",
|
|
48
|
+
"@goodfellow-cms/template-next": "0.0.0"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsdown && node scripts/bundle-templates.mjs",
|
|
55
|
+
"typecheck": "tsc -p tsconfig.json",
|
|
56
|
+
"test": "vitest run"
|
|
57
|
+
}
|
|
6
58
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Builds the site and publishes it with GitHub Pages whenever the main branch
|
|
2
|
+
# changes (including every Publish in the admin panel), and once a night so
|
|
3
|
+
# date-based content stays current. In the repository's Settings → Pages, set
|
|
4
|
+
# "Source" to "GitHub Actions". See README.md.
|
|
5
|
+
name: Deploy to GitHub Pages
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
branches: [main]
|
|
10
|
+
schedule:
|
|
11
|
+
- cron: "17 4 * * *"
|
|
12
|
+
workflow_dispatch:
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
pages: write
|
|
17
|
+
id-token: write
|
|
18
|
+
|
|
19
|
+
concurrency:
|
|
20
|
+
group: pages
|
|
21
|
+
cancel-in-progress: false
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
build:
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v5
|
|
28
|
+
- uses: actions/setup-node@v5
|
|
29
|
+
with:
|
|
30
|
+
node-version: 22
|
|
31
|
+
cache: npm
|
|
32
|
+
- id: pages
|
|
33
|
+
uses: actions/configure-pages@v5
|
|
34
|
+
- run: npm ci
|
|
35
|
+
# Project sites live in a subfolder (/repository-name/); user and organization sites don't.
|
|
36
|
+
- run: npx next build
|
|
37
|
+
env:
|
|
38
|
+
GOODFELLOW_BASE: ${{ steps.pages.outputs.base_path }}/
|
|
39
|
+
- uses: actions/upload-pages-artifact@v4
|
|
40
|
+
with:
|
|
41
|
+
path: out
|
|
42
|
+
|
|
43
|
+
deploy:
|
|
44
|
+
needs: build
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
environment:
|
|
47
|
+
name: github-pages
|
|
48
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
49
|
+
steps:
|
|
50
|
+
- id: deployment
|
|
51
|
+
uses: actions/deploy-pages@v4
|