@dependency-maritime/cli 0.1.0-beta.3 → 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 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 };
@@ -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';
@@ -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 });
@@ -1223,12 +1246,18 @@ function renderDependencyGraphToDot(graph) {
1223
1246
  edges.sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to) || JSON.stringify(a.dependency).localeCompare(JSON.stringify(b.dependency)));
1224
1247
  const lines = [
1225
1248
  'digraph "dependency-graph" {',
1226
- ' graph [compound="true", rankdir="LR", fontname="Helvetica"];',
1249
+ // Graphviz 2.42 can fail init_rank when the default cluster-local ranker
1250
+ // encounters a large recursively nested directory hierarchy. newrank asks
1251
+ // dot to compute one global ranking across clusters while preserving the
1252
+ // cluster boxes and deterministic left-to-right presentation.
1253
+ ' graph [compound="true", newrank="true", rankdir="LR", fontname="Helvetica"];',
1227
1254
  ' node [fontname="Helvetica", fontsize="10"];',
1228
1255
  ' edge [fontname="Helvetica", fontsize="8"];',
1229
1256
  ...renderDirectory(root, [], " ")
1230
1257
  ];
1231
- 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) {
1232
1261
  lines.push(' subgraph "cluster:external-packages" {', ' label="External packages";', ' style="dashed";', ' color="#64748b";');
1233
1262
  for (const packageName of [...externalPackages].sort()) {
1234
1263
  lines.push(` ${dotQuote(`external:${packageName}`)} [label=${dotQuote(packageName)}, shape="component", style="filled", fillcolor="#e2e8f0"];`);
@@ -1236,7 +1265,7 @@ function renderDependencyGraphToDot(graph) {
1236
1265
  lines.push(" }");
1237
1266
  }
1238
1267
  for (const edge of edges) {
1239
- 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)};`);
1240
1269
  }
1241
1270
  lines.push("}", "");
1242
1271
  return lines.join("\n");
@@ -1288,7 +1317,10 @@ async function runGraphCommand(args) {
1288
1317
  input: { type: "string", short: "i" },
1289
1318
  output: { type: "string", short: "o" },
1290
1319
  cwd: { type: "string" },
1291
- 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" }
1292
1324
  } }).values;
1293
1325
  } catch (error) {
1294
1326
  console.error(`Error parsing arguments: ${error instanceof Error ? error.message : String(error)}`);
@@ -1303,6 +1335,9 @@ Renders existing canonical graph evidence without running dependency analysis.
1303
1335
  Options:
1304
1336
  -i, --input <path> .maritime directory or dependency-graph.json (default: .maritime)
1305
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)
1306
1341
  --cwd <dir> Working directory root for resolution
1307
1342
  -h, --help Show help message
1308
1343
  `);
@@ -1312,6 +1347,22 @@ Options:
1312
1347
  console.error("Error: --output is required.");
1313
1348
  return 2;
1314
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
+ }
1315
1366
  const cwd = path9.resolve(values.cwd ?? process.cwd());
1316
1367
  const input = path9.resolve(cwd, values.input ?? ".maritime");
1317
1368
  const output = path9.resolve(cwd, values.output);
@@ -1321,7 +1372,7 @@ Options:
1321
1372
  const parsed = JSON.parse(await fs4.readFile(graphPath, "utf8"));
1322
1373
  const result = CruiseResultSchema.safeParse(parsed);
1323
1374
  if (!result.success) throw new Error(`Invalid Maritime dependency graph: ${result.error.message}`);
1324
- const dot = renderDependencyGraphToDot(result.data);
1375
+ const dot = renderDependencyGraphToDot(result.data, { externalPackages, folderGrouping, edgeLabels });
1325
1376
  const format = inferGraphvizFormat(output);
1326
1377
  await fs4.mkdir(path9.dirname(output), { recursive: true });
1327
1378
  if (format === "dot") await fs4.writeFile(output, dot);
@@ -1380,6 +1431,10 @@ export {
1380
1431
  ArtifactManifestArtifactsSchema,
1381
1432
  ArtifactManifestSchema,
1382
1433
  ArtifactManifestSummarySchema,
1434
+ DEFAULT_GRAPH_PRESENTATION,
1435
+ EDGE_LABEL_MODES,
1436
+ EXTERNAL_PACKAGE_MODES,
1437
+ FOLDER_GROUPING_MODES,
1383
1438
  MANIFEST_SCHEMA_VERSION,
1384
1439
  ValidationError,
1385
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 });
@@ -1228,12 +1251,18 @@ function renderDependencyGraphToDot(graph) {
1228
1251
  edges.sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to) || JSON.stringify(a.dependency).localeCompare(JSON.stringify(b.dependency)));
1229
1252
  const lines = [
1230
1253
  'digraph "dependency-graph" {',
1231
- ' graph [compound="true", rankdir="LR", fontname="Helvetica"];',
1254
+ // Graphviz 2.42 can fail init_rank when the default cluster-local ranker
1255
+ // encounters a large recursively nested directory hierarchy. newrank asks
1256
+ // dot to compute one global ranking across clusters while preserving the
1257
+ // cluster boxes and deterministic left-to-right presentation.
1258
+ ' graph [compound="true", newrank="true", rankdir="LR", fontname="Helvetica"];',
1232
1259
  ' node [fontname="Helvetica", fontsize="10"];',
1233
1260
  ' edge [fontname="Helvetica", fontsize="8"];',
1234
1261
  ...renderDirectory(root, [], " ")
1235
1262
  ];
1236
- 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) {
1237
1266
  lines.push(' subgraph "cluster:external-packages" {', ' label="External packages";', ' style="dashed";', ' color="#64748b";');
1238
1267
  for (const packageName of [...externalPackages].sort()) {
1239
1268
  lines.push(` ${dotQuote(`external:${packageName}`)} [label=${dotQuote(packageName)}, shape="component", style="filled", fillcolor="#e2e8f0"];`);
@@ -1241,7 +1270,7 @@ function renderDependencyGraphToDot(graph) {
1241
1270
  lines.push(" }");
1242
1271
  }
1243
1272
  for (const edge of edges) {
1244
- 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)};`);
1245
1274
  }
1246
1275
  lines.push("}", "");
1247
1276
  return lines.join("\n");
@@ -1293,7 +1322,10 @@ async function runGraphCommand(args) {
1293
1322
  input: { type: "string", short: "i" },
1294
1323
  output: { type: "string", short: "o" },
1295
1324
  cwd: { type: "string" },
1296
- 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" }
1297
1329
  } }).values;
1298
1330
  } catch (error) {
1299
1331
  console.error(`Error parsing arguments: ${error instanceof Error ? error.message : String(error)}`);
@@ -1308,6 +1340,9 @@ Renders existing canonical graph evidence without running dependency analysis.
1308
1340
  Options:
1309
1341
  -i, --input <path> .maritime directory or dependency-graph.json (default: .maritime)
1310
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)
1311
1346
  --cwd <dir> Working directory root for resolution
1312
1347
  -h, --help Show help message
1313
1348
  `);
@@ -1317,6 +1352,22 @@ Options:
1317
1352
  console.error("Error: --output is required.");
1318
1353
  return 2;
1319
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
+ }
1320
1371
  const cwd = path9.resolve(values.cwd ?? process.cwd());
1321
1372
  const input = path9.resolve(cwd, values.input ?? ".maritime");
1322
1373
  const output = path9.resolve(cwd, values.output);
@@ -1326,7 +1377,7 @@ Options:
1326
1377
  const parsed = JSON.parse(await fs4.readFile(graphPath, "utf8"));
1327
1378
  const result = CruiseResultSchema.safeParse(parsed);
1328
1379
  if (!result.success) throw new Error(`Invalid Maritime dependency graph: ${result.error.message}`);
1329
- const dot = renderDependencyGraphToDot(result.data);
1380
+ const dot = renderDependencyGraphToDot(result.data, { externalPackages, folderGrouping, edgeLabels });
1330
1381
  const format = inferGraphvizFormat(output);
1331
1382
  await fs4.mkdir(path9.dirname(output), { recursive: true });
1332
1383
  if (format === "dot") await fs4.writeFile(output, dot);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dependency-maritime/cli",
3
- "version": "0.1.0-beta.3",
3
+ "version": "0.1.0-beta.5",
4
4
  "type": "module",
5
5
  "main": "./dist/cli/index.js",
6
6
  "types": "./dist/cli/index.d.ts",