@principal-ai/codebase-composition 0.2.8 → 0.2.10

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.
Files changed (109) hide show
  1. package/package.json +5 -4
  2. package/dist/adapters/NodeFileSystemAdapter.d.ts +0 -28
  3. package/dist/adapters/NodeFileSystemAdapter.d.ts.map +0 -1
  4. package/dist/adapters/NodeFileSystemAdapter.js +0 -128
  5. package/dist/adapters/NodeFileSystemAdapter.js.map +0 -1
  6. package/dist/helpers/QualityMetricsCalculator.d.ts +0 -46
  7. package/dist/helpers/QualityMetricsCalculator.d.ts.map +0 -1
  8. package/dist/helpers/QualityMetricsCalculator.js +0 -161
  9. package/dist/helpers/QualityMetricsCalculator.js.map +0 -1
  10. package/dist/helpers/packageLayerHelpers.d.ts +0 -30
  11. package/dist/helpers/packageLayerHelpers.d.ts.map +0 -1
  12. package/dist/helpers/packageLayerHelpers.js +0 -88
  13. package/dist/helpers/packageLayerHelpers.js.map +0 -1
  14. package/dist/index.d.ts +0 -11
  15. package/dist/index.d.ts.map +0 -1
  16. package/dist/index.js +0 -14
  17. package/dist/index.js.map +0 -1
  18. package/dist/modules/DependencyLayerModule.d.ts +0 -44
  19. package/dist/modules/DependencyLayerModule.d.ts.map +0 -1
  20. package/dist/modules/DependencyLayerModule.js +0 -282
  21. package/dist/modules/DependencyLayerModule.js.map +0 -1
  22. package/dist/modules/FileSystemModule.d.ts +0 -30
  23. package/dist/modules/FileSystemModule.d.ts.map +0 -1
  24. package/dist/modules/FileSystemModule.js +0 -43
  25. package/dist/modules/FileSystemModule.js.map +0 -1
  26. package/dist/modules/FileTypeLayerModule.d.ts +0 -34
  27. package/dist/modules/FileTypeLayerModule.d.ts.map +0 -1
  28. package/dist/modules/FileTypeLayerModule.js +0 -165
  29. package/dist/modules/FileTypeLayerModule.js.map +0 -1
  30. package/dist/modules/FrameworkLayerModule.d.ts +0 -22
  31. package/dist/modules/FrameworkLayerModule.d.ts.map +0 -1
  32. package/dist/modules/FrameworkLayerModule.js +0 -384
  33. package/dist/modules/FrameworkLayerModule.js.map +0 -1
  34. package/dist/modules/PackageLayerModule.d.ts +0 -23
  35. package/dist/modules/PackageLayerModule.d.ts.map +0 -1
  36. package/dist/modules/PackageLayerModule.js +0 -775
  37. package/dist/modules/PackageLayerModule.js.map +0 -1
  38. package/dist/modules/TypeExtractionModule.d.ts +0 -37
  39. package/dist/modules/TypeExtractionModule.d.ts.map +0 -1
  40. package/dist/modules/TypeExtractionModule.js +0 -141
  41. package/dist/modules/TypeExtractionModule.js.map +0 -1
  42. package/dist/modules/VersionControlLayerModule.d.ts +0 -10
  43. package/dist/modules/VersionControlLayerModule.d.ts.map +0 -1
  44. package/dist/modules/VersionControlLayerModule.js +0 -26
  45. package/dist/modules/VersionControlLayerModule.js.map +0 -1
  46. package/dist/modules/__fixtures__/typescript-packages/complex-types/src/index.d.ts +0 -4
  47. package/dist/modules/__fixtures__/typescript-packages/complex-types/src/index.d.ts.map +0 -1
  48. package/dist/modules/__fixtures__/typescript-packages/complex-types/src/index.js +0 -3
  49. package/dist/modules/__fixtures__/typescript-packages/complex-types/src/index.js.map +0 -1
  50. package/dist/modules/__fixtures__/typescript-packages/complex-types/src/models/category.d.ts +0 -15
  51. package/dist/modules/__fixtures__/typescript-packages/complex-types/src/models/category.d.ts.map +0 -1
  52. package/dist/modules/__fixtures__/typescript-packages/complex-types/src/models/category.js +0 -2
  53. package/dist/modules/__fixtures__/typescript-packages/complex-types/src/models/category.js.map +0 -1
  54. package/dist/modules/__fixtures__/typescript-packages/complex-types/src/models/product.d.ts +0 -34
  55. package/dist/modules/__fixtures__/typescript-packages/complex-types/src/models/product.d.ts.map +0 -1
  56. package/dist/modules/__fixtures__/typescript-packages/complex-types/src/models/product.js +0 -19
  57. package/dist/modules/__fixtures__/typescript-packages/complex-types/src/models/product.js.map +0 -1
  58. package/dist/modules/__fixtures__/typescript-packages/simple-types/index.d.ts +0 -39
  59. package/dist/modules/__fixtures__/typescript-packages/simple-types/index.d.ts.map +0 -1
  60. package/dist/modules/__fixtures__/typescript-packages/simple-types/index.js +0 -36
  61. package/dist/modules/__fixtures__/typescript-packages/simple-types/index.js.map +0 -1
  62. package/dist/modules/extractors/TypeScriptExtractor.d.ts +0 -18
  63. package/dist/modules/extractors/TypeScriptExtractor.d.ts.map +0 -1
  64. package/dist/modules/extractors/TypeScriptExtractor.js +0 -322
  65. package/dist/modules/extractors/TypeScriptExtractor.js.map +0 -1
  66. package/dist/modules/index.d.ts +0 -13
  67. package/dist/modules/index.d.ts.map +0 -1
  68. package/dist/modules/index.js +0 -10
  69. package/dist/modules/index.js.map +0 -1
  70. package/dist/providers/GitVersionControlProvider.d.ts +0 -108
  71. package/dist/providers/GitVersionControlProvider.d.ts.map +0 -1
  72. package/dist/providers/GitVersionControlProvider.js +0 -375
  73. package/dist/providers/GitVersionControlProvider.js.map +0 -1
  74. package/dist/providers/PackageManagerApiProvider.d.ts +0 -78
  75. package/dist/providers/PackageManagerApiProvider.d.ts.map +0 -1
  76. package/dist/providers/PackageManagerApiProvider.js +0 -10
  77. package/dist/providers/PackageManagerApiProvider.js.map +0 -1
  78. package/dist/providers/index.d.ts +0 -4
  79. package/dist/providers/index.d.ts.map +0 -1
  80. package/dist/providers/index.js +0 -5
  81. package/dist/providers/index.js.map +0 -1
  82. package/dist/services/FilesystemService.d.ts +0 -59
  83. package/dist/services/FilesystemService.d.ts.map +0 -1
  84. package/dist/services/FilesystemService.js +0 -394
  85. package/dist/services/FilesystemService.js.map +0 -1
  86. package/dist/services/index.d.ts +0 -2
  87. package/dist/services/index.d.ts.map +0 -1
  88. package/dist/services/index.js +0 -3
  89. package/dist/services/index.js.map +0 -1
  90. package/dist/types/file-system.d.ts +0 -6
  91. package/dist/types/file-system.d.ts.map +0 -1
  92. package/dist/types/file-system.js +0 -6
  93. package/dist/types/file-system.js.map +0 -1
  94. package/dist/types/index.d.ts +0 -4
  95. package/dist/types/index.d.ts.map +0 -1
  96. package/dist/types/index.js +0 -2
  97. package/dist/types/index.js.map +0 -1
  98. package/dist/types/layer-types.d.ts +0 -205
  99. package/dist/types/layer-types.d.ts.map +0 -1
  100. package/dist/types/layer-types.js +0 -6
  101. package/dist/types/layer-types.js.map +0 -1
  102. package/dist/types/version-control-layer.d.ts +0 -53
  103. package/dist/types/version-control-layer.d.ts.map +0 -1
  104. package/dist/types/version-control-layer.js +0 -2
  105. package/dist/types/version-control-layer.js.map +0 -1
  106. package/dist/types/workspace-boundaries.d.ts +0 -17
  107. package/dist/types/workspace-boundaries.d.ts.map +0 -1
  108. package/dist/types/workspace-boundaries.js +0 -6
  109. package/dist/types/workspace-boundaries.js.map +0 -1
@@ -1,775 +0,0 @@
1
- import * as TOML from 'js-toml';
2
- import { parsePipRequirementsFile } from 'pip-requirements-js';
3
- import { QualityMetricsCalculator } from '../helpers/QualityMetricsCalculator.js';
4
- // Node.js package.json parser
5
- class NodePackageParser {
6
- manifestFileName = 'package.json';
7
- packageType = 'node';
8
- canParse(filename) {
9
- return filename.endsWith('package.json');
10
- }
11
- parseContent(rawContent) {
12
- try {
13
- return JSON.parse(rawContent);
14
- }
15
- catch {
16
- return null;
17
- }
18
- }
19
- extractPackageData(content, path) {
20
- // Handle cases where path is just "package.json" (root package)
21
- // or a proper path like "packages/foo/package.json"
22
- let packagePath;
23
- if (path === 'package.json') {
24
- packagePath = ''; // Root directory - empty string for root
25
- }
26
- else if (path.endsWith('/package.json')) {
27
- packagePath = path.slice(0, -13); // Remove '/package.json'
28
- }
29
- else {
30
- // Shouldn't happen, but handle gracefully
31
- packagePath = path;
32
- }
33
- const availableCommands = this.extractCommands(content, packagePath);
34
- return {
35
- name: content.name || 'unnamed',
36
- version: content.version,
37
- path: packagePath,
38
- manifestPath: path, // The full relative path to package.json
39
- packageManager: 'unknown',
40
- dependencies: content.dependencies || {},
41
- devDependencies: content.devDependencies || {},
42
- peerDependencies: content.peerDependencies || {},
43
- isMonorepoRoot: !!content.workspaces,
44
- isWorkspace: false, // Will be determined by context
45
- parentPackage: undefined,
46
- availableCommands,
47
- };
48
- }
49
- extractCommands(content, packagePath) {
50
- const commands = [];
51
- // Extract npm scripts
52
- if (content.scripts) {
53
- Object.entries(content.scripts).forEach(([name, script]) => {
54
- // Use npm as default, will be updated with correct package manager later
55
- commands.push({
56
- name,
57
- command: `npm run ${name}`,
58
- description: script.length > 50 ? script.substring(0, 47) + '...' : script,
59
- type: 'script',
60
- workingDirectory: packagePath,
61
- });
62
- });
63
- }
64
- // Add standard npm commands - these will also be updated with correct package manager
65
- const standardCommands = [
66
- { name: 'install', command: 'npm install', description: 'Install dependencies' },
67
- { name: 'ci', command: 'npm ci', description: 'Clean install dependencies' },
68
- { name: 'update', command: 'npm update', description: 'Update dependencies' },
69
- ];
70
- standardCommands.forEach(cmd => {
71
- commands.push({
72
- ...cmd,
73
- type: 'standard',
74
- workingDirectory: packagePath,
75
- });
76
- });
77
- return commands;
78
- }
79
- detectWorkspaces(content) {
80
- if (Array.isArray(content.workspaces)) {
81
- return content.workspaces;
82
- }
83
- if (content.workspaces &&
84
- typeof content.workspaces === 'object' &&
85
- 'packages' in content.workspaces) {
86
- return content.workspaces.packages || null;
87
- }
88
- return null;
89
- }
90
- detectPackageManager(content, lockFiles) {
91
- // Check for packageManager field (corepack)
92
- if (content.packageManager) {
93
- const pm = content.packageManager.split('@')[0];
94
- if (['npm', 'yarn', 'pnpm'].includes(pm)) {
95
- return pm;
96
- }
97
- }
98
- // Check lock files
99
- if (lockFiles.some((f) => f.endsWith('yarn.lock')))
100
- return 'yarn';
101
- if (lockFiles.some((f) => f.endsWith('pnpm-lock.yaml')))
102
- return 'pnpm';
103
- if (lockFiles.some((f) => f.endsWith('package-lock.json')))
104
- return 'npm';
105
- // Default to npm instead of unknown for Node packages
106
- return 'npm';
107
- }
108
- detectConfigs(packagePath, fileTree, manifestContent) {
109
- const configs = {};
110
- // Define config patterns for Node/JavaScript projects
111
- const configPatterns = {
112
- knip: [
113
- 'knip.json',
114
- 'knip.jsonc',
115
- '.knip.json',
116
- 'knip.config.js',
117
- 'knip.config.ts',
118
- 'knip.config.mjs',
119
- ],
120
- eslint: [
121
- '.eslintrc',
122
- '.eslintrc.js',
123
- '.eslintrc.json',
124
- '.eslintrc.yml',
125
- '.eslintrc.yaml',
126
- 'eslint.config.js',
127
- 'eslint.config.mjs',
128
- '.eslintrc.cjs',
129
- ],
130
- prettier: [
131
- '.prettierrc',
132
- '.prettierrc.js',
133
- '.prettierrc.json',
134
- '.prettierrc.yml',
135
- '.prettierrc.yaml',
136
- 'prettier.config.js',
137
- '.prettierrc.toml',
138
- ],
139
- typescript: ['tsconfig.json', 'tsconfig.base.json'],
140
- jest: [
141
- 'jest.config.js',
142
- 'jest.config.ts',
143
- 'jest.config.mjs',
144
- 'jest.config.cjs',
145
- 'jest.config.json',
146
- ],
147
- vitest: ['vitest.config.js', 'vitest.config.ts', 'vitest.config.mjs'],
148
- webpack: ['webpack.config.js', 'webpack.config.ts', 'webpack.config.mjs'],
149
- vite: ['vite.config.js', 'vite.config.ts', 'vite.config.mjs'],
150
- rollup: ['rollup.config.js', 'rollup.config.ts', 'rollup.config.mjs'],
151
- babel: ['.babelrc', '.babelrc.js', '.babelrc.json', 'babel.config.js', 'babel.config.json'],
152
- dockerfile: ['Dockerfile', 'dockerfile', 'Dockerfile.dev', 'Dockerfile.prod'],
153
- gitignore: ['.gitignore'],
154
- editorconfig: ['.editorconfig'],
155
- };
156
- // Normalize package path
157
- const normalizedPath = packagePath === '.' || packagePath === ''
158
- ? ''
159
- : packagePath.endsWith('/')
160
- ? packagePath
161
- : packagePath + '/';
162
- // Check files in tree
163
- if (fileTree.allFiles) {
164
- for (const [tool, patterns] of Object.entries(configPatterns)) {
165
- for (const pattern of patterns) {
166
- const fullPath = normalizedPath + pattern;
167
- const fileExists = fileTree.allFiles.some(f => f.path === fullPath || f.relativePath === fullPath);
168
- if (fileExists) {
169
- configs[tool] = {
170
- path: fullPath,
171
- exists: true,
172
- type: this.getConfigFileType(pattern),
173
- };
174
- break; // Found config for this tool
175
- }
176
- }
177
- }
178
- }
179
- // Check for inline configs in package.json
180
- if (manifestContent) {
181
- if (manifestContent.eslintConfig) {
182
- configs.eslint = {
183
- path: normalizedPath + 'package.json',
184
- exists: true,
185
- type: 'json',
186
- isInline: true,
187
- inlineField: 'eslintConfig',
188
- };
189
- }
190
- if (manifestContent.prettier) {
191
- configs.prettier = {
192
- path: normalizedPath + 'package.json',
193
- exists: true,
194
- type: 'json',
195
- isInline: true,
196
- inlineField: 'prettier',
197
- };
198
- }
199
- if (manifestContent.jest) {
200
- configs.jest = {
201
- path: normalizedPath + 'package.json',
202
- exists: true,
203
- type: 'json',
204
- isInline: true,
205
- inlineField: 'jest',
206
- };
207
- }
208
- if (manifestContent.babel) {
209
- configs.babel = {
210
- path: normalizedPath + 'package.json',
211
- exists: true,
212
- type: 'json',
213
- isInline: true,
214
- inlineField: 'babel',
215
- };
216
- }
217
- }
218
- return configs;
219
- }
220
- getConfigFileType(filename) {
221
- if (filename.endsWith('.json') || filename.endsWith('.jsonc'))
222
- return 'json';
223
- if (filename.endsWith('.yml') || filename.endsWith('.yaml'))
224
- return 'yaml';
225
- if (filename.endsWith('.toml'))
226
- return 'toml';
227
- if (filename.endsWith('.js') || filename.endsWith('.mjs') || filename.endsWith('.cjs'))
228
- return 'js';
229
- if (filename.endsWith('.ts') || filename.endsWith('.mts') || filename.endsWith('.cts'))
230
- return 'ts';
231
- if (filename.endsWith('.ini'))
232
- return 'ini';
233
- return 'custom';
234
- }
235
- detectDocsFolder(packagePath, fileTree, _manifestContent) {
236
- // Look for common documentation folder names
237
- const docsFolderNames = ['docs', 'documentation', 'doc'];
238
- // Normalize package path
239
- let packagePrefix = packagePath === '.' || packagePath === '' ? '' : packagePath;
240
- if (packagePrefix && !packagePrefix.endsWith('/')) {
241
- packagePrefix += '/';
242
- }
243
- if (!fileTree.allFiles)
244
- return undefined;
245
- // Look for root-level docs folders first (preferred)
246
- for (const folderName of docsFolderNames) {
247
- const expectedPath = packagePrefix + folderName;
248
- const hasFiles = fileTree.allFiles.some(file => file.path?.toLowerCase().startsWith(expectedPath.toLowerCase() + '/'));
249
- if (hasFiles) {
250
- // Find the actual folder name with correct case
251
- const actualPath = fileTree.allFiles.find(file => file.path?.toLowerCase().startsWith(expectedPath.toLowerCase() + '/'))?.path;
252
- if (actualPath) {
253
- const actualFolderPath = actualPath.substring(0, actualPath.indexOf('/', packagePrefix.length));
254
- return actualFolderPath.substring(packagePrefix.length);
255
- }
256
- return folderName; // Fallback to lowercase version
257
- }
258
- }
259
- // If no explicit docs folder found, look for directories with high concentration of docs
260
- const potentialFolders = new Map();
261
- for (const file of fileTree.allFiles) {
262
- if (!file.path)
263
- continue;
264
- // Only consider files within this package
265
- if (packagePrefix !== '' && !file.path.startsWith(packagePrefix)) {
266
- continue;
267
- }
268
- const relativePath = packagePrefix === '' ? file.path : file.path.substring(packagePrefix.length);
269
- const pathParts = relativePath.split('/');
270
- // Only look at direct subdirectories (depth 1)
271
- if (pathParts.length === 2) {
272
- const dirName = pathParts[0];
273
- const fileName = pathParts[1];
274
- if (!potentialFolders.has(dirName)) {
275
- potentialFolders.set(dirName, { docFiles: 0, totalFiles: 0 });
276
- }
277
- const stats = potentialFolders.get(dirName);
278
- stats.totalFiles++;
279
- if (this.isDocumentationFile(fileName)) {
280
- stats.docFiles++;
281
- }
282
- }
283
- }
284
- // Find the best candidate folder (highest percentage of docs, minimum 2 doc files)
285
- let bestFolder;
286
- let bestScore = 0.5; // Must be at least 50% docs
287
- for (const [folderName, stats] of Array.from(potentialFolders.entries())) {
288
- if (stats.docFiles >= 2) {
289
- const score = stats.docFiles / stats.totalFiles;
290
- if (score > bestScore) {
291
- bestScore = score;
292
- bestFolder = folderName;
293
- }
294
- }
295
- }
296
- return bestFolder;
297
- }
298
- isDocumentationFile(fileName) {
299
- const lowerName = fileName.toLowerCase();
300
- // Documentation file extensions
301
- const docExtensions = ['.md', '.mdx', '.rst', '.txt', '.adoc', '.asciidoc'];
302
- const hasDocExtension = docExtensions.some(ext => lowerName.endsWith(ext));
303
- if (hasDocExtension)
304
- return true;
305
- // Documentation file name patterns
306
- const docPatterns = [
307
- /^readme/i,
308
- /^changelog/i,
309
- /^changes/i,
310
- /^history/i,
311
- /^license/i,
312
- /^copying/i,
313
- /^install/i,
314
- /^usage/i,
315
- /^guide/i,
316
- /^tutorial/i,
317
- /^manual/i,
318
- /^faq/i,
319
- /^api/i,
320
- /^reference/i,
321
- /^spec/i,
322
- /^specification/i,
323
- ];
324
- return docPatterns.some(pattern => pattern.test(fileName));
325
- }
326
- }
327
- // Python pyproject.toml parser - simplified version
328
- class PythonPackageParser {
329
- manifestFileName = 'pyproject.toml';
330
- packageType = 'python';
331
- canParse(filename) {
332
- return (filename.endsWith('pyproject.toml') ||
333
- filename.endsWith('setup.py') ||
334
- filename.endsWith('requirements.txt'));
335
- }
336
- parseContent(rawContent) {
337
- try {
338
- return TOML.load(rawContent);
339
- }
340
- catch {
341
- return null;
342
- }
343
- }
344
- extractPackageData(content, path) {
345
- // Extract package directory path from manifest path
346
- let packagePath;
347
- const manifestName = path.split('/').pop() || '';
348
- if (path === manifestName) {
349
- // Just the filename, package is at root
350
- packagePath = ''; // Empty string for root
351
- }
352
- else {
353
- // Remove the manifest filename from path
354
- packagePath = path.substring(0, path.lastIndexOf('/'));
355
- }
356
- // Handle pyproject.toml structure
357
- const poetryData = content.tool?.poetry;
358
- const projectData = content.project;
359
- const name = poetryData?.name || projectData?.name || 'unnamed';
360
- const version = poetryData?.version || projectData?.version;
361
- // Extract dependencies
362
- const dependencies = {};
363
- const devDependencies = {};
364
- // Poetry format
365
- if (poetryData?.dependencies) {
366
- Object.entries(poetryData.dependencies).forEach(([key, value]) => {
367
- if (key !== 'python') {
368
- dependencies[key] = typeof value === 'string' ? value : JSON.stringify(value);
369
- }
370
- });
371
- }
372
- if (poetryData?.['dev-dependencies']) {
373
- Object.entries(poetryData['dev-dependencies']).forEach(([key, value]) => {
374
- devDependencies[key] = typeof value === 'string' ? value : JSON.stringify(value);
375
- });
376
- }
377
- // PEP 621 format
378
- if (projectData?.dependencies && Array.isArray(projectData.dependencies)) {
379
- try {
380
- // Use pip-requirements-js to properly parse PEP 508 dependency specifications
381
- const depString = projectData.dependencies.join('\n');
382
- const requirements = parsePipRequirementsFile(depString);
383
- requirements.forEach((req) => {
384
- if (req.type === 'ProjectName' && req.name) {
385
- let version = '*';
386
- if (req.versionSpec && req.versionSpec.length > 0) {
387
- // Use the first version spec for simplicity
388
- const firstSpec = req.versionSpec[0];
389
- version = firstSpec.version || '*';
390
- }
391
- dependencies[req.name] = version;
392
- }
393
- });
394
- }
395
- catch (error) {
396
- console.warn('Failed to parse PEP 621 dependencies with pip-requirements-js, falling back to simple parsing:', error);
397
- // Fallback to simple parsing if pip-requirements-js fails
398
- projectData.dependencies.forEach((dep) => {
399
- const cleanDep = dep.trim();
400
- const spaceIndex = cleanDep.indexOf(' ');
401
- if (spaceIndex > 0) {
402
- dependencies[cleanDep.substring(0, spaceIndex)] = '*';
403
- }
404
- else {
405
- dependencies[cleanDep] = '*';
406
- }
407
- });
408
- }
409
- }
410
- const packageManager = this.detectPythonPackageManager(content);
411
- return {
412
- name,
413
- version,
414
- path: packagePath,
415
- manifestPath: path, // The full relative path to pyproject.toml/requirements.txt/setup.py
416
- packageManager,
417
- dependencies,
418
- devDependencies,
419
- peerDependencies: {},
420
- isMonorepoRoot: false,
421
- isWorkspace: false,
422
- parentPackage: undefined,
423
- availableCommands: [],
424
- };
425
- }
426
- detectWorkspaces(_content) {
427
- // Python doesn't have built-in workspace support like npm
428
- return null;
429
- }
430
- detectPackageManager(content, _lockFiles) {
431
- return this.detectPythonPackageManager(content);
432
- }
433
- detectPythonPackageManager(content) {
434
- // Check for Poetry
435
- if (content.tool?.poetry)
436
- return 'poetry';
437
- // Default to pip
438
- return 'pip';
439
- }
440
- detectDocsFolder(packagePath, fileTree, _manifestContent) {
441
- // Reuse the same logic as NodePackageParser
442
- const nodeParser = new NodePackageParser();
443
- return nodeParser.detectDocsFolder(packagePath, fileTree, {});
444
- }
445
- }
446
- // Rust Cargo.toml parser - simplified version
447
- class CargoPackageParser {
448
- manifestFileName = 'Cargo.toml';
449
- packageType = 'cargo';
450
- canParse(filename) {
451
- return filename.endsWith('Cargo.toml');
452
- }
453
- parseContent(rawContent) {
454
- try {
455
- return TOML.load(rawContent);
456
- }
457
- catch {
458
- return null;
459
- }
460
- }
461
- extractPackageData(content, path) {
462
- const packageData = content.package;
463
- if (!packageData)
464
- return null;
465
- // Extract package directory path from manifest path
466
- let packagePath;
467
- if (path === 'Cargo.toml') {
468
- packagePath = ''; // Root directory - empty string for root
469
- }
470
- else if (path.endsWith('/Cargo.toml')) {
471
- packagePath = path.slice(0, -11); // Remove '/Cargo.toml'
472
- }
473
- else {
474
- // Shouldn't happen, but handle gracefully
475
- packagePath = path;
476
- }
477
- // Extract dependencies
478
- const dependencies = {};
479
- const devDependencies = {};
480
- if (content.dependencies) {
481
- Object.entries(content.dependencies).forEach(([key, value]) => {
482
- dependencies[key] = typeof value === 'string' ? value : JSON.stringify(value);
483
- });
484
- }
485
- if (content['dev-dependencies']) {
486
- Object.entries(content['dev-dependencies']).forEach(([key, value]) => {
487
- devDependencies[key] = typeof value === 'string' ? value : JSON.stringify(value);
488
- });
489
- }
490
- return {
491
- name: packageData.name || 'unnamed',
492
- version: packageData.version,
493
- path: packagePath,
494
- manifestPath: path, // The full relative path to Cargo.toml
495
- packageManager: 'cargo',
496
- dependencies,
497
- devDependencies,
498
- peerDependencies: {},
499
- isMonorepoRoot: !!content.workspace,
500
- isWorkspace: false,
501
- parentPackage: undefined,
502
- availableCommands: [],
503
- };
504
- }
505
- detectWorkspaces(content) {
506
- return content.workspace?.members || null;
507
- }
508
- detectPackageManager() {
509
- return 'cargo';
510
- }
511
- detectDocsFolder(packagePath, fileTree, _manifestContent) {
512
- // Reuse the same logic as NodePackageParser
513
- const nodeParser = new NodePackageParser();
514
- return nodeParser.detectDocsFolder(packagePath, fileTree, {});
515
- }
516
- }
517
- export class PackageLayerModule {
518
- parsers = [
519
- new NodePackageParser(),
520
- new PythonPackageParser(),
521
- new CargoPackageParser(),
522
- ];
523
- /**
524
- * Extract package information from the file tree
525
- * @param fileTree - The file tree to analyze
526
- * @param fileReader - Optional function to read file contents. If not provided, manifest detection only.
527
- */
528
- async discoverPackages(fileTree, fileReader) {
529
- const packages = [];
530
- if (!fileTree.allFiles)
531
- return packages;
532
- // Find all package manifest files
533
- const manifestFiles = fileTree.allFiles.filter(file => {
534
- if (!file.path)
535
- return false;
536
- return this.parsers.some(parser => parser.canParse(file.path));
537
- });
538
- // Get all files for lock file detection
539
- const allFilePaths = fileTree.allFiles.map(f => f.path).filter(Boolean);
540
- for (const manifestFile of manifestFiles) {
541
- if (!manifestFile.path)
542
- continue;
543
- // Find appropriate parser
544
- const parser = this.parsers.find(p => p.canParse(manifestFile.path));
545
- if (!parser)
546
- continue;
547
- // Get manifest content
548
- let content = null;
549
- if (fileReader) {
550
- try {
551
- const fileContent = await fileReader(manifestFile.path);
552
- if (fileContent) {
553
- // Use the parser's parseContent method for type-safe parsing
554
- content = parser.parseContent(fileContent);
555
- }
556
- }
557
- catch (error) {
558
- console.warn(`Could not read or parse ${manifestFile.path}:`, error);
559
- continue;
560
- }
561
- }
562
- else {
563
- // If no file reader provided, create minimal package info from path
564
- const packageName = manifestFile.path.split('/').slice(-2, -1)[0] || 'unnamed';
565
- content = this.createMinimalManifest(parser.packageType, packageName);
566
- }
567
- if (!content)
568
- continue;
569
- // Extract package data
570
- const packageData = parser.extractPackageData(content, manifestFile.path);
571
- if (!packageData) {
572
- console.warn(`[PackageLayerModule] extractPackageData returned null for ${manifestFile.path}`, { content, parser: parser.packageType });
573
- continue;
574
- }
575
- // Detect package manager
576
- const dirPath = manifestFile.path.substring(0, manifestFile.path.lastIndexOf('/'));
577
- const lockFilesInDir = allFilePaths.filter(f => f.startsWith(dirPath) && this.isLockFile(f));
578
- packageData.packageManager = parser.detectPackageManager(content, lockFilesInDir);
579
- // Update commands with correct package manager for Node packages
580
- if (parser.packageType === 'node' && packageData.availableCommands) {
581
- // Only replace if we detected a valid package manager
582
- if (packageData.packageManager &&
583
- packageData.packageManager !== 'unknown' &&
584
- packageData.packageManager !== 'npm') {
585
- packageData.availableCommands = packageData.availableCommands.map(cmd => {
586
- // Update both script and standard commands
587
- if (cmd.command.includes('npm ')) {
588
- let pmCommand = cmd.command;
589
- // Handle different package manager syntaxes
590
- if (packageData.packageManager === 'yarn') {
591
- pmCommand = pmCommand
592
- .replace('npm run', 'yarn')
593
- .replace('npm install', 'yarn install')
594
- .replace('npm ci', 'yarn install --frozen-lockfile')
595
- .replace('npm update', 'yarn upgrade');
596
- }
597
- else if (packageData.packageManager === 'pnpm') {
598
- pmCommand = pmCommand.replace('npm', 'pnpm');
599
- }
600
- return { ...cmd, command: pmCommand };
601
- }
602
- return cmd;
603
- });
604
- }
605
- }
606
- // Create file set for the manifest
607
- const fileSet = {
608
- id: `package-manifest-${packageData.path}`,
609
- name: parser.manifestFileName,
610
- patterns: [
611
- {
612
- type: 'exact',
613
- pattern: manifestFile.path,
614
- description: `${parser.packageType} package manifest`,
615
- },
616
- ],
617
- matchedFiles: [manifestFile.path],
618
- fileCount: 1,
619
- };
620
- // Detect configuration files if the parser supports it
621
- let configFiles;
622
- if (parser.detectConfigs) {
623
- configFiles = parser.detectConfigs(packageData.path, fileTree, content);
624
- }
625
- // Detect documentation folder if the parser supports it
626
- let docsFolder;
627
- if (parser.detectDocsFolder) {
628
- docsFolder = parser.detectDocsFolder(packageData.path, fileTree, content);
629
- }
630
- // Calculate quality metrics from lens commands
631
- const qualityMetrics = packageData.availableCommands
632
- ? QualityMetricsCalculator.calculateQualityProfile(packageData.availableCommands)
633
- : undefined;
634
- // Create package layer
635
- const layer = {
636
- id: `package-${parser.packageType}-${packageData.path.replace(/[^a-zA-Z0-9-]/g, '-')}`,
637
- name: packageData.name,
638
- type: parser.packageType,
639
- enabled: true,
640
- derivedFrom: {
641
- fileSets: [fileSet],
642
- derivationType: 'content',
643
- description: `${parser.packageType} package defined in ${parser.manifestFileName}`,
644
- contentExtraction: {
645
- method: 'parse',
646
- parser: {
647
- format: parser.manifestFileName.endsWith('.json') ? 'json' : 'toml',
648
- paths: parser.packageType === 'node'
649
- ? ['']
650
- : parser.packageType === 'python'
651
- ? ['tool.poetry', 'project']
652
- : ['package'],
653
- },
654
- },
655
- },
656
- packageData,
657
- configFiles,
658
- docsFolder,
659
- qualityMetrics,
660
- pillar: 'foundationHealth',
661
- };
662
- packages.push(layer);
663
- }
664
- // Determine workspace relationships
665
- this.resolveWorkspaceRelationships(packages);
666
- return packages;
667
- }
668
- /**
669
- * Create package layers from workspace boundaries (backwards compatibility)
670
- */
671
- createPackageLayersFromBoundaries(boundaries) {
672
- return boundaries.map(boundary => {
673
- const fileSet = {
674
- id: `package-manifest-${boundary.rootPath || 'root'}`,
675
- name: 'package.json',
676
- patterns: [
677
- {
678
- type: 'exact',
679
- pattern: boundary.packageJsonPath,
680
- description: 'Node.js package manifest',
681
- },
682
- ],
683
- matchedFiles: [boundary.packageJsonPath],
684
- fileCount: 1,
685
- };
686
- const packageData = {
687
- name: boundary.packageData?.name || boundary.name,
688
- version: boundary.packageData?.version,
689
- path: boundary.rootPath || '.',
690
- manifestPath: boundary.packageJsonPath,
691
- packageManager: 'unknown',
692
- dependencies: {},
693
- devDependencies: {},
694
- peerDependencies: {},
695
- isMonorepoRoot: !!boundary.packageData?.workspaces,
696
- isWorkspace: !boundary.isRoot && boundaries.length > 1,
697
- parentPackage: boundary.isRoot ? undefined : 'root',
698
- };
699
- const layer = {
700
- id: `package-node-${boundary.id}`,
701
- name: packageData.name,
702
- type: 'node',
703
- enabled: true,
704
- derivedFrom: {
705
- fileSets: [fileSet],
706
- derivationType: 'content',
707
- description: 'Node.js package defined in package.json',
708
- },
709
- packageData,
710
- pillar: 'foundationHealth',
711
- };
712
- return layer;
713
- });
714
- }
715
- isLockFile(path) {
716
- const lockFiles = [
717
- 'package-lock.json',
718
- 'yarn.lock',
719
- 'pnpm-lock.yaml',
720
- 'poetry.lock',
721
- 'Pipfile.lock',
722
- 'Cargo.lock',
723
- ];
724
- return lockFiles.some(lock => path.endsWith(lock));
725
- }
726
- resolveWorkspaceRelationships(packages) {
727
- // Find root packages (typically at the repository root)
728
- const _rootPackages = packages.filter(p => p.packageData.path === '.' || p.packageData.path === '');
729
- // Mark workspace packages
730
- packages.forEach(pkg => {
731
- if (pkg.packageData.path !== '.' && pkg.packageData.path !== '') {
732
- pkg.packageData.isWorkspace = true;
733
- // Find parent package
734
- const parentPath = pkg.packageData.path.includes('/')
735
- ? pkg.packageData.path.substring(0, pkg.packageData.path.lastIndexOf('/'))
736
- : '.';
737
- const parent = packages.find(p => p.packageData.path === parentPath);
738
- if (parent) {
739
- pkg.packageData.parentPackage = parent.packageData.name;
740
- }
741
- }
742
- });
743
- }
744
- /**
745
- * Create a minimal manifest for when file content isn't available
746
- */
747
- createMinimalManifest(packageType, packageName) {
748
- switch (packageType) {
749
- case 'node':
750
- return {
751
- name: packageName,
752
- version: '0.0.0',
753
- dependencies: {},
754
- devDependencies: {},
755
- };
756
- case 'python':
757
- return {
758
- project: {
759
- name: packageName,
760
- version: '0.0.0',
761
- },
762
- };
763
- case 'cargo':
764
- return {
765
- package: {
766
- name: packageName,
767
- version: '0.0.0',
768
- },
769
- };
770
- default:
771
- return {};
772
- }
773
- }
774
- }
775
- //# sourceMappingURL=PackageLayerModule.js.map