deepspace 0.7.0 → 0.8.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +22 -8
  3. package/dist/cli.js +10306 -5256
  4. package/dist/cli.js.map +1 -1
  5. package/dist/feature-installer.js +638 -0
  6. package/dist/feature-installer.js.map +1 -0
  7. package/dist/index.d.ts +107 -117
  8. package/dist/index.js +610 -779
  9. package/dist/index.js.map +1 -1
  10. package/dist/server.d.ts +403 -167
  11. package/dist/server.js +498 -367
  12. package/dist/server.js.map +1 -1
  13. package/dist/testing.d.ts +115 -0
  14. package/dist/testing.js +3 -3
  15. package/dist/testing.js.map +1 -1
  16. package/dist/worker.d.ts +397 -160
  17. package/dist/worker.js +506 -367
  18. package/dist/worker.js.map +1 -1
  19. package/features/ai-chat/feature.json +2 -0
  20. package/features/ai-chat/src/AiChatPage.tsx +2 -3
  21. package/features/ai-chat/src/ChatPanel.messages.tsx +344 -0
  22. package/features/ai-chat/src/ChatPanel.stream.ts +397 -0
  23. package/features/ai-chat/src/ChatPanel.tsx +158 -862
  24. package/features/ai-chat/src/__tests__/ChatPanel.stream.test.ts +163 -0
  25. package/features/canvas/src/CanvasListPage.tsx +1 -1
  26. package/features/docs/feature.json +4 -8
  27. package/features/docs/src/DocsEditorPage.tsx +33 -326
  28. package/features/docs/src/DocsListPage.tsx +33 -29
  29. package/features/docs/src/InviteDialog.tsx +11 -40
  30. package/features/docs/src/__tests__/docs-presence-access.test.ts +71 -0
  31. package/features/docs/src/docs-library-types.ts +28 -3
  32. package/features/docs/src/docs-ui.css +1 -4
  33. package/features/docs/src/use-docs-presence-access.ts +330 -0
  34. package/features/file-attachments/feature.json +2 -2
  35. package/features/file-attachments/src/AttachmentPreview.tsx +428 -0
  36. package/features/file-attachments/src/AttachmentsPage.tsx +100 -462
  37. package/features/file-attachments/src/useFilePreview.ts +54 -68
  38. package/features/file-manager/src/FileManagerPage.tsx +1 -1
  39. package/features/integration-test/src/integration.spec.ts +1 -1
  40. package/features/items/feature.json +1 -5
  41. package/features/items/src/ItemsPage.tsx +207 -135
  42. package/features/landing/feature.json +3 -0
  43. package/features/landing/src/LandingPage.tsx +4 -18
  44. package/features/leaderboard/feature.json +1 -5
  45. package/features/leaderboard/src/LeaderboardPage.tsx +333 -198
  46. package/features/messaging/feature.json +0 -4
  47. package/features/messaging/src/ChatMultiPage.tsx +1 -3
  48. package/features/messaging/src/ChatPage.tsx +1 -3
  49. package/features/messaging/src/components/chat/MessageActionSheet.tsx +40 -16
  50. package/features/search-bar/feature.json +8 -3
  51. package/features/search-bar/src/SearchOverlay.tsx +108 -252
  52. package/features/search-bar/src/__tests__/search-model.test.ts +40 -0
  53. package/features/search-bar/src/search-model.ts +223 -0
  54. package/features/tasks/src/TasksPage.tsx +3 -3
  55. package/features/topbar/src/Topbar.tsx +0 -17
  56. package/package.json +1 -2
  57. package/features/docs/src/docs-favorites.ts +0 -18
  58. package/features/docs/src/use-yjs-room-with-awareness.ts +0 -289
  59. package/features/file-attachments/src/attachments-constants.ts +0 -9
  60. package/features/items/src/items-constants.ts +0 -10
  61. package/features/leaderboard/src/leaderboard-constants.ts +0 -21
  62. package/features/messaging/src/components/chat/ReactionPicker.tsx +0 -2
  63. package/scripts/add-feature.cjs +0 -807
@@ -0,0 +1,115 @@
1
+ import * as _playwright_test from '@playwright/test';
2
+ import { BrowserContext, Page, Browser } from '@playwright/test';
3
+ export { expect } from '@playwright/test';
4
+
5
+ interface MultiplayerUser {
6
+ context: BrowserContext;
7
+ page: Page;
8
+ email: string;
9
+ name: string;
10
+ /** Test account user ID, if known from the accounts registry. */
11
+ userId?: string;
12
+ }
13
+ /**
14
+ * Fixture function signature. Pass either a count (`users(2)`) or an
15
+ * array of account names (`users(['Alice', 'Bob'])`). Returns one entry
16
+ * per requested user.
17
+ */
18
+ type UsersFixture = (selector: number | string[], options?: {
19
+ label?: string;
20
+ }) => Promise<MultiplayerUser[]>;
21
+ interface MultiplayerFixtures {
22
+ users: UsersFixture;
23
+ }
24
+ declare const test: _playwright_test.TestType<_playwright_test.PlaywrightTestArgs & _playwright_test.PlaywrightTestOptions & MultiplayerFixtures, _playwright_test.PlaywrightWorkerArgs & _playwright_test.PlaywrightWorkerOptions>;
25
+
26
+ /**
27
+ * Account-credential plumbing for the testing module.
28
+ *
29
+ * Reads test accounts from `~/.deepspace/test-accounts.json` (the same
30
+ * file `deepspace test accounts create` writes). Each account has at
31
+ * minimum `{email, password}`; `name` and `label` are optional.
32
+ *
33
+ * Used by the multi-user Playwright fixture in `./fixtures.ts` and is
34
+ * also exported standalone in case suites want to do their own
35
+ * orchestration.
36
+ */
37
+ interface TestAccount {
38
+ email: string;
39
+ password: string;
40
+ name?: string;
41
+ label?: string | null;
42
+ id?: string;
43
+ userId?: string;
44
+ createdAt?: number;
45
+ }
46
+ /**
47
+ * Load all test accounts the developer has created. Returns an empty
48
+ * array if the file doesn't exist yet.
49
+ */
50
+ declare function loadAllTestAccounts(): TestAccount[];
51
+ /**
52
+ * Pick `count` test accounts from the local registry, optionally
53
+ * filtered by `label`. Order-stable: sorted by `createdAt` ascending so
54
+ * the first test account you created is always the first one returned.
55
+ *
56
+ * Throws with a helpful message if not enough accounts exist.
57
+ */
58
+ declare function pickTestAccounts(count: number, options?: {
59
+ label?: string;
60
+ }): TestAccount[];
61
+ /**
62
+ * Find a single account by name. Used by `users(['Alice', 'Bob'])`.
63
+ * Throws if not found.
64
+ */
65
+ declare function findTestAccountByName(name: string): TestAccount;
66
+
67
+ /**
68
+ * Per-account `storageState` cache for Playwright.
69
+ *
70
+ * Better Auth applies a per-IP rate limit on `/api/auth/sign-in/email`
71
+ * (~5 sign-ins / 60s / endpoint). A multiplayer suite that spins up
72
+ * 5+ users per spec would hit the limit and start failing in non-obvious
73
+ * ways. This module signs each account in *once*, persists the
74
+ * resulting browser cookies + storage to disk, and reuses the file on
75
+ * subsequent runs.
76
+ *
77
+ * Cache layout:
78
+ * ~/.deepspace/playwright-states/<sha256(email)>.json
79
+ *
80
+ * Validity: a cached file is reused if Playwright successfully loads it
81
+ * AND the resulting context produces a non-anonymous session on the
82
+ * target app. If validation fails, we fall back to a fresh sign-in and
83
+ * overwrite the cache.
84
+ */
85
+
86
+ interface EnsureStorageStateOptions {
87
+ /** Max age of a cached state file before we re-sign-in. Default 7 days. */
88
+ maxAgeMs?: number;
89
+ /** Force a fresh sign-in even if the cache is fresh. */
90
+ force?: boolean;
91
+ }
92
+ /**
93
+ * Ensure a Playwright `storageState` file exists for `account` and
94
+ * return its path. Signs in once if the cache is missing, stale, or
95
+ * `force: true`.
96
+ */
97
+ declare function ensureStorageState(browser: Browser, account: {
98
+ email: string;
99
+ password: string;
100
+ }, baseURL: string, options?: EnsureStorageStateOptions): Promise<string>;
101
+ /**
102
+ * Convenience: open a fresh `BrowserContext` for `account` using the
103
+ * cached storage state (signing in if needed). The caller is responsible
104
+ * for closing the context.
105
+ */
106
+ declare function newSignedInContext(browser: Browser, account: {
107
+ email: string;
108
+ password: string;
109
+ }, baseURL: string, options?: EnsureStorageStateOptions): Promise<BrowserContext>;
110
+ /** Read a cached storageState file (mostly for debugging). */
111
+ declare function readCachedState(email: string): unknown | null;
112
+ /** Where the cache for `email` lives. Useful for diagnostics. */
113
+ declare function getStatePathForEmail(email: string): string;
114
+
115
+ export { type EnsureStorageStateOptions, type MultiplayerUser, type TestAccount, type UsersFixture, ensureStorageState, findTestAccountByName, getStatePathForEmail, loadAllTestAccounts, newSignedInContext, pickTestAccounts, readCachedState, test };
package/dist/testing.js CHANGED
@@ -26,7 +26,7 @@ function pickTestAccounts(count, options) {
26
26
  const labelHint = options?.label ? ` (label="${options.label}")` : "";
27
27
  throw new Error(
28
28
  `Multiplayer test needs ${count} test accounts${labelHint}, but only ${sorted.length} are present in ${ACCOUNTS_PATH}. Create more with:
29
- deepspace test-accounts create --email <name>@deepspace.test --password <pw> --name "<name>"${options?.label ? ` --label ${options.label}` : ""}`
29
+ deepspace test accounts create --email <name>@deepspace.test --password <pw> --name "<name>"${options?.label ? ` --label ${options.label}` : ""}`
30
30
  );
31
31
  }
32
32
  return sorted.slice(0, count);
@@ -37,7 +37,7 @@ function findTestAccountByName(name) {
37
37
  if (!match) {
38
38
  throw new Error(
39
39
  `No test account named "${name}" in ${ACCOUNTS_PATH}. Create with:
40
- deepspace test-accounts create --email ${name.toLowerCase()}@deepspace.test --password <pw> --name "${name}"`
40
+ deepspace test accounts create --email ${name.toLowerCase()}@deepspace.test --password <pw> --name "${name}"`
41
41
  );
42
42
  }
43
43
  return match;
@@ -85,7 +85,7 @@ async function signInAndSaveState(browser, account, baseURL, outPath) {
85
85
  );
86
86
  if (!ok) {
87
87
  throw new Error(
88
- `Sign-in failed for ${account.email}. Check the password is current; if the account was deleted on the server, re-create with \`deepspace test-accounts create\`.`
88
+ `Sign-in failed for ${account.email}. Check the password is current; if the account was deleted on the server, re-create with \`deepspace test accounts create\`.`
89
89
  );
90
90
  }
91
91
  await ctx.storageState({ path: outPath });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/testing/fixtures.ts","../src/testing/accounts.ts","../src/testing/storage-state.ts"],"sourcesContent":["/**\n * Multi-user Playwright fixture for DeepSpace apps.\n *\n * Usage:\n *\n * import { test, expect } from 'deepspace/testing'\n *\n * test('A sends, B sees', async ({ users }) => {\n * const [a, b] = await users(2)\n * await a.page.goto('/chat')\n * await b.page.goto('/chat')\n * await a.page.getByTestId('message-input').fill('hi')\n * await a.page.getByTestId('send-message-btn').click()\n * await expect(b.page.getByText('hi')).toBeVisible()\n * })\n *\n * `users(N)` picks the first N test accounts from\n * `~/.deepspace/test-accounts.json` (created via\n * `deepspace test-accounts create`). `users(['Alice', 'Bob'])` picks\n * specific accounts by name.\n *\n * Each user gets a separate browser context with cached storageState\n * (per-account JSON in `~/.deepspace/playwright-states/`). Sign-in\n * happens once per account, not once per test, sidestepping Better\n * Auth's per-IP rate limit on `/api/auth/sign-in/email`.\n *\n * Contexts are auto-closed when the test finishes.\n */\n\nimport { test as baseTest, type Browser, type BrowserContext, type Page } from '@playwright/test'\nimport {\n type TestAccount,\n pickTestAccounts,\n findTestAccountByName,\n} from './accounts'\nimport { ensureStorageState } from './storage-state'\n\nexport interface MultiplayerUser {\n context: BrowserContext\n page: Page\n email: string\n name: string\n /** Test account user ID, if known from the accounts registry. */\n userId?: string\n}\n\n/**\n * Fixture function signature. Pass either a count (`users(2)`) or an\n * array of account names (`users(['Alice', 'Bob'])`). Returns one entry\n * per requested user.\n */\nexport type UsersFixture = (\n selector: number | string[],\n options?: { label?: string },\n) => Promise<MultiplayerUser[]>\n\ninterface MultiplayerFixtures {\n users: UsersFixture\n}\n\nasync function buildUser(\n browser: Browser,\n account: TestAccount,\n baseURL: string,\n): Promise<{ user: MultiplayerUser; cleanup: () => Promise<void> }> {\n const statePath = await ensureStorageState(browser, account, baseURL)\n const context = await browser.newContext({ storageState: statePath, baseURL })\n const page = await context.newPage()\n return {\n user: {\n context,\n page,\n email: account.email,\n name: account.name ?? account.email.split('@')[0],\n userId: account.userId,\n },\n cleanup: async () => {\n await context.close()\n },\n }\n}\n\nexport const test = baseTest.extend<MultiplayerFixtures>({\n users: async ({ browser, baseURL }, use) => {\n if (!baseURL) {\n throw new Error(\n '`users` fixture requires a baseURL. Set it on your Playwright config ' +\n '(e.g. `use: { baseURL: \"http://localhost:5173\" }`) or via the ' +\n 'webServer.url shortcut.',\n )\n }\n\n const cleanups: Array<() => Promise<void>> = []\n\n const usersFn: UsersFixture = async (selector, options) => {\n const accounts: TestAccount[] = Array.isArray(selector)\n ? selector.map((name) => findTestAccountByName(name))\n : pickTestAccounts(selector, options)\n\n const built = await Promise.all(\n accounts.map((acct) => buildUser(browser, acct, baseURL)),\n )\n for (const b of built) cleanups.push(b.cleanup)\n return built.map((b) => b.user)\n }\n\n await use(usersFn)\n\n // Tear down any contexts we created. Errors are swallowed — a test\n // failure shouldn't be masked by a cleanup error.\n for (const cleanup of cleanups) {\n await cleanup().catch(() => {})\n }\n },\n})\n\nexport { expect } from '@playwright/test'\n","/**\n * Account-credential plumbing for the testing module.\n *\n * Reads test accounts from `~/.deepspace/test-accounts.json` (the same\n * file `deepspace test-accounts create` writes). Each account has at\n * minimum `{email, password}`; `name` and `label` are optional.\n *\n * Used by the multi-user Playwright fixture in `./fixtures.ts` and is\n * also exported standalone in case suites want to do their own\n * orchestration.\n */\n\nimport { existsSync, readFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\n\nexport interface TestAccount {\n email: string\n password: string\n name?: string\n label?: string | null\n id?: string\n userId?: string\n createdAt?: number\n}\n\nconst ACCOUNTS_PATH = join(homedir(), '.deepspace', 'test-accounts.json')\n\n/**\n * Load all test accounts the developer has created. Returns an empty\n * array if the file doesn't exist yet.\n */\nexport function loadAllTestAccounts(): TestAccount[] {\n if (!existsSync(ACCOUNTS_PATH)) return []\n try {\n const raw = JSON.parse(readFileSync(ACCOUNTS_PATH, 'utf-8'))\n if (!Array.isArray(raw)) return []\n // Filter out any malformed entries (missing email or password). Keep\n // bare {email, password} rows so the file format stays forgiving.\n return raw.filter(\n (r) => r && typeof r.email === 'string' && typeof r.password === 'string',\n )\n } catch {\n return []\n }\n}\n\n/**\n * Pick `count` test accounts from the local registry, optionally\n * filtered by `label`. Order-stable: sorted by `createdAt` ascending so\n * the first test account you created is always the first one returned.\n *\n * Throws with a helpful message if not enough accounts exist.\n */\nexport function pickTestAccounts(\n count: number,\n options?: { label?: string },\n): TestAccount[] {\n const all = loadAllTestAccounts()\n const filtered = options?.label\n ? all.filter((a) => a.label === options.label)\n : all\n const sorted = filtered.slice().sort((a, b) => (a.createdAt ?? 0) - (b.createdAt ?? 0))\n\n if (sorted.length < count) {\n const labelHint = options?.label ? ` (label=\"${options.label}\")` : ''\n throw new Error(\n `Multiplayer test needs ${count} test accounts${labelHint}, but only ${sorted.length} ` +\n `are present in ${ACCOUNTS_PATH}. Create more with:\\n` +\n ` deepspace test-accounts create --email <name>@deepspace.test --password <pw> --name \"<name>\"${\n options?.label ? ` --label ${options.label}` : ''\n }`,\n )\n }\n\n return sorted.slice(0, count)\n}\n\n/**\n * Find a single account by name. Used by `users(['Alice', 'Bob'])`.\n * Throws if not found.\n */\nexport function findTestAccountByName(name: string): TestAccount {\n const all = loadAllTestAccounts()\n const match = all.find((a) => a.name === name)\n if (!match) {\n throw new Error(\n `No test account named \"${name}\" in ${ACCOUNTS_PATH}. Create with:\\n` +\n ` deepspace test-accounts create --email ${name.toLowerCase()}@deepspace.test --password <pw> --name \"${name}\"`,\n )\n }\n return match\n}\n","/**\n * Per-account `storageState` cache for Playwright.\n *\n * Better Auth applies a per-IP rate limit on `/api/auth/sign-in/email`\n * (~5 sign-ins / 60s / endpoint). A multiplayer suite that spins up\n * 5+ users per spec would hit the limit and start failing in non-obvious\n * ways. This module signs each account in *once*, persists the\n * resulting browser cookies + storage to disk, and reuses the file on\n * subsequent runs.\n *\n * Cache layout:\n * ~/.deepspace/playwright-states/<sha256(email)>.json\n *\n * Validity: a cached file is reused if Playwright successfully loads it\n * AND the resulting context produces a non-anonymous session on the\n * target app. If validation fails, we fall back to a fresh sign-in and\n * overwrite the cache.\n */\n\nimport { createHash } from 'node:crypto'\nimport { existsSync, mkdirSync, readFileSync, statSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport type { Browser, BrowserContext } from '@playwright/test'\n\nconst STATES_DIR = join(homedir(), '.deepspace', 'playwright-states')\n\n/** Default freshness window — re-sign-in if the cache is older than this. */\nconst DEFAULT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000 // 7 days\n\nfunction statePathForEmail(email: string): string {\n const hash = createHash('sha256').update(email.trim().toLowerCase()).digest('hex').slice(0, 24)\n return join(STATES_DIR, `${hash}.json`)\n}\n\nfunction ensureStatesDir() {\n if (!existsSync(STATES_DIR)) mkdirSync(STATES_DIR, { recursive: true })\n}\n\n/** Returns true if a cache file exists and is younger than `maxAgeMs`. */\nfunction isCacheFresh(path: string, maxAgeMs: number): boolean {\n if (!existsSync(path)) return false\n try {\n const age = Date.now() - statSync(path).mtimeMs\n return age < maxAgeMs\n } catch {\n return false\n }\n}\n\n/**\n * Sign `account` in by POSTing `/api/auth/sign-in/email` against\n * `baseURL` and capturing the resulting cookies. Returns the path to the\n * Playwright `storageState` JSON file.\n *\n * The browser-driven approach is intentional: we want the cookies set\n * on the same origin Playwright contexts will load from, otherwise the\n * Set-Cookie domain attribute won't match.\n */\nasync function signInAndSaveState(\n browser: Browser,\n account: { email: string; password: string },\n baseURL: string,\n outPath: string,\n): Promise<string> {\n ensureStatesDir()\n const ctx = await browser.newContext({ baseURL })\n try {\n const page = await ctx.newPage()\n // Hit the app first so the origin is established (some auth plugins\n // require a same-origin Origin header on sign-in).\n await page.goto('/')\n const ok = await page.evaluate(\n async ({ email, password }: { email: string; password: string }) => {\n const res = await fetch('/api/auth/sign-in/email', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email, password }),\n })\n return res.ok\n },\n account,\n )\n if (!ok) {\n throw new Error(\n `Sign-in failed for ${account.email}. Check the password is current; ` +\n `if the account was deleted on the server, re-create with ` +\n `\\`deepspace test-accounts create\\`.`,\n )\n }\n await ctx.storageState({ path: outPath })\n return outPath\n } finally {\n await ctx.close()\n }\n}\n\nexport interface EnsureStorageStateOptions {\n /** Max age of a cached state file before we re-sign-in. Default 7 days. */\n maxAgeMs?: number\n /** Force a fresh sign-in even if the cache is fresh. */\n force?: boolean\n}\n\n/**\n * Ensure a Playwright `storageState` file exists for `account` and\n * return its path. Signs in once if the cache is missing, stale, or\n * `force: true`.\n */\nexport async function ensureStorageState(\n browser: Browser,\n account: { email: string; password: string },\n baseURL: string,\n options: EnsureStorageStateOptions = {},\n): Promise<string> {\n const path = statePathForEmail(account.email)\n const maxAgeMs = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS\n\n if (!options.force && isCacheFresh(path, maxAgeMs)) {\n return path\n }\n\n return signInAndSaveState(browser, account, baseURL, path)\n}\n\n/**\n * Convenience: open a fresh `BrowserContext` for `account` using the\n * cached storage state (signing in if needed). The caller is responsible\n * for closing the context.\n */\nexport async function newSignedInContext(\n browser: Browser,\n account: { email: string; password: string },\n baseURL: string,\n options: EnsureStorageStateOptions = {},\n): Promise<BrowserContext> {\n const statePath = await ensureStorageState(browser, account, baseURL, options)\n return browser.newContext({ storageState: statePath, baseURL })\n}\n\n/** Read a cached storageState file (mostly for debugging). */\nexport function readCachedState(email: string): unknown | null {\n const path = statePathForEmail(email)\n if (!existsSync(path)) return null\n try {\n return JSON.parse(readFileSync(path, 'utf-8'))\n } catch {\n return null\n }\n}\n\n/** Where the cache for `email` lives. Useful for diagnostics. */\nexport function getStatePathForEmail(email: string): string {\n return statePathForEmail(email)\n}\n"],"mappings":"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","names":["existsSync","readFileSync","homedir","join"]}
1
+ {"version":3,"sources":["../src/testing/fixtures.ts","../src/testing/accounts.ts","../src/testing/storage-state.ts"],"sourcesContent":["/**\n * Multi-user Playwright fixture for DeepSpace apps.\n *\n * Usage:\n *\n * import { test, expect } from 'deepspace/testing'\n *\n * test('A sends, B sees', async ({ users }) => {\n * const [a, b] = await users(2)\n * await a.page.goto('/chat')\n * await b.page.goto('/chat')\n * await a.page.getByTestId('message-input').fill('hi')\n * await a.page.getByTestId('send-message-btn').click()\n * await expect(b.page.getByText('hi')).toBeVisible()\n * })\n *\n * `users(N)` picks the first N test accounts from\n * `~/.deepspace/test-accounts.json` (created via\n * `deepspace test accounts create`). `users(['Alice', 'Bob'])` picks\n * specific accounts by name.\n *\n * Each user gets a separate browser context with cached storageState\n * (per-account JSON in `~/.deepspace/playwright-states/`). Sign-in\n * happens once per account, not once per test, sidestepping Better\n * Auth's per-IP rate limit on `/api/auth/sign-in/email`.\n *\n * Contexts are auto-closed when the test finishes.\n */\n\nimport { test as baseTest, type Browser, type BrowserContext, type Page } from '@playwright/test'\nimport {\n type TestAccount,\n pickTestAccounts,\n findTestAccountByName,\n} from './accounts'\nimport { ensureStorageState } from './storage-state'\n\nexport interface MultiplayerUser {\n context: BrowserContext\n page: Page\n email: string\n name: string\n /** Test account user ID, if known from the accounts registry. */\n userId?: string\n}\n\n/**\n * Fixture function signature. Pass either a count (`users(2)`) or an\n * array of account names (`users(['Alice', 'Bob'])`). Returns one entry\n * per requested user.\n */\nexport type UsersFixture = (\n selector: number | string[],\n options?: { label?: string },\n) => Promise<MultiplayerUser[]>\n\ninterface MultiplayerFixtures {\n users: UsersFixture\n}\n\nasync function buildUser(\n browser: Browser,\n account: TestAccount,\n baseURL: string,\n): Promise<{ user: MultiplayerUser; cleanup: () => Promise<void> }> {\n const statePath = await ensureStorageState(browser, account, baseURL)\n const context = await browser.newContext({ storageState: statePath, baseURL })\n const page = await context.newPage()\n return {\n user: {\n context,\n page,\n email: account.email,\n name: account.name ?? account.email.split('@')[0],\n userId: account.userId,\n },\n cleanup: async () => {\n await context.close()\n },\n }\n}\n\nexport const test = baseTest.extend<MultiplayerFixtures>({\n users: async ({ browser, baseURL }, use) => {\n if (!baseURL) {\n throw new Error(\n '`users` fixture requires a baseURL. Set it on your Playwright config ' +\n '(e.g. `use: { baseURL: \"http://localhost:5173\" }`) or via the ' +\n 'webServer.url shortcut.',\n )\n }\n\n const cleanups: Array<() => Promise<void>> = []\n\n const usersFn: UsersFixture = async (selector, options) => {\n const accounts: TestAccount[] = Array.isArray(selector)\n ? selector.map((name) => findTestAccountByName(name))\n : pickTestAccounts(selector, options)\n\n const built = await Promise.all(\n accounts.map((acct) => buildUser(browser, acct, baseURL)),\n )\n for (const b of built) cleanups.push(b.cleanup)\n return built.map((b) => b.user)\n }\n\n await use(usersFn)\n\n // Tear down any contexts we created. Errors are swallowed — a test\n // failure shouldn't be masked by a cleanup error.\n for (const cleanup of cleanups) {\n await cleanup().catch(() => {})\n }\n },\n})\n\nexport { expect } from '@playwright/test'\n","/**\n * Account-credential plumbing for the testing module.\n *\n * Reads test accounts from `~/.deepspace/test-accounts.json` (the same\n * file `deepspace test accounts create` writes). Each account has at\n * minimum `{email, password}`; `name` and `label` are optional.\n *\n * Used by the multi-user Playwright fixture in `./fixtures.ts` and is\n * also exported standalone in case suites want to do their own\n * orchestration.\n */\n\nimport { existsSync, readFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\n\nexport interface TestAccount {\n email: string\n password: string\n name?: string\n label?: string | null\n id?: string\n userId?: string\n createdAt?: number\n}\n\nconst ACCOUNTS_PATH = join(homedir(), '.deepspace', 'test-accounts.json')\n\n/**\n * Load all test accounts the developer has created. Returns an empty\n * array if the file doesn't exist yet.\n */\nexport function loadAllTestAccounts(): TestAccount[] {\n if (!existsSync(ACCOUNTS_PATH)) return []\n try {\n const raw = JSON.parse(readFileSync(ACCOUNTS_PATH, 'utf-8'))\n if (!Array.isArray(raw)) return []\n // Filter out any malformed entries (missing email or password). Keep\n // bare {email, password} rows so the file format stays forgiving.\n return raw.filter(\n (r) => r && typeof r.email === 'string' && typeof r.password === 'string',\n )\n } catch {\n return []\n }\n}\n\n/**\n * Pick `count` test accounts from the local registry, optionally\n * filtered by `label`. Order-stable: sorted by `createdAt` ascending so\n * the first test account you created is always the first one returned.\n *\n * Throws with a helpful message if not enough accounts exist.\n */\nexport function pickTestAccounts(\n count: number,\n options?: { label?: string },\n): TestAccount[] {\n const all = loadAllTestAccounts()\n const filtered = options?.label\n ? all.filter((a) => a.label === options.label)\n : all\n const sorted = filtered.slice().sort((a, b) => (a.createdAt ?? 0) - (b.createdAt ?? 0))\n\n if (sorted.length < count) {\n const labelHint = options?.label ? ` (label=\"${options.label}\")` : ''\n throw new Error(\n `Multiplayer test needs ${count} test accounts${labelHint}, but only ${sorted.length} ` +\n `are present in ${ACCOUNTS_PATH}. Create more with:\\n` +\n ` deepspace test accounts create --email <name>@deepspace.test --password <pw> --name \"<name>\"${\n options?.label ? ` --label ${options.label}` : ''\n }`,\n )\n }\n\n return sorted.slice(0, count)\n}\n\n/**\n * Find a single account by name. Used by `users(['Alice', 'Bob'])`.\n * Throws if not found.\n */\nexport function findTestAccountByName(name: string): TestAccount {\n const all = loadAllTestAccounts()\n const match = all.find((a) => a.name === name)\n if (!match) {\n throw new Error(\n `No test account named \"${name}\" in ${ACCOUNTS_PATH}. Create with:\\n` +\n ` deepspace test accounts create --email ${name.toLowerCase()}@deepspace.test --password <pw> --name \"${name}\"`,\n )\n }\n return match\n}\n","/**\n * Per-account `storageState` cache for Playwright.\n *\n * Better Auth applies a per-IP rate limit on `/api/auth/sign-in/email`\n * (~5 sign-ins / 60s / endpoint). A multiplayer suite that spins up\n * 5+ users per spec would hit the limit and start failing in non-obvious\n * ways. This module signs each account in *once*, persists the\n * resulting browser cookies + storage to disk, and reuses the file on\n * subsequent runs.\n *\n * Cache layout:\n * ~/.deepspace/playwright-states/<sha256(email)>.json\n *\n * Validity: a cached file is reused if Playwright successfully loads it\n * AND the resulting context produces a non-anonymous session on the\n * target app. If validation fails, we fall back to a fresh sign-in and\n * overwrite the cache.\n */\n\nimport { createHash } from 'node:crypto'\nimport { existsSync, mkdirSync, readFileSync, statSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport type { Browser, BrowserContext } from '@playwright/test'\n\nconst STATES_DIR = join(homedir(), '.deepspace', 'playwright-states')\n\n/** Default freshness window — re-sign-in if the cache is older than this. */\nconst DEFAULT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000 // 7 days\n\nfunction statePathForEmail(email: string): string {\n const hash = createHash('sha256').update(email.trim().toLowerCase()).digest('hex').slice(0, 24)\n return join(STATES_DIR, `${hash}.json`)\n}\n\nfunction ensureStatesDir() {\n if (!existsSync(STATES_DIR)) mkdirSync(STATES_DIR, { recursive: true })\n}\n\n/** Returns true if a cache file exists and is younger than `maxAgeMs`. */\nfunction isCacheFresh(path: string, maxAgeMs: number): boolean {\n if (!existsSync(path)) return false\n try {\n const age = Date.now() - statSync(path).mtimeMs\n return age < maxAgeMs\n } catch {\n return false\n }\n}\n\n/**\n * Sign `account` in by POSTing `/api/auth/sign-in/email` against\n * `baseURL` and capturing the resulting cookies. Returns the path to the\n * Playwright `storageState` JSON file.\n *\n * The browser-driven approach is intentional: we want the cookies set\n * on the same origin Playwright contexts will load from, otherwise the\n * Set-Cookie domain attribute won't match.\n */\nasync function signInAndSaveState(\n browser: Browser,\n account: { email: string; password: string },\n baseURL: string,\n outPath: string,\n): Promise<string> {\n ensureStatesDir()\n const ctx = await browser.newContext({ baseURL })\n try {\n const page = await ctx.newPage()\n // Hit the app first so the origin is established (some auth plugins\n // require a same-origin Origin header on sign-in).\n await page.goto('/')\n const ok = await page.evaluate(\n async ({ email, password }: { email: string; password: string }) => {\n const res = await fetch('/api/auth/sign-in/email', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email, password }),\n })\n return res.ok\n },\n account,\n )\n if (!ok) {\n throw new Error(\n `Sign-in failed for ${account.email}. Check the password is current; ` +\n `if the account was deleted on the server, re-create with ` +\n `\\`deepspace test accounts create\\`.`,\n )\n }\n await ctx.storageState({ path: outPath })\n return outPath\n } finally {\n await ctx.close()\n }\n}\n\nexport interface EnsureStorageStateOptions {\n /** Max age of a cached state file before we re-sign-in. Default 7 days. */\n maxAgeMs?: number\n /** Force a fresh sign-in even if the cache is fresh. */\n force?: boolean\n}\n\n/**\n * Ensure a Playwright `storageState` file exists for `account` and\n * return its path. Signs in once if the cache is missing, stale, or\n * `force: true`.\n */\nexport async function ensureStorageState(\n browser: Browser,\n account: { email: string; password: string },\n baseURL: string,\n options: EnsureStorageStateOptions = {},\n): Promise<string> {\n const path = statePathForEmail(account.email)\n const maxAgeMs = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS\n\n if (!options.force && isCacheFresh(path, maxAgeMs)) {\n return path\n }\n\n return signInAndSaveState(browser, account, baseURL, path)\n}\n\n/**\n * Convenience: open a fresh `BrowserContext` for `account` using the\n * cached storage state (signing in if needed). The caller is responsible\n * for closing the context.\n */\nexport async function newSignedInContext(\n browser: Browser,\n account: { email: string; password: string },\n baseURL: string,\n options: EnsureStorageStateOptions = {},\n): Promise<BrowserContext> {\n const statePath = await ensureStorageState(browser, account, baseURL, options)\n return browser.newContext({ storageState: statePath, baseURL })\n}\n\n/** Read a cached storageState file (mostly for debugging). */\nexport function readCachedState(email: string): unknown | null {\n const path = statePathForEmail(email)\n if (!existsSync(path)) return null\n try {\n return JSON.parse(readFileSync(path, 'utf-8'))\n } catch {\n return null\n }\n}\n\n/** Where the cache for `email` lives. Useful for diagnostics. */\nexport function getStatePathForEmail(email: string): string {\n return statePathForEmail(email)\n}\n"],"mappings":"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","names":["existsSync","readFileSync","homedir","join"]}