@bbc/morty-docs 3.0.3 → 4.0.2

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/package.json CHANGED
@@ -1,22 +1,21 @@
1
1
  {
2
2
  "name": "@bbc/morty-docs",
3
- "version": "3.0.3",
3
+ "version": "4.0.2",
4
4
  "description": "To generate a static website from markdown documentation, to allow users to consume content in an easily accessible format",
5
5
  "main": "build/index.js",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },
9
9
  "scripts": {
10
- "prebuild": "[ -d node_modules ] || npm install",
11
- "preversion": "npm run test",
12
- "start": "rm -rf www && npm run build -- --source-maps && node run.js && npx http-server www",
13
- "build": "rm -rf build && mkdir build && babel src --out-dir build",
14
- "build-watch": "npm run build -- --source-maps --watch",
10
+ "prepare": "npm run build",
11
+ "prebuild": "[ -d node_modules ] || npm ci",
12
+ "start": "rm -rf www && npm run build -- --sourcemap && node run.js && npx http-server www",
13
+ "build": "rm -rf build && esbuild src/index.js --bundle --minify --platform=node --target=node22 --outfile=build/index.js",
15
14
  "test": "jest --runInBand --outputFile test-results.json --json",
16
15
  "coverage": "jest --coverage --runInBand && open coverage/lcov-report/index.html",
17
16
  "lint": "standard",
18
17
  "posttest": "npm run lint",
19
- "prepare": "npm run build",
18
+ "preversion": "npm run test",
20
19
  "postversion": "git push origin main && git push --tags origin main"
21
20
  },
22
21
  "files": [
@@ -41,29 +40,24 @@
41
40
  },
42
41
  "homepage": "https://github.com/bbc/morty-docs#readme",
43
42
  "dependencies": {
44
- "asciidoctor": "^2.2.6",
45
- "fs-extra": "^7.0.1",
46
- "react": "^16.8.6",
47
- "react-dom": "^16.8.6",
48
- "react-test-renderer": "^16.13.1",
49
- "recursive-readdir": "^2.2.2",
50
- "showdown": "^1.9.0"
43
+ "marked": "^15.0.12",
44
+ "node-emoji": "^2.2.0",
45
+ "react": "^19.2.1",
46
+ "react-dom": "^19.2.1"
51
47
  },
52
48
  "devDependencies": {
53
- "@babel/cli": "^7.17.0",
54
- "@babel/core": "^7.9.0",
55
- "@babel/node": "^7.4.5",
56
- "@babel/preset-react": "^7.0.0",
57
- "babel-eslint": "^10.0.3",
49
+ "@babel/core": "^7.28.5",
50
+ "@babel/preset-react": "^7.28.5",
51
+ "@happy-dom/jest-environment": "^20.0.11",
52
+ "@testing-library/react": "^16.3.0",
53
+ "esbuild": "^0.27.1",
58
54
  "http-server": "^14.1.1",
59
- "jest": "^29.7.0",
60
- "jest-serializer-html": "^7.0.0",
61
- "standard": "^12.0.1"
55
+ "jest": "^30.2.0",
56
+ "standard": "^17.1.2"
62
57
  },
63
58
  "standard": {
64
59
  "env": [
65
60
  "jest"
66
- ],
67
- "parser": "babel-eslint"
61
+ ]
68
62
  }
69
63
  }
@@ -1,46 +0,0 @@
1
- const path = require('path');
2
- const renderIndexPage = require('./page-renderers/IndexPage');
3
- const getDirectories = require('./helpers/get-directory-paths');
4
- const filterFilesInDirectory = require('./helpers/filter-files-in-dir');
5
- const filterDirectoriesInDirectory = require('./helpers/filter-dirs-in-dir');
6
- const generateIndex = (directory, filePaths, subDirPaths, options) => {
7
- const subDirLinks = subDirPaths.filter(dirPath => !dirPath.includes('/')).map(dirPath => ({
8
- link: `${dirPath}/index.html`,
9
- text: dirPath,
10
- iconName: 'folder'
11
- }));
12
- const fileLinks = filePaths.map(filePath => {
13
- const text = path.basename(filePath);
14
- return {
15
- link: encodeURIComponent(text),
16
- text,
17
- iconName: 'file'
18
- };
19
- });
20
- return renderIndexPage([...subDirLinks, ...fileLinks], options, directory);
21
- };
22
- const generateIndexes = (files, options = {
23
- contentTitle: ''
24
- }) => {
25
- const supportedFilePaths = files.map(file => file.relativePath).filter(relativePath => path.extname(relativePath) === '.html' || path.extname(relativePath) === '.pdf');
26
- const directories = getDirectories(supportedFilePaths);
27
- // If we have not got a 'root' folder, then add one.
28
- // TODO: Refactor this so it is not needed (maybe?)
29
- if (!directories.includes('')) {
30
- directories.push('');
31
- }
32
- const indexes = directories.flatMap(directory => {
33
- const filesInDir = filterFilesInDirectory(supportedFilePaths, directory);
34
- const subDirsInDir = filterDirectoriesInDirectory(directories, directory);
35
- const indexPath = path.join(directory, 'index.html');
36
- if (filesInDir.includes(indexPath)) {
37
- return [];
38
- }
39
- return {
40
- relativePath: indexPath,
41
- raw: Buffer.from(generateIndex(directory, filesInDir, subDirsInDir, options))
42
- };
43
- });
44
- return indexes;
45
- };
46
- module.exports = generateIndexes;
@@ -1,54 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const generateTransformInput = dir => {
4
- dir = path.format(path.parse(dir));
5
- let list = [];
6
- const files = fs.readdirSync(dir, {
7
- recursive: true,
8
- withFileTypes: true
9
- });
10
- // recursive option available from node 18+
11
- // when options.withFileTypes set to true, the returned array will contain <fs.Dirent> objects.
12
- for (const dirent of files) {
13
- // Node API for Dirent is unstable
14
- const dirPath = dirent.path || dirent.parentPath; // path is DEPRECATED! But parentPath does not work in 18.17
15
-
16
- const fullPath = path.join(dirPath, dirent.name);
17
- // console.log('fullPath = ', fullPath)
18
-
19
- if (dirent.isDirectory()) {
20
- console.log('directory... continue');
21
- continue;
22
- }
23
- if (dirent.isFile()) {
24
- list.push(makeInputObject(fullPath, dir));
25
- continue;
26
- }
27
- if (dirent.isSymbolicLink) {
28
- if (fs.existsSync(fullPath)) {
29
- // fs.exists() is deprecated, but fs.existsSync() is not.
30
- const stats = fs.statSync(fullPath);
31
- console.log('Good symlink');
32
- if (stats.isFile()) {
33
- // get file details
34
- list.push(makeInputObject(fullPath, dir)); // symlinks become copies
35
- } else {
36
- // recursive call to get all files in the symlinked directory
37
- const newlist = generateTransformInput(fullPath);
38
- list = list.concat(newlist);
39
- }
40
- } else {
41
- console.log(`Broken symlink at: ${fullPath}`);
42
- }
43
- continue;
44
- }
45
- }
46
- return list;
47
- };
48
- const makeInputObject = (fullPath, rootPath) => {
49
- return {
50
- relativePath: fullPath.replace(`${rootPath}/`, ''),
51
- raw: fs.readFileSync(fullPath)
52
- };
53
- };
54
- module.exports = generateTransformInput;
@@ -1,21 +0,0 @@
1
- const {
2
- sortArrayByDate
3
- } = require('./sort-array-by-date');
4
- const filterDirs = (directoryPaths, directory) => {
5
- const directoryArray = [];
6
- const nestedDirFolders = directoryPaths.filter(directoryPath => directoryPath.includes(directory) && !directoryPath.startsWith(directory) && directoryPath !== directory);
7
- if (nestedDirFolders.length) directoryArray.push(nestedDirFolders);
8
- // if directory path includes the config folder then return the full directory path
9
-
10
- const rootDirFolders = directoryPaths.filter(directoryPath => directoryPath.startsWith(directory) && directoryPath !== directory);
11
- const rootDir = rootDirFolders.map(directoryPath => directoryPath.startsWith(`${directory}/`) && directory !== '' ? directoryPath.replace(`${directory}/`, '') : directoryPath);
12
- if (rootDir.length) directoryArray.push(rootDir);
13
- // if directory path starts with the config folder then remove the directory from the path
14
- // sort array here
15
- let sortedDirectoryArray = directoryArray;
16
- if (sortedDirectoryArray.length) {
17
- sortedDirectoryArray.map(arr => sortArrayByDate(arr));
18
- }
19
- return sortedDirectoryArray.flat();
20
- };
21
- module.exports = filterDirs;
@@ -1,10 +0,0 @@
1
- const {
2
- sortArrayByDate
3
- } = require('./sort-array-by-date');
4
- const directoryDepth = path => path.split('/').length;
5
- const isInRootDirectory = filePath => directoryDepth(filePath) === 1;
6
- const isIn = directory => filePath => filePath.startsWith(directory) && directoryDepth(filePath) - 1 === directoryDepth(directory);
7
- module.exports = (filePaths, directory) => {
8
- const isInSpecifiedDirectory = directory ? isIn(directory) : isInRootDirectory;
9
- return sortArrayByDate(filePaths.filter(isInSpecifiedDirectory));
10
- };
@@ -1,24 +0,0 @@
1
- const getDirectoryPaths = filePaths => {
2
- const dirPaths = filePaths.map(path => {
3
- // file = SomeOtherDir/AnotherDir/AnotherNestedDir/file.html
4
- if (!path.includes('/')) return ''; // if file is not in the directory, return '', as this is the root
5
-
6
- const splitPaths = path.split('/'); // => [ 'SomeOtherDir', 'AnotherDir', 'AnotherNestedDir', 'File1.html' ]
7
- splitPaths.pop(); // remove file.html
8
-
9
- const allDirs = splitPaths.map((splitPath, index) => {
10
- const newPath = joinPathParts(splitPaths, index);
11
- return newPath;
12
- });
13
- return allDirs;
14
- });
15
- return [...new Set(dirPaths.flat())];
16
- };
17
- const joinPathParts = (pathsArray, end) => {
18
- if (end === 0) {
19
- return pathsArray[0];
20
- }
21
- end++;
22
- return pathsArray.slice(0, end).join('/');
23
- };
24
- module.exports = getDirectoryPaths;
@@ -1,14 +0,0 @@
1
- const getHeaderLinks = (basePath, relPath = '') => {
2
- const combinedParts = [...basePath.split('/'), ...relPath.split('/')];
3
- const paths = combinedParts.filter(part => part && !part.endsWith('.html') && !part.endsWith('.md'));
4
- return paths.reduce((acc, item, index) => {
5
- const prevPart = acc[index - 1];
6
- const path = prevPart ? `${prevPart.path}/${item}` : `/${item}`;
7
- acc.push({
8
- text: item,
9
- path
10
- });
11
- return acc;
12
- }, []);
13
- };
14
- module.exports = getHeaderLinks;
@@ -1,11 +0,0 @@
1
- const dateRegex = /\d{4}-\d{2}-\d{2}/; // i.e. 2019-10-29
2
-
3
- function sortByDate(a, b) {
4
- return a.match(dateRegex) && b.match(dateRegex) ? b.localeCompare(a) : a.localeCompare(b);
5
- }
6
- const sortArrayByDate = arr => {
7
- return arr.sort(sortByDate);
8
- };
9
- module.exports = {
10
- sortArrayByDate
11
- };
@@ -1,64 +0,0 @@
1
- const showdown = require('showdown');
2
-
3
- // there may be an option to enable this, but since we haven't found it here is a reg-ex
4
- // to convert links within a file from *.md to *.html
5
- const convertMdLinksToHtmlLinks = {
6
- type: 'output',
7
- regex: /<a href="([^:\n]*?).md">/g,
8
- // exclude colon, so external links aren't converted
9
- replace: '<a href="$1.html">'
10
- };
11
-
12
- // Replace hash links with .html e.g. page.md#Title becomes page.html#Title.
13
- const convertMdHashLinksToHtmlLinks = {
14
- type: 'output',
15
- regex: /<a href="([^:\n]*?).md#(.*?)">/g,
16
- // exclude colon, so external links aren't converted
17
- replace: '<a href="$1.html#$2">'
18
- };
19
- const headingExtension = {
20
- type: 'output',
21
- regex: /<(h[123456]) id="([^"]+)">(.*)<\/\1>/g,
22
- replace: '<$1 id="$2"><a href="#$2">$3</a></$1>'
23
- };
24
- const classMap = {
25
- img: 'img-responsive',
26
- table: 'table'
27
- };
28
- const bindings = Object.keys(classMap).map(key => ({
29
- type: 'output',
30
- regex: new RegExp(`<${key}(.*)>`, 'g'),
31
- replace: `<${key} class="${classMap[key]}" $1>`
32
- }));
33
- const normaliseBasePath = basePath => {
34
- const pathElements = (basePath || '').split('/').filter(entry => Boolean(entry));
35
- if (pathElements.length === 0) {
36
- return '';
37
- }
38
- return '/' + pathElements.join('/');
39
- };
40
- const createParser = options => {
41
- const basePath = normaliseBasePath(options.basePath);
42
- const addBasePathToRootLinks = {
43
- type: 'output',
44
- regex: /<a href="\/([^:\n]*)">/g,
45
- // exclude colon, so external links aren't converted
46
- replace: `<a href="${basePath}/$1">`
47
- };
48
- const addBasePathToLinkHrefs = {
49
- type: 'output',
50
- regex: /<link(.+)href="\/([^:\n]*)"(.*)\/>/g,
51
- // exclude colon, so external links aren't converted
52
- replace: `<link$1href="${basePath}/$2"$3/>`
53
- };
54
- const parser = new showdown.Converter({
55
- extensions: [convertMdLinksToHtmlLinks, convertMdHashLinksToHtmlLinks, addBasePathToRootLinks, addBasePathToLinkHrefs, headingExtension, ...bindings]
56
- });
57
- parser.setFlavor('github');
58
- return parser;
59
- };
60
- const parseToHTML = (markdown, options = {}) => {
61
- const parser = createParser(options);
62
- return parser.makeHtml(markdown);
63
- };
64
- module.exports = parseToHTML;
@@ -1,29 +0,0 @@
1
- const React = require('react');
2
- const {
3
- prettyDate
4
- } = require('./PrettyDate');
5
- const Styles = {
6
- footer: {
7
- bottom: '0',
8
- width: '100%',
9
- backgroundColor: '#f5f5f5',
10
- boxSizing: 'border-box',
11
- minHeight: '25vh',
12
- position: 'relative',
13
- textAlign: 'center',
14
- padding: '1em 0 2em 0',
15
- lineHeight: '1.5rem'
16
- },
17
- footerLink: {
18
- color: '#337ab7'
19
- }
20
- };
21
- const Footer = () => {
22
- return React.createElement("footer", {
23
- style: Styles.footer
24
- }, React.createElement("a", {
25
- href: "https://github.com/bbc/morty-docs",
26
- style: Styles.footerLink
27
- }, "Morty-Docs on github"), React.createElement("br", null), "Page generated on ", prettyDate(new Date()));
28
- };
29
- module.exports = Footer;
@@ -1,59 +0,0 @@
1
- const React = require('react');
2
- const getHeaderPaths = require('../../helpers/get-header-paths');
3
- const Styles = {
4
- navbar: {
5
- border: 'none',
6
- borderRadius: '0',
7
- backgroundColor: '#000',
8
- marginBottom: '0',
9
- width: '100%'
10
- },
11
- headerNav: {},
12
- headerLinks: {
13
- padding: '1rem',
14
- listStyle: 'none',
15
- display: 'flex',
16
- alignItems: 'center'
17
- },
18
- headerLink: {
19
- textAlign: 'left',
20
- color: 'lightblue',
21
- fontSize: '1.5rem',
22
- textDecoration: 'none'
23
- },
24
- separator: {
25
- margin: '0 0.28rem',
26
- color: '#ebebeb'
27
- }
28
- };
29
- const HeaderLinks = ({
30
- paths
31
- }) => paths.map(({
32
- text,
33
- path
34
- }, index) => {
35
- return React.createElement("li", {
36
- key: index
37
- }, index !== paths.length ? React.createElement("span", {
38
- "aria-hidden": true,
39
- style: Styles.separator
40
- }, "/") : undefined, React.createElement("a", {
41
- style: Styles.headerLink,
42
- href: path
43
- }, text));
44
- });
45
- const Header = ({
46
- relPath,
47
- basePath
48
- }) => {
49
- return React.createElement("div", {
50
- style: Styles.navbar
51
- }, React.createElement("nav", {
52
- style: Styles.headerNav
53
- }, React.createElement("ol", {
54
- style: Styles.headerLinks
55
- }, React.createElement(HeaderLinks, {
56
- paths: getHeaderPaths(basePath, relPath)
57
- }))));
58
- };
59
- module.exports = Header;
@@ -1,58 +0,0 @@
1
- const React = require('react');
2
- const IconFileLines = require('./icons/IconFileLines');
3
- const IconFolderOpen = require('./icons/IconFolderOpen');
4
- const Styles = {
5
- indexListItem: {
6
- fontSize: '1.5rem',
7
- borderBottom: 'solid black 1px',
8
- marginBottom: '1rem',
9
- paddingBottom: '0.5rem'
10
- },
11
- linkText: {
12
- marginLeft: '1rem'
13
- },
14
- linkAnchor: {
15
- textDecoration: 'none',
16
- color: '#337ab7'
17
- },
18
- icon: {
19
- fill: '#337ab7',
20
- height: '1em',
21
- verticalAlign: 'middle'
22
- },
23
- iconWrap: {
24
- display: 'inline-block',
25
- width: '1em'
26
- }
27
- };
28
- const Icon = ({
29
- iconName
30
- }) => {
31
- if (iconName === 'folder') {
32
- return React.createElement(IconFolderOpen, {
33
- style: Styles.icon
34
- });
35
- }
36
- return React.createElement(IconFileLines, {
37
- style: Styles.icon
38
- });
39
- };
40
- const IndexListItem = ({
41
- link,
42
- text,
43
- iconName
44
- }) => {
45
- return React.createElement("li", {
46
- style: Styles.indexListItem
47
- }, React.createElement("a", {
48
- href: link,
49
- style: Styles.linkAnchor
50
- }, React.createElement("span", {
51
- style: Styles.iconWrap
52
- }, React.createElement(Icon, {
53
- iconName: iconName
54
- })), React.createElement("span", {
55
- style: Styles.linkText
56
- }, text)));
57
- };
58
- module.exports = IndexListItem;
@@ -1,23 +0,0 @@
1
- const React = require('react');
2
- const nth = function (d) {
3
- if (d > 3 && d < 21) return 'th';
4
- switch (d % 10) {
5
- case 1:
6
- return 'st';
7
- case 2:
8
- return 'nd';
9
- case 3:
10
- return 'rd';
11
- default:
12
- return 'th';
13
- }
14
- };
15
- const month = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
16
- const prettyDate = dateObj => {
17
- const day = dateObj.getDate();
18
- const year = dateObj.getFullYear();
19
- return React.createElement(React.Fragment, null, day, React.createElement("sup", null, nth(day)), " ", month[dateObj.getMonth()], " ", year);
20
- };
21
- module.exports = {
22
- prettyDate
23
- };
@@ -1,61 +0,0 @@
1
- const React = require('react');
2
- const resetStyles = `/* http://meyerweb.com/eric/tools/css/reset/
3
- v2.0 | 20110126
4
- License: none (public domain)
5
- */
6
-
7
- html, body, div, span, applet, object, iframe,
8
- h1, h2, h3, h4, h5, h6, p, blockquote, pre,
9
- a, abbr, acronym, address, big, cite, code,
10
- del, dfn, em, img, ins, kbd, q, s, samp,
11
- small, strike, strong, sub, sup, tt, var,
12
- b, u, i, center,
13
- dl, dt, dd, ol, ul, li,
14
- fieldset, form, label, legend,
15
- table, caption, tbody, tfoot, thead, tr, th, td,
16
- article, aside, canvas, details, embed,
17
- figure, figcaption, footer, header, hgroup,
18
- menu, nav, output, ruby, section, summary,
19
- time, mark, audio, video {
20
- margin: 0;
21
- padding: 0;
22
- border: 0;
23
- font-size: 100%;
24
- font: inherit;
25
- vertical-align: baseline;
26
- }
27
- /* HTML5 display-role reset for older browsers */
28
- article, aside, details, figcaption, figure,
29
- footer, header, hgroup, menu, nav, section {
30
- display: block;
31
- }
32
- body {
33
- line-height: 1;
34
- }
35
- ol, ul {
36
- list-style: none;
37
- }
38
- blockquote, q {
39
- quotes: none;
40
- }
41
- blockquote:before, blockquote:after,
42
- q:before, q:after {
43
- content: '';
44
- content: none;
45
- }
46
- table {
47
- border-collapse: collapse;
48
- border-spacing: 0;
49
- }
50
- sup {
51
- vertical-align: super;
52
- font-size: smaller;
53
- padding-right: 0.2rem;
54
- }
55
- `;
56
- const Reset = () => React.createElement("style", {
57
- dangerouslySetInnerHTML: {
58
- __html: resetStyles
59
- }
60
- });
61
- module.exports = Reset;
@@ -1,23 +0,0 @@
1
- const React = require('react');
2
- const Styles = {
3
- heading: {
4
- fontSize: '2rem',
5
- fontWeight: '500'
6
- }
7
- };
8
- const Title = ({
9
- contentTitle
10
- }) => {
11
- if (contentTitle) {
12
- return React.createElement("div", {
13
- style: {
14
- textAlign: 'center',
15
- marginBottom: '20px',
16
- marginTop: '32px'
17
- }
18
- }, React.createElement("h1", {
19
- style: Styles.heading
20
- }, contentTitle));
21
- } else return null;
22
- };
23
- module.exports = Title;
@@ -1,12 +0,0 @@
1
- const React = require('react');
2
- const IconFileLines = ({
3
- style
4
- }) => React.createElement("svg", {
5
- "aria-hidden": "true",
6
- style: style,
7
- xmlns: "http://www.w3.org/2000/svg",
8
- viewBox: "0 0 384 512"
9
- }, '<!--! Font Awesome Free 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. -->', React.createElement("path", {
10
- d: "M256 0v128h128L256 0zM224 128L224 0H48C21.49 0 0 21.49 0 48v416C0 490.5 21.49 512 48 512h288c26.51 0 48-21.49 48-48V160h-127.1C238.3 160 224 145.7 224 128zM272 416h-160C103.2 416 96 408.8 96 400C96 391.2 103.2 384 112 384h160c8.836 0 16 7.162 16 16C288 408.8 280.8 416 272 416zM272 352h-160C103.2 352 96 344.8 96 336C96 327.2 103.2 320 112 320h160c8.836 0 16 7.162 16 16C288 344.8 280.8 352 272 352zM288 272C288 280.8 280.8 288 272 288h-160C103.2 288 96 280.8 96 272C96 263.2 103.2 256 112 256h160C280.8 256 288 263.2 288 272z"
11
- }));
12
- module.exports = IconFileLines;
@@ -1,12 +0,0 @@
1
- const React = require('react');
2
- const IconFolderOpen = ({
3
- style
4
- }) => React.createElement("svg", {
5
- "aria-hidden": "true",
6
- style: style,
7
- xmlns: "http://www.w3.org/2000/svg",
8
- viewBox: "0 0 576 512"
9
- }, '<!--! Font Awesome Free 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. -->', React.createElement("path", {
10
- d: "M147.8 192H480V144C480 117.5 458.5 96 432 96h-160l-64-64h-160C21.49 32 0 53.49 0 80v328.4l90.54-181.1C101.4 205.6 123.4 192 147.8 192zM543.1 224H147.8C135.7 224 124.6 230.8 119.2 241.7L0 480h447.1c12.12 0 23.2-6.852 28.62-17.69l96-192C583.2 249 567.7 224 543.1 224z"
11
- }));
12
- module.exports = IconFolderOpen;