diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 0000000..031767a --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,18 @@ + +name: 'Unit Tests' + +on: push + +jobs: + # Run unit tests via Node native test runner + unit-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '20.x' + - name: Install dependencies + run: npm ci + - name: Unit tests + run: npm run test:unit \ No newline at end of file diff --git a/.storybook/main.js b/.storybook/main.js index 73e2101..dfc7db2 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -1,7 +1,7 @@ import path from 'path'; /** @type { import('@storybook/web-components-vite').StorybookConfig } */ const config = { - stories: ['../src/github/*.mdx', '../src/github/*.stories.@(js|jsx|ts|tsx)'], + stories: ['../src/github/*.mdx', '../src/github/**/*.stories.@(js|jsx|ts|tsx)'], addons: [ '@storybook/addon-essentials', '@storybook/addon-a11y', diff --git a/README.md b/README.md index 7d4ded9..e156d9f 100644 --- a/README.md +++ b/README.md @@ -10,4 +10,5 @@ Web components which display profile information from various websites ## Other profile sources -* Reddit: https://www.reddit.com/user/scottnath/about/.json \ No newline at end of file +* Reddit: https://www.reddit.com/user/scottnath/about/.json +* LinkedIn: (3rd party) https://help.lix-it.com/en/articles/6674073-what-data-can-i-export-from-linkedin \ No newline at end of file diff --git a/custom-elements.json b/custom-elements.json index 7880ce3..91f7720 100644 --- a/custom-elements.json +++ b/custom-elements.json @@ -4,124 +4,123 @@ "modules": [ { "kind": "javascript-module", - "path": "src/github/repository.js", + "path": "src/github/repository/index.js", "declarations": [ { "kind": "class", - "description": "GitHub repository UI component\n When `fetch` is true, fetches repo from GitHub api, but content from the \n api _will be superseded_ by any attributes set on the element", + "description": "GitHub repository web component", "name": "GitHubRepository", "members": [ - { - "kind": "method", - "name": "_getAttributes", - "description": "Generate variables at `this.[attribute-name]` for each attribute on the element" - }, - { - "kind": "method", - "name": "_checkAttributes", - "description": "Check that required attributes are present, adjusts content as needed" - }, - { - "kind": "method", - "name": "_parseFetch", - "description": "Fetch repo data from GitHub API and parse out \n the content needed for this component. Either adds an error\n or adds the parsed content to the element's attributes" - }, - { - "kind": "method", - "name": "_render" - }, { "kind": "field", - "name": "error", + "name": "attrs", "type": { - "text": "null" + "text": "object" }, - "default": "null" - } - ], - "attributes": [ + "default": "{}" + }, { + "kind": "field", + "name": "repo", "type": { - "text": "string" + "text": "object" }, - "description": "repository org and name, as in `scottnath/profile-components`", - "name": "full_name" + "default": "{}" }, { "type": { - "text": "boolean" + "text": "string" }, - "description": "when true, fetches repo from GitHub api", - "name": "fetch" + "description": "repository org and name, as in `scottnath/profile-components`", + "name": "full_name", + "kind": "field" }, { "type": { "text": "string" }, "description": "repo name", - "name": "name" + "name": "name", + "kind": "field" }, { "type": { "text": "string" }, "description": "repo owner organization's login, found at `.organization.login`", - "name": "org" + "name": "org", + "kind": "field" }, { "type": { "text": "string" }, "description": "repo description", - "name": "description" + "name": "description", + "kind": "field" }, { "type": { "text": "string" }, "description": "programming language used in repo", - "name": "language" + "name": "language", + "kind": "field" }, { "type": { "text": "string" }, "description": "number of stars", - "name": "stargazers_count" + "name": "stargazers_count", + "kind": "field" }, { "type": { "text": "string" }, "description": "number of forks", - "name": "forks_count" + "name": "forks_count", + "kind": "field" }, { "type": { "text": "string" }, "description": "number of watchers", - "name": "subscribers_count" + "name": "subscribers_count", + "kind": "field" + }, + { + "type": { + "text": "boolean" + }, + "description": "when true, fetches repo from GitHub api", + "name": "fetch", + "kind": "field" }, { "type": { "text": "string" }, "description": "Itemprop content to go with a containing component's itemscope", - "name": "itemprop" + "name": "itemprop", + "kind": "field" }, { "type": { "text": "string" }, - "description": "User or organization login for use by containing component", - "name": "user_login" + "description": "Do not include the repo owner or organization", + "name": "no_org", + "kind": "field" } ], "superclass": { "name": "HTMLElement" }, "tagName": "github-repository", + "summary": "Native web component which shows a GitHub repository's content. Can use local data, \nfetch data from the GitHub rest API, or use a combination of both.", "customElement": true } ], @@ -131,7 +130,7 @@ "name": "GitHubRepository", "declaration": { "name": "GitHubRepository", - "module": "src/github/repository.js" + "module": "src/github/repository/index.js" } }, { @@ -139,165 +138,124 @@ "name": "github-repository", "declaration": { "name": "GitHubRepository", - "module": "src/github/repository.js" + "module": "src/github/repository/index.js" } } ] }, { "kind": "javascript-module", - "path": "src/github/user.js", + "path": "src/github/user/index.js", "declarations": [ - { - "kind": "variable", - "name": "githubLogoSvg", - "default": "`\n\n`", - "description": "Primer Octicons used by this component" - }, - { - "kind": "variable", - "name": "octiconPeople", - "default": "`\n\n`" - }, { "kind": "class", - "description": "GitHub repository UI component\n All props are attributes and should be the same content as the GitHub API\n endpoint for getting a repository", + "description": "GitHub user profile web component", "name": "GitHubUser", "members": [ { - "kind": "method", - "name": "_parseReposAttribute", - "parameters": [ - { - "name": "reposAttr", - "description": "String of GitHubRepository data", - "type": { - "text": "string" - } - } - ], - "description": "Parses a string, which should be a JSON stringified array of GitHubRepository \n objects or strings. If a string, it should be the `full_name` of the repository\n and `fetch=true` will be an attribute on the repository component", - "return": { - "type": { - "text": "" - } - } - }, - { - "kind": "method", - "name": "_getAttributes", - "description": "Generate variables at `this.[attribute-name]` for each attribute on the element" - }, - { - "kind": "method", - "name": "_checkAttributes", - "description": "Check that required attributes are present, adjusts content as needed" + "kind": "field", + "name": "attrs", + "type": { + "text": "object" + }, + "default": "{}" }, { - "kind": "method", - "name": "_parseFetch", - "description": "Fetch user data from GitHub API and parse out \n the content needed for this component. Either adds an error\n or adds the parsed content to the element's attributes" + "kind": "field", + "name": "content", + "type": { + "text": "object" + }, + "default": "{}" }, - { - "kind": "method", - "name": "_render" - } - ], - "attributes": [ { "type": { "text": "string" }, "description": "User's GitHub login", - "name": "login" + "name": "login", + "kind": "field" }, { "type": { "text": "string" }, "description": "URL to user's avatar", - "name": "avatar_url" + "name": "avatar_url", + "kind": "field" }, { "type": { "text": "string" }, "description": "User's name", - "name": "name" + "name": "name", + "kind": "field" }, { "type": { "text": "boolean" }, "description": "when true, fetches user from GitHub api", - "name": "fetch" + "name": "fetch", + "kind": "field" }, { "type": { "text": "string" }, "description": "alias for `login`", - "name": "username" + "name": "username", + "kind": "field" }, { "type": { "text": "string" }, "description": "User's biography content", - "name": "bio" + "name": "bio", + "kind": "field" }, { "type": { "text": "string" }, "description": "number of people user is following", - "name": "following" + "name": "following", + "kind": "field" }, { "type": { "text": "string" }, "description": "number of followers", - "name": "followers" + "name": "followers", + "kind": "field" }, { "type": { "text": "string" }, "description": "JSON stringified array of repositories", - "name": "repos" + "name": "repos", + "kind": "field" } ], "superclass": { "name": "HTMLElement" }, "tagName": "github-user", + "summary": "Native web component which shows a GitHub user's profile content. Can use local data, \nfetch data from the GitHub rest API, or use a combination of both.", "customElement": true } ], "exports": [ - { - "kind": "js", - "name": "githubLogoSvg", - "declaration": { - "name": "githubLogoSvg", - "module": "src/github/user.js" - } - }, - { - "kind": "js", - "name": "octiconPeople", - "declaration": { - "name": "octiconPeople", - "module": "src/github/user.js" - } - }, { "kind": "js", "name": "GitHubUser", "declaration": { "name": "GitHubUser", - "module": "src/github/user.js" + "module": "src/github/user/index.js" } }, { @@ -305,7 +263,7 @@ "name": "github-user", "declaration": { "name": "GitHubUser", - "module": "src/github/user.js" + "module": "src/github/user/index.js" } } ] diff --git a/lib/cli.js b/lib/cli.js index 0798a0e..bb41843 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -4,6 +4,7 @@ import { Command } from 'commander'; import { writePrimerCssFile } from '../src/github/utils/primer.js'; import { generateFixtures } from '../src/github/utils/fixtures.js'; +import { generateDocs } from './jsdoc.js'; const program = new Command(); @@ -19,4 +20,10 @@ program.command('generate-fixtures-github') generateFixtures(); }); +program.command('generate-docs') + .description('Generate README.md files from JSDoc comments') + .action(() => { + generateDocs(); + }); + program.parseAsync(); \ No newline at end of file diff --git a/lib/custom-elements-manifest.config.js b/lib/custom-elements-manifest.config.js index f48159a..b256e0f 100644 --- a/lib/custom-elements-manifest.config.js +++ b/lib/custom-elements-manifest.config.js @@ -6,12 +6,16 @@ import { readmePlugin } from "./cem-readme-plugin.js" import { getModulePaths } from './utils.js'; export default { - globs: ['src/github/repository.js', 'src/github/user.js'], + globs: ['src/github/repository/index.js', 'src/github/user/index.js'], exclude: [], dependencies: false, dev: false, packagejson: true, plugins: [ - readmePlugin({ from: getModulePaths().root, to: 'custom-elements-readme.md'}), + readmePlugin({ + from: getModulePaths().root, + to: 'custom-elements-readme.md', + omitSections: ['exports'] + }), ], } \ No newline at end of file diff --git a/lib/esbuild.config.js b/lib/esbuild.config.js index cff1016..4a2d2a6 100644 --- a/lib/esbuild.config.js +++ b/lib/esbuild.config.js @@ -14,20 +14,23 @@ const { src, dist } = getModulePaths(); /** * Entrypoints for esbuild generated from custom elements manifest * which _must_ be generated before this script is triggered - * @type {string[]} */ -const entryPoints = cem.modules.map(elm => pathFromRoot(elm.path)); -esbuild.build({ - entryPoints, - bundle: true, - format: 'esm', - entryNames: '[dir]-[name]', - outbase: src, - outdir: dist, - plugins: [ - inlineImportPlugin({ - filter: /\?inline$/, - }), - ], +cem.modules.forEach(elm => { + const name = elm.exports.find(elm => elm.kind === 'custom-element-definition').name; + + esbuild.build({ + entryPoints: [pathFromRoot(elm.path)], + bundle: true, + format: 'esm', + entryNames: name, + outbase: src, + outdir: dist, + plugins: [ + inlineImportPlugin({ + filter: /\?inline$/, + }), + ], + }); + }); diff --git a/lib/jsdoc.js b/lib/jsdoc.js new file mode 100644 index 0000000..40b6fcd --- /dev/null +++ b/lib/jsdoc.js @@ -0,0 +1,59 @@ +import { pathFromRoot } from './utils.js'; +import jsdoc2md from 'jsdoc-to-markdown'; +import { outputFile } from 'fs-extra'; + +/** + * + * @param {string} title - README title + * @param {string} dir - Directory to read files from, relative from root + * @returns jsdoc2md config object + */ +const generateJsdoc2Config = (title, dir, files=[]) => { + const template = `# ${title}\n\n{{>main}}`; + return { + files, + 'member-index-format': 'list', + outputFile: `${dir}/README.md`, + exampleLang: 'js', + template, + } +} + +/** + * Generate README.md files from JSDoc comments + * @todo set up for other directories + */ +export const generateDocs = async () => { + // const githubUtils = generateJsdoc2Config(`GitHub profile components' utilities`, 'src/github/utils'); + // const res = await jsdoc2md.render(githubUtils); + const toDocs = [ + { + title: `GitHub profile components' utilities`, + dir: pathFromRoot('src/github/utils'), + files: pathFromRoot('src/github/utils/*.js'), + }, + { + title: 'GitHub user profile component', + dir: pathFromRoot('src/github/user'), + files: [ + pathFromRoot('src/github/user/index.js'), + pathFromRoot('src/github/user/content.js'), + pathFromRoot('src/github/user/html.js'), + ], + }, + { + title: 'GitHub repository details component', + dir: pathFromRoot('src/github/repository'), + files: [ + pathFromRoot('src/github/repository/index.js'), + pathFromRoot('src/github/repository/content.js'), + pathFromRoot('src/github/repository/html.js'), + ], + }, + ] + for (const toDoc of toDocs) { + const githubComp = generateJsdoc2Config(toDoc.title, toDoc.dir, toDoc.files); + const res = await jsdoc2md.render(githubComp); + await outputFile(githubComp.outputFile, res); + } +} diff --git a/package-lock.json b/package-lock.json index c818ab6..2ebd3b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,10 +14,10 @@ "@primer/octicons": "^19.8.0", "@primer/primitives": "^7.13.0", "commander": "^11.0.0", - "documentation": "^14.0.2", "esbuild": "^0.19.3", "esbuild-plugin-inline-import": "^1.0.1", "fs-extra": "^11.1.1", + "jsdoc-to-markdown": "^8.0.0", "storydocker-storybook": "^0.0.16", "storydocker-utilities": "^0.0.11", "yaml": "^2.3.2" @@ -5014,6 +5014,18 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsdoc/salty": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.5.tgz", + "integrity": "sha512-TfRP53RqunNe2HBobVBJ0VLhK1HbfvBYeTC1ahnN64PWvyYyGebmMiPkuwvD9fpw2ZbkoPb8Q7mwy0aR8Z9rvw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v12.0.0" + } + }, "node_modules/@juggle/resize-observer": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", @@ -10708,12 +10720,6 @@ "@types/send": "*" } }, - "node_modules/@types/extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/extend/-/extend-3.0.1.tgz", - "integrity": "sha512-R1g/VyKFFI2HLC1QGAeTtCBWCo6n75l41OnsVYNbmKG+kempOESaodf6BeJyUM3Q0rKa/NQcTHbB2+66lNnxLw==", - "dev": true - }, "node_modules/@types/find-cache-dir": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/@types/find-cache-dir/-/find-cache-dir-3.2.1.tgz", @@ -10739,15 +10745,6 @@ "@types/node": "*" } }, - "node_modules/@types/hast": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.6.tgz", - "integrity": "sha512-47rJE80oqPmFdVDCD7IheXBrVdwuBgsYwoczFvKmwfo2Mzsnt+V9OONsYauFmICb6lQPpCuXYJWejBNs4pDJRg==", - "dev": true, - "dependencies": { - "@types/unist": "^2" - } - }, "node_modules/@types/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz", @@ -10839,12 +10836,28 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, + "node_modules/@types/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-pTjcqY9E4nOI55Wgpz7eiI8+LzdYnw3qxXCfHyBDdPbYvbyLgWLJGh8EdPvqawwMK1Uo1794AUkkR38Fr0g+2g==", + "dev": true + }, "node_modules/@types/lodash": { "version": "4.14.197", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.197.tgz", "integrity": "sha512-BMVOiWs0uNxHVlHBgzTIqJYmj+PgCo4euloGF+5m4okL3rEYzM2EEv78mw8zWSMM57dM7kVIgJ2QDvwHSoCI5g==", "dev": true }, + "node_modules/@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "dev": true, + "dependencies": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, "node_modules/@types/mdast": { "version": "3.0.12", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.12.tgz", @@ -10854,6 +10867,12 @@ "@types/unist": "^2" } }, + "node_modules/@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", + "dev": true + }, "node_modules/@types/mdx": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.7.tgz", @@ -10912,12 +10931,6 @@ "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "dev": true }, - "node_modules/@types/parse5": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz", - "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==", - "dev": true - }, "node_modules/@types/prettier": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", @@ -11007,12 +11020,6 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, - "node_modules/@types/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-dPWnWsf+kzIG140B8z2w3fr5D03TLWbOAFQl45xUpI3vcizeXriNR5VYkWZ+WTMsUHqZ9Xlt3hrxGNANFyNQfw==", - "dev": true - }, "node_modules/@types/trusted-types": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz", @@ -11705,6 +11712,27 @@ "node": ">=6" } }, + "node_modules/ansi-escape-sequences": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-escape-sequences/-/ansi-escape-sequences-4.1.0.tgz", + "integrity": "sha512-dzW9kHxH011uBsidTXd14JXgzye/YLb2LzeKZ4bsgl/Knwx8AtbSFkkGxagdNOoh0DlqHCmfiEjWKBaqjOanVw==", + "dev": true, + "dependencies": { + "array-back": "^3.0.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/ansi-escape-sequences/node_modules/array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -12418,6 +12446,12 @@ "node": ">=10.0.0" } }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, "node_modules/body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -12745,6 +12779,29 @@ "node": ">=8" } }, + "node_modules/cache-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cache-point/-/cache-point-2.0.0.tgz", + "integrity": "sha512-4gkeHlFpSKgm3vm2gJN5sPqfmijYRFYCQ6tv5cLw0xVmT6r1z1vd4FNnpuOREco3cBs1G709sZ72LdgddKvL5w==", + "dev": true, + "dependencies": { + "array-back": "^4.0.1", + "fs-then-native": "^2.0.0", + "mkdirp2": "^1.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cache-point/node_modules/array-back": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", + "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/caching-transform": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", @@ -12862,6 +12919,18 @@ "cdl": "bin/cdl.js" } }, + "node_modules/catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "dev": true, + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/ccount": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", @@ -12923,16 +12992,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/character-entities-legacy": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", @@ -13234,6 +13293,19 @@ "@types/estree": "^1.0.0" } }, + "node_modules/collect-all": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/collect-all/-/collect-all-1.0.4.tgz", + "integrity": "sha512-RKZhRwJtJEP5FWul+gkSMEnaK6H3AGPTTWOiRimCcs+rc/OmQE3Yhy1Q7A7KsdkG3ZXVdZq68Y6ONSdvkeEcKA==", + "dev": true, + "dependencies": { + "stream-connect": "^1.0.2", + "stream-via": "^1.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/collect-v8-coverage": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", @@ -13276,16 +13348,6 @@ "node": ">= 0.8" } }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/command-line-args": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.1.2.tgz", @@ -13301,6 +13363,73 @@ "node": ">=4.0.0" } }, + "node_modules/command-line-tool": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/command-line-tool/-/command-line-tool-0.8.0.tgz", + "integrity": "sha512-Xw18HVx/QzQV3Sc5k1vy3kgtOeGmsKIqwtFFoyjI4bbcpSgnw2CWVULvtakyw4s6fhyAdI6soQQhXc2OzJy62g==", + "dev": true, + "dependencies": { + "ansi-escape-sequences": "^4.0.0", + "array-back": "^2.0.0", + "command-line-args": "^5.0.0", + "command-line-usage": "^4.1.0", + "typical": "^2.6.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/command-line-tool/node_modules/array-back": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", + "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", + "dev": true, + "dependencies": { + "typical": "^2.6.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/command-line-tool/node_modules/typical": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", + "integrity": "sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==", + "dev": true + }, + "node_modules/command-line-usage": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-4.1.0.tgz", + "integrity": "sha512-MxS8Ad995KpdAC0Jopo/ovGIroV/m0KHwzKfXxKag6FHOkGsH8/lv5yjgablcRxCJJC0oJeUMuO/gmaq+Wq46g==", + "dev": true, + "dependencies": { + "ansi-escape-sequences": "^4.0.0", + "array-back": "^2.0.0", + "table-layout": "^0.4.2", + "typical": "^2.6.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/command-line-usage/node_modules/array-back": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", + "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", + "dev": true, + "dependencies": { + "typical": "^2.6.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/command-line-usage/node_modules/typical": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", + "integrity": "sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==", + "dev": true + }, "node_modules/commander": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz", @@ -13319,6 +13448,15 @@ "node": ">= 12.0.0" } }, + "node_modules/common-sequence": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/common-sequence/-/common-sequence-2.0.2.tgz", + "integrity": "sha512-jAg09gkdkrDO9EWTdXfv80WWH3yeZl5oT69fGfedBNS9pXUKYInVJ1bJ+/ht2+Moeei48TmSbQDYMc8EOx9G0g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -13441,6 +13579,24 @@ "proto-list": "~1.2.1" } }, + "node_modules/config-master": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/config-master/-/config-master-3.1.0.tgz", + "integrity": "sha512-n7LBL1zBzYdTpF1mx5DNcZnZn05CWIdsdvtPL4MosvqbBUK3Rq6VWEtGUuF3Y0s9/CIhMejezqlSkP6TnCJ/9g==", + "dev": true, + "dependencies": { + "walk-back": "^2.0.1" + } + }, + "node_modules/config-master/node_modules/walk-back": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-2.0.1.tgz", + "integrity": "sha512-Nb6GvBR8UWX1D+Le+xUq0+Q1kFmRBIWVrfLnQAOmcpEzA9oAxwJ9gIr36t9TWYfzvWRvuMtjHiVsJYEkXWaTAQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/constantinople": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", @@ -13963,13 +14119,6 @@ "node": "*" } }, - "node_modules/de-indent": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", - "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", - "dev": true, - "optional": true - }, "node_modules/debounce": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", @@ -14360,383 +14509,59 @@ "node": ">=8" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/doctrine-temporary-fork": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine-temporary-fork/-/doctrine-temporary-fork-2.1.0.tgz", - "integrity": "sha512-nliqOv5NkE4zMON4UA6AMJE6As35afs8aYXATpU4pTUdIKiARZwrJVEP1boA3Rx1ZXHVkwxkhcq4VkqvsuRLsA==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/doctypes": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", - "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==", - "dev": true - }, - "node_modules/documentation": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/documentation/-/documentation-14.0.2.tgz", - "integrity": "sha512-hWoTf8/u4pOjib02L7w94hwmhPfcSwyJNGtlPdGVe8GFyq8HkzcFzQQltaaikKunHEp0YSwDAbwBAO7nxrWIfA==", - "dev": true, - "dependencies": { - "@babel/core": "^7.18.10", - "@babel/generator": "^7.18.10", - "@babel/parser": "^7.18.11", - "@babel/traverse": "^7.18.11", - "@babel/types": "^7.18.10", - "chalk": "^5.0.1", - "chokidar": "^3.5.3", - "diff": "^5.1.0", - "doctrine-temporary-fork": "2.1.0", - "git-url-parse": "^13.1.0", - "github-slugger": "1.4.0", - "glob": "^8.0.3", - "globals-docs": "^2.4.1", - "highlight.js": "^11.6.0", - "ini": "^3.0.0", - "js-yaml": "^4.1.0", - "konan": "^2.1.1", - "lodash": "^4.17.21", - "mdast-util-find-and-replace": "^2.2.1", - "mdast-util-inject": "^1.1.0", - "micromark-util-character": "^1.1.0", - "parse-filepath": "^1.0.2", - "pify": "^6.0.0", - "read-pkg-up": "^9.1.0", - "remark": "^14.0.2", - "remark-gfm": "^3.0.1", - "remark-html": "^15.0.1", - "remark-reference-links": "^6.0.1", - "remark-toc": "^8.0.1", - "resolve": "^1.22.1", - "strip-json-comments": "^5.0.0", - "unist-builder": "^3.0.0", - "unist-util-visit": "^4.1.0", - "vfile": "^5.3.4", - "vfile-reporter": "^7.0.4", - "vfile-sort": "^3.0.0", - "yargs": "^17.5.1" - }, - "bin": { - "documentation": "bin/documentation.js" - }, - "engines": { - "node": ">=14" - }, - "optionalDependencies": { - "@vue/compiler-sfc": "^3.2.37", - "vue-template-compiler": "^2.7.8" - } - }, - "node_modules/documentation/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/documentation/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/documentation/node_modules/find-up": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", - "dev": true, - "dependencies": { - "locate-path": "^7.1.0", - "path-exists": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/documentation/node_modules/github-slugger": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.4.0.tgz", - "integrity": "sha512-w0dzqw/nt51xMVmlaV1+JRzN+oCa1KfcgGEWhxUG16wbdA+Xnt/yoFO8Z8x/V82ZcZ0wy6ln9QDup5avbhiDhQ==", - "dev": true - }, - "node_modules/documentation/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "node_modules/dmd": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/dmd/-/dmd-6.2.0.tgz", + "integrity": "sha512-uXWxLF1H7TkUAuoHK59/h/ts5cKavm2LnhrIgJWisip4BVzPoXavlwyoprFFn2CzcahKYgvkfaebS6oxzgflkg==", "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "array-back": "^6.2.2", + "cache-point": "^2.0.0", + "common-sequence": "^2.0.2", + "file-set": "^4.0.2", + "handlebars": "^4.7.7", + "marked": "^4.2.3", + "object-get": "^2.1.1", + "reduce-flatten": "^3.0.1", + "reduce-unique": "^2.0.1", + "reduce-without": "^1.0.1", + "test-value": "^3.0.0", + "walk-back": "^5.1.0" }, "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/documentation/node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/documentation/node_modules/ini": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-3.0.1.tgz", - "integrity": "sha512-it4HyVAUTKBc6m8e1iXWvXSTdndF7HbdN713+kvLrymxTaU4AUBWrJ4vEooP+V7fexnVD3LKcBshjGGPefSMUQ==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/documentation/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "node_modules/dmd/node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/documentation/node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", - "dev": true, - "dependencies": { - "p-locate": "^6.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/documentation/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/documentation/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/documentation/node_modules/normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/documentation/node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/documentation/node_modules/p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", - "dev": true, - "dependencies": { - "p-limit": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/documentation/node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/documentation/node_modules/pify": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-6.1.0.tgz", - "integrity": "sha512-KocF8ve28eFjjuBKKGvzOBGzG8ew2OqOOSxTTZhirkzH7h3BI1vyzqlR0qbfcDBve1Yzo3FVlWUAtCRrbVN8Fw==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/documentation/node_modules/read-pkg": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-7.1.0.tgz", - "integrity": "sha512-5iOehe+WF75IccPc30bWTbpdDQLOCc3Uu8bi3Dte3Eueij81yx1Mrufk8qBx/YAbR4uL1FdUr+7BKXDwEtisXg==", - "dev": true, - "dependencies": { - "@types/normalize-package-data": "^2.4.1", - "normalize-package-data": "^3.0.2", - "parse-json": "^5.2.0", - "type-fest": "^2.0.0" - }, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/documentation/node_modules/read-pkg-up": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-9.1.0.tgz", - "integrity": "sha512-vaMRR1AC1nrd5CQM0PhlRsO5oc2AAigqr7cCrZ/MW/Rsaflz4RlgzkpL4qoU/z1F6wrbd85iFv1OQj/y5RdGvg==", - "dev": true, - "dependencies": { - "find-up": "^6.3.0", - "read-pkg": "^7.1.0", - "type-fest": "^2.5.0" + "marked": "bin/marked.js" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 12" } }, - "node_modules/documentation/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "esutils": "^2.0.2" }, "engines": { - "node": ">=10" - } - }, - "node_modules/documentation/node_modules/strip-json-comments": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.1.tgz", - "integrity": "sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/documentation/node_modules/unist-util-visit": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", - "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^5.0.0", - "unist-util-visit-parents": "^5.1.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "node": ">=6.0.0" } }, - "node_modules/documentation/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "node_modules/doctypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", + "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==", "dev": true }, - "node_modules/documentation/node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", - "dev": true, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/dom-accessibility-api": { "version": "0.5.16", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", @@ -16240,6 +16065,70 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-set": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/file-set/-/file-set-4.0.2.tgz", + "integrity": "sha512-fuxEgzk4L8waGXaAkd8cMr73Pm0FxOVkn8hztzUW7BAHhOGH90viQNXbiOsnecCWmfInqU6YmAMwxRMdKETceQ==", + "dev": true, + "dependencies": { + "array-back": "^5.0.0", + "glob": "^7.1.6" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/file-set/node_modules/array-back": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-5.0.0.tgz", + "integrity": "sha512-kgVWwJReZWmVuWOQKEOohXKJX+nD02JAZ54D1RRWlv8L0NebauKAaFxACKzB74RTclt1+WNz5KHaLRDAPZbDEw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/file-set/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/file-set/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/file-set/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/file-system-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/file-system-cache/-/file-system-cache-2.3.0.tgz", @@ -16786,6 +16675,15 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/fs-then-native": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fs-then-native/-/fs-then-native-2.0.0.tgz", + "integrity": "sha512-X712jAOaWXkemQCAmWeg5rOT2i+KOpWz1Z/txk/cW0qlOu2oQ9H61vc5w3X/iyuUEfq/OyaFJ78/cZAQD1/bgA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -17072,12 +16970,6 @@ "node": ">=4" } }, - "node_modules/globals-docs": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/globals-docs/-/globals-docs-2.4.1.tgz", - "integrity": "sha512-qpPnUKkWnz8NESjrCvnlGklsgiQzlq+rcCxoG5uNQ+dNA7cFMCmn231slLAwS2N/PlkzZ3COL8CcS10jXmLHqg==", - "dev": true - }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -17281,212 +17173,6 @@ "node": ">=8" } }, - "node_modules/hast-util-from-parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz", - "integrity": "sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==", - "dev": true, - "dependencies": { - "@types/hast": "^2.0.0", - "@types/unist": "^2.0.0", - "hastscript": "^7.0.0", - "property-information": "^6.0.0", - "vfile": "^5.0.0", - "vfile-location": "^4.0.0", - "web-namespaces": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-parse-selector": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", - "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", - "dev": true, - "dependencies": { - "@types/hast": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-raw": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-7.2.3.tgz", - "integrity": "sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==", - "dev": true, - "dependencies": { - "@types/hast": "^2.0.0", - "@types/parse5": "^6.0.0", - "hast-util-from-parse5": "^7.0.0", - "hast-util-to-parse5": "^7.0.0", - "html-void-elements": "^2.0.0", - "parse5": "^6.0.0", - "unist-util-position": "^4.0.0", - "unist-util-visit": "^4.0.0", - "vfile": "^5.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-raw/node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true - }, - "node_modules/hast-util-raw/node_modules/unist-util-visit": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", - "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^5.0.0", - "unist-util-visit-parents": "^5.1.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-sanitize": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-4.1.0.tgz", - "integrity": "sha512-Hd9tU0ltknMGRDv+d6Ro/4XKzBqQnP/EZrpiTbpFYfXv/uOhWeKc+2uajcbEvAEH98VZd7eII2PiXm13RihnLw==", - "dev": true, - "dependencies": { - "@types/hast": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-html": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-8.0.4.tgz", - "integrity": "sha512-4tpQTUOr9BMjtYyNlt0P50mH7xj0Ks2xpo8M943Vykljf99HW6EzulIoJP1N3eKOSScEHzyzi9dm7/cn0RfGwA==", - "dev": true, - "dependencies": { - "@types/hast": "^2.0.0", - "@types/unist": "^2.0.0", - "ccount": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-raw": "^7.0.0", - "hast-util-whitespace": "^2.0.0", - "html-void-elements": "^2.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "stringify-entities": "^4.0.0", - "zwitch": "^2.0.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-html/node_modules/space-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", - "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/hast-util-to-parse5": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz", - "integrity": "sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==", - "dev": true, - "dependencies": { - "@types/hast": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-parse5/node_modules/space-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", - "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/hast-util-whitespace": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", - "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hastscript": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", - "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", - "dev": true, - "dependencies": { - "@types/hast": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-parse-selector": "^3.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hastscript/node_modules/space-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", - "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "optional": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/highlight.js": { - "version": "11.8.0", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.8.0.tgz", - "integrity": "sha512-MedQhoqVdr0U6SSnWPzfiadUcDHfN/Wzq25AkXiQv9oiOO/sG0S7XkvpFIqWBl9Yq1UYyYOOVORs5UW2XlPyzg==", - "dev": true, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -17547,16 +17233,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/html-void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz", - "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/htmlparser2": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", @@ -17984,19 +17660,6 @@ "node": ">= 0.10" } }, - "node_modules/is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", - "dev": true, - "dependencies": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-absolute-url": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", @@ -18006,15 +17669,6 @@ "node": ">=8" } }, - "node_modules/is-absolute/node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-alphabetical": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", @@ -18435,18 +18089,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", - "dev": true, - "dependencies": { - "is-unc-path": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-set": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", @@ -18552,18 +18194,6 @@ "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", "dev": true }, - "node_modules/is-unc-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", - "dev": true, - "dependencies": { - "unc-path-regex": "^0.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -22449,6 +22079,15 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "dev": true, + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, "node_modules/jscodeshift": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.14.0.tgz", @@ -22572,6 +22211,114 @@ "signal-exit": "^3.0.2" } }, + "node_modules/jsdoc": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.2.tgz", + "integrity": "sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.15", + "@jsdoc/salty": "^0.2.1", + "@types/markdown-it": "^12.2.3", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "underscore": "~1.13.2" + }, + "bin": { + "jsdoc": "jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsdoc-api": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/jsdoc-api/-/jsdoc-api-8.0.0.tgz", + "integrity": "sha512-Rnhor0suB1Ds1abjmFkFfKeD+kSMRN9oHMTMZoJVUrmtCGDwXty+sWMA9sa4xbe4UyxuPjhC7tavZ40mDKK6QQ==", + "dev": true, + "dependencies": { + "array-back": "^6.2.2", + "cache-point": "^2.0.0", + "collect-all": "^1.0.4", + "file-set": "^4.0.2", + "fs-then-native": "^2.0.0", + "jsdoc": "^4.0.0", + "object-to-spawn-args": "^2.0.1", + "temp-path": "^1.0.0", + "walk-back": "^5.1.0" + }, + "engines": { + "node": ">=12.17" + } + }, + "node_modules/jsdoc-parse": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsdoc-parse/-/jsdoc-parse-6.2.0.tgz", + "integrity": "sha512-Afu1fQBEb7QHt6QWX/6eUWvYHJofB90Fjx7FuJYF7mnG9z5BkAIpms1wsnvYLytfmqpEENHs/fax9p8gvMj7dw==", + "dev": true, + "dependencies": { + "array-back": "^6.2.2", + "lodash.omit": "^4.5.0", + "lodash.pick": "^4.4.0", + "reduce-extract": "^1.0.0", + "sort-array": "^4.1.5", + "test-value": "^3.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jsdoc-to-markdown": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/jsdoc-to-markdown/-/jsdoc-to-markdown-8.0.0.tgz", + "integrity": "sha512-2FQvYkg491+FP6s15eFlgSSWs69CvQrpbABGYBtvAvGWy/lWo8IKKToarT283w59rQFrpcjHl3YdhHCa3l7gXg==", + "dev": true, + "dependencies": { + "array-back": "^6.2.2", + "command-line-tool": "^0.8.0", + "config-master": "^3.1.0", + "dmd": "^6.2.0", + "jsdoc-api": "^8.0.0", + "jsdoc-parse": "^6.2.0", + "walk-back": "^5.1.0" + }, + "bin": { + "jsdoc2md": "bin/cli.js" + }, + "engines": { + "node": ">=12.17" + } + }, + "node_modules/jsdoc/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jsdoc/node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/jsdom": { "version": "22.1.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz", @@ -22784,6 +22531,15 @@ "node": ">=0.10.0" } }, + "node_modules/klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.9" + } + }, "node_modules/kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", @@ -22799,16 +22555,6 @@ "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", "dev": true }, - "node_modules/konan": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/konan/-/konan-2.1.1.tgz", - "integrity": "sha512-7ZhYV84UzJ0PR/RJnnsMZcAbn+kLasJhVNWsu8ZyVEJYRpGA5XESQ9d/7zOa08U0Ou4cmB++hMNY/3OSV9KIbg==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.10.5", - "@babel/traverse": "^7.10.5" - } - }, "node_modules/lazy-universal-dotenv": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/lazy-universal-dotenv/-/lazy-universal-dotenv-4.0.0.tgz", @@ -22851,6 +22597,15 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dev": true, + "dependencies": { + "uc.micro": "^1.0.1" + } + }, "node_modules/lit": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/lit/-/lit-2.8.0.tgz", @@ -23034,6 +22789,24 @@ "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", "dev": true }, + "node_modules/lodash.omit": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz", + "integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==", + "dev": true + }, + "node_modules/lodash.padend": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.6.1.tgz", + "integrity": "sha512-sOQs2aqGpbl27tmCS1QNZA09Uqp01ZzWfDUoD+xzTii0E7dSQfRKcRetFwa+uXaxaqL+TKm7CgD2JdKP7aZBSw==", + "dev": true + }, + "node_modules/lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==", + "dev": true + }, "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -23199,15 +22972,6 @@ "tmpl": "1.0.5" } }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/map-obj": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", @@ -23226,6 +22990,47 @@ "integrity": "sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==", "dev": true }, + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it-anchor": { + "version": "8.6.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", + "dev": true, + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/markdown-table": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", @@ -23514,25 +23319,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-inject": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-inject/-/mdast-util-inject-1.1.0.tgz", - "integrity": "sha512-CcJ0mHa36QYumDKiZ2OIR+ClhfOM7zIzN+Wfy8tRZ1hpH9DKLCS+Mh4DyK5bCxzE9uxMWcbIpeNFWsg1zrj/2g==", - "dev": true, - "dependencies": { - "mdast-util-to-string": "^1.0.0" - } - }, - "node_modules/mdast-util-inject/node_modules/mdast-util-to-string": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz", - "integrity": "sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/mdast-util-phrasing": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz", @@ -23547,56 +23333,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-to-hast": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", - "integrity": "sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==", - "dev": true, - "dependencies": { - "@types/hast": "^2.0.0", - "@types/mdast": "^3.0.0", - "mdast-util-definitions": "^5.0.0", - "micromark-util-sanitize-uri": "^1.1.0", - "trim-lines": "^3.0.0", - "unist-util-generated": "^2.0.0", - "unist-util-position": "^4.0.0", - "unist-util-visit": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-hast/node_modules/mdast-util-definitions": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", - "integrity": "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==", - "dev": true, - "dependencies": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "unist-util-visit": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-hast/node_modules/unist-util-visit": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", - "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^5.0.0", - "unist-util-visit-parents": "^5.1.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/mdast-util-to-markdown": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz", @@ -23645,46 +23381,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-toc": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/mdast-util-toc/-/mdast-util-toc-6.1.1.tgz", - "integrity": "sha512-Er21728Kow8hehecK2GZtb7Ny3omcoPUVrmObiSUwmoRYVZaXLR751QROEFjR8W/vAQdHMLj49Lz20J55XaNpw==", - "dev": true, - "dependencies": { - "@types/extend": "^3.0.0", - "@types/mdast": "^3.0.0", - "extend": "^3.0.0", - "github-slugger": "^2.0.0", - "mdast-util-to-string": "^3.1.0", - "unist-util-is": "^5.0.0", - "unist-util-visit": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-toc/node_modules/github-slugger": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", - "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", - "dev": true - }, - "node_modules/mdast-util-toc/node_modules/unist-util-visit": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", - "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^5.0.0", - "unist-util-visit-parents": "^5.1.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/mdn-data": { "version": "2.0.30", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", @@ -23692,6 +23388,12 @@ "dev": true, "peer": true }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "dev": true + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -24484,6 +24186,12 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "dev": true }, + "node_modules/mkdirp2": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/mkdirp2/-/mkdirp2-1.0.5.tgz", + "integrity": "sha512-xOE9xbICroUDmG1ye2h4bZ8WBie9EGmACaco8K8cx6RlkJJrxGIqjGqztAI+NMhexXBcdGbSEzI6N3EJPevxZw==", + "dev": true + }, "node_modules/mlly": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz", @@ -28144,6 +27852,12 @@ "node": ">=0.10.0" } }, + "node_modules/object-get": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/object-get/-/object-get-2.1.1.tgz", + "integrity": "sha512-7n4IpLMzGGcLEMiQKsNR7vCe+N5E9LORFrtNUVy4sO3dj9a3HedZCxEL2T7QuLhcHN1NBuBsMOKaOsAYI9IIvg==", + "dev": true + }, "node_modules/object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", @@ -28178,6 +27892,15 @@ "node": ">= 0.4" } }, + "node_modules/object-to-spawn-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object-to-spawn-args/-/object-to-spawn-args-2.0.1.tgz", + "integrity": "sha512-6FuKFQ39cOID+BMZ3QaphcC8Y4cw6LXBLyIgPU+OhIYwviJamPAn+4mITapnSBQrejB+NNp+FMskhD8Cq+Ys3w==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/object.assign": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", @@ -28598,20 +28321,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/parse-filepath": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", - "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", - "dev": true, - "dependencies": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -28723,27 +28432,6 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "node_modules/path-root": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", - "dev": true, - "dependencies": { - "path-root-regex": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-root-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-scurry": { "version": "1.10.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", @@ -29215,16 +28903,6 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, - "node_modules/property-information": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.3.0.tgz", - "integrity": "sha512-gVNZ74nqhRMiIUYWGQdosYetaKc83x8oT41a0LlV3AAFCAZwCpg4vmGkq8t34+cUhp3cnM4XDiU/7xlgK7HGrg==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -30041,6 +29719,110 @@ "esprima": "~4.0.0" } }, + "node_modules/reduce-extract": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/reduce-extract/-/reduce-extract-1.0.0.tgz", + "integrity": "sha512-QF8vjWx3wnRSL5uFMyCjDeDc5EBMiryoT9tz94VvgjKfzecHAVnqmXAwQDcr7X4JmLc2cjkjFGCVzhMqDjgR9g==", + "dev": true, + "dependencies": { + "test-value": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/reduce-extract/node_modules/array-back": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz", + "integrity": "sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw==", + "dev": true, + "dependencies": { + "typical": "^2.6.0" + }, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/reduce-extract/node_modules/test-value": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/test-value/-/test-value-1.1.0.tgz", + "integrity": "sha512-wrsbRo7qP+2Je8x8DsK8ovCGyxe3sYfQwOraIY/09A2gFXU9DYKiTF14W4ki/01AEh56kMzAmlj9CaHGDDUBJA==", + "dev": true, + "dependencies": { + "array-back": "^1.0.2", + "typical": "^2.4.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/reduce-extract/node_modules/typical": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", + "integrity": "sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==", + "dev": true + }, + "node_modules/reduce-flatten": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-3.0.1.tgz", + "integrity": "sha512-bYo+97BmUUOzg09XwfkwALt4PQH1M5L0wzKerBt6WLm3Fhdd43mMS89HiT1B9pJIqko/6lWx3OnV4J9f2Kqp5Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/reduce-unique": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/reduce-unique/-/reduce-unique-2.0.1.tgz", + "integrity": "sha512-x4jH/8L1eyZGR785WY+ePtyMNhycl1N2XOLxhCbzZFaqF4AXjLzqSxa2UHgJ2ZVR/HHyPOvl1L7xRnW8ye5MdA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/reduce-without": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/reduce-without/-/reduce-without-1.0.1.tgz", + "integrity": "sha512-zQv5y/cf85sxvdrKPlfcRzlDn/OqKFThNimYmsS3flmkioKvkUGn2Qg9cJVoQiEvdxFGLE0MQER/9fZ9sUqdxg==", + "dev": true, + "dependencies": { + "test-value": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/reduce-without/node_modules/array-back": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz", + "integrity": "sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw==", + "dev": true, + "dependencies": { + "typical": "^2.6.0" + }, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/reduce-without/node_modules/test-value": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/test-value/-/test-value-2.1.0.tgz", + "integrity": "sha512-+1epbAxtKeXttkGFMTX9H42oqzOTufR1ceCF+GYA5aOmvaPq9wd4PUS8329fn2RRLGNeUkgRLnVpycjx8DsO2w==", + "dev": true, + "dependencies": { + "array-back": "^1.0.3", + "typical": "^2.6.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/reduce-without/node_modules/typical": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", + "integrity": "sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==", + "dev": true + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -30165,22 +29947,6 @@ "node": ">=4" } }, - "node_modules/remark": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/remark/-/remark-14.0.3.tgz", - "integrity": "sha512-bfmJW1dmR2LvaMJuAnE88pZP9DktIFYXazkTfOIKZzi3Knk9lT0roItIA24ydOucI3bV/g/tXBA6hzqq3FV9Ew==", - "dev": true, - "dependencies": { - "@types/mdast": "^3.0.0", - "remark-parse": "^10.0.0", - "remark-stringify": "^10.0.0", - "unified": "^10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/remark-external-links": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/remark-external-links/-/remark-external-links-8.0.0.tgz", @@ -30214,68 +29980,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/remark-html": { - "version": "15.0.2", - "resolved": "https://registry.npmjs.org/remark-html/-/remark-html-15.0.2.tgz", - "integrity": "sha512-/CIOI7wzHJzsh48AiuIyIe1clxVkUtreul73zcCXLub0FmnevQE0UMFDQm7NUx8/3rl/4zCshlMfqBdWScQthw==", - "dev": true, - "dependencies": { - "@types/mdast": "^3.0.0", - "hast-util-sanitize": "^4.0.0", - "hast-util-to-html": "^8.0.0", - "mdast-util-to-hast": "^12.0.0", - "unified": "^10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-parse": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.2.tgz", - "integrity": "sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==", - "dev": true, - "dependencies": { - "@types/mdast": "^3.0.0", - "mdast-util-from-markdown": "^1.0.0", - "unified": "^10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-reference-links": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/remark-reference-links/-/remark-reference-links-6.0.1.tgz", - "integrity": "sha512-34wY2C6HXSuKVTRtyJJwefkUD8zBOZOSHFZ4aSTnU2F656gr9WeuQ2dL6IJDK3NPd2F6xKF2t4XXcQY9MygAXg==", - "dev": true, - "dependencies": { - "@types/mdast": "^3.0.0", - "unified": "^10.0.0", - "unist-util-visit": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-reference-links/node_modules/unist-util-visit": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", - "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^5.0.0", - "unist-util-visit-parents": "^5.1.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/remark-slug": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/remark-slug/-/remark-slug-6.1.0.tgz", @@ -30362,36 +30066,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/remark-toc": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/remark-toc/-/remark-toc-8.0.1.tgz", - "integrity": "sha512-7he2VOm/cy13zilnOTZcyAoyoolV26ULlon6XyCFU+vG54Z/LWJnwphj/xKIDLOt66QmJUgTyUvLVHi2aAElyg==", - "dev": true, - "dependencies": { - "@types/mdast": "^3.0.0", - "mdast-util-toc": "^6.0.0", - "unified": "^10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark/node_modules/remark-stringify": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-10.0.3.tgz", - "integrity": "sha512-koyOzCMYoUHudypbj4XpnAKFbkddRMYZHwghnxd7ue5210WzGw6kOBwauJTRUMq16jsovXx8dYNvSSWP89kZ3A==", - "dev": true, - "dependencies": { - "@types/mdast": "^3.0.0", - "mdast-util-to-markdown": "^1.0.0", - "unified": "^10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", @@ -30422,6 +30096,15 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "dev": true }, + "node_modules/requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21" + } + }, "node_modules/resolve": { "version": "1.22.4", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", @@ -31541,6 +31224,37 @@ "node": ">=8" } }, + "node_modules/sort-array": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/sort-array/-/sort-array-4.1.5.tgz", + "integrity": "sha512-Ya4peoS1fgFN42RN1REk2FgdNOeLIEMKFGJvs7VTP3OklF8+kl2SkpVliZ4tk/PurWsrWRsdNdU+tgyOBkB9sA==", + "dev": true, + "dependencies": { + "array-back": "^5.0.0", + "typical": "^6.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sort-array/node_modules/array-back": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-5.0.0.tgz", + "integrity": "sha512-kgVWwJReZWmVuWOQKEOohXKJX+nD02JAZ54D1RRWlv8L0NebauKAaFxACKzB74RTclt1+WNz5KHaLRDAPZbDEw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/sort-array/node_modules/typical": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/typical/-/typical-6.0.1.tgz", + "integrity": "sha512-+g3NEp7fJLe9DPa1TArHm9QAA7YciZmWnfAqEaFrBihQ7epOv9i99rjtgb6Iz0wh3WuQDjsCTDfgRoGnmHN81A==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -31965,12 +31679,51 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/stream-connect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-connect/-/stream-connect-1.0.2.tgz", + "integrity": "sha512-68Kl+79cE0RGKemKkhxTSg8+6AGrqBt+cbZAXevg2iJ6Y3zX4JhA/sZeGzLpxW9cXhmqAcE7KnJCisUmIUfnFQ==", + "dev": true, + "dependencies": { + "array-back": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stream-connect/node_modules/array-back": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz", + "integrity": "sha512-1WxbZvrmyhkNoeYcizokbmh5oiOCIfyvGtcqbK3Ls1v1fKcquzxnQSceOx6tzq7jmai2kFLWIpGND2cLhH6TPw==", + "dev": true, + "dependencies": { + "typical": "^2.6.0" + }, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/stream-connect/node_modules/typical": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", + "integrity": "sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==", + "dev": true + }, "node_modules/stream-shift": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "dev": true }, + "node_modules/stream-via": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/stream-via/-/stream-via-1.0.4.tgz", + "integrity": "sha512-DBp0lSvX5G9KGRDTkR/R+a29H+Wk2xItOF+MpZLLNDWbEV9tGPnqLPxHEYjmiz8xGtJHRIqmI+hCjmNzqoA4nQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -32090,30 +31843,6 @@ "node": ">=8" } }, - "node_modules/stringify-entities": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.3.tgz", - "integrity": "sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==", - "dev": true, - "dependencies": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^3.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/stringify-entities/node_modules/character-entities-legacy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/strip-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", @@ -32381,6 +32110,40 @@ "integrity": "sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==", "dev": true }, + "node_modules/table-layout": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-0.4.5.tgz", + "integrity": "sha512-zTvf0mcggrGeTe/2jJ6ECkJHAQPIYEwDoqsiqBjI24mvRmQbInK5jq33fyypaCBxX08hMkfmdOqj6haT33EqWw==", + "dev": true, + "dependencies": { + "array-back": "^2.0.0", + "deep-extend": "~0.6.0", + "lodash.padend": "^4.6.1", + "typical": "^2.6.1", + "wordwrapjs": "^3.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/table-layout/node_modules/array-back": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", + "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", + "dev": true, + "dependencies": { + "typical": "^2.6.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/table-layout/node_modules/typical": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", + "integrity": "sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==", + "dev": true + }, "node_modules/tar": { "version": "6.1.15", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", @@ -32477,6 +32240,12 @@ "node": ">=8" } }, + "node_modules/temp-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/temp-path/-/temp-path-1.0.0.tgz", + "integrity": "sha512-TvmyH7kC6ZVTYkqCODjJIbgvu0FKiwQpZ4D1aknE7xpcDf/qEOB8KZEK5ef2pfbVoiBhNWs3yx4y+ESMtNYmlg==", + "dev": true + }, "node_modules/temp/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -32634,6 +32403,37 @@ "node": "*" } }, + "node_modules/test-value": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/test-value/-/test-value-3.0.0.tgz", + "integrity": "sha512-sVACdAWcZkSU9x7AOmJo5TqE+GyNJknHaHsMrR6ZnhjVlVN9Yx6FjHrsKZ3BjIpPCT68zYesPWkakrNupwfOTQ==", + "dev": true, + "dependencies": { + "array-back": "^2.0.0", + "typical": "^2.6.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/test-value/node_modules/array-back": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", + "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", + "dev": true, + "dependencies": { + "typical": "^2.6.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/test-value/node_modules/typical": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", + "integrity": "sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==", + "dev": true + }, "node_modules/text-extensions": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", @@ -32836,16 +32636,6 @@ "tree-kill": "cli.js" } }, - "node_modules/trim-lines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", - "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -32990,6 +32780,12 @@ "node": ">=8" } }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, "node_modules/ufo": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.0.tgz", @@ -33009,14 +32805,11 @@ "node": ">=0.8.0" } }, - "node_modules/unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", + "dev": true }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", @@ -33089,29 +32882,6 @@ "node": ">=8" } }, - "node_modules/unist-builder": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-3.0.1.tgz", - "integrity": "sha512-gnpOw7DIpCA0vpr6NqdPvTWnlPTApCTRzr+38E6hCWx3rz/cjo83SsKIlS1Z+L5ttScQ2AwutNnb8+tAvpb6qQ==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-generated": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz", - "integrity": "sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/unist-util-is": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", @@ -33125,19 +32895,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/unist-util-position": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz", - "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/unist-util-stringify-position": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", @@ -33485,20 +33242,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/vfile-location": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.1.0.tgz", - "integrity": "sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0", - "vfile": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/vfile-message": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", @@ -33513,83 +33256,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/vfile-reporter": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/vfile-reporter/-/vfile-reporter-7.0.5.tgz", - "integrity": "sha512-NdWWXkv6gcd7AZMvDomlQbK3MqFWL1RlGzMn++/O2TI+68+nqxCPTvLugdOtfSzXmjh+xUyhp07HhlrbJjT+mw==", - "dev": true, - "dependencies": { - "@types/supports-color": "^8.0.0", - "string-width": "^5.0.0", - "supports-color": "^9.0.0", - "unist-util-stringify-position": "^3.0.0", - "vfile": "^5.0.0", - "vfile-message": "^3.0.0", - "vfile-sort": "^3.0.0", - "vfile-statistics": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-reporter/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vfile-reporter/node_modules/supports-color": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", - "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/vfile-sort": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/vfile-sort/-/vfile-sort-3.0.1.tgz", - "integrity": "sha512-1os1733XY6y0D5x0ugqSeaVJm9lYgj0j5qdcZQFyxlZOSy1jYarL77lLyb5gK4Wqr1d5OxmuyflSO3zKyFnTFw==", - "dev": true, - "dependencies": { - "vfile": "^5.0.0", - "vfile-message": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-statistics": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/vfile-statistics/-/vfile-statistics-2.0.1.tgz", - "integrity": "sha512-W6dkECZmP32EG/l+dp2jCLdYzmnDBIw6jwiLZSER81oR5AHRcVqL+k3Z+pfH1R73le6ayDkJRMk0sutj1bMVeg==", - "dev": true, - "dependencies": { - "vfile": "^5.0.0", - "vfile-message": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/vite": { "version": "4.4.9", "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", @@ -34278,17 +33944,6 @@ "vue": ">=2" } }, - "node_modules/vue-template-compiler": { - "version": "2.7.14", - "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz", - "integrity": "sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==", - "dev": true, - "optional": true, - "dependencies": { - "de-indent": "^1.0.2", - "he": "^1.2.0" - } - }, "node_modules/w3c-xmlserializer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", @@ -34343,6 +33998,15 @@ "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", "dev": true }, + "node_modules/walk-back": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-5.1.0.tgz", + "integrity": "sha512-Uhxps5yZcVNbLEAnb+xaEEMdgTXl9qAQDzKYejG2AZ7qPwRQ81lozY9ECDbjLPNWm7YsO1IK5rsP1KoQzXAcGA==", + "dev": true, + "engines": { + "node": ">=12.17" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -34374,16 +34038,6 @@ "defaults": "^1.0.3" } }, - "node_modules/web-namespaces": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", - "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -34565,6 +34219,34 @@ "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true }, + "node_modules/wordwrapjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-3.0.0.tgz", + "integrity": "sha512-mO8XtqyPvykVCsrwj5MlOVWvSnCdT+C+QVbm6blradR7JExAhbkZ7hZ9A+9NUtwzSqrlUo9a67ws0EiILrvRpw==", + "dev": true, + "dependencies": { + "reduce-flatten": "^1.0.1", + "typical": "^2.6.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/wordwrapjs/node_modules/reduce-flatten": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-1.0.1.tgz", + "integrity": "sha512-j5WfFJfc9CoXv/WbwVLHq74i/hdTUpy+iNC534LxczMRP67vJeK3V9JOdnL0N1cIRbn9mYhE2yVjvvKXDxvNXQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrapjs/node_modules/typical": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", + "integrity": "sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==", + "dev": true + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -34717,6 +34399,12 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "dev": true + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index cf5960a..b615ee7 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,11 @@ "dist": "node lib/esbuild.config.js", "prerelease": "npm run dist", "release": "semantic-release --debug=true", + "test:unit": "node --test --experimental-test-coverage", "generate:primer": "node lib/cli.js generate-vars-primer", "generate:fixtures": "node lib/cli.js generate-fixtures-github", - "generate": "npm run generate:primer && npm run generate:fixtures && npm run cem" + "generate:docs": "node lib/cli.js generate-docs", + "generate": "npm run generate:docs && npm run generate:primer && npm run generate:fixtures && npm run cem" }, "type": "module", "version": "0.0.0", @@ -51,10 +53,10 @@ "@primer/octicons": "^19.8.0", "@primer/primitives": "^7.13.0", "commander": "^11.0.0", - "documentation": "^14.0.2", "esbuild": "^0.19.3", "esbuild-plugin-inline-import": "^1.0.1", "fs-extra": "^11.1.1", + "jsdoc-to-markdown": "^8.0.0", "storydocker-storybook": "^0.0.16", "storydocker-utilities": "^0.0.11", "yaml": "^2.3.2" diff --git a/src/github/repository.js b/src/github/repository.js deleted file mode 100644 index 618bfac..0000000 --- a/src/github/repository.js +++ /dev/null @@ -1,145 +0,0 @@ -import { intToString, fetchRepo, parseFetchedRepo } from './utils/github'; -import stylesPrimer from './styles/vars-primer.css?inline'; -import stylesGlobal from './styles/vars-global.css?inline'; -import styles from './styles/repository.css?inline'; - -/** - * Styles for the component, imported during development, inlined during build - */ -const componentStyles = ` -${stylesPrimer} -${stylesGlobal} -${styles} -`; - -/** - * GitHub repository UI component - * When `fetch` is true, fetches repo from GitHub api, but content from the - * api _will be superseded_ by any attributes set on the element - * @see https://docs.github.com/en/rest/repos/repos#get-a-repository - * @element github-repository - * @name GitHubRepository - * - * @attribute {string} full_name - repository org and name, as in `scottnath/profile-components` - * @attribute {boolean} [fetch] - when true, fetches repo from GitHub api - * @attribute {string} [name] - repo name - * @attribute {string} [org] - repo owner organization's login, found at `.organization.login` - * @attribute {string} [description] - repo description - * @attribute {string} [language] - programming language used in repo - * @attribute {string} [stargazers_count] - number of stars - * @attribute {string} [forks_count] - number of forks - * @attribute {string} [subscribers_count] - number of watchers - * @attribute {string} [itemprop] - Itemprop content to go with a containing - * component's itemscope - * @attribute {string} [user_login] - User or organization login for use by - * containing component - */ -export class GitHubRepository extends HTMLElement { - constructor() { - super(); - this.error = null; - this.attachShadow({ mode: "open" }); - this._getAttributes(); - } - - /** - * Generate variables at `this.[attribute-name]` for each attribute on the element - */ - _getAttributes() { - for (let name of this.getAttributeNames()) { - if (this.getAttribute(name)) { - this[name] = this.getAttribute(name); - } - } - } - - /** - * Check that required attributes are present, adjusts content as needed - */ - _checkAttributes() { - if (!this.full_name || !this.full_name.split('/')[1]) { - this.error = 'Missing repo attribute: `full_name`'; - return; - } - if (!this.name) { - this.name = this.full_name.split('/')[1]; - } - if (!this.org) { - this.org = this.full_name.split('/')[0]; - } - if (this.org && this.user_login) { - delete this.org; - } - } - - /** - * Fetch repo data from GitHub API and parse out - * the content needed for this component. Either adds an error - * or adds the parsed content to the element's attributes - */ - async _parseFetch() { - const repo = await fetchRepo(this.full_name); - if (repo.message && repo.message === 'Not Found') { - this.error = `Repo "${this.full_name}" not found`; - return; - } - const parsedRepo = parseFetchedRepo(repo); - Object.entries(parsedRepo).forEach(([key, value]) => { - this[key] = this[key] || value; - }); - } - - async connectedCallback() { - let view = ``; - if (this.fetch) { - await this._parseFetch(); - } - this._checkAttributes(); - view += this._render(); - this.shadowRoot.innerHTML = view; - } - - _render() { - if (this.error) { - return ` -
-

${this.error}

-
- ` - } - - return ` -
- - ${this.org ? ` - ${this.org} / - ` : ''} - ${this.name} - - ${this.description ? ` -

${this.description}

- ` : ''} -
- ${this.language ? ` -
Language
-
${this.language}
- ` : ''} - ${this.stargazers_count ? ` -
Stars
-
${this.stargazers_count}
- ` : ''} - ${this.subscribers_count ? ` -
Watchers
-
${this.subscribers_count}
- ` : ''} - ${this.forks_count ? ` -
Forks
-
${this.forks_count}
- ` : ''} -
-
- `; - } -} - -customElements.define('github-repository', GitHubRepository); diff --git a/src/github/repository/README.md b/src/github/repository/README.md new file mode 100644 index 0000000..7343f4c --- /dev/null +++ b/src/github/repository/README.md @@ -0,0 +1,168 @@ +# GitHub repository details component + +## Modules + +
+
GitHub-Repo-Utilities
+

Utility functions for fetching and parsing GitHub Repository data

+
+
+ +## Members + +
+
GitHubRepository
+

GitHub repository web component

+
+
+ +## Constants + +
+
componentStyles
+

Styles for the component, imported during development, inlined during build

+
+
+ +## Functions + +
+
repository(content)string
+

GitHub repository HTML generation

+
+
+ + + +## GitHub-Repo-Utilities +Utility functions for fetching and parsing GitHub Repository data + +**Author**: @scottnath + +* [GitHub-Repo-Utilities](#module_GitHub-Repo-Utilities) + * [.fetchRepo(full_name)](#module_GitHub-Repo-Utilities.fetchRepo) ⇒ Object + * [.parseFetchedRepo(repo)](#module_GitHub-Repo-Utilities.parseFetchedRepo) ⇒ GitHubRepositoryHTML + * [.cleanRepoContent(content, [no_org])](#module_GitHub-Repo-Utilities.cleanRepoContent) ⇒ GitHubRepositoryHTML + * [.generateRepoContent(content, [fetch], [no_org])](#module_GitHub-Repo-Utilities.generateRepoContent) ⇒ GitHubRepositoryHTML + * [~GitHubRepositoryHTML](#module_GitHub-Repo-Utilities..GitHubRepositoryHTML) : Object + + + +### githubRepoUtils.fetchRepo(full_name) ⇒ Object +Fetch a GitHub repository's content from the GitHub api + +**Kind**: static method of [GitHub-Repo-Utilities](#module_GitHub-Repo-Utilities) +**Returns**: Object - response status 200: repo; else {Object} error +**See**: https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#get-a-repository + +| Param | Type | +| --- | --- | +| full_name | string | + + + +### githubRepoUtils.parseFetchedRepo(repo) ⇒ GitHubRepositoryHTML +Parse a GitHub repository's content. This is a reducer on the endpoint response, + but generally reduces any object to just the data required for the repo component HTML + +**Kind**: static method of [GitHub-Repo-Utilities](#module_GitHub-Repo-Utilities) + +| Param | Type | Description | +| --- | --- | --- | +| repo | Object | GitHub repository object | + + + +### githubRepoUtils.cleanRepoContent(content, [no_org]) ⇒ GitHubRepositoryHTML +Parses and cleans repository content to match what is expected by the repository HTML + +**Kind**: static method of [GitHub-Repo-Utilities](#module_GitHub-Repo-Utilities) +**Returns**: GitHubRepositoryHTML - ready for HTML content + +| Param | Type | Description | +| --- | --- | --- | +| content | GitHubRepositoryHTML | a content object either from component or GitHub API | +| [no_org] | boolean | if true, remove the `org` attribute from the returned object | + + + +### githubRepoUtils.generateRepoContent(content, [fetch], [no_org]) ⇒ GitHubRepositoryHTML +Generates an object of content for the repository HTML + +**Kind**: static method of [GitHub-Repo-Utilities](#module_GitHub-Repo-Utilities) +**Returns**: GitHubRepositoryHTML - content ready for HTML, possibly includes fetched content + +| Param | Type | +| --- | --- | +| content | GitHubRepositoryHTML | +| [fetch] | boolean | +| [no_org] | boolean | + + + +### GitHub-Repo-Utilities~GitHubRepositoryHTML : Object +Content needed to render a GitHub repository. This is a subset of the `repos` endpoint response + +**Kind**: inner typedef of [GitHub-Repo-Utilities](#module_GitHub-Repo-Utilities) +**Properties** + +| Name | Type | Description | +| --- | --- | --- | +| full_name | string | repository org and name, as in `scottnath/profile-components` | +| name | string | repo name | +| [org] | string | repo owner organization's login, found at `.organization.login` | +| [description] | string | repo description | +| [language] | string | programming language used in repo | +| [stargazers_count] | string | number of stars | +| [forks_count] | string | number of forks | +| [subscribers_count] | string | number of watchers | +| [error] | string | error message, if any | + + + +## GitHubRepository +GitHub repository web component + +**Kind**: global variable +**Summary**: Native web component which shows a GitHub repository's content. Can use local data, + fetch data from the GitHub rest API, or use a combination of both. +**Element**: github-repository +**See**: https://docs.github.com/en/rest/repos/repos#get-a-repository +**Properties** + +| Name | Type | Description | +| --- | --- | --- | +| full_name | string | repository org and name, as in `scottnath/profile-components` | +| [name] | string | repo name | +| [org] | string | repo owner organization's login, found at `.organization.login` | +| [description] | string | repo description | +| [language] | string | programming language used in repo | +| [stargazers_count] | string | number of stars | +| [forks_count] | string | number of forks | +| [subscribers_count] | string | number of watchers | +| [fetch] | boolean | when true, fetches repo from GitHub api | +| [itemprop] | string | Itemprop content to go with a containing component's itemscope | +| [no_org] | string | Do not include the repo owner or organization | + +**Example** +```js + +``` + + +## componentStyles +Styles for the component, imported during development, inlined during build + +**Kind**: global constant + + +## repository(content) ⇒ string +GitHub repository HTML generation + +**Kind**: global function +**Returns**: string - HTML which represents a GitHub repository + +| Param | Type | Description | +| --- | --- | --- | +| content | GitHubRepositoryHTML | content needed to render a GitHub repository | + diff --git a/src/github/repository/content.js b/src/github/repository/content.js new file mode 100644 index 0000000..08b2cac --- /dev/null +++ b/src/github/repository/content.js @@ -0,0 +1,113 @@ +/** + * @name GitHub-Repo-Utilities + * @module + * @typicalname githubRepoUtils + * @description Utility functions for fetching and parsing GitHub Repository data + * @author @scottnath + */ + +/** @ignore */ +const githubApi = 'https://api.github.com'; + +/** + * Content needed to render a GitHub repository. This is a subset of the `repos` endpoint response + * @typedef {Object} GitHubRepositoryHTML + * + * @property {string} full_name - repository org and name, as in `scottnath/profile-components` + * @property {string} name - repo name + * @property {string} [org] - repo owner organization's login, found at `.organization.login` + * @property {string} [description] - repo description + * @property {string} [language] - programming language used in repo + * @property {string} [stargazers_count] - number of stars + * @property {string} [forks_count] - number of forks + * @property {string} [subscribers_count] - number of watchers + * @property {string} [error] - error message, if any + */ + +/** + * Fetch a GitHub repository's content from the GitHub api + * @see https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#get-a-repository + * @param {string} full_name + * @returns response status 200: {Object} repo; else {Object} error + * @function + */ +export const fetchRepo = async (full_name) => { + const response = await fetch(`${githubApi}/repos/${full_name}`); + const repoJson = await response.json(); + return repoJson; +} + +/** + * Parse a GitHub repository's content. This is a reducer on the endpoint response, + * but generally reduces any object to just the data required for the repo component HTML + * @param {Object} repo - GitHub repository object + * @returns {GitHubRepositoryHTML} + * @function + */ +export const parseFetchedRepo = (repo = {}) => { + return { + full_name: repo.full_name, + name: repo.name, + org: repo.org || repo.organization?.login || repo.owner?.login, + description: repo.description, + language: repo.language, + stargazers_count: repo.stargazers_count, + forks_count: repo.forks_count, + subscribers_count: repo.subscribers_count, + } +} + +/** + * Parses and cleans repository content to match what is expected by the repository HTML + * @param {GitHubRepositoryHTML} content - a content object either from component or GitHub API + * @param {boolean} [no_org] - if true, remove the `org` attribute from the returned object + * @returns {GitHubRepositoryHTML} ready for HTML content + * @function + */ +export const cleanRepoContent = (content, no_org) => { + const repo = parseFetchedRepo(content); + if (!repo.full_name || !repo.full_name.split('/')[1]) { + repo.error = 'Missing repo attribute: `full_name`'; + return repo; + } + if (!repo.name) { + repo.name = repo.full_name.split('/')[1]; + } + if (!repo.org) { + repo.org = repo.full_name.split('/')[0]; + } + if (no_org) { + delete repo.org; + } + if (repo.stargazers_count === '0') delete repo.stargazers_count; + if (repo.forks_count === '0') delete repo.forks_count; + if (repo.subscribers_count === '0') delete repo.subscribers_count; + const r = {}; + // remove `undefined` values + for (const key in repo) { + if (repo[key]) r[key] = repo[key]; + } + return r; +} + +/** + * Generates an object of content for the repository HTML + * @param {GitHubRepositoryHTML} content + * @param {boolean} [fetch] + * @param {boolean} [no_org] + * @returns {GitHubRepositoryHTML} content ready for HTML, possibly includes fetched content + * @function + */ +export const generateRepoContent = async (content, fetch = false, no_org = false) => { + const repoFromContent = cleanRepoContent(content, no_org); + if (repoFromContent.error) return repoFromContent; + let fetched = {}; + if (fetch) { + fetched = await fetchRepo(repoFromContent.full_name); + if (fetched.message && fetched.message === 'Not Found') { + return { error: `Fetch Error: Repo "${repoFromContent.full_name}" not found`}; + } + fetched = cleanRepoContent(fetched, no_org); + } + return Object.assign({}, fetched, repoFromContent); +} \ No newline at end of file diff --git a/src/github/repository/html.js b/src/github/repository/html.js new file mode 100644 index 0000000..ce6a2b2 --- /dev/null +++ b/src/github/repository/html.js @@ -0,0 +1,51 @@ + + +import { intToString } from '../../utils/index.js'; + +/** + * GitHub repository HTML generation + * @param {GitHubRepositoryHTML} content - content needed to render a GitHub repository + * @returns {string} HTML which represents a GitHub repository + */ +function repository(content) { + if (content.error) { + return ` +
+

${content.error}

+
+ ` + } + return ` +
+ + ${content.org ? ` + ${content.org} / + ` : ''} + ${content.name} + + ${content.description ? ` +

${content.description}

+ ` : ''} +
+ ${content.language ? ` +
Language
+
${content.language}
+ ` : ''} + ${content.stargazers_count ? ` +
Stars
+
${content.stargazers_count}
+ ` : ''} + ${content.subscribers_count ? ` +
Watchers
+
${content.subscribers_count}
+ ` : ''} + ${content.forks_count ? ` +
Forks
+
${content.forks_count}
+ ` : ''} +
+
+ `; +} + +export default repository; \ No newline at end of file diff --git a/src/github/repository/index.js b/src/github/repository/index.js new file mode 100644 index 0000000..cabd54e --- /dev/null +++ b/src/github/repository/index.js @@ -0,0 +1,74 @@ +import { generateRepoContent } from './content.js'; +import repositoryHTML from './html.js'; +import stylesPrimer from '../styles/vars-primer.css?inline'; +import stylesGlobal from '../styles/vars-global.css?inline'; +import styles from '../styles/repository.css?inline'; + +/** + * Styles for the component, imported during development, inlined during build + */ +const componentStyles = ` +${stylesPrimer} +${stylesGlobal} +${styles} +`; + +/** + * GitHub repository web component + * @summary Native web component which shows a GitHub repository's content. Can use local data, + * fetch data from the GitHub rest API, or use a combination of both. + * @element github-repository + * @name GitHubRepository + * @see https://docs.github.com/en/rest/repos/repos#get-a-repository + * + * @property {string} full_name - repository org and name, as in `scottnath/profile-components` + * @property {string} [name] - repo name + * @property {string} [org] - repo owner organization's login, found at `.organization.login` + * @property {string} [description] - repo description + * @property {string} [language] - programming language used in repo + * @property {string} [stargazers_count] - number of stars + * @property {string} [forks_count] - number of forks + * @property {string} [subscribers_count] - number of watchers + * @property {boolean} [fetch] - when true, fetches repo from GitHub api + * @property {string} [itemprop] - Itemprop content to go with a containing component's itemscope + * @property {string} [no_org] - Do not include the repo owner or organization + * + * @example + * + */ +export class GitHubRepository extends HTMLElement { + /** + * @ignore + */ + constructor() { + super(); + this.attrs = {}; + this.repo = {}; + this.attachShadow({ mode: "open" }); + this._getAttributes(); + } + + /** + * Generate variables at `this.[attribute-name]` for each attribute on the element + * @ignore + */ + _getAttributes() { + for (let name of this.getAttributeNames()) { + if (this.getAttribute(name)) { + this.attrs[name] = this.getAttribute(name); + } + } + } + + async connectedCallback() { + let view = ``; + this.repo = await generateRepoContent(this.attrs, this.attrs.fetch, this.attrs.no_org); + view += repositoryHTML(this.repo); + this.shadowRoot.innerHTML = view; + if (this.attrs.itemprop) { + this.setAttribute('itemprop', this.attrs.itemprop); + } + } +} + +customElements.define('github-repository', GitHubRepository); diff --git a/src/github/repository.shared-spec.js b/src/github/repository/repository.shared-spec.js similarity index 92% rename from src/github/repository.shared-spec.js rename to src/github/repository/repository.shared-spec.js index 5bd287e..87c09ee 100644 --- a/src/github/repository.shared-spec.js +++ b/src/github/repository/repository.shared-spec.js @@ -6,7 +6,7 @@ import { within as shadowWithin } from 'shadow-dom-testing-library'; */ export const getElements = async (canvasElement) => { const screen = shadowWithin(canvasElement); - const container = await screen.queryByShadowLabelText(/GitHub repository/i); + const container = await screen.findByShadowLabelText(/GitHub repository/i); const link = await screen.queryByShadowRole('link'); const langDetails = await container?.querySelector('[itemprop="programmingLanguage"]'); const langTerm = await langDetails?.previousElementSibling; @@ -54,8 +54,8 @@ export const ensureElements = async (elements, args) => { } /** org from args or derived from full_nameSplit */ - const org = args?.org ? args.org : full_nameSplit[1] ? full_nameSplit[0] : null; - if (!org || (org && org === args.user_login)) { + const org = args?.org ? args.org : full_nameSplit[0]; + if (args.no_org) { await expect(elements.org).toBeFalsy(); } else { await expect(elements.org).toBeTruthy(); diff --git a/src/github/repository.stories.js b/src/github/repository/repository.stories.js similarity index 83% rename from src/github/repository.stories.js rename to src/github/repository/repository.stories.js index df44420..a88a17c 100644 --- a/src/github/repository.stories.js +++ b/src/github/repository/repository.stories.js @@ -1,9 +1,10 @@ -import { generateMockResponse, parseFetchedRepo } from './utils/github'; +import { generateMockResponse } from '../utils/testing'; +import { parseFetchedRepo } from './content'; import { getElements, ensureElements } from './repository.shared-spec'; -import { repoProfileComponents, repoFreeCodeCamp } from './fixtures'; +import { repoProfileComponents, repoFreeCodeCamp } from '../fixtures'; -import './repository'; +import './index.js'; export default { title: 'GitHub/github-repository', @@ -36,7 +37,7 @@ export const FullNameOnly = { export const OrgIsUser = { args: { full_name: repoProfileComponents.full_name, - user_login: repoProfileComponents.owner?.login, + no_org: true, }, play: FullNameOnly.play, } @@ -73,8 +74,6 @@ export const Fetch = { ] }, play: async ({ args, canvasElement, step }) => { - /** wait for fetch to complete */ - await new Promise(resolve => setTimeout(resolve, 0)); const elements = await getElements(canvasElement); const argsAfterFetch = { ...parseFetchedRepo(repoProfileComponents), @@ -104,12 +103,10 @@ export const FetchError = { ] }, play: async ({ args, canvasElement, step }) => { - /** wait for fetch to complete */ - await new Promise(resolve => setTimeout(resolve, 0)); const elements = await getElements(canvasElement); const argsAfterFetch = { ...args, - error: `Repo "${args.full_name}" not found`, + error: `Fetch Error: Repo "${args.full_name}" not found`, }; await ensureElements(elements, argsAfterFetch); } diff --git a/src/github/repository/repository.test.js b/src/github/repository/repository.test.js new file mode 100644 index 0000000..c8f0c79 --- /dev/null +++ b/src/github/repository/repository.test.js @@ -0,0 +1,152 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert' + +import { generateMockResponse } from '../utils/testing.js'; +import { fetchRepo, parseFetchedRepo, cleanRepoContent, generateRepoContent } from './content.js'; +import { default as repoScottnathdotcom } from '../fixtures/generated/repo--scottnath-scottnath.com.json' assert { type: 'json' }; +import { default as repoStorydocker } from '../fixtures/generated/repo--storydocker-storydocker.json' assert { type: 'json' }; +import { default as repoFreeCodeCamp } from '../fixtures/generated/repo--freeCodeCamp-freeCodeCamp.json' assert { type: 'json' }; + +describe('fetchRepo', () => { + it('Should accept a full_name and return a response', async (t) => { + const fn = t.mock.method(global, 'fetch'); + const mockRes = { + json: () => generateMockResponse(repoScottnathdotcom, 'repos'), + }; + fn.mock.mockImplementationOnce(() => + Promise.resolve(mockRes) + ) + + const res = await fetchRepo(repoScottnathdotcom.full_name); + assert.strictEqual(res.response, repoScottnathdotcom); + assert.strictEqual(fn.mock.calls[0].arguments[0], `https://api.github.com/repos/${repoScottnathdotcom.full_name}`); + }); + it('Should handle missing repos', async (t) => { + const fn = t.mock.method(global, 'fetch'); + const mockContent = generateMockResponse({full_name: 'meow'}, 'repos', 404); + const mockRes = { + json: () => mockContent, + }; + fn.mock.mockImplementationOnce(() => + Promise.resolve(mockRes) + ) + + const res = await fetchRepo('meow'); + assert.strictEqual(res.response, mockContent.response); + assert.strictEqual(fn.mock.calls[0].arguments[0], `https://api.github.com/repos/meow`); + }); +}) + +describe('parseFetchedRepo', () => { + it('Should know organization', () => { + const testRepo = repoStorydocker; + assert.deepEqual(parseFetchedRepo(testRepo), { + description: testRepo.description, + forks_count: testRepo.forks_count, + full_name: testRepo.full_name, + language: testRepo.language, + name: testRepo.name, + org: testRepo.organization.login, + stargazers_count: testRepo.stargazers_count, + subscribers_count: testRepo.subscribers_count, + }); + }) + it('Should know owner', () => { + const testRepo = repoScottnathdotcom; + assert.deepEqual(parseFetchedRepo(testRepo), { + description: testRepo.description, + forks_count: testRepo.forks_count, + full_name: testRepo.full_name, + language: testRepo.language, + name: testRepo.name, + org: testRepo.owner.login, + stargazers_count: testRepo.stargazers_count, + subscribers_count: testRepo.subscribers_count, + }); + }) + it('Should allow using just org', () => { + const testRepo = repoStorydocker; + testRepo.org = 'meow'; + assert.deepEqual(parseFetchedRepo(testRepo), { + description: testRepo.description, + forks_count: testRepo.forks_count, + full_name: testRepo.full_name, + language: testRepo.language, + name: testRepo.name, + org: 'meow', + stargazers_count: testRepo.stargazers_count, + subscribers_count: testRepo.subscribers_count, + }); + }) +}); + +describe('cleanRepoContent', () => { + it('Errors on missing content', () => { + assert.deepEqual(cleanRepoContent().error, 'Missing repo attribute: `full_name`'); + }); + it('Determines the correct repo org or name', () => { + assert.equal(cleanRepoContent({full_name: 'meow/purr'}).name, 'purr'); + assert.equal(cleanRepoContent({full_name: 'meow/purr'}).org, 'meow'); + }); + it('Allows removing the org', () => { + assert.equal(cleanRepoContent({full_name: 'meow/purr', org: 'meow'}, true).org, undefined); + }) + it('Removes 0 values', () => { + assert.equal(cleanRepoContent({full_name: 'meow/purr', forks_count: '1'}).forks_count, '1'); + assert.equal(cleanRepoContent({full_name: 'meow/purr', forks_count: '0'}).forks_count, undefined); + assert.equal(cleanRepoContent({full_name: 'meow/purr', stargazers_count: '1'}).stargazers_count, '1'); + assert.equal(cleanRepoContent({full_name: 'meow/purr', stargazers_count: '0'}).stargazers_count, undefined); + assert.equal(cleanRepoContent({full_name: 'meow/purr', subscribers_count: '1'}).subscribers_count, '1'); + assert.equal(cleanRepoContent({full_name: 'meow/purr', subscribers_count: '0'}).subscribers_count, undefined); + }) +}); + +describe('generateRepoContent', () => { + it('Errors on missing content', async () => { + const res = await generateRepoContent(); + assert.deepEqual(res.error, 'Missing repo attribute: `full_name`'); + }); + it('Cleans without fetching', async () => { + const res = await generateRepoContent({full_name: 'meow/purr'}); + assert.equal(res.name, 'purr'); + assert.equal(res.org, 'meow'); + }); + it('Fetches and fails', async (t) => { + const fn = t.mock.method(global, 'fetch'); + const mockContent = generateMockResponse({full_name: 'meow/purr'}, 'repos', 404); + const mockRes = { + json: () => mockContent.response, + }; + fn.mock.mockImplementationOnce(() => + Promise.resolve(mockRes) + ) + + const returned = await generateRepoContent({full_name: 'meow/purr'}, true); + assert.equal(returned.error, 'Fetch Error: Repo "meow/purr" not found'); + }); + it('Fetches and cleans', async (t) => { + const testRepo = { + ...repoFreeCodeCamp, + org: 'meow', + stargazers_count: '0' + }; + const expected = { + full_name: testRepo.full_name, + name: testRepo.name, + description: testRepo.description, + language: testRepo.language, + forks_count: testRepo.forks_count, + subscribers_count: testRepo.subscribers_count, + } + const fn = t.mock.method(global, 'fetch'); + const mockRes = { + json: () => generateMockResponse(testRepo, 'repos').response, + }; + fn.mock.mockImplementationOnce(() => + Promise.resolve(mockRes) + ) + + const returned = await generateRepoContent(testRepo, true, true); + assert.deepEqual(returned, expected); + }); +}); \ No newline at end of file diff --git a/src/github/styles/repository.css b/src/github/styles/repository.css index f722fe0..e5c6c59 100644 --- a/src/github/styles/repository.css +++ b/src/github/styles/repository.css @@ -1,14 +1,10 @@ /** styles for the GitHub repository web component */ -:host { - container-name: repository; -} - div[itemscope] { padding: var(--row-spacing); } -@container repository (min-width: 300px) { +@container (min-width: 300px) { div[itemscope] { padding: calc(var(--row-spacing) * 2); } @@ -46,7 +42,7 @@ div[itemscope] { color: var(--color-light); } -dl { +.repo dl { display: flex; flex-direction: row; flex-wrap: wrap; @@ -91,13 +87,13 @@ dl { } } - @container repository (min-width: 300px) { + @container (min-width: 300px) { > dd:not([itemprop="programmingLanguage"]) { flex: 1 0 auto; } } - @container repository (min-width: 400px) { + @container (min-width: 400px) { > dd { flex: 1 0 auto; } diff --git a/src/github/styles/user.css b/src/github/styles/user.css index f943466..ea6a8ce 100644 --- a/src/github/styles/user.css +++ b/src/github/styles/user.css @@ -169,7 +169,7 @@ section[itemscope] { } } } - & dl:has(github-repository) { + & dl:has(.repo) { margin-top: .5em; > dd { diff --git a/src/github/user.js b/src/github/user.js deleted file mode 100644 index 94a3812..0000000 --- a/src/github/user.js +++ /dev/null @@ -1,190 +0,0 @@ - -import { intToString, fetchUser, parseFetchedUser } from './utils/github'; -import stylesPrimer from './styles/vars-primer.css?inline'; -import stylesGlobal from './styles/vars-global.css?inline'; -import styles from './styles/user.css?inline'; - -import './repository'; - -/** - * Styles for the component, imported during development, inlined during build - */ -const componentStyles = ` -${stylesPrimer} -${stylesGlobal} -${styles} -`; - -/** - * Blank base64-encoded png - * @see https://png-pixel.com/ - */ -const blankPng = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mN8/x8AAuMB8DtXNJsAAAAASUVORK5CYII='; - -/** - * GitHub repository UI component - * All props are attributes and should be the same content as the GitHub API - * endpoint for getting a repository - * @see https://docs.github.com/en/rest/repos/repos#get-a-repository - * @element github-user - * @name GitHubUser - * - * @attribute {string} login - User's GitHub login - * @attribute {string} avatar_url - URL to user's avatar - * @attribute {string} name - User's name - * @attribute {boolean} [fetch] - when true, fetches user from GitHub api - * @attribute {string} [username] - alias for `login` - * @attribute {string} [bio] - User's biography content - * @attribute {string} [following] - number of people user is following - * @attribute {string} [followers] - number of followers - * @attribute {string} [repos] - JSON stringified array of repositories - */ -export class GitHubUser extends HTMLElement { - constructor() { - super(); - this.attachShadow({ mode: "open" }); - this._getAttributes(); - } - - /** - * Parses a string, which should be a JSON stringified array of GitHubRepository - * objects or strings. If a string, it should be the `full_name` of the repository - * and `fetch=true` will be an attribute on the repository component - * @param {string} reposAttr - String of GitHubRepository data - * @returns array of strings of attributes for each repository - */ - _parseReposAttribute(reposAttr) { - let repos = []; - try { - repos = JSON.parse(reposAttr); - } catch (error) { - console.error(error); - return []; - } - return repos.map((repo) => { - if (typeof repo === 'string') { - return repo.split('/')[1] ? `full_name="${repo}" fetch="true"` : `full_name="${this.login}/${repo}" fetch="true"`; - } - repo.itemprop = repo.itemprop || 'maintainer'; - return Object.entries(repo) - .map(([key, value]) => `${key}="${value}"`) - .join(' '); - }); - } - - /** - * Generate variables at `this.[attribute-name]` for each attribute on the element - */ - _getAttributes() { - for (let name of this.getAttributeNames()) { - if (this.getAttribute(name)) { - this[name] = this.getAttribute(name); - } - } - this.repositories = this.repos ? this._parseReposAttribute(this.repos) : []; - } - - /** - * Check that required attributes are present, adjusts content as needed - */ - _checkAttributes() { - if (this.username && !this.login) { - this.login = this.username; - } - if (!this.login) { - this.error = 'Missing required attribute: `login` || `username`'; - return; - } - if (!this.avatar_url) { - this.avatar_url = blankPng; - } - if (this.followers) { - this.followers = intToString(this.followers); - } - if (this.following) { - this.following = intToString(this.following); - } - } - - /** - * Fetch user data from GitHub API and parse out - * the content needed for this component. Either adds an error - * or adds the parsed content to the element's attributes - */ - async _parseFetch() { - const user = await fetchUser(this.username || this.login); - if (user.message && user.message === 'Not Found') { - this.error = `User "${this.username || this.login}" not found`; - return; - } - const parsedUser = parseFetchedUser(user); - Object.entries(parsedUser).forEach(([key, value]) => { - this[key] = this[key] || value; - }); - } - - async connectedCallback() { - let view = ``; - if (this.fetch) { - await this._parseFetch(); - } - this._checkAttributes(); - view += this._render(); - this.shadowRoot.innerHTML = view; - } - - _render() { - if (this.error) { - return ` -
-

${this.error}

-
- ` - } - - return ` -
-
- GitHub user - ${this.login} -
-
-
- -
- ${this.bio ? `

${this.bio}

` : ''} - ${this.following || this.followers ? ` -
- ${this.followers ? ` -
followers
-
${this.followers}
- ` : ''} - ${this.following ? ` -
following
-
${this.following}
- ` : ''} -
- ` : ''} - ${Array.isArray(this.repositories) && this.repositories?.length ? ` -
-
Pinned repositories
- ${this.repositories.map((repo) => ` -
- `).join('')} -
- ` : ''} -
-
- `; - } -} - -customElements.define('github-user', GitHubUser); \ No newline at end of file diff --git a/src/github/user/README.md b/src/github/user/README.md new file mode 100644 index 0000000..4e35ac4 --- /dev/null +++ b/src/github/user/README.md @@ -0,0 +1,170 @@ +# GitHub user profile component + +## Modules + +
+
GitHub-User-Utilities
+

Utility functions for fetching and parsing GitHub User data

+
+
+ +## Members + +
+
GitHubUser
+

GitHub user profile web component

+
+
+ +## Constants + +
+
componentStyles
+

Styles for the component, imported during development, inlined during build

+
+
+ + + +## GitHub-User-Utilities +Utility functions for fetching and parsing GitHub User data + +**Author**: @scottnath + +* [GitHub-User-Utilities](#module_GitHub-User-Utilities) + * [.fetchUser(username)](#module_GitHub-User-Utilities.fetchUser) ⇒ Object + * [.parseFetchedUser(user)](#module_GitHub-User-Utilities.parseFetchedUser) ⇒ GitHubUserHTML + * [.parseReposString(reposStr, [owner])](#module_GitHub-User-Utilities.parseReposString) ⇒ + * [.cleanUserContent(content)](#module_GitHub-User-Utilities.cleanUserContent) ⇒ GitHubUserHTML + * [.generateUserContent(content, [fetch])](#module_GitHub-User-Utilities.generateUserContent) ⇒ GitHubUserHTML + * [~blankPng](#module_GitHub-User-Utilities..blankPng) + * [~GitHubUserHTML](#module_GitHub-User-Utilities..GitHubUserHTML) : Object + + + +### githubUserUtils.fetchUser(username) ⇒ Object +Fetch a user from + +**Kind**: static method of [GitHub-User-Utilities](#module_GitHub-User-Utilities) +**Returns**: Object - response status 200: user; else {Object} error +**See**: https://docs.github.com/en/rest/users/users?apiVersion=2022-11-28#get-a-user + +| Param | Type | +| --- | --- | +| username | string | + + + +### githubUserUtils.parseFetchedUser(user) ⇒ GitHubUserHTML +Parse a GitHub user from the `user` endpoint response down to + only the data required for the user component + +**Kind**: static method of [GitHub-User-Utilities](#module_GitHub-User-Utilities) +**Returns**: GitHubUserHTML - component-ready user object + +| Param | Type | +| --- | --- | +| user | Object | + + + +### githubUserUtils.parseReposString(reposStr, [owner]) ⇒ +Parses a string, which should be a JSON stringified array of GitHubRepository + objects or JSON stringified array of strings. If an array of string, + each string should be the `full_name` of a repository. + +**Kind**: static method of [GitHub-User-Utilities](#module_GitHub-User-Utilities) +**Returns**: array of strings of attributes for each repository + +| Param | Type | Description | +| --- | --- | --- | +| reposStr | string | String of GitHubRepository data | +| [owner] | string | GitHub user login, repository strings are not `full_name`s | + + + +### githubUserUtils.cleanUserContent(content) ⇒ GitHubUserHTML +Parses and cleans user content to match what is expected by the user HTML + +**Kind**: static method of [GitHub-User-Utilities](#module_GitHub-User-Utilities) +**Returns**: GitHubUserHTML - ready for HTML content + +| Param | Type | Description | +| --- | --- | --- | +| content | GitHubUserHTML | a content object representing a GitHub user | + + + +### githubUserUtils.generateUserContent(content, [fetch]) ⇒ GitHubUserHTML +Generates an object of content for the repository HTML + +**Kind**: static method of [GitHub-User-Utilities](#module_GitHub-User-Utilities) +**Returns**: GitHubUserHTML - content ready for HTML, possibly includes fetched content + +| Param | Type | +| --- | --- | +| content | GitHubUserHTML | +| [fetch] | boolean | + + + +### GitHub-User-Utilities~blankPng +Blank base64-encoded png + +**Kind**: inner constant of [GitHub-User-Utilities](#module_GitHub-User-Utilities) +**See**: https://png-pixel.com/ + + +### GitHub-User-Utilities~GitHubUserHTML : Object +Content needed to render a GitHub user. This is a subset of the `users` endpoint response + +**Kind**: inner typedef of [GitHub-User-Utilities](#module_GitHub-User-Utilities) +**See**: https://docs.github.com/en/rest/users/users#get-a-user +**Properties** + +| Name | Type | Description | +| --- | --- | --- | +| login | string | User's GitHub login | +| name | string | User's name | +| [username] | string | alias for `login` | +| [avatar_url] | string | URL to user's avatar | +| [bio] | string | User's biography content | +| [following] | string | number of people user is following | +| [followers] | string | number of followers | +| [error] | string | error message, if any | +| [repositories] | Array.<GitHubRepositoryHTML> | array of repositories | + + + +## GitHubUser +GitHub user profile web component + +**Kind**: global variable +**Summary**: Native web component which shows a GitHub user's profile content. Can use local data, + fetch data from the GitHub rest API, or use a combination of both. +**Element**: github-user +**See**: https://docs.github.com/en/rest/repos/repos#get-a-repository +**Properties** + +| Name | Type | Description | +| --- | --- | --- | +| login | string | User's GitHub login | +| avatar_url | string | URL to user's avatar | +| name | string | User's name | +| [fetch] | boolean | when true, fetches user from GitHub api | +| [username] | string | alias for `login` | +| [bio] | string | User's biography content | +| [following] | string | number of people user is following | +| [followers] | string | number of followers | +| [repos] | string | JSON stringified array of repositories | + +**Example** +```js + +``` + + +## componentStyles +Styles for the component, imported during development, inlined during build + +**Kind**: global constant diff --git a/src/github/user/content.js b/src/github/user/content.js new file mode 100644 index 0000000..f432761 --- /dev/null +++ b/src/github/user/content.js @@ -0,0 +1,168 @@ +/** + * @name GitHub-User-Utilities + * @module + * @typicalname githubUserUtils + * @description Utility functions for fetching and parsing GitHub User data + * @author @scottnath + */ + +import { generateRepoContent } from '../repository/content.js'; + +/** @ignore */ +const githubApi = 'https://api.github.com'; + +/** + * Blank base64-encoded png + * @see https://png-pixel.com/ + */ +const blankPng = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mN8/x8AAuMB8DtXNJsAAAAASUVORK5CYII='; + +/** + * Content needed to render a GitHub user. This is a subset of the `users` endpoint response + * @see https://docs.github.com/en/rest/users/users#get-a-user + * @typedef {Object} GitHubUserHTML + * + * @property {string} login - User's GitHub login + * @property {string} name - User's name + * @property {string} [username] - alias for `login` + * @property {string} [avatar_url] - URL to user's avatar + * @property {string} [bio] - User's biography content + * @property {string} [following] - number of people user is following + * @property {string} [followers] - number of followers + * @property {string} [error] - error message, if any + * @property {Array} [repositories] - array of repositories + */ + +/** + * Fetch a user from + * @see https://docs.github.com/en/rest/users/users?apiVersion=2022-11-28#get-a-user + * @param {string} username + * @returns response status 200: {Object} user; else {Object} error + * @function + */ +export const fetchUser = async (username) => { + const response = await fetch(`${githubApi}/users/${username}`); + const userJson = await response.json(); + return userJson; +} + +/** + * Parse a GitHub user from the `user` endpoint response down to + * only the data required for the user component + * @param {Object} user + * @returns {GitHubUserHTML} component-ready user object + * @function + */ +export const parseFetchedUser = (user = {}) => { + return { + login: user.login, + name: user.name, + username: user.login, + avatar_url: user.avatar_url, + bio: user.bio, + following: user.following, + followers: user.followers, + } +} + +/** + * Parses a string, which should be a JSON stringified array of GitHubRepository + * objects or JSON stringified array of strings. If an array of string, + * each string should be the `full_name` of a repository. + * @param {string} reposStr - String of GitHubRepository data + * @param {string} [owner] - GitHub user login, repository strings are not `full_name`s + * @returns array of strings of attributes for each repository + * @function + */ +export const parseReposString = (reposStr, owner) => { + let repos = []; + try { + repos = JSON.parse(reposStr); + } catch (error) { + console.error(error); + return []; + } + return repos.map((repo) => { + if (typeof repo === 'string') { + if (repo.split('/')[1]) { + return { + full_name: repo, + fetch: true, + }; + } + if (!owner) return; + return { + full_name: `${owner}/${repo}`, + fetch: true, + no_org: true, + }; + } + repo.itemprop = repo.itemprop || 'maintainer'; + return repo; + }).filter((repo) => repo !== undefined); +} + +/** + * Parses and cleans user content to match what is expected by the user HTML + * @param {GitHubUserHTML} content - a content object representing a GitHub user + * @returns {GitHubUserHTML} ready for HTML content + * @function + */ +export const cleanUserContent = (content = {}) => { + if (content.username && !content.login) { + content.login = content.username; + } + const user = parseFetchedUser(content); + if (!user.login) { + user.error = 'Missing required attribute: `login` || `username`'; + return user; + } + if (!user.avatar_url) { + user.avatar_url = blankPng; + } + if (user.followers === '0') delete user.followers; + if (user.following === '0') delete user.following; + + user.repositories = content.repos ? parseReposString(content.repos, user.login) : []; + + const c = {}; + // remove `undefined` values + for (const key in user) { + if (user[key]) c[key] = user[key]; + } + return c; +}; + +/** + * Generates an object of content for the repository HTML + * @param {GitHubUserHTML} content + * @param {boolean} [fetch] + * @returns {GitHubUserHTML} content ready for HTML, possibly includes fetched content + * @function + */ +export const generateUserContent = async (content, fetch = false) => { + const userFromContent = cleanUserContent(content); + if (userFromContent.error) return userFromContent; + let fetched = {}; + if (fetch) { + fetched = await fetchUser(userFromContent.login); + if (fetched.message && fetched.message === 'Not Found') { + return { error: `Fetch Error: User "${content.login}" not found`}; + } + fetched = cleanUserContent(fetched); + delete fetched.repositories; + if (fetched.avatar_url && userFromContent.avatar_url === blankPng) { + delete userFromContent.avatar_url; + } + } + if (userFromContent.repositories?.length) { + const repos = new Set(); + for (const repo of userFromContent.repositories) { + const fullRepo = await generateRepoContent(repo, repo.fetch, repo.no_org); + if (fullRepo.name && !fullRepo.error) repos.add(fullRepo); + } + userFromContent.repositories = Array.from(repos); + } + return Object.assign({}, fetched, userFromContent); +} + \ No newline at end of file diff --git a/src/github/user/html.js b/src/github/user/html.js new file mode 100644 index 0000000..c9d798e --- /dev/null +++ b/src/github/user/html.js @@ -0,0 +1,66 @@ + +import repositoryHTML from '../repository/html.js'; +import { intToString } from '../../utils/index.js'; + +function user(content) { + { + if (content.error) { + return ` +
+

${content.error}

+
+ ` + } + + return ` +
+
+ GitHub user + ${content.login} +
+
+
+ +
+ ${content.bio ? `

${content.bio}

` : ''} + ${content.following || content.followers ? ` +
+ ${content.followers ? ` +
followers
+
+ + ${content.followers} +
+ ` : ''} + ${content.following ? ` +
following
+
+ + ${content.following} +
+ ` : ''} +
+ ` : ''} + ${Array.isArray(content.repositories) && content.repositories?.length ? ` +
+
Pinned repositories
+ ${content.repositories.map((repo) => ` +
${repositoryHTML(repo)}
+ `).join('')} +
+ ` : ''} +
+
+ `; + } +} + +export default user; diff --git a/src/github/user/index.js b/src/github/user/index.js new file mode 100644 index 0000000..81b79da --- /dev/null +++ b/src/github/user/index.js @@ -0,0 +1,71 @@ +import { generateUserContent } from './content.js'; +import userHTML from './html.js'; +import stylesPrimer from '../styles/vars-primer.css?inline'; +import stylesGlobal from '../styles/vars-global.css?inline'; +import stylesRepo from '../styles/repository.css?inline'; +import styles from '../styles/user.css?inline'; + +/** + * Styles for the component, imported during development, inlined during build + */ +const componentStyles = ` +${stylesPrimer} +${stylesGlobal} +${stylesRepo} +${styles} +`; + +/** + * GitHub user profile web component + * @summary Native web component which shows a GitHub user's profile content. Can use local data, + * fetch data from the GitHub rest API, or use a combination of both. + * @see https://docs.github.com/en/rest/repos/repos#get-a-repository + * @element github-user + * @name GitHubUser + * + * @property {string} login - User's GitHub login + * @property {string} avatar_url - URL to user's avatar + * @property {string} name - User's name + * @property {boolean} [fetch] - when true, fetches user from GitHub api + * @property {string} [username] - alias for `login` + * @property {string} [bio] - User's biography content + * @property {string} [following] - number of people user is following + * @property {string} [followers] - number of followers + * @property {string} [repos] - JSON stringified array of repositories + * + * @example + * + */ +export class GitHubUser extends HTMLElement { + /** + * @ignore + */ + constructor() { + super(); + this.attrs = {}; + this.content = {}; + this.attachShadow({ mode: "open" }); + this._getAttributes(); + } + + /** + * Generate variables at `this.[attribute-name]` for each attribute on the element + * @ignore + */ + _getAttributes() { + for (let name of this.getAttributeNames()) { + if (this.getAttribute(name)) { + this.attrs[name] = this.getAttribute(name); + } + } + } + + async connectedCallback() { + let view = ``; + this.content = await generateUserContent(this.attrs, this.attrs.fetch); + view += userHTML(this.content); + this.shadowRoot.innerHTML = view; + } +} + +customElements.define('github-user', GitHubUser); \ No newline at end of file diff --git a/src/github/user.shared-spec.js b/src/github/user/user.shared-spec.js similarity index 91% rename from src/github/user.shared-spec.js rename to src/github/user/user.shared-spec.js index eaccadd..0af62e4 100644 --- a/src/github/user.shared-spec.js +++ b/src/github/user/user.shared-spec.js @@ -2,14 +2,14 @@ import { expect } from '@storybook/jest'; import { within as shadowWithin } from 'shadow-dom-testing-library'; -import { intToString } from './utils/github'; +import { intToString } from '../../utils/index.js'; /** * Extract elements from an shadow DOM element */ export const getElements = async (canvasElement) => { const screen = shadowWithin(canvasElement); - const container = await screen.queryByShadowLabelText(/GitHub user profile/i); + const container = await screen.findByShadowLabelText(/GitHub user profile/i); const [headerName] = await container?.querySelectorAll('[itemprop="alternativeName"]'); const [mainLink] = await screen.queryAllByShadowRole('link'); const [ avatar ] = await screen.queryAllByShadowRole('img'); @@ -27,7 +27,7 @@ export const getElements = async (canvasElement) => { bio, followers: await container?.querySelector('[itemprop="followee"]'), following: await container?.querySelector('[itemprop="follows"]'), - repos: await Array.from(container?.querySelectorAll('github-repository')), + repos: await Array.from(container?.querySelectorAll('[itemscope].repo')), }; } @@ -35,7 +35,6 @@ export const getElements = async (canvasElement) => { * Ensure elements are present and have the correct content */ export const ensureElements = async (elements, args) => { - console.log('elements', elements); if (args.error) { await expect(elements.mainLink).toBeFalsy(); await expect(elements.container).toBeTruthy(); diff --git a/src/github/user.stories.js b/src/github/user/user.stories.js similarity index 78% rename from src/github/user.stories.js rename to src/github/user/user.stories.js index 89dd589..919c62d 100644 --- a/src/github/user.stories.js +++ b/src/github/user/user.stories.js @@ -1,9 +1,10 @@ -import { repoScottnathdotcom, repoStorydocker, userScottnath, userSindresorhus } from './fixtures'; -import { generateMockResponse, parseFetchedRepo, parseFetchedUser } from './utils/github'; +import { repoScottnathdotcom, repoStorydocker, userScottnath, userSindresorhus } from '../fixtures'; +import { generateMockResponse } from '../utils/testing'; +import { parseFetchedUser } from './content'; +import { parseFetchedRepo } from '../repository/content.js'; import { getElements, ensureElements } from './user.shared-spec'; -import { repoProfileComponents, repoFreeCodeCamp } from './fixtures'; -import './user'; +import '.'; export default { title: 'GitHub/github-user', @@ -16,38 +17,38 @@ export default { .join(' '); return ` - + `; } }; -export const OnlyRequired = { - args: { - login: userScottnath.login, - name: userScottnath.name, - }, +export const User = { + args: parseFetchedUser(userScottnath), play: async ({ args, canvasElement, step }) => { const elements = await getElements(canvasElement); await ensureElements(elements, args); } } -export const User = { - args: parseFetchedUser(userScottnath), - play: OnlyRequired.play, +export const UserRepos = { + args: { + ...User.args, + repos: JSON.stringify([parseFetchedRepo(repoStorydocker), { ...parseFetchedRepo(repoScottnathdotcom), user_login: userScottnath.login }]).replace(/"/g, """), + }, + play: User.play, } export const PopularUser = { args: parseFetchedUser(userSindresorhus), - play: OnlyRequired.play, + play: User.play, } -export const UserRepos = { +export const OnlyRequired = { args: { - ...User.args, - repos: JSON.stringify([parseFetchedRepo(repoStorydocker), { ...parseFetchedRepo(repoScottnathdotcom), user_login: userScottnath.login }]).replace(/"/g, """), + login: userScottnath.login, + name: userScottnath.name, }, - play: OnlyRequired.play, + play: User.play, } export const ReposFetch = { @@ -55,7 +56,14 @@ export const ReposFetch = { ...User.args, repos: JSON.stringify([repoScottnathdotcom.name, repoStorydocker.full_name]).replace(/"/g, """), }, - play: OnlyRequired.play, + play: async ({ args, canvasElement, step }) => { + const elements = await getElements(canvasElement); + const argsAfterFetch = { + ...parseFetchedUser(userScottnath), + ...args, + }; + await ensureElements(elements, args); + } } export const Fetch = { @@ -69,8 +77,6 @@ export const Fetch = { ] }, play: async ({ args, canvasElement, step }) => { - /** wait for fetch to complete */ - await new Promise(resolve => setTimeout(resolve, 0)); const elements = await getElements(canvasElement); const argsAfterFetch = { ...parseFetchedUser(userScottnath), @@ -91,8 +97,6 @@ export const FetchError = { ] }, play: async ({ args, canvasElement, step }) => { - /** wait for fetch to complete */ - await new Promise(resolve => setTimeout(resolve, 0)); const elements = await getElements(canvasElement); const argsAfterFetch = { ...args, diff --git a/src/github/user/user.test.js b/src/github/user/user.test.js new file mode 100644 index 0000000..c3ce4d4 --- /dev/null +++ b/src/github/user/user.test.js @@ -0,0 +1,177 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert' + +import { generateMockResponse } from '../utils/testing.js'; +import { fetchUser, parseFetchedUser, parseReposString, cleanUserContent, generateUserContent } from './content.js'; +import { default as repoScottnathdotcom } from '../fixtures/generated/repo--scottnath-scottnath.com.json' assert { type: 'json' }; +import { default as repoStorydocker } from '../fixtures/generated/repo--storydocker-storydocker.json' assert { type: 'json' }; +import { default as repoFreeCodeCamp } from '../fixtures/generated/repo--freeCodeCamp-freeCodeCamp.json' assert { type: 'json' }; + +import { default as userScottnath } from '../fixtures/generated/user--scottnath.json' assert { type: 'json' }; +import { default as userSindresorhus } from '../fixtures/generated/user--sindresorhus.json' assert { type: 'json' }; + +describe('fetchUser', () => { + it('Should accept a login and return a response', async (t) => { + const fn = t.mock.method(global, 'fetch'); + const mockRes = { + json: () => generateMockResponse(userScottnath, 'users'), + }; + fn.mock.mockImplementationOnce(() => + Promise.resolve(mockRes) + ) + + const res = await fetchUser(userScottnath.login); + assert.strictEqual(res.response, userScottnath); + assert.strictEqual(fn.mock.calls[0].arguments[0], `https://api.github.com/users/${userScottnath.login}`); + }); + it('Should handle missing user', async (t) => { + const fn = t.mock.method(global, 'fetch'); + const mockContent = generateMockResponse({login: 'not-a-real-user'}, 'users', 404); + const mockRes = { + json: () => mockContent, + }; + fn.mock.mockImplementationOnce(() => + Promise.resolve(mockRes) + ) + + const res = await fetchUser('not-a-real-user'); + assert.strictEqual(res.response, mockContent.response); + assert.strictEqual(fn.mock.calls[0].arguments[0], `https://api.github.com/users/not-a-real-user`); + }); +}) + +describe('parseFetchedUser', () => { + it('Should reduce user content', () => { + const testUser = userSindresorhus; + assert.deepEqual(parseFetchedUser(testUser), { + login: testUser.login, + name: testUser.name, + username: testUser.login, + avatar_url: testUser.avatar_url, + bio: testUser.bio, + following: testUser.following, + followers: testUser.followers, + }); + }) +}); + +describe('parseReposString', () => { + it('Should parse a string of repos full_names', () => { + const testRepos = ['meow/purr', 'woof/sniff']; + const testString = JSON.stringify(testRepos); + const expected = testRepos.map(repo => { + return { + full_name: repo, + fetch: true, + } + }); + assert.deepEqual(parseReposString(testString), expected); + }); + it('Should parse a string of repos names, adding owner, or fail gracefully', () => { + const testRepos = ['purr', 'sniff']; + const testString = JSON.stringify(testRepos); + const expected = testRepos.map(repo => { + return { + full_name: `meow/${repo}`, + fetch: true, + no_org: true, + } + }); + assert.deepEqual(parseReposString('gonna fail but return an array'), []); + assert.deepEqual(parseReposString(testString), []); + assert.deepEqual(parseReposString(testString, 'meow'), expected); + }); +}); + +describe('cleanUserContent', () => { + it('Errors on missing content', () => { + assert.deepEqual(cleanUserContent().error, 'Missing required attribute: `login` || `username`'); + }); + it('Adjusts for missing items', () => { + assert.equal(cleanUserContent({username: 'meow'}).login, 'meow'); + assert.ok(cleanUserContent({username: 'meow'}).avatar_url.includes('data:image/png')); + }); + it('Removes 0 values', () => { + assert.equal(cleanUserContent({login: 'meow', followers: '1'}).followers, '1'); + assert.equal(cleanUserContent({login: 'meow', followers: '0'}).followers, undefined); + assert.equal(cleanUserContent({login: 'meow', following: '1'}).following, '1'); + assert.equal(cleanUserContent({login: 'meow', following: '0'}).following, undefined); + }) + it('Should convert a string of repos to an array', () => { + const testRepos = ['meow/purr']; + const testString = JSON.stringify(testRepos); + const expected = testRepos.map(repo => { + return { + full_name: repo, + fetch: true, + } + }); + assert.deepEqual(cleanUserContent({login: 'meow', repos: testString}).repositories, expected); + }); +}); + +describe('generateUserContent', () => { + it('Errors on missing content', async () => { + const res = await generateUserContent(); + assert.deepEqual(res.error, 'Missing required attribute: `login` || `username`'); + }); + it('Cleans without fetching', async () => { + const res = await generateUserContent({username: 'meow'}); + assert.equal(res.username, 'meow'); + assert.equal(res.login, 'meow'); + }); + it('Fetches and fails', async (t) => { + const fn = t.mock.method(global, 'fetch'); + const mockContent = generateMockResponse({login: 'meow'}, 'users', 404); + const mockRes = { + json: () => mockContent.response, + }; + fn.mock.mockImplementationOnce(() => + Promise.resolve(mockRes) + ) + + const returned = await generateUserContent({login: 'meow'}, true); + assert.equal(returned.error, 'Fetch Error: User "meow" not found'); + }); + it('Fetches and cleans', async (t) => { + const testRepo = repoFreeCodeCamp; + const expectedRepo = { + full_name: testRepo.full_name, + name: testRepo.name, + description: testRepo.description, + language: testRepo.language, + forks_count: testRepo.forks_count, + org: testRepo.organization.login, + stargazers_count: testRepo.stargazers_count, + subscribers_count: testRepo.subscribers_count, + } + const testUser = { + ...userSindresorhus, + followers: 0, + username: userSindresorhus.login, + login: undefined, + repos: JSON.stringify([testRepo.full_name]), + }; + const expected = { + login: userSindresorhus.login, + name: testUser.name, + username: userSindresorhus.login, + avatar_url: testUser.avatar_url, + bio: testUser.bio, + following: testUser.following, + repositories: [expectedRepo] + } + const fn = t.mock.method(global,'fetch'); + const mockResUser = { + json: () => generateMockResponse(testUser, 'users').response, + }; + const mockResRepo = { + json: () => generateMockResponse(repoFreeCodeCamp, 'repos').response, + }; + fn.mock.mockImplementationOnce(async () => mockResUser, 0) + fn.mock.mockImplementationOnce(async () => mockResRepo, 1) + + const returned = await generateUserContent({...testUser, avatar_url: undefined}, true, true); + assert.deepEqual(returned, expected); + }); +}); \ No newline at end of file diff --git a/src/github/utils/README.md b/src/github/utils/README.md new file mode 100644 index 0000000..936d314 --- /dev/null +++ b/src/github/utils/README.md @@ -0,0 +1,215 @@ +# GitHub profile components' utilities + +## Modules + +
+
Fixtures
+

Utility functions for generating fixtures for GitHub data

+
+
Primer-Utilities
+

Primer design system utilities to generate assets for GitHub web components

+
+
Testing
+

Utility functions used for testing and prototyping components

+
+
+ + + +## Fixtures +Utility functions for generating fixtures for GitHub data + +**Author**: @scottnath + +* [Fixtures](#module_Fixtures) + * [.generateFixtureRepo](#module_Fixtures.generateFixtureRepo) + * [.generateFixtureUser](#module_Fixtures.generateFixtureUser) + * [.generateFixtures](#module_Fixtures.generateFixtures) + + + +### fixtures.generateFixtureRepo +Generate a fixture for a GitHub repository and write it to a JSON file + +**Kind**: static constant of [Fixtures](#module_Fixtures) + +| Param | Type | Description | +| --- | --- | --- | +| full_name | string | repo full name, as in `scottnath/profile-components` | + + + +### fixtures.generateFixtureUser +Generate a fixture for a GitHub user and write it to a JSON file + +**Kind**: static constant of [Fixtures](#module_Fixtures) + +| Param | Type | Description | +| --- | --- | --- | +| username | string | GitHub user login | + + + +### fixtures.generateFixtures +Generate fixtures for a set of GitHub repositories and users + +**Kind**: static constant of [Fixtures](#module_Fixtures) + + +## Primer-Utilities +Primer design system utilities to generate assets for GitHub web components + +**Author**: @scottnath + +* [Primer-Utilities](#module_Primer-Utilities) + * [.getGithubData](#module_Primer-Utilities.getGithubData) ⇒ + * [.getLangColorsSource](#module_Primer-Utilities.getLangColorsSource) ⇒ + * [.getPrimerCss](#module_Primer-Utilities.getPrimerCss) ⇒ string + * [.writePrimerCssFile](#module_Primer-Utilities.writePrimerCssFile) + * [~svgs](#module_Primer-Utilities..svgs) + * [~svgCircle](#module_Primer-Utilities..svgCircle) + * [~getLangCss()](#module_Primer-Utilities..getLangCss) ⇒ + * [~svgUrlCss(name, svg)](#module_Primer-Utilities..svgUrlCss) ⇒ + * [~octiconSvg(name)](#module_Primer-Utilities..octiconSvg) ⇒ + * [~getSvgCss()](#module_Primer-Utilities..getSvgCss) ⇒ + * [~globalStyles()](#module_Primer-Utilities..globalStyles) ⇒ string + * [~getThemeColors(colors)](#module_Primer-Utilities..getThemeColors) ⇒ string + * [~getTheme(theme)](#module_Primer-Utilities..getTheme) ⇒ string + + + +### primerUtils.getGithubData ⇒ +Fetches and processes statistical data from GitHut's tool. Reduces the + data to the top 30 languages with the most pull requests in the last quarter + +**Kind**: static constant of [Primer-Utilities](#module_Primer-Utilities) +**Returns**: set of strings which are the names of the top 30 languages +**See**: https://madnight.github.io/githut + +| Param | Type | +| --- | --- | +| filename | string | + + + +### primerUtils.getLangColorsSource ⇒ +Fetches the source file for GitHub's language colors and parses it into an object + +**Kind**: static constant of [Primer-Utilities](#module_Primer-Utilities) +**Returns**: an object of Linguist language data +**See**: https://github.com/github-linguist/linguist + + +### primerUtils.getPrimerCss ⇒ string +Get global and theme-specific primer styles needed for GitHub web components + +**Kind**: static constant of [Primer-Utilities](#module_Primer-Utilities) +**Returns**: string - primer theme color variables + + +### primerUtils.writePrimerCssFile +Write primer css file to styles directory + +**Kind**: static constant of [Primer-Utilities](#module_Primer-Utilities) + + +### Primer-Utilities~svgs +List of octicon names for SVGs needed for the primer CSS + +**Kind**: inner constant of [Primer-Utilities](#module_Primer-Utilities) + + +### Primer-Utilities~svgCircle +Circle SVG for language color + +**Kind**: inner constant of [Primer-Utilities](#module_Primer-Utilities) + + +### Primer-Utilities~getLangCss() ⇒ +Generates CSS for GitHub language colors + +**Kind**: inner method of [Primer-Utilities](#module_Primer-Utilities) +**Returns**: CSS for GitHub language colors + + +### Primer-Utilities~svgUrlCss(name, svg) ⇒ +Generates a CSS variable for an svg + +**Kind**: inner method of [Primer-Utilities](#module_Primer-Utilities) +**Returns**: CSS variable for svg + +| Param | Type | Description | +| --- | --- | --- | +| name | string | svg name | +| svg | string | svg element string | + + + +### Primer-Utilities~octiconSvg(name) ⇒ +Get CSS variable for an octicon svg + +**Kind**: inner method of [Primer-Utilities](#module_Primer-Utilities) +**Returns**: a CSS variable for an octicon svg + +| Param | Type | Description | +| --- | --- | --- | +| name | string | octicon name | + + + +### Primer-Utilities~getSvgCss() ⇒ +Generates CSS for SVGs + +**Kind**: inner method of [Primer-Utilities](#module_Primer-Utilities) +**Returns**: CSS for SVGs + + +### Primer-Utilities~globalStyles() ⇒ string +Get global primer styles needed for GitHub web components + +**Kind**: inner method of [Primer-Utilities](#module_Primer-Utilities) +**Returns**: string - global primer style variables + + +### Primer-Utilities~getThemeColors(colors) ⇒ string +Get theme colors of a primer theme + +**Kind**: inner method of [Primer-Utilities](#module_Primer-Utilities) +**Returns**: string - primer theme colors + +| Param | Type | Description | +| --- | --- | --- | +| colors | object | primer theme colors | + + + +### Primer-Utilities~getTheme(theme) ⇒ string +Generate CSS for one primer theme + +**Kind**: inner method of [Primer-Utilities](#module_Primer-Utilities) +**Returns**: string - primer theme color variables + +| Param | Type | Description | +| --- | --- | --- | +| theme | string | name of primer theme | + + + +## Testing +Utility functions used for testing and prototyping components + +**Author**: @scottnath + + +### testing.generateMockResponse(content, type, status) ⇒ +Generate a mock github api response + +**Kind**: static method of [Testing](#module_Testing) +**Returns**: mock response object + +| Param | Type | Description | +| --- | --- | --- | +| content | GitHubRepository \| GitHubUser | mock return data | +| type | string | 'users' or 'repos' | +| status | number | 200 or 404 | + diff --git a/src/github/utils/fixtures.js b/src/github/utils/fixtures.js index 8212ac1..a3e0db3 100644 --- a/src/github/utils/fixtures.js +++ b/src/github/utils/fixtures.js @@ -1,12 +1,17 @@ /** - * @fileoverview Utility functions for fetching and parsing GitHub data + * @name Fixtures + * @module + * @typicalname fixtures + * @description Utility functions for generating fixtures for GitHub data + * @author @scottnath */ import { outputFile } from 'fs-extra'; import path from 'path'; import { fileURLToPath } from 'url'; -import { fetchRepo, fetchUser } from './github.js'; +import { fetchUser } from '../user/content.js'; +import { fetchRepo } from '../repository/content.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); diff --git a/src/github/utils/github.js b/src/github/utils/github.js deleted file mode 100644 index 9f52c78..0000000 --- a/src/github/utils/github.js +++ /dev/null @@ -1,134 +0,0 @@ -/** - * @fileoverview Utility functions for fetching and parsing GitHub data - */ - -export { intToString } from "../../utils/index.js"; - -const githubApi = 'https://api.github.com'; - -/** - * Content needed to render a GitHub user. This is a subset of the `users` endpoint response - * @see https://docs.github.com/en/rest/users/users#get-a-user - * @typedef {Object} GitHubUser - * - * @property {string} login - User's GitHub login - * @property {string} name - User's name - * @property {string} [username] - alias for `login` - * @property {string} [avatar_url] - URL to user's avatar - * @property {string} [bio] - User's biography content - * @property {string} [following] - number of people user is following - * @property {string} [followers] - number of followers - */ - -/** - * Fetch a user from - * @see https://docs.github.com/en/rest/users/users?apiVersion=2022-11-28#get-a-user - * @param {string} username - * @returns response status 200: {Object} user; else {Object} error - */ -export const fetchUser = async (username) => { - const response = await fetch(`${githubApi}/users/${username}`); - const userJson = await response.json(); - return userJson; -} - -/** - * Parse a GitHub user from the `user` endpoint response down to - * only the data required for the user component - * @param {Object} user - * @returns {GitHubUser} - */ -export const parseFetchedUser = (user = {}) => { - return { - login: user.login, - name: user.name, - username: user.login, - avatar_url: user.avatar_url, - bio: user.bio, - following: user.following, - followers: user.followers, - } -} - -/** - * Content needed to render a GitHub repository. This is a subset of the `repos` endpoint response - * @typedef {Object} GitHubRepository - * - * @property {string} itemprop - Itemprop content to go on itemscope - * @property {string} full_name - repository org and name, as in `scottnath/profile-components` - * @property {string} [name] - repo name - * @property {string} [org] - repo owner organization's login, found at `.organization.login` - * @property {string} [description] - repo description - * @property {string} [language] - programming language used in repo - * @property {string} [stargazers_count] - number of stars - * @property {string} [forks_count] - number of forks - * @property {string} [subscribers_count] - number of watchers - */ - -/** - * Fetch a GitHub repository's content from the GitHub api - * @see https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#get-a-repository - * @param {string} full_name - * @returns response status 200: {Object} repo; else {Object} error - */ -export const fetchRepo = async (full_name) => { - const response = await fetch(`${githubApi}/repos/${full_name}`); - const repoJson = await response.json(); - return repoJson; -} - -/** - * Parse a GitHub repository from the `repos` endpoint response down to - * only the data required for the repository component - * @param {Object} repo - * @returns {GitHubRepository} - */ -export const parseFetchedRepo = (repo = {}) => { - return { - full_name: repo.full_name, - name: repo.name, - org: repo.organization?.login, - description: repo.description, - language: repo.language, - stargazers_count: repo.stargazers_count, - forks_count: repo.forks_count, - subscribers_count: repo.subscribers_count, - } -} - -/** - * Generate a mock github api response - * @param {(GitHubRepository | GitHubUser)} content - mock return data - * @param {string} type - 'users' or 'repos' - * @param {number} status - 200 or 404 - * @returns - */ -export const generateMockResponse = (content, type='users', status=200) => { - let url = `${githubApi}/${type}/`; - - if (type === 'users') { - url += content.login; - } else if (type === 'repos') { - url += content.full_name; - } - - if (status === 404) { - return { - url, - method: 'GET', - status: 404, - delay: 0, - response: { - documentation_url: `https://docs.github.com/rest/${type}/${type}`, - message: "Not Found" - }, - } - } - return { - url, - method: 'GET', - status: 200, - delay: 0, - response: content, - } -} diff --git a/src/github/utils/primer.js b/src/github/utils/primer.js index ac3f931..1b9d5c4 100644 --- a/src/github/utils/primer.js +++ b/src/github/utils/primer.js @@ -1,5 +1,9 @@ /** - * @fileoverview Primer design system utilities for GitHub web components + * @name Primer-Utilities + * @module + * @typicalname primerUtils + * @description Primer design system utilities to generate assets for GitHub web components + * @author @scottnath */ import { outputFile } from 'fs-extra'; @@ -118,9 +122,6 @@ const octiconSvg = (name) => { .replace(' class=""', '') .replace(' aria-hidden="true"', '') .replace(' version="1.1"', ''); - if (name === 'mark-github') { - console.log(svg); - } return svgUrlCss(name, svg) } diff --git a/src/github/utils/testing.js b/src/github/utils/testing.js new file mode 100644 index 0000000..f73b05f --- /dev/null +++ b/src/github/utils/testing.js @@ -0,0 +1,45 @@ +/** + * @name Testing + * @module + * @typicalname testing + * @description Utility functions used for testing and prototyping components + * @author @scottnath + */ + +/** + * Generate a mock github api response + * @param {(GitHubRepository | GitHubUser)} content - mock return data + * @param {string} type - 'users' or 'repos' + * @param {number} status - 200 or 404 + * @returns mock response object + * @function + */ +export const generateMockResponse = (content, type='users', status=200) => { + let url = `https://api.github.com/${type}/`; + + if (type === 'users') { + url += content.login; + } else if (type === 'repos') { + url += content.full_name; + } + + if (status === 404) { + return { + url, + method: 'GET', + status: 404, + delay: 0, + response: { + documentation_url: `https://docs.github.com/rest/${type}/${type}`, + message: "Not Found" + }, + } + } + return { + url, + method: 'GET', + status: 200, + delay: 0, + response: content, + } +} \ No newline at end of file