@goodfellow-cms/next 0.6.0 → 0.7.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/bin/goodfellow-next.js +41 -1
- package/dist/config.js +4 -4
- package/dist/{dev-editor-EIZQe3qO.js → dev-editor-CzE9HofK.js} +1 -1
- package/dist/export.d.ts +15 -0
- package/dist/export.js +64 -13
- package/dist/index.js +5 -5
- package/dist/{plugins-eLJalBij.js → plugins-K5i8XztM.js} +17 -11
- package/package.json +9 -9
package/bin/goodfellow-next.js
CHANGED
|
@@ -10,11 +10,13 @@
|
|
|
10
10
|
// content is kept in a repository of its own.
|
|
11
11
|
// `goodfellow-next import wordpress <file or address> [--dry-run]` imports a WordPress site, as `goodfellow import` does.
|
|
12
12
|
// `goodfellow-next export wordpress [file]` writes the site as a WordPress export file, as `goodfellow export` does.
|
|
13
|
+
// `goodfellow-next blocks add <item> [--registry <folder>] [--native <app>]` makes a block of a shadcn item, as
|
|
14
|
+
// `goodfellow blocks add` does, and with --native its version for an Expo app.
|
|
13
15
|
// `goodfellow-next admin add <address>|check|remove` sets the admin address, and `goodfellow-next broker use <address>|remove`
|
|
14
16
|
// the token broker, in goodfellow.config.tsx, as `goodfellow admin` and `goodfellow broker` do.
|
|
15
17
|
import { existsSync, readFileSync } from "node:fs";
|
|
16
18
|
import { rm } from "node:fs/promises";
|
|
17
|
-
import { relative } from "node:path";
|
|
19
|
+
import { relative, resolve } from "node:path";
|
|
18
20
|
import { parseArgs } from "node:util";
|
|
19
21
|
import { buildNotes, CLOUDFLARE_REDIRECTS_LIMIT, configAdmin, formatUpdateResult } from "@goodfellow-cms/core";
|
|
20
22
|
import { reportSiteProblems } from "@goodfellow-cms/core/accessibility";
|
|
@@ -39,8 +41,10 @@ import {
|
|
|
39
41
|
update,
|
|
40
42
|
} from "@goodfellow-cms/core/node";
|
|
41
43
|
import { writePagesSearchIndex, writeSearchIndex } from "@goodfellow-cms/core/search-index";
|
|
44
|
+
import { addShadcnBlock, blockWorks, formatShadcnBlock, ShadcnBlockError } from "@goodfellow-cms/core/shadcn";
|
|
42
45
|
import { WordPressReadError } from "@goodfellow-cms/core/wordpress";
|
|
43
46
|
import {
|
|
47
|
+
blockPageRender,
|
|
44
48
|
buildFileFolders,
|
|
45
49
|
builtPages,
|
|
46
50
|
checkPages,
|
|
@@ -50,6 +54,7 @@ import {
|
|
|
50
54
|
NEXT_SITE_FILES,
|
|
51
55
|
writeBuildRecordFile,
|
|
52
56
|
writeCalendarFiles,
|
|
57
|
+
writeContentApi,
|
|
53
58
|
writeEditorPolicy,
|
|
54
59
|
writeFeedFiles,
|
|
55
60
|
writeFontFiles,
|
|
@@ -80,6 +85,8 @@ if (command === "finish" && dir === "--server") {
|
|
|
80
85
|
if (calendars > 0) console.log(`Wrote ${calendars} calendar files, in public/calendars`);
|
|
81
86
|
const feeds = await writeFeedFiles("public");
|
|
82
87
|
if (feeds > 0) console.log(`Wrote ${feeds} feeds, in public/feeds`);
|
|
88
|
+
const api = await writeContentApi("public");
|
|
89
|
+
if (api !== undefined) console.log(`Wrote the content API, with ${api} pages, in public/api`);
|
|
83
90
|
// Before the pages are read, so the search index and the checks see each page's language.
|
|
84
91
|
const languages = await writePageLanguages(undefined);
|
|
85
92
|
if (languages > 0) console.log(`Marked ${languages} pages with their language`);
|
|
@@ -227,6 +234,31 @@ if (command === "export") {
|
|
|
227
234
|
process.exit(0);
|
|
228
235
|
}
|
|
229
236
|
}
|
|
237
|
+
if (command === "blocks") {
|
|
238
|
+
const { positionals, values } = parseArgs({
|
|
239
|
+
args: process.argv.slice(3),
|
|
240
|
+
allowPositionals: true,
|
|
241
|
+
options: { registry: { type: "string" }, native: { type: "string" } },
|
|
242
|
+
});
|
|
243
|
+
const [action, item] = positionals;
|
|
244
|
+
if (action === "add" && item) {
|
|
245
|
+
try {
|
|
246
|
+
const result = await addShadcnBlock({
|
|
247
|
+
root: process.cwd(),
|
|
248
|
+
item,
|
|
249
|
+
...(values.registry && { registry: resolve(values.registry) }),
|
|
250
|
+
...(values.native && { native: resolve(values.native) }),
|
|
251
|
+
render: blockPageRender("."),
|
|
252
|
+
});
|
|
253
|
+
console.log(formatShadcnBlock(result));
|
|
254
|
+
process.exit(blockWorks(result) ? 0 : 1);
|
|
255
|
+
} catch (error) {
|
|
256
|
+
if (!(error instanceof ShadcnBlockError)) throw error;
|
|
257
|
+
console.error(error.message);
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
230
262
|
if (command !== "finish" && command !== "index") {
|
|
231
263
|
console.error(
|
|
232
264
|
[
|
|
@@ -239,6 +271,7 @@ if (command !== "finish" && command !== "index") {
|
|
|
239
271
|
" goodfellow-next content link|copy [folder]",
|
|
240
272
|
" goodfellow-next import wordpress <file or address> [--dry-run]",
|
|
241
273
|
" goodfellow-next export wordpress [file]",
|
|
274
|
+
" goodfellow-next blocks add <item> [--registry <folder>] [--native <app>]",
|
|
242
275
|
" goodfellow-next admin add <address> | check | remove",
|
|
243
276
|
" goodfellow-next broker use <address> | remove",
|
|
244
277
|
"",
|
|
@@ -269,6 +302,11 @@ if (command !== "finish" && command !== "index") {
|
|
|
269
302
|
" says what it would bring across and changes nothing.",
|
|
270
303
|
"export Writes this site as a WordPress export file (default: named after the site),",
|
|
271
304
|
" which WordPress imports with Tools → Import → WordPress.",
|
|
305
|
+
"blocks Installs a shadcn registry item (button, @acme/hero, or an item's address) with",
|
|
306
|
+
" shadcn's own CLI, makes a block of its component, with fields from its props, and",
|
|
307
|
+
" says what it couldn't do. With --registry, writes the block into the block registry",
|
|
308
|
+
" in folder instead. Needs TypeScript 7 as typescript-7, since Next.js builds with 5:",
|
|
309
|
+
" npm install --save-dev typescript-7@npm:typescript@7.0.2",
|
|
272
310
|
"admin The admin panel's address of its own, such as admin.example.org, in",
|
|
273
311
|
" goodfellow.config.tsx: add names it; check checks it once the site has rebuilt with",
|
|
274
312
|
" it, then starts the admin panel only there; remove stops using it.",
|
|
@@ -288,6 +326,8 @@ if (command === "finish") {
|
|
|
288
326
|
if (calendars > 0) console.log(`Wrote ${calendars} calendar files`);
|
|
289
327
|
const feeds = await writeFeedFiles(dir);
|
|
290
328
|
if (feeds > 0) console.log(`Wrote ${feeds} feeds`);
|
|
329
|
+
const api = await writeContentApi(dir);
|
|
330
|
+
if (api !== undefined) console.log(`Wrote the content API, with ${api} pages, in api/`);
|
|
291
331
|
const languages = await writePageLanguages(dir);
|
|
292
332
|
if (languages > 0) console.log(`Marked ${languages} pages with their language`);
|
|
293
333
|
// Before the redirects' pages, which aren't the site's.
|
package/dist/config.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CALENDARS_DIR, FEEDS_DIR, allPages, brokerOrigin, configAdmin, configBroker, loadSiteContent, normalizeBase, redirectFrom, redirectProblems, shownContent, vercelAdminHeaders } from "@goodfellow-cms/core";
|
|
2
|
-
import { BUILD_DATES_DIR, BUILD_DATES_ENV, DEV_API_PREFIX,
|
|
2
|
+
import { BUILD_DATES_DIR, BUILD_DATES_ENV, DEV_API_PREFIX, builtSiteSource, contentRepositoryProblem, handleDevApi, isLocalOrigin, localFileStore, localRegistryDir } from "@goodfellow-cms/core/node";
|
|
3
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import { rm } from "node:fs/promises";
|
|
5
5
|
import { createServer } from "node:http";
|
|
@@ -41,13 +41,13 @@ function startDevApi(root) {
|
|
|
41
41
|
globals[SERVER_KEY] ??= new Promise((resolvePort, reject) => {
|
|
42
42
|
const store = localFileStore(root);
|
|
43
43
|
const registryDir = localRegistryDir(root);
|
|
44
|
-
const editor = import("./dev-editor-
|
|
44
|
+
const editor = import("./dev-editor-CzE9HofK.js").then(({ startDevEditor }) => startDevEditor(root)).catch((error) => {
|
|
45
45
|
console.error(`The editor's frames can't be served: ${error.message}`);
|
|
46
46
|
return { serve: async () => false };
|
|
47
47
|
});
|
|
48
48
|
const tasks = {
|
|
49
49
|
root,
|
|
50
|
-
plugins: async () => (await (await import("./plugins-
|
|
50
|
+
plugins: async () => (await (await import("./plugins-K5i8XztM.js").then((n) => n.o)).loadSiteConfig(root)).plugins ?? [],
|
|
51
51
|
server: "next-dev"
|
|
52
52
|
};
|
|
53
53
|
const server = createServer(async (req, res) => {
|
|
@@ -101,7 +101,7 @@ function redirectSource(path) {
|
|
|
101
101
|
* Content with a problem gives none, and the build reports the problem.
|
|
102
102
|
*/
|
|
103
103
|
async function siteRedirects(root) {
|
|
104
|
-
const content = await loadSiteContent(
|
|
104
|
+
const content = await loadSiteContent(builtSiteSource(root)).then(shownContent, () => void 0);
|
|
105
105
|
if (!content?.redirects) return [];
|
|
106
106
|
const taken = new Set(allPages(content).map((page) => page.path));
|
|
107
107
|
const problems = redirectProblems(content.redirects);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as loadSiteConfig, t as buildPluginFrame } from "./plugins-
|
|
1
|
+
import { i as loadSiteConfig, t as buildPluginFrame } from "./plugins-K5i8XztM.js";
|
|
2
2
|
import { DEV_EDITOR_PREFIX, EDITOR_PRELUDE, editorPage, pluginId } from "@goodfellow-cms/core";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
package/dist/export.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PluginTaskRequest, SelfHostedResult, SiteProblem, TaskResultFile } from "@goodfellow-cms/core";
|
|
2
2
|
import { SiteFile } from "@goodfellow-cms/core/node";
|
|
3
|
+
import { PageRender } from "@goodfellow-cms/core/shadcn";
|
|
3
4
|
//#region src/site-files.d.ts
|
|
4
5
|
/**
|
|
5
6
|
* The Next.js starter's files that `goodfellow-next update` keeps up to date
|
|
@@ -20,6 +21,14 @@ export declare function writeCalendarFiles(outDir: string, root?: string): Promi
|
|
|
20
21
|
* build` does. Returns how many it wrote: none without the site's address.
|
|
21
22
|
*/
|
|
22
23
|
export declare function writeFeedFiles(outDir: string, root?: string): Promise<number>;
|
|
24
|
+
/**
|
|
25
|
+
* Writes the content API (`/api/`) into the export, as `goodfellow build`
|
|
26
|
+
* does: the site's content as JSON, each page as it's shown, prepared in Node
|
|
27
|
+
* with the site's config as `goodfellow build` prepares it, so both write the
|
|
28
|
+
* same files. Returns how many pages it has, or `undefined` where the
|
|
29
|
+
* config's `api` is `false`.
|
|
30
|
+
*/
|
|
31
|
+
export declare function writeContentApi(outDir: string, root?: string): Promise<number | undefined>;
|
|
23
32
|
/**
|
|
24
33
|
* The folders the calendar files and feeds go in: `calendars/` and `feeds/`,
|
|
25
34
|
* and on a site in several languages, the same under each other language's
|
|
@@ -140,4 +149,10 @@ export declare function checkPages(pages: readonly BuiltPage[], root?: string):
|
|
|
140
149
|
* taken out of the environment before the config loads.
|
|
141
150
|
*/
|
|
142
151
|
export declare function writeTaskResult(root: string, request: PluginTaskRequest, out?: string): Promise<TaskResultFile>;
|
|
152
|
+
/**
|
|
153
|
+
* Renders a page with a block from its own file as well as the site's config's,
|
|
154
|
+
* for `goodfellow-next blocks add`'s check that a block it made renders: both
|
|
155
|
+
* loaded in Node, as `goodfellow-next finish` loads the config.
|
|
156
|
+
*/
|
|
157
|
+
export declare function blockPageRender(root: string): PageRender;
|
|
143
158
|
//#endregion
|
package/dist/export.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { i as loadSiteConfig, n as buildPluginFrames, r as configHasPlugins } from "./plugins-
|
|
1
|
+
import { a as loadSiteModule, i as loadSiteConfig, n as buildPluginFrames, r as configHasPlugins } from "./plugins-K5i8XztM.js";
|
|
2
2
|
import { r as NEXT_SITE_FILES } from "./site-files-BeLnLxCl.js";
|
|
3
|
-
import { CALENDARS_DIR, CLOUDFLARE_REDIRECTS_PATH, EDITOR_PRELUDE, FEEDS_DIR, SITE_DESCRIPTION_PATH, TASK_RESULT_FILE, absoluteUrl, allPages, calendarFiles, cloudflareRedirects, editorPolicy, feedFiles, hasLanguages, loadSiteContent, parseSiteDescription, pathLanguage, pluginId, redirectFiles, rootRobotsUrl, sealTaskAnswer, shownContent, siteLanguages, taskRequestProblem } from "@goodfellow-cms/core";
|
|
4
|
-
import { BUILD_DATES_DIR,
|
|
3
|
+
import { CALENDARS_DIR, CLOUDFLARE_REDIRECTS_PATH, EDITOR_PRELUDE, FEEDS_DIR, SITE_DESCRIPTION_PATH, TASK_RESULT_FILE, absoluteUrl, allPages, calendarFiles, cloudflareRedirects, editorPolicy, feedFiles, hasLanguages, loadSiteContent, parseSiteDescription, pathLanguage, pluginId, redirectFiles, rootRobotsUrl, sealTaskAnswer, shownContent, siteLanguages, sitePages, taskRequestProblem, todayIn } from "@goodfellow-cms/core";
|
|
4
|
+
import { BUILD_DATES_DIR, builtSiteSource, guardBuiltSite, reportedDate, runPluginBuildSteps, runPluginTask, selfHostFonts, taskSecretsFrom, writeBuildRecord } from "@goodfellow-cms/core/node";
|
|
5
5
|
import { access, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
6
6
|
import { dirname, join, relative, resolve } from "node:path";
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
8
|
import { checkBuiltPages } from "@goodfellow-cms/core/accessibility";
|
|
9
|
+
import { CONTENT_API_DIR } from "@goodfellow-cms/core/content-api";
|
|
9
10
|
//#region src/export.ts
|
|
10
11
|
/**
|
|
11
12
|
* Writes the calendar files of the site's collections of events into the
|
|
@@ -13,7 +14,7 @@ import { checkBuiltPages } from "@goodfellow-cms/core/accessibility";
|
|
|
13
14
|
* how many it wrote.
|
|
14
15
|
*/
|
|
15
16
|
async function writeCalendarFiles(outDir, root = process.cwd()) {
|
|
16
|
-
const files = calendarFiles(shownContent(await loadSiteContent(
|
|
17
|
+
const files = calendarFiles(shownContent(await loadSiteContent(builtSiteSource(resolve(root)))));
|
|
17
18
|
for (const file of files) {
|
|
18
19
|
const target = join(outDir, file.path.slice(1));
|
|
19
20
|
await mkdir(dirname(target), { recursive: true });
|
|
@@ -26,7 +27,7 @@ async function writeCalendarFiles(outDir, root = process.cwd()) {
|
|
|
26
27
|
* build` does. Returns how many it wrote: none without the site's address.
|
|
27
28
|
*/
|
|
28
29
|
async function writeFeedFiles(outDir, root = process.cwd()) {
|
|
29
|
-
const files = feedFiles(shownContent(await loadSiteContent(
|
|
30
|
+
const files = feedFiles(shownContent(await loadSiteContent(builtSiteSource(resolve(root)))));
|
|
30
31
|
for (const file of files) {
|
|
31
32
|
const target = join(outDir, file.path.slice(1));
|
|
32
33
|
await mkdir(dirname(target), { recursive: true });
|
|
@@ -35,12 +36,38 @@ async function writeFeedFiles(outDir, root = process.cwd()) {
|
|
|
35
36
|
return files.length;
|
|
36
37
|
}
|
|
37
38
|
/**
|
|
39
|
+
* Writes the content API (`/api/`) into the export, as `goodfellow build`
|
|
40
|
+
* does: the site's content as JSON, each page as it's shown, prepared in Node
|
|
41
|
+
* with the site's config as `goodfellow build` prepares it, so both write the
|
|
42
|
+
* same files. Returns how many pages it has, or `undefined` where the
|
|
43
|
+
* config's `api` is `false`.
|
|
44
|
+
*/
|
|
45
|
+
async function writeContentApi(outDir, root = process.cwd()) {
|
|
46
|
+
const dir = resolve(root);
|
|
47
|
+
const config = await loadSiteConfig(dir);
|
|
48
|
+
if (config.api === false) return void 0;
|
|
49
|
+
const { contentApi } = await import("@goodfellow-cms/react/server");
|
|
50
|
+
const content = shownContent(await loadSiteContent(builtSiteSource(dir)));
|
|
51
|
+
const pages = sitePages(content, todayIn(content.settings.timeZone), config.blocks);
|
|
52
|
+
const files = await contentApi(config, content, pages, { base: await exportBasePath(dir) });
|
|
53
|
+
await rm(join(outDir, CONTENT_API_DIR), {
|
|
54
|
+
recursive: true,
|
|
55
|
+
force: true
|
|
56
|
+
});
|
|
57
|
+
for (const file of files) {
|
|
58
|
+
const target = join(outDir, file.path.slice(1));
|
|
59
|
+
await mkdir(dirname(target), { recursive: true });
|
|
60
|
+
await writeFile(target, file.content);
|
|
61
|
+
}
|
|
62
|
+
return pages.length;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
38
65
|
* The folders the calendar files and feeds go in: `calendars/` and `feeds/`,
|
|
39
66
|
* and on a site in several languages, the same under each other language's
|
|
40
67
|
* code, such as `es/feeds/`.
|
|
41
68
|
*/
|
|
42
69
|
async function buildFileFolders(root = process.cwd()) {
|
|
43
|
-
const { settings } = await loadSiteContent(
|
|
70
|
+
const { settings } = await loadSiteContent(builtSiteSource(resolve(root)));
|
|
44
71
|
return siteLanguages(settings).flatMap((language) => [CALENDARS_DIR, FEEDS_DIR].map((dir) => `${language.prefix.slice(1)}${language.prefix ? "/" : ""}${dir}`));
|
|
45
72
|
}
|
|
46
73
|
/** The `basePath` the export was built with, which Next.js records in `.next/routes-manifest.json`. */
|
|
@@ -92,7 +119,7 @@ async function exists(path) {
|
|
|
92
119
|
* subfolder (`rootRobotsUrl()`).
|
|
93
120
|
*/
|
|
94
121
|
async function writeFontFiles(outDir, root = process.cwd()) {
|
|
95
|
-
const { settings } = await loadSiteContent(
|
|
122
|
+
const { settings } = await loadSiteContent(builtSiteSource(resolve(root)));
|
|
96
123
|
const fonts = await selfHostFonts(outDir, settings.theme, { base: await exportBasePath(root) });
|
|
97
124
|
const robotsElsewhere = rootRobotsUrl(settings.url);
|
|
98
125
|
return {
|
|
@@ -109,7 +136,7 @@ async function writeFontFiles(outDir, root = process.cwd()) {
|
|
|
109
136
|
*/
|
|
110
137
|
async function writeBuildRecordFile(outDir, root = process.cwd()) {
|
|
111
138
|
const dir = resolve(root);
|
|
112
|
-
const content = await loadSiteContent(
|
|
139
|
+
const content = await loadSiteContent(builtSiteSource(dir));
|
|
113
140
|
const date = await reportedDate(join(dir, ".next", BUILD_DATES_DIR));
|
|
114
141
|
return (await writeBuildRecord(outDir, {
|
|
115
142
|
root: dir,
|
|
@@ -126,7 +153,7 @@ async function writeBuildRecordFile(outDir, root = process.cwd()) {
|
|
|
126
153
|
*/
|
|
127
154
|
async function writeRedirectFiles(outDir, root = process.cwd()) {
|
|
128
155
|
const dir = resolve(root);
|
|
129
|
-
const content = shownContent(await loadSiteContent(
|
|
156
|
+
const content = shownContent(await loadSiteContent(builtSiteSource(dir)));
|
|
130
157
|
const base = await exportBasePath(dir);
|
|
131
158
|
const files = redirectFiles(content, {
|
|
132
159
|
taken: allPages(content).map((page) => page.path),
|
|
@@ -162,7 +189,7 @@ async function guardExport(outDir, root = process.cwd()) {
|
|
|
162
189
|
const admin = parsed?.ok ? parsed.description.admin : void 0;
|
|
163
190
|
if (!admin) return void 0;
|
|
164
191
|
const dir = resolve(root);
|
|
165
|
-
const content = await loadSiteContent(
|
|
192
|
+
const content = await loadSiteContent(builtSiteSource(dir));
|
|
166
193
|
return guardBuiltSite(outDir, {
|
|
167
194
|
admin,
|
|
168
195
|
...parsed?.ok && parsed.description.broker && { broker: parsed.description.broker },
|
|
@@ -246,7 +273,7 @@ function withPageLanguage(html, language) {
|
|
|
246
273
|
*/
|
|
247
274
|
async function writePageLanguages(outDir, root = process.cwd()) {
|
|
248
275
|
const dir = resolve(root);
|
|
249
|
-
const { settings } = await loadSiteContent(
|
|
276
|
+
const { settings } = await loadSiteContent(builtSiteSource(dir));
|
|
250
277
|
if (!hasLanguages(settings)) return 0;
|
|
251
278
|
const base = outDir ?? join(dir, ".next/server/app");
|
|
252
279
|
const files = (await readdir(base, { recursive: true })).map((file) => file.split("\\").join("/")).filter((file) => file.endsWith(".html") && !/^(admin|_next|pagefind)\//.test(file));
|
|
@@ -314,7 +341,7 @@ function contentFile(pages, path) {
|
|
|
314
341
|
* Pages that aren't the site's, such as Next.js's own, are left out.
|
|
315
342
|
*/
|
|
316
343
|
async function checkPages(pages, root = process.cwd()) {
|
|
317
|
-
const known = allPages(shownContent(await loadSiteContent(
|
|
344
|
+
const known = allPages(shownContent(await loadSiteContent(builtSiteSource(resolve(root)))));
|
|
318
345
|
return checkBuiltPages(pages.flatMap((page) => {
|
|
319
346
|
const path = page.url.replace(/(.)\/$/, "$1");
|
|
320
347
|
const file = contentFile(known, path);
|
|
@@ -358,5 +385,29 @@ async function writeTaskResult(root, request, out = TASK_RESULT_FILE) {
|
|
|
358
385
|
await writeFile(resolve(root, out), `${JSON.stringify(sealed)}\n`);
|
|
359
386
|
return sealed;
|
|
360
387
|
}
|
|
388
|
+
/**
|
|
389
|
+
* Renders a page with a block from its own file as well as the site's config's,
|
|
390
|
+
* for `goodfellow-next blocks add`'s check that a block it made renders: both
|
|
391
|
+
* loaded in Node, as `goodfellow-next finish` loads the config.
|
|
392
|
+
*/
|
|
393
|
+
function blockPageRender(root) {
|
|
394
|
+
return async (page, block) => {
|
|
395
|
+
const site = resolve(root);
|
|
396
|
+
const config = await loadSiteConfig(site);
|
|
397
|
+
const entry = (await loadSiteModule(site, join(site, block.entry))).default;
|
|
398
|
+
const { createPageRenderer } = await import("@goodfellow-cms/react/server");
|
|
399
|
+
return createPageRenderer({
|
|
400
|
+
...config,
|
|
401
|
+
blocks: {
|
|
402
|
+
...config.blocks,
|
|
403
|
+
[block.name]: entry
|
|
404
|
+
}
|
|
405
|
+
})(await loadSiteContent(builtSiteSource(site)), {
|
|
406
|
+
path: "/",
|
|
407
|
+
file: "content/pages/index.json",
|
|
408
|
+
content: page
|
|
409
|
+
});
|
|
410
|
+
};
|
|
411
|
+
}
|
|
361
412
|
//#endregion
|
|
362
|
-
export { EDITOR_PAGES, NEXT_SITE_FILES, buildFileFolders, builtPages, checkPages, exportedPages, fixSegmentFiles, guardExport, writeBuildRecordFile, writeCalendarFiles, writeEditorPolicy, writeFeedFiles, writeFontFiles, writePageLanguages, writePluginFiles, writeRedirectFiles, writeTaskResult };
|
|
413
|
+
export { EDITOR_PAGES, NEXT_SITE_FILES, blockPageRender, buildFileFolders, builtPages, checkPages, exportedPages, fixSegmentFiles, guardExport, writeBuildRecordFile, writeCalendarFiles, writeContentApi, writeEditorPolicy, writeFeedFiles, writeFontFiles, writePageLanguages, writePluginFiles, writeRedirectFiles, writeTaskResult };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ByLanguage, CurrentMenuLinks, NextImage, NextLink, PageLanguage } from "./components.js";
|
|
2
|
-
import { absoluteUrl, colorSchemeMeta, colorSchemeScript, contentForPage, demoContent, feedLinks, findRedirect, fontPreloads, googleFontsUrl, hasLanguages, languageMap, loadSiteContent, normalizePagePath, pageHead, shownContent, siteLanguages, sitePages, sitemapPages, storedMediaSizes, themeToCss, todayIn, withBase } from "@goodfellow-cms/core";
|
|
3
|
-
import { BUILD_DATES_ENV, fileSystemSource, fontStandIns, readMediaSizes, readStylesheet, reportDatesTo } from "@goodfellow-cms/core/node";
|
|
2
|
+
import { absoluteUrl, adminSiteForCopy, colorSchemeMeta, colorSchemeScript, contentForPage, demoContent, describedForCopy, feedLinks, findRedirect, fontPreloads, googleFontsUrl, hasLanguages, languageMap, loadSiteContent, normalizePagePath, pageHead, shownContent, siteCopyFrom, siteLanguages, sitePages, sitemapPages, storedMediaSizes, themeToCss, todayIn, withBase } from "@goodfellow-cms/core";
|
|
3
|
+
import { BUILD_DATES_ENV, builtSiteSource, fileSystemSource, fontStandIns, readMediaSizes, readStylesheet, reportDatesTo } from "@goodfellow-cms/core/node";
|
|
4
4
|
import { BodyCode, HeadCode, PageBody, PageContent, PageFooter, PageHeader, adminSite, applyPageEntry, createPuckConfigs, describeSite, prepareLayout, preparePage } from "@goodfellow-cms/react";
|
|
5
5
|
import { notFound, redirect } from "next/navigation";
|
|
6
6
|
import { cache } from "react";
|
|
@@ -24,7 +24,7 @@ function configuredBasePath() {
|
|
|
24
24
|
* for the length of one request, so a page and its metadata read it once, and
|
|
25
25
|
* content edits show on reload in development.
|
|
26
26
|
*/
|
|
27
|
-
const loadSite = cache(async (root = process.cwd()) => shownContent(await loadSiteContent(
|
|
27
|
+
const loadSite = cache(async (root = process.cwd()) => shownContent(await loadSiteContent(builtSiteSource(root))));
|
|
28
28
|
function pathOf(segments) {
|
|
29
29
|
try {
|
|
30
30
|
return normalizePagePath(`/${(segments ?? []).map((segment) => decodeURIComponent(segment)).join("/")}`);
|
|
@@ -137,7 +137,7 @@ function goodfellowPages(config, options = {}) {
|
|
|
137
137
|
* editor's frames, at `app/admin/editor/page.tsx`.
|
|
138
138
|
*/
|
|
139
139
|
admin() {
|
|
140
|
-
return adminSite(config);
|
|
140
|
+
return adminSiteForCopy(adminSite(config), siteCopyFrom(process.env));
|
|
141
141
|
},
|
|
142
142
|
/** For `app/admin/layout.tsx`: the admin panel's title, kept out of search engines, with the site's icon. */
|
|
143
143
|
async adminMetadata() {
|
|
@@ -271,7 +271,7 @@ function goodfellowPages(config, options = {}) {
|
|
|
271
271
|
* edit its content without its code, such as the Claude connector.
|
|
272
272
|
*/
|
|
273
273
|
siteDescription() {
|
|
274
|
-
return Response.json(describeSite(config));
|
|
274
|
+
return Response.json(describedForCopy(describeSite(config), siteCopyFrom(process.env)));
|
|
275
275
|
},
|
|
276
276
|
/** For `app/robots.ts`: lets search engines in, and points them at the sitemap once the site's address is set. */
|
|
277
277
|
async robots() {
|
|
@@ -28,7 +28,8 @@ var plugins_exports = /* @__PURE__ */ __exportAll({
|
|
|
28
28
|
buildPluginFrames: () => buildPluginFrames,
|
|
29
29
|
configHasPlugins: () => configHasPlugins,
|
|
30
30
|
findConfigFile: () => findConfigFile,
|
|
31
|
-
loadSiteConfig: () => loadSiteConfig
|
|
31
|
+
loadSiteConfig: () => loadSiteConfig,
|
|
32
|
+
loadSiteModule: () => loadSiteModule
|
|
32
33
|
});
|
|
33
34
|
const CONFIG_FILES = [
|
|
34
35
|
"goodfellow.config.tsx",
|
|
@@ -55,19 +56,17 @@ async function configHasPlugins(root) {
|
|
|
55
56
|
return existsSync(index) && /\binstalledPlugin\(/.test(await readFile(index, "utf8"));
|
|
56
57
|
}
|
|
57
58
|
/**
|
|
58
|
-
*
|
|
59
|
+
* A module of the site, loaded in Node: bundled with Rolldown, with the
|
|
59
60
|
* packages it imports left for Node to load and stylesheets left out, then
|
|
60
|
-
* imported.
|
|
61
|
+
* imported.
|
|
61
62
|
*/
|
|
62
|
-
async function
|
|
63
|
-
const configFile = findConfigFile(root);
|
|
64
|
-
if (!configFile) throw new Error(`No goodfellow.config.tsx in ${root}.`);
|
|
63
|
+
async function loadSiteModule(root, input) {
|
|
65
64
|
const { build } = await import("rolldown");
|
|
66
65
|
const outDir = join(root, "node_modules/.goodfellow");
|
|
67
66
|
await mkdir(outDir, { recursive: true });
|
|
68
|
-
const file = join(outDir, `
|
|
67
|
+
const file = join(outDir, `module-${process.pid}-${Date.now()}.mjs`);
|
|
69
68
|
await build({
|
|
70
|
-
input
|
|
69
|
+
input,
|
|
71
70
|
cwd: root,
|
|
72
71
|
platform: "node",
|
|
73
72
|
tsconfig: existsSync(join(root, "tsconfig.json")) ? join(root, "tsconfig.json") : false,
|
|
@@ -91,11 +90,17 @@ async function loadSiteConfig(root) {
|
|
|
91
90
|
}
|
|
92
91
|
});
|
|
93
92
|
try {
|
|
94
|
-
return
|
|
93
|
+
return await import(pathToFileURL(file).href);
|
|
95
94
|
} finally {
|
|
96
95
|
await rm(file, { force: true });
|
|
97
96
|
}
|
|
98
97
|
}
|
|
98
|
+
/** The site's config, loaded in Node with `loadSiteModule()`. Only what plugins and checks need is read from it. */
|
|
99
|
+
async function loadSiteConfig(root) {
|
|
100
|
+
const configFile = findConfigFile(root);
|
|
101
|
+
if (!configFile) throw new Error(`No goodfellow.config.tsx in ${root}.`);
|
|
102
|
+
return (await loadSiteModule(root, configFile)).default;
|
|
103
|
+
}
|
|
99
104
|
/** A plugin's admin module as the bundler imports it: a package's export, or a path from the site's root. */
|
|
100
105
|
function adminModule(root, specifier) {
|
|
101
106
|
return specifier.startsWith("./") || specifier.startsWith("../") ? resolve(root, specifier) : specifier;
|
|
@@ -119,7 +124,8 @@ async function buildPluginFrame(plugin, { root, dir, watch }) {
|
|
|
119
124
|
permissions: plugin.permissions ?? {},
|
|
120
125
|
scripts: ["plugin.js"],
|
|
121
126
|
stylesheets: ["plugin.css"],
|
|
122
|
-
licenses: LICENSES_FILE
|
|
127
|
+
licenses: LICENSES_FILE,
|
|
128
|
+
editor: Boolean(plugin.editor)
|
|
123
129
|
}));
|
|
124
130
|
};
|
|
125
131
|
const options = {
|
|
@@ -188,4 +194,4 @@ async function buildPluginFrames(plugins, { root, outDir }) {
|
|
|
188
194
|
return built;
|
|
189
195
|
}
|
|
190
196
|
//#endregion
|
|
191
|
-
export {
|
|
197
|
+
export { loadSiteModule as a, loadSiteConfig as i, buildPluginFrames as n, plugins_exports as o, configHasPlugins as r, buildPluginFrame as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodfellow-cms/next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Goodfellow pages and the admin panel in a Next.js site.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"goodfellow",
|
|
@@ -63,23 +63,23 @@
|
|
|
63
63
|
"dist"
|
|
64
64
|
],
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@goodfellow-cms/ai": "0.
|
|
67
|
-
"@goodfellow-cms/core": "0.
|
|
68
|
-
"@goodfellow-cms/react": "0.
|
|
66
|
+
"@goodfellow-cms/ai": "0.7.0",
|
|
67
|
+
"@goodfellow-cms/core": "0.7.0",
|
|
68
|
+
"@goodfellow-cms/react": "0.7.0",
|
|
69
69
|
"rolldown": "1.2.12"
|
|
70
70
|
},
|
|
71
71
|
"peerDependencies": {
|
|
72
|
-
"@goodfellow-cms/admin": "0.
|
|
72
|
+
"@goodfellow-cms/admin": "0.7.0",
|
|
73
73
|
"@puckeditor/core": "0.23.0",
|
|
74
74
|
"next": "^16.0.0",
|
|
75
75
|
"react": "^19.0.0",
|
|
76
76
|
"react-dom": "^19.0.0"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
|
-
"@goodfellow-cms/admin": "0.
|
|
80
|
-
"@goodfellow-cms/bitbucket": "0.
|
|
81
|
-
"@goodfellow-cms/github": "0.
|
|
82
|
-
"@goodfellow-cms/gitlab": "0.
|
|
79
|
+
"@goodfellow-cms/admin": "0.7.0",
|
|
80
|
+
"@goodfellow-cms/bitbucket": "0.7.0",
|
|
81
|
+
"@goodfellow-cms/github": "0.7.0",
|
|
82
|
+
"@goodfellow-cms/gitlab": "0.7.0",
|
|
83
83
|
"@puckeditor/core": "0.23.0",
|
|
84
84
|
"@tailwindcss/browser": "4.3.3",
|
|
85
85
|
"@types/react": "^19.2.0",
|