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.
Files changed (150) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +15 -2
  3. package/bin/create-goodfellow.js +2 -0
  4. package/dist/cli.d.ts +1 -0
  5. package/dist/cli.js +220 -0
  6. package/dist/index.d.ts +34 -0
  7. package/dist/index.js +2 -0
  8. package/dist/scaffold-BGxzPdN1.js +150 -0
  9. package/package.json +55 -3
  10. package/templates/next/.github/workflows/deploy.yml +86 -0
  11. package/templates/next/.gitlab-ci.yml +28 -0
  12. package/templates/next/README.md +127 -0
  13. package/templates/next/_gitignore +5 -0
  14. package/templates/next/app/(site)/[[...path]]/page.tsx +8 -0
  15. package/templates/next/app/(site)/layout.tsx +5 -0
  16. package/templates/next/app/admin/demo-content.json/route.ts +5 -0
  17. package/templates/next/app/admin/layout.tsx +8 -0
  18. package/templates/next/app/admin/page.tsx +8 -0
  19. package/templates/next/app/layout.tsx +11 -0
  20. package/templates/next/app/not-found.tsx +4 -0
  21. package/templates/next/app/robots.ts +4 -0
  22. package/templates/next/app/site.css +11 -0
  23. package/templates/next/app/sitemap.ts +4 -0
  24. package/templates/next/blocks/installed/index.ts +7 -0
  25. package/templates/next/components.json +24 -0
  26. package/templates/next/content/collections/news/_collection.json +113 -0
  27. package/templates/next/content/collections/news/open-house.json +9 -0
  28. package/templates/next/content/collections/news/welcome.json +10 -0
  29. package/templates/next/content/layout/footer.json +55 -0
  30. package/templates/next/content/layout/header.json +54 -0
  31. package/templates/next/content/menus.json +25 -0
  32. package/templates/next/content/pages/404.json +73 -0
  33. package/templates/next/content/pages/about.json +59 -0
  34. package/templates/next/content/pages/index.json +215 -0
  35. package/templates/next/content/pages/news.json +55 -0
  36. package/templates/next/content/site.json +25 -0
  37. package/templates/next/content/styles/custom.css +4 -0
  38. package/templates/next/goodfellow.config.tsx +17 -0
  39. package/templates/next/lib/site.ts +5 -0
  40. package/templates/next/next.config.ts +7 -0
  41. package/templates/next/package.json +40 -0
  42. package/templates/next/postcss.config.mjs +3 -0
  43. package/templates/next/public/media/favicon.svg +1 -0
  44. package/templates/next/public/media/logo.svg +1 -0
  45. package/templates/next/public/media/placeholder.svg +1 -0
  46. package/templates/next/tsconfig.json +27 -0
  47. package/templates/parish/.github/workflows/deploy.yml +84 -0
  48. package/templates/parish/.gitlab-ci.yml +28 -0
  49. package/templates/parish/README.md +128 -0
  50. package/templates/parish/_gitignore +2 -0
  51. package/templates/parish/blocks/installed/index.ts +7 -0
  52. package/templates/parish/blocks/mass-times.tsx +59 -0
  53. package/templates/parish/blocks/notice.tsx +46 -0
  54. package/templates/parish/blocks/scripture.tsx +40 -0
  55. package/templates/parish/components.json +24 -0
  56. package/templates/parish/content/collections/bulletins/2026-09-27.json +8 -0
  57. package/templates/parish/content/collections/bulletins/2026-10-04.json +8 -0
  58. package/templates/parish/content/collections/bulletins/_collection.json +113 -0
  59. package/templates/parish/content/collections/events/_collection.json +156 -0
  60. package/templates/parish/content/collections/events/advent-penance-service.json +14 -0
  61. package/templates/parish/content/collections/events/all-souls.json +14 -0
  62. package/templates/parish/content/collections/events/christmas-concert.json +14 -0
  63. package/templates/parish/content/collections/events/fall-festival.json +14 -0
  64. package/templates/parish/content/collections/events/youth-group.json +23 -0
  65. package/templates/parish/content/collections/news/_collection.json +137 -0
  66. package/templates/parish/content/collections/news/bell-tower-repairs.json +13 -0
  67. package/templates/parish/content/collections/news/choir-new-voices.json +14 -0
  68. package/templates/parish/content/collections/news/food-drive.json +13 -0
  69. package/templates/parish/content/collections/news/new-website.json +13 -0
  70. package/templates/parish/content/collections/news/winter-coats.json +13 -0
  71. package/templates/parish/content/collections/news/youth-group.json +13 -0
  72. package/templates/parish/content/collections/news/youth-service-day.json +14 -0
  73. package/templates/parish/content/collections/staff/_collection.json +34 -0
  74. package/templates/parish/content/collections/staff/david-okafor.json +9 -0
  75. package/templates/parish/content/collections/staff/fr-thomas-reed.json +9 -0
  76. package/templates/parish/content/collections/staff/maria-chen.json +9 -0
  77. package/templates/parish/content/collections/staff/sarah-lindqvist.json +9 -0
  78. package/templates/parish/content/layout/footer.json +142 -0
  79. package/templates/parish/content/layout/header.json +54 -0
  80. package/templates/parish/content/menus.json +67 -0
  81. package/templates/parish/content/pages/404.json +85 -0
  82. package/templates/parish/content/pages/about.json +153 -0
  83. package/templates/parish/content/pages/bulletins.json +64 -0
  84. package/templates/parish/content/pages/calendar.json +59 -0
  85. package/templates/parish/content/pages/contact.json +180 -0
  86. package/templates/parish/content/pages/events.json +107 -0
  87. package/templates/parish/content/pages/index.json +390 -0
  88. package/templates/parish/content/pages/mass-times.json +168 -0
  89. package/templates/parish/content/pages/news.json +68 -0
  90. package/templates/parish/content/pages/sacraments.json +259 -0
  91. package/templates/parish/content/site.json +36 -0
  92. package/templates/parish/content/styles/custom.css +4 -0
  93. package/templates/parish/goodfellow.config.tsx +24 -0
  94. package/templates/parish/package.json +36 -0
  95. package/templates/parish/public/media/bulletin-example.pdf +32 -0
  96. package/templates/parish/public/media/candles.svg +22 -0
  97. package/templates/parish/public/media/church.svg +33 -0
  98. package/templates/parish/public/media/favicon.svg +5 -0
  99. package/templates/parish/public/media/gathering.svg +18 -0
  100. package/templates/parish/public/media/logo.svg +5 -0
  101. package/templates/parish/public/media/person.svg +5 -0
  102. package/templates/parish/public/media/placeholder.svg +5 -0
  103. package/templates/parish/src/styles.css +8 -0
  104. package/templates/parish/tsconfig.json +14 -0
  105. package/templates/parish/vercel.ts +65 -0
  106. package/templates/registry/accordion.json +17 -0
  107. package/templates/registry/alert.json +16 -0
  108. package/templates/registry/badge.json +17 -0
  109. package/templates/registry/button.json +17 -0
  110. package/templates/registry/card.json +16 -0
  111. package/templates/registry/carousel.json +19 -0
  112. package/templates/registry/custom-html.json +31 -0
  113. package/templates/registry/registry.json +364 -0
  114. package/templates/registry/shadcn-call-to-action.json +26 -0
  115. package/templates/registry/shadcn-cards.json +26 -0
  116. package/templates/registry/shadcn-carousel.json +29 -0
  117. package/templates/registry/shadcn-faq.json +30 -0
  118. package/templates/registry/shadcn-hero.json +25 -0
  119. package/templates/registry/shadcn-notice.json +27 -0
  120. package/templates/registry/shadcn-pricing.json +28 -0
  121. package/templates/registry/shadcn-tabs.json +30 -0
  122. package/templates/registry/shadcn-testimonials.json +25 -0
  123. package/templates/registry/tabs.json +17 -0
  124. package/templates/starter/.github/workflows/deploy.yml +84 -0
  125. package/templates/starter/.gitlab-ci.yml +28 -0
  126. package/templates/starter/README.md +108 -0
  127. package/templates/starter/_gitignore +2 -0
  128. package/templates/starter/blocks/installed/index.ts +7 -0
  129. package/templates/starter/components.json +24 -0
  130. package/templates/starter/content/collections/news/_collection.json +113 -0
  131. package/templates/starter/content/collections/news/open-house.json +9 -0
  132. package/templates/starter/content/collections/news/welcome.json +10 -0
  133. package/templates/starter/content/layout/footer.json +55 -0
  134. package/templates/starter/content/layout/header.json +54 -0
  135. package/templates/starter/content/menus.json +25 -0
  136. package/templates/starter/content/pages/404.json +73 -0
  137. package/templates/starter/content/pages/about.json +59 -0
  138. package/templates/starter/content/pages/index.json +215 -0
  139. package/templates/starter/content/pages/news.json +55 -0
  140. package/templates/starter/content/site.json +25 -0
  141. package/templates/starter/content/styles/custom.css +4 -0
  142. package/templates/starter/goodfellow.config.tsx +17 -0
  143. package/templates/starter/package.json +36 -0
  144. package/templates/starter/public/media/favicon.svg +1 -0
  145. package/templates/starter/public/media/logo.svg +1 -0
  146. package/templates/starter/public/media/placeholder.svg +1 -0
  147. package/templates/starter/src/styles.css +8 -0
  148. package/templates/starter/tsconfig.json +14 -0
  149. package/templates/starter/vercel.ts +65 -0
  150. 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
- # Temporary Holding Version
1
+ # create-goodfellow
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "../dist/cli.js";
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 {};
@@ -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.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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