yamchart 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Simon Spencer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/bin/yamchart ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import('../dist/index.js');
@@ -0,0 +1,365 @@
1
+ // src/commands/validate.ts
2
+ import { readFile, readdir, access } from "fs/promises";
3
+ import { join, extname, relative } from "path";
4
+ import { parse as parseYaml } from "yaml";
5
+ import {
6
+ ProjectSchema,
7
+ ConnectionSchema,
8
+ ChartSchema,
9
+ DashboardSchema
10
+ } from "@yamchart/schema";
11
+ import { parseModelMetadata } from "@yamchart/query";
12
+ async function validateProject(projectDir, options) {
13
+ const errors = [];
14
+ const warnings = [];
15
+ let filesChecked = 0;
16
+ let filesPassed = 0;
17
+ const config = {
18
+ project: null,
19
+ connections: /* @__PURE__ */ new Map(),
20
+ models: /* @__PURE__ */ new Map(),
21
+ charts: /* @__PURE__ */ new Map(),
22
+ dashboards: /* @__PURE__ */ new Map()
23
+ };
24
+ const projectPath = join(projectDir, "yamchart.yaml");
25
+ try {
26
+ await access(projectPath);
27
+ filesChecked++;
28
+ const content = await readFile(projectPath, "utf-8");
29
+ const parsed = parseYaml(content);
30
+ const result = ProjectSchema.safeParse(parsed);
31
+ if (result.success) {
32
+ config.project = result.data;
33
+ filesPassed++;
34
+ } else {
35
+ errors.push({
36
+ file: "yamchart.yaml",
37
+ message: `Invalid schema: ${result.error.errors[0]?.message || "Unknown error"}`
38
+ });
39
+ }
40
+ } catch {
41
+ errors.push({
42
+ file: "yamchart.yaml",
43
+ message: "yamchart.yaml not found"
44
+ });
45
+ return {
46
+ success: false,
47
+ errors,
48
+ warnings,
49
+ stats: { files: filesChecked, passed: filesPassed, failed: filesChecked - filesPassed }
50
+ };
51
+ }
52
+ const connectionsDir = join(projectDir, "connections");
53
+ try {
54
+ await access(connectionsDir);
55
+ const files = await readdir(connectionsDir);
56
+ for (const file of files) {
57
+ if (extname(file) !== ".yaml" && extname(file) !== ".yml") continue;
58
+ filesChecked++;
59
+ const filePath = join(connectionsDir, file);
60
+ const content = await readFile(filePath, "utf-8");
61
+ const parsed = parseYaml(content);
62
+ const result = ConnectionSchema.safeParse(parsed);
63
+ if (result.success) {
64
+ config.connections.set(result.data.name, result.data);
65
+ filesPassed++;
66
+ } else {
67
+ errors.push({
68
+ file: `connections/${file}`,
69
+ message: `Invalid schema: ${result.error.errors[0]?.message || "Unknown error"}`
70
+ });
71
+ }
72
+ }
73
+ } catch {
74
+ }
75
+ const modelsDir = join(projectDir, "models");
76
+ const modelStats = { filesChecked: 0, filesPassed: 0 };
77
+ try {
78
+ await access(modelsDir);
79
+ await validateModelsDir(modelsDir, projectDir, config, errors, modelStats);
80
+ filesChecked += modelStats.filesChecked;
81
+ filesPassed += modelStats.filesPassed;
82
+ } catch {
83
+ }
84
+ const chartsDir = join(projectDir, "charts");
85
+ try {
86
+ await access(chartsDir);
87
+ const files = await readdir(chartsDir);
88
+ for (const file of files) {
89
+ if (extname(file) !== ".yaml" && extname(file) !== ".yml") continue;
90
+ filesChecked++;
91
+ const filePath = join(chartsDir, file);
92
+ const content = await readFile(filePath, "utf-8");
93
+ const parsed = parseYaml(content);
94
+ const result = ChartSchema.safeParse(parsed);
95
+ if (result.success) {
96
+ config.charts.set(result.data.name, result.data);
97
+ filesPassed++;
98
+ } else {
99
+ errors.push({
100
+ file: `charts/${file}`,
101
+ message: `Invalid schema: ${result.error.errors[0]?.message || "Unknown error"}`
102
+ });
103
+ }
104
+ }
105
+ } catch {
106
+ }
107
+ const dashboardsDir = join(projectDir, "dashboards");
108
+ try {
109
+ await access(dashboardsDir);
110
+ const files = await readdir(dashboardsDir);
111
+ for (const file of files) {
112
+ if (extname(file) !== ".yaml" && extname(file) !== ".yml") continue;
113
+ filesChecked++;
114
+ const filePath = join(dashboardsDir, file);
115
+ const content = await readFile(filePath, "utf-8");
116
+ const parsed = parseYaml(content);
117
+ const result = DashboardSchema.safeParse(parsed);
118
+ if (result.success) {
119
+ config.dashboards.set(result.data.name, result.data);
120
+ filesPassed++;
121
+ } else {
122
+ errors.push({
123
+ file: `dashboards/${file}`,
124
+ message: `Invalid schema: ${result.error.errors[0]?.message || "Unknown error"}`
125
+ });
126
+ }
127
+ }
128
+ } catch {
129
+ }
130
+ crossReferenceValidation(config, errors, warnings);
131
+ let dryRunStats;
132
+ if (options.dryRun) {
133
+ dryRunStats = await dryRunValidation(projectDir, config, options.connection, errors);
134
+ }
135
+ return {
136
+ success: errors.length === 0,
137
+ errors,
138
+ warnings,
139
+ stats: {
140
+ files: filesChecked,
141
+ passed: filesPassed,
142
+ failed: filesChecked - filesPassed
143
+ },
144
+ dryRunStats
145
+ };
146
+ }
147
+ async function validateModelsDir(dir, projectDir, config, errors, stats) {
148
+ const entries = await readdir(dir, { withFileTypes: true });
149
+ for (const entry of entries) {
150
+ const fullPath = join(dir, entry.name);
151
+ if (entry.isDirectory()) {
152
+ await validateModelsDir(fullPath, projectDir, config, errors, stats);
153
+ } else if (extname(entry.name) === ".sql") {
154
+ stats.filesChecked++;
155
+ const relPath = relative(projectDir, fullPath);
156
+ const content = await readFile(fullPath, "utf-8");
157
+ try {
158
+ const parsed = parseModelMetadata(content);
159
+ config.models.set(parsed.name, { name: parsed.name, sql: parsed.sql });
160
+ stats.filesPassed++;
161
+ } catch (err) {
162
+ errors.push({
163
+ file: relPath,
164
+ message: err instanceof Error ? err.message : "Failed to parse model"
165
+ });
166
+ }
167
+ }
168
+ }
169
+ }
170
+ function crossReferenceValidation(config, errors, warnings) {
171
+ for (const [chartName, chart] of config.charts) {
172
+ if (chart.source.model && !config.models.has(chart.source.model)) {
173
+ const suggestion = findSimilar(chart.source.model, Array.from(config.models.keys()));
174
+ errors.push({
175
+ file: `charts/${chartName}.yaml`,
176
+ message: `Unknown model reference "${chart.source.model}"`,
177
+ suggestion: suggestion ? `Did you mean "${suggestion}"?` : void 0
178
+ });
179
+ }
180
+ }
181
+ if (config.project?.defaults?.connection) {
182
+ const connName = config.project.defaults.connection;
183
+ if (!config.connections.has(connName)) {
184
+ const suggestion = findSimilar(connName, Array.from(config.connections.keys()));
185
+ errors.push({
186
+ file: "yamchart.yaml",
187
+ message: `Default connection "${connName}" not found`,
188
+ suggestion: suggestion ? `Did you mean "${suggestion}"?` : void 0
189
+ });
190
+ }
191
+ }
192
+ }
193
+ function findSimilar(target, candidates) {
194
+ const threshold = 3;
195
+ for (const candidate of candidates) {
196
+ if (levenshtein(target.toLowerCase(), candidate.toLowerCase()) <= threshold) {
197
+ return candidate;
198
+ }
199
+ }
200
+ return null;
201
+ }
202
+ function levenshtein(a, b) {
203
+ const matrix = [];
204
+ for (let i = 0; i <= b.length; i++) {
205
+ matrix[i] = [i];
206
+ }
207
+ for (let j = 0; j <= a.length; j++) {
208
+ matrix[0][j] = j;
209
+ }
210
+ for (let i = 1; i <= b.length; i++) {
211
+ for (let j = 1; j <= a.length; j++) {
212
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
213
+ matrix[i][j] = matrix[i - 1][j - 1];
214
+ } else {
215
+ matrix[i][j] = Math.min(
216
+ matrix[i - 1][j - 1] + 1,
217
+ matrix[i][j - 1] + 1,
218
+ matrix[i - 1][j] + 1
219
+ );
220
+ }
221
+ }
222
+ }
223
+ return matrix[b.length][a.length];
224
+ }
225
+ async function dryRunValidation(projectDir, config, connectionName, errors) {
226
+ const { DuckDBConnector } = await import("@yamchart/query");
227
+ let passed = 0;
228
+ let failed = 0;
229
+ const connName = connectionName || config.project?.defaults?.connection;
230
+ if (!connName) {
231
+ errors.push({
232
+ file: "yamchart.yaml",
233
+ message: "No connection specified for dry-run (use --connection or set defaults.connection)"
234
+ });
235
+ return { passed, failed: 1 };
236
+ }
237
+ const connection = config.connections.get(connName);
238
+ if (!connection) {
239
+ errors.push({
240
+ file: "yamchart.yaml",
241
+ message: `Connection "${connName}" not found`
242
+ });
243
+ return { passed, failed: 1 };
244
+ }
245
+ if (connection.type !== "duckdb") {
246
+ errors.push({
247
+ file: `connections/${connName}.yaml`,
248
+ message: `Dry-run not yet supported for connection type "${connection.type}"`
249
+ });
250
+ return { passed, failed: 1 };
251
+ }
252
+ const connPath = join(projectDir, "connections", `${connName}.yaml`);
253
+ const connContent = await readFile(connPath, "utf-8");
254
+ const connConfig = parseYaml(connContent);
255
+ const dbPath = connConfig.config.path.startsWith("/") ? connConfig.config.path : join(projectDir, connConfig.config.path);
256
+ const connector = new DuckDBConnector({ path: dbPath });
257
+ try {
258
+ await connector.connect();
259
+ for (const [modelName, model] of config.models) {
260
+ const result = await connector.explain(model.sql);
261
+ if (result.valid) {
262
+ passed++;
263
+ } else {
264
+ failed++;
265
+ errors.push({
266
+ file: `models/${modelName}.sql`,
267
+ message: result.error || "Query validation failed"
268
+ });
269
+ }
270
+ }
271
+ } finally {
272
+ await connector.disconnect();
273
+ }
274
+ return { passed, failed };
275
+ }
276
+
277
+ // src/utils/config.ts
278
+ import { access as access2 } from "fs/promises";
279
+ import { join as join2, dirname, resolve } from "path";
280
+ import { config as loadDotenv } from "dotenv";
281
+ async function findProjectRoot(startDir) {
282
+ let currentDir = resolve(startDir);
283
+ const root = dirname(currentDir);
284
+ while (currentDir !== root) {
285
+ const configPath = join2(currentDir, "yamchart.yaml");
286
+ try {
287
+ await access2(configPath);
288
+ return currentDir;
289
+ } catch {
290
+ currentDir = dirname(currentDir);
291
+ }
292
+ }
293
+ try {
294
+ await access2(join2(root, "yamchart.yaml"));
295
+ return root;
296
+ } catch {
297
+ return null;
298
+ }
299
+ }
300
+ function loadEnvFile(projectDir) {
301
+ loadDotenv({ path: join2(projectDir, ".env") });
302
+ }
303
+
304
+ // src/utils/output.ts
305
+ import pc from "picocolors";
306
+ import ora from "ora";
307
+ var symbols = {
308
+ success: pc.green("\u2713"),
309
+ error: pc.red("\u2717"),
310
+ warning: pc.yellow("\u26A0"),
311
+ info: pc.blue("\u2139"),
312
+ arrow: pc.dim("\u2192")
313
+ };
314
+ function success(message) {
315
+ console.log(`${symbols.success} ${message}`);
316
+ }
317
+ function error(message) {
318
+ console.log(`${symbols.error} ${message}`);
319
+ }
320
+ function warning(message) {
321
+ console.log(`${symbols.warning} ${message}`);
322
+ }
323
+ function info(message) {
324
+ console.log(`${symbols.info} ${message}`);
325
+ }
326
+ function detail(message) {
327
+ console.log(` ${symbols.arrow} ${message}`);
328
+ }
329
+ function newline() {
330
+ console.log();
331
+ }
332
+ function header(title) {
333
+ console.log(pc.bold(title));
334
+ newline();
335
+ }
336
+ function spinner(text) {
337
+ return ora({ text, color: "cyan" }).start();
338
+ }
339
+ function box(lines) {
340
+ const maxLength = Math.max(...lines.map((l) => l.length));
341
+ const width = maxLength + 4;
342
+ const border = "\u2500".repeat(width);
343
+ console.log(` \u250C${border}\u2510`);
344
+ for (const line of lines) {
345
+ const padding = " ".repeat(maxLength - line.length);
346
+ console.log(` \u2502 ${line}${padding} \u2502`);
347
+ }
348
+ console.log(` \u2514${border}\u2518`);
349
+ }
350
+
351
+ export {
352
+ validateProject,
353
+ findProjectRoot,
354
+ loadEnvFile,
355
+ success,
356
+ error,
357
+ warning,
358
+ info,
359
+ detail,
360
+ newline,
361
+ header,
362
+ spinner,
363
+ box
364
+ };
365
+ //# sourceMappingURL=chunk-TBILHUB3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/validate.ts","../src/utils/config.ts","../src/utils/output.ts"],"sourcesContent":["import { readFile, readdir, access } from 'fs/promises';\nimport { join, extname, relative } from 'path';\nimport { parse as parseYaml } from 'yaml';\nimport {\n ProjectSchema,\n ConnectionSchema,\n ChartSchema,\n DashboardSchema,\n} from '@yamchart/schema';\nimport { parseModelMetadata } from '@yamchart/query';\n\nexport interface ValidationError {\n file: string;\n line?: number;\n message: string;\n suggestion?: string;\n}\n\nexport interface ValidationResult {\n success: boolean;\n errors: ValidationError[];\n warnings: ValidationError[];\n stats: {\n files: number;\n passed: number;\n failed: number;\n };\n dryRunStats?: {\n passed: number;\n failed: number;\n };\n}\n\nexport interface ValidateOptions {\n dryRun: boolean;\n connection?: string;\n}\n\ninterface LoadedConfig {\n project: { name: string; version: string; defaults?: { connection?: string } } | null;\n connections: Map<string, { name: string; type: string }>;\n models: Map<string, { name: string; sql: string }>;\n charts: Map<string, { name: string; source: { model?: string; sql?: string } }>;\n dashboards: Map<string, { name: string; layout: unknown }>;\n}\n\nexport async function validateProject(\n projectDir: string,\n options: ValidateOptions\n): Promise<ValidationResult> {\n const errors: ValidationError[] = [];\n const warnings: ValidationError[] = [];\n let filesChecked = 0;\n let filesPassed = 0;\n\n const config: LoadedConfig = {\n project: null,\n connections: new Map(),\n models: new Map(),\n charts: new Map(),\n dashboards: new Map(),\n };\n\n // Phase 1: Schema validation\n\n // Validate yamchart.yaml\n const projectPath = join(projectDir, 'yamchart.yaml');\n try {\n await access(projectPath);\n filesChecked++;\n const content = await readFile(projectPath, 'utf-8');\n const parsed = parseYaml(content);\n const result = ProjectSchema.safeParse(parsed);\n\n if (result.success) {\n config.project = result.data;\n filesPassed++;\n } else {\n errors.push({\n file: 'yamchart.yaml',\n message: `Invalid schema: ${result.error.errors[0]?.message || 'Unknown error'}`,\n });\n }\n } catch {\n errors.push({\n file: 'yamchart.yaml',\n message: 'yamchart.yaml not found',\n });\n return {\n success: false,\n errors,\n warnings,\n stats: { files: filesChecked, passed: filesPassed, failed: filesChecked - filesPassed },\n };\n }\n\n // Validate connections\n const connectionsDir = join(projectDir, 'connections');\n try {\n await access(connectionsDir);\n const files = await readdir(connectionsDir);\n\n for (const file of files) {\n if (extname(file) !== '.yaml' && extname(file) !== '.yml') continue;\n filesChecked++;\n\n const filePath = join(connectionsDir, file);\n const content = await readFile(filePath, 'utf-8');\n const parsed = parseYaml(content);\n const result = ConnectionSchema.safeParse(parsed);\n\n if (result.success) {\n config.connections.set(result.data.name, result.data);\n filesPassed++;\n } else {\n errors.push({\n file: `connections/${file}`,\n message: `Invalid schema: ${result.error.errors[0]?.message || 'Unknown error'}`,\n });\n }\n }\n } catch {\n // No connections directory is ok\n }\n\n // Validate models\n const modelsDir = join(projectDir, 'models');\n const modelStats = { filesChecked: 0, filesPassed: 0 };\n try {\n await access(modelsDir);\n await validateModelsDir(modelsDir, projectDir, config, errors, modelStats);\n filesChecked += modelStats.filesChecked;\n filesPassed += modelStats.filesPassed;\n } catch {\n // No models directory is ok\n }\n\n // Validate charts\n const chartsDir = join(projectDir, 'charts');\n try {\n await access(chartsDir);\n const files = await readdir(chartsDir);\n\n for (const file of files) {\n if (extname(file) !== '.yaml' && extname(file) !== '.yml') continue;\n filesChecked++;\n\n const filePath = join(chartsDir, file);\n const content = await readFile(filePath, 'utf-8');\n const parsed = parseYaml(content);\n const result = ChartSchema.safeParse(parsed);\n\n if (result.success) {\n config.charts.set(result.data.name, result.data);\n filesPassed++;\n } else {\n errors.push({\n file: `charts/${file}`,\n message: `Invalid schema: ${result.error.errors[0]?.message || 'Unknown error'}`,\n });\n }\n }\n } catch {\n // No charts directory is ok\n }\n\n // Validate dashboards\n const dashboardsDir = join(projectDir, 'dashboards');\n try {\n await access(dashboardsDir);\n const files = await readdir(dashboardsDir);\n\n for (const file of files) {\n if (extname(file) !== '.yaml' && extname(file) !== '.yml') continue;\n filesChecked++;\n\n const filePath = join(dashboardsDir, file);\n const content = await readFile(filePath, 'utf-8');\n const parsed = parseYaml(content);\n const result = DashboardSchema.safeParse(parsed);\n\n if (result.success) {\n config.dashboards.set(result.data.name, result.data);\n filesPassed++;\n } else {\n errors.push({\n file: `dashboards/${file}`,\n message: `Invalid schema: ${result.error.errors[0]?.message || 'Unknown error'}`,\n });\n }\n }\n } catch {\n // No dashboards directory is ok\n }\n\n // Phase 2: Cross-reference validation\n crossReferenceValidation(config, errors, warnings);\n\n // Phase 3: Dry-run query validation (if enabled)\n let dryRunStats: { passed: number; failed: number } | undefined;\n if (options.dryRun) {\n dryRunStats = await dryRunValidation(projectDir, config, options.connection, errors);\n }\n\n return {\n success: errors.length === 0,\n errors,\n warnings,\n stats: {\n files: filesChecked,\n passed: filesPassed,\n failed: filesChecked - filesPassed,\n },\n dryRunStats,\n };\n}\n\nasync function validateModelsDir(\n dir: string,\n projectDir: string,\n config: LoadedConfig,\n errors: ValidationError[],\n stats: { filesChecked: number; filesPassed: number }\n): Promise<void> {\n const entries = await readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n\n if (entry.isDirectory()) {\n await validateModelsDir(fullPath, projectDir, config, errors, stats);\n } else if (extname(entry.name) === '.sql') {\n stats.filesChecked++;\n const relPath = relative(projectDir, fullPath);\n const content = await readFile(fullPath, 'utf-8');\n\n try {\n const parsed = parseModelMetadata(content);\n config.models.set(parsed.name, { name: parsed.name, sql: parsed.sql });\n stats.filesPassed++;\n } catch (err) {\n errors.push({\n file: relPath,\n message: err instanceof Error ? err.message : 'Failed to parse model',\n });\n }\n }\n }\n}\n\nfunction crossReferenceValidation(\n config: LoadedConfig,\n errors: ValidationError[],\n warnings: ValidationError[]\n): void {\n // Check that charts reference existing models\n for (const [chartName, chart] of config.charts) {\n if (chart.source.model && !config.models.has(chart.source.model)) {\n const suggestion = findSimilar(chart.source.model, Array.from(config.models.keys()));\n errors.push({\n file: `charts/${chartName}.yaml`,\n message: `Unknown model reference \"${chart.source.model}\"`,\n suggestion: suggestion ? `Did you mean \"${suggestion}\"?` : undefined,\n });\n }\n }\n\n // Check that default connection exists\n if (config.project?.defaults?.connection) {\n const connName = config.project.defaults.connection;\n if (!config.connections.has(connName)) {\n const suggestion = findSimilar(connName, Array.from(config.connections.keys()));\n errors.push({\n file: 'yamchart.yaml',\n message: `Default connection \"${connName}\" not found`,\n suggestion: suggestion ? `Did you mean \"${suggestion}\"?` : undefined,\n });\n }\n }\n}\n\nfunction findSimilar(target: string, candidates: string[]): string | null {\n const threshold = 3; // Levenshtein distance threshold\n\n for (const candidate of candidates) {\n if (levenshtein(target.toLowerCase(), candidate.toLowerCase()) <= threshold) {\n return candidate;\n }\n }\n return null;\n}\n\nfunction levenshtein(a: string, b: string): number {\n const matrix: number[][] = [];\n\n for (let i = 0; i <= b.length; i++) {\n matrix[i] = [i];\n }\n for (let j = 0; j <= a.length; j++) {\n matrix[0]![j] = j;\n }\n\n for (let i = 1; i <= b.length; i++) {\n for (let j = 1; j <= a.length; j++) {\n if (b.charAt(i - 1) === a.charAt(j - 1)) {\n matrix[i]![j] = matrix[i - 1]![j - 1]!;\n } else {\n matrix[i]![j] = Math.min(\n matrix[i - 1]![j - 1]! + 1,\n matrix[i]![j - 1]! + 1,\n matrix[i - 1]![j]! + 1\n );\n }\n }\n }\n\n return matrix[b.length]![a.length]!;\n}\n\nasync function dryRunValidation(\n projectDir: string,\n config: LoadedConfig,\n connectionName: string | undefined,\n errors: ValidationError[]\n): Promise<{ passed: number; failed: number }> {\n const { DuckDBConnector } = await import('@yamchart/query');\n\n let passed = 0;\n let failed = 0;\n\n // Determine which connection to use\n const connName = connectionName || config.project?.defaults?.connection;\n if (!connName) {\n errors.push({\n file: 'yamchart.yaml',\n message: 'No connection specified for dry-run (use --connection or set defaults.connection)',\n });\n return { passed, failed: 1 };\n }\n\n const connection = config.connections.get(connName);\n if (!connection) {\n errors.push({\n file: 'yamchart.yaml',\n message: `Connection \"${connName}\" not found`,\n });\n return { passed, failed: 1 };\n }\n\n // Only DuckDB supported for now\n if (connection.type !== 'duckdb') {\n errors.push({\n file: `connections/${connName}.yaml`,\n message: `Dry-run not yet supported for connection type \"${connection.type}\"`,\n });\n return { passed, failed: 1 };\n }\n\n // Load full connection config to get path\n const connPath = join(projectDir, 'connections', `${connName}.yaml`);\n const connContent = await readFile(connPath, 'utf-8');\n const connConfig = parseYaml(connContent) as { config: { path: string } };\n\n // Resolve path relative to project\n const dbPath = connConfig.config.path.startsWith('/')\n ? connConfig.config.path\n : join(projectDir, connConfig.config.path);\n\n const connector = new DuckDBConnector({ path: dbPath });\n\n try {\n await connector.connect();\n\n for (const [modelName, model] of config.models) {\n const result = await connector.explain(model.sql);\n\n if (result.valid) {\n passed++;\n } else {\n failed++;\n errors.push({\n file: `models/${modelName}.sql`,\n message: result.error || 'Query validation failed',\n });\n }\n }\n } finally {\n await connector.disconnect();\n }\n\n return { passed, failed };\n}\n","import { access } from 'fs/promises';\nimport { join, dirname, resolve } from 'path';\nimport { config as loadDotenv } from 'dotenv';\n\n/**\n * Find the project root by looking for yamchart.yaml.\n * Searches current directory and parent directories.\n */\nexport async function findProjectRoot(startDir: string): Promise<string | null> {\n let currentDir = resolve(startDir);\n const root = dirname(currentDir);\n\n while (currentDir !== root) {\n const configPath = join(currentDir, 'yamchart.yaml');\n try {\n await access(configPath);\n return currentDir;\n } catch {\n currentDir = dirname(currentDir);\n }\n }\n\n // Check root directory too\n try {\n await access(join(root, 'yamchart.yaml'));\n return root;\n } catch {\n return null;\n }\n}\n\n/**\n * Load .env file from project directory.\n */\nexport function loadEnvFile(projectDir: string): void {\n loadDotenv({ path: join(projectDir, '.env') });\n}\n\n/**\n * Resolve ${VAR} syntax in a string from environment variables.\n */\nexport function resolveEnvVars(value: string): string {\n return value.replace(/\\$\\{([^}]+)\\}/g, (match, varName) => {\n const envValue = process.env[varName];\n if (envValue === undefined) {\n throw new Error(`Environment variable not found: ${varName}`);\n }\n return envValue;\n });\n}\n\n/**\n * Recursively resolve env vars in an object.\n */\nexport function resolveEnvVarsInObject<T>(obj: T): T {\n if (typeof obj === 'string') {\n return resolveEnvVars(obj) as T;\n }\n if (Array.isArray(obj)) {\n return obj.map(resolveEnvVarsInObject) as T;\n }\n if (obj !== null && typeof obj === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n result[key] = resolveEnvVarsInObject(value);\n }\n return result as T;\n }\n return obj;\n}\n","import pc from 'picocolors';\nimport ora, { type Ora } from 'ora';\n\nexport const symbols = {\n success: pc.green('✓'),\n error: pc.red('✗'),\n warning: pc.yellow('⚠'),\n info: pc.blue('ℹ'),\n arrow: pc.dim('→'),\n};\n\nexport function success(message: string): void {\n console.log(`${symbols.success} ${message}`);\n}\n\nexport function error(message: string): void {\n console.log(`${symbols.error} ${message}`);\n}\n\nexport function warning(message: string): void {\n console.log(`${symbols.warning} ${message}`);\n}\n\nexport function info(message: string): void {\n console.log(`${symbols.info} ${message}`);\n}\n\nexport function detail(message: string): void {\n console.log(` ${symbols.arrow} ${message}`);\n}\n\nexport function newline(): void {\n console.log();\n}\n\nexport function header(title: string): void {\n console.log(pc.bold(title));\n newline();\n}\n\nexport function spinner(text: string): Ora {\n return ora({ text, color: 'cyan' }).start();\n}\n\nexport function box(lines: string[]): void {\n const maxLength = Math.max(...lines.map((l) => l.length));\n const width = maxLength + 4;\n const border = '─'.repeat(width);\n\n console.log(` ┌${border}┐`);\n for (const line of lines) {\n const padding = ' '.repeat(maxLength - line.length);\n console.log(` │ ${line}${padding} │`);\n }\n console.log(` └${border}┘`);\n}\n"],"mappings":"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","names":["access","join"]}
@@ -0,0 +1,85 @@
1
+ import {
2
+ box,
3
+ detail,
4
+ error,
5
+ header,
6
+ info,
7
+ loadEnvFile,
8
+ newline,
9
+ spinner,
10
+ success,
11
+ validateProject
12
+ } from "./chunk-TBILHUB3.js";
13
+
14
+ // src/commands/dev.ts
15
+ import { createServer } from "@yamchart/server";
16
+ async function runDevServer(projectDir, options) {
17
+ loadEnvFile(projectDir);
18
+ header("Validating configuration...");
19
+ const validation = await validateProject(projectDir, { dryRun: false });
20
+ if (!validation.success) {
21
+ for (const error2 of validation.errors) {
22
+ error(error2.file);
23
+ detail(error2.message);
24
+ if (error2.suggestion) {
25
+ detail(error2.suggestion);
26
+ }
27
+ }
28
+ newline();
29
+ error(`Validation failed with ${validation.errors.length} error(s)`);
30
+ process.exit(1);
31
+ }
32
+ success(`Validation passed (${validation.stats.passed} files)`);
33
+ newline();
34
+ const spinner2 = spinner("Starting server...");
35
+ let server;
36
+ try {
37
+ server = await createServer({
38
+ projectDir,
39
+ port: options.port,
40
+ watch: true,
41
+ serveStatic: !options.apiOnly
42
+ });
43
+ await server.start();
44
+ spinner2.stop();
45
+ } catch (err) {
46
+ spinner2.fail("Failed to start server");
47
+ error(err instanceof Error ? err.message : "Unknown error");
48
+ process.exit(1);
49
+ }
50
+ const project = server.configLoader.getProject();
51
+ const charts = server.configLoader.getCharts();
52
+ const models = server.configLoader.getModels();
53
+ newline();
54
+ box([
55
+ `Dashbook v0.1.0`,
56
+ ``,
57
+ `Dashboard: http://localhost:${options.port}`,
58
+ `API: http://localhost:${options.port}/api`,
59
+ ``,
60
+ `Project: ${project.name}`,
61
+ `Charts: ${charts.length} loaded`,
62
+ `Models: ${models.length} loaded`,
63
+ ``,
64
+ `Watching for changes...`
65
+ ]);
66
+ newline();
67
+ if (options.open && !options.apiOnly) {
68
+ const url = `http://localhost:${options.port}`;
69
+ const { exec } = await import("child_process");
70
+ const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
71
+ exec(`${command} ${url}`);
72
+ }
73
+ const shutdown = async () => {
74
+ newline();
75
+ info("Shutting down...");
76
+ await server.stop();
77
+ process.exit(0);
78
+ };
79
+ process.on("SIGINT", shutdown);
80
+ process.on("SIGTERM", shutdown);
81
+ }
82
+ export {
83
+ runDevServer
84
+ };
85
+ //# sourceMappingURL=dev-UHYN2RXH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/dev.ts"],"sourcesContent":["import { createServer, type DashbookServer } from '@yamchart/server';\nimport * as output from '../utils/output.js';\nimport { validateProject } from './validate.js';\nimport { loadEnvFile } from '../utils/config.js';\n\nexport interface DevOptions {\n port: number;\n apiOnly: boolean;\n open: boolean;\n}\n\nexport async function runDevServer(\n projectDir: string,\n options: DevOptions\n): Promise<void> {\n // Load .env file\n loadEnvFile(projectDir);\n\n // Validate first\n output.header('Validating configuration...');\n\n const validation = await validateProject(projectDir, { dryRun: false });\n\n if (!validation.success) {\n for (const error of validation.errors) {\n output.error(error.file);\n output.detail(error.message);\n if (error.suggestion) {\n output.detail(error.suggestion);\n }\n }\n output.newline();\n output.error(`Validation failed with ${validation.errors.length} error(s)`);\n process.exit(1);\n }\n\n output.success(`Validation passed (${validation.stats.passed} files)`);\n output.newline();\n\n // Start server\n const spinner = output.spinner('Starting server...');\n\n let server: DashbookServer;\n\n try {\n server = await createServer({\n projectDir,\n port: options.port,\n watch: true,\n serveStatic: !options.apiOnly,\n });\n\n await server.start();\n spinner.stop();\n } catch (err) {\n spinner.fail('Failed to start server');\n output.error(err instanceof Error ? err.message : 'Unknown error');\n process.exit(1);\n }\n\n // Print status\n const project = server.configLoader.getProject();\n const charts = server.configLoader.getCharts();\n const models = server.configLoader.getModels();\n\n output.newline();\n output.box([\n `Dashbook v0.1.0`,\n ``,\n `Dashboard: http://localhost:${options.port}`,\n `API: http://localhost:${options.port}/api`,\n ``,\n `Project: ${project.name}`,\n `Charts: ${charts.length} loaded`,\n `Models: ${models.length} loaded`,\n ``,\n `Watching for changes...`,\n ]);\n output.newline();\n\n // Open browser\n if (options.open && !options.apiOnly) {\n const url = `http://localhost:${options.port}`;\n const { exec } = await import('child_process');\n const command = process.platform === 'darwin' ? 'open' :\n process.platform === 'win32' ? 'start' : 'xdg-open';\n exec(`${command} ${url}`);\n }\n\n // Handle shutdown\n const shutdown = async () => {\n output.newline();\n output.info('Shutting down...');\n await server.stop();\n process.exit(0);\n };\n\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n}\n"],"mappings":";;;;;;;;;;;;;;AAAA,SAAS,oBAAyC;AAWlD,eAAsB,aACpB,YACA,SACe;AAEf,cAAY,UAAU;AAGtB,EAAO,OAAO,6BAA6B;AAE3C,QAAM,aAAa,MAAM,gBAAgB,YAAY,EAAE,QAAQ,MAAM,CAAC;AAEtE,MAAI,CAAC,WAAW,SAAS;AACvB,eAAWA,UAAS,WAAW,QAAQ;AACrC,MAAO,MAAMA,OAAM,IAAI;AACvB,MAAO,OAAOA,OAAM,OAAO;AAC3B,UAAIA,OAAM,YAAY;AACpB,QAAO,OAAOA,OAAM,UAAU;AAAA,MAChC;AAAA,IACF;AACA,IAAO,QAAQ;AACf,IAAO,MAAM,0BAA0B,WAAW,OAAO,MAAM,WAAW;AAC1E,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,EAAO,QAAQ,sBAAsB,WAAW,MAAM,MAAM,SAAS;AACrE,EAAO,QAAQ;AAGf,QAAMC,WAAiB,QAAQ,oBAAoB;AAEnD,MAAI;AAEJ,MAAI;AACF,aAAS,MAAM,aAAa;AAAA,MAC1B;AAAA,MACA,MAAM,QAAQ;AAAA,MACd,OAAO;AAAA,MACP,aAAa,CAAC,QAAQ;AAAA,IACxB,CAAC;AAED,UAAM,OAAO,MAAM;AACnB,IAAAA,SAAQ,KAAK;AAAA,EACf,SAAS,KAAK;AACZ,IAAAA,SAAQ,KAAK,wBAAwB;AACrC,IAAO,MAAM,eAAe,QAAQ,IAAI,UAAU,eAAe;AACjE,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,QAAM,UAAU,OAAO,aAAa,WAAW;AAC/C,QAAM,SAAS,OAAO,aAAa,UAAU;AAC7C,QAAM,SAAS,OAAO,aAAa,UAAU;AAE7C,EAAO,QAAQ;AACf,EAAO,IAAI;AAAA,IACT;AAAA,IACA;AAAA,IACA,gCAAgC,QAAQ,IAAI;AAAA,IAC5C,gCAAgC,QAAQ,IAAI;AAAA,IAC5C;AAAA,IACA,eAAe,QAAQ,IAAI;AAAA,IAC3B,eAAe,OAAO,MAAM;AAAA,IAC5B,eAAe,OAAO,MAAM;AAAA,IAC5B;AAAA,IACA;AAAA,EACF,CAAC;AACD,EAAO,QAAQ;AAGf,MAAI,QAAQ,QAAQ,CAAC,QAAQ,SAAS;AACpC,UAAM,MAAM,oBAAoB,QAAQ,IAAI;AAC5C,UAAM,EAAE,KAAK,IAAI,MAAM,OAAO,eAAe;AAC7C,UAAM,UAAU,QAAQ,aAAa,WAAW,SAChC,QAAQ,aAAa,UAAU,UAAU;AACzD,SAAK,GAAG,OAAO,IAAI,GAAG,EAAE;AAAA,EAC1B;AAGA,QAAM,WAAW,YAAY;AAC3B,IAAO,QAAQ;AACf,IAAO,KAAK,kBAAkB;AAC9B,UAAM,OAAO,KAAK;AAClB,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,UAAQ,GAAG,UAAU,QAAQ;AAC7B,UAAQ,GAAG,WAAW,QAAQ;AAChC;","names":["error","spinner"]}
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ detail,
4
+ error,
5
+ findProjectRoot,
6
+ header,
7
+ info,
8
+ loadEnvFile,
9
+ newline,
10
+ success,
11
+ validateProject,
12
+ warning
13
+ } from "./chunk-TBILHUB3.js";
14
+
15
+ // src/index.ts
16
+ import { Command } from "commander";
17
+ import { resolve, basename } from "path";
18
+ var program = new Command();
19
+ program.name("yamchart").description("Git-native business intelligence dashboards").version("0.1.0");
20
+ program.command("validate").description("Validate configuration files").argument("[path]", "Path to yamchart project", ".").option("--dry-run", "Connect to database and test queries with EXPLAIN").option("-c, --connection <name>", "Connection to use for dry-run").option("--json", "Output as JSON").action(async (path, options) => {
21
+ const startPath = resolve(path);
22
+ const projectDir = await findProjectRoot(startPath);
23
+ if (!projectDir) {
24
+ if (options.json) {
25
+ console.log(JSON.stringify({ success: false, error: "yamchart.yaml not found" }));
26
+ } else {
27
+ error("yamchart.yaml not found");
28
+ detail("Run this command from a yamchart project directory");
29
+ }
30
+ process.exit(2);
31
+ }
32
+ loadEnvFile(projectDir);
33
+ if (!options.json) {
34
+ header("Validating yamchart project...");
35
+ }
36
+ const result = await validateProject(projectDir, {
37
+ dryRun: options.dryRun ?? false,
38
+ connection: options.connection
39
+ });
40
+ if (options.json) {
41
+ console.log(JSON.stringify(result, null, 2));
42
+ } else {
43
+ for (const error2 of result.errors) {
44
+ error(error2.file);
45
+ detail(error2.message);
46
+ if (error2.suggestion) {
47
+ detail(error2.suggestion);
48
+ }
49
+ }
50
+ for (const warning2 of result.warnings) {
51
+ warning(warning2.file);
52
+ detail(warning2.message);
53
+ }
54
+ newline();
55
+ if (result.success) {
56
+ success(`Schema: ${result.stats.passed} passed`);
57
+ } else {
58
+ error(`Schema: ${result.stats.passed} passed, ${result.stats.failed} failed`);
59
+ }
60
+ if (result.dryRunStats) {
61
+ newline();
62
+ if (result.dryRunStats.failed === 0) {
63
+ success(`Queries: ${result.dryRunStats.passed} passed (EXPLAIN OK)`);
64
+ } else {
65
+ error(`Queries: ${result.dryRunStats.passed} passed, ${result.dryRunStats.failed} failed`);
66
+ }
67
+ }
68
+ newline();
69
+ if (result.success) {
70
+ success("Validation passed");
71
+ } else {
72
+ error(`Validation failed with ${result.errors.length} error(s)`);
73
+ }
74
+ }
75
+ process.exit(result.success ? 0 : 1);
76
+ });
77
+ program.command("dev").description("Start development server with hot reload").argument("[path]", "Path to yamchart project", ".").option("-p, --port <number>", "Port to listen on", "3001").option("--api-only", "Only serve API, no web UI").option("--no-open", "Do not open browser automatically").action(async (path, options) => {
78
+ const startPath = resolve(path);
79
+ const projectDir = await findProjectRoot(startPath);
80
+ if (!projectDir) {
81
+ error("yamchart.yaml not found");
82
+ detail("Run this command from a yamchart project directory");
83
+ process.exit(2);
84
+ }
85
+ const { runDevServer } = await import("./dev-UHYN2RXH.js");
86
+ await runDevServer(projectDir, {
87
+ port: parseInt(options.port, 10),
88
+ apiOnly: options.apiOnly ?? false,
89
+ open: options.open
90
+ });
91
+ });
92
+ program.command("init").description("Create a new yamchart project").argument("[directory]", "Target directory", ".").option("--example", "Create full example project with sample database").option("--empty", "Create only yamchart.yaml (no connections, models, or charts)").option("--force", "Overwrite existing files").action(async (directory, options) => {
93
+ const { initProject } = await import("./init-6D5VNGSP.js");
94
+ const targetDir = resolve(directory);
95
+ const result = await initProject(targetDir, options);
96
+ if (!result.success) {
97
+ error(result.error || "Failed to create project");
98
+ process.exit(1);
99
+ }
100
+ newline();
101
+ success(`Created ${directory === "." ? basename(targetDir) : directory}/`);
102
+ for (const file of result.files.slice(0, 10)) {
103
+ detail(file);
104
+ }
105
+ if (result.files.length > 10) {
106
+ detail(`... and ${result.files.length - 10} more files`);
107
+ }
108
+ newline();
109
+ info(`Run \`cd ${directory === "." ? basename(targetDir) : directory} && yamchart dev\` to start.`);
110
+ });
111
+ program.parse();
112
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from 'commander';\nimport { resolve, basename } from 'path';\nimport { validateProject } from './commands/validate.js';\nimport { findProjectRoot, loadEnvFile } from './utils/config.js';\nimport * as output from './utils/output.js';\n\nconst program = new Command();\n\nprogram\n .name('yamchart')\n .description('Git-native business intelligence dashboards')\n .version('0.1.0');\n\nprogram\n .command('validate')\n .description('Validate configuration files')\n .argument('[path]', 'Path to yamchart project', '.')\n .option('--dry-run', 'Connect to database and test queries with EXPLAIN')\n .option('-c, --connection <name>', 'Connection to use for dry-run')\n .option('--json', 'Output as JSON')\n .action(async (path: string, options: { dryRun?: boolean; connection?: string; json?: boolean }) => {\n const startPath = resolve(path);\n const projectDir = await findProjectRoot(startPath);\n\n if (!projectDir) {\n if (options.json) {\n console.log(JSON.stringify({ success: false, error: 'yamchart.yaml not found' }));\n } else {\n output.error('yamchart.yaml not found');\n output.detail('Run this command from a yamchart project directory');\n }\n process.exit(2);\n }\n\n // Load .env file\n loadEnvFile(projectDir);\n\n if (!options.json) {\n output.header('Validating yamchart project...');\n }\n\n const result = await validateProject(projectDir, {\n dryRun: options.dryRun ?? false,\n connection: options.connection,\n });\n\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n } else {\n // Print results\n for (const error of result.errors) {\n output.error(error.file);\n output.detail(error.message);\n if (error.suggestion) {\n output.detail(error.suggestion);\n }\n }\n\n for (const warning of result.warnings) {\n output.warning(warning.file);\n output.detail(warning.message);\n }\n\n output.newline();\n\n if (result.success) {\n output.success(`Schema: ${result.stats.passed} passed`);\n } else {\n output.error(`Schema: ${result.stats.passed} passed, ${result.stats.failed} failed`);\n }\n\n if (result.dryRunStats) {\n output.newline();\n if (result.dryRunStats.failed === 0) {\n output.success(`Queries: ${result.dryRunStats.passed} passed (EXPLAIN OK)`);\n } else {\n output.error(`Queries: ${result.dryRunStats.passed} passed, ${result.dryRunStats.failed} failed`);\n }\n }\n\n output.newline();\n\n if (result.success) {\n output.success('Validation passed');\n } else {\n output.error(`Validation failed with ${result.errors.length} error(s)`);\n }\n }\n\n process.exit(result.success ? 0 : 1);\n });\n\nprogram\n .command('dev')\n .description('Start development server with hot reload')\n .argument('[path]', 'Path to yamchart project', '.')\n .option('-p, --port <number>', 'Port to listen on', '3001')\n .option('--api-only', 'Only serve API, no web UI')\n .option('--no-open', 'Do not open browser automatically')\n .action(async (path: string, options: { port: string; apiOnly?: boolean; open: boolean }) => {\n const startPath = resolve(path);\n const projectDir = await findProjectRoot(startPath);\n\n if (!projectDir) {\n output.error('yamchart.yaml not found');\n output.detail('Run this command from a yamchart project directory');\n process.exit(2);\n }\n\n const { runDevServer } = await import('./commands/dev.js');\n\n await runDevServer(projectDir, {\n port: parseInt(options.port, 10),\n apiOnly: options.apiOnly ?? false,\n open: options.open,\n });\n });\n\nprogram\n .command('init')\n .description('Create a new yamchart project')\n .argument('[directory]', 'Target directory', '.')\n .option('--example', 'Create full example project with sample database')\n .option('--empty', 'Create only yamchart.yaml (no connections, models, or charts)')\n .option('--force', 'Overwrite existing files')\n .action(async (directory: string, options: { example?: boolean; empty?: boolean; force?: boolean }) => {\n const { initProject } = await import('./commands/init.js');\n const targetDir = resolve(directory);\n\n const result = await initProject(targetDir, options);\n\n if (!result.success) {\n output.error(result.error || 'Failed to create project');\n process.exit(1);\n }\n\n output.newline();\n output.success(`Created ${directory === '.' ? basename(targetDir) : directory}/`);\n for (const file of result.files.slice(0, 10)) {\n output.detail(file);\n }\n if (result.files.length > 10) {\n output.detail(`... and ${result.files.length - 10} more files`);\n }\n output.newline();\n output.info(`Run \\`cd ${directory === '.' ? basename(targetDir) : directory} && yamchart dev\\` to start.`);\n });\n\nprogram.parse();\n"],"mappings":"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","names":["error","warning"]}
@@ -0,0 +1,118 @@
1
+ // src/commands/init.ts
2
+ import { mkdir, readFile, writeFile, access, readdir, copyFile } from "fs/promises";
3
+ import { join, dirname, basename } from "path";
4
+ import { fileURLToPath } from "url";
5
+ var __dirname = dirname(fileURLToPath(import.meta.url));
6
+ async function directoryExists(path) {
7
+ try {
8
+ await access(path);
9
+ return true;
10
+ } catch {
11
+ return false;
12
+ }
13
+ }
14
+ async function getTemplatesDir() {
15
+ const distTemplates = join(__dirname, "templates");
16
+ if (await directoryExists(distTemplates)) {
17
+ return distTemplates;
18
+ }
19
+ const srcTemplates = join(__dirname, "../templates");
20
+ if (await directoryExists(srcTemplates)) {
21
+ return srcTemplates;
22
+ }
23
+ throw new Error("Templates not found. Reinstall yamchart or check installation.");
24
+ }
25
+ async function getExamplesDir() {
26
+ const distExamples = join(__dirname, "../../../examples");
27
+ if (await directoryExists(distExamples)) {
28
+ return distExamples;
29
+ }
30
+ const srcExamples = join(__dirname, "../../../../examples");
31
+ if (await directoryExists(srcExamples)) {
32
+ return srcExamples;
33
+ }
34
+ throw new Error("Example assets not found. Reinstall yamchart or use default mode.");
35
+ }
36
+ async function initProject(projectDir, options) {
37
+ const projectName = basename(projectDir);
38
+ const files = [];
39
+ const yamchartYamlPath = join(projectDir, "yamchart.yaml");
40
+ try {
41
+ await access(yamchartYamlPath);
42
+ if (!options.force) {
43
+ return {
44
+ success: false,
45
+ files: [],
46
+ error: `${yamchartYamlPath} already exists. Use --force to overwrite.`
47
+ };
48
+ }
49
+ } catch {
50
+ }
51
+ await mkdir(projectDir, { recursive: true });
52
+ try {
53
+ if (options.example) {
54
+ const examplesDir = await getExamplesDir();
55
+ await copyDirectory(examplesDir, projectDir, files, projectName);
56
+ } else if (options.empty) {
57
+ const templateDir = join(await getTemplatesDir(), "empty");
58
+ await copyTemplate(templateDir, projectDir, files, projectName);
59
+ } else {
60
+ const templateDir = join(await getTemplatesDir(), "default");
61
+ await copyTemplate(templateDir, projectDir, files, projectName);
62
+ }
63
+ } catch (err) {
64
+ return {
65
+ success: false,
66
+ files: [],
67
+ error: err instanceof Error ? err.message : "Failed to create project"
68
+ };
69
+ }
70
+ return { success: true, files };
71
+ }
72
+ async function copyTemplate(templateDir, targetDir, files, projectName, basePath = "") {
73
+ const entries = await readdir(templateDir, { withFileTypes: true });
74
+ for (const entry of entries) {
75
+ const srcPath = join(templateDir, entry.name);
76
+ const destPath = join(targetDir, entry.name);
77
+ const relativePath = basePath ? `${basePath}/${entry.name}` : entry.name;
78
+ if (entry.isDirectory()) {
79
+ await mkdir(destPath, { recursive: true });
80
+ await copyTemplate(srcPath, destPath, files, projectName, relativePath);
81
+ } else {
82
+ let content = await readFile(srcPath, "utf-8");
83
+ content = content.replace(/\{\{name\}\}/g, projectName);
84
+ await writeFile(destPath, content, "utf-8");
85
+ files.push(relativePath);
86
+ }
87
+ }
88
+ }
89
+ async function copyDirectory(srcDir, destDir, files, projectName, basePath = "") {
90
+ const entries = await readdir(srcDir, { withFileTypes: true });
91
+ for (const entry of entries) {
92
+ if (entry.name === "node_modules" || entry.name === "pnpm-lock.yaml" || entry.name === "package-lock.json" || entry.name === "yarn.lock" || entry.name === "package.json" || entry.name.startsWith(".")) {
93
+ continue;
94
+ }
95
+ const srcPath = join(srcDir, entry.name);
96
+ const destPath = join(destDir, entry.name);
97
+ const relativePath = basePath ? `${basePath}/${entry.name}` : entry.name;
98
+ if (entry.isDirectory()) {
99
+ await mkdir(destPath, { recursive: true });
100
+ await copyDirectory(srcPath, destPath, files, projectName, relativePath);
101
+ } else {
102
+ if (entry.name.endsWith(".duckdb")) {
103
+ await copyFile(srcPath, destPath);
104
+ } else {
105
+ let content = await readFile(srcPath, "utf-8");
106
+ if (entry.name === "yamchart.yaml") {
107
+ content = content.replace(/^name:\s*\S+/m, `name: ${projectName}`);
108
+ }
109
+ await writeFile(destPath, content, "utf-8");
110
+ }
111
+ files.push(relativePath);
112
+ }
113
+ }
114
+ }
115
+ export {
116
+ initProject
117
+ };
118
+ //# sourceMappingURL=init-6D5VNGSP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/init.ts"],"sourcesContent":["import { mkdir, readFile, writeFile, access, readdir, copyFile } from 'fs/promises';\nimport { join, dirname, basename } from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nexport interface InitOptions {\n empty?: boolean;\n example?: boolean;\n force?: boolean;\n}\n\nexport interface InitResult {\n success: boolean;\n files: string[];\n error?: string;\n}\n\n/**\n * Check if a directory exists and is accessible.\n */\nasync function directoryExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Get the templates directory path.\n * Handles both development (src/commands) and production (dist/) contexts.\n */\nasync function getTemplatesDir(): Promise<string> {\n // In production (dist/), templates are at dist/templates\n const distTemplates = join(__dirname, 'templates');\n if (await directoryExists(distTemplates)) {\n return distTemplates;\n }\n\n // In development (src/commands), templates are at src/templates\n const srcTemplates = join(__dirname, '../templates');\n if (await directoryExists(srcTemplates)) {\n return srcTemplates;\n }\n\n throw new Error('Templates not found. Reinstall yamchart or check installation.');\n}\n\n/**\n * Get the examples directory path.\n * Handles both development and production contexts.\n */\nasync function getExamplesDir(): Promise<string> {\n // In production (dist/): dist -> cli -> apps -> yamchart -> examples\n const distExamples = join(__dirname, '../../../examples');\n if (await directoryExists(distExamples)) {\n return distExamples;\n }\n\n // In development (src/commands): src/commands -> src -> cli -> apps -> yamchart -> examples\n const srcExamples = join(__dirname, '../../../../examples');\n if (await directoryExists(srcExamples)) {\n return srcExamples;\n }\n\n throw new Error('Example assets not found. Reinstall yamchart or use default mode.');\n}\n\nexport async function initProject(projectDir: string, options: InitOptions): Promise<InitResult> {\n const projectName = basename(projectDir);\n const files: string[] = [];\n\n // Check if yamchart.yaml already exists\n const yamchartYamlPath = join(projectDir, 'yamchart.yaml');\n try {\n await access(yamchartYamlPath);\n if (!options.force) {\n return {\n success: false,\n files: [],\n error: `${yamchartYamlPath} already exists. Use --force to overwrite.`,\n };\n }\n } catch {\n // File doesn't exist, continue\n }\n\n // Create project directory\n await mkdir(projectDir, { recursive: true });\n\n try {\n if (options.example) {\n // Copy example project\n const examplesDir = await getExamplesDir();\n await copyDirectory(examplesDir, projectDir, files, projectName);\n } else if (options.empty) {\n // Empty mode - only yamchart.yaml\n const templateDir = join(await getTemplatesDir(), 'empty');\n await copyTemplate(templateDir, projectDir, files, projectName);\n } else {\n // Default mode - minimal working project\n const templateDir = join(await getTemplatesDir(), 'default');\n await copyTemplate(templateDir, projectDir, files, projectName);\n }\n } catch (err) {\n return {\n success: false,\n files: [],\n error: err instanceof Error ? err.message : 'Failed to create project',\n };\n }\n\n return { success: true, files };\n}\n\nasync function copyTemplate(\n templateDir: string,\n targetDir: string,\n files: string[],\n projectName: string,\n basePath: string = ''\n): Promise<void> {\n const entries = await readdir(templateDir, { withFileTypes: true });\n\n for (const entry of entries) {\n const srcPath = join(templateDir, entry.name);\n const destPath = join(targetDir, entry.name);\n const relativePath = basePath ? `${basePath}/${entry.name}` : entry.name;\n\n if (entry.isDirectory()) {\n await mkdir(destPath, { recursive: true });\n await copyTemplate(srcPath, destPath, files, projectName, relativePath);\n } else {\n let content = await readFile(srcPath, 'utf-8');\n content = content.replace(/\\{\\{name\\}\\}/g, projectName);\n await writeFile(destPath, content, 'utf-8');\n files.push(relativePath);\n }\n }\n}\n\nasync function copyDirectory(\n srcDir: string,\n destDir: string,\n files: string[],\n projectName: string,\n basePath: string = ''\n): Promise<void> {\n const entries = await readdir(srcDir, { withFileTypes: true });\n\n for (const entry of entries) {\n // Skip node_modules, lock files, and hidden files\n if (\n entry.name === 'node_modules' ||\n entry.name === 'pnpm-lock.yaml' ||\n entry.name === 'package-lock.json' ||\n entry.name === 'yarn.lock' ||\n entry.name === 'package.json' ||\n entry.name.startsWith('.')\n ) {\n continue;\n }\n\n const srcPath = join(srcDir, entry.name);\n const destPath = join(destDir, entry.name);\n const relativePath = basePath ? `${basePath}/${entry.name}` : entry.name;\n\n if (entry.isDirectory()) {\n await mkdir(destPath, { recursive: true });\n await copyDirectory(srcPath, destPath, files, projectName, relativePath);\n } else {\n // For binary files (like .duckdb), copy directly\n if (entry.name.endsWith('.duckdb')) {\n await copyFile(srcPath, destPath);\n } else {\n // For text files, replace project name in yamchart.yaml\n let content = await readFile(srcPath, 'utf-8');\n if (entry.name === 'yamchart.yaml') {\n content = content.replace(/^name:\\s*\\S+/m, `name: ${projectName}`);\n }\n await writeFile(destPath, content, 'utf-8');\n }\n files.push(relativePath);\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,18 @@
1
+ name: revenue-by-day
2
+ title: Daily Revenue
3
+ description: Sample chart showing daily revenue
4
+
5
+ source:
6
+ model: sample_orders
7
+
8
+ chart:
9
+ type: line
10
+ x:
11
+ field: order_date
12
+ type: temporal
13
+ label: Date
14
+ y:
15
+ field: revenue
16
+ type: quantitative
17
+ format: "$,.0f"
18
+ label: Revenue
@@ -0,0 +1,4 @@
1
+ name: local
2
+ type: duckdb
3
+ config:
4
+ path: ":memory:"
@@ -0,0 +1,11 @@
1
+ -- @name: sample_orders
2
+ -- @description: Sample orders data for demo
3
+
4
+ SELECT * FROM (
5
+ VALUES
6
+ ('2024-01-01'::date, 'Electronics', 1200),
7
+ ('2024-01-02'::date, 'Electronics', 850),
8
+ ('2024-01-03'::date, 'Clothing', 430),
9
+ ('2024-01-04'::date, 'Electronics', 1100),
10
+ ('2024-01-05'::date, 'Clothing', 520)
11
+ ) AS t(order_date, category, revenue)
@@ -0,0 +1,6 @@
1
+ name: {{name}}
2
+ version: "1.0"
3
+ default_connection: local
4
+
5
+ defaults:
6
+ cache_ttl: 5m
@@ -0,0 +1,6 @@
1
+ name: {{name}}
2
+ version: "1.0"
3
+ # default_connection: <connection-name>
4
+
5
+ defaults:
6
+ cache_ttl: 5m
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "yamchart",
3
+ "version": "0.1.0",
4
+ "description": "Git-native business intelligence dashboards",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Simon Spencer",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/simon-spenc/yamchart.git",
11
+ "directory": "apps/cli"
12
+ },
13
+ "homepage": "https://github.com/simon-spenc/yamchart#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/simon-spenc/yamchart/issues"
16
+ },
17
+ "keywords": [
18
+ "bi",
19
+ "business-intelligence",
20
+ "dashboards",
21
+ "yaml",
22
+ "sql",
23
+ "charts",
24
+ "analytics"
25
+ ],
26
+ "bin": {
27
+ "yamchart": "./bin/yamchart"
28
+ },
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "import": "./dist/index.js"
33
+ }
34
+ },
35
+ "dependencies": {
36
+ "commander": "^12.1.0",
37
+ "dotenv": "^16.4.0",
38
+ "ora": "^8.0.0",
39
+ "picocolors": "^1.1.0",
40
+ "yaml": "^2.7.0",
41
+ "@yamchart/query": "0.1.0",
42
+ "@yamchart/schema": "0.1.0",
43
+ "@yamchart/server": "0.1.0"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^22.0.0",
47
+ "tsup": "^8.0.0",
48
+ "typescript": "^5.7.0",
49
+ "vitest": "^2.1.0",
50
+ "@yamchart/config": "0.1.0"
51
+ },
52
+ "files": [
53
+ "dist",
54
+ "bin"
55
+ ],
56
+ "publishConfig": {
57
+ "access": "public"
58
+ },
59
+ "scripts": {
60
+ "build": "tsup",
61
+ "dev": "tsup --watch",
62
+ "test": "vitest run",
63
+ "test:watch": "vitest",
64
+ "clean": "rm -rf dist"
65
+ }
66
+ }