c4studio 0.2.0__tar.gz → 0.4.0__tar.gz
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.
- c4studio-0.4.0/PKG-INFO +587 -0
- c4studio-0.4.0/README.md +555 -0
- c4studio-0.4.0/pyproject.toml +186 -0
- c4studio-0.4.0/src/c4studio/cli/main.py +890 -0
- c4studio-0.4.0/src/c4studio/diff.py +417 -0
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/generators/flowchart.py +0 -1
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/generators/json_export.py +20 -3
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/generators/mermaid.py +1 -3
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/generators/mermaid_common.py +0 -1
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/graph/view_graph.py +567 -25
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/icons.py +36 -1
- c4studio-0.4.0/src/c4studio/impact.py +288 -0
- c4studio-0.4.0/src/c4studio/layout_sidecar.py +342 -0
- c4studio-0.4.0/src/c4studio/lint.py +437 -0
- c4studio-0.4.0/src/c4studio/markdown.py +259 -0
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/models/elements.py +0 -1
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/models/enums.py +0 -1
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/models/views.py +47 -41
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/models/workspace.py +9 -6
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/parser/docs.py +0 -1
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/parser/dsl.py +960 -114
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/parser/expressions.py +52 -12
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/parser/json_parser.py +23 -18
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/parser/locations.py +1 -2
- c4studio-0.4.0/src/c4studio/publish.py +479 -0
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/render.py +87 -1
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/renderer/diagram-render.mjs +1201 -760
- c4studio-0.4.0/src/c4studio/templates/deployment.dsl +58 -0
- c4studio-0.4.0/src/c4studio/templates/full-c4.dsl +61 -0
- c4studio-0.4.0/src/c4studio/templates/minimal.dsl +18 -0
- c4studio-0.4.0/src/c4studio/templates/system-context.dsl +39 -0
- c4studio-0.4.0/src/c4studio/templates.py +111 -0
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/themes.py +9 -3
- c4studio-0.4.0/src/c4studio/webapp/assistant.py +211 -0
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/webapp/graph.py +90 -4
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/webapp/model_graph.py +16 -12
- c4studio-0.4.0/src/c4studio/webapp/perspectives.py +164 -0
- c4studio-0.4.0/src/c4studio/webapp/server.py +2001 -0
- c4studio-0.4.0/src/c4studio/webapp/static/assets/index-BWKhqzG-.js +95 -0
- c4studio-0.4.0/src/c4studio/webapp/static/assets/index-CJj5SYQu.css +1 -0
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/webapp/static/index.html +2 -2
- c4studio-0.2.0/PKG-INFO +0 -283
- c4studio-0.2.0/README.md +0 -258
- c4studio-0.2.0/pyproject.toml +0 -73
- c4studio-0.2.0/src/c4studio/cli/main.py +0 -329
- c4studio-0.2.0/src/c4studio/webapp/server.py +0 -713
- c4studio-0.2.0/src/c4studio/webapp/static/assets/index-CibNm4nc.js +0 -68
- c4studio-0.2.0/src/c4studio/webapp/static/assets/index-Px2v-U7I.css +0 -1
- {c4studio-0.2.0 → c4studio-0.4.0}/LICENSE +0 -0
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/__init__.py +7 -7
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/__main__.py +0 -0
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/cli/__init__.py +0 -0
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/diagnostics.py +0 -0
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/generators/__init__.py +0 -0
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/graph/__init__.py +0 -0
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/models/__init__.py +45 -45
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/models/deployment.py +0 -0
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/models/documentation.py +0 -0
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/parser/__init__.py +0 -0
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/parser/implied.py +0 -0
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/parser/sourcemap.py +0 -0
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/py.typed +0 -0
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/webapp/__init__.py +0 -0
- {c4studio-0.2.0 → c4studio-0.4.0}/src/c4studio/webapp/loader.py +0 -0
c4studio-0.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: c4studio
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Parse Structurizr DSL/JSON and generate C4 diagrams in Mermaid, with a CLI and a React web app
|
|
5
|
+
Keywords: structurizr,c4,architecture,diagrams,dsl
|
|
6
|
+
Author: Geoffrey Koh
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Web Environment
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Topic :: Documentation
|
|
14
|
+
Classifier: Topic :: Software Development :: Documentation
|
|
15
|
+
Requires-Dist: click>=8.1
|
|
16
|
+
Requires-Dist: pydantic>=2.0
|
|
17
|
+
Requires-Dist: fastapi>=0.115
|
|
18
|
+
Requires-Dist: uvicorn[standard]>=0.30
|
|
19
|
+
Requires-Dist: anthropic>=1.0 ; extra == 'assistant'
|
|
20
|
+
Requires-Dist: pytest>=8.0 ; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest-cov>=5.0 ; extra == 'dev'
|
|
22
|
+
Requires-Dist: anthropic>=1.0 ; extra == 'dev'
|
|
23
|
+
Requires-Python: >=3.13
|
|
24
|
+
Project-URL: Homepage, https://github.com/geoffkoh/c4studio
|
|
25
|
+
Project-URL: Repository, https://github.com/geoffkoh/c4studio
|
|
26
|
+
Project-URL: Documentation, https://github.com/geoffkoh/c4studio/blob/main/docs/dsl-reference.md
|
|
27
|
+
Project-URL: Issues, https://github.com/geoffkoh/c4studio/issues
|
|
28
|
+
Project-URL: Migration guide, https://github.com/geoffkoh/c4studio/blob/main/docs/MIGRATION.md
|
|
29
|
+
Provides-Extra: assistant
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# c4studio
|
|
34
|
+
|
|
35
|
+
Python implementation of [Structurizr](https://structurizr.com/) for architecture
|
|
36
|
+
modeling and C4 diagram generation — with a local-first **Studio** for writing
|
|
37
|
+
the DSL and watching the diagram follow.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
Published on PyPI as **`c4studio`**; the import package is `c4studio`
|
|
41
|
+
and the command is `c4`:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pipx install c4studio # or: pip install c4studio
|
|
45
|
+
c4 new my-architecture.dsl # start from a template
|
|
46
|
+
c4 webapp my-architecture.dsl # edit it, and see it
|
|
47
|
+
|
|
48
|
+
# or run without installing:
|
|
49
|
+
uvx --from c4studio c4 webapp my-architecture.dsl
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Requires Python 3.13+ (uv/uvx can provision it automatically).
|
|
53
|
+
|
|
54
|
+
## Features
|
|
55
|
+
- ✅ Full Structurizr metamodel support (C4 architecture model)
|
|
56
|
+
- ✅ DSL and JSON parsing
|
|
57
|
+
- ✅ **In-browser DSL editor** — syntax highlighting, autocomplete, live
|
|
58
|
+
diagnostics, save straight to disk, diagram beside the text
|
|
59
|
+
- ✅ **Searchable file tree** with create, rename and delete
|
|
60
|
+
- ✅ **Starter templates** and `c4 new`
|
|
61
|
+
- ✅ Mermaid diagram generation — C4 syntax or `flowchart`/`subgraph`
|
|
62
|
+
- ✅ Headless SVG rendering for CI and docs-as-code
|
|
63
|
+
- ✅ Comprehensive type hints
|
|
64
|
+
- ✅ **Perspectives** — annotate elements and relationships with security,
|
|
65
|
+
ownership or anything else, then show one over the diagram
|
|
66
|
+
- ✅ **`c4 lint`** — model standards in CI: orphans, missing descriptions,
|
|
67
|
+
duplicate relationships, styles that match nothing
|
|
68
|
+
- ✅ Custom properties on all elements
|
|
69
|
+
- ✅ Deployment infrastructure modeling
|
|
70
|
+
- ✅ Style and configuration management
|
|
71
|
+
- ⚙️ Optional AI assistant — off by default, and the only feature that
|
|
72
|
+
uses the network
|
|
73
|
+
|
|
74
|
+
## Studio — the web app
|
|
75
|
+
A React (Vite + TypeScript) single-page app, served by a FastAPI backend and
|
|
76
|
+
launched from the CLI. It loads DSL/JSON from disk, renders each view as an
|
|
77
|
+
interactive [React Flow](https://reactflow.dev/) graph, and — since 0.3.0 —
|
|
78
|
+
lets you write the DSL that produces them.
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
uv run c4 webapp samples/ # browse a directory
|
|
82
|
+
uv run c4 webapp file.dsl # preload a single file
|
|
83
|
+
uv run c4 webapp samples/ --viewer # read-only, for a kiosk or an embed
|
|
84
|
+
# → opens http://127.0.0.1:8090 (use --no-browser to skip, --port to change)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Studio and Viewer
|
|
88
|
+
|
|
89
|
+
| | Command | Behaviour |
|
|
90
|
+
| --- | --- | --- |
|
|
91
|
+
| **Studio** | `c4 webapp <path>` | Browse, edit, save, create. **The default.** |
|
|
92
|
+
| **Viewer** | `c4 webapp <path> --viewer` | No DSL writes — the routes answer 403. Layout dragging and group collapse still persist. |
|
|
93
|
+
|
|
94
|
+
> **Upgrading from 0.2.0?** `c4 webapp` used to be read-only and is now
|
|
95
|
+
> writable. If you point it at anything public-facing, add `--viewer`. The
|
|
96
|
+
> guarantee lives on the server, so it holds against a crafted request and
|
|
97
|
+
> not merely a hidden button.
|
|
98
|
+
|
|
99
|
+
### Editing
|
|
100
|
+
|
|
101
|
+
The source editor is a [CodeMirror 6](https://codemirror.net/) panel that
|
|
102
|
+
opens beside the diagram — from the **Source** button at the right of the
|
|
103
|
+
top bar, the `e` key, the rail on the diagram's right edge, or *Open in
|
|
104
|
+
Source* on any element. The diagram stays put while it opens, and a ● on
|
|
105
|
+
the button marks unsaved work behind a closed editor.
|
|
106
|
+
|
|
107
|
+
- **Syntax highlighting and autocomplete** for DSL keywords, your own element
|
|
108
|
+
identifiers, and view keys — drawn from the loaded workspace, so it knows
|
|
109
|
+
what you have actually defined.
|
|
110
|
+
- **Diagnostics as you type**, from the same parser the CLI uses. Invalid DSL
|
|
111
|
+
still saves: an editor that refuses to save mid-thought is unusable.
|
|
112
|
+
- **⌘S / Ctrl+S saves**, and the diagram re-renders from the text. Saving is
|
|
113
|
+
the render trigger — the same live-reload loop an external editor drives.
|
|
114
|
+
- **Conflict detection.** If a file changed on disk since you opened it, the
|
|
115
|
+
save stops and offers you both sides rather than overwriting.
|
|
116
|
+
- **A searchable file tree** covering every source under the root, including
|
|
117
|
+
`!include` fragments, with right-click to create, rename or delete.
|
|
118
|
+
- **New workspaces from templates** — `minimal`, `system-context`, `full-c4`
|
|
119
|
+
or `deployment`.
|
|
120
|
+
|
|
121
|
+
The diagram is a preview of the text, never the other way round: c4studio
|
|
122
|
+
never generates DSL from the model, so your comments, formatting and
|
|
123
|
+
`!include` structure are yours.
|
|
124
|
+
|
|
125
|
+
Select several nodes (Shift+drag, or ⌘/Ctrl+click) to align or distribute
|
|
126
|
+
them from the toolbar that appears, and nudge a selection with the arrow
|
|
127
|
+
keys — 10px with Shift. Everything persists to the layout sidecar.
|
|
128
|
+
|
|
129
|
+
Pass a directory to browse and load any `.dsl`/`.json` file from the
|
|
130
|
+
in-app file picker, or a single file to preload it. The element tree and
|
|
131
|
+
per-view graph come from the parser and `graph/view_graph`;
|
|
132
|
+
`systemLandscape`, `systemContext`, `container`, `component`, `dynamic`
|
|
133
|
+
and `deployment` views all render as interactive graphs (other view
|
|
134
|
+
types are flagged "not renderable yet").
|
|
135
|
+
|
|
136
|
+
The **Explorer** tab renders the entire static model as one graph —
|
|
137
|
+
independent of any curated view — at a selectable abstraction level
|
|
138
|
+
(systems / containers / components), with search across every element
|
|
139
|
+
(press `/`), and a details panel showing an element's metadata,
|
|
140
|
+
relationships, the views it appears in (click to jump) and a
|
|
141
|
+
show-definition link that opens the source editor.
|
|
142
|
+
|
|
143
|
+
The built SPA ships inside the package (`c4studio/webapp/static/`),
|
|
144
|
+
so end users need no Node toolchain. To rebuild the frontend after
|
|
145
|
+
changes (requires Node 20.19+ or 22.12+, what Vite asks for — distinct
|
|
146
|
+
from the Node 18+ that `c4 render` needs at runtime, below):
|
|
147
|
+
|
|
148
|
+
The frontend is an npm workspace: `packages/diagram-core` holds the
|
|
149
|
+
diagram layer (layout, node/edge components, image export) and
|
|
150
|
+
`frontend/` is the SPA that consumes it, so build from the repo root.
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
npm install
|
|
154
|
+
npm run build # diagram-core, then the SPA
|
|
155
|
+
# outputs to src/c4studio/webapp/static/
|
|
156
|
+
# dev loop: `npm run dev --workspace c4studio-frontend`
|
|
157
|
+
# (Vite :5173, proxies /api → :8090) alongside
|
|
158
|
+
# `uv run c4 webapp samples/ --no-browser`
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
> **Security**: the web app has no authentication and is intended for local
|
|
162
|
+
> use on `127.0.0.1`. It is local-first by design — single user, no server,
|
|
163
|
+
> no accounts — and sharing happens through git and generated artifacts.
|
|
164
|
+
> The one exception is the assistant, which is off unless you pass
|
|
165
|
+
> `--assistant`.
|
|
166
|
+
|
|
167
|
+
### Tests
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
uv run pytest # full suite
|
|
171
|
+
uv run pytest tests/test_webapp # web app tests only
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Starter templates
|
|
175
|
+
```bash
|
|
176
|
+
uv run c4 new # workspace.dsl, minimal
|
|
177
|
+
uv run c4 new arch.dsl --template full-c4 --name "Acme Platform"
|
|
178
|
+
uv run c4 new --list # what is available
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
| Template | What it shows |
|
|
182
|
+
| --- | --- |
|
|
183
|
+
| `minimal` | One person, one system, one view — the smallest thing that renders |
|
|
184
|
+
| `system-context` | A system in its context: who uses it, what it depends on |
|
|
185
|
+
| `full-c4` | Context, containers and components, drillable in the viewer |
|
|
186
|
+
| `deployment` | Containers mapped onto the infrastructure that runs them |
|
|
187
|
+
|
|
188
|
+
Existing files are never overwritten without `--force`.
|
|
189
|
+
|
|
190
|
+
## Assistant (optional, off by default)
|
|
191
|
+
c4studio is local-first: nothing it does reaches the network, with one
|
|
192
|
+
exception you have to switch on deliberately.
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
pip install 'c4studio[assistant]'
|
|
196
|
+
export ANTHROPIC_API_KEY=...
|
|
197
|
+
uv run c4 webapp samples/ --assistant
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Ask for a change in words; the reply comes back as a **diff against your
|
|
201
|
+
buffer**, which you apply or reject. Applying only changes the editor —
|
|
202
|
+
nothing is written until you save.
|
|
203
|
+
|
|
204
|
+
**What leaves your machine:** the workspace source, including unsaved
|
|
205
|
+
changes to the file you are editing. The panel lists exactly which files
|
|
206
|
+
were sent. Without `--assistant` the endpoint answers 403; the API key is
|
|
207
|
+
read from the environment at the moment of use and is never stored, logged
|
|
208
|
+
or sent back. A default install pulls in no extra dependency and behaves
|
|
209
|
+
exactly as it did before.
|
|
210
|
+
|
|
211
|
+
## Python API
|
|
212
|
+
|
|
213
|
+
c4studio is a library as well as an app: the parser, the model and the
|
|
214
|
+
generators are importable, and the web app is one consumer of them.
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
from c4studio.models import Workspace, Person, SoftwareSystem, Container, Relationship, View, ViewType
|
|
218
|
+
|
|
219
|
+
# Create workspace
|
|
220
|
+
ws = Workspace(
|
|
221
|
+
name="My Architecture",
|
|
222
|
+
description="System architecture model"
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
# Define people and systems
|
|
226
|
+
user = Person(id="user", name="User")
|
|
227
|
+
system = SoftwareSystem(id="sys", name="System")
|
|
228
|
+
ws.people.append(user)
|
|
229
|
+
ws.software_systems.append(system)
|
|
230
|
+
|
|
231
|
+
# Add relationship
|
|
232
|
+
rel = Relationship(
|
|
233
|
+
source_id="user",
|
|
234
|
+
destination_id="sys",
|
|
235
|
+
description="Uses"
|
|
236
|
+
)
|
|
237
|
+
ws.relationships.append(rel)
|
|
238
|
+
|
|
239
|
+
# Create view
|
|
240
|
+
view = View(type=ViewType.SYSTEM_CONTEXT, key="context")
|
|
241
|
+
ws.views.append(view)
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Parsing
|
|
245
|
+
Parse Structurizr DSL or JSON files:
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
from c4studio.parser.dsl import parse_dsl_file
|
|
249
|
+
from c4studio.parser.json_parser import parse_json_file
|
|
250
|
+
|
|
251
|
+
# Parse DSL
|
|
252
|
+
ws = parse_dsl_file("architecture.dsl")
|
|
253
|
+
|
|
254
|
+
# Parse JSON
|
|
255
|
+
ws = parse_json_file("workspace.json")
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Diagram Generation
|
|
259
|
+
Two Mermaid targets, both rendering from the same view graph — so what they
|
|
260
|
+
show matches the web app's view semantics exactly:
|
|
261
|
+
|
|
262
|
+
| Target | Syntax | Covers |
|
|
263
|
+
| --- | --- | --- |
|
|
264
|
+
| `MermaidGenerator` | `C4Context` / `C4Container` / `C4Component` | system landscape, system context, container, component views |
|
|
265
|
+
| `FlowchartGenerator` | `flowchart` + `subgraph` | all of the above **plus** dynamic, deployment and filtered views |
|
|
266
|
+
|
|
267
|
+
Mermaid's C4 diagram types are experimental upstream and lay out poorly on
|
|
268
|
+
dense models, and GitHub pins its own Mermaid version — so prefer the
|
|
269
|
+
flowchart target for anything rendered on GitHub or in a wiki.
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
from c4studio.generators import FlowchartGenerator, MermaidGenerator
|
|
273
|
+
|
|
274
|
+
for view_name, mermaid_code in MermaidGenerator(ws).generate_all().items():
|
|
275
|
+
print(f"{view_name}:\n{mermaid_code}\n")
|
|
276
|
+
|
|
277
|
+
# Or the flowchart target, which renders every view type:
|
|
278
|
+
diagrams = FlowchartGenerator(ws).generate_all()
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
From the CLI:
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
uv run c4 generate architecture.dsl # C4 (default)
|
|
285
|
+
uv run c4 generate architecture.dsl -f flowchart # flowchart
|
|
286
|
+
uv run c4 generate architecture.dsl -f flowchart -o out # one .mmd per view
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Perspectives
|
|
290
|
+
A perspective is a named annotation you hang on elements and
|
|
291
|
+
relationships — security, ownership, cost, anything — and then **show over
|
|
292
|
+
the diagram**. What carries it keeps its place and gets a badge with its
|
|
293
|
+
value; everything else fades. The legend follows, listing the values
|
|
294
|
+
rather than the element styles the diagram is no longer painted with.
|
|
295
|
+
|
|
296
|
+
```
|
|
297
|
+
api = container "Payments API" "Authorises and captures." "Python, FastAPI" {
|
|
298
|
+
perspectives {
|
|
299
|
+
"security" "mTLS to the ledger, PCI DSS scope." "High"
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+

|
|
307
|
+
|
|
308
|
+
Colour comes from ordinary styles, matched on the perspective and
|
|
309
|
+
optionally on its value:
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
element "Perspective:security[value==High]" {
|
|
313
|
+
background #2e7d32
|
|
314
|
+
color #ffffff
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
The same overlay is available everywhere the diagram is:
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
uv run c4 render architecture.dsl --perspective security -o diagrams/
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
and in the VS Code preview through **C4Studio: Show Perspective…**. A
|
|
325
|
+
perspective carrying a `url` instead of a value has that value **read
|
|
326
|
+
live** — off by default, because the addresses come from the workspace
|
|
327
|
+
file; `c4 webapp --dynamic-perspectives` turns it on.
|
|
328
|
+
|
|
329
|
+
## Publish a site
|
|
330
|
+
Turn the model into something people read without installing anything:
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
uv run c4 publish architecture.dsl -o site/
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Every view becomes its own page — not one page with a switcher, so a link
|
|
337
|
+
to a diagram survives being pasted into a ticket — with the `!docs` prose
|
|
338
|
+
and the ADRs beside them and navigation between. The result is
|
|
339
|
+
**self-contained**: diagrams are inlined SVG and theme icons are embedded
|
|
340
|
+
as `data:` URIs, so it works from a `file://` path, on an air-gapped
|
|
341
|
+
machine, or on any static host.
|
|
342
|
+
|
|
343
|
+
Published diagrams follow the layout you arranged in the Studio
|
|
344
|
+
(`--no-layout` for a fresh one), and `--perspective security` draws the
|
|
345
|
+
whole site through one perspective.
|
|
346
|
+
|
|
347
|
+
To publish on every merge, add the site to a Pages workflow:
|
|
348
|
+
|
|
349
|
+
```yaml
|
|
350
|
+
- uses: actions/checkout@v7
|
|
351
|
+
- uses: actions/setup-node@v6 # c4 render needs Node
|
|
352
|
+
with:
|
|
353
|
+
node-version: "20"
|
|
354
|
+
- run: pipx install c4studio
|
|
355
|
+
- run: c4 publish architecture.dsl -o site/
|
|
356
|
+
- uses: actions/upload-pages-artifact@v4
|
|
357
|
+
with:
|
|
358
|
+
path: site/
|
|
359
|
+
- uses: actions/deploy-pages@v4
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## What changed (`c4 diff`)
|
|
363
|
+
A pull request that changes the architecture should say what it changed:
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
uv run c4 diff architecture.dsl # working tree vs HEAD
|
|
367
|
+
uv run c4 diff architecture.dsl --from main --to HEAD --json
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
Elements and relationships added, removed and changed — and **the views a
|
|
371
|
+
reviewer has to look at again**. A comment-only edit produces nothing,
|
|
372
|
+
which is the signal worth having.
|
|
373
|
+
|
|
374
|
+
Renaming an element that has an alias is reported as a change. Renaming
|
|
375
|
+
one *without* an alias reads as a removal and an addition, because its id
|
|
376
|
+
comes from its name and nothing ties the two together; when a removal and
|
|
377
|
+
an addition share a name, the pair is flagged as a **possible** rename
|
|
378
|
+
rather than assumed. A diff that invents a rename hides a deletion.
|
|
379
|
+
|
|
380
|
+
### On every pull request
|
|
381
|
+
|
|
382
|
+
`c4 diff` needs no Node, so a short workflow can post the answer on each
|
|
383
|
+
pull request that touches the model, updating its own comment on every
|
|
384
|
+
push rather than adding one per push:
|
|
385
|
+
|
|
386
|
+
```yaml
|
|
387
|
+
name: Architecture diff
|
|
388
|
+
|
|
389
|
+
on:
|
|
390
|
+
pull_request:
|
|
391
|
+
paths:
|
|
392
|
+
- "architecture/**"
|
|
393
|
+
|
|
394
|
+
permissions:
|
|
395
|
+
contents: read
|
|
396
|
+
pull-requests: write # to comment
|
|
397
|
+
|
|
398
|
+
jobs:
|
|
399
|
+
diff:
|
|
400
|
+
runs-on: ubuntu-latest
|
|
401
|
+
steps:
|
|
402
|
+
- uses: actions/checkout@v7
|
|
403
|
+
with:
|
|
404
|
+
fetch-depth: 0 # c4 diff reads the base revision from git history
|
|
405
|
+
|
|
406
|
+
- run: pipx install c4studio # no Node needed: diff compares models
|
|
407
|
+
|
|
408
|
+
- name: Diff the model against the target branch
|
|
409
|
+
env:
|
|
410
|
+
BASE: origin/${{ github.base_ref }}
|
|
411
|
+
run: |
|
|
412
|
+
{
|
|
413
|
+
echo "### Architecture changes"
|
|
414
|
+
echo
|
|
415
|
+
echo '```text'
|
|
416
|
+
# A failure is shown rather than failing the job: a workspace
|
|
417
|
+
# the base branch does not have yet cannot be compared.
|
|
418
|
+
c4 diff architecture/workspace.dsl --from "$BASE" --to HEAD 2>&1 \
|
|
419
|
+
|| echo "c4 diff could not compare these revisions (exit $?)"
|
|
420
|
+
echo '```'
|
|
421
|
+
} > architecture-diff.md
|
|
422
|
+
# The job summary works where a comment cannot: a pull request
|
|
423
|
+
# from a fork gets a read-only token.
|
|
424
|
+
cat architecture-diff.md >> "$GITHUB_STEP_SUMMARY"
|
|
425
|
+
|
|
426
|
+
- name: Comment on the pull request
|
|
427
|
+
env:
|
|
428
|
+
GH_TOKEN: ${{ github.token }}
|
|
429
|
+
PR: ${{ github.event.pull_request.number }}
|
|
430
|
+
run: gh pr comment "$PR" --body-file architecture-diff.md --edit-last --create-if-none
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
It reads the base branch from git history, hence `fetch-depth: 0`. A
|
|
434
|
+
workspace the base branch does not have yet cannot be compared, and the
|
|
435
|
+
comment says so instead of failing the job. `--edit-last` edits the
|
|
436
|
+
latest comment made with the workflow's token, so keep this the only
|
|
437
|
+
step that comments with it — or give it a token of its own. Diagrams are
|
|
438
|
+
the action's job (`mode: comment`); this posts the model change, which is
|
|
439
|
+
what a reviewer needs before looking at any of them.
|
|
440
|
+
|
|
441
|
+
## Impact analysis
|
|
442
|
+
What else is involved when one element changes — asked of the model
|
|
443
|
+
rather than of whoever remembers:
|
|
444
|
+
|
|
445
|
+
```bash
|
|
446
|
+
uv run c4 impact architecture.dsl paymentsApi
|
|
447
|
+
uv run c4 impact architecture.dsl paymentsApi --json --depth 2
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
Dependents (what reaches it, and so may break when it changes),
|
|
451
|
+
dependencies (what it reaches, and so may break it), each with how many
|
|
452
|
+
relationships away it is, plus **the views that draw it** — so a change
|
|
453
|
+
advisory board knows which diagrams to open.
|
|
454
|
+
|
|
455
|
+
Containment counts in both directions: a relationship declared against a
|
|
456
|
+
container is one its software system takes part in, which is the same
|
|
457
|
+
rule the views apply when they lift an edge to the nearest visible
|
|
458
|
+
ancestor. An answer that ignored the hierarchy would disagree with the
|
|
459
|
+
diagrams it is about.
|
|
460
|
+
|
|
461
|
+
The same answer is on the Diagrams page: select an element, open its
|
|
462
|
+
details (`i`), and choose **Show impact**. The view repaints — the element
|
|
463
|
+
and whatever contains it marked *Changed*, what depends on it and what it
|
|
464
|
+
depends on in their own colours, the rest faded — and the card gives the
|
|
465
|
+
model-wide counts and how many views draw it. It stays on as you move
|
|
466
|
+
between views; the chip in the toolbar clears it, and choosing a
|
|
467
|
+
perspective replaces it.
|
|
468
|
+
|
|
469
|
+
## Model standards (`c4 lint`)
|
|
470
|
+
Where `c4 check` asks whether a file parses, `c4 lint` asks whether it is
|
|
471
|
+
a good model — the review comments someone would otherwise have to make
|
|
472
|
+
every time:
|
|
473
|
+
|
|
474
|
+
```bash
|
|
475
|
+
uv run c4 lint architecture.dsl
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
Orphaned elements, missing descriptions and technologies, relationships
|
|
479
|
+
declared twice, and styles whose tag nothing carries (invisible
|
|
480
|
+
otherwise, because the legend only lists styles in use). Every finding is
|
|
481
|
+
a warning — a model that breaks a house style still parses and renders —
|
|
482
|
+
so the **exit code** is what gates CI: 1 on any finding, `--exit-zero` to
|
|
483
|
+
report without failing on a model nobody has linted before.
|
|
484
|
+
|
|
485
|
+
Configure it in `c4studio.lint.json` beside the workspace:
|
|
486
|
+
|
|
487
|
+
```json
|
|
488
|
+
{"ignore": ["missing-technology"], "naming": {"container": "^[A-Z]"}}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
`--json` emits the same shape editors already read, so findings land in
|
|
492
|
+
the Studio's problems list and the VS Code Problems panel.
|
|
493
|
+
|
|
494
|
+
## Headless SVG Rendering
|
|
495
|
+
`c4 render` draws diagrams as standalone SVG with no browser
|
|
496
|
+
and no server — for CI, docs-as-code pipelines and static sites. Layout is
|
|
497
|
+
the same code the web app runs, so positions match what you see in the
|
|
498
|
+
viewer, including any layout you have saved for a view.
|
|
499
|
+
|
|
500
|
+
```bash
|
|
501
|
+
uv run c4 render architecture.dsl -o diagrams/ # one .svg per view
|
|
502
|
+
uv run c4 render architecture.dsl -v Containers # one view to stdout
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
Each diagram carries its own title and a legend of the styles it actually
|
|
506
|
+
uses, so an exported file explains itself to someone who did not build it.
|
|
507
|
+
Pass `--no-title` or `--no-legend` to leave either out.
|
|
508
|
+
|
|
509
|
+
The output is self-contained: no external fonts or stylesheets, and theme
|
|
510
|
+
icons (the AWS/Azure/GCP service logos) are fetched once and embedded as
|
|
511
|
+
`data:` URIs, so a file renders identically wherever it is opened — even
|
|
512
|
+
offline. An icon that cannot be fetched is simply left out.
|
|
513
|
+
|
|
514
|
+
> **This is the only command that needs [Node.js](https://nodejs.org) 18+**
|
|
515
|
+
> (the renderer is bundled into the wheel, so there is no `npm install`).
|
|
516
|
+
> Set `C4STUDIO_NODE` if `node` is not on your `PATH`. Parsing,
|
|
517
|
+
> Mermaid generation, JSON export and the web app all work without it.
|
|
518
|
+
|
|
519
|
+
## Workspace JSON Export
|
|
520
|
+
Export any workspace (DSL or JSON) back to Structurizr workspace JSON,
|
|
521
|
+
round-tripping with structurizr.com, Structurizr Lite, and this package's
|
|
522
|
+
own parser:
|
|
523
|
+
|
|
524
|
+
```bash
|
|
525
|
+
uv run c4 export workspace.dsl -o workspace.json
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
Or programmatically via `c4studio.generators.json_export.export_json`.
|
|
529
|
+
|
|
530
|
+
## GitHub Action
|
|
531
|
+
Render the diagrams on every push and pull request, so reviewers see the
|
|
532
|
+
architecture change alongside the code change:
|
|
533
|
+
|
|
534
|
+
```yaml
|
|
535
|
+
name: Diagrams
|
|
536
|
+
on: [push, pull_request]
|
|
537
|
+
|
|
538
|
+
jobs:
|
|
539
|
+
render:
|
|
540
|
+
runs-on: ubuntu-latest
|
|
541
|
+
steps:
|
|
542
|
+
- uses: actions/checkout@v4
|
|
543
|
+
- uses: geoffkoh/c4studio@v0.3.0
|
|
544
|
+
id: diagrams
|
|
545
|
+
with:
|
|
546
|
+
workspace: docs/architecture.dsl
|
|
547
|
+
- uses: actions/upload-artifact@v4
|
|
548
|
+
with:
|
|
549
|
+
name: diagrams
|
|
550
|
+
path: ${{ steps.diagrams.outputs.diagrams-path }}
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
| Input | Default | What it does |
|
|
554
|
+
| --- | --- | --- |
|
|
555
|
+
| `workspace` | *required* | The `.dsl` or `.json` file to render |
|
|
556
|
+
| `output` | `diagrams` | Directory for the SVGs, one per view |
|
|
557
|
+
| `view` | *(all)* | Render a single view key |
|
|
558
|
+
| `mode` | `render` | `render`, `commit` (push the files back when they change) or `comment` (comment on the PR) |
|
|
559
|
+
| `version` | `c4studio` | pip requirement to install; `local` uses the checkout |
|
|
560
|
+
| `python-version` | `3.13` | Python to set up first — runners still default to an older one than this package needs. Empty string skips the step |
|
|
561
|
+
|
|
562
|
+
The action checks the workspace before rendering, so a parse error fails
|
|
563
|
+
the job with diagnostics rather than a traceback, and sets a `changed`
|
|
564
|
+
output. Because rendering is deterministic, `mode: commit` produces no
|
|
565
|
+
diff when the model has not changed.
|
|
566
|
+
|
|
567
|
+
## VS Code Extension
|
|
568
|
+
[`editors/vscode/`](./editors/vscode/) ships a VS Code extension with
|
|
569
|
+
Structurizr DSL syntax highlighting and an in-editor C4 diagram preview
|
|
570
|
+
(the full web app in a side panel, live-reloading as you save). The preview
|
|
571
|
+
runs in **Viewer** mode — VS Code already has the file open in its own
|
|
572
|
+
editor, and a second editor writing to it behind VS Code's back is the one
|
|
573
|
+
thing the preview must not do. Build and install it locally:
|
|
574
|
+
|
|
575
|
+
```bash
|
|
576
|
+
cd editors/vscode && npm install && npm run package
|
|
577
|
+
code --install-extension c4studio-vscode-*.vsix
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
## Documentation
|
|
581
|
+
- **[DSL Reference](./docs/dsl-reference.md)** - **How to write DSL for c4studio**: every construct, the `c4studio.*` extensions, and what is silently ignored. Examples are parsed by the test suite
|
|
582
|
+
- **[DSL Language Support](./docs/dsl-support.md)** - Every keyword in the Structurizr DSL and whether c4studio supports it
|
|
583
|
+
- **[Getting Started](./docs/README.md)** - Workflow and common patterns
|
|
584
|
+
- **[Studio Editor Plan](./docs/studio-editor-plan.md)** - How the editor was designed and built, and the decisions behind it
|
|
585
|
+
- **[Migration Guide](./docs/MIGRATION.md)** - Breaking changes, including the 0.3.0 Studio-by-default flip
|
|
586
|
+
- **[Enterprise Roadmap](./docs/roadmap.md)** - The staged forward plan
|
|
587
|
+
|