serverless-ircd 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (394) hide show
  1. package/.github/workflows/ci.yml +40 -3
  2. package/.github/workflows/deploy-aws.yml +1 -2
  3. package/.github/workflows/deploy-cf-tcp.yml +1 -5
  4. package/.github/workflows/deploy-cf.yml +1 -2
  5. package/.gitmodules +3 -0
  6. package/CHANGELOG.md +474 -0
  7. package/README.md +219 -22
  8. package/apps/aws-stack/README.md +33 -0
  9. package/apps/aws-stack/package.json +1 -1
  10. package/apps/aws-stack/src/aws-stack.ts +74 -9
  11. package/apps/aws-stack/tests/coverage-command.test.ts +155 -0
  12. package/apps/aws-stack/tests/stack.test.ts +2 -2
  13. package/apps/aws-stack/tests/synth-no-bundle.test.ts +129 -0
  14. package/apps/aws-stack/tests/vitest.setup.ts +18 -0
  15. package/apps/aws-stack/vitest.config.ts +3 -0
  16. package/apps/cf-tcp-container/package.json +1 -1
  17. package/apps/cf-tcp-container/src/config-loader.ts +6 -7
  18. package/apps/cf-tcp-container/src/container-server.ts +3 -0
  19. package/apps/cf-tcp-container/tests/config-loader.test.ts +10 -3
  20. package/apps/cf-tcp-container/wrangler.toml +1 -1
  21. package/apps/cf-worker/package.json +2 -1
  22. package/apps/cf-worker/src/origin-allowlist.ts +99 -0
  23. package/apps/cf-worker/src/worker.ts +89 -9
  24. package/apps/cf-worker/tests/fixtures/web-dist/app/index.html +18 -0
  25. package/apps/cf-worker/tests/fixtures/web-dist/index.html +18 -0
  26. package/apps/cf-worker/tests/origin-allowlist.test.ts +101 -0
  27. package/apps/cf-worker/tests/smoke.test.ts +28 -2
  28. package/apps/cf-worker/tests/ws-origin.test.ts +89 -0
  29. package/apps/cf-worker/vitest.config.ts +1 -1
  30. package/apps/cf-worker/wrangler.test.toml +23 -0
  31. package/apps/cf-worker/wrangler.toml +59 -3
  32. package/apps/local-cli/package.json +1 -1
  33. package/apps/local-cli/src/config-loader.ts +6 -6
  34. package/apps/local-cli/src/main.ts +20 -0
  35. package/apps/local-cli/src/server.ts +9 -7
  36. package/apps/local-cli/tests/e2e.test.ts +112 -28
  37. package/apps/local-cli/tests/tcp.test.ts +71 -19
  38. package/apps/local-cli/tests/ws-subprotocol.test.ts +12 -7
  39. package/apps/web/landing/index.html +217 -0
  40. package/apps/web/package.json +28 -0
  41. package/apps/web/scripts/build.mjs +125 -0
  42. package/apps/web/src/build-env.ts +43 -0
  43. package/apps/web/src/config-schema.ts +138 -0
  44. package/apps/web/static/config.json +28 -0
  45. package/apps/web/static/config.prod.json +28 -0
  46. package/apps/web/static/config.staging.json +28 -0
  47. package/apps/web/tests/build-env.test.ts +63 -0
  48. package/apps/web/tests/build-smoke.test.ts +103 -0
  49. package/apps/web/tests/config-schema.test.ts +432 -0
  50. package/apps/web/tests/workspace.test.ts +12 -0
  51. package/apps/web/tsconfig.json +10 -0
  52. package/apps/web/upstream/.browserslistrc +3 -0
  53. package/apps/web/upstream/.editorconfig +9 -0
  54. package/apps/web/upstream/.eslintignore +3 -0
  55. package/apps/web/upstream/.eslintrc.js +90 -0
  56. package/apps/web/upstream/.github/FUNDING.yml +12 -0
  57. package/apps/web/upstream/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  58. package/apps/web/upstream/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  59. package/apps/web/upstream/.github/workflows/push.yml +26 -0
  60. package/apps/web/upstream/.github/workflows/release.yml +33 -0
  61. package/apps/web/upstream/.github/workflows/release_manual.yml +55 -0
  62. package/apps/web/upstream/.prettierrc.js +9 -0
  63. package/apps/web/upstream/.stylelintignore +5 -0
  64. package/apps/web/upstream/.stylelintrc.js +21 -0
  65. package/apps/web/upstream/LICENSE +201 -0
  66. package/apps/web/upstream/README.md +76 -0
  67. package/apps/web/upstream/babel.config.js +25 -0
  68. package/apps/web/upstream/crowdin.yml +3 -0
  69. package/apps/web/upstream/index.html +35 -0
  70. package/apps/web/upstream/jest.config.js +7 -0
  71. package/apps/web/upstream/jsdoc.json +22 -0
  72. package/apps/web/upstream/package.json +80 -0
  73. package/apps/web/upstream/scripts/pre-commit +43 -0
  74. package/apps/web/upstream/src/components/App.vue +516 -0
  75. package/apps/web/upstream/src/components/AppSettings.vue +587 -0
  76. package/apps/web/upstream/src/components/AutoComplete.vue +284 -0
  77. package/apps/web/upstream/src/components/Avatar.vue +105 -0
  78. package/apps/web/upstream/src/components/AwayStatusIndicator.vue +77 -0
  79. package/apps/web/upstream/src/components/BufferKey.vue +121 -0
  80. package/apps/web/upstream/src/components/BufferSettings.vue +97 -0
  81. package/apps/web/upstream/src/components/Captcha.vue +85 -0
  82. package/apps/web/upstream/src/components/ChannelBanlist.vue +96 -0
  83. package/apps/web/upstream/src/components/ChannelInfo.vue +159 -0
  84. package/apps/web/upstream/src/components/ChannelInvitelist.vue +276 -0
  85. package/apps/web/upstream/src/components/ChannelList.vue +345 -0
  86. package/apps/web/upstream/src/components/Container.vue +401 -0
  87. package/apps/web/upstream/src/components/ContainerHeader.vue +522 -0
  88. package/apps/web/upstream/src/components/ControlInput.vue +1016 -0
  89. package/apps/web/upstream/src/components/LoadingAnimation.vue +104 -0
  90. package/apps/web/upstream/src/components/MediaViewer.vue +199 -0
  91. package/apps/web/upstream/src/components/MessageInfo.vue +192 -0
  92. package/apps/web/upstream/src/components/MessageList.vue +1028 -0
  93. package/apps/web/upstream/src/components/MessageListAvatar.vue +20 -0
  94. package/apps/web/upstream/src/components/MessageListMessageCompact.vue +361 -0
  95. package/apps/web/upstream/src/components/MessageListMessageInline.vue +257 -0
  96. package/apps/web/upstream/src/components/MessageListMessageModern.vue +430 -0
  97. package/apps/web/upstream/src/components/NetworkSettings.vue +528 -0
  98. package/apps/web/upstream/src/components/Nicklist.vue +309 -0
  99. package/apps/web/upstream/src/components/NicklistUser.vue +181 -0
  100. package/apps/web/upstream/src/components/NotConnected.vue +271 -0
  101. package/apps/web/upstream/src/components/SelfUser.vue +283 -0
  102. package/apps/web/upstream/src/components/ServerSelector.vue +162 -0
  103. package/apps/web/upstream/src/components/ServerView.vue +126 -0
  104. package/apps/web/upstream/src/components/SettingsAdvanced.vue +232 -0
  105. package/apps/web/upstream/src/components/SettingsAliases.vue +121 -0
  106. package/apps/web/upstream/src/components/Sidebar.vue +429 -0
  107. package/apps/web/upstream/src/components/SidebarAboutBuffer.vue +288 -0
  108. package/apps/web/upstream/src/components/SidebarState.vue +145 -0
  109. package/apps/web/upstream/src/components/StartupError.vue +35 -0
  110. package/apps/web/upstream/src/components/StateBrowser.vue +420 -0
  111. package/apps/web/upstream/src/components/StateBrowserBuffer.vue +124 -0
  112. package/apps/web/upstream/src/components/StateBrowserNetwork.vue +823 -0
  113. package/apps/web/upstream/src/components/StateBrowserUsermenu.vue +162 -0
  114. package/apps/web/upstream/src/components/TypingStatusIndicator.vue +34 -0
  115. package/apps/web/upstream/src/components/TypingUsersList.vue +56 -0
  116. package/apps/web/upstream/src/components/UrlEmbed.vue +149 -0
  117. package/apps/web/upstream/src/components/UserBox.vue +728 -0
  118. package/apps/web/upstream/src/components/inputtools/Emoji.vue +67 -0
  119. package/apps/web/upstream/src/components/inputtools/TextStyle.vue +142 -0
  120. package/apps/web/upstream/src/components/startups/CommonLayout.vue +208 -0
  121. package/apps/web/upstream/src/components/startups/CustomServer.vue +495 -0
  122. package/apps/web/upstream/src/components/startups/KiwiBnc.vue +361 -0
  123. package/apps/web/upstream/src/components/startups/Personal.vue +317 -0
  124. package/apps/web/upstream/src/components/startups/Welcome.vue +544 -0
  125. package/apps/web/upstream/src/components/startups/ZncLogin.vue +229 -0
  126. package/apps/web/upstream/src/components/utils/InputConfirm.vue +68 -0
  127. package/apps/web/upstream/src/components/utils/InputPrompt.vue +118 -0
  128. package/apps/web/upstream/src/components/utils/InputText.vue +172 -0
  129. package/apps/web/upstream/src/components/utils/IrcInput.vue +579 -0
  130. package/apps/web/upstream/src/components/utils/PluginWrapper.vue +26 -0
  131. package/apps/web/upstream/src/components/utils/TabbedView.vue +149 -0
  132. package/apps/web/upstream/src/components/utils/TransitionExpand.vue +97 -0
  133. package/apps/web/upstream/src/helpers/Colours.js +128 -0
  134. package/apps/web/upstream/src/helpers/IrcdDiffs.js +26 -0
  135. package/apps/web/upstream/src/helpers/Md5.js +193 -0
  136. package/apps/web/upstream/src/helpers/Misc.js +401 -0
  137. package/apps/web/upstream/src/helpers/TextFormatting.js +237 -0
  138. package/apps/web/upstream/src/libs/AliasRewriter.js +157 -0
  139. package/apps/web/upstream/src/libs/AudioManager.js +60 -0
  140. package/apps/web/upstream/src/libs/BouncerMiddleware.js +247 -0
  141. package/apps/web/upstream/src/libs/BouncerProvider.js +551 -0
  142. package/apps/web/upstream/src/libs/ChathistoryMiddleware.js +153 -0
  143. package/apps/web/upstream/src/libs/ConfigLoader.js +94 -0
  144. package/apps/web/upstream/src/libs/EmojiProvider.js +46 -0
  145. package/apps/web/upstream/src/libs/GlobalApi.js +346 -0
  146. package/apps/web/upstream/src/libs/IPC.js +51 -0
  147. package/apps/web/upstream/src/libs/InputHandler.js +921 -0
  148. package/apps/web/upstream/src/libs/IrcClient.js +1506 -0
  149. package/apps/web/upstream/src/libs/Logger.js +71 -0
  150. package/apps/web/upstream/src/libs/Message.js +169 -0
  151. package/apps/web/upstream/src/libs/MessageFormatter.js +364 -0
  152. package/apps/web/upstream/src/libs/MessageParser.js +249 -0
  153. package/apps/web/upstream/src/libs/Notifications.js +78 -0
  154. package/apps/web/upstream/src/libs/ServerConnection.js +270 -0
  155. package/apps/web/upstream/src/libs/ServerSession.js +102 -0
  156. package/apps/web/upstream/src/libs/SoundBleep.js +20 -0
  157. package/apps/web/upstream/src/libs/StatePersistence.js +90 -0
  158. package/apps/web/upstream/src/libs/ThemeManager.js +128 -0
  159. package/apps/web/upstream/src/libs/TypingMiddleware.js +105 -0
  160. package/apps/web/upstream/src/libs/WindowTitle.js +63 -0
  161. package/apps/web/upstream/src/libs/batchedAdd.js +74 -0
  162. package/apps/web/upstream/src/libs/bufferTools.js +177 -0
  163. package/apps/web/upstream/src/libs/polyfill/Element.closest.js +18 -0
  164. package/apps/web/upstream/src/libs/renderers/Html.js +113 -0
  165. package/apps/web/upstream/src/libs/settingTools.js +31 -0
  166. package/apps/web/upstream/src/libs/state/BufferState.js +703 -0
  167. package/apps/web/upstream/src/libs/state/NetworkState.js +158 -0
  168. package/apps/web/upstream/src/libs/state/UserState.js +89 -0
  169. package/apps/web/upstream/src/libs/state/common.js +26 -0
  170. package/apps/web/upstream/src/libs/state.js +961 -0
  171. package/apps/web/upstream/src/libs/storage/Local.js +51 -0
  172. package/apps/web/upstream/src/main.js +566 -0
  173. package/apps/web/upstream/src/res/autocompleteCommands.js +31 -0
  174. package/apps/web/upstream/src/res/configTemplates.js +373 -0
  175. package/apps/web/upstream/src/res/globalStyle.css +277 -0
  176. package/apps/web/upstream/src/res/kiwiLoadingLogo.png +0 -0
  177. package/apps/web/upstream/src/res/locales/app.af-ZA.po +1145 -0
  178. package/apps/web/upstream/src/res/locales/app.ar-SA.po +1145 -0
  179. package/apps/web/upstream/src/res/locales/app.bg-BG.po +1145 -0
  180. package/apps/web/upstream/src/res/locales/app.bs-BA.po +1145 -0
  181. package/apps/web/upstream/src/res/locales/app.ca-ES.po +1145 -0
  182. package/apps/web/upstream/src/res/locales/app.cs-CZ.po +1145 -0
  183. package/apps/web/upstream/src/res/locales/app.da-DK.po +1145 -0
  184. package/apps/web/upstream/src/res/locales/app.de-DE.po +1145 -0
  185. package/apps/web/upstream/src/res/locales/app.dev.po +1246 -0
  186. package/apps/web/upstream/src/res/locales/app.el-GR.po +1145 -0
  187. package/apps/web/upstream/src/res/locales/app.en-US.po +1145 -0
  188. package/apps/web/upstream/src/res/locales/app.es-419.po +1145 -0
  189. package/apps/web/upstream/src/res/locales/app.es-AR.po +1145 -0
  190. package/apps/web/upstream/src/res/locales/app.es-EM.po +536 -0
  191. package/apps/web/upstream/src/res/locales/app.es-ES.po +1145 -0
  192. package/apps/web/upstream/src/res/locales/app.es-US.po +1145 -0
  193. package/apps/web/upstream/src/res/locales/app.eu-ES.po +1145 -0
  194. package/apps/web/upstream/src/res/locales/app.fi-FI.po +1145 -0
  195. package/apps/web/upstream/src/res/locales/app.fr-FR.po +1145 -0
  196. package/apps/web/upstream/src/res/locales/app.gl-ES.po +1145 -0
  197. package/apps/web/upstream/src/res/locales/app.he-IL.po +1145 -0
  198. package/apps/web/upstream/src/res/locales/app.hi-IN.po +1145 -0
  199. package/apps/web/upstream/src/res/locales/app.hu-HU.po +1145 -0
  200. package/apps/web/upstream/src/res/locales/app.id-ID.po +1145 -0
  201. package/apps/web/upstream/src/res/locales/app.it-IT.po +1145 -0
  202. package/apps/web/upstream/src/res/locales/app.ja-JP.po +1145 -0
  203. package/apps/web/upstream/src/res/locales/app.ko-KR.po +1145 -0
  204. package/apps/web/upstream/src/res/locales/app.nl-NL.po +1145 -0
  205. package/apps/web/upstream/src/res/locales/app.no-NO.po +1145 -0
  206. package/apps/web/upstream/src/res/locales/app.pl-PL.po +1145 -0
  207. package/apps/web/upstream/src/res/locales/app.pt-BR.po +1145 -0
  208. package/apps/web/upstream/src/res/locales/app.pt-PT.po +1145 -0
  209. package/apps/web/upstream/src/res/locales/app.ro-RO.po +1145 -0
  210. package/apps/web/upstream/src/res/locales/app.ru-RU.po +1145 -0
  211. package/apps/web/upstream/src/res/locales/app.sl-SI.po +1145 -0
  212. package/apps/web/upstream/src/res/locales/app.sq-AL.po +1145 -0
  213. package/apps/web/upstream/src/res/locales/app.sr-SP.po +1145 -0
  214. package/apps/web/upstream/src/res/locales/app.sv-SE.po +1145 -0
  215. package/apps/web/upstream/src/res/locales/app.tr-TR.po +1145 -0
  216. package/apps/web/upstream/src/res/locales/app.uk-UA.po +1145 -0
  217. package/apps/web/upstream/src/res/locales/app.vi-VN.po +1145 -0
  218. package/apps/web/upstream/src/res/locales/app.zh-CN.po +1145 -0
  219. package/apps/web/upstream/src/res/locales/app.zh-TW.po +1145 -0
  220. package/apps/web/upstream/src/res/localesList.json +27 -0
  221. package/apps/web/upstream/src/res/logo.png +0 -0
  222. package/apps/web/upstream/src/thirdparty/about.html +43 -0
  223. package/apps/web/upstream/src/thirdparty/index.js +5 -0
  224. package/apps/web/upstream/src/thirdparty/kiwiirccom.vue +185 -0
  225. package/apps/web/upstream/static/config.json +28 -0
  226. package/apps/web/upstream/static/emoticons/smile.png +0 -0
  227. package/apps/web/upstream/static/favicon.png +0 -0
  228. package/apps/web/upstream/static/highlight.mp3 +0 -0
  229. package/apps/web/upstream/static/highlight.ogg +0 -0
  230. package/apps/web/upstream/static/locales/.gitignore +2 -0
  231. package/apps/web/upstream/static/plugins/customise.html.example +10 -0
  232. package/apps/web/upstream/static/themes/coffee/theme.css +145 -0
  233. package/apps/web/upstream/static/themes/common/base.css +1064 -0
  234. package/apps/web/upstream/static/themes/dark/theme.css +196 -0
  235. package/apps/web/upstream/static/themes/default/theme.css +92 -0
  236. package/apps/web/upstream/static/themes/elite/theme.css +248 -0
  237. package/apps/web/upstream/static/themes/grayfox/theme.css +143 -0
  238. package/apps/web/upstream/static/themes/nightswatch/theme.css +367 -0
  239. package/apps/web/upstream/static/themes/osprey/theme.css +115 -0
  240. package/apps/web/upstream/static/themes/radioactive/theme.css +1211 -0
  241. package/apps/web/upstream/static/themes/sky/theme.css +107 -0
  242. package/apps/web/upstream/tests/unit/BatchAdd.spec.js +174 -0
  243. package/apps/web/upstream/tests/unit/MessageParser.spec.js +125 -0
  244. package/apps/web/upstream/tests/unit/Misc.spec.js +24 -0
  245. package/apps/web/upstream/tests/unit/NetworkState.spec.js +58 -0
  246. package/apps/web/upstream/tests/unit/StartupError.spec.js +20 -0
  247. package/apps/web/upstream/vue.config.js +150 -0
  248. package/apps/web/upstream/yarn.lock +9767 -0
  249. package/apps/web/vitest.config.ts +13 -0
  250. package/biome.json +2 -0
  251. package/docs/ADR-001-pure-reducers-and-effect-system.md +74 -0
  252. package/docs/ADR-002-location-of-authority.md +82 -0
  253. package/docs/ADR-003-durable-object-sharding.md +110 -0
  254. package/docs/ADR-004-dynamodb-schema.md +96 -0
  255. package/docs/ADR-005-wss-only-transport-v1.md +83 -0
  256. package/docs/ADR-006-sasl-mechanism-scope.md +96 -0
  257. package/docs/ADR-007-deterministic-ports.md +82 -0
  258. package/docs/ADR-008-monorepo-tooling.md +60 -0
  259. package/docs/ADR-009-dual-transport-wss-and-irc-tls.md +138 -0
  260. package/docs/ADR-Index.md +15 -0
  261. package/docs/AWS-Adapter-Architecture.md +496 -0
  262. package/docs/AWS-Deployment.md +1275 -0
  263. package/docs/AWS-TCP-Deployment.md +410 -0
  264. package/docs/Cloudflare-Deployment-Guide.md +719 -0
  265. package/docs/Cloudflare-TCP-Deployment.md +510 -0
  266. package/docs/Home.md +16 -0
  267. package/docs/Observability.md +87 -0
  268. package/docs/PlanExtensions.md +812 -0
  269. package/docs/PlanIRCv3Websocket.md +503 -0
  270. package/docs/PlanWebClient.md +451 -0
  271. package/docs/Release-Process.md +443 -0
  272. package/docs/WebClientGuide.md +535 -0
  273. package/package.json +2 -2
  274. package/packages/aws-adapter/package.json +1 -1
  275. package/packages/aws-adapter/src/handlers/default.ts +3 -0
  276. package/packages/aws-adapter/src/handlers/nlb-stream.ts +9 -0
  277. package/packages/aws-adapter/src/serialize.ts +29 -1
  278. package/packages/aws-adapter/tests/account-store-dynamo.test.ts +49 -8
  279. package/packages/aws-adapter/tests/account-store.test.ts +4 -7
  280. package/packages/aws-adapter/tests/aws-harness.ts +5 -2
  281. package/packages/aws-adapter/tests/aws-integration.test.ts +8 -4
  282. package/packages/aws-adapter/tests/aws-runtime.test.ts +79 -0
  283. package/packages/aws-adapter/tests/config-loader.test.ts +11 -2
  284. package/packages/aws-adapter/tests/connect.test.ts +9 -9
  285. package/packages/aws-adapter/tests/disconnect-fanout.test.ts +3 -4
  286. package/packages/aws-adapter/tests/global-setup.ts +55 -30
  287. package/packages/aws-adapter/tests/gone-exception.test.ts +4 -9
  288. package/packages/aws-adapter/tests/handlers.test.ts +90 -4
  289. package/packages/aws-adapter/tests/message-store.test.ts +6 -5
  290. package/packages/aws-adapter/tests/nlb-stream.test.ts +60 -8
  291. package/packages/cf-adapter/package.json +2 -1
  292. package/packages/cf-adapter/src/connection-do.ts +3 -0
  293. package/packages/cf-adapter/src/serialize.ts +25 -4
  294. package/packages/cf-adapter/tests/cf-harness.ts +12 -1
  295. package/packages/cf-adapter/tests/cf-runtime.test.ts +269 -0
  296. package/packages/cf-adapter/tests/channel-do.test.ts +117 -0
  297. package/packages/cf-adapter/tests/config-loader.test.ts +49 -0
  298. package/packages/cf-adapter/tests/connection-do-pure.test.ts +91 -0
  299. package/packages/cf-adapter/tests/d1-account-store.test.ts +48 -0
  300. package/packages/cf-adapter/tests/registry-do.test.ts +104 -0
  301. package/packages/cf-adapter/tests/serialize.test.ts +308 -0
  302. package/packages/cf-adapter/vitest.config.ts +1 -1
  303. package/packages/in-memory-runtime/package.json +1 -1
  304. package/packages/irc-core/package.json +1 -1
  305. package/packages/irc-core/reports/mutation/mutation.html +342 -0
  306. package/packages/irc-core/src/caps/capabilities.ts +37 -3
  307. package/packages/irc-core/src/caps/index.ts +1 -0
  308. package/packages/irc-core/src/caps/sts.ts +84 -0
  309. package/packages/irc-core/src/commands/account-notify.ts +53 -0
  310. package/packages/irc-core/src/commands/away.ts +9 -3
  311. package/packages/irc-core/src/commands/cap.ts +23 -5
  312. package/packages/irc-core/src/commands/chathistory.ts +40 -11
  313. package/packages/irc-core/src/commands/index.ts +34 -0
  314. package/packages/irc-core/src/commands/isupport.ts +53 -0
  315. package/packages/irc-core/src/commands/mode.ts +8 -1
  316. package/packages/irc-core/src/commands/monitor.ts +327 -0
  317. package/packages/irc-core/src/commands/multiline.ts +256 -0
  318. package/packages/irc-core/src/commands/pre-away.ts +112 -0
  319. package/packages/irc-core/src/commands/privmsg.ts +19 -12
  320. package/packages/irc-core/src/commands/read-marker.ts +108 -0
  321. package/packages/irc-core/src/commands/registration.ts +8 -0
  322. package/packages/irc-core/src/commands/sasl.ts +24 -1
  323. package/packages/irc-core/src/commands/tagmsg.ts +71 -1
  324. package/packages/irc-core/src/commands/whois.ts +5 -0
  325. package/packages/irc-core/src/config.ts +65 -10
  326. package/packages/irc-core/src/effects.ts +14 -0
  327. package/packages/irc-core/src/ports.ts +389 -0
  328. package/packages/irc-core/src/protocol/index.ts +14 -0
  329. package/packages/irc-core/src/protocol/labeled-response.ts +133 -0
  330. package/packages/irc-core/src/protocol/numerics.ts +15 -0
  331. package/packages/irc-core/src/protocol/outbound.ts +16 -1
  332. package/packages/irc-core/src/protocol/standard-replies.ts +277 -0
  333. package/packages/irc-core/src/state/connection.ts +32 -1
  334. package/packages/irc-core/src/types.ts +112 -0
  335. package/packages/irc-core/stryker.commands.conf.json +1 -2
  336. package/packages/irc-core/tests/away-store.test.ts +73 -0
  337. package/packages/irc-core/tests/batch.test.ts +15 -0
  338. package/packages/irc-core/tests/caps/capabilities.test.ts +119 -2
  339. package/packages/irc-core/tests/caps/sts.test.ts +123 -0
  340. package/packages/irc-core/tests/commands/account-notify.test.ts +171 -0
  341. package/packages/irc-core/tests/commands/away.test.ts +85 -2
  342. package/packages/irc-core/tests/commands/cap.test.ts +165 -0
  343. package/packages/irc-core/tests/commands/chathistory.test.ts +148 -1
  344. package/packages/irc-core/tests/commands/echo-message.test.ts +9 -3
  345. package/packages/irc-core/tests/commands/isupport.test.ts +80 -0
  346. package/packages/irc-core/tests/commands/mode.test.ts +72 -0
  347. package/packages/irc-core/tests/commands/monitor.test.ts +411 -0
  348. package/packages/irc-core/tests/commands/multiline.test.ts +428 -0
  349. package/packages/irc-core/tests/commands/pre-away.test.ts +241 -0
  350. package/packages/irc-core/tests/commands/privmsg.test.ts +154 -3
  351. package/packages/irc-core/tests/commands/read-marker.test.ts +211 -0
  352. package/packages/irc-core/tests/commands/registration.test.ts +64 -0
  353. package/packages/irc-core/tests/commands/sasl.test.ts +116 -2
  354. package/packages/irc-core/tests/commands/tagmsg.test.ts +357 -0
  355. package/packages/irc-core/tests/commands/whois.test.ts +34 -0
  356. package/packages/irc-core/tests/config.test.ts +103 -13
  357. package/packages/irc-core/tests/labeled-response.test.ts +181 -0
  358. package/packages/irc-core/tests/message-tags.test.ts +114 -0
  359. package/packages/irc-core/tests/monitor-store.test.ts +200 -0
  360. package/packages/irc-core/tests/numerics.test.ts +13 -0
  361. package/packages/irc-core/tests/parser.test.ts +109 -0
  362. package/packages/irc-core/tests/read-marker-store.test.ts +108 -0
  363. package/packages/irc-core/tests/standard-replies.test.ts +412 -0
  364. package/packages/irc-core/tests/types.test.ts +27 -0
  365. package/packages/irc-server/package.json +1 -1
  366. package/packages/irc-server/src/actor.ts +450 -3
  367. package/packages/irc-server/src/dispatch.ts +108 -16
  368. package/packages/irc-server/tests/actor.test.ts +1432 -39
  369. package/packages/irc-server/tests/dispatch.test.ts +263 -2
  370. package/packages/irc-test-support/package.json +1 -1
  371. package/packages/irc-test-support/src/index.ts +6 -0
  372. package/packages/irc-test-support/src/scenarios.ts +24 -2
  373. package/packages/irc-test-support/src/test-config.ts +54 -0
  374. package/packages/irc-test-support/tests/test-config.test.ts +51 -0
  375. package/tools/ci-hardening/package.json +1 -1
  376. package/tools/ci-hardening/tests/ddb-local.test.ts +82 -0
  377. package/tools/load-test/package.json +33 -0
  378. package/tools/load-test/src/client.ts +351 -0
  379. package/tools/load-test/src/config.ts +313 -0
  380. package/tools/load-test/src/harness.ts +116 -0
  381. package/tools/load-test/src/main.ts +120 -0
  382. package/tools/load-test/src/metrics.ts +168 -0
  383. package/tools/load-test/src/report.ts +106 -0
  384. package/tools/load-test/tests/client.test.ts +212 -0
  385. package/tools/load-test/tests/config.test.ts +152 -0
  386. package/tools/load-test/tests/framing.test.ts +37 -0
  387. package/tools/load-test/tests/harness.test.ts +165 -0
  388. package/tools/load-test/tests/metrics.test.ts +174 -0
  389. package/tools/load-test/tests/report.test.ts +161 -0
  390. package/tools/load-test/tests/smoke.test.ts +67 -0
  391. package/tools/load-test/tsconfig.build.json +12 -0
  392. package/tools/load-test/tsconfig.test.json +10 -0
  393. package/tools/load-test/vitest.config.ts +29 -0
  394. package/tools/tcp-ws-forwarder/package.json +1 -1
@@ -0,0 +1,18 @@
1
+ <!doctype html>
2
+ <!--
3
+ Test-only fixture mirroring the real `apps/web/dist/app/index.html` produced
4
+ by the Kiwi SPA build (`pnpm --filter web build`). The cf-worker smoke suite
5
+ points its `[assets]` binding here so the worker's fall-through routing
6
+ (WS → ConnectionDO, /health → plaintext, else → ASSETS) is exercised without
7
+ depending on the (slow, submodule-heavy) Kiwi build. Production points at
8
+ `apps/web/dist`; this fixture only stands in for the test run.
9
+ -->
10
+ <html lang="en">
11
+ <head>
12
+ <meta charset="utf-8" />
13
+ <title>KiwiIRC</title>
14
+ </head>
15
+ <body>
16
+ <div id="app"></div>
17
+ </body>
18
+ </html>
@@ -0,0 +1,18 @@
1
+ <!doctype html>
2
+ <!--
3
+ Test-only fixture standing in for the landing page shipped by the
4
+ `apps/web` build (`apps/web/dist/index.html`). Production will serve a
5
+ real landing page at `/`; the cf-worker smoke suite asserts that `GET /`
6
+ is served by the `[assets]` binding rather than the legacy plaintext
7
+ health response (now exclusive to `/health`).
8
+ -->
9
+ <html lang="en">
10
+ <head>
11
+ <meta charset="utf-8" />
12
+ <title>ServerlessIRCd</title>
13
+ </head>
14
+ <body>
15
+ <h1>ServerlessIRCd</h1>
16
+ <p>A serverless IRC daemon.</p>
17
+ </body>
18
+ </html>
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Unit tests for the WebSocket `Origin` allowlist helpers.
3
+ *
4
+ * The worker's CSWSH defense is split into two pure functions
5
+ * (`parseWebOrigins`, `decideOrigin`) so the policy logic is exhaustively
6
+ * coverable without driving a Durable Object through `worker.fetch`. The
7
+ * integration-side assertions (status codes against the real handler) live
8
+ * in `ws-origin.test.ts`.
9
+ */
10
+
11
+ import { describe, expect, it } from 'vitest';
12
+ import { decideOrigin, parseWebOrigins } from '../src/origin-allowlist';
13
+
14
+ describe('parseWebOrigins', () => {
15
+ it('splits a comma-separated list into a set of normalized origins', () => {
16
+ expect(parseWebOrigins('https://example.com, https://app.example.com')).toEqual(
17
+ new Set(['https://example.com', 'https://app.example.com']),
18
+ );
19
+ });
20
+
21
+ it('lowercases origins for case-insensitive comparison', () => {
22
+ expect(parseWebOrigins('https://Example.COM/Path').has('https://example.com/path')).toBe(true);
23
+ });
24
+
25
+ it('drops whitespace-only and empty entries', () => {
26
+ expect(parseWebOrigins('https://example.com, , ,https://b.example.com')).toEqual(
27
+ new Set(['https://example.com', 'https://b.example.com']),
28
+ );
29
+ });
30
+
31
+ it('returns an empty set for an empty/undefined var (fail-closed sentinel)', () => {
32
+ expect(parseWebOrigins('').size).toBe(0);
33
+ expect(parseWebOrigins(' ').size).toBe(0);
34
+ expect(parseWebOrigins(undefined).size).toBe(0);
35
+ });
36
+ });
37
+
38
+ describe('decideOrigin', () => {
39
+ const allowlist = parseWebOrigins('https://example.com, https://app.example.com');
40
+
41
+ // ---- missing Origin = non-browser client (curl, WeeChat, tcp-ws-forwarder)
42
+
43
+ it('allows a missing Origin header (non-browser client)', () => {
44
+ expect(decideOrigin(null, allowlist, 'https://example.com')).toBe('allow');
45
+ });
46
+
47
+ it('allows an empty Origin header (treated like a missing header)', () => {
48
+ expect(decideOrigin('', allowlist, 'https://example.com')).toBe('allow');
49
+ });
50
+
51
+ // ---- explicit allowlist (WEB_ORIGINS is set)
52
+
53
+ it('allows an Origin that is in the allowlist', () => {
54
+ expect(decideOrigin('https://example.com', allowlist, 'https://example.com')).toBe('allow');
55
+ });
56
+
57
+ it('allows an Origin that differs only by case', () => {
58
+ expect(decideOrigin('https://EXAMPLE.com', allowlist, 'https://example.com')).toBe('allow');
59
+ });
60
+
61
+ it('denies an Origin that is not in the allowlist', () => {
62
+ expect(decideOrigin('https://evil.example.net', allowlist, 'https://example.com')).toBe('deny');
63
+ });
64
+
65
+ it('denies an Origin that matches requestOrigin but is NOT in the allowlist (allowlist wins)', () => {
66
+ // WEB_ORIGINS takes precedence: if the operator pinned an explicit list,
67
+ // same-origin is NOT automatically allowed unless the origin is listed.
68
+ const strict = parseWebOrigins('https://only-this.com');
69
+ expect(decideOrigin('https://example.com', strict, 'https://example.com')).toBe('deny');
70
+ });
71
+
72
+ // ---- auto-derive (WEB_ORIGINS is empty — same-origin enforcement)
73
+
74
+ it('auto-allows a same-origin Origin when the allowlist is empty', () => {
75
+ expect(decideOrigin('https://example.com', new Set(), 'https://example.com')).toBe('allow');
76
+ });
77
+
78
+ it('auto-denies a cross-origin Origin when the allowlist is empty', () => {
79
+ expect(decideOrigin('https://evil.example.net', new Set(), 'https://example.com')).toBe('deny');
80
+ });
81
+
82
+ it('auto-derive is case-insensitive', () => {
83
+ expect(decideOrigin('https://EXAMPLE.com', new Set(), 'https://example.com')).toBe('allow');
84
+ });
85
+
86
+ it('auto-allows a missing Origin even when the allowlist is empty (non-browser client)', () => {
87
+ expect(decideOrigin(null, new Set(), 'https://example.com')).toBe('allow');
88
+ });
89
+
90
+ // ---- fail-closed (cannot determine same-origin AND no allowlist)
91
+
92
+ it('fail-closes when a browser sends an Origin but neither allowlist nor requestOrigin exist', () => {
93
+ expect(decideOrigin('https://example.com', new Set(), null)).toBe('fail-closed');
94
+ });
95
+
96
+ it('still allows a missing Origin even when neither allowlist nor requestOrigin exist (non-browser client)', () => {
97
+ // Non-browser clients never send Origin; they pass through regardless of
98
+ // configuration state. Only browser-sent Origins can be fail-closed.
99
+ expect(decideOrigin(null, new Set(), null)).toBe('allow');
100
+ });
101
+ });
@@ -30,6 +30,12 @@ declare global {
30
30
  SERVER_NAME: string;
31
31
  NETWORK_NAME: string;
32
32
  MOTD_LINES: string;
33
+ // Static-assets binding (`[assets]` in `wrangler.test.toml`, pointed at
34
+ // `tests/fixtures/web-dist`). Production points at `apps/web/dist`; the
35
+ // fixture mirrors the `app/index.html` + `index.html` layout so the
36
+ // worker's fall-through routing is exercised without depending on the
37
+ // Kiwi SPA build.
38
+ ASSETS: Fetcher;
33
39
  }
34
40
  }
35
41
  }
@@ -47,13 +53,33 @@ function numericEquals(line: string, code: string): boolean {
47
53
  }
48
54
 
49
55
  describe('cf-worker deploy glue', () => {
50
- it('non-WebSocket GET returns a human-readable health response', async () => {
51
- const response = await worker.fetch(new Request('https://irc.example.com/'), env);
56
+ it('GET /health returns a human-readable plaintext health response', async () => {
57
+ const response = await worker.fetch(new Request('https://irc.example.com/health'), env);
52
58
  expect(response.status).toBe(200);
59
+ expect(response.headers.get('content-type')).toBe('text/plain; charset=utf-8');
53
60
  const body = await response.text();
54
61
  expect(body.toLowerCase()).toContain('serverlessircd');
55
62
  });
56
63
 
64
+ it('GET /app/ serves the SPA index from the [assets] binding', async () => {
65
+ const response = await worker.fetch(new Request('https://irc.example.com/app/'), env);
66
+ expect(response.status).toBe(200);
67
+ const body = await response.text();
68
+ // The fixture SPA HTML carries this marker so the test asserts the
69
+ // assets binding (not the /health plaintext branch) produced the body.
70
+ expect(body).toContain('<title>KiwiIRC</title>');
71
+ });
72
+
73
+ it('GET / (landing page) is served by the [assets] binding, not the health plaintext', async () => {
74
+ const response = await worker.fetch(new Request('https://irc.example.com/'), env);
75
+ expect(response.status).toBe(200);
76
+ const body = await response.text();
77
+ // The fixture landing page carries this marker; the old health
78
+ // plaintext response is now exclusively at /health.
79
+ expect(body).toContain('<title>ServerlessIRCd</title>');
80
+ expect(body).not.toContain('Connect via WebSocket');
81
+ });
82
+
57
83
  it('completes CONNECT → REGISTER → JOIN → PRIVMSG → QUIT', async () => {
58
84
  // ----- CONNECT: WebSocket upgrade through the worker's fetch handler.
59
85
  const upgrade = new Request('https://irc.example.com/', {
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Integration tests for the WebSocket `Origin` policy (CSWSH defense).
3
+ *
4
+ * Drives the production worker entry (`src/worker.ts`) end-to-end through
5
+ * `worker.fetch` to assert the `Origin` header policy is enforced at the
6
+ * edge, before any upgrade reaches `ConnectionDO`. The pure policy logic
7
+ * is covered separately in `origin-allowlist.test.ts`; these tests assert
8
+ * the wiring (status codes + that the DO is never reached on a rejection).
9
+ *
10
+ * Two modes are exercised:
11
+ *
12
+ * • Explicit allowlist — `WEB_ORIGINS` is set in `wrangler.test.toml`
13
+ * `[vars]` as `https://example.com`. Only listed origins proceed.
14
+ *
15
+ * • Auto-derive (same-origin) — per-test override blanks `WEB_ORIGINS`;
16
+ * the Worker compares the browser's `Origin` against the request's own
17
+ * host. Same-origin upgrades proceed; cross-origin are rejected.
18
+ */
19
+
20
+ import { env } from 'cloudflare:test';
21
+ import { describe, expect, it } from 'vitest';
22
+ import worker from '../src/worker';
23
+
24
+ declare global {
25
+ namespace Cloudflare {
26
+ interface Env {
27
+ WEB_ORIGINS: string;
28
+ }
29
+ }
30
+ }
31
+
32
+ const UPGRADE_URL = 'https://irc.example.com/';
33
+ /** The request's own origin — what auto-derive compares Origin against. */
34
+ const SELF_ORIGIN = 'https://irc.example.com';
35
+
36
+ function upgradeRequest(origin?: string): Request {
37
+ const headers: Record<string, string> = { Upgrade: 'websocket' };
38
+ if (origin !== undefined) headers.Origin = origin;
39
+ return new Request(UPGRADE_URL, { headers });
40
+ }
41
+
42
+ describe('WS Origin policy — explicit allowlist (WEB_ORIGINS set)', () => {
43
+ it('proceeds (101) when the Origin is in WEB_ORIGINS', async () => {
44
+ const response = await worker.fetch(upgradeRequest('https://example.com'), env);
45
+ expect(response.status).toBe(101);
46
+ response.webSocket?.accept();
47
+ response.webSocket?.close();
48
+ });
49
+
50
+ it('rejects with 403 when the Origin is not in WEB_ORIGINS', async () => {
51
+ const response = await worker.fetch(upgradeRequest('https://evil.example.net'), env);
52
+ expect(response.status).toBe(403);
53
+ expect(response.webSocket).toBeNull();
54
+ const body = await response.text();
55
+ expect(body.toLowerCase()).toContain('origin');
56
+ });
57
+
58
+ it('proceeds (101) when the Origin header is missing (non-browser client)', async () => {
59
+ const response = await worker.fetch(upgradeRequest(), env);
60
+ expect(response.status).toBe(101);
61
+ response.webSocket?.accept();
62
+ response.webSocket?.close();
63
+ });
64
+ });
65
+
66
+ describe('WS Origin policy — auto-derive (WEB_ORIGINS unset → same-origin)', () => {
67
+ /** Env with WEB_ORIGINS blanked so the Worker falls back to auto-derive. */
68
+ const autoEnv = { ...env, WEB_ORIGINS: '' };
69
+
70
+ it('proceeds (101) for a same-origin upgrade', async () => {
71
+ const response = await worker.fetch(upgradeRequest(SELF_ORIGIN), autoEnv);
72
+ expect(response.status).toBe(101);
73
+ response.webSocket?.accept();
74
+ response.webSocket?.close();
75
+ });
76
+
77
+ it('rejects with 403 for a cross-origin upgrade', async () => {
78
+ const response = await worker.fetch(upgradeRequest('https://evil.example.net'), autoEnv);
79
+ expect(response.status).toBe(403);
80
+ expect(response.webSocket).toBeNull();
81
+ });
82
+
83
+ it('proceeds (101) when the Origin header is missing (non-browser client)', async () => {
84
+ const response = await worker.fetch(upgradeRequest(), autoEnv);
85
+ expect(response.status).toBe(101);
86
+ response.webSocket?.accept();
87
+ response.webSocket?.close();
88
+ });
89
+ });
@@ -5,7 +5,7 @@ export default defineConfig({
5
5
  test: {
6
6
  include: ['tests/**/*.test.ts'],
7
7
  coverage: {
8
- provider: 'v8',
8
+ provider: 'istanbul',
9
9
  all: false,
10
10
  include: ['src/**/*.ts'],
11
11
  reporter: ['text', 'html', 'json-summary'],
@@ -11,10 +11,33 @@ main = "src/worker.ts"
11
11
  compatibility_date = "2024-11-01"
12
12
  compatibility_flags = ["nodejs_compat"]
13
13
 
14
+ # Static-assets binding. Production points at `../../apps/web/dist` (the Kiwi
15
+ # SPA + landing page built by `apps/web`); the test config points at a fixture
16
+ # so the smoke suite does not depend on the (slow) Kiwi upstream build. The
17
+ # fixture mirrors the `app/index.html` + `index.html` layout; the worker's
18
+ # fall-through routing (`/health` → plaintext, else → ASSETS) is what's under
19
+ # test, not the SPA contents.
20
+ [assets]
21
+ directory = "./tests/fixtures/web-dist"
22
+ binding = "ASSETS"
23
+ # `none` returns 404 for unmatched paths — the SPA lives at `/app/` and the
24
+ # landing page at `/`, both with real `index.html` files, so no SPA fallback
25
+ # is required.
26
+ not_found_handling = "none"
27
+ # Mirror production: route every request through the Worker first so WS
28
+ # upgrades at `/` aren't intercepted by the asset platform (which would
29
+ # serve `index.html` with 200 instead of a 101 upgrade).
30
+ run_worker_first = true
31
+
14
32
  [vars]
15
33
  SERVER_NAME = "irc.example.com"
16
34
  NETWORK_NAME = "ServerlessIRCd"
17
35
  MOTD_LINES = "Welcome to the cf-worker smoke test."
36
+ # CSWSH allowlist. The ws-origin integration suite asserts an upgrade from
37
+ # `https://example.com` proceeds and `https://evil.example.net` is rejected
38
+ # (the fail-closed branch overrides this per request via `{ ...env,
39
+ # WEB_ORIGINS: '' }`).
40
+ WEB_ORIGINS = "https://example.com"
18
41
 
19
42
  [[durable_objects.bindings]]
20
43
  name = "CONNECTION_DO"
@@ -45,16 +45,62 @@ enabled = true
45
45
  invocation_logs = true
46
46
 
47
47
  ## Observable per-deployment knobs. Override per environment below.
48
- ## Production deployments SHOULD override `SERVER_NAME` to the public
49
- ## hostname clients will see in numerics (001, 005, etc.).
48
+ ## `SERVER_NAME` is REQUIRED — the CF config loader
49
+ ## (`loadServerConfigFromCfEnv`) fails fast at boot when it is unset.
50
+ ## Production deployments MUST override it to the public hostname
51
+ ## clients see in numerics (001, 005, etc.).
52
+ ##
53
+ ## `WEB_ORIGINS` is the OPTIONAL comma-separated allowlist of web origins
54
+ ## permitted to open WebSocket upgrades (CSWSH defense). When set, only
55
+ ## listed origins proceed. When unset/empty (the default), the Worker
56
+ ## auto-derives the expected origin from the request's own `Host` header
57
+ ## (same-origin enforcement) — no per-env config needed. Set this only for
58
+ ## cross-origin deployments where the SPA is served from a different domain
59
+ ## than the Worker. Non-browser clients (curl, WeeChat, tcp-ws-forwarder)
60
+ ## omit `Origin` and pass through unchanged regardless.
50
61
  [vars]
51
- SERVER_NAME = "irc.example.com"
62
+ SERVER_NAME = "irc.your-domain.invalid"
52
63
  NETWORK_NAME = "ServerlessIRCd"
53
64
  MOTD_LINES = """
54
65
  Welcome to ServerlessIRCd.
55
66
  This is a deployed Cloudflare Worker.
56
67
  """
57
68
 
69
+ ## ---------------------------------------------------------------------------
70
+ ## Static assets — Kiwi SPA + landing page.
71
+ ## ---------------------------------------------------------------------------
72
+ ## The `[assets]` binding serves `apps/web/dist` (built by `pnpm --filter web
73
+ ## build`). The non-WS, non-`/health` branch of `worker.ts`'s `fetch` falls
74
+ ## through to this binding, so:
75
+ ## • `/app/` → the Kiwi IRC SPA (`dist/app/index.html` + its
76
+ ## hashed assets under `/app/assets/...`).
77
+ ## • `/` → the landing page (`dist/index.html`, shipped by
78
+ ## the `apps/web` build).
79
+ ## • `/health` → plaintext liveness (handled by the worker, not
80
+ ## assets).
81
+ ## The deploy workflow (`deploy-cf.yml`) runs `pnpm build` before `wrangler
82
+ ## deploy` so `apps/web/dist` is freshly populated. `wrangler dev` serves the
83
+ ## same asset layer against the locally-built `dist`.
84
+ [assets]
85
+ ## Relative to this file (`apps/cf-worker/`), so `../../apps/web/dist`.
86
+ directory = "../../apps/web/dist"
87
+ ## Expose the asset `Fetcher` on `env.ASSETS` (see `WorkerEnv` in `worker.ts`).
88
+ binding = "ASSETS"
89
+ ## Return 404 for unmatched paths. The SPA lives at `/app/` and the landing
90
+ ## page at `/`; both are concrete `index.html` files, so no SPA fallback is
91
+ ## required. Kiwi uses query/hash-based client routing, not path routing.
92
+ not_found_handling = "none"
93
+ ## Route EVERY request through the Worker before the asset platform serves a
94
+ ## matching static file. Without this, the default (`run_worker_first = false`)
95
+ ## causes the asset platform to serve `dist/index.html` at `/` with 200 OK for
96
+ ## WebSocket upgrade requests — the Worker's `Upgrade: websocket` check never
97
+ ## fires and the client sees "HTTP error: 200 OK" instead of a 101 upgrade.
98
+ ## The WS endpoint and the landing page share path `/`, so a path-pattern
99
+ ## exclusion can't tell them apart; the Worker must run first for everything.
100
+ ## The Worker falls through to `env.ASSETS.fetch(request)` for non-WS,
101
+ ## non-`/health` requests, so static content is unaffected.
102
+ run_worker_first = true
103
+
58
104
  ## ---------------------------------------------------------------------------
59
105
  ## Staging environment.
60
106
  ## ---------------------------------------------------------------------------
@@ -77,6 +123,16 @@ This is a deployed Cloudflare Worker.
77
123
  This Worker was deployed by CI from the main branch.
78
124
  """
79
125
 
126
+ # Staging serves the same `apps/web/dist` asset layer. Wrangler requires the
127
+ # per-env assets block so `--env staging` deploys both the Worker and its
128
+ # assets in a single `wrangler deploy --env staging` (the same `pnpm build`
129
+ # populates `dist/` ahead of the deploy step).
130
+ [env.staging.assets]
131
+ directory = "../../apps/web/dist"
132
+ binding = "ASSETS"
133
+ not_found_handling = "none"
134
+ run_worker_first = true
135
+
80
136
  ## ---------------------------------------------------------------------------
81
137
  ## D1 database — SASL account persistence.
82
138
  ## ---------------------------------------------------------------------------
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serverless-ircd/local-cli",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "private": true,
5
5
  "description": "Runnable WebSocket IRC server using the in-memory runtime; manual-test harness and e2e fixture target",
6
6
  "license": "BSD-3-Clause",
@@ -13,14 +13,14 @@ import { type ParsedServerConfig, parseServerConfig } from '@serverless-ircd/irc
13
13
 
14
14
  /**
15
15
  * CLI / programmatic input for `loadServerConfigFromCliArgs`. Every
16
- * field is optional except the two required schema fields
17
- * (`serverName`, `networkName`); the schema applies defaults to the
18
- * rest. Field types match the schema's input shape (numbers stay
19
- * numbers, etc.) so callers don't need to coerce strings.
16
+ * field is optional; the schema rejects a missing `serverName` or
17
+ * `networkName` with a readable error so the caller learns at boot
18
+ * rather than mid-session. Field types match the schema's input shape
19
+ * (numbers stay numbers, etc.) so callers don't need to coerce strings.
20
20
  */
21
21
  export interface CliConfigInput {
22
- serverName: string;
23
- networkName: string;
22
+ serverName?: string | undefined;
23
+ networkName?: string | undefined;
24
24
  /**
25
25
  * Server version surfaced in `002`/`004`/`351`/`371`. When omitted the
26
26
  * schema default ({@link DEFAULT_SERVER_VERSION}) applies.
@@ -31,9 +31,15 @@ interface CliArgs {
31
31
  tcpPort: number | undefined;
32
32
  /** undefined when --motd-file is not given; built-in default MOTD applies. */
33
33
  motdFile: string | undefined;
34
+ /** Server identity shown in 001/005 numerics. */
35
+ serverName: string;
36
+ /** Network name advertised in 005 NETWORK=... */
37
+ networkName: string;
34
38
  }
35
39
 
36
40
  const DEFAULT_PORT = 6667;
41
+ const DEFAULT_SERVER_NAME = 'irc.localhost';
42
+ const DEFAULT_NETWORK_NAME = 'LocalNet';
37
43
 
38
44
  function parseArgs(argv: string[]): CliArgs {
39
45
  const out: CliArgs = {
@@ -41,6 +47,8 @@ function parseArgs(argv: string[]): CliArgs {
41
47
  hostname: '127.0.0.1',
42
48
  tcpPort: undefined,
43
49
  motdFile: undefined,
50
+ serverName: DEFAULT_SERVER_NAME,
51
+ networkName: DEFAULT_NETWORK_NAME,
44
52
  };
45
53
  let tcpDisabled = false;
46
54
  let tcpPortOverride: number | undefined;
@@ -59,6 +67,12 @@ function parseArgs(argv: string[]): CliArgs {
59
67
  } else if (a === '--motd-file' && next !== undefined) {
60
68
  out.motdFile = next;
61
69
  i++;
70
+ } else if (a === '--server-name' && next !== undefined) {
71
+ out.serverName = next;
72
+ i++;
73
+ } else if (a === '--network-name' && next !== undefined) {
74
+ out.networkName = next;
75
+ i++;
62
76
  } else if (a === '--no-tcp') {
63
77
  tcpDisabled = true;
64
78
  } else if (a === '--help' || a === '-h') {
@@ -74,6 +88,10 @@ function parseArgs(argv: string[]): CliArgs {
74
88
  ' --no-tcp Disable the TCP listener (WebSocket only).',
75
89
  ' --motd-file <path> Read MOTD lines from this file (one per line).',
76
90
  ' Optional; overrides the built-in default MOTD.',
91
+ ' --server-name <h> Server hostname in 001/005 numerics.',
92
+ ' Optional; defaults to irc.localhost for the local CLI.',
93
+ ' --network-name <n> Network name in 005 NETWORK=...',
94
+ ' Optional; defaults to LocalNet.',
77
95
  ' -h, --help Show this help and exit.',
78
96
  '',
79
97
  ].join('\n'),
@@ -99,6 +117,8 @@ async function main(): Promise<void> {
99
117
  port: args.port,
100
118
  hostname: args.hostname,
101
119
  tcpPort: args.tcpPort,
120
+ serverName: args.serverName,
121
+ networkName: args.networkName,
102
122
  ...(motdLines !== undefined ? { motdLines } : {}),
103
123
  });
104
124
 
@@ -235,9 +235,9 @@ const DEFAULT_CONFIG: Omit<
235
235
  | 'perIpConnectionRate'
236
236
  | 'historyMaxPerChannel'
237
237
  | 'saslAccounts'
238
+ | 'serverName'
239
+ | 'networkName'
238
240
  > = {
239
- serverName: 'irc.example.com',
240
- networkName: 'ExampleNet',
241
241
  motdLines: ['Welcome to the local-cli ServerlessIRCd instance.'],
242
242
  maxChannelsPerUser: 30,
243
243
  maxTargetsPerCommand: 10,
@@ -279,14 +279,16 @@ export function startLocalServer(opts: StartServerOptions): Promise<LocalServer>
279
279
  // Validate the user-supplied config through the shared ServerConfigSchema
280
280
  // (sourced from `irc-core`). Both adapters consume the same schema; here
281
281
  // we discard the parsed result and only retain the side-effect of
282
- // throwing on a malformed config at boot. The actor still consumes the
283
- // `LocalServerConfig` projection so existing callers (and the e2e
284
- // harness) keep their permissive defaults.
282
+ // throwing on a malformed config at boot. `serverName` and `networkName`
283
+ // are required by the schema passing them through as `undefined` when
284
+ // the caller omits them lets the loader fail fast with a readable error
285
+ // pointing at the missing knob, so the server never boots advertising a
286
+ // placeholder identity.
285
287
  let parsed: ReturnType<typeof loadServerConfigFromCliArgs>;
286
288
  try {
287
289
  parsed = loadServerConfigFromCliArgs({
288
- serverName: opts.serverName ?? DEFAULT_CONFIG.serverName,
289
- networkName: opts.networkName ?? DEFAULT_CONFIG.networkName,
290
+ serverName: opts.serverName,
291
+ networkName: opts.networkName,
290
292
  ...(opts.motdLines !== undefined ? { motdLines: opts.motdLines } : {}),
291
293
  ...(opts.maxChannelsPerUser !== undefined
292
294
  ? { maxChannelsPerUser: opts.maxChannelsPerUser }