@grafema/util 0.3.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/dist/api/GraphAPI.d.ts +87 -0
- package/dist/api/GraphAPI.d.ts.map +1 -0
- package/dist/api/GraphAPI.js +212 -0
- package/dist/api/GraphAPI.js.map +1 -0
- package/dist/api/GuaranteeAPI.d.ts +147 -0
- package/dist/api/GuaranteeAPI.d.ts.map +1 -0
- package/dist/api/GuaranteeAPI.js +290 -0
- package/dist/api/GuaranteeAPI.js.map +1 -0
- package/dist/config/ConfigLoader.d.ts +214 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -0
- package/dist/config/ConfigLoader.js +441 -0
- package/dist/config/ConfigLoader.js.map +1 -0
- package/dist/config/index.d.ts +6 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +5 -0
- package/dist/config/index.js.map +1 -0
- package/dist/core/CoverageAnalyzer.d.ts +65 -0
- package/dist/core/CoverageAnalyzer.d.ts.map +1 -0
- package/dist/core/CoverageAnalyzer.js +199 -0
- package/dist/core/CoverageAnalyzer.js.map +1 -0
- package/dist/core/FileExplainer.d.ts +101 -0
- package/dist/core/FileExplainer.d.ts.map +1 -0
- package/dist/core/FileExplainer.js +140 -0
- package/dist/core/FileExplainer.js.map +1 -0
- package/dist/core/FileOverview.d.ts +124 -0
- package/dist/core/FileOverview.d.ts.map +1 -0
- package/dist/core/FileOverview.js +279 -0
- package/dist/core/FileOverview.js.map +1 -0
- package/dist/core/GrafemaUri.d.ts +66 -0
- package/dist/core/GrafemaUri.d.ts.map +1 -0
- package/dist/core/GrafemaUri.js +191 -0
- package/dist/core/GrafemaUri.js.map +1 -0
- package/dist/core/GraphBackend.d.ts +158 -0
- package/dist/core/GraphBackend.d.ts.map +1 -0
- package/dist/core/GraphBackend.js +85 -0
- package/dist/core/GraphBackend.js.map +1 -0
- package/dist/core/GraphFreshnessChecker.d.ts +33 -0
- package/dist/core/GraphFreshnessChecker.d.ts.map +1 -0
- package/dist/core/GraphFreshnessChecker.js +104 -0
- package/dist/core/GraphFreshnessChecker.js.map +1 -0
- package/dist/core/GuaranteeManager.d.ts +254 -0
- package/dist/core/GuaranteeManager.d.ts.map +1 -0
- package/dist/core/GuaranteeManager.js +447 -0
- package/dist/core/GuaranteeManager.js.map +1 -0
- package/dist/core/HashUtils.d.ts +24 -0
- package/dist/core/HashUtils.d.ts.map +1 -0
- package/dist/core/HashUtils.js +46 -0
- package/dist/core/HashUtils.js.map +1 -0
- package/dist/core/IncrementalReanalyzer.d.ts +33 -0
- package/dist/core/IncrementalReanalyzer.d.ts.map +1 -0
- package/dist/core/IncrementalReanalyzer.js +67 -0
- package/dist/core/IncrementalReanalyzer.js.map +1 -0
- package/dist/core/ResourceRegistry.d.ts +17 -0
- package/dist/core/ResourceRegistry.d.ts.map +1 -0
- package/dist/core/ResourceRegistry.js +32 -0
- package/dist/core/ResourceRegistry.js.map +1 -0
- package/dist/core/SemanticId.d.ts +159 -0
- package/dist/core/SemanticId.d.ts.map +1 -0
- package/dist/core/SemanticId.js +291 -0
- package/dist/core/SemanticId.js.map +1 -0
- package/dist/core/VersionManager.d.ts +166 -0
- package/dist/core/VersionManager.d.ts.map +1 -0
- package/dist/core/VersionManager.js +239 -0
- package/dist/core/VersionManager.js.map +1 -0
- package/dist/core/brandNodeInternal.d.ts +14 -0
- package/dist/core/brandNodeInternal.d.ts.map +1 -0
- package/dist/core/brandNodeInternal.js +4 -0
- package/dist/core/brandNodeInternal.js.map +1 -0
- package/dist/core/nodes/GuaranteeNode.d.ts +76 -0
- package/dist/core/nodes/GuaranteeNode.d.ts.map +1 -0
- package/dist/core/nodes/GuaranteeNode.js +118 -0
- package/dist/core/nodes/GuaranteeNode.js.map +1 -0
- package/dist/core/nodes/IssueNode.d.ts +73 -0
- package/dist/core/nodes/IssueNode.d.ts.map +1 -0
- package/dist/core/nodes/IssueNode.js +130 -0
- package/dist/core/nodes/IssueNode.js.map +1 -0
- package/dist/core/nodes/NodeKind.d.ts +104 -0
- package/dist/core/nodes/NodeKind.d.ts.map +1 -0
- package/dist/core/nodes/NodeKind.js +166 -0
- package/dist/core/nodes/NodeKind.js.map +1 -0
- package/dist/diagnostics/DiagnosticCollector.d.ts +103 -0
- package/dist/diagnostics/DiagnosticCollector.d.ts.map +1 -0
- package/dist/diagnostics/DiagnosticCollector.js +133 -0
- package/dist/diagnostics/DiagnosticCollector.js.map +1 -0
- package/dist/diagnostics/DiagnosticReporter.d.ts +122 -0
- package/dist/diagnostics/DiagnosticReporter.d.ts.map +1 -0
- package/dist/diagnostics/DiagnosticReporter.js +300 -0
- package/dist/diagnostics/DiagnosticReporter.js.map +1 -0
- package/dist/diagnostics/DiagnosticWriter.d.ts +31 -0
- package/dist/diagnostics/DiagnosticWriter.d.ts.map +1 -0
- package/dist/diagnostics/DiagnosticWriter.js +44 -0
- package/dist/diagnostics/DiagnosticWriter.js.map +1 -0
- package/dist/diagnostics/categories.d.ts +57 -0
- package/dist/diagnostics/categories.d.ts.map +1 -0
- package/dist/diagnostics/categories.js +71 -0
- package/dist/diagnostics/categories.js.map +1 -0
- package/dist/diagnostics/index.d.ts +17 -0
- package/dist/diagnostics/index.d.ts.map +1 -0
- package/dist/diagnostics/index.js +15 -0
- package/dist/diagnostics/index.js.map +1 -0
- package/dist/errors/GrafemaError.d.ts +200 -0
- package/dist/errors/GrafemaError.d.ts.map +1 -0
- package/dist/errors/GrafemaError.js +209 -0
- package/dist/errors/GrafemaError.js.map +1 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/instructions/index.d.ts +8 -0
- package/dist/instructions/index.d.ts.map +1 -0
- package/dist/instructions/index.js +20 -0
- package/dist/instructions/index.js.map +1 -0
- package/dist/instructions/onboarding.md +133 -0
- package/dist/knowledge/KnowledgeBase.d.ts +113 -0
- package/dist/knowledge/KnowledgeBase.d.ts.map +1 -0
- package/dist/knowledge/KnowledgeBase.js +420 -0
- package/dist/knowledge/KnowledgeBase.js.map +1 -0
- package/dist/knowledge/SemanticAddressResolver.d.ts +59 -0
- package/dist/knowledge/SemanticAddressResolver.d.ts.map +1 -0
- package/dist/knowledge/SemanticAddressResolver.js +160 -0
- package/dist/knowledge/SemanticAddressResolver.js.map +1 -0
- package/dist/knowledge/git-ingest.d.ts +58 -0
- package/dist/knowledge/git-ingest.d.ts.map +1 -0
- package/dist/knowledge/git-ingest.js +301 -0
- package/dist/knowledge/git-ingest.js.map +1 -0
- package/dist/knowledge/git-queries.d.ts +86 -0
- package/dist/knowledge/git-queries.d.ts.map +1 -0
- package/dist/knowledge/git-queries.js +177 -0
- package/dist/knowledge/git-queries.js.map +1 -0
- package/dist/knowledge/index.d.ts +14 -0
- package/dist/knowledge/index.d.ts.map +1 -0
- package/dist/knowledge/index.js +10 -0
- package/dist/knowledge/index.js.map +1 -0
- package/dist/knowledge/parser.d.ts +39 -0
- package/dist/knowledge/parser.d.ts.map +1 -0
- package/dist/knowledge/parser.js +292 -0
- package/dist/knowledge/parser.js.map +1 -0
- package/dist/knowledge/types.d.ts +133 -0
- package/dist/knowledge/types.d.ts.map +1 -0
- package/dist/knowledge/types.js +8 -0
- package/dist/knowledge/types.js.map +1 -0
- package/dist/logging/Logger.d.ts +98 -0
- package/dist/logging/Logger.d.ts.map +1 -0
- package/dist/logging/Logger.js +274 -0
- package/dist/logging/Logger.js.map +1 -0
- package/dist/notation/archetypes.d.ts +36 -0
- package/dist/notation/archetypes.d.ts.map +1 -0
- package/dist/notation/archetypes.js +173 -0
- package/dist/notation/archetypes.js.map +1 -0
- package/dist/notation/fold.d.ts +25 -0
- package/dist/notation/fold.d.ts.map +1 -0
- package/dist/notation/fold.js +598 -0
- package/dist/notation/fold.js.map +1 -0
- package/dist/notation/index.d.ts +18 -0
- package/dist/notation/index.d.ts.map +1 -0
- package/dist/notation/index.js +16 -0
- package/dist/notation/index.js.map +1 -0
- package/dist/notation/lodExtractor.d.ts +32 -0
- package/dist/notation/lodExtractor.d.ts.map +1 -0
- package/dist/notation/lodExtractor.js +149 -0
- package/dist/notation/lodExtractor.js.map +1 -0
- package/dist/notation/nameShortener.d.ts +22 -0
- package/dist/notation/nameShortener.d.ts.map +1 -0
- package/dist/notation/nameShortener.js +24 -0
- package/dist/notation/nameShortener.js.map +1 -0
- package/dist/notation/perspectives.d.ts +11 -0
- package/dist/notation/perspectives.d.ts.map +1 -0
- package/dist/notation/perspectives.js +16 -0
- package/dist/notation/perspectives.js.map +1 -0
- package/dist/notation/renderer.d.ts +31 -0
- package/dist/notation/renderer.d.ts.map +1 -0
- package/dist/notation/renderer.js +315 -0
- package/dist/notation/renderer.js.map +1 -0
- package/dist/notation/traceRenderer.d.ts +39 -0
- package/dist/notation/traceRenderer.d.ts.map +1 -0
- package/dist/notation/traceRenderer.js +358 -0
- package/dist/notation/traceRenderer.js.map +1 -0
- package/dist/notation/types.d.ts +66 -0
- package/dist/notation/types.d.ts.map +1 -0
- package/dist/notation/types.js +10 -0
- package/dist/notation/types.js.map +1 -0
- package/dist/queries/NodeContext.d.ts +81 -0
- package/dist/queries/NodeContext.d.ts.map +1 -0
- package/dist/queries/NodeContext.js +196 -0
- package/dist/queries/NodeContext.js.map +1 -0
- package/dist/queries/findCallsInFunction.d.ts +62 -0
- package/dist/queries/findCallsInFunction.d.ts.map +1 -0
- package/dist/queries/findCallsInFunction.js +169 -0
- package/dist/queries/findCallsInFunction.js.map +1 -0
- package/dist/queries/findContainingFunction.d.ts +57 -0
- package/dist/queries/findContainingFunction.d.ts.map +1 -0
- package/dist/queries/findContainingFunction.js +91 -0
- package/dist/queries/findContainingFunction.js.map +1 -0
- package/dist/queries/index.d.ts +18 -0
- package/dist/queries/index.d.ts.map +1 -0
- package/dist/queries/index.js +14 -0
- package/dist/queries/index.js.map +1 -0
- package/dist/queries/traceDataflow.d.ts +65 -0
- package/dist/queries/traceDataflow.d.ts.map +1 -0
- package/dist/queries/traceDataflow.js +754 -0
- package/dist/queries/traceDataflow.js.map +1 -0
- package/dist/queries/traceValues.d.ts +70 -0
- package/dist/queries/traceValues.d.ts.map +1 -0
- package/dist/queries/traceValues.js +373 -0
- package/dist/queries/traceValues.js.map +1 -0
- package/dist/queries/types.d.ts +166 -0
- package/dist/queries/types.d.ts.map +1 -0
- package/dist/queries/types.js +10 -0
- package/dist/queries/types.js.map +1 -0
- package/dist/schema/GraphSchemaExtractor.d.ts +53 -0
- package/dist/schema/GraphSchemaExtractor.d.ts.map +1 -0
- package/dist/schema/GraphSchemaExtractor.js +125 -0
- package/dist/schema/GraphSchemaExtractor.js.map +1 -0
- package/dist/schema/InterfaceSchemaExtractor.d.ts +73 -0
- package/dist/schema/InterfaceSchemaExtractor.d.ts.map +1 -0
- package/dist/schema/InterfaceSchemaExtractor.js +113 -0
- package/dist/schema/InterfaceSchemaExtractor.js.map +1 -0
- package/dist/schema/index.d.ts +5 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +3 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/storage/backends/RFDBServerBackend.d.ts +356 -0
- package/dist/storage/backends/RFDBServerBackend.d.ts.map +1 -0
- package/dist/storage/backends/RFDBServerBackend.js +748 -0
- package/dist/storage/backends/RFDBServerBackend.js.map +1 -0
- package/dist/storage/backends/typeValidation.d.ts +47 -0
- package/dist/storage/backends/typeValidation.d.ts.map +1 -0
- package/dist/storage/backends/typeValidation.js +141 -0
- package/dist/storage/backends/typeValidation.js.map +1 -0
- package/dist/utils/findRfdbBinary.d.ts +67 -0
- package/dist/utils/findRfdbBinary.d.ts.map +1 -0
- package/dist/utils/findRfdbBinary.js +261 -0
- package/dist/utils/findRfdbBinary.js.map +1 -0
- package/dist/utils/lazyDownload.d.ts +43 -0
- package/dist/utils/lazyDownload.d.ts.map +1 -0
- package/dist/utils/lazyDownload.js +175 -0
- package/dist/utils/lazyDownload.js.map +1 -0
- package/dist/utils/moduleResolution.d.ts +134 -0
- package/dist/utils/moduleResolution.d.ts.map +1 -0
- package/dist/utils/moduleResolution.js +189 -0
- package/dist/utils/moduleResolution.js.map +1 -0
- package/dist/utils/resolveNodeFile.d.ts +13 -0
- package/dist/utils/resolveNodeFile.d.ts.map +1 -0
- package/dist/utils/resolveNodeFile.js +18 -0
- package/dist/utils/resolveNodeFile.js.map +1 -0
- package/dist/utils/startRfdbServer.d.ts +63 -0
- package/dist/utils/startRfdbServer.d.ts.map +1 -0
- package/dist/utils/startRfdbServer.js +142 -0
- package/dist/utils/startRfdbServer.js.map +1 -0
- package/dist/validation/PathValidator.d.ts +80 -0
- package/dist/validation/PathValidator.d.ts.map +1 -0
- package/dist/validation/PathValidator.js +252 -0
- package/dist/validation/PathValidator.js.map +1 -0
- package/dist/version.d.ts +11 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +26 -0
- package/dist/version.js.map +1 -0
- package/package.json +50 -0
- package/src/api/GraphAPI.ts +307 -0
- package/src/api/GuaranteeAPI.ts +402 -0
- package/src/config/ConfigLoader.ts +653 -0
- package/src/config/index.ts +13 -0
- package/src/core/CoverageAnalyzer.ts +243 -0
- package/src/core/FileExplainer.ts +179 -0
- package/src/core/FileOverview.ts +397 -0
- package/src/core/GrafemaUri.ts +216 -0
- package/src/core/GraphBackend.ts +266 -0
- package/src/core/GraphFreshnessChecker.ts +145 -0
- package/src/core/GuaranteeManager.ts +684 -0
- package/src/core/HashUtils.ts +48 -0
- package/src/core/IncrementalReanalyzer.ts +106 -0
- package/src/core/ResourceRegistry.ts +39 -0
- package/src/core/SemanticId.ts +423 -0
- package/src/core/VersionManager.ts +405 -0
- package/src/core/brandNodeInternal.ts +16 -0
- package/src/core/nodes/GuaranteeNode.ts +162 -0
- package/src/core/nodes/IssueNode.ts +177 -0
- package/src/core/nodes/NodeKind.ts +192 -0
- package/src/diagnostics/DiagnosticCollector.ts +170 -0
- package/src/diagnostics/DiagnosticReporter.ts +395 -0
- package/src/diagnostics/DiagnosticWriter.ts +50 -0
- package/src/diagnostics/categories.ts +104 -0
- package/src/diagnostics/index.ts +30 -0
- package/src/errors/GrafemaError.ts +297 -0
- package/src/index.ts +261 -0
- package/src/instructions/index.ts +21 -0
- package/src/instructions/onboarding.md +133 -0
- package/src/knowledge/KnowledgeBase.ts +486 -0
- package/src/knowledge/SemanticAddressResolver.ts +191 -0
- package/src/knowledge/git-ingest.ts +402 -0
- package/src/knowledge/git-queries.ts +269 -0
- package/src/knowledge/index.ts +29 -0
- package/src/knowledge/parser.ts +294 -0
- package/src/knowledge/types.ts +146 -0
- package/src/logging/Logger.ts +303 -0
- package/src/notation/archetypes.ts +189 -0
- package/src/notation/fold.ts +696 -0
- package/src/notation/index.ts +27 -0
- package/src/notation/lodExtractor.ts +177 -0
- package/src/notation/nameShortener.ts +24 -0
- package/src/notation/perspectives.ts +18 -0
- package/src/notation/renderer.ts +394 -0
- package/src/notation/traceRenderer.ts +458 -0
- package/src/notation/types.ts +79 -0
- package/src/queries/NodeContext.ts +280 -0
- package/src/queries/findCallsInFunction.ts +249 -0
- package/src/queries/findContainingFunction.ts +124 -0
- package/src/queries/index.ts +44 -0
- package/src/queries/traceDataflow.ts +838 -0
- package/src/queries/traceValues.ts +531 -0
- package/src/queries/types.ts +191 -0
- package/src/schema/GraphSchemaExtractor.ts +177 -0
- package/src/schema/InterfaceSchemaExtractor.ts +173 -0
- package/src/schema/index.ts +5 -0
- package/src/storage/backends/RFDBServerBackend.ts +895 -0
- package/src/storage/backends/typeValidation.ts +154 -0
- package/src/utils/findRfdbBinary.ts +288 -0
- package/src/utils/lazyDownload.ts +206 -0
- package/src/utils/moduleResolution.ts +271 -0
- package/src/utils/resolveNodeFile.ts +18 -0
- package/src/utils/startRfdbServer.ts +197 -0
- package/src/validation/PathValidator.ts +334 -0
- package/src/version.ts +28 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type Validation Utilities
|
|
3
|
+
*
|
|
4
|
+
* Levenshtein distance and typo detection for node/edge types.
|
|
5
|
+
* Extracted from ReginaFlowBackend for use in multiple contexts.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Known node types - source of truth for validation
|
|
9
|
+
// Initial set of types, dynamically expanded as new valid types are encountered
|
|
10
|
+
const KNOWN_NODE_TYPES = new Set<string>([
|
|
11
|
+
// Base types
|
|
12
|
+
'FUNCTION', 'CLASS', 'METHOD', 'VARIABLE', 'PARAMETER', 'CONSTANT', 'LITERAL',
|
|
13
|
+
'MODULE', 'IMPORT', 'EXPORT', 'CALL', 'PROJECT', 'SERVICE', 'FILE', 'SCOPE',
|
|
14
|
+
'EXTERNAL', 'EXTERNAL_MODULE', 'SIDE_EFFECT',
|
|
15
|
+
// Namespaced types - HTTP/Express
|
|
16
|
+
'http:route', 'http:request',
|
|
17
|
+
'express:router', 'express:middleware', 'express:mount',
|
|
18
|
+
// Namespaced types - Socket.IO
|
|
19
|
+
'socketio:emit', 'socketio:on', 'socketio:namespace', 'socketio:room',
|
|
20
|
+
// Namespaced types - Database
|
|
21
|
+
'db:query', 'db:connection', 'db:table',
|
|
22
|
+
// Namespaced types - File System
|
|
23
|
+
'fs:read', 'fs:write', 'fs:operation',
|
|
24
|
+
// Namespaced types - Network/IO
|
|
25
|
+
'net:request', 'net:stdio',
|
|
26
|
+
// Namespaced types - Events
|
|
27
|
+
'event:listener', 'event:emit',
|
|
28
|
+
// Service layer types (legacy, to be refactored)
|
|
29
|
+
'SERVICE_CLASS', 'SERVICE_INSTANCE', 'SERVICE_REGISTRATION', 'SERVICE_USAGE',
|
|
30
|
+
// Guarantees/Invariants
|
|
31
|
+
'GUARANTEE',
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
// Store initial types for reset
|
|
35
|
+
const INITIAL_NODE_TYPES = [...KNOWN_NODE_TYPES];
|
|
36
|
+
|
|
37
|
+
// Known edge types - source of truth for validation
|
|
38
|
+
const KNOWN_EDGE_TYPES = new Set<string>([
|
|
39
|
+
// Base edge types
|
|
40
|
+
'UNKNOWN', 'CONTAINS', 'DEPENDS_ON', 'CALLS', 'EXTENDS', 'IMPLEMENTS',
|
|
41
|
+
'USES', 'DEFINES', 'IMPORTS', 'EXPORTS', 'ROUTES_TO', 'HAS_SCOPE',
|
|
42
|
+
'CAPTURES', 'MODIFIES', 'DECLARES', 'WRITES_TO', 'INSTANCE_OF',
|
|
43
|
+
'HAS_CALLBACK', 'IMPORTS_FROM', 'HANDLED_BY', 'MAKES_REQUEST',
|
|
44
|
+
'PASSES_ARGUMENT', 'ASSIGNED_FROM', 'MOUNTS', 'EXPOSES',
|
|
45
|
+
'INTERACTS_WITH', 'CALLS_API', 'LISTENS_TO', 'JOINS_ROOM', 'EMITS_EVENT',
|
|
46
|
+
'RETURNS', 'RECEIVES_ARGUMENT', 'READS_FROM', 'THROWS', 'REGISTERS_VIEW',
|
|
47
|
+
'GOVERNS', 'VIOLATES', 'HAS_PARAMETER', 'DERIVES_FROM',
|
|
48
|
+
'RESOLVES_TO', // Promise resolve() data flow
|
|
49
|
+
'YIELDS', // Generator yield data flow (REG-270)
|
|
50
|
+
'DELEGATES_TO', // Generator yield* delegation (REG-270)
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
// Store initial edge types for reset
|
|
54
|
+
const INITIAL_EDGE_TYPES = [...KNOWN_EDGE_TYPES];
|
|
55
|
+
|
|
56
|
+
export interface TypoCheckResult {
|
|
57
|
+
isTooSimilar: boolean;
|
|
58
|
+
similarTo: string | null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Levenshtein distance for typo detection
|
|
63
|
+
*/
|
|
64
|
+
export function levenshtein(a: string, b: string): number {
|
|
65
|
+
if (a.length === 0) return b.length;
|
|
66
|
+
if (b.length === 0) return a.length;
|
|
67
|
+
const matrix: number[][] = [];
|
|
68
|
+
for (let i = 0; i <= b.length; i++) matrix[i] = [i];
|
|
69
|
+
for (let j = 0; j <= a.length; j++) matrix[0][j] = j;
|
|
70
|
+
for (let i = 1; i <= b.length; i++) {
|
|
71
|
+
for (let j = 1; j <= a.length; j++) {
|
|
72
|
+
matrix[i][j] = b[i - 1] === a[j - 1]
|
|
73
|
+
? matrix[i - 1][j - 1]
|
|
74
|
+
: Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return matrix[b.length][a.length];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check if a type is too similar to any existing known type (possible typo)
|
|
82
|
+
*/
|
|
83
|
+
export function checkTypoAgainstKnownTypes(newType: string): TypoCheckResult {
|
|
84
|
+
const newTypeLower = newType.toLowerCase();
|
|
85
|
+
for (const known of KNOWN_NODE_TYPES) {
|
|
86
|
+
const dist = levenshtein(newTypeLower, known.toLowerCase());
|
|
87
|
+
if (dist > 0 && dist <= 2) {
|
|
88
|
+
return { isTooSimilar: true, similarTo: known };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return { isTooSimilar: false, similarTo: null };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Reset KNOWN_NODE_TYPES to initial state (for testing)
|
|
96
|
+
*/
|
|
97
|
+
export function resetKnownNodeTypes(): void {
|
|
98
|
+
KNOWN_NODE_TYPES.clear();
|
|
99
|
+
for (const t of INITIAL_NODE_TYPES) {
|
|
100
|
+
KNOWN_NODE_TYPES.add(t);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get current known node types (for testing/debugging)
|
|
106
|
+
*/
|
|
107
|
+
export function getKnownNodeTypes(): Set<string> {
|
|
108
|
+
return new Set(KNOWN_NODE_TYPES);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Check if an edge type is too similar to any existing known edge type (possible typo)
|
|
113
|
+
*/
|
|
114
|
+
export function checkTypoAgainstKnownEdgeTypes(newType: string): TypoCheckResult {
|
|
115
|
+
const newTypeLower = newType.toLowerCase();
|
|
116
|
+
for (const known of KNOWN_EDGE_TYPES) {
|
|
117
|
+
const dist = levenshtein(newTypeLower, known.toLowerCase());
|
|
118
|
+
if (dist > 0 && dist <= 2) {
|
|
119
|
+
return { isTooSimilar: true, similarTo: known };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return { isTooSimilar: false, similarTo: null };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Reset KNOWN_EDGE_TYPES to initial state (for testing)
|
|
127
|
+
*/
|
|
128
|
+
export function resetKnownEdgeTypes(): void {
|
|
129
|
+
KNOWN_EDGE_TYPES.clear();
|
|
130
|
+
for (const t of INITIAL_EDGE_TYPES) {
|
|
131
|
+
KNOWN_EDGE_TYPES.add(t);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get current known edge types (for testing/debugging)
|
|
137
|
+
*/
|
|
138
|
+
export function getKnownEdgeTypes(): Set<string> {
|
|
139
|
+
return new Set(KNOWN_EDGE_TYPES);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Add a new node type to the known types set
|
|
144
|
+
*/
|
|
145
|
+
export function addKnownNodeType(nodeType: string): void {
|
|
146
|
+
KNOWN_NODE_TYPES.add(nodeType);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Add a new edge type to the known types set
|
|
151
|
+
*/
|
|
152
|
+
export function addKnownEdgeType(edgeType: string): void {
|
|
153
|
+
KNOWN_EDGE_TYPES.add(edgeType);
|
|
154
|
+
}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified binary lookup for Grafema native binaries.
|
|
3
|
+
*
|
|
4
|
+
* Used by CLI, MCP server, and the unified grafema package.
|
|
5
|
+
* Finds rfdb-server, grafema-orchestrator, or any binary by name.
|
|
6
|
+
*
|
|
7
|
+
* Search order:
|
|
8
|
+
* 1. Explicit path (from config or flag)
|
|
9
|
+
* 2. Environment variable (GRAFEMA_RFDB_SERVER / GRAFEMA_ORCHESTRATOR)
|
|
10
|
+
* 3. Platform package (@grafema/grafema-{os}-{arch})
|
|
11
|
+
* 4. Monorepo target/release (development)
|
|
12
|
+
* 5. Monorepo target/debug (development)
|
|
13
|
+
* 6. System PATH lookup
|
|
14
|
+
* 7. ~/.grafema/bin/ (lazy-downloaded analyzers)
|
|
15
|
+
* 8. ~/.local/bin/ (user-installed)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { existsSync } from 'fs';
|
|
19
|
+
import { join, delimiter, dirname, resolve } from 'path';
|
|
20
|
+
import { createRequire } from 'module';
|
|
21
|
+
import { fileURLToPath } from 'url';
|
|
22
|
+
|
|
23
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
24
|
+
const __dirname = dirname(__filename);
|
|
25
|
+
|
|
26
|
+
export type BinaryName = 'rfdb-server' | 'grafema-orchestrator';
|
|
27
|
+
|
|
28
|
+
export interface FindBinaryOptions {
|
|
29
|
+
/** Explicit path to binary (highest priority) */
|
|
30
|
+
explicitPath?: string;
|
|
31
|
+
/** Base directory for monorepo search (defaults to auto-detect) */
|
|
32
|
+
monorepoRoot?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface BinaryConfig {
|
|
36
|
+
envVar: string;
|
|
37
|
+
monorepoPackage: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const BINARY_CONFIG: Record<BinaryName, BinaryConfig> = {
|
|
41
|
+
'rfdb-server': {
|
|
42
|
+
envVar: 'GRAFEMA_RFDB_SERVER',
|
|
43
|
+
monorepoPackage: 'rfdb-server',
|
|
44
|
+
},
|
|
45
|
+
'grafema-orchestrator': {
|
|
46
|
+
envVar: 'GRAFEMA_ORCHESTRATOR',
|
|
47
|
+
monorepoPackage: 'grafema-orchestrator',
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get platform directory name for prebuilt binaries.
|
|
53
|
+
* E.g., "darwin-arm64", "linux-x64"
|
|
54
|
+
*/
|
|
55
|
+
export function getPlatformDir(): string {
|
|
56
|
+
const platform = process.platform;
|
|
57
|
+
const arch = process.arch;
|
|
58
|
+
|
|
59
|
+
if (platform === 'darwin') {
|
|
60
|
+
return arch === 'arm64' ? 'darwin-arm64' : 'darwin-x64';
|
|
61
|
+
} else if (platform === 'linux') {
|
|
62
|
+
return arch === 'arm64' ? 'linux-arm64' : 'linux-x64';
|
|
63
|
+
}
|
|
64
|
+
return `${platform}-${arch}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get platform package name for the current OS/arch.
|
|
69
|
+
* E.g., "@grafema/grafema-darwin-arm64"
|
|
70
|
+
*/
|
|
71
|
+
export function getPlatformPackageName(): string {
|
|
72
|
+
const platform = process.platform === 'darwin' ? 'darwin' : 'linux';
|
|
73
|
+
const arch = process.arch === 'arm64' ? 'arm64' : 'x64';
|
|
74
|
+
return `@grafema/grafema-${platform}-${arch}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Try to load the platform package and get a binary path from it.
|
|
79
|
+
*/
|
|
80
|
+
function tryPlatformPackage(binaryName: BinaryName): string | null {
|
|
81
|
+
try {
|
|
82
|
+
const require = createRequire(import.meta.url);
|
|
83
|
+
const pkgName = getPlatformPackageName();
|
|
84
|
+
const pkg = require(pkgName);
|
|
85
|
+
|
|
86
|
+
if (binaryName === 'rfdb-server' && pkg.rfdbServerPath) {
|
|
87
|
+
const p = pkg.rfdbServerPath;
|
|
88
|
+
if (existsSync(p)) return p;
|
|
89
|
+
}
|
|
90
|
+
if (binaryName === 'grafema-orchestrator' && pkg.orchestratorPath) {
|
|
91
|
+
const p = pkg.orchestratorPath;
|
|
92
|
+
if (existsSync(p)) return p;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Fallback: try binDir + binaryName
|
|
96
|
+
if (pkg.binDir) {
|
|
97
|
+
const p = join(pkg.binDir, binaryName);
|
|
98
|
+
if (existsSync(p)) return p;
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
// Platform package not installed
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Find monorepo root by looking for characteristic files.
|
|
108
|
+
*/
|
|
109
|
+
function findMonorepoRoot(startDir?: string): string | null {
|
|
110
|
+
// Walk up from provided dir or this file's location
|
|
111
|
+
let dir = startDir || join(__dirname, '..', '..', '..', '..');
|
|
112
|
+
for (let i = 0; i < 8; i++) {
|
|
113
|
+
const hasPackagesDir = existsSync(join(dir, 'packages', 'util'));
|
|
114
|
+
const hasPnpmWorkspace = existsSync(join(dir, 'pnpm-workspace.yaml'));
|
|
115
|
+
if (hasPackagesDir && hasPnpmWorkspace) {
|
|
116
|
+
return dir;
|
|
117
|
+
}
|
|
118
|
+
const parent = dirname(dir);
|
|
119
|
+
if (parent === dir) break;
|
|
120
|
+
dir = parent;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Try GRAFEMA_ROOT env var
|
|
124
|
+
const envRoot = process.env.GRAFEMA_ROOT;
|
|
125
|
+
if (envRoot && existsSync(join(envRoot, 'packages', 'util'))) {
|
|
126
|
+
return envRoot;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Find a Grafema native binary.
|
|
134
|
+
*
|
|
135
|
+
* @param binaryName - Which binary to find
|
|
136
|
+
* @param options - Search options
|
|
137
|
+
* @returns Absolute path to the binary, or null if not found
|
|
138
|
+
*/
|
|
139
|
+
export function findBinary(binaryName: BinaryName, options: FindBinaryOptions = {}): string | null {
|
|
140
|
+
const config = BINARY_CONFIG[binaryName];
|
|
141
|
+
|
|
142
|
+
// 1. Explicit path (from config or flag) — no fallback
|
|
143
|
+
if (options.explicitPath) {
|
|
144
|
+
const resolved = resolve(options.explicitPath);
|
|
145
|
+
return existsSync(resolved) ? resolved : null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 2. Environment variable
|
|
149
|
+
const envPath = process.env[config.envVar];
|
|
150
|
+
if (envPath && existsSync(envPath)) {
|
|
151
|
+
return envPath;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 3. Platform package (@grafema/grafema-{os}-{arch})
|
|
155
|
+
const platformPath = tryPlatformPackage(binaryName);
|
|
156
|
+
if (platformPath) return platformPath;
|
|
157
|
+
|
|
158
|
+
// 4-5. Monorepo development builds
|
|
159
|
+
const monorepoRoot = findMonorepoRoot(options.monorepoRoot);
|
|
160
|
+
if (monorepoRoot) {
|
|
161
|
+
for (const profile of ['release', 'debug']) {
|
|
162
|
+
const p = join(monorepoRoot, 'packages', config.monorepoPackage, 'target', profile, binaryName);
|
|
163
|
+
if (existsSync(p)) return p;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 6. System PATH lookup
|
|
168
|
+
const pathDirs = (process.env.PATH || '').split(delimiter);
|
|
169
|
+
for (const dir of pathDirs) {
|
|
170
|
+
if (!dir) continue;
|
|
171
|
+
const p = join(dir, binaryName);
|
|
172
|
+
if (existsSync(p)) return p;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 7. ~/.grafema/bin/ (lazy-downloaded analyzers)
|
|
176
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
177
|
+
if (home) {
|
|
178
|
+
const p = join(home, '.grafema', 'bin', binaryName);
|
|
179
|
+
if (existsSync(p)) return p;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// 8. ~/.local/bin/ (user-installed)
|
|
183
|
+
if (home) {
|
|
184
|
+
const p = join(home, '.local', 'bin', binaryName);
|
|
185
|
+
if (existsSync(p)) return p;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Legacy: @grafema/rfdb npm package (old prebuilt location)
|
|
189
|
+
if (binaryName === 'rfdb-server') {
|
|
190
|
+
try {
|
|
191
|
+
const require = createRequire(import.meta.url);
|
|
192
|
+
const rfdbPkg = require.resolve('@grafema/rfdb');
|
|
193
|
+
const rfdbDir = dirname(rfdbPkg);
|
|
194
|
+
const platformDir = getPlatformDir();
|
|
195
|
+
const npmBinary = join(rfdbDir, 'prebuilt', platformDir, 'rfdb-server');
|
|
196
|
+
if (existsSync(npmBinary)) {
|
|
197
|
+
return npmBinary;
|
|
198
|
+
}
|
|
199
|
+
} catch {
|
|
200
|
+
// @grafema/rfdb not installed
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Find rfdb-server binary.
|
|
209
|
+
*
|
|
210
|
+
* @param options - Search options
|
|
211
|
+
* @returns Path to binary or null if not found
|
|
212
|
+
*/
|
|
213
|
+
export function findRfdbBinary(options: FindBinaryOptions = {}): string | null {
|
|
214
|
+
return findBinary('rfdb-server', options);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Find grafema-orchestrator binary.
|
|
219
|
+
*
|
|
220
|
+
* @param options - Search options
|
|
221
|
+
* @returns Path to binary or null if not found
|
|
222
|
+
*/
|
|
223
|
+
export function findOrchestratorBinary(options: FindBinaryOptions = {}): string | null {
|
|
224
|
+
return findBinary('grafema-orchestrator', options);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Find an analyzer binary by name (e.g. "grafema-analyzer", "haskell-resolve").
|
|
229
|
+
*
|
|
230
|
+
* Simpler search than findBinary() — no env var or platform package lookup.
|
|
231
|
+
* Search order: ~/.grafema/bin/, ~/.local/bin/, ~/.cabal/bin/, system PATH.
|
|
232
|
+
*/
|
|
233
|
+
export function findAnalyzerBinary(binaryName: string): string | null {
|
|
234
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
235
|
+
|
|
236
|
+
// ~/.grafema/bin/ (lazy-downloaded)
|
|
237
|
+
if (home) {
|
|
238
|
+
const p = join(home, '.grafema', 'bin', binaryName);
|
|
239
|
+
if (existsSync(p)) return p;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ~/.local/bin/
|
|
243
|
+
if (home) {
|
|
244
|
+
const p = join(home, '.local', 'bin', binaryName);
|
|
245
|
+
if (existsSync(p)) return p;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ~/.cabal/bin/ (Haskell builds)
|
|
249
|
+
if (home) {
|
|
250
|
+
const p = join(home, '.cabal', 'bin', binaryName);
|
|
251
|
+
if (existsSync(p)) return p;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// System PATH
|
|
255
|
+
const pathDirs = (process.env.PATH || '').split(delimiter);
|
|
256
|
+
for (const dir of pathDirs) {
|
|
257
|
+
if (!dir) continue;
|
|
258
|
+
const p = join(dir, binaryName);
|
|
259
|
+
if (existsSync(p)) return p;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Get human-readable error message when binary not found.
|
|
267
|
+
*/
|
|
268
|
+
export function getBinaryNotFoundMessage(binaryName?: BinaryName): string {
|
|
269
|
+
const name = binaryName || 'rfdb-server';
|
|
270
|
+
const config = BINARY_CONFIG[name];
|
|
271
|
+
const platformDir = getPlatformDir();
|
|
272
|
+
|
|
273
|
+
return `${name} binary not found for ${platformDir}
|
|
274
|
+
|
|
275
|
+
Options:
|
|
276
|
+
1. Install the grafema package (includes all binaries):
|
|
277
|
+
npm install grafema
|
|
278
|
+
|
|
279
|
+
2. Set environment variable:
|
|
280
|
+
export ${config.envVar}=/path/to/${name}
|
|
281
|
+
|
|
282
|
+
3. Build from source:
|
|
283
|
+
cd packages/${config.monorepoPackage} && cargo build --release
|
|
284
|
+
|
|
285
|
+
4. Install to system PATH or ~/.local/bin:
|
|
286
|
+
cp target/release/${name} ~/.local/bin/
|
|
287
|
+
`;
|
|
288
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy binary downloader for Grafema.
|
|
3
|
+
*
|
|
4
|
+
* Downloads Haskell analyzer binaries from GitHub Releases on first use.
|
|
5
|
+
* Binaries are cached in ~/.grafema/bin/ and reused across projects.
|
|
6
|
+
*
|
|
7
|
+
* Asset naming convention: {binary-name}-{platform}
|
|
8
|
+
* Example: grafema-analyzer-darwin-arm64
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, mkdirSync, chmodSync, renameSync, unlinkSync, createWriteStream } from 'fs';
|
|
12
|
+
import { join } from 'path';
|
|
13
|
+
import { get as httpsGet } from 'https';
|
|
14
|
+
import type { IncomingMessage } from 'http';
|
|
15
|
+
import { getPlatformDir } from './findRfdbBinary.js';
|
|
16
|
+
|
|
17
|
+
const GITHUB_REPO = 'Disentinel/grafema';
|
|
18
|
+
|
|
19
|
+
/** All analyzer binaries that can be lazy-downloaded. */
|
|
20
|
+
const DOWNLOADABLE_BINARIES = [
|
|
21
|
+
'grafema-analyzer',
|
|
22
|
+
'grafema-resolve',
|
|
23
|
+
'haskell-analyzer',
|
|
24
|
+
'haskell-resolve',
|
|
25
|
+
'grafema-rust-analyzer',
|
|
26
|
+
'grafema-rust-resolve',
|
|
27
|
+
'grafema-java-analyzer',
|
|
28
|
+
'java-resolve',
|
|
29
|
+
'java-parser',
|
|
30
|
+
'grafema-kotlin-analyzer',
|
|
31
|
+
'kotlin-resolve',
|
|
32
|
+
'kotlin-parser',
|
|
33
|
+
'jvm-cross-resolve',
|
|
34
|
+
'grafema-python-analyzer',
|
|
35
|
+
'python-resolve',
|
|
36
|
+
'grafema-go-analyzer',
|
|
37
|
+
'go-resolve',
|
|
38
|
+
'go-parser',
|
|
39
|
+
'grafema-cpp-analyzer',
|
|
40
|
+
'cpp-resolve',
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get the directory where lazy-downloaded binaries are stored.
|
|
45
|
+
*/
|
|
46
|
+
export function getGrafemaBinDir(): string {
|
|
47
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
48
|
+
return join(home, '.grafema', 'bin');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check if a binary name is eligible for lazy download.
|
|
53
|
+
*/
|
|
54
|
+
export function isDownloadable(binaryName: string): boolean {
|
|
55
|
+
return DOWNLOADABLE_BINARIES.includes(binaryName);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Find a binary in ~/.grafema/bin/.
|
|
60
|
+
*/
|
|
61
|
+
export function findInGrafemaBin(binaryName: string): string | null {
|
|
62
|
+
const binDir = getGrafemaBinDir();
|
|
63
|
+
const p = join(binDir, binaryName);
|
|
64
|
+
return existsSync(p) ? p : null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Simple HTTPS GET that follows redirects (GitHub uses 302 → S3).
|
|
69
|
+
*/
|
|
70
|
+
function httpsGetFollowRedirects(url: string): Promise<IncomingMessage> {
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
const req = httpsGet(url, { headers: { 'User-Agent': 'grafema-cli' } }, (res) => {
|
|
73
|
+
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
74
|
+
const location = res.headers.location;
|
|
75
|
+
if (!location) return reject(new Error('Redirect without location'));
|
|
76
|
+
httpsGetFollowRedirects(location).then(resolve, reject);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
resolve(res);
|
|
80
|
+
});
|
|
81
|
+
req.on('error', reject);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Find the latest binaries-v* release tag from GitHub API.
|
|
87
|
+
*/
|
|
88
|
+
async function findLatestReleaseTag(): Promise<string> {
|
|
89
|
+
const url = `https://api.github.com/repos/${GITHUB_REPO}/releases?per_page=30`;
|
|
90
|
+
const res = await httpsGetFollowRedirects(url);
|
|
91
|
+
|
|
92
|
+
const chunks: Buffer[] = [];
|
|
93
|
+
for await (const chunk of res) {
|
|
94
|
+
chunks.push(chunk as Buffer);
|
|
95
|
+
}
|
|
96
|
+
const body = Buffer.concat(chunks).toString('utf-8');
|
|
97
|
+
|
|
98
|
+
if (res.statusCode !== 200) {
|
|
99
|
+
throw new Error(`GitHub API returned ${res.statusCode}: ${body.slice(0, 200)}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const releases = JSON.parse(body) as Array<{ tag_name: string }>;
|
|
103
|
+
const binRelease = releases.find(r => r.tag_name.startsWith('binaries-v'));
|
|
104
|
+
if (!binRelease) {
|
|
105
|
+
throw new Error('No binaries-v* release found on GitHub');
|
|
106
|
+
}
|
|
107
|
+
return binRelease.tag_name;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Download a single binary from a GitHub Release.
|
|
112
|
+
*
|
|
113
|
+
* @param binaryName - e.g. "grafema-analyzer"
|
|
114
|
+
* @param tag - e.g. "binaries-v0.3.0-beta"
|
|
115
|
+
* @param onProgress - optional callback for download progress
|
|
116
|
+
* @returns Path to the downloaded binary
|
|
117
|
+
*/
|
|
118
|
+
export async function downloadBinary(
|
|
119
|
+
binaryName: string,
|
|
120
|
+
tag?: string,
|
|
121
|
+
onProgress?: (msg: string) => void,
|
|
122
|
+
): Promise<string> {
|
|
123
|
+
const platform = getPlatformDir();
|
|
124
|
+
const assetName = `${binaryName}-${platform}`;
|
|
125
|
+
const binDir = getGrafemaBinDir();
|
|
126
|
+
|
|
127
|
+
// Ensure ~/.grafema/bin/ exists
|
|
128
|
+
if (!existsSync(binDir)) {
|
|
129
|
+
mkdirSync(binDir, { recursive: true });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const targetPath = join(binDir, binaryName);
|
|
133
|
+
const tmpPath = `${targetPath}.downloading`;
|
|
134
|
+
|
|
135
|
+
// Resolve tag
|
|
136
|
+
const releaseTag = tag || await findLatestReleaseTag();
|
|
137
|
+
const downloadUrl = `https://github.com/${GITHUB_REPO}/releases/download/${releaseTag}/${assetName}`;
|
|
138
|
+
|
|
139
|
+
const log = onProgress || (() => {});
|
|
140
|
+
log(`Downloading ${binaryName} for ${platform}...`);
|
|
141
|
+
|
|
142
|
+
// Download to temp file
|
|
143
|
+
const res = await httpsGetFollowRedirects(downloadUrl);
|
|
144
|
+
|
|
145
|
+
if (res.statusCode !== 200) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
`Failed to download ${assetName}: HTTP ${res.statusCode}. ` +
|
|
148
|
+
`Check that release ${releaseTag} has asset ${assetName}.`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const contentLength = parseInt(res.headers['content-length'] || '0', 10);
|
|
153
|
+
const sizeMB = contentLength > 0 ? `${(contentLength / 1024 / 1024).toFixed(1)}MB` : 'unknown size';
|
|
154
|
+
log(` ${assetName} (${sizeMB})`);
|
|
155
|
+
|
|
156
|
+
await new Promise<void>((resolve, reject) => {
|
|
157
|
+
const file = createWriteStream(tmpPath);
|
|
158
|
+
res.pipe(file);
|
|
159
|
+
file.on('finish', () => { file.close(); resolve(); });
|
|
160
|
+
file.on('error', (err) => { unlinkSync(tmpPath); reject(err); });
|
|
161
|
+
res.on('error', (err) => { unlinkSync(tmpPath); reject(err); });
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Atomic rename + make executable
|
|
165
|
+
renameSync(tmpPath, targetPath);
|
|
166
|
+
chmodSync(targetPath, 0o755);
|
|
167
|
+
|
|
168
|
+
log(` Installed to ${targetPath}`);
|
|
169
|
+
return targetPath;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Ensure a binary exists, downloading it if missing and eligible.
|
|
174
|
+
*
|
|
175
|
+
* This is the main entry point for lazy downloading. Call it before
|
|
176
|
+
* spawning an analyzer binary. Returns the path if found or downloaded,
|
|
177
|
+
* null if the binary can't be obtained.
|
|
178
|
+
*
|
|
179
|
+
* @param binaryName - e.g. "grafema-analyzer"
|
|
180
|
+
* @param existingPath - path from findBinary() (null = not found locally)
|
|
181
|
+
* @param onProgress - optional callback for download progress
|
|
182
|
+
*/
|
|
183
|
+
export async function ensureBinary(
|
|
184
|
+
binaryName: string,
|
|
185
|
+
existingPath: string | null,
|
|
186
|
+
onProgress?: (msg: string) => void,
|
|
187
|
+
): Promise<string | null> {
|
|
188
|
+
// Already found locally
|
|
189
|
+
if (existingPath) return existingPath;
|
|
190
|
+
|
|
191
|
+
// Check ~/.grafema/bin/ first (may have been downloaded previously)
|
|
192
|
+
const cached = findInGrafemaBin(binaryName);
|
|
193
|
+
if (cached) return cached;
|
|
194
|
+
|
|
195
|
+
// Not downloadable (e.g. rfdb-server, grafema-orchestrator — these must be in npm package)
|
|
196
|
+
if (!isDownloadable(binaryName)) return null;
|
|
197
|
+
|
|
198
|
+
// Download
|
|
199
|
+
try {
|
|
200
|
+
return await downloadBinary(binaryName, undefined, onProgress);
|
|
201
|
+
} catch (err) {
|
|
202
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
203
|
+
(onProgress || console.error)(`Failed to download ${binaryName}: ${msg}`);
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
}
|