@stardeck-customer-apps/testing 0.14.0 → 0.16.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/SKILL.md +48 -4
- package/dist/index.d.mts +104 -1
- package/dist/index.d.ts +104 -1
- package/dist/index.js +355 -46
- package/dist/index.mjs +355 -46
- package/dist/next/headers-shim.js +12 -0
- package/dist/next/headers-shim.mjs +12 -0
- package/dist/setup.js +245 -28
- package/dist/setup.mjs +245 -28
- package/package.json +10 -4
package/dist/setup.mjs
CHANGED
|
@@ -6,6 +6,23 @@ function globalSingleton(key, create) {
|
|
|
6
6
|
return holder[symbol];
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
// src/constants.ts
|
|
10
|
+
var TEST_DOMAIN_SUFFIX = ".stardeck.test";
|
|
11
|
+
var CONTROL_PLANE_TEST_URL = "https://control-plane.stardeck.test";
|
|
12
|
+
var DATA_STORE_TEST_HOST = "db.stardeck.test";
|
|
13
|
+
var DATA_STORE_TEST_URL = `postgresql://test:test@${DATA_STORE_TEST_HOST}/main`;
|
|
14
|
+
var STORAGE_TEST_URL = "https://storage.stardeck.test";
|
|
15
|
+
var STORAGE_TEST_HOST = "storage.stardeck.test";
|
|
16
|
+
var APP_TEST_ORIGIN = "http://localhost:3333";
|
|
17
|
+
var TEST_ENV_DEFAULTS = {
|
|
18
|
+
CONTROL_PLANE_URL: CONTROL_PLANE_TEST_URL,
|
|
19
|
+
DEPLOYMENT_SECRET: "stardeck-test-deployment-secret",
|
|
20
|
+
ORGANIZATION_ID: "00000000-0000-4000-8000-00000000000a",
|
|
21
|
+
PROJECT_ID: "00000000-0000-4000-8000-00000000000b",
|
|
22
|
+
DEPLOYMENT_ID: "00000000-0000-4000-8000-00000000000c",
|
|
23
|
+
STORAGE_URL: STORAGE_TEST_URL
|
|
24
|
+
};
|
|
25
|
+
|
|
9
26
|
// src/state.ts
|
|
10
27
|
var state = globalSingleton("state", () => ({
|
|
11
28
|
db: null,
|
|
@@ -17,6 +34,10 @@ var state = globalSingleton("state", () => ({
|
|
|
17
34
|
emailCounter: 0,
|
|
18
35
|
identities: /* @__PURE__ */ new Map(),
|
|
19
36
|
identityLinks: [],
|
|
37
|
+
identityClaims: /* @__PURE__ */ new Map(),
|
|
38
|
+
identityStaffAssistEnabled: false,
|
|
39
|
+
identitySuppressClaimDelivery: false,
|
|
40
|
+
identityAllowedReturnOrigins: /* @__PURE__ */ new Set([APP_TEST_ORIGIN]),
|
|
20
41
|
checkouts: [],
|
|
21
42
|
checkoutCounter: 0,
|
|
22
43
|
sessionStatuses: /* @__PURE__ */ new Map(),
|
|
@@ -44,6 +65,9 @@ var state = globalSingleton("state", () => ({
|
|
|
44
65
|
edgeBindings: /* @__PURE__ */ new Map(),
|
|
45
66
|
edgeCapabilities: null,
|
|
46
67
|
edgePeripheralCapabilities: /* @__PURE__ */ new Map(),
|
|
68
|
+
starlensCalls: [],
|
|
69
|
+
starlensReplies: [],
|
|
70
|
+
starlensDefaultReply: null,
|
|
47
71
|
allowNetwork: false
|
|
48
72
|
}));
|
|
49
73
|
function requireDb() {
|
|
@@ -55,22 +79,6 @@ function requireDb() {
|
|
|
55
79
|
return state.db;
|
|
56
80
|
}
|
|
57
81
|
|
|
58
|
-
// src/constants.ts
|
|
59
|
-
var TEST_DOMAIN_SUFFIX = ".stardeck.test";
|
|
60
|
-
var CONTROL_PLANE_TEST_URL = "https://control-plane.stardeck.test";
|
|
61
|
-
var DATA_STORE_TEST_HOST = "db.stardeck.test";
|
|
62
|
-
var DATA_STORE_TEST_URL = `postgresql://test:test@${DATA_STORE_TEST_HOST}/main`;
|
|
63
|
-
var STORAGE_TEST_URL = "https://storage.stardeck.test";
|
|
64
|
-
var STORAGE_TEST_HOST = "storage.stardeck.test";
|
|
65
|
-
var TEST_ENV_DEFAULTS = {
|
|
66
|
-
CONTROL_PLANE_URL: CONTROL_PLANE_TEST_URL,
|
|
67
|
-
DEPLOYMENT_SECRET: "stardeck-test-deployment-secret",
|
|
68
|
-
ORGANIZATION_ID: "00000000-0000-4000-8000-00000000000a",
|
|
69
|
-
PROJECT_ID: "00000000-0000-4000-8000-00000000000b",
|
|
70
|
-
DEPLOYMENT_ID: "00000000-0000-4000-8000-00000000000c",
|
|
71
|
-
STORAGE_URL: STORAGE_TEST_URL
|
|
72
|
-
};
|
|
73
|
-
|
|
74
82
|
// src/simulator/hmac.ts
|
|
75
83
|
import crypto from "crypto";
|
|
76
84
|
var TIMESTAMP_TOLERANCE_SECONDS = 300;
|
|
@@ -121,8 +129,8 @@ function json(body, status = 200) {
|
|
|
121
129
|
function success(data) {
|
|
122
130
|
return json({ success: true, data });
|
|
123
131
|
}
|
|
124
|
-
function failure(error, status = 400) {
|
|
125
|
-
return json({ success: false, error }, status);
|
|
132
|
+
function failure(error, status = 400, code) {
|
|
133
|
+
return json({ success: false, error, ...code ? { code } : {} }, status);
|
|
126
134
|
}
|
|
127
135
|
async function readJsonBody(request) {
|
|
128
136
|
try {
|
|
@@ -599,6 +607,12 @@ async function handleEmailSend(request) {
|
|
|
599
607
|
import crypto3 from "crypto";
|
|
600
608
|
var ATTACH_LINK_KINDS = /* @__PURE__ */ new Set(["line", "facebook", "instagram", "email", "phone"]);
|
|
601
609
|
var RESOLVE_KINDS = /* @__PURE__ */ new Set(["phone", "email", "line"]);
|
|
610
|
+
var CLAIM_KINDS = /* @__PURE__ */ new Set(["email", "phone"]);
|
|
611
|
+
var CLAIM_FLOWS = /* @__PURE__ */ new Set(["invite", "assist", "transfer"]);
|
|
612
|
+
var DUPLICABLE_UNVERIFIED_KINDS = /* @__PURE__ */ new Set(["email", "phone"]);
|
|
613
|
+
var CLAIM_TTL_MS = 48 * 60 * 60 * 1e3;
|
|
614
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
615
|
+
var STAFF_ASSISTANCE_NOT_ENABLED = "STAFF_ASSISTANCE_NOT_ENABLED";
|
|
602
616
|
var E164_PHONE = /^\+[1-9]\d{1,14}$/;
|
|
603
617
|
var SEARCH_DEFAULT_LIMIT = 50;
|
|
604
618
|
var SEARCH_MAX_LIMIT = 100;
|
|
@@ -681,6 +695,11 @@ function matchingLinks(kind, externalId) {
|
|
|
681
695
|
(link) => link.kind === kind && comparableExternalId(link.kind, link.externalId) === comparable
|
|
682
696
|
);
|
|
683
697
|
}
|
|
698
|
+
function sortedMatchingLinks(kind, externalId) {
|
|
699
|
+
return matchingLinks(kind, externalId).sort(
|
|
700
|
+
(a, b) => Number(b.verified) - Number(a.verified) || a.createdAt.localeCompare(b.createdAt) || state.identityLinks.indexOf(a) - state.identityLinks.indexOf(b)
|
|
701
|
+
);
|
|
702
|
+
}
|
|
684
703
|
function createPerson(displayName) {
|
|
685
704
|
const createdAt = now();
|
|
686
705
|
const identity = {
|
|
@@ -791,9 +810,10 @@ async function handleAttachLink(identityId, request) {
|
|
|
791
810
|
);
|
|
792
811
|
if ("error" in normalized) return failure(normalized.error);
|
|
793
812
|
const { kind, externalId } = normalized;
|
|
794
|
-
const
|
|
795
|
-
|
|
796
|
-
|
|
813
|
+
const matches = matchingLinks(kind, externalId);
|
|
814
|
+
const ownRow = matches.find((link2) => link2.identityId === identityId);
|
|
815
|
+
if (ownRow) return success({ link: ownRow });
|
|
816
|
+
if (!DUPLICABLE_UNVERIFIED_KINDS.has(kind) && matches.length > 0) {
|
|
797
817
|
return failure("identifier already linked to another identity", 409);
|
|
798
818
|
}
|
|
799
819
|
const link = createUnverifiedLink(identityId, kind, externalId);
|
|
@@ -810,7 +830,7 @@ async function handleResolve(request) {
|
|
|
810
830
|
if (body.displayName !== void 0 && typeof body.displayName !== "string") {
|
|
811
831
|
return failure("displayName must be a string");
|
|
812
832
|
}
|
|
813
|
-
const links =
|
|
833
|
+
const links = sortedMatchingLinks(normalized.kind, normalized.externalId);
|
|
814
834
|
if (links.length > 0) {
|
|
815
835
|
const canonicalIds = /* @__PURE__ */ new Set();
|
|
816
836
|
for (const link of links) {
|
|
@@ -821,12 +841,15 @@ async function handleResolve(request) {
|
|
|
821
841
|
return failure(`cannot resolve identity link: ${message}`, 409);
|
|
822
842
|
}
|
|
823
843
|
}
|
|
824
|
-
|
|
844
|
+
const verifiedOwners = new Set(
|
|
845
|
+
links.filter((link) => link.verified).map((link) => link.identityId)
|
|
846
|
+
);
|
|
847
|
+
if (verifiedOwners.size > 1 || !DUPLICABLE_UNVERIFIED_KINDS.has(normalized.kind) && canonicalIds.size !== 1) {
|
|
825
848
|
return failure("identifier is linked to multiple identities", 409);
|
|
826
849
|
}
|
|
827
|
-
const
|
|
828
|
-
const
|
|
829
|
-
if (provenance === "guest" &&
|
|
850
|
+
const [chosen] = links;
|
|
851
|
+
const canonicalId = canonicalIdentity(chosen.identityId).id;
|
|
852
|
+
if (provenance === "guest" && chosen.verified) {
|
|
830
853
|
return success({ identityId: null, reason: "verified_conflict" });
|
|
831
854
|
}
|
|
832
855
|
return success({ identityId: canonicalId, created: false });
|
|
@@ -928,6 +951,116 @@ async function handleAliases(request) {
|
|
|
928
951
|
}
|
|
929
952
|
return success({ aliases });
|
|
930
953
|
}
|
|
954
|
+
function claimView(claim) {
|
|
955
|
+
const state_ = claim.state === "pending" && Date.parse(claim.expiresAt) <= Date.now() ? "expired" : claim.state;
|
|
956
|
+
return {
|
|
957
|
+
claimId: claim.claimId,
|
|
958
|
+
flow: claim.flow,
|
|
959
|
+
state: state_,
|
|
960
|
+
refusalReason: claim.refusalReason,
|
|
961
|
+
completedIdentityId: claim.completedIdentityId,
|
|
962
|
+
expiresAt: claim.expiresAt
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
function validateClaimStaff(flow, staff) {
|
|
966
|
+
if (flow === "invite") {
|
|
967
|
+
if (staff !== void 0) return { error: "staff is only accepted for assist or transfer" };
|
|
968
|
+
return { staffActorRef: null };
|
|
969
|
+
}
|
|
970
|
+
if (!staff || typeof staff !== "object") {
|
|
971
|
+
return { error: `staff is required for a ${flow} claim` };
|
|
972
|
+
}
|
|
973
|
+
const { actorRef, grant } = staff;
|
|
974
|
+
if (typeof actorRef !== "string" || actorRef.length === 0) {
|
|
975
|
+
return { error: "staff.actorRef is required" };
|
|
976
|
+
}
|
|
977
|
+
if (grant !== flow) return { error: `staff.grant must be '${flow}' for a ${flow} claim` };
|
|
978
|
+
return { staffActorRef: actorRef };
|
|
979
|
+
}
|
|
980
|
+
async function handleCreateClaim(request) {
|
|
981
|
+
const body = await readJsonBody(request);
|
|
982
|
+
const flow = body.flow;
|
|
983
|
+
if (typeof flow !== "string" || !CLAIM_FLOWS.has(flow)) {
|
|
984
|
+
return failure("flow must be one of: invite, assist, transfer");
|
|
985
|
+
}
|
|
986
|
+
if (typeof body.identityId !== "string" || !UUID.test(body.identityId)) {
|
|
987
|
+
return failure("identityId must be a uuid");
|
|
988
|
+
}
|
|
989
|
+
const normalized = normalizeLinkInput(
|
|
990
|
+
body.kind,
|
|
991
|
+
body.externalId,
|
|
992
|
+
CLAIM_KINDS,
|
|
993
|
+
"kind must be one of: email, phone"
|
|
994
|
+
);
|
|
995
|
+
if ("error" in normalized) return failure(normalized.error);
|
|
996
|
+
let returnTo = null;
|
|
997
|
+
if (body.returnTo !== void 0) {
|
|
998
|
+
if (typeof body.returnTo !== "string") return failure("returnTo must be a string");
|
|
999
|
+
let parsed;
|
|
1000
|
+
try {
|
|
1001
|
+
parsed = new URL(body.returnTo);
|
|
1002
|
+
} catch {
|
|
1003
|
+
return failure("returnTo must be an absolute URL");
|
|
1004
|
+
}
|
|
1005
|
+
returnTo = state.identityAllowedReturnOrigins.has(parsed.origin) ? body.returnTo : null;
|
|
1006
|
+
}
|
|
1007
|
+
if (body.deliver !== void 0 && typeof body.deliver !== "boolean") {
|
|
1008
|
+
return failure("deliver must be a boolean");
|
|
1009
|
+
}
|
|
1010
|
+
const staff = validateClaimStaff(flow, body.staff);
|
|
1011
|
+
if ("error" in staff) return failure(staff.error);
|
|
1012
|
+
if (flow !== "invite" && !state.identityStaffAssistEnabled) {
|
|
1013
|
+
return failure(
|
|
1014
|
+
"staff-assisted profile claiming is not enabled for this project",
|
|
1015
|
+
403,
|
|
1016
|
+
STAFF_ASSISTANCE_NOT_ENABLED
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
const identity = state.identities.get(body.identityId);
|
|
1020
|
+
if (!identity || identity.type !== "person" || identity.status !== "active") {
|
|
1021
|
+
return failure("identity not found", 404);
|
|
1022
|
+
}
|
|
1023
|
+
const holdsContact = matchingLinks(normalized.kind, normalized.externalId).some(
|
|
1024
|
+
(link) => link.identityId === identity.id
|
|
1025
|
+
);
|
|
1026
|
+
if (!holdsContact) {
|
|
1027
|
+
return failure("identity does not hold that contact", 409);
|
|
1028
|
+
}
|
|
1029
|
+
const claimId = crypto3.randomUUID();
|
|
1030
|
+
const deliver = body.deliver === true;
|
|
1031
|
+
const handle = crypto3.randomBytes(32).toString("base64url");
|
|
1032
|
+
const claim = {
|
|
1033
|
+
claimId,
|
|
1034
|
+
flow,
|
|
1035
|
+
identityId: identity.id,
|
|
1036
|
+
kind: normalized.kind,
|
|
1037
|
+
externalId: normalized.externalId,
|
|
1038
|
+
returnTo,
|
|
1039
|
+
staffActorRef: staff.staffActorRef,
|
|
1040
|
+
state: "pending",
|
|
1041
|
+
refusalReason: null,
|
|
1042
|
+
completedIdentityId: null,
|
|
1043
|
+
claimantClerkUserId: null,
|
|
1044
|
+
claimUrl: `${CONTROL_PLANE_TEST_URL}/claim/${handle}`,
|
|
1045
|
+
delivered: deliver ? !state.identitySuppressClaimDelivery : null,
|
|
1046
|
+
expiresAt: new Date(Date.now() + CLAIM_TTL_MS).toISOString(),
|
|
1047
|
+
createdAt: now()
|
|
1048
|
+
};
|
|
1049
|
+
state.identityClaims.set(claimId, claim);
|
|
1050
|
+
return success({
|
|
1051
|
+
claimId,
|
|
1052
|
+
claimUrl: claim.claimUrl,
|
|
1053
|
+
expiresAt: claim.expiresAt,
|
|
1054
|
+
// Absent unless asked for: an app delivering its own link must not read a
|
|
1055
|
+
// missing field as "nothing was sent".
|
|
1056
|
+
...deliver ? { delivered: claim.delivered } : {}
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
function handleGetClaim(claimId) {
|
|
1060
|
+
const claim = state.identityClaims.get(claimId);
|
|
1061
|
+
if (!claim) return failure("claim not found", 404);
|
|
1062
|
+
return success(claimView(claim));
|
|
1063
|
+
}
|
|
931
1064
|
async function handleIdentitiesRequest(request, subPath) {
|
|
932
1065
|
const method = request.method;
|
|
933
1066
|
if (subPath === "" || subPath === "/") {
|
|
@@ -943,6 +1076,13 @@ async function handleIdentitiesRequest(request, subPath) {
|
|
|
943
1076
|
if (subPath === "/search" && method === "GET") {
|
|
944
1077
|
return handleSearch(request);
|
|
945
1078
|
}
|
|
1079
|
+
if (subPath === "/claims" && method === "POST") {
|
|
1080
|
+
return handleCreateClaim(request);
|
|
1081
|
+
}
|
|
1082
|
+
const claimMatch = subPath.match(/^\/claims\/([^/]+)$/);
|
|
1083
|
+
if (claimMatch && method === "GET") {
|
|
1084
|
+
return handleGetClaim(decodeURIComponent(claimMatch[1]));
|
|
1085
|
+
}
|
|
946
1086
|
const linksMatch = subPath.match(/^\/([^/]+)\/links$/);
|
|
947
1087
|
if (linksMatch && method === "POST") {
|
|
948
1088
|
return handleAttachLink(linksMatch[1], request);
|
|
@@ -1976,6 +2116,77 @@ async function handleEdgeRequest(request, subPath) {
|
|
|
1976
2116
|
return edgeFailure(`No edge simulator for ${method} .../edge${subPath}`, 404);
|
|
1977
2117
|
}
|
|
1978
2118
|
|
|
2119
|
+
// src/simulator/starlens.ts
|
|
2120
|
+
function starlensFailure(error, status, code) {
|
|
2121
|
+
return json({ success: false, error, code }, status);
|
|
2122
|
+
}
|
|
2123
|
+
function nullFlagged(output, issues) {
|
|
2124
|
+
const copy = JSON.parse(JSON.stringify(output ?? null));
|
|
2125
|
+
for (const { path } of issues) {
|
|
2126
|
+
const keys = path.replace(/^output\./, "").replace(/\[(\d+)\]/g, ".$1").split(".");
|
|
2127
|
+
const last = keys.pop();
|
|
2128
|
+
const parent = keys.reduce(
|
|
2129
|
+
(o, k) => o != null && typeof o === "object" ? o[k] : void 0,
|
|
2130
|
+
copy
|
|
2131
|
+
);
|
|
2132
|
+
if (parent != null && typeof parent === "object" && Object.prototype.hasOwnProperty.call(parent, last)) {
|
|
2133
|
+
parent[last] = null;
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
return copy;
|
|
2137
|
+
}
|
|
2138
|
+
async function handleStarlensExtract(request, signedDeploymentId) {
|
|
2139
|
+
const body = await readJsonBody(request);
|
|
2140
|
+
if (signedDeploymentId !== null && body.deploymentId !== signedDeploymentId) {
|
|
2141
|
+
return starlensFailure("Deployment ID mismatch", 401, "AUTHENTICATION_ERROR");
|
|
2142
|
+
}
|
|
2143
|
+
const inputs = Array.isArray(body.inputs) ? body.inputs : [];
|
|
2144
|
+
if (inputs.length === 0) {
|
|
2145
|
+
return starlensFailure(
|
|
2146
|
+
"Validation failed \u2014 inputs: at least one input",
|
|
2147
|
+
400,
|
|
2148
|
+
"VALIDATION_ERROR"
|
|
2149
|
+
);
|
|
2150
|
+
}
|
|
2151
|
+
const schema = body.schema;
|
|
2152
|
+
if (!schema || schema.type !== "object" || typeof schema.properties !== "object") {
|
|
2153
|
+
return starlensFailure(
|
|
2154
|
+
"Validation failed \u2014 schema.properties is required",
|
|
2155
|
+
400,
|
|
2156
|
+
"VALIDATION_ERROR"
|
|
2157
|
+
);
|
|
2158
|
+
}
|
|
2159
|
+
const call = {
|
|
2160
|
+
version: typeof body.version === "string" ? body.version : "latest",
|
|
2161
|
+
inputs: inputs.map((i) => ({
|
|
2162
|
+
name: typeof i.filename === "string" ? i.filename : typeof i.url === "string" ? i.url : "upload",
|
|
2163
|
+
mediaType: typeof i.mediaType === "string" ? i.mediaType : null,
|
|
2164
|
+
source: typeof i.url === "string" ? "url" : "data"
|
|
2165
|
+
})),
|
|
2166
|
+
schema,
|
|
2167
|
+
instructions: typeof body.instructions === "string" ? body.instructions : void 0
|
|
2168
|
+
};
|
|
2169
|
+
state.starlensCalls.push(call);
|
|
2170
|
+
const reply = state.starlensReplies.shift() ?? state.starlensDefaultReply;
|
|
2171
|
+
if (!reply) {
|
|
2172
|
+
return starlensFailure(
|
|
2173
|
+
"[stardeck-testing] No Starlens reply registered. Call app.starlens.mock({ output, issues }) or app.starlens.reject(reason) before the request.",
|
|
2174
|
+
502,
|
|
2175
|
+
"model_failed"
|
|
2176
|
+
);
|
|
2177
|
+
}
|
|
2178
|
+
if ("reject" in reply) {
|
|
2179
|
+
return starlensFailure(reply.reject, 422, "unsupported_document");
|
|
2180
|
+
}
|
|
2181
|
+
const issues = reply.issues ?? [];
|
|
2182
|
+
return success({
|
|
2183
|
+
version: call.version === "latest" ? "v1" : call.version,
|
|
2184
|
+
output: nullFlagged(reply.output, issues),
|
|
2185
|
+
issues,
|
|
2186
|
+
usage: { inputTokens: 0, outputTokens: 0, cost: 0 }
|
|
2187
|
+
});
|
|
2188
|
+
}
|
|
2189
|
+
|
|
1979
2190
|
// src/simulator/router.ts
|
|
1980
2191
|
var fetchHolder = globalSingleton("fetch-holder", () => ({
|
|
1981
2192
|
originalFetch: null
|
|
@@ -1997,7 +2208,8 @@ function requiresDeploymentHmac(request, url) {
|
|
|
1997
2208
|
/^\/api\/deployments\/[^/]+\/integrations\/(slack|line|facebook)(\/.*)?$/
|
|
1998
2209
|
);
|
|
1999
2210
|
const edgeMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/edge(\/.*)?$/);
|
|
2000
|
-
|
|
2211
|
+
const isStarlens = url.pathname === "/api/starlens/extract";
|
|
2212
|
+
return !!(isStarlens || dataStoreMatch || isEmail || identitiesMatch || storeMatch || messagingMatch || edgeMatch);
|
|
2001
2213
|
}
|
|
2002
2214
|
function deploymentIdFromPath(url) {
|
|
2003
2215
|
const match = url.pathname.match(/\/api\/deployments\/([^/]+)(?:\/|$)/);
|
|
@@ -2027,6 +2239,7 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2027
2239
|
);
|
|
2028
2240
|
const edgeMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/edge(\/.*)?$/);
|
|
2029
2241
|
const isStorageHost = url.hostname === STORAGE_TEST_HOST;
|
|
2242
|
+
let signedDeploymentId = null;
|
|
2030
2243
|
if (requiresDeploymentHmac(request, url)) {
|
|
2031
2244
|
const authHeader = request.headers.get("X-Stardeck-Auth");
|
|
2032
2245
|
if (!authHeader) {
|
|
@@ -2038,6 +2251,7 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2038
2251
|
if (!authPayload || pathDeploymentId !== null && authPayload.deploymentId !== pathDeploymentId) {
|
|
2039
2252
|
return failure("Invalid authentication", 401);
|
|
2040
2253
|
}
|
|
2254
|
+
signedDeploymentId = authPayload.deploymentId;
|
|
2041
2255
|
}
|
|
2042
2256
|
if (isStorageHost) {
|
|
2043
2257
|
return handleStorageRequest(request, url);
|
|
@@ -2058,6 +2272,9 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2058
2272
|
if (edgeMatch) {
|
|
2059
2273
|
return handleEdgeRequest(request, edgeMatch[1] ?? "");
|
|
2060
2274
|
}
|
|
2275
|
+
if (url.pathname === "/api/starlens/extract" && request.method === "POST") {
|
|
2276
|
+
return handleStarlensExtract(request, signedDeploymentId);
|
|
2277
|
+
}
|
|
2061
2278
|
if (dataStoreMatch) {
|
|
2062
2279
|
const subPath = dataStoreMatch[1] ?? "";
|
|
2063
2280
|
const db = requireDb();
|
|
@@ -2079,7 +2296,7 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2079
2296
|
}
|
|
2080
2297
|
}
|
|
2081
2298
|
return failure(
|
|
2082
|
-
`[stardeck-testing] No simulator for ${request.method} ${url.pathname}. Supported: data-store query/mutate/schema, email send, identities CRUD, payments store/checkout, storage upload/files, messaging send, edge print/display/bindings, auth verify/refresh, Neon /sql.`,
|
|
2299
|
+
`[stardeck-testing] No simulator for ${request.method} ${url.pathname}. Supported: data-store query/mutate/schema, email send, identities CRUD, payments store/checkout, storage upload/files, messaging send, edge print/display/bindings, starlens extract, auth verify/refresh, Neon /sql.`,
|
|
2083
2300
|
404
|
|
2084
2301
|
);
|
|
2085
2302
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stardeck-customer-apps/testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Vitest test harness for Stardeck customer apps — in-process Postgres (PGlite) plus a control-plane simulator so the real Stardeck SDKs run unmodified in tests",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -66,11 +66,12 @@
|
|
|
66
66
|
},
|
|
67
67
|
"peerDependencies": {
|
|
68
68
|
"@stardeck-customer-apps/edge-sdk": ">=1.4.0",
|
|
69
|
-
"@stardeck-customer-apps/integrations-sdk": ">=1.
|
|
69
|
+
"@stardeck-customer-apps/integrations-sdk": ">=1.16.0",
|
|
70
70
|
"@stardeck-customer-apps/payments-sdk": ">=0.1.0",
|
|
71
71
|
"@stardeck-customer-apps/storage-sdk": ">=0.1.0",
|
|
72
72
|
"next": "^14.0.0 || ^15.0.0 || ^16.0.0",
|
|
73
|
-
"vitest": ">=2.0.0"
|
|
73
|
+
"vitest": ">=2.0.0",
|
|
74
|
+
"@stardeck-customer-apps/starlens": ">=0.1.0"
|
|
74
75
|
},
|
|
75
76
|
"peerDependenciesMeta": {
|
|
76
77
|
"@stardeck-customer-apps/integrations-sdk": {
|
|
@@ -87,6 +88,9 @@
|
|
|
87
88
|
},
|
|
88
89
|
"next": {
|
|
89
90
|
"optional": true
|
|
91
|
+
},
|
|
92
|
+
"@stardeck-customer-apps/starlens": {
|
|
93
|
+
"optional": true
|
|
90
94
|
}
|
|
91
95
|
},
|
|
92
96
|
"devDependencies": {
|
|
@@ -106,6 +110,8 @@
|
|
|
106
110
|
"tsup": "^8.0.0",
|
|
107
111
|
"typescript": "^5.0.0",
|
|
108
112
|
"typescript-eslint": "^8.0.0",
|
|
109
|
-
"vitest": "^3.2.4"
|
|
113
|
+
"vitest": "^3.2.4",
|
|
114
|
+
"@stardeck-customer-apps/starlens": "*",
|
|
115
|
+
"zod": "^4.3.5"
|
|
110
116
|
}
|
|
111
117
|
}
|