@revos/cli 0.2.0 → 0.2.2
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/LICENSE +9 -0
- package/README.md +286 -41
- package/dist/adapters/oclif/commands/action-runs/get.mjs +1 -1
- package/dist/adapters/oclif/commands/action-runs/list.mjs +8 -2
- package/dist/adapters/oclif/commands/actions/get-input-schema.mjs +2 -2
- package/dist/adapters/oclif/commands/actions/get-params-schema.mjs +2 -2
- package/dist/adapters/oclif/commands/actions/get.mjs +1 -1
- package/dist/adapters/oclif/commands/actions/list.mjs +8 -4
- package/dist/adapters/oclif/commands/ai-instructions/create.mjs +1 -1
- package/dist/adapters/oclif/commands/ai-instructions/delete.mjs +1 -1
- package/dist/adapters/oclif/commands/ai-instructions/get.mjs +1 -1
- package/dist/adapters/oclif/commands/ai-instructions/list.mjs +8 -2
- package/dist/adapters/oclif/commands/ai-instructions/update.mjs +1 -1
- package/dist/adapters/oclif/commands/api.d.mts +11 -0
- package/dist/adapters/oclif/commands/api.mjs +112 -0
- package/dist/adapters/oclif/commands/apply.d.mts +28 -0
- package/dist/adapters/oclif/commands/apply.mjs +77 -0
- package/dist/adapters/oclif/commands/auth/login.d.mts +5 -4
- package/dist/adapters/oclif/commands/auth/login.mjs +22 -11
- package/dist/adapters/oclif/commands/auth/logout.d.mts +1 -1
- package/dist/adapters/oclif/commands/auth/logout.mjs +7 -3
- package/dist/adapters/oclif/commands/auth/status.d.mts +2 -2
- package/dist/adapters/oclif/commands/auth/status.mjs +2 -2
- package/dist/adapters/oclif/commands/connections/create.d.mts +6 -0
- package/dist/adapters/oclif/commands/connections/create.mjs +8 -0
- package/dist/adapters/oclif/commands/connections/delete.d.mts +6 -0
- package/dist/adapters/oclif/commands/connections/delete.mjs +8 -0
- package/dist/adapters/oclif/commands/connections/get.d.mts +6 -0
- package/dist/adapters/oclif/commands/connections/get.mjs +8 -0
- package/dist/adapters/oclif/commands/connections/list.d.mts +6 -0
- package/dist/adapters/oclif/commands/connections/list.mjs +14 -0
- package/dist/adapters/oclif/commands/connections/update.d.mts +6 -0
- package/dist/adapters/oclif/commands/connections/update.mjs +8 -0
- package/dist/adapters/oclif/commands/cubes/create.d.mts +6 -0
- package/dist/adapters/oclif/commands/cubes/create.mjs +8 -0
- package/dist/adapters/oclif/commands/cubes/delete.d.mts +6 -0
- package/dist/adapters/oclif/commands/cubes/delete.mjs +8 -0
- package/dist/adapters/oclif/commands/cubes/get.d.mts +6 -0
- package/dist/adapters/oclif/commands/cubes/get.mjs +8 -0
- package/dist/adapters/oclif/commands/cubes/list.d.mts +6 -0
- package/dist/adapters/oclif/commands/cubes/list.mjs +13 -0
- package/dist/adapters/oclif/commands/cubes/update.d.mts +6 -0
- package/dist/adapters/oclif/commands/cubes/update.mjs +8 -0
- package/dist/adapters/oclif/commands/diff.d.mts +27 -0
- package/dist/adapters/oclif/commands/diff.mjs +66 -0
- package/dist/adapters/oclif/commands/gservice-account-keys/get.mjs +1 -1
- package/dist/adapters/oclif/commands/gservice-account-keys/reveal.mjs +2 -2
- package/dist/adapters/oclif/commands/gservice-accounts/create.mjs +1 -1
- package/dist/adapters/oclif/commands/gservice-accounts/delete.mjs +1 -1
- package/dist/adapters/oclif/commands/gservice-accounts/get.mjs +1 -1
- package/dist/adapters/oclif/commands/gservice-accounts/list.mjs +7 -2
- package/dist/adapters/oclif/commands/init.d.mts +2 -1
- package/dist/adapters/oclif/commands/init.mjs +28 -24
- package/dist/adapters/oclif/commands/org/create.mjs +1 -1
- package/dist/adapters/oclif/commands/org/current.d.mts +2 -2
- package/dist/adapters/oclif/commands/org/current.mjs +2 -2
- package/dist/adapters/oclif/commands/org/get.mjs +1 -1
- package/dist/adapters/oclif/commands/org/list.d.mts +3 -11
- package/dist/adapters/oclif/commands/org/list.mjs +26 -26
- package/dist/adapters/oclif/commands/org/switch.d.mts +3 -2
- package/dist/adapters/oclif/commands/org/switch.mjs +13 -5
- package/dist/adapters/oclif/commands/pull.d.mts +28 -0
- package/dist/adapters/oclif/commands/pull.mjs +88 -0
- package/dist/adapters/oclif/commands/score-groups/create.mjs +3 -2
- package/dist/adapters/oclif/commands/score-groups/delete.mjs +1 -1
- package/dist/adapters/oclif/commands/score-groups/get.mjs +1 -1
- package/dist/adapters/oclif/commands/score-groups/list.mjs +3 -2
- package/dist/adapters/oclif/commands/score-groups/update.mjs +1 -1
- package/dist/adapters/oclif/commands/scores/create.mjs +3 -2
- package/dist/adapters/oclif/commands/scores/delete.mjs +1 -1
- package/dist/adapters/oclif/commands/scores/list.mjs +3 -2
- package/dist/adapters/oclif/commands/scores/update.mjs +1 -1
- package/dist/adapters/oclif/commands/segments/create.mjs +1 -1
- package/dist/adapters/oclif/commands/segments/delete.mjs +1 -1
- package/dist/adapters/oclif/commands/segments/evaluate.mjs +2 -2
- package/dist/adapters/oclif/commands/segments/get-evaluation-history.mjs +2 -2
- package/dist/adapters/oclif/commands/segments/get-version.mjs +2 -2
- package/dist/adapters/oclif/commands/segments/get.mjs +1 -1
- package/dist/adapters/oclif/commands/segments/list-versions.mjs +16 -5
- package/dist/adapters/oclif/commands/segments/list.mjs +9 -2
- package/dist/adapters/oclif/commands/segments/restore-version.mjs +2 -2
- package/dist/adapters/oclif/commands/segments/update.mjs +1 -1
- package/dist/adapters/oclif/commands/sources/create.d.mts +11 -0
- package/dist/adapters/oclif/commands/sources/create.mjs +16 -0
- package/dist/adapters/oclif/commands/sources/delete.d.mts +6 -0
- package/dist/adapters/oclif/commands/sources/delete.mjs +8 -0
- package/dist/adapters/oclif/commands/sources/get.d.mts +6 -0
- package/dist/adapters/oclif/commands/sources/get.mjs +8 -0
- package/dist/adapters/oclif/commands/sources/list-streams.d.mts +6 -0
- package/dist/adapters/oclif/commands/sources/list-streams.mjs +31 -0
- package/dist/adapters/oclif/commands/sources/list.d.mts +6 -0
- package/dist/adapters/oclif/commands/sources/list.mjs +13 -0
- package/dist/adapters/oclif/commands/sources/update.d.mts +15 -0
- package/dist/adapters/oclif/commands/sources/update.mjs +21 -0
- package/dist/adapters/oclif/commands/status.d.mts +26 -0
- package/dist/adapters/oclif/commands/status.mjs +77 -0
- package/dist/adapters/oclif/commands/table-views/create.mjs +3 -2
- package/dist/adapters/oclif/commands/table-views/delete.mjs +1 -1
- package/dist/adapters/oclif/commands/table-views/list.mjs +3 -2
- package/dist/adapters/oclif/commands/table-views/update.mjs +1 -1
- package/dist/adapters/oclif/commands/tables/create.mjs +1 -1
- package/dist/adapters/oclif/commands/tables/delete.mjs +1 -1
- package/dist/adapters/oclif/commands/tables/get.mjs +1 -1
- package/dist/adapters/oclif/commands/tables/list.mjs +3 -2
- package/dist/adapters/oclif/commands/tables/update.mjs +1 -1
- package/dist/{base.command-d7VW6WTp.d.mts → base.command-D7X3ZNtY.d.mts} +0 -1
- package/dist/{base.command-DlVQ9Cqa.mjs → base.command-cV5d65r8.mjs} +15 -12
- package/dist/chunk-CfYAbeIz.mjs +13 -0
- package/dist/core-CMrP5BQS.mjs +2378 -0
- package/dist/{factory-D9sR_S_g.mjs → factory-C6XLqhT9.mjs} +44 -10
- package/dist/iac-render-BSZZEP0n.mjs +17 -0
- package/dist/index-BqKwXXAo.d.mts +598 -0
- package/dist/index.d.mts +3 -4
- package/dist/index.mjs +2 -2
- package/dist/{presets-Cvazkjmu.mjs → presets-CJbFbHlw.mjs} +35 -8
- package/dist/templates/.claude/settings.json +39 -0
- package/dist/templates/.devcontainer/devcontainer.json +2 -2
- package/dist/templates/.devcontainer/setup.sh +3 -0
- package/dist/templates/AGENTS.md +36 -13
- package/dist/templates/dbt/dbt_project.yml +2 -2
- package/dist/templates/skills/create-connections/SKILL.md +210 -0
- package/dist/templates/skills/create-connections/references/mappers.md +152 -0
- package/dist/templates/skills/{create-semantic-model → create-cubes}/SKILL.md +28 -26
- package/dist/templates/skills/create-cubes/references/bq-pk-fk-conventions.md +183 -0
- package/dist/templates/skills/{create-semantic-model → create-cubes}/references/cube-examples.md +85 -7
- package/dist/templates/skills/create-cubes/references/hubspot-entities.md +289 -0
- package/dist/templates/skills/create-cubes/references/jira-entities.md +201 -0
- package/dist/templates/skills/create-cubes/references/netsuite-entities.md +121 -0
- package/dist/templates/skills/create-cubes/references/stripe-entities.md +114 -0
- package/dist/templates/skills/create-dbt-transformations/SKILL.md +62 -33
- package/dist/templates/skills/create-dbt-transformations/references/edge-cases.md +21 -3
- package/dist/templates/skills/create-dbt-transformations/references/schema-conventions.md +21 -7
- package/dist/templates/skills/create-dbt-transformations/references/sql-templates.md +34 -20
- package/dist/templates/skills/explore-lakehouse/SKILL.md +8 -4
- package/dist/templates/skills/load-sample-data/SKILL.md +119 -0
- package/dist/templates/skills/visualize-semantic-model/SKILL.md +159 -0
- package/dist/templates/skills/visualize-semantic-model/scripts/render_graph.py +186 -0
- package/dist/{types-Y_ht_ja5.d.mts → types-CGjxcj4L.d.mts} +3 -0
- package/package.json +48 -6
- package/dist/adapters/oclif/commands/overlays/diff.d.mts +0 -19
- package/dist/adapters/oclif/commands/overlays/diff.mjs +0 -80
- package/dist/adapters/oclif/commands/overlays/pull.d.mts +0 -15
- package/dist/adapters/oclif/commands/overlays/pull.mjs +0 -44
- package/dist/adapters/oclif/commands/overlays/push.d.mts +0 -18
- package/dist/adapters/oclif/commands/overlays/push.mjs +0 -59
- package/dist/adapters/oclif/commands/overlays/status.d.mts +0 -18
- package/dist/adapters/oclif/commands/overlays/status.mjs +0 -53
- package/dist/core-gKJ_V-K5.mjs +0 -973
- package/dist/index-KAzwt5vr.d.mts +0 -190
- package/dist/types-C_p_6rkj.d.mts +0 -69
- /package/dist/templates/skills/{create-semantic-model → create-cubes}/references/key-patterns.md +0 -0
- /package/dist/templates/skills/{create-semantic-model → create-cubes}/references/validation-queries.md +0 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: visualize-semantic-model
|
|
3
|
+
description: >
|
|
4
|
+
Generate a model-graph.png visualization of Cube.dev semantic models — render the
|
|
5
|
+
cube graph, show relationships between cubes, draw the fact spine, or diagram the
|
|
6
|
+
semantic layer. Use whenever the user mentions visualizing, drawing, diagramming, or
|
|
7
|
+
graphing the semantic model / cube model / cube relationships, even if they don't
|
|
8
|
+
explicitly ask for a PNG. Triggers: "visualize the semantic model", "draw the cube
|
|
9
|
+
graph", "show relationships between cubes", "generate model-graph.png", "diagram the
|
|
10
|
+
semantic layer", "render the cube model". Accepts an optional folder argument
|
|
11
|
+
(defaults to `cubes/`).
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Visualize Semantic Model
|
|
15
|
+
|
|
16
|
+
Render a dark-themed directed graph of a Cube.dev model by parsing cube YAML files,
|
|
17
|
+
detecting the fact spine, then invoking the bundled renderer with a JSON spec.
|
|
18
|
+
|
|
19
|
+
The renderer (`scripts/render_graph.py`) is pure layout + drawing — it takes a graph
|
|
20
|
+
spec on stdin and writes the PNG. Keeping it bundled means every run produces visually
|
|
21
|
+
consistent output and you don't reinvent matplotlib code each time.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Step 1: Resolve the cubes folder
|
|
26
|
+
|
|
27
|
+
If the user passed a folder argument, use it. Otherwise default to `cubes/`. If the
|
|
28
|
+
folder doesn't exist, ask the user where the cube definitions live before proceeding.
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
find <folder> -name "*.yml" -not -name "model-graph.*" | sort
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Step 2: Parse the cube graph
|
|
37
|
+
|
|
38
|
+
For each `.yml` file extract:
|
|
39
|
+
|
|
40
|
+
- `name` — cube name
|
|
41
|
+
- `joins` — map of `target_cube → { relationship, sql }`
|
|
42
|
+
- A short join-key label parsed from each join's `sql`
|
|
43
|
+
|
|
44
|
+
**Join-key label rules.** Strip `${CUBE}.` and `${<target>}.` prefixes. For a single
|
|
45
|
+
equality, use the LHS column. For composite keys (multiple `AND`), join the LHS column
|
|
46
|
+
names with `+`.
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
sql: "${CUBE}.user_id = ${users}.user_id"
|
|
50
|
+
# → "user_id"
|
|
51
|
+
|
|
52
|
+
sql: "${CUBE}.traffic_source = ${rev}.traffic_source AND ${CUBE}.order_date = ${rev}.order_date"
|
|
53
|
+
# → "traffic_source + order_date"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Cardinality** (always render edges from the _one_ side to the _many_ side):
|
|
57
|
+
|
|
58
|
+
| Declared on this cube | This cube's side | Other cube's side |
|
|
59
|
+
| --------------------- | ---------------- | ----------------- |
|
|
60
|
+
| `many_to_one` | ∞ | 1 |
|
|
61
|
+
| `one_to_many` | 1 | ∞ |
|
|
62
|
+
| `one_to_one` | 1 | 1 |
|
|
63
|
+
|
|
64
|
+
**Fact-spine detection.** The cube with the most `many_to_one` joins (it holds the FKs)
|
|
65
|
+
is the spine. Tie-break by preferring names containing `enriched`, `fact`, or `items`.
|
|
66
|
+
If no cube has any `many_to_one` joins (e.g. the model is all `one_to_many` from a hub),
|
|
67
|
+
fall back to the cube with the most outgoing joins of any kind. If still ambiguous, ask
|
|
68
|
+
the user which cube to treat as the spine.
|
|
69
|
+
|
|
70
|
+
**Edge cases — stop and tell the user instead of rendering:**
|
|
71
|
+
|
|
72
|
+
- The folder contains fewer than 2 cubes → at least two cubes are needed.
|
|
73
|
+
- No `joins` found anywhere → there are no relationships to draw.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Step 3: Build the graph spec
|
|
78
|
+
|
|
79
|
+
Build a JSON object the renderer understands:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"title": "Semantic Model — <project_name>",
|
|
84
|
+
"fact_spine": {
|
|
85
|
+
"name": "<fact_cube>",
|
|
86
|
+
"pk": "<pk_col>",
|
|
87
|
+
"fks": ["fk_a", "fk_b", "fk_c", "fk_d"]
|
|
88
|
+
},
|
|
89
|
+
"dimensions": [
|
|
90
|
+
{ "name": "<dim_cube>", "pk": "<pk_col>", "extras": ["metric1", "metric2"] }
|
|
91
|
+
],
|
|
92
|
+
"edges": [
|
|
93
|
+
{
|
|
94
|
+
"from": "<dim_cube>",
|
|
95
|
+
"to": "<fact_cube>",
|
|
96
|
+
"label": "<join_key>",
|
|
97
|
+
"from_card": "1",
|
|
98
|
+
"to_card": "∞"
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`from_card` / `to_card` are the labels rendered at each end of the arrow — `"1"` or
|
|
105
|
+
`"∞"`. For `one_to_one` joins both ends are `"1"`.
|
|
106
|
+
|
|
107
|
+
The arrow always points _from_ dimension _to_ fact, regardless of how the relationship
|
|
108
|
+
was declared on the YAML.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Step 4: Render
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
python3 -c "import matplotlib" 2>/dev/null || python3 -m pip install matplotlib --quiet
|
|
116
|
+
|
|
117
|
+
python3 .claude/skills/visualize-semantic-model/scripts/render_graph.py \
|
|
118
|
+
--output <folder>/model-graph.png \
|
|
119
|
+
<<'EOF'
|
|
120
|
+
{ ... the JSON spec from Step 3 ... }
|
|
121
|
+
EOF
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
If `<folder>/model-graph.png` already exists and the user did not explicitly ask to
|
|
125
|
+
regenerate, ask before overwriting.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Step 5: Show the result
|
|
130
|
+
|
|
131
|
+
Use the Read tool (not a Python `Read()` call — Read is a Claude Code tool) on the PNG
|
|
132
|
+
path so the image renders inline in chat:
|
|
133
|
+
|
|
134
|
+
> Read `<folder>/model-graph.png`
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Final response template
|
|
139
|
+
|
|
140
|
+
```text
|
|
141
|
+
Generated: <folder>/model-graph.png
|
|
142
|
+
|
|
143
|
+
Cubes visualized: <n>
|
|
144
|
+
Fact spine: <cube_name>
|
|
145
|
+
Dimensions: <dim1>, <dim2>, ...
|
|
146
|
+
Edges: <dim1> → <fact> (<join_key>) [1:∞]
|
|
147
|
+
<dim2> → <fact> (<join_key>) [1:∞]
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Rules
|
|
153
|
+
|
|
154
|
+
- Always parse YAML — never hardcode cube names or relationships.
|
|
155
|
+
- Edge direction is always **dimension → fact** (arrow tail at dimension, head at fact).
|
|
156
|
+
- The `1` and `∞` markers are positioned by the renderer based on `from_card`/`to_card`;
|
|
157
|
+
set them correctly per the cardinality table above.
|
|
158
|
+
- Do not overwrite an existing `model-graph.png` without confirming.
|
|
159
|
+
- After saving, always show the image inline using the Read tool.
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Render a Cube.dev semantic model graph to a PNG.
|
|
3
|
+
|
|
4
|
+
Reads a JSON spec from stdin (see SKILL.md for the schema) and writes a dark-themed
|
|
5
|
+
directed-graph PNG to --output. Layout is automatic: the fact spine sits centered;
|
|
6
|
+
dimensions are evenly distributed in a ring around it.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import json
|
|
11
|
+
import math
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
import matplotlib
|
|
15
|
+
|
|
16
|
+
matplotlib.use("Agg")
|
|
17
|
+
import matplotlib.pyplot as plt
|
|
18
|
+
from matplotlib.patches import FancyBboxPatch
|
|
19
|
+
|
|
20
|
+
INDIGO = "#6366f1"
|
|
21
|
+
SKY = "#0ea5e9"
|
|
22
|
+
BG = "#0f1117"
|
|
23
|
+
NODE_FILL = "#1e293b"
|
|
24
|
+
TITLE_FG = "#f1f5f9"
|
|
25
|
+
SUB_FG = "#94a3b8"
|
|
26
|
+
LEGEND_FG = "#64748b"
|
|
27
|
+
|
|
28
|
+
W, H = 13.0, 7.0
|
|
29
|
+
SPINE_CENTER = (W / 2, H / 2)
|
|
30
|
+
DIM_RADIUS_X = 4.6
|
|
31
|
+
DIM_RADIUS_Y = 2.2
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def draw_node(ax, cx, cy, w, h, title, subtitle_lines, tag, border, tag_color):
|
|
35
|
+
box = FancyBboxPatch(
|
|
36
|
+
(cx - w / 2, cy - h / 2), w, h,
|
|
37
|
+
boxstyle="round,pad=0.05", linewidth=2,
|
|
38
|
+
edgecolor=border, facecolor=NODE_FILL, zorder=3,
|
|
39
|
+
)
|
|
40
|
+
ax.add_patch(box)
|
|
41
|
+
ax.text(cx, cy + h / 2 - 0.28, tag, ha="center", va="top",
|
|
42
|
+
fontsize=7.5, fontweight="bold", color=tag_color,
|
|
43
|
+
fontfamily="monospace", zorder=4)
|
|
44
|
+
ax.text(cx, cy + 0.12, title, ha="center", va="center",
|
|
45
|
+
fontsize=9.5, fontweight="bold", color=TITLE_FG, zorder=4)
|
|
46
|
+
for i, line in enumerate(subtitle_lines):
|
|
47
|
+
ax.text(cx, cy - 0.32 - i * 0.32, line, ha="center", va="center",
|
|
48
|
+
fontsize=8, color=SUB_FG, zorder=4)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def draw_edge(ax, x1, y1, x2, y2, label, from_card, to_card, color):
|
|
52
|
+
ax.annotate(
|
|
53
|
+
"", xy=(x2, y2), xytext=(x1, y1),
|
|
54
|
+
arrowprops=dict(arrowstyle="->", color=color, lw=1.8,
|
|
55
|
+
connectionstyle="arc3,rad=0.0"),
|
|
56
|
+
zorder=2,
|
|
57
|
+
)
|
|
58
|
+
mx, my = (x1 + x2) / 2, (y1 + y2) / 2
|
|
59
|
+
dx, dy = x2 - x1, y2 - y1
|
|
60
|
+
length = math.hypot(-dy, dx) or 1.0
|
|
61
|
+
ox, oy = -dy / length * 0.22, dx / length * 0.22
|
|
62
|
+
ax.text(mx + ox, my + oy, label, ha="center", va="center",
|
|
63
|
+
fontsize=7.5, color=LEGEND_FG, zorder=4)
|
|
64
|
+
# cardinality markers near each endpoint
|
|
65
|
+
ax.text(x1 + dx * 0.08, y1 + dy * 0.08 + 0.18, from_card,
|
|
66
|
+
ha="center", va="center", fontsize=11, fontweight="bold",
|
|
67
|
+
color=SUB_FG, zorder=5)
|
|
68
|
+
ax.text(x2 - dx * 0.08, y2 - dy * 0.08 + 0.18, to_card,
|
|
69
|
+
ha="center", va="center", fontsize=11, fontweight="bold",
|
|
70
|
+
color=SUB_FG, zorder=5)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def fact_subtitle_lines(spine):
|
|
74
|
+
lines = [f"PK: {spine['pk']}"] if spine.get("pk") else []
|
|
75
|
+
fks = spine.get("fks") or []
|
|
76
|
+
# pack FKs two per line
|
|
77
|
+
for i in range(0, len(fks), 2):
|
|
78
|
+
chunk = fks[i:i + 2]
|
|
79
|
+
lines.append("FK: " + " · ".join(chunk))
|
|
80
|
+
return lines
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def dim_subtitle_lines(dim):
|
|
84
|
+
lines = []
|
|
85
|
+
if dim.get("pk"):
|
|
86
|
+
lines.append(f"PK: {dim['pk']}")
|
|
87
|
+
if dim.get("extras"):
|
|
88
|
+
lines.append(" · ".join(dim["extras"]))
|
|
89
|
+
return lines
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def dim_position(i, n):
|
|
93
|
+
# Distribute dims evenly around an ellipse centered on the spine.
|
|
94
|
+
# Start from the left (angle = π) and go clockwise so a single dim sits left.
|
|
95
|
+
angle = math.pi - (2 * math.pi * i / n)
|
|
96
|
+
cx = SPINE_CENTER[0] + DIM_RADIUS_X * math.cos(angle)
|
|
97
|
+
cy = SPINE_CENTER[1] + DIM_RADIUS_Y * math.sin(angle)
|
|
98
|
+
return cx, cy
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def edge_endpoints(src, dst, src_w=3.0, src_h=1.6, dst_w=3.2, dst_h=2.0):
|
|
102
|
+
# Stop the arrow at the bounding boxes so it doesn't overlap node text.
|
|
103
|
+
sx, sy = src
|
|
104
|
+
dx, dy = dst
|
|
105
|
+
vx, vy = dx - sx, dy - sy
|
|
106
|
+
length = math.hypot(vx, vy) or 1.0
|
|
107
|
+
ux, uy = vx / length, vy / length
|
|
108
|
+
# rough rectangular inset — works fine for the box sizes we use
|
|
109
|
+
src_inset = max(src_w / 2 * abs(ux), src_h / 2 * abs(uy))
|
|
110
|
+
dst_inset = max(dst_w / 2 * abs(ux), dst_h / 2 * abs(uy))
|
|
111
|
+
return (sx + ux * src_inset, sy + uy * src_inset,
|
|
112
|
+
dx - ux * dst_inset, dy - uy * dst_inset)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def render(spec, output_path):
|
|
116
|
+
fig, ax = plt.subplots(figsize=(W, H))
|
|
117
|
+
fig.patch.set_facecolor(BG)
|
|
118
|
+
ax.set_facecolor(BG)
|
|
119
|
+
ax.set_xlim(0, W)
|
|
120
|
+
ax.set_ylim(0, H)
|
|
121
|
+
ax.axis("off")
|
|
122
|
+
|
|
123
|
+
spine = spec["fact_spine"]
|
|
124
|
+
dims = spec.get("dimensions", [])
|
|
125
|
+
edges = spec.get("edges", [])
|
|
126
|
+
|
|
127
|
+
# nodes
|
|
128
|
+
draw_node(ax, *SPINE_CENTER, 3.2, 2.0,
|
|
129
|
+
spine["name"], fact_subtitle_lines(spine),
|
|
130
|
+
"FACT SPINE", INDIGO, "#818cf8")
|
|
131
|
+
|
|
132
|
+
positions = {spine["name"]: SPINE_CENTER}
|
|
133
|
+
n = max(len(dims), 1)
|
|
134
|
+
for i, dim in enumerate(dims):
|
|
135
|
+
cx, cy = dim_position(i, n)
|
|
136
|
+
positions[dim["name"]] = (cx, cy)
|
|
137
|
+
draw_node(ax, cx, cy, 3.0, 1.6,
|
|
138
|
+
dim["name"], dim_subtitle_lines(dim),
|
|
139
|
+
"DIMENSION", SKY, "#38bdf8")
|
|
140
|
+
|
|
141
|
+
# edges
|
|
142
|
+
for e in edges:
|
|
143
|
+
src = positions.get(e["from"])
|
|
144
|
+
dst = positions.get(e["to"])
|
|
145
|
+
if not src or not dst:
|
|
146
|
+
continue
|
|
147
|
+
x1, y1, x2, y2 = edge_endpoints(src, dst)
|
|
148
|
+
draw_edge(ax, x1, y1, x2, y2,
|
|
149
|
+
e.get("label", ""),
|
|
150
|
+
e.get("from_card", "1"),
|
|
151
|
+
e.get("to_card", "∞"),
|
|
152
|
+
SKY)
|
|
153
|
+
|
|
154
|
+
# title
|
|
155
|
+
ax.text(W / 2, H - 0.3, spec.get("title", "Semantic Model"),
|
|
156
|
+
ha="center", va="center", fontsize=12,
|
|
157
|
+
fontweight="bold", color="#e2e8f0")
|
|
158
|
+
|
|
159
|
+
# legend
|
|
160
|
+
for i, (col, lbl) in enumerate([(INDIGO, "Fact spine"),
|
|
161
|
+
(SKY, "Dimension / edge")]):
|
|
162
|
+
lx = 4.0 + i * 2.8
|
|
163
|
+
ax.plot([lx, lx + 0.5], [0.35, 0.35], color=col, lw=2.5)
|
|
164
|
+
ax.text(lx + 0.65, 0.35, lbl, va="center", fontsize=8, color=LEGEND_FG)
|
|
165
|
+
ax.text(10.5, 0.35, "1 = one side ∞ = many side",
|
|
166
|
+
va="center", fontsize=8, color=LEGEND_FG)
|
|
167
|
+
|
|
168
|
+
plt.tight_layout(pad=0.3)
|
|
169
|
+
plt.savefig(output_path, dpi=160, bbox_inches="tight", facecolor=BG)
|
|
170
|
+
print(f"Saved: {output_path}")
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def main():
|
|
174
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
175
|
+
parser.add_argument("--output", required=True, help="Path to write the PNG.")
|
|
176
|
+
parser.add_argument("--input", default="-",
|
|
177
|
+
help="JSON spec path, or '-' for stdin (default).")
|
|
178
|
+
args = parser.parse_args()
|
|
179
|
+
|
|
180
|
+
raw = sys.stdin.read() if args.input == "-" else open(args.input).read()
|
|
181
|
+
spec = json.loads(raw)
|
|
182
|
+
render(spec, args.output)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
if __name__ == "__main__":
|
|
186
|
+
main()
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revos/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "RevOS CLI for managing RevOS platform resources",
|
|
5
|
+
"license": "MIT",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"main": "dist/index.js",
|
|
7
8
|
"types": "dist/index.d.ts",
|
|
@@ -29,25 +30,66 @@
|
|
|
29
30
|
"org": {
|
|
30
31
|
"description": "Organization management commands"
|
|
31
32
|
},
|
|
32
|
-
"
|
|
33
|
-
"description": "Manage
|
|
33
|
+
"sources": {
|
|
34
|
+
"description": "Manage data sources (list/get/delete via API; create/update open RevOS UI)"
|
|
35
|
+
},
|
|
36
|
+
"tables": {
|
|
37
|
+
"description": "Manage tables (scoring models)"
|
|
38
|
+
},
|
|
39
|
+
"table-views": {
|
|
40
|
+
"description": "Manage table views"
|
|
41
|
+
},
|
|
42
|
+
"cubes": {
|
|
43
|
+
"description": "Manage Cube.dev semantic models"
|
|
44
|
+
},
|
|
45
|
+
"connections": {
|
|
46
|
+
"description": "Manage Connections (syncs from Sources to BigQuery)"
|
|
47
|
+
},
|
|
48
|
+
"scores": {
|
|
49
|
+
"description": "Manage scores attached to a scoring model"
|
|
50
|
+
},
|
|
51
|
+
"score-groups": {
|
|
52
|
+
"description": "Manage score groups"
|
|
53
|
+
},
|
|
54
|
+
"segments": {
|
|
55
|
+
"description": "Manage segments and their evaluation history"
|
|
56
|
+
},
|
|
57
|
+
"actions": {
|
|
58
|
+
"description": "Inspect actions and their input/params schemas"
|
|
59
|
+
},
|
|
60
|
+
"action-runs": {
|
|
61
|
+
"description": "Inspect action run history"
|
|
62
|
+
},
|
|
63
|
+
"ai-instructions": {
|
|
64
|
+
"description": "Manage AI instructions"
|
|
65
|
+
},
|
|
66
|
+
"gservice-accounts": {
|
|
67
|
+
"description": "Manage Google service accounts"
|
|
68
|
+
},
|
|
69
|
+
"gservice-account-keys": {
|
|
70
|
+
"description": "Manage Google service account keys"
|
|
34
71
|
}
|
|
35
72
|
}
|
|
36
73
|
},
|
|
37
74
|
"files": [
|
|
38
75
|
"dist",
|
|
39
|
-
"bin"
|
|
76
|
+
"bin",
|
|
77
|
+
"LICENSE"
|
|
40
78
|
],
|
|
41
79
|
"dependencies": {
|
|
80
|
+
"@dagrejs/graphlib": "^2.2.4",
|
|
42
81
|
"@inquirer/search": "^4.1.7",
|
|
43
82
|
"@inquirer/select": "^4.4.2",
|
|
44
83
|
"@oclif/core": "^4.2.10",
|
|
45
84
|
"@oclif/plugin-warn-if-update-available": "^3.1.61",
|
|
46
85
|
"@oclif/table": "^0.5.4",
|
|
47
86
|
"chalk": "^4.1.2",
|
|
87
|
+
"microdiff": "^1.5.0",
|
|
48
88
|
"open": "^10.1.0",
|
|
49
89
|
"yaml": "^2.8.3",
|
|
50
|
-
"
|
|
90
|
+
"zod": "^4.3.5",
|
|
91
|
+
"zod-validation-error": "^4.0.1",
|
|
92
|
+
"@revos/api-client": "0.1.1"
|
|
51
93
|
},
|
|
52
94
|
"devDependencies": {
|
|
53
95
|
"@swc/core": "^1.7.26",
|
|
@@ -56,7 +98,7 @@
|
|
|
56
98
|
"@types/node": "^22.5.4",
|
|
57
99
|
"jest": "^29.7.0",
|
|
58
100
|
"tsdown": "^0.21.9",
|
|
59
|
-
"typescript": "^5.
|
|
101
|
+
"typescript": "^5.9.0",
|
|
60
102
|
"@revos/config-eslint": "0.0.0"
|
|
61
103
|
},
|
|
62
104
|
"publishConfig": {
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { o as DiffResult } from "../../../../types-C_p_6rkj.mjs";
|
|
2
|
-
import { t as BaseCommand } from "../../../../base.command-d7VW6WTp.mjs";
|
|
3
|
-
import * as _$_oclif_core_interfaces0 from "@oclif/core/interfaces";
|
|
4
|
-
|
|
5
|
-
//#region src/adapters/oclif/commands/overlays/diff.d.ts
|
|
6
|
-
declare class OverlaysDiff extends BaseCommand<typeof OverlaysDiff> {
|
|
7
|
-
static description: string;
|
|
8
|
-
static flags: {
|
|
9
|
-
dir: _$_oclif_core_interfaces0.OptionFlag<string, _$_oclif_core_interfaces0.CustomOptions>;
|
|
10
|
-
};
|
|
11
|
-
static args: {
|
|
12
|
-
files: _$_oclif_core_interfaces0.Arg<string[] | undefined, Record<string, unknown>>;
|
|
13
|
-
};
|
|
14
|
-
run(): Promise<DiffResult>;
|
|
15
|
-
private renderEntry;
|
|
16
|
-
private renderDataDiff;
|
|
17
|
-
}
|
|
18
|
-
//#endregion
|
|
19
|
-
export { OverlaysDiff as default };
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { p as createApiClient, r as DiffService, y as getConfig } from "../../../../core-gKJ_V-K5.mjs";
|
|
2
|
-
import { t as BaseCommand } from "../../../../base.command-DlVQ9Cqa.mjs";
|
|
3
|
-
import chalk from "chalk";
|
|
4
|
-
import { Args, Flags } from "@oclif/core";
|
|
5
|
-
//#region src/adapters/oclif/commands/overlays/diff.ts
|
|
6
|
-
var OverlaysDiff = class extends BaseCommand {
|
|
7
|
-
static description = "Show differences between local files and remote overlays";
|
|
8
|
-
static flags = { dir: Flags.string({
|
|
9
|
-
char: "d",
|
|
10
|
-
description: "Directory containing overlay files",
|
|
11
|
-
default: "./semantic"
|
|
12
|
-
}) };
|
|
13
|
-
static args = { files: Args.string({
|
|
14
|
-
description: "Specific files to diff (optional)",
|
|
15
|
-
required: false,
|
|
16
|
-
multiple: true
|
|
17
|
-
}) };
|
|
18
|
-
async run() {
|
|
19
|
-
const { flags, args } = this;
|
|
20
|
-
const files = args.files ?? [];
|
|
21
|
-
const result = await new DiffService({ api: createApiClient(await getConfig()) }).execute({
|
|
22
|
-
dir: flags.dir,
|
|
23
|
-
files: files.length > 0 ? files : void 0
|
|
24
|
-
});
|
|
25
|
-
if (!this.jsonEnabled()) {
|
|
26
|
-
if (result.entries.length === 0) {
|
|
27
|
-
this.warn("No overlay files found.");
|
|
28
|
-
return result;
|
|
29
|
-
}
|
|
30
|
-
for (const entry of result.entries) this.renderEntry(entry);
|
|
31
|
-
this.log("");
|
|
32
|
-
}
|
|
33
|
-
return result;
|
|
34
|
-
}
|
|
35
|
-
renderEntry(entry) {
|
|
36
|
-
this.log(chalk.bold(`\n${entry.name}:`));
|
|
37
|
-
if (entry.isRemoteOnly) {
|
|
38
|
-
this.log(chalk.red(" - Exists only on remote (not in local files)"));
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
if (entry.isSynced) {
|
|
42
|
-
this.log(chalk.gray(" No changes"));
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
if (entry.isNew) {
|
|
46
|
-
this.log(chalk.green("+ New overlay (will be created)"));
|
|
47
|
-
for (const change of entry.changes) if (change.field === "data") {
|
|
48
|
-
this.log(chalk.green(` ${change.field}:`));
|
|
49
|
-
for (const line of (change.newValue ?? "").split("\n")) this.log(chalk.green(` ${line}`));
|
|
50
|
-
} else this.log(chalk.green(` ${change.field}: ${change.newValue}`));
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
for (const change of entry.changes) if (change.field === "data") {
|
|
54
|
-
this.log(chalk.yellow(" ~ data: (modified)"));
|
|
55
|
-
this.renderDataDiff(change.oldValue ?? "", change.newValue ?? "");
|
|
56
|
-
} else {
|
|
57
|
-
this.log(chalk.red(` - ${change.field}: ${change.oldValue}`));
|
|
58
|
-
this.log(chalk.green(` + ${change.field}: ${change.newValue}`));
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
renderDataDiff(oldValue, newValue) {
|
|
62
|
-
const oldLines = oldValue.split("\n");
|
|
63
|
-
const newLines = newValue.split("\n");
|
|
64
|
-
const maxLines = Math.max(oldLines.length, newLines.length);
|
|
65
|
-
const maxDiffLines = 20;
|
|
66
|
-
let diffCount = 0;
|
|
67
|
-
for (let i = 0; i < maxLines && diffCount < maxDiffLines; i++) {
|
|
68
|
-
const oldLine = oldLines[i] ?? "";
|
|
69
|
-
const newLine = newLines[i] ?? "";
|
|
70
|
-
if (oldLine !== newLine) {
|
|
71
|
-
if (oldLine) this.log(chalk.red(` - ${oldLine}`));
|
|
72
|
-
if (newLine) this.log(chalk.green(` + ${newLine}`));
|
|
73
|
-
diffCount++;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
if (diffCount >= maxDiffLines) this.log(chalk.gray(` ... (${maxLines - maxDiffLines} more lines)`));
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
//#endregion
|
|
80
|
-
export { OverlaysDiff as default };
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { u as PullResult } from "../../../../types-C_p_6rkj.mjs";
|
|
2
|
-
import { t as BaseCommand } from "../../../../base.command-d7VW6WTp.mjs";
|
|
3
|
-
import * as _$_oclif_core_interfaces0 from "@oclif/core/interfaces";
|
|
4
|
-
|
|
5
|
-
//#region src/adapters/oclif/commands/overlays/pull.d.ts
|
|
6
|
-
declare class OverlaysPull extends BaseCommand<typeof OverlaysPull> {
|
|
7
|
-
static description: string;
|
|
8
|
-
static flags: {
|
|
9
|
-
dir: _$_oclif_core_interfaces0.OptionFlag<string, _$_oclif_core_interfaces0.CustomOptions>;
|
|
10
|
-
name: _$_oclif_core_interfaces0.OptionFlag<string[] | undefined, _$_oclif_core_interfaces0.CustomOptions>;
|
|
11
|
-
};
|
|
12
|
-
run(): Promise<PullResult>;
|
|
13
|
-
}
|
|
14
|
-
//#endregion
|
|
15
|
-
export { OverlaysPull as default };
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { a as PullService, p as createApiClient, y as getConfig } from "../../../../core-gKJ_V-K5.mjs";
|
|
2
|
-
import { t as BaseCommand } from "../../../../base.command-DlVQ9Cqa.mjs";
|
|
3
|
-
import chalk from "chalk";
|
|
4
|
-
import { Flags } from "@oclif/core";
|
|
5
|
-
//#region src/adapters/oclif/commands/overlays/pull.ts
|
|
6
|
-
var OverlaysPull = class extends BaseCommand {
|
|
7
|
-
static description = "Pull overlays from RevOS API to local files";
|
|
8
|
-
static flags = {
|
|
9
|
-
dir: Flags.string({
|
|
10
|
-
char: "d",
|
|
11
|
-
description: "Directory to save overlay files",
|
|
12
|
-
default: "./semantic"
|
|
13
|
-
}),
|
|
14
|
-
name: Flags.string({
|
|
15
|
-
char: "n",
|
|
16
|
-
description: "Specific overlay names to pull",
|
|
17
|
-
multiple: true
|
|
18
|
-
})
|
|
19
|
-
};
|
|
20
|
-
async run() {
|
|
21
|
-
const { flags } = this;
|
|
22
|
-
const result = await new PullService({ api: createApiClient(await getConfig()) }).execute({
|
|
23
|
-
dir: flags.dir,
|
|
24
|
-
names: flags.name
|
|
25
|
-
});
|
|
26
|
-
if (!this.jsonEnabled()) {
|
|
27
|
-
if (result.pulled.length === 0 && result.notFound.length === 0) {
|
|
28
|
-
this.warn("No overlays to pull.");
|
|
29
|
-
return result;
|
|
30
|
-
}
|
|
31
|
-
if (result.notFound.length > 0) this.warn(`The following overlays were not found:\n${result.notFound.map((n) => ` - ${n}`).join("\n")}`);
|
|
32
|
-
for (const item of result.pulled) this.log(chalk.green(`↓ Pulled: ${item.name} → ${item.filePath}`));
|
|
33
|
-
for (const e of result.errors) this.log(chalk.red(`✗ Error saving ${e.name}: ${e.error}`));
|
|
34
|
-
this.log("");
|
|
35
|
-
this.log(chalk.bold("Summary:"));
|
|
36
|
-
this.log(chalk.green(` Pulled: ${result.pulled.length}`));
|
|
37
|
-
if (result.errors.length > 0) this.log(chalk.red(` Errors: ${result.errors.length}`));
|
|
38
|
-
}
|
|
39
|
-
if (result.errors.length > 0) this.exit(1);
|
|
40
|
-
return result;
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
//#endregion
|
|
44
|
-
export { OverlaysPull as default };
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { d as PushResult } from "../../../../types-C_p_6rkj.mjs";
|
|
2
|
-
import { t as BaseCommand } from "../../../../base.command-d7VW6WTp.mjs";
|
|
3
|
-
import * as _$_oclif_core_interfaces0 from "@oclif/core/interfaces";
|
|
4
|
-
|
|
5
|
-
//#region src/adapters/oclif/commands/overlays/push.d.ts
|
|
6
|
-
declare class OverlaysPush extends BaseCommand<typeof OverlaysPush> {
|
|
7
|
-
static description: string;
|
|
8
|
-
static flags: {
|
|
9
|
-
dir: _$_oclif_core_interfaces0.OptionFlag<string, _$_oclif_core_interfaces0.CustomOptions>;
|
|
10
|
-
force: _$_oclif_core_interfaces0.BooleanFlag<boolean>;
|
|
11
|
-
};
|
|
12
|
-
static args: {
|
|
13
|
-
files: _$_oclif_core_interfaces0.Arg<string[] | undefined, Record<string, unknown>>;
|
|
14
|
-
};
|
|
15
|
-
run(): Promise<PushResult>;
|
|
16
|
-
}
|
|
17
|
-
//#endregion
|
|
18
|
-
export { OverlaysPush as default };
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { o as PushService, p as createApiClient, y as getConfig } from "../../../../core-gKJ_V-K5.mjs";
|
|
2
|
-
import { t as BaseCommand } from "../../../../base.command-DlVQ9Cqa.mjs";
|
|
3
|
-
import chalk from "chalk";
|
|
4
|
-
import { Args, Flags } from "@oclif/core";
|
|
5
|
-
//#region src/adapters/oclif/commands/overlays/push.ts
|
|
6
|
-
var OverlaysPush = class extends BaseCommand {
|
|
7
|
-
static description = "Push overlay files to RevOS API";
|
|
8
|
-
static flags = {
|
|
9
|
-
dir: Flags.string({
|
|
10
|
-
char: "d",
|
|
11
|
-
description: "Directory containing overlay files",
|
|
12
|
-
default: "./semantic"
|
|
13
|
-
}),
|
|
14
|
-
force: Flags.boolean({
|
|
15
|
-
char: "f",
|
|
16
|
-
description: "Force push even if remote is newer",
|
|
17
|
-
default: false
|
|
18
|
-
})
|
|
19
|
-
};
|
|
20
|
-
static args = { files: Args.string({
|
|
21
|
-
description: "Specific files to push (optional)",
|
|
22
|
-
required: false,
|
|
23
|
-
multiple: true
|
|
24
|
-
}) };
|
|
25
|
-
async run() {
|
|
26
|
-
const { flags, args } = this;
|
|
27
|
-
const files = args.files ?? [];
|
|
28
|
-
const result = await new PushService({ api: createApiClient(await getConfig()) }).execute({
|
|
29
|
-
dir: flags.dir,
|
|
30
|
-
force: flags.force,
|
|
31
|
-
files: files.length > 0 ? files : void 0
|
|
32
|
-
});
|
|
33
|
-
if (!this.jsonEnabled()) {
|
|
34
|
-
if (result.created.length === 0 && result.updated.length === 0 && result.conflicts.length === 0 && result.errors.length === 0) {
|
|
35
|
-
this.warn("No overlay files found.");
|
|
36
|
-
return result;
|
|
37
|
-
}
|
|
38
|
-
const remoteOnlyConflicts = result.conflicts.filter((c) => c.reason === "exists remotely but not locally");
|
|
39
|
-
if (remoteOnlyConflicts.length > 0) {
|
|
40
|
-
for (const c of remoteOnlyConflicts) this.log(chalk.red(` - ${c.name}`));
|
|
41
|
-
this.error("The following overlays exist remotely but not locally. Run 'revos overlays pull' first.", { exit: 1 });
|
|
42
|
-
}
|
|
43
|
-
for (const name of result.created) this.log(chalk.green(`+ Created: ${name}`));
|
|
44
|
-
for (const name of result.updated) this.log(chalk.blue(`~ Updated: ${name}`));
|
|
45
|
-
for (const c of result.conflicts) this.log(chalk.yellow(`! Conflict: ${c.name} - ${c.reason}`));
|
|
46
|
-
for (const e of result.errors) this.log(chalk.red(`✗ Error: ${e.name} - ${e.error}`));
|
|
47
|
-
this.log("");
|
|
48
|
-
this.log(chalk.bold("Summary:"));
|
|
49
|
-
if (result.created.length > 0) this.log(chalk.green(` Created: ${result.created.length}`));
|
|
50
|
-
if (result.updated.length > 0) this.log(chalk.blue(` Updated: ${result.updated.length}`));
|
|
51
|
-
if (result.conflicts.length > 0) this.log(chalk.yellow(` Conflicts: ${result.conflicts.length}`));
|
|
52
|
-
if (result.errors.length > 0) this.log(chalk.red(` Errors: ${result.errors.length}`));
|
|
53
|
-
}
|
|
54
|
-
if (result.errors.length > 0 || result.conflicts.length > 0) this.exit(1);
|
|
55
|
-
return result;
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
//#endregion
|
|
59
|
-
export { OverlaysPush as default };
|