@salesforcedevs/sfdocs-remark-include-plugin 0.0.4-alpha → 0.0.5-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,21 @@
1
- # Remark Include plugin
1
+ # Remark Include Plugin
2
2
 
3
- ## About
3
+ ## Usage
4
+
5
+ Use with `remark-directive` in your unified pipeline:
4
6
 
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.
7
+ ## Examples
6
8
 
7
- Example:
9
+ Basic include:
8
10
 
9
- ```markdown
11
+ ```
10
12
  ::include{src="../shared/snippet.md"}
11
13
  ```
12
14
 
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:
15
+ Include with shared media rewrite:
18
16
 
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
17
  ```
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
- });
18
+ ::include{src="../shared/test_include.md"}
44
19
  ```
45
20
 
46
- ## Development
47
-
48
- - `yarn build` — compile TypeScript
49
- - `yarn test` — run tests
21
+ If the included file path contains `/shared/`, relative media links inside that file are rewritten to content-root absolute 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":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAwCtC;;;;;;;;GAQG;AACH,iBAAS,aAAa,UACmB,WAAW,CAgCnD;AAED,eAAe,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const unist_util_visit_1 = __importDefault(require("unist-util-visit"));
7
+ const hastscript_1 = __importDefault(require("hastscript"));
7
8
  const path_1 = __importDefault(require("path"));
8
9
  const fs_1 = __importDefault(require("fs"));
9
10
  const vfile_1 = __importDefault(require("vfile"));
@@ -46,10 +47,9 @@ function transformImageLinksInTree(root, contentDir) {
46
47
  * with the included content. Must be used after remark-directive.
47
48
  * When the included file path contains /shared/, image URLs in that content are rewritten to absolute paths using the content directory.
48
49
  *
49
- * @param customRenderFunctions - Optional { renderInclude } for custom HTML rendering (same as genericDirective)
50
50
  * @returns Function that returns a Remark transformer
51
51
  */
52
- function includePlugin(customRenderFunctions) {
52
+ function includePlugin() {
53
53
  return function includePluginImpl() {
54
54
  const processor = this;
55
55
  return function transformer(tree, file) {
@@ -58,10 +58,6 @@ function includePlugin(customRenderFunctions) {
58
58
  if (node.type !== LEAF_DIRECTIVE_TYPE || node.name !== INCLUDE_DIRECTIVE || !parent)
59
59
  return;
60
60
  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
61
  const filename = path_1.default.resolve(file.dirname || '', src);
66
62
  if (!fs_1.default.existsSync(filename)) {
67
63
  file.fail(`File not found ${filename}`, node);
@@ -70,20 +66,17 @@ function includePlugin(customRenderFunctions) {
70
66
  try {
71
67
  const content = fs_1.default.readFileSync(filename);
72
68
  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 : []));
69
+ let root = processor.parse(includeFile);
70
+ if (filename.includes(SHARED_PATH_SEGMENT)) {
71
+ transformImageLinksInTree(root, getContentDirPath(filename));
86
72
  }
73
+ const { children } = root;
74
+ const data = node.data || (node.data = {});
75
+ const hast = (0, hastscript_1.default)(INCLUDE_DIRECTIVE, node.attributes || {});
76
+ // node.type = 'containerDirective';
77
+ node.children = Array.isArray(children) ? children : [];
78
+ data.hName = INCLUDE_DIRECTIVE;
79
+ data.hProperties = hast.properties;
87
80
  }
88
81
  catch (err) {
89
82
  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.5-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,105 @@
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('Include from shared folder testing', 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
+ expect(transformedHtml.contents).toContain('<h2>Include file</h2>');
58
+ expect(transformedHtml.contents).toContain('<p>shared content</p>');
59
+ });
60
+
61
+ test('Include from shared folder with media image (rewrites image URL)', async () => {
62
+ fs.mkdirSync(path.join(testRoot, 'abc/xyz/content/guides'), { recursive: true });
63
+ fs.mkdirSync(path.join(testRoot, 'abc/xyz/content/shared'), { recursive: true });
64
+ fs.writeFileSync(
65
+ path.join(testRoot, 'abc/xyz/content/shared/test_include.md'),
66
+ stripIndent(`
67
+ ## Include file
68
+ shared content
69
+ ![Image file](../../media/test.jpeg)
70
+ `),
71
+ );
72
+ const content = stripIndent(`
73
+ ::include{src="../shared/test_include.md"}
74
+ `);
75
+ const file = vfile({
76
+ path: path.join(testRoot, 'abc/xyz/content/guides/parent.md'),
77
+ contents: content,
78
+ });
79
+ const transformedHtml = await processMarkdown(file);
80
+ expect(transformedHtml.contents).toContain('<h2>Include file</h2>');
81
+ expect(transformedHtml.contents).toContain(path.join(testRoot, 'abc/xyz/content/media/test.jpeg'));
82
+ });
83
+
84
+ test('Include from shared folder with media image in locale (rewrites image URL)', async () => {
85
+ fs.mkdirSync(path.join(testRoot, 'abc/content/ja-jp/guides'), { recursive: true });
86
+ fs.mkdirSync(path.join(testRoot, 'abc/content/ja-jp/shared'), { recursive: true });
87
+ fs.writeFileSync(
88
+ path.join(testRoot, 'abc/content/ja-jp/shared/test_include.md'),
89
+ stripIndent(`
90
+ ## Include file
91
+ shared content
92
+ ![Image file](../../media/test.jpeg)
93
+ `),
94
+ );
95
+ const content = stripIndent(`
96
+ ::include{src="../shared/test_include.md"}
97
+ `);
98
+ const file = vfile({
99
+ path: path.join(testRoot, 'abc/content/ja-jp/guides/parent.md'),
100
+ contents: content,
101
+ });
102
+ const transformedHtml = await processMarkdown(file);
103
+ expect(transformedHtml.contents).toContain(path.join(testRoot, 'abc/content/media/test.jpeg'));
104
+ });
105
+ });
package/src/index.ts ADDED
@@ -0,0 +1,87 @@
1
+ import visit from 'unist-util-visit';
2
+ import h from 'hastscript';
3
+ import { Transformer } from 'unified';
4
+ import path from 'path';
5
+ import fs from 'fs';
6
+ import vfile, { VFile } from 'vfile';
7
+
8
+ const INCLUDE_DIRECTIVE = 'include';
9
+ const LEAF_DIRECTIVE_TYPE = 'leafDirective';
10
+ const CONTENT_FOLDER = '/content/';
11
+ const SHARED_PATH_SEGMENT = '/shared/';
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): string {
22
+ const idx = folderPath.indexOf('/media');
23
+
24
+ if (idx !== -1) return folderPath.substring(idx + 1);
25
+ const idx2 = folderPath.indexOf('media');
26
+
27
+ if (idx2 !== -1) return folderPath.substring(idx2);
28
+ return folderPath;
29
+ }
30
+
31
+ function transformImageLinksInTree(root: any, contentDir: string): void {
32
+ if (!contentDir) return;
33
+ visit(root, 'image', (node: any) => {
34
+ const filePath = node.url;
35
+ const isRelativeUrl = !filePath?.match(/^(https?:\/\/|www\.).*/);
36
+ if (isRelativeUrl && filePath) {
37
+ const afterMedia = getSubstringAfterMedia(filePath);
38
+ node.url = path.join(contentDir, afterMedia);
39
+ }
40
+ });
41
+ }
42
+
43
+ /**
44
+ * SFDocs Include Plugin
45
+ * Processes include directives (::include{src="path/to/file.md"}) by reading the file,
46
+ * parsing or processing it through the unified pipeline, and replacing the directive
47
+ * with the included content. Must be used after remark-directive.
48
+ * When the included file path contains /shared/, image URLs in that content are rewritten to absolute paths using the content directory.
49
+ *
50
+ * @returns Function that returns a Remark transformer
51
+ */
52
+ function includePlugin() {
53
+ return function includePluginImpl(): Transformer {
54
+ const processor = this;
55
+ return function transformer(tree: any, file: VFile): void {
56
+ visit(tree, (node: any, i: number, parent: any) => {
57
+ if (node.type !== LEAF_DIRECTIVE_TYPE || node.name !== INCLUDE_DIRECTIVE || !parent) return;
58
+ const src = node.attributes?.src;
59
+ const filename = path.resolve(file.dirname || '', src);
60
+ if (!fs.existsSync(filename)) {
61
+ file.fail(`File not found ${filename}`, node);
62
+ return;
63
+ }
64
+
65
+ try {
66
+ const content = fs.readFileSync(filename);
67
+ const includeFile = vfile({ path: filename, contents: content });
68
+ let root = processor.parse(includeFile);
69
+ if (filename.includes(SHARED_PATH_SEGMENT)) {
70
+ transformImageLinksInTree(root, getContentDirPath(filename));
71
+ }
72
+ const { children } = root;
73
+ const data = node.data || (node.data = {});
74
+ const hast = h(INCLUDE_DIRECTIVE, node.attributes || {});
75
+ // node.type = 'containerDirective';
76
+ node.children = Array.isArray(children) ? children : [];
77
+ data.hName = INCLUDE_DIRECTIVE;
78
+ data.hProperties = hast.properties;
79
+ } catch (err: any) {
80
+ file.fail(err?.message || `Failed to read file ${filename}`, node);
81
+ }
82
+ });
83
+ };
84
+ };
85
+ }
86
+
87
+ 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
+ }