@yesprasad/fluent-graph 0.2.1 → 0.2.3
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/dist/cli/index.js +32 -36
- package/dist/index.d.ts +117 -2
- package/dist/index.js +1 -18
- package/package.json +5 -5
- package/dist/blast/traversal.d.ts +0 -48
- package/dist/blast/traversal.js +0 -141
- package/dist/cli/commands/blast.d.ts +0 -2
- package/dist/cli/commands/blast.js +0 -247
- package/dist/cli/commands/extract.d.ts +0 -6
- package/dist/cli/commands/extract.js +0 -99
- package/dist/cli/index.d.ts +0 -1
- package/dist/cli/utils/banner.d.ts +0 -2
- package/dist/cli/utils/banner.js +0 -25
- package/dist/cli/utils/display.d.ts +0 -3
- package/dist/cli/utils/display.js +0 -137
- package/dist/extractor/edges.d.ts +0 -13
- package/dist/extractor/edges.js +0 -136
- package/dist/extractor/index.d.ts +0 -3
- package/dist/extractor/index.js +0 -24
- package/dist/extractor/metadata.d.ts +0 -6
- package/dist/extractor/metadata.js +0 -37
- package/dist/extractor/nodes.d.ts +0 -2
- package/dist/extractor/nodes.js +0 -64
- package/dist/graph/extractor.d.ts +0 -7
- package/dist/graph/extractor.js +0 -36
- package/dist/output/writer.d.ts +0 -9
- package/dist/output/writer.js +0 -28
- package/dist/sdk/detector.d.ts +0 -6
- package/dist/sdk/detector.js +0 -32
- package/dist/sdk/loader.d.ts +0 -7
- package/dist/sdk/loader.js +0 -19
- package/dist/sdk/registry.d.ts +0 -30
- package/dist/sdk/registry.js +0 -128
- package/dist/types/graphs.d.ts +0 -35
- package/dist/types/graphs.js +0 -2
|
@@ -1,247 +0,0 @@
|
|
|
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.blastCommand = void 0;
|
|
40
|
-
const commander_1 = require("commander");
|
|
41
|
-
const fs = __importStar(require("fs"));
|
|
42
|
-
const path = __importStar(require("path"));
|
|
43
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
44
|
-
const cli_table3_1 = __importDefault(require("cli-table3"));
|
|
45
|
-
const registry_1 = require("../../sdk/registry");
|
|
46
|
-
const detector_1 = require("../../sdk/detector");
|
|
47
|
-
const traversal_1 = require("../../blast/traversal");
|
|
48
|
-
exports.blastCommand = new commander_1.Command('blast')
|
|
49
|
-
.description('Analyze blast radius for a target table')
|
|
50
|
-
.argument('<target>', 'Target table name (e.g., incident, x_bank_account)')
|
|
51
|
-
.option('-g, --graph <path>', 'Path to graph JSON file', 'fluent-graph.json')
|
|
52
|
-
.option('-f, --format <type>', 'Output format: table, json, csv', 'table')
|
|
53
|
-
.option('--depth <n>', 'Maximum traversal depth', '10')
|
|
54
|
-
.option('--show-paths', 'Show the full dependency chain for each transitive artifact')
|
|
55
|
-
.option('--direct-only', 'Show only hop-1 (direct) impacts')
|
|
56
|
-
.option('--include-deleted', 'Include deleted artifacts in the analysis')
|
|
57
|
-
.option('--no-banner', 'Skip banner (inherited)')
|
|
58
|
-
.action(async (targetTableName, options) => {
|
|
59
|
-
try {
|
|
60
|
-
const graphFilePath = path.isAbsolute(options.graph)
|
|
61
|
-
? options.graph
|
|
62
|
-
: path.join(process.cwd(), options.graph);
|
|
63
|
-
// Load registry for type name derivation (best-effort; falls back gracefully)
|
|
64
|
-
const registry = new registry_1.ArtifactTypeRegistry();
|
|
65
|
-
const sdkDetection = await (0, detector_1.detectSDK)(process.cwd());
|
|
66
|
-
if (sdkDetection.found && sdkDetection.sdkPath) {
|
|
67
|
-
registry.load(sdkDetection.sdkPath);
|
|
68
|
-
}
|
|
69
|
-
if (!fs.existsSync(graphFilePath)) {
|
|
70
|
-
console.error(chalk_1.default.red(`\n❌ Error: Graph file not found at ${graphFilePath}`));
|
|
71
|
-
process.exit(1);
|
|
72
|
-
}
|
|
73
|
-
const lineageGraph = JSON.parse(fs.readFileSync(graphFilePath, 'utf8'));
|
|
74
|
-
// ── STEP 1: Validate target exists or is referenced ─────────────────────
|
|
75
|
-
const targetNode = lineageGraph.nodes.find(node => node.name === targetTableName);
|
|
76
|
-
const targetNodeId = targetNode ? targetNode.id : `platform:${targetTableName}`;
|
|
77
|
-
const isBaseTable = !targetNode;
|
|
78
|
-
const isTargetReferenced = lineageGraph.edges.some(edge => edge.to === targetNodeId || edge.targetTable === targetTableName);
|
|
79
|
-
if (!targetNode && !isTargetReferenced) {
|
|
80
|
-
console.log(chalk_1.default.red(`\n❌ Target "${targetTableName}" not found or referenced`));
|
|
81
|
-
console.log(chalk_1.default.yellow('\n💡 Available custom tables:'));
|
|
82
|
-
const customTables = lineageGraph.nodes
|
|
83
|
-
.filter(node => registry.getPluginName(node.table) === 'TablePlugin' && !registry.isChild(node.table) && node.action !== 'DELETE')
|
|
84
|
-
.map(node => node.name)
|
|
85
|
-
.sort();
|
|
86
|
-
customTables.forEach(tableName => console.log(chalk_1.default.gray(' • ') + chalk_1.default.white(tableName)));
|
|
87
|
-
console.log(chalk_1.default.yellow('\n💡 Referenced platform tables:'));
|
|
88
|
-
const platformTables = [...new Set(lineageGraph.edges
|
|
89
|
-
.filter(edge => edge.to.startsWith('platform:'))
|
|
90
|
-
.map(edge => edge.to.split(':')[1]))].sort();
|
|
91
|
-
platformTables.forEach(tableName => console.log(chalk_1.default.gray(' • ') + chalk_1.default.magenta(tableName)));
|
|
92
|
-
process.exit(1);
|
|
93
|
-
}
|
|
94
|
-
// ── STEP 2: Display header ───────────────────────────────────────────────
|
|
95
|
-
console.log(chalk_1.default.yellow.bold('\n🔥 BLAST RADIUS ANALYSIS'));
|
|
96
|
-
console.log(chalk_1.default.gray('═'.repeat(80)));
|
|
97
|
-
console.log(chalk_1.default.gray('Target: ') + chalk_1.default.white(targetTableName) + (isBaseTable ? chalk_1.default.yellow(' [Base Table]') : ''));
|
|
98
|
-
if (!isBaseTable) {
|
|
99
|
-
console.log(chalk_1.default.gray('File: ') + chalk_1.default.gray(targetNode.source.file));
|
|
100
|
-
}
|
|
101
|
-
console.log(chalk_1.default.gray('═'.repeat(80)));
|
|
102
|
-
// ── STEP 3: BFS traversal ────────────────────────────────────────────────
|
|
103
|
-
const maxDepth = options.directOnly ? 1 : Math.max(1, parseInt(options.depth, 10) || 10);
|
|
104
|
-
const traversal = (0, traversal_1.traverseBlastRadius)(lineageGraph, targetTableName, maxDepth);
|
|
105
|
-
// ── STEP 4: Apply display filter ─────────────────────────────────────────
|
|
106
|
-
// Show a node if it has a source file OR is a non-deleted local node.
|
|
107
|
-
// Suppress SDK-managed child records that have no user-authored source.
|
|
108
|
-
const isDisplayable = (tn) => {
|
|
109
|
-
if (!options.includeDeleted && tn.node.action === 'DELETE')
|
|
110
|
-
return false;
|
|
111
|
-
if (tn.node.source.file)
|
|
112
|
-
return true;
|
|
113
|
-
// Suppress isChild records with no source file (intermediate composition nodes)
|
|
114
|
-
if (registry.isChild(tn.node.table))
|
|
115
|
-
return false;
|
|
116
|
-
return true;
|
|
117
|
-
};
|
|
118
|
-
const displayed = traversal.hops.filter(isDisplayable);
|
|
119
|
-
const directImpact = displayed.filter(tn => tn.hop === 1);
|
|
120
|
-
const transitiveImpact = displayed.filter(tn => tn.hop > 1);
|
|
121
|
-
if (displayed.length === 0) {
|
|
122
|
-
console.log(chalk_1.default.green('\n✅ Zero dependencies detected in local project.\n'));
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
// ── STEP 5: Output ───────────────────────────────────────────────────────
|
|
126
|
-
switch (options.format) {
|
|
127
|
-
case 'json': {
|
|
128
|
-
const output = displayed.map(tn => ({
|
|
129
|
-
artifact: tn.node.name,
|
|
130
|
-
type: registry.getDisplayGroupName(tn.node.table).toLowerCase(),
|
|
131
|
-
file: tn.node.source.file,
|
|
132
|
-
hop: tn.hop,
|
|
133
|
-
path: tn.path,
|
|
134
|
-
via: tn.viaEdge.via ?? tn.viaEdge.type,
|
|
135
|
-
edgeType: tn.viaEdge.type,
|
|
136
|
-
state: tn.node.action,
|
|
137
|
-
}));
|
|
138
|
-
console.log(JSON.stringify(output, null, 2));
|
|
139
|
-
break;
|
|
140
|
-
}
|
|
141
|
-
case 'csv': {
|
|
142
|
-
console.log('Artifact,Type,File,Hop,Via,State,Path');
|
|
143
|
-
displayed.forEach(tn => {
|
|
144
|
-
const via = tn.viaEdge.via ?? tn.viaEdge.type;
|
|
145
|
-
const pathStr = tn.path.join(' > ');
|
|
146
|
-
console.log(`"${tn.node.name}","${registry.getDisplayGroupName(tn.node.table).toLowerCase()}","${tn.node.source.file}",${tn.hop},"${via}","${tn.node.action}","${pathStr}"`);
|
|
147
|
-
});
|
|
148
|
-
break;
|
|
149
|
-
}
|
|
150
|
-
case 'table':
|
|
151
|
-
default: {
|
|
152
|
-
// ── Direct Impact section ────────────────────────────────────────────
|
|
153
|
-
if (directImpact.length > 0) {
|
|
154
|
-
console.log(chalk_1.default.bold('\n⚡ DIRECT IMPACT') + chalk_1.default.gray(' (Hop 1)'));
|
|
155
|
-
const directTable = new cli_table3_1.default({
|
|
156
|
-
head: [
|
|
157
|
-
chalk_1.default.cyan('Artifact'),
|
|
158
|
-
chalk_1.default.cyan('Type'),
|
|
159
|
-
chalk_1.default.cyan('File'),
|
|
160
|
-
chalk_1.default.cyan('Via'),
|
|
161
|
-
chalk_1.default.cyan('State'),
|
|
162
|
-
],
|
|
163
|
-
wordWrap: true,
|
|
164
|
-
style: { head: [], border: ['gray'] },
|
|
165
|
-
});
|
|
166
|
-
directImpact.forEach(tn => {
|
|
167
|
-
const via = formatVia(tn);
|
|
168
|
-
const stateColor = tn.node.action === 'DELETE' ? chalk_1.default.red : chalk_1.default.green;
|
|
169
|
-
directTable.push([
|
|
170
|
-
chalk_1.default.white(tn.node.name),
|
|
171
|
-
chalk_1.default.gray(registry.getDisplayGroupName(tn.node.table).toLowerCase()),
|
|
172
|
-
chalk_1.default.gray(tn.node.source.file || '—'),
|
|
173
|
-
chalk_1.default.gray(via),
|
|
174
|
-
stateColor(tn.node.action === 'DELETE' ? 'DELETE' : 'ACTIVE'),
|
|
175
|
-
]);
|
|
176
|
-
});
|
|
177
|
-
console.log(directTable.toString());
|
|
178
|
-
}
|
|
179
|
-
// ── Transitive Impact section ────────────────────────────────────────
|
|
180
|
-
if (transitiveImpact.length > 0 && !options.directOnly) {
|
|
181
|
-
console.log(chalk_1.default.bold('\n🌊 TRANSITIVE IMPACT') + chalk_1.default.gray(' (Hop 2+)'));
|
|
182
|
-
const transitiveHead = [
|
|
183
|
-
chalk_1.default.cyan('Artifact'),
|
|
184
|
-
chalk_1.default.cyan('Type'),
|
|
185
|
-
chalk_1.default.cyan('File'),
|
|
186
|
-
chalk_1.default.cyan('Hop'),
|
|
187
|
-
chalk_1.default.cyan('State'),
|
|
188
|
-
];
|
|
189
|
-
if (options.showPaths) {
|
|
190
|
-
transitiveHead.push(chalk_1.default.cyan('Path'));
|
|
191
|
-
}
|
|
192
|
-
const transitiveTable = new cli_table3_1.default({
|
|
193
|
-
head: transitiveHead,
|
|
194
|
-
wordWrap: true,
|
|
195
|
-
style: { head: [], border: ['gray'] },
|
|
196
|
-
});
|
|
197
|
-
transitiveImpact.forEach(tn => {
|
|
198
|
-
const stateColor = tn.node.action === 'DELETE' ? chalk_1.default.red : chalk_1.default.green;
|
|
199
|
-
const row = [
|
|
200
|
-
chalk_1.default.white(tn.node.name),
|
|
201
|
-
chalk_1.default.gray(registry.getDisplayGroupName(tn.node.table).toLowerCase()),
|
|
202
|
-
chalk_1.default.gray(tn.node.source.file || '—'),
|
|
203
|
-
chalk_1.default.yellow(String(tn.hop)),
|
|
204
|
-
stateColor(tn.node.action === 'DELETE' ? 'DELETE' : 'ACTIVE'),
|
|
205
|
-
];
|
|
206
|
-
if (options.showPaths) {
|
|
207
|
-
row.push(chalk_1.default.gray(tn.path.join(' → ')));
|
|
208
|
-
}
|
|
209
|
-
transitiveTable.push(row);
|
|
210
|
-
});
|
|
211
|
-
console.log(transitiveTable.toString());
|
|
212
|
-
}
|
|
213
|
-
// ── Impact Summary ───────────────────────────────────────────────────
|
|
214
|
-
console.log(chalk_1.default.yellow('\n📊 IMPACT SUMMARY'));
|
|
215
|
-
const totalCount = displayed.length;
|
|
216
|
-
const riskLevel = totalCount < 3 ? 'LOW' : totalCount < 8 ? 'MEDIUM' : 'HIGH';
|
|
217
|
-
const riskColor = riskLevel === 'LOW' ? chalk_1.default.green
|
|
218
|
-
: riskLevel === 'MEDIUM' ? chalk_1.default.yellow
|
|
219
|
-
: chalk_1.default.red;
|
|
220
|
-
console.log(chalk_1.default.gray('Total Impacted: ') + chalk_1.default.cyan(totalCount) +
|
|
221
|
-
chalk_1.default.gray(` (${directImpact.length} direct, ${transitiveImpact.length} transitive)`));
|
|
222
|
-
console.log(chalk_1.default.gray('Max Depth Reached: ') + chalk_1.default.cyan(traversal.maxHopReached));
|
|
223
|
-
console.log(chalk_1.default.gray('Risk Level: ') + riskColor.bold(riskLevel));
|
|
224
|
-
console.log(chalk_1.default.gray('─'.repeat(80)) + '\n');
|
|
225
|
-
if (riskLevel === 'HIGH') {
|
|
226
|
-
console.log(chalk_1.default.yellow('⚠️ High impact — review carefully before deploying'));
|
|
227
|
-
}
|
|
228
|
-
else if (riskLevel === 'MEDIUM') {
|
|
229
|
-
console.log(chalk_1.default.yellow('💡 Moderate impact'));
|
|
230
|
-
}
|
|
231
|
-
break;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
catch (error) {
|
|
236
|
-
console.error(chalk_1.default.red('\n❌ Error:'), error.message);
|
|
237
|
-
process.exit(1);
|
|
238
|
-
}
|
|
239
|
-
});
|
|
240
|
-
/** Format the "Via" column label from a traversal node's edge. */
|
|
241
|
-
function formatVia(tn) {
|
|
242
|
-
const edge = tn.viaEdge;
|
|
243
|
-
if (edge.type === 'schema_relationship') {
|
|
244
|
-
return `${edge.via ?? 'ref'} (ref field)`;
|
|
245
|
-
}
|
|
246
|
-
return edge.via ?? edge.type;
|
|
247
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
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.extractCommand = void 0;
|
|
7
|
-
const commander_1 = require("commander");
|
|
8
|
-
const path_1 = __importDefault(require("path"));
|
|
9
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
-
const ora_1 = __importDefault(require("ora"));
|
|
11
|
-
const detector_1 = require("../../sdk/detector");
|
|
12
|
-
const loader_1 = require("../../sdk/loader");
|
|
13
|
-
const extractor_1 = require("../../extractor");
|
|
14
|
-
const writer_1 = require("../../output/writer");
|
|
15
|
-
const display_1 = require("../utils/display");
|
|
16
|
-
/**
|
|
17
|
-
* The Extract Command: Orchestrates the transformation of a ServiceNow Fluent
|
|
18
|
-
* project into a formal directed graph.
|
|
19
|
-
*/
|
|
20
|
-
exports.extractCommand = new commander_1.Command('analyze')
|
|
21
|
-
.description('Analyze and extract dependency graph from the current project')
|
|
22
|
-
.option('-o, --output <path>', 'Output file path', 'fluent-graph.json')
|
|
23
|
-
.option('-q, --quiet', 'Suppress console output')
|
|
24
|
-
.option('--no-display', 'Skip visual display')
|
|
25
|
-
.option('--no-banner', 'Skip banner (inherited)')
|
|
26
|
-
.action(async (options) => {
|
|
27
|
-
let spinner;
|
|
28
|
-
try {
|
|
29
|
-
const currentWorkingDir = process.cwd();
|
|
30
|
-
// Step 1: Detect ServiceNow SDK Environment
|
|
31
|
-
if (!options.quiet) {
|
|
32
|
-
spinner = (0, ora_1.default)('Detecting ServiceNow SDK...').start();
|
|
33
|
-
}
|
|
34
|
-
const sdkDetectionResult = await (0, detector_1.detectSDK)(currentWorkingDir);
|
|
35
|
-
if (!sdkDetectionResult.found) {
|
|
36
|
-
if (spinner)
|
|
37
|
-
spinner.fail('SDK not found');
|
|
38
|
-
console.error(chalk_1.default.red('\n❌ Error:'), sdkDetectionResult.error);
|
|
39
|
-
console.log(chalk_1.default.yellow('\n💡 Make sure you\'re in a ServiceNow Fluent project directory'));
|
|
40
|
-
process.exit(1);
|
|
41
|
-
}
|
|
42
|
-
if (spinner) {
|
|
43
|
-
spinner.succeed(`✅ SDK detected at ${chalk_1.default.gray(sdkDetectionResult.sdkPath)}`);
|
|
44
|
-
}
|
|
45
|
-
// Step 2: Dynamically Load Workspace SDK
|
|
46
|
-
if (!options.quiet) {
|
|
47
|
-
spinner = (0, ora_1.default)('Loading workspace SDK...').start();
|
|
48
|
-
}
|
|
49
|
-
const workspaceSDK = (0, loader_1.loadWorkspaceSDK)(sdkDetectionResult.sdkPath);
|
|
50
|
-
if (spinner) {
|
|
51
|
-
spinner.succeed('Workspace SDK loaded');
|
|
52
|
-
}
|
|
53
|
-
// Step 3: Instantiate the Project Proxy
|
|
54
|
-
if (!options.quiet) {
|
|
55
|
-
spinner = (0, ora_1.default)('Loading project configuration...').start();
|
|
56
|
-
}
|
|
57
|
-
const projectInstance = new workspaceSDK.sdkapi.Project({ rootDir: currentWorkingDir });
|
|
58
|
-
if (spinner) {
|
|
59
|
-
spinner.succeed('Project loaded');
|
|
60
|
-
}
|
|
61
|
-
// Step 4: Extract Lineage Graph
|
|
62
|
-
if (!options.quiet) {
|
|
63
|
-
spinner = (0, ora_1.default)('Building dependency graph...').start();
|
|
64
|
-
}
|
|
65
|
-
const lineageGraph = await (0, extractor_1.extractLineageGraph)(projectInstance, workspaceSDK.registry);
|
|
66
|
-
if (spinner) {
|
|
67
|
-
spinner.succeed(`Graph built: ${chalk_1.default.cyan(lineageGraph.nodes.length)} nodes, ${chalk_1.default.cyan(lineageGraph.edges.length)} edges`);
|
|
68
|
-
}
|
|
69
|
-
// Step 5: Persist the Graph
|
|
70
|
-
const outputFilePath = path_1.default.isAbsolute(options.output)
|
|
71
|
-
? options.output
|
|
72
|
-
: path_1.default.join(currentWorkingDir, options.output);
|
|
73
|
-
(0, writer_1.writeGraph)(lineageGraph, outputFilePath);
|
|
74
|
-
if (!options.quiet) {
|
|
75
|
-
console.log(chalk_1.default.green('\n✅ Analysis complete'));
|
|
76
|
-
console.log(chalk_1.default.gray(' Location: ') + chalk_1.default.white(outputFilePath));
|
|
77
|
-
console.log(chalk_1.default.gray(' Complexity: ') + chalk_1.default.cyan((lineageGraph.edges.length / lineageGraph.nodes.length).toFixed(2)) + chalk_1.default.gray(' edges/node'));
|
|
78
|
-
console.log(chalk_1.default.gray(' Generated at: ') + chalk_1.default.gray(new Date().toLocaleTimeString()));
|
|
79
|
-
}
|
|
80
|
-
// Step 6: Visual Lineage Map
|
|
81
|
-
if (options.display && !options.quiet) {
|
|
82
|
-
(0, display_1.displayLineageMap)(lineageGraph, workspaceSDK.registry);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
catch (error) {
|
|
86
|
-
if (error instanceof Error) {
|
|
87
|
-
console.error(chalk_1.default.red('\n❌ Error:'), error.message);
|
|
88
|
-
if (process.env.DEBUG) {
|
|
89
|
-
console.error(chalk_1.default.gray('\nStack trace:'));
|
|
90
|
-
console.error(error.stack);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
console.error(chalk_1.default.red('\n❌ Unknown error occurred'));
|
|
95
|
-
console.error(error);
|
|
96
|
-
}
|
|
97
|
-
process.exit(1);
|
|
98
|
-
}
|
|
99
|
-
});
|
package/dist/cli/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/cli/utils/banner.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
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.displayBanner = displayBanner;
|
|
7
|
-
exports.displayVersion = displayVersion;
|
|
8
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
-
function displayBanner() {
|
|
10
|
-
const banner = `
|
|
11
|
-
███████╗██╗ ██╗ ██╗███████╗███╗ ██╗████████╗ ██████╗ ██████╗ █████╗ ██████╗ ██╗ ██╗
|
|
12
|
-
██╔════╝██║ ██║ ██║██╔════╝████╗ ██║╚══██╔══╝██╔════╝ ██╔══██╗██╔══██╗██╔══██╗██║ ██║
|
|
13
|
-
█████╗ ██║ ██║ ██║█████╗ ██╔██╗ ██║ ██║ ██║ ███╗██████╔╝███████║██████╔╝███████║
|
|
14
|
-
██╔══╝ ██║ ██║ ██║██╔══╝ ██║╚██╗██║ ██║ ██║ ██║██╔══██╗██╔══██║██╔════╝██╔══██║
|
|
15
|
-
██║ ███████╗╚██████╔╝███████╗██║ ╚████║ ██║ ╚██████╔╝██║ ██║██║ ██║██║ ██║ ██║
|
|
16
|
-
╚═╝ ╚══════╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝
|
|
17
|
-
`;
|
|
18
|
-
console.log(chalk_1.default.cyan(banner));
|
|
19
|
-
console.log(chalk_1.default.gray(' Dependency visibility and impact analysis for ServiceNow Fluent SDK projects'));
|
|
20
|
-
console.log(chalk_1.default.gray(' ────────────────────────────────────────────────────────────────────────────\n'));
|
|
21
|
-
}
|
|
22
|
-
function displayVersion() {
|
|
23
|
-
console.log(chalk_1.default.cyan.bold('Fluent Graph') + chalk_1.default.gray(' v0.1.0'));
|
|
24
|
-
console.log(chalk_1.default.gray('Dependency visibility and impact analysis for ServiceNow Fluent SDK projects\n'));
|
|
25
|
-
}
|
|
@@ -1,137 +0,0 @@
|
|
|
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.displayLineageMap = displayLineageMap;
|
|
7
|
-
const path_1 = __importDefault(require("path"));
|
|
8
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
-
const cli_table3_1 = __importDefault(require("cli-table3"));
|
|
10
|
-
function displayLineageMap(graph, registry) {
|
|
11
|
-
console.log(chalk_1.default.yellow.bold('\n📊 LINEAGE MAP'));
|
|
12
|
-
console.log(chalk_1.default.gray('═'.repeat(80)));
|
|
13
|
-
// ===== DYNAMIC PLUGIN GROUPS =====
|
|
14
|
-
const pluginGroups = new Map();
|
|
15
|
-
for (const node of graph.nodes) {
|
|
16
|
-
if (node.action === 'DELETE')
|
|
17
|
-
continue;
|
|
18
|
-
const groupName = registry.getDisplayGroupName(node.table);
|
|
19
|
-
if (!pluginGroups.has(groupName)) {
|
|
20
|
-
pluginGroups.set(groupName, []);
|
|
21
|
-
}
|
|
22
|
-
pluginGroups.get(groupName).push(node);
|
|
23
|
-
}
|
|
24
|
-
for (const [groupName, nodes] of pluginGroups) {
|
|
25
|
-
// Only show primary (non-child) nodes in the section
|
|
26
|
-
const primaryNodes = nodes.filter(n => !registry.isChild(n.table) && n.source.file);
|
|
27
|
-
if (primaryNodes.length === 0)
|
|
28
|
-
continue;
|
|
29
|
-
console.log(chalk_1.default.cyan.bold(`\n${groupName}`));
|
|
30
|
-
const isBusinessRuleGroup = primaryNodes.some(n => registry.getPluginName(n.table) === 'BusinessRulePlugin');
|
|
31
|
-
if (isBusinessRuleGroup) {
|
|
32
|
-
const table = new cli_table3_1.default({
|
|
33
|
-
head: [
|
|
34
|
-
chalk_1.default.cyan('Name'),
|
|
35
|
-
chalk_1.default.cyan('When'),
|
|
36
|
-
chalk_1.default.cyan('Table'),
|
|
37
|
-
chalk_1.default.cyan('On'),
|
|
38
|
-
chalk_1.default.cyan('Order'),
|
|
39
|
-
chalk_1.default.cyan('Status'),
|
|
40
|
-
chalk_1.default.cyan('File'),
|
|
41
|
-
],
|
|
42
|
-
colWidths: [32, 8, 28, 16, 7, 8, 28],
|
|
43
|
-
wordWrap: true,
|
|
44
|
-
style: { head: [], border: ['gray'] },
|
|
45
|
-
});
|
|
46
|
-
primaryNodes.forEach(node => {
|
|
47
|
-
const statusLabel = node.action === 'DELETE' ? chalk_1.default.red('DELETE') : chalk_1.default.green('ACTIVE');
|
|
48
|
-
const when = node.attributes.when || '';
|
|
49
|
-
const depEdge = graph.edges.find(e => e.from === node.id && e.type === 'dependency' && e.targetTable);
|
|
50
|
-
const targetTable = depEdge?.targetTable || '';
|
|
51
|
-
const ops = [];
|
|
52
|
-
if (node.attributes.action_insert)
|
|
53
|
-
ops.push('Insert');
|
|
54
|
-
if (node.attributes.action_update)
|
|
55
|
-
ops.push('Update');
|
|
56
|
-
if (node.attributes.action_delete)
|
|
57
|
-
ops.push('Delete');
|
|
58
|
-
if (node.attributes.action_query)
|
|
59
|
-
ops.push('Query');
|
|
60
|
-
table.push([
|
|
61
|
-
chalk_1.default.white(node.name),
|
|
62
|
-
chalk_1.default.yellow(when),
|
|
63
|
-
chalk_1.default.magenta(targetTable),
|
|
64
|
-
ops.length ? ops.join(', ') : chalk_1.default.gray('—'),
|
|
65
|
-
String(node.attributes.order ?? ''),
|
|
66
|
-
statusLabel,
|
|
67
|
-
chalk_1.default.gray(path_1.default.basename(node.source.file)),
|
|
68
|
-
]);
|
|
69
|
-
});
|
|
70
|
-
console.log(table.toString());
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
const table = new cli_table3_1.default({
|
|
74
|
-
head: [
|
|
75
|
-
chalk_1.default.cyan('Name'),
|
|
76
|
-
chalk_1.default.cyan('Status'),
|
|
77
|
-
chalk_1.default.cyan('Source File')
|
|
78
|
-
],
|
|
79
|
-
wordWrap: true,
|
|
80
|
-
style: { head: [], border: ['gray'] }
|
|
81
|
-
});
|
|
82
|
-
primaryNodes.forEach(node => {
|
|
83
|
-
const statusLabel = node.action === 'DELETE' ? chalk_1.default.red('DELETE') : chalk_1.default.green('ACTIVE');
|
|
84
|
-
table.push([
|
|
85
|
-
chalk_1.default.white(node.name),
|
|
86
|
-
statusLabel,
|
|
87
|
-
chalk_1.default.gray(node.source.file)
|
|
88
|
-
]);
|
|
89
|
-
});
|
|
90
|
-
console.log(table.toString());
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
// ===== SCHEMA RELATIONSHIPS =====
|
|
94
|
-
const schemaEdges = graph.edges.filter(edge => edge.type === 'schema_relationship');
|
|
95
|
-
if (schemaEdges.length > 0) {
|
|
96
|
-
console.log(chalk_1.default.cyan.bold('\n🔗 SCHEMA RELATIONSHIPS (Reference Fields)'));
|
|
97
|
-
const table = new cli_table3_1.default({
|
|
98
|
-
head: [
|
|
99
|
-
chalk_1.default.cyan('Field'),
|
|
100
|
-
chalk_1.default.cyan('From Table'),
|
|
101
|
-
chalk_1.default.cyan('Target Table')
|
|
102
|
-
],
|
|
103
|
-
wordWrap: true,
|
|
104
|
-
style: { head: [], border: ['gray'] }
|
|
105
|
-
});
|
|
106
|
-
schemaEdges.forEach(edge => {
|
|
107
|
-
const fromNode = graph.nodes.find(node => node.id === edge.from);
|
|
108
|
-
const fieldName = fromNode?.attributes?.element || edge.via || 'unknown';
|
|
109
|
-
const fromTableName = fromNode?.attributes?.name || 'unknown';
|
|
110
|
-
const targetTableName = edge.to.startsWith('platform:')
|
|
111
|
-
? edge.to.split(':')[1]
|
|
112
|
-
: graph.nodes.find(node => node.id === edge.to)?.name || edge.to;
|
|
113
|
-
table.push([chalk_1.default.white(fieldName), chalk_1.default.gray(fromTableName), chalk_1.default.magenta(targetTableName)]);
|
|
114
|
-
});
|
|
115
|
-
console.log(table.toString());
|
|
116
|
-
}
|
|
117
|
-
// ===== PROJECT INSIGHTS =====
|
|
118
|
-
console.log(chalk_1.default.cyan.bold('\n🧠 PROJECT INSIGHTS'));
|
|
119
|
-
const platformDeps = graph.edges.filter(edge => edge.to.startsWith('platform:')).length;
|
|
120
|
-
const internalDeps = graph.edges.filter(edge => !edge.to.startsWith('platform:')).length;
|
|
121
|
-
const userArtifacts = graph.nodes.filter(node => node.action !== 'DELETE' &&
|
|
122
|
-
node.source.file &&
|
|
123
|
-
!node.id.startsWith('platform:') &&
|
|
124
|
-
!registry.isChild(node.table));
|
|
125
|
-
console.log(` • Total User Artifacts: ${chalk_1.default.cyan(userArtifacts.length)}`);
|
|
126
|
-
const countsByType = userArtifacts.reduce((acc, node) => {
|
|
127
|
-
const groupName = registry.getDisplayGroupName(node.table);
|
|
128
|
-
acc[groupName] = (acc[groupName] || 0) + 1;
|
|
129
|
-
return acc;
|
|
130
|
-
}, {});
|
|
131
|
-
console.log(` • User Artifacts Breakdown: ${JSON.stringify(countsByType, null, 2)}`);
|
|
132
|
-
console.log(` • Platform Dependencies: ${chalk_1.default.magenta(platformDeps)}`);
|
|
133
|
-
console.log(` • Internal References: ${chalk_1.default.white(internalDeps)}`);
|
|
134
|
-
console.log(` • Total Artifacts: ${chalk_1.default.cyan(graph.nodes.length)}`);
|
|
135
|
-
console.log(` • Total Relationships: ${chalk_1.default.cyan(graph.edges.length)}`);
|
|
136
|
-
console.log(chalk_1.default.gray('═'.repeat(80)) + '\n');
|
|
137
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { ArtifactEdge, ArtifactNode } from '../types/graphs';
|
|
2
|
-
import type { ArtifactTypeRegistry } from '../sdk/registry';
|
|
3
|
-
/**
|
|
4
|
-
* UNIVERSAL EDGE EXTRACTOR
|
|
5
|
-
*
|
|
6
|
-
* Strategy:
|
|
7
|
-
* 1. Property-based schema relationship detection (internal_type=reference)
|
|
8
|
-
* 2. Logic attachment via table/collection property
|
|
9
|
-
* 3. ACL name parsing — detected via registry plugin name
|
|
10
|
-
* 4. Universal RecordId scan across all properties
|
|
11
|
-
*/
|
|
12
|
-
export declare function extractReferenceEdges(records: any, allNodes: ArtifactNode[], registry: ArtifactTypeRegistry): ArtifactEdge[];
|
|
13
|
-
export declare function extractCompositionEdges(records: any): ArtifactEdge[];
|