diff --git a/.eslintignore b/.eslintignore index e426d6c1d..cbe2fbd47 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,3 @@ +node_modules coverage/ bundlesize/ diff --git a/.eslintrc.js b/.eslintrc.js index e7542dc4e..6b594419a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,7 +2,13 @@ module.exports = { extends: ['@open-wc/eslint-config', 'eslint-config-prettier'].map(require.resolve), overrides: [ { - files: ['**/test-suites/**/*.js', '**/test/**/*.js', '**/stories/**/*.js', '**/*.config.js'], + files: [ + '**/test-suites/**/*.js', + '**/test/**/*.js', + '**/test-node/**/*.js', + '**/stories/**/*.js', + '**/*.config.js', + ], rules: { 'no-console': 'off', 'no-unused-expressions': 'off', diff --git a/package.json b/package.json index b339dd602..15aa301be 100644 --- a/package.json +++ b/package.json @@ -21,12 +21,14 @@ "babel-eslint": "^8.2.6", "babel-polyfill": "^6.26.0", "bundlesize": "^0.17.1", + "chai": "^4.2.0", "eclint": "^2.8.1", "eslint": "^6.1.0", "husky": "^1.0.0", "lerna": "3.4.3", "lint-staged": "^8.0.0", "markdownlint-cli": "^0.17.0", + "mocha": "^7.1.1", "npm-run-all": "^4.1.5", "rimraf": "^2.6.3", "rollup": "^1.27.8", @@ -38,7 +40,9 @@ "start": "npm run storybook", "storybook": "start-storybook -p 9001", "storybook:build": "build-storybook", - "test": "karma start --coverage", + "test": "run-p test:browser test:node", + "test:browser": "karma start --coverage", + "test:node": "lerna run test:node", "test:watch": "karma start --auto-watch=true --single-run=false", "test:update-snapshots": "karma start --update-snapshots", "test:prune-snapshots": "karma start --prune-snapshots", diff --git a/packages/remark-extend/README.md b/packages/remark-extend/README.md new file mode 100644 index 000000000..0499b5538 --- /dev/null +++ b/packages/remark-extend/README.md @@ -0,0 +1,322 @@ +# remark-extend + +A plugin for remark to extend one markdown file with another. + +`remark-extend` is build to be integrated within the [unifiedjs](https://unifiedjs.com/) system. + +## Installation + +```bash +npm i -D remark-extend +``` + +```js +const unified = require('unified'); +const markdown = require('remark-parse'); +const mdStringify = require('remark-html'); + +const { remarkExtend } = require('remark-extend'); + +const sourceMd = '# Headline'; +const extendMd = 'extending instructions'; + +const parser = unified() + .use(markdown) + .use(remarkExtend, { extendMd }) + .use(mdStringify); +const result = await parser.process(sourceMd); +``` + +## Extending Instructions + +### Selection + +For modifications you will need to provide a starting node. +In order to get this node css like selectors from [unist-util-select](https://github.com/syntax-tree/unist-util-select#support) are supported. + +Some examples are: + +- `:root` for the top of the markdown file +- `:scope:last-child` for the end of the markdown file +- `heading:has([value=Red])` first heading with a text value of Red (e.g. ### Red) +- `heading[depth=2]` first third level heading (e.g. ## Something) + +### Markdown AST + +All adjustments to the markdown file happen via the markdown AST (Abstract Syntax Tree). + +You can explore it via the [ASTExplorer](https://astexplorer.net/). + +```md +### Red + +red is the fire +``` + +Resulting AST. + +```json +{ + "type": "root", + "children": [ + { + "type": "heading", + "depth": 3, + "children": [ + { + "type": "text", + "value": "Red" + } + ] + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "red is the fire" + } + ] + } + ] +} +``` + +--- + +### Replacement + +Does adjustments from a start point downwards. +The provided function does get called for every node from the starting point (including the starting point). + +#### Replacement Input File + +```md +### Red + +red is the fire +``` + +#### Replacement Extending File + +Goal is to replace all red with green. + +````md +```js ::replaceFrom(':root') +module.exports.replaceSection = node => { + if (node.value) { + node.value = node.value.replace(/red/g, 'green').replace(/Red/g, 'Green'); + } + return node; +}; +``` +```` + +This function gets called with these nodes in order. + +1. root +2. heading +3. text +4. paragraph +5. text + +#### Replacement Result + +```md +### Green + +green is the fire +``` + +### Replacement Range + +Whenever a section or part of the original markdown needs to be adjusted. +The function does get every node from the starting point (including the starting point) till the end point (excluding the end point). + +#### Replacement Range Input File + +```md +### Red <-- starting point (including) + +red is the fire + +### More Red <-- end point (excluding) + +the sun can get red +``` + +#### Replacement Range Extending File + +````md +```js ::replaceBetween('heading:has([value=Red])', 'heading:has([value=More Red])') +module.exports.replaceSection = node => { + if (node.value) { + node.value = node.value.replace(/red/g, 'green').replace(/Red/g, 'Green'); + } + return node; +}; +``` +```` + +#### Replacement Range Result + +```md +### Green <-- starting point (including) + +green is the fire + +### More Red <-- end point (excluding) + +the sun can get red +``` + +--- + +### Add More Markdown Content After + +If additional markdown content should be inserted after at a specific location. + +#### Add After Input File + +```md +### Red + +red is the fire +``` + +#### Add After Extending File + +````md +``` +::addMdAfter('heading:has([value=Red])') +``` + +the ocean is blue +```` + +#### Add After Result + +```md +### Red + +the ocean is blue +red is the fire +``` + +More example use cases: + +- Add more markdown at the top `::addMdAfter(':root')` +- Add more markdown at the bottom `::addMdAfter(':scope:last-child')` + +### Add More Markdown Content Before + +If additional markdown content should be inserted before at a specific location. +Useful for adding disclaimers above a headline. + +#### Add Before Input File + +```md +### Red + +red is the fire + +### Green <-- staring point +``` + +#### Add Before Extending File + +````md +``` +::addMdBefore('heading:has([value=Red])') +``` + +the ocean is blue +```` + +#### Add Before Result + +```md +### Red + +red is the fire + +the ocean is blue + +### Green <-- staring point +``` + +More example use cases: + +- Add something at the end of a "section": `::addMdBefore('heading:has([value=Red]) ~ heading[depth=3]')` + It works by selecting the headline of your section and add before the next sibling headline with the same depth. + +--- + +### Removal + +Does adjustments from a start point downwards. +The provided function does get called for every node from the starting point (including the starting point). + +#### Removal Input File + +```md +### Red + +red is the fire + +### More Red // <-- start + +the sun can get red +``` + +#### Removal Extending File + +Goal is to remove everything after `### More Red` + +````md +``` +::removeFrom('heading:has([value=More Red])') +``` +```` + +#### Removal Result + +```md +### Red + +red is the fire +``` + +### Removal Range + +Whenever a section or part of the original markdown needs to be removed. + +#### Removal Range Input File + +```md +### Red <-- starting point (including) + +red is the fire + +### More Red <-- end point (excluding) + +the sun can get red +``` + +#### Removal Range Extending File + +Starting from `### Red` until the next headline with depth of 3. + +````md +``` +::removeBetween('heading:has([value=Red])', 'heading:has([value=Red]) ~ heading[depth=3]') +``` +```` + +#### Removal Range Result + +```md +### More Red <-- end point (excluding) + +the sun can get red +``` diff --git a/packages/remark-extend/package.json b/packages/remark-extend/package.json new file mode 100644 index 000000000..8a6f91fe2 --- /dev/null +++ b/packages/remark-extend/package.json @@ -0,0 +1,43 @@ +{ + "name": "remark-extend", + "version": "0.0.0", + "description": "A plugin for remark that allows to extend a md file with another md file", + "author": "ing-bank", + "homepage": "https://github.com/ing-bank/lion/", + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/ing-bank/lion.git", + "directory": "packages/remark-extend" + }, + "scripts": { + "prepublishOnly": "../../scripts/npm-prepublish.js", + "test": "npm run test:node", + "test:node": "mocha test-node", + "test:watch": "mocha test-node --watch" + }, + "keywords": [ + "remark" + ], + "main": "index.js", + "files": [ + "docs", + "src", + "stories", + "test", + "*.js" + ], + "dependencies": { + "remark-parse": "^8.0.0", + "unified": "^9.0.0", + "unist-util-is": "^4.0.2", + "unist-util-select": "^3.0.1", + "unist-util-visit": "^2.0.2" + }, + "devDependencies": { + "remark-html": "^11.0.1" + } +} diff --git a/packages/remark-extend/src/remarkExtend.js b/packages/remark-extend/src/remarkExtend.js new file mode 100644 index 000000000..7b1f133d0 --- /dev/null +++ b/packages/remark-extend/src/remarkExtend.js @@ -0,0 +1,305 @@ +// unified works my modifying the original passed node +/* eslint-disable no-param-reassign */ +const visit = require('unist-util-visit'); +const { select } = require('unist-util-select'); +const unified = require('unified'); +const markdown = require('remark-parse'); +const is = require('unist-util-is'); + +function addTask(file, newAction) { + if (!file.data.remarkExtend) { + file.data.remarkExtend = []; + } + file.data.remarkExtend.push(newAction); +} + +function findReplacementTasks(tree, file) { + visit(tree, 'code', node => { + if (node.lang === 'js' && node.meta && node.meta.startsWith('::replaceFrom')) { + const startSelector = node.meta.substring( + node.meta.indexOf("('") + 2, + node.meta.indexOf("')"), + ); + addTask(file, { + action: 'replaceFrom', + startSelector, + jsCode: node.value, + }); + } + if (node.lang === 'js' && node.meta && node.meta.startsWith('::replaceBetween')) { + const startSelector = node.meta.substring( + node.meta.indexOf("('") + 2, + node.meta.indexOf("',"), + ); + const endSelector = node.meta + .substring(node.meta.indexOf("',") + 4, node.meta.indexOf("')")) + .trim(); + addTask(file, { + action: 'replaceBetween', + startSelector, + endSelector, + jsCode: node.value, + }); + } + }); +} + +function shouldFinishGathering(node) { + if (node.type === 'code' && node.lang === 'js' && node.meta && node.meta.startsWith('::')) { + return true; + } + if (node.value && node.value.startsWith('::')) { + return true; + } + return false; +} + +function findMdAdditionTasks(tree, file) { + let addNodes = []; + let gathering = false; + let startSelector; + let action = ''; + visit(tree, (node, index, parent) => { + if (gathering === true && shouldFinishGathering(node)) { + gathering = false; + addTask(file, { + action, + startSelector, + addNodes, + }); + addNodes = []; + } + + if (gathering === true) { + if (parent.type === 'root') { + addNodes.push(node); + } + } + + if (node.type === 'code' && node.value && node.value.startsWith('::addMdAfter')) { + startSelector = node.value.substring(node.value.indexOf("('") + 2, node.value.indexOf("')")); + gathering = true; + action = 'addMdAfter'; + } + if (node.type === 'code' && node.value && node.value.startsWith('::addMdBefore')) { + startSelector = node.value.substring(node.value.indexOf("('") + 2, node.value.indexOf("')")); + gathering = true; + action = 'addMdBefore'; + } + }); + + if (gathering === true) { + addTask(file, { + action, + startSelector, + addNodes, + }); + } +} + +function findRemoveTasks(tree, file) { + visit(tree, 'code', node => { + if (node.value && node.value.startsWith('::removeFrom')) { + const startSelector = node.value.substring( + node.value.indexOf("('") + 2, + node.value.indexOf("')"), + ); + addTask(file, { + action: 'removeFrom', + startSelector, + }); + } + if (node.value && node.value.startsWith('::removeBetween')) { + const startSelector = node.value.substring( + node.value.indexOf("('") + 2, + node.value.indexOf("',"), + ); + const endSelector = node.value + .substring(node.value.indexOf("',") + 4, node.value.indexOf("')")) + .trim(); + addTask(file, { + action: 'removeBetween', + startSelector, + endSelector, + }); + } + }); +} + +function findExtendTasks() { + return (tree, file) => { + findReplacementTasks(tree, file); + findMdAdditionTasks(tree, file); + findRemoveTasks(tree, file); + }; +} + +/** + * Allows to execute an actual node module code block. + * Supports imports (via require) within those code blocks. + * + * @example + * const virtualMod = requireFromString('module.export = { a: "a value", fn: () => {} }'); + * console.log(virtualMod.a); // a value + * // execute function + * virtualMod.fn(); + * + * @param {*} src + * @param {*} filename + */ +function requireFromString(src, filename = 'tmp.js') { + const m = new module.constructor(); + m.paths = module.paths; + m._compile(src, filename); + return m.exports; +} + +function handleAdditions(tree, action, startIsNode, addNodes) { + visit(tree, (node, index, parent) => { + if (is(node, startIsNode)) { + if (action === 'addMdAfter') { + if (node.type === 'root') { + node.children.splice(0, 0, ...addNodes); + } else { + parent.children.splice(index + 1, 0, ...addNodes); + } + } + if (action === 'addMdBefore') { + if (node.remarkExtendedProcessed === undefined) { + // preventing infinite loops as adding a node before means we visit the target node again and insert again + node.remarkExtendedProcessed = true; + if (node.type === 'root') { + node.children.splice(0, 0, ...addNodes); + } else { + parent.children.splice(index, 0, ...addNodes); + } + } + } + } + }); +} + +function handleReplacements(tree, action, startIsNode, endIsNode, jsCode) { + let doReplacements = false; + let resetAtEnd = false; + let userFunction; + if (action === 'replaceFrom' || action === 'replaceBetween') { + const virtualMod = requireFromString(jsCode); + const keys = Object.keys(virtualMod); + userFunction = virtualMod[keys[0]]; + } + visit(tree, (node, index, parent) => { + if (is(node, startIsNode)) { + doReplacements = true; + } + if (action === 'replaceBetween' && is(node, endIsNode)) { + resetAtEnd = true; + } + if (doReplacements) { + node = userFunction(node, { index, parent, tree }); + } + if (resetAtEnd === true) { + resetAtEnd = false; + doReplacements = false; + } + }); +} + +/** + * Needs 2 loops as + * - First to mark nodes for removal + * - Do actual removal in revers order (to not effect the index of the loop) + * + * @param {*} tree + * @param {*} action + * @param {*} startIsNode + * @param {*} endIsNode + */ +function handleRemovals(tree, action, startIsNode, endIsNode) { + let removeIt = false; + visit(tree, (node, index, parent) => { + if (is(node, startIsNode)) { + removeIt = true; + } + if (action === 'removeBetween' && is(node, endIsNode)) { + removeIt = false; + } + if (removeIt && parent.type === 'root') { + // only mark for removal + // removing directly messes with the index which prevents further removals down the line + parent.children[index].__remarkExtendRemove = true; + } + }); + visit( + tree, + (node, index, parent) => { + if (node.__remarkExtendRemove) { + parent.children.splice(index, 1); + } + }, + true, + ); +} + +// unified expect direct +// eslint-disable-next-line consistent-return +function remarkExtend(options) { + if (options.extendMd) { + const parser = unified() + .use(markdown) + .use(findExtendTasks) + .use(function plugin() { + this.Compiler = () => ''; + }); + + const changes = parser.processSync(options.extendMd); + + const extensionTasks = changes.data.remarkExtend; + + if (!extensionTasks) { + return tree => tree; + } + + return tree => { + for (const extensionTask of extensionTasks) { + const { action, startSelector, endSelector, jsCode, addNodes } = extensionTask; + const start = select(extensionTask.startSelector, tree); + if (!start) { + throw new Error(`The start selector "${startSelector}" could not find a matching node.`); + } + const startIsNode = { ...start }; + delete startIsNode.children; // unified is comparison does not support children + + let endIsNode; + if (action === 'replaceBetween' || action === 'removeBetween') { + const end = select(endSelector, tree); + if (!end) { + throw new Error(`The end selector "${endSelector}" could not find a matching node.`); + } + endIsNode = { ...end }; + delete endIsNode.children; // unified is comparison does not support children + } + + switch (action) { + case 'addMdAfter': + case 'addMdBefore': + handleAdditions(tree, action, startIsNode, addNodes); + break; + case 'replaceFrom': + case 'replaceBetween': + handleReplacements(tree, action, startIsNode, endIsNode, jsCode); + break; + case 'removeFrom': + case 'removeBetween': + handleRemovals(tree, action, startIsNode, endIsNode); + break; + /* no default */ + } + } + }; + } +} + +module.exports = { + remarkExtend, +}; diff --git a/packages/remark-extend/test-node/remark-extend.test.js b/packages/remark-extend/test-node/remark-extend.test.js new file mode 100644 index 000000000..a4bf19035 --- /dev/null +++ b/packages/remark-extend/test-node/remark-extend.test.js @@ -0,0 +1,628 @@ +const { expect } = require('chai'); + +const unified = require('unified'); +const markdown = require('remark-parse'); +const mdStringify = require('remark-html'); + +const { remarkExtend } = require('../src/remarkExtend.js'); + +async function expectThrowsAsync(method, errorMessage) { + let error = null; + try { + await method(); + } catch (err) { + error = err; + } + expect(error).to.be.an('Error', 'No error was thrown'); + if (errorMessage) { + expect(error.message).to.equal(errorMessage); + } +} + +describe('remarkExtend', () => { + it('does no modifications if no action is found', async () => { + const input = [ + '### Red', + '', + 'red is the fire', + '', + '#### More Red', + '', + 'the sun can get red', + ].join('\n'); + const extendMd = ''; + const output = [ + '
red is the fire
', + 'the sun can get red
', + '', + ].join('\n'); + + const parser = unified() + // + .use(markdown) + .use(remarkExtend, { extendMd }) + .use(mdStringify); + const result = await parser.process(input); + + expect(result.contents).to.equal(output); + }); + + it('can do replacements on the full file', async () => { + const input = [ + '### Red', // <-- start + '', + 'red is the fire', + '', + '#### More Red', + '', + 'the sun can get red', + ].join('\n'); + const extendMd = [ + // + "```js ::replaceFrom(':root')", + 'module.exports.replaceSection = (node) => {', + ' if (node.value) {', + " node.value = node.value.replace(/red/g, 'green').replace(/Red/g, 'Green');", + ' }', + ' return node;', + '};', + '```', + ].join('\n'); + const output = [ + 'green is the fire
', + 'the sun can get green
', + '', + ].join('\n'); + + const parser = unified() + // + .use(markdown) + .use(remarkExtend, { extendMd }) + .use(mdStringify); + const result = await parser.process(input); + + expect(result.contents).to.equal(output); + }); + + it('does replacements in order of extendMd', async () => { + const input = [ + '### Red', // <-- start + '', + 'red is the fire', + ].join('\n'); + const extendMd = [ + "```js ::replaceFrom(':root')", + 'module.exports.replaceSection = (node) => {', + ' if (node.value) {', + " node.value = node.value.replace(/red/g, 'green').replace(/Red/g, 'Green');", + ' }', + ' return node;', + '};', + '```', + "```js ::replaceFrom(':root')", + 'module.exports.replaceSection = (node) => {', + ' if (node.value) {', + " node.value = node.value.replace(/green/g, 'yellow').replace(/Green/g, 'Yellow');", + ' }', + ' return node;', + '};', + '```', + ].join('\n'); + const output = [ + 'yellow is the fire
', + '', + ].join('\n'); + + const parser = unified() + // + .use(markdown) + .use(remarkExtend, { extendMd }) + .use(mdStringify); + const result = await parser.process(input); + + expect(result.contents).to.equal(output); + }); + + it('can replace from a starting point downward', async () => { + const input = [ + '### Red', + '', + 'red is the fire', + '', + '### More Red', // <-- start + '', + 'the sun can get red', + ].join('\n'); + const extendMd = [ + "```js ::replaceFrom('heading[depth=3]:has([value=More Red])')", + 'module.exports.replaceSection = (node) => {', + ' if (node.value) {', + " node.value = node.value.replace(/red/g, 'green').replace(/Red/g, 'Green');", + ' }', + ' return node;', + '};', + '```', + ].join('\n'); + const output = [ + 'red is the fire
', + 'the sun can get green
', + '', + ].join('\n'); + + const parser = unified() + // + .use(markdown) + .use(remarkExtend, { extendMd }) + .use(mdStringify); + const result = await parser.process(input); + + expect(result.contents).to.equal(output); + }); + + it('throws if a start selector is not found', async () => { + const input = [ + // + '### Red', + ].join('\n'); + const extendMd = [ + "```js ::replaceFrom('heading:has([value=More Red])')", + 'module.exports.replaceSection = (node) => {}', + '```', + ].join('\n'); + + const parser = unified() + // + .use(markdown) + .use(remarkExtend, { extendMd }) + .use(mdStringify); + + await expectThrowsAsync( + () => parser.process(input), + 'The start selector "heading:has([value=More Red])" could not find a matching node.', + ); + }); + + it('can replace within a range (start point included, endpoint not)', async () => { + const input = [ + '### Red', + '', + 'red is the fire', + '', + 'red is the cross', // <-- start + '', + 'red is the flag', + '', + '#### More Red', + '', + 'the sun can get red', + ].join('\n'); + const extendMd = [ + "```js ::replaceBetween('heading:has([value=Red]) ~ paragraph:nth-of-type(2)', 'heading:has([value=More Red])')", + 'module.exports.replaceSection = (node) => {', + ' if (node.value) {', + " node.value = node.value.replace(/red/g, 'green').replace(/Red/g, 'Green');", + ' }', + ' return node', + '};', + ].join('\n'); + const output = [ + 'red is the fire
', + 'green is the cross
', // <-- start + 'green is the flag
', + 'the sun can get red
', + '', + ].join('\n'); + const parser = unified() + // + .use(markdown) + .use(remarkExtend, { extendMd }) + .use(mdStringify); + const result = await parser.process(input); + + expect(result.contents).to.equal(output); + }); + + it('throws if a end selector is not found', async () => { + const input = [ + // + '### Red', + ].join('\n'); + const extendMd = [ + "```js ::replaceBetween('heading:has([value=Red])', 'heading:has([value=Red2])')", + 'module.exports.replaceSection = (node) => {}', + '```', + ].join('\n'); + + const parser = unified() + .use(markdown) + .use(remarkExtend, { extendMd }) + .use(mdStringify); + + await expectThrowsAsync( + () => parser.process(input), + 'The end selector "heading:has([value=Red2])" could not find a matching node.', + ); + }); + + it('replaces a single node if replacing between the start and end of the same node', async () => { + const input = [ + // + '### Red', + 'red', + '### Red', + ].join('\n'); + const extendMd = [ + "```js ::replaceBetween('heading:has([value=Red]) > text', 'heading:has([value=Red]) > text')", + 'module.exports.replaceSection = (node) => {', + ' if (node.value) {', + " node.value = node.value.replace(/red/g, 'green').replace(/Red/g, 'Green');", + ' }', + ' return node', + '};', + '```', + ].join('\n'); + const output = [ + 'red
', + 'the ocean is blue
', + 'red is the fire
', + '', + ].join('\n'); + const parser = unified() + // + .use(markdown) + .use(remarkExtend, { extendMd }) + .use(mdStringify); + const result = await parser.process(input); + + expect(result.contents).to.equal(output); + }); + + it('adding stops at the next ::[action]', async () => { + const input = [ + // + '### Red', + '', + 'red is the fire', + '', + '### More Red', + ].join('\n'); + const extendMd = [ + '```', + "::addMdAfter('heading:has([value=Red])')", + '```', + '', + 'the ocean is blue', + '', + '```', + "::addMdAfter('heading:has([value=More Red])')", + '```', + '', + 'as in the sun is the ultimate red', + '', + "```js ::replaceBetween('heading:has([value=Red])', 'heading:has([value=Red])')", + 'module.exports.replaceSection = (node) => {}', + '```', + 'content not part of an add so it gets ignored', + ].join('\n'); + const output = [ + // + 'the ocean is blue
', + 'red is the fire
', + 'as in the sun is the ultimate red
', + '', + ].join('\n'); + const parser = unified() + // + .use(markdown) + .use(remarkExtend, { extendMd }) + .use(mdStringify); + const result = await parser.process(input); + + expect(result.contents).to.equal(output); + }); + + it('will throw if trying to immediate run replacements of added content', async () => { + const input = [ + // + '### Red', + '', + ].join('\n'); + const extendMd = [ + '```', + "::addMdAfter('heading:has([value=Red])')", + '```', + '', + '## Blue', + '', + "```js ::replaceFrom('heading:has([value=Blue])')", + 'module.exports.replaceSection = (node) => {', + ' if (node.value) {', + " node.value = node.value.replace(/Blue/g, 'Yellow');", + ' }', + ' return node', + '}', + '```', + ].join('\n'); + const parser = unified() + // + .use(markdown) + .use(remarkExtend, { extendMd }) + .use(mdStringify); + + await expectThrowsAsync( + () => parser.process(input), + 'The start selector "heading:has([value=Blue])" could not find a matching node.', + ); + }); + + it(`can put something right at the top via "::addMdAfter(':root')"`, async () => { + const input = [ + // + '### Red', + ].join('\n'); + const extendMd = [ + // + '```', + "::addMdAfter(':root')", + '```', + '', + '# New Headline', + ].join('\n'); + const output = [ + // + 'red is the fire
', + 'extra text
', + '', + ].join('\n'); + const parser = unified() + // + .use(markdown) + .use(remarkExtend, { extendMd }) + .use(mdStringify); + const result = await parser.process(input); + + expect(result.contents).to.equal(output); + }); + + it('can put something before via "::addMdBefore"', async () => { + const input = [ + // + '### Red', + '', + 'red is the fire', + '', + '### Blue', + ].join('\n'); + const extendMd = [ + '```', + "::addMdBefore('heading:has([value=Blue])')", + '```', + '', + 'the ocean is blue', + ].join('\n'); + const output = [ + // + 'red is the fire
', + 'the ocean is blue
', + 'red is the fire
', + 'the sun will be red
', + 'the ocean is blue
', + 'red is the fire
', + 'red is the fire
', + '', + ].join('\n'); + + const parser = unified() + // + .use(markdown) + .use(remarkExtend, { extendMd }) + .use(mdStringify); + const result = await parser.process(input); + + expect(result.contents).to.equal(output); + }); + + it('can remove a range (start point included, endpoint not)', async () => { + const input = [ + '### Red', // <-- start + '', + 'red is the fire', + '', + '### More Red', // <-- end + '', + 'the sun can get red', + ].join('\n'); + const extendMd = [ + '```', + `::removeBetween('heading:has([value=Red])', 'heading:has([value=Red]) ~ heading[depth=3]')`, + '```', + ].join('\n'); + const output = [ + 'the sun can get red
', + '', + ].join('\n'); + const parser = unified() + // + .use(markdown) + .use(remarkExtend, { extendMd }) + .use(mdStringify); + const result = await parser.process(input); + + expect(result.contents).to.equal(output); + }); +}); diff --git a/yarn.lock b/yarn.lock index 31186de7e..292e80973 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3933,6 +3933,11 @@ body-parser@^1.16.1: raw-body "2.4.0" type-is "~1.6.17" +boolbase@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= + brace-expansion@^1.0.0, brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -4355,7 +4360,7 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -ccount@^1.0.3: +ccount@^1.0.0, ccount@^1.0.3: version "1.0.5" resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.5.tgz#ac82a944905a65ce204eb03023157edf29425c17" integrity sha512-MOli1W+nfbPLlKEhInaxhRdp7KVLFxLN5ykwzHgLsLI3H3gs5jjFAK4Eoj3OzzcxCtumDaI8onoVDeQyWaNTkw== @@ -4433,6 +4438,11 @@ change-emitter@^0.1.2: resolved "https://registry.yarnpkg.com/change-emitter/-/change-emitter-0.1.6.tgz#e8b2fe3d7f1ab7d69a32199aff91ea6931409515" integrity sha1-6LL+PX8at9aaMhma/5HqaTFAlRU= +character-entities-html4@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-1.1.4.tgz#0e64b0a3753ddbf1fdc044c5fd01d0199a02e125" + integrity sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g== + character-entities-legacy@^1.0.0: version "1.1.4" resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1" @@ -4477,6 +4487,21 @@ checkstyle-formatter@^1.1.0: dependencies: xml-escape "^1.0.0" +chokidar@3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.0.tgz#12c0714668c55800f659e262d4962a97faf554a6" + integrity sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.2.0" + optionalDependencies: + fsevents "~2.1.1" + chokidar@^2.0.2: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" @@ -5188,6 +5213,11 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" +css-selector-parser@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/css-selector-parser/-/css-selector-parser-1.3.0.tgz#5f1ad43e2d8eefbfdc304fcd39a521664943e3eb" + integrity sha1-XxrUPi2O77/cME/NOaUhZklD4+s= + csstype@^2.2.0, csstype@^2.5.7: version "2.6.9" resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098" @@ -6687,7 +6717,7 @@ fsevents@^1.2.7: bindings "^1.5.0" nan "^2.12.1" -fsevents@~2.1.2: +fsevents@~2.1.1, fsevents@~2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA== @@ -7259,6 +7289,11 @@ hast-util-from-parse5@^5.0.0: web-namespaces "^1.1.2" xtend "^4.0.1" +hast-util-is-element@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/hast-util-is-element/-/hast-util-is-element-1.0.4.tgz#059090a05cc02e275df1ad02caf8cb422fcd2e02" + integrity sha512-NFR6ljJRvDcyPP5SbV7MyPBgF47X3BsskLnmw1U34yL+X6YC0MoBx9EyMg8Jtx4FzGH95jw8+c1VPLHaRA0wDQ== + hast-util-parse-selector@^2.0.0: version "2.2.3" resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.3.tgz#57edd449103900c7f63fd9e6f694ffd7e4634719" @@ -7278,6 +7313,29 @@ hast-util-raw@5.0.1: xtend "^4.0.1" zwitch "^1.0.0" +hast-util-sanitize@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hast-util-sanitize/-/hast-util-sanitize-2.0.2.tgz#8a4299bccba6cc8836284466d446060d2ecb2f5c" + integrity sha512-ppfgtI6pVb0/dopboV/N2SZju/CKEJzLs6jm58NxoYU1c1ib+/sh14JV5bjLDOEYvyeb5hYIttFKanYm0rtnHQ== + dependencies: + xtend "^4.0.0" + +hast-util-to-html@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-7.1.0.tgz#aa580e7b846438ee1e7af2bee6a650062eb0c3ec" + integrity sha512-RjvNL41bYnxTOA+s8W8mjhVxH1J0dXza9hlakVHbghFsfj3JO/lsq3DsR/Md2HbynS8y89/LA7k0lhtnYJ1Yzw== + dependencies: + ccount "^1.0.0" + comma-separated-tokens "^1.0.0" + hast-util-is-element "^1.0.0" + hast-util-whitespace "^1.0.0" + html-void-elements "^1.0.0" + property-information "^5.0.0" + space-separated-tokens "^1.0.0" + stringify-entities "^3.0.0" + unist-util-is "^4.0.0" + xtend "^4.0.0" + hast-util-to-parse5@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-5.1.1.tgz#cabf2dbe9ed988a5128fc708457b37cdf535a2e8" @@ -7289,6 +7347,11 @@ hast-util-to-parse5@^5.0.0: xtend "^4.0.1" zwitch "^1.0.0" +hast-util-whitespace@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-1.0.4.tgz#e4fe77c4a9ae1cb2e6c25e02df0043d0164f6e41" + integrity sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A== + hastscript@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-5.1.1.tgz#71726ee1e97220575d1f29a8e937387d99d48275" @@ -7363,7 +7426,7 @@ html-tags@^3.1.0: resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140" integrity sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg== -html-void-elements@^1.0.1: +html-void-elements@^1.0.0, html-void-elements@^1.0.1: version "1.0.5" resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.5.tgz#ce9159494e86d95e45795b166c2021c2cfca4483" integrity sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w== @@ -7824,7 +7887,7 @@ is-date-object@^1.0.1: resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== -is-decimal@^1.0.0: +is-decimal@^1.0.0, is-decimal@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5" integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw== @@ -9099,6 +9162,13 @@ log-symbols@2.2.0, log-symbols@^2.1.0, log-symbols@^2.2.0: dependencies: chalk "^2.0.1" +log-symbols@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" + integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== + dependencies: + chalk "^2.4.2" + log-symbols@^1.0.0, log-symbols@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18" @@ -9375,6 +9445,13 @@ mdast-util-definitions@^1.2.0, mdast-util-definitions@^1.2.3: dependencies: unist-util-visit "^1.0.0" +mdast-util-definitions@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-2.0.1.tgz#2c931d8665a96670639f17f98e32c3afcfee25f3" + integrity sha512-Co+DQ6oZlUzvUR7JCpP249PcexxygiaKk9axJh+eRzHDZJk2julbIdKB4PXHVxdBuLzvJ1Izb+YDpj2deGMOuA== + dependencies: + unist-util-visit "^2.0.0" + mdast-util-to-hast@7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-7.0.0.tgz#589b562ce1ae0a7849e6c38536a9e7bc4f415e54" @@ -9390,12 +9467,27 @@ mdast-util-to-hast@7.0.0: unist-util-position "^3.0.0" unist-util-visit "^2.0.0" +mdast-util-to-hast@^8.0.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-8.2.0.tgz#adf9f824defcd382e53dd7bace4282a45602ac67" + integrity sha512-WjH/KXtqU66XyTJQ7tg7sjvTw1OQcVV0hKdFh3BgHPwZ96fSBCQ/NitEHsN70Mmnggt+5eUUC7pCnK+2qGQnCA== + dependencies: + collapse-white-space "^1.0.0" + detab "^2.0.0" + mdast-util-definitions "^2.0.0" + mdurl "^1.0.0" + trim-lines "^1.0.0" + unist-builder "^2.0.0" + unist-util-generated "^1.0.0" + unist-util-position "^3.0.0" + unist-util-visit "^2.0.0" + mdast-util-to-string@^1.0.0: version "1.0.8" resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-1.0.8.tgz#a414cee45ad4bef93a60f32f48266d43e263d88f" integrity sha512-GBracya0dOzckEEizUBzfrkWRLCHMsppuU97LPUriY9kWnYyGFWTx4VDW+sUcj2LneBz/Tp1aYp3aUCibzjtWg== -mdurl@^1.0.1: +mdurl@^1.0.0, mdurl@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= @@ -9614,6 +9706,11 @@ minimist@1.2.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= +minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + minimist@~0.0.1: version "0.0.10" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" @@ -9677,6 +9774,13 @@ mkdirp@0.5.1, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1: dependencies: minimist "0.0.8" +mkdirp@0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.3.tgz#5a514b7179259287952881e94410ec5465659f8c" + integrity sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg== + dependencies: + minimist "^1.2.5" + mocha@^6.2.2: version "6.2.2" resolved "https://registry.yarnpkg.com/mocha/-/mocha-6.2.2.tgz#5d8987e28940caf8957a7d7664b910dc5b2fea20" @@ -9706,6 +9810,36 @@ mocha@^6.2.2: yargs-parser "13.1.1" yargs-unparser "1.6.0" +mocha@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.1.1.tgz#89fbb30d09429845b1bb893a830bf5771049a441" + integrity sha512-3qQsu3ijNS3GkWcccT5Zw0hf/rWvu1fTN9sPvEd81hlwsr30GX2GcDSSoBxo24IR8FelmrAydGC6/1J5QQP4WA== + dependencies: + ansi-colors "3.2.3" + browser-stdout "1.3.1" + chokidar "3.3.0" + debug "3.2.6" + diff "3.5.0" + escape-string-regexp "1.0.5" + find-up "3.0.0" + glob "7.1.3" + growl "1.10.5" + he "1.2.0" + js-yaml "3.13.1" + log-symbols "3.0.0" + minimatch "3.0.4" + mkdirp "0.5.3" + ms "2.1.1" + node-environment-flags "1.0.6" + object.assign "4.1.0" + strip-json-comments "2.0.1" + supports-color "6.0.0" + which "1.3.1" + wide-align "1.1.3" + yargs "13.3.2" + yargs-parser "13.1.2" + yargs-unparser "1.6.0" + modify-values@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" @@ -9864,6 +9998,14 @@ node-environment-flags@1.0.5: object.getownpropertydescriptors "^2.0.3" semver "^5.7.0" +node-environment-flags@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088" + integrity sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw== + dependencies: + object.getownpropertydescriptors "^2.0.3" + semver "^5.7.0" + node-fetch-npm@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz#7258c9046182dca345b4208eda918daf33697ff7" @@ -9989,6 +10131,11 @@ normalize-url@^3.3.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== +not@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/not/-/not-0.1.0.tgz#c9691c1746c55dcfbe54cbd8bd4ff041bc2b519d" + integrity sha1-yWkcF0bFXc++VMvYvU/wQbwrUZ0= + now-and-later@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.1.tgz#8e579c8685764a7cc02cb680380e94f43ccb1f7c" @@ -10098,6 +10245,13 @@ npmlog@^4.0.1, npmlog@^4.1.2: gauge "~2.7.3" set-blocking "~2.0.0" +nth-check@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" + integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== + dependencies: + boolbase "~1.0.0" + number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" @@ -10457,6 +10611,18 @@ parse-entities@^1.0.2, parse-entities@^1.1.0, parse-entities@^1.1.2: is-decimal "^1.0.0" is-hexadecimal "^1.0.0" +parse-entities@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8" + integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ== + dependencies: + character-entities "^1.0.0" + character-entities-legacy "^1.0.0" + character-reference-invalid "^1.0.0" + is-alphanumerical "^1.0.0" + is-decimal "^1.0.0" + is-hexadecimal "^1.0.0" + parse-github-repo-url@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz#9e7d8bb252a6cb6ba42595060b7bf6df3dbc1f50" @@ -11486,6 +11652,13 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" +readdirp@~3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839" + integrity sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ== + dependencies: + picomatch "^2.0.4" + readdirp@~3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.3.0.tgz#984458d13a1e42e2e9f5841b129e162f369aff17" @@ -11657,6 +11830,16 @@ remark-external-links@^5.0.0: space-separated-tokens "^1.1.2" unist-util-visit "^1.4.0" +remark-html@^11.0.1: + version "11.0.1" + resolved "https://registry.yarnpkg.com/remark-html/-/remark-html-11.0.1.tgz#fe12671be9dd67e75dd90dfb4d78c9d42f2e11ec" + integrity sha512-bz7tSu9kDbaRNaiqQWrgXITkqJsZXoc2fjau+ms7WA4vflDoB+xMdY0QfNhUpGgmAv4SdDD1m+3BxWRHLizmiQ== + dependencies: + hast-util-sanitize "^2.0.0" + hast-util-to-html "^7.0.0" + mdast-util-to-hast "^8.0.0" + xtend "^4.0.1" + remark-mdx@^1.5.7: version "1.5.7" resolved "https://registry.yarnpkg.com/remark-mdx/-/remark-mdx-1.5.7.tgz#bdeb83e7e625a8e1e446c233c6cb30dd5f0cfd98" @@ -11713,6 +11896,28 @@ remark-parse@^4.0.0: vfile-location "^2.0.0" xtend "^4.0.1" +remark-parse@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-8.0.0.tgz#0aaae4b49e607ee7e972a6cf688026f46bbf6d1a" + integrity sha512-Ck14G1Ns/GEPXhSw6m1Uv28kMtVk63e59NyL+QlhBBwBdIUXROM6MPfBehPhW6TW2d73batMdZsKwuxl5i3tEA== + dependencies: + ccount "^1.0.0" + collapse-white-space "^1.0.2" + is-alphabetical "^1.0.0" + is-decimal "^1.0.0" + is-whitespace-character "^1.0.0" + is-word-character "^1.0.0" + markdown-escapes "^1.0.0" + parse-entities "^2.0.0" + repeat-string "^1.5.4" + state-toggle "^1.0.0" + trim "0.0.1" + trim-trailing-lines "^1.0.0" + unherit "^1.0.4" + unist-util-remove-position "^2.0.0" + vfile-location "^3.0.0" + xtend "^4.0.1" + remark-slug@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/remark-slug/-/remark-slug-5.1.2.tgz#715ecdef8df1226786204b1887d31ab16aa24609" @@ -12722,6 +12927,17 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +stringify-entities@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-3.0.0.tgz#455abe501f8b7859ba5726a25a8872333c65b0a7" + integrity sha512-h7NJJIssprqlyjHT2eQt2W1F+MCcNmwPGlKb0bWEdET/3N44QN3QbUF/ueKCgAssyKRZ3Br9rQ7FcXjHr0qLHw== + dependencies: + character-entities-html4 "^1.0.0" + character-entities-legacy "^1.0.0" + is-alphanumerical "^1.0.0" + is-decimal "^1.0.2" + is-hexadecimal "^1.0.0" + stringify-object@^3.2.2, stringify-object@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" @@ -13441,6 +13657,18 @@ unified@^6.1.5: vfile "^2.0.0" x-is-string "^0.1.0" +unified@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/unified/-/unified-9.0.0.tgz#12b099f97ee8b36792dbad13d278ee2f696eed1d" + integrity sha512-ssFo33gljU3PdlWLjNp15Inqb77d6JnJSfyplGJPT/a+fNRNyCBeveBAYJdO5khKdF6WVHa/yYCC7Xl6BDwZUQ== + dependencies: + bail "^1.0.0" + extend "^3.0.0" + is-buffer "^2.0.0" + is-plain-obj "^2.0.0" + trough "^1.0.0" + vfile "^4.0.0" + union-value@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" @@ -13488,7 +13716,7 @@ unist-util-is@^3.0.0: resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-3.0.0.tgz#d9e84381c2468e82629e4a5be9d7d05a2dd324cd" integrity sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A== -unist-util-is@^4.0.0: +unist-util-is@^4.0.0, unist-util-is@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.0.2.tgz#c7d1341188aa9ce5b3cff538958de9895f14a5de" integrity sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ== @@ -13505,6 +13733,13 @@ unist-util-remove-position@^1.0.0: dependencies: unist-util-visit "^1.1.0" +unist-util-remove-position@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz#5d19ca79fdba712301999b2b73553ca8f3b352cc" + integrity sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA== + dependencies: + unist-util-visit "^2.0.0" + unist-util-remove@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/unist-util-remove/-/unist-util-remove-1.0.3.tgz#58ec193dfa84b52d5a055ffbc58e5444eb8031a3" @@ -13512,6 +13747,17 @@ unist-util-remove@^1.0.0: dependencies: unist-util-is "^3.0.0" +unist-util-select@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/unist-util-select/-/unist-util-select-3.0.1.tgz#787fc452db9ba77f0ade0e7dc53c3d9d4acc79c7" + integrity sha512-VQpTuqZVJlRbosQdnLdTPIIqwZeU70YZ5aMBOqtFNGeeCdYn6ORZt/9RiaVlbl06ocuf58SVMoFa7a13CSGPMA== + dependencies: + css-selector-parser "^1.0.0" + not "^0.1.0" + nth-check "^1.0.0" + unist-util-is "^4.0.0" + zwitch "^1.0.0" + unist-util-stringify-position@^1.0.0, unist-util-stringify-position@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz#3f37fcf351279dcbca7480ab5889bb8a832ee1c6" @@ -13539,7 +13785,7 @@ unist-util-visit-parents@^3.0.0: "@types/unist" "^2.0.0" unist-util-is "^4.0.0" -unist-util-visit@2.0.2, unist-util-visit@^2.0.0: +unist-util-visit@2.0.2, unist-util-visit@^2.0.0, unist-util-visit@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.2.tgz#3843782a517de3d2357b4c193b24af2d9366afb7" integrity sha512-HoHNhGnKj6y+Sq+7ASo2zpVdfdRifhTgX2KTU3B/sO/TTlZchp7E3S4vjRzDJ7L60KmrCPsQkVK3lEF3cz36XQ== @@ -13752,6 +13998,11 @@ vfile-location@^2.0.0: resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-2.0.6.tgz#8a274f39411b8719ea5728802e10d9e0dff1519e" integrity sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA== +vfile-location@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-3.0.1.tgz#d78677c3546de0f7cd977544c367266764d31bb3" + integrity sha512-yYBO06eeN/Ki6Kh1QAkgzYpWT1d3Qln+ZCtSbJqFExPl1S3y2qqotJQXoh6qEvl/jDlgpUJolBn3PItVnnZRqQ== + vfile-message@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-1.1.1.tgz#5833ae078a1dfa2d96e9647886cd32993ab313e1" @@ -14337,6 +14588,14 @@ yargs-parser@13.1.1, yargs-parser@^13.1.1: camelcase "^5.0.0" decamelize "^1.2.0" +yargs-parser@13.1.2, yargs-parser@^13.1.2: + version "13.1.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" + integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + yargs-parser@^10.0.0: version "10.1.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8" @@ -14385,6 +14644,22 @@ yargs@13.3.0, yargs@^13.3.0: y18n "^4.0.0" yargs-parser "^13.1.1" +yargs@13.3.2: + version "13.3.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" + integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== + dependencies: + cliui "^5.0.0" + find-up "^3.0.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^3.0.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^13.1.2" + yargs@^12.0.2: version "12.0.5" resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13"