@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,500 @@
|
|
|
1
|
+
import ignore from 'ignore';
|
|
2
|
+
import type { Ignore } from 'ignore';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
VersionControlProvider,
|
|
6
|
+
VersionControlLayer,
|
|
7
|
+
VersionControlIgnorePattern,
|
|
8
|
+
} from '../types/version-control-layer';
|
|
9
|
+
|
|
10
|
+
// Minimal file system interface needed by this provider and electron-react
|
|
11
|
+
export interface FileSystemAdapter {
|
|
12
|
+
readFile(path: string): Promise<{ content: string } | null>;
|
|
13
|
+
fileExists?(path: string): Promise<boolean>;
|
|
14
|
+
isDirectory?(path: string): Promise<boolean>;
|
|
15
|
+
readDirectory?(path: string): Promise<string[]>;
|
|
16
|
+
getFileStats?(path: string): Promise<{
|
|
17
|
+
size: number;
|
|
18
|
+
isDirectory: boolean;
|
|
19
|
+
lastModified: Date;
|
|
20
|
+
} | null>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Fast filtered file tree building - adapters must implement this efficiently
|
|
24
|
+
* for their environment (e.g., using fdir in Node.js, batch APIs for remote, etc.)
|
|
25
|
+
* @returns paths: Array of relative paths from directoryPath (directories end with '/')
|
|
26
|
+
* @returns stats: Optional map of path to file stats for performance
|
|
27
|
+
*/
|
|
28
|
+
buildFilteredFileTree(
|
|
29
|
+
directoryPath: string,
|
|
30
|
+
patterns?: string[], // Array of gitignore-style patterns to exclude
|
|
31
|
+
sourceDirectory?: string, // Optional source directory for scoped filtering
|
|
32
|
+
): Promise<{
|
|
33
|
+
paths: string[];
|
|
34
|
+
stats?: Map<
|
|
35
|
+
string,
|
|
36
|
+
{
|
|
37
|
+
size: number;
|
|
38
|
+
isDirectory: boolean;
|
|
39
|
+
lastModified: Date;
|
|
40
|
+
}
|
|
41
|
+
>;
|
|
42
|
+
}>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Git interface needed by this provider and electron-react
|
|
46
|
+
export interface GitAdapter {
|
|
47
|
+
getCurrentBranch?(repoPath: string): Promise<string | null>;
|
|
48
|
+
getCurrentCommit?(repoPath: string): Promise<string | null>;
|
|
49
|
+
getRemotes?(
|
|
50
|
+
repoPath: string,
|
|
51
|
+
): Promise<Array<{ name: string; url: string; type?: 'push' | 'fetch' | 'both' }>>;
|
|
52
|
+
getStatus?(
|
|
53
|
+
repoPath: string,
|
|
54
|
+
): Promise<{ modified: string[]; untracked: string[]; staged: string[] }>;
|
|
55
|
+
|
|
56
|
+
// Extended methods for electron-react
|
|
57
|
+
detectRepository?(path: string): Promise<{
|
|
58
|
+
isGitRepository: boolean;
|
|
59
|
+
currentBranch?: string;
|
|
60
|
+
owner?: string;
|
|
61
|
+
repo?: string;
|
|
62
|
+
} | null>;
|
|
63
|
+
watchGitRepository?(path: string): Promise<boolean>;
|
|
64
|
+
stopWatchingGit?(): Promise<void>;
|
|
65
|
+
onGitStatusChange?(
|
|
66
|
+
callback: (data: {
|
|
67
|
+
changedFiles: Array<{
|
|
68
|
+
path: string;
|
|
69
|
+
status: 'added' | 'modified' | 'deleted' | 'renamed';
|
|
70
|
+
lastModified?: Date;
|
|
71
|
+
}>;
|
|
72
|
+
}) => void,
|
|
73
|
+
): () => void;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Shell adapter interface for electron-react
|
|
77
|
+
export interface ShellAdapter {
|
|
78
|
+
openExternal(url: string): Promise<void>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Path utilities that work in both Node and browser environments
|
|
82
|
+
const path = {
|
|
83
|
+
join: (...parts: string[]) => {
|
|
84
|
+
const joined = parts.filter(p => p).join('/');
|
|
85
|
+
return joined.replace(/\/+/g, '/');
|
|
86
|
+
},
|
|
87
|
+
dirname: (p: string) => {
|
|
88
|
+
const parts = p.split('/');
|
|
89
|
+
parts.pop();
|
|
90
|
+
return parts.join('/') || '/';
|
|
91
|
+
},
|
|
92
|
+
relative: (from: string, to: string) => {
|
|
93
|
+
if (!from || from === '.') return to;
|
|
94
|
+
if (to.startsWith(from + '/')) return to.slice(from.length + 1);
|
|
95
|
+
if (to === from) return '.';
|
|
96
|
+
return to;
|
|
97
|
+
},
|
|
98
|
+
basename: (p: string) => {
|
|
99
|
+
const parts = p.split('/');
|
|
100
|
+
return parts[parts.length - 1] || '';
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Git implementation of VersionControlProvider for core library
|
|
106
|
+
* Handles .gitignore parsing and Git-specific operations
|
|
107
|
+
*/
|
|
108
|
+
export class GitVersionControlProvider implements VersionControlProvider {
|
|
109
|
+
constructor(
|
|
110
|
+
private fileSystemAdapter: FileSystemAdapter,
|
|
111
|
+
private gitAdapter?: GitAdapter,
|
|
112
|
+
) {}
|
|
113
|
+
|
|
114
|
+
async detect(directoryPath: string): Promise<boolean> {
|
|
115
|
+
// Check if .git directory exists
|
|
116
|
+
let currentPath = directoryPath;
|
|
117
|
+
|
|
118
|
+
while (currentPath && currentPath !== '/' && currentPath !== '.') {
|
|
119
|
+
const gitPath = path.join(currentPath, '.git');
|
|
120
|
+
|
|
121
|
+
// Try using isDirectory if available, otherwise check for .git/HEAD file
|
|
122
|
+
if (this.fileSystemAdapter.isDirectory) {
|
|
123
|
+
if (await this.fileSystemAdapter.isDirectory(gitPath)) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
} else if (this.fileSystemAdapter.fileExists) {
|
|
127
|
+
// Fallback: check for .git/HEAD file
|
|
128
|
+
const headPath = path.join(gitPath, 'HEAD');
|
|
129
|
+
if (await this.fileSystemAdapter.fileExists(headPath)) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Move up one directory
|
|
135
|
+
const parent = path.dirname(currentPath);
|
|
136
|
+
if (parent === currentPath) break; // Reached root
|
|
137
|
+
currentPath = parent;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async getRepositoryRoot(directoryPath: string): Promise<string | null> {
|
|
144
|
+
let currentPath = directoryPath;
|
|
145
|
+
|
|
146
|
+
while (currentPath && currentPath !== '/' && currentPath !== '.') {
|
|
147
|
+
const gitPath = path.join(currentPath, '.git');
|
|
148
|
+
|
|
149
|
+
// Check using same logic as detect
|
|
150
|
+
let isGitRepo = false;
|
|
151
|
+
if (this.fileSystemAdapter.isDirectory) {
|
|
152
|
+
isGitRepo = await this.fileSystemAdapter.isDirectory(gitPath);
|
|
153
|
+
} else if (this.fileSystemAdapter.fileExists) {
|
|
154
|
+
const headPath = path.join(gitPath, 'HEAD');
|
|
155
|
+
isGitRepo = await this.fileSystemAdapter.fileExists(headPath);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (isGitRepo) {
|
|
159
|
+
return currentPath;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Move up one directory
|
|
163
|
+
const parent = path.dirname(currentPath);
|
|
164
|
+
if (parent === currentPath) break; // Reached root
|
|
165
|
+
currentPath = parent;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async createLayer(repositoryRoot: string): Promise<VersionControlLayer> {
|
|
172
|
+
// PERFORMANCE OPTIMIZATION: Return minimal git metadata since buildFilteredFileTree
|
|
173
|
+
// now handles all filtering. This avoids expensive file reads for .git/HEAD, .git/config, etc.
|
|
174
|
+
|
|
175
|
+
const ignorePatterns = await this.extractIgnorePatterns(repositoryRoot);
|
|
176
|
+
|
|
177
|
+
// Return default values instead of reading git files
|
|
178
|
+
const currentBranch = 'main'; // Default branch name
|
|
179
|
+
const currentRevision = 'unknown'; // Skip expensive git file reads
|
|
180
|
+
const remotes: Array<{ name: string; url: string; type: 'push' | 'fetch' | 'both' }> = []; // Empty remotes
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
id: `vcs-git-${repositoryRoot.replace(/[^a-zA-Z0-9]/g, '-')}`,
|
|
184
|
+
name: 'Git Version Control',
|
|
185
|
+
type: 'version-control',
|
|
186
|
+
enabled: true,
|
|
187
|
+
workspaceRoot: repositoryRoot,
|
|
188
|
+
vcsData: {
|
|
189
|
+
system: 'git',
|
|
190
|
+
repositoryRoot,
|
|
191
|
+
currentBranch: currentBranch || 'unknown',
|
|
192
|
+
currentRevision: currentRevision || 'unknown',
|
|
193
|
+
remotes: remotes || [],
|
|
194
|
+
ignorePatterns,
|
|
195
|
+
},
|
|
196
|
+
derivedFrom: {
|
|
197
|
+
fileSets: [
|
|
198
|
+
{
|
|
199
|
+
id: 'git-files',
|
|
200
|
+
name: '.git directory and .gitignore files',
|
|
201
|
+
patterns: [
|
|
202
|
+
{ type: 'glob', pattern: '.git/**' },
|
|
203
|
+
{ type: 'glob', pattern: '**/.gitignore' },
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
derivationType: 'content',
|
|
208
|
+
description: 'Git version control metadata and ignore patterns',
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Extract all .gitignore patterns from the repository
|
|
215
|
+
*/
|
|
216
|
+
async extractIgnorePatterns(repositoryRoot: string): Promise<VersionControlIgnorePattern[]> {
|
|
217
|
+
const patterns: VersionControlIgnorePattern[] = [];
|
|
218
|
+
|
|
219
|
+
// Always add .git directory as ignored
|
|
220
|
+
patterns.push({
|
|
221
|
+
pattern: '.git',
|
|
222
|
+
type: 'exact',
|
|
223
|
+
source: 'implicit',
|
|
224
|
+
scope: 'repository',
|
|
225
|
+
negated: false,
|
|
226
|
+
description: 'Git metadata directory',
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Parse root .gitignore
|
|
230
|
+
const rootGitignorePath = path.join(repositoryRoot, '.gitignore');
|
|
231
|
+
const rootPatterns = await this.parseGitignoreFile(rootGitignorePath, repositoryRoot);
|
|
232
|
+
patterns.push(...rootPatterns);
|
|
233
|
+
|
|
234
|
+
// Find and parse subdirectory .gitignore files
|
|
235
|
+
if (this.fileSystemAdapter.readDirectory) {
|
|
236
|
+
const subdirPatterns = await this.findAndParseGitignoreFiles(repositoryRoot, repositoryRoot);
|
|
237
|
+
patterns.push(...subdirPatterns);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return patterns;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Recursively find and parse .gitignore files
|
|
245
|
+
*/
|
|
246
|
+
private async findAndParseGitignoreFiles(
|
|
247
|
+
currentPath: string,
|
|
248
|
+
repositoryRoot: string,
|
|
249
|
+
): Promise<VersionControlIgnorePattern[]> {
|
|
250
|
+
const patterns: VersionControlIgnorePattern[] = [];
|
|
251
|
+
|
|
252
|
+
if (!this.fileSystemAdapter.readDirectory) {
|
|
253
|
+
return patterns;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
const entries = await this.fileSystemAdapter.readDirectory(currentPath);
|
|
258
|
+
|
|
259
|
+
for (const entry of entries) {
|
|
260
|
+
const fullPath = path.join(currentPath, entry);
|
|
261
|
+
|
|
262
|
+
// Skip .git directory
|
|
263
|
+
if (entry === '.git') continue;
|
|
264
|
+
|
|
265
|
+
// Check for .gitignore file
|
|
266
|
+
if (entry === '.gitignore' && currentPath !== repositoryRoot) {
|
|
267
|
+
const gitignorePatterns = await this.parseGitignoreFile(fullPath, repositoryRoot);
|
|
268
|
+
patterns.push(...gitignorePatterns);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Recursively check subdirectories
|
|
272
|
+
if (
|
|
273
|
+
this.fileSystemAdapter.isDirectory &&
|
|
274
|
+
(await this.fileSystemAdapter.isDirectory(fullPath))
|
|
275
|
+
) {
|
|
276
|
+
const subdirPatterns = await this.findAndParseGitignoreFiles(fullPath, repositoryRoot);
|
|
277
|
+
patterns.push(...subdirPatterns);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
} catch (error) {
|
|
281
|
+
console.warn(`Error reading directory ${currentPath}:`, error);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return patterns;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Parse a .gitignore file and return patterns using the ignore library
|
|
289
|
+
*/
|
|
290
|
+
private async parseGitignoreFile(
|
|
291
|
+
gitignorePath: string,
|
|
292
|
+
repositoryRoot: string,
|
|
293
|
+
): Promise<VersionControlIgnorePattern[]> {
|
|
294
|
+
const patterns: VersionControlIgnorePattern[] = [];
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
const result = await this.fileSystemAdapter.readFile(gitignorePath);
|
|
298
|
+
if (!result) return patterns;
|
|
299
|
+
|
|
300
|
+
const content = result.content;
|
|
301
|
+
const lines = content.split('\n');
|
|
302
|
+
const relativePath = path.relative(repositoryRoot, path.dirname(gitignorePath));
|
|
303
|
+
// For root gitignore, use "./" prefix to show it's in the current directory
|
|
304
|
+
const source = relativePath === '.' ? './.gitignore' : `${relativePath}/.gitignore`;
|
|
305
|
+
|
|
306
|
+
// Use ignore library to validate patterns as we process them
|
|
307
|
+
const ig = ignore();
|
|
308
|
+
|
|
309
|
+
for (const line of lines) {
|
|
310
|
+
const trimmed = line.trim();
|
|
311
|
+
|
|
312
|
+
// Skip empty lines and comments
|
|
313
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
314
|
+
|
|
315
|
+
// Handle negation patterns
|
|
316
|
+
const negated = trimmed.startsWith('!');
|
|
317
|
+
const pattern = negated ? trimmed.slice(1) : trimmed;
|
|
318
|
+
|
|
319
|
+
// Validate pattern using ignore library
|
|
320
|
+
try {
|
|
321
|
+
// Test if the pattern is valid by trying to add it to ignore
|
|
322
|
+
const testIgnore = ignore();
|
|
323
|
+
testIgnore.add([trimmed]); // Add the original line (with ! if present)
|
|
324
|
+
|
|
325
|
+
// Determine pattern type based on content
|
|
326
|
+
const hasGlobChars = /[*?[\]]/.test(pattern);
|
|
327
|
+
const hasSlash = pattern.includes('/');
|
|
328
|
+
const patternType = hasGlobChars || hasSlash ? 'glob' : 'exact';
|
|
329
|
+
|
|
330
|
+
// Determine scope
|
|
331
|
+
const scope = source === '.gitignore' ? 'repository' : 'directory';
|
|
332
|
+
|
|
333
|
+
patterns.push({
|
|
334
|
+
pattern: pattern,
|
|
335
|
+
type: patternType,
|
|
336
|
+
source,
|
|
337
|
+
scope,
|
|
338
|
+
negated,
|
|
339
|
+
description: `From ${source}`,
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// Add valid patterns to our ignore instance for potential future use
|
|
343
|
+
ig.add([trimmed]);
|
|
344
|
+
} catch (err) {
|
|
345
|
+
console.warn(`Invalid gitignore pattern: ${trimmed} in ${source}`, err);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
} catch (error) {
|
|
349
|
+
console.warn(`Error reading gitignore file ${gitignorePath}:`, error);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return patterns;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Create an ignore instance from patterns for efficient filtering
|
|
357
|
+
* This can be used by FilesystemService or other components
|
|
358
|
+
*/
|
|
359
|
+
createIgnoreFromPatterns(patterns: VersionControlIgnorePattern[]): Ignore | null {
|
|
360
|
+
const ig = ignore();
|
|
361
|
+
|
|
362
|
+
// Convert patterns back to gitignore format
|
|
363
|
+
const gitignoreLines = patterns.map(p => (p.negated ? `!${p.pattern}` : p.pattern));
|
|
364
|
+
|
|
365
|
+
if (gitignoreLines.length > 0) {
|
|
366
|
+
ig.add(gitignoreLines);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return ig;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Get current branch name
|
|
374
|
+
*/
|
|
375
|
+
async getCurrentBranch(repositoryRoot: string): Promise<string | null> {
|
|
376
|
+
// First try using GitAdapter if available
|
|
377
|
+
if (this.gitAdapter?.getCurrentBranch) {
|
|
378
|
+
try {
|
|
379
|
+
return await this.gitAdapter.getCurrentBranch(repositoryRoot);
|
|
380
|
+
} catch (error) {
|
|
381
|
+
console.warn('Error getting branch via GitAdapter:', error);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Fallback to reading .git/HEAD file
|
|
386
|
+
try {
|
|
387
|
+
const headPath = path.join(repositoryRoot, '.git', 'HEAD');
|
|
388
|
+
const result = await this.fileSystemAdapter.readFile(headPath);
|
|
389
|
+
if (!result) return null;
|
|
390
|
+
|
|
391
|
+
const content = result.content.trim();
|
|
392
|
+
if (content.startsWith('ref: refs/heads/')) {
|
|
393
|
+
return content.replace('ref: refs/heads/', '');
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Detached HEAD state
|
|
397
|
+
return 'HEAD';
|
|
398
|
+
} catch (error) {
|
|
399
|
+
console.warn('Error reading .git/HEAD:', error);
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Get current commit SHA
|
|
406
|
+
*/
|
|
407
|
+
async getCurrentRevision(repositoryRoot: string): Promise<string | null> {
|
|
408
|
+
// First try using GitAdapter if available
|
|
409
|
+
if (this.gitAdapter?.getCurrentCommit) {
|
|
410
|
+
try {
|
|
411
|
+
return await this.gitAdapter.getCurrentCommit(repositoryRoot);
|
|
412
|
+
} catch (error) {
|
|
413
|
+
console.warn('Error getting commit via GitAdapter:', error);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Fallback to reading from .git files
|
|
418
|
+
try {
|
|
419
|
+
const headPath = path.join(repositoryRoot, '.git', 'HEAD');
|
|
420
|
+
const headResult = await this.fileSystemAdapter.readFile(headPath);
|
|
421
|
+
if (!headResult) return null;
|
|
422
|
+
|
|
423
|
+
const headContent = headResult.content.trim();
|
|
424
|
+
|
|
425
|
+
// If HEAD points to a ref, read that ref
|
|
426
|
+
if (headContent.startsWith('ref: ')) {
|
|
427
|
+
const ref = headContent.replace('ref: ', '');
|
|
428
|
+
const refPath = path.join(repositoryRoot, '.git', ref);
|
|
429
|
+
const refResult = await this.fileSystemAdapter.readFile(refPath);
|
|
430
|
+
if (refResult) {
|
|
431
|
+
return refResult.content.trim().substring(0, 7); // Short SHA
|
|
432
|
+
}
|
|
433
|
+
} else {
|
|
434
|
+
// Direct commit SHA in HEAD (detached state)
|
|
435
|
+
return headContent.substring(0, 7);
|
|
436
|
+
}
|
|
437
|
+
} catch (error) {
|
|
438
|
+
console.warn('Error reading git revision:', error);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Get remote repositories
|
|
446
|
+
*/
|
|
447
|
+
async getRemotes(
|
|
448
|
+
repositoryRoot: string,
|
|
449
|
+
): Promise<Array<{ name: string; url: string; type: 'push' | 'fetch' | 'both' }>> {
|
|
450
|
+
// First try using GitAdapter if available
|
|
451
|
+
if (this.gitAdapter?.getRemotes) {
|
|
452
|
+
try {
|
|
453
|
+
const remotes = await this.gitAdapter.getRemotes(repositoryRoot);
|
|
454
|
+
// Ensure all remotes have a type, default to 'both' if not specified
|
|
455
|
+
return remotes.map(remote => ({
|
|
456
|
+
name: remote.name,
|
|
457
|
+
url: remote.url,
|
|
458
|
+
type: remote.type || 'both',
|
|
459
|
+
}));
|
|
460
|
+
} catch (error) {
|
|
461
|
+
console.warn('Error getting remotes via GitAdapter:', error);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Fallback to reading .git/config file
|
|
466
|
+
try {
|
|
467
|
+
const configPath = path.join(repositoryRoot, '.git', 'config');
|
|
468
|
+
const result = await this.fileSystemAdapter.readFile(configPath);
|
|
469
|
+
if (!result) return [];
|
|
470
|
+
|
|
471
|
+
const remotes: Array<{ name: string; url: string; type: 'push' | 'fetch' | 'both' }> = [];
|
|
472
|
+
const lines = result.content.split('\n');
|
|
473
|
+
let currentRemote: string | null = null;
|
|
474
|
+
|
|
475
|
+
for (const line of lines) {
|
|
476
|
+
const trimmed = line.trim();
|
|
477
|
+
|
|
478
|
+
// Check for remote section
|
|
479
|
+
const remoteMatch = trimmed.match(/^\[remote "(.+)"\]$/);
|
|
480
|
+
if (remoteMatch) {
|
|
481
|
+
currentRemote = remoteMatch[1];
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Extract URL for current remote
|
|
486
|
+
if (currentRemote && trimmed.startsWith('url = ')) {
|
|
487
|
+
const url = trimmed.replace('url = ', '');
|
|
488
|
+
// Git remotes are typically used for both fetch and push unless specified otherwise
|
|
489
|
+
remotes.push({ name: currentRemote, url, type: 'both' });
|
|
490
|
+
currentRemote = null;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return remotes;
|
|
495
|
+
} catch (error) {
|
|
496
|
+
console.warn('Error reading .git/config:', error);
|
|
497
|
+
return [];
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package management API provider for checking versions, vulnerabilities, and licenses
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type PackageManager = 'npm' | 'pnpm' | 'yarn';
|
|
6
|
+
|
|
7
|
+
export interface PackageVersionInfo {
|
|
8
|
+
name: string;
|
|
9
|
+
currentVersion: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface VersionCheckResult {
|
|
13
|
+
packageName: string;
|
|
14
|
+
currentVersion: string;
|
|
15
|
+
latestVersion?: string;
|
|
16
|
+
updateType?: 'major' | 'minor' | 'patch' | 'none';
|
|
17
|
+
isOutdated: boolean;
|
|
18
|
+
isDeprecated?: boolean;
|
|
19
|
+
deprecationMessage?: string;
|
|
20
|
+
error?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface VulnerabilityInfo {
|
|
24
|
+
id: string;
|
|
25
|
+
severity: 'low' | 'moderate' | 'high' | 'critical';
|
|
26
|
+
title: string;
|
|
27
|
+
description?: string;
|
|
28
|
+
fixAvailable?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface VulnerabilityCheckResult {
|
|
32
|
+
packageName: string;
|
|
33
|
+
version: string;
|
|
34
|
+
vulnerabilities: VulnerabilityInfo[];
|
|
35
|
+
error?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface LicenseInfo {
|
|
39
|
+
license: string;
|
|
40
|
+
licenseType: 'permissive' | 'copyleft' | 'proprietary' | 'unknown';
|
|
41
|
+
requiresAttribution: boolean;
|
|
42
|
+
requiresShareAlike: boolean;
|
|
43
|
+
allowsCommercialUse: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface LicenseCheckResult {
|
|
47
|
+
packageName: string;
|
|
48
|
+
version: string;
|
|
49
|
+
license?: LicenseInfo;
|
|
50
|
+
error?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface DependencyCheckProgress {
|
|
54
|
+
phase:
|
|
55
|
+
| 'idle'
|
|
56
|
+
| 'counting'
|
|
57
|
+
| 'checking-versions'
|
|
58
|
+
| 'checking-vulnerabilities'
|
|
59
|
+
| 'checking-licenses'
|
|
60
|
+
| 'complete';
|
|
61
|
+
current: number;
|
|
62
|
+
total: number;
|
|
63
|
+
message?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface BatchCheckOptions {
|
|
67
|
+
batchSize?: number; // How many packages to check at once
|
|
68
|
+
maxConcurrent?: number; // Max concurrent API calls
|
|
69
|
+
skipCache?: boolean; // Force fresh checks
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Abstract provider for package manager API operations
|
|
74
|
+
* Implementations can use direct API calls, server proxies, or mock data
|
|
75
|
+
*/
|
|
76
|
+
export abstract class PackageManagerApiProvider {
|
|
77
|
+
/**
|
|
78
|
+
* Check versions for multiple packages, yielding results as they complete
|
|
79
|
+
*/
|
|
80
|
+
abstract checkVersions(
|
|
81
|
+
packages: PackageVersionInfo[],
|
|
82
|
+
packageManager: PackageManager,
|
|
83
|
+
options?: BatchCheckOptions,
|
|
84
|
+
): AsyncGenerator<VersionCheckResult, void, unknown>;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check vulnerabilities for multiple packages, yielding results as they complete
|
|
88
|
+
*/
|
|
89
|
+
abstract checkVulnerabilities(
|
|
90
|
+
packages: PackageVersionInfo[],
|
|
91
|
+
packageManager: PackageManager,
|
|
92
|
+
options?: BatchCheckOptions,
|
|
93
|
+
): AsyncGenerator<VulnerabilityCheckResult, void, unknown>;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Check licenses for multiple packages, yielding results as they complete
|
|
97
|
+
*/
|
|
98
|
+
abstract checkLicenses(
|
|
99
|
+
packages: PackageVersionInfo[],
|
|
100
|
+
packageManager: PackageManager,
|
|
101
|
+
options?: BatchCheckOptions,
|
|
102
|
+
): AsyncGenerator<LicenseCheckResult, void, unknown>;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get overall progress of the current check operation
|
|
106
|
+
*/
|
|
107
|
+
abstract getProgress(): DependencyCheckProgress;
|
|
108
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Layer Providers
|
|
2
|
+
|
|
3
|
+
This directory contains provider implementations for the layer system.
|
|
4
|
+
|
|
5
|
+
## GitVersionControlProvider
|
|
6
|
+
|
|
7
|
+
A Git implementation of the `VersionControlProvider` interface that:
|
|
8
|
+
|
|
9
|
+
- Detects Git repositories by looking for `.git` directories
|
|
10
|
+
- Parses `.gitignore` files throughout the repository
|
|
11
|
+
- Extracts branch, commit, and remote information
|
|
12
|
+
- Creates `VersionControlLayer` objects with Git metadata
|
|
13
|
+
|
|
14
|
+
### Usage with Electron-React
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import {
|
|
18
|
+
VersionControlLayerFactory,
|
|
19
|
+
GitVersionControlProvider,
|
|
20
|
+
FileSystemModule,
|
|
21
|
+
} from 'core/layers';
|
|
22
|
+
import { ElectronPlatformAdapters } from '../adapters';
|
|
23
|
+
|
|
24
|
+
// Use electron's existing adapters
|
|
25
|
+
const adapters = new ElectronPlatformAdapters();
|
|
26
|
+
|
|
27
|
+
// Create Git provider using electron's adapters
|
|
28
|
+
const gitProvider = new GitVersionControlProvider(
|
|
29
|
+
adapters.fileSystem, // ElectronFileSystemAdapter
|
|
30
|
+
adapters.git, // ElectronGitAdapter (optional)
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
// Register with factory
|
|
34
|
+
const vcsFactory = new VersionControlLayerFactory();
|
|
35
|
+
vcsFactory.registerProvider('git', gitProvider);
|
|
36
|
+
|
|
37
|
+
// Use in FileSystemModule
|
|
38
|
+
const fsModule = new FileSystemModule({
|
|
39
|
+
directoryPath: '/path/to/repo',
|
|
40
|
+
buildFileSystemTree: async (path, filters) => {
|
|
41
|
+
// ... your implementation
|
|
42
|
+
},
|
|
43
|
+
versionControlLayerFactory: vcsFactory,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Load filesystem with VCS awareness
|
|
47
|
+
const result = await fsModule.loadFileSystemTree();
|
|
48
|
+
// result.versionControlLayer will contain Git metadata
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Adapter Requirements
|
|
52
|
+
|
|
53
|
+
The `GitVersionControlProvider` requires a `FileSystemAdapter` with these methods:
|
|
54
|
+
|
|
55
|
+
- `readFile(path)` - Read file contents
|
|
56
|
+
- `fileExists(path)` - Check if file exists (optional)
|
|
57
|
+
- `isDirectory(path)` - Check if path is directory (optional)
|
|
58
|
+
- `readDirectory(path)` - List directory contents (optional)
|
|
59
|
+
|
|
60
|
+
Optionally, you can provide a `GitAdapter` for more accurate Git operations:
|
|
61
|
+
|
|
62
|
+
- `getCurrentBranch(repoPath)` - Get current branch name
|
|
63
|
+
- `getCurrentCommit(repoPath)` - Get current commit SHA
|
|
64
|
+
- `getRemotes(repoPath)` - Get remote repositories
|
|
65
|
+
- `getStatus(repoPath)` - Get working tree status
|
|
66
|
+
|
|
67
|
+
### Features
|
|
68
|
+
|
|
69
|
+
- **Repository Detection**: Walks up directory tree to find `.git` directory
|
|
70
|
+
- **Ignore Pattern Parsing**: Recursively finds and parses all `.gitignore` files
|
|
71
|
+
- **Pattern Types**: Supports glob, exact, and regex patterns
|
|
72
|
+
- **Pattern Scopes**: Repository-wide or directory-specific patterns
|
|
73
|
+
- **Negation Support**: Handles `!` negation patterns in `.gitignore`
|
|
74
|
+
- **Fallback Logic**: Falls back to file reading when Git adapter not available
|
|
75
|
+
|
|
76
|
+
### Integration with Shared-Lib
|
|
77
|
+
|
|
78
|
+
This provider is compatible with the same interfaces used by `markdown-slides-shared-lib/workspace`,
|
|
79
|
+
making it easy to migrate from shared-lib's `GitVersionControlProvider` to this one:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// Before (using shared-lib)
|
|
83
|
+
import { GitVersionControlProvider } from 'markdown-slides-shared-lib/workspace';
|
|
84
|
+
|
|
85
|
+
// After (using core)
|
|
86
|
+
import { GitVersionControlProvider } from 'core/layers';
|
|
87
|
+
// Same usage, same interface!
|
|
88
|
+
```
|