@bonnard/cli 0.2.0 → 0.2.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/bin/bon.mjs +62 -621
- package/dist/bin/validate-DEh1XQnH.mjs +365 -0
- package/dist/docs/topics/cubes.measures.format.md +19 -0
- package/dist/docs/topics/features.cli.md +1 -2
- package/dist/docs/topics/workflow.md +2 -5
- package/dist/docs/topics/workflow.validate.md +2 -29
- package/dist/templates/claude/skills/bonnard-get-started/SKILL.md +5 -24
- package/dist/templates/cursor/rules/bonnard-get-started.mdc +5 -24
- package/dist/templates/shared/bonnard.md +2 -4
- package/package.json +4 -8
- package/dist/bin/validate-C31hmPk8.mjs +0 -122
- /package/dist/bin/{cubes-De1_2_YJ.mjs → cubes-Bf0IPYd7.mjs} +0 -0
package/dist/bin/bon.mjs
CHANGED
|
@@ -1519,7 +1519,6 @@ async function addDemo(options) {
|
|
|
1519
1519
|
console.log(pc.dim(" fact_sales, dim_product, dim_store, dim_customer"));
|
|
1520
1520
|
console.log();
|
|
1521
1521
|
console.log(pc.dim(`Test connection: bon datasource test ${name}`));
|
|
1522
|
-
console.log(pc.dim(`Explore tables: bon preview ${name} "SELECT table_name FROM information_schema.tables WHERE table_schema = 'contoso'"`));
|
|
1523
1522
|
}
|
|
1524
1523
|
/**
|
|
1525
1524
|
* Main datasource add command
|
|
@@ -1626,464 +1625,11 @@ async function datasourceListCommand(options = {}) {
|
|
|
1626
1625
|
if (showRemote) await listRemoteDatasources();
|
|
1627
1626
|
}
|
|
1628
1627
|
|
|
1629
|
-
//#endregion
|
|
1630
|
-
//#region src/lib/connection/snowflake.ts
|
|
1631
|
-
/**
|
|
1632
|
-
* Snowflake connection testing and querying
|
|
1633
|
-
*/
|
|
1634
|
-
const require$4 = createRequire(import.meta.url);
|
|
1635
|
-
function loadSnowflake() {
|
|
1636
|
-
try {
|
|
1637
|
-
const snowflake = require$4("snowflake-sdk");
|
|
1638
|
-
snowflake.configure({ logLevel: "ERROR" });
|
|
1639
|
-
return snowflake;
|
|
1640
|
-
} catch {
|
|
1641
|
-
return null;
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
async function testSnowflakeConnection(config, credentials) {
|
|
1645
|
-
const snowflake = loadSnowflake();
|
|
1646
|
-
if (!snowflake) return {
|
|
1647
|
-
success: false,
|
|
1648
|
-
message: "Snowflake driver not installed",
|
|
1649
|
-
error: "Run: pnpm add snowflake-sdk"
|
|
1650
|
-
};
|
|
1651
|
-
const startTime = Date.now();
|
|
1652
|
-
return new Promise((resolve) => {
|
|
1653
|
-
const connection = snowflake.createConnection({
|
|
1654
|
-
account: config.account,
|
|
1655
|
-
username: credentials.username,
|
|
1656
|
-
password: credentials.password,
|
|
1657
|
-
database: config.database,
|
|
1658
|
-
warehouse: config.warehouse,
|
|
1659
|
-
schema: config.schema,
|
|
1660
|
-
role: config.role
|
|
1661
|
-
});
|
|
1662
|
-
connection.connect((err) => {
|
|
1663
|
-
if (err) {
|
|
1664
|
-
resolve({
|
|
1665
|
-
success: false,
|
|
1666
|
-
message: "Connection failed",
|
|
1667
|
-
error: err.message
|
|
1668
|
-
});
|
|
1669
|
-
return;
|
|
1670
|
-
}
|
|
1671
|
-
connection.execute({
|
|
1672
|
-
sqlText: "SELECT 1",
|
|
1673
|
-
complete: (queryErr) => {
|
|
1674
|
-
const latencyMs = Date.now() - startTime;
|
|
1675
|
-
connection.destroy(() => {});
|
|
1676
|
-
if (queryErr) resolve({
|
|
1677
|
-
success: false,
|
|
1678
|
-
message: "Query failed",
|
|
1679
|
-
error: queryErr.message,
|
|
1680
|
-
latencyMs
|
|
1681
|
-
});
|
|
1682
|
-
else resolve({
|
|
1683
|
-
success: true,
|
|
1684
|
-
message: "Connection successful",
|
|
1685
|
-
latencyMs
|
|
1686
|
-
});
|
|
1687
|
-
}
|
|
1688
|
-
});
|
|
1689
|
-
});
|
|
1690
|
-
});
|
|
1691
|
-
}
|
|
1692
|
-
async function querySnowflake(config, credentials, sql, options = {}) {
|
|
1693
|
-
const snowflake = loadSnowflake();
|
|
1694
|
-
if (!snowflake) return {
|
|
1695
|
-
columns: [],
|
|
1696
|
-
rows: [],
|
|
1697
|
-
rowCount: 0,
|
|
1698
|
-
truncated: false,
|
|
1699
|
-
error: "Snowflake driver not installed. Run: pnpm add snowflake-sdk"
|
|
1700
|
-
};
|
|
1701
|
-
const limit = options.limit ?? 1e3;
|
|
1702
|
-
return new Promise((resolve) => {
|
|
1703
|
-
const connection = snowflake.createConnection({
|
|
1704
|
-
account: config.account,
|
|
1705
|
-
username: credentials.username,
|
|
1706
|
-
password: credentials.password,
|
|
1707
|
-
database: options.database || config.database,
|
|
1708
|
-
warehouse: config.warehouse,
|
|
1709
|
-
schema: options.schema || config.schema,
|
|
1710
|
-
role: config.role
|
|
1711
|
-
});
|
|
1712
|
-
connection.connect((err) => {
|
|
1713
|
-
if (err) {
|
|
1714
|
-
resolve({
|
|
1715
|
-
columns: [],
|
|
1716
|
-
rows: [],
|
|
1717
|
-
rowCount: 0,
|
|
1718
|
-
truncated: false,
|
|
1719
|
-
error: err.message
|
|
1720
|
-
});
|
|
1721
|
-
return;
|
|
1722
|
-
}
|
|
1723
|
-
connection.execute({
|
|
1724
|
-
sqlText: sql,
|
|
1725
|
-
complete: (queryErr, _stmt, rows) => {
|
|
1726
|
-
connection.destroy(() => {});
|
|
1727
|
-
if (queryErr) {
|
|
1728
|
-
resolve({
|
|
1729
|
-
columns: [],
|
|
1730
|
-
rows: [],
|
|
1731
|
-
rowCount: 0,
|
|
1732
|
-
truncated: false,
|
|
1733
|
-
error: queryErr.message
|
|
1734
|
-
});
|
|
1735
|
-
return;
|
|
1736
|
-
}
|
|
1737
|
-
const allRows = rows || [];
|
|
1738
|
-
const truncated = allRows.length > limit;
|
|
1739
|
-
const resultRows = truncated ? allRows.slice(0, limit) : allRows;
|
|
1740
|
-
resolve({
|
|
1741
|
-
columns: resultRows.length > 0 ? Object.keys(resultRows[0]) : [],
|
|
1742
|
-
rows: resultRows,
|
|
1743
|
-
rowCount: resultRows.length,
|
|
1744
|
-
truncated
|
|
1745
|
-
});
|
|
1746
|
-
}
|
|
1747
|
-
});
|
|
1748
|
-
});
|
|
1749
|
-
});
|
|
1750
|
-
}
|
|
1751
|
-
|
|
1752
|
-
//#endregion
|
|
1753
|
-
//#region src/lib/connection/postgres.ts
|
|
1754
|
-
/**
|
|
1755
|
-
* Postgres connection testing and querying
|
|
1756
|
-
*/
|
|
1757
|
-
const require$3 = createRequire(import.meta.url);
|
|
1758
|
-
function loadPg() {
|
|
1759
|
-
try {
|
|
1760
|
-
return require$3("pg");
|
|
1761
|
-
} catch {
|
|
1762
|
-
return null;
|
|
1763
|
-
}
|
|
1764
|
-
}
|
|
1765
|
-
function createClient(config, credentials, pg) {
|
|
1766
|
-
return new pg.Client({
|
|
1767
|
-
host: config.host,
|
|
1768
|
-
port: config.port ? parseInt(config.port, 10) : 5432,
|
|
1769
|
-
database: config.database,
|
|
1770
|
-
user: credentials.username,
|
|
1771
|
-
password: credentials.password,
|
|
1772
|
-
ssl: config.sslmode === "require" ? { rejectUnauthorized: false } : void 0
|
|
1773
|
-
});
|
|
1774
|
-
}
|
|
1775
|
-
async function testPostgresConnection(config, credentials) {
|
|
1776
|
-
const pg = loadPg();
|
|
1777
|
-
if (!pg) return {
|
|
1778
|
-
success: false,
|
|
1779
|
-
message: "Postgres driver not installed",
|
|
1780
|
-
error: "Run: pnpm add pg"
|
|
1781
|
-
};
|
|
1782
|
-
const startTime = Date.now();
|
|
1783
|
-
const client = createClient(config, credentials, pg);
|
|
1784
|
-
try {
|
|
1785
|
-
await client.connect();
|
|
1786
|
-
await client.query("SELECT 1");
|
|
1787
|
-
const latencyMs = Date.now() - startTime;
|
|
1788
|
-
await client.end();
|
|
1789
|
-
return {
|
|
1790
|
-
success: true,
|
|
1791
|
-
message: "Connection successful",
|
|
1792
|
-
latencyMs
|
|
1793
|
-
};
|
|
1794
|
-
} catch (err) {
|
|
1795
|
-
try {
|
|
1796
|
-
await client.end();
|
|
1797
|
-
} catch {}
|
|
1798
|
-
return {
|
|
1799
|
-
success: false,
|
|
1800
|
-
message: "Connection failed",
|
|
1801
|
-
error: err.message
|
|
1802
|
-
};
|
|
1803
|
-
}
|
|
1804
|
-
}
|
|
1805
|
-
async function queryPostgres(config, credentials, sql, options = {}) {
|
|
1806
|
-
const pg = loadPg();
|
|
1807
|
-
if (!pg) return {
|
|
1808
|
-
columns: [],
|
|
1809
|
-
rows: [],
|
|
1810
|
-
rowCount: 0,
|
|
1811
|
-
truncated: false,
|
|
1812
|
-
error: "Postgres driver not installed. Run: pnpm add pg"
|
|
1813
|
-
};
|
|
1814
|
-
const limit = options.limit ?? 1e3;
|
|
1815
|
-
const client = createClient(config, credentials, pg);
|
|
1816
|
-
try {
|
|
1817
|
-
await client.connect();
|
|
1818
|
-
const schema = options.schema || config.schema;
|
|
1819
|
-
if (schema) {
|
|
1820
|
-
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(schema)) throw new Error("Invalid schema name");
|
|
1821
|
-
await client.query(`SET search_path TO "${schema}"`);
|
|
1822
|
-
}
|
|
1823
|
-
const result = await client.query(sql);
|
|
1824
|
-
await client.end();
|
|
1825
|
-
const columns = result.fields?.map((f) => f.name) || [];
|
|
1826
|
-
const allRows = result.rows || [];
|
|
1827
|
-
const truncated = allRows.length > limit;
|
|
1828
|
-
const rows = truncated ? allRows.slice(0, limit) : allRows;
|
|
1829
|
-
return {
|
|
1830
|
-
columns,
|
|
1831
|
-
rows,
|
|
1832
|
-
rowCount: rows.length,
|
|
1833
|
-
truncated
|
|
1834
|
-
};
|
|
1835
|
-
} catch (err) {
|
|
1836
|
-
try {
|
|
1837
|
-
await client.end();
|
|
1838
|
-
} catch {}
|
|
1839
|
-
return {
|
|
1840
|
-
columns: [],
|
|
1841
|
-
rows: [],
|
|
1842
|
-
rowCount: 0,
|
|
1843
|
-
truncated: false,
|
|
1844
|
-
error: err.message
|
|
1845
|
-
};
|
|
1846
|
-
}
|
|
1847
|
-
}
|
|
1848
|
-
|
|
1849
|
-
//#endregion
|
|
1850
|
-
//#region src/lib/connection/bigquery.ts
|
|
1851
|
-
/**
|
|
1852
|
-
* BigQuery connection testing
|
|
1853
|
-
*/
|
|
1854
|
-
const require$2 = createRequire(import.meta.url);
|
|
1855
|
-
async function testBigQueryConnection(config, credentials) {
|
|
1856
|
-
let BigQuery;
|
|
1857
|
-
try {
|
|
1858
|
-
BigQuery = require$2("@google-cloud/bigquery").BigQuery;
|
|
1859
|
-
} catch {
|
|
1860
|
-
return {
|
|
1861
|
-
success: false,
|
|
1862
|
-
message: "BigQuery driver not installed",
|
|
1863
|
-
error: "Run: pnpm add @google-cloud/bigquery"
|
|
1864
|
-
};
|
|
1865
|
-
}
|
|
1866
|
-
const startTime = Date.now();
|
|
1867
|
-
try {
|
|
1868
|
-
const options = { projectId: config.project_id };
|
|
1869
|
-
if (config.location) options.location = config.location;
|
|
1870
|
-
if (credentials.service_account_json) options.credentials = JSON.parse(credentials.service_account_json);
|
|
1871
|
-
else if (credentials.keyfile_path) options.keyFilename = credentials.keyfile_path;
|
|
1872
|
-
await new BigQuery(options).query("SELECT 1");
|
|
1873
|
-
return {
|
|
1874
|
-
success: true,
|
|
1875
|
-
message: "Connection successful",
|
|
1876
|
-
latencyMs: Date.now() - startTime
|
|
1877
|
-
};
|
|
1878
|
-
} catch (err) {
|
|
1879
|
-
return {
|
|
1880
|
-
success: false,
|
|
1881
|
-
message: "Connection failed",
|
|
1882
|
-
error: err.message
|
|
1883
|
-
};
|
|
1884
|
-
}
|
|
1885
|
-
}
|
|
1886
|
-
|
|
1887
|
-
//#endregion
|
|
1888
|
-
//#region src/lib/connection/databricks.ts
|
|
1889
|
-
/**
|
|
1890
|
-
* Databricks connection testing
|
|
1891
|
-
*/
|
|
1892
|
-
const require$1 = createRequire(import.meta.url);
|
|
1893
|
-
async function testDatabricksConnection(config, credentials) {
|
|
1894
|
-
let DBSQLClient;
|
|
1895
|
-
try {
|
|
1896
|
-
const module = require$1("@databricks/sql");
|
|
1897
|
-
DBSQLClient = module.default || module;
|
|
1898
|
-
} catch {
|
|
1899
|
-
return {
|
|
1900
|
-
success: false,
|
|
1901
|
-
message: "Databricks driver not installed",
|
|
1902
|
-
error: "Run: pnpm add @databricks/sql"
|
|
1903
|
-
};
|
|
1904
|
-
}
|
|
1905
|
-
const startTime = Date.now();
|
|
1906
|
-
const client = new DBSQLClient();
|
|
1907
|
-
try {
|
|
1908
|
-
const connection = await client.connect({
|
|
1909
|
-
host: config.hostname,
|
|
1910
|
-
path: config.http_path,
|
|
1911
|
-
token: credentials.token
|
|
1912
|
-
});
|
|
1913
|
-
const session = await connection.openSession({
|
|
1914
|
-
initialCatalog: config.catalog,
|
|
1915
|
-
initialSchema: config.schema
|
|
1916
|
-
});
|
|
1917
|
-
const operation = await session.executeStatement("SELECT 1");
|
|
1918
|
-
await operation.fetchAll();
|
|
1919
|
-
await operation.close();
|
|
1920
|
-
const latencyMs = Date.now() - startTime;
|
|
1921
|
-
await session.close();
|
|
1922
|
-
await connection.close();
|
|
1923
|
-
return {
|
|
1924
|
-
success: true,
|
|
1925
|
-
message: "Connection successful",
|
|
1926
|
-
latencyMs
|
|
1927
|
-
};
|
|
1928
|
-
} catch (err) {
|
|
1929
|
-
try {
|
|
1930
|
-
await client.close();
|
|
1931
|
-
} catch {}
|
|
1932
|
-
return {
|
|
1933
|
-
success: false,
|
|
1934
|
-
message: "Connection failed",
|
|
1935
|
-
error: err.message
|
|
1936
|
-
};
|
|
1937
|
-
}
|
|
1938
|
-
}
|
|
1939
|
-
|
|
1940
|
-
//#endregion
|
|
1941
|
-
//#region src/lib/connection/index.ts
|
|
1942
|
-
var connection_exports = /* @__PURE__ */ __exportAll({
|
|
1943
|
-
executeQuery: () => executeQuery,
|
|
1944
|
-
testConnection: () => testConnection
|
|
1945
|
-
});
|
|
1946
|
-
/**
|
|
1947
|
-
* Test connection to a datasource
|
|
1948
|
-
*/
|
|
1949
|
-
async function testConnection(datasource) {
|
|
1950
|
-
const { type, config, credentials } = datasource;
|
|
1951
|
-
switch (type) {
|
|
1952
|
-
case "snowflake": return testSnowflakeConnection({
|
|
1953
|
-
account: config.account,
|
|
1954
|
-
database: config.database,
|
|
1955
|
-
warehouse: config.warehouse,
|
|
1956
|
-
schema: config.schema,
|
|
1957
|
-
role: config.role
|
|
1958
|
-
}, {
|
|
1959
|
-
username: credentials.username,
|
|
1960
|
-
password: credentials.password
|
|
1961
|
-
});
|
|
1962
|
-
case "postgres": return testPostgresConnection({
|
|
1963
|
-
host: config.host,
|
|
1964
|
-
port: config.port,
|
|
1965
|
-
database: config.database,
|
|
1966
|
-
schema: config.schema,
|
|
1967
|
-
sslmode: config.sslmode
|
|
1968
|
-
}, {
|
|
1969
|
-
username: credentials.username,
|
|
1970
|
-
password: credentials.password
|
|
1971
|
-
});
|
|
1972
|
-
case "bigquery": return testBigQueryConnection({
|
|
1973
|
-
project_id: config.project_id,
|
|
1974
|
-
dataset: config.dataset,
|
|
1975
|
-
location: config.location
|
|
1976
|
-
}, {
|
|
1977
|
-
service_account_json: credentials.service_account_json,
|
|
1978
|
-
keyfile_path: credentials.keyfile_path
|
|
1979
|
-
});
|
|
1980
|
-
case "databricks": return testDatabricksConnection({
|
|
1981
|
-
hostname: config.hostname,
|
|
1982
|
-
http_path: config.http_path,
|
|
1983
|
-
catalog: config.catalog,
|
|
1984
|
-
schema: config.schema
|
|
1985
|
-
}, { token: credentials.token });
|
|
1986
|
-
default: return {
|
|
1987
|
-
success: false,
|
|
1988
|
-
message: `Unsupported warehouse type: ${type}`
|
|
1989
|
-
};
|
|
1990
|
-
}
|
|
1991
|
-
}
|
|
1992
|
-
/**
|
|
1993
|
-
* Execute a query against a datasource
|
|
1994
|
-
*/
|
|
1995
|
-
async function executeQuery(datasource, sql, options = {}) {
|
|
1996
|
-
const { type, config, credentials } = datasource;
|
|
1997
|
-
switch (type) {
|
|
1998
|
-
case "snowflake": return querySnowflake({
|
|
1999
|
-
account: config.account,
|
|
2000
|
-
database: config.database,
|
|
2001
|
-
warehouse: config.warehouse,
|
|
2002
|
-
schema: config.schema,
|
|
2003
|
-
role: config.role
|
|
2004
|
-
}, {
|
|
2005
|
-
username: credentials.username,
|
|
2006
|
-
password: credentials.password
|
|
2007
|
-
}, sql, options);
|
|
2008
|
-
case "postgres": return queryPostgres({
|
|
2009
|
-
host: config.host,
|
|
2010
|
-
port: config.port,
|
|
2011
|
-
database: config.database,
|
|
2012
|
-
schema: config.schema,
|
|
2013
|
-
sslmode: config.sslmode
|
|
2014
|
-
}, {
|
|
2015
|
-
username: credentials.username,
|
|
2016
|
-
password: credentials.password
|
|
2017
|
-
}, sql, options);
|
|
2018
|
-
case "bigquery": return {
|
|
2019
|
-
columns: [],
|
|
2020
|
-
rows: [],
|
|
2021
|
-
rowCount: 0,
|
|
2022
|
-
truncated: false,
|
|
2023
|
-
error: "BigQuery local querying not yet implemented"
|
|
2024
|
-
};
|
|
2025
|
-
case "databricks": return {
|
|
2026
|
-
columns: [],
|
|
2027
|
-
rows: [],
|
|
2028
|
-
rowCount: 0,
|
|
2029
|
-
truncated: false,
|
|
2030
|
-
error: "Databricks local querying not yet implemented"
|
|
2031
|
-
};
|
|
2032
|
-
default: return {
|
|
2033
|
-
columns: [],
|
|
2034
|
-
rows: [],
|
|
2035
|
-
rowCount: 0,
|
|
2036
|
-
truncated: false,
|
|
2037
|
-
error: `Unsupported warehouse type: ${type}`
|
|
2038
|
-
};
|
|
2039
|
-
}
|
|
2040
|
-
}
|
|
2041
|
-
|
|
2042
1628
|
//#endregion
|
|
2043
1629
|
//#region src/commands/datasource/test.ts
|
|
2044
|
-
async function datasourceTestCommand(name
|
|
2045
|
-
const localDs = getLocalDatasource(name);
|
|
2046
|
-
if (options.remote || !localDs) {
|
|
2047
|
-
await testRemote(name, !localDs);
|
|
2048
|
-
return;
|
|
2049
|
-
}
|
|
2050
|
-
await testLocal(name, localDs);
|
|
2051
|
-
}
|
|
2052
|
-
/**
|
|
2053
|
-
* Test datasource locally using direct connection
|
|
2054
|
-
*/
|
|
2055
|
-
async function testLocal(name, ds) {
|
|
2056
|
-
console.log(pc.dim(`Testing ${name} locally...`));
|
|
2057
|
-
console.log();
|
|
2058
|
-
const { resolved, missing } = resolveEnvVarsInCredentials(ds.credentials);
|
|
2059
|
-
if (missing.length > 0) {
|
|
2060
|
-
console.log(pc.red(`Missing environment variables: ${missing.join(", ")}`));
|
|
2061
|
-
console.log(pc.dim("Set these env vars or update .bon/datasources.yaml with actual values."));
|
|
2062
|
-
process.exit(1);
|
|
2063
|
-
}
|
|
2064
|
-
const result = await testConnection({
|
|
2065
|
-
type: ds.type,
|
|
2066
|
-
config: ds.config,
|
|
2067
|
-
credentials: resolved
|
|
2068
|
-
});
|
|
2069
|
-
if (result.success) {
|
|
2070
|
-
console.log(pc.green(`✓ ${result.message}`));
|
|
2071
|
-
if (result.latencyMs) console.log(pc.dim(` Latency: ${result.latencyMs}ms`));
|
|
2072
|
-
} else {
|
|
2073
|
-
console.log(pc.red(`✗ ${result.message}`));
|
|
2074
|
-
if (result.error) console.log(pc.dim(` ${result.error}`));
|
|
2075
|
-
process.exit(1);
|
|
2076
|
-
}
|
|
2077
|
-
}
|
|
2078
|
-
/**
|
|
2079
|
-
* Test datasource via remote API (requires login)
|
|
2080
|
-
*/
|
|
2081
|
-
async function testRemote(name, notFoundLocally) {
|
|
1630
|
+
async function datasourceTestCommand(name) {
|
|
2082
1631
|
if (!loadCredentials()) {
|
|
2083
|
-
|
|
2084
|
-
console.log(pc.red(`Datasource "${name}" not found locally.`));
|
|
2085
|
-
console.log(pc.dim("Run `bon datasource add` to create it, or `bon login` to test remote datasources."));
|
|
2086
|
-
} else console.log(pc.red("Not logged in. Run `bon login` to test remote datasources."));
|
|
1632
|
+
console.log(pc.red("Not logged in. Run `bon login` to test datasources."));
|
|
2087
1633
|
process.exit(1);
|
|
2088
1634
|
}
|
|
2089
1635
|
console.log(pc.dim(`Testing ${name} via remote API...`));
|
|
@@ -2228,58 +1774,16 @@ async function pushDatasource(name, options = {}) {
|
|
|
2228
1774
|
}
|
|
2229
1775
|
}
|
|
2230
1776
|
|
|
2231
|
-
//#endregion
|
|
2232
|
-
//#region src/commands/preview.ts
|
|
2233
|
-
async function previewCommand(datasourceName, sql, options) {
|
|
2234
|
-
const limit = options.limit ? parseInt(options.limit, 10) : 1e3;
|
|
2235
|
-
const format = options.format ?? "toon";
|
|
2236
|
-
const ds = getLocalDatasource(datasourceName);
|
|
2237
|
-
if (!ds) {
|
|
2238
|
-
console.error(pc.red(`Datasource "${datasourceName}" not found in .bon/datasources.yaml`));
|
|
2239
|
-
console.log(pc.dim("Run `bon datasource add` to create it."));
|
|
2240
|
-
process.exit(1);
|
|
2241
|
-
}
|
|
2242
|
-
const { resolved, missing } = resolveEnvVarsInCredentials(ds.credentials);
|
|
2243
|
-
if (missing.length > 0) {
|
|
2244
|
-
console.error(pc.red(`Missing environment variables: ${missing.join(", ")}`));
|
|
2245
|
-
console.log(pc.dim("Set these env vars or update .bon/datasources.yaml with actual values."));
|
|
2246
|
-
process.exit(1);
|
|
2247
|
-
}
|
|
2248
|
-
const result = await executeQuery({
|
|
2249
|
-
type: ds.type,
|
|
2250
|
-
config: ds.config,
|
|
2251
|
-
credentials: resolved
|
|
2252
|
-
}, sql, {
|
|
2253
|
-
limit,
|
|
2254
|
-
schema: options.schema,
|
|
2255
|
-
database: options.database
|
|
2256
|
-
});
|
|
2257
|
-
if (result.error) {
|
|
2258
|
-
console.error(pc.red(result.error));
|
|
2259
|
-
process.exit(1);
|
|
2260
|
-
}
|
|
2261
|
-
if (result.rowCount === 0) {
|
|
2262
|
-
console.log("No rows returned.");
|
|
2263
|
-
return;
|
|
2264
|
-
}
|
|
2265
|
-
if (format === "json") console.log(JSON.stringify(result, null, 2));
|
|
2266
|
-
else {
|
|
2267
|
-
const toon = encode({ results: result.rows });
|
|
2268
|
-
console.log(toon);
|
|
2269
|
-
}
|
|
2270
|
-
if (result.truncated) console.log(pc.dim(`(truncated to ${result.rowCount} rows)`));
|
|
2271
|
-
}
|
|
2272
|
-
|
|
2273
1777
|
//#endregion
|
|
2274
1778
|
//#region src/commands/validate.ts
|
|
2275
|
-
async function validateCommand(
|
|
1779
|
+
async function validateCommand() {
|
|
2276
1780
|
const cwd = process.cwd();
|
|
2277
1781
|
const paths = getProjectPaths(cwd);
|
|
2278
1782
|
if (!fs.existsSync(paths.config)) {
|
|
2279
1783
|
console.log(pc.red("No bon.yaml found. Are you in a Bonnard project?"));
|
|
2280
1784
|
process.exit(1);
|
|
2281
1785
|
}
|
|
2282
|
-
const { validate } = await import("./validate-
|
|
1786
|
+
const { validate } = await import("./validate-DEh1XQnH.mjs");
|
|
2283
1787
|
const result = await validate(cwd);
|
|
2284
1788
|
if (result.cubes.length === 0 && result.views.length === 0 && result.valid) {
|
|
2285
1789
|
console.log(pc.yellow(`No cube or view files found in ${BONNARD_DIR}/cubes/ or ${BONNARD_DIR}/views/.`));
|
|
@@ -2314,62 +1818,6 @@ async function validateCommand(options = {}) {
|
|
|
2314
1818
|
console.log(pc.dim(" This can cause issues when multiple warehouses are configured."));
|
|
2315
1819
|
console.log(pc.dim(` ${result.cubesMissingDataSource.join(", ")}`));
|
|
2316
1820
|
}
|
|
2317
|
-
if (options.testConnection) {
|
|
2318
|
-
console.log();
|
|
2319
|
-
await testReferencedConnections(cwd);
|
|
2320
|
-
}
|
|
2321
|
-
}
|
|
2322
|
-
/**
|
|
2323
|
-
* Test connections for datasources referenced by cubes and views
|
|
2324
|
-
* Lenient: warns but doesn't fail validation
|
|
2325
|
-
*/
|
|
2326
|
-
async function testReferencedConnections(cwd) {
|
|
2327
|
-
const { extractDatasourcesFromCubes } = await import("./cubes-De1_2_YJ.mjs");
|
|
2328
|
-
const { loadLocalDatasources, resolveEnvVarsInCredentials } = await Promise.resolve().then(() => local_exports);
|
|
2329
|
-
const { testConnection } = await Promise.resolve().then(() => connection_exports);
|
|
2330
|
-
const references = extractDatasourcesFromCubes(cwd);
|
|
2331
|
-
if (references.length === 0) {
|
|
2332
|
-
console.log(pc.dim("No datasource references found in cubes."));
|
|
2333
|
-
return;
|
|
2334
|
-
}
|
|
2335
|
-
console.log(pc.bold("Testing connections..."));
|
|
2336
|
-
console.log();
|
|
2337
|
-
const localDatasources = loadLocalDatasources(cwd);
|
|
2338
|
-
let warnings = 0;
|
|
2339
|
-
for (const ref of references) {
|
|
2340
|
-
const ds = localDatasources.find((d) => d.name === ref.name);
|
|
2341
|
-
if (!ds) {
|
|
2342
|
-
console.log(pc.yellow(`⚠ ${ref.name}: not found in .bon/datasources.yaml`));
|
|
2343
|
-
console.log(pc.dim(` Used by: ${ref.cubes.join(", ")}`));
|
|
2344
|
-
warnings++;
|
|
2345
|
-
continue;
|
|
2346
|
-
}
|
|
2347
|
-
const { resolved, missing } = resolveEnvVarsInCredentials(ds.credentials);
|
|
2348
|
-
if (missing.length > 0) {
|
|
2349
|
-
console.log(pc.yellow(`⚠ ${ref.name}: missing env vars: ${missing.join(", ")}`));
|
|
2350
|
-
console.log(pc.dim(` Used by: ${ref.cubes.join(", ")}`));
|
|
2351
|
-
warnings++;
|
|
2352
|
-
continue;
|
|
2353
|
-
}
|
|
2354
|
-
const result = await testConnection({
|
|
2355
|
-
type: ds.type,
|
|
2356
|
-
config: ds.config,
|
|
2357
|
-
credentials: resolved
|
|
2358
|
-
});
|
|
2359
|
-
if (result.success) {
|
|
2360
|
-
const latency = result.latencyMs ? pc.dim(` (${result.latencyMs}ms)`) : "";
|
|
2361
|
-
console.log(pc.green(`✓ ${ref.name}${latency}`));
|
|
2362
|
-
} else {
|
|
2363
|
-
console.log(pc.yellow(`⚠ ${ref.name}: ${result.error || result.message}`));
|
|
2364
|
-
console.log(pc.dim(` Used by: ${ref.cubes.join(", ")}`));
|
|
2365
|
-
warnings++;
|
|
2366
|
-
}
|
|
2367
|
-
}
|
|
2368
|
-
if (warnings > 0) {
|
|
2369
|
-
console.log();
|
|
2370
|
-
console.log(pc.yellow(`${warnings} connection warning(s)`));
|
|
2371
|
-
console.log(pc.dim("Connection issues won't block file validation, but will fail at deploy."));
|
|
2372
|
-
}
|
|
2373
1821
|
}
|
|
2374
1822
|
|
|
2375
1823
|
//#endregion
|
|
@@ -2401,7 +1849,7 @@ async function deployCommand(options = {}) {
|
|
|
2401
1849
|
process.exit(1);
|
|
2402
1850
|
}
|
|
2403
1851
|
console.log(pc.dim("Validating cubes and views..."));
|
|
2404
|
-
const { validate } = await import("./validate-
|
|
1852
|
+
const { validate } = await import("./validate-DEh1XQnH.mjs");
|
|
2405
1853
|
const result = await validate(cwd);
|
|
2406
1854
|
if (!result.valid) {
|
|
2407
1855
|
console.log(pc.red("Validation failed:\n"));
|
|
@@ -2470,51 +1918,29 @@ async function deployCommand(options = {}) {
|
|
|
2470
1918
|
* Returns true if any connection failed (strict mode)
|
|
2471
1919
|
*/
|
|
2472
1920
|
async function testAndSyncDatasources(cwd, options = {}) {
|
|
2473
|
-
const { extractDatasourcesFromCubes } = await import("./cubes-
|
|
2474
|
-
const { loadLocalDatasources
|
|
2475
|
-
const { testConnection } = await Promise.resolve().then(() => connection_exports);
|
|
1921
|
+
const { extractDatasourcesFromCubes } = await import("./cubes-Bf0IPYd7.mjs");
|
|
1922
|
+
const { loadLocalDatasources } = await Promise.resolve().then(() => local_exports);
|
|
2476
1923
|
const { pushDatasource } = await Promise.resolve().then(() => push_exports);
|
|
2477
1924
|
const references = extractDatasourcesFromCubes(cwd);
|
|
2478
1925
|
if (references.length === 0) return false;
|
|
2479
1926
|
console.log();
|
|
2480
|
-
console.log(pc.dim("
|
|
1927
|
+
console.log(pc.dim("Checking datasources..."));
|
|
2481
1928
|
const localDatasources = loadLocalDatasources(cwd);
|
|
2482
1929
|
let failed = false;
|
|
2483
|
-
const
|
|
1930
|
+
const foundDatasources = [];
|
|
2484
1931
|
for (const ref of references) {
|
|
2485
|
-
|
|
2486
|
-
if (!ds) {
|
|
1932
|
+
if (!localDatasources.find((d) => d.name === ref.name)) {
|
|
2487
1933
|
console.log(pc.red(`✗ ${ref.name}: not found in .bon/datasources.yaml`));
|
|
2488
1934
|
console.log(pc.dim(` Used by: ${ref.cubes.join(", ")}`));
|
|
2489
1935
|
console.log(pc.dim(` Run: bon datasource add --from-dbt`));
|
|
2490
1936
|
failed = true;
|
|
2491
1937
|
continue;
|
|
2492
1938
|
}
|
|
2493
|
-
|
|
2494
|
-
if (missing.length > 0) {
|
|
2495
|
-
console.log(pc.red(`✗ ${ref.name}: missing env vars: ${missing.join(", ")}`));
|
|
2496
|
-
console.log(pc.dim(` Used by: ${ref.cubes.join(", ")}`));
|
|
2497
|
-
failed = true;
|
|
2498
|
-
continue;
|
|
2499
|
-
}
|
|
2500
|
-
const result = await testConnection({
|
|
2501
|
-
type: ds.type,
|
|
2502
|
-
config: ds.config,
|
|
2503
|
-
credentials: resolved
|
|
2504
|
-
});
|
|
2505
|
-
if (result.success) {
|
|
2506
|
-
const latency = result.latencyMs ? pc.dim(` (${result.latencyMs}ms)`) : "";
|
|
2507
|
-
console.log(pc.green(`✓ ${ref.name}${latency}`));
|
|
2508
|
-
validatedDatasources.push(ref.name);
|
|
2509
|
-
} else {
|
|
2510
|
-
console.log(pc.red(`✗ ${ref.name}: ${result.error || result.message}`));
|
|
2511
|
-
console.log(pc.dim(` Used by: ${ref.cubes.join(", ")}`));
|
|
2512
|
-
failed = true;
|
|
2513
|
-
}
|
|
1939
|
+
foundDatasources.push(ref.name);
|
|
2514
1940
|
}
|
|
2515
|
-
console.log();
|
|
2516
1941
|
if (failed) {
|
|
2517
|
-
console.log(
|
|
1942
|
+
console.log();
|
|
1943
|
+
console.log(pc.red("Missing datasources. Fix issues before deploying."));
|
|
2518
1944
|
return true;
|
|
2519
1945
|
}
|
|
2520
1946
|
console.log(pc.dim("Checking remote datasources..."));
|
|
@@ -2526,22 +1952,17 @@ async function testAndSyncDatasources(cwd, options = {}) {
|
|
|
2526
1952
|
return true;
|
|
2527
1953
|
}
|
|
2528
1954
|
const remoteNames = new Set(remoteDatasources.map((ds) => ds.name));
|
|
2529
|
-
const missingRemote =
|
|
2530
|
-
if (missingRemote.length
|
|
2531
|
-
console.log(pc.green("✓ All datasources exist on remote"));
|
|
1955
|
+
const missingRemote = foundDatasources.filter((name) => !remoteNames.has(name));
|
|
1956
|
+
if (missingRemote.length > 0) {
|
|
2532
1957
|
console.log();
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
return true;
|
|
2542
|
-
}
|
|
2543
|
-
if (options.pushDatasources) {
|
|
2544
|
-
for (const name of missingRemote) {
|
|
1958
|
+
console.log(pc.yellow(`⚠ Missing remote datasource${missingRemote.length > 1 ? "s" : ""}: ${missingRemote.join(", ")}`));
|
|
1959
|
+
console.log();
|
|
1960
|
+
if (options.ci) {
|
|
1961
|
+
console.log(pc.red("Deploy aborted (--ci mode)."));
|
|
1962
|
+
console.log(pc.dim(`Run: bon datasource push <name>`));
|
|
1963
|
+
return true;
|
|
1964
|
+
}
|
|
1965
|
+
if (options.pushDatasources) for (const name of missingRemote) {
|
|
2545
1966
|
console.log(pc.dim(`Pushing "${name}"...`));
|
|
2546
1967
|
if (await pushDatasource(name, { silent: true })) console.log(pc.green(`✓ Pushed "${name}"`));
|
|
2547
1968
|
else {
|
|
@@ -2549,25 +1970,46 @@ async function testAndSyncDatasources(cwd, options = {}) {
|
|
|
2549
1970
|
return true;
|
|
2550
1971
|
}
|
|
2551
1972
|
}
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
1973
|
+
else {
|
|
1974
|
+
if (!await confirm({
|
|
1975
|
+
message: `Push ${missingRemote.length > 1 ? "these datasources" : `"${missingRemote[0]}"`} to Bonnard? (credentials will be encrypted)`,
|
|
1976
|
+
default: true
|
|
1977
|
+
})) {
|
|
1978
|
+
console.log(pc.dim("Deploy aborted."));
|
|
1979
|
+
return true;
|
|
1980
|
+
}
|
|
1981
|
+
console.log();
|
|
1982
|
+
for (const name of missingRemote) {
|
|
1983
|
+
console.log(pc.dim(`Pushing "${name}"...`));
|
|
1984
|
+
if (await pushDatasource(name, { silent: true })) console.log(pc.green(`✓ Pushed "${name}"`));
|
|
1985
|
+
else {
|
|
1986
|
+
console.log(pc.red(`✗ Failed to push "${name}"`));
|
|
1987
|
+
return true;
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
2561
1991
|
}
|
|
2562
1992
|
console.log();
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
1993
|
+
console.log(pc.dim("Testing datasource connections..."));
|
|
1994
|
+
for (const name of foundDatasources) try {
|
|
1995
|
+
const result = await post("/api/datasources/test", { name });
|
|
1996
|
+
if (result.success) {
|
|
1997
|
+
const latency = result.details?.latencyMs ? pc.dim(` (${result.details.latencyMs}ms)`) : "";
|
|
1998
|
+
console.log(pc.green(`✓ ${name}${latency}`));
|
|
1999
|
+
} else {
|
|
2000
|
+
console.log(pc.red(`✗ ${name}: ${result.message}`));
|
|
2001
|
+
failed = true;
|
|
2569
2002
|
}
|
|
2003
|
+
} catch (err) {
|
|
2004
|
+
console.log(pc.red(`✗ ${name}: ${err.message}`));
|
|
2005
|
+
failed = true;
|
|
2006
|
+
}
|
|
2007
|
+
console.log();
|
|
2008
|
+
if (failed) {
|
|
2009
|
+
console.log(pc.red("Connection tests failed. Fix datasource issues before deploying."));
|
|
2010
|
+
return true;
|
|
2570
2011
|
}
|
|
2012
|
+
console.log(pc.green("✓ All datasources connected"));
|
|
2571
2013
|
console.log();
|
|
2572
2014
|
return false;
|
|
2573
2015
|
}
|
|
@@ -3093,12 +2535,11 @@ program.command("whoami").description("Show current login status").option("--ver
|
|
|
3093
2535
|
const datasource = program.command("datasource").description("Manage warehouse data source connections");
|
|
3094
2536
|
datasource.command("add").description("Add a data source to .bon/datasources.yaml. Use --name and --type together for non-interactive mode").option("--demo", "Add a read-only demo datasource (Contoso retail dataset) for testing").option("--from-dbt [profile]", "Import from dbt profiles.yml (optionally specify profile/target)").option("--target <target>", "Target name when using --from-dbt").option("--all", "Import all connections from dbt profiles").option("--default-targets", "Import only default targets from dbt profiles (non-interactive)").option("--name <name>", "Datasource name (required for non-interactive mode)").option("--type <type>", "Warehouse type: snowflake, postgres, bigquery, databricks (required for non-interactive mode)").option("--account <account>", "Snowflake account identifier").option("--database <database>", "Database name").option("--schema <schema>", "Schema name").option("--warehouse <warehouse>", "Warehouse name (Snowflake)").option("--role <role>", "Role (Snowflake)").option("--host <host>", "Host (Postgres)").option("--port <port>", "Port (Postgres, default: 5432)").option("--project-id <projectId>", "GCP Project ID (BigQuery)").option("--dataset <dataset>", "Dataset name (BigQuery)").option("--location <location>", "Location (BigQuery)").option("--hostname <hostname>", "Server hostname (Databricks)").option("--http-path <httpPath>", "HTTP path (Databricks)").option("--catalog <catalog>", "Catalog name (Databricks)").option("--user <user>", "Username").option("--password <password>", "Password (use --password-env for env var reference)").option("--token <token>", "Access token (use --token-env for env var reference)").option("--service-account-json <json>", "Service account JSON (BigQuery)").option("--keyfile <path>", "Path to service account key file (BigQuery)").option("--password-env <varName>", "Env var name for password, stores as {{ env_var('NAME') }}").option("--token-env <varName>", "Env var name for token, stores as {{ env_var('NAME') }}").option("--force", "Overwrite existing datasource without prompting").action(datasourceAddCommand);
|
|
3095
2537
|
datasource.command("list").description("List data sources (shows both local and remote by default)").option("--local", "Show only local data sources from .bon/datasources.yaml").option("--remote", "Show only remote data sources from Bonnard server (requires login)").action(datasourceListCommand);
|
|
3096
|
-
datasource.command("test").description("Test data source connectivity
|
|
2538
|
+
datasource.command("test").description("Test data source connectivity via Bonnard API (requires login)").argument("<name>", "Data source name from .bon/datasources.yaml").action(datasourceTestCommand);
|
|
3097
2539
|
datasource.command("remove").description("Remove a data source from .bon/datasources.yaml (local by default)").argument("<name>", "Data source name").option("--remote", "Remove from Bonnard server instead of local (requires login)").action(datasourceRemoveCommand);
|
|
3098
2540
|
datasource.command("push").description("Push a local data source to Bonnard server (requires login)").argument("<name>", "Data source name from .bon/datasources.yaml").option("--force", "Overwrite if already exists on remote").action(datasourcePushCommand);
|
|
3099
|
-
program.command("
|
|
3100
|
-
program.command("
|
|
3101
|
-
program.command("deploy").description("Deploy cubes and views to Bonnard. Requires login, validates, tests connections (fails on error)").option("--ci", "Non-interactive mode (fail if missing datasources)").option("--push-datasources", "Auto-push missing datasources without prompting").requiredOption("-m, --message <text>", "Deploy message describing your changes").action(deployCommand);
|
|
2541
|
+
program.command("validate").description("Validate YAML syntax in bonnard/cubes/ and bonnard/views/").action(validateCommand);
|
|
2542
|
+
program.command("deploy").description("Deploy cubes and views to Bonnard. Requires login, validates, syncs datasources").option("--ci", "Non-interactive mode (fail if missing datasources)").option("--push-datasources", "Auto-push missing datasources without prompting").requiredOption("-m, --message <text>", "Deploy message describing your changes").action(deployCommand);
|
|
3102
2543
|
program.command("deployments").description("List deployment history").option("--all", "Show all deployments (default: last 10)").option("--format <format>", "Output format: table or json", "table").action(deploymentsCommand);
|
|
3103
2544
|
program.command("diff").description("Show changes in a deployment").argument("<id>", "Deployment ID").option("--format <format>", "Output format: table or json", "table").option("--breaking", "Show only breaking changes").action(diffCommand);
|
|
3104
2545
|
program.command("annotate").description("Annotate deployment changes with reasoning").argument("<id>", "Deployment ID").option("--data <json>", "Annotations JSON").action(annotateCommand);
|