jamdesk 1.1.195 → 1.1.197

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.
@@ -4,8 +4,14 @@
4
4
  * Frontmatter is whatever YAML decided, not what the author meant:
5
5
  * `description: 404` parses to a number and `description: 2026-01-01` to a
6
6
  * Date. Both reach surfaces that accept only strings — react-markdown asserts
7
- * on `children` and THROWS in development, `.trim()`/`.replace()` throw
8
- * outright, and `<meta name="description">` would carry `[object Object]`.
7
+ * on `children` and THROWS in development, and `.trim()`/`.replace()` throw
8
+ * outright.
9
+ *
10
+ * What this does NOT do is make every malformed shape presentable. A YAML
11
+ * mapping still coerces to `[object Object]` and a sequence to `a,b`, because
12
+ * the alternative — returning '' — hides an authoring mistake behind an empty
13
+ * page rather than a visibly wrong one. The contract is only that no input
14
+ * makes a caller throw.
9
15
  *
10
16
  * The same is true of a dereferenced OpenAPI spec, which is why ApiMarkdown
11
17
  * calls this too: the ISR loader runs SwaggerParser.dereference, NOT .validate
@@ -1 +1 @@
1
- {"version":3,"file":"frontmatter-text.d.ts","sourceRoot":"","sources":["../../src/lib/frontmatter-text.ts"],"names":[],"mappings":"AAYA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAItD"}
1
+ {"version":3,"file":"frontmatter-text.d.ts","sourceRoot":"","sources":["../../src/lib/frontmatter-text.ts"],"names":[],"mappings":"AAYA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CActD"}
@@ -15,8 +15,14 @@
15
15
  * Frontmatter is whatever YAML decided, not what the author meant:
16
16
  * `description: 404` parses to a number and `description: 2026-01-01` to a
17
17
  * Date. Both reach surfaces that accept only strings — react-markdown asserts
18
- * on `children` and THROWS in development, `.trim()`/`.replace()` throw
19
- * outright, and `<meta name="description">` would carry `[object Object]`.
18
+ * on `children` and THROWS in development, and `.trim()`/`.replace()` throw
19
+ * outright.
20
+ *
21
+ * What this does NOT do is make every malformed shape presentable. A YAML
22
+ * mapping still coerces to `[object Object]` and a sequence to `a,b`, because
23
+ * the alternative — returning '' — hides an authoring mistake behind an empty
24
+ * page rather than a visibly wrong one. The contract is only that no input
25
+ * makes a caller throw.
20
26
  *
21
27
  * The same is true of a dereferenced OpenAPI spec, which is why ApiMarkdown
22
28
  * calls this too: the ISR loader runs SwaggerParser.dereference, NOT .validate
@@ -34,8 +40,17 @@
34
40
  export function frontmatterText(value) {
35
41
  if (value === null || value === undefined || value === '')
36
42
  return '';
37
- if (value instanceof Date)
43
+ // An out-of-range Date still satisfies `instanceof`, and toISOString() throws
44
+ // RangeError on it — the one input that would make this function do the very
45
+ // thing it exists to prevent. js-yaml cannot produce one (its timestamp
46
+ // resolver builds through Date.UTC, which rolls over rather than going
47
+ // invalid) and JSON.parse produces no Dates at all, but a JS caller can:
48
+ // Firestore's Timestamp.toDate() on out-of-range seconds, or a reviver.
49
+ // Falling through yields "Invalid Date" — visibly wrong, which is the right
50
+ // failure for a coercion whose contract is that it never throws.
51
+ if (value instanceof Date && !Number.isNaN(value.getTime())) {
38
52
  return value.toISOString().slice(0, 10);
53
+ }
39
54
  return String(value);
40
55
  }
41
56
  //# sourceMappingURL=frontmatter-text.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"frontmatter-text.js","sourceRoot":"","sources":["../../src/lib/frontmatter-text.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,EAAE;AACF,uEAAuE;AACvE,EAAE;AACF,+EAA+E;AAC/E,6EAA6E;AAC7E,4EAA4E;AAC5E,4EAA4E;AAC5E,yEAAyE;AACzE,8EAA8E;AAC9E,4EAA4E;AAE5E;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,EAAE,CAAC;IACrE,IAAI,KAAK,YAAY,IAAI;QAAE,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACnE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC"}
1
+ {"version":3,"file":"frontmatter-text.js","sourceRoot":"","sources":["../../src/lib/frontmatter-text.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,EAAE;AACF,uEAAuE;AACvE,EAAE;AACF,+EAA+E;AAC/E,6EAA6E;AAC7E,4EAA4E;AAC5E,4EAA4E;AAC5E,yEAAyE;AACzE,8EAA8E;AAC9E,4EAA4E;AAE5E;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,EAAE,CAAC;IACrE,8EAA8E;IAC9E,6EAA6E;IAC7E,wEAAwE;IACxE,uEAAuE;IACvE,yEAAyE;IACzE,wEAAwE;IACxE,4EAA4E;IAC5E,iEAAiE;IACjE,IAAI,KAAK,YAAY,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;QAC5D,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"frontmatter-utils.d.ts","sourceRoot":"","sources":["../../src/lib/frontmatter-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA8D7D;AAqBD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG;IAExD,IAAI,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;CACjB,CAIA;AAMD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"frontmatter-utils.d.ts","sourceRoot":"","sources":["../../src/lib/frontmatter-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAkE7D;AAmED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG;IAExD,IAAI,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;CACjB,CAIA;AAMD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC"}
@@ -24,10 +24,15 @@ export function preprocessFrontmatter(content) {
24
24
  const body = content.slice(frontmatterEnd);
25
25
  // Process each line of frontmatter
26
26
  const lines = frontmatter.split('\n');
27
+ // Lines belonging to a previous key's multi-line value are VALUE TEXT, not
28
+ // key/value pairs, and must be left exactly as written.
29
+ const isContinuation = markContinuationLines(lines);
27
30
  const fixedLines = lines.map((line, i) => {
28
31
  // Skip empty lines and comments
29
32
  if (!line.trim() || line.trim().startsWith('#'))
30
33
  return line;
34
+ if (isContinuation[i])
35
+ return line;
31
36
  // Match key: value pattern (simple single-line values only).
32
37
  // The key may contain colon-segments (e.g. `og:title`, `twitter:card`,
33
38
  // `article:published_time`) so we split on the FIRST colon followed by
@@ -35,13 +40,13 @@ export function preprocessFrontmatter(content) {
35
40
  // A plain `[a-zA-Z_][a-zA-Z0-9_]*` key class stopped at the first colon,
36
41
  // misparsing `og:title: Social` as key=`og`, value=`title: Social`, then
37
42
  // quote-wrapping it into `og: "title: Social"` and destroying `og:title`.
38
- const match = line.match(/^(\s*)([a-zA-Z_][a-zA-Z0-9_.-]*(?::[a-zA-Z0-9_.-]+)*):\s*(.*)$/);
43
+ const match = line.match(KEY_LINE);
39
44
  if (!match)
40
45
  return line;
41
46
  const [, indent, key, value] = match;
42
47
  // Skip if value is empty, already quoted, or is a multi-line indicator
43
48
  // Multi-line indicators: | (literal), > (folded), with optional modifiers: |- |+ >- >+
44
- if (!value || value.startsWith('"') || value.startsWith("'") || /^[|>][-+]?$/.test(value)) {
49
+ if (!value || value.startsWith('"') || value.startsWith("'") || isBlockScalar(value)) {
45
50
  return line;
46
51
  }
47
52
  // Skip if value looks like a nested object or array
@@ -69,6 +74,55 @@ export function preprocessFrontmatter(content) {
69
74
  // `---<Info>` and tripping MDX's "expected closing tag" validator.
70
75
  return '---\n' + fixedLines.join('\n') + endMatch[0] + body;
71
76
  }
77
+ /**
78
+ * A `key:` line. Shared with `markContinuationLines` so the two cannot disagree
79
+ * about what counts as a key — if they did, a line would be rewritten as a key
80
+ * by one and treated as value text by the other.
81
+ */
82
+ const KEY_LINE = /^(\s*)([a-zA-Z_][a-zA-Z0-9_.-]*(?::[a-zA-Z0-9_.-]+)*):\s*(.*)$/;
83
+ /** `|`, `>`, `|-`, `>+`, `|2` … — the value is the indented block below. */
84
+ function isBlockScalar(value) {
85
+ return /^[|>][-+]?\d*$/.test(value.trim());
86
+ }
87
+ /**
88
+ * Mark every line that is part of a PREVIOUS key's multi-line value.
89
+ *
90
+ * Those lines are value text. Running the key/value rewrite over them is how a
91
+ * Swedish changelog's continuation line ` Kotlin-SDK:er, Afterpay…` was
92
+ * rebuilt as ` Kotlin-SDK: er, …`: the reconstruction always emits `key: value`
93
+ * with a space, which turned plain prose into what YAML reads as a nested
94
+ * mapping entry, and js-yaml rejected the whole file — so a page lost its
95
+ * frontmatter entirely to a preprocessor whose only job is to RESCUE files.
96
+ *
97
+ * Inside a block scalar the same rewrite does not throw, which is worse: the
98
+ * injected space lands silently in the description text and ships to the page.
99
+ */
100
+ function markContinuationLines(lines) {
101
+ const out = new Array(lines.length).fill(false);
102
+ for (let i = 0; i < lines.length; i++) {
103
+ if (out[i])
104
+ continue;
105
+ const match = lines[i].match(KEY_LINE);
106
+ if (!match)
107
+ continue;
108
+ const [, indent, , value] = match;
109
+ // A block scalar always has a body; a plain value only sometimes wraps.
110
+ if (!isBlockScalar(value) && !hasFoldedContinuation(lines, i))
111
+ continue;
112
+ for (let j = i + 1; j < lines.length; j++) {
113
+ // A blank line inside a block scalar is part of it; one at the end is
114
+ // harmless to mark, since the mapper skips blank lines anyway.
115
+ if (!lines[j].trim()) {
116
+ out[j] = true;
117
+ continue;
118
+ }
119
+ if (lines[j].search(/\S/) <= indent.length)
120
+ break;
121
+ out[j] = true;
122
+ }
123
+ }
124
+ return out;
125
+ }
72
126
  /**
73
127
  * True if the value on `lines[i]` continues onto a subsequent indented line
74
128
  * (a YAML folded scalar without an explicit `|` or `>` indicator). The next
@@ -1 +1 @@
1
- {"version":3,"file":"frontmatter-utils.js","sourceRoot":"","sources":["../../src/lib/frontmatter-utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"frontmatter-utils.js","sourceRoot":"","sources":["../../src/lib/frontmatter-utils.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jamdesk",
3
- "version": "1.1.195",
3
+ "version": "1.1.197",
4
4
  "description": "CLI for Jamdesk — build, preview, and deploy documentation sites from MDX. Dev server with hot reload, 50+ components, OpenAPI support, AI search, and Mintlify migration",
5
5
  "keywords": [
6
6
  "jamdesk",
@@ -91,13 +91,25 @@ export const API_DESCRIPTION_PROSE = [
91
91
  * tempting to read as redundant — the page already wraps its content in
92
92
  * `.prose` (render-doc-page.tsx `proseClasses`), so every `.prose <descendant>`
93
93
  * rule reaches the element anyway — but `.prose` also sets `font-size` on the
94
- * element it matches (themes/jam/variables.css, themes/pulsar/variables.css),
94
+ * element it matches (themes/jam, themes/pulsar, themes/halo),
95
95
  * and that is a different question from inheritance: the div carries `text-sm`,
96
96
  * and `.prose` only outranks it by being UNLAYERED where Tailwind utilities sit
97
97
  * in `@layer utilities`. Drop the class and `text-sm` wins, taking the
98
- * operation description from 16px to 14px on the default theme. The themes that
99
- * set no `.prose` font-size (base, nebula, halo) are the reason `text-sm` has to
100
- * stay too it is the fallback, not the intended size.
98
+ * operation description from 16px to 14px on jam (the default), pulsar and
99
+ * halo. `text-sm` has to stay too: base and nebula set no `.prose` font-size
100
+ * at all, so for them it is the size that actually renders.
101
+ *
102
+ * Audit those rules with a selector-agnostic search, not `^\.prose` — jam and
103
+ * halo write theirs as `body[data-theme="..."] .prose`, and an anchored grep
104
+ * reports both as clean.
105
+ *
106
+ * Font-size is not all of it, either. Below 1024px `.api-page-layout .prose`
107
+ * (themes/base.css) switches this element to `display: flex; flex-direction:
108
+ * column`, so its paragraph margins stop collapsing and start adding. That
109
+ * predates the consolidation — the old inline string carried `prose` too — but
110
+ * it means the class is a layout switch as well as a type size, and removing
111
+ * it changes the narrow-viewport rendering on every theme, not just the three
112
+ * that set a `.prose` font-size.
101
113
  *
102
114
  * `prose-sm` and `dark:prose-invert` went with the inline version and are not
103
115
  * coming back: they are Tailwind Typography modifiers and this build has no
@@ -16,8 +16,14 @@
16
16
  * Frontmatter is whatever YAML decided, not what the author meant:
17
17
  * `description: 404` parses to a number and `description: 2026-01-01` to a
18
18
  * Date. Both reach surfaces that accept only strings — react-markdown asserts
19
- * on `children` and THROWS in development, `.trim()`/`.replace()` throw
20
- * outright, and `<meta name="description">` would carry `[object Object]`.
19
+ * on `children` and THROWS in development, and `.trim()`/`.replace()` throw
20
+ * outright.
21
+ *
22
+ * What this does NOT do is make every malformed shape presentable. A YAML
23
+ * mapping still coerces to `[object Object]` and a sequence to `a,b`, because
24
+ * the alternative — returning '' — hides an authoring mistake behind an empty
25
+ * page rather than a visibly wrong one. The contract is only that no input
26
+ * makes a caller throw.
21
27
  *
22
28
  * The same is true of a dereferenced OpenAPI spec, which is why ApiMarkdown
23
29
  * calls this too: the ISR loader runs SwaggerParser.dereference, NOT .validate
@@ -34,6 +40,16 @@
34
40
  */
35
41
  export function frontmatterText(value: unknown): string {
36
42
  if (value === null || value === undefined || value === '') return '';
37
- if (value instanceof Date) return value.toISOString().slice(0, 10);
43
+ // An out-of-range Date still satisfies `instanceof`, and toISOString() throws
44
+ // RangeError on it — the one input that would make this function do the very
45
+ // thing it exists to prevent. js-yaml cannot produce one (its timestamp
46
+ // resolver builds through Date.UTC, which rolls over rather than going
47
+ // invalid) and JSON.parse produces no Dates at all, but a JS caller can:
48
+ // Firestore's Timestamp.toDate() on out-of-range seconds, or a reviver.
49
+ // Falling through yields "Invalid Date" — visibly wrong, which is the right
50
+ // failure for a coercion whose contract is that it never throws.
51
+ if (value instanceof Date && !Number.isNaN(value.getTime())) {
52
+ return value.toISOString().slice(0, 10);
53
+ }
38
54
  return String(value);
39
55
  }
@@ -28,9 +28,13 @@ export function preprocessFrontmatter(content: string): string {
28
28
 
29
29
  // Process each line of frontmatter
30
30
  const lines = frontmatter.split('\n');
31
+ // Lines belonging to a previous key's multi-line value are VALUE TEXT, not
32
+ // key/value pairs, and must be left exactly as written.
33
+ const isContinuation = markContinuationLines(lines);
31
34
  const fixedLines = lines.map((line, i) => {
32
35
  // Skip empty lines and comments
33
36
  if (!line.trim() || line.trim().startsWith('#')) return line;
37
+ if (isContinuation[i]) return line;
34
38
 
35
39
  // Match key: value pattern (simple single-line values only).
36
40
  // The key may contain colon-segments (e.g. `og:title`, `twitter:card`,
@@ -39,14 +43,14 @@ export function preprocessFrontmatter(content: string): string {
39
43
  // A plain `[a-zA-Z_][a-zA-Z0-9_]*` key class stopped at the first colon,
40
44
  // misparsing `og:title: Social` as key=`og`, value=`title: Social`, then
41
45
  // quote-wrapping it into `og: "title: Social"` and destroying `og:title`.
42
- const match = line.match(/^(\s*)([a-zA-Z_][a-zA-Z0-9_.-]*(?::[a-zA-Z0-9_.-]+)*):\s*(.*)$/);
46
+ const match = line.match(KEY_LINE);
43
47
  if (!match) return line;
44
48
 
45
49
  const [, indent, key, value] = match;
46
50
 
47
51
  // Skip if value is empty, already quoted, or is a multi-line indicator
48
52
  // Multi-line indicators: | (literal), > (folded), with optional modifiers: |- |+ >- >+
49
- if (!value || value.startsWith('"') || value.startsWith("'") || /^[|>][-+]?$/.test(value)) {
53
+ if (!value || value.startsWith('"') || value.startsWith("'") || isBlockScalar(value)) {
50
54
  return line;
51
55
  }
52
56
 
@@ -79,6 +83,52 @@ export function preprocessFrontmatter(content: string): string {
79
83
  return '---\n' + fixedLines.join('\n') + endMatch[0] + body;
80
84
  }
81
85
 
86
+ /**
87
+ * A `key:` line. Shared with `markContinuationLines` so the two cannot disagree
88
+ * about what counts as a key — if they did, a line would be rewritten as a key
89
+ * by one and treated as value text by the other.
90
+ */
91
+ const KEY_LINE =
92
+ /^(\s*)([a-zA-Z_][a-zA-Z0-9_.-]*(?::[a-zA-Z0-9_.-]+)*):\s*(.*)$/;
93
+
94
+ /** `|`, `>`, `|-`, `>+`, `|2` … — the value is the indented block below. */
95
+ function isBlockScalar(value: string): boolean {
96
+ return /^[|>][-+]?\d*$/.test(value.trim());
97
+ }
98
+
99
+ /**
100
+ * Mark every line that is part of a PREVIOUS key's multi-line value.
101
+ *
102
+ * Those lines are value text. Running the key/value rewrite over them is how a
103
+ * Swedish changelog's continuation line ` Kotlin-SDK:er, Afterpay…` was
104
+ * rebuilt as ` Kotlin-SDK: er, …`: the reconstruction always emits `key: value`
105
+ * with a space, which turned plain prose into what YAML reads as a nested
106
+ * mapping entry, and js-yaml rejected the whole file — so a page lost its
107
+ * frontmatter entirely to a preprocessor whose only job is to RESCUE files.
108
+ *
109
+ * Inside a block scalar the same rewrite does not throw, which is worse: the
110
+ * injected space lands silently in the description text and ships to the page.
111
+ */
112
+ function markContinuationLines(lines: string[]): boolean[] {
113
+ const out = new Array<boolean>(lines.length).fill(false);
114
+ for (let i = 0; i < lines.length; i++) {
115
+ if (out[i]) continue;
116
+ const match = lines[i].match(KEY_LINE);
117
+ if (!match) continue;
118
+ const [, indent, , value] = match;
119
+ // A block scalar always has a body; a plain value only sometimes wraps.
120
+ if (!isBlockScalar(value) && !hasFoldedContinuation(lines, i)) continue;
121
+ for (let j = i + 1; j < lines.length; j++) {
122
+ // A blank line inside a block scalar is part of it; one at the end is
123
+ // harmless to mark, since the mapper skips blank lines anyway.
124
+ if (!lines[j].trim()) { out[j] = true; continue; }
125
+ if (lines[j].search(/\S/) <= indent.length) break;
126
+ out[j] = true;
127
+ }
128
+ }
129
+ return out;
130
+ }
131
+
82
132
  /**
83
133
  * True if the value on `lines[i]` continues onto a subsequent indented line
84
134
  * (a YAML folded scalar without an explicit `|` or `>` indicator). The next
@@ -9,6 +9,7 @@ import { getContentDir } from './docs';
9
9
  import { injectPromptSources } from './inject-prompt-source';
10
10
  import { mdxSecurityOptions } from './mdx-security-options';
11
11
  import { remarkSvgNamespaceAttrs } from './remark-svg-namespace-attrs';
12
+ import { remarkStyleStringToObject } from './remark-style-string-to-object';
12
13
  import { getShikiOptions } from './shiki-config';
13
14
 
14
15
  export interface DocFrontmatter {
@@ -46,7 +47,7 @@ export async function getDocContent(slug: string[]): Promise<DocContent | null>
46
47
  // Keep expression props (e.g. cols={2}) compatible under next-mdx-remote v6.
47
48
  ...mdxSecurityOptions,
48
49
  mdxOptions: {
49
- remarkPlugins: [remarkGfm, remarkSvgNamespaceAttrs],
50
+ remarkPlugins: [remarkGfm, remarkSvgNamespaceAttrs, remarkStyleStringToObject],
50
51
  rehypePlugins: [[rehypeShiki, shikiOptions]],
51
52
  },
52
53
  scope: data,