@principal-ai/codebase-composition 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/DependencyLayerModule.d.ts +44 -0
- package/dist/modules/DependencyLayerModule.d.ts.map +1 -0
- package/dist/modules/DependencyLayerModule.js +286 -0
- package/dist/modules/DependencyLayerModule.js.map +1 -0
- package/dist/modules/FileSystemModule.d.ts +30 -0
- package/dist/modules/FileSystemModule.d.ts.map +1 -0
- package/dist/modules/FileSystemModule.js +46 -0
- package/dist/modules/FileSystemModule.js.map +1 -0
- package/dist/modules/FileTypeLayerModule.d.ts +34 -0
- package/dist/modules/FileTypeLayerModule.d.ts.map +1 -0
- package/dist/modules/FileTypeLayerModule.js +169 -0
- package/dist/modules/FileTypeLayerModule.js.map +1 -0
- package/dist/modules/FrameworkLayerModule.d.ts +22 -0
- package/dist/modules/FrameworkLayerModule.d.ts.map +1 -0
- package/dist/modules/FrameworkLayerModule.js +388 -0
- package/dist/modules/FrameworkLayerModule.js.map +1 -0
- package/dist/modules/PackageLayerModule.d.ts +23 -0
- package/dist/modules/PackageLayerModule.d.ts.map +1 -0
- package/dist/modules/PackageLayerModule.js +810 -0
- package/dist/modules/PackageLayerModule.js.map +1 -0
- package/dist/modules/TypeExtractionModule.d.ts +37 -0
- package/dist/modules/TypeExtractionModule.d.ts.map +1 -0
- package/dist/modules/TypeExtractionModule.js +180 -0
- package/dist/modules/TypeExtractionModule.js.map +1 -0
- package/dist/modules/VersionControlLayerModule.d.ts +10 -0
- package/dist/modules/VersionControlLayerModule.d.ts.map +1 -0
- package/dist/modules/VersionControlLayerModule.js +32 -0
- package/dist/modules/VersionControlLayerModule.js.map +1 -0
- package/dist/modules/__fixtures__/typescript-packages/complex-types/src/index.d.ts +4 -0
- package/dist/modules/__fixtures__/typescript-packages/complex-types/src/index.d.ts.map +1 -0
- package/dist/modules/__fixtures__/typescript-packages/complex-types/src/index.js +7 -0
- package/dist/modules/__fixtures__/typescript-packages/complex-types/src/index.js.map +1 -0
- package/dist/modules/__fixtures__/typescript-packages/complex-types/src/models/category.d.ts +15 -0
- package/dist/modules/__fixtures__/typescript-packages/complex-types/src/models/category.d.ts.map +1 -0
- package/dist/modules/__fixtures__/typescript-packages/complex-types/src/models/category.js +3 -0
- package/dist/modules/__fixtures__/typescript-packages/complex-types/src/models/category.js.map +1 -0
- package/dist/modules/__fixtures__/typescript-packages/complex-types/src/models/product.d.ts +34 -0
- package/dist/modules/__fixtures__/typescript-packages/complex-types/src/models/product.d.ts.map +1 -0
- package/dist/modules/__fixtures__/typescript-packages/complex-types/src/models/product.js +23 -0
- package/dist/modules/__fixtures__/typescript-packages/complex-types/src/models/product.js.map +1 -0
- package/dist/modules/__fixtures__/typescript-packages/simple-types/index.d.ts +39 -0
- package/dist/modules/__fixtures__/typescript-packages/simple-types/index.d.ts.map +1 -0
- package/dist/modules/__fixtures__/typescript-packages/simple-types/index.js +39 -0
- package/dist/modules/__fixtures__/typescript-packages/simple-types/index.js.map +1 -0
- package/dist/modules/extractors/TypeScriptExtractor.d.ts +18 -0
- package/dist/modules/extractors/TypeScriptExtractor.d.ts.map +1 -0
- package/dist/modules/extractors/TypeScriptExtractor.js +361 -0
- package/dist/modules/extractors/TypeScriptExtractor.js.map +1 -0
- package/dist/modules/index.d.ts +13 -0
- package/dist/modules/index.d.ts.map +1 -0
- package/dist/modules/index.js +21 -0
- package/dist/modules/index.js.map +1 -0
- package/dist/providers/GitVersionControlProvider.d.ts +108 -0
- package/dist/providers/GitVersionControlProvider.d.ts.map +1 -0
- package/dist/providers/GitVersionControlProvider.js +380 -0
- package/dist/providers/GitVersionControlProvider.js.map +1 -0
- package/dist/providers/PackageManagerApiProvider.d.ts +78 -0
- package/dist/providers/PackageManagerApiProvider.d.ts.map +1 -0
- package/dist/providers/PackageManagerApiProvider.js +14 -0
- package/dist/providers/PackageManagerApiProvider.js.map +1 -0
- package/dist/providers/index.d.ts +4 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +10 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/services/FilesystemService.d.ts +59 -0
- package/dist/services/FilesystemService.d.ts.map +1 -0
- package/dist/services/FilesystemService.js +391 -0
- package/dist/services/FilesystemService.js.map +1 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +7 -0
- package/dist/services/index.js.map +1 -0
- package/dist/types/file-system.d.ts +7 -0
- package/dist/types/file-system.d.ts.map +1 -0
- package/dist/types/file-system.js +7 -0
- package/dist/types/file-system.js.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/layer-types.d.ts +187 -0
- package/dist/types/layer-types.d.ts.map +1 -0
- package/dist/types/layer-types.js +7 -0
- package/dist/types/layer-types.js.map +1 -0
- package/dist/types/version-control-layer.d.ts +53 -0
- package/dist/types/version-control-layer.d.ts.map +1 -0
- package/dist/types/version-control-layer.js +3 -0
- package/dist/types/version-control-layer.js.map +1 -0
- package/dist/types/workspace-boundaries.d.ts +17 -0
- package/dist/types/workspace-boundaries.d.ts.map +1 -0
- package/dist/types/workspace-boundaries.js +7 -0
- package/dist/types/workspace-boundaries.js.map +1 -0
- package/package.json +42 -0
- package/src/index.ts +62 -0
- package/src/modules/DependencyLayerModule.ts +329 -0
- package/src/modules/FileSystemModule.ts +65 -0
- package/src/modules/FileTypeLayerModule.ts +199 -0
- package/src/modules/FrameworkLayerModule.ts +437 -0
- package/src/modules/PackageLayerModule.ts +979 -0
- package/src/modules/TypeExtractionModule.test.ts +340 -0
- package/src/modules/TypeExtractionModule.ts +180 -0
- package/src/modules/VersionControlLayerModule.ts +31 -0
- package/src/modules/__fixtures__/typescript-packages/complex-types/package.json +6 -0
- package/src/modules/__fixtures__/typescript-packages/complex-types/src/index.ts +6 -0
- package/src/modules/__fixtures__/typescript-packages/complex-types/src/models/category.ts +15 -0
- package/src/modules/__fixtures__/typescript-packages/complex-types/src/models/product.ts +48 -0
- package/src/modules/__fixtures__/typescript-packages/javascript-only/index.js +18 -0
- package/src/modules/__fixtures__/typescript-packages/javascript-only/package.json +5 -0
- package/src/modules/__fixtures__/typescript-packages/simple-types/index.ts +53 -0
- package/src/modules/__fixtures__/typescript-packages/simple-types/package.json +6 -0
- package/src/modules/extractors/README.md +55 -0
- package/src/modules/extractors/TypeScriptExtractor.ts +409 -0
- package/src/modules/index.ts +13 -0
- package/src/providers/GitVersionControlProvider.ts +500 -0
- package/src/providers/PackageManagerApiProvider.ts +108 -0
- package/src/providers/README.md +88 -0
- package/src/providers/index.ts +17 -0
- package/src/services/FilesystemService.ts +530 -0
- package/src/services/index.ts +2 -0
- package/src/types/file-system.ts +11 -0
- package/src/types/index.ts +24 -0
- package/src/types/layer-types.ts +264 -0
- package/src/types/version-control-layer.ts +87 -0
- package/src/types/workspace-boundaries.ts +17 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import { TypeExtractionModule } from './TypeExtractionModule';
|
|
3
|
+
import { PackageLayer } from '../types/layer-types';
|
|
4
|
+
|
|
5
|
+
describe('TypeExtractionModule', () => {
|
|
6
|
+
let module: TypeExtractionModule;
|
|
7
|
+
const fixturesPath = path.join(__dirname, '__fixtures__', 'typescript-packages');
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
module = new TypeExtractionModule();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe('extractTypes', () => {
|
|
14
|
+
describe('with simple TypeScript package', () => {
|
|
15
|
+
const packagePath = path.join(fixturesPath, 'simple-types');
|
|
16
|
+
|
|
17
|
+
it('should extract exported interfaces', async () => {
|
|
18
|
+
const result = await module.extractTypes(packagePath);
|
|
19
|
+
|
|
20
|
+
expect(result.packageName).toBe('simple-types-test');
|
|
21
|
+
expect(result.packagePath).toBe(packagePath);
|
|
22
|
+
|
|
23
|
+
const userInterface = result.types.find(t => t.name === 'User' && t.kind === 'interface');
|
|
24
|
+
expect(userInterface).toBeDefined();
|
|
25
|
+
expect(userInterface?.isExported).toBe(true);
|
|
26
|
+
expect(userInterface?.fileName).toBe('./index.ts');
|
|
27
|
+
expect(userInterface?.jsDoc).toContain('Represents a user in the system');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should extract exported type aliases', async () => {
|
|
31
|
+
const result = await module.extractTypes(packagePath);
|
|
32
|
+
|
|
33
|
+
const statusType = result.types.find(t => t.name === 'Status' && t.kind === 'type');
|
|
34
|
+
expect(statusType).toBeDefined();
|
|
35
|
+
expect(statusType?.isExported).toBe(true);
|
|
36
|
+
expect(statusType?.text).toContain("'active' | 'inactive' | 'pending'");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should extract exported classes', async () => {
|
|
40
|
+
const result = await module.extractTypes(packagePath);
|
|
41
|
+
|
|
42
|
+
const userProfileClass = result.types.find(
|
|
43
|
+
t => t.name === 'UserProfile' && t.kind === 'class',
|
|
44
|
+
);
|
|
45
|
+
expect(userProfileClass).toBeDefined();
|
|
46
|
+
expect(userProfileClass?.isExported).toBe(true);
|
|
47
|
+
expect(userProfileClass?.text).toContain('getDisplayName()');
|
|
48
|
+
expect(userProfileClass?.text).toContain('isActive()');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should extract exported enums', async () => {
|
|
52
|
+
const result = await module.extractTypes(packagePath);
|
|
53
|
+
|
|
54
|
+
const actionEnum = result.types.find(t => t.name === 'Action' && t.kind === 'enum');
|
|
55
|
+
expect(actionEnum).toBeDefined();
|
|
56
|
+
expect(actionEnum?.isExported).toBe(true);
|
|
57
|
+
expect(actionEnum?.text).toContain('CREATE');
|
|
58
|
+
expect(actionEnum?.text).toContain('UPDATE');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should extract exported functions', async () => {
|
|
62
|
+
const result = await module.extractTypes(packagePath);
|
|
63
|
+
|
|
64
|
+
const formatUserFunc = result.types.find(
|
|
65
|
+
t => t.name === 'formatUser' && t.kind === 'function',
|
|
66
|
+
);
|
|
67
|
+
expect(formatUserFunc).toBeDefined();
|
|
68
|
+
expect(formatUserFunc?.isExported).toBe(true);
|
|
69
|
+
expect(formatUserFunc?.text).toContain('function formatUser(user: User): string');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should not extract non-exported types', async () => {
|
|
73
|
+
const result = await module.extractTypes(packagePath);
|
|
74
|
+
|
|
75
|
+
const internalConfig = result.types.find(t => t.name === 'InternalConfig');
|
|
76
|
+
expect(internalConfig).toBeUndefined();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should identify default export', async () => {
|
|
80
|
+
const result = await module.extractTypes(packagePath);
|
|
81
|
+
|
|
82
|
+
expect(result.exports.default).toBe('UserProfile');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should list all named exports', async () => {
|
|
86
|
+
const result = await module.extractTypes(packagePath);
|
|
87
|
+
|
|
88
|
+
expect(result.exports.named).toContain('User');
|
|
89
|
+
expect(result.exports.named).toContain('Status');
|
|
90
|
+
expect(result.exports.named).toContain('UserProfile');
|
|
91
|
+
expect(result.exports.named).toContain('Action');
|
|
92
|
+
expect(result.exports.named).toContain('formatUser');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('with complex multi-file package', () => {
|
|
97
|
+
const packagePath = path.join(fixturesPath, 'complex-types');
|
|
98
|
+
|
|
99
|
+
it('should extract types from multiple files', async () => {
|
|
100
|
+
const result = await module.extractTypes(packagePath);
|
|
101
|
+
|
|
102
|
+
expect(result.packageName).toBe('complex-types-test');
|
|
103
|
+
|
|
104
|
+
// Should find types from different files
|
|
105
|
+
const product = result.types.find(t => t.name === 'Product');
|
|
106
|
+
const category = result.types.find(t => t.name === 'Category');
|
|
107
|
+
const repository = result.types.find(t => t.name === 'Repository');
|
|
108
|
+
|
|
109
|
+
expect(product).toBeDefined();
|
|
110
|
+
expect(category).toBeDefined();
|
|
111
|
+
expect(repository).toBeDefined();
|
|
112
|
+
|
|
113
|
+
// Check file paths
|
|
114
|
+
expect(product?.fileName).toBe('./src/models/product.ts');
|
|
115
|
+
expect(category?.fileName).toBe('./src/models/category.ts');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should handle generic types', async () => {
|
|
119
|
+
const result = await module.extractTypes(packagePath);
|
|
120
|
+
|
|
121
|
+
const repository = result.types.find(t => t.name === 'Repository');
|
|
122
|
+
expect(repository).toBeDefined();
|
|
123
|
+
expect(repository?.text).toContain('Repository<T>');
|
|
124
|
+
expect(repository?.text).toContain('findById(id: string): Promise<T | null>');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should extract type aliases with intersections', async () => {
|
|
128
|
+
const result = await module.extractTypes(packagePath);
|
|
129
|
+
|
|
130
|
+
const categoryNode = result.types.find(t => t.name === 'CategoryNode');
|
|
131
|
+
expect(categoryNode).toBeDefined();
|
|
132
|
+
expect(categoryNode?.kind).toBe('type');
|
|
133
|
+
expect(categoryNode?.text).toContain('Category &');
|
|
134
|
+
expect(categoryNode?.text).toContain('children: CategoryNode[]');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('with JavaScript package', () => {
|
|
139
|
+
const packagePath = path.join(fixturesPath, 'javascript-only');
|
|
140
|
+
|
|
141
|
+
it('should return empty types array for JavaScript files', async () => {
|
|
142
|
+
const result = await module.extractTypes(packagePath);
|
|
143
|
+
|
|
144
|
+
expect(result.packageName).toBe('javascript-only-test');
|
|
145
|
+
expect(result.types).toEqual([]);
|
|
146
|
+
expect(result.exports.named).toEqual([]);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe('with non-existent package', () => {
|
|
151
|
+
it('should throw error for non-existent path', async () => {
|
|
152
|
+
const fakePath = path.join(fixturesPath, 'non-existent');
|
|
153
|
+
|
|
154
|
+
await expect(module.extractTypes(fakePath)).rejects.toThrow();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('with options', () => {
|
|
159
|
+
const packagePath = path.join(fixturesPath, 'simple-types');
|
|
160
|
+
|
|
161
|
+
it('should respect includeTests option', async () => {
|
|
162
|
+
// Create a test file fixture
|
|
163
|
+
const testFilePath = path.join(packagePath, 'index.test.ts');
|
|
164
|
+
const testContent = `
|
|
165
|
+
import { User } from './index';
|
|
166
|
+
|
|
167
|
+
export interface TestHelper {
|
|
168
|
+
createUser(): User;
|
|
169
|
+
}
|
|
170
|
+
`;
|
|
171
|
+
|
|
172
|
+
// This would need filesystem mocking or actual file creation
|
|
173
|
+
// For now, just test the option is passed through
|
|
174
|
+
const result = await module.extractTypes(packagePath, {
|
|
175
|
+
includeTests: false,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Verify test files are excluded
|
|
179
|
+
const testTypes = result.types.filter(
|
|
180
|
+
t => t.fileName.includes('.test.') || t.fileName.includes('.spec.'),
|
|
181
|
+
);
|
|
182
|
+
expect(testTypes).toHaveLength(0);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe('extractTypesFromLayer', () => {
|
|
188
|
+
it('should extract types using PackageLayer', async () => {
|
|
189
|
+
const packageLayer: PackageLayer = {
|
|
190
|
+
id: 'test-package',
|
|
191
|
+
name: 'simple-types-test',
|
|
192
|
+
type: 'package',
|
|
193
|
+
enabled: true,
|
|
194
|
+
packageData: {
|
|
195
|
+
name: 'simple-types-test',
|
|
196
|
+
version: '1.0.0',
|
|
197
|
+
path: 'simple-types',
|
|
198
|
+
packageManager: 'npm',
|
|
199
|
+
dependencies: {},
|
|
200
|
+
devDependencies: {},
|
|
201
|
+
peerDependencies: {},
|
|
202
|
+
isMonorepoRoot: false,
|
|
203
|
+
isWorkspace: false,
|
|
204
|
+
},
|
|
205
|
+
derivedFrom: {
|
|
206
|
+
fileSets: [],
|
|
207
|
+
derivationType: 'content',
|
|
208
|
+
description: 'Test package',
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const result = await module.extractTypesFromLayer(packageLayer, fixturesPath);
|
|
213
|
+
|
|
214
|
+
expect(result.packageName).toBe('simple-types-test');
|
|
215
|
+
expect(result.types.length).toBeGreaterThan(0);
|
|
216
|
+
|
|
217
|
+
const userInterface = result.types.find(t => t.name === 'User');
|
|
218
|
+
expect(userInterface).toBeDefined();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should handle PackageLayer with empty path', async () => {
|
|
222
|
+
const packageLayer: PackageLayer = {
|
|
223
|
+
id: 'root-package',
|
|
224
|
+
name: 'root-test',
|
|
225
|
+
type: 'package',
|
|
226
|
+
enabled: true,
|
|
227
|
+
packageData: {
|
|
228
|
+
name: 'simple-types-test',
|
|
229
|
+
version: '1.0.0',
|
|
230
|
+
path: '', // Empty path means root
|
|
231
|
+
packageManager: 'npm',
|
|
232
|
+
dependencies: {},
|
|
233
|
+
devDependencies: {},
|
|
234
|
+
peerDependencies: {},
|
|
235
|
+
isMonorepoRoot: true,
|
|
236
|
+
isWorkspace: false,
|
|
237
|
+
},
|
|
238
|
+
derivedFrom: {
|
|
239
|
+
fileSets: [],
|
|
240
|
+
derivationType: 'content',
|
|
241
|
+
description: 'Root package',
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const result = await module.extractTypesFromLayer(
|
|
246
|
+
packageLayer,
|
|
247
|
+
path.join(fixturesPath, 'simple-types'),
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
expect(result.packageName).toBe('simple-types-test');
|
|
251
|
+
expect(result.packagePath).toBe(path.join(fixturesPath, 'simple-types'));
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
describe('generateDefinitionFile', () => {
|
|
256
|
+
it('should generate a .d.ts file content', async () => {
|
|
257
|
+
const packagePath = path.join(fixturesPath, 'simple-types');
|
|
258
|
+
const packageTypes = await module.extractTypes(packagePath);
|
|
259
|
+
|
|
260
|
+
const definitionContent = await module.generateDefinitionFile(packageTypes);
|
|
261
|
+
|
|
262
|
+
expect(definitionContent).toContain('// Type definitions for simple-types-test');
|
|
263
|
+
expect(definitionContent).toContain('export interface User');
|
|
264
|
+
expect(definitionContent).toContain('export type Status');
|
|
265
|
+
expect(definitionContent).toContain('export class UserProfile');
|
|
266
|
+
expect(definitionContent).toContain('export enum Action');
|
|
267
|
+
expect(definitionContent).toContain('export function formatUser');
|
|
268
|
+
|
|
269
|
+
// Should include JSDoc comments
|
|
270
|
+
expect(definitionContent).toContain('/** User interface for testing');
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('should organize types by kind', async () => {
|
|
274
|
+
const packagePath = path.join(fixturesPath, 'simple-types');
|
|
275
|
+
const packageTypes = await module.extractTypes(packagePath);
|
|
276
|
+
|
|
277
|
+
const definitionContent = await module.generateDefinitionFile(packageTypes);
|
|
278
|
+
|
|
279
|
+
// Check that types are grouped by kind
|
|
280
|
+
const lines = definitionContent.split('\n');
|
|
281
|
+
const interfaceIndex = lines.findIndex(l => l.includes('// Interfaces'));
|
|
282
|
+
const typeIndex = lines.findIndex(l => l.includes('// Types'));
|
|
283
|
+
const classIndex = lines.findIndex(l => l.includes('// Classs')); // Note: will be "Classes" in real impl
|
|
284
|
+
const enumIndex = lines.findIndex(l => l.includes('// Enums'));
|
|
285
|
+
const functionIndex = lines.findIndex(l => l.includes('// Functions'));
|
|
286
|
+
|
|
287
|
+
// Interfaces should come before types, types before classes, etc.
|
|
288
|
+
if (interfaceIndex > -1 && typeIndex > -1) {
|
|
289
|
+
expect(interfaceIndex).toBeLessThan(typeIndex);
|
|
290
|
+
}
|
|
291
|
+
if (typeIndex > -1 && classIndex > -1) {
|
|
292
|
+
expect(typeIndex).toBeLessThan(classIndex);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
describe('error handling', () => {
|
|
298
|
+
it('should handle malformed TypeScript files gracefully', async () => {
|
|
299
|
+
// Would need a fixture with syntax errors
|
|
300
|
+
// For now, test the structure exists
|
|
301
|
+
expect(module.extractTypes).toBeDefined();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('should handle circular dependencies', async () => {
|
|
305
|
+
// Would need fixtures with circular imports
|
|
306
|
+
// Structure test for now
|
|
307
|
+
expect(module.extractTypesFromLayer).toBeDefined();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('should handle missing package.json', async () => {
|
|
311
|
+
// Test with a directory that has .ts files but no package.json
|
|
312
|
+
// This would need another fixture
|
|
313
|
+
expect(module.extractTypes).toBeDefined();
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
describe('performance', () => {
|
|
318
|
+
it('should handle large codebases efficiently', async () => {
|
|
319
|
+
const startTime = Date.now();
|
|
320
|
+
const packagePath = path.join(fixturesPath, 'complex-types');
|
|
321
|
+
|
|
322
|
+
await module.extractTypes(packagePath);
|
|
323
|
+
|
|
324
|
+
const duration = Date.now() - startTime;
|
|
325
|
+
// Should complete in reasonable time (< 1 second for small packages)
|
|
326
|
+
expect(duration).toBeLessThan(1000);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('should cache results when called multiple times', async () => {
|
|
330
|
+
// This would require implementing caching in the module
|
|
331
|
+
// For now, just verify the method can be called multiple times
|
|
332
|
+
const packagePath = path.join(fixturesPath, 'simple-types');
|
|
333
|
+
|
|
334
|
+
const result1 = await module.extractTypes(packagePath);
|
|
335
|
+
const result2 = await module.extractTypes(packagePath);
|
|
336
|
+
|
|
337
|
+
expect(result1.types.length).toBe(result2.types.length);
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
});
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
import { PackageLayer, PackageTypes, TypeExtractor } from '../types/layer-types';
|
|
5
|
+
|
|
6
|
+
import { TypeScriptExtractor } from './extractors/TypeScriptExtractor';
|
|
7
|
+
|
|
8
|
+
export interface TypeExtractionOptions {
|
|
9
|
+
includeTests?: boolean;
|
|
10
|
+
includeInternal?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class TypeExtractionModule {
|
|
14
|
+
private extractors: TypeExtractor[] = [new TypeScriptExtractor()];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Register a new type extractor
|
|
18
|
+
*/
|
|
19
|
+
registerExtractor(extractor: TypeExtractor): void {
|
|
20
|
+
this.extractors.push(extractor);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get all registered extractors
|
|
25
|
+
*/
|
|
26
|
+
getExtractors(): TypeExtractor[] {
|
|
27
|
+
return [...this.extractors];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Extract types from a package directory
|
|
32
|
+
*/
|
|
33
|
+
async extractTypes(
|
|
34
|
+
packagePath: string,
|
|
35
|
+
options: TypeExtractionOptions = {},
|
|
36
|
+
): Promise<PackageTypes> {
|
|
37
|
+
console.log(`[TypeExtraction] Extracting types from package path: ${packagePath}`);
|
|
38
|
+
|
|
39
|
+
// Verify package path exists
|
|
40
|
+
if (!fs.existsSync(packagePath)) {
|
|
41
|
+
throw new Error(`Package path does not exist: ${packagePath}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Find appropriate extractor based on file extensions in the package
|
|
45
|
+
const fileExtensions = this.getFileExtensions(packagePath);
|
|
46
|
+
const extractor = this.findExtractor(packagePath, fileExtensions);
|
|
47
|
+
|
|
48
|
+
if (!extractor) {
|
|
49
|
+
console.log(`[TypeExtraction] No suitable extractor found for package: ${packagePath}`);
|
|
50
|
+
|
|
51
|
+
// Still try to read package name from package.json
|
|
52
|
+
let packageName = path.basename(packagePath);
|
|
53
|
+
try {
|
|
54
|
+
const packageJsonPath = path.join(packagePath, 'package.json');
|
|
55
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
56
|
+
packageName = packageJson.name || packageName;
|
|
57
|
+
} catch {
|
|
58
|
+
// Use directory name as fallback
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
packagePath,
|
|
63
|
+
packageName,
|
|
64
|
+
types: [],
|
|
65
|
+
exports: { named: [] },
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log(`[TypeExtraction] Using ${extractor.name} extractor for ${packagePath}`);
|
|
70
|
+
return extractor.extractTypes(packagePath, options);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Extract types from a PackageLayer
|
|
75
|
+
*/
|
|
76
|
+
async extractTypesFromLayer(
|
|
77
|
+
packageLayer: PackageLayer,
|
|
78
|
+
workingDirectory: string,
|
|
79
|
+
options: TypeExtractionOptions = {},
|
|
80
|
+
): Promise<PackageTypes> {
|
|
81
|
+
if (!workingDirectory) {
|
|
82
|
+
throw new Error('workingDirectory is required when using PackageLayer');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Compute absolute path from working directory and layer path
|
|
86
|
+
const packagePath = packageLayer.packageData.path
|
|
87
|
+
? path.resolve(workingDirectory, packageLayer.packageData.path)
|
|
88
|
+
: workingDirectory;
|
|
89
|
+
|
|
90
|
+
const packageName = packageLayer.packageData.name;
|
|
91
|
+
|
|
92
|
+
console.log(`[TypeExtraction] Extracting types from package layer:`, {
|
|
93
|
+
layerId: packageLayer.id,
|
|
94
|
+
layerName: packageLayer.name,
|
|
95
|
+
packageName: packageName,
|
|
96
|
+
layerPath: packageLayer.packageData.path,
|
|
97
|
+
computedPath: packagePath,
|
|
98
|
+
workingDirectory,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const result = await this.extractTypes(packagePath, options);
|
|
102
|
+
|
|
103
|
+
// Override package name from layer if different
|
|
104
|
+
return {
|
|
105
|
+
...result,
|
|
106
|
+
packageName,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Generate a TypeScript definition file from extracted types
|
|
112
|
+
*/
|
|
113
|
+
async generateDefinitionFile(packageTypes: PackageTypes): Promise<string> {
|
|
114
|
+
// Find the extractor that created these types (for now, assume TypeScript)
|
|
115
|
+
const extractor = this.extractors.find(e => e.name === 'TypeScript');
|
|
116
|
+
|
|
117
|
+
if (extractor?.generateDefinitionFile) {
|
|
118
|
+
return extractor.generateDefinitionFile(packageTypes);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Fallback basic implementation
|
|
122
|
+
const lines = packageTypes.types.map(type =>
|
|
123
|
+
type.isExported ? `export ${type.text}` : type.text,
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
return [
|
|
127
|
+
`// Type definitions for ${packageTypes.packageName}`,
|
|
128
|
+
`// Generated from: ${packageTypes.packagePath}`,
|
|
129
|
+
`// Generated on: ${new Date().toISOString()}`,
|
|
130
|
+
'',
|
|
131
|
+
...lines,
|
|
132
|
+
].join('\n');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Find the appropriate extractor for a package
|
|
137
|
+
*/
|
|
138
|
+
private findExtractor(packagePath: string, fileExtensions: string[]): TypeExtractor | null {
|
|
139
|
+
for (const extractor of this.extractors) {
|
|
140
|
+
if (extractor.canHandle(packagePath, fileExtensions)) {
|
|
141
|
+
return extractor;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get unique file extensions from a package directory
|
|
149
|
+
*/
|
|
150
|
+
private getFileExtensions(packagePath: string): string[] {
|
|
151
|
+
const extensions = new Set<string>();
|
|
152
|
+
|
|
153
|
+
const walk = (dir: string) => {
|
|
154
|
+
try {
|
|
155
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
156
|
+
|
|
157
|
+
for (const entry of entries) {
|
|
158
|
+
const fullPath = path.join(dir, entry.name);
|
|
159
|
+
|
|
160
|
+
if (entry.isDirectory()) {
|
|
161
|
+
// Skip node_modules and hidden directories
|
|
162
|
+
if (entry.name !== 'node_modules' && !entry.name.startsWith('.')) {
|
|
163
|
+
walk(fullPath);
|
|
164
|
+
}
|
|
165
|
+
} else if (entry.isFile()) {
|
|
166
|
+
const ext = path.extname(entry.name);
|
|
167
|
+
if (ext) {
|
|
168
|
+
extensions.add(ext);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.error(`[TypeExtraction] Error reading directory ${dir}:`, error);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
walk(packagePath);
|
|
178
|
+
return Array.from(extensions);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { VersionControlProvider, VersionControlLayer } from '../types/version-control-layer';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Factory for creating version control layers
|
|
5
|
+
*/
|
|
6
|
+
export class VersionControlLayerFactory {
|
|
7
|
+
private providers: Map<string, VersionControlProvider> = new Map();
|
|
8
|
+
|
|
9
|
+
registerProvider(name: string, provider: VersionControlProvider): void {
|
|
10
|
+
this.providers.set(name, provider);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async detectAndCreateLayer(directoryPath: string): Promise<VersionControlLayer | null> {
|
|
14
|
+
// Try each provider in order of preference
|
|
15
|
+
const providerOrder = ['git', 'svn', 'mercurial', 'perforce'];
|
|
16
|
+
|
|
17
|
+
for (const providerName of providerOrder) {
|
|
18
|
+
const provider = this.providers.get(providerName);
|
|
19
|
+
if (!provider) continue;
|
|
20
|
+
|
|
21
|
+
if (await provider.detect(directoryPath)) {
|
|
22
|
+
const root = await provider.getRepositoryRoot(directoryPath);
|
|
23
|
+
if (root) {
|
|
24
|
+
return await provider.createLayer(root);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Category } from './category';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Product interface with nested types
|
|
5
|
+
*/
|
|
6
|
+
export interface Product {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
price: number;
|
|
10
|
+
category: Category;
|
|
11
|
+
metadata: {
|
|
12
|
+
createdAt: Date;
|
|
13
|
+
updatedAt: Date;
|
|
14
|
+
tags: string[];
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Generic repository pattern
|
|
20
|
+
*/
|
|
21
|
+
export interface Repository<T> {
|
|
22
|
+
findById(id: string): Promise<T | null>;
|
|
23
|
+
findAll(): Promise<T[]>;
|
|
24
|
+
save(entity: T): Promise<T>;
|
|
25
|
+
delete(id: string): Promise<boolean>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Product repository with specific implementation
|
|
30
|
+
*/
|
|
31
|
+
export class ProductRepository implements Repository<Product> {
|
|
32
|
+
async findById(_id: string): Promise<Product | null> {
|
|
33
|
+
// Mock implementation
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async findAll(): Promise<Product[]> {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async save(product: Product): Promise<Product> {
|
|
42
|
+
return product;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async delete(_id: string): Promise<boolean> {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// JavaScript file without types - should handle gracefully
|
|
2
|
+
export function greet(name) {
|
|
3
|
+
return `Hello, ${name}!`;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export class Calculator {
|
|
7
|
+
add(a, b) {
|
|
8
|
+
return a + b;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
subtract(a, b) {
|
|
12
|
+
return a - b;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const VERSION = '1.0.0';
|
|
17
|
+
|
|
18
|
+
export default Calculator;
|