@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.
@@ -0,0 +1,3 @@
1
+ import { n as get, r as post, t as del } from "./api-DqgY-30K.mjs";
2
+
3
+ export { del, get };
@@ -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 { fileURLToPath } from "node:url";
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/project.ts
34
- /**
35
- * The subdirectory name used for Bonnard cube/view files.
36
- * Keeps Bonnard files namespaced to avoid conflicts with existing
37
- * project directories (e.g. dbt's models/).
38
- */
39
- const BONNARD_DIR = "bonnard";
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
- * Resolve Bonnard project paths relative to the working directory.
42
- * All cube/view operations should use these paths.
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 getProjectPaths(cwd) {
45
- const bonnardRoot = path.join(cwd, BONNARD_DIR);
46
- return {
47
- root: bonnardRoot,
48
- cubes: path.join(bonnardRoot, "cubes"),
49
- views: path.join(bonnardRoot, "views"),
50
- config: path.join(cwd, "bon.yaml"),
51
- localState: path.join(cwd, ".bon")
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$1 = process.env.BON_APP_URL || "https://app.bonnard.dev";
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$1}/auth/device?state=${state}&port=${port}`;
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 Promise.resolve().then(() => api_exports);
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 Promise.resolve().then(() => api_exports);
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-Bc8zGNw7.mjs");
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-Bc8zGNw7.mjs");
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-9rklhdAJ.mjs");
1856
- const { loadLocalDatasources } = await Promise.resolve().then(() => local_exports);
1857
- const { pushDatasource } = await import("./push-Bv9AFGc2.mjs");
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
- program.parse();
3721
+ const showUpdateNotice = startUpdateCheck(version);
3722
+ await program.parseAsync();
3723
+ await showUpdateNotice();
3919
3724
 
3920
3725
  //#endregion
3921
- export { getProjectPaths as i, resolveEnvVarsInCredentials as n, post as r, getLocalDatasource as t };
3726
+ export { };
@@ -1,4 +1,4 @@
1
- import { i as getProjectPaths } from "./bon.mjs";
1
+ import { n as getProjectPaths } from "./project-Dj085D_B.mjs";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import YAML from "yaml";
@@ -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 { n as resolveEnvVarsInCredentials, r as post, t as getLocalDatasource } from "./bon.mjs";
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
 
@@ -1,4 +1,4 @@
1
- import { i as getProjectPaths } from "./bon.mjs";
1
+ import { n as getProjectPaths } from "./project-Dj085D_B.mjs";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import YAML from "yaml";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bonnard/cli",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "bon": "./dist/bin/bon.mjs"