@stardeck-customer-apps/testing 0.19.0 → 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/SKILL.md +26 -21
- package/dist/index.d.mts +13 -4
- package/dist/index.d.ts +13 -4
- package/dist/index.js +297 -54
- package/dist/index.mjs +283 -30
- package/dist/setup.js +53 -7
- package/dist/setup.mjs +53 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -48,9 +48,9 @@ __export(index_exports, {
|
|
|
48
48
|
module.exports = __toCommonJS(index_exports);
|
|
49
49
|
|
|
50
50
|
// src/test-app.ts
|
|
51
|
-
var
|
|
52
|
-
var
|
|
53
|
-
var
|
|
51
|
+
var import_node_crypto9 = __toESM(require("crypto"));
|
|
52
|
+
var import_node_fs2 = require("fs");
|
|
53
|
+
var import_node_path2 = require("path");
|
|
54
54
|
|
|
55
55
|
// src/pglite/database.ts
|
|
56
56
|
var import_pglite = require("@electric-sql/pglite");
|
|
@@ -58,10 +58,10 @@ var import_citext = require("@electric-sql/pglite/contrib/citext");
|
|
|
58
58
|
var import_pg_trgm = require("@electric-sql/pglite/contrib/pg_trgm");
|
|
59
59
|
var import_pgcrypto = require("@electric-sql/pglite/contrib/pgcrypto");
|
|
60
60
|
var import_uuid_ossp = require("@electric-sql/pglite/contrib/uuid_ossp");
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
});
|
|
61
|
+
var EXTENSIONS = { pg_trgm: import_pg_trgm.pg_trgm, pgcrypto: import_pgcrypto.pgcrypto, uuid_ossp: import_uuid_ossp.uuid_ossp, citext: import_citext.citext };
|
|
62
|
+
var PGLITE_EXTENSION_NAMES = Object.keys(EXTENSIONS);
|
|
63
|
+
async function createPgliteDatabase(options = {}) {
|
|
64
|
+
const db = new import_pglite.PGlite({ extensions: EXTENSIONS, ...options });
|
|
65
65
|
await db.waitReady;
|
|
66
66
|
return db;
|
|
67
67
|
}
|
|
@@ -82,10 +82,6 @@ async function listPublicTables(db) {
|
|
|
82
82
|
`);
|
|
83
83
|
return rows.map(({ name }) => name);
|
|
84
84
|
}
|
|
85
|
-
async function truncateAllTables(db) {
|
|
86
|
-
const tables = await listPublicTables(db);
|
|
87
|
-
if (tables.length) await db.exec(`TRUNCATE TABLE ${tables.join(", ")} RESTART IDENTITY`);
|
|
88
|
-
}
|
|
89
85
|
var identityParsers = null;
|
|
90
86
|
function getIdentityParsers() {
|
|
91
87
|
if (!identityParsers) {
|
|
@@ -98,6 +94,198 @@ function getIdentityParsers() {
|
|
|
98
94
|
return identityParsers;
|
|
99
95
|
}
|
|
100
96
|
|
|
97
|
+
// src/pglite/baseline.ts
|
|
98
|
+
var HARNESS_SCHEMA = "stardeck_test";
|
|
99
|
+
var SCHEMA_CHANGES = `${HARNESS_SCHEMA}.schema_changes`;
|
|
100
|
+
var SCHEMA_CHANGE_TRACKING_SQL = `
|
|
101
|
+
CREATE SCHEMA IF NOT EXISTS ${HARNESS_SCHEMA};
|
|
102
|
+
CREATE TABLE IF NOT EXISTS ${SCHEMA_CHANGES} ();
|
|
103
|
+
CREATE OR REPLACE FUNCTION ${HARNESS_SCHEMA}.mark_schema_changed() RETURNS event_trigger
|
|
104
|
+
LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog
|
|
105
|
+
AS $$ BEGIN INSERT INTO ${SCHEMA_CHANGES} DEFAULT VALUES; END $$;
|
|
106
|
+
DROP EVENT TRIGGER IF EXISTS stardeck_test_ddl;
|
|
107
|
+
DROP EVENT TRIGGER IF EXISTS stardeck_test_drop;
|
|
108
|
+
CREATE EVENT TRIGGER stardeck_test_ddl ON ddl_command_end
|
|
109
|
+
EXECUTE FUNCTION ${HARNESS_SCHEMA}.mark_schema_changed();
|
|
110
|
+
CREATE EVENT TRIGGER stardeck_test_drop ON sql_drop
|
|
111
|
+
EXECUTE FUNCTION ${HARNESS_SCHEMA}.mark_schema_changed();
|
|
112
|
+
ALTER EVENT TRIGGER stardeck_test_ddl ENABLE ALWAYS;
|
|
113
|
+
ALTER EVENT TRIGGER stardeck_test_drop ENABLE ALWAYS;
|
|
114
|
+
`;
|
|
115
|
+
async function schemaChangedSinceBaseline(db) {
|
|
116
|
+
const { rows } = await db.query(
|
|
117
|
+
`SELECT EXISTS (SELECT FROM ${SCHEMA_CHANGES}) AS changed`
|
|
118
|
+
);
|
|
119
|
+
return rows[0]?.changed === true;
|
|
120
|
+
}
|
|
121
|
+
async function acceptSchemaChanges(db) {
|
|
122
|
+
await db.exec(`DELETE FROM ${SCHEMA_CHANGES};`);
|
|
123
|
+
}
|
|
124
|
+
async function sequenceStates(db) {
|
|
125
|
+
const { rows: sequences } = await db.query(`
|
|
126
|
+
SELECT format('%I.%I', n.nspname, c.relname) AS name
|
|
127
|
+
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
128
|
+
WHERE n.nspname = 'public' AND c.relkind = 'S'
|
|
129
|
+
`);
|
|
130
|
+
if (!sequences.length) return null;
|
|
131
|
+
const { rows } = await db.query(
|
|
132
|
+
sequences.map(
|
|
133
|
+
({ name }) => `SELECT ${quoteLiteral(name)} AS name, last_value::text, is_called FROM ${name}`
|
|
134
|
+
).join(" UNION ALL ")
|
|
135
|
+
);
|
|
136
|
+
return `SELECT ${rows.map((s) => `setval(${quoteLiteral(s.name)}, ${s.last_value}, ${s.is_called})`).join(", ")};`;
|
|
137
|
+
}
|
|
138
|
+
function quoteLiteral(value) {
|
|
139
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
140
|
+
}
|
|
141
|
+
async function captureBaseline(db) {
|
|
142
|
+
const { rows: unsilenceable } = await db.query(`
|
|
143
|
+
SELECT FROM pg_trigger t JOIN pg_class c ON c.oid = t.tgrelid
|
|
144
|
+
WHERE c.relnamespace = 'public'::regnamespace AND NOT t.tgisinternal
|
|
145
|
+
AND t.tgenabled IN ('A', 'R')`);
|
|
146
|
+
if (unsilenceable.length) return null;
|
|
147
|
+
const tables = await listPublicTables(db);
|
|
148
|
+
const statements = [];
|
|
149
|
+
await db.exec(`DROP SCHEMA IF EXISTS ${HARNESS_SCHEMA}_baseline CASCADE;
|
|
150
|
+
CREATE SCHEMA ${HARNESS_SCHEMA}_baseline;`);
|
|
151
|
+
if (tables.length) {
|
|
152
|
+
const { rows: populated } = await db.query(
|
|
153
|
+
tables.map(
|
|
154
|
+
(table, index) => `SELECT ${index} AS index, (
|
|
155
|
+
SELECT string_agg(quote_ident(attname), ', ' ORDER BY attnum) FROM pg_attribute
|
|
156
|
+
WHERE attrelid = ${quoteLiteral(table)}::regclass AND attnum > 0
|
|
157
|
+
AND NOT attisdropped AND attgenerated = ''
|
|
158
|
+
) AS columns WHERE EXISTS (SELECT 1 FROM ONLY ${table})`
|
|
159
|
+
).join(" UNION ALL ")
|
|
160
|
+
);
|
|
161
|
+
for (const { index, columns } of populated) {
|
|
162
|
+
const copy = `${HARNESS_SCHEMA}_baseline.t${index}`;
|
|
163
|
+
await db.exec(`CREATE TABLE ${copy} AS SELECT ${columns} FROM ONLY ${tables[index]};`);
|
|
164
|
+
statements.push(
|
|
165
|
+
`INSERT INTO ${tables[index]} (${columns}) OVERRIDING SYSTEM VALUE SELECT ${columns} FROM ${copy};`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const setvals = await sequenceStates(db);
|
|
170
|
+
if (setvals) statements.push(setvals);
|
|
171
|
+
return { restoreSql: statements.join("\n") };
|
|
172
|
+
}
|
|
173
|
+
async function restoreBaseline(db, baseline) {
|
|
174
|
+
const tables = await listPublicTables(db);
|
|
175
|
+
const truncate = tables.length ? `TRUNCATE TABLE ${tables.join(", ")} RESTART IDENTITY;` : "";
|
|
176
|
+
try {
|
|
177
|
+
await db.exec(`BEGIN; SET LOCAL session_replication_role = replica;
|
|
178
|
+
${truncate}
|
|
179
|
+
SELECT setval(format('%I.%I', schemaname, sequencename), start_value, false)
|
|
180
|
+
FROM pg_sequences WHERE schemaname = 'public';
|
|
181
|
+
${baseline.restoreSql}
|
|
182
|
+
COMMIT;`);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
await db.exec("ROLLBACK;").catch(() => {
|
|
185
|
+
});
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/pglite/snapshot.ts
|
|
191
|
+
var import_node_crypto = require("crypto");
|
|
192
|
+
var import_node_fs = require("fs");
|
|
193
|
+
var import_node_path = require("path");
|
|
194
|
+
var SNAPSHOT_FORMAT = 2;
|
|
195
|
+
var MAX_SNAPSHOTS = 8;
|
|
196
|
+
function snapshotKey(schemaSql) {
|
|
197
|
+
const day = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
198
|
+
return (0, import_node_crypto.createHash)("sha256").update(JSON.stringify({ format: SNAPSHOT_FORMAT, extensions: PGLITE_EXTENSION_NAMES, day })).update(SCHEMA_CHANGE_TRACKING_SQL).update(schemaSql).digest("hex").slice(0, 32);
|
|
199
|
+
}
|
|
200
|
+
function snapshotPath(schemaSql) {
|
|
201
|
+
return (0, import_node_path.resolve)(
|
|
202
|
+
process.cwd(),
|
|
203
|
+
"node_modules/.cache/stardeck-testing",
|
|
204
|
+
`${snapshotKey(schemaSql)}.tar`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
var CUSTOM_SETTING = /\bset_config\s*\(\s*(?:'([^']*)'|(\$\w*\$)([\s\S]*?)\2)|\bSET\s+(?:SESSION\s+)?(?:"([^"]*)"|([a-z_][\w$]*\.[\w$.]+))/gi;
|
|
208
|
+
async function sessionSettings(db, schemaSql) {
|
|
209
|
+
const custom = [...schemaSql.matchAll(CUSTOM_SETTING)].map(
|
|
210
|
+
([, quoted, , dollarQuoted, identifier, bare]) => quoted ?? dollarQuoted ?? identifier ?? bare
|
|
211
|
+
).filter((name) => name?.includes("."));
|
|
212
|
+
const { rows } = await db.query(
|
|
213
|
+
`SELECT string_agg(name || '=' || setting, ',' ORDER BY name) AS settings FROM (
|
|
214
|
+
SELECT name, setting FROM pg_settings WHERE source = 'session'
|
|
215
|
+
UNION ALL
|
|
216
|
+
SELECT name, current_setting(name, true) FROM unnest($1::text[]) AS name
|
|
217
|
+
WHERE current_setting(name, true) IS NOT NULL
|
|
218
|
+
) settings`,
|
|
219
|
+
[custom]
|
|
220
|
+
);
|
|
221
|
+
return rows[0]?.settings ?? "";
|
|
222
|
+
}
|
|
223
|
+
async function buildSchemaDatabase(schemaSql) {
|
|
224
|
+
const db = await createPgliteDatabase();
|
|
225
|
+
try {
|
|
226
|
+
await db.exec(SCHEMA_CHANGE_TRACKING_SQL);
|
|
227
|
+
const settingsBefore = await sessionSettings(db, schemaSql);
|
|
228
|
+
await applySchema(db, schemaSql);
|
|
229
|
+
const { rows: sessionBound } = await db.query(
|
|
230
|
+
`SELECT FROM pg_class WHERE relpersistence IN ('u', 't')`
|
|
231
|
+
);
|
|
232
|
+
const cacheable = !sessionBound.length && await sessionSettings(db, schemaSql) === settingsBefore;
|
|
233
|
+
await db.exec(`COMMENT ON SCHEMA stardeck_test IS '${snapshotKey(schemaSql)}'`);
|
|
234
|
+
await db.exec("CHECKPOINT");
|
|
235
|
+
return { db, cacheable };
|
|
236
|
+
} catch (error) {
|
|
237
|
+
await db.close().catch(() => {
|
|
238
|
+
});
|
|
239
|
+
throw error;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function pruneSnapshots(dir) {
|
|
243
|
+
const snapshots = (0, import_node_fs.readdirSync)(dir).filter((name) => name.endsWith(".tar")).map((name) => ({ path: (0, import_node_path.join)(dir, name), mtime: (0, import_node_fs.statSync)((0, import_node_path.join)(dir, name)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
|
|
244
|
+
for (const { path } of snapshots.slice(MAX_SNAPSHOTS)) (0, import_node_fs.rmSync)(path, { force: true });
|
|
245
|
+
}
|
|
246
|
+
async function writeSnapshot(path, db) {
|
|
247
|
+
try {
|
|
248
|
+
const data = Buffer.from(await (await db.dumpDataDir("none")).arrayBuffer());
|
|
249
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path), { recursive: true });
|
|
250
|
+
const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
251
|
+
(0, import_node_fs.writeFileSync)(tmp, data);
|
|
252
|
+
(0, import_node_fs.renameSync)(tmp, path);
|
|
253
|
+
pruneSnapshots((0, import_node_path.dirname)(path));
|
|
254
|
+
} catch (error) {
|
|
255
|
+
console.warn(`[stardeck-testing] could not cache schema snapshot at ${path}:`, error);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async function loadSnapshot(path, key) {
|
|
259
|
+
let db = null;
|
|
260
|
+
try {
|
|
261
|
+
db = await createPgliteDatabase({ loadDataDir: new Blob([(0, import_node_fs.readFileSync)(path)]) });
|
|
262
|
+
const { rows } = await db.query(
|
|
263
|
+
`SELECT obj_description(to_regnamespace('stardeck_test'), 'pg_namespace') AS key`
|
|
264
|
+
);
|
|
265
|
+
if (rows[0]?.key === key) {
|
|
266
|
+
const now3 = /* @__PURE__ */ new Date();
|
|
267
|
+
(0, import_node_fs.utimesSync)(path, now3, now3);
|
|
268
|
+
return db;
|
|
269
|
+
}
|
|
270
|
+
console.warn(`[stardeck-testing] rebuilding schema snapshot ${path}: key mismatch`);
|
|
271
|
+
} catch (error) {
|
|
272
|
+
console.warn(`[stardeck-testing] rebuilding unreadable schema snapshot ${path}:`, error);
|
|
273
|
+
}
|
|
274
|
+
await db?.close().catch(() => {
|
|
275
|
+
});
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
async function bootWithSchema(schemaSql) {
|
|
279
|
+
const path = snapshotPath(schemaSql);
|
|
280
|
+
if ((0, import_node_fs.existsSync)(path)) {
|
|
281
|
+
const loaded = await loadSnapshot(path, snapshotKey(schemaSql));
|
|
282
|
+
if (loaded) return loaded;
|
|
283
|
+
}
|
|
284
|
+
const { db, cacheable } = await buildSchemaDatabase(schemaSql);
|
|
285
|
+
if (cacheable) await writeSnapshot(path, db);
|
|
286
|
+
return db;
|
|
287
|
+
}
|
|
288
|
+
|
|
101
289
|
// src/global-singleton.ts
|
|
102
290
|
function globalSingleton(key, create) {
|
|
103
291
|
const symbol = /* @__PURE__ */ Symbol.for(`stardeck-testing.${key}`);
|
|
@@ -189,7 +377,7 @@ function requireDb() {
|
|
|
189
377
|
}
|
|
190
378
|
|
|
191
379
|
// src/simulator/hmac.ts
|
|
192
|
-
var
|
|
380
|
+
var import_node_crypto2 = __toESM(require("crypto"));
|
|
193
381
|
var TIMESTAMP_TOLERANCE_SECONDS = 300;
|
|
194
382
|
function isNonEmptyString(value) {
|
|
195
383
|
return typeof value === "string" && value.length > 0;
|
|
@@ -210,10 +398,10 @@ function verifyDeploymentAuthHeader(secret, header) {
|
|
|
210
398
|
} catch {
|
|
211
399
|
return null;
|
|
212
400
|
}
|
|
213
|
-
const expected =
|
|
401
|
+
const expected = import_node_crypto2.default.createHmac("sha256", secret).update(payloadJson).digest("hex");
|
|
214
402
|
const expectedBuf = Buffer.from(expected, "utf-8");
|
|
215
403
|
const actualBuf = Buffer.from(signature, "utf-8");
|
|
216
|
-
if (expectedBuf.length !== actualBuf.length || !
|
|
404
|
+
if (expectedBuf.length !== actualBuf.length || !import_node_crypto2.default.timingSafeEqual(expectedBuf, actualBuf)) {
|
|
217
405
|
return null;
|
|
218
406
|
}
|
|
219
407
|
let parsed;
|
|
@@ -630,7 +818,7 @@ async function handleNeonSql(db, request) {
|
|
|
630
818
|
}
|
|
631
819
|
|
|
632
820
|
// src/simulator/auth.ts
|
|
633
|
-
var
|
|
821
|
+
var import_node_crypto3 = __toESM(require("crypto"));
|
|
634
822
|
function handleAuthVerify(request) {
|
|
635
823
|
const authHeader = request.headers.get("Authorization");
|
|
636
824
|
const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
|
|
@@ -647,7 +835,7 @@ function handleAuthRefresh(request) {
|
|
|
647
835
|
if (!user || !refreshToken) {
|
|
648
836
|
return json({ error: "invalid_grant" }, 401);
|
|
649
837
|
}
|
|
650
|
-
const accessToken = `test-access-${
|
|
838
|
+
const accessToken = `test-access-${import_node_crypto3.default.randomUUID()}`;
|
|
651
839
|
state.sessions.set(accessToken, user);
|
|
652
840
|
return json({
|
|
653
841
|
access_token: accessToken,
|
|
@@ -715,7 +903,7 @@ function createInbox() {
|
|
|
715
903
|
}
|
|
716
904
|
|
|
717
905
|
// src/simulator/identities.ts
|
|
718
|
-
var
|
|
906
|
+
var import_node_crypto4 = __toESM(require("crypto"));
|
|
719
907
|
var ATTACH_LINK_KINDS = /* @__PURE__ */ new Set(["line", "facebook", "instagram", "email", "phone"]);
|
|
720
908
|
var TRUSTED_LINK_KINDS = /* @__PURE__ */ new Set([
|
|
721
909
|
"line",
|
|
@@ -828,7 +1016,7 @@ function normalizedLinkKey(link) {
|
|
|
828
1016
|
function createPerson(displayName) {
|
|
829
1017
|
const createdAt = now();
|
|
830
1018
|
const identity = {
|
|
831
|
-
id:
|
|
1019
|
+
id: import_node_crypto4.default.randomUUID(),
|
|
832
1020
|
type: "person",
|
|
833
1021
|
parentId: null,
|
|
834
1022
|
displayName,
|
|
@@ -844,7 +1032,7 @@ function createPerson(displayName) {
|
|
|
844
1032
|
}
|
|
845
1033
|
function createUnverifiedLink(identityId, kind, externalId) {
|
|
846
1034
|
const link = {
|
|
847
|
-
id:
|
|
1035
|
+
id: import_node_crypto4.default.randomUUID(),
|
|
848
1036
|
identityId,
|
|
849
1037
|
kind,
|
|
850
1038
|
externalId,
|
|
@@ -895,7 +1083,7 @@ async function handleCreate(request) {
|
|
|
895
1083
|
}
|
|
896
1084
|
const createdAt = now();
|
|
897
1085
|
const identity = {
|
|
898
|
-
id:
|
|
1086
|
+
id: import_node_crypto4.default.randomUUID(),
|
|
899
1087
|
type,
|
|
900
1088
|
parentId,
|
|
901
1089
|
displayName: body.displayName ?? null,
|
|
@@ -1173,8 +1361,8 @@ function claimableIdentity(identityId) {
|
|
|
1173
1361
|
return identity;
|
|
1174
1362
|
}
|
|
1175
1363
|
function mintClaim(params) {
|
|
1176
|
-
const claimId =
|
|
1177
|
-
const handle =
|
|
1364
|
+
const claimId = import_node_crypto4.default.randomUUID();
|
|
1365
|
+
const handle = import_node_crypto4.default.randomBytes(32).toString("base64url");
|
|
1178
1366
|
const claim = {
|
|
1179
1367
|
claimId,
|
|
1180
1368
|
flow: params.flow,
|
|
@@ -1528,7 +1716,7 @@ function createDirectory() {
|
|
|
1528
1716
|
}
|
|
1529
1717
|
|
|
1530
1718
|
// src/simulator/payments.ts
|
|
1531
|
-
var
|
|
1719
|
+
var import_node_crypto6 = __toESM(require("crypto"));
|
|
1532
1720
|
|
|
1533
1721
|
// src/next/headers-shim.ts
|
|
1534
1722
|
var import_node_async_hooks = require("async_hooks");
|
|
@@ -1611,7 +1799,7 @@ function decodeNextRedirect(error) {
|
|
|
1611
1799
|
}
|
|
1612
1800
|
|
|
1613
1801
|
// src/simulator/webhook-signing.ts
|
|
1614
|
-
var
|
|
1802
|
+
var import_node_crypto5 = __toESM(require("crypto"));
|
|
1615
1803
|
function signEventDelivery(secret, context, rawBody) {
|
|
1616
1804
|
const payload = {
|
|
1617
1805
|
type: "deployment-request",
|
|
@@ -1619,11 +1807,11 @@ function signEventDelivery(secret, context, rawBody) {
|
|
|
1619
1807
|
projectId: context.projectId,
|
|
1620
1808
|
deploymentId: context.deploymentId,
|
|
1621
1809
|
timestamp: Math.floor(Date.now() / 1e3),
|
|
1622
|
-
nonce:
|
|
1810
|
+
nonce: import_node_crypto5.default.randomUUID()
|
|
1623
1811
|
};
|
|
1624
1812
|
const payloadJson = JSON.stringify(payload);
|
|
1625
1813
|
const payloadB64 = Buffer.from(payloadJson).toString("base64");
|
|
1626
|
-
const signature =
|
|
1814
|
+
const signature = import_node_crypto5.default.createHmac("sha256", secret).update(payloadJson).update(rawBody).digest("hex");
|
|
1627
1815
|
return `${payloadB64}.${signature}`;
|
|
1628
1816
|
}
|
|
1629
1817
|
|
|
@@ -2157,7 +2345,7 @@ function createPayments() {
|
|
|
2157
2345
|
},
|
|
2158
2346
|
async deliverStripeEvent(handler, event, options) {
|
|
2159
2347
|
const envelope = {
|
|
2160
|
-
id: `evt_test_${
|
|
2348
|
+
id: `evt_test_${import_node_crypto6.default.randomUUID()}`,
|
|
2161
2349
|
kind: "stripe_webhook",
|
|
2162
2350
|
timestamp: Date.now(),
|
|
2163
2351
|
stripeEvent: {
|
|
@@ -2170,7 +2358,7 @@ function createPayments() {
|
|
|
2170
2358
|
},
|
|
2171
2359
|
async deliverBeamEvent(handler, event, options) {
|
|
2172
2360
|
const envelope = {
|
|
2173
|
-
id: `beam_evt_test_${
|
|
2361
|
+
id: `beam_evt_test_${import_node_crypto6.default.randomUUID()}`,
|
|
2174
2362
|
kind: "beam_webhook",
|
|
2175
2363
|
timestamp: Date.now(),
|
|
2176
2364
|
beamEvent: {
|
|
@@ -2253,7 +2441,7 @@ function createPayments() {
|
|
|
2253
2441
|
}
|
|
2254
2442
|
|
|
2255
2443
|
// src/simulator/storage.ts
|
|
2256
|
-
var
|
|
2444
|
+
var import_node_crypto7 = __toESM(require("crypto"));
|
|
2257
2445
|
function storageErr(error, status = 400) {
|
|
2258
2446
|
return json({ error }, status);
|
|
2259
2447
|
}
|
|
@@ -2332,7 +2520,7 @@ async function handleStorageRequest(request, url) {
|
|
|
2332
2520
|
}
|
|
2333
2521
|
return new Response(null, {
|
|
2334
2522
|
status: 200,
|
|
2335
|
-
headers: { etag: `"${
|
|
2523
|
+
headers: { etag: `"${import_node_crypto7.default.randomUUID()}"` }
|
|
2336
2524
|
});
|
|
2337
2525
|
}
|
|
2338
2526
|
if (pathname === "/upload/presign/complete" && request.method === "POST") {
|
|
@@ -2480,7 +2668,7 @@ function createStorage() {
|
|
|
2480
2668
|
}
|
|
2481
2669
|
|
|
2482
2670
|
// src/simulator/messaging.ts
|
|
2483
|
-
var
|
|
2671
|
+
var import_node_crypto8 = __toESM(require("crypto"));
|
|
2484
2672
|
function isValidLineImageUrl(url) {
|
|
2485
2673
|
if (url.length > 2e3 || !url.startsWith("https://")) return false;
|
|
2486
2674
|
try {
|
|
@@ -2534,7 +2722,7 @@ async function handleMessagingRequest(request, channel, subPath) {
|
|
|
2534
2722
|
);
|
|
2535
2723
|
return success({
|
|
2536
2724
|
ok: true,
|
|
2537
|
-
ts: `${Date.now()}.${
|
|
2725
|
+
ts: `${Date.now()}.${import_node_crypto8.default.randomUUID().slice(0, 6)}`,
|
|
2538
2726
|
channel: slackChannel
|
|
2539
2727
|
});
|
|
2540
2728
|
}
|
|
@@ -2914,6 +3102,7 @@ function createEdge() {
|
|
|
2914
3102
|
}
|
|
2915
3103
|
|
|
2916
3104
|
// src/simulator/starlens.ts
|
|
3105
|
+
var LATEST_VERSION = "v2";
|
|
2917
3106
|
function starlensFailure(error, status, code) {
|
|
2918
3107
|
return json({ success: false, error, code }, status);
|
|
2919
3108
|
}
|
|
@@ -2932,11 +3121,18 @@ function nullFlagged(output, issues) {
|
|
|
2932
3121
|
}
|
|
2933
3122
|
return copy;
|
|
2934
3123
|
}
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
3124
|
+
function isInputShape(input) {
|
|
3125
|
+
if (!input || typeof input !== "object") return false;
|
|
3126
|
+
const i = input;
|
|
3127
|
+
return typeof i.url === "string" || typeof i.data === "string" && i.data.length > 0 && typeof i.mediaType === "string";
|
|
3128
|
+
}
|
|
3129
|
+
var NON_IMAGE_EXTENSION = /\.(pdf|mp3|m4a|wav|ogg|aac|flac|mp4|mov|webm|mkv)(\?|#|$)/i;
|
|
3130
|
+
function isCroppable(input) {
|
|
3131
|
+
if (typeof input.mediaType === "string") return input.mediaType.startsWith("image/");
|
|
3132
|
+
const name = typeof input.filename === "string" ? input.filename : input.url;
|
|
3133
|
+
return !(typeof name === "string" && NON_IMAGE_EXTENSION.test(name));
|
|
3134
|
+
}
|
|
3135
|
+
function validateStarlensBody(body) {
|
|
2940
3136
|
const inputs = Array.isArray(body.inputs) ? body.inputs : [];
|
|
2941
3137
|
if (inputs.length === 0) {
|
|
2942
3138
|
return starlensFailure(
|
|
@@ -2945,6 +3141,13 @@ async function handleStarlensExtract(request, signedDeploymentId) {
|
|
|
2945
3141
|
"VALIDATION_ERROR"
|
|
2946
3142
|
);
|
|
2947
3143
|
}
|
|
3144
|
+
if (!inputs.every(isInputShape)) {
|
|
3145
|
+
return starlensFailure(
|
|
3146
|
+
"Validation failed \u2014 inputs: each input needs a url, or data with a mediaType",
|
|
3147
|
+
400,
|
|
3148
|
+
"VALIDATION_ERROR"
|
|
3149
|
+
);
|
|
3150
|
+
}
|
|
2948
3151
|
const schema = body.schema;
|
|
2949
3152
|
if (!schema || schema.type !== "object" || typeof schema.properties !== "object") {
|
|
2950
3153
|
return starlensFailure(
|
|
@@ -2953,8 +3156,35 @@ async function handleStarlensExtract(request, signedDeploymentId) {
|
|
|
2953
3156
|
"VALIDATION_ERROR"
|
|
2954
3157
|
);
|
|
2955
3158
|
}
|
|
3159
|
+
const version = body.version ?? "latest";
|
|
3160
|
+
if (version !== "v1" && version !== "v2" && version !== "latest") {
|
|
3161
|
+
return starlensFailure("Validation failed \u2014 version: unknown version", 400, "VALIDATION_ERROR");
|
|
3162
|
+
}
|
|
3163
|
+
const effort = body.effort ?? "low";
|
|
3164
|
+
if (effort !== "low" && effort !== "high") {
|
|
3165
|
+
return starlensFailure("Validation failed \u2014 effort: low or high", 400, "VALIDATION_ERROR");
|
|
3166
|
+
}
|
|
3167
|
+
const resolved = version === "latest" ? LATEST_VERSION : version;
|
|
3168
|
+
if (effort === "high" && resolved === "v1") {
|
|
3169
|
+
return starlensFailure(
|
|
3170
|
+
`Starlens v1 does not support effort "high"; use version "v2"`,
|
|
3171
|
+
400,
|
|
3172
|
+
"unsupported_effort"
|
|
3173
|
+
);
|
|
3174
|
+
}
|
|
3175
|
+
return { version, resolved, effort, inputs, schema };
|
|
3176
|
+
}
|
|
3177
|
+
async function handleStarlensExtract(request, signedDeploymentId) {
|
|
3178
|
+
const body = await readJsonBody(request);
|
|
3179
|
+
const valid = validateStarlensBody(body);
|
|
3180
|
+
if (valid instanceof Response) return valid;
|
|
3181
|
+
const { version, resolved, effort, inputs, schema } = valid;
|
|
3182
|
+
if (signedDeploymentId !== null && body.deploymentId !== signedDeploymentId) {
|
|
3183
|
+
return starlensFailure("Deployment ID mismatch", 401, "AUTHENTICATION_ERROR");
|
|
3184
|
+
}
|
|
2956
3185
|
const call = {
|
|
2957
|
-
version
|
|
3186
|
+
version,
|
|
3187
|
+
effort,
|
|
2958
3188
|
inputs: inputs.map((i) => ({
|
|
2959
3189
|
name: typeof i.filename === "string" ? i.filename : typeof i.url === "string" ? i.url : "upload",
|
|
2960
3190
|
mediaType: typeof i.mediaType === "string" ? i.mediaType : null,
|
|
@@ -2976,8 +3206,10 @@ async function handleStarlensExtract(request, signedDeploymentId) {
|
|
|
2976
3206
|
return starlensFailure(reply.reject, 422, "unsupported_document");
|
|
2977
3207
|
}
|
|
2978
3208
|
const issues = reply.issues ?? [];
|
|
3209
|
+
const ran = reply.effort ?? (effort === "high" && !inputs.some(isCroppable) ? "low" : effort);
|
|
2979
3210
|
return success({
|
|
2980
|
-
version:
|
|
3211
|
+
version: resolved,
|
|
3212
|
+
effort: ran,
|
|
2981
3213
|
output: nullFlagged(reply.output, issues),
|
|
2982
3214
|
issues,
|
|
2983
3215
|
usage: { inputTokens: 0, outputTokens: 0, cost: 0 }
|
|
@@ -3247,8 +3479,8 @@ function buildUser(partial) {
|
|
|
3247
3479
|
function loadSchemaSql(options) {
|
|
3248
3480
|
if (options.schemaSql !== void 0) return options.schemaSql;
|
|
3249
3481
|
if (options.schema === null) return null;
|
|
3250
|
-
const schemaPath = (0,
|
|
3251
|
-
if (!(0,
|
|
3482
|
+
const schemaPath = (0, import_node_path2.resolve)(process.cwd(), options.schema ?? DEFAULT_SCHEMA_PATH);
|
|
3483
|
+
if (!(0, import_node_fs2.existsSync)(schemaPath)) {
|
|
3252
3484
|
if (options.schema) {
|
|
3253
3485
|
throw new Error(
|
|
3254
3486
|
`[stardeck-testing] Schema snapshot not found at ${schemaPath}. Run \`npx stardeck-data-store generate-types\` to regenerate it.`
|
|
@@ -3256,7 +3488,7 @@ function loadSchemaSql(options) {
|
|
|
3256
3488
|
}
|
|
3257
3489
|
return null;
|
|
3258
3490
|
}
|
|
3259
|
-
return (0,
|
|
3491
|
+
return (0, import_node_fs2.readFileSync)(schemaPath, "utf-8");
|
|
3260
3492
|
}
|
|
3261
3493
|
async function createTestApp(options = {}) {
|
|
3262
3494
|
if (state.db) {
|
|
@@ -3267,15 +3499,23 @@ async function createTestApp(options = {}) {
|
|
|
3267
3499
|
applyTestEnv(options);
|
|
3268
3500
|
state.allowNetwork = options.allowNetwork ?? false;
|
|
3269
3501
|
installFetchRouter();
|
|
3270
|
-
const
|
|
3502
|
+
const schemaSql = loadSchemaSql(options) ?? "";
|
|
3503
|
+
const db = await bootWithSchema(schemaSql);
|
|
3271
3504
|
state.db = db;
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
if (schemaSql) await applySchema(db, schemaSql);
|
|
3505
|
+
let baseline;
|
|
3506
|
+
const seed = async () => {
|
|
3275
3507
|
if (options.seed) await options.seed(db);
|
|
3508
|
+
await acceptSchemaChanges(db);
|
|
3509
|
+
};
|
|
3510
|
+
const rebuild = async () => {
|
|
3511
|
+
await dropAllTables(db);
|
|
3512
|
+
await applySchema(db, schemaSql);
|
|
3513
|
+
baseline = await captureBaseline(db);
|
|
3514
|
+
await seed();
|
|
3276
3515
|
};
|
|
3277
3516
|
try {
|
|
3278
|
-
await
|
|
3517
|
+
baseline = await captureBaseline(db);
|
|
3518
|
+
await seed();
|
|
3279
3519
|
} catch (error) {
|
|
3280
3520
|
state.db = null;
|
|
3281
3521
|
await db.close().catch(() => {
|
|
@@ -3313,20 +3553,23 @@ async function createTestApp(options = {}) {
|
|
|
3313
3553
|
issueSession(user) {
|
|
3314
3554
|
const fullUser = buildUser(user);
|
|
3315
3555
|
const tokens = {
|
|
3316
|
-
accessToken: `test-access-${
|
|
3317
|
-
refreshToken: `test-refresh-${
|
|
3556
|
+
accessToken: `test-access-${import_node_crypto9.default.randomUUID()}`,
|
|
3557
|
+
refreshToken: `test-refresh-${import_node_crypto9.default.randomUUID()}`
|
|
3318
3558
|
};
|
|
3319
3559
|
state.sessions.set(tokens.accessToken, fullUser);
|
|
3320
3560
|
state.refreshSessions.set(tokens.refreshToken, fullUser);
|
|
3321
3561
|
return tokens;
|
|
3322
3562
|
},
|
|
3323
3563
|
async reset(resetOptions) {
|
|
3324
|
-
if (resetOptions?.mode === "
|
|
3325
|
-
await
|
|
3326
|
-
if (options.seed) await options.seed(db);
|
|
3564
|
+
if (resetOptions?.mode === "schema" || !baseline || await schemaChangedSinceBaseline(db)) {
|
|
3565
|
+
await rebuild();
|
|
3327
3566
|
} else {
|
|
3328
|
-
await
|
|
3329
|
-
|
|
3567
|
+
await restoreBaseline(db, baseline);
|
|
3568
|
+
try {
|
|
3569
|
+
await seed();
|
|
3570
|
+
} catch {
|
|
3571
|
+
await rebuild();
|
|
3572
|
+
}
|
|
3330
3573
|
}
|
|
3331
3574
|
state.currentUser = null;
|
|
3332
3575
|
state.sessions.clear();
|