Skip to content

Commit 2221a6a

Browse files
committed
Plugins and ignores
1 parent f33e7ce commit 2221a6a

File tree

10 files changed

+1788
-9
lines changed

10 files changed

+1788
-9
lines changed

package-lock.json

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

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44
"private": true,
55
"description": "PostHog example projects",
66
"scripts": {
7-
"build:docs": "node scripts/build-examples-mcp-resources.js"
7+
"build:docs": "node scripts/build-examples-mcp-resources.js",
8+
"test:plugins": "vitest run scripts/plugins/tests",
9+
"test:plugins:watch": "vitest scripts/plugins/tests"
810
},
911
"devDependencies": {
10-
"archiver": "^7.0.1"
12+
"archiver": "^7.0.1",
13+
"vitest": "^2.0.0"
1114
}
1215
}

scripts/build-examples-mcp-resources.js

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@
88
const fs = require('fs');
99
const path = require('path');
1010
const archiver = require('archiver');
11+
const { composePlugins, ignoreLinePlugin, ignoreFilePlugin, ignoreBlockPlugin } = require('./plugins/index');
1112

1213
/**
1314
* Build configuration
1415
*/
1516
const defaultConfig = {
17+
// Global plugins applied to all examples
18+
plugins: [ignoreFilePlugin, ignoreBlockPlugin, ignoreLinePlugin],
1619
examples: [
1720
{
1821
path: 'basics/next-app-router',
@@ -23,6 +26,8 @@ const defaultConfig = {
2326
includes: [],
2427
regex: [],
2528
},
29+
// Example-specific plugins (optional)
30+
plugins: [],
2631
},
2732
{
2833
path: 'basics/next-pages-router',
@@ -33,6 +38,8 @@ const defaultConfig = {
3338
includes: [],
3439
regex: [],
3540
},
41+
// Example-specific plugins (optional)
42+
plugins: [],
3643
},
3744
],
3845
globalSkipPatterns: {
@@ -100,14 +107,38 @@ function buildMarkdownHeader(frameworkName, repoUrl, relativePath) {
100107

101108
/**
102109
* Convert a file to a markdown code block
110+
* Now supports plugins for content transformation
111+
*
112+
* @param {string} relativePath - The relative path of the file
113+
* @param {string} content - The file content
114+
* @param {string} extension - The file extension
115+
* @param {Array} plugins - Optional array of plugins to transform content
116+
* @returns {string|null} - The markdown representation of the file, or null if content is empty after transformation
103117
*/
104-
function fileToMarkdown(relativePath, content, extension) {
118+
function fileToMarkdown(relativePath, content, extension, plugins = []) {
119+
// Create context object for plugins
120+
const context = {
121+
relativePath,
122+
extension,
123+
};
124+
125+
// Apply plugins to content if provided
126+
const transformedContent = plugins.length > 0
127+
? composePlugins(plugins)(content, context)
128+
: content;
129+
130+
// If content is empty after transformation, return null to skip the file
131+
if (!transformedContent || transformedContent.trim() === '') {
132+
return null;
133+
}
134+
135+
// Build markdown output
105136
let markdown = `## ${relativePath}\n\n`;
106137
if (extension === 'md') {
107-
markdown += content;
138+
markdown += transformedContent;
108139
} else {
109140
markdown += `\`\`\`${extension}\n`;
110-
markdown += content;
141+
markdown += transformedContent;
111142
markdown += '\n```\n\n';
112143
}
113144
markdown += '\n---\n\n';
@@ -181,7 +212,7 @@ function getAllFiles(dirPath, arrayOfFiles = [], baseDir = dirPath, skipPatterns
181212
/**
182213
* Convert an example project directory to markdown
183214
*/
184-
function convertProjectToMarkdown(absolutePath, frameworkInfo, relativePath, skipPatterns) {
215+
function convertProjectToMarkdown(absolutePath, frameworkInfo, relativePath, skipPatterns, plugins = []) {
185216
const repoUrl = 'https://github.com/PostHog/examples';
186217
let markdown = buildMarkdownHeader(frameworkInfo.displayName, repoUrl, relativePath);
187218

@@ -203,7 +234,12 @@ function convertProjectToMarkdown(absolutePath, frameworkInfo, relativePath, ski
203234
try {
204235
const content = fs.readFileSync(file.fullPath, 'utf8');
205236
const extension = path.extname(file.fullPath).slice(1) || '';
206-
markdown += fileToMarkdown(file.relativePath, content, extension);
237+
const fileMarkdown = fileToMarkdown(file.relativePath, content, extension, plugins);
238+
239+
// Skip file if plugins returned empty content
240+
if (fileMarkdown !== null) {
241+
markdown += fileMarkdown;
242+
}
207243
} catch (e) {
208244
// Skip files that can't be read as text
209245
console.warn(`Skipping ${file.relativePath}: ${e.message}`);
@@ -243,12 +279,19 @@ async function build() {
243279
example.skipPatterns
244280
);
245281

282+
// Merge global and example-specific plugins
283+
const plugins = [
284+
...(defaultConfig.plugins || []),
285+
...(example.plugins || [])
286+
];
287+
246288
console.log(`Processing ${example.displayName}...`);
247289
const markdown = convertProjectToMarkdown(
248290
absolutePath,
249291
example,
250292
example.path,
251-
skipPatterns
293+
skipPatterns,
294+
plugins
252295
);
253296

254297
const outputFilename = `${example.id}.md`;

scripts/plugins/ignore-block.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* Plugin to remove blocks of code marked with @ignoreBlock
3+
* Removes all lines between @ignoreBlockStart and @ignoreBlockEnd (inclusive)
4+
* Supports both line comments and block comments
5+
*/
6+
const ignoreBlockPlugin = {
7+
name: 'ignore-block',
8+
transform: (content) => {
9+
const lines = content.split('\n');
10+
const result = [];
11+
let insideIgnoreBlock = false;
12+
13+
for (const line of lines) {
14+
// Check for block start marker (must be at start of comment)
15+
if (line.match(/(?:\/\/|#|\/\*|<!--)\s*@ignoreBlockStart(?:\s|$|\*\/|-->)/)) {
16+
insideIgnoreBlock = true;
17+
continue;
18+
}
19+
20+
// Check for block end marker (must be at start of comment)
21+
if (line.match(/(?:\/\/|#|\/\*|<!--)\s*@ignoreBlockEnd(?:\s|$|\*\/|-->)/)) {
22+
insideIgnoreBlock = false;
23+
continue;
24+
}
25+
26+
// Skip lines inside ignore block
27+
if (insideIgnoreBlock) {
28+
continue;
29+
}
30+
31+
result.push(line);
32+
}
33+
34+
return result.join('\n');
35+
},
36+
};
37+
38+
module.exports = ignoreBlockPlugin;

scripts/plugins/ignore-file.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* Plugin to ignore entire files marked with @ignoreFile at the start
3+
* Returns empty string if @ignoreFile is found in the first few lines
4+
* Supports both line comments and block comments
5+
*/
6+
const ignoreFilePlugin = {
7+
name: 'ignore-file',
8+
transform: (content) => {
9+
// Check first 10 lines for @ignoreFile marker
10+
const lines = content.split('\n');
11+
const checkLines = lines.slice(0, 10);
12+
13+
for (const line of checkLines) {
14+
// Must be at start of comment: // @ignoreFile is valid, // text @ignoreFile is not
15+
if (line.match(/(?:\/\/|#|\/\*|<!--)\s*@ignoreFile(?:\s|$|\*\/|-->)/)) {
16+
return ''; // Return empty to skip entire file
17+
}
18+
}
19+
20+
return content;
21+
},
22+
};
23+
24+
module.exports = ignoreFilePlugin;

scripts/plugins/ignore-line.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* Plugin to remove lines marked with @ignoreLine comment
3+
* Removes both the @ignoreLine comment and the following line
4+
* Supports both line comments and block comments
5+
*/
6+
const ignoreLinePlugin = {
7+
name: 'ignore-line',
8+
transform: (content) => {
9+
const lines = content.split('\n');
10+
const result = [];
11+
let skipNext = false;
12+
13+
for (let i = 0; i < lines.length; i++) {
14+
const line = lines[i];
15+
16+
// Check if this line should be skipped due to previous @ignoreLine
17+
if (skipNext) {
18+
skipNext = false;
19+
continue;
20+
}
21+
22+
// Check if line ends with comment and @ignoreLine directive
23+
// Valid: code // @ignoreLine, code # @ignoreLine
24+
// Invalid: code // text @ignoreLine (must be at start of comment)
25+
if (line.match(/(?:\/\/|#|\/\*|<!--)\s*@ignoreLine(?:\s|$|\*\/|-->)/)) {
26+
skipNext = true;
27+
continue;
28+
}
29+
30+
result.push(line);
31+
}
32+
33+
return result.join('\n');
34+
},
35+
};
36+
37+
module.exports = ignoreLinePlugin;

scripts/plugins/index.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* Plugin system for content transformation
3+
*/
4+
5+
const ignoreLinePlugin = require('./ignore-line');
6+
const ignoreFilePlugin = require('./ignore-file');
7+
const ignoreBlockPlugin = require('./ignore-block');
8+
9+
/**
10+
* Compose multiple plugins into a single transformation function
11+
* Plugins are applied in order (left to right)
12+
* Short-circuits and returns empty string if content becomes empty
13+
*
14+
* @param {Array} plugins - Array of plugins to compose
15+
* @returns {function(string, Object): string} - Composed transformation function
16+
*/
17+
function composePlugins(plugins = []) {
18+
return (content, context) => {
19+
return plugins.reduce((transformedContent, plugin) => {
20+
// Short-circuit if content is already empty
21+
if (!transformedContent || transformedContent.trim() === '') {
22+
return transformedContent;
23+
}
24+
25+
try {
26+
return plugin.transform(transformedContent, context);
27+
} catch (error) {
28+
console.error(`Error in plugin '${plugin.name}':`, error.message);
29+
// Return content as-is if plugin fails
30+
return transformedContent;
31+
}
32+
}, content);
33+
};
34+
}
35+
36+
module.exports = {
37+
composePlugins,
38+
ignoreLinePlugin,
39+
ignoreFilePlugin,
40+
ignoreBlockPlugin,
41+
};
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { describe, it, expect } from 'vitest';
2+
import ignoreBlockPlugin from '../ignore-block.js';
3+
4+
describe('ignore-block plugin', () => {
5+
it('removes block with // comments', () => {
6+
const input = `line 1
7+
// @ignoreBlockStart
8+
line 2
9+
line 3
10+
// @ignoreBlockEnd
11+
line 4`;
12+
const expected = `line 1
13+
line 4`;
14+
expect(ignoreBlockPlugin.transform(input)).toBe(expected);
15+
});
16+
17+
it('removes block with # comments', () => {
18+
const input = `line 1
19+
# @ignoreBlockStart
20+
line 2
21+
line 3
22+
# @ignoreBlockEnd
23+
line 4`;
24+
const expected = `line 1
25+
line 4`;
26+
expect(ignoreBlockPlugin.transform(input)).toBe(expected);
27+
});
28+
29+
it('removes block with /* */ comments', () => {
30+
const input = `line 1
31+
/* @ignoreBlockStart */
32+
line 2
33+
line 3
34+
/* @ignoreBlockEnd */
35+
line 4`;
36+
const expected = `line 1
37+
line 4`;
38+
expect(ignoreBlockPlugin.transform(input)).toBe(expected);
39+
});
40+
41+
it('removes block with HTML comments', () => {
42+
const input = `line 1
43+
<!-- @ignoreBlockStart -->
44+
line 2
45+
line 3
46+
<!-- @ignoreBlockEnd -->
47+
line 4`;
48+
const expected = `line 1
49+
line 4`;
50+
expect(ignoreBlockPlugin.transform(input)).toBe(expected);
51+
});
52+
53+
it('handles multiple ignore blocks', () => {
54+
const input = `line 1
55+
// @ignoreBlockStart
56+
line 2
57+
// @ignoreBlockEnd
58+
line 3
59+
# @ignoreBlockStart
60+
line 4
61+
# @ignoreBlockEnd
62+
line 5`;
63+
const expected = `line 1
64+
line 3
65+
line 5`;
66+
expect(ignoreBlockPlugin.transform(input)).toBe(expected);
67+
});
68+
69+
it('does not modify content without ignore blocks', () => {
70+
const input = `line 1
71+
line 2
72+
line 3`;
73+
expect(ignoreBlockPlugin.transform(input)).toBe(input);
74+
});
75+
76+
it('does NOT remove when markers are not at start of comment', () => {
77+
const input = `line 1
78+
// some text @ignoreBlockStart
79+
line 2
80+
// some text @ignoreBlockEnd
81+
line 3`;
82+
expect(ignoreBlockPlugin.transform(input)).toBe(input);
83+
});
84+
});

0 commit comments

Comments
 (0)