yamchart 0.1.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,315 @@
1
+ // src/commands/generate.ts
2
+ import { readFile } from "fs/promises";
3
+ import { join as join2 } from "path";
4
+ import { existsSync as existsSync2 } from "fs";
5
+
6
+ // src/generate/detector.ts
7
+ var DATE_TYPES = ["date", "timestamp", "datetime", "timestamptz", "timestamp_ntz"];
8
+ var NUMERIC_TYPES = ["int", "integer", "bigint", "smallint", "numeric", "decimal", "float", "double", "real", "number"];
9
+ function isDateColumn(col) {
10
+ const typeLower = (col.data_type || "").toLowerCase();
11
+ if (DATE_TYPES.some((t) => typeLower.includes(t))) return true;
12
+ const nameLower = col.name.toLowerCase();
13
+ return nameLower.endsWith("_at") || nameLower.endsWith("_date") || nameLower.endsWith("_time");
14
+ }
15
+ function isNumericColumn(col) {
16
+ const typeLower = (col.data_type || "").toLowerCase();
17
+ return NUMERIC_TYPES.some((t) => typeLower.includes(t));
18
+ }
19
+ function isPrimaryKey(col) {
20
+ if (col.hints.includes("primary_key") || col.hints.includes("unique")) return true;
21
+ return col.name === "id";
22
+ }
23
+ function isForeignKey(col) {
24
+ return col.hints.some((h) => h.startsWith("fk:"));
25
+ }
26
+ function detectColumnTypes(columns) {
27
+ const dateColumns = [];
28
+ const metricColumns = [];
29
+ const dimensionColumns = [];
30
+ const primaryKeys = [];
31
+ const foreignKeys = [];
32
+ for (const col of columns) {
33
+ if (isPrimaryKey(col)) {
34
+ primaryKeys.push(col.name);
35
+ continue;
36
+ }
37
+ if (isForeignKey(col)) {
38
+ foreignKeys.push(col.name);
39
+ continue;
40
+ }
41
+ if (isDateColumn(col)) {
42
+ dateColumns.push(col.name);
43
+ } else if (isNumericColumn(col)) {
44
+ metricColumns.push(col.name);
45
+ } else {
46
+ dimensionColumns.push(col.name);
47
+ }
48
+ }
49
+ return { dateColumns, metricColumns, dimensionColumns, primaryKeys, foreignKeys };
50
+ }
51
+
52
+ // src/generate/variants.ts
53
+ function formatDate() {
54
+ return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
55
+ }
56
+ function quoteIdentifier(name) {
57
+ return `"${name.replace(/"/g, '""')}"`;
58
+ }
59
+ function generateHeader(modelName, variantName, description, tableName) {
60
+ return `-- @generated: from dbt model '${modelName}' on ${formatDate()}
61
+ -- @name: ${variantName}
62
+ -- @description: ${description}
63
+ -- @source: ${tableName}
64
+
65
+ `;
66
+ }
67
+ function generateMetricSelects(metrics) {
68
+ return metrics.map((m) => ` ${m.aggregation.toUpperCase()}(${quoteIdentifier(m.name)}) AS ${quoteIdentifier(m.aggregation + "_" + m.name)}`).join(",\n");
69
+ }
70
+ function generateVariants(config) {
71
+ const variants = [];
72
+ const { modelName, tableName, dateColumn, metricColumns, dimensionColumns } = config;
73
+ if (dateColumn && metricColumns.length > 0) {
74
+ const name = `${modelName}_over_time`;
75
+ const description = `${modelName} aggregated over time`;
76
+ const sql = `${generateHeader(modelName, name, description, tableName)}SELECT
77
+ date_trunc('{{ granularity }}', ${quoteIdentifier(dateColumn)}) AS period,
78
+ ${generateMetricSelects(metricColumns)}
79
+ FROM ${tableName}
80
+ WHERE ${quoteIdentifier(dateColumn)} >= '{{ start_date }}'
81
+ AND ${quoteIdentifier(dateColumn)} <= '{{ end_date }}'
82
+ GROUP BY 1
83
+ ORDER BY 1
84
+ `;
85
+ variants.push({ name, filename: `${name}.sql`, description, sql });
86
+ }
87
+ for (const dim of dimensionColumns) {
88
+ const name = `${modelName}_by_${dim}`;
89
+ const description = `${modelName} grouped by ${dim}`;
90
+ let sql = generateHeader(modelName, name, description, tableName);
91
+ sql += `SELECT
92
+ ${quoteIdentifier(dim)},
93
+ ${generateMetricSelects(metricColumns)}
94
+ FROM ${tableName}
95
+ `;
96
+ if (dateColumn) {
97
+ sql += `WHERE ${quoteIdentifier(dateColumn)} >= '{{ start_date }}'
98
+ AND ${quoteIdentifier(dateColumn)} <= '{{ end_date }}'
99
+ `;
100
+ }
101
+ sql += `GROUP BY 1
102
+ ORDER BY 2 DESC
103
+ `;
104
+ variants.push({ name, filename: `${name}.sql`, description, sql });
105
+ }
106
+ if (metricColumns.length > 0) {
107
+ const name = `${modelName}_kpi`;
108
+ const description = `${modelName} summary metrics`;
109
+ let sql = generateHeader(modelName, name, description, tableName);
110
+ sql += `SELECT
111
+ ${generateMetricSelects(metricColumns)}
112
+ FROM ${tableName}
113
+ `;
114
+ if (dateColumn) {
115
+ sql += `WHERE ${quoteIdentifier(dateColumn)} >= '{{ start_date }}'
116
+ AND ${quoteIdentifier(dateColumn)} <= '{{ end_date }}'
117
+ `;
118
+ }
119
+ variants.push({ name, filename: `${name}.sql`, description, sql });
120
+ }
121
+ return variants;
122
+ }
123
+
124
+ // src/generate/writer.ts
125
+ import { writeFile, mkdir } from "fs/promises";
126
+ import { join } from "path";
127
+ import { existsSync } from "fs";
128
+ async function writeStub(projectDir, filename, content) {
129
+ const modelsDir = join(projectDir, "models");
130
+ await mkdir(modelsDir, { recursive: true });
131
+ await writeFile(join(modelsDir, filename), content, "utf-8");
132
+ }
133
+ async function stubExists(projectDir, filename) {
134
+ return existsSync(join(projectDir, "models", filename));
135
+ }
136
+
137
+ // src/generate/prompts.ts
138
+ import { confirm, select, checkbox } from "@inquirer/prompts";
139
+ function buildDateColumnChoices(detected) {
140
+ const choices = detected.dateColumns.map((col, i) => ({
141
+ name: col,
142
+ value: col,
143
+ checked: i === 0
144
+ }));
145
+ choices.push({ name: "Skip time series", value: null, checked: false });
146
+ return choices;
147
+ }
148
+ function buildMetricChoices(detected) {
149
+ return detected.metricColumns.map((col) => ({
150
+ name: `${col} (sum)`,
151
+ value: { name: col, aggregation: "sum" },
152
+ checked: true
153
+ }));
154
+ }
155
+ function buildDimensionChoices(detected) {
156
+ return detected.dimensionColumns.map((col) => ({
157
+ name: col,
158
+ value: col,
159
+ checked: true
160
+ }));
161
+ }
162
+ async function promptDateColumn(modelName, detected) {
163
+ if (detected.dateColumns.length === 0) return null;
164
+ const defaultCol = detected.dateColumns[0];
165
+ const confirmed = await confirm({
166
+ message: `${modelName}: Use '${defaultCol}' for time series?`,
167
+ default: true
168
+ });
169
+ if (confirmed) return defaultCol;
170
+ if (detected.dateColumns.length === 1) return null;
171
+ return select({
172
+ message: "Select date column:",
173
+ choices: buildDateColumnChoices(detected).map((c) => ({ name: c.name, value: c.value }))
174
+ });
175
+ }
176
+ async function promptMetrics(modelName, detected) {
177
+ if (detected.metricColumns.length === 0) return [];
178
+ const choices = buildMetricChoices(detected);
179
+ const selected = await checkbox({
180
+ message: `${modelName}: Select metrics to aggregate:`,
181
+ choices: choices.map((c) => ({ name: c.name, value: c.value, checked: c.checked }))
182
+ });
183
+ return selected;
184
+ }
185
+ async function promptDimensions(modelName, detected) {
186
+ if (detected.dimensionColumns.length === 0) return [];
187
+ const choices = buildDimensionChoices(detected);
188
+ const selected = await checkbox({
189
+ message: `${modelName}: Select dimensions for grouping:`,
190
+ choices: choices.map((c) => ({ name: c.name, value: c.value, checked: c.checked }))
191
+ });
192
+ return selected;
193
+ }
194
+ async function promptConfirmVariants(modelName, variantNames) {
195
+ const choices = variantNames.map((name) => ({ name, value: name, checked: true }));
196
+ return checkbox({
197
+ message: `${modelName}: Generate these stubs?`,
198
+ choices
199
+ });
200
+ }
201
+ async function promptOverwrite(filename) {
202
+ return select({
203
+ message: `${filename} already exists:`,
204
+ choices: [
205
+ { name: "Overwrite", value: "overwrite" },
206
+ { name: "Skip", value: "skip" },
207
+ { name: "Rename (add suffix)", value: "rename" }
208
+ ]
209
+ });
210
+ }
211
+
212
+ // src/commands/generate.ts
213
+ async function loadCatalog(projectDir) {
214
+ const catalogPath = join2(projectDir, ".yamchart", "catalog.json");
215
+ if (!existsSync2(catalogPath)) return null;
216
+ const content = await readFile(catalogPath, "utf-8");
217
+ return JSON.parse(content);
218
+ }
219
+ async function processModel(projectDir, model, yolo) {
220
+ const detected = detectColumnTypes(model.columns);
221
+ let dateColumn;
222
+ let metrics;
223
+ let dimensions;
224
+ if (yolo) {
225
+ dateColumn = detected.dateColumns[0] || null;
226
+ metrics = detected.metricColumns.map((name) => ({ name, aggregation: "sum" }));
227
+ dimensions = detected.dimensionColumns;
228
+ } else {
229
+ dateColumn = await promptDateColumn(model.name, detected);
230
+ metrics = await promptMetrics(model.name, detected);
231
+ dimensions = await promptDimensions(model.name, detected);
232
+ }
233
+ if (metrics.length === 0) {
234
+ return { created: 0, skipped: 0 };
235
+ }
236
+ const variants = generateVariants({
237
+ modelName: model.name,
238
+ tableName: model.table || model.name,
239
+ dateColumn,
240
+ metricColumns: metrics,
241
+ dimensionColumns: dimensions
242
+ });
243
+ let confirmedVariants = variants;
244
+ if (!yolo && variants.length > 0) {
245
+ const selected = await promptConfirmVariants(
246
+ model.name,
247
+ variants.map((v) => v.name)
248
+ );
249
+ confirmedVariants = variants.filter((v) => selected.includes(v.name));
250
+ }
251
+ let created = 0;
252
+ let skipped = 0;
253
+ for (const variant of confirmedVariants) {
254
+ const exists = await stubExists(projectDir, variant.filename);
255
+ if (exists && !yolo) {
256
+ const action = await promptOverwrite(variant.filename);
257
+ if (action === "skip") {
258
+ skipped++;
259
+ continue;
260
+ }
261
+ if (action === "rename") {
262
+ variant.filename = variant.filename.replace(".sql", "_new.sql");
263
+ }
264
+ }
265
+ await writeStub(projectDir, variant.filename, variant.sql);
266
+ created++;
267
+ }
268
+ return { created, skipped };
269
+ }
270
+ async function generate(projectDir, options) {
271
+ const catalog = await loadCatalog(projectDir);
272
+ if (!catalog) {
273
+ return {
274
+ success: false,
275
+ error: "catalog.json not found. Run `yamchart sync-dbt` first.",
276
+ modelsProcessed: 0,
277
+ filesCreated: 0,
278
+ filesSkipped: 0
279
+ };
280
+ }
281
+ let models = catalog.models;
282
+ if (options.model) {
283
+ models = models.filter((m) => m.name === options.model);
284
+ if (models.length === 0) {
285
+ return {
286
+ success: false,
287
+ error: `Model '${options.model}' not found in catalog`,
288
+ modelsProcessed: 0,
289
+ filesCreated: 0,
290
+ filesSkipped: 0
291
+ };
292
+ }
293
+ }
294
+ let totalCreated = 0;
295
+ let totalSkipped = 0;
296
+ for (const model of models) {
297
+ const { created, skipped } = await processModel(
298
+ projectDir,
299
+ model,
300
+ options.yolo ?? false
301
+ );
302
+ totalCreated += created;
303
+ totalSkipped += skipped;
304
+ }
305
+ return {
306
+ success: true,
307
+ modelsProcessed: models.length,
308
+ filesCreated: totalCreated,
309
+ filesSkipped: totalSkipped
310
+ };
311
+ }
312
+ export {
313
+ generate
314
+ };
315
+ //# sourceMappingURL=generate-RD3LCS73.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/generate.ts","../src/generate/detector.ts","../src/generate/variants.ts","../src/generate/writer.ts","../src/generate/prompts.ts"],"sourcesContent":["// apps/cli/src/commands/generate.ts\nimport { readFile } from 'fs/promises';\nimport { join } from 'path';\nimport { existsSync } from 'fs';\nimport type { CatalogData, CatalogModel } from '../dbt/catalog.js';\nimport { detectColumnTypes } from '../generate/detector.js';\nimport { generateVariants, type MetricColumn } from '../generate/variants.js';\nimport { writeStub, stubExists } from '../generate/writer.js';\nimport {\n promptDateColumn,\n promptMetrics,\n promptDimensions,\n promptConfirmVariants,\n promptOverwrite,\n} from '../generate/prompts.js';\n\nexport interface GenerateOptions {\n model?: string;\n yolo?: boolean;\n}\n\nexport interface GenerateResult {\n success: boolean;\n error?: string;\n modelsProcessed: number;\n filesCreated: number;\n filesSkipped: number;\n}\n\nasync function loadCatalog(projectDir: string): Promise<CatalogData | null> {\n const catalogPath = join(projectDir, '.yamchart', 'catalog.json');\n if (!existsSync(catalogPath)) return null;\n\n const content = await readFile(catalogPath, 'utf-8');\n return JSON.parse(content);\n}\n\nasync function processModel(\n projectDir: string,\n model: CatalogModel,\n yolo: boolean\n): Promise<{ created: number; skipped: number }> {\n const detected = detectColumnTypes(model.columns);\n\n let dateColumn: string | null;\n let metrics: MetricColumn[];\n let dimensions: string[];\n\n if (yolo) {\n // Use all defaults\n dateColumn = detected.dateColumns[0] || null;\n metrics = detected.metricColumns.map(name => ({ name, aggregation: 'sum' as const }));\n dimensions = detected.dimensionColumns;\n } else {\n // Interactive prompts\n dateColumn = await promptDateColumn(model.name, detected);\n metrics = await promptMetrics(model.name, detected);\n dimensions = await promptDimensions(model.name, detected);\n }\n\n if (metrics.length === 0) {\n return { created: 0, skipped: 0 };\n }\n\n const variants = generateVariants({\n modelName: model.name,\n tableName: model.table || model.name,\n dateColumn,\n metricColumns: metrics,\n dimensionColumns: dimensions,\n });\n\n let confirmedVariants = variants;\n if (!yolo && variants.length > 0) {\n const selected = await promptConfirmVariants(\n model.name,\n variants.map(v => v.name)\n );\n confirmedVariants = variants.filter(v => selected.includes(v.name));\n }\n\n let created = 0;\n let skipped = 0;\n\n for (const variant of confirmedVariants) {\n const exists = await stubExists(projectDir, variant.filename);\n\n if (exists && !yolo) {\n const action = await promptOverwrite(variant.filename);\n if (action === 'skip') {\n skipped++;\n continue;\n }\n if (action === 'rename') {\n variant.filename = variant.filename.replace('.sql', '_new.sql');\n }\n }\n\n await writeStub(projectDir, variant.filename, variant.sql);\n created++;\n }\n\n return { created, skipped };\n}\n\nexport async function generate(\n projectDir: string,\n options: GenerateOptions\n): Promise<GenerateResult> {\n const catalog = await loadCatalog(projectDir);\n\n if (!catalog) {\n return {\n success: false,\n error: 'catalog.json not found. Run `yamchart sync-dbt` first.',\n modelsProcessed: 0,\n filesCreated: 0,\n filesSkipped: 0,\n };\n }\n\n let models = catalog.models;\n\n if (options.model) {\n models = models.filter(m => m.name === options.model);\n if (models.length === 0) {\n return {\n success: false,\n error: `Model '${options.model}' not found in catalog`,\n modelsProcessed: 0,\n filesCreated: 0,\n filesSkipped: 0,\n };\n }\n }\n\n let totalCreated = 0;\n let totalSkipped = 0;\n\n for (const model of models) {\n const { created, skipped } = await processModel(\n projectDir,\n model,\n options.yolo ?? false\n );\n totalCreated += created;\n totalSkipped += skipped;\n }\n\n return {\n success: true,\n modelsProcessed: models.length,\n filesCreated: totalCreated,\n filesSkipped: totalSkipped,\n };\n}\n","import type { DbtColumn } from '../dbt/types.js';\n\nexport interface DetectedColumns {\n dateColumns: string[];\n metricColumns: string[];\n dimensionColumns: string[];\n primaryKeys: string[];\n foreignKeys: string[];\n}\n\nconst DATE_TYPES = ['date', 'timestamp', 'datetime', 'timestamptz', 'timestamp_ntz'];\nconst NUMERIC_TYPES = ['int', 'integer', 'bigint', 'smallint', 'numeric', 'decimal', 'float', 'double', 'real', 'number'];\nconst STRING_TYPES = ['string', 'varchar', 'char', 'text'];\nconst BOOLEAN_TYPES = ['boolean', 'bool', 'bit'];\n\nfunction isDateColumn(col: DbtColumn): boolean {\n const typeLower = (col.data_type || '').toLowerCase();\n if (DATE_TYPES.some(t => typeLower.includes(t))) return true;\n\n const nameLower = col.name.toLowerCase();\n return nameLower.endsWith('_at') || nameLower.endsWith('_date') || nameLower.endsWith('_time');\n}\n\nfunction isNumericColumn(col: DbtColumn): boolean {\n const typeLower = (col.data_type || '').toLowerCase();\n return NUMERIC_TYPES.some(t => typeLower.includes(t));\n}\n\nfunction isStringColumn(col: DbtColumn): boolean {\n const typeLower = (col.data_type || '').toLowerCase();\n return STRING_TYPES.some(t => typeLower.includes(t));\n}\n\nfunction isBooleanColumn(col: DbtColumn): boolean {\n const typeLower = (col.data_type || '').toLowerCase();\n return BOOLEAN_TYPES.some(t => typeLower.includes(t));\n}\n\nfunction isPrimaryKey(col: DbtColumn): boolean {\n if (col.hints.includes('primary_key') || col.hints.includes('unique')) return true;\n return col.name === 'id';\n}\n\nfunction isForeignKey(col: DbtColumn): boolean {\n return col.hints.some(h => h.startsWith('fk:'));\n}\n\nexport function detectColumnTypes(columns: DbtColumn[]): DetectedColumns {\n const dateColumns: string[] = [];\n const metricColumns: string[] = [];\n const dimensionColumns: string[] = [];\n const primaryKeys: string[] = [];\n const foreignKeys: string[] = [];\n\n for (const col of columns) {\n if (isPrimaryKey(col)) {\n primaryKeys.push(col.name);\n continue;\n }\n\n if (isForeignKey(col)) {\n foreignKeys.push(col.name);\n continue;\n }\n\n if (isDateColumn(col)) {\n dateColumns.push(col.name);\n } else if (isNumericColumn(col)) {\n metricColumns.push(col.name);\n } else {\n // String, boolean, or unknown types all become dimensions\n dimensionColumns.push(col.name);\n }\n }\n\n return { dateColumns, metricColumns, dimensionColumns, primaryKeys, foreignKeys };\n}\n","// apps/cli/src/generate/variants.ts\n\nexport interface MetricColumn {\n name: string;\n aggregation: 'sum' | 'avg' | 'count' | 'min' | 'max';\n}\n\nexport interface VariantConfig {\n modelName: string;\n tableName: string;\n dateColumn: string | null;\n metricColumns: MetricColumn[];\n dimensionColumns: string[];\n}\n\nexport interface GeneratedVariant {\n name: string;\n filename: string;\n description: string;\n sql: string;\n}\n\nfunction formatDate(): string {\n return new Date().toISOString().split('T')[0];\n}\n\nfunction quoteIdentifier(name: string): string {\n // Double quotes for ANSI SQL standard\n // Escape any existing double quotes by doubling them\n return `\"${name.replace(/\"/g, '\"\"')}\"`;\n}\n\nfunction generateHeader(modelName: string, variantName: string, description: string, tableName: string): string {\n return `-- @generated: from dbt model '${modelName}' on ${formatDate()}\n-- @name: ${variantName}\n-- @description: ${description}\n-- @source: ${tableName}\n\n`;\n}\n\nfunction generateMetricSelects(metrics: MetricColumn[]): string {\n return metrics\n .map(m => ` ${m.aggregation.toUpperCase()}(${quoteIdentifier(m.name)}) AS ${quoteIdentifier(m.aggregation + '_' + m.name)}`)\n .join(',\\n');\n}\n\nexport function generateVariants(config: VariantConfig): GeneratedVariant[] {\n const variants: GeneratedVariant[] = [];\n const { modelName, tableName, dateColumn, metricColumns, dimensionColumns } = config;\n\n // Time series variant\n if (dateColumn && metricColumns.length > 0) {\n const name = `${modelName}_over_time`;\n const description = `${modelName} aggregated over time`;\n const sql = `${generateHeader(modelName, name, description, tableName)}SELECT\n date_trunc('{{ granularity }}', ${quoteIdentifier(dateColumn)}) AS period,\n${generateMetricSelects(metricColumns)}\nFROM ${tableName}\nWHERE ${quoteIdentifier(dateColumn)} >= '{{ start_date }}'\n AND ${quoteIdentifier(dateColumn)} <= '{{ end_date }}'\nGROUP BY 1\nORDER BY 1\n`;\n variants.push({ name, filename: `${name}.sql`, description, sql });\n }\n\n // Dimension variants\n for (const dim of dimensionColumns) {\n const name = `${modelName}_by_${dim}`;\n const description = `${modelName} grouped by ${dim}`;\n let sql = generateHeader(modelName, name, description, tableName);\n sql += `SELECT\n ${quoteIdentifier(dim)},\n${generateMetricSelects(metricColumns)}\nFROM ${tableName}\n`;\n if (dateColumn) {\n sql += `WHERE ${quoteIdentifier(dateColumn)} >= '{{ start_date }}'\n AND ${quoteIdentifier(dateColumn)} <= '{{ end_date }}'\n`;\n }\n sql += `GROUP BY 1\nORDER BY 2 DESC\n`;\n variants.push({ name, filename: `${name}.sql`, description, sql });\n }\n\n // KPI variant\n if (metricColumns.length > 0) {\n const name = `${modelName}_kpi`;\n const description = `${modelName} summary metrics`;\n let sql = generateHeader(modelName, name, description, tableName);\n sql += `SELECT\n${generateMetricSelects(metricColumns)}\nFROM ${tableName}\n`;\n if (dateColumn) {\n sql += `WHERE ${quoteIdentifier(dateColumn)} >= '{{ start_date }}'\n AND ${quoteIdentifier(dateColumn)} <= '{{ end_date }}'\n`;\n }\n variants.push({ name, filename: `${name}.sql`, description, sql });\n }\n\n return variants;\n}\n","// apps/cli/src/generate/writer.ts\nimport { writeFile, mkdir } from 'fs/promises';\nimport { join } from 'path';\nimport { existsSync } from 'fs';\n\nexport async function writeStub(projectDir: string, filename: string, content: string): Promise<void> {\n const modelsDir = join(projectDir, 'models');\n await mkdir(modelsDir, { recursive: true });\n await writeFile(join(modelsDir, filename), content, 'utf-8');\n}\n\nexport async function stubExists(projectDir: string, filename: string): Promise<boolean> {\n return existsSync(join(projectDir, 'models', filename));\n}\n","// apps/cli/src/generate/prompts.ts\nimport { confirm, select, checkbox } from '@inquirer/prompts';\nimport type { DetectedColumns } from './detector.js';\nimport type { MetricColumn } from './variants.js';\n\nexport interface DateColumnChoice {\n name: string;\n value: string | null;\n checked: boolean;\n}\n\nexport interface MetricChoice {\n name: string;\n value: MetricColumn;\n checked: boolean;\n}\n\nexport interface DimensionChoice {\n name: string;\n value: string;\n checked: boolean;\n}\n\nexport function buildDateColumnChoices(detected: DetectedColumns): DateColumnChoice[] {\n const choices: DateColumnChoice[] = detected.dateColumns.map((col, i) => ({\n name: col,\n value: col,\n checked: i === 0,\n }));\n choices.push({ name: 'Skip time series', value: null, checked: false });\n return choices;\n}\n\nexport function buildMetricChoices(detected: DetectedColumns): MetricChoice[] {\n return detected.metricColumns.map(col => ({\n name: `${col} (sum)`,\n value: { name: col, aggregation: 'sum' as const },\n checked: true,\n }));\n}\n\nexport function buildDimensionChoices(detected: DetectedColumns): DimensionChoice[] {\n return detected.dimensionColumns.map(col => ({\n name: col,\n value: col,\n checked: true,\n }));\n}\n\nexport async function promptDateColumn(modelName: string, detected: DetectedColumns): Promise<string | null> {\n if (detected.dateColumns.length === 0) return null;\n\n const defaultCol = detected.dateColumns[0];\n const confirmed = await confirm({\n message: `${modelName}: Use '${defaultCol}' for time series?`,\n default: true,\n });\n\n if (confirmed) return defaultCol;\n\n if (detected.dateColumns.length === 1) return null;\n\n return select({\n message: 'Select date column:',\n choices: buildDateColumnChoices(detected).map(c => ({ name: c.name, value: c.value })),\n });\n}\n\nexport async function promptMetrics(modelName: string, detected: DetectedColumns): Promise<MetricColumn[]> {\n if (detected.metricColumns.length === 0) return [];\n\n const choices = buildMetricChoices(detected);\n const selected = await checkbox({\n message: `${modelName}: Select metrics to aggregate:`,\n choices: choices.map(c => ({ name: c.name, value: c.value, checked: c.checked })),\n });\n\n return selected;\n}\n\nexport async function promptDimensions(modelName: string, detected: DetectedColumns): Promise<string[]> {\n if (detected.dimensionColumns.length === 0) return [];\n\n const choices = buildDimensionChoices(detected);\n const selected = await checkbox({\n message: `${modelName}: Select dimensions for grouping:`,\n choices: choices.map(c => ({ name: c.name, value: c.value, checked: c.checked })),\n });\n\n return selected;\n}\n\nexport async function promptConfirmVariants(modelName: string, variantNames: string[]): Promise<string[]> {\n const choices = variantNames.map(name => ({ name, value: name, checked: true }));\n\n return checkbox({\n message: `${modelName}: Generate these stubs?`,\n choices,\n });\n}\n\nexport async function promptOverwrite(filename: string): Promise<'overwrite' | 'skip' | 'rename'> {\n return select({\n message: `${filename} already exists:`,\n choices: [\n { name: 'Overwrite', value: 'overwrite' as const },\n { name: 'Skip', value: 'skip' as const },\n { name: 'Rename (add suffix)', value: 'rename' as const },\n ],\n });\n}\n"],"mappings":"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","names":["join","existsSync","join","existsSync"]}
package/dist/index.js CHANGED
@@ -1,22 +1,33 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ findProjectRoot,
4
+ loadEnvFile,
5
+ validateProject
6
+ } from "./chunk-6GDL3DH4.js";
7
+ import {
8
+ checkForUpdate
9
+ } from "./chunk-3CLMQNNR.js";
2
10
  import {
3
11
  detail,
4
12
  error,
5
- findProjectRoot,
6
13
  header,
7
14
  info,
8
- loadEnvFile,
9
15
  newline,
16
+ spinner,
10
17
  success,
11
- validateProject,
12
18
  warning
13
- } from "./chunk-TBILHUB3.js";
19
+ } from "./chunk-HJVVHYVN.js";
14
20
 
15
21
  // src/index.ts
16
22
  import { Command } from "commander";
17
- import { resolve, basename } from "path";
23
+ import { resolve, basename, dirname, join } from "path";
24
+ import { readFileSync } from "fs";
25
+ import { fileURLToPath } from "url";
26
+ import pc from "picocolors";
27
+ var __dirname = dirname(fileURLToPath(import.meta.url));
28
+ var pkg = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
18
29
  var program = new Command();
19
- program.name("yamchart").description("Git-native business intelligence dashboards").version("0.1.0");
30
+ program.name("yamchart").description("Git-native business intelligence dashboards").version(pkg.version);
20
31
  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
32
  const startPath = resolve(path);
22
33
  const projectDir = await findProjectRoot(startPath);
@@ -82,7 +93,7 @@ program.command("dev").description("Start development server with hot reload").a
82
93
  detail("Run this command from a yamchart project directory");
83
94
  process.exit(2);
84
95
  }
85
- const { runDevServer } = await import("./dev-UHYN2RXH.js");
96
+ const { runDevServer } = await import("./dev-HMLMSTA7.js");
86
97
  await runDevServer(projectDir, {
87
98
  port: parseInt(options.port, 10),
88
99
  apiOnly: options.apiOnly ?? false,
@@ -108,5 +119,131 @@ program.command("init").description("Create a new yamchart project").argument("[
108
119
  newline();
109
120
  info(`Run \`cd ${directory === "." ? basename(targetDir) : directory} && yamchart dev\` to start.`);
110
121
  });
122
+ program.command("sync-dbt").description("Sync dbt project metadata into AI-readable catalog").option("-s, --source <type>", "Source type: local, github, dbt-cloud", "local").option("-p, --path <dir>", "Path to dbt project (for local source)").option("--repo <repo>", "GitHub repository (for github source)").option("--branch <branch>", "Git branch (for github source)", "main").option("-i, --include <patterns...>", "Include glob patterns").option("-e, --exclude <patterns...>", "Exclude glob patterns").option("-t, --tag <tags...>", "Filter by dbt tags").option("--refresh", "Re-sync using saved configuration").action(async (options) => {
123
+ const { syncDbt, loadSyncConfig } = await import("./sync-dbt-IDDD4X2Z.js");
124
+ const projectDir = await findProjectRoot(process.cwd());
125
+ if (!projectDir) {
126
+ error("yamchart.yaml not found");
127
+ detail("Run this command from a yamchart project directory");
128
+ process.exit(2);
129
+ }
130
+ if (options.refresh) {
131
+ const savedConfig = await loadSyncConfig(projectDir);
132
+ if (!savedConfig) {
133
+ error("No saved sync config found");
134
+ detail("Run sync-dbt without --refresh first");
135
+ process.exit(1);
136
+ }
137
+ info(`Re-syncing from ${savedConfig.source}:${savedConfig.path || savedConfig.repo}`);
138
+ }
139
+ const spin = spinner("Syncing dbt metadata...");
140
+ const result = await syncDbt(projectDir, {
141
+ source: options.source,
142
+ path: options.path,
143
+ repo: options.repo,
144
+ branch: options.branch,
145
+ include: options.include || [],
146
+ exclude: options.exclude || [],
147
+ tags: options.tag || [],
148
+ refresh: options.refresh
149
+ });
150
+ spin.stop();
151
+ if (!result.success) {
152
+ error(result.error || "Sync failed");
153
+ process.exit(1);
154
+ }
155
+ success(`Synced ${result.modelsIncluded} models to .yamchart/catalog.md`);
156
+ if (result.modelsExcluded > 0) {
157
+ detail(`${result.modelsExcluded} models filtered out`);
158
+ }
159
+ });
160
+ program.command("generate").description("Generate SQL model stubs from dbt catalog").argument("[model]", "Specific model to generate (optional)").option("--yolo", "Skip all prompts, use defaults for everything").action(async (model, options) => {
161
+ const { generate } = await import("./generate-RD3LCS73.js");
162
+ const projectDir = await findProjectRoot(process.cwd());
163
+ if (!projectDir) {
164
+ error("yamchart.yaml not found");
165
+ detail("Run this command from a yamchart project directory");
166
+ process.exit(2);
167
+ }
168
+ const result = await generate(projectDir, {
169
+ model,
170
+ yolo: options.yolo
171
+ });
172
+ if (!result.success) {
173
+ error(result.error || "Generate failed");
174
+ process.exit(1);
175
+ }
176
+ success(`Generated ${result.filesCreated} model stubs`);
177
+ if (result.filesSkipped > 0) {
178
+ detail(`${result.filesSkipped} files skipped`);
179
+ }
180
+ });
181
+ program.command("test").description("Run model tests (@returns schema checks and @tests data assertions)").argument("[model]", "Specific model to test (optional, tests all if omitted)").option("-c, --connection <name>", "Connection to use (overrides default)").option("--json", "Output as JSON").action(async (model, options) => {
182
+ const startPath = resolve(".");
183
+ const projectDir = await findProjectRoot(startPath);
184
+ if (!projectDir) {
185
+ if (options.json) {
186
+ console.log(JSON.stringify({ success: false, error: "yamchart.yaml not found" }));
187
+ } else {
188
+ error("yamchart.yaml not found");
189
+ detail("Run this command from a yamchart project directory");
190
+ }
191
+ process.exit(2);
192
+ }
193
+ loadEnvFile(projectDir);
194
+ try {
195
+ const { testProject, formatTestOutput } = await import("./test-N4KIIKQN.js");
196
+ const result = await testProject(projectDir, model, {
197
+ connection: options.connection,
198
+ json: options.json
199
+ });
200
+ if (options.json) {
201
+ console.log(JSON.stringify(result, null, 2));
202
+ } else {
203
+ formatTestOutput(result, result.connectionName);
204
+ }
205
+ process.exit(result.success ? 0 : 1);
206
+ } catch (err) {
207
+ if (options.json) {
208
+ console.log(
209
+ JSON.stringify({ success: false, error: err instanceof Error ? err.message : String(err) })
210
+ );
211
+ } else {
212
+ error(err instanceof Error ? err.message : String(err));
213
+ }
214
+ process.exit(2);
215
+ }
216
+ });
217
+ program.command("update").description("Check for yamchart updates").action(async () => {
218
+ const { runUpdate } = await import("./update-QHLCWS56.js");
219
+ await runUpdate(pkg.version);
220
+ });
221
+ program.command("reset-password").description("Reset a user password (requires auth to be enabled)").requiredOption("-e, --email <email>", "Email address of the user").action(async (options) => {
222
+ const startPath = resolve(".");
223
+ const projectDir = await findProjectRoot(startPath);
224
+ if (!projectDir) {
225
+ error("yamchart.yaml not found");
226
+ detail("Run this command from a yamchart project directory");
227
+ process.exit(2);
228
+ }
229
+ loadEnvFile(projectDir);
230
+ const { resetPassword } = await import("./reset-password-MJ54ICGP.js");
231
+ await resetPassword(projectDir, options.email);
232
+ });
111
233
  program.parse();
234
+ var updateNotification = null;
235
+ checkForUpdate(pkg.version).then((update) => {
236
+ if (update) {
237
+ updateNotification = `
238
+ Update available: ${update.current} \u2192 ${update.latest}
239
+ Run: yamchart update
240
+ `;
241
+ }
242
+ });
243
+ process.on("exit", () => {
244
+ if (updateNotification) {
245
+ console.error(`
246
+ ${pc.dim(updateNotification)}`);
247
+ }
248
+ });
112
249
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +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"]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from 'commander';\nimport { resolve, basename, dirname, join } from 'path';\nimport { readFileSync } from 'fs';\nimport { fileURLToPath } from 'url';\nimport { validateProject } from './commands/validate.js';\nimport { findProjectRoot, loadEnvFile } from './utils/config.js';\nimport pc from 'picocolors';\nimport * as output from './utils/output.js';\nimport { checkForUpdate } from './utils/update-check.js';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst pkg = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8'));\n\nconst program = new Command();\n\nprogram\n .name('yamchart')\n .description('Git-native business intelligence dashboards')\n .version(pkg.version);\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\n .command('sync-dbt')\n .description('Sync dbt project metadata into AI-readable catalog')\n .option('-s, --source <type>', 'Source type: local, github, dbt-cloud', 'local')\n .option('-p, --path <dir>', 'Path to dbt project (for local source)')\n .option('--repo <repo>', 'GitHub repository (for github source)')\n .option('--branch <branch>', 'Git branch (for github source)', 'main')\n .option('-i, --include <patterns...>', 'Include glob patterns')\n .option('-e, --exclude <patterns...>', 'Exclude glob patterns')\n .option('-t, --tag <tags...>', 'Filter by dbt tags')\n .option('--refresh', 'Re-sync using saved configuration')\n .action(async (options: {\n source: 'local' | 'github' | 'dbt-cloud';\n path?: string;\n repo?: string;\n branch?: string;\n include?: string[];\n exclude?: string[];\n tag?: string[];\n refresh?: boolean;\n }) => {\n const { syncDbt, loadSyncConfig } = await import('./commands/sync-dbt.js');\n\n // Find project root\n const projectDir = await findProjectRoot(process.cwd());\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 // Handle refresh mode\n if (options.refresh) {\n const savedConfig = await loadSyncConfig(projectDir);\n if (!savedConfig) {\n output.error('No saved sync config found');\n output.detail('Run sync-dbt without --refresh first');\n process.exit(1);\n }\n output.info(`Re-syncing from ${savedConfig.source}:${savedConfig.path || savedConfig.repo}`);\n }\n\n const spin = output.spinner('Syncing dbt metadata...');\n\n const result = await syncDbt(projectDir, {\n source: options.source,\n path: options.path,\n repo: options.repo,\n branch: options.branch,\n include: options.include || [],\n exclude: options.exclude || [],\n tags: options.tag || [],\n refresh: options.refresh,\n });\n\n spin.stop();\n\n if (!result.success) {\n output.error(result.error || 'Sync failed');\n process.exit(1);\n }\n\n output.success(`Synced ${result.modelsIncluded} models to .yamchart/catalog.md`);\n if (result.modelsExcluded > 0) {\n output.detail(`${result.modelsExcluded} models filtered out`);\n }\n });\n\nprogram\n .command('generate')\n .description('Generate SQL model stubs from dbt catalog')\n .argument('[model]', 'Specific model to generate (optional)')\n .option('--yolo', 'Skip all prompts, use defaults for everything')\n .action(async (model: string | undefined, options: { yolo?: boolean }) => {\n const { generate } = await import('./commands/generate.js');\n\n const projectDir = await findProjectRoot(process.cwd());\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 result = await generate(projectDir, {\n model,\n yolo: options.yolo,\n });\n\n if (!result.success) {\n output.error(result.error || 'Generate failed');\n process.exit(1);\n }\n\n output.success(`Generated ${result.filesCreated} model stubs`);\n if (result.filesSkipped > 0) {\n output.detail(`${result.filesSkipped} files skipped`);\n }\n });\n\nprogram\n .command('test')\n .description('Run model tests (@returns schema checks and @tests data assertions)')\n .argument('[model]', 'Specific model to test (optional, tests all if omitted)')\n .option('-c, --connection <name>', 'Connection to use (overrides default)')\n .option('--json', 'Output as JSON')\n .action(async (model: string | undefined, options: { connection?: string; json?: boolean }) => {\n const startPath = resolve('.');\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 loadEnvFile(projectDir);\n\n try {\n const { testProject, formatTestOutput } = await import('./commands/test.js');\n const result = await testProject(projectDir, model, {\n connection: options.connection,\n json: options.json,\n });\n\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n } else {\n formatTestOutput(result, result.connectionName);\n }\n\n process.exit(result.success ? 0 : 1);\n } catch (err) {\n if (options.json) {\n console.log(\n JSON.stringify({ success: false, error: err instanceof Error ? err.message : String(err) }),\n );\n } else {\n output.error(err instanceof Error ? err.message : String(err));\n }\n process.exit(2);\n }\n });\n\nprogram\n .command('update')\n .description('Check for yamchart updates')\n .action(async () => {\n const { runUpdate } = await import('./commands/update.js');\n await runUpdate(pkg.version);\n });\n\nprogram\n .command('reset-password')\n .description('Reset a user password (requires auth to be enabled)')\n .requiredOption('-e, --email <email>', 'Email address of the user')\n .action(async (options: { email: string }) => {\n const startPath = resolve('.');\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 loadEnvFile(projectDir);\n\n const { resetPassword } = await import('./commands/reset-password.js');\n await resetPassword(projectDir, options.email);\n });\n\nprogram.parse();\n\n// Passive update check — runs in background, prints on exit if outdated\nlet updateNotification: string | null = null;\n\ncheckForUpdate(pkg.version).then((update) => {\n if (update) {\n updateNotification = `\\n Update available: ${update.current} → ${update.latest}\\n Run: yamchart update\\n`;\n }\n});\n\nprocess.on('exit', () => {\n if (updateNotification) {\n // Use dim styling so it doesn't compete with command output\n console.error(`\\n${pc.dim(updateNotification)}`);\n }\n});\n"],"mappings":"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","names":["error","warning"]}
@@ -0,0 +1,59 @@
1
+ import {
2
+ detail,
3
+ error,
4
+ info,
5
+ newline,
6
+ success
7
+ } from "./chunk-HJVVHYVN.js";
8
+
9
+ // src/commands/reset-password.ts
10
+ import { resolve } from "path";
11
+ import { homedir } from "os";
12
+ import { randomBytes } from "crypto";
13
+ async function resetPassword(projectDir, email) {
14
+ const { AuthDatabase, hashPassword } = await import("@yamchart/auth-local");
15
+ const { readFileSync } = await import("fs");
16
+ const { parse } = await import("yaml");
17
+ const projectPath = resolve(projectDir, "yamchart.yaml");
18
+ let dbPath;
19
+ try {
20
+ const raw = readFileSync(projectPath, "utf-8");
21
+ const project = parse(raw);
22
+ if (!project.auth?.enabled) {
23
+ error("Auth is not enabled in this project");
24
+ detail('Add "auth: { enabled: true }" to yamchart.yaml');
25
+ process.exit(1);
26
+ }
27
+ dbPath = project.auth.db_path ? resolve(project.auth.db_path.replace(/^~/, homedir())) : resolve(homedir(), ".yamchart", "auth.db");
28
+ } catch {
29
+ error("Could not read yamchart.yaml");
30
+ process.exit(1);
31
+ }
32
+ let db;
33
+ try {
34
+ db = new AuthDatabase(dbPath);
35
+ } catch {
36
+ error(`Could not open auth database at ${dbPath}`);
37
+ process.exit(1);
38
+ }
39
+ const user = db.getUserByEmail(email);
40
+ if (!user) {
41
+ db.close();
42
+ error(`No user found with email: ${email}`);
43
+ process.exit(1);
44
+ }
45
+ const tempPassword = randomBytes(12).toString("base64url");
46
+ const hash = await hashPassword(tempPassword);
47
+ db.updateUser(user.id, { password_hash: hash });
48
+ db.close();
49
+ newline();
50
+ success(`Password reset for ${email}`);
51
+ newline();
52
+ info(`Temporary password: ${tempPassword}`);
53
+ detail("The user should change this password after logging in.");
54
+ newline();
55
+ }
56
+ export {
57
+ resetPassword
58
+ };
59
+ //# sourceMappingURL=reset-password-MJ54ICGP.js.map