serverless-ircd 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/.github/workflows/ci.yml +4 -0
  2. package/.github/workflows/deploy-aws.yml +156 -32
  3. package/.github/workflows/deploy-cf-tcp.yml +11 -9
  4. package/.github/workflows/deploy-cf.yml +14 -14
  5. package/CHANGELOG.md +550 -0
  6. package/README.md +275 -222
  7. package/apps/aws-stack/README.md +3 -5
  8. package/apps/aws-stack/bin/aws.ts +82 -9
  9. package/apps/aws-stack/cdk.json +0 -3
  10. package/apps/aws-stack/package.json +3 -4
  11. package/apps/aws-stack/src/aws-stack.ts +177 -52
  12. package/apps/aws-stack/src/static-site.ts +323 -0
  13. package/apps/aws-stack/tests/smoke-helpers.test.ts +1 -1
  14. package/apps/aws-stack/tests/stack.test.ts +267 -92
  15. package/apps/aws-stack/tests/static-site.test.ts +491 -0
  16. package/apps/aws-stack/tests/synth-no-bundle.test.ts +0 -1
  17. package/apps/cf-tcp-container/package.json +2 -3
  18. package/apps/cf-tcp-container/src/container-server.ts +33 -10
  19. package/apps/cf-tcp-container/tests/config-loader.test.ts +43 -0
  20. package/apps/cf-tcp-container/tests/container-server.test.ts +249 -1
  21. package/apps/cf-tcp-container/tests/persistence.test.ts +9 -0
  22. package/apps/cf-tcp-container/tests/tls-e2e.test.ts +24 -5
  23. package/apps/cf-tcp-container/wrangler.toml +1 -10
  24. package/apps/cf-worker/package.json +3 -4
  25. package/apps/cf-worker/wrangler.toml +12 -71
  26. package/apps/local-cli/package.json +1 -1
  27. package/apps/local-cli/src/server.ts +115 -48
  28. package/apps/local-cli/tests/config-resolution.test.ts +65 -0
  29. package/apps/local-cli/tests/motd-file-non-error.test.ts +29 -0
  30. package/apps/local-cli/tests/rehash.test.ts +147 -0
  31. package/apps/local-cli/tests/server-helpers.test.ts +63 -0
  32. package/apps/local-cli/tests/tcp.test.ts +89 -0
  33. package/apps/local-cli/tests/ws-subprotocol.test.ts +92 -0
  34. package/apps/web/landing/favicon.ico +0 -0
  35. package/apps/web/landing/index.html +227 -3
  36. package/apps/web/package.json +3 -2
  37. package/apps/web/scripts/build.mjs +91 -6
  38. package/apps/web/src/build-env.ts +125 -4
  39. package/apps/web/src/config-schema.ts +20 -6
  40. package/apps/web/src/render-docs.ts +292 -0
  41. package/apps/web/static/{config.staging.json → config.prod-aws.json} +3 -2
  42. package/apps/web/tests/build-env.test.ts +210 -9
  43. package/apps/web/tests/build-smoke.test.ts +33 -4
  44. package/apps/web/tests/config-schema.test.ts +149 -25
  45. package/apps/web/tests/landing-content.test.ts +103 -0
  46. package/apps/web/tests/render-docs.test.ts +198 -0
  47. package/docs/AWS-Adapter-Architecture.md +3 -2
  48. package/docs/AWS-Deployment.md +670 -96
  49. package/docs/AWS-TCP-Deployment.md +20 -45
  50. package/docs/Cloudflare-Deployment-Guide.md +87 -113
  51. package/docs/Cloudflare-TCP-Deployment.md +25 -49
  52. package/docs/Release-Process.md +27 -23
  53. package/docs/Services.md +102 -23
  54. package/docs/WebClientGuide.md +35 -26
  55. package/package.json +7 -10
  56. package/packages/aws-adapter/package.json +1 -1
  57. package/packages/aws-adapter/src/aws-runtime.ts +15 -1
  58. package/packages/aws-adapter/src/cdk-table-defs.ts +6 -11
  59. package/packages/aws-adapter/src/config-loader.ts +19 -2
  60. package/packages/aws-adapter/src/dynamo-services-store.ts +7 -0
  61. package/packages/aws-adapter/src/handlers/connect.ts +26 -0
  62. package/packages/aws-adapter/src/handlers/default.ts +190 -123
  63. package/packages/aws-adapter/src/handlers/index.ts +67 -23
  64. package/packages/aws-adapter/src/handlers/nlb-stream.ts +13 -8
  65. package/packages/aws-adapter/src/index.ts +5 -7
  66. package/packages/aws-adapter/src/origin-allowlist.ts +94 -0
  67. package/packages/aws-adapter/src/serialize.ts +15 -0
  68. package/packages/aws-adapter/src/tables.ts +2 -12
  69. package/packages/aws-adapter/tests/aws-harness.ts +0 -1
  70. package/packages/aws-adapter/tests/aws-runtime.test.ts +23 -1
  71. package/packages/aws-adapter/tests/config-loader.test.ts +66 -0
  72. package/packages/aws-adapter/tests/connect.test.ts +124 -1
  73. package/packages/aws-adapter/tests/connection-counter.test.ts +17 -0
  74. package/packages/aws-adapter/tests/default-occ.test.ts +219 -0
  75. package/packages/aws-adapter/tests/dynamo-services-store-unit.test.ts +11 -0
  76. package/packages/aws-adapter/tests/global-setup.ts +28 -1
  77. package/packages/aws-adapter/tests/gone-exception.test.ts +21 -2
  78. package/packages/aws-adapter/tests/handlers.test.ts +117 -11
  79. package/packages/aws-adapter/tests/migrate-accounts-to-services.test.ts +164 -0
  80. package/packages/aws-adapter/tests/nlb-stream.test.ts +29 -1
  81. package/packages/aws-adapter/tests/origin-allowlist.test.ts +110 -0
  82. package/packages/aws-adapter/tests/ping-checker.test.ts +0 -1
  83. package/packages/aws-adapter/tests/stats.test.ts +0 -3
  84. package/packages/aws-adapter/tests/sweeper.test.ts +20 -1
  85. package/packages/aws-adapter/tests/tables.test.ts +1 -8
  86. package/packages/aws-adapter/tests/transactions.test.ts +0 -1
  87. package/packages/cf-adapter/package.json +1 -5
  88. package/packages/cf-adapter/src/cf-runtime.ts +59 -8
  89. package/packages/cf-adapter/src/channel-do.ts +13 -3
  90. package/packages/cf-adapter/src/connection-do.ts +284 -115
  91. package/packages/cf-adapter/src/d1-services-store.ts +63 -26
  92. package/packages/cf-adapter/src/env.ts +11 -10
  93. package/packages/cf-adapter/src/index.ts +0 -6
  94. package/packages/cf-adapter/tests/cf-runtime.test.ts +101 -1
  95. package/packages/cf-adapter/tests/channel-do.test.ts +118 -1
  96. package/packages/cf-adapter/tests/connection-do-coverage.test.ts +460 -0
  97. package/packages/cf-adapter/tests/connection-do-pure.test.ts +222 -51
  98. package/packages/cf-adapter/tests/connection-do-sasl-d1.test.ts +62 -38
  99. package/packages/cf-adapter/tests/d1-services-store.test.ts +53 -2
  100. package/packages/cf-adapter/tests/serialize.test.ts +25 -0
  101. package/packages/in-memory-runtime/package.json +1 -1
  102. package/packages/irc-core/package.json +1 -1
  103. package/packages/irc-core/src/account-migration.ts +140 -0
  104. package/packages/irc-core/src/commands/account-auth.ts +60 -35
  105. package/packages/irc-core/src/commands/chanserv.ts +288 -4
  106. package/packages/irc-core/src/commands/hostserv.ts +38 -3
  107. package/packages/irc-core/src/commands/index.ts +1 -0
  108. package/packages/irc-core/src/commands/join.ts +41 -35
  109. package/packages/irc-core/src/commands/memoserv.ts +1 -1
  110. package/packages/irc-core/src/commands/nickserv.ts +138 -15
  111. package/packages/irc-core/src/commands/registration.ts +28 -17
  112. package/packages/irc-core/src/commands/sasl.ts +22 -31
  113. package/packages/irc-core/src/commands/service-aliases.ts +52 -0
  114. package/packages/irc-core/src/commands/topic.ts +23 -10
  115. package/packages/irc-core/src/config.ts +35 -9
  116. package/packages/irc-core/src/credential-hashing.ts +11 -54
  117. package/packages/irc-core/src/index.ts +1 -0
  118. package/packages/irc-core/src/ports.ts +159 -179
  119. package/packages/irc-core/src/state/channel.ts +17 -0
  120. package/packages/irc-core/src/types.ts +38 -10
  121. package/packages/irc-core/tests/account-migration.test.ts +133 -0
  122. package/packages/irc-core/tests/commands/chanserv.test.ts +668 -1
  123. package/packages/irc-core/tests/commands/hostserv.test.ts +71 -0
  124. package/packages/irc-core/tests/commands/join.test.ts +179 -0
  125. package/packages/irc-core/tests/commands/markread.test.ts +54 -0
  126. package/packages/irc-core/tests/commands/memoserv.test.ts +19 -0
  127. package/packages/irc-core/tests/commands/nickserv.test.ts +422 -3
  128. package/packages/irc-core/tests/commands/oper.test.ts +15 -0
  129. package/packages/irc-core/tests/commands/registration.test.ts +336 -108
  130. package/packages/irc-core/tests/commands/sasl.test.ts +194 -169
  131. package/packages/irc-core/tests/commands/service-aliases.test.ts +52 -0
  132. package/packages/irc-core/tests/commands/unified-account.test.ts +102 -84
  133. package/packages/irc-core/tests/credential-hashing.test.ts +0 -78
  134. package/packages/irc-core/tests/message-store.test.ts +5 -0
  135. package/packages/irc-core/tests/persistent-services-store.test.ts +71 -12
  136. package/packages/irc-core/tests/ports.test.ts +71 -0
  137. package/packages/irc-core/tests/services-store.test.ts +204 -0
  138. package/packages/irc-core/vitest.config.ts +6 -1
  139. package/packages/irc-server/package.json +1 -1
  140. package/packages/irc-server/src/actor.ts +80 -44
  141. package/packages/irc-server/tests/actor.test.ts +384 -50
  142. package/packages/irc-test-support/package.json +1 -1
  143. package/packages/irc-test-support/src/in-memory-harness.ts +8 -5
  144. package/packages/irc-test-support/src/scenarios.ts +21 -6
  145. package/packages/irc-test-support/tests/in-memory-harness.test.ts +19 -0
  146. package/packages/irc-test-support/vitest.config.ts +6 -1
  147. package/pnpm-workspace.yaml +1 -0
  148. package/scripts/__tests__/deploy-web-aws.test.ts +491 -0
  149. package/scripts/deploy-web-aws.mjs +290 -0
  150. package/scripts/package.json +23 -0
  151. package/scripts/tsconfig.test.json +12 -0
  152. package/scripts/vitest.config.ts +19 -0
  153. package/tools/ci-hardening/package.json +1 -1
  154. package/tools/ci-hardening/src/index.ts +2 -0
  155. package/tools/ci-hardening/src/validate.ts +57 -0
  156. package/tools/ci-hardening/tests/deploy-aws-oidc.test.ts +96 -0
  157. package/tools/ci-hardening/tests/validate.test.ts +42 -0
  158. package/tools/load-test/package.json +1 -1
  159. package/tools/load-test/src/client.ts +13 -13
  160. package/tools/load-test/tests/client.test.ts +258 -2
  161. package/tools/load-test/tests/config.test.ts +39 -0
  162. package/tools/load-test/tests/harness.test.ts +21 -0
  163. package/tools/load-test/tests/metrics.test.ts +7 -0
  164. package/tools/migrate-accounts-to-services.ts +270 -0
  165. package/tools/package.json +2 -1
  166. package/tools/seed-aws-accounts.ts +35 -10
  167. package/tools/seed-cf-accounts.ts +42 -9
  168. package/tools/tcp-ws-forwarder/package.json +1 -1
  169. package/tools/tcp-ws-forwarder/tests/close-error.test.ts +40 -0
  170. package/tools/tcp-ws-forwarder/tests/defensive-branches.test.ts +78 -0
  171. package/tools/tcp-ws-forwarder/tests/forwarder.test.ts +51 -0
  172. package/tools/tcp-ws-forwarder/tests/logger.test.ts +31 -1
  173. package/packages/aws-adapter/src/account-store.ts +0 -121
  174. package/packages/aws-adapter/src/dynamo-account-store.ts +0 -95
  175. package/packages/aws-adapter/tests/account-store-dynamo.test.ts +0 -223
  176. package/packages/aws-adapter/tests/account-store.test.ts +0 -276
  177. package/packages/cf-adapter/src/d1-account-store.ts +0 -198
  178. package/packages/cf-adapter/tests/d1-account-store.test.ts +0 -274
  179. package/packages/irc-core/tests/account-store.test.ts +0 -131
@@ -0,0 +1,198 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ mkdtempSync,
5
+ readFileSync,
6
+ readdirSync,
7
+ rmSync,
8
+ statSync,
9
+ } from 'node:fs';
10
+ import os from 'node:os';
11
+ import path from 'node:path';
12
+ import { fileURLToPath } from 'node:url';
13
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
14
+ import { buildDocs, renderMarkdown, rewriteDocLinks } from '../src/render-docs';
15
+
16
+ const pkgRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
17
+ const docsSrc = path.resolve(pkgRoot, '..', '..', 'docs');
18
+
19
+ // ============================================================================
20
+ // renderMarkdown — GFM rendering of markdown source
21
+ // ============================================================================
22
+ describe('renderMarkdown', () => {
23
+ it('renders an ATX H1 heading as <h1>', () => {
24
+ const html = renderMarkdown('# Title');
25
+ expect(html).toMatch(/<h1[^>]*>\s*Title\s*<\/h1>/);
26
+ });
27
+
28
+ it('renders a fenced code block as <pre><code>', () => {
29
+ const md = ['```ts', 'const x = 1;', '```'].join('\n');
30
+ const html = renderMarkdown(md);
31
+ expect(html).toMatch(/<pre[^>]*><code[^>]*>/);
32
+ });
33
+
34
+ it('renders GFM tables', () => {
35
+ const md = ['| a | b |', '| --- | --- |', '| 1 | 2 |'].join('\n');
36
+ const html = renderMarkdown(md);
37
+ expect(html).toContain('<table>');
38
+ expect(html).toContain('<th');
39
+ });
40
+
41
+ it('renders inline code', () => {
42
+ const html = renderMarkdown('Use `npm` to install.');
43
+ expect(html).toContain('<code>npm</code>');
44
+ });
45
+ });
46
+
47
+ // ============================================================================
48
+ // rewriteDocLinks — intra-docs link rewriting
49
+ // ============================================================================
50
+ describe('rewriteDocLinks', () => {
51
+ const known = new Set([
52
+ 'Services',
53
+ 'Home',
54
+ 'ADR-Index',
55
+ 'ADR-001-pure-reducers-and-effect-system',
56
+ ]);
57
+
58
+ it('rewrites a relative OtherDoc.md href to OtherDoc.html', () => {
59
+ const input = '<a href="./Services.md">Services</a>';
60
+ const out = rewriteDocLinks(input, known);
61
+ expect(out).toContain('href="Services.html"');
62
+ expect(out).not.toContain('Services.md');
63
+ });
64
+
65
+ it('rewrites a bare wikilink-style href (no extension) when the slug is a known doc', () => {
66
+ const input = '<a href="Services">Services</a>';
67
+ const out = rewriteDocLinks(input, known);
68
+ expect(out).toContain('href="Services.html"');
69
+ });
70
+
71
+ it('leaves unknown bare hrefs alone (could be an external path)', () => {
72
+ const input = '<a href="SomeUnknownPage">x</a>';
73
+ const out = rewriteDocLinks(input, new Set(['Services']));
74
+ expect(out).toContain('href="SomeUnknownPage"');
75
+ });
76
+
77
+ it('preserves an anchor fragment when rewriting', () => {
78
+ const input = '<a href="./Services.md#section">link</a>';
79
+ const out = rewriteDocLinks(input, known);
80
+ expect(out).toContain('href="Services.html#section"');
81
+ });
82
+
83
+ it('does not touch external (http/https) links', () => {
84
+ const input = '<a href="https://example.com/foo.md">external</a>';
85
+ const out = rewriteDocLinks(input, known);
86
+ expect(out).toContain('href="https://example.com/foo.md"');
87
+ });
88
+
89
+ it('does not touch pure-anchor (#section) links', () => {
90
+ const input = '<a href="#section">anchor</a>';
91
+ const out = rewriteDocLinks(input, known);
92
+ expect(out).toContain('href="#section"');
93
+ });
94
+
95
+ it('handles a trailing anchor on a bare wikilink-style href', () => {
96
+ const input = '<a href="Home#pages">Home</a>';
97
+ const out = rewriteDocLinks(input, known);
98
+ expect(out).toContain('href="Home.html#pages"');
99
+ });
100
+ });
101
+
102
+ // ============================================================================
103
+ // buildDocs — end-to-end render pipeline (against the real docs/ submodule)
104
+ // ============================================================================
105
+ describe('buildDocs against docs/ submodule', () => {
106
+ let outDir: string;
107
+
108
+ beforeEach(() => {
109
+ outDir = mkdtempSync(path.join(os.tmpdir(), 'render-docs-'));
110
+ });
111
+
112
+ afterEach(() => {
113
+ rmSync(outDir, { recursive: true, force: true });
114
+ });
115
+
116
+ it('throws a clear error when the docs source directory is missing', async () => {
117
+ await expect(buildDocs({ srcDir: path.join(outDir, 'nope'), outDir })).rejects.toThrow(
118
+ /docs.*missing|missing.*docs|submodule/iu,
119
+ );
120
+ });
121
+
122
+ it('throws a clear error when the docs source directory is present but empty', async () => {
123
+ // Mirrors the build-script guard: an empty `docs/` checkout (submodule
124
+ // init'd but not populated) must fail loudly with the recovery command,
125
+ // not silently produce a docs section with zero pages.
126
+ const empty = mkdirSync(path.join(outDir, 'empty-docs'), { recursive: true });
127
+ if (empty === undefined) {
128
+ throw new Error('test setup failed: could not create empty-docs dir');
129
+ }
130
+ await expect(buildDocs({ srcDir: empty, outDir })).rejects.toThrow(/empty|submodule/iu);
131
+ });
132
+
133
+ it('emits an index.html under the output directory', async () => {
134
+ await buildDocs({ srcDir: docsSrc, outDir });
135
+ const idx = path.join(outDir, 'index.html');
136
+ expect(existsSync(idx)).toBe(true);
137
+ expect(statSync(idx).size).toBeGreaterThan(0);
138
+ });
139
+
140
+ it('emits one HTML file per markdown source (non-empty output directory)', async () => {
141
+ const result = await buildDocs({ srcDir: docsSrc, outDir });
142
+ expect(result.emitted.length).toBeGreaterThanOrEqual(20);
143
+ for (const f of result.emitted) {
144
+ expect(f.endsWith('.html')).toBe(true);
145
+ expect(existsSync(path.join(outDir, f))).toBe(true);
146
+ }
147
+ expect(readdirSync(outDir).filter((f) => f.endsWith('.html')).length).toBe(
148
+ result.emitted.length,
149
+ );
150
+ });
151
+
152
+ it('renders Services.html with an <h1> and at least one <code> block', async () => {
153
+ await buildDocs({ srcDir: docsSrc, outDir });
154
+ const html = readFileSync(path.join(outDir, 'Services.html'), 'utf8');
155
+ expect(html).toMatch(/<h1[^>]*>/);
156
+ expect(html).toMatch(/<pre[^>]*><code/);
157
+ // The raw markdown marker should not survive rendering.
158
+ expect(html).not.toMatch(/^# IRC Services Reference/m);
159
+ });
160
+
161
+ it('emits Home as the index (index.html)', async () => {
162
+ await buildDocs({ srcDir: docsSrc, outDir });
163
+ const html = readFileSync(path.join(outDir, 'index.html'), 'utf8');
164
+ // Home.md's "Welcome to the Wiki." lead-in should be present.
165
+ expect(html).toMatch(/Welcome to the Wiki/i);
166
+ });
167
+
168
+ it('rewrites intra-docs links to .html in the rendered output (no .md hrefs remain)', async () => {
169
+ await buildDocs({ srcDir: docsSrc, outDir });
170
+ const files = readdirSync(outDir).filter((f) => f.endsWith('.html'));
171
+ expect(files.length).toBeGreaterThan(0);
172
+ for (const f of files) {
173
+ const html = readFileSync(path.join(outDir, f), 'utf8');
174
+ // No intra-docs .md href may remain. (External https://…/foo.md is fine.)
175
+ const intraMd = html.match(/href="(?!https?:|mailto:|#)[^"]*\.md[^"]*"/g) ?? [];
176
+ expect(intraMd).toEqual([]);
177
+ }
178
+ });
179
+
180
+ it('emits a shared stylesheet (referenced by every page)', async () => {
181
+ await buildDocs({ srcDir: docsSrc, outDir });
182
+ const files = readdirSync(outDir).filter((f) => f.endsWith('.html'));
183
+ // Every page links the same stylesheet.
184
+ for (const f of files) {
185
+ const html = readFileSync(path.join(outDir, f), 'utf8');
186
+ expect(html).toMatch(/<link[^>]+rel=["']stylesheet["']/);
187
+ }
188
+ // And the stylesheet actually exists in the output.
189
+ const css = readdirSync(outDir).filter((f) => f.endsWith('.css'));
190
+ expect(css.length).toBeGreaterThanOrEqual(1);
191
+ });
192
+
193
+ it('emits an ADR-Index page that links each ADR via a rewritten .html href', async () => {
194
+ await buildDocs({ srcDir: docsSrc, outDir });
195
+ const html = readFileSync(path.join(outDir, 'ADR-Index.html'), 'utf8');
196
+ expect(html).toMatch(/ADR-001-pure-reducers-and-effect-system\.html/);
197
+ });
198
+ });
@@ -373,8 +373,9 @@ scheme (post-v1).
373
373
 
374
374
  │ all-or-nothing → races structurally impossible
375
375
 
376
-
377
- dispatch Broadcast(JOIN), 353 NAMES, 366 END-NAMES
376
+
377
+ dispatch Broadcast(JOIN), 353 NAMES, 366 END-NAMES,
378
+ and when a topic is set: 332 RPL_TOPIC, 333 RPL_TOPICWHOTIME
378
379
  ```
379
380
 
380
381
  A `CancellationException` from `TransactWriteItems` maps to the