diff --git a/README.md b/README.md index 404dbef6..458c09e7 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ features! support](#editor-support)) - Vivify server starts lazily and automatically shuts down when no more viewers are connected -- various [config options](#config) +- various [customization options](docs/customization.md) If you need any additional features, feel free to [open an issue](https://github.com/jannis-baum/vivify/issues/new/choose) or @@ -54,47 +54,6 @@ list! - for Vim and Neovim: [vivify.vim](https://github.com/jannis-baum/vivify.vim) -### Config - -Vivify will look for an optional config file at `~/.vivify/config.json` and -`~/.vivify.json`. This file should contain a JSON object that can have the -following optional keys: - -- **`"styles"`**\ - a path to a custom style sheet, see [the default - styles](./static/) for examples -- **`"port"`**\ - the port Vivify's server should run on; this will be overwritten by - the environment variable `VIV_PORT` (default is 31622) -- **`"timeout"`**\ - how long the server should wait in ms before shutting down after - the last client disconnected; this will be overwritten by the environment - variable `VIV_TIMEOUT` (default is 10000) -- **`"katexOptions"`**\ - [available KaTeX options](https://katex.org/docs/options.html), such as - - ```json - { - "errorColor": "#cc0000", - "macros": { - "\\RR": "\\mathbb{R}" - } - } - ``` - -- **`"pageTitle"`**\ - JavaScript code that will be evaluated to determine the viewer's page title. - Here, the variable `components` is set to a string array of path components - for the current file, e.g. `['/', 'Users', 'you', 'file.txt']`. If this - evaluation fails, the title will be *custom title error* and you will see the - error message on the page. The default title are the last two components - joined with the path separator, e.g. `you/file.txt` -- **`"mdExtensions"`**\ - An array of file extensions that Vivify will parse as Markdown. All other - files will be displayed as monospaced text with code highlighting if - available. Default Markdown extensions are `['markdown', 'md', 'mdown', - 'mdwn', 'mkd', 'mkdn']` - ## Installation Once you have Vivify installed, use it by running `viv` with any text file or diff --git a/CONTRIBUTING.md b/docs/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to docs/CONTRIBUTING.md diff --git a/docs/customization.md b/docs/customization.md new file mode 100644 index 00000000..629f8750 --- /dev/null +++ b/docs/customization.md @@ -0,0 +1,55 @@ +# Customizing Vivify + +Vivify offers various configuration options. It aims to have sensible defaults +while being built for maximal customizability. + +Vivify will look for an optional config file at `~/.vivify/config.json` and +`~/.vivify.json`. This file should contain a JSON object that can have the +following optional keys: + +- **`"styles"`**\ + A path to a single custom style sheet, or an array of multiple style sheets + applied in order. These will be applied after Vivify's [default + styles](./static/) are applied so that there are always sensible fallbacks but + you can override everything. +- **`"scripts"`**\ + A path to a single custom JavaScript to inject into the viewing pages, or an + array of multiple custom scripts. +- **`"dirListIgnore"`**\ + A path to a file with globs to ignore in Vivify's directory viewer, or an + array of multiple paths to ignore files. The syntax here is the same as in + `.gitignore` files. +- **`"port"`**\ + The port Vivify's server should run on; this will be overwritten by + the environment variable `VIV_PORT` (default is 31622) +- **`"timeout"`**\ + How long the server should wait in milliseconds before shutting down after the + last client disconnected; this will be overwritten by the environment variable + `VIV_TIMEOUT` (default is 10000) +- **`"katexOptions"`**\ + [Available KaTeX options](https://katex.org/docs/options.html), such as + + ```json + { + "errorColor": "#cc0000", + "macros": { + "\\RR": "\\mathbb{R}" + } + } + ``` + +- **`"pageTitle"`**\ + JavaScript code that will be evaluated to determine the viewer's page title. + Here, the variable `components` is set to a string array of path components + for the current file, e.g. `['~', 'some', 'path', 'file.txt']`. If this + evaluation fails, the title will be *custom title error* and you will see the + error message on the page. The default title are the last two components + joined with the path separator, e.g. `path/file.txt` +- **`"mdExtensions"`**\ + An array of file extensions that Vivify will render as Markdown. All other + files (except for Jupyter Notebooks) will be displayed as monospaced text with + code highlighting if available. The default Markdown extensions are + `['markdown', 'md', 'mdown', 'mdwn', 'mkd', 'mkdn']` +- **`"preferHomeTilde"`**\ + Prefer using `~` as a placeholder for your home directory in URLs as well as + the `components` for `"pageTitle"` (default is `true`) diff --git a/package.json b/package.json index 3130749c..cc233cd6 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@viz-js/viz": "^3.7.0", "ansi_up": "^6.0.2", "express": "^4.19.2", + "glob": "10.4.5", "highlight.js": "^11.10.0", "katex": "^0.16.11", "markdown-it": "^14.1.0", diff --git a/src/app.ts b/src/app.ts index c52d927f..8424e96e 100644 --- a/src/app.ts +++ b/src/app.ts @@ -9,7 +9,7 @@ import { router as healthRouter } from './routes/health.js'; import { router as staticRouter } from './routes/static.js'; import { router as viewerRouter } from './routes/viewer.js'; import { setupSockets } from './sockets.js'; -import { pathToURL, urlToPath } from './utils/path.js'; +import { pathToURL, preferredPath, urlToPath } from './utils/path.js'; import { existsSync } from 'fs'; const app = express(); @@ -48,8 +48,8 @@ const openArgs = async () => { console.log(`File not found: ${path}`); return; } - const absolute = presolve(path); - const url = `${address}${pathToURL(absolute)}`; + const target = preferredPath(presolve(path)); + const url = `${address}${pathToURL(target)}`; await open(url); }), ); diff --git a/src/parser/config.ts b/src/parser/config.ts index 6163fe78..275f9c05 100644 --- a/src/parser/config.ts +++ b/src/parser/config.ts @@ -4,18 +4,22 @@ import path from 'path'; type Config = { styles?: string; + scripts?: string; + dirListIgnore?: string[]; port: number; + timeout: number; /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ katexOptions?: any; pageTitle?: string; mdExtensions: string[]; - timeout: number; + preferHomeTilde: boolean; }; const defaultConfig: Config = { port: 31622, mdExtensions: ['markdown', 'md', 'mdown', 'mdwn', 'mkd', 'mkdn'], timeout: 10000, + preferHomeTilde: true, }; const envConfigs: [string, keyof Config][] = [ @@ -28,9 +32,23 @@ const configPaths = [ path.join(homedir(), '.vivify.json'), ]; +// read contents of file at paths or files at paths +const getFileContents = (paths: string[] | string | undefined): string => { + if (paths === undefined) return ''; + const getFileContent = (p: string): string => { + const resolved = p[0] === '~' ? path.join(homedir(), p.slice(1)) : p; + return fs.existsSync(resolved) ? fs.readFileSync(resolved, 'utf8') : ''; + }; + + if (Array.isArray(paths)) { + return paths.map(getFileContent).join('\n'); + } + return getFileContent(paths); +}; + const getConfig = (): Config => { let config = undefined; - // greedily get config + // greedily find config for (const cp of configPaths) { if (!fs.existsSync(cp)) continue; try { @@ -41,12 +59,13 @@ const getConfig = (): Config => { if (config === undefined) return defaultConfig; - // get styles - if (config.styles && config.styles.length > 0) { - const stylePath = - config.styles[0] === '~' ? path.join(homedir(), config.styles.slice(1)) : config.styles; - config.styles = fs.existsSync(stylePath) ? fs.readFileSync(stylePath, 'utf8') : ''; - } + // get styles, scripts and ignore files + config.styles = getFileContents(config.styles); + config.scripts = getFileContents(config.scripts); + config.dirListIgnore = getFileContents(config.dirListIgnore) + .split('\n') + .filter((pattern) => pattern !== '' && pattern[0] !== '#'); + // fill missing values from default config for (const [key, value] of Object.entries(defaultConfig)) { if (config[key] === undefined) config[key] = value; diff --git a/src/parser/parser.ts b/src/parser/parser.ts index 9765ab53..a75ed59c 100644 --- a/src/parser/parser.ts +++ b/src/parser/parser.ts @@ -1,10 +1,11 @@ -import { Dirent, readdirSync } from 'fs'; +import { Dirent } from 'fs'; import { homedir } from 'os'; import { join as pjoin } from 'path'; import { pathToURL } from '../utils/path.js'; import config from './config.js'; import renderNotebook from './ipynb.js'; import renderMarkdown from './markdown.js'; +import { globSync } from 'glob'; export type Renderer = (content: string) => string; @@ -38,13 +39,20 @@ export function renderTextFile(content: string, path: string): string { } const dirListItem = (item: Dirent, path: string) => - `
  • ${item.name}
  • `; export function renderDirectory(path: string): string { - const list = readdirSync(path, { withFileTypes: true }) - .sort((a, b) => +b.isDirectory() - +a.isDirectory()) + const list = globSync('*', { + cwd: path, + withFileTypes: true, + ignore: config.dirListIgnore, + dot: true, + maxDepth: 1, + }) + // sort directories first and alphabetically in one combined smart step + .sort((a, b) => +b.isDirectory() - +a.isDirectory() || a.name.localeCompare(b.name)) .map((item) => dirListItem(item, path)) .join('\n'); return wrap( diff --git a/src/routes/viewer.ts b/src/routes/viewer.ts index 84f39b12..05ee4216 100644 --- a/src/routes/viewer.ts +++ b/src/routes/viewer.ts @@ -1,11 +1,12 @@ import { lstatSync, readFileSync } from 'fs'; import { dirname as pdirname, join as pjoin } from 'path'; +import { homedir } from 'os'; import { Request, Response, Router } from 'express'; import { messageClientsAt } from '../app.js'; import config from '../parser/config.js'; -import { pathToURL, pcomponents, pmime } from '../utils/path.js'; +import { absPath, pathToURL, pcomponents, pmime, preferredPath } from '../utils/path.js'; import { renderDirectory, renderTextFile } from '../parser/parser.js'; export const router = Router(); @@ -13,7 +14,7 @@ export const router = Router(); const liveContent = new Map(); const pageTitle = (path: string) => { - const comps = pcomponents(path); + const comps = pcomponents(preferredPath(path)); if (config.pageTitle) { return eval(` const components = ${JSON.stringify(comps)}; @@ -22,6 +23,16 @@ const pageTitle = (path: string) => { } else return pjoin(...comps.slice(-2)); }; +if (config.preferHomeTilde) { + router.use((req, res, next) => { + if (req.method === 'GET' && req.path.startsWith(homedir())) { + res.redirect(req.baseUrl + req.path.replace(homedir(), '/~')); + } else { + next(); + } + }); +} + router.get(/.*/, async (req: Request, res: Response) => { const path = res.locals.filepath; @@ -63,9 +74,7 @@ router.get(/.*/, async (req: Request, res: Response) => { - + ${config.styles ? `` : ''}
    @@ -74,8 +83,9 @@ router.get(/.*/, async (req: Request, res: Response) => { + ${config.scripts ? `` : ''} `); diff --git a/src/utils/path.ts b/src/utils/path.ts index 27cecb0b..38cd7e24 100644 --- a/src/utils/path.ts +++ b/src/utils/path.ts @@ -1,6 +1,7 @@ import { execSync } from 'child_process'; import { homedir } from 'os'; import { basename as pbasename, dirname as pdirname, parse as pparse } from 'path'; +import config from '../parser/config.js'; export const pmime = (path: string) => execSync(`file --mime-type -b '${path}'`).toString().trim(); @@ -9,7 +10,7 @@ export const pcomponents = (path: string) => { const components = new Array(); // directory let dir = parsed.dir; - while (dir !== '/' && dir !== '') { + while (dir !== '/' && dir !== '.') { components.unshift(pbasename(dir)); dir = pdirname(dir); } @@ -20,12 +21,17 @@ export const pcomponents = (path: string) => { return components; }; +export const absPath = (path: string) => path.replace(/^\/~/, homedir()).replace(/\/+$/, ''); + export const urlToPath = (url: string) => { - const path = decodeURIComponent(url.replace(/^\/(viewer|health)/, '')) - .replace(/^\/~/, homedir()) - .replace(/\/+$/, ''); + const path = absPath(decodeURIComponent(url.replace(/^\/(viewer|health)/, ''))); return path === '' ? '/' : path; }; -export const pathToURL = (path: string, route: string = 'viewer') => - `/${route}${encodeURIComponent(path).replaceAll('%2F', '/')}`; +export const pathToURL = (path: string, route: string = 'viewer') => { + const withoutPrefix = path.startsWith('/') ? path.slice(1) : path; + return `/${route}/${encodeURIComponent(withoutPrefix).replaceAll('%2F', '/')}`; +}; + +export const preferredPath = (path: string): string => + config.preferHomeTilde && path.startsWith(homedir()) ? path.replace(homedir(), '~') : path; diff --git a/yarn.lock b/yarn.lock index 33206895..0a7c953a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -75,6 +75,18 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.0.tgz#6d86b8cb322660f03d3f0aa94b99bdd8e172d570" integrity sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew== +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + "@jridgewell/gen-mapping@^0.3.5": version "0.3.5" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" @@ -168,6 +180,11 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + "@pkgr/core@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31" @@ -633,13 +650,23 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-styles@^4.1.0: +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: color-convert "^2.0.1" +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + ansi_up@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/ansi_up/-/ansi_up-6.0.2.tgz#083adb65be5b21ba283fd105d3102e64f3f0b092" @@ -881,7 +908,7 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -956,6 +983,11 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -966,6 +998,16 @@ electron-to-chromium@^1.4.796: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.816.tgz#3624649d1e7fde5cdbadf59d31a524245d8ee85f" integrity sha512-EKH5X5oqC6hLmiS7/vYtZHZFTNdhsYG5NVPRN6Yn0kQHNBlT59+xSM8HBy66P5fxWpKgZbPqb+diC64ng295Jw== +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -1289,6 +1331,14 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== +foreground-child@^3.1.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.2.1.tgz#767004ccf3a5b30df39bed90718bab43fe0a59f7" + integrity sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -1343,6 +1393,18 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== +glob@10.4.5: + version "10.4.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + globals@^14.0.0: version "14.0.0" resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" @@ -1502,6 +1564,11 @@ is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" @@ -1550,6 +1617,15 @@ isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + jest-worker@^27.4.5: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" @@ -1644,6 +1720,11 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lru-cache@^10.2.0: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -1800,6 +1881,11 @@ minimatch@^9.0.4: dependencies: brace-expansion "^2.0.1" +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -1935,6 +2021,11 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +package-json-from-dist@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz#e501cd3094b278495eb4258d4c9f6d5ac3019f00" + integrity sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -1962,6 +2053,14 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" @@ -2227,6 +2326,11 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + simple-update-notifier@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb" @@ -2262,13 +2366,54 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== -strip-ansi@^6.0.1: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" @@ -2561,6 +2706,24 @@ wildcard@^2.0.0: resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + ws@^8.18.0: version "8.18.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc"