mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-05 02:43:32 +00:00
Merge branch 'main' into codex/cpp-braced-init-list-overload
This commit is contained in:
commit
f69c21e170
19 changed files with 1289 additions and 304 deletions
|
|
@ -32,7 +32,17 @@ const EXTENSION_MAP: Record<SupportedLanguages, readonly string[]> = {
|
|||
[SupportedLanguages.Python]: ['.py'],
|
||||
[SupportedLanguages.Java]: ['.java'],
|
||||
[SupportedLanguages.C]: ['.c'],
|
||||
[SupportedLanguages.CPlusPlus]: ['.cpp', '.cc', '.cxx', '.h', '.hpp', '.hxx', '.hh'],
|
||||
[SupportedLanguages.CPlusPlus]: [
|
||||
'.cpp',
|
||||
'.cc',
|
||||
'.cxx',
|
||||
'.h',
|
||||
'.hpp',
|
||||
'.hxx',
|
||||
'.hh',
|
||||
'.cu',
|
||||
'.cuh',
|
||||
],
|
||||
[SupportedLanguages.CSharp]: ['.cs'],
|
||||
[SupportedLanguages.Go]: ['.go'],
|
||||
[SupportedLanguages.Ruby]: ['.rb', '.rake', '.gemspec'],
|
||||
|
|
|
|||
412
gitnexus-web/package-lock.json
generated
412
gitnexus-web/package-lock.json
generated
|
|
@ -63,7 +63,7 @@
|
|||
"jsdom": "^29.1.1",
|
||||
"tree-sitter-wasms": "^0.1.13",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^8.0.11",
|
||||
"vite": "^8.0.16",
|
||||
"vitest": "^4.1.5",
|
||||
"wait-on": "^9.0.5"
|
||||
},
|
||||
|
|
@ -169,13 +169,13 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
||||
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
|
||||
"integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"@babel/helper-validator-identifier": "^7.29.7",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.1.1"
|
||||
},
|
||||
|
|
@ -184,9 +184,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/compat-data": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz",
|
||||
"integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
|
||||
"integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
|
@ -194,21 +194,21 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/core": {
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
|
||||
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
|
||||
"integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.29.0",
|
||||
"@babel/generator": "^7.29.0",
|
||||
"@babel/helper-compilation-targets": "^7.28.6",
|
||||
"@babel/helper-module-transforms": "^7.28.6",
|
||||
"@babel/helpers": "^7.28.6",
|
||||
"@babel/parser": "^7.29.0",
|
||||
"@babel/template": "^7.28.6",
|
||||
"@babel/traverse": "^7.29.0",
|
||||
"@babel/types": "^7.29.0",
|
||||
"@babel/code-frame": "^7.29.7",
|
||||
"@babel/generator": "^7.29.7",
|
||||
"@babel/helper-compilation-targets": "^7.29.7",
|
||||
"@babel/helper-module-transforms": "^7.29.7",
|
||||
"@babel/helpers": "^7.29.7",
|
||||
"@babel/parser": "^7.29.7",
|
||||
"@babel/template": "^7.29.7",
|
||||
"@babel/traverse": "^7.29.7",
|
||||
"@babel/types": "^7.29.7",
|
||||
"@jridgewell/remapping": "^2.3.5",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"debug": "^4.1.0",
|
||||
|
|
@ -235,14 +235,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
"version": "7.29.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
|
||||
"integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz",
|
||||
"integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.29.0",
|
||||
"@babel/types": "^7.29.0",
|
||||
"@babel/parser": "^7.29.7",
|
||||
"@babel/types": "^7.29.7",
|
||||
"@jridgewell/gen-mapping": "^0.3.12",
|
||||
"@jridgewell/trace-mapping": "^0.3.28",
|
||||
"jsesc": "^3.0.2"
|
||||
|
|
@ -252,14 +252,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/helper-compilation-targets": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
|
||||
"integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
|
||||
"integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/compat-data": "^7.28.6",
|
||||
"@babel/helper-validator-option": "^7.27.1",
|
||||
"@babel/compat-data": "^7.29.7",
|
||||
"@babel/helper-validator-option": "^7.29.7",
|
||||
"browserslist": "^4.24.0",
|
||||
"lru-cache": "^5.1.1",
|
||||
"semver": "^6.3.1"
|
||||
|
|
@ -296,9 +296,9 @@
|
|||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@babel/helper-globals": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
|
||||
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
|
||||
"integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
|
@ -306,29 +306,29 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/helper-module-imports": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
|
||||
"integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
|
||||
"integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/traverse": "^7.28.6",
|
||||
"@babel/types": "^7.28.6"
|
||||
"@babel/traverse": "^7.29.7",
|
||||
"@babel/types": "^7.29.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-module-transforms": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
|
||||
"integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
|
||||
"integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-module-imports": "^7.28.6",
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"@babel/traverse": "^7.28.6"
|
||||
"@babel/helper-module-imports": "^7.29.7",
|
||||
"@babel/helper-validator-identifier": "^7.29.7",
|
||||
"@babel/traverse": "^7.29.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
|
|
@ -348,9 +348,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
|
||||
"integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
|
@ -358,9 +358,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
|
||||
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
|
@ -368,9 +368,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-option": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
|
||||
"integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
|
||||
"integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
|
@ -378,27 +378,27 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/helpers": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
|
||||
"integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
|
||||
"integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.28.6",
|
||||
"@babel/types": "^7.28.6"
|
||||
"@babel/template": "^7.29.7",
|
||||
"@babel/types": "^7.29.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.29.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
|
||||
"integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
|
||||
"integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.29.0"
|
||||
"@babel/types": "^7.29.7"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
|
|
@ -449,33 +449,33 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
||||
"integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
|
||||
"integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.28.6",
|
||||
"@babel/parser": "^7.28.6",
|
||||
"@babel/types": "^7.28.6"
|
||||
"@babel/code-frame": "^7.29.7",
|
||||
"@babel/parser": "^7.29.7",
|
||||
"@babel/types": "^7.29.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
|
||||
"integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz",
|
||||
"integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.29.0",
|
||||
"@babel/generator": "^7.29.0",
|
||||
"@babel/helper-globals": "^7.28.0",
|
||||
"@babel/parser": "^7.29.0",
|
||||
"@babel/template": "^7.28.6",
|
||||
"@babel/types": "^7.29.0",
|
||||
"@babel/code-frame": "^7.29.7",
|
||||
"@babel/generator": "^7.29.7",
|
||||
"@babel/helper-globals": "^7.29.7",
|
||||
"@babel/parser": "^7.29.7",
|
||||
"@babel/template": "^7.29.7",
|
||||
"@babel/types": "^7.29.7",
|
||||
"debug": "^4.3.1"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -483,14 +483,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
|
||||
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
|
||||
"integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.28.5"
|
||||
"@babel/helper-string-parser": "^7.29.7",
|
||||
"@babel/helper-validator-identifier": "^7.29.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
|
|
@ -1621,13 +1621,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
|
||||
"integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz",
|
||||
"integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@tybys/wasm-util": "^0.10.1"
|
||||
"@tybys/wasm-util": "^0.10.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
|
|
@ -1677,9 +1677,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@oxc-project/types": {
|
||||
"version": "0.128.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.128.0.tgz",
|
||||
"integrity": "sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ==",
|
||||
"version": "0.133.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz",
|
||||
"integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/Boshen"
|
||||
|
|
@ -1713,9 +1713,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-lIDyUAfD7U3+BWKzdxMbJcsYHuqXqmGz40aeRqvuAm3y5TkJSYTBW2RDrn65DJFPQqVjUAUqq5uz8urzQ8aBdQ==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz",
|
||||
"integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -1729,9 +1729,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-apJq2ktnGp27nSInMR5Vcj8kY6xJzDAvfdIFlpDcAK/w4cDO58qVoi1YQsES/SKiFNge/6e4CUzgjfHduYqWpQ==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz",
|
||||
"integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -1745,9 +1745,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-x64": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-5Ofot8xbs+pxRHJqm9/9N/4sTQOvdrwEsmPE9pdLEEoAbdZtG6F2LMDfO1sp6ZAtXJuJV/21ew2srq3W8NXB5g==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz",
|
||||
"integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -1761,9 +1761,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-7h8eeOTT1eyqJyx64BFCnWZpNm486hGWt2sqeLLgDxA0xI1oGZ9H7gK1S85uNGmBhkdPwa/6reTxfFFKvIsebw==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz",
|
||||
"integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -1777,9 +1777,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-eRcm/HVt9U/JFu5RKAEKwGQYtDCKWLiaH6wOnsSEp6NMBb/3Os8LgHZlNyzMpFVNmiiMFlfb2zEnebfzJrHFmg==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz",
|
||||
"integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
|
|
@ -1793,9 +1793,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-SOrT/cT4ukTmgnrEz/Hg3m7LBnuCLW9psDeMKrimRWY4I8DmnO7Lco8W2vtqPmMkbVu8iJ+g4GFLVLLOVjJ9DQ==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -1809,9 +1809,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-QWjdxN1HJCpBTAcZ5N5F7wju3gVPzRzSpmGzx7na0c/1qpN9CFil+xt+l9lV/1M6/gqHSNXCiqPfwhVJPeLnug==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz",
|
||||
"integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -1825,9 +1825,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-ugCOyj7a4d9h3q9B+wXmf6g3a68UsjGh6dob5DHevHGMwDUbhsYNbSPxJsENcIttJZ9jv7qGM2UesLw5jqIhdg==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
|
|
@ -1841,9 +1841,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-kKWRhbsotpXkGbcd5dllUWg5gEXcDAa8u5YnP9AV5DYNbvJHGzzuwv7dpmhc8NqKMJldl0a+x76IHbspEpEmdA==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
|
|
@ -1857,9 +1857,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-uCo8ElcCIAMyYAZyuIZ81oFkhTSIllNvUCHCAlbhlN4ji3uC28h7IIdlXyIvGO7HsuqnV9p3rD/bpH7XhIyhRw==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -1873,9 +1873,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-XNOQZtuE6yUIvx4rwGemwh8kpL1xvU41FXy/s9K7T/3JVcqGzo3NfKM2HrbrGgfPYGFW42f07Wk++aOC6B9NWA==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz",
|
||||
"integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -1889,9 +1889,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-openharmony-arm64": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-tSn/kzrfa7tNOXr7sEacDBN4YsIqTyLqh45IO0nHDwtpKIDNDJr+VFojt+4klSpChxB29JLyduSsE0MKEwa65A==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz",
|
||||
"integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -1905,9 +1905,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-+J9YGmc+czgqlhYmwun3S3O0FIZhsH8ep2456xwjAdIOmuJxM7xz4P4PtrxU+Bz17a/5bqPA8o3HAAoX0teUdg==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz",
|
||||
"integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
|
|
@ -1923,9 +1923,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-zsu47DgU0FQzSwi6sU9dZoEdUv7pc1AptSEz/Z8HBg54sV0Pbs3N0+CrIbTsgiu6EyoaNN9CHboqbLaz9lhOyQ==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz",
|
||||
"integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -1939,9 +1939,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-7H+3yqGgmnlDTRRhw/xpYY9J1kf4GC681nVc4GqKhExZTDrVVrV2tsOR9kso0fvgBdcTCcQShx4SLLoHgaLwhg==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz",
|
||||
"integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -2451,9 +2451,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
|
||||
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
|
||||
"version": "0.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
|
||||
"integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
|
|
@ -3495,13 +3495,16 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.9.15",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.15.tgz",
|
||||
"integrity": "sha512-kX8h7K2srmDyYnXRIppo4AH/wYgzWVCs+eKr3RusRSQ5PvRYoEFmR/I0PbdTjKFAoKqp5+kbxnNTFO9jOfSVJg==",
|
||||
"version": "2.10.37",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.37.tgz",
|
||||
"integrity": "sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"baseline-browser-mapping": "dist/cli.js"
|
||||
"baseline-browser-mapping": "dist/cli.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bidi-js": {
|
||||
|
|
@ -3551,9 +3554,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
|
||||
"integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
|
||||
"version": "4.28.2",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
|
||||
"integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -3571,11 +3574,11 @@
|
|||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
"electron-to-chromium": "^1.5.263",
|
||||
"node-releases": "^2.0.27",
|
||||
"update-browserslist-db": "^1.2.0"
|
||||
"baseline-browser-mapping": "^2.10.12",
|
||||
"caniuse-lite": "^1.0.30001782",
|
||||
"electron-to-chromium": "^1.5.328",
|
||||
"node-releases": "^2.0.36",
|
||||
"update-browserslist-db": "^1.2.3"
|
||||
},
|
||||
"bin": {
|
||||
"browserslist": "cli.js"
|
||||
|
|
@ -3598,9 +3601,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001764",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz",
|
||||
"integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==",
|
||||
"version": "1.0.30001799",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz",
|
||||
"integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -4525,9 +4528,9 @@
|
|||
"license": "ISC"
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.267",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
|
||||
"integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==",
|
||||
"version": "1.5.372",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.372.tgz",
|
||||
"integrity": "sha512-M3yhbAlilnwqC8D21t28UCDGHyitShTmmLRU/H+b74P6Ski16Nb9HONYEaVpMj/pwC7BEo5B95FpjODLCWbtfA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
|
|
@ -4851,16 +4854,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
|
||||
"integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
"hasown": "^2.0.4",
|
||||
"mime-types": "^2.1.35"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
|
|
@ -5146,9 +5149,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
|
||||
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
|
|
@ -7300,11 +7303,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.27",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
|
||||
"integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
|
||||
"version": "2.0.47",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz",
|
||||
"integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/nopt": {
|
||||
"version": "8.1.0",
|
||||
|
|
@ -7628,9 +7634,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.14",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
|
||||
"integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
|
||||
"version": "8.5.15",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
|
||||
"integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
|
@ -7647,7 +7653,7 @@
|
|||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"nanoid": "^3.3.12",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
|
|
@ -8017,13 +8023,13 @@
|
|||
"license": "Unlicense"
|
||||
},
|
||||
"node_modules/rolldown": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-phmyKBpuBdRYDf4hgyynGAYn/rDDe+iZXKVJ7WX5b1zQzpLkP5oJRPGsfJuHdzPMlyyEO/4sPW6yfSx2gf7lVg==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz",
|
||||
"integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oxc-project/types": "=0.128.0",
|
||||
"@rolldown/pluginutils": "1.0.0-rc.18"
|
||||
"@oxc-project/types": "=0.133.0",
|
||||
"@rolldown/pluginutils": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"rolldown": "bin/cli.mjs"
|
||||
|
|
@ -8032,27 +8038,27 @@
|
|||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rolldown/binding-android-arm64": "1.0.0-rc.18",
|
||||
"@rolldown/binding-darwin-arm64": "1.0.0-rc.18",
|
||||
"@rolldown/binding-darwin-x64": "1.0.0-rc.18",
|
||||
"@rolldown/binding-freebsd-x64": "1.0.0-rc.18",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.18",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.18",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.0.0-rc.18",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.18",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.18",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.0.0-rc.18",
|
||||
"@rolldown/binding-linux-x64-musl": "1.0.0-rc.18",
|
||||
"@rolldown/binding-openharmony-arm64": "1.0.0-rc.18",
|
||||
"@rolldown/binding-wasm32-wasi": "1.0.0-rc.18",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.18",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.0.0-rc.18"
|
||||
"@rolldown/binding-android-arm64": "1.0.3",
|
||||
"@rolldown/binding-darwin-arm64": "1.0.3",
|
||||
"@rolldown/binding-darwin-x64": "1.0.3",
|
||||
"@rolldown/binding-freebsd-x64": "1.0.3",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.0.3",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.0.3",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-x64-musl": "1.0.3",
|
||||
"@rolldown/binding-openharmony-arm64": "1.0.3",
|
||||
"@rolldown/binding-wasm32-wasi": "1.0.3",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.0.3",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/rolldown/node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-CUY5Mnhe64xQBGZEEXQ5WyZwsc1JU3vAZLIxtrsBt3LO6UOb+C8GunVKqe9sT8NeWb4lqSaoJtp2xo6GxT1MNw==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
|
||||
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/roughjs": {
|
||||
|
|
@ -8361,9 +8367,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.16",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
|
||||
"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
|
||||
"version": "0.2.17",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
||||
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fdir": "^6.5.0",
|
||||
|
|
@ -8745,16 +8751,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "8.0.11",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.11.tgz",
|
||||
"integrity": "sha512-Jz1mxtUBR5xTT65VOdJZUUeoyLtqljmFkiUXhPTLZka3RDc9vpi/xXkyrnsdRcm2lIi3l3GPMnAidTsEGIj3Ow==",
|
||||
"version": "8.0.16",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz",
|
||||
"integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lightningcss": "^1.32.0",
|
||||
"picomatch": "^4.0.4",
|
||||
"postcss": "^8.5.14",
|
||||
"rolldown": "1.0.0-rc.18",
|
||||
"tinyglobby": "^0.2.16"
|
||||
"postcss": "^8.5.15",
|
||||
"rolldown": "1.0.3",
|
||||
"tinyglobby": "^0.2.17"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@
|
|||
"jsdom": "^29.1.1",
|
||||
"tree-sitter-wasms": "^0.1.13",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^8.0.11",
|
||||
"vite": "^8.0.16",
|
||||
"vitest": "^4.1.5",
|
||||
"wait-on": "^9.0.5"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import { logger } from '../../logger.js';
|
|||
/**
|
||||
* Cross-repo C/C++ `#include` dependency extractor.
|
||||
*
|
||||
* **Provider side:** registers every `.h/.hpp/.hxx/.hh` file in the repo
|
||||
* **Provider side:** registers every `.h/.hpp/.hxx/.hh/.cuh` file in the repo
|
||||
* as a provider contract with `include::<relative-path>`.
|
||||
*
|
||||
* **Consumer side:** parses all C/C++ source/header files for `#include "…"`
|
||||
|
|
@ -45,13 +45,20 @@ import { logger } from '../../logger.js';
|
|||
|
||||
// ---------- constants ----------
|
||||
|
||||
const HEADER_EXTENSIONS = new Set(['.h', '.hpp', '.hxx', '.hh']);
|
||||
const HEADER_EXTENSIONS = new Set(['.h', '.hpp', '.hxx', '.hh', '.cuh']);
|
||||
|
||||
// Source = headers (provider-eligible) ∪ implementation files (.c/.cpp/.cc/.cxx).
|
||||
// Source = headers (provider-eligible) ∪ implementation files (.c/.cpp/.cc/.cxx/.cu).
|
||||
// Spread keeps the subset relationship explicit so a future contributor adding
|
||||
// a new header extension to HEADER_EXTENSIONS does not have to remember to
|
||||
// also add it here.
|
||||
const SOURCE_EXTENSIONS = new Set<string>([...HEADER_EXTENSIONS, '.c', '.cpp', '.cc', '.cxx']);
|
||||
const SOURCE_EXTENSIONS = new Set<string>([
|
||||
...HEADER_EXTENSIONS,
|
||||
'.c',
|
||||
'.cpp',
|
||||
'.cc',
|
||||
'.cxx',
|
||||
'.cu',
|
||||
]);
|
||||
|
||||
const INCLUDE_QUERY_SRC = '(preproc_include path: (_) @import.source) @import';
|
||||
|
||||
|
|
@ -275,6 +282,8 @@ function getLanguageForFile(filePath: string): unknown | null {
|
|||
case '.hpp':
|
||||
case '.hxx':
|
||||
case '.hh':
|
||||
case '.cu':
|
||||
case '.cuh':
|
||||
return Cpp;
|
||||
default:
|
||||
return null;
|
||||
|
|
@ -298,7 +307,7 @@ function getLanguageForFile(filePath: string): unknown | null {
|
|||
function isLocalInclude(cleaned: string, suffixIndex: SuffixIndex): boolean {
|
||||
const candidates = [cleaned];
|
||||
if (!/\.[a-zA-Z0-9]+$/.test(cleaned)) {
|
||||
for (const ext of ['.h', '.hpp', '.hxx', '.hh']) candidates.push(cleaned + ext);
|
||||
for (const ext of HEADER_EXTENSIONS) candidates.push(cleaned + ext);
|
||||
}
|
||||
for (const c of candidates) {
|
||||
if (suffixIndex.get(c) || suffixIndex.getInsensitive(c)) return true;
|
||||
|
|
@ -428,7 +437,7 @@ export class IncludeExtractor implements ContractExtractor {
|
|||
try {
|
||||
const rows = await db(
|
||||
`MATCH (f:File)
|
||||
WHERE f.filePath =~ '.*\\\\.(h|hpp|hxx|hh)$'
|
||||
WHERE f.filePath =~ '.*\\\\.(h|hpp|hxx|hh|cuh)$'
|
||||
RETURN f.filePath AS filePath, f.id AS fileId`,
|
||||
);
|
||||
// gitnexus analyze stores absolute paths in the File.filePath column.
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ export const EXTENSIONS = [
|
|||
'.cxx',
|
||||
'.hxx',
|
||||
'.hh',
|
||||
'.cu',
|
||||
'.cuh',
|
||||
// C#
|
||||
'.cs',
|
||||
// Go
|
||||
|
|
|
|||
|
|
@ -427,7 +427,9 @@ export const cProvider = defineLanguage({
|
|||
|
||||
export const cppProvider = defineLanguage({
|
||||
id: SupportedLanguages.CPlusPlus,
|
||||
extensions: ['.cpp', '.cc', '.cxx', '.h', '.hpp', '.hxx', '.hh'],
|
||||
// CUDA files route through tree-sitter-cpp as a conservative C++-subset parser:
|
||||
// definitions still extract, but CUDA launch syntax (`<<< >>>`) is not modeled as calls.
|
||||
extensions: ['.cpp', '.cc', '.cxx', '.h', '.hpp', '.hxx', '.hh', '.cu', '.cuh'],
|
||||
entryPointPatterns: [
|
||||
/^main$/,
|
||||
/^init_/,
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@ import { readdirSync, type Dirent } from 'fs';
|
|||
import { join, relative } from 'path';
|
||||
|
||||
/** C++ header extensions to scan for in the workspace. */
|
||||
const HEADER_EXTENSIONS = new Set(['.h', '.hpp', '.hxx', '.hh']);
|
||||
const HEADER_EXTENSIONS = new Set(['.h', '.hpp', '.hxx', '.hh', '.cuh']);
|
||||
|
||||
/**
|
||||
* Walk `repoPath` recursively and return relative paths of all C++ header files.
|
||||
* Used by `loadResolutionConfig` so the C++ resolver can resolve `#include`
|
||||
* targets that live in header files.
|
||||
*
|
||||
* Scans for: .h, .hpp, .hxx, .hh
|
||||
* Scans for: .h, .hpp, .hxx, .hh, .cuh
|
||||
*/
|
||||
export function scanCppHeaderFiles(repoPath: string): ReadonlySet<string> {
|
||||
const headers = new Set<string>();
|
||||
|
|
|
|||
|
|
@ -47,6 +47,15 @@ const orderedRelationships = (
|
|||
/** Flush buffered rows to disk every N rows */
|
||||
const FLUSH_EVERY = 500;
|
||||
|
||||
/**
|
||||
* Yield the event loop every N relationship rows during the emit pass (#2226 F4)
|
||||
* so a concurrent node COPY (the overlap in loadGraphToLbug) and write-stream
|
||||
* drain callbacks get scheduling time during long synchronous emit stretches.
|
||||
* Scheduling-only — never changes row content or order (byte-identical). Tuning
|
||||
* constant, not load-bearing.
|
||||
*/
|
||||
const REL_YIELD_EVERY = 5000;
|
||||
|
||||
// ============================================================================
|
||||
// CSV ESCAPE UTILITIES
|
||||
// ============================================================================
|
||||
|
|
@ -282,11 +291,26 @@ export interface StreamedCSVResult {
|
|||
* Stream all CSV data directly to disk files.
|
||||
* Iterates graph nodes exactly ONCE — routes each node to the right writer.
|
||||
* File contents are lazy-read from disk with a generous LRU cache.
|
||||
*
|
||||
* `onNodePhaseComplete` (optional, #2203 parallelism leg): fired exactly once,
|
||||
* right after every node CSV is fully flushed to disk and BEFORE the
|
||||
* relationship pass starts writing any `rel_*.csv`. It receives the finished
|
||||
* node-file manifest so the caller can begin `COPY`-ing nodes while this
|
||||
* function keeps generating relationship CSVs (the only single-writer-safe
|
||||
* overlap — node `COPY` ‖ relationship emit). It is intentionally NOT awaited:
|
||||
* the relationship pass proceeds concurrently with whatever the caller
|
||||
* schedules. A synchronous throw from the callback is allowed and propagates out
|
||||
* of this function (rejecting the returned promise) — it is raised before the
|
||||
* relationship pass begins, so no `rel_*.csv` is written; `loadGraphToLbug` uses
|
||||
* this to surface its PDG-manifest collision guard. The callback must NOT, however,
|
||||
* schedule un-awaited async work that can reject unobserved. Absent ⇒ today's
|
||||
* behavior, byte-for-byte.
|
||||
*/
|
||||
export const streamAllCSVsToDisk = async (
|
||||
graph: KnowledgeGraph,
|
||||
repoPath: string,
|
||||
csvDir: string,
|
||||
onNodePhaseComplete?: (nodeFiles: Map<NodeTableName, { csvPath: string; rows: number }>) => void,
|
||||
): Promise<StreamedCSVResult> => {
|
||||
// Deterministic (id-sorted) node/relationship row order when enabled;
|
||||
// default off = today's graph-insertion order (byte-identical).
|
||||
|
|
@ -615,29 +639,11 @@ export const streamAllCSVsToDisk = async (
|
|||
];
|
||||
await Promise.all(allWriters.map((w) => w.finish()));
|
||||
|
||||
// --- Stream relationships directly to per-FROM→TO-label-pair files ---
|
||||
// (#2203 U2) Route every edge to its pair file in this single pass. The old
|
||||
// monolithic relations.csv — and its line-by-line re-read + per-edge regex
|
||||
// re-split in loadGraphToLbug — are gone, so the ~1M-edge set is written and
|
||||
// read once instead of twice. The router applies the SAME label-derivation +
|
||||
// validTables filter as the legacy splitRelCsvByLabelPair, so the per-pair
|
||||
// files are byte-identical (asserted by the differential test).
|
||||
const relRouter = new RelPairRouter(csvDir, REL_CSV_HEADER, new Set<string>(NODE_TABLES));
|
||||
try {
|
||||
for (const rel of orderedRelationships(graph, sortOutput)) {
|
||||
const pending = relRouter.route(rel.sourceId, rel.targetId, buildRelRow(rel));
|
||||
if (pending) await pending;
|
||||
}
|
||||
await relRouter.close();
|
||||
} catch (err) {
|
||||
relRouter.destroy();
|
||||
// Rethrow the real stream error (EMFILE / disk-full) rather than the generic
|
||||
// AbortError a pending drain-await rejects with — mirrors the retained
|
||||
// splitRelCsvByLabelPair's `throw streamError ?? err`.
|
||||
throw relRouter.lastError ?? err;
|
||||
}
|
||||
|
||||
// Build result map — only include tables that have rows
|
||||
// Build the node-file manifest now (all writers are flushed; `.rows` is
|
||||
// final). Hoisted above the relationship pass so `onNodePhaseComplete` can
|
||||
// hand the caller a complete node manifest to start COPY-ing while we keep
|
||||
// generating relationship CSVs below (#2203 overlap). The same map is
|
||||
// returned, so the result is unchanged when no callback is supplied.
|
||||
const nodeFiles = new Map<NodeTableName, { csvPath: string; rows: number }>();
|
||||
const tableMap: [NodeTableName, BufferedCSVWriter][] = [
|
||||
['File', fileWriter],
|
||||
|
|
@ -666,6 +672,37 @@ export const streamAllCSVsToDisk = async (
|
|||
}
|
||||
}
|
||||
|
||||
// Node CSVs are on disk; relationship CSVs have not been touched yet. Hand
|
||||
// the manifest to the caller (not awaited — the rel pass runs concurrently).
|
||||
onNodePhaseComplete?.(nodeFiles);
|
||||
|
||||
// --- Stream relationships directly to per-FROM→TO-label-pair files ---
|
||||
// (#2203 U2) Route every edge to its pair file in this single pass. The old
|
||||
// monolithic relations.csv — and its line-by-line re-read + per-edge regex
|
||||
// re-split in loadGraphToLbug — are gone, so the ~1M-edge set is written and
|
||||
// read once instead of twice. The router applies the SAME label-derivation +
|
||||
// validTables filter as the legacy splitRelCsvByLabelPair, so the per-pair
|
||||
// files are byte-identical (asserted by the differential test).
|
||||
const relRouter = new RelPairRouter(csvDir, REL_CSV_HEADER, new Set<string>(NODE_TABLES));
|
||||
try {
|
||||
let emitted = 0;
|
||||
for (const rel of orderedRelationships(graph, sortOutput)) {
|
||||
const pending = relRouter.route(rel.sourceId, rel.targetId, buildRelRow(rel));
|
||||
if (pending) await pending;
|
||||
// Periodically hand the event loop back so the overlapped node COPY and
|
||||
// write-stream drains run instead of starving behind this synchronous
|
||||
// loop (#2226 F4). No effect on emitted bytes — pure scheduling.
|
||||
if (++emitted % REL_YIELD_EVERY === 0) await new Promise((r) => setImmediate(r));
|
||||
}
|
||||
await relRouter.close();
|
||||
} catch (err) {
|
||||
relRouter.destroy();
|
||||
// Rethrow the real stream error (EMFILE / disk-full) rather than the generic
|
||||
// AbortError a pending drain-await rejects with — mirrors the retained
|
||||
// splitRelCsvByLabelPair's `throw streamError ?? err`.
|
||||
throw relRouter.lastError ?? err;
|
||||
}
|
||||
|
||||
return {
|
||||
nodeFiles,
|
||||
relsByPair: relRouter.byPair,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import {
|
|||
STALE_HASH_SENTINEL,
|
||||
NodeTableName,
|
||||
} from './schema.js';
|
||||
import { streamAllCSVsToDisk } from './csv-generator.js';
|
||||
import { streamAllCSVsToDisk, type StreamedCSVResult } from './csv-generator.js';
|
||||
import type { PdgEmitManifest } from './pdg-emit-sink.js';
|
||||
import { getNodeLabel as deriveNodeLabel, type WriteStreamFactory } from './rel-pair-routing.js';
|
||||
import type { CachedEmbedding } from '../embeddings/types.js';
|
||||
|
|
@ -876,6 +876,73 @@ const doInitLbug = async (dbPath: string, readOnly: boolean = false) => {
|
|||
|
||||
export type LbugProgressCallback = (message: string) => void;
|
||||
|
||||
/**
|
||||
* Run a COPY, retrying once with IGNORE_ERRORS=true (which skips row-level
|
||||
* errors) on first failure. On a second failure, hand the RAW retry error to
|
||||
* `onError` — each call site formats + slices its own message (#2226 F5: node
|
||||
* COPY slices to 200 chars and throws; relationship COPY slices to 80 and warns,
|
||||
* so the helper must not pre-format and lose that distinction). `onError` may
|
||||
* throw to propagate the failure.
|
||||
*/
|
||||
const copyCsvWithRetry = async (
|
||||
targetConn: lbug.Connection,
|
||||
copyQuery: string,
|
||||
onError: (retryErr: unknown) => void,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
await queryAndDrain(targetConn, copyQuery);
|
||||
} catch {
|
||||
try {
|
||||
const retryQuery = copyQuery.replace(
|
||||
'auto_detect=false)',
|
||||
'auto_detect=false, IGNORE_ERRORS=true)',
|
||||
);
|
||||
await queryAndDrain(targetConn, retryQuery);
|
||||
} catch (retryErr) {
|
||||
onError(retryErr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Bulk-COPY every node CSV sequentially on the single writable connection
|
||||
* (LadybugDB allows one write txn at a time). Extracted from loadGraphToLbug so
|
||||
* it can run either at the node-phase boundary — overlapping the relationship
|
||||
* emit pass (#2203) — or after emit in the serial escape-hatch path. Each COPY
|
||||
* keeps the IGNORE_ERRORS=true retry; a hard failure throws (no node rows ⇒ the
|
||||
* relationship COPY would dangle on missing endpoints).
|
||||
*/
|
||||
const copyNodeCSVs = async (
|
||||
targetConn: lbug.Connection,
|
||||
nodeFileEntries: [NodeTableName, { csvPath: string; rows: number }][],
|
||||
log: (message: string) => void,
|
||||
totalSteps: number,
|
||||
): Promise<void> => {
|
||||
let stepsDone = 0;
|
||||
for (const [table, { csvPath, rows }] of nodeFileEntries) {
|
||||
stepsDone++;
|
||||
log(`Loading nodes ${stepsDone}/${totalSteps}: ${table} (${rows.toLocaleString()} rows)`);
|
||||
|
||||
const copyQuery = getCopyQuery(table, normalizeCopyPath(csvPath));
|
||||
await copyCsvWithRetry(targetConn, copyQuery, (retryErr) => {
|
||||
const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
|
||||
throw new Error(`COPY failed for ${table}: ${retryMsg.slice(0, 200)}`);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Persist a KnowledgeGraph: stream CSVs, then bulk-COPY nodes (overlapped with
|
||||
* relationship emit — see the body) and relationships.
|
||||
*
|
||||
* NOT TRANSACTIONAL (#2226). Each `COPY` commits independently and there is no
|
||||
* surrounding transaction, so a failure partway through — a node `COPY` that
|
||||
* throws at the FK barrier, a relationship `COPY` failure, or a `pdgEmitManifest`
|
||||
* collision raised after node rows have already committed in the overlap path —
|
||||
* leaves a partially-loaded DB. The caller surfaces the error; recovery is a
|
||||
* `--force` re-analyze (a full rebuild), not a partial retry. Callers must not
|
||||
* assume the DB is either fully loaded or untouched after a rejection.
|
||||
*/
|
||||
export const loadGraphToLbug = async (
|
||||
graph: KnowledgeGraph,
|
||||
repoPath: string,
|
||||
|
|
@ -904,36 +971,99 @@ export const loadGraphToLbug = async (
|
|||
// the gap that the DB-persistence path is un-timed today (the analyze
|
||||
// "emit" number is the scope-resolution emit bucket, not this COPY path).
|
||||
const PROF = process.env.PROF_LBUG_LOAD === '1';
|
||||
// Escape hatch / differential oracle (#2203): force the legacy strictly-serial
|
||||
// load order (emit everything, THEN COPY nodes, THEN COPY rels) instead of the
|
||||
// default node-COPY ‖ rel-emit overlap. Lets an operator revert the behavior at
|
||||
// runtime, and lets a test load the same graph both ways and assert identical
|
||||
// persisted content.
|
||||
const SERIAL = process.env.GITNEXUS_SERIAL_LBUG_LOAD === '1';
|
||||
const mark = (): bigint => (PROF ? process.hrtime.bigint() : 0n);
|
||||
const span = (a: bigint, b: bigint): string => (Number(b - a) / 1e6).toFixed(1);
|
||||
const tStart = mark();
|
||||
|
||||
const csvDir = resolveNativeSafeStorageDir(storagePath, 'csv');
|
||||
|
||||
log('Streaming CSVs to disk...');
|
||||
const csvResult = await streamAllCSVsToDisk(graph, repoPath, csvDir);
|
||||
// The single writable connection (LadybugDB is single-writer). Captured as a
|
||||
// const so the node-COPY closure has a non-null reference — TS cannot narrow
|
||||
// the reassignable module-level `conn` across the callback boundary.
|
||||
const writeConn = conn;
|
||||
const validTables = new Set<string>(NODE_TABLES as readonly string[]);
|
||||
|
||||
// Merge the streamed PDG-emit CSVs (#2202) into the COPY plan so the
|
||||
// BasicBlock node table + per-pair PDG edges (CFG / REACHING_DEF / CDG /
|
||||
// POST_DOMINATE / TAINTED / SANITIZES) load through the SAME node + per-pair
|
||||
// COPY loops as the structural CSVs. The graph held zero BasicBlocks when
|
||||
// streaming, so `streamAllCSVsToDisk` produced none of these — the manifest
|
||||
// is the sole source and there is no double-COPY. Absent ⇒ no-op.
|
||||
if (pdgEmitManifest) {
|
||||
// Merge the streamed PDG-emit node CSVs (#2202) into a node-file map. Collision
|
||||
// guard: a BasicBlock in the in-memory graph during a streamed run is an
|
||||
// invariant violation (streamAllCSVsToDisk would also emit basicblock.csv), so
|
||||
// fail loudly rather than drop rows (#2202 review #3). Runs at the node-phase
|
||||
// boundary so the manifest BasicBlock table COPYs with the structural CSVs.
|
||||
const mergeManifestNodeFiles = (
|
||||
nodeFilesMap: Map<NodeTableName, { csvPath: string; rows: number }>,
|
||||
): void => {
|
||||
if (!pdgEmitManifest) return;
|
||||
for (const [table, meta] of pdgEmitManifest.nodeFiles) {
|
||||
// A collision means a BasicBlock leaked into the in-memory graph during a
|
||||
// streamed run (streamAllCSVsToDisk then emitted a structural basicblock.csv).
|
||||
// That is a streaming-invariant violation — fail loudly rather than
|
||||
// silently overwrite one CSV with the other and drop its rows (#2202 review #3).
|
||||
if (csvResult.nodeFiles.has(table)) {
|
||||
if (nodeFilesMap.has(table)) {
|
||||
throw new Error(
|
||||
`Streaming PDG manifest collides with a structural node CSV for "${table}" — ` +
|
||||
`the in-memory graph should hold zero ${table} nodes when streaming. ` +
|
||||
`A ${table} node leaked into the graph during a streamed emit.`,
|
||||
);
|
||||
}
|
||||
csvResult.nodeFiles.set(table, meta);
|
||||
nodeFilesMap.set(table, meta);
|
||||
}
|
||||
};
|
||||
|
||||
// Node COPY is the only DB write that can overlap relationship CSV emit: the
|
||||
// rel pass writes new rel_*.csv files and never touches `conn`, while node COPY
|
||||
// uses `conn` and never touches the rel files. We start node COPY at the
|
||||
// node-phase boundary and let the rel pass run concurrently — the only
|
||||
// single-writer-safe parallelism (#2203). The rel COPY still waits for node
|
||||
// COPY (FK precondition), so the DB load order is unchanged.
|
||||
let nodeCopyPromise: Promise<void> | undefined;
|
||||
let nodeCopyError: unknown;
|
||||
const beginNodeCopy = (
|
||||
nodeFilesMap: Map<NodeTableName, { csvPath: string; rows: number }>,
|
||||
): void => {
|
||||
mergeManifestNodeFiles(nodeFilesMap);
|
||||
const entries = [...nodeFilesMap.entries()];
|
||||
// copyNodeCSVs logs node progress as step/total; it processes only node
|
||||
// tables (the rel COPY has its own "Loading edges" progress line), so the
|
||||
// denominator is the node-table count — not +1 reserving a rel step.
|
||||
// .catch captures the failure so an overlapped (mid-emit) rejection cannot
|
||||
// surface as an unhandled rejection; it is rethrown at the FK barrier below.
|
||||
nodeCopyPromise = copyNodeCSVs(writeConn, entries, log, entries.length).catch((e) => {
|
||||
nodeCopyError = e;
|
||||
});
|
||||
};
|
||||
|
||||
log('Streaming CSVs to disk...');
|
||||
let csvResult: StreamedCSVResult;
|
||||
try {
|
||||
csvResult = SERIAL
|
||||
? await streamAllCSVsToDisk(graph, repoPath, csvDir)
|
||||
: await streamAllCSVsToDisk(graph, repoPath, csvDir, beginNodeCopy);
|
||||
} catch (emitErr) {
|
||||
// Relationship emit failed. In overlap mode a node COPY may be in flight —
|
||||
// settle it (the .catch above means this never rejects) before rethrowing so
|
||||
// it cannot leak as an unhandled rejection.
|
||||
if (nodeCopyPromise) await nodeCopyPromise;
|
||||
// If node COPY ALSO failed, emitErr wins the throw — log the swallowed node
|
||||
// error so a half-loaded DB isn't misattributed to the emit failure alone.
|
||||
if (nodeCopyError) {
|
||||
logger.warn(
|
||||
{ err: nodeCopyError },
|
||||
'[lbug-load] node COPY also failed while relationship emit was failing',
|
||||
);
|
||||
}
|
||||
throw emitErr;
|
||||
}
|
||||
const tCsv = mark();
|
||||
|
||||
// Merge the streamed PDG-emit per-pair rel CSVs (#2202) into the COPY plan —
|
||||
// collision-guarded. Done BEFORE node COPY so the serial escape hatch detects a
|
||||
// manifest/structural pair collision before committing any node rows (legacy
|
||||
// parity with the pre-overlap path), and the overlap path detects it as early
|
||||
// as csvResult is available. When a manifest is present, streaming was on and
|
||||
// the in-memory graph held zero BasicBlocks, so a structural collision means a
|
||||
// streaming-invariant violation — fail loudly rather than load corrupt data.
|
||||
if (pdgEmitManifest) {
|
||||
for (const [pairKey, meta] of pdgEmitManifest.relsByPair) {
|
||||
if (csvResult.relsByPair.has(pairKey)) {
|
||||
throw new Error(
|
||||
|
|
@ -945,38 +1075,18 @@ export const loadGraphToLbug = async (
|
|||
csvResult.totalValidRels += meta.rows;
|
||||
}
|
||||
}
|
||||
const tCsv = mark();
|
||||
|
||||
const validTables = new Set<string>(NODE_TABLES as readonly string[]);
|
||||
// Serial path: all CSVs are on disk and node COPY has not started — start it
|
||||
// here so the barrier below blocks on it exactly as the legacy path did.
|
||||
if (SERIAL) beginNodeCopy(csvResult.nodeFiles);
|
||||
|
||||
// Bulk COPY all node CSVs (sequential — LadybugDB allows only one write txn at a time)
|
||||
const nodeFiles = [...csvResult.nodeFiles.entries()];
|
||||
const totalSteps = nodeFiles.length + 1; // +1 for relationships
|
||||
let stepsDone = 0;
|
||||
|
||||
for (const [table, { csvPath, rows }] of nodeFiles) {
|
||||
stepsDone++;
|
||||
log(`Loading nodes ${stepsDone}/${totalSteps}: ${table} (${rows.toLocaleString()} rows)`);
|
||||
|
||||
const normalizedPath = normalizeCopyPath(csvPath);
|
||||
const copyQuery = getCopyQuery(table, normalizedPath);
|
||||
|
||||
try {
|
||||
await queryAndDrain(conn, copyQuery);
|
||||
} catch (err) {
|
||||
try {
|
||||
const retryQuery = copyQuery.replace(
|
||||
'auto_detect=false)',
|
||||
'auto_detect=false, IGNORE_ERRORS=true)',
|
||||
);
|
||||
await queryAndDrain(conn, retryQuery);
|
||||
} catch (retryErr) {
|
||||
const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
|
||||
throw new Error(`COPY failed for ${table}: ${retryMsg.slice(0, 200)}`);
|
||||
}
|
||||
}
|
||||
// FK barrier: node rows must exist before the relationship COPY resolves their
|
||||
// endpoints. In overlap mode most of node COPY was hidden behind rel emit, so
|
||||
// this await is the *residual* node-COPY time (≈0 when fully overlapped).
|
||||
if (nodeCopyPromise) await nodeCopyPromise;
|
||||
if (nodeCopyError) {
|
||||
throw nodeCopyError instanceof Error ? nodeCopyError : new Error(String(nodeCopyError));
|
||||
}
|
||||
|
||||
const tCopyNodes = mark();
|
||||
|
||||
// Bulk COPY relationships. They were already routed to per-FROM→TO-label-pair
|
||||
|
|
@ -999,28 +1109,19 @@ export const loadGraphToLbug = async (
|
|||
pairIdx++;
|
||||
const [fromLabel, toLabel] = pairKey.split('|');
|
||||
const normalizedPath = normalizeCopyPath(pairCsvPath);
|
||||
// PARALLEL=false is load-bearing here too — see COPY_CSV_OPTS (#2203 / kuzudb/kuzu#5778).
|
||||
const copyQuery = `COPY ${REL_TABLE_NAME} FROM "${normalizedPath}" (from="${fromLabel}", to="${toLabel}", HEADER=true, ESCAPE='"', DELIM=',', QUOTE='"', PARALLEL=false, auto_detect=false)`;
|
||||
|
||||
if (pairIdx % 5 === 0 || rows > 1000) {
|
||||
log(`Loading edges: ${pairIdx}/${relsByPair.size} types (${fromLabel} -> ${toLabel})`);
|
||||
}
|
||||
|
||||
try {
|
||||
await queryAndDrain(conn, copyQuery);
|
||||
} catch (err) {
|
||||
try {
|
||||
const retryQuery = copyQuery.replace(
|
||||
'auto_detect=false)',
|
||||
'auto_detect=false, IGNORE_ERRORS=true)',
|
||||
);
|
||||
await queryAndDrain(conn, retryQuery);
|
||||
} catch (retryErr) {
|
||||
const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
|
||||
warnings.push(`${fromLabel}->${toLabel} (${rows} edges): ${retryMsg.slice(0, 80)}`);
|
||||
failedPairEdges += rows;
|
||||
failedPairCsvPaths.add(pairCsvPath);
|
||||
}
|
||||
}
|
||||
await copyCsvWithRetry(conn, copyQuery, (retryErr) => {
|
||||
const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
|
||||
warnings.push(`${fromLabel}->${toLabel} (${rows} edges): ${retryMsg.slice(0, 80)}`);
|
||||
failedPairEdges += rows;
|
||||
failedPairCsvPaths.add(pairCsvPath);
|
||||
});
|
||||
// Only delete if not in failedPairCsvPaths (needed for fallback)
|
||||
if (!failedPairCsvPaths.has(pairCsvPath)) {
|
||||
try {
|
||||
|
|
@ -1077,8 +1178,13 @@ export const loadGraphToLbug = async (
|
|||
const tEnd = mark();
|
||||
let totalNodeRows = 0;
|
||||
for (const [, { rows }] of csvResult.nodeFiles) totalNodeRows += rows;
|
||||
// `mode` records which load path ran. In overlap mode `csv-emit` is the wall
|
||||
// to streamAllCSVsToDisk's return (node COPY overlapped part of it) and
|
||||
// `copy-nodes` is the RESIDUAL node-COPY await after emit returned — it
|
||||
// trends to 0 as the overlap hides node COPY behind relationship emit. In
|
||||
// serial mode the buckets carry their legacy, disjoint meaning.
|
||||
logger.warn(
|
||||
`[lbug-load prof] csv-emit=${span(tStart, tCsv)}ms ` +
|
||||
`[lbug-load prof] mode=${SERIAL ? 'serial' : 'overlap'} csv-emit=${span(tStart, tCsv)}ms ` +
|
||||
`copy-nodes=${span(tCsv, tCopyNodes)}ms copy-rels=${span(tCopyNodes, tCopyRels)}ms ` +
|
||||
`fallback=${span(tCopyRels, tFallback)}ms total=${span(tStart, tEnd)}ms ` +
|
||||
`(${totalNodeRows} nodes, ${insertedRels} rels)`,
|
||||
|
|
@ -1092,7 +1198,18 @@ export const loadGraphToLbug = async (
|
|||
// Source code content is full of backslashes which confuse the auto-detection.
|
||||
// We MUST explicitly set ESCAPE='"' to use RFC 4180 escaping, and disable auto_detect to prevent
|
||||
// LadybugDB from overriding our settings based on sample rows.
|
||||
const COPY_CSV_OPTS = `(HEADER=true, ESCAPE='"', DELIM=',', QUOTE='"', PARALLEL=false, auto_detect=false)`;
|
||||
//
|
||||
// PARALLEL=false IS LOAD-BEARING FOR CORRECTNESS — DO NOT FLIP IT (#2203).
|
||||
// LadybugDB's parallel CSV reader (Kuzu-derived; default PARALLEL=true) splits the
|
||||
// file into byte ranges parsed concurrently, and CANNOT determine line boundaries
|
||||
// when a quoted field contains an embedded newline — it errors with "Quoted newlines
|
||||
// are not supported in parallel CSV reader. Please specify PARALLEL=FALSE", or worse,
|
||||
// mis-parses silently (upstream kuzudb/kuzu#5778, still open). Our `content`/`text`
|
||||
// columns hold source code, so quoted multiline fields are guaranteed. PARALLEL=false
|
||||
// is therefore required, not conservative. The multiline-quoted round-trip in
|
||||
// test/integration/copy-parallel-invariant.test.ts fails loudly if this is ever flipped.
|
||||
// Exported so that test asserts the invariant statically as well.
|
||||
export const COPY_CSV_OPTS = `(HEADER=true, ESCAPE='"', DELIM=',', QUOTE='"', PARALLEL=false, auto_detect=false)`;
|
||||
|
||||
// Multi-language table names that were created with backticks in CODE_ELEMENT_BASE
|
||||
// and must always be referenced with backticks in queries
|
||||
|
|
@ -1170,7 +1287,7 @@ const TABLES_WITH_EXPORTED = new Set<string>([
|
|||
'CodeElement',
|
||||
]);
|
||||
|
||||
const getCopyQuery = (table: NodeTableName, filePath: string): string => {
|
||||
export const getCopyQuery = (table: NodeTableName, filePath: string): string => {
|
||||
const t = escapeTableName(table);
|
||||
if (table === 'File') {
|
||||
return `COPY ${t}(id, name, filePath, content) FROM "${filePath}" ${COPY_CSV_OPTS}`;
|
||||
|
|
|
|||
130
gitnexus/test/integration/copy-parallel-invariant.test.ts
Normal file
130
gitnexus/test/integration/copy-parallel-invariant.test.ts
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
/**
|
||||
* Integration test: PARALLEL=false is a load-bearing correctness invariant for
|
||||
* the bulk-COPY persistence path (#2203 — the "parallelized emit" follow-up).
|
||||
*
|
||||
* Issue #2203 asked us to investigate parallelizing emit. The most obvious
|
||||
* lever — LadybugDB's intra-COPY parallel CSV reader (`PARALLEL=true`, the
|
||||
* Kuzu default) — is UNSAFE for our data: that reader splits the file into byte
|
||||
* ranges parsed concurrently and cannot find line boundaries when a quoted
|
||||
* field contains an embedded newline (upstream kuzudb/kuzu#5778, still open;
|
||||
* error text "Quoted newlines are not supported in parallel CSV reader. Please
|
||||
* specify PARALLEL=FALSE"). Our `content`/`text` columns hold source code, so
|
||||
* quoted multiline fields are guaranteed. PARALLEL=false is therefore MANDATORY,
|
||||
* not conservative — this test locks that in two ways:
|
||||
*
|
||||
* 1. Live-DB proof — a node whose `text` carries embedded newlines AND quotes
|
||||
* round-trips byte-exact through the real csv-emit → COPY → query path.
|
||||
* If anyone flips PARALLEL=true, the parallel reader mis-parses this row and
|
||||
* the assertion fails loudly. (An edge also round-trips, exercising the rel
|
||||
* COPY path, which uses the same PARALLEL=false option.)
|
||||
* 2. Static guard — the generated COPY query strings still carry PARALLEL=false,
|
||||
* giving a crisp failure independent of a live DB.
|
||||
*
|
||||
* Needs a real LadybugDB connection (initLbug), so it lives under integration.
|
||||
*/
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import { NODE_TABLES } from 'gitnexus-shared';
|
||||
import { buildTestGraph } from '../helpers/test-graph.js';
|
||||
|
||||
let tmpBase: string;
|
||||
let storagePath: string;
|
||||
let dbPath: string;
|
||||
|
||||
// A BasicBlock `text` with the exact hazard the parallel reader cannot handle:
|
||||
// embedded newlines INSIDE a field that also contains double-quotes. After
|
||||
// escapeCSVField this becomes a quoted multiline CSV field.
|
||||
const HAZARD_TEXT = 'const msg = "line one";\nconst other = "she said \\"hi\\"";\nreturn msg;';
|
||||
const BB1 = 'BasicBlock:src/hazard.ts:0';
|
||||
const BB2 = 'BasicBlock:src/hazard.ts:1';
|
||||
|
||||
beforeAll(async () => {
|
||||
// mkdtemp (not a predictable os.tmpdir join) + the `gitnexus-lbug-` prefix
|
||||
// that TEST_FIXTURE_PREFIXES recognizes for the stale-sidecar sweep.
|
||||
tmpBase = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-lbug-copy-parallel-'));
|
||||
storagePath = path.join(tmpBase, '.gitnexus');
|
||||
dbPath = path.join(storagePath, 'lbug');
|
||||
await fs.mkdir(dbPath, { recursive: true });
|
||||
|
||||
const adapter = await import('../../src/core/lbug/lbug-adapter.js');
|
||||
await adapter.initLbug(dbPath);
|
||||
|
||||
const graph = buildTestGraph(
|
||||
[
|
||||
{
|
||||
id: BB1,
|
||||
label: 'BasicBlock',
|
||||
name: '',
|
||||
filePath: 'src/hazard.ts',
|
||||
startLine: 1,
|
||||
endLine: 3,
|
||||
extra: { text: HAZARD_TEXT },
|
||||
},
|
||||
{
|
||||
id: BB2,
|
||||
label: 'BasicBlock',
|
||||
name: '',
|
||||
filePath: 'src/hazard.ts',
|
||||
startLine: 4,
|
||||
endLine: 4,
|
||||
extra: { text: 'sink(msg);' },
|
||||
},
|
||||
],
|
||||
[{ sourceId: BB1, targetId: BB2, type: 'CFG', reason: 'cfg-edge' }],
|
||||
);
|
||||
|
||||
await adapter.loadGraphToLbug(graph, tmpBase, storagePath);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
try {
|
||||
const adapter = await import('../../src/core/lbug/lbug-adapter.js');
|
||||
await adapter.closeLbug();
|
||||
} catch {
|
||||
/* may not have opened */
|
||||
}
|
||||
if (tmpBase) {
|
||||
for (let attempt = 0; attempt < 5; attempt++) {
|
||||
try {
|
||||
await fs.rm(tmpBase, { recursive: true, force: true });
|
||||
return;
|
||||
} catch {
|
||||
if (attempt < 4) await new Promise((r) => setTimeout(r, 200 * (attempt + 1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
describe('PARALLEL=false correctness invariant (#2203 / kuzudb/kuzu#5778)', () => {
|
||||
it('a multiline-quoted content field round-trips byte-exact through COPY', async () => {
|
||||
const adapter = await import('../../src/core/lbug/lbug-adapter.js');
|
||||
const rows = await adapter.executeQuery(
|
||||
`MATCH (n:BasicBlock {id: '${BB1}'}) RETURN n.text AS text`,
|
||||
);
|
||||
expect(rows).toHaveLength(1);
|
||||
// Byte-exact: the embedded newlines and the doubled quotes survived the
|
||||
// quoted-field round-trip. PARALLEL=true would have mis-split this row.
|
||||
expect(rows[0].text).toBe(HAZARD_TEXT);
|
||||
});
|
||||
|
||||
it('an edge round-trips through the relationship COPY path (same PARALLEL=false)', async () => {
|
||||
const adapter = await import('../../src/core/lbug/lbug-adapter.js');
|
||||
const rows = await adapter.executeQuery(
|
||||
"MATCH (:BasicBlock)-[r:CodeRelation {type: 'CFG'}]->(:BasicBlock) RETURN count(r) AS c",
|
||||
);
|
||||
expect(Number(rows[0].c)).toBe(1);
|
||||
});
|
||||
|
||||
it('every generated node COPY query statically carries PARALLEL=false', async () => {
|
||||
const adapter = await import('../../src/core/lbug/lbug-adapter.js');
|
||||
expect(adapter.COPY_CSV_OPTS).toContain('PARALLEL=false');
|
||||
expect(adapter.COPY_CSV_OPTS).not.toContain('PARALLEL=true');
|
||||
for (const table of NODE_TABLES) {
|
||||
const q = adapter.getCopyQuery(table, '/tmp/x.csv');
|
||||
expect(q, `${table} COPY must keep PARALLEL=false`).toContain('PARALLEL=false');
|
||||
expect(q, `${table} COPY must not enable PARALLEL`).not.toContain('PARALLEL=true');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
import { describe, it, expect, beforeAll, beforeEach, afterAll } from 'vitest';
|
||||
import fs from 'fs/promises';
|
||||
import { readdirSync } from 'node:fs';
|
||||
import { finished } from 'stream/promises';
|
||||
import path from 'path';
|
||||
import { createTempDir, type TestDBHandle } from '../helpers/test-db.js';
|
||||
|
|
@ -565,3 +566,69 @@ describe('streamAllCSVsToDisk — direct per-pair emit matches the split oracle'
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
// The overlap leg (#2203) needs to start COPY-ing nodes while relationship CSVs
|
||||
// are still being written. streamAllCSVsToDisk exposes that boundary via an
|
||||
// onNodePhaseComplete callback. These tests pin the contract: it fires once,
|
||||
// after node CSVs exist and before any rel CSV does, and supplying it does not
|
||||
// change the emitted output.
|
||||
describe('onNodePhaseComplete hook (#2203 overlap boundary)', () => {
|
||||
const hookGraph = () =>
|
||||
buildTestGraph(
|
||||
[
|
||||
{ id: 'File:src/index.ts', label: 'File', name: 'index.ts', filePath: 'src/index.ts' },
|
||||
{
|
||||
id: 'Function:src/index.ts:main:1',
|
||||
label: 'Function',
|
||||
name: 'main',
|
||||
filePath: 'src/index.ts',
|
||||
startLine: 1,
|
||||
endLine: 3,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
sourceId: 'File:src/index.ts',
|
||||
targetId: 'Function:src/index.ts:main:1',
|
||||
type: 'DEFINES',
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
it('fires exactly once, after node CSVs are flushed and before any rel CSV exists', async () => {
|
||||
const hookCsvDir = path.join(tmpHandle.dbPath, 'csv-hook-timing');
|
||||
let calls = 0;
|
||||
let nodeCsvsPresent = false;
|
||||
let relCsvsPresent = true;
|
||||
let handedKeys: string[] = [];
|
||||
|
||||
const result = await streamAllCSVsToDisk(hookGraph(), repoDir, hookCsvDir, (nodeFiles) => {
|
||||
calls++;
|
||||
handedKeys = [...nodeFiles.keys()].sort();
|
||||
const entries = readdirSync(hookCsvDir);
|
||||
nodeCsvsPresent = entries.includes('file.csv') && entries.includes('function.csv');
|
||||
relCsvsPresent = entries.some((f) => f.startsWith('rel_'));
|
||||
});
|
||||
|
||||
expect(calls).toBe(1);
|
||||
expect(nodeCsvsPresent).toBe(true);
|
||||
// No relationship CSV may exist yet — the rel pass starts after the hook.
|
||||
expect(relCsvsPresent).toBe(false);
|
||||
// The manifest handed to the callback is the one returned to the caller.
|
||||
expect(handedKeys).toEqual([...result.nodeFiles.keys()].sort());
|
||||
expect(result.totalValidRels).toBe(1);
|
||||
});
|
||||
|
||||
it('supplying the callback does not change the node manifest (no behavior change)', async () => {
|
||||
const withDir = path.join(tmpHandle.dbPath, 'csv-hook-with');
|
||||
const withoutDir = path.join(tmpHandle.dbPath, 'csv-hook-without');
|
||||
const withCb = await streamAllCSVsToDisk(hookGraph(), repoDir, withDir, () => {});
|
||||
const without = await streamAllCSVsToDisk(hookGraph(), repoDir, withoutDir);
|
||||
|
||||
const manifest = (r: typeof withCb) =>
|
||||
[...r.nodeFiles.entries()].map(([k, v]) => `${k}:${v.rows}`).sort();
|
||||
expect(manifest(withCb)).toEqual(manifest(without));
|
||||
expect(withCb.totalValidRels).toBe(without.totalValidRels);
|
||||
expect(withCb.skippedRels).toBe(without.skippedRels);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -192,4 +192,39 @@ describe('IncludeExtractor → syncGroup integration (finding #7)', () => {
|
|||
fs.rmSync(consumerDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('suppresses extensionless local includes that resolve to .cuh headers', async () => {
|
||||
const repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-include-cuh-'));
|
||||
try {
|
||||
fs.mkdirSync(path.join(repoDir, 'include'), { recursive: true });
|
||||
fs.mkdirSync(path.join(repoDir, 'src'), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(repoDir, 'include/kernel.cuh'),
|
||||
'#pragma once\n__global__ void kernel();',
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(repoDir, 'src/main.cu'),
|
||||
'#include "include/kernel"\nvoid host() {}',
|
||||
);
|
||||
|
||||
const extractor = new IncludeExtractor();
|
||||
const contracts = await extractor.extract(null, repoDir, {
|
||||
id: 'cuda-repo',
|
||||
path: 'app/cuda-repo',
|
||||
repoPath: repoDir,
|
||||
storagePath: path.join(repoDir, '.gitnexus'),
|
||||
});
|
||||
|
||||
expect(
|
||||
contracts.some(
|
||||
(c) => c.role === 'provider' && c.contractId === 'include::include/kernel.cuh',
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
contracts.some((c) => c.role === 'consumer' && c.contractId === 'include::include/kernel'),
|
||||
).toBe(false);
|
||||
} finally {
|
||||
fs.rmSync(repoDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
211
gitnexus/test/integration/lbug-load-overlap-errors.test.ts
Normal file
211
gitnexus/test/integration/lbug-load-overlap-errors.test.ts
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
/**
|
||||
* Integration test: loadGraphToLbug overlap ERROR paths (#2226 review F1/F2).
|
||||
*
|
||||
* The node-COPY ‖ relationship-emit overlap (#2203) added two error branches the
|
||||
* happy-path tests don't exercise:
|
||||
* - F1: relationship emit fails while node COPY is in flight — the emit error
|
||||
* must surface from loadGraphToLbug AND the in-flight node-COPY promise must
|
||||
* be settled, so nothing leaks as an unhandled rejection.
|
||||
* - F2: node COPY itself fails — the captured error must be rethrown at the FK
|
||||
* barrier rather than silently swallowed.
|
||||
*
|
||||
* Both are fault-injected by mocking `streamAllCSVsToDisk` so the test controls
|
||||
* when `onNodePhaseComplete` fires (starting node COPY) and whether emit throws,
|
||||
* while a REAL LadybugDB connection (`initLbug`) runs the node COPY. The mock
|
||||
* pattern (vi.hoisted + vi.mock with importOriginal, preserving every other
|
||||
* export) mirrors test/unit/api-graph-streaming.test.ts.
|
||||
*/
|
||||
import { describe, it, expect, beforeAll, afterAll, afterEach, vi } from 'vitest';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import type { StreamedCSVResult } from '../../src/core/lbug/csv-generator.js';
|
||||
import type { NodeTableName } from '../../src/core/lbug/schema.js';
|
||||
import { buildTestGraph } from '../helpers/test-graph.js';
|
||||
|
||||
const { emitMock } = vi.hoisted(() => ({ emitMock: vi.fn() }));
|
||||
|
||||
// Replace ONLY streamAllCSVsToDisk; importOriginal preserves StreamedCSVResult
|
||||
// and every other export lbug-adapter (and its transitive deps) rely on.
|
||||
vi.mock('../../src/core/lbug/csv-generator.js', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('../../src/core/lbug/csv-generator.js')>();
|
||||
return { ...actual, streamAllCSVsToDisk: emitMock };
|
||||
});
|
||||
|
||||
type NodeFiles = Map<NodeTableName, { csvPath: string; rows: number }>;
|
||||
|
||||
const REL_HEADER = 'from,to,type,confidence,reason,step';
|
||||
const emptyResult = (): StreamedCSVResult => ({
|
||||
nodeFiles: new Map(),
|
||||
relsByPair: new Map(),
|
||||
relHeader: REL_HEADER,
|
||||
skippedRels: 0,
|
||||
totalValidRels: 0,
|
||||
});
|
||||
|
||||
let tmpBase: string;
|
||||
let storagePath: string;
|
||||
let dbPath: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
tmpBase = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-lbug-overlap-err-'));
|
||||
storagePath = path.join(tmpBase, '.gitnexus');
|
||||
dbPath = path.join(storagePath, 'lbug');
|
||||
await fs.mkdir(dbPath, { recursive: true });
|
||||
|
||||
const adapter = await import('../../src/core/lbug/lbug-adapter.js');
|
||||
await adapter.initLbug(dbPath);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
emitMock.mockReset();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
try {
|
||||
const adapter = await import('../../src/core/lbug/lbug-adapter.js');
|
||||
await adapter.closeLbug();
|
||||
} catch {
|
||||
/* may already be closed */
|
||||
}
|
||||
if (tmpBase) {
|
||||
for (let attempt = 0; attempt < 5; attempt++) {
|
||||
try {
|
||||
await fs.rm(tmpBase, { recursive: true, force: true });
|
||||
return;
|
||||
} catch {
|
||||
if (attempt < 4) await new Promise((r) => setTimeout(r, 200 * (attempt + 1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
describe('loadGraphToLbug overlap error paths (#2226 F1)', () => {
|
||||
it('relationship-emit failure with node COPY in flight surfaces the emit error and leaks no unhandled rejection', async () => {
|
||||
const adapter = await import('../../src/core/lbug/lbug-adapter.js');
|
||||
const graph = buildTestGraph(
|
||||
[{ id: 'File:src/u1.ts', label: 'File', name: 'u1.ts', filePath: 'src/u1.ts' }],
|
||||
[],
|
||||
);
|
||||
|
||||
// emit writes a REAL node CSV (so node COPY actually runs to completion in
|
||||
// flight), fires the hook to start node COPY, then throws as if the
|
||||
// relationship pass blew up (EMFILE / disk-full).
|
||||
emitMock.mockImplementation(
|
||||
async (
|
||||
_g: unknown,
|
||||
_r: unknown,
|
||||
dir: string,
|
||||
onNodePhaseComplete?: (n: NodeFiles) => void,
|
||||
) => {
|
||||
await fs.mkdir(dir, { recursive: true }); // mocked emit skips the real csvDir creation
|
||||
const csvPath = path.join(dir, 'file.csv');
|
||||
await fs.writeFile(
|
||||
csvPath,
|
||||
'id,name,filePath,content\n"File:src/u1.ts","u1.ts","src/u1.ts",""\n',
|
||||
);
|
||||
onNodePhaseComplete?.(new Map([['File', { csvPath, rows: 1 }]]) as NodeFiles);
|
||||
throw new Error('simulated rel emit failure');
|
||||
},
|
||||
);
|
||||
|
||||
const rejections: unknown[] = [];
|
||||
const onUnhandled = (e: unknown): void => {
|
||||
rejections.push(e);
|
||||
};
|
||||
process.on('unhandledRejection', onUnhandled);
|
||||
try {
|
||||
await expect(adapter.loadGraphToLbug(graph, tmpBase, storagePath)).rejects.toThrow(
|
||||
'simulated rel emit failure',
|
||||
);
|
||||
// Flush the macrotask queue so any stray rejection from the in-flight
|
||||
// node-COPY promise actually surfaces BEFORE we assert — otherwise the
|
||||
// check passes vacuously.
|
||||
await new Promise((r) => setImmediate(r));
|
||||
expect(rejections).toEqual([]);
|
||||
} finally {
|
||||
// forks reuse the process across this file's tests — a leaked listener
|
||||
// would corrupt sibling tests.
|
||||
process.off('unhandledRejection', onUnhandled);
|
||||
}
|
||||
});
|
||||
|
||||
it('also logs the node-COPY error when BOTH emit and node COPY fail (emit error still wins)', async () => {
|
||||
const adapter = await import('../../src/core/lbug/lbug-adapter.js');
|
||||
const { _captureLogger } = await import('../../src/core/logger.js');
|
||||
const graph = buildTestGraph(
|
||||
[{ id: 'File:src/u1b.ts', label: 'File', name: 'u1b.ts', filePath: 'src/u1b.ts' }],
|
||||
[],
|
||||
);
|
||||
|
||||
// nodeFiles points at a MISSING csv (node COPY fails) AND emit throws.
|
||||
emitMock.mockImplementation(
|
||||
async (
|
||||
_g: unknown,
|
||||
_r: unknown,
|
||||
dir: string,
|
||||
onNodePhaseComplete?: (n: NodeFiles) => void,
|
||||
) => {
|
||||
onNodePhaseComplete?.(
|
||||
new Map([['File', { csvPath: path.join(dir, 'missing-u1b.csv'), rows: 1 }]]) as NodeFiles,
|
||||
);
|
||||
throw new Error('simulated rel emit failure (double)');
|
||||
},
|
||||
);
|
||||
|
||||
const cap = _captureLogger();
|
||||
const rejections: unknown[] = [];
|
||||
const onUnhandled = (e: unknown): void => {
|
||||
rejections.push(e);
|
||||
};
|
||||
process.on('unhandledRejection', onUnhandled);
|
||||
try {
|
||||
await expect(adapter.loadGraphToLbug(graph, tmpBase, storagePath)).rejects.toThrow(
|
||||
'simulated rel emit failure (double)',
|
||||
);
|
||||
await new Promise((r) => setImmediate(r));
|
||||
expect(rejections).toEqual([]);
|
||||
const warned = cap
|
||||
.records()
|
||||
.map((rec) => (typeof rec.msg === 'string' ? rec.msg : ''))
|
||||
.some((m) => m.includes('node COPY also failed'));
|
||||
expect(warned).toBe(true);
|
||||
} finally {
|
||||
cap.restore();
|
||||
process.off('unhandledRejection', onUnhandled);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadGraphToLbug overlap error paths (#2226 F2)', () => {
|
||||
it('a node-COPY hard failure is rethrown at the FK barrier', async () => {
|
||||
const adapter = await import('../../src/core/lbug/lbug-adapter.js');
|
||||
const graph = buildTestGraph(
|
||||
[{ id: 'File:src/u2.ts', label: 'File', name: 'u2.ts', filePath: 'src/u2.ts' }],
|
||||
[],
|
||||
);
|
||||
|
||||
// Node COPY targets a MISSING csv → COPY fails at bind time ("No file
|
||||
// found …"), which IGNORE_ERRORS does NOT suppress (it only skips row-level
|
||||
// errors), so copyNodeCSVs throws. Emit otherwise "succeeds" (returns an
|
||||
// empty result), so the only failure is the node COPY captured in
|
||||
// nodeCopyError and rethrown at the FK barrier.
|
||||
emitMock.mockImplementation(
|
||||
async (
|
||||
_g: unknown,
|
||||
_r: unknown,
|
||||
dir: string,
|
||||
onNodePhaseComplete?: (n: NodeFiles) => void,
|
||||
) => {
|
||||
onNodePhaseComplete?.(
|
||||
new Map([['File', { csvPath: path.join(dir, 'missing-u2.csv'), rows: 1 }]]) as NodeFiles,
|
||||
);
|
||||
return emptyResult();
|
||||
},
|
||||
);
|
||||
|
||||
await expect(adapter.loadGraphToLbug(graph, tmpBase, storagePath)).rejects.toThrow(
|
||||
/COPY failed for File/,
|
||||
);
|
||||
});
|
||||
});
|
||||
206
gitnexus/test/integration/lbug-load-overlap.test.ts
Normal file
206
gitnexus/test/integration/lbug-load-overlap.test.ts
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
/**
|
||||
* Integration test: the node-COPY ‖ relationship-emit overlap (#2203) persists
|
||||
* BYTE-IDENTICAL graph content to the legacy strictly-serial path.
|
||||
*
|
||||
* This is the acceptance gate for the #2203 "parallelized emit" follow-up: the
|
||||
* overlap reorders *scheduling* (node COPY runs while relationship CSVs are
|
||||
* still being written), never the data. We prove that by loading one fixture
|
||||
* graph into two fresh DBs — once via the default overlap path, once via the
|
||||
* GITNEXUS_SERIAL_LBUG_LOAD=1 escape hatch — and asserting the two databases are
|
||||
* content-equivalent: same per-table node counts, same per-type edge counts,
|
||||
* same multiline `content`/`text` field bytes, and the same loadGraphToLbug
|
||||
* return accounting.
|
||||
*
|
||||
* Needs a real LadybugDB connection (initLbug), so it lives under integration.
|
||||
*/
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import { buildTestGraph } from '../helpers/test-graph.js';
|
||||
|
||||
let tmpBase: string;
|
||||
let repoDir: string;
|
||||
|
||||
// Source-code-shaped content with embedded newlines AND quotes — the exact
|
||||
// shape PARALLEL=false exists to handle. Read from disk for the File node and
|
||||
// carried inline for the BasicBlock node, so both content sources are checked.
|
||||
const FILE_SRC = 'export function f() {\n const s = "a,b\\"c";\n return s;\n}\n';
|
||||
const BB_TEXT = 'if (cond) {\n log("x = " + x);\n}\nreturn "done";';
|
||||
|
||||
const BB1 = 'BasicBlock:src/a.ts:0';
|
||||
const BB2 = 'BasicBlock:src/a.ts:1';
|
||||
|
||||
const NODE_TABLES_CHECKED = ['File', 'Function', 'Class', 'BasicBlock'] as const;
|
||||
const EDGE_TYPES_CHECKED = ['DEFINES', 'CALLS', 'CFG', 'REACHING_DEF'] as const;
|
||||
|
||||
const buildFixture = () =>
|
||||
buildTestGraph(
|
||||
[
|
||||
{ id: 'File:src/a.ts', label: 'File', name: 'a.ts', filePath: 'src/a.ts' },
|
||||
{
|
||||
id: 'Function:src/a.ts:f:1',
|
||||
label: 'Function',
|
||||
name: 'f',
|
||||
filePath: 'src/a.ts',
|
||||
startLine: 1,
|
||||
endLine: 4,
|
||||
isExported: true,
|
||||
},
|
||||
{
|
||||
id: 'Class:src/a.ts:C:6',
|
||||
label: 'Class',
|
||||
name: 'C',
|
||||
filePath: 'src/a.ts',
|
||||
startLine: 6,
|
||||
endLine: 8,
|
||||
},
|
||||
{
|
||||
id: BB1,
|
||||
label: 'BasicBlock',
|
||||
name: '',
|
||||
filePath: 'src/a.ts',
|
||||
startLine: 1,
|
||||
endLine: 3,
|
||||
extra: { text: BB_TEXT },
|
||||
},
|
||||
{ id: BB2, label: 'BasicBlock', name: '', filePath: 'src/a.ts', startLine: 4, endLine: 4 },
|
||||
],
|
||||
[
|
||||
{ sourceId: 'File:src/a.ts', targetId: 'Function:src/a.ts:f:1', type: 'DEFINES' },
|
||||
{ sourceId: 'File:src/a.ts', targetId: 'Class:src/a.ts:C:6', type: 'DEFINES' },
|
||||
{ sourceId: 'Function:src/a.ts:f:1', targetId: 'Class:src/a.ts:C:6', type: 'CALLS' },
|
||||
{ sourceId: BB1, targetId: BB2, type: 'CFG', reason: 'cfg' },
|
||||
{ sourceId: BB1, targetId: BB2, type: 'REACHING_DEF', reason: 's' },
|
||||
],
|
||||
);
|
||||
|
||||
interface Snapshot {
|
||||
nodeCounts: Record<string, number>;
|
||||
edgeCounts: Record<string, number>;
|
||||
bbText: string | undefined;
|
||||
fileContent: string | undefined;
|
||||
ret: { insertedRels: number; skippedRels: number; warnings: string[] };
|
||||
}
|
||||
|
||||
const snapshotDb = async (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
adapter: any,
|
||||
ret: Snapshot['ret'],
|
||||
): Promise<Snapshot> => {
|
||||
const nodeCounts: Record<string, number> = {};
|
||||
for (const t of NODE_TABLES_CHECKED) {
|
||||
const rows = await adapter.executeQuery(`MATCH (n:\`${t}\`) RETURN count(n) AS c`);
|
||||
nodeCounts[t] = Number(rows[0].c);
|
||||
}
|
||||
const edgeCounts: Record<string, number> = {};
|
||||
for (const ty of EDGE_TYPES_CHECKED) {
|
||||
const rows = await adapter.executeQuery(
|
||||
`MATCH ()-[r:CodeRelation {type: '${ty}'}]->() RETURN count(r) AS c`,
|
||||
);
|
||||
edgeCounts[ty] = Number(rows[0].c);
|
||||
}
|
||||
const bbRows = await adapter.executeQuery(
|
||||
`MATCH (n:BasicBlock {id: '${BB1}'}) RETURN n.text AS text`,
|
||||
);
|
||||
const fileRows = await adapter.executeQuery(
|
||||
"MATCH (n:File {id: 'File:src/a.ts'}) RETURN n.content AS content",
|
||||
);
|
||||
return {
|
||||
nodeCounts,
|
||||
edgeCounts,
|
||||
bbText: bbRows[0]?.text,
|
||||
fileContent: fileRows[0]?.content,
|
||||
ret,
|
||||
};
|
||||
};
|
||||
|
||||
const loadAndSnapshot = async (label: string, serial: boolean): Promise<Snapshot> => {
|
||||
const adapter = await import('../../src/core/lbug/lbug-adapter.js');
|
||||
const storagePath = path.join(tmpBase, label, '.gitnexus');
|
||||
const dbPath = path.join(storagePath, 'lbug');
|
||||
await fs.mkdir(dbPath, { recursive: true });
|
||||
|
||||
if (serial) process.env.GITNEXUS_SERIAL_LBUG_LOAD = '1';
|
||||
else delete process.env.GITNEXUS_SERIAL_LBUG_LOAD;
|
||||
try {
|
||||
await adapter.initLbug(dbPath);
|
||||
const ret = await adapter.loadGraphToLbug(buildFixture(), repoDir, storagePath);
|
||||
const snap = await snapshotDb(adapter, {
|
||||
insertedRels: ret.insertedRels,
|
||||
skippedRels: ret.skippedRels,
|
||||
warnings: ret.warnings,
|
||||
});
|
||||
await adapter.closeLbug();
|
||||
return snap;
|
||||
} finally {
|
||||
delete process.env.GITNEXUS_SERIAL_LBUG_LOAD;
|
||||
}
|
||||
};
|
||||
|
||||
let overlapSnap: Snapshot;
|
||||
let serialSnap: Snapshot;
|
||||
|
||||
beforeAll(async () => {
|
||||
// mkdtemp (not a predictable os.tmpdir join) — secure unique dir, and the
|
||||
// `gitnexus-lbug-` prefix is in TEST_FIXTURE_PREFIXES so the stale-sidecar
|
||||
// sweep recognizes it on Windows (lbug-config.ts).
|
||||
tmpBase = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-lbug-overlap-'));
|
||||
repoDir = path.join(tmpBase, 'repo');
|
||||
await fs.mkdir(path.join(repoDir, 'src'), { recursive: true });
|
||||
await fs.writeFile(path.join(repoDir, 'src', 'a.ts'), FILE_SRC);
|
||||
|
||||
// Default overlap path first, then the serial escape hatch into a fresh DB.
|
||||
overlapSnap = await loadAndSnapshot('overlap', false);
|
||||
serialSnap = await loadAndSnapshot('serial', true);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
try {
|
||||
const adapter = await import('../../src/core/lbug/lbug-adapter.js');
|
||||
await adapter.closeLbug();
|
||||
} catch {
|
||||
/* may already be closed */
|
||||
}
|
||||
if (tmpBase) {
|
||||
for (let attempt = 0; attempt < 5; attempt++) {
|
||||
try {
|
||||
await fs.rm(tmpBase, { recursive: true, force: true });
|
||||
return;
|
||||
} catch {
|
||||
if (attempt < 4) await new Promise((r) => setTimeout(r, 200 * (attempt + 1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
describe('node-COPY ‖ rel-emit overlap persists identical content (#2203)', () => {
|
||||
it('per-table node counts are identical between overlap and serial', () => {
|
||||
expect(overlapSnap.nodeCounts).toEqual(serialSnap.nodeCounts);
|
||||
// Sanity: the fixture actually populated every checked table.
|
||||
for (const t of NODE_TABLES_CHECKED) {
|
||||
expect(overlapSnap.nodeCounts[t], `${t} should have rows`).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
|
||||
it('per-type edge counts are identical between overlap and serial', () => {
|
||||
expect(overlapSnap.edgeCounts).toEqual(serialSnap.edgeCounts);
|
||||
for (const ty of EDGE_TYPES_CHECKED) {
|
||||
expect(overlapSnap.edgeCounts[ty], `${ty} should have edges`).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
|
||||
it('multiline content/text fields round-trip identically (byte-for-byte)', () => {
|
||||
expect(overlapSnap.bbText).toBe(serialSnap.bbText);
|
||||
expect(overlapSnap.bbText).toBe(BB_TEXT);
|
||||
expect(overlapSnap.fileContent).toBe(serialSnap.fileContent);
|
||||
expect(overlapSnap.fileContent).toBe(FILE_SRC);
|
||||
});
|
||||
|
||||
it('loadGraphToLbug accounting (insertedRels/skippedRels/warnings) is identical', () => {
|
||||
expect(overlapSnap.ret).toEqual(serialSnap.ret);
|
||||
expect(overlapSnap.ret.insertedRels).toBe(5);
|
||||
expect(overlapSnap.ret.skippedRels).toBe(0);
|
||||
expect(overlapSnap.ret.warnings).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
|
@ -135,6 +135,9 @@ describe('PROF_LBUG_LOAD persistence-path profiling (#2203 U1)', () => {
|
|||
for (const key of ['csv-emit=', 'copy-nodes=', 'copy-rels=', 'fallback=', 'total=']) {
|
||||
expect(line).toContain(key);
|
||||
}
|
||||
// Default load path is the node-COPY ‖ rel-emit overlap (#2203); the prof
|
||||
// line records which path ran. GITNEXUS_SERIAL_LBUG_LOAD is unset here.
|
||||
expect(line).toContain('mode=overlap');
|
||||
// 3 node rows (File, Function, Class), 2 valid rels emitted.
|
||||
expect(line).toContain('(3 nodes, 2 rels)');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -179,3 +179,67 @@ describe('streamed PDG manifest → disjoint-key merge guard (#2202 review #3)',
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('streamed PDG manifest → rel-pair collision guard (#2226 F3)', () => {
|
||||
// The rel-pair analogue of the node-CSV guard above, for the case Codex
|
||||
// flagged on PR #2226. The collision check was moved ahead of node COPY so the
|
||||
// serial escape hatch detects it before committing node rows; this asserts the
|
||||
// guard fires in BOTH the overlap (default) and serial paths.
|
||||
|
||||
// A leaky graph carrying a structural BasicBlock→BasicBlock EDGE but NO
|
||||
// BasicBlock nodes: RelPairRouter derives the label from the `BasicBlock:` id
|
||||
// prefix, so the structural relsByPair gets a `BasicBlock|BasicBlock` pair
|
||||
// while nodeFiles stays empty — isolating the REL-pair collision from the
|
||||
// node-CSV one. The manifest declares the same pair via PdgEmitSink.
|
||||
const buildRelCollision = async (label: string) => {
|
||||
const leakyGraph = createKnowledgeGraph();
|
||||
leakyGraph.addRelationship({
|
||||
id: 'CFG:0->1-structural',
|
||||
sourceId: BB(0),
|
||||
targetId: BB(1),
|
||||
type: 'CFG',
|
||||
confidence: 1,
|
||||
reason: 'leak',
|
||||
});
|
||||
const base = await fs.mkdtemp(path.join(os.tmpdir(), `gitnexus-lbug-relcollide-${label}-`));
|
||||
const storage = path.join(base, '.gitnexus');
|
||||
await fs.mkdir(storage, { recursive: true });
|
||||
const sink = new PdgEmitSink(createKnowledgeGraph(), path.join(storage, 'pdg-csv'));
|
||||
sink.addRelationship({
|
||||
id: 'CFG:0->1-manifest',
|
||||
sourceId: BB(0),
|
||||
targetId: BB(1),
|
||||
type: 'CFG',
|
||||
confidence: 1,
|
||||
reason: 'manifest',
|
||||
});
|
||||
const manifest = sink.finalize();
|
||||
return { leakyGraph, base, storage, manifest };
|
||||
};
|
||||
|
||||
it('throws on a rel-pair collision in the overlap (default) path', async () => {
|
||||
const adapter = await import('../../src/core/lbug/lbug-adapter.js');
|
||||
const { leakyGraph, base, storage, manifest } = await buildRelCollision('overlap');
|
||||
try {
|
||||
await expect(
|
||||
adapter.loadGraphToLbug(leakyGraph, base, storage, undefined, manifest),
|
||||
).rejects.toThrow(/collides with a structural relationship CSV for pair/);
|
||||
} finally {
|
||||
await fs.rm(base, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('throws on a rel-pair collision in the serial (GITNEXUS_SERIAL_LBUG_LOAD=1) path', async () => {
|
||||
const adapter = await import('../../src/core/lbug/lbug-adapter.js');
|
||||
const { leakyGraph, base, storage, manifest } = await buildRelCollision('serial');
|
||||
process.env.GITNEXUS_SERIAL_LBUG_LOAD = '1';
|
||||
try {
|
||||
await expect(
|
||||
adapter.loadGraphToLbug(leakyGraph, base, storage, undefined, manifest),
|
||||
).rejects.toThrow(/collides with a structural relationship CSV for pair/);
|
||||
} finally {
|
||||
delete process.env.GITNEXUS_SERIAL_LBUG_LOAD;
|
||||
await fs.rm(base, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -36,6 +36,16 @@ function extractDefinitions(matches: any[]) {
|
|||
return defs;
|
||||
}
|
||||
|
||||
function extractCapturedCallNames(matches: any[]) {
|
||||
const names: string[] = [];
|
||||
for (const match of matches) {
|
||||
if (!match.captures.some((c: any) => c.name === 'call')) continue;
|
||||
const nameCapture = match.captures.find((c: any) => c.name === 'call.name');
|
||||
if (nameCapture) names.push(nameCapture.node.text);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
describe('Tree-sitter multi-language parsing', () => {
|
||||
let parser: Parser;
|
||||
|
||||
|
|
@ -288,6 +298,48 @@ describe('Tree-sitter multi-language parsing', () => {
|
|||
expect(names).toContain('helper');
|
||||
});
|
||||
|
||||
it('treats CUDA .cu and .cuh files as C++ for definition extraction', async () => {
|
||||
expect(getLanguageFromFilename('src/kernels/force.cu')).toBe(SupportedLanguages.CPlusPlus);
|
||||
expect(getLanguageFromFilename('src/force/nep.cuh')).toBe(SupportedLanguages.CPlusPlus);
|
||||
|
||||
await loadLanguage(SupportedLanguages.CPlusPlus, 'src/kernels/force.cu');
|
||||
const code = `class Force { public: void apply(); };\nvoid launchKernel() {}`;
|
||||
const provider = getProvider(SupportedLanguages.CPlusPlus);
|
||||
const { matches } = parseAndQuery(parser, code, provider.treeSitterQueries);
|
||||
const defs = extractDefinitions(matches);
|
||||
const names = defs.map((d) => d.name);
|
||||
|
||||
expect(defs.some((d) => d.type === 'definition.class' && d.name === 'Force')).toBe(true);
|
||||
expect(names).toContain('launchKernel');
|
||||
});
|
||||
|
||||
it('characterizes CUDA syntax when routed through the C++ parser', async () => {
|
||||
await loadLanguage(SupportedLanguages.CPlusPlus, 'src/kernels/force.cu');
|
||||
const code = `
|
||||
__global__ void axpy(float *x) { x[0] = 1.0f; }
|
||||
void host() {
|
||||
axpy<<<1, 32>>>(nullptr);
|
||||
cudaDeviceSynchronize();
|
||||
}
|
||||
`;
|
||||
const provider = getProvider(SupportedLanguages.CPlusPlus);
|
||||
const { tree, matches } = parseAndQuery(parser, code, provider.treeSitterQueries);
|
||||
const defs = extractDefinitions(matches);
|
||||
const callNames = extractCapturedCallNames(matches);
|
||||
const ordinaryCalls = extractCapturedCallNames(
|
||||
parseAndQuery(
|
||||
parser,
|
||||
'void host() { cudaDeviceSynchronize(); }',
|
||||
provider.treeSitterQueries,
|
||||
).matches,
|
||||
);
|
||||
|
||||
expect(tree.rootNode.hasError).toBe(true);
|
||||
expect(defs.some((d) => d.name === 'axpy')).toBe(true);
|
||||
expect(ordinaryCalls).toContain('cudaDeviceSynchronize');
|
||||
expect(callNames).not.toContain('axpy');
|
||||
});
|
||||
|
||||
it('captures C++ typedef anonymous structs, enums, and enumerators', async () => {
|
||||
await loadLanguage(SupportedLanguages.CPlusPlus);
|
||||
const code = `
|
||||
|
|
|
|||
|
|
@ -67,6 +67,16 @@ describe('IncludeExtractor', () => {
|
|||
expect(providers[0].contractId).toBe('include::utils/helper.hpp');
|
||||
});
|
||||
|
||||
it('registers .cuh CUDA headers as providers', async () => {
|
||||
writeFile('src/force/nep.cuh', '#pragma once\nclass NEP {};');
|
||||
|
||||
const contracts = await extractor.extract(null, tmpDir, makeRepo(tmpDir));
|
||||
const providers = contracts.filter((c) => c.role === 'provider');
|
||||
|
||||
expect(providers).toHaveLength(1);
|
||||
expect(providers[0].contractId).toBe('include::src/force/nep.cuh');
|
||||
});
|
||||
|
||||
it('does not register .cpp files as providers', async () => {
|
||||
writeFile('src/main.cpp', 'int main() { return 0; }');
|
||||
writeFile('src/utils.h', '#pragma once');
|
||||
|
|
@ -237,6 +247,22 @@ int main() { return 0; }`,
|
|||
expect(consumers).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('scans .cu files for includes and resolves local .cuh headers', async () => {
|
||||
writeFile('include/kernel.cuh', '#pragma once\nvoid launchKernel();');
|
||||
writeFile(
|
||||
'src/main.cu',
|
||||
`#include "include/kernel.cuh"
|
||||
#include "external/gpu_runtime.cuh"
|
||||
void launch() { launchKernel(); }`,
|
||||
);
|
||||
|
||||
const contracts = await extractor.extract(null, tmpDir, makeRepo(tmpDir));
|
||||
const consumers = contracts.filter((c) => c.role === 'consumer');
|
||||
|
||||
expect(consumers).toHaveLength(1);
|
||||
expect(consumers[0].contractId).toBe('include::external/gpu_runtime.cuh');
|
||||
});
|
||||
|
||||
it('resolves locally when include omits extension and a matching .h exists', async () => {
|
||||
writeFile('foo/bar.h', '#pragma once');
|
||||
writeFile('src/main.cpp', '#include "foo/bar"\nint main(){return 0;}');
|
||||
|
|
|
|||
|
|
@ -68,9 +68,12 @@ describe('getLanguageFromFilename', () => {
|
|||
});
|
||||
|
||||
describe('C++', () => {
|
||||
it.each(['.cpp', '.cc', '.cxx', '.h', '.hpp', '.hxx', '.hh'])('detects %s files', (ext) => {
|
||||
expect(getLanguageFromFilename(`file${ext}`)).toBe(SupportedLanguages.CPlusPlus);
|
||||
});
|
||||
it.each(['.cpp', '.cc', '.cxx', '.h', '.hpp', '.hxx', '.hh', '.cu', '.cuh'])(
|
||||
'detects %s files',
|
||||
(ext) => {
|
||||
expect(getLanguageFromFilename(`file${ext}`)).toBe(SupportedLanguages.CPlusPlus);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('C#', () => {
|
||||
|
|
@ -172,6 +175,11 @@ describe('getProviderForFile', () => {
|
|||
SupportedLanguages.PHP,
|
||||
);
|
||||
});
|
||||
|
||||
it('routes CUDA C++ source and header files to the C++ provider', () => {
|
||||
expect(getProviderForFile('src/kernels/integrate.cu')?.id).toBe(SupportedLanguages.CPlusPlus);
|
||||
expect(getProviderForFile('src/force/nep.cuh')?.id).toBe(SupportedLanguages.CPlusPlus);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isBuiltInOrNoise', () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue