yamchart 0.1.2 → 0.3.1

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.
@@ -0,0 +1,315 @@
1
+ # Yamchart Configuration Reference
2
+
3
+ Yamchart is a Git-native BI framework. Dashboards are defined as YAML configs and SQL models — if it's not in Git, it doesn't exist.
4
+
5
+ ## Project Structure
6
+
7
+ ```
8
+ {{name}}/
9
+ ├── yamchart.yaml # Project config + default connection
10
+ ├── connections/ # Database connection configs (.yaml)
11
+ ├── models/ # SQL models with Jinja templating (.sql)
12
+ ├── charts/ # Chart definitions (.yaml)
13
+ └── dashboards/ # Dashboard layouts (.yaml)
14
+ ```
15
+
16
+ ## Commands
17
+
18
+ ```bash
19
+ yamchart dev # Start dev server with hot reload
20
+ yamchart validate # Validate all config files
21
+ yamchart validate --dry-run # Also test queries with EXPLAIN
22
+ yamchart test # Run model @returns and @tests assertions
23
+ yamchart test <model> # Test specific model
24
+ ```
25
+
26
+ ## SQL Models (`models/*.sql`)
27
+
28
+ Models are SQL files with metadata comments and Jinja templating.
29
+
30
+ ```sql
31
+ -- @name: model_name
32
+ -- @description: What this model computes
33
+ -- @returns:
34
+ -- - column1: type
35
+ -- - column2: type
36
+
37
+ SELECT
38
+ date_trunc('{{ granularity }}', order_date) AS period,
39
+ SUM(amount) AS revenue
40
+ FROM {{ ref('orders') }}
41
+ WHERE order_date BETWEEN '{{ start_date }}' AND '{{ end_date }}'
42
+ {% if category and category != 'All' %}
43
+ AND category = '{{ category }}'
44
+ {% endif %}
45
+ GROUP BY 1
46
+ ORDER BY 1
47
+ ```
48
+
49
+ **Metadata tags:** `@name` (required), `@description`, `@param name: type = default`, `@returns` (column: type list), `@tests` (SQL assertions where zero rows = pass)
50
+
51
+ **Templating:** `{{ variable }}` for substitution, `{% if %}` / `{% endif %}` for conditionals, `{{ ref('table') }}` for table references, `{{ user.attribute }}` for row-level security.
52
+
53
+ **Date parameters:** When a chart has `type: date_range`, the model receives `start_date` and `end_date` automatically.
54
+
55
+ ## Charts (`charts/*.yaml`)
56
+
57
+ ### Common Structure
58
+
59
+ ```yaml
60
+ name: my-chart # Unique identifier (required)
61
+ title: My Chart # Display title (required)
62
+ description: Description # Optional
63
+ source:
64
+ model: model_name # SQL model to use
65
+ parameters: [] # Interactive filters (optional)
66
+ chart:
67
+ type: line # Chart type
68
+ # ... type-specific config
69
+ ```
70
+
71
+ ### Chart Types
72
+
73
+ **Line / Bar / Area** — time series and categories:
74
+ ```yaml
75
+ chart:
76
+ type: line # or bar, area
77
+ x: { field: date, type: temporal }
78
+ y: { field: revenue, format: "$,.0f" }
79
+ series:
80
+ field: category # Multi-series by grouping field (optional)
81
+ ```
82
+
83
+ **Pie / Donut** — part-to-whole:
84
+ ```yaml
85
+ chart:
86
+ type: pie # or donut
87
+ x: { field: segment, type: nominal }
88
+ y: { field: amount, format: "$,.0f" }
89
+ ```
90
+
91
+ **Scatter** — correlations with optional size/group/regression:
92
+ ```yaml
93
+ chart:
94
+ type: scatter
95
+ x: { field: revenue, type: quantitative }
96
+ y: { field: orders, type: quantitative }
97
+ size: { field: avg_value, min: 8, max: 40 }
98
+ group: { field: region }
99
+ regression: { type: linear, show_equation: true, show_r_squared: true }
100
+ ```
101
+
102
+ **KPI** — single metric with comparison:
103
+ ```yaml
104
+ chart:
105
+ type: kpi
106
+ value: { field: value }
107
+ format: { type: currency, currency: USD, decimals: 0 }
108
+ comparison: { enabled: true, field: previous_value, type: percent_change }
109
+ ```
110
+
111
+ **Heatmap** — two-dimensional intensity:
112
+ ```yaml
113
+ chart:
114
+ type: heatmap
115
+ x: { field: hour, type: ordinal }
116
+ y: { field: day, type: ordinal }
117
+ value: { field: count }
118
+ color_range: { min: '#EBF5FB', max: '#1A5276' }
119
+ ```
120
+
121
+ **Funnel** — conversion stages:
122
+ ```yaml
123
+ chart:
124
+ type: funnel
125
+ stage: { field: stage_name }
126
+ value: { field: user_count }
127
+ ```
128
+
129
+ **Waterfall** — incremental deltas:
130
+ ```yaml
131
+ chart:
132
+ type: waterfall
133
+ category: { field: label }
134
+ value: { field: amount }
135
+ total_field: is_total
136
+ ```
137
+
138
+ **Gauge** — single metric dial:
139
+ ```yaml
140
+ chart:
141
+ type: gauge
142
+ value: { field: score }
143
+ min: 0
144
+ max: 100
145
+ thresholds:
146
+ - { value: 60, color: '#EF4444' }
147
+ - { value: 85, color: '#F59E0B' }
148
+ - { value: 100, color: '#22C55E' }
149
+ ```
150
+
151
+ **Combo** — mixed types with dual y-axes:
152
+ ```yaml
153
+ chart:
154
+ type: combo
155
+ x: { field: date, type: temporal }
156
+ series:
157
+ columns:
158
+ - { field: revenue, type: bar, axis: left, color: "#3B82F6" }
159
+ - { field: margin_pct, type: line, axis: right, color: "#10B981" }
160
+ axes:
161
+ left: { format: "$,.0f", label: Revenue }
162
+ right: { format: ".0%", label: Margin }
163
+ ```
164
+
165
+ ### Multi-Series
166
+
167
+ **Long format** — group by a field:
168
+ ```yaml
169
+ series:
170
+ field: region
171
+ colors:
172
+ North: "#3B82F6"
173
+ South: "#EF4444"
174
+ ```
175
+
176
+ **Wide format** — explicit columns:
177
+ ```yaml
178
+ series:
179
+ columns:
180
+ - { field: revenue, name: Revenue, color: "#3B82F6" }
181
+ - { field: cost, name: Cost, color: "#EF4444", style: dashed }
182
+ ```
183
+
184
+ ### Stacking
185
+
186
+ ```yaml
187
+ chart:
188
+ type: bar
189
+ stacking: stacked # or "percent" (normalizes to 100%)
190
+ ```
191
+
192
+ ### Parameters
193
+
194
+ ```yaml
195
+ parameters:
196
+ - name: date_range
197
+ type: date_range
198
+ default: last_30_days
199
+
200
+ - name: region
201
+ type: select
202
+ options: [North, South, East, West]
203
+ default: North
204
+
205
+ - name: category
206
+ type: dynamic_select
207
+ source: { model: category_options, value_field: id, label_field: name }
208
+ ```
209
+
210
+ **Presets for date_range:** today, yesterday, last_7_days, last_30_days, last_90_days, last_365_days, this_week, last_week, this_month, last_month, this_quarter, last_quarter, this_year, last_year
211
+
212
+ ### Drill-Down
213
+
214
+ ```yaml
215
+ drillDown:
216
+ chart: detail-chart # Target chart name
217
+ field: category # Field to pass as filter
218
+ columns: # Optional table columns on landing
219
+ - { field: name, label: Name }
220
+ - { field: revenue, label: Revenue, format: "$,.0f" }
221
+ ```
222
+
223
+ ### Gradients and Colors
224
+
225
+ ```yaml
226
+ chart:
227
+ gradient: true # Auto gradient
228
+ color: "#3B82F6" # Single color override
229
+ color: # Conditional coloring
230
+ conditions:
231
+ - { when: "> 0", color: "#10B981" }
232
+ - { when: "< 0", color: "#EF4444" }
233
+ default: "#6B7280"
234
+ ```
235
+
236
+ ## Dashboards (`dashboards/*.yaml`)
237
+
238
+ 12-column grid layout with chart and text widgets.
239
+
240
+ ```yaml
241
+ name: overview
242
+ title: Executive Overview
243
+
244
+ filters:
245
+ - { name: date_range, type: date_range, default: last_30_days }
246
+
247
+ layout:
248
+ gap: 16
249
+ rows:
250
+ - height: 180
251
+ widgets:
252
+ - { type: chart, ref: revenue-kpi, cols: 3 }
253
+ - { type: chart, ref: orders-kpi, cols: 3 }
254
+ - { type: chart, ref: customers-kpi, cols: 6 }
255
+ - height: 400
256
+ widgets:
257
+ - { type: chart, ref: revenue-trend, cols: 8 }
258
+ - type: text
259
+ cols: 4
260
+ content: |
261
+ ## Summary
262
+ Revenue: **{{revenue-kpi}}**
263
+ vs last month: {{revenue-kpi@previous_month}}
264
+ ```
265
+
266
+ **KPI references in text:** `{{chart}}`, `{{chart.field}}`, `{{chart@preset}}`
267
+
268
+ ## Connections (`connections/*.yaml`)
269
+
270
+ ```yaml
271
+ # DuckDB (local file)
272
+ name: local
273
+ type: duckdb
274
+ path: ./data/analytics.duckdb
275
+
276
+ # PostgreSQL
277
+ name: warehouse
278
+ type: postgres
279
+ host: ${DB_HOST}
280
+ port: 5432
281
+ database: analytics
282
+ user: ${DB_USER}
283
+ password: ${DB_PASSWORD}
284
+ ssl: true
285
+ ```
286
+
287
+ **Supported:** DuckDB, PostgreSQL, MySQL, SQLite, Snowflake
288
+
289
+ Use `${ENV_VAR}` for credentials. Never commit secrets.
290
+
291
+ ## Axis Types
292
+
293
+ | Type | Use | Example |
294
+ |------|-----|---------|
295
+ | `temporal` | Dates/times | `2024-01-15` |
296
+ | `ordinal` | Categories | `Electronics` |
297
+ | `quantitative` | Numbers | `250.5` |
298
+
299
+ ## Number Formats (d3-format)
300
+
301
+ | Format | Output | Use |
302
+ |--------|--------|-----|
303
+ | `$,.0f` | $1,234 | Currency |
304
+ | `,.0f` | 1,234 | Numbers |
305
+ | `.2%` | 12.34% | Percentages |
306
+ | `.2s` | 1.2M | SI prefix |
307
+
308
+ ## Theme (`yamchart.yaml`)
309
+
310
+ ```yaml
311
+ theme:
312
+ palette: ["#3B82F6", "#EF4444", "#10B981", "#F59E0B"]
313
+ gradient: false
314
+ opacity: 1.0
315
+ ```
@@ -0,0 +1,221 @@
1
+ import {
2
+ detail,
3
+ error,
4
+ header,
5
+ newline,
6
+ success,
7
+ warning
8
+ } from "./chunk-HJVVHYVN.js";
9
+
10
+ // src/commands/test.ts
11
+ import { readFile, readdir, access } from "fs/promises";
12
+ import { join, extname } from "path";
13
+ import { parse as parseYaml } from "yaml";
14
+ import {
15
+ parseModelMetadata,
16
+ DuckDBConnector,
17
+ runAll,
18
+ renderTemplate,
19
+ createTemplateContext,
20
+ expandDatePreset,
21
+ isDatePreset
22
+ } from "@yamchart/query";
23
+ async function testProject(projectDir, modelFilter, options) {
24
+ const projectPath = join(projectDir, "yamchart.yaml");
25
+ const projectContent = await readFile(projectPath, "utf-8");
26
+ const projectConfig = parseYaml(projectContent);
27
+ const connName = options.connection || projectConfig.defaults?.connection;
28
+ if (!connName) {
29
+ throw new Error(
30
+ "No connection specified. Use --connection or set defaults.connection in yamchart.yaml"
31
+ );
32
+ }
33
+ const connConfig = await loadConnectionConfig(projectDir, connName);
34
+ if (connConfig.type !== "duckdb") {
35
+ throw new Error(`Test command currently supports DuckDB only, got "${connConfig.type}"`);
36
+ }
37
+ const dbPathRaw = connConfig.config.path;
38
+ const dbPath = dbPathRaw.startsWith("/") ? dbPathRaw : join(projectDir, dbPathRaw);
39
+ const modelsDir = join(projectDir, "models");
40
+ const allModels = await loadModels(modelsDir);
41
+ let modelsToTest = allModels;
42
+ if (modelFilter) {
43
+ modelsToTest = allModels.filter((m) => m.name === modelFilter);
44
+ if (modelsToTest.length === 0) {
45
+ throw new Error(`Model "${modelFilter}" not found`);
46
+ }
47
+ }
48
+ const refs = {};
49
+ for (const m of allModels) {
50
+ refs[m.name] = m.name;
51
+ }
52
+ const testInputs = [];
53
+ for (const model of modelsToTest) {
54
+ try {
55
+ const compiledSql = compileWithDefaults(model.sql, model.metadata, refs);
56
+ testInputs.push({ compiledSql, metadata: model.metadata });
57
+ } catch {
58
+ testInputs.push({ compiledSql: model.sql, metadata: model.metadata });
59
+ }
60
+ }
61
+ const connector = new DuckDBConnector({ path: dbPath });
62
+ try {
63
+ await connector.connect();
64
+ const suite = await runAll(testInputs, connector);
65
+ return { success: suite.failed === 0, suite, connectionName: connName };
66
+ } finally {
67
+ await connector.disconnect();
68
+ }
69
+ }
70
+ async function loadConnectionConfig(projectDir, connName) {
71
+ const yamlPath = join(projectDir, "connections", `${connName}.yaml`);
72
+ const ymlPath = join(projectDir, "connections", `${connName}.yml`);
73
+ let connPath = yamlPath;
74
+ try {
75
+ await access(yamlPath);
76
+ } catch {
77
+ try {
78
+ await access(ymlPath);
79
+ connPath = ymlPath;
80
+ } catch {
81
+ throw new Error(`Connection "${connName}" not found at connections/${connName}.yaml`);
82
+ }
83
+ }
84
+ const connContent = await readFile(connPath, "utf-8");
85
+ return parseYaml(connContent);
86
+ }
87
+ function resolveDynamicDefault(value) {
88
+ const trimmed = value.trim().toLowerCase();
89
+ if (trimmed === "current_date()" || trimmed === "current_date" || trimmed === "now()" || trimmed.includes("current_date")) {
90
+ return formatISODate(/* @__PURE__ */ new Date());
91
+ }
92
+ return value;
93
+ }
94
+ function formatISODate(date) {
95
+ const y = date.getFullYear();
96
+ const m = String(date.getMonth() + 1).padStart(2, "0");
97
+ const d = String(date.getDate()).padStart(2, "0");
98
+ return `${y}-${m}-${d}`;
99
+ }
100
+ function compileWithDefaults(sql, metadata, refs) {
101
+ const params = {};
102
+ if (metadata.params) {
103
+ for (const p of metadata.params) {
104
+ if (p.default !== void 0) {
105
+ params[p.name] = resolveDynamicDefault(p.default);
106
+ }
107
+ }
108
+ }
109
+ if (typeof params.date_range === "string" && isDatePreset(params.date_range)) {
110
+ const range = expandDatePreset(params.date_range);
111
+ if (range) {
112
+ params.start_date = range.start_date;
113
+ params.end_date = range.end_date;
114
+ }
115
+ }
116
+ const context = createTemplateContext(params, refs);
117
+ return renderTemplate(sql, context);
118
+ }
119
+ async function loadModels(dir) {
120
+ const models = [];
121
+ let entries;
122
+ try {
123
+ entries = await readdir(dir, { withFileTypes: true });
124
+ } catch {
125
+ return models;
126
+ }
127
+ for (const entry of entries) {
128
+ const fullPath = join(dir, entry.name);
129
+ if (entry.isDirectory()) {
130
+ const subModels = await loadModels(fullPath);
131
+ models.push(...subModels);
132
+ } else if (extname(entry.name) === ".sql") {
133
+ const content = await readFile(fullPath, "utf-8");
134
+ try {
135
+ const parsed = parseModelMetadata(content);
136
+ models.push({ name: parsed.name, sql: parsed.sql, metadata: parsed });
137
+ } catch {
138
+ }
139
+ }
140
+ }
141
+ return models;
142
+ }
143
+ function formatTestOutput(result, connectionName) {
144
+ const { suite } = result;
145
+ const testedModels = suite.models.filter(
146
+ (m) => m.schemaCheck || m.assertions.length > 0 || m.error
147
+ );
148
+ header(`Testing ${suite.models.length} model(s) against ${connectionName}...`);
149
+ for (const model of suite.models) {
150
+ const hasChecks = model.schemaCheck || model.assertions.length > 0 || model.error;
151
+ if (!hasChecks) continue;
152
+ console.log(` ${model.modelName}`);
153
+ if (model.error) {
154
+ error(` error: ${model.error}`);
155
+ continue;
156
+ }
157
+ if (model.schemaCheck) {
158
+ formatSchemaCheck(model.schemaCheck);
159
+ }
160
+ for (const assertion of model.assertions) {
161
+ formatAssertion(assertion);
162
+ }
163
+ newline();
164
+ }
165
+ const totalChecks = suite.passed + suite.failed;
166
+ const summary = `${testedModels.length} model(s), ${totalChecks} check(s): ${suite.passed} passed, ${suite.failed} failed`;
167
+ const skippedSuffix = suite.skipped > 0 ? ` (${suite.skipped} skipped)` : "";
168
+ const durationSuffix = ` (${suite.durationMs.toFixed(0)}ms)`;
169
+ if (suite.failed === 0) {
170
+ success(`${summary}${skippedSuffix}${durationSuffix}`);
171
+ } else {
172
+ error(`${summary}${skippedSuffix}${durationSuffix}`);
173
+ }
174
+ }
175
+ function formatSchemaCheck(check) {
176
+ if (check.passed) {
177
+ success(` schema: returns expected columns (${check.expectedColumns.join(", ")})`);
178
+ return;
179
+ }
180
+ const issues = [];
181
+ if (check.missingColumns.length > 0) {
182
+ issues.push(`missing: ${check.missingColumns.join(", ")}`);
183
+ }
184
+ for (const m of check.typeMismatches) {
185
+ issues.push(`${m.column}: expected ${m.expected}, got ${m.actual}`);
186
+ }
187
+ error(` schema: ${issues.join("; ")}`);
188
+ }
189
+ function formatAssertion(assertion) {
190
+ const label = extractAssertionLabel(assertion.sql);
191
+ if (assertion.warning) {
192
+ warning(` ${label}`);
193
+ detail(` ${assertion.warning}`);
194
+ }
195
+ if (assertion.error) {
196
+ error(` test: ${label} -- error`);
197
+ detail(` ${assertion.error}`);
198
+ } else if (assertion.passed) {
199
+ success(` test: ${label}`);
200
+ } else {
201
+ error(` test: ${label} -- ${assertion.violationCount} failing row(s)`);
202
+ if (assertion.sampleViolations) {
203
+ for (const row of assertion.sampleViolations.slice(0, 3)) {
204
+ detail(` ${JSON.stringify(row)}`);
205
+ }
206
+ }
207
+ }
208
+ }
209
+ function extractAssertionLabel(sql) {
210
+ const whereMatch = sql.match(/WHERE\s+(.+?)$/i);
211
+ if (whereMatch?.[1]) {
212
+ const clause = whereMatch[1].trim();
213
+ return clause.length > 60 ? clause.slice(0, 57) + "..." : clause;
214
+ }
215
+ return sql.length > 60 ? sql.slice(0, 57) + "..." : sql;
216
+ }
217
+ export {
218
+ formatTestOutput,
219
+ testProject
220
+ };
221
+ //# sourceMappingURL=test-N4KIIKQN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/test.ts"],"sourcesContent":["import { readFile, readdir, access } from 'fs/promises';\nimport { join, extname } from 'path';\nimport { parse as parseYaml } from 'yaml';\nimport {\n parseModelMetadata,\n DuckDBConnector,\n runAll,\n renderTemplate,\n createTemplateContext,\n expandDatePreset,\n isDatePreset,\n type TestSuiteResult,\n type TestModelInput,\n} from '@yamchart/query';\nimport type { ModelMetadata } from '@yamchart/schema';\nimport * as output from '../utils/output.js';\n\nexport interface TestOptions {\n connection?: string;\n json?: boolean;\n}\n\nexport interface TestResult {\n success: boolean;\n suite: TestSuiteResult;\n connectionName: string;\n}\n\nexport async function testProject(\n projectDir: string,\n modelFilter: string | undefined,\n options: TestOptions,\n): Promise<TestResult> {\n const projectPath = join(projectDir, 'yamchart.yaml');\n const projectContent = await readFile(projectPath, 'utf-8');\n const projectConfig = parseYaml(projectContent) as {\n name: string;\n defaults?: { connection?: string };\n };\n\n const connName = options.connection || projectConfig.defaults?.connection;\n if (!connName) {\n throw new Error(\n 'No connection specified. Use --connection or set defaults.connection in yamchart.yaml',\n );\n }\n\n const connConfig = await loadConnectionConfig(projectDir, connName);\n\n if (connConfig.type !== 'duckdb') {\n throw new Error(`Test command currently supports DuckDB only, got \"${connConfig.type}\"`);\n }\n\n const dbPathRaw = connConfig.config.path as string;\n const dbPath = dbPathRaw.startsWith('/') ? dbPathRaw : join(projectDir, dbPathRaw);\n\n const modelsDir = join(projectDir, 'models');\n const allModels = await loadModels(modelsDir);\n\n let modelsToTest = allModels;\n if (modelFilter) {\n modelsToTest = allModels.filter((m) => m.name === modelFilter);\n if (modelsToTest.length === 0) {\n throw new Error(`Model \"${modelFilter}\" not found`);\n }\n }\n\n // Build refs map: model name -> model name (identity for standalone execution)\n const refs: Record<string, string> = {};\n for (const m of allModels) {\n refs[m.name] = m.name;\n }\n\n const testInputs: TestModelInput[] = [];\n for (const model of modelsToTest) {\n try {\n const compiledSql = compileWithDefaults(model.sql, model.metadata, refs);\n testInputs.push({ compiledSql, metadata: model.metadata });\n } catch {\n // If compilation fails, include raw SQL so the error is reported during test execution\n testInputs.push({ compiledSql: model.sql, metadata: model.metadata });\n }\n }\n\n const connector = new DuckDBConnector({ path: dbPath });\n try {\n await connector.connect();\n const suite = await runAll(testInputs, connector);\n return { success: suite.failed === 0, suite, connectionName: connName };\n } finally {\n await connector.disconnect();\n }\n}\n\nasync function loadConnectionConfig(\n projectDir: string,\n connName: string,\n): Promise<{ type: string; config: Record<string, unknown> }> {\n const yamlPath = join(projectDir, 'connections', `${connName}.yaml`);\n const ymlPath = join(projectDir, 'connections', `${connName}.yml`);\n\n let connPath = yamlPath;\n try {\n await access(yamlPath);\n } catch {\n try {\n await access(ymlPath);\n connPath = ymlPath;\n } catch {\n throw new Error(`Connection \"${connName}\" not found at connections/${connName}.yaml`);\n }\n }\n\n const connContent = await readFile(connPath, 'utf-8');\n return parseYaml(connContent) as { type: string; config: Record<string, unknown> };\n}\n\nfunction resolveDynamicDefault(value: string): string {\n const trimmed = value.trim().toLowerCase();\n if (\n trimmed === 'current_date()' ||\n trimmed === 'current_date' ||\n trimmed === 'now()' ||\n trimmed.includes('current_date')\n ) {\n return formatISODate(new Date());\n }\n return value;\n}\n\nfunction formatISODate(date: Date): string {\n const y = date.getFullYear();\n const m = String(date.getMonth() + 1).padStart(2, '0');\n const d = String(date.getDate()).padStart(2, '0');\n return `${y}-${m}-${d}`;\n}\n\nfunction compileWithDefaults(\n sql: string,\n metadata: ModelMetadata,\n refs: Record<string, string>,\n): string {\n const params: Record<string, unknown> = {};\n if (metadata.params) {\n for (const p of metadata.params) {\n if (p.default !== undefined) {\n params[p.name] = resolveDynamicDefault(p.default);\n }\n }\n }\n\n // Expand date presets into start_date/end_date\n if (typeof params.date_range === 'string' && isDatePreset(params.date_range)) {\n const range = expandDatePreset(params.date_range);\n if (range) {\n params.start_date = range.start_date;\n params.end_date = range.end_date;\n }\n }\n\n const context = createTemplateContext(params, refs);\n return renderTemplate(sql, context);\n}\n\ninterface LoadedModel {\n name: string;\n sql: string;\n metadata: ModelMetadata;\n}\n\nasync function loadModels(dir: string): Promise<LoadedModel[]> {\n const models: LoadedModel[] = [];\n\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return models;\n }\n\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n const subModels = await loadModels(fullPath);\n models.push(...subModels);\n } else if (extname(entry.name) === '.sql') {\n const content = await readFile(fullPath, 'utf-8');\n try {\n const parsed = parseModelMetadata(content);\n models.push({ name: parsed.name, sql: parsed.sql, metadata: parsed });\n } catch {\n // Skip unparseable models\n }\n }\n }\n\n return models;\n}\n\nexport function formatTestOutput(result: TestResult, connectionName: string): void {\n const { suite } = result;\n\n const testedModels = suite.models.filter(\n (m) => m.schemaCheck || m.assertions.length > 0 || m.error,\n );\n\n output.header(`Testing ${suite.models.length} model(s) against ${connectionName}...`);\n\n for (const model of suite.models) {\n const hasChecks = model.schemaCheck || model.assertions.length > 0 || model.error;\n if (!hasChecks) continue;\n\n console.log(` ${model.modelName}`);\n\n if (model.error) {\n output.error(` error: ${model.error}`);\n continue;\n }\n\n if (model.schemaCheck) {\n formatSchemaCheck(model.schemaCheck);\n }\n\n for (const assertion of model.assertions) {\n formatAssertion(assertion);\n }\n\n output.newline();\n }\n\n // Summary line\n const totalChecks = suite.passed + suite.failed;\n const summary = `${testedModels.length} model(s), ${totalChecks} check(s): ${suite.passed} passed, ${suite.failed} failed`;\n const skippedSuffix = suite.skipped > 0 ? ` (${suite.skipped} skipped)` : '';\n const durationSuffix = ` (${suite.durationMs.toFixed(0)}ms)`;\n\n if (suite.failed === 0) {\n output.success(`${summary}${skippedSuffix}${durationSuffix}`);\n } else {\n output.error(`${summary}${skippedSuffix}${durationSuffix}`);\n }\n}\n\nfunction formatSchemaCheck(check: NonNullable<TestSuiteResult['models'][0]['schemaCheck']>): void {\n if (check.passed) {\n output.success(` schema: returns expected columns (${check.expectedColumns.join(', ')})`);\n return;\n }\n\n const issues: string[] = [];\n if (check.missingColumns.length > 0) {\n issues.push(`missing: ${check.missingColumns.join(', ')}`);\n }\n for (const m of check.typeMismatches) {\n issues.push(`${m.column}: expected ${m.expected}, got ${m.actual}`);\n }\n output.error(` schema: ${issues.join('; ')}`);\n}\n\nfunction formatAssertion(assertion: TestSuiteResult['models'][0]['assertions'][0]): void {\n const label = extractAssertionLabel(assertion.sql);\n\n if (assertion.warning) {\n output.warning(` ${label}`);\n output.detail(` ${assertion.warning}`);\n }\n\n if (assertion.error) {\n output.error(` test: ${label} -- error`);\n output.detail(` ${assertion.error}`);\n } else if (assertion.passed) {\n output.success(` test: ${label}`);\n } else {\n output.error(` test: ${label} -- ${assertion.violationCount} failing row(s)`);\n if (assertion.sampleViolations) {\n for (const row of assertion.sampleViolations.slice(0, 3)) {\n output.detail(` ${JSON.stringify(row)}`);\n }\n }\n }\n}\n\nfunction extractAssertionLabel(sql: string): string {\n const whereMatch = sql.match(/WHERE\\s+(.+?)$/i);\n if (whereMatch?.[1]) {\n const clause = whereMatch[1].trim();\n return clause.length > 60 ? clause.slice(0, 57) + '...' : clause;\n }\n return sql.length > 60 ? sql.slice(0, 57) + '...' : sql;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,75 @@
1
+ import {
2
+ checkForUpdate
3
+ } from "./chunk-3CLMQNNR.js";
4
+ import {
5
+ detail,
6
+ error,
7
+ info,
8
+ spinner,
9
+ success
10
+ } from "./chunk-HJVVHYVN.js";
11
+
12
+ // src/commands/update.ts
13
+ import { execSync } from "child_process";
14
+ import { readFile, writeFile, mkdir, access } from "fs/promises";
15
+ import { join, dirname } from "path";
16
+ import { fileURLToPath } from "url";
17
+ var __dirname = dirname(fileURLToPath(import.meta.url));
18
+ async function runUpdate(currentVersion) {
19
+ const spin = spinner("Checking for updates...");
20
+ const update = await checkForUpdate(currentVersion);
21
+ spin.stop();
22
+ if (!update) {
23
+ success(`yamchart ${currentVersion} is the latest version`);
24
+ } else {
25
+ info(`Update available: ${update.current} \u2192 ${update.latest}`);
26
+ const installSpin = spinner("Installing yamchart@latest...");
27
+ try {
28
+ execSync("npm install -g yamchart@latest", { stdio: "pipe" });
29
+ installSpin.stop();
30
+ success(`Updated yamchart ${update.current} \u2192 ${update.latest}`);
31
+ } catch {
32
+ installSpin.stop();
33
+ error("Automatic install failed. Run manually:");
34
+ detail("npm install -g yamchart@latest");
35
+ return;
36
+ }
37
+ }
38
+ await refreshDocs();
39
+ }
40
+ async function refreshDocs() {
41
+ const projectFile = join(process.cwd(), "yamchart.yaml");
42
+ try {
43
+ await access(projectFile);
44
+ } catch {
45
+ return;
46
+ }
47
+ const bundled = await readBundledReference();
48
+ if (!bundled) return;
49
+ const refPath = join(process.cwd(), "docs", "yamchart-reference.md");
50
+ try {
51
+ const existing = await readFile(refPath, "utf-8");
52
+ if (existing === bundled) return;
53
+ } catch {
54
+ }
55
+ await mkdir(join(process.cwd(), "docs"), { recursive: true });
56
+ await writeFile(refPath, bundled, "utf-8");
57
+ success("Updated docs/yamchart-reference.md");
58
+ }
59
+ async function readBundledReference() {
60
+ const distPath = join(__dirname, "templates", "default", "docs", "yamchart-reference.md");
61
+ const srcPath = join(__dirname, "..", "templates", "default", "docs", "yamchart-reference.md");
62
+ for (const path of [distPath, srcPath]) {
63
+ try {
64
+ return await readFile(path, "utf-8");
65
+ } catch {
66
+ continue;
67
+ }
68
+ }
69
+ return null;
70
+ }
71
+ export {
72
+ refreshDocs,
73
+ runUpdate
74
+ };
75
+ //# sourceMappingURL=update-QHLCWS56.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/update.ts"],"sourcesContent":["import { execSync } from 'child_process';\nimport { readFile, writeFile, mkdir, access } from 'fs/promises';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport * as output from '../utils/output.js';\nimport { checkForUpdate } from '../utils/update-check.js';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nexport async function runUpdate(currentVersion: string): Promise<void> {\n const spin = output.spinner('Checking for updates...');\n\n const update = await checkForUpdate(currentVersion);\n\n spin.stop();\n\n if (!update) {\n output.success(`yamchart ${currentVersion} is the latest version`);\n } else {\n output.info(`Update available: ${update.current} → ${update.latest}`);\n const installSpin = output.spinner('Installing yamchart@latest...');\n try {\n execSync('npm install -g yamchart@latest', { stdio: 'pipe' });\n installSpin.stop();\n output.success(`Updated yamchart ${update.current} → ${update.latest}`);\n } catch {\n installSpin.stop();\n output.error('Automatic install failed. Run manually:');\n output.detail('npm install -g yamchart@latest');\n return;\n }\n }\n\n // Refresh project docs if we're in a yamchart project\n await refreshDocs();\n}\n\n/**\n * Refresh docs/yamchart-reference.md from the bundled template.\n * Only runs when yamchart.yaml exists in the current directory.\n */\nexport async function refreshDocs(): Promise<void> {\n const projectFile = join(process.cwd(), 'yamchart.yaml');\n try {\n await access(projectFile);\n } catch {\n return; // Not in a yamchart project\n }\n\n const bundled = await readBundledReference();\n if (!bundled) return;\n\n const refPath = join(process.cwd(), 'docs', 'yamchart-reference.md');\n\n try {\n const existing = await readFile(refPath, 'utf-8');\n if (existing === bundled) return; // Already up to date\n } catch {\n // File doesn't exist — create it\n }\n\n await mkdir(join(process.cwd(), 'docs'), { recursive: true });\n await writeFile(refPath, bundled, 'utf-8');\n output.success('Updated docs/yamchart-reference.md');\n}\n\nasync function readBundledReference(): Promise<string | null> {\n // Production: dist/templates/default/docs/yamchart-reference.md\n const distPath = join(__dirname, 'templates', 'default', 'docs', 'yamchart-reference.md');\n // Development: src/commands → src/templates\n const srcPath = join(__dirname, '..', 'templates', 'default', 'docs', 'yamchart-reference.md');\n\n for (const path of [distPath, srcPath]) {\n try {\n return await readFile(path, 'utf-8');\n } catch {\n continue;\n }\n }\n return null;\n}\n"],"mappings":";;;;;;;;;;;;AAAA,SAAS,gBAAgB;AACzB,SAAS,UAAU,WAAW,OAAO,cAAc;AACnD,SAAS,MAAM,eAAe;AAC9B,SAAS,qBAAqB;AAI9B,IAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AAExD,eAAsB,UAAU,gBAAuC;AACrE,QAAM,OAAc,QAAQ,yBAAyB;AAErD,QAAM,SAAS,MAAM,eAAe,cAAc;AAElD,OAAK,KAAK;AAEV,MAAI,CAAC,QAAQ;AACX,IAAO,QAAQ,YAAY,cAAc,wBAAwB;AAAA,EACnE,OAAO;AACL,IAAO,KAAK,qBAAqB,OAAO,OAAO,WAAM,OAAO,MAAM,EAAE;AACpE,UAAM,cAAqB,QAAQ,+BAA+B;AAClE,QAAI;AACF,eAAS,kCAAkC,EAAE,OAAO,OAAO,CAAC;AAC5D,kBAAY,KAAK;AACjB,MAAO,QAAQ,oBAAoB,OAAO,OAAO,WAAM,OAAO,MAAM,EAAE;AAAA,IACxE,QAAQ;AACN,kBAAY,KAAK;AACjB,MAAO,MAAM,yCAAyC;AACtD,MAAO,OAAO,gCAAgC;AAC9C;AAAA,IACF;AAAA,EACF;AAGA,QAAM,YAAY;AACpB;AAMA,eAAsB,cAA6B;AACjD,QAAM,cAAc,KAAK,QAAQ,IAAI,GAAG,eAAe;AACvD,MAAI;AACF,UAAM,OAAO,WAAW;AAAA,EAC1B,QAAQ;AACN;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,qBAAqB;AAC3C,MAAI,CAAC,QAAS;AAEd,QAAM,UAAU,KAAK,QAAQ,IAAI,GAAG,QAAQ,uBAAuB;AAEnE,MAAI;AACF,UAAM,WAAW,MAAM,SAAS,SAAS,OAAO;AAChD,QAAI,aAAa,QAAS;AAAA,EAC5B,QAAQ;AAAA,EAER;AAEA,QAAM,MAAM,KAAK,QAAQ,IAAI,GAAG,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5D,QAAM,UAAU,SAAS,SAAS,OAAO;AACzC,EAAO,QAAQ,oCAAoC;AACrD;AAEA,eAAe,uBAA+C;AAE5D,QAAM,WAAW,KAAK,WAAW,aAAa,WAAW,QAAQ,uBAAuB;AAExF,QAAM,UAAU,KAAK,WAAW,MAAM,aAAa,WAAW,QAAQ,uBAAuB;AAE7F,aAAW,QAAQ,CAAC,UAAU,OAAO,GAAG;AACtC,QAAI;AACF,aAAO,MAAM,SAAS,MAAM,OAAO;AAAA,IACrC,QAAQ;AACN;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yamchart",
3
- "version": "0.1.2",
3
+ "version": "0.3.1",
4
4
  "description": "Git-native business intelligence dashboards",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -33,14 +33,19 @@
33
33
  }
34
34
  },
35
35
  "dependencies": {
36
+ "@inquirer/prompts": "^8.2.0",
36
37
  "commander": "^12.1.0",
37
38
  "dotenv": "^16.4.0",
39
+ "fast-glob": "^3.3.3",
40
+ "minimatch": "^10.1.2",
38
41
  "ora": "^8.0.0",
39
42
  "picocolors": "^1.1.0",
40
43
  "yaml": "^2.7.0",
44
+ "zod": "^3.24.0",
45
+ "@yamchart/auth-local": "0.1.0",
41
46
  "@yamchart/query": "0.1.2",
42
- "@yamchart/schema": "0.1.2",
43
- "@yamchart/server": "0.1.2"
47
+ "@yamchart/server": "0.1.2",
48
+ "@yamchart/schema": "0.1.2"
44
49
  },
45
50
  "devDependencies": {
46
51
  "@types/node": "^22.0.0",