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.
- package/.github/workflows/ci.yml +40 -3
- package/.github/workflows/deploy-aws.yml +1 -2
- package/.github/workflows/deploy-cf-tcp.yml +1 -5
- package/.github/workflows/deploy-cf.yml +1 -2
- package/.gitmodules +3 -0
- package/CHANGELOG.md +474 -0
- package/README.md +219 -22
- package/apps/aws-stack/README.md +33 -0
- package/apps/aws-stack/package.json +1 -1
- package/apps/aws-stack/src/aws-stack.ts +74 -9
- package/apps/aws-stack/tests/coverage-command.test.ts +155 -0
- package/apps/aws-stack/tests/stack.test.ts +2 -2
- package/apps/aws-stack/tests/synth-no-bundle.test.ts +129 -0
- package/apps/aws-stack/tests/vitest.setup.ts +18 -0
- package/apps/aws-stack/vitest.config.ts +3 -0
- package/apps/cf-tcp-container/package.json +1 -1
- package/apps/cf-tcp-container/src/config-loader.ts +6 -7
- package/apps/cf-tcp-container/src/container-server.ts +3 -0
- package/apps/cf-tcp-container/tests/config-loader.test.ts +10 -3
- package/apps/cf-tcp-container/wrangler.toml +1 -1
- package/apps/cf-worker/package.json +2 -1
- package/apps/cf-worker/src/origin-allowlist.ts +99 -0
- package/apps/cf-worker/src/worker.ts +89 -9
- package/apps/cf-worker/tests/fixtures/web-dist/app/index.html +18 -0
- package/apps/cf-worker/tests/fixtures/web-dist/index.html +18 -0
- package/apps/cf-worker/tests/origin-allowlist.test.ts +101 -0
- package/apps/cf-worker/tests/smoke.test.ts +28 -2
- package/apps/cf-worker/tests/ws-origin.test.ts +89 -0
- package/apps/cf-worker/vitest.config.ts +1 -1
- package/apps/cf-worker/wrangler.test.toml +23 -0
- package/apps/cf-worker/wrangler.toml +59 -3
- package/apps/local-cli/package.json +1 -1
- package/apps/local-cli/src/config-loader.ts +6 -6
- package/apps/local-cli/src/main.ts +20 -0
- package/apps/local-cli/src/server.ts +9 -7
- package/apps/local-cli/tests/e2e.test.ts +112 -28
- package/apps/local-cli/tests/tcp.test.ts +71 -19
- package/apps/local-cli/tests/ws-subprotocol.test.ts +12 -7
- package/apps/web/landing/index.html +217 -0
- package/apps/web/package.json +28 -0
- package/apps/web/scripts/build.mjs +125 -0
- package/apps/web/src/build-env.ts +43 -0
- package/apps/web/src/config-schema.ts +138 -0
- package/apps/web/static/config.json +28 -0
- package/apps/web/static/config.prod.json +28 -0
- package/apps/web/static/config.staging.json +28 -0
- package/apps/web/tests/build-env.test.ts +63 -0
- package/apps/web/tests/build-smoke.test.ts +103 -0
- package/apps/web/tests/config-schema.test.ts +432 -0
- package/apps/web/tests/workspace.test.ts +12 -0
- package/apps/web/tsconfig.json +10 -0
- package/apps/web/upstream/.browserslistrc +3 -0
- package/apps/web/upstream/.editorconfig +9 -0
- package/apps/web/upstream/.eslintignore +3 -0
- package/apps/web/upstream/.eslintrc.js +90 -0
- package/apps/web/upstream/.github/FUNDING.yml +12 -0
- package/apps/web/upstream/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- package/apps/web/upstream/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/apps/web/upstream/.github/workflows/push.yml +26 -0
- package/apps/web/upstream/.github/workflows/release.yml +33 -0
- package/apps/web/upstream/.github/workflows/release_manual.yml +55 -0
- package/apps/web/upstream/.prettierrc.js +9 -0
- package/apps/web/upstream/.stylelintignore +5 -0
- package/apps/web/upstream/.stylelintrc.js +21 -0
- package/apps/web/upstream/LICENSE +201 -0
- package/apps/web/upstream/README.md +76 -0
- package/apps/web/upstream/babel.config.js +25 -0
- package/apps/web/upstream/crowdin.yml +3 -0
- package/apps/web/upstream/index.html +35 -0
- package/apps/web/upstream/jest.config.js +7 -0
- package/apps/web/upstream/jsdoc.json +22 -0
- package/apps/web/upstream/package.json +80 -0
- package/apps/web/upstream/scripts/pre-commit +43 -0
- package/apps/web/upstream/src/components/App.vue +516 -0
- package/apps/web/upstream/src/components/AppSettings.vue +587 -0
- package/apps/web/upstream/src/components/AutoComplete.vue +284 -0
- package/apps/web/upstream/src/components/Avatar.vue +105 -0
- package/apps/web/upstream/src/components/AwayStatusIndicator.vue +77 -0
- package/apps/web/upstream/src/components/BufferKey.vue +121 -0
- package/apps/web/upstream/src/components/BufferSettings.vue +97 -0
- package/apps/web/upstream/src/components/Captcha.vue +85 -0
- package/apps/web/upstream/src/components/ChannelBanlist.vue +96 -0
- package/apps/web/upstream/src/components/ChannelInfo.vue +159 -0
- package/apps/web/upstream/src/components/ChannelInvitelist.vue +276 -0
- package/apps/web/upstream/src/components/ChannelList.vue +345 -0
- package/apps/web/upstream/src/components/Container.vue +401 -0
- package/apps/web/upstream/src/components/ContainerHeader.vue +522 -0
- package/apps/web/upstream/src/components/ControlInput.vue +1016 -0
- package/apps/web/upstream/src/components/LoadingAnimation.vue +104 -0
- package/apps/web/upstream/src/components/MediaViewer.vue +199 -0
- package/apps/web/upstream/src/components/MessageInfo.vue +192 -0
- package/apps/web/upstream/src/components/MessageList.vue +1028 -0
- package/apps/web/upstream/src/components/MessageListAvatar.vue +20 -0
- package/apps/web/upstream/src/components/MessageListMessageCompact.vue +361 -0
- package/apps/web/upstream/src/components/MessageListMessageInline.vue +257 -0
- package/apps/web/upstream/src/components/MessageListMessageModern.vue +430 -0
- package/apps/web/upstream/src/components/NetworkSettings.vue +528 -0
- package/apps/web/upstream/src/components/Nicklist.vue +309 -0
- package/apps/web/upstream/src/components/NicklistUser.vue +181 -0
- package/apps/web/upstream/src/components/NotConnected.vue +271 -0
- package/apps/web/upstream/src/components/SelfUser.vue +283 -0
- package/apps/web/upstream/src/components/ServerSelector.vue +162 -0
- package/apps/web/upstream/src/components/ServerView.vue +126 -0
- package/apps/web/upstream/src/components/SettingsAdvanced.vue +232 -0
- package/apps/web/upstream/src/components/SettingsAliases.vue +121 -0
- package/apps/web/upstream/src/components/Sidebar.vue +429 -0
- package/apps/web/upstream/src/components/SidebarAboutBuffer.vue +288 -0
- package/apps/web/upstream/src/components/SidebarState.vue +145 -0
- package/apps/web/upstream/src/components/StartupError.vue +35 -0
- package/apps/web/upstream/src/components/StateBrowser.vue +420 -0
- package/apps/web/upstream/src/components/StateBrowserBuffer.vue +124 -0
- package/apps/web/upstream/src/components/StateBrowserNetwork.vue +823 -0
- package/apps/web/upstream/src/components/StateBrowserUsermenu.vue +162 -0
- package/apps/web/upstream/src/components/TypingStatusIndicator.vue +34 -0
- package/apps/web/upstream/src/components/TypingUsersList.vue +56 -0
- package/apps/web/upstream/src/components/UrlEmbed.vue +149 -0
- package/apps/web/upstream/src/components/UserBox.vue +728 -0
- package/apps/web/upstream/src/components/inputtools/Emoji.vue +67 -0
- package/apps/web/upstream/src/components/inputtools/TextStyle.vue +142 -0
- package/apps/web/upstream/src/components/startups/CommonLayout.vue +208 -0
- package/apps/web/upstream/src/components/startups/CustomServer.vue +495 -0
- package/apps/web/upstream/src/components/startups/KiwiBnc.vue +361 -0
- package/apps/web/upstream/src/components/startups/Personal.vue +317 -0
- package/apps/web/upstream/src/components/startups/Welcome.vue +544 -0
- package/apps/web/upstream/src/components/startups/ZncLogin.vue +229 -0
- package/apps/web/upstream/src/components/utils/InputConfirm.vue +68 -0
- package/apps/web/upstream/src/components/utils/InputPrompt.vue +118 -0
- package/apps/web/upstream/src/components/utils/InputText.vue +172 -0
- package/apps/web/upstream/src/components/utils/IrcInput.vue +579 -0
- package/apps/web/upstream/src/components/utils/PluginWrapper.vue +26 -0
- package/apps/web/upstream/src/components/utils/TabbedView.vue +149 -0
- package/apps/web/upstream/src/components/utils/TransitionExpand.vue +97 -0
- package/apps/web/upstream/src/helpers/Colours.js +128 -0
- package/apps/web/upstream/src/helpers/IrcdDiffs.js +26 -0
- package/apps/web/upstream/src/helpers/Md5.js +193 -0
- package/apps/web/upstream/src/helpers/Misc.js +401 -0
- package/apps/web/upstream/src/helpers/TextFormatting.js +237 -0
- package/apps/web/upstream/src/libs/AliasRewriter.js +157 -0
- package/apps/web/upstream/src/libs/AudioManager.js +60 -0
- package/apps/web/upstream/src/libs/BouncerMiddleware.js +247 -0
- package/apps/web/upstream/src/libs/BouncerProvider.js +551 -0
- package/apps/web/upstream/src/libs/ChathistoryMiddleware.js +153 -0
- package/apps/web/upstream/src/libs/ConfigLoader.js +94 -0
- package/apps/web/upstream/src/libs/EmojiProvider.js +46 -0
- package/apps/web/upstream/src/libs/GlobalApi.js +346 -0
- package/apps/web/upstream/src/libs/IPC.js +51 -0
- package/apps/web/upstream/src/libs/InputHandler.js +921 -0
- package/apps/web/upstream/src/libs/IrcClient.js +1506 -0
- package/apps/web/upstream/src/libs/Logger.js +71 -0
- package/apps/web/upstream/src/libs/Message.js +169 -0
- package/apps/web/upstream/src/libs/MessageFormatter.js +364 -0
- package/apps/web/upstream/src/libs/MessageParser.js +249 -0
- package/apps/web/upstream/src/libs/Notifications.js +78 -0
- package/apps/web/upstream/src/libs/ServerConnection.js +270 -0
- package/apps/web/upstream/src/libs/ServerSession.js +102 -0
- package/apps/web/upstream/src/libs/SoundBleep.js +20 -0
- package/apps/web/upstream/src/libs/StatePersistence.js +90 -0
- package/apps/web/upstream/src/libs/ThemeManager.js +128 -0
- package/apps/web/upstream/src/libs/TypingMiddleware.js +105 -0
- package/apps/web/upstream/src/libs/WindowTitle.js +63 -0
- package/apps/web/upstream/src/libs/batchedAdd.js +74 -0
- package/apps/web/upstream/src/libs/bufferTools.js +177 -0
- package/apps/web/upstream/src/libs/polyfill/Element.closest.js +18 -0
- package/apps/web/upstream/src/libs/renderers/Html.js +113 -0
- package/apps/web/upstream/src/libs/settingTools.js +31 -0
- package/apps/web/upstream/src/libs/state/BufferState.js +703 -0
- package/apps/web/upstream/src/libs/state/NetworkState.js +158 -0
- package/apps/web/upstream/src/libs/state/UserState.js +89 -0
- package/apps/web/upstream/src/libs/state/common.js +26 -0
- package/apps/web/upstream/src/libs/state.js +961 -0
- package/apps/web/upstream/src/libs/storage/Local.js +51 -0
- package/apps/web/upstream/src/main.js +566 -0
- package/apps/web/upstream/src/res/autocompleteCommands.js +31 -0
- package/apps/web/upstream/src/res/configTemplates.js +373 -0
- package/apps/web/upstream/src/res/globalStyle.css +277 -0
- package/apps/web/upstream/src/res/kiwiLoadingLogo.png +0 -0
- package/apps/web/upstream/src/res/locales/app.af-ZA.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ar-SA.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.bg-BG.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.bs-BA.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ca-ES.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.cs-CZ.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.da-DK.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.de-DE.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.dev.po +1246 -0
- package/apps/web/upstream/src/res/locales/app.el-GR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.en-US.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.es-419.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.es-AR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.es-EM.po +536 -0
- package/apps/web/upstream/src/res/locales/app.es-ES.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.es-US.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.eu-ES.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.fi-FI.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.fr-FR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.gl-ES.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.he-IL.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.hi-IN.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.hu-HU.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.id-ID.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.it-IT.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ja-JP.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ko-KR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.nl-NL.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.no-NO.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.pl-PL.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.pt-BR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.pt-PT.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ro-RO.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ru-RU.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.sl-SI.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.sq-AL.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.sr-SP.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.sv-SE.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.tr-TR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.uk-UA.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.vi-VN.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.zh-CN.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.zh-TW.po +1145 -0
- package/apps/web/upstream/src/res/localesList.json +27 -0
- package/apps/web/upstream/src/res/logo.png +0 -0
- package/apps/web/upstream/src/thirdparty/about.html +43 -0
- package/apps/web/upstream/src/thirdparty/index.js +5 -0
- package/apps/web/upstream/src/thirdparty/kiwiirccom.vue +185 -0
- package/apps/web/upstream/static/config.json +28 -0
- package/apps/web/upstream/static/emoticons/smile.png +0 -0
- package/apps/web/upstream/static/favicon.png +0 -0
- package/apps/web/upstream/static/highlight.mp3 +0 -0
- package/apps/web/upstream/static/highlight.ogg +0 -0
- package/apps/web/upstream/static/locales/.gitignore +2 -0
- package/apps/web/upstream/static/plugins/customise.html.example +10 -0
- package/apps/web/upstream/static/themes/coffee/theme.css +145 -0
- package/apps/web/upstream/static/themes/common/base.css +1064 -0
- package/apps/web/upstream/static/themes/dark/theme.css +196 -0
- package/apps/web/upstream/static/themes/default/theme.css +92 -0
- package/apps/web/upstream/static/themes/elite/theme.css +248 -0
- package/apps/web/upstream/static/themes/grayfox/theme.css +143 -0
- package/apps/web/upstream/static/themes/nightswatch/theme.css +367 -0
- package/apps/web/upstream/static/themes/osprey/theme.css +115 -0
- package/apps/web/upstream/static/themes/radioactive/theme.css +1211 -0
- package/apps/web/upstream/static/themes/sky/theme.css +107 -0
- package/apps/web/upstream/tests/unit/BatchAdd.spec.js +174 -0
- package/apps/web/upstream/tests/unit/MessageParser.spec.js +125 -0
- package/apps/web/upstream/tests/unit/Misc.spec.js +24 -0
- package/apps/web/upstream/tests/unit/NetworkState.spec.js +58 -0
- package/apps/web/upstream/tests/unit/StartupError.spec.js +20 -0
- package/apps/web/upstream/vue.config.js +150 -0
- package/apps/web/upstream/yarn.lock +9767 -0
- package/apps/web/vitest.config.ts +13 -0
- package/biome.json +2 -0
- package/docs/ADR-001-pure-reducers-and-effect-system.md +74 -0
- package/docs/ADR-002-location-of-authority.md +82 -0
- package/docs/ADR-003-durable-object-sharding.md +110 -0
- package/docs/ADR-004-dynamodb-schema.md +96 -0
- package/docs/ADR-005-wss-only-transport-v1.md +83 -0
- package/docs/ADR-006-sasl-mechanism-scope.md +96 -0
- package/docs/ADR-007-deterministic-ports.md +82 -0
- package/docs/ADR-008-monorepo-tooling.md +60 -0
- package/docs/ADR-009-dual-transport-wss-and-irc-tls.md +138 -0
- package/docs/ADR-Index.md +15 -0
- package/docs/AWS-Adapter-Architecture.md +496 -0
- package/docs/AWS-Deployment.md +1275 -0
- package/docs/AWS-TCP-Deployment.md +410 -0
- package/docs/Cloudflare-Deployment-Guide.md +719 -0
- package/docs/Cloudflare-TCP-Deployment.md +510 -0
- package/docs/Home.md +16 -0
- package/docs/Observability.md +87 -0
- package/docs/PlanExtensions.md +812 -0
- package/docs/PlanIRCv3Websocket.md +503 -0
- package/docs/PlanWebClient.md +451 -0
- package/docs/Release-Process.md +443 -0
- package/docs/WebClientGuide.md +535 -0
- package/package.json +2 -2
- package/packages/aws-adapter/package.json +1 -1
- package/packages/aws-adapter/src/handlers/default.ts +3 -0
- package/packages/aws-adapter/src/handlers/nlb-stream.ts +9 -0
- package/packages/aws-adapter/src/serialize.ts +29 -1
- package/packages/aws-adapter/tests/account-store-dynamo.test.ts +49 -8
- package/packages/aws-adapter/tests/account-store.test.ts +4 -7
- package/packages/aws-adapter/tests/aws-harness.ts +5 -2
- package/packages/aws-adapter/tests/aws-integration.test.ts +8 -4
- package/packages/aws-adapter/tests/aws-runtime.test.ts +79 -0
- package/packages/aws-adapter/tests/config-loader.test.ts +11 -2
- package/packages/aws-adapter/tests/connect.test.ts +9 -9
- package/packages/aws-adapter/tests/disconnect-fanout.test.ts +3 -4
- package/packages/aws-adapter/tests/global-setup.ts +55 -30
- package/packages/aws-adapter/tests/gone-exception.test.ts +4 -9
- package/packages/aws-adapter/tests/handlers.test.ts +90 -4
- package/packages/aws-adapter/tests/message-store.test.ts +6 -5
- package/packages/aws-adapter/tests/nlb-stream.test.ts +60 -8
- package/packages/cf-adapter/package.json +2 -1
- package/packages/cf-adapter/src/connection-do.ts +3 -0
- package/packages/cf-adapter/src/serialize.ts +25 -4
- package/packages/cf-adapter/tests/cf-harness.ts +12 -1
- package/packages/cf-adapter/tests/cf-runtime.test.ts +269 -0
- package/packages/cf-adapter/tests/channel-do.test.ts +117 -0
- package/packages/cf-adapter/tests/config-loader.test.ts +49 -0
- package/packages/cf-adapter/tests/connection-do-pure.test.ts +91 -0
- package/packages/cf-adapter/tests/d1-account-store.test.ts +48 -0
- package/packages/cf-adapter/tests/registry-do.test.ts +104 -0
- package/packages/cf-adapter/tests/serialize.test.ts +308 -0
- package/packages/cf-adapter/vitest.config.ts +1 -1
- package/packages/in-memory-runtime/package.json +1 -1
- package/packages/irc-core/package.json +1 -1
- package/packages/irc-core/reports/mutation/mutation.html +342 -0
- package/packages/irc-core/src/caps/capabilities.ts +37 -3
- package/packages/irc-core/src/caps/index.ts +1 -0
- package/packages/irc-core/src/caps/sts.ts +84 -0
- package/packages/irc-core/src/commands/account-notify.ts +53 -0
- package/packages/irc-core/src/commands/away.ts +9 -3
- package/packages/irc-core/src/commands/cap.ts +23 -5
- package/packages/irc-core/src/commands/chathistory.ts +40 -11
- package/packages/irc-core/src/commands/index.ts +34 -0
- package/packages/irc-core/src/commands/isupport.ts +53 -0
- package/packages/irc-core/src/commands/mode.ts +8 -1
- package/packages/irc-core/src/commands/monitor.ts +327 -0
- package/packages/irc-core/src/commands/multiline.ts +256 -0
- package/packages/irc-core/src/commands/pre-away.ts +112 -0
- package/packages/irc-core/src/commands/privmsg.ts +19 -12
- package/packages/irc-core/src/commands/read-marker.ts +108 -0
- package/packages/irc-core/src/commands/registration.ts +8 -0
- package/packages/irc-core/src/commands/sasl.ts +24 -1
- package/packages/irc-core/src/commands/tagmsg.ts +71 -1
- package/packages/irc-core/src/commands/whois.ts +5 -0
- package/packages/irc-core/src/config.ts +65 -10
- package/packages/irc-core/src/effects.ts +14 -0
- package/packages/irc-core/src/ports.ts +389 -0
- package/packages/irc-core/src/protocol/index.ts +14 -0
- package/packages/irc-core/src/protocol/labeled-response.ts +133 -0
- package/packages/irc-core/src/protocol/numerics.ts +15 -0
- package/packages/irc-core/src/protocol/outbound.ts +16 -1
- package/packages/irc-core/src/protocol/standard-replies.ts +277 -0
- package/packages/irc-core/src/state/connection.ts +32 -1
- package/packages/irc-core/src/types.ts +112 -0
- package/packages/irc-core/stryker.commands.conf.json +1 -2
- package/packages/irc-core/tests/away-store.test.ts +73 -0
- package/packages/irc-core/tests/batch.test.ts +15 -0
- package/packages/irc-core/tests/caps/capabilities.test.ts +119 -2
- package/packages/irc-core/tests/caps/sts.test.ts +123 -0
- package/packages/irc-core/tests/commands/account-notify.test.ts +171 -0
- package/packages/irc-core/tests/commands/away.test.ts +85 -2
- package/packages/irc-core/tests/commands/cap.test.ts +165 -0
- package/packages/irc-core/tests/commands/chathistory.test.ts +148 -1
- package/packages/irc-core/tests/commands/echo-message.test.ts +9 -3
- package/packages/irc-core/tests/commands/isupport.test.ts +80 -0
- package/packages/irc-core/tests/commands/mode.test.ts +72 -0
- package/packages/irc-core/tests/commands/monitor.test.ts +411 -0
- package/packages/irc-core/tests/commands/multiline.test.ts +428 -0
- package/packages/irc-core/tests/commands/pre-away.test.ts +241 -0
- package/packages/irc-core/tests/commands/privmsg.test.ts +154 -3
- package/packages/irc-core/tests/commands/read-marker.test.ts +211 -0
- package/packages/irc-core/tests/commands/registration.test.ts +64 -0
- package/packages/irc-core/tests/commands/sasl.test.ts +116 -2
- package/packages/irc-core/tests/commands/tagmsg.test.ts +357 -0
- package/packages/irc-core/tests/commands/whois.test.ts +34 -0
- package/packages/irc-core/tests/config.test.ts +103 -13
- package/packages/irc-core/tests/labeled-response.test.ts +181 -0
- package/packages/irc-core/tests/message-tags.test.ts +114 -0
- package/packages/irc-core/tests/monitor-store.test.ts +200 -0
- package/packages/irc-core/tests/numerics.test.ts +13 -0
- package/packages/irc-core/tests/parser.test.ts +109 -0
- package/packages/irc-core/tests/read-marker-store.test.ts +108 -0
- package/packages/irc-core/tests/standard-replies.test.ts +412 -0
- package/packages/irc-core/tests/types.test.ts +27 -0
- package/packages/irc-server/package.json +1 -1
- package/packages/irc-server/src/actor.ts +450 -3
- package/packages/irc-server/src/dispatch.ts +108 -16
- package/packages/irc-server/tests/actor.test.ts +1432 -39
- package/packages/irc-server/tests/dispatch.test.ts +263 -2
- package/packages/irc-test-support/package.json +1 -1
- package/packages/irc-test-support/src/index.ts +6 -0
- package/packages/irc-test-support/src/scenarios.ts +24 -2
- package/packages/irc-test-support/src/test-config.ts +54 -0
- package/packages/irc-test-support/tests/test-config.test.ts +51 -0
- package/tools/ci-hardening/package.json +1 -1
- package/tools/ci-hardening/tests/ddb-local.test.ts +82 -0
- package/tools/load-test/package.json +33 -0
- package/tools/load-test/src/client.ts +351 -0
- package/tools/load-test/src/config.ts +313 -0
- package/tools/load-test/src/harness.ts +116 -0
- package/tools/load-test/src/main.ts +120 -0
- package/tools/load-test/src/metrics.ts +168 -0
- package/tools/load-test/src/report.ts +106 -0
- package/tools/load-test/tests/client.test.ts +212 -0
- package/tools/load-test/tests/config.test.ts +152 -0
- package/tools/load-test/tests/framing.test.ts +37 -0
- package/tools/load-test/tests/harness.test.ts +165 -0
- package/tools/load-test/tests/metrics.test.ts +174 -0
- package/tools/load-test/tests/report.test.ts +161 -0
- package/tools/load-test/tests/smoke.test.ts +67 -0
- package/tools/load-test/tsconfig.build.json +12 -0
- package/tools/load-test/tsconfig.test.json +10 -0
- package/tools/load-test/vitest.config.ts +29 -0
- package/tools/tcp-ws-forwarder/package.json +1 -1
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<!--
|
|
3
|
+
ServerlessIRCd — static landing page (project front door).
|
|
4
|
+
|
|
5
|
+
Served at `/` by the Worker `[assets]` binding (apps/cf-worker/wrangler.toml).
|
|
6
|
+
The build (`scripts/build.mjs`) copies this file verbatim to `dist/index.html`.
|
|
7
|
+
The Kiwi IRC SPA lives at `/app/`; this page links through to it plus the
|
|
8
|
+
project docs and source repo. Pure static HTML + inline CSS, no JavaScript.
|
|
9
|
+
-->
|
|
10
|
+
<html lang="en">
|
|
11
|
+
<head>
|
|
12
|
+
<meta charset="utf-8" />
|
|
13
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
14
|
+
<title>ServerlessIRCd — Serverless IRC Daemon</title>
|
|
15
|
+
<meta
|
|
16
|
+
name="description"
|
|
17
|
+
content="A serverless IRC daemon: one TypeScript core, running on Cloudflare Workers or AWS."
|
|
18
|
+
/>
|
|
19
|
+
<style>
|
|
20
|
+
:root {
|
|
21
|
+
--bg: #0d1117;
|
|
22
|
+
--surface: #161b22;
|
|
23
|
+
--border: #30363d;
|
|
24
|
+
--text: #e6edf3;
|
|
25
|
+
--muted: #8b949e;
|
|
26
|
+
--accent: #58a6ff;
|
|
27
|
+
--accent-hover: #79c0ff;
|
|
28
|
+
--mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
|
|
29
|
+
"Liberation Mono", monospace;
|
|
30
|
+
--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial,
|
|
31
|
+
sans-serif;
|
|
32
|
+
}
|
|
33
|
+
* {
|
|
34
|
+
box-sizing: border-box;
|
|
35
|
+
}
|
|
36
|
+
html {
|
|
37
|
+
-webkit-text-size-adjust: 100%;
|
|
38
|
+
}
|
|
39
|
+
body {
|
|
40
|
+
margin: 0;
|
|
41
|
+
font-family: var(--sans);
|
|
42
|
+
line-height: 1.6;
|
|
43
|
+
color: var(--text);
|
|
44
|
+
background-color: var(--bg);
|
|
45
|
+
display: flex;
|
|
46
|
+
flex-direction: column;
|
|
47
|
+
min-height: 100vh;
|
|
48
|
+
}
|
|
49
|
+
.container {
|
|
50
|
+
width: 100%;
|
|
51
|
+
max-width: 720px;
|
|
52
|
+
margin: 0 auto;
|
|
53
|
+
padding: 0 20px;
|
|
54
|
+
}
|
|
55
|
+
header {
|
|
56
|
+
padding: 32px 0 16px;
|
|
57
|
+
border-bottom: 1px solid var(--border);
|
|
58
|
+
}
|
|
59
|
+
header h1 {
|
|
60
|
+
margin: 0;
|
|
61
|
+
font-size: 1.75rem;
|
|
62
|
+
letter-spacing: -0.02em;
|
|
63
|
+
}
|
|
64
|
+
header h1 .irc {
|
|
65
|
+
color: var(--accent);
|
|
66
|
+
font-family: var(--mono);
|
|
67
|
+
}
|
|
68
|
+
header p {
|
|
69
|
+
margin: 8px 0 0;
|
|
70
|
+
color: var(--muted);
|
|
71
|
+
font-size: 1.05rem;
|
|
72
|
+
}
|
|
73
|
+
main {
|
|
74
|
+
flex: 1 0 auto;
|
|
75
|
+
padding: 32px 0;
|
|
76
|
+
}
|
|
77
|
+
main p {
|
|
78
|
+
font-size: 1.05rem;
|
|
79
|
+
}
|
|
80
|
+
.cta {
|
|
81
|
+
display: flex;
|
|
82
|
+
flex-wrap: wrap;
|
|
83
|
+
gap: 12px;
|
|
84
|
+
margin: 28px 0;
|
|
85
|
+
}
|
|
86
|
+
.cta a {
|
|
87
|
+
display: inline-block;
|
|
88
|
+
padding: 12px 22px;
|
|
89
|
+
border-radius: 8px;
|
|
90
|
+
text-decoration: none;
|
|
91
|
+
font-weight: 600;
|
|
92
|
+
font-size: 1.02rem;
|
|
93
|
+
border: 1px solid var(--border);
|
|
94
|
+
transition: background-color 0.15s, border-color 0.15s;
|
|
95
|
+
}
|
|
96
|
+
.cta a.primary {
|
|
97
|
+
background-color: var(--accent);
|
|
98
|
+
color: #0d1117;
|
|
99
|
+
border-color: var(--accent);
|
|
100
|
+
}
|
|
101
|
+
.cta a.primary:hover {
|
|
102
|
+
background-color: var(--accent-hover);
|
|
103
|
+
border-color: var(--accent-hover);
|
|
104
|
+
}
|
|
105
|
+
.cta a.secondary {
|
|
106
|
+
background-color: var(--surface);
|
|
107
|
+
color: var(--text);
|
|
108
|
+
}
|
|
109
|
+
.cta a.secondary:hover {
|
|
110
|
+
border-color: var(--accent);
|
|
111
|
+
}
|
|
112
|
+
ul.features {
|
|
113
|
+
list-style: none;
|
|
114
|
+
padding: 0;
|
|
115
|
+
margin: 24px 0;
|
|
116
|
+
display: grid;
|
|
117
|
+
gap: 10px;
|
|
118
|
+
}
|
|
119
|
+
ul.features li {
|
|
120
|
+
padding-left: 24px;
|
|
121
|
+
position: relative;
|
|
122
|
+
color: var(--text);
|
|
123
|
+
}
|
|
124
|
+
ul.features li::before {
|
|
125
|
+
content: "\203A";
|
|
126
|
+
position: absolute;
|
|
127
|
+
left: 0;
|
|
128
|
+
color: var(--accent);
|
|
129
|
+
font-weight: 700;
|
|
130
|
+
}
|
|
131
|
+
code {
|
|
132
|
+
font-family: var(--mono);
|
|
133
|
+
background-color: var(--surface);
|
|
134
|
+
padding: 2px 6px;
|
|
135
|
+
border-radius: 4px;
|
|
136
|
+
font-size: 0.9em;
|
|
137
|
+
}
|
|
138
|
+
.links {
|
|
139
|
+
margin-top: 28px;
|
|
140
|
+
padding: 20px 0;
|
|
141
|
+
border-top: 1px solid var(--border);
|
|
142
|
+
display: flex;
|
|
143
|
+
flex-wrap: wrap;
|
|
144
|
+
gap: 8px 24px;
|
|
145
|
+
}
|
|
146
|
+
.links a {
|
|
147
|
+
color: var(--accent);
|
|
148
|
+
text-decoration: none;
|
|
149
|
+
}
|
|
150
|
+
.links a:hover {
|
|
151
|
+
text-decoration: underline;
|
|
152
|
+
}
|
|
153
|
+
footer {
|
|
154
|
+
flex-shrink: 0;
|
|
155
|
+
padding: 20px 0;
|
|
156
|
+
border-top: 1px solid var(--border);
|
|
157
|
+
color: var(--muted);
|
|
158
|
+
font-size: 0.9rem;
|
|
159
|
+
}
|
|
160
|
+
footer a {
|
|
161
|
+
color: var(--muted);
|
|
162
|
+
text-decoration: none;
|
|
163
|
+
}
|
|
164
|
+
footer a:hover {
|
|
165
|
+
color: var(--accent);
|
|
166
|
+
}
|
|
167
|
+
</style>
|
|
168
|
+
</head>
|
|
169
|
+
<body>
|
|
170
|
+
<header>
|
|
171
|
+
<div class="container">
|
|
172
|
+
<h1>Serverless<span class="irc">IRCd</span></h1>
|
|
173
|
+
<p>A serverless IRC daemon. One TypeScript core, two serverless substrates.</p>
|
|
174
|
+
</div>
|
|
175
|
+
</header>
|
|
176
|
+
|
|
177
|
+
<main>
|
|
178
|
+
<div class="container">
|
|
179
|
+
<p>
|
|
180
|
+
ServerlessIRCd is an IRC server where the protocol logic lives in a
|
|
181
|
+
pure, platform-agnostic core, and two thin adapters run it on
|
|
182
|
+
<strong>Cloudflare Workers</strong> (Durable Objects) or
|
|
183
|
+
<strong>AWS</strong> (API Gateway WebSockets + Lambda + DynamoDB).
|
|
184
|
+
The same code, unchanged, on both.
|
|
185
|
+
</p>
|
|
186
|
+
|
|
187
|
+
<ul class="features">
|
|
188
|
+
<li>Pure-reducer protocol core — every command is a trivial unit test</li>
|
|
189
|
+
<li>Dual transport: WebSocket <em>and</em> <code>irc+tls</code> on port 6697</li>
|
|
190
|
+
<li>Full IRCv3 surface: message-tags, chathistory, SASL, labeled-response, and more</li>
|
|
191
|
+
<li>Authoritative state in one place per datum — races are structurally impossible</li>
|
|
192
|
+
</ul>
|
|
193
|
+
|
|
194
|
+
<div class="cta">
|
|
195
|
+
<a class="primary" href="/app/">Launch Web Client</a>
|
|
196
|
+
<a class="secondary" href="docs/">Read the Docs</a>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
<div class="links">
|
|
200
|
+
<a href="https://github.com/fowlmouth/ServerlessIRCd">Source Code (GitHub)</a>
|
|
201
|
+
<a href="https://github.com/fowlmouth/ServerlessIRCd/issues">Issue Tracker</a>
|
|
202
|
+
<a href="/app/">Web IRC Client</a>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
</main>
|
|
206
|
+
|
|
207
|
+
<footer>
|
|
208
|
+
<div class="container">
|
|
209
|
+
<p>
|
|
210
|
+
ServerlessIRCd ·
|
|
211
|
+
<a href="https://github.com/fowlmouth/ServerlessIRCd">GitHub</a> ·
|
|
212
|
+
BSD-3-Clause License
|
|
213
|
+
</p>
|
|
214
|
+
</div>
|
|
215
|
+
</footer>
|
|
216
|
+
</body>
|
|
217
|
+
</html>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@serverless-ircd/web",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Web client workspace: Kiwi IRC SPA served at /app/ and project landing page at /",
|
|
6
|
+
"license": "BSD-3-Clause",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "node scripts/build.mjs",
|
|
10
|
+
"build:staging": "node scripts/build.mjs --env staging",
|
|
11
|
+
"build:prod": "node scripts/build.mjs --env prod",
|
|
12
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
13
|
+
"test": "vitest run",
|
|
14
|
+
"test:watch": "vitest",
|
|
15
|
+
"coverage": "vitest run --coverage",
|
|
16
|
+
"clean": "rimraf dist coverage .turbo"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"zod": "^3.25.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^26.1.1",
|
|
23
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
24
|
+
"rimraf": "^6.0.0",
|
|
25
|
+
"typescript": "^5.6.0",
|
|
26
|
+
"vitest": "^4.1.0"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { resolveConfigFile } from '../src/build-env.ts';
|
|
7
|
+
import { parseKiwiConfig } from '../src/config-schema.ts';
|
|
8
|
+
|
|
9
|
+
const pkgRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
10
|
+
const upstream = path.resolve(pkgRoot, 'upstream');
|
|
11
|
+
const dist = path.resolve(pkgRoot, 'dist');
|
|
12
|
+
const appDist = path.join(dist, 'app');
|
|
13
|
+
const staticDir = path.join(pkgRoot, 'static');
|
|
14
|
+
|
|
15
|
+
// The SPA is served at /app/ on the Worker (the [assets] binding arrives in a
|
|
16
|
+
// later ticket). All hashed asset URLs must be /app/-prefixed so they load
|
|
17
|
+
// from /app/static/... rather than /static/... (which would 404 at root).
|
|
18
|
+
const PUBLIC_PATH = '/app/';
|
|
19
|
+
|
|
20
|
+
// Yarn runs from a pnpm lifecycle script. Kiwi v1.7.1's package.json has no
|
|
21
|
+
// `packageManager` field, so yarn classic walks up and finds the workspace
|
|
22
|
+
// root's `pnpm@...` pin and refuses to run. COREPACK_ROOT tells yarn it is
|
|
23
|
+
// corepack-managed and skips that check. --ignore-engines lets the upstream
|
|
24
|
+
// tree resolve on Node newer than a few transitive deps declare
|
|
25
|
+
// (e.g. @achrinza/node-ipc). On Node >= 17, webpack 5's md4 hashing needs the
|
|
26
|
+
// OpenSSL legacy provider.
|
|
27
|
+
function yarnEnv() {
|
|
28
|
+
const major = Number.parseInt(process.versions.node.split('.')[0] ?? '0', 10);
|
|
29
|
+
const existing = process.env.NODE_OPTIONS ?? '';
|
|
30
|
+
const nodeOptions =
|
|
31
|
+
major >= 17 && !existing.includes('openssl-legacy-provider')
|
|
32
|
+
? `${existing} --openssl-legacy-provider`.trim()
|
|
33
|
+
: existing;
|
|
34
|
+
return { ...process.env, COREPACK_ROOT: upstream, NODE_OPTIONS: nodeOptions || undefined };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function runYarn(args) {
|
|
38
|
+
execFileSync('yarn', args, { cwd: upstream, stdio: 'inherit', env: yarnEnv() });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!existsSync(upstream)) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Kiwi upstream missing at ${upstream}. Run \`git submodule update --init apps/web/upstream\` first.`,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 1. Install upstream deps. Kiwi ships a yarn.lock; --frozen-lockfile gives a
|
|
48
|
+
// reproducible tree. Contributors get yarn via `corepack enable`.
|
|
49
|
+
if (!existsSync(path.join(upstream, 'node_modules'))) {
|
|
50
|
+
runYarn(['install', '--frozen-lockfile', '--ignore-engines']);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 2. Build with publicPath=/app/. Upstream pins publicPath:'' (relative); we
|
|
54
|
+
// temporarily patch vue.config.js so webpack emits /app/-prefixed asset
|
|
55
|
+
// URLs and the runtime public_path matches the mount point. Restored in
|
|
56
|
+
// `finally` so the submodule stays clean.
|
|
57
|
+
const vueConfigPath = path.join(upstream, 'vue.config.js');
|
|
58
|
+
const originalVueConfig = await readFile(vueConfigPath, 'utf8');
|
|
59
|
+
try {
|
|
60
|
+
const patched = originalVueConfig.replace(/publicPath:\s*''/, `publicPath: '${PUBLIC_PATH}'`);
|
|
61
|
+
if (patched === originalVueConfig) {
|
|
62
|
+
throw new Error('Could not locate `publicPath` in upstream vue.config.js; patching failed.');
|
|
63
|
+
}
|
|
64
|
+
await writeFile(vueConfigPath, patched);
|
|
65
|
+
runYarn(['build']);
|
|
66
|
+
} finally {
|
|
67
|
+
await writeFile(vueConfigPath, originalVueConfig);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 3. Copy the built SPA into dist/app/.
|
|
71
|
+
await rm(appDist, { recursive: true, force: true });
|
|
72
|
+
await mkdir(appDist, { recursive: true });
|
|
73
|
+
await cp(path.join(upstream, 'dist'), appDist, { recursive: true });
|
|
74
|
+
|
|
75
|
+
// 3b. Webpack applies publicPath to the bundles it emits, but template-hardcoded
|
|
76
|
+
// refs (e.g. the favicon) stay relative. Prefix every bare `static/...`
|
|
77
|
+
// reference so all asset URLs resolve to /app/static/... when served at /app/.
|
|
78
|
+
const indexDest = path.join(appDist, 'index.html');
|
|
79
|
+
let indexHtml = await readFile(indexDest, 'utf8');
|
|
80
|
+
indexHtml = indexHtml.replace(/((?:src|href)=["'])(static\/)/g, `$1${PUBLIC_PATH}$2`);
|
|
81
|
+
await writeFile(indexDest, indexHtml);
|
|
82
|
+
|
|
83
|
+
// 4. Layer our per-env static/config.*.json over the upstream default. The
|
|
84
|
+
// selected file is parsed against the Zod schema (`src/config-schema.ts`)
|
|
85
|
+
// so missing fields, out-of-range ports, and non-`#` channels abort the
|
|
86
|
+
// build here with a readable error. The validated JSON is re-serialised
|
|
87
|
+
// (stable key order) so the baked config is identical to the schema output.
|
|
88
|
+
const { file: configFileName, env: configEnv } = resolveConfigFile(process.argv.slice(2));
|
|
89
|
+
const configSrcPath = path.join(staticDir, configFileName);
|
|
90
|
+
if (!existsSync(configSrcPath)) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`Config file for env "${configEnv}" not found at ${configSrcPath}. Expected one of: config.json, config.staging.json, config.prod.json.`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
const configRaw = await readFile(configSrcPath, 'utf8');
|
|
96
|
+
let configParsed;
|
|
97
|
+
try {
|
|
98
|
+
configParsed = parseKiwiConfig(JSON.parse(configRaw));
|
|
99
|
+
} catch (err) {
|
|
100
|
+
// Re-throw with the file name prepended so the build error names the
|
|
101
|
+
// offending env config, not just the schema path.
|
|
102
|
+
throw new Error(`${configFileName}: ${err.message}`, { cause: err });
|
|
103
|
+
}
|
|
104
|
+
await writeFile(
|
|
105
|
+
path.join(appDist, 'static', 'config.json'),
|
|
106
|
+
`${JSON.stringify(configParsed, null, 2)}\n`,
|
|
107
|
+
'utf8',
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// 5. Landing page: copy the static landing page (landing/index.html) to
|
|
111
|
+
// dist/index.html so the Worker `[assets]` binding serves it at `/`. Pure
|
|
112
|
+
// static HTML + inline CSS (no JS); links to /app/ (the SPA), docs/, and
|
|
113
|
+
// the source repo.
|
|
114
|
+
await mkdir(dist, { recursive: true });
|
|
115
|
+
const landingSrc = path.join(pkgRoot, 'landing', 'index.html');
|
|
116
|
+
if (!existsSync(landingSrc)) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`Landing page source missing at ${landingSrc}. Expected apps/web/landing/index.html.`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
await cp(landingSrc, path.join(dist, 'index.html'));
|
|
122
|
+
|
|
123
|
+
console.log(
|
|
124
|
+
`Kiwi SPA built -> dist/app/ (publicPath=${PUBLIC_PATH}, config=${configFileName}, env=${configEnv})`,
|
|
125
|
+
);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-env config-file resolution for the SPA build (`scripts/build.mjs`).
|
|
3
|
+
*
|
|
4
|
+
* Extracted from the build script so the `--env` → config-file mapping is
|
|
5
|
+
* unit-testable without running the (slow) Kiwi upstream build. The build
|
|
6
|
+
* script delegates here and then validates the selected file against
|
|
7
|
+
* {@link parseKiwiConfig} before baking it into `dist/app/static/config.json`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** argv values accepted by `--env`; mapped to a `static/config.<env>.json` file. */
|
|
11
|
+
export const ENV_TO_CONFIG_FILE = {
|
|
12
|
+
staging: 'config.staging.json',
|
|
13
|
+
prod: 'config.prod.json',
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
export type BuildEnv = keyof typeof ENV_TO_CONFIG_FILE;
|
|
17
|
+
|
|
18
|
+
/** Default config file baked when `--env` is not supplied (staging target). */
|
|
19
|
+
export const DEFAULT_CONFIG_FILE = 'config.json';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Resolves which `static/config.*.json` the build should bake from the
|
|
23
|
+
* `--env <staging|prod>` CLI flag.
|
|
24
|
+
*
|
|
25
|
+
* @param argv the CLI args after the script name (typically `process.argv.slice(2)`)
|
|
26
|
+
* @returns `{ file, env }` where `file` is the basename under `static/` and
|
|
27
|
+
* `env` is a human-readable label for the build log.
|
|
28
|
+
* @throws {Error} when `--env` is supplied with an unknown value.
|
|
29
|
+
*/
|
|
30
|
+
export function resolveConfigFile(argv: string[]): { file: string; env: string } {
|
|
31
|
+
const envIndex = argv.indexOf('--env');
|
|
32
|
+
const env = envIndex !== -1 ? argv[envIndex + 1] : undefined;
|
|
33
|
+
if (env === undefined) {
|
|
34
|
+
return { file: DEFAULT_CONFIG_FILE, env: 'default (staging)' };
|
|
35
|
+
}
|
|
36
|
+
const file = ENV_TO_CONFIG_FILE[env as BuildEnv];
|
|
37
|
+
if (file === undefined) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`Unknown --env value: ${env}. Expected one of: ${Object.keys(ENV_TO_CONFIG_FILE).join(', ')}.`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return { file, env };
|
|
43
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kiwi IRC web-client config schema (Zod) — the single source of truth for
|
|
3
|
+
* what the per-environment `static/config.{staging,prod,}.json` files must
|
|
4
|
+
* supply to drive the embedded Kiwi SPA.
|
|
5
|
+
*
|
|
6
|
+
* `build.mjs` runs {@link parseKiwiConfig} against the selected env config
|
|
7
|
+
* before baking it into `dist/app/static/config.json`, so a malformed config
|
|
8
|
+
* fails the build with a readable aggregated error rather than shipping a
|
|
9
|
+
* broken SPA. The schema only models the fields ServerlessIRCd cares about
|
|
10
|
+
* (server, port, channel, WS path); the rest of Kiwi's surface is passed
|
|
11
|
+
* through untouched.
|
|
12
|
+
*
|
|
13
|
+
* Hosting layout reminder:
|
|
14
|
+
* - The Worker's WebSocket endpoint is at `/` (root), so Kiwi's
|
|
15
|
+
* `startupOptions.path` — when supplied — MUST be `/`.
|
|
16
|
+
* - The SPA itself is served from `/app/` by the Worker `[assets]`
|
|
17
|
+
* binding (a separate concern handled by the public-path patch in
|
|
18
|
+
* `build.mjs`); that is independent of the WS path modelled here.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { z } from 'zod';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Regex for a single IRC channel name as advertised by this deployment.
|
|
25
|
+
* `CHANTYPES=#` is the default (`packages/irc-core/src/config.ts`), so the
|
|
26
|
+
* auto-join channel must begin with `#`. Comma-separated lists are accepted
|
|
27
|
+
* (Kiwi's `channel` field may hold several at once); each member is validated
|
|
28
|
+
* implicitly because the leading `#` gate is on the whole string.
|
|
29
|
+
*/
|
|
30
|
+
const CHANNEL_PATTERN = /^#/u;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Schema for Kiwi's `startupOptions` block. Only the ServerlessIRCd-relevant
|
|
34
|
+
* fields are modelled; the object is passthrough so Kiwi's many other knobs
|
|
35
|
+
* (nick, gecos, ircd password, SASL, ...) survive the round-trip.
|
|
36
|
+
*/
|
|
37
|
+
export const KiwiStartupOptionsSchema = z
|
|
38
|
+
.object({
|
|
39
|
+
/**
|
|
40
|
+
* IRC server hostname the browser client connects to (the Worker's public
|
|
41
|
+
* hostname, or whatever reverse-proxy fronts it). Required — a missing or
|
|
42
|
+
* empty value is the most common misconfiguration and breaks the SPA at
|
|
43
|
+
* first paint.
|
|
44
|
+
*
|
|
45
|
+
* Deploy-time auto-detect: bake the Kiwi template `{{hostname}}` (or
|
|
46
|
+
* `{{host}}` to include a non-default port) and the browser resolves it
|
|
47
|
+
* to `window.location.hostname` at load time, so the same build artifact
|
|
48
|
+
* targets staging, prod, preview, and PR deployments without a per-env
|
|
49
|
+
* hostname. Kiwi registers the value-replacement pass unconditionally on
|
|
50
|
+
* every page load (upstream `src/main.js`). A literal hostname is still
|
|
51
|
+
* accepted for operators who want to pin a specific target.
|
|
52
|
+
*/
|
|
53
|
+
server: z.string().min(1),
|
|
54
|
+
/**
|
|
55
|
+
* IRC server port. Optional; when omitted, irc-framework selects 443 for
|
|
56
|
+
* `tls: true` and 80 for `tls: false` (upstream `Welcome.vue:437-444`).
|
|
57
|
+
* When supplied it must be a valid TCP port (1-65535); out-of-range values
|
|
58
|
+
* produce a confusing "connection refused" at runtime.
|
|
59
|
+
*/
|
|
60
|
+
port: z.number().int().min(1).max(65535).optional(),
|
|
61
|
+
/**
|
|
62
|
+
* Auto-join channel(s). Must begin with `#` to match `CHANTYPES`. May be a
|
|
63
|
+
* comma-separated list (`#a,#b`); Kiwi splits and joins each.
|
|
64
|
+
*/
|
|
65
|
+
channel: z.string().regex(CHANNEL_PATTERN),
|
|
66
|
+
/**
|
|
67
|
+
* Direct-WebSocket mode. MUST be `true` for ServerlessIRCd: the Worker is
|
|
68
|
+
* a direct IRC-over-WebSocket server, not a kiwiServer WebIRC gateway. If
|
|
69
|
+
* `false` (the Kiwi default), Kiwi routes the connection through the
|
|
70
|
+
* gateway at `kiwiServer` (default `/webirc/kiwiirc/`) and probes
|
|
71
|
+
* `${kiwiServer}/info`; nothing serves that on the Worker, so the SPA
|
|
72
|
+
* 404s and cannot connect. With `true`, irc-framework opens a WebSocket
|
|
73
|
+
* straight to `server`:`port` (`IrcClient.js:79-82`).
|
|
74
|
+
*/
|
|
75
|
+
direct: z.boolean(),
|
|
76
|
+
/**
|
|
77
|
+
* TLS for the IRC connection. MUST be `true` on Cloudflare: the Worker is
|
|
78
|
+
* served over HTTPS, so the WS upgrade must be `wss://` (browsers refuse
|
|
79
|
+
* `ws://` on an HTTPS page). irc-framework's WS transport prefixes the
|
|
80
|
+
* address with `wss://` when `tls` is truthy (`transports/websocket.js:65`).
|
|
81
|
+
*/
|
|
82
|
+
tls: z.boolean(),
|
|
83
|
+
/**
|
|
84
|
+
* WebSocket path on the server. Kiwi reads `direct_path` (NOT `path`) —
|
|
85
|
+
* upstream `helpers/Misc.js:139` copies `config.direct_path` into the
|
|
86
|
+
* connection info; a `path` field is silently ignored. The Worker exposes
|
|
87
|
+
* its WS upgrade at root (`worker.ts` routes the upgrade before any path
|
|
88
|
+
* dispatch), so any path other than `/` would miss the handler. Optional
|
|
89
|
+
* — when omitted irc-framework builds `wss://host:port` with no path,
|
|
90
|
+
* which the browser normalises to `wss://host:port/`.
|
|
91
|
+
*/
|
|
92
|
+
direct_path: z.literal('/').optional(),
|
|
93
|
+
})
|
|
94
|
+
.passthrough();
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Top-level Kiwi config schema. `windowTitle` is the one field every
|
|
98
|
+
* ServerlessIRCd deployment MUST set (it is what the browser tab shows and
|
|
99
|
+
* what the build-smoke test asserts against); everything else at the top
|
|
100
|
+
* level (theme, themes, embedly, plugins, startupScreen, ...) is passed
|
|
101
|
+
* through verbatim so Kiwi's full configuration surface remains available.
|
|
102
|
+
*/
|
|
103
|
+
export const KiwiConfigSchema = z
|
|
104
|
+
.object({
|
|
105
|
+
windowTitle: z.string().min(1),
|
|
106
|
+
startupOptions: KiwiStartupOptionsSchema,
|
|
107
|
+
})
|
|
108
|
+
.passthrough();
|
|
109
|
+
|
|
110
|
+
/** Parsed Kiwi config: all modelled fields populated, plus any passthroughs. */
|
|
111
|
+
export type KiwiConfig = z.infer<typeof KiwiConfigSchema>;
|
|
112
|
+
|
|
113
|
+
/** Parsed `startupOptions` block. */
|
|
114
|
+
export type KiwiStartupOptions = z.infer<typeof KiwiStartupOptionsSchema>;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Parses a raw config value (typically the JSON-decoded contents of a
|
|
118
|
+
* `static/config.*.json` file) against {@link KiwiConfigSchema}, throwing a
|
|
119
|
+
* single aggregated {@link Error} on failure.
|
|
120
|
+
*
|
|
121
|
+
* `build.mjs` calls this exactly once per build, against the env-selected
|
|
122
|
+
* config file, so the build fails fast with every offending field named
|
|
123
|
+
* rather than shipping a broken SPA. The thrown message starts with
|
|
124
|
+
* "Invalid Kiwi web config" and lists one bullet per Zod issue.
|
|
125
|
+
*
|
|
126
|
+
* @throws {Error} when `input` fails schema validation.
|
|
127
|
+
*/
|
|
128
|
+
export function parseKiwiConfig(input: unknown): KiwiConfig {
|
|
129
|
+
const result = KiwiConfigSchema.safeParse(input);
|
|
130
|
+
if (!result.success) {
|
|
131
|
+
const lines = result.error.issues.map((issue) => {
|
|
132
|
+
const key = issue.path.length > 0 ? issue.path.join('.') : '<root>';
|
|
133
|
+
return ` - ${key}: ${issue.message}`;
|
|
134
|
+
});
|
|
135
|
+
throw new Error(`Invalid Kiwi web config:\n${lines.join('\n')}`);
|
|
136
|
+
}
|
|
137
|
+
return result.data;
|
|
138
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"windowTitle": "ServerlessIRCd",
|
|
3
|
+
"startupScreen": "welcome",
|
|
4
|
+
"restricted": false,
|
|
5
|
+
"theme": "Dark",
|
|
6
|
+
"themes": [
|
|
7
|
+
{ "name": "Default", "url": "static/themes/default" },
|
|
8
|
+
{ "name": "Dark", "url": "static/themes/dark" },
|
|
9
|
+
{ "name": "Coffee", "url": "static/themes/coffee" },
|
|
10
|
+
{ "name": "GrayFox", "url": "static/themes/grayfox" },
|
|
11
|
+
{ "name": "Nightswatch", "url": "static/themes/nightswatch" },
|
|
12
|
+
{ "name": "Osprey", "url": "static/themes/osprey" },
|
|
13
|
+
{ "name": "Radioactive", "url": "static/themes/radioactive" },
|
|
14
|
+
{ "name": "Sky", "url": "static/themes/sky" },
|
|
15
|
+
{ "name": "Elite", "url": "static/themes/elite" }
|
|
16
|
+
],
|
|
17
|
+
"startupOptions": {
|
|
18
|
+
"server": "{{hostname}}",
|
|
19
|
+
"direct": true,
|
|
20
|
+
"tls": true,
|
|
21
|
+
"port": 443,
|
|
22
|
+
"channel": "#welcome",
|
|
23
|
+
"nick": "Guest"
|
|
24
|
+
},
|
|
25
|
+
"embedly": {
|
|
26
|
+
"key": ""
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"windowTitle": "ServerlessIRCd",
|
|
3
|
+
"startupScreen": "welcome",
|
|
4
|
+
"restricted": false,
|
|
5
|
+
"theme": "Dark",
|
|
6
|
+
"themes": [
|
|
7
|
+
{ "name": "Default", "url": "static/themes/default" },
|
|
8
|
+
{ "name": "Dark", "url": "static/themes/dark" },
|
|
9
|
+
{ "name": "Coffee", "url": "static/themes/coffee" },
|
|
10
|
+
{ "name": "GrayFox", "url": "static/themes/grayfox" },
|
|
11
|
+
{ "name": "Nightswatch", "url": "static/themes/nightswatch" },
|
|
12
|
+
{ "name": "Osprey", "url": "static/themes/osprey" },
|
|
13
|
+
{ "name": "Radioactive", "url": "static/themes/radioactive" },
|
|
14
|
+
{ "name": "Sky", "url": "static/themes/sky" },
|
|
15
|
+
{ "name": "Elite", "url": "static/themes/elite" }
|
|
16
|
+
],
|
|
17
|
+
"startupOptions": {
|
|
18
|
+
"server": "{{hostname}}",
|
|
19
|
+
"direct": true,
|
|
20
|
+
"tls": true,
|
|
21
|
+
"port": 443,
|
|
22
|
+
"channel": "#welcome",
|
|
23
|
+
"nick": "Guest"
|
|
24
|
+
},
|
|
25
|
+
"embedly": {
|
|
26
|
+
"key": ""
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"windowTitle": "ServerlessIRCd (staging)",
|
|
3
|
+
"startupScreen": "welcome",
|
|
4
|
+
"restricted": false,
|
|
5
|
+
"theme": "Dark",
|
|
6
|
+
"themes": [
|
|
7
|
+
{ "name": "Default", "url": "static/themes/default" },
|
|
8
|
+
{ "name": "Dark", "url": "static/themes/dark" },
|
|
9
|
+
{ "name": "Coffee", "url": "static/themes/coffee" },
|
|
10
|
+
{ "name": "GrayFox", "url": "static/themes/grayfox" },
|
|
11
|
+
{ "name": "Nightswatch", "url": "static/themes/nightswatch" },
|
|
12
|
+
{ "name": "Osprey", "url": "static/themes/osprey" },
|
|
13
|
+
{ "name": "Radioactive", "url": "static/themes/radioactive" },
|
|
14
|
+
{ "name": "Sky", "url": "static/themes/sky" },
|
|
15
|
+
{ "name": "Elite", "url": "static/themes/elite" }
|
|
16
|
+
],
|
|
17
|
+
"startupOptions": {
|
|
18
|
+
"server": "{{hostname}}",
|
|
19
|
+
"direct": true,
|
|
20
|
+
"tls": true,
|
|
21
|
+
"port": 443,
|
|
22
|
+
"channel": "#welcome",
|
|
23
|
+
"nick": "Guest"
|
|
24
|
+
},
|
|
25
|
+
"embedly": {
|
|
26
|
+
"key": ""
|
|
27
|
+
}
|
|
28
|
+
}
|