@mugwork/mug 0.1.0
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/LICENSE +21 -0
- package/README.md +251 -0
- package/dist/explorer.js +3 -0
- package/dist/packages/email-template/src/email-template.d.ts +18 -0
- package/dist/packages/email-template/src/email-template.js +74 -0
- package/dist/packages/email-template/src/index.d.ts +1 -0
- package/dist/packages/email-template/src/index.js +1 -0
- package/dist/packages/surface-renderer/src/form-renderer.d.ts +117 -0
- package/dist/packages/surface-renderer/src/form-renderer.js +719 -0
- package/dist/packages/surface-renderer/src/index.d.ts +4 -0
- package/dist/packages/surface-renderer/src/index.js +2 -0
- package/dist/packages/surface-renderer/src/portal-renderer.d.ts +177 -0
- package/dist/packages/surface-renderer/src/portal-renderer.js +1089 -0
- package/dist/packages/surface-renderer/src/workspace-home.d.ts +46 -0
- package/dist/packages/surface-renderer/src/workspace-home.js +345 -0
- package/dist/runtime/agent-types.d.ts +48 -0
- package/dist/runtime/agent-types.js +3 -0
- package/dist/runtime/ai-router.d.ts +32 -0
- package/dist/runtime/ai-router.js +112 -0
- package/dist/runtime/app.d.ts +6 -0
- package/dist/runtime/app.js +399 -0
- package/dist/runtime/chunker.d.ts +6 -0
- package/dist/runtime/chunker.js +30 -0
- package/dist/runtime/context.d.ts +115 -0
- package/dist/runtime/context.js +440 -0
- package/dist/runtime/do/workspace-database.d.ts +10 -0
- package/dist/runtime/do/workspace-database.js +199 -0
- package/dist/runtime/form-types.d.ts +143 -0
- package/dist/runtime/form-types.js +1 -0
- package/dist/runtime/runtime.d.ts +9 -0
- package/dist/runtime/runtime.js +7 -0
- package/dist/runtime/source-types.d.ts +15 -0
- package/dist/runtime/source-types.js +1 -0
- package/dist/runtime/source.d.ts +70 -0
- package/dist/runtime/source.js +21 -0
- package/dist/runtime/sync-runtime.d.ts +10 -0
- package/dist/runtime/sync-runtime.js +185 -0
- package/dist/runtime/types.d.ts +21 -0
- package/dist/runtime/types.js +1 -0
- package/dist/runtime/workflow-entrypoint.d.ts +31 -0
- package/dist/runtime/workflow-entrypoint.js +1297 -0
- package/dist/runtime/workflow.d.ts +285 -0
- package/dist/runtime/workflow.js +1008 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +44116 -0
- package/dist/src/commands/ai-gateway-route.d.ts +24 -0
- package/dist/src/commands/ai-gateway-route.js +192 -0
- package/dist/src/commands/auth.d.ts +1 -0
- package/dist/src/commands/auth.js +42 -0
- package/dist/src/commands/billing.d.ts +6 -0
- package/dist/src/commands/billing.js +76 -0
- package/dist/src/commands/brain.d.ts +1 -0
- package/dist/src/commands/brain.js +194 -0
- package/dist/src/commands/demo.d.ts +12 -0
- package/dist/src/commands/demo.js +147 -0
- package/dist/src/commands/deploy.d.ts +1 -0
- package/dist/src/commands/deploy.js +1052 -0
- package/dist/src/commands/dev.d.ts +14 -0
- package/dist/src/commands/dev.js +2818 -0
- package/dist/src/commands/form.d.ts +8 -0
- package/dist/src/commands/form.js +396 -0
- package/dist/src/commands/init.d.ts +1 -0
- package/dist/src/commands/init.js +139 -0
- package/dist/src/commands/issue.d.ts +7 -0
- package/dist/src/commands/issue.js +191 -0
- package/dist/src/commands/login.d.ts +9 -0
- package/dist/src/commands/login.js +163 -0
- package/dist/src/commands/logs.d.ts +8 -0
- package/dist/src/commands/logs.js +113 -0
- package/dist/src/commands/portal.d.ts +2 -0
- package/dist/src/commands/portal.js +111 -0
- package/dist/src/commands/pull.d.ts +3 -0
- package/dist/src/commands/pull.js +184 -0
- package/dist/src/commands/push.d.ts +4 -0
- package/dist/src/commands/push.js +183 -0
- package/dist/src/commands/run.d.ts +6 -0
- package/dist/src/commands/run.js +91 -0
- package/dist/src/commands/secret.d.ts +7 -0
- package/dist/src/commands/secret.js +105 -0
- package/dist/src/commands/shutdown.d.ts +1 -0
- package/dist/src/commands/shutdown.js +46 -0
- package/dist/src/commands/sql.d.ts +8 -0
- package/dist/src/commands/sql.js +142 -0
- package/dist/src/commands/status.d.ts +5 -0
- package/dist/src/commands/status.js +39 -0
- package/dist/src/commands/sync.d.ts +7 -0
- package/dist/src/commands/sync.js +991 -0
- package/dist/src/commands/usage.d.ts +6 -0
- package/dist/src/commands/usage.js +78 -0
- package/dist/src/commands/webhooks.d.ts +1 -0
- package/dist/src/commands/webhooks.js +102 -0
- package/dist/src/commands/workspace.d.ts +23 -0
- package/dist/src/commands/workspace.js +590 -0
- package/dist/src/connector-migration.d.ts +20 -0
- package/dist/src/connector-migration.js +43 -0
- package/dist/src/connector-parser.d.ts +14 -0
- package/dist/src/connector-parser.js +94 -0
- package/dist/src/connector-service/discover.d.ts +37 -0
- package/dist/src/connector-service/discover.js +79 -0
- package/dist/src/connector-service/gather.d.ts +22 -0
- package/dist/src/connector-service/gather.js +89 -0
- package/dist/src/connector-service/init.d.ts +14 -0
- package/dist/src/connector-service/init.js +109 -0
- package/dist/src/connector-service/scaffold.d.ts +17 -0
- package/dist/src/connector-service/scaffold.js +194 -0
- package/dist/src/connector-service/spec-storage.d.ts +8 -0
- package/dist/src/connector-service/spec-storage.js +48 -0
- package/dist/src/connector-service/types.d.ts +57 -0
- package/dist/src/connector-service/types.js +2 -0
- package/dist/src/connector-service/verify.d.ts +24 -0
- package/dist/src/connector-service/verify.js +575 -0
- package/dist/src/email-template.d.ts +2 -0
- package/dist/src/email-template.js +1 -0
- package/dist/src/manifest.d.ts +31 -0
- package/dist/src/manifest.js +25 -0
- package/dist/src/mug-icon.d.ts +1 -0
- package/dist/src/mug-icon.js +12 -0
- package/dist/src/slack-manifest.d.ts +119 -0
- package/dist/src/slack-manifest.js +163 -0
- package/dist/src/source-migration.d.ts +20 -0
- package/dist/src/source-migration.js +43 -0
- package/dist/src/surface-renderer.d.ts +5 -0
- package/dist/src/surface-renderer.js +3 -0
- package/dist/src/templates.d.ts +3 -0
- package/dist/src/templates.js +48 -0
- package/dist/src/version-check.d.ts +1 -0
- package/dist/src/version-check.js +28 -0
- package/dist/src/workflow-parser.d.ts +95 -0
- package/dist/src/workflow-parser.js +526 -0
- package/dist/worker/src/agent-types.d.ts +27 -0
- package/dist/worker/src/agent-types.js +3 -0
- package/dist/worker/src/source-types.d.ts +14 -0
- package/dist/worker/src/source-types.js +1 -0
- package/package.json +90 -0
- package/src/data/model-capabilities.json +171 -0
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { readSpec, writeSpec } from "./spec-storage.js";
|
|
4
|
+
import { lintSpec } from "./gather.js";
|
|
5
|
+
export async function verify(workspaceRoot, slug, sourceName) {
|
|
6
|
+
const spec = readSpec(workspaceRoot, slug);
|
|
7
|
+
if (!spec)
|
|
8
|
+
throw new Error(`No spec found for "${slug}" — run gather first`);
|
|
9
|
+
const connection = loadSource(workspaceRoot, sourceName);
|
|
10
|
+
const baseUrl = connection.baseUrl ?? extractBaseUrl(spec);
|
|
11
|
+
if (!baseUrl)
|
|
12
|
+
throw new Error("No base URL found in spec or source config");
|
|
13
|
+
const endpoints = extractEndpoints(spec);
|
|
14
|
+
const headers = buildAuthHeaders(connection);
|
|
15
|
+
const probes = [];
|
|
16
|
+
probes.push(await probe1SpecQuality(spec));
|
|
17
|
+
probes.push(await probe2Auth(baseUrl, endpoints, headers));
|
|
18
|
+
if (probes[1].status === "fail") {
|
|
19
|
+
probes.push({ probe: "3-endpoints", status: "skip", summary: "Skipped — auth failed", details: {} });
|
|
20
|
+
probes.push({ probe: "4-pagination", status: "skip", summary: "Skipped — auth failed", details: {} });
|
|
21
|
+
probes.push({ probe: "5-rate-limits", status: "skip", summary: "Skipped — auth failed", details: {} });
|
|
22
|
+
probes.push({ probe: "6-sync", status: "skip", summary: "Skipped — auth failed", details: {} });
|
|
23
|
+
probes.push({ probe: "7-errors", status: "skip", summary: "Skipped — auth failed", details: {} });
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
const endpointResults = await probe3Endpoints(baseUrl, endpoints, headers);
|
|
27
|
+
probes.push(endpointResults.probe);
|
|
28
|
+
probes.push(await probe4Pagination(baseUrl, endpoints, headers, endpointResults.responses));
|
|
29
|
+
probes.push(await probe5RateLimits(endpointResults.responses));
|
|
30
|
+
probes.push(await probe6Sync(baseUrl, endpoints, headers, endpointResults.responses));
|
|
31
|
+
probes.push(await probe7Errors(baseUrl, endpoints, headers));
|
|
32
|
+
}
|
|
33
|
+
const enriched = enrichSpec(spec, probes);
|
|
34
|
+
writeSpec(workspaceRoot, slug, enriched);
|
|
35
|
+
const passCount = probes.filter((p) => p.status === "pass").length;
|
|
36
|
+
const failCount = probes.filter((p) => p.status === "fail").length;
|
|
37
|
+
const overallStatus = failCount === 0 ? "pass" : passCount === 0 ? "fail" : "partial";
|
|
38
|
+
return { slug, probes, enrichedSpecSaved: true, overallStatus };
|
|
39
|
+
}
|
|
40
|
+
function loadSource(workspaceRoot, name) {
|
|
41
|
+
const mugJsonPath = join(workspaceRoot, "mug.json");
|
|
42
|
+
if (!existsSync(mugJsonPath))
|
|
43
|
+
throw new Error("No mug.json found — run mug init first");
|
|
44
|
+
const mugJson = JSON.parse(readFileSync(mugJsonPath, "utf-8"));
|
|
45
|
+
const src = mugJson.sources?.[name];
|
|
46
|
+
if (src)
|
|
47
|
+
return src;
|
|
48
|
+
const conn = mugJson.connections?.[name];
|
|
49
|
+
if (conn)
|
|
50
|
+
return conn;
|
|
51
|
+
throw new Error(`Source "${name}" not found in mug.json`);
|
|
52
|
+
}
|
|
53
|
+
function extractBaseUrl(spec) {
|
|
54
|
+
const servers = spec.servers;
|
|
55
|
+
return servers?.[0]?.url;
|
|
56
|
+
}
|
|
57
|
+
function extractEndpoints(spec) {
|
|
58
|
+
const paths = spec.paths;
|
|
59
|
+
if (!paths)
|
|
60
|
+
return [];
|
|
61
|
+
const endpoints = [];
|
|
62
|
+
const methods = ["get", "post", "put", "patch", "delete"];
|
|
63
|
+
for (const [path, pathItem] of Object.entries(paths)) {
|
|
64
|
+
if (!pathItem || typeof pathItem !== "object")
|
|
65
|
+
continue;
|
|
66
|
+
for (const method of methods) {
|
|
67
|
+
const op = pathItem[method];
|
|
68
|
+
if (!op)
|
|
69
|
+
continue;
|
|
70
|
+
const rawParams = (op.parameters ?? []);
|
|
71
|
+
const params = rawParams.filter((p) => typeof p.name === "string");
|
|
72
|
+
const isList = method === "get" && !path.match(/\{[^}]+\}$/);
|
|
73
|
+
endpoints.push({
|
|
74
|
+
path,
|
|
75
|
+
method,
|
|
76
|
+
operationId: op.operationId,
|
|
77
|
+
parameters: params,
|
|
78
|
+
isList,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return endpoints;
|
|
83
|
+
}
|
|
84
|
+
function buildAuthHeaders(connection) {
|
|
85
|
+
const { auth } = connection;
|
|
86
|
+
switch (auth.type) {
|
|
87
|
+
case "api-key":
|
|
88
|
+
return { [auth.header ?? "Authorization"]: auth.value };
|
|
89
|
+
case "bearer":
|
|
90
|
+
return { Authorization: `${auth.prefix ?? "Bearer"} ${auth.value}` };
|
|
91
|
+
case "basic":
|
|
92
|
+
return { Authorization: `Basic ${Buffer.from(auth.value).toString("base64")}` };
|
|
93
|
+
case "oauth2":
|
|
94
|
+
return { Authorization: `Bearer ${auth.value}` };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function simplestEndpoint(endpoints) {
|
|
98
|
+
return endpoints.find((e) => e.method === "get" && e.isList)
|
|
99
|
+
?? endpoints.find((e) => e.method === "get")
|
|
100
|
+
?? endpoints[0];
|
|
101
|
+
}
|
|
102
|
+
function buildUrl(baseUrl, path) {
|
|
103
|
+
const base = baseUrl.replace(/\/$/, "");
|
|
104
|
+
return `${base}${path}`;
|
|
105
|
+
}
|
|
106
|
+
// --- Probe implementations ---
|
|
107
|
+
async function probe1SpecQuality(spec) {
|
|
108
|
+
const lint = await lintSpec(spec);
|
|
109
|
+
return {
|
|
110
|
+
probe: "1-spec-quality",
|
|
111
|
+
status: lint.score >= 50 ? "pass" : "partial",
|
|
112
|
+
summary: `Quality score: ${lint.score}/100, ${lint.errors.length} errors, ${lint.warnings.length} warnings`,
|
|
113
|
+
details: { score: lint.score, errors: lint.errors, warnings: lint.warnings },
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
async function probe2Auth(baseUrl, endpoints, headers) {
|
|
117
|
+
const target = simplestEndpoint(endpoints);
|
|
118
|
+
if (!target)
|
|
119
|
+
return { probe: "2-auth", status: "fail", summary: "No endpoints in spec to test auth", details: {} };
|
|
120
|
+
const url = buildUrl(baseUrl, target.path);
|
|
121
|
+
try {
|
|
122
|
+
const res = await fetch(url, { headers, signal: AbortSignal.timeout(15000) });
|
|
123
|
+
if (res.status === 401 || res.status === 403) {
|
|
124
|
+
return {
|
|
125
|
+
probe: "2-auth",
|
|
126
|
+
status: "fail",
|
|
127
|
+
summary: `Auth failed: ${res.status} on ${target.method.toUpperCase()} ${target.path}`,
|
|
128
|
+
details: { status: res.status, endpoint: target.path },
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
probe: "2-auth",
|
|
133
|
+
status: "pass",
|
|
134
|
+
summary: `Auth works: ${res.status} on ${target.method.toUpperCase()} ${target.path}`,
|
|
135
|
+
details: { status: res.status, endpoint: target.path },
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
return {
|
|
140
|
+
probe: "2-auth",
|
|
141
|
+
status: "fail",
|
|
142
|
+
summary: `Request failed: ${err instanceof Error ? err.message : err}`,
|
|
143
|
+
details: { endpoint: target.path, error: String(err) },
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function probe3Endpoints(baseUrl, endpoints, headers) {
|
|
148
|
+
const responses = [];
|
|
149
|
+
let tested = 0;
|
|
150
|
+
let reachable = 0;
|
|
151
|
+
const paramValues = new Map();
|
|
152
|
+
// Pass 1 — hit param-free GET endpoints
|
|
153
|
+
for (const ep of endpoints) {
|
|
154
|
+
if (ep.method !== "get")
|
|
155
|
+
continue;
|
|
156
|
+
if (ep.path.includes("{"))
|
|
157
|
+
continue;
|
|
158
|
+
const result = await fetchEndpoint(baseUrl, ep, headers);
|
|
159
|
+
responses.push(result);
|
|
160
|
+
tested++;
|
|
161
|
+
if (result.status >= 200 && result.status < 400) {
|
|
162
|
+
reachable++;
|
|
163
|
+
extractParamValues(result.body, paramValues);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Pass 2 — use discovered values to fill path params and test those endpoints
|
|
167
|
+
for (const ep of endpoints) {
|
|
168
|
+
if (ep.method !== "get")
|
|
169
|
+
continue;
|
|
170
|
+
if (!ep.path.includes("{"))
|
|
171
|
+
continue;
|
|
172
|
+
const filled = fillPathParams(ep.path, paramValues);
|
|
173
|
+
if (!filled)
|
|
174
|
+
continue;
|
|
175
|
+
const filledEp = { ...ep, path: filled };
|
|
176
|
+
const result = await fetchEndpoint(baseUrl, filledEp, headers);
|
|
177
|
+
responses.push({ ...result, endpoint: ep });
|
|
178
|
+
tested++;
|
|
179
|
+
if (result.status >= 200 && result.status < 400) {
|
|
180
|
+
reachable++;
|
|
181
|
+
extractParamValues(result.body, paramValues);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const skipped = endpoints.filter((e) => e.method === "get").length - tested;
|
|
185
|
+
return {
|
|
186
|
+
probe: {
|
|
187
|
+
probe: "3-endpoints",
|
|
188
|
+
status: reachable === tested ? "pass" : reachable > 0 ? "partial" : "fail",
|
|
189
|
+
summary: `${reachable}/${tested} GET endpoints reachable${skipped > 0 ? ` (${skipped} skipped — couldn't fill path params)` : ""}`,
|
|
190
|
+
details: {
|
|
191
|
+
tested,
|
|
192
|
+
reachable,
|
|
193
|
+
skipped,
|
|
194
|
+
paramValues: Object.fromEntries(paramValues),
|
|
195
|
+
results: responses.map((r) => ({
|
|
196
|
+
path: r.endpoint.path,
|
|
197
|
+
status: r.status,
|
|
198
|
+
})),
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
responses,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
async function probe4Pagination(baseUrl, endpoints, headers, responses) {
|
|
205
|
+
const paginationByPath = {};
|
|
206
|
+
const listResponses = responses.filter((r) => r.endpoint.isList && r.status >= 200 && r.status < 300);
|
|
207
|
+
for (const resp of listResponses) {
|
|
208
|
+
const pagination = detectPagination(resp);
|
|
209
|
+
if (pagination) {
|
|
210
|
+
paginationByPath[resp.endpoint.path] = pagination;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const detected = Object.keys(paginationByPath).length;
|
|
214
|
+
return {
|
|
215
|
+
probe: "4-pagination",
|
|
216
|
+
status: detected > 0 ? "pass" : listResponses.length > 0 ? "partial" : "skip",
|
|
217
|
+
summary: detected > 0
|
|
218
|
+
? `Pagination detected on ${detected}/${listResponses.length} list endpoints`
|
|
219
|
+
: `No pagination detected on ${listResponses.length} list endpoints`,
|
|
220
|
+
details: { pagination: paginationByPath },
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function detectPagination(resp) {
|
|
224
|
+
// Check Link header
|
|
225
|
+
const linkHeader = resp.headers["link"];
|
|
226
|
+
if (linkHeader && linkHeader.includes('rel="next"')) {
|
|
227
|
+
return { style: "link-header" };
|
|
228
|
+
}
|
|
229
|
+
// Check body for cursor/offset patterns
|
|
230
|
+
if (resp.body && typeof resp.body === "object") {
|
|
231
|
+
const body = resp.body;
|
|
232
|
+
const cursorFields = ["next_cursor", "cursor", "next_page_token", "nextPageToken", "next", "offset"];
|
|
233
|
+
for (const field of cursorFields) {
|
|
234
|
+
const value = findField(body, field);
|
|
235
|
+
if (value !== undefined && value !== null) {
|
|
236
|
+
if (field === "offset" || field === "next") {
|
|
237
|
+
if (typeof value === "number") {
|
|
238
|
+
return { style: "offset", offsetParam: field };
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return { style: "cursor", cursorPath: field };
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// Check for page-based patterns
|
|
245
|
+
if (findField(body, "page") !== undefined || findField(body, "total_pages") !== undefined) {
|
|
246
|
+
return { style: "page", pageParam: "page" };
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
async function probe5RateLimits(responses) {
|
|
252
|
+
const rateLimitHeaders = [];
|
|
253
|
+
const rateLimit = {};
|
|
254
|
+
for (const resp of responses) {
|
|
255
|
+
const h = resp.headers;
|
|
256
|
+
const found = {};
|
|
257
|
+
for (const [key, value] of Object.entries(h)) {
|
|
258
|
+
const lower = key.toLowerCase();
|
|
259
|
+
if (lower.includes("ratelimit") || lower.includes("rate-limit") || lower === "retry-after") {
|
|
260
|
+
found[key] = value;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (Object.keys(found).length > 0) {
|
|
264
|
+
rateLimitHeaders.push(found);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (rateLimitHeaders.length === 0) {
|
|
268
|
+
return {
|
|
269
|
+
probe: "5-rate-limits",
|
|
270
|
+
status: "partial",
|
|
271
|
+
summary: "No rate limit headers found in responses",
|
|
272
|
+
details: {},
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
const sample = rateLimitHeaders[0];
|
|
276
|
+
for (const [key, value] of Object.entries(sample)) {
|
|
277
|
+
const lower = key.toLowerCase();
|
|
278
|
+
if (lower.includes("limit") && !lower.includes("remaining") && !lower.includes("reset")) {
|
|
279
|
+
rateLimit.headerLimit = key;
|
|
280
|
+
const num = parseInt(value);
|
|
281
|
+
if (!isNaN(num))
|
|
282
|
+
rateLimit.requestsPerMinute = num;
|
|
283
|
+
}
|
|
284
|
+
if (lower.includes("remaining"))
|
|
285
|
+
rateLimit.headerRemaining = key;
|
|
286
|
+
if (lower.includes("reset")) {
|
|
287
|
+
rateLimit.headerReset = key;
|
|
288
|
+
const num = parseInt(value);
|
|
289
|
+
if (!isNaN(num)) {
|
|
290
|
+
rateLimit.resetFormat = num > 1_000_000_000 ? "unix" : "seconds";
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (lower === "retry-after")
|
|
294
|
+
rateLimit.retryAfterHeader = key;
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
probe: "5-rate-limits",
|
|
298
|
+
status: "pass",
|
|
299
|
+
summary: `Rate limit headers found: ${Object.keys(sample).join(", ")}`,
|
|
300
|
+
details: { rateLimits: rateLimit, sampleHeaders: sample },
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
async function probe6Sync(baseUrl, endpoints, headers, responses) {
|
|
304
|
+
const syncByPath = {};
|
|
305
|
+
const listEndpoints = endpoints.filter((e) => e.isList && e.method === "get");
|
|
306
|
+
for (const ep of listEndpoints) {
|
|
307
|
+
const sync = { supportsIncrementalSync: false };
|
|
308
|
+
// Check spec params for time-based filters
|
|
309
|
+
const timeParams = ["updated_since", "modified_after", "since", "updated_after",
|
|
310
|
+
"min_date_modified", "filter[updated_at]", "startModifiedAt"];
|
|
311
|
+
const params = ep.parameters ?? [];
|
|
312
|
+
for (const param of params) {
|
|
313
|
+
if (!param.name)
|
|
314
|
+
continue;
|
|
315
|
+
if (timeParams.some((tp) => param.name.toLowerCase().includes(tp.toLowerCase().replace(/[[\]]/g, "")))) {
|
|
316
|
+
sync.supportsIncrementalSync = true;
|
|
317
|
+
sync.filterParam = param.name;
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
// Check response body for timestamp/deletion fields
|
|
322
|
+
const resp = responses.find((r) => r.endpoint.path === ep.path);
|
|
323
|
+
if (resp?.body && typeof resp.body === "object") {
|
|
324
|
+
const sampleRecord = extractSampleRecord(resp.body);
|
|
325
|
+
if (sampleRecord) {
|
|
326
|
+
if (findField(sampleRecord, "updated_at") !== undefined || findField(sampleRecord, "modified_at") !== undefined) {
|
|
327
|
+
sync.updatedAtField = findField(sampleRecord, "updated_at") !== undefined ? "updated_at" : "modified_at";
|
|
328
|
+
}
|
|
329
|
+
if (findField(sampleRecord, "deleted_at") !== undefined) {
|
|
330
|
+
sync.deletedAtField = "deleted_at";
|
|
331
|
+
sync.deletionStrategy = "soft-delete-field";
|
|
332
|
+
}
|
|
333
|
+
else if (findField(sampleRecord, "is_deleted") !== undefined) {
|
|
334
|
+
sync.isDeletedField = "is_deleted";
|
|
335
|
+
sync.deletionStrategy = "soft-delete-field";
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (sync.supportsIncrementalSync || sync.updatedAtField || sync.deletedAtField) {
|
|
340
|
+
syncByPath[ep.path] = sync;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
const detected = Object.keys(syncByPath).length;
|
|
344
|
+
return {
|
|
345
|
+
probe: "6-sync",
|
|
346
|
+
status: detected > 0 ? "pass" : "partial",
|
|
347
|
+
summary: detected > 0
|
|
348
|
+
? `Sync capability detected on ${detected}/${listEndpoints.length} list endpoints`
|
|
349
|
+
: `No sync capability detected on ${listEndpoints.length} list endpoints`,
|
|
350
|
+
details: { sync: syncByPath },
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
async function probe7Errors(baseUrl, endpoints, headers) {
|
|
354
|
+
const target = simplestEndpoint(endpoints);
|
|
355
|
+
if (!target)
|
|
356
|
+
return { probe: "7-errors", status: "skip", summary: "No endpoints to test", details: {} };
|
|
357
|
+
const errorPatterns = {};
|
|
358
|
+
// Try a request with a bogus path to trigger 404
|
|
359
|
+
try {
|
|
360
|
+
const res = await fetch(buildUrl(baseUrl, "/___mug_probe_nonexistent___"), {
|
|
361
|
+
headers,
|
|
362
|
+
signal: AbortSignal.timeout(15000),
|
|
363
|
+
});
|
|
364
|
+
const ct = res.headers.get("content-type") ?? "";
|
|
365
|
+
let body = null;
|
|
366
|
+
if (ct.includes("json")) {
|
|
367
|
+
try {
|
|
368
|
+
body = await res.json();
|
|
369
|
+
}
|
|
370
|
+
catch { /* ignore */ }
|
|
371
|
+
}
|
|
372
|
+
errorPatterns["404"] = { status: res.status, contentType: ct, body };
|
|
373
|
+
}
|
|
374
|
+
catch { /* ignore */ }
|
|
375
|
+
// Try an invalid request to a real endpoint
|
|
376
|
+
if (target.isList) {
|
|
377
|
+
const url = buildUrl(baseUrl, target.path) + "?limit=-1";
|
|
378
|
+
try {
|
|
379
|
+
const res = await fetch(url, { headers, signal: AbortSignal.timeout(15000) });
|
|
380
|
+
const ct = res.headers.get("content-type") ?? "";
|
|
381
|
+
let body = null;
|
|
382
|
+
if (ct.includes("json")) {
|
|
383
|
+
try {
|
|
384
|
+
body = await res.json();
|
|
385
|
+
}
|
|
386
|
+
catch { /* ignore */ }
|
|
387
|
+
}
|
|
388
|
+
if (res.status >= 400) {
|
|
389
|
+
errorPatterns["invalid-param"] = { status: res.status, contentType: ct, body };
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
catch { /* ignore */ }
|
|
393
|
+
}
|
|
394
|
+
const hasJson = Object.values(errorPatterns).some((p) => p.contentType?.toString().includes("json"));
|
|
395
|
+
return {
|
|
396
|
+
probe: "7-errors",
|
|
397
|
+
status: Object.keys(errorPatterns).length > 0 ? "pass" : "partial",
|
|
398
|
+
summary: hasJson
|
|
399
|
+
? "API returns structured JSON errors"
|
|
400
|
+
: "Error response shape captured",
|
|
401
|
+
details: { errorPatterns },
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
// --- Spec enrichment ---
|
|
405
|
+
function enrichSpec(spec, probes) {
|
|
406
|
+
const enriched = { ...spec };
|
|
407
|
+
const paginationProbe = probes.find((p) => p.probe === "4-pagination");
|
|
408
|
+
const rateLimitProbe = probes.find((p) => p.probe === "5-rate-limits");
|
|
409
|
+
const syncProbe = probes.find((p) => p.probe === "6-sync");
|
|
410
|
+
if (rateLimitProbe?.details.rateLimits) {
|
|
411
|
+
enriched["x-mug-rate-limits"] = rateLimitProbe.details.rateLimits;
|
|
412
|
+
}
|
|
413
|
+
// Enrich individual paths
|
|
414
|
+
const paths = enriched.paths;
|
|
415
|
+
if (paths) {
|
|
416
|
+
const pagination = (paginationProbe?.details.pagination ?? {});
|
|
417
|
+
const sync = (syncProbe?.details.sync ?? {});
|
|
418
|
+
for (const [path, pathItem] of Object.entries(paths)) {
|
|
419
|
+
if (pagination[path]) {
|
|
420
|
+
pathItem["x-mug-pagination"] = pagination[path];
|
|
421
|
+
}
|
|
422
|
+
if (sync[path]) {
|
|
423
|
+
pathItem["x-mug-sync"] = sync[path];
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return enriched;
|
|
428
|
+
}
|
|
429
|
+
// --- Endpoint probing helpers ---
|
|
430
|
+
async function fetchEndpoint(baseUrl, ep, headers) {
|
|
431
|
+
const url = buildUrl(baseUrl, ep.path);
|
|
432
|
+
try {
|
|
433
|
+
const res = await fetch(url, { headers, signal: AbortSignal.timeout(15000) });
|
|
434
|
+
const resHeaders = {};
|
|
435
|
+
res.headers.forEach((v, k) => { resHeaders[k] = v; });
|
|
436
|
+
let body = null;
|
|
437
|
+
const ct = res.headers.get("content-type") ?? "";
|
|
438
|
+
if (ct.includes("json")) {
|
|
439
|
+
try {
|
|
440
|
+
body = await res.json();
|
|
441
|
+
}
|
|
442
|
+
catch { /* ignore */ }
|
|
443
|
+
}
|
|
444
|
+
return { endpoint: ep, status: res.status, headers: resHeaders, body };
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
return { endpoint: ep, status: 0, headers: {}, body: null };
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
function extractParamValues(body, params) {
|
|
451
|
+
if (!body || typeof body !== "object")
|
|
452
|
+
return;
|
|
453
|
+
const obj = body;
|
|
454
|
+
// Find the list wrapper key and items: { bases: [{id: "..."}], tables: [{id: "..."}] }
|
|
455
|
+
const { key: listKey, items } = findListInResponse(obj);
|
|
456
|
+
for (const item of items) {
|
|
457
|
+
if (typeof item !== "object" || item === null)
|
|
458
|
+
continue;
|
|
459
|
+
const record = item;
|
|
460
|
+
if (typeof record.id === "string") {
|
|
461
|
+
// Store generic id
|
|
462
|
+
if (!params.has("id"))
|
|
463
|
+
params.set("id", record.id);
|
|
464
|
+
// Derive resource-specific param from the list wrapper key
|
|
465
|
+
// { "bases": [{id: "app..."}] } → baseId = "app..."
|
|
466
|
+
// { "tables": [{id: "tbl..."}] } → tableId = "tbl..."
|
|
467
|
+
if (listKey) {
|
|
468
|
+
const singular = listKey.replace(/s$/, "").replace(/ies$/, "y");
|
|
469
|
+
const resourceParam = `${singular}Id`;
|
|
470
|
+
if (!params.has(resourceParam))
|
|
471
|
+
params.set(resourceParam, record.id);
|
|
472
|
+
const snakeParam = `${singular}_id`;
|
|
473
|
+
if (!params.has(snakeParam))
|
|
474
|
+
params.set(snakeParam, record.id);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// Also store name fields — useful for {tableIdOrName} style params
|
|
478
|
+
if (typeof record.name === "string" && listKey) {
|
|
479
|
+
const singular = listKey.replace(/s$/, "").replace(/ies$/, "y");
|
|
480
|
+
const nameParam = `${singular}IdOrName`;
|
|
481
|
+
if (!params.has(nameParam))
|
|
482
|
+
params.set(nameParam, record.name);
|
|
483
|
+
const nameParam2 = `${singular}Name`;
|
|
484
|
+
if (!params.has(nameParam2))
|
|
485
|
+
params.set(nameParam2, record.name);
|
|
486
|
+
}
|
|
487
|
+
// Store explicit *Id fields from the record itself
|
|
488
|
+
for (const [key, value] of Object.entries(record)) {
|
|
489
|
+
if (typeof value !== "string" || !value)
|
|
490
|
+
continue;
|
|
491
|
+
if (key.endsWith("Id") || key.endsWith("_id")) {
|
|
492
|
+
if (!params.has(key))
|
|
493
|
+
params.set(key, value);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
// Top-level fields
|
|
498
|
+
if (typeof obj.id === "string" && !params.has("id")) {
|
|
499
|
+
params.set("id", obj.id);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
function findListInResponse(obj) {
|
|
503
|
+
const listKeys = ["data", "results", "records", "items", "entries",
|
|
504
|
+
"bases", "tables", "users", "organizations", "projects", "accounts", "contacts"];
|
|
505
|
+
for (const key of listKeys) {
|
|
506
|
+
const val = obj[key];
|
|
507
|
+
if (Array.isArray(val) && val.length > 0)
|
|
508
|
+
return { key, items: val };
|
|
509
|
+
}
|
|
510
|
+
if (Array.isArray(obj))
|
|
511
|
+
return { key: null, items: obj };
|
|
512
|
+
return { key: null, items: [] };
|
|
513
|
+
}
|
|
514
|
+
function fillPathParams(path, params) {
|
|
515
|
+
const paramPattern = /\{([^}]+)\}/g;
|
|
516
|
+
let filled = path;
|
|
517
|
+
let match;
|
|
518
|
+
while ((match = paramPattern.exec(path)) !== null) {
|
|
519
|
+
const paramName = match[1];
|
|
520
|
+
// Try exact match first, then common variations
|
|
521
|
+
const value = params.get(paramName)
|
|
522
|
+
?? params.get(camelCase(paramName))
|
|
523
|
+
?? tryFuzzyParam(paramName, params);
|
|
524
|
+
if (!value)
|
|
525
|
+
return null;
|
|
526
|
+
filled = filled.replace(match[0], encodeURIComponent(value));
|
|
527
|
+
}
|
|
528
|
+
return filled;
|
|
529
|
+
}
|
|
530
|
+
function camelCase(s) {
|
|
531
|
+
return s.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
532
|
+
}
|
|
533
|
+
function tryFuzzyParam(name, params) {
|
|
534
|
+
const lower = name.toLowerCase();
|
|
535
|
+
// {baseId} or {base_id} → look for "id" from a /bases response
|
|
536
|
+
if (lower.includes("id")) {
|
|
537
|
+
// Try stripping "Id"/"_id" suffix to find the resource name, then look for that resource's id
|
|
538
|
+
const resource = lower.replace(/id$/, "").replace(/_id$/, "").replace(/_$/, "");
|
|
539
|
+
for (const [key, value] of params) {
|
|
540
|
+
if (key.toLowerCase() === lower)
|
|
541
|
+
return value;
|
|
542
|
+
if (key.toLowerCase() === `${resource}id`)
|
|
543
|
+
return value;
|
|
544
|
+
if (key.toLowerCase() === `${resource}_id`)
|
|
545
|
+
return value;
|
|
546
|
+
}
|
|
547
|
+
// Last resort: if we only have one "id" value and param name contains "id", use it
|
|
548
|
+
if (params.has("id") && params.size <= 3)
|
|
549
|
+
return params.get("id");
|
|
550
|
+
}
|
|
551
|
+
return null;
|
|
552
|
+
}
|
|
553
|
+
// --- Field helpers ---
|
|
554
|
+
function findField(obj, name) {
|
|
555
|
+
const lower = name.toLowerCase();
|
|
556
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
557
|
+
if (key.toLowerCase() === lower)
|
|
558
|
+
return value;
|
|
559
|
+
}
|
|
560
|
+
return undefined;
|
|
561
|
+
}
|
|
562
|
+
function extractSampleRecord(body) {
|
|
563
|
+
// APIs typically return { data: [...] }, { results: [...] }, { records: [...] }, or bare [...]
|
|
564
|
+
for (const key of ["data", "results", "records", "items", "entries"]) {
|
|
565
|
+
const val = body[key];
|
|
566
|
+
if (Array.isArray(val) && val.length > 0 && typeof val[0] === "object") {
|
|
567
|
+
return val[0];
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
// Check if body itself is an array
|
|
571
|
+
if (Array.isArray(body) && body.length > 0 && typeof body[0] === "object") {
|
|
572
|
+
return body[0];
|
|
573
|
+
}
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { renderEmailHtml, markdownToHtml } from "../packages/email-template/src/email-template.js";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface FileEntry {
|
|
2
|
+
size: number;
|
|
3
|
+
sha256: string;
|
|
4
|
+
updated_at: string;
|
|
5
|
+
}
|
|
6
|
+
export interface FilesManifest {
|
|
7
|
+
synced_at: string | null;
|
|
8
|
+
files: Record<string, FileEntry>;
|
|
9
|
+
}
|
|
10
|
+
export interface ColumnInfo {
|
|
11
|
+
name: string;
|
|
12
|
+
type: string;
|
|
13
|
+
}
|
|
14
|
+
export interface TableInfo {
|
|
15
|
+
columns: ColumnInfo[];
|
|
16
|
+
row_count: number;
|
|
17
|
+
}
|
|
18
|
+
export interface DatabaseEntry {
|
|
19
|
+
size_mb: number;
|
|
20
|
+
tables: Record<string, TableInfo>;
|
|
21
|
+
updated_at: string;
|
|
22
|
+
}
|
|
23
|
+
export interface DatabasesManifest {
|
|
24
|
+
synced_at: string | null;
|
|
25
|
+
databases: Record<string, DatabaseEntry>;
|
|
26
|
+
}
|
|
27
|
+
export declare function readFilesManifest(cwd: string): FilesManifest;
|
|
28
|
+
export declare function writeFilesManifest(cwd: string, manifest: FilesManifest): void;
|
|
29
|
+
export declare function readDatabasesManifest(cwd: string): DatabasesManifest;
|
|
30
|
+
export declare function writeDatabasesManifest(cwd: string, manifest: DatabasesManifest): void;
|
|
31
|
+
export declare function computeFileSha256(filePath: string): string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
export function readFilesManifest(cwd) {
|
|
5
|
+
const path = join(cwd, "files", ".remote");
|
|
6
|
+
if (!existsSync(path))
|
|
7
|
+
return { synced_at: null, files: {} };
|
|
8
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
9
|
+
}
|
|
10
|
+
export function writeFilesManifest(cwd, manifest) {
|
|
11
|
+
writeFileSync(join(cwd, "files", ".remote"), JSON.stringify(manifest, null, 2) + "\n");
|
|
12
|
+
}
|
|
13
|
+
export function readDatabasesManifest(cwd) {
|
|
14
|
+
const path = join(cwd, "databases", ".remote");
|
|
15
|
+
if (!existsSync(path))
|
|
16
|
+
return { synced_at: null, databases: {} };
|
|
17
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
18
|
+
}
|
|
19
|
+
export function writeDatabasesManifest(cwd, manifest) {
|
|
20
|
+
writeFileSync(join(cwd, "databases", ".remote"), JSON.stringify(manifest, null, 2) + "\n");
|
|
21
|
+
}
|
|
22
|
+
export function computeFileSha256(filePath) {
|
|
23
|
+
const content = readFileSync(filePath);
|
|
24
|
+
return createHash("sha256").update(content).digest("hex");
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function generateMugIconSvg(accentColor?: string): string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const MUG_LOGO_SVG = `<svg width="291" height="371" viewBox="0 0 291 371" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M134.129 122.279L140.915 122.8L141.155 124.478L141.394 126.157L151.534 126.674L161.674 127.191L167.245 128.392L172.817 129.593L176.743 130.899L180.668 132.206L179.403 132.915L178.139 133.624L170.306 134.807L162.472 135.99L156.284 135.995L150.097 136V138.406V140.813L103.591 140.606L57.0848 140.4L56.8325 138.2L56.5794 136L51.2438 135.984L45.9073 135.968L40.3186 135.187L34.7299 134.406L30.9376 133.523L27.1452 132.641V131.987V131.334L34.1311 129.714L41.117 128.094L47.362 127.247L53.6078 126.4H59.5374H65.4678V124.438V122.476L70.4577 122.192L75.4477 121.908L101.395 121.834L127.343 121.759L134.129 122.279Z" fill="#71B7FB"/>
|
|
3
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.07464 136.078L5.58875 136.957V137.573V138.189L10.5787 139.553L15.5686 140.917L19.6268 142.398L23.6858 143.878L29.8645 144.739L36.0433 145.6H43.9161H51.7891L52.0422 147.8L52.2944 150H103.391H154.488L154.729 147.918L154.97 145.836L167.503 145.398L180.036 144.96L184.827 143.222L189.617 141.485L195.206 139.871L200.795 138.258L201.623 137.208L202.452 136.158L204.418 135.879L206.383 135.6L206.407 231.8L206.431 328H204.315H202.198L201.636 335.2L201.074 342.4H199.138H197.202V344.8V347.2H194.807H192.411V349.6V352H190.016H187.621V354.4V356.8H182.831H178.04V359.2V361.6H173.25H168.46L168.461 363.8L168.461 366H157.084H145.706L145.454 368.2L145.2 370.4H103.391H61.5821L61.329 368.2L61.0767 366L49.3005 365.778L37.5243 365.558V363.578V361.6H33.1331H28.742V359.2V356.8H23.9517H19.1613V354.453V352.106L16.9658 351.853L14.7702 351.6L14.5179 349.4L14.2648 347.2H11.9232H9.58069V342.4V337.6H7.18553H4.79036V332.8V328H2.3952H3.8147e-05V231.6V135.2H0.280282H0.560526L3.07464 136.078ZM19.1613 323.094V243.547V164H23.9517H28.742V239.2V314.4H31.1372H33.5323V321.6V328.8H35.5283H37.5243V330.8V332.8H39.9194H42.3146V335.2V337.6H44.7097H47.1049V342.4V347.2H42.7665H38.4288L38.1757 345L37.9235 342.8L33.4948 342.565L29.0654 342.33L28.1751 339.965L27.2841 337.6H25.649H24.0139L23.7832 330.6L23.5525 323.6L21.3569 323.347L19.1613 323.094Z" fill="#71B7FB"/>
|
|
4
|
+
<path d="M237.972 155.228L239.516 156.056V157.586V159.116L243.708 159.358L247.899 159.6L248.687 161.4L249.475 163.2H251.182H252.889L255.384 163.7L257.879 164.2V166.5V168.8H260.189H262.499L262.961 170.007L263.423 171.214L265.442 172.135L267.46 173.057V175.275V177.494L269.655 177.747L271.851 178L272.101 180.6L272.352 183.2H274.585H276.819L276.438 185.2L276.056 187.2H278.544H281.033V192V196.8H283.428H285.823V204V211.2H288.218H290.613V230V248.8H288.218H285.823V255.547V262.294L283.627 262.547L281.432 262.8L281.192 267.4L280.953 272H278.977H277.001L276.522 274.4L276.043 276.8H274.146H272.25V278.508V280.217L270.454 282.033L268.658 283.85L267.628 285.125L266.598 286.4H264.634H262.67V288.8V291.2H260.274H257.879V293.2V295.2H255.484H253.089V297.6V300H246.303H239.516V302.4V304.8H227.541H215.565V295.622V286.442L227.341 286.222L239.117 286L239.086 283.8L239.054 281.6H243.677H248.299V279.2V276.8H250.694H253.089V274.4V272H255.484H257.879V269.6V267.2H260.274H262.67V264.8V262.4H265.065H267.46V257.492V252.583L269.456 250.73L271.452 248.876V230.038V211.2H269.487H267.522L267.291 204.2L267.061 197.2L264.865 196.947L262.67 196.694V194.347V192H260.274H257.879V190.063V188.127L255.484 187.6L253.089 187.073V185.19V183.306L250.893 183.053L248.698 182.8L248.445 180.6L248.192 178.4H243.907H239.623L239.369 176.2L239.117 174L227.341 173.778L215.565 173.558V163.978V154.4H225.996H236.427L237.972 155.228Z" fill="#71B7FB"/>
|
|
5
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M107.782 6.8V13.6H110.073H112.364L112.858 16.6L113.354 19.6V23.2V26.8L112.866 29.756L112.378 32.7128L110.28 32.956L108.182 33.2L107.929 35.4L107.676 37.6H105.786H103.897L103.644 39.8L103.391 42L101.196 42.2528L99.0001 42.5064V44.0792V45.6512L97.8433 48.4256L96.6864 51.2H95.5527H94.419L93.924 54.2L93.4289 57.2L93.4904 64.4L93.5511 71.6L94.0805 75.6L94.609 79.6L96.8045 79.8528L99.0001 80.1064V82.0528V84H103.391H107.782V79.6V75.2H105.88H103.976L103.495 73.4L103.014 71.6L103.003 65.852L102.992 60.104L103.77 58.052L104.549 56H106.113H107.676L107.929 53.8L108.182 51.6L110.328 51.3512L112.476 51.1032L112.724 48.9512L112.972 46.8L115.167 46.5472L117.363 46.2936V44.4V42.5064L119.559 42.2528L121.754 42L121.992 37.4496L122.23 32.8992L124.387 32.6496L126.544 32.4L126.303 26L126.062 19.6L123.583 12.2392L121.105 4.87841L119.433 4.6392L117.762 4.4L117.51 2.2L117.257 0H112.52H107.782V6.8ZM66.2662 37.548V47.096L65.4878 49.148L64.7093 51.2H63.0926H61.4759V53.6V56H59.0807H56.6856V58.4V60.8H54.2904H51.8952V69.9472V79.0936L54.0908 79.3472L56.2864 79.6L56.5386 81.8L56.7917 84H61.1294H65.4678V79.6V75.2H63.4719H61.4759V70.4V65.6H63.4088H65.3409L65.867 63.2L66.3932 60.8H68.2726H70.152L70.4051 58.6L70.6573 56.4L72.8042 56.1512L74.9519 55.9032L75.1994 53.7512L75.4477 51.6L77.6432 51.3472L79.8388 51.0936V41.9472V32.8H77.4436H75.0485V30.4V28H70.6573H66.2662V37.548ZM146.096 36.6L146.087 45.2L145.592 48.2L145.097 51.2H143.206H141.315V53.6V56H138.919H136.524V58.4V60.8H134.129H131.734V69.9368V79.0728L134.129 79.6L136.524 80.1272V82.0632V84H140.915H145.307V79.6V75.2H143.359H141.413L140.876 71.6096L140.338 68.02L140.801 66.8096L141.264 65.6H143.286H145.307V63.2V60.8H147.649H149.991L150.244 58.6L150.496 56.4L152.692 56.1472L154.887 55.8936V53.5472V51.2H156.883H158.879V42V32.8H156.883H154.887V30.4V28H150.496H146.105L146.096 36.6Z" fill="#71B7FB"/>
|
|
6
|
+
<path d="M145.307 105.2V107.2L147.901 107.222L150.496 107.243L161.274 107.678L172.053 108.113L173.051 108.431L174.049 108.75V110.261V111.772L178.719 112.262L183.39 112.75L189.697 114.747L196.004 116.744L199.797 119.154L203.589 121.565V123.164V124.763L201.78 125.951L199.97 127.139L196.544 128.35L193.118 129.562L190.538 126.981L187.958 124.4L182.8 122.609L177.641 120.818L167.262 118.471L156.883 116.125L149.698 115.261L142.512 114.396L132.217 113.598L121.922 112.8H104.641H87.362L73.0214 113.988L58.6815 115.176L53.0928 116.069L47.5041 116.962L35.7965 119.63L24.0882 122.298L21.0264 123.871L17.9638 125.444L16.0987 127.446L14.2345 129.449L12.0844 128.831L9.93437 128.214L6.51408 126.465L3.09379 124.716L3.3429 122.962L3.59278 121.209L7.73164 118.734L11.8705 116.26L17.2644 114.486L22.6575 112.711L27.637 112.246L32.6158 111.78L33.0892 109.89L33.5627 108H40.2108H46.8598L54.1675 107.48L61.4759 106.961V105.08V103.2H103.391H145.307V105.2Z" fill="#71B7FB"/>
|
|
7
|
+
</svg>`;
|
|
8
|
+
export function generateMugIconSvg(accentColor) {
|
|
9
|
+
if (!accentColor || accentColor === "#71B7FB")
|
|
10
|
+
return MUG_LOGO_SVG;
|
|
11
|
+
return MUG_LOGO_SVG.replace(/#71B7FB/g, accentColor);
|
|
12
|
+
}
|