diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6974fc87..7d498f32 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -52,6 +52,12 @@ jobs:
bun install
bun run build
+ - name: Build playground
+ run: |
+ cd monorepo/apps/di-playground
+ bun install
+ bun run build
+
- name: Upload test harness for Pages
uses: actions/upload-artifact@v4
with:
@@ -64,6 +70,12 @@ jobs:
name: docs-dist
path: monorepo/apps/docs-starlight/dist/
+ - name: Upload playground for Pages
+ uses: actions/upload-artifact@v4
+ with:
+ name: playground-dist
+ path: monorepo/apps/di-playground/dist/
+
# Deploy to GitHub Pages
deploy-pages:
name: Deploy to GitHub Pages
@@ -89,6 +101,12 @@ jobs:
name: test-harness-dist
path: ./pages-site/test-harness
+ - name: Download playground build
+ uses: actions/download-artifact@v4
+ with:
+ name: playground-dist
+ path: ./pages-site/playground
+
- name: Create root index page
run: |
cat > ./pages-site/index.html << 'EOF'
@@ -130,7 +148,7 @@ jobs:
}
.links {
display: grid;
- grid-template-columns: 1fr 1fr;
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
margin-top: 2rem;
}
@@ -184,6 +202,10 @@ jobs:
diff --git a/Backlog.md b/Backlog.md
index f6a2f0be..f0e17b01 100644
--- a/Backlog.md
+++ b/Backlog.md
@@ -2,85 +2,26 @@
## ordered log (for production release)
-### console log in di-core and vite-plugin
+### fix interface collision & test case
-### remove magic strings
+two interface of the same name and in different files are resolved inproperly
-see `.includes('` `*.*js*,*.md,*.test.ts`
+`should fail with current implementation - interface name collision`
-Magic strings that will cause plugin failures for users and need to be made configurable:
+### [โ
] playground
-#### ๐ด HIGH PRIORITY - Will break plugin functionality:
+> โ
use vite plugin in playground application similarly to https://typia.io/playground/ or others
-**1. File path filtering (affects all users):**
+- โ
fix all builds
+- โ
fix all tests
+- โ
add playground delpoyment to github pages
+- โ
add build and ALL tests to CI
+ - โ
dicore unit & e2e
+ - โ
cross package
+ - โ
e2e
-- `shouldSkipFile` / `shouldProcessFile` in multiple locations:
- - [functional-di-enhanced-transformer.ts:468-471](monorepo/packages/di-core/tools/functional-di-enhanced-transformer/functional-di-enhanced-transformer.ts#L468-L471) - `'generated'`, `'node_modules'`, `'.d.ts'`, `'.tdi2'`
- - [utils.ts:10-15](monorepo/packages/di-core/tools/functional-di-enhanced-transformer/utils.ts#L10-L15) - `'generated'`, `'node_modules'`, `'.d.ts'`, `'.tdi2'`, `'.test.'`, `'.spec.'`
- - [integrated-interface-resolver.ts:346-349](monorepo/packages/di-core/tools/interface-resolver/integrated-interface-resolver.ts#L346-L349) - Same patterns
- - [enhanced-di-transformer.ts:314-317](monorepo/packages/di-core/tools/enhanced-di-transformer.ts#L314-L317) - Same patterns
- - [plugin-core/config.ts:127,132](monorepo/packages/plugin-core/src/config.ts#L127,132) - `'node_modules'`, `'.tdi2'`, `'/generated/'`
-
- **Issue**: Users with custom output directories or non-standard project structures will have their files incorrectly filtered
- **Solution**: Add `excludePatterns?: string[]` to plugin options
-
-**2. DI pattern detection (affects pattern matching):**
-
-- [pattern-detection.ts:36-82](monorepo/packages/plugin-core/src/pattern-detection.ts#L36-L82):
- - Service patterns: `'@Service'`, `'Inject<'`, `'@Inject'`, `'implements'`, `'Interface'`
- - File naming: `'.service.'`, `'/services/'`, `'\\services\\'`, `'.component.'`, `'/components/'`, `'\\components\\'`
-
- **Issue**: Users with different naming conventions won't be detected
- **Solution**: Make pattern detection configurable via plugin options (already partially done via `advanced.diPatterns`)
-
-**3. Marker detection hardcoded strings:**
-
-- [utils.ts:41,88](monorepo/packages/di-core/tools/functional-di-enhanced-transformer/utils.ts#L41,88) - `'Inject<'`, `'InjectOptional<'`
-- [debug-file-generator.ts:144-170](monorepo/packages/di-core/tools/functional-di-enhanced-transformer/debug-file-generator.ts#L144-L170) - `'useService('`, `'useOptionalService('`, `'import'`, `'const {'`, `'} = props'`, etc.
-
- **Issue**: Users with custom DI markers won't be detected
- **Solution**: Already configurable via `advanced.diPatterns.interfaceMarker`
-
-#### ๐ก MEDIUM PRIORITY - May affect some users:
-
-**4. Vite plugin-specific patterns:**
-
-- [vite-plugin-di/plugin.ts:405,494](monorepo/packages/vite-plugin-di/src/plugin.ts#L405,494) - `'.tdi2'` for HMR filtering
-
- **Issue**: Users with custom outputDir won't get proper HMR
- **Solution**: Use `options.outputDir` instead of hardcoded `.tdi2`
-
-**5. HMR message detection:**
-
-- [vite-plugin-di/e2e/helpers.ts:198,217-218](monorepo/packages/vite-plugin-di/e2e/helpers.ts#L198,217-218) - `'[vite] hmr update'`, `'[vite] hot updated'`, `'[vite] page reload'`, `'full-reload'`
-
- **Issue**: Tests only - false positive
-
-#### ACTION ITEMS:
-
-1. **Add to plugin options**:
-
- ```typescript
- export interface DIPluginOptions {
- // ... existing options
- excludePatterns?: string[]; // Default: ['node_modules', '.d.ts', '.test.', '.spec.']
- excludeDirs?: string[]; // Default: ['node_modules']
- // Note: outputDir already configurable, should be used instead of hardcoded '.tdi2'
- }
- ```
-
-2. **Files to update**:
- - Replace hardcoded `'.tdi2'` checks with `options.outputDir` in vite-plugin HMR code
- - Replace hardcoded exclude patterns with `options.excludePatterns` in all `shouldSkipFile` functions
- - Document that `advanced.diPatterns` is available for custom marker detection
-
-3. **Testing**:
- - Add tests for custom `outputDir` with HMR
- - Add tests for custom `excludePatterns`
-
-### [โ] playground
-
-> use vite plugin in playground application similarly to https://typia.io/playground/ or others
+โ
fix preview
+โ
fix comments in PR
### [โ] write state ownership docs section
@@ -756,6 +697,84 @@ evaluate scenarios
## Done
+### โ
console log in di-core and vite-plugin
+
+improve logging with DEBUG and LOG_LEVEL
+
+### โ
remove magic strings
+
+see `.includes('` `*.*js*,*.md,*.test.ts`
+
+Magic strings that will cause plugin failures for users and need to be made configurable:
+
+#### ๐ด HIGH PRIORITY - Will break plugin functionality:
+
+**1. File path filtering (affects all users):**
+
+- `shouldSkipFile` / `shouldProcessFile` in multiple locations:
+ - [functional-di-enhanced-transformer.ts:468-471](monorepo/packages/di-core/tools/functional-di-enhanced-transformer/functional-di-enhanced-transformer.ts#L468-L471) - `'generated'`, `'node_modules'`, `'.d.ts'`, `'.tdi2'`
+ - [utils.ts:10-15](monorepo/packages/di-core/tools/functional-di-enhanced-transformer/utils.ts#L10-L15) - `'generated'`, `'node_modules'`, `'.d.ts'`, `'.tdi2'`, `'.test.'`, `'.spec.'`
+ - [integrated-interface-resolver.ts:346-349](monorepo/packages/di-core/tools/interface-resolver/integrated-interface-resolver.ts#L346-L349) - Same patterns
+ - [enhanced-di-transformer.ts:314-317](monorepo/packages/di-core/tools/enhanced-di-transformer.ts#L314-L317) - Same patterns
+ - [plugin-core/config.ts:127,132](monorepo/packages/plugin-core/src/config.ts#L127,132) - `'node_modules'`, `'.tdi2'`, `'/generated/'`
+
+ **Issue**: Users with custom output directories or non-standard project structures will have their files incorrectly filtered
+ **Solution**: Add `excludePatterns?: string[]` to plugin options
+
+**2. DI pattern detection (affects pattern matching):**
+
+- [pattern-detection.ts:36-82](monorepo/packages/plugin-core/src/pattern-detection.ts#L36-L82):
+ - Service patterns: `'@Service'`, `'Inject<'`, `'@Inject'`, `'implements'`, `'Interface'`
+ - File naming: `'.service.'`, `'/services/'`, `'\\services\\'`, `'.component.'`, `'/components/'`, `'\\components\\'`
+
+ **Issue**: Users with different naming conventions won't be detected
+ **Solution**: Make pattern detection configurable via plugin options (already partially done via `advanced.diPatterns`)
+
+**3. Marker detection hardcoded strings:**
+
+- [utils.ts:41,88](monorepo/packages/di-core/tools/functional-di-enhanced-transformer/utils.ts#L41,88) - `'Inject<'`, `'InjectOptional<'`
+- [debug-file-generator.ts:144-170](monorepo/packages/di-core/tools/functional-di-enhanced-transformer/debug-file-generator.ts#L144-L170) - `'useService('`, `'useOptionalService('`, `'import'`, `'const {'`, `'} = props'`, etc.
+
+ **Issue**: Users with custom DI markers won't be detected
+ **Solution**: Already configurable via `advanced.diPatterns.interfaceMarker`
+
+#### ๐ก MEDIUM PRIORITY - May affect some users:
+
+**4. Vite plugin-specific patterns:**
+
+- [vite-plugin-di/plugin.ts:405,494](monorepo/packages/vite-plugin-di/src/plugin.ts#L405,494) - `'.tdi2'` for HMR filtering
+
+ **Issue**: Users with custom outputDir won't get proper HMR
+ **Solution**: Use `options.outputDir` instead of hardcoded `.tdi2`
+
+**5. HMR message detection:**
+
+- [vite-plugin-di/e2e/helpers.ts:198,217-218](monorepo/packages/vite-plugin-di/e2e/helpers.ts#L198,217-218) - `'[vite] hmr update'`, `'[vite] hot updated'`, `'[vite] page reload'`, `'full-reload'`
+
+ **Issue**: Tests only - false positive
+
+#### ACTION ITEMS:
+
+1. **Add to plugin options**:
+
+ ```typescript
+ export interface DIPluginOptions {
+ // ... existing options
+ excludePatterns?: string[]; // Default: ['node_modules', '.d.ts', '.test.', '.spec.']
+ excludeDirs?: string[]; // Default: ['node_modules']
+ // Note: outputDir already configurable, should be used instead of hardcoded '.tdi2'
+ }
+ ```
+
+2. **Files to update**:
+ - Replace hardcoded `'.tdi2'` checks with `options.outputDir` in vite-plugin HMR code
+ - Replace hardcoded exclude patterns with `options.excludePatterns` in all `shouldSkipFile` functions
+ - Document that `advanced.diPatterns` is available for custom marker detection
+
+3. **Testing**:
+ - Add tests for custom `outputDir` with HMR
+ - Add tests for custom `excludePatterns`
+
### โ
hot reload fixes added path issue
see `src/generated` to src/.tdi2
diff --git a/monorepo/apps/di-playground/README.md b/monorepo/apps/di-playground/README.md
new file mode 100644
index 00000000..5628a9d0
--- /dev/null
+++ b/monorepo/apps/di-playground/README.md
@@ -0,0 +1,164 @@
+# TDI2 Playground
+
+An interactive, browser-based playground for exploring TDI2 (TypeScript Dependency Injection) code transformations. Similar to TypeScript Playground or Typia Playground, this tool provides live visualization of how TDI2 transforms your React components.
+
+## Features
+
+- **Live Code Transformation**: See real-time transformations as you type
+- **Monaco Editor**: Full-featured code editor with TypeScript support
+- **Split-Pane View**: Input code on the left, transformed output on the right
+- **Example Library**: Pre-built examples demonstrating common patterns
+- **Browser-Based**: No backend required - all transformations run in your browser
+
+## Quick Start
+
+### Development
+
+```bash
+# From monorepo root
+bun run playground:dev
+
+# Or directly
+cd monorepo/apps/di-playground
+bun run dev
+```
+
+Visit http://localhost:5174
+
+### Build
+
+```bash
+# From monorepo root
+bun run playground:build
+
+# Or directly
+cd monorepo/apps/di-playground
+bun run build
+```
+
+## Usage
+
+1. **Select an Example**: Use the dropdown to choose from pre-built examples
+2. **Edit Code**: Modify the input code in the left panel
+3. **View Transformation**: See the transformed output in the right panel (updates automatically after 500ms)
+4. **Reset**: Click "Reset" to restore the original example code
+5. **Transform**: Click "Transform" to manually trigger transformation
+
+## Examples
+
+The playground includes several examples:
+
+- **Basic Counter**: Simple counter component with DI marker
+- **Counter with Service**: Counter using dependency injection
+- **Todo List**: Todo list with service injection
+- **User Profile**: Multi-service component with authentication
+- **Shopping Cart**: Complex e-commerce cart example
+
+## How It Works
+
+### Transformation Process
+
+The playground uses the **actual** `FunctionalDIEnhancedTransformer` from `@tdi2/di-core` running in the browser:
+
+1. **In-Memory File System**: Uses ts-morph's in-memory file system for browser compatibility
+2. **Service Registry**: Pre-loads common service interfaces (Counter, Todo, User, Auth, Cart, Product)
+3. **Real Transformation**: Runs the same transformation pipeline used by the Vite plugin
+4. **DI Marker Detection**: Looks for `// @di-inject` comments
+5. **Interface Resolution**: Automatically resolves service interfaces to implementations
+6. **Code Generation**: Generates actual transformed code with proper imports and DI setup
+
+### Features
+
+The playground runs the full production transformer including:
+
+- โ
Full AST-based code transformation
+- โ
Interface resolution and validation
+- โ
Complex prop destructuring handling
+- โ
Service injection transformation
+- โ
Import management and code generation
+- โ
Error reporting and warnings
+
+Note: Configuration class processing and bean factory generation work but require specific setup.
+
+## Project Structure
+
+```
+di-playground/
+โโโ src/
+โ โโโ App.tsx # Main application component
+โ โโโ main.tsx # Application entry point
+โ โโโ transformer.ts # Browser-compatible transformer
+โ โโโ examples.ts # Example code snippets
+โ โโโ styles.css # Global styles
+โโโ index.html # HTML template
+โโโ package.json # Dependencies
+โโโ vite.config.ts # Vite configuration
+โโโ tsconfig.json # TypeScript configuration
+```
+
+## Technologies
+
+- **React 19**: UI framework
+- **Vite 6**: Build tool and dev server
+- **Monaco Editor**: Code editor (powers VS Code)
+- **ts-morph**: TypeScript AST manipulation
+- **@tdi2/di-core**: Core DI transformation logic
+
+## Development Notes
+
+### Adding New Examples
+
+Edit `src/examples.ts` and add a new entry to the `examples` array:
+
+```typescript
+{
+ name: 'My Example',
+ description: 'Brief description',
+ code: `// Your example code here`
+}
+```
+
+### Enhancing Transformations
+
+The transformer logic is in `src/transformer.ts` and uses the real `FunctionalDIEnhancedTransformer`. To add more features:
+
+1. **Add More Services**: Add service definitions in `createCommonServices()` method
+2. **Configuration Options**: Expose transformer options in the UI (e.g., enable/disable features)
+3. **Multiple Files**: Support multiple file transformations in the virtual filesystem
+4. **Advanced Debugging**: Show intermediate transformation steps or AST visualization
+
+### Styling
+
+The playground uses a dark VS Code-inspired theme. Styles are in `src/styles.css` and follow a component-based naming convention.
+
+## Future Enhancements
+
+- [ ] URL-based code sharing (encode input in URL)
+- [ ] Full production transformer integration
+- [ ] Multiple file support
+- [ ] TypeScript diagnostics display
+- [ ] Transformation step visualization
+- [ ] Export transformed code
+- [ ] Customizable transformation options
+- [ ] Side-by-side diff view
+- [ ] Mobile-responsive layout
+
+## Contributing
+
+To contribute to the playground:
+
+1. Make your changes in `monorepo/apps/di-playground`
+2. Test thoroughly with `bun run dev`
+3. Build to verify: `bun run build`
+4. Follow the project's coding conventions
+5. Submit a PR with clear description
+
+## Related Documentation
+
+- [TDI2 Documentation](https://7frank.github.io/tdi2/)
+- [Getting Started Guide](../../apps/docs-starlight/src/content/docs/getting-started/quick-start.md)
+- [Architecture Patterns](../../apps/docs-starlight/src/content/docs/guides/architecture/controller-service-pattern.md)
+
+## License
+
+MIT - See root LICENSE file
diff --git a/monorepo/apps/di-playground/index.html b/monorepo/apps/di-playground/index.html
new file mode 100644
index 00000000..1cb9ee0c
--- /dev/null
+++ b/monorepo/apps/di-playground/index.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
TDI2 Playground - Interactive Code Transformation
+
+
+
+
+
+
+
diff --git a/monorepo/apps/di-playground/package.json b/monorepo/apps/di-playground/package.json
new file mode 100644
index 00000000..6d63d808
--- /dev/null
+++ b/monorepo/apps/di-playground/package.json
@@ -0,0 +1,45 @@
+{
+ "name": "@tdi2/di-playground",
+ "version": "1.0.0",
+ "description": "TDI2 Interactive Playground - Live code transformation preview",
+ "type": "module",
+ "private": true,
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview",
+ "test": "vitest run",
+ "test:watch": "vitest",
+ "lint": "echo 'No linting configured yet'",
+ "clean": "rm -rf dist"
+ },
+ "keywords": [
+ "dependency-injection",
+ "typescript",
+ "playground",
+ "interactive"
+ ],
+ "author": "TDI2 Team",
+ "license": "MIT",
+ "dependencies": {
+ "@tdi2/di-core": "workspace:*",
+ "@codesandbox/sandpack-react": "^2.19.10",
+ "@monaco-editor/react": "^4.6.0",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "ts-morph": "^21.0.1",
+ "valtio": "^2.1.2"
+ },
+ "devDependencies": {
+ "@tdi2/vite-plugin-di": "workspace:*",
+ "@types/react": "^19.1.2",
+ "@types/react-dom": "^19.1.2",
+ "@vitejs/plugin-react": "^4.4.1",
+ "typescript": "~5.8.3",
+ "vite": "^6.0.0",
+ "vitest": "^2.1.8"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+}
diff --git a/monorepo/apps/di-playground/src/App.tsx b/monorepo/apps/di-playground/src/App.tsx
new file mode 100644
index 00000000..f7b9cefe
--- /dev/null
+++ b/monorepo/apps/di-playground/src/App.tsx
@@ -0,0 +1,513 @@
+import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
+import Editor from '@monaco-editor/react';
+import { BrowserTransformer } from './transformer';
+import { examples, defaultExample, ProjectExample, ProjectFile } from './examples';
+import { FileTree } from './components/FileTree';
+import { Preview } from './components/Preview';
+import type * as Monaco from 'monaco-editor';
+
+// Configure Monaco editor with type definitions
+const configureMonaco = (monaco: typeof Monaco) => {
+ const reactTypes = `declare module 'react' {
+ export function useState
(initialValue: T): [T, (value: T) => void];
+ export function useEffect(effect: () => void | (() => void), deps?: any[]): void;
+ export function useCallback(callback: T, deps: any[]): T;
+ export const FC: any;
+ export const ReactNode: any;
+ export default React;
+ const React: any;
+ }`;
+
+ const diCoreTypes = `declare module '@tdi2/di-core' {
+ export type Inject = T;
+ export type InjectOptional = T | undefined;
+ export function Service(): ClassDecorator;
+ export const Container: any;
+ }`;
+
+ const compilerOptions = {
+ target: monaco.languages.typescript.ScriptTarget.ES2020,
+ allowNonTsExtensions: true,
+ moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
+ module: monaco.languages.typescript.ModuleKind.ESNext,
+ noEmit: true,
+ esModuleInterop: true,
+ jsx: monaco.languages.typescript.JsxEmit.React,
+ reactNamespace: 'React',
+ allowJs: true,
+ typeRoots: ['node_modules/@types'],
+ };
+
+ const diagnosticOptions = {
+ noSemanticValidation: false,
+ noSyntaxValidation: false,
+ diagnosticCodesToIgnore: [
+ 1259, // Top-level 'await' expressions are only allowed when the 'module' option is set to 'esnext' or 'system'
+ 2792, // Cannot find module (we handle this with extra libs)
+ ],
+ };
+
+ // Configure TypeScript (for .ts files)
+ monaco.languages.typescript.typescriptDefaults.addExtraLib(reactTypes, 'file:///node_modules/@types/react/index.d.ts');
+ monaco.languages.typescript.typescriptDefaults.addExtraLib(diCoreTypes, 'file:///node_modules/@tdi2/di-core/index.d.ts');
+ monaco.languages.typescript.typescriptDefaults.setCompilerOptions(compilerOptions);
+ monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions(diagnosticOptions);
+
+ // Configure JavaScript (for JSX highlighting in .tsx files)
+ monaco.languages.typescript.javascriptDefaults.addExtraLib(reactTypes, 'file:///node_modules/@types/react/index.d.ts');
+ monaco.languages.typescript.javascriptDefaults.addExtraLib(diCoreTypes, 'file:///node_modules/@tdi2/di-core/index.d.ts');
+ monaco.languages.typescript.javascriptDefaults.setCompilerOptions(compilerOptions);
+ monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions(diagnosticOptions);
+};
+
+export interface TransformedFile {
+ path: string;
+ originalCode: string;
+ transformedCode: string;
+ error?: string;
+}
+
+export interface EditedFile {
+ path: string;
+ content: string;
+}
+
+type ViewMode = 'before' | 'after';
+
+function App() {
+ const [selectedExample, setSelectedExample] = useState(defaultExample);
+ const [selectedFilePath, setSelectedFilePath] = useState(null);
+ const [transformedFiles, setTransformedFiles] = useState>({});
+ const [editedFiles, setEditedFiles] = useState>({}); // Store user edits
+ const [viewMode, setViewMode] = useState('before');
+ const [error, setError] = useState(null);
+ const [isTransforming, setIsTransforming] = useState(false);
+ const [showPreview, setShowPreview] = useState(false); // Preview closed by default - opens after first transformation
+ const [isTransformerReady, setIsTransformerReady] = useState(false); // Track transformer initialization
+ const transformerRef = useRef(null);
+ const transformTimeoutRef = useRef(null);
+ const isTransformingRef = useRef(false); // Track transforming state in ref to prevent callback recreation
+ const hasInitialTransformRef = useRef(false); // Track if initial transformation is complete
+
+ // Refs to always access latest values (avoid stale closures)
+ const editedFilesRef = useRef(editedFiles);
+ const selectedExampleFilesRef = useRef(selectedExample.files);
+
+ // Keep refs in sync with state
+ useEffect(() => {
+ editedFilesRef.current = editedFiles;
+ }, [editedFiles]);
+
+ useEffect(() => {
+ selectedExampleFilesRef.current = selectedExample.files;
+ }, [selectedExample.files]);
+
+ // Initialize transformer
+ useEffect(() => {
+ console.log('๐ง Initializing BrowserTransformer...');
+ transformerRef.current = new BrowserTransformer();
+ console.log('โ
BrowserTransformer initialized');
+ setIsTransformerReady(true);
+ }, []);
+
+ // Set initial selected file when example changes
+ useEffect(() => {
+ if (selectedExample.files.length > 0) {
+ setSelectedFilePath(selectedExample.files[0].path);
+ }
+ // Clear edits when example changes
+ setEditedFiles({});
+ }, [selectedExample]);
+
+ // Transform all files
+ const transformAllFiles = useCallback(async () => {
+ if (!transformerRef.current || isTransformingRef.current) return;
+
+ console.log('๐ ========== STARTING transformAllFiles ==========');
+ console.log('๐ Files to process:', selectedExampleFilesRef.current.map(f => f.path));
+ console.log('๐ Edited files:', Object.keys(editedFilesRef.current));
+ console.log('๐ฏ Has initial transform?', hasInitialTransformRef.current);
+
+ isTransformingRef.current = true;
+ setIsTransforming(true);
+ setError(null);
+
+ const results: Record = {};
+
+ // CRITICAL: Update virtual files and re-scan interfaces FIRST
+ // This ensures DI_CONFIG generation uses current file content
+ // Use refs to get latest values (avoid stale closures)
+ try {
+ const filesToUpdate = selectedExampleFilesRef.current.map(file => ({
+ path: file.path,
+ content: editedFilesRef.current[file.path] ?? file.content,
+ }));
+ // Batch update all files and re-scan interfaces once (more efficient)
+ await transformerRef.current.updateFilesAndRescan(filesToUpdate);
+ } catch (err) {
+ console.error('Error updating virtual files:', err);
+ }
+
+ // Now transform all files with updated interface mappings
+ for (const file of selectedExampleFilesRef.current) {
+ try {
+ // Use edited content if available, otherwise use original
+ const contentToTransform = editedFilesRef.current[file.path] ?? file.content;
+ const result = await transformerRef.current.transform(contentToTransform, file.path);
+
+ results[file.path] = {
+ path: file.path,
+ originalCode: contentToTransform,
+ transformedCode: result.transformedCode || contentToTransform,
+ error: result.error,
+ };
+
+ if (result.warnings && result.warnings.length > 0) {
+ console.warn(`Warnings for ${file.path}:`, result.warnings);
+ }
+ } catch (err) {
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';
+ const contentToTransform = editedFilesRef.current[file.path] ?? file.content;
+ results[file.path] = {
+ path: file.path,
+ originalCode: contentToTransform,
+ transformedCode: `// Transformation failed: ${errorMessage}`,
+ error: errorMessage,
+ };
+ }
+ }
+
+ // CRITICAL: Re-scan interfaces after transformations
+ // Transformations remove/recreate source files, which clears interface mappings
+ // This rebuild ensures generateDIConfig() has the correct mappings
+ console.log('๐ Re-scanning interfaces after transformations...');
+ try {
+ await transformerRef.current.rescanInterfaces();
+ console.log('โ
Re-scanned interfaces after transformations');
+ } catch (err) {
+ console.error('โ Error re-scanning interfaces:', err);
+ }
+
+ console.log('๐ Transformation results:', Object.keys(results));
+ console.log('๐ ========== ENDING transformAllFiles ==========');
+
+ setTransformedFiles(results);
+ setIsTransforming(false);
+ isTransformingRef.current = false;
+
+ // After first successful transformation, mark as ready and open preview
+ if (!hasInitialTransformRef.current) {
+ hasInitialTransformRef.current = true;
+ setShowPreview(true);
+ console.log('โ
Initial transformation complete - preview ready, opening preview');
+ } else {
+ console.log('๐ Subsequent transformation complete');
+ }
+ }, []); // No dependencies - stable callback
+
+ // Debounced transform when edits change
+ useEffect(() => {
+ // Only transform if we have edits
+ if (Object.keys(editedFiles).length === 0) return;
+
+ // Clear any pending transformation
+ if (transformTimeoutRef.current) {
+ clearTimeout(transformTimeoutRef.current);
+ }
+
+ // Debounce transformation (500ms delay)
+ transformTimeoutRef.current = setTimeout(() => {
+ transformAllFiles();
+ }, 500);
+
+ // Cleanup on unmount or before next effect
+ return () => {
+ if (transformTimeoutRef.current) {
+ clearTimeout(transformTimeoutRef.current);
+ }
+ };
+ }, [editedFiles, transformAllFiles]);
+
+ // Transform when transformer is ready or example changes
+ useEffect(() => {
+ if (isTransformerReady) {
+ console.log('๐ข Transformer ready or example changed, triggering transformation');
+ transformAllFiles();
+ } else {
+ console.warn('โ ๏ธ Transformer not ready yet, skipping transformation');
+ }
+ }, [isTransformerReady, selectedExample, transformAllFiles]);
+
+ // Load edited files from URL hash on mount
+ useEffect(() => {
+ const hash = window.location.hash.slice(1);
+ if (hash) {
+ try {
+ const decoded = atob(hash);
+ const data = JSON.parse(decoded);
+ if (data.edits) {
+ setEditedFiles(data.edits);
+ }
+ } catch (e) {
+ console.warn('Failed to load edits from URL:', e);
+ }
+ }
+ }, []);
+
+ // Save edited files to URL hash
+ useEffect(() => {
+ if (Object.keys(editedFiles).length > 0) {
+ const data = { edits: editedFiles };
+ const encoded = btoa(JSON.stringify(data));
+ window.location.hash = encoded;
+ } else {
+ window.location.hash = '';
+ }
+ }, [editedFiles]);
+
+ const handleExampleChange = (index: number) => {
+ setSelectedExample(examples[index]);
+ setShowPreview(false);
+ hasInitialTransformRef.current = false; // Reset for new example
+ };
+
+ const handleFileSelect = (filePath: string) => {
+ setSelectedFilePath(filePath);
+ };
+
+ const handleRun = () => {
+ setShowPreview(true);
+ };
+
+ // Handle code changes with debounced transformation
+ const handleCodeChange = (value: string | undefined) => {
+ if (!selectedFilePath || !value) return;
+
+ // Update edited files immediately
+ setEditedFiles(prev => ({
+ ...prev,
+ [selectedFilePath]: value,
+ }));
+
+ // Debounce transformation (will trigger via useEffect)
+ if (transformTimeoutRef.current) {
+ clearTimeout(transformTimeoutRef.current);
+ }
+ };
+
+ // Generate DI_CONFIG.ts content using the actual transformer logic
+ const generateDIConfig = (): string => {
+ console.log('๐๏ธ generateDIConfig called');
+ console.log(' Transformer ready?', !!transformerRef.current);
+ console.log(' Transformed files:', Object.keys(transformedFiles));
+
+ if (!transformerRef.current) {
+ console.warn('โ ๏ธ Transformer not initialized, returning empty config');
+ return `// Transformer not initialized
+export const DI_CONFIG = {};
+export const SERVICE_TOKENS = {};
+export const INTERFACE_IMPLEMENTATIONS = {};
+`;
+ }
+
+ const config = transformerRef.current.generateDIConfig();
+ console.log('๐๏ธ Generated config length:', config.length);
+ console.log('๐๏ธ Config preview:', config.substring(0, 300));
+ return config;
+ };
+
+ // Memoize DI config to prevent unnecessary Sandpack reloads
+ const diConfigContent = useMemo(() => {
+ console.log('๐ useMemo for diConfigContent triggered');
+ return generateDIConfig();
+ }, [transformedFiles]); // Only regenerate when transformedFiles changes
+
+ // Get files to display based on view mode
+ const getDisplayFiles = (): ProjectFile[] => {
+ if (viewMode === 'before') {
+ return selectedExample.files;
+ } else {
+ // In "after" view, show transformed files + generated files
+ const transformedFilesList: ProjectFile[] = selectedExample.files.map(file => ({
+ ...file,
+ path: file.path,
+ content: transformedFiles[file.path]?.transformedCode || file.content,
+ }));
+
+ // Add generated DI_CONFIG file
+ const diConfigFile: ProjectFile = {
+ path: 'src/.tdi2/DI_CONFIG.ts',
+ language: 'typescript',
+ content: diConfigContent,
+ };
+
+ return [...transformedFilesList, diConfigFile];
+ }
+ };
+
+ const displayFiles = getDisplayFiles();
+
+ const getCurrentFile = (): ProjectFile | null => {
+ if (!selectedFilePath) return null;
+
+ // Check if it's a generated file
+ const generatedFile = displayFiles.find((f) => f.path === selectedFilePath);
+ if (generatedFile && selectedFilePath.includes('.tdi2/')) {
+ return generatedFile;
+ }
+
+ // Otherwise, look in original files
+ return selectedExample.files.find((f) => f.path === selectedFilePath) || null;
+ };
+
+ const getCurrentTransformedFile = (): TransformedFile | null => {
+ if (!selectedFilePath) return null;
+ return transformedFiles[selectedFilePath] || null;
+ };
+
+ const currentFile = getCurrentFile();
+ const currentTransformed = getCurrentTransformedFile();
+
+ // Get current code based on view mode
+ const currentCode = viewMode === 'before'
+ ? (editedFiles[selectedFilePath || ''] ?? currentFile?.content ?? '')
+ : (currentFile?.path.includes('.tdi2/')
+ ? currentFile.content // Show generated file as-is
+ : (currentTransformed?.transformedCode ?? ''));
+
+ const exampleIndex = examples.findIndex(ex => ex.name === selectedExample.name);
+
+ return (
+
+
+
+ ๐ฎ
+ TDI2 Playground
+
+
+
+
+ {showPreview && (
+
+ )}
+
+
+
+
+ {/* Left Panel - File Tree */}
+
+
+
+
+
+
+
+
{selectedExample.description}
+
+ {displayFiles.length} file{displayFiles.length !== 1 ? 's' : ''}
+ {viewMode === 'after' && ' (transformed)'}
+
+
+
+
+ {/* Center Panel - Code Editor */}
+
+
+
+ {currentFile?.path || 'No file selected'}
+
+ {isTransforming && (
+ โก Transforming...
+ )}
+ {currentFile?.path.includes('.tdi2/') && (
+ โ๏ธ Generated
+ )}
+ {viewMode === 'before' && !currentFile?.path.includes('.tdi2/') && (
+ Editable
+ )}
+ {viewMode === 'after' && !currentFile?.path.includes('.tdi2/') && (
+ Read-only
+ )}
+
+
+
+
+
+
+ {/* Right Panel - Live Preview (conditional) */}
+ {showPreview && Object.keys(transformedFiles).length > 0 && (
+
setShowPreview(false)}
+ />
+ )}
+
+
+ {error && (
+
+
โ Transformation Error
+
{error}
+
+ )}
+
+ );
+}
+
+export default App;
diff --git a/monorepo/apps/di-playground/src/__tests__/transformer.test.ts b/monorepo/apps/di-playground/src/__tests__/transformer.test.ts
new file mode 100644
index 00000000..35f2c0bb
--- /dev/null
+++ b/monorepo/apps/di-playground/src/__tests__/transformer.test.ts
@@ -0,0 +1,177 @@
+import { describe, it, expect, beforeAll } from 'vitest';
+import { BrowserTransformer } from '../transformer';
+
+describe('BrowserTransformer', () => {
+ let transformer: BrowserTransformer;
+
+ beforeAll(() => {
+ transformer = new BrowserTransformer();
+ });
+
+ it('should transform Counter component with Inject prop', async () => {
+ const input = `import React from 'react';
+import { Inject } from '@tdi2/di-core';
+import type { CounterServiceInterface } from '../services/CounterService';
+
+function Counter({counterService}:{counterService:Inject}) {
+ return (
+
+
Count: {counterService.state.count}
+
+
+
+ );
+}
+
+export default Counter;`;
+
+ const result = await transformer.transform(input, 'Counter.tsx');
+
+ // Debug output
+ if (!result.success) {
+ console.log('Transformation failed:', result.error);
+ console.log('Warnings:', result.warnings);
+ }
+
+ // Basic checks
+ expect(result.success).toBe(true);
+ expect(result.error).toBeUndefined();
+ expect(result.transformedCode).toBeDefined();
+
+ // Check transformation occurred
+ expect(result.stats?.transformedComponents).toBeGreaterThan(0);
+
+ // Check transformed code contains service injection
+ const transformed = result.transformedCode!;
+ expect(transformed).toContain('useService');
+ expect(transformed).toContain('CounterServiceInterface');
+
+ // Should NOT contain the original prop destructuring (it should be transformed)
+ // The transformation should add service extraction at the component top
+ expect(transformed.includes('useService') || transformed.includes('props')).toBe(true);
+ });
+
+ it('should handle TodoList component with Inject prop', async () => {
+ const input = `import React from 'react';
+import { Inject } from '@tdi2/di-core';
+import type { TodoServiceInterface } from '../services/TodoService';
+
+function TodoList({todoService}:{todoService:Inject}) {
+ const [newTodo, setNewTodo] = React.useState('');
+
+ const handleAdd = () => {
+ if (newTodo.trim()) {
+ todoService.addTodo(newTodo);
+ setNewTodo('');
+ }
+ };
+
+ return (
+
+
Todo List
+ setNewTodo(e.target.value)}
+ placeholder="What needs to be done?"
+ />
+
+
+ );
+}
+
+export default TodoList;`;
+
+ const result = await transformer.transform(input, 'TodoList.tsx');
+
+ expect(result.success).toBe(true);
+ expect(result.stats?.transformedComponents).toBeGreaterThan(0);
+ expect(result.transformedCode).toContain('useService');
+ expect(result.transformedCode).toContain('TodoServiceInterface');
+ });
+
+ it('should handle ShoppingCart with multiple Inject props', async () => {
+ const input = `import React from 'react';
+import { Inject } from '@tdi2/di-core';
+import type { ProductServiceInterface } from '../services/ProductService';
+import type { CartServiceInterface } from '../services/CartService';
+
+function ShoppingCart({productService, cartService}:{productService:Inject, cartService:Inject}) {
+ React.useEffect(() => {
+ productService.loadProducts();
+ }, []);
+
+ const total = cartService.getTotal();
+
+ return (
+
+
Shop
+
+
+ );
+}
+
+export default ShoppingCart;`;
+
+ const result = await transformer.transform(input, 'ShoppingCart.tsx');
+
+ expect(result.success).toBe(true);
+ expect(result.stats?.transformedComponents).toBeGreaterThan(0);
+
+ const transformed = result.transformedCode!;
+ expect(transformed).toContain('useService');
+ expect(transformed).toContain('ProductServiceInterface');
+ expect(transformed).toContain('CartServiceInterface');
+ });
+
+ it('should not transform components without Inject markers', async () => {
+ const input = `import React from 'react';
+
+function RegularComponent({ title }: { title: string }) {
+ return {title}
;
+}
+
+export default RegularComponent;`;
+
+ const result = await transformer.transform(input, 'RegularComponent.tsx');
+
+ expect(result.success).toBe(true);
+ expect(result.stats?.transformedComponents).toBe(0);
+ expect(result.warnings).toContain('No Inject type markers found in component props. Components need props with Inject types to be transformed.');
+ });
+
+ it('should handle service files without transformation', async () => {
+ const input = `import { Service } from '@tdi2/di-core';
+
+export interface CounterServiceInterface {
+ state: { count: number };
+ increment(): void;
+ decrement(): void;
+}
+
+@Service()
+export class CounterService implements CounterServiceInterface {
+ state = { count: 0 };
+
+ increment() {
+ this.state.count++;
+ }
+
+ decrement() {
+ this.state.count--;
+ }
+}`;
+
+ const result = await transformer.transform(input, 'CounterService.ts');
+
+ expect(result.success).toBe(true);
+ expect(result.stats?.transformedComponents).toBe(0);
+ // Service files should not be transformed
+ expect(result.transformedCode).toBe(input);
+ });
+});
diff --git a/monorepo/apps/di-playground/src/components/FileTree.tsx b/monorepo/apps/di-playground/src/components/FileTree.tsx
new file mode 100644
index 00000000..f02768c0
--- /dev/null
+++ b/monorepo/apps/di-playground/src/components/FileTree.tsx
@@ -0,0 +1,135 @@
+import React from 'react';
+import type { ProjectExample, ProjectFile } from '../examples';
+
+interface FileTreeProps {
+ example: ProjectExample;
+ selectedFile: string | null;
+ onFileSelect: (filePath: string) => void;
+}
+
+interface TreeNode {
+ name: string;
+ path: string;
+ isDirectory: boolean;
+ children?: TreeNode[];
+ file?: ProjectFile;
+}
+
+function buildTree(files: ProjectFile[]): TreeNode {
+ const root: TreeNode = {
+ name: 'root',
+ path: '',
+ isDirectory: true,
+ children: [],
+ };
+
+ files.forEach((file) => {
+ const parts = file.path.split('/');
+ let currentNode = root;
+
+ parts.forEach((part, index) => {
+ const isLastPart = index === parts.length - 1;
+ const currentPath = parts.slice(0, index + 1).join('/');
+
+ if (!currentNode.children) {
+ currentNode.children = [];
+ }
+
+ let existingNode = currentNode.children.find((child) => child.name === part);
+
+ if (!existingNode) {
+ existingNode = {
+ name: part,
+ path: currentPath,
+ isDirectory: !isLastPart,
+ children: isLastPart ? undefined : [],
+ file: isLastPart ? file : undefined,
+ };
+ currentNode.children.push(existingNode);
+ }
+
+ currentNode = existingNode;
+ });
+ });
+
+ return root;
+}
+
+function TreeItem({
+ node,
+ selectedFile,
+ onFileSelect,
+ depth = 0,
+}: {
+ node: TreeNode;
+ selectedFile: string | null;
+ onFileSelect: (filePath: string) => void;
+ depth?: number;
+}) {
+ const [isExpanded, setIsExpanded] = React.useState(true);
+
+ if (node.isDirectory) {
+ return (
+ <>
+ {node.name !== 'root' && (
+ setIsExpanded(!isExpanded)}
+ >
+ {isExpanded ? '๐' : '๐'}
+ {node.name}
+
+ )}
+ {isExpanded &&
+ node.children?.map((child) => (
+
+ ))}
+ >
+ );
+ }
+
+ const isSelected = selectedFile === node.path;
+ const isGenerated = node.path.includes('.tdi2/');
+
+ const icon = isGenerated
+ ? 'โ๏ธ' // Generated file icon
+ : node.name.endsWith('.tsx')
+ ? 'โ๏ธ'
+ : node.name.endsWith('.ts')
+ ? '๐'
+ : '๐';
+
+ return (
+ onFileSelect(node.path)}
+ >
+ {icon}
+ {node.name}
+ {isGenerated && Generated}
+
+ );
+}
+
+export function FileTree({ example, selectedFile, onFileSelect }: FileTreeProps) {
+ const tree = React.useMemo(() => buildTree(example.files), [example.files]);
+
+ return (
+
+
+ {example.name}
+
+
+
+
+
+ );
+}
diff --git a/monorepo/apps/di-playground/src/components/Preview.tsx b/monorepo/apps/di-playground/src/components/Preview.tsx
new file mode 100644
index 00000000..98a82252
--- /dev/null
+++ b/monorepo/apps/di-playground/src/components/Preview.tsx
@@ -0,0 +1,72 @@
+import { useMemo } from 'react';
+import { Sandpack } from '@codesandbox/sandpack-react';
+import type { ProjectExample } from '../examples';
+import type { TransformedFile } from '../App';
+import { generateSandpackFiles } from '../preview/projectGenerator';
+
+interface PreviewProps {
+ example: ProjectExample;
+ transformedFiles: Record;
+ diConfigContent: string;
+ onClose: () => void;
+}
+
+export function Preview({ example, transformedFiles, diConfigContent, onClose }: PreviewProps) {
+ console.log('๐ฌ Preview component rendering');
+ console.log(' Example:', example.name);
+ console.log(' Transformed files:', Object.keys(transformedFiles));
+ console.log(' DI Config length:', diConfigContent.length);
+ console.log(' DI Config preview:', diConfigContent.substring(0, 200));
+
+ // Generate Sandpack file structure from transformed files
+ const files = useMemo(() => {
+ console.log('๐ฆ Generating Sandpack files...');
+ const generatedFiles = generateSandpackFiles(example, transformedFiles, diConfigContent);
+ console.log('๐ฆ Generated files:', Object.keys(generatedFiles));
+ if (generatedFiles['/src/.tdi2/DI_CONFIG.ts']) {
+ console.log('๐ฆ DI_CONFIG in Sandpack:', generatedFiles['/src/.tdi2/DI_CONFIG.ts'].code.substring(0, 200));
+ }
+ return generatedFiles;
+ }, [example, transformedFiles, diConfigContent]);
+
+ return (
+
+
+
+ โถ๏ธ
+ Live Preview
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/monorepo/apps/di-playground/src/examples.ts b/monorepo/apps/di-playground/src/examples.ts
new file mode 100644
index 00000000..99a4c92a
--- /dev/null
+++ b/monorepo/apps/di-playground/src/examples.ts
@@ -0,0 +1,433 @@
+export interface ProjectFile {
+ path: string;
+ content: string;
+ language: 'typescript' | 'tsx';
+}
+
+export interface ProjectExample {
+ name: string;
+ description: string;
+ files: ProjectFile[];
+}
+
+export const examples: ProjectExample[] = [
+ {
+ name: 'Basic Counter',
+ description: 'Simple counter with service injection',
+ files: [
+ {
+ path: 'src/services/CounterService.ts',
+ language: 'typescript',
+ content: `import { Service } from '@tdi2/di-core';
+
+export interface CounterServiceInterface {
+ state: { count: number };
+ increment(): void;
+ decrement(): void;
+}
+
+@Service()
+export class CounterService implements CounterServiceInterface {
+ state = { count: 0 };
+
+ increment() {
+ this.state.count++;
+ }
+
+ decrement() {
+ this.state.count--;
+ }
+}
+`,
+ },
+ {
+ path: 'src/components/Counter.tsx',
+ language: 'tsx',
+ content: `import React from 'react';
+import { Inject } from '@tdi2/di-core';
+import type { CounterServiceInterface } from '../services/CounterService';
+
+function Counter({counterService}:{counterService:Inject}) {
+ return (
+
+
Count: {counterService.state.count}
+
+
+
+ );
+}
+
+export default Counter;
+`,
+ },
+ ],
+ },
+ {
+ name: 'Todo List',
+ description: 'Todo list with CRUD operations',
+ files: [
+ {
+ path: 'src/types/Todo.ts',
+ language: 'typescript',
+ content: `export interface Todo {
+ id: string;
+ text: string;
+ completed: boolean;
+ createdAt: Date;
+}
+`,
+ },
+ {
+ path: 'src/services/TodoService.ts',
+ language: 'typescript',
+ content: `import { Service } from '@tdi2/di-core';
+import type { Todo } from '../types/Todo';
+
+export interface TodoServiceInterface {
+ state: {
+ todos: Todo[];
+ filter: 'all' | 'active' | 'completed';
+ };
+ addTodo(text: string): void;
+ removeTodo(id: string): void;
+ toggleTodo(id: string): void;
+ setFilter(filter: 'all' | 'active' | 'completed'): void;
+ getFilteredTodos(): Todo[];
+}
+
+@Service()
+export class TodoService implements TodoServiceInterface {
+ state = {
+ todos: [] as Todo[],
+ filter: 'all' as 'all' | 'active' | 'completed'
+ };
+
+ addTodo(text: string) {
+ this.state.todos.push({
+ id: Math.random().toString(36).substr(2, 9),
+ text,
+ completed: false,
+ createdAt: new Date(),
+ });
+ }
+
+ removeTodo(id: string) {
+ this.state.todos = this.state.todos.filter(t => t.id !== id);
+ }
+
+ toggleTodo(id: string) {
+ const todo = this.state.todos.find(t => t.id === id);
+ if (todo) {
+ todo.completed = !todo.completed;
+ }
+ }
+
+ setFilter(filter: 'all' | 'active' | 'completed') {
+ this.state.filter = filter;
+ }
+
+ getFilteredTodos(): Todo[] {
+ const { todos, filter } = this.state;
+ if (filter === 'active') return todos.filter(t => !t.completed);
+ if (filter === 'completed') return todos.filter(t => t.completed);
+ return todos;
+ }
+}
+`,
+ },
+ {
+ path: 'src/components/TodoList.tsx',
+ language: 'tsx',
+ content: `import React from 'react';
+import { Inject } from '@tdi2/di-core';
+import type { TodoServiceInterface } from '../services/TodoService';
+
+function TodoList({todoService}:{todoService:Inject}) {
+ const [newTodo, setNewTodo] = React.useState('');
+
+ const handleAdd = () => {
+ if (newTodo.trim()) {
+ todoService.addTodo(newTodo);
+ setNewTodo('');
+ }
+ };
+
+ const filteredTodos = todoService.getFilteredTodos();
+
+ return (
+
+
Todo List
+
+
+ setNewTodo(e.target.value)}
+ onKeyPress={(e) => e.key === 'Enter' && handleAdd()}
+ placeholder="What needs to be done?"
+ />
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default TodoList;
+`,
+ },
+ ],
+ },
+ {
+ name: 'Shopping Cart',
+ description: 'E-commerce cart with product catalog',
+ files: [
+ {
+ path: 'src/types/Product.ts',
+ language: 'typescript',
+ content: `export interface Product {
+ id: string;
+ name: string;
+ price: number;
+ imageUrl?: string;
+ description?: string;
+}
+
+export interface CartItem extends Product {
+ quantity: number;
+}
+`,
+ },
+ {
+ path: 'src/services/ProductService.ts',
+ language: 'typescript',
+ content: `import { Service } from '@tdi2/di-core';
+import type { Product } from '../types/Product';
+
+export interface ProductServiceInterface {
+ state: {
+ products: Product[];
+ loading: boolean;
+ };
+ loadProducts(): void;
+ getProduct(id: string): Product | undefined;
+}
+
+@Service()
+export class ProductService implements ProductServiceInterface {
+ state = {
+ products: [
+ { id: '1', name: 'React T-Shirt', price: 24.99 },
+ { id: '2', name: 'TypeScript Mug', price: 14.99 },
+ { id: '3', name: 'DI Core Book', price: 34.99 },
+ { id: '4', name: 'Vite Stickers', price: 4.99 },
+ ] as Product[],
+ loading: false,
+ };
+
+ loadProducts() {
+ this.state.loading = true;
+ // Simulate API call
+ setTimeout(() => {
+ this.state.loading = false;
+ }, 1000);
+ }
+
+ getProduct(id: string) {
+ return this.state.products.find(p => p.id === id);
+ }
+}
+`,
+ },
+ {
+ path: 'src/services/CartService.ts',
+ language: 'typescript',
+ content: `import { Service } from '@tdi2/di-core';
+import type { Product, CartItem } from '../types/Product';
+
+export interface CartServiceInterface {
+ state: {
+ items: CartItem[];
+ isOpen: boolean;
+ };
+ addItem(product: Product): void;
+ removeItem(id: string): void;
+ updateQuantity(id: string, quantity: number): void;
+ clearCart(): void;
+ getTotal(): number;
+ toggleCart(): void;
+}
+
+@Service()
+export class CartService implements CartServiceInterface {
+ state = {
+ items: [] as CartItem[],
+ isOpen: false
+ };
+
+ addItem(product: Product) {
+ const existing = this.state.items.find(i => i.id === product.id);
+ if (existing) {
+ existing.quantity++;
+ } else {
+ this.state.items.push({ ...product, quantity: 1 });
+ }
+ }
+
+ removeItem(id: string) {
+ this.state.items = this.state.items.filter(i => i.id !== id);
+ }
+
+ updateQuantity(id: string, quantity: number) {
+ const item = this.state.items.find(i => i.id === id);
+ if (item) {
+ item.quantity = Math.max(0, quantity);
+ if (item.quantity === 0) {
+ this.removeItem(id);
+ }
+ }
+ }
+
+ clearCart() {
+ this.state.items = [];
+ }
+
+ getTotal() {
+ return this.state.items.reduce(
+ (sum, item) => sum + item.price * item.quantity,
+ 0
+ );
+ }
+
+ toggleCart() {
+ this.state.isOpen = !this.state.isOpen;
+ }
+}
+`,
+ },
+ {
+ path: 'src/components/ShoppingCart.tsx',
+ language: 'tsx',
+ content: `import React from 'react';
+import { Inject } from '@tdi2/di-core';
+import type { ProductServiceInterface } from '../services/ProductService';
+import type { CartServiceInterface } from '../services/CartService';
+
+function ShoppingCart({productService, cartService}:{productService:Inject, cartService:Inject}) {
+ React.useEffect(() => {
+ productService.loadProducts();
+ }, []);
+
+ const total = cartService.getTotal();
+
+ return (
+
+
+ Shop
+
+
+
+
+
Products
+ {productService.state.loading ? (
+
Loading...
+ ) : (
+
+ {productService.state.products.map((product) => (
+
+
{product.name}
+
\${product.price.toFixed(2)}
+
+
+ ))}
+
+ )}
+
+
+ {cartService.state.isOpen && (
+
+
Your Cart
+ {cartService.state.items.length === 0 ? (
+
Cart is empty
+ ) : (
+ <>
+
+
+ Total: \${total.toFixed(2)}
+
+
+ >
+ )}
+
+ )}
+
+ );
+}
+
+export default ShoppingCart;
+`,
+ },
+ ],
+ },
+];
+
+export const defaultExample = examples[0];
diff --git a/monorepo/apps/di-playground/src/main.tsx b/monorepo/apps/di-playground/src/main.tsx
new file mode 100644
index 00000000..8f7da6f6
--- /dev/null
+++ b/monorepo/apps/di-playground/src/main.tsx
@@ -0,0 +1,10 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App';
+import './styles.css';
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+ ,
+);
diff --git a/monorepo/apps/di-playground/src/polyfills/crypto.ts b/monorepo/apps/di-playground/src/polyfills/crypto.ts
new file mode 100644
index 00000000..f8b083c5
--- /dev/null
+++ b/monorepo/apps/di-playground/src/polyfills/crypto.ts
@@ -0,0 +1,23 @@
+// Browser-compatible crypto polyfill using Web Crypto API
+export function randomBytes(size: number): Uint8Array {
+ const bytes = new Uint8Array(size);
+ crypto.getRandomValues(bytes);
+ return bytes;
+}
+
+export function createHash(_algorithm: string) {
+ return {
+ update(_data: string) {
+ return this;
+ },
+ digest(_encoding?: string) {
+ // Return a simple hash-like string for browser compatibility
+ return Math.random().toString(36).substring(2, 15);
+ },
+ };
+}
+
+export default {
+ randomBytes,
+ createHash,
+};
diff --git a/monorepo/apps/di-playground/src/polyfills/empty.ts b/monorepo/apps/di-playground/src/polyfills/empty.ts
new file mode 100644
index 00000000..f79fd473
--- /dev/null
+++ b/monorepo/apps/di-playground/src/polyfills/empty.ts
@@ -0,0 +1,2 @@
+// Empty polyfill for Node.js modules not needed in browser
+export default {};
diff --git a/monorepo/apps/di-playground/src/polyfills/path.ts b/monorepo/apps/di-playground/src/polyfills/path.ts
new file mode 100644
index 00000000..7792a948
--- /dev/null
+++ b/monorepo/apps/di-playground/src/polyfills/path.ts
@@ -0,0 +1,50 @@
+// Browser-compatible path polyfill
+export const sep = '/';
+export const delimiter = ':';
+
+export function normalize(p: string): string {
+ return p.replace(/\\/g, '/');
+}
+
+export function join(...paths: string[]): string {
+ return paths.join('/').replace(/\/+/g, '/');
+}
+
+export function resolve(...paths: string[]): string {
+ return join(...paths);
+}
+
+export function relative(_from: string, to: string): string {
+ return to;
+}
+
+export function dirname(p: string): string {
+ const lastSlash = p.lastIndexOf('/');
+ return lastSlash === -1 ? '.' : p.slice(0, lastSlash);
+}
+
+export function basename(p: string, ext?: string): string {
+ const base = p.split('/').pop() || '';
+ if (ext && base.endsWith(ext)) {
+ return base.slice(0, -ext.length);
+ }
+ return base;
+}
+
+export function extname(p: string): string {
+ const base = basename(p);
+ const lastDot = base.lastIndexOf('.');
+ return lastDot === -1 ? '' : base.slice(lastDot);
+}
+
+export default {
+ sep,
+ delimiter,
+ normalize,
+ join,
+ resolve,
+ relative,
+ dirname,
+ basename,
+ extname,
+};
diff --git a/monorepo/apps/di-playground/src/preview/projectGenerator.ts b/monorepo/apps/di-playground/src/preview/projectGenerator.ts
new file mode 100644
index 00000000..a56ba8a3
--- /dev/null
+++ b/monorepo/apps/di-playground/src/preview/projectGenerator.ts
@@ -0,0 +1,191 @@
+import type { ProjectExample } from '../examples';
+
+export interface TransformedFile {
+ path: string;
+ originalCode: string;
+ transformedCode: string;
+ error?: string;
+}
+
+export interface SandpackFiles {
+ [key: string]: {
+ code: string;
+ hidden?: boolean;
+ active?: boolean;
+ readOnly?: boolean;
+ };
+}
+
+/**
+ * Generate Sandpack file structure with browser-transformed code
+ *
+ * NOTE: We use pre-transformed code (not the Vite plugin in Sandpack) because:
+ * - Vite plugins with complex AST transformations don't work well in Sandpack
+ * - The @tdi2/vite-plugin-di package may not be available in Sandpack
+ * - Browser transformation is already working and tested
+ *
+ * This still provides educational value:
+ * - Editor shows original โ transformed side-by-side
+ * - Preview shows the working result
+ * - Structure matches real TDI2 projects (with DI_CONFIG)
+ */
+export function generateSandpackFiles(
+ example: ProjectExample,
+ transformedFiles: Record,
+ diConfigContent: string
+): SandpackFiles {
+ const files: SandpackFiles = {};
+
+ // Add package.json (simple React, no Vite plugin)
+ files['/package.json'] = {
+ code: JSON.stringify(
+ {
+ name: 'tdi2-playground-preview',
+ version: '1.0.0',
+ dependencies: {
+ react: '^19.0.0',
+ 'react-dom': '^19.0.0',
+ '@tdi2/di-core': '3.3.0',
+ valtio: '^2.1.2',
+ },
+ },
+ null,
+ 2
+ ),
+ hidden: true,
+ };
+
+ // Add index.html
+ files['/index.html'] = {
+ code: `
+
+
+
+
+ TDI2 Playground - ${example.name}
+
+
+
+
+
+`,
+ hidden: true,
+ };
+
+ // Add the generated DI_CONFIG.ts (from browser transformer)
+ files['/src/.tdi2/DI_CONFIG.ts'] = {
+ code: diConfigContent,
+ hidden: true,
+ readOnly: true,
+ };
+
+ // Generate main entry point
+ files['/src/index.tsx'] = {
+ code: generateMainEntry(example),
+ hidden: true,
+ readOnly: true,
+ };
+
+ // Add TRANSFORMED component files (from browser transformer)
+ for (const file of example.files) {
+ const transformed = transformedFiles[file.path];
+
+ // Only include files that were successfully transformed OR are services
+ if (transformed || file.path.includes('services/')) {
+ const sandpackPath = `/src/${file.path.replace(/^src\//, '')}`;
+ files[sandpackPath] = {
+ code: transformed?.transformedCode || file.content,
+ active: file === example.files[0],
+ };
+ }
+ }
+
+ return files;
+}
+
+/**
+ * Generate index.tsx entry point (like real TDI2 projects)
+ */
+function generateMainEntry(
+ example: ProjectExample
+): string {
+ // Find the main component (first component file or first .tsx file)
+ const mainComponent = findMainComponent(example);
+
+ return `import React from 'react';
+import { createRoot } from 'react-dom/client';
+import { CompileTimeDIContainer } from '@tdi2/di-core/container';
+import { DIProvider } from '@tdi2/di-core/context';
+import { DI_CONFIG } from './.tdi2/DI_CONFIG';
+import ${mainComponent.componentName} from './${mainComponent.path}';
+
+// Create and configure DI container
+const container = new CompileTimeDIContainer();
+container.loadConfiguration(DI_CONFIG);
+
+console.log('๐ง TDI2 Playground - DI Container initialized');
+console.log('๐ Example: ${example.name}');
+console.log('๐ฆ Registered services:', container.getRegisteredTokens());
+
+createRoot(document.getElementById('root')!).render(
+
+
+ <${mainComponent.componentName} />
+
+
+);
+`;
+}
+
+/**
+ * Find the main component to render
+ */
+function findMainComponent(example: ProjectExample): {
+ componentName: string;
+ path: string;
+} {
+ // Look for component files
+ const componentFiles = example.files.filter((f) =>
+ f.path.includes('components/')
+ );
+
+ if (componentFiles.length > 0) {
+ const file = componentFiles[0];
+ // Extract component name from file
+ const nameMatch =
+ file.content.match(/export default (\w+)/) ||
+ file.content.match(/function (\w+)\s*\(/);
+
+ return {
+ componentName: nameMatch ? nameMatch[1] : 'App',
+ path: file.path.replace(/^src\//, ''),
+ };
+ }
+
+ // Fallback: use first .tsx file
+ const tsxFile = example.files.find((f) => f.path.endsWith('.tsx'));
+ if (tsxFile) {
+ const nameMatch =
+ tsxFile.content.match(/export default (\w+)/) ||
+ tsxFile.content.match(/function (\w+)\s*\(/);
+
+ return {
+ componentName: nameMatch ? nameMatch[1] : 'App',
+ path: tsxFile.path.replace(/^src\//, ''),
+ };
+ }
+
+ // Last resort
+ return {
+ componentName: 'App',
+ path: 'components/App.tsx',
+ };
+}
diff --git a/monorepo/apps/di-playground/src/stubs/node-stub.ts b/monorepo/apps/di-playground/src/stubs/node-stub.ts
new file mode 100644
index 00000000..85cfd77c
--- /dev/null
+++ b/monorepo/apps/di-playground/src/stubs/node-stub.ts
@@ -0,0 +1,24 @@
+// Stub for Node.js built-in modules that aren't needed in browser
+// ts-morph uses in-memory file system, so these modules are never actually called
+
+export default {};
+export const existsSync = () => false;
+export const readFileSync = () => '';
+export const writeFileSync = () => {};
+export const mkdirSync = () => {};
+export const resolve = (...args: string[]) => args.join('/');
+export const join = (...args: string[]) => args.join('/');
+export const dirname = (p: string) => p.split('/').slice(0, -1).join('/');
+export const basename = (p: string) => p.split('/').pop() || '';
+export const extname = (p: string) => {
+ const base = basename(p);
+ const idx = base.lastIndexOf('.');
+ return idx > 0 ? base.slice(idx) : '';
+};
+export const createRequire = () => () => ({});
+export const randomBytes = () => new Uint8Array(16);
+export const createHash = () => ({
+ update: () => ({ digest: () => 'stub-hash' })
+});
+export const platform = 'browser';
+export const sep = '/';
diff --git a/monorepo/apps/di-playground/src/styles.css b/monorepo/apps/di-playground/src/styles.css
new file mode 100644
index 00000000..3ebf5e04
--- /dev/null
+++ b/monorepo/apps/di-playground/src/styles.css
@@ -0,0 +1,545 @@
+* {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+}
+
+#root {
+ width: 100vw;
+ height: 100vh;
+ overflow: hidden;
+}
+
+.playground-container {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ background: #1e1e1e;
+ color: #d4d4d4;
+}
+
+.playground-header {
+ background: #252526;
+ border-bottom: 1px solid #3c3c3c;
+ padding: 12px 20px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ height: 50px;
+}
+
+.playground-title {
+ font-size: 18px;
+ font-weight: 600;
+ color: #cccccc;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.playground-controls {
+ display: flex;
+ gap: 10px;
+ align-items: center;
+}
+
+.playground-button {
+ background: #0e639c;
+ color: white;
+ border: none;
+ padding: 6px 16px;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 13px;
+ font-weight: 500;
+ transition: background 0.2s;
+}
+
+.playground-button:hover {
+ background: #1177bb;
+}
+
+.playground-button:active {
+ background: #0d5a8f;
+}
+
+.playground-button.secondary {
+ background: #3c3c3c;
+}
+
+.playground-button.secondary:hover {
+ background: #4c4c4c;
+}
+
+.playground-select {
+ background: #3c3c3c;
+ color: #cccccc;
+ border: 1px solid #555555;
+ padding: 6px 12px;
+ border-radius: 4px;
+ font-size: 13px;
+ cursor: pointer;
+}
+
+.playground-select:hover {
+ background: #4c4c4c;
+}
+
+.playground-content {
+ flex: 1;
+ display: flex;
+ overflow: hidden;
+}
+
+/* Sidebar Panel - File Tree */
+.sidebar-panel {
+ width: 250px;
+ display: flex;
+ flex-direction: column;
+ background: #252526;
+ border-right: 1px solid #3c3c3c;
+ overflow: hidden;
+}
+
+/* View Mode Toggle */
+.view-mode-toggle {
+ display: flex;
+ padding: 12px;
+ gap: 8px;
+ background: #2d2d30;
+ border-bottom: 1px solid #3c3c3c;
+}
+
+.toggle-button {
+ flex: 1;
+ background: #3c3c3c;
+ color: #cccccc;
+ border: none;
+ padding: 8px 12px;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 12px;
+ font-weight: 500;
+ transition: all 0.2s;
+}
+
+.toggle-button:hover {
+ background: #4c4c4c;
+}
+
+.toggle-button.active {
+ background: #0e639c;
+ color: white;
+}
+
+.sidebar-info {
+ padding: 12px;
+ border-top: 1px solid #3c3c3c;
+ background: #1e1e1e;
+}
+
+.info-description {
+ margin: 0 0 8px 0;
+ font-size: 12px;
+ color: #888888;
+ line-height: 1.4;
+}
+
+.info-stats {
+ margin: 0;
+ font-size: 11px;
+ color: #666666;
+}
+
+/* File Tree */
+.file-tree {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+.file-tree-header {
+ background: #2d2d30;
+ padding: 8px 12px;
+ border-bottom: 1px solid #3c3c3c;
+ font-size: 12px;
+ color: #cccccc;
+ font-weight: 600;
+}
+
+.file-tree-title {
+ display: block;
+}
+
+.file-tree-content {
+ flex: 1;
+ overflow-y: auto;
+ padding: 4px 0;
+}
+
+.file-tree-item {
+ display: flex;
+ align-items: center;
+ padding: 4px 8px 4px 12px;
+ cursor: pointer;
+ font-size: 13px;
+ color: #cccccc;
+ user-select: none;
+}
+
+.file-tree-item:hover {
+ background: #2a2d2e;
+}
+
+.file-tree-item.selected {
+ background: #37373d;
+ color: #ffffff;
+}
+
+.file-tree-item.directory {
+ font-weight: 500;
+}
+
+.file-tree-icon {
+ margin-right: 6px;
+ font-size: 14px;
+}
+
+.file-tree-name {
+ flex: 1;
+}
+
+.file-tree-item.generated {
+ color: #4ec9b0;
+}
+
+.file-badge {
+ background: #4ec9b0;
+ color: #1e1e1e;
+ padding: 1px 6px;
+ border-radius: 3px;
+ font-size: 9px;
+ font-weight: 600;
+ text-transform: uppercase;
+ margin-left: 6px;
+}
+
+/* Editor Panel */
+.editor-panel {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ border-right: 1px solid #3c3c3c;
+}
+
+.editor-panel:last-child {
+ border-right: none;
+}
+
+.editor-tabs {
+ display: flex;
+ align-items: center;
+ background: #252526;
+ border-bottom: 1px solid #3c3c3c;
+ gap: 4px;
+ padding: 0 8px;
+}
+
+.editor-tab {
+ background: transparent;
+ color: #888888;
+ border: none;
+ padding: 8px 16px;
+ cursor: pointer;
+ font-size: 13px;
+ font-weight: 500;
+ border-bottom: 2px solid transparent;
+ transition: all 0.2s;
+}
+
+.editor-tab:hover {
+ color: #cccccc;
+ background: #2a2d2e;
+}
+
+.editor-tab.active {
+ color: #ffffff;
+ border-bottom-color: #0e639c;
+}
+
+.transforming-indicator {
+ margin-left: auto;
+ font-size: 11px;
+ color: #0e639c;
+ padding: 4px 8px;
+}
+
+.editor-header {
+ background: #2d2d30;
+ padding: 6px 16px;
+ border-bottom: 1px solid #3c3c3c;
+ font-size: 12px;
+ color: #888888;
+ font-weight: 400;
+ font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.editor-file-path {
+ flex: 1;
+}
+
+.editor-mode-badge {
+ background: #0e639c;
+ color: white;
+ padding: 2px 8px;
+ border-radius: 3px;
+ font-size: 10px;
+ font-weight: 600;
+ text-transform: uppercase;
+}
+
+.editor-mode-badge.readonly {
+ background: #6b6b6b;
+}
+
+.editor-mode-badge.generated {
+ background: #4ec9b0;
+ color: #1e1e1e;
+}
+
+.editor-wrapper {
+ flex: 1;
+ overflow: hidden;
+}
+
+.error-panel {
+ background: #1e1e1e;
+ border-top: 2px solid #f48771;
+ padding: 16px;
+ max-height: 200px;
+ overflow-y: auto;
+ font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
+ font-size: 13px;
+}
+
+.error-title {
+ color: #f48771;
+ font-weight: 600;
+ margin-bottom: 8px;
+}
+
+.error-message {
+ color: #d4d4d4;
+ white-space: pre-wrap;
+ line-height: 1.5;
+}
+
+.loading-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(30, 30, 30, 0.9);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+}
+
+.loading-spinner {
+ border: 3px solid #3c3c3c;
+ border-top: 3px solid #0e639c;
+ border-radius: 50%;
+ width: 40px;
+ height: 40px;
+ animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
+
+/* Resizer */
+.resizer {
+ width: 5px;
+ cursor: col-resize;
+ background: #3c3c3c;
+ transition: background 0.2s;
+}
+
+.resizer:hover {
+ background: #0e639c;
+}
+
+/* Preview Panel */
+.preview-panel {
+ width: 600px;
+ min-width: 400px;
+ display: flex;
+ flex-direction: column;
+ background: #1e1e1e;
+ border-left: 1px solid #3c3c3c;
+ overflow: hidden;
+}
+
+.preview-header {
+ background: #2d2d30;
+ padding: 8px 16px;
+ border-bottom: 1px solid #3c3c3c;
+ font-size: 13px;
+ color: #cccccc;
+ font-weight: 500;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.preview-title {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-weight: 600;
+}
+
+.preview-icon {
+ font-size: 14px;
+}
+
+.preview-close {
+ background: transparent;
+ border: none;
+ color: #888888;
+ cursor: pointer;
+ font-size: 16px;
+ padding: 4px 8px;
+ transition: color 0.2s;
+}
+
+.preview-close:hover {
+ color: #ffffff;
+}
+
+.preview-content {
+ flex: 1;
+ overflow: hidden;
+ background: #ffffff;
+ color: #000000;
+}
+
+/* Sandpack overrides for dark theme */
+.preview-content > div {
+ height: 100%;
+}
+
+.preview-content > div > div {
+ height: 100%;
+}
+
+.preview-info {
+ background: #2d2d30;
+ border-top: 1px solid #3c3c3c;
+ padding: 8px 16px;
+ display: flex;
+ gap: 16px;
+}
+
+.preview-info-item {
+ display: flex;
+ gap: 6px;
+ font-size: 11px;
+}
+
+.preview-info-label {
+ color: #888888;
+}
+
+.preview-info-value {
+ color: #cccccc;
+ font-weight: 600;
+}
+
+.preview-placeholder {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ padding: 40px;
+ text-align: center;
+ color: #666666;
+}
+
+.preview-icon {
+ font-size: 64px;
+ margin-bottom: 20px;
+}
+
+.preview-placeholder h3 {
+ margin: 0 0 12px 0;
+ font-size: 18px;
+ color: #333333;
+}
+
+.preview-placeholder p {
+ margin: 0 0 20px 0;
+ font-size: 14px;
+ color: #666666;
+}
+
+.preview-info {
+ background: #f5f5f5;
+ border: 1px solid #e0e0e0;
+ border-radius: 4px;
+ padding: 16px;
+ width: 100%;
+ max-width: 300px;
+}
+
+.preview-info p {
+ margin: 0 0 8px 0;
+ font-size: 13px;
+ color: #444444;
+ text-align: left;
+}
+
+.preview-info p:last-child {
+ margin-bottom: 0;
+}
+
+/* Scrollbar styling */
+.file-tree-content::-webkit-scrollbar,
+.preview-content::-webkit-scrollbar {
+ width: 10px;
+}
+
+.file-tree-content::-webkit-scrollbar-track,
+.preview-content::-webkit-scrollbar-track {
+ background: #1e1e1e;
+}
+
+.file-tree-content::-webkit-scrollbar-thumb {
+ background: #424242;
+ border-radius: 5px;
+}
+
+.preview-content::-webkit-scrollbar-thumb {
+ background: #cccccc;
+ border-radius: 5px;
+}
+
+.file-tree-content::-webkit-scrollbar-thumb:hover,
+.preview-content::-webkit-scrollbar-thumb:hover {
+ background: #555555;
+}
diff --git a/monorepo/apps/di-playground/src/transformer.ts b/monorepo/apps/di-playground/src/transformer.ts
new file mode 100644
index 00000000..3bacc0fb
--- /dev/null
+++ b/monorepo/apps/di-playground/src/transformer.ts
@@ -0,0 +1,537 @@
+import { Project, Node } from 'ts-morph';
+
+// Import browser-compatible components directly from source
+// @ts-ignore - importing from source files
+import { TransformationPipeline } from '../../packages/di-core/tools/functional-di-enhanced-transformer/transformation-pipeline.ts';
+// @ts-ignore
+import { IntegratedInterfaceResolver } from '../../packages/di-core/tools/interface-resolver/integrated-interface-resolver.ts';
+// @ts-ignore
+import { SharedDependencyExtractor } from '../../packages/di-core/tools/shared/SharedDependencyExtractor.ts';
+// @ts-ignore
+import { SharedTypeResolver } from '../../packages/di-core/tools/shared/SharedTypeResolver.ts';
+// @ts-ignore
+import { DiInjectMarkers } from '../../packages/di-core/tools/functional-di-enhanced-transformer/di-inject-markers.ts';
+// @ts-ignore
+import { ImportManager } from '../../packages/di-core/tools/functional-di-enhanced-transformer/import-manager.ts';
+
+export interface TransformationResult {
+ success: boolean;
+ transformedCode?: string;
+ error?: string;
+ warnings?: string[];
+ stats?: {
+ transformedComponents: number;
+ errors: number;
+ warnings: number;
+ };
+}
+
+/**
+ * Browser-compatible transformer using the actual TDI2 transformation pipeline.
+ * Runs entirely in-memory without Node.js dependencies.
+ */
+export class BrowserTransformer {
+ private project: Project;
+ private virtualRoot = '/virtual';
+ private interfaceResolver: IntegratedInterfaceResolver;
+ private typeResolver: SharedTypeResolver;
+ private dependencyExtractor: SharedDependencyExtractor;
+ private transformationPipeline: TransformationPipeline;
+ private diInjectMarkers: DiInjectMarkers;
+ private importManager: ImportManager;
+ private cachedUsedServices: Set = new Set(); // Cache services found before transformation
+
+ constructor() {
+ // Create in-memory project for browser use
+ this.project = new Project({
+ useInMemoryFileSystem: true,
+ compilerOptions: {
+ target: 99, // ESNext
+ module: 99, // ESNext
+ jsx: 2, // React
+ experimentalDecorators: true,
+ lib: ['es2020', 'dom'],
+ skipLibCheck: true,
+ },
+ });
+
+ // Initialize DI inject markers detector
+ this.diInjectMarkers = new DiInjectMarkers();
+
+ // Initialize the transformation components - CRITICAL: Pass our project instance
+ this.interfaceResolver = new IntegratedInterfaceResolver({
+ scanDirs: [this.virtualRoot],
+ enableInheritanceDI: true,
+ enableStateDI: true,
+ project: this.project, // Pass our project so it scans the right files
+ });
+
+ this.typeResolver = new SharedTypeResolver(this.interfaceResolver);
+ this.dependencyExtractor = new SharedDependencyExtractor(this.typeResolver, {
+ scanDirs: [this.virtualRoot],
+ });
+
+ this.transformationPipeline = new TransformationPipeline({
+ generateFallbacks: true,
+ preserveTypeAnnotations: true,
+ interfaceResolver: this.interfaceResolver,
+ });
+
+ this.importManager = new ImportManager({
+ scanDirs: [this.virtualRoot],
+ outputDir: `${this.virtualRoot}/.tdi2`,
+ });
+
+ // Don't pre-create common services - they pollute the virtual filesystem
+ // Services will be created dynamically when examples load their files
+ }
+
+ /**
+ * Update a single virtual file's content (without re-scanning)
+ */
+ updateVirtualFile(filePath: string, content: string): void {
+ const virtualPath = `${this.virtualRoot}/${filePath.replace(/^src\//, '')}`;
+
+ // Check if file exists and update it, or create new one
+ const existingFile = this.project.getSourceFile(virtualPath);
+ if (existingFile) {
+ existingFile.replaceWithText(content);
+ console.log(`๐ Updated virtual file: ${virtualPath}`);
+ } else {
+ this.project.createSourceFile(virtualPath, content);
+ console.log(`๐ Created new virtual file: ${virtualPath}`);
+ }
+ }
+
+ /**
+ * Update multiple virtual files and re-scan interfaces once
+ * More efficient than updating one-by-one
+ */
+ async updateFilesAndRescan(files: Array<{ path: string; content: string }>): Promise {
+ // CRITICAL: Clear all existing files to prevent pollution from previous examples
+ const allFiles = this.project.getSourceFiles();
+ console.log(`๐๏ธ Clearing ${allFiles.length} existing files from virtual filesystem`);
+ for (const file of allFiles) {
+ this.project.removeSourceFile(file);
+ }
+ console.log(`โ
Virtual filesystem cleared`);
+
+ // Update all files first
+ for (const file of files) {
+ this.updateVirtualFile(file.path, file.content);
+ }
+
+ // CRITICAL: Cache used services BEFORE transformation happens
+ // After transformation, Inject markers are replaced with useService() calls
+ this.cachedUsedServices = this.findUsedServices();
+ console.log(`๐ Cached ${this.cachedUsedServices.size} used services before transformation`);
+
+ // Then re-scan interfaces once
+ await this.scanInterfaces();
+ console.log(`โ
Updated ${files.length} files and re-scanned interfaces`);
+ }
+
+ /**
+ * Re-scan interfaces without updating files
+ * Used after transformations that modify source files
+ */
+ async rescanInterfaces(): Promise {
+ await this.scanInterfaces();
+ }
+
+ private async scanInterfaces(): Promise {
+ try {
+ // BROWSER FIX: Don't call scanProject() because it tries to read from disk
+ // Instead, manually scan the source files we already created in memory
+
+ // Clear any existing mappings
+ const interfaces = (this.interfaceResolver as any).interfaces;
+ if (interfaces) {
+ interfaces.clear();
+ }
+
+ // Get all source files already in the project
+ const sourceFiles = this.project.getSourceFiles();
+ console.log(`๐ Scanning ${sourceFiles.length} source files already in project...`);
+
+ // Manually trigger the interface collection process
+ for (const sourceFile of sourceFiles) {
+ const filePath = sourceFile.getFilePath();
+ if (filePath.includes('node_modules') || filePath.includes('.tdi2')) continue;
+
+ console.log(` ๐ Scanning ${filePath}...`);
+ const classes = sourceFile.getClasses();
+
+ for (const classDecl of classes) {
+ const className = classDecl.getName();
+ if (!className) continue;
+
+ // Check for @Service decorator
+ const decorators = classDecl.getDecorators();
+ const hasServiceDecorator = decorators.some(d => {
+ const name = d.getText();
+ return name.includes('Service') || name.includes('@Service');
+ });
+
+ if (!hasServiceDecorator) continue;
+
+ console.log(` โ Found service: ${className}`);
+
+ // Get implemented interfaces
+ const implementsClause = classDecl.getImplements();
+ for (const impl of implementsClause) {
+ const interfaceName = impl.getText();
+ console.log(` โ implements ${interfaceName}`);
+
+ // Generate sanitized key (used as service token)
+ const sanitizedKey = `${interfaceName}__${filePath.replace(/^\/virtual\//, '').replace(/\//g, '_').replace(/\.ts$/, '')}`;
+ console.log(` โ sanitizedKey: ${sanitizedKey}`);
+
+ // Register this interface->class mapping
+ const mapping = {
+ implementationClass: className,
+ interfaceName: interfaceName,
+ filePath: filePath,
+ sanitizedKey: sanitizedKey,
+ isGeneric: false,
+ typeParameters: [],
+ scope: 'singleton' as const,
+ isAutoResolved: true,
+ registrationType: 'interface',
+ isClassBased: false,
+ isInheritanceBased: false,
+ };
+
+ // Store in the resolver's internal map (correct property name is 'interfaces')
+ const interfaceMap = (this.interfaceResolver as any).interfaces;
+ if (interfaceMap) {
+ interfaceMap.set(interfaceName, mapping);
+ }
+ }
+
+ // Also register the class itself
+ const classSanitizedKey = `${className}__${filePath.replace(/^\/virtual\//, '').replace(/\//g, '_').replace(/\.ts$/, '')}`;
+ const classMapping = {
+ implementationClass: className,
+ interfaceName: className,
+ filePath: filePath,
+ sanitizedKey: classSanitizedKey,
+ isGeneric: false,
+ typeParameters: [],
+ scope: 'singleton' as const,
+ isAutoResolved: true,
+ registrationType: 'class',
+ isClassBased: true,
+ isInheritanceBased: false,
+ };
+ const interfaceMap = (this.interfaceResolver as any).interfaces;
+ if (interfaceMap) {
+ interfaceMap.set(className, classMapping);
+ }
+ }
+ }
+
+ const interfaceMap = (this.interfaceResolver as any).interfaces;
+ const mappingCount = interfaceMap ? interfaceMap.size : 0;
+ console.log(`โ
Interface scan complete. Found ${mappingCount} mappings`);
+ } catch (error) {
+ console.error('Error scanning interfaces:', error);
+ }
+ }
+
+ /**
+ * Check if a component has Inject markers in its parameters
+ */
+ private hasInjectMarkers(component: any, sourceFile: any): boolean {
+ try {
+ const parameters = component.getParameters();
+ for (const param of parameters) {
+ const typeNode = param.getTypeNode();
+ if (typeNode && this.diInjectMarkers.hasInjectMarkersRecursive(typeNode, sourceFile)) {
+ return true;
+ }
+ }
+ return false;
+ } catch (error) {
+ console.warn('Error checking for inject markers:', error);
+ return false;
+ }
+ }
+
+ /**
+ * Find all service interfaces referenced in component files
+ */
+ private findUsedServices(): Set {
+ const usedServices = new Set();
+
+ // Get ALL source files to see what we have
+ const allFiles = this.project.getSourceFiles();
+ console.log(`๐ All files in project (${allFiles.length}):`, allFiles.map(f => f.getFilePath()));
+
+ // Get all component files
+ const componentFiles = allFiles.filter(f =>
+ f.getFilePath().includes('/components/')
+ );
+
+ console.log(`๐ Analyzing ${componentFiles.length} component files for service usage...`);
+ if (componentFiles.length === 0) {
+ console.warn(`โ ๏ธ No component files found! Check if files contain '/components/' in path`);
+ }
+
+ for (const file of componentFiles) {
+ const filePath = file.getFilePath();
+ const fileText = file.getFullText();
+ console.log(` ๐ Scanning ${filePath}...`);
+ console.log(` ๐ File content preview:`, fileText.substring(0, 200));
+
+ // Find all Inject patterns
+ const injectPattern = /Inject<(\w+)>/g;
+ let match;
+ let foundInFile = false;
+ while ((match = injectPattern.exec(fileText)) !== null) {
+ const serviceName = match[1];
+ usedServices.add(serviceName);
+ console.log(` โ Found usage: ${serviceName} in ${file.getBaseName()}`);
+ foundInFile = true;
+ }
+
+ // Also find InjectOptional patterns
+ const injectOptionalPattern = /InjectOptional<(\w+)>/g;
+ while ((match = injectOptionalPattern.exec(fileText)) !== null) {
+ const serviceName = match[1];
+ usedServices.add(serviceName);
+ console.log(` โ Found optional usage: ${serviceName} in ${file.getBaseName()}`);
+ foundInFile = true;
+ }
+
+ if (!foundInFile) {
+ console.log(` โ ๏ธ No Inject<> patterns found in ${file.getBaseName()}`);
+ }
+ }
+
+ console.log(`๐ Total unique services used: ${usedServices.size}`, Array.from(usedServices));
+ return usedServices;
+ }
+
+ /**
+ * Generate the DI_CONFIG file content with the same structure as the real Vite plugin
+ */
+ generateDIConfig(): string {
+ const mappings = (this.interfaceResolver as any).interfaces || new Map();
+
+ console.log('Generating DI_CONFIG. Mappings size:', mappings.size);
+ console.log('All files in project:', this.project.getSourceFiles().map(f => f.getFilePath()));
+
+ if (mappings.size === 0) {
+ return `// Auto-generated DI configuration
+// No services found
+// Project has ${this.project.getSourceFiles().length} files
+
+export const DI_CONFIG = {};
+
+export const SERVICE_TOKENS = {};
+
+export const INTERFACE_IMPLEMENTATIONS = {};
+`;
+ }
+
+ // Use cached services (found before transformation)
+ // We can't call findUsedServices() here because components are already transformed
+ const usedServices = this.cachedUsedServices;
+
+ console.log(`๐ Cached used services (${usedServices.size}):`, Array.from(usedServices));
+ console.log(`๐ Available mappings (${mappings.size}):`, Array.from(mappings.keys()));
+
+ if (usedServices.size === 0) {
+ return `// Auto-generated DI configuration
+// No services used in this example
+// Tip: Add Inject types to component props to use dependency injection
+
+export const DI_CONFIG = {};
+
+export const SERVICE_TOKENS = {};
+
+export const INTERFACE_IMPLEMENTATIONS = {};
+`;
+ }
+
+ const factoryFunctions: string[] = [];
+ const configEntries: string[] = [];
+ const serviceTokens: Record = {};
+ const interfaceImplementations: Record = {};
+ const imports: Set = new Set();
+ const processedClasses = new Set(); // Track which classes we've processed
+
+ // Process all interface->implementation mappings
+ mappings.forEach((implementation: any, interfaceName: string) => {
+ // FILTER: Only include if this interface is used in components
+ if (!usedServices.has(interfaceName)) {
+ console.log(` โญ๏ธ Skipping unused service: ${interfaceName}`);
+ return;
+ }
+ console.log(` โ
Registering service: ${interfaceName}`);
+ const className = implementation.implementationClass;
+ const filePath = implementation.filePath.replace(/^\/virtual\//, '').replace(/\.ts$/, '');
+
+ // Create unique token
+ const token = `${interfaceName}__${filePath.replace(/\//g, '_')}`;
+
+ // Add import (DI_CONFIG is in .tdi2 folder, so use ../ to go up to src/)
+ imports.add(`import { ${className} } from '../${filePath}';`);
+
+ // Create factory function (only once per class)
+ if (!processedClasses.has(className)) {
+ factoryFunctions.push(`function create${className}(container: any) {
+ return () => new ${className}();
+}`);
+ processedClasses.add(className);
+ }
+
+ // Add config entry
+ configEntries.push(` '${token}': {
+ factory: create${className},
+ scope: 'singleton' as const,
+ dependencies: [],
+ interfaceName: '${interfaceName}',
+ implementationClass: '${className}',
+ implementationClassPath: '${token}',
+ isAutoResolved: true,
+ registrationType: 'interface',
+ isClassBased: false,
+ isInheritanceBased: false,
+ baseClass: null,
+ baseClassGeneric: null,
+ }`);
+
+ // Add service token mapping
+ serviceTokens[className] = token;
+
+ // Add interface implementation mapping
+ if (!interfaceImplementations[interfaceName]) {
+ interfaceImplementations[interfaceName] = [];
+ }
+ interfaceImplementations[interfaceName].push(className);
+ });
+
+ const timestamp = new Date().toISOString();
+ const usedServicesList = Array.from(usedServices).join(', ');
+
+ return `// Auto-generated DI configuration
+// Generated: ${timestamp}
+// Services used in this example: ${usedServicesList}
+
+${Array.from(imports).join('\n')}
+
+// Factory functions
+${factoryFunctions.join('\n\n')}
+
+// DI Configuration Map
+export const DI_CONFIG = {
+${configEntries.join(',\n')}
+};
+
+// Service mappings
+export const SERVICE_TOKENS = ${JSON.stringify(serviceTokens, null, 2)};
+
+export const INTERFACE_IMPLEMENTATIONS = ${JSON.stringify(interfaceImplementations, null, 2)};
+`;
+ }
+
+ async transform(inputCode: string, fileName: string = 'Component.tsx'): Promise {
+ try {
+ // Create the component file in virtual filesystem (fileName already includes path like src/components/...)
+ const componentPath = `${this.virtualRoot}/${fileName.replace(/^src\//, '')}`;
+
+ // CRITICAL: Delete existing file first to prevent transformation stacking
+ const existingFile = this.project.getSourceFile(componentPath);
+ if (existingFile) {
+ this.project.removeSourceFile(existingFile);
+ }
+
+ // Create fresh source file
+ const sourceFile = this.project.createSourceFile(componentPath, inputCode, { overwrite: true });
+
+ // Find components with @di-inject marker
+ const functions = sourceFile.getFunctions();
+ const variables = sourceFile.getVariableDeclarations();
+
+ let transformedCount = 0;
+ const warnings: string[] = [];
+ const errors: string[] = [];
+
+ // Transform function declarations
+ for (const func of functions) {
+ if (this.hasInjectMarkers(func, sourceFile)) {
+ try {
+ // Extract dependencies
+ const dependencies = this.dependencyExtractor.extractFromFunctionParameter(func, sourceFile);
+
+ // Run transformation pipeline
+ this.transformationPipeline.transformComponent(func, dependencies, sourceFile);
+ transformedCount++;
+ } catch (err) {
+ errors.push(`Error transforming ${func.getName()}: ${err instanceof Error ? err.message : 'Unknown error'}`);
+ }
+ }
+ }
+
+ // Transform arrow function components
+ for (const varDecl of variables) {
+ const initializer = varDecl.getInitializer();
+ if (initializer && Node.isArrowFunction(initializer)) {
+ if (this.hasInjectMarkers(initializer, sourceFile)) {
+ try {
+ // Extract dependencies
+ const dependencies = this.dependencyExtractor.extractFromArrowFunction(initializer as any, sourceFile);
+
+ // Run transformation pipeline
+ this.transformationPipeline.transformComponent(initializer as any, dependencies, sourceFile);
+ transformedCount++;
+ } catch (err) {
+ errors.push(`Error transforming ${varDecl.getName()}: ${err instanceof Error ? err.message : 'Unknown error'}`);
+ }
+ }
+ }
+ }
+
+ // Add useService imports if transformations were made
+ if (transformedCount > 0) {
+ this.importManager.ensureDIImports(sourceFile);
+ }
+
+ // Get the transformed code
+ const transformedCode = sourceFile.getFullText();
+
+ // Add warnings if no transformations occurred
+ if (transformedCount === 0) {
+ warnings.push('No Inject type markers found in component props. Components need props with Inject types to be transformed.');
+ }
+
+ if (errors.length > 0) {
+ return {
+ success: false,
+ error: errors.join('\n'),
+ transformedCode: inputCode,
+ };
+ }
+
+ return {
+ success: true,
+ transformedCode,
+ warnings,
+ stats: {
+ transformedComponents: transformedCount,
+ errors: errors.length,
+ warnings: warnings.length,
+ },
+ };
+ } catch (error) {
+ console.error('Transformation error:', error);
+ return {
+ success: false,
+ error: error instanceof Error ? error.message : 'Unknown transformation error',
+ transformedCode: inputCode,
+ };
+ }
+ }
+}
diff --git a/monorepo/apps/di-playground/tsconfig.json b/monorepo/apps/di-playground/tsconfig.json
new file mode 100644
index 00000000..34959124
--- /dev/null
+++ b/monorepo/apps/di-playground/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "experimentalDecorators": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src"],
+ "references": [{ "path": "./tsconfig.node.json" }]
+}
diff --git a/monorepo/apps/di-playground/tsconfig.node.json b/monorepo/apps/di-playground/tsconfig.node.json
new file mode 100644
index 00000000..42872c59
--- /dev/null
+++ b/monorepo/apps/di-playground/tsconfig.node.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/monorepo/apps/di-playground/vite.config.ts b/monorepo/apps/di-playground/vite.config.ts
new file mode 100644
index 00000000..8b898d85
--- /dev/null
+++ b/monorepo/apps/di-playground/vite.config.ts
@@ -0,0 +1,44 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import path from 'path';
+
+export default defineConfig({
+ base: '/playground/',
+ plugins: [
+ react(),
+ ],
+ build: {
+ outDir: 'dist',
+ sourcemap: true,
+ },
+ server: {
+ port: 5174,
+ host: true
+ },
+ resolve: {
+ alias: {
+ // Import browser-compatible tools directly from source
+ '../../packages/di-core/tools/functional-di-enhanced-transformer/transformation-pipeline.ts': path.resolve(__dirname, '../../packages/di-core/tools/functional-di-enhanced-transformer/transformation-pipeline.ts'),
+ '../../packages/di-core/tools/interface-resolver/integrated-interface-resolver.ts': path.resolve(__dirname, '../../packages/di-core/tools/interface-resolver/integrated-interface-resolver.ts'),
+ '../../packages/di-core/tools/shared/SharedDependencyExtractor.ts': path.resolve(__dirname, '../../packages/di-core/tools/shared/SharedDependencyExtractor.ts'),
+ '../../packages/di-core/tools/shared/SharedTypeResolver.ts': path.resolve(__dirname, '../../packages/di-core/tools/shared/SharedTypeResolver.ts'),
+ '../../packages/di-core/tools/functional-di-enhanced-transformer/di-inject-markers.ts': path.resolve(__dirname, '../../packages/di-core/tools/functional-di-enhanced-transformer/di-inject-markers.ts'),
+ '../../packages/di-core/tools/functional-di-enhanced-transformer/import-manager.ts': path.resolve(__dirname, '../../packages/di-core/tools/functional-di-enhanced-transformer/import-manager.ts'),
+
+ // Prevent Node.js modules from being bundled
+ 'crypto': path.resolve(__dirname, './src/polyfills/crypto.ts'),
+ 'module': path.resolve(__dirname, './src/polyfills/empty.ts'),
+ 'fs': path.resolve(__dirname, './src/polyfills/empty.ts'),
+ 'path': path.resolve(__dirname, './src/polyfills/path.ts'),
+ 'os': path.resolve(__dirname, './src/polyfills/empty.ts'),
+ },
+ },
+ optimizeDeps: {
+ exclude: ['@tdi2/di-core'],
+ esbuildOptions: {
+ define: {
+ global: 'globalThis'
+ }
+ }
+ }
+});
diff --git a/monorepo/apps/di-playground/vitest.config.ts b/monorepo/apps/di-playground/vitest.config.ts
new file mode 100644
index 00000000..80077371
--- /dev/null
+++ b/monorepo/apps/di-playground/vitest.config.ts
@@ -0,0 +1,24 @@
+import { defineConfig } from 'vitest/config';
+import path from 'path';
+
+export default defineConfig({
+ test: {
+ globals: true,
+ environment: 'node',
+ include: ['src/**/*.test.ts'],
+ coverage: {
+ provider: 'v8',
+ reporter: ['text', 'json', 'html'],
+ },
+ },
+ resolve: {
+ alias: {
+ // Browser polyfills for Node.js modules
+ 'crypto': path.resolve(__dirname, './src/polyfills/crypto.ts'),
+ 'module': path.resolve(__dirname, './src/polyfills/empty.ts'),
+ 'fs': path.resolve(__dirname, './src/polyfills/empty.ts'),
+ 'path': path.resolve(__dirname, './src/polyfills/path.ts'),
+ 'os': path.resolve(__dirname, './src/polyfills/empty.ts'),
+ },
+ },
+});
diff --git a/monorepo/apps/legacy/package.json b/monorepo/apps/legacy/package.json
index c66de309..74c61fbc 100644
--- a/monorepo/apps/legacy/package.json
+++ b/monorepo/apps/legacy/package.json
@@ -7,7 +7,7 @@
"prebuild": "npm run di:build-prepare",
"dev": "vite",
"dev:clean": "npm run clean && npm run di:reset && npm run dev",
- "build-bak": "tsc -b && vite build",
+ "build": "vite build",
"clean": "rm -rf node_modules/.tdi2 src/.tdi2 node_modules/.vite node_modules/.vite-temp",
"lint": "eslint .",
"preview": "vite preview",
@@ -39,7 +39,8 @@
"test:functional": "bun run test-functional-di.tsx",
"test:interfaces": "tsx -e \"import {InterfaceResolver} from '@tdi2/di-core/interface-resolver.ts'; const r = new InterfaceResolver({verbose: true}); r.scanProject().then(() => { console.log('โ
Interface scanning completed'); console.log('๐ Implementations:', r.getInterfaceImplementations().size); console.log('๐ Dependencies:', r.getServiceDependencies().size); const validation = r.validateDependencies(); console.log('โ
Validation:', validation.isValid ? 'PASSED' : 'FAILED'); if (!validation.isValid) { console.log('โ Issues:', validation); } });\"",
"test:io": "bash -c 'select f in $(find . -type f -name \"*.test.ts\"); do [ -n \"$f\" ] && exec bun test \"$f\"; done'",
- "test": "bun test"
+ "test2": "bun test",
+ "test": "echo 'No tests configured yet'"
},
"dependencies": {
"@tdi2/di-core": "workspace:*",
diff --git a/monorepo/bun.lock b/monorepo/bun.lock
index 63cdd11b..1bcf810a 100644
--- a/monorepo/bun.lock
+++ b/monorepo/bun.lock
@@ -1,6 +1,5 @@
{
"lockfileVersion": 1,
- "configVersion": 0,
"workspaces": {
"": {
"name": "tdi2",
@@ -52,6 +51,28 @@
"vite": "^6.0.0",
},
},
+ "apps/di-playground": {
+ "name": "@tdi2/di-playground",
+ "version": "1.0.0",
+ "dependencies": {
+ "@codesandbox/sandpack-react": "^2.19.10",
+ "@monaco-editor/react": "^4.6.0",
+ "@tdi2/di-core": "workspace:*",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "ts-morph": "^21.0.1",
+ "valtio": "^2.1.2",
+ },
+ "devDependencies": {
+ "@tdi2/vite-plugin-di": "workspace:*",
+ "@types/react": "^19.1.2",
+ "@types/react-dom": "^19.1.2",
+ "@vitejs/plugin-react": "^4.4.1",
+ "typescript": "~5.8.3",
+ "vite": "^6.0.0",
+ "vitest": "^2.1.8",
+ },
+ },
"apps/di-test-harness": {
"name": "di-test-harness",
"version": "1.0.2",
@@ -434,6 +455,30 @@
"@changesets/write": ["@changesets/write@0.4.0", "", { "dependencies": { "@changesets/types": "^6.1.0", "fs-extra": "^7.0.1", "human-id": "^4.1.1", "prettier": "^2.7.1" } }, "sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q=="],
+ "@codemirror/autocomplete": ["@codemirror/autocomplete@6.20.0", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg=="],
+
+ "@codemirror/commands": ["@codemirror/commands@6.10.0", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.4.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, "sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w=="],
+
+ "@codemirror/lang-css": ["@codemirror/lang-css@6.3.1", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.0.2", "@lezer/css": "^1.1.7" } }, "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg=="],
+
+ "@codemirror/lang-html": ["@codemirror/lang-html@6.4.11", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/lang-css": "^6.0.0", "@codemirror/lang-javascript": "^6.0.0", "@codemirror/language": "^6.4.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0", "@lezer/css": "^1.1.0", "@lezer/html": "^1.3.12" } }, "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw=="],
+
+ "@codemirror/lang-javascript": ["@codemirror/lang-javascript@6.2.4", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.6.0", "@codemirror/lint": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0", "@lezer/javascript": "^1.0.0" } }, "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA=="],
+
+ "@codemirror/language": ["@codemirror/language@6.11.3", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", "@lezer/common": "^1.1.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" } }, "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA=="],
+
+ "@codemirror/lint": ["@codemirror/lint@6.9.2", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.35.0", "crelt": "^1.0.5" } }, "sha512-sv3DylBiIyi+xKwRCJAAsBZZZWo82shJ/RTMymLabAdtbkV5cSKwWDeCgtUq3v8flTaXS2y1kKkICuRYtUswyQ=="],
+
+ "@codemirror/state": ["@codemirror/state@6.5.2", "", { "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } }, "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA=="],
+
+ "@codemirror/view": ["@codemirror/view@6.38.8", "", { "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A=="],
+
+ "@codesandbox/nodebox": ["@codesandbox/nodebox@0.1.8", "", { "dependencies": { "outvariant": "^1.4.0", "strict-event-emitter": "^0.4.3" } }, "sha512-2VRS6JDSk+M+pg56GA6CryyUSGPjBEe8Pnae0QL3jJF1mJZJVMDKr93gJRtBbLkfZN6LD/DwMtf+2L0bpWrjqg=="],
+
+ "@codesandbox/sandpack-client": ["@codesandbox/sandpack-client@2.19.8", "", { "dependencies": { "@codesandbox/nodebox": "0.1.8", "buffer": "^6.0.3", "dequal": "^2.0.2", "mime-db": "^1.52.0", "outvariant": "1.4.0", "static-browser-server": "1.0.3" } }, "sha512-CMV4nr1zgKzVpx4I3FYvGRM5YT0VaQhALMW9vy4wZRhEyWAtJITQIqZzrTGWqB1JvV7V72dVEUCUPLfYz5hgJQ=="],
+
+ "@codesandbox/sandpack-react": ["@codesandbox/sandpack-react@2.20.0", "", { "dependencies": { "@codemirror/autocomplete": "^6.4.0", "@codemirror/commands": "^6.1.3", "@codemirror/lang-css": "^6.0.1", "@codemirror/lang-html": "^6.4.0", "@codemirror/lang-javascript": "^6.1.2", "@codemirror/language": "^6.3.2", "@codemirror/state": "^6.2.0", "@codemirror/view": "^6.7.1", "@codesandbox/sandpack-client": "^2.19.8", "@lezer/highlight": "^1.1.3", "@react-hook/intersection-observer": "^3.1.1", "@stitches/core": "^1.2.6", "anser": "^2.1.1", "clean-set": "^1.1.2", "dequal": "^2.0.2", "escape-carriage": "^1.3.1", "lz-string": "^1.4.4", "react-devtools-inline": "4.4.0", "react-is": "^17.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19", "react-dom": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-takd1YpW/PMQ6KPQfvseWLHWklJovGY8QYj8MtWnskGKbjOGJ6uZfyZbcJ6aCFLQMpNyjTqz9AKNbvhCOZ1TUQ=="],
+
"@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
"@ctrl/tinycolor": ["@ctrl/tinycolor@4.2.0", "", {}, "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A=="],
@@ -612,14 +657,32 @@
"@ladle/react-context": ["@ladle/react-context@1.0.1", "", { "peerDependencies": { "react": ">=16.14.0", "react-dom": ">=16.14.0" } }, "sha512-xVQ8siyOEQG6e4Knibes1uA3PTyXnqiMmfSmd5pIbkzeDty8NCBtYHhTXSlfmcDNEsw/G8OzNWo4VbyQAVDl2A=="],
+ "@lezer/common": ["@lezer/common@1.3.0", "", {}, "sha512-L9X8uHCYU310o99L3/MpJKYxPzXPOS7S0NmBaM7UO/x2Kb2WbmMLSkfvdr1KxRIFYOpbY0Jhn7CfLSUDzL8arQ=="],
+
+ "@lezer/css": ["@lezer/css@1.3.0", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.3.0" } }, "sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw=="],
+
+ "@lezer/highlight": ["@lezer/highlight@1.2.3", "", { "dependencies": { "@lezer/common": "^1.3.0" } }, "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g=="],
+
+ "@lezer/html": ["@lezer/html@1.3.12", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-RJ7eRWdaJe3bsiiLLHjCFT1JMk8m1YP9kaUbvu2rMLEoOnke9mcTVDyfOslsln0LtujdWespjJ39w6zo+RsQYw=="],
+
+ "@lezer/javascript": ["@lezer/javascript@1.5.4", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.1.3", "@lezer/lr": "^1.3.0" } }, "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA=="],
+
+ "@lezer/lr": ["@lezer/lr@1.4.3", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-yenN5SqAxAPv/qMnpWW0AT7l+SxVrgG+u0tNsRQWqbrz66HIl8DnEbBObvy21J5K7+I1v7gsAnlE2VQ5yYVSeA=="],
+
"@manypkg/find-root": ["@manypkg/find-root@1.1.0", "", { "dependencies": { "@babel/runtime": "^7.5.5", "@types/node": "^12.7.1", "find-up": "^4.1.0", "fs-extra": "^8.1.0" } }, "sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA=="],
"@manypkg/get-packages": ["@manypkg/get-packages@1.1.3", "", { "dependencies": { "@babel/runtime": "^7.5.5", "@changesets/types": "^4.0.1", "@manypkg/find-root": "^1.1.0", "fs-extra": "^8.1.0", "globby": "^11.0.0", "read-yaml-file": "^1.1.0" } }, "sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A=="],
+ "@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.2", "", {}, "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="],
+
"@mdx-js/mdx": ["@mdx-js/mdx@3.1.1", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="],
"@mdx-js/react": ["@mdx-js/react@3.1.1", "", { "dependencies": { "@types/mdx": "^2.0.0" }, "peerDependencies": { "@types/react": ">=16", "react": ">=16" } }, "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw=="],
+ "@monaco-editor/loader": ["@monaco-editor/loader@1.6.1", "", { "dependencies": { "state-local": "^1.0.6" } }, "sha512-w3tEnj9HYEC73wtjdpR089AqkUPskFRcdkxsiSFt3SoUc3OHpmu+leP94CXBm4mHfefmhsdfI0ZQu6qJ0wgtPg=="],
+
+ "@monaco-editor/react": ["@monaco-editor/react@4.7.0", "", { "dependencies": { "@monaco-editor/loader": "^1.5.0" }, "peerDependencies": { "monaco-editor": ">= 0.25.0 < 1", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA=="],
+
"@mswjs/interceptors": ["@mswjs/interceptors@0.40.0", "", { "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "strict-event-emitter": "^0.5.1" } }, "sha512-EFd6cVbHsgLa6wa4RljGj6Wk75qoHxUSyc5asLyyPSyuhIcdS2Q3Phw6ImS1q+CkALthJRShiYfKANcQMuMqsQ=="],
"@next/eslint-plugin-next": ["@next/eslint-plugin-next@15.5.6", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-YxDvsT2fwy1j5gMqk3ppXlsgDopHnkM4BoxSVASbvvgh5zgsK8lvWerDzPip8k3WVzsTZ1O7A7si1KNfN4OZfQ=="],
@@ -692,6 +755,10 @@
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
+ "@react-hook/intersection-observer": ["@react-hook/intersection-observer@3.1.2", "", { "dependencies": { "@react-hook/passive-layout-effect": "^1.2.0", "intersection-observer": "^0.10.0" }, "peerDependencies": { "react": ">=16.8" } }, "sha512-mWU3BMkmmzyYMSuhO9wu3eJVP21N8TcgYm9bZnTrMwuM818bEk+0NRM3hP+c/TqA9Ln5C7qE53p1H0QMtzYdvQ=="],
+
+ "@react-hook/passive-layout-effect": ["@react-hook/passive-layout-effect@1.2.1", "", { "peerDependencies": { "react": ">=16.8" } }, "sha512-IwEphTD75liO8g+6taS+4oqz+nnroocNfWVHWz7j+N+ZO2vYrc6PV1q7GQhuahL0IOR7JccFTsFKQ/mb6iZWAg=="],
+
"@repo/eslint-config": ["@repo/eslint-config@workspace:packages/eslint-config"],
"@repo/typescript-config": ["@repo/typescript-config@workspace:packages/typescript-config"],
@@ -766,6 +833,8 @@
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
+ "@stitches/core": ["@stitches/core@1.2.8", "", {}, "sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg=="],
+
"@swc/core": ["@swc/core@1.15.2", "", { "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.25" }, "optionalDependencies": { "@swc/core-darwin-arm64": "1.15.2", "@swc/core-darwin-x64": "1.15.2", "@swc/core-linux-arm-gnueabihf": "1.15.2", "@swc/core-linux-arm64-gnu": "1.15.2", "@swc/core-linux-arm64-musl": "1.15.2", "@swc/core-linux-x64-gnu": "1.15.2", "@swc/core-linux-x64-musl": "1.15.2", "@swc/core-win32-arm64-msvc": "1.15.2", "@swc/core-win32-ia32-msvc": "1.15.2", "@swc/core-win32-x64-msvc": "1.15.2" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" }, "optionalPeers": ["@swc/helpers"] }, "sha512-OQm+yJdXxvSjqGeaWhP6Ia264ogifwAO7Q12uTDVYj/Ks4jBTI4JknlcjDRAXtRhqbWsfbZyK/5RtuIPyptk3w=="],
"@swc/core-darwin-arm64": ["@swc/core-darwin-arm64@1.15.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Ghyz4RJv4zyXzrUC1B2MLQBbppIB5c4jMZJybX2ebdEQAvryEKp3gq1kBksCNsatKGmEgXul88SETU19sMWcrw=="],
@@ -800,6 +869,8 @@
"@tdi2/di-debug": ["@tdi2/di-debug@workspace:apps/di-debug"],
+ "@tdi2/di-playground": ["@tdi2/di-playground@workspace:apps/di-playground"],
+
"@tdi2/di-testing": ["@tdi2/di-testing@workspace:packages/di-testing"],
"@tdi2/docs-starlight": ["@tdi2/docs-starlight@workspace:apps/docs-starlight"],
@@ -1084,6 +1155,8 @@
"ajv-keywords": ["ajv-keywords@5.1.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3" }, "peerDependencies": { "ajv": "^8.8.2" } }, "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw=="],
+ "anser": ["anser@2.3.3", "", {}, "sha512-QGY1oxYE7/kkeNmbtY/2ZjQ07BCG3zYdz+k/+sf69kMzEIxb93guHkPnIXITQ+BYi61oQwG74twMOX1tD4aesg=="],
+
"ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="],
"ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="],
@@ -1168,6 +1241,8 @@
"browserslist": ["browserslist@4.28.0", "", { "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", "electron-to-chromium": "^1.5.249", "node-releases": "^2.0.27", "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" } }, "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ=="],
+ "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
+
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
"bun": ["bun@1.3.2", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.3.2", "@oven/bun-darwin-x64": "1.3.2", "@oven/bun-darwin-x64-baseline": "1.3.2", "@oven/bun-linux-aarch64": "1.3.2", "@oven/bun-linux-aarch64-musl": "1.3.2", "@oven/bun-linux-x64": "1.3.2", "@oven/bun-linux-x64-baseline": "1.3.2", "@oven/bun-linux-x64-musl": "1.3.2", "@oven/bun-linux-x64-musl-baseline": "1.3.2", "@oven/bun-windows-x64": "1.3.2", "@oven/bun-windows-x64-baseline": "1.3.2" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-x75mPJiEfhO1j4Tfc65+PtW6ZyrAB6yTZInydnjDZXF9u9PRAnr6OK3v0Q9dpDl0dxRHkXlYvJ8tteJxc8t4Sw=="],
@@ -1198,7 +1273,7 @@
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
- "chai": ["chai@6.2.1", "", {}, "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg=="],
+ "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="],
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
@@ -1212,6 +1287,8 @@
"chardet": ["chardet@2.1.1", "", {}, "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ=="],
+ "check-error": ["check-error@2.1.1", "", {}, "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw=="],
+
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"chrome-trace-event": ["chrome-trace-event@1.0.4", "", {}, "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ=="],
@@ -1222,6 +1299,8 @@
"classnames": ["classnames@2.5.1", "", {}, "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="],
+ "clean-set": ["clean-set@1.1.2", "", {}, "sha512-cA8uCj0qSoG9e0kevyOWXwPaELRPVg5Pxp6WskLMwerx257Zfnh8Nl0JBH59d7wQzij2CK7qEfJQK3RjuKKIug=="],
+
"cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="],
"cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="],
@@ -1272,6 +1351,8 @@
"create-require": ["create-require@1.1.1", "", {}, "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="],
+ "crelt": ["crelt@1.0.6", "", {}, "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="],
+
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"crossws": ["crossws@0.3.5", "", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA=="],
@@ -1286,6 +1367,8 @@
"csstype": ["csstype@3.2.2", "", {}, "sha512-D80T+tiqkd/8B0xNlbstWDG4x6aqVfO52+OlSUNIdkTvmNw0uQpJLeos2J/2XvpyidAFuTPmpad+tUxLndwj6g=="],
+ "d": ["d@1.0.2", "", { "dependencies": { "es5-ext": "^0.10.64", "type": "^2.7.2" } }, "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw=="],
+
"d3": ["d3@7.9.0", "", { "dependencies": { "d3-array": "3", "d3-axis": "3", "d3-brush": "3", "d3-chord": "3", "d3-color": "3", "d3-contour": "4", "d3-delaunay": "6", "d3-dispatch": "3", "d3-drag": "3", "d3-dsv": "3", "d3-ease": "3", "d3-fetch": "3", "d3-force": "3", "d3-format": "3", "d3-geo": "3", "d3-hierarchy": "3", "d3-interpolate": "3", "d3-path": "3", "d3-polygon": "3", "d3-quadtree": "3", "d3-random": "3", "d3-scale": "4", "d3-scale-chromatic": "3", "d3-selection": "3", "d3-shape": "3", "d3-time": "3", "d3-time-format": "4", "d3-timer": "3", "d3-transition": "3", "d3-zoom": "3" } }, "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA=="],
"d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="],
@@ -1362,6 +1445,8 @@
"decode-uri-component": ["decode-uri-component@0.4.1", "", {}, "sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ=="],
+ "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],
+
"deep-equal": ["deep-equal@1.0.1", "", {}, "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw=="],
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
@@ -1422,6 +1507,8 @@
"dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="],
+ "dompurify": ["dompurify@3.1.7", "", {}, "sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ=="],
+
"dotenv": ["dotenv@8.6.0", "", {}, "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g=="],
"dset": ["dset@3.1.4", "", {}, "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA=="],
@@ -1464,6 +1551,12 @@
"es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="],
+ "es5-ext": ["es5-ext@0.10.64", "", { "dependencies": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", "esniff": "^2.0.1", "next-tick": "^1.1.0" } }, "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg=="],
+
+ "es6-iterator": ["es6-iterator@2.0.3", "", { "dependencies": { "d": "1", "es5-ext": "^0.10.35", "es6-symbol": "^3.1.1" } }, "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g=="],
+
+ "es6-symbol": ["es6-symbol@3.1.4", "", { "dependencies": { "d": "^1.0.2", "ext": "^1.7.0" } }, "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg=="],
+
"esast-util-from-estree": ["esast-util-from-estree@2.0.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-visit": "^2.0.0", "unist-util-position-from-estree": "^2.0.0" } }, "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ=="],
"esast-util-from-js": ["esast-util-from-js@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "acorn": "^8.0.0", "esast-util-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw=="],
@@ -1472,6 +1565,8 @@
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
+ "escape-carriage": ["escape-carriage@1.3.1", "", {}, "sha512-GwBr6yViW3ttx1kb7/Oh+gKQ1/TrhYwxKqVmg5gS+BK+Qe2KrOa/Vh7w3HPBvgGf0LfcDGoY9I6NHKoA5Hozhw=="],
+
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
@@ -1494,6 +1589,8 @@
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
+ "esniff": ["esniff@2.0.1", "", { "dependencies": { "d": "^1.0.1", "es5-ext": "^0.10.62", "event-emitter": "^0.3.5", "type": "^2.7.2" } }, "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg=="],
+
"espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
"esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
@@ -1522,6 +1619,8 @@
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
+ "event-emitter": ["event-emitter@0.3.5", "", { "dependencies": { "d": "1", "es5-ext": "~0.10.14" } }, "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA=="],
+
"eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="],
"events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
@@ -1532,6 +1631,8 @@
"expressive-code": ["expressive-code@0.41.3", "", { "dependencies": { "@expressive-code/core": "^0.41.3", "@expressive-code/plugin-frames": "^0.41.3", "@expressive-code/plugin-shiki": "^0.41.3", "@expressive-code/plugin-text-markers": "^0.41.3" } }, "sha512-YLnD62jfgBZYrXIPQcJ0a51Afv9h8VlWqEGK9uU2T5nL/5rb8SnA86+7+mgCZe5D34Tff5RNEA5hjNVJYHzrFg=="],
+ "ext": ["ext@1.7.0", "", { "dependencies": { "type": "^2.7.2" } }, "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw=="],
+
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
"extendable-error": ["extendable-error@0.1.7", "", {}, "sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg=="],
@@ -1720,6 +1821,8 @@
"iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="],
+ "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
+
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
@@ -1740,6 +1843,8 @@
"internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="],
+ "intersection-observer": ["intersection-observer@0.10.0", "", {}, "sha512-fn4bQ0Xq8FTej09YC/jqKZwtijpvARlRp6wxL5WTA6yPe2YWSJ5RJh7Nm79rK2qB0wr6iDQzH60XGq5V/7u8YQ=="],
+
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
"iron-webcrypto": ["iron-webcrypto@1.2.1", "", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="],
@@ -1892,6 +1997,8 @@
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
+ "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="],
+
"lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="],
@@ -1906,6 +2013,8 @@
"markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="],
+ "marked": ["marked@14.0.0", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ=="],
+
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
"mdast-util-definitions": ["mdast-util-definitions@6.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ=="],
@@ -2046,6 +2155,8 @@
"mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="],
+ "monaco-editor": ["monaco-editor@0.54.0", "", { "dependencies": { "dompurify": "3.1.7", "marked": "14.0.0" } }, "sha512-hx45SEUoLatgWxHKCmlLJH81xBo0uXP4sRkESUpmDQevfi+e7K1VuiSprK6UpQ8u4zOcKNiH0pMvHvlMWA/4cw=="],
+
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
@@ -2068,6 +2179,8 @@
"neotraverse": ["neotraverse@0.6.18", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="],
+ "next-tick": ["next-tick@1.1.0", "", {}, "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="],
+
"nlcst-to-string": ["nlcst-to-string@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0" } }, "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA=="],
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
@@ -2116,7 +2229,7 @@
"outdent": ["outdent@0.5.0", "", {}, "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q=="],
- "outvariant": ["outvariant@1.4.3", "", {}, "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA=="],
+ "outvariant": ["outvariant@1.4.0", "", {}, "sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw=="],
"own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="],
@@ -2170,6 +2283,8 @@
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
+ "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="],
+
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
@@ -2234,6 +2349,8 @@
"react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
+ "react-devtools-inline": ["react-devtools-inline@4.4.0", "", { "dependencies": { "es6-symbol": "^3" } }, "sha512-ES0GolSrKO8wsKbsEkVeiR/ZAaHQTY4zDh1UW8DImVmm8oaGLl3ijJDvSGe+qDRKPZdPRnDtWWnSvvrgxXdThQ=="],
+
"react-diff-view": ["react-diff-view@3.3.2", "", { "dependencies": { "classnames": "^2.3.2", "diff-match-patch": "^1.0.5", "gitdiff-parser": "^0.3.1", "lodash": "^4.17.21", "shallow-equal": "^3.1.0", "warning": "^4.0.3" }, "peerDependencies": { "react": ">=16.14.0" } }, "sha512-wPVq4ktTcGOHbhnWKU/gHLtd3N2Xd+OZ/XQWcKA06dsxlSsESePAumQILwHtiak2nMCMiWcIfBpqZ5OiharUPA=="],
"react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="],
@@ -2242,7 +2359,7 @@
"react-inspector": ["react-inspector@6.0.2", "", { "peerDependencies": { "react": "^16.8.4 || ^17.0.0 || ^18.0.0" } }, "sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ=="],
- "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
+ "react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
"react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="],
@@ -2418,6 +2535,10 @@
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
+ "state-local": ["state-local@1.0.7", "", {}, "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w=="],
+
+ "static-browser-server": ["static-browser-server@1.0.3", "", { "dependencies": { "@open-draft/deferred-promise": "^2.1.0", "dotenv": "^16.0.3", "mime-db": "^1.52.0", "outvariant": "^1.3.0" } }, "sha512-ZUyfgGDdFRbZGGJQ1YhiM930Yczz5VlbJObrQLlk24+qNHVQx4OlLcYswEUo3bIyNAbQUIUR9Yr5/Hqjzqb4zA=="],
+
"statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
"std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
@@ -2454,6 +2575,8 @@
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
+ "style-mod": ["style-mod@4.1.3", "", {}, "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="],
+
"style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="],
"style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="],
@@ -2486,8 +2609,12 @@
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
+ "tinypool": ["tinypool@1.1.1", "", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="],
+
"tinyrainbow": ["tinyrainbow@3.0.3", "", {}, "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q=="],
+ "tinyspy": ["tinyspy@3.0.2", "", {}, "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q=="],
+
"tldts": ["tldts@7.0.18", "", { "dependencies": { "tldts-core": "^7.0.18" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-lCcgTAgMxQ1JKOWrVGo6E69Ukbnx4Gc1wiYLRf6J5NN4HRYJtCby1rPF8rkQ4a6qqoFBK5dvjJ1zJ0F7VfDSvw=="],
"tldts-core": ["tldts-core@7.0.18", "", {}, "sha512-jqJC13oP4FFAahv4JT/0WTDrCF9Okv7lpKtOZUGPLiAnNbACcSg8Y8T+Z9xthOmRBqi/Sob4yi0TE0miRCvF7Q=="],
@@ -2542,6 +2669,8 @@
"turbo-windows-arm64": ["turbo-windows-arm64@2.6.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-7w+AD5vJp3R+FB0YOj1YJcNcOOvBior7bcHTodqp90S3x3bLgpr7tE6xOea1e8JkP7GK6ciKVUpQvV7psiwU5Q=="],
+ "type": ["type@2.7.3", "", {}, "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="],
+
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
@@ -2634,12 +2763,16 @@
"vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="],
+ "vite-node": ["vite-node@2.1.9", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.3.7", "es-module-lexer": "^1.5.4", "pathe": "^1.1.2", "vite": "^5.0.0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA=="],
+
"vite-tsconfig-paths": ["vite-tsconfig-paths@5.1.4", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "tsconfck": "^3.0.3" }, "peerDependencies": { "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w=="],
"vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="],
"vitest": ["vitest@4.0.9", "", { "dependencies": { "@vitest/expect": "4.0.9", "@vitest/mocker": "4.0.9", "@vitest/pretty-format": "4.0.9", "@vitest/runner": "4.0.9", "@vitest/snapshot": "4.0.9", "@vitest/spy": "4.0.9", "@vitest/utils": "4.0.9", "debug": "^4.4.3", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.0.9", "@vitest/browser-preview": "4.0.9", "@vitest/browser-webdriverio": "4.0.9", "@vitest/ui": "4.0.9", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-E0Ja2AX4th+CG33yAFRC+d1wFx2pzU5r6HtG6LiPSE04flaE0qB6YyjSw9ZcpJAtVPfsvZGtJlKWZpuW7EHRxg=="],
+ "w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="],
+
"warning": ["warning@4.0.3", "", { "dependencies": { "loose-envify": "^1.0.0" } }, "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w=="],
"watchpack": ["watchpack@2.4.4", "", { "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" } }, "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA=="],
@@ -2744,6 +2877,10 @@
"@changesets/write/prettier": ["prettier@2.8.8", "", { "bin": { "prettier": "bin-prettier.js" } }, "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q=="],
+ "@codesandbox/nodebox/outvariant": ["outvariant@1.4.3", "", {}, "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA=="],
+
+ "@codesandbox/nodebox/strict-event-emitter": ["strict-event-emitter@0.4.6", "", {}, "sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg=="],
+
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
@@ -2776,6 +2913,10 @@
"@manypkg/get-packages/globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="],
+ "@mswjs/interceptors/outvariant": ["outvariant@1.4.3", "", {}, "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA=="],
+
+ "@open-draft/logger/outvariant": ["outvariant@1.4.3", "", {}, "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA=="],
+
"@opentelemetry/sdk-logs/@opentelemetry/core": ["@opentelemetry/core@2.0.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw=="],
"@opentelemetry/sdk-logs/@opentelemetry/resources": ["@opentelemetry/resources@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw=="],
@@ -2792,6 +2933,10 @@
"@tdi2/di-debug/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
+ "@tdi2/di-playground/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
+
+ "@tdi2/di-playground/vitest": ["vitest@2.1.9", "", { "dependencies": { "@vitest/expect": "2.1.9", "@vitest/mocker": "2.1.9", "@vitest/pretty-format": "^2.1.9", "@vitest/runner": "2.1.9", "@vitest/snapshot": "2.1.9", "@vitest/spy": "2.1.9", "@vitest/utils": "2.1.9", "chai": "^5.1.2", "debug": "^4.3.7", "expect-type": "^1.1.0", "magic-string": "^0.30.12", "pathe": "^1.1.2", "std-env": "^3.8.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.1", "tinypool": "^1.0.1", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", "vite-node": "2.1.9", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", "@vitest/browser": "2.1.9", "@vitest/ui": "2.1.9", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q=="],
+
"@tdi2/di-testing/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"@tdi2/logging/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
@@ -2856,6 +3001,8 @@
"@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
+ "@vitest/expect/chai": ["chai@6.2.1", "", {}, "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg=="],
+
"ajv-formats/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
"ajv-keywords/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
@@ -2934,6 +3081,8 @@
"msw/cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="],
+ "msw/outvariant": ["outvariant@1.4.3", "", {}, "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA=="],
+
"msw/path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="],
"msw/statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
@@ -2946,7 +3095,7 @@
"pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
- "pretty-format/react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
+ "prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
"raw-body/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
@@ -2966,6 +3115,10 @@
"source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
+ "static-browser-server/dotenv": ["dotenv@16.0.3", "", {}, "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ=="],
+
+ "static-browser-server/outvariant": ["outvariant@1.4.3", "", {}, "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA=="],
+
"string-width/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
"string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
@@ -2988,6 +3141,10 @@
"vite/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
+ "vite-node/pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="],
+
+ "vite-node/vite": ["vite@5.4.21", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="],
+
"webpack/eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="],
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
@@ -3046,6 +3203,26 @@
"@manypkg/get-packages/globby/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
+ "@tdi2/di-playground/vitest/@vitest/expect": ["@vitest/expect@2.1.9", "", { "dependencies": { "@vitest/spy": "2.1.9", "@vitest/utils": "2.1.9", "chai": "^5.1.2", "tinyrainbow": "^1.2.0" } }, "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw=="],
+
+ "@tdi2/di-playground/vitest/@vitest/mocker": ["@vitest/mocker@2.1.9", "", { "dependencies": { "@vitest/spy": "2.1.9", "estree-walker": "^3.0.3", "magic-string": "^0.30.12" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg=="],
+
+ "@tdi2/di-playground/vitest/@vitest/pretty-format": ["@vitest/pretty-format@2.1.9", "", { "dependencies": { "tinyrainbow": "^1.2.0" } }, "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ=="],
+
+ "@tdi2/di-playground/vitest/@vitest/runner": ["@vitest/runner@2.1.9", "", { "dependencies": { "@vitest/utils": "2.1.9", "pathe": "^1.1.2" } }, "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g=="],
+
+ "@tdi2/di-playground/vitest/@vitest/snapshot": ["@vitest/snapshot@2.1.9", "", { "dependencies": { "@vitest/pretty-format": "2.1.9", "magic-string": "^0.30.12", "pathe": "^1.1.2" } }, "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ=="],
+
+ "@tdi2/di-playground/vitest/@vitest/spy": ["@vitest/spy@2.1.9", "", { "dependencies": { "tinyspy": "^3.0.2" } }, "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ=="],
+
+ "@tdi2/di-playground/vitest/@vitest/utils": ["@vitest/utils@2.1.9", "", { "dependencies": { "@vitest/pretty-format": "2.1.9", "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" } }, "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ=="],
+
+ "@tdi2/di-playground/vitest/pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="],
+
+ "@tdi2/di-playground/vitest/tinyrainbow": ["tinyrainbow@1.2.0", "", {}, "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ=="],
+
+ "@tdi2/di-playground/vitest/vite": ["vite@5.4.21", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="],
+
"@tdi2/plugin-core/eslint/@eslint/eslintrc": ["@eslint/eslintrc@2.1.4", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ=="],
"@tdi2/plugin-core/eslint/@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="],
@@ -3394,6 +3571,8 @@
"typescript-eslint/@typescript-eslint/parser/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.46.4", "", { "dependencies": { "@typescript-eslint/types": "8.46.4", "eslint-visitor-keys": "^4.2.1" } }, "sha512-/++5CYLQqsO9HFGLI7APrxBJYo+5OCMpViuhV8q5/Qa3o5mMrF//eQHks+PXcsAVaLdn817fMuS7zqoXNNZGaw=="],
+ "vite-node/vite/esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="],
+
"vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
"vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
@@ -3460,6 +3639,8 @@
"@manypkg/get-packages/globby/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
+ "@tdi2/di-playground/vitest/vite/esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="],
+
"@tdi2/plugin-core/eslint/file-entry-cache/flat-cache": ["flat-cache@3.2.0", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw=="],
"@tdi2/plugin-core/eslint/globals/type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="],
@@ -3514,6 +3695,98 @@
"typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.46.4", "", {}, "sha512-USjyxm3gQEePdUwJBFjjGNG18xY9A2grDVGuk7/9AkjIF1L+ZrVnwR5VAU5JXtUnBL/Nwt3H31KlRDaksnM7/w=="],
+ "vite-node/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="],
+
+ "vite-node/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="],
+
+ "vite-node/vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.21.5", "", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="],
+
+ "vite-node/vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.21.5", "", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="],
+
+ "vite-node/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.21.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="],
+
+ "vite-node/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.21.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="],
+
+ "vite-node/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.21.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="],
+
+ "vite-node/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.21.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="],
+
+ "vite-node/vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.21.5", "", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="],
+
+ "vite-node/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.21.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="],
+
+ "vite-node/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.21.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="],
+
+ "vite-node/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="],
+
+ "vite-node/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="],
+
+ "vite-node/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.21.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="],
+
+ "vite-node/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="],
+
+ "vite-node/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.21.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="],
+
+ "vite-node/vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.21.5", "", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="],
+
+ "vite-node/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.21.5", "", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="],
+
+ "vite-node/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.21.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="],
+
+ "vite-node/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.21.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="],
+
+ "vite-node/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.21.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="],
+
+ "vite-node/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.21.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="],
+
+ "vite-node/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="],
+
+ "@tdi2/di-playground/vitest/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="],
+
+ "@tdi2/di-playground/vitest/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="],
+
+ "@tdi2/di-playground/vitest/vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.21.5", "", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="],
+
+ "@tdi2/di-playground/vitest/vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.21.5", "", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="],
+
+ "@tdi2/di-playground/vitest/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.21.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="],
+
+ "@tdi2/di-playground/vitest/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.21.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="],
+
+ "@tdi2/di-playground/vitest/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.21.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="],
+
+ "@tdi2/di-playground/vitest/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.21.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="],
+
+ "@tdi2/di-playground/vitest/vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.21.5", "", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="],
+
+ "@tdi2/di-playground/vitest/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.21.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="],
+
+ "@tdi2/di-playground/vitest/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.21.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="],
+
+ "@tdi2/di-playground/vitest/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="],
+
+ "@tdi2/di-playground/vitest/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="],
+
+ "@tdi2/di-playground/vitest/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.21.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="],
+
+ "@tdi2/di-playground/vitest/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="],
+
+ "@tdi2/di-playground/vitest/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.21.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="],
+
+ "@tdi2/di-playground/vitest/vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.21.5", "", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="],
+
+ "@tdi2/di-playground/vitest/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.21.5", "", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="],
+
+ "@tdi2/di-playground/vitest/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.21.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="],
+
+ "@tdi2/di-playground/vitest/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.21.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="],
+
+ "@tdi2/di-playground/vitest/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.21.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="],
+
+ "@tdi2/di-playground/vitest/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.21.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="],
+
+ "@tdi2/di-playground/vitest/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="],
+
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/globby/fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/globby/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
diff --git a/monorepo/package.json b/monorepo/package.json
index 322e94a7..e9568f0b 100644
--- a/monorepo/package.json
+++ b/monorepo/package.json
@@ -5,6 +5,7 @@
"build": "turbo run build",
"dev": "turbo run dev",
"test": "turbo run test",
+ "test:ci": "CI=true turbo run test",
"lint": "turbo run lint",
"check-types": "turbo run check-types",
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
@@ -16,8 +17,10 @@
"rm": "find . -maxdepth 3 -type d -name \"node_modules\" -exec rm -rf {} +",
"docs:dev": "cd apps/docs-starlight && bun run dev",
"docs:build": "cd apps/docs-starlight && bun run build",
- "docs:preview": "cd apps/docs-starlight && bun run preview"
-
+ "docs:preview": "cd apps/docs-starlight && bun run preview",
+ "playground:dev": "cd apps/di-playground && bun run dev",
+ "playground:build": "cd apps/di-playground && bun run build",
+ "playground:preview": "cd apps/di-playground && bun run preview"
},
"devDependencies": {
"@changesets/changelog-github": "^0.5.1",
diff --git a/monorepo/packages/di-core/tools/interface-resolver/integrated-interface-resolver.ts b/monorepo/packages/di-core/tools/interface-resolver/integrated-interface-resolver.ts
index 6f8b9f92..7cb566be 100644
--- a/monorepo/packages/di-core/tools/interface-resolver/integrated-interface-resolver.ts
+++ b/monorepo/packages/di-core/tools/interface-resolver/integrated-interface-resolver.ts
@@ -37,6 +37,7 @@ export interface IntegratedResolverOptions {
sourceConfig?: Partial;
excludePatterns?: string[];
outputDir?: string;
+ project?: Project; // Allow passing an existing project (for browser/custom use)
}
export class IntegratedInterfaceResolver {
@@ -65,9 +66,15 @@ export class IntegratedInterfaceResolver {
...options,
} as Required;
- this.project = new Project({
- tsConfigFilePath: "./tsconfig.json",
- });
+ // Use provided project or create a default one
+ if (options.project) {
+ this.project = options.project;
+ } else {
+ // Default: Node.js file system with tsconfig
+ this.project = new Project({
+ tsConfigFilePath: "./tsconfig.json",
+ });
+ }
// Initialize components with source configuration
this.keySanitizer = new KeySanitizer();
diff --git a/monorepo/packages/logging/package.json b/monorepo/packages/logging/package.json
index 7e0f0192..de8b079f 100644
--- a/monorepo/packages/logging/package.json
+++ b/monorepo/packages/logging/package.json
@@ -30,7 +30,7 @@
"scripts": {
"dev": "tsup --watch",
"build": "tsup",
- "test": "bun test",
+ "test": "echo 'No tests configured yet'",
"lint": "echo 'No linting configured yet'",
"check-types": "tsc --noEmit"
},
diff --git a/monorepo/packages/plugin-core/package.json b/monorepo/packages/plugin-core/package.json
index a612afc1..02085391 100644
--- a/monorepo/packages/plugin-core/package.json
+++ b/monorepo/packages/plugin-core/package.json
@@ -43,7 +43,7 @@
"build": "tsup",
"build:watch": "tsup --watch",
"dev": "tsup --watch",
- "test": "vitest",
+ "test": "echo 'No tests configured yet'",
"test:watch": "vitest --watch",
"type-check": "tsc --noEmit",
"lint": "eslint src --ext .ts",
diff --git a/monorepo/packages/plugin-esbuild-di/src/index.ts b/monorepo/packages/plugin-esbuild-di/src/index.ts
index 2d095acb..876c3ebd 100644
--- a/monorepo/packages/plugin-esbuild-di/src/index.ts
+++ b/monorepo/packages/plugin-esbuild-di/src/index.ts
@@ -14,6 +14,9 @@ import {
createPerformanceTracker,
type PluginConfig,
} from '@tdi2/plugin-core';
+import { consoleFor } from '@tdi2/di-core/tools';
+
+const console = consoleFor('plugin-esbuild-di');
export interface EsbuildPluginDIOptions extends PluginConfig {}
@@ -33,8 +36,7 @@ export interface EsbuildPluginDIOptions extends PluginConfig {}
* plugins: [
* tdi2Plugin({
* srcDir: './src',
- * enableFunctionalDI: true,
- * verbose: false
+ * enableFunctionalDI: true
* })
* ]
* });
@@ -54,10 +56,7 @@ export function tdi2Plugin(userOptions: EsbuildPluginDIOptions = {}): Plugin {
setup(build) {
// Initialize on build start
build.onStart(async () => {
- if (config.verbose) {
- console.log('๐ TDI2 esbuild Plugin: Starting build...');
- }
-
+ console.log('๐ TDI2 esbuild Plugin: Starting build...');
performanceTracker.startTransformation();
try {
@@ -69,10 +68,7 @@ export function tdi2Plugin(userOptions: EsbuildPluginDIOptions = {}): Plugin {
await orchestrator.initialize();
initialized = true;
performanceTracker.recordCacheHit();
-
- if (config.verbose) {
- console.log('โ
TDI2 esbuild Plugin: Initialization complete');
- }
+ console.log('โ
TDI2 esbuild Plugin: Initialization complete');
} catch (error) {
performanceTracker.recordError();
console.error('โ TDI2 initialization failed:', error);
@@ -108,10 +104,7 @@ export function tdi2Plugin(userOptions: EsbuildPluginDIOptions = {}): Plugin {
if (result.wasTransformed) {
performanceTracker.recordCacheHit();
-
- if (config.verbose) {
- console.log(`๐ Transformed: ${args.path}`);
- }
+ console.debug(`๐ Transformed: ${args.path}`);
return {
contents: result.code,
@@ -130,7 +123,7 @@ export function tdi2Plugin(userOptions: EsbuildPluginDIOptions = {}): Plugin {
// Report statistics on build end
build.onEnd(() => {
- if (config.verbose && orchestrator) {
+ if (orchestrator) {
console.log('\n๐ TDI2 esbuild Plugin Statistics:');
console.log(` Transformed files: ${orchestrator.getTransformedFileCount()}`);
console.log(performanceTracker.formatStats());
diff --git a/monorepo/packages/plugin-rollup-di/src/index.ts b/monorepo/packages/plugin-rollup-di/src/index.ts
index 8f8d8b45..175d5d74 100644
--- a/monorepo/packages/plugin-rollup-di/src/index.ts
+++ b/monorepo/packages/plugin-rollup-di/src/index.ts
@@ -13,6 +13,9 @@ import {
createPerformanceTracker,
type PluginConfig,
} from '@tdi2/plugin-core';
+import { consoleFor } from '@tdi2/di-core/tools';
+
+const console = consoleFor('plugin-rollup-di');
export interface RollupPluginDIOptions extends PluginConfig {}
@@ -29,8 +32,7 @@ export interface RollupPluginDIOptions extends PluginConfig {}
* plugins: [
* tdi2Plugin({
* srcDir: './src',
- * enableFunctionalDI: true,
- * verbose: false
+ * enableFunctionalDI: true
* })
* ]
* };
@@ -47,10 +49,7 @@ export function tdi2Plugin(userOptions: RollupPluginDIOptions = {}): Plugin {
name: 'tdi2-rollup-plugin',
async buildStart() {
- if (config.verbose) {
- console.log('๐ TDI2 Rollup Plugin: Starting build...');
- }
-
+ console.log('๐ TDI2 Rollup Plugin: Starting build...');
performanceTracker.startTransformation();
// Initialize orchestrator
@@ -69,10 +68,7 @@ export function tdi2Plugin(userOptions: RollupPluginDIOptions = {}): Plugin {
}
performanceTracker.endTransformation();
-
- if (config.verbose) {
- console.log('โ
TDI2 Rollup Plugin: Initialization complete');
- }
+ console.log('โ
TDI2 Rollup Plugin: Initialization complete');
},
load(id: string) {
@@ -85,9 +81,7 @@ export function tdi2Plugin(userOptions: RollupPluginDIOptions = {}): Plugin {
const transformedCode = orchestrator?.getTransformedContent(id);
if (transformedCode) {
- if (config.verbose) {
- console.log(`[Rollup] ๐ Using transformed version of ${id}`);
- }
+ console.debug(`[Rollup] ๐ Using transformed version of ${id}`);
return transformedCode;
}
@@ -121,11 +115,9 @@ export function tdi2Plugin(userOptions: RollupPluginDIOptions = {}): Plugin {
},
buildEnd() {
- if (config.verbose) {
- console.log('\n๐ TDI2 Rollup Plugin Statistics:');
- console.log(` Transformed files: ${orchestrator?.getTransformedFileCount() ?? 0}`);
- console.log(performanceTracker.formatStats());
- }
+ console.log('\n๐ TDI2 Rollup Plugin Statistics:');
+ console.log(` Transformed files: ${orchestrator?.getTransformedFileCount() ?? 0}`);
+ console.log(performanceTracker.formatStats());
},
};
}
diff --git a/monorepo/packages/plugin-webpack-di/src/index.ts b/monorepo/packages/plugin-webpack-di/src/index.ts
index 98a86624..fb744d73 100644
--- a/monorepo/packages/plugin-webpack-di/src/index.ts
+++ b/monorepo/packages/plugin-webpack-di/src/index.ts
@@ -13,6 +13,9 @@ import {
createPerformanceTracker,
type PluginConfig,
} from '@tdi2/plugin-core';
+import { consoleFor } from '@tdi2/di-core/tools';
+
+const console = consoleFor('plugin-webpack-di');
export interface WebpackPluginDIOptions extends PluginConfig {}
@@ -29,8 +32,7 @@ export interface WebpackPluginDIOptions extends PluginConfig {}
* plugins: [
* new TDI2WebpackPlugin({
* srcDir: './src',
- * enableFunctionalDI: true,
- * verbose: false
+ * enableFunctionalDI: true
* })
* ]
* };
@@ -51,10 +53,7 @@ export class TDI2WebpackPlugin {
// Initialize on compilation start
compiler.hooks.beforeCompile.tapPromise(pluginName, async () => {
- if (this.config.verbose) {
- console.log('๐ TDI2 Webpack Plugin: Starting compilation...');
- }
-
+ console.log('๐ TDI2 Webpack Plugin: Starting compilation...');
this.performanceTracker.startTransformation();
try {
@@ -65,10 +64,7 @@ export class TDI2WebpackPlugin {
await this.orchestrator.initialize();
this.performanceTracker.recordCacheHit();
-
- if (this.config.verbose) {
- console.log('โ
TDI2 Webpack Plugin: Initialization complete');
- }
+ console.log('โ
TDI2 Webpack Plugin: Initialization complete');
} catch (error) {
this.performanceTracker.recordError();
console.error('โ TDI2 initialization failed:', error);
@@ -101,10 +97,7 @@ export class TDI2WebpackPlugin {
module._source = new webpack.sources.RawSource(transformedCode);
this.performanceTracker.recordCacheHit();
-
- if (this.config.verbose) {
- console.log(`๐ Applied transformation: ${filePath}`);
- }
+ console.debug(`๐ Applied transformation: ${filePath}`);
} else {
this.performanceTracker.recordCacheMiss();
}
@@ -113,7 +106,7 @@ export class TDI2WebpackPlugin {
// Report statistics on done
compiler.hooks.done.tap(pluginName, () => {
- if (this.config.verbose && this.orchestrator) {
+ if (this.orchestrator) {
console.log('\n๐ TDI2 Webpack Plugin Statistics:');
console.log(` Transformed files: ${this.orchestrator.getTransformedFileCount()}`);
console.log(this.performanceTracker.formatStats());
diff --git a/monorepo/packages/vite-plugin-di/e2e/helpers.ts b/monorepo/packages/vite-plugin-di/e2e/helpers.ts
index 56ec9543..4baf7b7b 100644
--- a/monorepo/packages/vite-plugin-di/e2e/helpers.ts
+++ b/monorepo/packages/vite-plugin-di/e2e/helpers.ts
@@ -1,7 +1,7 @@
-import { Page } from '@playwright/test';
-import { createServer, ViteDevServer } from 'vite';
-import { promises as fsPromises } from 'fs';
-import * as pathModule from 'path';
+import { Page } from "@playwright/test";
+import { createServer, ViteDevServer } from "vite";
+import { promises as fsPromises } from "fs";
+import * as pathModule from "path";
// Create fs and path references
const fs = fsPromises;
@@ -54,13 +54,15 @@ async function removeDir(dir: string): Promise {
/**
* Start a Vite dev server programmatically
*/
-export async function startDevServer(testAppDir: string): Promise<{ server: ViteDevServer; port: number }> {
+export async function startDevServer(
+ testAppDir: string
+): Promise<{ server: ViteDevServer; port: number }> {
// Use random port to avoid conflicts when tests run in parallel
const port = 3000 + Math.floor(Math.random() * 1000);
const server = await createServer({
root: testAppDir,
- configFile: path.join(testAppDir, 'vite.config.ts'),
+ configFile: path.join(testAppDir, "vite.config.ts"),
server: {
port,
strictPort: false, // Allow fallback to another port
@@ -68,7 +70,7 @@ export async function startDevServer(testAppDir: string): Promise<{ server: Vite
port,
},
},
- logLevel: 'silent', // Suppress all Vite logs during tests
+ logLevel: "silent", // Suppress all Vite logs during tests
clearScreen: false,
});
@@ -78,7 +80,7 @@ export async function startDevServer(testAppDir: string): Promise<{ server: Vite
const actualPort = (server.config.server.port || port) as number;
// Give the plugin time to generate di-config
- await new Promise(resolve => setTimeout(resolve, 2000));
+ await new Promise((resolve) => setTimeout(resolve, 2000));
return { server, port: actualPort };
}
@@ -94,7 +96,7 @@ export async function stopDevServer(server: ViteDevServer): Promise {
* Copy the fixture test app to a temporary directory and set up dependencies
*/
export async function setupTestApp(tempDir: string): Promise {
- const fixtureDir = path.join(__dirname, 'fixtures', 'test-app');
+ const fixtureDir = path.join(__dirname, "fixtures", "test-app");
// Clean temp directory if it exists
if (await pathExists(tempDir)) {
@@ -106,9 +108,9 @@ export async function setupTestApp(tempDir: string): Promise {
// Symlink node_modules from monorepo root to temp directory
// This gives access to all workspace packages
- const monorepoRoot = path.resolve(__dirname, '../../..');
- const monorepoNodeModules = path.join(monorepoRoot, 'node_modules');
- const tempNodeModules = path.join(tempDir, 'node_modules');
+ const monorepoRoot = path.resolve(__dirname, "../../..");
+ const monorepoNodeModules = path.join(monorepoRoot, "node_modules");
+ const tempNodeModules = path.join(tempDir, "node_modules");
// Create symlink to monorepo node_modules
if (await pathExists(monorepoNodeModules)) {
@@ -121,7 +123,7 @@ export async function setupTestApp(tempDir: string): Promise {
await removeDir(tempNodeModules);
}
}
- await fs.symlink(monorepoNodeModules, tempNodeModules, 'dir');
+ await fs.symlink(monorepoNodeModules, tempNodeModules, "dir");
}
}
@@ -144,7 +146,7 @@ export async function replaceFile(
await fs.copyFile(sourcePath, targetPath);
// Delay to ensure file system change is detected (longer for parallel tests)
- await new Promise(resolve => setTimeout(resolve, 300));
+ await new Promise((resolve) => setTimeout(resolve, 300));
}
/**
@@ -155,12 +157,12 @@ export async function modifyFile(
find: string,
replace: string
): Promise {
- const content = await fs.readFile(filePath, 'utf-8');
+ const content = await fs.readFile(filePath, "utf-8");
const newContent = content.replace(find, replace);
- await fs.writeFile(filePath, newContent, 'utf-8');
+ await fs.writeFile(filePath, newContent, "utf-8");
// Small delay to ensure file system change is detected
- await new Promise(resolve => setTimeout(resolve, 100));
+ await new Promise((resolve) => setTimeout(resolve, 100));
}
/**
@@ -170,10 +172,10 @@ export async function appendToFile(
filePath: string,
content: string
): Promise {
- await fs.appendFile(filePath, content, 'utf-8');
+ await fs.appendFile(filePath, content, "utf-8");
// Small delay to ensure file system change is detected
- await new Promise(resolve => setTimeout(resolve, 100));
+ await new Promise((resolve) => setTimeout(resolve, 100));
}
/**
@@ -186,7 +188,7 @@ export async function addFile(
await fs.copyFile(sourcePath, targetPath);
// Small delay to ensure file system change is detected
- await new Promise(resolve => setTimeout(resolve, 100));
+ await new Promise((resolve) => setTimeout(resolve, 100));
}
/**
@@ -194,8 +196,10 @@ export async function addFile(
*/
export async function waitForHMR(page: Page, timeout = 5000): Promise {
try {
- await page.waitForEvent('console', {
- predicate: msg => msg.text().includes('[vite] hmr update') || msg.text().includes('[vite] hot updated'),
+ await page.waitForEvent("console", {
+ predicate: (msg) =>
+ msg.text().includes("[vite] hmr update") ||
+ msg.text().includes("[vite] hot updated"),
timeout,
});
@@ -210,31 +214,40 @@ export async function waitForHMR(page: Page, timeout = 5000): Promise {
/**
* Wait for full page reload
*/
-export async function waitForFullReload(page: Page, timeout = 5000): Promise {
+export async function waitForFullReload(
+ page: Page,
+ timeout = 5000
+): Promise {
try {
- await page.waitForEvent('console', {
- predicate: msg =>
- msg.text().includes('[vite] page reload') ||
- msg.text().includes('full-reload'),
+ await page.waitForEvent("console", {
+ predicate: (msg) =>
+ msg.text().includes("[vite] page reload") ||
+ msg.text().includes("full-reload"),
timeout,
});
// Wait for page to reload
- await page.waitForLoadState('networkidle');
+ await page.waitForLoadState("networkidle");
} catch (error) {
// Fallback: just wait for network idle
- await page.waitForLoadState('networkidle');
+ await page.waitForLoadState("networkidle");
}
}
/**
* Inject a marker into window object to detect full reloads
*/
-export async function injectReloadMarker(page: Page, markerName = '__hmr_test_id'): Promise {
+export async function injectReloadMarker(
+ page: Page,
+ markerName = "__hmr_test_id"
+): Promise {
const markerId = Math.random().toString(36).substring(7);
- await page.evaluate(({ name, id }) => {
- (window as any)[name] = id;
- }, { name: markerName, id: markerId });
+ await page.evaluate(
+ ({ name, id }) => {
+ (window as any)[name] = id;
+ },
+ { name: markerName, id: markerId }
+ );
return markerId;
}
@@ -245,11 +258,14 @@ export async function injectReloadMarker(page: Page, markerName = '__hmr_test_id
export async function expectNoFullReload(
page: Page,
markerId: string,
- markerName = '__hmr_test_id'
+ markerName = "__hmr_test_id"
): Promise {
- const currentId = await page.evaluate(({ name }) => {
- return (window as any)[name];
- }, { name: markerName });
+ const currentId = await page.evaluate(
+ ({ name }) => {
+ return (window as any)[name];
+ },
+ { name: markerName }
+ );
return currentId === markerId;
}
@@ -259,14 +275,16 @@ export async function expectNoFullReload(
*/
async function findDiConfig(testAppDir: string): Promise {
// Resolve the actual node_modules path (might be a symlink)
- const nodeModulesPath = path.join(testAppDir, 'node_modules');
+ const nodeModulesPath = path.join(testAppDir, "node_modules");
let resolvedNodeModules = nodeModulesPath;
try {
const stats = await fs.lstat(nodeModulesPath);
if (stats.isSymbolicLink()) {
resolvedNodeModules = await fs.readlink(nodeModulesPath);
- console.log(`[findDiConfig] node_modules is a symlink to: ${resolvedNodeModules}`);
+ console.log(
+ `[findDiConfig] node_modules is a symlink to: ${resolvedNodeModules}`
+ );
}
} catch (err) {
console.log(`[findDiConfig] Error checking node_modules symlink:`, err);
@@ -274,21 +292,21 @@ async function findDiConfig(testAppDir: string): Promise {
// Possible locations for di-config
const possibleLocations = [
- path.join(testAppDir, 'node_modules', '.tdi2', 'configs'),
- path.join(resolvedNodeModules, '.tdi2', 'configs'),
- path.join(testAppDir, '.tdi2'),
+ path.join(testAppDir, "node_modules", ".tdi2", "configs"),
+ path.join(resolvedNodeModules, ".tdi2", "configs"),
+ path.join(testAppDir, ".tdi2"),
];
for (const location of possibleLocations) {
console.log(`[findDiConfig] Checking location: ${location}`);
- if (!await pathExists(location)) {
+ if (!(await pathExists(location))) {
console.log(`[findDiConfig] Location does not exist: ${location}`);
continue;
}
// Check if di-config.ts is directly in this directory
- const directConfigPath = path.join(location, 'di-config.ts');
+ const directConfigPath = path.join(location, "di-config.ts");
if (await pathExists(directConfigPath)) {
console.log(`[findDiConfig] Found di-config.ts at: ${directConfigPath}`);
return directConfigPath;
@@ -297,12 +315,17 @@ async function findDiConfig(testAppDir: string): Promise {
// Check if there are subdirectories (configs/* pattern)
try {
const entries = await fs.readdir(location, { withFileTypes: true });
- const dirs = entries.filter(e => e.isDirectory());
- console.log(`[findDiConfig] Found subdirs in ${location}:`, dirs.map(d => d.name));
+ const dirs = entries.filter((e) => e.isDirectory());
+ console.log(
+ `[findDiConfig] Found subdirs in ${location}:`,
+ dirs.map((d) => d.name)
+ );
for (const dir of dirs) {
- const diConfigPath = path.join(location, dir.name, 'di-config.ts');
- console.log(`[findDiConfig] Checking for di-config at: ${diConfigPath}`);
+ const diConfigPath = path.join(location, dir.name, "di-config.ts");
+ console.log(
+ `[findDiConfig] Checking for di-config at: ${diConfigPath}`
+ );
if (await pathExists(diConfigPath)) {
console.log(`[findDiConfig] Found di-config.ts at: ${diConfigPath}`);
return diConfigPath;
@@ -328,7 +351,7 @@ export async function expectServiceRegistered(
try {
// If configPath doesn't end with .ts, treat it as test app directory
let actualConfigPath = configPath;
- if (!configPath.endsWith('.ts')) {
+ if (!configPath.endsWith(".ts")) {
const foundPath = await findDiConfig(configPath);
if (!foundPath) {
return false;
@@ -336,7 +359,7 @@ export async function expectServiceRegistered(
actualConfigPath = foundPath;
}
- const content = await fs.readFile(actualConfigPath, 'utf-8');
+ const content = await fs.readFile(actualConfigPath, "utf-8");
return content.includes(serviceName);
} catch {
return false;
@@ -349,7 +372,7 @@ export async function expectServiceRegistered(
export function captureConsoleLogs(page: Page): string[] {
const logs: string[] = [];
- page.on('console', msg => {
+ page.on("console", (msg) => {
logs.push(msg.text());
});
@@ -390,29 +413,31 @@ export async function expectTextContent(
export async function waitForAppReady(page: Page): Promise {
// Capture console errors
const errors: string[] = [];
- page.on('pageerror', err => {
+ page.on("pageerror", (err) => {
errors.push(err.message);
- console.error('Browser error:', err.message);
+ console.error("waitForAppReady: Browser error:", err.message);
});
- page.on('console', msg => {
- if (msg.type() === 'error') {
- console.error('Browser console error:', msg.text());
+ page.on("console", (msg) => {
+ if (msg.type() === "error") {
+ console.error("waitForAppReady: Browser console error:", msg.text());
}
});
// Wait for React root to render
try {
- await page.waitForSelector('#root > *', { timeout: 10000 });
+ await page.waitForSelector("#root > *", { timeout: 10000 });
} catch (error) {
- console.error('Failed to find #root > *, page errors:', errors);
+ console.error("Failed to find #root > *, page errors:", errors);
// Check if there's anything in the root at all
- const rootHTML = await page.$eval('#root', el => el.innerHTML).catch(() => 'Could not read root');
- console.error('Root HTML:', rootHTML);
+ const rootHTML = await page
+ .$eval("#root", (el) => el.innerHTML)
+ .catch(() => "Could not read root");
+ console.error("Root HTML:", rootHTML);
throw error;
}
// Wait for network to be idle
- await page.waitForLoadState('networkidle');
+ await page.waitForLoadState("networkidle");
// Small additional delay for DI initialization
await page.waitForTimeout(500);
diff --git a/monorepo/packages/vite-plugin-di/package.json b/monorepo/packages/vite-plugin-di/package.json
index d76c121c..2fed8d93 100644
--- a/monorepo/packages/vite-plugin-di/package.json
+++ b/monorepo/packages/vite-plugin-di/package.json
@@ -43,7 +43,8 @@
"build": "tsup",
"build:watch": "tsup --watch",
"dev": "tsup --watch",
- "test": "vitest",
+ "test":"CI=true bun run test:unit && bun run test:e2e",
+ "test:unit": "vitest",
"test:watch": "vitest --watch",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
diff --git a/monorepo/packages/vite-plugin-di/src/types.ts b/monorepo/packages/vite-plugin-di/src/types.ts
index eed833d3..4c702b18 100644
--- a/monorepo/packages/vite-plugin-di/src/types.ts
+++ b/monorepo/packages/vite-plugin-di/src/types.ts
@@ -28,6 +28,7 @@ export interface DIPluginOptions extends BasePluginConfig {
* @default true
*/
reuseExistingConfig?: boolean;
+
}
export interface TransformationSummary {
diff --git a/monorepo/turbo.json b/monorepo/turbo.json
index 6765465d..a7db7f39 100644
--- a/monorepo/turbo.json
+++ b/monorepo/turbo.json
@@ -18,7 +18,7 @@
"persistent": true
},
"test": {
- "dependsOn": ["^test"]
+ "cache": true
},
"publish:package": {
"dependsOn": ["build"],