@stelstone/server 0.26.1 → 0.26.3
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/package.json +1 -1
- package/src/adapters/github-content.mjs +18 -1
- package/src/server.mjs +43 -9
- package/src/version.mjs +1 -1
package/package.json
CHANGED
|
@@ -73,6 +73,8 @@ export function createGitHubContent({ token, owner, repo, branch, draftBranch, p
|
|
|
73
73
|
// Lazily create the draft branch off the published branch. Checked once per
|
|
74
74
|
// process; a 404 on the ref is the "first ever use" case, not an error.
|
|
75
75
|
let draftBranchReady = !draftMode;
|
|
76
|
+
let indexesStaleAfterSync = false;
|
|
77
|
+
const refreshedAfterSync = new Set();
|
|
76
78
|
async function ensureDraftBranch() {
|
|
77
79
|
if (draftBranchReady) return;
|
|
78
80
|
const ref = await apiGet(`/git/ref/heads/${draftBranch}`);
|
|
@@ -88,7 +90,13 @@ export function createGitHubContent({ token, owner, repo, branch, draftBranch, p
|
|
|
88
90
|
// on main from code and the admin listed an empty collection. Keep the
|
|
89
91
|
// draft current by merging the published branch in once per process.
|
|
90
92
|
try {
|
|
91
|
-
await apiPost(`/merges`, { base: draftBranch, head: branch });
|
|
93
|
+
const merged = await apiPost(`/merges`, { base: draftBranch, head: branch });
|
|
94
|
+
// A real merge means files changed under us — the per-collection
|
|
95
|
+
// _index.json manifests may now be stale (the empty-index variant of
|
|
96
|
+
// this bit stelstone.com: an index bootstrapped before content
|
|
97
|
+
// existed served an empty list forever). Mark them suspect; the
|
|
98
|
+
// lazy strategy rebuilds on next list.
|
|
99
|
+
if (merged?.sha) indexesStaleAfterSync = true;
|
|
92
100
|
} catch (err) {
|
|
93
101
|
if (err?.upstreamStatus === 409) {
|
|
94
102
|
// Same record edited on both branches: the draft (the editor's
|
|
@@ -226,8 +234,16 @@ export function createGitHubContent({ token, owner, repo, branch, draftBranch, p
|
|
|
226
234
|
}
|
|
227
235
|
|
|
228
236
|
// 2. strategy "index": try the pre-built manifest first (1 subrequest).
|
|
237
|
+
// After a draft-branch sync merged changes in, an existing manifest is
|
|
238
|
+
// suspect: skip it once per collection so the lazy path rebuilds it
|
|
239
|
+
// from the merged tree. ("build" sites rebuild via the CLI instead.)
|
|
240
|
+
const skipIndex =
|
|
241
|
+
indexesStaleAfterSync &&
|
|
242
|
+
listConfig.rebuild === "lazy" &&
|
|
243
|
+
!refreshedAfterSync.has(safeCollection);
|
|
229
244
|
const idxPath = indexFilePath(safeCollection);
|
|
230
245
|
try {
|
|
246
|
+
if (skipIndex) throw new Error("index suspect after sync");
|
|
231
247
|
const data = await apiGet(`/contents/${idxPath}?ref=${workBranch}`);
|
|
232
248
|
if (data && !Array.isArray(data)) {
|
|
233
249
|
shaCache.set(idxPath, data.sha);
|
|
@@ -262,6 +278,7 @@ export function createGitHubContent({ token, owner, repo, branch, draftBranch, p
|
|
|
262
278
|
// Persist for all future calls.
|
|
263
279
|
try {
|
|
264
280
|
await writeIndex(safeCollection, { entries: pages });
|
|
281
|
+
refreshedAfterSync.add(safeCollection);
|
|
265
282
|
} catch (err) {
|
|
266
283
|
console.warn(`[listPages] Could not write ${listConfig.indexFile}:`, err?.message);
|
|
267
284
|
}
|
package/src/server.mjs
CHANGED
|
@@ -155,20 +155,44 @@ export function startScheduler(content, intervalMs = 60_000) {
|
|
|
155
155
|
return () => clearInterval(timer);
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
const PREVIEW_THEME_PATH = "/admin/preview-theme.css";
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The per-site canvas stylesheet.
|
|
162
|
+
*
|
|
163
|
+
* Always answered, even when the site configured none: the admin SPA links it
|
|
164
|
+
* unconditionally, and every mount ends in an SPA fallback that would hand the
|
|
165
|
+
* browser index.html labelled as CSS.
|
|
166
|
+
*/
|
|
167
|
+
function previewThemeResponse(previewThemeCss) {
|
|
168
|
+
if (previewThemeCss && fs.existsSync(previewThemeCss)) {
|
|
169
|
+
return fileResponse(path.resolve(previewThemeCss));
|
|
170
|
+
}
|
|
171
|
+
return new Response("/* no preview-theme.css configured */", {
|
|
172
|
+
headers: { "Content-Type": "text/css" },
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Connect middleware that answers the preview stylesheet and passes everything
|
|
178
|
+
* else along. Used by the Vite mount, whose own SPA fallback would otherwise
|
|
179
|
+
* swallow the path.
|
|
180
|
+
*/
|
|
181
|
+
export function createPreviewThemeMiddleware(previewThemeCss) {
|
|
182
|
+
return toNodeMiddleware(async (request) =>
|
|
183
|
+
new URL(request.url).pathname === PREVIEW_THEME_PATH
|
|
184
|
+
? previewThemeResponse(previewThemeCss)
|
|
185
|
+
: null,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
158
189
|
/** Serve the prebuilt admin SPA, plus the optional per-site preview stylesheet. */
|
|
159
190
|
function createAdminUiHandler({ dir, previewThemeCss }) {
|
|
160
191
|
const files = createStaticHandler({ root: dir, mount: "/admin", spaFallback: true });
|
|
161
192
|
return function serveAdminUi(request) {
|
|
162
193
|
const { pathname } = new URL(request.url);
|
|
163
194
|
// Answer before the static handler so the <link> never 404s.
|
|
164
|
-
if (pathname ===
|
|
165
|
-
if (previewThemeCss && fs.existsSync(previewThemeCss)) {
|
|
166
|
-
return fileResponse(path.resolve(previewThemeCss));
|
|
167
|
-
}
|
|
168
|
-
return new Response("/* no preview-theme.css configured */", {
|
|
169
|
-
headers: { "Content-Type": "text/css" },
|
|
170
|
-
});
|
|
171
|
-
}
|
|
195
|
+
if (pathname === PREVIEW_THEME_PATH) return previewThemeResponse(previewThemeCss);
|
|
172
196
|
return files(request);
|
|
173
197
|
};
|
|
174
198
|
}
|
|
@@ -258,8 +282,18 @@ export async function resolveAdminUi(adminUi) {
|
|
|
258
282
|
appType: "spa",
|
|
259
283
|
});
|
|
260
284
|
console.log("Admin UI: Vite dev middleware (HMR enabled)");
|
|
285
|
+
|
|
286
|
+
// Vite owns /admin here, and its SPA fallback answers every unmatched path
|
|
287
|
+
// with index.html — so the preview stylesheet arrived as HTML and the
|
|
288
|
+
// browser dropped it, leaving the canvas unstyled whenever the admin UI
|
|
289
|
+
// ran from source. Only the static mount used to answer this path; now
|
|
290
|
+
// both do, and `previewThemeCss` means the same thing in dev and in prod.
|
|
291
|
+
const viteMiddleware = mountPrefix("/admin", vite.middlewares);
|
|
292
|
+
const previewTheme = createPreviewThemeMiddleware(resolved.previewThemeCss);
|
|
293
|
+
|
|
261
294
|
return {
|
|
262
|
-
nodeMiddleware:
|
|
295
|
+
nodeMiddleware: (req, res, next) =>
|
|
296
|
+
previewTheme(req, res, () => viteMiddleware(req, res, next)),
|
|
263
297
|
previewThemeCss: resolved.previewThemeCss,
|
|
264
298
|
};
|
|
265
299
|
}
|
package/src/version.mjs
CHANGED