@getmikk/core 1.9.1 → 2.0.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,37 @@
1
+ # @getmikk/core
2
+
3
+ ## 2.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 1217e39: chore: synchronize all packages to v2.0.1 and finalize release infrastructure.
8
+
9
+ ## 2.0.3
10
+
11
+ ### Patch Changes
12
+
13
+ - 06a410b: chore: synchronize monorepo to v2.0.1 and finalize release infrastructure.
14
+
15
+ ## 2.0.2
16
+
17
+ ### Patch Changes
18
+
19
+ - 5dfe317: test
20
+ - 9118944: test
21
+ - 36bbb2f: test
22
+ - fe8ac06: chore: synchronize all packages to v2.0.1 and finalize release infrastructure.
23
+ - b00faed: test
24
+
25
+ ## 2.0.1
26
+
27
+ ### Patch Changes
28
+
29
+ - bd5c050: 2.0.1
30
+ - ff1444f: Use dynamic import for oxc-parser to support ESM-only versions (0.121.0+)
31
+ - 0ccbf45: 2.0.1
32
+
33
+ ## 3.0.0
34
+
35
+ ### Major Changes
36
+
37
+ - 568a3d5: 2.0.0
package/package.json CHANGED
@@ -1,38 +1,40 @@
1
1
  {
2
- "name": "@getmikk/core",
3
- "version": "1.9.1",
4
- "license": "Apache-2.0",
5
- "repository": {
6
- "type": "git",
7
- "url": "https://github.com/Ansh-dhanani/mikk"
8
- },
9
- "type": "module",
10
- "main": "./dist/index.js",
11
- "types": "./dist/index.d.ts",
12
- "exports": {
13
- ".": {
14
- "import": "./dist/index.js",
15
- "types": "./dist/index.d.ts"
16
- }
17
- },
18
- "scripts": {
19
- "build": "tsc",
20
- "test": "bun test",
21
- "dev": "tsc --watch"
22
- },
23
- "dependencies": {
24
- "@types/better-sqlite3": "^7.6.13",
25
- "better-sqlite3": "^12.6.2",
26
- "fast-glob": "^3.3.0",
27
- "oxc-parser": "^0.121.0",
28
- "oxc-resolver": "^11.19.1",
29
- "tree-sitter-wasms": "^0.1.13",
30
- "web-tree-sitter": "0.20.8",
31
- "zod": "^3.22.0",
32
- "typescript": "^5.7.0"
33
- },
34
- "devDependencies": {
35
- "@types/bun": "^1.3.10",
36
- "@types/node": "^22.0.0"
2
+ "name": "@getmikk/core",
3
+ "version": "2.0.10",
4
+ "publishConfig": {
5
+ "access": "public",
6
+ "registry": "https://registry.npmjs.org/"
7
+ },
8
+ "license": "Apache-2.0",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/Ansh-dhanani/mikk"
12
+ },
13
+ "type": "module",
14
+ "main": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "import": "./dist/index.js",
19
+ "types": "./dist/index.d.ts"
37
20
  }
21
+ },
22
+ "scripts": {
23
+ "build": "tsc",
24
+ "test": "bun test",
25
+ "dev": "tsc --watch",
26
+ "lint": "eslint ."
27
+ },
28
+ "dependencies": {
29
+ "@types/better-sqlite3": "^7.6.13",
30
+ "better-sqlite3": "^12.6.2",
31
+ "fast-glob": "^3.3.0",
32
+ "tree-sitter-wasms": "^0.1.13",
33
+ "web-tree-sitter": "^0.20.8",
34
+ "zod": "^3.22.0"
35
+ },
36
+ "devDependencies": {
37
+ "typescript": "^5.7.0",
38
+ "@types/node": "^22.0.0"
39
+ }
38
40
  }
@@ -600,7 +600,7 @@ export class ClusterDetector {
600
600
  const split = id
601
601
  .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
602
602
  .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
603
- .split(/[\s_\-\.]+/)
603
+ .split(/[\s_\-.]+/)
604
604
  .map(w => w.toLowerCase())
605
605
  .filter(w => w.length > 1)
606
606
  words.push(...split)
@@ -9,7 +9,7 @@ export abstract class BaseParser {
9
9
  abstract parse(filePath: string, content: string): Promise<ParsedFile>
10
10
 
11
11
  /** Given a list of parsed files, resolve all import paths to absolute project paths */
12
- abstract resolveImports(files: ParsedFile[], projectRoot: string): ParsedFile[]
12
+ abstract resolveImports(files: ParsedFile[], projectRoot: string): Promise<ParsedFile[]>
13
13
 
14
14
  /** Returns which file extensions this parser handles */
15
15
  abstract getSupportedExtensions(): string[]
@@ -523,7 +523,7 @@ function parseImportLine(line: string): ParsedImport | null {
523
523
  * Statefully track brace depth through content, handling:
524
524
  * - string literals ("...", `...`), rune literals ('.')
525
525
  * - line comments (//)
526
- * - block comments (/* ... *​/)
526
+ * - block comments (/* ... * /)
527
527
  */
528
528
  function findBodyBounds(lines: string[], startLine: number): { bodyStart: number; bodyEnd: number } {
529
529
  let braceDepth = 0
@@ -29,7 +29,7 @@ export class GoParser extends BaseParser {
29
29
  }
30
30
  }
31
31
 
32
- resolveImports(files: ParsedFile[], projectRoot: string): ParsedFile[] {
32
+ async resolveImports(files: ParsedFile[], projectRoot: string): Promise<ParsedFile[]> {
33
33
  const resolver = new GoResolver(projectRoot)
34
34
  return files.map(file => ({
35
35
  ...file,
@@ -137,12 +137,12 @@ export async function parseFiles(
137
137
  let resolvedTreeFiles: ParsedFile[] = treeFiles
138
138
  if (treeFiles.length > 0) {
139
139
  const treeParser = treeSitterParser ?? await getTreeSitter()
140
- resolvedTreeFiles = treeParser.resolveImports(treeFiles, normalizedRoot)
140
+ resolvedTreeFiles = await treeParser.resolveImports(treeFiles, normalizedRoot)
141
141
  }
142
142
 
143
143
  const resolved: ParsedFile[] = [
144
- ...oxcParser.resolveImports(oxcFiles, normalizedRoot),
145
- ...goParser.resolveImports(goFiles, normalizedRoot),
144
+ ...await oxcParser.resolveImports(oxcFiles, normalizedRoot),
145
+ ...await goParser.resolveImports(goFiles, normalizedRoot),
146
146
  ...resolvedTreeFiles,
147
147
  ]
148
148
 
@@ -55,7 +55,7 @@ export class JavaScriptParser extends BaseParser {
55
55
  }
56
56
  }
57
57
 
58
- resolveImports(files: ParsedFile[], projectRoot: string): ParsedFile[] {
58
+ async resolveImports(files: ParsedFile[], projectRoot: string): Promise<ParsedFile[]> {
59
59
  const aliases = loadAliases(projectRoot)
60
60
  // Only pass the file list when it represents a reasonably complete scan.
61
61
  // A sparse list (< MIN_FILES_FOR_COMPLETE_SCAN files) causes valid alias-resolved
@@ -1,5 +1,4 @@
1
1
  import path from 'node:path';
2
- import { parseSync } from 'oxc-parser';
3
2
  import { BaseParser } from './base-parser.js';
4
3
  import { OxcResolver } from './oxc-resolver.js';
5
4
  import { hashContent } from '../hash/file-hasher.js';
@@ -283,6 +282,7 @@ export class OxcParser extends BaseParser {
283
282
 
284
283
  let ast: any;
285
284
  try {
285
+ const { parseSync } = await import('oxc-parser');
286
286
  const result = parseSync(filePath, content, {
287
287
  sourceType: 'module',
288
288
  lang: isTS ? 'ts' : 'js',
@@ -389,8 +389,8 @@ export class OxcParser extends BaseParser {
389
389
  const key = member.key;
390
390
  if (!key) continue;
391
391
  const mName = key.type === 'Identifier' ? key.name :
392
- key.type === 'PrivateIdentifier' ? `#${key.name}` :
393
- null;
392
+ key.type === 'PrivateIdentifier' ? `#${key.name}` :
393
+ null;
394
394
  if (!mName) continue;
395
395
 
396
396
  if (member.type === 'MethodDefinition') {
@@ -613,7 +613,7 @@ export class OxcParser extends BaseParser {
613
613
  const callExpr = node.expression;
614
614
  const calls = extractCalls(callExpr, lineIndex);
615
615
  moduleCalls.push(...calls);
616
-
616
+
617
617
  // Route detection
618
618
  const callee = callExpr.callee;
619
619
  if (callee && (callee.type === 'StaticMemberExpression' || callee.type === 'MemberExpression')) {
@@ -624,14 +624,14 @@ export class OxcParser extends BaseParser {
624
624
  const pathArg = args[0];
625
625
  if (pathArg && (pathArg.type === 'StringLiteral' || pathArg.type === 'Literal' || pathArg.type === 'TemplateLiteral')) {
626
626
  const pathVal = pathArg.value || (pathArg.quasis && pathArg.quasis[0]?.value?.raw) || '';
627
-
627
+
628
628
  const handlerArg = args[args.length - 1];
629
629
  const handlerStr = handlerArg ? content.slice(getSpan(handlerArg).start, getSpan(handlerArg).end).replace(/\s+/g, ' ').trim() : 'unknown';
630
-
631
- const middlewares = args.slice(1, -1).map((a: any) =>
630
+
631
+ const middlewares = args.slice(1, -1).map((a: any) =>
632
632
  content.slice(getSpan(a).start, getSpan(a).end).replace(/\s+/g, ' ').trim()
633
633
  );
634
-
634
+
635
635
  routes.push({
636
636
  method: propName.toUpperCase() as any,
637
637
  path: String(pathVal),
@@ -680,9 +680,9 @@ export class OxcParser extends BaseParser {
680
680
  };
681
681
  }
682
682
 
683
- public resolveImports(files: ParsedFile[], projectRoot: string): ParsedFile[] {
683
+ public async resolveImports(files: ParsedFile[], projectRoot: string): Promise<ParsedFile[]> {
684
684
  const resolver = new OxcResolver(projectRoot);
685
- return resolver.resolveBatch(files);
685
+ return await resolver.resolveBatch(files);
686
686
  }
687
687
 
688
688
  public getSupportedExtensions(): string[] {
@@ -1,8 +1,9 @@
1
- import { ResolverFactory } from 'oxc-resolver';
1
+
2
2
  import path from 'node:path';
3
3
  import fs from 'node:fs';
4
4
  import type { ParsedFile } from './types.js';
5
5
 
6
+
6
7
  /**
7
8
  * OxcResolver — Rust-backed compiler-grade module resolution.
8
9
  *
@@ -21,10 +22,15 @@ export class OxcResolver {
21
22
  private resolver: any;
22
23
  private readonly normalizedRoot: string;
23
24
 
25
+
24
26
  constructor(private readonly projectRoot: string) {
25
27
  this.normalizedRoot = path.resolve(projectRoot).replace(/\\/g, '/');
28
+ }
26
29
 
27
- const tsconfigPath = path.resolve(projectRoot, 'tsconfig.json');
30
+ private async ensureResolver() {
31
+ if (this.resolver) return;
32
+ const { ResolverFactory } = await import('oxc-resolver');
33
+ const tsconfigPath = path.resolve(this.projectRoot, 'tsconfig.json');
28
34
  const hasTsConfig = fs.existsSync(tsconfigPath);
29
35
 
30
36
  this.resolver = new ResolverFactory({
@@ -46,14 +52,16 @@ export class OxcResolver {
46
52
  * fromFile MUST be an absolute path (as produced by parseFiles).
47
53
  * Returns an absolute posix path, or '' if unresolvable/external.
48
54
  */
49
- public resolve(source: string, fromFile: string): string {
55
+ public async resolve(source: string, fromFile: string): Promise<string> {
50
56
  try {
57
+ await this.ensureResolver();
51
58
  const absFrom = path.isAbsolute(fromFile)
52
59
  ? fromFile
53
60
  : path.resolve(this.projectRoot, fromFile);
54
61
  const dir = path.dirname(absFrom);
55
62
 
56
63
  const result = this.resolver.sync(dir, source);
64
+
57
65
  if (!result?.path) return '';
58
66
 
59
67
  const resolved = result.path.replace(/\\/g, '/');
@@ -70,14 +78,16 @@ export class OxcResolver {
70
78
  }
71
79
  }
72
80
 
81
+
73
82
  /** Resolve all imports for a batch of files in one pass */
74
- public resolveBatch(files: ParsedFile[]): ParsedFile[] {
75
- return files.map(file => ({
83
+ public async resolveBatch(files: ParsedFile[]): Promise<ParsedFile[]> {
84
+ return Promise.all(files.map(async file => ({
76
85
  ...file,
77
- imports: file.imports.map(imp => ({
86
+ imports: await Promise.all(file.imports.map(async imp => ({
78
87
  ...imp,
79
- resolvedPath: this.resolve(imp.source, file.path),
80
- })),
81
- }));
88
+ resolvedPath: await this.resolve(imp.source, file.path),
89
+ }))),
90
+ })));
82
91
  }
83
92
  }
93
+
@@ -5,14 +5,20 @@ import { BaseParser } from '../base-parser.js'
5
5
  import type { ParsedFile, ParsedFunction, ParsedClass, ParsedParam, ParsedImport } from '../types.js'
6
6
  import * as Queries from './queries.js'
7
7
 
8
- // Safely require web-tree-sitter via CJS
8
+ // Safely require web-tree-sitter via CJS.
9
+ // Wrapped in try/catch so that importing this module never throws when the
10
+ // package is absent — callers receive an empty ParsedFile instead.
9
11
  const getRequire = () => {
10
12
  if (typeof require !== 'undefined') return require
11
13
  return createRequire(import.meta.url)
12
14
  }
13
15
  const _require = getRequire()
14
- const ParserModule = _require('web-tree-sitter')
15
- const Parser = ParserModule.Parser || ParserModule
16
+
17
+ let Parser: any = null
18
+ try {
19
+ const ParserModule = _require('web-tree-sitter')
20
+ Parser = ParserModule.Parser ?? ParserModule
21
+ } catch { /* web-tree-sitter not installed — Parser stays null */ }
16
22
 
17
23
  // ---------------------------------------------------------------------------
18
24
  // Language-specific export visibility rules
@@ -150,6 +156,7 @@ export class TreeSitterParser extends BaseParser {
150
156
 
151
157
  private async init() {
152
158
  if (!this.parser) {
159
+ if (!Parser) return // web-tree-sitter not available
153
160
  await Parser.init()
154
161
  this.parser = new Parser()
155
162
  }
@@ -159,6 +166,12 @@ export class TreeSitterParser extends BaseParser {
159
166
  this.nameCounter.clear()
160
167
  await this.init()
161
168
  const ext = path.extname(filePath).toLowerCase()
169
+
170
+ if (!this.parser) {
171
+ // web-tree-sitter unavailable — return structurally valid empty file
172
+ return this.buildEmptyFile(filePath, content, ext)
173
+ }
174
+
162
175
  const config = await this.getLanguageConfig(ext)
163
176
 
164
177
  if (!config || !config.lang) {
@@ -224,7 +237,7 @@ export class TreeSitterParser extends BaseParser {
224
237
  this.nameCounter.set(fnName, count)
225
238
 
226
239
  // Unique ID: use stable format with counter for collisions
227
- let fnId = count === 1 ? `fn:${filePath}:${fnName}` : `fn:${filePath}:${fnName}#${count}`
240
+ const fnId = count === 1 ? `fn:${filePath}:${fnName}` : `fn:${filePath}:${fnName}#${count}`
228
241
  if (seenFnIds.has(fnId)) {
229
242
  continue
230
243
  }
@@ -345,7 +358,7 @@ export class TreeSitterParser extends BaseParser {
345
358
  }
346
359
  }
347
360
 
348
- resolveImports(files: ParsedFile[], _projectRoot: string): ParsedFile[] {
361
+ async resolveImports(files: ParsedFile[], _projectRoot: string): Promise<ParsedFile[]> {
349
362
  // Tree-sitter resolver: no cross-file resolution implemented.
350
363
  // Imports are left with resolvedPath = '' which signals unresolved to the graph builder.
351
364
  // A future pass can resolve Go/Python/Java imports using language-specific rules.
@@ -472,9 +485,8 @@ function extractDocComment(content: string, startLine: number): string {
472
485
  // Walk back to find the first meaningful JSDoc line
473
486
  for (let i = targetIdx - 1; i >= 0; i--) {
474
487
  const line = lines[i].trim()
475
- if (line.startsWith('/*') || line.startsWith('/**')) break
476
488
  const cleaned = line.replace(/^\*+\s?/, '')
477
- if (cleaned && !/^[\-_=*]{3,}$/.test(cleaned)) return cleaned
489
+ if (cleaned && !/^[ \-_=*]{3,}$/.test(cleaned)) return cleaned
478
490
  }
479
491
  }
480
492
  return ''
@@ -24,7 +24,7 @@ export class TypeScriptParser extends BaseParser {
24
24
  }
25
25
  }
26
26
 
27
- public resolveImports(files: ParsedFile[], projectRoot: string): ParsedFile[] {
27
+ public async resolveImports(files: ParsedFile[], projectRoot: string): Promise<ParsedFile[]> {
28
28
  const resolver = new TypeScriptResolver(projectRoot)
29
29
  return resolver.resolveBatch(files)
30
30
  }
@@ -1,3 +1,4 @@
1
+
1
2
  import { describe, it, expect, beforeAll, afterAll } from 'bun:test'
2
3
  import * as path from 'node:path'
3
4
  import * as fs from 'node:fs/promises'
@@ -49,19 +50,19 @@ describe('OxcResolver - ESM and CJS Resolution', () => {
49
50
  await fs.rm(FIXTURE_DIR, { recursive: true, force: true })
50
51
  })
51
52
 
52
- it('resolves ESM exports correctly', () => {
53
+ it('resolves ESM exports correctly', async () => {
53
54
  const resolver = new OxcResolver(FIXTURE_DIR)
54
55
 
55
56
  // Resolve 'some-pkg' (should hit exports['.'].import)
56
- const res = resolver.resolve('some-pkg', path.join(FIXTURE_DIR, 'index.ts'))
57
+ const res = await resolver.resolve('some-pkg', path.join(FIXTURE_DIR, 'index.ts'))
57
58
  expect(res).toContain('node_modules/some-pkg/dist/esm/index.js')
58
59
  })
59
60
 
60
- it('resolves subpath exports correctly', () => {
61
+ it('resolves subpath exports correctly', async () => {
61
62
  const resolver = new OxcResolver(FIXTURE_DIR)
62
63
 
63
64
  // Resolve 'some-pkg/subpath'
64
- const res = resolver.resolve('some-pkg/subpath', path.join(FIXTURE_DIR, 'index.ts'))
65
+ const res = await resolver.resolve('some-pkg/subpath', path.join(FIXTURE_DIR, 'index.ts'))
65
66
  expect(res).toContain('node_modules/some-pkg/dist/sub.js')
66
67
  })
67
68
 
@@ -69,7 +70,7 @@ describe('OxcResolver - ESM and CJS Resolution', () => {
69
70
  const resolver = new OxcResolver(FIXTURE_DIR)
70
71
  await fs.writeFile(path.join(FIXTURE_DIR, 'local.ts'), 'export const x = 1')
71
72
 
72
- const res = resolver.resolve('./local', path.join(FIXTURE_DIR, 'index.ts'))
73
+ const res = await resolver.resolve('./local', path.join(FIXTURE_DIR, 'index.ts'))
73
74
  expect(res).toContain('.test-fixture-esm/local.ts')
74
75
  })
75
76
  })
@@ -360,7 +360,7 @@ describe('GoParser', () => {
360
360
  const parser = new GoParser()
361
361
  const files = [await parser.parse('utils/format.go', TOPLEVEL_GO)]
362
362
  // Should not throw even without go.mod
363
- const resolved = parser.resolveImports(files, '/tmp/no-gomod-' + Date.now())
363
+ const resolved = await parser.resolveImports(files, '/tmp/no-gomod-' + Date.now())
364
364
  expect(resolved.length).toBe(1)
365
365
  })
366
366
  })
@@ -550,7 +550,7 @@ describe('JavaScriptParser', () => {
550
550
 
551
551
  test('resolveImports leaves external packages unresolved (empty resolvedPath)', async () => {
552
552
  const files = [await parser.parse('src/auth.js', CJS_MODULE)]
553
- const resolved = parser.resolveImports(files, '/project')
553
+ const resolved = await parser.resolveImports(files, '/project')
554
554
  const file = resolved[0]
555
555
  const cryptoImp = file.imports.find((i: any) => i.source === 'crypto')
556
556
  expect(cryptoImp!.resolvedPath).toBe('')
@@ -57,7 +57,7 @@ describe('ts-parser config resolution', () => {
57
57
 
58
58
  // Parse and resolve imports
59
59
  const parsed = await parser.parse(filePath, await fs.readFile(filePath, 'utf-8'))
60
- const resolved = parser.resolveImports([parsed], FIXTURE_DIR)[0]
60
+ const resolved = (await parser.resolveImports([parsed], FIXTURE_DIR))[0]
61
61
 
62
62
  const impApp = resolved.imports.find(i => i.source === '@app/local')
63
63
  expect(impApp?.resolvedPath).toBe(path.join(FIXTURE_DIR, 'src/app/local.ts').replace(/\\/g, '/'))