@fairfox/polly 0.73.1 → 0.74.1
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/src/mesh.js +32 -12
- package/dist/src/mesh.js.map +6 -5
- package/dist/src/polly-ui/ActionSelect.d.ts +7 -3
- package/dist/src/polly-ui/Dropdown.d.ts +9 -0
- package/dist/src/polly-ui/Select.d.ts +2 -0
- package/dist/src/polly-ui/index.css +29 -5
- package/dist/src/polly-ui/index.js +65 -19
- package/dist/src/polly-ui/index.js.map +5 -5
- package/dist/src/polly-ui/styles.css +29 -5
- package/dist/src/shared/lib/derive-document-id.d.ts +21 -0
- package/dist/src/shared/lib/mesh-state.d.ts +6 -6
- package/dist/src/shared/lib/mesh-webrtc-adapter.d.ts +43 -2
- package/dist/tools/visualize/src/cli.js +365 -21
- package/dist/tools/visualize/src/cli.js.map +8 -6
- package/package.json +1 -1
|
@@ -667,7 +667,7 @@ function detectProjectConfig(projectRoot) {
|
|
|
667
667
|
var init_project_detector = () => {};
|
|
668
668
|
|
|
669
669
|
// tools/visualize/src/cli.ts
|
|
670
|
-
import * as
|
|
670
|
+
import * as fs7 from "node:fs";
|
|
671
671
|
import * as path6 from "node:path";
|
|
672
672
|
|
|
673
673
|
// tools/analysis/src/extract/architecture.ts
|
|
@@ -4590,6 +4590,122 @@ async function analyzeArchitecture(options) {
|
|
|
4590
4590
|
return analyzer.analyze();
|
|
4591
4591
|
}
|
|
4592
4592
|
|
|
4593
|
+
// src/shared/lib/derive-document-id.ts
|
|
4594
|
+
import {
|
|
4595
|
+
interpretAsDocumentId
|
|
4596
|
+
} from "@automerge/automerge-repo/slim";
|
|
4597
|
+
import nacl from "tweetnacl";
|
|
4598
|
+
var DOC_ID_DOMAIN = "polly/meshState/v1";
|
|
4599
|
+
var keyEncoder = new TextEncoder;
|
|
4600
|
+
function deriveDocumentId(key) {
|
|
4601
|
+
const digest = nacl.hash(keyEncoder.encode(`${DOC_ID_DOMAIN}:${key}`));
|
|
4602
|
+
const bytes = digest.slice(0, 16);
|
|
4603
|
+
return interpretAsDocumentId(bytes);
|
|
4604
|
+
}
|
|
4605
|
+
|
|
4606
|
+
// tools/visualize/src/mesh-snapshot.ts
|
|
4607
|
+
import * as fs5 from "node:fs";
|
|
4608
|
+
|
|
4609
|
+
class MeshSnapshotError extends Error {
|
|
4610
|
+
constructor(message) {
|
|
4611
|
+
super(message);
|
|
4612
|
+
this.name = "MeshSnapshotError";
|
|
4613
|
+
}
|
|
4614
|
+
}
|
|
4615
|
+
function isObject(value) {
|
|
4616
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4617
|
+
}
|
|
4618
|
+
function requireStringArray(value, path5) {
|
|
4619
|
+
if (!Array.isArray(value) || value.some((v) => typeof v !== "string")) {
|
|
4620
|
+
throw new MeshSnapshotError(`${path5} must be an array of strings`);
|
|
4621
|
+
}
|
|
4622
|
+
return value;
|
|
4623
|
+
}
|
|
4624
|
+
function validateHandle(value, path5) {
|
|
4625
|
+
if (!isObject(value)) {
|
|
4626
|
+
throw new MeshSnapshotError(`${path5} must be an object`);
|
|
4627
|
+
}
|
|
4628
|
+
if (typeof value["docSynchronizerExists"] !== "boolean") {
|
|
4629
|
+
throw new MeshSnapshotError(`${path5}.docSynchronizerExists must be a boolean`);
|
|
4630
|
+
}
|
|
4631
|
+
const knowsPeer = value["docSynchronizerKnowsPeer"];
|
|
4632
|
+
if (knowsPeer !== undefined && typeof knowsPeer !== "boolean") {
|
|
4633
|
+
throw new MeshSnapshotError(`${path5}.docSynchronizerKnowsPeer must be a boolean or absent`);
|
|
4634
|
+
}
|
|
4635
|
+
const status = value["peerDocumentStatus"];
|
|
4636
|
+
if (status !== undefined && typeof status !== "string") {
|
|
4637
|
+
throw new MeshSnapshotError(`${path5}.peerDocumentStatus must be a string or absent`);
|
|
4638
|
+
}
|
|
4639
|
+
return value;
|
|
4640
|
+
}
|
|
4641
|
+
function validatePeer(value, path5) {
|
|
4642
|
+
if (!isObject(value)) {
|
|
4643
|
+
throw new MeshSnapshotError(`${path5} must be an object`);
|
|
4644
|
+
}
|
|
4645
|
+
if (typeof value["peerId"] !== "string") {
|
|
4646
|
+
throw new MeshSnapshotError(`${path5}.peerId must be a string`);
|
|
4647
|
+
}
|
|
4648
|
+
const slot = value["slot"];
|
|
4649
|
+
if (slot !== undefined && slot !== null) {
|
|
4650
|
+
if (!isObject(slot)) {
|
|
4651
|
+
throw new MeshSnapshotError(`${path5}.slot must be an object or absent`);
|
|
4652
|
+
}
|
|
4653
|
+
const handles = slot["handles"];
|
|
4654
|
+
if (!isObject(handles)) {
|
|
4655
|
+
throw new MeshSnapshotError(`${path5}.slot.handles must be an object`);
|
|
4656
|
+
}
|
|
4657
|
+
for (const [docId, handle] of Object.entries(handles)) {
|
|
4658
|
+
validateHandle(handle, `${path5}.slot.handles[${docId}]`);
|
|
4659
|
+
}
|
|
4660
|
+
}
|
|
4661
|
+
return value;
|
|
4662
|
+
}
|
|
4663
|
+
function validateMeshSnapshot(data) {
|
|
4664
|
+
if (!isObject(data)) {
|
|
4665
|
+
throw new MeshSnapshotError("snapshot must be a JSON object");
|
|
4666
|
+
}
|
|
4667
|
+
if (typeof data["localPeerId"] !== "string") {
|
|
4668
|
+
throw new MeshSnapshotError("snapshot.localPeerId must be a string");
|
|
4669
|
+
}
|
|
4670
|
+
requireStringArray(data["knownPeerIds"], "snapshot.knownPeerIds");
|
|
4671
|
+
requireStringArray(data["presentPeerIds"], "snapshot.presentPeerIds");
|
|
4672
|
+
const peers = data["peers"];
|
|
4673
|
+
if (!Array.isArray(peers)) {
|
|
4674
|
+
throw new MeshSnapshotError("snapshot.peers must be an array");
|
|
4675
|
+
}
|
|
4676
|
+
peers.forEach((peer, i) => {
|
|
4677
|
+
validatePeer(peer, `snapshot.peers[${i}]`);
|
|
4678
|
+
});
|
|
4679
|
+
return data;
|
|
4680
|
+
}
|
|
4681
|
+
function loadMeshSnapshot(filePath) {
|
|
4682
|
+
let raw;
|
|
4683
|
+
try {
|
|
4684
|
+
raw = fs5.readFileSync(filePath, "utf-8");
|
|
4685
|
+
} catch (error) {
|
|
4686
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
4687
|
+
throw new MeshSnapshotError(`cannot read snapshot file '${filePath}': ${reason}`);
|
|
4688
|
+
}
|
|
4689
|
+
let parsed;
|
|
4690
|
+
try {
|
|
4691
|
+
parsed = JSON.parse(raw);
|
|
4692
|
+
} catch (error) {
|
|
4693
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
4694
|
+
throw new MeshSnapshotError(`snapshot file '${filePath}' is not valid JSON: ${reason}`);
|
|
4695
|
+
}
|
|
4696
|
+
return validateMeshSnapshot(parsed);
|
|
4697
|
+
}
|
|
4698
|
+
function collectSnapshotPeerIds(snapshot) {
|
|
4699
|
+
const ids = new Set([snapshot.localPeerId]);
|
|
4700
|
+
for (const id of snapshot.knownPeerIds)
|
|
4701
|
+
ids.add(id);
|
|
4702
|
+
for (const id of snapshot.presentPeerIds)
|
|
4703
|
+
ids.add(id);
|
|
4704
|
+
for (const peer of snapshot.peers)
|
|
4705
|
+
ids.add(peer.peerId);
|
|
4706
|
+
return [...ids];
|
|
4707
|
+
}
|
|
4708
|
+
|
|
4593
4709
|
// tools/visualize/src/types/structurizr.ts
|
|
4594
4710
|
var DEFAULT_COLORS = {
|
|
4595
4711
|
messageHandler: "#1168bd",
|
|
@@ -4690,6 +4806,46 @@ var DEFAULT_RELATIONSHIP_STYLES = {
|
|
|
4690
4806
|
color: DEFAULT_COLORS.database
|
|
4691
4807
|
}
|
|
4692
4808
|
};
|
|
4809
|
+
var MESH_OVERLAY_ELEMENT_STYLES = {
|
|
4810
|
+
"Mesh Peer": {
|
|
4811
|
+
shape: "Person",
|
|
4812
|
+
background: "#495057",
|
|
4813
|
+
color: DEFAULT_COLORS.textLight
|
|
4814
|
+
},
|
|
4815
|
+
"Local Mesh Peer": {
|
|
4816
|
+
shape: "Person",
|
|
4817
|
+
background: DEFAULT_COLORS.messageHandler,
|
|
4818
|
+
color: DEFAULT_COLORS.textLight
|
|
4819
|
+
},
|
|
4820
|
+
"Snapshot Document": {
|
|
4821
|
+
shape: "Cylinder",
|
|
4822
|
+
background: "#adb5bd",
|
|
4823
|
+
color: DEFAULT_COLORS.textDark,
|
|
4824
|
+
border: "Dashed"
|
|
4825
|
+
}
|
|
4826
|
+
};
|
|
4827
|
+
var MESH_OVERLAY_RELATIONSHIP_STYLES = {
|
|
4828
|
+
"sync:has": {
|
|
4829
|
+
color: "#2f9e44",
|
|
4830
|
+
style: "Solid",
|
|
4831
|
+
thickness: 3
|
|
4832
|
+
},
|
|
4833
|
+
"sync:wants": {
|
|
4834
|
+
color: "#f08c00",
|
|
4835
|
+
style: "Dashed",
|
|
4836
|
+
thickness: 2
|
|
4837
|
+
},
|
|
4838
|
+
"sync:unavailable": {
|
|
4839
|
+
color: "#e03131",
|
|
4840
|
+
style: "Dashed",
|
|
4841
|
+
thickness: 2
|
|
4842
|
+
},
|
|
4843
|
+
"sync:unknown": {
|
|
4844
|
+
color: "#868e96",
|
|
4845
|
+
style: "Dotted",
|
|
4846
|
+
thickness: 2
|
|
4847
|
+
}
|
|
4848
|
+
};
|
|
4693
4849
|
var DEFAULT_THEME = "https://static.structurizr.com/themes/default/theme.json";
|
|
4694
4850
|
|
|
4695
4851
|
// tools/visualize/src/codegen/structurizr.ts
|
|
@@ -4715,10 +4871,18 @@ var MESH_TRANSPORT_NODES = [
|
|
|
4715
4871
|
description: "The rendezvous server peers exchange WebRTC offers through"
|
|
4716
4872
|
}
|
|
4717
4873
|
];
|
|
4874
|
+
function syncStatusOf(handle) {
|
|
4875
|
+
const status = handle.peerDocumentStatus;
|
|
4876
|
+
if (status === "has" || status === "wants" || status === "unavailable") {
|
|
4877
|
+
return status;
|
|
4878
|
+
}
|
|
4879
|
+
return "unknown";
|
|
4880
|
+
}
|
|
4718
4881
|
|
|
4719
4882
|
class StructurizrDSLGenerator {
|
|
4720
4883
|
analysis;
|
|
4721
4884
|
options;
|
|
4885
|
+
overlayPlanCache;
|
|
4722
4886
|
constructor(analysis, options = {}) {
|
|
4723
4887
|
this.analysis = analysis;
|
|
4724
4888
|
this.options = {
|
|
@@ -4737,10 +4901,12 @@ class StructurizrDSLGenerator {
|
|
|
4737
4901
|
theme: options.styles?.theme || DEFAULT_THEME,
|
|
4738
4902
|
elements: {
|
|
4739
4903
|
...DEFAULT_ELEMENT_STYLES,
|
|
4904
|
+
...options.snapshot ? MESH_OVERLAY_ELEMENT_STYLES : {},
|
|
4740
4905
|
...options.styles?.elements
|
|
4741
4906
|
},
|
|
4742
4907
|
relationships: {
|
|
4743
4908
|
...DEFAULT_RELATIONSHIP_STYLES,
|
|
4909
|
+
...options.snapshot ? MESH_OVERLAY_RELATIONSHIP_STYLES : {},
|
|
4744
4910
|
...options.styles?.relationships
|
|
4745
4911
|
}
|
|
4746
4912
|
}
|
|
@@ -4774,6 +4940,9 @@ class StructurizrDSLGenerator {
|
|
|
4774
4940
|
const parts = [];
|
|
4775
4941
|
parts.push(" model {");
|
|
4776
4942
|
parts.push(this.generatePeople());
|
|
4943
|
+
const meshPeers = this.generateMeshPeers();
|
|
4944
|
+
if (meshPeers)
|
|
4945
|
+
parts.push(meshPeers);
|
|
4777
4946
|
parts.push(this.generateExternalSystems());
|
|
4778
4947
|
parts.push(this.generateMainSystem());
|
|
4779
4948
|
if (this.options.deploymentNodes && this.options.deploymentNodes.length > 0) {
|
|
@@ -4814,6 +4983,9 @@ class StructurizrDSLGenerator {
|
|
|
4814
4983
|
const meshDocs = this.generateMeshDocuments();
|
|
4815
4984
|
if (meshDocs)
|
|
4816
4985
|
parts.push(meshDocs);
|
|
4986
|
+
const snapshotDocs = this.generateSnapshotOnlyDocuments();
|
|
4987
|
+
if (snapshotDocs)
|
|
4988
|
+
parts.push(snapshotDocs);
|
|
4817
4989
|
const meshTransport = this.generateMeshTransport();
|
|
4818
4990
|
if (meshTransport)
|
|
4819
4991
|
parts.push(meshTransport);
|
|
@@ -5202,6 +5374,7 @@ class StructurizrDSLGenerator {
|
|
|
5202
5374
|
parts.push(...this.generateExternalAPIRelationships());
|
|
5203
5375
|
parts.push(...this.generateMeshRelationships());
|
|
5204
5376
|
parts.push(...this.generateMeshTransportRelationships());
|
|
5377
|
+
parts.push(...this.generateMeshOverlayRelationships());
|
|
5205
5378
|
return parts.join(`
|
|
5206
5379
|
`);
|
|
5207
5380
|
}
|
|
@@ -5268,6 +5441,130 @@ class StructurizrDSLGenerator {
|
|
|
5268
5441
|
}
|
|
5269
5442
|
return parts;
|
|
5270
5443
|
}
|
|
5444
|
+
getOverlayPlan() {
|
|
5445
|
+
const snapshot = this.options.snapshot;
|
|
5446
|
+
if (!snapshot)
|
|
5447
|
+
return;
|
|
5448
|
+
if (!this.overlayPlanCache) {
|
|
5449
|
+
this.overlayPlanCache = this.buildOverlayPlan(snapshot);
|
|
5450
|
+
}
|
|
5451
|
+
return this.overlayPlanCache;
|
|
5452
|
+
}
|
|
5453
|
+
buildOverlayPlan(snapshot) {
|
|
5454
|
+
const docIdToNode = this.buildDocIdNodeMap();
|
|
5455
|
+
const { peers, peerDslById } = this.buildOverlayPeers(snapshot);
|
|
5456
|
+
const { edges, snapshotOnlyDocs } = this.buildOverlayEdges(snapshot, docIdToNode, peerDslById);
|
|
5457
|
+
return { peers, edges, snapshotOnlyDocs };
|
|
5458
|
+
}
|
|
5459
|
+
buildDocIdNodeMap() {
|
|
5460
|
+
const docIdToNode = new Map;
|
|
5461
|
+
const seenKeys = new Set;
|
|
5462
|
+
for (const sig of this.analysis.meshOrPeerSignals ?? []) {
|
|
5463
|
+
if (seenKeys.has(sig.key))
|
|
5464
|
+
continue;
|
|
5465
|
+
seenKeys.add(sig.key);
|
|
5466
|
+
docIdToNode.set(String(deriveDocumentId(sig.key)), this.meshDocId(sig.key));
|
|
5467
|
+
}
|
|
5468
|
+
return docIdToNode;
|
|
5469
|
+
}
|
|
5470
|
+
buildOverlayPeers(snapshot) {
|
|
5471
|
+
const peers = [];
|
|
5472
|
+
const peerDslById = new Map;
|
|
5473
|
+
const usedPeerIds = new Set;
|
|
5474
|
+
for (const peerId of collectSnapshotPeerIds(snapshot)) {
|
|
5475
|
+
const base = `peer_${this.toId(peerId)}`;
|
|
5476
|
+
let id = base;
|
|
5477
|
+
let suffix = 2;
|
|
5478
|
+
while (usedPeerIds.has(id))
|
|
5479
|
+
id = `${base}_${suffix++}`;
|
|
5480
|
+
usedPeerIds.add(id);
|
|
5481
|
+
peerDslById.set(peerId, id);
|
|
5482
|
+
peers.push({ id, peerId, isLocal: peerId === snapshot.localPeerId });
|
|
5483
|
+
}
|
|
5484
|
+
return { peers, peerDslById };
|
|
5485
|
+
}
|
|
5486
|
+
buildOverlayEdges(snapshot, docIdToNode, peerDslById) {
|
|
5487
|
+
const edges = [];
|
|
5488
|
+
const snapshotOnlyDocs = [];
|
|
5489
|
+
const snapshotOnlyById = new Map;
|
|
5490
|
+
for (const peer of snapshot.peers) {
|
|
5491
|
+
const handles = peer.slot?.handles;
|
|
5492
|
+
const fromId = peerDslById.get(peer.peerId);
|
|
5493
|
+
if (!handles || !fromId)
|
|
5494
|
+
continue;
|
|
5495
|
+
for (const [docId, handle] of Object.entries(handles)) {
|
|
5496
|
+
const nodeId = this.resolveOverlayDocNode(docId, docIdToNode, snapshotOnlyById, snapshotOnlyDocs);
|
|
5497
|
+
const status = syncStatusOf(handle);
|
|
5498
|
+
edges.push({
|
|
5499
|
+
fromId,
|
|
5500
|
+
toRef: `extension.${nodeId}`,
|
|
5501
|
+
status,
|
|
5502
|
+
label: this.overlayEdgeLabel(status, handle)
|
|
5503
|
+
});
|
|
5504
|
+
}
|
|
5505
|
+
}
|
|
5506
|
+
return { edges, snapshotOnlyDocs };
|
|
5507
|
+
}
|
|
5508
|
+
resolveOverlayDocNode(docId, docIdToNode, snapshotOnlyById, snapshotOnlyDocs) {
|
|
5509
|
+
const staticNode = docIdToNode.get(docId);
|
|
5510
|
+
if (staticNode)
|
|
5511
|
+
return staticNode;
|
|
5512
|
+
const existing = snapshotOnlyById.get(docId);
|
|
5513
|
+
if (existing)
|
|
5514
|
+
return existing;
|
|
5515
|
+
const nodeId = `mesh_doc_${this.toId(docId)}`;
|
|
5516
|
+
snapshotOnlyById.set(docId, nodeId);
|
|
5517
|
+
snapshotOnlyDocs.push({ id: nodeId, docId });
|
|
5518
|
+
return nodeId;
|
|
5519
|
+
}
|
|
5520
|
+
overlayEdgeLabel(status, handle) {
|
|
5521
|
+
if (!handle.docSynchronizerExists)
|
|
5522
|
+
return `${status} · no synchronizer`;
|
|
5523
|
+
if (handle.docSynchronizerKnowsPeer === false)
|
|
5524
|
+
return `${status} · peer not added`;
|
|
5525
|
+
return status;
|
|
5526
|
+
}
|
|
5527
|
+
generateMeshPeers() {
|
|
5528
|
+
const plan = this.getOverlayPlan();
|
|
5529
|
+
if (!plan || plan.peers.length === 0)
|
|
5530
|
+
return "";
|
|
5531
|
+
const parts = [];
|
|
5532
|
+
for (const peer of plan.peers) {
|
|
5533
|
+
const tag = peer.isLocal ? "Local Mesh Peer" : "Mesh Peer";
|
|
5534
|
+
const description = peer.isLocal ? "Local mesh peer (this node)" : "Runtime mesh peer";
|
|
5535
|
+
parts.push(` ${peer.id} = person "${this.escape(peer.peerId)}" "${description}" {`);
|
|
5536
|
+
parts.push(` tags "${tag}"`);
|
|
5537
|
+
parts.push(" }");
|
|
5538
|
+
}
|
|
5539
|
+
return parts.join(`
|
|
5540
|
+
`);
|
|
5541
|
+
}
|
|
5542
|
+
generateSnapshotOnlyDocuments() {
|
|
5543
|
+
const plan = this.getOverlayPlan();
|
|
5544
|
+
if (!plan || plan.snapshotOnlyDocs.length === 0)
|
|
5545
|
+
return "";
|
|
5546
|
+
const parts = [];
|
|
5547
|
+
for (const doc of plan.snapshotOnlyDocs) {
|
|
5548
|
+
const description = `Mesh document seen only in the runtime snapshot — docId ${doc.docId}`;
|
|
5549
|
+
parts.push(` ${doc.id} = container "${this.escape(doc.docId)}" "${this.escape(description)}" "snapshot" {`);
|
|
5550
|
+
parts.push(' tags "Snapshot Document"');
|
|
5551
|
+
parts.push(" }");
|
|
5552
|
+
}
|
|
5553
|
+
return parts.join(`
|
|
5554
|
+
`);
|
|
5555
|
+
}
|
|
5556
|
+
generateMeshOverlayRelationships() {
|
|
5557
|
+
const plan = this.getOverlayPlan();
|
|
5558
|
+
if (!plan)
|
|
5559
|
+
return [];
|
|
5560
|
+
const parts = [];
|
|
5561
|
+
for (const edge of plan.edges) {
|
|
5562
|
+
parts.push(` ${edge.fromId} -> ${edge.toRef} "${this.escape(edge.label)}" {`);
|
|
5563
|
+
parts.push(` tags "sync:${edge.status}"`);
|
|
5564
|
+
parts.push(" }");
|
|
5565
|
+
}
|
|
5566
|
+
return parts;
|
|
5567
|
+
}
|
|
5271
5568
|
generateUserRelationships() {
|
|
5272
5569
|
const parts = [];
|
|
5273
5570
|
const uiContexts = ["popup", "options", "devtools"];
|
|
@@ -6015,7 +6312,7 @@ function generateStructurizrDSL(analysis, options) {
|
|
|
6015
6312
|
|
|
6016
6313
|
// tools/visualize/src/runner/export.ts
|
|
6017
6314
|
import { spawn } from "node:child_process";
|
|
6018
|
-
import * as
|
|
6315
|
+
import * as fs6 from "node:fs";
|
|
6019
6316
|
import * as path5 from "node:path";
|
|
6020
6317
|
|
|
6021
6318
|
class DiagramExporter {
|
|
@@ -6023,15 +6320,15 @@ class DiagramExporter {
|
|
|
6023
6320
|
static DEFAULT_TIMEOUT = 120000;
|
|
6024
6321
|
async export(options) {
|
|
6025
6322
|
const { dslPath, outputDir, timeout = DiagramExporter.DEFAULT_TIMEOUT } = options;
|
|
6026
|
-
if (!
|
|
6323
|
+
if (!fs6.existsSync(dslPath)) {
|
|
6027
6324
|
return {
|
|
6028
6325
|
success: false,
|
|
6029
6326
|
siteDir: "",
|
|
6030
6327
|
error: `DSL file not found: ${dslPath}`
|
|
6031
6328
|
};
|
|
6032
6329
|
}
|
|
6033
|
-
if (!
|
|
6034
|
-
|
|
6330
|
+
if (!fs6.existsSync(outputDir)) {
|
|
6331
|
+
fs6.mkdirSync(outputDir, { recursive: true });
|
|
6035
6332
|
}
|
|
6036
6333
|
const dockerAvailable = await this.isDockerAvailable();
|
|
6037
6334
|
if (!dockerAvailable) {
|
|
@@ -6179,7 +6476,7 @@ async function main() {
|
|
|
6179
6476
|
switch (command) {
|
|
6180
6477
|
case "--generate":
|
|
6181
6478
|
case "generate":
|
|
6182
|
-
await generateCommand();
|
|
6479
|
+
await generateCommand(args.slice(1));
|
|
6183
6480
|
break;
|
|
6184
6481
|
case "--export":
|
|
6185
6482
|
case "export":
|
|
@@ -6196,22 +6493,64 @@ async function main() {
|
|
|
6196
6493
|
showHelp();
|
|
6197
6494
|
break;
|
|
6198
6495
|
default:
|
|
6199
|
-
await generateCommand();
|
|
6496
|
+
await generateCommand(args);
|
|
6200
6497
|
}
|
|
6201
6498
|
}
|
|
6202
|
-
async function generateCommand() {
|
|
6499
|
+
async function generateCommand(args) {
|
|
6203
6500
|
console.log(color(`
|
|
6204
6501
|
\uD83D\uDCCA Analyzing architecture...
|
|
6205
6502
|
`, COLORS.blue));
|
|
6206
6503
|
try {
|
|
6504
|
+
const { snapshotPath } = parseGenerateArgs(args);
|
|
6505
|
+
const snapshot = snapshotPath ? loadAndReportSnapshot(snapshotPath) : undefined;
|
|
6207
6506
|
const { tsConfigPath, projectRoot } = findAndDisplayProjectConfig();
|
|
6208
6507
|
const analysis = await analyzeAndDisplayResults(tsConfigPath, projectRoot);
|
|
6209
|
-
const dslPath = generateAndWriteDSL(analysis);
|
|
6508
|
+
const dslPath = generateAndWriteDSL(analysis, snapshot);
|
|
6210
6509
|
displayNextSteps(dslPath);
|
|
6211
6510
|
} catch (_error) {
|
|
6212
6511
|
process.exit(1);
|
|
6213
6512
|
}
|
|
6214
6513
|
}
|
|
6514
|
+
function parseGenerateArgs(args) {
|
|
6515
|
+
const result = {};
|
|
6516
|
+
for (let i = 0;i < args.length; i++) {
|
|
6517
|
+
const arg = args[i];
|
|
6518
|
+
if (arg === "--snapshot") {
|
|
6519
|
+
const next = args[i + 1];
|
|
6520
|
+
if (!next || next.startsWith("--")) {
|
|
6521
|
+
console.log(color(`
|
|
6522
|
+
✗ --snapshot requires a file path
|
|
6523
|
+
`, COLORS.red));
|
|
6524
|
+
process.exit(1);
|
|
6525
|
+
}
|
|
6526
|
+
result.snapshotPath = next;
|
|
6527
|
+
i++;
|
|
6528
|
+
} else if (arg?.startsWith("--snapshot=")) {
|
|
6529
|
+
result.snapshotPath = arg.slice("--snapshot=".length);
|
|
6530
|
+
}
|
|
6531
|
+
}
|
|
6532
|
+
return result;
|
|
6533
|
+
}
|
|
6534
|
+
function loadAndReportSnapshot(snapshotPath) {
|
|
6535
|
+
let snapshot;
|
|
6536
|
+
try {
|
|
6537
|
+
snapshot = loadMeshSnapshot(snapshotPath);
|
|
6538
|
+
} catch (error) {
|
|
6539
|
+
const message = error instanceof MeshSnapshotError ? error.message : String(error);
|
|
6540
|
+
console.log(color(`
|
|
6541
|
+
✗ Snapshot error: ${message}
|
|
6542
|
+
`, COLORS.red));
|
|
6543
|
+
process.exit(1);
|
|
6544
|
+
}
|
|
6545
|
+
const isEmpty = snapshot.knownPeerIds.length === 0 && snapshot.presentPeerIds.length === 0 && snapshot.peers.length === 0;
|
|
6546
|
+
if (isEmpty) {
|
|
6547
|
+
console.log(color("⚠ Snapshot has no peers — generating the static diagram only.", COLORS.yellow));
|
|
6548
|
+
return;
|
|
6549
|
+
}
|
|
6550
|
+
const peerCount = collectSnapshotPeerIds(snapshot).length;
|
|
6551
|
+
console.log(color(`✓ Loaded runtime snapshot: ${peerCount} peer(s)`, COLORS.green));
|
|
6552
|
+
return snapshot;
|
|
6553
|
+
}
|
|
6215
6554
|
function findAndDisplayProjectConfig() {
|
|
6216
6555
|
const tsConfigPath = findTsConfig();
|
|
6217
6556
|
if (!tsConfigPath) {
|
|
@@ -6227,7 +6566,7 @@ function findAndDisplayProjectConfig() {
|
|
|
6227
6566
|
return { tsConfigPath, projectRoot };
|
|
6228
6567
|
}
|
|
6229
6568
|
function displayProjectType(projectRoot) {
|
|
6230
|
-
const hasManifest =
|
|
6569
|
+
const hasManifest = fs7.existsSync(path6.join(projectRoot, "manifest.json"));
|
|
6231
6570
|
const projectType = hasManifest ? "Chrome Extension" : "Detecting from project structure...";
|
|
6232
6571
|
console.log(color(` Type: ${projectType}`, COLORS.gray));
|
|
6233
6572
|
}
|
|
@@ -6261,7 +6600,7 @@ function displayArchitectureSummary(analysis) {
|
|
|
6261
6600
|
}
|
|
6262
6601
|
}
|
|
6263
6602
|
}
|
|
6264
|
-
function generateAndWriteDSL(analysis) {
|
|
6603
|
+
function generateAndWriteDSL(analysis, snapshot) {
|
|
6265
6604
|
console.log(color(`
|
|
6266
6605
|
\uD83D\uDCDD Generating Structurizr DSL...
|
|
6267
6606
|
`, COLORS.blue));
|
|
@@ -6269,14 +6608,15 @@ function generateAndWriteDSL(analysis) {
|
|
|
6269
6608
|
const dsl = generateStructurizrDSL(analysis, {
|
|
6270
6609
|
includeDynamicDiagrams: true,
|
|
6271
6610
|
includeComponentDiagrams: true,
|
|
6272
|
-
componentDiagramContexts: contextTypes.length > 0 ? contextTypes : ["background"]
|
|
6611
|
+
componentDiagramContexts: contextTypes.length > 0 ? contextTypes : ["background"],
|
|
6612
|
+
snapshot
|
|
6273
6613
|
});
|
|
6274
6614
|
const outputDir = path6.join(process.cwd(), "docs");
|
|
6275
|
-
if (!
|
|
6276
|
-
|
|
6615
|
+
if (!fs7.existsSync(outputDir)) {
|
|
6616
|
+
fs7.mkdirSync(outputDir, { recursive: true });
|
|
6277
6617
|
}
|
|
6278
6618
|
const dslPath = path6.join(outputDir, "architecture.dsl");
|
|
6279
|
-
|
|
6619
|
+
fs7.writeFileSync(dslPath, dsl, "utf-8");
|
|
6280
6620
|
return dslPath;
|
|
6281
6621
|
}
|
|
6282
6622
|
function displayNextSteps(dslPath) {
|
|
@@ -6312,7 +6652,7 @@ async function exportCommand(_args) {
|
|
|
6312
6652
|
`, COLORS.blue));
|
|
6313
6653
|
try {
|
|
6314
6654
|
const dslPath = path6.join(process.cwd(), "docs", "architecture.dsl");
|
|
6315
|
-
if (!
|
|
6655
|
+
if (!fs7.existsSync(dslPath)) {
|
|
6316
6656
|
process.exit(1);
|
|
6317
6657
|
}
|
|
6318
6658
|
const outputDir = path6.join(process.cwd(), "docs", "site");
|
|
@@ -6350,7 +6690,7 @@ async function serveCommand(args) {
|
|
|
6350
6690
|
try {
|
|
6351
6691
|
const siteDir = path6.join(process.cwd(), "docs", "site");
|
|
6352
6692
|
const indexPath = path6.join(siteDir, "index.html");
|
|
6353
|
-
if (!
|
|
6693
|
+
if (!fs7.existsSync(indexPath)) {
|
|
6354
6694
|
process.exit(1);
|
|
6355
6695
|
}
|
|
6356
6696
|
const portArg = args.find((arg) => arg.startsWith("--port="));
|
|
@@ -6367,7 +6707,7 @@ async function serveCommand(args) {
|
|
|
6367
6707
|
fetch(req) {
|
|
6368
6708
|
const url = new URL(req.url);
|
|
6369
6709
|
const filePath = path6.join(siteDir, url.pathname === "/" ? "index.html" : url.pathname);
|
|
6370
|
-
if (
|
|
6710
|
+
if (fs7.existsSync(filePath) && fs7.statSync(filePath).isFile()) {
|
|
6371
6711
|
const file = BunGlobal.file(filePath);
|
|
6372
6712
|
return new Response(file);
|
|
6373
6713
|
}
|
|
@@ -6411,6 +6751,10 @@ ${color("Commands:", COLORS.blue)}
|
|
|
6411
6751
|
${color("bun visualize --generate", COLORS.green)}
|
|
6412
6752
|
Analyze codebase and generate Structurizr DSL
|
|
6413
6753
|
|
|
6754
|
+
${color("bun visualize generate --snapshot <path>", COLORS.green)}
|
|
6755
|
+
Overlay a captured MeshClientPeerStateSnapshot — runtime mesh peers
|
|
6756
|
+
and sync-state-coloured replication edges — onto the diagram
|
|
6757
|
+
|
|
6414
6758
|
${color("bun visualize --export", COLORS.green)}
|
|
6415
6759
|
Generate static HTML site with interactive diagrams (requires Docker)
|
|
6416
6760
|
|
|
@@ -6447,7 +6791,7 @@ function findTsConfig() {
|
|
|
6447
6791
|
path6.join(process.cwd(), "..", "tsconfig.json")
|
|
6448
6792
|
];
|
|
6449
6793
|
for (const loc of locations) {
|
|
6450
|
-
if (
|
|
6794
|
+
if (fs7.existsSync(loc)) {
|
|
6451
6795
|
return loc;
|
|
6452
6796
|
}
|
|
6453
6797
|
}
|
|
@@ -6456,7 +6800,7 @@ function findTsConfig() {
|
|
|
6456
6800
|
function findProjectRoot() {
|
|
6457
6801
|
const locations = [process.cwd(), path6.join(process.cwd(), "..")];
|
|
6458
6802
|
for (const loc of locations) {
|
|
6459
|
-
if (
|
|
6803
|
+
if (fs7.existsSync(path6.join(loc, "manifest.json")) || fs7.existsSync(path6.join(loc, "package.json")) || fs7.existsSync(path6.join(loc, "tsconfig.json"))) {
|
|
6460
6804
|
return loc;
|
|
6461
6805
|
}
|
|
6462
6806
|
}
|
|
@@ -6466,4 +6810,4 @@ main().catch((_error) => {
|
|
|
6466
6810
|
process.exit(1);
|
|
6467
6811
|
});
|
|
6468
6812
|
|
|
6469
|
-
//# debugId=
|
|
6813
|
+
//# debugId=4CFC761842CDF97B64756E2164756E21
|