yamchart 0.1.4 → 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.
- package/dist/chunk-3CLMQNNR.js +64 -0
- package/dist/chunk-3CLMQNNR.js.map +1 -0
- package/dist/{chunk-TBILHUB3.js → chunk-6GDL3DH4.js} +49 -60
- package/dist/chunk-6GDL3DH4.js.map +1 -0
- package/dist/chunk-HJVVHYVN.js +59 -0
- package/dist/chunk-HJVVHYVN.js.map +1 -0
- package/dist/{dev-UHYN2RXH.js → dev-HMLMSTA7.js} +27 -6
- package/dist/dev-HMLMSTA7.js.map +1 -0
- package/dist/index.js +84 -7
- package/dist/index.js.map +1 -1
- package/dist/reset-password-MJ54ICGP.js +59 -0
- package/dist/reset-password-MJ54ICGP.js.map +1 -0
- package/dist/templates/default/CLAUDE.md +9 -0
- package/dist/templates/default/docs/yamchart-reference.md +315 -0
- package/dist/test-N4KIIKQN.js +221 -0
- package/dist/test-N4KIIKQN.js.map +1 -0
- package/dist/update-QHLCWS56.js +75 -0
- package/dist/update-QHLCWS56.js.map +1 -0
- package/package.json +4 -3
- package/dist/chunk-TBILHUB3.js.map +0 -1
- package/dist/dev-UHYN2RXH.js.map +0 -1
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// src/utils/update-check.ts
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
var REGISTRY_URL = "https://registry.npmjs.org/yamchart";
|
|
6
|
+
var CACHE_DIR = join(homedir(), ".yamchart");
|
|
7
|
+
var CACHE_FILE = join(CACHE_DIR, "update-check.json");
|
|
8
|
+
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
9
|
+
function readCache() {
|
|
10
|
+
try {
|
|
11
|
+
const raw = readFileSync(CACHE_FILE, "utf-8");
|
|
12
|
+
const data = JSON.parse(raw);
|
|
13
|
+
if (Date.now() - data.checkedAt < CACHE_TTL_MS) {
|
|
14
|
+
return data;
|
|
15
|
+
}
|
|
16
|
+
} catch {
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
function writeCache(latest) {
|
|
21
|
+
try {
|
|
22
|
+
mkdirSync(CACHE_DIR, { recursive: true });
|
|
23
|
+
writeFileSync(CACHE_FILE, JSON.stringify({ latest, checkedAt: Date.now() }));
|
|
24
|
+
} catch {
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async function checkForUpdate(currentVersion) {
|
|
28
|
+
try {
|
|
29
|
+
const cached = readCache();
|
|
30
|
+
const latest = cached?.latest ?? await fetchLatestVersion();
|
|
31
|
+
if (!latest) return null;
|
|
32
|
+
if (!cached) {
|
|
33
|
+
writeCache(latest);
|
|
34
|
+
}
|
|
35
|
+
if (latest !== currentVersion && latest > currentVersion) {
|
|
36
|
+
return { current: currentVersion, latest };
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function fetchLatestVersion() {
|
|
44
|
+
const controller = new AbortController();
|
|
45
|
+
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
46
|
+
try {
|
|
47
|
+
const response = await fetch(REGISTRY_URL, {
|
|
48
|
+
signal: controller.signal,
|
|
49
|
+
headers: { Accept: "application/vnd.npm.install-v1+json" }
|
|
50
|
+
});
|
|
51
|
+
clearTimeout(timeout);
|
|
52
|
+
if (!response.ok) return null;
|
|
53
|
+
const data = await response.json();
|
|
54
|
+
return data["dist-tags"]?.latest ?? null;
|
|
55
|
+
} catch {
|
|
56
|
+
clearTimeout(timeout);
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export {
|
|
62
|
+
checkForUpdate
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=chunk-3CLMQNNR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/update-check.ts"],"sourcesContent":["import { readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\n\nconst REGISTRY_URL = 'https://registry.npmjs.org/yamchart';\nconst CACHE_DIR = join(homedir(), '.yamchart');\nconst CACHE_FILE = join(CACHE_DIR, 'update-check.json');\nconst CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours\n\nexport interface UpdateCheckResult {\n current: string;\n latest: string;\n}\n\ninterface CachedCheck {\n latest: string;\n checkedAt: number;\n}\n\nfunction readCache(): CachedCheck | null {\n try {\n const raw = readFileSync(CACHE_FILE, 'utf-8');\n const data = JSON.parse(raw) as CachedCheck;\n if (Date.now() - data.checkedAt < CACHE_TTL_MS) {\n return data;\n }\n } catch {\n // Cache miss or corrupt — ignore\n }\n return null;\n}\n\nfunction writeCache(latest: string): void {\n try {\n mkdirSync(CACHE_DIR, { recursive: true });\n writeFileSync(CACHE_FILE, JSON.stringify({ latest, checkedAt: Date.now() }));\n } catch {\n // Non-critical — ignore write errors\n }\n}\n\nexport async function checkForUpdate(currentVersion: string): Promise<UpdateCheckResult | null> {\n try {\n // Check cache first\n const cached = readCache();\n const latest = cached?.latest ?? await fetchLatestVersion();\n if (!latest) return null;\n\n if (!cached) {\n writeCache(latest);\n }\n\n // Compare versions (simple string comparison works for semver)\n if (latest !== currentVersion && latest > currentVersion) {\n return { current: currentVersion, latest };\n }\n\n return null;\n } catch {\n return null; // Silent fail — never block CLI on update check\n }\n}\n\nasync function fetchLatestVersion(): Promise<string | null> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 3000); // 3s timeout\n\n try {\n const response = await fetch(REGISTRY_URL, {\n signal: controller.signal,\n headers: { Accept: 'application/vnd.npm.install-v1+json' },\n });\n clearTimeout(timeout);\n\n if (!response.ok) return null;\n\n const data = await response.json() as { 'dist-tags'?: { latest?: string } };\n return data['dist-tags']?.latest ?? null;\n } catch {\n clearTimeout(timeout);\n return null;\n }\n}\n"],"mappings":";AAAA,SAAS,cAAc,eAAe,iBAAiB;AACvD,SAAS,YAAY;AACrB,SAAS,eAAe;AAExB,IAAM,eAAe;AACrB,IAAM,YAAY,KAAK,QAAQ,GAAG,WAAW;AAC7C,IAAM,aAAa,KAAK,WAAW,mBAAmB;AACtD,IAAM,eAAe,KAAK,KAAK,KAAK;AAYpC,SAAS,YAAgC;AACvC,MAAI;AACF,UAAM,MAAM,aAAa,YAAY,OAAO;AAC5C,UAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,QAAI,KAAK,IAAI,IAAI,KAAK,YAAY,cAAc;AAC9C,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAEA,SAAS,WAAW,QAAsB;AACxC,MAAI;AACF,cAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AACxC,kBAAc,YAAY,KAAK,UAAU,EAAE,QAAQ,WAAW,KAAK,IAAI,EAAE,CAAC,CAAC;AAAA,EAC7E,QAAQ;AAAA,EAER;AACF;AAEA,eAAsB,eAAe,gBAA2D;AAC9F,MAAI;AAEF,UAAM,SAAS,UAAU;AACzB,UAAM,SAAS,QAAQ,UAAU,MAAM,mBAAmB;AAC1D,QAAI,CAAC,OAAQ,QAAO;AAEpB,QAAI,CAAC,QAAQ;AACX,iBAAW,MAAM;AAAA,IACnB;AAGA,QAAI,WAAW,kBAAkB,SAAS,gBAAgB;AACxD,aAAO,EAAE,SAAS,gBAAgB,OAAO;AAAA,IAC3C;AAEA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,qBAA6C;AAC1D,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,UAAU,WAAW,MAAM,WAAW,MAAM,GAAG,GAAI;AAEzD,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,cAAc;AAAA,MACzC,QAAQ,WAAW;AAAA,MACnB,SAAS,EAAE,QAAQ,sCAAsC;AAAA,IAC3D,CAAC;AACD,iBAAa,OAAO;AAEpB,QAAI,CAAC,SAAS,GAAI,QAAO;AAEzB,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,WAAO,KAAK,WAAW,GAAG,UAAU;AAAA,EACtC,QAAQ;AACN,iBAAa,OAAO;AACpB,WAAO;AAAA,EACT;AACF;","names":[]}
|
|
@@ -6,7 +6,8 @@ import {
|
|
|
6
6
|
ProjectSchema,
|
|
7
7
|
ConnectionSchema,
|
|
8
8
|
ChartSchema,
|
|
9
|
-
DashboardSchema
|
|
9
|
+
DashboardSchema,
|
|
10
|
+
ScheduleSchema
|
|
10
11
|
} from "@yamchart/schema";
|
|
11
12
|
import { parseModelMetadata } from "@yamchart/query";
|
|
12
13
|
async function validateProject(projectDir, options) {
|
|
@@ -19,7 +20,8 @@ async function validateProject(projectDir, options) {
|
|
|
19
20
|
connections: /* @__PURE__ */ new Map(),
|
|
20
21
|
models: /* @__PURE__ */ new Map(),
|
|
21
22
|
charts: /* @__PURE__ */ new Map(),
|
|
22
|
-
dashboards: /* @__PURE__ */ new Map()
|
|
23
|
+
dashboards: /* @__PURE__ */ new Map(),
|
|
24
|
+
schedules: /* @__PURE__ */ new Map()
|
|
23
25
|
};
|
|
24
26
|
const projectPath = join(projectDir, "yamchart.yaml");
|
|
25
27
|
try {
|
|
@@ -127,6 +129,29 @@ async function validateProject(projectDir, options) {
|
|
|
127
129
|
}
|
|
128
130
|
} catch {
|
|
129
131
|
}
|
|
132
|
+
const schedulesDir = join(projectDir, "schedules");
|
|
133
|
+
try {
|
|
134
|
+
await access(schedulesDir);
|
|
135
|
+
const files = await readdir(schedulesDir);
|
|
136
|
+
for (const file of files) {
|
|
137
|
+
if (extname(file) !== ".yaml" && extname(file) !== ".yml") continue;
|
|
138
|
+
filesChecked++;
|
|
139
|
+
const filePath = join(schedulesDir, file);
|
|
140
|
+
const content = await readFile(filePath, "utf-8");
|
|
141
|
+
const parsed = parseYaml(content);
|
|
142
|
+
const result = ScheduleSchema.safeParse(parsed);
|
|
143
|
+
if (result.success) {
|
|
144
|
+
config.schedules.set(result.data.name, result.data);
|
|
145
|
+
filesPassed++;
|
|
146
|
+
} else {
|
|
147
|
+
errors.push({
|
|
148
|
+
file: `schedules/${file}`,
|
|
149
|
+
message: `Invalid schema: ${result.error.errors[0]?.message || "Unknown error"}`
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} catch {
|
|
154
|
+
}
|
|
130
155
|
crossReferenceValidation(config, errors, warnings);
|
|
131
156
|
let dryRunStats;
|
|
132
157
|
if (options.dryRun) {
|
|
@@ -178,6 +203,26 @@ function crossReferenceValidation(config, errors, warnings) {
|
|
|
178
203
|
});
|
|
179
204
|
}
|
|
180
205
|
}
|
|
206
|
+
for (const [scheduleName, schedule] of config.schedules) {
|
|
207
|
+
const chartNames = schedule.type === "report" ? schedule.charts : [schedule.chart];
|
|
208
|
+
for (const chartName of chartNames) {
|
|
209
|
+
if (!config.charts.has(chartName)) {
|
|
210
|
+
const suggestion = findSimilar(chartName, Array.from(config.charts.keys()));
|
|
211
|
+
warnings.push({
|
|
212
|
+
file: `schedules/${scheduleName}.yaml`,
|
|
213
|
+
message: `Unknown chart reference "${chartName}"`,
|
|
214
|
+
suggestion: suggestion ? `Did you mean "${suggestion}"?` : void 0
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const cronFields = schedule.schedule.trim().split(/\s+/);
|
|
219
|
+
if (cronFields.length < 5 || cronFields.length > 6) {
|
|
220
|
+
errors.push({
|
|
221
|
+
file: `schedules/${scheduleName}.yaml`,
|
|
222
|
+
message: `Invalid cron expression "${schedule.schedule}" (expected 5-6 fields)`
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
181
226
|
if (config.project?.defaults?.connection) {
|
|
182
227
|
const connName = config.project.defaults.connection;
|
|
183
228
|
if (!config.connections.has(connName)) {
|
|
@@ -301,65 +346,9 @@ function loadEnvFile(projectDir) {
|
|
|
301
346
|
loadDotenv({ path: join2(projectDir, ".env") });
|
|
302
347
|
}
|
|
303
348
|
|
|
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
349
|
export {
|
|
352
350
|
validateProject,
|
|
353
351
|
findProjectRoot,
|
|
354
|
-
loadEnvFile
|
|
355
|
-
success,
|
|
356
|
-
error,
|
|
357
|
-
warning,
|
|
358
|
-
info,
|
|
359
|
-
detail,
|
|
360
|
-
newline,
|
|
361
|
-
header,
|
|
362
|
-
spinner,
|
|
363
|
-
box
|
|
352
|
+
loadEnvFile
|
|
364
353
|
};
|
|
365
|
-
//# sourceMappingURL=chunk-
|
|
354
|
+
//# sourceMappingURL=chunk-6GDL3DH4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/validate.ts","../src/utils/config.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 ScheduleSchema,\n type Schedule,\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 schedules: Map<string, Schedule>;\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 schedules: 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 // Validate schedules\n const schedulesDir = join(projectDir, 'schedules');\n try {\n await access(schedulesDir);\n const files = await readdir(schedulesDir);\n\n for (const file of files) {\n if (extname(file) !== '.yaml' && extname(file) !== '.yml') continue;\n filesChecked++;\n\n const filePath = join(schedulesDir, file);\n const content = await readFile(filePath, 'utf-8');\n const parsed = parseYaml(content);\n const result = ScheduleSchema.safeParse(parsed);\n\n if (result.success) {\n config.schedules.set(result.data.name, result.data);\n filesPassed++;\n } else {\n errors.push({\n file: `schedules/${file}`,\n message: `Invalid schema: ${result.error.errors[0]?.message || 'Unknown error'}`,\n });\n }\n }\n } catch {\n // No schedules 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 schedules reference existing charts\n for (const [scheduleName, schedule] of config.schedules) {\n const chartNames = schedule.type === 'report' ? schedule.charts : [schedule.chart];\n for (const chartName of chartNames) {\n if (!config.charts.has(chartName)) {\n const suggestion = findSimilar(chartName, Array.from(config.charts.keys()));\n warnings.push({\n file: `schedules/${scheduleName}.yaml`,\n message: `Unknown chart reference \"${chartName}\"`,\n suggestion: suggestion ? `Did you mean \"${suggestion}\"?` : undefined,\n });\n }\n }\n\n // Validate cron expression (basic: 5 or 6 space-separated fields)\n const cronFields = schedule.schedule.trim().split(/\\s+/);\n if (cronFields.length < 5 || cronFields.length > 6) {\n errors.push({\n file: `schedules/${scheduleName}.yaml`,\n message: `Invalid cron expression \"${schedule.schedule}\" (expected 5-6 fields)`,\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"],"mappings":"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","names":["access","join"]}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// src/utils/output.ts
|
|
2
|
+
import pc from "picocolors";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
var symbols = {
|
|
5
|
+
success: pc.green("\u2713"),
|
|
6
|
+
error: pc.red("\u2717"),
|
|
7
|
+
warning: pc.yellow("\u26A0"),
|
|
8
|
+
info: pc.blue("\u2139"),
|
|
9
|
+
arrow: pc.dim("\u2192")
|
|
10
|
+
};
|
|
11
|
+
function success(message) {
|
|
12
|
+
console.log(`${symbols.success} ${message}`);
|
|
13
|
+
}
|
|
14
|
+
function error(message) {
|
|
15
|
+
console.log(`${symbols.error} ${message}`);
|
|
16
|
+
}
|
|
17
|
+
function warning(message) {
|
|
18
|
+
console.log(`${symbols.warning} ${message}`);
|
|
19
|
+
}
|
|
20
|
+
function info(message) {
|
|
21
|
+
console.log(`${symbols.info} ${message}`);
|
|
22
|
+
}
|
|
23
|
+
function detail(message) {
|
|
24
|
+
console.log(` ${symbols.arrow} ${message}`);
|
|
25
|
+
}
|
|
26
|
+
function newline() {
|
|
27
|
+
console.log();
|
|
28
|
+
}
|
|
29
|
+
function header(title) {
|
|
30
|
+
console.log(pc.bold(title));
|
|
31
|
+
newline();
|
|
32
|
+
}
|
|
33
|
+
function spinner(text) {
|
|
34
|
+
return ora({ text, color: "cyan" }).start();
|
|
35
|
+
}
|
|
36
|
+
function box(lines) {
|
|
37
|
+
const maxLength = Math.max(...lines.map((l) => l.length));
|
|
38
|
+
const width = maxLength + 4;
|
|
39
|
+
const border = "\u2500".repeat(width);
|
|
40
|
+
console.log(` \u250C${border}\u2510`);
|
|
41
|
+
for (const line of lines) {
|
|
42
|
+
const padding = " ".repeat(maxLength - line.length);
|
|
43
|
+
console.log(` \u2502 ${line}${padding} \u2502`);
|
|
44
|
+
}
|
|
45
|
+
console.log(` \u2514${border}\u2518`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export {
|
|
49
|
+
success,
|
|
50
|
+
error,
|
|
51
|
+
warning,
|
|
52
|
+
info,
|
|
53
|
+
detail,
|
|
54
|
+
newline,
|
|
55
|
+
header,
|
|
56
|
+
spinner,
|
|
57
|
+
box
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=chunk-HJVVHYVN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/output.ts"],"sourcesContent":["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":";AAAA,OAAO,QAAQ;AACf,OAAO,SAAuB;AAEvB,IAAM,UAAU;AAAA,EACrB,SAAS,GAAG,MAAM,QAAG;AAAA,EACrB,OAAO,GAAG,IAAI,QAAG;AAAA,EACjB,SAAS,GAAG,OAAO,QAAG;AAAA,EACtB,MAAM,GAAG,KAAK,QAAG;AAAA,EACjB,OAAO,GAAG,IAAI,QAAG;AACnB;AAEO,SAAS,QAAQ,SAAuB;AAC7C,UAAQ,IAAI,GAAG,QAAQ,OAAO,IAAI,OAAO,EAAE;AAC7C;AAEO,SAAS,MAAM,SAAuB;AAC3C,UAAQ,IAAI,GAAG,QAAQ,KAAK,IAAI,OAAO,EAAE;AAC3C;AAEO,SAAS,QAAQ,SAAuB;AAC7C,UAAQ,IAAI,GAAG,QAAQ,OAAO,IAAI,OAAO,EAAE;AAC7C;AAEO,SAAS,KAAK,SAAuB;AAC1C,UAAQ,IAAI,GAAG,QAAQ,IAAI,IAAI,OAAO,EAAE;AAC1C;AAEO,SAAS,OAAO,SAAuB;AAC5C,UAAQ,IAAI,KAAK,QAAQ,KAAK,IAAI,OAAO,EAAE;AAC7C;AAEO,SAAS,UAAgB;AAC9B,UAAQ,IAAI;AACd;AAEO,SAAS,OAAO,OAAqB;AAC1C,UAAQ,IAAI,GAAG,KAAK,KAAK,CAAC;AAC1B,UAAQ;AACV;AAEO,SAAS,QAAQ,MAAmB;AACzC,SAAO,IAAI,EAAE,MAAM,OAAO,OAAO,CAAC,EAAE,MAAM;AAC5C;AAEO,SAAS,IAAI,OAAuB;AACzC,QAAM,YAAY,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC;AACxD,QAAM,QAAQ,YAAY;AAC1B,QAAM,SAAS,SAAI,OAAO,KAAK;AAE/B,UAAQ,IAAI,WAAM,MAAM,QAAG;AAC3B,aAAW,QAAQ,OAAO;AACxB,UAAM,UAAU,IAAI,OAAO,YAAY,KAAK,MAAM;AAClD,YAAQ,IAAI,aAAQ,IAAI,GAAG,OAAO,UAAK;AAAA,EACzC;AACA,UAAQ,IAAI,WAAM,MAAM,QAAG;AAC7B;","names":[]}
|
|
@@ -1,18 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadEnvFile,
|
|
3
|
+
validateProject
|
|
4
|
+
} from "./chunk-6GDL3DH4.js";
|
|
1
5
|
import {
|
|
2
6
|
box,
|
|
3
7
|
detail,
|
|
4
8
|
error,
|
|
5
9
|
header,
|
|
6
10
|
info,
|
|
7
|
-
loadEnvFile,
|
|
8
11
|
newline,
|
|
9
12
|
spinner,
|
|
10
|
-
success
|
|
11
|
-
|
|
12
|
-
} from "./chunk-TBILHUB3.js";
|
|
13
|
+
success
|
|
14
|
+
} from "./chunk-HJVVHYVN.js";
|
|
13
15
|
|
|
14
16
|
// src/commands/dev.ts
|
|
17
|
+
import { resolve } from "path";
|
|
18
|
+
import { homedir } from "os";
|
|
15
19
|
import { createServer } from "@yamchart/server";
|
|
20
|
+
import { parseTtl as parseAuthTtl } from "@yamchart/auth-local";
|
|
16
21
|
async function runDevServer(projectDir, options) {
|
|
17
22
|
loadEnvFile(projectDir);
|
|
18
23
|
header("Validating configuration...");
|
|
@@ -34,11 +39,27 @@ async function runDevServer(projectDir, options) {
|
|
|
34
39
|
const spinner2 = spinner("Starting server...");
|
|
35
40
|
let server;
|
|
36
41
|
try {
|
|
42
|
+
let localAuth;
|
|
43
|
+
try {
|
|
44
|
+
const { readFileSync } = await import("fs");
|
|
45
|
+
const { parse } = await import("yaml");
|
|
46
|
+
const raw = readFileSync(resolve(projectDir, "yamchart.yaml"), "utf-8");
|
|
47
|
+
const projectConfig = parse(raw);
|
|
48
|
+
if (projectConfig?.auth?.enabled) {
|
|
49
|
+
localAuth = {
|
|
50
|
+
enabled: true,
|
|
51
|
+
dbPath: projectConfig.auth.db_path ? resolve(projectConfig.auth.db_path.replace(/^~/, homedir())) : resolve(homedir(), ".yamchart", "auth.db"),
|
|
52
|
+
sessionTtlMs: projectConfig.auth.session_ttl ? parseAuthTtl(projectConfig.auth.session_ttl) : 30 * 24 * 60 * 60 * 1e3
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
37
57
|
server = await createServer({
|
|
38
58
|
projectDir,
|
|
39
59
|
port: options.port,
|
|
40
60
|
watch: true,
|
|
41
|
-
serveStatic: !options.apiOnly
|
|
61
|
+
serveStatic: !options.apiOnly,
|
|
62
|
+
localAuth
|
|
42
63
|
});
|
|
43
64
|
await server.start();
|
|
44
65
|
spinner2.stop();
|
|
@@ -82,4 +103,4 @@ async function runDevServer(projectDir, options) {
|
|
|
82
103
|
export {
|
|
83
104
|
runDevServer
|
|
84
105
|
};
|
|
85
|
-
//# sourceMappingURL=dev-
|
|
106
|
+
//# sourceMappingURL=dev-HMLMSTA7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/dev.ts"],"sourcesContent":["import { resolve } from 'path';\nimport { homedir } from 'os';\nimport { createServer, type DashbookServer } from '@yamchart/server';\nimport { parseTtl as parseAuthTtl } from '@yamchart/auth-local';\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 // Check if local auth is configured\n let localAuth: { enabled: boolean; dbPath: string; sessionTtlMs: number } | undefined;\n try {\n const { readFileSync } = await import('fs');\n const { parse } = await import('yaml');\n const raw = readFileSync(resolve(projectDir, 'yamchart.yaml'), 'utf-8');\n const projectConfig = parse(raw);\n if (projectConfig?.auth?.enabled) {\n localAuth = {\n enabled: true,\n dbPath: projectConfig.auth.db_path\n ? resolve(projectConfig.auth.db_path.replace(/^~/, homedir()))\n : resolve(homedir(), '.yamchart', 'auth.db'),\n sessionTtlMs: projectConfig.auth.session_ttl\n ? parseAuthTtl(projectConfig.auth.session_ttl)\n : 30 * 24 * 60 * 60 * 1000,\n };\n }\n } catch { /* auth config read failed — proceed without auth */ }\n\n server = await createServer({\n projectDir,\n port: options.port,\n watch: true,\n serveStatic: !options.apiOnly,\n localAuth,\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":"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","names":["error","spinner"]}
|
package/dist/index.js
CHANGED
|
@@ -1,23 +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,
|
|
10
16
|
spinner,
|
|
11
17
|
success,
|
|
12
|
-
validateProject,
|
|
13
18
|
warning
|
|
14
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-HJVVHYVN.js";
|
|
15
20
|
|
|
16
21
|
// src/index.ts
|
|
17
22
|
import { Command } from "commander";
|
|
18
|
-
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"));
|
|
19
29
|
var program = new Command();
|
|
20
|
-
program.name("yamchart").description("Git-native business intelligence dashboards").version(
|
|
30
|
+
program.name("yamchart").description("Git-native business intelligence dashboards").version(pkg.version);
|
|
21
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) => {
|
|
22
32
|
const startPath = resolve(path);
|
|
23
33
|
const projectDir = await findProjectRoot(startPath);
|
|
@@ -83,7 +93,7 @@ program.command("dev").description("Start development server with hot reload").a
|
|
|
83
93
|
detail("Run this command from a yamchart project directory");
|
|
84
94
|
process.exit(2);
|
|
85
95
|
}
|
|
86
|
-
const { runDevServer } = await import("./dev-
|
|
96
|
+
const { runDevServer } = await import("./dev-HMLMSTA7.js");
|
|
87
97
|
await runDevServer(projectDir, {
|
|
88
98
|
port: parseInt(options.port, 10),
|
|
89
99
|
apiOnly: options.apiOnly ?? false,
|
|
@@ -168,5 +178,72 @@ program.command("generate").description("Generate SQL model stubs from dbt catal
|
|
|
168
178
|
detail(`${result.filesSkipped} files skipped`);
|
|
169
179
|
}
|
|
170
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
|
+
});
|
|
171
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
|
+
});
|
|
172
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\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.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"]}
|