@yesprasad/fluent-graph 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +36 -0
- package/README.md +447 -0
- package/bin/fluent-graph.js +9 -0
- package/dist/cli/commands/blast.d.ts +2 -0
- package/dist/cli/commands/blast.js +202 -0
- package/dist/cli/commands/extract.d.ts +6 -0
- package/dist/cli/commands/extract.js +178 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +61 -0
- package/dist/cli/utils/banner.d.ts +2 -0
- package/dist/cli/utils/banner.js +25 -0
- package/dist/cli/utils/display.d.ts +2 -0
- package/dist/cli/utils/display.js +172 -0
- package/dist/extractor/edges.d.ts +11 -0
- package/dist/extractor/edges.js +120 -0
- package/dist/extractor/index.d.ts +2 -0
- package/dist/extractor/index.js +24 -0
- package/dist/extractor/metadata.d.ts +6 -0
- package/dist/extractor/metadata.js +37 -0
- package/dist/extractor/nodes.d.ts +2 -0
- package/dist/extractor/nodes.js +64 -0
- package/dist/graph/extractor.d.ts +6 -0
- package/dist/graph/extractor.js +34 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +18 -0
- package/dist/output/writer.d.ts +9 -0
- package/dist/output/writer.js +28 -0
- package/dist/sdk/detector.d.ts +6 -0
- package/dist/sdk/detector.js +32 -0
- package/dist/sdk/loader.d.ts +5 -0
- package/dist/sdk/loader.js +15 -0
- package/dist/types/graphs.d.ts +35 -0
- package/dist/types/graphs.js +2 -0
- package/package.json +53 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ArtifactEdge, ArtifactNode } from '../types/graphs';
|
|
2
|
+
/**
|
|
3
|
+
* UNIVERSAL EDGE EXTRACTOR
|
|
4
|
+
*
|
|
5
|
+
* Strategy:
|
|
6
|
+
* 1. For every record, scan ALL properties
|
|
7
|
+
* 2. If a property looks like a reference (RecordId or string table name), create an edge
|
|
8
|
+
* 3. No hardcoded artifact types
|
|
9
|
+
*/
|
|
10
|
+
export declare function extractReferenceEdges(records: any, allNodes: ArtifactNode[]): ArtifactEdge[];
|
|
11
|
+
export declare function extractCompositionEdges(records: any): ArtifactEdge[];
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractReferenceEdges = extractReferenceEdges;
|
|
4
|
+
exports.extractCompositionEdges = extractCompositionEdges;
|
|
5
|
+
/**
|
|
6
|
+
* UNIVERSAL EDGE EXTRACTOR
|
|
7
|
+
*
|
|
8
|
+
* Strategy:
|
|
9
|
+
* 1. For every record, scan ALL properties
|
|
10
|
+
* 2. If a property looks like a reference (RecordId or string table name), create an edge
|
|
11
|
+
* 3. No hardcoded artifact types
|
|
12
|
+
*/
|
|
13
|
+
function extractReferenceEdges(records, allNodes) {
|
|
14
|
+
const edges = [];
|
|
15
|
+
for (const record of records.query()) {
|
|
16
|
+
const fromId = record.getId()?.getValue()?.toString();
|
|
17
|
+
const props = record.setProperties || {};
|
|
18
|
+
const recordTable = record.getTable()?.toString();
|
|
19
|
+
// === STRATEGY 1: SCHEMA RELATIONSHIPS ===
|
|
20
|
+
// Extract reference fields from sys_dictionary records
|
|
21
|
+
if (recordTable === 'sys_dictionary') {
|
|
22
|
+
const internalType = props.internal_type?.getValue?.()?.toString();
|
|
23
|
+
const refTable = props.reference?.getValue?.()?.toString();
|
|
24
|
+
const elementName = props.element?.getValue?.()?.toString();
|
|
25
|
+
if (internalType === 'reference' && refTable) {
|
|
26
|
+
const targetNode = allNodes.find(n => n.name === refTable);
|
|
27
|
+
edges.push({
|
|
28
|
+
from: fromId,
|
|
29
|
+
to: targetNode?.id || `platform:${refTable}`,
|
|
30
|
+
via: elementName || 'reference',
|
|
31
|
+
type: 'schema_relationship'
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// === STRATEGY 2: LOGIC ATTACHMENTS ===
|
|
36
|
+
// Any artifact with 'table' or 'collection' property
|
|
37
|
+
const tableRef = props.table?.getValue?.()?.toString() ||
|
|
38
|
+
props.collection?.getValue?.()?.toString();
|
|
39
|
+
if (tableRef) {
|
|
40
|
+
const targetNode = allNodes.find(n => n.name === tableRef);
|
|
41
|
+
edges.push({
|
|
42
|
+
from: fromId,
|
|
43
|
+
to: targetNode?.id || `platform:${tableRef}`,
|
|
44
|
+
via: props.table ? 'table' : 'collection',
|
|
45
|
+
type: 'dependency',
|
|
46
|
+
targetTable: tableRef // Added: Set targetTable for consistency (fixes fallback in display for platform tables)
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
// === STRATEGY 3: ACL NAME PARSING ===
|
|
50
|
+
// sys_security_acl uses 'name' field like 'task.description'
|
|
51
|
+
if (recordTable === 'sys_security_acl') {
|
|
52
|
+
const aclName = props.name?.getValue?.()?.toString();
|
|
53
|
+
if (aclName && aclName.includes('.')) {
|
|
54
|
+
const [targetTable] = aclName.split('.');
|
|
55
|
+
const targetNode = allNodes.find(n => n.name === targetTable);
|
|
56
|
+
edges.push({
|
|
57
|
+
from: fromId,
|
|
58
|
+
to: targetNode?.id || `platform:${targetTable}`,
|
|
59
|
+
via: 'name',
|
|
60
|
+
type: 'dependency',
|
|
61
|
+
targetTable
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// === STRATEGY 4: UNIVERSAL REFERENCE SCAN ===
|
|
66
|
+
// Scan ALL properties for RecordId types (sys_id references)
|
|
67
|
+
for (const [key, val] of Object.entries(props)) {
|
|
68
|
+
// Added: Handle arrays of RecordId (e.g., roles: [role1, role2]) to create multiple edges
|
|
69
|
+
if (Array.isArray(val)) {
|
|
70
|
+
val.forEach((item) => {
|
|
71
|
+
if (item && item.constructor?.name === 'RecordId') {
|
|
72
|
+
const targetId = item.getValue?.()?.toString();
|
|
73
|
+
const targetTable = item.getTable?.()?.toString();
|
|
74
|
+
if (targetId && targetTable !== 'sys_db_object') {
|
|
75
|
+
edges.push({
|
|
76
|
+
from: fromId,
|
|
77
|
+
to: targetId,
|
|
78
|
+
via: key,
|
|
79
|
+
type: 'dependency',
|
|
80
|
+
targetTable
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
else if (val && val.constructor?.name === 'RecordId') {
|
|
87
|
+
const targetId = val.getValue?.()?.toString();
|
|
88
|
+
const targetTable = val.getTable?.()?.toString();
|
|
89
|
+
if (targetId && targetTable !== 'sys_db_object') {
|
|
90
|
+
edges.push({
|
|
91
|
+
from: fromId,
|
|
92
|
+
to: targetId,
|
|
93
|
+
via: key,
|
|
94
|
+
type: 'dependency',
|
|
95
|
+
targetTable
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return edges;
|
|
102
|
+
}
|
|
103
|
+
function extractCompositionEdges(records) {
|
|
104
|
+
const edges = [];
|
|
105
|
+
for (const record of records.query()) {
|
|
106
|
+
const parentId = record.getId()?.getValue()?.toString();
|
|
107
|
+
const children = record.getRelated?.() || [];
|
|
108
|
+
for (const child of children) {
|
|
109
|
+
const childId = child.getId()?.getValue()?.toString();
|
|
110
|
+
if (childId && parentId) {
|
|
111
|
+
edges.push({
|
|
112
|
+
from: parentId,
|
|
113
|
+
to: childId,
|
|
114
|
+
type: 'composition'
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return edges;
|
|
120
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractLineageGraph = extractLineageGraph;
|
|
4
|
+
const nodes_1 = require("./nodes");
|
|
5
|
+
const edges_1 = require("./edges");
|
|
6
|
+
const metadata_1 = require("./metadata");
|
|
7
|
+
async function extractLineageGraph(project) {
|
|
8
|
+
// Get raw records from the ServiceNow SDK Project
|
|
9
|
+
const records = await project.getRecords();
|
|
10
|
+
const rootDir = project.getRootDir();
|
|
11
|
+
// 1. Extract Nodes (Includes dynamic attributes like order, when, active)
|
|
12
|
+
const nodes = (0, nodes_1.extractNodes)(records, rootDir);
|
|
13
|
+
// 2. Extract Edges (Requires node list for implicit string-to-table resolution)
|
|
14
|
+
const referenceEdges = (0, edges_1.extractReferenceEdges)(records, nodes);
|
|
15
|
+
const compositionEdges = (0, edges_1.extractCompositionEdges)(records);
|
|
16
|
+
const edges = [...referenceEdges, ...compositionEdges];
|
|
17
|
+
// 3. Generate Summary Metadata
|
|
18
|
+
const metadata = (0, metadata_1.generateMetadata)(project, records.query().length, referenceEdges.length, compositionEdges.length);
|
|
19
|
+
return {
|
|
20
|
+
metadata,
|
|
21
|
+
nodes,
|
|
22
|
+
edges
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { GraphMetadata } from '../types/graphs';
|
|
2
|
+
/**
|
|
3
|
+
* Generates the Global Graph Summary.
|
|
4
|
+
* provide architectural context for the extracted lineage.
|
|
5
|
+
*/
|
|
6
|
+
export declare function generateMetadata(project: any, recordCount: number, referenceCount: number, compositionCount: number): GraphMetadata;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateMetadata = generateMetadata;
|
|
4
|
+
/**
|
|
5
|
+
* Generates the Global Graph Summary.
|
|
6
|
+
* provide architectural context for the extracted lineage.
|
|
7
|
+
*/
|
|
8
|
+
function generateMetadata(project, recordCount, referenceCount, compositionCount) {
|
|
9
|
+
// These calls safely interact with the SDK Project Proxy
|
|
10
|
+
const config = project.getConfig() || {};
|
|
11
|
+
const pkg = project.getPackage() || {};
|
|
12
|
+
return {
|
|
13
|
+
projectRoot: project.getRootDir() || process.cwd(),
|
|
14
|
+
/**
|
|
15
|
+
* Identification:
|
|
16
|
+
* Essential for determining the "Home Scope" of the graph.
|
|
17
|
+
*/
|
|
18
|
+
scopeId: config.scopeId || 'unknown',
|
|
19
|
+
scopeName: config.scope || 'Global',
|
|
20
|
+
/**
|
|
21
|
+
* Quantitative Metrics:
|
|
22
|
+
* recordCount represents the Vertex count (Nodes).
|
|
23
|
+
* referenceCount + compositionCount represents the Edge density.
|
|
24
|
+
*/
|
|
25
|
+
recordCount,
|
|
26
|
+
referenceCount,
|
|
27
|
+
compositionCount,
|
|
28
|
+
/**
|
|
29
|
+
* Temporal & Environment context:
|
|
30
|
+
* Helps the CLI determine if the graph file is stale.
|
|
31
|
+
*/
|
|
32
|
+
generatedAt: new Date().toISOString(),
|
|
33
|
+
sdkVersion: pkg.dependencies?.['@servicenow/sdk'] ||
|
|
34
|
+
pkg.devDependencies?.['@servicenow/sdk'] ||
|
|
35
|
+
'unknown'
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.extractNodes = extractNodes;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
/**
|
|
9
|
+
* UNIVERSAL NODE EXTRACTOR
|
|
10
|
+
*
|
|
11
|
+
* Strategy:
|
|
12
|
+
* - Extract ALL primitive attributes (strings, numbers, booleans)
|
|
13
|
+
* - Ignore complex objects (they become edges)
|
|
14
|
+
* - Preserve metadata like 'element', 'name', 'type' for display logic
|
|
15
|
+
*/
|
|
16
|
+
function sanitizeAttributes(props) {
|
|
17
|
+
const clean = {};
|
|
18
|
+
for (const [key, val] of Object.entries(props)) {
|
|
19
|
+
if (!val)
|
|
20
|
+
continue;
|
|
21
|
+
try {
|
|
22
|
+
// Extract the primitive value if it's a SDK wrapper
|
|
23
|
+
const plainValue = typeof val.getValue === 'function'
|
|
24
|
+
? val.getValue()
|
|
25
|
+
: val;
|
|
26
|
+
// Only keep primitives and arrays of primitives
|
|
27
|
+
if (typeof plainValue !== 'object' || plainValue === null || Array.isArray(plainValue)) {
|
|
28
|
+
clean[key] = plainValue;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return clean;
|
|
36
|
+
}
|
|
37
|
+
function extractNodes(records, projectRoot) {
|
|
38
|
+
const nodes = [];
|
|
39
|
+
for (const record of records.query()) {
|
|
40
|
+
const props = record.setProperties || {};
|
|
41
|
+
const idValue = record.getId()?.getValue()?.toString() || '';
|
|
42
|
+
const tableName = record.getTable()?.toString() || '';
|
|
43
|
+
// Determine a human-readable name
|
|
44
|
+
const name = props.name?.getValue?.()?.toString() ||
|
|
45
|
+
props.label?.getValue?.()?.toString() ||
|
|
46
|
+
props.sys_name?.getValue?.()?.toString() ||
|
|
47
|
+
idValue;
|
|
48
|
+
// Capture file source (relative to project root)
|
|
49
|
+
const rawFile = record.getOriginalFilePath()?.toString() || '';
|
|
50
|
+
const relativeFile = rawFile ? path_1.default.relative(projectRoot, rawFile) : '';
|
|
51
|
+
nodes.push({
|
|
52
|
+
id: idValue,
|
|
53
|
+
table: tableName,
|
|
54
|
+
action: record.getAction(),
|
|
55
|
+
name,
|
|
56
|
+
attributes: sanitizeAttributes(props),
|
|
57
|
+
source: {
|
|
58
|
+
file: relativeFile,
|
|
59
|
+
type: record.getOriginalSource()?.getKindName?.() || 'SourceFile'
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return nodes;
|
|
64
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { LineageGraph } from '../types/graphs';
|
|
2
|
+
/**
|
|
3
|
+
* The Orchestrator: Converts raw SDK records into a formal Directed Graph.
|
|
4
|
+
* It treats every artifact as a node and every relationship as an iterable edge.
|
|
5
|
+
*/
|
|
6
|
+
export declare function extractLineageGraph(project: any): Promise<LineageGraph>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractLineageGraph = extractLineageGraph;
|
|
4
|
+
const nodes_1 = require("../extractor/nodes");
|
|
5
|
+
const edges_1 = require("../extractor/edges");
|
|
6
|
+
const metadata_1 = require("../extractor/metadata");
|
|
7
|
+
/**
|
|
8
|
+
* The Orchestrator: Converts raw SDK records into a formal Directed Graph.
|
|
9
|
+
* It treats every artifact as a node and every relationship as an iterable edge.
|
|
10
|
+
*/
|
|
11
|
+
async function extractLineageGraph(project) {
|
|
12
|
+
const records = project.getRecords();
|
|
13
|
+
const projectRoot = project.getRootDir();
|
|
14
|
+
// 1. PHASE ONE: Vertex Extraction (Nodes)
|
|
15
|
+
// We only extract the "winning" records to avoid conflict noise.
|
|
16
|
+
// This creates the list of local artifacts.
|
|
17
|
+
const nodes = (0, nodes_1.extractNodes)(records, projectRoot);
|
|
18
|
+
// 2. PHASE TWO: Edge Extraction (Relationships)
|
|
19
|
+
// We pass the 'nodes' array into extractReferenceEdges.
|
|
20
|
+
// This allows the edge extractor to differentiate between:
|
|
21
|
+
// a) A reference to a local table (e.g. x_1566039_seventh_account)
|
|
22
|
+
// b) A reference to a platform table (e.g. platform:incident)
|
|
23
|
+
const refEdges = (0, edges_1.extractReferenceEdges)(records, nodes);
|
|
24
|
+
const compEdges = (0, edges_1.extractCompositionEdges)(records);
|
|
25
|
+
const allEdges = [...refEdges, ...compEdges];
|
|
26
|
+
// 3. PHASE THREE: Metadata & Context
|
|
27
|
+
// Calculate graph-wide statistics and SDK versioning.
|
|
28
|
+
const metadata = (0, metadata_1.generateMetadata)(project, nodes.length, refEdges.length, compEdges.length);
|
|
29
|
+
return {
|
|
30
|
+
metadata,
|
|
31
|
+
nodes,
|
|
32
|
+
edges: allEdges
|
|
33
|
+
};
|
|
34
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./extractor"), exports);
|
|
18
|
+
__exportStar(require("./types/graphs"), exports);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { LineageGraph } from '../types/graphs';
|
|
2
|
+
/**
|
|
3
|
+
* Write lineage graph to JSON file
|
|
4
|
+
*/
|
|
5
|
+
export declare function writeGraph(graph: LineageGraph, outputPath: string): void;
|
|
6
|
+
/**
|
|
7
|
+
* Generate default output filename with timestamp
|
|
8
|
+
*/
|
|
9
|
+
export declare function getDefaultOutputPath(projectRoot: string): string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.writeGraph = writeGraph;
|
|
7
|
+
exports.getDefaultOutputPath = getDefaultOutputPath;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
/**
|
|
11
|
+
* Write lineage graph to JSON file
|
|
12
|
+
*/
|
|
13
|
+
function writeGraph(graph, outputPath) {
|
|
14
|
+
const formatted = JSON.stringify(graph, null, 2);
|
|
15
|
+
// Ensure directory exists
|
|
16
|
+
const dir = path_1.default.dirname(outputPath);
|
|
17
|
+
if (!fs_1.default.existsSync(dir)) {
|
|
18
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
fs_1.default.writeFileSync(outputPath, formatted, 'utf-8');
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Generate default output filename with timestamp
|
|
24
|
+
*/
|
|
25
|
+
function getDefaultOutputPath(projectRoot) {
|
|
26
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
27
|
+
return path_1.default.join(projectRoot, `fluent-graph-${timestamp}.json`);
|
|
28
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.detectSDK = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const detectSDK = async (cwd) => {
|
|
10
|
+
// Step 1: Check if node_modules exist
|
|
11
|
+
const nodeModulesPath = path_1.default.join(cwd, 'node_modules');
|
|
12
|
+
if (!fs_1.default.existsSync(nodeModulesPath)) {
|
|
13
|
+
return {
|
|
14
|
+
found: false,
|
|
15
|
+
error: 'node_modules not found. Run npm install first.'
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
// Step 2: Check if @servicenow/sdk exists
|
|
19
|
+
const sdkPath = path_1.default.join(nodeModulesPath, '@servicenow', 'sdk');
|
|
20
|
+
if (!fs_1.default.existsSync(sdkPath)) {
|
|
21
|
+
return {
|
|
22
|
+
found: false,
|
|
23
|
+
error: '@servicenow/sdk not found. This is not a ServiceNow Fluent project.'
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
// Step 3: Success
|
|
27
|
+
return {
|
|
28
|
+
found: true,
|
|
29
|
+
sdkPath
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
exports.detectSDK = detectSDK;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadWorkspaceSDK = loadWorkspaceSDK;
|
|
7
|
+
const module_1 = require("module");
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
function loadWorkspaceSDK(sdkPath) {
|
|
10
|
+
const requireFromWorkspace = (0, module_1.createRequire)(path_1.default.join(sdkPath, 'package.json'));
|
|
11
|
+
return {
|
|
12
|
+
buildCore: requireFromWorkspace('@servicenow/sdk-build-core'),
|
|
13
|
+
sdkapi: requireFromWorkspace('@servicenow/sdk-api'),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface ArtifactNode {
|
|
2
|
+
id: string;
|
|
3
|
+
table: string;
|
|
4
|
+
name: string;
|
|
5
|
+
label?: string;
|
|
6
|
+
action: 'INSERT_OR_UPDATE' | 'DELETE';
|
|
7
|
+
attributes: Record<string, any>;
|
|
8
|
+
source: {
|
|
9
|
+
file: string;
|
|
10
|
+
type: string;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export interface ArtifactEdge {
|
|
14
|
+
from: string;
|
|
15
|
+
to: string;
|
|
16
|
+
via?: string;
|
|
17
|
+
type: 'reference' | 'composition' | 'dependency' | 'inheritance' | 'schema_relationship';
|
|
18
|
+
targetTable?: string;
|
|
19
|
+
childTable?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface GraphMetadata {
|
|
22
|
+
projectRoot: string;
|
|
23
|
+
scopeId: string;
|
|
24
|
+
scopeName: string;
|
|
25
|
+
recordCount: number;
|
|
26
|
+
referenceCount: number;
|
|
27
|
+
compositionCount: number;
|
|
28
|
+
generatedAt: string;
|
|
29
|
+
sdkVersion: string;
|
|
30
|
+
}
|
|
31
|
+
export interface LineageGraph {
|
|
32
|
+
nodes: ArtifactNode[];
|
|
33
|
+
edges: ArtifactEdge[];
|
|
34
|
+
metadata: GraphMetadata;
|
|
35
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yesprasad/fluent-graph",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Dependency/ Lineage graph extraction & Blast-radius/ impact analysis for ServiceNow Fluent Artifacts",
|
|
5
|
+
"bin": {
|
|
6
|
+
"fluent-graph": "./bin/fluent-graph.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"bin"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc && chmod +x bin/fluent-graph.js",
|
|
16
|
+
"dev": "tsx src/cli/index.ts",
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"servicenow",
|
|
21
|
+
"fluent",
|
|
22
|
+
"lineage",
|
|
23
|
+
"dependency-graph",
|
|
24
|
+
"blast-radius",
|
|
25
|
+
"impact-analysis",
|
|
26
|
+
"cli"
|
|
27
|
+
],
|
|
28
|
+
"author": "Eshwar Sowbhagya Prasad Yaddanapudi",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/yesprasad/fluent-graph.git"
|
|
32
|
+
},
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/yesprasad/fluent-graph/issues"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/yesprasad/fluent-graph#readme",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"type": "commonjs",
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"chalk": "^4.1.2",
|
|
41
|
+
"cli-table3": "^0.6.5",
|
|
42
|
+
"commander": "^12.0.0",
|
|
43
|
+
"ora": "^5.4.1"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^20.0.0",
|
|
47
|
+
"tsx": "^4.0.0",
|
|
48
|
+
"typescript": "^5.0.0"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"@servicenow/sdk": "4.1.0"
|
|
52
|
+
}
|
|
53
|
+
}
|