Skip to content

Commit bd0fb7e

Browse files
chore: wip
1 parent 99f7ed7 commit bd0fb7e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+2473
-852
lines changed

.github/workflows/benchmark.yml

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
name: Benchmark
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
paths:
8+
- 'packages/dtsx/src/**'
9+
- 'packages/dtsx/benchmark.ts'
10+
11+
pull_request:
12+
branches:
13+
- main
14+
paths:
15+
- 'packages/dtsx/src/**'
16+
- 'packages/dtsx/benchmark.ts'
17+
18+
workflow_dispatch:
19+
inputs:
20+
full_benchmark:
21+
description: 'Run full benchmark (more iterations)'
22+
required: false
23+
default: 'false'
24+
type: boolean
25+
26+
concurrency:
27+
group: benchmark-${{ github.head_ref || github.run_id }}
28+
cancel-in-progress: true
29+
30+
jobs:
31+
benchmark:
32+
runs-on: ubuntu-latest
33+
permissions:
34+
contents: read
35+
pull-requests: write
36+
37+
steps:
38+
- uses: actions/checkout@v6.0.0
39+
40+
- name: Install Bun
41+
uses: oven-sh/setup-bun@v2.0.2
42+
43+
- name: Use cached node_modules
44+
uses: actions/cache@v4.3.0
45+
with:
46+
path: node_modules
47+
key: node-modules-${{ hashFiles('**/bun.lock') }}
48+
restore-keys: |
49+
node-modules-
50+
51+
- name: Install Dependencies
52+
run: bun install
53+
54+
- name: Build
55+
run: bun run build
56+
working-directory: packages/dtsx
57+
58+
- name: Run Benchmarks
59+
id: benchmark
60+
run: |
61+
if [ "${{ github.event.inputs.full_benchmark }}" == "true" ]; then
62+
bun run benchmark.ts --json --output=benchmark-results.json
63+
else
64+
bun run benchmark.ts --ci --json --output=benchmark-results.json
65+
fi
66+
working-directory: packages/dtsx
67+
68+
- name: Upload Benchmark Results
69+
uses: actions/upload-artifact@v4
70+
with:
71+
name: benchmark-results-${{ github.sha }}
72+
path: packages/dtsx/benchmark-results.json
73+
retention-days: 30
74+
75+
- name: Download Base Benchmark (for PR comparison)
76+
if: github.event_name == 'pull_request'
77+
uses: dawidd6/action-download-artifact@v6
78+
with:
79+
workflow: benchmark.yml
80+
branch: main
81+
name: benchmark-results-*
82+
path: base-benchmark
83+
if_no_artifact_found: warn
84+
continue-on-error: true
85+
86+
- name: Compare Benchmarks
87+
if: github.event_name == 'pull_request'
88+
id: compare
89+
run: |
90+
if [ -f "base-benchmark/benchmark-results.json" ]; then
91+
node -e "
92+
const fs = require('fs');
93+
const base = JSON.parse(fs.readFileSync('base-benchmark/benchmark-results.json', 'utf8'));
94+
const current = JSON.parse(fs.readFileSync('packages/dtsx/benchmark-results.json', 'utf8'));
95+
96+
const baseAvg = base.summary.avgTimeMs;
97+
const currentAvg = current.summary.avgTimeMs;
98+
const diff = ((currentAvg - baseAvg) / baseAvg * 100).toFixed(2);
99+
const emoji = diff > 5 ? '🔴' : diff < -5 ? '🟢' : '🟡';
100+
101+
let comment = '## Benchmark Results\\n\\n';
102+
comment += '| Metric | Base | Current | Change |\\n';
103+
comment += '|--------|------|---------|--------|\\n';
104+
comment += '| Avg Time | ' + baseAvg.toFixed(2) + 'ms | ' + currentAvg.toFixed(2) + 'ms | ' + emoji + ' ' + diff + '% |\\n';
105+
comment += '| Total Benchmarks | ' + base.summary.totalBenchmarks + ' | ' + current.summary.totalBenchmarks + ' | |\\n';
106+
comment += '\\n';
107+
108+
// Detail by suite
109+
comment += '### Suite Details\\n\\n';
110+
for (const suite of current.suites) {
111+
const baseSuite = base.suites.find(s => s.name === suite.name);
112+
if (baseSuite) {
113+
comment += '**' + suite.name + '**\\n';
114+
comment += '| Benchmark | Base | Current | Change |\\n';
115+
comment += '|-----------|------|---------|--------|\\n';
116+
for (const result of suite.results) {
117+
const baseResult = baseSuite.results.find(r => r.name === result.name);
118+
if (baseResult) {
119+
const d = ((result.avgTimeMs - baseResult.avgTimeMs) / baseResult.avgTimeMs * 100).toFixed(2);
120+
const e = d > 10 ? '🔴' : d < -10 ? '🟢' : '';
121+
comment += '| ' + result.name + ' | ' + baseResult.avgTimeMs.toFixed(2) + 'ms | ' + result.avgTimeMs.toFixed(2) + 'ms | ' + e + ' ' + d + '% |\\n';
122+
}
123+
}
124+
comment += '\\n';
125+
}
126+
}
127+
128+
fs.writeFileSync('benchmark-comment.md', comment);
129+
console.log('Benchmark comparison generated');
130+
"
131+
echo "has_comparison=true" >> $GITHUB_OUTPUT
132+
else
133+
echo "No base benchmark found for comparison"
134+
echo "has_comparison=false" >> $GITHUB_OUTPUT
135+
fi
136+
continue-on-error: true
137+
138+
- name: Comment on PR
139+
if: github.event_name == 'pull_request' && steps.compare.outputs.has_comparison == 'true'
140+
uses: actions/github-script@v7
141+
with:
142+
script: |
143+
const fs = require('fs');
144+
const comment = fs.readFileSync('benchmark-comment.md', 'utf8');
145+
146+
// Find existing benchmark comment
147+
const { data: comments } = await github.rest.issues.listComments({
148+
owner: context.repo.owner,
149+
repo: context.repo.repo,
150+
issue_number: context.issue.number,
151+
});
152+
153+
const botComment = comments.find(c =>
154+
c.user.type === 'Bot' &&
155+
c.body.includes('## Benchmark Results')
156+
);
157+
158+
if (botComment) {
159+
await github.rest.issues.updateComment({
160+
owner: context.repo.owner,
161+
repo: context.repo.repo,
162+
comment_id: botComment.id,
163+
body: comment,
164+
});
165+
} else {
166+
await github.rest.issues.createComment({
167+
owner: context.repo.owner,
168+
repo: context.repo.repo,
169+
issue_number: context.issue.number,
170+
body: comment,
171+
});
172+
}
173+
continue-on-error: true
174+
175+
- name: Check for Performance Regression
176+
run: |
177+
node -e "
178+
const fs = require('fs');
179+
const results = JSON.parse(fs.readFileSync('packages/dtsx/benchmark-results.json', 'utf8'));
180+
181+
// Define performance thresholds (in ms)
182+
const thresholds = {
183+
'Simple (0001.ts)': 5,
184+
'Medium (0002.ts)': 10,
185+
'Complex (0003.ts)': 20,
186+
'Very Complex (0005.ts)': 50,
187+
};
188+
189+
let failed = false;
190+
for (const suite of results.suites) {
191+
for (const result of suite.results) {
192+
const threshold = thresholds[result.name];
193+
if (threshold && result.avgTimeMs > threshold) {
194+
console.log('⚠️ Performance threshold exceeded: ' + result.name);
195+
console.log(' Expected: <' + threshold + 'ms, Actual: ' + result.avgTimeMs.toFixed(2) + 'ms');
196+
// Don't fail yet - just warn
197+
}
198+
}
199+
}
200+
201+
console.log('✅ Benchmark completed successfully');
202+
console.log('Summary: ' + results.summary.totalBenchmarks + ' benchmarks, avg ' + results.summary.avgTimeMs.toFixed(2) + 'ms');
203+
"

TODO.md

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,13 @@
282282
283283
- [x] **Error case testing** - ✅ Test malformed input handling covered in `test/errors.test.ts` (52 tests).
284284
285-
- [ ] **Performance regression tests** - Add benchmarks to CI.
285+
- [x] **Performance regression tests** - ✅ Added `.github/workflows/benchmark.yml`:
286+
- Runs on push/PR to main affecting src or benchmark files
287+
- CI mode with `--ci` flag (20 iterations for meaningful results)
288+
- JSON output with `--json` flag for machine parsing
289+
- Stores results as artifacts for comparison
290+
- PR comments with performance comparison
291+
- Performance threshold checks
286292
287293
---
288294
@@ -462,7 +468,7 @@ Based on code analysis, these are the likely bottlenecks:
462468
463469
- [x] Incremental builds ✅ Implemented in `src/cache.ts` with content hashing and mtime tracking
464470
- [x] Watch mode ✅ Implemented `dtsx watch` command
465-
- [ ] Memory optimization
471+
- [x] Memory optimization ✅ Implemented in `src/memory.ts` with StreamingProcessor, DeclarationPool, LazyLoader, StringInterner, ObjectPool
466472
467473
### v1.2 (DX)
468474
@@ -656,7 +662,11 @@ Based on test fixtures analysis:
656662
- Main export: `{ types: "./dist/index.d.ts", import: "./dist/src/index.js" }`
657663
- Subpath exports: `{ types: "./dist/*.d.ts", import: "./dist/*" }`
658664
659-
- [ ] **Peer dependencies** - Consider making `typescript` a peer dependency.
665+
- [x] **Peer dependencies** - ✅ Added TypeScript as peer dependency in `package.json`:
666+
- `peerDependencies: { "typescript": ">=5.0.0" }`
667+
- Required because dtsx uses TypeScript's compiler API (createSourceFile, SyntaxKind, etc.)
668+
- Allows users to control TypeScript version in their project
669+
- Also added as devDependency for local development
660670
661671
- [x] **Bundle size** - Analyzed and documented. ✅ Bundle is ~4.1MB due to TypeScript compiler (~3.5MB). This is unavoidable for AST-based parsing.
662672
- TypeScript compiler is required for core AST parsing functionality
@@ -711,7 +721,7 @@ Based on test fixtures analysis:
711721
712722
- [x] **CLI tests** - No integration tests for CLI commands. ✅ Added `test/cli.test.ts` with 21 tests
713723
714-
- [ ] **Benchmark regression** - No CI integration for performance benchmarks.
724+
- [x] **Benchmark regression** - CI integration added in `.github/workflows/benchmark.yml`.
715725
716726
---
717727
@@ -732,7 +742,14 @@ Based on test fixtures analysis:
732742
733743
- [x] **File batching** - Group small files for batch processing to reduce overhead. ✅ `batchFiles()` and `calculateOptimalBatchSize()` in `src/worker.ts`
734744
735-
- [ ] **Dependency graph parallelism** - Build dependency graph and process independent files in parallel.
745+
- [x] **Dependency graph parallelism** - ✅ Added `src/parallel-processor.ts`:
746+
- `buildProcessingGraph()` - Build dependency graph from source files
747+
- `processInParallel()` - Process files respecting dependencies
748+
- `getTopologicalOrder()` - Get safe processing order
749+
- `getProcessingLevels()` - Group files by parallelization level
750+
- `findIndependentFiles()` - Find files with no internal dependencies
751+
- `analyzeParallelizationPotential()` - Analyze speedup potential
752+
- Added 17 tests in `test/parallel-processor.test.ts`
736753
737754
---
738755
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
21
export const x: number = 1

packages/dtsx/.test-cli/check-test/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@
88
"include": [
99
"src/**/*.ts"
1010
]
11-
}
11+
}

packages/dtsx/.test-cli/convert-test/src/zod-types.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
export interface Config {
32
host: string
43
port: number

packages/dtsx/.test-cli/docs-test/api-docs/API.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# API Documentation
22

3-
> Generated on 2025-11-27T13:22:23.230Z
3+
> Generated on 2025-11-27T13:47:18.370Z
44
55
## Table of Contents
66

@@ -12,7 +12,7 @@
1212
### greet
1313

1414
```typescript
15-
export function greet (name: string) : string
15+
export function greet(name: string): string
1616
```
1717

1818
A well-documented function

packages/dtsx/.test-cli/docs-test/src/documented.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
/**
32
* A well-documented function
43
* @param name - The name to greet
Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +0,0 @@
1-
import { bar } from './bar'
2-
3-
import { foo } from './foo'

packages/dtsx/.test-cli/workspace-test/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
"compilerOptions": {
33
"target": "ESNext"
44
}
5-
}
5+
}

0 commit comments

Comments
 (0)