Skip to content

Commit 99b264d

Browse files
authored
fix: downgrade marked library to v4.x.x for CommonJS compatibility (#100)
2 parents 668624f + 27426be commit 99b264d

File tree

5 files changed

+49
-43
lines changed

5 files changed

+49
-43
lines changed

nodes/Substack/MarkdownParser.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1+
import { marked } from 'marked';
12
import type { OwnProfile } from 'substack-api';
23

4+
// Configure marked library
5+
marked.setOptions({
6+
gfm: true,
7+
breaks: false
8+
});
9+
310
// Helper function to decode HTML entities
411
function decodeHtmlEntities(text: string): string {
512
const entityMap: Record<string, string> = {
@@ -23,20 +30,11 @@ export class MarkdownParser {
2330
* Parse markdown text and apply it to a NoteBuilder using structured approach
2431
* Returns the final ParagraphBuilder with all content applied (handles immutable builders)
2532
*/
26-
static async parseMarkdownToNoteStructured(markdown: string, noteBuilder: ReturnType<OwnProfile['newNote']>): Promise<ReturnType<ReturnType<OwnProfile['newNote']>['paragraph']>> {
33+
static parseMarkdownToNoteStructured(markdown: string, noteBuilder: ReturnType<OwnProfile['newNote']>): ReturnType<ReturnType<OwnProfile['newNote']>['paragraph']> {
2734
if (!markdown.trim()) {
2835
throw new Error('Note body cannot be empty - at least one paragraph with content is required');
2936
}
3037

31-
// Dynamically import marked library
32-
const { marked } = await import('marked');
33-
34-
// Configure marked library
35-
marked.setOptions({
36-
gfm: true,
37-
breaks: false
38-
});
39-
4038
// Parse markdown into tokens using marked
4139
const tokens = marked.lexer(markdown);
4240

@@ -72,8 +70,8 @@ export class MarkdownParser {
7270
/**
7371
* Legacy method for backward compatibility
7472
*/
75-
static async parseMarkdownToNote(markdown: string, noteBuilder: ReturnType<OwnProfile['newNote']>): Promise<ReturnType<ReturnType<OwnProfile['newNote']>['paragraph']>> {
76-
return await this.parseMarkdownToNoteStructured(markdown, noteBuilder);
73+
static parseMarkdownToNote(markdown: string, noteBuilder: ReturnType<OwnProfile['newNote']>): ReturnType<ReturnType<OwnProfile['newNote']>['paragraph']> {
74+
return this.parseMarkdownToNoteStructured(markdown, noteBuilder);
7775
}
7876

7977
/**

nodes/Substack/Note.operations.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ async function createAdvancedNote(
235235
}
236236

237237
try {
238-
const finalBuilder = await MarkdownParser.parseMarkdownToNoteStructured(body.trim(), ownProfile.newNote());
238+
const finalBuilder = MarkdownParser.parseMarkdownToNoteStructured(body.trim(), ownProfile.newNote());
239239
return await finalBuilder.publish();
240240
} catch (error) {
241241
let userMessage = error.message;

package-lock.json

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
},
4646
"devDependencies": {
4747
"@types/jest": "^30.0.0",
48-
"@types/marked": "^5.0.2",
48+
"@types/marked": "^4.3.2",
4949
"@typescript-eslint/parser": "~8.38.0",
5050
"eslint": "^9.29.0",
5151
"eslint-plugin-n8n-nodes-base": "^1.16.3",
@@ -59,7 +59,7 @@
5959
"n8n-workflow": "^1.82.0"
6060
},
6161
"dependencies": {
62-
"marked": "^16.1.1",
62+
"marked": "^4.3.0",
6363
"substack-api": "^1.3.0"
6464
}
6565
}

tests/unit/markdown-parser.test.ts

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ describe('MarkdownParser - Real NoteBuilder', () => {
100100
const profile = await mockSubstackClient.ownProfile();
101101
const noteBuilder = profile.newNote();
102102

103-
const result = await MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
103+
const result = MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
104104
await result.publish();
105105

106106
expect(capturedPayload).toMatchObject(expectedJson);
@@ -132,7 +132,7 @@ describe('MarkdownParser - Real NoteBuilder', () => {
132132
const profile = await mockSubstackClient.ownProfile();
133133
const noteBuilder = profile.newNote();
134134

135-
const result = await MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
135+
const result = MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
136136
await result.publish();
137137

138138
expect(capturedPayload).toMatchObject(expectedJson);
@@ -164,7 +164,7 @@ describe('MarkdownParser - Real NoteBuilder', () => {
164164
const profile = await mockSubstackClient.ownProfile();
165165
const noteBuilder = profile.newNote();
166166

167-
const result = await MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
167+
const result = MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
168168
await result.publish();
169169

170170
expect(capturedPayload).toMatchObject(expectedJson);
@@ -196,7 +196,7 @@ describe('MarkdownParser - Real NoteBuilder', () => {
196196
const profile = await mockSubstackClient.ownProfile();
197197
const noteBuilder = profile.newNote();
198198

199-
const result = await MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
199+
const result = MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
200200
await result.publish();
201201

202202
expect(capturedPayload).toMatchObject(expectedJson);
@@ -246,7 +246,7 @@ describe('MarkdownParser - Real NoteBuilder', () => {
246246
const profile = await mockSubstackClient.ownProfile();
247247
const noteBuilder = profile.newNote();
248248

249-
const result = await MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
249+
const result = MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
250250
await result.publish();
251251

252252
expect(capturedPayload).toMatchObject(expectedJson);
@@ -272,7 +272,7 @@ describe('MarkdownParser - Real NoteBuilder', () => {
272272
const profile = await mockSubstackClient.ownProfile();
273273
const noteBuilder = profile.newNote();
274274

275-
const result = await MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
275+
const result = MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
276276
await result.publish();
277277

278278
expect(capturedPayload).toMatchObject(expectedJson);
@@ -330,7 +330,7 @@ describe('MarkdownParser - Real NoteBuilder', () => {
330330
const profile = await mockSubstackClient.ownProfile();
331331
const noteBuilder = profile.newNote();
332332

333-
const result = await MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
333+
const result = MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
334334
await result.publish();
335335

336336
expect(capturedPayload).toMatchObject(expectedJson);
@@ -397,7 +397,7 @@ Second paragraph with *italic* text.`;
397397
const profile = await mockSubstackClient.ownProfile();
398398
const noteBuilder = profile.newNote();
399399

400-
const result = await MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
400+
const result = MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
401401
await result.publish();
402402

403403
expect(capturedPayload).toMatchObject(expectedJson);
@@ -430,7 +430,7 @@ Second paragraph with *italic* text.`;
430430
const profile = await mockSubstackClient.ownProfile();
431431
const noteBuilder = profile.newNote();
432432

433-
const result = await MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
433+
const result = MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
434434
await result.publish();
435435

436436
expect(capturedPayload).toMatchObject(expectedJson);
@@ -469,7 +469,7 @@ Second paragraph with *italic* text.`;
469469
const profile = await mockSubstackClient.ownProfile();
470470
const noteBuilder = profile.newNote();
471471

472-
const result = await MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
472+
const result = MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
473473
await result.publish();
474474

475475
expect(capturedPayload).toMatchObject(expectedJson);
@@ -509,7 +509,7 @@ Second paragraph with *italic* text.`;
509509
const profile = await mockSubstackClient.ownProfile();
510510
const noteBuilder = profile.newNote();
511511

512-
const result = await MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
512+
const result = MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
513513
await result.publish();
514514

515515
expect(capturedPayload).toMatchObject(expectedJson);
@@ -547,7 +547,7 @@ Second paragraph with *italic* text.`;
547547
const profile = await mockSubstackClient.ownProfile();
548548
const noteBuilder = profile.newNote();
549549

550-
const result = await MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
550+
const result = MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
551551
await result.publish();
552552

553553
expect(capturedPayload).toMatchObject(expectedJson);
@@ -591,7 +591,7 @@ Second paragraph with *italic* text.`;
591591
const profile = await mockSubstackClient.ownProfile();
592592
const noteBuilder = profile.newNote();
593593

594-
const result = await MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
594+
const result = MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
595595
await result.publish();
596596

597597
expect(capturedPayload).toMatchObject(expectedJson);
@@ -622,7 +622,7 @@ Second paragraph with *italic* text.`;
622622
const profile = await mockSubstackClient.ownProfile();
623623
const noteBuilder = profile.newNote();
624624

625-
const result = await MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
625+
const result = MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
626626
await result.publish();
627627

628628
expect(capturedPayload).toMatchObject(expectedJson);
@@ -728,7 +728,7 @@ Final paragraph.`;
728728
const profile = await mockSubstackClient.ownProfile();
729729
const noteBuilder = profile.newNote();
730730

731-
const result = await MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
731+
const result = MarkdownParser.parseMarkdownToNoteStructured(markdown, noteBuilder);
732732
await result.publish();
733733

734734
expect(capturedPayload).toMatchObject(expectedJson);
@@ -740,28 +740,36 @@ Final paragraph.`;
740740
const profile = await mockSubstackClient.ownProfile();
741741
const noteBuilder = profile.newNote();
742742

743-
await expect(MarkdownParser.parseMarkdownToNoteStructured('', noteBuilder)).rejects.toThrow('Note body cannot be empty - at least one paragraph with content is required');
743+
expect(() => {
744+
MarkdownParser.parseMarkdownToNoteStructured('', noteBuilder);
745+
}).toThrow('Note body cannot be empty - at least one paragraph with content is required');
744746
});
745747

746748
it('should throw error for whitespace-only markdown', async () => {
747749
const profile = await mockSubstackClient.ownProfile();
748750
const noteBuilder = profile.newNote();
749751

750-
await expect(MarkdownParser.parseMarkdownToNoteStructured(' \n\t ', noteBuilder)).rejects.toThrow('Note body cannot be empty - at least one paragraph with content is required');
752+
expect(() => {
753+
MarkdownParser.parseMarkdownToNoteStructured(' \n\t ', noteBuilder);
754+
}).toThrow('Note body cannot be empty - at least one paragraph with content is required');
751755
});
752756

753757
it('should throw error for empty headings', async () => {
754758
const profile = await mockSubstackClient.ownProfile();
755759
const noteBuilder = profile.newNote();
756760

757-
await expect(MarkdownParser.parseMarkdownToNoteStructured('## \n### \n#### ', noteBuilder)).rejects.toThrow('Note must contain at least one paragraph with actual content');
761+
expect(() => {
762+
MarkdownParser.parseMarkdownToNoteStructured('## \n### \n#### ', noteBuilder);
763+
}).toThrow('Note must contain at least one paragraph with actual content');
758764
});
759765

760766
it('should throw error for empty list items only', async () => {
761767
const profile = await mockSubstackClient.ownProfile();
762768
const noteBuilder = profile.newNote();
763769

764-
await expect(MarkdownParser.parseMarkdownToNoteStructured('- \n* \n1. ', noteBuilder)).rejects.toThrow('Note must contain at least one paragraph with actual content');
770+
expect(() => {
771+
MarkdownParser.parseMarkdownToNoteStructured('- \n* \n1. ', noteBuilder);
772+
}).toThrow('Note must contain at least one paragraph with actual content');
765773
});
766774
});
767775
});

0 commit comments

Comments
 (0)