@goodfellow-cms/next 0.5.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/README.md +1 -1
- package/bin/goodfellow-next.js +39 -7
- package/dist/admin.js +1 -0
- package/dist/config.js +8 -2
- package/dist/{dev-editor-DiPKrIAS.js → dev-editor-EIZQe3qO.js} +47 -1
- package/dist/export.d.ts +23 -1
- package/dist/export.js +67 -3
- package/dist/index.js +5 -2
- package/dist/plugins-eLJalBij.js +191 -0
- package/dist/server.d.ts +7 -0
- package/dist/server.js +2 -1
- package/package.json +9 -8
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Goodfellow pages and the admin panel in a Next.js App Router site, exported as s
|
|
|
4
4
|
|
|
5
5
|
Start from the Next.js starter: `npm create goodfellow@latest my-site -- --template next`.
|
|
6
6
|
|
|
7
|
-
[Goodfellow](https://github.com/goodfellow-cms/goodfellow-cms) is a website builder for people who aren't developers, with no server or database: pages are files in your own GitHub or
|
|
7
|
+
[Goodfellow](https://github.com/goodfellow-cms/goodfellow-cms) is a website builder for people who aren't developers, with no server or database: pages are files in your own GitHub, GitLab or Bitbucket repository, edited in a visual editor built on [Puck](https://puckeditor.com). To start a new site, run `npm create goodfellow@latest my-site`. See the [documentation](https://goodfellow-cms.github.io/goodfellow-cms/) for everything else.
|
|
8
8
|
|
|
9
9
|
More in the docs: [Next.js](https://goodfellow-cms.github.io/goodfellow-cms/docs/nextjs) and [Full-stack Next.js sites](https://goodfellow-cms.github.io/goodfellow-cms/docs/full-stack-nextjs).
|
|
10
10
|
|
package/bin/goodfellow-next.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// After `next build`: `goodfellow-next finish out` fixes the export's prefetch files on Windows,
|
|
3
3
|
// writes the calendar files of collections of events, collections' feeds, the pages at redirects' old addresses and Cloudflare Pages' _redirects, and the search index for sites with a search block,
|
|
4
|
-
// copies the site's Google Fonts into the export, starts every page with the admin address's guard for a site with one, and reports what automated checks find on the exported pages. `index` only writes the index.
|
|
4
|
+
// copies the site's Google Fonts into the export, builds plugins' admin frames and runs their build steps, starts every page with the admin address's guard for a site with one, and reports what automated checks find on the exported pages. `index` only writes the index.
|
|
5
5
|
// `goodfellow-next update` updates Goodfellow, as `goodfellow update` does for other sites.
|
|
6
|
-
// `goodfellow-next
|
|
6
|
+
// `goodfellow-next task [--out <file>]` runs one of a plugin's tasks for the admin panel, as `goodfellow task` does.
|
|
7
|
+
// `goodfellow-next due [--since <time>] [--output <file>]` says whether a scheduled change has come due, as `goodfellow due` does.
|
|
7
8
|
// `goodfellow-next nightly [--mode <mode>]` says whether tonight's build is needed, as `goodfellow nightly` does.
|
|
8
9
|
// `goodfellow-next content link|copy [folder]` puts a content repository's files in place, for a site whose
|
|
9
10
|
// content is kept in a repository of its own.
|
|
@@ -53,7 +54,9 @@ import {
|
|
|
53
54
|
writeFeedFiles,
|
|
54
55
|
writeFontFiles,
|
|
55
56
|
writePageLanguages,
|
|
57
|
+
writePluginFiles,
|
|
56
58
|
writeRedirectFiles,
|
|
59
|
+
writeTaskResult,
|
|
57
60
|
} from "@goodfellow-cms/next/export";
|
|
58
61
|
|
|
59
62
|
const [command, dir = "out"] = process.argv.slice(2);
|
|
@@ -80,6 +83,10 @@ if (command === "finish" && dir === "--server") {
|
|
|
80
83
|
// Before the pages are read, so the search index and the checks see each page's language.
|
|
81
84
|
const languages = await writePageLanguages(undefined);
|
|
82
85
|
if (languages > 0) console.log(`Marked ${languages} pages with their language`);
|
|
86
|
+
// Plugins' admin frames and build steps, into public/, which next start serves.
|
|
87
|
+
const plugins = await writePluginFiles("public");
|
|
88
|
+
if (plugins.frames.length > 0)
|
|
89
|
+
console.log(`Wrote the admin frames of ${plugins.frames.join(", ")}, in public/admin/plugins`);
|
|
83
90
|
const pages = await builtPages();
|
|
84
91
|
const indexed = await writePagesSearchIndex(pages, "public/pagefind");
|
|
85
92
|
console.log(
|
|
@@ -102,9 +109,30 @@ if (command === "update") {
|
|
|
102
109
|
console.log(formatUpdateResult(result));
|
|
103
110
|
process.exit(result.state === "failed" ? 1 : 0);
|
|
104
111
|
}
|
|
112
|
+
if (command === "task") {
|
|
113
|
+
const { values } = parseArgs({ args: process.argv.slice(3), options: { out: { type: "string" } } });
|
|
114
|
+
const env = process.env;
|
|
115
|
+
await writeTaskResult(
|
|
116
|
+
".",
|
|
117
|
+
{
|
|
118
|
+
id: env.GOODFELLOW_TASK_REQUEST ?? "",
|
|
119
|
+
plugin: env.GOODFELLOW_TASK_PLUGIN ?? "",
|
|
120
|
+
task: env.GOODFELLOW_TASK_NAME ?? "",
|
|
121
|
+
input: env.GOODFELLOW_TASK_INPUT ?? "",
|
|
122
|
+
key: env.GOODFELLOW_TASK_KEY ?? "",
|
|
123
|
+
},
|
|
124
|
+
values.out,
|
|
125
|
+
);
|
|
126
|
+
process.exit(0);
|
|
127
|
+
}
|
|
105
128
|
if (command === "due") {
|
|
106
|
-
const { values } = parseArgs({
|
|
107
|
-
|
|
129
|
+
const { values } = parseArgs({
|
|
130
|
+
args: process.argv.slice(3),
|
|
131
|
+
options: { since: { type: "string" }, output: { type: "string" } },
|
|
132
|
+
});
|
|
133
|
+
const result = await scheduleDue({ since: values.since });
|
|
134
|
+
for (const line of await reportDue(result, process.env, values.output ?? process.env.GITHUB_OUTPUT))
|
|
135
|
+
console.log(line);
|
|
108
136
|
process.exit(0);
|
|
109
137
|
}
|
|
110
138
|
if (command === "nightly") {
|
|
@@ -206,7 +234,7 @@ if (command !== "finish" && command !== "index") {
|
|
|
206
234
|
" goodfellow-next finish --server",
|
|
207
235
|
" goodfellow-next index [folder]",
|
|
208
236
|
" goodfellow-next update [--to fixes|automatic|latest|<release>] [--publish | --commit]",
|
|
209
|
-
" goodfellow-next due [--since <time>]",
|
|
237
|
+
" goodfellow-next due [--since <time>] [--output <file>]",
|
|
210
238
|
" goodfellow-next nightly [--mode changed|always|off] [--output <file>]",
|
|
211
239
|
" goodfellow-next content link|copy [folder]",
|
|
212
240
|
" goodfellow-next import wordpress <file or address> [--dry-run]",
|
|
@@ -217,7 +245,8 @@ if (command !== "finish" && command !== "index") {
|
|
|
217
245
|
"finish Gets the exported site in folder (default: out) ready to publish: puts the files",
|
|
218
246
|
" Next.js uses to load pages ahead where browsers look for them, writes calendar files",
|
|
219
247
|
" for collections of events, collections' feeds and pages that send visitors on from",
|
|
220
|
-
" old addresses,
|
|
248
|
+
" old addresses, builds plugins' admin frames and runs their build steps, indexes it",
|
|
249
|
+
" for search, and lists what automated accessibility checks",
|
|
221
250
|
" find on its pages. With --server, for a full-stack site, writes the calendar files",
|
|
222
251
|
" and feeds into public/calendars and public/feeds and indexes and checks the pages",
|
|
223
252
|
" next build rendered, with the index in public/pagefind, instead.",
|
|
@@ -227,7 +256,7 @@ if (command !== "finish" && command !== "index") {
|
|
|
227
256
|
" that runs none of the site's code to publish.",
|
|
228
257
|
"due Says whether a scheduled change has come due since --since (default: the last",
|
|
229
258
|
" hour), so the site needs building again, and sets the step's due output on",
|
|
230
|
-
" GitHub Actions.",
|
|
259
|
+
" GitHub Actions, or adds it to --output.",
|
|
231
260
|
"nightly Says whether tonight's build is needed: when the site has changed since the live",
|
|
232
261
|
" site was built, what it shows by today's date has, or an update is available",
|
|
233
262
|
" (--mode, or GOODFELLOW_NIGHTLY: changed, the default, always or off), and sets the",
|
|
@@ -271,6 +300,9 @@ if (command === "finish") {
|
|
|
271
300
|
);
|
|
272
301
|
}
|
|
273
302
|
for (const line of buildNotes(await writeFontFiles(dir))) console.log(line);
|
|
303
|
+
// Plugins' admin frames and build steps, once Goodfellow has written the rest, before the index and the guards.
|
|
304
|
+
const plugins = await writePluginFiles(dir);
|
|
305
|
+
if (plugins.frames.length > 0) console.log(`Wrote the admin frames of ${plugins.frames.join(", ")}`);
|
|
274
306
|
// What the site was built from, and when it stops being current, for the nightly check.
|
|
275
307
|
const currentUntil = await writeBuildRecordFile(dir);
|
|
276
308
|
if (currentUntil) {
|
package/dist/admin.js
CHANGED
|
@@ -58,6 +58,7 @@ function useEditorRebuilds() {
|
|
|
58
58
|
* dependencies are.
|
|
59
59
|
*/
|
|
60
60
|
const HOSTS = {
|
|
61
|
+
"@goodfellow-cms/bitbucket": () => import("@goodfellow-cms/bitbucket"),
|
|
61
62
|
"@goodfellow-cms/github": () => import("@goodfellow-cms/github"),
|
|
62
63
|
"@goodfellow-cms/gitlab": () => import("@goodfellow-cms/gitlab")
|
|
63
64
|
};
|
package/dist/config.js
CHANGED
|
@@ -41,15 +41,21 @@ 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-EIZQe3qO.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
|
+
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
|
+
};
|
|
48
53
|
const server = createServer(async (req, res) => {
|
|
49
54
|
if (await (await editor).serve(req, res)) return;
|
|
50
55
|
if (!await handleDevApi(store, req, res, {
|
|
51
56
|
trustOrigin: isLocalOrigin,
|
|
52
|
-
registryDir
|
|
57
|
+
registryDir,
|
|
58
|
+
tasks
|
|
53
59
|
})) res.writeHead(404).end();
|
|
54
60
|
});
|
|
55
61
|
server.on("error", reject);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
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";
|
|
2
3
|
import { existsSync } from "node:fs";
|
|
3
4
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
5
|
import { join, resolve, sep } from "node:path";
|
|
@@ -101,8 +102,53 @@ async function startDevEditor(root) {
|
|
|
101
102
|
built();
|
|
102
103
|
}
|
|
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
|
+
};
|
|
104
136
|
return { async serve(req, res) {
|
|
105
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
|
+
}
|
|
106
152
|
const page = pathname === DEV_EDITOR_PREFIX.slice(0, -1);
|
|
107
153
|
if (!(page || pathname.startsWith(DEV_EDITOR_PREFIX)) || req.method !== "GET" && req.method !== "HEAD") return false;
|
|
108
154
|
await ready;
|
package/dist/export.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SelfHostedResult, SiteProblem } from "@goodfellow-cms/core";
|
|
1
|
+
import { PluginTaskRequest, SelfHostedResult, SiteProblem, TaskResultFile } from "@goodfellow-cms/core";
|
|
2
2
|
import { SiteFile } from "@goodfellow-cms/core/node";
|
|
3
3
|
//#region src/site-files.d.ts
|
|
4
4
|
/**
|
|
@@ -26,6 +26,21 @@ export declare function writeFeedFiles(outDir: string, root?: string): Promise<n
|
|
|
26
26
|
* code, such as `es/feeds/`.
|
|
27
27
|
*/
|
|
28
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>;
|
|
29
44
|
/** What `writeFontFiles()` did, for `buildNotes()`. */
|
|
30
45
|
export interface FontsWritten {
|
|
31
46
|
fonts?: SelfHostedResult;
|
|
@@ -118,4 +133,11 @@ export declare function exportedPages(outDir: string): Promise<BuiltPage[]>;
|
|
|
118
133
|
* Pages that aren't the site's, such as Next.js's own, are left out.
|
|
119
134
|
*/
|
|
120
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>;
|
|
121
143
|
//#endregion
|
package/dist/export.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { i as loadSiteConfig, n as buildPluginFrames, r as configHasPlugins } from "./plugins-eLJalBij.js";
|
|
1
2
|
import { r as NEXT_SITE_FILES } from "./site-files-BeLnLxCl.js";
|
|
2
|
-
import { CALENDARS_DIR, CLOUDFLARE_REDIRECTS_PATH, EDITOR_PRELUDE, FEEDS_DIR, SITE_DESCRIPTION_PATH, absoluteUrl, allPages, calendarFiles, cloudflareRedirects, editorPolicy, feedFiles, hasLanguages, loadSiteContent, parseSiteDescription, pathLanguage, redirectFiles, rootRobotsUrl, shownContent, siteLanguages } from "@goodfellow-cms/core";
|
|
3
|
-
import { BUILD_DATES_DIR, fileSystemSource, guardBuiltSite, reportedDate, selfHostFonts, 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, taskRequestProblem } from "@goodfellow-cms/core";
|
|
4
|
+
import { BUILD_DATES_DIR, fileSystemSource, guardBuiltSite, reportedDate, runPluginBuildSteps, runPluginTask, selfHostFonts, taskSecretsFrom, writeBuildRecord } from "@goodfellow-cms/core/node";
|
|
4
5
|
import { access, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
5
6
|
import { dirname, join, relative, resolve } from "node:path";
|
|
6
7
|
import { createHash } from "node:crypto";
|
|
@@ -51,6 +52,36 @@ async function exportBasePath(root) {
|
|
|
51
52
|
return "";
|
|
52
53
|
}
|
|
53
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Plugins' parts that builds make: each plugin's admin frame, beside the
|
|
57
|
+
* editor's frames at `admin/plugins/<id>/`, and its build steps, each in a
|
|
58
|
+
* process of its own held to what the plugin declared. Into a static export
|
|
59
|
+
* (`outDir`), or a full-stack site's `public/`, where `next start` serves
|
|
60
|
+
* them. The config is loaded outside Next.js only when it lists plugins.
|
|
61
|
+
*/
|
|
62
|
+
async function writePluginFiles(outDir, root = process.cwd()) {
|
|
63
|
+
const site = resolve(root);
|
|
64
|
+
if (!await configHasPlugins(site)) return {
|
|
65
|
+
frames: [],
|
|
66
|
+
steps: []
|
|
67
|
+
};
|
|
68
|
+
const config = await loadSiteConfig(site);
|
|
69
|
+
const base = `${await exportBasePath(site)}/`;
|
|
70
|
+
const dir = resolve(site, outDir);
|
|
71
|
+
return {
|
|
72
|
+
frames: await buildPluginFrames(config.plugins, {
|
|
73
|
+
root: site,
|
|
74
|
+
outDir: dir
|
|
75
|
+
}),
|
|
76
|
+
steps: (await runPluginBuildSteps({
|
|
77
|
+
root: site,
|
|
78
|
+
outDir: dir,
|
|
79
|
+
base,
|
|
80
|
+
plugins: config.plugins ?? [],
|
|
81
|
+
onStep: (step) => console.log(`Running ${step.title}, from ${step.plugin}…`)
|
|
82
|
+
})).map((step) => `${step.plugin}: ${step.title}`)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
54
85
|
async function exists(path) {
|
|
55
86
|
return access(path).then(() => true, () => false);
|
|
56
87
|
}
|
|
@@ -294,5 +325,38 @@ async function checkPages(pages, root = process.cwd()) {
|
|
|
294
325
|
}] : [];
|
|
295
326
|
}));
|
|
296
327
|
}
|
|
328
|
+
/**
|
|
329
|
+
* Runs one of a plugin's tasks for the admin panel, as `goodfellow task`
|
|
330
|
+
* does for other sites, with the site's config loaded in Node, and writes its
|
|
331
|
+
* answer sealed to the request's key, even when the task fails. Secrets are
|
|
332
|
+
* taken out of the environment before the config loads.
|
|
333
|
+
*/
|
|
334
|
+
async function writeTaskResult(root, request, out = TASK_RESULT_FILE) {
|
|
335
|
+
const secrets = taskSecretsFrom(process.env);
|
|
336
|
+
const problem = taskRequestProblem(request);
|
|
337
|
+
if (problem) throw new Error(problem);
|
|
338
|
+
let answer;
|
|
339
|
+
try {
|
|
340
|
+
const plugin = ((await loadSiteConfig(resolve(root))).plugins ?? []).find((candidate) => pluginId(candidate.name) === request.plugin);
|
|
341
|
+
answer = plugin ? await runPluginTask({
|
|
342
|
+
root: resolve(root),
|
|
343
|
+
plugin,
|
|
344
|
+
task: request.task,
|
|
345
|
+
input: request.input,
|
|
346
|
+
secrets
|
|
347
|
+
}) : {
|
|
348
|
+
ok: false,
|
|
349
|
+
error: `The site has no plugin ${request.plugin}.`
|
|
350
|
+
};
|
|
351
|
+
} catch (error) {
|
|
352
|
+
answer = {
|
|
353
|
+
ok: false,
|
|
354
|
+
error: `The site couldn't run the task: ${error instanceof Error ? error.message : error}`
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
const sealed = await sealTaskAnswer(answer, request.key);
|
|
358
|
+
await writeFile(resolve(root, out), `${JSON.stringify(sealed)}\n`);
|
|
359
|
+
return sealed;
|
|
360
|
+
}
|
|
297
361
|
//#endregion
|
|
298
|
-
export { EDITOR_PAGES, NEXT_SITE_FILES, buildFileFolders, builtPages, checkPages, exportedPages, fixSegmentFiles, guardExport, writeBuildRecordFile, writeCalendarFiles, writeEditorPolicy, writeFeedFiles, writeFontFiles, writePageLanguages, writeRedirectFiles };
|
|
362
|
+
export { EDITOR_PAGES, NEXT_SITE_FILES, buildFileFolders, builtPages, checkPages, exportedPages, fixSegmentFiles, guardExport, writeBuildRecordFile, writeCalendarFiles, writeEditorPolicy, writeFeedFiles, writeFontFiles, writePageLanguages, writePluginFiles, writeRedirectFiles, writeTaskResult };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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, themeToCss, todayIn, withBase } from "@goodfellow-cms/core";
|
|
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
3
|
import { BUILD_DATES_ENV, 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";
|
|
@@ -87,7 +87,10 @@ function goodfellowPages(config, options = {}) {
|
|
|
87
87
|
const renderOptions = async () => ({
|
|
88
88
|
base: basePath(),
|
|
89
89
|
components,
|
|
90
|
-
media:
|
|
90
|
+
media: {
|
|
91
|
+
...await readMediaSizes(root),
|
|
92
|
+
...storedMediaSizes((await site()).mediaStorage)
|
|
93
|
+
}
|
|
91
94
|
});
|
|
92
95
|
/** The site's theme and fonts, which React moves into the document's head. themeToCss escapes its output. */
|
|
93
96
|
async function Theme({ content }) {
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { EDITOR_PRELUDE, INSTALLED_PLUGINS_INDEX, PLUGINS_PATH, framedPlugins, pluginFrameEntry, pluginFramePage, pluginId } from "@goodfellow-cms/core";
|
|
2
|
+
import { LICENSES_FILE, licensesPlugin } from "@goodfellow-cms/core/node";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
5
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
6
|
+
import { pathToFileURL } from "node:url";
|
|
7
|
+
//#region \0rolldown/runtime.js
|
|
8
|
+
var __defProp = Object.defineProperty;
|
|
9
|
+
var __exportAll = (all, no_symbols) => {
|
|
10
|
+
let target = {};
|
|
11
|
+
for (var name in all) __defProp(target, name, {
|
|
12
|
+
get: all[name],
|
|
13
|
+
enumerable: true
|
|
14
|
+
});
|
|
15
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
16
|
+
return target;
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/plugins.ts
|
|
20
|
+
/**
|
|
21
|
+
* Plugins on Next.js sites: their admin frames, bundled with Rolldown as
|
|
22
|
+
* `next dev` bundles the editor's frames, and their build steps, both run by
|
|
23
|
+
* `goodfellow-next finish` from the site's config, which Next.js alone
|
|
24
|
+
* bundles otherwise. Node only.
|
|
25
|
+
*/
|
|
26
|
+
var plugins_exports = /* @__PURE__ */ __exportAll({
|
|
27
|
+
buildPluginFrame: () => buildPluginFrame,
|
|
28
|
+
buildPluginFrames: () => buildPluginFrames,
|
|
29
|
+
configHasPlugins: () => configHasPlugins,
|
|
30
|
+
findConfigFile: () => findConfigFile,
|
|
31
|
+
loadSiteConfig: () => loadSiteConfig
|
|
32
|
+
});
|
|
33
|
+
const CONFIG_FILES = [
|
|
34
|
+
"goodfellow.config.tsx",
|
|
35
|
+
"goodfellow.config.ts",
|
|
36
|
+
"goodfellow.config.jsx",
|
|
37
|
+
"goodfellow.config.js"
|
|
38
|
+
];
|
|
39
|
+
const FRAME_ENTRY = "\0goodfellow:plugin-frame";
|
|
40
|
+
function findConfigFile(root) {
|
|
41
|
+
return CONFIG_FILES.map((name) => join(root, name)).find((file) => existsSync(file));
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Whether the site's config lists plugins, read from its text, so builds of
|
|
45
|
+
* sites without any never load the config outside Next.js: plugins of its
|
|
46
|
+
* own, or any installed from the admin panel, which `plugins/installed/`
|
|
47
|
+
* lists.
|
|
48
|
+
*/
|
|
49
|
+
async function configHasPlugins(root) {
|
|
50
|
+
const file = findConfigFile(root);
|
|
51
|
+
const listed = file && /\bplugins\s*:\s*([^\n]*)/.exec(await readFile(file, "utf8"))?.[1];
|
|
52
|
+
if (!listed) return false;
|
|
53
|
+
if (!/^installedPlugins\s*,?$/.test(listed.trim())) return true;
|
|
54
|
+
const index = join(root, INSTALLED_PLUGINS_INDEX);
|
|
55
|
+
return existsSync(index) && /\binstalledPlugin\(/.test(await readFile(index, "utf8"));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The site's config, loaded in Node: bundled with Rolldown, with the
|
|
59
|
+
* packages it imports left for Node to load and stylesheets left out, then
|
|
60
|
+
* imported. Only what plugins need is read from it.
|
|
61
|
+
*/
|
|
62
|
+
async function loadSiteConfig(root) {
|
|
63
|
+
const configFile = findConfigFile(root);
|
|
64
|
+
if (!configFile) throw new Error(`No goodfellow.config.tsx in ${root}.`);
|
|
65
|
+
const { build } = await import("rolldown");
|
|
66
|
+
const outDir = join(root, "node_modules/.goodfellow");
|
|
67
|
+
await mkdir(outDir, { recursive: true });
|
|
68
|
+
const file = join(outDir, `config-${process.pid}-${Date.now()}.mjs`);
|
|
69
|
+
await build({
|
|
70
|
+
input: configFile,
|
|
71
|
+
cwd: root,
|
|
72
|
+
platform: "node",
|
|
73
|
+
tsconfig: existsSync(join(root, "tsconfig.json")) ? join(root, "tsconfig.json") : false,
|
|
74
|
+
transform: { jsx: { runtime: "automatic" } },
|
|
75
|
+
external: (id, _importer, resolved) => resolved ? /[\\/]node_modules[\\/]/.test(id) : !id.startsWith(".") && !isAbsolute(id) && !id.startsWith("@/"),
|
|
76
|
+
plugins: [{
|
|
77
|
+
name: "goodfellow:no-styles",
|
|
78
|
+
load: (id) => id.endsWith(".css") ? {
|
|
79
|
+
code: "",
|
|
80
|
+
moduleType: "js"
|
|
81
|
+
} : void 0
|
|
82
|
+
}],
|
|
83
|
+
onLog(level, log, handler) {
|
|
84
|
+
if (log.code === "MODULE_LEVEL_DIRECTIVE") return;
|
|
85
|
+
handler(level, log);
|
|
86
|
+
},
|
|
87
|
+
output: {
|
|
88
|
+
file,
|
|
89
|
+
format: "esm",
|
|
90
|
+
codeSplitting: false
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
try {
|
|
94
|
+
return (await import(pathToFileURL(file).href)).default;
|
|
95
|
+
} finally {
|
|
96
|
+
await rm(file, { force: true });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** A plugin's admin module as the bundler imports it: a package's export, or a path from the site's root. */
|
|
100
|
+
function adminModule(root, specifier) {
|
|
101
|
+
return specifier.startsWith("./") || specifier.startsWith("../") ? resolve(root, specifier) : specifier;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Bundles a plugin's admin module into its frame, at `admin/plugins/<id>/`
|
|
105
|
+
* under `outDir`: one classic script starting with `EDITOR_PRELUDE`, its
|
|
106
|
+
* stylesheets in one file, `pluginFramePage()`, whose Content Security
|
|
107
|
+
* Policy reaches only the addresses the plugin declared, and the notices of
|
|
108
|
+
* what it bundles (`LICENSES_FILE`). Answers a function that stops rebuilding.
|
|
109
|
+
*/
|
|
110
|
+
async function buildPluginFrame(plugin, { root, dir, watch }) {
|
|
111
|
+
if (!plugin.admin) throw new Error(`${plugin.name} has no admin module.`);
|
|
112
|
+
const rolldown = await import("rolldown");
|
|
113
|
+
await mkdir(dir, { recursive: true });
|
|
114
|
+
const stylesheets = /* @__PURE__ */ new Set();
|
|
115
|
+
const writePage = async () => {
|
|
116
|
+
const css = await Promise.all([...stylesheets].map((file) => readFile(file, "utf8")));
|
|
117
|
+
await writeFile(join(dir, "plugin.css"), css.join("\n"));
|
|
118
|
+
await writeFile(join(dir, "index.html"), pluginFramePage({
|
|
119
|
+
permissions: plugin.permissions ?? {},
|
|
120
|
+
scripts: ["plugin.js"],
|
|
121
|
+
stylesheets: ["plugin.css"],
|
|
122
|
+
licenses: LICENSES_FILE
|
|
123
|
+
}));
|
|
124
|
+
};
|
|
125
|
+
const options = {
|
|
126
|
+
input: { plugin: FRAME_ENTRY },
|
|
127
|
+
cwd: root,
|
|
128
|
+
platform: "browser",
|
|
129
|
+
tsconfig: existsSync(join(root, "tsconfig.json")) ? join(root, "tsconfig.json") : false,
|
|
130
|
+
transform: {
|
|
131
|
+
jsx: { runtime: "automatic" },
|
|
132
|
+
define: { "process.env.NODE_ENV": JSON.stringify(watch ? "development" : "production") }
|
|
133
|
+
},
|
|
134
|
+
plugins: [{
|
|
135
|
+
name: "goodfellow:plugin-frame",
|
|
136
|
+
resolveId: (id) => id === FRAME_ENTRY ? id : void 0,
|
|
137
|
+
load(id) {
|
|
138
|
+
if (id === FRAME_ENTRY) return pluginFrameEntry(adminModule(root, plugin.admin ?? ""));
|
|
139
|
+
if (id.endsWith(".css")) {
|
|
140
|
+
stylesheets.add(id);
|
|
141
|
+
return {
|
|
142
|
+
code: "",
|
|
143
|
+
moduleType: "js"
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}, licensesPlugin(plugin.title, LICENSES_FILE)],
|
|
148
|
+
onLog(level, log, handler) {
|
|
149
|
+
if (log.code === "MODULE_LEVEL_DIRECTIVE") return;
|
|
150
|
+
handler(level, log);
|
|
151
|
+
},
|
|
152
|
+
output: {
|
|
153
|
+
dir,
|
|
154
|
+
format: "iife",
|
|
155
|
+
codeSplitting: false,
|
|
156
|
+
entryFileNames: "plugin.js",
|
|
157
|
+
banner: EDITOR_PRELUDE,
|
|
158
|
+
...watch ? { sourcemap: true } : { minify: true }
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
if (!watch) {
|
|
162
|
+
await rolldown.build(options);
|
|
163
|
+
await writePage();
|
|
164
|
+
return async () => {};
|
|
165
|
+
}
|
|
166
|
+
const watcher = rolldown.watch(options);
|
|
167
|
+
watcher.on("event", (event) => {
|
|
168
|
+
if (event.code === "BUNDLE_END") {
|
|
169
|
+
event.result.close();
|
|
170
|
+
writePage().then(() => watch.onBuilt(), (error) => watch.onBuilt(error));
|
|
171
|
+
} else if (event.code === "ERROR") {
|
|
172
|
+
event.result?.close();
|
|
173
|
+
watch.onBuilt(event.error);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
return () => watcher.close();
|
|
177
|
+
}
|
|
178
|
+
/** Builds every plugin's admin frame into `admin/plugins/<id>/` in `outDir`. Answers the plugins' names. */
|
|
179
|
+
async function buildPluginFrames(plugins, { root, outDir }) {
|
|
180
|
+
const built = [];
|
|
181
|
+
for (const plugin of framedPlugins(plugins)) {
|
|
182
|
+
await buildPluginFrame(plugin, {
|
|
183
|
+
root,
|
|
184
|
+
dir: join(outDir, PLUGINS_PATH, pluginId(plugin.name))
|
|
185
|
+
});
|
|
186
|
+
built.push(plugin.name);
|
|
187
|
+
}
|
|
188
|
+
return built;
|
|
189
|
+
}
|
|
190
|
+
//#endregion
|
|
191
|
+
export { plugins_exports as a, loadSiteConfig as i, buildPluginFrames as n, configHasPlugins as r, buildPluginFrame as t };
|
package/dist/server.d.ts
CHANGED
|
@@ -31,6 +31,13 @@ export interface GoodfellowApiOptions {
|
|
|
31
31
|
ai?: ServerKeys;
|
|
32
32
|
/** Told about failures editors only hear about as "something went wrong". Defaults to `console.error`. */
|
|
33
33
|
onError?: (error: unknown) => void;
|
|
34
|
+
/**
|
|
35
|
+
* The server modules of the config's plugins, by plugin name, imported in
|
|
36
|
+
* the route's file, such as `{ "goodfellow-plugin-s3": s3Server }` from
|
|
37
|
+
* `import s3Server from "goodfellow-plugin-s3/server"`. Each answers
|
|
38
|
+
* `plugins/<id>/…`, and runs with everything the server has.
|
|
39
|
+
*/
|
|
40
|
+
plugins?: AppApiOptions["plugins"];
|
|
34
41
|
}
|
|
35
42
|
/** The route parameters of a catch-all route named `[...path]`. */
|
|
36
43
|
interface RouteContext {
|
package/dist/server.js
CHANGED
|
@@ -31,7 +31,8 @@ function goodfellowApi(options) {
|
|
|
31
31
|
signIn: options.signIn,
|
|
32
32
|
...options.signOut && { signOut: options.signOut },
|
|
33
33
|
...ai && ai.services.length > 0 && { ai },
|
|
34
|
-
...options.onError && { onError: options.onError }
|
|
34
|
+
...options.onError && { onError: options.onError },
|
|
35
|
+
...options.plugins && { plugins: options.plugins }
|
|
35
36
|
});
|
|
36
37
|
return api;
|
|
37
38
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodfellow-cms/next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Goodfellow pages and the admin panel in a Next.js site.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"goodfellow",
|
|
@@ -63,22 +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.6.0",
|
|
67
|
+
"@goodfellow-cms/core": "0.6.0",
|
|
68
|
+
"@goodfellow-cms/react": "0.6.0",
|
|
69
69
|
"rolldown": "1.2.12"
|
|
70
70
|
},
|
|
71
71
|
"peerDependencies": {
|
|
72
|
-
"@goodfellow-cms/admin": "0.
|
|
72
|
+
"@goodfellow-cms/admin": "0.6.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/
|
|
81
|
-
"@goodfellow-cms/
|
|
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",
|
|
82
83
|
"@puckeditor/core": "0.23.0",
|
|
83
84
|
"@tailwindcss/browser": "4.3.3",
|
|
84
85
|
"@types/react": "^19.2.0",
|