quickdbd-cli 0.1.12__tar.gz → 0.1.14__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.
Files changed (61) hide show
  1. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/NOTICE +2 -1
  2. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/PKG-INFO +6 -2
  3. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/README.md +5 -1
  4. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/pyproject.toml +1 -1
  5. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/scripts/build_gallery.py +2 -2
  6. quickdbd_cli-0.1.14/src/quickdbd_cli/__init__.py +1 -0
  7. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/assets/style.css +78 -59
  8. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/assets/viewer.js +1 -1
  9. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/cli.py +31 -1
  10. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/html.py +27 -6
  11. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/test_gallery.py +5 -0
  12. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/test_pipeline.py +41 -0
  13. quickdbd_cli-0.1.12/src/quickdbd_cli/__init__.py +0 -1
  14. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/.gitignore +0 -0
  15. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/LICENSE +0 -0
  16. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/examples/quickdbd-sample.txt +0 -0
  17. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/examples/synthetic/finance.txt +0 -0
  18. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/examples/synthetic/hr.txt +0 -0
  19. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/examples/synthetic/inventory.txt +0 -0
  20. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/examples/synthetic/marketing.txt +0 -0
  21. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/examples/synthetic/qdbd.layout.json +0 -0
  22. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/examples/synthetic/sales.txt +0 -0
  23. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/scripts/gen_font_metrics.py +0 -0
  24. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/scripts/gen_schema.py +0 -0
  25. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/scripts/gen_thumbnails.py +0 -0
  26. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/scripts/release.sh +0 -0
  27. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/__main__.py +0 -0
  28. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/assets/Inter-Bold.ttf +0 -0
  29. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/assets/Inter-Bold.woff2 +0 -0
  30. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/assets/Inter-LICENSE.txt +0 -0
  31. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/assets/Inter-Regular.ttf +0 -0
  32. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/assets/Inter-Regular.woff2 +0 -0
  33. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/assets/editor.js +0 -0
  34. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/assets/elk-LICENSE.md +0 -0
  35. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/assets/elk.bundled.js +0 -0
  36. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/assets/font-metrics.json +0 -0
  37. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/assets/render.js +0 -0
  38. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/dbt.py +0 -0
  39. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/diff.py +0 -0
  40. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/export.py +0 -0
  41. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/jsrt.py +0 -0
  42. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/layout.py +0 -0
  43. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/measure.py +0 -0
  44. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/model.py +0 -0
  45. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/parser.py +0 -0
  46. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/project.py +0 -0
  47. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/serve.py +0 -0
  48. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/share.py +0 -0
  49. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/tags.py +0 -0
  50. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/fixtures/jaffle/LICENSE +0 -0
  51. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/fixtures/jaffle/README.md +0 -0
  52. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/fixtures/jaffle/catalog.json +0 -0
  53. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/fixtures/jaffle/manifest.json +0 -0
  54. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/test_dbt.py +0 -0
  55. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/test_diff.py +0 -0
  56. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/test_parser.py +0 -0
  57. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/test_path.py +0 -0
  58. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/test_select.py +0 -0
  59. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/test_serve.py +0 -0
  60. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/test_share.py +0 -0
  61. {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/test_tags.py +0 -0
@@ -16,7 +16,8 @@ This product bundles third-party components, distributed unmodified:
16
16
 
17
17
  The demo gallery (gallery/, not part of the Python package) includes schemas derived from:
18
18
 
19
- * Northwind and AdventureWorks (gallery/northwind, gallery/adventureworks), converted from
19
+ * Northwind, pubs, AdventureWorks and AdventureWorks DW (gallery/northwind, gallery/pubs,
20
+ gallery/adventureworks, gallery/adventureworks-dw), converted from
20
21
  microsoft/sql-server-samples. Copyright (c) Microsoft Corporation. MIT License.
21
22
  * Sakila (gallery/sakila), converted from sakila-schema.sql. Copyright (c) 2006, 2026, Oracle and/or
22
23
  its affiliates. BSD-3-Clause.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: quickdbd-cli
3
- Version: 0.1.12
3
+ Version: 0.1.14
4
4
  Summary: Compile QuickDBD-syntax schema files into interactive HTML ER diagrams, with no table limit.
5
5
  Project-URL: Homepage, https://github.com/MarkPhamm/quickdbd-cli
6
6
  Author: Minh (Mark) Pham
@@ -77,7 +77,7 @@ Every demo is a single HTML page written by `qdbd build`, from a QuickDBD schema
77
77
  | [![200 tables](https://raw.githubusercontent.com/MarkPhamm/quickdbd-cli/main/docs/assets/img/gallery/synthetic.png)](https://qdbd.dev/demo/synthetic/)<br>**[200 tables](https://qdbd.dev/demo/synthetic/)**<br>Five business domains, 200 tables and every relationship type. Search, filter, click a table to focus it. | [![Schema diff](https://raw.githubusercontent.com/MarkPhamm/quickdbd-cli/main/docs/assets/img/gallery/diff.png)](https://qdbd.dev/demo/diff/)<br>**[Schema diff](https://qdbd.dev/demo/diff/)**<br>What a branch changed, coloured in: new tables in green, changed columns in amber, removals listed. |
78
78
  | [![AdventureWorks](https://raw.githubusercontent.com/MarkPhamm/quickdbd-cli/main/docs/assets/img/gallery/adventureworks.png)](https://qdbd.dev/demo/adventureworks/)<br>**[AdventureWorks](https://qdbd.dev/demo/adventureworks/)**<br>Microsoft's bicycle-company OLTP database: 68 tables in five schemas, one region each, with column descriptions. | [![OMOP CDM on Synthea](https://raw.githubusercontent.com/MarkPhamm/quickdbd-cli/main/docs/assets/img/gallery/synthea.png)](https://qdbd.dev/demo/synthea/#t=concept)<br>**[OMOP CDM on Synthea](https://qdbd.dev/demo/synthea/#t=concept)**<br>OHDSI's dbt-synthea: synthetic patient records mapped to the OMOP Common Data Model. 145 tables, 235 relationships from tests. |
79
79
 
80
- [View all 14 demos →](https://qdbd.dev/demo/)
80
+ [View all 18 demos →](https://qdbd.dev/demo/)
81
81
  <!-- gallery:end -->
82
82
 
83
83
  ## Quickstart
@@ -244,6 +244,10 @@ You edit the text; the diagram is generated from it. Table positions are the one
244
244
  - [Schema Diffs](https://qdbd.dev/diffs/), the [GitHub Action](https://qdbd.dev/github-action/) and the [CLI reference](https://qdbd.dev/cli/)
245
245
  - [Development](https://qdbd.dev/development/): working on qdbd itself, releasing, and adding a demo
246
246
 
247
+ ## About the author
248
+
249
+ qdbd is built and maintained by **Minh (Mark) Pham** ([@MarkPhamm](https://github.com/MarkPhamm)). Issues, ideas and pull requests are welcome on [GitHub](https://github.com/MarkPhamm/quickdbd-cli/issues).
250
+
247
251
  ## License
248
252
 
249
253
  Apache-2.0. Bundled third-party files (ELK, Inter) and the sources of the demo schemas are listed in [NOTICE](https://github.com/MarkPhamm/quickdbd-cli/blob/main/NOTICE).
@@ -49,7 +49,7 @@ Every demo is a single HTML page written by `qdbd build`, from a QuickDBD schema
49
49
  | [![200 tables](https://raw.githubusercontent.com/MarkPhamm/quickdbd-cli/main/docs/assets/img/gallery/synthetic.png)](https://qdbd.dev/demo/synthetic/)<br>**[200 tables](https://qdbd.dev/demo/synthetic/)**<br>Five business domains, 200 tables and every relationship type. Search, filter, click a table to focus it. | [![Schema diff](https://raw.githubusercontent.com/MarkPhamm/quickdbd-cli/main/docs/assets/img/gallery/diff.png)](https://qdbd.dev/demo/diff/)<br>**[Schema diff](https://qdbd.dev/demo/diff/)**<br>What a branch changed, coloured in: new tables in green, changed columns in amber, removals listed. |
50
50
  | [![AdventureWorks](https://raw.githubusercontent.com/MarkPhamm/quickdbd-cli/main/docs/assets/img/gallery/adventureworks.png)](https://qdbd.dev/demo/adventureworks/)<br>**[AdventureWorks](https://qdbd.dev/demo/adventureworks/)**<br>Microsoft's bicycle-company OLTP database: 68 tables in five schemas, one region each, with column descriptions. | [![OMOP CDM on Synthea](https://raw.githubusercontent.com/MarkPhamm/quickdbd-cli/main/docs/assets/img/gallery/synthea.png)](https://qdbd.dev/demo/synthea/#t=concept)<br>**[OMOP CDM on Synthea](https://qdbd.dev/demo/synthea/#t=concept)**<br>OHDSI's dbt-synthea: synthetic patient records mapped to the OMOP Common Data Model. 145 tables, 235 relationships from tests. |
51
51
 
52
- [View all 14 demos →](https://qdbd.dev/demo/)
52
+ [View all 18 demos →](https://qdbd.dev/demo/)
53
53
  <!-- gallery:end -->
54
54
 
55
55
  ## Quickstart
@@ -216,6 +216,10 @@ You edit the text; the diagram is generated from it. Table positions are the one
216
216
  - [Schema Diffs](https://qdbd.dev/diffs/), the [GitHub Action](https://qdbd.dev/github-action/) and the [CLI reference](https://qdbd.dev/cli/)
217
217
  - [Development](https://qdbd.dev/development/): working on qdbd itself, releasing, and adding a demo
218
218
 
219
+ ## About the author
220
+
221
+ qdbd is built and maintained by **Minh (Mark) Pham** ([@MarkPhamm](https://github.com/MarkPhamm)). Issues, ideas and pull requests are welcome on [GitHub](https://github.com/MarkPhamm/quickdbd-cli/issues).
222
+
219
223
  ## License
220
224
 
221
225
  Apache-2.0. Bundled third-party files (ELK, Inter) and the sources of the demo schemas are listed in [NOTICE](https://github.com/MarkPhamm/quickdbd-cli/blob/main/NOTICE).
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "quickdbd-cli"
7
- version = "0.1.12"
7
+ version = "0.1.14"
8
8
  description = "Compile QuickDBD-syntax schema files into interactive HTML ER diagrams, with no table limit."
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -37,8 +37,8 @@ KINDS = ("source", "dbt", "of")
37
37
 
38
38
  # Added to every demo page: a way back to the gallery, as the first item of the viewer's toolbar.
39
39
  BACK_LINK = (
40
- '<a href="../" style="color:var(--blue);background:#fff;padding:5px 10px;border-radius:6px;text-decoration:none;'
41
- 'font-weight:500;box-shadow:0 1px 2px rgba(0,0,0,.12)">&larr; All demos</a><div class="sep"></div>'
40
+ '<a href="../" style="color:var(--blue);padding:5px 9px;border-radius:6px;text-decoration:none;'
41
+ 'font-weight:500">&larr; All demos</a><div class="sep"></div>'
42
42
  )
43
43
 
44
44
 
@@ -0,0 +1 @@
1
+ __version__ = "0.1.14"
@@ -1,11 +1,19 @@
1
1
  :root {
2
- --bg: #f4f5f4;
2
+ --bg: #f5f6f7;
3
3
  --blue: #2b5797;
4
- --ink: #333;
5
- --muted: #8a8f96;
4
+ --ink: #15181c;
5
+ --ink-2: #4a515b;
6
+ --muted: #6b7280;
6
7
  --panel: #ffffff;
7
- --line: #dfe3e8;
8
+ --line: #e4e7eb;
9
+ --hover: #f1f3f5;
10
+ --sel: #eaf0f8;
11
+ --amber: #f0a020;
12
+ --shadow: 0 1px 2px rgba(16, 24, 40, .05), 0 8px 24px rgba(16, 24, 40, .07);
8
13
  }
14
+ ::selection { background: #fde3b0; color: var(--ink); }
15
+ :focus-visible { outline: 2px solid var(--amber); outline-offset: 1px; }
16
+ * { scrollbar-width: thin; scrollbar-color: #cfd4da transparent; }
9
17
  * { box-sizing: border-box; }
10
18
  html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
11
19
  font-family: Inter, "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 13px; overflow: hidden; }
@@ -26,32 +34,36 @@ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
26
34
  #canvas.panning .t { cursor: grabbing; }
27
35
 
28
36
  /* toolbar (top right, like QuickDBD's zoom controls) */
29
- .bar { position: absolute; top: 14px; right: 16px; display: flex; gap: 6px; align-items: center; }
30
- .bar button, .seg button { border: 0; background: transparent; color: #555; font: inherit; cursor: pointer;
31
- padding: 6px 9px; border-radius: 4px; }
32
- .bar button:hover, .seg button:hover { background: #e4e7ea; }
33
- .bar button.on { background: #fff; color: var(--blue); box-shadow: 0 1px 2px rgba(0,0,0,.12); }
34
- .zoomval { min-width: 48px; text-align: center; color: #666; font-variant-numeric: tabular-nums; }
35
- .seg { display: flex; background: #e9ecee; border-radius: 6px; padding: 2px; }
36
- .seg button.on { background: #fff; color: var(--blue); box-shadow: 0 1px 2px rgba(0,0,0,.12); }
37
- .sep { width: 1px; height: 20px; background: #d5d9dd; margin: 0 4px; }
37
+ .bar { position: absolute; top: 14px; right: 16px; display: flex; gap: 2px; align-items: center; padding: 4px;
38
+ background: var(--panel); border: 1px solid var(--line); border-radius: 10px; box-shadow: var(--shadow); z-index: 4; }
39
+ .bar button, .seg button { border: 0; background: transparent; color: var(--ink-2); font: inherit; font-weight: 500;
40
+ cursor: pointer; padding: 5px 9px; border-radius: 6px; transition: background .12s, color .12s; }
41
+ .bar button:hover, .seg button:hover { background: var(--hover); color: var(--ink); }
42
+ .bar button.on { background: var(--ink); color: #fff; }
43
+ .zoomval { min-width: 46px; text-align: center; color: var(--muted); font-variant-numeric: tabular-nums; }
44
+ .seg { display: flex; background: var(--hover); border-radius: 7px; padding: 2px; }
45
+ .seg button.on { background: var(--panel); color: var(--ink); box-shadow: 0 1px 2px rgba(16, 24, 40, .12); }
46
+ .sep { width: 1px; height: 18px; background: var(--line); margin: 0 4px; }
47
+ .bar button svg, .exhead button svg, .details .close svg { display: block; }
48
+ #zin, #zout, #zfit { padding: 5px 6px; }
38
49
 
39
50
  /* explorer (left): search + table tree grouped by file/folder */
40
51
  .explorer { position: absolute; top: 14px; left: 16px; width: 270px; max-height: calc(100% - 60px); display: flex;
41
- flex-direction: column; background: #fff; border: 1px solid var(--line); border-radius: 8px;
42
- box-shadow: 0 4px 14px rgba(0,0,0,.06); z-index: 3; }
52
+ flex-direction: column; background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
53
+ box-shadow: var(--shadow); z-index: 3; }
43
54
  .exhead { display: flex; align-items: center; gap: 4px; padding: 6px; border-bottom: 1px solid var(--line); }
44
55
  .explorer.collapsed .exhead { border-bottom: 0; }
45
- .exhead button { border: 0; background: transparent; cursor: pointer; font-size: 15px; color: #666; padding: 4px 6px; border-radius: 4px; }
46
- .exhead button:hover { background: #eef1f4; }
47
- .exhead input { flex: 1; min-width: 0; padding: 6px 8px; border: 1px solid transparent; border-radius: 5px; font: inherit;
48
- background: #f4f5f6; outline: none; }
49
- .exhead input:focus { border-color: var(--blue); background: #fff; }
56
+ .exhead button { border: 0; background: transparent; cursor: pointer; font-size: 15px; color: var(--muted); padding: 4px 7px; border-radius: 6px; }
57
+ .exhead button:hover { background: var(--hover); color: var(--ink); }
58
+ .exhead input { flex: 1; min-width: 0; padding: 6px 9px; border: 1px solid transparent; border-radius: 7px; font: inherit;
59
+ background: var(--hover); color: var(--ink); outline: none; }
60
+ .exhead input::placeholder, .exfilter input::placeholder, .details .pathbox input::placeholder { color: var(--muted); }
61
+ .exhead input:focus { border-color: var(--ink-2); background: var(--panel); }
50
62
  .exfilter { padding: 6px 6px 4px; border-bottom: 1px solid var(--line); }
51
63
  .explorer.collapsed .exfilter { display: none; }
52
- .exfilter input { width: 100%; padding: 5px 8px; border: 1px solid var(--line); border-radius: 5px; font: 12px Menlo, Consolas, monospace;
53
- outline: none; }
54
- .exfilter input:focus { border-color: var(--blue); }
64
+ .exfilter input { width: 100%; padding: 5px 8px; border: 1px solid var(--line); border-radius: 7px; font: 12px ui-monospace, Menlo, Consolas, monospace;
65
+ color: var(--ink); outline: none; }
66
+ .exfilter input:focus { border-color: var(--ink-2); }
55
67
  .exfilter input.bad { border-color: #e57373; }
56
68
  .fstat { font-size: 11px; color: var(--muted); padding: 3px 2px 0; min-height: 14px; }
57
69
  .fstat.bad { color: #c0392b; }
@@ -62,50 +74,55 @@ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
62
74
  .tchip i { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
63
75
  .tchip small { color: var(--muted); }
64
76
  .tchip:hover { border-color: #b8c2d0; }
65
- .tchip.on { border-color: var(--blue); background: #eef3fa; }
77
+ .tchip.on { border-color: var(--ink-2); background: var(--sel); }
66
78
  .tdot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; margin: 0 6px 1px -13px; vertical-align: middle; }
67
79
  .tree.flat .tdot { margin-left: 0; }
68
80
  .details .ttags { margin: -4px 0 10px; display: flex; gap: 4px; flex-wrap: wrap; }
69
81
  .details .ttag { color: #fff; font-size: 11px; padding: 1px 8px; border-radius: 10px; cursor: pointer; text-decoration: none; }
70
82
  .tree { overflow: auto; padding: 4px 0 8px; }
71
83
  .explorer.collapsed .tree { display: none; }
72
- .gh { padding: 6px 12px 4px; font-size: 11px; font-weight: bold; color: var(--muted); text-transform: uppercase;
73
- letter-spacing: .04em; cursor: pointer; user-select: none; display: flex; justify-content: space-between; }
74
- .gh::before { content: "\25BE"; margin-right: 6px; }
75
- .grp.shut .gh::before { content: "\25B8"; }
84
+ .gh { padding: 8px 12px 4px; font-size: 11px; font-weight: 600; color: var(--muted); text-transform: uppercase;
85
+ letter-spacing: .05em; cursor: pointer; user-select: none; display: flex; align-items: center; justify-content: space-between; }
86
+ .gh::before { content: ""; width: 5px; height: 5px; margin: -2px 9px 0 2px; border-right: 1.5px solid currentColor;
87
+ border-bottom: 1.5px solid currentColor; transform: rotate(45deg); transition: transform .15s; }
88
+ .grp.shut .gh::before { transform: rotate(-45deg); margin-top: 0; }
89
+ .gh:hover { color: var(--ink); }
76
90
  .gh span { font-weight: normal; }
77
91
  .gh .gname { flex: 1; }
78
92
  .grp.shut .gl { display: none; }
79
93
  .ti { padding: 4px 12px 4px 26px; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
80
94
  .tree.flat .ti { padding-left: 14px; }
81
95
  .ti small { color: var(--muted); margin-left: 6px; }
82
- .ti:hover, .ti.sel { background: #eef3fa; }
96
+ .ti { border-radius: 6px; margin: 0 6px; padding-left: 20px; }
97
+ .ti:hover { background: var(--hover); }
98
+ .ti.sel { background: var(--sel); }
83
99
  .ti.cur { color: var(--blue); font-weight: bold; }
84
100
  .tree .empty { padding: 8px 14px; color: var(--muted); }
85
101
 
86
102
  /* details drawer (right) */
87
- .details { position: absolute; top: 58px; right: 16px; bottom: 16px; width: 380px; background: #fff;
88
- border: 1px solid var(--line); border-radius: 8px; box-shadow: 0 6px 20px rgba(0,0,0,.08); overflow: auto;
103
+ .details { position: absolute; top: 64px; right: 16px; bottom: 16px; width: 380px; background: var(--panel);
104
+ border: 1px solid var(--line); border-radius: 10px; box-shadow: var(--shadow); overflow: auto;
89
105
  display: none; z-index: 3; padding: 16px 18px; }
90
106
  .details.open { display: block; }
91
107
  .has-details .minimap { right: 412px; }
92
- .details .close { position: absolute; top: 8px; right: 10px; border: 0; background: transparent; font-size: 20px;
93
- color: #888; cursor: pointer; }
94
- .details h2 { margin: 0 24px 2px 0; font-size: 18px; color: var(--blue); word-break: break-word; }
108
+ .details .close { position: absolute; top: 10px; right: 10px; width: 28px; height: 28px; border: 0; border-radius: 6px;
109
+ background: transparent; display: flex; align-items: center; justify-content: center; color: var(--muted); cursor: pointer; }
110
+ .details .close:hover { background: var(--hover); color: var(--ink); }
111
+ .details h2 { margin: 0 28px 2px 0; font-size: 18px; font-weight: 650; letter-spacing: -.01em; color: var(--ink); word-break: break-word; }
95
112
  .details .meta { color: var(--muted); font-size: 12px; margin-bottom: 10px; }
96
113
  .details .doc { white-space: pre-wrap; margin: 8px 0 12px; line-height: 1.45; }
97
- .details h3 { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin: 16px 0 6px; }
114
+ .details h3 { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin: 18px 0 6px; }
98
115
  .details table { width: 100%; border-collapse: collapse; font-size: 12px; }
99
- .details td { padding: 5px 4px; border-top: 1px solid #eef0f2; vertical-align: top; }
116
+ .details td { padding: 6px 4px; border-top: 1px solid var(--line); vertical-align: top; }
100
117
  .details td.cn { font-weight: 600; word-break: break-word; }
101
- .details td.ct { color: #888; text-align: right; white-space: nowrap; }
102
- .details .cd { color: #666; font-weight: normal; margin-top: 2px; white-space: pre-wrap; }
118
+ .details td.ct { color: var(--muted); text-align: right; white-space: nowrap; }
119
+ .details .cd { color: var(--ink-2); font-weight: normal; margin-top: 2px; white-space: pre-wrap; }
103
120
  .details .tag { display: inline-block; font-size: 10px; padding: 0 5px; border-radius: 3px; background: #eef1f5;
104
121
  color: #6b7a8c; margin: 0 3px 0 0; font-weight: normal; }
105
122
  .details .tag.pk { background: #dfe8f6; color: var(--blue); }
106
123
  .details .tag.fk { background: #ece6f7; color: #6a4fa3; }
107
124
  .details ul { list-style: none; margin: 0; padding: 0; }
108
- .details li { padding: 5px 0; border-top: 1px solid #eef0f2; }
125
+ .details li { padding: 6px 0; border-top: 1px solid var(--line); }
109
126
  .details a { color: var(--blue); text-decoration: none; cursor: pointer; }
110
127
  .details a:hover { text-decoration: underline; }
111
128
  .details .card { color: var(--muted); font-size: 11px; margin-left: 4px; }
@@ -113,22 +130,23 @@ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
113
130
  .details .hidden { color: var(--muted); }
114
131
 
115
132
  /* minimap (bottom right) */
116
- .minimap { position: absolute; right: 16px; bottom: 16px; width: 200px; height: 140px; background: rgba(255,255,255,.9);
117
- border: 1px solid var(--line); border-radius: 6px; overflow: hidden; cursor: pointer; }
133
+ .minimap { position: absolute; right: 16px; bottom: 16px; width: 200px; height: 140px; background: rgba(255, 255, 255, .94);
134
+ border: 1px solid var(--line); border-radius: 10px; box-shadow: var(--shadow); overflow: hidden; cursor: pointer; }
118
135
  .minimap svg { width: 100%; height: 100%; display: block; }
119
136
  .minimap .vpr { fill: rgba(43,87,151,.12); stroke: var(--blue); stroke-width: 1.5; vector-effect: non-scaling-stroke; }
120
137
 
121
- .toast { position: absolute; left: 50%; bottom: 24px; transform: translateX(-50%); background: #23272b; color: #fff;
122
- padding: 8px 14px; border-radius: 6px; font-size: 12px; opacity: 0; transition: opacity .2s; pointer-events: none; z-index: 6; }
138
+ .toast { position: absolute; left: 50%; bottom: 24px; transform: translateX(-50%); background: var(--ink); color: #fff;
139
+ padding: 8px 14px; border-radius: 8px; font-size: 12px; opacity: 0; transition: opacity .2s; pointer-events: none; z-index: 6; }
123
140
  .toast.open { opacity: 1; }
124
141
 
125
142
  /* path finder (in the details panel) */
126
143
  .details .pathbox { display: flex; gap: 6px; margin: 10px 0 4px; }
127
- .details .pathbox input { flex: 1; min-width: 0; padding: 5px 8px; border: 1px solid var(--line); border-radius: 5px; font: inherit; outline: none; }
128
- .details .pathbox input:focus { border-color: var(--blue); }
144
+ .details .pathbox input { flex: 1; min-width: 0; padding: 6px 9px; border: 1px solid var(--line); border-radius: 7px; font: inherit;
145
+ color: var(--ink); outline: none; }
146
+ .details .pathbox input:focus { border-color: var(--ink-2); }
129
147
  .details .pathres ol { margin: 6px 0 8px 18px; padding: 0; }
130
148
  .details .pathres li { border: 0; padding: 2px 0; }
131
- .details .pathres pre { background: #f6f8fa; border: 1px solid #eceff2; border-radius: 6px; padding: 8px; font-size: 11px;
149
+ .details .pathres pre { background: var(--bg); border: 1px solid var(--line); border-radius: 7px; padding: 8px; font-size: 11px;
132
150
  overflow-x: auto; margin: 6px 0; }
133
151
  .details .pathres .acts a { margin-right: 12px; font-size: 12px; }
134
152
  .details .pathres .none { margin-top: 6px; }
@@ -137,8 +155,8 @@ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
137
155
  .t.ph .tb { stroke: #f0a020; stroke-width: 2.5; }
138
156
 
139
157
  /* diff banner (build --diff-base) */
140
- .diffbar { position: absolute; top: 58px; left: 50%; transform: translateX(-50%); background: #fff; border: 1px solid var(--line);
141
- border-radius: 8px; padding: 6px 12px; font-size: 12px; box-shadow: 0 4px 14px rgba(0,0,0,.06); display: none; z-index: 2;
158
+ .diffbar { position: absolute; top: 64px; left: 50%; transform: translateX(-50%); background: var(--panel); border: 1px solid var(--line);
159
+ border-radius: 10px; padding: 7px 14px; font-size: 12px; box-shadow: var(--shadow); display: none; z-index: 2;
142
160
  white-space: nowrap; }
143
161
  .diffbar.open { display: block; }
144
162
  .diffbar b.add { color: #2e7d32; }
@@ -155,7 +173,7 @@ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
155
173
  .details .state.changed { background: #d48806; }
156
174
 
157
175
  /* doc-comment tooltip */
158
- .tip { position: absolute; display: none; max-width: 340px; background: #23272b; color: #eee; border-radius: 6px;
176
+ .tip { position: absolute; display: none; max-width: 340px; background: var(--ink); color: #eef0f2; border-radius: 8px;
159
177
  padding: 8px 10px; font-size: 12px; line-height: 1.45; pointer-events: none; box-shadow: 0 4px 14px rgba(0,0,0,.25);
160
178
  z-index: 5; }
161
179
  .tip.open { display: block; }
@@ -166,17 +184,18 @@ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
166
184
  .hint { position: absolute; left: 16px; bottom: 14px; color: var(--muted); font-size: 12px; }
167
185
 
168
186
  /* serve: editor pane */
169
- #side { width: 420px; display: flex; flex-direction: column; background: #343a40; color: #ddd; border-right: 1px solid #222; }
187
+ #side { width: 420px; display: flex; flex-direction: column; background: #0f1114; color: #d9dce1; border-right: 1px solid #000; }
170
188
  #side.hidden { display: none; }
171
- .tabs { display: flex; overflow-x: auto; background: #2b3035; flex: none; }
172
- .tabs button { background: transparent; color: #aab; border: 0; padding: 8px 12px; font: inherit; cursor: pointer; white-space: nowrap; }
173
- .tabs button.on { color: #fff; background: #343a40; }
189
+ .tabs { display: flex; overflow-x: auto; background: #08090b; border-bottom: 1px solid rgba(255, 255, 255, .08); flex: none; }
190
+ .tabs button { background: transparent; color: #a1a7b0; border: 0; padding: 9px 13px; font: inherit; cursor: pointer; white-space: nowrap; }
191
+ .tabs button:hover { color: #edeef0; }
192
+ .tabs button.on { color: #edeef0; background: #0f1114; box-shadow: inset 0 -2px 0 var(--amber); }
174
193
  .tabs button .dirty { color: #f0a020; }
175
194
  #editor { flex: 1; min-height: 0; position: relative; }
176
195
  #editor .CodeMirror, #editor textarea { position: absolute; inset: 0; height: 100%; width: 100%; font-size: 13px;
177
- font-family: Menlo, Consolas, monospace; background: #343a40; color: #ddd; border: 0; padding: 6px; resize: none; }
178
- #problems { height: 140px; flex: none; overflow: auto; border-top: 2px solid #4caf50; padding: 8px 10px;
179
- font-family: Menlo, Consolas, monospace; font-size: 12px; color: #ccc; }
196
+ font-family: ui-monospace, Menlo, Consolas, monospace; background: #0f1114; color: #d9dce1; border: 0; padding: 6px; resize: none; }
197
+ #problems { height: 140px; flex: none; overflow: auto; border-top: 2px solid #4ade80; padding: 8px 10px; background: #08090b;
198
+ font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 12px; color: #a1a7b0; }
180
199
  #problems.bad { border-top-color: #e57373; }
181
200
  #problems .err { color: #ff9b9b; cursor: pointer; }
182
201
  #problems .warn { color: #f0c674; cursor: pointer; }
@@ -185,17 +204,17 @@ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
185
204
  .banner button { margin-left: 8px; }
186
205
  .cm-qdbd-op { color: #f0a020; }
187
206
  .cm-qdbd-kw { color: #8fbcff; }
188
- .CodeMirror-gutters { background: #2f353a !important; border-right: 0 !important; }
207
+ .CodeMirror-gutters { background: #0b0c0f !important; border-right: 0 !important; }
189
208
  .cm-s-default .cm-comment { color: #7f8c8d; }
190
209
  .cm-s-default .cm-header { color: #6fc3df; }
191
210
  .CodeMirror-cursor { border-left-color: #fff !important; }
192
- .CodeMirror-selected { background: #4a525a !important; }
211
+ .CodeMirror-selected { background: #2a2e35 !important; }
193
212
  .lineerr { background: rgba(229,115,115,.18); }
194
213
 
195
214
  /* narrow screens and embedded frames: toolbar to the bottom, smaller minimap */
196
215
  @media (max-width: 900px) {
197
216
  .bar { top: auto; bottom: 12px; right: auto; left: 12px; flex-wrap: wrap; max-width: calc(100% - 24px);
198
- background: rgba(255,255,255,.92); border-radius: 8px; padding: 2px; box-shadow: 0 2px 10px rgba(0,0,0,.08); }
217
+ border-radius: 10px; padding: 3px; }
199
218
  .hint { display: none; }
200
219
  .minimap { width: 140px; height: 100px; bottom: auto; top: 14px; right: 12px; }
201
220
  .explorer { max-width: calc(100% - 180px); }
@@ -60,7 +60,7 @@
60
60
  // Keep clear of the floating search box / toolbar at the top and, on narrow screens, the toolbar at the bottom.
61
61
  var top = 70, bottom = sz[0] < 900 ? 70 : pad, availH = sz[1] - top - bottom;
62
62
  var k = Math.min((sz[0] - 2 * pad) / (b[2] - b[0]), availH / (b[3] - b[1]), 1.5);
63
- k = Math.max(k, 0.05);
63
+ k = Math.max(k, 0.03); // same floor as manual zoom, so wide diagrams still fit a phone screen
64
64
  S.view = { k: k, x: (sz[0] - (b[2] - b[0]) * k) / 2 - b[0] * k, y: top + (availH - (b[3] - b[1]) * k) / 2 - b[1] * k };
65
65
  applyView();
66
66
  }
@@ -3,11 +3,13 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import argparse
6
+ import dataclasses
7
+ import json
6
8
  import sys
7
9
  from pathlib import Path
8
10
 
9
11
  from . import __version__
10
- from .model import Schema
12
+ from .model import Diagnostic, Schema
11
13
  from .project import Project, SelectionError, selector_expr
12
14
 
13
15
  OK = "No errors found. Good job!"
@@ -36,9 +38,31 @@ def _title(project: Project) -> str:
36
38
  return "diagram"
37
39
 
38
40
 
41
+ def _github_annotation(d: Diagnostic, level: str) -> str:
42
+ """A GitHub Actions workflow command, which shows `d` inline on the PR's changed lines."""
43
+
44
+ def esc(s: str) -> str:
45
+ return s.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A")
46
+
47
+ def esc_prop(s: str) -> str:
48
+ return esc(s).replace(":", "%3A").replace(",", "%2C")
49
+
50
+ return f"::{level} file={esc_prop(d.path)},line={d.line},title=qdbd::{esc(d.message)}"
51
+
52
+
39
53
  def cmd_check(args) -> int:
40
54
  project = _open(args)
41
55
  schema = project.compile()
56
+ if args.format == "json":
57
+ ok = not schema.errors and not (args.strict and schema.warnings)
58
+ diags = [dataclasses.asdict(d) for d in schema.diagnostics]
59
+ report = {"ok": ok, "errors": len(schema.errors), "warnings": len(schema.warnings), "diagnostics": diags}
60
+ print(json.dumps(report, indent=2))
61
+ return 0 if ok else 1
62
+ if args.format == "github":
63
+ # Under --strict a warning fails the job, so it's annotated as an error.
64
+ for d in schema.diagnostics:
65
+ print(_github_annotation(d, "error" if args.strict else d.severity))
42
66
  ok = _print_diagnostics(schema, args.strict)
43
67
  if ok and not schema.diagnostics:
44
68
  print(OK)
@@ -345,6 +369,12 @@ def build_parser() -> argparse.ArgumentParser:
345
369
  sp = sub.add_parser("check", help="validate schema files")
346
370
  paths(sp)
347
371
  sp.add_argument("--strict", action="store_true", help="treat warnings (QuickDBD incompatibilities) as errors")
372
+ sp.add_argument(
373
+ "--format",
374
+ choices=["text", "github", "json"],
375
+ default="text",
376
+ help="github: also emit GitHub Actions annotations (inline on PR lines); json: a machine-readable report",
377
+ )
348
378
  sp.set_defaults(func=cmd_check)
349
379
 
350
380
  sp = sub.add_parser("build", help="write an interactive HTML diagram")
@@ -23,6 +23,25 @@ def _escape(text: str) -> str:
23
23
  return text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;")
24
24
 
25
25
 
26
+ def _icon(d: str) -> str:
27
+ """A 16px stroke icon, drawn in the toolbar's text colour."""
28
+ return (
29
+ '<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">'
30
+ f'<path d="{d}" fill="none" stroke="currentColor" stroke-width="1.8"'
31
+ ' stroke-linecap="round" stroke-linejoin="round"/>'
32
+ "</svg>"
33
+ )
34
+
35
+
36
+ ICONS = {
37
+ "icon_menu": _icon("M4 6h16M4 12h16M4 18h16"),
38
+ "icon_close": _icon("M6 6l12 12M18 6L6 18"),
39
+ "icon_minus": _icon("M5 12h14"),
40
+ "icon_plus": _icon("M12 5v14M5 12h14"),
41
+ "icon_fit": _icon("M4 9V4h5M15 4h5v5M20 15v5h-5M9 20H4v-5"),
42
+ }
43
+
44
+
26
45
  BODY = """
27
46
  <div id="app">
28
47
  {side}
@@ -30,7 +49,7 @@ BODY = """
30
49
  <svg id="canvas" xmlns="http://www.w3.org/2000/svg"><g id="vp"></g></svg>
31
50
  <div class="explorer" id="explorer">
32
51
  <div class="exhead">
33
- <button id="extoggle" title="Show/hide table list">&#9776;</button>
52
+ <button id="extoggle" title="Show/hide table list" aria-label="Show or hide the table list">{icon_menu}</button>
34
53
  <input id="q" type="search" placeholder="Search {count} tables ( / )" autocomplete="off" spellcheck="false">
35
54
  </div>
36
55
  <div class="exfilter">
@@ -42,7 +61,7 @@ BODY = """
42
61
  <div id="tree" class="tree"></div>
43
62
  </div>
44
63
  <aside class="details" id="details" aria-label="Table details">
45
- <button class="close" id="dclose" title="Close (Esc)">&times;</button>
64
+ <button class="close" id="dclose" title="Close (Esc)" aria-label="Close">{icon_close}</button>
46
65
  <div id="dbody"></div>
47
66
  </aside>
48
67
  <div class="bar">
@@ -59,10 +78,10 @@ BODY = """
59
78
  <button id="xpng" title="Export PNG">PNG</button>
60
79
  <button id="xdrawio" title="Export an editable draw.io / diagrams.net file">draw.io</button>
61
80
  <div class="sep"></div>
62
- <button id="zout" title="Zoom out">&#8722;</button>
81
+ <button id="zout" title="Zoom out" aria-label="Zoom out">{icon_minus}</button>
63
82
  <span class="zoomval" id="zoomval">100%</span>
64
- <button id="zin" title="Zoom in">+</button>
65
- <button id="zfit" title="Fit to screen">&#9678;</button>
83
+ <button id="zin" title="Zoom in" aria-label="Zoom in">{icon_plus}</button>
84
+ <button id="zfit" title="Fit to screen" aria-label="Fit to screen">{icon_fit}</button>
66
85
  </div>
67
86
  <div class="minimap">
68
87
  <svg id="mini" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet"></svg>
@@ -113,7 +132,9 @@ def render_page(model: dict, positions: dict, title: str, serve: bool = False, l
113
132
  hint = (
114
133
  "Drag tables to rearrange (not saved). Click a table for details. Scroll to zoom, drag empty space to pan."
115
134
  )
116
- body = BODY.format(side=SIDE if serve else "", count=len(model["tables"]), hint=hint, filter_help=FILTER_HELP)
135
+ body = BODY.format(
136
+ side=SIDE if serve else "", count=len(model["tables"]), hint=hint, filter_help=FILTER_HELP, **ICONS
137
+ )
117
138
  head_extra = ""
118
139
  scripts = ""
119
140
  if serve:
@@ -91,6 +91,11 @@ def test_sample_databases_and_dbt_projects(built):
91
91
  assert sizes["adventureworks"][0] == 68 and sizes["adventureworks"][1] >= 90
92
92
  assert sizes["synthea"] == (145, 235)
93
93
  assert sizes["shopify"][0] == 155 and sizes["shopify"][1] > 150 # every link inferred
94
+ for d, n in (("pubs", 11), ("adventureworks-dw", 28), ("saas-billing", 13), ("courses", 14)):
95
+ m = _model(built / d / "index.html")
96
+ assert len(m["tables"]) == n and m["rels"], d
97
+ dw = _model(built / "adventureworks-dw" / "index.html")
98
+ assert {"dim", "fact"} <= {t["name"] for t in dw["tags"]} # coloured by the Dim/Fact name prefixes
94
99
  aw = _model(built / "adventureworks" / "index.html")
95
100
  assert aw["regions"] and any(t.get("doc") for t in aw["tables"]) # MS_Description -> tooltips
96
101
 
@@ -136,6 +136,47 @@ def test_check_strict_warnings(tmp_path):
136
136
  assert main(["check", "--strict", str(f)]) == 1
137
137
 
138
138
 
139
+ def test_check_format_github(tmp_path, capsys):
140
+ bad = tmp_path / "bad.txt"
141
+ bad.write_text("T\n-\nA int foo\nB decimal(10, 2)\n")
142
+ assert main(["check", "--format", "github", str(bad)]) == 1
143
+ out = capsys.readouterr().out.splitlines()
144
+ assert f"::error file={bad},line=3,title=qdbd::Unrecognized keyword 'foo'" in out
145
+ assert any(line.startswith(f"::warning file={bad},line=4,title=qdbd::QuickDBD would reject this") for line in out)
146
+
147
+
148
+ def test_check_format_github_strict_promotes_warnings(tmp_path, capsys):
149
+ f = tmp_path / "w.txt"
150
+ f.write_text("T\n-\nA decimal(10, 2)\n")
151
+ assert main(["check", "--format", "github", "--strict", str(f)]) == 1
152
+ assert capsys.readouterr().out.startswith(f"::error file={f},line=3,")
153
+
154
+
155
+ def test_github_annotation_escaping():
156
+ from quickdbd_cli.cli import _github_annotation
157
+ from quickdbd_cli.model import Diagnostic
158
+
159
+ d = Diagnostic("a,b:c.txt", 2, "50% bad\nnext", "warning")
160
+ assert _github_annotation(d, "warning") == "::warning file=a%2Cb%3Ac.txt,line=2,title=qdbd::50%25 bad%0Anext"
161
+
162
+
163
+ def test_check_format_json(tmp_path, capsys):
164
+ bad = tmp_path / "bad.txt"
165
+ bad.write_text("T\n-\nA int foo\n")
166
+ assert main(["check", "--format", "json", str(bad)]) == 1
167
+ data = json.loads(capsys.readouterr().out)
168
+ assert data["ok"] is False and data["errors"] == 1 and data["warnings"] == 0
169
+ assert data["diagnostics"] == [
170
+ {"path": str(bad), "line": 3, "severity": "error", "message": "Unrecognized keyword 'foo'"}
171
+ ]
172
+
173
+
174
+ def test_check_format_json_ok(capsys):
175
+ assert main(["check", "--format", "json", str(SAMPLE)]) == 0
176
+ data = json.loads(capsys.readouterr().out)
177
+ assert data == {"ok": True, "errors": 0, "warnings": 0, "diagnostics": []}
178
+
179
+
139
180
  def test_build_is_deterministic_and_does_not_write_sidecar(tmp_path):
140
181
  work = tmp_path / "s"
141
182
  shutil.copytree(SYNTH, work)
@@ -1 +0,0 @@
1
- __version__ = "0.1.12"
File without changes
File without changes