@bonnard/cli 0.2.10 → 0.2.11
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/bin/api-B7cdKn9j.mjs +3 -0
- package/dist/bin/api-DqgY-30K.mjs +75 -0
- package/dist/bin/bon.mjs +81 -276
- package/dist/bin/{cubes-9rklhdAJ.mjs → cubes-BvtwNBUG.mjs} +1 -1
- package/dist/bin/local-BkK5XL7T.mjs +3 -0
- package/dist/bin/local-ByvuW3eV.mjs +149 -0
- package/dist/bin/project-Dj085D_B.mjs +27 -0
- package/dist/bin/{push-Bv9AFGc2.mjs → push-BOkUmRL8.mjs} +2 -1
- package/dist/bin/{validate-Bc8zGNw7.mjs → validate-C4W_Vto2.mjs} +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import pc from "picocolors";
|
|
5
|
+
|
|
6
|
+
//#region src/lib/credentials.ts
|
|
7
|
+
const CREDENTIALS_DIR = path.join(os.homedir(), ".config", "bon");
|
|
8
|
+
const CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, "credentials.json");
|
|
9
|
+
function saveCredentials(credentials) {
|
|
10
|
+
fs.mkdirSync(CREDENTIALS_DIR, {
|
|
11
|
+
recursive: true,
|
|
12
|
+
mode: 448
|
|
13
|
+
});
|
|
14
|
+
fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2), { mode: 384 });
|
|
15
|
+
}
|
|
16
|
+
function loadCredentials() {
|
|
17
|
+
try {
|
|
18
|
+
const raw = fs.readFileSync(CREDENTIALS_FILE, "utf-8");
|
|
19
|
+
const parsed = JSON.parse(raw);
|
|
20
|
+
if (parsed.token && parsed.email) return parsed;
|
|
21
|
+
return null;
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function clearCredentials() {
|
|
27
|
+
try {
|
|
28
|
+
fs.unlinkSync(CREDENTIALS_FILE);
|
|
29
|
+
} catch {}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/lib/api.ts
|
|
34
|
+
const APP_URL = process.env.BON_APP_URL || "https://app.bonnard.dev";
|
|
35
|
+
const VERCEL_BYPASS = process.env.VERCEL_AUTOMATION_BYPASS_SECRET;
|
|
36
|
+
function getToken() {
|
|
37
|
+
const creds = loadCredentials();
|
|
38
|
+
if (!creds) {
|
|
39
|
+
console.error(pc.red("Not logged in. Run `bon login` first."));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
return creds.token;
|
|
43
|
+
}
|
|
44
|
+
async function request(method, path, body) {
|
|
45
|
+
const token = getToken();
|
|
46
|
+
const url = `${APP_URL}${path}`;
|
|
47
|
+
const headers = {
|
|
48
|
+
Authorization: `Bearer ${token}`,
|
|
49
|
+
"Content-Type": "application/json"
|
|
50
|
+
};
|
|
51
|
+
if (VERCEL_BYPASS) headers["x-vercel-protection-bypass"] = VERCEL_BYPASS;
|
|
52
|
+
const res = await fetch(url, {
|
|
53
|
+
method,
|
|
54
|
+
headers,
|
|
55
|
+
body: body ? JSON.stringify(body) : void 0
|
|
56
|
+
});
|
|
57
|
+
const data = await res.json();
|
|
58
|
+
if (!res.ok) {
|
|
59
|
+
const message = data.error || res.statusText;
|
|
60
|
+
throw new Error(message);
|
|
61
|
+
}
|
|
62
|
+
return data;
|
|
63
|
+
}
|
|
64
|
+
function get(path) {
|
|
65
|
+
return request("GET", path);
|
|
66
|
+
}
|
|
67
|
+
function post(path, body) {
|
|
68
|
+
return request("POST", path, body);
|
|
69
|
+
}
|
|
70
|
+
function del(path) {
|
|
71
|
+
return request("DELETE", path);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
//#endregion
|
|
75
|
+
export { loadCredentials as a, clearCredentials as i, get as n, saveCredentials as o, post as r, del as t };
|
package/dist/bin/bon.mjs
CHANGED
|
@@ -1,56 +1,90 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { n as getProjectPaths, t as BONNARD_DIR } from "./project-Dj085D_B.mjs";
|
|
3
|
+
import { a as loadCredentials, i as clearCredentials, n as get, o as saveCredentials, r as post } from "./api-DqgY-30K.mjs";
|
|
4
|
+
import { i as ensureBonDir, n as addLocalDatasource, o as loadLocalDatasources, r as datasourceExists, s as removeLocalDatasource, t as isDatasourcesTrackedByGit } from "./local-ByvuW3eV.mjs";
|
|
2
5
|
import { createRequire } from "node:module";
|
|
3
6
|
import { program } from "commander";
|
|
4
7
|
import fs from "node:fs";
|
|
5
8
|
import path from "node:path";
|
|
6
|
-
import
|
|
9
|
+
import os from "node:os";
|
|
7
10
|
import pc from "picocolors";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
8
12
|
import YAML from "yaml";
|
|
9
|
-
import os from "node:os";
|
|
10
13
|
import http from "node:http";
|
|
11
14
|
import crypto from "node:crypto";
|
|
12
|
-
import { execFileSync } from "node:child_process";
|
|
13
15
|
import { encode } from "@toon-format/toon";
|
|
14
16
|
|
|
15
17
|
//#region \0rolldown/runtime.js
|
|
16
|
-
var __defProp = Object.defineProperty;
|
|
17
|
-
var __exportAll = (all, no_symbols) => {
|
|
18
|
-
let target = {};
|
|
19
|
-
for (var name in all) {
|
|
20
|
-
__defProp(target, name, {
|
|
21
|
-
get: all[name],
|
|
22
|
-
enumerable: true
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
if (!no_symbols) {
|
|
26
|
-
__defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
27
|
-
}
|
|
28
|
-
return target;
|
|
29
|
-
};
|
|
30
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
31
19
|
|
|
32
20
|
//#endregion
|
|
33
|
-
//#region src/lib/
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
21
|
+
//#region src/lib/update-check.ts
|
|
22
|
+
const CACHE_DIR = path.join(os.homedir(), ".config", "bon");
|
|
23
|
+
const CACHE_FILE = path.join(CACHE_DIR, "update-check.json");
|
|
24
|
+
const CHECK_INTERVAL_MS = 1440 * 60 * 1e3;
|
|
25
|
+
const REGISTRY_URL = "https://registry.npmjs.org/@bonnard/cli/latest";
|
|
26
|
+
const FETCH_TIMEOUT_MS = 3e3;
|
|
27
|
+
function readCache() {
|
|
28
|
+
try {
|
|
29
|
+
const raw = fs.readFileSync(CACHE_FILE, "utf-8");
|
|
30
|
+
return JSON.parse(raw);
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function writeCache(data) {
|
|
36
|
+
try {
|
|
37
|
+
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
38
|
+
fs.writeFileSync(CACHE_FILE, JSON.stringify(data));
|
|
39
|
+
} catch {}
|
|
40
|
+
}
|
|
41
|
+
function isNewer(latest, current) {
|
|
42
|
+
const l = latest.split(".").map(Number);
|
|
43
|
+
const c = current.split(".").map(Number);
|
|
44
|
+
for (let i = 0; i < 3; i++) {
|
|
45
|
+
if ((l[i] ?? 0) > (c[i] ?? 0)) return true;
|
|
46
|
+
if ((l[i] ?? 0) < (c[i] ?? 0)) return false;
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
async function fetchLatestVersion() {
|
|
51
|
+
try {
|
|
52
|
+
const controller = new AbortController();
|
|
53
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
54
|
+
const res = await fetch(REGISTRY_URL, { signal: controller.signal });
|
|
55
|
+
clearTimeout(timeout);
|
|
56
|
+
if (!res.ok) return null;
|
|
57
|
+
return (await res.json()).version ?? null;
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
40
62
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
63
|
+
* Start a background version check. Returns a function that,
|
|
64
|
+
* when called, prints an update notice if a newer version exists.
|
|
65
|
+
* The check is cached for 24 hours and never blocks the CLI.
|
|
43
66
|
*/
|
|
44
|
-
function
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
67
|
+
function startUpdateCheck(currentVersion) {
|
|
68
|
+
const cached = readCache();
|
|
69
|
+
const now = Date.now();
|
|
70
|
+
if (cached && now - cached.lastCheck < CHECK_INTERVAL_MS) return async () => {
|
|
71
|
+
if (isNewer(cached.latestVersion, currentVersion)) printUpdateNotice(cached.latestVersion, currentVersion);
|
|
72
|
+
};
|
|
73
|
+
const fetchPromise = fetchLatestVersion();
|
|
74
|
+
return async () => {
|
|
75
|
+
const latest = await fetchPromise;
|
|
76
|
+
if (latest) {
|
|
77
|
+
writeCache({
|
|
78
|
+
lastCheck: now,
|
|
79
|
+
latestVersion: latest
|
|
80
|
+
});
|
|
81
|
+
if (isNewer(latest, currentVersion)) printUpdateNotice(latest, currentVersion);
|
|
82
|
+
}
|
|
52
83
|
};
|
|
53
84
|
}
|
|
85
|
+
function printUpdateNotice(latest, current) {
|
|
86
|
+
console.error(`\nUpdate available: ${pc.yellow(latest)} (currently installed ${current})\nRun ${pc.cyan("npm install -g @bonnard/cli")} to update\n`);
|
|
87
|
+
}
|
|
54
88
|
|
|
55
89
|
//#endregion
|
|
56
90
|
//#region src/lib/dbt/profiles.ts
|
|
@@ -625,41 +659,14 @@ async function initCommand() {
|
|
|
625
659
|
}
|
|
626
660
|
}
|
|
627
661
|
|
|
628
|
-
//#endregion
|
|
629
|
-
//#region src/lib/credentials.ts
|
|
630
|
-
const CREDENTIALS_DIR = path.join(os.homedir(), ".config", "bon");
|
|
631
|
-
const CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, "credentials.json");
|
|
632
|
-
function saveCredentials(credentials) {
|
|
633
|
-
fs.mkdirSync(CREDENTIALS_DIR, {
|
|
634
|
-
recursive: true,
|
|
635
|
-
mode: 448
|
|
636
|
-
});
|
|
637
|
-
fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2), { mode: 384 });
|
|
638
|
-
}
|
|
639
|
-
function loadCredentials() {
|
|
640
|
-
try {
|
|
641
|
-
const raw = fs.readFileSync(CREDENTIALS_FILE, "utf-8");
|
|
642
|
-
const parsed = JSON.parse(raw);
|
|
643
|
-
if (parsed.token && parsed.email) return parsed;
|
|
644
|
-
return null;
|
|
645
|
-
} catch {
|
|
646
|
-
return null;
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
function clearCredentials() {
|
|
650
|
-
try {
|
|
651
|
-
fs.unlinkSync(CREDENTIALS_FILE);
|
|
652
|
-
} catch {}
|
|
653
|
-
}
|
|
654
|
-
|
|
655
662
|
//#endregion
|
|
656
663
|
//#region src/commands/login.ts
|
|
657
|
-
const APP_URL
|
|
664
|
+
const APP_URL = process.env.BON_APP_URL || "https://app.bonnard.dev";
|
|
658
665
|
const TIMEOUT_MS = 120 * 1e3;
|
|
659
666
|
async function loginCommand() {
|
|
660
667
|
const state = crypto.randomUUID();
|
|
661
668
|
const { port, close } = await startCallbackServer(state);
|
|
662
|
-
const url = `${APP_URL
|
|
669
|
+
const url = `${APP_URL}/auth/device?state=${state}&port=${port}`;
|
|
663
670
|
console.log(pc.dim(`Opening browser to ${url}`));
|
|
664
671
|
const open = (await import("open")).default;
|
|
665
672
|
await open(url);
|
|
@@ -788,53 +795,6 @@ async function logoutCommand() {
|
|
|
788
795
|
console.log(pc.green("Logged out"));
|
|
789
796
|
}
|
|
790
797
|
|
|
791
|
-
//#endregion
|
|
792
|
-
//#region src/lib/api.ts
|
|
793
|
-
var api_exports = /* @__PURE__ */ __exportAll({
|
|
794
|
-
del: () => del,
|
|
795
|
-
get: () => get,
|
|
796
|
-
post: () => post
|
|
797
|
-
});
|
|
798
|
-
const APP_URL = process.env.BON_APP_URL || "https://app.bonnard.dev";
|
|
799
|
-
const VERCEL_BYPASS = process.env.VERCEL_AUTOMATION_BYPASS_SECRET;
|
|
800
|
-
function getToken() {
|
|
801
|
-
const creds = loadCredentials();
|
|
802
|
-
if (!creds) {
|
|
803
|
-
console.error(pc.red("Not logged in. Run `bon login` first."));
|
|
804
|
-
process.exit(1);
|
|
805
|
-
}
|
|
806
|
-
return creds.token;
|
|
807
|
-
}
|
|
808
|
-
async function request(method, path, body) {
|
|
809
|
-
const token = getToken();
|
|
810
|
-
const url = `${APP_URL}${path}`;
|
|
811
|
-
const headers = {
|
|
812
|
-
Authorization: `Bearer ${token}`,
|
|
813
|
-
"Content-Type": "application/json"
|
|
814
|
-
};
|
|
815
|
-
if (VERCEL_BYPASS) headers["x-vercel-protection-bypass"] = VERCEL_BYPASS;
|
|
816
|
-
const res = await fetch(url, {
|
|
817
|
-
method,
|
|
818
|
-
headers,
|
|
819
|
-
body: body ? JSON.stringify(body) : void 0
|
|
820
|
-
});
|
|
821
|
-
const data = await res.json();
|
|
822
|
-
if (!res.ok) {
|
|
823
|
-
const message = data.error || res.statusText;
|
|
824
|
-
throw new Error(message);
|
|
825
|
-
}
|
|
826
|
-
return data;
|
|
827
|
-
}
|
|
828
|
-
function get(path) {
|
|
829
|
-
return request("GET", path);
|
|
830
|
-
}
|
|
831
|
-
function post(path, body) {
|
|
832
|
-
return request("POST", path, body);
|
|
833
|
-
}
|
|
834
|
-
function del(path) {
|
|
835
|
-
return request("DELETE", path);
|
|
836
|
-
}
|
|
837
|
-
|
|
838
798
|
//#endregion
|
|
839
799
|
//#region src/commands/whoami.ts
|
|
840
800
|
async function whoamiCommand(options = {}) {
|
|
@@ -859,163 +819,6 @@ async function whoamiCommand(options = {}) {
|
|
|
859
819
|
}
|
|
860
820
|
}
|
|
861
821
|
|
|
862
|
-
//#endregion
|
|
863
|
-
//#region src/lib/local/datasources.ts
|
|
864
|
-
/**
|
|
865
|
-
* Local datasource storage (.bon/datasources.yaml)
|
|
866
|
-
*
|
|
867
|
-
* Single file containing both config and credentials.
|
|
868
|
-
* Credentials may contain:
|
|
869
|
-
* - Plain values: "my_password"
|
|
870
|
-
* - dbt env var syntax: "{{ env_var('MY_PASSWORD') }}"
|
|
871
|
-
*
|
|
872
|
-
* Env vars are resolved at deploy time, not import time.
|
|
873
|
-
*/
|
|
874
|
-
const BON_DIR$2 = ".bon";
|
|
875
|
-
const DATASOURCES_FILE$1 = "datasources.yaml";
|
|
876
|
-
function getBonDir(cwd = process.cwd()) {
|
|
877
|
-
return path.join(cwd, BON_DIR$2);
|
|
878
|
-
}
|
|
879
|
-
function getDatasourcesPath$1(cwd = process.cwd()) {
|
|
880
|
-
return path.join(getBonDir(cwd), DATASOURCES_FILE$1);
|
|
881
|
-
}
|
|
882
|
-
/**
|
|
883
|
-
* Ensure .bon directory exists
|
|
884
|
-
*/
|
|
885
|
-
function ensureBonDir(cwd = process.cwd()) {
|
|
886
|
-
const bonDir = getBonDir(cwd);
|
|
887
|
-
if (!fs.existsSync(bonDir)) fs.mkdirSync(bonDir, { recursive: true });
|
|
888
|
-
}
|
|
889
|
-
/**
|
|
890
|
-
* Load all local datasources
|
|
891
|
-
*/
|
|
892
|
-
function loadLocalDatasources(cwd = process.cwd()) {
|
|
893
|
-
const filePath = getDatasourcesPath$1(cwd);
|
|
894
|
-
if (!fs.existsSync(filePath)) return [];
|
|
895
|
-
try {
|
|
896
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
897
|
-
return YAML.parse(content)?.datasources ?? [];
|
|
898
|
-
} catch {
|
|
899
|
-
return [];
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
/**
|
|
903
|
-
* Save all local datasources (with secure permissions since it contains credentials)
|
|
904
|
-
*/
|
|
905
|
-
function saveLocalDatasources(datasources, cwd = process.cwd()) {
|
|
906
|
-
ensureBonDir(cwd);
|
|
907
|
-
const filePath = getDatasourcesPath$1(cwd);
|
|
908
|
-
const file = { datasources };
|
|
909
|
-
const content = `# Bonnard datasources configuration
|
|
910
|
-
# This file contains credentials - add to .gitignore
|
|
911
|
-
# Env vars like {{ env_var('PASSWORD') }} are resolved at deploy time
|
|
912
|
-
|
|
913
|
-
` + YAML.stringify(file, { indent: 2 });
|
|
914
|
-
fs.writeFileSync(filePath, content, { mode: 384 });
|
|
915
|
-
}
|
|
916
|
-
/**
|
|
917
|
-
* Add a single datasource (updates existing or appends new)
|
|
918
|
-
*/
|
|
919
|
-
function addLocalDatasource(datasource, cwd = process.cwd()) {
|
|
920
|
-
const existing = loadLocalDatasources(cwd);
|
|
921
|
-
const index = existing.findIndex((ds) => ds.name === datasource.name);
|
|
922
|
-
if (index >= 0) existing[index] = datasource;
|
|
923
|
-
else existing.push(datasource);
|
|
924
|
-
saveLocalDatasources(existing, cwd);
|
|
925
|
-
}
|
|
926
|
-
/**
|
|
927
|
-
* Remove a datasource by name
|
|
928
|
-
*/
|
|
929
|
-
function removeLocalDatasource(name, cwd = process.cwd()) {
|
|
930
|
-
const existing = loadLocalDatasources(cwd);
|
|
931
|
-
const filtered = existing.filter((ds) => ds.name !== name);
|
|
932
|
-
if (filtered.length === existing.length) return false;
|
|
933
|
-
saveLocalDatasources(filtered, cwd);
|
|
934
|
-
return true;
|
|
935
|
-
}
|
|
936
|
-
/**
|
|
937
|
-
* Get a single datasource by name
|
|
938
|
-
*/
|
|
939
|
-
function getLocalDatasource(name, cwd = process.cwd()) {
|
|
940
|
-
return loadLocalDatasources(cwd).find((ds) => ds.name === name) ?? null;
|
|
941
|
-
}
|
|
942
|
-
/**
|
|
943
|
-
* Check if a datasource name already exists locally
|
|
944
|
-
*/
|
|
945
|
-
function datasourceExists(name, cwd = process.cwd()) {
|
|
946
|
-
return getLocalDatasource(name, cwd) !== null;
|
|
947
|
-
}
|
|
948
|
-
/**
|
|
949
|
-
* Resolve {{ env_var('VAR_NAME') }} patterns in credentials
|
|
950
|
-
* Used at deploy time to resolve env vars before uploading
|
|
951
|
-
*/
|
|
952
|
-
function resolveEnvVarsInCredentials(credentials) {
|
|
953
|
-
const resolved = {};
|
|
954
|
-
const missing = [];
|
|
955
|
-
const envVarPattern = /\{\{\s*env_var\(['"]([\w_]+)['"]\)\s*\}\}/;
|
|
956
|
-
for (const [key, value] of Object.entries(credentials)) {
|
|
957
|
-
const match = value.match(envVarPattern);
|
|
958
|
-
if (match) {
|
|
959
|
-
const varName = match[1];
|
|
960
|
-
const envValue = process.env[varName];
|
|
961
|
-
if (envValue !== void 0) resolved[key] = envValue;
|
|
962
|
-
else {
|
|
963
|
-
missing.push(varName);
|
|
964
|
-
resolved[key] = value;
|
|
965
|
-
}
|
|
966
|
-
} else resolved[key] = value;
|
|
967
|
-
}
|
|
968
|
-
return {
|
|
969
|
-
resolved,
|
|
970
|
-
missing
|
|
971
|
-
};
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
//#endregion
|
|
975
|
-
//#region src/lib/local/credentials.ts
|
|
976
|
-
/**
|
|
977
|
-
* Credential utilities (git tracking check)
|
|
978
|
-
*/
|
|
979
|
-
const BON_DIR$1 = ".bon";
|
|
980
|
-
const DATASOURCES_FILE = "datasources.yaml";
|
|
981
|
-
function getDatasourcesPath(cwd = process.cwd()) {
|
|
982
|
-
return path.join(cwd, BON_DIR$1, DATASOURCES_FILE);
|
|
983
|
-
}
|
|
984
|
-
/**
|
|
985
|
-
* Check if datasources file is tracked by git (it shouldn't be - contains credentials)
|
|
986
|
-
*/
|
|
987
|
-
function isDatasourcesTrackedByGit(cwd = process.cwd()) {
|
|
988
|
-
const filePath = getDatasourcesPath(cwd);
|
|
989
|
-
if (!fs.existsSync(filePath)) return false;
|
|
990
|
-
try {
|
|
991
|
-
execFileSync("git", [
|
|
992
|
-
"ls-files",
|
|
993
|
-
"--error-unmatch",
|
|
994
|
-
filePath
|
|
995
|
-
], {
|
|
996
|
-
cwd,
|
|
997
|
-
stdio: "pipe"
|
|
998
|
-
});
|
|
999
|
-
return true;
|
|
1000
|
-
} catch {
|
|
1001
|
-
return false;
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
//#endregion
|
|
1006
|
-
//#region src/lib/local/index.ts
|
|
1007
|
-
var local_exports = /* @__PURE__ */ __exportAll({
|
|
1008
|
-
addLocalDatasource: () => addLocalDatasource,
|
|
1009
|
-
datasourceExists: () => datasourceExists,
|
|
1010
|
-
ensureBonDir: () => ensureBonDir,
|
|
1011
|
-
getLocalDatasource: () => getLocalDatasource,
|
|
1012
|
-
isDatasourcesTrackedByGit: () => isDatasourcesTrackedByGit,
|
|
1013
|
-
loadLocalDatasources: () => loadLocalDatasources,
|
|
1014
|
-
removeLocalDatasource: () => removeLocalDatasource,
|
|
1015
|
-
resolveEnvVarsInCredentials: () => resolveEnvVarsInCredentials,
|
|
1016
|
-
saveLocalDatasources: () => saveLocalDatasources
|
|
1017
|
-
});
|
|
1018
|
-
|
|
1019
822
|
//#endregion
|
|
1020
823
|
//#region src/lib/dbt/mapping.ts
|
|
1021
824
|
/**
|
|
@@ -1630,7 +1433,7 @@ async function listRemoteDatasources() {
|
|
|
1630
1433
|
return;
|
|
1631
1434
|
}
|
|
1632
1435
|
try {
|
|
1633
|
-
const { get } = await
|
|
1436
|
+
const { get } = await import("./api-B7cdKn9j.mjs");
|
|
1634
1437
|
const result = await get("/api/datasources");
|
|
1635
1438
|
if (result.dataSources.length === 0) {
|
|
1636
1439
|
console.log(pc.dim("No remote data sources found."));
|
|
@@ -1691,7 +1494,7 @@ async function removeRemote(name) {
|
|
|
1691
1494
|
process.exit(1);
|
|
1692
1495
|
}
|
|
1693
1496
|
try {
|
|
1694
|
-
const { del } = await
|
|
1497
|
+
const { del } = await import("./api-B7cdKn9j.mjs");
|
|
1695
1498
|
await del(`/api/datasources/${encodeURIComponent(name)}`);
|
|
1696
1499
|
console.log(pc.green(`✓ Removed "${name}" from remote server`));
|
|
1697
1500
|
} catch (err) {
|
|
@@ -1709,7 +1512,7 @@ async function validateCommand() {
|
|
|
1709
1512
|
console.log(pc.red("No bon.yaml found. Are you in a Bonnard project?"));
|
|
1710
1513
|
process.exit(1);
|
|
1711
1514
|
}
|
|
1712
|
-
const { validate } = await import("./validate-
|
|
1515
|
+
const { validate } = await import("./validate-C4W_Vto2.mjs");
|
|
1713
1516
|
const result = await validate(cwd);
|
|
1714
1517
|
if (result.cubes.length === 0 && result.views.length === 0 && result.valid) {
|
|
1715
1518
|
console.log(pc.yellow(`No cube or view files found in ${BONNARD_DIR}/cubes/ or ${BONNARD_DIR}/views/.`));
|
|
@@ -1783,7 +1586,7 @@ async function deployCommand(options = {}) {
|
|
|
1783
1586
|
process.exit(1);
|
|
1784
1587
|
}
|
|
1785
1588
|
console.log(pc.dim("Validating cubes and views..."));
|
|
1786
|
-
const { validate } = await import("./validate-
|
|
1589
|
+
const { validate } = await import("./validate-C4W_Vto2.mjs");
|
|
1787
1590
|
const result = await validate(cwd);
|
|
1788
1591
|
if (!result.valid) {
|
|
1789
1592
|
console.log(pc.red("Validation failed:\n"));
|
|
@@ -1852,9 +1655,9 @@ async function deployCommand(options = {}) {
|
|
|
1852
1655
|
* Returns true if any connection failed (strict mode)
|
|
1853
1656
|
*/
|
|
1854
1657
|
async function testAndSyncDatasources(cwd, options = {}) {
|
|
1855
|
-
const { extractDatasourcesFromCubes } = await import("./cubes-
|
|
1856
|
-
const { loadLocalDatasources } = await
|
|
1857
|
-
const { pushDatasource } = await import("./push-
|
|
1658
|
+
const { extractDatasourcesFromCubes } = await import("./cubes-BvtwNBUG.mjs");
|
|
1659
|
+
const { loadLocalDatasources } = await import("./local-BkK5XL7T.mjs");
|
|
1660
|
+
const { pushDatasource } = await import("./push-BOkUmRL8.mjs");
|
|
1858
1661
|
const references = extractDatasourcesFromCubes(cwd);
|
|
1859
1662
|
if (references.length === 0) return false;
|
|
1860
1663
|
console.log();
|
|
@@ -3915,7 +3718,9 @@ const metabase = program.command("metabase").description("Connect to and explore
|
|
|
3915
3718
|
metabase.command("connect").description("Configure Metabase API connection").option("--url <url>", "Metabase instance URL").option("--api-key <key>", "Metabase API key").option("--force", "Overwrite existing configuration").action(metabaseConnectCommand);
|
|
3916
3719
|
metabase.command("explore").description("Browse Metabase databases, collections, cards, and dashboards").argument("[resource]", "databases, collections, cards, dashboards, card, dashboard, database, table, collection").argument("[id]", "Resource ID (e.g. card <id>, dashboard <id>, database <id>, table <id>, collection <id>)").action(metabaseExploreCommand);
|
|
3917
3720
|
metabase.command("analyze").description("Analyze Metabase instance and generate a structured report for semantic layer planning").option("--output <path>", "Output file path", ".bon/metabase-analysis.md").option("--top-cards <n>", "Number of top cards to include in report", "50").action(metabaseAnalyzeCommand);
|
|
3918
|
-
|
|
3721
|
+
const showUpdateNotice = startUpdateCheck(version);
|
|
3722
|
+
await program.parseAsync();
|
|
3723
|
+
await showUpdateNotice();
|
|
3919
3724
|
|
|
3920
3725
|
//#endregion
|
|
3921
|
-
export {
|
|
3726
|
+
export { };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { a as getLocalDatasource, c as resolveEnvVarsInCredentials, i as ensureBonDir, l as saveLocalDatasources, n as addLocalDatasource, o as loadLocalDatasources, r as datasourceExists, s as removeLocalDatasource, t as isDatasourcesTrackedByGit } from "./local-ByvuW3eV.mjs";
|
|
2
|
+
|
|
3
|
+
export { loadLocalDatasources };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import YAML from "yaml";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
|
|
6
|
+
//#region src/lib/local/datasources.ts
|
|
7
|
+
/**
|
|
8
|
+
* Local datasource storage (.bon/datasources.yaml)
|
|
9
|
+
*
|
|
10
|
+
* Single file containing both config and credentials.
|
|
11
|
+
* Credentials may contain:
|
|
12
|
+
* - Plain values: "my_password"
|
|
13
|
+
* - dbt env var syntax: "{{ env_var('MY_PASSWORD') }}"
|
|
14
|
+
*
|
|
15
|
+
* Env vars are resolved at deploy time, not import time.
|
|
16
|
+
*/
|
|
17
|
+
const BON_DIR$1 = ".bon";
|
|
18
|
+
const DATASOURCES_FILE$1 = "datasources.yaml";
|
|
19
|
+
function getBonDir(cwd = process.cwd()) {
|
|
20
|
+
return path.join(cwd, BON_DIR$1);
|
|
21
|
+
}
|
|
22
|
+
function getDatasourcesPath$1(cwd = process.cwd()) {
|
|
23
|
+
return path.join(getBonDir(cwd), DATASOURCES_FILE$1);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Ensure .bon directory exists
|
|
27
|
+
*/
|
|
28
|
+
function ensureBonDir(cwd = process.cwd()) {
|
|
29
|
+
const bonDir = getBonDir(cwd);
|
|
30
|
+
if (!fs.existsSync(bonDir)) fs.mkdirSync(bonDir, { recursive: true });
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Load all local datasources
|
|
34
|
+
*/
|
|
35
|
+
function loadLocalDatasources(cwd = process.cwd()) {
|
|
36
|
+
const filePath = getDatasourcesPath$1(cwd);
|
|
37
|
+
if (!fs.existsSync(filePath)) return [];
|
|
38
|
+
try {
|
|
39
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
40
|
+
return YAML.parse(content)?.datasources ?? [];
|
|
41
|
+
} catch {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Save all local datasources (with secure permissions since it contains credentials)
|
|
47
|
+
*/
|
|
48
|
+
function saveLocalDatasources(datasources, cwd = process.cwd()) {
|
|
49
|
+
ensureBonDir(cwd);
|
|
50
|
+
const filePath = getDatasourcesPath$1(cwd);
|
|
51
|
+
const file = { datasources };
|
|
52
|
+
const content = `# Bonnard datasources configuration
|
|
53
|
+
# This file contains credentials - add to .gitignore
|
|
54
|
+
# Env vars like {{ env_var('PASSWORD') }} are resolved at deploy time
|
|
55
|
+
|
|
56
|
+
` + YAML.stringify(file, { indent: 2 });
|
|
57
|
+
fs.writeFileSync(filePath, content, { mode: 384 });
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Add a single datasource (updates existing or appends new)
|
|
61
|
+
*/
|
|
62
|
+
function addLocalDatasource(datasource, cwd = process.cwd()) {
|
|
63
|
+
const existing = loadLocalDatasources(cwd);
|
|
64
|
+
const index = existing.findIndex((ds) => ds.name === datasource.name);
|
|
65
|
+
if (index >= 0) existing[index] = datasource;
|
|
66
|
+
else existing.push(datasource);
|
|
67
|
+
saveLocalDatasources(existing, cwd);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Remove a datasource by name
|
|
71
|
+
*/
|
|
72
|
+
function removeLocalDatasource(name, cwd = process.cwd()) {
|
|
73
|
+
const existing = loadLocalDatasources(cwd);
|
|
74
|
+
const filtered = existing.filter((ds) => ds.name !== name);
|
|
75
|
+
if (filtered.length === existing.length) return false;
|
|
76
|
+
saveLocalDatasources(filtered, cwd);
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get a single datasource by name
|
|
81
|
+
*/
|
|
82
|
+
function getLocalDatasource(name, cwd = process.cwd()) {
|
|
83
|
+
return loadLocalDatasources(cwd).find((ds) => ds.name === name) ?? null;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Check if a datasource name already exists locally
|
|
87
|
+
*/
|
|
88
|
+
function datasourceExists(name, cwd = process.cwd()) {
|
|
89
|
+
return getLocalDatasource(name, cwd) !== null;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Resolve {{ env_var('VAR_NAME') }} patterns in credentials
|
|
93
|
+
* Used at deploy time to resolve env vars before uploading
|
|
94
|
+
*/
|
|
95
|
+
function resolveEnvVarsInCredentials(credentials) {
|
|
96
|
+
const resolved = {};
|
|
97
|
+
const missing = [];
|
|
98
|
+
const envVarPattern = /\{\{\s*env_var\(['"]([\w_]+)['"]\)\s*\}\}/;
|
|
99
|
+
for (const [key, value] of Object.entries(credentials)) {
|
|
100
|
+
const match = value.match(envVarPattern);
|
|
101
|
+
if (match) {
|
|
102
|
+
const varName = match[1];
|
|
103
|
+
const envValue = process.env[varName];
|
|
104
|
+
if (envValue !== void 0) resolved[key] = envValue;
|
|
105
|
+
else {
|
|
106
|
+
missing.push(varName);
|
|
107
|
+
resolved[key] = value;
|
|
108
|
+
}
|
|
109
|
+
} else resolved[key] = value;
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
resolved,
|
|
113
|
+
missing
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/lib/local/credentials.ts
|
|
119
|
+
/**
|
|
120
|
+
* Credential utilities (git tracking check)
|
|
121
|
+
*/
|
|
122
|
+
const BON_DIR = ".bon";
|
|
123
|
+
const DATASOURCES_FILE = "datasources.yaml";
|
|
124
|
+
function getDatasourcesPath(cwd = process.cwd()) {
|
|
125
|
+
return path.join(cwd, BON_DIR, DATASOURCES_FILE);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Check if datasources file is tracked by git (it shouldn't be - contains credentials)
|
|
129
|
+
*/
|
|
130
|
+
function isDatasourcesTrackedByGit(cwd = process.cwd()) {
|
|
131
|
+
const filePath = getDatasourcesPath(cwd);
|
|
132
|
+
if (!fs.existsSync(filePath)) return false;
|
|
133
|
+
try {
|
|
134
|
+
execFileSync("git", [
|
|
135
|
+
"ls-files",
|
|
136
|
+
"--error-unmatch",
|
|
137
|
+
filePath
|
|
138
|
+
], {
|
|
139
|
+
cwd,
|
|
140
|
+
stdio: "pipe"
|
|
141
|
+
});
|
|
142
|
+
return true;
|
|
143
|
+
} catch {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
//#endregion
|
|
149
|
+
export { getLocalDatasource as a, resolveEnvVarsInCredentials as c, ensureBonDir as i, saveLocalDatasources as l, addLocalDatasource as n, loadLocalDatasources as o, datasourceExists as r, removeLocalDatasource as s, isDatasourcesTrackedByGit as t };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
//#region src/lib/project.ts
|
|
5
|
+
/**
|
|
6
|
+
* The subdirectory name used for Bonnard cube/view files.
|
|
7
|
+
* Keeps Bonnard files namespaced to avoid conflicts with existing
|
|
8
|
+
* project directories (e.g. dbt's models/).
|
|
9
|
+
*/
|
|
10
|
+
const BONNARD_DIR = "bonnard";
|
|
11
|
+
/**
|
|
12
|
+
* Resolve Bonnard project paths relative to the working directory.
|
|
13
|
+
* All cube/view operations should use these paths.
|
|
14
|
+
*/
|
|
15
|
+
function getProjectPaths(cwd) {
|
|
16
|
+
const bonnardRoot = path.join(cwd, BONNARD_DIR);
|
|
17
|
+
return {
|
|
18
|
+
root: bonnardRoot,
|
|
19
|
+
cubes: path.join(bonnardRoot, "cubes"),
|
|
20
|
+
views: path.join(bonnardRoot, "views"),
|
|
21
|
+
config: path.join(cwd, "bon.yaml"),
|
|
22
|
+
localState: path.join(cwd, ".bon")
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
export { getProjectPaths as n, BONNARD_DIR as t };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { r as post } from "./api-DqgY-30K.mjs";
|
|
2
|
+
import { a as getLocalDatasource, c as resolveEnvVarsInCredentials } from "./local-ByvuW3eV.mjs";
|
|
2
3
|
import pc from "picocolors";
|
|
3
4
|
import "@inquirer/prompts";
|
|
4
5
|
|