@getmikk/core 1.2.0 → 1.3.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 +431 -0
- package/package.json +6 -2
- package/src/contract/contract-generator.ts +85 -85
- package/src/contract/contract-reader.ts +28 -28
- package/src/contract/contract-writer.ts +114 -114
- package/src/contract/index.ts +12 -12
- package/src/contract/lock-compiler.ts +221 -221
- package/src/contract/lock-reader.ts +34 -34
- package/src/contract/schema.ts +147 -147
- package/src/graph/cluster-detector.ts +312 -312
- package/src/graph/graph-builder.ts +211 -211
- package/src/graph/impact-analyzer.ts +55 -55
- package/src/graph/index.ts +4 -4
- package/src/graph/types.ts +59 -59
- package/src/hash/file-hasher.ts +30 -30
- package/src/hash/hash-store.ts +119 -119
- package/src/hash/index.ts +3 -3
- package/src/hash/tree-hasher.ts +20 -20
- package/src/index.ts +12 -12
- package/src/parser/base-parser.ts +16 -16
- package/src/parser/boundary-checker.ts +211 -211
- package/src/parser/index.ts +46 -46
- package/src/parser/types.ts +90 -90
- package/src/parser/typescript/ts-extractor.ts +543 -543
- package/src/parser/typescript/ts-parser.ts +41 -41
- package/src/parser/typescript/ts-resolver.ts +86 -86
- package/src/utils/errors.ts +42 -42
- package/src/utils/fs.ts +75 -75
- package/src/utils/fuzzy-match.ts +186 -186
- package/src/utils/logger.ts +36 -36
- package/src/utils/minimatch.ts +19 -19
- package/tests/contract.test.ts +134 -134
- package/tests/fixtures/simple-api/package.json +5 -5
- package/tests/fixtures/simple-api/src/auth/middleware.ts +9 -9
- package/tests/fixtures/simple-api/src/auth/verify.ts +6 -6
- package/tests/fixtures/simple-api/src/index.ts +9 -9
- package/tests/fixtures/simple-api/src/utils/jwt.ts +3 -3
- package/tests/fixtures/simple-api/tsconfig.json +8 -8
- package/tests/fuzzy-match.test.ts +142 -142
- package/tests/graph.test.ts +169 -169
- package/tests/hash.test.ts +49 -49
- package/tests/helpers.ts +83 -83
- package/tests/parser.test.ts +218 -218
- package/tsconfig.json +15 -15
|
@@ -1,221 +1,221 @@
|
|
|
1
|
-
import * as path from 'node:path'
|
|
2
|
-
import { createHash } from 'node:crypto'
|
|
3
|
-
import type { MikkContract, MikkLock } from './schema.js'
|
|
4
|
-
import type { DependencyGraph } from '../graph/types.js'
|
|
5
|
-
import type { ParsedFile } from '../parser/types.js'
|
|
6
|
-
import { hashContent } from '../hash/file-hasher.js'
|
|
7
|
-
import { computeModuleHash, computeRootHash } from '../hash/tree-hasher.js'
|
|
8
|
-
import { minimatch } from '../utils/minimatch.js'
|
|
9
|
-
|
|
10
|
-
const VERSION = '
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* LockCompiler — takes a DependencyGraph and a MikkContract
|
|
14
|
-
* and compiles the complete mikk.lock.json.
|
|
15
|
-
*/
|
|
16
|
-
export class LockCompiler {
|
|
17
|
-
/** Main entry — compile full lock from graph + contract + parsed files */
|
|
18
|
-
compile(
|
|
19
|
-
graph: DependencyGraph,
|
|
20
|
-
contract: MikkContract,
|
|
21
|
-
parsedFiles: ParsedFile[]
|
|
22
|
-
): MikkLock {
|
|
23
|
-
const functions = this.compileFunctions(graph, contract)
|
|
24
|
-
const classes = this.compileClasses(graph, contract)
|
|
25
|
-
const generics = this.compileGenerics(graph, contract)
|
|
26
|
-
const modules = this.compileModules(contract, parsedFiles)
|
|
27
|
-
const files = this.compileFiles(parsedFiles, contract)
|
|
28
|
-
|
|
29
|
-
const moduleHashes: Record<string, string> = {}
|
|
30
|
-
for (const [id, mod] of Object.entries(modules)) {
|
|
31
|
-
moduleHashes[id] = mod.hash
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const lockData: MikkLock = {
|
|
35
|
-
version: '1.0.0',
|
|
36
|
-
generatedAt: new Date().toISOString(),
|
|
37
|
-
generatorVersion: VERSION,
|
|
38
|
-
projectRoot: contract.project.name,
|
|
39
|
-
syncState: {
|
|
40
|
-
status: 'clean',
|
|
41
|
-
lastSyncAt: new Date().toISOString(),
|
|
42
|
-
lockHash: '',
|
|
43
|
-
contractHash: hashContent(JSON.stringify(contract)),
|
|
44
|
-
},
|
|
45
|
-
modules,
|
|
46
|
-
functions,
|
|
47
|
-
classes: Object.keys(classes).length > 0 ? classes : undefined,
|
|
48
|
-
generics: Object.keys(generics).length > 0 ? generics : undefined,
|
|
49
|
-
files,
|
|
50
|
-
graph: {
|
|
51
|
-
nodes: graph.nodes.size,
|
|
52
|
-
edges: graph.edges.length,
|
|
53
|
-
rootHash: computeRootHash(moduleHashes),
|
|
54
|
-
},
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Compute overall lock hash from the compiled data
|
|
58
|
-
lockData.syncState.lockHash = hashContent(JSON.stringify({
|
|
59
|
-
functions: lockData.functions,
|
|
60
|
-
classes: lockData.classes,
|
|
61
|
-
generics: lockData.generics,
|
|
62
|
-
modules: lockData.modules,
|
|
63
|
-
files: lockData.files,
|
|
64
|
-
}))
|
|
65
|
-
|
|
66
|
-
return lockData
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** Compile function entries, assigning each to its module */
|
|
70
|
-
private compileFunctions(
|
|
71
|
-
graph: DependencyGraph,
|
|
72
|
-
contract: MikkContract
|
|
73
|
-
): Record<string, MikkLock['functions'][string]> {
|
|
74
|
-
const result: Record<string, MikkLock['functions'][string]> = {}
|
|
75
|
-
|
|
76
|
-
for (const [id, node] of graph.nodes) {
|
|
77
|
-
if (node.type !== 'function') continue
|
|
78
|
-
|
|
79
|
-
const moduleId = this.findModule(node.file, contract.declared.modules)
|
|
80
|
-
const inEdges = graph.inEdges.get(id) || []
|
|
81
|
-
const outEdges = graph.outEdges.get(id) || []
|
|
82
|
-
|
|
83
|
-
result[id] = {
|
|
84
|
-
id,
|
|
85
|
-
name: node.label,
|
|
86
|
-
file: node.file,
|
|
87
|
-
startLine: node.metadata.startLine ?? 0,
|
|
88
|
-
endLine: node.metadata.endLine ?? 0,
|
|
89
|
-
hash: node.metadata.hash ?? '',
|
|
90
|
-
calls: outEdges.filter(e => e.type === 'calls').map(e => e.target),
|
|
91
|
-
calledBy: inEdges.filter(e => e.type === 'calls').map(e => e.source),
|
|
92
|
-
moduleId: moduleId || 'unknown',
|
|
93
|
-
purpose: node.metadata.purpose,
|
|
94
|
-
edgeCasesHandled: node.metadata.edgeCasesHandled,
|
|
95
|
-
errorHandling: node.metadata.errorHandling,
|
|
96
|
-
detailedLines: node.metadata.detailedLines,
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return result
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
private compileClasses(
|
|
104
|
-
graph: DependencyGraph,
|
|
105
|
-
contract: MikkContract
|
|
106
|
-
): Record<string, any> {
|
|
107
|
-
const result: Record<string, any> = {}
|
|
108
|
-
for (const [id, node] of graph.nodes) {
|
|
109
|
-
if (node.type !== 'class') continue
|
|
110
|
-
const moduleId = this.findModule(node.file, contract.declared.modules)
|
|
111
|
-
result[id] = {
|
|
112
|
-
id,
|
|
113
|
-
name: node.label,
|
|
114
|
-
file: node.file,
|
|
115
|
-
startLine: node.metadata.startLine ?? 0,
|
|
116
|
-
endLine: node.metadata.endLine ?? 0,
|
|
117
|
-
moduleId: moduleId || 'unknown',
|
|
118
|
-
isExported: node.metadata.isExported ?? false,
|
|
119
|
-
purpose: node.metadata.purpose,
|
|
120
|
-
edgeCasesHandled: node.metadata.edgeCasesHandled,
|
|
121
|
-
errorHandling: node.metadata.errorHandling,
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return result
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
private compileGenerics(
|
|
128
|
-
graph: DependencyGraph,
|
|
129
|
-
contract: MikkContract
|
|
130
|
-
): Record<string, any> {
|
|
131
|
-
const result: Record<string, any> = {}
|
|
132
|
-
for (const [id, node] of graph.nodes) {
|
|
133
|
-
if (node.type !== 'generic') continue
|
|
134
|
-
const moduleId = this.findModule(node.file, contract.declared.modules)
|
|
135
|
-
result[id] = {
|
|
136
|
-
id,
|
|
137
|
-
name: node.label,
|
|
138
|
-
type: node.metadata.hash ?? 'generic', // we stored type name in hash
|
|
139
|
-
file: node.file,
|
|
140
|
-
startLine: node.metadata.startLine ?? 0,
|
|
141
|
-
endLine: node.metadata.endLine ?? 0,
|
|
142
|
-
moduleId: moduleId || 'unknown',
|
|
143
|
-
isExported: node.metadata.isExported ?? false,
|
|
144
|
-
purpose: node.metadata.purpose,
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return result
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/** Compile module entries from contract definitions */
|
|
151
|
-
private compileModules(
|
|
152
|
-
contract: MikkContract,
|
|
153
|
-
parsedFiles: ParsedFile[]
|
|
154
|
-
): Record<string, MikkLock['modules'][string]> {
|
|
155
|
-
const result: Record<string, MikkLock['modules'][string]> = {}
|
|
156
|
-
|
|
157
|
-
for (const module of contract.declared.modules) {
|
|
158
|
-
const moduleFiles = parsedFiles
|
|
159
|
-
.filter(f => this.fileMatchesModule(f.path, module.paths))
|
|
160
|
-
.map(f => f.path)
|
|
161
|
-
|
|
162
|
-
const fileHashes = moduleFiles.map(f => {
|
|
163
|
-
const parsed = parsedFiles.find(pf => pf.path === f)
|
|
164
|
-
return parsed?.hash ?? ''
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
result[module.id] = {
|
|
168
|
-
id: module.id,
|
|
169
|
-
files: moduleFiles,
|
|
170
|
-
hash: computeModuleHash(fileHashes),
|
|
171
|
-
fragmentPath: `.mikk/fragments/${module.id}.lock`,
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return result
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/** Compile file entries */
|
|
179
|
-
private compileFiles(
|
|
180
|
-
parsedFiles: ParsedFile[],
|
|
181
|
-
contract: MikkContract
|
|
182
|
-
): Record<string, MikkLock['files'][string]> {
|
|
183
|
-
const result: Record<string, MikkLock['files'][string]> = {}
|
|
184
|
-
|
|
185
|
-
for (const file of parsedFiles) {
|
|
186
|
-
const moduleId = this.findModule(file.path, contract.declared.modules)
|
|
187
|
-
result[file.path] = {
|
|
188
|
-
path: file.path,
|
|
189
|
-
hash: file.hash,
|
|
190
|
-
moduleId: moduleId || 'unknown',
|
|
191
|
-
lastModified: new Date().toISOString(),
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return result
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/** Find which module a file belongs to based on path patterns */
|
|
199
|
-
private findModule(
|
|
200
|
-
filePath: string,
|
|
201
|
-
modules: MikkContract['declared']['modules']
|
|
202
|
-
): string | null {
|
|
203
|
-
for (const module of modules) {
|
|
204
|
-
if (this.fileMatchesModule(filePath, module.paths)) {
|
|
205
|
-
return module.id
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
return null
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/** Check if a file path matches any of the module's path patterns */
|
|
212
|
-
private fileMatchesModule(filePath: string, patterns: string[]): boolean {
|
|
213
|
-
const normalized = filePath.replace(/\\/g, '/')
|
|
214
|
-
for (const pattern of patterns) {
|
|
215
|
-
if (minimatch(normalized, pattern)) {
|
|
216
|
-
return true
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
return false
|
|
220
|
-
}
|
|
221
|
-
}
|
|
1
|
+
import * as path from 'node:path'
|
|
2
|
+
import { createHash } from 'node:crypto'
|
|
3
|
+
import type { MikkContract, MikkLock } from './schema.js'
|
|
4
|
+
import type { DependencyGraph } from '../graph/types.js'
|
|
5
|
+
import type { ParsedFile } from '../parser/types.js'
|
|
6
|
+
import { hashContent } from '../hash/file-hasher.js'
|
|
7
|
+
import { computeModuleHash, computeRootHash } from '../hash/tree-hasher.js'
|
|
8
|
+
import { minimatch } from '../utils/minimatch.js'
|
|
9
|
+
|
|
10
|
+
const VERSION = '@getmikk/cli@1.2.1'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* LockCompiler — takes a DependencyGraph and a MikkContract
|
|
14
|
+
* and compiles the complete mikk.lock.json.
|
|
15
|
+
*/
|
|
16
|
+
export class LockCompiler {
|
|
17
|
+
/** Main entry — compile full lock from graph + contract + parsed files */
|
|
18
|
+
compile(
|
|
19
|
+
graph: DependencyGraph,
|
|
20
|
+
contract: MikkContract,
|
|
21
|
+
parsedFiles: ParsedFile[]
|
|
22
|
+
): MikkLock {
|
|
23
|
+
const functions = this.compileFunctions(graph, contract)
|
|
24
|
+
const classes = this.compileClasses(graph, contract)
|
|
25
|
+
const generics = this.compileGenerics(graph, contract)
|
|
26
|
+
const modules = this.compileModules(contract, parsedFiles)
|
|
27
|
+
const files = this.compileFiles(parsedFiles, contract)
|
|
28
|
+
|
|
29
|
+
const moduleHashes: Record<string, string> = {}
|
|
30
|
+
for (const [id, mod] of Object.entries(modules)) {
|
|
31
|
+
moduleHashes[id] = mod.hash
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const lockData: MikkLock = {
|
|
35
|
+
version: '1.0.0',
|
|
36
|
+
generatedAt: new Date().toISOString(),
|
|
37
|
+
generatorVersion: VERSION,
|
|
38
|
+
projectRoot: contract.project.name,
|
|
39
|
+
syncState: {
|
|
40
|
+
status: 'clean',
|
|
41
|
+
lastSyncAt: new Date().toISOString(),
|
|
42
|
+
lockHash: '',
|
|
43
|
+
contractHash: hashContent(JSON.stringify(contract)),
|
|
44
|
+
},
|
|
45
|
+
modules,
|
|
46
|
+
functions,
|
|
47
|
+
classes: Object.keys(classes).length > 0 ? classes : undefined,
|
|
48
|
+
generics: Object.keys(generics).length > 0 ? generics : undefined,
|
|
49
|
+
files,
|
|
50
|
+
graph: {
|
|
51
|
+
nodes: graph.nodes.size,
|
|
52
|
+
edges: graph.edges.length,
|
|
53
|
+
rootHash: computeRootHash(moduleHashes),
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Compute overall lock hash from the compiled data
|
|
58
|
+
lockData.syncState.lockHash = hashContent(JSON.stringify({
|
|
59
|
+
functions: lockData.functions,
|
|
60
|
+
classes: lockData.classes,
|
|
61
|
+
generics: lockData.generics,
|
|
62
|
+
modules: lockData.modules,
|
|
63
|
+
files: lockData.files,
|
|
64
|
+
}))
|
|
65
|
+
|
|
66
|
+
return lockData
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Compile function entries, assigning each to its module */
|
|
70
|
+
private compileFunctions(
|
|
71
|
+
graph: DependencyGraph,
|
|
72
|
+
contract: MikkContract
|
|
73
|
+
): Record<string, MikkLock['functions'][string]> {
|
|
74
|
+
const result: Record<string, MikkLock['functions'][string]> = {}
|
|
75
|
+
|
|
76
|
+
for (const [id, node] of graph.nodes) {
|
|
77
|
+
if (node.type !== 'function') continue
|
|
78
|
+
|
|
79
|
+
const moduleId = this.findModule(node.file, contract.declared.modules)
|
|
80
|
+
const inEdges = graph.inEdges.get(id) || []
|
|
81
|
+
const outEdges = graph.outEdges.get(id) || []
|
|
82
|
+
|
|
83
|
+
result[id] = {
|
|
84
|
+
id,
|
|
85
|
+
name: node.label,
|
|
86
|
+
file: node.file,
|
|
87
|
+
startLine: node.metadata.startLine ?? 0,
|
|
88
|
+
endLine: node.metadata.endLine ?? 0,
|
|
89
|
+
hash: node.metadata.hash ?? '',
|
|
90
|
+
calls: outEdges.filter(e => e.type === 'calls').map(e => e.target),
|
|
91
|
+
calledBy: inEdges.filter(e => e.type === 'calls').map(e => e.source),
|
|
92
|
+
moduleId: moduleId || 'unknown',
|
|
93
|
+
purpose: node.metadata.purpose,
|
|
94
|
+
edgeCasesHandled: node.metadata.edgeCasesHandled,
|
|
95
|
+
errorHandling: node.metadata.errorHandling,
|
|
96
|
+
detailedLines: node.metadata.detailedLines,
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return result
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private compileClasses(
|
|
104
|
+
graph: DependencyGraph,
|
|
105
|
+
contract: MikkContract
|
|
106
|
+
): Record<string, any> {
|
|
107
|
+
const result: Record<string, any> = {}
|
|
108
|
+
for (const [id, node] of graph.nodes) {
|
|
109
|
+
if (node.type !== 'class') continue
|
|
110
|
+
const moduleId = this.findModule(node.file, contract.declared.modules)
|
|
111
|
+
result[id] = {
|
|
112
|
+
id,
|
|
113
|
+
name: node.label,
|
|
114
|
+
file: node.file,
|
|
115
|
+
startLine: node.metadata.startLine ?? 0,
|
|
116
|
+
endLine: node.metadata.endLine ?? 0,
|
|
117
|
+
moduleId: moduleId || 'unknown',
|
|
118
|
+
isExported: node.metadata.isExported ?? false,
|
|
119
|
+
purpose: node.metadata.purpose,
|
|
120
|
+
edgeCasesHandled: node.metadata.edgeCasesHandled,
|
|
121
|
+
errorHandling: node.metadata.errorHandling,
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return result
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private compileGenerics(
|
|
128
|
+
graph: DependencyGraph,
|
|
129
|
+
contract: MikkContract
|
|
130
|
+
): Record<string, any> {
|
|
131
|
+
const result: Record<string, any> = {}
|
|
132
|
+
for (const [id, node] of graph.nodes) {
|
|
133
|
+
if (node.type !== 'generic') continue
|
|
134
|
+
const moduleId = this.findModule(node.file, contract.declared.modules)
|
|
135
|
+
result[id] = {
|
|
136
|
+
id,
|
|
137
|
+
name: node.label,
|
|
138
|
+
type: node.metadata.hash ?? 'generic', // we stored type name in hash
|
|
139
|
+
file: node.file,
|
|
140
|
+
startLine: node.metadata.startLine ?? 0,
|
|
141
|
+
endLine: node.metadata.endLine ?? 0,
|
|
142
|
+
moduleId: moduleId || 'unknown',
|
|
143
|
+
isExported: node.metadata.isExported ?? false,
|
|
144
|
+
purpose: node.metadata.purpose,
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return result
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Compile module entries from contract definitions */
|
|
151
|
+
private compileModules(
|
|
152
|
+
contract: MikkContract,
|
|
153
|
+
parsedFiles: ParsedFile[]
|
|
154
|
+
): Record<string, MikkLock['modules'][string]> {
|
|
155
|
+
const result: Record<string, MikkLock['modules'][string]> = {}
|
|
156
|
+
|
|
157
|
+
for (const module of contract.declared.modules) {
|
|
158
|
+
const moduleFiles = parsedFiles
|
|
159
|
+
.filter(f => this.fileMatchesModule(f.path, module.paths))
|
|
160
|
+
.map(f => f.path)
|
|
161
|
+
|
|
162
|
+
const fileHashes = moduleFiles.map(f => {
|
|
163
|
+
const parsed = parsedFiles.find(pf => pf.path === f)
|
|
164
|
+
return parsed?.hash ?? ''
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
result[module.id] = {
|
|
168
|
+
id: module.id,
|
|
169
|
+
files: moduleFiles,
|
|
170
|
+
hash: computeModuleHash(fileHashes),
|
|
171
|
+
fragmentPath: `.mikk/fragments/${module.id}.lock`,
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return result
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Compile file entries */
|
|
179
|
+
private compileFiles(
|
|
180
|
+
parsedFiles: ParsedFile[],
|
|
181
|
+
contract: MikkContract
|
|
182
|
+
): Record<string, MikkLock['files'][string]> {
|
|
183
|
+
const result: Record<string, MikkLock['files'][string]> = {}
|
|
184
|
+
|
|
185
|
+
for (const file of parsedFiles) {
|
|
186
|
+
const moduleId = this.findModule(file.path, contract.declared.modules)
|
|
187
|
+
result[file.path] = {
|
|
188
|
+
path: file.path,
|
|
189
|
+
hash: file.hash,
|
|
190
|
+
moduleId: moduleId || 'unknown',
|
|
191
|
+
lastModified: new Date().toISOString(),
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return result
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Find which module a file belongs to based on path patterns */
|
|
199
|
+
private findModule(
|
|
200
|
+
filePath: string,
|
|
201
|
+
modules: MikkContract['declared']['modules']
|
|
202
|
+
): string | null {
|
|
203
|
+
for (const module of modules) {
|
|
204
|
+
if (this.fileMatchesModule(filePath, module.paths)) {
|
|
205
|
+
return module.id
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return null
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Check if a file path matches any of the module's path patterns */
|
|
212
|
+
private fileMatchesModule(filePath: string, patterns: string[]): boolean {
|
|
213
|
+
const normalized = filePath.replace(/\\/g, '/')
|
|
214
|
+
for (const pattern of patterns) {
|
|
215
|
+
if (minimatch(normalized, pattern)) {
|
|
216
|
+
return true
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return false
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
import * as fs from 'node:fs/promises'
|
|
2
|
-
import { MikkLockSchema, type MikkLock } from './schema.js'
|
|
3
|
-
import { LockNotFoundError } from '../utils/errors.js'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* LockReader — reads and validates mikk.lock.json from disk.
|
|
7
|
-
*/
|
|
8
|
-
export class LockReader {
|
|
9
|
-
/** Read and validate mikk.lock.json */
|
|
10
|
-
async read(lockPath: string): Promise<MikkLock> {
|
|
11
|
-
let content: string
|
|
12
|
-
try {
|
|
13
|
-
content = await fs.readFile(lockPath, 'utf-8')
|
|
14
|
-
} catch {
|
|
15
|
-
throw new LockNotFoundError()
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const json = JSON.parse(content)
|
|
19
|
-
const result = MikkLockSchema.safeParse(json)
|
|
20
|
-
|
|
21
|
-
if (!result.success) {
|
|
22
|
-
const errors = result.error.issues.map(i => ` ${i.path.join('.')}: ${i.message}`).join('\n')
|
|
23
|
-
throw new Error(`Invalid mikk.lock.json:\n${errors}`)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return result.data
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Write lock file to disk */
|
|
30
|
-
async write(lock: MikkLock, lockPath: string): Promise<void> {
|
|
31
|
-
const json = JSON.stringify(lock, null, 2)
|
|
32
|
-
await fs.writeFile(lockPath, json, 'utf-8')
|
|
33
|
-
}
|
|
34
|
-
}
|
|
1
|
+
import * as fs from 'node:fs/promises'
|
|
2
|
+
import { MikkLockSchema, type MikkLock } from './schema.js'
|
|
3
|
+
import { LockNotFoundError } from '../utils/errors.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* LockReader — reads and validates mikk.lock.json from disk.
|
|
7
|
+
*/
|
|
8
|
+
export class LockReader {
|
|
9
|
+
/** Read and validate mikk.lock.json */
|
|
10
|
+
async read(lockPath: string): Promise<MikkLock> {
|
|
11
|
+
let content: string
|
|
12
|
+
try {
|
|
13
|
+
content = await fs.readFile(lockPath, 'utf-8')
|
|
14
|
+
} catch {
|
|
15
|
+
throw new LockNotFoundError()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const json = JSON.parse(content)
|
|
19
|
+
const result = MikkLockSchema.safeParse(json)
|
|
20
|
+
|
|
21
|
+
if (!result.success) {
|
|
22
|
+
const errors = result.error.issues.map(i => ` ${i.path.join('.')}: ${i.message}`).join('\n')
|
|
23
|
+
throw new Error(`Invalid mikk.lock.json:\n${errors}`)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return result.data
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Write lock file to disk */
|
|
30
|
+
async write(lock: MikkLock, lockPath: string): Promise<void> {
|
|
31
|
+
const json = JSON.stringify(lock, null, 2)
|
|
32
|
+
await fs.writeFile(lockPath, json, 'utf-8')
|
|
33
|
+
}
|
|
34
|
+
}
|