├── .gitignore
├── packages
├── typeslayer
│ ├── src
│ │ ├── components
│ │ │ ├── types-list.tsx
│ │ │ ├── constants.tsx
│ │ │ ├── no-data.tsx
│ │ │ ├── center-loader.tsx
│ │ │ ├── inline-code.tsx
│ │ │ ├── stat-pill.tsx
│ │ │ ├── callout.tsx
│ │ │ ├── show-more.tsx
│ │ │ ├── show-more-children.tsx
│ │ │ ├── tab-label.tsx
│ │ │ ├── openable-path.tsx
│ │ │ ├── action-bar.tsx
│ │ │ └── auth-gate.tsx
│ │ ├── svg.d.ts
│ │ ├── assets
│ │ │ ├── ts.png
│ │ │ ├── typeslayer.png
│ │ │ ├── dimitropoulos.png
│ │ │ ├── ts-nightmare.png
│ │ │ ├── typeslayer-icon.png
│ │ │ └── typeslayer-nightmare.png
│ │ ├── index.html
│ │ ├── types
│ │ │ ├── tauri-plugin-upload.d.ts
│ │ │ └── type-graph.ts
│ │ ├── pages
│ │ │ ├── award-winners
│ │ │ │ ├── title-subtitle.tsx
│ │ │ │ ├── inline-bar-graph.tsx
│ │ │ │ ├── use-award-id.ts
│ │ │ │ ├── award-nav-item.tsx
│ │ │ │ ├── award-winners.tsx
│ │ │ │ └── performance-metrics.tsx
│ │ │ ├── start
│ │ │ │ ├── step.tsx
│ │ │ │ ├── step-1-packagejson.tsx
│ │ │ │ ├── start.tsx
│ │ │ │ └── error-dialog.tsx
│ │ │ ├── cicd-integration.tsx
│ │ │ ├── speedscope.tsx
│ │ │ └── perfetto.tsx
│ │ ├── main.tsx
│ │ ├── shikiTheme.ts
│ │ └── contexts
│ │ │ └── toast-context.tsx
│ ├── .gitignore
│ ├── src-tauri
│ │ ├── build.rs
│ │ ├── src
│ │ │ ├── validate
│ │ │ │ ├── mod.rs
│ │ │ │ └── utils.rs
│ │ │ ├── analyze_trace
│ │ │ │ ├── constants.rs
│ │ │ │ ├── node_module_paths.rs
│ │ │ │ ├── duplicate_node_modules.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── types.rs
│ │ │ │ └── depth_limits.rs
│ │ │ ├── log.rs
│ │ │ ├── mcp
│ │ │ │ ├── tools
│ │ │ │ │ ├── mod.rs
│ │ │ │ │ ├── get_hot_files.rs
│ │ │ │ │ └── get_depth_limits.rs
│ │ │ │ └── status.rs
│ │ │ ├── http_server.rs
│ │ │ ├── utils.rs
│ │ │ ├── main.rs
│ │ │ ├── process_controller.rs
│ │ │ ├── screenshot.rs
│ │ │ ├── treemap.rs
│ │ │ └── auth.rs
│ │ ├── icons
│ │ │ ├── icon.ico
│ │ │ ├── icon.png
│ │ │ ├── 32x32.png
│ │ │ ├── 64x64.png
│ │ │ ├── icon.icns
│ │ │ ├── 128x128.png
│ │ │ ├── StoreLogo.png
│ │ │ ├── 128x128@2x.png
│ │ │ ├── Square30x30Logo.png
│ │ │ ├── Square44x44Logo.png
│ │ │ ├── Square71x71Logo.png
│ │ │ ├── Square89x89Logo.png
│ │ │ ├── Square107x107Logo.png
│ │ │ ├── Square142x142Logo.png
│ │ │ ├── Square150x150Logo.png
│ │ │ ├── Square284x284Logo.png
│ │ │ ├── Square310x310Logo.png
│ │ │ ├── ios
│ │ │ │ ├── AppIcon-512@2x.png
│ │ │ │ ├── AppIcon-20x20@1x.png
│ │ │ │ ├── AppIcon-20x20@2x.png
│ │ │ │ ├── AppIcon-20x20@3x.png
│ │ │ │ ├── AppIcon-29x29@1x.png
│ │ │ │ ├── AppIcon-29x29@2x.png
│ │ │ │ ├── AppIcon-29x29@3x.png
│ │ │ │ ├── AppIcon-40x40@1x.png
│ │ │ │ ├── AppIcon-40x40@2x.png
│ │ │ │ ├── AppIcon-40x40@3x.png
│ │ │ │ ├── AppIcon-60x60@2x.png
│ │ │ │ ├── AppIcon-60x60@3x.png
│ │ │ │ ├── AppIcon-76x76@1x.png
│ │ │ │ ├── AppIcon-76x76@2x.png
│ │ │ │ ├── AppIcon-20x20@2x-1.png
│ │ │ │ ├── AppIcon-29x29@2x-1.png
│ │ │ │ ├── AppIcon-40x40@2x-1.png
│ │ │ │ └── AppIcon-83.5x83.5@2x.png
│ │ │ └── android
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ ├── values
│ │ │ │ └── ic_launcher_background.xml
│ │ │ │ └── mipmap-anydpi-v26
│ │ │ │ └── ic_launcher.xml
│ │ ├── gen
│ │ │ └── schemas
│ │ │ │ └── capabilities.json
│ │ ├── capabilities
│ │ │ └── default.json
│ │ ├── tauri.conf.json
│ │ └── Cargo.toml
│ ├── public
│ │ ├── speedscope-ui
│ │ │ ├── release.txt
│ │ │ ├── jfrview_bg-BLJXNNQB.wasm
│ │ │ ├── SourceCodePro-Regular.ttf-ILST5JV6.woff2
│ │ │ ├── index.html
│ │ │ ├── speedscope-GHPHNKXC.css
│ │ │ └── source-code-pro.LICENSE.md
│ │ └── perfetto-ui
│ │ │ └── v53.0-867ef5020
│ │ │ ├── traceconv.wasm
│ │ │ ├── assets
│ │ │ ├── brand.png
│ │ │ ├── favicon.png
│ │ │ ├── logo-128.png
│ │ │ ├── logo-3d.png
│ │ │ ├── rec_lmk.png
│ │ │ ├── rec_wifi.png
│ │ │ ├── rec_atrace.png
│ │ │ ├── rec_ftrace.png
│ │ │ ├── rec_logcat.png
│ │ │ ├── rec_vmstat.png
│ │ │ ├── Roboto-100.woff2
│ │ │ ├── Roboto-300.woff2
│ │ │ ├── Roboto-400.woff2
│ │ │ ├── Roboto-500.woff2
│ │ │ ├── rec_cpu_fine.png
│ │ │ ├── rec_cpu_freq.png
│ │ │ ├── rec_meminfo.png
│ │ │ ├── rec_one_shot.png
│ │ │ ├── rec_profiling.png
│ │ │ ├── rec_ps_stats.png
│ │ │ ├── rec_ring_buf.png
│ │ │ ├── rec_syscalls.png
│ │ │ ├── rec_cpu_coarse.png
│ │ │ ├── rec_cpu_voltage.png
│ │ │ ├── rec_long_trace.png
│ │ │ ├── rec_mem_hifreq.png
│ │ │ ├── rec_board_voltage.png
│ │ │ ├── rec_frame_timeline.png
│ │ │ ├── rec_gpu_mem_total.png
│ │ │ ├── rec_java_heap_dump.png
│ │ │ ├── scheduling_latency.png
│ │ │ ├── RobotoMono-Regular.woff2
│ │ │ ├── rec_battery_counters.png
│ │ │ ├── RobotoCondensed-Light.woff2
│ │ │ ├── MaterialSymbolsOutlined.woff2
│ │ │ ├── RobotoCondensed-Regular.woff2
│ │ │ ├── rec_native_heap_profiler.png
│ │ │ └── ._MaterialSymbolsOutlined.woff2
│ │ │ ├── trace_processor.wasm
│ │ │ ├── trace_config_utils.wasm
│ │ │ ├── trace_processor_memory64.wasm
│ │ │ └── manifest.json
│ ├── tsconfig.node.json
│ ├── tsconfig.json
│ ├── vite.config.ts
│ └── package.json
├── validate
│ ├── src
│ │ ├── node.ts
│ │ ├── tsc-cpuprofile.ts
│ │ ├── utils.ts
│ │ ├── type-registry.ts
│ │ ├── grab-file.ts
│ │ ├── package-name.test.ts
│ │ ├── index.ts
│ │ └── package-name.ts
│ ├── tsdown.config.ts
│ ├── tsconfig.json
│ ├── package.json
│ └── README.md
└── analyze-trace
│ ├── src
│ ├── index.ts
│ ├── constants.ts
│ ├── browser.ts
│ ├── node-module-paths.ts
│ ├── get-duplicate-node-modules.ts
│ ├── analyze-trace.ts
│ └── spans.ts
│ ├── tsconfig.json
│ ├── tsdown.config.ts
│ ├── bin
│ └── typeslayer-analyze-trace.mjs
│ └── package.json
├── pnpm-workspace.yaml
├── testbed
├── huge-union
│ ├── tsconfig.json
│ ├── package.json
│ ├── package-lock.json
│ └── index.ts
└── as-simple-as-possible
│ ├── tsconfig.json
│ ├── package.json
│ ├── pnpm-lock.yaml
│ └── index.ts
├── .gitattributes
├── package.json
├── npm
├── linux-x64
│ ├── package.json
│ └── README.md
├── win32-x64
│ ├── package.json
│ └── README.md
├── darwin-arm64
│ ├── package.json
│ └── README.md
└── typeslayer
│ ├── package.json
│ ├── bin
│ └── typeslayer.js
│ └── README.md
├── biome.json
├── .vscode
└── settings.json
├── scripts
├── tag.ts
└── bump.ts
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/components/types-list.tsx:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/typeslayer/.gitignore:
--------------------------------------------------------------------------------
1 | binaries
2 | target
3 |
--------------------------------------------------------------------------------
/packages/validate/src/node.ts:
--------------------------------------------------------------------------------
1 | export { grabFile } from "./grab-file.js";
2 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/build.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | tauri_build::build()
3 | }
4 |
--------------------------------------------------------------------------------
/packages/validate/src/tsc-cpuprofile.ts:
--------------------------------------------------------------------------------
1 | export const CPU_PROFILE_FILENAME = "tsc.cpuprofile";
2 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - packages/*
3 | onlyBuiltDependencies:
4 | - esbuild
5 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/src/validate/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod trace_json;
2 | pub mod types_json;
3 | pub mod utils;
4 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/svg.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.png" {
2 | const src: string;
3 | export default src;
4 | }
5 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/src/analyze_trace/constants.rs:
--------------------------------------------------------------------------------
1 | pub const ANALYZE_TRACE_FILENAME: &str = "analyze-trace.json";
2 |
--------------------------------------------------------------------------------
/testbed/huge-union/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "strict": true
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Exclude static assets from GitHub Linguist language stats
2 | packages/typeslayer/public/** linguist-vendored
3 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/assets/ts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src/assets/ts.png
--------------------------------------------------------------------------------
/testbed/as-simple-as-possible/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "strict": true
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/icon.ico
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/icon.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/speedscope-ui/release.txt:
--------------------------------------------------------------------------------
1 | speedscope@1.25.0
2 | Sun Dec 7 16:07:24 EST 2025
3 | 3613918de0dd55a263d0d04f85b0c8c2039c7bee
4 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/32x32.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/64x64.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/icon.icns
--------------------------------------------------------------------------------
/packages/typeslayer/src/assets/typeslayer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src/assets/typeslayer.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/128x128.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/StoreLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/StoreLogo.png
--------------------------------------------------------------------------------
/packages/typeslayer/src/assets/dimitropoulos.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src/assets/dimitropoulos.png
--------------------------------------------------------------------------------
/packages/typeslayer/src/assets/ts-nightmare.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src/assets/ts-nightmare.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/128x128@2x.png
--------------------------------------------------------------------------------
/packages/typeslayer/src/assets/typeslayer-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src/assets/typeslayer-icon.png
--------------------------------------------------------------------------------
/packages/analyze-trace/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./analyze-trace";
2 | export * from "./constants";
3 | export * from "./depth-limits";
4 | export * from "./utils";
5 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/Square30x30Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/Square30x30Logo.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/Square44x44Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/Square44x44Logo.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/Square71x71Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/Square71x71Logo.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/Square89x89Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/Square89x89Logo.png
--------------------------------------------------------------------------------
/packages/typeslayer/src/assets/typeslayer-nightmare.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src/assets/typeslayer-nightmare.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/Square107x107Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/Square107x107Logo.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/Square142x142Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/Square142x142Logo.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/Square150x150Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/Square150x150Logo.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/Square284x284Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/Square284x284Logo.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/Square310x310Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/Square310x310Logo.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/ios/AppIcon-512@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/ios/AppIcon-512@2x.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/ios/AppIcon-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/ios/AppIcon-20x20@1x.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/ios/AppIcon-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/ios/AppIcon-20x20@2x.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/ios/AppIcon-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/ios/AppIcon-20x20@3x.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/ios/AppIcon-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/ios/AppIcon-29x29@1x.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/ios/AppIcon-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/ios/AppIcon-29x29@2x.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/ios/AppIcon-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/ios/AppIcon-29x29@3x.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/ios/AppIcon-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/ios/AppIcon-40x40@1x.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/ios/AppIcon-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/ios/AppIcon-40x40@2x.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/ios/AppIcon-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/ios/AppIcon-40x40@3x.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/ios/AppIcon-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/ios/AppIcon-60x60@2x.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/ios/AppIcon-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/ios/AppIcon-60x60@3x.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/ios/AppIcon-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/ios/AppIcon-76x76@1x.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/ios/AppIcon-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/ios/AppIcon-76x76@2x.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/ios/AppIcon-20x20@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/ios/AppIcon-20x20@2x-1.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/ios/AppIcon-29x29@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/ios/AppIcon-29x29@2x-1.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/ios/AppIcon-40x40@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/ios/AppIcon-40x40@2x-1.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/speedscope-ui/jfrview_bg-BLJXNNQB.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/speedscope-ui/jfrview_bg-BLJXNNQB.wasm
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/traceconv.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/traceconv.wasm
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/brand.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/brand.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/favicon.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/logo-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/logo-128.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/logo-3d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/logo-3d.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_lmk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_lmk.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_wifi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_wifi.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/android/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #fff
4 |
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_atrace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_atrace.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_ftrace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_ftrace.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_logcat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_logcat.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_vmstat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_vmstat.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/trace_processor.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/trace_processor.wasm
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/Roboto-100.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/Roboto-100.woff2
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/Roboto-300.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/Roboto-300.woff2
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/Roboto-400.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/Roboto-400.woff2
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/Roboto-500.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/Roboto-500.woff2
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_cpu_fine.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_cpu_fine.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_cpu_freq.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_cpu_freq.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_meminfo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_meminfo.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_one_shot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_one_shot.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_profiling.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_profiling.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_ps_stats.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_ps_stats.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_ring_buf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_ring_buf.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_syscalls.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_syscalls.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/trace_config_utils.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/trace_config_utils.wasm
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/packages/analyze-trace/src/constants.ts:
--------------------------------------------------------------------------------
1 | // you may need to import this directly via ESM, otherwise Vite will complain about externalized fs dependencies
2 | export const ANALYZE_TRACE_FILENAME = "analyze-trace.json";
3 |
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_cpu_coarse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_cpu_coarse.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_cpu_voltage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_cpu_voltage.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_long_trace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_long_trace.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_mem_hifreq.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_mem_hifreq.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/speedscope-ui/SourceCodePro-Regular.ttf-ILST5JV6.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/speedscope-ui/SourceCodePro-Regular.ttf-ILST5JV6.woff2
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_board_voltage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_board_voltage.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_frame_timeline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_frame_timeline.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_gpu_mem_total.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_gpu_mem_total.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_java_heap_dump.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_java_heap_dump.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/scheduling_latency.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/scheduling_latency.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/trace_processor_memory64.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/trace_processor_memory64.wasm
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/RobotoMono-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/RobotoMono-Regular.woff2
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_battery_counters.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_battery_counters.png
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/RobotoCondensed-Light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/RobotoCondensed-Light.woff2
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/MaterialSymbolsOutlined.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/MaterialSymbolsOutlined.woff2
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/RobotoCondensed-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/RobotoCondensed-Regular.woff2
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_native_heap_profiler.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/rec_native_heap_profiler.png
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/._MaterialSymbolsOutlined.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dimitropoulos/typeslayer/HEAD/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/assets/._MaterialSymbolsOutlined.woff2
--------------------------------------------------------------------------------
/packages/typeslayer/src/components/constants.tsx:
--------------------------------------------------------------------------------
1 | export const SERVER_PORT = 8666;
2 | export const CLIENT_PORT = 1993;
3 | export const TYPESCRIPT_TOML = "typescript.toml";
4 | export const MITS_DISCORD = "https://discord.michigantypescript.com";
5 |
--------------------------------------------------------------------------------
/testbed/huge-union/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "huge-union",
3 | "main": "index.ts",
4 | "type": "module",
5 | "scripts": {
6 | "typecheck": "tsc --noEmit"
7 | },
8 | "devDependencies": {
9 | "typescript": "^5.9.3"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/validate/tsdown.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "tsdown";
2 |
3 | export default defineConfig({
4 | entry: ["src/index.ts", "src/node.ts"],
5 | format: ["esm"],
6 | target: "es2024",
7 | dts: true,
8 | sourcemap: true,
9 | clean: true,
10 | });
11 |
--------------------------------------------------------------------------------
/testbed/as-simple-as-possible/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "as-simple-as-possible",
3 | "main": "index.ts",
4 | "type": "module",
5 | "scripts": {
6 | "typecheck": "tsc --noEmit"
7 | },
8 | "devDependencies": {
9 | "typescript": "^5.9.3"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/typeslayer/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/components/no-data.tsx:
--------------------------------------------------------------------------------
1 | import { Alert, Link } from "@mui/material";
2 |
3 | export const NoData = () => {
4 | return (
5 |
6 | it looks like you haven't run diagnostics yet. go to{" "}
7 | Start.
8 |
9 | );
10 | };
11 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/packages/analyze-trace/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "target": "ES2024",
7 | "strict": true,
8 | "esModuleInterop": true,
9 | "resolveJsonModule": true,
10 | "skipLibCheck": true
11 | },
12 | "include": ["**/*.ts"]
13 | }
14 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | TypeSlayer
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/gen/schemas/capabilities.json:
--------------------------------------------------------------------------------
1 | {"default":{"identifier":"default","description":"Capability for the main window","local":true,"windows":["main"],"permissions":["core:default","opener:default","dialog:default","core:webview:allow-set-webview-zoom","clipboard-manager:default","upload:default","core:window:allow-is-fullscreen","core:window:allow-set-fullscreen","screenshots:default"]}}
--------------------------------------------------------------------------------
/packages/typeslayer/src/components/center-loader.tsx:
--------------------------------------------------------------------------------
1 | import { Box, CircularProgress } from "@mui/material";
2 |
3 | export const CenterLoader = () => (
4 |
13 |
14 |
15 | );
16 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/types/tauri-plugin-upload.d.ts:
--------------------------------------------------------------------------------
1 | declare module "@tauri-apps/plugin-upload" {
2 | export interface DownloadProgress {
3 | progress: number;
4 | total: number;
5 | }
6 | export function download(
7 | url: string,
8 | destination: string,
9 | progressCb?: (p: DownloadProgress) => void,
10 | headers?: Record,
11 | ): Promise;
12 | }
13 |
--------------------------------------------------------------------------------
/packages/validate/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2024",
4 | "module": "ES2022",
5 | "moduleResolution": "bundler",
6 | "declaration": true,
7 | "outDir": "dist",
8 | "strict": true,
9 | "esModuleInterop": false,
10 | "forceConsistentCasingInFileNames": true,
11 | "noEmitOnError": true,
12 | "resolveJsonModule": true,
13 | "skipLibCheck": true
14 | },
15 | "include": ["**/*.ts"]
16 | }
17 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/components/inline-code.tsx:
--------------------------------------------------------------------------------
1 | import { theme } from "../theme";
2 |
3 | export const InlineCode = ({
4 | children,
5 | primary = false,
6 | style,
7 | }: {
8 | children: React.ReactNode;
9 | primary?: boolean;
10 | style?: React.CSSProperties;
11 | }) => (
12 |
18 | {children}
19 |
20 | );
21 |
--------------------------------------------------------------------------------
/packages/validate/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod/v4";
2 |
3 | export const CPU_PROFILE_FILENAME = "tsc.cpuprofile";
4 |
5 | export const typeId = z.number().int().positive().or(z.literal(-1));
6 |
7 | export type TypeId = z.infer;
8 |
9 | export const position = z.object({
10 | line: typeId,
11 | character: z.number(),
12 | });
13 |
14 | export const absolutePath = z.string();
15 |
16 | export const location = z.object({
17 | path: absolutePath,
18 | start: position,
19 | end: position,
20 | });
21 |
--------------------------------------------------------------------------------
/packages/analyze-trace/tsdown.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "tsdown";
2 |
3 | export default defineConfig([
4 | // Main Node.js build
5 | {
6 | entry: ["src/index.ts"],
7 | format: ["esm"],
8 | target: "es2024",
9 | dts: true,
10 | sourcemap: true,
11 | clean: true,
12 | },
13 | // Browser-safe build (types only)
14 | {
15 | entry: { browser: "src/browser.ts" },
16 | format: ["esm"],
17 | target: "es2024",
18 | dts: true,
19 | sourcemap: true,
20 | clean: false,
21 | },
22 | ]);
23 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/src/log.rs:
--------------------------------------------------------------------------------
1 | use tracing_subscriber::{EnvFilter, fmt};
2 |
3 | pub fn init() {
4 | let verbose = std::env::args().any(|arg| arg == "--verbose");
5 | let default_level = if verbose { "info" } else { "warn" };
6 | let env_filter =
7 | EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(default_level));
8 |
9 | let _ = fmt()
10 | .with_env_filter(env_filter)
11 | .with_target(false)
12 | .with_level(true)
13 | .compact()
14 | .try_init();
15 | }
16 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/capabilities/default.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../gen/schemas/desktop-schema.json",
3 | "identifier": "default",
4 | "description": "Capability for the main window",
5 | "windows": ["main"],
6 | "permissions": [
7 | "core:default",
8 | "opener:default",
9 | "dialog:default",
10 | "core:webview:allow-set-webview-zoom",
11 | "clipboard-manager:default",
12 | "upload:default",
13 | "core:window:allow-is-fullscreen",
14 | "core:window:allow-set-fullscreen",
15 | "screenshots:default"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/testbed/as-simple-as-possible/pnpm-lock.yaml:
--------------------------------------------------------------------------------
1 | lockfileVersion: '9.0'
2 |
3 | settings:
4 | autoInstallPeers: true
5 | excludeLinksFromLockfile: false
6 |
7 | importers:
8 |
9 | .:
10 | devDependencies:
11 | typescript:
12 | specifier: ^5.8.3
13 | version: 5.8.3
14 |
15 | packages:
16 |
17 | typescript@5.8.3:
18 | resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
19 | engines: {node: '>=14.17'}
20 | hasBin: true
21 |
22 | snapshots:
23 |
24 | typescript@5.8.3: {}
25 |
--------------------------------------------------------------------------------
/packages/analyze-trace/src/browser.ts:
--------------------------------------------------------------------------------
1 | // Browser-safe exports - only types and schemas, no Node.js APIs
2 | export type {
3 | TraceJsonSchema,
4 | TypesJsonSchema,
5 | } from "@typeslayer/validate";
6 | export { ANALYZE_TRACE_FILENAME } from "./constants";
7 | export type { DepthLimitsRecord } from "./depth-limits";
8 | export type {
9 | AbsolutePath,
10 | AnalyzeTraceOptions,
11 | AnalyzeTraceResult,
12 | DuplicatedPackage,
13 | DuplicatedPackageInstance,
14 | EventSpan,
15 | HotSpot,
16 | Microseconds,
17 | NodeModulePaths,
18 | ParseResult,
19 | Project,
20 | ProjectResult,
21 | RootSpan,
22 | } from "./utils";
23 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/src/validate/utils.rs:
--------------------------------------------------------------------------------
1 | use serde::{Deserialize, Serialize};
2 |
3 | pub const CPU_PROFILE_FILENAME: &str = "tsc.cpuprofile";
4 |
5 | /// TypeId corresponds to z.number().int().positive().or(z.literal(-1)) in TS.
6 | /// We keep it as i64 to be safe with larger ids.
7 | pub type TypeId = usize;
8 |
9 | #[derive(Debug, Clone, Serialize, Deserialize)]
10 | pub struct Position {
11 | pub line: TypeId,
12 | pub character: i64,
13 | }
14 |
15 | #[derive(Debug, Clone, Serialize, Deserialize)]
16 | pub struct Location {
17 | pub path: String,
18 | pub start: Position,
19 | pub end: Position,
20 | }
21 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/pages/award-winners/title-subtitle.tsx:
--------------------------------------------------------------------------------
1 | import { Stack, Typography } from "@mui/material";
2 | import type { ReactNode } from "react";
3 |
4 | export function TitleSubtitle({
5 | title,
6 | subtitle,
7 | icon,
8 | }: {
9 | title: string;
10 | subtitle: ReactNode | string;
11 | icon: ReactNode;
12 | }) {
13 | return (
14 |
15 |
16 | {icon}
17 | {title}
18 |
19 | {subtitle}
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@typeslayer/root",
3 | "private": true,
4 | "version": "0.0.0",
5 | "engines": {
6 | "node": ">=24.0.0",
7 | "pnpm": ">=10.0.0"
8 | },
9 | "packageManager": "pnpm@10.6.5",
10 | "pnpm": {
11 | "onlyBuiltDependencies": [
12 | "esbuild"
13 | ]
14 | },
15 | "scripts": {
16 | "biome:check": "biome check --write --unsafe",
17 | "rip-and-tear": "echo \"did you?\"",
18 | "bump": "tsx scripts/bump.ts",
19 | "tag": "tsx scripts/tag.ts",
20 | "typecheck": "pnpm --recursive run typecheck"
21 | },
22 | "devDependencies": {
23 | "@types/node": "^24.10.1",
24 | "@biomejs/biome": "^2.3.7"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/validate/src/type-registry.ts:
--------------------------------------------------------------------------------
1 | import type { ResolvedType } from "./types-json";
2 |
3 | /**
4 | * Think of a TypeRegistry like an object that you'd use to look up types by their ID.
5 | *
6 | */
7 | export type TypeRegistry = ResolvedType[];
8 |
9 | const ALL_LIFE_IS_SUFFERING_THAT_BASKS_IN_NOTHINGNESS___ALL_LIFE_IS_TEMPORARY___WHAT_LASTS_IS_CONSCIOUSNESS: [
10 | ResolvedType,
11 | ] = [{ id: 0, recursionId: -1, flags: [] }];
12 |
13 | export const createTypeRegistry = (typesJson: ResolvedType[]): TypeRegistry => {
14 | return ALL_LIFE_IS_SUFFERING_THAT_BASKS_IN_NOTHINGNESS___ALL_LIFE_IS_TEMPORARY___WHAT_LASTS_IS_CONSCIOUSNESS.concat(
15 | typesJson,
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/pages/start/step.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Typography, useTheme } from "@mui/material";
2 | import type { PropsWithChildren } from "react";
3 |
4 | export const Step = ({
5 | step,
6 | children,
7 | }: PropsWithChildren<{ step: number }>) => {
8 | const theme = useTheme();
9 | return (
10 |
11 |
20 | {step}
21 |
22 | {children}
23 |
24 | );
25 | };
26 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/pages/cicd-integration.tsx:
--------------------------------------------------------------------------------
1 | import { Link, Stack, Typography } from "@mui/material";
2 |
3 | export const CiCdIntegration = () => {
4 | return (
5 |
6 | CI/CD Integration
7 |
8 | this is something that's planned, but I don't want to implement it until
9 | I get specific use-cases on what exactly people would want this for.
10 |
11 |
12 | that said, the fact that you clicked here means you're one of those
13 | people. lemme know..
14 |
15 |
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/npm/linux-x64/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@typeslayer/linux-x64",
3 | "version": "0.1.20",
4 | "description": "TypeSlayer binary for Linux x64",
5 | "private": false,
6 | "type": "module",
7 | "license": "MIT",
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/dimitropoulos/typeslayer.git",
11 | "directory": "npm/linux-x64"
12 | },
13 | "bugs": {
14 | "url": "https://github.com/dimitropoulos/typeslayer/issues"
15 | },
16 | "homepage": "https://github.com/dimitropoulos/typeslayer#readme",
17 | "os": [
18 | "linux"
19 | ],
20 | "cpu": [
21 | "x64"
22 | ],
23 | "files": [
24 | "typeslayer"
25 | ],
26 | "bin": {
27 | "typeslayer-linux-x64": "typeslayer"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/typeslayer/public/speedscope-ui/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | speedscope
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/npm/win32-x64/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@typeslayer/win32-x64",
3 | "version": "0.1.20",
4 | "description": "TypeSlayer binary for Windows x64",
5 | "private": false,
6 | "type": "module",
7 | "license": "MIT",
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/dimitropoulos/typeslayer.git",
11 | "directory": "npm/win32-x64"
12 | },
13 | "bugs": {
14 | "url": "https://github.com/dimitropoulos/typeslayer/issues"
15 | },
16 | "homepage": "https://github.com/dimitropoulos/typeslayer#readme",
17 | "os": [
18 | "win32"
19 | ],
20 | "cpu": [
21 | "x64"
22 | ],
23 | "files": [
24 | "typeslayer.exe"
25 | ],
26 | "bin": {
27 | "typeslayer-win32-x64": "typeslayer"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/components/stat-pill.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Typography } from "@mui/material";
2 |
3 | export const StatPill = ({
4 | label,
5 | value,
6 | }: {
7 | label: string;
8 | value: number;
9 | }) => (
10 | `1px solid ${t.palette.primary.main}80`,
18 | }}
19 | >
20 |
21 | {value.toLocaleString()}
22 |
23 |
27 | {label}
28 |
29 |
30 | );
31 |
--------------------------------------------------------------------------------
/npm/darwin-arm64/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@typeslayer/darwin-arm64",
3 | "version": "0.1.20",
4 | "description": "TypeSlayer binary for macOS arm64",
5 | "private": false,
6 | "type": "module",
7 | "license": "MIT",
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/dimitropoulos/typeslayer.git",
11 | "directory": "npm/darwin-arm64"
12 | },
13 | "bugs": {
14 | "url": "https://github.com/dimitropoulos/typeslayer/issues"
15 | },
16 | "homepage": "https://github.com/dimitropoulos/typeslayer#readme",
17 | "os": [
18 | "darwin"
19 | ],
20 | "cpu": [
21 | "arm64"
22 | ],
23 | "files": [
24 | "typeslayer"
25 | ],
26 | "bin": {
27 | "typeslayer-darwin-arm64": "typeslayer"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/pages/award-winners/inline-bar-graph.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Stack, Typography } from "@mui/material";
2 |
3 | export const InlineBarGraph = ({
4 | width,
5 | label,
6 | }: {
7 | width: string;
8 | label: string;
9 | }) => {
10 | return (
11 |
12 | theme.palette.primary.main,
17 | borderRadius: "2px",
18 | marginTop: "2px",
19 | }}
20 | />
21 |
22 | theme.palette.text.secondary,
25 | fontSize: "0.8rem",
26 | }}
27 | >
28 | {label}
29 |
30 |
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/packages/typeslayer/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2022", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 | "types": ["vite/client", "node"],
17 |
18 | /* Linting */
19 | "strict": true,
20 | "noUnusedLocals": true,
21 | "noUnusedParameters": true,
22 | "noFallthroughCasesInSwitch": true
23 | },
24 | "include": ["src"],
25 | "references": [{ "path": "./tsconfig.node.json" }]
26 | }
27 |
--------------------------------------------------------------------------------
/packages/typeslayer/vite.config.ts:
--------------------------------------------------------------------------------
1 | import react from "@vitejs/plugin-react";
2 | import { defineConfig } from "vite";
3 |
4 | const host = process.env.TAURI_DEV_HOST;
5 |
6 | export default defineConfig(() => {
7 | return {
8 | plugins: [react()],
9 | root: "./src",
10 | publicDir: "../public",
11 | build: {
12 | outDir: "../dist",
13 | emptyOutDir: true,
14 | },
15 | clearScreen: false,
16 | server: {
17 | port: 1993,
18 | strictPort: true,
19 | host: host || false,
20 | hmr: host
21 | ? {
22 | protocol: "ws",
23 | host,
24 | port: 1421,
25 | }
26 | : undefined,
27 | watch: {
28 | ignored: ["**/src-tauri/**"],
29 | },
30 | },
31 | };
32 | });
33 |
--------------------------------------------------------------------------------
/testbed/huge-union/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "huge-union",
3 | "lockfileVersion": 3,
4 | "requires": true,
5 | "packages": {
6 | "": {
7 | "name": "huge-union",
8 | "devDependencies": {
9 | "typescript": "^5.9.3"
10 | }
11 | },
12 | "node_modules/typescript": {
13 | "version": "5.9.3",
14 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
15 | "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
16 | "dev": true,
17 | "license": "Apache-2.0",
18 | "bin": {
19 | "tsc": "bin/tsc",
20 | "tsserver": "bin/tsserver"
21 | },
22 | "engines": {
23 | "node": ">=14.17"
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/validate/src/grab-file.ts:
--------------------------------------------------------------------------------
1 | import { readFile } from "node:fs/promises";
2 | import type { z } from "zod/v4";
3 |
4 | export const grabFile = async (
5 | filePath: string,
6 | validator: V,
7 | ) => {
8 | console.log("grabFile:", filePath);
9 | const json = await readSmallJson(filePath, "utf8");
10 | const parsed = await validator.safeParseAsync(json);
11 | if (!parsed.success) {
12 | console.error("Error parsing file", { filePath, parsed });
13 | throw new Error(`Error parsing file ${filePath}`);
14 | }
15 | return parsed.data;
16 | };
17 |
18 | export const readSmallJson = async (
19 | filePath: string,
20 | encoding: "utf8",
21 | ): Promise => {
22 | const fileString = await readFile(filePath, { encoding });
23 | const parsed = JSON.parse(fileString);
24 | return parsed;
25 | };
26 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/components/callout.tsx:
--------------------------------------------------------------------------------
1 | import { Info } from "@mui/icons-material";
2 | import { Stack, type SxProps, Typography } from "@mui/material";
3 | import { theme } from "../theme";
4 |
5 | export function Callout({
6 | children,
7 | title,
8 | sx = {},
9 | }: {
10 | title: string;
11 | children: React.ReactNode;
12 | sx?: SxProps;
13 | }) {
14 | return (
15 |
25 |
26 |
27 | {title}
28 | {children}
29 |
30 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/testbed/huge-union/index.ts:
--------------------------------------------------------------------------------
1 | const colors = [
2 | "red",
3 | "green",
4 | "blue",
5 | "yellow",
6 | "purple",
7 | "orange",
8 | "pink",
9 | "brown",
10 | "gray",
11 | "black",
12 | "cyan",
13 | "magenta",
14 | "lime",
15 | "teal",
16 | "indigo",
17 | "violet",
18 | "gold",
19 | "silver",
20 | "bronze",
21 | "navy",
22 | ] as const;
23 |
24 | type Color = (typeof colors)[number];
25 |
26 | export type Palette<
27 | Primary extends Color = Color,
28 | Secondary extends Color = Color,
29 | > = `${Primary}-${Secondary}`;
30 |
31 | export type FullPalette<
32 | Dark extends Palette = Palette,
33 | Light extends Palette = Palette,
34 | > = `${Dark}|${Light}`;
35 |
36 | export type Z = FullPalette<"red-blue", "yellow-green">;
37 | // ^?
38 |
39 | // @ts-expect-error
40 | export const fullPalette = (lightDarkPalette: FullPalette) => {
41 | // do stuff
42 | console.log(lightDarkPalette);
43 | };
44 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/tauri.conf.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.tauri.app/config/2",
3 | "productName": "typeslayer",
4 | "version": "0.0.0",
5 | "identifier": "typeslayer",
6 | "build": {
7 | "beforeDevCommand": "pnpm dev",
8 | "devUrl": "http://localhost:1993",
9 | "beforeBuildCommand": "pnpm build",
10 | "frontendDist": "../dist"
11 | },
12 | "app": {
13 | "windows": [
14 | {
15 | "title": "TypeSlayer",
16 | "width": 1920,
17 | "height": 1080,
18 | "zoomHotkeysEnabled": true
19 | }
20 | ],
21 | "security": {
22 | "csp": null
23 | }
24 | },
25 | "bundle": {
26 | "active": true,
27 | "targets": "all",
28 | "icon": [
29 | "icons/32x32.png",
30 | "icons/64x64.png",
31 | "icons/128x128.png",
32 | "icons/128x128@2x.png",
33 | "icons/icon.png",
34 | "icons/icon.icns",
35 | "icons/icon.ico"
36 | ]
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/npm/linux-x64/README.md:
--------------------------------------------------------------------------------
1 | # Linux x64 Binary
2 |
3 | TypeSlayer binary for Linux x64
4 |
5 | This is an internal package distributed by the main [@typeslayer](https://www.npmjs.com/package/typeslayer) package.
6 |
7 | ## Installation
8 |
9 | You should not install this package directly. Instead, install the main package:
10 |
11 | ```bash
12 | npm install --save typeslayer
13 | ```
14 |
15 | The appropriate binary for your platform will be automatically downloaded.
16 |
17 | ## Usage
18 |
19 | After installing the main `typeslayer` package, use it via:
20 |
21 | ```bash
22 | npx typeslayer --help
23 | ```
24 |
25 | Or globally:
26 |
27 | ```bash
28 | npm install -g typeslayer
29 | typeslayer --help
30 | ```
31 |
32 | ## About TypeSlayer
33 |
34 | TypeSlayer analyzes your TypeScript types to identify and optimize unused or redundant type definitions.
35 |
36 | For more information, visit the [main repository](https://github.com/dimitropoulos/typeslayer).
37 |
38 | ## License
39 |
40 | MIT
41 |
--------------------------------------------------------------------------------
/testbed/as-simple-as-possible/index.ts:
--------------------------------------------------------------------------------
1 | // biome-ignore-all lint: this file is for demonstrating bad things
2 |
3 | export type Bit = "0" | "1";
4 | export type BitArray = Bit[];
5 | export type BitMatrix = BitArray[];
6 | export type BitMatrix3D = BitMatrix[];
7 |
8 | export type Byte = `${Bit}${Bit}${Bit}${Bit}${Bit}${Bit}${Bit}${Bit}`;
9 | export type ByteArray = Byte[];
10 | export type ByteMatrix = ByteArray[];
11 | export type ByteMatrix3D = ByteMatrix[];
12 |
13 | export type Mapping = {
14 | [key in T extends "asdf" ? never : T]: `${T}${Byte}`;
15 | };
16 |
17 | export type Thing1 = Mapping;
18 | export type Thing2 = Mapping<`${Byte}${Byte}`>;
19 |
20 | export type Boom = T extends any[] ? Boom : Boom;
21 | // @ts-expect-error naa naa nuh boo boo
22 | export type Explosion = Boom;
23 | export type UseExplosion = `exploded-${Explosion}`;
24 |
25 | export type Sometime = "later" | "sooner";
26 | export type EvenLater = `even-${Sometime}`;
27 |
--------------------------------------------------------------------------------
/npm/win32-x64/README.md:
--------------------------------------------------------------------------------
1 | # Windows x64 Binary
2 |
3 | TypeSlayer binary for Windows x64
4 |
5 | This is an internal package distributed by the main [@typeslayer](https://www.npmjs.com/package/typeslayer) package.
6 |
7 | ## Installation
8 |
9 | You should not install this package directly. Instead, install the main package:
10 |
11 | ```bash
12 | npm install --save typeslayer
13 | ```
14 |
15 | The appropriate binary for your platform will be automatically downloaded.
16 |
17 | ## Usage
18 |
19 | After installing the main `typeslayer` package, use it via:
20 |
21 | ```bash
22 | npx typeslayer --help
23 | ```
24 |
25 | Or globally:
26 |
27 | ```bash
28 | npm install -g typeslayer
29 | typeslayer --help
30 | ```
31 |
32 | ## About TypeSlayer
33 |
34 | TypeSlayer analyzes your TypeScript types to identify and optimize unused or redundant type definitions.
35 |
36 | For more information, visit the [main repository](https://github.com/dimitropoulos/typeslayer).
37 |
38 | ## License
39 |
40 | MIT
41 |
--------------------------------------------------------------------------------
/npm/darwin-arm64/README.md:
--------------------------------------------------------------------------------
1 | # macOS arm64 Binary
2 |
3 | TypeSlayer binary for macOS arm64 (Apple Silicon)
4 |
5 | This is an internal package distributed by the main [@typeslayer](https://www.npmjs.com/package/typeslayer) package.
6 |
7 | ## Installation
8 |
9 | You should not install this package directly. Instead, install the main package:
10 |
11 | ```bash
12 | npm install --save typeslayer
13 | ```
14 |
15 | The appropriate binary for your platform will be automatically downloaded.
16 |
17 | ## Usage
18 |
19 | After installing the main `typeslayer` package, use it via:
20 |
21 | ```bash
22 | npx typeslayer --help
23 | ```
24 |
25 | Or globally:
26 |
27 | ```bash
28 | npm install -g typeslayer
29 | typeslayer --help
30 | ```
31 |
32 | ## About TypeSlayer
33 |
34 | TypeSlayer analyzes your TypeScript types to identify and optimize unused or redundant type definitions.
35 |
36 | For more information, visit the [main repository](https://github.com/dimitropoulos/typeslayer).
37 |
38 | ## License
39 |
40 | MIT
41 |
--------------------------------------------------------------------------------
/packages/validate/src/package-name.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from "vitest";
2 | import { extractPackageName, relativizePath } from "./package-name";
3 |
4 | test("extractPackageName", () => {
5 | const pnpmLodash =
6 | "/home/tr/src/github.com/mui/material-ui/node_modules/.pnpm/@types+lodash@4.17.17/node_modules/@types/lodash/common/common.d.ts:200:6";
7 | expect(extractPackageName(pnpmLodash)).toBe("@types/lodash@4.17.17");
8 |
9 | const pnpmTypeScript =
10 | "/home/tr/src/github.com/mui/material-ui/node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/lib/lib.es5.d.ts:1322:17";
11 | expect(extractPackageName(pnpmTypeScript)).toBe("typescript@5.8.3");
12 | });
13 |
14 | test("relativizePath", () => {
15 | const base = "/home/tr/src/github.com/mui/material-ui/packages/material-ui/";
16 | const target =
17 | "/home/tr/src/github.com/mui/material-ui/packages/mui-system/src/useMediaQuery/useMediaQuery.ts";
18 | const expected = "../mui-system/src/useMediaQuery/useMediaQuery.ts";
19 | expect(relativizePath(base, target)).toBe(expected);
20 | });
21 |
--------------------------------------------------------------------------------
/packages/typeslayer/public/speedscope-ui/speedscope-GHPHNKXC.css:
--------------------------------------------------------------------------------
1 | html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:"";content:none}table{border-collapse:collapse;border-spacing:0}html{overflow:hidden;height:100%}body{height:100%;overflow:auto}@font-face{font-family:Source Code Pro;font-weight:400;font-style:normal;font-stretch:normal;src:url("./SourceCodePro-Regular.ttf-ILST5JV6.woff2") format("woff2")}
2 | /*# sourceMappingURL=speedscope-GHPHNKXC.css.map */
3 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/pages/speedscope.tsx:
--------------------------------------------------------------------------------
1 | import Box from "@mui/material/Box";
2 | import { CPU_PROFILE_FILENAME } from "@typeslayer/validate";
3 | import { CenterLoader } from "../components/center-loader";
4 | import { NoData } from "../components/no-data";
5 | import { serverBaseUrl } from "../components/utils";
6 | import { useOutputFileSizes } from "../hooks/tauri-hooks";
7 |
8 | export const SpeedScope = () => {
9 | const { data: fileSizes, isLoading } = useOutputFileSizes();
10 | if (isLoading) {
11 | ;
12 | }
13 |
14 | if (!fileSizes || !(CPU_PROFILE_FILENAME in fileSizes)) {
15 | return (
16 |
17 |
18 |
19 | );
20 | }
21 |
22 | const profileUrl = `${serverBaseUrl}/outputs/${CPU_PROFILE_FILENAME}`;
23 | const embeddedUrl = `/speedscope-ui/index.html#profileURL=${encodeURIComponent(profileUrl)}`;
24 | return (
25 |
30 | );
31 | };
32 |
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
3 | "vcs": {
4 | "enabled": true,
5 | "clientKind": "git",
6 | "useIgnoreFile": true
7 | },
8 | "linter": {
9 | "rules": {
10 | "complexity": {
11 | "noForEach": "off"
12 | },
13 | "suspicious": {
14 | "noConfusingVoidType": "off"
15 | },
16 | "correctness": {
17 | "useHookAtTopLevel": "error"
18 | },
19 | "style": {
20 | "useBlockStatements": "error"
21 | },
22 | "performance": {
23 | "noAccumulatingSpread": "off"
24 | }
25 | }
26 | },
27 | "formatter": {
28 | "indentStyle": "space"
29 | },
30 | "javascript": {
31 | "formatter": {
32 | "arrowParentheses": "asNeeded"
33 | }
34 | },
35 |
36 | "files": {
37 | "maxSize": 18446744073709551615,
38 | "includes": [
39 | "**/*.ts",
40 | "**/*.tsx",
41 | "**/*.js",
42 | "**/*.json",
43 | "!**/public",
44 | "!**/dist",
45 | "!node_modules",
46 | "!**/gen"
47 | ]
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/pages/award-winners/use-award-id.ts:
--------------------------------------------------------------------------------
1 | import { useNavigate, useParams } from "@tanstack/react-router";
2 | import { useCallback } from "react";
3 | import { type AwardId, awards } from "./awards";
4 |
5 | const fallbackAward = "type_unionTypes";
6 |
7 | const getAwardIdFromRoute = (route: string | undefined): AwardId => {
8 | if (!route) {
9 | return fallbackAward;
10 | }
11 | const entry = Object.entries(awards).find(
12 | ([_, award]) => award.route === route,
13 | );
14 | return entry ? (entry[0] as AwardId) : fallbackAward;
15 | };
16 |
17 | export const useAwardId = () => {
18 | const params = useParams({ strict: false });
19 | const awardId = params.awardId as string | undefined;
20 | const navigate = useNavigate();
21 |
22 | const activeAward: AwardId = getAwardIdFromRoute(awardId);
23 | const setActiveAward = useCallback(
24 | (id: AwardId) => {
25 | const route = awards[id].route;
26 | navigate({ to: `/award-winners/${route}` });
27 | },
28 | [navigate],
29 | );
30 | return {
31 | activeAward,
32 | setActiveAward,
33 | };
34 | };
35 |
--------------------------------------------------------------------------------
/npm/typeslayer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "typeslayer",
3 | "version": "0.1.20",
4 | "description": "Slay your TypeScript types",
5 | "private": false,
6 | "type": "module",
7 | "keywords": [
8 | "typescript",
9 | "ts",
10 | "tauri",
11 | "desktop",
12 | "benchmarking",
13 | "performance"
14 | ],
15 | "author": "Dimitri Mitropoulos",
16 | "license": "MIT",
17 | "repository": {
18 | "type": "git",
19 | "url": "https://github.com/dimitropoulos/typeslayer.git",
20 | "directory": "packages/typeslayer"
21 | },
22 | "bugs": {
23 | "url": "https://github.com/dimitropoulos/typeslayer/issues"
24 | },
25 | "homepage": "https://github.com/dimitropoulos/typeslayer#readme",
26 | "bin": {
27 | "typeslayer": "bin/typeslayer.js"
28 | },
29 | "files": [
30 | "bin",
31 | "scripts"
32 | ],
33 | "os": [
34 | "linux",
35 | "darwin",
36 | "win32"
37 | ],
38 | "cpu": [
39 | "x64",
40 | "arm64"
41 | ],
42 | "scripts": {
43 | "postinstall": "node scripts/postinstall.js"
44 | },
45 | "optionalDependencies": {
46 | "@typeslayer/linux-x64": "0.1.20",
47 | "@typeslayer/darwin-arm64": "0.1.20",
48 | "@typeslayer/win32-x64": "0.1.20"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/packages/analyze-trace/bin/typeslayer-analyze-trace.mjs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import { statSync, writeFileSync } from "node:fs";
4 | import { resolve } from "node:path";
5 | import process, { exit } from "node:process";
6 | import { ANALYZE_TRACE_FILENAME, analyzeTrace, defaultOptions } from "../dist/index.mjs";
7 |
8 | const { argv } = process;
9 |
10 | const traceDirArg = argv[2];
11 |
12 | if (!traceDirArg) {
13 | console.error("Gotta give a trace directory, brah.");
14 | exit(1);
15 | }
16 |
17 | const traceDir = resolve(traceDirArg);
18 |
19 | try {
20 | const stat = statSync(traceDir);
21 | if (!stat.isDirectory()) {
22 | console.error(`Trace directory "${traceDir}" is not a directory.`);
23 | exit(1);
24 | }
25 | } catch (err) {
26 | if (err.code === "ENOENT") {
27 | console.error(`Trace directory "${traceDir}" does not exist.`);
28 | } else {
29 | console.error(
30 | `Error checking trace directory "${traceDir}": ${err.message}`,
31 | );
32 | }
33 | exit(1);
34 | }
35 |
36 | analyzeTrace({ traceDir, options: defaultOptions }).then((result) => {
37 | const destination = resolve(traceDir, ANALYZE_TRACE_FILENAME);
38 | writeFileSync(destination, JSON.stringify(result, null, 2), "utf-8");
39 | console.log("Analysis result:", result);
40 | });
41 |
--------------------------------------------------------------------------------
/packages/analyze-trace/src/node-module-paths.ts:
--------------------------------------------------------------------------------
1 | import {
2 | packageNameRegex,
3 | type TraceEvent,
4 | type TraceJsonSchema,
5 | } from "@typeslayer/validate";
6 | import type { NodeModulePaths } from "./utils";
7 |
8 | export function getNodeModulePaths(
9 | traceJson: TraceJsonSchema,
10 | ): NodeModulePaths {
11 | const nodeModulePaths: NodeModulePaths = {};
12 | traceJson.forEach((event: TraceEvent) => {
13 | if (event.name !== "findSourceFile") {
14 | return;
15 | }
16 | const path = event.args.fileName;
17 | if (path) {
18 | while (true) {
19 | const match = packageNameRegex.exec(path);
20 | if (!match) {
21 | break;
22 | }
23 | const packageName = match[1];
24 |
25 | const packagePath = match.input.substring(
26 | 0,
27 | match.index + match[0].length,
28 | );
29 |
30 | if (packageName in nodeModulePaths) {
31 | const paths = nodeModulePaths[packageName];
32 | if (paths && paths.indexOf(packagePath) < 0) {
33 | // Usually contains exactly one element
34 | paths.push(packagePath);
35 | }
36 | } else {
37 | nodeModulePaths[packageName] = [packagePath];
38 | }
39 | }
40 | }
41 | });
42 |
43 | return nodeModulePaths;
44 | }
45 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/components/show-more.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Stack, Typography } from "@mui/material";
2 | import type { Dispatch, SetStateAction } from "react";
3 |
4 | export const ShowMore = ({
5 | incrementsOf = 100,
6 | totalItems,
7 | displayLimit,
8 | setDisplayLimit,
9 | }: {
10 | incrementsOf: number;
11 | displayLimit: number;
12 | totalItems: number;
13 | setDisplayLimit: Dispatch>;
14 | }) => {
15 | /** the point at which it will start showing this at all, and the number of which you can advance by */
16 | const remaining = totalItems - displayLimit;
17 | const hasMore = totalItems > displayLimit;
18 |
19 | if (!hasMore) {
20 | return null;
21 | }
22 |
23 | return (
24 |
30 | setDisplayLimit(prev => prev + incrementsOf)}
34 | >
35 | Show {Math.min(incrementsOf, remaining).toLocaleString()} more
36 |
37 |
38 | showing {displayLimit.toLocaleString()} out of{" "}
39 | {totalItems.toLocaleString()}
40 |
41 |
42 | );
43 | };
44 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/components/show-more-children.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Stack, Typography } from "@mui/material";
2 | import { type ReactNode, useState } from "react";
3 |
4 | export const ShowMoreChildren = ({
5 | incrementsOf = 100,
6 | children,
7 | }: {
8 | incrementsOf: number;
9 | children: ReactNode[];
10 | }) => {
11 | const [currentlyShowing, setCurrentlyShowing] = useState(incrementsOf);
12 | const totalItems = children.length;
13 | const remaining = totalItems - currentlyShowing;
14 | const hasMore = totalItems > currentlyShowing;
15 | return (
16 | <>
17 | {children.slice(0, currentlyShowing)}
18 | {hasMore ? (
19 |
25 | setCurrentlyShowing(prev => prev + incrementsOf)}
29 | >
30 | Show {Math.min(incrementsOf, remaining).toLocaleString()} more
31 |
32 |
33 | showing {currentlyShowing.toLocaleString()} out of{" "}
34 | {totalItems.toLocaleString()}
35 |
36 |
37 | ) : null}
38 | >
39 | );
40 | };
41 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "Autorenew",
4 | "bivariant",
5 | "checktypes",
6 | "chrono",
7 | "cpuprofile",
8 | "Dimitri",
9 | "gibibyte",
10 | "hotspot",
11 | "Hotspots",
12 | "Kabaddi",
13 | "kibibyte",
14 | "Lapce",
15 | "layercake",
16 | "majenta",
17 | "mebibyte",
18 | "mibibyte",
19 | "Millis",
20 | "Mitropoulos",
21 | "nvim",
22 | "perfetto",
23 | "speedscope",
24 | "subl",
25 | "tauri",
26 | "toolpad",
27 | "treemap",
28 | "treemaps",
29 | "TYPEIDS",
30 | "typeslayer",
31 | "webstorm",
32 | "Whatshot"
33 | ],
34 | "editor.codeActionsOnSave": {
35 | "source.organizeImports.biome": "always",
36 | "source.fixAll.biome": "always"
37 | },
38 | "editor.defaultFormatter": "biomejs.biome",
39 | "search.exclude": {
40 | "packages/typeslayer/public/**": true
41 | },
42 | "files.watcherExclude": {
43 | "packages/typeslayer/public/**": true
44 | },
45 | "[json]": {
46 | "editor.defaultFormatter": "biomejs.biome"
47 | },
48 | "[rust]": {
49 | "editor.defaultFormatter": "rust-lang.rust-analyzer"
50 | },
51 | "[typescript]": {
52 | "editor.defaultFormatter": "biomejs.biome"
53 | },
54 | "[typescriptreact]": {
55 | "editor.defaultFormatter": "biomejs.biome"
56 | },
57 | "rust-analyzer.linkedProjects": ["packages/typeslayer/src-tauri/Cargo.toml"]
58 | }
59 |
--------------------------------------------------------------------------------
/packages/validate/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@typeslayer/validate",
3 | "version": "0.1.20",
4 | "description": "Validation schemas and utilities for TypeScript compiler trace analysis",
5 | "license": "MIT",
6 | "private": false,
7 | "type": "module",
8 | "repository": "github:dimitropoulos/typeslayer",
9 | "homepage": "https://github.com/dimitropoulos/typeslayer#readme",
10 | "bugs": "https://github.com/dimitropoulos/typeslayer/issues",
11 | "keywords": [
12 | "typescript",
13 | "performance",
14 | "analysis",
15 | "validation",
16 | "trace"
17 | ],
18 | "engines": {
19 | "node": ">=20"
20 | },
21 | "packageManager": "pnpm@10.6.5",
22 | "exports": {
23 | ".": {
24 | "types": "./dist/index.d.mts",
25 | "import": "./dist/index.mjs"
26 | },
27 | "./node": {
28 | "types": "./dist/node.d.mts",
29 | "import": "./dist/node.mjs"
30 | }
31 | },
32 | "main": "./dist/index.mjs",
33 | "types": "./dist/index.d.mts",
34 | "scripts": {
35 | "build": "tsdown",
36 | "dev": "tsdown --watch",
37 | "typecheck": "tsc --noEmit",
38 | "attw": "attw --pack --profile esm-only .",
39 | "test": "vitest"
40 | },
41 | "dependencies": {
42 | "zod": "^4.1.13"
43 | },
44 | "devDependencies": {
45 | "@arethetypeswrong/cli": "^0.18.2",
46 | "@types/node": "^24.10.1",
47 | "@types/stream-json": "^1.7.8",
48 | "tsdown": "^0.17.2",
49 | "typescript": "^5.9.3",
50 | "vitest": "^4.0.15"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/packages/analyze-trace/src/get-duplicate-node-modules.ts:
--------------------------------------------------------------------------------
1 | import { existsSync } from "node:fs";
2 | import { readFile } from "node:fs/promises";
3 | import { join } from "node:path";
4 | import type {
5 | DuplicatedPackage,
6 | DuplicatedPackageInstance,
7 | NodeModulePaths,
8 | } from "./utils";
9 |
10 | export async function getPackageVersion(packagePath: string) {
11 | const packageJsonPath = join(packagePath, "package.json");
12 | console.log("packageJsonPath", packageJsonPath);
13 | if (!existsSync(packageJsonPath)) {
14 | console.warn(
15 | `Package.json not found at ${packageJsonPath}. This may not be a node module.`,
16 | );
17 | return "unknown";
18 | }
19 | const jsonString = await readFile(packageJsonPath, "utf-8");
20 | const jsonObj = JSON.parse(jsonString);
21 | return jsonObj.version;
22 | }
23 |
24 | export const getDuplicateNodeModules = async (
25 | nodeModulePaths: NodeModulePaths,
26 | ) => {
27 | const duplicates: DuplicatedPackage[] = [];
28 | for (const [packageName, packagePaths] of Object.entries(nodeModulePaths)) {
29 | if (packagePaths.length < 2) {
30 | continue;
31 | }
32 | const instances: DuplicatedPackageInstance[] = [];
33 | for (const packagePath of packagePaths) {
34 | instances.push({
35 | path: packagePath,
36 | version: await getPackageVersion(packagePath),
37 | });
38 | }
39 | duplicates.push({
40 | name: packageName,
41 | instances,
42 | });
43 | }
44 |
45 | return duplicates;
46 | };
47 |
--------------------------------------------------------------------------------
/packages/analyze-trace/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@typeslayer/analyze-trace",
3 | "version": "0.1.20",
4 | "description": "Analyze TypeScript compiler trace events to identify performance bottlenecks",
5 | "license": "MIT",
6 | "private": false,
7 | "type": "module",
8 | "repository": "github:dimitropoulos/typeslayer",
9 | "homepage": "https://github.com/dimitropoulos/typeslayer#readme",
10 | "bugs": "https://github.com/dimitropoulos/typeslayer/issues",
11 | "keywords": [
12 | "typescript",
13 | "performance",
14 | "analysis",
15 | "trace",
16 | "cli"
17 | ],
18 | "engines": {
19 | "node": ">=20"
20 | },
21 | "packageManager": "pnpm@10.6.5",
22 | "main": "./dist/index.mjs",
23 | "types": "./dist/index.d.mts",
24 | "exports": {
25 | ".": {
26 | "types": "./dist/index.d.mts",
27 | "import": "./dist/index.mjs"
28 | },
29 | "./browser": {
30 | "types": "./dist/browser.d.mts",
31 | "import": "./dist/browser.mjs"
32 | }
33 | },
34 | "bin": {
35 | "typeslayer-analyze-trace": "./bin/typeslayer-analyze-trace.mjs"
36 | },
37 | "scripts": {
38 | "build": "tsdown",
39 | "dev": "tsdown --watch",
40 | "attw": "attw --pack --profile esm-only .",
41 | "typecheck": "tsc --noEmit"
42 | },
43 | "dependencies": {
44 | "@typeslayer/validate": "workspace:*",
45 | "zod": "4.1.13"
46 | },
47 | "devDependencies": {
48 | "@arethetypeswrong/cli": "^0.18.2",
49 | "@types/node": "24.10.1",
50 | "tsdown": "^0.17.2",
51 | "typescript": "5.9.3"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/pages/award-winners/award-nav-item.tsx:
--------------------------------------------------------------------------------
1 | import { Box, ListItemButton, ListItemIcon, ListItemText } from "@mui/material";
2 | import { type AwardId, awards } from "./awards";
3 | import { useAwardId } from "./use-award-id";
4 |
5 | /** format a number to be no more than 4 chars, using K for thousands and M for millions */
6 | const formatNumber = (num: number): string | null => {
7 | if (num === 0) {
8 | return null;
9 | }
10 | if (num >= 1000000) {
11 | return `${(num / 1000000).toFixed(1)}M`;
12 | }
13 | if (num >= 1000) {
14 | return `${(num / 1000).toFixed(1)}K`;
15 | }
16 | return num.toString();
17 | };
18 |
19 | export const AwardNavItem = ({
20 | awardId,
21 | value,
22 | }: {
23 | awardId: AwardId;
24 | value: number;
25 | }) => {
26 | const { activeAward, setActiveAward } = useAwardId();
27 |
28 | const { title, icon: Icon } = awards[awardId];
29 | const selected = activeAward === awardId;
30 | const count = formatNumber(value);
31 | return (
32 | {
36 | setActiveAward(awardId);
37 | }}
38 | dense
39 | >
40 |
41 |
42 |
43 |
44 |
45 | t.palette.secondary.main,
50 | fontWeight: "bold",
51 | fontFamily: "monospace",
52 | }}
53 | >
54 | {count === null ? <> > : count}
55 |
56 |
57 | );
58 | };
59 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/main.tsx:
--------------------------------------------------------------------------------
1 | import CssBaseline from "@mui/material/CssBaseline";
2 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
3 | // import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
4 | import ReactDOM from "react-dom/client";
5 | import { AppRouterProvider } from "./routes";
6 |
7 | import "@fontsource/roboto/300.css";
8 | import "@fontsource/roboto/400.css";
9 | import "@fontsource/roboto/500.css";
10 | import "@fontsource/roboto/700.css";
11 | import { ThemeProvider } from "@mui/material";
12 | import { ToastProvider } from "./contexts/toast-context";
13 | import { theme } from "./theme";
14 |
15 | const queryClient = new QueryClient({
16 | defaultOptions: {
17 | queries: {
18 | // Don't refetch on window focus in desktop app
19 | refetchOnWindowFocus: false,
20 | // we're not over the internet so no reason to retry
21 | retry: 0,
22 | // Cache data for 5 minutes
23 | staleTime: 1000 * 60 * 5,
24 | // Keep data in cache for 10 minutes
25 | gcTime: 1000 * 60 * 10,
26 | },
27 | mutations: {
28 | // we're not over the internet so no reason to retry
29 | retry: 0,
30 | },
31 | },
32 | });
33 |
34 | const root = document.getElementById("root");
35 | if (!root) {
36 | throw new Error("Root element not found");
37 | }
38 |
39 | ReactDOM.createRoot(root).render(
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | {/* */}
48 | ,
49 | );
50 |
--------------------------------------------------------------------------------
/packages/validate/src/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | extractPackageName,
3 | packageNameRegex,
4 | relativizePath,
5 | } from "./package-name";
6 | export {
7 | type DepthLimitNames,
8 | depthLimits,
9 | type EventChecktypes__CheckCrossProductUnion_DepthLimit,
10 | type EventChecktypes__CheckTypeRelatedTo_DepthLimit,
11 | type EventChecktypes__GetTypeAtFlowNode_DepthLimit,
12 | type EventChecktypes__InstantiateType_DepthLimit,
13 | type EventChecktypes__RecursiveTypeRelatedTo_DepthLimit,
14 | type EventChecktypes__RemoveSubtypes_DepthLimit,
15 | type EventChecktypes__TraceUnionsOrIntersectionsTooLarge_DepthLimit,
16 | type EventChecktypes__TypeRelatedToDiscriminatedType_DepthLimit,
17 | event_checktypes__checkCrossProductUnion_DepthLimit,
18 | event_checktypes__checkTypeRelatedTo_DepthLimit,
19 | event_checktypes__getTypeAtFlowNode_DepthLimit,
20 | event_checktypes__instantiateType_DepthLimit,
21 | event_checktypes__recursiveTypeRelatedTo_DepthLimit,
22 | event_checktypes__removeSubtypes_DepthLimit,
23 | event_checktypes__traceUnionsOrIntersectionsTooLarge_DepthLimit,
24 | event_checktypes__typeRelatedToDiscriminatedType_DepthLimit,
25 | eventPhase,
26 | TRACE_JSON_FILENAME,
27 | type TraceEvent,
28 | type TraceJsonSchema,
29 | traceEvent,
30 | traceJsonSchema,
31 | } from "./trace-json";
32 | export { CPU_PROFILE_FILENAME } from "./tsc-cpuprofile";
33 | export { createTypeRegistry, type TypeRegistry } from "./type-registry";
34 | export {
35 | type ResolvedType,
36 | resolvedType,
37 | TYPES_JSON_FILENAME,
38 | type TypeRelationInfo,
39 | type TypesJsonSchema,
40 | typeRelationInfo,
41 | typesJsonSchema,
42 | } from "./types-json";
43 | export type { TypeId } from "./utils";
44 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/src/analyze_trace/node_module_paths.rs:
--------------------------------------------------------------------------------
1 | use crate::analyze_trace::types::NodeModulePaths;
2 | use crate::validate::trace_json::TraceEvent;
3 | use regex::Regex;
4 | use std::sync::OnceLock;
5 |
6 | fn package_name_regex() -> &'static Regex {
7 | static REGEX: OnceLock = OnceLock::new();
8 | REGEX.get_or_init(|| {
9 | // Match node_modules/(package-name) or node_modules/@scope/package-name
10 | Regex::new(r"node_modules/((?:@[^/]+/)?[^/]+)").unwrap()
11 | })
12 | }
13 |
14 | pub fn get_node_module_paths(trace_json: &[TraceEvent]) -> NodeModulePaths {
15 | let mut node_module_paths: NodeModulePaths = NodeModulePaths::new();
16 | let regex = package_name_regex();
17 |
18 | for event in trace_json {
19 | if event.name != "findSourceFile" {
20 | continue;
21 | }
22 |
23 | if let Some(file_name) = event.args.get("fileName").and_then(|v| v.as_str()) {
24 | for captures in regex.captures_iter(file_name) {
25 | if let Some(package_name_match) = captures.get(1) {
26 | let package_name = package_name_match.as_str().to_string();
27 | let package_path = &file_name[..captures.get(0).unwrap().end()];
28 |
29 | node_module_paths
30 | .entry(package_name)
31 | .or_insert_with(Vec::new)
32 | .push(package_path.to_string());
33 | }
34 | }
35 | }
36 | }
37 |
38 | // Deduplicate paths for each package
39 | for paths in node_module_paths.values_mut() {
40 | paths.sort();
41 | paths.dedup();
42 | }
43 |
44 | node_module_paths
45 | }
46 |
--------------------------------------------------------------------------------
/packages/validate/src/package-name.ts:
--------------------------------------------------------------------------------
1 | export const packageNameRegex =
2 | /\/node_modules\/((?:[^@][^/]+)|(?:@[^/]+\/[^/]+))/g;
3 |
4 | export const extractPackageName = (path: string): string | null => {
5 | const pnpmSentinel = "/node_modules/.pnpm/";
6 | if (path.includes(pnpmSentinel)) {
7 | // go to the character immediately after the pnpm sentinel
8 | const startIndex = path.indexOf(pnpmSentinel) + pnpmSentinel.length;
9 | const endIndex = path.indexOf("/", startIndex);
10 | if (endIndex === -1) {
11 | throw new Error(
12 | "Invalid path format: no closing slash found after pnpm sentinel",
13 | );
14 | }
15 | return path.substring(startIndex, endIndex).replace("+", "/");
16 | }
17 |
18 | return path;
19 | };
20 |
21 | /**
22 | * Computes the relative path from one absolute path to another.
23 | *
24 | * @param from - The anchor directory (must be an absolute path).
25 | * @param to - The absolute path you want to relativize.
26 | * @returns A relative path from `from` to `to`.
27 | */
28 | export function relativizePath(from: string, to: string): string {
29 | // Ensure both paths end with a slash if they're directories
30 | const fromURL = new URL(`file://${from.endsWith("/") ? from : `${from}/}`}`);
31 | const toURL = new URL(`file://${to}`);
32 |
33 | const fromParts = fromURL.pathname.split("/").filter(Boolean);
34 | const toParts = toURL.pathname.split("/").filter(Boolean);
35 |
36 | // Find where they diverge
37 | let i = 0;
38 | while (
39 | i < fromParts.length &&
40 | i < toParts.length &&
41 | fromParts[i] === toParts[i]
42 | ) {
43 | i++;
44 | }
45 |
46 | const up = fromParts.length - i;
47 | const down = toParts.slice(i).join("/");
48 |
49 | return `${"../".repeat(up)}${down}`;
50 | }
51 |
--------------------------------------------------------------------------------
/packages/typeslayer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@typeslayer/internal",
3 | "version": "0.0.0",
4 | "description": "Slay your TypeScript types",
5 | "private": true,
6 | "type": "module",
7 | "bugs": {
8 | "url": "https://github.com/dimitropoulos/typeslayer/issues"
9 | },
10 | "engines": {
11 | "node": ">=24.0.0",
12 | "pnpm": ">=10.0.0"
13 | },
14 | "packageManager": "pnpm@10.6.5",
15 | "scripts": {
16 | "dev": "vite",
17 | "build": "tsc && vite build",
18 | "preview": "vite preview",
19 | "tauri": "tauri",
20 | "typecheck": "tsc --noEmit"
21 | },
22 | "dependencies": {
23 | "@cosmos.gl/graph": "2.6.2",
24 | "@emotion/react": "11.14.0",
25 | "@emotion/styled": "11.14.1",
26 | "@fontsource/roboto": "5.2.9",
27 | "@mui/icons-material": "7.3.6",
28 | "@mui/material": "7.3.6",
29 | "@tanstack/react-query": "5.90.12",
30 | "@tanstack/react-query-devtools": "5.91.1",
31 | "@tanstack/react-router": "1.141.2",
32 | "@tauri-apps/api": "2.9.1",
33 | "@tauri-apps/plugin-clipboard-manager": "2.3.2",
34 | "@tauri-apps/plugin-dialog": "2.4.2",
35 | "@tauri-apps/plugin-opener": "2.5.2",
36 | "@tauri-apps/plugin-upload": "2.3.2",
37 | "echarts": "6.0.0",
38 | "echarts-for-react": "3.0.5",
39 | "react": "19.2.3",
40 | "react-dom": "19.2.3",
41 | "react-window": "2.2.3",
42 | "shiki": "3.20.0"
43 | },
44 | "devDependencies": {
45 | "@tauri-apps/cli": "2.9.6",
46 | "@types/mime-types": "3.0.1",
47 | "@types/node": "24.10.4",
48 | "@types/react": "19.2.7",
49 | "@types/react-dom": "19.2.3",
50 | "@typeslayer/analyze-trace": "workspace:*",
51 | "@typeslayer/validate": "workspace:*",
52 | "@vitejs/plugin-react": "5.1.2",
53 | "typescript": "5.9.3",
54 | "vite": "7.3.0",
55 | "zod": "4.2.0"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/types/type-graph.ts:
--------------------------------------------------------------------------------
1 | import type { AbsolutePath } from "@typeslayer/analyze-trace/browser";
2 | import type { TypeId } from "@typeslayer/validate";
3 |
4 | export const TYPE_GRAPH_FILENAME = "type-graph.json";
5 |
6 | export type LinkKind =
7 | | "union"
8 | | "typeArgument"
9 | | "instantiated"
10 | | "substitutionBase"
11 | | "constraint"
12 | | "indexedAccessObject"
13 | | "indexedAccessIndex"
14 | | "conditionalCheck"
15 | | "conditionalExtends"
16 | | "conditionalTrue"
17 | | "conditionalFalse"
18 | | "keyof"
19 | | "evolvingArrayElement"
20 | | "evolvingArrayFinal"
21 | | "reverseMappedSource"
22 | | "reverseMappedMapped"
23 | | "reverseMappedConstraint"
24 | | "alias"
25 | | "aliasTypeArgument"
26 | | "intersection";
27 |
28 | export type GraphLink = {
29 | source: number;
30 | target: number;
31 | kind: LinkKind;
32 | };
33 |
34 | export type GraphStats = {
35 | count: Record;
36 | };
37 |
38 | export type LinkStatLink = {
39 | targetId: TypeId;
40 | sourceIds: TypeId[];
41 | path?: string | undefined;
42 | };
43 |
44 | type LinkStats = {
45 | max: number;
46 | links: LinkStatLink[];
47 | };
48 |
49 | export type GraphLinkStats = Record;
50 |
51 | export type NodeStatKind =
52 | | "typeArguments"
53 | | "unionTypes"
54 | | "intersectionTypes"
55 | | "aliasTypeArguments";
56 |
57 | export type NodeStatNode = {
58 | id: TypeId;
59 | name: string;
60 | value: number;
61 | path: AbsolutePath | null;
62 | };
63 |
64 | type NodeStatCategory = {
65 | max: number;
66 | nodes: NodeStatNode[];
67 | };
68 |
69 | export type GraphNodeStats = Record;
70 |
71 | export type TypeGraph = {
72 | nodes: number;
73 | links: GraphLink[];
74 | stats: GraphStats;
75 | linkStats: GraphLinkStats;
76 | nodeStats: GraphNodeStats;
77 | };
78 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/src/analyze_trace/duplicate_node_modules.rs:
--------------------------------------------------------------------------------
1 | use crate::analyze_trace::types::{DuplicatedPackage, DuplicatedPackageInstance, NodeModulePaths};
2 | use serde_json;
3 | use std::fs;
4 | use std::path::Path;
5 |
6 | pub fn get_package_version(package_path: &str) -> Result {
7 | let package_json_path = Path::new(package_path).join("package.json");
8 |
9 | if !package_json_path.exists() {
10 | eprintln!(
11 | "Package.json not found at {}. This may not be a node module.",
12 | package_json_path.display()
13 | );
14 | return Ok("unknown".to_string());
15 | }
16 |
17 | let json_string = fs::read_to_string(&package_json_path)
18 | .map_err(|e| format!("Failed to read package.json: {}", e))?;
19 |
20 | let json_obj: serde_json::Value = serde_json::from_str(&json_string)
21 | .map_err(|e| format!("Failed to parse package.json: {}", e))?;
22 |
23 | Ok(json_obj
24 | .get("version")
25 | .and_then(|v| v.as_str())
26 | .unwrap_or("unknown")
27 | .to_string())
28 | }
29 |
30 | pub fn get_duplicate_node_modules(
31 | node_module_paths: &NodeModulePaths,
32 | ) -> Result, String> {
33 | let mut duplicates = Vec::new();
34 |
35 | for (package_name, package_paths) in node_module_paths {
36 | if package_paths.len() < 2 {
37 | continue;
38 | }
39 |
40 | let mut instances = Vec::new();
41 | for package_path in package_paths {
42 | instances.push(DuplicatedPackageInstance {
43 | path: package_path.clone(),
44 | version: get_package_version(package_path)?,
45 | });
46 | }
47 |
48 | duplicates.push(DuplicatedPackage {
49 | name: package_name.clone(),
50 | instances,
51 | });
52 | }
53 |
54 | Ok(duplicates)
55 | }
56 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/pages/perfetto.tsx:
--------------------------------------------------------------------------------
1 | import { TRACE_JSON_FILENAME } from "@typeslayer/validate";
2 | import { useEffect, useRef } from "react";
3 | import { useTraceJson } from "../hooks/tauri-hooks";
4 |
5 | export const Perfetto = () => {
6 | const { data, isLoading } = useTraceJson();
7 | const iframeRef = useRef(null);
8 |
9 | // Auto-load trace when data is available
10 | useEffect(() => {
11 | if (data && iframeRef.current) {
12 | if (isLoading || !iframeRef.current?.contentWindow) {
13 | console.error("No trace data or iframe not ready.");
14 | return;
15 | }
16 | const buffer = new TextEncoder().encode(JSON.stringify(data)).buffer;
17 | const perfettoWindow = iframeRef.current.contentWindow;
18 |
19 | // Wait for iframe to be ready via PING/PONG
20 | const interval = setInterval(() => {
21 | perfettoWindow.postMessage("PING", "*");
22 | }, 100);
23 |
24 | const handleMessage = (event: MessageEvent) => {
25 | if (event.source !== perfettoWindow) {
26 | return;
27 | }
28 | if (event.data !== "PONG") {
29 | return;
30 | }
31 |
32 | clearInterval(interval);
33 | window.removeEventListener("message", handleMessage);
34 |
35 | perfettoWindow.postMessage(
36 | {
37 | perfetto: {
38 | buffer,
39 | title: TRACE_JSON_FILENAME,
40 | },
41 | },
42 | "*",
43 | );
44 | };
45 |
46 | window.addEventListener("message", handleMessage);
47 | }
48 | }, [data, isLoading]);
49 |
50 | return (
51 |
61 | );
62 | };
63 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/shikiTheme.ts:
--------------------------------------------------------------------------------
1 | import type { ThemeRegistrationAny } from "shiki";
2 | import { theme as muiTheme } from "./theme";
3 |
4 | export const shikiTheme = {
5 | name: "typeslayer-dark",
6 | type: "dark",
7 | tokenColors: [
8 | {
9 | scope: ["comment", "punctuation.definition.comment"],
10 | settings: {
11 | foreground: muiTheme.palette.text.disabled,
12 | fontStyle: "italic",
13 | },
14 | },
15 | {
16 | scope: ["keyword", "storage.type", "storage.modifier"],
17 | settings: {
18 | foreground: muiTheme.palette.primary.main,
19 | fontStyle: "bold",
20 | },
21 | },
22 | {
23 | scope: ["string", "constant.other.symbol"],
24 | settings: { foreground: muiTheme.palette.secondary.main },
25 | },
26 | {
27 | scope: ["variable", "identifier", "meta.definition.variable"],
28 | settings: { foreground: muiTheme.palette.text.primary },
29 | },
30 | {
31 | scope: ["constant.numeric"],
32 | settings: { foreground: muiTheme.palette.secondary.light },
33 | },
34 | {
35 | scope: ["entity.name.function", "support.function"],
36 | settings: { foreground: muiTheme.palette.success.main },
37 | },
38 | {
39 | scope: ["entity.name.type", "support.type"],
40 | settings: { foreground: "#dddddd" },
41 | },
42 | {
43 | scope: ["entity.name.class", "entity.name.namespace"],
44 | settings: { foreground: muiTheme.palette.primary.contrastText },
45 | },
46 | {
47 | scope: ["invalid", "invalid.deprecated"],
48 | settings: {
49 | foreground: muiTheme.palette.error.main,
50 | },
51 | },
52 | {
53 | scope: ["markup.bold"],
54 | settings: { fontStyle: "bold" },
55 | },
56 | {
57 | scope: ["markup.italic"],
58 | settings: { fontStyle: "italic" },
59 | },
60 | ],
61 | } satisfies ThemeRegistrationAny;
62 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "typeslayer"
3 | version = "0.1.0"
4 | description = "Slaying TypeScript types since 2026"
5 | authors = ["Dimitri Mitropoulos"]
6 | edition = "2024"
7 |
8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
9 |
10 | [lib]
11 | # The `_lib` suffix may seem redundant but it is necessary
12 | # to make the lib name unique and wouldn't conflict with the bin name.
13 | # This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
14 | name = "typeslayer_lib"
15 | crate-type = ["staticlib", "cdylib", "rlib"]
16 |
17 | [build-dependencies]
18 | tauri-build = { version = "2", features = [] }
19 |
20 | [dependencies]
21 | tauri = { version = "2", features = [] }
22 | tauri-plugin-opener = "2"
23 | serde = { version = "1", features = ["derive"] }
24 | serde_json = "1"
25 | tauri-plugin-dialog = "2.4.2"
26 | regex = "1"
27 | tokio = { version = "1", features = ["fs", "macros", "rt-multi-thread"] }
28 | tracing = "0.1"
29 | tracing-subscriber = { version = "0.3", features = ["env-filter"] }
30 | toml = "0.8"
31 | chrono = { version = "0.4", features = ["clock"] }
32 | tauri-plugin-clipboard-manager = "2"
33 | tauri-plugin-upload = "2"
34 | tauri-plugin-screenshots = "2"
35 | xcap = "0.3"
36 | image = "0.25"
37 | axum = { version = "0.7", default-features = false, features = ["http1", "tokio", "tower-log"] }
38 | tower-http = { version = "0.5", default-features = false, features = ["cors"] }
39 | base64 = "0.22"
40 | dirs = "5"
41 | libc = "0.2"
42 | rmcp = { version = "0.11", features = ["server", "macros", "transport-io"] }
43 | schemars = "1"
44 | once_cell = "1.19"
45 | zip = { version = "6.0.0", default-features = false, features = ["deflate"] }
46 | shlex = "1.3.0"
47 | strum = "0.25"
48 | strum_macros = "0.25"
49 | serde_plain = "1.0"
50 |
51 | [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
52 | tauri-plugin-single-instance = "2"
53 | serde_json = { version = "1", features = ["raw_value"] }
54 |
--------------------------------------------------------------------------------
/packages/validate/README.md:
--------------------------------------------------------------------------------
1 | # @typeslayer/validate
2 |
3 | Validation schemas and utilities for TypeScript compiler trace analysis. This package provides comprehensive Zod-based schema validation (and types!!) for trace events, type metrics, and analysis results.
4 |
5 | ## Usage
6 |
7 | ### Trace JSON Validation
8 |
9 | ```ts
10 | import { traceJsonSchema, type TraceEvent } from '@typeslayer/validate';
11 |
12 | // Validate trace.json output from TypeScript compiler
13 | const events: TraceEvent[] = traceJsonSchema.parse(jsonData);
14 |
15 | // Or use with Node.js streams
16 | import { createReadStream } from 'fs';
17 | import { validateTraceJson } from '@typeslayer/validate/node';
18 |
19 | const stream = createReadStream('trace.json');
20 | const events = await validateTraceJson(stream);
21 | ```
22 |
23 | ### Types JSON Validation
24 |
25 | ```ts
26 | import { typesJsonSchema, type TypesJsonSchema } from '@typeslayer/validate';
27 |
28 | const types: TypesJsonSchema = typesJsonSchema.parse(jsonData);
29 | ```
30 |
31 | ### CPU Profile Validation
32 |
33 | ```ts
34 | import { tscCpuProfileSchema } from '@typeslayer/validate';
35 |
36 | const profile = tscCpuProfileSchema.parse(jsonData);
37 | ```
38 |
39 | ## Exports
40 |
41 | - **Main export (`@typeslayer/validate`)**: Browser-compatible validation schemas
42 | - `traceJsonSchema` - Validates TypeScript trace events
43 | - `typesJsonSchema` - Validates type information
44 | - `tscCpuProfileSchema` - Validates CPU profile data
45 |
46 | - **Node export (`@typeslayer/validate/node`)**: Node.js-specific utilities
47 | - `validateTraceJson()` - Stream-based trace validation
48 | - `readTraceJson()` - Read and parse trace.json files
49 |
50 | ## TypeScript Support
51 |
52 | Full TypeScript support with type inference from schemas:
53 |
54 | ```ts
55 | import type { TraceEvent } from '@typeslayer/validate';
56 |
57 | // Type-safe event handling
58 | function processEvent(event: TraceEvent) {
59 | switch (event.name) {
60 | case 'createSourceFile':
61 | console.log('Creating source file:', event.args.path);
62 | break;
63 | // ... other event types
64 | }
65 | }
66 | ```
67 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/src/mcp/tools/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod get_depth_limits;
2 | pub mod get_duplicate_packages;
3 | pub mod get_hot_files;
4 | pub mod get_hot_types;
5 |
6 | use serde::{Deserialize, Serialize};
7 | use serde_json::Value;
8 |
9 | #[doc = r" Get all tool definitions sorted by command"]
10 | pub fn get_tool_definitions() -> Vec {
11 | let mut definitions: Vec> = vec![
12 | map_tool_definition(get_duplicate_packages::tool_definition()),
13 | map_tool_definition(get_hot_types::tool_definition()),
14 | map_tool_definition(get_hot_files::tool_definition()),
15 | map_tool_definition(get_depth_limits::tool_definition()),
16 | ];
17 | definitions.sort_by(|a, b| a.command.cmp(&b.command));
18 | definitions
19 | .into_iter()
20 | .map(|def| serde_json::to_value(def).expect("Failed to serialize tool definition"))
21 | .collect()
22 | }
23 |
24 | /// Convert a typed ToolDefinition to a Value-based ToolDefinition
25 | fn map_tool_definition(def: ToolDefinition) -> ToolDefinition {
26 | ToolDefinition {
27 | command: def.command,
28 | display_name: def.display_name,
29 | description: def.description,
30 | parameters: def.parameters,
31 | returns: serde_json::to_value(&def.returns).expect("Failed to serialize tool returns"),
32 | }
33 | }
34 |
35 | /// Represents a parameter for an MCP tool
36 | #[derive(Debug, Clone, Serialize, Deserialize)]
37 | #[serde(rename_all = "camelCase")]
38 | pub struct ToolParameter {
39 | pub name: String,
40 | pub optional: bool,
41 | #[serde(skip_serializing_if = "Option::is_none")]
42 | pub default: Option,
43 | pub description: String,
44 | }
45 |
46 | /// Represents the definition of an MCP tool with generic return type
47 | #[derive(Debug, Clone, Serialize, Deserialize)]
48 | #[serde(rename_all = "camelCase")]
49 | pub struct ToolDefinition {
50 | pub command: String,
51 | pub display_name: String,
52 | pub description: String,
53 | pub parameters: Vec,
54 | pub returns: T,
55 | }
56 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/src/http_server.rs:
--------------------------------------------------------------------------------
1 | use crate::app_data::AppData;
2 | use axum::{
3 | Router,
4 | extract::{Path, State},
5 | response::IntoResponse,
6 | routing::get,
7 | };
8 | use std::sync::{Arc, Mutex};
9 | use tower_http::cors::{Any, CorsLayer};
10 | use tracing::{error, info};
11 |
12 | /// Standalone HTTP server for serving output files
13 | ///
14 | /// This server runs on port 4765 and serves files from the outputs directory.
15 | /// It's independent of the Tauri app and can run alongside it.
16 | pub async fn run_http_server(
17 | app_data: Arc>,
18 | ) -> Result<(), Box> {
19 | info!("Starting HTTP server for outputs on port 4765");
20 |
21 | async fn serve_output(
22 | Path(name): Path,
23 | State(app_data): State>>,
24 | ) -> impl IntoResponse {
25 | let outputs_dir = {
26 | let data = app_data.lock().unwrap();
27 | data.outputs_dir().to_string_lossy().to_string()
28 | };
29 |
30 | let path = std::path::Path::new(&outputs_dir).join(&name);
31 |
32 | match tokio::fs::read(&path).await {
33 | Ok(bytes) => {
34 | let content_type = if name.ends_with(".json") || name.ends_with(".cpuprofile") {
35 | "application/json"
36 | } else {
37 | "application/octet-stream"
38 | };
39 |
40 | ([("Content-Type", content_type)], bytes)
41 | }
42 | Err(e) => {
43 | error!("Failed to read {:?}: {}", path, e);
44 | ([("Content-Type", "text/plain")], Vec::new())
45 | }
46 | }
47 | }
48 |
49 | let cors = CorsLayer::new().allow_methods(Any).allow_origin(Any);
50 |
51 | let app_router = Router::new()
52 | .route("/outputs/:name", get(serve_output))
53 | .layer(cors)
54 | .with_state(app_data);
55 |
56 | let listener = tokio::net::TcpListener::bind("127.0.0.1:4765").await?;
57 |
58 | info!("HTTP server listening on http://127.0.0.1:4765");
59 |
60 | axum::serve(listener, app_router.into_make_service()).await?;
61 |
62 | Ok(())
63 | }
64 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/src/utils.rs:
--------------------------------------------------------------------------------
1 | use std::path::PathBuf;
2 | use std::process::Stdio;
3 |
4 | use shlex::Quoter;
5 | use tokio::io::AsyncReadExt;
6 | use tracing::debug;
7 |
8 | pub fn quote_if_needed(s: &str) -> String {
9 | // Equivalent to the now deprecated try_quote.
10 | // Should never panic since the only error is only possible with `allow_nul(false)`
11 | Quoter::new().allow_nul(true).quote(s).unwrap().into_owned()
12 | }
13 |
14 | /// takes in a string flag and a string path and makes it a cli arg, handing quoting if the arg contains spaces
15 | /// E.g. --project-root "/some path/with spaces"
16 | pub fn make_cli_arg(flag: &str, path: &str) -> String {
17 | format!("{} {}", flag, quote_if_needed(path))
18 | }
19 |
20 | pub const PACKAGE_JSON_FILENAME: &str = "package.json";
21 | pub const TSCONFIG_FILENAME: &str = "tsconfig.json";
22 | pub const OUTPUTS_DIRECTORY: &str = "outputs";
23 |
24 | pub fn command_exists(cmd: &str) -> bool {
25 | #[cfg(target_os = "windows")]
26 | {
27 | std::process::Command::new("where")
28 | .arg(cmd)
29 | .stdout(Stdio::null())
30 | .stderr(Stdio::null())
31 | .status()
32 | .map(|s| s.success())
33 | .unwrap_or(false)
34 | }
35 |
36 | #[cfg(not(target_os = "windows"))]
37 | {
38 | std::process::Command::new("command")
39 | .arg("-v")
40 | .arg(cmd)
41 | .stdout(Stdio::null())
42 | .stderr(Stdio::null())
43 | .status()
44 | .map(|s| s.success())
45 | .unwrap_or(false)
46 | }
47 | }
48 |
49 | pub async fn get_output_file_preview(path: &PathBuf) -> Result {
50 | let mut file = tokio::fs::File::open(path)
51 | .await
52 | .map_err(|e| format!("Failed to open {}: {}", path.display(), e))?;
53 | let mut buf = vec![0u8; 1024 * 100]; // 100 KiB
54 | let n = file
55 | .read(&mut buf)
56 | .await
57 | .map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
58 | buf.truncate(n);
59 | debug!(
60 | "[get_output_file_preview]: read {} bytes from {}",
61 | n,
62 | path.display()
63 | );
64 | String::from_utf8(buf).map_err(|e| format!("Invalid UTF-8: {}", e))
65 | }
66 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/src/main.rs:
--------------------------------------------------------------------------------
1 | // Prevents additional console window on Windows in release, DO NOT REMOVE!!
2 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
3 |
4 | use std::sync::{Arc, Mutex};
5 |
6 | fn main() -> Result<(), String> {
7 | let data_dir = typeslayer_lib::app_data::get_typeslayer_base_data_dir();
8 |
9 | // Create AppData as the root of our application (single instance shared by all components)
10 | let app_data = Arc::new(Mutex::new(typeslayer_lib::app_data::AppData::new(
11 | data_dir,
12 | )?));
13 |
14 | let args: Vec = std::env::args().collect();
15 | let is_mcp_mode = args.len() > 1 && args[1] == "mcp";
16 | if is_mcp_mode {
17 | // Run as MCP server (STDIO mode) with shared AppData
18 | // In MCP mode, stdout is reserved for JSON-RPC protocol, so no HTTP server or GUI
19 | if let Err(e) = typeslayer_lib::run_mcp_server(app_data) {
20 | eprintln!("MCP server error: {}", e);
21 | std::process::exit(1);
22 | }
23 | } else {
24 | // GUI mode: Spawn HTTP server and Tauri app with shared AppData
25 |
26 | // Initialize logging first
27 | typeslayer_lib::log::init();
28 |
29 | // Clone for HTTP server
30 | let app_data_for_http = app_data.clone();
31 |
32 | // Spawn HTTP server in background with better error handling
33 | std::thread::spawn(move || {
34 | let runtime = tokio::runtime::Runtime::new().expect("Failed to create tokio runtime");
35 | runtime.block_on(async {
36 | match typeslayer_lib::run_http_server(app_data_for_http).await {
37 | Ok(_) => println!("HTTP server stopped gracefully"),
38 | Err(e) => {
39 | eprintln!("HTTP server error: {}", e);
40 | eprintln!("The app will continue but file serving won't work");
41 | }
42 | }
43 | });
44 | });
45 |
46 | // Give the HTTP server a moment to bind to the port
47 | std::thread::sleep(std::time::Duration::from_millis(100));
48 |
49 | // Run Tauri GUI app with the shared AppData
50 | typeslayer_lib::run_tauri_app(app_data);
51 | }
52 |
53 | Ok(())
54 | }
55 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/contexts/toast-context.tsx:
--------------------------------------------------------------------------------
1 | import { Alert, Button, Snackbar, type SnackbarOrigin } from "@mui/material";
2 | import {
3 | createContext,
4 | type ReactNode,
5 | useCallback,
6 | useContext,
7 | useState,
8 | } from "react";
9 |
10 | export interface ToastData {
11 | message: string;
12 | severity?: "success" | "error" | "warning" | "info";
13 | action?: {
14 | label: string;
15 | icon?: ReactNode;
16 | onClick: () => void | Promise;
17 | };
18 | duration?: number;
19 | anchorOrigin?: SnackbarOrigin;
20 | }
21 |
22 | interface ToastContextType {
23 | showToast: (toast: ToastData) => void;
24 | }
25 |
26 | const ToastContext = createContext(null);
27 |
28 | export const useToast = () => {
29 | const context = useContext(ToastContext);
30 | if (!context) {
31 | throw new Error("useToast must be used within a ToastProvider");
32 | }
33 | return context;
34 | };
35 |
36 | export const ToastProvider = ({ children }: { children: ReactNode }) => {
37 | const [toast, setToast] = useState(null);
38 | const [open, setOpen] = useState(false);
39 |
40 | const showToast = useCallback((toastData: ToastData) => {
41 | setToast(toastData);
42 | setOpen(true);
43 | }, []);
44 |
45 | const handleClose = () => {
46 | setOpen(false);
47 | };
48 |
49 | return (
50 |
51 | {children}
52 |
61 |
73 | {toast.action.label}
74 |
75 | ) : undefined
76 | }
77 | >
78 | {toast?.message}
79 |
80 |
81 |
82 | );
83 | };
84 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/src/process_controller.rs:
--------------------------------------------------------------------------------
1 | #[cfg(unix)]
2 | use std::io;
3 | #[cfg(windows)]
4 | use std::process::Command;
5 | use std::sync::{Arc, Mutex};
6 |
7 | #[derive(Default)]
8 | struct ProcessState {
9 | current_pid: Option,
10 | cancel_requested: bool,
11 | }
12 |
13 | #[derive(Clone, Default)]
14 | pub struct ProcessController {
15 | inner: Arc>,
16 | }
17 |
18 | impl ProcessController {
19 | pub fn new() -> Self {
20 | Self {
21 | inner: Arc::new(Mutex::new(ProcessState::default())),
22 | }
23 | }
24 |
25 | pub fn register_process(&self, pid: u32) {
26 | let mut state = self.inner.lock().expect("process controller poisoned");
27 | state.current_pid = Some(pid);
28 | state.cancel_requested = false;
29 | }
30 |
31 | pub fn clear_current_process(&self) {
32 | let mut state = self.inner.lock().expect("process controller poisoned");
33 | state.current_pid = None;
34 | }
35 |
36 | pub fn reset_cancel_flag(&self) {
37 | let mut state = self.inner.lock().expect("process controller poisoned");
38 | state.cancel_requested = false;
39 | }
40 |
41 | pub fn cancel_requested(&self) -> bool {
42 | let state = self.inner.lock().expect("process controller poisoned");
43 | state.cancel_requested
44 | }
45 |
46 | pub fn request_cancel(&self) -> Result<(), String> {
47 | let pid = {
48 | let mut state = self.inner.lock().expect("process controller poisoned");
49 | state.cancel_requested = true;
50 | state.current_pid
51 | };
52 |
53 | if let Some(pid) = pid {
54 | send_terminate_signal(pid)?;
55 | }
56 |
57 | Ok(())
58 | }
59 | }
60 |
61 | fn send_terminate_signal(pid: u32) -> Result<(), String> {
62 | #[cfg(unix)]
63 | unsafe {
64 | if libc::kill(pid as i32, libc::SIGTERM) == 0 {
65 | return Ok(());
66 | }
67 | return Err(io::Error::last_os_error().to_string());
68 | }
69 |
70 | #[cfg(windows)]
71 | {
72 | let status = Command::new("taskkill")
73 | .args(["/PID", &pid.to_string(), "/T", "/F"])
74 | .status()
75 | .map_err(|e| format!("failed to execute taskkill: {e}"))?;
76 | if status.success() {
77 | Ok(())
78 | } else {
79 | Err("taskkill failed to terminate process".to_string())
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/npm/typeslayer/bin/typeslayer.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import { spawn, spawnSync } from "node:child_process";
4 | import { existsSync } from "node:fs";
5 | import { arch, platform } from "node:os";
6 | import { dirname, join } from "node:path";
7 | import { fileURLToPath } from "node:url";
8 |
9 | const __filename = fileURLToPath(import.meta.url);
10 | const __dirname = dirname(__filename);
11 |
12 | // Store the original directory where the user ran the command
13 | const userCwd = process.cwd();
14 |
15 | // Get the binary path based on platform
16 | const getBinaryInfo = () => {
17 | const plat = platform();
18 | const architecture = arch();
19 |
20 | let platformDir, binaryName;
21 |
22 | if (plat === "linux" && architecture === "x64") {
23 | platformDir = "linux-x64";
24 | binaryName = "typeslayer";
25 | } else if (plat === "darwin" && architecture === "arm64") {
26 | platformDir = "darwin-arm64";
27 | binaryName = "typeslayer";
28 | } else if (plat === "win32" && architecture === "x64") {
29 | platformDir = "win32-x64";
30 | binaryName = "typeslayer.exe";
31 | } else {
32 | throw new Error(`Unsupported platform: ${plat}-${architecture}`);
33 | }
34 |
35 | return {
36 | path: join(__dirname, "..", "binaries", platformDir, binaryName),
37 | platform: `${plat}-${architecture}`,
38 | };
39 | };
40 |
41 | try {
42 | const binaryInfo = getBinaryInfo();
43 |
44 | if (!existsSync(binaryInfo.path)) {
45 | console.error(
46 | `\n⚠️ Binary not found for ${binaryInfo.platform}. Attempting download...`,
47 | );
48 | const postinstallPath = join(__dirname, "..", "scripts", "postinstall.js");
49 | const res = spawnSync(process.execPath, [postinstallPath], {
50 | stdio: "inherit",
51 | env: process.env,
52 | });
53 | if (res.status !== 0) {
54 | console.error("\n❌ Failed to download platform binary.");
55 | }
56 | if (!existsSync(binaryInfo.path)) {
57 | console.error(
58 | `\n❌ Binary still unavailable at ${binaryInfo.path}. Please check the release assets or your network.`,
59 | );
60 | console.error(
61 | "You can manually download from: https://github.com/dimitropoulos/typeslayer/releases",
62 | );
63 | process.exit(1);
64 | }
65 | }
66 |
67 | const proc = spawn(binaryInfo.path, [], {
68 | stdio: "inherit",
69 | env: {
70 | ...process.env,
71 | USER_CWD: userCwd,
72 | },
73 | });
74 |
75 | proc.on("exit", code => {
76 | process.exit(code || 0);
77 | });
78 | } catch (error) {
79 | console.error("Error:", error.message);
80 | process.exit(1);
81 | }
82 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/components/tab-label.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Stack, Tab, type TabProps, Typography } from "@mui/material";
2 |
3 | export const TabLabel = ({
4 | label,
5 | count,
6 | }: {
7 | label: string;
8 | count: number | null;
9 | }) => (
10 |
17 |
22 | {label}
23 |
24 | {count !== null && (
25 |
32 | {count.toLocaleString()}
33 |
34 | )}
35 |
36 | );
37 |
38 | export function VerticalTab({
39 | label,
40 | count,
41 | value,
42 | ...props
43 | }: {
44 | label: string;
45 | count: number | null;
46 | value: string;
47 | } & TabProps) {
48 | return (
49 |
76 |
83 | {label}
84 |
85 | {count !== null && (
86 |
96 | {count.toLocaleString()}
97 |
98 | )}
99 |
100 | }
101 | key={value}
102 | value={value}
103 | />
104 | );
105 | }
106 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/components/openable-path.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Stack,
3 | type SxProps,
4 | Typography,
5 | type TypographyVariant,
6 | } from "@mui/material";
7 | import { invoke } from "@tauri-apps/api/core";
8 | import { useCallback } from "react";
9 | import { useProjectRoot, useRelativePaths } from "../hooks/tauri-hooks";
10 | import { friendlyPath } from "./utils";
11 |
12 | export const propertyTextStyle = {
13 | fontFamily: "monospace",
14 | fontSize: "0.9rem",
15 | };
16 |
17 | export function OpenablePath({
18 | absolutePath,
19 | line,
20 | character,
21 | title,
22 | pathVariant = "body1",
23 | forceAbsolute,
24 | propertyTextStyle = {},
25 | }: {
26 | absolutePath: string;
27 | line?: number;
28 | character?: number;
29 | title?: string;
30 | pathVariant?: TypographyVariant;
31 | forceAbsolute?: boolean;
32 | propertyTextStyle?: SxProps | undefined;
33 | }) {
34 | const relativePaths = useRelativePaths();
35 | const projectRoot = useProjectRoot();
36 | const findInPage = useCallback(async () => {
37 | try {
38 | const goto =
39 | line !== undefined && character !== undefined
40 | ? `${absolutePath}:${line}:${character}`
41 | : absolutePath;
42 | console.log("Opening file via backend:", goto);
43 | await invoke("open_file", { path: goto });
44 | } catch (e) {
45 | console.error("Failed to open file via backend", e);
46 | }
47 | }, [absolutePath, line, character]);
48 |
49 | if (
50 | relativePaths.isLoading ||
51 | projectRoot.isLoading ||
52 | relativePaths.data === undefined ||
53 | projectRoot.data === undefined
54 | ) {
55 | return null;
56 | }
57 |
58 | const lineChar =
59 | line !== undefined && character !== undefined
60 | ? `:${line}:${character}`
61 | : "";
62 |
63 | const exactLocation = `${friendlyPath(absolutePath, projectRoot.data, forceAbsolute ? false : relativePaths.data)}${lineChar}`;
64 |
65 | return (
66 | t.palette.secondary.dark,
73 | },
74 | alignItems: "center",
75 | gap: 1,
76 | }}
77 | key={exactLocation}
78 | onClick={findInPage}
79 | >
80 | {title ? {title} : null}
81 |
90 | {exactLocation}
91 |
92 |
93 | );
94 | }
95 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/src/screenshot.rs:
--------------------------------------------------------------------------------
1 | use tauri::{AppHandle, Manager, WebviewWindow};
2 |
3 | #[tauri::command]
4 | pub async fn take_screenshot(app: AppHandle) -> Result {
5 | // Get the main Tauri window
6 | let tauri_window = app
7 | .get_webview_window("main")
8 | .ok_or("Main window not found")?;
9 |
10 | // Take screenshot using xcap
11 | let screenshot_bytes = capture_window_to_png(&tauri_window)?;
12 |
13 | // Get downloads directory
14 | let downloads_dir = dirs::download_dir().ok_or("Could not find downloads directory")?;
15 |
16 | // Generate filename with timestamp
17 | let timestamp = chrono::Local::now().format("%Y%m%d_%H%M%S");
18 | let filename = format!("typeslayer_{}.png", timestamp);
19 | let screenshot_path = downloads_dir.join(&filename);
20 |
21 | // Save to file
22 | std::fs::write(&screenshot_path, &screenshot_bytes)
23 | .map_err(|e| format!("Failed to save screenshot: {}", e))?;
24 |
25 | // Copy image data to clipboard
26 | use tauri_plugin_clipboard_manager::ClipboardExt;
27 |
28 | let path_str = screenshot_path.to_string_lossy().to_string();
29 |
30 | // Decode PNG to get RGBA data for clipboard
31 | let img = image::load_from_memory(&screenshot_bytes)
32 | .map_err(|e| format!("Failed to decode image: {}", e))?;
33 | let rgba_img = img.to_rgba8();
34 | let (width, height) = rgba_img.dimensions();
35 | let rgba_data = rgba_img.into_raw();
36 |
37 | // Create Tauri Image from RGBA data
38 | let clipboard_image = tauri::image::Image::new(&rgba_data, width, height);
39 |
40 | app.clipboard()
41 | .write_image(&clipboard_image)
42 | .map_err(|e| format!("Failed to copy image to clipboard: {}", e))?;
43 |
44 | Ok(path_str)
45 | }
46 |
47 | fn capture_window_to_png(window: &WebviewWindow) -> Result, String> {
48 | use xcap::Window;
49 |
50 | // Get window title to match against xcap windows
51 | let window_title = window.title().map_err(|e| e.to_string())?;
52 |
53 | // Get all system windows
54 | let windows = Window::all().map_err(|e| e.to_string())?;
55 |
56 | // Find our window by title
57 | let xcap_window = windows
58 | .into_iter()
59 | .find(|w| w.title().contains(&window_title))
60 | .ok_or("Could not find window for screenshot")?;
61 |
62 | // Capture the image
63 | let image = xcap_window.capture_image().map_err(|e| e.to_string())?;
64 |
65 | // Convert to PNG bytes
66 | let mut png_bytes = Vec::new();
67 | image
68 | .write_to(
69 | &mut std::io::Cursor::new(&mut png_bytes),
70 | image::ImageFormat::Png,
71 | )
72 | .map_err(|e| e.to_string())?;
73 |
74 | Ok(png_bytes)
75 | }
76 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/pages/start/step-1-packagejson.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Stack, TextField, Typography } from "@mui/material";
2 | import { open } from "@tauri-apps/plugin-dialog";
3 | import { useCallback, useEffect, useState } from "react";
4 | import { InlineCode } from "../../components/inline-code";
5 | import { useProjectRoot } from "../../hooks/tauri-hooks";
6 | import { Step } from "./step";
7 |
8 | export const Step1PackageJson = () => {
9 | const projectRoot = useProjectRoot();
10 | const [localProjectRoot, setLocalProjectRoot] = useState("");
11 |
12 | // handle update from server on mount
13 | useEffect(() => {
14 | if (localProjectRoot === "" && projectRoot.data) {
15 | setLocalProjectRoot(projectRoot.data);
16 | }
17 | }, [localProjectRoot, projectRoot.data]);
18 |
19 | const applyProjectRoot = useCallback(
20 | async (pkgPath: string) => {
21 | // allow the user to type whether or not it's valid
22 | setLocalProjectRoot(pkgPath);
23 |
24 | try {
25 | await projectRoot.set(pkgPath);
26 | } catch (error) {
27 | console.error("Failed to set project root:", error);
28 | }
29 | },
30 | [projectRoot.set],
31 | );
32 |
33 | const onChange = useCallback(
34 | async (event: React.ChangeEvent) => {
35 | await applyProjectRoot(event.target.value);
36 | },
37 | [applyProjectRoot],
38 | );
39 |
40 | const locatePackageJson = useCallback(async () => {
41 | try {
42 | const selected = await open({
43 | multiple: false,
44 | filters: [{ name: "package.json", extensions: ["json"] }],
45 | });
46 | if (selected && typeof selected === "string") {
47 | // Normalize to package.json path
48 | let pkgPath = selected;
49 | if (!pkgPath.endsWith("package.json")) {
50 | // If it's a directory, append package.json
51 | if (!pkgPath.endsWith("/")) {
52 | pkgPath += "/";
53 | }
54 | pkgPath += "package.json";
55 | }
56 | await applyProjectRoot(pkgPath);
57 | }
58 | } catch (error) {
59 | console.error("Failed to open file picker:", error);
60 | }
61 | }, [applyProjectRoot]);
62 |
63 | return (
64 |
65 |
66 |
67 | locate the package.json of the package you'd
68 | like to investigate
69 |
70 |
71 |
72 |
73 | Locate
74 |
75 |
83 |
84 |
85 |
86 | );
87 | };
88 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/src/treemap.rs:
--------------------------------------------------------------------------------
1 | use crate::analyze_trace::{EventSpan, EventSpanEvent, create_spans};
2 | use crate::validate::trace_json::TraceEvent;
3 | use serde::{Deserialize, Serialize};
4 | use std::collections::HashMap;
5 |
6 | #[derive(Debug, Clone, Serialize, Deserialize)]
7 | pub struct TreemapNode {
8 | pub name: String,
9 | pub value: f64,
10 | #[serde(skip_serializing_if = "Option::is_none")]
11 | pub path: Option,
12 | #[serde(skip_serializing_if = "Option::is_none")]
13 | pub children: Option>,
14 | }
15 |
16 | /// Build treemap data from trace events, grouping by file and summing
17 | /// `checkSourceFile` durations derived from span reconstruction. This mirrors
18 | /// Perfetto's per-file timings and avoids double-counting nested work.
19 | pub fn build_treemap_from_trace(events: &[TraceEvent]) -> Result, String> {
20 | let parse_result = create_spans(events)?;
21 | let last_span_end = if parse_result.last_span_end.is_finite() {
22 | parse_result.last_span_end
23 | } else if let Some(last_event) = events.last() {
24 | last_event.ts
25 | } else {
26 | 0.0
27 | };
28 |
29 | // Incorporate any unclosed spans by stretching them to the last observed
30 | // timestamp so they still contribute deterministically.
31 | let mut spans: Vec = parse_result.spans;
32 | for event in parse_result.unclosed_stack.into_iter() {
33 | let start = event.ts;
34 | spans.push(EventSpan {
35 | event: EventSpanEvent::TraceEvent(event),
36 | start,
37 | end: last_span_end,
38 | duration: last_span_end - start,
39 | children: Vec::new(),
40 | });
41 | }
42 |
43 | let mut file_durations: HashMap = HashMap::new();
44 |
45 | for span in spans {
46 | let EventSpanEvent::TraceEvent(event) = span.event else {
47 | continue;
48 | };
49 |
50 | if event.name != "checkSourceFile" {
51 | continue;
52 | }
53 |
54 | if let Some(path) = event.args.get("path").and_then(|v| v.as_str()) {
55 | *file_durations.entry(path.to_string()).or_insert(0.0) += span.duration;
56 | }
57 | }
58 |
59 | let mut nodes: Vec = file_durations
60 | .into_iter()
61 | .map(|(path, duration)| {
62 | let filename = path
63 | .rsplit('/')
64 | .next()
65 | .map(|s| s.to_string())
66 | .unwrap_or_else(|| path.clone());
67 |
68 | TreemapNode {
69 | name: filename,
70 | value: duration,
71 | path: Some(path),
72 | children: None,
73 | }
74 | })
75 | .collect();
76 |
77 | nodes.sort_by(|a, b| {
78 | b.value
79 | .partial_cmp(&a.value)
80 | .unwrap_or(std::cmp::Ordering::Equal)
81 | });
82 |
83 | Ok(nodes)
84 | }
85 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/pages/start/start.tsx:
--------------------------------------------------------------------------------
1 | import { Stack } from "@mui/material";
2 | import Box from "@mui/material/Box";
3 | import Typography from "@mui/material/Typography";
4 | import { useState } from "react";
5 | import typeslayerLogo from "../../assets/typeslayer.png";
6 | import typeslayerNightmareLogo from "../../assets/typeslayer-nightmare.png";
7 | import { Step0Prerequisites } from "./step-0-prerequisites";
8 | import { Step1PackageJson } from "./step-1-packagejson";
9 | import { Step2Tsconfig } from "./step-2-tsconfig";
10 | import { Step3Diagnostics } from "./step-3-diagnostics";
11 |
12 | export function Start() {
13 | const [isFading, setIsFading] = useState(false);
14 | const [fadePhase, setFadePhase] = useState<0 | 1 | 2>(0);
15 |
16 | return (
17 |
29 |
30 |
31 | Start
32 |
33 |
34 |
35 |
36 |
40 |
41 |
42 |
53 | t.transitions.create("opacity", {
54 | duration: 1000,
55 | easing: t.transitions.easing.easeInOut,
56 | }),
57 | zIndex: t => t.zIndex.modal + 1,
58 | }}
59 | >
60 |
61 |
74 |
89 |
90 |
91 |
92 | );
93 | }
94 |
--------------------------------------------------------------------------------
/scripts/tag.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import { execSync } from "node:child_process";
4 | import { readFileSync } from "node:fs";
5 | import { join } from "node:path";
6 | import { createInterface } from "node:readline";
7 | import { fileURLToPath } from "node:url";
8 |
9 | const __filename = fileURLToPath(import.meta.url);
10 | const __dirname = import.meta.dirname || join(__filename, "..");
11 | const rootDir = join(__dirname, "..");
12 |
13 | function getVersion(pkgPath: string): string {
14 | const pkgJson = JSON.parse(readFileSync(pkgPath, "utf-8"));
15 | return pkgJson.version;
16 | }
17 |
18 | // Get main package version
19 | const mainVersion = getVersion(
20 | join(rootDir, "npm", "typeslayer", "package.json"),
21 | );
22 |
23 | console.log(`\n📌 Validating all packages are at version ${mainVersion}...\n`);
24 |
25 | const packages = [
26 | {
27 | name: "@typeslayer/validate",
28 | path: join(rootDir, "packages", "validate", "package.json"),
29 | },
30 | {
31 | name: "@typeslayer/analyze-trace",
32 | path: join(rootDir, "packages", "analyze-trace", "package.json"),
33 | },
34 | {
35 | name: "@typeslayer/linux-x64",
36 | path: join(rootDir, "npm", "linux-x64", "package.json"),
37 | },
38 | {
39 | name: "@typeslayer/darwin-arm64",
40 | path: join(rootDir, "npm", "darwin-arm64", "package.json"),
41 | },
42 | {
43 | name: "@typeslayer/win32-x64",
44 | path: join(rootDir, "npm", "win32-x64", "package.json"),
45 | },
46 | ];
47 |
48 | let allMatch = true;
49 | for (const pkg of packages) {
50 | const version = getVersion(pkg.path);
51 | if (version === mainVersion) {
52 | console.log(`✅ ${pkg.name}: ${version}`);
53 | } else {
54 | console.log(`❌ ${pkg.name}: ${version} (expected ${mainVersion})`);
55 | allMatch = false;
56 | }
57 | }
58 |
59 | if (!allMatch) {
60 | console.error("\n❌ Not all packages are at the same version!");
61 | process.exit(1);
62 | }
63 |
64 | console.log(`\n📝 Tags to create:\n`);
65 |
66 | const tags = [
67 | `typeslayer-v${mainVersion}`,
68 | `analyze-trace-v${mainVersion}`,
69 | `validate-v${mainVersion}`,
70 | ];
71 |
72 | for (const tag of tags) {
73 | console.log(` - ${tag}`);
74 | }
75 |
76 | console.log();
77 |
78 | // Ask for confirmation
79 | const rl = createInterface({
80 | input: process.stdin,
81 | output: process.stdout,
82 | });
83 |
84 | rl.question(
85 | "Press Enter to confirm and create tags, or Ctrl+C to cancel: ",
86 | () => {
87 | rl.close();
88 |
89 | console.log(`\n🏷️ Creating tags...\n`);
90 |
91 | for (const tag of tags) {
92 | execSync(`git tag --force ${tag}`, { cwd: rootDir, stdio: "inherit" });
93 | console.log(`✅ Created tag: ${tag}`);
94 | }
95 |
96 | console.log(`\n📤 Pushing tags...\n`);
97 |
98 | const tagRefs = tags.map(tag => `refs/tags/${tag}`).join(" ");
99 | execSync(`git push origin --force ${tagRefs}`, {
100 | cwd: rootDir,
101 | stdio: "inherit",
102 | });
103 |
104 | console.log(`\n✨ All tags created and pushed!`);
105 | },
106 | );
107 |
--------------------------------------------------------------------------------
/scripts/bump.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import { readFileSync, writeFileSync } from "node:fs";
4 | import { join } from "node:path";
5 | import { fileURLToPath } from "node:url";
6 |
7 | const __filename = fileURLToPath(import.meta.url);
8 | const __dirname = import.meta.dirname || join(__filename, "..");
9 | const rootDir = join(__dirname, "..");
10 |
11 | const bumpType = process.argv[2] ?? "patch";
12 |
13 | if (!bumpType || !["patch", "minor", "major"].includes(bumpType)) {
14 | console.error("❌ Usage: bump-typeslayer.ts ");
15 | process.exit(1);
16 | }
17 |
18 | function bumpVersion(version: string, type: string): string {
19 | const parts = version.split(".");
20 | const major = parseInt(parts[0], 10);
21 | const minor = parseInt(parts[1], 10);
22 | const patch = parseInt(parts[2], 10);
23 |
24 | if (type === "major") {
25 | return `${major + 1}.0.0`;
26 | } else if (type === "minor") {
27 | return `${major}.${minor + 1}.0`;
28 | } else {
29 | return `${major}.${minor}.${patch + 1}`;
30 | }
31 | }
32 |
33 | // Read main package version
34 | const mainPackageJsonPath = join(rootDir, "npm", "typeslayer", "package.json");
35 | const mainPackageJson = JSON.parse(readFileSync(mainPackageJsonPath, "utf-8"));
36 | const oldMainVersion = mainPackageJson.version;
37 | const newMainVersion = bumpVersion(oldMainVersion, bumpType);
38 |
39 | mainPackageJson.version = newMainVersion;
40 |
41 | // Also update optionalDependencies versions in main package
42 | if (mainPackageJson.optionalDependencies) {
43 | for (const [depName] of Object.entries(
44 | mainPackageJson.optionalDependencies,
45 | )) {
46 | mainPackageJson.optionalDependencies[depName] = newMainVersion;
47 | }
48 | }
49 |
50 | writeFileSync(
51 | mainPackageJsonPath,
52 | `${JSON.stringify(mainPackageJson, null, 2)}\n`,
53 | );
54 |
55 | console.log(`📦 Bumped main package: ${oldMainVersion} → ${newMainVersion}`);
56 |
57 | // Bump workspace packages
58 | const workspacePackages = ["validate", "analyze-trace"];
59 |
60 | for (const pkgName of workspacePackages) {
61 | const pkgPath = join(rootDir, "packages", pkgName, "package.json");
62 | const pkgJson = JSON.parse(readFileSync(pkgPath, "utf-8"));
63 | const oldVersion = pkgJson.version;
64 |
65 | pkgJson.version = newMainVersion;
66 | writeFileSync(pkgPath, `${JSON.stringify(pkgJson, null, 2)}\n`);
67 | console.log(` ✅ @typeslayer/${pkgName}: ${oldVersion} → ${newMainVersion}`);
68 | }
69 |
70 | const platforms = ["linux-x64", "darwin-arm64", "win32-x64"];
71 |
72 | for (const platform of platforms) {
73 | const buildPackageJsonPath = join(rootDir, "npm", platform, "package.json");
74 |
75 | const buildPackageJson = JSON.parse(
76 | readFileSync(buildPackageJsonPath, "utf-8"),
77 | );
78 | const oldVersion = buildPackageJson.version;
79 |
80 | buildPackageJson.version = newMainVersion;
81 | writeFileSync(
82 | buildPackageJsonPath,
83 | `${JSON.stringify(buildPackageJson, null, 2)}\n`,
84 | );
85 | console.log(
86 | ` ✅ @typeslayer/${platform}: ${oldVersion} → ${newMainVersion}`,
87 | );
88 | }
89 |
90 | console.log("\n✨ All packages bumped and synced!");
91 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/components/action-bar.tsx:
--------------------------------------------------------------------------------
1 | import { CameraAlt, Help } from "@mui/icons-material";
2 | import {
3 | Button,
4 | Dialog,
5 | DialogActions,
6 | DialogTitle,
7 | IconButton,
8 | Link,
9 | Stack,
10 | Typography,
11 | } from "@mui/material";
12 | import { invoke } from "@tauri-apps/api/core";
13 | import { type FC, useCallback, useState } from "react";
14 | import { useToast } from "../contexts/toast-context";
15 | import { BugReport } from "./bug-report";
16 | import { MITS_DISCORD } from "./constants";
17 | import { InlineCode } from "./inline-code";
18 | import { createOpenHandler } from "./utils";
19 |
20 | const HelpDialog = () => {
21 | const [open, setOpen] = useState(false);
22 |
23 | const handleClose = useCallback(() => {
24 | setOpen(false);
25 | }, []);
26 |
27 | return (
28 | <>
29 |
30 | TypeSlayer Help
31 |
32 | this is a pretty new thing.
33 |
34 | {" "}
35 | if you have questions or need help, check out the{" "}
36 | #typeslayer channel on the{" "}
37 |
38 | Michigan TypeScript Discord
39 | {" "}
40 | or send a bug report:
41 |
42 |
43 |
44 |
45 | Close
46 |
47 |
48 |
49 | setOpen(true)}>
50 |
51 |
52 | >
53 | );
54 | };
55 |
56 | export const ActionBar: FC<{ collapsed: boolean }> = ({ collapsed }) => {
57 | const { showToast } = useToast();
58 |
59 | const handleScreenshot = async () => {
60 | try {
61 | const savedPath = await invoke("take_screenshot");
62 | showToast({
63 | message: savedPath
64 | ? `Screenshot saved to ${savedPath} and copied to clipboard`
65 | : "Screenshot captured",
66 | severity: "success",
67 | duration: 4000,
68 | });
69 | } catch (error) {
70 | showToast({
71 | message: `Unable to capture screenshot: ${String(error)}`,
72 | severity: "error",
73 | duration: 4000,
74 | });
75 | }
76 | };
77 |
78 | return (
79 | t.palette.text.secondary,
87 | "&:hover": {
88 | color: t => t.palette.text.primary,
89 | },
90 | },
91 | }}
92 | >
93 |
94 |
95 |
96 |
97 |
98 |
99 | );
100 | };
101 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/src/analyze_trace/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod constants;
2 | mod depth_limits;
3 | mod duplicate_node_modules;
4 | mod hotspots;
5 | mod node_module_paths;
6 | mod spans;
7 | mod types;
8 |
9 | pub use depth_limits::DepthLimitKind;
10 | pub use spans::create_spans;
11 | pub use types::*;
12 |
13 | use crate::analyze_trace::constants::ANALYZE_TRACE_FILENAME;
14 | use crate::analyze_trace::depth_limits::create_depth_limits;
15 | use crate::analyze_trace::duplicate_node_modules::get_duplicate_node_modules;
16 | use crate::analyze_trace::hotspots::get_hotspots;
17 | use crate::analyze_trace::node_module_paths::get_node_module_paths;
18 | use crate::analyze_trace::spans::create_span_tree;
19 | use crate::validate::trace_json::{TRACE_JSON_FILENAME, TraceEvent};
20 | use serde_json;
21 | use std::fs::{self, File};
22 | use std::io::{self, BufReader};
23 | use std::path::Path;
24 |
25 | pub fn validate_options(options: &AnalyzeTraceOptions) -> Result<(), String> {
26 | if options.force_millis < options.skip_millis {
27 | return Err("forceMillis cannot be less than skipMillis".to_string());
28 | }
29 | Ok(())
30 | }
31 |
32 | pub fn analyze_trace(
33 | trace_dir: &str,
34 | options: Option,
35 | ) -> Result {
36 | let options = options.unwrap_or_default();
37 | validate_options(&options)?;
38 |
39 | // Validate trace directory and read files
40 | let trace_dir_path = Path::new(trace_dir);
41 | if !trace_dir_path.is_dir() {
42 | return Err(format!("{} is not a directory", trace_dir));
43 | }
44 |
45 | // Read trace.json
46 | let trace_file_path = trace_dir_path.join(TRACE_JSON_FILENAME);
47 | let trace_file = match File::open(&trace_file_path) {
48 | Ok(f) => f,
49 | Err(e) if e.kind() == io::ErrorKind::NotFound => {
50 | return Err(format!(
51 | "trace.json must exist in {}. first run --generateTrace",
52 | trace_dir
53 | ));
54 | }
55 | Err(e) => return Err(format!("Failed to open trace.json: {}", e)),
56 | };
57 | let trace_file: Vec = serde_json::from_reader(BufReader::new(trace_file))
58 | .map_err(|e| format!("Failed to parse trace.json: {}", e))?;
59 |
60 | let node_module_paths = get_node_module_paths(&trace_file);
61 | let parse_result = create_spans(&trace_file)?;
62 | let hot_paths_tree = create_span_tree(parse_result.clone(), &options);
63 | let hot_spots = get_hotspots(&hot_paths_tree, &options)?;
64 | let duplicate_packages = get_duplicate_node_modules(&node_module_paths)?;
65 | let depth_limits = create_depth_limits(&trace_file);
66 | let unterminated_events = parse_result.unclosed_stack.into_iter().rev().collect();
67 |
68 | let result = AnalyzeTraceResult {
69 | depth_limits,
70 | duplicate_packages,
71 | hot_spots,
72 | unterminated_events,
73 | node_module_paths,
74 | };
75 |
76 | // Write result to analyze-trace.json
77 | let output_path = trace_dir_path.join(ANALYZE_TRACE_FILENAME);
78 | let output_json = serde_json::to_string_pretty(&result)
79 | .map_err(|e| format!("Failed to serialize result: {}", e))?;
80 | fs::write(&output_path, output_json)
81 | .map_err(|e| format!("Failed to write analyze-trace.json: {}", e))?;
82 |
83 | Ok(result)
84 | }
85 |
--------------------------------------------------------------------------------
/packages/analyze-trace/src/analyze-trace.ts:
--------------------------------------------------------------------------------
1 | import { existsSync } from "node:fs";
2 | import { readFile, writeFile } from "node:fs/promises";
3 | import { join } from "node:path";
4 | import {
5 | TRACE_JSON_FILENAME,
6 | type TraceJsonSchema,
7 | TYPES_JSON_FILENAME,
8 | type TypesJsonSchema,
9 | traceJsonSchema,
10 | typesJsonSchema,
11 | } from "@typeslayer/validate";
12 | import { ANALYZE_TRACE_FILENAME } from "./constants";
13 | import { createDepthLimits } from "./depth-limits";
14 | import { getDuplicateNodeModules } from "./get-duplicate-node-modules";
15 | import { getHotspots } from "./get-hotspots";
16 | import { getNodeModulePaths } from "./node-module-paths";
17 | import { createSpans, createSpanTree } from "./spans";
18 | import type { AnalyzeTraceResult } from "./utils";
19 | import {
20 | type AbsolutePath,
21 | type AnalyzeTraceOptions,
22 | throwIfNotDirectory,
23 | } from "./utils";
24 |
25 | export function validateOptions(options: AnalyzeTraceOptions) {
26 | if (options.forceMillis < options.skipMillis) {
27 | throw new Error("forceMillis cannot be less than skipMillis");
28 | }
29 | }
30 |
31 | const validateTraceDir = async (
32 | traceDir: AbsolutePath,
33 | ): Promise<{
34 | traceFile: TraceJsonSchema;
35 | typesFile: TypesJsonSchema;
36 | }> => {
37 | await throwIfNotDirectory(traceDir);
38 |
39 | const typesFilePath = join(traceDir, TYPES_JSON_FILENAME);
40 | if (!existsSync(typesFilePath)) {
41 | throw new Error(
42 | `types.json must exist in ${traceDir}. first run --generateTrace`,
43 | );
44 | }
45 | const typesFileJson = JSON.parse(await readFile(typesFilePath, "utf8"));
46 | const typesFile = typesJsonSchema.parse(typesFileJson);
47 |
48 | const traceFilePath = join(traceDir, TRACE_JSON_FILENAME);
49 | if (!existsSync(traceFilePath)) {
50 | throw new Error(
51 | `trace.json must exist in ${traceDir}. first run --generateTrace`,
52 | );
53 | }
54 | const traceFileJson = JSON.parse(await readFile(traceFilePath, "utf8"));
55 | const traceFile = traceJsonSchema.parse(traceFileJson);
56 |
57 | return {
58 | traceFile,
59 | typesFile,
60 | };
61 | };
62 |
63 | export const defaultOptions: AnalyzeTraceOptions = {
64 | forceMillis: 500,
65 | skipMillis: 100,
66 | expandTypes: true,
67 | minSpanParentPercentage: 0.6,
68 | importExpressionThreshold: 10,
69 | };
70 |
71 | export const analyzeTrace = async ({
72 | traceDir,
73 | options = defaultOptions,
74 | }: {
75 | traceDir: AbsolutePath;
76 | options?: AnalyzeTraceOptions;
77 | }) => {
78 | validateOptions(options);
79 | const { traceFile, typesFile } = await validateTraceDir(traceDir);
80 |
81 | const nodeModulePaths = getNodeModulePaths(traceFile);
82 |
83 | const spans = createSpans(traceFile);
84 | const hotPathsTree = createSpanTree(spans, options);
85 |
86 | const result: AnalyzeTraceResult = {
87 | nodeModulePaths,
88 | unterminatedEvents: spans.unclosedStack.reverse(),
89 | hotSpots: await getHotspots(hotPathsTree, typesFile, options),
90 | duplicatePackages: await getDuplicateNodeModules(nodeModulePaths),
91 | depthLimits: createDepthLimits(traceFile),
92 | };
93 | await writeFile(
94 | join(traceDir, ANALYZE_TRACE_FILENAME),
95 | JSON.stringify(result, null, 2),
96 | );
97 |
98 | return result;
99 | };
100 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/src/analyze_trace/types.rs:
--------------------------------------------------------------------------------
1 | use crate::{analyze_trace::depth_limits::DepthLimitKind, validate::trace_json::TraceEvent};
2 | use serde::{Deserialize, Serialize};
3 | use std::collections::HashMap;
4 |
5 | #[derive(Debug, Clone, Serialize, Deserialize)]
6 | #[serde(rename_all = "camelCase")]
7 | pub struct AnalyzeTraceOptions {
8 | pub force_millis: f64,
9 | pub skip_millis: f64,
10 | pub expand_types: bool,
11 | pub min_span_parent_percentage: f64,
12 | pub import_expression_threshold: u32,
13 | }
14 |
15 | impl Default for AnalyzeTraceOptions {
16 | fn default() -> Self {
17 | Self {
18 | force_millis: 500.0,
19 | skip_millis: 100.0,
20 | expand_types: true,
21 | min_span_parent_percentage: 0.6,
22 | import_expression_threshold: 10,
23 | }
24 | }
25 | }
26 |
27 | #[derive(Debug, Clone, Serialize, Deserialize)]
28 | pub struct EventSpan {
29 | pub event: EventSpanEvent,
30 | pub start: f64,
31 | pub end: f64,
32 | pub duration: f64,
33 | pub children: Vec,
34 | }
35 |
36 | #[derive(Debug, Clone, Serialize, Deserialize)]
37 | #[serde(untagged)]
38 | pub enum EventSpanEvent {
39 | Root { name: String, cat: String },
40 | TraceEvent(TraceEvent),
41 | }
42 |
43 | #[derive(Debug, Clone, Serialize, Deserialize)]
44 | #[serde(rename_all = "camelCase")]
45 | pub struct ParseResult {
46 | pub first_span_start: f64,
47 | pub last_span_end: f64,
48 | pub spans: Vec,
49 | pub unclosed_stack: Vec,
50 | }
51 |
52 | #[derive(Debug, Clone, Serialize, Deserialize)]
53 | #[serde(rename_all = "camelCase")]
54 | pub struct HotSpot {
55 | pub description: String,
56 | #[serde(rename = "timeMs")]
57 | pub time_ms: i64,
58 | #[serde(skip_serializing_if = "Option::is_none")]
59 | pub path: Option,
60 | #[serde(skip_serializing_if = "Option::is_none")]
61 | pub types: Option>,
62 | #[serde(skip_serializing_if = "Option::is_none")]
63 | pub start_line: Option,
64 | #[serde(skip_serializing_if = "Option::is_none")]
65 | pub start_char: Option,
66 | #[serde(skip_serializing_if = "Option::is_none")]
67 | pub start_offset: Option,
68 | #[serde(skip_serializing_if = "Option::is_none")]
69 | pub end_line: Option,
70 | #[serde(skip_serializing_if = "Option::is_none")]
71 | pub end_char: Option,
72 | #[serde(skip_serializing_if = "Option::is_none")]
73 | pub end_offset: Option,
74 | pub children: Vec,
75 | }
76 |
77 | #[derive(Debug, Clone, Serialize, Deserialize)]
78 | pub struct DuplicatedPackageInstance {
79 | pub path: String,
80 | pub version: String,
81 | }
82 |
83 | #[derive(Debug, Clone, Serialize, Deserialize)]
84 | pub struct DuplicatedPackage {
85 | pub name: String,
86 | pub instances: Vec,
87 | }
88 |
89 | pub type NodeModulePaths = HashMap>;
90 |
91 | #[derive(Debug, Clone, Serialize, Deserialize)]
92 | #[serde(rename_all = "camelCase")]
93 | pub struct AnalyzeTraceResult {
94 | pub depth_limits: HashMap>,
95 | pub duplicate_packages: Vec,
96 | pub hot_spots: Vec,
97 | pub unterminated_events: Vec,
98 | pub node_module_paths: NodeModulePaths,
99 | }
100 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/src/mcp/tools/get_hot_files.rs:
--------------------------------------------------------------------------------
1 | use crate::{
2 | app_data::AppData,
3 | mcp::tools::{ToolDefinition, ToolParameter},
4 | };
5 | use serde::{Deserialize, Serialize};
6 | use std::sync::{Arc, Mutex};
7 | use tracing::info;
8 |
9 | pub const COMMAND: &str = "get_hot_files";
10 | pub const DESCRIPTION: &str = "Returns the files that took the longest to compile, based on the trace data used by the treemap.";
11 |
12 | #[derive(Debug, Clone, Serialize, Deserialize)]
13 | pub struct HotFileInfo {
14 | pub path: String,
15 | pub duration_ms: f64,
16 | }
17 |
18 | #[derive(Debug, Serialize, Deserialize)]
19 | pub struct GetHotFilesResponse {
20 | pub files: Vec,
21 | pub total_files: usize,
22 | }
23 |
24 | /// Return type example for get_hot_files tool
25 | #[derive(Debug, Clone, Serialize, Deserialize)]
26 | #[serde(rename_all = "camelCase")]
27 | pub struct GetHotFilesExample {
28 | pub files: Vec,
29 | pub total_files: u32,
30 | }
31 |
32 | pub fn tool_definition() -> ToolDefinition {
33 | ToolDefinition {
34 | command: COMMAND.to_string(),
35 | display_name: "Get Hot Files".to_string(),
36 | description: DESCRIPTION.to_string(),
37 | parameters: vec![ToolParameter {
38 | name: "limit".to_string(),
39 | optional: true,
40 | default: Some(serde_json::json!(10)),
41 | description: "Maximum number of hottest files to return".to_string(),
42 | }],
43 | returns: GetHotFilesExample {
44 | files: vec![HotFileInfo {
45 | path: "src/example.ts".to_string(),
46 | duration_ms: 1234.5,
47 | }],
48 | total_files: 1,
49 | },
50 | }
51 | }
52 |
53 | pub async fn execute(app_data: Arc>) -> String {
54 | info!("get_hot_files called");
55 |
56 | // Lock app_data to access trace data
57 | let data = match app_data.lock() {
58 | Ok(d) => d,
59 | Err(e) => return format!("{{\"error\": \"Failed to lock app data: {}\"}}", e),
60 | };
61 |
62 | if data.trace_json.is_empty() {
63 | return r#"{"error": "No trace data available. Please generate a trace first."}"#
64 | .to_string();
65 | }
66 |
67 | // Build treemap data (already sorted desc by duration)
68 | let treemap_nodes = match crate::treemap::build_treemap_from_trace(&data.trace_json) {
69 | Ok(nodes) => nodes,
70 | Err(e) => return format!("{{\"error\": \"Failed to build treemap data: {}\"}}", e),
71 | };
72 |
73 | let limit = 10usize; // keep stubbed for now; matches tool definition default
74 |
75 | let mut files: Vec = treemap_nodes
76 | .into_iter()
77 | .take(limit)
78 | .map(|node| HotFileInfo {
79 | path: node.path.unwrap_or_else(|| node.name.clone()),
80 | duration_ms: node.value,
81 | })
82 | .collect();
83 |
84 | // Ensure stable descending order (treemap already sorted, but be explicit)
85 | files.sort_by(|a, b| {
86 | b.duration_ms
87 | .partial_cmp(&a.duration_ms)
88 | .unwrap_or(std::cmp::Ordering::Equal)
89 | });
90 |
91 | let response = GetHotFilesResponse {
92 | total_files: files.len(),
93 | files,
94 | };
95 |
96 | match serde_json::to_string_pretty(&response) {
97 | Ok(json) => json,
98 | Err(e) => format!("{{\"error\": \"Failed to serialize response: {}\"}}", e),
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/src/mcp/status.rs:
--------------------------------------------------------------------------------
1 | use serde::{Deserialize, Serialize};
2 | use std::collections::HashMap;
3 | use std::sync::{Arc, Mutex};
4 | use tracing::info;
5 |
6 | /// Represents the execution status of an MCP tool
7 | #[derive(Debug, Clone, Serialize, Deserialize)]
8 | #[serde(rename_all = "camelCase")]
9 | pub struct ToolStatus {
10 | /// Name of the tool
11 | pub command: String,
12 | /// Timestamp when the tool started execution (Unix seconds)
13 | pub started_at: u64,
14 | }
15 |
16 | /// Global MCP status tracker - tracks which tools are currently running
17 | #[derive(Clone)]
18 | pub struct McpStatusTracker {
19 | // Map of tool_name -> ToolStatus
20 | tools: Arc>>,
21 | }
22 |
23 | impl McpStatusTracker {
24 | /// Create a new MCP status tracker
25 | pub fn new() -> Self {
26 | Self {
27 | tools: Arc::new(Mutex::new(HashMap::new())),
28 | }
29 | }
30 |
31 | /// Mark a tool as started
32 | #[allow(dead_code)]
33 | pub fn start_tool(&self, tool_name: impl Into) {
34 | let tool_name = tool_name.into();
35 | info!("MCP tool started: {}", tool_name);
36 |
37 | if let Ok(mut tools) = self.tools.lock() {
38 | tools.insert(
39 | tool_name.clone(),
40 | ToolStatus {
41 | command: tool_name,
42 | started_at: std::time::SystemTime::now()
43 | .duration_since(std::time::UNIX_EPOCH)
44 | .map(|d| d.as_secs())
45 | .unwrap_or(0),
46 | },
47 | );
48 | }
49 | }
50 |
51 | /// Mark a tool as finished (remove from running tools)
52 | #[allow(dead_code)]
53 | pub fn end_tool(&self, tool_name: &str) {
54 | info!("MCP tool completed: {}", tool_name);
55 | if let Ok(mut tools) = self.tools.lock() {
56 | tools.remove(tool_name);
57 | }
58 | }
59 |
60 | /// Get the status of a specific tool if it's running
61 | #[allow(dead_code)]
62 | pub fn get_tool_status(&self, tool_name: &str) -> Option {
63 | self.tools
64 | .lock()
65 | .ok()
66 | .and_then(|tools| tools.get(tool_name).cloned())
67 | }
68 |
69 | /// Get all currently running tools
70 | pub fn get_running_tools(&self) -> Vec {
71 | self.tools
72 | .lock()
73 | .ok()
74 | .map(|tools| tools.values().cloned().collect())
75 | .unwrap_or_default()
76 | }
77 |
78 | /// Clear all tool statuses
79 | #[allow(dead_code)]
80 | pub fn clear_all(&self) {
81 | if let Ok(mut tools) = self.tools.lock() {
82 | tools.clear();
83 | }
84 | }
85 | }
86 |
87 | impl Default for McpStatusTracker {
88 | fn default() -> Self {
89 | Self::new()
90 | }
91 | }
92 |
93 | /// Tauri command to get the status of running MCP tools
94 | #[tauri::command]
95 | pub fn get_mcp_running_tools(state: tauri::State<'_, McpStatusTracker>) -> Vec {
96 | state.get_running_tools()
97 | }
98 |
99 | /// Tauri command to get available MCP tool definitions
100 | #[tauri::command]
101 | pub fn get_available_mcp_tools() -> Vec {
102 | crate::mcp::tools::get_tool_definitions()
103 | }
104 |
105 | /// Tauri command to get available MCP resources
106 | #[tauri::command]
107 | pub fn get_available_mcp_resources() -> Vec {
108 | crate::mcp::resources::resources::get_output_resources()
109 | }
110 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | a tool for diagnosing and fixing TypeScript performance problems
8 |
9 |
10 | ## Quickstart
11 |
12 | run:
13 |
14 | ```bash
15 | npx typeslayer
16 | ```
17 |
18 | in the root package you want to inspect (i.e. colocated to your package.json). The tool will:
19 |
20 | 1. start a local web UI,
21 | 2. run TypeScript tooling to produce traces and CPU profiles
22 | 3. provide interactive visualizations (treemaps, force graphs, speedscope/perfetto views) that you can use to identify problems
23 |
24 | ## Frequently Asked Questions
25 |
26 | - will this ever get CI support for analysis file generation?
27 | - yeah prolly eventually.
28 | - will this support monorepos?
29 | - that's the hope. one step at a time.
30 | - why isn't this just a CLI tool?
31 | - a goal of the project is show intuitive/beautiful interactive visualizations like treemaps and force graphs, inherently not something a terminal can provide.
32 | - I don't like CLI tools. I view them as a last resort, at this point in engineering history. if you're someone that stays up late into the night staring at your dotfiles from neovim... I'm happy for you. be happy for me too?
33 | - will this work with ts-go?
34 | - that's the hope but it ain't ready yet on the ts-go side
35 |
36 | # Who needs this stupid thing, anyway?
37 |
38 | TypeSlayer is one of those things that most developers don't need. others might just find it lulzy to play around with.
39 |
40 | but then there's the power users. the library authors. the people attending SquiggleConf. you know the kind.
41 |
42 | for the people pushing the boundaries of TypeScript (or at companies doing so intentionally or not), a tool like this is something we haven't seen before. there were ways to trash _hours_ messing with multi-hundred-megabyte JSON files. and that's not even counting the time required to learn how the TypeScript compiler works so you can understand the problem... it's a lot.
43 |
44 | consider the fact that this can sometimes feel like an impossible task for a library author because the code that actually exercises the library isn't directly accessible - it's in the _library user_'s code (which is usually private).
45 |
46 | I made TypeSlayer because I learned delulu-levels-of-detail about TypeScript performance tuning [while getting Doom to run in TypeScript types](https://youtu.be/0mCsluv5FXA), yet many of those techniques are still opaque to most people.
47 |
48 | I wanted to make it easy for others to put up PRs at their companies all like "I increased type-checking perf on this file by 380,000x and shaved 23 seconds off every CI run" (real story btw lol). I took what I learned about how to debug type performance and wrapped it all up into this tool.
49 |
50 | ## Data / Security
51 |
52 | TypeSlayer supports Linux x64 (glibc 2.39+), macOS ARM64 (Apple Silicon), and Windows x64. Please note that next year is the year of the Linux desktop.
53 |
54 | TypeSlayer currently does not collect any analytics - although it probably will try to collect "someone somewhere ran it at XYZ timestamp" data in the future. all data is stored:
55 |
56 | - Linux: `~/.local/share/typeslayer/`
57 | - macOS: `~/Library/Application Support/typeslayer/`
58 | - Windows: `%APPDATA%\typeslayer\`
59 |
60 | TypeSlayer can read any file the running user can read and it can run package.json scripts (so treat it as you would your terminal).
61 |
62 | ## Contributing
63 |
64 | 1. all commits (and therefor PR merges) must be the next bar from "My Name Is" by Eminem, until further notice
65 | 2. no further requirements
66 |
67 | ## Thank You
68 |
69 | this app is built with Tauri, TanStack, Vite, Biome, React, MUI, Rust, and of course, TypeScript.
70 |
--------------------------------------------------------------------------------
/npm/typeslayer/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | a tool for diagnosing and fixing TypeScript performance problems
8 |
9 |
10 | ## Quickstart
11 |
12 | run:
13 |
14 | ```bash
15 | npx typeslayer
16 | ```
17 |
18 | in the root package you want to inspect (i.e. colocated to your package.json). The tool will:
19 |
20 | 1. start a local web UI,
21 | 2. run TypeScript tooling to produce traces and CPU profiles
22 | 3. provide interactive visualizations (treemaps, force graphs, speedscope/perfetto views) that you can use to identify problems
23 |
24 | ## Frequently Asked Questions
25 |
26 | - will this ever get CI support for analysis file generation?
27 | - yeah prolly eventually.
28 | - will this support monorepos?
29 | - that's the hope. one step at a time.
30 | - why isn't this just a CLI tool?
31 | - a goal of the project is show intuitive/beautiful interactive visualizations like treemaps and force graphs, inherently not something a terminal can provide.
32 | - I don't like CLI tools. I view them as a last resort, at this point in engineering history. if you're someone that stays up late into the night staring at your dotfiles from neovim... I'm happy for you. be happy for me too?
33 | - will this work with ts-go?
34 | - that's the hope but it ain't ready yet on the ts-go side
35 |
36 | # Who needs this stupid thing, anyway?
37 |
38 | TypeSlayer is one of those things that most developers don't need. others might just find it lulzy to play around with.
39 |
40 | but then there's the power users. the library authors. the people attending SquiggleConf. you know the kind.
41 |
42 | for the people pushing the boundaries of TypeScript (or at companies doing so intentionally or not), a tool like this is something we haven't seen before. there were ways to trash _hours_ messing with multi-hundred-megabyte JSON files. and that's not even counting the time required to learn how the TypeScript compiler works so you can understand the problem... it's a lot.
43 |
44 | consider the fact that this can sometimes feel like an impossible task for a library author because the code that actually exercises the library isn't directly accessible - it's in the _library user_'s code (which is usually private).
45 |
46 | I made TypeSlayer because I learned delulu-levels-of-detail about TypeScript performance tuning [while getting Doom to run in TypeScript types](https://youtu.be/0mCsluv5FXA), yet many of those techniques are still opaque to most people.
47 |
48 | I wanted to make it easy for others to put up PRs at their companies all like "I increased type-checking perf on this file by 380,000x and shaved 23 seconds off every CI run" (real story btw lol). I took what I learned about how to debug type performance and wrapped it all up into this tool.
49 |
50 | ## Data / Security
51 |
52 | TypeSlayer supports Linux x64 (glibc 2.39+), macOS ARM64 (Apple Silicon), and Windows x64. Please note that next year is the year of the Linux desktop.
53 |
54 | TypeSlayer currently does not collect any analytics - although it probably will try to collect "someone somewhere ran it at XYZ timestamp" data in the future. all data is stored:
55 |
56 | - Linux: `~/.local/share/typeslayer/`
57 | - macOS: `~/Library/Application Support/typeslayer/`
58 | - Windows: `%APPDATA%\typeslayer\`
59 |
60 | TypeSlayer can read any file the running user can read and it can run package.json scripts (so treat it as you would your terminal).
61 |
62 | ## Contributing
63 |
64 | 1. all commits (and therefor PR merges) must be the next bar from "My Name Is" by Eminem, until further notice
65 | 2. no further requirements
66 |
67 | ## Thank You
68 |
69 | this app is built with Tauri, TanStack, Vite, Biome, React, MUI, Rust, and of course, TypeScript.
70 |
--------------------------------------------------------------------------------
/packages/analyze-trace/src/spans.ts:
--------------------------------------------------------------------------------
1 | import {
2 | eventPhase,
3 | type TraceEvent,
4 | type TraceJsonSchema,
5 | } from "@typeslayer/validate";
6 | import type {
7 | AnalyzeTraceOptions,
8 | EventSpan,
9 | Microseconds,
10 | ParseResult,
11 | } from "./utils";
12 |
13 | /*
14 | * This function takes an array of trace events and converts them into spans.
15 | */
16 | export function createSpans(traceFile: TraceJsonSchema): ParseResult {
17 | // Sorted in increasing order of start time (even when below timestamp resolution)
18 | const unclosedStack: TraceEvent[] = [];
19 |
20 | // Sorted in increasing order of end time, then increasing order of start time (even when below timestamp resolution)
21 | const spans: EventSpan[] = [];
22 |
23 | traceFile.forEach((event: TraceEvent) => {
24 | switch (event.ph) {
25 | case eventPhase.begin:
26 | unclosedStack.push(event);
27 | return;
28 |
29 | case eventPhase.end: {
30 | const beginEvent = unclosedStack.pop();
31 | if (!beginEvent) {
32 | throw new Error("Unmatched end event");
33 | }
34 | spans.push({
35 | event: beginEvent,
36 | start: beginEvent.ts,
37 | end: event.ts,
38 | duration: event.ts - beginEvent.ts,
39 | children: [],
40 | });
41 | break;
42 | }
43 |
44 | case eventPhase.complete: {
45 | const start = event.ts;
46 | const duration = event.dur ?? 0;
47 | spans.push({
48 | event,
49 | start,
50 | end: start + duration,
51 | duration,
52 | children: [],
53 | });
54 | break;
55 | }
56 |
57 | case eventPhase.instantGlobal:
58 | case eventPhase.metadata:
59 | return;
60 |
61 | default:
62 | event satisfies never;
63 | }
64 | });
65 |
66 | const parseResult: ParseResult = {
67 | firstSpanStart: Math.min(...spans.map(span => span.start)),
68 | lastSpanEnd: Math.max(...spans.map(span => span.end)),
69 | spans,
70 | unclosedStack,
71 | };
72 | return parseResult;
73 | }
74 |
75 | export function createSpanTree(
76 | parseResult: ParseResult,
77 | options: AnalyzeTraceOptions,
78 | ): EventSpan {
79 | const { firstSpanStart, lastSpanEnd, spans, unclosedStack } = parseResult;
80 |
81 | // Add unclosed events to the spans
82 | for (let i = unclosedStack.length - 1; i >= 0; i--) {
83 | const event = unclosedStack[i];
84 | const start = event.ts;
85 | const end = lastSpanEnd;
86 | spans.push({
87 | event,
88 | start,
89 | end,
90 | duration: end - start,
91 | children: [],
92 | });
93 | }
94 |
95 | spans.sort((a, b) => a.start - b.start);
96 |
97 | const root: EventSpan = {
98 | event: {
99 | name: "root",
100 | cat: "program",
101 | },
102 | start: firstSpanStart,
103 | end: lastSpanEnd,
104 | duration: lastSpanEnd - firstSpanStart,
105 | children: [],
106 | };
107 | const stack = [root];
108 |
109 | for (const span of spans) {
110 | let i = stack.length - 1;
111 | for (; i > 0; i--) {
112 | // No need to check root at stack[0]
113 | const curr = stack[i];
114 | if (curr.end > span.start) {
115 | // Pop down to parent
116 | stack.length = i + 1;
117 | break;
118 | }
119 | }
120 |
121 | /** Microseconds */
122 | const thresholdDuration: Microseconds = options.forceMillis * 1000;
123 | const isAboveThresholdDuration = span.duration >= thresholdDuration;
124 |
125 | const parent = stack[i];
126 | const parentDuration = parent.end - parent.start;
127 | const isSignificantPortionOfParent =
128 | span.duration >= parentDuration * options.minSpanParentPercentage;
129 |
130 | if (isAboveThresholdDuration || isSignificantPortionOfParent) {
131 | parent.children.push(span);
132 | stack.push(span);
133 | }
134 | }
135 |
136 | return root;
137 | }
138 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/components/auth-gate.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Button, Container, TextField, Typography } from "@mui/material";
2 | import { invoke } from "@tauri-apps/api/core";
3 | import { type FC, useCallback, useEffect, useState } from "react";
4 | import typeslayerLogo from "../assets/typeslayer.png";
5 | import { useToast } from "../contexts/toast-context";
6 |
7 | interface AuthGateProps {
8 | onAuthorized: () => void;
9 | }
10 |
11 | export const AuthGate: FC = ({ onAuthorized }) => {
12 | const [code, setCode] = useState("");
13 | const [checking, setChecking] = useState(true);
14 | const { showToast } = useToast();
15 |
16 | // On mount, check if already authorized via env/flag/config
17 | useEffect(() => {
18 | (async () => {
19 | try {
20 | const authorized = await invoke("is_authorized");
21 | if (authorized) {
22 | onAuthorized();
23 | return;
24 | }
25 | } catch (err) {
26 | console.error("[AuthGate] Auth check failed:", err);
27 | } finally {
28 | setChecking(false);
29 | }
30 | })();
31 | }, [onAuthorized]);
32 |
33 | const handleSubmit = useCallback(async () => {
34 | try {
35 | const valid = await invoke("validate_auth_code", { code });
36 | if (valid) {
37 | onAuthorized();
38 | } else {
39 | showToast({
40 | message: "Invalid code. Please try again.",
41 | severity: "error",
42 | });
43 | }
44 | } catch (err) {
45 | showToast({
46 | message: "Validation failed. Please try again.",
47 | severity: "error",
48 | });
49 | console.error(err);
50 | }
51 | }, [code, onAuthorized, showToast]);
52 |
53 | if (checking) {
54 | return (
55 |
64 |
65 | Checking authorization...
66 |
67 |
68 | );
69 | }
70 |
71 | return (
72 |
81 |
82 |
93 |
98 |
99 | TypeSlayer isn't quite ready for prime time yet.
100 |
101 | setCode(e.target.value)}
107 | onKeyDown={e => {
108 | if (e.key === "Enter") {
109 | handleSubmit();
110 | }
111 | }}
112 | autoFocus
113 | inputProps={{ maxLength: 8 }}
114 | />
115 |
121 | Unlock
122 |
123 |
124 |
125 |
126 | );
127 | };
128 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/pages/award-winners/award-winners.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Divider, List, Stack } from "@mui/material";
2 | import {
3 | BundleImplicationsAward,
4 | BundleImplicationsNavItems,
5 | } from "./bundle-implications";
6 | import {
7 | PerformanceMetricsAward,
8 | PerformanceMetricsNavItems,
9 | } from "./performance-metrics";
10 | import {
11 | TypeLevelLimitAward,
12 | TypeLevelLimitsNavItems,
13 | } from "./type-level-limits";
14 | import { TypeMetricsAward, TypeMetricsNavItems } from "./type-metrics";
15 | import {
16 | TypeRelationMetricsAward,
17 | TypeRelationMetricsNavItems,
18 | } from "./type-relation-metrics";
19 | import { useAwardId } from "./use-award-id";
20 |
21 | export const RenderPlayground = () => {
22 | const { activeAward } = useAwardId();
23 |
24 | switch (activeAward) {
25 | case "type_typeArguments":
26 | case "type_unionTypes":
27 | case "type_intersectionTypes":
28 | case "type_aliasTypeArguments":
29 | return ;
30 |
31 | case "relation_union":
32 | case "relation_intersection":
33 | case "relation_typeArgument":
34 | case "relation_instantiated":
35 | case "relation_aliasTypeArgument":
36 | case "relation_conditionalCheck":
37 | case "relation_conditionalExtends":
38 | case "relation_conditionalFalse":
39 | case "relation_conditionalTrue":
40 | case "relation_indexedAccessObject":
41 | case "relation_indexedAccessIndex":
42 | case "relation_keyof":
43 | case "relation_reverseMappedSource":
44 | case "relation_reverseMappedMapped":
45 | case "relation_reverseMappedConstraint":
46 | case "relation_substitutionBase":
47 | case "relation_constraint":
48 | case "relation_evolvingArrayElement":
49 | case "relation_evolvingArrayFinal":
50 | case "relation_alias":
51 | return (
52 |
53 | );
54 |
55 | case "perf_hotSpots":
56 | return (
57 |
58 | );
59 |
60 | case "limit_instantiateType":
61 | case "limit_recursiveTypeRelatedTo":
62 | case "limit_typeRelatedToDiscriminatedType":
63 | case "limit_checkCrossProductUnion":
64 | case "limit_getTypeAtFlowNode":
65 | case "limit_checkTypeRelatedTo":
66 | case "limit_removeSubtypes":
67 | case "limit_traceUnionsOrIntersectionsTooLarge":
68 | return ;
69 |
70 | case "bundle_duplicatePackages":
71 | return (
72 |
73 | );
74 |
75 | default:
76 | activeAward satisfies never;
77 | throw new Error(`Unknown award: ${activeAward}`);
78 | }
79 | };
80 |
81 | export const AwardWinners = () => {
82 | return (
83 |
94 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
127 |
128 |
129 |
130 | );
131 | };
132 |
--------------------------------------------------------------------------------
/packages/typeslayer/public/perfetto-ui/v53.0-867ef5020/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "resources": {
3 | "assets/._MaterialSymbolsOutlined.woff2": "sha256-60gItEMJBO8lLsagQjUUQb22tQe2uCU7YK5qYTnOTyk=",
4 | "assets/MaterialSymbolsOutlined.woff2": "sha256-+wJwsRRT+mS8qIFxOCzr9+SL0N+L6U0lLJSLl1Bfbm8=",
5 | "assets/Roboto-100.woff2": "sha256-IkglhK6qex103gcnkyRsZeOLQCrCMfOLsNkQKAJUMjA=",
6 | "assets/Roboto-300.woff2": "sha256-M1MLAHBxKBqX55uqsT3ffMS53pQuvT4hIiSFczX3y5c=",
7 | "assets/Roboto-400.woff2": "sha256-zEYyLVxNQdpEfyb3+nFIJ/LsmhEpaMEu9XNsdJSYXso=",
8 | "assets/Roboto-500.woff2": "sha256-u0btB5w908Oa9QUbStpI8p9JFR2tT6IYEXutL9teYW8=",
9 | "assets/RobotoCondensed-Light.woff2": "sha256-rELob/HQ/Hinhwpyz10bvwpQmoUtuh2Kvcc0iSsNSEQ=",
10 | "assets/RobotoCondensed-Regular.woff2": "sha256-SaG04SlmRaovUTyHoOX+VqMFp+1njC9kmWMewfOzWFY=",
11 | "assets/RobotoMono-Regular.woff2": "sha256-5DK7glyj4CZ9Yo+ttqjKY7DMo/xzRfFcfwgPeouCFl4=",
12 | "assets/brand.png": "sha256-qQtoKOldUDc8Nzh2Ub8e3dnoXMGRUOxrdSCL42YmNbk=",
13 | "assets/catapult_trace_viewer.html": "sha256-wLrVZQID01LZXrQygBUzpUlJcvHKCcoetygA1jrOjj8=",
14 | "assets/catapult_trace_viewer.js": "sha256-tpvMkJYBPHRuDjmhKIiiuCVJzjgWa4LcIRxqsb3axf0=",
15 | "assets/favicon.png": "sha256-sor3K6wnzaql75B5y8ZxCEDN2YFe17tmqE+yvWYi2+E=",
16 | "assets/logo-128.png": "sha256-XitC0usT0U7vEJBZwae257GKyY75m56/26Fii2mnnKo=",
17 | "assets/logo-3d.png": "sha256-WI2BmF9A64gFzZSraFJXfOXEqJP3PIhaz9OasMFKz3Q=",
18 | "assets/rec_atrace.png": "sha256-vT3mgQCULhTTtItfHBzlUDpCNvRsJmVGYx8uywilK1g=",
19 | "assets/rec_battery_counters.png": "sha256-ijAy8hVxldcrRGNA/fe2wmahyewH6O3il3fwgpbDTh0=",
20 | "assets/rec_board_voltage.png": "sha256-8BIn67H/6oUbv529ZCNAOf1AMCiEzARJYFdPqNSl8y0=",
21 | "assets/rec_cpu_coarse.png": "sha256-uHhKocq2CeYpWaV+SmSVDXLgbXnEi79+V5jGDvRNNz0=",
22 | "assets/rec_cpu_fine.png": "sha256-Pzs2n6ZOwD2Ug1DX1YaaoyJ0iy7mnXmwxZwI7TB+ut0=",
23 | "assets/rec_cpu_freq.png": "sha256-X9pgx6F2f+Rk1ONZh8pE538vCBk8dUhHp0NrR2MKar4=",
24 | "assets/rec_cpu_voltage.png": "sha256-ZSTitbm3yGRGLupYhjKOQh0ibOfFplGKZNDh3A3qSgc=",
25 | "assets/rec_frame_timeline.png": "sha256-+KGhOsbxx04f2Hsyyq+lxhj+YAIkhKK173EtOfKvrok=",
26 | "assets/rec_ftrace.png": "sha256-VThuh/OGltpvuDgUyOEfEOpbsPumW2MCvTVF7S5Q6Hw=",
27 | "assets/rec_gpu_mem_total.png": "sha256-4iEInY0xQQgdI1r2nng7geZWTeoBqNWmRmNYZQBlei4=",
28 | "assets/rec_java_heap_dump.png": "sha256-Stu8IkRjep9gI/7gGtB/g0hdZBNYOq6psmz7csFzv6Y=",
29 | "assets/rec_lmk.png": "sha256-KMgNuukjCxcUogqUFqCWPPxhZ3vDdFr8U9GSUdN6Clc=",
30 | "assets/rec_logcat.png": "sha256-7zPKP0FpayanH7e9LpKrO5eKm8NeZv71txSFjSvI66I=",
31 | "assets/rec_long_trace.png": "sha256-8ZNsE+AOhS6LiRLEkdqwOJuUQC3L/ErExByhJGIZvew=",
32 | "assets/rec_mem_hifreq.png": "sha256-QxhmLA10n1HX5WKFjS8rhP/OcI3zWhqfaf1ZDgy6eUc=",
33 | "assets/rec_meminfo.png": "sha256-k71vAECd0Hle8TJqmdhe93R86d9WGe3ADKeFekwXQ7A=",
34 | "assets/rec_native_heap_profiler.png": "sha256-ewas12596LRXr/rXNKwqr41oSdMMtbCLHwN0TUIN+Gc=",
35 | "assets/rec_one_shot.png": "sha256-g8YXF1vZWhowKZsSPx8c498Hq8Lx0wVXTL0u5Rf7+c0=",
36 | "assets/rec_profiling.png": "sha256-hM8jBeyyqvrpbFEGqnewcj50xT5GFsBF5qBLmS/UHIk=",
37 | "assets/rec_ps_stats.png": "sha256-Ii+3QkS3iMNiaEKJIiG8YFqHvNi0lMn8qNMyeCfaPh0=",
38 | "assets/rec_ring_buf.png": "sha256-/1+5esQP9EZmTLCSuoCwIcU9l1wrzQSLNQ9wzIGdbKc=",
39 | "assets/rec_syscalls.png": "sha256-JtxPGmHopytXrEgwJbKIaqiHMMum6M5aJgmggybCcIU=",
40 | "assets/rec_vmstat.png": "sha256-lCtlnOQNTNfGrt+U5iSnfyfQx7Hd+z7s3MKMgGv9s5o=",
41 | "assets/rec_wifi.png": "sha256-/qAHbRlch8q/mJM7JEjHE4vD2WtjKxTzYkpJ7NVkATY=",
42 | "assets/scheduling_latency.png": "sha256-o08K+ewOuGio7sBa83hTtd1/rJZU7M/nYvF927TLyWI=",
43 | "engine_bundle.js": "sha256-JDA+1kwiIXCgJyW26fZRycBYpx1FW4j9Ai8n8qghcKg=",
44 | "frontend_bundle.js": "sha256-o0PVgBch6qB635GiUC/RmzeMcL0H1gmSauOkEnCUFTM=",
45 | "perfetto.css": "sha256-nFXiInGsZHDV5yo0tfyzFen4cE4gkqOPWCXNeQGDVOA=",
46 | "stdlib_docs.json": "sha256-QStH+FNooX3tZ9gHh0I8JzROTpoRot6ZXzHjlHBRBE4=",
47 | "trace_config_utils.wasm": "sha256-75eaXHTAE12bpUqnMwvSzdbebQu3yU/9nS3Z9+Vi0yE=",
48 | "trace_processor.wasm": "sha256-R9ICay/kkgQ7iEb061ob1ADTOsT6ElT56ppNG7ad8yY=",
49 | "trace_processor_memory64.wasm": "sha256-vGEEdhF5mxYLnerDkQb+e1rKvg6ve3oxOCQ5k+1W9aU=",
50 | "traceconv.wasm": "sha256-Jb/TPyC4inO5uiq35nQfmVsk18wTiGHIaEwesZIxP5o=",
51 | "traceconv_bundle.js": "sha256-bguDsemRfiSPZVWVpSQXHVZTmYDt4gTqTaGOlBYHHC0="
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/src/auth.rs:
--------------------------------------------------------------------------------
1 | use base64::{Engine as _, engine::general_purpose};
2 | use std::sync::{Arc, Mutex};
3 | use tauri::State;
4 |
5 | use crate::app_data::AppData;
6 |
7 | const ENTROPY: &str = r#"Hi, my name is, what? My name is, who?
8 | My name is, chka-chka, Slim Shady
9 | Hi, my name is, huh? My name is, what?
10 | My name is, chka-chka, Slim Shady
11 | Hi, my name is, what? (Excuse me) My name is, who?
12 | My name is, chka-chka, Slim Shady
13 | (Can I have the attention of the class for one second?)
14 | Hi, my name is, huh? My name is, what?
15 | My name is, chka-chka, Slim Shady
16 |
17 | Hi, kids, do you like violence? (Yeah, yeah, yeah)
18 | Wanna see me stick nine-inch nails through each one of my eyelids? (Uh-huh)
19 | Wanna copy me and do exactly like I did? (Yeah, yeah)
20 | Try 'cid and get fucked up worse than my life is? (Huh?)
21 | My brain's dead weight, I'm tryna get my head straight
22 | But I can't figure out which Spice Girl I want to impregnate (Oh)
23 | And Dr. Dre said, "Slim Shady, you a basehead" (Uh-uh)
24 | "Then why's your face red? Man, you wasted"
25 | Well, since age 12, I felt like I'm someone else
26 | 'Cause I hung my original self from the top bunk with a belt
27 | Got pissed off and ripped Pamela Lee's tits off
28 | And smacked her so hard I knocked her clothes backwards like Kris Kross
29 | I smoke a fat pound of grass, and fall on my ass
30 | Faster than a fat bitch who sat down too fast
31 | Come here, slut; "Shady, wait a minute, that's my girl, dawg"
32 | I don't give a fuck, God sent me to piss the world off
33 |
34 | My English teacher wanted to flunk me in junior high (Shh)
35 | Thanks a lot, next semester I'll be 35
36 | I smacked him in his face with an eraser, chased him with a stapler
37 | And stapled his nuts to a stack of paper (Ow)
38 | Walked in the strip club, had my jacket zipped up
39 | Flashed the bartender, then stuck my dick in the tip cup
40 | Extraterrestrial, running over pedestrians in a spaceship While they're screaming at me, "Let's just be friends"
41 | 99 percent of my life, I was lied to
42 | I just found out my mom does more dope than I do (Damn)
43 | I told her I'd grow up to be a famous rapper
44 | Make a record about doin' drugs and name it after her (Oh, thank you)
45 | You know you blew up when the women rush your stands
46 | And try to touch your hands like some screamin' Usher fans (Ahh, ahh, ahh)
47 | This guy at White Castle asked for my autograph (Dude, can I get your autograph?)
48 | So I signed it, "Dear Dave, thanks for the support, asshole"
49 |
50 | Stop the tape, this kid needs to be locked away (Get him)
51 | Dr. Dre, don't just stand there, operate
52 | I'm not ready to leave, it's too scary to die (Fuck that)
53 | I'll have to be carried inside the cemetery and buried alive
54 | (Huh, yup)
55 | Am I comin' or goin'? I can barely decide
56 | I just drank a fifth of vodka, dare me to drive? (Go ahead)
57 | All my life I was very deprived
58 | I ain't had a woman in years and my palms are too hairy to hide (Whoops)
59 | Clothes ripped like the Incredible Hulk
60 | I spit when I talk, I'll fuck anything that walks (Come here)
61 | When I was little, I used to get so hungry I would throw fits
62 | How you gonna breastfeed me, Mom? You ain't got no tits
63 | I lay awake and strap myself in the bed
64 | With a bulletproof vest on and shoot myself in the head (Bang)
65 | 'Cause I'm steamin' mad (Grr)
66 | And by the way, when you see my dad (Yeah?)
67 | Tell him that I slit his throat in this dream I had"#;
68 |
69 | pub fn is_valid_key(key: &str) -> bool {
70 | if key.len() != 8 {
71 | return false;
72 | }
73 | let encoded_entropy = general_purpose::STANDARD.encode(ENTROPY);
74 | let valid = encoded_entropy.contains(key);
75 | valid
76 | }
77 |
78 | #[tauri::command]
79 | pub async fn validate_auth_code(
80 | state: State<'_, Arc>>,
81 | code: String,
82 | ) -> Result {
83 | let mut data = state.lock().map_err(|e| e.to_string())?;
84 | let valid = is_valid_key(&code);
85 | if valid {
86 | data.auth_code = Some(code.clone());
87 | }
88 | Ok(valid)
89 | }
90 |
91 | #[tauri::command]
92 | pub async fn is_authorized(state: State<'_, Arc>>) -> Result {
93 | let data = state.lock().map_err(|e| e.to_string())?;
94 | if let Some(ref code) = data.auth_code {
95 | let valid = is_valid_key(code);
96 | Ok(valid)
97 | } else {
98 | Ok(false)
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/src/mcp/tools/get_depth_limits.rs:
--------------------------------------------------------------------------------
1 | use crate::{
2 | analyze_trace::DepthLimitKind,
3 | app_data::AppData,
4 | mcp::tools::{ToolDefinition, ToolParameter},
5 | };
6 | use serde::{Deserialize, Serialize};
7 | use std::sync::{Arc, Mutex};
8 | use tracing::info;
9 |
10 | pub const COMMAND: &str = "get_depth_limits";
11 | pub const DESCRIPTION: &str = "Returns depth limit events grouped by category (e.g., instantiateType_DepthLimit, checkTypeRelatedTo_DepthLimit). These are TypeScript compiler internal limits hit during type checking.";
12 |
13 | #[derive(Debug, Clone, Serialize, Deserialize)]
14 | #[serde(rename_all = "camelCase")]
15 | pub struct DepthLimitCategory {
16 | pub category: DepthLimitKind,
17 | pub count: usize,
18 | pub events: Vec,
19 | }
20 |
21 | #[derive(Debug, Clone, Serialize, Deserialize)]
22 | #[serde(rename_all = "camelCase")]
23 | pub struct DepthLimitEvent {
24 | pub timestamp: f64,
25 | pub args: serde_json::Value,
26 | }
27 |
28 | #[derive(Debug, Serialize, Deserialize)]
29 | pub struct GetDepthLimitsResponse {
30 | pub categories: Vec,
31 | pub total_events: usize,
32 | }
33 |
34 | /// Return type example for get_depth_limits tool
35 | #[derive(Debug, Clone, Serialize, Deserialize)]
36 | #[serde(rename_all = "camelCase")]
37 | pub struct GetDepthLimitsExample {
38 | pub categories: Vec,
39 | pub total_events: u32,
40 | }
41 |
42 | pub fn tool_definition() -> ToolDefinition {
43 | ToolDefinition {
44 | command: COMMAND.to_string(),
45 | display_name: "Get Depth Limits".to_string(),
46 | description: DESCRIPTION.to_string(),
47 | parameters: vec![ToolParameter {
48 | name: "limit".to_string(),
49 | optional: true,
50 | default: Some(serde_json::json!(10)),
51 | description: "Maximum number of events per category to return".to_string(),
52 | }],
53 | returns: GetDepthLimitsExample {
54 | categories: vec![DepthLimitCategory {
55 | category: DepthLimitKind::InstantiateType,
56 | count: 3,
57 | events: vec![DepthLimitEvent {
58 | timestamp: 123456.789,
59 | args: serde_json::json!({"instantiationDepth": 50}),
60 | }],
61 | }],
62 | total_events: 3,
63 | },
64 | }
65 | }
66 |
67 | pub async fn execute(app_data: Arc>) -> String {
68 | info!("get_depth_limits called");
69 |
70 | // Lock app_data to access analyze_trace
71 | let data = match app_data.lock() {
72 | Ok(d) => d,
73 | Err(e) => return format!("{{\"error\": \"Failed to lock app data: {}\"}}", e),
74 | };
75 |
76 | // Check if analyze-trace data is available
77 | let analyze_trace = match data.analyze_trace.as_ref() {
78 | Some(at) => at,
79 | None => {
80 | return r#"{"error": "No analyze-trace data available. Please run trace analysis first."}"#
81 | .to_string()
82 | }
83 | };
84 |
85 | let limit = 10usize; // stub parameter, matches tool definition default
86 |
87 | let mut total_events = 0;
88 | let mut categories: Vec = analyze_trace
89 | .depth_limits
90 | .iter()
91 | .map(|(category, events)| {
92 | total_events += events.len();
93 | let limited_events: Vec = events
94 | .iter()
95 | .take(limit)
96 | .map(|ev| DepthLimitEvent {
97 | timestamp: ev.ts,
98 | args: ev.args.clone(),
99 | })
100 | .collect();
101 |
102 | DepthLimitCategory {
103 | category: category.clone(),
104 | count: events.len(),
105 | events: limited_events,
106 | }
107 | })
108 | .collect();
109 |
110 | // Sort categories by event count (desc) for relevance
111 | categories.sort_by(|a, b| b.count.cmp(&a.count));
112 |
113 | let response = GetDepthLimitsResponse {
114 | categories,
115 | total_events,
116 | };
117 |
118 | match serde_json::to_string_pretty(&response) {
119 | Ok(json) => json,
120 | Err(e) => format!("{{\"error\": \"Failed to serialize response: {}\"}}", e),
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/pages/start/error-dialog.tsx:
--------------------------------------------------------------------------------
1 | import { ContentCopy } from "@mui/icons-material";
2 | import {
3 | Box,
4 | Button,
5 | Dialog,
6 | DialogActions,
7 | DialogContent,
8 | DialogTitle,
9 | IconButton,
10 | Stack,
11 | Typography,
12 | } from "@mui/material";
13 | import { useCallback } from "react";
14 | import { BugReport } from "../../components/bug-report";
15 | import { stripAnsi } from "../../components/utils";
16 | import { ErrorHelper } from "./error-helper";
17 |
18 | const ErrorStreamSection = ({
19 | title,
20 | content,
21 | }: {
22 | title: string;
23 | content: string;
24 | }) => {
25 | const handleCopy = useCallback(() => {
26 | navigator.clipboard.writeText(content).catch(err => {
27 | console.error("Failed to copy to clipboard:", err);
28 | });
29 | }, [content]);
30 |
31 | return (
32 | `1px solid ${theme.palette.divider}`,
37 | borderRadius: 1,
38 | display: "flex",
39 | flexDirection: "column",
40 | overflow: "hidden",
41 | }}
42 | >
43 | theme.palette.background.paper,
48 | borderBottom: theme => `1px solid ${theme.palette.divider}`,
49 | fontSize: 12,
50 | fontWeight: 600,
51 | textTransform: "uppercase",
52 | justifyContent: "space-between",
53 | display: "flex",
54 | alignItems: "center",
55 | letterSpacing: 0.75,
56 | }}
57 | >
58 | {title}
59 |
65 |
66 |
67 |
68 | theme.palette.background.default,
80 | }}
81 | >
82 | {stripAnsi(content)}
83 |
84 |
85 | );
86 | };
87 |
88 | export const ErrorDialog = ({
89 | open,
90 | onClose,
91 | processingErrorStdout,
92 | processingErrorStderr,
93 | processingError,
94 | }: {
95 | open: boolean;
96 | onClose: () => void;
97 | processingErrorStdout: string | null;
98 | processingErrorStderr: string | null;
99 | processingError: string | null;
100 | }) => {
101 | const errorDialogTitle = processingError ?? "Diagnostics failed";
102 |
103 | if (!processingErrorStderr && !processingErrorStdout) {
104 | return (
105 |
106 | Internal Error
107 |
108 |
112 |
113 |
114 | );
115 | }
116 |
117 | return (
118 |
119 | Error
120 |
121 |
122 | {"TypeSlayer System Error"}
123 |
128 | {processingErrorStdout ? (
129 |
133 | ) : null}
134 | {processingErrorStderr ? (
135 |
139 | ) : null}
140 |
141 |
142 |
143 |
149 | Close
150 |
151 |
152 | );
153 | };
154 |
--------------------------------------------------------------------------------
/packages/typeslayer/src-tauri/src/analyze_trace/depth_limits.rs:
--------------------------------------------------------------------------------
1 | use crate::validate::trace_json::TraceEvent;
2 | use std::collections::HashMap;
3 | use strum::IntoEnumIterator;
4 | use strum_macros::EnumIter;
5 |
6 | #[derive(
7 | Eq, Hash, PartialEq, Clone, Copy, Debug, serde::Deserialize, serde::Serialize, EnumIter,
8 | )]
9 | pub enum DepthLimitKind {
10 | #[serde(rename = "checkCrossProductUnion_DepthLimit")]
11 | CheckCrossProductUnion,
12 | #[serde(rename = "checkTypeRelatedTo_DepthLimit")]
13 | CheckTypeRelatedTo,
14 | #[serde(rename = "getTypeAtFlowNode_DepthLimit")]
15 | GetTypeAtFlowNode,
16 | #[serde(rename = "instantiateType_DepthLimit")]
17 | InstantiateType,
18 | #[serde(rename = "recursiveTypeRelatedTo_DepthLimit")]
19 | RecursiveTypeRelatedTo,
20 | #[serde(rename = "removeSubtypes_DepthLimit")]
21 | RemoveSubtypes,
22 | #[serde(rename = "traceUnionsOrIntersectionsTooLarge_DepthLimit")]
23 | TraceUnionsOrIntersectionsTooLarge,
24 | #[serde(rename = "typeRelatedToDiscriminatedType_DepthLimit")]
25 | TypeRelatedToDiscriminatedType,
26 | }
27 |
28 | pub fn create_depth_limits(
29 | trace_file: &Vec,
30 | ) -> HashMap> {
31 | let mut depth_limits: HashMap> = HashMap::new();
32 |
33 | fn kind_from_event_name(name: &str) -> Option {
34 | for kind in DepthLimitKind::iter() {
35 | if let Ok(serialized) = serde_plain::to_string(&kind) {
36 | if serialized == name {
37 | return Some(kind);
38 | }
39 | }
40 | }
41 | None
42 | }
43 |
44 | for kind in DepthLimitKind::iter() {
45 | depth_limits.insert(kind, Vec::new());
46 | }
47 |
48 | for ev in trace_file.iter() {
49 | if let Some(kind) = kind_from_event_name(ev.name.as_str()) {
50 | if let Some(vec) = depth_limits.get_mut(&kind) {
51 | vec.push(ev.clone());
52 | }
53 | }
54 | }
55 |
56 | // Helpers to extract numeric args
57 | fn num_arg(ev: &TraceEvent, key: &str) -> f64 {
58 | match ev.args.get(key) {
59 | Some(serde_json::Value::Number(n)) => n.as_f64().unwrap_or(0.0),
60 | _ => 0.0,
61 | }
62 | }
63 |
64 | // Sort per bucket based on noted criteria
65 | if let Some(v) = depth_limits.get_mut(&DepthLimitKind::CheckCrossProductUnion) {
66 | v.sort_by(|a, b| {
67 | num_arg(b, "size")
68 | .partial_cmp(&num_arg(a, "size"))
69 | .unwrap_or(std::cmp::Ordering::Equal)
70 | });
71 | }
72 | if let Some(v) = depth_limits.get_mut(&DepthLimitKind::CheckTypeRelatedTo) {
73 | v.sort_by(|a, b| {
74 | num_arg(b, "depth")
75 | .partial_cmp(&num_arg(a, "depth"))
76 | .unwrap_or(std::cmp::Ordering::Equal)
77 | });
78 | }
79 | if let Some(v) = depth_limits.get_mut(&DepthLimitKind::GetTypeAtFlowNode) {
80 | v.sort_by(|a, b| {
81 | num_arg(b, "flowId")
82 | .partial_cmp(&num_arg(a, "flowId"))
83 | .unwrap_or(std::cmp::Ordering::Equal)
84 | });
85 | }
86 | if let Some(v) = depth_limits.get_mut(&DepthLimitKind::InstantiateType) {
87 | v.sort_by(|a, b| {
88 | num_arg(b, "instantiationDepth")
89 | .partial_cmp(&num_arg(a, "instantiationDepth"))
90 | .unwrap_or(std::cmp::Ordering::Equal)
91 | });
92 | }
93 | if let Some(v) = depth_limits.get_mut(&DepthLimitKind::RecursiveTypeRelatedTo) {
94 | v.sort_by(|a, b| {
95 | num_arg(b, "depth")
96 | .partial_cmp(&num_arg(a, "depth"))
97 | .unwrap_or(std::cmp::Ordering::Equal)
98 | });
99 | }
100 |
101 | // RemoveSubtypes: not sorted
102 |
103 | if let Some(v) = depth_limits.get_mut(&DepthLimitKind::TraceUnionsOrIntersectionsTooLarge) {
104 | v.sort_by(|a, b| {
105 | let a_prod = num_arg(a, "sourceSize") * num_arg(a, "targetSize");
106 | let b_prod = num_arg(b, "sourceSize") * num_arg(b, "targetSize");
107 | b_prod
108 | .partial_cmp(&a_prod)
109 | .unwrap_or(std::cmp::Ordering::Equal)
110 | });
111 | }
112 | if let Some(v) = depth_limits.get_mut(&DepthLimitKind::TypeRelatedToDiscriminatedType) {
113 | v.sort_by(|a, b| {
114 | num_arg(b, "numCombinations")
115 | .partial_cmp(&num_arg(a, "numCombinations"))
116 | .unwrap_or(std::cmp::Ordering::Equal)
117 | });
118 | }
119 |
120 | depth_limits
121 | }
122 |
--------------------------------------------------------------------------------
/packages/typeslayer/public/speedscope-ui/source-code-pro.LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright 2010-2019 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries.
2 |
3 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
4 |
5 | This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
6 |
7 |
8 | -----------------------------------------------------------
9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10 | -----------------------------------------------------------
11 |
12 | PREAMBLE
13 | The goals of the Open Font License (OFL) are to stimulate worldwide
14 | development of collaborative font projects, to support the font creation
15 | efforts of academic and linguistic communities, and to provide a free and
16 | open framework in which fonts may be shared and improved in partnership
17 | with others.
18 |
19 | The OFL allows the licensed fonts to be used, studied, modified and
20 | redistributed freely as long as they are not sold by themselves. The
21 | fonts, including any derivative works, can be bundled, embedded,
22 | redistributed and/or sold with any software provided that any reserved
23 | names are not used by derivative works. The fonts and derivatives,
24 | however, cannot be released under any other type of license. The
25 | requirement for fonts to remain under this license does not apply
26 | to any document created using the fonts or their derivatives.
27 |
28 | DEFINITIONS
29 | "Font Software" refers to the set of files released by the Copyright
30 | Holder(s) under this license and clearly marked as such. This may
31 | include source files, build scripts and documentation.
32 |
33 | "Reserved Font Name" refers to any names specified as such after the
34 | copyright statement(s).
35 |
36 | "Original Version" refers to the collection of Font Software components as
37 | distributed by the Copyright Holder(s).
38 |
39 | "Modified Version" refers to any derivative made by adding to, deleting,
40 | or substituting -- in part or in whole -- any of the components of the
41 | Original Version, by changing formats or by porting the Font Software to a
42 | new environment.
43 |
44 | "Author" refers to any designer, engineer, programmer, technical
45 | writer or other person who contributed to the Font Software.
46 |
47 | PERMISSION & CONDITIONS
48 | Permission is hereby granted, free of charge, to any person obtaining
49 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
50 | redistribute, and sell modified and unmodified copies of the Font
51 | Software, subject to the following conditions:
52 |
53 | 1) Neither the Font Software nor any of its individual components,
54 | in Original or Modified Versions, may be sold by itself.
55 |
56 | 2) Original or Modified Versions of the Font Software may be bundled,
57 | redistributed and/or sold with any software, provided that each copy
58 | contains the above copyright notice and this license. These can be
59 | included either as stand-alone text files, human-readable headers or
60 | in the appropriate machine-readable metadata fields within text or
61 | binary files as long as those fields can be easily viewed by the user.
62 |
63 | 3) No Modified Version of the Font Software may use the Reserved Font
64 | Name(s) unless explicit written permission is granted by the corresponding
65 | Copyright Holder. This restriction only applies to the primary font name as
66 | presented to the users.
67 |
68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69 | Software shall not be used to promote, endorse or advertise any
70 | Modified Version, except to acknowledge the contribution(s) of the
71 | Copyright Holder(s) and the Author(s) or with their explicit written
72 | permission.
73 |
74 | 5) The Font Software, modified or unmodified, in part or in whole,
75 | must be distributed entirely under this license, and must not be
76 | distributed under any other license. The requirement for fonts to
77 | remain under this license does not apply to any document created
78 | using the Font Software.
79 |
80 | TERMINATION
81 | This license becomes null and void if any of the above conditions are
82 | not met.
83 |
84 | DISCLAIMER
85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93 | OTHER DEALINGS IN THE FONT SOFTWARE.
94 |
--------------------------------------------------------------------------------
/packages/typeslayer/src/pages/award-winners/performance-metrics.tsx:
--------------------------------------------------------------------------------
1 | import { FindInPage } from "@mui/icons-material";
2 | import {
3 | IconButton,
4 | List,
5 | ListItem,
6 | ListItemText,
7 | ListSubheader,
8 | Stack,
9 | Typography,
10 | } from "@mui/material";
11 | import { openPath } from "@tauri-apps/plugin-opener";
12 | import { useCallback } from "react";
13 | import { CenterLoader } from "../../components/center-loader";
14 | import { NoData } from "../../components/no-data";
15 | import {
16 | useAnalyzeTrace,
17 | useProjectRoot,
18 | useRelativePaths,
19 | } from "../../hooks/tauri-hooks";
20 | import { AwardNavItem } from "./award-nav-item";
21 | import { type AwardId, awards, MaybePathCaption } from "./awards";
22 | import { InlineBarGraph } from "./inline-bar-graph";
23 | import { TitleSubtitle } from "./title-subtitle";
24 |
25 | const ShowHotSpots = () => {
26 | const relativePaths = useRelativePaths();
27 | const projectRoot = useProjectRoot();
28 | const { data: analyzeTrace, isLoading } = useAnalyzeTrace();
29 |
30 | const findInPage = useCallback(async (path: string | undefined) => {
31 | if (!path) {
32 | return;
33 | }
34 | try {
35 | await openPath(path);
36 | } catch (e) {
37 | console.error("Failed to open file", e);
38 | }
39 | }, []);
40 |
41 | if (
42 | relativePaths.isLoading ||
43 | projectRoot.isLoading ||
44 | relativePaths.data === undefined ||
45 | projectRoot.data === undefined
46 | ) {
47 | return null;
48 | }
49 |
50 | const hotSpots = analyzeTrace?.hotSpots ?? [];
51 |
52 | const firstHotSpot = hotSpots[0];
53 | const Icon = awards.perf_hotSpots.icon;
54 |
55 | const hasHotSpots = (
56 |
57 | {hotSpots.map(({ path, timeMs }) => {
58 | const relativeTime = timeMs / firstHotSpot.timeMs;
59 | const fileName = path?.split("/").slice(-1)[0] ?? "";
60 | return (
61 |
62 |
63 |
64 | {fileName}
65 | {path ? (
66 | findInPage(path)}
69 | sx={{ ml: 1 }}
70 | >
71 |
72 |
73 | ) : null}
74 |
75 |
76 |
77 |
81 |
82 |
83 |
84 | );
85 | })}
86 |
87 | );
88 |
89 | return (
90 |
91 | }
95 | />
96 | {isLoading ? : firstHotSpot ? hasHotSpots : }
97 |
98 | );
99 | };
100 |
101 | const performanceMetrics = ["perf_hotSpots"] satisfies AwardId[];
102 | type PerformanceMetricsAwardId = (typeof performanceMetrics)[number];
103 |
104 | const usePerformanceMetricsValue = () => {
105 | const { data: analyzeTrace } = useAnalyzeTrace();
106 |
107 | return useCallback(
108 | (awardId: PerformanceMetricsAwardId): number => {
109 | switch (awardId) {
110 | case "perf_hotSpots": {
111 | const hotSpots = analyzeTrace?.hotSpots ?? [];
112 | return hotSpots.length;
113 | }
114 | default:
115 | awardId satisfies never;
116 | throw new Error(`Unknown award: ${awardId}`);
117 | }
118 | },
119 | [analyzeTrace],
120 | );
121 | };
122 |
123 | export const PerformanceMetricsNavItems = () => {
124 | const getValue = usePerformanceMetricsValue();
125 |
126 | return (
127 | <>
128 | Performance Metrics
129 |
130 | {performanceMetrics.map(awardId => (
131 |
136 | ))}
137 | >
138 | );
139 | };
140 |
141 | export const PerformanceMetricsAward = ({
142 | awardId,
143 | }: {
144 | awardId: PerformanceMetricsAwardId;
145 | }) => {
146 | if (awardId !== "perf_hotSpots") {
147 | throw new Error(`Unknown award: ${awardId}`);
148 | }
149 |
150 | return ;
151 | };
152 |
--------------------------------------------------------------------------------