diff --git a/.projenrc.ts b/.projenrc.ts index 03815e5c0..dc16bcec3 100644 --- a/.projenrc.ts +++ b/.projenrc.ts @@ -1692,12 +1692,23 @@ 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', ], tsconfig: { compilerOptions: { ...defaultTsOptions, }, + exclude: ['frontend'], }, jestOptions: jestOptionsForProject({ jestConfig: { @@ -1712,6 +1723,10 @@ const cdkExplorer = configureProject( }), ); fixupTestTask(cdkExplorer); +cdkExplorer.postCompileTask.exec('tsx build-tools/bundle-frontend.ts'); +cdkExplorer.tsconfigDev.addInclude('build-tools/**/*.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); // #endregion diff --git a/packages/@aws-cdk/cdk-explorer/.gitignore b/packages/@aws-cdk/cdk-explorer/.gitignore index 7722c6259..5cbc9d581 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 75d96a45f..ed5888bfa 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 2c7be48f1..0a5c77243 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", @@ -28,6 +38,21 @@ "version": "^20", "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", @@ -43,6 +68,10 @@ "version": "^10.0.0", "type": "build" }, + { + "name": "esbuild", + "type": "build" + }, { "name": "eslint-config-prettier", "type": "build" @@ -94,10 +123,29 @@ "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", diff --git a/packages/@aws-cdk/cdk-explorer/.projen/tasks.json b/packages/@aws-cdk/cdk-explorer/.projen/tasks.json index 2116f760c..f17cea020 100644 --- a/packages/@aws-cdk/cdk-explorer/.projen/tasks.json +++ b/packages/@aws-cdk/cdk-explorer/.projen/tasks.json @@ -39,7 +39,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" } ] }, @@ -119,7 +119,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..ee3809586 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/frontend/App.tsx @@ -0,0 +1,40 @@ +import Box from '@cloudscape-design/components/box'; +import Container from '@cloudscape-design/components/container'; +import ContentLayout from '@cloudscape-design/components/content-layout'; +import Grid from '@cloudscape-design/components/grid'; +import Header from '@cloudscape-design/components/header'; +import SpaceBetween from '@cloudscape-design/components/space-between'; +import * as React from 'react'; +import { FilePane } from './components/FilePane'; + +/** Web explorer shell: Resource Tree (left), two file panes, Violations (bottom). Tree/violations are placeholders until the cloud-assembly reader is wired in. */ +export function App(): JSX.Element { + return ( + + CDK Web Explorer + + } + > + + + Resource Tree}> + + Construct tree appears here once the cloud-assembly reader is wired in. + + + + + + + + Violations}> + + Policy-validation violations appear here once the cloud-assembly reader is wired in. + + + + + ); +} 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..3e1c9ed90 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/frontend/api.ts @@ -0,0 +1,17 @@ +import type { DirEntry, FilesResponse, FileResponse } from '../lib/web/protocol'; + +export type { DirEntry, FilesResponse, FileResponse }; + +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 const api = { + listFiles: (dir = ''): Promise => getJson(`/api/files?dir=${encodeURIComponent(dir)}`), + readFile: (filePath: string): Promise => getJson(`/api/file?path=${encodeURIComponent(filePath)}`), +}; diff --git a/packages/@aws-cdk/cdk-explorer/frontend/components/FilePane.tsx b/packages/@aws-cdk/cdk-explorer/frontend/components/FilePane.tsx new file mode 100644 index 000000000..b0a97ac49 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/frontend/components/FilePane.tsx @@ -0,0 +1,115 @@ +import Box from '@cloudscape-design/components/box'; +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 * as React from 'react'; +import { api, type DirEntry } from '../api'; + +interface FilePaneProps { + /** Heading shown in the pane header (e.g. "file 1"). */ + readonly title: string; +} + +/** + * A self-contained code pane with a server-backed file picker. Browses + * directories under the app root via /api/files and shows file contents via + * /api/file. Rendered once per code pane (center and right). + */ +export function FilePane({ title }: FilePaneProps): JSX.Element { + const [picking, setPicking] = React.useState(false); + const [dir, setDir] = React.useState(''); + const [entries, setEntries] = React.useState([]); + const [filePath, setFilePath] = React.useState(); + const [content, setContent] = React.useState(''); + const [error, setError] = React.useState(); + + const browse = React.useCallback(async (nextDir: string) => { + try { + const res = await api.listFiles(nextDir); + setDir(res.dir); + setEntries(res.entries); + setError(undefined); + } catch (err) { + setError(err instanceof Error ? err.message : String(err)); + } + }, []); + + const openPicker = React.useCallback(() => { + setPicking(true); + void browse(''); + }, [browse]); + + const choose = React.useCallback(async (entry: DirEntry) => { + if (entry.type === 'dir') { + void browse(entry.path); + return; + } + try { + const res = await api.readFile(entry.path); + setFilePath(res.path); + setContent(res.content); + setPicking(false); + setError(undefined); + } catch (err) { + setError(err instanceof Error ? err.message : String(err)); + } + }, [browse]); + + return ( + Open file…}> + {filePath ?? title} + + } + > + + {error && {error}} + {picking ? ( + browse(parentOf(dir))} /> + ) : ( +
{content || 'No file selected.'}
+ )} +
+
+ ); +} + +const CODE_STYLE: React.CSSProperties = { + margin: 0, + maxHeight: '60vh', + overflow: 'auto', + fontFamily: 'Monaco, Menlo, "Courier New", monospace', + fontSize: '12px', + whiteSpace: 'pre', +}; + +function FileBrowser(props: { + readonly dir: string; + readonly entries: readonly DirEntry[]; + readonly onChoose: (entry: DirEntry) => void; + readonly onUp: () => void; +}): JSX.Element { + return ( + + /{props.dir} + {props.dir !== '' && } + {props.entries.map((entry) => ( + + ))} + + ); +} + +function parentOf(dir: string): string { + const idx = dir.lastIndexOf('/'); + return idx === -1 ? '' : dir.slice(0, idx); +} 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..8aacc9e5a --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + 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..8f261abc4 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/frontend/index.tsx @@ -0,0 +1,17 @@ +import '@cloudscape-design/global-styles/index.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/lib/web/protocol.ts b/packages/@aws-cdk/cdk-explorer/lib/web/protocol.ts new file mode 100644 index 000000000..a4f512935 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/lib/web/protocol.ts @@ -0,0 +1,18 @@ +/** HTTP contract shared between the web server and the SPA. Types only. */ + +export interface DirEntry { + readonly name: string; + /** Path relative to the app directory, usable as the next `dir`/`path` value. POSIX separators. */ + readonly path: string; + readonly type: 'dir' | 'file'; +} + +export interface FilesResponse { + readonly dir: string; + readonly entries: readonly DirEntry[]; +} + +export interface FileResponse { + readonly path: string; + readonly content: string; +} 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..433400733 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/lib/web/routes.ts @@ -0,0 +1,106 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { type Router, type Express } from 'express'; +// eslint-disable-next-line @typescript-eslint/no-require-imports +import express = require('express'); +import type { DirEntry } from './protocol'; +import { resolveWithinRoot } from './safe-path'; + +/** Largest file the viewer will return inline, to avoid streaming huge artifacts. */ +const MAX_FILE_BYTES = 2 * 1024 * 1024; + +export interface ApiOptions { + /** Root of the CDK app; all file listing/reading is confined to this directory. */ + readonly appDir: string; +} + +export function createApiRouter(options: ApiOptions): Router { + const appDir = canonicalDir(options.appDir); + const router = express.Router(); + + router.get('/health', (_req, res) => { + res.json({ status: 'ok' }); + }); + + router.get('/files', (req, res) => { + const dir = typeof req.query.dir === 'string' ? req.query.dir : ''; + const resolved = resolveWithinRoot(appDir, dir); + 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: 'directory not found' }); + } + if (!stat.isDirectory()) { + return res.status(400).json({ error: 'not a directory' }); + } + return res.json({ dir: toPosix(path.relative(appDir, resolved)), entries: listDir(appDir, resolved) }); + }); + + 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') }); + }); + + return router; +} + +export function registerApi(app: Express, options: ApiOptions): void { + app.use('/api', createApiRouter(options)); +} + +function listDir(appDir: string, dir: string): DirEntry[] { + return fs.readdirSync(dir, { withFileTypes: true }) + .map((entry): DirEntry => ({ + name: entry.name, + path: toPosix(path.relative(appDir, path.join(dir, entry.name))), + type: entry.isDirectory() ? 'dir' : 'file', + })) + .sort(byTypeThenName); +} + +function byTypeThenName(a: DirEntry, b: DirEntry): number { + if (a.type !== b.type) return a.type === 'dir' ? -1 : 1; + return a.name.localeCompare(b.name); +} + +/** 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 index 338a77106..56021d169 100644 --- a/packages/@aws-cdk/cdk-explorer/lib/web/server.ts +++ b/packages/@aws-cdk/cdk-explorer/lib/web/server.ts @@ -1,6 +1,8 @@ import * as http from 'http'; // eslint-disable-next-line @typescript-eslint/no-require-imports import express = require('express'); +import { registerApi } from './routes'; +import { indexHtml, webAsset } from './web-assets'; export const DEFAULT_PORT = 4200; const MAX_PORT_ATTEMPTS = 100; @@ -8,6 +10,11 @@ const MAX_PORT_ATTEMPTS = 100; export interface WebServerOptions { readonly port?: number; readonly host?: string; + /** + * Root of the CDK app. File listing/reading is confined here. Defaults to + * `process.cwd()`. + */ + readonly appDir?: string; } export interface WebServer { @@ -25,11 +32,25 @@ export interface WebServer { */ export async function startWebServer(options: WebServerOptions = {}): Promise { const host = options.host ?? '127.0.0.1'; + const appDir = options.appDir ?? process.cwd(); const app = express(); - app.get('/api/health', (_req, res) => { - res.json({ status: 'ok' }); + registerApi(app, { appDir }); + + // 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. + app.get('/:asset', (req, res, next) => { + const asset = webAsset(req.params.asset); + if (!asset) return next(); + return res.type(asset.contentType).send(asset.body); + }); + app.get('*', (_req, res) => { + const index = indexHtml(); + res.type(index.contentType).send(index.body); }); const server = http.createServer(app); 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 40ac05126..981fc453b 100644 --- a/packages/@aws-cdk/cdk-explorer/package.json +++ b/packages/@aws-cdk/cdk-explorer/package.json @@ -31,14 +31,20 @@ }, "devDependencies": { "@cdklabs/eslint-plugin": "^2.0.8", + "@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/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", @@ -51,7 +57,11 @@ "nx": "^22.7.5", "prettier": "^2.8", "projen": "^0.99.70", + "react": "^18", + "react-dom": "^18", + "supertest": "^6", "ts-jest": "^29.4.11", + "tsx": "^4.22.4", "typescript": "5.9", "vscode-languageserver-protocol": "^3" }, 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..9028bfbe0 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/test/web/routes.test.ts @@ -0,0 +1,113 @@ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +// 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 { createApiRouter } from '../../lib/web/routes'; + +let appDir: string; +let app: express.Express; + +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 })); +}); + +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/files', () => { + test('lists the root directory with directories first', async () => { + const res = await request(app).get('/api/files'); + expect(res.status).toBe(200); + expect(res.body.dir).toBe(''); + expect(res.body.entries).toEqual([ + { name: 'lib', path: 'lib', type: 'dir' }, + { name: 'app.ts', path: 'app.ts', type: 'file' }, + ]); + }); + + test('lists a subdirectory', async () => { + const res = await request(app).get('/api/files').query({ dir: 'lib' }); + expect(res.status).toBe(200); + expect(res.body.entries).toEqual([{ name: 'stack.ts', path: path.join('lib', 'stack.ts'), type: 'file' }]); + }); + + test('rejects traversal outside the app directory with 403', async () => { + const res = await request(app).get('/api/files').query({ dir: '../..' }); + expect(res.status).toBe(403); + }); + + test('returns 404 for a missing directory', async () => { + const res = await request(app).get('/api/files').query({ dir: 'nope' }); + expect(res.status).toBe(404); + }); +}); + +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' }); + }); + + 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 }); + } + }); +}); 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..bf8f23547 --- /dev/null +++ b/packages/@aws-cdk/cdk-explorer/test/web/safe-path.test.ts @@ -0,0 +1,32 @@ +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(); + }); +}); diff --git a/packages/@aws-cdk/cdk-explorer/test/web/server.test.ts b/packages/@aws-cdk/cdk-explorer/test/web/server.test.ts index c73549ae2..ce05239ab 100644 --- a/packages/@aws-cdk/cdk-explorer/test/web/server.test.ts +++ b/packages/@aws-cdk/cdk-explorer/test/web/server.test.ts @@ -56,4 +56,12 @@ describe('Web Server', () => { 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(); + }); }); diff --git a/packages/@aws-cdk/cdk-explorer/tsconfig.dev.json b/packages/@aws-cdk/cdk-explorer/tsconfig.dev.json index ac3e8501d..ed785310b 100644 --- a/packages/@aws-cdk/cdk-explorer/tsconfig.dev.json +++ b/packages/@aws-cdk/cdk-explorer/tsconfig.dev.json @@ -28,7 +28,10 @@ "convert-source-map", "express", "jest", - "node" + "node", + "react-dom", + "react", + "supertest" ], "incremental": true, "skipLibCheck": true, @@ -38,10 +41,12 @@ }, "include": [ "lib/**/*.ts", - "test/**/*.ts" + "test/**/*.ts", + "build-tools/**/*.ts" ], "exclude": [ - "node_modules" + "node_modules", + "frontend" ], "references": [ { 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 7324429b1..54247ff97 100644 --- a/packages/@aws-cdk/cdk-explorer/tsconfig.json +++ b/packages/@aws-cdk/cdk-explorer/tsconfig.json @@ -30,7 +30,10 @@ "convert-source-map", "express", "jest", - "node" + "node", + "react-dom", + "react", + "supertest" ], "incremental": true, "skipLibCheck": true, @@ -40,7 +43,9 @@ "include": [ "lib/**/*.ts" ], - "exclude": [], + "exclude": [ + "frontend" + ], "references": [ { "path": "../cloud-assembly-schema" diff --git a/packages/aws-cdk/THIRD_PARTY_LICENSES b/packages/aws-cdk/THIRD_PARTY_LICENSES index 0a3a5dc06..3cd33d7f3 100644 --- a/packages/aws-cdk/THIRD_PARTY_LICENSES +++ b/packages/aws-cdk/THIRD_PARTY_LICENSES @@ -16039,6 +16039,77 @@ Apache License of your accepting any such warranty or additional liability. +---------------- + +** @jridgewell/resolve-uri@3.1.2 - https://www.npmjs.com/package/@jridgewell/resolve-uri/v/3.1.2 | MIT +Copyright 2019 Justin Ridgewell + +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. + +---------------- + +** @jridgewell/sourcemap-codec@1.5.5 - https://www.npmjs.com/package/@jridgewell/sourcemap-codec/v/1.5.5 | MIT +Copyright 2024 Justin Ridgewell + +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. + + +---------------- + +** @jridgewell/trace-mapping@0.3.31 - https://www.npmjs.com/package/@jridgewell/trace-mapping/v/0.3.31 | MIT +Copyright 2024 Justin Ridgewell + +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. + + ---------------- ** @nodelib/fs.scandir@2.1.5 - https://www.npmjs.com/package/@nodelib/fs.scandir/v/2.1.5 | MIT @@ -29002,6 +29073,34 @@ 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 +Copyright 2013 Thorsten Lorenz. +All rights reserved. + +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. + + ---------------- ** cookie-signature@1.0.7 - https://www.npmjs.com/package/cookie-signature/v/1.0.7 | MIT diff --git a/yarn.lock b/yarn.lock index 787d55ea9..78a67465e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -179,16 +179,22 @@ __metadata: "@aws-cdk/cloud-assembly-schema": "npm:^0.0.0" "@aws-cdk/toolkit-lib": "npm:^0.0.0" "@cdklabs/eslint-plugin": "npm:^2.0.8" + "@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/react": "npm:^18" + "@types/react-dom": "npm:^18" + "@types/supertest": "npm:^6" "@typescript-eslint/eslint-plugin": "npm:^8" "@typescript-eslint/parser": "npm:^8" 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" @@ -202,7 +208,11 @@ __metadata: nx: "npm:^22.7.5" prettier: "npm:^2.8" projen: "npm:^0.99.70" + 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" @@ -3835,6 +3845,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.28.6, @babel/template@npm:^7.3.3": version: 7.28.6 resolution: "@babel/template@npm:7.28.6" @@ -3922,6 +3939,83 @@ __metadata: languageName: node linkType: hard +"@cloudscape-design/collection-hooks@npm:^1.0.0": + version: 1.0.98 + resolution: "@cloudscape-design/collection-hooks@npm:1.0.98" + dependencies: + "@cloudscape-design/component-toolkit": "npm:^1.0.0-beta" + peerDependencies: + react: ">=16.8.0" + checksum: 10c0/74bfe864a896f2f870fbeec8be915b6f50a109c6539cb98f18dbb8e29c17163c733a92d6810d053a0c489f7988c3ea568a9a4622d051bf69b315f7cba72a04db + languageName: node + linkType: hard + +"@cloudscape-design/component-toolkit@npm:^1.0.0-beta": + version: 1.0.0-beta.165 + resolution: "@cloudscape-design/component-toolkit@npm:1.0.0-beta.165" + dependencies: + tslib: "npm:^2.3.1" + weekstart: "npm:^2.0.0" + peerDependencies: + react: ">=16.8.0" + checksum: 10c0/e8cc367653f36d48a1dd2c261ec46900df5e12246f615f6ae16824d37913001377004f030d2cc8f14916bb134111693e42169f133615620d430fe3c766f48a75 + languageName: node + linkType: hard + +"@cloudscape-design/components@npm:^3": + version: 3.0.1310 + resolution: "@cloudscape-design/components@npm:3.0.1310" + 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/f52cec1103c77a9c478fa00de85026cb226840d20831c306d41129a0446566032b79c15f525aa30f0826828107e0410b4f031b5d8347954b1edee60e9f0af62d + languageName: node + linkType: hard + +"@cloudscape-design/global-styles@npm:^1": + version: 1.0.60 + resolution: "@cloudscape-design/global-styles@npm:1.0.60" + checksum: 10c0/51b99ef21c10ca73c91571550f1e886fa73dd6b12339d6f6a2cdd43a359ebabbf048e93a611ca41ac069a528d35a1ef9570278cd732f2f649d574f945d4832e3 + languageName: node + linkType: hard + +"@cloudscape-design/test-utils-core@npm:^1.0.0": + version: 1.0.83 + resolution: "@cloudscape-design/test-utils-core@npm:1.0.83" + dependencies: + css-selector-tokenizer: "npm:^0.8.0" + css.escape: "npm:^1.5.1" + checksum: 10c0/2d99192bc89699187fc1b6c96c7eea305f530cf47082c6d5b37eed1258850a9a4d792845dcd77a2b24362fc823f2b33c7f3da04c218fabf7e2c221e97d0569b1 + languageName: node + linkType: hard + +"@cloudscape-design/theming-runtime@npm:^1.0.0": + version: 1.0.115 + resolution: "@cloudscape-design/theming-runtime@npm:1.0.115" + dependencies: + "@material/material-color-utilities": "npm:^0.3.0" + tslib: "npm:^2.4.0" + checksum: 10c0/509c9369f7eef017047fe08206259cd386720e438d1665293dc755773fb9ab151041480c859e9d23f8b8b745ca27dd02e35efa1ab41d33fe46832c476d1365b4 + 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" @@ -3931,6 +4025,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" @@ -4014,6 +4157,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/aix-ppc64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/aix-ppc64@npm:0.28.1" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/android-arm64@npm:0.28.0" @@ -4021,6 +4171,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/android-arm64@npm:0.28.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/android-arm@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/android-arm@npm:0.28.0" @@ -4028,6 +4185,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/android-arm@npm:0.28.1" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@esbuild/android-x64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/android-x64@npm:0.28.0" @@ -4035,6 +4199,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-x64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/android-x64@npm:0.28.1" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + "@esbuild/darwin-arm64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/darwin-arm64@npm:0.28.0" @@ -4042,6 +4213,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-arm64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/darwin-arm64@npm:0.28.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/darwin-x64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/darwin-x64@npm:0.28.0" @@ -4049,6 +4227,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-x64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/darwin-x64@npm:0.28.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@esbuild/freebsd-arm64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/freebsd-arm64@npm:0.28.0" @@ -4056,6 +4241,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-arm64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/freebsd-arm64@npm:0.28.1" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/freebsd-x64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/freebsd-x64@npm:0.28.0" @@ -4063,6 +4255,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-x64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/freebsd-x64@npm:0.28.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/linux-arm64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/linux-arm64@npm:0.28.0" @@ -4070,6 +4269,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/linux-arm64@npm:0.28.1" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/linux-arm@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/linux-arm@npm:0.28.0" @@ -4077,6 +4283,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/linux-arm@npm:0.28.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@esbuild/linux-ia32@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/linux-ia32@npm:0.28.0" @@ -4084,6 +4297,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ia32@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/linux-ia32@npm:0.28.1" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/linux-loong64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/linux-loong64@npm:0.28.0" @@ -4091,6 +4311,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-loong64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/linux-loong64@npm:0.28.1" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + "@esbuild/linux-mips64el@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/linux-mips64el@npm:0.28.0" @@ -4098,6 +4325,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-mips64el@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/linux-mips64el@npm:0.28.1" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + "@esbuild/linux-ppc64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/linux-ppc64@npm:0.28.0" @@ -4105,6 +4339,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ppc64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/linux-ppc64@npm:0.28.1" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/linux-riscv64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/linux-riscv64@npm:0.28.0" @@ -4112,6 +4353,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-riscv64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/linux-riscv64@npm:0.28.1" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + "@esbuild/linux-s390x@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/linux-s390x@npm:0.28.0" @@ -4119,6 +4367,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-s390x@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/linux-s390x@npm:0.28.1" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + "@esbuild/linux-x64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/linux-x64@npm:0.28.0" @@ -4126,6 +4381,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/linux-x64@npm:0.28.1" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@esbuild/netbsd-arm64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/netbsd-arm64@npm:0.28.0" @@ -4133,6 +4395,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-arm64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/netbsd-arm64@npm:0.28.1" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/netbsd-x64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/netbsd-x64@npm:0.28.0" @@ -4140,6 +4409,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/netbsd-x64@npm:0.28.1" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openbsd-arm64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/openbsd-arm64@npm:0.28.0" @@ -4147,6 +4423,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-arm64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/openbsd-arm64@npm:0.28.1" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/openbsd-x64@npm:0.28.0" @@ -4154,6 +4437,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/openbsd-x64@npm:0.28.1" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openharmony-arm64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/openharmony-arm64@npm:0.28.0" @@ -4161,6 +4451,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openharmony-arm64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/openharmony-arm64@npm:0.28.1" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/sunos-x64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/sunos-x64@npm:0.28.0" @@ -4168,6 +4465,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/sunos-x64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/sunos-x64@npm:0.28.1" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + "@esbuild/win32-arm64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/win32-arm64@npm:0.28.0" @@ -4175,6 +4479,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-arm64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/win32-arm64@npm:0.28.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/win32-ia32@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/win32-ia32@npm:0.28.0" @@ -4182,6 +4493,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-ia32@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/win32-ia32@npm:0.28.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/win32-x64@npm:0.28.0": version: 0.28.0 resolution: "@esbuild/win32-x64@npm:0.28.0" @@ -4189,6 +4507,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-x64@npm:0.28.1": + version: 0.28.1 + resolution: "@esbuild/win32-x64@npm:0.28.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@eslint-community/eslint-utils@npm:^4.8.0, @eslint-community/eslint-utils@npm:^4.9.1": version: 4.9.1 resolution: "@eslint-community/eslint-utils@npm:4.9.1" @@ -4277,6 +4602,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" @@ -4866,6 +5242,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" @@ -4941,6 +5324,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, @nodable/entities@npm:^2.1.0": version: 2.1.0 resolution: "@nodable/entities@npm:2.1.0" @@ -5409,6 +5799,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 + "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -6685,6 +7084,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" @@ -6851,6 +7257,13 @@ __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" @@ -6945,6 +7358,13 @@ __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" @@ -6959,6 +7379,25 @@ __metadata: 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/readdir-glob@npm:*": version: 1.1.5 resolution: "@types/readdir-glob@npm:1.1.5" @@ -7037,6 +7476,28 @@ __metadata: languageName: node linkType: hard +"@types/superagent@npm:^8.1.0": + version: 8.1.10 + resolution: "@types/superagent@npm:8.1.10" + dependencies: + "@types/cookiejar": "npm:^2.1.5" + "@types/methods": "npm:^1.1.4" + "@types/node": "npm:*" + form-data: "npm:^4.0.0" + checksum: 10c0/cd911f7e926aae3f23caeebac055c46c5a63576561548356e0ca809147b372a27359112e02a569d527fd677cd90ee6d018b5cdf509a0a5ac70f74289bd29c9bc + 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" @@ -7614,6 +8075,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" @@ -9149,6 +9617,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" @@ -9325,6 +9800,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 + "compress-commons@npm:^6.0.2": version: 6.0.2 resolution: "compress-commons@npm:6.0.2" @@ -9592,6 +10074,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" @@ -9692,6 +10181,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" @@ -9701,6 +10207,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" @@ -9755,6 +10284,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" @@ -9830,6 +10368,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" @@ -10111,7 +10656,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: @@ -10181,6 +10726,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" @@ -10585,6 +11140,95 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:^0.28.1": + version: 0.28.1 + resolution: "esbuild@npm:0.28.1" + dependencies: + "@esbuild/aix-ppc64": "npm:0.28.1" + "@esbuild/android-arm": "npm:0.28.1" + "@esbuild/android-arm64": "npm:0.28.1" + "@esbuild/android-x64": "npm:0.28.1" + "@esbuild/darwin-arm64": "npm:0.28.1" + "@esbuild/darwin-x64": "npm:0.28.1" + "@esbuild/freebsd-arm64": "npm:0.28.1" + "@esbuild/freebsd-x64": "npm:0.28.1" + "@esbuild/linux-arm": "npm:0.28.1" + "@esbuild/linux-arm64": "npm:0.28.1" + "@esbuild/linux-ia32": "npm:0.28.1" + "@esbuild/linux-loong64": "npm:0.28.1" + "@esbuild/linux-mips64el": "npm:0.28.1" + "@esbuild/linux-ppc64": "npm:0.28.1" + "@esbuild/linux-riscv64": "npm:0.28.1" + "@esbuild/linux-s390x": "npm:0.28.1" + "@esbuild/linux-x64": "npm:0.28.1" + "@esbuild/netbsd-arm64": "npm:0.28.1" + "@esbuild/netbsd-x64": "npm:0.28.1" + "@esbuild/openbsd-arm64": "npm:0.28.1" + "@esbuild/openbsd-x64": "npm:0.28.1" + "@esbuild/openharmony-arm64": "npm:0.28.1" + "@esbuild/sunos-x64": "npm:0.28.1" + "@esbuild/win32-arm64": "npm:0.28.1" + "@esbuild/win32-ia32": "npm:0.28.1" + "@esbuild/win32-x64": "npm:0.28.1" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/29cd456a79ce35ac2c7e05fe871330416b2c395c045d849653f843e51378d6e0d6e774d6dcd01b35f4e83238a29bf8decd04fcd34b3780c589a250b21e5f92bb + languageName: node + linkType: hard + "escalade@npm:3.2.0, escalade@npm:^3.1.1, escalade@npm:^3.2.0": version: 3.2.0 resolution: "escalade@npm:3.2.0" @@ -11219,6 +11863,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" @@ -11285,6 +11936,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" @@ -11495,7 +12153,7 @@ __metadata: languageName: node linkType: hard -"form-data@npm:4.0.5, form-data@npm:^4.0.5": +"form-data@npm:4.0.5, form-data@npm:^4.0.0, form-data@npm:^4.0.5": version: 4.0.5 resolution: "form-data@npm:4.0.5" dependencies: @@ -11508,6 +12166,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" @@ -12402,6 +13072,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" @@ -13461,7 +14143,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 @@ -14131,6 +14813,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:^10.2.0": version: 10.4.3 resolution: "lru-cache@npm:10.4.3" @@ -14332,7 +15025,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 @@ -14374,7 +15067,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: @@ -14565,6 +15258,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" @@ -15314,7 +16016,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 @@ -16266,6 +16968,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" @@ -16369,21 +17082,21 @@ __metadata: languageName: node linkType: hard -"qs@npm:~6.14.0": - version: 6.14.2 - resolution: "qs@npm:6.14.2" +"qs@npm:^6.11.0, qs@npm:~6.15.1": + version: 6.15.2 + resolution: "qs@npm:6.15.2" dependencies: side-channel: "npm:^1.1.0" - checksum: 10c0/646110124476fc9acf3c80994c8c3a0600cbad06a4ede1c9e93341006e8426d64e85e048baf8f0c4995f0f1bf0f37d1f3acc5ec1455850b81978792969a60ef6 + checksum: 10c0/e6fd5f6f0aab06d480fe9ab15cebfc4ce4235303e2f91dc69a8f7f4df1e668a61c11d1cfbabacf4295cbbeb7b670ed23db45307480726259761f98e5695e93a7 languageName: node linkType: hard -"qs@npm:~6.15.1": - version: 6.15.2 - resolution: "qs@npm:6.15.2" +"qs@npm:~6.14.0": + version: 6.14.2 + resolution: "qs@npm:6.14.2" dependencies: side-channel: "npm:^1.1.0" - checksum: 10c0/e6fd5f6f0aab06d480fe9ab15cebfc4ce4235303e2f91dc69a8f7f4df1e668a61c11d1cfbabacf4295cbbeb7b670ed23db45307480726259761f98e5695e93a7 + checksum: 10c0/646110124476fc9acf3c80994c8c3a0600cbad06a4ede1c9e93341006e8426d64e85e048baf8f0c4995f0f1bf0f37d1f3acc5ec1455850b81978792969a60ef6 languageName: node linkType: hard @@ -16455,13 +17168,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" @@ -16956,6 +17712,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" @@ -16992,6 +17757,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.3.8": + version: 7.8.4 + resolution: "semver@npm:7.8.4" + bin: + semver: bin/semver.js + checksum: 10c0/81b7c296fd7927b80f67fa516b75fa1017caac8167795320de28e76ccbc6f7f01763c30ecd10d6a0d8fd089708ab0548a5aebb94b0870e99c2a2b4600a46389b + languageName: node + linkType: hard + "semver@npm:^7.8.0": version: 7.8.0 resolution: "semver@npm:7.8.0" @@ -17831,6 +18605,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" @@ -18225,7 +19027,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 @@ -18823,6 +19625,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"