@seriousgaming/sdk-tools 0.0.0-stage → 1.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (391) hide show
  1. package/LICENSE +93 -0
  2. package/README.md +36 -2
  3. package/dist/bundling/logic-exports.d.ts +34 -0
  4. package/dist/bundling/node-builtins.d.ts +21 -0
  5. package/dist/bundling/transpiler.d.ts +7 -0
  6. package/dist/cli.js +27368 -0
  7. package/dist/client/dev-server.d.ts +59 -0
  8. package/dist/client/index.d.ts +28 -0
  9. package/dist/client/stub-services.d.ts +32 -0
  10. package/dist/client/test-session.d.ts +195 -0
  11. package/dist/client.js +16884 -0
  12. package/dist/core/asset-reference-translator.d.ts +46 -0
  13. package/dist/core/clock.d.ts +57 -0
  14. package/dist/core/faults.d.ts +65 -0
  15. package/dist/core/game-settings.d.ts +65 -0
  16. package/dist/core/history.d.ts +89 -0
  17. package/dist/core/listen-with-fallback.d.ts +30 -0
  18. package/dist/core/mutation-queue.d.ts +61 -0
  19. package/dist/core/package-scenario.d.ts +89 -0
  20. package/dist/core/participant-registry.d.ts +109 -0
  21. package/dist/core/pending-changes.d.ts +14 -0
  22. package/dist/core/protocol.d.ts +83 -0
  23. package/dist/core/scenario-drafts.d.ts +32 -0
  24. package/dist/core/session-endpoints.d.ts +108 -0
  25. package/dist/core/session-gates.d.ts +75 -0
  26. package/dist/core/session-host.d.ts +94 -0
  27. package/dist/core/session-refusals.d.ts +56 -0
  28. package/dist/core/session-rewind.d.ts +32 -0
  29. package/dist/core/session-roster.d.ts +128 -0
  30. package/dist/core/session-surfaces.d.ts +32 -0
  31. package/dist/core/session-ticks.d.ts +18 -0
  32. package/dist/core/session-timers.d.ts +84 -0
  33. package/dist/core/session-writes.d.ts +40 -0
  34. package/dist/core/session.d.ts +385 -0
  35. package/dist/core/settings-locks.d.ts +22 -0
  36. package/dist/core/team-names.d.ts +52 -0
  37. package/dist/core/team-state-store.d.ts +209 -0
  38. package/dist/core/tick-timer.d.ts +75 -0
  39. package/dist/dev/blob-links.d.ts +10 -0
  40. package/dist/dev/console/assets/index-DgFglsnA.js +204 -0
  41. package/dist/dev/console/index.html +12 -0
  42. package/dist/dev/console-path.d.ts +19 -0
  43. package/dist/dev/lockfile.d.ts +124 -0
  44. package/dist/dev/package-watcher.d.ts +21 -0
  45. package/dist/dev/routes.d.ts +67 -0
  46. package/dist/dev/shell.d.ts +260 -0
  47. package/dist/dev/surfaces.d.ts +41 -0
  48. package/dist/dev/tools-identity.d.ts +23 -0
  49. package/dist/dev/view/batch.d.ts +172 -0
  50. package/dist/dev/view/commands.d.ts +75 -0
  51. package/dist/dev/view/route.d.ts +58 -0
  52. package/dist/dev/view/view.d.ts +78 -0
  53. package/dist/dev/view/viewer.d.ts +32 -0
  54. package/dist/discovery/config.d.ts +9 -0
  55. package/dist/discovery/manifest-paths.d.ts +21 -0
  56. package/dist/discovery/removed-fields.d.ts +90 -0
  57. package/dist/docs/api/deck.md +386 -0
  58. package/dist/docs/api/index.md +67 -0
  59. package/dist/docs/api/sdk-commands.md +286 -0
  60. package/dist/docs/api/sdk-e2e.md +433 -0
  61. package/dist/docs/api/sdk-projection.md +207 -0
  62. package/dist/docs/api/sdk-testing.md +214 -0
  63. package/dist/docs/api/sdk-tools-client.md +560 -0
  64. package/dist/docs/api/sdk-ui-testing.md +231 -0
  65. package/dist/docs/api/sdk-ui.md +1269 -0
  66. package/dist/docs/api/sdk.md +2140 -0
  67. package/dist/docs/changelog-0.x.md +1156 -0
  68. package/dist/docs/changelog.md +236 -0
  69. package/dist/docs/cli-and-dev-server.md +217 -0
  70. package/dist/docs/cli-error-codes.md +154 -0
  71. package/dist/docs/cli-reference.md +2488 -0
  72. package/dist/docs/cli-simulation-testing.md +222 -0
  73. package/dist/docs/concepts/index.md +46 -0
  74. package/dist/docs/concepts/terminology.md +322 -0
  75. package/dist/docs/environment-variables.md +102 -0
  76. package/dist/docs/getting-started.md +145 -0
  77. package/dist/docs/guides/ai-facilitation.md +220 -0
  78. package/dist/docs/guides/capabilities.md +460 -0
  79. package/dist/docs/guides/endpoints.md +335 -0
  80. package/dist/docs/guides/engine-testing.md +477 -0
  81. package/dist/docs/guides/engine.md +527 -0
  82. package/dist/docs/guides/file-handling.md +405 -0
  83. package/dist/docs/guides/games.md +176 -0
  84. package/dist/docs/guides/global-settings.md +94 -0
  85. package/dist/docs/guides/guide.md +123 -0
  86. package/dist/docs/guides/hooks.md +183 -0
  87. package/dist/docs/guides/index.md +51 -0
  88. package/dist/docs/guides/internationalization.md +288 -0
  89. package/dist/docs/guides/llm-integration.md +635 -0
  90. package/dist/docs/guides/multi-team-games.md +586 -0
  91. package/dist/docs/guides/patterns.md +391 -0
  92. package/dist/docs/guides/presentation.md +196 -0
  93. package/dist/docs/guides/production-constraints.md +130 -0
  94. package/dist/docs/guides/scenario-editor.md +143 -0
  95. package/dist/docs/guides/scenarios.md +515 -0
  96. package/dist/docs/guides/session.md +972 -0
  97. package/dist/docs/guides/settings.md +458 -0
  98. package/dist/docs/guides/setup.md +193 -0
  99. package/dist/docs/guides/speech-generation.md +233 -0
  100. package/dist/docs/guides/starter-game.md +164 -0
  101. package/dist/docs/guides/testing.md +575 -0
  102. package/dist/docs/guides/tick-controller.md +569 -0
  103. package/dist/docs/guides/type-safety.md +516 -0
  104. package/dist/docs/guides/ui.md +1389 -0
  105. package/dist/docs/guides/upgrading.md +636 -0
  106. package/dist/docs/guides/validators.md +217 -0
  107. package/dist/docs/guides/waiting-room.md +244 -0
  108. package/dist/docs/index.md +41 -0
  109. package/dist/docs/recipes/act-on-the-session-from-an-endpoint.md +95 -0
  110. package/dist/docs/recipes/adapt-a-game-for-walkthroughs.md +133 -0
  111. package/dist/docs/recipes/adapt-ui-for-observers.md +74 -0
  112. package/dist/docs/recipes/call-endpoint-from-ui.md +109 -0
  113. package/dist/docs/recipes/check-a-package-from-a-script.md +117 -0
  114. package/dist/docs/recipes/create-capability.md +66 -0
  115. package/dist/docs/recipes/create-endpoint.md +127 -0
  116. package/dist/docs/recipes/create-engine.md +111 -0
  117. package/dist/docs/recipes/create-game.md +70 -0
  118. package/dist/docs/recipes/create-guide.md +75 -0
  119. package/dist/docs/recipes/create-hook.md +80 -0
  120. package/dist/docs/recipes/create-presentation.md +142 -0
  121. package/dist/docs/recipes/create-scenario-editor.md +102 -0
  122. package/dist/docs/recipes/create-scenario.md +98 -0
  123. package/dist/docs/recipes/create-session-ui.md +116 -0
  124. package/dist/docs/recipes/create-settings.md +88 -0
  125. package/dist/docs/recipes/create-setup.md +115 -0
  126. package/dist/docs/recipes/create-tick-controller.md +65 -0
  127. package/dist/docs/recipes/create-user-interface.md +96 -0
  128. package/dist/docs/recipes/create-validator.md +67 -0
  129. package/dist/docs/recipes/create-waiting-room-ui.md +109 -0
  130. package/dist/docs/recipes/declare-api-uses.md +67 -0
  131. package/dist/docs/recipes/design-state-for-concurrent-writes.md +96 -0
  132. package/dist/docs/recipes/distribute-package.md +80 -0
  133. package/dist/docs/recipes/e2e-testing.md +116 -0
  134. package/dist/docs/recipes/end-game-on-condition.md +154 -0
  135. package/dist/docs/recipes/fix-duplicate-packages.md +101 -0
  136. package/dist/docs/recipes/index.md +86 -0
  137. package/dist/docs/recipes/make-an-endpoint-external.md +101 -0
  138. package/dist/docs/recipes/negotiate-across-teams.md +164 -0
  139. package/dist/docs/recipes/observe-other-teams-state.md +119 -0
  140. package/dist/docs/recipes/publish-a-package.md +139 -0
  141. package/dist/docs/recipes/remember-a-viewers-choice.md +55 -0
  142. package/dist/docs/recipes/review-a-past-day.md +73 -0
  143. package/dist/docs/recipes/share-assets-across-scenarios.md +120 -0
  144. package/dist/docs/recipes/share-logic-between-engine-and-ui.md +130 -0
  145. package/dist/docs/recipes/show-all-teams-participants.md +109 -0
  146. package/dist/docs/recipes/start-from-an-empty-package.md +169 -0
  147. package/dist/docs/recipes/take-a-file-in-an-endpoint.md +82 -0
  148. package/dist/docs/recipes/test-a-multi-role-game.md +51 -0
  149. package/dist/docs/recipes/test-user-interface.md +95 -0
  150. package/dist/docs/recipes/test-with-engine.md +115 -0
  151. package/dist/docs/recipes/upload-and-display-files.md +148 -0
  152. package/dist/docs/recipes/use-request-play.md +65 -0
  153. package/dist/docs/recipes/use-request-tick.md +75 -0
  154. package/dist/docs/troubleshooting.md +816 -0
  155. package/dist/execution/blob-inputs.d.ts +57 -0
  156. package/dist/execution/bundle-reload.d.ts +42 -0
  157. package/dist/execution/config-resolver.d.ts +127 -0
  158. package/dist/execution/configuration-builder.d.ts +66 -0
  159. package/dist/execution/context-composition.d.ts +161 -0
  160. package/dist/execution/endpoint-actions.d.ts +9 -0
  161. package/dist/execution/endpoint-payloads.d.ts +32 -0
  162. package/dist/execution/endpoint-runner.d.ts +129 -0
  163. package/dist/execution/engine-state.d.ts +40 -0
  164. package/dist/execution/executor-callbacks.d.ts +56 -0
  165. package/dist/execution/executor.d.ts +436 -0
  166. package/dist/execution/gates.d.ts +45 -0
  167. package/dist/execution/hook-chain.d.ts +25 -0
  168. package/dist/execution/logic-calls.d.ts +190 -0
  169. package/dist/execution/logic-failure.d.ts +84 -0
  170. package/dist/execution/logic-loader.d.ts +54 -0
  171. package/dist/execution/logic-result.d.ts +76 -0
  172. package/dist/execution/tick-controller-call.d.ts +32 -0
  173. package/dist/execution/types.d.ts +48 -0
  174. package/dist/generation/ts-type.d.ts +39 -0
  175. package/dist/guide/locale.d.ts +12 -0
  176. package/dist/guide/video-registry.d.ts +42 -0
  177. package/dist/help-examples.d.ts +125 -0
  178. package/dist/host/adapter.d.ts +67 -0
  179. package/dist/host/host-channel.d.ts +222 -0
  180. package/dist/host/index.d.ts +20 -0
  181. package/dist/host/protocol.d.ts +783 -0
  182. package/dist/host/surfaces.d.ts +42 -0
  183. package/dist/host/theme.d.ts +55 -0
  184. package/dist/host.js +583 -0
  185. package/dist/prompts/index.d.ts +7 -0
  186. package/dist/prompts/schema-builder.d.ts +29 -0
  187. package/dist/prompts/types.d.ts +108 -0
  188. package/dist/prompts/validators.d.ts +32 -0
  189. package/dist/runtime/capability-scope.d.ts +6 -0
  190. package/dist/runtime/context-merge.d.ts +29 -0
  191. package/dist/runtime/context-projection.d.ts +41 -0
  192. package/dist/runtime/guarded-callbacks.d.ts +15 -0
  193. package/dist/runtime/index.d.ts +27 -0
  194. package/dist/runtime/manifest-version.d.ts +55 -0
  195. package/dist/runtime/tick-validation.d.ts +25 -0
  196. package/dist/runtime.js +346 -0
  197. package/dist/schema.json +1546 -0
  198. package/dist/server/vite-plugin.d.ts +46 -0
  199. package/dist/services/tts/client.d.ts +51 -0
  200. package/dist/services/tts/elevenlabs.d.ts +24 -0
  201. package/dist/services/tts/types.d.ts +44 -0
  202. package/dist/storage/asset-resolver.d.ts +49 -0
  203. package/dist/storage/blob-store.d.ts +41 -0
  204. package/dist/templates/endpoint/logic.test.ts.liquid +61 -0
  205. package/dist/templates/endpoint/logic.ts.liquid +25 -0
  206. package/dist/templates/engine/init.json.liquid +7 -0
  207. package/dist/templates/engine/logic-aggregate.test.ts.liquid +90 -0
  208. package/dist/templates/engine/logic-aggregate.ts.liquid +46 -0
  209. package/dist/templates/engine/logic.test.ts.liquid +61 -0
  210. package/dist/templates/engine/logic.ts.liquid +32 -0
  211. package/dist/templates/engine/run.json.liquid +7 -0
  212. package/dist/templates/guide/01-at-a-glance.md.liquid +19 -0
  213. package/dist/templates/guide/02-preparation.md.liquid +18 -0
  214. package/dist/templates/guide/03-running-the-session.md.liquid +37 -0
  215. package/dist/templates/hook/logic.test.ts.liquid +46 -0
  216. package/dist/templates/hook/logic.ts.liquid +12 -0
  217. package/dist/templates/package/CLAUDE.md.template +338 -0
  218. package/dist/templates/package/DESIGN.md.liquid +144 -0
  219. package/dist/templates/package/README.md.liquid +143 -0
  220. package/dist/templates/package/claude/rules/workflow-create-capability.md +26 -0
  221. package/dist/templates/package/claude/rules/workflow-create-endpoint.md +35 -0
  222. package/dist/templates/package/claude/rules/workflow-create-engine.md +35 -0
  223. package/dist/templates/package/claude/rules/workflow-create-guide.md +40 -0
  224. package/dist/templates/package/claude/rules/workflow-create-presentation.md +37 -0
  225. package/dist/templates/package/claude/rules/workflow-create-user-interface.md +31 -0
  226. package/dist/templates/package/claude/rules/workflow-publish-package.md +54 -0
  227. package/dist/templates/package/claude/rules/workflow-test-engine-or-ui.md +30 -0
  228. package/dist/templates/package/claude/rules/workflow-update-sdk-version.md +43 -0
  229. package/dist/templates/package/claude/settings.json.template +16 -0
  230. package/dist/templates/package/gitignore.template +15 -0
  231. package/dist/templates/package/global-setup.ts.template +21 -0
  232. package/dist/templates/package/globals.d.ts.template +2 -0
  233. package/dist/templates/package/oxfmtrc.json.template +14 -0
  234. package/dist/templates/package/oxlintrc.json.template +238 -0
  235. package/dist/templates/package/package.json.liquid +65 -0
  236. package/dist/templates/package/playwright.config.ts.liquid +19 -0
  237. package/dist/templates/package/pnpm-workspace.yaml.template +27 -0
  238. package/dist/templates/package/test-setup.ts.liquid +7 -0
  239. package/dist/templates/package/tsconfig.json.liquid +15 -0
  240. package/dist/templates/package/vite.config.ts.liquid +19 -0
  241. package/dist/templates/package/vitest.config.ts.liquid +25 -0
  242. package/dist/templates/package/vitest.engine.config.ts.liquid +12 -0
  243. package/dist/templates/presentation/index.html.liquid +20 -0
  244. package/dist/templates/presentation/src/App.tsx.liquid +49 -0
  245. package/dist/templates/presentation/src/Slides.tsx.liquid +81 -0
  246. package/dist/templates/presentation/src/i18n.ts.liquid +54 -0
  247. package/dist/templates/presentation/src/main.tsx.liquid +14 -0
  248. package/dist/templates/scenario_editor/src/App.test.tsx.liquid +30 -0
  249. package/dist/templates/scenario_editor/src/App.tsx.liquid +95 -0
  250. package/dist/templates/scenario_editor/src/i18n.ts.liquid +44 -0
  251. package/dist/templates/session/src/App.test.tsx.liquid +60 -0
  252. package/dist/templates/session/src/App.tsx.liquid +128 -0
  253. package/dist/templates/session/src/i18n.ts.liquid +53 -0
  254. package/dist/templates/session/src/main.tsx.liquid +48 -0
  255. package/dist/templates/session/src/theme.ts.liquid +46 -0
  256. package/dist/templates/settings-editor/index.html.liquid +12 -0
  257. package/dist/templates/settings-editor/src/main.tsx.liquid +48 -0
  258. package/dist/templates/settings-editor/src/theme.ts.liquid +24 -0
  259. package/dist/templates/setup/src/App.test.tsx.liquid +39 -0
  260. package/dist/templates/setup/src/App.tsx.liquid +102 -0
  261. package/dist/templates/setup/src/i18n.ts.liquid +47 -0
  262. package/dist/templates/starter-game/capabilities/briefing/capability.sdk.json +18 -0
  263. package/dist/templates/starter-game/capabilities/plan/capability.sdk.json +17 -0
  264. package/dist/templates/starter-game/capabilities/results/capability.sdk.json +61 -0
  265. package/dist/templates/starter-game/capabilities/roles/capability.sdk.json +17 -0
  266. package/dist/templates/starter-game/capabilities/weather/capability.sdk.json +23 -0
  267. package/dist/templates/starter-game/engine/defaults.ts +81 -0
  268. package/dist/templates/starter-game/engine/engine.sdk.json +12 -0
  269. package/dist/templates/starter-game/engine/logic.test.ts +221 -0
  270. package/dist/templates/starter-game/engine/logic.ts +149 -0
  271. package/dist/templates/starter-game/engine/test-helpers.ts +74 -0
  272. package/dist/templates/starter-game/engine/types.ts +139 -0
  273. package/dist/templates/starter-game/guide/content/en/01-at-a-glance.md +51 -0
  274. package/dist/templates/starter-game/guide/content/en/02-preparation.md +35 -0
  275. package/dist/templates/starter-game/guide/content/en/03-running-the-day.md +58 -0
  276. package/dist/templates/starter-game/guide/guide.sdk.json +10 -0
  277. package/dist/templates/starter-game/guide/guide.test.ts +108 -0
  278. package/dist/templates/starter-game/hooks/assignproducts/defaults.ts +37 -0
  279. package/dist/templates/starter-game/hooks/assignproducts/hook.sdk.json +13 -0
  280. package/dist/templates/starter-game/hooks/assignproducts/logic/logic.test.ts +77 -0
  281. package/dist/templates/starter-game/hooks/assignproducts/logic/logic.ts +47 -0
  282. package/dist/templates/starter-game/hooks/assignproducts/types.ts +55 -0
  283. package/dist/templates/starter-game/package.json +68 -0
  284. package/dist/templates/starter-game/playwright.config.ts +19 -0
  285. package/dist/templates/starter-game/scenarios/heatwave/scenario.sdk.json +20 -0
  286. package/dist/templates/starter-game/scenarios/ordinary_week/scenario.sdk.json +12 -0
  287. package/dist/templates/starter-game/seriousgaming.sdk.json +23 -0
  288. package/dist/templates/starter-game/session/defaults.ts +80 -0
  289. package/dist/templates/starter-game/session/index.html +12 -0
  290. package/dist/templates/starter-game/session/session.sdk.json +11 -0
  291. package/dist/templates/starter-game/session/src/App.test.tsx +307 -0
  292. package/dist/templates/starter-game/session/src/App.tsx +309 -0
  293. package/dist/templates/starter-game/session/src/Section.tsx +37 -0
  294. package/dist/templates/starter-game/session/src/TeamDoor.tsx +96 -0
  295. package/dist/templates/starter-game/session/src/format.ts +8 -0
  296. package/dist/templates/starter-game/session/src/i18n.ts +51 -0
  297. package/dist/templates/starter-game/session/src/main.tsx +48 -0
  298. package/dist/templates/starter-game/session/src/theme.ts +45 -0
  299. package/dist/templates/starter-game/session/types.ts +135 -0
  300. package/dist/templates/starter-game/settings/engine/settings.sdk.json +50 -0
  301. package/dist/templates/starter-game/settings/menu/settings.sdk.json +78 -0
  302. package/dist/templates/starter-game/settings/session/settings.sdk.json +23 -0
  303. package/dist/templates/starter-game/settings/ui/settings.sdk.json +25 -0
  304. package/dist/templates/starter-game/test-setup.ts +7 -0
  305. package/dist/templates/starter-game/testing/e2e/game.spec.ts +139 -0
  306. package/dist/templates/starter-game/testing/e2e/global-setup.ts +21 -0
  307. package/dist/templates/starter-game/testing/engine/game.engine.test.ts +171 -0
  308. package/dist/templates/starter-game/tsconfig.json +19 -0
  309. package/dist/templates/starter-game/ui/defaults.ts +60 -0
  310. package/dist/templates/starter-game/ui/index.html +12 -0
  311. package/dist/templates/starter-game/ui/src/App.test.tsx +138 -0
  312. package/dist/templates/starter-game/ui/src/App.tsx +175 -0
  313. package/dist/templates/starter-game/ui/src/DayBar.test.tsx +78 -0
  314. package/dist/templates/starter-game/ui/src/DayBar.tsx +72 -0
  315. package/dist/templates/starter-game/ui/src/HistoryPanel.test.tsx +74 -0
  316. package/dist/templates/starter-game/ui/src/HistoryPanel.tsx +120 -0
  317. package/dist/templates/starter-game/ui/src/MakeRow.tsx +129 -0
  318. package/dist/templates/starter-game/ui/src/Section.tsx +37 -0
  319. package/dist/templates/starter-game/ui/src/ThemeToggle.tsx +49 -0
  320. package/dist/templates/starter-game/ui/src/TodayPanel.test.tsx +179 -0
  321. package/dist/templates/starter-game/ui/src/TodayPanel.tsx +92 -0
  322. package/dist/templates/starter-game/ui/src/YesterdayPanel.tsx +142 -0
  323. package/dist/templates/starter-game/ui/src/format.ts +12 -0
  324. package/dist/templates/starter-game/ui/src/i18n.ts +95 -0
  325. package/dist/templates/starter-game/ui/src/main.tsx +48 -0
  326. package/dist/templates/starter-game/ui/src/menu.ts +18 -0
  327. package/dist/templates/starter-game/ui/src/test-helpers.ts +58 -0
  328. package/dist/templates/starter-game/ui/src/theme.ts +45 -0
  329. package/dist/templates/starter-game/ui/src/writes.ts +43 -0
  330. package/dist/templates/starter-game/ui/types.ts +137 -0
  331. package/dist/templates/starter-game/ui/ui.sdk.json +11 -0
  332. package/dist/templates/starter-game/validators/capacity/defaults.ts +37 -0
  333. package/dist/templates/starter-game/validators/capacity/logic/logic.test.ts +96 -0
  334. package/dist/templates/starter-game/validators/capacity/logic/logic.ts +45 -0
  335. package/dist/templates/starter-game/validators/capacity/types.ts +55 -0
  336. package/dist/templates/starter-game/validators/capacity/validator.sdk.json +11 -0
  337. package/dist/templates/starter-game/vite.config.ts +19 -0
  338. package/dist/templates/starter-game/vitest.config.ts +15 -0
  339. package/dist/templates/starter-game/vitest.engine.config.ts +19 -0
  340. package/dist/templates/starter-game/waiting_room/defaults.ts +40 -0
  341. package/dist/templates/starter-game/waiting_room/index.html +12 -0
  342. package/dist/templates/starter-game/waiting_room/src/App.test.tsx +102 -0
  343. package/dist/templates/starter-game/waiting_room/src/App.tsx +194 -0
  344. package/dist/templates/starter-game/waiting_room/src/i18n.ts +33 -0
  345. package/dist/templates/starter-game/waiting_room/src/main.tsx +48 -0
  346. package/dist/templates/starter-game/waiting_room/src/theme.ts +45 -0
  347. package/dist/templates/starter-game/waiting_room/types.ts +59 -0
  348. package/dist/templates/starter-game/waiting_room/waiting_room.sdk.json +10 -0
  349. package/dist/templates/tick-controller/logic.ts.liquid +8 -0
  350. package/dist/templates/ui/index.html.liquid +12 -0
  351. package/dist/templates/ui/src/App.test.tsx.liquid +41 -0
  352. package/dist/templates/ui/src/App.tsx.liquid +103 -0
  353. package/dist/templates/ui/src/i18n.ts.liquid +47 -0
  354. package/dist/templates/ui/src/main.tsx.liquid +48 -0
  355. package/dist/templates/ui/src/theme.ts.liquid +46 -0
  356. package/dist/templates/validator/logic.test.ts.liquid +42 -0
  357. package/dist/templates/validator/logic.ts.liquid +21 -0
  358. package/dist/templates/waiting-room/index.html.liquid +12 -0
  359. package/dist/templates/waiting-room/src/App.test.tsx.liquid +57 -0
  360. package/dist/templates/waiting-room/src/App.tsx.liquid +118 -0
  361. package/dist/templates/waiting-room/src/i18n.ts.liquid +44 -0
  362. package/dist/templates/waiting-room/src/main.tsx.liquid +48 -0
  363. package/dist/templates/waiting-room/src/theme.ts.liquid +46 -0
  364. package/dist/templating/resource-metadata.d.ts +131 -0
  365. package/dist/types.d.ts +14 -0
  366. package/dist/utils/atomic-write.d.ts +40 -0
  367. package/dist/utils/cli-error.d.ts +64 -0
  368. package/dist/utils/document-parser-client.d.ts +20 -0
  369. package/dist/utils/env-loader.d.ts +40 -0
  370. package/dist/utils/error-codes.d.ts +335 -0
  371. package/dist/utils/errors.d.ts +19 -0
  372. package/dist/utils/global-settings.d.ts +65 -0
  373. package/dist/utils/llm-client.d.ts +34 -0
  374. package/dist/utils/logger.d.ts +36 -0
  375. package/dist/utils/output.d.ts +109 -0
  376. package/dist/utils/platform.d.ts +14 -0
  377. package/dist/utils/prompt.d.ts +36 -0
  378. package/dist/utils/real-path.d.ts +6 -0
  379. package/dist/utils/schema-properties.d.ts +35 -0
  380. package/dist/validation/ajv.d.ts +17 -0
  381. package/dist/validation/facilitator-guide-validator.d.ts +27 -0
  382. package/dist/validation/findings.d.ts +40 -0
  383. package/dist/validation/game-constraints.d.ts +10 -0
  384. package/dist/validation/game-validator.d.ts +34 -0
  385. package/dist/validation/package-validation.d.ts +41 -0
  386. package/dist/validation/package-validator.d.ts +19 -0
  387. package/dist/validation/resource-validator.d.ts +79 -0
  388. package/dist/validation/schema-defaults-validator.d.ts +29 -0
  389. package/dist/validation/schema-validation.d.ts +115 -0
  390. package/dist/validation/semver.d.ts +9 -0
  391. package/package.json +122 -3
@@ -0,0 +1,149 @@
1
+ import { deriveSeed, InitializationFunction, RunFunction, seededRandom } from '@seriousgaming/sdk';
2
+
3
+ import type { Context, Settings } from './types';
4
+
5
+ /**
6
+ * A day at the lemonade stand.
7
+ *
8
+ * The team decides how many of each thing to make before the day starts. Then
9
+ * people turn up. Anything they made and did not sell is poured away, and they
10
+ * paid for it either way; anyone who wanted something they had run out of is
11
+ * turned away, and that is a sale they never see again.
12
+ *
13
+ * The whole simulation is that one sentence. Everything below is arithmetic.
14
+ */
15
+
16
+ /** The weather for a given day: the same day always gets the same weather. */
17
+ function weatherFor(seed: string, teamIndex: number, day: number): Context['weather']['Today'] {
18
+ // Derived rather than drawn. An engine function can be invoked more than
19
+ // once for one logical tick and only the last result is kept, so a live
20
+ // `Math.random()` would give two runs of the same day different weather and
21
+ // an author no way to tell which one the team saw.
22
+ const roll = seededRandom(deriveSeed(seed, teamIndex, 'weather', day))();
23
+ if (roll < 0.4) return 'sunny';
24
+ if (roll < 0.75) return 'cloudy';
25
+ return 'rainy';
26
+ }
27
+
28
+ const pullFor = (weather: Context['weather']['Today'], settings: Settings) =>
29
+ weather === 'sunny'
30
+ ? settings.engine.SunnyPull
31
+ : weather === 'rainy'
32
+ ? settings.engine.RainyPull
33
+ : 1;
34
+
35
+ /**
36
+ * How many people want an item on a cloudy day. The menu and the demand are
37
+ * separate settings, so a scenario can add an item without saying how popular
38
+ * it is; one it says nothing about has no custom, which shows on the forecast
39
+ * straight away rather than being quietly made up.
40
+ */
41
+ const usualDemand = (settings: Settings, id: string) => settings.engine.UsualDemand[id] ?? 0;
42
+
43
+ /** The forecast a team is shown before a day is played. */
44
+ function forecastFor(settings: Settings, weather: Context['weather']['Today']) {
45
+ const pull = pullFor(weather, settings);
46
+ const expected: Record<string, number> = {};
47
+ for (const id of Object.keys(settings.menu.Menu)) {
48
+ expected[id] = Math.round(usualDemand(settings, id) * pull);
49
+ }
50
+ return expected;
51
+ }
52
+
53
+ export const Init: InitializationFunction<Context, Settings> = async ({
54
+ context,
55
+ settings,
56
+ meta,
57
+ }) => {
58
+ const make: Record<string, number> = {};
59
+ for (const id of Object.keys(settings.menu.Menu)) {
60
+ // Somewhere to start from, obviously provisional. Starting at nothing
61
+ // reads as a broken screen; starting at the forecast decides day one
62
+ // for them.
63
+ make[id] = Math.round(usualDemand(settings, id) / 2);
64
+ }
65
+
66
+ const weather = weatherFor(meta.seed, meta.index, 1);
67
+
68
+ return {
69
+ capabilities: {
70
+ ...context,
71
+ plan: { Make: make },
72
+ weather: { Today: weather, Expected: forecastFor(settings, weather) },
73
+ results: {
74
+ Day: 0,
75
+ Weather: weather,
76
+ Lines: {},
77
+ Takings: 0,
78
+ Spent: 0,
79
+ Profit: 0,
80
+ Purse: 0,
81
+ },
82
+ },
83
+ };
84
+ };
85
+
86
+ export const Run: RunFunction<Context, Settings> = async ({ context, settings, meta }) => {
87
+ // `meta.tick` is the tick being executed, so it is today's number
88
+ // directly. A surface reads `completedTicks` instead, which is the number of
89
+ // days already finished, and adds one to name the day in play.
90
+ const day = meta.tick;
91
+ const weather = weatherFor(meta.seed, meta.index, day);
92
+ const pull = pullFor(weather, settings);
93
+
94
+ // One generator for the day's custom, drawn per item in menu order, so the
95
+ // same day always plays out the same way.
96
+ const random = seededRandom(deriveSeed(meta.seed, meta.index, 'custom', day));
97
+
98
+ const lines: Context['results']['Lines'] = {};
99
+ let takings = 0;
100
+ let spent = 0;
101
+
102
+ for (const [id, entry] of sortedMenu(settings)) {
103
+ const made = Math.max(0, Math.round(context.plan.Make[id] ?? 0));
104
+ const swing = 1 + (random() * 2 - 1) * settings.engine.Variation;
105
+ const wanted = Math.max(0, Math.round(usualDemand(settings, id) * pull * swing));
106
+
107
+ const sold = Math.min(made, wanted);
108
+ lines[id] = {
109
+ Made: made,
110
+ Wanted: wanted,
111
+ Sold: sold,
112
+ PouredAway: made - sold,
113
+ TurnedAway: wanted - sold,
114
+ };
115
+
116
+ takings += sold * entry.Price;
117
+ // You paid to make everything, including what you poured away. That is
118
+ // the whole of the lesson about making too much.
119
+ spent += made * entry.Cost;
120
+ }
121
+
122
+ const profit = takings - spent;
123
+ const purse = context.results.Purse + profit;
124
+
125
+ // Tomorrow's forecast, so the team has something to decide on before the
126
+ // next day runs.
127
+ const tomorrow = weatherFor(meta.seed, meta.index, day + 1);
128
+
129
+ return {
130
+ capabilities: {
131
+ ...context,
132
+ weather: { Today: tomorrow, Expected: forecastFor(settings, tomorrow) },
133
+ results: {
134
+ Day: day,
135
+ Weather: weather,
136
+ Lines: lines,
137
+ Takings: takings,
138
+ Spent: spent,
139
+ Profit: profit,
140
+ Purse: purse,
141
+ },
142
+ },
143
+ };
144
+ };
145
+
146
+ /** Menu order, so the day is played and reported in the order it is displayed. */
147
+ function sortedMenu(settings: Settings) {
148
+ return Object.entries(settings.menu.Menu).sort(([, a], [, b]) => a.Order - b.Order);
149
+ }
@@ -0,0 +1,74 @@
1
+ import type { TeamRunMeta } from '@seriousgaming/sdk';
2
+
3
+ import type { Context, Settings } from './types';
4
+
5
+ /**
6
+ * Starting fixtures for the engine's tests.
7
+ *
8
+ * A test spreads what it cares about over these rather than restating the whole
9
+ * context, so adding a field to a capability does not break every test that was
10
+ * never about that field.
11
+ */
12
+ export const baseSettings: Settings = {
13
+ menu: {
14
+ Capacity: 120,
15
+ Menu: {
16
+ lemonade: { Name: 'Lemonade', Order: 1, Cost: 15, Price: 50 },
17
+ icedtea: { Name: 'Iced tea', Order: 2, Cost: 20, Price: 60 },
18
+ cookies: { Name: 'Cookies', Order: 3, Cost: 25, Price: 70 },
19
+ },
20
+ },
21
+ engine: {
22
+ UsualDemand: { lemonade: 55, icedtea: 30, cookies: 20 },
23
+ SunnyPull: 1.3,
24
+ RainyPull: 0.65,
25
+ Variation: 0.2,
26
+ },
27
+ };
28
+
29
+ export const baseContext: Context = {
30
+ plan: { Make: { lemonade: 28, icedtea: 15, cookies: 10 } },
31
+ weather: { Today: 'cloudy', Expected: { lemonade: 55, icedtea: 30, cookies: 20 } },
32
+ results: {
33
+ Day: 0,
34
+ Weather: 'cloudy',
35
+ Lines: {},
36
+ Takings: 0,
37
+ Spent: 0,
38
+ Profit: 0,
39
+ Purse: 0,
40
+ },
41
+ };
42
+
43
+ /**
44
+ * The `meta` the runtime supplies. `seed` is fixed, so the weather and the
45
+ * day's custom are the same every run: this game derives both from it rather
46
+ * than drawing them, which is what makes a test like this possible at all.
47
+ */
48
+ export const baseMeta: TeamRunMeta = {
49
+ tick: 1,
50
+ locale: 'en',
51
+ teamId: 'team-1',
52
+ index: 0,
53
+ seed: 'lemonade-test-seed',
54
+ runKind: 'session',
55
+ now: 0,
56
+ };
57
+
58
+ /**
59
+ * The engine declares no `uses`, so every gated callback throws rather than
60
+ * returning a benign value: a call that reaches one is a declaration bug, and a
61
+ * test that quietly passes hides it.
62
+ *
63
+ * The ungated ones are a different case. They need no declaration, so reaching
64
+ * one is legitimate, and they answer as a session with nothing in it would.
65
+ */
66
+ export const callbacks = {
67
+ generateText: async () => {
68
+ throw new Error('generateText is not declared by this engine');
69
+ },
70
+ readAsset: async () => {
71
+ throw new Error('readAsset reached, but this engine references no assets');
72
+ },
73
+ getPastState: async () => [],
74
+ };
@@ -0,0 +1,139 @@
1
+ // This file is auto-generated from the engine's capability definitions.
2
+ // Do not edit this file directly. It will be regenerated on build.
3
+
4
+ export interface Context {
5
+ plan: {
6
+ /**
7
+ * How many of each menu item to make today, keyed by item id. Keyed rather than a flat set of fields so that two people changing two different items write different keys, and the host's merge keeps both.
8
+ */
9
+ Make: {
10
+ [k: string]: number;
11
+ };
12
+ };
13
+ weather: {
14
+ /**
15
+ * The forecast for the day about to be played
16
+ */
17
+ Today: 'sunny' | 'cloudy' | 'rainy';
18
+ /**
19
+ * Roughly how many of each item people will want today, keyed by item id. A forecast, not a promise: the day lands somewhere near it.
20
+ */
21
+ Expected: {
22
+ [k: string]: number;
23
+ };
24
+ };
25
+ results: {
26
+ /**
27
+ * The day these results are for
28
+ */
29
+ Day: number;
30
+ /**
31
+ * What the weather actually did
32
+ */
33
+ Weather: string;
34
+ /**
35
+ * What happened to each menu item, keyed by item id
36
+ */
37
+ Lines: {
38
+ [k: string]: DayLine;
39
+ };
40
+ /**
41
+ * Money in, in pence
42
+ */
43
+ Takings: number;
44
+ /**
45
+ * Cost of everything made, in pence
46
+ */
47
+ Spent: number;
48
+ /**
49
+ * Takings less spend, in pence
50
+ */
51
+ Profit: number;
52
+ /**
53
+ * Everything made so far, in pence
54
+ */
55
+ Purse: number;
56
+ };
57
+ }
58
+ export interface DayLine {
59
+ Made: number;
60
+ /**
61
+ * How many people asked for one
62
+ */
63
+ Wanted: number;
64
+ Sold: number;
65
+ /**
66
+ * Made and not sold
67
+ */
68
+ PouredAway: number;
69
+ /**
70
+ * Asked for and not available
71
+ */
72
+ TurnedAway: number;
73
+ }
74
+
75
+ export type TeamContext = Pick<Context, 'plan' | 'weather' | 'results'>;
76
+
77
+ export type SharedContext = Record<string, never>;
78
+
79
+ export interface Settings {
80
+ menu: {
81
+ /**
82
+ * How many items the stall can make in a day, across the whole menu
83
+ */
84
+ Capacity: number;
85
+ Menu: Menu;
86
+ };
87
+ engine: {
88
+ UsualDemand: UsualDemand;
89
+ /**
90
+ * How much busier a sunny day is than a cloudy one, as a multiplier on usual demand. 1.3 brings 30% more people
91
+ */
92
+ SunnyPull: number;
93
+ /**
94
+ * How much quieter a rainy day is than a cloudy one, as a multiplier on usual demand. 0.65 brings 35% fewer people, and above 1 makes rain busier
95
+ */
96
+ RainyPull: number;
97
+ /**
98
+ * How far a day can land either side of its forecast, as a fraction. 0.2 lands within 20% of it, and 0 makes every forecast exact
99
+ */
100
+ Variation: number;
101
+ };
102
+ }
103
+ /**
104
+ * What the stall sells, keyed by item id. Money is in pence, so the sums stay in whole numbers. Read by the engine and every surface, so the price on screen and the price in the sums are the same number.
105
+ */
106
+ export interface Menu {
107
+ [k: string]: MenuItem;
108
+ }
109
+ export interface MenuItem {
110
+ /**
111
+ * What the item is called on every screen
112
+ */
113
+ Name: string;
114
+ /**
115
+ * Where it sits on the menu, so every screen lists it in the same order. 1 comes first
116
+ */
117
+ Order: number;
118
+ /**
119
+ * Cost to make one, in pence
120
+ */
121
+ Cost: number;
122
+ /**
123
+ * Sale price, in pence
124
+ */
125
+ Price: number;
126
+ }
127
+ /**
128
+ * How many people want each menu item on an ordinary cloudy day, keyed by item id. Kept apart from the menu because only the engine reads it: a surface that could see it would be showing the answer. An item on the menu with no entry here has no custom at all, so its forecast reads 0 and nobody buys one.
129
+ */
130
+ export interface UsualDemand {
131
+ /**
132
+ * People a day who want this item when it is cloudy
133
+ */
134
+ [k: string]: number;
135
+ }
136
+
137
+ import type { LogicCallbacks } from '@seriousgaming/sdk';
138
+
139
+ export type Callbacks = LogicCallbacks;
@@ -0,0 +1,51 @@
1
+ # Lemonade Stand at a glance
2
+
3
+ Teams run a lemonade stand for ten days. Each morning they decide how many of each thing to make. Then the day happens.
4
+
5
+ :::timing
6
+ Total session length: about 50 minutes.
7
+ :::
8
+
9
+ | Phase | Duration | Facilitator focus |
10
+ | -------- | ---------- | ------------------------------------------------------- |
11
+ | Briefing | 5 minutes | Hand out the stalls and read the one rule |
12
+ | Play | 30 minutes | Walk the room while stalls run their own days |
13
+ | Debrief | 15 minutes | Connect the two kinds of mistake to the learning points |
14
+
15
+ ## The one rule
16
+
17
+ Whatever they make in the morning, they have paid for. Anything left at the end of the day is poured away. Anyone who wanted something they had run out of goes home thirsty, and that is a sale they never get back.
18
+
19
+ That is the whole simulation. There is nothing else to explain.
20
+
21
+ ## The menu
22
+
23
+ | Item | Costs to make | Sells for | On an ordinary day |
24
+ | -------- | ------------- | --------- | ------------------------ |
25
+ | Lemonade | 15p | 50p | About 55 people want one |
26
+ | Iced tea | 20p | 60p | About 30 |
27
+ | Cookies | 25p | 70p | About 20 |
28
+
29
+ The stall can make 120 things in a day, all told. On a sunny day people want more than that, so a team has to choose what to fill the stall with. Cookies earn the most per item and the fewest people want one.
30
+
31
+ ## What makes it interesting
32
+
33
+ The forecast tells them roughly what to expect, and the day lands somewhere near it rather than on it. So there is no number that is simply right, and a team that makes exactly the forecast every day still has days it gets wrong.
34
+
35
+ :::note
36
+ The two mistakes do not feel the same. Pouring lemonade away is visible and annoying; turning people away costs more and leaves nothing behind to look at. Most teams over-correct towards caution after one bad day of waste, and stop noticing the queue they are losing.
37
+ :::
38
+
39
+ ## Two versions
40
+
41
+ | Scenario | When to use it |
42
+ | ---------------- | --------------------------------------------------------------------------------------------------------------------- |
43
+ | An ordinary week | Ten days of mixed weather. The one to run first |
44
+ | Heatwave | Five hot days. Demand is always past what the stall can make, so every day forces a choice about what to fill it with |
45
+
46
+ Pick one when you create the session.
47
+
48
+ ## Materials
49
+
50
+ - One laptop or tablet per stall, plus one for you
51
+ - This guide, printed or on a second screen
@@ -0,0 +1,35 @@
1
+ # Preparation
2
+
3
+ ## Before the room arrives
4
+
5
+ - Create the session and pick a scenario. An ordinary week is ten days of mixed weather; Heatwave is five hot ones, and every day of it forces a choice. The waiting room opens on its own, so the first person through the door lands on a holding screen rather than in day one
6
+ - Add one stall per group of two to five people. Stalls are independent, so you can add one at any point if a late group arrives
7
+ - Open the session surface on your own screen and leave it there. It is where you can see every stall at once, and where the Observe and Join buttons are
8
+
9
+ :::tip
10
+ Groups name their own stall from the waiting room. Ask them to do it while you finish the briefing, and your screen stops being a list of 'Team 1' through 'Team 6'.
11
+ :::
12
+
13
+ ## Briefing the room
14
+
15
+ :::say
16
+ You are running a lemonade stand. Every morning you decide how much lemonade, iced tea and cookies to make. You pay for everything you make. Anything you do not sell gets poured away at the end of the day. Anyone who wants something you have run out of just leaves. When your stall is ready, you open it yourselves and the day happens. Ten days. Most money in the jar wins.
17
+ :::
18
+
19
+ :::do
20
+ Do not explain the weather, the capacity, or the margins. All three are on the screen, and a stall that discovers the cookies earn the most has learned something a stall that was told has not.
21
+ :::
22
+
23
+ ## Who does what
24
+
25
+ As people join, the game puts each of them in charge of one thing on the menu. A stall of three gets one person each on lemonade, iced tea and cookies; a stall of five gets two people on the lemonade.
26
+
27
+ Nobody is stopped from changing somebody else's item. The point of handing them out is that in ordinary play three people work on three different things at once and nothing gets in anyone's way.
28
+
29
+ :::note
30
+ There is one thing they share: the stall can only make 120 things a day. If two people each add to their own item at the same moment and the total goes over, the second change is refused and says so. It is worth watching for: a team that hits it once usually starts talking to each other properly.
31
+ :::
32
+
33
+ ## Opening up
34
+
35
+ Close the waiting room when every stall is named and settled. Every player's screen swaps to the game without a reload, and nothing about the session changes, so you can reopen it mid-session for a discussion and pick up exactly where the stalls were.
@@ -0,0 +1,58 @@
1
+ # Running the day
2
+
3
+ ## The shape of a day
4
+
5
+ 1. The stall reads the forecast and sets its numbers
6
+ 2. The stall presses Open the stall, and the day plays out
7
+ 3. The stall reads what happened and changes its mind about tomorrow
8
+
9
+ **The stalls run their own days.** Each one moves at its own pace, and nobody waits for the room. You are not driving the session, which means you are free to walk around it.
10
+
11
+ :::do
12
+ The Today column on your screen says which stalls have changed their numbers since the day they last traded. There is no 'ready' button anywhere, so this is what tells you who is thinking and who has stopped. A stall showing 'Same as yesterday' has either decided to repeat itself or forgotten to look.
13
+ :::
14
+
15
+ :::note
16
+ A fast group will be on day six while a slow one is on day two. That is fine, and it is worth saying so at the briefing, or the fast group will think they have broken something. Pull the room together for the debrief rather than trying to keep it in step.
17
+ :::
18
+
19
+ ## Getting into a stall
20
+
21
+ Every row has two ways in.
22
+
23
+ - **Observe** opens that stall's screen exactly as they see it, without joining. Nobody appears on their roster and nothing changes. This is the one to use when you are checking on a quiet group
24
+ - **Join** puts you on the stall as a player, which is what you want when you are showing somebody how something works
25
+ - **Copy link** gives you the join link to hand out, for a latecomer or a slide
26
+
27
+ **Undo** is the one thing that only you can do. If a stall opens up before it meant to, take the day back and let them try again.
28
+
29
+ ## What to listen for
30
+
31
+ | Day | What usually happens | Worth asking |
32
+ | ------- | -------------------------------------------------------------- | --------------------------------------------------- |
33
+ | 1 | Stalls make roughly the forecast, or roughly nothing | 'How much of that did you actually sell?' |
34
+ | 2 to 3 | A sunny day catches them out and they hit the capacity limit | 'You could not make everything. What did you drop?' |
35
+ | 4 to 6 | They over-correct after a day of pouring lemonade away | 'How many people went home without one?' |
36
+ | 7 to 10 | The gap between the stalls that kept adjusting becomes obvious | 'When did you last change your numbers, and why?' |
37
+
38
+ ## Two things participants ask
39
+
40
+ **'Why did my change get refused?'** Somebody else on the stall was changing their own item at the same moment, and together the two changes asked for more than the stall can make. The screen says so. They take some off something and try again.
41
+
42
+ **'Can I change somebody else's item?'** Yes. The game hands out the menu so that three people can work at once without treading on each other, not to stop anyone.
43
+
44
+ **'Somebody opened the stall before I was ready.'** Anyone on the stall can open it, and their change counts for the day that just ran. If a number was still being moved when it happened, the screen says the change counts for the next day instead. Ask them to sort out who says go.
45
+
46
+ ## Debrief
47
+
48
+ The mechanical answer is that you cannot know the day in advance, so you are choosing which mistake to make.
49
+
50
+ The conversation worth having is underneath that.
51
+
52
+ - Which mistake did your stall prefer, and did you ever say so out loud?
53
+ - Pouring lemonade away is right there on the screen. The people who went home are a number you have to go looking for. Which one drove your decisions?
54
+ - What did the capacity limit make you give up, and was it the right thing?
55
+
56
+ :::note
57
+ If nobody raises it, show a stall the Turned away column for a good day and a bad one side by side. Most teams have never added it up.
58
+ :::
@@ -0,0 +1,10 @@
1
+ {
2
+ "definition": {
3
+ "locales": {
4
+ "en": {
5
+ "name": "Facilitator guide"
6
+ }
7
+ }
8
+ },
9
+ "rootDirectory": "."
10
+ }
@@ -0,0 +1,108 @@
1
+ // @vitest-environment node
2
+
3
+ /**
4
+ * Structural tests for the facilitator guide.
5
+ *
6
+ * A guide is content, not code, so these guard the contract the host relies on
7
+ * rather than anything it says: it sits where the package will find it, every
8
+ * declared locale carries the same chapters, every admonition is one the
9
+ * renderer knows, and every image reference resolves. `pnpm run sdk build --
10
+ * --validate-only` runs the same checks at build time; this keeps them in the
11
+ * package's own test loop.
12
+ */
13
+
14
+ import { existsSync, readdirSync, readFileSync } from 'fs';
15
+ import { dirname, join, resolve } from 'path';
16
+ import { fileURLToPath } from 'url';
17
+
18
+ import { describe, expect, it } from 'vitest';
19
+
20
+ const GUIDE_DIR = fileURLToPath(new URL('.', import.meta.url));
21
+ const PACKAGE_DIR = resolve(GUIDE_DIR, '..');
22
+
23
+ /** The six the renderer defines. Anything else renders as an inert error block. */
24
+ const ADMONITIONS = ['note', 'warning', 'tip', 'say', 'do', 'timing'];
25
+
26
+ const bundle = JSON.parse(readFileSync(join(GUIDE_DIR, 'guide.sdk.json'), 'utf-8'));
27
+ const declaredLocales: string[] = Object.keys(bundle.definition.locales);
28
+
29
+ const chapters = (locale: string) =>
30
+ readdirSync(join(GUIDE_DIR, 'content', locale))
31
+ .filter((name) => name.endsWith('.md'))
32
+ .sort();
33
+
34
+ const chapterFiles = () =>
35
+ declaredLocales.flatMap((locale) =>
36
+ chapters(locale).map((file) => ({
37
+ locale,
38
+ file,
39
+ content: readFileSync(join(GUIDE_DIR, 'content', locale, file), 'utf-8'),
40
+ }))
41
+ );
42
+
43
+ describe('the bundle', () => {
44
+ it('sits in the slot the package scans', () => {
45
+ expect(existsSync(join(PACKAGE_DIR, 'guide', 'guide.sdk.json'))).toBe(true);
46
+ });
47
+
48
+ it('carries no identifier of its own, because the slot names it', () => {
49
+ expect(bundle.definition.identifier).toBeUndefined();
50
+ });
51
+
52
+ it('names itself in every declared locale', () => {
53
+ expect(declaredLocales.length).toBeGreaterThan(0);
54
+ for (const locale of declaredLocales) {
55
+ expect(bundle.definition.locales[locale].name).toBeTruthy();
56
+ }
57
+ });
58
+ });
59
+
60
+ describe('the content', () => {
61
+ it('mirrors the same chapter file set across every declared locale', () => {
62
+ const [reference, ...rest] = declaredLocales;
63
+ const referenceChapters = chapters(reference as string);
64
+ expect(referenceChapters.length).toBeGreaterThan(0);
65
+ for (const locale of rest) {
66
+ expect(chapters(locale)).toEqual(referenceChapters);
67
+ }
68
+ });
69
+
70
+ it('opens every chapter with a heading, which is what the contents list', () => {
71
+ for (const { locale, file, content } of chapterFiles()) {
72
+ expect(content.startsWith('# '), `${locale}/${file}`).toBe(true);
73
+ }
74
+ });
75
+
76
+ it('uses only admonitions the renderer defines', () => {
77
+ for (const { locale, file, content } of chapterFiles()) {
78
+ for (const match of content.matchAll(/^:::(\w+)/gm)) {
79
+ expect(ADMONITIONS, `${locale}/${file}`).toContain(match[1]);
80
+ }
81
+ }
82
+ });
83
+
84
+ it('closes every admonition it opens', () => {
85
+ for (const { locale, file, content } of chapterFiles()) {
86
+ const markers = content.match(/^:::/gm) ?? [];
87
+ expect(markers.length % 2, `${locale}/${file}`).toBe(0);
88
+ }
89
+ });
90
+
91
+ it('references only images that exist inside the guide directory', () => {
92
+ for (const { locale, file, content } of chapterFiles()) {
93
+ for (const match of content.matchAll(/!\[[^\]]*\]\(([^()\s]+)\)/g)) {
94
+ const target = match[1] as string;
95
+ if (/^[a-z][a-z0-9+.-]*:/i.test(target) || target.startsWith('/')) continue;
96
+ const resolved = resolve(dirname(join(GUIDE_DIR, 'content', locale, file)), target);
97
+ expect(
98
+ resolved.startsWith(resolve(GUIDE_DIR)),
99
+ `${locale}/${file}: ${target} escapes the guide directory`
100
+ ).toBe(true);
101
+ expect(
102
+ existsSync(resolved),
103
+ `${locale}/${file}: ${target} does not resolve to a file`
104
+ ).toBe(true);
105
+ }
106
+ }
107
+ });
108
+ });
@@ -0,0 +1,37 @@
1
+ // This file is auto-generated from the schemas the resource declares: the values
2
+ // a session starts with, for use as test fixtures.
3
+ // Do not edit this file directly. It will be regenerated on build.
4
+
5
+ import type { Context, Settings } from './types';
6
+
7
+ export const defaultContext: Context = {
8
+ roles: {
9
+ LooksAfter: {},
10
+ },
11
+ };
12
+
13
+ export const defaultSettings: Settings = {
14
+ menu: {
15
+ Capacity: 120,
16
+ Menu: {
17
+ lemonade: {
18
+ Name: 'Lemonade',
19
+ Order: 1,
20
+ Cost: 15,
21
+ Price: 50,
22
+ },
23
+ icedtea: {
24
+ Name: 'Iced tea',
25
+ Order: 2,
26
+ Cost: 20,
27
+ Price: 60,
28
+ },
29
+ cookies: {
30
+ Name: 'Cookies',
31
+ Order: 3,
32
+ Cost: 25,
33
+ Price: 70,
34
+ },
35
+ },
36
+ },
37
+ };
@@ -0,0 +1,13 @@
1
+ {
2
+ "definition": {
3
+ "signal": "participantJoin",
4
+ "reads": ["roles"],
5
+ "writes": ["roles"],
6
+ "settings": ["menu"]
7
+ },
8
+ "logic": {
9
+ "directory": "logic",
10
+ "entrypoint": "logic.ts"
11
+ },
12
+ "rootDirectory": "."
13
+ }