@tonalisk-labs/kit 1.0.1 → 1.0.6

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 (79) hide show
  1. package/dist/testing/src/config-coverage.d.ts.map +1 -1
  2. package/dist/testing/src/config-coverage.js +3 -2
  3. package/dist/testing/src/config-coverage.js.map +1 -1
  4. package/dist/testing/src/fixtures.d.ts +9 -3
  5. package/dist/testing/src/fixtures.d.ts.map +1 -1
  6. package/dist/testing/src/fixtures.js +98 -42
  7. package/dist/testing/src/fixtures.js.map +1 -1
  8. package/dist/testing/src/floors/node.js +11 -3
  9. package/dist/testing/src/floors/node.js.map +1 -1
  10. package/dist/testing/src/stack/browsers.d.ts +13 -3
  11. package/dist/testing/src/stack/browsers.d.ts.map +1 -1
  12. package/dist/testing/src/stack/browsers.js +117 -14
  13. package/dist/testing/src/stack/browsers.js.map +1 -1
  14. package/dist/testing/src/stack/containers.d.ts +6 -2
  15. package/dist/testing/src/stack/containers.d.ts.map +1 -1
  16. package/dist/testing/src/stack/containers.js +8 -1
  17. package/dist/testing/src/stack/containers.js.map +1 -1
  18. package/dist/testing/src/stack/control.d.ts +18 -3
  19. package/dist/testing/src/stack/control.d.ts.map +1 -1
  20. package/dist/testing/src/stack/control.js +50 -10
  21. package/dist/testing/src/stack/control.js.map +1 -1
  22. package/dist/testing/src/stack/gate-member.d.ts +10 -0
  23. package/dist/testing/src/stack/gate-member.d.ts.map +1 -1
  24. package/dist/testing/src/stack/gate-member.js +68 -7
  25. package/dist/testing/src/stack/gate-member.js.map +1 -1
  26. package/dist/testing/src/stack/gate-server.d.ts +37 -4
  27. package/dist/testing/src/stack/gate-server.d.ts.map +1 -1
  28. package/dist/testing/src/stack/gate-server.js +124 -12
  29. package/dist/testing/src/stack/gate-server.js.map +1 -1
  30. package/dist/testing/src/stack/hold.d.ts +24 -3
  31. package/dist/testing/src/stack/hold.d.ts.map +1 -1
  32. package/dist/testing/src/stack/hold.js +136 -11
  33. package/dist/testing/src/stack/hold.js.map +1 -1
  34. package/dist/testing/src/stack/host.d.ts.map +1 -1
  35. package/dist/testing/src/stack/host.js +26 -6
  36. package/dist/testing/src/stack/host.js.map +1 -1
  37. package/dist/testing/src/stack/lock.d.ts +15 -4
  38. package/dist/testing/src/stack/lock.d.ts.map +1 -1
  39. package/dist/testing/src/stack/lock.js +47 -18
  40. package/dist/testing/src/stack/lock.js.map +1 -1
  41. package/dist/testing/src/stack/preview-child.js +14 -3
  42. package/dist/testing/src/stack/preview-child.js.map +1 -1
  43. package/dist/testing/src/stack/runtime-apple.d.ts.map +1 -1
  44. package/dist/testing/src/stack/runtime-apple.js +16 -0
  45. package/dist/testing/src/stack/runtime-apple.js.map +1 -1
  46. package/dist/testing/src/stack/runtime-docker.d.ts +2 -1
  47. package/dist/testing/src/stack/runtime-docker.d.ts.map +1 -1
  48. package/dist/testing/src/stack/runtime-docker.js +29 -3
  49. package/dist/testing/src/stack/runtime-docker.js.map +1 -1
  50. package/dist/testing/src/stack/runtime.d.ts +10 -0
  51. package/dist/testing/src/stack/runtime.d.ts.map +1 -1
  52. package/dist/testing/src/stack/runtime.js +1 -0
  53. package/dist/testing/src/stack/runtime.js.map +1 -1
  54. package/dist/testing/src/stack/socks.d.ts +25 -0
  55. package/dist/testing/src/stack/socks.d.ts.map +1 -0
  56. package/dist/testing/src/stack/socks.js +195 -0
  57. package/dist/testing/src/stack/socks.js.map +1 -0
  58. package/dist/testing/src/stack/stall.d.ts +36 -0
  59. package/dist/testing/src/stack/stall.d.ts.map +1 -0
  60. package/dist/testing/src/stack/stall.js +93 -0
  61. package/dist/testing/src/stack/stall.js.map +1 -0
  62. package/package.json +4 -4
  63. package/testing/src/config-coverage.ts +3 -2
  64. package/testing/src/fixtures.ts +277 -197
  65. package/testing/src/floors/node.ts +19 -3
  66. package/testing/src/stack/browsers.ts +129 -15
  67. package/testing/src/stack/containers.ts +14 -2
  68. package/testing/src/stack/control.ts +62 -11
  69. package/testing/src/stack/gate-member.ts +90 -8
  70. package/testing/src/stack/gate-server.ts +166 -21
  71. package/testing/src/stack/hold.ts +163 -11
  72. package/testing/src/stack/host.ts +32 -9
  73. package/testing/src/stack/lock.ts +61 -21
  74. package/testing/src/stack/preview-child.ts +21 -4
  75. package/testing/src/stack/runtime-apple.ts +20 -1
  76. package/testing/src/stack/runtime-docker.ts +47 -3
  77. package/testing/src/stack/runtime.ts +11 -0
  78. package/testing/src/stack/socks.ts +233 -0
  79. package/testing/src/stack/stall.ts +145 -0
@@ -0,0 +1,195 @@
1
+ // The host's end of the Linux image's shared browsers: a SOCKS5 server on the host's address on
2
+ // their network. Playwright's launch server, unlike its run server, carries no tunnel to the host's
3
+ // loopback (exposeNetwork), so each browser is launched with this server as its proxy. With a SOCKS
4
+ // proxy, Playwright's Chromium resolves no name itself, so every request a browser makes, its
5
+ // page's and its context's own, comes here: the loopback's go to the host's, and the rest out, as
6
+ // a contained browser's went out before.
7
+ import { once } from 'node:events';
8
+ import { connect, createServer, isIP } from 'node:net';
9
+ import { codeOf } from '../unknown.js';
10
+ const VERSION = 5;
11
+ const NO_AUTHENTICATION = 0;
12
+ const NO_ACCEPTABLE_METHOD = 255;
13
+ const CONNECT = 1;
14
+ const TYPE = { ipv4: 1, name: 3, ipv6: 4 };
15
+ const REPLY = {
16
+ succeeded: 0,
17
+ failed: 1,
18
+ networkUnreachable: 3,
19
+ hostUnreachable: 4,
20
+ refused: 5,
21
+ commandUnsupported: 7,
22
+ addressUnsupported: 8,
23
+ };
24
+ /** IPv6's loopback, as `ipv6Of` writes it. */
25
+ const IPV6_LOOPBACK = '0:0:0:0:0:0:0:1';
26
+ /** The names and addresses the host's loopback answers: Playwright's `<loopback>`, and all of
27
+ * 127.0.0.0/8, which is the loopback too. */
28
+ export const onLoopback = (host) => host === 'localhost' ||
29
+ host.endsWith('.localhost') ||
30
+ (isIP(host) === 4 && host.startsWith('127.')) ||
31
+ host === '::1' ||
32
+ host === IPV6_LOOPBACK;
33
+ /** Where a request goes from the host: a name on the loopback to 127.0.0.1, where every edge
34
+ * listens and macOS puts names under localhost; any other as it was asked. Behind a SOCKS proxy
35
+ * Playwright launches Chromium with `--host-resolver-rules=MAP * ~NOTFOUND`, so nothing else
36
+ * reaches out but through here, and the Stripe sandbox's journeys go to checkout.stripe.com. */
37
+ export const routeOf = ({ host, port }) => onLoopback(host) && isIP(host) === 0 ? { host: '127.0.0.1', port } : { host, port };
38
+ /** A reply with no bound address, which a browser never reads. */
39
+ const reply = (code) => Buffer.from([VERSION, code, 0, TYPE.ipv4, 0, 0, 0, 0, 0, 0]);
40
+ /** Resolves once `socket` has more to read, or is gone. */
41
+ const stirred = async (socket) => {
42
+ const { promise, resolve } = Promise.withResolvers();
43
+ const done = () => {
44
+ socket.off('readable', done).off('close', done);
45
+ resolve(true);
46
+ };
47
+ socket.on('readable', done).on('close', done);
48
+ return promise;
49
+ };
50
+ /** The next `count` bytes from `socket`, once they have come; a failure if it ends first. */
51
+ const bytesOf = async (socket, count) => {
52
+ if (count === 0) {
53
+ return Buffer.alloc(0);
54
+ }
55
+ const chunk = socket.read(count);
56
+ if (Buffer.isBuffer(chunk) && chunk.length === count) {
57
+ return chunk;
58
+ }
59
+ if (Buffer.isBuffer(chunk) || socket.readableEnded || socket.destroyed) {
60
+ throw new Error('the client ended its SOCKS message part way');
61
+ }
62
+ await stirred(socket);
63
+ return bytesOf(socket, count);
64
+ };
65
+ /** The client's greeting, answered: it must offer to go without authentication. */
66
+ const greet = async (client) => {
67
+ const [version, count = 0] = await bytesOf(client, 2);
68
+ if (version !== VERSION) {
69
+ throw new Error(`a SOCKS${String(version)} greeting, not SOCKS5`);
70
+ }
71
+ const methods = await bytesOf(client, count);
72
+ if (!methods.includes(NO_AUTHENTICATION)) {
73
+ client.end(Buffer.from([VERSION, NO_ACCEPTABLE_METHOD]));
74
+ throw new Error('the client offered no way without authentication');
75
+ }
76
+ client.write(Buffer.from([VERSION, NO_AUTHENTICATION]));
77
+ };
78
+ /** Sixteen bytes as an IPv6 address's eight groups, written out whole. */
79
+ const ipv6Of = (bytes) => Array.from({ length: 8 }, (_unused, group) => bytes.readUInt16BE(group * 2).toString(16)).join(':');
80
+ /** The address a request names, by its type; undefined for a type SOCKS5 lacks. */
81
+ const hostOf = async (client, type) => {
82
+ if (type === TYPE.ipv4) {
83
+ const octets = await bytesOf(client, 4);
84
+ return [...octets].join('.');
85
+ }
86
+ if (type === TYPE.ipv6) {
87
+ return ipv6Of(await bytesOf(client, 16));
88
+ }
89
+ if (type === TYPE.name) {
90
+ const [length = 0] = await bytesOf(client, 1);
91
+ const name = await bytesOf(client, length);
92
+ return name.toString('latin1');
93
+ }
94
+ return undefined;
95
+ };
96
+ /** The destination of the client's CONNECT; any other command, or address type, is refused. */
97
+ const destinationOf = async (client) => {
98
+ const [, command, , type] = await bytesOf(client, 4);
99
+ if (command !== CONNECT) {
100
+ client.end(reply(REPLY.commandUnsupported));
101
+ throw new Error(`SOCKS command ${String(command)}, which this server doesn't serve`);
102
+ }
103
+ const host = await hostOf(client, type);
104
+ if (host === undefined) {
105
+ client.end(reply(REPLY.addressUnsupported));
106
+ throw new Error(`SOCKS address type ${String(type)}`);
107
+ }
108
+ const port = await bytesOf(client, 2);
109
+ return { host, port: port.readUInt16BE(0) };
110
+ };
111
+ /** The reply for a connection that failed, by its error's code. */
112
+ const failureOf = (error) => {
113
+ const code = codeOf(error);
114
+ if (code === 'ECONNREFUSED') {
115
+ return REPLY.refused;
116
+ }
117
+ if (code === 'ENETUNREACH') {
118
+ return REPLY.networkUnreachable;
119
+ }
120
+ return code === 'EHOSTUNREACH' || code === 'ENOTFOUND' ? REPLY.hostUnreachable : REPLY.failed;
121
+ };
122
+ /** Each side's end ends the other's writing, through the pipes, and a side closed or broken
123
+ * closes the other, so neither outlives its pair. */
124
+ const joined = (client, upstream) => {
125
+ for (const [one, other] of [
126
+ [client, upstream],
127
+ [upstream, client],
128
+ ]) {
129
+ const end = () => {
130
+ other.destroy();
131
+ };
132
+ one.on('error', end).on('close', end);
133
+ }
134
+ client.pipe(upstream);
135
+ upstream.pipe(client);
136
+ };
137
+ /** Serves one client: its greeting, its CONNECT, then its bytes both ways. */
138
+ const serve = async (client) => {
139
+ await greet(client);
140
+ const destination = routeOf(await destinationOf(client));
141
+ const upstream = connect({ port: destination.port, host: destination.host, allowHalfOpen: true });
142
+ // A browser that gives up first leaves no connection waiting on the operating system's timeout.
143
+ client.once('close', () => {
144
+ upstream.destroy();
145
+ });
146
+ try {
147
+ await once(upstream, 'connect');
148
+ }
149
+ catch (error) {
150
+ client.end(reply(failureOf(error)));
151
+ return;
152
+ }
153
+ client.write(reply(REPLY.succeeded));
154
+ joined(client, upstream);
155
+ };
156
+ /** A SOCKS5 server on `host`, serving only clients from `peer`, the browsers' own address: any
157
+ * other is closed unanswered, as it may reach the host's loopback through it. */
158
+ export const startSocks = async ({ host, peer, }) => {
159
+ const open = new Set();
160
+ // Half open, so a side that has said all it will still hears the other's answer.
161
+ const server = createServer({ allowHalfOpen: true }, (client) => {
162
+ if (client.remoteAddress !== peer) {
163
+ client.destroy();
164
+ return;
165
+ }
166
+ open.add(client);
167
+ // A client that resets part way is closed, never an error the host's process throws.
168
+ client
169
+ .on('error', () => {
170
+ client.destroy();
171
+ })
172
+ .once('close', () => {
173
+ open.delete(client);
174
+ });
175
+ // A broken greeting or a client gone part way ends that client alone.
176
+ serve(client).catch(() => {
177
+ client.destroy();
178
+ });
179
+ });
180
+ server.listen(0, host);
181
+ await once(server, 'listening');
182
+ const address = server.address();
183
+ const port = typeof address === 'object' && address !== null ? address.port : 0;
184
+ return {
185
+ port,
186
+ stop: async () => {
187
+ for (const client of open) {
188
+ client.destroy();
189
+ }
190
+ server.close();
191
+ await once(server, 'close');
192
+ },
193
+ };
194
+ };
195
+ //# sourceMappingURL=socks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"socks.js","sourceRoot":"","sources":["../../../../testing/src/stack/socks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /** The clock a stall is read on, and its alarm, which `after`'s answer cancels; a test's is its
2
+ * own. */
3
+ export interface Clock {
4
+ readonly now: () => number;
5
+ readonly after: (ms: number, ring: () => void) => () => void;
6
+ }
7
+ /** How waits are bounded: the stillness they fail after, on its clock. */
8
+ export interface Stillness {
9
+ readonly stall: number;
10
+ readonly clock: Clock;
11
+ }
12
+ export declare const STILLNESS: Stillness;
13
+ /** What a stalled wait says: what stood still, and who held and waited for it. */
14
+ export interface Still {
15
+ readonly what: string;
16
+ readonly named: () => string;
17
+ }
18
+ /** One resource's watch over its waits: its moves, and its waits, bounded by them. */
19
+ export interface Watch {
20
+ /** A grant or a give: the resource changed hands. */
21
+ readonly moved: () => void;
22
+ /** Counts a wait from now, which `fail` ends should the resource stand still; the answer is
23
+ * called once the wait ends, granted or not. */
24
+ readonly waiting: (fail: (error: Error) => void) => () => void;
25
+ /** Counts a holder, by its test's deadline, until it lets go; its test may move the deadline. */
26
+ readonly holding: (deadline: number | undefined) => Held;
27
+ }
28
+ /** A holder the watch counts: its deadline moved, or let go. */
29
+ export interface Held {
30
+ readonly renew: (deadline: number | undefined) => void;
31
+ readonly release: () => void;
32
+ }
33
+ /** A resource's watch: one alarm while any wait is pending, measuring from the later of its last
34
+ * move and when its waits began. A wait costs a set entry, so thousands queue as cheaply as one. */
35
+ export declare const watchOf: (stillness: Stillness, still: Still) => Watch;
36
+ //# sourceMappingURL=stall.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stall.d.ts","sourceRoot":"","sources":["../../../../testing/src/stack/stall.ts"],"names":[],"mappings":"AASA;WACW;AACX,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;CAC9D;AAeD,0EAA0E;AAC1E,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;CACvB;AAED,eAAO,MAAM,SAAS,EAAE,SAA4C,CAAC;AAErE,kFAAkF;AAClF,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,MAAM,CAAC;CAC9B;AAED,sFAAsF;AACtF,MAAM,WAAW,KAAK;IACpB,qDAAqD;IACrD,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC;IAC3B;qDACiD;IACjD,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IAC/D,iGAAiG;IACjG,QAAQ,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;CAC1D;AAED,gEAAgE;AAChE,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IACvD,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;CAC9B;AAsCD;qGACqG;AACrG,eAAO,MAAM,OAAO,cAAe,SAAS,SAAS,KAAK,KAAG,KA8C5D,CAAC"}
@@ -0,0 +1,93 @@
1
+ // The bound on every hold's wait: a CPU turn, a stack lock, a seat. Waits fail once nothing they
2
+ // wait among has changed hands for a while and every holder is past its test's deadline, naming
3
+ // who holds what and by when: a queue that moves, or a long test inside its own timeout, never
4
+ // trips it, and a holder that outlives its test is a leak.
5
+ /** How long a hold's wait may see nothing change hands; and for a holder that names no deadline,
6
+ * as a hold outside a test, how long it may keep it still. */
7
+ const STALL_MS = 600_000;
8
+ /** Wall-clock time, as a holder's deadline is: its test's start, plus the hold waits it isn't
9
+ * charged, plus its timeout (control.ts). */
10
+ const WALL = {
11
+ now: () => Date.now(),
12
+ after: (ms, ring) => {
13
+ // An alarm never holds its process open: a wait still pending keeps it open itself.
14
+ const timer = setTimeout(ring, ms).unref();
15
+ return () => {
16
+ clearTimeout(timer);
17
+ };
18
+ },
19
+ };
20
+ export const STILLNESS = { stall: STALL_MS, clock: WALL };
21
+ /** When the resource's waits may fail: once still for the stall, and past every holder's deadline,
22
+ * a holder that names none keeping it still for the stall alone. */
23
+ const dueOf = ({ stall }, alarm) => Math.max(alarm.at + stall, ...[...alarm.holders].map(({ deadline }) => deadline ?? alarm.at + stall));
24
+ /** Rings the stall: re-armed for the rest while the resource moved, or a holder is inside its
25
+ * deadline, else every pending wait fails, as all wait on the same stillness. */
26
+ const ringOf = (stillness, still, alarm) => () => {
27
+ const { stall, clock } = stillness;
28
+ alarm.cancel = undefined;
29
+ if (alarm.failing.size === 0) {
30
+ return;
31
+ }
32
+ const left = dueOf(stillness, alarm) - clock.now();
33
+ if (left > 0) {
34
+ alarm.cancel = clock.after(left, ringOf(stillness, still, alarm));
35
+ return;
36
+ }
37
+ const seconds = String(Math.round(stall / 1000));
38
+ const past = `${still.what} changed hands in ${seconds} s, every holder past its deadline`;
39
+ const error = new Error(`${past}: ${still.named()}`);
40
+ for (const fail of alarm.failing) {
41
+ fail(error);
42
+ }
43
+ };
44
+ /** A resource's watch: one alarm while any wait is pending, measuring from the later of its last
45
+ * move and when its waits began. A wait costs a set entry, so thousands queue as cheaply as one. */
46
+ export const watchOf = (stillness, still) => {
47
+ const { stall, clock } = stillness;
48
+ const alarm = {
49
+ at: clock.now(),
50
+ cancel: undefined,
51
+ failing: new Set(),
52
+ holders: new Set(),
53
+ };
54
+ const ring = ringOf(stillness, still, alarm);
55
+ return {
56
+ moved: () => {
57
+ alarm.at = clock.now();
58
+ },
59
+ holding: (deadline) => {
60
+ const holder = { deadline };
61
+ alarm.holders.add(holder);
62
+ return {
63
+ renew: (moved) => {
64
+ holder.deadline = moved;
65
+ // An earlier deadline brings the stall's check forward; a later one the check re-arms for.
66
+ if (alarm.cancel !== undefined) {
67
+ alarm.cancel();
68
+ alarm.cancel = undefined;
69
+ ring();
70
+ }
71
+ },
72
+ release: () => {
73
+ alarm.holders.delete(holder);
74
+ },
75
+ };
76
+ },
77
+ waiting: (fail) => {
78
+ if (alarm.failing.size === 0) {
79
+ alarm.at = clock.now();
80
+ }
81
+ alarm.failing.add(fail);
82
+ alarm.cancel ??= clock.after(stall, ring);
83
+ return () => {
84
+ alarm.failing.delete(fail);
85
+ if (alarm.failing.size === 0 && alarm.cancel !== undefined) {
86
+ alarm.cancel();
87
+ alarm.cancel = undefined;
88
+ }
89
+ };
90
+ },
91
+ };
92
+ };
93
+ //# sourceMappingURL=stall.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stall.js","sourceRoot":"","sources":["../../../../testing/src/stack/stall.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tonalisk-labs/kit",
3
- "version": "1.0.1",
3
+ "version": "1.0.6",
4
4
  "description": "The shared base of the tonalisk-labs sites: the E2E harness, the theme, and the rules every repo extends.",
5
5
  "keywords": [
6
6
  "astro",
@@ -86,10 +86,10 @@
86
86
  "dependencies": {
87
87
  "@tailwindcss/node": "4.3.3",
88
88
  "axe-core": "4.14.0",
89
- "lighthouse": "13.5.0",
89
+ "lighthouse": "13.6.0",
90
90
  "oxc-parser": "0.153.0",
91
91
  "parse5": "8.0.1",
92
- "puppeteer-core": "25.14.0",
92
+ "puppeteer-core": "25.16.0",
93
93
  "robots-parser": "3.0.1",
94
94
  "satteri": "0.10.5",
95
95
  "sax": "1.6.1",
@@ -102,7 +102,7 @@
102
102
  "@types/hast": "3.0.5",
103
103
  "@types/node": "26.6.4",
104
104
  "@types/sax": "1.2.7",
105
- "astro": "7.3.8",
105
+ "astro": "7.4.9",
106
106
  "knip": "6.41.0",
107
107
  "oxfmt": "0.72.0",
108
108
  "oxlint": "1.87.0",
@@ -2,8 +2,9 @@ import { readFileSync } from 'node:fs';
2
2
 
3
3
  import { isObject } from './unknown.ts';
4
4
 
5
- /** A header line of a spec naming the configuration keys it proves, comma separated. */
6
- const COVERS = /^\s*(?:\/\/|\*)\s*Covers:\s*(?<keys>.+)$/gmu;
5
+ /** A header line of a spec naming the configuration keys it proves, comma separated. Its blanks
6
+ * are spaces and tabs only, so it never reaches the next line, and it reads in linear time. */
7
+ const COVERS = /^[ \t]*(?:\/\/|\*)[ \t]*Covers:[ \t]*(?<keys>\S.*)$/gmu;
7
8
 
8
9
  /** The keys a spec's header claims, as dotted paths: `limits.max_open_notes`. */
9
10
  export const claimsIn = (source: string): string[] =>