@tecture/core 0.0.6 → 0.0.8
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/CHANGELOG.md +23 -0
- package/README.md +30 -0
- package/dist/cli.js +77 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.js +8 -4
- package/dist/index.js.map +1 -1
- package/dist/public/assets/{_baseUniq-Dhw8ANLN.js → _baseUniq-CUITAAZ7.js} +2 -2
- package/dist/public/assets/{_baseUniq-Dhw8ANLN.js.map → _baseUniq-CUITAAZ7.js.map} +1 -1
- package/dist/public/assets/{arc-DHr716eG.js → arc-DyFMcdgg.js} +2 -2
- package/dist/public/assets/{arc-DHr716eG.js.map → arc-DyFMcdgg.js.map} +1 -1
- package/dist/public/assets/{architectureDiagram-Q4EWVU46-CiupZxBl.js → architectureDiagram-Q4EWVU46-DZTi6y6q.js} +2 -2
- package/dist/public/assets/{architectureDiagram-Q4EWVU46-CiupZxBl.js.map → architectureDiagram-Q4EWVU46-DZTi6y6q.js.map} +1 -1
- package/dist/public/assets/{blockDiagram-DXYQGD6D-CZsYmXdZ.js → blockDiagram-DXYQGD6D-C5DgUxsA.js} +2 -2
- package/dist/public/assets/{blockDiagram-DXYQGD6D-CZsYmXdZ.js.map → blockDiagram-DXYQGD6D-C5DgUxsA.js.map} +1 -1
- package/dist/public/assets/{c4Diagram-AHTNJAMY-oYpz_hY-.js → c4Diagram-AHTNJAMY-BF_SaG8K.js} +2 -2
- package/dist/public/assets/{c4Diagram-AHTNJAMY-oYpz_hY-.js.map → c4Diagram-AHTNJAMY-BF_SaG8K.js.map} +1 -1
- package/dist/public/assets/channel-Chx65KuQ.js +2 -0
- package/dist/public/assets/{channel-D4DHyqBz.js.map → channel-Chx65KuQ.js.map} +1 -1
- package/dist/public/assets/{chunk-4BX2VUAB-BJASz7t9.js → chunk-4BX2VUAB-JU2t15qH.js} +2 -2
- package/dist/public/assets/{chunk-4BX2VUAB-BJASz7t9.js.map → chunk-4BX2VUAB-JU2t15qH.js.map} +1 -1
- package/dist/public/assets/{chunk-4TB4RGXK-BmBuFub9.js → chunk-4TB4RGXK-BqjiENIB.js} +2 -2
- package/dist/public/assets/{chunk-4TB4RGXK-BmBuFub9.js.map → chunk-4TB4RGXK-BqjiENIB.js.map} +1 -1
- package/dist/public/assets/chunk-55IACEB6-CwPOewiq.js +2 -0
- package/dist/public/assets/{chunk-55IACEB6-DpRX_xgZ.js.map → chunk-55IACEB6-CwPOewiq.js.map} +1 -1
- package/dist/public/assets/{chunk-EDXVE4YY-CjyvbsHL.js → chunk-EDXVE4YY-CeAdKClT.js} +2 -2
- package/dist/public/assets/{chunk-EDXVE4YY-CjyvbsHL.js.map → chunk-EDXVE4YY-CeAdKClT.js.map} +1 -1
- package/dist/public/assets/{chunk-FMBD7UC4-CopKIX9d.js → chunk-FMBD7UC4-BmV52drU.js} +2 -2
- package/dist/public/assets/{chunk-FMBD7UC4-CopKIX9d.js.map → chunk-FMBD7UC4-BmV52drU.js.map} +1 -1
- package/dist/public/assets/{chunk-OYMX7WX6-Qy94MD6F.js → chunk-OYMX7WX6-DMQ6OBmH.js} +2 -2
- package/dist/public/assets/{chunk-OYMX7WX6-Qy94MD6F.js.map → chunk-OYMX7WX6-DMQ6OBmH.js.map} +1 -1
- package/dist/public/assets/{chunk-QZHKN3VN-q0VhU4fD.js → chunk-QZHKN3VN-BbECxIVF.js} +2 -2
- package/dist/public/assets/{chunk-QZHKN3VN-q0VhU4fD.js.map → chunk-QZHKN3VN-BbECxIVF.js.map} +1 -1
- package/dist/public/assets/{chunk-YZCP3GAM-CLHu-ytS.js → chunk-YZCP3GAM-LV4b5o7A.js} +2 -2
- package/dist/public/assets/{chunk-YZCP3GAM-CLHu-ytS.js.map → chunk-YZCP3GAM-LV4b5o7A.js.map} +1 -1
- package/dist/public/assets/classDiagram-6PBFFD2Q-CuGjbxVu.js +2 -0
- package/dist/public/assets/{classDiagram-6PBFFD2Q-C_GcL8CY.js.map → classDiagram-6PBFFD2Q-CuGjbxVu.js.map} +1 -1
- package/dist/public/assets/classDiagram-v2-HSJHXN6E-CuGjbxVu.js +2 -0
- package/dist/public/assets/{classDiagram-v2-HSJHXN6E-C_GcL8CY.js.map → classDiagram-v2-HSJHXN6E-CuGjbxVu.js.map} +1 -1
- package/dist/public/assets/clone-B5zl9853.js +2 -0
- package/dist/public/assets/{clone-Cpn4E8-V.js.map → clone-B5zl9853.js.map} +1 -1
- package/dist/public/assets/{cose-bilkent-S5V4N54A-BCNrUYFS.js → cose-bilkent-S5V4N54A-NxYN3t_X.js} +2 -2
- package/dist/public/assets/{cose-bilkent-S5V4N54A-BCNrUYFS.js.map → cose-bilkent-S5V4N54A-NxYN3t_X.js.map} +1 -1
- package/dist/public/assets/{dagre-KV5264BT-BVapQShq.js → dagre-KV5264BT-DTQ94K2e.js} +2 -2
- package/dist/public/assets/{dagre-KV5264BT-BVapQShq.js.map → dagre-KV5264BT-DTQ94K2e.js.map} +1 -1
- package/dist/public/assets/{diagram-5BDNPKRD-D6B3PE9C.js → diagram-5BDNPKRD-BI7YTuYk.js} +2 -2
- package/dist/public/assets/{diagram-5BDNPKRD-D6B3PE9C.js.map → diagram-5BDNPKRD-BI7YTuYk.js.map} +1 -1
- package/dist/public/assets/{diagram-G4DWMVQ6-BaWJIFI_.js → diagram-G4DWMVQ6-CRfC6IFe.js} +2 -2
- package/dist/public/assets/{diagram-G4DWMVQ6-BaWJIFI_.js.map → diagram-G4DWMVQ6-CRfC6IFe.js.map} +1 -1
- package/dist/public/assets/{diagram-MMDJMWI5-CCy9j0cK.js → diagram-MMDJMWI5-CfWU0Xcy.js} +2 -2
- package/dist/public/assets/{diagram-MMDJMWI5-CCy9j0cK.js.map → diagram-MMDJMWI5-CfWU0Xcy.js.map} +1 -1
- package/dist/public/assets/{diagram-TYMM5635-Dlu7NmTQ.js → diagram-TYMM5635-DUUA764g.js} +2 -2
- package/dist/public/assets/{diagram-TYMM5635-Dlu7NmTQ.js.map → diagram-TYMM5635-DUUA764g.js.map} +1 -1
- package/dist/public/assets/{erDiagram-SMLLAGMA-X6FgEuRR.js → erDiagram-SMLLAGMA-CC6HpSJv.js} +2 -2
- package/dist/public/assets/{erDiagram-SMLLAGMA-X6FgEuRR.js.map → erDiagram-SMLLAGMA-CC6HpSJv.js.map} +1 -1
- package/dist/public/assets/{flowDiagram-DWJPFMVM-Bu3JFQlu.js → flowDiagram-DWJPFMVM-BcUwc3Qv.js} +2 -2
- package/dist/public/assets/{flowDiagram-DWJPFMVM-Bu3JFQlu.js.map → flowDiagram-DWJPFMVM-BcUwc3Qv.js.map} +1 -1
- package/dist/public/assets/{ganttDiagram-T4ZO3ILL-n168zCl5.js → ganttDiagram-T4ZO3ILL-BQ0OUFDb.js} +2 -2
- package/dist/public/assets/{ganttDiagram-T4ZO3ILL-n168zCl5.js.map → ganttDiagram-T4ZO3ILL-BQ0OUFDb.js.map} +1 -1
- package/dist/public/assets/{gitGraphDiagram-UUTBAWPF-DArjnyo2.js → gitGraphDiagram-UUTBAWPF-BMtxQA7c.js} +2 -2
- package/dist/public/assets/{gitGraphDiagram-UUTBAWPF-DArjnyo2.js.map → gitGraphDiagram-UUTBAWPF-BMtxQA7c.js.map} +1 -1
- package/dist/public/assets/{graph-Bfm3kqd3.js → graph-D-_LAAxI.js} +2 -2
- package/dist/public/assets/{graph-Bfm3kqd3.js.map → graph-D-_LAAxI.js.map} +1 -1
- package/dist/public/assets/{index-DMY07AUl.js → index-B08bCLVX.js} +3 -3
- package/dist/public/assets/{index-DMY07AUl.js.map → index-B08bCLVX.js.map} +1 -1
- package/dist/public/assets/{infoDiagram-42DDH7IO-pBrtdPq_.js → infoDiagram-42DDH7IO-Bamzqbzk.js} +2 -2
- package/dist/public/assets/{infoDiagram-42DDH7IO-pBrtdPq_.js.map → infoDiagram-42DDH7IO-Bamzqbzk.js.map} +1 -1
- package/dist/public/assets/{ishikawaDiagram-UXIWVN3A-BDIZnWTx.js → ishikawaDiagram-UXIWVN3A-zKsJsf_v.js} +2 -2
- package/dist/public/assets/{ishikawaDiagram-UXIWVN3A-BDIZnWTx.js.map → ishikawaDiagram-UXIWVN3A-zKsJsf_v.js.map} +1 -1
- package/dist/public/assets/{journeyDiagram-VCZTEJTY-DmSjb-PU.js → journeyDiagram-VCZTEJTY-C-6pvS6y.js} +2 -2
- package/dist/public/assets/{journeyDiagram-VCZTEJTY-DmSjb-PU.js.map → journeyDiagram-VCZTEJTY-C-6pvS6y.js.map} +1 -1
- package/dist/public/assets/{kanban-definition-6JOO6SKY-C8FgUjbs.js → kanban-definition-6JOO6SKY-DS7zW-ig.js} +2 -2
- package/dist/public/assets/{kanban-definition-6JOO6SKY-C8FgUjbs.js.map → kanban-definition-6JOO6SKY-DS7zW-ig.js.map} +1 -1
- package/dist/public/assets/{layout-DJh2njKG.js → layout-CVDJjYNO.js} +2 -2
- package/dist/public/assets/{layout-DJh2njKG.js.map → layout-CVDJjYNO.js.map} +1 -1
- package/dist/public/assets/{linear-kYTx6SLx.js → linear-lUY0N4kd.js} +2 -2
- package/dist/public/assets/{linear-kYTx6SLx.js.map → linear-lUY0N4kd.js.map} +1 -1
- package/dist/public/assets/{mermaid.core-sEG8CMeA.js → mermaid.core-H7Dmb05b.js} +5 -5
- package/dist/public/assets/{mermaid.core-sEG8CMeA.js.map → mermaid.core-H7Dmb05b.js.map} +1 -1
- package/dist/public/assets/{min-Cxji_8ZQ.js → min-CXznxZeM.js} +2 -2
- package/dist/public/assets/{min-Cxji_8ZQ.js.map → min-CXznxZeM.js.map} +1 -1
- package/dist/public/assets/{mindmap-definition-QFDTVHPH-B0-J9mrb.js → mindmap-definition-QFDTVHPH-jXt705eB.js} +2 -2
- package/dist/public/assets/{mindmap-definition-QFDTVHPH-B0-J9mrb.js.map → mindmap-definition-QFDTVHPH-jXt705eB.js.map} +1 -1
- package/dist/public/assets/{pieDiagram-DEJITSTG-CDZHsOGg.js → pieDiagram-DEJITSTG-Cs44d6OE.js} +2 -2
- package/dist/public/assets/{pieDiagram-DEJITSTG-CDZHsOGg.js.map → pieDiagram-DEJITSTG-Cs44d6OE.js.map} +1 -1
- package/dist/public/assets/{quadrantDiagram-34T5L4WZ-Cr3XLn8k.js → quadrantDiagram-34T5L4WZ-CpymTeDv.js} +2 -2
- package/dist/public/assets/{quadrantDiagram-34T5L4WZ-Cr3XLn8k.js.map → quadrantDiagram-34T5L4WZ-CpymTeDv.js.map} +1 -1
- package/dist/public/assets/{requirementDiagram-MS252O5E-DhckNPK2.js → requirementDiagram-MS252O5E-RyLqPw4W.js} +2 -2
- package/dist/public/assets/{requirementDiagram-MS252O5E-DhckNPK2.js.map → requirementDiagram-MS252O5E-RyLqPw4W.js.map} +1 -1
- package/dist/public/assets/{sankeyDiagram-XADWPNL6-Cst7lZQ5.js → sankeyDiagram-XADWPNL6-DamuB2BV.js} +2 -2
- package/dist/public/assets/{sankeyDiagram-XADWPNL6-Cst7lZQ5.js.map → sankeyDiagram-XADWPNL6-DamuB2BV.js.map} +1 -1
- package/dist/public/assets/{sequenceDiagram-FGHM5R23-BSz6hk1R.js → sequenceDiagram-FGHM5R23-6-TOylZP.js} +2 -2
- package/dist/public/assets/{sequenceDiagram-FGHM5R23-BSz6hk1R.js.map → sequenceDiagram-FGHM5R23-6-TOylZP.js.map} +1 -1
- package/dist/public/assets/{stateDiagram-FHFEXIEX-racSpEdx.js → stateDiagram-FHFEXIEX-B6cKPsIC.js} +2 -2
- package/dist/public/assets/{stateDiagram-FHFEXIEX-racSpEdx.js.map → stateDiagram-FHFEXIEX-B6cKPsIC.js.map} +1 -1
- package/dist/public/assets/stateDiagram-v2-QKLJ7IA2-CXb0Znc0.js +2 -0
- package/dist/public/assets/{stateDiagram-v2-QKLJ7IA2-C8oha-gC.js.map → stateDiagram-v2-QKLJ7IA2-CXb0Znc0.js.map} +1 -1
- package/dist/public/assets/{timeline-definition-GMOUNBTQ-pBR5gxYm.js → timeline-definition-GMOUNBTQ-CdeBXF6y.js} +2 -2
- package/dist/public/assets/{timeline-definition-GMOUNBTQ-pBR5gxYm.js.map → timeline-definition-GMOUNBTQ-CdeBXF6y.js.map} +1 -1
- package/dist/public/assets/{vennDiagram-DHZGUBPP-OCE87T_5.js → vennDiagram-DHZGUBPP-D8DqpbrW.js} +2 -2
- package/dist/public/assets/{vennDiagram-DHZGUBPP-OCE87T_5.js.map → vennDiagram-DHZGUBPP-D8DqpbrW.js.map} +1 -1
- package/dist/public/assets/{wardley-RL74JXVD-691yvuUx.js → wardley-RL74JXVD-B1MU-JlC.js} +2 -2
- package/dist/public/assets/{wardley-RL74JXVD-691yvuUx.js.map → wardley-RL74JXVD-B1MU-JlC.js.map} +1 -1
- package/dist/public/assets/{wardleyDiagram-NUSXRM2D-CewxpYfR.js → wardleyDiagram-NUSXRM2D-MYKM6CF8.js} +2 -2
- package/dist/public/assets/{wardleyDiagram-NUSXRM2D-CewxpYfR.js.map → wardleyDiagram-NUSXRM2D-MYKM6CF8.js.map} +1 -1
- package/dist/public/assets/{xychartDiagram-5P7HB3ND-C_kdNFR1.js → xychartDiagram-5P7HB3ND-Brec7d4F.js} +2 -2
- package/dist/public/assets/{xychartDiagram-5P7HB3ND-C_kdNFR1.js.map → xychartDiagram-5P7HB3ND-Brec7d4F.js.map} +1 -1
- package/dist/public/index.html +1 -1
- package/package.json +1 -1
- package/dist/public/assets/channel-D4DHyqBz.js +0 -2
- package/dist/public/assets/chunk-55IACEB6-DpRX_xgZ.js +0 -2
- package/dist/public/assets/classDiagram-6PBFFD2Q-C_GcL8CY.js +0 -2
- package/dist/public/assets/classDiagram-v2-HSJHXN6E-C_GcL8CY.js +0 -2
- package/dist/public/assets/clone-Cpn4E8-V.js +0 -2
- package/dist/public/assets/stateDiagram-v2-QKLJ7IA2-C8oha-gC.js +0 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@tecture/core` are documented here.
|
|
4
4
|
|
|
5
|
+
## [0.0.8] — 2026-06-16
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Anonymous, opt-out usage telemetry** for the standalone viewer. Records only
|
|
10
|
+
the C4 diagram level and counts — never names, paths, or architecture content.
|
|
11
|
+
Disable with `TECTURE_TELEMETRY=0` or `DO_NOT_TRACK=1`; a one-time notice
|
|
12
|
+
prints on first run. (Only the standalone viewer reports; the VS Code webview
|
|
13
|
+
uses the extension's own telemetry, so there's no double-counting.)
|
|
14
|
+
- First `@tecture/core` README, documenting usage and the telemetry policy.
|
|
15
|
+
|
|
16
|
+
## [0.0.7] — 2026-06-02
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- **Copy deep-dive prompt** now copies a slash command —
|
|
21
|
+
`/architecture-docs deep-dive <id>` — instead of the longer
|
|
22
|
+
"Use the architecture-docs skill to deep-dive…" sentence. Shorter to
|
|
23
|
+
paste, and it invokes the skill directly. The id alone resolves the
|
|
24
|
+
component.
|
|
25
|
+
- Renamed the bundled skill from `tecture` to `architecture-docs`, so
|
|
26
|
+
the deep-dive prompt and skill invocation use the new name.
|
|
27
|
+
|
|
5
28
|
## [0.0.6] — 2026-06-02
|
|
6
29
|
|
|
7
30
|
### Added
|
package/README.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# @tecture/core
|
|
2
|
+
|
|
3
|
+
Standalone browser viewer for [Tecture](https://github.com/tecture-io/tecture)
|
|
4
|
+
architecture docs — render the file-based architecture in `./architecture/`
|
|
5
|
+
(JSON + Markdown) as interactive, navigable diagrams.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
From a repo that has an `architecture/` folder:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx @tecture/core
|
|
13
|
+
# → http://localhost:3000
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Options: `--port <n>`, `--architecture-path <dir>`, `--tecture-path <dir>`.
|
|
17
|
+
|
|
18
|
+
To generate the architecture in the first place, install the skill into your
|
|
19
|
+
coding agent with `npx @tecture/skill@latest` and run `/architecture-docs`.
|
|
20
|
+
|
|
21
|
+
## Telemetry
|
|
22
|
+
|
|
23
|
+
The viewer sends **anonymous** usage stats (which C4 diagram **level** is
|
|
24
|
+
viewed, and how often) to help prioritize improvements. It records only the
|
|
25
|
+
level (1/2/3) and counts — **never** your diagram names, node names, file
|
|
26
|
+
paths, or any architecture content. Events are keyed to a random id stored at
|
|
27
|
+
`~/.config/tecture/telemetry-id`.
|
|
28
|
+
|
|
29
|
+
It's opt-out: set `TECTURE_TELEMETRY=0` (or the standard `DO_NOT_TRACK=1`) to
|
|
30
|
+
disable it entirely. A one-time notice is printed on first run.
|
package/dist/cli.js
CHANGED
|
@@ -150,7 +150,7 @@ function send400(res, body) {
|
|
|
150
150
|
}
|
|
151
151
|
function createArchitectureRouter(opts) {
|
|
152
152
|
const router = Router2();
|
|
153
|
-
const { resolveSource, resolveLayoutStore } = opts;
|
|
153
|
+
const { resolveSource, resolveLayoutStore, report } = opts;
|
|
154
154
|
router.get("/", async (req, res, next) => {
|
|
155
155
|
try {
|
|
156
156
|
const source = await resolveSource(req);
|
|
@@ -195,6 +195,10 @@ function createArchitectureRouter(opts) {
|
|
|
195
195
|
edges: diagram.edges ?? []
|
|
196
196
|
};
|
|
197
197
|
res.json(body);
|
|
198
|
+
report?.capture(
|
|
199
|
+
"diagram.viewed",
|
|
200
|
+
typeof diagram.level === "number" ? { level: diagram.level } : {}
|
|
201
|
+
);
|
|
198
202
|
} catch (err) {
|
|
199
203
|
if (err instanceof DiagramNotFoundError) {
|
|
200
204
|
return send404(res, { error: "diagram_not_found", slug: err.slug });
|
|
@@ -353,7 +357,8 @@ function createApp(options) {
|
|
|
353
357
|
"/api/architecture",
|
|
354
358
|
createArchitectureRouter({
|
|
355
359
|
resolveSource: toSourceResolver(options.source),
|
|
356
|
-
resolveLayoutStore: toLayoutStoreResolver(options.layoutStore)
|
|
360
|
+
resolveLayoutStore: toLayoutStoreResolver(options.layoutStore),
|
|
361
|
+
report: options.report
|
|
357
362
|
})
|
|
358
363
|
);
|
|
359
364
|
app2.use("/api/architecture", architectureErrorHandler);
|
|
@@ -479,6 +484,72 @@ var FsLayoutStore = class {
|
|
|
479
484
|
}
|
|
480
485
|
};
|
|
481
486
|
|
|
487
|
+
// src/telemetry.ts
|
|
488
|
+
import { randomUUID } from "crypto";
|
|
489
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
490
|
+
import { homedir } from "os";
|
|
491
|
+
import { join as join2 } from "path";
|
|
492
|
+
|
|
493
|
+
// package.json
|
|
494
|
+
var version = "0.0.8";
|
|
495
|
+
|
|
496
|
+
// src/telemetry.ts
|
|
497
|
+
var POSTHOG_KEY = "phc_wTpBpKNGhboxPitLJYLSi8aGGCaYJ7fHbvLAT93edmHp";
|
|
498
|
+
var POSTHOG_HOST = "https://us.i.posthog.com";
|
|
499
|
+
var NOOP = { capture() {
|
|
500
|
+
} };
|
|
501
|
+
function disabledByEnv() {
|
|
502
|
+
const dnt = process.env.DO_NOT_TRACK;
|
|
503
|
+
if (dnt === "1" || dnt === "true") return true;
|
|
504
|
+
const t = process.env.TECTURE_TELEMETRY?.toLowerCase();
|
|
505
|
+
return t === "0" || t === "false" || t === "off" || t === "no";
|
|
506
|
+
}
|
|
507
|
+
function createTelemetry() {
|
|
508
|
+
if (POSTHOG_KEY.length === 0 || disabledByEnv()) return NOOP;
|
|
509
|
+
let distinctId;
|
|
510
|
+
let firstRun = false;
|
|
511
|
+
try {
|
|
512
|
+
const dir = join2(homedir(), ".config", "tecture");
|
|
513
|
+
const idFile = join2(dir, "telemetry-id");
|
|
514
|
+
if (existsSync2(idFile)) {
|
|
515
|
+
distinctId = readFileSync(idFile, "utf8").trim() || randomUUID();
|
|
516
|
+
} else {
|
|
517
|
+
distinctId = randomUUID();
|
|
518
|
+
mkdirSync(dir, { recursive: true });
|
|
519
|
+
writeFileSync(idFile, distinctId);
|
|
520
|
+
firstRun = true;
|
|
521
|
+
}
|
|
522
|
+
} catch {
|
|
523
|
+
distinctId = randomUUID();
|
|
524
|
+
}
|
|
525
|
+
if (firstRun) {
|
|
526
|
+
console.error(
|
|
527
|
+
"\nTecture collects anonymous usage stats (which diagram levels are viewed, how often)\nto help improve the tool. It never sends your architecture content, names, or paths.\nOpt out any time: set TECTURE_TELEMETRY=0 (or DO_NOT_TRACK=1).\n"
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
const superProps = {
|
|
531
|
+
coreVersion: version,
|
|
532
|
+
nodeVersion: process.version,
|
|
533
|
+
platform: process.platform
|
|
534
|
+
};
|
|
535
|
+
return {
|
|
536
|
+
capture(event, properties = {}) {
|
|
537
|
+
const body = JSON.stringify({
|
|
538
|
+
api_key: POSTHOG_KEY,
|
|
539
|
+
event,
|
|
540
|
+
distinct_id: distinctId,
|
|
541
|
+
properties: { ...superProps, ...properties, $process_person_profile: false }
|
|
542
|
+
});
|
|
543
|
+
void fetch(`${POSTHOG_HOST}/capture/`, {
|
|
544
|
+
method: "POST",
|
|
545
|
+
headers: { "content-type": "application/json" },
|
|
546
|
+
body
|
|
547
|
+
}).catch(() => {
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
482
553
|
// src/cli.ts
|
|
483
554
|
function parseArgs(argv) {
|
|
484
555
|
let port2 = Number(process.env.PORT ?? 3e3);
|
|
@@ -519,13 +590,16 @@ function parseArgs(argv) {
|
|
|
519
590
|
};
|
|
520
591
|
}
|
|
521
592
|
var { port, architecturePath, tecturePath } = parseArgs(process.argv.slice(2));
|
|
593
|
+
var telemetry = createTelemetry();
|
|
522
594
|
var app = createApp({
|
|
523
595
|
source: new FsArchitectureDataSource(architecturePath),
|
|
524
|
-
layoutStore: new FsLayoutStore(tecturePath)
|
|
596
|
+
layoutStore: new FsLayoutStore(tecturePath),
|
|
597
|
+
report: telemetry
|
|
525
598
|
});
|
|
526
599
|
app.listen(port, () => {
|
|
527
600
|
console.log(`Tecture IO running at http://localhost:${port}`);
|
|
528
601
|
console.log(`Architecture root: ${architecturePath}`);
|
|
529
602
|
console.log(`Tecture state: ${tecturePath}`);
|
|
603
|
+
telemetry.capture("viewer.opened");
|
|
530
604
|
});
|
|
531
605
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/server.ts","../src/routes/health.ts","../src/routes/architecture.ts","../../shared/src/validators.ts","../src/source/fs.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { createApp } from \"./server.js\";\nimport { FsArchitectureDataSource, FsLayoutStore } from \"./source/fs.js\";\n\ninterface CliOptions {\n port: number;\n architecturePath: string;\n tecturePath: string;\n}\n\nfunction parseArgs(argv: string[]): CliOptions {\n let port = Number(process.env.PORT ?? 3000);\n let architecturePath = process.env.ARCHITECTURE_PATH;\n let tecturePath = process.env.TECTURE_PATH;\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n if (arg === \"--port\" || arg === \"-p\") {\n const next = argv[i + 1];\n if (!next) throw new Error(`Missing value for ${arg}`);\n const parsed = Number(next);\n if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {\n throw new Error(`Invalid port: ${next}`);\n }\n port = parsed;\n i++;\n } else if (arg === \"--architecture-path\" || arg === \"-a\") {\n const next = argv[i + 1];\n if (!next) throw new Error(`Missing value for ${arg}`);\n architecturePath = next;\n i++;\n } else if (arg === \"--tecture-path\" || arg === \"-t\") {\n const next = argv[i + 1];\n if (!next) throw new Error(`Missing value for ${arg}`);\n tecturePath = next;\n i++;\n } else if (arg === \"--help\" || arg === \"-h\") {\n console.log(\n \"Usage: tecture-io [--port <number>] [--architecture-path <path>] [--tecture-path <path>]\",\n );\n process.exit(0);\n }\n }\n return {\n port,\n architecturePath: resolve(architecturePath ?? \"architecture\"),\n tecturePath: resolve(tecturePath ?? \"architecture/.tecture\"),\n };\n}\n\nconst { port, architecturePath, tecturePath } = parseArgs(process.argv.slice(2));\nconst app = createApp({\n source: new FsArchitectureDataSource(architecturePath),\n layoutStore: new FsLayoutStore(tecturePath),\n});\n\napp.listen(port, () => {\n console.log(`Tecture IO running at http://localhost:${port}`);\n console.log(`Architecture root: ${architecturePath}`);\n console.log(`Tecture state: ${tecturePath}`);\n});\n","import { existsSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport express, { type Express, type Request } from \"express\";\nimport { healthRouter } from \"./routes/health.js\";\nimport {\n architectureErrorHandler,\n createArchitectureRouter,\n type LayoutStoreResolver,\n type SourceResolver,\n} from \"./routes/architecture.js\";\nimport type {\n ArchitectureDataSource,\n LayoutStore,\n} from \"./source/types.js\";\n\nexport interface CreateAppOptions {\n source: ArchitectureDataSource | SourceResolver;\n layoutStore?: LayoutStore | LayoutStoreResolver | null;\n}\n\nfunction toSourceResolver(\n value: ArchitectureDataSource | SourceResolver,\n): SourceResolver {\n if (typeof value === \"function\") return value as SourceResolver;\n return () => value;\n}\n\nfunction toLayoutStoreResolver(\n value: LayoutStore | LayoutStoreResolver | null | undefined,\n): LayoutStoreResolver | undefined {\n if (value == null) return undefined;\n if (typeof value === \"function\") return value as LayoutStoreResolver;\n return () => value;\n}\n\nexport function createApp(options: CreateAppOptions): Express {\n const app = express();\n\n app.use(express.json({ limit: \"256kb\" }));\n app.use(\"/api/health\", healthRouter);\n app.use(\n \"/api/architecture\",\n createArchitectureRouter({\n resolveSource: toSourceResolver(options.source),\n resolveLayoutStore: toLayoutStoreResolver(options.layoutStore),\n }),\n );\n app.use(\"/api/architecture\", architectureErrorHandler);\n\n const publicDir = fileURLToPath(new URL(\"./public\", import.meta.url));\n\n if (existsSync(publicDir)) {\n app.use(express.static(publicDir));\n app.get(\"*\", (req: Request, res, next) => {\n if (req.path.startsWith(\"/api/\")) return next();\n res.sendFile(fileURLToPath(new URL(\"./public/index.html\", import.meta.url)));\n });\n }\n\n return app;\n}\n","import { Router } from \"express\";\nimport type { ApiHealthResponse } from \"@tecture/shared\";\n\nexport const healthRouter: Router = Router();\n\nhealthRouter.get(\"/\", (_req, res) => {\n const body: ApiHealthResponse = {\n status: \"ok\",\n uptime: process.uptime(),\n timestamp: new Date().toISOString(),\n };\n res.json(body);\n});\n","import { Router, type Request, type Response, type NextFunction } from \"express\";\nimport type {\n ApiArchitectureError,\n ApiDiagram,\n ApiDiagramEdges,\n ApiDiagramLayout,\n ApiDiagramLayoutUpdate,\n ApiDiagramNodes,\n ApiNodeDetail,\n ManifestFile,\n} from \"@tecture/shared\";\nimport {\n type ArchitectureDataSource,\n DescriptionNotFoundError,\n DiagramNotFoundError,\n type LayoutStore,\n LayoutInvalidError,\n NodeNotFoundError,\n SLUG_RE,\n buildArchitectureSummary,\n emptyLayout,\n findNode,\n} from \"../source/types.js\";\n\nexport type SourceResolver = (\n req: Request,\n) => ArchitectureDataSource | Promise<ArchitectureDataSource>;\n\nexport type LayoutStoreResolver = (\n req: Request,\n) => LayoutStore | null | Promise<LayoutStore | null>;\n\nexport interface ArchitectureRouterOptions {\n resolveSource: SourceResolver;\n resolveLayoutStore?: LayoutStoreResolver;\n}\n\nfunction send501(res: Response, body: ApiArchitectureError): void {\n res.status(501).json(body);\n}\n\nfunction send404(res: Response, body: ApiArchitectureError): void {\n res.status(404).json(body);\n}\n\nfunction send400(res: Response, body: ApiArchitectureError): void {\n res.status(400).json(body);\n}\n\nexport function createArchitectureRouter(\n opts: ArchitectureRouterOptions,\n): Router {\n const router = Router();\n const { resolveSource, resolveLayoutStore } = opts;\n\n router.get(\"/\", async (req, res, next) => {\n try {\n const source = await resolveSource(req);\n const summary = await buildArchitectureSummary(source);\n res.json(summary);\n } catch (err) {\n next(err);\n }\n });\n\n router.get(\"/manifest\", async (req, res, next) => {\n try {\n const source = await resolveSource(req);\n const manifest: ManifestFile = await source.loadManifest();\n res.json(manifest);\n } catch (err) {\n next(err);\n }\n });\n\n router.get(\"/diagrams\", async (req, res, next) => {\n try {\n const source = await resolveSource(req);\n const summary = await buildArchitectureSummary(source);\n res.json(summary.diagrams);\n } catch (err) {\n next(err);\n }\n });\n\n router.get(\"/diagrams/:diagramId\", async (req, res, next) => {\n const { diagramId } = req.params;\n if (!SLUG_RE.test(diagramId)) {\n return send404(res, { error: \"diagram_not_found\", slug: diagramId });\n }\n try {\n const source = await resolveSource(req);\n const diagram = await source.loadDiagram(diagramId);\n const body: ApiDiagram = {\n slug: diagramId,\n name: diagram.name,\n level: diagram.level,\n meta: diagram.meta,\n nodes: diagram.nodes,\n edges: diagram.edges ?? [],\n };\n res.json(body);\n } catch (err) {\n if (err instanceof DiagramNotFoundError) {\n return send404(res, { error: \"diagram_not_found\", slug: err.slug });\n }\n next(err);\n }\n });\n\n router.get(\"/diagrams/:diagramId/nodes\", async (req, res, next) => {\n const { diagramId } = req.params;\n if (!SLUG_RE.test(diagramId)) {\n return send404(res, { error: \"diagram_not_found\", slug: diagramId });\n }\n try {\n const source = await resolveSource(req);\n const diagram = await source.loadDiagram(diagramId);\n const body: ApiDiagramNodes = { diagramId, nodes: diagram.nodes };\n res.json(body);\n } catch (err) {\n if (err instanceof DiagramNotFoundError) {\n return send404(res, { error: \"diagram_not_found\", slug: err.slug });\n }\n next(err);\n }\n });\n\n router.get(\"/diagrams/:diagramId/edges\", async (req, res, next) => {\n const { diagramId } = req.params;\n if (!SLUG_RE.test(diagramId)) {\n return send404(res, { error: \"diagram_not_found\", slug: diagramId });\n }\n try {\n const source = await resolveSource(req);\n const diagram = await source.loadDiagram(diagramId);\n const body: ApiDiagramEdges = { diagramId, edges: diagram.edges ?? [] };\n res.json(body);\n } catch (err) {\n if (err instanceof DiagramNotFoundError) {\n return send404(res, { error: \"diagram_not_found\", slug: err.slug });\n }\n next(err);\n }\n });\n\n router.get(\"/diagrams/:diagramId/layout\", async (req, res, next) => {\n const { diagramId } = req.params;\n if (!SLUG_RE.test(diagramId)) {\n return send404(res, { error: \"diagram_not_found\", slug: diagramId });\n }\n try {\n const source = await resolveSource(req);\n await source.loadDiagram(diagramId);\n const store = resolveLayoutStore ? await resolveLayoutStore(req) : null;\n const layout: ApiDiagramLayout = store\n ? await store.loadLayout(diagramId)\n : emptyLayout(diagramId);\n res.json(layout);\n } catch (err) {\n if (err instanceof DiagramNotFoundError) {\n return send404(res, { error: \"diagram_not_found\", slug: err.slug });\n }\n next(err);\n }\n });\n\n router.put(\"/diagrams/:diagramId/layout\", async (req, res, next) => {\n const { diagramId } = req.params;\n if (!SLUG_RE.test(diagramId)) {\n return send404(res, { error: \"diagram_not_found\", slug: diagramId });\n }\n try {\n const source = await resolveSource(req);\n await source.loadDiagram(diagramId);\n const store = resolveLayoutStore ? await resolveLayoutStore(req) : null;\n if (!store) {\n return send501(res, {\n error: \"layout_not_supported\",\n message: \"Layout persistence is not configured for this data source\",\n });\n }\n const update = req.body as ApiDiagramLayoutUpdate;\n const saved = await store.saveLayout(diagramId, update);\n res.json(saved);\n } catch (err) {\n if (err instanceof DiagramNotFoundError) {\n return send404(res, { error: \"diagram_not_found\", slug: err.slug });\n }\n if (err instanceof LayoutInvalidError) {\n return send400(res, { error: \"layout_invalid\", message: err.message });\n }\n next(err);\n }\n });\n\n router.get(\"/nodes/:nodeId\", async (req, res, next) => {\n const { nodeId } = req.params;\n if (!SLUG_RE.test(nodeId)) {\n return send404(res, { error: \"node_not_found\", id: nodeId });\n }\n try {\n const source = await resolveSource(req);\n const { node, diagramId } = await findNode(source, nodeId);\n const description = await source.loadDescription(nodeId);\n const body: ApiNodeDetail = { ...node, diagramId, description };\n res.json(body);\n } catch (err) {\n if (err instanceof NodeNotFoundError) {\n return send404(res, { error: \"node_not_found\", id: err.id });\n }\n if (err instanceof DescriptionNotFoundError) {\n return send404(res, { error: \"description_not_found\", id: err.id });\n }\n next(err);\n }\n });\n\n router.get(\"/nodes/:nodeId/description\", async (req, res, next) => {\n const { nodeId } = req.params;\n if (!SLUG_RE.test(nodeId)) {\n return send404(res, { error: \"description_not_found\", id: nodeId });\n }\n try {\n const source = await resolveSource(req);\n await findNode(source, nodeId);\n const markdown = await source.loadDescription(nodeId);\n res.type(\"text/markdown; charset=utf-8\").send(markdown);\n } catch (err) {\n if (err instanceof NodeNotFoundError) {\n return send404(res, { error: \"node_not_found\", id: err.id });\n }\n if (err instanceof DescriptionNotFoundError) {\n return send404(res, { error: \"description_not_found\", id: err.id });\n }\n next(err);\n }\n });\n\n return router;\n}\n\nexport function architectureErrorHandler(\n err: unknown,\n _req: Request,\n res: Response,\n next: NextFunction,\n): void {\n if (res.headersSent) return next(err);\n const message = err instanceof Error ? err.message : String(err);\n const body: ApiArchitectureError = {\n error: \"architecture_unreadable\",\n message,\n };\n res.status(500).json(body);\n}\n","import type {\n ApiArchitectureSummary,\n ApiDiagramLayoutUpdate,\n ApiDiagramSummary,\n ArchitectureNode,\n DiagramFile,\n DiagramLayoutFile,\n ManifestFile,\n NodeLayoutEntry,\n SourceHost,\n} from \"./index\";\n\nexport const SLUG_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;\n\nexport class DiagramNotFoundError extends Error {\n constructor(public readonly slug: string) {\n super(`Diagram not found: ${slug}`);\n this.name = \"DiagramNotFoundError\";\n }\n}\n\nexport class NodeNotFoundError extends Error {\n constructor(public readonly id: string) {\n super(`Node not found: ${id}`);\n this.name = \"NodeNotFoundError\";\n }\n}\n\nexport class DescriptionNotFoundError extends Error {\n constructor(public readonly id: string) {\n super(`Description not found: ${id}`);\n this.name = \"DescriptionNotFoundError\";\n }\n}\n\nexport class LayoutInvalidError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"LayoutInvalidError\";\n }\n}\n\nexport interface ArchitectureDataSource {\n loadManifest(): Promise<ManifestFile>;\n loadDiagram(slug: string): Promise<DiagramFile>;\n loadDescription(nodeId: string): Promise<string>;\n}\n\nexport interface LayoutStore {\n loadLayout(slug: string): Promise<DiagramLayoutFile>;\n saveLayout(\n slug: string,\n update: ApiDiagramLayoutUpdate,\n ): Promise<DiagramLayoutFile>;\n}\n\nexport function isFiniteNumber(v: unknown): v is number {\n return typeof v === \"number\" && Number.isFinite(v);\n}\n\nexport function isValidLayoutEntry(v: unknown): v is NodeLayoutEntry {\n if (!v || typeof v !== \"object\") return false;\n const e = v as Record<string, unknown>;\n if (!isFiniteNumber(e.x) || !isFiniteNumber(e.y)) return false;\n if (\"width\" in e && e.width !== undefined && !isFiniteNumber(e.width)) return false;\n if (\"height\" in e && e.height !== undefined && !isFiniteNumber(e.height)) return false;\n return true;\n}\n\nexport function emptyLayout(slug: string): DiagramLayoutFile {\n return { version: 1, diagramId: slug, updatedAt: \"\", nodes: {} };\n}\n\nexport function normalizeLayoutUpdate(\n slug: string,\n update: ApiDiagramLayoutUpdate,\n): DiagramLayoutFile {\n if (!SLUG_RE.test(slug)) {\n throw new LayoutInvalidError(`invalid diagram slug: ${slug}`);\n }\n if (\n !update ||\n typeof update !== \"object\" ||\n !update.nodes ||\n typeof update.nodes !== \"object\"\n ) {\n throw new LayoutInvalidError(\n \"body must be { nodes: Record<string, { x, y, width?, height? }> }\",\n );\n }\n const nodes: Record<string, NodeLayoutEntry> = {};\n for (const [id, entry] of Object.entries(update.nodes)) {\n if (!SLUG_RE.test(id)) {\n throw new LayoutInvalidError(`invalid node id: ${id}`);\n }\n if (!isValidLayoutEntry(entry)) {\n throw new LayoutInvalidError(`invalid layout entry for ${id}`);\n }\n const out: NodeLayoutEntry = { x: entry.x, y: entry.y };\n if (isFiniteNumber(entry.width)) out.width = entry.width;\n if (isFiniteNumber(entry.height)) out.height = entry.height;\n nodes[id] = out;\n }\n return {\n version: 1,\n diagramId: slug,\n updatedAt: new Date().toISOString(),\n nodes,\n };\n}\n\nexport async function buildArchitectureSummary(\n source: ArchitectureDataSource,\n): Promise<ApiArchitectureSummary> {\n const manifest = await source.loadManifest();\n const diagrams = await Promise.all(\n manifest.diagrams.map(async (slug): Promise<ApiDiagramSummary> => {\n const diagram = await source.loadDiagram(slug);\n return {\n slug,\n name: diagram.name,\n level: diagram.level,\n nodeCount: diagram.nodes?.length ?? 0,\n edgeCount: diagram.edges?.length ?? 0,\n };\n }),\n );\n return {\n name: manifest.name,\n description: manifest.description,\n source: manifest.source,\n sourceHost: manifest.sourceHost,\n topDiagram: manifest.topDiagram,\n diagrams,\n };\n}\n\nconst HOST_DOMAINS: Array<{ host: SourceHost; match: RegExp }> = [\n { host: \"github\", match: /(^|\\.)github\\.com$/i },\n { host: \"gitlab\", match: /(^|\\.)gitlab\\.com$/i },\n { host: \"bitbucket\", match: /(^|\\.)bitbucket\\.org$/i },\n];\n\nfunction inferSourceHost(source: string): SourceHost | undefined {\n // Extract the hostname without relying on the URL global (kept lib-agnostic for shared):\n // scheme://[userinfo@]host[:port]/...\n const hostname = /^[a-z][a-z0-9+.-]*:\\/\\/(?:[^@/]*@)?([^:/?#]+)/i.exec(source)?.[1];\n if (!hostname) return undefined;\n return HOST_DOMAINS.find(({ match }) => match.test(hostname))?.host;\n}\n\n/**\n * Build a web URL that views `path` (a repo-root-relative file or directory, trailing \"/\" = dir)\n * on the source repo host. `HEAD` resolves to the default branch so links stay current. Falls back\n * to the repo root when the host is unknown, and returns undefined when there is no source.\n */\nexport function buildSourceUrl(\n source: string | undefined,\n host: SourceHost | undefined,\n path: string,\n): string | undefined {\n if (!source) return undefined;\n const base = source.replace(/\\/+$/, \"\").replace(/\\.git$/i, \"\");\n const resolvedHost = host ?? inferSourceHost(base);\n const isDir = path.endsWith(\"/\");\n const encoded = path\n .replace(/^\\/+/, \"\")\n .replace(/\\/+$/, \"\")\n .split(\"/\")\n .map(encodeURIComponent)\n .join(\"/\");\n if (!encoded) return base;\n switch (resolvedHost) {\n case \"github\":\n return `${base}/${isDir ? \"tree\" : \"blob\"}/HEAD/${encoded}`;\n case \"gitlab\":\n return `${base}/-/${isDir ? \"tree\" : \"blob\"}/HEAD/${encoded}`;\n case \"bitbucket\":\n return `${base}/src/HEAD/${encoded}`;\n default:\n return base;\n }\n}\n\nexport async function findNode(\n source: ArchitectureDataSource,\n nodeId: string,\n): Promise<{ node: ArchitectureNode; diagramId: string }> {\n if (!SLUG_RE.test(nodeId)) throw new NodeNotFoundError(nodeId);\n const manifest = await source.loadManifest();\n for (const slug of manifest.diagrams) {\n let diagram: DiagramFile;\n try {\n diagram = await source.loadDiagram(slug);\n } catch (err) {\n if (err instanceof DiagramNotFoundError) continue;\n throw err;\n }\n const match = diagram.nodes?.find((n) => n.id === nodeId);\n if (match) return { node: match, diagramId: slug };\n }\n throw new NodeNotFoundError(nodeId);\n}\n","import { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { dirname, join, resolve, sep } from \"node:path\";\nimport type {\n ApiDiagramLayoutUpdate,\n DiagramFile,\n DiagramLayoutFile,\n ManifestFile,\n NodeLayoutEntry,\n} from \"@tecture/shared\";\nimport {\n type ArchitectureDataSource,\n DescriptionNotFoundError,\n DiagramNotFoundError,\n type LayoutStore,\n SLUG_RE,\n emptyLayout,\n isFiniteNumber,\n isValidLayoutEntry,\n normalizeLayoutUpdate,\n} from \"./types.js\";\n\nexport function safeJoin(root: string, ...segments: string[]): string {\n const absRoot = resolve(root);\n const candidate = resolve(join(absRoot, ...segments));\n const guard = absRoot.endsWith(sep) ? absRoot : absRoot + sep;\n if (candidate !== absRoot && !candidate.startsWith(guard)) {\n throw new Error(`Path escapes architecture root: ${candidate}`);\n }\n return candidate;\n}\n\nfunction isNodeErrnoError(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && \"code\" in err;\n}\n\nasync function loadJsonFile<T>(path: string): Promise<T> {\n const raw = await readFile(path, \"utf8\");\n return JSON.parse(raw) as T;\n}\n\nexport class FsArchitectureDataSource implements ArchitectureDataSource {\n constructor(private readonly root: string) {}\n\n async loadManifest(): Promise<ManifestFile> {\n const path = safeJoin(this.root, \"manifest.json\");\n return loadJsonFile<ManifestFile>(path);\n }\n\n async loadDiagram(slug: string): Promise<DiagramFile> {\n if (!SLUG_RE.test(slug)) throw new DiagramNotFoundError(slug);\n const path = safeJoin(this.root, \"diagrams\", `${slug}.json`);\n try {\n return await loadJsonFile<DiagramFile>(path);\n } catch (err) {\n if (isNodeErrnoError(err) && err.code === \"ENOENT\") {\n throw new DiagramNotFoundError(slug);\n }\n throw err;\n }\n }\n\n async loadDescription(nodeId: string): Promise<string> {\n if (!SLUG_RE.test(nodeId)) throw new DescriptionNotFoundError(nodeId);\n const path = safeJoin(this.root, \"descriptions\", `${nodeId}.md`);\n try {\n return await readFile(path, \"utf8\");\n } catch (err) {\n if (isNodeErrnoError(err) && err.code === \"ENOENT\") {\n throw new DescriptionNotFoundError(nodeId);\n }\n throw err;\n }\n }\n}\n\nexport class FsLayoutStore implements LayoutStore {\n constructor(private readonly tectureRoot: string) {}\n\n private path(slug: string): string {\n return safeJoin(this.tectureRoot, \"layouts\", `${slug}.json`);\n }\n\n async loadLayout(slug: string): Promise<DiagramLayoutFile> {\n if (!SLUG_RE.test(slug)) return emptyLayout(slug);\n const path = this.path(slug);\n let raw: string;\n try {\n raw = await readFile(path, \"utf8\");\n } catch (err) {\n if (isNodeErrnoError(err) && err.code === \"ENOENT\") return emptyLayout(slug);\n console.warn(`[tecture] failed to read layout ${path}: ${String(err)}`);\n return emptyLayout(slug);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n console.warn(`[tecture] malformed layout JSON at ${path}: ${String(err)}`);\n return emptyLayout(slug);\n }\n\n if (!parsed || typeof parsed !== \"object\") return emptyLayout(slug);\n const p = parsed as Record<string, unknown>;\n const nodes: Record<string, NodeLayoutEntry> = {};\n if (p.nodes && typeof p.nodes === \"object\") {\n for (const [id, entry] of Object.entries(p.nodes as Record<string, unknown>)) {\n if (!SLUG_RE.test(id)) continue;\n if (!isValidLayoutEntry(entry)) continue;\n const out: NodeLayoutEntry = { x: entry.x, y: entry.y };\n if (isFiniteNumber(entry.width)) out.width = entry.width;\n if (isFiniteNumber(entry.height)) out.height = entry.height;\n nodes[id] = out;\n }\n }\n return {\n version: 1,\n diagramId: slug,\n updatedAt: typeof p.updatedAt === \"string\" ? p.updatedAt : \"\",\n nodes,\n };\n }\n\n async saveLayout(\n slug: string,\n update: ApiDiagramLayoutUpdate,\n ): Promise<DiagramLayoutFile> {\n const file = normalizeLayoutUpdate(slug, update);\n const path = this.path(slug);\n await mkdir(dirname(path), { recursive: true });\n const tmp = `${path}.tmp`;\n await writeFile(tmp, `${JSON.stringify(file, null, 2)}\\n`, \"utf8\");\n await rename(tmp, path);\n return file;\n }\n}\n"],"mappings":"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","names":["resolve","Router","Router","app","port","architecturePath","tecturePath","resolve"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/server.ts","../src/routes/health.ts","../src/routes/architecture.ts","../../shared/src/validators.ts","../src/source/fs.ts","../src/telemetry.ts","../package.json"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { createApp } from \"./server.js\";\nimport { FsArchitectureDataSource, FsLayoutStore } from \"./source/fs.js\";\nimport { createTelemetry } from \"./telemetry.js\";\n\ninterface CliOptions {\n port: number;\n architecturePath: string;\n tecturePath: string;\n}\n\nfunction parseArgs(argv: string[]): CliOptions {\n let port = Number(process.env.PORT ?? 3000);\n let architecturePath = process.env.ARCHITECTURE_PATH;\n let tecturePath = process.env.TECTURE_PATH;\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n if (arg === \"--port\" || arg === \"-p\") {\n const next = argv[i + 1];\n if (!next) throw new Error(`Missing value for ${arg}`);\n const parsed = Number(next);\n if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {\n throw new Error(`Invalid port: ${next}`);\n }\n port = parsed;\n i++;\n } else if (arg === \"--architecture-path\" || arg === \"-a\") {\n const next = argv[i + 1];\n if (!next) throw new Error(`Missing value for ${arg}`);\n architecturePath = next;\n i++;\n } else if (arg === \"--tecture-path\" || arg === \"-t\") {\n const next = argv[i + 1];\n if (!next) throw new Error(`Missing value for ${arg}`);\n tecturePath = next;\n i++;\n } else if (arg === \"--help\" || arg === \"-h\") {\n console.log(\n \"Usage: tecture-io [--port <number>] [--architecture-path <path>] [--tecture-path <path>]\",\n );\n process.exit(0);\n }\n }\n return {\n port,\n architecturePath: resolve(architecturePath ?? \"architecture\"),\n tecturePath: resolve(tecturePath ?? \"architecture/.tecture\"),\n };\n}\n\nconst { port, architecturePath, tecturePath } = parseArgs(process.argv.slice(2));\nconst telemetry = createTelemetry();\nconst app = createApp({\n source: new FsArchitectureDataSource(architecturePath),\n layoutStore: new FsLayoutStore(tecturePath),\n report: telemetry,\n});\n\napp.listen(port, () => {\n console.log(`Tecture IO running at http://localhost:${port}`);\n console.log(`Architecture root: ${architecturePath}`);\n console.log(`Tecture state: ${tecturePath}`);\n telemetry.capture(\"viewer.opened\");\n});\n","import { existsSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport express, { type Express, type Request } from \"express\";\nimport { healthRouter } from \"./routes/health.js\";\nimport {\n architectureErrorHandler,\n createArchitectureRouter,\n type LayoutStoreResolver,\n type SourceResolver,\n} from \"./routes/architecture.js\";\nimport type {\n ArchitectureDataSource,\n LayoutStore,\n} from \"./source/types.js\";\nimport type { Reporter } from \"./telemetry.js\";\n\nexport interface CreateAppOptions {\n source: ArchitectureDataSource | SourceResolver;\n layoutStore?: LayoutStore | LayoutStoreResolver | null;\n /** Optional anonymous usage reporter (standalone viewer only). */\n report?: Reporter;\n}\n\nfunction toSourceResolver(\n value: ArchitectureDataSource | SourceResolver,\n): SourceResolver {\n if (typeof value === \"function\") return value as SourceResolver;\n return () => value;\n}\n\nfunction toLayoutStoreResolver(\n value: LayoutStore | LayoutStoreResolver | null | undefined,\n): LayoutStoreResolver | undefined {\n if (value == null) return undefined;\n if (typeof value === \"function\") return value as LayoutStoreResolver;\n return () => value;\n}\n\nexport function createApp(options: CreateAppOptions): Express {\n const app = express();\n\n app.use(express.json({ limit: \"256kb\" }));\n app.use(\"/api/health\", healthRouter);\n app.use(\n \"/api/architecture\",\n createArchitectureRouter({\n resolveSource: toSourceResolver(options.source),\n resolveLayoutStore: toLayoutStoreResolver(options.layoutStore),\n report: options.report,\n }),\n );\n app.use(\"/api/architecture\", architectureErrorHandler);\n\n const publicDir = fileURLToPath(new URL(\"./public\", import.meta.url));\n\n if (existsSync(publicDir)) {\n app.use(express.static(publicDir));\n app.get(\"*\", (req: Request, res, next) => {\n if (req.path.startsWith(\"/api/\")) return next();\n res.sendFile(fileURLToPath(new URL(\"./public/index.html\", import.meta.url)));\n });\n }\n\n return app;\n}\n","import { Router } from \"express\";\nimport type { ApiHealthResponse } from \"@tecture/shared\";\n\nexport const healthRouter: Router = Router();\n\nhealthRouter.get(\"/\", (_req, res) => {\n const body: ApiHealthResponse = {\n status: \"ok\",\n uptime: process.uptime(),\n timestamp: new Date().toISOString(),\n };\n res.json(body);\n});\n","import { Router, type Request, type Response, type NextFunction } from \"express\";\nimport type {\n ApiArchitectureError,\n ApiDiagram,\n ApiDiagramEdges,\n ApiDiagramLayout,\n ApiDiagramLayoutUpdate,\n ApiDiagramNodes,\n ApiNodeDetail,\n ManifestFile,\n} from \"@tecture/shared\";\nimport {\n type ArchitectureDataSource,\n DescriptionNotFoundError,\n DiagramNotFoundError,\n type LayoutStore,\n LayoutInvalidError,\n NodeNotFoundError,\n SLUG_RE,\n buildArchitectureSummary,\n emptyLayout,\n findNode,\n} from \"../source/types.js\";\nimport type { Reporter } from \"../telemetry.js\";\n\nexport type SourceResolver = (\n req: Request,\n) => ArchitectureDataSource | Promise<ArchitectureDataSource>;\n\nexport type LayoutStoreResolver = (\n req: Request,\n) => LayoutStore | null | Promise<LayoutStore | null>;\n\nexport interface ArchitectureRouterOptions {\n resolveSource: SourceResolver;\n resolveLayoutStore?: LayoutStoreResolver;\n /** Optional anonymous usage reporter (standalone viewer only). */\n report?: Reporter;\n}\n\nfunction send501(res: Response, body: ApiArchitectureError): void {\n res.status(501).json(body);\n}\n\nfunction send404(res: Response, body: ApiArchitectureError): void {\n res.status(404).json(body);\n}\n\nfunction send400(res: Response, body: ApiArchitectureError): void {\n res.status(400).json(body);\n}\n\nexport function createArchitectureRouter(\n opts: ArchitectureRouterOptions,\n): Router {\n const router = Router();\n const { resolveSource, resolveLayoutStore, report } = opts;\n\n router.get(\"/\", async (req, res, next) => {\n try {\n const source = await resolveSource(req);\n const summary = await buildArchitectureSummary(source);\n res.json(summary);\n } catch (err) {\n next(err);\n }\n });\n\n router.get(\"/manifest\", async (req, res, next) => {\n try {\n const source = await resolveSource(req);\n const manifest: ManifestFile = await source.loadManifest();\n res.json(manifest);\n } catch (err) {\n next(err);\n }\n });\n\n router.get(\"/diagrams\", async (req, res, next) => {\n try {\n const source = await resolveSource(req);\n const summary = await buildArchitectureSummary(source);\n res.json(summary.diagrams);\n } catch (err) {\n next(err);\n }\n });\n\n router.get(\"/diagrams/:diagramId\", async (req, res, next) => {\n const { diagramId } = req.params;\n if (!SLUG_RE.test(diagramId)) {\n return send404(res, { error: \"diagram_not_found\", slug: diagramId });\n }\n try {\n const source = await resolveSource(req);\n const diagram = await source.loadDiagram(diagramId);\n const body: ApiDiagram = {\n slug: diagramId,\n name: diagram.name,\n level: diagram.level,\n meta: diagram.meta,\n nodes: diagram.nodes,\n edges: diagram.edges ?? [],\n };\n res.json(body);\n // Anonymous usage signal: C4 level only, never the slug/name/content.\n report?.capture(\n \"diagram.viewed\",\n typeof diagram.level === \"number\" ? { level: diagram.level } : {},\n );\n } catch (err) {\n if (err instanceof DiagramNotFoundError) {\n return send404(res, { error: \"diagram_not_found\", slug: err.slug });\n }\n next(err);\n }\n });\n\n router.get(\"/diagrams/:diagramId/nodes\", async (req, res, next) => {\n const { diagramId } = req.params;\n if (!SLUG_RE.test(diagramId)) {\n return send404(res, { error: \"diagram_not_found\", slug: diagramId });\n }\n try {\n const source = await resolveSource(req);\n const diagram = await source.loadDiagram(diagramId);\n const body: ApiDiagramNodes = { diagramId, nodes: diagram.nodes };\n res.json(body);\n } catch (err) {\n if (err instanceof DiagramNotFoundError) {\n return send404(res, { error: \"diagram_not_found\", slug: err.slug });\n }\n next(err);\n }\n });\n\n router.get(\"/diagrams/:diagramId/edges\", async (req, res, next) => {\n const { diagramId } = req.params;\n if (!SLUG_RE.test(diagramId)) {\n return send404(res, { error: \"diagram_not_found\", slug: diagramId });\n }\n try {\n const source = await resolveSource(req);\n const diagram = await source.loadDiagram(diagramId);\n const body: ApiDiagramEdges = { diagramId, edges: diagram.edges ?? [] };\n res.json(body);\n } catch (err) {\n if (err instanceof DiagramNotFoundError) {\n return send404(res, { error: \"diagram_not_found\", slug: err.slug });\n }\n next(err);\n }\n });\n\n router.get(\"/diagrams/:diagramId/layout\", async (req, res, next) => {\n const { diagramId } = req.params;\n if (!SLUG_RE.test(diagramId)) {\n return send404(res, { error: \"diagram_not_found\", slug: diagramId });\n }\n try {\n const source = await resolveSource(req);\n await source.loadDiagram(diagramId);\n const store = resolveLayoutStore ? await resolveLayoutStore(req) : null;\n const layout: ApiDiagramLayout = store\n ? await store.loadLayout(diagramId)\n : emptyLayout(diagramId);\n res.json(layout);\n } catch (err) {\n if (err instanceof DiagramNotFoundError) {\n return send404(res, { error: \"diagram_not_found\", slug: err.slug });\n }\n next(err);\n }\n });\n\n router.put(\"/diagrams/:diagramId/layout\", async (req, res, next) => {\n const { diagramId } = req.params;\n if (!SLUG_RE.test(diagramId)) {\n return send404(res, { error: \"diagram_not_found\", slug: diagramId });\n }\n try {\n const source = await resolveSource(req);\n await source.loadDiagram(diagramId);\n const store = resolveLayoutStore ? await resolveLayoutStore(req) : null;\n if (!store) {\n return send501(res, {\n error: \"layout_not_supported\",\n message: \"Layout persistence is not configured for this data source\",\n });\n }\n const update = req.body as ApiDiagramLayoutUpdate;\n const saved = await store.saveLayout(diagramId, update);\n res.json(saved);\n } catch (err) {\n if (err instanceof DiagramNotFoundError) {\n return send404(res, { error: \"diagram_not_found\", slug: err.slug });\n }\n if (err instanceof LayoutInvalidError) {\n return send400(res, { error: \"layout_invalid\", message: err.message });\n }\n next(err);\n }\n });\n\n router.get(\"/nodes/:nodeId\", async (req, res, next) => {\n const { nodeId } = req.params;\n if (!SLUG_RE.test(nodeId)) {\n return send404(res, { error: \"node_not_found\", id: nodeId });\n }\n try {\n const source = await resolveSource(req);\n const { node, diagramId } = await findNode(source, nodeId);\n const description = await source.loadDescription(nodeId);\n const body: ApiNodeDetail = { ...node, diagramId, description };\n res.json(body);\n } catch (err) {\n if (err instanceof NodeNotFoundError) {\n return send404(res, { error: \"node_not_found\", id: err.id });\n }\n if (err instanceof DescriptionNotFoundError) {\n return send404(res, { error: \"description_not_found\", id: err.id });\n }\n next(err);\n }\n });\n\n router.get(\"/nodes/:nodeId/description\", async (req, res, next) => {\n const { nodeId } = req.params;\n if (!SLUG_RE.test(nodeId)) {\n return send404(res, { error: \"description_not_found\", id: nodeId });\n }\n try {\n const source = await resolveSource(req);\n await findNode(source, nodeId);\n const markdown = await source.loadDescription(nodeId);\n res.type(\"text/markdown; charset=utf-8\").send(markdown);\n } catch (err) {\n if (err instanceof NodeNotFoundError) {\n return send404(res, { error: \"node_not_found\", id: err.id });\n }\n if (err instanceof DescriptionNotFoundError) {\n return send404(res, { error: \"description_not_found\", id: err.id });\n }\n next(err);\n }\n });\n\n return router;\n}\n\nexport function architectureErrorHandler(\n err: unknown,\n _req: Request,\n res: Response,\n next: NextFunction,\n): void {\n if (res.headersSent) return next(err);\n const message = err instanceof Error ? err.message : String(err);\n const body: ApiArchitectureError = {\n error: \"architecture_unreadable\",\n message,\n };\n res.status(500).json(body);\n}\n","import type {\n ApiArchitectureSummary,\n ApiDiagramLayoutUpdate,\n ApiDiagramSummary,\n ArchitectureNode,\n DiagramFile,\n DiagramLayoutFile,\n ManifestFile,\n NodeLayoutEntry,\n SourceHost,\n} from \"./index\";\n\nexport const SLUG_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;\n\nexport class DiagramNotFoundError extends Error {\n constructor(public readonly slug: string) {\n super(`Diagram not found: ${slug}`);\n this.name = \"DiagramNotFoundError\";\n }\n}\n\nexport class NodeNotFoundError extends Error {\n constructor(public readonly id: string) {\n super(`Node not found: ${id}`);\n this.name = \"NodeNotFoundError\";\n }\n}\n\nexport class DescriptionNotFoundError extends Error {\n constructor(public readonly id: string) {\n super(`Description not found: ${id}`);\n this.name = \"DescriptionNotFoundError\";\n }\n}\n\nexport class LayoutInvalidError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"LayoutInvalidError\";\n }\n}\n\nexport interface ArchitectureDataSource {\n loadManifest(): Promise<ManifestFile>;\n loadDiagram(slug: string): Promise<DiagramFile>;\n loadDescription(nodeId: string): Promise<string>;\n}\n\nexport interface LayoutStore {\n loadLayout(slug: string): Promise<DiagramLayoutFile>;\n saveLayout(\n slug: string,\n update: ApiDiagramLayoutUpdate,\n ): Promise<DiagramLayoutFile>;\n}\n\nexport function isFiniteNumber(v: unknown): v is number {\n return typeof v === \"number\" && Number.isFinite(v);\n}\n\nexport function isValidLayoutEntry(v: unknown): v is NodeLayoutEntry {\n if (!v || typeof v !== \"object\") return false;\n const e = v as Record<string, unknown>;\n if (!isFiniteNumber(e.x) || !isFiniteNumber(e.y)) return false;\n if (\"width\" in e && e.width !== undefined && !isFiniteNumber(e.width)) return false;\n if (\"height\" in e && e.height !== undefined && !isFiniteNumber(e.height)) return false;\n return true;\n}\n\nexport function emptyLayout(slug: string): DiagramLayoutFile {\n return { version: 1, diagramId: slug, updatedAt: \"\", nodes: {} };\n}\n\nexport function normalizeLayoutUpdate(\n slug: string,\n update: ApiDiagramLayoutUpdate,\n): DiagramLayoutFile {\n if (!SLUG_RE.test(slug)) {\n throw new LayoutInvalidError(`invalid diagram slug: ${slug}`);\n }\n if (\n !update ||\n typeof update !== \"object\" ||\n !update.nodes ||\n typeof update.nodes !== \"object\"\n ) {\n throw new LayoutInvalidError(\n \"body must be { nodes: Record<string, { x, y, width?, height? }> }\",\n );\n }\n const nodes: Record<string, NodeLayoutEntry> = {};\n for (const [id, entry] of Object.entries(update.nodes)) {\n if (!SLUG_RE.test(id)) {\n throw new LayoutInvalidError(`invalid node id: ${id}`);\n }\n if (!isValidLayoutEntry(entry)) {\n throw new LayoutInvalidError(`invalid layout entry for ${id}`);\n }\n const out: NodeLayoutEntry = { x: entry.x, y: entry.y };\n if (isFiniteNumber(entry.width)) out.width = entry.width;\n if (isFiniteNumber(entry.height)) out.height = entry.height;\n nodes[id] = out;\n }\n return {\n version: 1,\n diagramId: slug,\n updatedAt: new Date().toISOString(),\n nodes,\n };\n}\n\nexport async function buildArchitectureSummary(\n source: ArchitectureDataSource,\n): Promise<ApiArchitectureSummary> {\n const manifest = await source.loadManifest();\n const diagrams = await Promise.all(\n manifest.diagrams.map(async (slug): Promise<ApiDiagramSummary> => {\n const diagram = await source.loadDiagram(slug);\n return {\n slug,\n name: diagram.name,\n level: diagram.level,\n nodeCount: diagram.nodes?.length ?? 0,\n edgeCount: diagram.edges?.length ?? 0,\n };\n }),\n );\n return {\n name: manifest.name,\n description: manifest.description,\n source: manifest.source,\n sourceHost: manifest.sourceHost,\n topDiagram: manifest.topDiagram,\n diagrams,\n };\n}\n\nconst HOST_DOMAINS: Array<{ host: SourceHost; match: RegExp }> = [\n { host: \"github\", match: /(^|\\.)github\\.com$/i },\n { host: \"gitlab\", match: /(^|\\.)gitlab\\.com$/i },\n { host: \"bitbucket\", match: /(^|\\.)bitbucket\\.org$/i },\n];\n\nfunction inferSourceHost(source: string): SourceHost | undefined {\n // Extract the hostname without relying on the URL global (kept lib-agnostic for shared):\n // scheme://[userinfo@]host[:port]/...\n const hostname = /^[a-z][a-z0-9+.-]*:\\/\\/(?:[^@/]*@)?([^:/?#]+)/i.exec(source)?.[1];\n if (!hostname) return undefined;\n return HOST_DOMAINS.find(({ match }) => match.test(hostname))?.host;\n}\n\n/**\n * Build a web URL that views `path` (a repo-root-relative file or directory, trailing \"/\" = dir)\n * on the source repo host. `HEAD` resolves to the default branch so links stay current. Falls back\n * to the repo root when the host is unknown, and returns undefined when there is no source.\n */\nexport function buildSourceUrl(\n source: string | undefined,\n host: SourceHost | undefined,\n path: string,\n): string | undefined {\n if (!source) return undefined;\n const base = source.replace(/\\/+$/, \"\").replace(/\\.git$/i, \"\");\n const resolvedHost = host ?? inferSourceHost(base);\n const isDir = path.endsWith(\"/\");\n const encoded = path\n .replace(/^\\/+/, \"\")\n .replace(/\\/+$/, \"\")\n .split(\"/\")\n .map(encodeURIComponent)\n .join(\"/\");\n if (!encoded) return base;\n switch (resolvedHost) {\n case \"github\":\n return `${base}/${isDir ? \"tree\" : \"blob\"}/HEAD/${encoded}`;\n case \"gitlab\":\n return `${base}/-/${isDir ? \"tree\" : \"blob\"}/HEAD/${encoded}`;\n case \"bitbucket\":\n return `${base}/src/HEAD/${encoded}`;\n default:\n return base;\n }\n}\n\nexport async function findNode(\n source: ArchitectureDataSource,\n nodeId: string,\n): Promise<{ node: ArchitectureNode; diagramId: string }> {\n if (!SLUG_RE.test(nodeId)) throw new NodeNotFoundError(nodeId);\n const manifest = await source.loadManifest();\n for (const slug of manifest.diagrams) {\n let diagram: DiagramFile;\n try {\n diagram = await source.loadDiagram(slug);\n } catch (err) {\n if (err instanceof DiagramNotFoundError) continue;\n throw err;\n }\n const match = diagram.nodes?.find((n) => n.id === nodeId);\n if (match) return { node: match, diagramId: slug };\n }\n throw new NodeNotFoundError(nodeId);\n}\n","import { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { dirname, join, resolve, sep } from \"node:path\";\nimport type {\n ApiDiagramLayoutUpdate,\n DiagramFile,\n DiagramLayoutFile,\n ManifestFile,\n NodeLayoutEntry,\n} from \"@tecture/shared\";\nimport {\n type ArchitectureDataSource,\n DescriptionNotFoundError,\n DiagramNotFoundError,\n type LayoutStore,\n SLUG_RE,\n emptyLayout,\n isFiniteNumber,\n isValidLayoutEntry,\n normalizeLayoutUpdate,\n} from \"./types.js\";\n\nexport function safeJoin(root: string, ...segments: string[]): string {\n const absRoot = resolve(root);\n const candidate = resolve(join(absRoot, ...segments));\n const guard = absRoot.endsWith(sep) ? absRoot : absRoot + sep;\n if (candidate !== absRoot && !candidate.startsWith(guard)) {\n throw new Error(`Path escapes architecture root: ${candidate}`);\n }\n return candidate;\n}\n\nfunction isNodeErrnoError(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && \"code\" in err;\n}\n\nasync function loadJsonFile<T>(path: string): Promise<T> {\n const raw = await readFile(path, \"utf8\");\n return JSON.parse(raw) as T;\n}\n\nexport class FsArchitectureDataSource implements ArchitectureDataSource {\n constructor(private readonly root: string) {}\n\n async loadManifest(): Promise<ManifestFile> {\n const path = safeJoin(this.root, \"manifest.json\");\n return loadJsonFile<ManifestFile>(path);\n }\n\n async loadDiagram(slug: string): Promise<DiagramFile> {\n if (!SLUG_RE.test(slug)) throw new DiagramNotFoundError(slug);\n const path = safeJoin(this.root, \"diagrams\", `${slug}.json`);\n try {\n return await loadJsonFile<DiagramFile>(path);\n } catch (err) {\n if (isNodeErrnoError(err) && err.code === \"ENOENT\") {\n throw new DiagramNotFoundError(slug);\n }\n throw err;\n }\n }\n\n async loadDescription(nodeId: string): Promise<string> {\n if (!SLUG_RE.test(nodeId)) throw new DescriptionNotFoundError(nodeId);\n const path = safeJoin(this.root, \"descriptions\", `${nodeId}.md`);\n try {\n return await readFile(path, \"utf8\");\n } catch (err) {\n if (isNodeErrnoError(err) && err.code === \"ENOENT\") {\n throw new DescriptionNotFoundError(nodeId);\n }\n throw err;\n }\n }\n}\n\nexport class FsLayoutStore implements LayoutStore {\n constructor(private readonly tectureRoot: string) {}\n\n private path(slug: string): string {\n return safeJoin(this.tectureRoot, \"layouts\", `${slug}.json`);\n }\n\n async loadLayout(slug: string): Promise<DiagramLayoutFile> {\n if (!SLUG_RE.test(slug)) return emptyLayout(slug);\n const path = this.path(slug);\n let raw: string;\n try {\n raw = await readFile(path, \"utf8\");\n } catch (err) {\n if (isNodeErrnoError(err) && err.code === \"ENOENT\") return emptyLayout(slug);\n console.warn(`[tecture] failed to read layout ${path}: ${String(err)}`);\n return emptyLayout(slug);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n console.warn(`[tecture] malformed layout JSON at ${path}: ${String(err)}`);\n return emptyLayout(slug);\n }\n\n if (!parsed || typeof parsed !== \"object\") return emptyLayout(slug);\n const p = parsed as Record<string, unknown>;\n const nodes: Record<string, NodeLayoutEntry> = {};\n if (p.nodes && typeof p.nodes === \"object\") {\n for (const [id, entry] of Object.entries(p.nodes as Record<string, unknown>)) {\n if (!SLUG_RE.test(id)) continue;\n if (!isValidLayoutEntry(entry)) continue;\n const out: NodeLayoutEntry = { x: entry.x, y: entry.y };\n if (isFiniteNumber(entry.width)) out.width = entry.width;\n if (isFiniteNumber(entry.height)) out.height = entry.height;\n nodes[id] = out;\n }\n }\n return {\n version: 1,\n diagramId: slug,\n updatedAt: typeof p.updatedAt === \"string\" ? p.updatedAt : \"\",\n nodes,\n };\n }\n\n async saveLayout(\n slug: string,\n update: ApiDiagramLayoutUpdate,\n ): Promise<DiagramLayoutFile> {\n const file = normalizeLayoutUpdate(slug, update);\n const path = this.path(slug);\n await mkdir(dirname(path), { recursive: true });\n const tmp = `${path}.tmp`;\n await writeFile(tmp, `${JSON.stringify(file, null, 2)}\\n`, \"utf8\");\n await rename(tmp, path);\n return file;\n }\n}\n","import { randomUUID } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { version as coreVersion } from \"../package.json\";\n\n// PostHog *project* API key — a public client key, safe to embed. Same project\n// as the VS Code extension + website, so standalone-viewer usage joins the same\n// funnel. Empty string disables telemetry entirely.\nconst POSTHOG_KEY = \"phc_wTpBpKNGhboxPitLJYLSi8aGGCaYJ7fHbvLAT93edmHp\";\nconst POSTHOG_HOST = \"https://us.i.posthog.com\";\n\ntype EventProps = Record<string, string | number | boolean>;\n\nexport interface Reporter {\n /** Send an anonymous usage event. No-op when telemetry is disabled. */\n capture(event: string, properties?: EventProps): void;\n}\n\nconst NOOP: Reporter = { capture() {} };\n\nfunction disabledByEnv(): boolean {\n const dnt = process.env.DO_NOT_TRACK;\n if (dnt === \"1\" || dnt === \"true\") return true;\n const t = process.env.TECTURE_TELEMETRY?.toLowerCase();\n return t === \"0\" || t === \"false\" || t === \"off\" || t === \"no\";\n}\n\n/**\n * Anonymous, opt-out usage telemetry for the standalone viewer (only ever runs\n * for `npx @tecture/core` — the VS Code extension talks to its own host, not\n * this server, so there is no double-counting).\n *\n * - Opt-out: honors `DO_NOT_TRACK=1` and `TECTURE_TELEMETRY=0`, and prints a\n * one-time notice on first run.\n * - Anonymous: a random id persisted under `~/.config/tecture/telemetry-id`\n * (so repeat runs count as one user). No machine name, user, or repo data.\n * - NEVER sends architecture content: only the C4 level (1/2/3) and counts.\n */\nexport function createTelemetry(): Reporter {\n if (POSTHOG_KEY.length === 0 || disabledByEnv()) return NOOP;\n\n let distinctId: string;\n let firstRun = false;\n try {\n const dir = join(homedir(), \".config\", \"tecture\");\n const idFile = join(dir, \"telemetry-id\");\n if (existsSync(idFile)) {\n distinctId = readFileSync(idFile, \"utf8\").trim() || randomUUID();\n } else {\n distinctId = randomUUID();\n mkdirSync(dir, { recursive: true });\n writeFileSync(idFile, distinctId);\n firstRun = true;\n }\n } catch {\n distinctId = randomUUID(); // ephemeral fallback if the config dir isn't writable\n }\n\n if (firstRun) {\n console.error(\n \"\\nTecture collects anonymous usage stats (which diagram levels are viewed, how often)\\n\" +\n \"to help improve the tool. It never sends your architecture content, names, or paths.\\n\" +\n \"Opt out any time: set TECTURE_TELEMETRY=0 (or DO_NOT_TRACK=1).\\n\",\n );\n }\n\n const superProps: EventProps = {\n coreVersion,\n nodeVersion: process.version,\n platform: process.platform,\n };\n\n return {\n capture(event, properties = {}) {\n const body = JSON.stringify({\n api_key: POSTHOG_KEY,\n event,\n distinct_id: distinctId,\n properties: { ...superProps, ...properties, $process_person_profile: false },\n });\n // Fire-and-forget: never block the server or throw on a dead network.\n void fetch(`${POSTHOG_HOST}/capture/`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body,\n }).catch(() => {});\n },\n };\n}\n","{\n \"name\": \"@tecture/core\",\n \"version\": \"0.0.8\",\n \"description\": \"Next generation architecture documentation generated by coding agents — render file-based architecture diagrams (JSON + Markdown) in your browser.\",\n \"keywords\": [\n \"c4\",\n \"architecture\",\n \"diagram\",\n \"documentation\",\n \"visualization\",\n \"reactflow\",\n \"agents\",\n \"llm\",\n \"coding-agents\"\n ],\n \"license\": \"MIT\",\n \"author\": \"Shanika Wijerathna <shanikacj@gmail.com>\",\n \"homepage\": \"https://github.com/tecture-io/tecture#readme\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/tecture-io/tecture.git\",\n \"directory\": \"packages/server\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/tecture-io/tecture/issues\"\n },\n \"type\": \"module\",\n \"main\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\"\n },\n \"./cli\": \"./dist/cli.js\"\n },\n \"bin\": \"./dist/cli.js\",\n \"files\": [\n \"dist\",\n \"CHANGELOG.md\"\n ],\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"scripts\": {\n \"dev\": \"tsx watch src/cli.ts --port 3001 --architecture-path ../../architecture --tecture-path ../../architecture/.tecture\",\n \"build\": \"tsup && node scripts/copy-web-assets.mjs\",\n \"start\": \"node dist/cli.js\",\n \"typecheck\": \"tsc --noEmit\"\n },\n \"dependencies\": {\n \"express\": \"^4.21.1\"\n },\n \"devDependencies\": {\n \"@tecture/shared\": \"workspace:*\",\n \"@tecture/web\": \"workspace:*\",\n \"@types/express\": \"^4.17.21\",\n \"@types/node\": \"^20.17.6\",\n \"tsup\": \"^8.3.5\",\n \"tsx\": \"^4.19.2\",\n \"typescript\": \"^5.6.3\"\n }\n}\n"],"mappings":"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","names":["resolve","Router","Router","app","existsSync","join","join","existsSync","port","architecturePath","tecturePath","resolve"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,11 +3,19 @@ import { ArchitectureDataSource, LayoutStore, ManifestFile, DiagramFile, Diagram
|
|
|
3
3
|
export * from '@tecture/shared';
|
|
4
4
|
export { ArchitectureDataSource, DescriptionNotFoundError, DiagramNotFoundError, LayoutInvalidError, LayoutStore, NodeNotFoundError, SLUG_RE, buildArchitectureSummary, emptyLayout, findNode, isFiniteNumber, isValidLayoutEntry, normalizeLayoutUpdate } from '@tecture/shared';
|
|
5
5
|
|
|
6
|
+
type EventProps = Record<string, string | number | boolean>;
|
|
7
|
+
interface Reporter {
|
|
8
|
+
/** Send an anonymous usage event. No-op when telemetry is disabled. */
|
|
9
|
+
capture(event: string, properties?: EventProps): void;
|
|
10
|
+
}
|
|
11
|
+
|
|
6
12
|
type SourceResolver = (req: Request) => ArchitectureDataSource | Promise<ArchitectureDataSource>;
|
|
7
13
|
type LayoutStoreResolver = (req: Request) => LayoutStore | null | Promise<LayoutStore | null>;
|
|
8
14
|
interface ArchitectureRouterOptions {
|
|
9
15
|
resolveSource: SourceResolver;
|
|
10
16
|
resolveLayoutStore?: LayoutStoreResolver;
|
|
17
|
+
/** Optional anonymous usage reporter (standalone viewer only). */
|
|
18
|
+
report?: Reporter;
|
|
11
19
|
}
|
|
12
20
|
declare function createArchitectureRouter(opts: ArchitectureRouterOptions): Router;
|
|
13
21
|
declare function architectureErrorHandler(err: unknown, _req: Request, res: Response, next: NextFunction): void;
|
|
@@ -15,6 +23,8 @@ declare function architectureErrorHandler(err: unknown, _req: Request, res: Resp
|
|
|
15
23
|
interface CreateAppOptions {
|
|
16
24
|
source: ArchitectureDataSource | SourceResolver;
|
|
17
25
|
layoutStore?: LayoutStore | LayoutStoreResolver | null;
|
|
26
|
+
/** Optional anonymous usage reporter (standalone viewer only). */
|
|
27
|
+
report?: Reporter;
|
|
18
28
|
}
|
|
19
29
|
declare function createApp(options: CreateAppOptions): Express;
|
|
20
30
|
|
package/dist/index.js
CHANGED
|
@@ -173,8 +173,7 @@ function isDeepDivable(node) {
|
|
|
173
173
|
return type !== void 0 && CODE_NODE_TYPES.has(type);
|
|
174
174
|
}
|
|
175
175
|
function buildDeepDivePrompt(node) {
|
|
176
|
-
|
|
177
|
-
return `Use the tecture skill to deep-dive the architecture component \`${node.id}\`${named}.`;
|
|
176
|
+
return `/architecture-docs deep-dive ${node.id}`;
|
|
178
177
|
}
|
|
179
178
|
|
|
180
179
|
// src/routes/architecture.ts
|
|
@@ -189,7 +188,7 @@ function send400(res, body) {
|
|
|
189
188
|
}
|
|
190
189
|
function createArchitectureRouter(opts) {
|
|
191
190
|
const router = Router2();
|
|
192
|
-
const { resolveSource, resolveLayoutStore } = opts;
|
|
191
|
+
const { resolveSource, resolveLayoutStore, report } = opts;
|
|
193
192
|
router.get("/", async (req, res, next) => {
|
|
194
193
|
try {
|
|
195
194
|
const source = await resolveSource(req);
|
|
@@ -234,6 +233,10 @@ function createArchitectureRouter(opts) {
|
|
|
234
233
|
edges: diagram.edges ?? []
|
|
235
234
|
};
|
|
236
235
|
res.json(body);
|
|
236
|
+
report?.capture(
|
|
237
|
+
"diagram.viewed",
|
|
238
|
+
typeof diagram.level === "number" ? { level: diagram.level } : {}
|
|
239
|
+
);
|
|
237
240
|
} catch (err) {
|
|
238
241
|
if (err instanceof DiagramNotFoundError) {
|
|
239
242
|
return send404(res, { error: "diagram_not_found", slug: err.slug });
|
|
@@ -392,7 +395,8 @@ function createApp(options) {
|
|
|
392
395
|
"/api/architecture",
|
|
393
396
|
createArchitectureRouter({
|
|
394
397
|
resolveSource: toSourceResolver(options.source),
|
|
395
|
-
resolveLayoutStore: toLayoutStoreResolver(options.layoutStore)
|
|
398
|
+
resolveLayoutStore: toLayoutStoreResolver(options.layoutStore),
|
|
399
|
+
report: options.report
|
|
396
400
|
})
|
|
397
401
|
);
|
|
398
402
|
app.use("/api/architecture", architectureErrorHandler);
|