create-goodfellow 0.0.0-stage → 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/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 +220 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +2 -0
- package/dist/scaffold-BGxzPdN1.js +150 -0
- package/package.json +55 -3
- package/templates/next/.github/workflows/deploy.yml +86 -0
- package/templates/next/.gitlab-ci.yml +28 -0
- package/templates/next/README.md +127 -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/parish/.github/workflows/deploy.yml +84 -0
- package/templates/parish/.gitlab-ci.yml +28 -0
- package/templates/parish/README.md +128 -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 +156 -0
- package/templates/parish/content/collections/events/advent-penance-service.json +14 -0
- package/templates/parish/content/collections/events/all-souls.json +14 -0
- package/templates/parish/content/collections/events/christmas-concert.json +14 -0
- package/templates/parish/content/collections/events/fall-festival.json +14 -0
- package/templates/parish/content/collections/events/youth-group.json +23 -0
- package/templates/parish/content/collections/news/_collection.json +137 -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 +13 -0
- package/templates/parish/content/collections/news/new-website.json +13 -0
- package/templates/parish/content/collections/news/winter-coats.json +13 -0
- package/templates/parish/content/collections/news/youth-group.json +13 -0
- package/templates/parish/content/collections/news/youth-service-day.json +14 -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 +67 -0
- package/templates/parish/content/pages/404.json +85 -0
- package/templates/parish/content/pages/about.json +153 -0
- package/templates/parish/content/pages/bulletins.json +64 -0
- package/templates/parish/content/pages/calendar.json +59 -0
- package/templates/parish/content/pages/contact.json +180 -0
- package/templates/parish/content/pages/events.json +107 -0
- package/templates/parish/content/pages/index.json +390 -0
- package/templates/parish/content/pages/mass-times.json +168 -0
- package/templates/parish/content/pages/news.json +68 -0
- package/templates/parish/content/pages/sacraments.json +259 -0
- package/templates/parish/content/site.json +36 -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.ts +65 -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 +84 -0
- package/templates/starter/.gitlab-ci.yml +28 -0
- package/templates/starter/README.md +108 -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.ts +65 -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/goodfellow-cms/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://goodfellow-cms.github.io/goodfellow-cms/) for everything else.
|
|
11
|
+
|
|
12
|
+
More in the docs: [Create a site](https://goodfellow-cms.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,220 @@
|
|
|
1
|
+
import { a as TEMPLATES, d as installPackages, h as scaffold, i as ScaffoldError, l as hostsFor, o as commitAll, p as parseRepo, r as HOSTS, t as BLOCK_CHOICES, u as initGitRepository } from "./scaffold-BGxzPdN1.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
|
+
--no-install Don't run npm install
|
|
20
|
+
--no-git Don't make the site a git repository, or commit it
|
|
21
|
+
-y, --yes Don't ask anything: use the options given, and defaults for the rest
|
|
22
|
+
-h, --help Show this help
|
|
23
|
+
`;
|
|
24
|
+
/** Bundled into the package when it's built: see scripts/bundle-templates.mjs. */
|
|
25
|
+
const templatesDir = fileURLToPath(new URL("../templates/", import.meta.url));
|
|
26
|
+
const HOST_NAMES = {
|
|
27
|
+
github: "GitHub",
|
|
28
|
+
gitlab: "GitLab"
|
|
29
|
+
};
|
|
30
|
+
async function choose(rl, question, choices) {
|
|
31
|
+
stdout.write(`\n${question}\n`);
|
|
32
|
+
choices.forEach((choice, index) => {
|
|
33
|
+
stdout.write(` ${index + 1}. ${choice.label}${choice.note ? `\n ${choice.note}` : ""}\n`);
|
|
34
|
+
});
|
|
35
|
+
for (;;) {
|
|
36
|
+
const answer = (await rl.question(`Choose 1-${choices.length} (1): `)).trim();
|
|
37
|
+
const choice = choices[answer === "" ? 0 : Number(answer) - 1];
|
|
38
|
+
if (choice) return choice.value;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function askRepo(rl, host) {
|
|
42
|
+
const example = host === "github" ? "your-name/your-site" : "your-group/your-site";
|
|
43
|
+
for (;;) {
|
|
44
|
+
const answer = await rl.question(`\nThe ${HOST_NAMES[host]} ${host === "github" ? "repository" : "project"}, such as ${example} (leave empty if it doesn't exist yet): `);
|
|
45
|
+
if (answer.trim() === "") return void 0;
|
|
46
|
+
const repo = parseRepo(host, answer);
|
|
47
|
+
if (repo) return repo;
|
|
48
|
+
stdout.write(`That doesn't look like a ${HOST_NAMES[host]} address. Type it as ${example}.\n`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function isTemplate(name) {
|
|
52
|
+
return Object.hasOwn(TEMPLATES, name);
|
|
53
|
+
}
|
|
54
|
+
function isHost(name) {
|
|
55
|
+
return Object.hasOwn(HOSTS, name);
|
|
56
|
+
}
|
|
57
|
+
function backendFromOptions(github, gitlab) {
|
|
58
|
+
if (github !== void 0 && gitlab !== void 0) throw new ScaffoldError("Choose either --github or --gitlab, not both.");
|
|
59
|
+
const host = github !== void 0 ? "github" : gitlab !== void 0 ? "gitlab" : void 0;
|
|
60
|
+
if (!host) return void 0;
|
|
61
|
+
const repo = parseRepo(host, github ?? gitlab);
|
|
62
|
+
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.");
|
|
63
|
+
return {
|
|
64
|
+
host,
|
|
65
|
+
repo
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async function main() {
|
|
69
|
+
const { positionals, values } = parseArgs({
|
|
70
|
+
allowPositionals: true,
|
|
71
|
+
options: {
|
|
72
|
+
template: { type: "string" },
|
|
73
|
+
github: { type: "string" },
|
|
74
|
+
gitlab: { type: "string" },
|
|
75
|
+
host: { type: "string" },
|
|
76
|
+
blocks: { type: "string" },
|
|
77
|
+
"no-git": { type: "boolean" },
|
|
78
|
+
"no-install": { type: "boolean" },
|
|
79
|
+
yes: {
|
|
80
|
+
type: "boolean",
|
|
81
|
+
short: "y"
|
|
82
|
+
},
|
|
83
|
+
help: {
|
|
84
|
+
type: "boolean",
|
|
85
|
+
short: "h"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
if (values.help) {
|
|
90
|
+
stdout.write(HELP);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
let folder = positionals[0];
|
|
94
|
+
let template = values.template;
|
|
95
|
+
if (template !== void 0 && !isTemplate(template)) throw new ScaffoldError(`There's no template called "${template}". Choose ${Object.keys(TEMPLATES).join(" or ")}.`);
|
|
96
|
+
let backend = backendFromOptions(values.github, values.gitlab);
|
|
97
|
+
let host = values.host;
|
|
98
|
+
if (host !== void 0 && !isHost(host)) throw new ScaffoldError(`--host takes ${Object.keys(HOSTS).join(", ")}.`);
|
|
99
|
+
let blocks = values.blocks;
|
|
100
|
+
if (blocks !== void 0 && !(blocks in BLOCK_CHOICES)) throw new ScaffoldError(`--blocks takes ${Object.keys(BLOCK_CHOICES).join(" or ")}.`);
|
|
101
|
+
if (stdin.isTTY && !values.yes) {
|
|
102
|
+
const rl = createInterface({
|
|
103
|
+
input: stdin,
|
|
104
|
+
output: stdout
|
|
105
|
+
});
|
|
106
|
+
try {
|
|
107
|
+
if (!folder) folder = (await rl.question("Folder for the new site (my-site): ")).trim() || "my-site";
|
|
108
|
+
template ??= await choose(rl, "Start from:", Object.entries(TEMPLATES).map(([value, { label, description }]) => ({
|
|
109
|
+
value,
|
|
110
|
+
label,
|
|
111
|
+
note: description
|
|
112
|
+
})));
|
|
113
|
+
let storage = backend?.host;
|
|
114
|
+
if (!backend) {
|
|
115
|
+
storage = await choose(rl, "Where will the site be stored? Editors sign in there, and publishing saves to it.", [
|
|
116
|
+
{
|
|
117
|
+
value: "github",
|
|
118
|
+
label: "GitHub"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
value: "gitlab",
|
|
122
|
+
label: "GitLab"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
value: void 0,
|
|
126
|
+
label: "Not decided yet"
|
|
127
|
+
}
|
|
128
|
+
]);
|
|
129
|
+
const repo = storage && await askRepo(rl, storage);
|
|
130
|
+
if (storage && repo) backend = {
|
|
131
|
+
host: storage,
|
|
132
|
+
repo
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
host ??= await chooseHost(rl, storage);
|
|
136
|
+
blocks ??= await choose(rl, "Which blocks should the editor offer to start with?", Object.entries(BLOCK_CHOICES).map(([value, { label, description }]) => ({
|
|
137
|
+
value,
|
|
138
|
+
label,
|
|
139
|
+
note: description
|
|
140
|
+
})));
|
|
141
|
+
} finally {
|
|
142
|
+
rl.close();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (!folder) throw new ScaffoldError("Say which folder to create the site in, such as: npm create goodfellow@latest my-site");
|
|
146
|
+
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]}.`);
|
|
147
|
+
const target = resolve(folder);
|
|
148
|
+
const versions = JSON.parse(readFileSync(join(templatesDir, "versions.json"), "utf8"));
|
|
149
|
+
await scaffold({
|
|
150
|
+
template: join(templatesDir, template ?? "starter"),
|
|
151
|
+
target,
|
|
152
|
+
versions,
|
|
153
|
+
...backend && { backend },
|
|
154
|
+
...host && { host },
|
|
155
|
+
...(blocks ?? "recommended") === "recommended" && { registryDir: join(templatesDir, "registry") }
|
|
156
|
+
});
|
|
157
|
+
const git = values["no-git"] ? void 0 : initGitRepository(target, backend);
|
|
158
|
+
let installed = false;
|
|
159
|
+
if (!values["no-install"]) {
|
|
160
|
+
stdout.write("\nInstalling the site's packages with npm...\n\n");
|
|
161
|
+
installed = installPackages(target);
|
|
162
|
+
}
|
|
163
|
+
const commit = git?.created && (installed || values["no-install"]) ? commitAll(target, "Create the site") : void 0;
|
|
164
|
+
const where = relative(process.cwd(), target) || ".";
|
|
165
|
+
const cd = where === "." ? [] : [` cd ${/\s/.test(where) ? JSON.stringify(where) : where}`];
|
|
166
|
+
const lines = [
|
|
167
|
+
"",
|
|
168
|
+
`Created a new site in ${where}.`,
|
|
169
|
+
...git ? gitLines(git, commit, installed) : [],
|
|
170
|
+
...!installed && !values["no-install"] ? ["npm install didn't finish, so run it again in the site's folder."] : [],
|
|
171
|
+
"",
|
|
172
|
+
"Next:",
|
|
173
|
+
...cd,
|
|
174
|
+
...installed ? [] : [" npm install"],
|
|
175
|
+
" npm run dev",
|
|
176
|
+
"",
|
|
177
|
+
"Then open http://localhost:4321/admin to start editing. README.md explains how to put the site online."
|
|
178
|
+
];
|
|
179
|
+
if (!backend) lines.push("Before you do, say where the site is stored in goodfellow.config.tsx; README.md shows how.");
|
|
180
|
+
stdout.write(`${lines.join("\n")}\n`);
|
|
181
|
+
}
|
|
182
|
+
/** What happened with git, and what's left to do. */
|
|
183
|
+
function gitLines(git, commit, installed) {
|
|
184
|
+
if (!git.created) return [git.reason === "inside-repository" ? "It's inside another git repository, so it isn't one of its own." : "Git isn't installed, so it isn't a git repository yet: once it is, run git init -b main in it, then commit."];
|
|
185
|
+
const repository = "It's a git repository on the branch main";
|
|
186
|
+
const origin = git.remote ? [`Its origin is ${git.remote}.`] : [];
|
|
187
|
+
if (commit === "committed") return [
|
|
188
|
+
`${repository}, with everything in its first commit.`,
|
|
189
|
+
...origin,
|
|
190
|
+
...installed ? [] : ["Commit package-lock.json after npm install: the deploy setups need it."]
|
|
191
|
+
];
|
|
192
|
+
if (commit === "no-identity") return [
|
|
193
|
+
`${repository}, but git doesn't know your name and email yet, so nothing is committed. Tell it, then commit:`,
|
|
194
|
+
" git config --global user.name \"Your Name\"",
|
|
195
|
+
" git config --global user.email you@example.com",
|
|
196
|
+
" git add -A && git commit -m \"Create the site\"",
|
|
197
|
+
...origin
|
|
198
|
+
];
|
|
199
|
+
return [`${repository}. Nothing is committed yet: commit everything once npm install has run.`, ...origin];
|
|
200
|
+
}
|
|
201
|
+
async function chooseHost(rl, storage) {
|
|
202
|
+
stdout.write("\nEach host's free plan has its own rules about business use. Check them if the site is for a business,");
|
|
203
|
+
stdout.write("\nsells anything, shows ads, or anyone is paid to build or look after it.\n");
|
|
204
|
+
return choose(rl, "Where will the site be hosted?", [...hostsFor(storage).map((value) => ({
|
|
205
|
+
value,
|
|
206
|
+
label: HOSTS[value].label,
|
|
207
|
+
note: HOSTS[value].note
|
|
208
|
+
})), {
|
|
209
|
+
value: void 0,
|
|
210
|
+
label: "Not decided yet",
|
|
211
|
+
note: "Keeps the setup files for every host."
|
|
212
|
+
}]);
|
|
213
|
+
}
|
|
214
|
+
main().catch((error) => {
|
|
215
|
+
if (error.code === "ABORT_ERR") process.exit(130);
|
|
216
|
+
console.error(error instanceof ScaffoldError ? error.message : error);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
});
|
|
219
|
+
//#endregion
|
|
220
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { BLOCK_CHOICES, BUNDLED_GITIGNORE, Backend, Backend as Backend$1, BlockChoice, HOSTS, Host, Host as Host$1, SiteFiles, TEMPLATES, TemplateName, configureBackend, hostsFor, parseRepo, sitePackageJson, withoutRepositoryNotes } from "@goodfellow-cms/core";
|
|
2
|
+
//#region src/scaffold.d.ts
|
|
3
|
+
/** A problem the person creating the site can fix, described in plain words. */
|
|
4
|
+
export declare class ScaffoldError extends Error {
|
|
5
|
+
name: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Copies a template's files, leaving out installed packages and build output.
|
|
9
|
+
* `bundling` renames `.gitignore` files for publishing; otherwise they're renamed back.
|
|
10
|
+
*/
|
|
11
|
+
export declare function copyTemplate(from: string, to: string, { bundling }?: {
|
|
12
|
+
bundling?: boolean | undefined;
|
|
13
|
+
}): Promise<void>;
|
|
14
|
+
/** A valid npm package name from a folder name, such as `My Parish` → `my-parish`. */
|
|
15
|
+
export declare function packageName(folder: string): string;
|
|
16
|
+
/** Reads a template's files, leaving out installed packages and build output, as `copyTemplate()` does. */
|
|
17
|
+
export declare function readTemplate(dir: string): Promise<SiteFiles>;
|
|
18
|
+
interface ScaffoldOptions {
|
|
19
|
+
/** The template's folder. */
|
|
20
|
+
template: string;
|
|
21
|
+
/** The new site's folder. It must be empty or not exist yet. */
|
|
22
|
+
target: string;
|
|
23
|
+
/** Versions of Goodfellow's packages, by name, to replace the template's workspace links. */
|
|
24
|
+
versions: Record<string, string>;
|
|
25
|
+
backend?: Backend$1;
|
|
26
|
+
/** Keeps only this host's setup file. All are kept if it isn't set. */
|
|
27
|
+
host?: Host$1;
|
|
28
|
+
/** The built block registry to install recommended blocks from. Without it, the site has the built-in blocks only. */
|
|
29
|
+
registryDir?: string;
|
|
30
|
+
}
|
|
31
|
+
/** Creates a new site in `target` from a template. */
|
|
32
|
+
export declare function scaffold({ template, target, versions, backend, host, registryDir }: ScaffoldOptions): Promise<void>;
|
|
33
|
+
//#endregion
|
|
34
|
+
export { BLOCK_CHOICES, BUNDLED_GITIGNORE, type Backend, type BlockChoice, HOSTS, type Host, type ScaffoldOptions, TEMPLATES, type TemplateName, configureBackend, hostsFor, parseRepo, sitePackageJson, withoutRepositoryNotes };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { _ as withoutRepositoryNotes, a as TEMPLATES, c as copyTemplate, f as packageName, g as sitePackageJson, h as scaffold, i as ScaffoldError, l as hostsFor, m as readTemplate, n as BUNDLED_GITIGNORE, p as parseRepo, r as HOSTS, s as configureBackend, t as BLOCK_CHOICES } from "./scaffold-BGxzPdN1.js";
|
|
2
|
+
export { BLOCK_CHOICES, BUNDLED_GITIGNORE, HOSTS, ScaffoldError, TEMPLATES, configureBackend, copyTemplate, hostsFor, packageName, parseRepo, readTemplate, scaffold, sitePackageJson, withoutRepositoryNotes };
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { cp, mkdir, readFile, readdir, rename, writeFile } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { BLOCK_CHOICES, BUNDLED_GITIGNORE, BUNDLED_GITIGNORE as BUNDLED_GITIGNORE$1, GOODFELLOW_REGISTRY, HOSTS, TEMPLATES, configureBackend, hostsFor, parseRepo, planSite, sitePackageJson, sitePackageName, withoutRepositoryNotes } from "@goodfellow-cms/core";
|
|
5
|
+
//#region src/scaffold.ts
|
|
6
|
+
/** A problem the person creating the site can fix, described in plain words. */
|
|
7
|
+
var ScaffoldError = class extends Error {
|
|
8
|
+
name = "ScaffoldError";
|
|
9
|
+
};
|
|
10
|
+
/** Never copied, wherever they are: installed packages and build caches. */
|
|
11
|
+
const SKIPPED = /* @__PURE__ */ new Set(["node_modules", ".turbo"]);
|
|
12
|
+
/** Never copied from the template's own folder: build output, and files only the Goodfellow repository uses. */
|
|
13
|
+
const SKIPPED_AT_ROOT = /* @__PURE__ */ new Set([
|
|
14
|
+
"dist",
|
|
15
|
+
"out",
|
|
16
|
+
".next",
|
|
17
|
+
"next-env.d.ts",
|
|
18
|
+
"turbo.json"
|
|
19
|
+
]);
|
|
20
|
+
/**
|
|
21
|
+
* Copies a template's files, leaving out installed packages and build output.
|
|
22
|
+
* `bundling` renames `.gitignore` files for publishing; otherwise they're renamed back.
|
|
23
|
+
*/
|
|
24
|
+
async function copyTemplate(from, to, { bundling = false } = {}) {
|
|
25
|
+
await cp(from, to, {
|
|
26
|
+
recursive: true,
|
|
27
|
+
filter: (source) => {
|
|
28
|
+
const path = relative(from, source).split(sep);
|
|
29
|
+
return !path.some((part) => SKIPPED.has(part)) && !SKIPPED_AT_ROOT.has(path[0] ?? "");
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
const [find, replace] = bundling ? [".gitignore", BUNDLED_GITIGNORE] : [BUNDLED_GITIGNORE, ".gitignore"];
|
|
33
|
+
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));
|
|
34
|
+
}
|
|
35
|
+
/** A valid npm package name from a folder name, such as `My Parish` → `my-parish`. */
|
|
36
|
+
function packageName(folder) {
|
|
37
|
+
return sitePackageName(basename(resolve(folder)));
|
|
38
|
+
}
|
|
39
|
+
/** Reads a built registry from disk, as the admin panel would read it from its address. */
|
|
40
|
+
function localRegistry(dir) {
|
|
41
|
+
const base = "https://registry.invalid/r/";
|
|
42
|
+
return {
|
|
43
|
+
registries: { [GOODFELLOW_REGISTRY]: `${base}{name}.json` },
|
|
44
|
+
fetchJson: async (url) => JSON.parse(await readFile(join(dir, url.slice(27)), "utf8"))
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Reads a template's files, leaving out installed packages and build output, as `copyTemplate()` does. */
|
|
48
|
+
async function readTemplate(dir) {
|
|
49
|
+
const files = /* @__PURE__ */ new Map();
|
|
50
|
+
for (const entry of await readdir(dir, {
|
|
51
|
+
recursive: true,
|
|
52
|
+
withFileTypes: true
|
|
53
|
+
})) {
|
|
54
|
+
if (!entry.isFile()) continue;
|
|
55
|
+
const path = relative(dir, join(entry.parentPath, entry.name)).split(sep);
|
|
56
|
+
if (path.some((part) => SKIPPED.has(part)) || SKIPPED_AT_ROOT.has(path[0] ?? "")) continue;
|
|
57
|
+
files.set(path.join("/"), new Uint8Array(await readFile(join(dir, ...path))));
|
|
58
|
+
}
|
|
59
|
+
return files;
|
|
60
|
+
}
|
|
61
|
+
async function isEmptyFolder(path) {
|
|
62
|
+
try {
|
|
63
|
+
return (await readdir(path)).length === 0;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error.code === "ENOENT") return true;
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** Where a backend's repository is pushed to, over HTTPS. */
|
|
70
|
+
function remoteUrl(backend) {
|
|
71
|
+
return `https://${backend.host === "github" ? "github.com" : "gitlab.com"}/${backend.repo}.git`;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Makes the new site a git repository on the branch `main`, which the deploy
|
|
75
|
+
* setups build from, with `origin` set when the site's repository is known.
|
|
76
|
+
* A folder inside another repository, such as a monorepo, is left as it is.
|
|
77
|
+
*/
|
|
78
|
+
function initGitRepository(target, backend) {
|
|
79
|
+
const git = (...args) => spawnSync("git", args, {
|
|
80
|
+
cwd: target,
|
|
81
|
+
encoding: "utf8"
|
|
82
|
+
});
|
|
83
|
+
const inside = git("rev-parse", "--is-inside-work-tree");
|
|
84
|
+
if (inside.error) return {
|
|
85
|
+
created: false,
|
|
86
|
+
reason: "no-git"
|
|
87
|
+
};
|
|
88
|
+
if (inside.status === 0) return {
|
|
89
|
+
created: false,
|
|
90
|
+
reason: "inside-repository"
|
|
91
|
+
};
|
|
92
|
+
if (git("init", "-q", "-b", "main").status !== 0) {
|
|
93
|
+
if (git("init", "-q").status !== 0) return {
|
|
94
|
+
created: false,
|
|
95
|
+
reason: "no-git"
|
|
96
|
+
};
|
|
97
|
+
git("symbolic-ref", "HEAD", "refs/heads/main");
|
|
98
|
+
}
|
|
99
|
+
if (!backend) return { created: true };
|
|
100
|
+
const remote = remoteUrl(backend);
|
|
101
|
+
git("remote", "add", "origin", remote);
|
|
102
|
+
return {
|
|
103
|
+
created: true,
|
|
104
|
+
remote
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Runs `npm install` in the new site, showing its output. Its `package-lock.json`
|
|
109
|
+
* goes in the first commit, since the deploy setups install with `npm ci`.
|
|
110
|
+
*/
|
|
111
|
+
function installPackages(target) {
|
|
112
|
+
return (process.platform === "win32" ? spawnSync("npm install", {
|
|
113
|
+
cwd: target,
|
|
114
|
+
stdio: "inherit",
|
|
115
|
+
shell: true
|
|
116
|
+
}) : spawnSync("npm", ["install"], {
|
|
117
|
+
cwd: target,
|
|
118
|
+
stdio: "inherit"
|
|
119
|
+
})).status === 0;
|
|
120
|
+
}
|
|
121
|
+
/** Commits everything in the new site, which `.gitignore` doesn't leave out. */
|
|
122
|
+
function commitAll(target, message) {
|
|
123
|
+
const git = (...args) => spawnSync("git", args, {
|
|
124
|
+
cwd: target,
|
|
125
|
+
encoding: "utf8"
|
|
126
|
+
});
|
|
127
|
+
if (git("add", "-A").status !== 0) return "failed";
|
|
128
|
+
const commit = git("commit", "-q", "-m", message);
|
|
129
|
+
if (commit.status === 0) return "committed";
|
|
130
|
+
return /user\.(name|email)|tell me who you are|identity/i.test(commit.stderr) ? "no-identity" : "failed";
|
|
131
|
+
}
|
|
132
|
+
/** Creates a new site in `target` from a template. */
|
|
133
|
+
async function scaffold({ template, target, versions, backend, host, registryDir }) {
|
|
134
|
+
if (!await isEmptyFolder(target)) throw new ScaffoldError(`The folder ${target} already has files in it. Choose a new folder for the site.`);
|
|
135
|
+
const files = await planSite({
|
|
136
|
+
template: await readTemplate(template),
|
|
137
|
+
name: packageName(target),
|
|
138
|
+
versions,
|
|
139
|
+
backend,
|
|
140
|
+
host,
|
|
141
|
+
registry: registryDir ? localRegistry(registryDir) : void 0
|
|
142
|
+
});
|
|
143
|
+
await mkdir(target, { recursive: true });
|
|
144
|
+
for (const [path, file] of files) {
|
|
145
|
+
await mkdir(dirname(join(target, path)), { recursive: true });
|
|
146
|
+
await writeFile(join(target, path), file);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
//#endregion
|
|
150
|
+
export { withoutRepositoryNotes as _, TEMPLATES as a, copyTemplate as c, installPackages as d, packageName as f, sitePackageJson as g, scaffold as h, ScaffoldError as i, hostsFor as l, readTemplate as m, BUNDLED_GITIGNORE$1 as n, commitAll as o, parseRepo as p, HOSTS as r, configureBackend as s, BLOCK_CHOICES as t, initGitRepository 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.2.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://goodfellow-cms.github.io/goodfellow-cms/docs/create-a-site",
|
|
15
|
+
"bugs": "https://github.com/goodfellow-cms/goodfellow-cms/issues",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/goodfellow-cms/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.2.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@goodfellow-cms/registry": "0.2.0",
|
|
46
|
+
"@goodfellow-cms/example-parish": "0.0.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,86 @@
|
|
|
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. 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.
|
|
8
|
+
name: Deploy to GitHub Pages
|
|
9
|
+
|
|
10
|
+
on:
|
|
11
|
+
push:
|
|
12
|
+
branches: [main]
|
|
13
|
+
schedule:
|
|
14
|
+
- cron: "17 4 * * *"
|
|
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: ""
|
|
21
|
+
|
|
22
|
+
permissions:
|
|
23
|
+
contents: read
|
|
24
|
+
pages: write
|
|
25
|
+
id-token: write
|
|
26
|
+
|
|
27
|
+
concurrency:
|
|
28
|
+
group: pages
|
|
29
|
+
cancel-in-progress: false
|
|
30
|
+
|
|
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-next update --publish --to "$TO"
|
|
51
|
+
|
|
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() }}
|
|
56
|
+
runs-on: ubuntu-latest
|
|
57
|
+
steps:
|
|
58
|
+
- uses: actions/checkout@v5
|
|
59
|
+
with:
|
|
60
|
+
# The update's commit, when it published one.
|
|
61
|
+
ref: ${{ needs.update.outputs.sha || github.sha }}
|
|
62
|
+
- uses: actions/setup-node@v5
|
|
63
|
+
with:
|
|
64
|
+
node-version: 22
|
|
65
|
+
cache: npm
|
|
66
|
+
- id: pages
|
|
67
|
+
uses: actions/configure-pages@v5
|
|
68
|
+
- run: npm ci
|
|
69
|
+
# Project sites live in a subfolder (/repository-name/); user and organization sites don't.
|
|
70
|
+
- run: npx next build
|
|
71
|
+
env:
|
|
72
|
+
GOODFELLOW_BASE: ${{ steps.pages.outputs.base_path }}/
|
|
73
|
+
- uses: actions/upload-pages-artifact@v4
|
|
74
|
+
with:
|
|
75
|
+
path: out
|
|
76
|
+
|
|
77
|
+
deploy:
|
|
78
|
+
needs: build
|
|
79
|
+
if: ${{ !cancelled() && needs.build.result == 'success' }}
|
|
80
|
+
runs-on: ubuntu-latest
|
|
81
|
+
environment:
|
|
82
|
+
name: github-pages
|
|
83
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
84
|
+
steps:
|
|
85
|
+
- id: deployment
|
|
86
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Builds the site and publishes it with GitLab Pages whenever the default
|
|
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.
|
|
9
|
+
pages:
|
|
10
|
+
image: node:22
|
|
11
|
+
cache:
|
|
12
|
+
key:
|
|
13
|
+
files: [package-lock.json]
|
|
14
|
+
paths: [.npm/, .next/cache/]
|
|
15
|
+
script:
|
|
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-next update --publish --to "${GOODFELLOW_UPDATE:-automatic}" || echo "The update wasn't published; building the site as it is."
|
|
21
|
+
fi
|
|
22
|
+
# Pages may serve the site from a subfolder, such as https://group.gitlab.io/site/.
|
|
23
|
+
- export GOODFELLOW_BASE="$(node -e 'console.log(new URL(process.env.CI_PAGES_URL).pathname)')"
|
|
24
|
+
- npx next build
|
|
25
|
+
pages:
|
|
26
|
+
publish: out
|
|
27
|
+
rules:
|
|
28
|
+
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|