From 639508190a1dc01062c17c68dda3b3106bd27463 Mon Sep 17 00:00:00 2001 From: Laegel Date: Thu, 18 Mar 2021 17:03:56 +0100 Subject: [PATCH] feat(generation): Generate + save sidebar from this repo --- .gitignore | 4 +- actions/transform.js | 0 generateSidebar.js | 31 ---------- github-action/index.js | 63 +++++++-------------- index.js | 64 +++++++-------------- main.js | 13 ----- package.json | 4 +- src/front-matter.ts | 126 +++++++++++++++++++++++++++++++++++++++++ src/index.ts | 1 + src/options.ts | 113 ++++++++++++++++++++++++++++++++++++ src/plugin.ts | 52 +++++++++++++++++ src/render.ts | 34 +++++++++++ src/sidebar.ts | 112 ++++++++++++++++++++++++++++++++++++ src/types.ts | 46 +++++++++++++++ src/watch.ts | 23 ++++++++ tsconfig.json | 15 +++++ yarn.lock | 10 ++++ 17 files changed, 577 insertions(+), 134 deletions(-) delete mode 100644 actions/transform.js delete mode 100644 generateSidebar.js delete mode 100644 main.js create mode 100644 src/front-matter.ts create mode 100644 src/index.ts create mode 100644 src/options.ts create mode 100644 src/plugin.ts create mode 100644 src/render.ts create mode 100644 src/sidebar.ts create mode 100644 src/types.ts create mode 100644 src/watch.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index b512c09..b510670 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -node_modules \ No newline at end of file +node_modules +src/*.d.ts +src/*.js \ No newline at end of file diff --git a/actions/transform.js b/actions/transform.js deleted file mode 100644 index e69de29..0000000 diff --git a/generateSidebar.js b/generateSidebar.js deleted file mode 100644 index eb9fa06..0000000 --- a/generateSidebar.js +++ /dev/null @@ -1,31 +0,0 @@ -const { readdirSync, statSync } = require("fs"); -const path = require("path"); -const { dir } = require("console"); - -const generateSidebar = (originPath) => { - let out = getTree(originPath) - - return out; -}; - -const getTree = (dirPath, items = []) => { - files = readdirSync(dirPath); - - files.forEach((file) => { - console.log(file); - if (statSync(dirPath + "/" + file).isDirectory()) { - const out = { - label: file.charAt(0).toUpperCase() + file.slice(1), - type: "category", - items: getTree(dirPath + "/" + file, []), - }; - items.push(out); - } else { - items.push(path.join(__dirname, dirPath, "/", file)); - } - }); - - return items; -}; - -module.exports = generateSidebar; diff --git a/github-action/index.js b/github-action/index.js index 127db8f..a5af3af 100644 --- a/github-action/index.js +++ b/github-action/index.js @@ -1,60 +1,35 @@ const core = require("@actions/core"); -const { transformDocs } = require("../main"); -const generateSidebar = require("../generateSidebar"); -const { readFile, writeFile } = require("fs").promises; +const { rmdir } = require("fs").promises; +const { default: generate } = require("../src/plugin"); (async () => { try { // Where your docs live, should be the folder containing the crates docs const originPath = core.getInput("originPath"); // e.g. "/path/to/project/src/"; + const sidebarFile = process.env["sidebarFile"]; // Where you'll save your MD files const targetPath = core.getInput("targetPath"); // e.g. "/path/to/docusaurus/website/docs/api/js/"; + const docusaurusPath = core.getInput("docusaurusPath"); - /* - Where lives your sidebars config file - Doesn't have to be JSON but it's easier to change programmatically, - you may create your own saving method - */ - const sidebarPath = core.getInput("sidebarPath"); // e.g. "/path/to/docusaurus/website/sidebars.json"; + await rmdir(targetPath, { recursive: true }); - // rustdoc uses relative links for crate types relations - const linksRoot = core.getInput("linksRoot"); // e.g. "/docs/api/rust/"; - - const entryPoints = core.getInput("entryPoints").split(","); - - // await Promise.all( - // entryPoints.map((entryPoint) => - // fs.rmdir(targetPath + entryPoint, { recursive: true }) - // ) - // ); - - // const sidebarItems = ( - // await Promise.all( - // entryPoints.map(async (entryPoint) => ({ - // entryPoint, - // docs: await transformDocs( - // originPath + entryPoint, - // originPath, - // targetPath - // ), - // })) - // ) - // ).map((item) => generateSidebar(item.docs, item.entryPoint, originPath)); - const sidebarItems = generateSidebar(item.docs, item.entryPoint, originPath) - - // Automatically add the sidebar items to Docusaurus sidebar file config - const sidebarContent = JSON.parse(await readFile(sidebarPath, "utf-8")); - const index = sidebarContent.docs[3].items - .map((row, index) => (row.label && row.label === "JavaScript" ? index : 0)) - .reduce((accumulator, value) => accumulator + value); - sidebarContent.docs[3].items[index].items = sidebarItems; // Specify where to put the items - - console.log(sidebarContent); - // writeFile(sidebarPath, JSON.stringify(sidebarContent, null, 2)); + await generate(docusaurusPath, { + entryPoints: originPath + "src", + out: targetPath, + entryDocument: "index.md", + hideInPageTOC: true, + hideBreadcrumbs: true, + watch: false, + tsconfig: originPath + "tsconfig.json", + sidebar: { + sidebarFile, + }, + readme: "none", + }); console.log("Tasks completed!"); } catch (error) { core.setFailed(error.message); } -})(); \ No newline at end of file +})(); diff --git a/index.js b/index.js index 276ccbf..bff1ccb 100644 --- a/index.js +++ b/index.js @@ -1,60 +1,36 @@ // const core = require("@actions/core"); -const { transformDocs } = require("./main"); -const generateSidebar = require("./generateSidebar"); -const { readFile, writeFile, rmdir } = require("fs").promises; +const { rmdir } = require("fs").promises; +const { default: generate } = require("./src/plugin"); (async () => { try { // Where your docs live, should be the folder containing the crates docs const originPath = process.env["originPath"]; // e.g. "/path/to/project/src/"; + const sidebarFile = process.env["sidebarFile"]; // Where you'll save your MD files const targetPath = process.env["targetPath"]; // e.g. "/path/to/docusaurus/website/docs/api/js/"; - /* - Where lives your sidebars config file - Doesn't have to be JSON but it's easier to change programmatically, - you may create your own saving method - */ - const sidebarPath = process.env["sidebarPath"]; // e.g. "/path/to/docusaurus/website/sidebars.json"; + const docusaurusPath = process.env["docusaurusPath"]; + await rmdir(targetPath, { recursive: true }); - // rustdoc uses relative links for crate types relations - // const linksRoot = core.getInput("linksRoot"); // e.g. "/docs/api/rust/"; - - // await Promise.all( - // entryPoints.map((entryPoint) => - // rmdir(targetPath + entryPoint, { recursive: true }) - // ) - // ); - - // const sidebarItems = ( - // await Promise.all( - // entryPoints.map(async (entryPoint) => ({ - // entryPoint, - // docs: await transformDocs( - // originPath + entryPoint, - // originPath, - // targetPath - // ), - // })) - // ) - // ).map((item) => generateSidebar(item.docs, item.entryPoint, originPath)); - - const sidebarItems = generateSidebar(originPath) - - // Automatically add the sidebar items to Docusaurus sidebar file config - const sidebarContent = JSON.parse(await readFile(sidebarPath, "utf-8")); - const index = sidebarContent.docs[3].items - .map((row, index) => (row.label && row.label === "JavaScript" ? index : 0)) - .reduce((accumulator, value) => accumulator + value); - sidebarContent.docs[3].items[index].items = sidebarItems; // Specify where to put the items - - console.log(sidebarContent.docs[3].items[index].items); - // writeFile(sidebarPath, JSON.stringify(sidebarContent, null, 2)); + await generate(docusaurusPath, { + entryPoints: originPath + "src", + out: targetPath, + entryDocument: "index.md", + hideInPageTOC: true, + hideBreadcrumbs: true, + watch: false, + tsconfig: originPath + "tsconfig.json", + sidebar: { + sidebarFile, + }, + readme: "none", + }); console.log("Tasks completed!"); } catch (error) { - throw error + throw error; // core.setFailed(error.message); } -})(); \ No newline at end of file +})(); diff --git a/main.js b/main.js deleted file mode 100644 index 7c840f9..0000000 --- a/main.js +++ /dev/null @@ -1,13 +0,0 @@ - - -const transformDocs = async (folderPath, originPath, targetPath) => { - - await transform(contents, crateName); - await save(results, originPath, targetPath); - - return results; -}; - -module.exports = { - transformDocs, -}; \ No newline at end of file diff --git a/package.json b/package.json index 32bd7ee..8e56fe6 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,9 @@ "main": "index.js", "license": "MIT", "dependencies": { + "@types/node": "^14.14.35", "typedoc": "^0.20.30", - "typedoc-plugin-markdown": "^3.6.0" + "typedoc-plugin-markdown": "^3.6.0", + "typescript": "^4.2.3" } } diff --git a/src/front-matter.ts b/src/front-matter.ts new file mode 100644 index 0000000..ed32f98 --- /dev/null +++ b/src/front-matter.ts @@ -0,0 +1,126 @@ +import * as path from 'path'; + +import { BindOption } from 'typedoc'; +import { Component } from 'typedoc/dist/lib/converter/components'; +import { RendererComponent } from 'typedoc/dist/lib/output/components'; +import { PageEvent } from 'typedoc/dist/lib/output/events'; + +import { FrontMatter, Sidebar } from './types'; + +import { reflectionTitle } from 'typedoc-plugin-markdown/dist/resources/helpers/reflection-title'; + +export interface FrontMatterVars { + [key: string]: string | number | boolean; +} + +/** + * Prepends YAML block to a string + * @param contents - the string to prepend + * @param vars - object of required front matter variables + */ +export const prependYAML = (contents: string, vars: FrontMatterVars) => { + return contents + .replace(/^/, toYAML(vars) + '\n\n') + .replace(/[\r\n]{3,}/g, '\n\n'); +}; + +/** + * Returns the page title as rendered in the document h1(# title) + * @param page + */ +export const getPageTitle = (page: PageEvent) => { + return reflectionTitle.call(page, false); +}; + +/** + * Converts YAML object to a YAML string + * @param vars + */ +const toYAML = (vars: FrontMatterVars) => { + const yaml = `--- +${Object.entries(vars) + .map( + ([key, value]) => + `${key}: ${ + typeof value === 'string' ? `"${escapeString(value)}"` : value + }`, + ) + .join('\n')} +---`; + return yaml; +}; + +// prettier-ignore +const escapeString=(str: string) => str.replace(/([^\\])'/g, '$1\\\''); + +@Component({ name: 'front-matter' }) +export class FrontMatterComponent extends RendererComponent { + @BindOption('out') + out!: string; + @BindOption('sidebar') + sidebar!: Sidebar; + @BindOption('globalsTitle') + globalsTitle!: string; + @BindOption('readmeTitle') + readmeTitle!: string; + @BindOption('entryDocument') + entryDocument!: string; + + globalsFile = 'modules.md'; + + initialize() { + super.initialize(); + this.listenTo(this.application.renderer, { + [PageEvent.END]: this.onPageEnd, + }); + } + onPageEnd(page: PageEvent) { + if (page.contents) { + page.contents = prependYAML(page.contents, this.getYamlItems(page)); + } + } + + getYamlItems(page: PageEvent): any { + const pageTitle = this.getTitle(page); + const sidebarLabel = this.getSidebarLabel(page); + let items: FrontMatter = { + title: pageTitle, + }; + if (sidebarLabel && sidebarLabel !== pageTitle) { + items = { ...items, sidebar_label: sidebarLabel }; + } + return { + ...items, + custom_edit_url: null, + hide_title: true, + }; + } + + getSidebarLabel(page: PageEvent) { + if (!this.sidebar) { + return null; + } + if (page.url === this.entryDocument) { + return page.url === page.project.url + ? this.sidebar.indexLabel + : this.sidebar.readmeLabel; + } + + if (page.url === this.globalsFile) { + return this.sidebar.indexLabel; + } + return this.sidebar.fullNames ? page.model.getFullName() : page.model.name; + } + + getId(page: PageEvent) { + return path.basename(page.url, path.extname(page.url)); + } + + getTitle(page: PageEvent) { + const readmeTitle = this.readmeTitle || page.project.name; + if (page.url === this.entryDocument && page.url !== page.project.url) { + return readmeTitle; + } + return getPageTitle(page); + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..b68aea5 --- /dev/null +++ b/src/index.ts @@ -0,0 +1 @@ +export { default } from './plugin'; diff --git a/src/options.ts b/src/options.ts new file mode 100644 index 0000000..a9539b9 --- /dev/null +++ b/src/options.ts @@ -0,0 +1,113 @@ +import * as path from 'path'; + +import { + Application, + MixedDeclarationOption, + ParameterType, + StringDeclarationOption, + TSConfigReader, + TypeDocReader, +} from 'typedoc'; + +import { PluginOptions, SidebarOptions } from './types'; + +/** + * Default plugin options + */ +const DEFAULT_PLUGIN_OPTIONS: PluginOptions = { + id: 'default', + docsRoot: 'docs', + out: 'api', + entryDocument: 'index.md', + hideInPageTOC: true, + hideBreadcrumbs: true, + sidebar: { + fullNames: false, + sidebarFile: 'typedoc-sidebar.js', + indexLabel: 'Table of contents', + readmeLabel: 'Readme', + sidebarPath: '', + }, + plugin: ['none'], + outputDirectory: '', + siteDir: '', + watch: false, +}; + +/** + * Merge default with user options + * @param opts + */ +export const getOptions = ( + siteDir: string, + opts: Partial, +): PluginOptions => { + // base options + let options = { + ...DEFAULT_PLUGIN_OPTIONS, + ...opts, + }; + // sidebar + if (opts.sidebar === null) { + options = { ...options, sidebar: null }; + } else { + const sidebar = { + ...DEFAULT_PLUGIN_OPTIONS.sidebar, + ...opts.sidebar, + } as SidebarOptions; + options = { + ...options, + sidebar: { + ...sidebar, + sidebarPath: path.resolve(siteDir, sidebar.sidebarFile), + }, + }; + } + // additional + options = { + ...options, + siteDir, + outputDirectory: path.resolve(siteDir, options.docsRoot, options.out), + }; + return options; +}; + +/** + * Add docusaurus options to converter + * @param app + */ +export const addOptions = (app: Application) => { + // configure deault typedoc options + app.options.addReader(new TypeDocReader()); + app.options.addReader(new TSConfigReader()); + + // expose plugin options to typedoc so we can access if required + app.options.addDeclaration({ + name: 'id', + } as StringDeclarationOption); + + app.options.addDeclaration({ + name: 'docsRoot', + } as StringDeclarationOption); + + app.options.addDeclaration({ + name: 'siteDir', + } as MixedDeclarationOption); + + app.options.addDeclaration({ + name: 'outputDirectory', + } as StringDeclarationOption); + + app.options.addDeclaration({ + name: 'globalsTitle', + } as StringDeclarationOption); + + app.options.addDeclaration({ + name: 'readmeTitle', + } as StringDeclarationOption); + + app.options.addDeclaration({ + name: 'sidebar', + type: ParameterType.Mixed, + } as MixedDeclarationOption); +}; diff --git a/src/plugin.ts b/src/plugin.ts new file mode 100644 index 0000000..3d33244 --- /dev/null +++ b/src/plugin.ts @@ -0,0 +1,52 @@ +import { Application } from 'typedoc'; +import * as MarkdownPlugin from 'typedoc-plugin-markdown'; + +import { FrontMatterComponent } from './front-matter'; +import { addOptions, getOptions } from './options'; +import { render } from './render'; +import { SidebarComponent } from './sidebar'; +import { PluginOptions } from './types'; + + +export default async function generate( + siteDir: string, + opts: Partial, +) { + // we need to generate an empty sidebar up-front so it can be resolved from sidebars.js + const options = getOptions(siteDir, opts); + // if (options.sidebar) { + // writeSidebar(options.sidebar, 'module.exports=[];'); + // } + + // initialize and build app + const app = new Application(); + + // load the markdown plugin + MarkdownPlugin(app); + + // customise render + app.renderer.render = render; + + // add plugin options + addOptions(app); + + // bootstrap typedoc app + app.bootstrap(options); + + // add frontmatter component to typedoc renderer + app.renderer.addComponent('fm', new FrontMatterComponent(app.renderer)); + + // add sidebar component to typedoc renderer + app.renderer.addComponent('sidebar', new SidebarComponent(app.renderer)); + + // return the generated reflections + const project = app.convert(); + + // if project is undefined typedoc has a problem - error logging will be supplied by typedoc. + if (!project) { + return; + } + + // generate or watch app + return app.generateDocs(project, options.outputDirectory); +} diff --git a/src/render.ts b/src/render.ts new file mode 100644 index 0000000..8d9d67c --- /dev/null +++ b/src/render.ts @@ -0,0 +1,34 @@ +import { ProjectReflection, UrlMapping } from 'typedoc'; +import { RendererEvent } from 'typedoc/dist/lib/output/events'; +import * as ts from 'typescript'; + +export async function render( + project: ProjectReflection, + outputDirectory: string, +) { + if (!this.prepareTheme() || !this.prepareOutputDirectory(outputDirectory)) { + return; + } + + const output = new RendererEvent( + RendererEvent.BEGIN, + outputDirectory, + project, + ); + + output.settings = this.application.options.getRawValues(); + output.urls = this.theme!.getUrls(project); + + this.trigger(output); + + if (!output.isDefaultPrevented) { + output.urls?.forEach((mapping: UrlMapping, i) => { + this.renderDocument(output.createPageEvent(mapping)); + ts.sys.write( + `\rGenerated ${i + 1} of ${output.urls?.length} TypeDoc docs`, + ); + }); + ts.sys.write(`\n`); + this.trigger(RendererEvent.END, output); + } +} diff --git a/src/sidebar.ts b/src/sidebar.ts new file mode 100644 index 0000000..8251d43 --- /dev/null +++ b/src/sidebar.ts @@ -0,0 +1,112 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +import { BindOption } from 'typedoc'; +import { Component } from 'typedoc/dist/lib/converter/components'; +import { RendererComponent } from 'typedoc/dist/lib/output/components'; +import { RendererEvent } from 'typedoc/dist/lib/output/events'; + +import { SidebarItem, SidebarOptions } from './types'; +import { readFile, writeFile } from 'fs/promises'; + +@Component({ name: 'sidebar' }) +export class SidebarComponent extends RendererComponent { + @BindOption('sidebar') + sidebar!: SidebarOptions; + @BindOption('siteDir') + siteDir!: string; + @BindOption('out') + out!: string; + + initialize() { + this.listenTo(this.application.renderer, { + [RendererEvent.BEGIN]: this.onRendererBegin, + }); + } + + async onRendererBegin(renderer: RendererEvent) { + const navigation = this.application.renderer.theme?.getNavigation( + renderer.project, + ); + + const out = this.out.match(/(?:.*)en\/(.*)/)![1]; + + // map the navigation object to a Docuaurus sidebar format + const sidebarItems = navigation?.children + ? navigation.children.map((navigationItem) => { + if (navigationItem.isLabel) { + const sidebarCategoryItems = navigationItem.children + ? navigationItem.children.map((navItem) => { + const url = this.getUrlKey(out, navItem.url); + if (navItem.children && navItem.children.length > 0) { + const sidebarCategoryChildren = navItem.children.map( + (childGroup) => + this.getSidebarCategory( + childGroup.title, + childGroup.children + ? childGroup.children.map((childItem) => + this.getUrlKey(out, childItem.url), + ) + : [], + ), + ); + return this.getSidebarCategory(navItem.title, [ + url, + ...sidebarCategoryChildren, + ]); + } + return url; + }) + : []; + return this.getSidebarCategory( + navigationItem.title, + sidebarCategoryItems, + ); + } + return this.getUrlKey(out, navigationItem.url); + }) + : []; + + const sidebarPath = this.sidebar.sidebarPath; + + const sidebarContent = JSON.parse(await readFile(sidebarPath!, "utf-8")); + const index = sidebarContent.docs[3].items + .map((row, index) => (row.label && row.label === "JavaScript" ? index : 0)) + .reduce((accumulator, value) => accumulator + value); + sidebarContent.docs[3].items[index].items = sidebarItems; // Specify where to put the items + + writeFile(sidebarPath, JSON.stringify(sidebarContent, null, 2)); + this.application.logger.success( + `TypeDoc sidebar written to ${sidebarPath}`, + ); + } + + /** + * returns a sidebar category node + */ + getSidebarCategory(title: string, items: SidebarItem[]) { + return { + type: 'category', + label: title, + items, + }; + } + + /** + * returns the url key for relevant doc + */ + getUrlKey(out: string, url: string) { + const urlKey = url.replace('.md', ''); + return out ? out + '/' + urlKey : urlKey; + } +} + +/** + * Write content to sidebar file + */ +export const writeSidebar = (sidebar: SidebarOptions, content: string) => { + if (!fs.existsSync(path.dirname(sidebar.sidebarPath))) { + fs.mkdirSync(path.dirname(sidebar.sidebarPath)); + } + fs.writeFileSync(sidebar.sidebarPath, content); +}; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..ba24417 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,46 @@ +export interface PluginOptions { + id: string; + docsRoot: string; + out: string; + sidebar: SidebarOptions | null; + readmeTitle?: string; + globalsTitle?: string; + plugin: string[]; + readme?: string; + disableOutputCheck?: boolean; + entryPoints?: string[]; + entryDocument: string; + hideInPageTOC: boolean; + hideBreadcrumbs: boolean; + siteDir: string; + outputDirectory: string; + watch: boolean; +} + +export interface FrontMatter { + id?: string; + title: string; + slug?: string; + sidebar_label?: string; + hide_title?: boolean; +} + +export interface SidebarOptions { + fullNames?: boolean; + sidebarFile: string; + sidebarPath: string; + indexLabel?: string; + readmeLabel?: string; +} + +export interface Sidebar { + [sidebarId: string]: SidebarItem[]; +} + +export interface SidebarCategory { + type: string; + label: string; + items: SidebarItem[]; +} + +export type SidebarItem = SidebarCategory | string; diff --git a/src/watch.ts b/src/watch.ts new file mode 100644 index 0000000..a68b38b --- /dev/null +++ b/src/watch.ts @@ -0,0 +1,23 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +import { Application } from 'typedoc'; + +import { PluginOptions } from './types'; + +/** + * Calls TypeDoc's `convertAndWatch` and force trigger sidebars refresh. + */ +export const convertAndWatch = (app: Application, options: PluginOptions) => { + const sidebarsJsPath = path.resolve(options.siteDir, 'sidebars.js'); + app.convertAndWatch(async (project) => { + if (options.sidebar) { + // remove typedoc sidebar from require cache + delete require.cache[options.sidebar.sidebarPath]; + // force trigger a sidebars.js refresh + const sidebarJsContent = fs.readFileSync(sidebarsJsPath); + fs.writeFileSync(sidebarsJsPath, sidebarJsContent); + } + await app.generateDocs(project, options.outputDirectory); + }); +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..7d52828 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "declaration": true, + "experimentalDecorators": true, + "lib": ["es2018", "dom"], + "module": "commonjs", + "moduleResolution": "node", + "noImplicitAny": false, + "noUnusedLocals": true, + "removeComments": false, + "sourceMap": false, + "strictNullChecks": true, + "target": "es2018" + } +} diff --git a/yarn.lock b/yarn.lock index 9788e4c..3eb7c0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@types/node@^14.14.35": + version "14.14.35" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.35.tgz#42c953a4e2b18ab931f72477e7012172f4ffa313" + integrity sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag== + at-least-node@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" @@ -254,6 +259,11 @@ typedoc@^0.20.30: shiki "^0.9.2" typedoc-default-themes "^0.12.8" +typescript@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.3.tgz#39062d8019912d43726298f09493d598048c1ce3" + integrity sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw== + uglify-js@^3.1.4: version "3.13.0" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.13.0.tgz#66ed69f7241f33f13531d3d51d5bcebf00df7f69"