forgepress 0.0.1 → 0.0.2
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/THIRD-PARTY-LICENSES.md +28 -83
- package/dist/_chunks/client.d.mts +1 -1
- package/dist/_chunks/config.mjs +61 -19
- package/dist/_chunks/{validate.mjs → content.mjs} +350 -217
- package/dist/_chunks/fetch.mjs +2 -2
- package/dist/_chunks/files.mjs +32 -0
- package/dist/_chunks/libs/diff.mjs +485 -0
- package/dist/_chunks/media.mjs +282 -0
- package/dist/_chunks/once.mjs +16 -0
- package/dist/_chunks/output.mjs +98 -252
- package/dist/_chunks/plugin.mjs +87 -0
- package/dist/_chunks/preview.mjs +248 -0
- package/dist/_chunks/project.d.mts +7 -0
- package/dist/_chunks/reader.mjs +5 -9
- package/dist/_chunks/reader2.mjs +150 -202
- package/dist/_chunks/references.mjs +8 -5
- package/dist/_chunks/resolve.d.mts +2 -0
- package/dist/_chunks/response.mjs +5 -0
- package/dist/_chunks/routes.mjs +9 -0
- package/dist/_chunks/serialize.mjs +82 -0
- package/dist/_chunks/settings.mjs +328 -0
- package/dist/_chunks/settings2.mjs +2 -0
- package/dist/_chunks/types.d.mts +229 -24
- package/dist/_chunks/types2.d.mts +25 -0
- package/dist/_chunks/value.mjs +123 -1
- package/dist/cli/bin.mjs +50 -37
- package/dist/editor/index.mjs +9961 -8653
- package/dist/index.d.mts +2 -3
- package/dist/index.mjs +14 -24
- package/dist/next/preview.d.mts +1 -0
- package/dist/next/preview.mjs +3 -0
- package/dist/next/reload.d.mts +1 -0
- package/dist/next/reload.mjs +20 -0
- package/dist/next/settings.d.mts +12 -0
- package/dist/next/settings.mjs +2 -0
- package/dist/plugin/next.d.mts +11 -0
- package/dist/plugin/next.mjs +213 -0
- package/dist/plugin/nuxt.d.mts +7 -0
- package/dist/plugin/nuxt.mjs +52 -0
- package/dist/plugin/watcher.d.mts +1 -0
- package/dist/plugin/watcher.mjs +23 -0
- package/dist/preview/index.d.mts +1 -2
- package/dist/preview/index.mjs +1 -243
- package/dist/preview/react.d.mts +1 -0
- package/dist/preview/react.mjs +34 -0
- package/dist/unplugin.d.mts +12 -17
- package/dist/unplugin.mjs +1 -500
- package/package.json +72 -8
- package/dist/_chunks/config.d.mts +0 -35
- package/dist/_chunks/entry.d.mts +0 -194
- package/dist/_chunks/libs/@oxc-project/types.d.mts +0 -1297
- package/dist/_chunks/libs/@rolldown/pluginutils.d.mts +0 -62
- package/dist/_chunks/libs/rolldown.d.mts +0 -4711
- package/dist/_chunks/output2.mjs +0 -333
- package/dist/_chunks/parse.mjs +0 -22
- package/dist/_chunks/types.mjs +0 -2
package/dist/unplugin.mjs
CHANGED
|
@@ -1,501 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { errorMessage } from "./_chunks/error.mjs";
|
|
3
|
-
import { ENDPOINT, assetUrl, checkUpload, defaultPaths, isAssetName, isCollectionName, isEntryId, isMediaFile, mediaType, resolveMedia, sortAssets, sortByCreation, toAssetName } from "./_chunks/output.mjs";
|
|
4
|
-
import { findRoot, loadConfig, resolveConfig } from "./_chunks/config.mjs";
|
|
5
|
-
import { ContentError, META_KEYS, validateSchema } from "./_chunks/validate.mjs";
|
|
6
|
-
import { buildOutput, outputDir, readEntryIds } from "./_chunks/output2.mjs";
|
|
7
|
-
import { OUTPUT_VARIABLE } from "./_chunks/reader.mjs";
|
|
8
|
-
import { parseEntry, parseSchema } from "./_chunks/parse.mjs";
|
|
9
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
10
|
-
import { mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
11
|
-
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
12
|
-
import process from "node:process";
|
|
13
|
-
import { createUnplugin } from "unplugin";
|
|
14
|
-
import { createHash } from "node:crypto";
|
|
15
|
-
import { Buffer } from "node:buffer";
|
|
16
|
-
function createMediaStore(root, config) {
|
|
17
|
-
const media = resolveMedia(config);
|
|
18
|
-
const dir = join(root, media.dir);
|
|
19
|
-
async function describe(name) {
|
|
20
|
-
const info = await stat(join(dir, name));
|
|
21
|
-
return {
|
|
22
|
-
name,
|
|
23
|
-
url: assetUrl(media.url, name),
|
|
24
|
-
type: mediaType(name),
|
|
25
|
-
size: info.size,
|
|
26
|
-
modifiedAt: info.mtime.toISOString()
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
return {
|
|
30
|
-
async list() {
|
|
31
|
-
if (!existsSync(dir)) return [];
|
|
32
|
-
const files = (await readdir(dir)).filter(isMediaFile);
|
|
33
|
-
return sortAssets(await Promise.all(files.map(describe)));
|
|
34
|
-
},
|
|
35
|
-
async write({ name, data }) {
|
|
36
|
-
checkUpload(name, data.byteLength, media.maxSize);
|
|
37
|
-
const file = toAssetName(name, createHash("sha256").update(data).digest("hex"));
|
|
38
|
-
await mkdir(dir, { recursive: true });
|
|
39
|
-
await writeFile(join(dir, file), data);
|
|
40
|
-
return describe(file);
|
|
41
|
-
},
|
|
42
|
-
async remove(name) {
|
|
43
|
-
if (!isAssetName(name)) throw new Error(`[forgepress] "${name}" is not a valid asset name`);
|
|
44
|
-
await rm(join(dir, name), { force: true });
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
function createSource(start, paths = defaultPaths, options = {}) {
|
|
49
|
-
const rows = /* @__PURE__ */ new Map();
|
|
50
|
-
let root;
|
|
51
|
-
let schema;
|
|
52
|
-
const resolve = () => root ??= findRoot(start, paths.dir);
|
|
53
|
-
const visible = (row) => options.unpublished === true || row.status === "published";
|
|
54
|
-
async function read(collection, id) {
|
|
55
|
-
const file = paths.entry(collection, id);
|
|
56
|
-
return parseEntry(await readFile(join(resolve(), file), "utf8"), file);
|
|
57
|
-
}
|
|
58
|
-
async function load(collection) {
|
|
59
|
-
const ids = readEntryIds(join(resolve(), paths.collection(collection)));
|
|
60
|
-
return sortByCreation((await Promise.all(ids.map((id) => read(collection, id)))).filter(visible));
|
|
61
|
-
}
|
|
62
|
-
function all(collection) {
|
|
63
|
-
let pending = rows.get(collection);
|
|
64
|
-
if (!pending) {
|
|
65
|
-
pending = load(collection);
|
|
66
|
-
rows.set(collection, pending);
|
|
67
|
-
}
|
|
68
|
-
return pending;
|
|
69
|
-
}
|
|
70
|
-
return {
|
|
71
|
-
schema: () => schema ??= readFile(join(resolve(), paths.schema), "utf8").then((text) => parseSchema(text, paths.schema)),
|
|
72
|
-
list: all,
|
|
73
|
-
entry: async (collection, id) => {
|
|
74
|
-
if (!existsSync(join(resolve(), paths.entry(collection, id)))) return void 0;
|
|
75
|
-
const row = await read(collection, id);
|
|
76
|
-
return visible(row) ? row : void 0;
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
const IDENTIFIER = /^[A-Z_$][\w$]*$/i;
|
|
81
|
-
const WIDTH = 80;
|
|
82
|
-
const ESCAPES = {
|
|
83
|
-
"\\": "\\\\",
|
|
84
|
-
"'": "\\'",
|
|
85
|
-
"\n": "\\n",
|
|
86
|
-
"\r": "\\r",
|
|
87
|
-
" ": "\\t"
|
|
88
|
-
};
|
|
89
|
-
function style(config) {
|
|
90
|
-
return {
|
|
91
|
-
indent: " ".repeat(config?.indent ?? 2),
|
|
92
|
-
semi: config?.semi ? ";" : ""
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
function unsafe(code) {
|
|
96
|
-
return code < 32 || code >= 127 && code <= 159 || code === 8232 || code === 8233;
|
|
97
|
-
}
|
|
98
|
-
function string(value) {
|
|
99
|
-
let out = "";
|
|
100
|
-
for (const char of value) {
|
|
101
|
-
const escaped = ESCAPES[char];
|
|
102
|
-
const code = char.charCodeAt(0);
|
|
103
|
-
if (escaped !== void 0) out += escaped;
|
|
104
|
-
else if (unsafe(code)) out += `\\u${code.toString(16).padStart(4, "0")}`;
|
|
105
|
-
else out += char;
|
|
106
|
-
}
|
|
107
|
-
return `'${out}'`;
|
|
108
|
-
}
|
|
109
|
-
function key(value) {
|
|
110
|
-
return IDENTIFIER.test(value) ? value : string(value);
|
|
111
|
-
}
|
|
112
|
-
function storable(input) {
|
|
113
|
-
return input !== void 0 && input !== null && !(typeof input === "number" && !Number.isFinite(input));
|
|
114
|
-
}
|
|
115
|
-
function value(input, style, depth) {
|
|
116
|
-
if (typeof input === "string") return string(input);
|
|
117
|
-
if (typeof input === "number") return String(input);
|
|
118
|
-
if (typeof input === "boolean") return String(input);
|
|
119
|
-
const pad = style.indent.repeat(depth + 1);
|
|
120
|
-
const close = style.indent.repeat(depth);
|
|
121
|
-
if (Array.isArray(input)) {
|
|
122
|
-
const kept = input.filter(storable);
|
|
123
|
-
if (kept.length === 0) return "[]";
|
|
124
|
-
const items = kept.map((item) => value(item, style, depth + 1));
|
|
125
|
-
const inline = `[${items.join(", ")}]`;
|
|
126
|
-
if (kept.every((item) => typeof item !== "object") && close.length + inline.length <= WIDTH) return inline;
|
|
127
|
-
return `[\n${items.map((item) => `${pad}${item},`).join("\n")}\n${close}]`;
|
|
128
|
-
}
|
|
129
|
-
const entries = Object.entries(input).filter(([, item]) => storable(item));
|
|
130
|
-
if (entries.length === 0) return "{}";
|
|
131
|
-
return `{\n${entries.map(([name, item]) => `${pad}${key(name)}: ${value(item, style, depth + 1)},`).join("\n")}\n${close}}`;
|
|
132
|
-
}
|
|
133
|
-
function serializeSchema(schema, config) {
|
|
134
|
-
const current = style(config);
|
|
135
|
-
return [
|
|
136
|
-
`import type { ForgePressSchema } from 'forgepress'${current.semi}`,
|
|
137
|
-
"",
|
|
138
|
-
`export default ${value(schema, current, 0)} as const satisfies ForgePressSchema${current.semi}`,
|
|
139
|
-
""
|
|
140
|
-
].join("\n");
|
|
141
|
-
}
|
|
142
|
-
function ordered(row) {
|
|
143
|
-
const entries = Object.entries(row).filter(([, item]) => storable(item));
|
|
144
|
-
const rank = (name) => {
|
|
145
|
-
const index = META_KEYS.indexOf(name);
|
|
146
|
-
return index === -1 ? META_KEYS.length : index;
|
|
147
|
-
};
|
|
148
|
-
return entries.sort(([left], [right]) => rank(left) - rank(right));
|
|
149
|
-
}
|
|
150
|
-
function serializeEntry(collection, row, config) {
|
|
151
|
-
const current = style(config);
|
|
152
|
-
const body = ordered(row).map(([name, item]) => `${current.indent}${key(name)}: ${value(item, current, 1)},`).join("\n");
|
|
153
|
-
return [
|
|
154
|
-
`import type { ForgePressEntry } from 'forgepress'${current.semi}`,
|
|
155
|
-
"",
|
|
156
|
-
`export default {\n${body}\n} satisfies ForgePressEntry<${string(collection)}>${current.semi}`,
|
|
157
|
-
""
|
|
158
|
-
].join("\n");
|
|
159
|
-
}
|
|
160
|
-
function collectionName$1(collection) {
|
|
161
|
-
if (!isCollectionName(collection)) throw new Error(`[forgepress] ${JSON.stringify(collection)} is not a collection name`);
|
|
162
|
-
return collection;
|
|
163
|
-
}
|
|
164
|
-
function entryId$1(id) {
|
|
165
|
-
if (typeof id !== "string" || !isEntryId(id)) throw new Error(`[forgepress] ${JSON.stringify(id)} is not an entry id`);
|
|
166
|
-
return id;
|
|
167
|
-
}
|
|
168
|
-
function createWriter(root, paths = defaultPaths, config) {
|
|
169
|
-
const directory = (collection) => join(root, paths.collection(collectionName$1(collection)));
|
|
170
|
-
const file = (collection, id) => join(root, paths.entry(collectionName$1(collection), entryId$1(id)));
|
|
171
|
-
return {
|
|
172
|
-
async writeSchema(schema) {
|
|
173
|
-
await mkdir(join(root, paths.dir), { recursive: true });
|
|
174
|
-
await writeFile(join(root, paths.schema), serializeSchema(schema, config));
|
|
175
|
-
},
|
|
176
|
-
async writeEntry(collection, row) {
|
|
177
|
-
const target = file(collection, row.id);
|
|
178
|
-
await mkdir(directory(collection), { recursive: true });
|
|
179
|
-
await writeFile(target, serializeEntry(collection, row, config));
|
|
180
|
-
},
|
|
181
|
-
async removeEntry(collection, id) {
|
|
182
|
-
await rm(file(collection, id), { force: true });
|
|
183
|
-
},
|
|
184
|
-
async writeContent(collection, rows) {
|
|
185
|
-
const targets = rows.map((row) => [file(collection, row.id), serializeEntry(collection, row, config)]);
|
|
186
|
-
const kept = new Set(rows.map((row) => row.id));
|
|
187
|
-
await mkdir(directory(collection), { recursive: true });
|
|
188
|
-
const stale = readEntryIds(directory(collection)).filter((id) => !kept.has(id));
|
|
189
|
-
await Promise.all(stale.map((id) => rm(file(collection, id), { force: true })));
|
|
190
|
-
await Promise.all(targets.map(([target, text]) => writeFile(target, text)));
|
|
191
|
-
},
|
|
192
|
-
async removeCollection(collection) {
|
|
193
|
-
await rm(directory(collection), {
|
|
194
|
-
recursive: true,
|
|
195
|
-
force: true
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
var EndpointError = class extends Error {
|
|
201
|
-
status;
|
|
202
|
-
constructor(status, message) {
|
|
203
|
-
super(message);
|
|
204
|
-
this.name = "EndpointError";
|
|
205
|
-
this.status = status;
|
|
206
|
-
}
|
|
207
|
-
};
|
|
208
|
-
function sameOrigin(request) {
|
|
209
|
-
const site = request.headers["sec-fetch-site"];
|
|
210
|
-
if (site !== void 0) return site === "same-origin" || site === "none";
|
|
211
|
-
const { origin, host } = request.headers;
|
|
212
|
-
if (origin === void 0) return true;
|
|
213
|
-
try {
|
|
214
|
-
return new URL(origin).host === host;
|
|
215
|
-
} catch {
|
|
216
|
-
return false;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
function missing(path) {
|
|
220
|
-
return new EndpointError(404, `there is no endpoint ${JSON.stringify(path)}`);
|
|
221
|
-
}
|
|
222
|
-
async function bytes(request) {
|
|
223
|
-
const chunks = [];
|
|
224
|
-
for await (const chunk of request) chunks.push(chunk);
|
|
225
|
-
return Buffer.concat(chunks);
|
|
226
|
-
}
|
|
227
|
-
async function body(request) {
|
|
228
|
-
const text = (await bytes(request)).toString("utf8");
|
|
229
|
-
try {
|
|
230
|
-
return JSON.parse(text);
|
|
231
|
-
} catch {
|
|
232
|
-
throw new EndpointError(400, "the request body is not valid JSON");
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
function json(response, payload) {
|
|
236
|
-
response.statusCode = 200;
|
|
237
|
-
response.setHeader("content-type", "application/json");
|
|
238
|
-
response.end(JSON.stringify(payload));
|
|
239
|
-
}
|
|
240
|
-
function done(response) {
|
|
241
|
-
response.statusCode = 204;
|
|
242
|
-
response.end();
|
|
243
|
-
}
|
|
244
|
-
function decode(value, path) {
|
|
245
|
-
try {
|
|
246
|
-
return decodeURIComponent(value);
|
|
247
|
-
} catch {
|
|
248
|
-
throw new EndpointError(400, `${JSON.stringify(path)} is not a valid path`);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
function segments(path, prefix, count) {
|
|
252
|
-
const found = path.slice(prefix.length).split("/").filter(Boolean).map((segment) => decode(segment, path));
|
|
253
|
-
if (found.length > count) throw missing(path);
|
|
254
|
-
return found;
|
|
255
|
-
}
|
|
256
|
-
function collectionName(name) {
|
|
257
|
-
if (name === void 0 || !isCollectionName(name)) throw new EndpointError(400, `${JSON.stringify(name ?? "")} is not a collection name`);
|
|
258
|
-
return name;
|
|
259
|
-
}
|
|
260
|
-
function entryId(id) {
|
|
261
|
-
if (typeof id !== "string" || !isEntryId(id)) throw new EndpointError(400, `${JSON.stringify(id ?? "")} is not an entry id`);
|
|
262
|
-
return id;
|
|
263
|
-
}
|
|
264
|
-
function entry(value) {
|
|
265
|
-
if (!isRecord(value)) throw new EndpointError(400, "an entry has to be an object");
|
|
266
|
-
entryId(value.id);
|
|
267
|
-
return value;
|
|
268
|
-
}
|
|
269
|
-
async function media(config, root, path, request, response) {
|
|
270
|
-
const store = createMediaStore(root, config.media);
|
|
271
|
-
if (request.method === "GET") return json(response, await store.list());
|
|
272
|
-
const name = decode(path.slice(7), path);
|
|
273
|
-
if (request.method === "DELETE") {
|
|
274
|
-
if (!isAssetName(name)) throw new EndpointError(400, `${JSON.stringify(name)} is not an asset name`);
|
|
275
|
-
await store.remove(name);
|
|
276
|
-
return done(response);
|
|
277
|
-
}
|
|
278
|
-
if (!mediaType(name)) throw new EndpointError(400, `${JSON.stringify(name)} is not a supported media file`);
|
|
279
|
-
return json(response, await store.write({
|
|
280
|
-
name,
|
|
281
|
-
data: await bytes(request)
|
|
282
|
-
}));
|
|
283
|
-
}
|
|
284
|
-
async function read(config, root, path, response) {
|
|
285
|
-
const source = createSource(root, config.paths, { unpublished: true });
|
|
286
|
-
if (path === "/schema") return json(response, await source.schema());
|
|
287
|
-
if (path.startsWith("/content/")) {
|
|
288
|
-
const [collection = ""] = segments(path, "/content/", 1);
|
|
289
|
-
const schema = await source.schema();
|
|
290
|
-
return json(response, Object.hasOwn(schema.collections, collection) ? await source.list(collection) : []);
|
|
291
|
-
}
|
|
292
|
-
if (!path.startsWith("/entry/")) throw missing(path);
|
|
293
|
-
const [collection = "", id = ""] = segments(path, "/entry/", 2);
|
|
294
|
-
const schema = await source.schema();
|
|
295
|
-
const row = Object.hasOwn(schema.collections, collection) && isEntryId(id) ? await source.entry(collection, id) : void 0;
|
|
296
|
-
if (row) return json(response, row);
|
|
297
|
-
response.statusCode = 404;
|
|
298
|
-
response.end();
|
|
299
|
-
}
|
|
300
|
-
async function write(config, root, path, request, response) {
|
|
301
|
-
const writer = createWriter(root, config.paths, config.content);
|
|
302
|
-
const removing = request.method === "DELETE";
|
|
303
|
-
if (path === "/schema" && !removing) {
|
|
304
|
-
const schema = await body(request);
|
|
305
|
-
const issues = validateSchema(schema);
|
|
306
|
-
if (issues.length > 0) throw new EndpointError(400, `the schema is not valid:\n${issues.map((issue) => issue.message).join("\n")}`);
|
|
307
|
-
await writer.writeSchema(schema);
|
|
308
|
-
} else if (path.startsWith("/entry/")) {
|
|
309
|
-
const [name, id] = segments(path, "/entry/", 2);
|
|
310
|
-
const collection = collectionName(name);
|
|
311
|
-
if (removing) await writer.removeEntry(collection, entryId(id));
|
|
312
|
-
else {
|
|
313
|
-
const row = entry(await body(request));
|
|
314
|
-
if (row.id !== entryId(id)) throw new EndpointError(400, `the entry id ${JSON.stringify(row.id)} doesn't match the path`);
|
|
315
|
-
await writer.writeEntry(collection, row);
|
|
316
|
-
}
|
|
317
|
-
} else if (path.startsWith("/content/")) {
|
|
318
|
-
const [name] = segments(path, "/content/", 1);
|
|
319
|
-
const collection = collectionName(name);
|
|
320
|
-
if (removing) await writer.removeCollection(collection);
|
|
321
|
-
else {
|
|
322
|
-
const rows = await body(request);
|
|
323
|
-
if (!Array.isArray(rows)) throw new EndpointError(400, "the entries have to be a list");
|
|
324
|
-
await writer.writeContent(collection, rows.map(entry));
|
|
325
|
-
}
|
|
326
|
-
} else throw missing(path);
|
|
327
|
-
done(response);
|
|
328
|
-
}
|
|
329
|
-
async function handle(config, root, request, response) {
|
|
330
|
-
if (!sameOrigin(request)) throw new EndpointError(403, "the dev endpoint only accepts requests from pages on its own origin");
|
|
331
|
-
const path = (request.url ?? "").slice(ENDPOINT.length);
|
|
332
|
-
if (path === "/media" || path.startsWith("/media/")) return media(config, root, path, request, response);
|
|
333
|
-
if (request.method === "GET") return read(config, root, path, response);
|
|
334
|
-
return write(config, root, path, request, response);
|
|
335
|
-
}
|
|
336
|
-
function resolved(id) {
|
|
337
|
-
return `\0${id}`;
|
|
338
|
-
}
|
|
339
|
-
function generateSettings(local, config) {
|
|
340
|
-
return [
|
|
341
|
-
`export const local = ${local}`,
|
|
342
|
-
`export const provider = ${JSON.stringify(config.provider ?? null)}`,
|
|
343
|
-
`export const format = ${JSON.stringify(config.content ?? null)}`,
|
|
344
|
-
`export const contentPath = ${JSON.stringify(config.paths.dir)}`,
|
|
345
|
-
`export const media = ${JSON.stringify(config.media)}`,
|
|
346
|
-
""
|
|
347
|
-
].join("\n");
|
|
348
|
-
}
|
|
349
|
-
const TYPES = `// Generated by ForgePress. Do not edit.
|
|
350
|
-
import type schema from './schema'
|
|
351
|
-
|
|
352
|
-
declare module 'forgepress' {
|
|
353
|
-
interface ForgePressSchemaRegistry {
|
|
354
|
-
schema: typeof schema
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
export {}
|
|
359
|
-
`;
|
|
360
|
-
function writeTypes(root, config) {
|
|
361
|
-
const path = join(root, config.paths.types);
|
|
362
|
-
if ((existsSync(path) ? readFileSync(path, "utf8") : void 0) === TYPES) return;
|
|
363
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
364
|
-
writeFileSync(path, TYPES);
|
|
365
|
-
}
|
|
366
|
-
function inside(parent, child) {
|
|
367
|
-
const path = relative(parent, child);
|
|
368
|
-
return path !== "" && path !== ".." && !path.startsWith(`..${sep}`) && !isAbsolute(path);
|
|
369
|
-
}
|
|
370
|
-
async function sendOutput(response, file) {
|
|
371
|
-
try {
|
|
372
|
-
const text = await readFile(file);
|
|
373
|
-
response.setHeader("Content-Type", "application/json");
|
|
374
|
-
response.setHeader("Cache-Control", "no-cache");
|
|
375
|
-
response.end(text);
|
|
376
|
-
} catch {
|
|
377
|
-
response.statusCode = 404;
|
|
378
|
-
response.end();
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
const unpluginFactory = (options) => {
|
|
382
|
-
let root = options?.root ? resolve(options.root) : findRoot(process.cwd());
|
|
383
|
-
let local = false;
|
|
384
|
-
let serving = false;
|
|
385
|
-
let publicDir = "";
|
|
386
|
-
let config;
|
|
387
|
-
async function load() {
|
|
388
|
-
const loaded = await loadConfig(root);
|
|
389
|
-
return resolveConfig({
|
|
390
|
-
...loaded,
|
|
391
|
-
media: {
|
|
392
|
-
...loaded?.media,
|
|
393
|
-
...options?.media
|
|
394
|
-
}
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
function resolve$1() {
|
|
398
|
-
config ??= load();
|
|
399
|
-
return config;
|
|
400
|
-
}
|
|
401
|
-
return {
|
|
402
|
-
name: "unplugin-forgepress",
|
|
403
|
-
vite: {
|
|
404
|
-
configResolved(resolvedConfig) {
|
|
405
|
-
root = options?.root ? resolve(resolvedConfig.root, options.root) : findRoot(resolvedConfig.root);
|
|
406
|
-
serving = resolvedConfig.command === "serve";
|
|
407
|
-
local = serving && options?.write !== false;
|
|
408
|
-
publicDir = resolvedConfig.publicDir ?? "";
|
|
409
|
-
},
|
|
410
|
-
async configureServer(server) {
|
|
411
|
-
const current = await resolve$1();
|
|
412
|
-
const { logger } = server.config;
|
|
413
|
-
const output = outputDir(root, current);
|
|
414
|
-
let reported = "";
|
|
415
|
-
process.env[OUTPUT_VARIABLE] = output;
|
|
416
|
-
let pending;
|
|
417
|
-
function report(issues) {
|
|
418
|
-
const message = issues.length > 0 ? new ContentError(issues).message : "";
|
|
419
|
-
if (message === reported) return;
|
|
420
|
-
if (message) logger.warn(`${message}\n[forgepress] the build fails until ${issues.length === 1 ? "this problem is" : "these problems are"} fixed`, { timestamp: true });
|
|
421
|
-
else logger.info("[forgepress] content problems are fixed", { timestamp: true });
|
|
422
|
-
reported = message;
|
|
423
|
-
}
|
|
424
|
-
async function refresh() {
|
|
425
|
-
try {
|
|
426
|
-
report((await buildOutput(root, current, { dev: true })).issues);
|
|
427
|
-
} catch (error) {
|
|
428
|
-
if (!(error instanceof ContentError)) throw error;
|
|
429
|
-
report(error.issues);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
function reload() {
|
|
433
|
-
server.ws.send({ type: "full-reload" });
|
|
434
|
-
}
|
|
435
|
-
function schedule() {
|
|
436
|
-
clearTimeout(pending);
|
|
437
|
-
pending = setTimeout(() => {
|
|
438
|
-
refresh().catch((error) => logger.error(`[forgepress] could not write the content output: ${errorMessage(error)}`)).finally(reload);
|
|
439
|
-
}, 100);
|
|
440
|
-
}
|
|
441
|
-
const schemaFile = join(root, current.paths.schema);
|
|
442
|
-
const contentDir = join(root, current.paths.content);
|
|
443
|
-
function watch(file) {
|
|
444
|
-
if (file === schemaFile || file.startsWith(`${contentDir}${sep}`)) schedule();
|
|
445
|
-
}
|
|
446
|
-
server.watcher.add([schemaFile, contentDir]);
|
|
447
|
-
server.watcher.on("add", watch);
|
|
448
|
-
server.watcher.on("change", watch);
|
|
449
|
-
server.watcher.on("unlink", watch);
|
|
450
|
-
await refresh().catch((error) => logger.error(`[forgepress] could not write the content output: ${errorMessage(error)}`));
|
|
451
|
-
if (publicDir && inside(publicDir, output)) {
|
|
452
|
-
const prefix = `/${relative(publicDir, output).split(sep).join("/")}/`;
|
|
453
|
-
server.middlewares.use((request, response, next) => {
|
|
454
|
-
const path = decodeURIComponent(request.url?.split("?")[0] ?? "");
|
|
455
|
-
if (request.method !== "GET" && request.method !== "HEAD" || !path.startsWith(prefix)) return next();
|
|
456
|
-
const file = join(output, path.slice(prefix.length));
|
|
457
|
-
if (!inside(output, file)) {
|
|
458
|
-
response.statusCode = 404;
|
|
459
|
-
response.end();
|
|
460
|
-
return;
|
|
461
|
-
}
|
|
462
|
-
sendOutput(response, file);
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
if (options?.write === false) return;
|
|
466
|
-
server.middlewares.use((request, response, next) => {
|
|
467
|
-
const method = request.method ?? "";
|
|
468
|
-
if (!(method === "POST" || method === "DELETE" || method === "GET") || !request.url?.startsWith(`/__forgepress/`)) return next();
|
|
469
|
-
handle(current, root, request, response).then(() => {
|
|
470
|
-
if (method !== "GET") schedule();
|
|
471
|
-
}).catch((error) => {
|
|
472
|
-
response.statusCode = error instanceof EndpointError ? error.status : 500;
|
|
473
|
-
response.end(errorMessage(error));
|
|
474
|
-
});
|
|
475
|
-
});
|
|
476
|
-
}
|
|
477
|
-
},
|
|
478
|
-
async buildStart() {
|
|
479
|
-
const current = await resolve$1();
|
|
480
|
-
writeTypes(root, current);
|
|
481
|
-
if (serving) return;
|
|
482
|
-
process.env[OUTPUT_VARIABLE] = outputDir(root, current);
|
|
483
|
-
await buildOutput(root, current);
|
|
484
|
-
},
|
|
485
|
-
resolveId(id) {
|
|
486
|
-
if (id === "virtual:forgepress/settings") return resolved(id);
|
|
487
|
-
},
|
|
488
|
-
async load(id) {
|
|
489
|
-
if (id === resolved("virtual:forgepress/settings")) return generateSettings(local, await resolve$1());
|
|
490
|
-
}
|
|
491
|
-
};
|
|
492
|
-
};
|
|
493
|
-
const unplugin = /* #__PURE__ */ createUnplugin(unpluginFactory);
|
|
494
|
-
const vitePlugin = unplugin.vite;
|
|
495
|
-
const rollupPlugin = unplugin.rollup;
|
|
496
|
-
const rolldownPlugin = unplugin.rolldown;
|
|
497
|
-
const webpackPlugin = unplugin.webpack;
|
|
498
|
-
const rspackPlugin = unplugin.rspack;
|
|
499
|
-
const farmPlugin = unplugin.farm;
|
|
500
|
-
const bunPlugin = unplugin.bun;
|
|
1
|
+
import { bunPlugin, farmPlugin, rolldownPlugin, rollupPlugin, rspackPlugin, unplugin, unpluginFactory, vitePlugin, webpackPlugin } from "./_chunks/plugin.mjs";
|
|
501
2
|
export { bunPlugin, unplugin as default, farmPlugin, rolldownPlugin, rollupPlugin, rspackPlugin, unplugin, unpluginFactory, vitePlugin, webpackPlugin };
|
package/package.json
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forgepress",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.2",
|
|
5
5
|
"description": "Fully static, git based CMS.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/
|
|
9
|
+
"url": "git+https://github.com/forgepress-cms/forgepress.git"
|
|
10
10
|
},
|
|
11
|
-
"sideEffects":
|
|
11
|
+
"sideEffects": [
|
|
12
|
+
"./dist/next/preview.mjs",
|
|
13
|
+
"./dist/next/reload.mjs"
|
|
14
|
+
],
|
|
12
15
|
"imports": {
|
|
13
16
|
"#content-reader": {
|
|
14
17
|
"node": "./dist/disk/reader.mjs",
|
|
@@ -24,6 +27,26 @@
|
|
|
24
27
|
"types": "./dist/unplugin.d.mts",
|
|
25
28
|
"default": "./dist/unplugin.mjs"
|
|
26
29
|
},
|
|
30
|
+
"./nuxt": {
|
|
31
|
+
"types": "./dist/plugin/nuxt.d.mts",
|
|
32
|
+
"default": "./dist/plugin/nuxt.mjs"
|
|
33
|
+
},
|
|
34
|
+
"./next": {
|
|
35
|
+
"types": "./dist/plugin/next.d.mts",
|
|
36
|
+
"default": "./dist/plugin/next.mjs"
|
|
37
|
+
},
|
|
38
|
+
"./next/preview": {
|
|
39
|
+
"types": "./dist/next/preview.d.mts",
|
|
40
|
+
"default": "./dist/next/preview.mjs"
|
|
41
|
+
},
|
|
42
|
+
"./next/reload": {
|
|
43
|
+
"types": "./dist/next/reload.d.mts",
|
|
44
|
+
"default": "./dist/next/reload.mjs"
|
|
45
|
+
},
|
|
46
|
+
"./next/settings": {
|
|
47
|
+
"types": "./dist/next/settings.d.mts",
|
|
48
|
+
"default": "./dist/next/settings.mjs"
|
|
49
|
+
},
|
|
27
50
|
"./editor": {
|
|
28
51
|
"types": "./dist/editor/index.d.mts",
|
|
29
52
|
"default": "./dist/editor/index.mjs"
|
|
@@ -31,6 +54,10 @@
|
|
|
31
54
|
"./preview": {
|
|
32
55
|
"types": "./dist/preview/index.d.mts",
|
|
33
56
|
"default": "./dist/preview/index.mjs"
|
|
57
|
+
},
|
|
58
|
+
"./react": {
|
|
59
|
+
"types": "./dist/preview/react.d.mts",
|
|
60
|
+
"default": "./dist/preview/react.mjs"
|
|
34
61
|
}
|
|
35
62
|
},
|
|
36
63
|
"types": "./dist/index.d.mts",
|
|
@@ -41,28 +68,62 @@
|
|
|
41
68
|
"dist"
|
|
42
69
|
],
|
|
43
70
|
"peerDependencies": {
|
|
71
|
+
"@nuxt/kit": "^4.0.0",
|
|
72
|
+
"@nuxt/schema": "^4.0.0",
|
|
73
|
+
"next": "^16.3.0",
|
|
74
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
44
75
|
"vue": "^3.5.0"
|
|
45
76
|
},
|
|
46
77
|
"peerDependenciesMeta": {
|
|
78
|
+
"@nuxt/kit": {
|
|
79
|
+
"optional": true
|
|
80
|
+
},
|
|
81
|
+
"@nuxt/schema": {
|
|
82
|
+
"optional": true
|
|
83
|
+
},
|
|
84
|
+
"next": {
|
|
85
|
+
"optional": true
|
|
86
|
+
},
|
|
87
|
+
"react": {
|
|
88
|
+
"optional": true
|
|
89
|
+
},
|
|
47
90
|
"vue": {
|
|
48
91
|
"optional": true
|
|
49
92
|
}
|
|
50
93
|
},
|
|
51
94
|
"dependencies": {
|
|
52
|
-
"
|
|
95
|
+
"citty": "^0.2.2",
|
|
96
|
+
"idb-keyval": "^6.3.0",
|
|
97
|
+
"jsonc-parser": "^3.3.1",
|
|
98
|
+
"oauth4webapi": "^3.8.8",
|
|
99
|
+
"perfect-debounce": "^2.1.0",
|
|
100
|
+
"unplugin": "^3.0.0",
|
|
101
|
+
"ws": "^8.21.3"
|
|
53
102
|
},
|
|
54
103
|
"devDependencies": {
|
|
55
104
|
"@antfu/eslint-config": "^9.5.1",
|
|
56
105
|
"@arethetypeswrong/cli": "^0.18.5",
|
|
57
|
-
"@
|
|
106
|
+
"@fontsource-variable/bricolage-grotesque": "^5.3.0",
|
|
107
|
+
"@iconify-json/hugeicons": "^1.2.34",
|
|
108
|
+
"@nuxt/kit": "^4.5.2",
|
|
109
|
+
"@nuxt/schema": "^4.5.2",
|
|
58
110
|
"@nuxt/ui": "^4.11.1",
|
|
59
111
|
"@tanstack/vue-table": "^8.21.3",
|
|
60
112
|
"@types/node": "^24.0.0",
|
|
113
|
+
"@types/react": "^19.3.0",
|
|
114
|
+
"@types/react-dom": "^19.3.0",
|
|
115
|
+
"@types/ws": "^8.18.1",
|
|
61
116
|
"@vitejs/plugin-vue": "^6.0.8",
|
|
117
|
+
"@vueuse/core": "^14.4.0",
|
|
118
|
+
"diff": "^8.0.4",
|
|
62
119
|
"eslint": "^10.10.0",
|
|
120
|
+
"fake-indexeddb": "^6.2.5",
|
|
63
121
|
"happy-dom": "^20.14.3",
|
|
122
|
+
"next": "^16.3.5",
|
|
64
123
|
"obuild": "^0.4.39",
|
|
65
124
|
"publint": "^0.3.24",
|
|
125
|
+
"react": "^19.3.0",
|
|
126
|
+
"react-dom": "^19.3.0",
|
|
66
127
|
"tailwindcss": "^4.3.3",
|
|
67
128
|
"typescript": "^6.0.3",
|
|
68
129
|
"vite": "^8.3.0",
|
|
@@ -72,13 +133,16 @@
|
|
|
72
133
|
},
|
|
73
134
|
"scripts": {
|
|
74
135
|
"lint": "eslint .",
|
|
75
|
-
"typecheck": "vue-tsc --noEmit && pnpm --filter=playground-nuxt run typecheck",
|
|
136
|
+
"typecheck": "vue-tsc --noEmit && pnpm --filter=playground-nuxt run typecheck && pnpm --filter=playground-next run typecheck && pnpm --filter=docs run typecheck",
|
|
76
137
|
"test": "vitest run",
|
|
77
138
|
"test:watch": "vitest",
|
|
78
139
|
"build": "obuild && pnpm run build:editor",
|
|
79
|
-
"build:editor": "vite build --config vite.
|
|
80
|
-
"dev
|
|
140
|
+
"build:editor": "vite build --config editor/vite.config.mjs",
|
|
141
|
+
"dev": "node --watch-path=src --watch-preserve-output scripts/build.mjs",
|
|
142
|
+
"dev:editor": "vite build --config editor/vite.config.mjs --watch",
|
|
81
143
|
"dev:nuxt": "pnpm --filter=playground-nuxt run dev",
|
|
144
|
+
"dev:next": "pnpm --filter=playground-next run dev",
|
|
145
|
+
"dev:docs": "pnpm --filter=docs run dev",
|
|
82
146
|
"check:package": "publint --strict && attw --pack . --profile esm-only"
|
|
83
147
|
}
|
|
84
148
|
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
type ProviderType = 'github' | 'gitlab' | 'forgejo';
|
|
2
|
-
interface ProviderRepository {
|
|
3
|
-
owner: string;
|
|
4
|
-
name: string;
|
|
5
|
-
branch?: string;
|
|
6
|
-
}
|
|
7
|
-
interface ProviderConfig {
|
|
8
|
-
type: ProviderType;
|
|
9
|
-
repository: ProviderRepository;
|
|
10
|
-
base?: string;
|
|
11
|
-
url?: string;
|
|
12
|
-
clientId?: string;
|
|
13
|
-
scopes?: readonly string[];
|
|
14
|
-
redirectUri?: string;
|
|
15
|
-
commitMessage?: string;
|
|
16
|
-
}
|
|
17
|
-
interface ContentConfig {
|
|
18
|
-
indent?: number;
|
|
19
|
-
semi?: boolean;
|
|
20
|
-
}
|
|
21
|
-
export interface MediaConfig {
|
|
22
|
-
dir?: string;
|
|
23
|
-
url?: string;
|
|
24
|
-
maxSize?: number;
|
|
25
|
-
}
|
|
26
|
-
export interface OutputConfig {
|
|
27
|
-
dir?: string;
|
|
28
|
-
}
|
|
29
|
-
export interface ForgePressConfig {
|
|
30
|
-
path?: string;
|
|
31
|
-
provider?: ProviderConfig;
|
|
32
|
-
content?: ContentConfig;
|
|
33
|
-
media?: MediaConfig;
|
|
34
|
-
output?: OutputConfig;
|
|
35
|
-
}
|