@dependency-maritime/cli 0.1.0-beta.4 → 0.1.0-beta.5
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/README.md +8 -1
- package/dist/cli/cli/graph/render-dot.d.ts +17 -1
- package/dist/cli/cli/index.d.ts +2 -1
- package/dist/cli/index.d.ts +2 -1
- package/dist/cli/index.js +61 -10
- package/dist/cli/main.js +57 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -134,9 +134,16 @@ With Graphviz `dot` installed, render without performing a second analysis:
|
|
|
134
134
|
|
|
135
135
|
```bash
|
|
136
136
|
maritime graph --input .maritime --output docs/images/dependency-graph.svg
|
|
137
|
+
maritime graph --input .maritime --output docs/images/architecture.svg \
|
|
138
|
+
--external-packages none --folder-grouping nested --edge-labels none
|
|
137
139
|
```
|
|
138
140
|
|
|
139
|
-
`.maritime/dependency-graph.json` is canonical evidence; SVG and DOT outputs are derived
|
|
141
|
+
`.maritime/dependency-graph.json` is canonical evidence; SVG (and transient DOT) outputs are derived
|
|
140
142
|
presentations. The composite Action's reproducible committed-SVG contract is limited to its pinned
|
|
141
143
|
Ubuntu Graphviz path; other runners must provide and pin `dot` themselves because layout can vary
|
|
142
144
|
between Graphviz versions.
|
|
145
|
+
|
|
146
|
+
The rendering flags are presentation-only: `--external-packages` accepts `none`, `summary`, or
|
|
147
|
+
`direct`; `--folder-grouping` accepts `none`, `top-level`, or `nested`; and `--edge-labels` accepts
|
|
148
|
+
`none` or `types`. Defaults (`direct`, `nested`, `types`) preserve existing output. The canonical
|
|
149
|
+
`.maritime/dependency-graph.json` stays complete and is never rescanned or rewritten.
|
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import type { MaritimeCruiseResult } from '../../schema/dependency-cruiser';
|
|
2
|
+
export declare const EXTERNAL_PACKAGE_MODES: readonly ["none", "summary", "direct"];
|
|
3
|
+
export declare const FOLDER_GROUPING_MODES: readonly ["none", "top-level", "nested"];
|
|
4
|
+
export declare const EDGE_LABEL_MODES: readonly ["none", "types"];
|
|
5
|
+
export type ExternalPackagesMode = typeof EXTERNAL_PACKAGE_MODES[number];
|
|
6
|
+
export type FolderGroupingMode = typeof FOLDER_GROUPING_MODES[number];
|
|
7
|
+
export type EdgeLabelsMode = typeof EDGE_LABEL_MODES[number];
|
|
8
|
+
export type GraphPresentationOptions = {
|
|
9
|
+
externalPackages?: ExternalPackagesMode;
|
|
10
|
+
folderGrouping?: FolderGroupingMode;
|
|
11
|
+
edgeLabels?: EdgeLabelsMode;
|
|
12
|
+
};
|
|
13
|
+
export declare const DEFAULT_GRAPH_PRESENTATION: {
|
|
14
|
+
readonly externalPackages: "direct";
|
|
15
|
+
readonly folderGrouping: "nested";
|
|
16
|
+
readonly edgeLabels: "types";
|
|
17
|
+
};
|
|
2
18
|
declare function externalPackageName(value: string): string | undefined;
|
|
3
19
|
/** Pure, deterministic conversion of a validated dependency-cruiser result to Graphviz DOT. */
|
|
4
|
-
export declare function renderDependencyGraphToDot(graph: MaritimeCruiseResult): string;
|
|
20
|
+
export declare function renderDependencyGraphToDot(graph: MaritimeCruiseResult, options?: GraphPresentationOptions): string;
|
|
5
21
|
export declare function inferGraphvizFormat(outputPath: string): 'svg' | 'dot';
|
|
6
22
|
export { externalPackageName };
|
package/dist/cli/cli/index.d.ts
CHANGED
|
@@ -3,7 +3,8 @@ import { runValidateCommand } from './commands/validate';
|
|
|
3
3
|
import { runGraphCommand } from './commands/graph';
|
|
4
4
|
import { validateArtifacts } from './validate/validate';
|
|
5
5
|
export { runAnalyzeCommand, runValidateCommand, runGraphCommand, validateArtifacts };
|
|
6
|
-
export { renderDependencyGraphToDot, inferGraphvizFormat } from './graph/render-dot';
|
|
6
|
+
export { renderDependencyGraphToDot, inferGraphvizFormat, DEFAULT_GRAPH_PRESENTATION, EXTERNAL_PACKAGE_MODES, FOLDER_GROUPING_MODES, EDGE_LABEL_MODES } from './graph/render-dot';
|
|
7
|
+
export type { GraphPresentationOptions, ExternalPackagesMode, FolderGroupingMode, EdgeLabelsMode } from './graph/render-dot';
|
|
7
8
|
export { renderDotWithGraphviz, normalizeGraphvizSvg } from './graph/render-graphviz';
|
|
8
9
|
export * from '../schema/manifest';
|
|
9
10
|
export { readDependencyGraph, resolveDepcruiseConfig, generateDependencyGraph, runEslintComplexityScan, countLinesOfCode, writeOutputFiles } from './analyze/adapters';
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -3,7 +3,8 @@ import { runValidateCommand } from './commands/validate';
|
|
|
3
3
|
import { runGraphCommand } from './commands/graph';
|
|
4
4
|
import { validateArtifacts } from './validate/validate';
|
|
5
5
|
export { runAnalyzeCommand, runValidateCommand, runGraphCommand, validateArtifacts };
|
|
6
|
-
export { renderDependencyGraphToDot, inferGraphvizFormat } from './graph/render-dot';
|
|
6
|
+
export { renderDependencyGraphToDot, inferGraphvizFormat, DEFAULT_GRAPH_PRESENTATION, EXTERNAL_PACKAGE_MODES, FOLDER_GROUPING_MODES, EDGE_LABEL_MODES } from './graph/render-dot';
|
|
7
|
+
export type { GraphPresentationOptions, ExternalPackagesMode, FolderGroupingMode, EdgeLabelsMode } from './graph/render-dot';
|
|
7
8
|
export { renderDotWithGraphviz, normalizeGraphvizSvg } from './graph/render-graphviz';
|
|
8
9
|
export * from '../schema/manifest';
|
|
9
10
|
export { readDependencyGraph, resolveDepcruiseConfig, generateDependencyGraph, runEslintComplexityScan, countLinesOfCode, writeOutputFiles } from './analyze/adapters';
|
package/dist/cli/index.js
CHANGED
|
@@ -1132,6 +1132,14 @@ import * as path9 from "node:path";
|
|
|
1132
1132
|
|
|
1133
1133
|
// src/cli/graph/render-dot.ts
|
|
1134
1134
|
import * as path7 from "node:path";
|
|
1135
|
+
var EXTERNAL_PACKAGE_MODES = ["none", "summary", "direct"];
|
|
1136
|
+
var FOLDER_GROUPING_MODES = ["none", "top-level", "nested"];
|
|
1137
|
+
var EDGE_LABEL_MODES = ["none", "types"];
|
|
1138
|
+
var DEFAULT_GRAPH_PRESENTATION = {
|
|
1139
|
+
externalPackages: "direct",
|
|
1140
|
+
folderGrouping: "nested",
|
|
1141
|
+
edgeLabels: "types"
|
|
1142
|
+
};
|
|
1135
1143
|
var dotQuote = (value) => JSON.stringify(value);
|
|
1136
1144
|
var normalizedPath = (value) => value.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
1137
1145
|
function externalPackageName(value) {
|
|
@@ -1178,10 +1186,10 @@ function renderDirectory(directory, segments, indent) {
|
|
|
1178
1186
|
}
|
|
1179
1187
|
return lines;
|
|
1180
1188
|
}
|
|
1181
|
-
function edgeAttributes(dependency) {
|
|
1189
|
+
function edgeAttributes(dependency, edgeLabels) {
|
|
1182
1190
|
const attributes = [];
|
|
1183
1191
|
const dependencyTypes = [...dependency.dependencyTypes].sort();
|
|
1184
|
-
if (dependencyTypes.length > 0) attributes.push(`label=${dotQuote(dependencyTypes.join(", "))}`);
|
|
1192
|
+
if (edgeLabels === "types" && dependencyTypes.length > 0) attributes.push(`label=${dotQuote(dependencyTypes.join(", "))}`);
|
|
1185
1193
|
if (dependency.typeOnly || dependency.preCompilationOnly) attributes.push('style="dashed"');
|
|
1186
1194
|
if (dependency.circular) attributes.push('color="#d97706"', 'penwidth="2"');
|
|
1187
1195
|
if (!dependency.valid) {
|
|
@@ -1190,7 +1198,12 @@ function edgeAttributes(dependency) {
|
|
|
1190
1198
|
}
|
|
1191
1199
|
return attributes.length > 0 ? ` [${attributes.join(", ")}]` : "";
|
|
1192
1200
|
}
|
|
1193
|
-
function renderDependencyGraphToDot(graph) {
|
|
1201
|
+
function renderDependencyGraphToDot(graph, options = {}) {
|
|
1202
|
+
const presentation = {
|
|
1203
|
+
externalPackages: options.externalPackages ?? DEFAULT_GRAPH_PRESENTATION.externalPackages,
|
|
1204
|
+
folderGrouping: options.folderGrouping ?? DEFAULT_GRAPH_PRESENTATION.folderGrouping,
|
|
1205
|
+
edgeLabels: options.edgeLabels ?? DEFAULT_GRAPH_PRESENTATION.edgeLabels
|
|
1206
|
+
};
|
|
1194
1207
|
const root = { directories: /* @__PURE__ */ new Map(), files: [] };
|
|
1195
1208
|
const localSources = /* @__PURE__ */ new Set();
|
|
1196
1209
|
const externalPackages = /* @__PURE__ */ new Set();
|
|
@@ -1198,7 +1211,17 @@ function renderDependencyGraphToDot(graph) {
|
|
|
1198
1211
|
const source = normalizedPath(module.source);
|
|
1199
1212
|
if (isExternalPath(source) || module.coreModule) continue;
|
|
1200
1213
|
localSources.add(source);
|
|
1201
|
-
addLocalModule(root, source);
|
|
1214
|
+
if (presentation.folderGrouping === "nested") addLocalModule(root, source);
|
|
1215
|
+
else if (presentation.folderGrouping === "top-level") {
|
|
1216
|
+
if (!source.includes("/")) {
|
|
1217
|
+
root.files.push({ source, name: source });
|
|
1218
|
+
continue;
|
|
1219
|
+
}
|
|
1220
|
+
const [topLevel] = source.split("/");
|
|
1221
|
+
const directory = root.directories.get(topLevel) ?? { directories: /* @__PURE__ */ new Map(), files: [] };
|
|
1222
|
+
root.directories.set(topLevel, directory);
|
|
1223
|
+
directory.files.push({ source, name: path7.posix.basename(source) });
|
|
1224
|
+
} else root.files.push({ source, name: path7.posix.basename(source) });
|
|
1202
1225
|
}
|
|
1203
1226
|
const edges = [];
|
|
1204
1227
|
for (const module of [...graph.modules].sort((a, b) => a.source.localeCompare(b.source))) {
|
|
@@ -1212,9 +1235,9 @@ function renderDependencyGraphToDot(graph) {
|
|
|
1212
1235
|
} else if (!dependency.coreModule) {
|
|
1213
1236
|
const looksExternal = isExternalPath(resolved) || dependency.dependencyTypes.includes("npm");
|
|
1214
1237
|
const packageName = looksExternal ? externalPackageName(isExternalPath(resolved) ? resolved : dependency.module) : void 0;
|
|
1215
|
-
if (packageName) {
|
|
1238
|
+
if (packageName && presentation.externalPackages !== "none") {
|
|
1216
1239
|
externalPackages.add(packageName);
|
|
1217
|
-
target = `external:${packageName}`;
|
|
1240
|
+
target = presentation.externalPackages === "summary" ? "external:boundary" : `external:${packageName}`;
|
|
1218
1241
|
}
|
|
1219
1242
|
}
|
|
1220
1243
|
if (target) edges.push({ from: `local:${source}`, to: target, dependency });
|
|
@@ -1232,7 +1255,9 @@ function renderDependencyGraphToDot(graph) {
|
|
|
1232
1255
|
' edge [fontname="Helvetica", fontsize="8"];',
|
|
1233
1256
|
...renderDirectory(root, [], " ")
|
|
1234
1257
|
];
|
|
1235
|
-
if (externalPackages.size > 0) {
|
|
1258
|
+
if (presentation.externalPackages === "summary" && externalPackages.size > 0) {
|
|
1259
|
+
lines.push(' "external:boundary" [label="External packages", shape="component", style="filled,dashed", fillcolor="#e2e8f0", color="#64748b"];');
|
|
1260
|
+
} else if (presentation.externalPackages === "direct" && externalPackages.size > 0) {
|
|
1236
1261
|
lines.push(' subgraph "cluster:external-packages" {', ' label="External packages";', ' style="dashed";', ' color="#64748b";');
|
|
1237
1262
|
for (const packageName of [...externalPackages].sort()) {
|
|
1238
1263
|
lines.push(` ${dotQuote(`external:${packageName}`)} [label=${dotQuote(packageName)}, shape="component", style="filled", fillcolor="#e2e8f0"];`);
|
|
@@ -1240,7 +1265,7 @@ function renderDependencyGraphToDot(graph) {
|
|
|
1240
1265
|
lines.push(" }");
|
|
1241
1266
|
}
|
|
1242
1267
|
for (const edge of edges) {
|
|
1243
|
-
lines.push(` ${dotQuote(edge.from)} -> ${dotQuote(edge.to)}${edgeAttributes(edge.dependency)};`);
|
|
1268
|
+
lines.push(` ${dotQuote(edge.from)} -> ${dotQuote(edge.to)}${edgeAttributes(edge.dependency, presentation.edgeLabels)};`);
|
|
1244
1269
|
}
|
|
1245
1270
|
lines.push("}", "");
|
|
1246
1271
|
return lines.join("\n");
|
|
@@ -1292,7 +1317,10 @@ async function runGraphCommand(args) {
|
|
|
1292
1317
|
input: { type: "string", short: "i" },
|
|
1293
1318
|
output: { type: "string", short: "o" },
|
|
1294
1319
|
cwd: { type: "string" },
|
|
1295
|
-
help: { type: "boolean", short: "h" }
|
|
1320
|
+
help: { type: "boolean", short: "h" },
|
|
1321
|
+
"external-packages": { type: "string" },
|
|
1322
|
+
"folder-grouping": { type: "string" },
|
|
1323
|
+
"edge-labels": { type: "string" }
|
|
1296
1324
|
} }).values;
|
|
1297
1325
|
} catch (error) {
|
|
1298
1326
|
console.error(`Error parsing arguments: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -1307,6 +1335,9 @@ Renders existing canonical graph evidence without running dependency analysis.
|
|
|
1307
1335
|
Options:
|
|
1308
1336
|
-i, --input <path> .maritime directory or dependency-graph.json (default: .maritime)
|
|
1309
1337
|
-o, --output <path> SVG or DOT output path (required)
|
|
1338
|
+
--external-packages <none|summary|direct> External package presentation (default: direct)
|
|
1339
|
+
--folder-grouping <none|top-level|nested> Local folder clustering (default: nested)
|
|
1340
|
+
--edge-labels <none|types> Dependency-type labels (default: types)
|
|
1310
1341
|
--cwd <dir> Working directory root for resolution
|
|
1311
1342
|
-h, --help Show help message
|
|
1312
1343
|
`);
|
|
@@ -1316,6 +1347,22 @@ Options:
|
|
|
1316
1347
|
console.error("Error: --output is required.");
|
|
1317
1348
|
return 2;
|
|
1318
1349
|
}
|
|
1350
|
+
const validatePolicy = (flag, value, allowed, fallback) => {
|
|
1351
|
+
if (value === void 0) return fallback;
|
|
1352
|
+
if (allowed.includes(value)) return value;
|
|
1353
|
+
throw new Error(`Invalid --${flag} value "${value}". Expected one of: ${allowed.join(", ")}.`);
|
|
1354
|
+
};
|
|
1355
|
+
let externalPackages;
|
|
1356
|
+
let folderGrouping;
|
|
1357
|
+
let edgeLabels;
|
|
1358
|
+
try {
|
|
1359
|
+
externalPackages = validatePolicy("external-packages", values["external-packages"], EXTERNAL_PACKAGE_MODES, "direct");
|
|
1360
|
+
folderGrouping = validatePolicy("folder-grouping", values["folder-grouping"], FOLDER_GROUPING_MODES, "nested");
|
|
1361
|
+
edgeLabels = validatePolicy("edge-labels", values["edge-labels"], EDGE_LABEL_MODES, "types");
|
|
1362
|
+
} catch (error) {
|
|
1363
|
+
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
1364
|
+
return 2;
|
|
1365
|
+
}
|
|
1319
1366
|
const cwd = path9.resolve(values.cwd ?? process.cwd());
|
|
1320
1367
|
const input = path9.resolve(cwd, values.input ?? ".maritime");
|
|
1321
1368
|
const output = path9.resolve(cwd, values.output);
|
|
@@ -1325,7 +1372,7 @@ Options:
|
|
|
1325
1372
|
const parsed = JSON.parse(await fs4.readFile(graphPath, "utf8"));
|
|
1326
1373
|
const result = CruiseResultSchema.safeParse(parsed);
|
|
1327
1374
|
if (!result.success) throw new Error(`Invalid Maritime dependency graph: ${result.error.message}`);
|
|
1328
|
-
const dot = renderDependencyGraphToDot(result.data);
|
|
1375
|
+
const dot = renderDependencyGraphToDot(result.data, { externalPackages, folderGrouping, edgeLabels });
|
|
1329
1376
|
const format = inferGraphvizFormat(output);
|
|
1330
1377
|
await fs4.mkdir(path9.dirname(output), { recursive: true });
|
|
1331
1378
|
if (format === "dot") await fs4.writeFile(output, dot);
|
|
@@ -1384,6 +1431,10 @@ export {
|
|
|
1384
1431
|
ArtifactManifestArtifactsSchema,
|
|
1385
1432
|
ArtifactManifestSchema,
|
|
1386
1433
|
ArtifactManifestSummarySchema,
|
|
1434
|
+
DEFAULT_GRAPH_PRESENTATION,
|
|
1435
|
+
EDGE_LABEL_MODES,
|
|
1436
|
+
EXTERNAL_PACKAGE_MODES,
|
|
1437
|
+
FOLDER_GROUPING_MODES,
|
|
1387
1438
|
MANIFEST_SCHEMA_VERSION,
|
|
1388
1439
|
ValidationError,
|
|
1389
1440
|
analyzeProject,
|
package/dist/cli/main.js
CHANGED
|
@@ -1137,6 +1137,14 @@ import * as path9 from "node:path";
|
|
|
1137
1137
|
|
|
1138
1138
|
// src/cli/graph/render-dot.ts
|
|
1139
1139
|
import * as path7 from "node:path";
|
|
1140
|
+
var EXTERNAL_PACKAGE_MODES = ["none", "summary", "direct"];
|
|
1141
|
+
var FOLDER_GROUPING_MODES = ["none", "top-level", "nested"];
|
|
1142
|
+
var EDGE_LABEL_MODES = ["none", "types"];
|
|
1143
|
+
var DEFAULT_GRAPH_PRESENTATION = {
|
|
1144
|
+
externalPackages: "direct",
|
|
1145
|
+
folderGrouping: "nested",
|
|
1146
|
+
edgeLabels: "types"
|
|
1147
|
+
};
|
|
1140
1148
|
var dotQuote = (value) => JSON.stringify(value);
|
|
1141
1149
|
var normalizedPath = (value) => value.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
1142
1150
|
function externalPackageName(value) {
|
|
@@ -1183,10 +1191,10 @@ function renderDirectory(directory, segments, indent) {
|
|
|
1183
1191
|
}
|
|
1184
1192
|
return lines;
|
|
1185
1193
|
}
|
|
1186
|
-
function edgeAttributes(dependency) {
|
|
1194
|
+
function edgeAttributes(dependency, edgeLabels) {
|
|
1187
1195
|
const attributes = [];
|
|
1188
1196
|
const dependencyTypes = [...dependency.dependencyTypes].sort();
|
|
1189
|
-
if (dependencyTypes.length > 0) attributes.push(`label=${dotQuote(dependencyTypes.join(", "))}`);
|
|
1197
|
+
if (edgeLabels === "types" && dependencyTypes.length > 0) attributes.push(`label=${dotQuote(dependencyTypes.join(", "))}`);
|
|
1190
1198
|
if (dependency.typeOnly || dependency.preCompilationOnly) attributes.push('style="dashed"');
|
|
1191
1199
|
if (dependency.circular) attributes.push('color="#d97706"', 'penwidth="2"');
|
|
1192
1200
|
if (!dependency.valid) {
|
|
@@ -1195,7 +1203,12 @@ function edgeAttributes(dependency) {
|
|
|
1195
1203
|
}
|
|
1196
1204
|
return attributes.length > 0 ? ` [${attributes.join(", ")}]` : "";
|
|
1197
1205
|
}
|
|
1198
|
-
function renderDependencyGraphToDot(graph) {
|
|
1206
|
+
function renderDependencyGraphToDot(graph, options = {}) {
|
|
1207
|
+
const presentation = {
|
|
1208
|
+
externalPackages: options.externalPackages ?? DEFAULT_GRAPH_PRESENTATION.externalPackages,
|
|
1209
|
+
folderGrouping: options.folderGrouping ?? DEFAULT_GRAPH_PRESENTATION.folderGrouping,
|
|
1210
|
+
edgeLabels: options.edgeLabels ?? DEFAULT_GRAPH_PRESENTATION.edgeLabels
|
|
1211
|
+
};
|
|
1199
1212
|
const root = { directories: /* @__PURE__ */ new Map(), files: [] };
|
|
1200
1213
|
const localSources = /* @__PURE__ */ new Set();
|
|
1201
1214
|
const externalPackages = /* @__PURE__ */ new Set();
|
|
@@ -1203,7 +1216,17 @@ function renderDependencyGraphToDot(graph) {
|
|
|
1203
1216
|
const source = normalizedPath(module.source);
|
|
1204
1217
|
if (isExternalPath(source) || module.coreModule) continue;
|
|
1205
1218
|
localSources.add(source);
|
|
1206
|
-
addLocalModule(root, source);
|
|
1219
|
+
if (presentation.folderGrouping === "nested") addLocalModule(root, source);
|
|
1220
|
+
else if (presentation.folderGrouping === "top-level") {
|
|
1221
|
+
if (!source.includes("/")) {
|
|
1222
|
+
root.files.push({ source, name: source });
|
|
1223
|
+
continue;
|
|
1224
|
+
}
|
|
1225
|
+
const [topLevel] = source.split("/");
|
|
1226
|
+
const directory = root.directories.get(topLevel) ?? { directories: /* @__PURE__ */ new Map(), files: [] };
|
|
1227
|
+
root.directories.set(topLevel, directory);
|
|
1228
|
+
directory.files.push({ source, name: path7.posix.basename(source) });
|
|
1229
|
+
} else root.files.push({ source, name: path7.posix.basename(source) });
|
|
1207
1230
|
}
|
|
1208
1231
|
const edges = [];
|
|
1209
1232
|
for (const module of [...graph.modules].sort((a, b) => a.source.localeCompare(b.source))) {
|
|
@@ -1217,9 +1240,9 @@ function renderDependencyGraphToDot(graph) {
|
|
|
1217
1240
|
} else if (!dependency.coreModule) {
|
|
1218
1241
|
const looksExternal = isExternalPath(resolved) || dependency.dependencyTypes.includes("npm");
|
|
1219
1242
|
const packageName = looksExternal ? externalPackageName(isExternalPath(resolved) ? resolved : dependency.module) : void 0;
|
|
1220
|
-
if (packageName) {
|
|
1243
|
+
if (packageName && presentation.externalPackages !== "none") {
|
|
1221
1244
|
externalPackages.add(packageName);
|
|
1222
|
-
target = `external:${packageName}`;
|
|
1245
|
+
target = presentation.externalPackages === "summary" ? "external:boundary" : `external:${packageName}`;
|
|
1223
1246
|
}
|
|
1224
1247
|
}
|
|
1225
1248
|
if (target) edges.push({ from: `local:${source}`, to: target, dependency });
|
|
@@ -1237,7 +1260,9 @@ function renderDependencyGraphToDot(graph) {
|
|
|
1237
1260
|
' edge [fontname="Helvetica", fontsize="8"];',
|
|
1238
1261
|
...renderDirectory(root, [], " ")
|
|
1239
1262
|
];
|
|
1240
|
-
if (externalPackages.size > 0) {
|
|
1263
|
+
if (presentation.externalPackages === "summary" && externalPackages.size > 0) {
|
|
1264
|
+
lines.push(' "external:boundary" [label="External packages", shape="component", style="filled,dashed", fillcolor="#e2e8f0", color="#64748b"];');
|
|
1265
|
+
} else if (presentation.externalPackages === "direct" && externalPackages.size > 0) {
|
|
1241
1266
|
lines.push(' subgraph "cluster:external-packages" {', ' label="External packages";', ' style="dashed";', ' color="#64748b";');
|
|
1242
1267
|
for (const packageName of [...externalPackages].sort()) {
|
|
1243
1268
|
lines.push(` ${dotQuote(`external:${packageName}`)} [label=${dotQuote(packageName)}, shape="component", style="filled", fillcolor="#e2e8f0"];`);
|
|
@@ -1245,7 +1270,7 @@ function renderDependencyGraphToDot(graph) {
|
|
|
1245
1270
|
lines.push(" }");
|
|
1246
1271
|
}
|
|
1247
1272
|
for (const edge of edges) {
|
|
1248
|
-
lines.push(` ${dotQuote(edge.from)} -> ${dotQuote(edge.to)}${edgeAttributes(edge.dependency)};`);
|
|
1273
|
+
lines.push(` ${dotQuote(edge.from)} -> ${dotQuote(edge.to)}${edgeAttributes(edge.dependency, presentation.edgeLabels)};`);
|
|
1249
1274
|
}
|
|
1250
1275
|
lines.push("}", "");
|
|
1251
1276
|
return lines.join("\n");
|
|
@@ -1297,7 +1322,10 @@ async function runGraphCommand(args) {
|
|
|
1297
1322
|
input: { type: "string", short: "i" },
|
|
1298
1323
|
output: { type: "string", short: "o" },
|
|
1299
1324
|
cwd: { type: "string" },
|
|
1300
|
-
help: { type: "boolean", short: "h" }
|
|
1325
|
+
help: { type: "boolean", short: "h" },
|
|
1326
|
+
"external-packages": { type: "string" },
|
|
1327
|
+
"folder-grouping": { type: "string" },
|
|
1328
|
+
"edge-labels": { type: "string" }
|
|
1301
1329
|
} }).values;
|
|
1302
1330
|
} catch (error) {
|
|
1303
1331
|
console.error(`Error parsing arguments: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -1312,6 +1340,9 @@ Renders existing canonical graph evidence without running dependency analysis.
|
|
|
1312
1340
|
Options:
|
|
1313
1341
|
-i, --input <path> .maritime directory or dependency-graph.json (default: .maritime)
|
|
1314
1342
|
-o, --output <path> SVG or DOT output path (required)
|
|
1343
|
+
--external-packages <none|summary|direct> External package presentation (default: direct)
|
|
1344
|
+
--folder-grouping <none|top-level|nested> Local folder clustering (default: nested)
|
|
1345
|
+
--edge-labels <none|types> Dependency-type labels (default: types)
|
|
1315
1346
|
--cwd <dir> Working directory root for resolution
|
|
1316
1347
|
-h, --help Show help message
|
|
1317
1348
|
`);
|
|
@@ -1321,6 +1352,22 @@ Options:
|
|
|
1321
1352
|
console.error("Error: --output is required.");
|
|
1322
1353
|
return 2;
|
|
1323
1354
|
}
|
|
1355
|
+
const validatePolicy = (flag, value, allowed, fallback) => {
|
|
1356
|
+
if (value === void 0) return fallback;
|
|
1357
|
+
if (allowed.includes(value)) return value;
|
|
1358
|
+
throw new Error(`Invalid --${flag} value "${value}". Expected one of: ${allowed.join(", ")}.`);
|
|
1359
|
+
};
|
|
1360
|
+
let externalPackages;
|
|
1361
|
+
let folderGrouping;
|
|
1362
|
+
let edgeLabels;
|
|
1363
|
+
try {
|
|
1364
|
+
externalPackages = validatePolicy("external-packages", values["external-packages"], EXTERNAL_PACKAGE_MODES, "direct");
|
|
1365
|
+
folderGrouping = validatePolicy("folder-grouping", values["folder-grouping"], FOLDER_GROUPING_MODES, "nested");
|
|
1366
|
+
edgeLabels = validatePolicy("edge-labels", values["edge-labels"], EDGE_LABEL_MODES, "types");
|
|
1367
|
+
} catch (error) {
|
|
1368
|
+
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
1369
|
+
return 2;
|
|
1370
|
+
}
|
|
1324
1371
|
const cwd = path9.resolve(values.cwd ?? process.cwd());
|
|
1325
1372
|
const input = path9.resolve(cwd, values.input ?? ".maritime");
|
|
1326
1373
|
const output = path9.resolve(cwd, values.output);
|
|
@@ -1330,7 +1377,7 @@ Options:
|
|
|
1330
1377
|
const parsed = JSON.parse(await fs4.readFile(graphPath, "utf8"));
|
|
1331
1378
|
const result = CruiseResultSchema.safeParse(parsed);
|
|
1332
1379
|
if (!result.success) throw new Error(`Invalid Maritime dependency graph: ${result.error.message}`);
|
|
1333
|
-
const dot = renderDependencyGraphToDot(result.data);
|
|
1380
|
+
const dot = renderDependencyGraphToDot(result.data, { externalPackages, folderGrouping, edgeLabels });
|
|
1334
1381
|
const format = inferGraphvizFormat(output);
|
|
1335
1382
|
await fs4.mkdir(path9.dirname(output), { recursive: true });
|
|
1336
1383
|
if (format === "dot") await fs4.writeFile(output, dot);
|