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,63 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { DEFAULT_CONFIG_FILE, ENV_TO_CONFIG_FILE, resolveConfigFile } from '../src/build-env';
|
|
3
|
+
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// resolveConfigFile — per-env config selection
|
|
6
|
+
// ============================================================================
|
|
7
|
+
|
|
8
|
+
describe('resolveConfigFile', () => {
|
|
9
|
+
it('selects config.staging.json for --env staging', () => {
|
|
10
|
+
const result = resolveConfigFile(['--env', 'staging']);
|
|
11
|
+
expect(result.file).toBe('config.staging.json');
|
|
12
|
+
expect(result.env).toBe('staging');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('selects config.prod.json for --env prod', () => {
|
|
16
|
+
const result = resolveConfigFile(['--env', 'prod']);
|
|
17
|
+
expect(result.file).toBe('config.prod.json');
|
|
18
|
+
expect(result.env).toBe('prod');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('falls back to the default config (staging target) when --env is absent', () => {
|
|
22
|
+
const result = resolveConfigFile([]);
|
|
23
|
+
expect(result.file).toBe(DEFAULT_CONFIG_FILE);
|
|
24
|
+
expect(result.file).toBe('config.json');
|
|
25
|
+
expect(result.env).toBe('default (staging)');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('throws on an unknown --env value', () => {
|
|
29
|
+
expect(() => resolveConfigFile(['--env', 'local'])).toThrowError(/Unknown --env value: local/u);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("throws on a typo'd env value", () => {
|
|
33
|
+
expect(() => resolveConfigFile(['--env', 'production'])).toThrowError(
|
|
34
|
+
/Unknown --env value: production/u,
|
|
35
|
+
);
|
|
36
|
+
expect(() => resolveConfigFile(['--env', 'stg'])).toThrowError(/Unknown --env value: stg/u);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('names the accepted env values in the error message', () => {
|
|
40
|
+
try {
|
|
41
|
+
resolveConfigFile(['--env', 'nope']);
|
|
42
|
+
expect.fail('expected resolveConfigFile to throw');
|
|
43
|
+
} catch (err) {
|
|
44
|
+
const msg = (err as Error).message;
|
|
45
|
+
// Every accepted key is listed so the operator knows the valid set.
|
|
46
|
+
for (const key of Object.keys(ENV_TO_CONFIG_FILE)) {
|
|
47
|
+
expect(msg).toContain(key);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('ignores unrelated argv flags', () => {
|
|
53
|
+
const result = resolveConfigFile(['--verbose', '--env', 'prod', '--dry-run']);
|
|
54
|
+
expect(result.file).toBe('config.prod.json');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('treats a missing value after --env as undefined (falls back to default)', () => {
|
|
58
|
+
// `--env` with no following arg: argv.indexOf('--env') + 1 yields
|
|
59
|
+
// `undefined`, which hits the "no flag" branch.
|
|
60
|
+
const result = resolveConfigFile(['--env']);
|
|
61
|
+
expect(result.file).toBe(DEFAULT_CONFIG_FILE);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import { parseKiwiConfig } from '../src/config-schema';
|
|
6
|
+
|
|
7
|
+
const pkgRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
8
|
+
const appDist = path.join(pkgRoot, 'dist', 'app');
|
|
9
|
+
const indexPath = path.join(appDist, 'index.html');
|
|
10
|
+
const landingSrc = path.join(pkgRoot, 'landing', 'index.html');
|
|
11
|
+
const landingDist = path.join(pkgRoot, 'dist', 'index.html');
|
|
12
|
+
|
|
13
|
+
// Validates the output of `pnpm --filter web build` (or `build:staging` /
|
|
14
|
+
// `build:prod`). Skipped until the SPA has been built (the Kiwi upstream
|
|
15
|
+
// build is too heavy to run inside the test suite); run the build first to
|
|
16
|
+
// exercise these assertions.
|
|
17
|
+
const built = existsSync(indexPath);
|
|
18
|
+
// The landing page is copied by the same build script but is an independent
|
|
19
|
+
// artifact; gate the build-output assertion on its own presence.
|
|
20
|
+
const landingBuilt = existsSync(landingDist) && statSync(landingDist).size > 0;
|
|
21
|
+
|
|
22
|
+
describe.skipIf(!built)('Kiwi SPA build output (dist/app/)', () => {
|
|
23
|
+
it('emits dist/app/index.html', () => {
|
|
24
|
+
expect(existsSync(indexPath)).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('layers a schema-valid static/config.json into dist/app/static/config.json', () => {
|
|
28
|
+
const configPath = path.join(appDist, 'static', 'config.json');
|
|
29
|
+
expect(existsSync(configPath)).toBe(true);
|
|
30
|
+
// The baked config must clear the Zod schema no matter which --env was
|
|
31
|
+
// selected at build time.
|
|
32
|
+
const cfg = parseKiwiConfig(JSON.parse(readFileSync(configPath, 'utf8')));
|
|
33
|
+
expect(cfg.windowTitle.length).toBeGreaterThan(0);
|
|
34
|
+
expect(cfg.startupOptions.channel.startsWith('#')).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('prefixes every asset reference with /app/', () => {
|
|
38
|
+
const html = readFileSync(indexPath, 'utf8');
|
|
39
|
+
// No bare relative `static/...` references may remain.
|
|
40
|
+
const bareStaticRefs = html.match(/(?:src|href)=["']static\//g) ?? [];
|
|
41
|
+
expect(bareStaticRefs).toEqual([]);
|
|
42
|
+
// At least one asset must be /app/-prefixed (the main bundle).
|
|
43
|
+
const appPrefixed = html.match(/(?:src|href)=["']\/app\/static\//g) ?? [];
|
|
44
|
+
expect(appPrefixed.length).toBeGreaterThan(0);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('ships a hashed JS bundle under dist/app/static/js/', () => {
|
|
48
|
+
const jsDir = path.join(appDist, 'static', 'js');
|
|
49
|
+
expect(existsSync(jsDir)).toBe(true);
|
|
50
|
+
const js = readdirSync(jsDir).filter((f) => f.endsWith('.js'));
|
|
51
|
+
expect(js.length).toBeGreaterThan(0);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// The landing page is a static file with no compilation step — the build
|
|
56
|
+
// copies it verbatim to dist/index.html. Validating the source directly means
|
|
57
|
+
// these assertions run in CI without the (heavy) Kiwi build; the build-output
|
|
58
|
+
// describe below verifies the copy is wired up.
|
|
59
|
+
describe('landing page source (landing/index.html)', () => {
|
|
60
|
+
it('exists as a non-empty HTML document', () => {
|
|
61
|
+
expect(existsSync(landingSrc)).toBe(true);
|
|
62
|
+
const html = readFileSync(landingSrc, 'utf8');
|
|
63
|
+
expect(html.length).toBeGreaterThan(0);
|
|
64
|
+
expect(html).toContain('<!doctype html>');
|
|
65
|
+
expect(html).toContain('<title>');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('links to the IRC client at /app/', () => {
|
|
69
|
+
const html = readFileSync(landingSrc, 'utf8');
|
|
70
|
+
expect(html).toMatch(/href=["']\/app\/?["']/);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('links to the project docs', () => {
|
|
74
|
+
const html = readFileSync(landingSrc, 'utf8');
|
|
75
|
+
expect(html).toMatch(/href=["'][^"']*docs\/?["']/);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('links to the source repository', () => {
|
|
79
|
+
const html = readFileSync(landingSrc, 'utf8');
|
|
80
|
+
expect(html.toLowerCase()).toMatch(/github\.com/);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('is responsive (declares a viewport meta tag)', () => {
|
|
84
|
+
const html = readFileSync(landingSrc, 'utf8');
|
|
85
|
+
expect(html).toMatch(/<meta[^>]+name=["']viewport["']/);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('works without JavaScript (no <script> tags — pure static HTML/CSS)', () => {
|
|
89
|
+
const html = readFileSync(landingSrc, 'utf8');
|
|
90
|
+
// The landing page is the project's front door and must render fully
|
|
91
|
+
// without JS (acceptance: "works without JS").
|
|
92
|
+
const scriptTags = html.match(/<script\b/gi) ?? [];
|
|
93
|
+
expect(scriptTags).toEqual([]);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe.skipIf(!landingBuilt)('landing page build output (dist/index.html)', () => {
|
|
98
|
+
it('is a verbatim copy of landing/index.html', () => {
|
|
99
|
+
const src = readFileSync(landingSrc, 'utf8');
|
|
100
|
+
const dist = readFileSync(landingDist, 'utf8');
|
|
101
|
+
expect(dist).toBe(src);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import { KiwiConfigSchema, parseKiwiConfig } from '../src/config-schema';
|
|
6
|
+
|
|
7
|
+
const pkgRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
8
|
+
const staticDir = path.join(pkgRoot, 'static');
|
|
9
|
+
|
|
10
|
+
function readStaticConfig(name: string): unknown {
|
|
11
|
+
return JSON.parse(readFileSync(path.join(staticDir, name), 'utf8'));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// A minimal valid startupOptions payload, extended per test. `direct` + `tls`
|
|
15
|
+
// are included because the Worker is a direct IRC-over-WebSocket server (not
|
|
16
|
+
// a kiwiServer WebIRC gateway), so every real config MUST set both; the
|
|
17
|
+
// schema makes them required to prevent the kiwiServer-gateway regression.
|
|
18
|
+
function validStartupOptions(overrides: Record<string, unknown> = {}): Record<string, unknown> {
|
|
19
|
+
return {
|
|
20
|
+
server: '{{hostname}}',
|
|
21
|
+
direct: true,
|
|
22
|
+
tls: true,
|
|
23
|
+
channel: '#welcome',
|
|
24
|
+
...overrides,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function validConfig(overrides: Record<string, unknown> = {}): Record<string, unknown> {
|
|
29
|
+
return {
|
|
30
|
+
windowTitle: 'ServerlessIRCd',
|
|
31
|
+
startupOptions: validStartupOptions(),
|
|
32
|
+
...overrides,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// KiwiConfigSchema — required fields (TDD outline: Red #1 — Zod accepts `{}`)
|
|
38
|
+
// ============================================================================
|
|
39
|
+
|
|
40
|
+
describe('KiwiConfigSchema — required fields', () => {
|
|
41
|
+
it('rejects an empty object (missing windowTitle + startupOptions)', () => {
|
|
42
|
+
expect(() => parseKiwiConfig({})).toThrowError(/Invalid Kiwi web config/u);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('rejects a config missing startupOptions', () => {
|
|
46
|
+
expect(() => parseKiwiConfig({ windowTitle: 'ServerlessIRCd' })).toThrowError(
|
|
47
|
+
/startupOptions/u,
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('rejects a config missing windowTitle', () => {
|
|
52
|
+
expect(() => parseKiwiConfig({ startupOptions: validStartupOptions() })).toThrowError(
|
|
53
|
+
/windowTitle/u,
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('rejects an empty windowTitle', () => {
|
|
58
|
+
expect(() =>
|
|
59
|
+
parseKiwiConfig({ windowTitle: '', startupOptions: validStartupOptions() }),
|
|
60
|
+
).toThrowError(/windowTitle/u);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('rejects startupOptions missing server (TDD outline #1)', () => {
|
|
64
|
+
expect(() =>
|
|
65
|
+
parseKiwiConfig({ windowTitle: 'x', startupOptions: { channel: '#welcome' } }),
|
|
66
|
+
).toThrowError(/server/u);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('rejects startupOptions with an empty server', () => {
|
|
70
|
+
expect(() =>
|
|
71
|
+
parseKiwiConfig({
|
|
72
|
+
windowTitle: 'x',
|
|
73
|
+
startupOptions: { server: '', channel: '#welcome' },
|
|
74
|
+
}),
|
|
75
|
+
).toThrowError(/server/u);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('rejects startupOptions missing channel', () => {
|
|
79
|
+
expect(() =>
|
|
80
|
+
parseKiwiConfig({ windowTitle: 'x', startupOptions: { server: 'irc.x' } }),
|
|
81
|
+
).toThrowError(/channel/u);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// ============================================================================
|
|
86
|
+
// KiwiConfigSchema — port range (TDD outline: Red #3 — `port: 99999` passes)
|
|
87
|
+
// ============================================================================
|
|
88
|
+
|
|
89
|
+
describe('KiwiConfigSchema — port range', () => {
|
|
90
|
+
it('rejects a port above 65535 (TDD outline #3)', () => {
|
|
91
|
+
expect(() =>
|
|
92
|
+
parseKiwiConfig(
|
|
93
|
+
validConfig({
|
|
94
|
+
startupOptions: validStartupOptions({ port: 99999 }),
|
|
95
|
+
}),
|
|
96
|
+
),
|
|
97
|
+
).toThrowError(/port/u);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('rejects port 0', () => {
|
|
101
|
+
expect(() =>
|
|
102
|
+
parseKiwiConfig(
|
|
103
|
+
validConfig({
|
|
104
|
+
startupOptions: validStartupOptions({ port: 0 }),
|
|
105
|
+
}),
|
|
106
|
+
),
|
|
107
|
+
).toThrowError(/port/u);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('rejects a negative port', () => {
|
|
111
|
+
expect(() =>
|
|
112
|
+
parseKiwiConfig(
|
|
113
|
+
validConfig({
|
|
114
|
+
startupOptions: validStartupOptions({ port: -1 }),
|
|
115
|
+
}),
|
|
116
|
+
),
|
|
117
|
+
).toThrowError(/port/u);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('rejects a non-integer port', () => {
|
|
121
|
+
expect(() =>
|
|
122
|
+
parseKiwiConfig(
|
|
123
|
+
validConfig({
|
|
124
|
+
startupOptions: validStartupOptions({ port: 6667.5 }),
|
|
125
|
+
}),
|
|
126
|
+
),
|
|
127
|
+
).toThrowError(/port/u);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('accepts port 1 (lower bound)', () => {
|
|
131
|
+
const cfg = parseKiwiConfig(
|
|
132
|
+
validConfig({
|
|
133
|
+
startupOptions: validStartupOptions({ port: 1 }),
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
expect(cfg.startupOptions.port).toBe(1);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('accepts port 65535 (upper bound)', () => {
|
|
140
|
+
const cfg = parseKiwiConfig(
|
|
141
|
+
validConfig({
|
|
142
|
+
startupOptions: validStartupOptions({ port: 65535 }),
|
|
143
|
+
}),
|
|
144
|
+
);
|
|
145
|
+
expect(cfg.startupOptions.port).toBe(65535);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// ============================================================================
|
|
150
|
+
// KiwiConfigSchema — channel prefix (TDD outline: Red #5 — `channels: ["foo"]`)
|
|
151
|
+
// ============================================================================
|
|
152
|
+
|
|
153
|
+
describe('KiwiConfigSchema — channel prefix', () => {
|
|
154
|
+
it('rejects a channel without the # prefix (TDD outline #5)', () => {
|
|
155
|
+
expect(() =>
|
|
156
|
+
parseKiwiConfig(
|
|
157
|
+
validConfig({
|
|
158
|
+
startupOptions: validStartupOptions({ channel: 'welcome' }),
|
|
159
|
+
}),
|
|
160
|
+
),
|
|
161
|
+
).toThrowError(/channel/u);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('rejects an empty channel', () => {
|
|
165
|
+
expect(() =>
|
|
166
|
+
parseKiwiConfig(
|
|
167
|
+
validConfig({
|
|
168
|
+
startupOptions: validStartupOptions({ channel: '' }),
|
|
169
|
+
}),
|
|
170
|
+
),
|
|
171
|
+
).toThrowError(/channel/u);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('accepts a single # channel', () => {
|
|
175
|
+
const cfg = parseKiwiConfig(validConfig());
|
|
176
|
+
expect(cfg.startupOptions.channel).toBe('#welcome');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('accepts a comma-separated channel list when each starts with #', () => {
|
|
180
|
+
const cfg = parseKiwiConfig(
|
|
181
|
+
validConfig({
|
|
182
|
+
startupOptions: validStartupOptions({ channel: '#a,#b' }),
|
|
183
|
+
}),
|
|
184
|
+
);
|
|
185
|
+
expect(cfg.startupOptions.channel).toBe('#a,#b');
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// ============================================================================
|
|
190
|
+
// KiwiConfigSchema — direct WebSocket mode (bypass the kiwiServer gateway)
|
|
191
|
+
// ============================================================================
|
|
192
|
+
//
|
|
193
|
+
// Kiwi has two connection modes (upstream `src/libs/IrcClient.js:69-82`):
|
|
194
|
+
// • `direct: false` (the Kiwi default) → routes the IRC connection through
|
|
195
|
+
// a server-side WebIRC gateway at `kiwiServer` (default `/webirc/kiwiirc/`)
|
|
196
|
+
// via the ServerSession control channel. The browser first probes
|
|
197
|
+
// `${kiwiServer}/info`; if nothing serves it (our Worker doesn't), the
|
|
198
|
+
// SPA logs a 404 and cannot connect.
|
|
199
|
+
// • `direct: true` → the browser opens a WebSocket straight to
|
|
200
|
+
// `startupOptions.server` (irc-framework's default transport), which is
|
|
201
|
+
// exactly what our Worker serves at `/`.
|
|
202
|
+
//
|
|
203
|
+
// The Worker is NOT a kiwiServer gateway, so every shipped config MUST set
|
|
204
|
+
// `direct: true` (and `tls: true`, since Cloudflare terminates HTTPS and the
|
|
205
|
+
// WS upgrade must be `wss://`). The schema makes both required so a config
|
|
206
|
+
// that regresses to gateway mode fails the build rather than 404-ing at
|
|
207
|
+
// runtime.
|
|
208
|
+
|
|
209
|
+
describe('KiwiConfigSchema — direct WebSocket mode', () => {
|
|
210
|
+
it('rejects startupOptions missing direct (would fall back to the kiwiServer gateway and 404)', () => {
|
|
211
|
+
expect(() =>
|
|
212
|
+
parseKiwiConfig(
|
|
213
|
+
validConfig({
|
|
214
|
+
startupOptions: { server: '{{hostname}}', tls: true, channel: '#welcome' },
|
|
215
|
+
}),
|
|
216
|
+
),
|
|
217
|
+
).toThrowError(/direct/u);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('rejects startupOptions missing tls (Cloudflare serves HTTPS; the WS must be wss://)', () => {
|
|
221
|
+
expect(() =>
|
|
222
|
+
parseKiwiConfig(
|
|
223
|
+
validConfig({
|
|
224
|
+
startupOptions: { server: '{{hostname}}', direct: true, channel: '#welcome' },
|
|
225
|
+
}),
|
|
226
|
+
),
|
|
227
|
+
).toThrowError(/tls/u);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('accepts direct: true with tls: true', () => {
|
|
231
|
+
const cfg = parseKiwiConfig(validConfig());
|
|
232
|
+
expect(cfg.startupOptions.direct).toBe(true);
|
|
233
|
+
expect(cfg.startupOptions.tls).toBe(true);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// ============================================================================
|
|
238
|
+
// KiwiConfigSchema — WS path (direct_path)
|
|
239
|
+
// ============================================================================
|
|
240
|
+
//
|
|
241
|
+
// NOTE: Kiwi reads `startupOptions.direct_path`, NOT `startupOptions.path`
|
|
242
|
+
// (upstream `src/helpers/Misc.js:139`: `connection.direct_path = config.direct_path || ''`).
|
|
243
|
+
// The Worker accepts WS upgrades at `/` (root), so `direct_path` — when
|
|
244
|
+
// supplied — MUST be `/`. It is optional: an empty/absent value lets
|
|
245
|
+
// irc-framework build `wss://host:port` with no path, which the browser
|
|
246
|
+
// normalises to `wss://host:port/` and the Worker accepts.
|
|
247
|
+
|
|
248
|
+
describe('KiwiConfigSchema — WS path (direct_path)', () => {
|
|
249
|
+
it('rejects a direct_path other than / (WS endpoint is at root)', () => {
|
|
250
|
+
expect(() =>
|
|
251
|
+
parseKiwiConfig(
|
|
252
|
+
validConfig({
|
|
253
|
+
startupOptions: validStartupOptions({ direct_path: '/app/' }),
|
|
254
|
+
}),
|
|
255
|
+
),
|
|
256
|
+
).toThrowError(/direct_path/u);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('accepts the / direct_path', () => {
|
|
260
|
+
const cfg = parseKiwiConfig(
|
|
261
|
+
validConfig({
|
|
262
|
+
startupOptions: validStartupOptions({ direct_path: '/' }),
|
|
263
|
+
}),
|
|
264
|
+
);
|
|
265
|
+
expect(cfg.startupOptions.direct_path).toBe('/');
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('accepts an absent direct_path (irc-framework omits the path; browser defaults to /)', () => {
|
|
269
|
+
const cfg = parseKiwiConfig(validConfig());
|
|
270
|
+
expect(cfg.startupOptions.direct_path).toBeUndefined();
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// ============================================================================
|
|
275
|
+
// KiwiConfigSchema — passthrough of Kiwi-specific fields
|
|
276
|
+
// ============================================================================
|
|
277
|
+
|
|
278
|
+
describe('KiwiConfigSchema — passthrough', () => {
|
|
279
|
+
it('preserves unknown top-level Kiwi fields (themes, plugins, embedly)', () => {
|
|
280
|
+
const input = validConfig({
|
|
281
|
+
theme: 'Dark',
|
|
282
|
+
themes: [{ name: 'Dark', url: 'static/themes/dark' }],
|
|
283
|
+
embedly: { key: '' },
|
|
284
|
+
plugins: [{ name: 'customise', url: 'static/plugins/customise.html' }],
|
|
285
|
+
});
|
|
286
|
+
const cfg = parseKiwiConfig(input);
|
|
287
|
+
expect(cfg).toHaveProperty('theme', 'Dark');
|
|
288
|
+
expect(cfg).toHaveProperty('embedly');
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('preserves unknown startupOptions fields (nick, startupScreen)', () => {
|
|
292
|
+
const input = validConfig({
|
|
293
|
+
startupScreen: 'welcome',
|
|
294
|
+
startupOptions: validStartupOptions({ nick: 'guest-?' }),
|
|
295
|
+
});
|
|
296
|
+
const cfg = parseKiwiConfig(input);
|
|
297
|
+
expect(cfg).toHaveProperty('startupScreen', 'welcome');
|
|
298
|
+
expect(cfg.startupOptions).toHaveProperty('nick', 'guest-?');
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// ============================================================================
|
|
303
|
+
// parseKiwiConfig — error formatting
|
|
304
|
+
// ============================================================================
|
|
305
|
+
|
|
306
|
+
describe('parseKiwiConfig — error formatting', () => {
|
|
307
|
+
it('throws a single aggregated Error listing every invalid field', () => {
|
|
308
|
+
try {
|
|
309
|
+
parseKiwiConfig({
|
|
310
|
+
windowTitle: '',
|
|
311
|
+
startupOptions: { server: '', port: 99999, channel: 'x' },
|
|
312
|
+
});
|
|
313
|
+
expect.fail('expected parseKiwiConfig to throw');
|
|
314
|
+
} catch (err) {
|
|
315
|
+
expect(err).toBeInstanceOf(Error);
|
|
316
|
+
const msg = String((err as Error).message);
|
|
317
|
+
expect(msg).toMatch(/Invalid Kiwi web config/u);
|
|
318
|
+
// Aggregated: every offending field is named.
|
|
319
|
+
expect(msg).toMatch(/windowTitle/u);
|
|
320
|
+
expect(msg).toMatch(/server/u);
|
|
321
|
+
expect(msg).toMatch(/port/u);
|
|
322
|
+
expect(msg).toMatch(/channel/u);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('rejects non-object inputs', () => {
|
|
327
|
+
expect(() => parseKiwiConfig('hello')).toThrowError(/Invalid Kiwi web config/u);
|
|
328
|
+
expect(() => parseKiwiConfig(null)).toThrowError(/Invalid Kiwi web config/u);
|
|
329
|
+
expect(() => parseKiwiConfig([])).toThrowError(/Invalid Kiwi web config/u);
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// ============================================================================
|
|
334
|
+
// KiwiConfigSchema — server auto-detect ({{hostname}} template)
|
|
335
|
+
// ============================================================================
|
|
336
|
+
//
|
|
337
|
+
// Kiwi registers a value-replacement pass on every page load (upstream
|
|
338
|
+
// `src/main.js`: `addValueReplacement('hostname', window.location.hostname)`
|
|
339
|
+
// et al.) that substitutes `{{hostname}}` / `{{host}}` / `{{port}}` in any
|
|
340
|
+
// config string at browser load time. Baking `{{hostname}}` into
|
|
341
|
+
// `startupOptions.server` therefore makes the SPA connect to whatever host
|
|
342
|
+
// the Worker served it from — staging, prod, preview, PR deployment — without
|
|
343
|
+
// a per-env hostname baked at build time. This is the deploy-time resolution
|
|
344
|
+
// path; the schema accepts (but no longer requires) a literal hostname so an
|
|
345
|
+
// operator can still pin one for bespoke deployments.
|
|
346
|
+
|
|
347
|
+
describe('KiwiConfigSchema — server auto-detect ({{hostname}} template)', () => {
|
|
348
|
+
it('accepts the {{hostname}} template (Kiwi resolves it to window.location.hostname)', () => {
|
|
349
|
+
const cfg = parseKiwiConfig(
|
|
350
|
+
validConfig({ startupOptions: validStartupOptions({ server: '{{hostname}}' }) }),
|
|
351
|
+
);
|
|
352
|
+
expect(cfg.startupOptions.server).toBe('{{hostname}}');
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('accepts the {{host}} template (includes port when non-default)', () => {
|
|
356
|
+
const cfg = parseKiwiConfig(
|
|
357
|
+
validConfig({ startupOptions: validStartupOptions({ server: '{{host}}' }) }),
|
|
358
|
+
);
|
|
359
|
+
expect(cfg.startupOptions.server).toBe('{{host}}');
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('still accepts a literal hostname (operator pins a specific target)', () => {
|
|
363
|
+
const cfg = parseKiwiConfig(
|
|
364
|
+
validConfig({ startupOptions: validStartupOptions({ server: 'irc.example.com' }) }),
|
|
365
|
+
);
|
|
366
|
+
expect(cfg.startupOptions.server).toBe('irc.example.com');
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// ============================================================================
|
|
371
|
+
// Static config files — per-env build matrix
|
|
372
|
+
// ============================================================================
|
|
373
|
+
|
|
374
|
+
describe('static config files', () => {
|
|
375
|
+
it('config.json parses (default = staging target)', () => {
|
|
376
|
+
const cfg = parseKiwiConfig(readStaticConfig('config.json'));
|
|
377
|
+
expect(cfg.startupOptions.server.length).toBeGreaterThan(0);
|
|
378
|
+
expect(cfg.startupOptions.channel.startsWith('#')).toBe(true);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it('config.staging.json parses', () => {
|
|
382
|
+
const cfg = parseKiwiConfig(readStaticConfig('config.staging.json'));
|
|
383
|
+
expect(cfg.startupOptions.channel.startsWith('#')).toBe(true);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it('config.prod.json parses', () => {
|
|
387
|
+
const cfg = parseKiwiConfig(readStaticConfig('config.prod.json'));
|
|
388
|
+
expect(cfg.startupOptions.channel.startsWith('#')).toBe(true);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it('the raw schema also accepts every static config', () => {
|
|
392
|
+
expect(KiwiConfigSchema.safeParse(readStaticConfig('config.json')).success).toBe(true);
|
|
393
|
+
expect(KiwiConfigSchema.safeParse(readStaticConfig('config.staging.json')).success).toBe(true);
|
|
394
|
+
expect(KiwiConfigSchema.safeParse(readStaticConfig('config.prod.json')).success).toBe(true);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// Deploy-time resolution: every shipped config uses the {{hostname}} template
|
|
398
|
+
// so the browser resolves `server` from window.location.hostname at load
|
|
399
|
+
// time. This is the whole point of NOT baking a per-env hostname; if any
|
|
400
|
+
// config regresses to a literal host, this assertion fires.
|
|
401
|
+
it('every static config bakes {{hostname}} for server (deploy-time auto-detect)', () => {
|
|
402
|
+
for (const file of ['config.json', 'config.staging.json', 'config.prod.json']) {
|
|
403
|
+
const cfg = parseKiwiConfig(readStaticConfig(file));
|
|
404
|
+
expect(cfg.startupOptions.server, `${file} server`).toBe('{{hostname}}');
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// The Worker is a direct IRC-over-WebSocket server, NOT a kiwiServer WebIRC
|
|
409
|
+
// gateway. Every shipped config MUST set direct:true + tls:true so Kiwi
|
|
410
|
+
// opens a wss:// WebSocket straight to the Worker instead of probing the
|
|
411
|
+
// (non-existent) /webirc/kiwiirc/info gateway endpoint and 404-ing.
|
|
412
|
+
it('every static config sets direct:true + tls:true (no kiwiServer gateway)', () => {
|
|
413
|
+
for (const file of ['config.json', 'config.staging.json', 'config.prod.json']) {
|
|
414
|
+
const cfg = parseKiwiConfig(readStaticConfig(file));
|
|
415
|
+
expect(cfg.startupOptions.direct, `${file} direct`).toBe(true);
|
|
416
|
+
expect(cfg.startupOptions.tls, `${file} tls`).toBe(true);
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
// Upstream Kiwi ships `static/plugins/customise.html.example` (a template),
|
|
421
|
+
// not `customise.html`. Referencing the un-served `.html` path — which the
|
|
422
|
+
// upstream default config also does — produces a 404 + console error on
|
|
423
|
+
// every page load. We don't customise Kiwi, so no static config may ship a
|
|
424
|
+
// `plugins` block. If plugin support is ever needed, the build must copy
|
|
425
|
+
// `.example` → `.html` (or ship a real plugin file) first.
|
|
426
|
+
it('no static config ships a plugins block (avoids the customise.html 404)', () => {
|
|
427
|
+
for (const file of ['config.json', 'config.staging.json', 'config.prod.json']) {
|
|
428
|
+
const raw = readStaticConfig(file) as Record<string, unknown>;
|
|
429
|
+
expect(raw.plugins, `${file} plugins`).toBeUndefined();
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import pkg from '../package.json' with { type: 'json' };
|
|
3
|
+
|
|
4
|
+
describe('apps/web workspace skeleton', () => {
|
|
5
|
+
it('exposes the @serverless-ircd/web package name', () => {
|
|
6
|
+
expect(pkg.name).toBe('@serverless-ircd/web');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('reserves a build task that the workspace can run', () => {
|
|
10
|
+
expect(typeof pkg.scripts?.build).toBe('string');
|
|
11
|
+
});
|
|
12
|
+
});
|