@growthub/cli 0.9.12 → 0.9.14
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/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/README.md +27 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/settings/apis-webhooks/route.js +59 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/settings/workspace/route.js +70 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/integration-entities/route.js +41 -9
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/list-entities/route.js +67 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/refresh-source/route.js +124 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/refresh-sources/route.js +127 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/register-resolver/route.js +119 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/resolvers/route.js +41 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/test-api-record/route.js +126 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/test-source/route.js +130 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/page.jsx +700 -214
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/global-error.jsx +21 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +2468 -793
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/apis-webhooks/apis-webhooks-form.jsx +208 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/apis-webhooks/page.jsx +19 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/apps/apps-list.jsx +43 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/apps/page.jsx +109 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/general/general-settings-form.jsx +134 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/general/page.jsx +25 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/integrations/page.jsx +22 -3
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/page.jsx +25 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/settings-shell.jsx +33 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +1558 -437
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/docs/data-sources-api-registry.md +139 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/resolver-loader.js +57 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/resolvers/README.md +133 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/resolvers/google-analytics.js +160 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/source-resolver-registry.js +85 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-config.js +264 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +104 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +23 -6
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package.json +1 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +7 -0
- package/dist/index.js +1764 -40675
- package/package.json +1 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /api/workspace/resolvers
|
|
3
|
+
*
|
|
4
|
+
* Lists resolver files present in lib/adapters/integrations/resolvers/ and
|
|
5
|
+
* returns provider-agnostic metadata for each registered resolver.
|
|
6
|
+
* Used by the generic resolver management panel and ResolverControlPanel in the
|
|
7
|
+
* widget inspector. No provider names appear in the response shape.
|
|
8
|
+
*
|
|
9
|
+
* Response:
|
|
10
|
+
* {
|
|
11
|
+
* files: string[],
|
|
12
|
+
* registeredIds: string[],
|
|
13
|
+
* resolvers: {
|
|
14
|
+
* integrationId: string,
|
|
15
|
+
* entityTypes: string[],
|
|
16
|
+
* hasListEntities: boolean,
|
|
17
|
+
* configSchema: SchemaField[] | null
|
|
18
|
+
* }[],
|
|
19
|
+
* canUpload: boolean
|
|
20
|
+
* }
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { NextResponse } from "next/server";
|
|
24
|
+
import { loadAllResolvers, listResolverFiles } from "@/lib/adapters/integrations/resolver-loader";
|
|
25
|
+
import { describeRegisteredResolvers } from "@/lib/adapters/integrations/source-resolver-registry";
|
|
26
|
+
import { describePersistenceMode } from "@/lib/workspace-config";
|
|
27
|
+
|
|
28
|
+
async function GET() {
|
|
29
|
+
await loadAllResolvers();
|
|
30
|
+
const files = await listResolverFiles();
|
|
31
|
+
const resolvers = describeRegisteredResolvers();
|
|
32
|
+
const persistence = describePersistenceMode();
|
|
33
|
+
return NextResponse.json({
|
|
34
|
+
files,
|
|
35
|
+
registeredIds: resolvers.map((r) => r.integrationId),
|
|
36
|
+
resolvers,
|
|
37
|
+
canUpload: persistence.canSave
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { GET };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { readWorkspaceConfig } from "@/lib/workspace-config";
|
|
3
|
+
|
|
4
|
+
const DEFAULT_TIMEOUT_MS = 15000;
|
|
5
|
+
|
|
6
|
+
function normalizeMethod(value) {
|
|
7
|
+
const method = String(value || "GET").trim().toUpperCase();
|
|
8
|
+
return ["GET", "POST", "PUT", "PATCH", "DELETE"].includes(method) ? method : "GET";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function buildUrl(record) {
|
|
12
|
+
const baseUrl = String(record?.baseUrl || "").trim();
|
|
13
|
+
const endpoint = String(record?.endpoint || "").trim();
|
|
14
|
+
const raw = endpoint || baseUrl;
|
|
15
|
+
if (!raw) throw new Error("baseUrl or endpoint is required");
|
|
16
|
+
if (/^https?:\/\//i.test(endpoint)) return endpoint;
|
|
17
|
+
if (!baseUrl) throw new Error("baseUrl is required when endpoint is relative");
|
|
18
|
+
return `${baseUrl.replace(/\/+$/, "")}/${endpoint.replace(/^\/+/, "")}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function envKeyCandidates(ref) {
|
|
22
|
+
const token = String(ref || "")
|
|
23
|
+
.trim()
|
|
24
|
+
.replace(/[^a-z0-9]+/gi, "_")
|
|
25
|
+
.replace(/^_+|_+$/g, "")
|
|
26
|
+
.toUpperCase();
|
|
27
|
+
return Array.from(new Set([
|
|
28
|
+
token,
|
|
29
|
+
token ? `${token}_API_KEY` : "",
|
|
30
|
+
token ? `${token}_TOKEN` : "",
|
|
31
|
+
].filter(Boolean)));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function readServerSecret(authRef) {
|
|
35
|
+
for (const key of envKeyCandidates(authRef)) {
|
|
36
|
+
if (process.env[key]) return process.env[key];
|
|
37
|
+
}
|
|
38
|
+
return "";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function findRegistryRecord(workspaceConfig, registryId) {
|
|
42
|
+
const id = String(registryId || "").trim();
|
|
43
|
+
if (!id) return null;
|
|
44
|
+
const objects = Array.isArray(workspaceConfig?.dataModel?.objects) ? workspaceConfig.dataModel.objects : [];
|
|
45
|
+
for (const object of objects) {
|
|
46
|
+
if (object?.objectType !== "api-registry") continue;
|
|
47
|
+
const match = (object.rows || []).find((row) => row?.integrationId === id || row?.id === id || row?.Name === id);
|
|
48
|
+
if (match) return match;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function buildAuthHeaders(record, secret) {
|
|
54
|
+
if (!secret) return {};
|
|
55
|
+
const headerName = String(record?.authHeaderName || record?.authHeader || "x-api-key").trim();
|
|
56
|
+
if (!headerName) return {};
|
|
57
|
+
const prefix = String(record?.authPrefix || "").trim();
|
|
58
|
+
return { [headerName]: prefix ? `${prefix} ${secret}` : secret };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function POST(request) {
|
|
62
|
+
let body;
|
|
63
|
+
try {
|
|
64
|
+
body = await request.json();
|
|
65
|
+
} catch {
|
|
66
|
+
return NextResponse.json({ ok: false, error: "invalid JSON body" }, { status: 400 });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const workspaceConfig = await readWorkspaceConfig();
|
|
70
|
+
const dataSourceRecord = body?.dataSourceRecord && typeof body.dataSourceRecord === "object"
|
|
71
|
+
? body.dataSourceRecord
|
|
72
|
+
: null;
|
|
73
|
+
const registryRecord = dataSourceRecord ? findRegistryRecord(workspaceConfig, dataSourceRecord.registryId) : null;
|
|
74
|
+
const record = {
|
|
75
|
+
...(registryRecord || {}),
|
|
76
|
+
...(body?.record || {}),
|
|
77
|
+
...(dataSourceRecord || {}),
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
let url;
|
|
81
|
+
try {
|
|
82
|
+
url = buildUrl(record);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
return NextResponse.json({ ok: false, error: error.message }, { status: 400 });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const method = normalizeMethod(record.method);
|
|
88
|
+
const authRef = record.authRef || record.integrationId || dataSourceRecord?.registryId;
|
|
89
|
+
const secret = readServerSecret(authRef);
|
|
90
|
+
const controller = new AbortController();
|
|
91
|
+
const timeout = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const response = await fetch(url, {
|
|
95
|
+
method,
|
|
96
|
+
headers: {
|
|
97
|
+
accept: "application/json",
|
|
98
|
+
...(method !== "GET" ? { "content-type": "application/json" } : {}),
|
|
99
|
+
...buildAuthHeaders(record, secret),
|
|
100
|
+
},
|
|
101
|
+
signal: controller.signal,
|
|
102
|
+
});
|
|
103
|
+
const contentType = response.headers.get("content-type") || "";
|
|
104
|
+
const payload = contentType.includes("application/json") ? await response.json() : await response.text();
|
|
105
|
+
return NextResponse.json({
|
|
106
|
+
ok: response.ok,
|
|
107
|
+
status: response.status,
|
|
108
|
+
statusText: response.statusText,
|
|
109
|
+
url,
|
|
110
|
+
authRef: authRef || null,
|
|
111
|
+
usedServerSecret: Boolean(secret),
|
|
112
|
+
response: payload,
|
|
113
|
+
}, { status: response.ok ? 200 : 502 });
|
|
114
|
+
} catch (error) {
|
|
115
|
+
return NextResponse.json({
|
|
116
|
+
ok: false,
|
|
117
|
+
error: error.name === "AbortError" ? "request timed out" : error.message || "request failed",
|
|
118
|
+
url,
|
|
119
|
+
authRef: authRef || null,
|
|
120
|
+
}, { status: 502 });
|
|
121
|
+
} finally {
|
|
122
|
+
clearTimeout(timeout);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export { POST };
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/workspace/test-source
|
|
3
|
+
*
|
|
4
|
+
* Tests a live source binding by calling the registered resolver for the
|
|
5
|
+
* requested integrationId and returning a preview of normalized records.
|
|
6
|
+
* Used by the no-code Live Source wizard in the widget source panel before
|
|
7
|
+
* the user applies the binding and triggers a full refresh.
|
|
8
|
+
*
|
|
9
|
+
* Dynamically loads resolver files from lib/adapters/integrations/resolvers/
|
|
10
|
+
* so operators can drop new resolver files without restarting the server
|
|
11
|
+
* (effective in Next.js dev mode with filesystem persistence).
|
|
12
|
+
*
|
|
13
|
+
* Request body:
|
|
14
|
+
* {
|
|
15
|
+
* integrationId: string, // provider slug, e.g. "my-crm"
|
|
16
|
+
* binding: { // provisional binding config from wizard
|
|
17
|
+
* entityType?: string,
|
|
18
|
+
* entityId?: string,
|
|
19
|
+
* sourceId?: string,
|
|
20
|
+
* authMode?: "bridge" | "byo-token"
|
|
21
|
+
* }
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* Response — success:
|
|
25
|
+
* { ok: true, integrationId, recordCount, columns, preview: Record[] }
|
|
26
|
+
*
|
|
27
|
+
* Response — no resolver:
|
|
28
|
+
* { ok: false, reason: "no-resolver", registeredResolvers: string[] }
|
|
29
|
+
*
|
|
30
|
+
* Response — resolver error:
|
|
31
|
+
* { ok: false, reason: "fetch-error", error: string }
|
|
32
|
+
*
|
|
33
|
+
* Authority contract: tokens never leave the server. The browser sends only
|
|
34
|
+
* non-secret binding metadata. Provider auth is read from env server-side.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import { NextResponse } from "next/server";
|
|
38
|
+
import { readAdapterConfig } from "@/lib/adapters/env";
|
|
39
|
+
import { listGovernedWorkspaceIntegrations } from "@/lib/adapters/integrations";
|
|
40
|
+
import { loadAllResolvers } from "@/lib/adapters/integrations/resolver-loader";
|
|
41
|
+
import { getSourceResolver, listRegisteredResolvers } from "@/lib/adapters/integrations/source-resolver-registry";
|
|
42
|
+
|
|
43
|
+
const PREVIEW_ROW_LIMIT = 8;
|
|
44
|
+
|
|
45
|
+
function inferColumns(records) {
|
|
46
|
+
const cols = new Set();
|
|
47
|
+
for (const record of records.slice(0, 20)) {
|
|
48
|
+
if (record && typeof record === "object" && !Array.isArray(record)) {
|
|
49
|
+
for (const key of Object.keys(record)) cols.add(key);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return Array.from(cols);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function POST(request) {
|
|
56
|
+
let body;
|
|
57
|
+
try {
|
|
58
|
+
body = await request.json();
|
|
59
|
+
} catch {
|
|
60
|
+
return NextResponse.json({ ok: false, reason: "bad-request", error: "invalid JSON body" }, { status: 400 });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const { integrationId, binding } = body || {};
|
|
64
|
+
if (typeof integrationId !== "string" || !integrationId.trim()) {
|
|
65
|
+
return NextResponse.json({ ok: false, reason: "bad-request", error: "integrationId must be a non-empty string" }, { status: 400 });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Load any resolver files the operator has dropped in the resolvers directory.
|
|
69
|
+
await loadAllResolvers();
|
|
70
|
+
|
|
71
|
+
const resolver = getSourceResolver(integrationId.trim());
|
|
72
|
+
if (!resolver) {
|
|
73
|
+
return NextResponse.json({
|
|
74
|
+
ok: false,
|
|
75
|
+
reason: "no-resolver",
|
|
76
|
+
integrationId: integrationId.trim(),
|
|
77
|
+
registeredResolvers: listRegisteredResolvers(),
|
|
78
|
+
hint: "Drop a resolver file in lib/adapters/integrations/resolvers/ that calls registerSourceResolver({ integrationId }) and re-run the test."
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const adapterConfig = readAdapterConfig();
|
|
83
|
+
|
|
84
|
+
// Resolve the live bridge connection for this integration so the resolver
|
|
85
|
+
// receives the full connection object (connectionId, authPath, metadata).
|
|
86
|
+
let connection = null;
|
|
87
|
+
try {
|
|
88
|
+
const integrations = await listGovernedWorkspaceIntegrations();
|
|
89
|
+
connection = integrations.find(
|
|
90
|
+
(i) => i.provider === integrationId.trim() || i.id === integrationId.trim()
|
|
91
|
+
) || null;
|
|
92
|
+
} catch {
|
|
93
|
+
// Non-fatal — resolver falls back to env-only auth
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let records;
|
|
97
|
+
try {
|
|
98
|
+
records = await resolver.fetchRecords(adapterConfig, connection, binding || {});
|
|
99
|
+
} catch (err) {
|
|
100
|
+
return NextResponse.json({
|
|
101
|
+
ok: false,
|
|
102
|
+
reason: "fetch-error",
|
|
103
|
+
integrationId: integrationId.trim(),
|
|
104
|
+
error: err?.message || "resolver.fetchRecords threw an error"
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!Array.isArray(records)) {
|
|
109
|
+
return NextResponse.json({
|
|
110
|
+
ok: false,
|
|
111
|
+
reason: "bad-resolver-response",
|
|
112
|
+
integrationId: integrationId.trim(),
|
|
113
|
+
error: "resolver.fetchRecords must return an array"
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const columns = inferColumns(records);
|
|
118
|
+
const preview = records.slice(0, PREVIEW_ROW_LIMIT);
|
|
119
|
+
|
|
120
|
+
return NextResponse.json({
|
|
121
|
+
ok: true,
|
|
122
|
+
integrationId: integrationId.trim(),
|
|
123
|
+
recordCount: records.length,
|
|
124
|
+
columns,
|
|
125
|
+
preview,
|
|
126
|
+
entityTypes: resolver.entityTypes || []
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export { POST };
|