serverless-ircd 0.2.0 → 0.4.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 +2 -2
- package/.github/workflows/deploy-aws.yml +1 -3
- package/.github/workflows/deploy-cf-tcp.yml +87 -0
- package/.github/workflows/deploy-cf.yml +1 -1
- package/.node-version +1 -0
- package/.nvmrc +1 -0
- package/CHANGELOG.md +258 -18
- package/README.md +72 -30
- package/apps/aws-stack/README.md +2 -2
- package/apps/aws-stack/bin/aws.ts +7 -0
- package/apps/aws-stack/package.json +4 -4
- package/apps/aws-stack/src/aws-stack.ts +118 -6
- package/apps/aws-stack/tests/stack.test.ts +98 -3
- package/apps/cf-tcp-container/Dockerfile +69 -0
- package/apps/cf-tcp-container/package.json +34 -0
- package/apps/cf-tcp-container/src/config-loader.ts +145 -0
- package/apps/cf-tcp-container/src/container-do.ts +38 -0
- package/apps/cf-tcp-container/src/container-server.ts +363 -0
- package/apps/cf-tcp-container/src/main.ts +77 -0
- package/apps/cf-tcp-container/src/persistence.ts +144 -0
- package/apps/cf-tcp-container/src/worker.ts +41 -0
- package/apps/cf-tcp-container/terraform/provider.tf +24 -0
- package/apps/cf-tcp-container/terraform/spectrum.tf +81 -0
- package/apps/cf-tcp-container/tests/config-loader.test.ts +217 -0
- package/apps/cf-tcp-container/tests/container-server.test.ts +465 -0
- package/apps/cf-tcp-container/tests/persistence.test.ts +227 -0
- package/apps/cf-tcp-container/tests/tls-e2e.test.ts +275 -0
- package/apps/cf-tcp-container/tsconfig.build.json +17 -0
- package/apps/cf-tcp-container/tsconfig.test.json +15 -0
- package/apps/cf-tcp-container/vitest.config.ts +26 -0
- package/apps/cf-tcp-container/wrangler.toml +63 -0
- package/apps/cf-worker/package.json +1 -1
- package/apps/cf-worker/scripts/smoke.mjs +0 -0
- package/apps/cf-worker/src/worker.ts +8 -2
- package/apps/cf-worker/tests/smoke.test.ts +1 -0
- package/apps/cf-worker/wrangler.test.toml +11 -1
- package/apps/cf-worker/wrangler.toml +69 -19
- package/apps/local-cli/package.json +1 -1
- package/apps/local-cli/src/config-loader.ts +11 -0
- package/apps/local-cli/src/main.ts +1 -1
- package/apps/local-cli/src/server.ts +46 -3
- package/apps/local-cli/tests/e2e.test.ts +52 -0
- package/package.json +19 -16
- package/packages/aws-adapter/package.json +3 -3
- package/packages/aws-adapter/src/account-store.ts +121 -0
- package/packages/aws-adapter/src/admission.ts +74 -0
- package/packages/aws-adapter/src/aws-runtime.ts +124 -34
- package/packages/aws-adapter/src/cdk-table-defs.ts +5 -1
- package/packages/aws-adapter/src/config-loader.ts +62 -0
- package/packages/aws-adapter/src/dynamo-account-store.ts +95 -0
- package/packages/aws-adapter/src/handlers/connect.ts +64 -8
- package/packages/aws-adapter/src/handlers/default.ts +43 -2
- package/packages/aws-adapter/src/handlers/disconnect.ts +27 -8
- package/packages/aws-adapter/src/handlers/index.ts +120 -9
- package/packages/aws-adapter/src/handlers/nlb-stream.ts +481 -0
- package/packages/aws-adapter/src/handlers/ping-checker.ts +1 -1
- package/packages/aws-adapter/src/index.ts +13 -0
- package/packages/aws-adapter/tests/account-store-dynamo.test.ts +182 -0
- package/packages/aws-adapter/tests/account-store.test.ts +279 -0
- package/packages/aws-adapter/tests/admission.test.ts +70 -0
- package/packages/aws-adapter/tests/aws-harness.ts +13 -1
- package/packages/aws-adapter/tests/config-loader.test.ts +75 -0
- package/packages/aws-adapter/tests/connect.test.ts +78 -0
- package/packages/aws-adapter/tests/disconnect-fanout.test.ts +343 -0
- package/packages/aws-adapter/tests/gone-exception.test.ts +31 -26
- package/packages/aws-adapter/tests/handlers.test.ts +194 -47
- package/packages/aws-adapter/tests/nlb-stream.test.ts +478 -0
- package/packages/aws-adapter/tests/ping-checker.test.ts +34 -29
- package/packages/aws-adapter/tests/sweeper.test.ts +25 -18
- package/packages/aws-adapter/tests/transactions.test.ts +25 -20
- package/packages/cf-adapter/package.json +1 -1
- package/packages/cf-adapter/src/cf-runtime.ts +40 -22
- package/packages/cf-adapter/src/channel-do.ts +40 -1
- package/packages/cf-adapter/src/channel-registry-do.ts +58 -0
- package/packages/cf-adapter/src/config-loader.ts +62 -0
- package/packages/cf-adapter/src/connection-do.ts +181 -31
- package/packages/cf-adapter/src/d1-account-store.ts +198 -0
- package/packages/cf-adapter/src/env.ts +58 -8
- package/packages/cf-adapter/src/index.ts +11 -9
- package/packages/cf-adapter/tests/cf-harness.ts +11 -1
- package/packages/cf-adapter/tests/cf-integration.test.ts +2 -1
- package/packages/cf-adapter/tests/cf-runtime.test.ts +91 -0
- package/packages/cf-adapter/tests/config-loader.test.ts +22 -0
- package/packages/cf-adapter/tests/connection-do-channel-registration.test.ts +37 -0
- package/packages/cf-adapter/tests/connection-do-no-batching-reservation.test.ts +52 -0
- package/packages/cf-adapter/tests/connection-do-sasl-d1.test.ts +166 -0
- package/packages/cf-adapter/tests/connection-do.test.ts +40 -0
- package/packages/cf-adapter/tests/d1-account-store.test.ts +226 -0
- package/packages/cf-adapter/tests/raw-modules.d.ts +11 -0
- package/packages/cf-adapter/tests/worker/main.ts +9 -1
- package/packages/cf-adapter/tests/worker/stubs/channel-stub.ts +7 -1
- package/packages/cf-adapter/wrangler.test.toml +18 -1
- package/packages/in-memory-runtime/package.json +1 -1
- package/packages/in-memory-runtime/src/in-memory-runtime.ts +14 -28
- package/packages/in-memory-runtime/tests/in-memory-runtime.test.ts +19 -0
- package/packages/irc-core/package.json +8 -2
- package/packages/irc-core/scripts/generate-build-info.mjs +31 -0
- package/packages/irc-core/src/caps/capabilities.ts +23 -2
- package/packages/irc-core/src/case-fold.ts +64 -0
- package/packages/irc-core/src/cloak.ts +1 -1
- package/packages/irc-core/src/commands/cap.ts +8 -1
- package/packages/irc-core/src/commands/index.ts +11 -0
- package/packages/irc-core/src/commands/invite.ts +6 -9
- package/packages/irc-core/src/commands/ison.ts +61 -0
- package/packages/irc-core/src/commands/isupport.ts +1 -1
- package/packages/irc-core/src/commands/join.ts +4 -3
- package/packages/irc-core/src/commands/kick.ts +4 -11
- package/packages/irc-core/src/commands/list.ts +5 -4
- package/packages/irc-core/src/commands/mode.ts +5 -9
- package/packages/irc-core/src/commands/motd-lines.ts +127 -0
- package/packages/irc-core/src/commands/motd.ts +6 -110
- package/packages/irc-core/src/commands/oper.ts +151 -0
- package/packages/irc-core/src/commands/quit.ts +12 -0
- package/packages/irc-core/src/commands/registration.ts +26 -19
- package/packages/irc-core/src/commands/sasl.ts +72 -9
- package/packages/irc-core/src/commands/server-info.ts +129 -0
- package/packages/irc-core/src/commands/tagmsg.ts +205 -0
- package/packages/irc-core/src/commands/userhost.ts +84 -0
- package/packages/irc-core/src/commands/whowas.ts +113 -0
- package/packages/irc-core/src/config.ts +84 -3
- package/packages/irc-core/src/credential-hashing.ts +124 -0
- package/packages/irc-core/src/effects.ts +6 -29
- package/packages/irc-core/src/index.ts +3 -0
- package/packages/irc-core/src/ports.ts +240 -7
- package/packages/irc-core/src/protocol/numerics.ts +14 -8
- package/packages/irc-core/src/types.ts +60 -1
- package/packages/irc-core/tests/account-store.test.ts +131 -0
- package/packages/irc-core/tests/caps/capabilities.test.ts +4 -3
- package/packages/irc-core/tests/case-fold.test.ts +80 -0
- package/packages/irc-core/tests/commands/cap.test.ts +33 -1
- package/packages/irc-core/tests/commands/ison.test.ts +166 -0
- package/packages/irc-core/tests/commands/kick.test.ts +15 -0
- package/packages/irc-core/tests/commands/oper.test.ts +257 -0
- package/packages/irc-core/tests/commands/quit.test.ts +69 -2
- package/packages/irc-core/tests/commands/registration.test.ts +256 -19
- package/packages/irc-core/tests/commands/sasl.test.ts +118 -10
- package/packages/irc-core/tests/commands/server-info.test.ts +274 -0
- package/packages/irc-core/tests/commands/tagmsg.test.ts +662 -0
- package/packages/irc-core/tests/commands/userhost.test.ts +264 -0
- package/packages/irc-core/tests/commands/who.test.ts +22 -4
- package/packages/irc-core/tests/commands/whois.test.ts +8 -1
- package/packages/irc-core/tests/commands/whowas.test.ts +312 -0
- package/packages/irc-core/tests/config.test.ts +170 -1
- package/packages/irc-core/tests/credential-hashing.test.ts +170 -0
- package/packages/irc-core/tests/effects.test.ts +0 -27
- package/packages/irc-core/tests/nick-history-store.test.ts +162 -0
- package/packages/irc-core/tests/numerics.test.ts +12 -0
- package/packages/irc-core/tests/types.test.ts +35 -1
- package/packages/irc-core/tsconfig.build.json +1 -1
- package/packages/irc-core/tsconfig.test.json +1 -1
- package/packages/irc-server/package.json +1 -1
- package/packages/irc-server/src/actor.ts +182 -14
- package/packages/irc-server/src/dispatch.ts +0 -3
- package/packages/irc-server/src/index.ts +10 -2
- package/packages/irc-server/src/routing.ts +21 -0
- package/packages/irc-server/src/transport.ts +101 -0
- package/packages/irc-server/tests/actor.test.ts +617 -1
- package/packages/irc-server/tests/dispatch.test.ts +0 -17
- package/packages/irc-server/tests/routing.test.ts +7 -0
- package/packages/irc-server/tests/transport.test.ts +230 -0
- package/packages/irc-test-support/package.json +1 -1
- package/packages/irc-test-support/src/harness.ts +44 -9
- package/packages/irc-test-support/src/in-memory-harness.ts +78 -13
- package/packages/irc-test-support/src/index.ts +3 -0
- package/packages/irc-test-support/src/scenarios.ts +132 -2
- package/packages/irc-test-support/tests/in-memory-harness.test.ts +2 -1
- package/packages/irc-test-support/tests/in-memory-scenarios.test.ts +23 -9
- package/pnpm-workspace.yaml +9 -0
- package/tools/ci-hardening/package.json +1 -1
- package/tools/package.json +4 -0
- package/tools/seed-aws-accounts.ts +79 -0
- package/tools/seed-cf-accounts.ts +104 -0
- package/tools/tcp-ws-forwarder/package.json +1 -1
- package/AGENTS.md +0 -5
- package/MOTD.txt +0 -3
- package/PLAN-FIXES.md +0 -358
- package/PLAN.md +0 -420
- package/dashboards/cloudwatch-irc.json +0 -139
- package/docs/AWS-Adapter-Architecture.md +0 -494
- package/docs/AWS-Deployment.md +0 -1107
- package/docs/Cloudflare-Deployment-Guide.md +0 -660
- package/docs/Home.md +0 -1
- package/docs/Observability.md +0 -87
- package/docs/PlanIRCv3Websocket.md +0 -489
- package/docs/PlanWebClient.md +0 -451
- package/docs/Release-Process.md +0 -440
- package/progress.md +0 -107
- package/tickets.md +0 -2485
- package/webircgateway/LICENSE +0 -201
- package/webircgateway/Makefile +0 -44
- package/webircgateway/README.md +0 -134
- package/webircgateway/config.conf.example +0 -135
- package/webircgateway/go.mod +0 -16
- package/webircgateway/go.sum +0 -89
- package/webircgateway/main.go +0 -118
- package/webircgateway/pkg/dnsbl/dnsbl.go +0 -121
- package/webircgateway/pkg/identd/identd.go +0 -86
- package/webircgateway/pkg/identd/rpcclient.go +0 -59
- package/webircgateway/pkg/irc/isupport.go +0 -56
- package/webircgateway/pkg/irc/message.go +0 -217
- package/webircgateway/pkg/irc/state.go +0 -79
- package/webircgateway/pkg/proxy/proxy.go +0 -129
- package/webircgateway/pkg/proxy/server.go +0 -237
- package/webircgateway/pkg/recaptcha/recaptcha.go +0 -59
- package/webircgateway/pkg/webircgateway/client.go +0 -741
- package/webircgateway/pkg/webircgateway/client_command_handlers.go +0 -495
- package/webircgateway/pkg/webircgateway/config.go +0 -385
- package/webircgateway/pkg/webircgateway/gateway.go +0 -278
- package/webircgateway/pkg/webircgateway/gateway_utils.go +0 -133
- package/webircgateway/pkg/webircgateway/hooks.go +0 -152
- package/webircgateway/pkg/webircgateway/letsencrypt.go +0 -41
- package/webircgateway/pkg/webircgateway/messagetags.go +0 -103
- package/webircgateway/pkg/webircgateway/transport_kiwiirc.go +0 -206
- package/webircgateway/pkg/webircgateway/transport_sockjs.go +0 -107
- package/webircgateway/pkg/webircgateway/transport_tcp.go +0 -113
- package/webircgateway/pkg/webircgateway/transport_websocket.go +0 -126
- package/webircgateway/pkg/webircgateway/utils.go +0 -147
- package/webircgateway/plugins/example/plugin.go +0 -11
- package/webircgateway/plugins/stats/plugin.go +0 -52
- package/webircgateway/staticcheck.conf +0 -1
- package/webircgateway/webircgateway.svg +0 -3
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Reusable IRC scenario runner — exported so adapter packages (cf-adapter,
|
|
3
3
|
* aws-stack) can register their own {@link IrcHarnessFactory} and re-run
|
|
4
|
-
* the exact same scenario suite under their native runtime.
|
|
4
|
+
* the exact same scenario suite under their native runtime AND transport.
|
|
5
5
|
*
|
|
6
6
|
* Each scenario is written once against the {@link IrcHarness} seam. The
|
|
7
7
|
* runner takes a list of factories and produces one `describe` block per
|
|
8
8
|
* factory, mirroring the parametrized matrix called for in PLAN §8
|
|
9
9
|
* ("Runtime contract: vitest parametrized over IrcRuntime. Same scenarios
|
|
10
|
-
* pass in-memory + CF + AWS.")
|
|
10
|
+
* pass in-memory + CF + AWS.") extended to cover the transport dimension
|
|
11
|
+
* ("Same scenarios pass in-memory+WS, in-memory+TCP, CF+WS, CF+TCP,
|
|
12
|
+
* AWS+WS, AWS+TCP.").
|
|
13
|
+
*
|
|
14
|
+
* The general scenario block runs against every factory regardless of
|
|
15
|
+
* transport. The TCP-framing block (chunk boundaries, slow-loris partial
|
|
16
|
+
* sends, mixed line endings) runs ONLY against factories declaring
|
|
17
|
+
* `transport: 'tcp'` — those cases are meaningless (and behave
|
|
18
|
+
* differently) under the WebSocket text-frame transport where every
|
|
19
|
+
* `feed()` is a complete unit.
|
|
11
20
|
*/
|
|
12
21
|
|
|
13
22
|
import { describe, expect, it } from 'vitest';
|
|
@@ -491,5 +500,126 @@ export function runIrcScenarios(factories: readonly IrcHarnessFactory[]): void {
|
|
|
491
500
|
await harness.close();
|
|
492
501
|
});
|
|
493
502
|
});
|
|
503
|
+
|
|
504
|
+
// -------------------------------------------------------------------------
|
|
505
|
+
// TCP-framing scenarios.
|
|
506
|
+
//
|
|
507
|
+
// These cases exercise the stateful `\r\n` byte-stream transport
|
|
508
|
+
// (PLAN §9, ADR-009): chunk boundaries, partial-line buffering across
|
|
509
|
+
// chunks, slow-loris-style one-byte sends, bare `\n` tolerance, and
|
|
510
|
+
// multi-message chunks. They are meaningless under the WebSocket
|
|
511
|
+
// text-frame transport (where every feed() is a complete unit), so
|
|
512
|
+
// they only run against factories declaring `transport: 'tcp'`.
|
|
513
|
+
// -------------------------------------------------------------------------
|
|
514
|
+
if (factory.transport === 'tcp') {
|
|
515
|
+
describe(`TCP framing — ${factory.name}`, () => {
|
|
516
|
+
let harness: IrcHarness;
|
|
517
|
+
|
|
518
|
+
it('tcp 01: message split across two chunks produces one command', async () => {
|
|
519
|
+
harness = await factory.create();
|
|
520
|
+
const c = await harness.spawnClient({ nick: 'alice' });
|
|
521
|
+
await c.waitForNumeric('001');
|
|
522
|
+
c.received.length = 0;
|
|
523
|
+
// Feed the first half — incomplete, must buffer without dispatching.
|
|
524
|
+
await c.feed('PI');
|
|
525
|
+
// Nothing has terminated yet: no PONG, no 421.
|
|
526
|
+
expect(c.received.length).toBe(0);
|
|
527
|
+
await c.feed('NG :split\r\n');
|
|
528
|
+
await c.waitForLine((l) => l === 'PONG :split');
|
|
529
|
+
await harness.close();
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it('tcp 02: partial line buffered across many one-byte chunks (slow-loris style)', async () => {
|
|
533
|
+
harness = await factory.create();
|
|
534
|
+
const c = await harness.spawnClient({ nick: 'alice' });
|
|
535
|
+
await c.waitForNumeric('001');
|
|
536
|
+
c.received.length = 0;
|
|
537
|
+
const line = 'PING :slow';
|
|
538
|
+
for (const ch of line) {
|
|
539
|
+
await c.feed(ch);
|
|
540
|
+
// No terminator yet — nothing dispatches.
|
|
541
|
+
expect(c.received.length).toBe(0);
|
|
542
|
+
}
|
|
543
|
+
await c.feed('\r\n');
|
|
544
|
+
await c.waitForLine((l) => l === 'PONG :slow');
|
|
545
|
+
await harness.close();
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
it('tcp 03: bare LF line ending is tolerated (legacy/mixed clients)', async () => {
|
|
549
|
+
harness = await factory.create();
|
|
550
|
+
const c = await harness.spawnClient({ nick: 'alice' });
|
|
551
|
+
await c.waitForNumeric('001');
|
|
552
|
+
c.received.length = 0;
|
|
553
|
+
await c.feed('PING :bare\n');
|
|
554
|
+
await c.waitForLine((l) => l === 'PONG :bare');
|
|
555
|
+
await harness.close();
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
it('tcp 04: multiple CRLF-terminated messages in one chunk dispatch in order', async () => {
|
|
559
|
+
harness = await factory.create();
|
|
560
|
+
const c = await harness.spawnClient({ nick: 'alice' });
|
|
561
|
+
await c.waitForNumeric('001');
|
|
562
|
+
c.received.length = 0;
|
|
563
|
+
await c.feed('PING :one\r\nPING :two\r\nPING :three\r\n');
|
|
564
|
+
await c.waitForLine((l) => l === 'PONG :three');
|
|
565
|
+
const pongs = c.received.filter((l) => l.startsWith('PONG :'));
|
|
566
|
+
expect(pongs).toEqual(['PONG :one', 'PONG :two', 'PONG :three']);
|
|
567
|
+
await harness.close();
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
it('tcp 05: mixed CRLF and LF terminators in one chunk both frame', async () => {
|
|
571
|
+
harness = await factory.create();
|
|
572
|
+
const c = await harness.spawnClient({ nick: 'alice' });
|
|
573
|
+
await c.waitForNumeric('001');
|
|
574
|
+
c.received.length = 0;
|
|
575
|
+
await c.feed('PING :crlf\r\nPING :lf\n');
|
|
576
|
+
await c.waitForLine((l) => l === 'PONG :lf');
|
|
577
|
+
const toks = c.received
|
|
578
|
+
.filter((l) => l.startsWith('PONG :'))
|
|
579
|
+
.map((l) => l.slice('PONG :'.length));
|
|
580
|
+
expect(toks).toEqual(['crlf', 'lf']);
|
|
581
|
+
await harness.close();
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
it('tcp 06: CRLF terminator split across chunks (CR in one, LF in next)', async () => {
|
|
585
|
+
harness = await factory.create();
|
|
586
|
+
const c = await harness.spawnClient({ nick: 'alice' });
|
|
587
|
+
await c.waitForNumeric('001');
|
|
588
|
+
c.received.length = 0;
|
|
589
|
+
await c.feed('PING :cr\r');
|
|
590
|
+
expect(c.received.length).toBe(0);
|
|
591
|
+
await c.feed('\n');
|
|
592
|
+
await c.waitForLine((l) => l === 'PONG :cr');
|
|
593
|
+
await harness.close();
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
it('tcp 07: an empty chunk is a no-op (no dispatch, no crash)', async () => {
|
|
597
|
+
harness = await factory.create();
|
|
598
|
+
const c = await harness.spawnClient({ nick: 'alice' });
|
|
599
|
+
await c.waitForNumeric('001');
|
|
600
|
+
c.received.length = 0;
|
|
601
|
+
await c.feed('');
|
|
602
|
+
expect(c.received.length).toBe(0);
|
|
603
|
+
// The connection is still usable after the empty chunk.
|
|
604
|
+
await c.feed('PING :after-empty\r\n');
|
|
605
|
+
await c.waitForLine((l) => l === 'PONG :after-empty');
|
|
606
|
+
await harness.close();
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
it('tcp 08: a complete line followed by a partial line buffers the tail', async () => {
|
|
610
|
+
harness = await factory.create();
|
|
611
|
+
const c = await harness.spawnClient({ nick: 'alice' });
|
|
612
|
+
await c.waitForNumeric('001');
|
|
613
|
+
c.received.length = 0;
|
|
614
|
+
await c.feed('PING :first\r\nPI');
|
|
615
|
+
await c.waitForLine((l) => l === 'PONG :first');
|
|
616
|
+
// The trailing partial 'PI' must not have dispatched yet.
|
|
617
|
+
expect(c.received.some((l) => l === 'PONG :')).toBe(false);
|
|
618
|
+
await c.feed('NG :second\r\n');
|
|
619
|
+
await c.waitForLine((l) => l === 'PONG :second');
|
|
620
|
+
await harness.close();
|
|
621
|
+
});
|
|
622
|
+
});
|
|
623
|
+
}
|
|
494
624
|
}
|
|
495
625
|
}
|
|
@@ -12,7 +12,8 @@ import { InMemoryHarness, inMemoryHarnessFactory } from '../src/index.js';
|
|
|
12
12
|
|
|
13
13
|
describe('inMemoryHarnessFactory', () => {
|
|
14
14
|
it('exposes a stable name for the describe.each matrix', () => {
|
|
15
|
-
expect(inMemoryHarnessFactory.name).toBe('in-memory');
|
|
15
|
+
expect(inMemoryHarnessFactory.name).toBe('in-memory+ws');
|
|
16
|
+
expect(inMemoryHarnessFactory.transport).toBe('ws');
|
|
16
17
|
});
|
|
17
18
|
|
|
18
19
|
it('create() returns a fresh, independent InMemoryHarness each call', async () => {
|
|
@@ -1,19 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Parametrized IRC scenarios — 032.
|
|
2
|
+
* Parametrized IRC scenarios — 032 + 057.
|
|
3
3
|
*
|
|
4
4
|
* The scenario bodies live in `src/scenarios.ts` (now re-exported as
|
|
5
5
|
* `runIrcScenarios`) so adapter packages can register their own
|
|
6
6
|
* {@link IrcHarnessFactory} against the exact same suite under their
|
|
7
7
|
* native vitest configuration (CF runs in `workerd`, AWS runs in node
|
|
8
|
-
* with dynamodb-local, etc.). This file is the in-memory entry point
|
|
9
|
-
* it registers only {@link inMemoryHarnessFactory}.
|
|
8
|
+
* with dynamodb-local, etc.). This file is the in-memory entry point.
|
|
10
9
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* The transport dimension extends the matrix: every scenario runs
|
|
11
|
+
* against (runtime × transport). The in-memory runtime
|
|
12
|
+
* registers both transports — `inMemoryWsHarnessFactory` (WebSocket
|
|
13
|
+
* text-frame framing) and `inMemoryTcpHarnessFactory` (raw TCP+TLS
|
|
14
|
+
* byte-stream framing) — proving the same scenario suite is green across
|
|
15
|
+
* both transports on the reference runtime. CF and AWS append their own
|
|
16
|
+
* factories (per transport) to their package suites.
|
|
17
|
+
*
|
|
18
|
+
* Adding a runtime OR a transport to the in-memory suite = appending one
|
|
19
|
+
* entry to the `FACTORIES` array (acceptance criterion: "adding a new
|
|
20
|
+
* transport requires only registering a factory").
|
|
14
21
|
*/
|
|
15
22
|
|
|
16
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
inMemoryTcpHarnessFactory,
|
|
25
|
+
inMemoryWsHarnessFactory,
|
|
26
|
+
runIrcScenarios,
|
|
27
|
+
} from '../src/index.js';
|
|
17
28
|
|
|
18
|
-
/**
|
|
19
|
-
|
|
29
|
+
/**
|
|
30
|
+
* The (runtime × transport) matrix. Adding a runtime or transport =
|
|
31
|
+
* appending one entry here.
|
|
32
|
+
*/
|
|
33
|
+
runIrcScenarios([inMemoryWsHarnessFactory, inMemoryTcpHarnessFactory]);
|
package/pnpm-workspace.yaml
CHANGED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Seed SASL PLAIN accounts into the DynamoDB `Accounts` table.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node --import tsx tools/seed-aws-accounts.ts \
|
|
7
|
+
* --table StagingAccounts \
|
|
8
|
+
* --endpoint http://localhost:8000 \
|
|
9
|
+
* --accounts alice:s3cret bob:password2
|
|
10
|
+
*
|
|
11
|
+
* Or read from a file (newline-delimited `username:password`):
|
|
12
|
+
* node --import tsx tools/seed-aws-accounts.ts \
|
|
13
|
+
* --table StagingAccounts \
|
|
14
|
+
* --file accounts.txt
|
|
15
|
+
*
|
|
16
|
+
* The script hashes each password with scrypt (never writes plaintext).
|
|
17
|
+
* Re-running with the same username overwrites the prior row.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { readFileSync } from 'node:fs';
|
|
21
|
+
import { parseArgs } from 'node:util';
|
|
22
|
+
import { createDynamoDocumentClient, putAccountCredential } from '@serverless-ircd/aws-adapter';
|
|
23
|
+
|
|
24
|
+
const { values } = parseArgs({
|
|
25
|
+
options: {
|
|
26
|
+
table: { type: 'string' },
|
|
27
|
+
endpoint: { type: 'string', default: undefined },
|
|
28
|
+
region: { type: 'string', default: undefined },
|
|
29
|
+
accounts: { type: 'string', multiple: true, default: [] },
|
|
30
|
+
file: { type: 'string', default: undefined },
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (values.table === undefined) {
|
|
35
|
+
console.error(
|
|
36
|
+
'Usage: seed-aws-accounts --table <TableName> [--endpoint <url>] [--accounts user:pass ...] [--file <path>]',
|
|
37
|
+
);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const clientOptions: Record<string, string> = {};
|
|
42
|
+
if (values.endpoint !== undefined) clientOptions.endpoint = values.endpoint;
|
|
43
|
+
if (values.region !== undefined) clientOptions.region = values.region;
|
|
44
|
+
|
|
45
|
+
const docClient = createDynamoDocumentClient(clientOptions);
|
|
46
|
+
|
|
47
|
+
const pairs: Array<{ username: string; password: string }> = [];
|
|
48
|
+
|
|
49
|
+
for (const pair of values.accounts) {
|
|
50
|
+
const sep = pair.indexOf(':');
|
|
51
|
+
if (sep <= 0) {
|
|
52
|
+
console.error(`Skipping malformed entry (no colon): ${pair}`);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
pairs.push({ username: pair.slice(0, sep), password: pair.slice(sep + 1) });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (values.file !== undefined) {
|
|
59
|
+
const content = readFileSync(values.file, 'utf-8');
|
|
60
|
+
for (const line of content.split('\n')) {
|
|
61
|
+
const trimmed = line.trim();
|
|
62
|
+
if (trimmed.length === 0) continue;
|
|
63
|
+
const sep = trimmed.indexOf(':');
|
|
64
|
+
if (sep <= 0) continue;
|
|
65
|
+
pairs.push({ username: trimmed.slice(0, sep), password: trimmed.slice(sep + 1) });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (pairs.length === 0) {
|
|
70
|
+
console.error('No accounts to seed. Use --accounts or --file.');
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (const { username, password } of pairs) {
|
|
75
|
+
const entry = await putAccountCredential(docClient, values.table, username, password);
|
|
76
|
+
console.log(`Seeded ${username} (algorithm=${entry.algorithm})`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
console.log(`Done: ${pairs.length} account(s) written to ${values.table}.`);
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Seed SASL PLAIN accounts into a Cloudflare D1 `accounts` table.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node --import tsx tools/seed-cf-accounts.ts \
|
|
7
|
+
* --database serverless-ircd-accounts-staging \
|
|
8
|
+
* --env staging \
|
|
9
|
+
* --remote \
|
|
10
|
+
* --accounts alice:s3cret bob:password2
|
|
11
|
+
*
|
|
12
|
+
* Or read from a file (newline-delimited `username:password`):
|
|
13
|
+
* node --import tsx tools/seed-cf-accounts.ts \
|
|
14
|
+
* --database serverless-ircd-accounts-staging \
|
|
15
|
+
* --env staging \
|
|
16
|
+
* --remote \
|
|
17
|
+
* --file accounts.txt
|
|
18
|
+
*
|
|
19
|
+
* The script hashes each password with scrypt (never writes plaintext) and
|
|
20
|
+
* executes the resulting `INSERT OR REPLACE` via `wrangler d1 execute`.
|
|
21
|
+
* Re-running with the same username overwrites the prior row. The `accounts`
|
|
22
|
+
* table is created (IF NOT EXISTS) on the first run.
|
|
23
|
+
*
|
|
24
|
+
* Flags:
|
|
25
|
+
* --database D1 database name (as declared in wrangler.toml). Required.
|
|
26
|
+
* --env Wrangler environment (e.g. `staging`). Optional.
|
|
27
|
+
* --remote Target the remote D1 (default: local preview).
|
|
28
|
+
* --local Target local D1 (explicit; default).
|
|
29
|
+
* --accounts One or more `username:password` pairs.
|
|
30
|
+
* --file Path to a newline-delimited `username:password` file.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { execFileSync } from 'node:child_process';
|
|
34
|
+
import { readFileSync } from 'node:fs';
|
|
35
|
+
import { parseArgs } from 'node:util';
|
|
36
|
+
import { CREATE_ACCOUNTS_TABLE_SQL, buildAccountPutStatement } from '@serverless-ircd/cf-adapter';
|
|
37
|
+
|
|
38
|
+
const { values } = parseArgs({
|
|
39
|
+
options: {
|
|
40
|
+
database: { type: 'string' },
|
|
41
|
+
env: { type: 'string', default: undefined },
|
|
42
|
+
remote: { type: 'boolean', default: false },
|
|
43
|
+
local: { type: 'boolean', default: false },
|
|
44
|
+
accounts: { type: 'string', multiple: true, default: [] },
|
|
45
|
+
file: { type: 'string', default: undefined },
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (values.database === undefined) {
|
|
50
|
+
console.error(
|
|
51
|
+
'Usage: seed-cf-accounts --database <d1-name> [--env staging] [--remote] [--accounts user:pass ...] [--file <path>]',
|
|
52
|
+
);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const pairs: Array<{ username: string; password: string }> = [];
|
|
57
|
+
|
|
58
|
+
for (const pair of values.accounts) {
|
|
59
|
+
const sep = pair.indexOf(':');
|
|
60
|
+
if (sep <= 0) {
|
|
61
|
+
console.error(`Skipping malformed entry (no colon): ${pair}`);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
pairs.push({ username: pair.slice(0, sep), password: pair.slice(sep + 1) });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (values.file !== undefined) {
|
|
68
|
+
const content = readFileSync(values.file, 'utf-8');
|
|
69
|
+
for (const line of content.split('\n')) {
|
|
70
|
+
const trimmed = line.trim();
|
|
71
|
+
if (trimmed.length === 0) continue;
|
|
72
|
+
const sep = trimmed.indexOf(':');
|
|
73
|
+
if (sep <= 0) continue;
|
|
74
|
+
pairs.push({ username: trimmed.slice(0, sep), password: trimmed.slice(sep + 1) });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (pairs.length === 0) {
|
|
79
|
+
console.error('No accounts to seed. Use --accounts or --file.');
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Runs `wrangler d1 execute` with the supplied SQL command. */
|
|
84
|
+
function runD1Execute(sql: string): void {
|
|
85
|
+
const args = ['d1', 'execute', values.database as string, '--command', sql];
|
|
86
|
+
if (values.env !== undefined) {
|
|
87
|
+
args.push('--env', values.env);
|
|
88
|
+
}
|
|
89
|
+
if (values.remote) {
|
|
90
|
+
args.push('--remote');
|
|
91
|
+
}
|
|
92
|
+
execFileSync('npx', ['wrangler', ...args], { stdio: 'inherit' });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Ensure the table exists (idempotent).
|
|
96
|
+
runD1Execute(CREATE_ACCOUNTS_TABLE_SQL);
|
|
97
|
+
|
|
98
|
+
for (const { username, password } of pairs) {
|
|
99
|
+
const { entry, sql } = buildAccountPutStatement(username, password);
|
|
100
|
+
runD1Execute(sql);
|
|
101
|
+
console.log(`Seeded ${username} (algorithm=${entry.algorithm})`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
console.log(`Done: ${pairs.length} account(s) written to D1 database ${values.database}.`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@serverless-ircd/tcp-ws-forwarder",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Local TCP to ws/wss forwarder so standard IRC clients can reach the WebSocket-only ServerlessIRCd transport",
|
|
6
6
|
"license": "BSD-3-Clause",
|
package/AGENTS.md
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
# AGENTS.md
|
|
2
|
-
|
|
3
|
-
## Code Style
|
|
4
|
-
|
|
5
|
-
- **Do not reference ticket IDs in source files.** Never put `TICKET-###` or similar ticket-tracker tokens in `.ts` or `.js` files (code or comments). Describe the intent or cross-reference by feature/command name instead. Ticket tracking lives in `progress.md` / `tickets.md`, not in source.
|
package/MOTD.txt
DELETED