Skip to content

Commit f2e6fa2

Browse files
committedMar 6, 2024
fix(cli): keep comments in YAML frontmatter
Switch the YAML library from [`js-yaml`][1] to [`yaml`][2]. This allows us to keep YAML comments in the frontmatter. [1]: https://www.npmjs.com/package/js-yaml [2]: https://www.npmjs.com/package/yaml
1 parent 3211343 commit f2e6fa2

File tree

5 files changed

+46
-50
lines changed

5 files changed

+46
-50
lines changed
 

‎packages/cli/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,6 @@
5757
"@inquirer/select": "^1.3.1",
5858
"@mermaidchart/sdk": "workspace:^",
5959
"commander": "^11.1.0",
60-
"js-yaml": "^4.1.0"
60+
"yaml": "^2.3.4"
6161
}
6262
}

‎packages/cli/src/commander.test.ts

+7
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ describe('pull', () => {
249249
const mockedDiagram = {
250250
...mockedEmptyDiagram,
251251
code: `---
252+
# comments in YAML shouldn't be removed
252253
title: My cool flowchart
253254
---
254255
flowchart TD
@@ -262,6 +263,7 @@ title: My cool flowchart
262263
const diagramContents = await readFile(diagram, { encoding: 'utf8' });
263264

264265
expect(diagramContents).toContain(`id: ${mockedDiagram.documentID}`);
266+
expect(diagramContents).toContain("# comments in YAML shouldn't be removed");
265267
expect(diagramContents).toContain("flowchart TD\n A[I've been updated!]");
266268
});
267269
});
@@ -298,5 +300,10 @@ describe('push', () => {
298300
code: expect.not.stringContaining('id:'),
299301
}),
300302
);
303+
expect(vi.mocked(MermaidChart.prototype.setDocument)).toHaveBeenCalledWith(
304+
expect.objectContaining({
305+
code: expect.stringMatching(/^# This comment should be pushed to the server/m),
306+
}),
307+
);
301308
});
302309
});

‎packages/cli/src/frontmatter.ts

+20-37
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
/**
22
* Copied from https://github.com/mermaid-js/mermaid/blob/4a4e614b646bdb5f91f02d0483a7704b315d09fd/packages/mermaid/src/diagram-api/regexes.ts
33
*/
4-
5-
// The "* as yaml" part is necessary for tree-shaking
6-
import * as yaml from 'js-yaml';
4+
import { parseDocument, type Document, YAMLMap, isMap } from 'yaml';
75

86
const frontMatterRegex = /^-{3}\s*[\n\r](.*?)[\n\r]-{3}\s*[\n\r]+/s;
97

@@ -36,20 +34,13 @@ function splitFrontMatter(text: string) {
3634
}
3735
}
3836

39-
function parseFrontMatterYAML(frontMatterYaml: string) {
40-
let parsed: FrontMatterMetadata =
41-
// TODO: replace with https://www.npmjs.com/package/yaml so that we can
42-
// read/write comments too
43-
yaml.load(frontMatterYaml, {
44-
// To support config, we need JSON schema.
45-
// https://www.yaml.org/spec/1.2/spec.html#id2803231
46-
schema: yaml.JSON_SCHEMA,
47-
}) ?? {};
48-
49-
// To handle runtime data type changes
50-
parsed = typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
37+
function parseFrontMatterYAML(frontMatterYaml: string): Document<YAMLMap, false> {
38+
const document: Document = parseDocument(frontMatterYaml);
39+
if (!isMap(document.contents)) {
40+
document.contents = new YAMLMap();
41+
}
5142

52-
return parsed;
43+
return document as unknown as Document<YAMLMap, false>;
5344
}
5445

5546
/**
@@ -62,7 +53,7 @@ function parseFrontMatterYAML(frontMatterYaml: string) {
6253
export function extractFrontMatter(text: string): FrontMatterResult {
6354
const { diagramText, frontMatter } = splitFrontMatter(text);
6455

65-
const parsed = parseFrontMatterYAML(frontMatter);
56+
const parsed = parseFrontMatterYAML(frontMatter).toJSON();
6657

6758
const metadata: FrontMatterMetadata = {};
6859

@@ -93,16 +84,16 @@ export function extractFrontMatter(text: string): FrontMatterResult {
9384
* @param newMetadata - The metadata fields to update.
9485
* @returns The text with the updated YAML frontmatter.
9586
*/
96-
export function injectFrontMatter(text: string, newMetadata: Partial<FrontMatterMetadata>) {
87+
export function injectFrontMatter(text: string, newMetadata: Pick<FrontMatterMetadata, 'id'>) {
9788
const { diagramText, frontMatter } = splitFrontMatter(text);
9889

99-
const parsed = parseFrontMatterYAML(frontMatter);
90+
const document = parseFrontMatterYAML(frontMatter);
10091

101-
const mergedFrontmatter = { ...parsed, ...newMetadata };
92+
for (const [key, value] of Object.entries(newMetadata)) {
93+
document.contents.set(key, value);
94+
}
10295

103-
return `---\n${yaml.dump(mergedFrontmatter, {
104-
schema: yaml.JSON_SCHEMA,
105-
})}---\n${diagramText}`;
96+
return `---\n${document.toString()}---\n${diagramText}`;
10697
}
10798

10899
/**
@@ -115,24 +106,16 @@ export function injectFrontMatter(text: string, newMetadata: Partial<FrontMatter
115106
export function removeFrontMatterKeys(text: string, keysToRemove: Set<keyof FrontMatterMetadata>) {
116107
const { diagramText, frontMatter } = splitFrontMatter(text);
117108

118-
const parsedFrontMatter = parseFrontMatterYAML(frontMatter);
109+
const document = parseFrontMatterYAML(frontMatter);
119110

120-
const entries = Object.entries(parsedFrontMatter)
121-
.map((val) => {
122-
if (keysToRemove.has(val[0] as keyof FrontMatterMetadata)) {
123-
return null;
124-
} else {
125-
return val;
126-
}
127-
})
128-
.filter((val) => val) as [string, any][]; // eslint-disable-line @typescript-eslint/no-explicit-any
111+
for (const key of keysToRemove) {
112+
document.contents.delete(key);
113+
}
129114

130-
if (entries.length === 0) {
115+
if (document.contents.items.length === 0) {
131116
// skip creating frontmatter if there is no frontmatter
132117
return diagramText;
133118
} else {
134-
return `---\n${yaml.dump(Object.fromEntries(entries), {
135-
schema: yaml.JSON_SCHEMA,
136-
})}---\n${diagramText}`;
119+
return `---\n${document.toString()}---\n${diagramText}`;
137120
}
138121
}

‎packages/cli/test/fixtures/connected-diagram.mmd

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
---
2+
# This comment should be pushed to the server
23
title: My cool flowchart
34
id: my-test-document-id
45
---

‎pnpm-lock.yaml

+17-12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
Failed to load comments.