@bonnard/cli 0.1.2 → 0.1.4

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 (49) hide show
  1. package/dist/bin/bon.mjs +1892 -97
  2. package/dist/bin/models-IsV2sX74.mjs +76 -0
  3. package/dist/bin/{validate-Bd1D39Bj.mjs → validate-C4EHvJzJ.mjs} +47 -4
  4. package/dist/docs/README.md +82 -0
  5. package/dist/docs/_index.md +69 -0
  6. package/dist/docs/topics/cubes.data-source.md +96 -0
  7. package/dist/docs/topics/cubes.dimensions.format.md +199 -0
  8. package/dist/docs/topics/cubes.dimensions.md +188 -0
  9. package/dist/docs/topics/cubes.dimensions.primary-key.md +110 -0
  10. package/dist/docs/topics/cubes.dimensions.sub-query.md +178 -0
  11. package/dist/docs/topics/cubes.dimensions.time.md +115 -0
  12. package/dist/docs/topics/cubes.dimensions.types.md +111 -0
  13. package/dist/docs/topics/cubes.extends.md +153 -0
  14. package/dist/docs/topics/cubes.hierarchies.md +178 -0
  15. package/dist/docs/topics/cubes.joins.md +119 -0
  16. package/dist/docs/topics/cubes.md +121 -0
  17. package/dist/docs/topics/cubes.measures.calculated.md +103 -0
  18. package/dist/docs/topics/cubes.measures.drill-members.md +162 -0
  19. package/dist/docs/topics/cubes.measures.filters.md +90 -0
  20. package/dist/docs/topics/cubes.measures.format.md +157 -0
  21. package/dist/docs/topics/cubes.measures.md +166 -0
  22. package/dist/docs/topics/cubes.measures.rolling.md +123 -0
  23. package/dist/docs/topics/cubes.measures.types.md +126 -0
  24. package/dist/docs/topics/cubes.public.md +176 -0
  25. package/dist/docs/topics/cubes.refresh-key.md +157 -0
  26. package/dist/docs/topics/cubes.segments.md +125 -0
  27. package/dist/docs/topics/cubes.sql.md +65 -0
  28. package/dist/docs/topics/pre-aggregations.md +130 -0
  29. package/dist/docs/topics/pre-aggregations.rollup.md +166 -0
  30. package/dist/docs/topics/syntax.context-variables.md +157 -0
  31. package/dist/docs/topics/syntax.md +137 -0
  32. package/dist/docs/topics/syntax.references.md +178 -0
  33. package/dist/docs/topics/views.cubes.md +166 -0
  34. package/dist/docs/topics/views.folders.md +158 -0
  35. package/dist/docs/topics/views.includes.md +143 -0
  36. package/dist/docs/topics/views.md +142 -0
  37. package/dist/docs/topics/workflow.deploy.md +132 -0
  38. package/dist/docs/topics/workflow.md +151 -0
  39. package/dist/docs/topics/workflow.query.md +203 -0
  40. package/dist/docs/topics/workflow.validate.md +156 -0
  41. package/dist/templates/claude/rules/bonnard.md +15 -0
  42. package/dist/templates/claude/settings.json +7 -0
  43. package/dist/templates/claude/skills/bonnard-cli/SKILL.md +59 -0
  44. package/dist/templates/claude/skills/bonnard-queries/SKILL.md +68 -0
  45. package/dist/templates/cursor/rules/bonnard-cli.mdc +47 -0
  46. package/dist/templates/cursor/rules/bonnard-queries.mdc +49 -0
  47. package/dist/templates/cursor/rules/bonnard.mdc +20 -0
  48. package/dist/templates/shared/bonnard.md +81 -0
  49. package/package.json +8 -3
@@ -0,0 +1,76 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import YAML from "yaml";
4
+
5
+ //#region src/lib/models/datasources.ts
6
+ /**
7
+ * Extract datasource references from Cube model files
8
+ */
9
+ /**
10
+ * Collect all YAML files from a directory recursively
11
+ */
12
+ function collectYamlFiles(dir) {
13
+ if (!fs.existsSync(dir)) return [];
14
+ const results = [];
15
+ function walk(current) {
16
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
17
+ const fullPath = path.join(current, entry.name);
18
+ if (entry.isDirectory()) walk(fullPath);
19
+ else if (entry.name.endsWith(".yaml") || entry.name.endsWith(".yml")) results.push(fullPath);
20
+ }
21
+ }
22
+ walk(dir);
23
+ return results;
24
+ }
25
+ /**
26
+ * Parse a single model file and extract datasource references
27
+ */
28
+ function extractFromFile(filePath) {
29
+ const datasourceToCubes = /* @__PURE__ */ new Map();
30
+ try {
31
+ const content = fs.readFileSync(filePath, "utf-8");
32
+ const parsed = YAML.parse(content);
33
+ if (!parsed) return datasourceToCubes;
34
+ if (parsed.cubes) for (const cube of parsed.cubes) {
35
+ const ds = cube.data_source || "default";
36
+ const existing = datasourceToCubes.get(ds) || [];
37
+ existing.push(cube.name);
38
+ datasourceToCubes.set(ds, existing);
39
+ }
40
+ if (parsed.views) {
41
+ for (const view of parsed.views) if (view.data_source) {
42
+ const existing = datasourceToCubes.get(view.data_source) || [];
43
+ existing.push(view.name);
44
+ datasourceToCubes.set(view.data_source, existing);
45
+ }
46
+ }
47
+ } catch {}
48
+ return datasourceToCubes;
49
+ }
50
+ /**
51
+ * Extract all unique datasource references from models/ and views/ directories
52
+ * Returns datasource names mapped to the cubes that use them
53
+ */
54
+ function extractDatasourcesFromModels(projectPath) {
55
+ const modelsDir = path.join(projectPath, "models");
56
+ const viewsDir = path.join(projectPath, "views");
57
+ const allFiles = [...collectYamlFiles(modelsDir), ...collectYamlFiles(viewsDir)];
58
+ const aggregated = /* @__PURE__ */ new Map();
59
+ for (const file of allFiles) {
60
+ const fileRefs = extractFromFile(file);
61
+ for (const [ds, cubes] of fileRefs) {
62
+ const existing = aggregated.get(ds) || [];
63
+ existing.push(...cubes);
64
+ aggregated.set(ds, existing);
65
+ }
66
+ }
67
+ const results = [];
68
+ for (const [name, cubes] of aggregated) if (name !== "default") results.push({
69
+ name,
70
+ cubes
71
+ });
72
+ return results;
73
+ }
74
+
75
+ //#endregion
76
+ export { extractDatasourcesFromModels };
@@ -1,5 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import YAML from "yaml";
3
4
  import { compile } from "@cubejs-backend/schema-compiler";
4
5
 
5
6
  //#region src/lib/validate.ts
@@ -19,6 +20,44 @@ function collectYamlFiles(dir, rootDir) {
19
20
  walk(dir);
20
21
  return results;
21
22
  }
23
+ function checkMissingDescriptions(files) {
24
+ const missing = [];
25
+ for (const file of files) try {
26
+ const parsed = YAML.parse(file.content);
27
+ if (!parsed) continue;
28
+ const cubes = parsed.cubes || [];
29
+ for (const cube of cubes) {
30
+ if (!cube.name) continue;
31
+ if (!cube.description) missing.push({
32
+ parent: cube.name,
33
+ type: "cube",
34
+ name: cube.name
35
+ });
36
+ const measures = cube.measures || [];
37
+ for (const measure of measures) if (measure.name && !measure.description) missing.push({
38
+ parent: cube.name,
39
+ type: "measure",
40
+ name: measure.name
41
+ });
42
+ const dimensions = cube.dimensions || [];
43
+ for (const dimension of dimensions) if (dimension.name && !dimension.description) missing.push({
44
+ parent: cube.name,
45
+ type: "dimension",
46
+ name: dimension.name
47
+ });
48
+ }
49
+ const views = parsed.views || [];
50
+ for (const view of views) {
51
+ if (!view.name) continue;
52
+ if (!view.description) missing.push({
53
+ parent: view.name,
54
+ type: "view",
55
+ name: view.name
56
+ });
57
+ }
58
+ } catch {}
59
+ return missing;
60
+ }
22
61
  function createModelRepository(projectPath) {
23
62
  const modelsDir = path.join(projectPath, "models");
24
63
  const viewsDir = path.join(projectPath, "views");
@@ -32,11 +71,13 @@ function createModelRepository(projectPath) {
32
71
  }
33
72
  async function validate(projectPath) {
34
73
  const repo = createModelRepository(projectPath);
35
- if ((await repo.dataSchemaFiles()).length === 0) return {
74
+ const files = await repo.dataSchemaFiles();
75
+ if (files.length === 0) return {
36
76
  valid: true,
37
77
  errors: [],
38
78
  cubes: [],
39
- views: []
79
+ views: [],
80
+ missingDescriptions: []
40
81
  };
41
82
  try {
42
83
  const { cubeEvaluator } = await compile(repo, {});
@@ -48,7 +89,8 @@ async function validate(projectPath) {
48
89
  valid: true,
49
90
  errors: [],
50
91
  cubes,
51
- views
92
+ views,
93
+ missingDescriptions: checkMissingDescriptions(files)
52
94
  };
53
95
  } catch (err) {
54
96
  const raw = err.messages ?? err.message ?? String(err);
@@ -56,7 +98,8 @@ async function validate(projectPath) {
56
98
  valid: false,
57
99
  errors: Array.isArray(raw) ? raw : [raw],
58
100
  cubes: [],
59
- views: []
101
+ views: [],
102
+ missingDescriptions: []
60
103
  };
61
104
  }
62
105
  }
@@ -0,0 +1,82 @@
1
+ # Bonnard CLI Documentation
2
+
3
+ This directory contains the documentation served by `bon docs`.
4
+
5
+ ## Structure
6
+
7
+ ```
8
+ docs/
9
+ ├── _index.md # Index shown by `bon docs` (llms.txt style)
10
+ ├── topics/ # Individual topic files
11
+ │ ├── cubes.md
12
+ │ ├── cubes.measures.md
13
+ │ ├── cubes.measures.types.md
14
+ │ └── ...
15
+ ├── schemas/ # JSON schemas for validation
16
+ │ ├── cube.schema.json
17
+ │ └── view.schema.json
18
+ └── README.md # This file
19
+ ```
20
+
21
+ ## Topic Naming Convention
22
+
23
+ Topic IDs use dot notation that maps directly to filenames:
24
+
25
+ | Topic ID | File |
26
+ |----------|------|
27
+ | `cubes` | `topics/cubes.md` |
28
+ | `cubes.measures` | `topics/cubes.measures.md` |
29
+ | `cubes.measures.types` | `topics/cubes.measures.types.md` |
30
+
31
+ ## Topic File Format
32
+
33
+ Each topic file should follow this structure:
34
+
35
+ ```markdown
36
+ # topic.name
37
+
38
+ > Brief one-line description.
39
+
40
+ ## Overview
41
+
42
+ Short explanation (2-3 sentences).
43
+
44
+ ## Example
45
+
46
+ ```yaml
47
+ # Minimal working example
48
+ ```
49
+
50
+ ## Reference
51
+
52
+ | Property | Type | Description |
53
+ |----------|------|-------------|
54
+ | name | string | ... |
55
+
56
+ ## See Also
57
+
58
+ - related.topic
59
+ - another.topic
60
+
61
+ ## More Information
62
+
63
+ https://cube.dev/docs/...
64
+ ```
65
+
66
+ ## Guidelines
67
+
68
+ - Keep topics concise (~20-40 lines)
69
+ - Lead with examples, not theory
70
+ - Use tables for property references
71
+ - Link to cube.dev for exhaustive details
72
+ - Include "See Also" for discoverability
73
+
74
+ ## Commands
75
+
76
+ ```bash
77
+ bon docs # Show index
78
+ bon docs <topic> # Show specific topic
79
+ bon docs <topic> --recursive # Show topic + children
80
+ bon docs --search <query> # Search topics
81
+ bon docs schema <type> # Show JSON schema
82
+ ```
@@ -0,0 +1,69 @@
1
+ # Bonnard Documentation
2
+
3
+ > Build semantic layers with Cube models and views.
4
+
5
+ ## Cubes
6
+
7
+ ### Core
8
+ - [cubes](cubes) - Define data models with measures and dimensions
9
+ - [cubes.sql](cubes.sql) - Base SQL table or query
10
+ - [cubes.extends](cubes.extends) - Reuse members from other cubes
11
+ - [cubes.public](cubes.public) - Control API visibility
12
+ - [cubes.refresh-key](cubes.refresh-key) - Cache invalidation strategies
13
+ - [cubes.data-source](cubes.data-source) - Connect cubes to warehouses
14
+
15
+ ### Measures
16
+ - [cubes.measures](cubes.measures) - Quantitative metrics (count, sum, avg)
17
+ - [cubes.measures.types](cubes.measures.types) - All 12 measure types
18
+ - [cubes.measures.filters](cubes.measures.filters) - Measure-level filters
19
+ - [cubes.measures.calculated](cubes.measures.calculated) - Derived metrics from other measures
20
+ - [cubes.measures.rolling](cubes.measures.rolling) - Rolling window aggregations
21
+ - [cubes.measures.drill-members](cubes.measures.drill-members) - Drill-down dimensions
22
+ - [cubes.measures.format](cubes.measures.format) - Output formatting (percent, currency)
23
+
24
+ ### Dimensions
25
+ - [cubes.dimensions](cubes.dimensions) - Attributes for grouping/filtering
26
+ - [cubes.dimensions.types](cubes.dimensions.types) - All 6 dimension types
27
+ - [cubes.dimensions.primary-key](cubes.dimensions.primary-key) - Unique identifiers for joins
28
+ - [cubes.dimensions.time](cubes.dimensions.time) - Time-based analysis
29
+ - [cubes.dimensions.sub-query](cubes.dimensions.sub-query) - Bring measures into dimensions
30
+ - [cubes.dimensions.format](cubes.dimensions.format) - Display formatting (link, image, etc.)
31
+
32
+ ### Relationships & Filters
33
+ - [cubes.joins](cubes.joins) - Relationships between cubes
34
+ - [cubes.hierarchies](cubes.hierarchies) - Drill-down paths for analysis
35
+ - [cubes.segments](cubes.segments) - Reusable predefined filters
36
+
37
+ ## Views
38
+
39
+ - [views](views) - Compose cubes into focused interfaces
40
+ - [views.cubes](views.cubes) - Include cubes with join paths
41
+ - [views.includes](views.includes) - Select members to expose
42
+ - [views.folders](views.folders) - Organize members into groups
43
+
44
+ ## Pre-Aggregations
45
+
46
+ - [pre-aggregations](pre-aggregations) - Materialize query results for performance
47
+ - [pre-aggregations.rollup](pre-aggregations.rollup) - Summarized data tables
48
+
49
+ ## Syntax
50
+
51
+ - [syntax](syntax) - YAML syntax and conventions
52
+ - [syntax.references](syntax.references) - Reference columns, members, and cubes
53
+ - [syntax.context-variables](syntax.context-variables) - CUBE, FILTER_PARAMS, COMPILE_CONTEXT
54
+
55
+ ## Workflow
56
+
57
+ - [workflow](workflow) - End-to-end development workflow
58
+ - [workflow.validate](workflow.validate) - Validate models locally
59
+ - [workflow.deploy](workflow.deploy) - Deploy to Bonnard
60
+ - [workflow.query](workflow.query) - Query the deployed semantic layer
61
+
62
+ ## Quick Reference
63
+
64
+ ```bash
65
+ bon docs <topic> # View topic
66
+ bon docs <topic> --recursive # View topic + children
67
+ bon docs --search <query> # Search all topics
68
+ bon docs schema <type> # JSON schema for validation
69
+ ```
@@ -0,0 +1,96 @@
1
+ # cubes.data-source
2
+
3
+ > Connect cubes to specific data warehouses.
4
+
5
+ ## Overview
6
+
7
+ The `data_source` property specifies which configured data source a cube should use. This enables multi-database architectures where different cubes query different warehouses.
8
+
9
+ ## Example
10
+
11
+ ```yaml
12
+ cubes:
13
+ - name: orders
14
+ sql_table: public.orders
15
+ data_source: default
16
+
17
+ - name: analytics_events
18
+ sql_table: events
19
+ data_source: clickhouse_analytics
20
+ ```
21
+
22
+ ## Syntax
23
+
24
+ ### Single Data Source
25
+
26
+ ```yaml
27
+ cubes:
28
+ - name: users
29
+ sql_table: users
30
+ data_source: postgres_main
31
+ ```
32
+
33
+ ### Default Behavior
34
+
35
+ If `data_source` is omitted, the cube uses the `default` data source:
36
+
37
+ ```yaml
38
+ cubes:
39
+ - name: orders
40
+ sql_table: orders
41
+ # Uses "default" data source
42
+ ```
43
+
44
+ ## Multi-Database Architecture
45
+
46
+ Different cubes can query different databases:
47
+
48
+ ```yaml
49
+ cubes:
50
+ # Transactional data from Postgres
51
+ - name: orders
52
+ sql_table: public.orders
53
+ data_source: postgres
54
+
55
+ # Analytics events from ClickHouse
56
+ - name: events
57
+ sql_table: analytics.events
58
+ data_source: clickhouse
59
+
60
+ # ML features from Snowflake
61
+ - name: predictions
62
+ sql_table: ml.predictions
63
+ data_source: snowflake
64
+ ```
65
+
66
+ ## Data Source Configuration
67
+
68
+ Data sources are configured in your Bonnard project:
69
+
70
+ ```yaml
71
+ # .bon/datasources.yaml
72
+ datasources:
73
+ - name: default
74
+ type: postgres
75
+ host: localhost
76
+ database: mydb
77
+
78
+ - name: analytics
79
+ type: snowflake
80
+ account: myaccount
81
+ database: ANALYTICS
82
+ ```
83
+
84
+ ## Cross-Database Joins
85
+
86
+ Cubes from different data sources cannot be directly joined. Use views or pre-aggregations to combine data from multiple sources.
87
+
88
+ ## See Also
89
+
90
+ - cubes
91
+ - cubes.sql
92
+ - workflow.deploy
93
+
94
+ ## More Information
95
+
96
+ https://cube.dev/docs/reference/data-model/cube#data-source
@@ -0,0 +1,199 @@
1
+ # cubes.dimensions.format
2
+
3
+ > Control how dimension values are displayed.
4
+
5
+ ## Overview
6
+
7
+ The `format` property hints to BI tools how to render dimension values. Dimensions support more format options than measures.
8
+
9
+ ## Example
10
+
11
+ ```yaml
12
+ dimensions:
13
+ - name: avatar_url
14
+ type: string
15
+ sql: avatar_url
16
+ format: imageUrl
17
+
18
+ - name: profile_link
19
+ type: string
20
+ sql: "CONCAT('https://example.com/users/', id)"
21
+ format: link
22
+
23
+ - name: price
24
+ type: number
25
+ sql: unit_price
26
+ format: currency
27
+
28
+ - name: external_id
29
+ type: number
30
+ sql: external_system_id
31
+ format: id
32
+ ```
33
+
34
+ ## Supported Formats
35
+
36
+ ### imageUrl
37
+
38
+ Displays the value as an image in table views:
39
+
40
+ ```yaml
41
+ - name: product_image
42
+ type: string
43
+ sql: image_url
44
+ format: imageUrl
45
+
46
+ - name: avatar
47
+ type: string
48
+ sql: profile_picture_url
49
+ format: imageUrl
50
+ ```
51
+
52
+ ### link
53
+
54
+ Displays the value as a clickable hyperlink:
55
+
56
+ ```yaml
57
+ - name: website
58
+ type: string
59
+ sql: website_url
60
+ format: link
61
+
62
+ - name: order_link
63
+ type: string
64
+ sql: "CONCAT('https://admin.example.com/orders/', id)"
65
+ format: link
66
+ ```
67
+
68
+ ### id
69
+
70
+ Prevents number formatting (no commas in large numbers):
71
+
72
+ ```yaml
73
+ - name: external_id
74
+ type: number
75
+ sql: external_system_id
76
+ format: id
77
+ ```
78
+
79
+ Output: `1234567890` instead of `1,234,567,890`
80
+
81
+ ### currency
82
+
83
+ Displays as monetary value:
84
+
85
+ ```yaml
86
+ - name: unit_price
87
+ type: number
88
+ sql: price
89
+ format: currency
90
+ ```
91
+
92
+ ### percent
93
+
94
+ Displays as percentage:
95
+
96
+ ```yaml
97
+ - name: discount_rate
98
+ type: number
99
+ sql: discount
100
+ format: percent
101
+ ```
102
+
103
+ ### Custom Time Format
104
+
105
+ Use POSIX strftime format strings for time dimensions:
106
+
107
+ ```yaml
108
+ - name: created_at
109
+ type: time
110
+ sql: created_at
111
+ format: "%Y-%m-%d %H:%M:%S"
112
+
113
+ - name: birth_date
114
+ type: time
115
+ sql: birth_date
116
+ format: "%B %d, %Y" # "January 15, 2024"
117
+ ```
118
+
119
+ ## Time Format Specifiers
120
+
121
+ | Specifier | Description | Example |
122
+ |-----------|-------------|---------|
123
+ | `%Y` | 4-digit year | 2024 |
124
+ | `%y` | 2-digit year | 24 |
125
+ | `%m` | Month (01-12) | 03 |
126
+ | `%B` | Full month name | March |
127
+ | `%b` | Abbreviated month | Mar |
128
+ | `%d` | Day of month (01-31) | 15 |
129
+ | `%H` | Hour (00-23) | 14 |
130
+ | `%I` | Hour (01-12) | 02 |
131
+ | `%M` | Minute (00-59) | 30 |
132
+ | `%S` | Second (00-59) | 45 |
133
+ | `%p` | AM/PM | PM |
134
+
135
+ ## Common Patterns
136
+
137
+ ### User Profiles
138
+
139
+ ```yaml
140
+ dimensions:
141
+ - name: avatar
142
+ type: string
143
+ sql: avatar_url
144
+ format: imageUrl
145
+
146
+ - name: profile_url
147
+ type: string
148
+ sql: "CONCAT('/users/', username)"
149
+ format: link
150
+ ```
151
+
152
+ ### Product Catalog
153
+
154
+ ```yaml
155
+ dimensions:
156
+ - name: image
157
+ type: string
158
+ sql: image_url
159
+ format: imageUrl
160
+
161
+ - name: sku
162
+ type: string
163
+ sql: sku
164
+ format: id
165
+
166
+ - name: price
167
+ type: number
168
+ sql: price
169
+ format: currency
170
+ ```
171
+
172
+ ### Timestamps
173
+
174
+ ```yaml
175
+ dimensions:
176
+ - name: created_at
177
+ type: time
178
+ sql: created_at
179
+ format: "%Y-%m-%d"
180
+
181
+ - name: last_login
182
+ type: time
183
+ sql: last_login_at
184
+ format: "%b %d, %Y at %I:%M %p"
185
+ ```
186
+
187
+ ## BI Tool Support
188
+
189
+ Format support varies by visualization tool. Most tools recognize common formats, but rendering details may differ.
190
+
191
+ ## See Also
192
+
193
+ - cubes.dimensions
194
+ - cubes.dimensions.types
195
+ - cubes.measures.format
196
+
197
+ ## More Information
198
+
199
+ https://cube.dev/docs/reference/data-model/types-and-formats#format