@salesforcedevs/sfdocs-remark-include-plugin 0.0.4-alpha → 0.0.6-alpha

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/README.md CHANGED
@@ -1,49 +1,13 @@
1
- # Remark Include plugin
1
+ # Remark Include Plugin
2
2
 
3
- ## About
3
+ ## Examples
4
4
 
5
- This is a custom plugin for including the contents of another file in a markdown document. It processes the include directive (`::include{src="..."}`) by reading the target file, parsing or processing it through the unified pipeline, and replacing the directive with the included content.
5
+ Include another markdown file:
6
6
 
7
- Example:
8
-
9
- ```markdown
10
- ::include{src="../shared/snippet.md"}
11
7
  ```
12
-
13
- When the included file path contains `/shared/`, image URLs in that content are rewritten to absolute paths using the content directory.
14
-
15
- ## Usage
16
-
17
- Use with `remark-directive` in your unified pipeline:
18
-
19
- ```javascript
20
- import unified from 'unified';
21
- import remarkParse from 'remark-parse';
22
- import remarkDirective from 'remark-directive';
23
- import includePlugin from '@salesforcedevs/sfdocs-remark-include-plugin';
24
-
25
- unified()
26
- .use(remarkParse)
27
- .use(remarkDirective)
28
- .use(includePlugin())
29
- .processSync(markdown);
30
- ```
31
-
32
- Optional custom renderer (for custom HTML wrapping of included content):
33
-
34
- ```javascript
35
- includePlugin({
36
- renderInclude(metadata) {
37
- const root = metadata.get('root');
38
- const node = metadata.get('node');
39
- const out = new Map();
40
- out.set('outputDom', `<div class="include">${root}</div>`);
41
- return out;
42
- },
43
- });
8
+ ::include{src="../shared/snippet.md"}
44
9
  ```
45
10
 
46
- ## Development
11
+ The plugin reads the target file, parses it, and inserts its children into the current document.
47
12
 
48
- - `yarn build` compile TypeScript
49
- - `yarn test` — run tests
13
+ If the included file path contains `/shared/`, relative image URLs in that included content are rewritten to absolute content paths.
package/dist/index.d.ts CHANGED
@@ -6,11 +6,8 @@ import { Transformer } from 'unified';
6
6
  * with the included content. Must be used after remark-directive.
7
7
  * When the included file path contains /shared/, image URLs in that content are rewritten to absolute paths using the content directory.
8
8
  *
9
- * @param customRenderFunctions - Optional { renderInclude } for custom HTML rendering (same as genericDirective)
10
9
  * @returns Function that returns a Remark transformer
11
10
  */
12
- declare function includePlugin(customRenderFunctions?: {
13
- renderInclude?: (metadata: Map<string, unknown>) => Map<string, string>;
14
- }): () => Transformer;
11
+ declare function includePlugin(): () => Transformer;
15
12
  export default includePlugin;
16
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAwCtC;;;;;;;;;GASG;AACH,iBAAS,aAAa,CAAC,qBAAqB,CAAC,EAAE;IAAE,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,SACjF,WAAW,CAuCnD;AAED,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAoCtC;;;;;;;;GAQG;AACH,iBAAS,aAAa,UACmB,WAAW,CAuBnD;AAED,eAAe,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ const INCLUDE_DIRECTIVE = 'include';
11
11
  const LEAF_DIRECTIVE_TYPE = 'leafDirective';
12
12
  const CONTENT_FOLDER = '/content/';
13
13
  const SHARED_PATH_SEGMENT = '/shared/';
14
+ const MEDIA_FOLDER = '/media';
14
15
  function getContentDirPath(curFilePath) {
15
16
  const position = curFilePath.indexOf(CONTENT_FOLDER);
16
17
  if (position !== -1) {
@@ -19,13 +20,8 @@ function getContentDirPath(curFilePath) {
19
20
  return '';
20
21
  }
21
22
  function getSubstringAfterMedia(folderPath) {
22
- const idx = folderPath.indexOf('/media');
23
- if (idx !== -1)
24
- return folderPath.substring(idx + 1);
25
- const idx2 = folderPath.indexOf('media');
26
- if (idx2 !== -1)
27
- return folderPath.substring(idx2);
28
- return folderPath;
23
+ const index = folderPath.indexOf(MEDIA_FOLDER);
24
+ return folderPath.substring(index);
29
25
  }
30
26
  function transformImageLinksInTree(root, contentDir) {
31
27
  if (!contentDir)
@@ -46,10 +42,9 @@ function transformImageLinksInTree(root, contentDir) {
46
42
  * with the included content. Must be used after remark-directive.
47
43
  * When the included file path contains /shared/, image URLs in that content are rewritten to absolute paths using the content directory.
48
44
  *
49
- * @param customRenderFunctions - Optional { renderInclude } for custom HTML rendering (same as genericDirective)
50
45
  * @returns Function that returns a Remark transformer
51
46
  */
52
- function includePlugin(customRenderFunctions) {
47
+ function includePlugin() {
53
48
  return function includePluginImpl() {
54
49
  const processor = this;
55
50
  return function transformer(tree, file) {
@@ -58,32 +53,16 @@ function includePlugin(customRenderFunctions) {
58
53
  if (node.type !== LEAF_DIRECTIVE_TYPE || node.name !== INCLUDE_DIRECTIVE || !parent)
59
54
  return;
60
55
  const src = (_a = node.attributes) === null || _a === void 0 ? void 0 : _a.src;
61
- if (!src) {
62
- file.fail('Include directive requires a src attribute', node);
63
- return;
64
- }
65
56
  const filename = path_1.default.resolve(file.dirname || '', src);
66
- if (!fs_1.default.existsSync(filename)) {
67
- file.fail(`File not found ${filename}`, node);
68
- return;
69
- }
70
57
  try {
71
58
  const content = fs_1.default.readFileSync(filename);
72
59
  const includeFile = (0, vfile_1.default)({ path: filename, contents: content });
73
- if (customRenderFunctions === null || customRenderFunctions === void 0 ? void 0 : customRenderFunctions.renderInclude) {
74
- const root = processor.processSync(includeFile);
75
- const metadata = new Map([['root', root], ['node', node]]);
76
- const out = customRenderFunctions.renderInclude(metadata).get('outputDom');
77
- parent.children.splice(i, 1, { type: 'html', value: out !== null && out !== void 0 ? out : '' });
78
- }
79
- else {
80
- let root = processor.parse(includeFile);
81
- if (filename.includes(SHARED_PATH_SEGMENT)) {
82
- transformImageLinksInTree(root, getContentDirPath(filename));
83
- }
84
- const { children } = root;
85
- parent.children.splice(i + 1, 0, ...(Array.isArray(children) ? children : []));
60
+ let root = processor.parse(includeFile);
61
+ if (filename.includes(SHARED_PATH_SEGMENT)) {
62
+ transformImageLinksInTree(root, getContentDirPath(filename));
86
63
  }
64
+ const { children } = root;
65
+ parent.children.splice(i + 1, 0, ...(Array.isArray(children) ? children : []));
87
66
  }
88
67
  catch (err) {
89
68
  file.fail((err === null || err === void 0 ? void 0 : err.message) || `Failed to read file ${filename}`, node);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/jest.config.js ADDED
@@ -0,0 +1,15 @@
1
+ const BASE_CONFIG = require('../../scripts/jest/common.config');
2
+
3
+ module.exports = {
4
+ ...BASE_CONFIG,
5
+ collectCoverageFrom: ['<rootDir>/**/src/*.{ts,js}'],
6
+ displayName: 'remark-include-plugin',
7
+ coveragePathIgnorePatterns: ['/node_modules/', '/dist/'],
8
+ testMatch: ['<rootDir>/**/__tests__/**/*.{test,spec}.ts'],
9
+ moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
10
+ preset: 'ts-jest',
11
+ testEnvironment: 'node',
12
+ transform: {
13
+ '^.+\\.ts$': 'ts-jest',
14
+ }
15
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
- "version": "0.0.4-alpha",
2
+ "version": "0.0.6-alpha",
3
3
  "name": "@salesforcedevs/sfdocs-remark-include-plugin",
4
- "description": "SFDocs remark plugin for the include directive",
4
+ "description": "SFDocs remark plugin for the include directive (::include)",
5
5
  "author": "SFDocs Team",
6
6
  "keywords": [
7
7
  "sfdocs-plugin",
@@ -9,11 +9,6 @@
9
9
  ],
10
10
  "license": "MIT",
11
11
  "main": "dist/index.js",
12
- "files": [
13
- "dist/*",
14
- "package.json",
15
- "README.md"
16
- ],
17
12
  "scripts": {
18
13
  "build": "yarn compile",
19
14
  "compile": "tsc -p tsconfig.json",
@@ -0,0 +1,108 @@
1
+ import stripIndent from 'strip-indent';
2
+ import vfile from 'vfile';
3
+ import unified from 'unified';
4
+ import remarkGfm from 'remark-gfm';
5
+ import parse from 'remark-parse';
6
+ import directive from 'remark-directive';
7
+ import remark2rehype from 'remark-rehype';
8
+ import html from 'rehype-stringify';
9
+ import rawHtml from 'rehype-raw';
10
+ import fs from 'fs';
11
+ import path from 'path';
12
+ import includePlugin from '../index';
13
+
14
+ const processMarkdown = (md: any): any => {
15
+ return unified()
16
+ .use(parse)
17
+ .use(directive)
18
+ .use(includePlugin())
19
+ .use(remark2rehype, { allowDangerousHtml: true })
20
+ .use(rawHtml)
21
+ .use(remarkGfm)
22
+ .use(html)
23
+ .processSync(md);
24
+ };
25
+
26
+ describe('Include plugin test', () => {
27
+ let testRoot = '';
28
+
29
+ beforeEach(() => {
30
+ testRoot = path.join(
31
+ process.cwd(),
32
+ `.tmp-remark-include-${Date.now()}-${Math.random().toString(16).slice(2)}`,
33
+ );
34
+ fs.mkdirSync(testRoot, { recursive: true });
35
+ });
36
+
37
+ afterEach(() => {
38
+ fs.rmSync(testRoot, { recursive: true, force: true });
39
+ jest.restoreAllMocks();
40
+ });
41
+
42
+ test('includes markdown content from a shared file', async () => {
43
+ fs.mkdirSync(path.join(testRoot, 'abc/xyz/guides'), { recursive: true });
44
+ fs.mkdirSync(path.join(testRoot, 'abc/xyz/shared'), { recursive: true });
45
+ fs.writeFileSync(
46
+ path.join(testRoot, 'abc/xyz/shared/test_include.md'),
47
+ stripIndent(`
48
+ ## Include file
49
+ shared content
50
+ `),
51
+ );
52
+ const content = stripIndent(`
53
+ ::include{src="../shared/test_include.md"}
54
+ `);
55
+ const file = vfile({ path: path.join(testRoot, 'abc/xyz/guides/parent.md'), contents: content });
56
+ const transformedHtml = await processMarkdown(file);
57
+
58
+ expect(transformedHtml.contents).toContain('<h2>Include file</h2>');
59
+ expect(transformedHtml.contents).toContain('<p>shared content</p>');
60
+ });
61
+
62
+ test('rewrites shared include media image URL to content media path', async () => {
63
+ fs.mkdirSync(path.join(testRoot, 'abc/xyz/content/guides'), { recursive: true });
64
+ fs.mkdirSync(path.join(testRoot, 'abc/xyz/content/shared'), { recursive: true });
65
+ fs.writeFileSync(
66
+ path.join(testRoot, 'abc/xyz/content/shared/test_include.md'),
67
+ stripIndent(`
68
+ ## Include file
69
+ shared content
70
+ ![Image file](../../media/test.jpeg)
71
+ `),
72
+ );
73
+ const content = stripIndent(`
74
+ ::include{src="../shared/test_include.md"}
75
+ `);
76
+ const file = vfile({
77
+ path: path.join(testRoot, 'abc/xyz/content/guides/parent.md'),
78
+ contents: content,
79
+ });
80
+ const transformedHtml = await processMarkdown(file);
81
+
82
+ expect(transformedHtml.contents).toContain('<h2>Include file</h2>');
83
+ expect(transformedHtml.contents).toContain(path.join(testRoot, 'abc/xyz/content/media/test.jpeg'));
84
+ });
85
+
86
+ test('rewrites localized shared include media image URL to root content media path', async () => {
87
+ fs.mkdirSync(path.join(testRoot, 'abc/content/ja-jp/guides'), { recursive: true });
88
+ fs.mkdirSync(path.join(testRoot, 'abc/content/ja-jp/shared'), { recursive: true });
89
+ fs.writeFileSync(
90
+ path.join(testRoot, 'abc/content/ja-jp/shared/test_include.md'),
91
+ stripIndent(`
92
+ ## Include file
93
+ shared content
94
+ ![Image file](../../media/test.jpeg)
95
+ `),
96
+ );
97
+ const content = stripIndent(`
98
+ ::include{src="../shared/test_include.md"}
99
+ `);
100
+ const file = vfile({
101
+ path: path.join(testRoot, 'abc/content/ja-jp/guides/parent.md'),
102
+ contents: content,
103
+ });
104
+ const transformedHtml = await processMarkdown(file);
105
+
106
+ expect(transformedHtml.contents).toContain(path.join(testRoot, 'abc/content/media/test.jpeg'));
107
+ });
108
+ });
package/src/index.ts ADDED
@@ -0,0 +1,73 @@
1
+ import visit from 'unist-util-visit';
2
+ import { Transformer } from 'unified';
3
+ import path from 'path';
4
+ import fs from 'fs';
5
+ import vfile, { VFile } from 'vfile';
6
+
7
+ const INCLUDE_DIRECTIVE = 'include';
8
+ const LEAF_DIRECTIVE_TYPE = 'leafDirective';
9
+ const CONTENT_FOLDER = '/content/';
10
+ const SHARED_PATH_SEGMENT = '/shared/';
11
+ const MEDIA_FOLDER = '/media';
12
+
13
+ function getContentDirPath(curFilePath: string): string {
14
+ const position = curFilePath.indexOf(CONTENT_FOLDER);
15
+ if (position !== -1) {
16
+ return curFilePath.slice(0, position + CONTENT_FOLDER.length);
17
+ }
18
+ return '';
19
+ }
20
+
21
+ function getSubstringAfterMedia(folderPath: string) {
22
+ const index = folderPath.indexOf(MEDIA_FOLDER);
23
+ return folderPath.substring(index);
24
+ }
25
+
26
+ function transformImageLinksInTree(root: any, contentDir: string): void {
27
+ if (!contentDir) return;
28
+ visit(root, 'image', (node: any) => {
29
+ const filePath = node.url;
30
+ const isRelativeUrl = !filePath?.match(/^(https?:\/\/|www\.).*/);
31
+ if (isRelativeUrl && filePath) {
32
+ const afterMedia = getSubstringAfterMedia(filePath);
33
+ node.url = path.join(contentDir, afterMedia);
34
+ }
35
+ });
36
+ }
37
+
38
+ /**
39
+ * SFDocs Include Plugin
40
+ * Processes include directives (::include{src="path/to/file.md"}) by reading the file,
41
+ * parsing or processing it through the unified pipeline, and replacing the directive
42
+ * with the included content. Must be used after remark-directive.
43
+ * When the included file path contains /shared/, image URLs in that content are rewritten to absolute paths using the content directory.
44
+ *
45
+ * @returns Function that returns a Remark transformer
46
+ */
47
+ function includePlugin() {
48
+ return function includePluginImpl(): Transformer {
49
+ const processor = this;
50
+ return function transformer(tree: any, file: VFile): void {
51
+ visit(tree, (node: any, i: number, parent: any) => {
52
+ if (node.type !== LEAF_DIRECTIVE_TYPE || node.name !== INCLUDE_DIRECTIVE || !parent) return;
53
+ const src = node.attributes?.src;
54
+ const filename = path.resolve(file.dirname || '', src);
55
+
56
+ try {
57
+ const content = fs.readFileSync(filename);
58
+ const includeFile = vfile({ path: filename, contents: content });
59
+ let root = processor.parse(includeFile);
60
+ if (filename.includes(SHARED_PATH_SEGMENT)) {
61
+ transformImageLinksInTree(root, getContentDirPath(filename));
62
+ }
63
+ const { children } = root;
64
+ parent.children.splice(i + 1, 0, ...(Array.isArray(children) ? children : []));
65
+ } catch (err: any) {
66
+ file.fail(err?.message || `Failed to read file ${filename}`, node);
67
+ }
68
+ });
69
+ };
70
+ };
71
+ }
72
+
73
+ export default includePlugin;
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.settings.json",
3
+ "moduleResolution": "node",
4
+ "compilerOptions": {
5
+ "rootDir": "src",
6
+ "outDir": "dist"
7
+ }
8
+ }