serverless-ircd 0.6.0 → 0.8.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 +9 -4
- package/.gitmodules +4 -1
- package/CHANGELOG.md +480 -22
- package/README.md +244 -57
- package/apps/aws-stack/package.json +1 -1
- package/apps/aws-stack/src/aws-stack.ts +186 -18
- package/apps/aws-stack/tests/stack.test.ts +400 -56
- package/apps/cf-tcp-container/package.json +1 -1
- package/apps/cf-worker/package.json +1 -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/index.html +18 -0
- package/apps/cf-worker/tests/fixtures/web-dist/webclient/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/wrangler.test.toml +23 -0
- package/apps/cf-worker/wrangler.toml +56 -0
- package/apps/local-cli/package.json +1 -1
- package/apps/local-cli/src/config-loader.ts +8 -0
- package/apps/local-cli/src/main.ts +16 -0
- package/apps/local-cli/src/server.ts +1 -0
- package/apps/local-cli/tests/config-loader.test.ts +14 -0
- package/apps/web/landing/index.html +215 -0
- package/apps/web/package.json +28 -0
- package/apps/web/scripts/build.mjs +132 -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 +1 -0
- package/docs/AWS-Deployment.md +21 -8
- package/docs/Cloudflare-Deployment-Guide.md +22 -6
- package/docs/Home.md +1 -0
- package/docs/PlanExtensions.md +113 -3
- package/docs/Release-Process.md +23 -13
- package/docs/Services.md +546 -0
- package/docs/WebClientGuide.md +536 -0
- package/package.json +2 -2
- package/packages/aws-adapter/package.json +1 -1
- package/packages/aws-adapter/src/aws-runtime.ts +5 -0
- package/packages/aws-adapter/src/cdk-table-defs.ts +6 -0
- package/packages/aws-adapter/src/config-loader.ts +11 -0
- package/packages/aws-adapter/src/connection-counter.ts +89 -0
- package/packages/aws-adapter/src/dynamo-services-store.ts +649 -0
- package/packages/aws-adapter/src/handlers/connect.ts +55 -51
- package/packages/aws-adapter/src/handlers/default.ts +36 -4
- package/packages/aws-adapter/src/handlers/index.ts +15 -0
- package/packages/aws-adapter/src/handlers/nlb-stream.ts +15 -0
- package/packages/aws-adapter/src/handlers/sweeper.ts +5 -1
- package/packages/aws-adapter/src/index.ts +4 -0
- package/packages/aws-adapter/src/stats.ts +6 -1
- package/packages/aws-adapter/src/tables.ts +34 -4
- package/packages/aws-adapter/tests/aws-harness.ts +3 -0
- package/packages/aws-adapter/tests/config-loader.test.ts +8 -0
- package/packages/aws-adapter/tests/connect.test.ts +158 -32
- package/packages/aws-adapter/tests/connection-counter.test.ts +127 -0
- package/packages/aws-adapter/tests/dynamo-services-store-dynamo.test.ts +183 -0
- package/packages/aws-adapter/tests/dynamo-services-store-unit.test.ts +568 -0
- package/packages/aws-adapter/tests/handlers.test.ts +105 -3
- package/packages/aws-adapter/tests/tables.test.ts +6 -1
- package/packages/cf-adapter/package.json +1 -1
- package/packages/cf-adapter/src/config-loader.ts +11 -0
- package/packages/cf-adapter/src/connection-do.ts +112 -2
- package/packages/cf-adapter/src/d1-services-store.ts +703 -0
- package/packages/cf-adapter/src/env.ts +8 -0
- package/packages/cf-adapter/src/index.ts +5 -0
- package/packages/cf-adapter/tests/cf-harness.ts +12 -1
- package/packages/cf-adapter/tests/config-loader.test.ts +19 -0
- package/packages/cf-adapter/tests/connection-do-nickserv-d1.test.ts +128 -0
- package/packages/cf-adapter/tests/connection-do.test.ts +150 -2
- package/packages/cf-adapter/tests/d1-services-store.test.ts +582 -0
- package/packages/cf-adapter/tests/serialize.test.ts +1 -0
- 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/scripts/generate-build-info.mjs +26 -5
- package/packages/irc-core/src/commands/account-auth.ts +172 -0
- package/packages/irc-core/src/commands/chanserv.ts +882 -0
- package/packages/irc-core/src/commands/hostserv.ts +487 -0
- package/packages/irc-core/src/commands/index.ts +12 -0
- package/packages/irc-core/src/commands/join.ts +164 -8
- package/packages/irc-core/src/commands/markread.ts +202 -0
- package/packages/irc-core/src/commands/memoserv.ts +319 -0
- package/packages/irc-core/src/commands/mode.ts +96 -4
- package/packages/irc-core/src/commands/nickserv.ts +390 -0
- package/packages/irc-core/src/commands/oper.ts +18 -1
- package/packages/irc-core/src/commands/operserv.ts +346 -0
- package/packages/irc-core/src/commands/pre-away.ts +3 -1
- package/packages/irc-core/src/commands/privmsg.ts +42 -0
- package/packages/irc-core/src/commands/read-marker.ts +8 -8
- package/packages/irc-core/src/commands/registration.ts +61 -6
- package/packages/irc-core/src/commands/sasl.ts +18 -49
- package/packages/irc-core/src/commands/tagmsg.ts +41 -6
- package/packages/irc-core/src/commands/topic.ts +37 -0
- package/packages/irc-core/src/config.ts +36 -5
- package/packages/irc-core/src/effects.ts +56 -1
- package/packages/irc-core/src/ports.ts +1653 -84
- package/packages/irc-core/src/protocol/numerics.ts +8 -0
- package/packages/irc-core/src/state/channel.ts +21 -1
- package/packages/irc-core/src/state/connection.ts +25 -1
- package/packages/irc-core/src/types.ts +48 -12
- package/packages/irc-core/tests/batch.test.ts +15 -0
- package/packages/irc-core/tests/commands/chanserv.test.ts +1668 -0
- package/packages/irc-core/tests/commands/chathistory.test.ts +6 -0
- package/packages/irc-core/tests/commands/hostserv.test.ts +935 -0
- package/packages/irc-core/tests/commands/join.test.ts +393 -1
- package/packages/irc-core/tests/commands/markread.test.ts +361 -0
- package/packages/irc-core/tests/commands/memoserv.test.ts +654 -0
- package/packages/irc-core/tests/commands/mode.test.ts +381 -2
- package/packages/irc-core/tests/commands/nickserv.test.ts +807 -0
- package/packages/irc-core/tests/commands/oper.test.ts +13 -0
- package/packages/irc-core/tests/commands/operserv.test.ts +656 -0
- package/packages/irc-core/tests/commands/privmsg.test.ts +147 -0
- package/packages/irc-core/tests/commands/read-marker.test.ts +28 -28
- package/packages/irc-core/tests/commands/registration.test.ts +788 -14
- package/packages/irc-core/tests/commands/sasl.test.ts +185 -12
- package/packages/irc-core/tests/commands/server-info.test.ts +9 -5
- package/packages/irc-core/tests/commands/tagmsg.test.ts +73 -33
- package/packages/irc-core/tests/commands/topic.test.ts +94 -2
- package/packages/irc-core/tests/commands/unified-account.test.ts +416 -0
- package/packages/irc-core/tests/config.test.ts +49 -5
- package/packages/irc-core/tests/effects.test.ts +19 -0
- package/packages/irc-core/tests/message-store.test.ts +63 -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/persistent-services-store.test.ts +582 -0
- package/packages/irc-core/tests/services-store.test.ts +1289 -0
- package/packages/irc-core/tests/state/channel.test.ts +3 -0
- package/packages/irc-server/package.json +1 -1
- package/packages/irc-server/src/actor.ts +71 -16
- package/packages/irc-server/src/dispatch.ts +94 -7
- package/packages/irc-server/src/routing.ts +19 -0
- package/packages/irc-server/tests/actor.test.ts +623 -12
- package/packages/irc-server/tests/dispatch.test.ts +270 -2
- package/packages/irc-server/tests/routing.test.ts +6 -0
- package/packages/irc-test-support/package.json +1 -1
- package/packages/irc-test-support/src/in-memory-harness.ts +29 -3
- package/packages/irc-test-support/src/index.ts +1 -0
- package/packages/irc-test-support/src/scenarios.ts +24 -2
- package/packages/irc-test-support/tests/in-memory-harness.test.ts +32 -0
- package/tools/ci-hardening/package.json +1 -1
- package/tools/load-test/package.json +1 -1
- package/tools/tcp-ws-forwarder/package.json +1 -1
- package/tools/tcp-ws-forwarder/tests/forwarder.test.ts +2 -2
- package/packages/irc-core/tests/read-marker-store.test.ts +0 -108
|
@@ -35,6 +35,12 @@ interface CliArgs {
|
|
|
35
35
|
serverName: string;
|
|
36
36
|
/** Network name advertised in 005 NETWORK=... */
|
|
37
37
|
networkName: string;
|
|
38
|
+
/**
|
|
39
|
+
* Server-password gate. Undefined disables the gate; when set every
|
|
40
|
+
* connection must supply the matching `PASS <value>` (or SASL-identify)
|
|
41
|
+
* before `001 RPL_WELCOME`.
|
|
42
|
+
*/
|
|
43
|
+
serverPassword: string | undefined;
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
const DEFAULT_PORT = 6667;
|
|
@@ -49,6 +55,7 @@ function parseArgs(argv: string[]): CliArgs {
|
|
|
49
55
|
motdFile: undefined,
|
|
50
56
|
serverName: DEFAULT_SERVER_NAME,
|
|
51
57
|
networkName: DEFAULT_NETWORK_NAME,
|
|
58
|
+
serverPassword: undefined,
|
|
52
59
|
};
|
|
53
60
|
let tcpDisabled = false;
|
|
54
61
|
let tcpPortOverride: number | undefined;
|
|
@@ -73,6 +80,9 @@ function parseArgs(argv: string[]): CliArgs {
|
|
|
73
80
|
} else if (a === '--network-name' && next !== undefined) {
|
|
74
81
|
out.networkName = next;
|
|
75
82
|
i++;
|
|
83
|
+
} else if (a === '--server-password' && next !== undefined) {
|
|
84
|
+
out.serverPassword = next;
|
|
85
|
+
i++;
|
|
76
86
|
} else if (a === '--no-tcp') {
|
|
77
87
|
tcpDisabled = true;
|
|
78
88
|
} else if (a === '--help' || a === '-h') {
|
|
@@ -92,6 +102,11 @@ function parseArgs(argv: string[]): CliArgs {
|
|
|
92
102
|
' Optional; defaults to irc.localhost for the local CLI.',
|
|
93
103
|
' --network-name <n> Network name in 005 NETWORK=...',
|
|
94
104
|
' Optional; defaults to LocalNet.',
|
|
105
|
+
' --server-password <p>',
|
|
106
|
+
' Server-password gate (optional). When set, every',
|
|
107
|
+
' connection must supply the matching PASS <p> (or',
|
|
108
|
+
' SASL-identify) before 001 RPL_WELCOME. Treat as a',
|
|
109
|
+
' secret; do not log it.',
|
|
95
110
|
' -h, --help Show this help and exit.',
|
|
96
111
|
'',
|
|
97
112
|
].join('\n'),
|
|
@@ -119,6 +134,7 @@ async function main(): Promise<void> {
|
|
|
119
134
|
tcpPort: args.tcpPort,
|
|
120
135
|
serverName: args.serverName,
|
|
121
136
|
networkName: args.networkName,
|
|
137
|
+
...(args.serverPassword !== undefined ? { serverPassword: args.serverPassword } : {}),
|
|
122
138
|
...(motdLines !== undefined ? { motdLines } : {}),
|
|
123
139
|
});
|
|
124
140
|
|
|
@@ -302,6 +302,7 @@ export function startLocalServer(opts: StartServerOptions): Promise<LocalServer>
|
|
|
302
302
|
...(opts.topicLen !== undefined ? { topicLen: opts.topicLen } : {}),
|
|
303
303
|
...(opts.quitMessage !== undefined ? { quitMessage: opts.quitMessage } : {}),
|
|
304
304
|
...(opts.saslAccounts !== undefined ? { saslAccounts: opts.saslAccounts } : {}),
|
|
305
|
+
...(opts.serverPassword !== undefined ? { serverPassword: opts.serverPassword } : {}),
|
|
305
306
|
});
|
|
306
307
|
} catch (err) {
|
|
307
308
|
return Promise.reject(err);
|
|
@@ -55,6 +55,20 @@ describe('loadServerConfigFromCliArgs — minimal input', () => {
|
|
|
55
55
|
expect(cfg.maxClients).toBe(50);
|
|
56
56
|
expect(cfg.operCreds).toEqual([{ user: 'op', password: 'p' }]);
|
|
57
57
|
});
|
|
58
|
+
|
|
59
|
+
it('threads serverPassword through to the parsed config', () => {
|
|
60
|
+
const cfg = loadServerConfigFromCliArgs({
|
|
61
|
+
serverName: 's',
|
|
62
|
+
networkName: 'n',
|
|
63
|
+
serverPassword: 'hunter2',
|
|
64
|
+
});
|
|
65
|
+
expect(cfg.serverPassword).toBe('hunter2');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('leaves serverPassword undefined when not supplied', () => {
|
|
69
|
+
const cfg = loadServerConfigFromCliArgs({ serverName: 's', networkName: 'n' });
|
|
70
|
+
expect(cfg.serverPassword).toBeUndefined();
|
|
71
|
+
});
|
|
58
72
|
});
|
|
59
73
|
|
|
60
74
|
describe('loadServerConfigFromCliArgs — failure modes', () => {
|
|
@@ -0,0 +1,215 @@
|
|
|
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 `/webclient/`; this page links through to it plus
|
|
8
|
+
the 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>An IRC daemon with a serverless deployment model.</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
|
+
platform-agnostic TypeScript core. Two adapters run that core on
|
|
182
|
+
<strong>Cloudflare Workers</strong> (Durable Objects) and
|
|
183
|
+
<strong>AWS</strong> (API Gateway WebSockets + Lambda + DynamoDB).
|
|
184
|
+
</p>
|
|
185
|
+
|
|
186
|
+
<ul class="features">
|
|
187
|
+
<li>Reducer-based protocol core: each command is a pure function</li>
|
|
188
|
+
<li>WebSocket and <code>irc+tls</code> (port 6697) transports</li>
|
|
189
|
+
<li>IRCv3 extensions: message-tags, chathistory, SASL, labeled-response</li>
|
|
190
|
+
<li>Per-datum authoritative state held in a single location</li>
|
|
191
|
+
</ul>
|
|
192
|
+
|
|
193
|
+
<div class="cta">
|
|
194
|
+
<a class="primary" href="/webclient/">Launch Web Client</a>
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
<div class="links">
|
|
198
|
+
<a href="https://gitea.com/ServerlessIRCd/ServerlessIRCd">Source Code (Gitea)</a>
|
|
199
|
+
<a href="https://gitea.com/ServerlessIRCd/ServerlessIRCd/issues">Issue Tracker</a>
|
|
200
|
+
<a href="/webclient/">Web IRC Client</a>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
</main>
|
|
204
|
+
|
|
205
|
+
<footer>
|
|
206
|
+
<div class="container">
|
|
207
|
+
<p>
|
|
208
|
+
ServerlessIRCd ·
|
|
209
|
+
<a href="https://gitea.com/ServerlessIRCd/ServerlessIRCd">Gitea</a> ·
|
|
210
|
+
BSD-3-Clause License
|
|
211
|
+
</p>
|
|
212
|
+
</div>
|
|
213
|
+
</footer>
|
|
214
|
+
</body>
|
|
215
|
+
</html>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@serverless-ircd/web",
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Web client workspace: Kiwi IRC SPA served at /webclient/ 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,132 @@
|
|
|
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, 'webclient');
|
|
13
|
+
const staticDir = path.join(pkgRoot, 'static');
|
|
14
|
+
|
|
15
|
+
// The SPA is served at /webclient/ on the Worker (the [assets] binding serves
|
|
16
|
+
// `dist/`). All hashed asset URLs must be /webclient/-prefixed so they load
|
|
17
|
+
// from /webclient/static/... rather than /static/... (which would 404 at root).
|
|
18
|
+
const PUBLIC_PATH = '/webclient/';
|
|
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`. The
|
|
49
|
+
// guard checks for the actual binary the build invokes, not just the
|
|
50
|
+
// `node_modules` directory — a stale/partial install (interrupted, or
|
|
51
|
+
// populated by a foreign package manager that skipped `.bin/` linking)
|
|
52
|
+
// leaves the directory present but `vue-cli-service` unresolvable, which
|
|
53
|
+
// would otherwise crash the build at step 2 with a confusing exit 127.
|
|
54
|
+
const vueCliBin = path.join(upstream, 'node_modules', '.bin', 'vue-cli-service');
|
|
55
|
+
if (!existsSync(vueCliBin)) {
|
|
56
|
+
runYarn(['install', '--frozen-lockfile', '--ignore-engines']);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 2. Build with publicPath=/webclient/. Upstream pins publicPath:'' (relative);
|
|
60
|
+
// we temporarily patch vue.config.js so webpack emits /webclient/-prefixed
|
|
61
|
+
// asset URLs and the runtime public_path matches the mount point. Restored
|
|
62
|
+
// in `finally` so the submodule stays clean.
|
|
63
|
+
const vueConfigPath = path.join(upstream, 'vue.config.js');
|
|
64
|
+
const originalVueConfig = await readFile(vueConfigPath, 'utf8');
|
|
65
|
+
try {
|
|
66
|
+
const patched = originalVueConfig.replace(/publicPath:\s*''/, `publicPath: '${PUBLIC_PATH}'`);
|
|
67
|
+
if (patched === originalVueConfig) {
|
|
68
|
+
throw new Error('Could not locate `publicPath` in upstream vue.config.js; patching failed.');
|
|
69
|
+
}
|
|
70
|
+
await writeFile(vueConfigPath, patched);
|
|
71
|
+
runYarn(['build']);
|
|
72
|
+
} finally {
|
|
73
|
+
await writeFile(vueConfigPath, originalVueConfig);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 3. Copy the built SPA into dist/webclient/.
|
|
77
|
+
await rm(appDist, { recursive: true, force: true });
|
|
78
|
+
await mkdir(appDist, { recursive: true });
|
|
79
|
+
await cp(path.join(upstream, 'dist'), appDist, { recursive: true });
|
|
80
|
+
|
|
81
|
+
// 3b. Webpack applies publicPath to the bundles it emits, but template-hardcoded
|
|
82
|
+
// refs (e.g. the favicon) stay relative. Prefix every bare `static/...`
|
|
83
|
+
// reference so all asset URLs resolve to /webclient/static/... when served
|
|
84
|
+
// at /webclient/.
|
|
85
|
+
const indexDest = path.join(appDist, 'index.html');
|
|
86
|
+
let indexHtml = await readFile(indexDest, 'utf8');
|
|
87
|
+
indexHtml = indexHtml.replace(/((?:src|href)=["'])(static\/)/g, `$1${PUBLIC_PATH}$2`);
|
|
88
|
+
await writeFile(indexDest, indexHtml);
|
|
89
|
+
|
|
90
|
+
// 4. Layer our per-env static/config.*.json over the upstream default. The
|
|
91
|
+
// selected file is parsed against the Zod schema (`src/config-schema.ts`)
|
|
92
|
+
// so missing fields, out-of-range ports, and non-`#` channels abort the
|
|
93
|
+
// build here with a readable error. The validated JSON is re-serialised
|
|
94
|
+
// (stable key order) so the baked config is identical to the schema output.
|
|
95
|
+
const { file: configFileName, env: configEnv } = resolveConfigFile(process.argv.slice(2));
|
|
96
|
+
const configSrcPath = path.join(staticDir, configFileName);
|
|
97
|
+
if (!existsSync(configSrcPath)) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
`Config file for env "${configEnv}" not found at ${configSrcPath}. Expected one of: config.json, config.staging.json, config.prod.json.`,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
const configRaw = await readFile(configSrcPath, 'utf8');
|
|
103
|
+
let configParsed;
|
|
104
|
+
try {
|
|
105
|
+
configParsed = parseKiwiConfig(JSON.parse(configRaw));
|
|
106
|
+
} catch (err) {
|
|
107
|
+
// Re-throw with the file name prepended so the build error names the
|
|
108
|
+
// offending env config, not just the schema path.
|
|
109
|
+
throw new Error(`${configFileName}: ${err.message}`, { cause: err });
|
|
110
|
+
}
|
|
111
|
+
await writeFile(
|
|
112
|
+
path.join(appDist, 'static', 'config.json'),
|
|
113
|
+
`${JSON.stringify(configParsed, null, 2)}\n`,
|
|
114
|
+
'utf8',
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// 5. Landing page: copy the static landing page (landing/index.html) to
|
|
118
|
+
// dist/index.html so the Worker `[assets]` binding serves it at `/`. Pure
|
|
119
|
+
// static HTML + inline CSS (no JS); links to /webclient/ (the SPA), docs/,
|
|
120
|
+
// and the source repo.
|
|
121
|
+
await mkdir(dist, { recursive: true });
|
|
122
|
+
const landingSrc = path.join(pkgRoot, 'landing', 'index.html');
|
|
123
|
+
if (!existsSync(landingSrc)) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`Landing page source missing at ${landingSrc}. Expected apps/web/landing/index.html.`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
await cp(landingSrc, path.join(dist, 'index.html'));
|
|
129
|
+
|
|
130
|
+
console.log(
|
|
131
|
+
`Kiwi SPA built -> dist/webclient/ (publicPath=${PUBLIC_PATH}, config=${configFileName}, env=${configEnv})`,
|
|
132
|
+
);
|
|
@@ -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/webclient/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/webclient/static/config.json`, so a malformed
|
|
8
|
+
* config fails the build with a readable aggregated error rather than
|
|
9
|
+
* shipping a broken SPA. The schema only models the fields ServerlessIRCd
|
|
10
|
+
* cares about (server, port, channel, WS path); the rest of Kiwi's surface
|
|
11
|
+
* is passed 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 `/webclient/` 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
|
+
}
|