create-goodfellow 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cli.js +36 -3
- package/dist/index.d.ts +6 -79
- package/dist/index.js +2 -2
- package/dist/scaffold-BGxzPdN1.js +150 -0
- package/package.json +6 -6
- package/templates/next/.github/workflows/deploy.yml +37 -2
- package/templates/next/.gitlab-ci.yml +12 -2
- package/templates/next/README.md +14 -7
- package/templates/next/app/admin/layout.tsx +2 -2
- package/templates/parish/.github/workflows/deploy.yml +37 -2
- package/templates/parish/.gitlab-ci.yml +12 -2
- package/templates/parish/README.md +14 -7
- package/templates/parish/content/collections/events/_collection.json +19 -20
- package/templates/parish/content/collections/events/advent-penance-service.json +5 -3
- package/templates/parish/content/collections/events/all-souls.json +5 -3
- package/templates/parish/content/collections/events/christmas-concert.json +5 -3
- package/templates/parish/content/collections/events/fall-festival.json +5 -3
- package/templates/parish/content/collections/events/youth-group.json +23 -0
- package/templates/parish/content/collections/news/_collection.json +24 -0
- package/templates/parish/content/collections/news/bell-tower-repairs.json +13 -0
- package/templates/parish/content/collections/news/choir-new-voices.json +14 -0
- package/templates/parish/content/collections/news/food-drive.json +4 -1
- package/templates/parish/content/collections/news/new-website.json +4 -1
- package/templates/parish/content/collections/news/winter-coats.json +13 -0
- package/templates/parish/content/collections/news/youth-group.json +4 -1
- package/templates/parish/content/collections/news/youth-service-day.json +14 -0
- package/templates/parish/content/menus.json +4 -0
- package/templates/parish/content/pages/about.json +48 -5
- package/templates/parish/content/pages/calendar.json +59 -0
- package/templates/parish/content/pages/events.json +27 -11
- package/templates/parish/content/pages/index.json +4 -37
- package/templates/parish/content/pages/news.json +14 -1
- package/templates/parish/content/site.json +1 -0
- package/templates/parish/vercel.ts +65 -0
- package/templates/registry/custom-html.json +1 -1
- package/templates/registry/registry.json +11 -11
- package/templates/registry/shadcn-call-to-action.json +1 -1
- package/templates/registry/shadcn-cards.json +1 -1
- package/templates/registry/shadcn-carousel.json +1 -1
- package/templates/registry/shadcn-faq.json +1 -1
- package/templates/registry/shadcn-hero.json +1 -1
- package/templates/registry/shadcn-notice.json +1 -1
- package/templates/registry/shadcn-pricing.json +1 -1
- package/templates/registry/shadcn-tabs.json +1 -1
- package/templates/registry/shadcn-testimonials.json +1 -1
- package/templates/starter/.github/workflows/deploy.yml +37 -2
- package/templates/starter/.gitlab-ci.yml +12 -2
- package/templates/starter/README.md +14 -7
- package/templates/starter/vercel.ts +65 -0
- package/templates/versions.json +11 -11
- package/dist/scaffold-4nOF_fOJ.js +0 -207
- package/templates/next/tsconfig.tsbuildinfo +0 -1
- package/templates/parish/vercel.json +0 -7
- package/templates/starter/vercel.json +0 -7
package/README.md
CHANGED
|
@@ -7,9 +7,9 @@ npm create goodfellow@latest my-site
|
|
|
7
7
|
npm create goodfellow@latest my-site -- --template parish --host github-pages
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
[Goodfellow](https://github.com/
|
|
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
11
|
|
|
12
|
-
More in the docs: [Create a site](https://
|
|
12
|
+
More in the docs: [Create a site](https://goodfellow-cms.github.io/goodfellow-cms/docs/create-a-site).
|
|
13
13
|
|
|
14
14
|
## License
|
|
15
15
|
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as TEMPLATES,
|
|
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
2
|
import { join, relative, resolve } from "node:path";
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import { stdin, stdout } from "node:process";
|
|
@@ -16,6 +16,8 @@ Options:
|
|
|
16
16
|
--gitlab <project> The GitLab project the site will be stored in, such as your-group/your-site
|
|
17
17
|
--host <host> github-pages, gitlab-pages or vercel. Keeps only that host's setup file.
|
|
18
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
|
|
19
21
|
-y, --yes Don't ask anything: use the options given, and defaults for the rest
|
|
20
22
|
-h, --help Show this help
|
|
21
23
|
`;
|
|
@@ -72,6 +74,8 @@ async function main() {
|
|
|
72
74
|
gitlab: { type: "string" },
|
|
73
75
|
host: { type: "string" },
|
|
74
76
|
blocks: { type: "string" },
|
|
77
|
+
"no-git": { type: "boolean" },
|
|
78
|
+
"no-install": { type: "boolean" },
|
|
75
79
|
yes: {
|
|
76
80
|
type: "boolean",
|
|
77
81
|
short: "y"
|
|
@@ -150,14 +154,24 @@ async function main() {
|
|
|
150
154
|
...host && { host },
|
|
151
155
|
...(blocks ?? "recommended") === "recommended" && { registryDir: join(templatesDir, "registry") }
|
|
152
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;
|
|
153
164
|
const where = relative(process.cwd(), target) || ".";
|
|
165
|
+
const cd = where === "." ? [] : [` cd ${/\s/.test(where) ? JSON.stringify(where) : where}`];
|
|
154
166
|
const lines = [
|
|
155
167
|
"",
|
|
156
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."] : [],
|
|
157
171
|
"",
|
|
158
172
|
"Next:",
|
|
159
|
-
...
|
|
160
|
-
" npm install",
|
|
173
|
+
...cd,
|
|
174
|
+
...installed ? [] : [" npm install"],
|
|
161
175
|
" npm run dev",
|
|
162
176
|
"",
|
|
163
177
|
"Then open http://localhost:4321/admin to start editing. README.md explains how to put the site online."
|
|
@@ -165,6 +179,25 @@ async function main() {
|
|
|
165
179
|
if (!backend) lines.push("Before you do, say where the site is stored in goodfellow.config.tsx; README.md shows how.");
|
|
166
180
|
stdout.write(`${lines.join("\n")}\n`);
|
|
167
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
|
+
}
|
|
168
201
|
async function chooseHost(rl, storage) {
|
|
169
202
|
stdout.write("\nEach host's free plan has its own rules about business use. Check them if the site is for a business,");
|
|
170
203
|
stdout.write("\nsells anything, shows ads, or anyone is paid to build or look after it.\n");
|
package/dist/index.d.ts
CHANGED
|
@@ -1,56 +1,9 @@
|
|
|
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";
|
|
1
2
|
//#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
3
|
/** A problem the person creating the site can fix, described in plain words. */
|
|
46
4
|
export declare class ScaffoldError extends Error {
|
|
47
5
|
name: string;
|
|
48
6
|
}
|
|
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
7
|
/**
|
|
55
8
|
* Copies a template's files, leaving out installed packages and build output.
|
|
56
9
|
* `bundling` renames `.gitignore` files for publishing; otherwise they're renamed back.
|
|
@@ -60,34 +13,8 @@ export declare function copyTemplate(from: string, to: string, { bundling }?: {
|
|
|
60
13
|
}): Promise<void>;
|
|
61
14
|
/** A valid npm package name from a folder name, such as `My Parish` → `my-parish`. */
|
|
62
15
|
export declare function packageName(folder: string): string;
|
|
63
|
-
/**
|
|
64
|
-
|
|
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[]>;
|
|
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>;
|
|
91
18
|
interface ScaffoldOptions {
|
|
92
19
|
/** The template's folder. */
|
|
93
20
|
template: string;
|
|
@@ -95,13 +22,13 @@ interface ScaffoldOptions {
|
|
|
95
22
|
target: string;
|
|
96
23
|
/** Versions of Goodfellow's packages, by name, to replace the template's workspace links. */
|
|
97
24
|
versions: Record<string, string>;
|
|
98
|
-
backend?: Backend;
|
|
25
|
+
backend?: Backend$1;
|
|
99
26
|
/** Keeps only this host's setup file. All are kept if it isn't set. */
|
|
100
|
-
host?: Host;
|
|
27
|
+
host?: Host$1;
|
|
101
28
|
/** The built block registry to install recommended blocks from. Without it, the site has the built-in blocks only. */
|
|
102
29
|
registryDir?: string;
|
|
103
30
|
}
|
|
104
31
|
/** Creates a new site in `target` from a template. */
|
|
105
32
|
export declare function scaffold({ template, target, versions, backend, host, registryDir }: ScaffoldOptions): Promise<void>;
|
|
106
33
|
//#endregion
|
|
107
|
-
export type
|
|
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
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as TEMPLATES, c as
|
|
2
|
-
export { BLOCK_CHOICES, BUNDLED_GITIGNORE, HOSTS, ScaffoldError, TEMPLATES, configureBackend, copyTemplate, hostsFor,
|
|
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,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-goodfellow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Creates a new Goodfellow site: npm create goodfellow@latest my-site",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"goodfellow",
|
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
"starter",
|
|
12
12
|
"scaffold"
|
|
13
13
|
],
|
|
14
|
-
"homepage": "https://
|
|
15
|
-
"bugs": "https://github.com/
|
|
14
|
+
"homepage": "https://goodfellow-cms.github.io/goodfellow-cms/docs/create-a-site",
|
|
15
|
+
"bugs": "https://github.com/goodfellow-cms/goodfellow-cms/issues",
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"repository": {
|
|
18
18
|
"type": "git",
|
|
19
|
-
"url": "git+https://github.com/
|
|
19
|
+
"url": "git+https://github.com/goodfellow-cms/goodfellow-cms.git",
|
|
20
20
|
"directory": "packages/create-goodfellow"
|
|
21
21
|
},
|
|
22
22
|
"type": "module",
|
|
@@ -39,11 +39,11 @@
|
|
|
39
39
|
"node": ">=22"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@goodfellow-cms/core": "0.
|
|
42
|
+
"@goodfellow-cms/core": "0.2.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
+
"@goodfellow-cms/registry": "0.2.0",
|
|
45
46
|
"@goodfellow-cms/example-parish": "0.0.0",
|
|
46
|
-
"@goodfellow-cms/registry": "0.1.0",
|
|
47
47
|
"@goodfellow-cms/template-starter": "0.0.0",
|
|
48
48
|
"@goodfellow-cms/template-next": "0.0.0"
|
|
49
49
|
},
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# Builds the site and publishes it with GitHub Pages whenever the main branch
|
|
2
2
|
# changes (including every Publish in the admin panel), and once a night so
|
|
3
|
-
# date-based content stays current.
|
|
4
|
-
#
|
|
3
|
+
# date-based content stays current. Each night it first installs Goodfellow's
|
|
4
|
+
# newest fixes, and publishes them only if the site still builds with them,
|
|
5
|
+
# unless content/updates.json turns automatic updates off.
|
|
6
|
+
# In the repository's Settings → Pages, set "Source" to "GitHub Actions".
|
|
7
|
+
# See README.md.
|
|
5
8
|
name: Deploy to GitHub Pages
|
|
6
9
|
|
|
7
10
|
on:
|
|
@@ -10,6 +13,11 @@ on:
|
|
|
10
13
|
schedule:
|
|
11
14
|
- cron: "17 4 * * *"
|
|
12
15
|
workflow_dispatch:
|
|
16
|
+
inputs:
|
|
17
|
+
update:
|
|
18
|
+
description: "Update Goodfellow first: fixes, latest, or a release such as 0.5.0 (an older one goes back to it). Leave empty to only build."
|
|
19
|
+
required: false
|
|
20
|
+
default: ""
|
|
13
21
|
|
|
14
22
|
permissions:
|
|
15
23
|
contents: read
|
|
@@ -21,10 +29,36 @@ concurrency:
|
|
|
21
29
|
cancel-in-progress: false
|
|
22
30
|
|
|
23
31
|
jobs:
|
|
32
|
+
update:
|
|
33
|
+
# Each night, and when the admin panel's Updates screen asks.
|
|
34
|
+
if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.update != '')
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
permissions:
|
|
37
|
+
contents: write
|
|
38
|
+
outputs:
|
|
39
|
+
sha: ${{ steps.update.outputs.sha }}
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v5
|
|
42
|
+
- uses: actions/setup-node@v5
|
|
43
|
+
with:
|
|
44
|
+
node-version: 22
|
|
45
|
+
cache: npm
|
|
46
|
+
- run: npm ci
|
|
47
|
+
- id: update
|
|
48
|
+
env:
|
|
49
|
+
TO: ${{ inputs.update || 'automatic' }}
|
|
50
|
+
run: npx goodfellow-next update --publish --to "$TO"
|
|
51
|
+
|
|
24
52
|
build:
|
|
53
|
+
needs: update
|
|
54
|
+
# Also when there was no update to run, or it didn't work: the site is built either way.
|
|
55
|
+
if: ${{ !cancelled() }}
|
|
25
56
|
runs-on: ubuntu-latest
|
|
26
57
|
steps:
|
|
27
58
|
- uses: actions/checkout@v5
|
|
59
|
+
with:
|
|
60
|
+
# The update's commit, when it published one.
|
|
61
|
+
ref: ${{ needs.update.outputs.sha || github.sha }}
|
|
28
62
|
- uses: actions/setup-node@v5
|
|
29
63
|
with:
|
|
30
64
|
node-version: 22
|
|
@@ -42,6 +76,7 @@ jobs:
|
|
|
42
76
|
|
|
43
77
|
deploy:
|
|
44
78
|
needs: build
|
|
79
|
+
if: ${{ !cancelled() && needs.build.result == 'success' }}
|
|
45
80
|
runs-on: ubuntu-latest
|
|
46
81
|
environment:
|
|
47
82
|
name: github-pages
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# Builds the site and publishes it with GitLab Pages whenever the default
|
|
2
|
-
# branch changes (including every Publish in the admin panel).
|
|
3
|
-
#
|
|
2
|
+
# branch changes (including every Publish in the admin panel). On scheduled
|
|
3
|
+
# pipelines, which the admin panel's Updates screen sets up to run each night,
|
|
4
|
+
# it first installs Goodfellow's newest fixes, unless content/updates.json turns
|
|
5
|
+
# automatic updates off (or the release GOODFELLOW_UPDATE names), and publishes
|
|
6
|
+
# them only if the site still builds with them. That
|
|
7
|
+
# needs "Allow Git push requests to the repository" under Settings → CI/CD →
|
|
8
|
+
# Job token permissions, which the Updates screen turns on. See README.md.
|
|
4
9
|
pages:
|
|
5
10
|
image: node:22
|
|
6
11
|
cache:
|
|
@@ -9,6 +14,11 @@ pages:
|
|
|
9
14
|
paths: [.npm/, .next/cache/]
|
|
10
15
|
script:
|
|
11
16
|
- npm ci --cache .npm --prefer-offline
|
|
17
|
+
- |
|
|
18
|
+
if [ "$CI_PIPELINE_SOURCE" = "schedule" ] || [ -n "$GOODFELLOW_UPDATE" ]; then
|
|
19
|
+
git remote set-url origin "https://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_FQDN:-$CI_SERVER_HOST}/${CI_PROJECT_PATH}.git"
|
|
20
|
+
npx goodfellow-next update --publish --to "${GOODFELLOW_UPDATE:-automatic}" || echo "The update wasn't published; building the site as it is."
|
|
21
|
+
fi
|
|
12
22
|
# Pages may serve the site from a subfolder, such as https://group.gitlab.io/site/.
|
|
13
23
|
- export GOODFELLOW_BASE="$(node -e 'console.log(new URL(process.env.CI_PAGES_URL).pathname)')"
|
|
14
24
|
- npx next build
|
package/templates/next/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# My site
|
|
2
2
|
|
|
3
|
-
A website built with [Goodfellow](https://github.com/
|
|
3
|
+
A website built with [Goodfellow](https://github.com/goodfellow-cms/goodfellow-cms) and [Next.js](https://nextjs.org). Pages are files in `content/`, edited in the admin panel at `/admin`, and built into static files that a free host serves.
|
|
4
4
|
|
|
5
5
|
## Working on the site
|
|
6
6
|
|
|
@@ -27,14 +27,14 @@ The News page and its stories are an example of a collection: a group of similar
|
|
|
27
27
|
| `app/(site)/[[...path]]/page.tsx` | Every page in `content/`, rendered as Server Components. Links between pages use `next/link`, and images `next/image`. |
|
|
28
28
|
| `app/not-found.tsx` | The site's "Page not found" page, `content/pages/404.json` |
|
|
29
29
|
| `app/admin/page.tsx` | The admin panel |
|
|
30
|
-
| `app/admin/demo-content.json/route.ts` | For a [demo](https://github.
|
|
30
|
+
| `app/admin/demo-content.json/route.ts` | For a [demo](https://goodfellow-cms.github.io/goodfellow-cms/docs/demo-mode) (`demo: true` in the config), the copy of the content its admin panel starts from |
|
|
31
31
|
| `app/site.css` | The site's styles: Tailwind, the theme, and the custom CSS from the admin panel |
|
|
32
32
|
| `app/layout.tsx`, `app/sitemap.ts`, `app/robots.ts` | The page around everything, and files for search engines |
|
|
33
33
|
| `next.config.ts` | `withGoodfellow()` builds static files, serves the site from a subfolder when the host needs it, and runs the admin panel's local backend in `next dev` |
|
|
34
34
|
|
|
35
35
|
Your own Next.js pages can go beside these, in `app/`, and import from the project root as `@/`, such as `@/lib/site`.
|
|
36
36
|
|
|
37
|
-
More blocks, such as an FAQ, tabs and a pricing table, can be added on the admin panel's **Blocks** screen, which publishes their code into `blocks/installed/` and the shadcn components they use into `components/ui/`. See [
|
|
37
|
+
More blocks, such as an FAQ, tabs and a pricing table, can be added on the admin panel's **Blocks** screen, which publishes their code into `blocks/installed/` and the shadcn components they use into `components/ui/`. See [Add blocks](https://goodfellow-cms.github.io/goodfellow-cms/docs/add-blocks).
|
|
38
38
|
|
|
39
39
|
Blocks are React components in `blocks/`, added to the editor in `goodfellow.config.tsx`. For links and images, use `SiteLink` and `SiteImage` from `@goodfellow-cms/react` rather than `<a>` and `<img>`, so they follow the site's address and use `next/link` and `next/image`. Read the site's settings, menus and collections with `useSite()`.
|
|
40
40
|
|
|
@@ -48,7 +48,14 @@ Setup takes four steps. It's done once, usually by whoever set up the site.
|
|
|
48
48
|
|
|
49
49
|
### 1. Store the site on GitHub or GitLab
|
|
50
50
|
|
|
51
|
-
Create
|
|
51
|
+
Create an empty repository on GitHub or GitLab, without a README, and push this folder to it:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
git remote add origin https://github.com/your-name/your-site.git # unless origin is already set
|
|
55
|
+
git push -u origin main
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`create-goodfellow` makes the new site a git repository on the branch `main`, with everything, `package-lock.json` included, in its first commit, and sets `origin` when it's given the repository. Commit any changes since (`git add -A` and `git commit`) before pushing.
|
|
52
59
|
|
|
53
60
|
### 2. Tell Goodfellow where the site is stored
|
|
54
61
|
|
|
@@ -70,7 +77,7 @@ Also set the site's address under **Site settings → General → Site address**
|
|
|
70
77
|
|
|
71
78
|
### 3. Choose a host
|
|
72
79
|
|
|
73
|
-
Each host's free plan has its own rules about business use. If the site is for a business, sells anything, shows ads, or is built or looked after by someone who's paid for it, check those rules first. As of October 2026, GitHub Pages and Vercel's free plan don't allow most of these, and GitLab Pages has no rule against them that we know of. See [
|
|
80
|
+
Each host's free plan has its own rules about business use. If the site is for a business, sells anything, shows ads, or is built or looked after by someone who's paid for it, check those rules first. As of October 2026, GitHub Pages and Vercel's free plan don't allow most of these, and GitLab Pages has no rule against them that we know of. See [Hosts and business sites](https://goodfellow-cms.github.io/goodfellow-cms/docs/hosts) in Goodfellow's documentation.
|
|
74
81
|
|
|
75
82
|
**GitHub Pages** (free for public repositories; not for online businesses or shops)
|
|
76
83
|
|
|
@@ -85,7 +92,7 @@ Each host's free plan has its own rules about business use. If the site is for a
|
|
|
85
92
|
|
|
86
93
|
**Vercel** (works with GitHub and GitLab; the free plan is for non-commercial sites only)
|
|
87
94
|
|
|
88
|
-
1. Import the repository in Vercel, which recognizes it as a Next.js site.
|
|
95
|
+
1. Import the repository in Vercel, which recognizes it as a Next.js site, and sends the site's redirects itself.
|
|
89
96
|
2. For nightly rebuilds, create a deploy hook under **Settings → Git → Deploy Hooks**, and call it from a scheduled GitHub Action or GitLab pipeline schedule.
|
|
90
97
|
|
|
91
98
|
Delete the setup files for the hosts you don't use.
|
|
@@ -113,7 +120,7 @@ Upload images and files on the admin panel's **Media** screen, or with the **Cho
|
|
|
113
120
|
|
|
114
121
|
## Writing with AI
|
|
115
122
|
|
|
116
|
-
The admin panel's **AI** tab can draft pages, rewrite blocks and fill in news stories. Each editor picks an AI service there. Claude and some others need the editor's own API key and charge for use; there are free options, including copying the request into a chat app such as Claude.ai. To offer only some services, or to turn the assistant off, see `ai` in [
|
|
123
|
+
The admin panel's **AI** tab can draft pages, rewrite blocks and fill in news stories. Each editor picks an AI service there. Claude and some others need the editor's own API key and charge for use; there are free options, including copying the request into a chat app such as Claude.ai. To offer only some services, or to turn the assistant off, see `ai` in [Configuration](https://goodfellow-cms.github.io/goodfellow-cms/docs/configuration).
|
|
117
124
|
|
|
118
125
|
## Publishing
|
|
119
126
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { Metadata } from "next";
|
|
2
1
|
import type { ReactNode } from "react";
|
|
2
|
+
import { site } from "@/lib/site";
|
|
3
3
|
|
|
4
|
-
export const
|
|
4
|
+
export const generateMetadata = site.adminMetadata;
|
|
5
5
|
|
|
6
6
|
export default function AdminLayout({ children }: { children: ReactNode }) {
|
|
7
7
|
return children;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# Builds the site and publishes it with GitHub Pages whenever the main branch
|
|
2
2
|
# changes (including every Publish in the admin panel), and once a night so
|
|
3
|
-
# date-based content stays current.
|
|
4
|
-
#
|
|
3
|
+
# date-based content stays current. Each night it first installs Goodfellow's
|
|
4
|
+
# newest fixes, and publishes them only if the site still builds with them,
|
|
5
|
+
# unless content/updates.json turns automatic updates off.
|
|
6
|
+
# In the repository's Settings → Pages, set "Source" to "GitHub Actions".
|
|
7
|
+
# See README.md.
|
|
5
8
|
name: Deploy to GitHub Pages
|
|
6
9
|
|
|
7
10
|
on:
|
|
@@ -10,6 +13,11 @@ on:
|
|
|
10
13
|
schedule:
|
|
11
14
|
- cron: "17 4 * * *"
|
|
12
15
|
workflow_dispatch:
|
|
16
|
+
inputs:
|
|
17
|
+
update:
|
|
18
|
+
description: "Update Goodfellow first: fixes, latest, or a release such as 0.5.0 (an older one goes back to it). Leave empty to only build."
|
|
19
|
+
required: false
|
|
20
|
+
default: ""
|
|
13
21
|
|
|
14
22
|
permissions:
|
|
15
23
|
contents: read
|
|
@@ -21,10 +29,36 @@ concurrency:
|
|
|
21
29
|
cancel-in-progress: false
|
|
22
30
|
|
|
23
31
|
jobs:
|
|
32
|
+
update:
|
|
33
|
+
# Each night, and when the admin panel's Updates screen asks.
|
|
34
|
+
if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.update != '')
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
permissions:
|
|
37
|
+
contents: write
|
|
38
|
+
outputs:
|
|
39
|
+
sha: ${{ steps.update.outputs.sha }}
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v5
|
|
42
|
+
- uses: actions/setup-node@v5
|
|
43
|
+
with:
|
|
44
|
+
node-version: 22
|
|
45
|
+
cache: npm
|
|
46
|
+
- run: npm ci
|
|
47
|
+
- id: update
|
|
48
|
+
env:
|
|
49
|
+
TO: ${{ inputs.update || 'automatic' }}
|
|
50
|
+
run: npx goodfellow update --publish --to "$TO"
|
|
51
|
+
|
|
24
52
|
build:
|
|
53
|
+
needs: update
|
|
54
|
+
# Also when there was no update to run, or it didn't work: the site is built either way.
|
|
55
|
+
if: ${{ !cancelled() }}
|
|
25
56
|
runs-on: ubuntu-latest
|
|
26
57
|
steps:
|
|
27
58
|
- uses: actions/checkout@v5
|
|
59
|
+
with:
|
|
60
|
+
# The update's commit, when it published one.
|
|
61
|
+
ref: ${{ needs.update.outputs.sha || github.sha }}
|
|
28
62
|
- uses: actions/setup-node@v5
|
|
29
63
|
with:
|
|
30
64
|
node-version: 22
|
|
@@ -40,6 +74,7 @@ jobs:
|
|
|
40
74
|
|
|
41
75
|
deploy:
|
|
42
76
|
needs: build
|
|
77
|
+
if: ${{ !cancelled() && needs.build.result == 'success' }}
|
|
43
78
|
runs-on: ubuntu-latest
|
|
44
79
|
environment:
|
|
45
80
|
name: github-pages
|