@goodfellow-cms/next 0.4.0 → 0.6.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/config.js CHANGED
@@ -1,10 +1,35 @@
1
- import { CALENDARS_DIR, allPages, loadSiteContent, normalizeBase, redirectFrom, redirectProblems } from "@goodfellow-cms/core";
2
- import { DEV_API_PREFIX, contentRepositoryProblem, fileSystemSource, handleDevApi, isLocalOrigin, localFileStore, localRegistryDir } from "@goodfellow-cms/core/node";
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";
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import { rm } from "node:fs/promises";
3
5
  import { createServer } from "node:http";
4
- import { resolve } from "node:path";
6
+ import { join, resolve } from "node:path";
5
7
  //#region src/config.ts
8
+ const CONFIG_FILES = [
9
+ "goodfellow.config.tsx",
10
+ "goodfellow.config.ts",
11
+ "goodfellow.config.jsx",
12
+ "goodfellow.config.js"
13
+ ];
14
+ /** The config's admin address, from its line of its own, since Next.js's config can't load the site's. */
15
+ function siteAdmin(root) {
16
+ const file = CONFIG_FILES.map((name) => join(root, name)).find((path) => existsSync(path));
17
+ return file ? configAdmin(readFileSync(file, "utf8")) : void 0;
18
+ }
19
+ /**
20
+ * The admin address's header rules, for hosts that read them from the build,
21
+ * such as Vercel: every page there sandboxed but the admin panel's own, as
22
+ * `goodfellow build`'s `_headers` does on Cloudflare Pages. Next.js adds the base path itself.
23
+ */
24
+ function adminHeaders(root) {
25
+ const admin = siteAdmin(root);
26
+ const file = CONFIG_FILES.map((name) => join(root, name)).find((path) => existsSync(path));
27
+ const broker = brokerOrigin(file ? configBroker(readFileSync(file, "utf8")) : void 0);
28
+ return admin ? vercelAdminHeaders(admin, "/", broker) : [];
29
+ }
6
30
  /** `PHASE_DEVELOPMENT_SERVER` from `next/constants`, which Node can't import by that name. */
7
31
  const PHASE_DEVELOPMENT_SERVER = "phase-development-server";
32
+ const PHASE_PRODUCTION_BUILD = "phase-production-build";
8
33
  const SERVER_KEY = Symbol.for("goodfellow.next.devApi");
9
34
  /**
10
35
  * The admin panel's local backend, which reads and writes the site's files on
@@ -16,10 +41,21 @@ function startDevApi(root) {
16
41
  globals[SERVER_KEY] ??= new Promise((resolvePort, reject) => {
17
42
  const store = localFileStore(root);
18
43
  const registryDir = localRegistryDir(root);
44
+ const editor = import("./dev-editor-EIZQe3qO.js").then(({ startDevEditor }) => startDevEditor(root)).catch((error) => {
45
+ console.error(`The editor's frames can't be served: ${error.message}`);
46
+ return { serve: async () => false };
47
+ });
48
+ const tasks = {
49
+ root,
50
+ plugins: async () => (await (await import("./plugins-eLJalBij.js").then((n) => n.a)).loadSiteConfig(root)).plugins ?? [],
51
+ server: "next-dev"
52
+ };
19
53
  const server = createServer(async (req, res) => {
54
+ if (await (await editor).serve(req, res)) return;
20
55
  if (!await handleDevApi(store, req, res, {
21
56
  trustOrigin: isLocalOrigin,
22
- registryDir
57
+ registryDir,
58
+ tasks
23
59
  })) res.writeHead(404).end();
24
60
  });
25
61
  server.on("error", reject);
@@ -65,7 +101,7 @@ function redirectSource(path) {
65
101
  * Content with a problem gives none, and the build reports the problem.
66
102
  */
67
103
  async function siteRedirects(root) {
68
- const content = await loadSiteContent(fileSystemSource(root)).catch(() => void 0);
104
+ const content = await loadSiteContent(fileSystemSource(root)).then(shownContent, () => void 0);
69
105
  if (!content?.redirects) return [];
70
106
  const taken = new Set(allPages(content).map((page) => page.path));
71
107
  const problems = redirectProblems(content.redirects);
@@ -91,6 +127,8 @@ async function siteRedirects(root) {
91
127
  * - Serves images as they are unless the site sets an image loader, since static files can't be optimized on request.
92
128
  * - Adds the site's redirects (Site Settings → Redirects) ahead of the site's own, for hosts that
93
129
  * send them, such as Vercel. Others get the pages `goodfellow-next finish` writes at old addresses.
130
+ * - For a site with an admin address, adds header rules that sandbox every page there but the
131
+ * admin panel's own, for hosts that send headers, such as Vercel.
94
132
  * - With `{ server: true }`, builds a full-stack site for a server instead of as static
95
133
  * files: see `goodfellowApi()` in `@goodfellow-cms/next/server`.
96
134
  * - In `next dev`, runs the admin panel's local backend, so publishing saves to
@@ -115,40 +153,65 @@ function withGoodfellow(nextConfig = {}, options = {}) {
115
153
  GOODFELLOW_BASE_PATH: basePath
116
154
  }
117
155
  };
118
- if (phase !== PHASE_DEVELOPMENT_SERVER && options.server) return {
119
- ...config,
120
- images: nextConfig.images,
121
- outputFileTracingIncludes: {
122
- ...nextConfig.outputFileTracingIncludes,
123
- "/**": [
124
- ...nextConfig.outputFileTracingIncludes?.["/**"] ?? [],
125
- "./content/**/*",
126
- "./public/media/**/*"
127
- ]
128
- },
129
- redirects: async () => [...await siteRedirects(root), ...await nextConfig.redirects?.() ?? []]
130
- };
156
+ if (phase === PHASE_PRODUCTION_BUILD) {
157
+ const dates = join(root, nextConfig.distDir ?? ".next", BUILD_DATES_DIR);
158
+ await rm(dates, {
159
+ recursive: true,
160
+ force: true
161
+ });
162
+ process.env[BUILD_DATES_ENV] = dates;
163
+ }
164
+ if (phase !== PHASE_DEVELOPMENT_SERVER && options.server) {
165
+ if (siteAdmin(root)) throw new Error("A full-stack site can't have an admin address: its admin panel signs in with the site's own sign-in.");
166
+ return {
167
+ ...config,
168
+ images: nextConfig.images,
169
+ outputFileTracingIncludes: {
170
+ ...nextConfig.outputFileTracingIncludes,
171
+ "/**": [
172
+ ...nextConfig.outputFileTracingIncludes?.["/**"] ?? [],
173
+ "./content/**/*",
174
+ "./public/media/**/*"
175
+ ]
176
+ },
177
+ redirects: async () => [...await siteRedirects(root), ...await nextConfig.redirects?.() ?? []]
178
+ };
179
+ }
131
180
  if (phase !== PHASE_DEVELOPMENT_SERVER) return {
132
181
  output: "export",
133
182
  ...config,
134
- redirects: async () => [...await siteRedirects(root), ...await nextConfig.redirects?.() ?? []]
183
+ redirects: async () => [...await siteRedirects(root), ...await nextConfig.redirects?.() ?? []],
184
+ ...siteAdmin(root) && { headers: async () => [...adminHeaders(root), ...await nextConfig.headers?.() ?? []] }
135
185
  };
136
186
  const port = await startDevApi(root);
137
187
  const rewrite = {
138
188
  source: `${DEV_API_PREFIX}/:path*`,
139
189
  destination: `http://127.0.0.1:${port}${DEV_API_PREFIX}/:path*`
140
190
  };
141
- const calendars = {
142
- source: `/${CALENDARS_DIR}/:file(.+\\.ics)`,
143
- destination: `http://127.0.0.1:${port}${DEV_API_PREFIX}/${CALENDARS_DIR}/:file`
144
- };
191
+ const buildFiles = [{
192
+ dir: CALENDARS_DIR,
193
+ file: ":file(.+\\.ics)"
194
+ }, {
195
+ dir: FEEDS_DIR,
196
+ file: ":file(.+\\.rss|.+\\.atom)"
197
+ }].flatMap(({ dir, file }) => [{
198
+ source: `/${dir}/${file}`,
199
+ destination: `http://127.0.0.1:${port}${DEV_API_PREFIX}/${dir}/:file`
200
+ }, {
201
+ source: `/:language([a-z0-9-]+)/${dir}/${file}`,
202
+ destination: `http://127.0.0.1:${port}${DEV_API_PREFIX}/:language/${dir}/:file`
203
+ }]);
145
204
  return {
146
205
  ...config,
147
206
  experimental: {
148
207
  reactDebugChannel: false,
149
208
  ...nextConfig.experimental
150
209
  },
151
- rewrites: async () => withRewrite(withRewrite(await nextConfig.rewrites?.(), calendars), rewrite)
210
+ rewrites: async () => {
211
+ let rewrites = await nextConfig.rewrites?.();
212
+ for (const next of [...buildFiles, rewrite]) rewrites = withRewrite(rewrites, next);
213
+ return rewrites ?? [];
214
+ }
152
215
  };
153
216
  };
154
217
  }
@@ -0,0 +1,170 @@
1
+ import { i as loadSiteConfig, t as buildPluginFrame } from "./plugins-eLJalBij.js";
2
+ import { DEV_EDITOR_PREFIX, EDITOR_PRELUDE, editorPage, pluginId } from "@goodfellow-cms/core";
3
+ import { existsSync } from "node:fs";
4
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
5
+ import { join, resolve, sep } from "node:path";
6
+ //#region src/dev-editor.ts
7
+ const CONFIG_FILES = [
8
+ "goodfellow.config.tsx",
9
+ "goodfellow.config.ts",
10
+ "goodfellow.config.jsx",
11
+ "goodfellow.config.js"
12
+ ];
13
+ const ENTRY = "\0goodfellow:next-editor";
14
+ const TAILWIND_BROWSER = "tailwind-browser.js";
15
+ /** How long the editor's frames stay as built before the admin panel reloads them. */
16
+ const REBUILT_DELAY = 300;
17
+ const TYPES = {
18
+ html: "text/html; charset=utf-8",
19
+ js: "text/javascript; charset=utf-8",
20
+ css: "text/css; charset=utf-8",
21
+ map: "application/json",
22
+ json: "application/json"
23
+ };
24
+ async function startDevEditor(root) {
25
+ const configFile = CONFIG_FILES.map((name) => join(root, name)).find((file) => existsSync(file));
26
+ if (!configFile) throw new Error(`No goodfellow.config.tsx in ${root}.`);
27
+ const outDir = join(root, "node_modules/.goodfellow/next-editor");
28
+ await mkdir(outDir, { recursive: true });
29
+ const [{ watch }, { default: tailwindBrowser }] = await Promise.all([import("rolldown"), import("./tailwind-browser-Ds-ZH7sO.js")]);
30
+ await writeFile(join(outDir, TAILWIND_BROWSER), tailwindBrowser);
31
+ const stylesheets = /* @__PURE__ */ new Set();
32
+ let version = 0;
33
+ let built = () => {};
34
+ const ready = new Promise((resolveReady) => {
35
+ built = resolveReady;
36
+ });
37
+ let counting;
38
+ const writePage = async () => {
39
+ const css = await Promise.all([...stylesheets].map((file) => readFile(file, "utf8")));
40
+ await writeFile(join(outDir, "editor.css"), css.join("\n"));
41
+ await writeFile(join(outDir, "index.html"), editorPage({
42
+ scripts: ["editor.js"],
43
+ stylesheets: ["editor.css"]
44
+ }));
45
+ counting = setTimeout(() => {
46
+ version += 1;
47
+ writeFile(join(outDir, "version.json"), JSON.stringify({ version }));
48
+ }, version === 0 ? 0 : REBUILT_DELAY);
49
+ };
50
+ watch({
51
+ input: { editor: ENTRY },
52
+ cwd: root,
53
+ platform: "browser",
54
+ tsconfig: existsSync(join(root, "tsconfig.json")) ? join(root, "tsconfig.json") : false,
55
+ transform: {
56
+ jsx: { runtime: "automatic" },
57
+ define: { "process.env.NODE_ENV": JSON.stringify("development") }
58
+ },
59
+ plugins: [{
60
+ name: "goodfellow:next-editor",
61
+ resolveId: (id) => id === ENTRY ? id : void 0,
62
+ load(id) {
63
+ if (id === ENTRY) return [
64
+ `import config from ${JSON.stringify(configFile)};`,
65
+ `import { mountEditorFrame } from "@goodfellow-cms/admin/editor";`,
66
+ `import "@puckeditor/core/puck.css";`,
67
+ `import "@goodfellow-cms/admin/styles.css";`,
68
+ `mountEditorFrame(config, undefined, { tailwindBrowserUrl: ${JSON.stringify(TAILWIND_BROWSER)} });`
69
+ ].join("\n");
70
+ if (id.endsWith(".css")) {
71
+ stylesheets.add(id);
72
+ return {
73
+ code: "",
74
+ moduleType: "js"
75
+ };
76
+ }
77
+ }
78
+ }],
79
+ onLog(level, log, handler) {
80
+ if (log.code === "MODULE_LEVEL_DIRECTIVE") return;
81
+ handler(level, log);
82
+ },
83
+ output: {
84
+ dir: outDir,
85
+ format: "iife",
86
+ codeSplitting: false,
87
+ entryFileNames: "editor.js",
88
+ banner: EDITOR_PRELUDE,
89
+ sourcemap: true
90
+ }
91
+ }).on("event", (event) => {
92
+ if (event.code === "BUNDLE_START") clearTimeout(counting);
93
+ else if (event.code === "BUNDLE_END") {
94
+ event.result.close();
95
+ writePage().then(built, (error) => {
96
+ console.error(`The editor's frames couldn't be written: ${error.message}`);
97
+ built();
98
+ });
99
+ } else if (event.code === "ERROR") {
100
+ event.result?.close();
101
+ console.error(`The editor's frames couldn't be built: ${event.error.message}`);
102
+ built();
103
+ }
104
+ });
105
+ const pluginsDir = join(root, "node_modules/.goodfellow/next-plugins");
106
+ const pluginsPrefix = DEV_EDITOR_PREFIX.replace(/editor\/$/, "plugins/");
107
+ const pluginBuilds = /* @__PURE__ */ new Map();
108
+ const pluginFrame = (id) => {
109
+ let ready = pluginBuilds.get(id);
110
+ if (!ready) {
111
+ ready = (async () => {
112
+ const plugin = (await loadSiteConfig(root)).plugins?.find((candidate) => pluginId(candidate.name) === id && candidate.admin);
113
+ if (!plugin) {
114
+ pluginBuilds.delete(id);
115
+ return false;
116
+ }
117
+ await new Promise((done) => {
118
+ buildPluginFrame(plugin, {
119
+ root,
120
+ dir: join(pluginsDir, id),
121
+ watch: { onBuilt(error) {
122
+ if (error) console.error(`${plugin.title}'s admin frame couldn't be built: ${error.message}`);
123
+ done();
124
+ } }
125
+ }).catch((error) => {
126
+ console.error(`${plugin.title}'s admin frame couldn't be built: ${error.message}`);
127
+ done();
128
+ });
129
+ });
130
+ return true;
131
+ })();
132
+ pluginBuilds.set(id, ready);
133
+ }
134
+ return ready;
135
+ };
136
+ return { async serve(req, res) {
137
+ const pathname = new URL(req.url ?? "/", "http://localhost").pathname;
138
+ if (pathname.startsWith(pluginsPrefix) && (req.method === "GET" || req.method === "HEAD")) {
139
+ const [id = "", ...rest] = decodeURIComponent(pathname.slice(pluginsPrefix.length)).split("/");
140
+ const file = resolve(pluginsDir, id, rest.join("/") || "index.html");
141
+ if (!file.startsWith(`${pluginsDir}${sep}`) || !await pluginFrame(id)) return false;
142
+ try {
143
+ const data = await readFile(file);
144
+ res.setHeader("Content-Type", TYPES[file.slice(file.lastIndexOf(".") + 1)] ?? "application/octet-stream");
145
+ res.setHeader("Cache-Control", "no-store");
146
+ res.end(req.method === "HEAD" ? void 0 : data);
147
+ } catch {
148
+ res.writeHead(404).end();
149
+ }
150
+ return true;
151
+ }
152
+ const page = pathname === DEV_EDITOR_PREFIX.slice(0, -1);
153
+ if (!(page || pathname.startsWith(DEV_EDITOR_PREFIX)) || req.method !== "GET" && req.method !== "HEAD") return false;
154
+ await ready;
155
+ const name = page ? "index.html" : pathname.slice(DEV_EDITOR_PREFIX.length) || "index.html";
156
+ const file = resolve(outDir, name);
157
+ if (!file.startsWith(`${outDir}${sep}`)) return false;
158
+ try {
159
+ const data = await readFile(file);
160
+ res.setHeader("Content-Type", TYPES[file.slice(file.lastIndexOf(".") + 1)] ?? "application/octet-stream");
161
+ res.setHeader("Cache-Control", "no-store");
162
+ res.end(req.method === "HEAD" ? void 0 : data);
163
+ } catch {
164
+ res.writeHead(404).end();
165
+ }
166
+ return true;
167
+ } };
168
+ }
169
+ //#endregion
170
+ export { startDevEditor };
@@ -0,0 +1,14 @@
1
+ import { GoodfellowConfig } from "@goodfellow-cms/core";
2
+ import "@goodfellow-cms/admin/styles.css";
3
+ import "@puckeditor/core/puck.css";
4
+ //#region src/editor.d.ts
5
+ /**
6
+ * The editor's frames, for a Next.js site's `app/admin/editor/page.tsx`,
7
+ * which must be a Client Component (`"use client"`) that imports the site's
8
+ * config: Puck, the config and every block, sandboxed away from the admin
9
+ * panel's page, which signs in and publishes.
10
+ */
11
+ export declare function GoodfellowEditor({ config }: {
12
+ config: GoodfellowConfig;
13
+ }): import("react").JSX.Element;
14
+ //#endregion
package/dist/editor.js ADDED
@@ -0,0 +1,32 @@
1
+ "use client";
2
+ import { useEffect, useRef } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ import "@goodfellow-cms/admin/styles.css";
5
+ import "@puckeditor/core/puck.css";
6
+ //#region src/editor.tsx
7
+ /** Started once a page: React runs effects twice in development, and a second start would connect again. */
8
+ let started = false;
9
+ /**
10
+ * The editor's frames, for a Next.js site's `app/admin/editor/page.tsx`,
11
+ * which must be a Client Component (`"use client"`) that imports the site's
12
+ * config: Puck, the config and every block, sandboxed away from the admin
13
+ * panel's page, which signs in and publishes.
14
+ */
15
+ function GoodfellowEditor({ config }) {
16
+ const element = useRef(null);
17
+ useEffect(() => {
18
+ const container = element.current;
19
+ if (!container || started) return;
20
+ started = true;
21
+ Promise.all([import("@goodfellow-cms/admin/editor"), import("./tailwind-browser-Ds-ZH7sO.js").then((module) => module.default)]).then(([{ mountEditorFrame }, tailwindBrowser]) => {
22
+ const tailwindBrowserUrl = URL.createObjectURL(new Blob([tailwindBrowser], { type: "text/javascript" }));
23
+ mountEditorFrame(config, container, { tailwindBrowserUrl });
24
+ });
25
+ }, [config]);
26
+ return /* @__PURE__ */ jsx("div", {
27
+ ref: element,
28
+ id: "goodfellow-frame"
29
+ });
30
+ }
31
+ //#endregion
32
+ export { GoodfellowEditor };
package/dist/export.d.ts CHANGED
@@ -1,3 +1,13 @@
1
+ import { PluginTaskRequest, SelfHostedResult, SiteProblem, TaskResultFile } from "@goodfellow-cms/core";
2
+ import { SiteFile } from "@goodfellow-cms/core/node";
3
+ //#region src/site-files.d.ts
4
+ /**
5
+ * The Next.js starter's files that `goodfellow-next update` keeps up to date
6
+ * where nobody has changed them, as the starter now has them. A test checks
7
+ * they match `templates/next`.
8
+ */
9
+ export declare const NEXT_SITE_FILES: readonly SiteFile[];
10
+ //#endregion
1
11
  //#region src/export.d.ts
2
12
  /**
3
13
  * Writes the calendar files of the site's collections of events into the
@@ -5,12 +15,74 @@
5
15
  * how many it wrote.
6
16
  */
7
17
  export declare function writeCalendarFiles(outDir: string, root?: string): Promise<number>;
18
+ /**
19
+ * Writes the feeds of the site's collections into the export, as `goodfellow
20
+ * build` does. Returns how many it wrote: none without the site's address.
21
+ */
22
+ export declare function writeFeedFiles(outDir: string, root?: string): Promise<number>;
23
+ /**
24
+ * The folders the calendar files and feeds go in: `calendars/` and `feeds/`,
25
+ * and on a site in several languages, the same under each other language's
26
+ * code, such as `es/feeds/`.
27
+ */
28
+ export declare function buildFileFolders(root?: string): Promise<string[]>;
29
+ /** What `writePluginFiles()` wrote and ran. */
30
+ export interface PluginFilesWritten {
31
+ /** The plugins whose admin frames were written. */
32
+ frames: string[];
33
+ /** The build steps that ran, as `plugin: step`. */
34
+ steps: string[];
35
+ }
36
+ /**
37
+ * Plugins' parts that builds make: each plugin's admin frame, beside the
38
+ * editor's frames at `admin/plugins/<id>/`, and its build steps, each in a
39
+ * process of its own held to what the plugin declared. Into a static export
40
+ * (`outDir`), or a full-stack site's `public/`, where `next start` serves
41
+ * them. The config is loaded outside Next.js only when it lists plugins.
42
+ */
43
+ export declare function writePluginFiles(outDir: string, root?: string): Promise<PluginFilesWritten>;
44
+ /** What `writeFontFiles()` did, for `buildNotes()`. */
45
+ export interface FontsWritten {
46
+ fonts?: SelfHostedResult;
47
+ robotsElsewhere?: string;
48
+ sitemap?: string;
49
+ }
50
+ /**
51
+ * Copies the site's Google Fonts into the export and points its pages at
52
+ * them, as `goodfellow build` does (`selfHostFonts()`), with the export's base
53
+ * path, and says where search engines read `robots.txt` for a site in a
54
+ * subfolder (`rootRobotsUrl()`).
55
+ */
56
+ export declare function writeFontFiles(outDir: string, root?: string): Promise<FontsWritten>;
57
+ /**
58
+ * Writes the build record (`goodfellow-build.json`) into the export, as
59
+ * `goodfellow build` does, from the dates the processes that rendered its
60
+ * pages wrote into `.next/` (see `withGoodfellow()`). Returns when the site
61
+ * stops being current, if it does.
62
+ */
63
+ export declare function writeBuildRecordFile(outDir: string, root?: string): Promise<string | undefined>;
64
+ /** What `writeRedirectFiles()` wrote. */
65
+ export interface RedirectsWritten {
66
+ /** How many pages at old addresses. */
67
+ pages: number;
68
+ /** How many redirects didn't fit in Cloudflare Pages' `_redirects`, and are left to their pages. */
69
+ cloudflareLeft: number;
70
+ }
8
71
  /**
9
72
  * Writes a page at each of the site's old addresses that sends visitors on, as
10
- * `goodfellow build` does. Pages win: an address the export has a page at keeps
11
- * it. Returns how many it wrote.
73
+ * `goodfellow build` does, and the same redirects in `_redirects` for
74
+ * Cloudflare Pages, which sends them itself. Pages win: an address the export
75
+ * has a page at keeps it.
76
+ */
77
+ export declare function writeRedirectFiles(outDir: string, root?: string): Promise<RedirectsWritten>;
78
+ /**
79
+ * For a site with an admin address (its config's `admin`, which the exported
80
+ * `goodfellow-site.json` carries), puts the guard first in every page of the
81
+ * export and adds Cloudflare Pages' header rules, as `goodfellow build` does.
82
+ * Run it last, once every page is written. Returns how many pages it guarded,
83
+ * or `undefined` for a site without an admin address.
12
84
  */
13
- export declare function writeRedirectFiles(outDir: string, root?: string): Promise<number>;
85
+ export declare function guardExport(outDir: string, root?: string): Promise<number | undefined>;
14
86
  /**
15
87
  * Puts a static export's prefetch files where the browser asks for them.
16
88
  * Next.js names each after its segment path with `/` turned into `.`
@@ -19,6 +91,23 @@ export declare function writeRedirectFiles(outDir: string, root?: string): Promi
19
91
  * prefetch is "not found". Returns how many files it moved; none elsewhere.
20
92
  */
21
93
  export declare function fixSegmentFiles(outDir: string): Promise<number>;
94
+ /** Where the editor's frames' page is in an export, and in a full-stack site's build. */
95
+ export declare const EDITOR_PAGES: string[];
96
+ /**
97
+ * Gives the editor's frames' page (`app/admin/editor/`) its Content Security
98
+ * Policy and first script, which removes WebRTC, as the first things in its
99
+ * head, so they apply before anything Next.js put there. The policy runs only
100
+ * the site's own scripts, Next.js's inline ones by their hashes, and fetches
101
+ * nothing (`editorPolicy()`). Returns whether it found the page.
102
+ */
103
+ export declare function writeEditorPolicy(dir: string): Promise<boolean>;
104
+ /**
105
+ * On a site in several languages, puts each page's language on its `<html>`,
106
+ * which Next.js's root layout, shared by every page, says is the main one:
107
+ * in a static export's pages (`outDir`), or, without one, in the pages a
108
+ * full-stack site's `next build` rendered ahead. Returns how many it changed.
109
+ */
110
+ export declare function writePageLanguages(outDir: string | undefined, root?: string): Promise<number>;
22
111
  /** A page `next build` rendered ahead: its address on the site, and its HTML. */
23
112
  export interface BuiltPage {
24
113
  url: string;
@@ -31,4 +120,24 @@ export interface BuiltPage {
31
120
  * and keeps their HTML in `.next/server/app/`. The admin panel and Next.js's own pages are left out.
32
121
  */
33
122
  export declare function builtPages(root?: string): Promise<BuiltPage[]>;
123
+ /**
124
+ * The pages a static export has, at their addresses on the site, without its
125
+ * base path: `about.html` and `about/index.html` are `/about`. The admin panel
126
+ * and Next.js's own files are left out.
127
+ */
128
+ export declare function exportedPages(outDir: string): Promise<BuiltPage[]>;
129
+ /**
130
+ * What automated checks find on the pages Next.js built, as `goodfellow build`
131
+ * reports them: images without alt text, headings that skip a level, links
132
+ * that don't say where they go, and axe-core's checks (`checkBuiltPages()`).
133
+ * Pages that aren't the site's, such as Next.js's own, are left out.
134
+ */
135
+ export declare function checkPages(pages: readonly BuiltPage[], root?: string): Promise<SiteProblem[]>;
136
+ /**
137
+ * Runs one of a plugin's tasks for the admin panel, as `goodfellow task`
138
+ * does for other sites, with the site's config loaded in Node, and writes its
139
+ * answer sealed to the request's key, even when the task fails. Secrets are
140
+ * taken out of the environment before the config loads.
141
+ */
142
+ export declare function writeTaskResult(root: string, request: PluginTaskRequest, out?: string): Promise<TaskResultFile>;
34
143
  //#endregion