@lovable.dev/mcp-js 0.21.0-rc.3 → 0.21.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/README.md +2 -1
- package/dist/{base-DTtaX5Rf.d.ts → base-CFy5vZ5Z.d.ts} +7 -2
- package/dist/chunk-6QFZYYUV.js +11 -0
- package/dist/{chunk-5W6JK6OS.js → chunk-CI7JYLV3.js} +6 -2
- package/dist/{chunk-SIDWSSWV.js → chunk-E3Q3FRKQ.js} +1 -1
- package/dist/{chunk-NENAFXZJ.js → chunk-ENJDNFEB.js} +1 -1
- package/dist/{chunk-TVU5FL2T.js → chunk-LFXK3FNF.js} +1 -1
- package/dist/{chunk-JIOOUIDX.js → chunk-QPKPYDPB.js} +6 -2
- package/dist/{chunk-CIAVINZ5.js → chunk-YLU2JTHL.js} +45 -80
- package/dist/cli/extract-manifest.cjs +3 -1
- package/dist/cli/extract-manifest.js +5 -3
- package/dist/index.cjs +12 -0
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +11 -1
- package/dist/protocols/mcp/index.cjs +52 -79
- package/dist/protocols/mcp/index.d.cts +2 -2
- package/dist/protocols/mcp/index.d.ts +2 -2
- package/dist/protocols/mcp/index.js +4 -3
- package/dist/protocols/oauth-metadata.cjs +40 -60
- package/dist/protocols/oauth-metadata.d.cts +1 -1
- package/dist/protocols/oauth-metadata.d.ts +1 -1
- package/dist/protocols/oauth-metadata.js +3 -3
- package/dist/protocols/rest/index.cjs +52 -79
- package/dist/protocols/rest/index.d.cts +2 -2
- package/dist/protocols/rest/index.d.ts +2 -2
- package/dist/protocols/rest/index.js +5 -4
- package/dist/stacks/supabase/index.cjs +57 -82
- package/dist/stacks/supabase/index.d.cts +1 -1
- package/dist/stacks/supabase/index.d.ts +1 -1
- package/dist/stacks/supabase/index.js +7 -6
- package/dist/stacks/supabase/vite.cjs +1 -1
- package/dist/stacks/supabase/vite.js +1 -1
- package/dist/stacks/tanstack/index.cjs +57 -82
- package/dist/stacks/tanstack/index.d.cts +1 -1
- package/dist/stacks/tanstack/index.d.ts +1 -1
- package/dist/stacks/tanstack/index.js +7 -6
- package/dist/{types-CPkhCRxc.d.ts → types-qqrmJ736.d.ts} +10 -0
- package/package.json +1 -1
|
@@ -129,7 +129,7 @@ function describeError(err) {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
// package.json
|
|
132
|
-
var version = "0.21.0
|
|
132
|
+
var version = "0.21.0";
|
|
133
133
|
|
|
134
134
|
// src/metrics/otlp.ts
|
|
135
135
|
var SCOPE_NAME = "@lovable.dev/mcp-js";
|
|
@@ -236,7 +236,8 @@ var BaseMetricRecorder = class {
|
|
|
236
236
|
method: ev.method,
|
|
237
237
|
outcome: ev.outcome,
|
|
238
238
|
durationMs: ev.durationMs,
|
|
239
|
-
stack: this.stack
|
|
239
|
+
stack: this.stack,
|
|
240
|
+
...ev.errorText !== void 0 && { errorText: ev.errorText }
|
|
240
241
|
});
|
|
241
242
|
if (!this.config.enabled)
|
|
242
243
|
return;
|
|
@@ -416,23 +417,9 @@ function parseSafeUrl(subject, raw, ErrorClass = Error) {
|
|
|
416
417
|
|
|
417
418
|
// src/auth/discovery.ts
|
|
418
419
|
var OAuthConfigurationError = class extends Error {
|
|
419
|
-
constructor(message, stage) {
|
|
420
|
-
super(message);
|
|
421
|
-
this.stage = stage;
|
|
422
|
-
this.name = "OAuthConfigurationError";
|
|
423
|
-
}
|
|
424
|
-
};
|
|
425
|
-
var OAuthUpstreamUnreachableError = class extends Error {
|
|
426
|
-
constructor(message, stage) {
|
|
427
|
-
super(message);
|
|
428
|
-
this.stage = stage;
|
|
429
|
-
this.name = "OAuthUpstreamUnreachableError";
|
|
430
|
-
}
|
|
431
|
-
};
|
|
432
|
-
var MetadataUnreachableError = class extends Error {
|
|
433
420
|
constructor(message) {
|
|
434
421
|
super(message);
|
|
435
|
-
this.name = "
|
|
422
|
+
this.name = "OAuthConfigurationError";
|
|
436
423
|
}
|
|
437
424
|
};
|
|
438
425
|
var METADATA_FETCH_TIMEOUT_MS = 5e3;
|
|
@@ -464,28 +451,16 @@ async function fetchFirstValidOAuthServerMetadata(metadataUrls, expectedIssuer)
|
|
|
464
451
|
try {
|
|
465
452
|
return await fetchOAuthServerMetadata(url, expectedIssuer);
|
|
466
453
|
} catch (err) {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
errors.push(error);
|
|
454
|
+
log.debug("oauth.discovery.attempt_failed", { url, ...describeError(err) });
|
|
455
|
+
errors.push(`${url}: ${err instanceof Error ? err.message : String(err)}`);
|
|
470
456
|
}
|
|
471
457
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors: detail, unreachable });
|
|
475
|
-
const message = `failed to discover OAuth server metadata (${detail})`;
|
|
476
|
-
throw unreachable ? new MetadataUnreachableError(message) : new Error(message);
|
|
458
|
+
log.error("oauth.discovery.exhausted", { expectedIssuer, urlsTried: metadataUrls, errors });
|
|
459
|
+
throw new Error(`failed to discover OAuth server metadata (${errors.join("; ")})`);
|
|
477
460
|
}
|
|
478
461
|
async function fetchOAuthServerMetadata(url, expectedIssuer) {
|
|
479
462
|
log.debug("oauth.discovery.fetch", { url });
|
|
480
|
-
|
|
481
|
-
try {
|
|
482
|
-
response = await fetch(url, {
|
|
483
|
-
signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS),
|
|
484
|
-
redirect: "manual"
|
|
485
|
-
});
|
|
486
|
-
} catch (err) {
|
|
487
|
-
throw new MetadataUnreachableError(err instanceof Error ? err.message : String(err));
|
|
488
|
-
}
|
|
463
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS), redirect: "manual" });
|
|
489
464
|
if (!response.ok) {
|
|
490
465
|
throw new Error(String(response.status));
|
|
491
466
|
}
|
|
@@ -508,21 +483,14 @@ async function fetchIssuerOAuthServerMetadata(issuer) {
|
|
|
508
483
|
try {
|
|
509
484
|
return await fetchFirstValidOAuthServerMetadata(oauthMetadataUrlsForIssuer(issuer), issuer);
|
|
510
485
|
} catch (err) {
|
|
511
|
-
const message = `OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
512
|
-
if (err instanceof MetadataUnreachableError) {
|
|
513
|
-
log.error("oauth.discovery.unreachable", {
|
|
514
|
-
issuer,
|
|
515
|
-
...describeError(err),
|
|
516
|
-
outcome: "503 authorization server unreachable"
|
|
517
|
-
});
|
|
518
|
-
throw new OAuthUpstreamUnreachableError(message, "discovery");
|
|
519
|
-
}
|
|
520
486
|
log.error("oauth.discovery.config_error", {
|
|
521
487
|
issuer,
|
|
522
488
|
...describeError(err),
|
|
523
489
|
outcome: "500 oauth configuration error"
|
|
524
490
|
});
|
|
525
|
-
throw new OAuthConfigurationError(
|
|
491
|
+
throw new OAuthConfigurationError(
|
|
492
|
+
`OAuth issuer discovery failed: ${err instanceof Error ? err.message : String(err)}`
|
|
493
|
+
);
|
|
526
494
|
}
|
|
527
495
|
}
|
|
528
496
|
function createOAuthDiscoveryResolver(auth) {
|
|
@@ -582,13 +550,18 @@ function readString(value) {
|
|
|
582
550
|
function splitScopes(value) {
|
|
583
551
|
if (typeof value === "string")
|
|
584
552
|
return value.split(/\s+/).filter(Boolean);
|
|
585
|
-
|
|
586
|
-
return value.filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
587
|
-
return [];
|
|
553
|
+
return stringClaimList(value);
|
|
588
554
|
}
|
|
589
555
|
function stringClaim(claims, name) {
|
|
590
556
|
return readString(claims[name]);
|
|
591
557
|
}
|
|
558
|
+
function stringClaimList(value) {
|
|
559
|
+
if (typeof value === "string")
|
|
560
|
+
return value === "" ? [] : [value];
|
|
561
|
+
if (Array.isArray(value))
|
|
562
|
+
return value.filter((entry) => typeof entry === "string" && entry !== "");
|
|
563
|
+
return [];
|
|
564
|
+
}
|
|
592
565
|
|
|
593
566
|
// src/auth/verifier.ts
|
|
594
567
|
var DEFAULT_JWT_ALGORITHMS = ["RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "EdDSA"];
|
|
@@ -615,24 +588,15 @@ function tokenHeaderFields(token) {
|
|
|
615
588
|
}
|
|
616
589
|
}
|
|
617
590
|
async function fetchVerificationKeySet(jwksUri) {
|
|
618
|
-
let response;
|
|
619
|
-
try {
|
|
620
|
-
response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
|
|
621
|
-
} catch (err) {
|
|
622
|
-
log.error("oauth.jwks.unreachable", { ...describeError(err), outcome: "503 authorization server unreachable" });
|
|
623
|
-
throw new OAuthUpstreamUnreachableError(
|
|
624
|
-
`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
625
|
-
"jwks"
|
|
626
|
-
);
|
|
627
|
-
}
|
|
628
591
|
try {
|
|
592
|
+
const response = await fetch(jwksUri, { signal: AbortSignal.timeout(JWKS_FETCH_TIMEOUT_MS), redirect: "manual" });
|
|
629
593
|
if (!response.ok)
|
|
630
594
|
throw new Error(`JWKS endpoint returned ${response.status}`);
|
|
631
595
|
const json = await response.json();
|
|
632
596
|
return (0, import_jose.createLocalJWKSet)(json);
|
|
633
597
|
} catch (err) {
|
|
634
598
|
log.error("oauth.jwks.fetch_failed", { ...describeError(err), outcome: "500 oauth configuration error" });
|
|
635
|
-
throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}
|
|
599
|
+
throw new OAuthConfigurationError(`JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
636
600
|
}
|
|
637
601
|
}
|
|
638
602
|
function assertAccessTokenTyp(token, allowed) {
|
|
@@ -649,13 +613,12 @@ function assertAccessTokenTyp(token, allowed) {
|
|
|
649
613
|
throw new OAuthTokenError(401, "invalid_token", "Access token typ header is not accepted");
|
|
650
614
|
}
|
|
651
615
|
}
|
|
652
|
-
async function verifyJwtClaims(token, keySet, issuer,
|
|
616
|
+
async function verifyJwtClaims(token, keySet, issuer, auth) {
|
|
653
617
|
try {
|
|
654
618
|
const { payload } = await (0, import_jose.jwtVerify)(token, keySet, {
|
|
655
619
|
// `issuer` is trimmed of any trailing slash; accept both forms so a token whose
|
|
656
620
|
// `iss` carries the slash the AS publishes still verifies.
|
|
657
621
|
issuer: [issuer, `${issuer}/`],
|
|
658
|
-
audience: [...audience],
|
|
659
622
|
algorithms: auth.algorithms ? [...auth.algorithms] : DEFAULT_JWT_ALGORITHMS,
|
|
660
623
|
requiredClaims: ["sub", "exp"],
|
|
661
624
|
clockTolerance: auth.clockToleranceSeconds ?? DEFAULT_CLOCK_TOLERANCE_SECONDS
|
|
@@ -666,6 +629,22 @@ async function verifyJwtClaims(token, keySet, issuer, audience, auth) {
|
|
|
666
629
|
throw err;
|
|
667
630
|
}
|
|
668
631
|
}
|
|
632
|
+
function checkAcceptedAudience(claims, accepted, auth) {
|
|
633
|
+
const audPopulated = claims.aud !== void 0 && !(Array.isArray(claims.aud) && claims.aud.length === 0);
|
|
634
|
+
if (audPopulated) {
|
|
635
|
+
if (stringClaimList(claims.aud).some((audience) => accepted.includes(audience)))
|
|
636
|
+
return "aud";
|
|
637
|
+
log.debug("oauth.verify.bad_audience", { tokenAud: claims.aud, accepted, outcome: "401 invalid_token" });
|
|
638
|
+
} else if (auth.acceptResourceClaim !== false) {
|
|
639
|
+
const acceptedTrimmed = accepted.map(trimTrailingSlash);
|
|
640
|
+
if (stringClaimList(claims.resource).some((resource) => acceptedTrimmed.includes(trimTrailingSlash(resource))))
|
|
641
|
+
return "resource";
|
|
642
|
+
log.debug("oauth.verify.bad_resource", { tokenResource: claims.resource, accepted, outcome: "401 invalid_token" });
|
|
643
|
+
} else {
|
|
644
|
+
log.debug("oauth.verify.empty_audience", { accepted, outcome: "401 invalid_token" });
|
|
645
|
+
}
|
|
646
|
+
throw new OAuthTokenError(401, "invalid_token", "token audience is not accepted");
|
|
647
|
+
}
|
|
669
648
|
function assertNonEmptySubject(claims) {
|
|
670
649
|
const sub = claims["sub"];
|
|
671
650
|
if (typeof sub !== "string" || sub.trim() === "") {
|
|
@@ -710,14 +689,16 @@ function createOAuthTokenVerifier(auth, discovery) {
|
|
|
710
689
|
log.debug("oauth.jwks.fetch", { jwksUri });
|
|
711
690
|
const keySet = await fetchVerificationKeySet(jwksUri);
|
|
712
691
|
assertAccessTokenTyp(token, allowedTyps);
|
|
713
|
-
const claims = await verifyJwtClaims(token, keySet, issuer,
|
|
692
|
+
const claims = await verifyJwtClaims(token, keySet, issuer, auth);
|
|
693
|
+
const audienceVia = checkAcceptedAudience(claims, acceptedAudiences, auth);
|
|
714
694
|
assertNonEmptySubject(claims);
|
|
715
695
|
const context = buildMcpAuthContext({ token, claims, issuer, resource, acceptedAudiences });
|
|
716
696
|
assertOAuthClientClaim(auth, context.principal.clientId);
|
|
717
697
|
log.info("oauth.verify.ok", {
|
|
718
698
|
sub: context.principal.sub,
|
|
719
699
|
clientId: context.principal.clientId,
|
|
720
|
-
scopes: context.principal.scopes
|
|
700
|
+
scopes: context.principal.scopes,
|
|
701
|
+
audienceVia
|
|
721
702
|
});
|
|
722
703
|
return context;
|
|
723
704
|
};
|
|
@@ -766,12 +747,6 @@ function oauthConfigurationErrorResponse() {
|
|
|
766
747
|
headers: { ...JSON_HEADERS, "Cache-Control": "no-store" }
|
|
767
748
|
});
|
|
768
749
|
}
|
|
769
|
-
function oauthUpstreamUnreachableResponse() {
|
|
770
|
-
return new Response(JSON.stringify({ error: "authorization server unreachable" }), {
|
|
771
|
-
status: 503,
|
|
772
|
-
headers: { ...JSON_HEADERS, "Cache-Control": "no-store", "Retry-After": "30" }
|
|
773
|
-
});
|
|
774
|
-
}
|
|
775
750
|
function parseBearerToken(request) {
|
|
776
751
|
const header = request.headers.get("Authorization");
|
|
777
752
|
if (!header)
|
|
@@ -844,22 +819,12 @@ function createRequestAuthorizer(mcp, options = {}) {
|
|
|
844
819
|
assertRequiredScopes(runtime.auth, auth);
|
|
845
820
|
return { ok: true, auth };
|
|
846
821
|
} catch (err) {
|
|
847
|
-
if (err instanceof OAuthUpstreamUnreachableError) {
|
|
848
|
-
log.error("auth.upstream_unreachable", { ...describeError(err), stage: err.stage, outcome: "503" });
|
|
849
|
-
await recorder?.emit({
|
|
850
|
-
tool: null,
|
|
851
|
-
method: "authorize",
|
|
852
|
-
outcome: err.stage === "jwks" ? "auth_jwks_unreachable" : "auth_discovery_unreachable",
|
|
853
|
-
durationMs: nowMs() - startedAt
|
|
854
|
-
});
|
|
855
|
-
return { ok: false, response: oauthUpstreamUnreachableResponse() };
|
|
856
|
-
}
|
|
857
822
|
if (err instanceof OAuthConfigurationError) {
|
|
858
|
-
log.error("auth.config_error", { ...describeError(err),
|
|
823
|
+
log.error("auth.config_error", { ...describeError(err), outcome: "500" });
|
|
859
824
|
await recorder?.emit({
|
|
860
825
|
tool: null,
|
|
861
826
|
method: "authorize",
|
|
862
|
-
outcome:
|
|
827
|
+
outcome: "auth_config_error",
|
|
863
828
|
durationMs: nowMs() - startedAt
|
|
864
829
|
});
|
|
865
830
|
return { ok: false, response: oauthConfigurationErrorResponse() };
|
|
@@ -938,6 +903,14 @@ var ToolContext = class {
|
|
|
938
903
|
}
|
|
939
904
|
};
|
|
940
905
|
|
|
906
|
+
// src/core/content.ts
|
|
907
|
+
function extractTextContent(content) {
|
|
908
|
+
if (!content)
|
|
909
|
+
return void 0;
|
|
910
|
+
const text = content.filter((block) => block.type === "text").map((block) => block.text).join("\n").trim();
|
|
911
|
+
return text.length > 0 ? text : void 0;
|
|
912
|
+
}
|
|
913
|
+
|
|
941
914
|
// src/core/cors.ts
|
|
942
915
|
var EXPOSE_HEADERS = "WWW-Authenticate, Mcp-Session-Id, Mcp-Protocol-Version";
|
|
943
916
|
var ALLOW_HEADERS = "Authorization, Content-Type, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID";
|
|
@@ -988,7 +961,8 @@ function adaptToolToSdkCallback(tool, auth, recorder) {
|
|
|
988
961
|
tool: tool.name,
|
|
989
962
|
method: "tools/call",
|
|
990
963
|
outcome: result.isError ? "tool_error" : "ok",
|
|
991
|
-
durationMs: nowMs() - start
|
|
964
|
+
durationMs: nowMs() - start,
|
|
965
|
+
errorText: result.isError ? extractTextContent(result.content) : void 0
|
|
992
966
|
});
|
|
993
967
|
return { content: result.content ?? [], structuredContent: result.structuredContent, isError: result.isError };
|
|
994
968
|
};
|
|
@@ -1244,7 +1218,8 @@ function createInvokeToolHandler(mcp, options = {}) {
|
|
|
1244
1218
|
tool: tool.name,
|
|
1245
1219
|
method: "tools/call",
|
|
1246
1220
|
outcome: result.isError ? "tool_error" : "ok",
|
|
1247
|
-
durationMs: nowMs() - start
|
|
1221
|
+
durationMs: nowMs() - start,
|
|
1222
|
+
errorText: result.isError ? extractTextContent(result.content) : void 0
|
|
1248
1223
|
});
|
|
1249
1224
|
return Response.json({
|
|
1250
1225
|
content: result.content ?? [],
|
|
@@ -3,23 +3,24 @@ import {
|
|
|
3
3
|
} from "../../chunk-UQK5UO6C.js";
|
|
4
4
|
import {
|
|
5
5
|
createMcpProtocolHandler
|
|
6
|
-
} from "../../chunk-
|
|
6
|
+
} from "../../chunk-CI7JYLV3.js";
|
|
7
7
|
import {
|
|
8
8
|
createOAuthProtectedResourceMetadataHandler
|
|
9
|
-
} from "../../chunk-
|
|
9
|
+
} from "../../chunk-ENJDNFEB.js";
|
|
10
10
|
import {
|
|
11
11
|
createInvokeToolHandler
|
|
12
|
-
} from "../../chunk-
|
|
12
|
+
} from "../../chunk-QPKPYDPB.js";
|
|
13
13
|
import {
|
|
14
14
|
createListToolsHandler
|
|
15
|
-
} from "../../chunk-
|
|
15
|
+
} from "../../chunk-LFXK3FNF.js";
|
|
16
|
+
import "../../chunk-6QFZYYUV.js";
|
|
16
17
|
import "../../chunk-MA5H6PSF.js";
|
|
17
18
|
import {
|
|
18
19
|
createRecorderForRuntime
|
|
19
|
-
} from "../../chunk-
|
|
20
|
+
} from "../../chunk-YLU2JTHL.js";
|
|
20
21
|
import "../../chunk-H37EB22A.js";
|
|
21
22
|
import "../../chunk-6DXGZZA4.js";
|
|
22
|
-
import "../../chunk-
|
|
23
|
+
import "../../chunk-E3Q3FRKQ.js";
|
|
23
24
|
|
|
24
25
|
// src/stacks/tanstack/handlers.ts
|
|
25
26
|
var STACK = "tanstack";
|
|
@@ -10,6 +10,8 @@ type JwtClaims = Record<string, unknown> & {
|
|
|
10
10
|
scope?: string;
|
|
11
11
|
client_id?: string;
|
|
12
12
|
email?: string;
|
|
13
|
+
/** RFC 8707 resource indicator(s) the token was granted for (Lovable OAuth convention). */
|
|
14
|
+
resource?: string | string[];
|
|
13
15
|
};
|
|
14
16
|
/**
|
|
15
17
|
* The safe-to-surface half of the auth context, reachable by tools only through
|
|
@@ -60,6 +62,14 @@ interface McpAuthConfig {
|
|
|
60
62
|
readonly protectedResourceMetadataUrl?: string;
|
|
61
63
|
readonly requireOAuthClientClaim?: boolean;
|
|
62
64
|
readonly acceptedAudiences?: readonly string[];
|
|
65
|
+
/**
|
|
66
|
+
* Accept a token whose RFC 8707 `resource` claim matches an accepted
|
|
67
|
+
* audience (trailing slashes ignored) when its `aud` is an empty array or
|
|
68
|
+
* absent — the shape Lovable's OAuth server mints; applies to any configured
|
|
69
|
+
* issuer. A populated `aud` is always authoritative and is never overridden.
|
|
70
|
+
* Defaults to true. Set false to require a matching `aud` (strict RFC 9068).
|
|
71
|
+
*/
|
|
72
|
+
readonly acceptResourceClaim?: boolean;
|
|
63
73
|
readonly requiredScopes?: readonly string[];
|
|
64
74
|
readonly jwksUri?: string;
|
|
65
75
|
readonly algorithms?: readonly string[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lovable.dev/mcp-js",
|
|
3
|
-
"version": "0.21.0
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Author MCP servers for Lovable apps. Declare tools with defineTool, register them in defineMcp, and a framework adapter (TanStack or Supabase Edge Functions) emits the route(s) at build time.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|