@dogsbay/format-dogsbay-md 0.2.0-beta.130 → 0.2.0-beta.132
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/browser.d.ts +1 -0
- package/dist/browser.js +1 -0
- package/dist/cli.d.ts +1 -18
- package/dist/cli.js +16 -111
- package/dist/project.d.ts +60 -0
- package/dist/project.js +109 -0
- package/package.json +5 -4
package/dist/browser.d.ts
CHANGED
|
@@ -22,3 +22,4 @@ export { maskMinjaInCode, unmaskMinjaInCode, maskAllMinjaOpeners, isMinjaProcess
|
|
|
22
22
|
export { extractHeadings, slugifyHeadingText, type HeadingInfo } from "./headings.js";
|
|
23
23
|
export { splitFrontmatter, type Frontmatter } from "./frontmatter.js";
|
|
24
24
|
export { TemplatePages, TemplatePageError, declaresParams, ATTRIBUTE_BLOCK_OPEN, type TemplatePagesOptions } from "./template-pages.js";
|
|
25
|
+
export { dogsbayMdPageFiles, mountCopies, carriedFrontmatter, MEDIA_EXTS, type AssetMount } from "./project.js";
|
package/dist/browser.js
CHANGED
|
@@ -22,3 +22,4 @@ export { maskMinjaInCode, unmaskMinjaInCode, maskAllMinjaOpeners, isMinjaProcess
|
|
|
22
22
|
export { extractHeadings, slugifyHeadingText } from "./headings.js";
|
|
23
23
|
export { splitFrontmatter } from "./frontmatter.js";
|
|
24
24
|
export { TemplatePages, TemplatePageError, declaresParams, ATTRIBUTE_BLOCK_OPEN } from "./template-pages.js";
|
|
25
|
+
export { dogsbayMdPageFiles, mountCopies, carriedFrontmatter, MEDIA_EXTS } from "./project.js";
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,20 +1,3 @@
|
|
|
1
1
|
import type { FormatPlugin } from "@dogsbay/types";
|
|
2
|
-
|
|
3
|
-
* The source page's front matter, carried into the Dogsbay MD it becomes.
|
|
4
|
-
* It used to be DROPPED (only title and redirect were written), so a
|
|
5
|
-
* converted corpus lost `description`, `tags` and custom fields, and a
|
|
6
|
-
* query listing (`::child-links{match=…}`) over it found nothing.
|
|
7
|
-
*
|
|
8
|
-
* Keys Dogsbay MD gives meaning to keep their value only when the value
|
|
9
|
-
* MEANS that here; otherwise the key moves under `imported:` — kept, never
|
|
10
|
-
* misread. Starlight's `template: splash` (a page template) must not turn a
|
|
11
|
-
* page into a Jinja template page. A Dogsbay MD source round-trips unchanged:
|
|
12
|
-
* its values are valid by construction.
|
|
13
|
-
*
|
|
14
|
-
* `title`, `redirect` and the template keys are written by the caller.
|
|
15
|
-
* `layout` is free-form (a theme layout name) and passes as it is.
|
|
16
|
-
* `imported:`, not `source:` — Cloudflare pages carry their own `source:`
|
|
17
|
-
* (an origin URL, 91 pages), which the moved keys would have overwritten.
|
|
18
|
-
*/
|
|
19
|
-
export declare function carriedFrontmatter(fm: Record<string, unknown> | undefined): Record<string, unknown>;
|
|
2
|
+
export { carriedFrontmatter } from "./project.js";
|
|
20
3
|
export declare const plugin: FormatPlugin;
|
package/dist/cli.js
CHANGED
|
@@ -4,15 +4,17 @@
|
|
|
4
4
|
* Reads a directory of .md files, parses each via dogsbayMdToTree, builds nav
|
|
5
5
|
* from directory structure + frontmatter, returns ExportPage[] + NavItem[].
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { join, relative, basename, dirname } from "node:path";
|
|
9
9
|
import { extractHeadings } from "./headings.js";
|
|
10
10
|
import { parseMeta } from "@dogsbay/types";
|
|
11
11
|
import { dogsbayMdToTree } from "./parse.js";
|
|
12
|
-
import { treeToDogsbayMd } from "./serialize.js";
|
|
13
12
|
import { buildNavFromDirectory } from "./nav.js";
|
|
14
13
|
import { renderDiagrams } from "./renderers/diagrams.js";
|
|
15
14
|
import { DOGSBAY_MD_CAPABILITIES } from "./capabilities.js";
|
|
15
|
+
import { dogsbayMdPageFiles, mountCopies } from "./project.js";
|
|
16
|
+
import { readFolder } from "@dogsbay/corpus/folder";
|
|
17
|
+
export { carriedFrontmatter } from "./project.js";
|
|
16
18
|
/**
|
|
17
19
|
* Detect whether a directory contains a Dogsbay MD source tree.
|
|
18
20
|
*
|
|
@@ -221,79 +223,6 @@ function prettifyName(slug) {
|
|
|
221
223
|
* is the include path. Mirrors migrate-asciidoc's fragment convention so the
|
|
222
224
|
* build-time Minja preprocessor can `{% include %}` them.
|
|
223
225
|
*/
|
|
224
|
-
/** Media files worth carrying into `_assets` from a media-only mount. */
|
|
225
|
-
const MEDIA_EXTS = new Set([
|
|
226
|
-
".png", ".jpg", ".jpeg", ".gif", ".webp", ".avif",
|
|
227
|
-
".svg", ".ico", ".pdf", ".mp4", ".webm",
|
|
228
|
-
]);
|
|
229
|
-
/** Recursively copy only media files, preserving relative structure. */
|
|
230
|
-
function copyMediaTree(srcDir, destDir) {
|
|
231
|
-
for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
|
|
232
|
-
const full = join(srcDir, entry.name);
|
|
233
|
-
if (entry.isDirectory()) {
|
|
234
|
-
copyMediaTree(full, join(destDir, entry.name));
|
|
235
|
-
continue;
|
|
236
|
-
}
|
|
237
|
-
const dot = entry.name.lastIndexOf(".");
|
|
238
|
-
if (dot === -1 || !MEDIA_EXTS.has(entry.name.slice(dot).toLowerCase()))
|
|
239
|
-
continue;
|
|
240
|
-
mkdirSync(destDir, { recursive: true });
|
|
241
|
-
cpSync(full, join(destDir, entry.name));
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* The source page's front matter, carried into the Dogsbay MD it becomes.
|
|
246
|
-
* It used to be DROPPED (only title and redirect were written), so a
|
|
247
|
-
* converted corpus lost `description`, `tags` and custom fields, and a
|
|
248
|
-
* query listing (`::child-links{match=…}`) over it found nothing.
|
|
249
|
-
*
|
|
250
|
-
* Keys Dogsbay MD gives meaning to keep their value only when the value
|
|
251
|
-
* MEANS that here; otherwise the key moves under `imported:` — kept, never
|
|
252
|
-
* misread. Starlight's `template: splash` (a page template) must not turn a
|
|
253
|
-
* page into a Jinja template page. A Dogsbay MD source round-trips unchanged:
|
|
254
|
-
* its values are valid by construction.
|
|
255
|
-
*
|
|
256
|
-
* `title`, `redirect` and the template keys are written by the caller.
|
|
257
|
-
* `layout` is free-form (a theme layout name) and passes as it is.
|
|
258
|
-
* `imported:`, not `source:` — Cloudflare pages carry their own `source:`
|
|
259
|
-
* (an origin URL, 91 pages), which the moved keys would have overwritten.
|
|
260
|
-
*/
|
|
261
|
-
export function carriedFrontmatter(fm) {
|
|
262
|
-
if (!fm)
|
|
263
|
-
return {};
|
|
264
|
-
const out = {};
|
|
265
|
-
const source = {};
|
|
266
|
-
const valid = {
|
|
267
|
-
// `false` is the documented per-page opt-out of a source's `templates: jinja`.
|
|
268
|
-
template: (v) => v === "jinja" || v === false,
|
|
269
|
-
weight: (v) => typeof v === "number" && Number.isFinite(v),
|
|
270
|
-
order: (v) => typeof v === "number" && Number.isFinite(v),
|
|
271
|
-
draft: (v) => typeof v === "boolean",
|
|
272
|
-
status: (v) => typeof v === "string" && ["draft", "preview", "stable", "deprecated"].includes(v),
|
|
273
|
-
// `params:` declares a template PARTIAL's parameters: only with `template: jinja`.
|
|
274
|
-
params: (v) => Array.isArray(v) && fm.template === "jinja",
|
|
275
|
-
};
|
|
276
|
-
for (const [key, value] of Object.entries(fm)) {
|
|
277
|
-
if (key === "title" || key === "redirect" || value === undefined)
|
|
278
|
-
continue;
|
|
279
|
-
// A source that already has `imported:` keeps it: merged if a mapping,
|
|
280
|
-
// else nested under its own name.
|
|
281
|
-
if (key === "imported") {
|
|
282
|
-
if (value && typeof value === "object" && !Array.isArray(value))
|
|
283
|
-
Object.assign(source, value);
|
|
284
|
-
else
|
|
285
|
-
source.imported = value;
|
|
286
|
-
continue;
|
|
287
|
-
}
|
|
288
|
-
if (valid[key] && !valid[key](value))
|
|
289
|
-
source[key] = value;
|
|
290
|
-
else
|
|
291
|
-
out[key] = value;
|
|
292
|
-
}
|
|
293
|
-
if (Object.keys(source).length)
|
|
294
|
-
out.imported = source;
|
|
295
|
-
return out;
|
|
296
|
-
}
|
|
297
226
|
async function exportDogsbayMd(pages, nav, output, opts) {
|
|
298
227
|
mkdirSync(output, { recursive: true });
|
|
299
228
|
// Copy mounted asset dirs (e.g. Docusaurus `static/`) into `content/_assets/`,
|
|
@@ -303,51 +232,27 @@ async function exportDogsbayMd(pages, nav, output, opts) {
|
|
|
303
232
|
// `content/_assets/**` through to `/_assets/**`. A mount may target an
|
|
304
233
|
// `_assets` subdir (`into`) and restrict itself to media files (`mediaOnly`) —
|
|
305
234
|
// used for docs-co-located images where the mount dir IS the docs tree.
|
|
235
|
+
// The copy is `mountCopies` over the mount read as a corpus — the list the browser import
|
|
236
|
+
// copies from its map, so both hosts copy the same files. A mount is someone else's repo:
|
|
237
|
+
// a symlink out of it (`public/leak.png -> ~/.ssh/id_rsa`) was copied as a link to that
|
|
238
|
+
// file, and `node_modules` / `.git` inside it were copied whole (2026-10-21).
|
|
306
239
|
if (Array.isArray(opts.assetMounts)) {
|
|
307
240
|
for (const mount of opts.assetMounts) {
|
|
308
241
|
if (!existsSync(mount.dir))
|
|
309
242
|
continue;
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
if (mount.mediaOnly) {
|
|
316
|
-
copyMediaTree(mount.dir, dest);
|
|
317
|
-
continue;
|
|
243
|
+
const folder = readFolder(mount.dir);
|
|
244
|
+
for (const { from, to } of mountCopies(folder.paths, [{ ...mount, dir: "" }])) {
|
|
245
|
+
const dst = join(output, to);
|
|
246
|
+
mkdirSync(dirname(dst), { recursive: true });
|
|
247
|
+
copyFileSync(folder.absolute(from), dst);
|
|
318
248
|
}
|
|
319
|
-
const skip = new Set(mount.exclude ?? []);
|
|
320
|
-
cpSync(mount.dir, dest, {
|
|
321
|
-
recursive: true,
|
|
322
|
-
...(skip.size === 0 ? {} : {
|
|
323
|
-
// Relative to the mount, so an exclusion cannot be accidentally
|
|
324
|
-
// satisfied by a directory name somewhere above it on disk.
|
|
325
|
-
filter: (src) => {
|
|
326
|
-
const rel = relative(mount.dir, src);
|
|
327
|
-
return rel === "" || !rel.split(/[\\/]/).some((seg) => skip.has(seg));
|
|
328
|
-
},
|
|
329
|
-
}),
|
|
330
|
-
});
|
|
331
249
|
}
|
|
332
250
|
}
|
|
333
|
-
for (const
|
|
334
|
-
const
|
|
335
|
-
const outPath = join(output, `${slug}.md`);
|
|
251
|
+
for (const [path, text] of dogsbayMdPageFiles(pages, nav)) {
|
|
252
|
+
const outPath = join(output, path);
|
|
336
253
|
mkdirSync(dirname(outPath), { recursive: true });
|
|
337
|
-
|
|
338
|
-
// build treats it as a partial and checks every include against it.
|
|
339
|
-
const templateFm = page.template ? { template: "jinja", ...(page.templateParams ? { params: page.templateParams } : {}) } : {};
|
|
340
|
-
// A title-less template page still needs `template: jinja`, or the build
|
|
341
|
-
// renders it in content mode and shows the directives as text.
|
|
342
|
-
const frontmatter = page.fragment || !page.title
|
|
343
|
-
? (page.template ? templateFm : undefined)
|
|
344
|
-
: { title: page.title, ...carriedFrontmatter(page.frontmatter), ...(page.redirect ? { redirect: page.redirect } : {}), ...templateFm };
|
|
345
|
-
const md = treeToDogsbayMd(page.tree, frontmatter ? { frontmatter } : undefined);
|
|
346
|
-
writeFileSync(outPath, md.endsWith("\n") ? md : md + "\n", "utf-8");
|
|
254
|
+
writeFileSync(outPath, text, "utf-8");
|
|
347
255
|
}
|
|
348
|
-
// Nav as nav.json at the content root (the dogsbay-md importer + runtime
|
|
349
|
-
// loader both prefer nav.json). Fragments never appear here.
|
|
350
|
-
writeFileSync(join(output, "nav.json"), JSON.stringify(nav, null, 2) + "\n", "utf-8");
|
|
351
256
|
}
|
|
352
257
|
export const plugin = {
|
|
353
258
|
name: "dogsbay-md",
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A converted corpus as a Dogsbay MD project, as data: what `convert --to dogsbay-md` writes
|
|
3
|
+
* (each page as `<slug>.md`, the nav as `nav.json`, the importer's asset mounts under
|
|
4
|
+
* `_assets/`), without the file system. The exporter writes these files; the playground's
|
|
5
|
+
* browser imports (`@dogsbay/format-vitepress/migrate`) return them, so a migration in the
|
|
6
|
+
* browser is the one `convert` does.
|
|
7
|
+
*/
|
|
8
|
+
import type { ExportPage, NavItem } from "@dogsbay/types";
|
|
9
|
+
/** Media files worth carrying into `_assets` from a media-only mount. */
|
|
10
|
+
export declare const MEDIA_EXTS: Set<string>;
|
|
11
|
+
/**
|
|
12
|
+
* The source page's front matter, carried into the Dogsbay MD it becomes.
|
|
13
|
+
* It used to be DROPPED (only title and redirect were written), so a
|
|
14
|
+
* converted corpus lost `description`, `tags` and custom fields, and a
|
|
15
|
+
* query listing (`::child-links{match=…}`) over it found nothing.
|
|
16
|
+
*
|
|
17
|
+
* Keys Dogsbay MD gives meaning to keep their value only when the value
|
|
18
|
+
* MEANS that here; otherwise the key moves under `imported:` — kept, never
|
|
19
|
+
* misread. Starlight's `template: splash` (a page template) must not turn a
|
|
20
|
+
* page into a Jinja template page. A Dogsbay MD source round-trips unchanged:
|
|
21
|
+
* its values are valid by construction.
|
|
22
|
+
*
|
|
23
|
+
* `title`, `redirect` and the template keys are written by the caller.
|
|
24
|
+
* `layout` is free-form (a theme layout name) and passes as it is.
|
|
25
|
+
* `imported:`, not `source:` — Cloudflare pages carry their own `source:`
|
|
26
|
+
* (an origin URL, 91 pages), which the moved keys would have overwritten.
|
|
27
|
+
*/
|
|
28
|
+
export declare function carriedFrontmatter(fm: Record<string, unknown> | undefined): Record<string, unknown>;
|
|
29
|
+
/** Each page as `<slug>.md` and the nav as `nav.json`, relative to the content root. */
|
|
30
|
+
export declare function dogsbayMdPageFiles(pages: ExportPage[], nav: NavItem[]): Map<string, string>;
|
|
31
|
+
/** An importer's asset directory, as `ImportResult.assetMounts` declares it, relative to the corpus. */
|
|
32
|
+
export interface AssetMount {
|
|
33
|
+
dir: string;
|
|
34
|
+
/** A subdirectory of the root to land in (`pages` → `_assets/pages/`). */
|
|
35
|
+
into?: string;
|
|
36
|
+
/** Media files only (`MEDIA_EXTS`): the mount is the docs tree itself. */
|
|
37
|
+
mediaOnly?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Directory names to leave behind, matched at ANY depth in the mount.
|
|
40
|
+
*
|
|
41
|
+
* `mediaOnly` is the wrong tool when a mount is mostly wanted: it also drops scripts, YAML
|
|
42
|
+
* and text a corpus may legitimately carry. This skips named subtrees instead — e.g.
|
|
43
|
+
* Calico's `licenses/`, ~35 MB of third-party attribution HTML for versions the corpus does
|
|
44
|
+
* not even import, which nothing references. Matched at any depth on purpose: the first
|
|
45
|
+
* version compared only the FIRST path segment and silently did nothing, because the real
|
|
46
|
+
* tree is `_assets/calico/3.32/licenses/…`.
|
|
47
|
+
*/
|
|
48
|
+
exclude?: string[];
|
|
49
|
+
/** Public root the mount lands in; `_assets` unless the source used another. */
|
|
50
|
+
root?: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The files the exporter copies for `mounts`, from a corpus's `paths`: corpus path → path in
|
|
54
|
+
* the content root. The same rules as the exporter's copy on disk: the whole mount minus
|
|
55
|
+
* excluded names, or its media only.
|
|
56
|
+
*/
|
|
57
|
+
export declare function mountCopies(paths: string[], mounts: AssetMount[]): {
|
|
58
|
+
from: string;
|
|
59
|
+
to: string;
|
|
60
|
+
}[];
|
package/dist/project.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { treeToDogsbayMd } from "./serialize.js";
|
|
2
|
+
/** Media files worth carrying into `_assets` from a media-only mount. */
|
|
3
|
+
export const MEDIA_EXTS = new Set([
|
|
4
|
+
".png", ".jpg", ".jpeg", ".gif", ".webp", ".avif",
|
|
5
|
+
".svg", ".ico", ".pdf", ".mp4", ".webm",
|
|
6
|
+
]);
|
|
7
|
+
/**
|
|
8
|
+
* The source page's front matter, carried into the Dogsbay MD it becomes.
|
|
9
|
+
* It used to be DROPPED (only title and redirect were written), so a
|
|
10
|
+
* converted corpus lost `description`, `tags` and custom fields, and a
|
|
11
|
+
* query listing (`::child-links{match=…}`) over it found nothing.
|
|
12
|
+
*
|
|
13
|
+
* Keys Dogsbay MD gives meaning to keep their value only when the value
|
|
14
|
+
* MEANS that here; otherwise the key moves under `imported:` — kept, never
|
|
15
|
+
* misread. Starlight's `template: splash` (a page template) must not turn a
|
|
16
|
+
* page into a Jinja template page. A Dogsbay MD source round-trips unchanged:
|
|
17
|
+
* its values are valid by construction.
|
|
18
|
+
*
|
|
19
|
+
* `title`, `redirect` and the template keys are written by the caller.
|
|
20
|
+
* `layout` is free-form (a theme layout name) and passes as it is.
|
|
21
|
+
* `imported:`, not `source:` — Cloudflare pages carry their own `source:`
|
|
22
|
+
* (an origin URL, 91 pages), which the moved keys would have overwritten.
|
|
23
|
+
*/
|
|
24
|
+
export function carriedFrontmatter(fm) {
|
|
25
|
+
if (!fm)
|
|
26
|
+
return {};
|
|
27
|
+
const out = {};
|
|
28
|
+
const source = {};
|
|
29
|
+
const valid = {
|
|
30
|
+
// `false` is the documented per-page opt-out of a source's `templates: jinja`.
|
|
31
|
+
template: (v) => v === "jinja" || v === false,
|
|
32
|
+
weight: (v) => typeof v === "number" && Number.isFinite(v),
|
|
33
|
+
order: (v) => typeof v === "number" && Number.isFinite(v),
|
|
34
|
+
draft: (v) => typeof v === "boolean",
|
|
35
|
+
status: (v) => typeof v === "string" && ["draft", "preview", "stable", "deprecated"].includes(v),
|
|
36
|
+
// `params:` declares a template PARTIAL's parameters: only with `template: jinja`.
|
|
37
|
+
params: (v) => Array.isArray(v) && fm.template === "jinja",
|
|
38
|
+
};
|
|
39
|
+
for (const [key, value] of Object.entries(fm)) {
|
|
40
|
+
if (key === "title" || key === "redirect" || value === undefined)
|
|
41
|
+
continue;
|
|
42
|
+
// A source that already has `imported:` keeps it: merged if a mapping,
|
|
43
|
+
// else nested under its own name.
|
|
44
|
+
if (key === "imported") {
|
|
45
|
+
if (value && typeof value === "object" && !Array.isArray(value))
|
|
46
|
+
Object.assign(source, value);
|
|
47
|
+
else
|
|
48
|
+
source.imported = value;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (valid[key] && !valid[key](value))
|
|
52
|
+
source[key] = value;
|
|
53
|
+
else
|
|
54
|
+
out[key] = value;
|
|
55
|
+
}
|
|
56
|
+
if (Object.keys(source).length)
|
|
57
|
+
out.imported = source;
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
/** Each page as `<slug>.md` and the nav as `nav.json`, relative to the content root. */
|
|
61
|
+
export function dogsbayMdPageFiles(pages, nav) {
|
|
62
|
+
const files = new Map();
|
|
63
|
+
for (const page of pages) {
|
|
64
|
+
const slug = page.slug || "index";
|
|
65
|
+
// A template partial keeps its `params:` and the template flag, so the
|
|
66
|
+
// build treats it as a partial and checks every include against it.
|
|
67
|
+
const templateFm = page.template ? { template: "jinja", ...(page.templateParams ? { params: page.templateParams } : {}) } : {};
|
|
68
|
+
// A title-less template page still needs `template: jinja`, or the build
|
|
69
|
+
// renders it in content mode and shows the directives as text.
|
|
70
|
+
const frontmatter = page.fragment || !page.title
|
|
71
|
+
? (page.template ? templateFm : undefined)
|
|
72
|
+
: { title: page.title, ...carriedFrontmatter(page.frontmatter), ...(page.redirect ? { redirect: page.redirect } : {}), ...templateFm };
|
|
73
|
+
const md = treeToDogsbayMd(page.tree, frontmatter ? { frontmatter } : undefined);
|
|
74
|
+
files.set(`${slug}.md`, md.endsWith("\n") ? md : md + "\n");
|
|
75
|
+
}
|
|
76
|
+
// Nav as nav.json at the content root (the dogsbay-md importer + runtime
|
|
77
|
+
// loader both prefer nav.json). Fragments never appear here.
|
|
78
|
+
files.set("nav.json", JSON.stringify(nav, null, 2) + "\n");
|
|
79
|
+
return files;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The files the exporter copies for `mounts`, from a corpus's `paths`: corpus path → path in
|
|
83
|
+
* the content root. The same rules as the exporter's copy on disk: the whole mount minus
|
|
84
|
+
* excluded names, or its media only.
|
|
85
|
+
*/
|
|
86
|
+
export function mountCopies(paths, mounts) {
|
|
87
|
+
const out = [];
|
|
88
|
+
for (const mount of mounts) {
|
|
89
|
+
const dir = mount.dir.replace(/\/+$/, "");
|
|
90
|
+
const dest = [mount.root ?? "_assets", mount.into].filter(Boolean).join("/");
|
|
91
|
+
const skip = new Set(mount.exclude ?? []);
|
|
92
|
+
for (const path of paths) {
|
|
93
|
+
if (dir && !path.startsWith(dir + "/"))
|
|
94
|
+
continue;
|
|
95
|
+
const rel = dir ? path.slice(dir.length + 1) : path;
|
|
96
|
+
const segs = rel.split("/");
|
|
97
|
+
if (mount.mediaOnly) {
|
|
98
|
+
const name = segs[segs.length - 1];
|
|
99
|
+
const dot = name.lastIndexOf(".");
|
|
100
|
+
if (dot === -1 || !MEDIA_EXTS.has(name.slice(dot).toLowerCase()))
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
else if (segs.some((s) => skip.has(s)))
|
|
104
|
+
continue;
|
|
105
|
+
out.push({ from: path, to: `${dest}/${rel}` });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dogsbay/format-dogsbay-md",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.132",
|
|
4
4
|
"description": "Dogsbay Markdown dialect — canonical format for TreeNode[] with directives, YAML bodies, and intelligent fallbacks",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -40,9 +40,10 @@
|
|
|
40
40
|
"markdown-it-footnote": "^4.0.0",
|
|
41
41
|
"markdown-it-front-matter": "^0.2.4",
|
|
42
42
|
"pele": "1.6.0",
|
|
43
|
-
"@dogsbay/
|
|
44
|
-
"@dogsbay/
|
|
45
|
-
"@dogsbay/
|
|
43
|
+
"@dogsbay/corpus": "0.2.0-beta.132",
|
|
44
|
+
"@dogsbay/minja": "0.2.0-beta.132",
|
|
45
|
+
"@dogsbay/serialize-core": "0.2.0-beta.132",
|
|
46
|
+
"@dogsbay/types": "0.2.0-beta.132"
|
|
46
47
|
},
|
|
47
48
|
"license": "MIT",
|
|
48
49
|
"repository": {
|