diff --git a/.projenrc.ts b/.projenrc.ts index 249e1039d..316061224 100644 --- a/.projenrc.ts +++ b/.projenrc.ts @@ -1666,12 +1666,27 @@ const cdkExplorer = configureProject( ], devDeps: [ 'vscode-languageserver-protocol@^3', + '@types/express@^4', + 'react@^18', + 'react-dom@^18', + '@types/react@^18', + '@types/react-dom@^18', + '@cloudscape-design/components@^3', + '@cloudscape-design/global-styles@^1', + 'esbuild', + 'tsx', + 'supertest@^6', + '@types/supertest@^6', '@types/convert-source-map@^2', + 'prismjs@^1', + '@types/prismjs@^1', + 'yaml@^2', ], tsconfig: { compilerOptions: { ...defaultTsOptions, }, + exclude: ['frontend'], }, jestOptions: jestOptionsForProject({ jestConfig: { @@ -1686,7 +1701,11 @@ const cdkExplorer = configureProject( }), ); fixupTestTask(cdkExplorer); -void cdkExplorer; +cdkExplorer.postCompileTask.exec('tsx build-tools/bundle-frontend.ts'); +cdkExplorer.eslint?.allowDefaultProjectFiles('build-tools/bundle-frontend.ts'); +cdkExplorer.gitignore.addPatterns('lib/web/static/', 'lib/web/web-assets.generated.json'); +cdkExplorer.npmignore?.addPatterns('frontend', 'tsconfig.frontend.json'); +cli.deps.addDependency('@aws-cdk/cdk-explorer', pj.DependencyType.RUNTIME); cli.deps.addDependency('@aws-cdk/cdk-explorer', pj.DependencyType.RUNTIME); diff --git a/packages/@aws-cdk/cdk-explorer/.eslintrc.json b/packages/@aws-cdk/cdk-explorer/.eslintrc.json index e0c610e49..bdc74288f 100644 --- a/packages/@aws-cdk/cdk-explorer/.eslintrc.json +++ b/packages/@aws-cdk/cdk-explorer/.eslintrc.json @@ -17,7 +17,12 @@ "parserOptions": { "ecmaVersion": 2018, "sourceType": "module", - "projectService": true + "projectService": { + "allowDefaultProject": [ + "build-tools/bundle-frontend.ts" + ], + "defaultProject": "./tsconfig.json" + } }, "extends": [ "plugin:import/typescript", diff --git a/packages/@aws-cdk/cdk-explorer/.gitignore b/packages/@aws-cdk/cdk-explorer/.gitignore index 5f5292db9..08c0798a1 100644 --- a/packages/@aws-cdk/cdk-explorer/.gitignore +++ b/packages/@aws-cdk/cdk-explorer/.gitignore @@ -51,3 +51,5 @@ jspm_packages/ /dist/ !/.eslintrc.json !/.eslintrc.js +lib/web/static/ +lib/web/web-assets.generated.json diff --git a/packages/@aws-cdk/cdk-explorer/.npmignore b/packages/@aws-cdk/cdk-explorer/.npmignore index d9eda4d8c..dd6b08287 100644 --- a/packages/@aws-cdk/cdk-explorer/.npmignore +++ b/packages/@aws-cdk/cdk-explorer/.npmignore @@ -21,4 +21,6 @@ tsconfig.tsbuildinfo *.ts !*.d.ts build-tools +frontend +tsconfig.frontend.json /.gitattributes diff --git a/packages/@aws-cdk/cdk-explorer/.projen/deps.json b/packages/@aws-cdk/cdk-explorer/.projen/deps.json index c2152ce92..5285942b7 100644 --- a/packages/@aws-cdk/cdk-explorer/.projen/deps.json +++ b/packages/@aws-cdk/cdk-explorer/.projen/deps.json @@ -4,6 +4,16 @@ "name": "@cdklabs/eslint-plugin", "type": "build" }, + { + "name": "@cloudscape-design/components", + "version": "^3", + "type": "build" + }, + { + "name": "@cloudscape-design/global-styles", + "version": "^1", + "type": "build" + }, { "name": "@stylistic/eslint-plugin", "version": "^3", @@ -14,6 +24,11 @@ "version": "^2", "type": "build" }, + { + "name": "@types/express", + "version": "^4", + "type": "build" + }, { "name": "@types/jest", "type": "build" @@ -23,6 +38,26 @@ "version": "^20", "type": "build" }, + { + "name": "@types/prismjs", + "version": "^1", + "type": "build" + }, + { + "name": "@types/react-dom", + "version": "^18", + "type": "build" + }, + { + "name": "@types/react", + "version": "^18", + "type": "build" + }, + { + "name": "@types/supertest", + "version": "^6", + "type": "build" + }, { "name": "@typescript-eslint/eslint-plugin", "version": "^8", @@ -38,6 +73,10 @@ "version": "^10.0.0", "type": "build" }, + { + "name": "esbuild", + "type": "build" + }, { "name": "eslint-config-prettier", "type": "build" @@ -85,14 +124,38 @@ "version": "^2.8", "type": "build" }, + { + "name": "prismjs", + "version": "^1", + "type": "build" + }, { "name": "projen", "type": "build" }, + { + "name": "react-dom", + "version": "^18", + "type": "build" + }, + { + "name": "react", + "version": "^18", + "type": "build" + }, + { + "name": "supertest", + "version": "^6", + "type": "build" + }, { "name": "ts-jest", "type": "build" }, + { + "name": "tsx", + "type": "build" + }, { "name": "typescript", "version": "5.9", @@ -103,6 +166,11 @@ "version": "^3", "type": "build" }, + { + "name": "yaml", + "version": "^2", + "type": "build" + }, { "name": "@aws-cdk/cloud-assembly-api", "type": "runtime" diff --git a/packages/@aws-cdk/cdk-explorer/.projen/tasks.json b/packages/@aws-cdk/cdk-explorer/.projen/tasks.json index 2ca0bbe5e..0d121b2d1 100644 --- a/packages/@aws-cdk/cdk-explorer/.projen/tasks.json +++ b/packages/@aws-cdk/cdk-explorer/.projen/tasks.json @@ -43,7 +43,7 @@ }, "steps": [ { - "exec": "yarn dlx npm-check-updates@20 --upgrade --target=minor --cooldown=3 --peer --no-deprecated --dep=dev,peer,prod,optional --filter=@cdklabs/eslint-plugin,@types/jest,eslint-config-prettier,eslint-import-resolver-typescript,eslint-plugin-import,eslint-plugin-jest,eslint-plugin-jsdoc,eslint-plugin-prettier,jest,nx,projen,ts-jest" + "exec": "yarn dlx npm-check-updates@20 --upgrade --target=minor --cooldown=3 --peer --no-deprecated --dep=dev,peer,prod,optional --filter=@cdklabs/eslint-plugin,@types/jest,esbuild,eslint-config-prettier,eslint-import-resolver-typescript,eslint-plugin-import,eslint-plugin-jest,eslint-plugin-jsdoc,eslint-plugin-prettier,jest,nx,projen,ts-jest,tsx" } ] }, @@ -123,7 +123,12 @@ }, "post-compile": { "name": "post-compile", - "description": "Runs after successful compilation" + "description": "Runs after successful compilation", + "steps": [ + { + "exec": "tsx build-tools/bundle-frontend.ts" + } + ] }, "pre-compile": { "name": "pre-compile", diff --git a/packages/@aws-cdk/cdk-explorer/build-tools/bundle-frontend.ts b/packages/@aws-cdk/cdk-explorer/build-tools/bundle-frontend.ts new file mode 100644 index 000000000..7ff372d4a --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/build-tools/bundle-frontend.ts @@ -0,0 +1,59 @@ +/** + * Builds the web explorer SPA into lib/web/static (typechecked via + * tsconfig.frontend.json, since esbuild only transpiles), then writes the same + * assets to lib/web/web-assets.generated.json so they ride the require() graph + * into the published CLI bundle (express.static paths are not bundled). + */ +import { execFileSync } from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as esbuild from 'esbuild'; + +const packageRoot = path.resolve(__dirname, '..'); +const frontendDir = path.join(packageRoot, 'frontend'); +const outDir = path.join(packageRoot, 'lib', 'web', 'static'); +const embeddedAssetsFile = path.join(packageRoot, 'lib', 'web', 'web-assets.generated.json'); + +async function main(): Promise { + typecheck(); + + fs.mkdirSync(outDir, { recursive: true }); + + await esbuild.build({ + entryPoints: [path.join(frontendDir, 'index.tsx')], + bundle: true, + outfile: path.join(outDir, 'bundle.js'), + format: 'iife', + platform: 'browser', + target: 'es2020', + jsx: 'automatic', + loader: { '.css': 'css', '.svg': 'dataurl', '.png': 'dataurl' }, + sourcemap: true, + logLevel: 'info', + }); + + fs.copyFileSync(path.join(frontendDir, 'index.html'), path.join(outDir, 'index.html')); + writeEmbeddedAssets(); +} + +function writeEmbeddedAssets(): void { + const assets: Record = { + 'index.html': fs.readFileSync(path.join(frontendDir, 'index.html'), 'utf-8'), + 'bundle.js': fs.readFileSync(path.join(outDir, 'bundle.js'), 'utf-8'), + 'bundle.css': fs.readFileSync(path.join(outDir, 'bundle.css'), 'utf-8'), + }; + fs.writeFileSync(embeddedAssetsFile, JSON.stringify(assets)); +} + +function typecheck(): void { + execFileSync('tsc', ['--noEmit', '-p', path.join(packageRoot, 'tsconfig.frontend.json')], { + cwd: packageRoot, + stdio: 'inherit', + }); +} + +main().catch((err) => { + // eslint-disable-next-line no-console + console.error(err); + process.exit(1); +}); diff --git a/packages/@aws-cdk/cdk-explorer/frontend/App.tsx b/packages/@aws-cdk/cdk-explorer/frontend/App.tsx new file mode 100644 index 000000000..be9950f6e --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/frontend/App.tsx @@ -0,0 +1,757 @@ +import Box from '@cloudscape-design/components/box'; +import Alert from '@cloudscape-design/components/alert'; +import Button from '@cloudscape-design/components/button'; +import Container from '@cloudscape-design/components/container'; +import Header from '@cloudscape-design/components/header'; +import SpaceBetween from '@cloudscape-design/components/space-between'; +import Spinner from '@cloudscape-design/components/spinner'; +import * as React from 'react'; +import { buildSourceAnchorIndex, findConstructAtLine } from '../lib/web/source-nav'; +import { api, type TemplateResponse, type TreeResponse, type ViolationsResponse } from './api'; +import { CodeViewer, type Diagnostic } from './components/CodeViewer'; +import { ConstructTree } from './components/ConstructTree'; +import type { Language } from './syntax'; +import { TemplateViewer, type Format } from './components/TemplateViewer'; +import { ViolationsPanel } from './components/ViolationsPanel'; +import type { NavigateHandler } from './nav-types'; +export type { NavigateHandler } from './nav-types'; + +/** Navigation target describing what both panes should show. */ +export interface NavTarget { + readonly source?: { file: string; startLine: number; endLine: number }; + readonly template?: { file: string; logicalId: string }; + readonly color: string; + readonly navCounter: number; +} + +const NEUTRAL_COLOR = '#5f6b7a'; + +/** Web explorer shell. */ +export function App(): JSX.Element { + const [tree, setTree] = React.useState(); + const [violations, setViolations] = React.useState(); + const [error, setError] = React.useState(); + const [appDir, setAppDir] = React.useState(); + + // Source pane state. + const [sourceFile, setSourceFile] = React.useState(); + const [sourceContent, setSourceContent] = React.useState(''); + // True when the open source file was edited after the current assembly's synth + // started, so its squiggles/nav anchors may be stale. Set from /api/file. + const [sourceStale, setSourceStale] = React.useState(false); + + // Template pane state. + const [templateFile, setTemplateFile] = React.useState(); + const [templateData, setTemplateData] = React.useState(); + const [templateFormat, setTemplateFormat] = React.useState('yaml'); + + // Refs for current values in async callbacks (avoids stale closures). + const sourceFileRef = React.useRef(sourceFile); + sourceFileRef.current = sourceFile; + const templateFileRef = React.useRef(templateFile); + templateFileRef.current = templateFile; + const templateDataRef = React.useRef(templateData); + templateDataRef.current = templateData; + + // Navigation state shared across panes. + const [nav, setNav] = React.useState(); + const navCounterRef = React.useRef(0); + + // Violations filter: when set, only violations affecting this construct path are shown. + const [violationFilter, setViolationFilter] = React.useState(); + const [violationSearch, setViolationSearch] = React.useState(''); + + const reload = React.useCallback((): void => { + Promise.all([api.getTree(), api.getViolations(), api.getAppInfo()]) + .then(([t, v, info]) => { + setTree(t); + setViolations(v); + setAppDir(info.appDir); + setError(undefined); + }) + // Keep the last good render on a transient read (e.g. a mid-synth write); + // the next assembly-changed event re-fetches once the write has settled. + .catch((err) => setError(err instanceof Error ? err.message : String(err))); + }, []); + + // Re-read the currently open source file, refreshing both its content and its + // staleness flag. Used after a synth (assembly changed) to clear a banner, and + // on a source edit to raise one, without the user re-opening the file. + const refreshOpenSourceFile = React.useCallback(async (): Promise => { + const file = sourceFileRef.current; + if (!file) return; + try { + const res = await api.readFile(file); + setSourceContent(res.content); + setSourceStale(res.stale); + } catch { + // Keep the last good content; a later event re-tries. + } + }, []); + + React.useEffect(() => { + reload(); + return api.subscribe({ + onAssemblyChanged: () => { + reload(); + void refreshOpenSourceFile(); + }, + onSourceChanged: () => { + void refreshOpenSourceFile(); + }, + }); + }, [reload, refreshOpenSourceFile]); + + /** Navigate to a construct (from tree double-click or violation double-click). */ + const navigate: NavigateHandler = React.useCallback(async (opts) => { + const counter = ++navCounterRef.current; + const color = opts.color ?? NEUTRAL_COLOR; + + if (opts.constructPath) { + setViolationFilter(opts.constructPath); + } + + let sourceTarget: NavTarget['source']; + if (opts.sourceLocation) { + sourceTarget = { file: opts.sourceLocation.file, startLine: opts.sourceLocation.line, endLine: opts.sourceLocation.line }; + if (sourceFileRef.current !== opts.sourceLocation.file) { + try { + const res = await api.readFile(opts.sourceLocation.file); + setSourceFile(res.path); + setSourceContent(res.content); + setSourceStale(res.stale); + } catch { + setSourceFile(opts.sourceLocation.file); + setSourceContent(`// Could not load ${opts.sourceLocation.file}`); + setSourceStale(false); + } + } + } + + let templateTarget: NavTarget['template']; + if (opts.templateFile && opts.logicalId) { + if (templateFileRef.current !== opts.templateFile) { + try { + const data = await api.getTemplate(opts.templateFile); + setTemplateFile(opts.templateFile); + setTemplateData(data); + } catch { + setTemplateFile(opts.templateFile); + setTemplateData(undefined); + } + } + // Carry identity only; TemplateViewer resolves the highlight line from the + // rendered format (JSON or YAML), which is the sole coordinate authority. + templateTarget = { file: opts.templateFile, logicalId: opts.logicalId }; + } + + setNav({ source: sourceTarget, template: templateTarget, color, navCounter: counter }); + }, []); + + /** Jump from template pane to source (the "Open in source" button). */ + const jumpToSource = React.useCallback(async (logicalId: string) => { + const data = templateDataRef.current; + if (!data) return; + const resource = data.resources[logicalId]; + if (!resource?.source) return; + const counter = ++navCounterRef.current; + if (sourceFileRef.current !== resource.source.file) { + try { + const res = await api.readFile(resource.source.file); + setSourceFile(res.path); + setSourceContent(res.content); + setSourceStale(res.stale); + } catch { return; } + } + setNav({ + source: { file: resource.source.file, startLine: resource.source.line, endLine: resource.source.line }, + template: undefined, + color: NEUTRAL_COLOR, + navCounter: counter, + }); + }, []); + + // Per-file index of constructs navigable from source to template, built from + // the construct tree the app already loads. Rebuilt only when the tree changes. + const sourceAnchors = React.useMemo( + () => (tree?.status === 'ok' ? buildSourceAnchorIndex(tree.tree) : undefined), + [tree], + ); + + /** Double-click a line in the source pane to jump to its resource in the template. */ + const handleSourceDoubleClick = React.useCallback((line: number) => { + const file = sourceFileRef.current; + if (!file) return; + const node = findConstructAtLine(sourceAnchors?.get(file), line); + if (!node) return; + void navigate({ + sourceLocation: node.sourceLocation, + templateFile: node.templateFile, + logicalId: node.logicalId, + constructPath: node.path, + }); + }, [sourceAnchors, navigate]); + + // File picker state. + const [showFilePicker, setShowFilePicker] = React.useState(false); + const pickerRef = React.useRef(null); + + React.useEffect(() => { + if (!showFilePicker) return; + const handleClick = (e: MouseEvent) => { + const target = e.target as Node; + if (pickerRef.current && !pickerRef.current.contains(target)) { + if ((target as HTMLElement).closest?.('[aria-label="Open file"], [aria-label="Open template file"]')) return; + setShowFilePicker(false); + } + }; + document.addEventListener('mousedown', handleClick); + return () => document.removeEventListener('mousedown', handleClick); + }, [showFilePicker]); + + const knownSourceFiles = React.useMemo( + () => sourceAnchors ? [...sourceAnchors.keys()].sort() : [], + [sourceAnchors], + ); + + const knownTemplateFiles = React.useMemo(() => { + if (!tree || tree.status !== 'ok') return []; + const files = new Set(); + const walk = (nodes: readonly import('./api').WebConstructNode[]) => { + for (const n of nodes) { + if (n.templateFile) files.add(n.templateFile); + walk(n.children); + } + }; + walk(tree.tree); + return [...files].sort(); + }, [tree]); + + const pickFile = React.useCallback(async (filePath: string, pane: 'source' | 'template') => { + try { + if (pane === 'source') { + const res = await api.readFile(filePath); + setSourceFile(res.path); + setSourceContent(res.content); + setSourceStale(res.stale); + } else { + const data = await api.getTemplate(filePath); + setTemplateFile(filePath); + setTemplateData(data); + } + setShowFilePicker(false); + } catch { /* ignore */ } + }, []); + + // Vertical split: violations row's share of the page height (default 33%). + const vSplit = useSplit({ orientation: 'vertical', defaultFraction: 0.33, min: 0.15, max: 0.85 }); + // Horizontal split inside the top row: file panes' share of that row's width (default 75%; tree gets the remaining 25%). + const hSplit = useSplit({ orientation: 'horizontal', defaultFraction: 0.75, min: 0.4, max: 0.85 }); + + return ( +
+
+
CDK Web Explorer
+
+ {error && {error}} +
+
+ {!hSplit.collapsed && ( +
+ Construct Tree}> + + +
+ )} +
+ +
+
+
+ + + + {sourceFile ?? 'Source'} + + + {showFilePicker === 'source' && ( +
+ void pickFile(p, 'source')} /> +
+ )} +
+ + }> +
+ {sourceContent ? ( +
+ {sourceStale && ( + + This file has been modified since the last synth, so its violations and diagnostics may be stale. Re-run cdk synth to refresh. + + )} +
+ +
+
+ ) : ( + Double-click a construct to view its source. + )} +
+
+
+
+ }> + + + {templateFile ?? 'Template'} + + + {showFilePicker === 'template' && ( +
+ void pickFile(p, 'template')} /> +
+ )} +
+ + }> +
+ {templateData ? ( + + ) : templateFile ? ( + Could not load {templateFile} + ) : ( + Double-click a construct to view its template. + )} +
+
+
+
+
+
+
+ + {!vSplit.collapsed && ( +
+ }> + setViolationFilter(undefined)} /> + + }> + setViolationFilter(undefined)} search={violationSearch} /> + +
+ )} +
+
+ ); +} + + +function KnownFileList({ files, onPick }: { + readonly files: readonly string[]; + readonly onPick: (path: string) => void; +}): JSX.Element { + if (files.length === 0) return No files found.; + return ( + + {files.map((file) => ( + + ))} + + ); +} + +function ConstructTreeContent({ tree, onNavigate }: { readonly tree?: TreeResponse; readonly onNavigate: NavigateHandler }): JSX.Element { + if (!tree) return ; + if (tree.status === 'not-synthesized') { + return No cloud assembly found. Run cdk synth first.; + } + return ; +} + +function ViolationsContent({ violations, onNavigate, filter, onClearFilter, search }: { + readonly violations?: ViolationsResponse; + readonly onNavigate: NavigateHandler; + readonly filter?: string; + readonly onClearFilter: () => void; + readonly search: string; +}): JSX.Element { + if (!violations) return ; + if (violations.status === 'not-synthesized') { + return No cloud assembly found.; + } + return ; +} + +function ViolationsTitle({ filter, onClearFilter }: { readonly filter?: string; readonly onClearFilter: () => void }): JSX.Element { + if (!filter) return <>Violations; + const name = filter.split('/').pop() || filter; + return ( + + Violations for + + {name} + + + + ); +} + +function ViolationsActions({ search, onSearchChange }: { + readonly search: string; + readonly onSearchChange: (value: string) => void; +}): JSX.Element { + return ( + onSearchChange(e.target.value)} + style={VIOLATIONS_SEARCH_STYLE} + /> + ); +} + +interface SplitOptions { + readonly orientation: 'horizontal' | 'vertical'; + readonly defaultFraction: number; + readonly min: number; + readonly max: number; +} + +interface Split extends SplitOptions { + readonly fraction: number; + readonly collapsed: boolean; + readonly toggleCollapsed: () => void; + readonly startDrag: (e: React.MouseEvent) => void; + readonly containerRef: React.RefObject; +} + +function useSplit(opts: SplitOptions): Split { + const containerRef = React.useRef(null); + const [fraction, setFraction] = React.useState(opts.defaultFraction); + const [collapsed, setCollapsed] = React.useState(false); + + const startDrag = React.useCallback((e: React.MouseEvent) => { + if (collapsed) return; + e.preventDefault(); + const container = containerRef.current; + if (!container) return; + document.body.style.userSelect = 'none'; + document.body.style.cursor = opts.orientation === 'vertical' ? 'row-resize' : 'col-resize'; + const onMove = (ev: MouseEvent) => { + const rect = container.getBoundingClientRect(); + const frac = opts.orientation === 'vertical' + ? (rect.bottom - ev.clientY) / rect.height + : (rect.right - ev.clientX) / rect.width; + setFraction(Math.min(opts.max, Math.max(opts.min, frac))); + }; + const onUp = () => { + document.body.style.userSelect = ''; + document.body.style.cursor = ''; + document.removeEventListener('mousemove', onMove); + document.removeEventListener('mouseup', onUp); + }; + document.addEventListener('mousemove', onMove); + document.addEventListener('mouseup', onUp); + }, [collapsed, opts.orientation, opts.min, opts.max]); + + const toggleCollapsed = React.useCallback(() => setCollapsed((c) => !c), []); + + return { ...opts, fraction, collapsed, toggleCollapsed, startDrag, containerRef }; +} + +function Resizer({ split }: { readonly split: Split }): JSX.Element { + const isVertical = split.orientation === 'vertical'; + const arrow = isVertical + ? (split.collapsed ? '▲' : '▼') + : (split.collapsed ? '▶' : '◀'); + const verb = isVertical + ? (split.collapsed ? 'Expand violations' : 'Collapse violations') + : (split.collapsed ? 'Expand construct tree' : 'Collapse construct tree'); + return ( +
+ +
+ ); +} + +function topRowStyle(vSplit: Split): React.CSSProperties { + return { + display: 'flex', + minHeight: 0, + alignItems: 'stretch', + flexDirection: 'row', + flex: vSplit.collapsed ? '1 1 auto' : `${1 - vSplit.fraction} 1 0`, + }; +} + +function bottomRowStyle(vSplit: Split): React.CSSProperties { + return { + display: 'flex', + flexDirection: 'column', + minHeight: 0, + flex: vSplit.collapsed ? '0 0 auto' : `${vSplit.fraction} 1 0`, + }; +} + +function treePaneStyle(hSplit: Split): React.CSSProperties { + return { + flex: hSplit.collapsed ? '0 0 0' : `0 0 ${(1 - hSplit.fraction) * 100}%`, + minWidth: 0, + minHeight: 0, + display: 'flex', + }; +} + +function filesPaneStyle(hSplit: Split): React.CSSProperties { + return { + flex: hSplit.collapsed ? '1 1 auto' : `0 0 ${hSplit.fraction * 100}%`, + minWidth: 0, + minHeight: 0, + }; +} + +function resizerStyle(split: Split): React.CSSProperties { + if (split.orientation === 'vertical') { + return { + flexShrink: 0, + height: split.collapsed ? '20px' : '10px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + cursor: split.collapsed ? 'pointer' : 'row-resize', + position: 'relative', + marginBottom: split.collapsed ? 0 : '-4px', + }; + } + return { + flexShrink: 0, + width: split.collapsed ? '20px' : '10px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + cursor: split.collapsed ? 'pointer' : 'col-resize', + position: 'relative', + marginLeft: split.collapsed ? 0 : '-4px', + }; +} + +const PAGE_STYLE: React.CSSProperties = { + height: '100vh', + display: 'flex', + flexDirection: 'column', + padding: '16px', + boxSizing: 'border-box', + overflow: 'hidden', +}; +const TITLE_BLOCK_STYLE: React.CSSProperties = { flexShrink: 0, marginBottom: '12px' }; +const GROW_STYLE: React.CSSProperties = { flex: '1 1 0', minWidth: 0, minHeight: 0, display: 'flex', flexDirection: 'column', width: '100%' }; + +const HEADER_WITH_ACTION_STYLE: React.CSSProperties = { display: 'inline-flex', alignItems: 'center', gap: '6px' }; +const FOLDER_BUTTON_STYLE: React.CSSProperties = { + border: 'none', + background: 'none', + cursor: 'pointer', + padding: '2px', + color: '#5f6b7a', + display: 'inline-flex', + alignItems: 'center', +}; +const CODE_PANE_INNER_STYLE: React.CSSProperties = { height: '100%' }; +// Source pane stacks an optional staleness banner above the scrolling viewer. +const SOURCE_PANE_COLUMN_STYLE: React.CSSProperties = { + height: '100%', + display: 'flex', + flexDirection: 'column', + minHeight: 0, + gap: '8px', +}; +const PICKER_ANCHOR_STYLE: React.CSSProperties = { position: 'relative', display: 'inline-flex', alignItems: 'center' }; +const PICKER_DROPDOWN_STYLE: React.CSSProperties = { + position: 'absolute', + top: '100%', + left: 0, + marginTop: '4px', + maxHeight: '50vh', + minWidth: '320px', + overflowY: 'auto', + background: '#ffffff', + border: '1px solid #d1d5db', + borderRadius: '4px', + boxShadow: '0 4px 12px rgba(0,0,0,0.1)', + padding: '8px', + zIndex: 100, +}; + +const CODE_PANES_STYLE: React.CSSProperties = { + display: 'flex', + gap: '8px', + height: '100%', +}; + +const CODE_PANE_STYLE: React.CSSProperties = { + flex: '1 1 50%', + minWidth: 0, + minHeight: 0, + display: 'flex', + flexDirection: 'column', +}; + +const RESIZER_BUTTON_BASE: React.CSSProperties = { + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + border: '1px solid #d1d5db', + background: '#ffffff', + color: '#5f6b7a', + fontSize: '10px', + lineHeight: 1, + cursor: 'pointer', + zIndex: 1, + padding: 0, +}; +const RESIZER_BUTTON_HORIZONTAL: React.CSSProperties = { + ...RESIZER_BUTTON_BASE, + width: '40px', + height: '14px', + borderRadius: '7px', +}; +const RESIZER_BUTTON_VERTICAL: React.CSSProperties = { + ...RESIZER_BUTTON_BASE, + width: '14px', + height: '40px', + borderRadius: '7px', +}; + +function FormatToggle({ format, onChange }: { readonly format: Format; readonly onChange: (f: Format) => void }): JSX.Element { + return ( + + + + + ); +} + +function FolderIcon(): JSX.Element { + return ( + + + + ); +} + +const FORMAT_TOGGLE_GROUP_STYLE: React.CSSProperties = { display: 'inline-flex', gap: '2px' }; +const FORMAT_TOGGLE_STYLE: React.CSSProperties = { + border: '1px solid #d1d5db', borderRadius: '4px', background: '#fafafa', + cursor: 'pointer', fontSize: '11px', padding: '2px 8px', color: '#5f6b7a', lineHeight: '16px', +}; +const FORMAT_TOGGLE_ACTIVE_STYLE: React.CSSProperties = { + ...FORMAT_TOGGLE_STYLE, background: '#0972d3', color: '#ffffff', borderColor: '#0972d3', +}; +const VIOLATIONS_TITLE_STYLE: React.CSSProperties = { display: 'inline-flex', alignItems: 'center', gap: '6px' }; +const FILTER_PILL_STYLE: React.CSSProperties = { + display: 'inline-flex', alignItems: 'center', gap: '4px', + background: '#f2f8fd', border: '1px solid #89bdee', borderRadius: '12px', + padding: '1px 8px 1px 10px', fontSize: '14px', color: '#0972d3', fontWeight: 600, +}; +const FILTER_CLEAR_STYLE: React.CSSProperties = { + background: 'none', border: 'none', cursor: 'pointer', + fontSize: '14px', lineHeight: 1, color: '#0972d3', padding: '0 2px', +}; +const VIOLATIONS_SEARCH_STYLE: React.CSSProperties = { + width: '160px', padding: '3px 8px', border: '1px solid #d1d5db', + borderRadius: '4px', fontSize: '13px', outline: 'none', +}; + +function detectLanguage(file: string | undefined): Language { + if (!file) return 'typescript'; + if (file.endsWith('.json')) return 'json'; + if (file.endsWith('.yaml') || file.endsWith('.yml')) return 'yaml'; + if (file.endsWith('.py')) return 'python'; + if (file.endsWith('.java')) return 'java'; + if (file.endsWith('.cs')) return 'csharp'; + if (file.endsWith('.go')) return 'go'; + if (file.endsWith('.js') || file.endsWith('.jsx') || file.endsWith('.mjs')) return 'javascript'; + return 'typescript'; +} + +function buildDiagnostics( + sourceFile: string | undefined, + violations: ViolationsResponse | undefined, +): Diagnostic[] | undefined { + if (!sourceFile || !violations || violations.status !== 'ok' || violations.violations.length === 0) { + return undefined; + } + const diagnostics: Diagnostic[] = []; + for (const violation of violations.violations) { + const severity = violationSeverityToDiagnostic(violation.severity); + for (const occ of violation.occurrences) { + if (occ.sourceLocation?.file === sourceFile && occ.sourceLocation.line >= 1) { + diagnostics.push({ + startLine: occ.sourceLocation.line, + startCol: occ.sourceLocation.column >= 1 ? occ.sourceLocation.column : 1, + severity, + message: violation.description, + }); + } + } + } + return diagnostics.length > 0 ? diagnostics : undefined; +} + +function violationSeverityToDiagnostic(severity: string | undefined): 'error' | 'warning' | 'info' { + switch (severity) { + case 'fatal': + case 'error': + return 'error'; + case 'warning': + return 'warning'; + default: + return 'info'; + } +} diff --git a/packages/@aws-cdk/cdk-explorer/frontend/api.ts b/packages/@aws-cdk/cdk-explorer/frontend/api.ts new file mode 100644 index 000000000..77d72fcd7 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/frontend/api.ts @@ -0,0 +1,64 @@ +import { + ASSEMBLY_CHANGED, + SOURCE_CHANGED, + type FileResponse, + type LineRange, + type TemplateResource, + type TemplateResponse, + type TreeResponse, + type ViolationsResponse, + type WebConstructNode, + type WebSourceLocation, + type WebViolation, + type WebViolationOccurrence, +} from '../lib/web/protocol'; + +export type { + FileResponse, + LineRange, + TemplateResource, + TemplateResponse, + TreeResponse, + ViolationsResponse, + WebConstructNode, + WebSourceLocation, + WebViolation, + WebViolationOccurrence, +}; + +async function getJson(url: string): Promise { + const res = await fetch(url); + if (!res.ok) { + const body = await res.json().catch(() => ({})); + throw new Error((body as { error?: string }).error ?? `request failed: ${res.status}`); + } + return res.json() as Promise; +} + +export interface AppInfoResponse { + readonly appDir: string; +} + +export const api = { + readFile: (filePath: string): Promise => getJson(`/api/file?path=${encodeURIComponent(filePath)}`), + getTree: (): Promise => getJson('/api/tree'), + getViolations: (): Promise => getJson('/api/policy-validation'), + getAppInfo: (): Promise => getJson('/api/info'), + /** + * Subscribe to the server's live-refresh stream. `onAssemblyChanged` fires + * when the cloud assembly is rewritten (re-fetch tree/violations); + * `onSourceChanged` fires when a source file is edited (re-check the open + * file's staleness). Returns an unsubscribe that closes the EventSource. + */ + subscribe: (handlers: { onAssemblyChanged?: () => void; onSourceChanged?: () => void }): (() => void) => { + const source = new EventSource('/api/events'); + if (handlers.onAssemblyChanged) { + source.addEventListener(ASSEMBLY_CHANGED, () => handlers.onAssemblyChanged!()); + } + if (handlers.onSourceChanged) { + source.addEventListener(SOURCE_CHANGED, () => handlers.onSourceChanged!()); + } + return () => source.close(); + }, + getTemplate: (file: string): Promise => getJson(`/api/template?file=${encodeURIComponent(file)}`), +}; diff --git a/packages/@aws-cdk/cdk-explorer/frontend/components/CodeViewer.tsx b/packages/@aws-cdk/cdk-explorer/frontend/components/CodeViewer.tsx new file mode 100644 index 000000000..d5f2dd748 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/frontend/components/CodeViewer.tsx @@ -0,0 +1,241 @@ +import * as React from 'react'; +import { tokenizeLines, type Language, type Token } from '../syntax'; + +export interface Diagnostic { + readonly startLine: number; + readonly startCol: number; + readonly endCol?: number; + readonly severity: 'error' | 'warning' | 'info'; + readonly message?: string; +} + +export interface CodeViewerProps { + readonly content: string; + readonly language: Language; + readonly highlightStart?: number; + readonly highlightEnd?: number; + readonly highlightColor?: string; + readonly navCounter?: number; + readonly scrollToLine?: number; + readonly onLineDoubleClick?: (line: number) => void; + readonly diagnostics?: readonly Diagnostic[]; +} + +const SQUIGGLE_COLORS: Record = { + error: '#d91515', + warning: '#ff9900', + info: '#0972d3', +}; + +const TOKEN_COLORS: Record = { + keyword: '#0000ff', + string: '#a31515', + number: '#098658', + boolean: '#0000ff', + 'attr-name': '#0451a5', + property: '#0451a5', + operator: '#393a34', + punctuation: '#393a34', + comment: '#008000', + 'class-name': '#267f99', + builtin: '#267f99', + function: '#795e26', + tag: '#0451a5', + selector: '#0451a5', + key: '#0451a5', + 'atrule': '#0000ff', + // YAML-specific + important: '#098658', + // null/undefined + 'null': '#0000ff', +}; + +export function CodeViewer({ + content, + language, + highlightStart, + highlightEnd, + highlightColor, + navCounter, + scrollToLine, + onLineDoubleClick, + diagnostics, +}: CodeViewerProps): JSX.Element { + const scrollTargetRef = React.useRef(null); + + const tokenizedLines = React.useMemo( + () => tokenizeLines(content, language), + [content, language], + ); + + const gutterWidth = React.useMemo(() => { + const digits = String(tokenizedLines.length).length; + return `${digits * 8 + 8}px`; + }, [tokenizedLines.length]); + + const diagnosticsByLine = React.useMemo(() => { + if (!diagnostics?.length) return undefined; + const map = new Map(); + for (const d of diagnostics) { + const arr = map.get(d.startLine) ?? []; + arr.push(d); + map.set(d.startLine, arr); + } + return map; + }, [diagnostics]); + + const lastNavRef = React.useRef(); + const animateNav = navCounter !== lastNavRef.current; + React.useEffect(() => { lastNavRef.current = navCounter; }, [navCounter]); + + React.useEffect(() => { + if (scrollToLine && scrollTargetRef.current) { + scrollTargetRef.current.scrollIntoView({ block: 'start', behavior: animateNav ? 'smooth' : 'instant' }); + } + }, [scrollToLine, navCounter]); + + return ( +
+ {tokenizedLines.map((lineTokens, i) => { + const lineNum = i + 1; + const isHighlighted = highlightStart !== undefined + && highlightEnd !== undefined + && lineNum >= highlightStart + && lineNum <= highlightEnd; + const isScrollTarget = lineNum === scrollToLine; + const lineDiagnostics = diagnosticsByLine?.get(lineNum); + + return ( +
onLineDoubleClick(lineNum) : undefined} + > + {lineNum} + + {lineDiagnostics + ? renderWithDiagnostics(lineTokens, lineDiagnostics) + : renderTokens(lineTokens) + } + +
+ ); + })} +
+ ); +} + +function renderTokens(tokens: Token[]): React.ReactNode { + return tokens.map((token, j) => { + const color = token.type ? TOKEN_COLORS[token.type] : undefined; + return color + ? {token.content} + : {token.content}; + }); +} + +function renderWithDiagnostics(tokens: Token[], diagnostics: Diagnostic[]): React.ReactNode { + let col = 1; + const elements: React.ReactNode[] = []; + let key = 0; + + for (const token of tokens) { + const tokenStart = col; + const tokenEnd = col + token.content.length; + const color = token.type ? TOKEN_COLORS[token.type] : undefined; + + let hasOverlap = false; + for (const d of diagnostics) { + const dEnd = d.endCol ?? 999; + if (d.startCol < tokenEnd && dEnd > tokenStart) { + hasOverlap = true; + break; + } + } + + if (!hasOverlap) { + elements.push( + color + ? {token.content} + : {token.content}, + ); + } else { + const chars = token.content; + let segStart = 0; + let currentSeverity = ''; + + for (let c = 0; c <= chars.length; c++) { + const charCol = tokenStart + c; + let severity = ''; + for (const d of diagnostics) { + const dEnd = d.endCol ?? 999; + if (charCol >= d.startCol && charCol < dEnd) { + severity = d.severity; + break; + } + } + + if (c === chars.length || severity !== currentSeverity) { + if (segStart < c) { + const text = chars.slice(segStart, c); + if (currentSeverity) { + elements.push( + + {text} + , + ); + } else { + elements.push( + color + ? {text} + : {text}, + ); + } + } + segStart = c; + currentSeverity = severity; + } + } + } + + col = tokenEnd; + } + + return elements; +} + +const CONTAINER_STYLE: React.CSSProperties = { + margin: 0, + maxHeight: '100%', + overflowX: 'auto', + overflowY: 'auto', + fontFamily: 'Monaco, Menlo, "Courier New", monospace', + fontSize: '12px', + lineHeight: '18px', + whiteSpace: 'pre', +}; + +const LINE_STYLE: React.CSSProperties = { + display: 'flex', + alignItems: 'center', + minHeight: '18px', + position: 'relative', +}; + +const LINE_NUM_STYLE: React.CSSProperties = { + flexShrink: 0, + textAlign: 'right', + paddingRight: '8px', + color: '#9ba7b6', + userSelect: 'none', +}; + +const LINE_CONTENT_STYLE: React.CSSProperties = { + flex: '1 1 auto', + minWidth: 0, +}; diff --git a/packages/@aws-cdk/cdk-explorer/frontend/components/ConstructTree.tsx b/packages/@aws-cdk/cdk-explorer/frontend/components/ConstructTree.tsx new file mode 100644 index 000000000..762cec8b2 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/frontend/components/ConstructTree.tsx @@ -0,0 +1,156 @@ +import Box from '@cloudscape-design/components/box'; +import Icon from '@cloudscape-design/components/icon'; +import * as React from 'react'; +import { severityHexColor } from '../../lib/web/severity'; +import type { WebConstructNode } from '../api'; +import type { NavigateHandler } from '../nav-types'; + +interface ConstructTreeProps { + readonly nodes: readonly WebConstructNode[]; + readonly depth?: number; + readonly onNavigate: NavigateHandler; +} + +/** Auto-expand the tree through this depth on load (0 = root), so stacks and their top-level constructs are visible without manual clicks. */ +const AUTO_EXPAND_DEPTH = 2; + +export function ConstructTree({ nodes, depth = 0, onNavigate }: ConstructTreeProps): JSX.Element { + if (nodes.length === 0) { + return No constructs.; + } + const list = ( +
    + {nodes.map((node) => ( + + ))} +
+ ); + // Root render: wrap once in the clipping viewport; nested calls return the bare list. + return depth === 0 ?
{list}
: list; +} + +function TreeNode({ node, depth, onNavigate }: { readonly node: WebConstructNode; readonly depth: number; readonly onNavigate: NavigateHandler }): JSX.Element { + const hasChildren = node.children.length > 0; + const [expanded, setExpanded] = React.useState(depth < AUTO_EXPAND_DEPTH); + + const label = friendlyName(node); + const severity = node.highestSeverity; + const severityColor = severity ? severityHexColor(severity) : undefined; + const inherited = !severity ? node.inheritedSeverity : undefined; + const inheritedColor = inherited ? severityHexColor(inherited) : undefined; + + const clickTimer = React.useRef | null>(null); + + const handleClick = React.useCallback(() => { + if (!hasChildren) return; + if (clickTimer.current) return; + clickTimer.current = setTimeout(() => { + clickTimer.current = null; + setExpanded((e) => !e); + }, 200); + }, [hasChildren]); + + const handleDoubleClick = React.useCallback((e: React.MouseEvent) => { + e.stopPropagation(); + if (clickTimer.current) { + clearTimeout(clickTimer.current); + clickTimer.current = null; + } + if (!node.sourceLocation && !node.templateFile) return; + const color = severityColor ?? '#5f6b7a'; + onNavigate({ + sourceLocation: node.sourceLocation, + templateFile: node.templateFile, + logicalId: node.logicalId, + constructPath: node.path, + color, + }); + }, [node, severityColor, onNavigate]); + + return ( +
  • +
    + {hasChildren ? {expanded ? '▾' : '▸'} : } + + {severityColor && ( + + )} + + {label} + + {node.type && ( + + {friendlyType(node.type)} + + )} +
    + {hasChildren && expanded && } +
  • + ); +} + +/** Friendlier default label: a generic CDK id ("Resource"/"Default") shows its resource type instead. */ +function friendlyName(node: WebConstructNode): string { + if ((node.id === 'Resource' || node.id === 'Default') && node.type) { + return friendlyType(node.type); + } + return node.id; +} + +/** "AWS::DynamoDB::Table" -> "DynamoDB Table". */ +function friendlyType(type: string): string { + return type.replace(/^AWS::/, '').split('::').join(' '); +} + +function labelStyle(severityColor: string | undefined, inheritedColor: string | undefined): React.CSSProperties { + if (severityColor) return { ...LABEL_STYLE, color: severityColor }; + if (inheritedColor) return { ...LABEL_STYLE, color: inheritedColor }; + return LABEL_STYLE; +} + +const TREE_VIEWPORT_STYLE: React.CSSProperties = { overflowX: 'hidden', overflowY: 'auto', height: '100%' }; +const SEVERITY_DOT_STYLE: React.CSSProperties = { + flexShrink: 0, + width: '8px', + height: '8px', + borderRadius: '50%', +}; +const LIST_STYLE: React.CSSProperties = { listStyle: 'none', margin: 0, paddingLeft: '12px', borderLeft: '1px solid #e9ebed' }; +const ITEM_STYLE: React.CSSProperties = { padding: '2px 0' }; +const ROW_STYLE: React.CSSProperties = { display: 'flex', alignItems: 'center', gap: '4px', minWidth: 0, overflow: 'hidden', cursor: 'default' }; +const ROW_CLICKABLE_STYLE: React.CSSProperties = { ...ROW_STYLE, cursor: 'pointer' }; +const LABEL_STYLE: React.CSSProperties = { + flex: '1 1 auto', + minWidth: 0, + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + fontWeight: 500, +}; +const TYPE_STYLE: React.CSSProperties = { + flex: '0 1 auto', + minWidth: 0, + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + paddingLeft: '8px', + color: '#5f6b7a', + fontSize: '12px', +}; +const CARET_STYLE: React.CSSProperties = { + width: '12px', + flexShrink: 0, + fontSize: '10px', + lineHeight: 1, +}; +const CARET_SPACER: React.CSSProperties = { display: 'inline-block', width: '12px', flexShrink: 0 }; diff --git a/packages/@aws-cdk/cdk-explorer/frontend/components/TemplateViewer.tsx b/packages/@aws-cdk/cdk-explorer/frontend/components/TemplateViewer.tsx new file mode 100644 index 000000000..5c85b53f2 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/frontend/components/TemplateViewer.tsx @@ -0,0 +1,161 @@ +import { isNode, isScalar, isMap, LineCounter, parseDocument, stringify } from 'yaml'; +import * as React from 'react'; +import type { TemplateResource, WebViolation } from '../api'; +import { CodeViewer, type Diagnostic } from './CodeViewer'; + +export interface TemplateViewerProps { + readonly jsonContent: string; + readonly resources: Record; + readonly highlightLogicalId?: string; + readonly highlightColor?: string; + readonly navCounter?: number; + readonly onResourceDoubleClick?: (logicalId: string) => void; + readonly templateFile?: string; + readonly violations?: readonly WebViolation[]; + readonly format: Format; +} + +export type Format = 'yaml' | 'json'; + +interface ResourceSection { + readonly logicalId: string; + readonly startLine: number; + readonly endLine: number; +} + +export function TemplateViewer({ + jsonContent, + resources, + highlightLogicalId, + highlightColor, + navCounter, + onResourceDoubleClick, + templateFile, + violations, + format, +}: TemplateViewerProps): JSX.Element { + + const { displayContent, displayResources, sections } = React.useMemo(() => { + if (format === 'json') { + return { + displayContent: jsonContent, + displayResources: resources, + sections: buildSections(resources), + }; + } + return jsonToYaml(jsonContent); + }, [jsonContent, resources, format]); + + // Resolve the highlight from the rendered format's own ranges (JSON block in + // JSON view, YAML block in YAML view), so it is always in the coordinate + // system on screen. Derived, so toggling format re-resolves. + const highlight = React.useMemo(() => { + const block = highlightLogicalId ? displayResources[highlightLogicalId]?.block : undefined; + return block ? { start: block.startLine, end: block.endLine } : undefined; + }, [highlightLogicalId, displayResources]); + + const diagnostics = React.useMemo(() => { + if (!templateFile || !violations?.length) return undefined; + const lines = displayContent.split('\n'); + const diags: Diagnostic[] = []; + for (const violation of violations) { + const severity = violationSeverity(violation.severity); + for (const occ of violation.occurrences) { + if (occ.templateFile === templateFile && occ.logicalId) { + const resource = displayResources[occ.logicalId]; + if (resource) { + const line = lines[resource.block.startLine - 1]; + const startCol = line ? line.search(/\S/) + 1 : 1; + diags.push({ startLine: resource.block.startLine, startCol: Math.max(1, startCol), severity, message: violation.description }); + } + } + } + } + return diags.length > 0 ? diags : undefined; + }, [templateFile, violations, displayResources, displayContent]); + + const handleDoubleClick = React.useCallback((line: number) => { + if (!onResourceDoubleClick) return; + // Map the clicked line back to the resource that owns it, so the reverse + // jump is identity-based like the forward one. + const section = sections.find((s) => line >= s.startLine && line <= s.endLine); + if (section) { + onResourceDoubleClick(section.logicalId); + } + }, [sections, onResourceDoubleClick]); + + return ( + + ); +} + +function buildSections(resources: Record): ResourceSection[] { + return Object.entries(resources) + .map(([logicalId, r]) => ({ logicalId, startLine: r.block.startLine, endLine: r.block.endLine })) + .sort((a, b) => a.startLine - b.startLine); +} + +interface YamlResult { + displayContent: string; + displayResources: Record; + sections: ResourceSection[]; +} + +function jsonToYaml(jsonContent: string): YamlResult { + let parsed: unknown; + try { + parsed = JSON.parse(jsonContent); + } catch { + return { displayContent: jsonContent, displayResources: {}, sections: [] }; + } + + // Render and measure with the same parser so the text and the ranges always + // agree. lineWidth:0 disables wrapping (keeps one CFN value per line, like the + // old serializer); aliasDuplicateObjects:false keeps CloudFormation's repeated + // objects inline instead of emitting &anchor/*alias. + const displayContent = stringify(parsed, { indent: 2, lineWidth: 0, aliasDuplicateObjects: false }); + const lineCounter = new LineCounter(); + const doc = parseDocument(displayContent, { lineCounter }); + + const displayResources: Record = {}; + const resources = doc.get('Resources'); + if (isMap(resources)) { + for (const pair of resources.items) { + const key = pair.key; + const value = pair.value; + if (!isScalar(key) || !isNode(value) || key.range == null || value.range == null) { + continue; + } + // range is [valueStart, valueEnd, nodeEnd]; a resource block spans from its + // logical-id key line through the end of its value. + const startLine = lineCounter.linePos(key.range[0]).line; + const endLine = lineCounter.linePos(value.range[1]).line; + displayResources[String(key.value)] = { block: { startLine, endLine } }; + } + } + + return { displayContent, displayResources, sections: buildSections(displayResources) }; +} + + +function violationSeverity(severity: string | undefined): 'error' | 'warning' | 'info' { + switch (severity) { + case 'fatal': + case 'error': + return 'error'; + case 'warning': + return 'warning'; + default: + return 'info'; + } +} diff --git a/packages/@aws-cdk/cdk-explorer/frontend/components/ViolationsPanel.tsx b/packages/@aws-cdk/cdk-explorer/frontend/components/ViolationsPanel.tsx new file mode 100644 index 000000000..e29faef2b --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/frontend/components/ViolationsPanel.tsx @@ -0,0 +1,143 @@ +import Box from '@cloudscape-design/components/box'; +import ExpandableSection from '@cloudscape-design/components/expandable-section'; +import SpaceBetween from '@cloudscape-design/components/space-between'; +import StatusIndicator from '@cloudscape-design/components/status-indicator'; +import * as React from 'react'; +import { displaySeverity, severityHexColor, severityRank } from '../../lib/web/severity'; +import type { WebViolation, WebViolationOccurrence } from '../api'; +import type { NavigateHandler } from '../nav-types'; + +interface ViolationsPanelProps { + readonly violations: readonly WebViolation[]; + readonly onNavigate: NavigateHandler; + readonly filter?: string; + readonly onClearFilter: () => void; + readonly search: string; +} + +export function ViolationsPanel({ violations, onNavigate, filter, search }: ViolationsPanelProps): JSX.Element { + if (violations.length === 0) { + return No policy violations.; + } + + const filtered = filterViolations(violations, filter, search); + const sorted = [...filtered].sort((a, b) => severityRank(displaySeverity(a)) - severityRank(displaySeverity(b))); + + if (sorted.length === 0) { + return {filter ? 'No violations for this resource.' : 'No matching violations.'}; + } + + return ( +
    + + {sorted.map((violation, i) => ( + + ))} + +
    + ); +} + +function filterViolations(violations: readonly WebViolation[], filter: string | undefined, search: string): readonly WebViolation[] { + let result = violations; + + if (filter) { + const filtered: WebViolation[] = []; + for (const v of result) { + const matchingOccs = v.occurrences.filter( + (occ) => occ.constructPath === filter || occ.constructPath.startsWith(filter + '/'), + ); + if (matchingOccs.length > 0) { + filtered.push({ ...v, occurrences: matchingOccs }); + } + } + result = filtered; + } + + if (search.trim()) { + const q = search.trim().toLowerCase(); + result = result.filter((v) => + v.ruleName.toLowerCase().includes(q) || + (v.description ?? '').toLowerCase().includes(q) || + (v.suggestedFix ?? '').toLowerCase().includes(q) || + v.occurrences.some((occ) => occ.constructPath.toLowerCase().includes(q)), + ); + } + + return result; +} + + +function ViolationItem({ violation, onNavigate }: { readonly violation: WebViolation; readonly onNavigate: NavigateHandler }): JSX.Element { + const severity = displaySeverity(violation); + const count = violation.occurrences.length; + const title = violation.description?.trim() || violation.ruleName; + const showRuleName = title !== violation.ruleName; + return ( + +
    + + [{severity.toUpperCase()}] + {title} + + {showRuleName && {violation.ruleName}} +
    +
    + {count} {count === 1 ? 'construct' : 'constructs'} {'·'} {violation.source} +
    + + } + > +
    + {violation.suggestedFix && Suggested fix: {violation.suggestedFix}} + {violation.occurrences.map((occ, i) => ( + + ))} +
    +
    + ); +} + +function OccurrenceRow({ occurrence, severity, onNavigate }: { + readonly occurrence: WebViolationOccurrence; + readonly severity: string; + readonly onNavigate: NavigateHandler; +}): JSX.Element { + const handleClick = React.useCallback(() => { + if (!occurrence.sourceLocation && !occurrence.templateFile) return; + onNavigate({ + sourceLocation: occurrence.sourceLocation, + templateFile: occurrence.templateFile, + logicalId: occurrence.logicalId, + propertyPaths: occurrence.propertyPaths, + color: severityHexColor(severity), + }); + }, [occurrence, severity, onNavigate]); + + return ( + + + {occurrence.constructPath} + {occurrence.logicalId ? ` → ${occurrence.logicalId}` : ''} + {occurrence.templateFile ? ` (${occurrence.templateFile})` : ''} + + + ); +} + +function severityStyle(severity: string): React.CSSProperties { + return { color: severityHexColor(severity), fontWeight: 700, whiteSpace: 'nowrap', flexShrink: 0 }; +} + +const SCROLL_STYLE: React.CSSProperties = { flex: '1 1 0', overflowY: 'auto', overflowX: 'hidden', minHeight: 0 }; +const HEADER_WRAPPER_STYLE: React.CSSProperties = { display: 'flex', flexDirection: 'column', gap: '2px', width: '100%', overflow: 'hidden' }; +const HEADER_ROW_STYLE: React.CSSProperties = { display: 'flex', alignItems: 'baseline', gap: '8px', width: '100%', justifyContent: 'space-between', overflow: 'hidden' }; +const TITLE_GROUP_STYLE: React.CSSProperties = { display: 'flex', alignItems: 'baseline', gap: '8px', minWidth: 0, flex: '1 1 0', overflow: 'hidden' }; +const RULE_STYLE: React.CSSProperties = { fontWeight: 700, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }; +const RULE_NAME_STYLE: React.CSSProperties = { fontFamily: 'monospace', fontSize: '12px', color: '#5f6b7a', fontWeight: 400, whiteSpace: 'nowrap', flexShrink: 0 }; +const SUBTITLE_STYLE: React.CSSProperties = { color: '#5f6b7a', fontWeight: 400, fontSize: '12px' }; +const BODY_STYLE: React.CSSProperties = { paddingLeft: '4px', display: 'flex', flexDirection: 'column', gap: '4px' }; +const LINK_STYLE: React.CSSProperties = { color: '#0972d3', textDecoration: 'underline', cursor: 'pointer' }; diff --git a/packages/@aws-cdk/cdk-explorer/frontend/highlight.css b/packages/@aws-cdk/cdk-explorer/frontend/highlight.css new file mode 100644 index 000000000..438c3ea6e --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/frontend/highlight.css @@ -0,0 +1,34 @@ +@keyframes nav-highlight-fade { + from { opacity: 0.35; } + to { opacity: 0; } +} + +.nav-highlight { + position: relative; +} + +.nav-highlight::before { + content: ''; + position: absolute; + inset: 0; + background-color: var(--nav-highlight-color, #0972d3); + animation: nav-highlight-fade 3s ease-out forwards; + pointer-events: none; +} + +/* Make expandable section headers stretch to full width */ +[class*="header-wrapper_"] { + width: 100%; +} +[class*="expand-button_"][class*="header-button_"] { + flex: 1 1 auto; +} +[class*="header-text_"][class*="header-label_"] { + flex: 1 1 auto; +} + +/* Tighten expanded content: reduce top gap and indent under header text */ +[class*="content_"][class*="content-footer_"] { + padding-top: 2px !important; + padding-left: 20px !important; +} diff --git a/packages/@aws-cdk/cdk-explorer/frontend/index.html b/packages/@aws-cdk/cdk-explorer/frontend/index.html new file mode 100644 index 000000000..bfe35e541 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/frontend/index.html @@ -0,0 +1,17 @@ + + + + + + CDK Web Explorer + + + + +
    + + + diff --git a/packages/@aws-cdk/cdk-explorer/frontend/index.tsx b/packages/@aws-cdk/cdk-explorer/frontend/index.tsx new file mode 100644 index 000000000..83d6a6e82 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/frontend/index.tsx @@ -0,0 +1,18 @@ +import '@cloudscape-design/global-styles/index.css'; +import './highlight.css'; +import { applyMode, Mode } from '@cloudscape-design/global-styles'; +import * as React from 'react'; +import { createRoot } from 'react-dom/client'; +import { App } from './App'; + +applyMode(Mode.Light); + +const container = document.getElementById('root'); +if (!container) { + throw new Error('CDK Explorer: #root element not found'); +} +createRoot(container).render( + + + , +); diff --git a/packages/@aws-cdk/cdk-explorer/frontend/nav-types.ts b/packages/@aws-cdk/cdk-explorer/frontend/nav-types.ts new file mode 100644 index 000000000..8acafc0ff --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/frontend/nav-types.ts @@ -0,0 +1,10 @@ +import type { WebSourceLocation } from './api'; + +export type NavigateHandler = (opts: { + sourceLocation?: WebSourceLocation; + templateFile?: string; + logicalId?: string; + propertyPaths?: readonly string[]; + color?: string; + constructPath?: string; +}) => void; diff --git a/packages/@aws-cdk/cdk-explorer/frontend/prismjs.d.ts b/packages/@aws-cdk/cdk-explorer/frontend/prismjs.d.ts new file mode 100644 index 000000000..18aac4244 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/frontend/prismjs.d.ts @@ -0,0 +1,9 @@ +declare module 'prismjs/components/prism-core' { + import Prism from 'prismjs'; + export default Prism; +} +declare module 'prismjs/components/prism-json' {} +declare module 'prismjs/components/prism-yaml' {} +declare module 'prismjs/components/prism-clike' {} +declare module 'prismjs/components/prism-javascript' {} +declare module 'prismjs/components/prism-typescript' {} diff --git a/packages/@aws-cdk/cdk-explorer/frontend/syntax.ts b/packages/@aws-cdk/cdk-explorer/frontend/syntax.ts new file mode 100644 index 000000000..46b90c15a --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/frontend/syntax.ts @@ -0,0 +1,93 @@ +/** + * PrismJS-based syntax tokenizer covering the template formats (JSON, YAML) and + * every CDK source language (TypeScript, JavaScript, Python, Java, C#, Go). + * Returns structured tokens per line for our custom line renderer (which handles + * highlighting, scroll, diagnostics). + */ +import Prism from 'prismjs/components/prism-core'; +import 'prismjs/components/prism-json'; +import 'prismjs/components/prism-yaml'; +import 'prismjs/components/prism-clike'; +import 'prismjs/components/prism-javascript'; +import 'prismjs/components/prism-typescript'; +import 'prismjs/components/prism-python'; +import 'prismjs/components/prism-java'; +import 'prismjs/components/prism-csharp'; +import 'prismjs/components/prism-go'; + +/** Languages the viewer can highlight: template formats plus CDK source languages. */ +export type Language = 'json' | 'yaml' | 'typescript' | 'javascript' | 'python' | 'java' | 'csharp' | 'go'; + +export interface Token { + readonly type: string | undefined; + readonly content: string; +} + +const GRAMMAR_MAP: Record = { + json: Prism.languages.json, + yaml: Prism.languages.yaml, + typescript: Prism.languages.typescript, + javascript: Prism.languages.javascript, + python: Prism.languages.python, + java: Prism.languages.java, + csharp: Prism.languages.csharp, + go: Prism.languages.go, +}; + +export function tokenizeLines(code: string, language: Language): Token[][] { + const grammar = GRAMMAR_MAP[language]; + if (!grammar) { + return code.split('\n').map((line) => [{ type: undefined, content: line }]); + } + + const tokens = Prism.tokenize(code, grammar); + return splitIntoLines(tokens); +} + +function splitIntoLines(tokens: Array): Token[][] { + const lines: Token[][] = [[]]; + + for (const token of tokens) { + if (typeof token === 'string') { + const parts = token.split('\n'); + for (let i = 0; i < parts.length; i++) { + if (i > 0) lines.push([]); + if (parts[i]) { + lines[lines.length - 1].push({ type: undefined, content: parts[i] }); + } + } + } else { + flattenToken(token, lines); + } + } + + return lines; +} + +function flattenToken(token: Prism.Token, lines: Token[][]): void { + const type = token.type; + + if (typeof token.content === 'string') { + const parts = token.content.split('\n'); + for (let i = 0; i < parts.length; i++) { + if (i > 0) lines.push([]); + if (parts[i]) { + lines[lines.length - 1].push({ type, content: parts[i] }); + } + } + } else if (Array.isArray(token.content)) { + for (const inner of token.content) { + if (typeof inner === 'string') { + const parts = inner.split('\n'); + for (let i = 0; i < parts.length; i++) { + if (i > 0) lines.push([]); + if (parts[i]) { + lines[lines.length - 1].push({ type, content: parts[i] }); + } + } + } else { + flattenToken(inner, lines); + } + } + } +} diff --git a/packages/@aws-cdk/cdk-explorer/lib/api-private.ts b/packages/@aws-cdk/cdk-explorer/lib/api-private.ts index 006d91e2d..811a6e738 100644 --- a/packages/@aws-cdk/cdk-explorer/lib/api-private.ts +++ b/packages/@aws-cdk/cdk-explorer/lib/api-private.ts @@ -2,3 +2,4 @@ export { findCreationStackTrace } from '../../toolkit-lib/lib/api/source-tracing/private/stack-source-tracing'; export { WATCH_EXCLUDE_DEFAULTS } from '../../toolkit-lib/lib/actions/watch/private/helpers'; export { createIgnoreMatcher } from '../../toolkit-lib/lib/util/glob-matcher'; +export { SYNTH_LOCK_FILE } from '../../toolkit-lib/lib/api/rwlock'; diff --git a/packages/@aws-cdk/cdk-explorer/lib/core/assembly-watcher.ts b/packages/@aws-cdk/cdk-explorer/lib/core/assembly-watcher.ts index ce065f21e..03c839408 100644 --- a/packages/@aws-cdk/cdk-explorer/lib/core/assembly-watcher.ts +++ b/packages/@aws-cdk/cdk-explorer/lib/core/assembly-watcher.ts @@ -2,6 +2,7 @@ import * as path from 'path'; import { MANIFEST_FILE } from '@aws-cdk/cloud-assembly-api'; import { VALIDATION_REPORT_FILE } from '@aws-cdk/cloud-assembly-schema'; import * as chokidar from 'chokidar'; +import { SYNTH_LOCK_FILE } from '../api-private'; /** * The name of the construct tree metadata file emitted alongside the manifest. @@ -51,6 +52,12 @@ export interface AssemblyWatcherOptions { readonly assemblyDir: string; /** Invoked (debounced) when the assembly's signal files change. */ readonly onChange: () => void; + /** + * Invoked when synth write-lock activity (`synth.lock`) first appears, i.e. a + * synth started. `atMs` is the observation time. Lets a consumer date-stamp + * source-file staleness against synth start rather than synth finish. + */ + readonly onSynthActivity?: (atMs: number) => void; /** Receives non-fatal watcher errors. */ readonly onError?: (error: unknown) => void; /** @@ -90,9 +97,19 @@ export function startAssemblyWatcher(options: AssemblyWatcherOptions): AssemblyW const watcher = createWatcher(options.assemblyDir); - watcher.on('all', (_eventName, filePath) => { + watcher.on('all', (eventName, filePath) => { if (closed) return; - if (!ASSEMBLY_SIGNAL_FILES.has(path.basename(filePath))) return; + const base = path.basename(filePath); + if (base === SYNTH_LOCK_FILE) { + // toolkit-lib's `RWLock` creates this marker for the duration of a synth, + // so its appearance is our only signal that a synth has started (the + // explorer never starts synths itself). Record it, but never treat it as + // an assembly refresh: the lock's create/delete would otherwise cause + // spurious reloads. + if (eventName === 'add') options.onSynthActivity?.(Date.now()); + return; + } + if (!ASSEMBLY_SIGNAL_FILES.has(base)) return; if (timer) { clearTimeout(timer); } diff --git a/packages/@aws-cdk/cdk-explorer/lib/core/source-watcher.ts b/packages/@aws-cdk/cdk-explorer/lib/core/source-watcher.ts new file mode 100644 index 000000000..8fbefe812 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/lib/core/source-watcher.ts @@ -0,0 +1,130 @@ +import * as chokidar from 'chokidar'; +import { WATCH_EXCLUDE_DEFAULTS, createIgnoreMatcher } from '../api-private'; +import type { FileWatcher } from './assembly-watcher'; + +// Coalesce an editor's burst of writes (e.g. save-all) into a single signal. +const DEBOUNCE_MS = 200; + +/** + * Paths under the app dir that never count as a source change: toolkit-lib's + * own watch exclusions, dependencies, our synth output (`cdk.out`), and + * dotfiles (editor configs, `.git`). Applied both at the chokidar level (so the + * tree is never traversed) and re-checked in the handler. + */ +export const SOURCE_WATCH_EXCLUDES = [ + ...WATCH_EXCLUDE_DEFAULTS, + '**/node_modules/**', + '**/cdk.out/**', + '.*', + '**/.*', + '**/.*/**', +]; + +/** A running source-tree watcher. */ +export interface SourceWatcher { + /** Stop watching and release the underlying file handles. */ + close(): Promise; +} + +export interface SourceWatcherOptions { + /** The application directory whose source tree is watched. */ + readonly appDir: string; + /** Invoked (debounced) when a non-ignored source file changes. */ + readonly onChange: () => void; + /** Receives non-fatal watcher errors. */ + readonly onError: (error: unknown) => void; + /** + * Factory for the underlying file watcher. Defaults to chokidar; overridden + * in tests with a fake so behavior is verified without real file IO. + */ + readonly createWatcher?: (appDir: string) => FileWatcher; +} + +// Thin wrapper over real chokidar; exercised via integration, not unit tests. +/* c8 ignore start */ +function defaultCreateWatcher(appDir: string): FileWatcher { + // chokidar applies the same ignore policy while traversing, so excluded paths + // (node_modules, cdk.out, dotfiles) are skipped at the source instead of + // streamed to the handler. It emits absolute paths, which the handler + // re-checks against the same policy. + return chokidar.watch(appDir, { + ignored: createIgnoreMatcher({ exclude: SOURCE_WATCH_EXCLUDES, rootDir: appDir }), + ignoreInitial: true, + }) as unknown as FileWatcher; +} +/* c8 ignore stop */ + +/** A debounced action: coalesces a burst of triggers into one delayed run. */ +interface Debounced { + /** Schedule `action`, resetting the delay if a run is already pending. */ + trigger(): void; + /** Drop a pending run, if any. */ + cancel(): void; +} + +/** + * Run `action` once, `delayMs` after the most recent `trigger()`. Each new + * trigger restarts the delay, so a burst collapses into a single trailing run. + * `cancel()` discards a pending run (used on close). This is a debounce, not a + * sleep: the pending run is cancelable and reset on every trigger. + */ +function debounce(action: () => void, delayMs: number): Debounced { + let timer: NodeJS.Timeout | undefined; + return { + trigger() { + if (timer) { + clearTimeout(timer); + } + timer = setTimeout(() => { + timer = undefined; + action(); + }, delayMs); + }, + cancel() { + if (timer) { + clearTimeout(timer); + timer = undefined; + } + }, + }; +} + +/** + * Watch a CDK app's source tree and fire `onChange` (debounced) when a + * non-ignored file changes. + */ +export function startSourceWatcher(options: SourceWatcherOptions): SourceWatcher { + const createWatcher = options.createWatcher ?? defaultCreateWatcher; + // Drop changes to ignored paths so they never raise a spurious signal. chokidar + // emits absolute paths, which the matcher's rootDir resolves before matching. + const shouldIgnore = createIgnoreMatcher({ exclude: SOURCE_WATCH_EXCLUDES, rootDir: options.appDir }); + + let closed = false; + const debounced = debounce(() => { + try { + options.onChange(); + } catch (error) { + options.onError(error); + } + }, DEBOUNCE_MS); + + const watcher = createWatcher(options.appDir); + + watcher.on('all', (_eventName, filePath) => { + if (closed) return; + if (shouldIgnore(filePath)) return; + debounced.trigger(); + }); + + watcher.on('error', (error) => { + options.onError(error); + }); + + return { + async close() { + closed = true; + debounced.cancel(); + await watcher.close(); + }, + }; +} diff --git a/packages/@aws-cdk/cdk-explorer/lib/index.ts b/packages/@aws-cdk/cdk-explorer/lib/index.ts index e9aa0fdb8..1e2d38cf4 100644 --- a/packages/@aws-cdk/cdk-explorer/lib/index.ts +++ b/packages/@aws-cdk/cdk-explorer/lib/index.ts @@ -3,7 +3,12 @@ export const VERSION = '0.0.0'; export { readAssembly } from './core/assembly-reader'; export type { AssemblyData, AssemblyReadResult, ConstructNode } from './core/assembly-reader'; export type { SourceLocation } from './core/source-resolver'; +export { toolkitAssemblyLock } from './core/assembly-lock'; +export type { AssemblyLock, AcquireAssemblyLock } from './core/assembly-lock'; export { startLspServer } from './lsp/main'; export { startServer, createLspHandlers } from './lsp/server'; export type { LspHandlers, LspHandlerOptions, LspServerOptions } from './lsp/server'; + +export { startWebServer } from './web/server'; +export type { WebServer, WebServerOptions } from './web/server'; diff --git a/packages/@aws-cdk/cdk-explorer/lib/lsp/server.ts b/packages/@aws-cdk/cdk-explorer/lib/lsp/server.ts index 23b0fe5c8..b722ccebf 100644 --- a/packages/@aws-cdk/cdk-explorer/lib/lsp/server.ts +++ b/packages/@aws-cdk/cdk-explorer/lib/lsp/server.ts @@ -25,7 +25,7 @@ import { type Location, type RemoteConsole, } from 'vscode-languageserver/node'; -import { WATCH_EXCLUDE_DEFAULTS, createIgnoreMatcher } from '../api-private'; +import { createIgnoreMatcher } from '../api-private'; import { codeLensesForFile } from './codelens'; import { executeCommand, SUPPORTED_COMMANDS, type NotifySink } from './commands'; import { mapViolationsToDiagnostics } from './diagnostics'; @@ -45,6 +45,7 @@ import { type AssemblyWatcherOptions, } from '../core/assembly-watcher'; import { isWithinRoot } from '../core/source-resolver'; +import { SOURCE_WATCH_EXCLUDES } from '../core/source-watcher'; import type { SynthRunResult } from '../core/synth-runner'; /** @@ -399,18 +400,10 @@ export function createLspHandlers(options: LspHandlerOptions): LspHandlers { }, async onInitialized() { const projectDir = currentProjectDir(); - // Same exclusion logic as toolkit-lib's watch(): - // WATCH_EXCLUDE_DEFAULTS covers common non-source dirs, then we add cdk.out - // (our own output) and dotfiles (editor configs, .git, etc.) + // Ignore non-source paths (deps, cdk.out, dotfiles) using the same policy + // the web explorer's source watcher applies. shouldIgnore = createIgnoreMatcher({ - exclude: [ - ...WATCH_EXCLUDE_DEFAULTS, - '**/node_modules/**', - '**/cdk.out/**', - '.*', - '**/.*', - '**/.*/**', - ], + exclude: SOURCE_WATCH_EXCLUDES, rootDir: projectDir, }); await refreshFromAssembly(projectDir); diff --git a/packages/@aws-cdk/cdk-explorer/lib/web/events.ts b/packages/@aws-cdk/cdk-explorer/lib/web/events.ts new file mode 100644 index 000000000..46355b4cd --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/lib/web/events.ts @@ -0,0 +1,55 @@ +import type { Request, Response } from 'express'; +import type { SseEventName } from './protocol'; + +/** + * Tracks connected Server-Sent Events clients and pushes events to all of them. + * One instance lives per web server. `close()` ends every open stream on + * shutdown so the HTTP server can stop cleanly. + */ +export class SseBroadcaster { + private readonly clients = new Set(); + + /** + * Express handler for `GET /api/events`. Opens a long-lived SSE stream and + * registers the client, removing it when the request closes or the socket + * errors so a vanished client is never written to. + */ + public handle(req: Request, res: Response): void { + res.set({ + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-store', + 'Connection': 'keep-alive', + }); + res.flushHeaders(); + this.clients.add(res); + + const remove = (): void => { + this.clients.delete(res); + }; + req.on('close', remove); + // Evict on socket error too, so a vanished client is never written to. + res.on('error', remove); + } + + /** + * Push an event to every connected client. The `data: {}` line is required: + * EventSource does not dispatch a named event whose data buffer is empty, so + * the empty payload is what makes the client's listener fire. Writing to a + * client that already disconnected is a harmless no-op (returns false, does + * not throw); the `close`/`error` handlers in `handle` do the eviction. + */ + public broadcast(event: SseEventName): void { + const frame = `event: ${event}\ndata: {}\n\n`; + for (const client of this.clients) { + client.write(frame); + } + } + + /** End every open stream and forget the clients. Called on server shutdown. */ + public close(): void { + for (const client of this.clients) { + client.end(); + } + this.clients.clear(); + } +} diff --git a/packages/@aws-cdk/cdk-explorer/lib/web/protocol.ts b/packages/@aws-cdk/cdk-explorer/lib/web/protocol.ts new file mode 100644 index 000000000..0897d16ad --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/lib/web/protocol.ts @@ -0,0 +1,160 @@ +/** HTTP and SSE contract shared between the web server and the SPA. */ + +/** + * SSE event name the server sends (and the SPA listens for) when the cloud + * assembly is rewritten. It carries no meaningful payload: the server holds no + * assembly state, so the SPA re-fetches the tree and violations on receipt. + */ +export const ASSEMBLY_CHANGED = 'assembly-changed'; + +/** + * SSE event name sent when a source file under the app directory changes. Lets + * the SPA re-check the open file's staleness (and refresh its content) the + * instant it is edited, without waiting for the next assembly refresh. + */ +export const SOURCE_CHANGED = 'source-changed'; + +/** The SSE event names the server may send. */ +export type SseEventName = typeof ASSEMBLY_CHANGED | typeof SOURCE_CHANGED; + +export interface FileResponse { + readonly path: string; + readonly content: string; + /** + * True when the file on disk was modified after the synth that produced the + * currently loaded assembly started, so its highlighted lines and violations + * may be out of date. Best-effort (see `StalenessTracker`). + */ + readonly stale: boolean; +} + +/** + * A construct's source location for the SPA. Mirrors the core `SourceLocation` + * but with `file` made app-relative (POSIX), so the client can feed it straight + * back into `/api/file`. Line and column are 1-based, or 0 when only the file + * is known. + */ +export interface WebSourceLocation { + /** Source file path relative to the app directory. POSIX separators. */ + readonly file: string; + readonly line: number; + readonly column: number; +} + +/** + * A construct tree node as served to the SPA. A trimmed, wire-stable view of + * the core `ConstructNode`: absolute source paths are relativized to the app + * directory and nothing internal leaks over the wire. + */ +export interface WebConstructNode { + /** Construct path, e.g. "MyStack/DataBucket". */ + readonly path: string; + readonly id: string; + /** CFN resource type (e.g. "AWS::S3::Bucket"), if this construct is a resource. */ + readonly type?: string; + /** CFN logical ID, if this construct maps to a resource. */ + readonly logicalId?: string; + /** + * Path to the synthesized template that declares this construct's CFN + * resource, relative to the cloud assembly (`cdk.out`) directory, with POSIX + * separators. Usually a bare name like "MyStack.template.json", but includes + * the sub-assembly directory for staged stacks, e.g. + * "assembly-Prod/Prod-MyStack.template.json". Only set for CFN resources. The + * core's absolute path is relativized before it crosses the wire. + */ + readonly templateFile?: string; + readonly sourceLocation?: WebSourceLocation; + /** + * Highest-severity policy-violation label affecting this construct. A folded + * default child's violations count toward its parent. Absent when the + * construct has no violation. Used to flag the node in the tree. + */ + readonly highestSeverity?: string; + /** + * Highest severity inherited from any descendant. Set when a child (or + * deeper) has a violation but this node does not. Lets the tree color + * ancestor labels so users can drill down to the offending construct. + */ + readonly inheritedSeverity?: string; + readonly children: readonly WebConstructNode[]; +} + +/** Line range within a template (1-based, inclusive on both ends). */ +export interface LineRange { + readonly startLine: number; + readonly endLine: number; +} + +/** Resolved resource metadata returned by `GET /api/template`. */ +export interface TemplateResource { + /** Line range of the resource's value block `{ ... }`. */ + readonly block: LineRange; + /** User source location for the construct that owns this resource. */ + readonly source?: WebSourceLocation; +} + +/** Response for `GET /api/template?file=`. */ +export interface TemplateResponse { + readonly content: string; + readonly resources: Record; +} + +/** + * Response for `GET /api/tree`. `not-synthesized` means no cloud assembly was + * found (the user has not run `cdk synth`) + */ +export type TreeResponse = + | { readonly status: 'ok'; readonly tree: readonly WebConstructNode[]; readonly warnings: readonly string[] } + | { readonly status: 'not-synthesized' }; + +/** + * Severity exactly as reported by CDK policy validation. Unlike the LSP, which + * collapses these onto its three diagnostic levels, the SPA keeps the full set + * so the violations panel can distinguish (for example) fatal from error. + */ +export type WebViolationSeverity = 'fatal' | 'error' | 'warning' | 'info' | 'custom'; + +/** + * A single construct that triggered a violation, joined to construct-tree data + * (resolved source location and template file) so a future navigation feature + * can link to the resource and its source. + */ +export interface WebViolationOccurrence { + /** Construct path of the offending construct, e.g. "MyStack/MyBucket". */ + readonly constructPath: string; + /** CFN logical ID of the offending resource, if known. */ + readonly logicalId?: string; + /** + * Template that declares the resource, relative to the cloud assembly + * (`cdk.out`) directory with POSIX separators (see {@link WebConstructNode.templateFile}). + */ + readonly templateFile?: string; + /** Resolved user source location; absent for non-TypeScript apps. */ + readonly sourceLocation?: WebSourceLocation; + /** JSON property paths within the resource that violate the rule. */ + readonly propertyPaths?: readonly string[]; +} + +/** A policy-validation violation, normalized for the SPA. */ +export interface WebViolation { + readonly ruleName: string; + readonly description: string; + /** Severity as reported; absent for plugins (e.g. CfnGuard) that don't emit one. */ + readonly severity?: WebViolationSeverity; + /** Plugin-specific label when `severity` is "custom". */ + readonly customSeverity?: string; + /** Validation plugin that produced the violation. */ + readonly source: string; + /** Suggested fix text, when the plugin provides one. */ + readonly suggestedFix?: string; + readonly occurrences: readonly WebViolationOccurrence[]; +} + +/** + * Response for `GET /api/policy-validation`. `not-synthesized` mirrors the tree + * endpoint: no cloud assembly was found. When the assembly exists, `violations` + * is the normalized list (empty when the report is clean or absent). + */ +export type ViolationsResponse = + | { readonly status: 'ok'; readonly violations: readonly WebViolation[] } + | { readonly status: 'not-synthesized' }; diff --git a/packages/@aws-cdk/cdk-explorer/lib/web/routes.ts b/packages/@aws-cdk/cdk-explorer/lib/web/routes.ts new file mode 100644 index 000000000..307fbf139 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/lib/web/routes.ts @@ -0,0 +1,484 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { ConstructIndex, MANIFEST_FILE, resolveAllResourceRanges } from '@aws-cdk/cloud-assembly-api'; +import type { PolicyValidationReportJson, ViolatingConstructJson } from '@aws-cdk/cloud-assembly-schema'; +import { ToolkitError } from '@aws-cdk/toolkit-lib'; +import { type Router, type Express, type Response } from 'express'; +// eslint-disable-next-line @typescript-eslint/no-require-imports +import express = require('express'); +import type { LineRange, TemplateResource, TemplateResponse, TreeResponse, ViolationsResponse, WebConstructNode, WebSourceLocation, WebViolation, WebViolationOccurrence } from './protocol'; +import { resolveWithinRoot } from './safe-path'; +import { classifyReportSeverity, displaySeverity, severityRank } from './severity'; +import { StalenessTracker } from './staleness'; +import type { AcquireAssemblyLock, AssemblyLock } from '../core/assembly-lock'; +import { readAssembly as defaultReadAssembly, type AssemblyData, type AssemblyReadResult, type ConstructNode } from '../core/assembly-reader'; +import type { SourceLocation } from '../core/source-resolver'; + +/** Largest file the viewer returns inline, to avoid buffering huge artifacts into memory and the response. */ +const MAX_FILE_BYTES = 2 * 1024 * 1024; + +/** The read lock is fail-fast; retry this many times, this far apart, before replying 503. */ +const LOCK_RETRIES = 10; +const LOCK_RETRY_MS = 50; +const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + +export interface ApiOptions { + /** Root of the CDK app; all file listing/reading is confined to this directory. */ + readonly appDir: string; + /** + * Cloud assembly directory the construct tree and violations are read from. + * Defaults to `/cdk.out`. + */ + readonly assemblyDir?: string; + /** + * Reader for the cloud assembly. Injectable for tests; defaults to the real + * `readAssembly` against {@link assemblyDir}. + */ + readonly readAssembly?: (assemblyDir: string) => Promise; + /** + * Acquire a read lock around each assembly read so no request observes a + * mid-synth assembly. Built from the Toolkit in {@link registerApi}'s caller. + */ + readonly acquireAssemblyLock: AcquireAssemblyLock; + /** + * Tracks source-file staleness against synth timing; `/api/file` reads it to + * stamp each response. Injected by the server, which owns the reference: the + * assembly watcher advances it once per generation and feeds it synth-start + * activity. Defaults to a private tracker (never stale) for a standalone + * router with no watcher driving it. + */ + readonly staleness?: StalenessTracker; +} + +export function createApiRouter(options: ApiOptions): Router { + const appDir = canonicalDir(options.appDir); + const assemblyDir = options.assemblyDir ?? path.join(options.appDir, 'cdk.out'); + const readAssembly = options.readAssembly ?? defaultReadAssembly; + const acquireAssemblyLock = options.acquireAssemblyLock; + const staleness = options.staleness ?? new StalenessTracker(); + let cachedAssembly: { result: AssemblyReadResult; mtimeMs: number } | undefined; + + /** mtime of the assembly's manifest, or undefined when no assembly exists yet. */ + function manifestMtimeMs(): number | undefined { + try { + return fs.statSync(path.join(assemblyDir, MANIFEST_FILE)).mtimeMs; + } catch { + return undefined; + } + } + + /** + * Read the cloud assembly, memoized on the manifest's mtime. A request whose + * manifest is unchanged since the last successful read is served from cache + * without touching the lock (and, mid-synth, keeps serving the last complete + * generation rather than contending). A changed or first-seen manifest + * triggers a re-read under the assembly read lock, so a concurrent synth is + * never observed mid-write. The lock is fail-fast, so retry on writer + * contention; if it never clears, report `locked` (served as a 503). + */ + async function getCachedAssembly(): Promise { + const mtimeMs = manifestMtimeMs(); + if (mtimeMs === undefined) return { status: 'not-found' }; + if (cachedAssembly && cachedAssembly.mtimeMs === mtimeMs) return cachedAssembly.result; + + let lock: AssemblyLock | undefined; + for (let attempt = 0; lock === undefined; attempt++) { + try { + lock = await acquireAssemblyLock(assemblyDir); + } catch (err) { + if (!ToolkitError.isLockError(err)) return { status: 'error', message: (err as Error).message }; + if (attempt === LOCK_RETRIES) return { status: 'locked' }; + await delay(LOCK_RETRY_MS); + } + } + try { + // Under the read lock no synth can write, so the manifest mtime is stable + // across the read; cache the successful result against it. + const lockedMtimeMs = manifestMtimeMs(); + const result = await readAssembly(assemblyDir); + if (result.status === 'success' && lockedMtimeMs !== undefined) { + cachedAssembly = { result, mtimeMs: lockedMtimeMs }; + } + return result; + } finally { + await lock.release(); + } + } + + const router = express.Router(); + + router.get('/health', (_req, res) => { + res.json({ status: 'ok' }); + }); + + router.get('/info', (_req, res) => { + res.json({ appDir }); + }); + + router.get('/file', (req, res) => { + const requested = typeof req.query.path === 'string' ? req.query.path : ''; + if (!requested) { + return res.status(400).json({ error: 'path query parameter is required' }); + } + const resolved = resolveWithinRoot(appDir, requested); + if (!resolved) { + return res.status(403).json({ error: 'path escapes application directory' }); + } + let stat: fs.Stats; + try { + stat = fs.statSync(resolved); + } catch { + return res.status(404).json({ error: 'file not found' }); + } + if (!stat.isFile()) { + return res.status(400).json({ error: 'not a file' }); + } + if (stat.size > MAX_FILE_BYTES) { + return res.status(413).json({ error: `file exceeds ${MAX_FILE_BYTES} byte limit` }); + } + const buffer = fs.readFileSync(resolved); + if (isBinary(buffer)) { + return res.status(415).json({ error: 'binary file cannot be displayed' }); + } + return res.json({ + path: toPosix(path.relative(appDir, resolved)), + content: buffer.toString('utf-8'), + stale: staleness.isStale(stat.mtimeMs), + }); + }); + + router.get('/tree', async (_req, res) => { + withAssembly(await getCachedAssembly(), res, (data) => { + const severityByPath = highestSeverityByPath(data.violations); + const tree = data.tree.map((node) => toWebNode(node, severityByPath, assemblyDir, appDir)); + const body: TreeResponse = { status: 'ok', tree, warnings: data.warnings }; + res.json(body); + }); + }); + + router.get('/policy-validation', async (_req, res) => { + withAssembly(await getCachedAssembly(), res, (data) => { + const index = ConstructIndex.fromTree(data.tree); + const violations = normalizeViolations(data.violations, index, assemblyDir, appDir); + const body: ViolationsResponse = { status: 'ok', violations }; + res.json(body); + }); + }); + + router.get('/template', async (req, res) => { + const file = typeof req.query.file === 'string' ? req.query.file : ''; + if (!file) { + return res.status(400).json({ error: 'file query parameter is required' }); + } + const resolved = resolveWithinRoot(assemblyDir, file); + if (!resolved) { + return res.status(403).json({ error: 'path escapes assembly directory' }); + } + let content: string; + try { + const stat = fs.statSync(resolved); + if (!stat.isFile()) { + return res.status(400).json({ error: 'not a file' }); + } + if (stat.size > MAX_FILE_BYTES) { + return res.status(413).json({ error: `file exceeds ${MAX_FILE_BYTES} byte limit` }); + } + content = fs.readFileSync(resolved, 'utf-8'); + } catch { + return res.status(404).json({ error: 'template not found' }); + } + + const result = await getCachedAssembly(); + if (result.status === 'locked') { + return res.status(503).json({ error: 'synth in progress, please retry' }); + } + // On a cache hit the manifest is unchanged, so the on-disk template read + // above is the same generation as this index; on a miss the index was just + // re-read past the synth that changed it. + const index = result.status === 'success' ? ConstructIndex.fromTree(result.data.tree) : undefined; + const resources = buildTemplateResources(content, file, index, assemblyDir, appDir); + const body: TemplateResponse = { content, resources }; + return res.json(body); + }); + + return router; +} + +export function registerApi(app: Express, options: ApiOptions): void { + app.use('/api', createApiRouter(options)); +} + +/** + * Read the cloud assembly and send the shared not-synthesized / error responses, + * invoking `onReady` with the assembly data only on success. + */ +function withAssembly( + result: AssemblyReadResult | { status: 'locked' }, + res: Response, + onReady: (data: AssemblyData) => void, +): void { + if (result.status === 'locked') { + res.status(503).json({ error: 'synth in progress, please retry' }); + return; + } + if (result.status === 'not-found') { + res.json({ status: 'not-synthesized' }); + return; + } + if (result.status === 'error') { + res.status(500).json({ error: result.message }); + return; + } + onReady(result.data); +} + +/** Ids CDK gives a construct's synthetic default child (the L1 resource it wraps). */ +const DEFAULT_CHILD_IDS = new Set(['Resource', 'Default']); + +/** + * Map a core construct node to its wire form, owning every transform the + * displayed tree needs: + * - Relativizes paths the client can't resolve (`templateFile` to the cloud + * assembly, `sourceLocation.file` to the app dir; a source location outside + * the app dir is dropped, since `/api/file` cannot serve it). + * - Folds a synthetic default child (the leaf L1 resource CDK names "Resource" + * or "Default", carrying a CFN type) up into its parent, so an L2 like + * `ItemsTable` shows its CFN type directly instead of nesting a redundant + * leaf. Only a leaf default child with a type collapses; a resource that + * nests further children is left intact. + * - Annotates each node with the highest severity of any violation on it, + * folding an absorbed default child's severity into the parent so the dot + * tracks the displayed node. Because the join happens here, the client never + * re-derives the collapse rule. + * Recurses depth-first. + */ +export function toWebNode( + node: ConstructNode, + severityByPath: ReadonlyMap, + assemblyDir: string, + appDir: string, +): WebConstructNode { + const children = node.children.map((child) => toWebNode(child, severityByPath, assemblyDir, appDir)); + const ownSeverity = severityByPath.get(node.path); + const defaultChild = children.find( + (child) => DEFAULT_CHILD_IDS.has(child.id) && child.children.length === 0 && child.type !== undefined, + ); + if (!defaultChild) { + const highestSeverity = ownSeverity; + const inheritedSeverity = highestSeverity ? undefined : worstChildSeverity(children); + return { + path: node.path, + id: node.id, + type: node.type, + logicalId: node.logicalId, + templateFile: node.templateFile ? toPosix(path.relative(assemblyDir, node.templateFile)) : undefined, + sourceLocation: toWebSourceLocation(node.sourceLocation, appDir), + highestSeverity, + ...(inheritedSeverity && { inheritedSeverity }), + children, + }; + } + const highestSeverity = moreSevere(ownSeverity, defaultChild.highestSeverity); + const remainingChildren = children.filter((child) => child !== defaultChild); + const inheritedSeverity = highestSeverity ? undefined : worstChildSeverity(remainingChildren); + return { + path: node.path, + id: node.id, + type: defaultChild.type, + logicalId: defaultChild.logicalId, + templateFile: defaultChild.templateFile, + sourceLocation: toWebSourceLocation(node.sourceLocation, appDir) ?? defaultChild.sourceLocation, + highestSeverity, + ...(inheritedSeverity && { inheritedSeverity }), + children: remainingChildren, + }; +} + +/** Build a construct-path to highest-severity-label map from a (raw) validation report. */ +function highestSeverityByPath(report: PolicyValidationReportJson | undefined): Map { + const byPath = new Map(); + for (const plugin of report?.pluginReports ?? []) { + for (const violation of plugin.violations ?? []) { + const label = displaySeverity(classifyReportSeverity(violation.severity, violation.customSeverity)); + for (const vc of violation.violatingConstructs ?? []) { + const existing = byPath.get(vc.constructPath); + if (existing === undefined || severityRank(label) < severityRank(existing)) { + byPath.set(vc.constructPath, label); + } + } + } + } + return byPath; +} + +/** The more severe of two severity labels (lower rank = more severe); undefined loses to any defined label. */ +function moreSevere(a: string | undefined, b: string | undefined): string | undefined { + if (a === undefined) return b; + if (b === undefined) return a; + return severityRank(a) <= severityRank(b) ? a : b; +} + +/** The worst severity across all children (direct or inherited). */ +function worstChildSeverity(children: readonly WebConstructNode[]): string | undefined { + let worst: string | undefined; + for (const child of children) { + worst = moreSevere(worst, child.highestSeverity ?? child.inheritedSeverity); + } + return worst; +} + +/** Relativize a source location to the app dir, dropping any that escape it. */ +function toWebSourceLocation(loc: SourceLocation | undefined, appDir: string): WebSourceLocation | undefined { + if (!loc) return undefined; + const file = toPosix(path.relative(appDir, loc.file)); + if (file === '..' || file.startsWith('../')) return undefined; + return { file, line: loc.line, column: loc.column }; +} + +/** + * Normalize a policy-validation report into the SPA's flat violation model. + * Each violating construct is joined to the construct tree (by path): the + * resolved `sourceLocation` and `cdk.out`-relative `templateFile` come from the + * tree node when present, falling back to the report's own resource fields. + * These carry the data a future navigation feature would link from. + */ +export function normalizeViolations( + report: PolicyValidationReportJson | undefined, + index: ConstructIndex, + assemblyDir: string, + appDir: string, +): WebViolation[] { + return (report?.pluginReports ?? []).flatMap((plugin) => + (plugin.violations ?? []).map((violation) => ({ + ruleName: violation.ruleName, + description: violation.description, + ...classifyReportSeverity(violation.severity, violation.customSeverity), + source: plugin.pluginName, + suggestedFix: violation.suggestedFix, + occurrences: (violation.violatingConstructs ?? []).map((vc) => toOccurrence(vc, index, assemblyDir, appDir)), + }))); +} + +/** Join one violating construct to its tree node, preferring resolved tree data. */ +function toOccurrence( + vc: ViolatingConstructJson, + index: ConstructIndex, + assemblyDir: string, + appDir: string, +): WebViolationOccurrence { + const node = index.byPath(vc.constructPath); + const templateFile = node?.templateFile + ? toPosix(path.relative(assemblyDir, node.templateFile)) + : vc.cloudFormationResource?.templatePath; + return { + constructPath: vc.constructPath, + logicalId: node?.logicalId ?? vc.cloudFormationResource?.logicalId, + templateFile, + sourceLocation: toWebSourceLocation(node?.sourceLocation, appDir), + propertyPaths: vc.cloudFormationResource?.propertyPaths, + }; +} + +/** + * Build the resource metadata map for a template file. Each resource gets its + * block line range and the source location of the construct that owns it. + */ +function buildTemplateResources( + content: string, + templateFile: string, + index: ConstructIndex | undefined, + assemblyDir: string, + appDir: string, +): Record { + const allRanges = resolveAllResourceRanges(content); + if (!allRanges) return {}; + + const lineOffsets = computeLineOffsets(content); + const resources: Record = {}; + + for (const [logicalId, ranges] of Object.entries(allRanges)) { + const block = offsetRangeToLineRange(ranges.block, lineOffsets); + + let source: WebSourceLocation | undefined; + if (index) { + const owner = findOwnerByLogicalId(index, logicalId, templateFile, assemblyDir); + if (owner?.sourceLocation) { + source = toWebSourceLocation(owner.sourceLocation, appDir); + } + } + + resources[logicalId] = { block, ...(source && { source }) }; + } + return resources; +} + +/** + * Find the construct node that owns a given logicalId within a specific + * template file. Logical IDs are only unique per template, so we match on both. + */ +function findOwnerByLogicalId( + index: ConstructIndex, + logicalId: string, + templateFile: string, + assemblyDir: string, +): ConstructNode | undefined { + for (const node of index) { + if (node.logicalId === logicalId && node.templateFile) { + const relTemplate = toPosix(path.relative(assemblyDir, node.templateFile)); + if (relTemplate === templateFile) return node; + } + } + return undefined; +} + +/** + * Compute 0-based byte offsets for the start of each line. Line 1 starts at + * offset 0. Used for fast offset-to-line conversion. + */ +function computeLineOffsets(text: string): number[] { + const offsets = [0]; + for (let i = 0; i < text.length; i++) { + if (text[i] === '\n') { + offsets.push(i + 1); + } + } + return offsets; +} + +/** Convert a character offset to a 1-based line number using precomputed line offsets. */ +function offsetToLine(offset: number, lineOffsets: number[]): number { + let lo = 0; + let hi = lineOffsets.length - 1; + while (lo < hi) { + const mid = Math.floor((lo + hi + 1) / 2); + if (lineOffsets[mid] <= offset) { + lo = mid; + } else { + hi = mid - 1; + } + } + return lo + 1; +} + +/** Convert an OffsetRange to a 1-based inclusive LineRange. */ +function offsetRangeToLineRange(range: { start: number; end: number }, lineOffsets: number[]): LineRange { + return { + startLine: offsetToLine(range.start, lineOffsets), + endLine: offsetToLine(Math.max(range.start, range.end - 1), lineOffsets), + }; +} + +/** Normalize OS separators to '/' so the API contract is stable across platforms. */ +function toPosix(p: string): string { + return p.split(path.sep).join('/'); +} + +/** Canonical app root: realpath so relative paths match resolveWithinRoot's realpathed output. */ +function canonicalDir(dir: string): string { + return fs.realpathSync(path.resolve(dir)); +} + +/** A NUL byte in the first chunk reliably indicates non-text content. */ +function isBinary(buffer: Buffer): boolean { + return buffer.subarray(0, 8000).includes(0); +} diff --git a/packages/@aws-cdk/cdk-explorer/lib/web/safe-path.ts b/packages/@aws-cdk/cdk-explorer/lib/web/safe-path.ts new file mode 100644 index 000000000..5842e3a3d --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/lib/web/safe-path.ts @@ -0,0 +1,40 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * Resolve a client-supplied, root-relative path to an absolute path guaranteed + * to stay inside `root`. Returns `undefined` when the request escapes the root + * (via `..` or a symlink pointing outside), which callers must treat as a 403. + * + * `root` is expected to be absolute. A leading `/` on `requested` is stripped so + * absolute-looking inputs cannot jump to the filesystem root. + */ +export function resolveWithinRoot(root: string, requested: string): string | undefined { + const realRoot = realOrSelf(path.resolve(root)); + const relative = requested.replace(/^[/\\]+/, ''); + const resolved = path.resolve(realRoot, relative); + + if (!isInside(realRoot, resolved)) { + return undefined; + } + // Follow symlinks on the target: an existing file reached through a symlinked + // directory must still land inside the root. A non-existent target resolves to + // itself and stays caught by the lexical check above (and the caller 404s it). + if (!isInside(realRoot, realOrSelf(resolved))) { + return undefined; + } + return resolved; +} + +function isInside(root: string, candidate: string): boolean { + return candidate === root || candidate.startsWith(root + path.sep); +} + +/** Real path with symlinks resolved, or the input unchanged if it does not exist. */ +function realOrSelf(p: string): string { + try { + return fs.realpathSync(p); + } catch { + return p; + } +} diff --git a/packages/@aws-cdk/cdk-explorer/lib/web/server.ts b/packages/@aws-cdk/cdk-explorer/lib/web/server.ts new file mode 100644 index 000000000..bfe9d3bc2 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/lib/web/server.ts @@ -0,0 +1,223 @@ +import * as fs from 'fs'; +import * as http from 'http'; +import * as path from 'path'; +import { MANIFEST_FILE } from '@aws-cdk/cloud-assembly-api'; +import { Toolkit, NonInteractiveIoHost } from '@aws-cdk/toolkit-lib'; +// eslint-disable-next-line @typescript-eslint/no-require-imports +import express = require('express'); +import { SseBroadcaster } from './events'; +import { ASSEMBLY_CHANGED, SOURCE_CHANGED } from './protocol'; +import { registerApi } from './routes'; +import { StalenessTracker } from './staleness'; +import { indexHtml, webAsset } from './web-assets'; +import { toolkitAssemblyLock } from '../core/assembly-lock'; +import { + startAssemblyWatcher as defaultStartAssemblyWatcher, + type AssemblyWatcher, + type AssemblyWatcherOptions, +} from '../core/assembly-watcher'; +import { + startSourceWatcher as defaultStartSourceWatcher, + type SourceWatcher, + type SourceWatcherOptions, +} from '../core/source-watcher'; + +export const DEFAULT_PORT = 4200; +const MAX_PORT_ATTEMPTS = 100; +const HOST = 'localhost'; + +export interface WebServerOptions { + readonly port?: number; + /** + * Root of the CDK app. File listing/reading is confined here. Defaults to + * `process.cwd()`. + */ + readonly appDir?: string; + /** + * Cloud assembly directory to read the construct tree and violations from. + * Defaults to `/cdk.out`. + */ + readonly assemblyDir?: string; + /** + * Starts the cdk.out watcher. Defaults to the real chokidar-backed watcher; + * overridden in tests with a fake to drive change events deterministically. + */ + readonly startAssemblyWatcher?: (options: AssemblyWatcherOptions) => AssemblyWatcher; + /** + * Starts the source-tree watcher that drives live staleness. Defaults to the + * real chokidar-backed watcher; overridden in tests with a fake. + */ + readonly startSourceWatcher?: (options: SourceWatcherOptions) => SourceWatcher; + /** + * Reports a non-fatal watcher error (live refresh stops updating). Defaults to + * writing to stderr; the CLI command passes a sink that routes to its IoHost. + */ + readonly onWatcherError?: (err: unknown) => void; +} + +export interface WebServer { + readonly url: string; + stop(): Promise; +} + +/** + * Starts the CDK Explorer web server. + * + * If no port is specified, auto-increments from the default until one is available. + * If a port is explicitly specified and unavailable, throws. + * + * @returns A handle to the running server with its URL and a stop function. + */ +export async function startWebServer(options: WebServerOptions = {}): Promise { + const appDir = options.appDir ?? process.cwd(); + // Single owner of where the cloud assembly lives: the same resolved path feeds + // both the read endpoints and the change watcher, so the two never disagree. + const assemblyDir = options.assemblyDir ?? path.join(appDir, 'cdk.out'); + + // mtime of the assembly manifest (undefined when none exists yet). Used as the + // staleness fallback reference (synth-finish time) when no synth-start lock was + // observed for a generation. + const manifestMtimeMs = (): number | undefined => { + try { + return fs.statSync(path.join(assemblyDir, MANIFEST_FILE)).mtimeMs; + } catch { + return undefined; + } + }; + + const app = express(); + + // The Toolkit provides the assembly read lock (via fromAssemblyDirectory(). + // produce()); a non-interactive IoHost is fine here since stdout/stderr are + // free in the web process, unlike the LSP's stdio channel. + const toolkit = new Toolkit({ ioHost: new NonInteractiveIoHost() }); + // Owns source-file staleness: the assembly watcher advances its per-generation + // reference (see onChange below) and feeds it synth-start activity, and + // /api/file reads it. + const staleness = new StalenessTracker(); + // Anchor to the assembly present at startup. The watcher uses ignoreInitial so + // it never fires for an already-synthesized assembly; without this, a file + // edited before the server started would not read as stale until the next + // synth. No synth-start was observed, so this uses the manifest-mtime fallback. + const initialManifestMtime = manifestMtimeMs(); + if (initialManifestMtime !== undefined) staleness.onAssemblyRefreshed(initialManifestMtime); + registerApi(app, { + appDir, + assemblyDir, + acquireAssemblyLock: toolkitAssemblyLock(toolkit), + staleness, + }); + + // Live-refresh stream: browsers subscribe here and re-fetch when the assembly + // changes. Registered before the /api catch-all so it is not treated as unknown. + const events = new SseBroadcaster(); + app.get('/api/events', events.handle.bind(events)); + + // Unknown /api routes must return JSON 404, not fall through to the SPA. + app.use('/api', (_req, res) => res.status(404).json({ error: 'unknown endpoint' })); + + // Serve the SPA from the embedded bundle (survives CLI bundling). Named assets + // by path; any other GET falls back to index.html for client-side routing. + // The bundle filename is unversioned, so disable caching to ensure a rebuilt + // explorer is always picked up on reload rather than served stale by the browser. + app.get('/:asset', (req, res, next) => { + const asset = webAsset(req.params.asset); + if (!asset) return next(); + res.set('Cache-Control', 'no-store'); + return res.type(asset.contentType).send(asset.body); + }); + app.get('*', (_req, res) => { + const index = indexHtml(); + res.set('Cache-Control', 'no-store'); + res.type(index.contentType).send(index.body); + }); + + const server = http.createServer(app); + + const port = options.port !== undefined + ? await listenOnPort(server, options.port, HOST) + : await listenWithPortSearch(server, DEFAULT_PORT, HOST); + + // Start watching only after the server is listening, so a failed bind does not + // leave a watcher running. Any synth that rewrites cdk.out (an external + // `cdk synth`/`cdk watch`, or a future in-process synth) wakes every browser. + const startWatcher = options.startAssemblyWatcher ?? defaultStartAssemblyWatcher; + const watcher = startWatcher({ + assemblyDir, + onChange: () => { + // Advance the staleness reference to this new generation before waking + // browsers, so the /api/file they re-fetch reads the current reference. + // The watcher is the single owner of the reference: it observes both the + // synth-start lock (onSynthActivity) and the generation change here. + const mtime = manifestMtimeMs(); + if (mtime !== undefined) staleness.onAssemblyRefreshed(mtime); + events.broadcast(ASSEMBLY_CHANGED); + }, + onSynthActivity: (atMs) => staleness.noteSynthActivity(atMs), + onError: options.onWatcherError ?? ((err) => + process.stderr.write(`assembly watcher error: ${err instanceof Error ? err.message : String(err)}\n`)), + }); + + // Watch the app's source tree so an edit re-checks the open file's staleness + // (and refreshes its content) immediately, without waiting for the next synth. + const startSource = options.startSourceWatcher ?? defaultStartSourceWatcher; + const sourceWatcher = startSource({ + appDir, + onChange: () => events.broadcast(SOURCE_CHANGED), + onError: options.onWatcherError ?? ((err) => + process.stderr.write(`source watcher error: ${err instanceof Error ? err.message : String(err)}\n`)), + }); + + let stopped = false; + return { + url: `http://${HOST}:${port}`, + stop: async () => { + if (stopped) return; + stopped = true; + await watcher.close(); + await sourceWatcher.close(); + events.close(); + await new Promise((resolve) => { + server.close(() => resolve()); + server.closeAllConnections(); + }); + }, + }; +} + +async function listenOnPort( + server: http.Server, + port: number, + host: string, +): Promise { + await new Promise((resolve, reject) => { + server.once('error', reject); + server.listen(port, host, () => { + server.removeListener('error', reject); + resolve(); + }); + }); + return port; +} + +async function listenWithPortSearch( + server: http.Server, + startPort: number, + host: string, +): Promise { + for (let port = startPort; port < startPort + MAX_PORT_ATTEMPTS; port++) { + try { + await new Promise((resolve, reject) => { + server.once('error', reject); + server.listen(port, host, () => { + server.removeListener('error', reject); + resolve(); + }); + }); + return port; + } catch (err: unknown) { + if ((err as NodeJS.ErrnoException).code !== 'EADDRINUSE') throw err; + } + } + throw new Error(`No available port found in range ${startPort}-${startPort + MAX_PORT_ATTEMPTS - 1}`); +} diff --git a/packages/@aws-cdk/cdk-explorer/lib/web/severity.ts b/packages/@aws-cdk/cdk-explorer/lib/web/severity.ts new file mode 100644 index 000000000..678b0e9ce --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/lib/web/severity.ts @@ -0,0 +1,67 @@ +/** + * Severity vocabulary for CDK policy violations, shared by the server (ranking + * violations onto construct-tree nodes) and the SPA (sorting and coloring the + * violations panel) so the rules live in exactly one place. + */ + +import type { WebViolation, WebViolationSeverity } from './protocol'; + +/** Severities CDK policy validation reports natively. */ +const KNOWN_SEVERITIES = new Set(['fatal', 'error', 'warning', 'info', 'custom']); + +/** + * Coerce a report's severity into the wire model. aws-cdk-lib already emits standard + * levels as-is and non-standard ones as `severity: 'custom'` with the raw label in + * `customSeverity`. Since we read the report as untyped JSON, this also folds any + * unrecognized `severity` string into `'custom'` (keeping the raw value as its label), + * so the result can never be a value outside {@link WebViolationSeverity}. + */ +export function classifyReportSeverity( + severity: string | undefined, + customSeverity: string | undefined, +): { readonly severity?: WebViolationSeverity; readonly customSeverity?: string } { + if (severity !== undefined && !KNOWN_SEVERITIES.has(severity)) { + return { severity: 'custom', customSeverity: customSeverity ?? severity }; + } + return { severity: severity as WebViolationSeverity | undefined, customSeverity }; +} + +/** Lower-cased severity so 'Error'/'ERROR'/'error' all match (mirroring cdk validate). */ +function normalize(severity: string): string { + return severity.toLowerCase(); +} + +/** + * Severity label for display: a plugin's custom label, else the reported + * severity, else 'warning' (cdk validate's default for severity-less plugins + * like CfnGuard). + */ +export function displaySeverity(violation: Pick): string { + return violation.customSeverity ?? violation.severity ?? 'warning'; +} + +const SEVERITY_RANK: Record = { fatal: 0, error: 1, warning: 2, info: 3 }; + +/** + * Sort rank for a severity label; lower sorts first. Custom and unrecognized + * labels rank just after `info`, matching cdk validate's handling of unknown + * severities. + */ +export function severityRank(severity: string): number { + return SEVERITY_RANK[normalize(severity)] ?? 4; +} + +const SEVERITY_HEX: Record = { + fatal: '#d91515', + error: '#e07700', + warning: '#8d6605', + info: '#0972d3', +}; + +/** + * Text color for a severity label, mirroring cdk validate (fatal red, error + * orange, warning amber, info blue); custom and unrecognized labels render gray. + */ +export function severityHexColor(severity: string): string { + return SEVERITY_HEX[normalize(severity)] ?? '#5f6b7a'; +} diff --git a/packages/@aws-cdk/cdk-explorer/lib/web/source-nav.ts b/packages/@aws-cdk/cdk-explorer/lib/web/source-nav.ts new file mode 100644 index 000000000..e1d45e91d --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/lib/web/source-nav.ts @@ -0,0 +1,87 @@ +import type { WebConstructNode } from './protocol'; + +/** + * A construct anchored to a line in a source file, paired with the template + * coordinates needed to navigate to its resource. Produced by + * {@link buildSourceAnchorIndex} and consumed by {@link findConstructAtLine}. + */ +export interface SourceAnchor { + /** The construct's source line (its creation line). */ + readonly line: number; + /** The construct that owns this line. */ + readonly node: WebConstructNode; +} + +/** + * Build a per-file index of the constructs that can be navigated from source to + * template, each file's anchors sorted by line ascending. + * + * Only constructs carrying both a source location and template coordinates + * (`templateFile` + `logicalId`) are indexed, since those are exactly the ones + * a source-to-template navigation can resolve to a template resource. The tree + * is walked depth-first; ties on the same line keep tree order. + */ +export function buildSourceAnchorIndex(tree: readonly WebConstructNode[]): Map { + const byFile = new Map(); + + const walk = (nodes: readonly WebConstructNode[]): void => { + for (const node of nodes) { + const loc = node.sourceLocation; + if (loc && node.templateFile && node.logicalId) { + const anchors = byFile.get(loc.file); + if (anchors) { + anchors.push({ line: loc.line, node }); + } else { + byFile.set(loc.file, [{ line: loc.line, node }]); + } + } + walk(node.children); + } + }; + walk(tree); + + for (const anchors of byFile.values()) { + anchors.sort((a, b) => a.line - b.line); + } + return byFile; +} + +/** + * Resolve which construct a source line belongs to: the one whose definition is + * nearest at or above `line`. Source anchors are single lines (a construct's + * creation line), not ranges, so a construct is treated as owning every line + * from its definition down to the next construct's. Returns `undefined` when + * `line` sits above the first construct in the file, or when there are no + * anchors. + * + * When several constructs share the nearest line (a parent and its synthesized + * children are all anchored to the single `new Xyz(...)` line), the top-most one + * wins, since that is the construct the user actually authored on that line. The + * depth-first pre-order of {@link buildSourceAnchorIndex} places it first, so on + * a tie we keep the earliest match rather than overwriting it. + * + * `anchors` must be sorted by line ascending, as produced by + * {@link buildSourceAnchorIndex}. + */ +export function findConstructAtLine( + anchors: readonly SourceAnchor[] | undefined, + line: number, +): WebConstructNode | undefined { + if (!anchors) { + return undefined; + } + let match: WebConstructNode | undefined; + let matchLine = -Infinity; + for (const anchor of anchors) { + if (anchor.line > line) { + break; + } + // Anchors are line-ascending, so a strictly greater line is a nearer owner; + // an equal line is a same-line child, which must not displace its parent. + if (anchor.line > matchLine) { + match = anchor.node; + matchLine = anchor.line; + } + } + return match; +} diff --git a/packages/@aws-cdk/cdk-explorer/lib/web/staleness.ts b/packages/@aws-cdk/cdk-explorer/lib/web/staleness.ts new file mode 100644 index 000000000..425cae770 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/lib/web/staleness.ts @@ -0,0 +1,45 @@ +/** + * Tracks whether an open source file is out of date relative to the cloud + * assembly the explorer is currently showing. + * + * A file is stale when it was modified after the synth that produced the current + * assembly *started*, not merely after it finished. An edit made while a synth + * was in flight may not have been picked up by that synth, so its squiggles and + * navigation anchors can already be wrong; treating "modified after synth start" + * as stale catches that race. + * + * The reference timestamp for a generation is the moment synth write-lock + * activity (`synth.lock`) was first observed since the last refresh. If none was + * observed (the synth predated the server, or the lock was too brief to catch), + * it falls back to the assembly manifest's mtime, i.e. synth-finish time. This + * is a best-effort heuristic: at worst a squiggle sits on the wrong line until + * the next synth, which is acceptable. + */ +export class StalenessTracker { + /** Reference for the loaded generation; undefined until the first assembly read. */ + private reference: number | undefined; + + /** Earliest synth-activity timestamp seen since the last assembly refresh. */ + private synthActivitySince: number | undefined; + + /** Record synth write-lock activity (a synth started). Earliest since last refresh wins. */ + public noteSynthActivity(atMs: number): void { + if (this.synthActivitySince === undefined) { + this.synthActivitySince = atMs; + } + } + + /** + * Freeze the staleness reference for a newly loaded assembly generation. + * Prefers the observed synth start; falls back to the manifest mtime. + */ + public onAssemblyRefreshed(manifestMtimeMs: number): void { + this.reference = this.synthActivitySince ?? manifestMtimeMs; + this.synthActivitySince = undefined; + } + + /** True when a file last modified at `fileMtimeMs` is newer than the current reference. */ + public isStale(fileMtimeMs: number): boolean { + return this.reference !== undefined && fileMtimeMs > this.reference; + } +} diff --git a/packages/@aws-cdk/cdk-explorer/lib/web/web-assets.ts b/packages/@aws-cdk/cdk-explorer/lib/web/web-assets.ts new file mode 100644 index 000000000..b0815e880 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/lib/web/web-assets.ts @@ -0,0 +1,34 @@ +/** + * Serves the web explorer SPA from memory. The bytes come from + * web-assets.generated.json (written by build-tools/bundle-frontend.ts); the + * static `require` is what pulls them into the published CLI bundle. The build + * always generates this file, so its absence is a build error, not a runtime + * condition we handle (same convention as the CLI's build-info.json). + */ +export interface WebAsset { + readonly contentType: string; + readonly body: string; +} + +const CONTENT_TYPES: Record = { + 'index.html': 'text/html; charset=utf-8', + 'bundle.js': 'text/javascript; charset=utf-8', + 'bundle.css': 'text/css; charset=utf-8', +}; + +// eslint-disable-next-line @typescript-eslint/no-require-imports +const raw = require('./web-assets.generated.json') as Record; + +const WEB_ASSETS: Record = Object.fromEntries( + Object.entries(raw).map(([name, body]) => [name, { contentType: CONTENT_TYPES[name], body }]), +); + +/** The SPA entry document. */ +export function indexHtml(): WebAsset { + return WEB_ASSETS['index.html']; +} + +/** A named SPA asset (e.g. "bundle.js"), or undefined if not part of the build. */ +export function webAsset(name: string): WebAsset | undefined { + return WEB_ASSETS[name]; +} diff --git a/packages/@aws-cdk/cdk-explorer/package.json b/packages/@aws-cdk/cdk-explorer/package.json index d726a92e0..461a613ee 100644 --- a/packages/@aws-cdk/cdk-explorer/package.json +++ b/packages/@aws-cdk/cdk-explorer/package.json @@ -31,13 +31,21 @@ }, "devDependencies": { "@cdklabs/eslint-plugin": "^2.0.11", + "@cloudscape-design/components": "^3", + "@cloudscape-design/global-styles": "^1", "@stylistic/eslint-plugin": "^3", "@types/convert-source-map": "^2", + "@types/express": "^4", "@types/jest": "^29.5.14", "@types/node": "^20", + "@types/prismjs": "^1", + "@types/react": "^18", + "@types/react-dom": "^18", + "@types/supertest": "^6", "@typescript-eslint/eslint-plugin": "^8", "@typescript-eslint/parser": "^8", "constructs": "^10.0.0", + "esbuild": "^0.28.1", "eslint": "^9", "eslint-config-prettier": "^10.1.8", "eslint-import-resolver-typescript": "^4.4.5", @@ -49,10 +57,16 @@ "jest-junit": "^16", "nx": "^22.7.6", "prettier": "^2.8", + "prismjs": "^1", "projen": "^0.100.7", + "react": "^18", + "react-dom": "^18", + "supertest": "^6", "ts-jest": "^29.4.11", + "tsx": "^4.22.4", "typescript": "5.9", - "vscode-languageserver-protocol": "^3" + "vscode-languageserver-protocol": "^3", + "yaml": "^2" }, "dependencies": { "@aws-cdk/cloud-assembly-api": "^0.0.0", diff --git a/packages/@aws-cdk/cdk-explorer/test/core/assembly-watcher.test.ts b/packages/@aws-cdk/cdk-explorer/test/core/assembly-watcher.test.ts index 89b414f54..070ad8f66 100644 --- a/packages/@aws-cdk/cdk-explorer/test/core/assembly-watcher.test.ts +++ b/packages/@aws-cdk/cdk-explorer/test/core/assembly-watcher.test.ts @@ -36,12 +36,16 @@ class FakeWatcher implements FileWatcher { function setup() { const fake = new FakeWatcher(); const onChange = jest.fn(); + const onError = jest.fn(); + const onSynthActivity = jest.fn(); const watcher = startAssemblyWatcher({ assemblyDir: '/p/cdk.out', onChange, + onError, + onSynthActivity, createWatcher: () => fake, }); - return { fake, onChange, watcher }; + return { fake, onChange, onError, onSynthActivity, watcher }; } describe('Assembly Watcher', () => { @@ -71,6 +75,27 @@ describe('Assembly Watcher', () => { expect(onChange).not.toHaveBeenCalled(); }); + test('reports the write lock (synth.lock) appearing as synth activity', () => { + const { fake, onChange, onSynthActivity } = setup(); + + fake.emitFile('add', '/p/cdk.out/synth.lock'); + + expect(onSynthActivity).toHaveBeenCalledTimes(1); + expect(onSynthActivity).toHaveBeenCalledWith(expect.any(Number)); + // The write lock is never an assembly refresh. + jest.advanceTimersByTime(500); + expect(onChange).not.toHaveBeenCalled(); + }); + + test('does not report synth activity for a read lock or a lock removal', () => { + const { fake, onSynthActivity } = setup(); + + fake.emitFile('add', '/p/cdk.out/read.12345.1.lock'); + fake.emitFile('unlink', '/p/cdk.out/synth.lock'); + + expect(onSynthActivity).not.toHaveBeenCalled(); + }); + test('ignores non-signal files such as templates', () => { const { fake, onChange } = setup(); diff --git a/packages/@aws-cdk/cdk-explorer/test/core/source-watcher.test.ts b/packages/@aws-cdk/cdk-explorer/test/core/source-watcher.test.ts new file mode 100644 index 000000000..964830c15 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/test/core/source-watcher.test.ts @@ -0,0 +1,109 @@ +import type { FileWatcher } from '../../lib/core/assembly-watcher'; +import { startSourceWatcher } from '../../lib/core/source-watcher'; + +type AnyListener = (...args: unknown[]) => void; + +/** A controllable in-memory stand-in for chokidar's FSWatcher. */ +class FakeWatcher implements FileWatcher { + public closed = false; + private readonly listeners: Record = {}; + + public on(event: string, listener: AnyListener): FileWatcher { + (this.listeners[event] ??= []).push(listener); + return this; + } + + public emitFile(eventName: string, filePath: string): void { + for (const listener of this.listeners.all ?? []) { + listener(eventName, filePath); + } + } + + public emit(event: string, ...args: unknown[]): void { + for (const listener of this.listeners[event] ?? []) { + listener(...args); + } + } + + public async close(): Promise { + this.closed = true; + } +} + +function setup() { + const fake = new FakeWatcher(); + const onChange = jest.fn(); + const onError = jest.fn(); + const watcher = startSourceWatcher({ + appDir: '/proj', + onChange, + onError, + createWatcher: () => fake, + }); + return { fake, onChange, onError, watcher }; +} + +describe('Source Watcher', () => { + beforeEach(() => jest.useFakeTimers()); + afterEach(() => jest.useRealTimers()); + + test('fires a single debounced onChange for a burst of source edits', () => { + const { fake, onChange } = setup(); + + fake.emitFile('change', '/proj/lib/stack.ts'); + fake.emitFile('change', '/proj/app.ts'); + expect(onChange).not.toHaveBeenCalled(); + + jest.advanceTimersByTime(200); + expect(onChange).toHaveBeenCalledTimes(1); + }); + + test('ignores dependencies, synth output, and dotfiles', () => { + const { fake, onChange } = setup(); + + fake.emitFile('change', '/proj/node_modules/pkg/index.js'); + fake.emitFile('change', '/proj/cdk.out/tree.json'); + fake.emitFile('change', '/proj/.git/HEAD'); + + jest.advanceTimersByTime(500); + expect(onChange).not.toHaveBeenCalled(); + }); + + test('close stops a pending onChange and closes the underlying watcher', async () => { + const { fake, onChange, watcher } = setup(); + + fake.emitFile('change', '/proj/lib/stack.ts'); + await watcher.close(); + + jest.advanceTimersByTime(500); + expect(onChange).not.toHaveBeenCalled(); + expect(fake.closed).toBe(true); + }); + + test('forwards watcher errors to onError', () => { + const { fake, onError } = setup(); + + fake.emit('error', new Error('boom')); + + expect(onError).toHaveBeenCalledWith(expect.any(Error)); + }); + + test('forwards an onChange throw to onError instead of leaking from the timer', () => { + const fake = new FakeWatcher(); + const failure = new Error('handler blew up'); + const onError = jest.fn(); + startSourceWatcher({ + appDir: '/proj', + onChange: () => { + throw failure; + }, + onError, + createWatcher: () => fake, + }); + + fake.emitFile('change', '/proj/lib/stack.ts'); + expect(() => jest.advanceTimersByTime(200)).not.toThrow(); + + expect(onError).toHaveBeenCalledWith(failure); + }); +}); diff --git a/packages/@aws-cdk/cdk-explorer/test/web/events.test.ts b/packages/@aws-cdk/cdk-explorer/test/web/events.test.ts new file mode 100644 index 000000000..869a07f34 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/test/web/events.test.ts @@ -0,0 +1,106 @@ +import type { Request, Response } from 'express'; +import { SseBroadcaster } from '../../lib/web/events'; +import { ASSEMBLY_CHANGED } from '../../lib/web/protocol'; + +/** + * Minimal Request/Response doubles that capture writes and expose the close and + * error handlers the broadcaster registers, so behavior is verified without a + * real socket. + */ +function fakeClient() { + const writes: string[] = []; + let ended = false; + const reqHandlers: Record void> = {}; + const resHandlers: Record void> = {}; + + const req = { + on: (event: string, handler: () => void) => { + reqHandlers[event] = handler; + return req; + }, + } as unknown as Request; + + const res = { + set: () => res, + flushHeaders: () => undefined, + write: (chunk: string) => { + writes.push(chunk); + return true; + }, + end: () => { + ended = true; + }, + on: (event: string, handler: (err?: unknown) => void) => { + resHandlers[event] = handler; + return res; + }, + } as unknown as Response; + + return { + req, + res, + writes, + isEnded: () => ended, + disconnect: () => reqHandlers.close?.(), + fail: () => resHandlers.error?.(new Error('broken pipe')), + }; +} + +const FRAME = `event: ${ASSEMBLY_CHANGED}\ndata: {}\n\n`; + +describe('SseBroadcaster', () => { + test('broadcasts a named, payload-free frame to every connected client', () => { + const broadcaster = new SseBroadcaster(); + const a = fakeClient(); + const b = fakeClient(); + broadcaster.handle(a.req, a.res); + broadcaster.handle(b.req, b.res); + + broadcaster.broadcast(ASSEMBLY_CHANGED); + + expect(a.writes).toEqual([FRAME]); + expect(b.writes).toEqual([FRAME]); + }); + + test('stops writing to a client after it disconnects', () => { + const broadcaster = new SseBroadcaster(); + const gone = fakeClient(); + const live = fakeClient(); + broadcaster.handle(gone.req, gone.res); + broadcaster.handle(live.req, live.res); + + gone.disconnect(); + broadcaster.broadcast(ASSEMBLY_CHANGED); + + expect(gone.writes).toEqual([]); + expect(live.writes).toEqual([FRAME]); + }); + + test('drops a client whose socket errors so a broken pipe is not written again', () => { + const broadcaster = new SseBroadcaster(); + const a = fakeClient(); + broadcaster.handle(a.req, a.res); + + a.fail(); + broadcaster.broadcast(ASSEMBLY_CHANGED); + + expect(a.writes).toEqual([]); + }); + + test('close ends every open stream and reaches no one afterwards', () => { + const broadcaster = new SseBroadcaster(); + const a = fakeClient(); + const b = fakeClient(); + broadcaster.handle(a.req, a.res); + broadcaster.handle(b.req, b.res); + + broadcaster.close(); + + expect(a.isEnded()).toBe(true); + expect(b.isEnded()).toBe(true); + + broadcaster.broadcast(ASSEMBLY_CHANGED); + expect(a.writes).toEqual([]); + expect(b.writes).toEqual([]); + }); +}); diff --git a/packages/@aws-cdk/cdk-explorer/test/web/normalize-violations.test.ts b/packages/@aws-cdk/cdk-explorer/test/web/normalize-violations.test.ts new file mode 100644 index 000000000..e529537da --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/test/web/normalize-violations.test.ts @@ -0,0 +1,127 @@ +import { ConstructIndex } from '@aws-cdk/cloud-assembly-api'; +import type { PolicyValidationReportJson } from '@aws-cdk/cloud-assembly-schema'; +import type { ConstructNode } from '../../lib/core/assembly-reader'; +import { normalizeViolations } from '../../lib/web/routes'; + +const ASSEMBLY_DIR = '/abs/cdk.out'; +const APP_DIR = '/abs/app'; + +function makeNode(props: { + path: string; + id: string; + type?: string; + logicalId?: string; + templateFile?: string; + sourceLocation?: { file: string; line: number; column: number }; + children?: ConstructNode[]; +}): ConstructNode { + return { ...props, children: props.children ?? [] }; +} + +function report(violatingConstructs: PolicyValidationReportJson['pluginReports'][number]['violations'][number]['violatingConstructs']): PolicyValidationReportJson { + return { + version: '1.0', + pluginReports: [ + { + pluginName: 'cdk-validator', + conclusion: 'failure', + violations: [ + { + ruleName: 'no-public-access', + description: 'S3 bucket should not allow public access', + severity: 'error', + suggestedFix: 'set blockPublicAccess', + violatingConstructs, + }, + ], + }, + ], + }; +} + +describe('normalizeViolations', () => { + test('prefers resolved tree data over the report when the construct is in the index', () => { + const index = ConstructIndex.fromTree([ + makeNode({ + path: 'MyStack/Bucket', + id: 'Bucket', + type: 'AWS::S3::Bucket', + logicalId: 'Bucket123', + templateFile: `${ASSEMBLY_DIR}/MyStack.template.json`, + sourceLocation: { file: `${APP_DIR}/lib/stack.ts`, line: 12, column: 5 }, + }), + ]); + const r = report([ + { + constructPath: 'MyStack/Bucket', + cloudFormationResource: { templatePath: 'IGNORED.json', logicalId: 'IGNORED', propertyPaths: ['Properties.PublicAccessBlock'] }, + }, + ]); + + const out = normalizeViolations(r, index, ASSEMBLY_DIR, APP_DIR); + + expect(out).toHaveLength(1); + expect(out[0]).toMatchObject({ + ruleName: 'no-public-access', + description: 'S3 bucket should not allow public access', + severity: 'error', + source: 'cdk-validator', + suggestedFix: 'set blockPublicAccess', + }); + expect(out[0].occurrences[0]).toEqual({ + constructPath: 'MyStack/Bucket', + logicalId: 'Bucket123', + templateFile: 'MyStack.template.json', + sourceLocation: { file: 'lib/stack.ts', line: 12, column: 5 }, + propertyPaths: ['Properties.PublicAccessBlock'], + }); + }); + + test('falls back to the report resource fields when the construct is not in the index', () => { + const index = ConstructIndex.fromTree([]); + const r = report([ + { + constructPath: 'Other/X', + cloudFormationResource: { templatePath: 'Other.template.json', logicalId: 'X9', propertyPaths: ['P'] }, + }, + ]); + + const occ = normalizeViolations(r, index, ASSEMBLY_DIR, APP_DIR)[0].occurrences[0]; + + expect(occ.constructPath).toBe('Other/X'); + expect(occ.logicalId).toBe('X9'); + expect(occ.templateFile).toBe('Other.template.json'); + expect(occ.sourceLocation).toBeUndefined(); + expect(occ.propertyPaths).toEqual(['P']); + }); + + test('leaves resource fields undefined when neither the index nor the report has them', () => { + const index = ConstructIndex.fromTree([]); + const occ = normalizeViolations(report([{ constructPath: 'Ghost' }]), index, ASSEMBLY_DIR, APP_DIR)[0].occurrences[0]; + expect(occ.logicalId).toBeUndefined(); + expect(occ.templateFile).toBeUndefined(); + expect(occ.sourceLocation).toBeUndefined(); + expect(occ.propertyPaths).toBeUndefined(); + }); + + test('flattens violations across plugins', () => { + const index = ConstructIndex.fromTree([]); + const r: PolicyValidationReportJson = { + version: '1.0', + pluginReports: [ + { pluginName: 'a', conclusion: 'failure', violations: [{ ruleName: 'r1', description: 'd1', severity: 'warning', violatingConstructs: [] }] }, + { pluginName: 'b', conclusion: 'failure', violations: [{ ruleName: 'r2', description: 'd2', severity: 'fatal', customSeverity: 'BLOCKER', violatingConstructs: [] }] }, + ], + }; + + const out = normalizeViolations(r, index, ASSEMBLY_DIR, APP_DIR); + + expect(out).toHaveLength(2); + expect(out.map((v) => v.source)).toEqual(['a', 'b']); + expect(out[1]).toMatchObject({ severity: 'fatal', customSeverity: 'BLOCKER' }); + }); + + test('returns an empty list for an undefined report', () => { + expect(normalizeViolations(undefined, ConstructIndex.fromTree([]), ASSEMBLY_DIR, APP_DIR)).toEqual([]); + }); +}); diff --git a/packages/@aws-cdk/cdk-explorer/test/web/routes.test.ts b/packages/@aws-cdk/cdk-explorer/test/web/routes.test.ts new file mode 100644 index 000000000..1d85b5bf4 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/test/web/routes.test.ts @@ -0,0 +1,429 @@ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import type { PolicyValidationReportJson } from '@aws-cdk/cloud-assembly-schema'; +import { LockError } from '@aws-cdk/toolkit-lib'; +// eslint-disable-next-line @typescript-eslint/no-require-imports +import express = require('express'); +// eslint-disable-next-line @typescript-eslint/no-require-imports +import request = require('supertest'); +import type { AssemblyReadResult, ConstructNode } from '../../lib/core/assembly-reader'; +import { createApiRouter } from '../../lib/web/routes'; +import { StalenessTracker } from '../../lib/web/staleness'; + +let appDir: string; +let app: express.Express; + +/** No-op assembly lock; these tests inject readAssembly directly, so no real lock is exercised. */ +const noopAssemblyLock = async () => ({ release: () => Promise.resolve() }); + +/** Writes a minimal manifest so the router's mtime check reaches the injected readAssembly. */ +function writeManifest(dir: string): void { + fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync(path.join(dir, 'manifest.json'), '{"version":"36.0.0"}'); +} + +beforeEach(() => { + appDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cdk-explorer-routes-')); + fs.writeFileSync(path.join(appDir, 'app.ts'), 'export const x = 1;\n'); + fs.mkdirSync(path.join(appDir, 'lib')); + fs.writeFileSync(path.join(appDir, 'lib', 'stack.ts'), 'class Stack {}\n'); + + app = express(); + app.use('/api', createApiRouter({ appDir, acquireAssemblyLock: noopAssemblyLock })); +}); + +afterEach(() => { + fs.rmSync(appDir, { recursive: true, force: true }); +}); + +describe('GET /api/health', () => { + test('returns ok', async () => { + const res = await request(app).get('/api/health'); + expect(res.status).toBe(200); + expect(res.body).toEqual({ status: 'ok' }); + }); +}); + +describe('GET /api/file', () => { + test('returns file content', async () => { + const res = await request(app).get('/api/file').query({ path: 'app.ts' }); + expect(res.status).toBe(200); + expect(res.body).toEqual({ path: 'app.ts', content: 'export const x = 1;\n', stale: false }); + }); + + test('flags a file modified after the staleness reference as stale', async () => { + const staleness = new StalenessTracker(); + // Reference well before the fixture file was written, so it reads as stale. + staleness.onAssemblyRefreshed(0); + const a = express(); + a.use('/api', createApiRouter({ appDir, acquireAssemblyLock: noopAssemblyLock, staleness })); + + const res = await request(a).get('/api/file').query({ path: 'app.ts' }); + expect(res.status).toBe(200); + expect(res.body.stale).toBe(true); + }); + + test('does not flag a file older than the staleness reference', async () => { + const staleness = new StalenessTracker(); + // Reference far in the future, so no file can be newer than it. + staleness.onAssemblyRefreshed(Date.now() + 60_000); + const a = express(); + a.use('/api', createApiRouter({ appDir, acquireAssemblyLock: noopAssemblyLock, staleness })); + + const res = await request(a).get('/api/file').query({ path: 'app.ts' }); + expect(res.status).toBe(200); + expect(res.body.stale).toBe(false); + }); + + test('requires a path parameter', async () => { + const res = await request(app).get('/api/file'); + expect(res.status).toBe(400); + }); + + test('rejects traversal with 403', async () => { + const res = await request(app).get('/api/file').query({ path: '../../../etc/passwd' }); + expect(res.status).toBe(403); + }); + + test('returns 404 for a missing file', async () => { + const res = await request(app).get('/api/file').query({ path: 'missing.ts' }); + expect(res.status).toBe(404); + }); + + test('returns 400 when the path is a directory', async () => { + const res = await request(app).get('/api/file').query({ path: 'lib' }); + expect(res.status).toBe(400); + }); + + test('rejects binary files with 415', async () => { + fs.writeFileSync(path.join(appDir, 'bin.dat'), Buffer.from([0x00, 0x01, 0x02, 0x03])); + const res = await request(app).get('/api/file').query({ path: 'bin.dat' }); + expect(res.status).toBe(415); + }); +}); + +describe('symlink containment', () => { + test('rejects a symlink inside appDir that points outside with 403', async () => { + const outside = fs.mkdtempSync(path.join(os.tmpdir(), 'cdk-explorer-outside-')); + fs.writeFileSync(path.join(outside, 'secret.txt'), 'top secret'); + try { + try { + fs.symlinkSync(path.join(outside, 'secret.txt'), path.join(appDir, 'link.txt')); + } catch { + return; // symlinks not permitted in this environment; skip + } + const res = await request(app).get('/api/file').query({ path: 'link.txt' }); + expect(res.status).toBe(403); + } finally { + fs.rmSync(outside, { recursive: true, force: true }); + } + }); +}); + +describe('GET /api/tree', () => { + /** Build an app whose /api router uses an injected assembly reader. */ + function appWith( + readAssembly: (dir: string) => Promise, + opts: { assemblyDir?: string } = {}, + ): express.Express { + const assemblyDir = opts.assemblyDir ?? path.join(appDir, 'cdk.out'); + writeManifest(assemblyDir); + const a = express(); + a.use('/api', createApiRouter({ appDir, assemblyDir, readAssembly, acquireAssemblyLock: noopAssemblyLock })); + return a; + } + + test('maps a successful read into an ok TreeResponse with relativized paths', async () => { + const realAppDir = fs.realpathSync(appDir); + const reader = async (dir: string): Promise => { + const node: ConstructNode = { + path: 'MyStack/Bucket', + id: 'Bucket', + type: 'AWS::S3::Bucket', + logicalId: 'Bucket123', + templateFile: path.join(dir, 'MyStack.template.json'), + sourceLocation: { file: path.join(realAppDir, 'lib', 'stack.ts'), line: 12, column: 5 }, + children: [], + }; + return { status: 'success', data: { tree: [node], warnings: ['heads up'] } }; + }; + + const res = await request(appWith(reader)).get('/api/tree'); + + expect(res.status).toBe(200); + expect(res.body.status).toBe('ok'); + expect(res.body.warnings).toEqual(['heads up']); + expect(res.body.tree).toHaveLength(1); + expect(res.body.tree[0]).toMatchObject({ + path: 'MyStack/Bucket', + logicalId: 'Bucket123', + templateFile: 'MyStack.template.json', + sourceLocation: { file: 'lib/stack.ts', line: 12, column: 5 }, + }); + }); + + test('returns not-synthesized (200) when no assembly is found', async () => { + const res = await request(appWith(async () => ({ status: 'not-found' }))).get('/api/tree'); + expect(res.status).toBe(200); + expect(res.body).toEqual({ status: 'not-synthesized' }); + }); + + test('returns 500 when the read errors', async () => { + const res = await request(appWith(async () => ({ status: 'error', message: 'bad manifest' }))).get('/api/tree'); + expect(res.status).toBe(500); + expect(res.body).toEqual({ error: 'bad manifest' }); + }); + + test('defaults the assembly dir to /cdk.out', async () => { + let seen: string | undefined; + const reader = async (dir: string): Promise => { + seen = dir; + return { status: 'not-found' }; + }; + await request(appWith(reader)).get('/api/tree'); + expect(seen).toBe(path.join(appDir, 'cdk.out')); + }); + + test('honors an explicit assemblyDir override', async () => { + let seen: string | undefined; + const reader = async (dir: string): Promise => { + seen = dir; + return { status: 'not-found' }; + }; + const override = path.join(appDir, 'custom-out'); + await request(appWith(reader, { assemblyDir: override })).get('/api/tree'); + expect(seen).toBe(override); + }); +}); + +describe('GET /api/template', () => { + const TEMPLATE_OBJ = { + Resources: { + Bucket123: { + Type: 'AWS::S3::Bucket', + Properties: { BucketName: 'my-bucket', VersioningConfiguration: { Status: 'Enabled' } }, + }, + Topic456: { Type: 'AWS::SNS::Topic' }, + }, + }; + const TEMPLATE_TEXT = JSON.stringify(TEMPLATE_OBJ, undefined, 1); + + function appWith( + readAssembly: (dir: string) => Promise, + opts: { assemblyDir?: string } = {}, + ): express.Express { + const assemblyDir = opts.assemblyDir ?? path.join(appDir, 'cdk.out'); + writeManifest(assemblyDir); + fs.writeFileSync(path.join(assemblyDir, 'MyStack.template.json'), TEMPLATE_TEXT); + const a = express(); + a.use('/api', createApiRouter({ appDir, assemblyDir, readAssembly, acquireAssemblyLock: noopAssemblyLock })); + return a; + } + + test('returns template content and resource line ranges', async () => { + const realAppDir = fs.realpathSync(appDir); + const assemblyDir = path.join(appDir, 'cdk.out'); + const reader = async (dir: string): Promise => { + const node: ConstructNode = { + path: 'MyStack/Bucket', + id: 'Bucket', + type: 'AWS::S3::Bucket', + logicalId: 'Bucket123', + templateFile: path.join(dir, 'MyStack.template.json'), + sourceLocation: { file: path.join(realAppDir, 'lib', 'stack.ts'), line: 10, column: 5 }, + children: [], + }; + return { status: 'success', data: { tree: [node], warnings: [] } }; + }; + + const res = await request(appWith(reader)).get('/api/template').query({ file: 'MyStack.template.json' }); + expect(res.status).toBe(200); + expect(res.body.content).toBe(TEMPLATE_TEXT); + expect(res.body.resources.Bucket123).toBeDefined(); + expect(res.body.resources.Bucket123.block.startLine).toBeGreaterThan(0); + expect(res.body.resources.Bucket123.block.endLine).toBeGreaterThanOrEqual(res.body.resources.Bucket123.block.startLine); + expect(res.body.resources.Bucket123.source).toEqual({ file: 'lib/stack.ts', line: 10, column: 5 }); + expect(res.body.resources.Topic456).toBeDefined(); + expect(res.body.resources.Topic456.source).toBeUndefined(); + }); + + test('requires a file parameter', async () => { + const reader = async (): Promise => ({ status: 'not-found' }); + const res = await request(appWith(reader)).get('/api/template'); + expect(res.status).toBe(400); + }); + + test('rejects path traversal with 403', async () => { + const reader = async (): Promise => ({ status: 'not-found' }); + const res = await request(appWith(reader)).get('/api/template').query({ file: '../../etc/passwd' }); + expect(res.status).toBe(403); + }); + + test('returns 404 for a missing template', async () => { + const reader = async (): Promise => ({ status: 'not-found' }); + const res = await request(appWith(reader)).get('/api/template').query({ file: 'nope.template.json' }); + expect(res.status).toBe(404); + }); + + test('works without a construct tree (assembly not found)', async () => { + const reader = async (): Promise => ({ status: 'not-found' }); + const res = await request(appWith(reader)).get('/api/template').query({ file: 'MyStack.template.json' }); + expect(res.status).toBe(200); + expect(res.body.content).toBe(TEMPLATE_TEXT); + expect(res.body.resources.Bucket123).toBeDefined(); + expect(res.body.resources.Bucket123.source).toBeUndefined(); + }); +}); + +describe('GET /api/policy-validation', () => { + function appWith(readAssembly: (dir: string) => Promise): express.Express { + const assemblyDir = path.join(appDir, 'cdk.out'); + writeManifest(assemblyDir); + const a = express(); + a.use('/api', createApiRouter({ appDir, assemblyDir, readAssembly, acquireAssemblyLock: noopAssemblyLock })); + return a; + } + + test('normalizes violations joined to the construct tree', async () => { + const realAppDir = fs.realpathSync(appDir); + const reader = async (dir: string): Promise => { + const node: ConstructNode = { + path: 'MyStack/Bucket', + id: 'Bucket', + type: 'AWS::S3::Bucket', + logicalId: 'Bucket123', + templateFile: path.join(dir, 'MyStack.template.json'), + sourceLocation: { file: path.join(realAppDir, 'lib', 'stack.ts'), line: 7, column: 3 }, + children: [], + }; + const violations: PolicyValidationReportJson = { + version: '1.0', + pluginReports: [ + { + pluginName: 'cdk-validator', + conclusion: 'failure', + violations: [ + { + ruleName: 'no-public-access', + description: 'no public access', + severity: 'error', + violatingConstructs: [ + { constructPath: 'MyStack/Bucket', cloudFormationResource: { templatePath: 'x', logicalId: 'y', propertyPaths: ['Properties.Public'] } }, + ], + }, + ], + }, + ], + }; + return { status: 'success', data: { tree: [node], violations, warnings: [] } }; + }; + + const res = await request(appWith(reader)).get('/api/policy-validation'); + + expect(res.status).toBe(200); + expect(res.body.status).toBe('ok'); + expect(res.body.violations).toHaveLength(1); + expect(res.body.violations[0]).toMatchObject({ ruleName: 'no-public-access', severity: 'error', source: 'cdk-validator' }); + expect(res.body.violations[0].occurrences[0]).toMatchObject({ + constructPath: 'MyStack/Bucket', + logicalId: 'Bucket123', + templateFile: 'MyStack.template.json', + sourceLocation: { file: 'lib/stack.ts', line: 7, column: 3 }, + propertyPaths: ['Properties.Public'], + }); + }); + + test('returns not-synthesized (200) when no assembly is found', async () => { + const res = await request(appWith(async () => ({ status: 'not-found' }))).get('/api/policy-validation'); + expect(res.status).toBe(200); + expect(res.body).toEqual({ status: 'not-synthesized' }); + }); + + test('returns 500 when the read errors', async () => { + const res = await request(appWith(async () => ({ status: 'error', message: 'boom' }))).get('/api/policy-validation'); + expect(res.status).toBe(500); + expect(res.body).toEqual({ error: 'boom' }); + }); +}); + +describe('assembly read lock', () => { + function appWithLock(acquireAssemblyLock: () => Promise<{ release: () => Promise }>): express.Express { + const assemblyDir = path.join(appDir, 'cdk.out'); + writeManifest(assemblyDir); + const a = express(); + a.use('/api', createApiRouter({ + appDir, + assemblyDir, + readAssembly: () => ({ status: 'not-found' }), + acquireAssemblyLock, + })); + return a; + } + + test('acquires the lock and releases it after reading', async () => { + const release = jest.fn(() => Promise.resolve()); + const acquireAssemblyLock = jest.fn(async () => ({ release })); + const res = await request(appWithLock(acquireAssemblyLock)).get('/api/tree'); + expect(res.status).toBe(200); + expect(acquireAssemblyLock).toHaveBeenCalledWith(path.join(appDir, 'cdk.out')); + expect(release).toHaveBeenCalledTimes(1); + }); + + test('retries while a synth holds the write lock, then reads once it clears', async () => { + let calls = 0; + const acquireAssemblyLock = jest.fn(async () => { + calls += 1; + if (calls <= 2) throw new LockError('ConcurrentWriteLock', 'a synth is writing'); + return { release: () => Promise.resolve() }; + }); + const res = await request(appWithLock(acquireAssemblyLock)).get('/api/tree'); + expect(res.status).toBe(200); + expect(acquireAssemblyLock).toHaveBeenCalledTimes(3); // 2 contended + 1 success + }); + + test('returns 503 when the write lock never clears', async () => { + const acquireAssemblyLock = jest.fn(async () => { + throw new LockError('ConcurrentWriteLock', 'a synth is writing'); + }); + const res = await request(appWithLock(acquireAssemblyLock)).get('/api/tree'); + expect(res.status).toBe(503); + expect(res.body).toEqual({ error: 'synth in progress, please retry' }); + }); + + test('returns 500 when the lock fails for a non-contention reason', async () => { + const acquireAssemblyLock = jest.fn(async () => { + throw new Error('corrupt manifest'); + }); + const res = await request(appWithLock(acquireAssemblyLock)).get('/api/tree'); + expect(res.status).toBe(500); + expect(res.body).toEqual({ error: 'corrupt manifest' }); + }); + + test('serves a cached read without re-locking while the manifest mtime is unchanged', async () => { + const assemblyDir = path.join(appDir, 'cdk.out'); + writeManifest(assemblyDir); + const acquireAssemblyLock = jest.fn(async () => ({ release: () => Promise.resolve() })); + const readAssembly = jest.fn((): AssemblyReadResult => ({ status: 'success', data: { tree: [], warnings: [] } })); + const a = express(); + a.use('/api', createApiRouter({ appDir, assemblyDir, readAssembly, acquireAssemblyLock })); + await request(a).get('/api/tree'); + await request(a).get('/api/tree'); + expect(readAssembly).toHaveBeenCalledTimes(1); // second request served from cache + expect(acquireAssemblyLock).toHaveBeenCalledTimes(1); // no lock taken on a cache hit + }); + + test('re-reads under the lock when the manifest mtime changes', async () => { + const assemblyDir = path.join(appDir, 'cdk.out'); + writeManifest(assemblyDir); + const acquireAssemblyLock = jest.fn(async () => ({ release: () => Promise.resolve() })); + const readAssembly = jest.fn((): AssemblyReadResult => ({ status: 'success', data: { tree: [], warnings: [] } })); + const a = express(); + a.use('/api', createApiRouter({ appDir, assemblyDir, readAssembly, acquireAssemblyLock })); + await request(a).get('/api/tree'); + const future = new Date(Date.now() + 5000); + fs.utimesSync(path.join(assemblyDir, 'manifest.json'), future, future); + await request(a).get('/api/tree'); + expect(readAssembly).toHaveBeenCalledTimes(2); // mtime changed, so re-read + }); +}); diff --git a/packages/@aws-cdk/cdk-explorer/test/web/safe-path.test.ts b/packages/@aws-cdk/cdk-explorer/test/web/safe-path.test.ts new file mode 100644 index 000000000..5c65eb6a7 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/test/web/safe-path.test.ts @@ -0,0 +1,71 @@ +import * as path from 'path'; +import { resolveWithinRoot } from '../../lib/web/safe-path'; + +const root = path.resolve('/srv/app'); + +describe('resolveWithinRoot', () => { + test('resolves a simple relative path inside the root', () => { + expect(resolveWithinRoot(root, 'lib/stack.ts')).toBe(path.join(root, 'lib/stack.ts')); + }); + + test('treats an empty path as the root itself', () => { + expect(resolveWithinRoot(root, '')).toBe(root); + }); + + test('strips a leading slash instead of jumping to filesystem root', () => { + expect(resolveWithinRoot(root, '/lib/stack.ts')).toBe(path.join(root, 'lib/stack.ts')); + }); + + test('rejects traversal above the root', () => { + expect(resolveWithinRoot(root, '../secrets')).toBeUndefined(); + expect(resolveWithinRoot(root, '../../etc/passwd')).toBeUndefined(); + expect(resolveWithinRoot(root, 'lib/../../escape')).toBeUndefined(); + }); + + test('allows traversal that stays within the root', () => { + expect(resolveWithinRoot(root, 'lib/../bin/cdk.ts')).toBe(path.join(root, 'bin/cdk.ts')); + }); + + test('does not treat a sibling directory with a shared prefix as inside', () => { + expect(resolveWithinRoot(root, '../app-secrets/file')).toBeUndefined(); + }); +}); + +// Reload safe-path with `path` swapped for its +// win32 flavor to test real Windows behavior on the Linux runner. +describe('resolveWithinRoot (win32 semantics)', () => { + const winRoot = 'C:\\srv\\app'; + let resolveWin: typeof resolveWithinRoot; + + beforeAll(() => { + jest.resetModules(); + jest.doMock('path', () => jest.requireActual('path').win32); + // eslint-disable-next-line @typescript-eslint/no-require-imports + resolveWin = require('../../lib/web/safe-path').resolveWithinRoot; + }); + + afterAll(() => { + jest.dontMock('path'); + jest.resetModules(); + }); + + test('resolves forward-slash client paths under the root', () => { + expect(resolveWin(winRoot, 'lib/stack.ts')).toBe('C:\\srv\\app\\lib\\stack.ts'); + }); + + test('strips a leading backslash instead of jumping to the drive root', () => { + expect(resolveWin(winRoot, '\\lib\\stack.ts')).toBe('C:\\srv\\app\\lib\\stack.ts'); + }); + + test('rejects backslash traversal above the root', () => { + expect(resolveWin(winRoot, '..\\secrets')).toBeUndefined(); + }); + + test('rejects an absolute path on a different drive', () => { + expect(resolveWin(winRoot, 'D:\\evil')).toBeUndefined(); + }); + + test('does not treat a sibling drive-prefixed directory as inside', () => { + expect(resolveWin(winRoot, '..\\app-secrets\\file')).toBeUndefined(); + }); +}); diff --git a/packages/@aws-cdk/cdk-explorer/test/web/server.test.ts b/packages/@aws-cdk/cdk-explorer/test/web/server.test.ts new file mode 100644 index 000000000..c7e625264 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/test/web/server.test.ts @@ -0,0 +1,158 @@ +import { ASSEMBLY_CHANGED, SOURCE_CHANGED } from '../../lib/web/protocol'; +import { startWebServer, DEFAULT_PORT, type WebServer } from '../../lib/web/server'; + +describe('Web Server', () => { + let server: WebServer; + + afterEach(async () => { + if (server) { + await server.stop(); + } + }); + + test('starts and responds to health check', async () => { + server = await startWebServer(); + + const res = await fetch(`${server.url}/api/health`); + expect(res.status).toBe(200); + + const body = await res.json(); + expect(body).toEqual({ status: 'ok' }); + }); + + test('binds to localhost by default', async () => { + server = await startWebServer(); + expect(server.url).toMatch(/^http:\/\/localhost:\d+$/); + }); + + test('auto-increments port by 1 when default is taken', async () => { + const first = await startWebServer({ port: DEFAULT_PORT }); + server = await startWebServer(); + + expect(first.url).toBe(`http://localhost:${DEFAULT_PORT}`); + expect(server.url).toBe(`http://localhost:${DEFAULT_PORT + 1}`); + await first.stop(); + }); + + test('throws when explicit port is taken', async () => { + const first = await startWebServer({ port: 4567 }); + try { + await expect(startWebServer({ port: 4567 })).rejects.toThrow(); + } finally { + await first.stop(); + } + }); + + test('stops cleanly', async () => { + server = await startWebServer(); + const url = server.url; + + await server.stop(); + + await expect(fetch(`${url}/api/health`)).rejects.toThrow(); + }); + + test('stop is idempotent', async () => { + server = await startWebServer(); + await server.stop(); + await server.stop(); + }); + + test('unknown /api route returns a JSON 404 rather than the SPA', async () => { + server = await startWebServer(); + const res = await fetch(`${server.url}/api/does-not-exist`); + expect(res.status).toBe(404); + expect(res.headers.get('content-type')).toMatch(/application\/json/); + expect((await res.json()).error).toBeDefined(); + }); + + test('serves the SPA index with Cache-Control: no-store so a rebuilt bundle is not served stale', async () => { + server = await startWebServer(); + const res = await fetch(`${server.url}/`); + expect(res.status).toBe(200); + expect(res.headers.get('cache-control')).toBe('no-store'); + }); + + test('watches the resolved assembly dir and closes the watcher on stop', async () => { + let seenDir: string | undefined; + let closed = false; + server = await startWebServer({ + assemblyDir: '/tmp/explorer-test/cdk.out', + startAssemblyWatcher: (opts) => { + seenDir = opts.assemblyDir; + return { + close: async () => { + closed = true; + }, + }; + }, + }); + + expect(seenDir).toBe('/tmp/explorer-test/cdk.out'); + + await server.stop(); + expect(closed).toBe(true); + }); + + test('broadcasts an assembly-changed event to a connected client when the watcher fires', async () => { + let fireChange = (): void => undefined; + server = await startWebServer({ + startAssemblyWatcher: (opts) => { + fireChange = opts.onChange; + return { close: async () => undefined }; + }, + }); + + const res = await fetch(`${server.url}/api/events`); + expect(res.headers.get('content-type')).toMatch(/text\/event-stream/); + const body = res.body; + if (!body) throw new Error('SSE response had no body'); + const reader = body.getReader(); + + fireChange(); + const { value } = await reader.read(); + expect(new TextDecoder().decode(value)).toContain(`event: ${ASSEMBLY_CHANGED}`); + + await reader.cancel(); + }); + + test('starts a source watcher and closes it on stop', async () => { + let closed = false; + server = await startWebServer({ + startAssemblyWatcher: () => ({ close: async () => undefined }), + startSourceWatcher: (opts) => { + expect(opts.appDir).toBeDefined(); + return { + close: async () => { + closed = true; + }, + }; + }, + }); + + await server.stop(); + expect(closed).toBe(true); + }); + + test('broadcasts a source-changed event when the source watcher fires', async () => { + let fireSourceChange = (): void => undefined; + server = await startWebServer({ + startAssemblyWatcher: () => ({ close: async () => undefined }), + startSourceWatcher: (opts) => { + fireSourceChange = opts.onChange; + return { close: async () => undefined }; + }, + }); + + const res = await fetch(`${server.url}/api/events`); + const body = res.body; + if (!body) throw new Error('SSE response had no body'); + const reader = body.getReader(); + + fireSourceChange(); + const { value } = await reader.read(); + expect(new TextDecoder().decode(value)).toContain(`event: ${SOURCE_CHANGED}`); + + await reader.cancel(); + }); +}); diff --git a/packages/@aws-cdk/cdk-explorer/test/web/source-nav.test.ts b/packages/@aws-cdk/cdk-explorer/test/web/source-nav.test.ts new file mode 100644 index 000000000..b3162c8c6 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/test/web/source-nav.test.ts @@ -0,0 +1,113 @@ +import type { WebConstructNode } from '../../lib/web/protocol'; +import { buildSourceAnchorIndex, findConstructAtLine } from '../../lib/web/source-nav'; + +function node(props: { + path: string; + logicalId?: string; + templateFile?: string; + file?: string; + line?: number; + children?: WebConstructNode[]; +}): WebConstructNode { + return { + path: props.path, + id: props.path.split('/').pop()!, + logicalId: props.logicalId, + templateFile: props.templateFile, + sourceLocation: props.file !== undefined ? { file: props.file, line: props.line ?? 0, column: 0 } : undefined, + children: props.children ?? [], + }; +} + +/** A fully-navigable construct: has source location + template coordinates. */ +function navigable(path: string, file: string, line: number, children?: WebConstructNode[]): WebConstructNode { + return node({ path, logicalId: path.replace(/\W/g, ''), templateFile: 'App.template.json', file, line, children }); +} + +describe('buildSourceAnchorIndex', () => { + test('returns an empty map for an empty tree', () => { + expect(buildSourceAnchorIndex([]).size).toBe(0); + }); + + test('indexes navigable constructs grouped by file', () => { + const index = buildSourceAnchorIndex([ + navigable('App/Bucket', 'app.ts', 10), + navigable('App/Queue', 'app.ts', 20), + navigable('App/Nested/Table', 'db.ts', 5), + ]); + + expect([...index.keys()].sort()).toEqual(['app.ts', 'db.ts']); + expect(index.get('app.ts')!.map((a) => a.node.path)).toEqual(['App/Bucket', 'App/Queue']); + expect(index.get('db.ts')!.map((a) => a.node.path)).toEqual(['App/Nested/Table']); + }); + + test('walks children depth-first and sorts each file by line ascending', () => { + const index = buildSourceAnchorIndex([ + navigable('App/Outer', 'app.ts', 30, [ + navigable('App/Outer/Inner', 'app.ts', 12), + ]), + navigable('App/First', 'app.ts', 4), + ]); + + expect(index.get('app.ts')!.map((a) => a.line)).toEqual([4, 12, 30]); + }); + + test('excludes constructs missing a source location, templateFile, or logicalId', () => { + const index = buildSourceAnchorIndex([ + node({ path: 'App/NoSource', logicalId: 'X', templateFile: 'App.template.json' }), + node({ path: 'App/NoTemplate', logicalId: 'Y', file: 'app.ts', line: 1 }), + node({ path: 'App/NoLogicalId', templateFile: 'App.template.json', file: 'app.ts', line: 2 }), + navigable('App/Ok', 'app.ts', 3), + ]); + + expect([...index.keys()]).toEqual(['app.ts']); + expect(index.get('app.ts')!.map((a) => a.node.path)).toEqual(['App/Ok']); + }); +}); + +describe('findConstructAtLine', () => { + const anchors = buildSourceAnchorIndex([ + navigable('App/Bucket', 'app.ts', 10), + navigable('App/Queue', 'app.ts', 20), + ]).get('app.ts'); + + test('returns undefined when anchors are undefined', () => { + expect(findConstructAtLine(undefined, 10)).toBeUndefined(); + }); + + test('returns undefined for a line above the first construct', () => { + expect(findConstructAtLine(anchors, 3)).toBeUndefined(); + }); + + test('matches the construct on its exact definition line', () => { + expect(findConstructAtLine(anchors, 10)!.path).toBe('App/Bucket'); + expect(findConstructAtLine(anchors, 20)!.path).toBe('App/Queue'); + }); + + test('matches the nearest preceding construct for a line inside its block', () => { + expect(findConstructAtLine(anchors, 15)!.path).toBe('App/Bucket'); + }); + + test('matches the last construct for a line below all definitions', () => { + expect(findConstructAtLine(anchors, 999)!.path).toBe('App/Queue'); + }); + + test('returns undefined for an empty anchor list', () => { + expect(findConstructAtLine([], 10)).toBeUndefined(); + }); + + test('prefers the top-most construct when several share the nearest line', () => { + // Mirrors the real VPC case: a parent and its synthesized children are all + // anchored to the single `new Vpc(...)` line. Double-clicking that line + // should land on the authored parent, not a deep synthetic child. + const shared = buildSourceAnchorIndex([ + navigable('Net/Vpc', 'net.ts', 11, [ + navigable('Net/Vpc/PublicSubnet1', 'net.ts', 11), + navigable('Net/Vpc/PublicSubnet1/NatGateway', 'net.ts', 11), + ]), + ]).get('net.ts'); + + expect(findConstructAtLine(shared, 11)!.path).toBe('Net/Vpc'); + expect(findConstructAtLine(shared, 40)!.path).toBe('Net/Vpc'); + }); +}); diff --git a/packages/@aws-cdk/cdk-explorer/test/web/staleness.test.ts b/packages/@aws-cdk/cdk-explorer/test/web/staleness.test.ts new file mode 100644 index 000000000..b36a6b802 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/test/web/staleness.test.ts @@ -0,0 +1,50 @@ +import { StalenessTracker } from '../../lib/web/staleness'; + +describe('StalenessTracker', () => { + test('reports nothing stale before any assembly is loaded', () => { + const t = new StalenessTracker(); + expect(t.isStale(Number.MAX_SAFE_INTEGER)).toBe(false); + }); + + test('a file modified after the reference is stale; at or before is not', () => { + const t = new StalenessTracker(); + t.onAssemblyRefreshed(1_000); + expect(t.isStale(1_001)).toBe(true); + expect(t.isStale(1_000)).toBe(false); // equal is not newer + expect(t.isStale(999)).toBe(false); + }); + + test('prefers observed synth-start over the manifest mtime (option 2)', () => { + const t = new StalenessTracker(); + t.noteSynthActivity(500); // synth started at 500 + t.onAssemblyRefreshed(2_000); // ...and finished (manifest) at 2000 + // A file edited at 800 (during the synth) is newer than synth-start -> stale, + // even though it predates synth-finish. + expect(t.isStale(800)).toBe(true); + }); + + test('keeps the earliest synth-activity timestamp within a generation', () => { + const t = new StalenessTracker(); + t.noteSynthActivity(500); + t.noteSynthActivity(1_500); // later activity does not move the reference + t.onAssemblyRefreshed(3_000); + expect(t.isStale(600)).toBe(true); // newer than 500 + }); + + test('falls back to the manifest mtime when no synth activity was observed', () => { + const t = new StalenessTracker(); + t.onAssemblyRefreshed(2_000); + expect(t.isStale(1_500)).toBe(false); + expect(t.isStale(2_500)).toBe(true); + }); + + test('clears synth activity after a refresh so the next generation re-measures', () => { + const t = new StalenessTracker(); + t.noteSynthActivity(500); + t.onAssemblyRefreshed(2_000); // reference = 500 + // Next generation: no new activity observed -> falls back to its manifest mtime. + t.onAssemblyRefreshed(4_000); // reference = 4000 + expect(t.isStale(3_000)).toBe(false); + expect(t.isStale(4_500)).toBe(true); + }); +}); diff --git a/packages/@aws-cdk/cdk-explorer/test/web/to-web-node.test.ts b/packages/@aws-cdk/cdk-explorer/test/web/to-web-node.test.ts new file mode 100644 index 000000000..3f28b7e8d --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/test/web/to-web-node.test.ts @@ -0,0 +1,289 @@ +import type { ConstructNode } from '../../lib/core/assembly-reader'; +import { toWebNode } from '../../lib/web/routes'; + +const ASSEMBLY_DIR = '/abs/cdk.out'; +const APP_DIR = '/abs/app'; +const NO_SEVERITY = new Map(); + +function makeNode(props: { + path: string; + id: string; + type?: string; + logicalId?: string; + templateFile?: string; + sourceLocation?: { file: string; line: number; column: number }; + children?: ConstructNode[]; +}): ConstructNode { + return { ...props, children: props.children ?? [] }; +} + +describe('toWebNode', () => { + test('relativizes a flat template to its bare name', () => { + const web = toWebNode( + makeNode({ path: 'S/B', id: 'B', logicalId: 'B123', templateFile: `${ASSEMBLY_DIR}/MyStack.template.json` }), + NO_SEVERITY, + ASSEMBLY_DIR, + APP_DIR, + ); + expect(web.templateFile).toBe('MyStack.template.json'); + }); + + test('keeps the sub-assembly directory for staged stacks', () => { + const web = toWebNode( + makeNode({ + path: 'Prod/S', + id: 'S', + logicalId: 'S1', + templateFile: `${ASSEMBLY_DIR}/assembly-Prod/Prod-MyStack.template.json`, + }), + NO_SEVERITY, + ASSEMBLY_DIR, + APP_DIR, + ); + expect(web.templateFile).toBe('assembly-Prod/Prod-MyStack.template.json'); + }); + + test('relativizes a source location inside the app dir', () => { + const web = toWebNode( + makeNode({ path: 'S/B', id: 'B', sourceLocation: { file: `${APP_DIR}/lib/stack.ts`, line: 12, column: 5 } }), + NO_SEVERITY, + ASSEMBLY_DIR, + APP_DIR, + ); + expect(web.sourceLocation).toEqual({ file: 'lib/stack.ts', line: 12, column: 5 }); + }); + + test('drops a source location that escapes the app dir', () => { + const web = toWebNode( + makeNode({ path: 'S/B', id: 'B', sourceLocation: { file: '/abs/other/lib.ts', line: 1, column: 1 } }), + NO_SEVERITY, + ASSEMBLY_DIR, + APP_DIR, + ); + expect(web.sourceLocation).toBeUndefined(); + }); + + test('leaves template and source undefined for a non-resource, non-TS node', () => { + const web = toWebNode(makeNode({ path: 'S', id: 'S' }), NO_SEVERITY, ASSEMBLY_DIR, APP_DIR); + expect(web.templateFile).toBeUndefined(); + expect(web.sourceLocation).toBeUndefined(); + }); + + test('passes through type and logicalId', () => { + const web = toWebNode( + makeNode({ path: 'S/B', id: 'B', type: 'AWS::S3::Bucket', logicalId: 'B123' }), + NO_SEVERITY, + ASSEMBLY_DIR, + APP_DIR, + ); + expect(web.type).toBe('AWS::S3::Bucket'); + expect(web.logicalId).toBe('B123'); + }); + + test('recurses over children', () => { + const tree = makeNode({ + path: 'S', + id: 'S', + children: [ + makeNode({ path: 'S/B', id: 'B', logicalId: 'B1', templateFile: `${ASSEMBLY_DIR}/S.template.json` }), + ], + }); + const web = toWebNode(tree, NO_SEVERITY, ASSEMBLY_DIR, APP_DIR); + expect(web.children).toHaveLength(1); + expect(web.children[0].path).toBe('S/B'); + expect(web.children[0].templateFile).toBe('S.template.json'); + }); + + test('annotates a node with its violation severity', () => { + const web = toWebNode(makeNode({ path: 'S/B', id: 'B' }), new Map([['S/B', 'error']]), ASSEMBLY_DIR, APP_DIR); + expect(web.highestSeverity).toBe('error'); + }); + + test('leaves highestSeverity undefined when the node has no violation', () => { + const web = toWebNode(makeNode({ path: 'S/B', id: 'B' }), NO_SEVERITY, ASSEMBLY_DIR, APP_DIR); + expect(web.highestSeverity).toBeUndefined(); + }); +}); + +describe('toWebNode default-child collapse', () => { + test('folds a leaf "Resource" child into its parent', () => { + const web = toWebNode( + makeNode({ + path: 'S/ItemsTable', + id: 'ItemsTable', + children: [ + makeNode({ + path: 'S/ItemsTable/Resource', + id: 'Resource', + type: 'AWS::DynamoDB::Table', + logicalId: 'ItemsTable0ABC', + templateFile: `${ASSEMBLY_DIR}/S.template.json`, + }), + ], + }), + NO_SEVERITY, + ASSEMBLY_DIR, + APP_DIR, + ); + expect(web.type).toBe('AWS::DynamoDB::Table'); + expect(web.logicalId).toBe('ItemsTable0ABC'); + expect(web.templateFile).toBe('S.template.json'); + expect(web.children).toHaveLength(0); + }); + + test('folds a leaf "Default" child too', () => { + const web = toWebNode( + makeNode({ + path: 'S/Custom', + id: 'Custom', + children: [makeNode({ path: 'S/Custom/Default', id: 'Default', type: 'AWS::CloudFormation::CustomResource' })], + }), + NO_SEVERITY, + ASSEMBLY_DIR, + APP_DIR, + ); + expect(web.type).toBe('AWS::CloudFormation::CustomResource'); + expect(web.children).toHaveLength(0); + }); + + test('collapses each construct independently, keeping siblings', () => { + const web = toWebNode( + makeNode({ + path: 'S/Bucket', + id: 'Bucket', + children: [ + makeNode({ path: 'S/Bucket/Resource', id: 'Resource', type: 'AWS::S3::Bucket', logicalId: 'Bucket1' }), + makeNode({ + path: 'S/Bucket/Policy', + id: 'Policy', + children: [ + makeNode({ path: 'S/Bucket/Policy/Resource', id: 'Resource', type: 'AWS::S3::BucketPolicy', logicalId: 'Policy1' }), + ], + }), + ], + }), + NO_SEVERITY, + ASSEMBLY_DIR, + APP_DIR, + ); + expect(web.type).toBe('AWS::S3::Bucket'); + expect(web.children).toHaveLength(1); + expect(web.children[0].id).toBe('Policy'); + expect(web.children[0].type).toBe('AWS::S3::BucketPolicy'); + expect(web.children[0].children).toHaveLength(0); + }); + + test('does not collapse a "Resource" child that has its own children', () => { + const web = toWebNode( + makeNode({ + path: 'S/Weird', + id: 'Weird', + children: [ + makeNode({ + path: 'S/Weird/Resource', + id: 'Resource', + type: 'AWS::Some::Thing', + children: [makeNode({ path: 'S/Weird/Resource/Inner', id: 'Inner' })], + }), + ], + }), + NO_SEVERITY, + ASSEMBLY_DIR, + APP_DIR, + ); + expect(web.type).toBeUndefined(); + expect(web.children).toHaveLength(1); + expect(web.children[0].id).toBe('Resource'); + }); + + test('does not collapse a "Resource" child with no CFN type', () => { + const web = toWebNode( + makeNode({ path: 'S/X', id: 'X', children: [makeNode({ path: 'S/X/Resource', id: 'Resource' })] }), + NO_SEVERITY, + ASSEMBLY_DIR, + APP_DIR, + ); + expect(web.type).toBeUndefined(); + expect(web.children).toHaveLength(1); + }); + + test('keeps the parent source location (relativized), falling back to the child when absent', () => { + const parentLoc = { file: `${APP_DIR}/lib/s.ts`, line: 5, column: 2 }; + const childLoc = { file: `${APP_DIR}/lib/s.ts`, line: 99, column: 9 }; + const kept = toWebNode( + makeNode({ + path: 'S/T', + id: 'T', + sourceLocation: parentLoc, + children: [makeNode({ path: 'S/T/Resource', id: 'Resource', type: 'AWS::X::Y', sourceLocation: childLoc })], + }), + NO_SEVERITY, + ASSEMBLY_DIR, + APP_DIR, + ); + expect(kept.sourceLocation).toEqual({ file: 'lib/s.ts', line: 5, column: 2 }); + + const fellBack = toWebNode( + makeNode({ + path: 'S/T', + id: 'T', + children: [makeNode({ path: 'S/T/Resource', id: 'Resource', type: 'AWS::X::Y', sourceLocation: childLoc })], + }), + NO_SEVERITY, + ASSEMBLY_DIR, + APP_DIR, + ); + expect(fellBack.sourceLocation).toEqual({ file: 'lib/s.ts', line: 99, column: 9 }); + }); + + test("folds an absorbed default child's severity into the parent", () => { + const web = toWebNode( + makeNode({ + path: 'S/T', + id: 'T', + children: [makeNode({ path: 'S/T/Resource', id: 'Resource', type: 'AWS::X::Y' })], + }), + new Map([['S/T/Resource', 'error']]), + ASSEMBLY_DIR, + APP_DIR, + ); + expect(web.highestSeverity).toBe('error'); + expect(web.children).toHaveLength(0); + }); + + test('takes the more severe of the parent and the absorbed child', () => { + const web = toWebNode( + makeNode({ + path: 'S/T', + id: 'T', + children: [makeNode({ path: 'S/T/Resource', id: 'Resource', type: 'AWS::X::Y' })], + }), + new Map([['S/T', 'warning'], ['S/T/Resource', 'error']]), + ASSEMBLY_DIR, + APP_DIR, + ); + expect(web.highestSeverity).toBe('error'); + }); + + test("does not attribute an uncollapsed Resource child's severity to its parent", () => { + const web = toWebNode( + makeNode({ + path: 'S/Weird', + id: 'Weird', + children: [ + makeNode({ + path: 'S/Weird/Resource', + id: 'Resource', + type: 'AWS::Some::Thing', + children: [makeNode({ path: 'S/Weird/Resource/Inner', id: 'Inner' })], + }), + ], + }), + new Map([['S/Weird/Resource', 'error']]), + ASSEMBLY_DIR, + APP_DIR, + ); + expect(web.highestSeverity).toBeUndefined(); + expect(web.children[0].highestSeverity).toBe('error'); + }); +}); diff --git a/packages/@aws-cdk/cdk-explorer/tsconfig.frontend.json b/packages/@aws-cdk/cdk-explorer/tsconfig.frontend.json new file mode 100644 index 000000000..d17fdb341 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/tsconfig.frontend.json @@ -0,0 +1,18 @@ +{ + "//": "TypeScript config for the web explorer SPA. Bundled by esbuild, not by the package's CommonJS/Node tsc build. Used for editor IntelliSense and the bundler's typecheck pass.", + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "skipLibCheck": true, + "isolatedModules": true, + "forceConsistentCasingInFileNames": true, + "types": ["react", "react-dom"] + }, + "include": ["frontend"] +} diff --git a/packages/@aws-cdk/cdk-explorer/tsconfig.json b/packages/@aws-cdk/cdk-explorer/tsconfig.json index 8874ce8bf..357fd5aeb 100644 --- a/packages/@aws-cdk/cdk-explorer/tsconfig.json +++ b/packages/@aws-cdk/cdk-explorer/tsconfig.json @@ -28,8 +28,13 @@ "target": "es2022", "types": [ "convert-source-map", + "express", "jest", - "node" + "node", + "prismjs", + "react-dom", + "react", + "supertest" ], "incremental": true, "skipLibCheck": true, @@ -40,7 +45,8 @@ "lib/**/*.ts" ], "exclude": [ - "node_modules" + "node_modules", + "frontend" ], "references": [ { diff --git a/packages/@aws-cdk/cloud-assembly-api/lib/template-ranges.ts b/packages/@aws-cdk/cloud-assembly-api/lib/template-ranges.ts index 730c9c162..dbd9e117f 100644 --- a/packages/@aws-cdk/cloud-assembly-api/lib/template-ranges.ts +++ b/packages/@aws-cdk/cloud-assembly-api/lib/template-ranges.ts @@ -99,6 +99,48 @@ function propertyRanges(pointers: Pointers, escapedId: string): Record | undefined { + const pointers = parsePointers(templateText); + if (pointers === undefined) { + return undefined; + } + + const result: Record = {}; + const resourceProperties = new Map>(); + + for (const [pointer, mapping] of Object.entries(pointers)) { + const resourceMatch = /^\/Resources\/([^/]+)$/.exec(pointer); + if (resourceMatch) { + const id = unescapePointerSegment(resourceMatch[1]); + result[id] = { block: { start: mapping.value.pos, end: mapping.valueEnd.pos }, properties: {} }; + continue; + } + const propMatch = /^\/Resources\/([^/]+)\/Properties\/([^/]+)$/.exec(pointer); + if (propMatch && mapping.key !== undefined) { + const id = unescapePointerSegment(propMatch[1]); + let props = resourceProperties.get(id); + if (!props) { + props = {}; + resourceProperties.set(id, props); + } + props[unescapePointerSegment(propMatch[2])] = { start: mapping.key.pos, end: mapping.valueEnd.pos }; + } + } + + for (const [id, props] of resourceProperties) { + if (result[id]) { + result[id] = { block: result[id].block, properties: props }; + } + } + + return result; +} + /** * The inverse of `resolveResourceRange`: given a character offset into a * template's text, returns the logical id of the resource whose block contains diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/rwlock.ts b/packages/@aws-cdk/toolkit-lib/lib/api/rwlock.ts index cfbb18b5d..a5a714dfa 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/rwlock.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/rwlock.ts @@ -2,6 +2,12 @@ import { promises as fs } from 'fs'; import * as path from 'path'; import { LockError } from '../toolkit/toolkit-error'; +/** + * The marker file a writer (i.e. a synth) creates in the locked directory for + * the duration of the write. + */ +export const SYNTH_LOCK_FILE = 'synth.lock'; + /** * A single-writer/multi-reader lock on a directory * @@ -21,7 +27,7 @@ export class RWLock { constructor(public readonly directory: string) { this.pidString = `${process.pid}`; - this.writerFile = path.join(this.directory, 'synth.lock'); + this.writerFile = path.join(this.directory, SYNTH_LOCK_FILE); } /** diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/source-tracing/index.ts b/packages/@aws-cdk/toolkit-lib/lib/api/source-tracing/index.ts index fcb073fef..46e5f1757 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/source-tracing/index.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/source-tracing/index.ts @@ -1 +1,2 @@ export * from './types'; +export { findCreationStackTrace, findMutationStackTraces } from './private/stack-source-tracing'; diff --git a/packages/aws-cdk/THIRD_PARTY_LICENSES b/packages/aws-cdk/THIRD_PARTY_LICENSES index be879de73..dd9318dc1 100644 --- a/packages/aws-cdk/THIRD_PARTY_LICENSES +++ b/packages/aws-cdk/THIRD_PARTY_LICENSES @@ -10437,6 +10437,34 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +---------------- + +** accepts@1.3.8 - https://www.npmjs.com/package/accepts/v/1.3.8 | MIT +(The MIT License) + +Copyright (c) 2014 Jonathan Ong +Copyright (c) 2015 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + ---------------- ** agent-base@7.1.4 - https://www.npmjs.com/package/agent-base/v/7.1.4 | MIT @@ -10544,6 +10572,32 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +---------------- + +** array-flatten@1.1.1 - https://www.npmjs.com/package/array-flatten/v/1.1.1 | MIT +The MIT License (MIT) + +Copyright (c) 2014 Blake Embrey (hello@blakeembrey.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + ---------------- ** ast-types@0.13.4 - https://www.npmjs.com/package/ast-types/v/0.13.4 | MIT @@ -10606,6 +10660,34 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +---------------- + +** body-parser@1.20.4 - https://www.npmjs.com/package/body-parser/v/1.20.4 | MIT +(The MIT License) + +Copyright (c) 2014 Jonathan Ong +Copyright (c) 2014-2015 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + ---------------- ** bowser@2.14.1 - https://www.npmjs.com/package/bowser/v/2.14.1 | MIT @@ -10700,6 +10782,86 @@ FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TOR ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +---------------- + +** bytes@3.1.2 - https://www.npmjs.com/package/bytes/v/3.1.2 | MIT +(The MIT License) + +Copyright (c) 2012-2014 TJ Holowaychuk +Copyright (c) 2015 Jed Watson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** call-bind-apply-helpers@1.0.2 - https://www.npmjs.com/package/call-bind-apply-helpers/v/1.0.2 | MIT +MIT License + +Copyright (c) 2024 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +---------------- + +** call-bound@1.0.4 - https://www.npmjs.com/package/call-bound/v/1.0.4 | MIT +MIT License + +Copyright (c) 2024 Jordan Harband + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + ---------------- ** camelcase@5.3.1 - https://www.npmjs.com/package/camelcase/v/5.3.1 | MIT @@ -10803,6 +10965,60 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +---------------- + +** content-disposition@0.5.4 - https://www.npmjs.com/package/content-disposition/v/0.5.4 | MIT +(The MIT License) + +Copyright (c) 2014-2017 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** content-type@1.0.5 - https://www.npmjs.com/package/content-type/v/1.0.5 | MIT +(The MIT License) + +Copyright (c) 2015 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + ---------------- ** convert-source-map@2.0.0 - https://www.npmjs.com/package/convert-source-map/v/2.0.0 | MIT @@ -10831,6 +11047,39 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +---------------- + +** cookie-signature@1.0.7 - https://www.npmjs.com/package/cookie-signature/v/1.0.7 | MIT + +---------------- + +** cookie@0.7.2 - https://www.npmjs.com/package/cookie/v/0.7.2 | MIT +(The MIT License) + +Copyright (c) 2012-2014 Roman Shtylman +Copyright (c) 2015 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + + ---------------- ** data-uri-to-buffer@6.0.2 - https://www.npmjs.com/package/data-uri-to-buffer/v/6.0.2 | MIT @@ -10857,6 +11106,30 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +---------------- + +** debug@2.6.9 - https://www.npmjs.com/package/debug/v/2.6.9 | MIT +(The MIT License) + +Copyright (c) 2014 TJ Holowaychuk + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the 'Software'), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + + ---------------- ** debug@4.4.3 - https://www.npmjs.com/package/debug/v/4.4.3 | MIT @@ -10928,12 +11201,14 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI ---------------- -** emoji-regex@8.0.0 - https://www.npmjs.com/package/emoji-regex/v/8.0.0 | MIT -Copyright Mathias Bynens +** depd@2.0.0 - https://www.npmjs.com/package/depd/v/2.0.0 | MIT +(The MIT License) + +Copyright (c) 2014-2018 Douglas Christopher Wilson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including +'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to @@ -10942,21 +11217,23 @@ the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - ----------------- +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** destroy@1.2.0 - https://www.npmjs.com/package/destroy/v/1.2.0 | MIT -** enquirer@2.4.1 - https://www.npmjs.com/package/enquirer/v/2.4.1 | MIT The MIT License (MIT) -Copyright (c) 2016-present, Jon Schlinkert. +Copyright (c) 2014 Jonathan Ong me@jongleberry.com +Copyright (c) 2015-2022 Douglas Christopher Wilson doug@somethingdoug.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -10979,110 +11256,115 @@ THE SOFTWARE. ---------------- -** escodegen@2.1.0 - https://www.npmjs.com/package/escodegen/v/2.1.0 | BSD-2-Clause -Copyright (C) 2012 Yusuke Suzuki (twitter: @Constellation) and other contributors. +** dunder-proto@1.0.1 - https://www.npmjs.com/package/dunder-proto/v/1.0.1 | MIT +MIT License -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: +Copyright (c) 2024 ECMAScript Shims - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. ---------------- -** esprima@4.0.1 - https://www.npmjs.com/package/esprima/v/4.0.1 | BSD-2-Clause -Copyright JS Foundation and other contributors, https://js.foundation/ +** ee-first@1.1.1 - https://www.npmjs.com/package/ee-first/v/1.1.1 | MIT -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: +The MIT License (MIT) - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. +Copyright (c) 2014 Jonathan Ong me@jongleberry.com -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. ---------------- -** estraverse@5.3.0 - https://www.npmjs.com/package/estraverse/v/5.3.0 | BSD-2-Clause -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: +** emoji-regex@8.0.0 - https://www.npmjs.com/package/emoji-regex/v/8.0.0 | MIT +Copyright Mathias Bynens - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------- -** esutils@2.0.3 - https://www.npmjs.com/package/esutils/v/2.0.3 | BSD-2-Clause -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: +** encodeurl@2.0.0 - https://www.npmjs.com/package/encodeurl/v/2.0.0 | MIT +(The MIT License) - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. +Copyright (c) 2016 Douglas Christopher Wilson -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------- -** eventemitter3@4.0.7 - https://www.npmjs.com/package/eventemitter3/v/4.0.7 | MIT +** enquirer@2.4.1 - https://www.npmjs.com/package/enquirer/v/2.4.1 | MIT The MIT License (MIT) -Copyright (c) 2014 Arnout Kazemier +Copyright (c) 2016-present, Jon Schlinkert. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -11091,24 +11373,24 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. ---------------- -** fast-deep-equal@3.1.3 - https://www.npmjs.com/package/fast-deep-equal/v/3.1.3 | MIT +** es-define-property@1.0.1 - https://www.npmjs.com/package/es-define-property/v/1.0.1 | MIT MIT License -Copyright (c) 2017 Evgeny Poberezkin +Copyright (c) 2024 Jordan Harband Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -11131,10 +11413,10 @@ SOFTWARE. ---------------- -** fast-glob@3.3.3 - https://www.npmjs.com/package/fast-glob/v/3.3.3 | MIT -The MIT License (MIT) +** es-errors@1.3.0 - https://www.npmjs.com/package/es-errors/v/1.3.0 | MIT +MIT License -Copyright (c) Denis Malinochkin +Copyright (c) 2024 Jordan Harband Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -11157,28 +11439,10 @@ SOFTWARE. ---------------- -** fastq@1.20.1 - https://www.npmjs.com/package/fastq/v/1.20.1 | ISC -Copyright (c) 2015-2020, Matteo Collina - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - ----------------- - -** fill-range@7.1.1 - https://www.npmjs.com/package/fill-range/v/7.1.1 | MIT -The MIT License (MIT) +** es-object-atoms@1.1.1 - https://www.npmjs.com/package/es-object-atoms/v/1.1.1 | MIT +MIT License -Copyright (c) 2014-present, Jon Schlinkert. +Copyright (c) 2024 Jordan Harband Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -11187,62 +11451,26 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - - ----------------- - -** find-up@4.1.0 - https://www.npmjs.com/package/find-up/v/4.1.0 | MIT -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - ----------------- - -** fs-extra@11.3.5 - https://www.npmjs.com/package/fs-extra/v/11.3.5 | MIT -(The MIT License) - -Copyright (c) 2011-2024 JP Richardson - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files -(the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, - merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - ----------------- +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. -** get-caller-file@2.0.5 - https://www.npmjs.com/package/get-caller-file/v/2.0.5 | ISC ---------------- -** get-uri@6.0.5 - https://www.npmjs.com/package/get-uri/v/6.0.5 | MIT +** escape-html@1.0.3 - https://www.npmjs.com/package/escape-html/v/1.0.3 | MIT (The MIT License) -Copyright (c) 2014 Nathan Rajlich +Copyright (c) 2012-2013 TJ Holowaychuk +Copyright (c) 2015 Andreas Lubbe +Copyright (c) 2015 Tiancheng "Timothy" Gu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -11263,66 +11491,113 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ---------------- -** glob-parent@5.1.2 - https://www.npmjs.com/package/glob-parent/v/5.1.2 | ISC -The ISC License +** escodegen@2.1.0 - https://www.npmjs.com/package/escodegen/v/2.1.0 | BSD-2-Clause +Copyright (C) 2012 Yusuke Suzuki (twitter: @Constellation) and other contributors. -Copyright (c) 2015, 2019 Elan Shanker +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR -IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------- -** graceful-fs@4.2.11 - https://www.npmjs.com/package/graceful-fs/v/4.2.11 | ISC -The ISC License +** esprima@4.0.1 - https://www.npmjs.com/package/esprima/v/4.0.1 | BSD-2-Clause +Copyright JS Foundation and other contributors, https://js.foundation/ -Copyright (c) 2011-2022 Isaac Z. Schlueter, Ben Noordhuis, and Contributors +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR -IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------- -** has-flag@4.0.0 - https://www.npmjs.com/package/has-flag/v/4.0.0 | MIT -MIT License +** estraverse@5.3.0 - https://www.npmjs.com/package/estraverse/v/5.3.0 | BSD-2-Clause +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: -Copyright (c) Sindre Sorhus (sindresorhus.com) + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +---------------- + +** esutils@2.0.3 - https://www.npmjs.com/package/esutils/v/2.0.3 | BSD-2-Clause +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------- -** http-proxy-agent@7.0.2 - https://www.npmjs.com/package/http-proxy-agent/v/7.0.2 | MIT +** etag@1.8.1 - https://www.npmjs.com/package/etag/v/1.8.1 | MIT (The MIT License) -Copyright (c) 2013 Nathan Rajlich +Copyright (c) 2014-2016 Douglas Christopher Wilson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -11346,10 +11621,38 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------- -** https-proxy-agent@7.0.6 - https://www.npmjs.com/package/https-proxy-agent/v/7.0.6 | MIT +** eventemitter3@4.0.7 - https://www.npmjs.com/package/eventemitter3/v/4.0.7 | MIT +The MIT License (MIT) + +Copyright (c) 2014 Arnout Kazemier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +---------------- + +** express@4.22.1 - https://www.npmjs.com/package/express/v/4.22.1 | MIT (The MIT License) -Copyright (c) 2013 Nathan Rajlich +Copyright (c) 2009-2014 TJ Holowaychuk +Copyright (c) 2013-2014 Roman Shtylman +Copyright (c) 2014-2015 Douglas Christopher Wilson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -11370,10 +11673,13 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ---------------- -** ip-address@10.2.0 - https://www.npmjs.com/package/ip-address/v/10.2.0 | MIT -Copyright (C) 2011 by Beau Gunderson +** fast-deep-equal@3.1.3 - https://www.npmjs.com/package/fast-deep-equal/v/3.1.3 | MIT +MIT License + +Copyright (c) 2017 Evgeny Poberezkin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -11382,24 +11688,24 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. ---------------- -** is-extglob@2.1.1 - https://www.npmjs.com/package/is-extglob/v/2.1.1 | MIT +** fast-glob@3.3.3 - https://www.npmjs.com/package/fast-glob/v/3.3.3 | MIT The MIT License (MIT) -Copyright (c) 2014-2016, Jon Schlinkert +Copyright (c) Denis Malinochkin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -11408,38 +11714,42 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. ---------------- -** is-fullwidth-code-point@3.0.0 - https://www.npmjs.com/package/is-fullwidth-code-point/v/3.0.0 | MIT -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +** fastq@1.20.1 - https://www.npmjs.com/package/fastq/v/1.20.1 | ISC +Copyright (c) 2015-2020, Matteo Collina -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ---------------- -** is-glob@4.0.3 - https://www.npmjs.com/package/is-glob/v/4.0.3 | MIT +** fill-range@7.1.1 - https://www.npmjs.com/package/fill-range/v/7.1.1 | MIT The MIT License (MIT) -Copyright (c) 2014-2017, Jon Schlinkert. +Copyright (c) 2014-present, Jon Schlinkert. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -11462,136 +11772,83 @@ THE SOFTWARE. ---------------- -** is-number@7.0.0 - https://www.npmjs.com/package/is-number/v/7.0.0 | MIT -The MIT License (MIT) +** finalhandler@1.3.2 - https://www.npmjs.com/package/finalhandler/v/1.3.2 | MIT +(The MIT License) -Copyright (c) 2014-present, Jon Schlinkert. +Copyright (c) 2014-2022 Douglas Christopher Wilson -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------- -** json-source-map@0.6.1 - https://www.npmjs.com/package/json-source-map/v/0.6.1 | MIT +** find-up@4.1.0 - https://www.npmjs.com/package/find-up/v/4.1.0 | MIT MIT License -Copyright (c) 2017 Evgeny Poberezkin +Copyright (c) Sindre Sorhus (sindresorhus.com) -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------- -** jsonfile@6.2.0 - https://www.npmjs.com/package/jsonfile/v/6.2.0 | MIT +** forwarded@0.2.0 - https://www.npmjs.com/package/forwarded/v/0.2.0 | MIT (The MIT License) -Copyright (c) 2012-2015, JP Richardson +Copyright (c) 2014-2017 Douglas Christopher Wilson -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files -(the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, - merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - ----------------- - -** jsonschema@1.5.0 - https://www.npmjs.com/package/jsonschema/v/1.5.0 | MIT -jsonschema is licensed under MIT license. - -Copyright (C) 2012-2015 Tom de Grunt - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ----------------- - -** locate-path@5.0.0 - https://www.npmjs.com/package/locate-path/v/5.0.0 | MIT -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------- -** lodash.truncate@4.4.2 - https://www.npmjs.com/package/lodash.truncate/v/4.4.2 | MIT -Copyright jQuery Foundation and other contributors - -Based on Underscore.js, copyright Jeremy Ashkenas, -DocumentCloud and Investigative Reporters & Editors - -This software consists of voluntary contributions made by many -individuals. For exact contribution history, see the revision history -available at https://github.com/lodash/lodash - -The following license applies to all parts of this software except as -documented below: +** fresh@0.5.2 - https://www.npmjs.com/package/fresh/v/0.5.2 | MIT +(The MIT License) -==== +Copyright (c) 2012 TJ Holowaychuk +Copyright (c) 2016-2017 Douglas Christopher Wilson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including +'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to @@ -11600,56 +11857,39 @@ the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -==== - -Copyright and related rights for sample code are waived via CC0. Sample -code is defined as all source code displayed within the prose of the -documentation. - -CC0: http://creativecommons.org/publicdomain/zero/1.0/ - -==== - -Files located in the node_modules and vendor directories are externally -maintained libraries used by this software which have their own -licenses; we recommend you read them, as their terms may differ from the -terms above. +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------- -** lru-cache@7.18.3 - https://www.npmjs.com/package/lru-cache/v/7.18.3 | ISC -The ISC License +** fs-extra@11.3.5 - https://www.npmjs.com/package/fs-extra/v/11.3.5 | MIT +(The MIT License) -Copyright (c) 2010-2023 Isaac Z. Schlueter and Contributors +Copyright (c) 2011-2024 JP Richardson -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +(the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR -IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ----------------- -** merge2@1.4.1 - https://www.npmjs.com/package/merge2/v/1.4.1 | MIT -The MIT License (MIT) +---------------- -Copyright (c) 2014-2020 Teambition +** function-bind@1.1.2 - https://www.npmjs.com/package/function-bind/v/1.1.2 | MIT +Copyright (c) 2013 Raynos. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -11658,24 +11898,29 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + ---------------- -** micromatch@4.0.8 - https://www.npmjs.com/package/micromatch/v/4.0.8 | MIT -The MIT License (MIT) +** get-caller-file@2.0.5 - https://www.npmjs.com/package/get-caller-file/v/2.0.5 | ISC -Copyright (c) 2014-present, Jon Schlinkert. +---------------- + +** get-intrinsic@1.3.0 - https://www.npmjs.com/package/get-intrinsic/v/1.3.0 | MIT +MIT License + +Copyright (c) 2020 Jordan Harband Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -11684,24 +11929,24 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. ---------------- -** mime@2.6.0 - https://www.npmjs.com/package/mime/v/2.6.0 | MIT -The MIT License (MIT) +** get-proto@1.0.1 - https://www.npmjs.com/package/get-proto/v/1.0.1 | MIT +MIT License -Copyright (c) 2010 Benjamin Thomas, Robert Kieffer +Copyright (c) 2025 Jordan Harband Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -11710,28 +11955,50 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. ---------------- -** ms@2.1.3 - https://www.npmjs.com/package/ms/v/2.1.3 | MIT +** get-uri@6.0.5 - https://www.npmjs.com/package/get-uri/v/6.0.5 | MIT +(The MIT License) + +Copyright (c) 2014 Nathan Rajlich + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------- -** mute-stream@0.0.8 - https://www.npmjs.com/package/mute-stream/v/0.0.8 | ISC +** glob-parent@5.1.2 - https://www.npmjs.com/package/glob-parent/v/5.1.2 | ISC The ISC License -Copyright (c) Isaac Z. Schlueter and Contributors +Copyright (c) 2015, 2019 Elan Shanker Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -11748,14 +12015,10 @@ IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ---------------- -** netmask@2.1.1 - https://www.npmjs.com/package/netmask/v/2.1.1 | MIT - ----------------- - -** p-finally@1.0.0 - https://www.npmjs.com/package/p-finally/v/1.0.0 | MIT -The MIT License (MIT) +** gopd@1.2.0 - https://www.npmjs.com/package/gopd/v/1.2.0 | MIT +MIT License -Copyright (c) Sindre Sorhus (sindresorhus.com) +Copyright (c) 2022 Jordan Harband Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -11764,38 +12027,44 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. ---------------- -** p-limit@2.3.0 - https://www.npmjs.com/package/p-limit/v/2.3.0 | MIT -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) +** graceful-fs@4.2.11 - https://www.npmjs.com/package/graceful-fs/v/4.2.11 | ISC +The ISC License -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Copyright (c) 2011-2022 Isaac Z. Schlueter, Ben Noordhuis, and Contributors -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ---------------- -** p-limit@3.1.0 - https://www.npmjs.com/package/p-limit/v/3.1.0 | MIT +** has-flag@4.0.0 - https://www.npmjs.com/package/has-flag/v/4.0.0 | MIT MIT License -Copyright (c) Sindre Sorhus (https://sindresorhus.com) +Copyright (c) Sindre Sorhus (sindresorhus.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: @@ -11806,66 +12075,90 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI ---------------- -** p-locate@4.1.0 - https://www.npmjs.com/package/p-locate/v/4.1.0 | MIT +** has-symbols@1.1.0 - https://www.npmjs.com/package/has-symbols/v/1.1.0 | MIT MIT License -Copyright (c) Sindre Sorhus (sindresorhus.com) +Copyright (c) 2016 Jordan Harband -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. ---------------- -** p-queue@6.6.2 - https://www.npmjs.com/package/p-queue/v/6.6.2 | MIT +** hasown@2.0.4 - https://www.npmjs.com/package/hasown/v/2.0.4 | MIT MIT License -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Copyright (c) Jordan Harband and contributors +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: ----------------- - -** p-timeout@3.2.0 - https://www.npmjs.com/package/p-timeout/v/3.2.0 | MIT -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. ---------------- -** p-try@2.2.0 - https://www.npmjs.com/package/p-try/v/2.2.0 | MIT -MIT License +** http-errors@2.0.1 - https://www.npmjs.com/package/http-errors/v/2.0.1 | MIT -Copyright (c) Sindre Sorhus (sindresorhus.com) +The MIT License (MIT) -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Copyright (c) 2014 Jonathan Ong me@jongleberry.com +Copyright (c) 2016 Douglas Christopher Wilson doug@somethingdoug.com -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. ---------------- -** pac-proxy-agent@7.2.0 - https://www.npmjs.com/package/pac-proxy-agent/v/7.2.0 | MIT +** http-proxy-agent@7.0.2 - https://www.npmjs.com/package/http-proxy-agent/v/7.0.2 | MIT (The MIT License) -Copyright (c) 2014 Nathan Rajlich +Copyright (c) 2013 Nathan Rajlich Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -11889,7 +12182,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------- -** pac-resolver@7.0.1 - https://www.npmjs.com/package/pac-resolver/v/7.0.1 | MIT +** https-proxy-agent@7.0.6 - https://www.npmjs.com/package/https-proxy-agent/v/7.0.6 | MIT (The MIT License) Copyright (c) 2013 Nathan Rajlich @@ -11915,24 +12208,55 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------- -** path-exists@4.0.0 - https://www.npmjs.com/package/path-exists/v/4.0.0 | MIT -MIT License +** iconv-lite@0.4.24 - https://www.npmjs.com/package/iconv-lite/v/0.4.24 | MIT +Copyright (c) 2011 Alexander Shtuchkin -Copyright (c) Sindre Sorhus (sindresorhus.com) +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------- -** picomatch@2.3.2 - https://www.npmjs.com/package/picomatch/v/2.3.2 | MIT -The MIT License (MIT) +** inherits@2.0.4 - https://www.npmjs.com/package/inherits/v/2.0.4 | ISC +The ISC License -Copyright (c) 2017-present, Jon Schlinkert. +Copyright (c) Isaac Z. Schlueter + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. + + + +---------------- + +** ip-address@10.2.0 - https://www.npmjs.com/package/ip-address/v/10.2.0 | MIT +Copyright (C) 2011 by Beau Gunderson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -11955,10 +12279,8 @@ THE SOFTWARE. ---------------- -** picomatch@4.0.4 - https://www.npmjs.com/package/picomatch/v/4.0.4 | MIT -The MIT License (MIT) - -Copyright (c) 2017-present, Jon Schlinkert. +** ipaddr.js@1.9.1 - https://www.npmjs.com/package/ipaddr.js/v/1.9.1 | MIT +Copyright (C) 2011-2017 whitequark Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -11981,10 +12303,10 @@ THE SOFTWARE. ---------------- -** promptly@3.2.0 - https://www.npmjs.com/package/promptly/v/3.2.0 | MIT +** is-extglob@2.1.1 - https://www.npmjs.com/package/is-extglob/v/2.1.1 | MIT The MIT License (MIT) -Copyright (c) 2018 Made With MOXY Lda +Copyright (c) 2014-2016, Jon Schlinkert Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -12007,120 +12329,1326 @@ THE SOFTWARE. ---------------- -** proxy-agent@6.5.0 - https://www.npmjs.com/package/proxy-agent/v/6.5.0 | MIT -(The MIT License) +** is-fullwidth-code-point@3.0.0 - https://www.npmjs.com/package/is-fullwidth-code-point/v/3.0.0 | MIT +MIT License -Copyright (c) 2013 Nathan Rajlich +Copyright (c) Sindre Sorhus (sindresorhus.com) -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -'Software'), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------- -** proxy-from-env@1.1.0 - https://www.npmjs.com/package/proxy-from-env/v/1.1.0 | MIT -The MIT License +** is-glob@4.0.3 - https://www.npmjs.com/package/is-glob/v/4.0.3 | MIT +The MIT License (MIT) -Copyright (C) 2016-2018 Rob Wu +Copyright (c) 2014-2017, Jon Schlinkert. -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. ---------------- -** queue-microtask@1.2.3 - https://www.npmjs.com/package/queue-microtask/v/1.2.3 | MIT +** is-number@7.0.0 - https://www.npmjs.com/package/is-number/v/7.0.0 | MIT The MIT License (MIT) -Copyright (c) Feross Aboukhadijeh +Copyright (c) 2014-present, Jon Schlinkert. -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. ---------------- -** read@1.0.7 - https://www.npmjs.com/package/read/v/1.0.7 | ISC -The ISC License +** json-source-map@0.6.1 - https://www.npmjs.com/package/json-source-map/v/0.6.1 | MIT +MIT License -Copyright (c) Isaac Z. Schlueter and Contributors +Copyright (c) 2017 Evgeny Poberezkin -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR -IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. ---------------- -** require-directory@2.1.1 - https://www.npmjs.com/package/require-directory/v/2.1.1 | MIT -The MIT License (MIT) +** jsonfile@6.2.0 - https://www.npmjs.com/package/jsonfile/v/6.2.0 | MIT +(The MIT License) -Copyright (c) 2011 Troy Goode +Copyright (c) 2012-2015, JP Richardson -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +(the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** jsonschema@1.5.0 - https://www.npmjs.com/package/jsonschema/v/1.5.0 | MIT +jsonschema is licensed under MIT license. + +Copyright (C) 2012-2015 Tom de Grunt + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +---------------- + +** locate-path@5.0.0 - https://www.npmjs.com/package/locate-path/v/5.0.0 | MIT +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** lodash.truncate@4.4.2 - https://www.npmjs.com/package/lodash.truncate/v/4.4.2 | MIT +Copyright jQuery Foundation and other contributors + +Based on Underscore.js, copyright Jeremy Ashkenas, +DocumentCloud and Investigative Reporters & Editors + +This software consists of voluntary contributions made by many +individuals. For exact contribution history, see the revision history +available at https://github.com/lodash/lodash + +The following license applies to all parts of this software except as +documented below: + +==== + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +==== + +Copyright and related rights for sample code are waived via CC0. Sample +code is defined as all source code displayed within the prose of the +documentation. + +CC0: http://creativecommons.org/publicdomain/zero/1.0/ + +==== + +Files located in the node_modules and vendor directories are externally +maintained libraries used by this software which have their own +licenses; we recommend you read them, as their terms may differ from the +terms above. + + +---------------- + +** lru-cache@7.18.3 - https://www.npmjs.com/package/lru-cache/v/7.18.3 | ISC +The ISC License + +Copyright (c) 2010-2023 Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + +---------------- + +** math-intrinsics@1.1.0 - https://www.npmjs.com/package/math-intrinsics/v/1.1.0 | MIT +MIT License + +Copyright (c) 2024 ECMAScript Shims + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +---------------- + +** media-typer@0.3.0 - https://www.npmjs.com/package/media-typer/v/0.3.0 | MIT +(The MIT License) + +Copyright (c) 2014 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** merge-descriptors@1.0.3 - https://www.npmjs.com/package/merge-descriptors/v/1.0.3 | MIT +(The MIT License) + +Copyright (c) 2013 Jonathan Ong +Copyright (c) 2015 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** merge2@1.4.1 - https://www.npmjs.com/package/merge2/v/1.4.1 | MIT +The MIT License (MIT) + +Copyright (c) 2014-2020 Teambition + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +---------------- + +** methods@1.1.2 - https://www.npmjs.com/package/methods/v/1.1.2 | MIT +(The MIT License) + +Copyright (c) 2013-2014 TJ Holowaychuk +Copyright (c) 2015-2016 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + + +---------------- + +** micromatch@4.0.8 - https://www.npmjs.com/package/micromatch/v/4.0.8 | MIT +The MIT License (MIT) + +Copyright (c) 2014-present, Jon Schlinkert. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +---------------- + +** mime-db@1.52.0 - https://www.npmjs.com/package/mime-db/v/1.52.0 | MIT +(The MIT License) + +Copyright (c) 2014 Jonathan Ong +Copyright (c) 2015-2022 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** mime-types@2.1.35 - https://www.npmjs.com/package/mime-types/v/2.1.35 | MIT +(The MIT License) + +Copyright (c) 2014 Jonathan Ong +Copyright (c) 2015 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** mime@1.6.0 - https://www.npmjs.com/package/mime/v/1.6.0 | MIT +The MIT License (MIT) + +Copyright (c) 2010 Benjamin Thomas, Robert Kieffer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +---------------- + +** mime@2.6.0 - https://www.npmjs.com/package/mime/v/2.6.0 | MIT +The MIT License (MIT) + +Copyright (c) 2010 Benjamin Thomas, Robert Kieffer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +---------------- + +** ms@2.0.0 - https://www.npmjs.com/package/ms/v/2.0.0 | MIT + +---------------- + +** ms@2.1.3 - https://www.npmjs.com/package/ms/v/2.1.3 | MIT + +---------------- + +** mute-stream@0.0.8 - https://www.npmjs.com/package/mute-stream/v/0.0.8 | ISC +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + +---------------- + +** negotiator@0.6.3 - https://www.npmjs.com/package/negotiator/v/0.6.3 | MIT +(The MIT License) + +Copyright (c) 2012-2014 Federico Romero +Copyright (c) 2012-2014 Isaac Z. Schlueter +Copyright (c) 2014-2015 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** netmask@2.1.1 - https://www.npmjs.com/package/netmask/v/2.1.1 | MIT + +---------------- + +** object-inspect@1.13.4 - https://www.npmjs.com/package/object-inspect/v/1.13.4 | MIT +MIT License + +Copyright (c) 2013 James Halliday + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +---------------- + +** on-finished@2.4.1 - https://www.npmjs.com/package/on-finished/v/2.4.1 | MIT +(The MIT License) + +Copyright (c) 2013 Jonathan Ong +Copyright (c) 2014 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** p-finally@1.0.0 - https://www.npmjs.com/package/p-finally/v/1.0.0 | MIT +The MIT License (MIT) + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +---------------- + +** p-limit@2.3.0 - https://www.npmjs.com/package/p-limit/v/2.3.0 | MIT +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** p-limit@3.1.0 - https://www.npmjs.com/package/p-limit/v/3.1.0 | MIT +MIT License + +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** p-locate@4.1.0 - https://www.npmjs.com/package/p-locate/v/4.1.0 | MIT +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** p-queue@6.6.2 - https://www.npmjs.com/package/p-queue/v/6.6.2 | MIT +MIT License + +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** p-timeout@3.2.0 - https://www.npmjs.com/package/p-timeout/v/3.2.0 | MIT +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** p-try@2.2.0 - https://www.npmjs.com/package/p-try/v/2.2.0 | MIT +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** pac-proxy-agent@7.2.0 - https://www.npmjs.com/package/pac-proxy-agent/v/7.2.0 | MIT +(The MIT License) + +Copyright (c) 2014 Nathan Rajlich + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** pac-resolver@7.0.1 - https://www.npmjs.com/package/pac-resolver/v/7.0.1 | MIT +(The MIT License) + +Copyright (c) 2013 Nathan Rajlich + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** parseurl@1.3.3 - https://www.npmjs.com/package/parseurl/v/1.3.3 | MIT + +(The MIT License) + +Copyright (c) 2014 Jonathan Ong +Copyright (c) 2014-2017 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** path-exists@4.0.0 - https://www.npmjs.com/package/path-exists/v/4.0.0 | MIT +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** path-to-regexp@0.1.13 - https://www.npmjs.com/package/path-to-regexp/v/0.1.13 | MIT +The MIT License (MIT) + +Copyright (c) 2014 Blake Embrey (hello@blakeembrey.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +---------------- + +** picomatch@2.3.2 - https://www.npmjs.com/package/picomatch/v/2.3.2 | MIT +The MIT License (MIT) + +Copyright (c) 2017-present, Jon Schlinkert. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +---------------- + +** picomatch@4.0.4 - https://www.npmjs.com/package/picomatch/v/4.0.4 | MIT +The MIT License (MIT) + +Copyright (c) 2017-present, Jon Schlinkert. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +---------------- + +** promptly@3.2.0 - https://www.npmjs.com/package/promptly/v/3.2.0 | MIT +The MIT License (MIT) + +Copyright (c) 2018 Made With MOXY Lda + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +---------------- + +** proxy-addr@2.0.7 - https://www.npmjs.com/package/proxy-addr/v/2.0.7 | MIT +(The MIT License) + +Copyright (c) 2014-2016 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** proxy-agent@6.5.0 - https://www.npmjs.com/package/proxy-agent/v/6.5.0 | MIT +(The MIT License) + +Copyright (c) 2013 Nathan Rajlich + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------- + +** proxy-from-env@1.1.0 - https://www.npmjs.com/package/proxy-from-env/v/1.1.0 | MIT +The MIT License + +Copyright (C) 2016-2018 Rob Wu + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** qs@6.14.2 - https://www.npmjs.com/package/qs/v/6.14.2 | BSD-3-Clause + +---------------- + +** queue-microtask@1.2.3 - https://www.npmjs.com/package/queue-microtask/v/1.2.3 | MIT +The MIT License (MIT) + +Copyright (c) Feross Aboukhadijeh + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** range-parser@1.2.1 - https://www.npmjs.com/package/range-parser/v/1.2.1 | MIT +(The MIT License) + +Copyright (c) 2012-2014 TJ Holowaychuk +Copyright (c) 2015-2016 Douglas Christopher Wilson +Copyright (c) 2014-2022 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +---------------- + +** read@1.0.7 - https://www.npmjs.com/package/read/v/1.0.7 | ISC +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + +---------------- + +** require-directory@2.1.1 - https://www.npmjs.com/package/require-directory/v/2.1.1 | MIT +The MIT License (MIT) + +Copyright (c) 2011 Troy Goode + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** require-main-filename@2.0.0 - https://www.npmjs.com/package/require-main-filename/v/2.0.0 | ISC +Copyright (c) 2016, Contributors + +Permission to use, copy, modify, and/or distribute this software +for any purpose with or without fee is hereby granted, provided +that the above copyright notice and this permission notice +appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE +LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + +---------------- + +** reusify@1.1.0 - https://www.npmjs.com/package/reusify/v/1.1.0 | MIT +The MIT License (MIT) + +Copyright (c) 2015-2024 Matteo Collina + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + + +---------------- + +** run-parallel@1.2.0 - https://www.npmjs.com/package/run-parallel/v/1.2.0 | MIT +The MIT License (MIT) + +Copyright (c) Feross Aboukhadijeh + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** safe-buffer@5.2.1 - https://www.npmjs.com/package/safe-buffer/v/5.2.1 | MIT +The MIT License (MIT) + +Copyright (c) Feross Aboukhadijeh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +---------------- + +** safer-buffer@2.1.2 - https://www.npmjs.com/package/safer-buffer/v/2.1.2 | MIT +MIT License + +Copyright (c) 2018 Nikita Skovoroda + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +---------------- + +** semver@7.8.5 - https://www.npmjs.com/package/semver/v/7.8.5 | ISC +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + +---------------- + +** send@0.19.2 - https://www.npmjs.com/package/send/v/0.19.2 | MIT +(The MIT License) + +Copyright (c) 2012 TJ Holowaychuk +Copyright (c) 2014-2022 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** serve-static@1.16.3 - https://www.npmjs.com/package/serve-static/v/1.16.3 | MIT +(The MIT License) + +Copyright (c) 2010 Sencha Inc. +Copyright (c) 2011 LearnBoost +Copyright (c) 2011 TJ Holowaychuk +Copyright (c) 2014-2016 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, @@ -12130,7 +13658,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------- -** require-main-filename@2.0.0 - https://www.npmjs.com/package/require-main-filename/v/2.0.0 | ISC +** set-blocking@2.0.0 - https://www.npmjs.com/package/set-blocking/v/2.0.0 | ISC Copyright (c) 2016, Contributors Permission to use, copy, modify, and/or distribute this software @@ -12149,10 +13677,28 @@ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ---------------- -** reusify@1.1.0 - https://www.npmjs.com/package/reusify/v/1.1.0 | MIT -The MIT License (MIT) +** setprototypeof@1.2.0 - https://www.npmjs.com/package/setprototypeof/v/1.2.0 | ISC +Copyright (c) 2015, Wes Todd -Copyright (c) 2015-2024 Matteo Collina +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + +---------------- + +** side-channel-list@1.0.1 - https://www.npmjs.com/package/side-channel-list/v/1.0.1 | MIT +MIT License + +Copyright (c) 2024 Jordan Harband Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -12173,69 +13719,82 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------- -** run-parallel@1.2.0 - https://www.npmjs.com/package/run-parallel/v/1.2.0 | MIT -The MIT License (MIT) +** side-channel-map@1.0.1 - https://www.npmjs.com/package/side-channel-map/v/1.0.1 | MIT +MIT License -Copyright (c) Feross Aboukhadijeh +Copyright (c) 2024 Jordan Harband -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. ---------------- -** semver@7.8.5 - https://www.npmjs.com/package/semver/v/7.8.5 | ISC -The ISC License +** side-channel-weakmap@1.0.2 - https://www.npmjs.com/package/side-channel-weakmap/v/1.0.2 | MIT +MIT License -Copyright (c) Isaac Z. Schlueter and Contributors +Copyright (c) 2019 Jordan Harband -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR -IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. ---------------- -** set-blocking@2.0.0 - https://www.npmjs.com/package/set-blocking/v/2.0.0 | ISC -Copyright (c) 2016, Contributors +** side-channel@1.1.0 - https://www.npmjs.com/package/side-channel/v/1.1.0 | MIT +MIT License -Permission to use, copy, modify, and/or distribute this software -for any purpose with or without fee is hereby granted, provided -that the above copyright notice and this permission notice -appear in all copies. +Copyright (c) 2019 Jordan Harband -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE -LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES -OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, -WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, -ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. ---------------- @@ -12380,6 +13939,34 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +---------------- + +** statuses@2.0.2 - https://www.npmjs.com/package/statuses/v/2.0.2 | MIT + +The MIT License (MIT) + +Copyright (c) 2014 Jonathan Ong +Copyright (c) 2016 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + ---------------- ** string-width@4.2.3 - https://www.npmjs.com/package/string-width/v/4.2.3 | MIT @@ -12477,6 +14064,32 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +---------------- + +** toidentifier@1.0.1 - https://www.npmjs.com/package/toidentifier/v/1.0.1 | MIT +MIT License + +Copyright (c) 2016 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + ---------------- ** tslib@2.8.1 - https://www.npmjs.com/package/tslib/v/2.8.1 | 0BSD @@ -12493,6 +14106,34 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +---------------- + +** type-is@1.6.18 - https://www.npmjs.com/package/type-is/v/1.6.18 | MIT +(The MIT License) + +Copyright (c) 2014 Jonathan Ong +Copyright (c) 2014-2015 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + ---------------- ** universalify@2.0.1 - https://www.npmjs.com/package/universalify/v/2.0.1 | MIT @@ -12518,6 +14159,85 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +---------------- + +** unpipe@1.0.0 - https://www.npmjs.com/package/unpipe/v/1.0.0 | MIT +(The MIT License) + +Copyright (c) 2015 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** utils-merge@1.0.1 - https://www.npmjs.com/package/utils-merge/v/1.0.1 | MIT +The MIT License (MIT) + +Copyright (c) 2013-2017 Jared Hanson + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +---------------- + +** vary@1.1.2 - https://www.npmjs.com/package/vary/v/1.1.2 | MIT +(The MIT License) + +Copyright (c) 2014-2017 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + ---------------- ** vscode-jsonrpc@8.2.0 - https://www.npmjs.com/package/vscode-jsonrpc/v/8.2.0 | MIT diff --git a/packages/aws-cdk/lib/cli/cli-config.ts b/packages/aws-cdk/lib/cli/cli-config.ts index 470587236..6bad7f56b 100644 --- a/packages/aws-cdk/lib/cli/cli-config.ts +++ b/packages/aws-cdk/lib/cli/cli-config.ts @@ -527,6 +527,12 @@ export async function makeConfig(): Promise { 'doctor': { description: 'Check your set-up for potential problems', }, + 'explore': { + description: 'Opens an interactive web explorer for your CDK app', + options: { + port: { type: 'number', desc: 'Port to bind the web server on', default: 4200 }, + }, + }, 'lsp': { description: 'Start the CDK Language Server (LSP) over stdio for editor and AI-agent integration', }, diff --git a/packages/aws-cdk/lib/cli/cli-type-registry.json b/packages/aws-cdk/lib/cli/cli-type-registry.json index 02f71f7ff..656704b5f 100644 --- a/packages/aws-cdk/lib/cli/cli-type-registry.json +++ b/packages/aws-cdk/lib/cli/cli-type-registry.json @@ -1164,6 +1164,16 @@ "doctor": { "description": "Check your set-up for potential problems" }, + "explore": { + "description": "Opens an interactive web explorer for your CDK app", + "options": { + "port": { + "type": "number", + "desc": "Port to bind the web server on", + "default": 4200 + } + } + }, "lsp": { "description": "Start the CDK Language Server (LSP) over stdio for editor and AI-agent integration" }, diff --git a/packages/aws-cdk/lib/cli/cli.ts b/packages/aws-cdk/lib/cli/cli.ts index 42da6f799..d05f9cf4b 100644 --- a/packages/aws-cdk/lib/cli/cli.ts +++ b/packages/aws-cdk/lib/cli/cli.ts @@ -32,6 +32,7 @@ import type { Settings } from '../api/settings'; import { contextHandler as context } from '../commands/context'; import { docs } from '../commands/docs'; import { doctor } from '../commands/doctor'; +import { explore } from '../commands/explore'; import { FlagCommandHandler } from '../commands/flags/flags'; import { cliInit, printAvailableTemplates } from '../commands/init'; import { getLanguageFromAlias } from '../commands/language'; @@ -325,6 +326,13 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise): any { }), ) .command('doctor', 'Check your set-up for potential problems') + .command('explore', 'Opens an interactive web explorer for your CDK app', (yargs: Argv) => + yargs.option('port', { + default: 4200, + type: 'number', + desc: 'Port to bind the web server on', + }), + ) .command('lsp', 'Start the CDK Language Server (LSP) over stdio for editor and AI-agent integration') .command('orphan [PATHS..]', 'Detach resources from a CloudFormation stack without deleting them', (yargs: Argv) => yargs) .command('refactor [STACKS..]', 'Moves resources between stacks or within the same stack', (yargs: Argv) => diff --git a/packages/aws-cdk/lib/cli/user-configuration.ts b/packages/aws-cdk/lib/cli/user-configuration.ts index 92738d9d1..78c432341 100644 --- a/packages/aws-cdk/lib/cli/user-configuration.ts +++ b/packages/aws-cdk/lib/cli/user-configuration.ts @@ -38,6 +38,7 @@ export enum Command { DOCS = 'docs', DOC = 'doc', DOCTOR = 'doctor', + EXPLORE = 'explore', LSP = 'lsp', ORPHAN = 'orphan', REFACTOR = 'refactor', diff --git a/packages/aws-cdk/lib/cli/user-input.ts b/packages/aws-cdk/lib/cli/user-input.ts index ee69df1fc..1d84825d1 100644 --- a/packages/aws-cdk/lib/cli/user-input.ts +++ b/packages/aws-cdk/lib/cli/user-input.ts @@ -144,6 +144,11 @@ export interface UserInput { */ readonly doctor?: {}; + /** + * Opens an interactive web explorer for your CDK app + */ + readonly explore?: ExploreOptions; + /** * Start the CDK Language Server (LSP) over stdio for editor and AI-agent integration */ @@ -1774,6 +1779,20 @@ export interface DocsOptions { readonly browser?: string; } +/** + * Opens an interactive web explorer for your CDK app + * + * @struct + */ +export interface ExploreOptions { + /** + * Port to bind the web server on + * + * @default - 4200 + */ + readonly port?: number; +} + /** * Detach resources from a CloudFormation stack without deleting them * diff --git a/packages/aws-cdk/lib/commands/explore.ts b/packages/aws-cdk/lib/commands/explore.ts new file mode 100644 index 000000000..50e1e7154 --- /dev/null +++ b/packages/aws-cdk/lib/commands/explore.ts @@ -0,0 +1,30 @@ +import { startWebServer } from '@aws-cdk/cdk-explorer'; +import type { IoHelper } from '../api-private'; + +export interface ExploreOptions { + readonly ioHelper: IoHelper; + readonly port?: number; +} + +export async function explore(options: ExploreOptions): Promise { + const server = await startWebServer({ + port: options.port, + onWatcherError: (err) => void options.ioHelper.defaults.error( + `CDK Explorer live refresh stopped: ${err instanceof Error ? err.message : String(err)}`, + ), + }); + await options.ioHelper.defaults.info(`CDK Explorer running at ${server.url}`); + + await new Promise((resolve) => { + const onSignal = () => { + process.removeListener('SIGINT', onSignal); + process.removeListener('SIGTERM', onSignal); + resolve(); + }; + process.once('SIGINT', onSignal); + process.once('SIGTERM', onSignal); + }); + + await server.stop(); + return 0; +} diff --git a/packages/aws-cdk/test/cli/cli-arguments.test.ts b/packages/aws-cdk/test/cli/cli-arguments.test.ts index defb0d3a5..802a1a7ba 100644 --- a/packages/aws-cdk/test/cli/cli-arguments.test.ts +++ b/packages/aws-cdk/test/cli/cli-arguments.test.ts @@ -191,6 +191,7 @@ describe('config', () => { gc: expect.anything(), flags: expect.anything(), doctor: expect.anything(), + explore: expect.anything(), docs: expect.anything(), lsp: expect.anything(), orphan: expect.anything(), diff --git a/packages/aws-cdk/test/commands/explore.test.ts b/packages/aws-cdk/test/commands/explore.test.ts new file mode 100644 index 000000000..5e3eb714d --- /dev/null +++ b/packages/aws-cdk/test/commands/explore.test.ts @@ -0,0 +1,27 @@ +import { explore } from '../../lib/commands/explore'; + +describe('explore command', () => { + test('starts server and prints URL', async () => { + const messages: string[] = []; + const fakeIoHelper = { + defaults: { + info: async (msg: string) => { + messages.push(msg); + }, + }, + }; + + // Run explore in background, then immediately send SIGINT to unblock it + const resultPromise = explore({ ioHelper: fakeIoHelper as any }); + + // Give the server time to start, then signal exit + await new Promise((r) => setTimeout(r, 100)); + process.emit('SIGINT', 'SIGINT'); + + const exitCode = await resultPromise; + + expect(exitCode).toBe(0); + expect(messages).toHaveLength(1); + expect(messages[0]).toMatch(/CDK Explorer running at http:\/\/localhost:\d+/); + }); +}); diff --git a/yarn.lock b/yarn.lock index d4d54ecb7..6ce186174 100644 --- a/yarn.lock +++ b/yarn.lock @@ -167,16 +167,24 @@ __metadata: "@aws-cdk/cloud-assembly-schema": "npm:^0.0.0" "@aws-cdk/toolkit-lib": "npm:^0.0.0" "@cdklabs/eslint-plugin": "npm:^2.0.11" + "@cloudscape-design/components": "npm:^3" + "@cloudscape-design/global-styles": "npm:^1" "@jridgewell/trace-mapping": "npm:^0.3" "@stylistic/eslint-plugin": "npm:^3" "@types/convert-source-map": "npm:^2" + "@types/express": "npm:^4" "@types/jest": "npm:^29.5.14" "@types/node": "npm:^20" + "@types/prismjs": "npm:^1" + "@types/react": "npm:^18" + "@types/react-dom": "npm:^18" + "@types/supertest": "npm:^6" "@typescript-eslint/eslint-plugin": "npm:^8" "@typescript-eslint/parser": "npm:^8" chokidar: "npm:^4" constructs: "npm:^10.0.0" convert-source-map: "npm:^2" + esbuild: "npm:^0.28.1" eslint: "npm:^9" eslint-config-prettier: "npm:^10.1.8" eslint-import-resolver-typescript: "npm:^4.4.5" @@ -188,13 +196,19 @@ __metadata: jest-junit: "npm:^16" nx: "npm:^22.7.6" prettier: "npm:^2.8" + prismjs: "npm:^1" projen: "npm:^0.100.7" + react: "npm:^18" + react-dom: "npm:^18" + supertest: "npm:^6" ts-jest: "npm:^29.4.11" + tsx: "npm:^4.22.4" typescript: "npm:5.9" vscode-jsonrpc: "npm:^8" vscode-languageserver: "npm:^9" vscode-languageserver-protocol: "npm:^3" vscode-languageserver-textdocument: "npm:^1" + yaml: "npm:^2" languageName: unknown linkType: soft @@ -1754,6 +1768,13 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.0, @babel/runtime@npm:^7.8.7": + version: 7.29.7 + resolution: "@babel/runtime@npm:7.29.7" + checksum: 10c0/ca11572f7146b21e0bde6a9ed4bb6a89eafbee5f0944c7eb54d0d8a2dac962c33638a1d611e14faa71dfbb92b4b5f9236232208568a6b7d5c6f3f39ddb91771e + languageName: node + linkType: hard + "@babel/template@npm:^7.29.7, @babel/template@npm:^7.3.3": version: 7.29.7 resolution: "@babel/template@npm:7.29.7" @@ -1841,6 +1862,83 @@ __metadata: languageName: node linkType: hard +"@cloudscape-design/collection-hooks@npm:^1.0.0": + version: 1.0.103 + resolution: "@cloudscape-design/collection-hooks@npm:1.0.103" + dependencies: + "@cloudscape-design/component-toolkit": "npm:^1.0.0-beta" + peerDependencies: + react: ">=16.8.0" + checksum: 10c0/c26ad2e7f529ceb9952aadc627ac0b6c8c52b68017da9af8eef7d0b5bb21dfd2fa1e957c670bddc0334556b36a38d70da0458d6c894ce0de049bad2a5bf204c7 + languageName: node + linkType: hard + +"@cloudscape-design/component-toolkit@npm:^1.0.0-beta": + version: 1.0.0-beta.170 + resolution: "@cloudscape-design/component-toolkit@npm:1.0.0-beta.170" + dependencies: + tslib: "npm:^2.3.1" + weekstart: "npm:^2.0.0" + peerDependencies: + react: ">=16.8.0" + checksum: 10c0/54d23bd9d3ef4cf2dd1573b95a44104e4036a2c281144cc7a5e034d4beefad85b83776e2db31a7d639c6dfa0e39912824d67a9f19cb1d0cdedf16e651f70c741 + languageName: node + linkType: hard + +"@cloudscape-design/components@npm:^3": + version: 3.0.1330 + resolution: "@cloudscape-design/components@npm:3.0.1330" + dependencies: + "@cloudscape-design/collection-hooks": "npm:^1.0.0" + "@cloudscape-design/component-toolkit": "npm:^1.0.0-beta" + "@cloudscape-design/test-utils-core": "npm:^1.0.0" + "@cloudscape-design/theming-runtime": "npm:^1.0.0" + "@dnd-kit/core": "npm:^6.0.8" + "@dnd-kit/sortable": "npm:^7.0.2" + "@dnd-kit/utilities": "npm:^3.2.1" + ace-builds: "npm:^1.34.0" + clsx: "npm:^1.1.0" + d3-shape: "npm:^1.3.7" + date-fns: "npm:^2.25.0" + intl-messageformat: "npm:^10.3.1" + mnth: "npm:^2.0.0" + react-is: "npm:^18.2.0" + react-transition-group: "npm:^4.4.2" + tslib: "npm:^2.4.0" + weekstart: "npm:^1.1.0" + peerDependencies: + react: ">=16.8.0" + checksum: 10c0/b5bebf97d4a8630ed23d60cfb23db0eaecd62247e0244e5b6da608c4a3b33b309d363f37cf8683879b9ac55b60b0eb4f2e4b56e216593856c1ec23da78bd0a5a + languageName: node + linkType: hard + +"@cloudscape-design/global-styles@npm:^1": + version: 1.0.62 + resolution: "@cloudscape-design/global-styles@npm:1.0.62" + checksum: 10c0/c94d4a3a30ea71806a42d0198b3afefbe24510c491810610c222fa1368b06d59aeef4926bc827c9dc75dcd5ce61b4c7d2891c3b1e08819e0bbd3eba9e1632a88 + languageName: node + linkType: hard + +"@cloudscape-design/test-utils-core@npm:^1.0.0": + version: 1.0.85 + resolution: "@cloudscape-design/test-utils-core@npm:1.0.85" + dependencies: + css-selector-tokenizer: "npm:^0.8.0" + css.escape: "npm:^1.5.1" + checksum: 10c0/d79a0bfe5043068d5c9c61699e9baba250076346964e067d0c67244550a51f18b5570816f43d653058067969e65805e3ed416842ea451a47ac462c040b245522 + languageName: node + linkType: hard + +"@cloudscape-design/theming-runtime@npm:^1.0.0": + version: 1.0.121 + resolution: "@cloudscape-design/theming-runtime@npm:1.0.121" + dependencies: + "@material/material-color-utilities": "npm:^0.3.0" + tslib: "npm:^2.4.0" + checksum: 10c0/23a723dbbfb48013f5db941ff8d1db21a4679f411219535f9eaed9c52f458cac2feb8ee34ec885500f4e36650dcdfe44d0b35b1594090da9d7c4de24c75fa7db + languageName: node + linkType: hard + "@cspotcode/source-map-support@npm:^0.8.0": version: 0.8.1 resolution: "@cspotcode/source-map-support@npm:0.8.1" @@ -1850,6 +1948,55 @@ __metadata: languageName: node linkType: hard +"@dnd-kit/accessibility@npm:^3.1.1": + version: 3.1.1 + resolution: "@dnd-kit/accessibility@npm:3.1.1" + dependencies: + tslib: "npm:^2.0.0" + peerDependencies: + react: ">=16.8.0" + checksum: 10c0/be0bf41716dc58f9386bc36906ec1ce72b7b42b6d1d0e631d347afe9bd8714a829bd6f58a346dd089b1519e93918ae2f94497411a61a4f5e4d9247c6cfd1fef8 + languageName: node + linkType: hard + +"@dnd-kit/core@npm:^6.0.8": + version: 6.3.1 + resolution: "@dnd-kit/core@npm:6.3.1" + dependencies: + "@dnd-kit/accessibility": "npm:^3.1.1" + "@dnd-kit/utilities": "npm:^3.2.2" + tslib: "npm:^2.0.0" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 10c0/196db95d81096d9dc248983533eab91ba83591770fa5c894b1ac776f42af0d99522b3fd5bb3923411470e4733fcfa103e6ee17adc17b9b7eb54c7fbec5ff7c52 + languageName: node + linkType: hard + +"@dnd-kit/sortable@npm:^7.0.2": + version: 7.0.2 + resolution: "@dnd-kit/sortable@npm:7.0.2" + dependencies: + "@dnd-kit/utilities": "npm:^3.2.0" + tslib: "npm:^2.0.0" + peerDependencies: + "@dnd-kit/core": ^6.0.7 + react: ">=16.8.0" + checksum: 10c0/06aeb113eeeb470bb2443bf1c48d597157bb3a1caa9740e60c2fa73a3076e753cd083a2d381f0556bd7e9873e851a49ce8ea14796ac02e2d796eabea4e27196d + languageName: node + linkType: hard + +"@dnd-kit/utilities@npm:^3.2.0, @dnd-kit/utilities@npm:^3.2.1, @dnd-kit/utilities@npm:^3.2.2": + version: 3.2.2 + resolution: "@dnd-kit/utilities@npm:3.2.2" + dependencies: + tslib: "npm:^2.0.0" + peerDependencies: + react: ">=16.8.0" + checksum: 10c0/9aa90526f3e3fd567b5acc1b625a63177b9e8d00e7e50b2bd0e08fa2bf4dba7e19529777e001fdb8f89a7ce69f30b190c8364d390212634e0afdfa8c395e85a0 + languageName: node + linkType: hard + "@emnapi/core@npm:1.4.5": version: 1.4.5 resolution: "@emnapi/core@npm:1.4.5" @@ -2196,6 +2343,57 @@ __metadata: languageName: node linkType: hard +"@formatjs/ecma402-abstract@npm:2.3.6": + version: 2.3.6 + resolution: "@formatjs/ecma402-abstract@npm:2.3.6" + dependencies: + "@formatjs/fast-memoize": "npm:2.2.7" + "@formatjs/intl-localematcher": "npm:0.6.2" + decimal.js: "npm:^10.4.3" + tslib: "npm:^2.8.0" + checksum: 10c0/63be2a73d3168bf45ab5d50db58376e852db5652d89511ae6e44f1fa03ad96ebbfe9b06a1dfaa743db06e40eb7f33bd77530b9388289855cca79a0e3fc29eacf + languageName: node + linkType: hard + +"@formatjs/fast-memoize@npm:2.2.7": + version: 2.2.7 + resolution: "@formatjs/fast-memoize@npm:2.2.7" + dependencies: + tslib: "npm:^2.8.0" + checksum: 10c0/f5eabb0e4ab7162297df8252b4cfde194b23248120d9df267592eae2be2d2f7c4f670b5a70523d91b4ecdc35d40e65823bb8eeba8dd79fbf8601a972bf3b8866 + languageName: node + linkType: hard + +"@formatjs/icu-messageformat-parser@npm:2.11.4": + version: 2.11.4 + resolution: "@formatjs/icu-messageformat-parser@npm:2.11.4" + dependencies: + "@formatjs/ecma402-abstract": "npm:2.3.6" + "@formatjs/icu-skeleton-parser": "npm:1.8.16" + tslib: "npm:^2.8.0" + checksum: 10c0/3ea9e9dae18282881d19a5f88107b6013f514ec8675684ed2c04bee2a174032377858937243e3bd9c9263a470988a3773a53bf8d208a34a78e7843ce66f87f3b + languageName: node + linkType: hard + +"@formatjs/icu-skeleton-parser@npm:1.8.16": + version: 1.8.16 + resolution: "@formatjs/icu-skeleton-parser@npm:1.8.16" + dependencies: + "@formatjs/ecma402-abstract": "npm:2.3.6" + tslib: "npm:^2.8.0" + checksum: 10c0/6fa1586dc11c925cd8d17e927cc635d238c969a6b7e97282a924376f78622fc25336c407589d19796fb6f8124a0e7765f99ecdb1aac014edcfbe852e7c3d87f3 + languageName: node + linkType: hard + +"@formatjs/intl-localematcher@npm:0.6.2": + version: 0.6.2 + resolution: "@formatjs/intl-localematcher@npm:0.6.2" + dependencies: + tslib: "npm:^2.8.0" + checksum: 10c0/22a17a4c67160b6c9f52667914acfb7b79cd6d80630d4ac6d4599ce447cb89d2a64f7d58fa35c3145ddb37fef893f0a45b9a55e663a4eb1f2ae8b10a89fac235 + languageName: node + linkType: hard + "@gar/promise-retry@npm:^1.0.0, @gar/promise-retry@npm:^1.0.2": version: 1.0.3 resolution: "@gar/promise-retry@npm:1.0.3" @@ -2754,6 +2952,13 @@ __metadata: languageName: node linkType: hard +"@material/material-color-utilities@npm:^0.3.0": + version: 0.3.0 + resolution: "@material/material-color-utilities@npm:0.3.0" + checksum: 10c0/3bef025428b893f2acc9e9e2bd186363a60b7c0836fe43c78222e29fe67dc579618e844f0661a20657ed9f7fd8b94fd43a2961892894d0b6a2ba5264ce2673f8 + languageName: node + linkType: hard + "@microsoft/api-extractor-model@npm:7.33.8": version: 7.33.8 resolution: "@microsoft/api-extractor-model@npm:7.33.8" @@ -2829,6 +3034,13 @@ __metadata: languageName: node linkType: hard +"@noble/hashes@npm:^1.1.5": + version: 1.8.0 + resolution: "@noble/hashes@npm:1.8.0" + checksum: 10c0/06a0b52c81a6fa7f04d67762e08b2c476a00285858150caeaaff4037356dd5e119f45b2a530f638b77a5eeca013168ec1b655db41bae3236cb2e9d511484fc77 + languageName: node + linkType: hard + "@nodable/entities@npm:^2.1.0": version: 2.1.0 resolution: "@nodable/entities@npm:2.1.0" @@ -3297,6 +3509,15 @@ __metadata: languageName: node linkType: hard +"@paralleldrive/cuid2@npm:^2.2.2": + version: 2.3.1 + resolution: "@paralleldrive/cuid2@npm:2.3.1" + dependencies: + "@noble/hashes": "npm:^1.1.5" + checksum: 10c0/6576b73de49d826b0f33cbab88424dec1f6fa454a9e59a7b621f78c2cfdd2e59d7f48175826d698940a717f45eeb5e87a508583a7316e608f6a05a861a40c129 + languageName: node + linkType: hard + "@rtsao/scc@npm:^1.1.0": version: 1.1.0 resolution: "@rtsao/scc@npm:1.1.0" @@ -3839,6 +4060,16 @@ __metadata: languageName: node linkType: hard +"@types/body-parser@npm:*": + version: 1.19.6 + resolution: "@types/body-parser@npm:1.19.6" + dependencies: + "@types/connect": "npm:*" + "@types/node": "npm:*" + checksum: 10c0/542da05c924dce58ee23f50a8b981fee36921850c82222e384931fda3e106f750f7880c47be665217d72dbe445129049db6eb1f44e7a06b09d62af8f3cca8ea7 + languageName: node + linkType: hard + "@types/chai@npm:^5.2.2": version: 5.2.3 resolution: "@types/chai@npm:5.2.3" @@ -3849,6 +4080,15 @@ __metadata: languageName: node linkType: hard +"@types/connect@npm:*": + version: 3.4.38 + resolution: "@types/connect@npm:3.4.38" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/2e1cdba2c410f25649e77856505cd60223250fa12dff7a503e492208dbfdd25f62859918f28aba95315251fd1f5e1ffbfca1e25e73037189ab85dd3f8d0a148c + languageName: node + linkType: hard + "@types/convert-source-map@npm:^2": version: 2.0.3 resolution: "@types/convert-source-map@npm:2.0.3" @@ -3856,6 +4096,13 @@ __metadata: languageName: node linkType: hard +"@types/cookiejar@npm:^2.1.5": + version: 2.1.5 + resolution: "@types/cookiejar@npm:2.1.5" + checksum: 10c0/af38c3d84aebb3ccc6e46fb6afeeaac80fb26e63a487dd4db5a8b87e6ad3d4b845ba1116b2ae90d6f886290a36200fa433d8b1f6fe19c47da6b81872ce9a2764 + languageName: node + linkType: hard + "@types/cors@npm:^2.8.6": version: 2.8.19 resolution: "@types/cors@npm:2.8.19" @@ -3879,6 +4126,30 @@ __metadata: languageName: node linkType: hard +"@types/express-serve-static-core@npm:^4.17.33": + version: 4.19.9 + resolution: "@types/express-serve-static-core@npm:4.19.9" + dependencies: + "@types/node": "npm:*" + "@types/qs": "npm:*" + "@types/range-parser": "npm:*" + "@types/send": "npm:*" + checksum: 10c0/31d7afe63c4f0090159a869e4199aae38cbc222948bf9be5d7c257bfdc8e14c9dec46e9f50c08e0004565565acb7ecd95d5c0dbde6d9fdd45a5d0adfabbf00fe + languageName: node + linkType: hard + +"@types/express@npm:^4": + version: 4.17.25 + resolution: "@types/express@npm:4.17.25" + dependencies: + "@types/body-parser": "npm:*" + "@types/express-serve-static-core": "npm:^4.17.33" + "@types/qs": "npm:*" + "@types/serve-static": "npm:^1" + checksum: 10c0/f42b616d2c9dbc50352c820db7de182f64ebbfa8dba6fb6c98e5f8f0e2ef3edde0131719d9dc6874803d25ad9ca2d53471d0fec2fbc60a6003a43d015bab72c4 + languageName: node + linkType: hard + "@types/fs-extra@npm:^11": version: 11.0.4 resolution: "@types/fs-extra@npm:11.0.4" @@ -3898,6 +4169,13 @@ __metadata: languageName: node linkType: hard +"@types/http-errors@npm:*": + version: 2.0.5 + resolution: "@types/http-errors@npm:2.0.5" + checksum: 10c0/00f8140fbc504f47356512bd88e1910c2f07e04233d99c88c854b3600ce0523c8cd0ba7d1897667243282eb44c59abb9245959e2428b9de004f93937f52f7c15 + languageName: node + linkType: hard + "@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1, @types/istanbul-lib-coverage@npm:^2.0.6": version: 2.0.6 resolution: "@types/istanbul-lib-coverage@npm:2.0.6" @@ -3998,6 +4276,20 @@ __metadata: languageName: node linkType: hard +"@types/methods@npm:^1.1.4": + version: 1.1.4 + resolution: "@types/methods@npm:1.1.4" + checksum: 10c0/a78534d79c300718298bfff92facd07bf38429c66191f640c1db4c9cff1e36f819304298a96f7536b6512bfc398e5c3e6b831405e138cd774b88ad7be78d682a + languageName: node + linkType: hard + +"@types/mime@npm:^1": + version: 1.3.5 + resolution: "@types/mime@npm:1.3.5" + checksum: 10c0/c2ee31cd9b993804df33a694d5aa3fa536511a49f2e06eeab0b484fef59b4483777dbb9e42a4198a0809ffbf698081fdbca1e5c2218b82b91603dfab10a10fbc + languageName: node + linkType: hard + "@types/mime@npm:^2": version: 2.0.3 resolution: "@types/mime@npm:2.0.3" @@ -4076,6 +4368,13 @@ __metadata: languageName: node linkType: hard +"@types/prismjs@npm:^1": + version: 1.26.6 + resolution: "@types/prismjs@npm:1.26.6" + checksum: 10c0/152a27500cb32b114edfb77f9d0dccd03bebc84828d1e92abacaf212b22d3ccdde041ce421dd58b6ec8461bbec7cd76ed5ee773cae4be7ca36a6dd4ddcf0f9e7 + languageName: node + linkType: hard + "@types/promptly@npm:^3.0.5": version: 3.0.5 resolution: "@types/promptly@npm:3.0.5" @@ -4085,6 +4384,46 @@ __metadata: languageName: node linkType: hard +"@types/prop-types@npm:*": + version: 15.7.15 + resolution: "@types/prop-types@npm:15.7.15" + checksum: 10c0/b59aad1ad19bf1733cf524fd4e618196c6c7690f48ee70a327eb450a42aab8e8a063fbe59ca0a5701aebe2d92d582292c0fb845ea57474f6a15f6994b0e260b2 + languageName: node + linkType: hard + +"@types/qs@npm:*": + version: 6.15.1 + resolution: "@types/qs@npm:6.15.1" + checksum: 10c0/1dfdbcb4cf2a8f66d57f0b9a9fe6b1c7091cb816687b6698c1351eaf31f62e412cea9b7453a9637b570cd5fad8dced527e5a9e69b4fcc6e318daacd8b749f094 + languageName: node + linkType: hard + +"@types/range-parser@npm:*": + version: 1.2.7 + resolution: "@types/range-parser@npm:1.2.7" + checksum: 10c0/361bb3e964ec5133fa40644a0b942279ed5df1949f21321d77de79f48b728d39253e5ce0408c9c17e4e0fd95ca7899da36841686393b9f7a1e209916e9381a3c + languageName: node + linkType: hard + +"@types/react-dom@npm:^18": + version: 18.3.7 + resolution: "@types/react-dom@npm:18.3.7" + peerDependencies: + "@types/react": ^18.0.0 + checksum: 10c0/8bd309e2c3d1604a28a736a24f96cbadf6c05d5288cfef8883b74f4054c961b6b3a5e997fd5686e492be903c8f3380dba5ec017eff3906b1256529cd2d39603e + languageName: node + linkType: hard + +"@types/react@npm:^18": + version: 18.3.31 + resolution: "@types/react@npm:18.3.31" + dependencies: + "@types/prop-types": "npm:*" + csstype: "npm:^3.2.2" + checksum: 10c0/44180549dd045f536ececd39e39aacdf828e76adc1c4a90b132f453e23cc370c4648d9102ae401172ebd8fd8b1977a901a39e214e53ec77171b27514b588c179 + languageName: node + linkType: hard + "@types/semver@npm:^7, @types/semver@npm:^7.7.1": version: 7.7.1 resolution: "@types/semver@npm:7.7.1" @@ -4092,6 +4431,36 @@ __metadata: languageName: node linkType: hard +"@types/send@npm:*": + version: 1.2.1 + resolution: "@types/send@npm:1.2.1" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/7673747f8c2d8e67f3b1b3b57e9d4d681801a4f7b526ecf09987bb9a84a61cf94aa411c736183884dc762c1c402a61681eb1ef200d8d45d7e5ec0ab67ea5f6c1 + languageName: node + linkType: hard + +"@types/send@npm:<1": + version: 0.17.6 + resolution: "@types/send@npm:0.17.6" + dependencies: + "@types/mime": "npm:^1" + "@types/node": "npm:*" + checksum: 10c0/a9d76797f0637738062f1b974e0fcf3d396a28c5dc18c3f95ecec5dabda82e223afbc2d56a0bca46b6326fd7bb229979916cea40de2270a98128fd94441b87c2 + languageName: node + linkType: hard + +"@types/serve-static@npm:^1": + version: 1.15.10 + resolution: "@types/serve-static@npm:1.15.10" + dependencies: + "@types/http-errors": "npm:*" + "@types/node": "npm:*" + "@types/send": "npm:<1" + checksum: 10c0/842fca14c9e80468f89b6cea361773f2dcd685d4616a9f59013b55e1e83f536e4c93d6d8e3ba5072d40c4e7e64085210edd6646b15d538ded94512940a23021f + languageName: node + linkType: hard + "@types/sinon@npm:^17.0.3, @types/sinon@npm:^17.0.4": version: 17.0.4 resolution: "@types/sinon@npm:17.0.4" @@ -4124,6 +4493,28 @@ __metadata: languageName: node linkType: hard +"@types/superagent@npm:^8.1.0": + version: 8.1.11 + resolution: "@types/superagent@npm:8.1.11" + dependencies: + "@types/cookiejar": "npm:^2.1.5" + "@types/methods": "npm:^1.1.4" + "@types/node": "npm:*" + form-data: "npm:^4.0.0" + checksum: 10c0/79d7ed64a0766628f81b43e9e2475b9e6012e8634df0c5792d80c14d2ad8793f24a7ce6af8e0e9a1cccc247cfa6c4a24ee4024b4af3e790841e5426261123e14 + languageName: node + linkType: hard + +"@types/supertest@npm:^6": + version: 6.0.3 + resolution: "@types/supertest@npm:6.0.3" + dependencies: + "@types/methods": "npm:^1.1.4" + "@types/superagent": "npm:^8.1.0" + checksum: 10c0/a2080f870154b09db123864a484fb633bc9e2a0f7294a194388df4c7effe5af9de36d5a5ebf819f72b404fa47b5e813c47d5a3a51354251fd2fa8589bfb64f2c + languageName: node + linkType: hard + "@types/workerpool@npm:^6": version: 6.4.7 resolution: "@types/workerpool@npm:6.4.7" @@ -4701,6 +5092,13 @@ __metadata: languageName: node linkType: hard +"ace-builds@npm:^1.34.0": + version: 1.44.0 + resolution: "ace-builds@npm:1.44.0" + checksum: 10c0/c5a614b082daef1621e5392024daaa0c472275cff2ac79e85422027a9f12442fcee8ad8362eab7223d0c90feeaa84280d83991f75a64372ade4854fb4ea60291 + languageName: node + linkType: hard + "acorn-jsx@npm:^5.3.2": version: 5.3.2 resolution: "acorn-jsx@npm:5.3.2" @@ -6070,6 +6468,13 @@ __metadata: languageName: node linkType: hard +"clsx@npm:^1.1.0": + version: 1.2.1 + resolution: "clsx@npm:1.2.1" + checksum: 10c0/34dead8bee24f5e96f6e7937d711978380647e936a22e76380290e35486afd8634966ce300fc4b74a32f3762c7d4c0303f442c3e259f4ce02374eb0c82834f27 + languageName: node + linkType: hard + "cmd-shim@npm:^8.0.0": version: 8.0.0 resolution: "cmd-shim@npm:8.0.0" @@ -6246,6 +6651,13 @@ __metadata: languageName: node linkType: hard +"component-emitter@npm:^1.3.0": + version: 1.3.1 + resolution: "component-emitter@npm:1.3.1" + checksum: 10c0/e4900b1b790b5e76b8d71b328da41482118c0f3523a516a41be598dc2785a07fd721098d9bf6e22d89b19f4fa4e1025160dc00317ea111633a3e4f75c2b86032 + languageName: node + linkType: hard + "concat-map@npm:0.0.1": version: 0.0.1 resolution: "concat-map@npm:0.0.1" @@ -6500,6 +6912,13 @@ __metadata: languageName: node linkType: hard +"cookiejar@npm:^2.1.4": + version: 2.1.4 + resolution: "cookiejar@npm:2.1.4" + checksum: 10c0/2dae55611c6e1678f34d93984cbd4bda58f4fe3e5247cc4993f4a305cd19c913bbaf325086ed952e892108115073a747596453d3dc1c34947f47f731818b8ad1 + languageName: node + linkType: hard + "core-util-is@npm:^1.0.3, core-util-is@npm:~1.0.0": version: 1.0.3 resolution: "core-util-is@npm:1.0.3" @@ -6568,6 +6987,23 @@ __metadata: languageName: node linkType: hard +"css-selector-tokenizer@npm:^0.8.0": + version: 0.8.0 + resolution: "css-selector-tokenizer@npm:0.8.0" + dependencies: + cssesc: "npm:^3.0.0" + fastparse: "npm:^1.1.2" + checksum: 10c0/86f68cc666d41f9d153351677694002a9d00e2609e6abc66fcfd7f580be3d6ecc0929e46a06c621ab28da5febbb54567db9709b819414edae4a36d9ff9133e16 + languageName: node + linkType: hard + +"css.escape@npm:^1.5.1": + version: 1.5.1 + resolution: "css.escape@npm:1.5.1" + checksum: 10c0/5e09035e5bf6c2c422b40c6df2eb1529657a17df37fda5d0433d722609527ab98090baf25b13970ca754079a0f3161dd3dfc0e743563ded8cfa0749d861c1525 + languageName: node + linkType: hard + "cssesc@npm:^3.0.0": version: 3.0.0 resolution: "cssesc@npm:3.0.0" @@ -6577,6 +7013,29 @@ __metadata: languageName: node linkType: hard +"csstype@npm:^3.0.2, csstype@npm:^3.2.2": + version: 3.2.3 + resolution: "csstype@npm:3.2.3" + checksum: 10c0/cd29c51e70fa822f1cecd8641a1445bed7063697469d35633b516e60fe8c1bde04b08f6c5b6022136bb669b64c63d4173af54864510fbb4ee23281801841a3ce + languageName: node + linkType: hard + +"d3-path@npm:1": + version: 1.0.9 + resolution: "d3-path@npm:1.0.9" + checksum: 10c0/e35e84df5abc18091f585725b8235e1fa97efc287571585427d3a3597301e6c506dea56b11dfb3c06ca5858b3eb7f02c1bf4f6a716aa9eade01c41b92d497eb5 + languageName: node + linkType: hard + +"d3-shape@npm:^1.3.7": + version: 1.3.7 + resolution: "d3-shape@npm:1.3.7" + dependencies: + d3-path: "npm:1" + checksum: 10c0/548057ce59959815decb449f15632b08e2a1bdce208f9a37b5f98ec7629dda986c2356bc7582308405ce68aedae7d47b324df41507404df42afaf352907577ae + languageName: node + linkType: hard + "dargs@npm:^7.0.0": version: 7.0.0 resolution: "dargs@npm:7.0.0" @@ -6631,6 +7090,15 @@ __metadata: languageName: node linkType: hard +"date-fns@npm:^2.25.0": + version: 2.30.0 + resolution: "date-fns@npm:2.30.0" + dependencies: + "@babel/runtime": "npm:^7.21.0" + checksum: 10c0/e4b521fbf22bc8c3db332bbfb7b094fd3e7627de0259a9d17c7551e2d2702608a7307a449206065916538e384f37b181565447ce2637ae09828427aed9cb5581 + languageName: node + linkType: hard + "date-format@npm:^4.0.14": version: 4.0.14 resolution: "date-format@npm:4.0.14" @@ -6715,6 +7183,13 @@ __metadata: languageName: node linkType: hard +"decimal.js@npm:^10.4.3": + version: 10.6.0 + resolution: "decimal.js@npm:10.6.0" + checksum: 10c0/07d69fbcc54167a340d2d97de95f546f9ff1f69d2b45a02fd7a5292412df3cd9eb7e23065e532a318f5474a2e1bccf8392fdf0443ef467f97f3bf8cb0477e5aa + languageName: node + linkType: hard + "dedent@npm:^1.0.0": version: 1.7.2 resolution: "dedent@npm:1.7.2" @@ -6996,7 +7471,7 @@ __metadata: languageName: node linkType: hard -"dezalgo@npm:^1.0.0": +"dezalgo@npm:^1.0.0, dezalgo@npm:^1.0.4": version: 1.0.4 resolution: "dezalgo@npm:1.0.4" dependencies: @@ -7066,6 +7541,16 @@ __metadata: languageName: node linkType: hard +"dom-helpers@npm:^5.0.1": + version: 5.2.1 + resolution: "dom-helpers@npm:5.2.1" + dependencies: + "@babel/runtime": "npm:^7.8.7" + csstype: "npm:^3.0.2" + checksum: 10c0/f735074d66dd759b36b158fa26e9d00c9388ee0e8c9b16af941c38f014a37fc80782de83afefd621681b19ac0501034b4f1c4a3bff5caa1b8667f0212b5e124c + languageName: node + linkType: hard + "dot-prop@npm:^5.1.0": version: 5.3.0 resolution: "dot-prop@npm:5.3.0" @@ -8013,6 +8498,13 @@ __metadata: languageName: node linkType: hard +"fast-safe-stringify@npm:^2.1.1": + version: 2.1.1 + resolution: "fast-safe-stringify@npm:2.1.1" + checksum: 10c0/d90ec1c963394919828872f21edaa3ad6f1dddd288d2bd4e977027afff09f5db40f94e39536d4646f7e01761d704d72d51dce5af1b93717f3489ef808f5f4e4d + languageName: node + linkType: hard + "fast-uri@npm:^3.0.1": version: 3.1.2 resolution: "fast-uri@npm:3.1.2" @@ -8051,6 +8543,13 @@ __metadata: languageName: node linkType: hard +"fastparse@npm:^1.1.2": + version: 1.1.2 + resolution: "fastparse@npm:1.1.2" + checksum: 10c0/c08d6e7ef10c0928426c1963dd4593e2baaf44d223ab1e5ba5d7b30470144b3a4ecb3605958b73754cea3f857ecef00b67c885f07ca2c312b38b67d9d88b84b5 + languageName: node + linkType: hard + "fastq@npm:^1.6.0": version: 1.20.1 resolution: "fastq@npm:1.20.1" @@ -8251,7 +8750,7 @@ __metadata: languageName: node linkType: hard -"form-data@npm:4.0.6, form-data@npm:^4.0.5": +"form-data@npm:4.0.6, form-data@npm:^4.0.0, form-data@npm:^4.0.5": version: 4.0.6 resolution: "form-data@npm:4.0.6" dependencies: @@ -8264,6 +8763,18 @@ __metadata: languageName: node linkType: hard +"formidable@npm:^2.1.2": + version: 2.1.5 + resolution: "formidable@npm:2.1.5" + dependencies: + "@paralleldrive/cuid2": "npm:^2.2.2" + dezalgo: "npm:^1.0.4" + once: "npm:^1.4.0" + qs: "npm:^6.11.0" + checksum: 10c0/2c68ca6cccc1ac3de497c50236631fafea8e1a09396d88b4dd2dc9db6029b5abaeb6747b8b97ebc1143cd40cf62c27ba485b8c6317088c066fc999af3ac621d4 + languageName: node + linkType: hard + "forwarded@npm:0.2.0": version: 0.2.0 resolution: "forwarded@npm:0.2.0" @@ -9115,6 +9626,18 @@ __metadata: languageName: node linkType: hard +"intl-messageformat@npm:^10.3.1": + version: 10.7.18 + resolution: "intl-messageformat@npm:10.7.18" + dependencies: + "@formatjs/ecma402-abstract": "npm:2.3.6" + "@formatjs/fast-memoize": "npm:2.2.7" + "@formatjs/icu-messageformat-parser": "npm:2.11.4" + tslib: "npm:^2.8.0" + checksum: 10c0/d54da9987335cb2bca26246304cea2ca6b1cb44ca416d6b28f3aa62b11477c72f7ce0bf3f11f5d236ceb1842bdc3378a926e606496d146fde18783ec92c314e1 + languageName: node + linkType: hard + "ip-address@npm:^10.0.1": version: 10.2.0 resolution: "ip-address@npm:10.2.0" @@ -10154,7 +10677,7 @@ __metadata: languageName: node linkType: hard -"js-tokens@npm:^4.0.0": +"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed @@ -10815,6 +11338,17 @@ __metadata: languageName: node linkType: hard +"loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": + version: 1.4.0 + resolution: "loose-envify@npm:1.4.0" + dependencies: + js-tokens: "npm:^3.0.0 || ^4.0.0" + bin: + loose-envify: cli.js + checksum: 10c0/655d110220983c1a4b9c0c679a2e8016d4b67f6e9c7b5435ff5979ecdb20d0813f4dec0a08674fcbdd4846a3f07edbb50a36811fd37930b94aaa0d9daceb017e + languageName: node + linkType: hard + "lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1": version: 11.3.3 resolution: "lru-cache@npm:11.3.3" @@ -11009,7 +11543,7 @@ __metadata: languageName: node linkType: hard -"methods@npm:~1.1.2": +"methods@npm:^1.1.2, methods@npm:~1.1.2": version: 1.1.2 resolution: "methods@npm:1.1.2" checksum: 10c0/bdf7cc72ff0a33e3eede03708c08983c4d7a173f91348b4b1e4f47d4cdbf734433ad971e7d1e8c77247d9e5cd8adb81ea4c67b0a2db526b758b2233d7814b8b2 @@ -11051,7 +11585,7 @@ __metadata: languageName: node linkType: hard -"mime@npm:^2": +"mime@npm:2.6.0, mime@npm:^2": version: 2.6.0 resolution: "mime@npm:2.6.0" bin: @@ -11224,6 +11758,15 @@ __metadata: languageName: node linkType: hard +"mnth@npm:^2.0.0": + version: 2.0.0 + resolution: "mnth@npm:2.0.0" + dependencies: + "@babel/runtime": "npm:^7.8.0" + checksum: 10c0/7767b52008ed7aee6c8ad7c7e6f4a4b2b9102a44aa9454bc3e2beec84e3f2e928c8b75b53b125127aa48d32a44e9bac491a29f7980ca8e83dc77fbbbcee181a8 + languageName: node + linkType: hard + "mock-fs@npm:^5, mock-fs@npm:^5.5.0": version: 5.5.0 resolution: "mock-fs@npm:5.5.0" @@ -11957,7 +12500,7 @@ __metadata: languageName: node linkType: hard -"object-assign@npm:^4": +"object-assign@npm:^4, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 @@ -12788,6 +13331,13 @@ __metadata: languageName: node linkType: hard +"prismjs@npm:^1": + version: 1.30.0 + resolution: "prismjs@npm:1.30.0" + checksum: 10c0/f56205bfd58ef71ccfcbcb691fd0eb84adc96c6ff21b0b69fc6fdcf02be42d6ef972ba4aed60466310de3d67733f6a746f89f2fb79c00bf217406d465b3e8f23 + languageName: node + linkType: hard + "proc-log@npm:^6.0.0, proc-log@npm:^6.1.0": version: 6.1.0 resolution: "proc-log@npm:6.1.0" @@ -12878,6 +13428,17 @@ __metadata: languageName: node linkType: hard +"prop-types@npm:^15.6.2": + version: 15.8.1 + resolution: "prop-types@npm:15.8.1" + dependencies: + loose-envify: "npm:^1.4.0" + object-assign: "npm:^4.1.1" + react-is: "npm:^16.13.1" + checksum: 10c0/59ece7ca2fb9838031d73a48d4becb9a7cc1ed10e610517c7d8f19a1e02fa47f7c27d557d8a5702bec3cfeccddc853579832b43f449e54635803f277b1c78077 + languageName: node + linkType: hard + "propagate@npm:^2.0.0": version: 2.0.1 resolution: "propagate@npm:2.0.1" @@ -12971,6 +13532,16 @@ __metadata: languageName: node linkType: hard +"qs@npm:^6.11.0": + version: 6.15.3 + resolution: "qs@npm:6.15.3" + dependencies: + es-define-property: "npm:^1.0.1" + side-channel: "npm:^1.1.1" + checksum: 10c0/8f3f6e45ece255347d57696628401cde29e9ec649fff698b53bd3150dea7cefdf33036e1bc1826b9f110bfa7cb0ec4ab9f5297eca628ce216c55af82c304e08e + languageName: node + linkType: hard + "qs@npm:~6.14.0": version: 6.14.2 resolution: "qs@npm:6.14.2" @@ -13048,13 +13619,56 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^18.0.0, react-is@npm:^18.3.1": +"react-dom@npm:^18": + version: 18.3.1 + resolution: "react-dom@npm:18.3.1" + dependencies: + loose-envify: "npm:^1.1.0" + scheduler: "npm:^0.23.2" + peerDependencies: + react: ^18.3.1 + checksum: 10c0/a752496c1941f958f2e8ac56239172296fcddce1365ce45222d04a1947e0cc5547df3e8447f855a81d6d39f008d7c32eab43db3712077f09e3f67c4874973e85 + languageName: node + linkType: hard + +"react-is@npm:^16.13.1": + version: 16.13.1 + resolution: "react-is@npm:16.13.1" + checksum: 10c0/33977da7a5f1a287936a0c85639fec6ca74f4f15ef1e59a6bc20338fc73dc69555381e211f7a3529b8150a1f71e4225525b41b60b52965bda53ce7d47377ada1 + languageName: node + linkType: hard + +"react-is@npm:^18.0.0, react-is@npm:^18.2.0, react-is@npm:^18.3.1": version: 18.3.1 resolution: "react-is@npm:18.3.1" checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072 languageName: node linkType: hard +"react-transition-group@npm:^4.4.2": + version: 4.4.5 + resolution: "react-transition-group@npm:4.4.5" + dependencies: + "@babel/runtime": "npm:^7.5.5" + dom-helpers: "npm:^5.0.1" + loose-envify: "npm:^1.4.0" + prop-types: "npm:^15.6.2" + peerDependencies: + react: ">=16.6.0" + react-dom: ">=16.6.0" + checksum: 10c0/2ba754ba748faefa15f87c96dfa700d5525054a0141de8c75763aae6734af0740e77e11261a1e8f4ffc08fd9ab78510122e05c21c2d79066c38bb6861a886c82 + languageName: node + linkType: hard + +"react@npm:^18": + version: 18.3.1 + resolution: "react@npm:18.3.1" + dependencies: + loose-envify: "npm:^1.1.0" + checksum: 10c0/283e8c5efcf37802c9d1ce767f302dd569dd97a70d9bb8c7be79a789b9902451e0d16334b05d73299b20f048cbc3c7d288bbbde10b701fa194e2089c237dbea3 + languageName: node + linkType: hard + "read-cmd-shim@npm:^6.0.0": version: 6.0.0 resolution: "read-cmd-shim@npm:6.0.0" @@ -13518,6 +14132,15 @@ __metadata: languageName: node linkType: hard +"scheduler@npm:^0.23.2": + version: 0.23.2 + resolution: "scheduler@npm:0.23.2" + dependencies: + loose-envify: "npm:^1.1.0" + checksum: 10c0/26383305e249651d4c58e6705d5f8425f153211aef95f15161c151f7b8de885f24751b377e4a0b3dd42cce09aad3f87a61dab7636859c0d89b7daf1a1e2a5c78 + languageName: node + linkType: hard + "semver-intersect@npm:^1.5.0": version: 1.5.0 resolution: "semver-intersect@npm:1.5.0" @@ -13554,7 +14177,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7, semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.7.1, semver@npm:^7.7.2, semver@npm:^7.7.3, semver@npm:^7.7.4, semver@npm:^7.8.0, semver@npm:^7.8.1, semver@npm:^7.8.4, semver@npm:^7.8.5": +"semver@npm:^7, semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.7.1, semver@npm:^7.7.2, semver@npm:^7.7.3, semver@npm:^7.7.4, semver@npm:^7.8.0, semver@npm:^7.8.1, semver@npm:^7.8.4, semver@npm:^7.8.5": version: 7.8.5 resolution: "semver@npm:7.8.5" bin: @@ -13677,7 +14300,7 @@ __metadata: languageName: node linkType: hard -"side-channel-list@npm:^1.0.0": +"side-channel-list@npm:^1.0.0, side-channel-list@npm:^1.0.1": version: 1.0.1 resolution: "side-channel-list@npm:1.0.1" dependencies: @@ -13725,6 +14348,19 @@ __metadata: languageName: node linkType: hard +"side-channel@npm:^1.1.1": + version: 1.1.1 + resolution: "side-channel@npm:1.1.1" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.4" + side-channel-list: "npm:^1.0.1" + side-channel-map: "npm:^1.0.1" + side-channel-weakmap: "npm:^1.0.2" + checksum: 10c0/dc0ab81d67f61bda9247d053ce93f41c3fd8ad2bdcb9cf9d8d2f8540d488f26d87a5e99ebfc07eea49ec025867b2452b705442d974b1478f0395e69f6bfb3270 + languageName: node + linkType: hard + "signal-exit@npm:3.0.7, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" @@ -14313,6 +14949,34 @@ __metadata: languageName: node linkType: hard +"superagent@npm:^8.1.2": + version: 8.1.2 + resolution: "superagent@npm:8.1.2" + dependencies: + component-emitter: "npm:^1.3.0" + cookiejar: "npm:^2.1.4" + debug: "npm:^4.3.4" + fast-safe-stringify: "npm:^2.1.1" + form-data: "npm:^4.0.0" + formidable: "npm:^2.1.2" + methods: "npm:^1.1.2" + mime: "npm:2.6.0" + qs: "npm:^6.11.0" + semver: "npm:^7.3.8" + checksum: 10c0/016416fc9c3d3a04fb648bc0efb3d3d5c9d96da00de47e4a625d9976d28c6c37ab0a7f185f2c3ec6d653ee8bb522f70fba0c1072aea7774341a6c0269a9fa77f + languageName: node + linkType: hard + +"supertest@npm:^6": + version: 6.3.4 + resolution: "supertest@npm:6.3.4" + dependencies: + methods: "npm:^1.1.2" + superagent: "npm:^8.1.2" + checksum: 10c0/f8c0b6c73b5e87da31feee6ccb36e7af766a438513cad89d6907f22c97edd83b1e765b4c8de955d5f7af4bca5fd0aaf9149ff48e21567dd290b326a8633af2a7 + languageName: node + linkType: hard + "supports-color@npm:7.2.0, supports-color@npm:^7, supports-color@npm:^7.1.0, supports-color@npm:^7.2.0": version: 7.2.0 resolution: "supports-color@npm:7.2.0" @@ -14684,7 +15348,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:2.8.1, tslib@npm:^2.0.1, tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.6.2": +"tslib@npm:2.8.1, tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.1.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.6.2, tslib@npm:^2.8.0": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 @@ -15313,6 +15977,20 @@ __metadata: languageName: node linkType: hard +"weekstart@npm:^1.1.0": + version: 1.1.0 + resolution: "weekstart@npm:1.1.0" + checksum: 10c0/ed35ac719917ee7d1b9261bddb5337c91b894738d619eadf1b35b87f7f21c5edb6f95ff91706de094da79697e7816eebaaf54ccdcbd56b4dbec1dc873bc65de7 + languageName: node + linkType: hard + +"weekstart@npm:^2.0.0": + version: 2.0.0 + resolution: "weekstart@npm:2.0.0" + checksum: 10c0/c955cde61d107c70face430f641543be93caf7c81903626cea4207ca3bed0ae69db39b34b168ff23c7ec65dd656b9b4132142c9c5fb52f287f8bf65d0e65b73a + languageName: node + linkType: hard + "whatwg-url@npm:^5.0.0": version: 5.0.0 resolution: "whatwg-url@npm:5.0.0" @@ -15590,7 +16268,7 @@ __metadata: languageName: node linkType: hard -"yaml@npm:2.9.0, yaml@npm:^2.2.2, yaml@npm:^2.6.0, yaml@npm:^2.9.0": +"yaml@npm:2.9.0, yaml@npm:^2, yaml@npm:^2.2.2, yaml@npm:^2.6.0, yaml@npm:^2.9.0": version: 2.9.0 resolution: "yaml@npm:2.9.0" bin: