@squadbase/vite-server 0.0.1-build-5 → 0.0.1-build-7
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/cli/index.d.ts +1 -0
- package/dist/cli/index.js +644 -0
- package/dist/index.js +4 -4
- package/dist/main.js +4 -4
- package/dist/vite-plugin.js +3 -3
- package/package.json +4 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/cli/interactive.ts
|
|
13
|
+
var interactive_exports = {};
|
|
14
|
+
__export(interactive_exports, {
|
|
15
|
+
confirmRunAll: () => confirmRunAll,
|
|
16
|
+
inputParameters: () => inputParameters,
|
|
17
|
+
selectDataSource: () => selectDataSource
|
|
18
|
+
});
|
|
19
|
+
async function getPrompts() {
|
|
20
|
+
try {
|
|
21
|
+
const mod = await import("@clack/prompts");
|
|
22
|
+
return mod;
|
|
23
|
+
} catch {
|
|
24
|
+
throw new Error(
|
|
25
|
+
"@clack/prompts is not installed. Run: npm install @clack/prompts --save-dev"
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async function selectDataSource(slugs) {
|
|
30
|
+
const { select, isCancel } = await getPrompts();
|
|
31
|
+
const result = await select({
|
|
32
|
+
message: "Select a data source to test:",
|
|
33
|
+
options: slugs.map((s) => ({ value: s, label: s }))
|
|
34
|
+
});
|
|
35
|
+
if (isCancel(result)) return null;
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
async function inputParameters(params) {
|
|
39
|
+
if (params.length === 0) return {};
|
|
40
|
+
const { text, isCancel } = await getPrompts();
|
|
41
|
+
const result = {};
|
|
42
|
+
for (const p of params) {
|
|
43
|
+
const defaultHint = p.default !== void 0 ? ` (default: ${p.default})` : "";
|
|
44
|
+
const requiredHint = p.required ? " *required*" : "";
|
|
45
|
+
const value = await text({
|
|
46
|
+
message: `${p.name}${requiredHint} [${p.type}]${defaultHint}: ${p.description}`,
|
|
47
|
+
placeholder: p.default !== void 0 ? String(p.default) : "",
|
|
48
|
+
validate: (v) => {
|
|
49
|
+
if (p.required && !v && p.default === void 0) {
|
|
50
|
+
return `${p.name} is required`;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
if (isCancel(value)) {
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
const strValue = value;
|
|
58
|
+
if (!strValue && p.default !== void 0) {
|
|
59
|
+
result[p.name] = p.default;
|
|
60
|
+
} else if (strValue) {
|
|
61
|
+
if (p.type === "number") result[p.name] = Number(strValue);
|
|
62
|
+
else if (p.type === "boolean") result[p.name] = strValue === "true";
|
|
63
|
+
else result[p.name] = strValue;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
async function confirmRunAll() {
|
|
69
|
+
const { confirm, isCancel } = await getPrompts();
|
|
70
|
+
const result = await confirm({
|
|
71
|
+
message: "Run all data sources?"
|
|
72
|
+
});
|
|
73
|
+
if (isCancel(result)) return false;
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
var init_interactive = __esm({
|
|
77
|
+
"src/cli/interactive.ts"() {
|
|
78
|
+
"use strict";
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// src/cli/index.ts
|
|
83
|
+
import { parseArgs } from "util";
|
|
84
|
+
import path4 from "path";
|
|
85
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
86
|
+
|
|
87
|
+
// src/connector-client/registry.ts
|
|
88
|
+
import { readFileSync, watch as fsWatch } from "fs";
|
|
89
|
+
import path from "path";
|
|
90
|
+
|
|
91
|
+
// src/connector-client/postgresql.ts
|
|
92
|
+
import pg from "pg";
|
|
93
|
+
var { Pool } = pg;
|
|
94
|
+
function createPostgreSQLClient(connectionString) {
|
|
95
|
+
const pool = new Pool({ connectionString, ssl: { rejectUnauthorized: false } });
|
|
96
|
+
return {
|
|
97
|
+
async query(sql, params) {
|
|
98
|
+
const result = await pool.query(sql, params);
|
|
99
|
+
return { rows: result.rows };
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/connector-client/env.ts
|
|
105
|
+
function resolveEnvVar(entry, key, connectionId) {
|
|
106
|
+
const envVarName = entry.envVars[key];
|
|
107
|
+
if (!envVarName) {
|
|
108
|
+
throw new Error(`Connection "${connectionId}" is missing envVars mapping for key "${key}"`);
|
|
109
|
+
}
|
|
110
|
+
const value = process.env[envVarName];
|
|
111
|
+
if (!value) {
|
|
112
|
+
throw new Error(`Environment variable "${envVarName}" (for connection "${connectionId}", key "${key}") is not set`);
|
|
113
|
+
}
|
|
114
|
+
return value;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/connector-client/bigquery.ts
|
|
118
|
+
function createBigQueryClient(entry, connectionId) {
|
|
119
|
+
const projectId = resolveEnvVar(entry, "project-id", connectionId);
|
|
120
|
+
const serviceAccountJsonBase64 = resolveEnvVar(entry, "service-account-key-json-base64", connectionId);
|
|
121
|
+
const serviceAccountJson = Buffer.from(serviceAccountJsonBase64, "base64").toString("utf-8");
|
|
122
|
+
let gcpCredentials;
|
|
123
|
+
try {
|
|
124
|
+
gcpCredentials = JSON.parse(serviceAccountJson);
|
|
125
|
+
} catch {
|
|
126
|
+
throw new Error(
|
|
127
|
+
`BigQuery service account JSON (decoded from base64) is not valid JSON for connection "${connectionId}"`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
async query(sql) {
|
|
132
|
+
const { BigQuery } = await import("@google-cloud/bigquery");
|
|
133
|
+
const bq = new BigQuery({ projectId, credentials: gcpCredentials });
|
|
134
|
+
const [job] = await bq.createQueryJob({ query: sql });
|
|
135
|
+
const [allRows] = await job.getQueryResults({ timeoutMs: 3e4 });
|
|
136
|
+
return { rows: allRows };
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/connector-client/snowflake.ts
|
|
142
|
+
function createSnowflakeClient(entry, connectionId) {
|
|
143
|
+
const accountIdentifier = resolveEnvVar(entry, "account", connectionId);
|
|
144
|
+
const user = resolveEnvVar(entry, "user", connectionId);
|
|
145
|
+
const role = resolveEnvVar(entry, "role", connectionId);
|
|
146
|
+
const warehouse = resolveEnvVar(entry, "warehouse", connectionId);
|
|
147
|
+
const privateKeyBase64 = resolveEnvVar(entry, "private-key-base64", connectionId);
|
|
148
|
+
const privateKey = Buffer.from(privateKeyBase64, "base64").toString("utf-8");
|
|
149
|
+
return {
|
|
150
|
+
async query(sql) {
|
|
151
|
+
const snowflake = (await import("snowflake-sdk")).default;
|
|
152
|
+
snowflake.configure({ logLevel: "ERROR" });
|
|
153
|
+
const connection = snowflake.createConnection({
|
|
154
|
+
account: accountIdentifier,
|
|
155
|
+
username: user,
|
|
156
|
+
role,
|
|
157
|
+
warehouse,
|
|
158
|
+
authenticator: "SNOWFLAKE_JWT",
|
|
159
|
+
privateKey
|
|
160
|
+
});
|
|
161
|
+
await new Promise((resolve, reject) => {
|
|
162
|
+
connection.connect((err) => {
|
|
163
|
+
if (err) reject(new Error(`Snowflake connect failed: ${err.message}`));
|
|
164
|
+
else resolve();
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
const rows = await new Promise((resolve, reject) => {
|
|
168
|
+
connection.execute({
|
|
169
|
+
sqlText: sql,
|
|
170
|
+
complete: (err, _stmt, rows2) => {
|
|
171
|
+
if (err) reject(new Error(`Snowflake query failed: ${err.message}`));
|
|
172
|
+
else resolve(rows2 ?? []);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
connection.destroy((err) => {
|
|
177
|
+
if (err) console.warn(`[connector-client] Snowflake destroy error: ${err.message}`);
|
|
178
|
+
});
|
|
179
|
+
return { rows };
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// src/connector-client/registry.ts
|
|
185
|
+
function createConnectorRegistry() {
|
|
186
|
+
let connectionsCache = null;
|
|
187
|
+
const clientCache = /* @__PURE__ */ new Map();
|
|
188
|
+
function getConnectionsFilePath() {
|
|
189
|
+
return process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), ".squadbase/connections.json");
|
|
190
|
+
}
|
|
191
|
+
function loadConnections() {
|
|
192
|
+
if (connectionsCache !== null) return connectionsCache;
|
|
193
|
+
const filePath = getConnectionsFilePath();
|
|
194
|
+
try {
|
|
195
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
196
|
+
connectionsCache = JSON.parse(raw);
|
|
197
|
+
} catch {
|
|
198
|
+
connectionsCache = {};
|
|
199
|
+
}
|
|
200
|
+
return connectionsCache;
|
|
201
|
+
}
|
|
202
|
+
async function getClient2(connectionId) {
|
|
203
|
+
const connections = loadConnections();
|
|
204
|
+
const entry = connections[connectionId];
|
|
205
|
+
if (!entry) {
|
|
206
|
+
throw new Error(`connection '${connectionId}' not found in .squadbase/connections.json`);
|
|
207
|
+
}
|
|
208
|
+
const connectorSlug = entry.connector.slug;
|
|
209
|
+
const cached = clientCache.get(connectionId);
|
|
210
|
+
if (cached) return { client: cached, connectorSlug };
|
|
211
|
+
if (connectorSlug === "snowflake") {
|
|
212
|
+
return { client: createSnowflakeClient(entry, connectionId), connectorSlug };
|
|
213
|
+
}
|
|
214
|
+
if (connectorSlug === "bigquery") {
|
|
215
|
+
return { client: createBigQueryClient(entry, connectionId), connectorSlug };
|
|
216
|
+
}
|
|
217
|
+
if (connectorSlug === "postgresql" || connectorSlug === "squadbase-db") {
|
|
218
|
+
const urlEnvName = entry.envVars["connection-url"];
|
|
219
|
+
if (!urlEnvName) {
|
|
220
|
+
throw new Error(`'connection-url' is not defined in envVars for connection '${connectionId}'`);
|
|
221
|
+
}
|
|
222
|
+
const connectionUrl = process.env[urlEnvName];
|
|
223
|
+
if (!connectionUrl) {
|
|
224
|
+
throw new Error(
|
|
225
|
+
`environment variable '${urlEnvName}' (mapped from connection '${connectionId}') is not set`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
const client = createPostgreSQLClient(connectionUrl);
|
|
229
|
+
clientCache.set(connectionId, client);
|
|
230
|
+
return { client, connectorSlug };
|
|
231
|
+
}
|
|
232
|
+
throw new Error(
|
|
233
|
+
`connector type '${connectorSlug}' is not supported. Supported: "snowflake", "bigquery", "postgresql", "squadbase-db"`
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
function reloadEnvFile2(envPath) {
|
|
237
|
+
try {
|
|
238
|
+
const raw = readFileSync(envPath, "utf-8");
|
|
239
|
+
for (const line of raw.split("\n")) {
|
|
240
|
+
const trimmed = line.trim();
|
|
241
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
242
|
+
const eqIdx = trimmed.indexOf("=");
|
|
243
|
+
if (eqIdx === -1) continue;
|
|
244
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
245
|
+
const value = trimmed.slice(eqIdx + 1).trim();
|
|
246
|
+
if (key) process.env[key] = value;
|
|
247
|
+
}
|
|
248
|
+
console.log("[connector-client] .env reloaded");
|
|
249
|
+
} catch {
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function watchConnectionsFile2() {
|
|
253
|
+
const filePath = getConnectionsFilePath();
|
|
254
|
+
const envPath = path.join(process.cwd(), ".env");
|
|
255
|
+
try {
|
|
256
|
+
fsWatch(filePath, { persistent: false }, () => {
|
|
257
|
+
console.log("[connector-client] connections.json changed, clearing cache");
|
|
258
|
+
connectionsCache = null;
|
|
259
|
+
clientCache.clear();
|
|
260
|
+
setImmediate(() => reloadEnvFile2(envPath));
|
|
261
|
+
});
|
|
262
|
+
} catch {
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return { getClient: getClient2, reloadEnvFile: reloadEnvFile2, watchConnectionsFile: watchConnectionsFile2 };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// src/connector-client/index.ts
|
|
269
|
+
var { getClient, reloadEnvFile, watchConnectionsFile } = createConnectorRegistry();
|
|
270
|
+
|
|
271
|
+
// src/cli/env-loader.ts
|
|
272
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
273
|
+
function loadEnvFile(envPath) {
|
|
274
|
+
reloadEnvFile(envPath);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// src/cli/runner.ts
|
|
278
|
+
import { pathToFileURL } from "url";
|
|
279
|
+
import { readFile as readFile2, readdir as readdir2 } from "fs/promises";
|
|
280
|
+
import path3 from "path";
|
|
281
|
+
|
|
282
|
+
// src/registry.ts
|
|
283
|
+
import { readdir, readFile, mkdir } from "fs/promises";
|
|
284
|
+
import { watch as fsWatch2 } from "fs";
|
|
285
|
+
import path2 from "path";
|
|
286
|
+
function buildQuery(queryTemplate, parameterMeta, runtimeParams) {
|
|
287
|
+
const defaults = new Map(
|
|
288
|
+
parameterMeta.map((p) => [p.name, p.default ?? null])
|
|
289
|
+
);
|
|
290
|
+
const placeholderToIndex = /* @__PURE__ */ new Map();
|
|
291
|
+
const values = [];
|
|
292
|
+
const text = queryTemplate.replace(
|
|
293
|
+
/\{\{(\w+)\}\}/g,
|
|
294
|
+
(_match, name) => {
|
|
295
|
+
if (!placeholderToIndex.has(name)) {
|
|
296
|
+
const value = Object.prototype.hasOwnProperty.call(runtimeParams, name) ? runtimeParams[name] : defaults.get(name) ?? null;
|
|
297
|
+
values.push(value);
|
|
298
|
+
placeholderToIndex.set(name, values.length);
|
|
299
|
+
}
|
|
300
|
+
return `$${placeholderToIndex.get(name)}`;
|
|
301
|
+
}
|
|
302
|
+
);
|
|
303
|
+
return { text, values };
|
|
304
|
+
}
|
|
305
|
+
var defaultDataSourceDir = path2.join(process.cwd(), "data-source");
|
|
306
|
+
|
|
307
|
+
// src/cli/runner.ts
|
|
308
|
+
function createStubContext(params) {
|
|
309
|
+
const stub = {
|
|
310
|
+
req: {
|
|
311
|
+
json: () => Promise.resolve(params),
|
|
312
|
+
query: (name) => {
|
|
313
|
+
if (name === void 0) {
|
|
314
|
+
return Object.fromEntries(
|
|
315
|
+
Object.entries(params).map(([k, v2]) => [k, String(v2)])
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
const v = params[name];
|
|
319
|
+
return v !== void 0 ? String(v) : "";
|
|
320
|
+
},
|
|
321
|
+
param: (_name) => void 0,
|
|
322
|
+
header: (_name) => void 0,
|
|
323
|
+
raw: new Request("http://localhost/cli")
|
|
324
|
+
},
|
|
325
|
+
json: (data) => data,
|
|
326
|
+
text: (data) => data,
|
|
327
|
+
body: (data) => data,
|
|
328
|
+
env: {},
|
|
329
|
+
var: {},
|
|
330
|
+
get: (_key) => void 0,
|
|
331
|
+
set: () => {
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
return stub;
|
|
335
|
+
}
|
|
336
|
+
async function runSqlDataSource(slug, def, params, limit) {
|
|
337
|
+
const start = Date.now();
|
|
338
|
+
try {
|
|
339
|
+
const { client, connectorSlug } = await getClient(def.connectionId);
|
|
340
|
+
const isExternal = connectorSlug === "snowflake" || connectorSlug === "bigquery";
|
|
341
|
+
let queryText;
|
|
342
|
+
let queryValues;
|
|
343
|
+
if (isExternal) {
|
|
344
|
+
const defaults = new Map(
|
|
345
|
+
(def.parameters ?? []).map((p) => [p.name, p.default ?? null])
|
|
346
|
+
);
|
|
347
|
+
queryText = def.query.replace(/\{\{(\w+)\}\}/g, (_match, name) => {
|
|
348
|
+
const value = Object.prototype.hasOwnProperty.call(params, name) ? params[name] : defaults.get(name) ?? "";
|
|
349
|
+
if (typeof value === "string") return `'${value.replace(/'/g, "''")}'`;
|
|
350
|
+
if (value === null || value === void 0) return "NULL";
|
|
351
|
+
return String(value);
|
|
352
|
+
});
|
|
353
|
+
queryValues = [];
|
|
354
|
+
} else {
|
|
355
|
+
const built = buildQuery(def.query, def.parameters ?? [], params);
|
|
356
|
+
queryText = built.text;
|
|
357
|
+
queryValues = built.values;
|
|
358
|
+
}
|
|
359
|
+
const result = await client.query(queryText, queryValues);
|
|
360
|
+
const rows = result.rows.slice(0, limit);
|
|
361
|
+
return {
|
|
362
|
+
slug,
|
|
363
|
+
rows,
|
|
364
|
+
rowCount: result.rows.length,
|
|
365
|
+
durationMs: Date.now() - start,
|
|
366
|
+
query: queryText,
|
|
367
|
+
queryValues
|
|
368
|
+
};
|
|
369
|
+
} catch (error) {
|
|
370
|
+
return {
|
|
371
|
+
slug,
|
|
372
|
+
rows: [],
|
|
373
|
+
rowCount: 0,
|
|
374
|
+
durationMs: Date.now() - start,
|
|
375
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
async function runTypescriptDataSource(slug, handlerPath, params) {
|
|
380
|
+
const start = Date.now();
|
|
381
|
+
try {
|
|
382
|
+
const mod = await import(pathToFileURL(handlerPath).href);
|
|
383
|
+
const handler = mod.default;
|
|
384
|
+
if (typeof handler !== "function") {
|
|
385
|
+
throw new Error(`Handler must export a default function: ${handlerPath}`);
|
|
386
|
+
}
|
|
387
|
+
const ctx = createStubContext(params);
|
|
388
|
+
const raw = await handler(ctx);
|
|
389
|
+
let rows;
|
|
390
|
+
if (Array.isArray(raw)) {
|
|
391
|
+
rows = raw;
|
|
392
|
+
} else if (raw !== null && typeof raw === "object") {
|
|
393
|
+
rows = [raw];
|
|
394
|
+
} else {
|
|
395
|
+
rows = [{ result: raw }];
|
|
396
|
+
}
|
|
397
|
+
return {
|
|
398
|
+
slug,
|
|
399
|
+
rows,
|
|
400
|
+
rowCount: rows.length,
|
|
401
|
+
durationMs: Date.now() - start
|
|
402
|
+
};
|
|
403
|
+
} catch (error) {
|
|
404
|
+
return {
|
|
405
|
+
slug,
|
|
406
|
+
rows: [],
|
|
407
|
+
rowCount: 0,
|
|
408
|
+
durationMs: Date.now() - start,
|
|
409
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
async function runDataSource(slug, dirPath, params, limit) {
|
|
414
|
+
const jsonPath = path3.join(dirPath, `${slug}.json`);
|
|
415
|
+
let def;
|
|
416
|
+
try {
|
|
417
|
+
const raw = await readFile2(jsonPath, "utf-8");
|
|
418
|
+
def = JSON.parse(raw);
|
|
419
|
+
} catch {
|
|
420
|
+
return {
|
|
421
|
+
slug,
|
|
422
|
+
rows: [],
|
|
423
|
+
rowCount: 0,
|
|
424
|
+
durationMs: 0,
|
|
425
|
+
error: new Error(`Data source not found: ${jsonPath}`)
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
if (def.type === "typescript") {
|
|
429
|
+
const absolutePath = path3.resolve(dirPath, def.handlerPath);
|
|
430
|
+
return runTypescriptDataSource(slug, absolutePath, params);
|
|
431
|
+
}
|
|
432
|
+
return runSqlDataSource(slug, def, params, limit);
|
|
433
|
+
}
|
|
434
|
+
async function listSlugs(dirPath) {
|
|
435
|
+
try {
|
|
436
|
+
const files = await readdir2(dirPath);
|
|
437
|
+
return files.filter((f) => f.endsWith(".json")).map((f) => f.replace(/\.json$/, ""));
|
|
438
|
+
} catch {
|
|
439
|
+
return [];
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
async function runAll(dirPath, params, limit) {
|
|
443
|
+
const slugs = await listSlugs(dirPath);
|
|
444
|
+
return Promise.all(slugs.map((slug) => runDataSource(slug, dirPath, params, limit)));
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// src/cli/display.ts
|
|
448
|
+
var RESET = "\x1B[0m";
|
|
449
|
+
var BOLD = "\x1B[1m";
|
|
450
|
+
var RED = "\x1B[31m";
|
|
451
|
+
var GREEN = "\x1B[32m";
|
|
452
|
+
var YELLOW = "\x1B[33m";
|
|
453
|
+
var CYAN = "\x1B[36m";
|
|
454
|
+
var DIM = "\x1B[2m";
|
|
455
|
+
function truncate(value, maxLen) {
|
|
456
|
+
return value.length > maxLen ? value.slice(0, maxLen - 1) + "\u2026" : value;
|
|
457
|
+
}
|
|
458
|
+
function formatValue(value) {
|
|
459
|
+
if (value === null || value === void 0) return DIM + "null" + RESET;
|
|
460
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
461
|
+
return String(value);
|
|
462
|
+
}
|
|
463
|
+
function displayTable(rows, limit) {
|
|
464
|
+
if (rows.length === 0) {
|
|
465
|
+
console.log(DIM + " (no rows)" + RESET);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const display = rows.slice(0, limit);
|
|
469
|
+
const columns = Object.keys(display[0] ?? {});
|
|
470
|
+
const colWidths = columns.map((col) => {
|
|
471
|
+
const maxData = Math.max(...display.map((r) => formatValue(r[col]).replace(/\x1b\[[0-9;]*m/g, "").length));
|
|
472
|
+
return Math.min(40, Math.max(col.length, maxData));
|
|
473
|
+
});
|
|
474
|
+
const header = columns.map((c, i) => BOLD + CYAN + c.padEnd(colWidths[i] ?? 0) + RESET).join(" ");
|
|
475
|
+
const divider = colWidths.map((w) => "-".repeat(w)).join("--");
|
|
476
|
+
console.log(" " + header);
|
|
477
|
+
console.log(" " + DIM + divider + RESET);
|
|
478
|
+
for (const row of display) {
|
|
479
|
+
const line = columns.map((c, i) => {
|
|
480
|
+
const raw = formatValue(row[c]);
|
|
481
|
+
const plain = raw.replace(/\x1b\[[0-9;]*m/g, "");
|
|
482
|
+
const padded = truncate(plain, colWidths[i] ?? 40).padEnd(colWidths[i] ?? 0);
|
|
483
|
+
return raw.startsWith(DIM) ? DIM + padded + RESET : padded;
|
|
484
|
+
}).join(" ");
|
|
485
|
+
console.log(" " + line);
|
|
486
|
+
}
|
|
487
|
+
if (rows.length > limit) {
|
|
488
|
+
console.log(DIM + ` \u2026 and ${rows.length - limit} more rows (use --limit to show more)` + RESET);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
function displaySummary(result) {
|
|
492
|
+
const status = result.error ? RED + "\u2717 FAIL" + RESET : GREEN + "\u2713 OK" + RESET;
|
|
493
|
+
const duration = DIM + `(${result.durationMs}ms)` + RESET;
|
|
494
|
+
console.log(`
|
|
495
|
+
${BOLD}[${result.slug}]${RESET} ${status} ${duration}`);
|
|
496
|
+
if (result.error) {
|
|
497
|
+
console.log(RED + ` Error: ${result.error.message}` + RESET);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
console.log(DIM + ` ${result.rowCount} row(s)` + RESET);
|
|
501
|
+
}
|
|
502
|
+
function displayDebug(result) {
|
|
503
|
+
if (result.query) {
|
|
504
|
+
console.log(YELLOW + " Query:" + RESET);
|
|
505
|
+
console.log(DIM + ` ${result.query.replace(/\n/g, "\n ")}` + RESET);
|
|
506
|
+
}
|
|
507
|
+
if (result.queryValues && result.queryValues.length > 0) {
|
|
508
|
+
console.log(YELLOW + " Params:" + RESET, result.queryValues);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
function displayJson(results) {
|
|
512
|
+
const output = results.map((r) => ({
|
|
513
|
+
slug: r.slug,
|
|
514
|
+
rowCount: r.rowCount,
|
|
515
|
+
durationMs: r.durationMs,
|
|
516
|
+
rows: r.rows,
|
|
517
|
+
error: r.error?.message
|
|
518
|
+
}));
|
|
519
|
+
console.log(JSON.stringify(output, null, 2));
|
|
520
|
+
}
|
|
521
|
+
function displayError(error) {
|
|
522
|
+
console.error(RED + "Error: " + error.message + RESET);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// src/cli/index.ts
|
|
526
|
+
var HELP = `
|
|
527
|
+
Usage: squadbase-ds-test [options]
|
|
528
|
+
|
|
529
|
+
Options:
|
|
530
|
+
--slug <slug> Run a specific data source
|
|
531
|
+
--all Run all data sources
|
|
532
|
+
--params k=v,... Comma-separated key=value parameters
|
|
533
|
+
--env <path> Path to .env file (default: ../../.env)
|
|
534
|
+
--dir <path> Data source directory (default: ./data-source)
|
|
535
|
+
--format table|json Output format (default: table)
|
|
536
|
+
--limit <n> Max rows to display (default: 50)
|
|
537
|
+
--debug Show SQL query and parameter values
|
|
538
|
+
--help Show this help
|
|
539
|
+
|
|
540
|
+
Examples:
|
|
541
|
+
npx tsx src/cli/index.ts --slug sales-summary
|
|
542
|
+
npx tsx src/cli/index.ts --slug sales-summary --params year=2024,limit=10
|
|
543
|
+
npx tsx src/cli/index.ts --all --format json
|
|
544
|
+
npx tsx src/cli/index.ts # interactive mode
|
|
545
|
+
`;
|
|
546
|
+
async function main() {
|
|
547
|
+
const { values } = parseArgs({
|
|
548
|
+
options: {
|
|
549
|
+
slug: { type: "string" },
|
|
550
|
+
all: { type: "boolean", default: false },
|
|
551
|
+
params: { type: "string" },
|
|
552
|
+
env: { type: "string" },
|
|
553
|
+
dir: { type: "string" },
|
|
554
|
+
format: { type: "string", default: "table" },
|
|
555
|
+
limit: { type: "string", default: "50" },
|
|
556
|
+
debug: { type: "boolean", default: false },
|
|
557
|
+
help: { type: "boolean", default: false }
|
|
558
|
+
},
|
|
559
|
+
allowPositionals: false
|
|
560
|
+
});
|
|
561
|
+
if (values.help) {
|
|
562
|
+
console.log(HELP);
|
|
563
|
+
process.exit(0);
|
|
564
|
+
}
|
|
565
|
+
const cwd = process.cwd();
|
|
566
|
+
const dirPath = values.dir ? path4.resolve(cwd, values.dir) : path4.join(cwd, "data-source");
|
|
567
|
+
const envPath = values.env ? path4.resolve(cwd, values.env) : path4.join(cwd, "../../.env");
|
|
568
|
+
const limit = parseInt(values.limit ?? "50", 10);
|
|
569
|
+
const format = values.format ?? "table";
|
|
570
|
+
loadEnvFile(envPath);
|
|
571
|
+
const params = {};
|
|
572
|
+
if (values.params) {
|
|
573
|
+
for (const pair of values.params.split(",")) {
|
|
574
|
+
const eqIdx = pair.indexOf("=");
|
|
575
|
+
if (eqIdx === -1) continue;
|
|
576
|
+
const key = pair.slice(0, eqIdx).trim();
|
|
577
|
+
const val = pair.slice(eqIdx + 1).trim();
|
|
578
|
+
params[key] = val;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
if (values.slug) {
|
|
582
|
+
const result = await runDataSource(values.slug, dirPath, params, limit);
|
|
583
|
+
if (format === "json") {
|
|
584
|
+
displayJson([result]);
|
|
585
|
+
} else {
|
|
586
|
+
displaySummary(result);
|
|
587
|
+
if (values.debug) displayDebug(result);
|
|
588
|
+
if (!result.error) displayTable(result.rows, limit);
|
|
589
|
+
}
|
|
590
|
+
if (result.error) process.exit(1);
|
|
591
|
+
} else if (values.all) {
|
|
592
|
+
const results = await runAll(dirPath, params, limit);
|
|
593
|
+
if (format === "json") {
|
|
594
|
+
displayJson(results);
|
|
595
|
+
} else {
|
|
596
|
+
for (const r of results) {
|
|
597
|
+
displaySummary(r);
|
|
598
|
+
if (values.debug) displayDebug(r);
|
|
599
|
+
if (!r.error) displayTable(r.rows, limit);
|
|
600
|
+
}
|
|
601
|
+
const failed = results.filter((r) => r.error).length;
|
|
602
|
+
console.log(`
|
|
603
|
+
Total: ${results.length}, Failed: ${failed}`);
|
|
604
|
+
}
|
|
605
|
+
const anyFailed = results.some((r) => r.error);
|
|
606
|
+
if (anyFailed) process.exit(1);
|
|
607
|
+
} else {
|
|
608
|
+
const slugs = await listSlugs(dirPath);
|
|
609
|
+
if (slugs.length === 0) {
|
|
610
|
+
displayError(new Error(`No data sources found in ${dirPath}`));
|
|
611
|
+
process.exit(1);
|
|
612
|
+
}
|
|
613
|
+
try {
|
|
614
|
+
const { selectDataSource: selectDataSource2, inputParameters: inputParameters2 } = await Promise.resolve().then(() => (init_interactive(), interactive_exports));
|
|
615
|
+
const slug = await selectDataSource2(slugs);
|
|
616
|
+
if (!slug) {
|
|
617
|
+
console.log("Cancelled.");
|
|
618
|
+
process.exit(0);
|
|
619
|
+
}
|
|
620
|
+
const jsonPath = path4.join(dirPath, `${slug}.json`);
|
|
621
|
+
let paramMeta = [];
|
|
622
|
+
try {
|
|
623
|
+
const raw = await readFile3(jsonPath, "utf-8");
|
|
624
|
+
const def = JSON.parse(raw);
|
|
625
|
+
paramMeta = def.parameters ?? [];
|
|
626
|
+
} catch {
|
|
627
|
+
}
|
|
628
|
+
const interactiveParams = await inputParameters2(paramMeta);
|
|
629
|
+
const merged = { ...interactiveParams, ...params };
|
|
630
|
+
const result = await runDataSource(slug, dirPath, merged, limit);
|
|
631
|
+
displaySummary(result);
|
|
632
|
+
if (values.debug) displayDebug(result);
|
|
633
|
+
if (!result.error) displayTable(result.rows, limit);
|
|
634
|
+
if (result.error) process.exit(1);
|
|
635
|
+
} catch (err) {
|
|
636
|
+
displayError(err instanceof Error ? err : new Error(String(err)));
|
|
637
|
+
process.exit(1);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
main().catch((err) => {
|
|
642
|
+
displayError(err instanceof Error ? err : new Error(String(err)));
|
|
643
|
+
process.exit(1);
|
|
644
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -41,7 +41,7 @@ function resolveEnvVar(entry, key, connectionId) {
|
|
|
41
41
|
// src/connector-client/bigquery.ts
|
|
42
42
|
function createBigQueryClient(entry, connectionId) {
|
|
43
43
|
const projectId = resolveEnvVar(entry, "project-id", connectionId);
|
|
44
|
-
const serviceAccountJsonBase64 = resolveEnvVar(entry, "service-account-json-base64", connectionId);
|
|
44
|
+
const serviceAccountJsonBase64 = resolveEnvVar(entry, "service-account-key-json-base64", connectionId);
|
|
45
45
|
const serviceAccountJson = Buffer.from(serviceAccountJsonBase64, "base64").toString("utf-8");
|
|
46
46
|
let gcpCredentials;
|
|
47
47
|
try {
|
|
@@ -110,7 +110,7 @@ function createConnectorRegistry() {
|
|
|
110
110
|
let connectionsCache = null;
|
|
111
111
|
const clientCache = /* @__PURE__ */ new Map();
|
|
112
112
|
function getConnectionsFilePath() {
|
|
113
|
-
return process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), "
|
|
113
|
+
return process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), ".squadbase/connections.json");
|
|
114
114
|
}
|
|
115
115
|
function loadConnections() {
|
|
116
116
|
if (connectionsCache !== null) return connectionsCache;
|
|
@@ -175,7 +175,7 @@ function createConnectorRegistry() {
|
|
|
175
175
|
}
|
|
176
176
|
function watchConnectionsFile2() {
|
|
177
177
|
const filePath = getConnectionsFilePath();
|
|
178
|
-
const envPath = path.join(process.cwd(), "
|
|
178
|
+
const envPath = path.join(process.cwd(), ".env");
|
|
179
179
|
try {
|
|
180
180
|
fsWatch(filePath, { persistent: false }, () => {
|
|
181
181
|
console.log("[connector-client] connections.json changed, clearing cache");
|
|
@@ -726,7 +726,7 @@ apiApp.route("/data-source", data_source_default);
|
|
|
726
726
|
apiApp.route("/data-source-meta", data_source_meta_default);
|
|
727
727
|
apiApp.route("/cache", cache_default);
|
|
728
728
|
apiApp.route("/", pages_default);
|
|
729
|
-
reloadEnvFile(path4.join(process.cwd(), "
|
|
729
|
+
reloadEnvFile(path4.join(process.cwd(), ".env"));
|
|
730
730
|
await initialize();
|
|
731
731
|
startWatching();
|
|
732
732
|
watchConnectionsFile();
|
package/dist/main.js
CHANGED
|
@@ -41,7 +41,7 @@ function resolveEnvVar(entry, key, connectionId) {
|
|
|
41
41
|
// src/connector-client/bigquery.ts
|
|
42
42
|
function createBigQueryClient(entry, connectionId) {
|
|
43
43
|
const projectId = resolveEnvVar(entry, "project-id", connectionId);
|
|
44
|
-
const serviceAccountJsonBase64 = resolveEnvVar(entry, "service-account-json-base64", connectionId);
|
|
44
|
+
const serviceAccountJsonBase64 = resolveEnvVar(entry, "service-account-key-json-base64", connectionId);
|
|
45
45
|
const serviceAccountJson = Buffer.from(serviceAccountJsonBase64, "base64").toString("utf-8");
|
|
46
46
|
let gcpCredentials;
|
|
47
47
|
try {
|
|
@@ -110,7 +110,7 @@ function createConnectorRegistry() {
|
|
|
110
110
|
let connectionsCache = null;
|
|
111
111
|
const clientCache = /* @__PURE__ */ new Map();
|
|
112
112
|
function getConnectionsFilePath() {
|
|
113
|
-
return process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), "
|
|
113
|
+
return process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), ".squadbase/connections.json");
|
|
114
114
|
}
|
|
115
115
|
function loadConnections() {
|
|
116
116
|
if (connectionsCache !== null) return connectionsCache;
|
|
@@ -175,7 +175,7 @@ function createConnectorRegistry() {
|
|
|
175
175
|
}
|
|
176
176
|
function watchConnectionsFile2() {
|
|
177
177
|
const filePath = getConnectionsFilePath();
|
|
178
|
-
const envPath = path.join(process.cwd(), "
|
|
178
|
+
const envPath = path.join(process.cwd(), ".env");
|
|
179
179
|
try {
|
|
180
180
|
fsWatch(filePath, { persistent: false }, () => {
|
|
181
181
|
console.log("[connector-client] connections.json changed, clearing cache");
|
|
@@ -726,7 +726,7 @@ apiApp.route("/data-source", data_source_default);
|
|
|
726
726
|
apiApp.route("/data-source-meta", data_source_meta_default);
|
|
727
727
|
apiApp.route("/cache", cache_default);
|
|
728
728
|
apiApp.route("/", pages_default);
|
|
729
|
-
reloadEnvFile(path4.join(process.cwd(), "
|
|
729
|
+
reloadEnvFile(path4.join(process.cwd(), ".env"));
|
|
730
730
|
await initialize();
|
|
731
731
|
startWatching();
|
|
732
732
|
watchConnectionsFile();
|
package/dist/vite-plugin.js
CHANGED
|
@@ -41,7 +41,7 @@ function resolveEnvVar(entry, key, connectionId) {
|
|
|
41
41
|
// src/connector-client/bigquery.ts
|
|
42
42
|
function createBigQueryClient(entry, connectionId) {
|
|
43
43
|
const projectId = resolveEnvVar(entry, "project-id", connectionId);
|
|
44
|
-
const serviceAccountJsonBase64 = resolveEnvVar(entry, "service-account-json-base64", connectionId);
|
|
44
|
+
const serviceAccountJsonBase64 = resolveEnvVar(entry, "service-account-key-json-base64", connectionId);
|
|
45
45
|
const serviceAccountJson = Buffer.from(serviceAccountJsonBase64, "base64").toString("utf-8");
|
|
46
46
|
let gcpCredentials;
|
|
47
47
|
try {
|
|
@@ -110,7 +110,7 @@ function createConnectorRegistry() {
|
|
|
110
110
|
let connectionsCache = null;
|
|
111
111
|
const clientCache = /* @__PURE__ */ new Map();
|
|
112
112
|
function getConnectionsFilePath() {
|
|
113
|
-
return process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), "
|
|
113
|
+
return process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), ".squadbase/connections.json");
|
|
114
114
|
}
|
|
115
115
|
function loadConnections() {
|
|
116
116
|
if (connectionsCache !== null) return connectionsCache;
|
|
@@ -175,7 +175,7 @@ function createConnectorRegistry() {
|
|
|
175
175
|
}
|
|
176
176
|
function watchConnectionsFile2() {
|
|
177
177
|
const filePath = getConnectionsFilePath();
|
|
178
|
-
const envPath = path.join(process.cwd(), "
|
|
178
|
+
const envPath = path.join(process.cwd(), ".env");
|
|
179
179
|
try {
|
|
180
180
|
fsWatch(filePath, { persistent: false }, () => {
|
|
181
181
|
console.log("[connector-client] connections.json changed, clearing cache");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@squadbase/vite-server",
|
|
3
|
-
"version": "0.0.1-build-
|
|
3
|
+
"version": "0.0.1-build-7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
"types": "./dist/vite-plugin.d.ts"
|
|
21
21
|
}
|
|
22
22
|
},
|
|
23
|
-
"files": [
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
24
26
|
"publishConfig": {
|
|
25
27
|
"access": "public"
|
|
26
28
|
},
|