sci2-ts 0.0.0-stage → 0.2.2

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 (357) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/LICENSE +21 -0
  3. package/README.md +113 -2
  4. package/apps/viewer/editor.html +43 -0
  5. package/apps/viewer/explore.html +27 -0
  6. package/apps/viewer/index.html +26 -0
  7. package/apps/viewer/package.json +27 -0
  8. package/apps/viewer/play.html +26 -0
  9. package/apps/viewer/plugin.d.ts +18 -0
  10. package/apps/viewer/plugin.js +75 -0
  11. package/apps/viewer/plugin.ts +84 -0
  12. package/apps/viewer/src/adlib.d.ts +20 -0
  13. package/apps/viewer/src/adlib.js +55 -0
  14. package/apps/viewer/src/adlib.ts +62 -0
  15. package/apps/viewer/src/app.d.ts +5 -0
  16. package/apps/viewer/src/app.js +5 -0
  17. package/apps/viewer/src/app.ts +5 -0
  18. package/apps/viewer/src/asset-index.d.ts +22 -0
  19. package/apps/viewer/src/asset-index.js +74 -0
  20. package/apps/viewer/src/asset-index.ts +90 -0
  21. package/apps/viewer/src/audio.d.ts +28 -0
  22. package/apps/viewer/src/audio.js +102 -0
  23. package/apps/viewer/src/audio.ts +110 -0
  24. package/apps/viewer/src/editor-handles.d.ts +63 -0
  25. package/apps/viewer/src/editor-handles.js +332 -0
  26. package/apps/viewer/src/editor-handles.ts +368 -0
  27. package/apps/viewer/src/editor-languages.d.ts +23 -0
  28. package/apps/viewer/src/editor-languages.js +157 -0
  29. package/apps/viewer/src/editor-languages.ts +135 -0
  30. package/apps/viewer/src/editor.css +213 -0
  31. package/apps/viewer/src/editor.d.ts +1 -0
  32. package/apps/viewer/src/editor.js +393 -0
  33. package/apps/viewer/src/editor.ts +412 -0
  34. package/apps/viewer/src/explore/classes.d.ts +7 -0
  35. package/apps/viewer/src/explore/classes.js +63 -0
  36. package/apps/viewer/src/explore/classes.ts +61 -0
  37. package/apps/viewer/src/explore/context.d.ts +33 -0
  38. package/apps/viewer/src/explore/context.js +96 -0
  39. package/apps/viewer/src/explore/context.ts +124 -0
  40. package/apps/viewer/src/explore/explore.css +93 -0
  41. package/apps/viewer/src/explore/explore.d.ts +1 -0
  42. package/apps/viewer/src/explore/explore.js +102 -0
  43. package/apps/viewer/src/explore/explore.ts +104 -0
  44. package/apps/viewer/src/explore/inspect.d.ts +5 -0
  45. package/apps/viewer/src/explore/inspect.js +208 -0
  46. package/apps/viewer/src/explore/inspect.ts +279 -0
  47. package/apps/viewer/src/explore/live.d.ts +22 -0
  48. package/apps/viewer/src/explore/live.js +318 -0
  49. package/apps/viewer/src/explore/live.ts +365 -0
  50. package/apps/viewer/src/explore/map.d.ts +11 -0
  51. package/apps/viewer/src/explore/map.js +182 -0
  52. package/apps/viewer/src/explore/map.ts +193 -0
  53. package/apps/viewer/src/files.d.ts +21 -0
  54. package/apps/viewer/src/files.js +69 -0
  55. package/apps/viewer/src/files.ts +72 -0
  56. package/apps/viewer/src/game-profile.d.ts +19 -0
  57. package/apps/viewer/src/game-profile.js +4 -0
  58. package/apps/viewer/src/game-profile.ts +26 -0
  59. package/apps/viewer/src/main.d.ts +1 -0
  60. package/apps/viewer/src/main.js +473 -0
  61. package/apps/viewer/src/main.ts +527 -0
  62. package/apps/viewer/src/music.d.ts +14 -0
  63. package/apps/viewer/src/music.js +32 -0
  64. package/apps/viewer/src/music.ts +34 -0
  65. package/apps/viewer/src/opl-worklet.d.ts +1 -0
  66. package/apps/viewer/src/opl-worklet.js +47 -0
  67. package/apps/viewer/src/opl-worklet.ts +55 -0
  68. package/apps/viewer/src/overlay.d.ts +31 -0
  69. package/apps/viewer/src/overlay.js +103 -0
  70. package/apps/viewer/src/overlay.ts +123 -0
  71. package/apps/viewer/src/play.css +14 -0
  72. package/apps/viewer/src/play.d.ts +1 -0
  73. package/apps/viewer/src/play.js +179 -0
  74. package/apps/viewer/src/play.ts +177 -0
  75. package/apps/viewer/src/saves.d.ts +10 -0
  76. package/apps/viewer/src/saves.js +50 -0
  77. package/apps/viewer/src/saves.ts +51 -0
  78. package/apps/viewer/src/session.d.ts +91 -0
  79. package/apps/viewer/src/session.js +301 -0
  80. package/apps/viewer/src/session.ts +321 -0
  81. package/apps/viewer/src/sound-preview.d.ts +7 -0
  82. package/apps/viewer/src/sound-preview.js +214 -0
  83. package/apps/viewer/src/sound-preview.ts +228 -0
  84. package/apps/viewer/src/style.css +64 -0
  85. package/apps/viewer/vite.config.ts +25 -0
  86. package/bin/sci-ts.mjs +9 -0
  87. package/lib/994.sc +49 -0
  88. package/lib/995.sc +53 -0
  89. package/lib/996.sc +299 -0
  90. package/lib/997.sc +198 -0
  91. package/lib/998.sc +338 -0
  92. package/lib/999.sc +189 -0
  93. package/lib/resources.d.ts +2 -0
  94. package/lib/resources.js +84 -0
  95. package/lib/resources.ts +88 -0
  96. package/lib/system.sh +62 -0
  97. package/package.json +75 -4
  98. package/packages/content/room.schema.json +267 -0
  99. package/packages/content/src/index.d.ts +3 -0
  100. package/packages/content/src/index.js +3 -0
  101. package/packages/content/src/index.ts +3 -0
  102. package/packages/content/src/room.d.ts +111 -0
  103. package/packages/content/src/room.js +886 -0
  104. package/packages/content/src/room.ts +1011 -0
  105. package/packages/content/src/target.d.ts +80 -0
  106. package/packages/content/src/target.js +1 -0
  107. package/packages/content/src/target.ts +60 -0
  108. package/packages/content/src/yarn.d.ts +90 -0
  109. package/packages/content/src/yarn.js +214 -0
  110. package/packages/content/src/yarn.ts +261 -0
  111. package/packages/sci/src/analysis/game.d.ts +90 -0
  112. package/packages/sci/src/analysis/game.js +221 -0
  113. package/packages/sci/src/analysis/game.ts +305 -0
  114. package/packages/sci/src/analysis/runtime.d.ts +58 -0
  115. package/packages/sci/src/analysis/runtime.js +133 -0
  116. package/packages/sci/src/analysis/runtime.ts +163 -0
  117. package/packages/sci/src/audio/adlib.d.ts +73 -0
  118. package/packages/sci/src/audio/adlib.js +280 -0
  119. package/packages/sci/src/audio/adlib.ts +329 -0
  120. package/packages/sci/src/audio/index.d.ts +43 -0
  121. package/packages/sci/src/audio/index.js +99 -0
  122. package/packages/sci/src/audio/index.ts +122 -0
  123. package/packages/sci/src/audio/midi.d.ts +92 -0
  124. package/packages/sci/src/audio/midi.js +242 -0
  125. package/packages/sci/src/audio/midi.ts +262 -0
  126. package/packages/sci/src/audio/opl2.d.ts +74 -0
  127. package/packages/sci/src/audio/opl2.js +283 -0
  128. package/packages/sci/src/audio/opl2.ts +283 -0
  129. package/packages/sci/src/audio/sol.d.ts +32 -0
  130. package/packages/sci/src/audio/sol.js +81 -0
  131. package/packages/sci/src/audio/sol.ts +100 -0
  132. package/packages/sci/src/audio/write.d.ts +35 -0
  133. package/packages/sci/src/audio/write.js +91 -0
  134. package/packages/sci/src/audio/write.ts +108 -0
  135. package/packages/sci/src/gfx/cel.d.ts +36 -0
  136. package/packages/sci/src/gfx/cel.js +82 -0
  137. package/packages/sci/src/gfx/cel.ts +110 -0
  138. package/packages/sci/src/gfx/palette.d.ts +24 -0
  139. package/packages/sci/src/gfx/palette.js +49 -0
  140. package/packages/sci/src/gfx/palette.ts +56 -0
  141. package/packages/sci/src/gfx/pic-file.d.ts +46 -0
  142. package/packages/sci/src/gfx/pic-file.js +114 -0
  143. package/packages/sci/src/gfx/pic-file.ts +155 -0
  144. package/packages/sci/src/gfx/pic.d.ts +27 -0
  145. package/packages/sci/src/gfx/pic.js +34 -0
  146. package/packages/sci/src/gfx/pic.ts +53 -0
  147. package/packages/sci/src/gfx/quantize.d.ts +42 -0
  148. package/packages/sci/src/gfx/quantize.js +199 -0
  149. package/packages/sci/src/gfx/quantize.ts +218 -0
  150. package/packages/sci/src/gfx/view-file.d.ts +62 -0
  151. package/packages/sci/src/gfx/view-file.js +182 -0
  152. package/packages/sci/src/gfx/view-file.ts +233 -0
  153. package/packages/sci/src/gfx/view.d.ts +28 -0
  154. package/packages/sci/src/gfx/view.js +45 -0
  155. package/packages/sci/src/gfx/view.ts +62 -0
  156. package/packages/sci/src/index.d.ts +38 -0
  157. package/packages/sci/src/index.js +38 -0
  158. package/packages/sci/src/index.ts +38 -0
  159. package/packages/sci/src/motion/pathfinding.d.ts +40 -0
  160. package/packages/sci/src/motion/pathfinding.js +140 -0
  161. package/packages/sci/src/motion/pathfinding.ts +147 -0
  162. package/packages/sci/src/resource/archive.d.ts +16 -0
  163. package/packages/sci/src/resource/archive.js +51 -0
  164. package/packages/sci/src/resource/archive.ts +56 -0
  165. package/packages/sci/src/resource/lzs.d.ts +12 -0
  166. package/packages/sci/src/resource/lzs.js +64 -0
  167. package/packages/sci/src/resource/lzs.ts +66 -0
  168. package/packages/sci/src/resource/manager.d.ts +45 -0
  169. package/packages/sci/src/resource/manager.js +158 -0
  170. package/packages/sci/src/resource/manager.ts +184 -0
  171. package/packages/sci/src/resource/map.d.ts +18 -0
  172. package/packages/sci/src/resource/map.js +34 -0
  173. package/packages/sci/src/resource/map.ts +44 -0
  174. package/packages/sci/src/resource/types.d.ts +37 -0
  175. package/packages/sci/src/resource/types.js +45 -0
  176. package/packages/sci/src/resource/types.ts +57 -0
  177. package/packages/sci/src/script/assembly.d.ts +67 -0
  178. package/packages/sci/src/script/assembly.js +748 -0
  179. package/packages/sci/src/script/assembly.ts +714 -0
  180. package/packages/sci/src/script/compiler.d.ts +75 -0
  181. package/packages/sci/src/script/compiler.js +993 -0
  182. package/packages/sci/src/script/compiler.ts +1040 -0
  183. package/packages/sci/src/script/disasm.d.ts +81 -0
  184. package/packages/sci/src/script/disasm.js +318 -0
  185. package/packages/sci/src/script/disasm.ts +323 -0
  186. package/packages/sci/src/script/kernel-names.d.ts +6 -0
  187. package/packages/sci/src/script/kernel-names.js +165 -0
  188. package/packages/sci/src/script/kernel-names.ts +165 -0
  189. package/packages/sci/src/script/opcodes.d.ts +22 -0
  190. package/packages/sci/src/script/opcodes.js +45 -0
  191. package/packages/sci/src/script/opcodes.ts +72 -0
  192. package/packages/sci/src/script/script.d.ts +56 -0
  193. package/packages/sci/src/script/script.js +63 -0
  194. package/packages/sci/src/script/script.ts +111 -0
  195. package/packages/sci/src/script/sexpr.d.ts +45 -0
  196. package/packages/sci/src/script/sexpr.js +151 -0
  197. package/packages/sci/src/script/sexpr.ts +139 -0
  198. package/packages/sci/src/script/vocab.d.ts +15 -0
  199. package/packages/sci/src/script/vocab.js +44 -0
  200. package/packages/sci/src/script/vocab.ts +48 -0
  201. package/packages/sci/src/script/world.d.ts +25 -0
  202. package/packages/sci/src/script/world.js +65 -0
  203. package/packages/sci/src/script/world.ts +70 -0
  204. package/packages/sci/src/text/font.d.ts +37 -0
  205. package/packages/sci/src/text/font.js +103 -0
  206. package/packages/sci/src/text/font.ts +125 -0
  207. package/packages/sci/src/text/message.d.ts +69 -0
  208. package/packages/sci/src/text/message.js +171 -0
  209. package/packages/sci/src/text/message.ts +205 -0
  210. package/packages/sci/src/vm/kernels/arrays.d.ts +19 -0
  211. package/packages/sci/src/vm/kernels/arrays.js +182 -0
  212. package/packages/sci/src/vm/kernels/arrays.ts +164 -0
  213. package/packages/sci/src/vm/kernels/audio.d.ts +70 -0
  214. package/packages/sci/src/vm/kernels/audio.js +347 -0
  215. package/packages/sci/src/vm/kernels/audio.ts +349 -0
  216. package/packages/sci/src/vm/kernels/core.d.ts +5 -0
  217. package/packages/sci/src/vm/kernels/core.js +87 -0
  218. package/packages/sci/src/vm/kernels/core.ts +91 -0
  219. package/packages/sci/src/vm/kernels/graphics.d.ts +141 -0
  220. package/packages/sci/src/vm/kernels/graphics.js +467 -0
  221. package/packages/sci/src/vm/kernels/graphics.ts +502 -0
  222. package/packages/sci/src/vm/kernels/index.d.ts +8 -0
  223. package/packages/sci/src/vm/kernels/index.js +29 -0
  224. package/packages/sci/src/vm/kernels/index.ts +32 -0
  225. package/packages/sci/src/vm/kernels/lists.d.ts +6 -0
  226. package/packages/sci/src/vm/kernels/lists.js +127 -0
  227. package/packages/sci/src/vm/kernels/lists.ts +111 -0
  228. package/packages/sci/src/vm/kernels/motion.d.ts +7 -0
  229. package/packages/sci/src/vm/kernels/motion.js +204 -0
  230. package/packages/sci/src/vm/kernels/motion.ts +194 -0
  231. package/packages/sci/src/vm/kernels/palette.d.ts +82 -0
  232. package/packages/sci/src/vm/kernels/palette.js +292 -0
  233. package/packages/sci/src/vm/kernels/palette.ts +303 -0
  234. package/packages/sci/src/vm/kernels/saves.d.ts +45 -0
  235. package/packages/sci/src/vm/kernels/saves.js +123 -0
  236. package/packages/sci/src/vm/kernels/saves.ts +149 -0
  237. package/packages/sci/src/vm/kernels/system.d.ts +62 -0
  238. package/packages/sci/src/vm/kernels/system.js +110 -0
  239. package/packages/sci/src/vm/kernels/system.ts +128 -0
  240. package/packages/sci/src/vm/kernels/text.d.ts +13 -0
  241. package/packages/sci/src/vm/kernels/text.js +338 -0
  242. package/packages/sci/src/vm/kernels/text.ts +328 -0
  243. package/packages/sci/src/vm/memory.d.ts +138 -0
  244. package/packages/sci/src/vm/memory.js +168 -0
  245. package/packages/sci/src/vm/memory.ts +266 -0
  246. package/packages/sci/src/vm/replace.d.ts +11 -0
  247. package/packages/sci/src/vm/replace.js +14 -0
  248. package/packages/sci/src/vm/replace.ts +16 -0
  249. package/packages/sci/src/vm/savegame.d.ts +89 -0
  250. package/packages/sci/src/vm/savegame.js +161 -0
  251. package/packages/sci/src/vm/savegame.ts +188 -0
  252. package/packages/sci/src/vm/value.d.ts +17 -0
  253. package/packages/sci/src/vm/value.js +12 -0
  254. package/packages/sci/src/vm/value.ts +25 -0
  255. package/packages/sci/src/vm/vm.d.ts +155 -0
  256. package/packages/sci/src/vm/vm.js +1053 -0
  257. package/packages/sci/src/vm/vm.ts +914 -0
  258. package/tools/adlib-render.d.ts +1 -0
  259. package/tools/adlib-render.js +49 -0
  260. package/tools/adlib-render.ts +42 -0
  261. package/tools/analyze.d.ts +1 -0
  262. package/tools/analyze.js +25 -0
  263. package/tools/analyze.ts +25 -0
  264. package/tools/art/build.d.ts +34 -0
  265. package/tools/art/build.js +175 -0
  266. package/tools/art/build.ts +153 -0
  267. package/tools/art.d.ts +1 -0
  268. package/tools/art.js +31 -0
  269. package/tools/art.ts +29 -0
  270. package/tools/asm.d.ts +1 -0
  271. package/tools/asm.js +61 -0
  272. package/tools/asm.ts +55 -0
  273. package/tools/cli.d.ts +1 -0
  274. package/tools/cli.js +99 -0
  275. package/tools/cli.ts +100 -0
  276. package/tools/editor-server.d.ts +29 -0
  277. package/tools/editor-server.js +162 -0
  278. package/tools/editor-server.ts +176 -0
  279. package/tools/explore.d.ts +1 -0
  280. package/tools/explore.js +305 -0
  281. package/tools/explore.ts +250 -0
  282. package/tools/game/audio.d.ts +19 -0
  283. package/tools/game/audio.js +143 -0
  284. package/tools/game/audio.ts +130 -0
  285. package/tools/game/build.d.ts +46 -0
  286. package/tools/game/build.js +259 -0
  287. package/tools/game/build.ts +283 -0
  288. package/tools/game/defaults.d.ts +26 -0
  289. package/tools/game/defaults.js +82 -0
  290. package/tools/game/defaults.ts +83 -0
  291. package/tools/game/font.d.ts +3 -0
  292. package/tools/game/font.js +124 -0
  293. package/tools/game/font.ts +126 -0
  294. package/tools/game/kit.d.ts +7 -0
  295. package/tools/game/kit.js +7 -0
  296. package/tools/game/kit.ts +10 -0
  297. package/tools/game/new.d.ts +8 -0
  298. package/tools/game/new.js +39 -0
  299. package/tools/game/new.ts +38 -0
  300. package/tools/game/target.d.ts +7 -0
  301. package/tools/game/target.js +41 -0
  302. package/tools/game/target.ts +42 -0
  303. package/tools/game/template/resources.ts.tmpl +43 -0
  304. package/tools/game/template/rooms/1.room.yaml +9 -0
  305. package/tools/game/template/rooms/1.yarn +17 -0
  306. package/tools/game/template/scripts/0.sc +24 -0
  307. package/tools/game.d.ts +1 -0
  308. package/tools/game.js +77 -0
  309. package/tools/game.ts +70 -0
  310. package/tools/mod-build.d.ts +28 -0
  311. package/tools/mod-build.js +173 -0
  312. package/tools/mod-build.ts +179 -0
  313. package/tools/mod-target.d.ts +16 -0
  314. package/tools/mod-target.js +26 -0
  315. package/tools/mod-target.ts +32 -0
  316. package/tools/mod.d.ts +1 -0
  317. package/tools/mod.js +47 -0
  318. package/tools/mod.ts +45 -0
  319. package/tools/msg.d.ts +1 -0
  320. package/tools/msg.js +40 -0
  321. package/tools/msg.ts +36 -0
  322. package/tools/node-files.d.ts +3 -0
  323. package/tools/node-files.js +34 -0
  324. package/tools/node-files.ts +33 -0
  325. package/tools/patches.d.ts +3 -0
  326. package/tools/patches.js +13 -0
  327. package/tools/patches.ts +13 -0
  328. package/tools/pic-build.d.ts +9 -0
  329. package/tools/pic-build.js +26 -0
  330. package/tools/pic-build.ts +27 -0
  331. package/tools/pic.d.ts +1 -0
  332. package/tools/pic.js +64 -0
  333. package/tools/pic.ts +61 -0
  334. package/tools/play.d.ts +1 -0
  335. package/tools/play.js +54 -0
  336. package/tools/play.ts +54 -0
  337. package/tools/png.d.ts +16 -0
  338. package/tools/png.js +124 -0
  339. package/tools/png.ts +123 -0
  340. package/tools/res.d.ts +1 -0
  341. package/tools/res.js +48 -0
  342. package/tools/res.ts +50 -0
  343. package/tools/scenario.d.ts +1 -0
  344. package/tools/scenario.js +74 -0
  345. package/tools/scenario.ts +80 -0
  346. package/tools/site/build.d.ts +8 -0
  347. package/tools/site/build.js +110 -0
  348. package/tools/site/build.ts +116 -0
  349. package/tools/site/markdown.d.ts +11 -0
  350. package/tools/site/markdown.js +88 -0
  351. package/tools/site/markdown.ts +84 -0
  352. package/tools/view-build.d.ts +34 -0
  353. package/tools/view-build.js +47 -0
  354. package/tools/view-build.ts +71 -0
  355. package/tools/view.d.ts +1 -0
  356. package/tools/view.js +54 -0
  357. package/tools/view.ts +52 -0
package/tools/res.js ADDED
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Resource CLI.
3
+ * pnpm res list [type] JSONL of every resource (optionally one type)
4
+ * pnpm res extract <type> <number> write decompressed bytes to out/<type>.<number>.bin
5
+ * pnpm res disasm <script> print a script's disassembly
6
+ */
7
+ import { mkdir, writeFile } from "node:fs/promises";
8
+ import { ResourceManager, ResourceType, ScriptWorld, disassemble, formatDisassembly, resourceTypeName } from "../packages/sci/src/index.js";
9
+ import { nodeFiles } from "./node-files.js";
10
+ const typeByName = (name) => {
11
+ const t = Object.entries(ResourceType).find(([k]) => k.toLowerCase() === name.toLowerCase());
12
+ if (!t)
13
+ throw new Error(`Unknown resource type "${name}"`);
14
+ return t[1];
15
+ };
16
+ process.stdout.on("error", (e) => e.code === "EPIPE" && process.exit(0));
17
+ const [cmd, ...args] = process.argv.slice(2);
18
+ const rm = await ResourceManager.open(nodeFiles(process.env.SCI_GAME ?? "original"));
19
+ switch (cmd) {
20
+ case "list": {
21
+ const type = args[0] ? typeByName(args[0]) : undefined;
22
+ for (const r of rm.list(type)) {
23
+ console.log(JSON.stringify({ type: resourceTypeName[r.type], number: r.number, source: r.source }));
24
+ }
25
+ break;
26
+ }
27
+ case "extract": {
28
+ const [typeName, num] = args;
29
+ if (!typeName || !num)
30
+ throw new Error("usage: extract <type> <number>");
31
+ const res = await rm.load({ type: typeByName(typeName), number: Number(num) });
32
+ await mkdir("out", { recursive: true });
33
+ const path = `out/${typeName.toLowerCase()}.${num}.bin`;
34
+ await writeFile(path, res.data);
35
+ console.log(JSON.stringify({ path, bytes: res.data.length }));
36
+ break;
37
+ }
38
+ case "disasm": {
39
+ const world = await ScriptWorld.open(rm);
40
+ await world.loadAllClasses();
41
+ const script = await world.script(Number(args[0]));
42
+ console.log(formatDisassembly(script, disassemble(script, world.context())));
43
+ break;
44
+ }
45
+ default:
46
+ console.error("usage: pnpm res <list [type] | extract <type> <number> | disasm <script>>");
47
+ process.exit(1);
48
+ }
package/tools/res.ts ADDED
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Resource CLI.
3
+ * pnpm res list [type] JSONL of every resource (optionally one type)
4
+ * pnpm res extract <type> <number> write decompressed bytes to out/<type>.<number>.bin
5
+ * pnpm res disasm <script> print a script's disassembly
6
+ */
7
+ import { mkdir, writeFile } from "node:fs/promises";
8
+ import { ResourceManager, ResourceType, ScriptWorld, disassemble, formatDisassembly, resourceTypeName } from "../packages/sci/src/index.ts";
9
+ import { nodeFiles } from "./node-files.ts";
10
+
11
+ const typeByName = (name: string): ResourceType => {
12
+ const t = Object.entries(ResourceType).find(([k]) => k.toLowerCase() === name.toLowerCase());
13
+ if (!t) throw new Error(`Unknown resource type "${name}"`);
14
+ return t[1];
15
+ };
16
+
17
+ process.stdout.on("error", (e: NodeJS.ErrnoException) => e.code === "EPIPE" && process.exit(0));
18
+
19
+ const [cmd, ...args] = process.argv.slice(2);
20
+ const rm = await ResourceManager.open(nodeFiles(process.env.SCI_GAME ?? "original"));
21
+
22
+ switch (cmd) {
23
+ case "list": {
24
+ const type = args[0] ? typeByName(args[0]) : undefined;
25
+ for (const r of rm.list(type)) {
26
+ console.log(JSON.stringify({ type: resourceTypeName[r.type], number: r.number, source: r.source }));
27
+ }
28
+ break;
29
+ }
30
+ case "extract": {
31
+ const [typeName, num] = args;
32
+ if (!typeName || !num) throw new Error("usage: extract <type> <number>");
33
+ const res = await rm.load({ type: typeByName(typeName), number: Number(num) });
34
+ await mkdir("out", { recursive: true });
35
+ const path = `out/${typeName.toLowerCase()}.${num}.bin`;
36
+ await writeFile(path, res.data);
37
+ console.log(JSON.stringify({ path, bytes: res.data.length }));
38
+ break;
39
+ }
40
+ case "disasm": {
41
+ const world = await ScriptWorld.open(rm);
42
+ await world.loadAllClasses();
43
+ const script = await world.script(Number(args[0]));
44
+ console.log(formatDisassembly(script, disassemble(script, world.context())));
45
+ break;
46
+ }
47
+ default:
48
+ console.error("usage: pnpm res <list [type] | extract <type> <number> | disasm <script>>");
49
+ process.exit(1);
50
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Scripted headless session: boots, then replays clicks at given frames.
3
+ * pnpm tsx tools/scenario.ts <frames> [frame:x,y ...] [--png-every N] [--out dir]
4
+ * frame:type=Text types Text followed by Enter at that frame
5
+ * frame:move=x,y moves the mouse without clicking (e.g. to open the icon bar)
6
+ * Example: pnpm tsx tools/scenario.ts 2400 1401:160,60 1800:100,110 2000:type=Hero
7
+ */
8
+ import { mkdirSync, writeFileSync } from "node:fs";
9
+ import { EventType, ResourceManager, Vm, allKernels, formatValue, graphics, input } from "../packages/sci/src/index.js";
10
+ import { nodeFiles } from "./node-files.js";
11
+ import { framePng } from "./png.js";
12
+ const argv = process.argv.slice(2);
13
+ const total = Number(argv[0]);
14
+ const opt = (name) => (argv.includes(name) ? argv[argv.indexOf(name) + 1] : undefined);
15
+ const pngEvery = Number(opt("--png-every") ?? 0);
16
+ const outDir = opt("--out") ?? "out/scenario";
17
+ const clicks = new Map(argv.filter((a) => /^\d+:\d+,\d+$/.test(a)).map((a) => {
18
+ const [f, xy] = a.split(":");
19
+ const [x, y] = xy.split(",").map(Number);
20
+ return [Number(f), { x: x, y: y }];
21
+ }));
22
+ const typing = new Map(argv.filter((a) => /^\d+:type=/.test(a)).map((a) => {
23
+ const i = a.indexOf(":type=");
24
+ return [Number(a.slice(0, i)), a.slice(i + 6)];
25
+ }));
26
+ const moves = new Map(argv.filter((a) => /^\d+:move=\d+,\d+$/.test(a)).map((a) => {
27
+ const [f, xy] = a.split(":move=");
28
+ const [x, y] = xy.split(",").map(Number);
29
+ return [Number(f), { x: x, y: y }];
30
+ }));
31
+ const rm = await ResourceManager.open(nodeFiles("original"));
32
+ await rm.preload();
33
+ const vm = new Vm(rm);
34
+ vm.registerKernels(allKernels);
35
+ const g = graphics(vm);
36
+ vm.clock = () => (g.frames * 1000) / 60;
37
+ if (pngEvery)
38
+ mkdirSync(outDir, { recursive: true });
39
+ g.onFrame = (f) => {
40
+ if (pngEvery && (g.frames % pngEvery === 0 || clicks.has(g.frames - 1) || typing.has(g.frames - 1)))
41
+ writeFileSync(`${outDir}/${String(g.frames).padStart(5, "0")}.png`, framePng(f));
42
+ };
43
+ const trace = argv.includes("--trace") ? (opt("--trace-from") ? Number(opt("--trace-from")) : 0) : Infinity;
44
+ vm.onKernelCall = (name, args, r) => {
45
+ if (g.frames >= trace && !["GetEvent", "FrameOut", "GetTime", "ListEachElementDo", "EmptyList", "FirstNode", "NextNode", "NodeValue", "Clone", "DisposeClone", "GameIsRestarting"].includes(name))
46
+ console.log(` [${g.frames}] ${name}(${args.map((a) => vm.memory.object(a)?.name ?? formatValue(a)).join(", ")}) → ${formatValue(r)}`);
47
+ };
48
+ vm.start(vm.exportAddress(0, 0), "play");
49
+ const inp = input(vm);
50
+ try {
51
+ while (g.frames < total && vm.running) {
52
+ const move = moves.get(g.frames);
53
+ if (move)
54
+ (inp.x = move.x), (inp.y = move.y);
55
+ const click = clicks.get(g.frames);
56
+ if (click) {
57
+ inp.x = click.x;
58
+ inp.y = click.y;
59
+ inp.push({ type: EventType.MouseDown, message: 0 });
60
+ inp.push({ type: EventType.MouseUp, message: 0 });
61
+ }
62
+ const text = typing.get(g.frames);
63
+ if (text !== undefined) {
64
+ for (const ch of text)
65
+ inp.push({ type: EventType.KeyDown, message: ch.charCodeAt(0) });
66
+ inp.push({ type: EventType.KeyDown, message: 13 });
67
+ }
68
+ vm.run();
69
+ }
70
+ }
71
+ catch (e) {
72
+ console.log(`ERROR at frame ${g.frames}: ${e.message}\n${vm.backtrace().map((l) => ` at ${l}`).join("\n")}`);
73
+ }
74
+ console.log(`frames ${g.frames}, missing kernels:`, Object.fromEntries(vm.missingKernels));
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Scripted headless session: boots, then replays clicks at given frames.
3
+ * pnpm tsx tools/scenario.ts <frames> [frame:x,y ...] [--png-every N] [--out dir]
4
+ * frame:type=Text types Text followed by Enter at that frame
5
+ * frame:move=x,y moves the mouse without clicking (e.g. to open the icon bar)
6
+ * Example: pnpm tsx tools/scenario.ts 2400 1401:160,60 1800:100,110 2000:type=Hero
7
+ */
8
+ import { mkdirSync, writeFileSync } from "node:fs";
9
+ import { EventType, ResourceManager, Vm, allKernels, formatValue, graphics, input } from "../packages/sci/src/index.ts";
10
+ import { nodeFiles } from "./node-files.ts";
11
+ import { framePng } from "./png.ts";
12
+
13
+ const argv = process.argv.slice(2);
14
+ const total = Number(argv[0]);
15
+ const opt = (name: string) => (argv.includes(name) ? argv[argv.indexOf(name) + 1] : undefined);
16
+ const pngEvery = Number(opt("--png-every") ?? 0);
17
+ const outDir = opt("--out") ?? "out/scenario";
18
+ const clicks = new Map(
19
+ argv.filter((a) => /^\d+:\d+,\d+$/.test(a)).map((a) => {
20
+ const [f, xy] = a.split(":");
21
+ const [x, y] = xy!.split(",").map(Number);
22
+ return [Number(f), { x: x!, y: y! }] as const;
23
+ }),
24
+ );
25
+
26
+ const typing = new Map(
27
+ argv.filter((a) => /^\d+:type=/.test(a)).map((a) => {
28
+ const i = a.indexOf(":type=");
29
+ return [Number(a.slice(0, i)), a.slice(i + 6)] as const;
30
+ }),
31
+ );
32
+
33
+ const moves = new Map(
34
+ argv.filter((a) => /^\d+:move=\d+,\d+$/.test(a)).map((a) => {
35
+ const [f, xy] = a.split(":move=");
36
+ const [x, y] = xy!.split(",").map(Number);
37
+ return [Number(f), { x: x!, y: y! }] as const;
38
+ }),
39
+ );
40
+
41
+ const rm = await ResourceManager.open(nodeFiles("original"));
42
+ await rm.preload();
43
+ const vm = new Vm(rm);
44
+ vm.registerKernels(allKernels);
45
+ const g = graphics(vm);
46
+ vm.clock = () => (g.frames * 1000) / 60;
47
+ if (pngEvery) mkdirSync(outDir, { recursive: true });
48
+ g.onFrame = (f) => {
49
+ if (pngEvery && (g.frames % pngEvery === 0 || clicks.has(g.frames - 1) || typing.has(g.frames - 1))) writeFileSync(`${outDir}/${String(g.frames).padStart(5, "0")}.png`, framePng(f));
50
+ };
51
+ const trace = argv.includes("--trace") ? (opt("--trace-from") ? Number(opt("--trace-from")) : 0) : Infinity;
52
+ vm.onKernelCall = (name, args, r) => {
53
+ if (g.frames >= trace && !["GetEvent", "FrameOut", "GetTime", "ListEachElementDo", "EmptyList", "FirstNode", "NextNode", "NodeValue", "Clone", "DisposeClone", "GameIsRestarting"].includes(name))
54
+ console.log(` [${g.frames}] ${name}(${args.map((a) => vm.memory.object(a)?.name ?? formatValue(a)).join(", ")}) → ${formatValue(r)}`);
55
+ };
56
+
57
+ vm.start(vm.exportAddress(0, 0), "play");
58
+ const inp = input(vm);
59
+ try {
60
+ while (g.frames < total && vm.running) {
61
+ const move = moves.get(g.frames);
62
+ if (move) (inp.x = move.x), (inp.y = move.y);
63
+ const click = clicks.get(g.frames);
64
+ if (click) {
65
+ inp.x = click.x;
66
+ inp.y = click.y;
67
+ inp.push({ type: EventType.MouseDown, message: 0 });
68
+ inp.push({ type: EventType.MouseUp, message: 0 });
69
+ }
70
+ const text = typing.get(g.frames);
71
+ if (text !== undefined) {
72
+ for (const ch of text) inp.push({ type: EventType.KeyDown, message: ch.charCodeAt(0) });
73
+ inp.push({ type: EventType.KeyDown, message: 13 });
74
+ }
75
+ vm.run();
76
+ }
77
+ } catch (e) {
78
+ console.log(`ERROR at frame ${g.frames}: ${(e as Error).message}\n${vm.backtrace().map((l) => ` at ${l}`).join("\n")}`);
79
+ }
80
+ console.log(`frames ${g.frames}, missing kernels:`, Object.fromEntries(vm.missingKernels));
@@ -0,0 +1,8 @@
1
+ export declare function buildSite(gameDir: string, outDir: string, options?: {
2
+ docs?: string;
3
+ name?: string;
4
+ }): Promise<{
5
+ pages: number;
6
+ game: string;
7
+ soundFont: boolean;
8
+ }>;
@@ -0,0 +1,110 @@
1
+ import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
2
+ import { basename, dirname, join, relative, resolve } from "node:path";
3
+ import { writeResourceArchive } from "../../packages/sci/src/index.js";
4
+ import { buildGame } from "../game/build.js";
5
+ import { markdown } from "./markdown.js";
6
+ /**
7
+ * A static site, to publish anywhere (GitHub Pages, any web server):
8
+ *
9
+ * index.html, CONTRIBUTING.html, ... the Markdown of this repository (or a game's), as pages
10
+ * docs/... docs/**.md as pages, and their screenshots
11
+ * play/play.html the player, with the game in play/game/
12
+ *
13
+ * The SoundFont for music is copied from assets/soundfonts/ if it's there (it isn't part
14
+ * of the repository); without it, games play silently.
15
+ */
16
+ const ROOT = resolve(import.meta.dirname, "../..");
17
+ const VIEWER = join(ROOT, "apps/viewer");
18
+ const SOUNDFONT = join(ROOT, "assets/soundfonts/GeneralUser-GS.sf2");
19
+ export async function buildSite(gameDir, outDir, options = {}) {
20
+ const out = resolve(outDir);
21
+ // Whose Markdown becomes the pages: this repository's, or the game's own.
22
+ const docs = resolve(options.docs ?? ROOT);
23
+ const name = options.name ?? "sci-ts";
24
+ rmSync(out, { recursive: true, force: true });
25
+ const game = await buildGame(gameDir);
26
+ // The player: built by Vite with relative paths, then the game beside it.
27
+ const { build } = await import("vite");
28
+ const play = join(out, "play");
29
+ await build({
30
+ root: VIEWER,
31
+ configFile: join(VIEWER, "vite.config.ts"),
32
+ base: "./",
33
+ logLevel: "warn",
34
+ build: { outDir: play, emptyOutDir: true, rollupOptions: { input: { play: join(VIEWER, "play.html") } } },
35
+ });
36
+ const { map, volume } = writeResourceArchive(game.resources);
37
+ const files = { "RESOURCE.MAP": map, "RESOURCE.000": volume, ...game.files };
38
+ mkdirSync(join(play, "game"), { recursive: true });
39
+ for (const [name, data] of Object.entries(files))
40
+ writeFileSync(join(play, "game", name), data);
41
+ // What the player lists in place of the dev server's directory listings.
42
+ writeFileSync(join(play, "game/files.json"), JSON.stringify({ "": Object.keys(files) }));
43
+ const soundFont = existsSync(SOUNDFONT);
44
+ if (soundFont)
45
+ cpSync(SOUNDFONT, join(play, "soundfonts", basename(SOUNDFONT)));
46
+ // The pages.
47
+ const sources = [
48
+ ...["README.md", "CONTRIBUTING.md", "CHANGELOG.md"].filter((f) => existsSync(join(docs, f))),
49
+ ...walk(docs, "docs").filter((f) => f.endsWith(".md")),
50
+ ];
51
+ const nav = [
52
+ ["index.html", name],
53
+ ["play/play.html", "Play"],
54
+ ...sources.filter((f) => f.startsWith("docs/") && dirname(f) === "docs").map((f) => [f.replace(/\.md$/, ".html"), titleOf(docs, f)]),
55
+ ];
56
+ for (const f of sources) {
57
+ const page = f === "README.md" ? "index.html" : f.replace(/\.md$/, ".html").replace(/(^|\/)README\.html$/, "$1index.html");
58
+ const { html, title } = markdown(readFileSync(join(docs, f), "utf8"));
59
+ const up = "../".repeat(page.split("/").length - 1);
60
+ const links = nav.map(([href, label]) => `<a href="${up}${href}"${href === page ? ' aria-current="page"' : ""}>${label}</a>`).join("");
61
+ mkdirSync(dirname(join(out, page)), { recursive: true });
62
+ writeFileSync(join(out, page), template(title || basename(f), name, links, html));
63
+ }
64
+ if (existsSync(join(docs, "docs/screenshots")))
65
+ cpSync(join(docs, "docs/screenshots"), join(out, "docs/screenshots"), { recursive: true });
66
+ if (existsSync(join(docs, "LICENSE")))
67
+ cpSync(join(docs, "LICENSE"), join(out, "LICENSE"));
68
+ return { pages: sources.length, game: relative(ROOT, resolve(gameDir)), soundFont };
69
+ }
70
+ function walk(root, dir) {
71
+ const abs = join(root, dir);
72
+ if (!existsSync(abs))
73
+ return [];
74
+ return readdirSync(abs).sort().flatMap((name) => (statSync(join(abs, name)).isDirectory() ? walk(root, `${dir}/${name}`) : [`${dir}/${name}`]));
75
+ }
76
+ const titleOf = (root, f) => markdown(readFileSync(join(root, f), "utf8")).title || basename(f, ".md");
77
+ function template(title, site, nav, body) {
78
+ return `<!doctype html>
79
+ <html lang="en">
80
+ <head>
81
+ <meta charset="utf-8">
82
+ <meta name="viewport" content="width=device-width, initial-scale=1">
83
+ <title>${title.replace(/</g, "&lt;")} · ${site}</title>
84
+ <style>
85
+ :root { color-scheme: light dark; --fg: #1d1d1f; --bg: #fbfbf8; --muted: #6b6b6b; --line: #ddd; --code: #f0efe9; --link: #2a5db0; }
86
+ @media (prefers-color-scheme: dark) { :root { --fg: #e6e6e3; --bg: #18181a; --muted: #9a9a9a; --line: #333; --code: #242428; --link: #8ab4f8; } }
87
+ body { margin: 0; background: var(--bg); color: var(--fg); font: 16px/1.55 system-ui, sans-serif; }
88
+ nav { display: flex; flex-wrap: wrap; gap: 4px 16px; padding: 12px 16px; border-bottom: 1px solid var(--line); font-size: 14px; }
89
+ nav a { color: var(--muted); text-decoration: none; }
90
+ nav a[aria-current] { color: var(--fg); font-weight: 600; }
91
+ main { max-width: 760px; margin: 0 auto; padding: 16px 16px 64px; }
92
+ a { color: var(--link); }
93
+ img { max-width: 100%; image-rendering: pixelated; }
94
+ pre { background: var(--code); padding: 12px; overflow-x: auto; border-radius: 4px; font-size: 13px; }
95
+ code { background: var(--code); padding: 1px 4px; border-radius: 3px; font-size: 0.9em; }
96
+ pre code { background: none; padding: 0; }
97
+ table { border-collapse: collapse; display: block; overflow-x: auto; font-size: 14px; }
98
+ th, td { border: 1px solid var(--line); padding: 4px 8px; text-align: left; vertical-align: top; }
99
+ blockquote { margin: 0; padding: 0 16px; border-left: 3px solid var(--line); color: var(--muted); }
100
+ </style>
101
+ </head>
102
+ <body>
103
+ <nav>${nav}</nav>
104
+ <main>
105
+ ${body}
106
+ </main>
107
+ </body>
108
+ </html>
109
+ `;
110
+ }
@@ -0,0 +1,116 @@
1
+ import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
2
+ import { basename, dirname, join, relative, resolve } from "node:path";
3
+ import { writeResourceArchive } from "../../packages/sci/src/index.ts";
4
+ import { buildGame } from "../game/build.ts";
5
+ import { markdown } from "./markdown.ts";
6
+
7
+ /**
8
+ * A static site, to publish anywhere (GitHub Pages, any web server):
9
+ *
10
+ * index.html, CONTRIBUTING.html, ... the Markdown of this repository (or a game's), as pages
11
+ * docs/... docs/**.md as pages, and their screenshots
12
+ * play/play.html the player, with the game in play/game/
13
+ *
14
+ * The SoundFont for music is copied from assets/soundfonts/ if it's there (it isn't part
15
+ * of the repository); without it, games play silently.
16
+ */
17
+ const ROOT = resolve(import.meta.dirname, "../..");
18
+ const VIEWER = join(ROOT, "apps/viewer");
19
+ const SOUNDFONT = join(ROOT, "assets/soundfonts/GeneralUser-GS.sf2");
20
+
21
+ export async function buildSite(
22
+ gameDir: string,
23
+ outDir: string,
24
+ options: { docs?: string; name?: string } = {},
25
+ ): Promise<{ pages: number; game: string; soundFont: boolean }> {
26
+ const out = resolve(outDir);
27
+ // Whose Markdown becomes the pages: this repository's, or the game's own.
28
+ const docs = resolve(options.docs ?? ROOT);
29
+ const name = options.name ?? "sci-ts";
30
+ rmSync(out, { recursive: true, force: true });
31
+ const game = await buildGame(gameDir);
32
+
33
+ // The player: built by Vite with relative paths, then the game beside it.
34
+ const { build } = await import("vite");
35
+ const play = join(out, "play");
36
+ await build({
37
+ root: VIEWER,
38
+ configFile: join(VIEWER, "vite.config.ts"),
39
+ base: "./",
40
+ logLevel: "warn",
41
+ build: { outDir: play, emptyOutDir: true, rollupOptions: { input: { play: join(VIEWER, "play.html") } } },
42
+ });
43
+ const { map, volume } = writeResourceArchive(game.resources);
44
+ const files: Record<string, Uint8Array> = { "RESOURCE.MAP": map, "RESOURCE.000": volume, ...game.files };
45
+ mkdirSync(join(play, "game"), { recursive: true });
46
+ for (const [name, data] of Object.entries(files)) writeFileSync(join(play, "game", name), data);
47
+ // What the player lists in place of the dev server's directory listings.
48
+ writeFileSync(join(play, "game/files.json"), JSON.stringify({ "": Object.keys(files) }));
49
+ const soundFont = existsSync(SOUNDFONT);
50
+ if (soundFont) cpSync(SOUNDFONT, join(play, "soundfonts", basename(SOUNDFONT)));
51
+
52
+ // The pages.
53
+ const sources = [
54
+ ...["README.md", "CONTRIBUTING.md", "CHANGELOG.md"].filter((f) => existsSync(join(docs, f))),
55
+ ...walk(docs, "docs").filter((f) => f.endsWith(".md")),
56
+ ];
57
+ const nav = [
58
+ ["index.html", name],
59
+ ["play/play.html", "Play"],
60
+ ...sources.filter((f) => f.startsWith("docs/") && dirname(f) === "docs").map((f) => [f.replace(/\.md$/, ".html"), titleOf(docs, f)]),
61
+ ];
62
+ for (const f of sources) {
63
+ const page = f === "README.md" ? "index.html" : f.replace(/\.md$/, ".html").replace(/(^|\/)README\.html$/, "$1index.html");
64
+ const { html, title } = markdown(readFileSync(join(docs, f), "utf8"));
65
+ const up = "../".repeat(page.split("/").length - 1);
66
+ const links = nav.map(([href, label]) => `<a href="${up}${href}"${href === page ? ' aria-current="page"' : ""}>${label}</a>`).join("");
67
+ mkdirSync(dirname(join(out, page)), { recursive: true });
68
+ writeFileSync(join(out, page), template(title || basename(f), name, links, html));
69
+ }
70
+ if (existsSync(join(docs, "docs/screenshots"))) cpSync(join(docs, "docs/screenshots"), join(out, "docs/screenshots"), { recursive: true });
71
+ if (existsSync(join(docs, "LICENSE"))) cpSync(join(docs, "LICENSE"), join(out, "LICENSE"));
72
+ return { pages: sources.length, game: relative(ROOT, resolve(gameDir)), soundFont };
73
+ }
74
+
75
+ function walk(root: string, dir: string): string[] {
76
+ const abs = join(root, dir);
77
+ if (!existsSync(abs)) return [];
78
+ return readdirSync(abs).sort().flatMap((name) => (statSync(join(abs, name)).isDirectory() ? walk(root, `${dir}/${name}`) : [`${dir}/${name}`]));
79
+ }
80
+
81
+ const titleOf = (root: string, f: string) => markdown(readFileSync(join(root, f), "utf8")).title || basename(f, ".md");
82
+
83
+ function template(title: string, site: string, nav: string, body: string): string {
84
+ return `<!doctype html>
85
+ <html lang="en">
86
+ <head>
87
+ <meta charset="utf-8">
88
+ <meta name="viewport" content="width=device-width, initial-scale=1">
89
+ <title>${title.replace(/</g, "&lt;")} · ${site}</title>
90
+ <style>
91
+ :root { color-scheme: light dark; --fg: #1d1d1f; --bg: #fbfbf8; --muted: #6b6b6b; --line: #ddd; --code: #f0efe9; --link: #2a5db0; }
92
+ @media (prefers-color-scheme: dark) { :root { --fg: #e6e6e3; --bg: #18181a; --muted: #9a9a9a; --line: #333; --code: #242428; --link: #8ab4f8; } }
93
+ body { margin: 0; background: var(--bg); color: var(--fg); font: 16px/1.55 system-ui, sans-serif; }
94
+ nav { display: flex; flex-wrap: wrap; gap: 4px 16px; padding: 12px 16px; border-bottom: 1px solid var(--line); font-size: 14px; }
95
+ nav a { color: var(--muted); text-decoration: none; }
96
+ nav a[aria-current] { color: var(--fg); font-weight: 600; }
97
+ main { max-width: 760px; margin: 0 auto; padding: 16px 16px 64px; }
98
+ a { color: var(--link); }
99
+ img { max-width: 100%; image-rendering: pixelated; }
100
+ pre { background: var(--code); padding: 12px; overflow-x: auto; border-radius: 4px; font-size: 13px; }
101
+ code { background: var(--code); padding: 1px 4px; border-radius: 3px; font-size: 0.9em; }
102
+ pre code { background: none; padding: 0; }
103
+ table { border-collapse: collapse; display: block; overflow-x: auto; font-size: 14px; }
104
+ th, td { border: 1px solid var(--line); padding: 4px 8px; text-align: left; vertical-align: top; }
105
+ blockquote { margin: 0; padding: 0 16px; border-left: 3px solid var(--line); color: var(--muted); }
106
+ </style>
107
+ </head>
108
+ <body>
109
+ <nav>${nav}</nav>
110
+ <main>
111
+ ${body}
112
+ </main>
113
+ </body>
114
+ </html>
115
+ `;
116
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Markdown to HTML, for the docs pages of a built site: the parts the docs use (headings,
3
+ * paragraphs, lists, tables, fenced code, quotes; code, links, images, bold and italic
4
+ * inline). Links to .md files become links to the .html pages made from them.
5
+ */
6
+ export declare const slug: (text: string) => string;
7
+ export declare function inline(text: string): string;
8
+ export declare function markdown(source: string): {
9
+ html: string;
10
+ title: string;
11
+ };
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Markdown to HTML, for the docs pages of a built site: the parts the docs use (headings,
3
+ * paragraphs, lists, tables, fenced code, quotes; code, links, images, bold and italic
4
+ * inline). Links to .md files become links to the .html pages made from them.
5
+ */
6
+ const escape = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
7
+ export const slug = (text) => text.toLowerCase().replace(/<[^>]+>/g, "").replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-");
8
+ const pageLink = (href) => /^[a-z]+:/i.test(href) || href.startsWith("#") ? href : href.replace(/(^|\/)README\.md(?=#|$)/, "$1index.md").replace(/\.md(?=#|$)/, ".html");
9
+ export function inline(text) {
10
+ const code = [];
11
+ let s = text.replace(/`([^`]+)`/g, (_, c) => `\u0000${code.push(`<code>${escape(c)}</code>`) - 1}\u0000`);
12
+ s = escape(s);
13
+ s = s.replace(/!\[([^\]]*)\]\(([^)\s]+)\)/g, (_, alt, src) => `<img src="${src}" alt="${alt}">`);
14
+ s = s.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (_, label, href) => `<a href="${pageLink(href)}">${label}</a>`);
15
+ s = s.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
16
+ s = s.replace(/(^|[\s(])\*([^*\s][^*]*)\*/g, "$1<em>$2</em>");
17
+ return s.replace(/\u0000(\d+)\u0000/g, (_, i) => code[Number(i)]);
18
+ }
19
+ export function markdown(source) {
20
+ const lines = source.replace(/\r\n/g, "\n").split("\n");
21
+ const out = [];
22
+ let title = "";
23
+ let i = 0;
24
+ const cells = (row) => row.trim().replace(/^\||\|$/g, "").split(/(?<!\\)\|/).map((c) => inline(c.trim().replace(/\\\|/g, "|")));
25
+ while (i < lines.length) {
26
+ const line = lines[i];
27
+ if (!line.trim()) {
28
+ i++;
29
+ continue;
30
+ }
31
+ const fence = /^```(\w*)/.exec(line);
32
+ if (fence) {
33
+ const body = [];
34
+ for (i++; i < lines.length && !lines[i].startsWith("```"); i++)
35
+ body.push(lines[i]);
36
+ i++;
37
+ out.push(`<pre><code${fence[1] ? ` class="language-${fence[1]}"` : ""}>${escape(body.join("\n"))}</code></pre>`);
38
+ continue;
39
+ }
40
+ const heading = /^(#{1,6})\s+(.*)$/.exec(line);
41
+ if (heading) {
42
+ const text = inline(heading[2]);
43
+ if (!title && heading[1].length === 1)
44
+ title = heading[2].replace(/[*`]/g, "");
45
+ out.push(`<h${heading[1].length} id="${slug(text)}">${text}</h${heading[1].length}>`);
46
+ i++;
47
+ continue;
48
+ }
49
+ if (line.startsWith("|") && /^\|?\s*:?-+/.test(lines[i + 1] ?? "")) {
50
+ const head = cells(line);
51
+ const rows = [];
52
+ for (i += 2; i < lines.length && lines[i].startsWith("|"); i++)
53
+ rows.push(cells(lines[i]));
54
+ out.push(`<table><thead><tr>${head.map((c) => `<th>${c}</th>`).join("")}</tr></thead><tbody>${rows.map((r) => `<tr>${r.map((c) => `<td>${c}</td>`).join("")}</tr>`).join("")}</tbody></table>`);
55
+ continue;
56
+ }
57
+ if (line.startsWith(">")) {
58
+ const body = [];
59
+ for (; i < lines.length && lines[i].startsWith(">"); i++)
60
+ body.push(lines[i].replace(/^>\s?/, ""));
61
+ out.push(`<blockquote>${markdown(body.join("\n")).html}</blockquote>`);
62
+ continue;
63
+ }
64
+ const item = /^(\s*)([-*]|\d+\.)\s+/;
65
+ if (item.test(line)) {
66
+ const ordered = /^\s*\d+\./.test(line);
67
+ const items = [];
68
+ for (; i < lines.length; i++) {
69
+ const l = lines[i];
70
+ if (item.test(l))
71
+ items.push(l.replace(item, ""));
72
+ else if (l.trim() && /^\s{2,}/.test(l) && items.length)
73
+ items[items.length - 1] += ` ${l.trim()}`;
74
+ else
75
+ break;
76
+ }
77
+ const tag = ordered ? "ol" : "ul";
78
+ out.push(`<${tag}>${items.map((t) => `<li>${inline(t)}</li>`).join("")}</${tag}>`);
79
+ continue;
80
+ }
81
+ // A paragraph: lines up to a blank one or the start of another block (always at least one).
82
+ const para = [lines[i++].trim()];
83
+ for (; i < lines.length && lines[i].trim() && !/^(#{1,6}\s|```|\||>|\s*([-*]|\d+\.)\s)/.test(lines[i]); i++)
84
+ para.push(lines[i].trim());
85
+ out.push(`<p>${inline(para.join(" "))}</p>`);
86
+ }
87
+ return { html: out.join("\n"), title };
88
+ }