@dogsbay/serialize-core 0.2.0-beta.98

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/text.js ADDED
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Format-agnostic text utilities.
3
+ *
4
+ * These are byte-identical (or strictly stronger) versions of helpers
5
+ * currently duplicated across serializers: `indent` exists three times
6
+ * (format-dogsbay-md, format-obsidian, format-astro's `indentStr`), and
7
+ * format-obsidian's `chooseFence` is a weaker `pickCodeFence` (hardcoded
8
+ * backtick, `match` instead of `matchAll`).
9
+ *
10
+ * Only genuinely universal helpers live here. Per-dialect escaping
11
+ * (CommonMark text escaping, Astro brace-neutralising, dogsbay-md attribute
12
+ * syntax) stays in its own package — those profiles are not interchangeable.
13
+ */
14
+ /**
15
+ * Choose a fence long enough to wrap `content` without being terminated early.
16
+ *
17
+ * Scans for the longest run of the fence character and returns one longer,
18
+ * with a minimum of three.
19
+ */
20
+ export function pickCodeFence(content, fenceChar = "`") {
21
+ const re = fenceChar === "`" ? /`+/g : /~+/g;
22
+ let max = 2;
23
+ for (const match of content.matchAll(re)) {
24
+ if (match[0].length > max)
25
+ max = match[0].length;
26
+ }
27
+ return fenceChar.repeat(max + 1);
28
+ }
29
+ /**
30
+ * Choose a `:::` directive fence long enough to wrap `content`.
31
+ * Used by markdown dialects with container directives (Docusaurus
32
+ * admonitions, MyST, dogsbay-md).
33
+ */
34
+ export function pickDirectiveFence(content) {
35
+ let max = 2;
36
+ for (const match of content.matchAll(/:{3,}/g)) {
37
+ if (match[0].length > max)
38
+ max = match[0].length;
39
+ }
40
+ return ":".repeat(max + 1);
41
+ }
42
+ /** Indent every non-empty line by `spaces`. Empty lines stay empty. */
43
+ export function indent(content, spaces) {
44
+ const prefix = " ".repeat(spaces);
45
+ return content
46
+ .split("\n")
47
+ .map((line) => (line ? `${prefix}${line}` : line))
48
+ .join("\n");
49
+ }
50
+ /** Prefix every line, using a trimmed prefix for empty lines (blockquotes). */
51
+ export function prefixLines(content, prefix) {
52
+ return content
53
+ .split("\n")
54
+ .map((line) => (line ? `${prefix}${line}` : prefix.trimEnd()))
55
+ .join("\n");
56
+ }
57
+ /** Collapse 3+ blank lines to 2 and strip trailing whitespace on each line. */
58
+ export function normalizeTrailingWhitespace(content) {
59
+ return content
60
+ .split("\n")
61
+ .map((line) => line.replace(/[ \t]+$/, ""))
62
+ .join("\n")
63
+ .replace(/\n{3,}/g, "\n\n");
64
+ }
65
+ /**
66
+ * Strip HTML tags, keeping text content — the lossy fallback markdown targets
67
+ * use for HTML they cannot represent. Callers should record the degradation.
68
+ */
69
+ export function stripHtml(html) {
70
+ return html
71
+ .replace(/<br\s*\/?>/gi, "\n")
72
+ .replace(/<\/p>\s*<p[^>]*>/gi, "\n\n")
73
+ // Comments and doctype are not tag-shaped (no letter after `<`), so the
74
+ // letter-anchored pattern below skips them. markdown-it really does hand
75
+ // importers `<!-- … -->` inside prose html, which then leaked as literal
76
+ // markup into plain-text contexts (step titles, table cells).
77
+ .replace(/<!--[\s\S]*?-->/g, "")
78
+ .replace(/<![^>]*>/g, "")
79
+ // An image is CONTENT, not markup: dropping the tag whole loses the alt
80
+ // text and every trace that a picture was there. Recovered as markdown
81
+ // image syntax before the generic tag sweep below.
82
+ //
83
+ // format-obsidian carried this rule locally and lost it when it adopted
84
+ // this function — measured: `See <img src="a.png" alt="Diagram of the
85
+ // flow">` became `See ` on a real mkdocs->obsidian conversion, because
86
+ // markdown-it hands importers raw `<img>` inside prose HTML routinely.
87
+ .replace(/<img\b(?:[^>"']|"[^"]*"|'[^']*')*>/gi, (tag) => {
88
+ const alt = /\balt\s*=\s*"([^"]*)"/i.exec(tag)?.[1]
89
+ ?? /\balt\s*=\s*'([^']*)'/i.exec(tag)?.[1];
90
+ const src = /\bsrc\s*=\s*"([^"]*)"/i.exec(tag)?.[1]
91
+ ?? /\bsrc\s*=\s*'([^']*)'/i.exec(tag)?.[1];
92
+ if (!alt && !src)
93
+ return "";
94
+ return src ? `![${alt ?? ""}](${src})` : `![${alt ?? ""}]`;
95
+ })
96
+ // Skip quoted attribute runs so a `>` inside an attribute doesn't end the tag early.
97
+ .replace(/<\/?[A-Za-z][^\s>]*(?:[^>"']|"[^"]*"|'[^']*')*>/g, "")
98
+ .replace(/&amp;/g, "&")
99
+ .replace(/&lt;/g, "<")
100
+ .replace(/&gt;/g, ">")
101
+ .replace(/&quot;/g, '"')
102
+ .replace(/&#39;/g, "'")
103
+ .trim();
104
+ }
105
+ /**
106
+ * Convert common INLINE HTML back to markdown.
107
+ *
108
+ * Importers frequently hand serializers pre-rendered HTML for prose
109
+ * (`<code>x</code>`, `<strong>y</strong>`). Passing it through works in
110
+ * MDX-ish targets but produces source no human wants to maintain — and the
111
+ * point of a migration is clean, editable markdown.
112
+ *
113
+ * Deliberately conservative: only unambiguous inline tags, non-greedy, and
114
+ * block-level markup is left completely alone (a regex cannot restructure
115
+ * tables or lists safely). Anything not listed here survives as raw HTML.
116
+ */
117
+ export function inlineHtmlToMarkdown(html) {
118
+ // Code spans are converted FIRST and then masked, so the emphasis/link rules
119
+ // below cannot rewrite markup that is meant to be shown literally
120
+ // (`<code><b>x</b></code>` must stay `<b>x</b>`, not become `**x**`).
121
+ const masked = [];
122
+ let out = html.replace(/<code>([\s\S]*?)<\/code>/gi, (_m, inner) => {
123
+ masked.push(codeSpanText(inner));
124
+ return `\u0000CODE${masked.length - 1}\u0000`;
125
+ });
126
+ out = out
127
+ .replace(/<(?:strong|b)>([\s\S]*?)<\/(?:strong|b)>/gi, "**$1**")
128
+ .replace(/<(?:em|i)>([\s\S]*?)<\/(?:em|i)>/gi, "*$1*")
129
+ .replace(/<(?:del|s|strike)>([\s\S]*?)<\/(?:del|s|strike)>/gi, "~~$1~~")
130
+ .replace(/<mark>([\s\S]*?)<\/mark>/gi, "==$1==")
131
+ .replace(
132
+ // `[^>]*` would stop at a `>` inside another attribute value
133
+ // (`<a href="x" title="a > b">`), so skip quoted runs explicitly.
134
+ /<a\s+href="([^"]*)"(?:[^>"']|"[^"]*"|'[^']*')*>([\s\S]*?)<\/a>/gi, (_m, href, text) => `[${text}](${href})`)
135
+ .replace(/<br\s*\/?>/gi, " \n");
136
+ return out.replace(/\u0000CODE(\d+)\u0000/g, (_m, i) => masked[Number(i)]);
137
+ }
138
+ /** CommonMark code span (see serialize-core `codeSpan`; duplicated here to keep
139
+ * text.ts free of an inline.ts import cycle). */
140
+ function codeSpanText(text) {
141
+ const longest = (text.match(/`+/g) ?? []).reduce((max, run) => Math.max(max, run.length), 0);
142
+ const fence = "`".repeat(longest + 1);
143
+ const needsPad = text.length === 0 ||
144
+ text.startsWith("`") || text.endsWith("`") ||
145
+ text.startsWith(" ") || text.endsWith(" ");
146
+ const pad = needsPad ? " " : "";
147
+ return `${fence}${pad}${text}${pad}${fence}`;
148
+ }
149
+ /** Serialize a value as YAML frontmatter scalar, quoting when required. */
150
+ export function yamlScalar(value) {
151
+ if (value === "")
152
+ return '""';
153
+ const needsQuote =
154
+ // structural / indicator characters
155
+ /[:#[\]{}&*!|>'"%@`,]/.test(value) ||
156
+ // leading indicators YAML reads as block syntax
157
+ /^[-?]/.test(value) ||
158
+ // surrounding whitespace, or any newline
159
+ /^\s|\s$/.test(value) ||
160
+ /[\n\r]/.test(value) ||
161
+ // values YAML 1.1 would coerce to a non-string: booleans, null, numbers
162
+ // (incl. hex/octal/binary/exponent), dates. A page titled "123" or "null"
163
+ // must stay a STRING in frontmatter.
164
+ /^(?:true|false|yes|no|on|off|null|~)$/i.test(value) ||
165
+ /^[+-]?(?:\d[\d_]*(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(value) ||
166
+ /^0[bxo][0-9a-fA-F_]+$/i.test(value) ||
167
+ // YAML 1.1 float specials — `.inf` loads as Infinity, `.nan` as NaN.
168
+ /^[-+]?\.(?:inf|nan)$/i.test(value) ||
169
+ /^\d{4}-\d{2}-\d{2}/.test(value);
170
+ if (needsQuote) {
171
+ // Newlines MUST be escaped, not embedded: a literal `\n---\n` inside a
172
+ // quoted scalar terminates the frontmatter block early and the file no
173
+ // longer parses.
174
+ return `"${value
175
+ .replace(/\\/g, "\\\\")
176
+ .replace(/"/g, '\\"')
177
+ .replace(/\n/g, "\\n")
178
+ .replace(/\r/g, "\\r")}"`;
179
+ }
180
+ return value;
181
+ }
182
+ /** Build a YAML frontmatter block from ordered entries. Empty → "". */
183
+ export function frontmatterBlock(entries) {
184
+ const lines = entries
185
+ .filter((e) => e[1] !== undefined)
186
+ .map(([key, value]) => typeof value === "string" ? `${key}: ${yamlScalar(value)}` : `${key}: ${value}`);
187
+ if (lines.length === 0)
188
+ return "";
189
+ return `---\n${lines.join("\n")}\n---`;
190
+ }
191
+ //# sourceMappingURL=text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Unknown / unsupported block-node fallback.
3
+ *
4
+ * Generalises `format-dogsbay-md`'s `renderUnknown`, with one deliberate
5
+ * change: **the loss is recorded.** The original's `skip` mode returns `""`
6
+ * and tells nobody, which is precisely the silent-loss failure this package
7
+ * exists to end.
8
+ *
9
+ * The three existing peer fallbacks (dogsbay-md's configurable one, obsidian's
10
+ * `stripHtml`-or-children, astro's children-or-empty) are all expressible
11
+ * here.
12
+ */
13
+ import type { TreeNode } from "@dogsbay/types";
14
+ export type UnknownNodePolicy =
15
+ /** Emit `node.html`, else children, else a comment. Most content-preserving. */
16
+ "html"
17
+ /** Emit a visible marker comment only. */
18
+ | "comment"
19
+ /** Emit nothing. Loss is still recorded. */
20
+ | "skip";
21
+ export interface UnknownNodeOptions {
22
+ policy?: UnknownNodePolicy;
23
+ /** Render child nodes (needed by the "html" policy). */
24
+ renderChildren?: (children: TreeNode[]) => string;
25
+ /** How to write a comment in the target format. Default: HTML comment. */
26
+ comment?: (text: string) => string;
27
+ /** Loss recorder — wire to an `ExportLedger`. */
28
+ report?: (feature: string, detail?: string) => void;
29
+ /** Separator when falling back to children. */
30
+ join?: string;
31
+ }
32
+ /**
33
+ * Render a node whose type the exporter has no mapping for.
34
+ *
35
+ * Always records the event before returning, so an exporter cannot lose
36
+ * content without it appearing in the ledger.
37
+ */
38
+ export declare function renderUnknownNode(node: TreeNode, options?: UnknownNodeOptions): string;
39
+ //# sourceMappingURL=unknown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unknown.d.ts","sourceRoot":"","sources":["../src/unknown.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,MAAM,MAAM,iBAAiB;AAC3B,gFAAgF;AAC9E,MAAM;AACR,0CAA0C;GACxC,SAAS;AACX,4CAA4C;GAC1C,MAAM,CAAC;AAEX,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,wDAAwD;IACxD,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,MAAM,CAAC;IAClD,0EAA0E;IAC1E,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACnC,iDAAiD;IACjD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,QAAQ,EACd,OAAO,GAAE,kBAAuB,GAC/B,MAAM,CAgCR"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Render a node whose type the exporter has no mapping for.
3
+ *
4
+ * Always records the event before returning, so an exporter cannot lose
5
+ * content without it appearing in the ledger.
6
+ */
7
+ export function renderUnknownNode(node, options = {}) {
8
+ const policy = options.policy ?? "html";
9
+ const comment = options.comment ?? ((text) => `<!-- ${text} -->`);
10
+ const report = options.report ?? (() => { });
11
+ const join = options.join ?? "\n\n";
12
+ switch (policy) {
13
+ case "skip":
14
+ report(node.type, "unsupported node type — omitted from output");
15
+ return "";
16
+ case "comment":
17
+ report(node.type, "unsupported node type — replaced with a comment");
18
+ return comment(`unsupported: ${node.type}`);
19
+ case "html":
20
+ default: {
21
+ if (node.html) {
22
+ report(node.type, "unsupported node type — emitted as raw HTML");
23
+ return node.html;
24
+ }
25
+ if (node.children && node.children.length > 0 && options.renderChildren) {
26
+ report(node.type, "unsupported node type — wrapper dropped, children preserved");
27
+ return options.renderChildren(node.children);
28
+ }
29
+ report(node.type, "unsupported node type — no content to preserve");
30
+ return comment(`unsupported: ${node.type}`);
31
+ }
32
+ }
33
+ }
34
+ //# sourceMappingURL=unknown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unknown.js","sourceRoot":"","sources":["../src/unknown.ts"],"names":[],"mappings":"AAkCA;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAAc,EACd,UAA8B,EAAE;IAEhC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,MAAM,CAAC;IACxC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC,CAAC,IAAY,EAAE,EAAE,CAAC,QAAQ,IAAI,MAAM,CAAC,CAAC;IAC1E,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAC5C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,MAAM,CAAC;IAEpC,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,MAAM;YACT,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,6CAA6C,CAAC,CAAC;YACjE,OAAO,EAAE,CAAC;QAEZ,KAAK,SAAS;YACZ,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,iDAAiD,CAAC,CAAC;YACrE,OAAO,OAAO,CAAC,gBAAgB,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAE9C,KAAK,MAAM,CAAC;QACZ,OAAO,CAAC,CAAC,CAAC;YACR,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;gBACd,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,6CAA6C,CAAC,CAAC;gBACjE,OAAO,IAAI,CAAC,IAAI,CAAC;YACnB,CAAC;YACD,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;gBACxE,MAAM,CACJ,IAAI,CAAC,IAAI,EACT,6DAA6D,CAC9D,CAAC;gBACF,OAAO,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC/C,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,gDAAgD,CAAC,CAAC;YACpE,OAAO,OAAO,CAAC,gBAAgB,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@dogsbay/serialize-core",
3
+ "version": "0.2.0-beta.98",
4
+ "description": "Shared serializer core — shape-tolerant TreeNode reads, a generic inline walker, fence/indent utilities, and the export capability ledger every format exporter builds on",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "devDependencies": {
19
+ "@types/node": "^25.9.5",
20
+ "typescript": "^5.9.3",
21
+ "vitest": "^4.1.10",
22
+ "@dogsbay/format-dogsbay-md": "0.2.0-beta.98"
23
+ },
24
+ "license": "MIT",
25
+ "dependencies": {
26
+ "@dogsbay/types": "0.2.0-beta.98"
27
+ },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/dogsbay/dogsbay.git",
31
+ "directory": "packages/serialize-core"
32
+ },
33
+ "homepage": "https://github.com/dogsbay/dogsbay/tree/main/packages/serialize-core",
34
+ "bugs": {
35
+ "url": "https://github.com/dogsbay/dogsbay/issues"
36
+ },
37
+ "scripts": {
38
+ "build": "tsc",
39
+ "test": "vitest run",
40
+ "test:watch": "vitest"
41
+ }
42
+ }