yamchart 0.7.2 → 0.8.0

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 (74) hide show
  1. package/CLAUDE.md +51 -0
  2. package/dist/{advisor-5BTRAICH.js → advisor-57BOMUUF.js} +11 -10
  3. package/dist/{advisor-5BTRAICH.js.map → advisor-57BOMUUF.js.map} +1 -1
  4. package/dist/{chunk-5VA43CTW.js → chunk-E2UZXDF6.js} +37 -42
  5. package/dist/chunk-E2UZXDF6.js.map +1 -0
  6. package/dist/{chunk-SSUVEADJ.js → chunk-JYQKDWLG.js} +76 -14
  7. package/dist/chunk-JYQKDWLG.js.map +1 -0
  8. package/dist/{chunk-565OEBW7.js → chunk-OTAUP5RC.js} +3 -3
  9. package/dist/chunk-UND73EOB.js +449 -0
  10. package/dist/chunk-UND73EOB.js.map +1 -0
  11. package/dist/{chunk-UDRJFEJU.js → chunk-X6LQGWUX.js} +349 -176
  12. package/dist/chunk-X6LQGWUX.js.map +1 -0
  13. package/dist/{connection-utils-AGSQ5HNN.js → connection-utils-FTSZU2VF.js} +5 -4
  14. package/dist/{describe-AA4UT4U6.js → describe-TGIOBNJB.js} +5 -4
  15. package/dist/{describe-AA4UT4U6.js.map → describe-TGIOBNJB.js.map} +1 -1
  16. package/dist/{dev-EUWIRL4N.js → dev-MRZ76V74.js} +485 -44
  17. package/dist/dev-MRZ76V74.js.map +1 -0
  18. package/dist/{dist-LZNDQDH3.js → dist-PVHFUYP2.js} +23 -3
  19. package/dist/{dist-YTGUIBKG.js → dist-WDTDQDTX.js} +72 -1
  20. package/dist/dist-WDTDQDTX.js.map +1 -0
  21. package/dist/index.js +135 -17
  22. package/dist/index.js.map +1 -1
  23. package/dist/{init-CI4VARQG.js → init-UYQE5DPU.js} +2 -2
  24. package/dist/{init-CI4VARQG.js.map → init-UYQE5DPU.js.map} +1 -1
  25. package/dist/public/assets/EventManagement-BlxJ2TFw.js +18 -0
  26. package/dist/public/assets/{LoginPage-BwMmpq8e.js → LoginPage-BT8ikmPn.js} +1 -1
  27. package/dist/public/assets/PublicViewer-B4uFxgbt.js +1 -0
  28. package/dist/public/assets/{SetupWizard-DU8R2dPp.js → SetupWizard-njrOhCzw.js} +1 -1
  29. package/dist/public/assets/ShareManagement-Bg16oJhW.js +1 -0
  30. package/dist/public/assets/UserManagement-tiCIT4UY.js +1 -0
  31. package/dist/public/assets/index-B41yj3io.js +187 -0
  32. package/dist/public/assets/{index-C0hWblBI.css → index-BZ25r23j.css} +1 -1
  33. package/dist/public/assets/{index.es-YeJujQ5o.js → index.es-BuktD_R2.js} +1 -1
  34. package/dist/public/assets/{jspdf.es.min-BzZgn3ka.js → jspdf.es.min-DFRl2hZQ.js} +3 -3
  35. package/dist/public/index.html +2 -2
  36. package/dist/{query-THDVBBVZ.js → query-JRMMNXX6.js} +5 -4
  37. package/dist/{query-THDVBBVZ.js.map → query-JRMMNXX6.js.map} +1 -1
  38. package/dist/{sample-6WPQS7PA.js → sample-VGIY4U4J.js} +5 -4
  39. package/dist/{sample-6WPQS7PA.js.map → sample-VGIY4U4J.js.map} +1 -1
  40. package/dist/{search-657DXBRS.js → search-QSYNG4SR.js} +5 -4
  41. package/dist/{search-657DXBRS.js.map → search-QSYNG4SR.js.map} +1 -1
  42. package/dist/semantic-RAP3S5PQ.js +39 -0
  43. package/dist/semantic-RAP3S5PQ.js.map +1 -0
  44. package/dist/{sync-warehouse-4KBV5S3L.js → sync-warehouse-XHTBZH25.js} +5 -4
  45. package/dist/{sync-warehouse-4KBV5S3L.js.map → sync-warehouse-XHTBZH25.js.map} +1 -1
  46. package/dist/{tables-KLDBUUSE.js → tables-UOO342TA.js} +5 -4
  47. package/dist/{tables-KLDBUUSE.js.map → tables-UOO342TA.js.map} +1 -1
  48. package/dist/templates/default/.claude/skills/databricks/SKILL.md +76 -0
  49. package/dist/templates/default/.claude/skills/dbt-advisor/SKILL.md +107 -0
  50. package/dist/templates/default/.claude/skills/duckdb/SKILL.md +67 -0
  51. package/dist/templates/default/.claude/skills/mysql/SKILL.md +69 -0
  52. package/dist/templates/default/.claude/skills/postgres/SKILL.md +69 -0
  53. package/dist/templates/default/.claude/skills/snowflake/SKILL.md +64 -0
  54. package/dist/templates/default/CLAUDE.md +7 -0
  55. package/dist/templates/default/docs/yamchart-reference.md +222 -1
  56. package/dist/{test-JSAWS5ZP.js → test-TXRZWNXK.js} +5 -4
  57. package/dist/{test-JSAWS5ZP.js.map → test-TXRZWNXK.js.map} +1 -1
  58. package/dist/update-UKMEWCSO.js +220 -0
  59. package/dist/update-UKMEWCSO.js.map +1 -0
  60. package/package.json +5 -3
  61. package/dist/chunk-5VA43CTW.js.map +0 -1
  62. package/dist/chunk-SSUVEADJ.js.map +0 -1
  63. package/dist/chunk-UDRJFEJU.js.map +0 -1
  64. package/dist/dev-EUWIRL4N.js.map +0 -1
  65. package/dist/dist-YTGUIBKG.js.map +0 -1
  66. package/dist/public/assets/PublicViewer-vIDojjIR.js +0 -1
  67. package/dist/public/assets/ShareManagement-7iS6lM2T.js +0 -1
  68. package/dist/public/assets/UserManagement-By7YRZrF.js +0 -1
  69. package/dist/public/assets/index-CXx1PiRF.js +0 -174
  70. package/dist/update-HCR6MYJX.js +0 -88
  71. package/dist/update-HCR6MYJX.js.map +0 -1
  72. /package/dist/{chunk-565OEBW7.js.map → chunk-OTAUP5RC.js.map} +0 -0
  73. /package/dist/{connection-utils-AGSQ5HNN.js.map → connection-utils-FTSZU2VF.js.map} +0 -0
  74. /package/dist/{dist-LZNDQDH3.js.map → dist-PVHFUYP2.js.map} +0 -0
@@ -71,6 +71,10 @@ yamchart sync-warehouse --json # Output sync summary as JSON
71
71
  yamchart lineage <model> # Show upstream dependency tree
72
72
  yamchart lineage <model> --depth 2 # Limit recursion depth
73
73
  yamchart lineage <model> --json # JSON output
74
+ yamchart semantic # List semantic layer entities
75
+ yamchart semantic --json # JSON output
76
+ yamchart semantic query -e <entity> -m <measures> # Structured query
77
+ yamchart semantic query -e <entity> -m <measures> --sql # SQL only
74
78
  ```
75
79
 
76
80
  ## SQL Models (`models/*.sql`)
@@ -264,6 +268,28 @@ Column headers are automatically humanized (`total_revenue` → "Total Revenue")
264
268
  **Summary row:** Per-column `summary` with `sum`, `avg`, `min`, `max`, or `count`. Computed client-side, formatted with the column's `format`, sticky at the bottom.
265
269
  **Default sort:** `sort: { field: revenue, direction: desc }` sets the initial sort column and direction. Users can still click headers to change sorting interactively.
266
270
 
271
+ **Pivot table** — transform flat query results into grouped, pivoted tables with subtotals and heatmap coloring:
272
+ ```yaml
273
+ chart:
274
+ type: table
275
+ pivot:
276
+ rows: [plan, event] # Row grouping fields (first = group, rest = detail)
277
+ columns: quarter # Field whose distinct values become column headers
278
+ values:
279
+ - field: event_count
280
+ aggregate: sum # sum | avg | min | max | count (default: sum)
281
+ format: "$" # Optional number format
282
+ heatmap: true # Enable green heatmap coloring (optional, off by default)
283
+ subtotals: true # Per-group subtotal rows (default: true)
284
+ grandTotals: true # Grand total row (default: true)
285
+ heatmap:
286
+ color: "#4CAF50" # Heatmap color (default: green)
287
+ scope: column # column | global — scale per-column or across all
288
+ ```
289
+
290
+ The SQL model should return flat rows with all fields (e.g. `SELECT plan, event, quarter, count(*) as event_count FROM events GROUP BY plan, event, quarter`). Yamchart pivots client-side.
291
+ Row groups are collapsible (click to toggle). Heatmap coloring auto-scales per column by default.
292
+
267
293
  **Combo** — mixed types with dual y-axes:
268
294
  ```yaml
269
295
  chart:
@@ -357,7 +383,7 @@ parameters:
357
383
 
358
384
  ### Drill-Down
359
385
 
360
- Right-click any chart element to navigate to a related detail chart with filtered data.
386
+ Click any chart element to open a context menu with options to cross-filter, view detail data, or navigate to a related chart.
361
387
 
362
388
  ```yaml
363
389
  drillDown:
@@ -772,6 +798,77 @@ fct_revenue
772
798
 
773
799
  **Note:** dbt lineage data requires `sync-dbt` with a dbt project that has run `dbt compile` or `dbt run` (which generates `target/manifest.json`). If the manifest is missing, `sync-dbt` will warn you. Yamchart model lineage works independently by parsing SQL files directly.
774
800
 
801
+ ## Semantic Layer
802
+
803
+ The semantic layer auto-generates structured query entities from your catalog. It classifies columns as measures (numeric, aggregatable) or dimensions (categorical, temporal), enabling structured queries without writing SQL.
804
+
805
+ ### How It Works
806
+
807
+ 1. Run `yamchart sync-dbt` or `yamchart sync-warehouse` to populate `.yamchart/catalog.json`
808
+ 2. The semantic layer automatically builds entities from gold/marts/core tables (or tables prefixed with `dim_`, `fct_`, `fact_`)
809
+ 3. Query entities using the CLI or the AI advisor
810
+
811
+ ### Configuration
812
+
813
+ Optional in `yamchart.yaml`:
814
+
815
+ ```yaml
816
+ semantic:
817
+ include:
818
+ paths: [gold/, marts/, core/] # Catalog paths to include (default)
819
+ prefixes: [dim_, fct_, fact_] # Table name prefixes to include (default)
820
+ ```
821
+
822
+ ### CLI Commands
823
+
824
+ ```bash
825
+ yamchart semantic # List all semantic entities
826
+ yamchart semantic --json # JSON output
827
+
828
+ yamchart semantic query -e fct_revenue -m revenue,order_count -d region
829
+ yamchart semantic query -e fct_revenue -m revenue -f region=US --limit 50
830
+ yamchart semantic query -e fct_revenue -m revenue -d region --order revenue --desc
831
+ yamchart semantic query -e fct_revenue -m revenue --sql # Print SQL only
832
+ yamchart semantic query -e fct_revenue -m revenue --json # JSON output
833
+ ```
834
+
835
+ | Option | Description |
836
+ |--------|-------------|
837
+ | `-e, --entity <name>` | Entity name (required) |
838
+ | `-m, --measures <m1,m2>` | Comma-separated measures (required) |
839
+ | `-d, --dimensions <d1,d2>` | Comma-separated dimensions |
840
+ | `-f, --filter <dim=value>` | Filter (repeatable) |
841
+ | `--order <field>` | Order by field |
842
+ | `--desc` | Descending order |
843
+ | `-l, --limit <n>` | Row limit (default: 100) |
844
+ | `-c, --connection <name>` | Override connection |
845
+ | `--sql` | Print compiled SQL without executing |
846
+ | `--json` | JSON output |
847
+
848
+ ### Supported Aggregations
849
+
850
+ | Type | SQL Generated |
851
+ |------|--------------|
852
+ | `sum` | `SUM("column")` |
853
+ | `count` | `COUNT("column")` |
854
+ | `avg` | `AVG("column")` |
855
+ | `min` | `MIN("column")` |
856
+ | `max` | `MAX("column")` |
857
+ | `count_distinct` | `COUNT(DISTINCT "column")` |
858
+
859
+ ### Advisor Integration
860
+
861
+ The AI advisor can discover and query the semantic layer:
862
+
863
+ - **`list_semantic_entities`** — Lists all entities with measures and dimensions
864
+ - **`semantic_query`** — Compiles a structured query to SQL
865
+
866
+ ```bash
867
+ yamchart advisor "What is total revenue by region?"
868
+ ```
869
+
870
+ The advisor uses `list_semantic_entities` to discover available fields, then `semantic_query` to compile and execute the query.
871
+
775
872
  ## Axis Types
776
873
 
777
874
  | Type | Use | Example |
@@ -781,6 +878,130 @@ fct_revenue
781
878
  | `nominal` | Unordered categories | `Electronics` |
782
879
  | `quantitative` | Numbers | `250.5` |
783
880
 
881
+ ## Chart Events (`events.yaml`)
882
+
883
+ Annotate time-series charts with notable dates — product launches, outages, campaigns, etc. Events appear as dashed vertical lines (point events) or shaded regions (range events) on line, bar, area, and combo charts.
884
+
885
+ Create `events.yaml` in your project root:
886
+
887
+ ```yaml
888
+ events:
889
+ - date: "2025-01-15"
890
+ label: "V2 Launch"
891
+ description: "Shipped new pricing page"
892
+ color: "#10b981"
893
+
894
+ - date: "2025-02-01"
895
+ endDate: "2025-02-14"
896
+ label: "Promo Campaign"
897
+ color: "#3b82f6"
898
+
899
+ - date: "2025-03-01"
900
+ label: "Outage"
901
+ charts: [revenue-trend, orders-trend] # Scope to specific charts
902
+ ```
903
+
904
+ | Field | Required | Description |
905
+ |-------|----------|-------------|
906
+ | `date` | Yes | Event start date (ISO format) |
907
+ | `endDate` | No | Event end date — creates a shaded range instead of a line |
908
+ | `label` | Yes | Short label shown on the chart |
909
+ | `description` | No | Longer description shown in tooltip and management UI |
910
+ | `color` | No | Hex color for the line/region (default: theme text color) |
911
+ | `charts` | No | Array of chart names — limits which charts show this event. Omit for all charts. |
912
+
913
+ **Point events** (no `endDate`) render as dashed vertical lines. **Range events** (with `endDate`) render as semi-transparent shaded regions.
914
+
915
+ **Management UI:** Access via the events icon in the top bar. Add, edit, and delete events with inline editing, date pickers, and chart scope selection.
916
+
917
+ **Date clamping:** Events outside the chart's visible date range are automatically clamped to the chart boundaries, so a range extending beyond the data still renders correctly.
918
+
919
+ ## Chart Goals
920
+
921
+ Add target lines, cumulative targets, or model-driven goal lines to line, bar, area, and combo charts. KPI and gauge charts support simple target values with progress display.
922
+
923
+ ### Fixed Goal — constant horizontal target line
924
+
925
+ ```yaml
926
+ chart:
927
+ type: bar
928
+ x: { field: month, type: temporal }
929
+ y: { field: revenue, format: "$,.0f" }
930
+ goals:
931
+ - type: fixed
932
+ value: 100000
933
+ label: "Monthly Target"
934
+ color: "#22c55e"
935
+ style: dashed # solid, dashed, or dotted
936
+ zone:
937
+ tolerance: 0.10 # Shaded band at ±10% of target
938
+ ```
939
+
940
+ ### Cumulative Goal — target value to reach by a deadline
941
+
942
+ ```yaml
943
+ goals:
944
+ - type: cumulative
945
+ value: 1000000 # Target to reach
946
+ by: "2025-12-31" # Deadline date
947
+ from: "2025-01-01" # Optional start date (defaults to first data point)
948
+ label: "Annual Target"
949
+ color: "#f59e0b"
950
+ style: dashed
951
+ zone:
952
+ tolerance: 0.05
953
+ ```
954
+
955
+ Renders as a diagonal line from `from` (or chart start) to `by`, showing the ideal pace toward the target.
956
+
957
+ ### Model Goal — target values from a SQL model
958
+
959
+ ```yaml
960
+ goals:
961
+ - type: model
962
+ source:
963
+ model: revenue_targets # SQL model name
964
+ value_field: target # Column with target values
965
+ date_field: month # Optional date column (aligns to x-axis)
966
+ label: "Plan"
967
+ color: "#8b5cf6"
968
+ style: dotted
969
+ zone:
970
+ tolerance: 0.10
971
+ ```
972
+
973
+ The model is queried alongside the chart data. Each row provides a target value, plotted as a goal line that can vary over time.
974
+
975
+ ### KPI / Gauge Goal — target value with progress
976
+
977
+ ```yaml
978
+ # KPI chart
979
+ chart:
980
+ type: kpi
981
+ value: { field: revenue }
982
+ format: { type: currency, currency: USD }
983
+ goal:
984
+ value: 500000
985
+ label: "Q1 Target"
986
+ show_progress: true # Shows progress bar and percentage
987
+ color: "#22c55e"
988
+
989
+ # Gauge chart
990
+ chart:
991
+ type: gauge
992
+ value: { field: score }
993
+ min: 0
994
+ max: 100
995
+ goal:
996
+ value: 85
997
+ label: "Target"
998
+ show_progress: true
999
+ ```
1000
+
1001
+ ### Goal Zones
1002
+
1003
+ All goal types (fixed, cumulative, model) support a `zone` with `tolerance` (0–1). This renders a semi-transparent shaded band around the goal line at ±tolerance of the target value. For example, `tolerance: 0.10` on a target of 100,000 shades the 90,000–110,000 range.
1004
+
784
1005
  ## Number Formats (d3-format)
785
1006
 
786
1007
  | Format | Output | Use |
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createConnector,
3
3
  resolveConnection
4
- } from "./chunk-565OEBW7.js";
4
+ } from "./chunk-OTAUP5RC.js";
5
5
  import {
6
6
  detail,
7
7
  error,
@@ -10,7 +10,7 @@ import {
10
10
  success,
11
11
  warning
12
12
  } from "./chunk-HJVVHYVN.js";
13
- import "./chunk-UDRJFEJU.js";
13
+ import "./chunk-X6LQGWUX.js";
14
14
  import {
15
15
  createTemplateContext,
16
16
  expandDatePreset,
@@ -18,7 +18,8 @@ import {
18
18
  parseModelMetadata,
19
19
  renderTemplate,
20
20
  runAll
21
- } from "./chunk-SSUVEADJ.js";
21
+ } from "./chunk-JYQKDWLG.js";
22
+ import "./chunk-UND73EOB.js";
22
23
  import "./chunk-DGUM43GV.js";
23
24
 
24
25
  // src/commands/test.ts
@@ -191,4 +192,4 @@ export {
191
192
  formatTestOutput,
192
193
  testProject
193
194
  };
194
- //# sourceMappingURL=test-JSAWS5ZP.js.map
195
+ //# sourceMappingURL=test-TXRZWNXK.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/commands/test.ts"],"sourcesContent":["import { readFile, readdir } from 'fs/promises';\nimport { join, extname } from 'path';\nimport {\n parseModelMetadata,\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';\nimport { resolveConnection, createConnector } from './connection-utils.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 connection = await resolveConnection(projectDir, options.connection);\n const connector = createConnector(connection, projectDir);\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 try {\n await connector.connect();\n const suite = await runAll(testInputs, connector);\n return { success: suite.failed === 0, suite, connectionName: connection.name };\n } finally {\n await connector.disconnect();\n }\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":[]}
1
+ {"version":3,"sources":["../src/commands/test.ts"],"sourcesContent":["import { readFile, readdir } from 'fs/promises';\nimport { join, extname } from 'path';\nimport {\n parseModelMetadata,\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';\nimport { resolveConnection, createConnector } from './connection-utils.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 connection = await resolveConnection(projectDir, options.connection);\n const connector = createConnector(connection, projectDir);\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 try {\n await connector.connect();\n const suite = await runAll(testInputs, connector);\n return { success: suite.failed === 0, suite, connectionName: connection.name };\n } finally {\n await connector.disconnect();\n }\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,220 @@
1
+ import {
2
+ checkForUpdate,
3
+ fetchReleaseNotes
4
+ } from "./chunk-F6QAWPYU.js";
5
+ import {
6
+ detail,
7
+ error,
8
+ header,
9
+ info,
10
+ newline,
11
+ spinner,
12
+ success
13
+ } from "./chunk-HJVVHYVN.js";
14
+ import "./chunk-DGUM43GV.js";
15
+
16
+ // src/commands/update.ts
17
+ import { execSync } from "child_process";
18
+ import { createInterface } from "readline";
19
+ import { readFile, writeFile, mkdir, access, readdir } from "fs/promises";
20
+ import { join, dirname, basename } from "path";
21
+ import { fileURLToPath } from "url";
22
+ import { parse as parseYaml } from "yaml";
23
+ var __dirname = dirname(fileURLToPath(import.meta.url));
24
+ async function runUpdate(currentVersion) {
25
+ const spin = spinner("Checking for updates...");
26
+ const update = await checkForUpdate(currentVersion, { skipCache: true });
27
+ spin.stop();
28
+ if (!update) {
29
+ success(`yamchart ${currentVersion} is the latest version`);
30
+ } else {
31
+ info(`Update available: ${update.current} \u2192 ${update.latest}`);
32
+ const notes = await fetchReleaseNotes(update.latest);
33
+ if (notes) {
34
+ newline();
35
+ header(`What's new in ${update.latest}:`);
36
+ for (const line of notes.split("\n")) {
37
+ detail(line);
38
+ }
39
+ newline();
40
+ }
41
+ const installSpin = spinner("Installing yamchart@latest...");
42
+ try {
43
+ execSync("npm install -g yamchart@latest", { stdio: "pipe" });
44
+ installSpin.stop();
45
+ success(`Updated yamchart ${update.current} \u2192 ${update.latest}`);
46
+ } catch {
47
+ installSpin.stop();
48
+ error("Automatic install failed. Run manually:");
49
+ detail("npm install -g yamchart@latest");
50
+ return;
51
+ }
52
+ }
53
+ await refreshProjectFiles();
54
+ }
55
+ async function refreshDocs() {
56
+ const projectFile = join(process.cwd(), "yamchart.yaml");
57
+ try {
58
+ await access(projectFile);
59
+ } catch {
60
+ return;
61
+ }
62
+ const bundled = await readBundledReference();
63
+ if (!bundled) return;
64
+ const refPath = join(process.cwd(), "docs", "yamchart-reference.md");
65
+ try {
66
+ const existing = await readFile(refPath, "utf-8");
67
+ if (existing === bundled) return;
68
+ } catch {
69
+ }
70
+ await mkdir(join(process.cwd(), "docs"), { recursive: true });
71
+ await writeFile(refPath, bundled, "utf-8");
72
+ success("Updated docs/yamchart-reference.md");
73
+ }
74
+ async function readBundledReference() {
75
+ const distPath = join(__dirname, "templates", "default", "docs", "yamchart-reference.md");
76
+ const srcPath = join(__dirname, "..", "templates", "default", "docs", "yamchart-reference.md");
77
+ for (const path of [distPath, srcPath]) {
78
+ try {
79
+ return await readFile(path, "utf-8");
80
+ } catch {
81
+ continue;
82
+ }
83
+ }
84
+ return null;
85
+ }
86
+ async function refreshProjectFiles() {
87
+ const projectFile = join(process.cwd(), "yamchart.yaml");
88
+ try {
89
+ await access(projectFile);
90
+ } catch {
91
+ return;
92
+ }
93
+ await refreshDocs();
94
+ await refreshSkills();
95
+ await refreshClaudeMd();
96
+ }
97
+ async function refreshSkills() {
98
+ try {
99
+ await access(join(process.cwd(), "yamchart.yaml"));
100
+ } catch {
101
+ return;
102
+ }
103
+ const bundledDir = await getBundledSkillsDir();
104
+ if (!bundledDir) return;
105
+ let entries;
106
+ try {
107
+ entries = await readdir(bundledDir, { withFileTypes: true });
108
+ } catch {
109
+ return;
110
+ }
111
+ let updated = 0;
112
+ let created = 0;
113
+ for (const entry of entries) {
114
+ if (!entry.isDirectory()) continue;
115
+ const skillFile = join(bundledDir, entry.name, "SKILL.md");
116
+ let bundledContent;
117
+ try {
118
+ bundledContent = await readFile(skillFile, "utf-8");
119
+ } catch {
120
+ continue;
121
+ }
122
+ const targetDir = join(process.cwd(), ".claude", "skills", entry.name);
123
+ const targetFile = join(targetDir, "SKILL.md");
124
+ let isNew = false;
125
+ try {
126
+ const existing = await readFile(targetFile, "utf-8");
127
+ if (existing === bundledContent) continue;
128
+ } catch {
129
+ isNew = true;
130
+ }
131
+ await mkdir(targetDir, { recursive: true });
132
+ await writeFile(targetFile, bundledContent, "utf-8");
133
+ if (isNew) created++;
134
+ else updated++;
135
+ }
136
+ if (created > 0 || updated > 0) {
137
+ const parts = [];
138
+ if (created > 0) parts.push(`${created} new`);
139
+ if (updated > 0) parts.push(`${updated} updated`);
140
+ success(`Synced .claude/skills/ (${parts.join(", ")})`);
141
+ }
142
+ }
143
+ async function refreshClaudeMd(options = {}) {
144
+ try {
145
+ await access(join(process.cwd(), "yamchart.yaml"));
146
+ } catch {
147
+ return;
148
+ }
149
+ const bundled = await readBundledFile("CLAUDE.md");
150
+ if (!bundled) return;
151
+ const projectName = await getProjectName();
152
+ const content = bundled.replace(/\{\{name\}\}/g, projectName);
153
+ const targetPath = join(process.cwd(), "CLAUDE.md");
154
+ try {
155
+ const existing = await readFile(targetPath, "utf-8");
156
+ if (existing === content) return;
157
+ const confirmFn = options.confirm ?? promptConfirm;
158
+ const shouldOverwrite = await confirmFn(
159
+ "CLAUDE.md differs from the current template. Overwrite? (y/n) "
160
+ );
161
+ if (!shouldOverwrite) {
162
+ info("Skipped CLAUDE.md update");
163
+ return;
164
+ }
165
+ } catch {
166
+ }
167
+ await writeFile(targetPath, content, "utf-8");
168
+ success("Updated CLAUDE.md");
169
+ }
170
+ async function getBundledSkillsDir() {
171
+ const distPath = join(__dirname, "templates", "default", ".claude", "skills");
172
+ const srcPath = join(__dirname, "..", "templates", "default", ".claude", "skills");
173
+ for (const path of [distPath, srcPath]) {
174
+ try {
175
+ await access(path);
176
+ return path;
177
+ } catch {
178
+ continue;
179
+ }
180
+ }
181
+ return null;
182
+ }
183
+ async function readBundledFile(relativePath) {
184
+ const distPath = join(__dirname, "templates", "default", relativePath);
185
+ const srcPath = join(__dirname, "..", "templates", "default", relativePath);
186
+ for (const path of [distPath, srcPath]) {
187
+ try {
188
+ return await readFile(path, "utf-8");
189
+ } catch {
190
+ continue;
191
+ }
192
+ }
193
+ return null;
194
+ }
195
+ async function getProjectName() {
196
+ try {
197
+ const raw = await readFile(join(process.cwd(), "yamchart.yaml"), "utf-8");
198
+ const parsed = parseYaml(raw);
199
+ if (parsed?.name) return parsed.name;
200
+ } catch {
201
+ }
202
+ return basename(process.cwd());
203
+ }
204
+ async function promptConfirm(message) {
205
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
206
+ return new Promise((resolve) => {
207
+ rl.question(message, (answer) => {
208
+ rl.close();
209
+ resolve(answer.trim().toLowerCase() === "y");
210
+ });
211
+ });
212
+ }
213
+ export {
214
+ refreshClaudeMd,
215
+ refreshDocs,
216
+ refreshProjectFiles,
217
+ refreshSkills,
218
+ runUpdate
219
+ };
220
+ //# sourceMappingURL=update-UKMEWCSO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/update.ts"],"sourcesContent":["import { execSync } from 'child_process';\nimport { createInterface } from 'readline';\nimport { readFile, writeFile, mkdir, access, readdir } from 'fs/promises';\nimport { join, dirname, basename } from 'path';\nimport { fileURLToPath } from 'url';\nimport { parse as parseYaml } from 'yaml';\nimport * as output from '../utils/output.js';\nimport { checkForUpdate, fetchReleaseNotes } 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, { skipCache: true });\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\n // Fetch and display release notes\n const notes = await fetchReleaseNotes(update.latest);\n if (notes) {\n output.newline();\n output.header(`What's new in ${update.latest}:`);\n for (const line of notes.split('\\n')) {\n output.detail(line);\n }\n output.newline();\n }\n\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 files if we're in a yamchart project\n await refreshProjectFiles();\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\n/**\n * Refresh all managed project files (docs, skills, CLAUDE.md).\n * Only runs when yamchart.yaml exists in the current directory.\n */\nexport async function refreshProjectFiles(): 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 await refreshDocs();\n await refreshSkills();\n await refreshClaudeMd();\n}\n\n/**\n * Refresh .claude/skills/ from bundled templates.\n * Overwrites bundled skills (managed by yamchart), leaves user-created skills alone.\n * Only runs when yamchart.yaml exists in the current directory.\n */\nexport async function refreshSkills(): Promise<void> {\n try {\n await access(join(process.cwd(), 'yamchart.yaml'));\n } catch {\n return;\n }\n\n const bundledDir = await getBundledSkillsDir();\n if (!bundledDir) return;\n\n let entries: Awaited<ReturnType<typeof readdir>>;\n try {\n entries = await readdir(bundledDir, { withFileTypes: true });\n } catch {\n return;\n }\n\n let updated = 0;\n let created = 0;\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n\n const skillFile = join(bundledDir, entry.name, 'SKILL.md');\n let bundledContent: string;\n try {\n bundledContent = await readFile(skillFile, 'utf-8');\n } catch {\n continue; // Skip directories without SKILL.md\n }\n\n const targetDir = join(process.cwd(), '.claude', 'skills', entry.name);\n const targetFile = join(targetDir, 'SKILL.md');\n\n let isNew = false;\n try {\n const existing = await readFile(targetFile, 'utf-8');\n if (existing === bundledContent) continue; // Already up to date\n } catch {\n isNew = true; // File doesn't exist\n }\n\n await mkdir(targetDir, { recursive: true });\n await writeFile(targetFile, bundledContent, 'utf-8');\n if (isNew) created++;\n else updated++;\n }\n\n if (created > 0 || updated > 0) {\n const parts: string[] = [];\n if (created > 0) parts.push(`${created} new`);\n if (updated > 0) parts.push(`${updated} updated`);\n output.success(`Synced .claude/skills/ (${parts.join(', ')})`);\n }\n}\n\n/**\n * Refresh CLAUDE.md from bundled template.\n * If the file exists and differs, prompts the user before overwriting.\n * Only runs when yamchart.yaml exists in the current directory.\n */\nexport async function refreshClaudeMd(\n options: { confirm?: (message: string) => Promise<boolean> } = {}\n): Promise<void> {\n try {\n await access(join(process.cwd(), 'yamchart.yaml'));\n } catch {\n return;\n }\n\n const bundled = await readBundledFile('CLAUDE.md');\n if (!bundled) return;\n\n // Substitute {{name}} with project name from yamchart.yaml\n const projectName = await getProjectName();\n const content = bundled.replace(/\\{\\{name\\}\\}/g, projectName);\n\n const targetPath = join(process.cwd(), 'CLAUDE.md');\n\n try {\n const existing = await readFile(targetPath, 'utf-8');\n if (existing === content) return; // Already up to date\n\n // File exists and differs — prompt user\n const confirmFn = options.confirm ?? promptConfirm;\n const shouldOverwrite = await confirmFn(\n 'CLAUDE.md differs from the current template. Overwrite? (y/n) '\n );\n if (!shouldOverwrite) {\n output.info('Skipped CLAUDE.md update');\n return;\n }\n } catch {\n // File doesn't exist — create without prompting\n }\n\n await writeFile(targetPath, content, 'utf-8');\n output.success('Updated CLAUDE.md');\n}\n\nasync function getBundledSkillsDir(): Promise<string | null> {\n const distPath = join(__dirname, 'templates', 'default', '.claude', 'skills');\n const srcPath = join(__dirname, '..', 'templates', 'default', '.claude', 'skills');\n\n for (const path of [distPath, srcPath]) {\n try {\n await access(path);\n return path;\n } catch {\n continue;\n }\n }\n return null;\n}\n\nasync function readBundledFile(relativePath: string): Promise<string | null> {\n const distPath = join(__dirname, 'templates', 'default', relativePath);\n const srcPath = join(__dirname, '..', 'templates', 'default', relativePath);\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\nasync function getProjectName(): Promise<string> {\n try {\n const raw = await readFile(join(process.cwd(), 'yamchart.yaml'), 'utf-8');\n const parsed = parseYaml(raw) as { name?: string };\n if (parsed?.name) return parsed.name;\n } catch {\n // Fall through to basename\n }\n return basename(process.cwd());\n}\n\nasync function promptConfirm(message: string): Promise<boolean> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => {\n rl.question(message, (answer) => {\n rl.close();\n resolve(answer.trim().toLowerCase() === 'y');\n });\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA,SAAS,gBAAgB;AACzB,SAAS,uBAAuB;AAChC,SAAS,UAAU,WAAW,OAAO,QAAQ,eAAe;AAC5D,SAAS,MAAM,SAAS,gBAAgB;AACxC,SAAS,qBAAqB;AAC9B,SAAS,SAAS,iBAAiB;AAInC,IAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AAExD,eAAsB,UAAU,gBAAuC;AACrE,QAAM,OAAc,QAAQ,yBAAyB;AAErD,QAAM,SAAS,MAAM,eAAe,gBAAgB,EAAE,WAAW,KAAK,CAAC;AAEvE,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;AAGpE,UAAM,QAAQ,MAAM,kBAAkB,OAAO,MAAM;AACnD,QAAI,OAAO;AACT,MAAO,QAAQ;AACf,MAAO,OAAO,iBAAiB,OAAO,MAAM,GAAG;AAC/C,iBAAW,QAAQ,MAAM,MAAM,IAAI,GAAG;AACpC,QAAO,OAAO,IAAI;AAAA,MACpB;AACA,MAAO,QAAQ;AAAA,IACjB;AAEA,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,oBAAoB;AAC5B;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;AAMA,eAAsB,sBAAqC;AACzD,QAAM,cAAc,KAAK,QAAQ,IAAI,GAAG,eAAe;AACvD,MAAI;AACF,UAAM,OAAO,WAAW;AAAA,EAC1B,QAAQ;AACN;AAAA,EACF;AAEA,QAAM,YAAY;AAClB,QAAM,cAAc;AACpB,QAAM,gBAAgB;AACxB;AAOA,eAAsB,gBAA+B;AACnD,MAAI;AACF,UAAM,OAAO,KAAK,QAAQ,IAAI,GAAG,eAAe,CAAC;AAAA,EACnD,QAAQ;AACN;AAAA,EACF;AAEA,QAAM,aAAa,MAAM,oBAAoB;AAC7C,MAAI,CAAC,WAAY;AAEjB,MAAI;AACJ,MAAI;AACF,cAAU,MAAM,QAAQ,YAAY,EAAE,eAAe,KAAK,CAAC;AAAA,EAC7D,QAAQ;AACN;AAAA,EACF;AAEA,MAAI,UAAU;AACd,MAAI,UAAU;AAEd,aAAW,SAAS,SAAS;AAC3B,QAAI,CAAC,MAAM,YAAY,EAAG;AAE1B,UAAM,YAAY,KAAK,YAAY,MAAM,MAAM,UAAU;AACzD,QAAI;AACJ,QAAI;AACF,uBAAiB,MAAM,SAAS,WAAW,OAAO;AAAA,IACpD,QAAQ;AACN;AAAA,IACF;AAEA,UAAM,YAAY,KAAK,QAAQ,IAAI,GAAG,WAAW,UAAU,MAAM,IAAI;AACrE,UAAM,aAAa,KAAK,WAAW,UAAU;AAE7C,QAAI,QAAQ;AACZ,QAAI;AACF,YAAM,WAAW,MAAM,SAAS,YAAY,OAAO;AACnD,UAAI,aAAa,eAAgB;AAAA,IACnC,QAAQ;AACN,cAAQ;AAAA,IACV;AAEA,UAAM,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;AAC1C,UAAM,UAAU,YAAY,gBAAgB,OAAO;AACnD,QAAI,MAAO;AAAA,QACN;AAAA,EACP;AAEA,MAAI,UAAU,KAAK,UAAU,GAAG;AAC9B,UAAM,QAAkB,CAAC;AACzB,QAAI,UAAU,EAAG,OAAM,KAAK,GAAG,OAAO,MAAM;AAC5C,QAAI,UAAU,EAAG,OAAM,KAAK,GAAG,OAAO,UAAU;AAChD,IAAO,QAAQ,2BAA2B,MAAM,KAAK,IAAI,CAAC,GAAG;AAAA,EAC/D;AACF;AAOA,eAAsB,gBACpB,UAA+D,CAAC,GACjD;AACf,MAAI;AACF,UAAM,OAAO,KAAK,QAAQ,IAAI,GAAG,eAAe,CAAC;AAAA,EACnD,QAAQ;AACN;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,gBAAgB,WAAW;AACjD,MAAI,CAAC,QAAS;AAGd,QAAM,cAAc,MAAM,eAAe;AACzC,QAAM,UAAU,QAAQ,QAAQ,iBAAiB,WAAW;AAE5D,QAAM,aAAa,KAAK,QAAQ,IAAI,GAAG,WAAW;AAElD,MAAI;AACF,UAAM,WAAW,MAAM,SAAS,YAAY,OAAO;AACnD,QAAI,aAAa,QAAS;AAG1B,UAAM,YAAY,QAAQ,WAAW;AACrC,UAAM,kBAAkB,MAAM;AAAA,MAC5B;AAAA,IACF;AACA,QAAI,CAAC,iBAAiB;AACpB,MAAO,KAAK,0BAA0B;AACtC;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,QAAM,UAAU,YAAY,SAAS,OAAO;AAC5C,EAAO,QAAQ,mBAAmB;AACpC;AAEA,eAAe,sBAA8C;AAC3D,QAAM,WAAW,KAAK,WAAW,aAAa,WAAW,WAAW,QAAQ;AAC5E,QAAM,UAAU,KAAK,WAAW,MAAM,aAAa,WAAW,WAAW,QAAQ;AAEjF,aAAW,QAAQ,CAAC,UAAU,OAAO,GAAG;AACtC,QAAI;AACF,YAAM,OAAO,IAAI;AACjB,aAAO;AAAA,IACT,QAAQ;AACN;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAe,gBAAgB,cAA8C;AAC3E,QAAM,WAAW,KAAK,WAAW,aAAa,WAAW,YAAY;AACrE,QAAM,UAAU,KAAK,WAAW,MAAM,aAAa,WAAW,YAAY;AAE1E,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;AAEA,eAAe,iBAAkC;AAC/C,MAAI;AACF,UAAM,MAAM,MAAM,SAAS,KAAK,QAAQ,IAAI,GAAG,eAAe,GAAG,OAAO;AACxE,UAAM,SAAS,UAAU,GAAG;AAC5B,QAAI,QAAQ,KAAM,QAAO,OAAO;AAAA,EAClC,QAAQ;AAAA,EAER;AACA,SAAO,SAAS,QAAQ,IAAI,CAAC;AAC/B;AAEA,eAAe,cAAc,SAAmC;AAC9D,QAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC3E,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,OAAG,SAAS,SAAS,CAAC,WAAW;AAC/B,SAAG,MAAM;AACT,cAAQ,OAAO,KAAK,EAAE,YAAY,MAAM,GAAG;AAAA,IAC7C,CAAC;AAAA,EACH,CAAC;AACH;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yamchart",
3
- "version": "0.7.2",
3
+ "version": "0.8.0",
4
4
  "description": "Git-native business intelligence dashboards",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -61,19 +61,21 @@
61
61
  },
62
62
  "devDependencies": {
63
63
  "@types/node": "^22.0.0",
64
+ "snowflake-sdk": "^2.3.4",
64
65
  "tsup": "^8.0.0",
65
66
  "typescript": "^5.7.0",
66
67
  "vitest": "^2.1.0",
67
68
  "@yamchart/advisor": "0.1.0",
68
- "@yamchart/config": "0.1.2",
69
69
  "@yamchart/auth-local": "0.1.0",
70
+ "@yamchart/config": "0.1.2",
70
71
  "@yamchart/query": "0.1.2",
71
72
  "@yamchart/schema": "0.1.2",
72
73
  "@yamchart/server": "0.1.2"
73
74
  },
74
75
  "files": [
75
76
  "dist",
76
- "bin"
77
+ "bin",
78
+ "CLAUDE.md"
77
79
  ],
78
80
  "publishConfig": {
79
81
  "access": "public"