@dreamboard-games/cli 0.1.30-alpha.16 → 0.1.30-alpha.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/agent-verifier/agent-workspace-verifier.mjs +12 -12
- package/dist/agent-verifier/{chunk-LKQ557TJ.mjs → chunk-334H4LE4.mjs} +3 -3
- package/dist/agent-verifier/{chunk-H3XNWKJU.mjs → chunk-7LFDFXLS.mjs} +2 -2
- package/dist/agent-verifier/{chunk-DWLTCUUX.mjs → chunk-7MAOGFFP.mjs} +6 -6
- package/dist/agent-verifier/{chunk-CO3BRUD6.mjs → chunk-AG5J3SMN.mjs} +11 -3
- package/dist/agent-verifier/chunk-AG5J3SMN.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-AXXUGU7Q.mjs → chunk-AQ6UQHPT.mjs} +4 -30
- package/dist/agent-verifier/chunk-AQ6UQHPT.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-A67WUYN2.mjs → chunk-B42OHJNY.mjs} +3 -3
- package/dist/agent-verifier/{chunk-V6AQDR7W.mjs → chunk-HUBV22JQ.mjs} +3 -3
- package/dist/agent-verifier/chunk-HUBV22JQ.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-5GCZZ6NW.mjs → chunk-JB7VXCMB.mjs} +2 -2
- package/dist/agent-verifier/{chunk-WAFBU5U7.mjs → chunk-OJFZVGEL.mjs} +38 -13
- package/dist/agent-verifier/chunk-OJFZVGEL.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-G2ECODRB.mjs → chunk-PLXXH5LY.mjs} +2 -2
- package/dist/agent-verifier/{chunk-655VJLXA.mjs → chunk-PWPOLHTW.mjs} +9 -12
- package/dist/agent-verifier/chunk-PWPOLHTW.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-DPYC2NDB.mjs → chunk-RCYO6HWW.mjs} +2 -2
- package/dist/agent-verifier/{chunk-NFL3Z4Z7.mjs → chunk-RP7ZWFVH.mjs} +12 -8
- package/dist/agent-verifier/{chunk-NFL3Z4Z7.mjs.map → chunk-RP7ZWFVH.mjs.map} +1 -1
- package/dist/agent-verifier/{compile-4VSMC275.mjs → compile-VOBO2I6D.mjs} +12 -12
- package/dist/agent-verifier/{global-config-6UGFPLDA.mjs → global-config-L7PLLUK5.mjs} +3 -3
- package/dist/agent-verifier/{keychain-backend-BQLW5VEC.mjs → keychain-backend-UF3Z26JM.mjs} +1 -1
- package/dist/agent-verifier/keychain-backend-UF3Z26JM.mjs.map +1 -0
- package/dist/agent-verifier/{local-files-WPHUV6GU.mjs → local-files-DAFIR7SN.mjs} +4 -4
- package/dist/agent-verifier/{materialize-workspace-CV2JNXLU.mjs → materialize-workspace-PAC75NSP.mjs} +6 -6
- package/dist/agent-verifier/{reducer-native-test-harness-GY2CCQWN.mjs → reducer-native-test-harness-HSXRUGOR.mjs} +8 -8
- package/dist/agent-verifier/{static-scaffold-DJJRKMNB.mjs → static-scaffold-KSOTKJNQ.mjs} +4 -4
- package/dist/agent-verifier/{sync-YWK4SPWV.mjs → sync-MQJJEZAA.mjs} +13 -13
- package/dist/agent-verifier/{test-LQAGEQLY.mjs → test-R6HC6CYZ.mjs} +11 -11
- package/dist/agent-verifier/{workspace-codegen-4IWICKLB.mjs → workspace-codegen-SPPVHURX.mjs} +3 -3
- package/dist/authoring-compatibility-internal.js +1 -1
- package/dist/{chunk-2R4L2YDX.js → chunk-2WB3DYW4.js} +17 -8
- package/dist/chunk-2WB3DYW4.js.map +1 -0
- package/dist/{chunk-PW7D2W5S.js → chunk-2XMBZPL5.js} +45 -23
- package/dist/{chunk-PW7D2W5S.js.map → chunk-2XMBZPL5.js.map} +1 -1
- package/dist/{chunk-AVOAT522.js → chunk-J3CWZHY7.js} +4 -30
- package/dist/chunk-J3CWZHY7.js.map +1 -0
- package/dist/{global-config-NLGAFSRU.js → global-config-VQWFTIAV.js} +2 -2
- package/dist/index.js +5 -5
- package/dist/internal.js +3 -3
- package/dist/{keychain-backend-47LZ5IX5.js → keychain-backend-GO34KGTG.js} +1 -1
- package/dist/keychain-backend-GO34KGTG.js.map +1 -0
- package/package.json +1 -1
- package/release/authoring-release-set.json +4 -4
- package/skills/dreamboard/SKILL.md +8 -0
- package/skills/dreamboard/references/building-your-first-game.md +37 -15
- package/skills/dreamboard/references/canonical-concepts.md +74 -0
- package/skills/dreamboard/references/game-interface.md +15 -2
- package/skills/dreamboard/references/manifest-authoring.md +8 -0
- package/skills/dreamboard/references/reducer.md +47 -2
- package/skills/dreamboard/references/rule-authoring.md +10 -0
- package/skills/dreamboard/references/testing.md +7 -3
- package/dist/agent-verifier/chunk-655VJLXA.mjs.map +0 -1
- package/dist/agent-verifier/chunk-AXXUGU7Q.mjs.map +0 -1
- package/dist/agent-verifier/chunk-CO3BRUD6.mjs.map +0 -1
- package/dist/agent-verifier/chunk-V6AQDR7W.mjs.map +0 -1
- package/dist/agent-verifier/chunk-WAFBU5U7.mjs.map +0 -1
- package/dist/agent-verifier/keychain-backend-BQLW5VEC.mjs.map +0 -1
- package/dist/chunk-2R4L2YDX.js.map +0 -1
- package/dist/chunk-AVOAT522.js.map +0 -1
- package/dist/keychain-backend-47LZ5IX5.js.map +0 -1
- /package/dist/agent-verifier/{chunk-LKQ557TJ.mjs.map → chunk-334H4LE4.mjs.map} +0 -0
- /package/dist/agent-verifier/{chunk-H3XNWKJU.mjs.map → chunk-7LFDFXLS.mjs.map} +0 -0
- /package/dist/agent-verifier/{chunk-DWLTCUUX.mjs.map → chunk-7MAOGFFP.mjs.map} +0 -0
- /package/dist/agent-verifier/{chunk-A67WUYN2.mjs.map → chunk-B42OHJNY.mjs.map} +0 -0
- /package/dist/agent-verifier/{chunk-5GCZZ6NW.mjs.map → chunk-JB7VXCMB.mjs.map} +0 -0
- /package/dist/agent-verifier/{chunk-G2ECODRB.mjs.map → chunk-PLXXH5LY.mjs.map} +0 -0
- /package/dist/agent-verifier/{chunk-DPYC2NDB.mjs.map → chunk-RCYO6HWW.mjs.map} +0 -0
- /package/dist/agent-verifier/{compile-4VSMC275.mjs.map → compile-VOBO2I6D.mjs.map} +0 -0
- /package/dist/agent-verifier/{global-config-6UGFPLDA.mjs.map → global-config-L7PLLUK5.mjs.map} +0 -0
- /package/dist/agent-verifier/{local-files-WPHUV6GU.mjs.map → local-files-DAFIR7SN.mjs.map} +0 -0
- /package/dist/agent-verifier/{materialize-workspace-CV2JNXLU.mjs.map → materialize-workspace-PAC75NSP.mjs.map} +0 -0
- /package/dist/agent-verifier/{reducer-native-test-harness-GY2CCQWN.mjs.map → reducer-native-test-harness-HSXRUGOR.mjs.map} +0 -0
- /package/dist/agent-verifier/{static-scaffold-DJJRKMNB.mjs.map → static-scaffold-KSOTKJNQ.mjs.map} +0 -0
- /package/dist/agent-verifier/{sync-YWK4SPWV.mjs.map → sync-MQJJEZAA.mjs.map} +0 -0
- /package/dist/agent-verifier/{test-LQAGEQLY.mjs.map → test-R6HC6CYZ.mjs.map} +0 -0
- /package/dist/agent-verifier/{workspace-codegen-4IWICKLB.mjs.map → workspace-codegen-SPPVHURX.mjs.map} +0 -0
- /package/dist/{global-config-NLGAFSRU.js.map → global-config-VQWFTIAV.js.map} +0 -0
|
@@ -17,6 +17,7 @@ Use the reducer framework for:
|
|
|
17
17
|
- prompts, continuations, and reducer-owned windows
|
|
18
18
|
- player-facing view projection
|
|
19
19
|
- setup bootstrap steps
|
|
20
|
+
- terminal outcomes, automated procedures, and game-event evidence
|
|
20
21
|
|
|
21
22
|
## Purity
|
|
22
23
|
|
|
@@ -63,6 +64,7 @@ import {
|
|
|
63
64
|
moveCardFromPlayerZoneToSharedZone,
|
|
64
65
|
setActivePlayers,
|
|
65
66
|
setPhaseState,
|
|
67
|
+
endGame,
|
|
66
68
|
} from "@dreamboard/app-sdk/reducer";
|
|
67
69
|
```
|
|
68
70
|
|
|
@@ -103,7 +105,8 @@ export const gameContract = defineGameContract({
|
|
|
103
105
|
state: {
|
|
104
106
|
public: z.object({
|
|
105
107
|
currentJudgeId: ids.playerId,
|
|
106
|
-
|
|
108
|
+
round: z.number().int().nonnegative(),
|
|
109
|
+
gameOver: z.boolean(),
|
|
107
110
|
}),
|
|
108
111
|
private: z.object({
|
|
109
112
|
secretNotes: z.array(z.string()),
|
|
@@ -114,7 +117,8 @@ export const gameContract = defineGameContract({
|
|
|
114
117
|
initial: {
|
|
115
118
|
public: ({ playerIds }) => ({
|
|
116
119
|
currentJudgeId: playerIds[0],
|
|
117
|
-
|
|
120
|
+
round: 1,
|
|
121
|
+
gameOver: false,
|
|
118
122
|
}),
|
|
119
123
|
private: () => ({
|
|
120
124
|
secretNotes: [],
|
|
@@ -127,6 +131,43 @@ export const gameContract = defineGameContract({
|
|
|
127
131
|
});
|
|
128
132
|
```
|
|
129
133
|
|
|
134
|
+
Use state fields for ongoing game progress. Use `endGame(...)` and
|
|
135
|
+
`GameOutcome` for terminal results instead of preserving a parallel
|
|
136
|
+
`winnerPlayerId` or score-map convention as the result contract.
|
|
137
|
+
|
|
138
|
+
### Terminal outcomes
|
|
139
|
+
|
|
140
|
+
Call `endGame(state, outcome)` when the reducer reaches a terminal state.
|
|
141
|
+
`GameOutcome` owns the result reason and ordered standings, including ties,
|
|
142
|
+
score breakdowns, and tie-break evidence.
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
return endGame(nextState, {
|
|
146
|
+
reason: {
|
|
147
|
+
code: "ROUND_LIMIT_REACHED",
|
|
148
|
+
message: "The final round is complete.",
|
|
149
|
+
},
|
|
150
|
+
standings: [
|
|
151
|
+
{
|
|
152
|
+
playerId: "player-1",
|
|
153
|
+
rank: 1,
|
|
154
|
+
result: "win",
|
|
155
|
+
score: 18,
|
|
156
|
+
scoreBreakdown: [{ id: "routes", label: "Routes", value: 12 }],
|
|
157
|
+
tieBreaks: [{ id: "cards-left", label: "Cards left", value: 2 }],
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
playerId: "player-2",
|
|
161
|
+
rank: 2,
|
|
162
|
+
result: "loss",
|
|
163
|
+
score: 15,
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The UI may present this result, but reducer authority decides it.
|
|
170
|
+
|
|
130
171
|
## Generated `shared/manifest-contract`
|
|
131
172
|
|
|
132
173
|
The generated `shared/manifest-contract` module exports more than the
|
|
@@ -417,6 +458,10 @@ Continuation IDs and window IDs default to their registry keys when you omit
|
|
|
417
458
|
Keep hard legality checks in `validate(...)` or `reduce(...)`. `available(...)`
|
|
418
459
|
only filters the surfaced available-action list.
|
|
419
460
|
|
|
461
|
+
When an action is unavailable for a player-facing reason, project that reason
|
|
462
|
+
through the reducer descriptor or view. React should display reducer authority;
|
|
463
|
+
it should not recompute a second legality model.
|
|
464
|
+
|
|
420
465
|
```ts
|
|
421
466
|
import { z } from "zod";
|
|
422
467
|
import { defineAction } from "@dreamboard/app-sdk/reducer";
|
|
@@ -73,6 +73,7 @@ Describe how players gain or lose progress.
|
|
|
73
73
|
- when scoring happens
|
|
74
74
|
- round-end or trick-end resolution
|
|
75
75
|
- tie-breakers for intermediate rankings
|
|
76
|
+
- named score breakdowns players need to understand the result
|
|
76
77
|
|
|
77
78
|
### `Winning conditions`
|
|
78
79
|
|
|
@@ -83,6 +84,10 @@ State the exact end trigger and winner resolution rule.
|
|
|
83
84
|
- how ties are broken
|
|
84
85
|
- what happens if multiple end conditions become true at once
|
|
85
86
|
|
|
87
|
+
Write outcome rules in enough detail for a coding agent to produce a
|
|
88
|
+
`GameOutcome`: result reason, ordered standings, tie ranks, tie-break rows, and
|
|
89
|
+
score breakdowns where relevant.
|
|
90
|
+
|
|
86
91
|
### `Special rules and edge cases`
|
|
87
92
|
|
|
88
93
|
List rules that are easy to miss during implementation.
|
|
@@ -92,6 +97,11 @@ List rules that are easy to miss during implementation.
|
|
|
92
97
|
- no-op or invalid actions
|
|
93
98
|
- forced actions versus optional actions
|
|
94
99
|
- what happens when a player cannot act
|
|
100
|
+
- deterministic solo or automa procedures, including what event should be shown
|
|
101
|
+
to players after the procedure runs
|
|
102
|
+
|
|
103
|
+
Automa and environment behavior should be described as game procedures, not as
|
|
104
|
+
extra participants. Session seats belong to human players.
|
|
95
105
|
|
|
96
106
|
## Example template
|
|
97
107
|
|
|
@@ -136,13 +136,17 @@ export default defineScenario({
|
|
|
136
136
|
expect(state.lastRoll).toBe(6);
|
|
137
137
|
expect(state.scores["player-1"]).toBe(9);
|
|
138
138
|
expect(state.scores["player-2"]).toBe(12);
|
|
139
|
-
expect(state.
|
|
140
|
-
expect(view("player-2").
|
|
139
|
+
expect(state.gameOver).toBe(true);
|
|
140
|
+
expect(view("player-2").gameOver).toBe(true);
|
|
141
141
|
expect(history().accepted().length).toBe(6);
|
|
142
142
|
},
|
|
143
143
|
});
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
+
For terminal games, assert the reducer-owned outcome when the generated
|
|
147
|
+
scenario helpers expose it. Do not infer the winner in tests by sorting scores
|
|
148
|
+
unless that derived view is itself part of the rules.
|
|
149
|
+
|
|
146
150
|
## Scenario context
|
|
147
151
|
|
|
148
152
|
`when(...)` and `then(...)` receive typed helpers from `test/testing-types.ts`.
|
|
@@ -224,7 +228,7 @@ then: ({ phase, history, expect, publicState }) => {
|
|
|
224
228
|
expect(history().rejected().length).toBe(1);
|
|
225
229
|
expect(history().accepted().length).toBe(0);
|
|
226
230
|
expect(phase()).toBe("takeTurn");
|
|
227
|
-
expect(publicState().
|
|
231
|
+
expect(publicState().gameOver).toBe(false);
|
|
228
232
|
};
|
|
229
233
|
```
|
|
230
234
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/services/project/static-scaffold.ts","../../src/release/authoring-release-set.generated.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { readdir, readFile, rmdir } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { GameTopologyManifest } from \"@dreamboard-games/sdk/types\";\nimport { AUTHORING_RELEASE_SET } from \"../../release/authoring-release-set.js\";\nimport { REDUCER_TESTING_TYPES_WRAPPER_CONTENT } from \"../../templates/testing-types-content.js\";\nimport {\n MANIFEST_TYPECHECK_CONFIG_FILE,\n PROJECT_CONFIG_FILE,\n PROJECT_DIR_NAME,\n} from \"../../constants.js\";\nimport type { LocalMaintainerRegistryConfig } from \"../../types.js\";\nimport { ensureDir } from \"../../utils/fs.js\";\nimport { materializeManifest } from \"./manifest-authoring.js\";\nimport { isDynamicSeedPath } from \"./scaffold-ownership.js\";\nimport {\n normalizeOwnedProjectPath,\n readWorkspaceTextFile,\n readWorkspaceTextFileIfExists,\n removeWorkspacePath,\n resolveWorkspacePath,\n unlinkWorkspaceFile,\n workspacePathExists,\n writeWorkspaceTextFile,\n} from \"./workspace-path.js\";\nimport {\n FRAMEWORK_PNPM_OVERRIDES,\n FRAMEWORK_REACT_DEPENDENCIES,\n FRAMEWORK_ZOD_VERSION,\n} from \"./framework-dependencies.js\";\n\ntype StaticScaffoldMode = \"new\" | \"update\";\ntype StaticAssetEntry = {\n targetPath: string;\n content: string;\n};\ntype StaticScaffoldOptions = {\n localMaintainerRegistry?: LocalMaintainerRegistryConfig | null;\n};\ntype RootPackageJsonShape = {\n private?: boolean;\n packageManager?: string;\n scripts?: Record<string, string>;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n optionalDependencies?: Record<string, string>;\n peerDependencies?: Record<string, string>;\n overrides?: Record<string, unknown>;\n pnpm?: Record<string, unknown>;\n [key: string]: unknown;\n};\n\nconst DREAMBOARD_SYNC_COMMAND = \"dreamboard sync\";\nconst DREAMBOARD_GITIGNORE_BLOCK = [\n \"# Dreamboard local state\",\n \".dreamboard/state.json\",\n \".dreamboard/snapshot.json\",\n \".dreamboard/dev/\",\n \".dreamboard/generated/\",\n \"\",\n].join(\"\\n\");\nconst TESTING_TYPES_STUB =\n \"export function defineScenario(scenario) { return scenario; }\\n\";\nconst GENERATED_TESTING_TYPES_PREFIX = \"// Generated by dreamboard\";\nconst GENERATED_SCENARIO_PREFIX = \"// Generated by dreamboard scaffold.\";\nconst LEGACY_DREAMBOARD_COMPONENT_INDEX_CONTENT = `export {\n ErrorBoundary,\n type ErrorBoundaryProps,\n} from \"@dreamboard-games/sdk/ui\";\nexport { PluginRuntime, type PluginRuntimeProps } from \"@dreamboard-games/sdk/runtime\";\n`;\nconst OLD_LEGACY_DREAMBOARD_COMPONENT_INDEX_CONTENT = [\n \"export {\",\n \" ErrorBoundary,\",\n \" PluginRuntime,\",\n \" type ErrorBoundaryProps,\",\n \" type PluginRuntimeProps,\",\n `} from \"@dreamboard/ui-${\"sdk\"}\";`,\n \"\",\n].join(\"\\n\");\nconst OLD_PUBLIC_DREAMBOARD_COMPONENT_INDEX_CONTENT = [\n \"export {\",\n \" ErrorBoundary,\",\n \" PluginRuntime,\",\n \" type ErrorBoundaryProps,\",\n \" type PluginRuntimeProps,\",\n `} from \"@dreamboard-games/ui-${\"sdk\"}\";`,\n \"\",\n].join(\"\\n\");\nconst INITIAL_SCENARIO_CONTENT = `${GENERATED_SCENARIO_PREFIX}\nimport { defineScenario } from \"../testing-types\";\n\nexport default defineScenario({\n id: \"smoke-initial-turn\",\n description:\n \"Sanity check that the scaffolded workspace boots into its initial phase.\",\n from: \"initial-turn\",\n when: async () => undefined,\n then: ({ expect, interactions, players, state }) => {\n const playerIds = players();\n expect(playerIds).toHaveLength(playerIds.length);\n expect(playerIds.length).toBeGreaterThanOrEqual(1);\n expect(state()).toBe(\"setup\");\n for (const playerId of playerIds) {\n expect(interactions(playerId)).toEqual([]);\n }\n },\n});\n`;\nconst STATIC_ASSET_ROOT = resolveStaticAssetRoot();\nconst SDK_DEPENDENCY_RANGES = {\n \"@dreamboard-games/sdk\": AUTHORING_RELEASE_SET.packages.sdk.version,\n} as const;\nconst DEV_HOST_DEPENDENCY_RANGES = {\n \"@dreamboard-games/dev-host\": AUTHORING_RELEASE_SET.packages.devHost.version,\n} as const;\nconst DREAMBOARD_PACKAGE_OVERRIDES = {\n \"@dreamboard-games/api-client\":\n AUTHORING_RELEASE_SET.packages.apiClient.version,\n \"@dreamboard-games/dev-host\": AUTHORING_RELEASE_SET.packages.devHost.version,\n \"@dreamboard-games/sdk\": AUTHORING_RELEASE_SET.packages.sdk.version,\n} as const;\n\nconst FRAMEWORK_SCRIPTS = {\n dev: \"dreamboard dev\",\n \"test:ui\":\n \"tsx --tsconfig test/tsconfig.tsx-runtime.json --test test/ui/**/*.test.tsx\",\n typecheck: `tsc --noEmit -p ${MANIFEST_TYPECHECK_CONFIG_FILE} && tsc --noEmit -p app/tsconfig.json && tsc --noEmit -p ui/tsconfig.json`,\n \"typecheck:manifest\": `tsc --noEmit -p ${MANIFEST_TYPECHECK_CONFIG_FILE}`,\n \"typecheck:app\": \"tsc --noEmit -p app/tsconfig.json\",\n \"typecheck:ui\": \"tsc --noEmit -p ui/tsconfig.json\",\n} as const;\nconst SHARED_DEPENDENCIES = {\n ...FRAMEWORK_REACT_DEPENDENCIES,\n} as const;\nconst ROOT_APP_DEPENDENCIES = {\n zod: FRAMEWORK_ZOD_VERSION,\n} as const;\nconst SHARED_DEV_DEPENDENCIES = {\n typescript: \"^5.9.2\",\n \"@types/node\": \"^24.5.2\",\n \"@types/react\": \"^19.0.0\",\n \"@types/react-dom\": \"^19.0.0\",\n csstype: \"^3.1.3\",\n tsx: \"^4.20.5\",\n} as const;\n\nexport async function scaffoldStaticWorkspace(\n projectRoot: string,\n mode: StaticScaffoldMode,\n options: StaticScaffoldOptions = {},\n): Promise<void> {\n await writeFrameworkStaticFiles(projectRoot, mode, options);\n await ensureDreamboardGitignore(projectRoot);\n await writeManifestTypecheckTsconfig(projectRoot);\n await removeLegacyVendoredSdkPaths(projectRoot);\n await removeLegacyDreamboardComponentPath(projectRoot);\n\n await ensureDir(resolveWorkspacePath(projectRoot, \"test/bases\"));\n await ensureDir(resolveWorkspacePath(projectRoot, \"test/scenarios\"));\n await ensureDir(resolveWorkspacePath(projectRoot, \"test/generated\"));\n\n await writeTestReadme(projectRoot);\n await writeGeneratedTestingStubs(projectRoot, mode);\n const initialTestPlayerCount = await inferInitialTestPlayerCount(projectRoot);\n await writeInitialBase(projectRoot, mode, initialTestPlayerCount);\n await writeInitialScenario(projectRoot, mode);\n await writeTestingTypes(projectRoot, mode);\n await writeTestTsconfig(projectRoot);\n\n if (await workspacePathExists(projectRoot, \"test/testing-types.d.ts\")) {\n await unlinkWorkspaceFile(projectRoot, \"test/testing-types.d.ts\");\n }\n if (await workspacePathExists(projectRoot, \"test/base-scenarios.json\")) {\n await unlinkWorkspaceFile(projectRoot, \"test/base-scenarios.json\");\n }\n\n await migrateLegacyScenarioImports(projectRoot);\n}\n\nasync function ensureDreamboardGitignore(projectRoot: string): Promise<void> {\n const existing = await readWorkspaceTextFileIfExists(\n projectRoot,\n \".gitignore\",\n );\n if (existing?.includes(\".dreamboard/state.json\")) {\n return;\n }\n await writeWorkspaceTextFile(\n projectRoot,\n \".gitignore\",\n `${existing ? `${existing.trimEnd()}\\n\\n` : \"\"}${DREAMBOARD_GITIGNORE_BLOCK}`,\n );\n}\n\nexport async function assertCliStaticScaffoldComplete(\n projectRoot: string,\n deletedPaths: readonly string[] = [],\n): Promise<void> {\n const expectedEntries = await getExpectedStaticEntries(projectRoot);\n const missingOrBlankPaths: string[] = [];\n\n for (const entry of expectedEntries) {\n const content = await readWorkspaceTextFileIfExists(\n projectRoot,\n entry.targetPath,\n );\n\n if (content === null || content.trim().length === 0) {\n missingOrBlankPaths.push(entry.targetPath);\n }\n }\n\n const staticPaths = new Set(expectedEntries.map((entry) => entry.targetPath));\n const deletedStaticPaths = deletedPaths\n .map(normalizeOwnedProjectPath)\n .filter(\n (filePath): filePath is string =>\n filePath !== null && staticPaths.has(filePath),\n )\n .sort();\n\n if (missingOrBlankPaths.length === 0 && deletedStaticPaths.length === 0) {\n return;\n }\n\n const problems: string[] = [];\n if (missingOrBlankPaths.length > 0) {\n problems.push(\n `missing or blank: ${summarizePaths(missingOrBlankPaths.sort())}`,\n );\n }\n if (deletedStaticPaths.length > 0) {\n problems.push(`deleted: ${summarizePaths(deletedStaticPaths)}`);\n }\n\n throw new Error(\n `CLI static scaffold is incomplete (${problems.join(\"; \")}). Run \\`${DREAMBOARD_SYNC_COMMAND}\\` to refresh the scaffold before compiling.`,\n );\n}\n\nasync function writeFrameworkStaticFiles(\n projectRoot: string,\n mode: StaticScaffoldMode,\n options: StaticScaffoldOptions,\n): Promise<void> {\n const assetEntries = await getStaticAssetEntries();\n\n for (const entry of assetEntries) {\n // Dynamic seed files are user-customizable: only write them on first\n // scaffold; preserve existing content on subsequent updates.\n if (mode === \"update\" && isDynamicSeedPath(entry.targetPath)) {\n const existing = await readWorkspaceTextFileIfExists(\n projectRoot,\n entry.targetPath,\n );\n if (existing !== null && existing.trim().length > 0) {\n continue;\n }\n }\n await writeWorkspaceTextFile(projectRoot, entry.targetPath, entry.content);\n }\n\n for (const entry of await getDynamicStaticEntries(\n projectRoot,\n mode,\n options,\n )) {\n await writeWorkspaceTextFile(projectRoot, entry.targetPath, entry.content);\n }\n\n if (!options.localMaintainerRegistry) {\n await removeWorkspacePath(projectRoot, \".npmrc\", { force: true });\n }\n}\n\nasync function removeLegacyVendoredSdkPaths(\n projectRoot: string,\n): Promise<void> {\n await removeWorkspacePath(projectRoot, \"app/sdk\", {\n recursive: true,\n force: true,\n });\n await removeWorkspacePath(projectRoot, \"ui/sdk\", {\n recursive: true,\n force: true,\n });\n}\n\nasync function removeLegacyDreamboardComponentPath(\n projectRoot: string,\n): Promise<void> {\n const legacyIndexProjectPath = \"ui/components/dreamboard/index.ts\";\n const legacyDirPath = resolveWorkspacePath(\n projectRoot,\n \"ui/components/dreamboard\",\n );\n const existing = await readWorkspaceTextFileIfExists(\n projectRoot,\n legacyIndexProjectPath,\n );\n\n const removableLegacyContents = new Set([\n LEGACY_DREAMBOARD_COMPONENT_INDEX_CONTENT.trim(),\n OLD_LEGACY_DREAMBOARD_COMPONENT_INDEX_CONTENT.trim(),\n OLD_PUBLIC_DREAMBOARD_COMPONENT_INDEX_CONTENT.trim(),\n ]);\n\n if (existing === null || !removableLegacyContents.has(existing.trim())) {\n return;\n }\n\n await unlinkWorkspaceFile(projectRoot, legacyIndexProjectPath);\n const remainingEntries = await readdir(legacyDirPath).catch(() => []);\n if (remainingEntries.length === 0) {\n await rmdir(legacyDirPath);\n }\n}\n\nasync function writeTestReadme(projectRoot: string): Promise<void> {\n await writeWorkspaceTextFile(\n projectRoot,\n \"test/README.md\",\n \"# Dreamboard Test Workspace\\n\\nTypeScript bases live in `test/bases/*.base.ts` and scenarios live in `test/scenarios/*.scenario.ts`.\\n\\n1. Define reusable seeded bases with `defineBase({ id, seed, players, setupProfileId?, setup })`.\\n2. Define scenarios with `defineScenario({ id, from, when, then })`.\\n3. Scenario assertions can read `players()`, `state()`, `view(playerId)`, and `interactions(playerId)`.\\n4. Generate deterministic base snapshots: `dreamboard test generate`.\\n5. Run tests: `dreamboard test run`.\\n\\nImport test helpers from `../testing-types`.\\n\\nGenerated artifacts are written to `test/generated/*` and should not be edited manually.\\n\",\n );\n}\n\nasync function writeInitialBase(\n projectRoot: string,\n mode: StaticScaffoldMode,\n players: number,\n): Promise<void> {\n if (mode === \"update\") {\n return;\n }\n\n await writeWorkspaceTextFile(\n projectRoot,\n \"test/bases/initial-turn.base.ts\",\n `import { defineBase } from \"../testing-types\";\n\nexport default defineBase({\n id: \"initial-turn\",\n seed: 1337,\n players: ${players},\n setup: async () => undefined,\n});\n`,\n );\n}\n\nasync function writeInitialScenario(\n projectRoot: string,\n mode: StaticScaffoldMode,\n): Promise<void> {\n if (mode === \"new\") {\n await writeWorkspaceTextFile(\n projectRoot,\n \"test/scenarios/smoke-initial-turn.scenario.ts\",\n INITIAL_SCENARIO_CONTENT,\n );\n return;\n }\n\n const existing = await readWorkspaceTextFileIfExists(\n projectRoot,\n \"test/scenarios/smoke-initial-turn.scenario.ts\",\n );\n if (\n existing === null ||\n existing.trim().length === 0 ||\n existing === INITIAL_SCENARIO_CONTENT\n ) {\n await writeWorkspaceTextFile(\n projectRoot,\n \"test/scenarios/smoke-initial-turn.scenario.ts\",\n INITIAL_SCENARIO_CONTENT,\n );\n }\n}\n\nasync function writeTestingTypes(\n projectRoot: string,\n mode: StaticScaffoldMode,\n): Promise<void> {\n if (mode === \"new\") {\n await writeWorkspaceTextFile(\n projectRoot,\n \"test/testing-types.ts\",\n REDUCER_TESTING_TYPES_WRAPPER_CONTENT,\n );\n return;\n }\n\n const existing = await readWorkspaceTextFileIfExists(\n projectRoot,\n \"test/testing-types.ts\",\n );\n if (shouldRefreshGeneratedTestingTypes(existing)) {\n await writeWorkspaceTextFile(\n projectRoot,\n \"test/testing-types.ts\",\n REDUCER_TESTING_TYPES_WRAPPER_CONTENT,\n );\n }\n}\n\nasync function writeGeneratedTestingStubs(\n projectRoot: string,\n mode: StaticScaffoldMode,\n): Promise<void> {\n const header = \"// Generated by dreamboard scaffold. Do not edit by hand.\\n\";\n await writeGeneratedTestingStubFile(\n projectRoot,\n \"test/generated/base-states.generated.ts\",\n `${header}export const BASE_STATES = {} as const;\\nexport const BASE_STATES_CONTRACT_FINGERPRINT = undefined;\\n`,\n mode,\n );\n await writeGeneratedTestingStubFile(\n projectRoot,\n \"test/generated/base-states.generated.d.ts\",\n `${header}export declare const BASE_STATES: Record<string, unknown>;\\nexport declare const BASE_STATES_CONTRACT_FINGERPRINT: string | undefined;\\n`,\n mode,\n );\n await writeGeneratedTestingStubFile(\n projectRoot,\n \"test/generated/testing-contract.ts\",\n `${header}export type BaseId = string;\\nexport type GameView = unknown;\\nexport type InteractionId = string;\\nexport type InteractionParamsOf<_Id extends string> = Record<string, unknown>;\\nexport type PhaseName = string;\\nexport type PlayerId = string;\\nexport type RejectionCode = string;\\nexport type StateName = string;\\nexport type TestRunner = \"reducer\" | \"remote\" | \"browser\";\\nexport type ViewByPhase = Record<string, GameView>;\\nexport type WorkspaceStageName<_Phase extends string = string> = string;\\nexport type Expectation = { [matcher: string]: (...args: unknown[]) => unknown; not: Expectation };\\nexport type ExpectFn = (actual: unknown) => Expectation;\\nexport type InteractionExplanation = { interactionId: string; phase: string; step: string | null; availability: \"available\" | \"notYourTurn\" | \"wrongPhase\" | \"wrongStep\" | \"blocked\"; rules: readonly { ruleId: string; outcome: \"passed\" | \"failed\" | \"notEvaluated\"; errorCode?: string; message?: string; }[]; actor: { required: readonly string[]; playerIsActor: boolean }; inputs: readonly { key: string; kind: string; eligibleCount: number | \"lazy\"; }[]; };\\nexport interface InteractionDescriptorFor<Id extends string = string> { interactionId: Id; [key: string]: unknown; }\\nexport interface ScenarioGameApi { start(): Promise<void>; submit<Id extends InteractionId>(playerId: PlayerId, interactionId: Id, params?: InteractionParamsOf<Id>): Promise<void>; }\\nexport interface BaseContext { game: ScenarioGameApi; players(): readonly PlayerId[]; seat(index: number): PlayerId; }\\nexport interface SharedScenarioContext { game: ScenarioGameApi; players(): readonly PlayerId[]; seat(index: number): PlayerId; state(): StateName; view(playerId: PlayerId): GameView; interactions(playerId: PlayerId): readonly InteractionDescriptorFor[]; explain(playerId: PlayerId, interactionId: InteractionId): InteractionExplanation; expect: ExpectFn; }\\nexport type ScenarioContext<Phase extends PhaseName | undefined = undefined> = Omit<SharedScenarioContext, \"state\" | \"view\"> & { state(): Phase extends PhaseName ? Phase : StateName; view(playerId: PlayerId): Phase extends PhaseName ? ViewByPhase[Phase] : GameView; };\\nexport type ScenarioThenContext<_Runners extends readonly TestRunner[] = readonly [\"reducer\"], Phase extends PhaseName | undefined = undefined> = ScenarioContext<Phase>;\\nexport interface BaseDefinition { id: string; seed?: number; players?: number; setupProfileId?: string; extends?: BaseId | string; setup: (ctx: BaseContext) => void | Promise<void>; }\\nexport interface ScenarioDefinition<Runners extends readonly TestRunner[] = readonly [\"reducer\"], Phase extends PhaseName | undefined = undefined> { id: string; description?: string; from: BaseId | string; runners?: Runners; phase?: Phase; stage?: Phase extends PhaseName ? WorkspaceStageName<Phase> : never; when: (ctx: ScenarioContext<Phase>) => void | Promise<void>; then: (ctx: ScenarioThenContext<Runners, Phase>) => void | Promise<void>; }\\n`,\n mode,\n );\n await writeGeneratedTestingStubFile(\n projectRoot,\n \"test/generated/scenario-manifest.generated.ts\",\n `${header}export const SCENARIO_MANIFEST = [] as const;\\n`,\n mode,\n );\n}\n\nasync function writeGeneratedTestingStubFile(\n projectRoot: string,\n projectPath: string,\n content: string,\n mode: StaticScaffoldMode,\n): Promise<void> {\n if (mode === \"new\") {\n await writeWorkspaceTextFile(projectRoot, projectPath, content);\n return;\n }\n\n const existing = await readWorkspaceTextFileIfExists(\n projectRoot,\n projectPath,\n );\n if (\n existing === null ||\n existing.trim().length === 0 ||\n existing.startsWith(GENERATED_SCENARIO_PREFIX)\n ) {\n await writeWorkspaceTextFile(projectRoot, projectPath, content);\n }\n}\n\nasync function writeTestTsconfig(projectRoot: string): Promise<void> {\n await writeWorkspaceTextFile(\n projectRoot,\n \"test/tsconfig.json\",\n `${JSON.stringify(\n {\n compilerOptions: {\n target: \"ES2022\",\n module: \"ESNext\",\n moduleResolution: \"bundler\",\n strict: true,\n esModuleInterop: true,\n skipLibCheck: true,\n noEmit: true,\n },\n include: [\n \"./**/*.ts\",\n \"./**/*.d.ts\",\n \"../shared/**/*.ts\",\n \"../shared/**/*.d.ts\",\n ],\n },\n null,\n 2,\n )}\\n`,\n );\n}\n\nasync function writeManifestTypecheckTsconfig(\n projectRoot: string,\n): Promise<void> {\n await writeWorkspaceTextFile(\n projectRoot,\n MANIFEST_TYPECHECK_CONFIG_FILE,\n `${JSON.stringify(\n {\n compilerOptions: {\n target: \"ES2022\",\n module: \"ESNext\",\n moduleResolution: \"bundler\",\n strict: true,\n esModuleInterop: true,\n skipLibCheck: true,\n noEmit: true,\n allowImportingTsExtensions: true,\n },\n include: [\"./manifest.ts\"],\n },\n null,\n 2,\n )}\\n`,\n );\n}\n\nexport async function migrateLegacyScenarioImports(\n projectRoot: string,\n): Promise<void> {\n if (!(await workspacePathExists(projectRoot, \"test/scenarios\"))) return;\n\n const scenariosRoot = resolveWorkspacePath(projectRoot, \"test/scenarios\");\n const scenarioFiles = await collectScenarioFiles(scenariosRoot);\n for (const filePath of scenarioFiles) {\n const projectPath = toWorkspaceProjectPath(projectRoot, filePath);\n const content = await readWorkspaceTextFile(projectRoot, projectPath);\n if (!content.includes(\"@dreamboard/cli/testing\")) continue;\n\n const relativeToTestingTypes = normalizeImportPath(\n path.relative(\n path.dirname(filePath),\n path.join(projectRoot, \"test\", \"testing-types\"),\n ),\n );\n\n const migrated = content\n .replaceAll('\"@dreamboard/cli/testing\"', `\"${relativeToTestingTypes}\"`)\n .replaceAll(\"'@dreamboard/cli/testing'\", `'${relativeToTestingTypes}'`);\n\n if (migrated !== content) {\n await writeWorkspaceTextFile(projectRoot, projectPath, migrated);\n }\n }\n}\n\nfunction shouldRefreshGeneratedTestingTypes(\n existingContent: string | null,\n): boolean {\n if (existingContent === null || existingContent.trim().length === 0) {\n return true;\n }\n if (existingContent === TESTING_TYPES_STUB) {\n return true;\n }\n return existingContent.startsWith(GENERATED_TESTING_TYPES_PREFIX);\n}\n\nasync function inferInitialTestPlayerCount(\n projectRoot: string,\n): Promise<number> {\n if (!(await workspacePathExists(projectRoot, \"manifest.ts\"))) {\n return 4;\n }\n\n try {\n const manifest = await materializeManifest(projectRoot);\n return manifest.players.optimalPlayers ?? manifest.players.minPlayers ?? 4;\n } catch {\n return 4;\n }\n}\n\nasync function collectScenarioFiles(rootDir: string): Promise<string[]> {\n const files: string[] = [];\n const stack = [rootDir];\n\n while (stack.length > 0) {\n const dir = stack.pop();\n if (!dir) continue;\n\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n stack.push(fullPath);\n } else if (entry.isFile() && entry.name.endsWith(\".ts\")) {\n files.push(fullPath);\n }\n }\n }\n\n return files;\n}\n\nexport function resolveStaticAssetRoot(\n importUrl: string = import.meta.url,\n): string {\n const candidates = [\n fileURLToPath(new URL(\"../../scaffold/assets/static/\", importUrl)),\n fileURLToPath(new URL(\"./scaffold/assets/static/\", importUrl)),\n fileURLToPath(new URL(\"../scaffold/assets/static/\", importUrl)),\n ];\n\n for (const candidate of candidates) {\n if (existsSync(candidate)) {\n return candidate;\n }\n }\n\n throw new Error(\n `Unable to locate CLI static scaffold assets. Checked: ${candidates.join(\", \")}`,\n );\n}\n\nasync function getStaticAssetEntries(): Promise<StaticAssetEntry[]> {\n const files = await walkFiles(STATIC_ASSET_ROOT);\n const entries: StaticAssetEntry[] = [];\n\n for (const filePath of files) {\n const targetPath = normalizeOwnedProjectPath(\n path.relative(STATIC_ASSET_ROOT, filePath).replaceAll(path.sep, \"/\"),\n );\n if (targetPath === null) {\n throw new Error(`Unsafe static scaffold asset path: ${filePath}`);\n }\n entries.push({\n targetPath,\n content: await readFile(filePath, \"utf8\"),\n });\n }\n\n entries.sort((left, right) =>\n left.targetPath.localeCompare(right.targetPath),\n );\n return entries;\n}\n\nasync function getDynamicStaticEntries(\n projectRoot: string,\n mode: StaticScaffoldMode,\n options: StaticScaffoldOptions = {},\n): Promise<StaticAssetEntry[]> {\n const entries: StaticAssetEntry[] = [\n {\n targetPath: \"package.json\",\n content: await buildRootPackageJson(projectRoot, mode, options),\n },\n {\n targetPath: \"ui/package.json\",\n content: buildUiPackageJson(),\n },\n ];\n\n if (options.localMaintainerRegistry) {\n entries.push({\n targetPath: \".npmrc\",\n content: buildWorkspaceNpmrc(options.localMaintainerRegistry.registryUrl),\n });\n }\n\n return entries;\n}\n\nasync function getExpectedStaticEntries(\n projectRoot: string,\n): Promise<StaticAssetEntry[]> {\n const entries = [\n ...(await getStaticAssetEntries()).filter(\n (entry) => entry.targetPath !== \".npmrc\",\n ),\n ...(await getDynamicStaticEntries(projectRoot, \"update\")),\n ];\n entries.sort((left, right) =>\n left.targetPath.localeCompare(right.targetPath),\n );\n return entries;\n}\n\nasync function walkFiles(rootDir: string): Promise<string[]> {\n const files: string[] = [];\n const stack = [rootDir];\n\n while (stack.length > 0) {\n const currentDir = stack.pop();\n if (!currentDir) continue;\n\n const entries = await readdir(currentDir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = path.join(currentDir, entry.name);\n if (entry.isDirectory()) {\n stack.push(fullPath);\n } else if (entry.isFile()) {\n files.push(fullPath);\n }\n }\n }\n\n files.sort((left, right) => left.localeCompare(right));\n return files;\n}\n\nasync function buildRootPackageJson(\n projectRoot: string,\n mode: StaticScaffoldMode,\n options: StaticScaffoldOptions,\n): Promise<string> {\n const sdkPackageRanges = {\n ...SDK_DEPENDENCY_RANGES,\n ...(options.localMaintainerRegistry?.packages ?? {}),\n };\n const existingPackageJson =\n mode === \"update\" &&\n (await workspacePathExists(projectRoot, \"package.json\"))\n ? (JSON.parse(\n await readWorkspaceTextFile(projectRoot, \"package.json\"),\n ) as RootPackageJsonShape)\n : null;\n const {\n dreamboardFrameworkVersion: _legacyFrameworkVersion,\n ...existingPackageJsonWithoutLegacyVersion\n } = existingPackageJson ?? {};\n const frameworkDependencies = {\n \"@dreamboard-games/sdk\":\n sdkPackageRanges[\"@dreamboard-games/sdk\"] ??\n SDK_DEPENDENCY_RANGES[\"@dreamboard-games/sdk\"],\n ...SHARED_DEPENDENCIES,\n ...ROOT_APP_DEPENDENCIES,\n };\n const frameworkDevDependencies = {\n ...SHARED_DEV_DEPENDENCIES,\n ...DEV_HOST_DEPENDENCY_RANGES,\n };\n const nextPackageJson: RootPackageJsonShape = {\n ...existingPackageJsonWithoutLegacyVersion,\n private: true,\n packageManager: AUTHORING_RELEASE_SET.packageManager,\n scripts: {\n ...(existingPackageJson?.scripts ?? {}),\n ...FRAMEWORK_SCRIPTS,\n },\n dependencies: {\n ...(existingPackageJson?.dependencies ?? {}),\n ...frameworkDependencies,\n },\n devDependencies: {\n ...(existingPackageJson?.devDependencies ?? {}),\n ...frameworkDevDependencies,\n },\n pnpm: mergePnpmConfig(existingPackageJson?.pnpm),\n };\n return `${JSON.stringify(nextPackageJson, null, 2)}\\n`;\n}\n\nfunction mergePnpmConfig(\n existingPnpm: Record<string, unknown> | undefined,\n): Record<string, unknown> {\n const existingOverrides =\n existingPnpm?.overrides &&\n typeof existingPnpm.overrides === \"object\" &&\n !Array.isArray(existingPnpm.overrides)\n ? (existingPnpm.overrides as Record<string, unknown>)\n : {};\n return {\n ...(existingPnpm ?? {}),\n overrides: {\n ...existingOverrides,\n ...FRAMEWORK_PNPM_OVERRIDES,\n ...DREAMBOARD_PACKAGE_OVERRIDES,\n },\n };\n}\n\nfunction buildWorkspaceNpmrc(registryUrl: string): string {\n return `@dreamboard-games:registry=${registryUrl}\\n`;\n}\n\nfunction buildUiPackageJson(): string {\n return `${JSON.stringify(\n {\n private: true,\n dependencies: SHARED_DEPENDENCIES,\n devDependencies: SHARED_DEV_DEPENDENCIES,\n },\n null,\n 2,\n )}\\n`;\n}\n\nfunction normalizeImportPath(relativePath: string): string {\n const normalized = relativePath.replaceAll(\"\\\\\", \"/\");\n if (normalized.startsWith(\".\")) return normalized;\n return `./${normalized}`;\n}\n\nfunction toWorkspaceProjectPath(projectRoot: string, filePath: string): string {\n const relativePath = path\n .relative(path.resolve(projectRoot), path.resolve(filePath))\n .replaceAll(path.sep, \"/\");\n const projectPath = normalizeOwnedProjectPath(relativePath);\n if (projectPath === null) {\n throw new Error(`Unsafe project path: ${relativePath}`);\n }\n return projectPath;\n}\n\nfunction summarizePaths(paths: readonly string[]): string {\n const maxShown = 5;\n const shown = paths.slice(0, maxShown).join(\", \");\n if (paths.length <= maxShown) return shown;\n return `${shown}, and ${paths.length - maxShown} more`;\n}\n","// Generated by scripts/generate-authoring-release-set.ts. Do not edit by hand.\nimport type { AuthoringReleaseSetV1 } from \"./authoring-release-set.js\";\n\nexport const AUTHORING_RELEASE_SET = {\n \"schemaVersion\": 1,\n \"channel\": \"public\",\n \"packages\": {\n \"cli\": {\n \"name\": \"@dreamboard-games/cli\",\n \"version\": \"0.1.30-alpha.16\"\n },\n \"sdk\": {\n \"name\": \"@dreamboard-games/sdk\",\n \"version\": \"0.4.0-alpha.4\"\n },\n \"apiClient\": {\n \"name\": \"@dreamboard-games/api-client\",\n \"version\": \"0.3.0-alpha.4\"\n },\n \"devHost\": {\n \"name\": \"@dreamboard-games/dev-host\",\n \"version\": \"0.1.30-alpha.16\"\n }\n },\n \"protocols\": {\n \"authoringAdapter\": 1,\n \"devHost\": 1,\n \"verifier\": 1\n },\n \"schemas\": {\n \"scaffold\": 2,\n \"manifest\": 2,\n \"generatedArtifacts\": 1\n },\n \"registry\": {\n \"kind\": \"public-npm\",\n \"portable\": true\n },\n \"packageManager\": \"pnpm@10.4.1\",\n \"releaseSetId\": \"sha256:ef5945255c08ccf7dbbff9abbf77fdcc91546bdd3c5703671ef6c22052e23d3f\"\n} as const satisfies AuthoringReleaseSetV1;\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/config/credential-store.ts","../../src/build-target.ts"],"sourcesContent":["/**\n * Single writer for the long-lived Dreamboard session credentials.\n *\n * Design invariants (enforced at the type level and tested in\n * `credential-store.test.ts`):\n *\n * 1. This module is the ONLY place in the CLI that writes credentials to\n * disk or the OS keychain. `global-config.ts` used to own both the\n * config and the credentials via `saveGlobalConfig`, which made it\n * trivial to wipe a refresh token by accident. The `GlobalConfig` type\n * no longer carries credentials, so attempting to persist one through\n * the config path is a type error.\n *\n * 2. The mutating surface is intentionally narrow:\n * - `setCredentials(c)` for refreshable sessions (both tokens present)\n * - `setAccessOnlySession(accessToken)` for the `auth set` / `config set\n * --token` power-user path, which has no refresh token by\n * construction\n * - `clearCredentials()` wipes the file entirely\n * There is no \"partial update\" API. `Credentials` requires both\n * `accessToken` and `refreshToken`, so it is impossible to persist a\n * half-populated refreshable session.\n *\n * 3. Writes go through `atomicWriteFile` + `withFileLock`, so a crash or\n * interrupt during `dreamboard sync`/`compile` cannot leave `auth.json`\n * truncated, and parallel CLI invocations cannot clobber each other's\n * rotated refresh tokens.\n *\n * 4. The on-disk JSON shape for the file backend is kept backward\n * compatible: we continue to read/write `authToken` + `refreshToken`\n * so existing users are not forced to log in again after this change.\n * A newer `accessToken` key is also accepted for read to ease any\n * future format bump.\n *\n * 5. Development builds may still use the file backend for local testing.\n * Published builds require the OS keychain and fail closed when it is\n * unavailable.\n */\n\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { promises as fs } from \"node:fs\";\nimport { PROJECT_DIR_NAME } from \"../constants.js\";\nimport { IS_PUBLISHED_BUILD } from \"../build-target.js\";\nimport {\n atomicWriteFile,\n withFileLock,\n type FileLockOptions,\n} from \"../utils/atomic-file.js\";\n\n/**\n * Fully refreshable session. `accessToken` is the Clerk OAuth bootstrap token\n * retained for refresh/exchange compatibility; ordinary API calls use\n * `dreamboardApiToken`.\n */\nexport type Credentials = {\n readonly accessToken: string;\n readonly refreshToken: string;\n readonly tokenExpiresAt?: string;\n readonly dreamboardApiToken?: string;\n readonly dreamboardApiExpiresAt?: string;\n readonly clerkOAuthIssuer?: string;\n readonly clerkOAuthClientId?: string;\n readonly clerkOAuthTokenUrl?: string;\n readonly environment?: string;\n};\n\n/**\n * Raw on-disk snapshot. Either or both fields may be present. The refresh\n * coordinator only acts on snapshots that have both tokens populated.\n */\nexport type StoredSessionSnapshot = {\n readonly accessToken?: string;\n readonly refreshToken?: string;\n readonly tokenExpiresAt?: string;\n readonly dreamboardApiToken?: string;\n readonly dreamboardApiExpiresAt?: string;\n readonly clerkOAuthIssuer?: string;\n readonly clerkOAuthClientId?: string;\n readonly clerkOAuthTokenUrl?: string;\n readonly environment?: string;\n};\n\nexport type CredentialBackendName = \"file\" | \"keychain\";\n\nexport type CredentialBackend = {\n readonly name: CredentialBackendName;\n read(): Promise<StoredSessionSnapshot | null>;\n writeFull(creds: Credentials): Promise<void>;\n writeAccessOnly(accessToken: string): Promise<void>;\n clear(): Promise<void>;\n};\n\nexport type CredentialLockOps = {\n readonly backendName: CredentialBackendName;\n read(): Promise<StoredSessionSnapshot | null>;\n writeFull(creds: Credentials): Promise<void>;\n writeAccessOnly(accessToken: string): Promise<void>;\n clear(): Promise<void>;\n};\n\ntype DiskShape = Partial<{\n clerkAccessToken: string;\n clerkAccessExpiresAt: string;\n accessToken: string;\n authToken: string;\n refreshToken: string;\n tokenExpiresAt: string;\n dreamboardApiToken: string;\n dreamboardApiExpiresAt: string;\n clerkOAuthIssuer: string;\n clerkOAuthClientId: string;\n clerkOAuthTokenUrl: string;\n environment: string;\n}>;\n\nexport function getCredentialFilePath(): string {\n return path.join(os.homedir(), PROJECT_DIR_NAME, \"auth.json\");\n}\n\nfunction getCredentialLockPath(): string {\n return `${getCredentialFilePath()}.lock`;\n}\n\nasync function fileRead(): Promise<StoredSessionSnapshot | null> {\n const filePath = getCredentialFilePath();\n let data: string;\n try {\n data = await fs.readFile(filePath, \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw err;\n }\n if (data.trim().length === 0) {\n return null;\n }\n let parsed: DiskShape;\n try {\n parsed = JSON.parse(data) as DiskShape;\n } catch {\n return null;\n }\n const accessToken =\n parsed.clerkAccessToken ?? parsed.accessToken ?? parsed.authToken;\n const refreshToken = parsed.refreshToken;\n if (!accessToken && !refreshToken) return null;\n return {\n accessToken: accessToken || undefined,\n refreshToken: refreshToken || undefined,\n tokenExpiresAt:\n parsed.clerkAccessExpiresAt || parsed.tokenExpiresAt || undefined,\n dreamboardApiToken: parsed.dreamboardApiToken || undefined,\n dreamboardApiExpiresAt: parsed.dreamboardApiExpiresAt || undefined,\n clerkOAuthIssuer: parsed.clerkOAuthIssuer || undefined,\n clerkOAuthClientId: parsed.clerkOAuthClientId || undefined,\n clerkOAuthTokenUrl: parsed.clerkOAuthTokenUrl || undefined,\n environment: parsed.environment || undefined,\n };\n}\n\nasync function writeFilePayload(payload: DiskShape): Promise<void> {\n await atomicWriteFile(\n getCredentialFilePath(),\n `${JSON.stringify(payload, null, 2)}\\n`,\n { mode: 0o600 },\n );\n}\n\nasync function fileWriteFull(creds: Credentials): Promise<void> {\n if (!creds.accessToken || !creds.refreshToken) {\n throw new Error(\n \"Refusing to persist credentials with an empty accessToken or refreshToken.\",\n );\n }\n await writeFilePayload({\n clerkAccessToken: creds.accessToken,\n refreshToken: creds.refreshToken,\n clerkAccessExpiresAt: creds.tokenExpiresAt,\n dreamboardApiToken: creds.dreamboardApiToken,\n dreamboardApiExpiresAt: creds.dreamboardApiExpiresAt,\n clerkOAuthIssuer: creds.clerkOAuthIssuer,\n clerkOAuthClientId: creds.clerkOAuthClientId,\n clerkOAuthTokenUrl: creds.clerkOAuthTokenUrl,\n environment: creds.environment,\n });\n}\n\nasync function fileWriteAccessOnly(accessToken: string): Promise<void> {\n if (!accessToken) {\n throw new Error(\"Refusing to persist an empty access token.\");\n }\n await writeFilePayload({ authToken: accessToken });\n}\n\nasync function fileClear(): Promise<void> {\n const filePath = getCredentialFilePath();\n try {\n await fs.unlink(filePath);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n}\n\nexport const fileCredentialBackend: CredentialBackend = {\n name: \"file\",\n read: fileRead,\n writeFull: fileWriteFull,\n writeAccessOnly: fileWriteAccessOnly,\n clear: fileClear,\n};\n\nexport type BackendResolver = () =>\n | CredentialBackend\n | Promise<CredentialBackend>;\n\nexport class CredentialStoreUnavailableError extends Error {\n readonly code = \"CREDENTIAL_STORE_UNAVAILABLE\";\n\n constructor(reason: string) {\n super(`Credential store unavailable: ${reason}`);\n this.name = \"CredentialStoreUnavailableError\";\n }\n}\n\nlet cachedBackend: CredentialBackend | null = null;\nlet migrationCompleted = false;\nlet backendResolver: BackendResolver = defaultBackendResolver;\n\n/**\n * Development resolver precedence:\n *\n * 1. `DREAMBOARD_CREDENTIAL_BACKEND` env var (debugging / CI override).\n * - \"file\" -> force file\n * - \"keychain\" -> force keychain (falls back to file if the native\n * module or the OS keyring is unavailable)\n * - \"auto\" -> same as unset (use config)\n * - unknown -> throw so typos fail loud\n * 2. `credentialBackend` in `~/.dreamboard/config.json`.\n * - \"keychain\" -> opt in to the OS keychain (with file fallback)\n * - \"file\" / unset / malformed -> file\n * 3. Default: file backend.\n *\n * Published builds skip this precedence, require keychain, and throw\n * CREDENTIAL_STORE_UNAVAILABLE instead of falling back to plaintext.\n *\n * In development, keychain is opt-in because on macOS the OS login-keychain prompts for\n * the user's password the first time a new binary tries to write to an\n * item, and re-prompts whenever the Node binary signature changes. We\n * would rather ship a zero-prompt default and let users who care about\n * encrypted-at-rest storage enable it.\n *\n * The resolver is async because the keychain probe requires a dynamic\n * `@napi-rs/keyring` import.\n */\nasync function defaultBackendResolver(): Promise<CredentialBackend> {\n const override = (process.env.DREAMBOARD_CREDENTIAL_BACKEND ?? \"\")\n .trim()\n .toLowerCase();\n if (IS_PUBLISHED_BUILD) {\n if (override && override !== \"keychain\" && override !== \"auto\") {\n throw new CredentialStoreUnavailableError(\n \"published builds require the OS credential store\",\n );\n }\n const { tryKeychainBackend } = await import(\"./keychain-backend.js\");\n const keychain = await tryKeychainBackend();\n if (keychain.available) {\n return keychain.backend;\n }\n throw new CredentialStoreUnavailableError(keychain.reason);\n }\n\n if (override === \"file\") {\n return fileCredentialBackend;\n }\n if (override && override !== \"keychain\" && override !== \"auto\") {\n // Fail loud on typos rather than silently falling back: this env\n // var exists specifically for users who are debugging auth issues\n // and need to know their override took effect.\n throw new Error(\n `Unknown DREAMBOARD_CREDENTIAL_BACKEND value \"${override}\" (expected \"file\", \"keychain\", or \"auto\").`,\n );\n }\n\n const useKeychain =\n override === \"keychain\" || (await readCredentialBackendPreference());\n if (!useKeychain) {\n return fileCredentialBackend;\n }\n\n const { tryKeychainBackend } = await import(\"./keychain-backend.js\");\n const keychain = await tryKeychainBackend();\n if (keychain.available) {\n return keychain.backend;\n }\n // The user explicitly asked for keychain but the platform can't\n // provide one (no libsecret on Linux, missing native module, etc).\n // Silently degrade to the file backend so the CLI stays usable; the\n // active backend is still visible through `dreamboard auth status`.\n return fileCredentialBackend;\n}\n\nasync function readCredentialBackendPreference(): Promise<boolean> {\n try {\n // Dynamic import to avoid a top-level cycle with `global-config.ts`\n // (which imports `getCredentialFilePath` from this module). Using\n // the async path keeps the cycle purely lazy.\n const { loadGlobalConfig } = await import(\"./global-config.js\");\n const config = await loadGlobalConfig();\n return config.credentialBackend === \"keychain\";\n } catch {\n // If the config file is unreadable or the dynamic import fails\n // (e.g. during early bootstrap), fall back to the file-backed\n // default rather than crashing credential lookups.\n return false;\n }\n}\n\n/**\n * Override which backend is used. Tests use this to inject in-memory\n * backends; production code uses the default keychain-first resolver.\n */\nexport function setCredentialBackendResolver(resolver: BackendResolver): void {\n backendResolver = resolver;\n cachedBackend = null;\n migrationCompleted = false;\n}\n\nexport async function getCredentialBackend(): Promise<CredentialBackend> {\n if (cachedBackend === null) {\n cachedBackend = await backendResolver();\n // One-time migration: if we resolved to a non-file backend and\n // `auth.json` still has credentials from the old layout, copy them\n // over and remove the file. We only do this when the new backend is\n // empty, so repeated migrations cannot stomp a newer keychain\n // session with a stale file session.\n if (!migrationCompleted && cachedBackend.name !== \"file\") {\n await migrateFromFileBackendIfNeeded(cachedBackend, {\n failClosed: IS_PUBLISHED_BUILD,\n });\n }\n migrationCompleted = true;\n }\n return cachedBackend;\n}\n\nasync function migrateFromFileBackendIfNeeded(\n target: CredentialBackend,\n options: { failClosed?: boolean } = {},\n): Promise<void> {\n try {\n const [onDisk, onTarget] = await Promise.all([\n fileCredentialBackend.read(),\n target.read(),\n ]);\n if (!onDisk) return;\n if (onTarget) {\n // Target already has a session - the user has already migrated.\n // Remove the file so it cannot get re-used accidentally.\n await fileCredentialBackend.clear();\n return;\n }\n if (onDisk.accessToken && onDisk.refreshToken) {\n const migrated: Credentials = {\n accessToken: onDisk.accessToken,\n refreshToken: onDisk.refreshToken,\n tokenExpiresAt: onDisk.tokenExpiresAt,\n dreamboardApiToken: onDisk.dreamboardApiToken,\n dreamboardApiExpiresAt: onDisk.dreamboardApiExpiresAt,\n clerkOAuthIssuer: onDisk.clerkOAuthIssuer,\n clerkOAuthClientId: onDisk.clerkOAuthClientId,\n clerkOAuthTokenUrl: onDisk.clerkOAuthTokenUrl,\n environment: onDisk.environment,\n };\n await target.writeFull(migrated);\n await verifyMigratedSession(target, migrated);\n } else if (onDisk.accessToken) {\n await target.writeAccessOnly(onDisk.accessToken);\n const migrated = await target.read();\n if (migrated?.accessToken !== onDisk.accessToken) {\n throw new Error(\"Credential migration verification failed.\");\n }\n } else {\n return;\n }\n await fileCredentialBackend.clear();\n } catch (error) {\n if (options.failClosed) {\n throw new CredentialStoreUnavailableError(\n error instanceof Error ? error.message : String(error),\n );\n }\n // Migration is best-effort. A failure here should not block CLI\n // operation; on next run the file backend is still consulted\n // directly because the keychain backend's `read` returns null and\n // callers fall through to \"missing session\" → login prompt.\n }\n}\n\nasync function verifyMigratedSession(\n target: CredentialBackend,\n expected: Credentials,\n): Promise<void> {\n const migrated = await target.read();\n if (\n migrated?.accessToken !== expected.accessToken ||\n migrated.refreshToken !== expected.refreshToken\n ) {\n throw new Error(\"Credential migration verification failed.\");\n }\n}\n\nexport async function getActiveCredentialBackendName(): Promise<CredentialBackendName> {\n const backend = await getCredentialBackend();\n return backend.name;\n}\n\n/** Loose read: returns whatever is on disk, including access-only sessions. */\nexport async function getStoredSession(): Promise<StoredSessionSnapshot | null> {\n if (process.env.DREAMBOARD_AGENT_TOKEN?.trim()) {\n return null;\n }\n const backend = await getCredentialBackend();\n return backend.read();\n}\n\n/** Strict read: returns a refreshable pair, or null if either token is missing. */\nexport async function getCredentials(): Promise<Credentials | null> {\n const snapshot = await getStoredSession();\n if (!snapshot) return null;\n const { accessToken, refreshToken } = snapshot;\n if (!accessToken || !refreshToken) return null;\n return {\n accessToken,\n refreshToken,\n tokenExpiresAt: snapshot.tokenExpiresAt,\n dreamboardApiToken: snapshot.dreamboardApiToken,\n dreamboardApiExpiresAt: snapshot.dreamboardApiExpiresAt,\n clerkOAuthIssuer: snapshot.clerkOAuthIssuer,\n clerkOAuthClientId: snapshot.clerkOAuthClientId,\n clerkOAuthTokenUrl: snapshot.clerkOAuthTokenUrl,\n environment: snapshot.environment,\n };\n}\n\nexport async function setCredentials(creds: Credentials): Promise<void> {\n await withFileLock(getCredentialLockPath(), async () => {\n const backend = await getCredentialBackend();\n await backend.writeFull(creds);\n });\n}\n\nexport async function setAccessOnlySession(accessToken: string): Promise<void> {\n await withFileLock(getCredentialLockPath(), async () => {\n const backend = await getCredentialBackend();\n await backend.writeAccessOnly(accessToken);\n });\n}\n\nexport async function clearCredentials(): Promise<void> {\n await withFileLock(getCredentialLockPath(), async () => {\n const backend = await getCredentialBackend();\n await backend.clear();\n });\n}\n\n/**\n * Run `fn` while holding the cross-process credential lock. `fn` receives\n * an ops handle that reads/writes the active backend without re-acquiring\n * the lock (avoiding deadlock).\n *\n * This is the only correct way to perform a read-modify-write on stored\n * credentials (e.g. CLI refresh rotation) in the presence of\n * concurrent CLI invocations.\n */\nexport async function withCredentialLock<T>(\n fn: (ops: CredentialLockOps) => Promise<T>,\n options?: FileLockOptions,\n): Promise<T> {\n return withFileLock(\n getCredentialLockPath(),\n async () => {\n const backend = await getCredentialBackend();\n const ops: CredentialLockOps = {\n backendName: backend.name,\n read: () => backend.read(),\n writeFull: (creds) => backend.writeFull(creds),\n writeAccessOnly: (accessToken) => backend.writeAccessOnly(accessToken),\n clear: () => backend.clear(),\n };\n return fn(ops);\n },\n options,\n );\n}\n\n/** Test-only reset of module state. Not exported through the barrel. */\nexport function _resetCredentialStoreForTests(): void {\n cachedBackend = null;\n migrationCompleted = false;\n backendResolver = defaultBackendResolver;\n}\n","declare const __DREAMBOARD_BUILD_CHANNEL__: string | undefined;\n\nconst injectedBuildChannel =\n typeof __DREAMBOARD_BUILD_CHANNEL__ === \"string\"\n ? __DREAMBOARD_BUILD_CHANNEL__\n : undefined;\n\nexport const BUILD_CHANNEL =\n injectedBuildChannel === \"published\" ? \"published\" : \"development\";\n\nexport const IS_PUBLISHED_BUILD = BUILD_CHANNEL === \"published\";\nexport const PUBLISHED_ENVIRONMENT = \"prod\" as const;\n"],"mappings":"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","names":["tryKeychainBackend","keychain"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/auth/clerk-oauth.ts","../../src/auth/token-exchange.ts","../../src/auth/user-token-manager.ts","../../src/config/local-harness-auth.ts"],"sourcesContent":["import crypto from \"node:crypto\";\n\nexport type ClerkOAuthConfig = {\n issuer?: string;\n clientId?: string;\n tokenUrl?: string;\n scope?: string;\n};\n\nexport type ClerkOAuthTokenResponse = {\n accessToken: string;\n refreshToken: string;\n expiresAt?: string;\n tokenUrl: string;\n};\n\nexport function createPkcePair(): { verifier: string; challenge: string } {\n const verifier = base64Url(crypto.randomBytes(32));\n const challenge = base64Url(\n crypto.createHash(\"sha256\").update(verifier).digest(),\n );\n return { verifier, challenge };\n}\n\nexport function buildClerkAuthorizationUrl(input: {\n config: ClerkOAuthConfig;\n redirectUri: string;\n state: string;\n codeChallenge: string;\n}): URL {\n const { issuer, clientId, scope } = assertConfigured(input.config);\n const url = new URL(\"/oauth/authorize\", issuer);\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"client_id\", clientId);\n url.searchParams.set(\"redirect_uri\", input.redirectUri);\n url.searchParams.set(\"state\", input.state);\n url.searchParams.set(\"code_challenge\", input.codeChallenge);\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n url.searchParams.set(\"scope\", scope ?? \"openid profile email offline_access\");\n return url;\n}\n\nexport async function exchangeClerkOAuthCode(input: {\n config: ClerkOAuthConfig;\n code: string;\n redirectUri: string;\n codeVerifier: string;\n}): Promise<ClerkOAuthTokenResponse> {\n const { clientId, tokenUrl } = assertConfigured(input.config);\n const body = new URLSearchParams({\n grant_type: \"authorization_code\",\n client_id: clientId,\n code: input.code,\n redirect_uri: input.redirectUri,\n code_verifier: input.codeVerifier,\n });\n return requestClerkToken(tokenUrl, body);\n}\n\nexport async function refreshClerkOAuthToken(input: {\n config: ClerkOAuthConfig;\n refreshToken: string;\n}): Promise<ClerkOAuthTokenResponse> {\n const { clientId, tokenUrl } = assertConfigured(input.config);\n const body = new URLSearchParams({\n grant_type: \"refresh_token\",\n client_id: clientId,\n refresh_token: input.refreshToken,\n });\n return requestClerkToken(tokenUrl, body);\n}\n\nfunction assertConfigured(config: ClerkOAuthConfig): {\n issuer: string;\n clientId: string;\n tokenUrl: string;\n scope?: string;\n} {\n const issuer = config.issuer?.trim().replace(/\\/$/, \"\");\n const clientId = config.clientId?.trim();\n if (!issuer || !clientId) {\n throw new Error(\n [\n \"Clerk OAuth CLI is not configured for this environment.\",\n \"The CLI expects first-party environments to be configured in its built-in registry.\",\n \"If this environment has no registered public Clerk OAuth client, create one and release a CLI with its client id.\",\n \"For emergency overrides, set the environment-specific DREAMBOARD_<ENV>_CLERK_OAUTH_* variables or DREAMBOARD_CLERK_OAUTH_*.\",\n \"For local harness auth, use `pnpm auth:local` or the auto-bootstrapped local harness flows instead.\",\n ].join(\" \"),\n );\n }\n return {\n issuer,\n clientId,\n tokenUrl:\n config.tokenUrl?.trim() || new URL(\"/oauth/token\", issuer).toString(),\n scope: config.scope?.trim() || undefined,\n };\n}\n\nasync function requestClerkToken(\n tokenUrl: string,\n body: URLSearchParams,\n): Promise<ClerkOAuthTokenResponse> {\n const response = await fetch(tokenUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n },\n body,\n });\n if (!response.ok) {\n const detail = await response.text();\n throw new Error(\n `Clerk OAuth token request failed (${response.status}): ${detail}`,\n );\n }\n const payload = (await response.json()) as {\n access_token?: unknown;\n refresh_token?: unknown;\n expires_in?: unknown;\n };\n if (typeof payload.access_token !== \"string\") {\n throw new Error(\"Clerk OAuth token response did not include access_token.\");\n }\n if (typeof payload.refresh_token !== \"string\") {\n throw new Error(\n \"Clerk OAuth token response did not include refresh_token.\",\n );\n }\n const expiresAt =\n typeof payload.expires_in === \"number\"\n ? new Date(Date.now() + payload.expires_in * 1000).toISOString()\n : undefined;\n return {\n accessToken: payload.access_token,\n refreshToken: payload.refresh_token,\n expiresAt,\n tokenUrl,\n };\n}\n\nfunction base64Url(bytes: Buffer): string {\n return bytes\n .toString(\"base64\")\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/g, \"\");\n}\n","export type DreamboardTokenAudience = \"dreamboard-api\" | \"dreamboard-git\";\n\nexport type DreamboardTokenResponse = {\n accessToken: string;\n tokenType: \"Bearer\";\n audience: DreamboardTokenAudience;\n expiresIn?: number;\n expiresAt?: string;\n};\n\nexport async function exchangeDreamboardUserToken(input: {\n apiBaseUrl: string;\n clerkAccessToken: string;\n audience: DreamboardTokenAudience;\n fetchImpl?: typeof fetch;\n}): Promise<DreamboardTokenResponse> {\n const fetchImpl = input.fetchImpl ?? globalThis.fetch.bind(globalThis);\n const response = await fetchImpl(\n new URL(\"/api/auth/token-exchange\", input.apiBaseUrl),\n {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${input.clerkAccessToken}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: JSON.stringify({ audience: input.audience }),\n },\n );\n\n if (!response.ok) {\n throw new Error(\n `Dreamboard token exchange failed (${response.status}). Run \\`dreamboard login\\` to authenticate again.`,\n );\n }\n\n const payload = (await response.json()) as {\n accessToken?: unknown;\n tokenType?: unknown;\n audience?: unknown;\n expiresIn?: unknown;\n };\n\n if (typeof payload.accessToken !== \"string\" || payload.accessToken === \"\") {\n throw new Error(\"Dreamboard token exchange response omitted accessToken.\");\n }\n if (payload.tokenType !== \"Bearer\") {\n throw new Error(\n \"Dreamboard token exchange response had invalid tokenType.\",\n );\n }\n if (payload.audience !== input.audience) {\n throw new Error(\"Dreamboard token exchange response had wrong audience.\");\n }\n\n const expiresIn =\n typeof payload.expiresIn === \"number\" && Number.isFinite(payload.expiresIn)\n ? payload.expiresIn\n : undefined;\n\n return {\n accessToken: payload.accessToken,\n tokenType: \"Bearer\",\n audience: input.audience,\n expiresIn,\n expiresAt:\n expiresIn === undefined\n ? undefined\n : new Date(Date.now() + expiresIn * 1000).toISOString(),\n };\n}\n","import type {\n Credentials,\n StoredSessionSnapshot,\n} from \"../config/credential-store.js\";\nimport { withCredentialLock } from \"../config/credential-store.js\";\nimport { refreshClerkOAuthToken } from \"./clerk-oauth.js\";\nimport {\n exchangeDreamboardUserToken,\n type DreamboardTokenAudience,\n} from \"./token-exchange.js\";\nimport type { ResolvedConfig } from \"../types.js\";\n\nconst TOKEN_REFRESH_WINDOW_MS = 60 * 1000;\n\nexport type AccessToken = {\n readonly token: string;\n readonly expiresAt?: string;\n readonly audience: DreamboardTokenAudience;\n};\n\nexport interface UserTokenManager {\n resolveApiToken(): Promise<AccessToken | null>;\n resolveGitToken(): Promise<AccessToken>;\n}\n\nexport function createUserTokenManager(\n config: ResolvedConfig,\n): UserTokenManager {\n return {\n async resolveApiToken() {\n const localOrInjected = resolveNonStoredToken(config, \"dreamboard-api\");\n if (localOrInjected) return localOrInjected;\n\n if (!usesStoredSession(config)) return null;\n\n return withCredentialLock(async (ops) => {\n const stored = await ops.read();\n const apiToken = freshStoredApiToken(stored);\n if (apiToken) return apiToken;\n\n const clerk = await resolveFreshClerkAccessToken(config, stored);\n const exchanged = await exchangeDreamboardUserToken({\n apiBaseUrl: config.apiBaseUrl,\n clerkAccessToken: clerk.accessToken,\n audience: \"dreamboard-api\",\n });\n\n await ops.writeFull({\n ...clerk,\n dreamboardApiToken: exchanged.accessToken,\n dreamboardApiExpiresAt: exchanged.expiresAt,\n });\n\n return {\n token: exchanged.accessToken,\n expiresAt: exchanged.expiresAt,\n audience: \"dreamboard-api\",\n };\n });\n },\n\n async resolveGitToken() {\n const localOrInjected = resolveNonStoredToken(config, \"dreamboard-git\");\n if (localOrInjected) return localOrInjected;\n\n if (!usesStoredSession(config)) {\n throw new Error(\n \"Missing Dreamboard session. Run `dreamboard login` to authenticate.\",\n );\n }\n\n return withCredentialLock(async (ops) => {\n const stored = await ops.read();\n const clerk = await resolveFreshClerkAccessToken(config, stored);\n const exchanged = await exchangeDreamboardUserToken({\n apiBaseUrl: config.apiBaseUrl,\n clerkAccessToken: clerk.accessToken,\n audience: \"dreamboard-git\",\n });\n\n await ops.writeFull(clerk);\n\n return {\n token: exchanged.accessToken,\n expiresAt: exchanged.expiresAt,\n audience: \"dreamboard-git\",\n };\n });\n },\n };\n}\n\nfunction resolveNonStoredToken(\n config: ResolvedConfig,\n audience: DreamboardTokenAudience,\n): AccessToken | null {\n if (usesStoredSession(config)) return null;\n if (!config.authToken) return null;\n return {\n token: config.authToken,\n expiresAt: config.tokenExpiresAt,\n audience,\n };\n}\n\nfunction freshStoredApiToken(\n stored: StoredSessionSnapshot | null,\n): AccessToken | null {\n if (!stored?.dreamboardApiToken) return null;\n if (isFresh(stored.dreamboardApiExpiresAt, stored.dreamboardApiToken)) {\n return {\n token: stored.dreamboardApiToken,\n expiresAt: stored.dreamboardApiExpiresAt,\n audience: \"dreamboard-api\",\n };\n }\n return null;\n}\n\nasync function resolveFreshClerkAccessToken(\n config: ResolvedConfig,\n stored: StoredSessionSnapshot | null,\n): Promise<Credentials> {\n const accessToken = stored?.accessToken ?? config.clerkAccessToken;\n const refreshToken = stored?.refreshToken ?? config.refreshToken;\n const tokenExpiresAt = stored?.tokenExpiresAt ?? config.clerkAccessExpiresAt;\n\n if (!refreshToken) {\n throw new Error(\n \"Stored Dreamboard session is missing its refresh token. Run `dreamboard login` to authenticate again.\",\n );\n }\n\n if (accessToken && isFresh(tokenExpiresAt, accessToken)) {\n return {\n accessToken,\n refreshToken,\n tokenExpiresAt,\n dreamboardApiToken: stored?.dreamboardApiToken,\n dreamboardApiExpiresAt: stored?.dreamboardApiExpiresAt,\n clerkOAuthIssuer: stored?.clerkOAuthIssuer ?? config.clerkOAuthIssuer,\n clerkOAuthClientId:\n stored?.clerkOAuthClientId ?? config.clerkOAuthClientId,\n clerkOAuthTokenUrl:\n stored?.clerkOAuthTokenUrl ?? config.clerkOAuthTokenUrl,\n environment: stored?.environment ?? config.environment,\n };\n }\n\n const payload = await refreshClerkOAuthToken({\n config: {\n issuer: stored?.clerkOAuthIssuer ?? config.clerkOAuthIssuer,\n clientId: stored?.clerkOAuthClientId ?? config.clerkOAuthClientId,\n tokenUrl: stored?.clerkOAuthTokenUrl ?? config.clerkOAuthTokenUrl,\n },\n refreshToken,\n });\n\n return {\n accessToken: payload.accessToken,\n refreshToken: payload.refreshToken,\n tokenExpiresAt: payload.expiresAt,\n clerkOAuthIssuer: stored?.clerkOAuthIssuer ?? config.clerkOAuthIssuer,\n clerkOAuthClientId: stored?.clerkOAuthClientId ?? config.clerkOAuthClientId,\n clerkOAuthTokenUrl: payload.tokenUrl,\n environment: stored?.environment ?? config.environment,\n };\n}\n\nfunction isFresh(expiresAt: string | undefined, token: string): boolean {\n const expiry = expiresAt ? new Date(expiresAt) : getJwtExpiry(token);\n return (\n expiry !== null &&\n Number.isFinite(expiry.getTime()) &&\n expiry.getTime() > Date.now() + TOKEN_REFRESH_WINDOW_MS\n );\n}\n\nfunction getJwtExpiry(accessToken: string | undefined): Date | null {\n if (!accessToken) return null;\n const parts = accessToken.split(\".\");\n if (parts.length !== 3) return null;\n try {\n const payload = JSON.parse(\n Buffer.from(parts[1]!, \"base64url\").toString(\"utf8\"),\n ) as { exp?: unknown };\n if (typeof payload.exp !== \"number\" || !Number.isFinite(payload.exp)) {\n return null;\n }\n return new Date(payload.exp * 1000);\n } catch {\n return null;\n }\n}\n\nfunction usesStoredSession(config: ResolvedConfig): boolean {\n return config.refreshTokenSource === \"global\";\n}\n","import { createHmac, randomUUID } from \"node:crypto\";\nimport type { ResolvedConfig } from \"../types.js\";\nimport { IS_PUBLISHED_BUILD } from \"../build-target.js\";\n\ntype LocalHarnessProfile = \"local\" | \"local-aws\";\n\nconst DEFAULT_SUBJECT = \"harness-smoke-local@dreamboard.local\";\nconst DEFAULT_ISSUER = \"dreamboard-local-harness\";\nconst DEFAULT_SECRET = \"dreamboard-local-harness-token-secret\";\nconst LOCAL_AWS_ISSUER = \"dreamboard-local-aws-harness\";\nconst LOCAL_AWS_SECRET = \"dreamboard-local-aws-harness-token-secret\";\nconst DEFAULT_TTL_SECONDS = 8 * 60 * 60;\n\nconst mintedTokens = new Map<string, string>();\n\nexport function resolveLocalHarnessAccessToken(\n config: ResolvedConfig,\n): string | undefined {\n if (IS_PUBLISHED_BUILD || config.environment !== \"local\") {\n return undefined;\n }\n\n const profile = inferLocalHarnessProfile(config);\n if (\n config.authToken &&\n (profile !== \"local-aws\" || isExplicitTokenSource(config.authTokenSource))\n ) {\n return undefined;\n }\n\n const cacheKey = [\n profile,\n process.env.LOCAL_HARNESS_TOKEN_ISSUER ?? \"\",\n process.env.LOCAL_HARNESS_TOKEN_SECRET ?? \"\",\n process.env.LOCAL_HARNESS_SUBJECT ?? \"\",\n process.env.HARNESS_USER_EMAIL ?? \"\",\n process.env.LOCAL_HARNESS_EMAIL ?? \"\",\n process.env.LOCAL_HARNESS_TOKEN_TTL_SECONDS ?? \"\",\n ].join(\"\\0\");\n const cached = mintedTokens.get(cacheKey);\n if (cached) return cached;\n\n const token = mintLocalHarnessToken(profile);\n mintedTokens.set(cacheKey, token);\n return token;\n}\n\nfunction isExplicitTokenSource(\n source: ResolvedConfig[\"authTokenSource\"],\n): boolean {\n return source === \"flag\" || source === \"env\" || source === \"agent-env\";\n}\n\nexport function inferLocalHarnessProfile(\n config: Pick<ResolvedConfig, \"apiBaseUrl\" | \"webBaseUrl\">,\n): LocalHarnessProfile {\n return isLocalAwsUrl(config.apiBaseUrl) || isLocalAwsUrl(config.webBaseUrl)\n ? \"local-aws\"\n : \"local\";\n}\n\nfunction mintLocalHarnessToken(profile: LocalHarnessProfile): string {\n const subject =\n envValue(process.env.LOCAL_HARNESS_SUBJECT) ??\n envValue(process.env.HARNESS_USER_EMAIL) ??\n DEFAULT_SUBJECT;\n const email =\n envValue(process.env.LOCAL_HARNESS_EMAIL) ??\n (subject.includes(\"@\") ? subject : undefined);\n const issuer =\n envValue(process.env.LOCAL_HARNESS_TOKEN_ISSUER) ??\n (profile === \"local-aws\" ? LOCAL_AWS_ISSUER : DEFAULT_ISSUER);\n const secret =\n envValue(process.env.LOCAL_HARNESS_TOKEN_SECRET) ??\n (profile === \"local-aws\" ? LOCAL_AWS_SECRET : DEFAULT_SECRET);\n const ttlSeconds = Number(\n envValue(process.env.LOCAL_HARNESS_TOKEN_TTL_SECONDS) ??\n String(DEFAULT_TTL_SECONDS),\n );\n if (!Number.isFinite(ttlSeconds) || ttlSeconds <= 0) {\n throw new Error(\n \"LOCAL_HARNESS_TOKEN_TTL_SECONDS must be a positive number.\",\n );\n }\n\n const now = Math.floor(Date.now() / 1000);\n const payload = {\n typ: \"local_harness_access\",\n dreamboard_provider: \"local-harness\",\n dreamboard_provider_subject: subject,\n ...(email ? { email } : {}),\n iss: issuer,\n sub: subject,\n iat: now,\n exp: now + Math.floor(ttlSeconds),\n jti: randomUUID(),\n };\n\n const headerPart = base64UrlJson({ alg: \"HS256\", typ: \"JWT\" });\n const payloadPart = base64UrlJson(payload);\n const signature = createHmac(\"sha256\", secret)\n .update(`${headerPart}.${payloadPart}`)\n .digest(\"base64url\");\n return `${headerPart}.${payloadPart}.${signature}`;\n}\n\nfunction base64UrlJson(value: unknown): string {\n return Buffer.from(JSON.stringify(value), \"utf8\").toString(\"base64url\");\n}\n\nfunction envValue(raw: string | undefined): string | undefined {\n return typeof raw === \"string\" && raw.trim().length > 0\n ? raw.trim()\n : undefined;\n}\n\nfunction isLocalAwsUrl(rawUrl: string | undefined): boolean {\n if (!rawUrl) return false;\n try {\n const url = new URL(rawUrl);\n return (\n (url.hostname === \"localhost\" || url.hostname === \"127.0.0.1\") &&\n (url.port === \"18080\" || url.port === \"8088\")\n );\n } catch {\n return false;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/services/project/workspace-codegen.ts"],"sourcesContent":["import type { GameTopologyManifest } from \"@dreamboard-games/sdk/types\";\nimport { loadProjectAuthoringAdapter } from \"../project-authoring/loader.js\";\nimport { validateGeneratedArtifacts } from \"../project-authoring/validation.js\";\nimport {\n readWorkspaceTextFileIfExists,\n writeWorkspaceTextFile,\n} from \"./workspace-path.js\";\n\nexport interface WorkspaceCodegenWriteResult {\n written: string[];\n skipped: string[];\n merged: string[];\n}\n\nconst STARTER_UI_SEED_FILES = new Set([\n \"ui/interaction-routes.tsx\",\n \"ui/setup-screen.tsx\",\n \"ui/styles.ts\",\n \"ui/ui-contract-typing-smoke.tsx\",\n]);\nconst SETUP_PROFILES_SEED_MARKER = \"Dreamboard generated setup profile seeds.\";\n\nfunction isFrameworkOwnedSetupProfilesSeed(\n content: string | null | undefined,\n): boolean {\n if (content === null || content === undefined) {\n return false;\n }\n const trimmed = content.trim();\n return trimmed.length === 0 || trimmed.includes(SETUP_PROFILES_SEED_MARKER);\n}\n\nexport async function applyWorkspaceCodegen(options: {\n projectRoot: string;\n manifest: GameTopologyManifest;\n}): Promise<WorkspaceCodegenWriteResult> {\n const { projectRoot, manifest } = options;\n const { adapter } = await loadProjectAuthoringAdapter(projectRoot);\n const artifacts = validateGeneratedArtifacts([\n ...adapter.generateWorkspaceArtifacts(manifest),\n ...adapter.generateTestArtifacts({ manifest }),\n ]);\n const authoritativeFiles = new Map(\n artifacts\n .filter((artifact) => artifact.ownership !== \"seed\")\n .map((artifact) => [artifact.path, artifact.content]),\n );\n const seedFiles = new Map(\n artifacts\n .filter((artifact) => artifact.ownership === \"seed\")\n .map((artifact) => [artifact.path, artifact.content]),\n );\n\n const written: string[] = [];\n const skipped: string[] = [];\n const merged: string[] = [];\n const existingUiAppBeforeSeeds = await readWorkspaceTextFileIfExists(\n projectRoot,\n \"ui/App.tsx\",\n );\n const shouldWriteStarterUiSeedFiles =\n existingUiAppBeforeSeeds === null ||\n existingUiAppBeforeSeeds.trim().length === 0;\n\n for (const [relativePath, content] of authoritativeFiles) {\n const existingContent = await readWorkspaceTextFileIfExists(\n projectRoot,\n relativePath,\n );\n await writeWorkspaceTextFile(projectRoot, relativePath, content);\n if (existingContent !== content) {\n written.push(relativePath);\n }\n }\n\n for (const [relativePath, content] of seedFiles) {\n const existingContent = await readWorkspaceTextFileIfExists(\n projectRoot,\n relativePath,\n );\n if (\n STARTER_UI_SEED_FILES.has(relativePath) &&\n !shouldWriteStarterUiSeedFiles &&\n existingContent === null\n ) {\n skipped.push(relativePath);\n continue;\n }\n\n const shouldRefreshFrameworkSeed =\n relativePath === \"app/setup-profiles.ts\" &&\n isFrameworkOwnedSetupProfilesSeed(existingContent);\n\n if (shouldRefreshFrameworkSeed) {\n await writeWorkspaceTextFile(projectRoot, relativePath, content);\n if (existingContent !== content) {\n written.push(relativePath);\n }\n continue;\n }\n\n const hasExistingContent =\n existingContent !== null && existingContent.trim().length > 0;\n if (hasExistingContent) {\n skipped.push(relativePath);\n continue;\n }\n\n await writeWorkspaceTextFile(projectRoot, relativePath, content);\n written.push(relativePath);\n }\n\n written.sort();\n skipped.sort();\n merged.sort();\n return { written, skipped, merged };\n}\n"],"mappings":";;;;;;;;;AAcA,IAAM,wBAAwB,oBAAI,IAAI;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AACD,IAAM,6BAA6B;AAEnC,SAAS,kCACP,SACS;AACT,MAAI,YAAY,QAAQ,YAAY,QAAW;AAC7C,WAAO;AAAA,EACT;AACA,QAAM,UAAU,QAAQ,KAAK;AAC7B,SAAO,QAAQ,WAAW,KAAK,QAAQ,SAAS,0BAA0B;AAC5E;AAEA,eAAsB,sBAAsB,SAGH;AACvC,QAAM,EAAE,aAAa,SAAS,IAAI;AAClC,QAAM,EAAE,QAAQ,IAAI,MAAM,4BAA4B,WAAW;AACjE,QAAM,YAAY,2BAA2B;AAAA,IAC3C,GAAG,QAAQ,2BAA2B,QAAQ;AAAA,IAC9C,GAAG,QAAQ,sBAAsB,EAAE,SAAS,CAAC;AAAA,EAC/C,CAAC;AACD,QAAM,qBAAqB,IAAI;AAAA,IAC7B,UACG,OAAO,CAAC,aAAa,SAAS,cAAc,MAAM,EAClD,IAAI,CAAC,aAAa,CAAC,SAAS,MAAM,SAAS,OAAO,CAAC;AAAA,EACxD;AACA,QAAM,YAAY,IAAI;AAAA,IACpB,UACG,OAAO,CAAC,aAAa,SAAS,cAAc,MAAM,EAClD,IAAI,CAAC,aAAa,CAAC,SAAS,MAAM,SAAS,OAAO,CAAC;AAAA,EACxD;AAEA,QAAM,UAAoB,CAAC;AAC3B,QAAM,UAAoB,CAAC;AAC3B,QAAM,SAAmB,CAAC;AAC1B,QAAM,2BAA2B,MAAM;AAAA,IACrC;AAAA,IACA;AAAA,EACF;AACA,QAAM,gCACJ,6BAA6B,QAC7B,yBAAyB,KAAK,EAAE,WAAW;AAE7C,aAAW,CAAC,cAAc,OAAO,KAAK,oBAAoB;AACxD,UAAM,kBAAkB,MAAM;AAAA,MAC5B;AAAA,MACA;AAAA,IACF;AACA,UAAM,uBAAuB,aAAa,cAAc,OAAO;AAC/D,QAAI,oBAAoB,SAAS;AAC/B,cAAQ,KAAK,YAAY;AAAA,IAC3B;AAAA,EACF;AAEA,aAAW,CAAC,cAAc,OAAO,KAAK,WAAW;AAC/C,UAAM,kBAAkB,MAAM;AAAA,MAC5B;AAAA,MACA;AAAA,IACF;AACA,QACE,sBAAsB,IAAI,YAAY,KACtC,CAAC,iCACD,oBAAoB,MACpB;AACA,cAAQ,KAAK,YAAY;AACzB;AAAA,IACF;AAEA,UAAM,6BACJ,iBAAiB,2BACjB,kCAAkC,eAAe;AAEnD,QAAI,4BAA4B;AAC9B,YAAM,uBAAuB,aAAa,cAAc,OAAO;AAC/D,UAAI,oBAAoB,SAAS;AAC/B,gBAAQ,KAAK,YAAY;AAAA,MAC3B;AACA;AAAA,IACF;AAEA,UAAM,qBACJ,oBAAoB,QAAQ,gBAAgB,KAAK,EAAE,SAAS;AAC9D,QAAI,oBAAoB;AACtB,cAAQ,KAAK,YAAY;AACzB;AAAA,IACF;AAEA,UAAM,uBAAuB,aAAa,cAAc,OAAO;AAC/D,YAAQ,KAAK,YAAY;AAAA,EAC3B;AAEA,UAAQ,KAAK;AACb,UAAQ,KAAK;AACb,SAAO,KAAK;AACZ,SAAO,EAAE,SAAS,SAAS,OAAO;AACpC;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/services/project-authoring/validation.ts","../../src/services/project-authoring/contract.ts","../../src/services/project-authoring/loader.ts","../../src/services/project/workspace-path.ts"],"sourcesContent":["import path from \"node:path\";\nimport { createHash } from \"node:crypto\";\nimport {\n ProjectAuthoringError,\n type GeneratedArtifactV1,\n type ProjectAuthoringAdapterV1,\n} from \"./contract.js\";\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction isValidGeneratedPath(relativePath: string): boolean {\n if (\n relativePath.length === 0 ||\n relativePath.startsWith(\"/\") ||\n relativePath.includes(\"\\\\\")\n ) {\n return false;\n }\n const normalized = path.posix.normalize(relativePath);\n if (normalized !== relativePath) {\n return false;\n }\n return !relativePath\n .split(\"/\")\n .some((segment) => segment.length === 0 || segment === \".\" || segment === \"..\");\n}\n\nfunction assertGeneratedPath(pathValue: unknown, label: string): string {\n if (typeof pathValue !== \"string\" || !isValidGeneratedPath(pathValue)) {\n throw new ProjectAuthoringError(\n \"GENERATED_PATH_CONTRACT_INVALID\",\n `${label} must be a normalized relative workspace path.`,\n );\n }\n return pathValue;\n}\n\nfunction assertGeneratedArtifact(value: unknown): GeneratedArtifactV1 {\n if (!isRecord(value)) {\n throw new ProjectAuthoringError(\n \"GENERATED_PATH_CONTRACT_INVALID\",\n \"Generated artifact must be an object.\",\n );\n }\n const artifactPath = assertGeneratedPath(value.path, \"Generated artifact path\");\n if (\n value.ownership !== \"authoritative\" &&\n value.ownership !== \"seed\" &&\n value.ownership !== \"derived-test\"\n ) {\n throw new ProjectAuthoringError(\n \"GENERATED_PATH_CONTRACT_INVALID\",\n `Generated artifact '${artifactPath}' has invalid ownership.`,\n );\n }\n if (typeof value.content !== \"string\") {\n throw new ProjectAuthoringError(\n \"GENERATED_PATH_CONTRACT_INVALID\",\n `Generated artifact '${artifactPath}' content must be a string.`,\n );\n }\n const expectedHash = createHash(\"sha256\")\n .update(value.content)\n .digest(\"hex\");\n if (value.contentSha256 !== expectedHash) {\n throw new ProjectAuthoringError(\n \"GENERATED_PATH_CONTRACT_INVALID\",\n `Generated artifact '${artifactPath}' has a stale content hash.`,\n );\n }\n return value as GeneratedArtifactV1;\n}\n\nexport function validateProjectAuthoringAdapter(\n adapter: unknown,\n): ProjectAuthoringAdapterV1 {\n if (!isRecord(adapter)) {\n throw new ProjectAuthoringError(\n \"AUTHORING_PROTOCOL_UNSUPPORTED\",\n \"SDK authoring export did not provide an adapter object.\",\n );\n }\n if (adapter.protocolVersion !== 1) {\n throw new ProjectAuthoringError(\n \"AUTHORING_PROTOCOL_UNSUPPORTED\",\n `Unsupported SDK authoring protocol '${String(adapter.protocolVersion)}'.`,\n );\n }\n if (!isRecord(adapter.metadata)) {\n throw new ProjectAuthoringError(\n \"AUTHORING_PROTOCOL_UNSUPPORTED\",\n \"SDK authoring adapter metadata is missing.\",\n );\n }\n for (const key of [\n \"sdkVersion\",\n \"codegenVersion\",\n \"manifestSchemaVersion\",\n \"generatedArtifactSchemaVersion\",\n ] as const) {\n if (\n adapter.metadata[key] === undefined ||\n (key.endsWith(\"Version\") && typeof adapter.metadata[key] !== \"string\" && typeof adapter.metadata[key] !== \"number\")\n ) {\n throw new ProjectAuthoringError(\n \"AUTHORING_PROTOCOL_UNSUPPORTED\",\n `SDK authoring adapter metadata '${key}' is missing.`,\n );\n }\n }\n for (const method of [\n \"validateManifest\",\n \"materializeManifest\",\n \"generateWorkspaceArtifacts\",\n \"generateTestArtifacts\",\n ] as const) {\n if (typeof adapter[method] !== \"function\") {\n throw new ProjectAuthoringError(\n \"AUTHORING_PROTOCOL_UNSUPPORTED\",\n `SDK authoring adapter method '${method}' is missing.`,\n );\n }\n }\n if (!Array.isArray(adapter.generatedPaths)) {\n throw new ProjectAuthoringError(\n \"GENERATED_PATH_CONTRACT_INVALID\",\n \"SDK authoring adapter generatedPaths must be an array.\",\n );\n }\n const seen = new Set<string>();\n for (const [index, generatedPath] of adapter.generatedPaths.entries()) {\n const normalized = assertGeneratedPath(\n generatedPath,\n `generatedPaths[${index}]`,\n );\n if (seen.has(normalized)) {\n throw new ProjectAuthoringError(\n \"GENERATED_PATH_CONTRACT_INVALID\",\n `Generated path '${normalized}' is declared more than once.`,\n );\n }\n seen.add(normalized);\n }\n return adapter as ProjectAuthoringAdapterV1;\n}\n\nexport function validateGeneratedArtifacts(\n artifacts: readonly unknown[],\n): readonly GeneratedArtifactV1[] {\n const seen = new Set<string>();\n const validated = artifacts.map(assertGeneratedArtifact);\n for (const artifact of validated) {\n if (seen.has(artifact.path)) {\n throw new ProjectAuthoringError(\n \"GENERATED_PATH_CONTRACT_INVALID\",\n `Generated artifact path '${artifact.path}' was emitted more than once.`,\n );\n }\n seen.add(artifact.path);\n }\n return validated;\n}\n","export type ProjectAuthoringProblemCode =\n | \"SDK_NOT_INSTALLED\"\n | \"AUTHORING_ADAPTER_NOT_EXPORTED\"\n | \"AUTHORING_PROTOCOL_UNSUPPORTED\"\n | \"SDK_METADATA_MISMATCH\"\n | \"GENERATED_PATH_CONTRACT_INVALID\";\n\nexport class ProjectAuthoringError extends Error {\n readonly code: ProjectAuthoringProblemCode;\n\n constructor(code: ProjectAuthoringProblemCode, message: string) {\n super(message);\n this.name = \"ProjectAuthoringError\";\n this.code = code;\n }\n}\n\nexport type GeneratedAuthoringMetadataV1 = {\n sdkVersion: string;\n codegenVersion: string;\n manifestSchemaVersion: number;\n generatedArtifactSchemaVersion: number;\n};\n\nexport type AuthoringValidationResultV1 = {\n valid: boolean;\n errors: readonly string[];\n warnings: readonly string[];\n};\n\nexport type GeneratedArtifactV1 = {\n path: string;\n ownership: \"authoritative\" | \"seed\" | \"derived-test\";\n content: string;\n contentSha256: string;\n};\n\nexport type GeneratedPathPatternV1 = {\n prefix: string;\n suffix: string;\n};\n\nexport type AuthoringManifestConformanceCaseV1 = {\n id: string;\n manifest: unknown;\n expected:\n | {\n valid: true;\n transportValid: true;\n materializedSha256: string;\n }\n | {\n valid: false;\n transportValid: boolean;\n diagnosticCodes: readonly string[];\n };\n};\n\nexport type ProjectAuthoringAdapterV1 = {\n protocolVersion: 1;\n metadata: GeneratedAuthoringMetadataV1;\n generatedPaths: readonly string[];\n generatedPathPatterns?: readonly GeneratedPathPatternV1[];\n manifestConformanceCases: readonly AuthoringManifestConformanceCaseV1[];\n validateManifest(manifest: unknown): AuthoringValidationResultV1;\n materializeManifest(manifest: unknown): unknown;\n generateWorkspaceArtifacts(manifest: unknown): readonly GeneratedArtifactV1[];\n generateTestArtifacts(input: {\n manifest: unknown;\n }): readonly GeneratedArtifactV1[];\n};\n\nexport type LoadedProjectAuthoringAdapterV1 = {\n packageRoot: string;\n packageVersion: string;\n adapterPath: string;\n adapter: ProjectAuthoringAdapterV1;\n};\n","import { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { createRequire } from \"node:module\";\nimport { setTimeout as delay } from \"node:timers/promises\";\nimport { pathToFileURL } from \"node:url\";\nimport {\n ProjectAuthoringError,\n type LoadedProjectAuthoringAdapterV1,\n} from \"./contract.js\";\nimport { validateProjectAuthoringAdapter } from \"./validation.js\";\n\ntype PackageJson = {\n name?: string;\n version?: string;\n exports?: unknown;\n};\n\ntype ResolvedProjectAuthoringAdapter = {\n packageJsonPath: string;\n adapterPath: string;\n};\n\nconst PROJECT_SDK_RESOLUTION_RETRY_DELAYS_MS = [50, 150, 300] as const;\n\nfunction problemFromResolveError(error: unknown): ProjectAuthoringError {\n if (error instanceof ProjectAuthoringError) {\n return error;\n }\n const code =\n (error as NodeJS.ErrnoException | undefined)?.code ===\n \"ERR_PACKAGE_PATH_NOT_EXPORTED\"\n ? \"AUTHORING_ADAPTER_NOT_EXPORTED\"\n : \"SDK_NOT_INSTALLED\";\n return new ProjectAuthoringError(\n code,\n code === \"AUTHORING_ADAPTER_NOT_EXPORTED\"\n ? \"Installed @dreamboard-games/sdk does not export @dreamboard-games/sdk/authoring.\"\n : \"Install @dreamboard-games/sdk in this workspace before running authoring commands.\",\n );\n}\n\nfunction assertResolvedInsidePackage(options: {\n packageRoot: string;\n resolvedPath: string;\n label: string;\n}): void {\n const packageRoot = path.resolve(options.packageRoot);\n const resolvedPath = path.resolve(options.resolvedPath);\n const relative = path.relative(packageRoot, resolvedPath);\n if (relative.startsWith(\"..\") || path.isAbsolute(relative)) {\n throw new ProjectAuthoringError(\n \"AUTHORING_ADAPTER_NOT_EXPORTED\",\n `${options.label} resolved outside the installed @dreamboard-games/sdk package.`,\n );\n }\n}\n\nfunction isAuthoringMetadataVersionCompatible(options: {\n metadataVersion: string;\n packageVersion: string;\n}): boolean {\n if (options.metadataVersion === options.packageVersion) {\n return true;\n }\n return options.packageVersion.startsWith(`${options.metadataVersion}-local.`);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction resolveExportTarget(value: unknown): string | null {\n if (typeof value === \"string\") {\n return value;\n }\n if (!isRecord(value)) {\n return null;\n }\n for (const condition of [\"import\", \"default\"]) {\n const resolved = resolveExportTarget(value[condition]);\n if (resolved) {\n return resolved;\n }\n }\n return null;\n}\n\nasync function resolveDirectProjectAuthoringAdapter(\n projectRoot: string,\n): Promise<ResolvedProjectAuthoringAdapter | null> {\n const packageRoot = path.join(\n projectRoot,\n \"node_modules\",\n \"@dreamboard-games\",\n \"sdk\",\n );\n const packageJsonPath = path.join(packageRoot, \"package.json\");\n let packageJson: PackageJson;\n try {\n packageJson = JSON.parse(await readFile(packageJsonPath, \"utf8\")) as PackageJson;\n } catch {\n return null;\n }\n const authoringExport = isRecord(packageJson.exports)\n ? resolveExportTarget(packageJson.exports[\"./authoring\"])\n : null;\n if (!authoringExport) {\n throw new ProjectAuthoringError(\n \"AUTHORING_ADAPTER_NOT_EXPORTED\",\n \"Installed @dreamboard-games/sdk does not export @dreamboard-games/sdk/authoring.\",\n );\n }\n const adapterPath = path.resolve(packageRoot, authoringExport);\n assertResolvedInsidePackage({\n packageRoot,\n resolvedPath: adapterPath,\n label: \"@dreamboard-games/sdk/authoring\",\n });\n return { packageJsonPath, adapterPath };\n}\n\nasync function resolveProjectAuthoringAdapter(\n projectRoot: string,\n): Promise<ResolvedProjectAuthoringAdapter> {\n const requireFromProject = createRequire(path.join(projectRoot, \"package.json\"));\n let lastError: unknown;\n\n for (\n let attempt = 0;\n attempt <= PROJECT_SDK_RESOLUTION_RETRY_DELAYS_MS.length;\n attempt += 1\n ) {\n try {\n const packageJsonPath = requireFromProject.resolve(\n \"@dreamboard-games/sdk/package.json\",\n );\n const adapterPath = requireFromProject.resolve(\n \"@dreamboard-games/sdk/authoring\",\n );\n return { packageJsonPath, adapterPath };\n } catch (error) {\n lastError = error;\n if (\n (error as NodeJS.ErrnoException | undefined)?.code ===\n \"ERR_PACKAGE_PATH_NOT_EXPORTED\"\n ) {\n throw error;\n }\n const directResolved =\n await resolveDirectProjectAuthoringAdapter(projectRoot);\n if (directResolved) {\n return directResolved;\n }\n const retryDelay = PROJECT_SDK_RESOLUTION_RETRY_DELAYS_MS[attempt];\n if (retryDelay === undefined) {\n break;\n }\n await delay(retryDelay);\n }\n }\n\n throw lastError;\n}\n\nexport async function loadProjectAuthoringAdapter(\n projectRoot: string,\n): Promise<LoadedProjectAuthoringAdapterV1> {\n let packageJsonPath: string;\n let adapterPath: string;\n try {\n ({ packageJsonPath, adapterPath } =\n await resolveProjectAuthoringAdapter(projectRoot));\n } catch (error) {\n throw problemFromResolveError(error);\n }\n\n const packageRoot = path.dirname(packageJsonPath);\n assertResolvedInsidePackage({\n packageRoot,\n resolvedPath: adapterPath,\n label: \"@dreamboard-games/sdk/authoring\",\n });\n\n const packageJson = JSON.parse(\n await readFile(packageJsonPath, \"utf8\"),\n ) as PackageJson;\n if (\n packageJson.name !== \"@dreamboard-games/sdk\" ||\n typeof packageJson.version !== \"string\" ||\n packageJson.version.trim().length === 0\n ) {\n throw new ProjectAuthoringError(\n \"SDK_METADATA_MISMATCH\",\n \"Installed SDK package metadata is invalid.\",\n );\n }\n\n const moduleRecord = (await import(pathToFileURL(adapterPath).href)) as {\n projectAuthoringAdapter?: unknown;\n default?: unknown;\n };\n const adapter = validateProjectAuthoringAdapter(\n moduleRecord.projectAuthoringAdapter ?? moduleRecord.default,\n );\n if (\n !isAuthoringMetadataVersionCompatible({\n metadataVersion: adapter.metadata.sdkVersion,\n packageVersion: packageJson.version,\n })\n ) {\n throw new ProjectAuthoringError(\n \"SDK_METADATA_MISMATCH\",\n `SDK authoring adapter reports version ${adapter.metadata.sdkVersion}, but package metadata is ${packageJson.version}.`,\n );\n }\n\n return {\n packageRoot,\n packageVersion: packageJson.version,\n adapterPath,\n adapter,\n };\n}\n","import {\n mkdir,\n readFile,\n realpath,\n rm,\n stat,\n unlink,\n writeFile,\n} from \"node:fs/promises\";\nimport path from \"node:path\";\n\nconst CONTROL_CHARS = /[\\x00-\\x1f\\x7f]/;\nconst URL_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*:/;\nconst WINDOWS_DEVICE_NAME = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\\..*)?$/i;\nconst ENCODED_SEPARATOR = /%(?:2f|5c)/i;\n\nfunction isWindowsDeviceSegment(segment: string): boolean {\n return WINDOWS_DEVICE_NAME.test(segment.replace(/[. ]+$/g, \"\"));\n}\n\nfunction isPathInside(parent: string, candidate: string): boolean {\n const relative = path.relative(parent, candidate);\n return (\n relative === \"\" ||\n (!relative.startsWith(\"..\") && !path.isAbsolute(relative))\n );\n}\n\nfunction assertContained(\n parent: string,\n candidate: string,\n label: string,\n): void {\n if (!isPathInside(parent, candidate)) {\n throw new Error(`${label} escapes the workspace.`);\n }\n}\n\nfunction isMissingFileError(error: unknown): boolean {\n return (\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n (error as { code?: unknown }).code === \"ENOENT\"\n );\n}\n\nexport function normalizeOwnedProjectPath(input: string): string | null {\n if (\n input.length === 0 ||\n input.trim().length === 0 ||\n input.startsWith(\"/\") ||\n input.includes(\"\\\\\") ||\n input.includes(\":\") ||\n URL_SCHEME.test(input) ||\n CONTROL_CHARS.test(input) ||\n ENCODED_SEPARATOR.test(input) ||\n path.win32.isAbsolute(input)\n ) {\n return null;\n }\n\n const segments = input.split(\"/\");\n if (\n segments.some(\n (segment) =>\n segment.length === 0 ||\n segment.trim().length === 0 ||\n segment === \".\" ||\n segment === \"..\" ||\n isWindowsDeviceSegment(segment),\n )\n ) {\n return null;\n }\n\n return segments.join(\"/\");\n}\n\nexport function resolveWorkspacePath(\n rootDir: string,\n projectPath: string,\n): string {\n const normalized = normalizeOwnedProjectPath(projectPath);\n if (normalized === null) {\n throw new Error(`Unsafe project path: ${projectPath}`);\n }\n\n const rootPath = path.resolve(rootDir);\n const resolvedPath = path.resolve(rootPath, normalized);\n assertContained(rootPath, resolvedPath, `Project path ${projectPath}`);\n return resolvedPath;\n}\n\nasync function realpathIfExists(filePath: string): Promise<string | null> {\n try {\n return await realpath(filePath);\n } catch (error) {\n if (isMissingFileError(error)) return null;\n throw error;\n }\n}\n\nasync function nearestExistingAncestor(filePath: string): Promise<string> {\n let current = filePath;\n while (true) {\n try {\n await stat(current);\n return current;\n } catch (error) {\n if (!isMissingFileError(error)) throw error;\n const parent = path.dirname(current);\n if (parent === current) throw error;\n current = parent;\n }\n }\n}\n\nasync function assertRealpathContained(\n rootDir: string,\n filePath: string,\n label: string,\n): Promise<void> {\n const rootRealpath = await realpath(rootDir);\n const targetRealpath = await realpath(filePath);\n assertContained(rootRealpath, targetRealpath, label);\n}\n\nasync function assertNearestParentContained(\n rootDir: string,\n filePath: string,\n label: string,\n): Promise<void> {\n const rootRealpath = await realpath(rootDir);\n const nearestParent = await nearestExistingAncestor(path.dirname(filePath));\n const nearestParentRealpath = await realpath(nearestParent);\n assertContained(rootRealpath, nearestParentRealpath, label);\n}\n\nasync function assertExistingTargetContained(\n rootDir: string,\n filePath: string,\n label: string,\n): Promise<void> {\n const targetRealpath = await realpathIfExists(filePath);\n if (targetRealpath === null) return;\n const rootRealpath = await realpath(rootDir);\n assertContained(rootRealpath, targetRealpath, label);\n}\n\nasync function prepareWorkspaceWriteTarget(\n rootDir: string,\n filePath: string,\n): Promise<void> {\n await assertNearestParentContained(rootDir, filePath, \"Project path\");\n await mkdir(path.dirname(filePath), { recursive: true });\n await assertRealpathContained(\n rootDir,\n path.dirname(filePath),\n \"Project path\",\n );\n await assertExistingTargetContained(rootDir, filePath, \"Project path\");\n}\n\nexport async function readWorkspaceTextFile(\n rootDir: string,\n projectPath: string,\n): Promise<string> {\n const filePath = resolveWorkspacePath(rootDir, projectPath);\n await assertExistingTargetContained(rootDir, filePath, \"Project path\");\n return readFile(filePath, \"utf8\");\n}\n\nexport async function readWorkspaceTextFileIfExists(\n rootDir: string,\n projectPath: string,\n): Promise<string | null> {\n const filePath = resolveWorkspacePath(rootDir, projectPath);\n const targetRealpath = await realpathIfExists(filePath);\n if (targetRealpath === null) return null;\n const rootRealpath = await realpath(rootDir);\n assertContained(rootRealpath, targetRealpath, \"Project path\");\n return readFile(filePath, \"utf8\");\n}\n\nexport async function writeWorkspaceTextFile(\n rootDir: string,\n projectPath: string,\n content: string,\n): Promise<void> {\n const filePath = resolveWorkspacePath(rootDir, projectPath);\n await prepareWorkspaceWriteTarget(rootDir, filePath);\n await writeFile(filePath, content, \"utf8\");\n}\n\nexport async function writeWorkspaceJsonFile(\n rootDir: string,\n projectPath: string,\n data: unknown,\n): Promise<void> {\n await writeWorkspaceTextFile(\n rootDir,\n projectPath,\n `${JSON.stringify(data, null, 2)}\\n`,\n );\n}\n\nexport async function workspacePathExists(\n rootDir: string,\n projectPath: string,\n): Promise<boolean> {\n const filePath = resolveWorkspacePath(rootDir, projectPath);\n const targetRealpath = await realpathIfExists(filePath);\n if (targetRealpath === null) return false;\n const rootRealpath = await realpath(rootDir);\n assertContained(rootRealpath, targetRealpath, \"Project path\");\n return true;\n}\n\nexport async function unlinkWorkspaceFile(\n rootDir: string,\n projectPath: string,\n): Promise<void> {\n const filePath = resolveWorkspacePath(rootDir, projectPath);\n await assertNearestParentContained(rootDir, filePath, \"Project path\");\n await assertExistingTargetContained(rootDir, filePath, \"Project path\");\n await unlink(filePath);\n}\n\nexport async function removeWorkspacePath(\n rootDir: string,\n projectPath: string,\n options: { recursive?: boolean; force?: boolean } = {},\n): Promise<void> {\n const filePath = resolveWorkspacePath(rootDir, projectPath);\n await assertNearestParentContained(rootDir, filePath, \"Project path\");\n await assertExistingTargetContained(rootDir, filePath, \"Project path\");\n await rm(filePath, options);\n}\n"],"mappings":"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","names":["path","path","isRecord","readFile","path"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/config/keychain-backend.ts"],"sourcesContent":["/**\n * OS keychain-backed `CredentialBackend` built on top of `@napi-rs/keyring`.\n *\n * Keychain is the required storage backend for published builds. Development\n * builds can still opt in with `credentialBackend: \"keychain\"` in\n * `~/.dreamboard/config.json` (see `credential-store.ts` for the resolver).\n * When enabled, it gives us:\n * - A refresh token encrypted at rest by the OS (Keychain on macOS,\n * Credential Vault on Windows, Secret Service on Linux).\n * - Protection against other processes running as the same user tailing\n * `~/.dreamboard/auth.json` to scrape the token.\n *\n * This module is loaded optionally: `@napi-rs/keyring` is declared as an\n * `optionalDependencies` entry. Published builds fail closed when the native\n * binary or OS keyring is unavailable; development builds may still use the\n * file backend.\n *\n * One-time migration: when the active backend is keychain and `auth.json` still\n * has tokens, `credential-store.ts` copies them into the keychain, verifies the\n * keychain read, and deletes the file. This is the only path that intentionally\n * mutates both backends.\n */\n\nimport type {\n CredentialBackend,\n Credentials,\n StoredSessionSnapshot,\n} from \"./credential-store.js\";\n\n/** Keychain service id. Shared across all Dreamboard CLI builds. */\nconst KEYCHAIN_SERVICE = \"dreamboard-cli\";\n/**\n * Keychain account id. The `user@host` shape is conventional but we keep\n * it fixed for now because the CLI only cares about \"the session for this\n * OS user\", not per-process sessions.\n */\nconst KEYCHAIN_ACCOUNT = \"session\";\n\ntype EntryInstance = {\n setPassword(value: string): void;\n getPassword(): string | null | undefined;\n deletePassword(): boolean;\n};\n\ntype KeyringModule = {\n Entry: new (service: string, account: string) => EntryInstance;\n};\n\nlet cachedModule: KeyringModule | null | undefined;\n\nasync function loadKeyringModule(): Promise<KeyringModule | null> {\n if (cachedModule !== undefined) return cachedModule;\n try {\n // `@napi-rs/keyring` is an optional dependency. If the native binary is\n // missing on this platform the dynamic import throws; resolver policy in\n // credential-store decides whether that is fatal.\n const mod = (await import(\"@napi-rs/keyring\")) as unknown as KeyringModule;\n cachedModule = mod;\n } catch {\n cachedModule = null;\n }\n return cachedModule;\n}\n\nfunction keychainProbe(entry: EntryInstance): boolean {\n // Some platforms have the module installed but no accessible keyring\n // (e.g. headless Linux without DBus). Touch getPassword to verify we\n // can talk to the service without side effects.\n try {\n entry.getPassword();\n return true;\n } catch {\n return false;\n }\n}\n\ntype KeychainPayload = {\n clerkAccessToken?: string;\n accessToken?: string;\n refreshToken?: string;\n clerkAccessExpiresAt?: string;\n tokenExpiresAt?: string;\n dreamboardApiToken?: string;\n dreamboardApiExpiresAt?: string;\n clerkOAuthIssuer?: string;\n clerkOAuthClientId?: string;\n clerkOAuthTokenUrl?: string;\n environment?: string;\n};\n\nfunction parsePayload(\n raw: string | null | undefined,\n): StoredSessionSnapshot | null {\n if (raw === null || raw === undefined) return null;\n const trimmed = raw.trim();\n if (trimmed.length === 0) return null;\n try {\n const parsed = JSON.parse(trimmed) as KeychainPayload;\n const accessToken = parsed.clerkAccessToken ?? parsed.accessToken;\n if (!accessToken && !parsed.refreshToken) return null;\n return {\n accessToken: accessToken || undefined,\n refreshToken: parsed.refreshToken || undefined,\n tokenExpiresAt:\n parsed.clerkAccessExpiresAt || parsed.tokenExpiresAt || undefined,\n dreamboardApiToken: parsed.dreamboardApiToken || undefined,\n dreamboardApiExpiresAt: parsed.dreamboardApiExpiresAt || undefined,\n clerkOAuthIssuer: parsed.clerkOAuthIssuer || undefined,\n clerkOAuthClientId: parsed.clerkOAuthClientId || undefined,\n clerkOAuthTokenUrl: parsed.clerkOAuthTokenUrl || undefined,\n environment: parsed.environment || undefined,\n };\n } catch {\n return null;\n }\n}\n\nfunction writeFull(entry: EntryInstance, creds: Credentials): void {\n if (!creds.accessToken || !creds.refreshToken) {\n throw new Error(\n \"Refusing to persist credentials with an empty accessToken or refreshToken.\",\n );\n }\n const payload: KeychainPayload = {\n clerkAccessToken: creds.accessToken,\n refreshToken: creds.refreshToken,\n clerkAccessExpiresAt: creds.tokenExpiresAt,\n dreamboardApiToken: creds.dreamboardApiToken,\n dreamboardApiExpiresAt: creds.dreamboardApiExpiresAt,\n clerkOAuthIssuer: creds.clerkOAuthIssuer,\n clerkOAuthClientId: creds.clerkOAuthClientId,\n clerkOAuthTokenUrl: creds.clerkOAuthTokenUrl,\n environment: creds.environment,\n };\n entry.setPassword(JSON.stringify(payload));\n}\n\nfunction writeAccessOnly(entry: EntryInstance, accessToken: string): void {\n if (!accessToken) {\n throw new Error(\"Refusing to persist an empty access token.\");\n }\n const payload: KeychainPayload = { accessToken };\n entry.setPassword(JSON.stringify(payload));\n}\n\nfunction clear(entry: EntryInstance): void {\n try {\n entry.deletePassword();\n } catch {\n // keyring-rs throws when the entry does not exist. That is fine -\n // `clearCredentials` contracts as idempotent.\n }\n}\n\nexport type KeychainAvailability =\n | { available: true; backend: CredentialBackend }\n | { available: false; reason: string };\n\n/**\n * Attempt to construct a keychain-backed `CredentialBackend`. Returns an\n * `available: false` result (with a reason) if the native module, the\n * OS keyring, or the probe fails.\n */\nexport async function tryKeychainBackend(): Promise<KeychainAvailability> {\n const mod = await loadKeyringModule();\n if (!mod) {\n return {\n available: false,\n reason: \"@napi-rs/keyring is not installed for this platform\",\n };\n }\n\n let entry: EntryInstance;\n try {\n entry = new mod.Entry(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT);\n } catch (err) {\n return {\n available: false,\n reason: `Failed to construct keyring entry: ${String((err as Error).message ?? err)}`,\n };\n }\n\n if (!keychainProbe(entry)) {\n return {\n available: false,\n reason: \"OS keyring is not accessible from this process\",\n };\n }\n\n const backend: CredentialBackend = {\n name: \"keychain\",\n async read() {\n try {\n return parsePayload(entry.getPassword());\n } catch (err) {\n const message = String((err as Error).message ?? err);\n // Transient keychain access errors (e.g. Touch ID prompt\n // cancelled) should not surface as \"session wiped\". Treat the\n // unreadable state as \"no session\" so the caller can fall back\n // to prompting for login.\n if (/no matching entry|not found/i.test(message)) {\n return null;\n }\n throw err;\n }\n },\n async writeFull(creds) {\n writeFull(entry, creds);\n },\n async writeAccessOnly(accessToken) {\n writeAccessOnly(entry, accessToken);\n },\n async clear() {\n clear(entry);\n },\n };\n return { available: true, backend };\n}\n\n/**\n * Test-only escape hatch so unit tests can install a fake keyring module\n * without going through the dynamic import cache.\n */\nexport function _setKeyringModuleForTests(mod: KeyringModule | null): void {\n cachedModule = mod;\n}\n\nexport function _resetKeyringModuleForTests(): void {\n cachedModule = undefined;\n}\n"],"mappings":";;;;AA8BA,IAAM,mBAAmB;AAMzB,IAAM,mBAAmB;AAYzB,IAAI;AAEJ,eAAe,oBAAmD;AAChE,MAAI,iBAAiB,OAAW,QAAO;AACvC,MAAI;AAIF,UAAM,MAAO,MAAM,OAAO,kBAAkB;AAC5C,mBAAe;AAAA,EACjB,QAAQ;AACN,mBAAe;AAAA,EACjB;AACA,SAAO;AACT;AAEA,SAAS,cAAc,OAA+B;AAIpD,MAAI;AACF,UAAM,YAAY;AAClB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAgBA,SAAS,aACP,KAC8B;AAC9B,MAAI,QAAQ,QAAQ,QAAQ,OAAW,QAAO;AAC9C,QAAM,UAAU,IAAI,KAAK;AACzB,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,OAAO;AACjC,UAAM,cAAc,OAAO,oBAAoB,OAAO;AACtD,QAAI,CAAC,eAAe,CAAC,OAAO,aAAc,QAAO;AACjD,WAAO;AAAA,MACL,aAAa,eAAe;AAAA,MAC5B,cAAc,OAAO,gBAAgB;AAAA,MACrC,gBACE,OAAO,wBAAwB,OAAO,kBAAkB;AAAA,MAC1D,oBAAoB,OAAO,sBAAsB;AAAA,MACjD,wBAAwB,OAAO,0BAA0B;AAAA,MACzD,kBAAkB,OAAO,oBAAoB;AAAA,MAC7C,oBAAoB,OAAO,sBAAsB;AAAA,MACjD,oBAAoB,OAAO,sBAAsB;AAAA,MACjD,aAAa,OAAO,eAAe;AAAA,IACrC;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,UAAU,OAAsB,OAA0B;AACjE,MAAI,CAAC,MAAM,eAAe,CAAC,MAAM,cAAc;AAC7C,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,QAAM,UAA2B;AAAA,IAC/B,kBAAkB,MAAM;AAAA,IACxB,cAAc,MAAM;AAAA,IACpB,sBAAsB,MAAM;AAAA,IAC5B,oBAAoB,MAAM;AAAA,IAC1B,wBAAwB,MAAM;AAAA,IAC9B,kBAAkB,MAAM;AAAA,IACxB,oBAAoB,MAAM;AAAA,IAC1B,oBAAoB,MAAM;AAAA,IAC1B,aAAa,MAAM;AAAA,EACrB;AACA,QAAM,YAAY,KAAK,UAAU,OAAO,CAAC;AAC3C;AAEA,SAAS,gBAAgB,OAAsB,aAA2B;AACxE,MAAI,CAAC,aAAa;AAChB,UAAM,IAAI,MAAM,4CAA4C;AAAA,EAC9D;AACA,QAAM,UAA2B,EAAE,YAAY;AAC/C,QAAM,YAAY,KAAK,UAAU,OAAO,CAAC;AAC3C;AAEA,SAAS,MAAM,OAA4B;AACzC,MAAI;AACF,UAAM,eAAe;AAAA,EACvB,QAAQ;AAAA,EAGR;AACF;AAWA,eAAsB,qBAAoD;AACxE,QAAM,MAAM,MAAM,kBAAkB;AACpC,MAAI,CAAC,KAAK;AACR,WAAO;AAAA,MACL,WAAW;AAAA,MACX,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AACF,YAAQ,IAAI,IAAI,MAAM,kBAAkB,gBAAgB;AAAA,EAC1D,SAAS,KAAK;AACZ,WAAO;AAAA,MACL,WAAW;AAAA,MACX,QAAQ,sCAAsC,OAAQ,IAAc,WAAW,GAAG,CAAC;AAAA,IACrF;AAAA,EACF;AAEA,MAAI,CAAC,cAAc,KAAK,GAAG;AACzB,WAAO;AAAA,MACL,WAAW;AAAA,MACX,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,QAAM,UAA6B;AAAA,IACjC,MAAM;AAAA,IACN,MAAM,OAAO;AACX,UAAI;AACF,eAAO,aAAa,MAAM,YAAY,CAAC;AAAA,MACzC,SAAS,KAAK;AACZ,cAAM,UAAU,OAAQ,IAAc,WAAW,GAAG;AAKpD,YAAI,+BAA+B,KAAK,OAAO,GAAG;AAChD,iBAAO;AAAA,QACT;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,MAAM,UAAU,OAAO;AACrB,gBAAU,OAAO,KAAK;AAAA,IACxB;AAAA,IACA,MAAM,gBAAgB,aAAa;AACjC,sBAAgB,OAAO,WAAW;AAAA,IACpC;AAAA,IACA,MAAM,QAAQ;AACZ,YAAM,KAAK;AAAA,IACb;AAAA,EACF;AACA,SAAO,EAAE,WAAW,MAAM,QAAQ;AACpC;AAMO,SAAS,0BAA0B,KAAiC;AACzE,iBAAe;AACjB;AAEO,SAAS,8BAAoC;AAClD,iBAAe;AACjB;","names":[]}
|