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/_chunks/reader2.mjs
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { isRecord, plain, same } from "./value.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import "./
|
|
4
|
-
import {
|
|
2
|
+
import { once } from "./once.mjs";
|
|
3
|
+
import { assetUrl, base64ToText, createOutput, createPaths, isEntryId, isMediaFile, repositoryPaths, sortByCreation, textToBase64 } from "./media.mjs";
|
|
4
|
+
import { createFileSource } from "./content.mjs";
|
|
5
|
+
import "./serialize.mjs";
|
|
6
|
+
import "./libs/diff.mjs";
|
|
7
|
+
import { AuthorizationResponseError, None, ResponseBodyError, allowInsecureRequests, processRefreshTokenResponse, refreshTokenGrantRequest } from "oauth4webapi";
|
|
8
|
+
import { clear, createStore, del, delMany, entries, get, set } from "idb-keyval";
|
|
5
9
|
function mergeEntries(rows, overlay) {
|
|
6
10
|
if (!overlay) return [...rows];
|
|
7
11
|
const merged = new Map(rows.map((row) => [row.id, row]));
|
|
@@ -41,6 +45,10 @@ function createContentChanges(base, ready, mutate) {
|
|
|
41
45
|
function publishedUploads(changes) {
|
|
42
46
|
return Object.values(changes.publishedMedia ?? {}).filter(Boolean);
|
|
43
47
|
}
|
|
48
|
+
function localUploads(changes) {
|
|
49
|
+
const removed = new Set(changes.removed);
|
|
50
|
+
return [...new Map([...Object.values(changes.uploads), ...publishedUploads(changes)].map((upload) => [upload.name, upload])).values()].filter((upload) => !removed.has(upload.name));
|
|
51
|
+
}
|
|
44
52
|
function createPreviews() {
|
|
45
53
|
const urls = /* @__PURE__ */ new Map();
|
|
46
54
|
function preview(upload) {
|
|
@@ -76,6 +84,19 @@ function createPreviews() {
|
|
|
76
84
|
}
|
|
77
85
|
};
|
|
78
86
|
}
|
|
87
|
+
function emptyChanges() {
|
|
88
|
+
return {
|
|
89
|
+
entries: {},
|
|
90
|
+
uploads: {},
|
|
91
|
+
removed: []
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
async function readChanges(store) {
|
|
95
|
+
return {
|
|
96
|
+
...emptyChanges(),
|
|
97
|
+
...await store.read()
|
|
98
|
+
};
|
|
99
|
+
}
|
|
79
100
|
const PROVIDERS = {
|
|
80
101
|
github: {
|
|
81
102
|
name: "GitHub",
|
|
@@ -118,6 +139,7 @@ function describe(config) {
|
|
|
118
139
|
api: `${root}/api/v4`,
|
|
119
140
|
scopes: config.scopes ?? ["api"],
|
|
120
141
|
oauth: {
|
|
142
|
+
issuer: root,
|
|
121
143
|
authorize: `${root}/oauth/authorize`,
|
|
122
144
|
token: `${root}/oauth/token`
|
|
123
145
|
}
|
|
@@ -127,6 +149,7 @@ function describe(config) {
|
|
|
127
149
|
api: `${root}/api/v1`,
|
|
128
150
|
scopes: config.scopes ?? ["read:user", "write:repository"],
|
|
129
151
|
oauth: {
|
|
152
|
+
issuer: root,
|
|
130
153
|
authorize: `${root}/login/oauth/authorize`,
|
|
131
154
|
token: `${root}/login/oauth/access_token`
|
|
132
155
|
}
|
|
@@ -136,6 +159,19 @@ function publishable(files) {
|
|
|
136
159
|
if (files.length === 0) throw new Error("[forgepress] there is nothing to publish");
|
|
137
160
|
return files;
|
|
138
161
|
}
|
|
162
|
+
function toIdentity(login, name, avatar) {
|
|
163
|
+
return {
|
|
164
|
+
login,
|
|
165
|
+
...name ? { name } : {},
|
|
166
|
+
...avatar ? { avatar } : {}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function blobs(directory, items) {
|
|
170
|
+
return items.filter((item) => item.type === "blob").map((item) => ({
|
|
171
|
+
path: `${directory}/${item.path}`,
|
|
172
|
+
sha: item.sha
|
|
173
|
+
}));
|
|
174
|
+
}
|
|
139
175
|
function createRepositoryApi(config, base, token, headers = {}) {
|
|
140
176
|
const { name } = describe(config);
|
|
141
177
|
let known = config.repository.branch;
|
|
@@ -158,6 +194,11 @@ function createRepositoryApi(config, base, token, headers = {}) {
|
|
|
158
194
|
const response = await check(await send(path, init));
|
|
159
195
|
return response.status === 204 ? void 0 : await response.json();
|
|
160
196
|
}
|
|
197
|
+
async function find(path, missing = [404]) {
|
|
198
|
+
const response = await send(path);
|
|
199
|
+
if (missing.includes(response.status)) return void 0;
|
|
200
|
+
return await (await check(response)).json();
|
|
201
|
+
}
|
|
161
202
|
function post(path, body) {
|
|
162
203
|
return call(path, {
|
|
163
204
|
method: "POST",
|
|
@@ -176,6 +217,7 @@ function createRepositoryApi(config, base, token, headers = {}) {
|
|
|
176
217
|
send,
|
|
177
218
|
check,
|
|
178
219
|
call,
|
|
220
|
+
find,
|
|
179
221
|
post,
|
|
180
222
|
details,
|
|
181
223
|
branch
|
|
@@ -213,19 +255,13 @@ function createForgejoForge(config, token) {
|
|
|
213
255
|
}
|
|
214
256
|
}
|
|
215
257
|
async function sha(path, ref) {
|
|
216
|
-
|
|
217
|
-
if (response.status === 404) return void 0;
|
|
218
|
-
return (await (await repo.check(response)).json()).sha;
|
|
258
|
+
return (await repo.find(`/contents/${encodePath(path)}?ref=${encodeURIComponent(ref)}`))?.sha;
|
|
219
259
|
}
|
|
220
260
|
return {
|
|
221
261
|
async access() {
|
|
222
262
|
const [user, repository] = await Promise.all([repo.call(`${api}/user`), repo.details()]);
|
|
223
263
|
return {
|
|
224
|
-
identity:
|
|
225
|
-
login: user.login,
|
|
226
|
-
...user.full_name ? { name: user.full_name } : {},
|
|
227
|
-
...user.avatar_url ? { avatar: user.avatar_url } : {}
|
|
228
|
-
},
|
|
264
|
+
identity: toIdentity(user.login, user.full_name, user.avatar_url),
|
|
229
265
|
writable: repository.permissions?.push === true,
|
|
230
266
|
branch: await repo.branch()
|
|
231
267
|
};
|
|
@@ -236,10 +272,7 @@ function createForgejoForge(config, token) {
|
|
|
236
272
|
async files(commit, directory) {
|
|
237
273
|
const tree = await findTree(commit, directory, (found) => entries(found, false));
|
|
238
274
|
if (!tree) return [];
|
|
239
|
-
return (await entries(tree, true))
|
|
240
|
-
path: `${directory}/${item.path}`,
|
|
241
|
-
sha: item.sha
|
|
242
|
-
}));
|
|
275
|
+
return blobs(directory, await entries(tree, true));
|
|
243
276
|
},
|
|
244
277
|
async read(blob) {
|
|
245
278
|
return base64ToText((await repo.call(`/git/blobs/${blob}`)).content);
|
|
@@ -288,6 +321,7 @@ const FAILED = /* @__PURE__ */ new Set([
|
|
|
288
321
|
"timed_out",
|
|
289
322
|
"startup_failure"
|
|
290
323
|
]);
|
|
324
|
+
const UNREADABLE = [403, 404];
|
|
291
325
|
function runState(status, conclusion) {
|
|
292
326
|
if (status !== "completed" || conclusion === "action_required") return "pending";
|
|
293
327
|
if (conclusion === "success") return "success";
|
|
@@ -307,27 +341,22 @@ function createGitHubForge(config, token) {
|
|
|
307
341
|
"x-github-api-version": "2022-11-28"
|
|
308
342
|
});
|
|
309
343
|
let apps = true;
|
|
310
|
-
async function readable(path) {
|
|
311
|
-
const response = await repo.send(path);
|
|
312
|
-
if (response.status === 403 || response.status === 404) return void 0;
|
|
313
|
-
return await (await repo.check(response)).json();
|
|
314
|
-
}
|
|
315
344
|
async function runs(commit) {
|
|
316
|
-
return (await
|
|
345
|
+
return (await repo.find(`/actions/runs?head_sha=${commit}&per_page=100`, UNREADABLE))?.workflow_runs.filter((run) => BUILD_EVENTS.has(run.event) || run.path.startsWith("dynamic/pages/")).map((run) => ({
|
|
317
346
|
name: run.name ?? run.path,
|
|
318
347
|
state: runState(run.status, run.conclusion),
|
|
319
348
|
url: run.html_url
|
|
320
349
|
}));
|
|
321
350
|
}
|
|
322
351
|
async function statuses(commit) {
|
|
323
|
-
return (await
|
|
352
|
+
return (await repo.find(`/commits/${commit}/status?per_page=100`, UNREADABLE))?.statuses.map((status) => ({
|
|
324
353
|
name: status.context,
|
|
325
354
|
state: statusState(status.state),
|
|
326
355
|
...linked(status.target_url)
|
|
327
356
|
}));
|
|
328
357
|
}
|
|
329
358
|
async function others(commit) {
|
|
330
|
-
const found = apps ? await
|
|
359
|
+
const found = apps ? await repo.find(`/commits/${commit}/check-runs?per_page=100`, UNREADABLE) : void 0;
|
|
331
360
|
if (!found) {
|
|
332
361
|
apps = false;
|
|
333
362
|
return [];
|
|
@@ -342,11 +371,7 @@ function createGitHubForge(config, token) {
|
|
|
342
371
|
async access() {
|
|
343
372
|
const [user, repository] = await Promise.all([repo.call(`${api}/user`), repo.details()]);
|
|
344
373
|
return {
|
|
345
|
-
identity:
|
|
346
|
-
login: user.login,
|
|
347
|
-
...user.name ? { name: user.name } : {},
|
|
348
|
-
...user.avatar_url ? { avatar: user.avatar_url } : {}
|
|
349
|
-
},
|
|
374
|
+
identity: toIdentity(user.login, user.name, user.avatar_url),
|
|
350
375
|
writable: repository.permissions?.push === true,
|
|
351
376
|
branch: await repo.branch()
|
|
352
377
|
};
|
|
@@ -359,10 +384,7 @@ function createGitHubForge(config, token) {
|
|
|
359
384
|
if (!sha) return [];
|
|
360
385
|
const tree = await repo.call(`/git/trees/${sha}?recursive=1`);
|
|
361
386
|
if (tree.truncated) throw new Error(`[forgepress] GitHub lists too many files in ${directory} to read them in one request`);
|
|
362
|
-
return tree.tree
|
|
363
|
-
path: `${directory}/${item.path}`,
|
|
364
|
-
sha: item.sha
|
|
365
|
-
}));
|
|
387
|
+
return blobs(directory, tree.tree);
|
|
366
388
|
},
|
|
367
389
|
async read(sha) {
|
|
368
390
|
return (await repo.check(await repo.send(`/git/blobs/${sha}`, { headers: { accept: "application/vnd.github.raw+json" } }))).text();
|
|
@@ -449,20 +471,14 @@ function nextPage(link) {
|
|
|
449
471
|
function createGitLabForge(config, token) {
|
|
450
472
|
const { api } = describe(config);
|
|
451
473
|
const repo = createRepositoryApi(config, `${api}/projects/${encodeURIComponent(`${config.repository.owner}/${config.repository.name}`)}`, token);
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
if (response.status === 404) return void 0;
|
|
455
|
-
return await (await repo.check(response)).json();
|
|
474
|
+
function existing(path, ref) {
|
|
475
|
+
return repo.find(`/repository/files/${encodeURIComponent(path)}?ref=${encodeURIComponent(ref)}`);
|
|
456
476
|
}
|
|
457
477
|
return {
|
|
458
478
|
async access() {
|
|
459
479
|
const [user, project] = await Promise.all([repo.call(`${api}/user`), repo.details()]);
|
|
460
480
|
return {
|
|
461
|
-
identity:
|
|
462
|
-
login: user.username,
|
|
463
|
-
...user.name ? { name: user.name } : {},
|
|
464
|
-
...user.avatar_url ? { avatar: user.avatar_url } : {}
|
|
465
|
-
},
|
|
481
|
+
identity: toIdentity(user.username, user.name, user.avatar_url),
|
|
466
482
|
writable: writable(project),
|
|
467
483
|
branch: await repo.branch()
|
|
468
484
|
};
|
|
@@ -534,38 +550,40 @@ function createForge(config, token) {
|
|
|
534
550
|
return createGitHubForge(config, token);
|
|
535
551
|
}
|
|
536
552
|
const SKEW = 3e4;
|
|
537
|
-
function toTokens(
|
|
538
|
-
const access = payload.access_token;
|
|
539
|
-
if (typeof access !== "string" || !access) throw new Error("[forgepress] the forge returned no access token");
|
|
540
|
-
const refresh = payload.refresh_token;
|
|
541
|
-
const expires = payload.expires_in;
|
|
553
|
+
function toTokens(response, now = Date.now()) {
|
|
542
554
|
return {
|
|
543
|
-
access,
|
|
544
|
-
...
|
|
545
|
-
...
|
|
555
|
+
access: response.access_token,
|
|
556
|
+
...response.refresh_token ? { refresh: response.refresh_token } : {},
|
|
557
|
+
...response.expires_in === void 0 ? {} : { expires: now + response.expires_in * 1e3 }
|
|
546
558
|
};
|
|
547
559
|
}
|
|
548
560
|
function expired(tokens, now = Date.now()) {
|
|
549
561
|
return tokens.expires !== void 0 && tokens.expires - SKEW <= now;
|
|
550
562
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
return
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
}
|
|
563
|
+
function server(endpoints) {
|
|
564
|
+
return {
|
|
565
|
+
issuer: endpoints.issuer,
|
|
566
|
+
authorization_endpoint: endpoints.authorize,
|
|
567
|
+
token_endpoint: endpoints.token
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
function requestOptions(endpoints) {
|
|
571
|
+
return { [allowInsecureRequests]: new URL(endpoints.token).protocol === "http:" };
|
|
572
|
+
}
|
|
573
|
+
function refusal(error) {
|
|
574
|
+
if (error instanceof AuthorizationResponseError) return new Error(`[forgepress] the forge refused the sign-in: ${error.error_description || error.error}`, { cause: error });
|
|
575
|
+
if (error instanceof ResponseBodyError) return new Error(`[forgepress] the forge rejected the token request: ${error.error_description || error.error}`, { cause: error });
|
|
576
|
+
return error;
|
|
577
|
+
}
|
|
578
|
+
async function renew(endpoints, clientId, refresh) {
|
|
579
|
+
const as = server(endpoints);
|
|
580
|
+
const client = { client_id: clientId };
|
|
581
|
+
try {
|
|
582
|
+
const response = await refreshTokenGrantRequest(as, client, None(), refresh, requestOptions(endpoints));
|
|
583
|
+
return toTokens(await processRefreshTokenResponse(as, client, response));
|
|
584
|
+
} catch (error) {
|
|
585
|
+
throw refusal(error);
|
|
586
|
+
}
|
|
569
587
|
}
|
|
570
588
|
function storedTokens(value) {
|
|
571
589
|
if (typeof value === "string") return value ? { access: value } : void 0;
|
|
@@ -576,6 +594,22 @@ async function refreshed(tokens, config) {
|
|
|
576
594
|
if (!expired(tokens) || !tokens.refresh || !oauth || !config.clientId) return tokens;
|
|
577
595
|
return renew(oauth, config.clientId, tokens.refresh);
|
|
578
596
|
}
|
|
597
|
+
function createTokenGetter(config, read, write) {
|
|
598
|
+
let renewal;
|
|
599
|
+
async function renewed(tokens) {
|
|
600
|
+
const next = await refreshed(tokens, config);
|
|
601
|
+
if (next !== tokens) await write(next);
|
|
602
|
+
return next;
|
|
603
|
+
}
|
|
604
|
+
return async () => {
|
|
605
|
+
const tokens = await read();
|
|
606
|
+
if (!expired(tokens)) return tokens.access;
|
|
607
|
+
renewal ??= renewed(tokens).finally(() => {
|
|
608
|
+
renewal = void 0;
|
|
609
|
+
});
|
|
610
|
+
return (await renewal).access;
|
|
611
|
+
};
|
|
612
|
+
}
|
|
579
613
|
const CONCURRENCY = 8;
|
|
580
614
|
function limit(size) {
|
|
581
615
|
const waiting = [];
|
|
@@ -592,12 +626,12 @@ function limit(size) {
|
|
|
592
626
|
}
|
|
593
627
|
};
|
|
594
628
|
}
|
|
595
|
-
function createForgeSource(forge,
|
|
596
|
-
const
|
|
629
|
+
function createForgeSource(forge, target, cache) {
|
|
630
|
+
const { paths, mediaDir } = target;
|
|
597
631
|
const queue = limit(CONCURRENCY);
|
|
598
632
|
const texts = /* @__PURE__ */ new Map();
|
|
599
|
-
let snapshot;
|
|
600
633
|
let pinned;
|
|
634
|
+
let snapshot = once(load);
|
|
601
635
|
function client() {
|
|
602
636
|
const current = forge();
|
|
603
637
|
if (!current) throw new Error("[forgepress] sign in to read the content from the repository");
|
|
@@ -614,25 +648,20 @@ function createForgeSource(forge, paths, base, cache, mediaDir) {
|
|
|
614
648
|
if (forge()) cache?.writeListing(commit, directory, listed).catch(() => void 0);
|
|
615
649
|
return listed;
|
|
616
650
|
}
|
|
651
|
+
async function uploads(commit) {
|
|
652
|
+
if (mediaDir === void 0) return [];
|
|
653
|
+
return [...(await listing(commit, mediaDir)).keys()].map((path) => path.slice(mediaDir.length + 1)).filter(isMediaFile);
|
|
654
|
+
}
|
|
617
655
|
async function load() {
|
|
618
656
|
const commit = pinned ?? await client().head();
|
|
619
|
-
const listed = await listing(commit,
|
|
657
|
+
const listed = await listing(commit, paths.dir);
|
|
620
658
|
await restore(new Set(listed.values()));
|
|
621
659
|
return {
|
|
622
660
|
commit,
|
|
623
|
-
files: listed
|
|
661
|
+
files: listed,
|
|
662
|
+
media: once(() => uploads(commit))
|
|
624
663
|
};
|
|
625
664
|
}
|
|
626
|
-
function current() {
|
|
627
|
-
if (!snapshot) {
|
|
628
|
-
const loading = load();
|
|
629
|
-
snapshot = loading;
|
|
630
|
-
loading.catch(() => {
|
|
631
|
-
if (snapshot === loading) snapshot = void 0;
|
|
632
|
-
});
|
|
633
|
-
}
|
|
634
|
-
return snapshot;
|
|
635
|
-
}
|
|
636
665
|
function text(sha) {
|
|
637
666
|
let pending = texts.get(sha);
|
|
638
667
|
if (!pending) {
|
|
@@ -642,129 +671,57 @@ function createForgeSource(forge, paths, base, cache, mediaDir) {
|
|
|
642
671
|
}
|
|
643
672
|
return pending;
|
|
644
673
|
}
|
|
645
|
-
async function files() {
|
|
646
|
-
return (await current()).files;
|
|
647
|
-
}
|
|
648
674
|
async function hash(path) {
|
|
649
|
-
return (await
|
|
650
|
-
}
|
|
651
|
-
async function media() {
|
|
652
|
-
if (mediaDir === void 0) return [];
|
|
653
|
-
const found = await current();
|
|
654
|
-
const directory = `${at(mediaDir)}/`;
|
|
655
|
-
if (!found.media) {
|
|
656
|
-
const loading = listing(found.commit, at(mediaDir)).then((listed) => [...listed.keys()].map((path) => path.slice(directory.length)).filter(isMediaFile));
|
|
657
|
-
found.media = loading;
|
|
658
|
-
loading.catch(() => {
|
|
659
|
-
if (found.media === loading) delete found.media;
|
|
660
|
-
});
|
|
661
|
-
}
|
|
662
|
-
return found.media;
|
|
663
|
-
}
|
|
664
|
-
async function list(collection) {
|
|
665
|
-
const directory = `${at(paths.collection(collection))}/`;
|
|
666
|
-
const rows = [...await files()].filter(([path]) => path.startsWith(directory) && isEntryFile(path.slice(directory.length))).map(async ([path, sha]) => parseEntry(await text(sha), path));
|
|
667
|
-
return sortByCreation(await Promise.all(rows));
|
|
675
|
+
return (await snapshot()).files.get(path);
|
|
668
676
|
}
|
|
669
677
|
return {
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
entry: async (collection, id) =>
|
|
678
|
-
|
|
679
|
-
const sha = isEntryId(id) ? await hash(path) : void 0;
|
|
680
|
-
return sha === void 0 ? void 0 : parseEntry(await text(sha), path);
|
|
681
|
-
},
|
|
682
|
-
hashes: { entry: async (collection, id) => isEntryId(id) ? hash(at(paths.entry(collection, id))) : void 0 },
|
|
683
|
-
media,
|
|
678
|
+
...createFileSource({
|
|
679
|
+
list: async (directory) => [...(await snapshot()).files.keys()].filter((path) => path.startsWith(`${directory}/`)),
|
|
680
|
+
read: async (path) => {
|
|
681
|
+
const sha = await hash(path);
|
|
682
|
+
return sha === void 0 ? void 0 : text(sha);
|
|
683
|
+
}
|
|
684
|
+
}, paths),
|
|
685
|
+
hashes: { entry: async (collection, id) => isEntryId(id) ? hash(paths.entry(collection, id)) : void 0 },
|
|
686
|
+
media: async () => mediaDir === void 0 ? [] : (await snapshot()).media(),
|
|
684
687
|
read: text,
|
|
685
688
|
reset: (commit) => {
|
|
686
689
|
pinned = commit;
|
|
687
|
-
snapshot =
|
|
690
|
+
snapshot = once(load);
|
|
688
691
|
}
|
|
689
692
|
};
|
|
690
693
|
}
|
|
691
|
-
const
|
|
692
|
-
const
|
|
693
|
-
const
|
|
694
|
-
const FILES = "files";
|
|
695
|
-
const LISTINGS = "listings";
|
|
696
|
-
const STORES = {
|
|
697
|
-
[DATABASE]: [STORE],
|
|
698
|
-
[CACHE]: [FILES, LISTINGS]
|
|
699
|
-
};
|
|
700
|
-
function settle(request) {
|
|
701
|
-
return new Promise((resolve, reject) => {
|
|
702
|
-
request.onsuccess = () => resolve(request.result);
|
|
703
|
-
request.onerror = () => reject(request.error ?? /* @__PURE__ */ new Error("[forgepress] IndexedDB request failed"));
|
|
704
|
-
});
|
|
705
|
-
}
|
|
706
|
-
function open(name) {
|
|
707
|
-
return new Promise((resolve, reject) => {
|
|
708
|
-
const opening = indexedDB.open(name, 1);
|
|
709
|
-
opening.onupgradeneeded = () => {
|
|
710
|
-
for (const store of STORES[name] ?? []) if (!opening.result.objectStoreNames.contains(store)) opening.result.createObjectStore(store);
|
|
711
|
-
};
|
|
712
|
-
opening.onsuccess = () => resolve(opening.result);
|
|
713
|
-
opening.onerror = () => reject(opening.error ?? /* @__PURE__ */ new Error("[forgepress] IndexedDB is unavailable"));
|
|
714
|
-
opening.onblocked = () => reject(/* @__PURE__ */ new Error("[forgepress] IndexedDB is blocked by another open tab"));
|
|
715
|
-
});
|
|
716
|
-
}
|
|
717
|
-
const databases = /* @__PURE__ */ new Map();
|
|
718
|
-
async function transact(name, store, mode, run) {
|
|
719
|
-
let database = databases.get(name);
|
|
720
|
-
if (!database) {
|
|
721
|
-
database = open(name);
|
|
722
|
-
databases.set(name, database);
|
|
723
|
-
}
|
|
724
|
-
return await run((await database).transaction(store, mode).objectStore(store));
|
|
725
|
-
}
|
|
694
|
+
const CHANGES = createStore("forgepress", "changes");
|
|
695
|
+
const FILES = createStore("forgepress-cache", "files");
|
|
696
|
+
const LISTINGS = createStore("forgepress-listings", "listings");
|
|
726
697
|
function createIdbStore(key) {
|
|
727
698
|
return {
|
|
728
|
-
read: () =>
|
|
729
|
-
write:
|
|
730
|
-
|
|
731
|
-
},
|
|
732
|
-
clear: async () => {
|
|
733
|
-
await transact(DATABASE, STORE, "readwrite", (objects) => settle(objects.delete(key)));
|
|
734
|
-
}
|
|
699
|
+
read: () => get(key, CHANGES),
|
|
700
|
+
write: (value) => set(key, value, CHANGES),
|
|
701
|
+
clear: () => del(key, CHANGES)
|
|
735
702
|
};
|
|
736
703
|
}
|
|
737
704
|
function createIdbCache() {
|
|
738
705
|
return {
|
|
739
706
|
readListing: async (commit, directory) => {
|
|
740
|
-
const stored = await
|
|
707
|
+
const stored = await get(directory, LISTINGS);
|
|
741
708
|
return stored?.commit === commit ? stored.files : void 0;
|
|
742
709
|
},
|
|
743
|
-
writeListing:
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
const
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
values.onsuccess = () => {
|
|
755
|
-
const kept = /* @__PURE__ */ new Map();
|
|
756
|
-
keys.result.forEach((key, index) => {
|
|
757
|
-
if (typeof key === "string" && hashes.has(key)) kept.set(key, values.result[index]);
|
|
758
|
-
else objects.delete(key);
|
|
759
|
-
});
|
|
760
|
-
resolve(kept);
|
|
761
|
-
};
|
|
762
|
-
})),
|
|
763
|
-
writeFile: async (hash, text) => {
|
|
764
|
-
await transact(CACHE, FILES, "readwrite", (objects) => settle(objects.put(text, hash)));
|
|
710
|
+
writeListing: (commit, directory, files) => set(directory, {
|
|
711
|
+
commit,
|
|
712
|
+
files
|
|
713
|
+
}, LISTINGS),
|
|
714
|
+
keep: async (hashes) => {
|
|
715
|
+
const kept = /* @__PURE__ */ new Map();
|
|
716
|
+
const stale = [];
|
|
717
|
+
for (const [key, text] of await entries(FILES)) if (typeof key === "string" && hashes.has(key)) kept.set(key, text);
|
|
718
|
+
else stale.push(key);
|
|
719
|
+
if (stale.length > 0) await delMany(stale, FILES);
|
|
720
|
+
return kept;
|
|
765
721
|
},
|
|
722
|
+
writeFile: (hash, text) => set(hash, text, FILES),
|
|
766
723
|
clear: async () => {
|
|
767
|
-
await Promise.all([FILES
|
|
724
|
+
await Promise.all([clear(FILES), clear(LISTINGS)]);
|
|
768
725
|
}
|
|
769
726
|
};
|
|
770
727
|
}
|
|
@@ -794,29 +751,25 @@ function swap(value, urls) {
|
|
|
794
751
|
if (isRecord(value)) return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, swap(item, urls)]));
|
|
795
752
|
return value;
|
|
796
753
|
}
|
|
797
|
-
function uploads(changes) {
|
|
798
|
-
return [...Object.values(changes.uploads), ...publishedUploads(changes)];
|
|
799
|
-
}
|
|
800
754
|
function createPreviewReader(settings) {
|
|
801
755
|
const tokens = persist(TOKEN_KEY);
|
|
802
756
|
const pending = persist(CHANGES_KEY);
|
|
803
757
|
const previews = createPreviews();
|
|
804
758
|
const shown = /* @__PURE__ */ new Set();
|
|
805
759
|
let token;
|
|
806
|
-
async function
|
|
807
|
-
const
|
|
808
|
-
if (!
|
|
809
|
-
|
|
810
|
-
if (current !== saved) await tokens.write(current);
|
|
811
|
-
return current.access;
|
|
760
|
+
async function saved() {
|
|
761
|
+
const found = storedTokens(await tokens.read());
|
|
762
|
+
if (!found) throw new Error("[forgepress] sign in to the editor to preview unpublished content");
|
|
763
|
+
return found;
|
|
812
764
|
}
|
|
765
|
+
const access = createTokenGetter(settings.provider, saved, (next) => tokens.write(next));
|
|
813
766
|
const forge = createForge(settings.provider, () => {
|
|
814
767
|
token ??= access();
|
|
815
768
|
return token;
|
|
816
769
|
});
|
|
817
|
-
const source = createForgeSource(() => forge, createPaths(settings.contentPath), settings.provider.base, repositoryCache());
|
|
770
|
+
const source = createForgeSource(() => forge, { paths: repositoryPaths(createPaths(settings.contentPath), settings.provider.base) }, repositoryCache());
|
|
818
771
|
function local(changes) {
|
|
819
|
-
const assets =
|
|
772
|
+
const assets = localUploads(changes).map((upload) => previews.asset(upload, settings.mediaUrl));
|
|
820
773
|
const names = new Set(assets.map((asset) => asset.name));
|
|
821
774
|
for (const name of shown) if (!names.has(name)) previews.forget(name);
|
|
822
775
|
shown.clear();
|
|
@@ -826,12 +779,7 @@ function createPreviewReader(settings) {
|
|
|
826
779
|
return { build: async () => {
|
|
827
780
|
token = void 0;
|
|
828
781
|
source.reset();
|
|
829
|
-
const changes =
|
|
830
|
-
entries: {},
|
|
831
|
-
uploads: {},
|
|
832
|
-
removed: [],
|
|
833
|
-
...await pending.read()
|
|
834
|
-
};
|
|
782
|
+
const changes = await readChanges(pending);
|
|
835
783
|
const content = createContentChanges(source, async () => changes, async () => {
|
|
836
784
|
throw new Error("[forgepress] the preview only reads content");
|
|
837
785
|
});
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { isRecord, quote } from "./value.mjs";
|
|
1
|
+
import { isRecord, isTranslated, quote } from "./value.mjs";
|
|
2
2
|
function entryKey(collection, id) {
|
|
3
3
|
return `${collection}/${id}`;
|
|
4
4
|
}
|
|
5
|
+
function isEntryRef(value) {
|
|
6
|
+
return isRecord(value) && typeof value.collection === "string" && typeof value.id === "string";
|
|
7
|
+
}
|
|
5
8
|
function localized(value, path, translated) {
|
|
6
9
|
if (!translated) return [[path, value]];
|
|
7
10
|
return isRecord(value) ? Object.entries(value).map(([locale, item]) => [[...path, locale], item]) : [];
|
|
@@ -18,7 +21,7 @@ function references(field, value, path) {
|
|
|
18
21
|
collection: field.collection,
|
|
19
22
|
id
|
|
20
23
|
}] : []);
|
|
21
|
-
if (field.type === "dynamic") return value.flatMap((block, index) =>
|
|
24
|
+
if (field.type === "dynamic") return value.flatMap((block, index) => isEntryRef(block) && field.collections.includes(block.collection) ? [{
|
|
22
25
|
path: [...path, index],
|
|
23
26
|
collection: block.collection,
|
|
24
27
|
id: block.id
|
|
@@ -26,8 +29,8 @@ function references(field, value, path) {
|
|
|
26
29
|
return [];
|
|
27
30
|
}
|
|
28
31
|
function entryReferences(schema, collection, row) {
|
|
29
|
-
const
|
|
30
|
-
return Object.entries(schema.collections[collection]?.fields ?? {}).flatMap(([key, field]) => localized(row[key], [key],
|
|
32
|
+
const locales = schema.locales ?? [];
|
|
33
|
+
return Object.entries(schema.collections[collection]?.fields ?? {}).flatMap(([key, field]) => localized(row[key], [key], isTranslated(field, locales)).flatMap(([path, value]) => references(field, value, path)));
|
|
31
34
|
}
|
|
32
35
|
function validateReferences(schema, content) {
|
|
33
36
|
const issues = [];
|
|
@@ -50,4 +53,4 @@ function validateReferences(schema, content) {
|
|
|
50
53
|
}
|
|
51
54
|
return issues;
|
|
52
55
|
}
|
|
53
|
-
export { entryKey, validateReferences };
|
|
56
|
+
export { entryKey, isEntryRef, validateReferences };
|