mechanica 2.1.0-beta.5 → 2.1.0-beta.6
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/chunks/{assets-C7yHGJBU.js → assets-nujjbCx8.js} +1 -1
- package/dist/chunks/{node-adapter-GZZrfGSz.js → node-adapter-CSkDXYGO.js} +5 -36
- package/dist/chunks/{rich-text-codec-BXOKV94u.js → rich-text-codec-BSD35k2U.js} +69 -1
- package/dist/chunks/{site-Co04D3rB.js → site-DfazSr3_.js} +1 -1
- package/dist/cli.js +22 -4
- package/dist/export.js +65 -39
- package/dist/plugin.js +4 -4
- package/dist/server.js +4 -4
- package/dist/types/cli/export.d.ts +27 -1
- package/dist/types/cli/run-export.d.ts +9 -0
- package/dist/types/server/assets-store.d.ts +19 -0
- package/dist/types/server/index.d.ts +1 -1
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { G as isAssetName, J as readImageManifest, X as updateImageManifest } from "./rich-text-codec-BSD35k2U.js";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { chmod, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
4
4
|
import os from "node:os";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { n as configureSite, r as indexGeneratedPages } from "./site-
|
|
2
|
-
import { A as pageVersion, C as harvestPageImages, D as movePage, E as listPages, I as setPageDraft,
|
|
1
|
+
import { n as configureSite, r as indexGeneratedPages } from "./site-DfazSr3_.js";
|
|
2
|
+
import { A as pageVersion, C as harvestPageImages, D as movePage, E as listPages, I as setPageDraft, L as translationsOf, M as renamePage, N as savePage, Q as uploadResponse, R as contentFilesOf, T as listFolders, U as contentTypeOf, W as fsAssetStore, Y as storeUpload, _ as deletePage, a as folderOf, b as fillContentDefaults, c as mergeLocaleSiteData, f as readSiteData, g as createTranslation, h as createPage, j as readPage, l as mergeSiteData, m as PageExistsError, o as mergeFolderData, q as listUploads, s as mergeLocaleFolderData, u as readFolderData, v as deleteTranslation, w as imageNamesOf, x as fillImageMeta, y as duplicatePage } from "./rich-text-codec-BSD35k2U.js";
|
|
3
3
|
import fs, { readFileSync, readdirSync } from "node:fs";
|
|
4
|
-
import { basename,
|
|
4
|
+
import { basename, join, resolve, sep } from "node:path";
|
|
5
5
|
import { EDITOR_DIST_DIR, mergeTranslation, parseLocalePath, passDataToHTML, resolveQueryKey, serializeState } from "mechanica-shared";
|
|
6
6
|
import { parseComposedBlock, serializeComposedBlock } from "mechanica-shared/block-format";
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
@@ -362,25 +362,6 @@ var json = (data, status = 200) => new Response(JSON.stringify(data), {
|
|
|
362
362
|
status,
|
|
363
363
|
headers: { "content-type": "application/json" }
|
|
364
364
|
});
|
|
365
|
-
/** Content types of the files a site stores — browsers don't sniff SVG or fonts. */
|
|
366
|
-
var MIME = {
|
|
367
|
-
".png": "image/png",
|
|
368
|
-
".jpg": "image/jpeg",
|
|
369
|
-
".jpeg": "image/jpeg",
|
|
370
|
-
".gif": "image/gif",
|
|
371
|
-
".webp": "image/webp",
|
|
372
|
-
".avif": "image/avif",
|
|
373
|
-
".svg": "image/svg+xml",
|
|
374
|
-
".ico": "image/x-icon",
|
|
375
|
-
".mp4": "video/mp4",
|
|
376
|
-
".webm": "video/webm",
|
|
377
|
-
".mp3": "audio/mpeg",
|
|
378
|
-
".pdf": "application/pdf",
|
|
379
|
-
".json": "application/json",
|
|
380
|
-
".txt": "text/plain; charset=utf-8",
|
|
381
|
-
".woff": "font/woff",
|
|
382
|
-
".woff2": "font/woff2"
|
|
383
|
-
};
|
|
384
365
|
/**
|
|
385
366
|
* Stream a file from `dir`, containing the resolved path so an encoded `..`
|
|
386
367
|
* (or an absolute path) cannot escape it.
|
|
@@ -395,7 +376,7 @@ function serveFile(dir, relative, headers = {}) {
|
|
|
395
376
|
const file = resolve(dir, decoded);
|
|
396
377
|
if (file !== dir && !file.startsWith(dir + sep)) return new Response("Forbidden", { status: 403 });
|
|
397
378
|
if (!fs.existsSync(file) || !fs.statSync(file).isFile()) return new Response("Not found", { status: 404 });
|
|
398
|
-
const type =
|
|
379
|
+
const type = contentTypeOf(file);
|
|
399
380
|
return new Response(Readable.toWeb(fs.createReadStream(file)), { headers: {
|
|
400
381
|
...type ? { "content-type": type } : {},
|
|
401
382
|
...headers
|
|
@@ -422,19 +403,7 @@ function validateComposed(def) {
|
|
|
422
403
|
function createEditorService(mech, options = {}) {
|
|
423
404
|
const mechDir = typeof mech === "string" ? mech : null;
|
|
424
405
|
const assets = options.assets ?? (mechDir ? fsAssetStore(mechDir) : null);
|
|
425
|
-
const asset =
|
|
426
|
-
let name;
|
|
427
|
-
try {
|
|
428
|
-
name = decodeURIComponent(encoded);
|
|
429
|
-
} catch {
|
|
430
|
-
return new Response("Bad request", { status: 400 });
|
|
431
|
-
}
|
|
432
|
-
if (!assets || !isAssetName(name)) return null;
|
|
433
|
-
const body = await assets.open(name);
|
|
434
|
-
if (!body) return null;
|
|
435
|
-
const type = MIME[extname(name).toLowerCase()];
|
|
436
|
-
return new Response(body, { headers: type ? { "content-type": type } : {} });
|
|
437
|
-
};
|
|
406
|
+
const asset = (encoded) => uploadResponse(assets, encoded);
|
|
438
407
|
let configured = null;
|
|
439
408
|
let generatedIndex = null;
|
|
440
409
|
const currentSite = async () => {
|
|
@@ -273,6 +273,74 @@ async function listUploads(assets) {
|
|
|
273
273
|
src: assetUrl(name)
|
|
274
274
|
}));
|
|
275
275
|
}
|
|
276
|
+
/** Content types of the files a site stores — browsers don't sniff SVG or fonts. */
|
|
277
|
+
var MIME = {
|
|
278
|
+
".png": "image/png",
|
|
279
|
+
".jpg": "image/jpeg",
|
|
280
|
+
".jpeg": "image/jpeg",
|
|
281
|
+
".gif": "image/gif",
|
|
282
|
+
".webp": "image/webp",
|
|
283
|
+
".avif": "image/avif",
|
|
284
|
+
".svg": "image/svg+xml",
|
|
285
|
+
".ico": "image/x-icon",
|
|
286
|
+
".mp4": "video/mp4",
|
|
287
|
+
".webm": "video/webm",
|
|
288
|
+
".mp3": "audio/mpeg",
|
|
289
|
+
".pdf": "application/pdf",
|
|
290
|
+
".json": "application/json",
|
|
291
|
+
".txt": "text/plain; charset=utf-8",
|
|
292
|
+
".woff": "font/woff",
|
|
293
|
+
".woff2": "font/woff2"
|
|
294
|
+
};
|
|
295
|
+
/** The content type a stored file is served with, when its extension says. */
|
|
296
|
+
var contentTypeOf = (fileName) => MIME[parse(fileName).ext.toLowerCase()];
|
|
297
|
+
/**
|
|
298
|
+
* The HTTP answer for an upload asked for by its address — the part of
|
|
299
|
+
* `/media/<name>` after the prefix, still percent-encoded. null when it is not
|
|
300
|
+
* an upload of this store, so the caller can fall through; a 400 for a name
|
|
301
|
+
* that is not valid encoding. One function for every host that serves uploads:
|
|
302
|
+
* the dev server, an editing host, a site's live host.
|
|
303
|
+
*/
|
|
304
|
+
async function uploadResponse(assets, encodedName) {
|
|
305
|
+
let name;
|
|
306
|
+
try {
|
|
307
|
+
name = decodeURIComponent(encodedName);
|
|
308
|
+
} catch {
|
|
309
|
+
return new Response("Bad request", { status: 400 });
|
|
310
|
+
}
|
|
311
|
+
if (!assets || !isAssetName(name)) return null;
|
|
312
|
+
const body = await assets.open(name);
|
|
313
|
+
if (!body) return null;
|
|
314
|
+
const type = contentTypeOf(name);
|
|
315
|
+
return new Response(body, { headers: type ? { "content-type": type } : {} });
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* A store for rendering a site whose uploads the host keeps and serves itself.
|
|
319
|
+
* `hosted` is what the host knows about them — a name with its image info. The
|
|
320
|
+
* result knows those names (`list`, `info`) but cannot open them: an export
|
|
321
|
+
* over it fills in their dimensions and previews, copies no bytes, and reports
|
|
322
|
+
* them as the host's to serve (see `ExportResult.uploads`). Whatever the local
|
|
323
|
+
* store has is used as before.
|
|
324
|
+
*/
|
|
325
|
+
function withHostedAssets(local, hosted) {
|
|
326
|
+
const names = Object.keys(hosted).filter(isAssetName);
|
|
327
|
+
return {
|
|
328
|
+
write: (name, data) => local.write(name, data),
|
|
329
|
+
open: (name) => local.open(name),
|
|
330
|
+
saveInfo: (entries) => local.saveInfo(entries),
|
|
331
|
+
async list() {
|
|
332
|
+
return [.../* @__PURE__ */ new Set([...await local.list(), ...names])];
|
|
333
|
+
},
|
|
334
|
+
async info(wanted) {
|
|
335
|
+
const known = await local.info(wanted);
|
|
336
|
+
for (const name of wanted) {
|
|
337
|
+
const entry = hosted[name];
|
|
338
|
+
if (entry && !known[name]) known[name] = entry;
|
|
339
|
+
}
|
|
340
|
+
return known;
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
}
|
|
276
344
|
var MISS_TTL_MS = 3e4;
|
|
277
345
|
var MEMORY_LIMIT = 67108864;
|
|
278
346
|
/**
|
|
@@ -1203,4 +1271,4 @@ function findRichTextProps(props) {
|
|
|
1203
1271
|
return found;
|
|
1204
1272
|
}
|
|
1205
1273
|
//#endregion
|
|
1206
|
-
export {
|
|
1274
|
+
export { withHostedAssets as $, pageVersion as A, memoryContentFiles as B, harvestPageImages as C, movePage as D, listPages as E, setPageCodec as F, isAssetName as G, assetFileOf as H, setPageDraft as I, readImageManifest as J, isDerivedAsset as K, translationsOf as L, renamePage as M, savePage as N, pageOfFile as O, setPageBlocks as P, uploadResponse as Q, contentFilesOf as R, getPagePath as S, listFolders as T, contentTypeOf as U, UPLOADS_PREFIX$1 as V, fsAssetStore as W, updateImageManifest as X, storeUpload as Y, uploadName as Z, deletePage as _, folderOf as a, fillContentDefaults as b, mergeLocaleSiteData as c, readFoldersData as d, withRemoteAssets as et, readSiteData as f, createTranslation as g, createPage as h, toBlockMeta as i, isRasterImage as it, readPage as j, pageUrlOf as k, mergeSiteData as l, PageExistsError as m, richTextCodecFromBlocks as n, analyzeImageBuffer as nt, mergeFolderData as o, readSiteLocaleOverride as p, listUploads as q, composedBlockMeta as r, hasSharp as rt, mergeLocaleFolderData as s, buildRichTextCodec as t, wasRecentlyMutated as tt, readFolderData as u, deleteTranslation as v, imageNamesOf as w, fillImageMeta as x, duplicatePage as y, fsContentFiles as z };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { F as setPageCodec, P as setPageBlocks, i as toBlockMeta, n as richTextCodecFromBlocks, r as composedBlockMeta } from "./rich-text-codec-
|
|
1
|
+
import { F as setPageCodec, P as setPageBlocks, i as toBlockMeta, n as richTextCodecFromBlocks, r as composedBlockMeta } from "./rich-text-codec-BSD35k2U.js";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { SITE_MANIFEST_FILE, areFieldSchemasRegistered, localePath, registerFieldSchemas } from "mechanica-shared";
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as buildSiteManifest } from "./chunks/site-
|
|
2
|
-
import {
|
|
3
|
-
import { a as git, c as PlatformError, d as request, f as requireToken, l as REMOTE_NAME, m as saveToken, o as gitAuthEnv, p as hostOrigin, r as runAssetsPull, s as gitOk, t as pushUploads, u as parseRemoteUrl } from "./chunks/assets-
|
|
1
|
+
import { t as buildSiteManifest } from "./chunks/site-DfazSr3_.js";
|
|
2
|
+
import { J as readImageManifest, K as isDerivedAsset, W as fsAssetStore, X as updateImageManifest, et as withRemoteAssets, it as isRasterImage, nt as analyzeImageBuffer, rt as hasSharp } from "./chunks/rich-text-codec-BSD35k2U.js";
|
|
3
|
+
import { a as git, c as PlatformError, d as request, f as requireToken, i as uploadsIgnored, l as REMOTE_NAME, m as saveToken, n as remoteAssetsFor, o as gitAuthEnv, p as hostOrigin, r as runAssetsPull, s as gitOk, t as pushUploads, u as parseRemoteUrl } from "./chunks/assets-nujjbCx8.js";
|
|
4
4
|
import { exportBuilt } from "./export.js";
|
|
5
5
|
import fs, { readFileSync } from "node:fs";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
@@ -128,13 +128,31 @@ async function writeSiteManifest(distDir) {
|
|
|
128
128
|
//#endregion
|
|
129
129
|
//#region src/cli/run-export.ts
|
|
130
130
|
/**
|
|
131
|
+
* The uploads a local export reads: the project's `.mech/assets`, and — for a
|
|
132
|
+
* project linked to a hosted platform — the uploads kept there, fetched as the
|
|
133
|
+
* pages turn out to need them (and kept in `.mech/assets` when git ignores it).
|
|
134
|
+
* Only local files are *listed*: the uploads the platform holds that no page
|
|
135
|
+
* here refers to are not this export's to warn about.
|
|
136
|
+
*/
|
|
137
|
+
function exportAssets(cwd) {
|
|
138
|
+
const local = fsAssetStore(join(cwd, ".mech"));
|
|
139
|
+
return {
|
|
140
|
+
...withRemoteAssets(local, remoteAssetsFor(cwd), { cache: () => uploadsIgnored(cwd) }),
|
|
141
|
+
list: () => local.list()
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
131
145
|
* `mechanica export`: build the project, then statically render every page
|
|
132
146
|
* into `export/`. Kept apart from `export.ts` so that module never imports
|
|
133
147
|
* Vite — `mechanica/export` runs where only a built bundle exists.
|
|
134
148
|
*/
|
|
135
149
|
async function runExport(options = {}) {
|
|
136
150
|
await runBuild();
|
|
137
|
-
const
|
|
151
|
+
const cwd = process.cwd();
|
|
152
|
+
const { pages } = await exportBuilt(cwd, {
|
|
153
|
+
assets: exportAssets(cwd),
|
|
154
|
+
...options
|
|
155
|
+
});
|
|
138
156
|
for (const path of pages) console.info("Generated", path);
|
|
139
157
|
console.info(`Exported ${pages.length} page(s) → ${options.outDir ?? "export"}/`);
|
|
140
158
|
}
|
package/dist/export.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as withHostedAssets, E as listPages, F as setPageCodec, H as assetFileOf, K as isDerivedAsset, S as getPagePath, V as UPLOADS_PREFIX$1, W as fsAssetStore, d as readFoldersData, f as readSiteData, i as toBlockMeta, nt as analyzeImageBuffer, p as readSiteLocaleOverride, r as composedBlockMeta, rt as hasSharp, t as buildRichTextCodec, u as readFolderData } from "./chunks/rich-text-codec-BSD35k2U.js";
|
|
2
2
|
import { n as blockAssetLinks, t as BLOCKS_MANIFEST_FILE } from "./chunks/page-assets-DsJXmoDs.js";
|
|
3
3
|
import { dirname, join, parse, relative, resolve } from "node:path";
|
|
4
4
|
import { EDITOR_DIST_DIR, SITE_MANIFEST_FILE, applySeoTags, auditPageHtml, buildRobotsTxt, buildSitemap, findUnknownBlocks, generateProject, isPaginatedQuery, localePath, mergeTranslation, migrateContent, paginationVariantPath, registerFieldSchemas, resolveQueryKey, templateBlockIds, validateLinks, walkSchema, walkTree } from "mechanica-shared";
|
|
@@ -218,45 +218,52 @@ function fillImageValue(value, entry) {
|
|
|
218
218
|
if (entry.width && !value.width) value.width = entry.width;
|
|
219
219
|
if (entry.height && !value.height) value.height = entry.height;
|
|
220
220
|
}
|
|
221
|
+
/** Everything an upload's bytes come as, in one buffer. */
|
|
222
|
+
var bytesOf = async (body) => body instanceof Uint8Array ? Buffer.from(body) : Buffer.from(await new Response(body).arrayBuffer());
|
|
221
223
|
/**
|
|
222
224
|
* Fill missing image metadata — LQIP `previewSrc` + intrinsic dimensions —
|
|
223
225
|
* into the state being exported (page files are never touched). Sources, in
|
|
224
|
-
* order: the
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
* Without sharp, leftover gaps get a one-time hint.
|
|
226
|
+
* order: what the asset store knows (written by uploads, editor saves and
|
|
227
|
+
* `mechanica images`), then the optional `sharp` dependency for anything still
|
|
228
|
+
* missing that the store can open — those computed entries are saved back to
|
|
229
|
+
* it. Without sharp, leftover gaps get a one-time hint.
|
|
228
230
|
*/
|
|
229
|
-
async function backfillImageMeta(pages, blocksMap,
|
|
230
|
-
const
|
|
231
|
-
const incomplete = /* @__PURE__ */ new Map();
|
|
231
|
+
async function backfillImageMeta(pages, blocksMap, assets, warn) {
|
|
232
|
+
const values = /* @__PURE__ */ new Map();
|
|
232
233
|
for (const page of pages) walkTree(page.content, (block) => {
|
|
233
234
|
const meta = blocksMap.get(block.blockId);
|
|
234
235
|
if (!meta) return;
|
|
235
236
|
walkSchema(block.data, meta.props, (value, schema) => {
|
|
236
237
|
if (schema.format !== "image") return;
|
|
237
238
|
const file = assetFileOf(value?.src);
|
|
238
|
-
if (
|
|
239
|
-
const entry = manifest[file];
|
|
240
|
-
if (entry) fillImageValue(value, entry);
|
|
241
|
-
if (!value.previewSrc || value.previewSrc === value.src || !value.width || !value.height) incomplete.set(file, [...incomplete.get(file) ?? [], value]);
|
|
239
|
+
if (file) values.set(file, [...values.get(file) ?? [], value]);
|
|
242
240
|
});
|
|
243
241
|
});
|
|
242
|
+
if (!values.size) return;
|
|
243
|
+
const known = await assets.info([...values.keys()]);
|
|
244
|
+
const incomplete = /* @__PURE__ */ new Map();
|
|
245
|
+
for (const [file, list] of values) {
|
|
246
|
+
const entry = known[file];
|
|
247
|
+
const lacking = list.filter((value) => {
|
|
248
|
+
if (entry) fillImageValue(value, entry);
|
|
249
|
+
return !value.previewSrc || value.previewSrc === value.src || !value.width || !value.height;
|
|
250
|
+
});
|
|
251
|
+
if (lacking.length) incomplete.set(file, lacking);
|
|
252
|
+
}
|
|
244
253
|
if (!incomplete.size) return;
|
|
245
254
|
if (!await hasSharp()) {
|
|
246
255
|
warn(`[mechanica] ${incomplete.size} image(s) lack a preview or dimensions — install the optional "sharp" dependency or run \`mechanica images\` to generate them`);
|
|
247
256
|
return;
|
|
248
257
|
}
|
|
249
258
|
const computed = {};
|
|
250
|
-
for (const [file,
|
|
251
|
-
const
|
|
252
|
-
const info =
|
|
259
|
+
for (const [file, list] of incomplete) {
|
|
260
|
+
const body = await assets.open(file).catch(() => null);
|
|
261
|
+
const info = body ? await analyzeImageBuffer(await bytesOf(body)) : null;
|
|
253
262
|
if (!info) continue;
|
|
254
263
|
computed[file] = info;
|
|
255
|
-
for (const value of
|
|
264
|
+
for (const value of list) fillImageValue(value, info);
|
|
256
265
|
}
|
|
257
|
-
|
|
258
|
-
updateImageManifest(mechDir, computed);
|
|
259
|
-
} catch {}
|
|
266
|
+
await assets.saveInfo(computed).catch(() => {});
|
|
260
267
|
}
|
|
261
268
|
/**
|
|
262
269
|
* Statically render every page of an already-built project into `export/`,
|
|
@@ -277,6 +284,7 @@ async function backfillImageMeta(pages, blocksMap, mechDir, warn) {
|
|
|
277
284
|
*/
|
|
278
285
|
async function exportProject(cwd, ssr, options = {}) {
|
|
279
286
|
const warn = options.onWarn ?? ((message) => console.warn(message));
|
|
287
|
+
const assets = options.assets ?? fsAssetStore(join(cwd, ".mech"));
|
|
280
288
|
const index = await readFile(join(cwd, "dist/index.html"), "utf-8");
|
|
281
289
|
const siteUrl = options.siteUrl ?? ssr.site?.url;
|
|
282
290
|
const siteName = options.siteName ?? ssr.site?.name;
|
|
@@ -374,7 +382,7 @@ async function exportProject(cwd, ssr, options = {}) {
|
|
|
374
382
|
}
|
|
375
383
|
if (!pages.some((page) => page.logicalPath === "/404")) warn("[mechanica] No /404 page — the static host will show its own error page. Create a page at /404 (it exports as 404.html and is noindexed automatically).");
|
|
376
384
|
for (const issue of validateLinks(pages, blocksMap)) warn(`[mechanica] Broken link on ${issue.page}: ${issue.url} has no matching page`);
|
|
377
|
-
await backfillImageMeta(pages, blocksMap,
|
|
385
|
+
await backfillImageMeta(pages, blocksMap, assets, warn);
|
|
378
386
|
const exportDir = resolve(cwd, options.outDir ?? "export");
|
|
379
387
|
await mkdir(exportDir, { recursive: true });
|
|
380
388
|
for (const entry of await readdir(exportDir)) await rm(join(exportDir, entry), {
|
|
@@ -435,8 +443,9 @@ async function exportProject(cwd, ssr, options = {}) {
|
|
|
435
443
|
onFile,
|
|
436
444
|
pageLinks
|
|
437
445
|
};
|
|
446
|
+
const stored = await assets.list();
|
|
438
447
|
const unclaimed = /* @__PURE__ */ new Map();
|
|
439
|
-
for (const name of
|
|
448
|
+
for (const name of stored) {
|
|
440
449
|
const spellings = [
|
|
441
450
|
name,
|
|
442
451
|
encodeURI(name),
|
|
@@ -550,26 +559,41 @@ async function exportProject(cwd, ssr, options = {}) {
|
|
|
550
559
|
}));
|
|
551
560
|
recordSitemap(path, source, source.page.pagination.page);
|
|
552
561
|
}
|
|
553
|
-
await copyUploads(
|
|
562
|
+
const uploads = await copyUploads(assets, new Set(stored), join(exportDir, MEDIA_DIR), referenced, warn);
|
|
554
563
|
if (written.includes("/404")) await copyFile(join(exportDir, "404/index.html"), join(exportDir, "404.html"));
|
|
555
564
|
if (siteUrl) {
|
|
556
565
|
await writeFile(join(exportDir, "sitemap.xml"), buildSitemap(siteUrl, sitemapEntries));
|
|
557
566
|
const robotsPath = join(exportDir, "robots.txt");
|
|
558
567
|
if (!await access(robotsPath).then(() => true, () => false)) await writeFile(robotsPath, buildRobotsTxt(siteUrl));
|
|
559
568
|
}
|
|
560
|
-
return
|
|
569
|
+
return {
|
|
570
|
+
pages: written,
|
|
571
|
+
uploads
|
|
572
|
+
};
|
|
561
573
|
}
|
|
562
|
-
/**
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
574
|
+
/**
|
|
575
|
+
* Put the referenced uploads into the export: copy the ones the store can open,
|
|
576
|
+
* leave to the host the ones it only knows about, warn about the ones it does
|
|
577
|
+
* not know — and about uploads no page refers to.
|
|
578
|
+
*/
|
|
579
|
+
async function copyUploads(assets, stored, outDir, referenced, warn) {
|
|
580
|
+
const uploads = {
|
|
581
|
+
copied: [],
|
|
582
|
+
hosted: []
|
|
583
|
+
};
|
|
584
|
+
for (const name of referenced) {
|
|
585
|
+
const body = await assets.open(name).catch(() => null);
|
|
586
|
+
if (body) {
|
|
587
|
+
const target = join(outDir, name);
|
|
588
|
+
await mkdir(dirname(target), { recursive: true });
|
|
589
|
+
await writeFile(target, await bytesOf(body));
|
|
590
|
+
uploads.copied.push(name);
|
|
591
|
+
} else if (stored.has(name)) uploads.hosted.push(name);
|
|
592
|
+
else warn(`[mechanica] Missing upload: ${name} is referenced by a page but is not among the site's uploads`);
|
|
570
593
|
}
|
|
571
|
-
const orphans =
|
|
572
|
-
if (orphans.length) warn(`[mechanica] ${orphans.length} unused upload(s)
|
|
594
|
+
const orphans = [...stored].filter((name) => /\.\w+$/.test(name) && !referenced.has(name) && !isDerivedAsset(name));
|
|
595
|
+
if (orphans.length) warn(`[mechanica] ${orphans.length} unused upload(s) (not exported): ${orphans.join(", ")}`);
|
|
596
|
+
return uploads;
|
|
573
597
|
}
|
|
574
598
|
/**
|
|
575
599
|
* Statically render an already-built project: load `<cwd>/dist/ssr.js` and run
|
|
@@ -585,13 +609,15 @@ async function exportBuilt(cwd, options = {}) {
|
|
|
585
609
|
warnings.push(message);
|
|
586
610
|
(options.onWarn ?? console.warn)(message);
|
|
587
611
|
};
|
|
612
|
+
const { pages, uploads } = await exportProject(cwd, await import(pathToFileURL(join(cwd, "dist/ssr.js")).href), {
|
|
613
|
+
...options,
|
|
614
|
+
onWarn
|
|
615
|
+
});
|
|
588
616
|
return {
|
|
589
|
-
pages
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
}),
|
|
593
|
-
warnings
|
|
617
|
+
pages,
|
|
618
|
+
warnings,
|
|
619
|
+
uploads
|
|
594
620
|
};
|
|
595
621
|
}
|
|
596
622
|
//#endregion
|
|
597
|
-
export { exportBuilt, exportProject };
|
|
623
|
+
export { exportBuilt, exportProject, fsAssetStore, withHostedAssets };
|
package/dist/plugin.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { a as collectGeneratedPages, n as configureSite, r as indexGeneratedPages, t as buildSiteManifest } from "./chunks/site-
|
|
2
|
-
import { a as renderEditablePage, c as fillStateImages, d as loadComposedDefinitions, n as toNodeMiddleware, o as buildGeneratedState, r as createEditorService, s as buildPageState, t as toAssetMiddleware, u as collectComposed } from "./chunks/node-adapter-
|
|
3
|
-
import {
|
|
4
|
-
import { i as uploadsIgnored, n as remoteAssetsFor } from "./chunks/assets-
|
|
1
|
+
import { a as collectGeneratedPages, n as configureSite, r as indexGeneratedPages, t as buildSiteManifest } from "./chunks/site-DfazSr3_.js";
|
|
2
|
+
import { a as renderEditablePage, c as fillStateImages, d as loadComposedDefinitions, n as toNodeMiddleware, o as buildGeneratedState, r as createEditorService, s as buildPageState, t as toAssetMiddleware, u as collectComposed } from "./chunks/node-adapter-CSkDXYGO.js";
|
|
3
|
+
import { W as fsAssetStore, et as withRemoteAssets, k as pageUrlOf, tt as wasRecentlyMutated } from "./chunks/rich-text-codec-BSD35k2U.js";
|
|
4
|
+
import { i as uploadsIgnored, n as remoteAssetsFor } from "./chunks/assets-nujjbCx8.js";
|
|
5
5
|
import { t as BLOCKS_MANIFEST_FILE } from "./chunks/page-assets-DsJXmoDs.js";
|
|
6
6
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
7
7
|
import { join, relative } from "node:path";
|
package/dist/server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { i as readSiteManifest, n as configureSite, t as buildSiteManifest } from "./chunks/site-
|
|
2
|
-
import { a as renderEditablePage, i as readEditorTemplate, n as toNodeMiddleware, r as createEditorService, t as toAssetMiddleware } from "./chunks/node-adapter-
|
|
3
|
-
import { B as memoryContentFiles, G as isDerivedAsset, O as pageOfFile, U as fsAssetStore,
|
|
1
|
+
import { i as readSiteManifest, n as configureSite, t as buildSiteManifest } from "./chunks/site-DfazSr3_.js";
|
|
2
|
+
import { a as renderEditablePage, i as readEditorTemplate, n as toNodeMiddleware, r as createEditorService, t as toAssetMiddleware } from "./chunks/node-adapter-CSkDXYGO.js";
|
|
3
|
+
import { $ as withHostedAssets, B as memoryContentFiles, G as isAssetName, K as isDerivedAsset, O as pageOfFile, Q as uploadResponse, U as contentTypeOf, W as fsAssetStore, Z as uploadName, et as withRemoteAssets, z as fsContentFiles } from "./chunks/rich-text-codec-BSD35k2U.js";
|
|
4
4
|
import { EDITOR_DIST_DIR, SITE_MANIFEST_FILE, UPLOADS_PREFIX, uploadNameOf } from "mechanica-shared";
|
|
5
|
-
export { EDITOR_DIST_DIR, SITE_MANIFEST_FILE, UPLOADS_PREFIX, buildSiteManifest, configureSite, createEditorService, fsAssetStore, fsContentFiles, isDerivedAsset, memoryContentFiles, pageOfFile, readEditorTemplate, readSiteManifest, renderEditablePage, toAssetMiddleware, toNodeMiddleware, uploadName, uploadNameOf, withRemoteAssets };
|
|
5
|
+
export { EDITOR_DIST_DIR, SITE_MANIFEST_FILE, UPLOADS_PREFIX, buildSiteManifest, configureSite, contentTypeOf, createEditorService, fsAssetStore, fsContentFiles, isAssetName, isDerivedAsset, memoryContentFiles, pageOfFile, readEditorTemplate, readSiteManifest, renderEditablePage, toAssetMiddleware, toNodeMiddleware, uploadName, uploadNameOf, uploadResponse, withHostedAssets, withRemoteAssets };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ComposedBlockDefinition, type LocalesConfig, type RenderResult, type VirtualPage } from 'mechanica-shared';
|
|
2
2
|
import { type BlockComponent } from '../editor/lib/block-meta';
|
|
3
|
+
import { fsAssetStore, withHostedAssets, type AssetStore, type ImageManifest } from '../server/assets-store';
|
|
3
4
|
/** An already-built SSR bundle (`dist/ssr.js`) exposes these. */
|
|
4
5
|
export interface SsrBundle {
|
|
5
6
|
/**
|
|
@@ -69,6 +70,24 @@ export interface ExportOptions {
|
|
|
69
70
|
fetchJson?: (options: {
|
|
70
71
|
url: string;
|
|
71
72
|
} & Record<string, unknown>) => Promise<unknown>;
|
|
73
|
+
/**
|
|
74
|
+
* Where the site's uploads are: their bytes and their image info. Defaults to
|
|
75
|
+
* the project's `.mech/assets` + `.mech/images.json`. The export reads uploads
|
|
76
|
+
* only through this — a linked project passes a store that also fetches the
|
|
77
|
+
* ones kept by its platform, and a hosted render one that knows uploads it
|
|
78
|
+
* cannot open (`withHostedAssets`): those are not copied, the host serves them.
|
|
79
|
+
*/
|
|
80
|
+
assets?: AssetStore;
|
|
81
|
+
}
|
|
82
|
+
/** What became of the uploads the pages refer to. */
|
|
83
|
+
export interface ExportedUploads {
|
|
84
|
+
/** Copied into the export's `media/` directory. */
|
|
85
|
+
copied: string[];
|
|
86
|
+
/**
|
|
87
|
+
* Known to the store but not readable through it — left for the host to
|
|
88
|
+
* serve at `/media/<name>`. Empty for an export over a plain directory.
|
|
89
|
+
*/
|
|
90
|
+
hosted: string[];
|
|
72
91
|
}
|
|
73
92
|
/**
|
|
74
93
|
* Statically render every page of an already-built project into `export/`,
|
|
@@ -87,13 +106,18 @@ export interface ExportOptions {
|
|
|
87
106
|
* absolute social-image URLs, pagination prev/next, JSON-LD) and emits
|
|
88
107
|
* `sitemap.xml` + `robots.txt`.
|
|
89
108
|
*/
|
|
90
|
-
export declare function exportProject(cwd: string, ssr: SsrBundle, options?: ExportOptions): Promise<
|
|
109
|
+
export declare function exportProject(cwd: string, ssr: SsrBundle, options?: ExportOptions): Promise<{
|
|
110
|
+
pages: string[];
|
|
111
|
+
uploads: ExportedUploads;
|
|
112
|
+
}>;
|
|
91
113
|
/** What {@link exportBuilt} produced. */
|
|
92
114
|
export interface ExportResult {
|
|
93
115
|
/** The generated page paths, in render order. */
|
|
94
116
|
pages: string[];
|
|
95
117
|
/** Everything the export warned about (broken links, missing uploads, SEO lint). */
|
|
96
118
|
warnings: string[];
|
|
119
|
+
/** The uploads the pages refer to: which were copied, which the host is to serve. */
|
|
120
|
+
uploads: ExportedUploads;
|
|
97
121
|
}
|
|
98
122
|
/**
|
|
99
123
|
* Statically render an already-built project: load `<cwd>/dist/ssr.js` and run
|
|
@@ -103,3 +127,5 @@ export interface ExportResult {
|
|
|
103
127
|
* Warnings are collected into the result and still forwarded to `onWarn`.
|
|
104
128
|
*/
|
|
105
129
|
export declare function exportBuilt(cwd: string, options?: ExportOptions): Promise<ExportResult>;
|
|
130
|
+
export { fsAssetStore, withHostedAssets };
|
|
131
|
+
export type { AssetStore, ImageManifest };
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
+
import { type AssetStore } from '../server/assets-store';
|
|
1
2
|
import { type ExportOptions } from './export';
|
|
3
|
+
/**
|
|
4
|
+
* The uploads a local export reads: the project's `.mech/assets`, and — for a
|
|
5
|
+
* project linked to a hosted platform — the uploads kept there, fetched as the
|
|
6
|
+
* pages turn out to need them (and kept in `.mech/assets` when git ignores it).
|
|
7
|
+
* Only local files are *listed*: the uploads the platform holds that no page
|
|
8
|
+
* here refers to are not this export's to warn about.
|
|
9
|
+
*/
|
|
10
|
+
export declare function exportAssets(cwd: string): AssetStore;
|
|
2
11
|
/**
|
|
3
12
|
* `mechanica export`: build the project, then statically render every page
|
|
4
13
|
* into `export/`. Kept apart from `export.ts` so that module never imports
|
|
@@ -84,6 +84,25 @@ export declare function listUploads(assets: AssetStore): Promise<{
|
|
|
84
84
|
name: string;
|
|
85
85
|
src: string;
|
|
86
86
|
}[]>;
|
|
87
|
+
/** The content type a stored file is served with, when its extension says. */
|
|
88
|
+
export declare const contentTypeOf: (fileName: string) => string | undefined;
|
|
89
|
+
/**
|
|
90
|
+
* The HTTP answer for an upload asked for by its address — the part of
|
|
91
|
+
* `/media/<name>` after the prefix, still percent-encoded. null when it is not
|
|
92
|
+
* an upload of this store, so the caller can fall through; a 400 for a name
|
|
93
|
+
* that is not valid encoding. One function for every host that serves uploads:
|
|
94
|
+
* the dev server, an editing host, a site's live host.
|
|
95
|
+
*/
|
|
96
|
+
export declare function uploadResponse(assets: AssetStore | null | undefined, encodedName: string): Promise<Response | null>;
|
|
97
|
+
/**
|
|
98
|
+
* A store for rendering a site whose uploads the host keeps and serves itself.
|
|
99
|
+
* `hosted` is what the host knows about them — a name with its image info. The
|
|
100
|
+
* result knows those names (`list`, `info`) but cannot open them: an export
|
|
101
|
+
* over it fills in their dimensions and previews, copies no bytes, and reports
|
|
102
|
+
* them as the host's to serve (see `ExportResult.uploads`). Whatever the local
|
|
103
|
+
* store has is used as before.
|
|
104
|
+
*/
|
|
105
|
+
export declare function withHostedAssets(local: AssetStore, hosted: ImageManifest): AssetStore;
|
|
87
106
|
/**
|
|
88
107
|
* Where a project's uploads also live — a hosted platform the project is linked
|
|
89
108
|
* to. Uploads made in its online editor are not in the project's directory
|
|
@@ -8,7 +8,7 @@ export { createEditorService, type EditorService, type EditorServiceOptions, typ
|
|
|
8
8
|
export { toNodeMiddleware, toAssetMiddleware, type NodeMiddleware } from './node-adapter';
|
|
9
9
|
export { fsContentFiles, memoryContentFiles, type ContentFiles, type ContentChanges, type MemoryContentFiles, type Mech, } from './content-files';
|
|
10
10
|
export { pageOfFile } from './pages-store';
|
|
11
|
-
export { fsAssetStore, withRemoteAssets, uploadName, isDerivedAsset, type AssetStore, type RemoteAssets, type RemoteAssetsOptions, type ImageManifest, type ImageManifestEntry, } from './assets-store';
|
|
11
|
+
export { fsAssetStore, withRemoteAssets, withHostedAssets, uploadResponse, contentTypeOf, isAssetName, uploadName, isDerivedAsset, type AssetStore, type RemoteAssets, type RemoteAssetsOptions, type ImageManifest, type ImageManifestEntry, } from './assets-store';
|
|
12
12
|
export { buildSiteManifest, readSiteManifest, configureSite, type SiteManifestInput } from './site';
|
|
13
13
|
export { renderEditablePage, readEditorTemplate, type EditablePageOptions } from './editor-page';
|
|
14
14
|
export { SITE_MANIFEST_FILE, EDITOR_DIST_DIR, UPLOADS_PREFIX, uploadNameOf, type SiteManifest, type EditorHostConfig, } from 'mechanica-shared';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mechanica",
|
|
3
|
-
"version": "2.1.0-beta.
|
|
3
|
+
"version": "2.1.0-beta.6",
|
|
4
4
|
"description": "Build Vue 3 websites with a visual block editor",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"prepublishOnly": "bun ../../scripts/ensure-bun-publish.js && bun run build"
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"mechanica-shared": "2.1.0-beta.
|
|
69
|
+
"mechanica-shared": "2.1.0-beta.6",
|
|
70
70
|
"@vue/compiler-sfc": "^3.5.38",
|
|
71
71
|
"compact-json-schema": "^0.1.5",
|
|
72
72
|
"estree-walker": "^3.0.3",
|