@squadbase/vite-server 0.1.12-dev.a9ac647 → 0.1.17-dev.24af54e
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.js +12374 -883
- package/dist/connectors/airtable-oauth.js +257 -46
- package/dist/connectors/airtable.js +294 -51
- package/dist/connectors/amplitude.js +297 -47
- package/dist/connectors/anthropic.js +135 -47
- package/dist/connectors/asana.js +302 -49
- package/dist/connectors/attio.js +277 -49
- package/dist/connectors/aws-billing.js +262 -46
- package/dist/connectors/azure-sql.js +396 -102
- package/dist/connectors/backlog-api-key.js +292 -47
- package/dist/connectors/clickup.js +313 -49
- package/dist/connectors/cosmosdb.js +280 -50
- package/dist/connectors/customerio.js +294 -47
- package/dist/connectors/dbt.js +315 -47
- package/dist/connectors/freshdesk.js +317 -53
- package/dist/connectors/freshsales.js +308 -52
- package/dist/connectors/freshservice.js +336 -53
- package/dist/connectors/gamma.js +302 -52
- package/dist/connectors/gemini.js +134 -47
- package/dist/connectors/github.js +361 -49
- package/dist/connectors/gmail-oauth.js +179 -7
- package/dist/connectors/gmail.js +325 -47
- package/dist/connectors/google-ads.js +263 -46
- package/dist/connectors/google-analytics-oauth.js +285 -46
- package/dist/connectors/google-analytics.js +387 -49
- package/dist/connectors/google-audit-log.js +413 -47
- package/dist/connectors/google-calendar-oauth.js +234 -46
- package/dist/connectors/google-calendar.js +334 -47
- package/dist/connectors/google-docs.js +195 -6
- package/dist/connectors/google-drive.js +237 -5
- package/dist/connectors/google-search-console-oauth.js +231 -46
- package/dist/connectors/google-sheets.js +247 -47
- package/dist/connectors/google-slides.js +180 -6
- package/dist/connectors/grafana.js +307 -49
- package/dist/connectors/hubspot-oauth.js +183 -5
- package/dist/connectors/hubspot.js +281 -49
- package/dist/connectors/influxdb.js +391 -51
- package/dist/connectors/intercom-oauth.js +185 -5
- package/dist/connectors/intercom.js +277 -49
- package/dist/connectors/jdbc.js +737 -110
- package/dist/connectors/jira-api-key.js +301 -47
- package/dist/connectors/kintone-api-token.js +256 -47
- package/dist/connectors/kintone.js +303 -47
- package/dist/connectors/linear.js +305 -49
- package/dist/connectors/linkedin-ads.js +243 -50
- package/dist/connectors/mailchimp-oauth.js +243 -46
- package/dist/connectors/mailchimp.js +295 -49
- package/dist/connectors/meta-ads-oauth.js +248 -48
- package/dist/connectors/meta-ads.js +260 -50
- package/dist/connectors/mixpanel.js +313 -47
- package/dist/connectors/monday.js +335 -49
- package/dist/connectors/mongodb.js +294 -57
- package/dist/connectors/notion-oauth.js +206 -5
- package/dist/connectors/notion.js +298 -51
- package/dist/connectors/openai.js +134 -47
- package/dist/connectors/oracle.js +414 -103
- package/dist/connectors/outlook-oauth.js +179 -5
- package/dist/connectors/powerbi-oauth.js +226 -5
- package/dist/connectors/salesforce.js +359 -49
- package/dist/connectors/semrush.js +289 -49
- package/dist/connectors/sentry.js +264 -50
- package/dist/connectors/shopify-oauth.js +162 -5
- package/dist/connectors/shopify.js +332 -47
- package/dist/connectors/sqlserver.js +390 -102
- package/dist/connectors/stripe-api-key.js +244 -46
- package/dist/connectors/stripe-oauth.js +177 -5
- package/dist/connectors/supabase.js +278 -48
- package/dist/connectors/tableau.js +389 -184
- package/dist/connectors/tiktok-ads.js +254 -48
- package/dist/connectors/wix-store.js +295 -49
- package/dist/connectors/zendesk-oauth.js +214 -5
- package/dist/connectors/zendesk.js +333 -47
- package/dist/index.d.ts +149 -1
- package/dist/index.js +13677 -1969
- package/dist/main.js +13627 -1927
- package/dist/vite-plugin.js +12391 -890
- package/package.json +1 -1
|
@@ -113,6 +113,28 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
113
113
|
tools;
|
|
114
114
|
query;
|
|
115
115
|
checkConnection;
|
|
116
|
+
/**
|
|
117
|
+
* SQPD-1212: Logic-based, rule-driven connection setup. Connectors that
|
|
118
|
+
* implement this expose a step-by-step exploration flow (database/schema/
|
|
119
|
+
* table/etc. discovery) that the dashboard backend drives via the
|
|
120
|
+
* `/connections/:connectionId/setup` endpoint. Implement by delegating to
|
|
121
|
+
* `runSetupFlow` from `setup-flow.ts`.
|
|
122
|
+
*/
|
|
123
|
+
setup;
|
|
124
|
+
/**
|
|
125
|
+
* Opt-out of the default "verify before save" behavior on connection
|
|
126
|
+
* creation. The backend invokes `checkConnection` synchronously while
|
|
127
|
+
* creating the connection and aborts (no row inserted) if it fails — this
|
|
128
|
+
* flag disables that for connectors where the check cannot succeed pre-save:
|
|
129
|
+
*
|
|
130
|
+
* - `squadbase-db` populates `connection-url` only after Neon provisioning
|
|
131
|
+
* - OAuth connectors require an OAuth-aware proxyFetch keyed by the
|
|
132
|
+
* connectionId, which doesn't exist until the row is saved
|
|
133
|
+
*
|
|
134
|
+
* Exceptions are the explicit position; new credential-input connectors get
|
|
135
|
+
* the default verify-on-create behavior without opt-in.
|
|
136
|
+
*/
|
|
137
|
+
skipConnectionCheckOnCreate;
|
|
116
138
|
constructor(config) {
|
|
117
139
|
this.slug = config.slug;
|
|
118
140
|
this.authType = config.authType;
|
|
@@ -129,6 +151,8 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
129
151
|
this.tools = config.tools;
|
|
130
152
|
this.query = config.query;
|
|
131
153
|
this.checkConnection = config.checkConnection;
|
|
154
|
+
this.setup = config.setup;
|
|
155
|
+
this.skipConnectionCheckOnCreate = config.skipConnectionCheckOnCreate;
|
|
132
156
|
}
|
|
133
157
|
get connectorKey() {
|
|
134
158
|
return _ConnectorPlugin.deriveKey(this.slug, this.authType);
|
|
@@ -193,6 +217,51 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
193
217
|
}
|
|
194
218
|
};
|
|
195
219
|
|
|
220
|
+
// ../connectors/src/setup-flow.ts
|
|
221
|
+
async function runSetupFlow(flow, params, ctx, config) {
|
|
222
|
+
const runtime = {
|
|
223
|
+
params,
|
|
224
|
+
language: ctx.language,
|
|
225
|
+
config
|
|
226
|
+
};
|
|
227
|
+
let state = flow.initialState();
|
|
228
|
+
let answerIdx = 0;
|
|
229
|
+
for (const step of flow.steps) {
|
|
230
|
+
const ans = ctx.answers[answerIdx];
|
|
231
|
+
if (ans && ans.questionSlug === step.slug) {
|
|
232
|
+
state = step.applyAnswer(state, ans.answer);
|
|
233
|
+
answerIdx += 1;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (step.type === "text") {
|
|
237
|
+
return {
|
|
238
|
+
type: "nextQuestion",
|
|
239
|
+
questionSlug: step.slug,
|
|
240
|
+
question: step.question[ctx.language],
|
|
241
|
+
questionType: "text"
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const options = step.fetchOptions ? await step.fetchOptions(state, runtime) : [];
|
|
245
|
+
if (options.length === 0) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
type: "nextQuestion",
|
|
250
|
+
questionSlug: step.slug,
|
|
251
|
+
question: step.question[ctx.language],
|
|
252
|
+
questionType: step.type,
|
|
253
|
+
options
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
const dataInvestigationResult = await flow.finalize(state, runtime);
|
|
257
|
+
return { type: "fulfilled", dataInvestigationResult };
|
|
258
|
+
}
|
|
259
|
+
async function resolveSetupSelection(params) {
|
|
260
|
+
const { selected, allSentinel, fetchAll, limit } = params;
|
|
261
|
+
const resolved = selected.includes(allSentinel) ? await fetchAll() : selected.filter((v) => v !== allSentinel);
|
|
262
|
+
return resolved.slice(0, limit);
|
|
263
|
+
}
|
|
264
|
+
|
|
196
265
|
// ../connectors/src/auth-types.ts
|
|
197
266
|
var AUTH_TYPES = {
|
|
198
267
|
OAUTH: "oauth",
|
|
@@ -223,6 +292,104 @@ var googleDocsOnboarding = new ConnectorOnboarding({
|
|
|
223
292
|
}
|
|
224
293
|
});
|
|
225
294
|
|
|
295
|
+
// ../connectors/src/connectors/google-docs/utils.ts
|
|
296
|
+
async function googleApiFetch(config, url) {
|
|
297
|
+
const res = await config.proxyFetch(url, { method: "GET" });
|
|
298
|
+
if (!res.ok) {
|
|
299
|
+
const text = await res.text().catch(() => res.statusText);
|
|
300
|
+
throw new Error(`Google Docs ${url} failed: HTTP ${res.status} ${text}`);
|
|
301
|
+
}
|
|
302
|
+
return await res.json();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ../connectors/src/connectors/google-docs/setup-flow.ts
|
|
306
|
+
var ALL_DOCUMENTS = "__ALL_DOCUMENTS__";
|
|
307
|
+
var GOOGLE_DOCS_SETUP_MAX_DOCUMENTS = 10;
|
|
308
|
+
var DRIVE_LIST_PAGE_SIZE = 50;
|
|
309
|
+
async function listDocuments(config) {
|
|
310
|
+
const q = encodeURIComponent(
|
|
311
|
+
"mimeType='application/vnd.google-apps.document' and trashed=false"
|
|
312
|
+
);
|
|
313
|
+
const url = `https://www.googleapis.com/drive/v3/files?q=${q}&pageSize=${DRIVE_LIST_PAGE_SIZE}&orderBy=modifiedTime%20desc&fields=files(id,name,modifiedTime)`;
|
|
314
|
+
const data = await googleApiFetch(config, url);
|
|
315
|
+
return data.files ?? [];
|
|
316
|
+
}
|
|
317
|
+
function extractHeadings(meta, max) {
|
|
318
|
+
const out = [];
|
|
319
|
+
for (const c of meta.body?.content ?? []) {
|
|
320
|
+
const p = c.paragraph;
|
|
321
|
+
if (!p) continue;
|
|
322
|
+
const style = p.paragraphStyle?.namedStyleType ?? "";
|
|
323
|
+
if (!style.startsWith("HEADING_")) continue;
|
|
324
|
+
const text = (p.elements ?? []).map((el) => el.textRun?.content ?? "").join("").trim();
|
|
325
|
+
if (text) out.push(`${style}: ${text}`);
|
|
326
|
+
if (out.length >= max) break;
|
|
327
|
+
}
|
|
328
|
+
return out;
|
|
329
|
+
}
|
|
330
|
+
var googleDocsSetupFlow = {
|
|
331
|
+
initialState: () => ({}),
|
|
332
|
+
steps: [
|
|
333
|
+
{
|
|
334
|
+
slug: "documents",
|
|
335
|
+
type: "multiSelect",
|
|
336
|
+
question: {
|
|
337
|
+
ja: "\u5BFE\u8C61\u306E\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044\uFF08\u8907\u6570\u9078\u629E\u53EF\uFF09",
|
|
338
|
+
en: "Select target documents (multi-select allowed)"
|
|
339
|
+
},
|
|
340
|
+
async fetchOptions(_state, rt) {
|
|
341
|
+
const files = await listDocuments(rt.config);
|
|
342
|
+
return [
|
|
343
|
+
{
|
|
344
|
+
value: ALL_DOCUMENTS,
|
|
345
|
+
label: rt.language === "ja" ? "\u30A2\u30AF\u30BB\u30B9\u53EF\u80FD\u306A\u3059\u3079\u3066\u306E\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8" : "All accessible documents"
|
|
346
|
+
},
|
|
347
|
+
...files.map((f) => ({ value: f.id, label: f.name }))
|
|
348
|
+
];
|
|
349
|
+
},
|
|
350
|
+
applyAnswer: (state, answer) => ({ ...state, documents: answer })
|
|
351
|
+
}
|
|
352
|
+
],
|
|
353
|
+
async finalize(state, rt) {
|
|
354
|
+
if (!state.documents) {
|
|
355
|
+
throw new Error("Google Docs setup: incomplete state on finalize");
|
|
356
|
+
}
|
|
357
|
+
const targetIds = await resolveSetupSelection({
|
|
358
|
+
selected: state.documents,
|
|
359
|
+
allSentinel: ALL_DOCUMENTS,
|
|
360
|
+
fetchAll: async () => {
|
|
361
|
+
const files = await listDocuments(rt.config);
|
|
362
|
+
return files.map((f) => f.id);
|
|
363
|
+
},
|
|
364
|
+
limit: GOOGLE_DOCS_SETUP_MAX_DOCUMENTS
|
|
365
|
+
});
|
|
366
|
+
const sections = ["## Google Docs", ""];
|
|
367
|
+
if (targetIds.length === 0) {
|
|
368
|
+
sections.push(
|
|
369
|
+
rt.language === "ja" ? "\u5BFE\u8C61\u306E\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\u304C\u9078\u629E\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002" : "No documents selected."
|
|
370
|
+
);
|
|
371
|
+
return sections.join("\n");
|
|
372
|
+
}
|
|
373
|
+
for (const id of targetIds) {
|
|
374
|
+
const url = `https://docs.googleapis.com/v1/documents/${encodeURIComponent(
|
|
375
|
+
id
|
|
376
|
+
)}`;
|
|
377
|
+
const meta = await googleApiFetch(rt.config, url);
|
|
378
|
+
const title = meta.title ?? id;
|
|
379
|
+
sections.push(`### Document: ${title}`, "");
|
|
380
|
+
sections.push(`- ID: ${id}`);
|
|
381
|
+
if (meta.revisionId) sections.push(`- Revision: ${meta.revisionId}`);
|
|
382
|
+
const headings = extractHeadings(meta, 5);
|
|
383
|
+
if (headings.length > 0) {
|
|
384
|
+
sections.push("- Outline:");
|
|
385
|
+
for (const h of headings) sections.push(` - ${h}`);
|
|
386
|
+
}
|
|
387
|
+
sections.push("");
|
|
388
|
+
}
|
|
389
|
+
return sections.join("\n");
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
|
|
226
393
|
// ../connectors/src/connectors/google-docs/parameters.ts
|
|
227
394
|
var parameters = {};
|
|
228
395
|
|
|
@@ -355,6 +522,7 @@ var tools = { request: requestTool };
|
|
|
355
522
|
var googleDocsConnector = new ConnectorPlugin({
|
|
356
523
|
slug: "google-docs",
|
|
357
524
|
authType: AUTH_TYPES.OAUTH,
|
|
525
|
+
skipConnectionCheckOnCreate: true,
|
|
358
526
|
name: "Google Docs",
|
|
359
527
|
description: "Connect to Google Docs for document data access and creation using OAuth.",
|
|
360
528
|
iconUrl: "https://images.ctfassets.net/9ncizv60xc5y/6vvcGJisvXjOumeTvswjzf/e9bb39e453cc0b71a20f26019b23b0d2/google_docs.png",
|
|
@@ -510,7 +678,8 @@ await docs.batchUpdate(documentId, [
|
|
|
510
678
|
]);
|
|
511
679
|
\`\`\``
|
|
512
680
|
},
|
|
513
|
-
tools
|
|
681
|
+
tools,
|
|
682
|
+
setup: (params, ctx, config) => runSetupFlow(googleDocsSetupFlow, params, ctx, config)
|
|
514
683
|
});
|
|
515
684
|
|
|
516
685
|
// src/connectors/create-connector-sdk.ts
|
|
@@ -539,6 +708,7 @@ function resolveEnvVarOptional(entry, key) {
|
|
|
539
708
|
import { getContext } from "hono/context-storage";
|
|
540
709
|
import { getCookie } from "hono/cookie";
|
|
541
710
|
var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
|
|
711
|
+
var TABLEAU_SESSION_SENTINEL_URL = "squadbase://tableau-session/";
|
|
542
712
|
function normalizeHeaders(input) {
|
|
543
713
|
const out = {};
|
|
544
714
|
if (!input) return out;
|
|
@@ -547,6 +717,11 @@ function normalizeHeaders(input) {
|
|
|
547
717
|
});
|
|
548
718
|
return out;
|
|
549
719
|
}
|
|
720
|
+
function extractInputUrl(input) {
|
|
721
|
+
if (typeof input === "string") return input;
|
|
722
|
+
if (input instanceof URL) return input.href;
|
|
723
|
+
return input.url;
|
|
724
|
+
}
|
|
550
725
|
function createSandboxProxyFetch(connectionId) {
|
|
551
726
|
return async (input, init) => {
|
|
552
727
|
const token = process.env.INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL;
|
|
@@ -556,10 +731,17 @@ function createSandboxProxyFetch(connectionId) {
|
|
|
556
731
|
"Connection proxy is not configured. Please check your deployment settings."
|
|
557
732
|
);
|
|
558
733
|
}
|
|
559
|
-
const originalUrl =
|
|
734
|
+
const originalUrl = extractInputUrl(input);
|
|
735
|
+
const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
|
|
736
|
+
if (originalUrl === TABLEAU_SESSION_SENTINEL_URL) {
|
|
737
|
+
const sessionUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
|
|
738
|
+
return fetch(sessionUrl, {
|
|
739
|
+
method: "POST",
|
|
740
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
741
|
+
});
|
|
742
|
+
}
|
|
560
743
|
const originalMethod = init?.method ?? "GET";
|
|
561
744
|
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
562
|
-
const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
|
|
563
745
|
const proxyUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
|
|
564
746
|
return fetch(proxyUrl, {
|
|
565
747
|
method: "POST",
|
|
@@ -585,10 +767,9 @@ function createDeployedAppProxyFetch(connectionId) {
|
|
|
585
767
|
}
|
|
586
768
|
const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? "squadbase.app";
|
|
587
769
|
const proxyUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
|
|
770
|
+
const sessionUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
|
|
588
771
|
return async (input, init) => {
|
|
589
|
-
const originalUrl =
|
|
590
|
-
const originalMethod = init?.method ?? "GET";
|
|
591
|
-
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
772
|
+
const originalUrl = extractInputUrl(input);
|
|
592
773
|
const c = getContext();
|
|
593
774
|
const appSession = getCookie(c, APP_SESSION_COOKIE_NAME);
|
|
594
775
|
if (!appSession) {
|
|
@@ -596,6 +777,14 @@ function createDeployedAppProxyFetch(connectionId) {
|
|
|
596
777
|
"No authentication method available for connection proxy."
|
|
597
778
|
);
|
|
598
779
|
}
|
|
780
|
+
if (originalUrl === TABLEAU_SESSION_SENTINEL_URL) {
|
|
781
|
+
return fetch(sessionUrl, {
|
|
782
|
+
method: "POST",
|
|
783
|
+
headers: { Authorization: `Bearer ${appSession}` }
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
const originalMethod = init?.method ?? "GET";
|
|
787
|
+
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
599
788
|
return fetch(proxyUrl, {
|
|
600
789
|
method: "POST",
|
|
601
790
|
headers: {
|
|
@@ -202,6 +202,28 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
202
202
|
tools;
|
|
203
203
|
query;
|
|
204
204
|
checkConnection;
|
|
205
|
+
/**
|
|
206
|
+
* SQPD-1212: Logic-based, rule-driven connection setup. Connectors that
|
|
207
|
+
* implement this expose a step-by-step exploration flow (database/schema/
|
|
208
|
+
* table/etc. discovery) that the dashboard backend drives via the
|
|
209
|
+
* `/connections/:connectionId/setup` endpoint. Implement by delegating to
|
|
210
|
+
* `runSetupFlow` from `setup-flow.ts`.
|
|
211
|
+
*/
|
|
212
|
+
setup;
|
|
213
|
+
/**
|
|
214
|
+
* Opt-out of the default "verify before save" behavior on connection
|
|
215
|
+
* creation. The backend invokes `checkConnection` synchronously while
|
|
216
|
+
* creating the connection and aborts (no row inserted) if it fails — this
|
|
217
|
+
* flag disables that for connectors where the check cannot succeed pre-save:
|
|
218
|
+
*
|
|
219
|
+
* - `squadbase-db` populates `connection-url` only after Neon provisioning
|
|
220
|
+
* - OAuth connectors require an OAuth-aware proxyFetch keyed by the
|
|
221
|
+
* connectionId, which doesn't exist until the row is saved
|
|
222
|
+
*
|
|
223
|
+
* Exceptions are the explicit position; new credential-input connectors get
|
|
224
|
+
* the default verify-on-create behavior without opt-in.
|
|
225
|
+
*/
|
|
226
|
+
skipConnectionCheckOnCreate;
|
|
205
227
|
constructor(config) {
|
|
206
228
|
this.slug = config.slug;
|
|
207
229
|
this.authType = config.authType;
|
|
@@ -218,6 +240,8 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
218
240
|
this.tools = config.tools;
|
|
219
241
|
this.query = config.query;
|
|
220
242
|
this.checkConnection = config.checkConnection;
|
|
243
|
+
this.setup = config.setup;
|
|
244
|
+
this.skipConnectionCheckOnCreate = config.skipConnectionCheckOnCreate;
|
|
221
245
|
}
|
|
222
246
|
get connectorKey() {
|
|
223
247
|
return _ConnectorPlugin.deriveKey(this.slug, this.authType);
|
|
@@ -282,6 +306,51 @@ var ConnectorPlugin = class _ConnectorPlugin {
|
|
|
282
306
|
}
|
|
283
307
|
};
|
|
284
308
|
|
|
309
|
+
// ../connectors/src/setup-flow.ts
|
|
310
|
+
async function runSetupFlow(flow, params, ctx, config) {
|
|
311
|
+
const runtime = {
|
|
312
|
+
params,
|
|
313
|
+
language: ctx.language,
|
|
314
|
+
config
|
|
315
|
+
};
|
|
316
|
+
let state = flow.initialState();
|
|
317
|
+
let answerIdx = 0;
|
|
318
|
+
for (const step of flow.steps) {
|
|
319
|
+
const ans = ctx.answers[answerIdx];
|
|
320
|
+
if (ans && ans.questionSlug === step.slug) {
|
|
321
|
+
state = step.applyAnswer(state, ans.answer);
|
|
322
|
+
answerIdx += 1;
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (step.type === "text") {
|
|
326
|
+
return {
|
|
327
|
+
type: "nextQuestion",
|
|
328
|
+
questionSlug: step.slug,
|
|
329
|
+
question: step.question[ctx.language],
|
|
330
|
+
questionType: "text"
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
const options = step.fetchOptions ? await step.fetchOptions(state, runtime) : [];
|
|
334
|
+
if (options.length === 0) {
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
type: "nextQuestion",
|
|
339
|
+
questionSlug: step.slug,
|
|
340
|
+
question: step.question[ctx.language],
|
|
341
|
+
questionType: step.type,
|
|
342
|
+
options
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
const dataInvestigationResult = await flow.finalize(state, runtime);
|
|
346
|
+
return { type: "fulfilled", dataInvestigationResult };
|
|
347
|
+
}
|
|
348
|
+
async function resolveSetupSelection(params) {
|
|
349
|
+
const { selected, allSentinel, fetchAll, limit } = params;
|
|
350
|
+
const resolved = selected.includes(allSentinel) ? await fetchAll() : selected.filter((v) => v !== allSentinel);
|
|
351
|
+
return resolved.slice(0, limit);
|
|
352
|
+
}
|
|
353
|
+
|
|
285
354
|
// ../connectors/src/auth-types.ts
|
|
286
355
|
var AUTH_TYPES = {
|
|
287
356
|
OAUTH: "oauth",
|
|
@@ -312,6 +381,147 @@ var googleDriveOnboarding = new ConnectorOnboarding({
|
|
|
312
381
|
}
|
|
313
382
|
});
|
|
314
383
|
|
|
384
|
+
// ../connectors/src/connectors/google-drive/utils.ts
|
|
385
|
+
async function googleApiFetch(config, url) {
|
|
386
|
+
const res = await config.proxyFetch(url, { method: "GET" });
|
|
387
|
+
if (!res.ok) {
|
|
388
|
+
const text = await res.text().catch(() => res.statusText);
|
|
389
|
+
throw new Error(`Google Drive ${url} failed: HTTP ${res.status} ${text}`);
|
|
390
|
+
}
|
|
391
|
+
return await res.json();
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// ../connectors/src/connectors/google-drive/setup-flow.ts
|
|
395
|
+
var MY_DRIVE = "__MY_DRIVE__";
|
|
396
|
+
var ALL_ITEMS = "__ALL_ITEMS__";
|
|
397
|
+
var GOOGLE_DRIVE_SETUP_MAX_ITEMS = 25;
|
|
398
|
+
var PAGE_SIZE = 50;
|
|
399
|
+
var FOLDER_MIME = "application/vnd.google-apps.folder";
|
|
400
|
+
async function listSharedDrives(config) {
|
|
401
|
+
const url = `https://www.googleapis.com/drive/v3/drives?pageSize=100&fields=drives(id,name)`;
|
|
402
|
+
try {
|
|
403
|
+
const data = await googleApiFetch(config, url);
|
|
404
|
+
return data.drives ?? [];
|
|
405
|
+
} catch {
|
|
406
|
+
return [];
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
function listItemsUrl(driveId) {
|
|
410
|
+
const params = new URLSearchParams({
|
|
411
|
+
pageSize: String(PAGE_SIZE),
|
|
412
|
+
fields: "files(id,name,mimeType,modifiedTime)",
|
|
413
|
+
orderBy: "modifiedTime desc"
|
|
414
|
+
});
|
|
415
|
+
if (driveId) {
|
|
416
|
+
params.set("corpora", "drive");
|
|
417
|
+
params.set("driveId", driveId);
|
|
418
|
+
params.set("includeItemsFromAllDrives", "true");
|
|
419
|
+
params.set("supportsAllDrives", "true");
|
|
420
|
+
params.set("q", `'${driveId}' in parents and trashed=false`);
|
|
421
|
+
} else {
|
|
422
|
+
params.set("q", `'root' in parents and trashed=false`);
|
|
423
|
+
}
|
|
424
|
+
return `https://www.googleapis.com/drive/v3/files?${params.toString()}`;
|
|
425
|
+
}
|
|
426
|
+
async function listItems(config, driveId) {
|
|
427
|
+
const data = await googleApiFetch(
|
|
428
|
+
config,
|
|
429
|
+
listItemsUrl(driveId)
|
|
430
|
+
);
|
|
431
|
+
return data.files ?? [];
|
|
432
|
+
}
|
|
433
|
+
var googleDriveSetupFlow = {
|
|
434
|
+
initialState: () => ({}),
|
|
435
|
+
steps: [
|
|
436
|
+
{
|
|
437
|
+
slug: "drive",
|
|
438
|
+
type: "select",
|
|
439
|
+
question: {
|
|
440
|
+
ja: "\u5BFE\u8C61\u306E\u30C9\u30E9\u30A4\u30D6\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044",
|
|
441
|
+
en: "Select the drive to use"
|
|
442
|
+
},
|
|
443
|
+
async fetchOptions(_state, rt) {
|
|
444
|
+
const shared = await listSharedDrives(rt.config);
|
|
445
|
+
const myDriveLabel = rt.language === "ja" ? "\u30DE\u30A4\u30C9\u30E9\u30A4\u30D6" : "My Drive";
|
|
446
|
+
return [
|
|
447
|
+
{ value: MY_DRIVE, label: myDriveLabel },
|
|
448
|
+
...shared.filter((d) => d.id).map((d) => ({ value: d.id, label: d.name ?? d.id }))
|
|
449
|
+
];
|
|
450
|
+
},
|
|
451
|
+
applyAnswer: (state, answer) => ({ ...state, drive: answer[0] })
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
slug: "items",
|
|
455
|
+
type: "multiSelect",
|
|
456
|
+
question: {
|
|
457
|
+
ja: "\u5BFE\u8C61\u306E\u30D5\u30A9\u30EB\u30C0\u30FB\u30D5\u30A1\u30A4\u30EB\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044\uFF08\u8907\u6570\u9078\u629E\u53EF\uFF09",
|
|
458
|
+
en: "Select target folders or files (multi-select allowed)"
|
|
459
|
+
},
|
|
460
|
+
async fetchOptions(state, rt) {
|
|
461
|
+
if (!state.drive) return [];
|
|
462
|
+
const driveId = state.drive === MY_DRIVE ? null : state.drive;
|
|
463
|
+
const files = await listItems(rt.config, driveId);
|
|
464
|
+
const fileOptions = files.map((f) => {
|
|
465
|
+
const isFolder = f.mimeType === FOLDER_MIME;
|
|
466
|
+
const prefix = isFolder ? "[folder] " : "";
|
|
467
|
+
return { value: f.id, label: `${prefix}${f.name}` };
|
|
468
|
+
});
|
|
469
|
+
return [
|
|
470
|
+
{
|
|
471
|
+
value: ALL_ITEMS,
|
|
472
|
+
label: rt.language === "ja" ? "\u3053\u306E\u30C9\u30E9\u30A4\u30D6\u306E\u3059\u3079\u3066\u306E\u30A2\u30A4\u30C6\u30E0" : "All items in this drive"
|
|
473
|
+
},
|
|
474
|
+
...fileOptions
|
|
475
|
+
];
|
|
476
|
+
},
|
|
477
|
+
applyAnswer: (state, answer) => ({ ...state, items: answer })
|
|
478
|
+
}
|
|
479
|
+
],
|
|
480
|
+
async finalize(state, rt) {
|
|
481
|
+
if (!state.drive || !state.items) {
|
|
482
|
+
throw new Error("Google Drive setup: incomplete state on finalize");
|
|
483
|
+
}
|
|
484
|
+
const driveId = state.drive === MY_DRIVE ? null : state.drive;
|
|
485
|
+
const allFiles = await listItems(rt.config, driveId);
|
|
486
|
+
const filesById = new Map(allFiles.map((f) => [f.id, f]));
|
|
487
|
+
const targetIds = await resolveSetupSelection({
|
|
488
|
+
selected: state.items,
|
|
489
|
+
allSentinel: ALL_ITEMS,
|
|
490
|
+
fetchAll: async () => allFiles.map((f) => f.id),
|
|
491
|
+
limit: GOOGLE_DRIVE_SETUP_MAX_ITEMS
|
|
492
|
+
});
|
|
493
|
+
const driveLabel = state.drive === MY_DRIVE ? rt.language === "ja" ? "\u30DE\u30A4\u30C9\u30E9\u30A4\u30D6" : "My Drive" : state.drive;
|
|
494
|
+
const sections = [
|
|
495
|
+
"## Google Drive",
|
|
496
|
+
"",
|
|
497
|
+
`### Drive: ${driveLabel}`,
|
|
498
|
+
""
|
|
499
|
+
];
|
|
500
|
+
if (targetIds.length === 0) {
|
|
501
|
+
sections.push(
|
|
502
|
+
rt.language === "ja" ? "- \u9078\u629E\u3055\u308C\u305F\u30A2\u30A4\u30C6\u30E0\u306F\u3042\u308A\u307E\u305B\u3093\u3002" : "- No items selected."
|
|
503
|
+
);
|
|
504
|
+
return sections.join("\n");
|
|
505
|
+
}
|
|
506
|
+
sections.push("| Name | Type | Modified |");
|
|
507
|
+
sections.push("|------|------|----------|");
|
|
508
|
+
for (const id of targetIds) {
|
|
509
|
+
const f = filesById.get(id);
|
|
510
|
+
if (!f) {
|
|
511
|
+
sections.push(`| ${id} | (unknown) | - |`);
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
const isFolder = f.mimeType === FOLDER_MIME;
|
|
515
|
+
const typeLabel = isFolder ? "folder" : f.mimeType ?? "file";
|
|
516
|
+
sections.push(
|
|
517
|
+
`| ${f.name} | ${typeLabel} | ${f.modifiedTime ?? "-"} |`
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
sections.push("");
|
|
521
|
+
return sections.join("\n");
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
|
|
315
525
|
// ../connectors/src/connectors/google-drive/parameters.ts
|
|
316
526
|
var parameters = {};
|
|
317
527
|
|
|
@@ -439,6 +649,7 @@ var tools = { request: requestTool };
|
|
|
439
649
|
var googleDriveConnector = new ConnectorPlugin({
|
|
440
650
|
slug: "google-drive",
|
|
441
651
|
authType: AUTH_TYPES.OAUTH,
|
|
652
|
+
skipConnectionCheckOnCreate: true,
|
|
442
653
|
name: "Google Drive",
|
|
443
654
|
description: "Connect to Google Drive for file management, sharing, and collaboration using OAuth.",
|
|
444
655
|
iconUrl: "https://images.ctfassets.net/9ncizv60xc5y/4GJX5yQTogUgar1buWxXbv/4b43a65353319c508111489f834d22c4/google_drive.png",
|
|
@@ -730,6 +941,7 @@ await drive.updateFile("fileId123", {}, "newFolderId", "oldFolderId");
|
|
|
730
941
|
\`\`\``
|
|
731
942
|
},
|
|
732
943
|
tools,
|
|
944
|
+
setup: (params, ctx, config) => runSetupFlow(googleDriveSetupFlow, params, ctx, config),
|
|
733
945
|
async checkConnection(_params, config) {
|
|
734
946
|
const { proxyFetch } = config;
|
|
735
947
|
const url = "https://www.googleapis.com/drive/v3/about?fields=user";
|
|
@@ -778,6 +990,7 @@ function resolveEnvVarOptional(entry, key) {
|
|
|
778
990
|
import { getContext } from "hono/context-storage";
|
|
779
991
|
import { getCookie } from "hono/cookie";
|
|
780
992
|
var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
|
|
993
|
+
var TABLEAU_SESSION_SENTINEL_URL = "squadbase://tableau-session/";
|
|
781
994
|
function normalizeHeaders(input) {
|
|
782
995
|
const out = {};
|
|
783
996
|
if (!input) return out;
|
|
@@ -786,6 +999,11 @@ function normalizeHeaders(input) {
|
|
|
786
999
|
});
|
|
787
1000
|
return out;
|
|
788
1001
|
}
|
|
1002
|
+
function extractInputUrl(input) {
|
|
1003
|
+
if (typeof input === "string") return input;
|
|
1004
|
+
if (input instanceof URL) return input.href;
|
|
1005
|
+
return input.url;
|
|
1006
|
+
}
|
|
789
1007
|
function createSandboxProxyFetch(connectionId) {
|
|
790
1008
|
return async (input, init) => {
|
|
791
1009
|
const token = process.env.INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL;
|
|
@@ -795,10 +1013,17 @@ function createSandboxProxyFetch(connectionId) {
|
|
|
795
1013
|
"Connection proxy is not configured. Please check your deployment settings."
|
|
796
1014
|
);
|
|
797
1015
|
}
|
|
798
|
-
const originalUrl =
|
|
1016
|
+
const originalUrl = extractInputUrl(input);
|
|
1017
|
+
const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
|
|
1018
|
+
if (originalUrl === TABLEAU_SESSION_SENTINEL_URL) {
|
|
1019
|
+
const sessionUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
|
|
1020
|
+
return fetch(sessionUrl, {
|
|
1021
|
+
method: "POST",
|
|
1022
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
799
1025
|
const originalMethod = init?.method ?? "GET";
|
|
800
1026
|
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
801
|
-
const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
|
|
802
1027
|
const proxyUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
|
|
803
1028
|
return fetch(proxyUrl, {
|
|
804
1029
|
method: "POST",
|
|
@@ -824,10 +1049,9 @@ function createDeployedAppProxyFetch(connectionId) {
|
|
|
824
1049
|
}
|
|
825
1050
|
const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? "squadbase.app";
|
|
826
1051
|
const proxyUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
|
|
1052
|
+
const sessionUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
|
|
827
1053
|
return async (input, init) => {
|
|
828
|
-
const originalUrl =
|
|
829
|
-
const originalMethod = init?.method ?? "GET";
|
|
830
|
-
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
1054
|
+
const originalUrl = extractInputUrl(input);
|
|
831
1055
|
const c = getContext();
|
|
832
1056
|
const appSession = getCookie(c, APP_SESSION_COOKIE_NAME);
|
|
833
1057
|
if (!appSession) {
|
|
@@ -835,6 +1059,14 @@ function createDeployedAppProxyFetch(connectionId) {
|
|
|
835
1059
|
"No authentication method available for connection proxy."
|
|
836
1060
|
);
|
|
837
1061
|
}
|
|
1062
|
+
if (originalUrl === TABLEAU_SESSION_SENTINEL_URL) {
|
|
1063
|
+
return fetch(sessionUrl, {
|
|
1064
|
+
method: "POST",
|
|
1065
|
+
headers: { Authorization: `Bearer ${appSession}` }
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
const originalMethod = init?.method ?? "GET";
|
|
1069
|
+
const originalBody = init?.body ? JSON.parse(init.body) : void 0;
|
|
838
1070
|
return fetch(proxyUrl, {
|
|
839
1071
|
method: "POST",
|
|
840
1072
|
headers: {
|