create-goodfellow 0.2.0 → 0.4.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/dist/cli.js +213 -51
- package/dist/index.d.ts +8 -2
- package/dist/index.js +1 -1
- package/dist/{scaffold-BGxzPdN1.js → scaffold-BzMu8DuO.js} +11 -4
- package/package.json +3 -3
- package/templates/next/.github/workflows/deploy.yml +1 -1
- package/templates/next/.gitlab-ci.yml +1 -1
- package/templates/next/_gitignore +3 -0
- package/templates/next/app/goodfellow-site.json/route.ts +5 -0
- package/templates/next/app/layout.tsx +2 -1
- package/templates/next/content/site.json +3 -1
- package/templates/parish/content/site.json +3 -1
- package/templates/registry/custom-html.json +1 -1
- package/templates/registry/registry.json +10 -10
- 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/content/site.json +3 -1
- package/templates/versions.json +12 -11
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,99 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { _ as parseRepo, a as HOSTS, c as commitAll, d as contentRepositoryDir, h as installPackages, i as CONTENT_REPOSITORY_DIR, m as initGitRepository, o as ScaffoldError, p as hostsFor, r as CONTENT_BUILDS, s as TEMPLATES, t as BLOCK_CHOICES, u as contentBuildsFor, y as scaffold } from "./scaffold-BzMu8DuO.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";
|
|
5
5
|
import { createInterface } from "node:readline/promises";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
import { parseArgs } from "node:util";
|
|
7
|
+
import { parseArgs, stripVTControlCharacters } from "node:util";
|
|
8
|
+
import { emitKeypressEvents } from "node:readline";
|
|
9
|
+
//#region src/select.ts
|
|
10
|
+
const HIDE_CURSOR = "\x1B[?25l";
|
|
11
|
+
const SHOW_CURSOR = "\x1B[?25h";
|
|
12
|
+
/** How many rows `lines` take up in a terminal `columns` wide, counting lines that wrap. */
|
|
13
|
+
function rowsFor(lines, columns) {
|
|
14
|
+
const width = columns && columns > 0 ? columns : 80;
|
|
15
|
+
return lines.reduce((rows, line) => rows + Math.max(1, Math.ceil(stripVTControlCharacters(line).length / width)), 0);
|
|
16
|
+
}
|
|
17
|
+
function painter(color) {
|
|
18
|
+
const paint = (open, close) => (text) => color ? `\x1b[${open}m${text}\x1b[${close}m` : text;
|
|
19
|
+
return {
|
|
20
|
+
bold: paint(1, 22),
|
|
21
|
+
dim: paint(2, 22),
|
|
22
|
+
cyan: paint(36, 39)
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** The menu's lines, with the choice at `index` marked. */
|
|
26
|
+
function menuLines(question, choices, index, color = false) {
|
|
27
|
+
const { bold, dim, cyan } = painter(color);
|
|
28
|
+
return [
|
|
29
|
+
bold(question),
|
|
30
|
+
...choices.flatMap((choice, i) => {
|
|
31
|
+
return [i === index ? cyan(`› ${choice.label}`) : ` ${choice.label}`, ...choice.note ? [dim(` ${choice.note}`)] : []];
|
|
32
|
+
}),
|
|
33
|
+
dim(" ↑ and ↓ to move, Enter to choose")
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
var AbortError = class extends Error {
|
|
37
|
+
code = "ABORT_ERR";
|
|
38
|
+
constructor() {
|
|
39
|
+
super("Stopped.");
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Asks `question` with a menu of `choices` that the arrow keys move through, as
|
|
44
|
+
* `create-next-app` does. Enter chooses, and the menu is replaced by the answer.
|
|
45
|
+
* Ctrl+C rejects with an error whose `code` is `"ABORT_ERR"`. The input must be
|
|
46
|
+
* a terminal, and nothing else may be reading it meanwhile.
|
|
47
|
+
*/
|
|
48
|
+
function select(question, choices, input = process.stdin, output = process.stdout) {
|
|
49
|
+
const color = output.hasColors?.() ?? false;
|
|
50
|
+
const { cyan } = painter(color);
|
|
51
|
+
let index = 0;
|
|
52
|
+
let rows = 0;
|
|
53
|
+
const draw = () => {
|
|
54
|
+
const lines = menuLines(question, choices, index, color);
|
|
55
|
+
output.write(`${rows > 0 ? `\x1b[${rows}A\r\x1b[J` : ""}${lines.join("\n")}\n`);
|
|
56
|
+
rows = rowsFor(lines, output.columns);
|
|
57
|
+
};
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
const wasRaw = input.isRaw ?? false;
|
|
60
|
+
const finish = (answer) => {
|
|
61
|
+
input.off("keypress", onKey);
|
|
62
|
+
input.setRawMode?.(wasRaw);
|
|
63
|
+
input.pause();
|
|
64
|
+
output.write(`\x1b[${rows}A\r\x1b[J${answer === void 0 ? "" : `${question} ${cyan(answer)}\n`}${SHOW_CURSOR}`);
|
|
65
|
+
};
|
|
66
|
+
const onKey = (text, key) => {
|
|
67
|
+
const name = key?.name;
|
|
68
|
+
if (key?.ctrl && name === "c") {
|
|
69
|
+
finish(void 0);
|
|
70
|
+
reject(new AbortError());
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (name === "return" || name === "enter") {
|
|
74
|
+
const choice = choices[index];
|
|
75
|
+
finish(choice.label);
|
|
76
|
+
resolve(choice.value);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const last = choices.length - 1;
|
|
80
|
+
if (name === "up" || name === "k" || name === "tab" && key?.shift) index = index === 0 ? last : index - 1;
|
|
81
|
+
else if (name === "down" || name === "j" || name === "tab") index = index === last ? 0 : index + 1;
|
|
82
|
+
else if (name === "home") index = 0;
|
|
83
|
+
else if (name === "end") index = last;
|
|
84
|
+
else if (text && /^[1-9]$/.test(text) && Number(text) <= choices.length) index = Number(text) - 1;
|
|
85
|
+
else return;
|
|
86
|
+
draw();
|
|
87
|
+
};
|
|
88
|
+
emitKeypressEvents(input);
|
|
89
|
+
input.setRawMode?.(true);
|
|
90
|
+
input.on("keypress", onKey);
|
|
91
|
+
input.resume();
|
|
92
|
+
output.write(HIDE_CURSOR);
|
|
93
|
+
draw();
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
8
97
|
//#region src/cli.ts
|
|
9
98
|
const HELP = `Creates a new Goodfellow site.
|
|
10
99
|
|
|
@@ -15,6 +104,10 @@ Options:
|
|
|
15
104
|
--github <repo> The GitHub repository the site will be stored in, such as your-name/your-site
|
|
16
105
|
--gitlab <project> The GitLab project the site will be stored in, such as your-group/your-site
|
|
17
106
|
--host <host> github-pages, gitlab-pages or vercel. Keeps only that host's setup file.
|
|
107
|
+
--content <repo> Keeps the site's content in a repository of its own, such as
|
|
108
|
+
your-name/your-site-content; --github or --gitlab is then the code's
|
|
109
|
+
--build <where> With --content on Vercel: content (the default) or code, the
|
|
110
|
+
repository Vercel builds the site from
|
|
18
111
|
--blocks <choice> recommended (the default) to add the recommended blocks, or built-in for Goodfellow's own only
|
|
19
112
|
--no-install Don't run npm install
|
|
20
113
|
--no-git Don't make the site a git repository, or commit it
|
|
@@ -27,21 +120,36 @@ const HOST_NAMES = {
|
|
|
27
120
|
github: "GitHub",
|
|
28
121
|
gitlab: "GitLab"
|
|
29
122
|
};
|
|
30
|
-
|
|
31
|
-
|
|
123
|
+
/** Asks one question. Each has its own interface, since menus read the terminal themselves in between. */
|
|
124
|
+
async function ask(question) {
|
|
125
|
+
const rl = createInterface({
|
|
126
|
+
input: stdin,
|
|
127
|
+
output: stdout
|
|
128
|
+
});
|
|
129
|
+
try {
|
|
130
|
+
return await rl.question(question);
|
|
131
|
+
} finally {
|
|
132
|
+
rl.close();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** A menu the arrow keys move through, or numbered choices where the output isn't a terminal. */
|
|
136
|
+
async function choose(question, choices) {
|
|
137
|
+
stdout.write("\n");
|
|
138
|
+
if (stdout.isTTY) return select(question, choices);
|
|
139
|
+
stdout.write(`${question}\n`);
|
|
32
140
|
choices.forEach((choice, index) => {
|
|
33
141
|
stdout.write(` ${index + 1}. ${choice.label}${choice.note ? `\n ${choice.note}` : ""}\n`);
|
|
34
142
|
});
|
|
35
143
|
for (;;) {
|
|
36
|
-
const answer = (await
|
|
144
|
+
const answer = (await ask(`Choose 1-${choices.length} (1): `)).trim();
|
|
37
145
|
const choice = choices[answer === "" ? 0 : Number(answer) - 1];
|
|
38
146
|
if (choice) return choice.value;
|
|
39
147
|
}
|
|
40
148
|
}
|
|
41
|
-
async function askRepo(
|
|
149
|
+
async function askRepo(host) {
|
|
42
150
|
const example = host === "github" ? "your-name/your-site" : "your-group/your-site";
|
|
43
151
|
for (;;) {
|
|
44
|
-
const answer = await
|
|
152
|
+
const answer = await ask(`\nThe ${HOST_NAMES[host]} ${host === "github" ? "repository" : "project"}, such as ${example} (leave empty if it doesn't exist yet): `);
|
|
45
153
|
if (answer.trim() === "") return void 0;
|
|
46
154
|
const repo = parseRepo(host, answer);
|
|
47
155
|
if (repo) return repo;
|
|
@@ -74,6 +182,8 @@ async function main() {
|
|
|
74
182
|
gitlab: { type: "string" },
|
|
75
183
|
host: { type: "string" },
|
|
76
184
|
blocks: { type: "string" },
|
|
185
|
+
content: { type: "string" },
|
|
186
|
+
build: { type: "string" },
|
|
77
187
|
"no-git": { type: "boolean" },
|
|
78
188
|
"no-install": { type: "boolean" },
|
|
79
189
|
yes: {
|
|
@@ -98,52 +208,64 @@ async function main() {
|
|
|
98
208
|
if (host !== void 0 && !isHost(host)) throw new ScaffoldError(`--host takes ${Object.keys(HOSTS).join(", ")}.`);
|
|
99
209
|
let blocks = values.blocks;
|
|
100
210
|
if (blocks !== void 0 && !(blocks in BLOCK_CHOICES)) throw new ScaffoldError(`--blocks takes ${Object.keys(BLOCK_CHOICES).join(" or ")}.`);
|
|
211
|
+
let build = values.build;
|
|
212
|
+
if (build !== void 0 && !Object.hasOwn(CONTENT_BUILDS, build)) throw new ScaffoldError(`--build takes ${Object.keys(CONTENT_BUILDS).join(" or ")}.`);
|
|
213
|
+
let contentRepo = values.content;
|
|
101
214
|
if (stdin.isTTY && !values.yes) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
value,
|
|
138
|
-
label,
|
|
139
|
-
note:
|
|
140
|
-
})));
|
|
141
|
-
} finally {
|
|
142
|
-
rl.close();
|
|
215
|
+
if (!folder) folder = (await ask("Folder for the new site (my-site): ")).trim() || "my-site";
|
|
216
|
+
template ??= await choose("Start from:", Object.entries(TEMPLATES).map(([value, { label, description }]) => ({
|
|
217
|
+
value,
|
|
218
|
+
label,
|
|
219
|
+
note: description
|
|
220
|
+
})));
|
|
221
|
+
let storage = backend?.host;
|
|
222
|
+
if (!backend) {
|
|
223
|
+
storage = await choose("Where will the site be stored? Editors sign in there, and publishing saves to it.", [
|
|
224
|
+
{
|
|
225
|
+
value: "github",
|
|
226
|
+
label: "GitHub"
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
value: "gitlab",
|
|
230
|
+
label: "GitLab"
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
value: void 0,
|
|
234
|
+
label: "Not decided yet"
|
|
235
|
+
}
|
|
236
|
+
]);
|
|
237
|
+
const repo = storage && await askRepo(storage);
|
|
238
|
+
if (storage && repo) backend = {
|
|
239
|
+
host: storage,
|
|
240
|
+
repo
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
host ??= await chooseHost(storage);
|
|
244
|
+
if (backend && host && contentRepo === void 0) {
|
|
245
|
+
if (await choose("Where should the site's content be kept?", [{
|
|
246
|
+
value: false,
|
|
247
|
+
label: "With the code",
|
|
248
|
+
note: "One repository for everything."
|
|
249
|
+
}, {
|
|
250
|
+
value: true,
|
|
251
|
+
label: "In a repository of its own",
|
|
252
|
+
note: "Editors can change the content but never the code. For organizations with a developer."
|
|
253
|
+
}])) contentRepo = await askContentRepo(backend);
|
|
143
254
|
}
|
|
255
|
+
if (contentRepo !== void 0 && host && build === void 0 && contentBuildsFor(host).length > 1) build = await choose(`Which repository should ${HOSTS[host].label} build the site from?`, contentBuildsFor(host).toReversed().map((value) => ({
|
|
256
|
+
value,
|
|
257
|
+
label: CONTENT_BUILDS[value].label,
|
|
258
|
+
note: CONTENT_BUILDS[value].description
|
|
259
|
+
})));
|
|
260
|
+
blocks ??= await choose("Which blocks should the editor offer to start with?", Object.entries(BLOCK_CHOICES).map(([value, { label, description }]) => ({
|
|
261
|
+
value,
|
|
262
|
+
label,
|
|
263
|
+
note: description
|
|
264
|
+
})));
|
|
144
265
|
}
|
|
145
266
|
if (!folder) throw new ScaffoldError("Say which folder to create the site in, such as: npm create goodfellow@latest my-site");
|
|
146
267
|
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]}.`);
|
|
268
|
+
const content = contentRepo === void 0 ? void 0 : separateContent(contentRepo, build, backend, host);
|
|
147
269
|
const target = resolve(folder);
|
|
148
270
|
const versions = JSON.parse(readFileSync(join(templatesDir, "versions.json"), "utf8"));
|
|
149
271
|
await scaffold({
|
|
@@ -152,8 +274,15 @@ async function main() {
|
|
|
152
274
|
versions,
|
|
153
275
|
...backend && { backend },
|
|
154
276
|
...host && { host },
|
|
155
|
-
...(blocks ?? "recommended") === "recommended" && { registryDir: join(templatesDir, "registry") }
|
|
277
|
+
...(blocks ?? "recommended") === "recommended" && { registryDir: join(templatesDir, "registry") },
|
|
278
|
+
...content && { content }
|
|
156
279
|
});
|
|
280
|
+
const contentDir = contentRepositoryDir(target);
|
|
281
|
+
const contentGit = content && backend && !values["no-git"] ? initGitRepository(contentDir, {
|
|
282
|
+
host: backend.host,
|
|
283
|
+
repo: content.repo
|
|
284
|
+
}) : void 0;
|
|
285
|
+
const contentCommit = contentGit?.created ? commitAll(contentDir, "Create the site's content") : void 0;
|
|
157
286
|
const git = values["no-git"] ? void 0 : initGitRepository(target, backend);
|
|
158
287
|
let installed = false;
|
|
159
288
|
if (!values["no-install"]) {
|
|
@@ -167,6 +296,7 @@ async function main() {
|
|
|
167
296
|
"",
|
|
168
297
|
`Created a new site in ${where}.`,
|
|
169
298
|
...git ? gitLines(git, commit, installed) : [],
|
|
299
|
+
...content ? contentLines(content, contentGit, contentCommit) : [],
|
|
170
300
|
...!installed && !values["no-install"] ? ["npm install didn't finish, so run it again in the site's folder."] : [],
|
|
171
301
|
"",
|
|
172
302
|
"Next:",
|
|
@@ -179,6 +309,38 @@ async function main() {
|
|
|
179
309
|
if (!backend) lines.push("Before you do, say where the site is stored in goodfellow.config.tsx; README.md shows how.");
|
|
180
310
|
stdout.write(`${lines.join("\n")}\n`);
|
|
181
311
|
}
|
|
312
|
+
/** Asks for the content repository, offering the code's with `-content` after it. */
|
|
313
|
+
async function askContentRepo(backend) {
|
|
314
|
+
const suggested = `${backend.repo}-content`;
|
|
315
|
+
for (;;) {
|
|
316
|
+
const answer = (await ask(`\nThe ${HOST_NAMES[backend.host]} ${backend.host === "github" ? "repository" : "project"} for the content (${suggested}): `)).trim();
|
|
317
|
+
if (answer === "") return suggested;
|
|
318
|
+
const repo = parseRepo(backend.host, answer);
|
|
319
|
+
if (repo && repo !== backend.repo) return repo;
|
|
320
|
+
stdout.write(repo ? "The content needs a repository of its own, not the code's.\n" : `That doesn't look like a ${HOST_NAMES[backend.host]} address. Type it as ${suggested}.\n`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
/** The content repository as the options or answers gave it, checked against the rest. */
|
|
324
|
+
function separateContent(repo, build, backend, host) {
|
|
325
|
+
if (!backend || !host) throw new ScaffoldError("Keeping the content in a repository of its own needs the code's repository (--github or --gitlab) and a host (--host).");
|
|
326
|
+
const parsed = parseRepo(backend.host, repo);
|
|
327
|
+
if (!parsed || parsed === backend.repo) throw new ScaffoldError("--content takes a repository of its own for the content, such as your-name/your-site-content.");
|
|
328
|
+
const builds = contentBuildsFor(host);
|
|
329
|
+
const chosen = build ?? builds.at(-1);
|
|
330
|
+
if (!builds.includes(chosen)) throw new ScaffoldError(`${HOSTS[host].label} can only build the site from the content repository.`);
|
|
331
|
+
return {
|
|
332
|
+
repo: parsed,
|
|
333
|
+
build: chosen
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
/** Where the content went, and what's left to do with it. */
|
|
337
|
+
function contentLines(content, git, commit) {
|
|
338
|
+
const lines = [`Its content is in ${CONTENT_REPOSITORY_DIR}/, which content/ and public/media/ link to: a repository of its own for ${content.repo}, which the site's repository ignores.`];
|
|
339
|
+
if (git?.created && commit === "committed") lines.push(`${CONTENT_REPOSITORY_DIR}/ has its content in its first commit.`);
|
|
340
|
+
else if (git?.created) lines.push(`Commit everything in ${CONTENT_REPOSITORY_DIR}/ as well.`);
|
|
341
|
+
lines.push("Push both repositories. README.md says what each needs to build the site.");
|
|
342
|
+
return lines;
|
|
343
|
+
}
|
|
182
344
|
/** What happened with git, and what's left to do. */
|
|
183
345
|
function gitLines(git, commit, installed) {
|
|
184
346
|
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."];
|
|
@@ -198,10 +360,10 @@ function gitLines(git, commit, installed) {
|
|
|
198
360
|
];
|
|
199
361
|
return [`${repository}. Nothing is committed yet: commit everything once npm install has run.`, ...origin];
|
|
200
362
|
}
|
|
201
|
-
async function chooseHost(
|
|
363
|
+
async function chooseHost(storage) {
|
|
202
364
|
stdout.write("\nEach host's free plan has its own rules about business use. Check them if the site is for a business,");
|
|
203
365
|
stdout.write("\nsells anything, shows ads, or anyone is paid to build or look after it.\n");
|
|
204
|
-
return choose(
|
|
366
|
+
return choose("Where will the site be hosted?", [...hostsFor(storage).map((value) => ({
|
|
205
367
|
value,
|
|
206
368
|
label: HOSTS[value].label,
|
|
207
369
|
note: HOSTS[value].note
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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
|
+
import { BLOCK_CHOICES, BUNDLED_GITIGNORE, Backend, Backend as Backend$1, BlockChoice, HOSTS, Host, Host as Host$1, SeparateContent, SiteFiles, TEMPLATES, TemplateName, configureBackend, hostsFor, parseRepo, sitePackageJson, withoutRepositoryNotes } from "@goodfellow-cms/core";
|
|
2
2
|
//#region src/scaffold.d.ts
|
|
3
3
|
/** A problem the person creating the site can fix, described in plain words. */
|
|
4
4
|
export declare class ScaffoldError extends Error {
|
|
@@ -27,8 +27,14 @@ interface ScaffoldOptions {
|
|
|
27
27
|
host?: Host$1;
|
|
28
28
|
/** The built block registry to install recommended blocks from. Without it, the site has the built-in blocks only. */
|
|
29
29
|
registryDir?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Keeps the content in a repository of its own, in the site's
|
|
32
|
+
* `content-repository/` folder, which `content/` and `public/media/` link to.
|
|
33
|
+
* `backend` is then the code's repository.
|
|
34
|
+
*/
|
|
35
|
+
content?: SeparateContent;
|
|
30
36
|
}
|
|
31
37
|
/** Creates a new site in `target` from a template. */
|
|
32
|
-
export declare function scaffold({ template, target, versions, backend, host, registryDir }: ScaffoldOptions): Promise<void>;
|
|
38
|
+
export declare function scaffold({ template, target, versions, backend, host, registryDir, content }: ScaffoldOptions): Promise<void>;
|
|
33
39
|
//#endregion
|
|
34
40
|
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 { _ as
|
|
1
|
+
import { _ as parseRepo, a as HOSTS, b as sitePackageJson, f as copyTemplate, g as packageName, l as configureBackend, n as BUNDLED_GITIGNORE, o as ScaffoldError, p as hostsFor, s as TEMPLATES, t as BLOCK_CHOICES, v as readTemplate, x as withoutRepositoryNotes, y as scaffold } from "./scaffold-BzMu8DuO.js";
|
|
2
2
|
export { BLOCK_CHOICES, BUNDLED_GITIGNORE, HOSTS, ScaffoldError, TEMPLATES, configureBackend, copyTemplate, hostsFor, packageName, parseRepo, readTemplate, scaffold, sitePackageJson, withoutRepositoryNotes };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { cp, mkdir, readFile, readdir, rename, writeFile } from "node:fs/promises";
|
|
3
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";
|
|
4
|
+
import { BLOCK_CHOICES, BUNDLED_GITIGNORE, BUNDLED_GITIGNORE as BUNDLED_GITIGNORE$1, CONTENT_BUILDS, CONTENT_REPOSITORY_DIR, CONTENT_REPOSITORY_DIR as CONTENT_REPOSITORY_DIR$1, GOODFELLOW_REGISTRY, HOSTS, TEMPLATES, configureBackend, contentBuildsFor, hostsFor, parseRepo, planSite, sitePackageJson, sitePackageName, withoutRepositoryNotes } from "@goodfellow-cms/core";
|
|
5
|
+
import { setUpContentRepository } from "@goodfellow-cms/core/node";
|
|
5
6
|
//#region src/scaffold.ts
|
|
6
7
|
/** A problem the person creating the site can fix, described in plain words. */
|
|
7
8
|
var ScaffoldError = class extends Error {
|
|
@@ -130,7 +131,7 @@ function commitAll(target, message) {
|
|
|
130
131
|
return /user\.(name|email)|tell me who you are|identity/i.test(commit.stderr) ? "no-identity" : "failed";
|
|
131
132
|
}
|
|
132
133
|
/** Creates a new site in `target` from a template. */
|
|
133
|
-
async function scaffold({ template, target, versions, backend, host, registryDir }) {
|
|
134
|
+
async function scaffold({ template, target, versions, backend, host, registryDir, content }) {
|
|
134
135
|
if (!await isEmptyFolder(target)) throw new ScaffoldError(`The folder ${target} already has files in it. Choose a new folder for the site.`);
|
|
135
136
|
const files = await planSite({
|
|
136
137
|
template: await readTemplate(template),
|
|
@@ -138,13 +139,19 @@ async function scaffold({ template, target, versions, backend, host, registryDir
|
|
|
138
139
|
versions,
|
|
139
140
|
backend,
|
|
140
141
|
host,
|
|
141
|
-
registry: registryDir ? localRegistry(registryDir) : void 0
|
|
142
|
+
registry: registryDir ? localRegistry(registryDir) : void 0,
|
|
143
|
+
content
|
|
142
144
|
});
|
|
143
145
|
await mkdir(target, { recursive: true });
|
|
144
146
|
for (const [path, file] of files) {
|
|
145
147
|
await mkdir(dirname(join(target, path)), { recursive: true });
|
|
146
148
|
await writeFile(join(target, path), file);
|
|
147
149
|
}
|
|
150
|
+
if (content) await setUpContentRepository(target);
|
|
151
|
+
}
|
|
152
|
+
/** The content repository's folder, inside the site's. */
|
|
153
|
+
function contentRepositoryDir(target) {
|
|
154
|
+
return join(target, CONTENT_REPOSITORY_DIR);
|
|
148
155
|
}
|
|
149
156
|
//#endregion
|
|
150
|
-
export {
|
|
157
|
+
export { parseRepo as _, HOSTS as a, sitePackageJson as b, commitAll as c, contentRepositoryDir as d, copyTemplate as f, packageName as g, installPackages as h, CONTENT_REPOSITORY_DIR$1 as i, configureBackend as l, initGitRepository as m, BUNDLED_GITIGNORE$1 as n, ScaffoldError as o, hostsFor as p, CONTENT_BUILDS as r, TEMPLATES as s, BLOCK_CHOICES as t, contentBuildsFor as u, readTemplate as v, withoutRepositoryNotes as x, scaffold as y };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-goodfellow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Creates a new Goodfellow site: npm create goodfellow@latest my-site",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"goodfellow",
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
"node": ">=22"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@goodfellow-cms/core": "0.
|
|
42
|
+
"@goodfellow-cms/core": "0.4.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@goodfellow-cms/registry": "0.
|
|
45
|
+
"@goodfellow-cms/registry": "0.4.0",
|
|
46
46
|
"@goodfellow-cms/example-parish": "0.0.0",
|
|
47
47
|
"@goodfellow-cms/template-starter": "0.0.0",
|
|
48
48
|
"@goodfellow-cms/template-next": "0.0.0"
|
|
@@ -67,7 +67,7 @@ jobs:
|
|
|
67
67
|
uses: actions/configure-pages@v5
|
|
68
68
|
- run: npm ci
|
|
69
69
|
# Project sites live in a subfolder (/repository-name/); user and organization sites don't.
|
|
70
|
-
- run:
|
|
70
|
+
- run: npm run build
|
|
71
71
|
env:
|
|
72
72
|
GOODFELLOW_BASE: ${{ steps.pages.outputs.base_path }}/
|
|
73
73
|
- uses: actions/upload-pages-artifact@v4
|
|
@@ -21,7 +21,7 @@ pages:
|
|
|
21
21
|
fi
|
|
22
22
|
# Pages may serve the site from a subfolder, such as https://group.gitlab.io/site/.
|
|
23
23
|
- export GOODFELLOW_BASE="$(node -e 'console.log(new URL(process.env.CI_PAGES_URL).pathname)')"
|
|
24
|
-
-
|
|
24
|
+
- npm run build
|
|
25
25
|
pages:
|
|
26
26
|
publish: out
|
|
27
27
|
rules:
|
|
@@ -4,7 +4,8 @@ import type { ReactNode } from "react";
|
|
|
4
4
|
export default async function RootLayout({ children }: { children: ReactNode }) {
|
|
5
5
|
const { settings } = await loadSite();
|
|
6
6
|
return (
|
|
7
|
-
<html
|
|
7
|
+
// A visitor's light or dark choice is put back on <html> before React starts (see the Dark mode switch block).
|
|
8
|
+
<html lang={settings.language} suppressHydrationWarning>
|
|
8
9
|
<body>{children}</body>
|
|
9
10
|
</html>
|
|
10
11
|
);
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"goodfellow": {
|
|
22
22
|
"category": "Page sections",
|
|
23
23
|
"recommended": true,
|
|
24
|
-
"version": "0.
|
|
24
|
+
"version": "0.4.0"
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
"dependencies": []
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"goodfellow": {
|
|
46
46
|
"category": "Page sections",
|
|
47
47
|
"recommended": true,
|
|
48
|
-
"version": "0.
|
|
48
|
+
"version": "0.4.0"
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
"dependencies": []
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"goodfellow": {
|
|
70
70
|
"category": "Page sections",
|
|
71
71
|
"recommended": true,
|
|
72
|
-
"version": "0.
|
|
72
|
+
"version": "0.4.0"
|
|
73
73
|
}
|
|
74
74
|
},
|
|
75
75
|
"dependencies": []
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"goodfellow": {
|
|
93
93
|
"category": "Page sections",
|
|
94
94
|
"recommended": true,
|
|
95
|
-
"version": "0.
|
|
95
|
+
"version": "0.4.0"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"dependencies": []
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
"meta": {
|
|
120
120
|
"goodfellow": {
|
|
121
121
|
"category": "Page sections",
|
|
122
|
-
"version": "0.
|
|
122
|
+
"version": "0.4.0"
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
125
|
},
|
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
"goodfellow": {
|
|
145
145
|
"category": "Page sections",
|
|
146
146
|
"recommended": true,
|
|
147
|
-
"version": "0.
|
|
147
|
+
"version": "0.4.0"
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
},
|
|
@@ -170,7 +170,7 @@
|
|
|
170
170
|
"meta": {
|
|
171
171
|
"goodfellow": {
|
|
172
172
|
"category": "Advanced",
|
|
173
|
-
"version": "0.
|
|
173
|
+
"version": "0.4.0"
|
|
174
174
|
}
|
|
175
175
|
},
|
|
176
176
|
"dependencies": []
|
|
@@ -197,7 +197,7 @@
|
|
|
197
197
|
"goodfellow": {
|
|
198
198
|
"category": "Interactive",
|
|
199
199
|
"recommended": true,
|
|
200
|
-
"version": "0.
|
|
200
|
+
"version": "0.4.0"
|
|
201
201
|
}
|
|
202
202
|
},
|
|
203
203
|
"dependencies": []
|
|
@@ -224,7 +224,7 @@
|
|
|
224
224
|
"goodfellow": {
|
|
225
225
|
"category": "Interactive",
|
|
226
226
|
"recommended": true,
|
|
227
|
-
"version": "0.
|
|
227
|
+
"version": "0.4.0"
|
|
228
228
|
}
|
|
229
229
|
},
|
|
230
230
|
"dependencies": []
|
|
@@ -250,7 +250,7 @@
|
|
|
250
250
|
"meta": {
|
|
251
251
|
"goodfellow": {
|
|
252
252
|
"category": "Interactive",
|
|
253
|
-
"version": "0.
|
|
253
|
+
"version": "0.4.0"
|
|
254
254
|
}
|
|
255
255
|
},
|
|
256
256
|
"dependencies": []
|
package/templates/versions.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
|
-
"@goodfellow-cms/admin": "0.
|
|
3
|
-
"@goodfellow-cms/ai": "0.
|
|
4
|
-
"@goodfellow-cms/blocks": "0.
|
|
5
|
-
"goodfellow": "0.
|
|
6
|
-
"@goodfellow-cms/core": "0.
|
|
7
|
-
"create-goodfellow": "0.
|
|
8
|
-
"@goodfellow-cms/github": "0.
|
|
9
|
-
"@goodfellow-cms/gitlab": "0.
|
|
10
|
-
"@goodfellow-cms/
|
|
11
|
-
"@goodfellow-cms/
|
|
12
|
-
"@goodfellow-cms/
|
|
2
|
+
"@goodfellow-cms/admin": "0.4.0",
|
|
3
|
+
"@goodfellow-cms/ai": "0.4.0",
|
|
4
|
+
"@goodfellow-cms/blocks": "0.4.0",
|
|
5
|
+
"goodfellow": "0.4.0",
|
|
6
|
+
"@goodfellow-cms/core": "0.4.0",
|
|
7
|
+
"create-goodfellow": "0.4.0",
|
|
8
|
+
"@goodfellow-cms/github": "0.4.0",
|
|
9
|
+
"@goodfellow-cms/gitlab": "0.4.0",
|
|
10
|
+
"@goodfellow-cms/mcp": "0.4.0",
|
|
11
|
+
"@goodfellow-cms/next": "0.4.0",
|
|
12
|
+
"@goodfellow-cms/react": "0.4.0",
|
|
13
|
+
"@goodfellow-cms/registry": "0.4.0"
|
|
13
14
|
}
|