@stardeck-customer-apps/testing 0.9.0 → 0.11.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 +91 -2
- package/dist/config.d.mts +6 -2
- package/dist/config.d.ts +6 -2
- package/dist/config.js +75 -1
- package/dist/config.mjs +73 -1
- package/dist/data-store-manifest-Zwgcv0DJ.d.mts +25 -0
- package/dist/data-store-manifest-Zwgcv0DJ.d.ts +25 -0
- package/dist/index.d.mts +16 -2
- package/dist/index.d.ts +16 -2
- package/dist/index.js +113 -8
- package/dist/index.mjs +110 -8
- package/dist/next/headers-shim.js +1 -0
- package/dist/next/headers-shim.mjs +1 -0
- package/dist/setup.js +27 -6
- package/dist/setup.mjs +27 -6
- package/package.json +2 -2
package/SKILL.md
CHANGED
|
@@ -29,6 +29,65 @@ This writes `src/generated/data-store-types.ts` and
|
|
|
29
29
|
`src/generated/data-store-schema.sql`. The harness applies the SQL snapshot to
|
|
30
30
|
PGlite at test boot. **Commit both files.**
|
|
31
31
|
|
|
32
|
+
### Data store bindings
|
|
33
|
+
|
|
34
|
+
Configure each app-local data store binding once in `vitest.config.ts`. The
|
|
35
|
+
harness injects the same `STARDECK_DATA_STORES` manifest shape as production, so
|
|
36
|
+
application code resolves the binding without test-only branches:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { defineStardeckTestConfig } from "@stardeck-customer-apps/testing/config";
|
|
40
|
+
|
|
41
|
+
export default defineStardeckTestConfig(
|
|
42
|
+
{},
|
|
43
|
+
{
|
|
44
|
+
dataStores: [{ bindingKey: "development" }],
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { createDataStore } from "@stardeck-customer-apps/data-store-sdk/server";
|
|
51
|
+
|
|
52
|
+
const db = await createDataStore({ storeName: "development" });
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Do not branch on `DATA_STORE_URL?.includes(".stardeck.test")` or otherwise
|
|
56
|
+
sniff test hosts. Configure the binding and use `createDataStore({ storeName })`
|
|
57
|
+
the same way in tests and production.
|
|
58
|
+
|
|
59
|
+
For one test file that needs a different manifest, pass the same entries to the
|
|
60
|
+
app instead. This overrides the config-level manifest for that file:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
app = await createTestApp({
|
|
64
|
+
dataStores: [
|
|
65
|
+
{ bindingKey: "development" },
|
|
66
|
+
{ bindingKey: "uploads", name: "Customer Files", storeType: "storage" },
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
An app setup file may also set `STARDECK_DATA_STORES` directly. The harness
|
|
72
|
+
adopts a non-empty raw env value when `createTestApp()` has no `dataStores`
|
|
73
|
+
option; a per-app `dataStores` option still overrides it. An empty env value is
|
|
74
|
+
treated as unset, matching the SDK manifest parser.
|
|
75
|
+
|
|
76
|
+
Strictness follows adoption:
|
|
77
|
+
|
|
78
|
+
- With no manifest configuration, `createTestApp()` injects a `main` database
|
|
79
|
+
entry and keeps `DATA_STORE_URL` for older bare `createDataStore()` calls.
|
|
80
|
+
- Configuring `dataStores` through either channel removes `DATA_STORE_URL` by
|
|
81
|
+
default. A misspelled `storeName` then fails instead of silently connecting to
|
|
82
|
+
the default database.
|
|
83
|
+
- Passing `dataStores: []` explicitly creates a strict zero-store test world:
|
|
84
|
+
the manifest is `[]`, `DATA_STORE_URL` is absent, and every
|
|
85
|
+
`createDataStore(...)` call fails until a store is configured.
|
|
86
|
+
- Empty-string `bindingKey`, `slug`, or `name` values throw during test app
|
|
87
|
+
setup so typos cannot silently change manifest resolution.
|
|
88
|
+
- Set `legacyDataStoreUrl: true` per file only while migrating legacy code. Set
|
|
89
|
+
it to `false` to test strict manifest-only resolution even with zero config.
|
|
90
|
+
|
|
32
91
|
## Writing tests
|
|
33
92
|
|
|
34
93
|
```ts
|
|
@@ -144,6 +203,26 @@ const integrations = createIntegrationsClient();
|
|
|
144
203
|
await integrations.line.push("U123", { type: "text", text: "Your order shipped" });
|
|
145
204
|
expect(app.messages.channel("line").to("U123")[0].body.text).toMatch(/shipped/i);
|
|
146
205
|
|
|
206
|
+
// LINE Flex card — the simulator preserves the complete LINE-native container.
|
|
207
|
+
const card = {
|
|
208
|
+
type: "bubble" as const,
|
|
209
|
+
body: {
|
|
210
|
+
type: "box",
|
|
211
|
+
layout: "vertical",
|
|
212
|
+
contents: [{ type: "text", text: "Order shipped" }],
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
await integrations.line.push("U123", {
|
|
216
|
+
type: "flex",
|
|
217
|
+
altText: "Your order shipped",
|
|
218
|
+
contents: card,
|
|
219
|
+
});
|
|
220
|
+
expect(app.messages.channel("line").to("U123")[1].body).toEqual({
|
|
221
|
+
type: "flex",
|
|
222
|
+
altText: "Your order shipped",
|
|
223
|
+
contents: card,
|
|
224
|
+
});
|
|
225
|
+
|
|
147
226
|
// Guest/counter identity resolution. `provenance` is selected by this trusted
|
|
148
227
|
// server route; never copy it from a browser form or request body.
|
|
149
228
|
const identity = await integrations.identities.resolveOrCreate({
|
|
@@ -186,7 +265,14 @@ expect(app.edge.latestDisplay()?.action).toBe("show");
|
|
|
186
265
|
|
|
187
266
|
- `createTestApp(options)` — boots PGlite + the control-plane simulator and
|
|
188
267
|
sets all Stardeck env vars (`CONTROL_PLANE_URL`, `DEPLOYMENT_SECRET`,
|
|
189
|
-
`
|
|
268
|
+
`STARDECK_DATA_STORES`, ids, and the legacy `DATA_STORE_URL` when enabled).
|
|
269
|
+
One per test file, in `beforeAll`.
|
|
270
|
+
- `dataStores` — per-file production-shaped data store manifest entries. The
|
|
271
|
+
first entry defaults to slug `main`; later entries need a `slug` or `name`.
|
|
272
|
+
Pass `[]` for strict zero-store mode. Empty `bindingKey`, `slug`, and `name`
|
|
273
|
+
values are rejected.
|
|
274
|
+
- `legacyDataStoreUrl` — force the legacy single-store URL on or off. When
|
|
275
|
+
omitted, it is on only for zero-config tests and off after manifest adoption.
|
|
190
276
|
- `schema` — path to the DDL snapshot (default
|
|
191
277
|
`./src/generated/data-store-schema.sql`); `null` for apps without a store.
|
|
192
278
|
- `schemaSql` — inline DDL instead of a file.
|
|
@@ -222,7 +308,10 @@ externalId })` helper models a trusted platform/channel link, and
|
|
|
222
308
|
list `limit` 1–100, status filter values).
|
|
223
309
|
- `app.storage` — uploads through storage-sdk: `.uploads`, `.latest()`, `.count`, `.clear()`.
|
|
224
310
|
- `app.messages` — outbound Slack/LINE/Facebook sends:
|
|
225
|
-
`.all()`, `.latest()`, `.to(recipient)`, `.channel("line")`, `.count`, `.clear()`.
|
|
311
|
+
`.all()`, `.latest()`, `.to(recipient)`, `.channel("line")`, `.count`, `.clear()`. LINE Flex
|
|
312
|
+
captures expose `body.type`, `body.altText`, and the complete `body.contents` bubble/carousel.
|
|
313
|
+
The simulator validates the Flex envelope like production; LINE itself remains responsible for
|
|
314
|
+
validating nested component layout.
|
|
226
315
|
- `app.edge` — edge print/display/bindings from edge-sdk: `.prints`, `.displays`,
|
|
227
316
|
`.testPrints`, `.latestPrint()`, `.latestDisplay()`, `.bindings`,
|
|
228
317
|
`.seedDevices()`, `.seedPeripherals()`, `.seedBindings()`,
|
package/dist/config.d.mts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { ViteUserConfig } from 'vitest/config';
|
|
2
|
+
import { T as TestDataStoreInput } from './data-store-manifest-Zwgcv0DJ.mjs';
|
|
2
3
|
|
|
4
|
+
interface StardeckTestConfigOptions {
|
|
5
|
+
dataStores?: TestDataStoreInput[];
|
|
6
|
+
}
|
|
3
7
|
/**
|
|
4
8
|
* Vitest config for Stardeck customer apps. Aliases `next/headers` (and
|
|
5
9
|
* `server-only`) to harness shims so server code runs outside a Next request
|
|
@@ -11,6 +15,6 @@ import { ViteUserConfig } from 'vitest/config';
|
|
|
11
15
|
* export default defineStardeckTestConfig();
|
|
12
16
|
* ```
|
|
13
17
|
*/
|
|
14
|
-
declare function defineStardeckTestConfig(overrides?: ViteUserConfig): ViteUserConfig;
|
|
18
|
+
declare function defineStardeckTestConfig(overrides?: ViteUserConfig, stardeck?: StardeckTestConfigOptions): ViteUserConfig;
|
|
15
19
|
|
|
16
|
-
export { defineStardeckTestConfig };
|
|
20
|
+
export { type StardeckTestConfigOptions, defineStardeckTestConfig };
|
package/dist/config.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { ViteUserConfig } from 'vitest/config';
|
|
2
|
+
import { T as TestDataStoreInput } from './data-store-manifest-Zwgcv0DJ.js';
|
|
2
3
|
|
|
4
|
+
interface StardeckTestConfigOptions {
|
|
5
|
+
dataStores?: TestDataStoreInput[];
|
|
6
|
+
}
|
|
3
7
|
/**
|
|
4
8
|
* Vitest config for Stardeck customer apps. Aliases `next/headers` (and
|
|
5
9
|
* `server-only`) to harness shims so server code runs outside a Next request
|
|
@@ -11,6 +15,6 @@ import { ViteUserConfig } from 'vitest/config';
|
|
|
11
15
|
* export default defineStardeckTestConfig();
|
|
12
16
|
* ```
|
|
13
17
|
*/
|
|
14
|
-
declare function defineStardeckTestConfig(overrides?: ViteUserConfig): ViteUserConfig;
|
|
18
|
+
declare function defineStardeckTestConfig(overrides?: ViteUserConfig, stardeck?: StardeckTestConfigOptions): ViteUserConfig;
|
|
15
19
|
|
|
16
|
-
export { defineStardeckTestConfig };
|
|
20
|
+
export { type StardeckTestConfigOptions, defineStardeckTestConfig };
|
package/dist/config.js
CHANGED
|
@@ -23,7 +23,77 @@ __export(config_exports, {
|
|
|
23
23
|
defineStardeckTestConfig: () => defineStardeckTestConfig
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(config_exports);
|
|
26
|
-
|
|
26
|
+
|
|
27
|
+
// src/constants.ts
|
|
28
|
+
var DATA_STORE_TEST_HOST = "db.stardeck.test";
|
|
29
|
+
var DATA_STORE_TEST_URL = `postgresql://test:test@${DATA_STORE_TEST_HOST}/main`;
|
|
30
|
+
|
|
31
|
+
// src/data-store-manifest.ts
|
|
32
|
+
var STARDECK_DATA_STORES_ENV = "STARDECK_DATA_STORES";
|
|
33
|
+
function dataStoreSlug(name) {
|
|
34
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
35
|
+
return slug || "store";
|
|
36
|
+
}
|
|
37
|
+
function buildTestDataStoreManifest(inputs = [{}]) {
|
|
38
|
+
const takenSlugs = /* @__PURE__ */ new Set();
|
|
39
|
+
const takenBindingKeys = /* @__PURE__ */ new Set();
|
|
40
|
+
const takenIds = /* @__PURE__ */ new Set();
|
|
41
|
+
return inputs.map((input, index) => {
|
|
42
|
+
for (const field of ["bindingKey", "slug", "name"]) {
|
|
43
|
+
if (input[field] === "") {
|
|
44
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].${field} cannot be empty.`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
let slug;
|
|
48
|
+
if (input.slug !== void 0) {
|
|
49
|
+
slug = input.slug;
|
|
50
|
+
if (takenSlugs.has(slug)) {
|
|
51
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].slug must be unique.`);
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
const base = input.name !== void 0 ? dataStoreSlug(input.name) : index === 0 ? "main" : null;
|
|
55
|
+
if (base === null) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`[stardeck-testing] dataStores[${index}] requires a slug or name. Only the first entry defaults to the "main" slug.`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
slug = base;
|
|
61
|
+
let suffix = 2;
|
|
62
|
+
while (takenSlugs.has(slug)) {
|
|
63
|
+
slug = `${base}_${suffix}`;
|
|
64
|
+
suffix++;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const id = input.id ?? `00000000-0000-4000-8000-${String(index + 1).padStart(12, "0")}`;
|
|
68
|
+
if (takenIds.has(id)) {
|
|
69
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].id must be unique.`);
|
|
70
|
+
}
|
|
71
|
+
if (input.bindingKey !== void 0 && takenBindingKeys.has(input.bindingKey)) {
|
|
72
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].bindingKey must be unique.`);
|
|
73
|
+
}
|
|
74
|
+
takenSlugs.add(slug);
|
|
75
|
+
takenIds.add(id);
|
|
76
|
+
if (input.bindingKey !== void 0) takenBindingKeys.add(input.bindingKey);
|
|
77
|
+
const storeType = input.storeType ?? "database";
|
|
78
|
+
return {
|
|
79
|
+
id,
|
|
80
|
+
name: input.name ?? slug,
|
|
81
|
+
slug,
|
|
82
|
+
storeType,
|
|
83
|
+
accessLevel: input.accessLevel ?? "admin",
|
|
84
|
+
...storeType === "database" ? { url: input.url ?? DATA_STORE_TEST_URL } : {},
|
|
85
|
+
...input.bindingKey ? { bindingKey: input.bindingKey } : {}
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/config.ts
|
|
91
|
+
function defineStardeckTestConfig(overrides = {}, stardeck = {}) {
|
|
92
|
+
const dataStoreEnv = stardeck.dataStores !== void 0 ? {
|
|
93
|
+
[STARDECK_DATA_STORES_ENV]: JSON.stringify(
|
|
94
|
+
buildTestDataStoreManifest(stardeck.dataStores)
|
|
95
|
+
)
|
|
96
|
+
} : {};
|
|
27
97
|
return {
|
|
28
98
|
...overrides,
|
|
29
99
|
resolve: {
|
|
@@ -44,6 +114,10 @@ function defineStardeckTestConfig(overrides = {}) {
|
|
|
44
114
|
pool: "forks",
|
|
45
115
|
isolate: true,
|
|
46
116
|
...overrides.test,
|
|
117
|
+
env: {
|
|
118
|
+
...dataStoreEnv,
|
|
119
|
+
...overrides.test?.env
|
|
120
|
+
},
|
|
47
121
|
setupFiles: [
|
|
48
122
|
"@stardeck-customer-apps/testing/setup",
|
|
49
123
|
...overrides.test?.setupFiles ? Array.isArray(overrides.test.setupFiles) ? overrides.test.setupFiles : [overrides.test.setupFiles] : []
|
package/dist/config.mjs
CHANGED
|
@@ -1,5 +1,73 @@
|
|
|
1
|
+
// src/constants.ts
|
|
2
|
+
var DATA_STORE_TEST_HOST = "db.stardeck.test";
|
|
3
|
+
var DATA_STORE_TEST_URL = `postgresql://test:test@${DATA_STORE_TEST_HOST}/main`;
|
|
4
|
+
|
|
5
|
+
// src/data-store-manifest.ts
|
|
6
|
+
var STARDECK_DATA_STORES_ENV = "STARDECK_DATA_STORES";
|
|
7
|
+
function dataStoreSlug(name) {
|
|
8
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
9
|
+
return slug || "store";
|
|
10
|
+
}
|
|
11
|
+
function buildTestDataStoreManifest(inputs = [{}]) {
|
|
12
|
+
const takenSlugs = /* @__PURE__ */ new Set();
|
|
13
|
+
const takenBindingKeys = /* @__PURE__ */ new Set();
|
|
14
|
+
const takenIds = /* @__PURE__ */ new Set();
|
|
15
|
+
return inputs.map((input, index) => {
|
|
16
|
+
for (const field of ["bindingKey", "slug", "name"]) {
|
|
17
|
+
if (input[field] === "") {
|
|
18
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].${field} cannot be empty.`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
let slug;
|
|
22
|
+
if (input.slug !== void 0) {
|
|
23
|
+
slug = input.slug;
|
|
24
|
+
if (takenSlugs.has(slug)) {
|
|
25
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].slug must be unique.`);
|
|
26
|
+
}
|
|
27
|
+
} else {
|
|
28
|
+
const base = input.name !== void 0 ? dataStoreSlug(input.name) : index === 0 ? "main" : null;
|
|
29
|
+
if (base === null) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`[stardeck-testing] dataStores[${index}] requires a slug or name. Only the first entry defaults to the "main" slug.`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
slug = base;
|
|
35
|
+
let suffix = 2;
|
|
36
|
+
while (takenSlugs.has(slug)) {
|
|
37
|
+
slug = `${base}_${suffix}`;
|
|
38
|
+
suffix++;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const id = input.id ?? `00000000-0000-4000-8000-${String(index + 1).padStart(12, "0")}`;
|
|
42
|
+
if (takenIds.has(id)) {
|
|
43
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].id must be unique.`);
|
|
44
|
+
}
|
|
45
|
+
if (input.bindingKey !== void 0 && takenBindingKeys.has(input.bindingKey)) {
|
|
46
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].bindingKey must be unique.`);
|
|
47
|
+
}
|
|
48
|
+
takenSlugs.add(slug);
|
|
49
|
+
takenIds.add(id);
|
|
50
|
+
if (input.bindingKey !== void 0) takenBindingKeys.add(input.bindingKey);
|
|
51
|
+
const storeType = input.storeType ?? "database";
|
|
52
|
+
return {
|
|
53
|
+
id,
|
|
54
|
+
name: input.name ?? slug,
|
|
55
|
+
slug,
|
|
56
|
+
storeType,
|
|
57
|
+
accessLevel: input.accessLevel ?? "admin",
|
|
58
|
+
...storeType === "database" ? { url: input.url ?? DATA_STORE_TEST_URL } : {},
|
|
59
|
+
...input.bindingKey ? { bindingKey: input.bindingKey } : {}
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
1
64
|
// src/config.ts
|
|
2
|
-
function defineStardeckTestConfig(overrides = {}) {
|
|
65
|
+
function defineStardeckTestConfig(overrides = {}, stardeck = {}) {
|
|
66
|
+
const dataStoreEnv = stardeck.dataStores !== void 0 ? {
|
|
67
|
+
[STARDECK_DATA_STORES_ENV]: JSON.stringify(
|
|
68
|
+
buildTestDataStoreManifest(stardeck.dataStores)
|
|
69
|
+
)
|
|
70
|
+
} : {};
|
|
3
71
|
return {
|
|
4
72
|
...overrides,
|
|
5
73
|
resolve: {
|
|
@@ -20,6 +88,10 @@ function defineStardeckTestConfig(overrides = {}) {
|
|
|
20
88
|
pool: "forks",
|
|
21
89
|
isolate: true,
|
|
22
90
|
...overrides.test,
|
|
91
|
+
env: {
|
|
92
|
+
...dataStoreEnv,
|
|
93
|
+
...overrides.test?.env
|
|
94
|
+
},
|
|
23
95
|
setupFiles: [
|
|
24
96
|
"@stardeck-customer-apps/testing/setup",
|
|
25
97
|
...overrides.test?.setupFiles ? Array.isArray(overrides.test.setupFiles) ? overrides.test.setupFiles : [overrides.test.setupFiles] : []
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
declare const STARDECK_DATA_STORES_ENV = "STARDECK_DATA_STORES";
|
|
2
|
+
interface TestDataStoreInput {
|
|
3
|
+
bindingKey?: string;
|
|
4
|
+
/** Stable snake_case handle for the store. */
|
|
5
|
+
slug?: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
id?: string;
|
|
8
|
+
accessLevel?: "read" | "write" | "admin";
|
|
9
|
+
storeType?: "database" | "storage";
|
|
10
|
+
/** Defaults to the PGlite simulator URL for database stores. */
|
|
11
|
+
url?: string;
|
|
12
|
+
}
|
|
13
|
+
interface TestDataStoreManifestEntry {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
slug: string;
|
|
17
|
+
storeType: "database" | "storage";
|
|
18
|
+
accessLevel: "read" | "write" | "admin";
|
|
19
|
+
url?: string;
|
|
20
|
+
bindingKey?: string;
|
|
21
|
+
}
|
|
22
|
+
/** Builds the production-shaped manifest injected into a test app. */
|
|
23
|
+
declare function buildTestDataStoreManifest(inputs?: TestDataStoreInput[]): TestDataStoreManifestEntry[];
|
|
24
|
+
|
|
25
|
+
export { STARDECK_DATA_STORES_ENV as S, type TestDataStoreInput as T, type TestDataStoreManifestEntry as a, buildTestDataStoreManifest as b };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
declare const STARDECK_DATA_STORES_ENV = "STARDECK_DATA_STORES";
|
|
2
|
+
interface TestDataStoreInput {
|
|
3
|
+
bindingKey?: string;
|
|
4
|
+
/** Stable snake_case handle for the store. */
|
|
5
|
+
slug?: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
id?: string;
|
|
8
|
+
accessLevel?: "read" | "write" | "admin";
|
|
9
|
+
storeType?: "database" | "storage";
|
|
10
|
+
/** Defaults to the PGlite simulator URL for database stores. */
|
|
11
|
+
url?: string;
|
|
12
|
+
}
|
|
13
|
+
interface TestDataStoreManifestEntry {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
slug: string;
|
|
17
|
+
storeType: "database" | "storage";
|
|
18
|
+
accessLevel: "read" | "write" | "admin";
|
|
19
|
+
url?: string;
|
|
20
|
+
bindingKey?: string;
|
|
21
|
+
}
|
|
22
|
+
/** Builds the production-shaped manifest injected into a test app. */
|
|
23
|
+
declare function buildTestDataStoreManifest(inputs?: TestDataStoreInput[]): TestDataStoreManifestEntry[];
|
|
24
|
+
|
|
25
|
+
export { STARDECK_DATA_STORES_ENV as S, type TestDataStoreInput as T, type TestDataStoreManifestEntry as a, buildTestDataStoreManifest as b };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as _stardeck_customer_apps_payments_sdk from '@stardeck-customer-apps/payments-sdk';
|
|
2
2
|
import * as next_server from 'next/server';
|
|
3
3
|
import { PGlite } from '@electric-sql/pglite';
|
|
4
|
+
import { T as TestDataStoreInput } from './data-store-manifest-Zwgcv0DJ.mjs';
|
|
5
|
+
export { S as STARDECK_DATA_STORES_ENV, a as TestDataStoreManifestEntry, b as buildTestDataStoreManifest } from './data-store-manifest-Zwgcv0DJ.mjs';
|
|
4
6
|
|
|
5
7
|
type RouteHandler<Req extends Request = Request, P = Record<string, string | string[]>> = (request: Req, context: {
|
|
6
8
|
params: Promise<P>;
|
|
@@ -250,6 +252,7 @@ interface CapturedMessage {
|
|
|
250
252
|
channel: "slack" | "line" | "facebook";
|
|
251
253
|
recipient: string;
|
|
252
254
|
body: {
|
|
255
|
+
type?: string;
|
|
253
256
|
text?: string;
|
|
254
257
|
blocks?: unknown[];
|
|
255
258
|
threadTs?: string;
|
|
@@ -259,6 +262,10 @@ interface CapturedMessage {
|
|
|
259
262
|
originalContentUrl?: string;
|
|
260
263
|
/** LINE image push — HTTPS URL of the preview; may be omitted when not sent. */
|
|
261
264
|
previewImageUrl?: string;
|
|
265
|
+
/** LINE Flex fallback text shown when the rich card cannot be rendered. */
|
|
266
|
+
altText?: string;
|
|
267
|
+
/** Complete LINE Flex bubble or carousel payload. */
|
|
268
|
+
contents?: Record<string, unknown>;
|
|
262
269
|
};
|
|
263
270
|
connectionId?: string;
|
|
264
271
|
sentAt: Date;
|
|
@@ -437,6 +444,13 @@ interface TestEdge {
|
|
|
437
444
|
get count(): number;
|
|
438
445
|
}
|
|
439
446
|
interface TestAppOptions {
|
|
447
|
+
/** Data stores exposed through the production-shaped STARDECK_DATA_STORES manifest. */
|
|
448
|
+
dataStores?: TestDataStoreInput[];
|
|
449
|
+
/**
|
|
450
|
+
* Force the legacy single-store DATA_STORE_URL on or off. By default it is
|
|
451
|
+
* present only for the zero-config manifest, and omitted once a manifest is configured.
|
|
452
|
+
*/
|
|
453
|
+
legacyDataStoreUrl?: boolean;
|
|
440
454
|
/**
|
|
441
455
|
* Path to the schema.sql snapshot generated by
|
|
442
456
|
* `npx stardeck-data-store generate-types` (relative to the project root).
|
|
@@ -517,6 +531,7 @@ declare function parseWorkflowName(describeTitle: string): string | null;
|
|
|
517
531
|
|
|
518
532
|
declare const CONTROL_PLANE_TEST_URL = "https://control-plane.stardeck.test";
|
|
519
533
|
declare const DATA_STORE_TEST_HOST = "db.stardeck.test";
|
|
534
|
+
declare const DATA_STORE_TEST_URL = "postgresql://test:test@db.stardeck.test/main";
|
|
520
535
|
declare const STORAGE_TEST_URL = "https://storage.stardeck.test";
|
|
521
536
|
declare const STORAGE_TEST_HOST = "storage.stardeck.test";
|
|
522
537
|
declare const TEST_ENV_DEFAULTS: {
|
|
@@ -525,10 +540,9 @@ declare const TEST_ENV_DEFAULTS: {
|
|
|
525
540
|
readonly ORGANIZATION_ID: "00000000-0000-4000-8000-00000000000a";
|
|
526
541
|
readonly PROJECT_ID: "00000000-0000-4000-8000-00000000000b";
|
|
527
542
|
readonly DEPLOYMENT_ID: "00000000-0000-4000-8000-00000000000c";
|
|
528
|
-
readonly DATA_STORE_URL: "postgresql://test:test@db.stardeck.test/main";
|
|
529
543
|
readonly STORAGE_URL: "https://storage.stardeck.test";
|
|
530
544
|
};
|
|
531
545
|
/** Default location of the DDL snapshot written by `generate-types`. */
|
|
532
546
|
declare const DEFAULT_SCHEMA_PATH = "./src/generated/data-store-schema.sql";
|
|
533
547
|
|
|
534
|
-
export { type BindingInfo, CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedDisplay, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedPrint, type CapturedTestPrint, type CapturedUpload, DATA_STORE_TEST_HOST, DEFAULT_SCHEMA_PATH, type DeviceInfo, type PeripheralInfo, STORAGE_TEST_HOST, STORAGE_TEST_URL, type SessionTokens, type SimBoltCharge, type SimBoltConnection, type SimBoltIntentRecord, type SimBoltIntentStatus, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
|
|
548
|
+
export { type BindingInfo, CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedDisplay, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedPrint, type CapturedTestPrint, type CapturedUpload, DATA_STORE_TEST_HOST, DATA_STORE_TEST_URL, DEFAULT_SCHEMA_PATH, type DeviceInfo, type PeripheralInfo, STORAGE_TEST_HOST, STORAGE_TEST_URL, type SessionTokens, type SimBoltCharge, type SimBoltConnection, type SimBoltIntentRecord, type SimBoltIntentStatus, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, TestDataStoreInput, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as _stardeck_customer_apps_payments_sdk from '@stardeck-customer-apps/payments-sdk';
|
|
2
2
|
import * as next_server from 'next/server';
|
|
3
3
|
import { PGlite } from '@electric-sql/pglite';
|
|
4
|
+
import { T as TestDataStoreInput } from './data-store-manifest-Zwgcv0DJ.js';
|
|
5
|
+
export { S as STARDECK_DATA_STORES_ENV, a as TestDataStoreManifestEntry, b as buildTestDataStoreManifest } from './data-store-manifest-Zwgcv0DJ.js';
|
|
4
6
|
|
|
5
7
|
type RouteHandler<Req extends Request = Request, P = Record<string, string | string[]>> = (request: Req, context: {
|
|
6
8
|
params: Promise<P>;
|
|
@@ -250,6 +252,7 @@ interface CapturedMessage {
|
|
|
250
252
|
channel: "slack" | "line" | "facebook";
|
|
251
253
|
recipient: string;
|
|
252
254
|
body: {
|
|
255
|
+
type?: string;
|
|
253
256
|
text?: string;
|
|
254
257
|
blocks?: unknown[];
|
|
255
258
|
threadTs?: string;
|
|
@@ -259,6 +262,10 @@ interface CapturedMessage {
|
|
|
259
262
|
originalContentUrl?: string;
|
|
260
263
|
/** LINE image push — HTTPS URL of the preview; may be omitted when not sent. */
|
|
261
264
|
previewImageUrl?: string;
|
|
265
|
+
/** LINE Flex fallback text shown when the rich card cannot be rendered. */
|
|
266
|
+
altText?: string;
|
|
267
|
+
/** Complete LINE Flex bubble or carousel payload. */
|
|
268
|
+
contents?: Record<string, unknown>;
|
|
262
269
|
};
|
|
263
270
|
connectionId?: string;
|
|
264
271
|
sentAt: Date;
|
|
@@ -437,6 +444,13 @@ interface TestEdge {
|
|
|
437
444
|
get count(): number;
|
|
438
445
|
}
|
|
439
446
|
interface TestAppOptions {
|
|
447
|
+
/** Data stores exposed through the production-shaped STARDECK_DATA_STORES manifest. */
|
|
448
|
+
dataStores?: TestDataStoreInput[];
|
|
449
|
+
/**
|
|
450
|
+
* Force the legacy single-store DATA_STORE_URL on or off. By default it is
|
|
451
|
+
* present only for the zero-config manifest, and omitted once a manifest is configured.
|
|
452
|
+
*/
|
|
453
|
+
legacyDataStoreUrl?: boolean;
|
|
440
454
|
/**
|
|
441
455
|
* Path to the schema.sql snapshot generated by
|
|
442
456
|
* `npx stardeck-data-store generate-types` (relative to the project root).
|
|
@@ -517,6 +531,7 @@ declare function parseWorkflowName(describeTitle: string): string | null;
|
|
|
517
531
|
|
|
518
532
|
declare const CONTROL_PLANE_TEST_URL = "https://control-plane.stardeck.test";
|
|
519
533
|
declare const DATA_STORE_TEST_HOST = "db.stardeck.test";
|
|
534
|
+
declare const DATA_STORE_TEST_URL = "postgresql://test:test@db.stardeck.test/main";
|
|
520
535
|
declare const STORAGE_TEST_URL = "https://storage.stardeck.test";
|
|
521
536
|
declare const STORAGE_TEST_HOST = "storage.stardeck.test";
|
|
522
537
|
declare const TEST_ENV_DEFAULTS: {
|
|
@@ -525,10 +540,9 @@ declare const TEST_ENV_DEFAULTS: {
|
|
|
525
540
|
readonly ORGANIZATION_ID: "00000000-0000-4000-8000-00000000000a";
|
|
526
541
|
readonly PROJECT_ID: "00000000-0000-4000-8000-00000000000b";
|
|
527
542
|
readonly DEPLOYMENT_ID: "00000000-0000-4000-8000-00000000000c";
|
|
528
|
-
readonly DATA_STORE_URL: "postgresql://test:test@db.stardeck.test/main";
|
|
529
543
|
readonly STORAGE_URL: "https://storage.stardeck.test";
|
|
530
544
|
};
|
|
531
545
|
/** Default location of the DDL snapshot written by `generate-types`. */
|
|
532
546
|
declare const DEFAULT_SCHEMA_PATH = "./src/generated/data-store-schema.sql";
|
|
533
547
|
|
|
534
|
-
export { type BindingInfo, CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedDisplay, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedPrint, type CapturedTestPrint, type CapturedUpload, DATA_STORE_TEST_HOST, DEFAULT_SCHEMA_PATH, type DeviceInfo, type PeripheralInfo, STORAGE_TEST_HOST, STORAGE_TEST_URL, type SessionTokens, type SimBoltCharge, type SimBoltConnection, type SimBoltIntentRecord, type SimBoltIntentStatus, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
|
|
548
|
+
export { type BindingInfo, CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedDisplay, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedPrint, type CapturedTestPrint, type CapturedUpload, DATA_STORE_TEST_HOST, DATA_STORE_TEST_URL, DEFAULT_SCHEMA_PATH, type DeviceInfo, type PeripheralInfo, STORAGE_TEST_HOST, STORAGE_TEST_URL, type SessionTokens, type SimBoltCharge, type SimBoltConnection, type SimBoltIntentRecord, type SimBoltIntentStatus, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, TestDataStoreInput, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
|
package/dist/index.js
CHANGED
|
@@ -32,11 +32,14 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
CONTROL_PLANE_TEST_URL: () => CONTROL_PLANE_TEST_URL,
|
|
34
34
|
DATA_STORE_TEST_HOST: () => DATA_STORE_TEST_HOST,
|
|
35
|
+
DATA_STORE_TEST_URL: () => DATA_STORE_TEST_URL,
|
|
35
36
|
DEFAULT_SCHEMA_PATH: () => DEFAULT_SCHEMA_PATH,
|
|
37
|
+
STARDECK_DATA_STORES_ENV: () => STARDECK_DATA_STORES_ENV,
|
|
36
38
|
STORAGE_TEST_HOST: () => STORAGE_TEST_HOST,
|
|
37
39
|
STORAGE_TEST_URL: () => STORAGE_TEST_URL,
|
|
38
40
|
TEST_ENV_DEFAULTS: () => TEST_ENV_DEFAULTS,
|
|
39
41
|
WORKFLOW_NAME_PREFIX: () => WORKFLOW_NAME_PREFIX,
|
|
42
|
+
buildTestDataStoreManifest: () => buildTestDataStoreManifest,
|
|
40
43
|
callRoute: () => callRoute,
|
|
41
44
|
createTestApp: () => createTestApp,
|
|
42
45
|
describeWorkflow: () => describeWorkflow,
|
|
@@ -93,6 +96,7 @@ function globalSingleton(key, create) {
|
|
|
93
96
|
// src/state.ts
|
|
94
97
|
var state = globalSingleton("state", () => ({
|
|
95
98
|
db: null,
|
|
99
|
+
lastHarnessDataStoreManifest: null,
|
|
96
100
|
currentUser: null,
|
|
97
101
|
sessions: /* @__PURE__ */ new Map(),
|
|
98
102
|
refreshSessions: /* @__PURE__ */ new Map(),
|
|
@@ -141,6 +145,7 @@ function requireDb() {
|
|
|
141
145
|
var TEST_DOMAIN_SUFFIX = ".stardeck.test";
|
|
142
146
|
var CONTROL_PLANE_TEST_URL = "https://control-plane.stardeck.test";
|
|
143
147
|
var DATA_STORE_TEST_HOST = "db.stardeck.test";
|
|
148
|
+
var DATA_STORE_TEST_URL = `postgresql://test:test@${DATA_STORE_TEST_HOST}/main`;
|
|
144
149
|
var STORAGE_TEST_URL = "https://storage.stardeck.test";
|
|
145
150
|
var STORAGE_TEST_HOST = "storage.stardeck.test";
|
|
146
151
|
var TEST_ENV_DEFAULTS = {
|
|
@@ -149,7 +154,6 @@ var TEST_ENV_DEFAULTS = {
|
|
|
149
154
|
ORGANIZATION_ID: "00000000-0000-4000-8000-00000000000a",
|
|
150
155
|
PROJECT_ID: "00000000-0000-4000-8000-00000000000b",
|
|
151
156
|
DEPLOYMENT_ID: "00000000-0000-4000-8000-00000000000c",
|
|
152
|
-
DATA_STORE_URL: `postgresql://test:test@${DATA_STORE_TEST_HOST}/main`,
|
|
153
157
|
STORAGE_URL: STORAGE_TEST_URL
|
|
154
158
|
};
|
|
155
159
|
var DEFAULT_TEST_USER = {
|
|
@@ -2068,6 +2072,9 @@ function isValidLineImageUrl(url) {
|
|
|
2068
2072
|
return false;
|
|
2069
2073
|
}
|
|
2070
2074
|
}
|
|
2075
|
+
function isObject(value) {
|
|
2076
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2077
|
+
}
|
|
2071
2078
|
function captureMessage(channel, recipient, body, connectionId) {
|
|
2072
2079
|
const message = {
|
|
2073
2080
|
channel,
|
|
@@ -2079,7 +2086,8 @@ function captureMessage(channel, recipient, body, connectionId) {
|
|
|
2079
2086
|
messagingType: body.messagingType ? String(body.messagingType) : void 0,
|
|
2080
2087
|
tag: body.tag ? String(body.tag) : void 0,
|
|
2081
2088
|
originalContentUrl: body.originalContentUrl ? String(body.originalContentUrl) : void 0,
|
|
2082
|
-
previewImageUrl: body.previewImageUrl ? String(body.previewImageUrl) : void 0
|
|
2089
|
+
previewImageUrl: body.previewImageUrl ? String(body.previewImageUrl) : void 0,
|
|
2090
|
+
...body.type === "flex" && isObject(body.contents) ? { type: "flex", altText: String(body.altText), contents: body.contents } : {}
|
|
2083
2091
|
},
|
|
2084
2092
|
connectionId,
|
|
2085
2093
|
sentAt: /* @__PURE__ */ new Date()
|
|
@@ -2117,8 +2125,8 @@ async function handleMessagingRequest(request, channel, subPath) {
|
|
|
2117
2125
|
const userId = String(body.userId ?? "");
|
|
2118
2126
|
const message = body.message;
|
|
2119
2127
|
if (!userId) return failure("userId is required");
|
|
2120
|
-
if (!message || message.type !== "text" && message.type !== "image") {
|
|
2121
|
-
return failure("message must be { type: 'text' | 'image', ... }");
|
|
2128
|
+
if (!message || message.type !== "text" && message.type !== "image" && message.type !== "flex") {
|
|
2129
|
+
return failure("message must be { type: 'text' | 'image' | 'flex', ... }");
|
|
2122
2130
|
}
|
|
2123
2131
|
const connectionId = body.connectionId ? String(body.connectionId) : void 0;
|
|
2124
2132
|
if (message.type === "text") {
|
|
@@ -2129,7 +2137,7 @@ async function handleMessagingRequest(request, channel, subPath) {
|
|
|
2129
2137
|
return failure("message text exceeds maximum length");
|
|
2130
2138
|
}
|
|
2131
2139
|
captureMessage("line", userId, { text: message.text }, connectionId);
|
|
2132
|
-
} else {
|
|
2140
|
+
} else if (message.type === "image") {
|
|
2133
2141
|
const originalContentUrl = message.originalContentUrl;
|
|
2134
2142
|
if (!originalContentUrl || !isValidLineImageUrl(originalContentUrl)) {
|
|
2135
2143
|
return failure("originalContentUrl must be a valid HTTPS URL (max 2000 chars)");
|
|
@@ -2140,7 +2148,23 @@ async function handleMessagingRequest(request, channel, subPath) {
|
|
|
2140
2148
|
captureMessage(
|
|
2141
2149
|
"line",
|
|
2142
2150
|
userId,
|
|
2143
|
-
{
|
|
2151
|
+
{
|
|
2152
|
+
originalContentUrl,
|
|
2153
|
+
previewImageUrl: message.previewImageUrl ?? originalContentUrl
|
|
2154
|
+
},
|
|
2155
|
+
connectionId
|
|
2156
|
+
);
|
|
2157
|
+
} else {
|
|
2158
|
+
if (typeof message.altText !== "string" || !message.altText || message.altText.length > 400) {
|
|
2159
|
+
return failure("altText must be between 1 and 400 characters");
|
|
2160
|
+
}
|
|
2161
|
+
if (!isObject(message.contents) || message.contents.type !== "bubble" && message.contents.type !== "carousel") {
|
|
2162
|
+
return failure("contents.type must be 'bubble' or 'carousel'");
|
|
2163
|
+
}
|
|
2164
|
+
captureMessage(
|
|
2165
|
+
"line",
|
|
2166
|
+
userId,
|
|
2167
|
+
{ type: "flex", altText: message.altText, contents: message.contents },
|
|
2144
2168
|
connectionId
|
|
2145
2169
|
);
|
|
2146
2170
|
}
|
|
@@ -2603,11 +2627,89 @@ function uninstallFetchRouter() {
|
|
|
2603
2627
|
}
|
|
2604
2628
|
}
|
|
2605
2629
|
|
|
2630
|
+
// src/data-store-manifest.ts
|
|
2631
|
+
var STARDECK_DATA_STORES_ENV = "STARDECK_DATA_STORES";
|
|
2632
|
+
function dataStoreSlug(name) {
|
|
2633
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
2634
|
+
return slug || "store";
|
|
2635
|
+
}
|
|
2636
|
+
function buildTestDataStoreManifest(inputs = [{}]) {
|
|
2637
|
+
const takenSlugs = /* @__PURE__ */ new Set();
|
|
2638
|
+
const takenBindingKeys = /* @__PURE__ */ new Set();
|
|
2639
|
+
const takenIds = /* @__PURE__ */ new Set();
|
|
2640
|
+
return inputs.map((input, index) => {
|
|
2641
|
+
for (const field of ["bindingKey", "slug", "name"]) {
|
|
2642
|
+
if (input[field] === "") {
|
|
2643
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].${field} cannot be empty.`);
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
let slug;
|
|
2647
|
+
if (input.slug !== void 0) {
|
|
2648
|
+
slug = input.slug;
|
|
2649
|
+
if (takenSlugs.has(slug)) {
|
|
2650
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].slug must be unique.`);
|
|
2651
|
+
}
|
|
2652
|
+
} else {
|
|
2653
|
+
const base = input.name !== void 0 ? dataStoreSlug(input.name) : index === 0 ? "main" : null;
|
|
2654
|
+
if (base === null) {
|
|
2655
|
+
throw new Error(
|
|
2656
|
+
`[stardeck-testing] dataStores[${index}] requires a slug or name. Only the first entry defaults to the "main" slug.`
|
|
2657
|
+
);
|
|
2658
|
+
}
|
|
2659
|
+
slug = base;
|
|
2660
|
+
let suffix = 2;
|
|
2661
|
+
while (takenSlugs.has(slug)) {
|
|
2662
|
+
slug = `${base}_${suffix}`;
|
|
2663
|
+
suffix++;
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
const id = input.id ?? `00000000-0000-4000-8000-${String(index + 1).padStart(12, "0")}`;
|
|
2667
|
+
if (takenIds.has(id)) {
|
|
2668
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].id must be unique.`);
|
|
2669
|
+
}
|
|
2670
|
+
if (input.bindingKey !== void 0 && takenBindingKeys.has(input.bindingKey)) {
|
|
2671
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].bindingKey must be unique.`);
|
|
2672
|
+
}
|
|
2673
|
+
takenSlugs.add(slug);
|
|
2674
|
+
takenIds.add(id);
|
|
2675
|
+
if (input.bindingKey !== void 0) takenBindingKeys.add(input.bindingKey);
|
|
2676
|
+
const storeType = input.storeType ?? "database";
|
|
2677
|
+
return {
|
|
2678
|
+
id,
|
|
2679
|
+
name: input.name ?? slug,
|
|
2680
|
+
slug,
|
|
2681
|
+
storeType,
|
|
2682
|
+
accessLevel: input.accessLevel ?? "admin",
|
|
2683
|
+
...storeType === "database" ? { url: input.url ?? DATA_STORE_TEST_URL } : {},
|
|
2684
|
+
...input.bindingKey ? { bindingKey: input.bindingKey } : {}
|
|
2685
|
+
};
|
|
2686
|
+
});
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2606
2689
|
// src/test-app.ts
|
|
2607
|
-
function applyTestEnv() {
|
|
2690
|
+
function applyTestEnv(options) {
|
|
2608
2691
|
for (const [key, value] of Object.entries(TEST_ENV_DEFAULTS)) {
|
|
2609
2692
|
process.env[key] = value;
|
|
2610
2693
|
}
|
|
2694
|
+
let manifestAdopted = true;
|
|
2695
|
+
if (options.dataStores !== void 0) {
|
|
2696
|
+
const manifest = JSON.stringify(buildTestDataStoreManifest(options.dataStores));
|
|
2697
|
+
process.env[STARDECK_DATA_STORES_ENV] = manifest;
|
|
2698
|
+
state.lastHarnessDataStoreManifest = manifest;
|
|
2699
|
+
} else {
|
|
2700
|
+
const currentManifest = process.env[STARDECK_DATA_STORES_ENV];
|
|
2701
|
+
if (!currentManifest || currentManifest === state.lastHarnessDataStoreManifest) {
|
|
2702
|
+
const manifest = JSON.stringify(buildTestDataStoreManifest());
|
|
2703
|
+
process.env[STARDECK_DATA_STORES_ENV] = manifest;
|
|
2704
|
+
state.lastHarnessDataStoreManifest = manifest;
|
|
2705
|
+
manifestAdopted = false;
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
if (options.legacyDataStoreUrl === true || !manifestAdopted && options.legacyDataStoreUrl === void 0) {
|
|
2709
|
+
process.env.DATA_STORE_URL = DATA_STORE_TEST_URL;
|
|
2710
|
+
} else {
|
|
2711
|
+
delete process.env.DATA_STORE_URL;
|
|
2712
|
+
}
|
|
2611
2713
|
delete process.env.DISABLE_AUTH;
|
|
2612
2714
|
delete process.env.NEXT_PUBLIC_DISABLE_AUTH;
|
|
2613
2715
|
delete process.env.SANDBOX_MODE;
|
|
@@ -2640,7 +2742,7 @@ async function createTestApp(options = {}) {
|
|
|
2640
2742
|
"[stardeck-testing] A test app is already active in this test file. Call `app.close()` first, or share one app per file."
|
|
2641
2743
|
);
|
|
2642
2744
|
}
|
|
2643
|
-
applyTestEnv();
|
|
2745
|
+
applyTestEnv(options);
|
|
2644
2746
|
state.allowNetwork = options.allowNetwork ?? false;
|
|
2645
2747
|
installFetchRouter();
|
|
2646
2748
|
const db = await createPgliteDatabase();
|
|
@@ -2742,11 +2844,14 @@ function parseWorkflowName(describeTitle) {
|
|
|
2742
2844
|
0 && (module.exports = {
|
|
2743
2845
|
CONTROL_PLANE_TEST_URL,
|
|
2744
2846
|
DATA_STORE_TEST_HOST,
|
|
2847
|
+
DATA_STORE_TEST_URL,
|
|
2745
2848
|
DEFAULT_SCHEMA_PATH,
|
|
2849
|
+
STARDECK_DATA_STORES_ENV,
|
|
2746
2850
|
STORAGE_TEST_HOST,
|
|
2747
2851
|
STORAGE_TEST_URL,
|
|
2748
2852
|
TEST_ENV_DEFAULTS,
|
|
2749
2853
|
WORKFLOW_NAME_PREFIX,
|
|
2854
|
+
buildTestDataStoreManifest,
|
|
2750
2855
|
callRoute,
|
|
2751
2856
|
createTestApp,
|
|
2752
2857
|
describeWorkflow,
|
package/dist/index.mjs
CHANGED
|
@@ -47,6 +47,7 @@ function globalSingleton(key, create) {
|
|
|
47
47
|
// src/state.ts
|
|
48
48
|
var state = globalSingleton("state", () => ({
|
|
49
49
|
db: null,
|
|
50
|
+
lastHarnessDataStoreManifest: null,
|
|
50
51
|
currentUser: null,
|
|
51
52
|
sessions: /* @__PURE__ */ new Map(),
|
|
52
53
|
refreshSessions: /* @__PURE__ */ new Map(),
|
|
@@ -95,6 +96,7 @@ function requireDb() {
|
|
|
95
96
|
var TEST_DOMAIN_SUFFIX = ".stardeck.test";
|
|
96
97
|
var CONTROL_PLANE_TEST_URL = "https://control-plane.stardeck.test";
|
|
97
98
|
var DATA_STORE_TEST_HOST = "db.stardeck.test";
|
|
99
|
+
var DATA_STORE_TEST_URL = `postgresql://test:test@${DATA_STORE_TEST_HOST}/main`;
|
|
98
100
|
var STORAGE_TEST_URL = "https://storage.stardeck.test";
|
|
99
101
|
var STORAGE_TEST_HOST = "storage.stardeck.test";
|
|
100
102
|
var TEST_ENV_DEFAULTS = {
|
|
@@ -103,7 +105,6 @@ var TEST_ENV_DEFAULTS = {
|
|
|
103
105
|
ORGANIZATION_ID: "00000000-0000-4000-8000-00000000000a",
|
|
104
106
|
PROJECT_ID: "00000000-0000-4000-8000-00000000000b",
|
|
105
107
|
DEPLOYMENT_ID: "00000000-0000-4000-8000-00000000000c",
|
|
106
|
-
DATA_STORE_URL: `postgresql://test:test@${DATA_STORE_TEST_HOST}/main`,
|
|
107
108
|
STORAGE_URL: STORAGE_TEST_URL
|
|
108
109
|
};
|
|
109
110
|
var DEFAULT_TEST_USER = {
|
|
@@ -2022,6 +2023,9 @@ function isValidLineImageUrl(url) {
|
|
|
2022
2023
|
return false;
|
|
2023
2024
|
}
|
|
2024
2025
|
}
|
|
2026
|
+
function isObject(value) {
|
|
2027
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2028
|
+
}
|
|
2025
2029
|
function captureMessage(channel, recipient, body, connectionId) {
|
|
2026
2030
|
const message = {
|
|
2027
2031
|
channel,
|
|
@@ -2033,7 +2037,8 @@ function captureMessage(channel, recipient, body, connectionId) {
|
|
|
2033
2037
|
messagingType: body.messagingType ? String(body.messagingType) : void 0,
|
|
2034
2038
|
tag: body.tag ? String(body.tag) : void 0,
|
|
2035
2039
|
originalContentUrl: body.originalContentUrl ? String(body.originalContentUrl) : void 0,
|
|
2036
|
-
previewImageUrl: body.previewImageUrl ? String(body.previewImageUrl) : void 0
|
|
2040
|
+
previewImageUrl: body.previewImageUrl ? String(body.previewImageUrl) : void 0,
|
|
2041
|
+
...body.type === "flex" && isObject(body.contents) ? { type: "flex", altText: String(body.altText), contents: body.contents } : {}
|
|
2037
2042
|
},
|
|
2038
2043
|
connectionId,
|
|
2039
2044
|
sentAt: /* @__PURE__ */ new Date()
|
|
@@ -2071,8 +2076,8 @@ async function handleMessagingRequest(request, channel, subPath) {
|
|
|
2071
2076
|
const userId = String(body.userId ?? "");
|
|
2072
2077
|
const message = body.message;
|
|
2073
2078
|
if (!userId) return failure("userId is required");
|
|
2074
|
-
if (!message || message.type !== "text" && message.type !== "image") {
|
|
2075
|
-
return failure("message must be { type: 'text' | 'image', ... }");
|
|
2079
|
+
if (!message || message.type !== "text" && message.type !== "image" && message.type !== "flex") {
|
|
2080
|
+
return failure("message must be { type: 'text' | 'image' | 'flex', ... }");
|
|
2076
2081
|
}
|
|
2077
2082
|
const connectionId = body.connectionId ? String(body.connectionId) : void 0;
|
|
2078
2083
|
if (message.type === "text") {
|
|
@@ -2083,7 +2088,7 @@ async function handleMessagingRequest(request, channel, subPath) {
|
|
|
2083
2088
|
return failure("message text exceeds maximum length");
|
|
2084
2089
|
}
|
|
2085
2090
|
captureMessage("line", userId, { text: message.text }, connectionId);
|
|
2086
|
-
} else {
|
|
2091
|
+
} else if (message.type === "image") {
|
|
2087
2092
|
const originalContentUrl = message.originalContentUrl;
|
|
2088
2093
|
if (!originalContentUrl || !isValidLineImageUrl(originalContentUrl)) {
|
|
2089
2094
|
return failure("originalContentUrl must be a valid HTTPS URL (max 2000 chars)");
|
|
@@ -2094,7 +2099,23 @@ async function handleMessagingRequest(request, channel, subPath) {
|
|
|
2094
2099
|
captureMessage(
|
|
2095
2100
|
"line",
|
|
2096
2101
|
userId,
|
|
2097
|
-
{
|
|
2102
|
+
{
|
|
2103
|
+
originalContentUrl,
|
|
2104
|
+
previewImageUrl: message.previewImageUrl ?? originalContentUrl
|
|
2105
|
+
},
|
|
2106
|
+
connectionId
|
|
2107
|
+
);
|
|
2108
|
+
} else {
|
|
2109
|
+
if (typeof message.altText !== "string" || !message.altText || message.altText.length > 400) {
|
|
2110
|
+
return failure("altText must be between 1 and 400 characters");
|
|
2111
|
+
}
|
|
2112
|
+
if (!isObject(message.contents) || message.contents.type !== "bubble" && message.contents.type !== "carousel") {
|
|
2113
|
+
return failure("contents.type must be 'bubble' or 'carousel'");
|
|
2114
|
+
}
|
|
2115
|
+
captureMessage(
|
|
2116
|
+
"line",
|
|
2117
|
+
userId,
|
|
2118
|
+
{ type: "flex", altText: message.altText, contents: message.contents },
|
|
2098
2119
|
connectionId
|
|
2099
2120
|
);
|
|
2100
2121
|
}
|
|
@@ -2557,11 +2578,89 @@ function uninstallFetchRouter() {
|
|
|
2557
2578
|
}
|
|
2558
2579
|
}
|
|
2559
2580
|
|
|
2581
|
+
// src/data-store-manifest.ts
|
|
2582
|
+
var STARDECK_DATA_STORES_ENV = "STARDECK_DATA_STORES";
|
|
2583
|
+
function dataStoreSlug(name) {
|
|
2584
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
2585
|
+
return slug || "store";
|
|
2586
|
+
}
|
|
2587
|
+
function buildTestDataStoreManifest(inputs = [{}]) {
|
|
2588
|
+
const takenSlugs = /* @__PURE__ */ new Set();
|
|
2589
|
+
const takenBindingKeys = /* @__PURE__ */ new Set();
|
|
2590
|
+
const takenIds = /* @__PURE__ */ new Set();
|
|
2591
|
+
return inputs.map((input, index) => {
|
|
2592
|
+
for (const field of ["bindingKey", "slug", "name"]) {
|
|
2593
|
+
if (input[field] === "") {
|
|
2594
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].${field} cannot be empty.`);
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
let slug;
|
|
2598
|
+
if (input.slug !== void 0) {
|
|
2599
|
+
slug = input.slug;
|
|
2600
|
+
if (takenSlugs.has(slug)) {
|
|
2601
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].slug must be unique.`);
|
|
2602
|
+
}
|
|
2603
|
+
} else {
|
|
2604
|
+
const base = input.name !== void 0 ? dataStoreSlug(input.name) : index === 0 ? "main" : null;
|
|
2605
|
+
if (base === null) {
|
|
2606
|
+
throw new Error(
|
|
2607
|
+
`[stardeck-testing] dataStores[${index}] requires a slug or name. Only the first entry defaults to the "main" slug.`
|
|
2608
|
+
);
|
|
2609
|
+
}
|
|
2610
|
+
slug = base;
|
|
2611
|
+
let suffix = 2;
|
|
2612
|
+
while (takenSlugs.has(slug)) {
|
|
2613
|
+
slug = `${base}_${suffix}`;
|
|
2614
|
+
suffix++;
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
const id = input.id ?? `00000000-0000-4000-8000-${String(index + 1).padStart(12, "0")}`;
|
|
2618
|
+
if (takenIds.has(id)) {
|
|
2619
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].id must be unique.`);
|
|
2620
|
+
}
|
|
2621
|
+
if (input.bindingKey !== void 0 && takenBindingKeys.has(input.bindingKey)) {
|
|
2622
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].bindingKey must be unique.`);
|
|
2623
|
+
}
|
|
2624
|
+
takenSlugs.add(slug);
|
|
2625
|
+
takenIds.add(id);
|
|
2626
|
+
if (input.bindingKey !== void 0) takenBindingKeys.add(input.bindingKey);
|
|
2627
|
+
const storeType = input.storeType ?? "database";
|
|
2628
|
+
return {
|
|
2629
|
+
id,
|
|
2630
|
+
name: input.name ?? slug,
|
|
2631
|
+
slug,
|
|
2632
|
+
storeType,
|
|
2633
|
+
accessLevel: input.accessLevel ?? "admin",
|
|
2634
|
+
...storeType === "database" ? { url: input.url ?? DATA_STORE_TEST_URL } : {},
|
|
2635
|
+
...input.bindingKey ? { bindingKey: input.bindingKey } : {}
|
|
2636
|
+
};
|
|
2637
|
+
});
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2560
2640
|
// src/test-app.ts
|
|
2561
|
-
function applyTestEnv() {
|
|
2641
|
+
function applyTestEnv(options) {
|
|
2562
2642
|
for (const [key, value] of Object.entries(TEST_ENV_DEFAULTS)) {
|
|
2563
2643
|
process.env[key] = value;
|
|
2564
2644
|
}
|
|
2645
|
+
let manifestAdopted = true;
|
|
2646
|
+
if (options.dataStores !== void 0) {
|
|
2647
|
+
const manifest = JSON.stringify(buildTestDataStoreManifest(options.dataStores));
|
|
2648
|
+
process.env[STARDECK_DATA_STORES_ENV] = manifest;
|
|
2649
|
+
state.lastHarnessDataStoreManifest = manifest;
|
|
2650
|
+
} else {
|
|
2651
|
+
const currentManifest = process.env[STARDECK_DATA_STORES_ENV];
|
|
2652
|
+
if (!currentManifest || currentManifest === state.lastHarnessDataStoreManifest) {
|
|
2653
|
+
const manifest = JSON.stringify(buildTestDataStoreManifest());
|
|
2654
|
+
process.env[STARDECK_DATA_STORES_ENV] = manifest;
|
|
2655
|
+
state.lastHarnessDataStoreManifest = manifest;
|
|
2656
|
+
manifestAdopted = false;
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
if (options.legacyDataStoreUrl === true || !manifestAdopted && options.legacyDataStoreUrl === void 0) {
|
|
2660
|
+
process.env.DATA_STORE_URL = DATA_STORE_TEST_URL;
|
|
2661
|
+
} else {
|
|
2662
|
+
delete process.env.DATA_STORE_URL;
|
|
2663
|
+
}
|
|
2565
2664
|
delete process.env.DISABLE_AUTH;
|
|
2566
2665
|
delete process.env.NEXT_PUBLIC_DISABLE_AUTH;
|
|
2567
2666
|
delete process.env.SANDBOX_MODE;
|
|
@@ -2594,7 +2693,7 @@ async function createTestApp(options = {}) {
|
|
|
2594
2693
|
"[stardeck-testing] A test app is already active in this test file. Call `app.close()` first, or share one app per file."
|
|
2595
2694
|
);
|
|
2596
2695
|
}
|
|
2597
|
-
applyTestEnv();
|
|
2696
|
+
applyTestEnv(options);
|
|
2598
2697
|
state.allowNetwork = options.allowNetwork ?? false;
|
|
2599
2698
|
installFetchRouter();
|
|
2600
2699
|
const db = await createPgliteDatabase();
|
|
@@ -2695,11 +2794,14 @@ function parseWorkflowName(describeTitle) {
|
|
|
2695
2794
|
export {
|
|
2696
2795
|
CONTROL_PLANE_TEST_URL,
|
|
2697
2796
|
DATA_STORE_TEST_HOST,
|
|
2797
|
+
DATA_STORE_TEST_URL,
|
|
2698
2798
|
DEFAULT_SCHEMA_PATH,
|
|
2799
|
+
STARDECK_DATA_STORES_ENV,
|
|
2699
2800
|
STORAGE_TEST_HOST,
|
|
2700
2801
|
STORAGE_TEST_URL,
|
|
2701
2802
|
TEST_ENV_DEFAULTS,
|
|
2702
2803
|
WORKFLOW_NAME_PREFIX,
|
|
2804
|
+
buildTestDataStoreManifest,
|
|
2703
2805
|
callRoute,
|
|
2704
2806
|
createTestApp,
|
|
2705
2807
|
describeWorkflow,
|
|
@@ -39,6 +39,7 @@ function globalSingleton(key, create) {
|
|
|
39
39
|
// src/state.ts
|
|
40
40
|
var state = globalSingleton("state", () => ({
|
|
41
41
|
db: null,
|
|
42
|
+
lastHarnessDataStoreManifest: null,
|
|
42
43
|
currentUser: null,
|
|
43
44
|
sessions: /* @__PURE__ */ new Map(),
|
|
44
45
|
refreshSessions: /* @__PURE__ */ new Map(),
|
|
@@ -12,6 +12,7 @@ function globalSingleton(key, create) {
|
|
|
12
12
|
// src/state.ts
|
|
13
13
|
var state = globalSingleton("state", () => ({
|
|
14
14
|
db: null,
|
|
15
|
+
lastHarnessDataStoreManifest: null,
|
|
15
16
|
currentUser: null,
|
|
16
17
|
sessions: /* @__PURE__ */ new Map(),
|
|
17
18
|
refreshSessions: /* @__PURE__ */ new Map(),
|
package/dist/setup.js
CHANGED
|
@@ -33,6 +33,7 @@ function globalSingleton(key, create) {
|
|
|
33
33
|
// src/state.ts
|
|
34
34
|
var state = globalSingleton("state", () => ({
|
|
35
35
|
db: null,
|
|
36
|
+
lastHarnessDataStoreManifest: null,
|
|
36
37
|
currentUser: null,
|
|
37
38
|
sessions: /* @__PURE__ */ new Map(),
|
|
38
39
|
refreshSessions: /* @__PURE__ */ new Map(),
|
|
@@ -81,6 +82,7 @@ function requireDb() {
|
|
|
81
82
|
var TEST_DOMAIN_SUFFIX = ".stardeck.test";
|
|
82
83
|
var CONTROL_PLANE_TEST_URL = "https://control-plane.stardeck.test";
|
|
83
84
|
var DATA_STORE_TEST_HOST = "db.stardeck.test";
|
|
85
|
+
var DATA_STORE_TEST_URL = `postgresql://test:test@${DATA_STORE_TEST_HOST}/main`;
|
|
84
86
|
var STORAGE_TEST_URL = "https://storage.stardeck.test";
|
|
85
87
|
var STORAGE_TEST_HOST = "storage.stardeck.test";
|
|
86
88
|
var TEST_ENV_DEFAULTS = {
|
|
@@ -89,7 +91,6 @@ var TEST_ENV_DEFAULTS = {
|
|
|
89
91
|
ORGANIZATION_ID: "00000000-0000-4000-8000-00000000000a",
|
|
90
92
|
PROJECT_ID: "00000000-0000-4000-8000-00000000000b",
|
|
91
93
|
DEPLOYMENT_ID: "00000000-0000-4000-8000-00000000000c",
|
|
92
|
-
DATA_STORE_URL: `postgresql://test:test@${DATA_STORE_TEST_HOST}/main`,
|
|
93
94
|
STORAGE_URL: STORAGE_TEST_URL
|
|
94
95
|
};
|
|
95
96
|
|
|
@@ -1621,6 +1622,9 @@ function isValidLineImageUrl(url) {
|
|
|
1621
1622
|
return false;
|
|
1622
1623
|
}
|
|
1623
1624
|
}
|
|
1625
|
+
function isObject(value) {
|
|
1626
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1627
|
+
}
|
|
1624
1628
|
function captureMessage(channel, recipient, body, connectionId) {
|
|
1625
1629
|
const message = {
|
|
1626
1630
|
channel,
|
|
@@ -1632,7 +1636,8 @@ function captureMessage(channel, recipient, body, connectionId) {
|
|
|
1632
1636
|
messagingType: body.messagingType ? String(body.messagingType) : void 0,
|
|
1633
1637
|
tag: body.tag ? String(body.tag) : void 0,
|
|
1634
1638
|
originalContentUrl: body.originalContentUrl ? String(body.originalContentUrl) : void 0,
|
|
1635
|
-
previewImageUrl: body.previewImageUrl ? String(body.previewImageUrl) : void 0
|
|
1639
|
+
previewImageUrl: body.previewImageUrl ? String(body.previewImageUrl) : void 0,
|
|
1640
|
+
...body.type === "flex" && isObject(body.contents) ? { type: "flex", altText: String(body.altText), contents: body.contents } : {}
|
|
1636
1641
|
},
|
|
1637
1642
|
connectionId,
|
|
1638
1643
|
sentAt: /* @__PURE__ */ new Date()
|
|
@@ -1670,8 +1675,8 @@ async function handleMessagingRequest(request, channel, subPath) {
|
|
|
1670
1675
|
const userId = String(body.userId ?? "");
|
|
1671
1676
|
const message = body.message;
|
|
1672
1677
|
if (!userId) return failure("userId is required");
|
|
1673
|
-
if (!message || message.type !== "text" && message.type !== "image") {
|
|
1674
|
-
return failure("message must be { type: 'text' | 'image', ... }");
|
|
1678
|
+
if (!message || message.type !== "text" && message.type !== "image" && message.type !== "flex") {
|
|
1679
|
+
return failure("message must be { type: 'text' | 'image' | 'flex', ... }");
|
|
1675
1680
|
}
|
|
1676
1681
|
const connectionId = body.connectionId ? String(body.connectionId) : void 0;
|
|
1677
1682
|
if (message.type === "text") {
|
|
@@ -1682,7 +1687,7 @@ async function handleMessagingRequest(request, channel, subPath) {
|
|
|
1682
1687
|
return failure("message text exceeds maximum length");
|
|
1683
1688
|
}
|
|
1684
1689
|
captureMessage("line", userId, { text: message.text }, connectionId);
|
|
1685
|
-
} else {
|
|
1690
|
+
} else if (message.type === "image") {
|
|
1686
1691
|
const originalContentUrl = message.originalContentUrl;
|
|
1687
1692
|
if (!originalContentUrl || !isValidLineImageUrl(originalContentUrl)) {
|
|
1688
1693
|
return failure("originalContentUrl must be a valid HTTPS URL (max 2000 chars)");
|
|
@@ -1693,7 +1698,23 @@ async function handleMessagingRequest(request, channel, subPath) {
|
|
|
1693
1698
|
captureMessage(
|
|
1694
1699
|
"line",
|
|
1695
1700
|
userId,
|
|
1696
|
-
{
|
|
1701
|
+
{
|
|
1702
|
+
originalContentUrl,
|
|
1703
|
+
previewImageUrl: message.previewImageUrl ?? originalContentUrl
|
|
1704
|
+
},
|
|
1705
|
+
connectionId
|
|
1706
|
+
);
|
|
1707
|
+
} else {
|
|
1708
|
+
if (typeof message.altText !== "string" || !message.altText || message.altText.length > 400) {
|
|
1709
|
+
return failure("altText must be between 1 and 400 characters");
|
|
1710
|
+
}
|
|
1711
|
+
if (!isObject(message.contents) || message.contents.type !== "bubble" && message.contents.type !== "carousel") {
|
|
1712
|
+
return failure("contents.type must be 'bubble' or 'carousel'");
|
|
1713
|
+
}
|
|
1714
|
+
captureMessage(
|
|
1715
|
+
"line",
|
|
1716
|
+
userId,
|
|
1717
|
+
{ type: "flex", altText: message.altText, contents: message.contents },
|
|
1697
1718
|
connectionId
|
|
1698
1719
|
);
|
|
1699
1720
|
}
|
package/dist/setup.mjs
CHANGED
|
@@ -9,6 +9,7 @@ function globalSingleton(key, create) {
|
|
|
9
9
|
// src/state.ts
|
|
10
10
|
var state = globalSingleton("state", () => ({
|
|
11
11
|
db: null,
|
|
12
|
+
lastHarnessDataStoreManifest: null,
|
|
12
13
|
currentUser: null,
|
|
13
14
|
sessions: /* @__PURE__ */ new Map(),
|
|
14
15
|
refreshSessions: /* @__PURE__ */ new Map(),
|
|
@@ -57,6 +58,7 @@ function requireDb() {
|
|
|
57
58
|
var TEST_DOMAIN_SUFFIX = ".stardeck.test";
|
|
58
59
|
var CONTROL_PLANE_TEST_URL = "https://control-plane.stardeck.test";
|
|
59
60
|
var DATA_STORE_TEST_HOST = "db.stardeck.test";
|
|
61
|
+
var DATA_STORE_TEST_URL = `postgresql://test:test@${DATA_STORE_TEST_HOST}/main`;
|
|
60
62
|
var STORAGE_TEST_URL = "https://storage.stardeck.test";
|
|
61
63
|
var STORAGE_TEST_HOST = "storage.stardeck.test";
|
|
62
64
|
var TEST_ENV_DEFAULTS = {
|
|
@@ -65,7 +67,6 @@ var TEST_ENV_DEFAULTS = {
|
|
|
65
67
|
ORGANIZATION_ID: "00000000-0000-4000-8000-00000000000a",
|
|
66
68
|
PROJECT_ID: "00000000-0000-4000-8000-00000000000b",
|
|
67
69
|
DEPLOYMENT_ID: "00000000-0000-4000-8000-00000000000c",
|
|
68
|
-
DATA_STORE_URL: `postgresql://test:test@${DATA_STORE_TEST_HOST}/main`,
|
|
69
70
|
STORAGE_URL: STORAGE_TEST_URL
|
|
70
71
|
};
|
|
71
72
|
|
|
@@ -1597,6 +1598,9 @@ function isValidLineImageUrl(url) {
|
|
|
1597
1598
|
return false;
|
|
1598
1599
|
}
|
|
1599
1600
|
}
|
|
1601
|
+
function isObject(value) {
|
|
1602
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1603
|
+
}
|
|
1600
1604
|
function captureMessage(channel, recipient, body, connectionId) {
|
|
1601
1605
|
const message = {
|
|
1602
1606
|
channel,
|
|
@@ -1608,7 +1612,8 @@ function captureMessage(channel, recipient, body, connectionId) {
|
|
|
1608
1612
|
messagingType: body.messagingType ? String(body.messagingType) : void 0,
|
|
1609
1613
|
tag: body.tag ? String(body.tag) : void 0,
|
|
1610
1614
|
originalContentUrl: body.originalContentUrl ? String(body.originalContentUrl) : void 0,
|
|
1611
|
-
previewImageUrl: body.previewImageUrl ? String(body.previewImageUrl) : void 0
|
|
1615
|
+
previewImageUrl: body.previewImageUrl ? String(body.previewImageUrl) : void 0,
|
|
1616
|
+
...body.type === "flex" && isObject(body.contents) ? { type: "flex", altText: String(body.altText), contents: body.contents } : {}
|
|
1612
1617
|
},
|
|
1613
1618
|
connectionId,
|
|
1614
1619
|
sentAt: /* @__PURE__ */ new Date()
|
|
@@ -1646,8 +1651,8 @@ async function handleMessagingRequest(request, channel, subPath) {
|
|
|
1646
1651
|
const userId = String(body.userId ?? "");
|
|
1647
1652
|
const message = body.message;
|
|
1648
1653
|
if (!userId) return failure("userId is required");
|
|
1649
|
-
if (!message || message.type !== "text" && message.type !== "image") {
|
|
1650
|
-
return failure("message must be { type: 'text' | 'image', ... }");
|
|
1654
|
+
if (!message || message.type !== "text" && message.type !== "image" && message.type !== "flex") {
|
|
1655
|
+
return failure("message must be { type: 'text' | 'image' | 'flex', ... }");
|
|
1651
1656
|
}
|
|
1652
1657
|
const connectionId = body.connectionId ? String(body.connectionId) : void 0;
|
|
1653
1658
|
if (message.type === "text") {
|
|
@@ -1658,7 +1663,7 @@ async function handleMessagingRequest(request, channel, subPath) {
|
|
|
1658
1663
|
return failure("message text exceeds maximum length");
|
|
1659
1664
|
}
|
|
1660
1665
|
captureMessage("line", userId, { text: message.text }, connectionId);
|
|
1661
|
-
} else {
|
|
1666
|
+
} else if (message.type === "image") {
|
|
1662
1667
|
const originalContentUrl = message.originalContentUrl;
|
|
1663
1668
|
if (!originalContentUrl || !isValidLineImageUrl(originalContentUrl)) {
|
|
1664
1669
|
return failure("originalContentUrl must be a valid HTTPS URL (max 2000 chars)");
|
|
@@ -1669,7 +1674,23 @@ async function handleMessagingRequest(request, channel, subPath) {
|
|
|
1669
1674
|
captureMessage(
|
|
1670
1675
|
"line",
|
|
1671
1676
|
userId,
|
|
1672
|
-
{
|
|
1677
|
+
{
|
|
1678
|
+
originalContentUrl,
|
|
1679
|
+
previewImageUrl: message.previewImageUrl ?? originalContentUrl
|
|
1680
|
+
},
|
|
1681
|
+
connectionId
|
|
1682
|
+
);
|
|
1683
|
+
} else {
|
|
1684
|
+
if (typeof message.altText !== "string" || !message.altText || message.altText.length > 400) {
|
|
1685
|
+
return failure("altText must be between 1 and 400 characters");
|
|
1686
|
+
}
|
|
1687
|
+
if (!isObject(message.contents) || message.contents.type !== "bubble" && message.contents.type !== "carousel") {
|
|
1688
|
+
return failure("contents.type must be 'bubble' or 'carousel'");
|
|
1689
|
+
}
|
|
1690
|
+
captureMessage(
|
|
1691
|
+
"line",
|
|
1692
|
+
userId,
|
|
1693
|
+
{ type: "flex", altText: message.altText, contents: message.contents },
|
|
1673
1694
|
connectionId
|
|
1674
1695
|
);
|
|
1675
1696
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stardeck-customer-apps/testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.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,7 +66,7 @@
|
|
|
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.13.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",
|