@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/README.md +1 -1
- package/bin/goodfellow-next.js +240 -15
- package/dist/admin.d.ts +19 -9
- package/dist/admin.js +89 -18
- package/dist/components.d.ts +21 -0
- package/dist/components.js +27 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.js +87 -24
- package/dist/dev-editor-EIZQe3qO.js +170 -0
- package/dist/editor.d.ts +14 -0
- package/dist/editor.js +32 -0
- package/dist/export.d.ts +112 -3
- package/dist/export.js +259 -12
- package/dist/index.d.ts +12 -4
- package/dist/index.js +90 -42
- package/dist/plugins-eLJalBij.js +191 -0
- package/dist/server.d.ts +7 -0
- package/dist/server.js +2 -1
- package/dist/site-files-BeLnLxCl.js +50 -0
- package/package.json +19 -12
|
@@ -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
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
//#region src/site-files.ts
|
|
2
|
+
/** The release whose Next.js sites have the editor's sandboxed frames at `app/admin/editor/`. */
|
|
3
|
+
const FRAMES_RELEASE = "0.5.0";
|
|
4
|
+
/** `app/admin/page.tsx` as every release before the frames wrote it, loading the site's config itself. */
|
|
5
|
+
const CLIENT_ADMIN_PAGE = `"use client";
|
|
6
|
+
|
|
7
|
+
import { GoodfellowAdmin } from "@goodfellow-cms/next/admin";
|
|
8
|
+
import config from "@/goodfellow.config";
|
|
9
|
+
|
|
10
|
+
export default function AdminPage() {
|
|
11
|
+
return <GoodfellowAdmin config={config} />;
|
|
12
|
+
}
|
|
13
|
+
`;
|
|
14
|
+
const ADMIN_PAGE = `import { GoodfellowAdmin } from "@goodfellow-cms/next/admin";
|
|
15
|
+
import { site } from "@/lib/site";
|
|
16
|
+
|
|
17
|
+
// The admin panel's page never loads the site's config: it's told about the site's blocks here, on the server,
|
|
18
|
+
// and the blocks themselves run in the editor's frames, at /admin/editor/.
|
|
19
|
+
export default function AdminPage() {
|
|
20
|
+
return <GoodfellowAdmin site={site.admin()} />;
|
|
21
|
+
}
|
|
22
|
+
`;
|
|
23
|
+
const EDITOR_PAGE = `"use client";
|
|
24
|
+
|
|
25
|
+
import { GoodfellowEditor } from "@goodfellow-cms/next/editor";
|
|
26
|
+
import config from "@/goodfellow.config";
|
|
27
|
+
|
|
28
|
+
// The editor's frames: Puck and the site's blocks, sandboxed away from the admin panel's page.
|
|
29
|
+
export default function EditorPage() {
|
|
30
|
+
return <GoodfellowEditor config={config} />;
|
|
31
|
+
}
|
|
32
|
+
`;
|
|
33
|
+
/**
|
|
34
|
+
* The Next.js starter's files that `goodfellow-next update` keeps up to date
|
|
35
|
+
* where nobody has changed them, as the starter now has them. A test checks
|
|
36
|
+
* they match `templates/next`.
|
|
37
|
+
*/
|
|
38
|
+
const NEXT_SITE_FILES = [{
|
|
39
|
+
path: "app/admin/page.tsx",
|
|
40
|
+
since: FRAMES_RELEASE,
|
|
41
|
+
content: ADMIN_PAGE,
|
|
42
|
+
earlier: [CLIENT_ADMIN_PAGE]
|
|
43
|
+
}, {
|
|
44
|
+
path: "app/admin/editor/page.tsx",
|
|
45
|
+
since: FRAMES_RELEASE,
|
|
46
|
+
content: EDITOR_PAGE,
|
|
47
|
+
earlier: []
|
|
48
|
+
}];
|
|
49
|
+
//#endregion
|
|
50
|
+
export { EDITOR_PAGE as n, NEXT_SITE_FILES as r, ADMIN_PAGE as t };
|
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",
|
|
@@ -38,6 +38,11 @@
|
|
|
38
38
|
"import": "./dist/admin.js",
|
|
39
39
|
"default": "./dist/admin.js"
|
|
40
40
|
},
|
|
41
|
+
"./editor": {
|
|
42
|
+
"types": "./dist/editor.d.ts",
|
|
43
|
+
"import": "./dist/editor.js",
|
|
44
|
+
"default": "./dist/editor.js"
|
|
45
|
+
},
|
|
41
46
|
"./export": {
|
|
42
47
|
"types": "./dist/export.d.ts",
|
|
43
48
|
"import": "./dist/export.js",
|
|
@@ -58,31 +63,33 @@
|
|
|
58
63
|
"dist"
|
|
59
64
|
],
|
|
60
65
|
"dependencies": {
|
|
61
|
-
"@goodfellow-cms/ai": "0.
|
|
62
|
-
"@goodfellow-cms/
|
|
63
|
-
"@goodfellow-cms/
|
|
66
|
+
"@goodfellow-cms/ai": "0.6.0",
|
|
67
|
+
"@goodfellow-cms/core": "0.6.0",
|
|
68
|
+
"@goodfellow-cms/react": "0.6.0",
|
|
69
|
+
"rolldown": "1.2.12"
|
|
64
70
|
},
|
|
65
71
|
"peerDependencies": {
|
|
72
|
+
"@goodfellow-cms/admin": "0.6.0",
|
|
66
73
|
"@puckeditor/core": "0.23.0",
|
|
67
74
|
"next": "^16.0.0",
|
|
68
75
|
"react": "^19.0.0",
|
|
69
|
-
"react-dom": "^19.0.0"
|
|
70
|
-
"@goodfellow-cms/admin": "0.4.0"
|
|
76
|
+
"react-dom": "^19.0.0"
|
|
71
77
|
},
|
|
72
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",
|
|
73
83
|
"@puckeditor/core": "0.23.0",
|
|
74
84
|
"@tailwindcss/browser": "4.3.3",
|
|
75
85
|
"@types/react": "^19.2.0",
|
|
76
86
|
"@types/react-dom": "^19.2.0",
|
|
77
|
-
"next": "16.3
|
|
87
|
+
"next": "16.4.3",
|
|
78
88
|
"react": "19.3.0",
|
|
79
|
-
"react-dom": "19.3.0"
|
|
80
|
-
"@goodfellow-cms/admin": "0.4.0",
|
|
81
|
-
"@goodfellow-cms/github": "0.4.0",
|
|
82
|
-
"@goodfellow-cms/gitlab": "0.4.0"
|
|
89
|
+
"react-dom": "19.3.0"
|
|
83
90
|
},
|
|
84
91
|
"engines": {
|
|
85
|
-
"node": ">=
|
|
92
|
+
"node": "^22.23.4 || ^24.22.1 || >=26.12.1"
|
|
86
93
|
},
|
|
87
94
|
"publishConfig": {
|
|
88
95
|
"access": "public"
|