@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.
@@ -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, contentRepositoryProblem, fileSystemSource, handleDevApi, isLocalOrigin, localFileStore, localRegistryDir } from "@goodfellow-cms/core/node";
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-EIZQe3qO.js").then(({ startDevEditor }) => startDevEditor(root)).catch((error) => {
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-eLJalBij.js").then((n) => n.a)).loadSiteConfig(root)).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(fileSystemSource(root)).then(shownContent, () => void 0);
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-eLJalBij.js";
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-eLJalBij.js";
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, fileSystemSource, guardBuiltSite, reportedDate, runPluginBuildSteps, runPluginTask, selfHostFonts, taskSecretsFrom, writeBuildRecord } from "@goodfellow-cms/core/node";
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(fileSystemSource(resolve(root)))));
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(fileSystemSource(resolve(root)))));
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(fileSystemSource(resolve(root)));
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(fileSystemSource(resolve(root)));
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(fileSystemSource(dir));
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(fileSystemSource(dir)));
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(fileSystemSource(dir));
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(fileSystemSource(dir));
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(fileSystemSource(resolve(root)))));
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(fileSystemSource(root))));
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
- * The site's config, loaded in Node: bundled with Rolldown, with the
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. Only what plugins need is read from it.
61
+ * imported.
61
62
  */
62
- async function loadSiteConfig(root) {
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, `config-${process.pid}-${Date.now()}.mjs`);
67
+ const file = join(outDir, `module-${process.pid}-${Date.now()}.mjs`);
69
68
  await build({
70
- input: configFile,
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 (await import(pathToFileURL(file).href)).default;
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 { plugins_exports as a, loadSiteConfig as i, buildPluginFrames as n, configHasPlugins as r, buildPluginFrame as t };
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.6.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.6.0",
67
- "@goodfellow-cms/core": "0.6.0",
68
- "@goodfellow-cms/react": "0.6.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.6.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.6.0",
80
- "@goodfellow-cms/bitbucket": "0.6.0",
81
- "@goodfellow-cms/github": "0.6.0",
82
- "@goodfellow-cms/gitlab": "0.6.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",