@sproutboat/toolchain 0.4.12 → 0.4.13
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/CHANGELOG.md +6 -0
- package/PORFFOR_SCORECARD.md +35 -0
- package/package.json +1 -1
- package/scorecards/alpha-9-de4eb58.json +58 -0
- package/src/acquire.test.ts +1 -0
- package/src/acquire.ts +1 -0
- package/src/date-native.test.ts +37 -0
- package/src/patch.ts +102 -1
package/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Porffor pin scorecard
|
|
2
|
+
|
|
3
|
+
Pin: alpha-9 (de4eb588264885b3a1596f75010e371a2052033f). Compatibility report: 2026-09-26T00:32:42.739Z.
|
|
4
|
+
|
|
5
|
+
| Measure | Result |
|
|
6
|
+
| --- | ---: |
|
|
7
|
+
| Fixtures compiled | 32/32 |
|
|
8
|
+
| Behavior matches | 30/32 |
|
|
9
|
+
| Median / p95 compile, ms | 3139 / 3472 |
|
|
10
|
+
| Median / p95 binary, bytes | 983392 / 1016464 |
|
|
11
|
+
| Patch markers | 27 |
|
|
12
|
+
| Upstream files written | 6 |
|
|
13
|
+
| Fresh patch application | pass |
|
|
14
|
+
|
|
15
|
+
This is the first recorded pin. Compare the next pin against this snapshot.
|
|
16
|
+
|
|
17
|
+
## Patch inventory
|
|
18
|
+
|
|
19
|
+
- `compiler/render.js`: PORT_MARKER, CONSOLE_MARKER, BYTESTRING_MARKER, READ_RAW_MARKER
|
|
20
|
+
- `compiler/index.js`: LINK_MARKER, CFLAGS_MARKER
|
|
21
|
+
- `compiler/builtins/typedarray.js`: TYPED_ARRAY_FROM_MARKER
|
|
22
|
+
- `compiler/builtins/date.ts`: DATE_PARSER_MARKER
|
|
23
|
+
- `compiler/uwebsockets.js`: BODY_MARKER, STATUS_SIG_MARKER, STATUS_303_MARKER, STATUS_DEFAULT_MARKER, HDR_SIG_MARKER, HDR_CALL_MARKER, HDR_CAP_MARKER, HDR_SKIP_MARKER, HDR_APPEND_MARKER, READ_RAW_DECL_MARKER, FORBIDDEN_HDR_MARKER, WRITE_RESPONSE_MARKER, R2_TRANSFER_MARKER, R2_TRANSFER_CALL_MARKER, R2_DOWNLOAD_CACHE_MARKER, R2_RANGE_CACHE_MARKER, R2_CONDITIONAL_CACHE_MARKER, R2_STREAMING_CACHE_MARKER, R2_RANGE_HEADER_CACHE_MARKER
|
|
24
|
+
- `compiler/builtins_precompiled.js`: regenerated builtin table
|
|
25
|
+
|
|
26
|
+
Patch source SHA-256: `69cf5d0bcf3f4d4c05b81df0ff04b83479af622539fa19566155531bf3ee469c`.
|
|
27
|
+
|
|
28
|
+
## Known behavior gaps
|
|
29
|
+
|
|
30
|
+
- 15-date-iso.js: request 3 mismatch: expected {"status":200,"body":"2008-03-04T23:00:00.000Z","content-type":null}, got {"status":200,"body":"0003-05-08T00:00:00.000Z","content-type":null}
|
|
31
|
+
- 16-date-parts.js: request 3 mismatch: expected {"status":200,"body":"{\"year\":2008,\"month\":3,\"day\":4}","content-type":"application/json;charset=utf-8"}, got {"status":200,"body":"{\"year\":3,\"month\":5,\"day\":8}","content-type":"application/json;charset=utf-8"}
|
|
32
|
+
|
|
33
|
+
## Review
|
|
34
|
+
|
|
35
|
+
Use this scorecard with the full compatibility report and issue #35's build telemetry. If behavior regresses or patch work grows, review the individual patches and measure an alternate backend with the same fixtures.
|
package/package.json
CHANGED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pin": "alpha-9 (de4eb588264885b3a1596f75010e371a2052033f)",
|
|
3
|
+
"reportGeneratedAt": "2026-09-26T00:32:42.739Z",
|
|
4
|
+
"fixtures": 32,
|
|
5
|
+
"compiled": 32,
|
|
6
|
+
"matched": 30,
|
|
7
|
+
"medianCompileMs": 3139,
|
|
8
|
+
"p95CompileMs": 3472,
|
|
9
|
+
"medianBinaryBytes": 983392,
|
|
10
|
+
"p95BinaryBytes": 1016464,
|
|
11
|
+
"patchSourceSha256": "69cf5d0bcf3f4d4c05b81df0ff04b83479af622539fa19566155531bf3ee469c",
|
|
12
|
+
"patchMarkerCount": 27,
|
|
13
|
+
"patchTargets": {
|
|
14
|
+
"compiler/render.js": [
|
|
15
|
+
"PORT_MARKER",
|
|
16
|
+
"CONSOLE_MARKER",
|
|
17
|
+
"BYTESTRING_MARKER",
|
|
18
|
+
"READ_RAW_MARKER"
|
|
19
|
+
],
|
|
20
|
+
"compiler/index.js": [
|
|
21
|
+
"LINK_MARKER",
|
|
22
|
+
"CFLAGS_MARKER"
|
|
23
|
+
],
|
|
24
|
+
"compiler/builtins/typedarray.js": [
|
|
25
|
+
"TYPED_ARRAY_FROM_MARKER"
|
|
26
|
+
],
|
|
27
|
+
"compiler/builtins/date.ts": [
|
|
28
|
+
"DATE_PARSER_MARKER"
|
|
29
|
+
],
|
|
30
|
+
"compiler/uwebsockets.js": [
|
|
31
|
+
"BODY_MARKER",
|
|
32
|
+
"STATUS_SIG_MARKER",
|
|
33
|
+
"STATUS_303_MARKER",
|
|
34
|
+
"STATUS_DEFAULT_MARKER",
|
|
35
|
+
"HDR_SIG_MARKER",
|
|
36
|
+
"HDR_CALL_MARKER",
|
|
37
|
+
"HDR_CAP_MARKER",
|
|
38
|
+
"HDR_SKIP_MARKER",
|
|
39
|
+
"HDR_APPEND_MARKER",
|
|
40
|
+
"READ_RAW_DECL_MARKER",
|
|
41
|
+
"FORBIDDEN_HDR_MARKER",
|
|
42
|
+
"WRITE_RESPONSE_MARKER",
|
|
43
|
+
"R2_TRANSFER_MARKER",
|
|
44
|
+
"R2_TRANSFER_CALL_MARKER",
|
|
45
|
+
"R2_DOWNLOAD_CACHE_MARKER",
|
|
46
|
+
"R2_RANGE_CACHE_MARKER",
|
|
47
|
+
"R2_CONDITIONAL_CACHE_MARKER",
|
|
48
|
+
"R2_STREAMING_CACHE_MARKER",
|
|
49
|
+
"R2_RANGE_HEADER_CACHE_MARKER"
|
|
50
|
+
],
|
|
51
|
+
"compiler/builtins_precompiled.js": []
|
|
52
|
+
},
|
|
53
|
+
"patchApplication": "pass",
|
|
54
|
+
"mismatches": [
|
|
55
|
+
"15-date-iso.js: request 3 mismatch: expected {\"status\":200,\"body\":\"2008-03-04T23:00:00.000Z\",\"content-type\":null}, got {\"status\":200,\"body\":\"0003-05-08T00:00:00.000Z\",\"content-type\":null}",
|
|
56
|
+
"16-date-parts.js: request 3 mismatch: expected {\"status\":200,\"body\":\"{\\\"year\\\":2008,\\\"month\\\":3,\\\"day\\\":4}\",\"content-type\":\"application/json;charset=utf-8\"}, got {\"status\":200,\"body\":\"{\\\"year\\\":3,\\\"month\\\":5,\\\"day\\\":8}\",\"content-type\":\"application/json;charset=utf-8\"}"
|
|
57
|
+
]
|
|
58
|
+
}
|
package/src/acquire.test.ts
CHANGED
|
@@ -34,6 +34,7 @@ async function fixture(): Promise<{ archive: string; sha256: string }> {
|
|
|
34
34
|
"const path = require('node:path');\n" +
|
|
35
35
|
"fs.writeFileSync(path.join(__dirname, 'builtins_precompiled.js'), fs.readFileSync(path.join(__dirname, 'builtins/typedarray.js')));\n",
|
|
36
36
|
"compiler/builtins_precompiled.js": "fixture:precompiled\n",
|
|
37
|
+
"compiler/builtins/date.ts": "export const __ecma262_ParseDTSF = (string: string) => {\n let n: number = 0;\n let nInd: number = 0;\n\n const len: i32 = string.length;\n const endPtr: i32 = Porffor.IR.ptr(string) + len;\n let ptr: i32 = Porffor.IR.ptr(string);\n\n while (ptr <= endPtr) { // <= to include extra null byte to set last n\n const chr: i32 = Porffor.IR.loadU8(ptr++, 4);\n if (Porffor.fastAnd(chr >= 48, chr <= 57)) { // 0-9\n n *= 10;\n n += chr - 48;\n continue;\n }\n\n if (chr == 45) { // -\n if (Porffor.fastOr(ptr == Porffor.IR.ptr(string), nInd == 7)) n = -n;\n }\n\n if (n > 0) {\n if (nInd == 0) y = n;\n else if (nInd == 1) m = n - 1;\n else if (nInd == 2) dt = n;\n else if (nInd == 3) h = n;\n else if (nInd == 4) min = n;\n else if (nInd == 5) s = n;\n else if (nInd == 6) milli = n;\n else if (nInd == 7) tzHour = n;\n else if (nInd == 8) tzMin = n;\n\n n = 0;\n nInd++;\n }\n }\n\n h += tzHour;\n min += tzMin;\n};\n// RFC 7231 or Date.prototype.toString() parser\n",
|
|
37
38
|
"compiler/builtins/typedarray.js":
|
|
38
39
|
"export const __${name}_from = (arg: any, mapFn: any): ${name} => {\n" +
|
|
39
40
|
" const arr: any[] = Porffor.array.new(4);\n" +
|
package/src/acquire.ts
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { ensurePorffor } from "./acquire";
|
|
6
|
+
|
|
7
|
+
test("native ISO dates honor signed offsets, zero fields, and fractional seconds", async () => {
|
|
8
|
+
const root = await mkdtemp(resolve(tmpdir(), "sb-date-native-"));
|
|
9
|
+
const samples = [
|
|
10
|
+
"2024-01-02T03:04:05Z",
|
|
11
|
+
"2024-01-02T03:04:05+02:00",
|
|
12
|
+
"2024-01-02T03:04:05-05:00",
|
|
13
|
+
"2024-01-02T03:04:05.250+01:00",
|
|
14
|
+
"2024-01-02T00:00:00Z",
|
|
15
|
+
"2024-01-02T03:04:05.2+02:30",
|
|
16
|
+
"2024-01-02T03:04:05.25-00:30",
|
|
17
|
+
];
|
|
18
|
+
try {
|
|
19
|
+
const porffor = await ensurePorffor({ cacheRoot: resolve(root, "cache") });
|
|
20
|
+
const source = resolve(root, "date.js");
|
|
21
|
+
const binary = resolve(root, "date-native");
|
|
22
|
+
await writeFile(source, `for (const value of ${JSON.stringify(samples)}) console.log(new Date(value).toISOString());\n`);
|
|
23
|
+
const compile = Bun.spawnSync(["bun", resolve(porffor, "runtime/index.js"), "native", source, "-o", binary, "-s"], {
|
|
24
|
+
cwd: root,
|
|
25
|
+
stdout: "pipe",
|
|
26
|
+
stderr: "pipe",
|
|
27
|
+
});
|
|
28
|
+
expect(compile.exitCode, Buffer.from(compile.stderr).toString()).toBe(0);
|
|
29
|
+
const run = Bun.spawnSync([binary], { stdout: "pipe", stderr: "pipe" });
|
|
30
|
+
expect(run.exitCode, Buffer.from(run.stderr).toString()).toBe(0);
|
|
31
|
+
expect(Buffer.from(run.stdout).toString().trim().split("\n")).toEqual(
|
|
32
|
+
samples.map((value) => new Date(value).toISOString()),
|
|
33
|
+
);
|
|
34
|
+
} finally {
|
|
35
|
+
await rm(root, { recursive: true, force: true });
|
|
36
|
+
}
|
|
37
|
+
}, 30_000);
|
package/src/patch.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Idempotent, marker-guarded in-place edits to Porffor's generated C
|
|
3
3
|
* (`compiler/render.js`, `compiler/index.js`, `compiler/uwebsockets.js`,
|
|
4
|
-
* `compiler/builtins/typedarray.js`). Run
|
|
4
|
+
* `compiler/builtins/typedarray.js`, `compiler/builtins/date.ts`). Run
|
|
5
5
|
* from the build path, not a `postinstall` hook: package managers block
|
|
6
6
|
* dependency lifecycle scripts by default, so a published `postinstall` would
|
|
7
7
|
* silently not run.
|
|
@@ -193,6 +193,93 @@ const TYPED_ARRAY_FROM_INJECT =
|
|
|
193
193
|
" }\n\n arr.length = len;\n\n return new ${name}(arr);\n};";
|
|
194
194
|
const TYPED_ARRAY_FROM_MARKER = "sproutboat: TypedArray.from accepts array-like objects";
|
|
195
195
|
|
|
196
|
+
// Porffor alpha 9 treats +02:00 as two milliseconds and adds offset hours
|
|
197
|
+
// instead of subtracting them. Count fields even when they contain zero, and
|
|
198
|
+
// apply the signed offset after parsing the local wall-clock fields.
|
|
199
|
+
const DATE_PARSER_MARKER = "sproutboat: ISO timezone offset";
|
|
200
|
+
const DATE_PARSER_ANCHOR = ` let n: number = 0;
|
|
201
|
+
let nInd: number = 0;
|
|
202
|
+
|
|
203
|
+
const len: i32 = string.length;
|
|
204
|
+
const endPtr: i32 = Porffor.IR.ptr(string) + len;
|
|
205
|
+
let ptr: i32 = Porffor.IR.ptr(string);
|
|
206
|
+
|
|
207
|
+
while (ptr <= endPtr) { // <= to include extra null byte to set last n
|
|
208
|
+
const chr: i32 = Porffor.IR.loadU8(ptr++, 4);
|
|
209
|
+
if (Porffor.fastAnd(chr >= 48, chr <= 57)) { // 0-9
|
|
210
|
+
n *= 10;
|
|
211
|
+
n += chr - 48;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (chr == 45) { // -
|
|
216
|
+
if (Porffor.fastOr(ptr == Porffor.IR.ptr(string), nInd == 7)) n = -n;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (n > 0) {
|
|
220
|
+
if (nInd == 0) y = n;
|
|
221
|
+
else if (nInd == 1) m = n - 1;
|
|
222
|
+
else if (nInd == 2) dt = n;
|
|
223
|
+
else if (nInd == 3) h = n;
|
|
224
|
+
else if (nInd == 4) min = n;
|
|
225
|
+
else if (nInd == 5) s = n;
|
|
226
|
+
else if (nInd == 6) milli = n;
|
|
227
|
+
else if (nInd == 7) tzHour = n;
|
|
228
|
+
else if (nInd == 8) tzMin = n;
|
|
229
|
+
|
|
230
|
+
n = 0;
|
|
231
|
+
nInd++;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
h += tzHour;
|
|
236
|
+
min += tzMin;`;
|
|
237
|
+
const DATE_PARSER_INJECT = ` // sproutboat: ISO timezone offset
|
|
238
|
+
let n: number = 0;
|
|
239
|
+
let nInd: number = 0;
|
|
240
|
+
let digits: i32 = 0;
|
|
241
|
+
let offsetSign: i32 = 0;
|
|
242
|
+
|
|
243
|
+
const len: i32 = string.length;
|
|
244
|
+
const endPtr: i32 = Porffor.IR.ptr(string) + len;
|
|
245
|
+
let ptr: i32 = Porffor.IR.ptr(string);
|
|
246
|
+
|
|
247
|
+
while (ptr <= endPtr) {
|
|
248
|
+
const chr: i32 = Porffor.IR.loadU8(ptr++, 4);
|
|
249
|
+
if (Porffor.fastAnd(chr >= 48, chr <= 57)) {
|
|
250
|
+
n = n * 10 + chr - 48;
|
|
251
|
+
digits++;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (digits > 0) {
|
|
256
|
+
if (nInd == 0) y = n;
|
|
257
|
+
else if (nInd == 1) m = n - 1;
|
|
258
|
+
else if (nInd == 2) dt = n;
|
|
259
|
+
else if (nInd == 3) h = n;
|
|
260
|
+
else if (nInd == 4) min = n;
|
|
261
|
+
else if (nInd == 5) s = n;
|
|
262
|
+
else if (nInd == 6) {
|
|
263
|
+
while (digits < 3) { n *= 10; digits++; }
|
|
264
|
+
while (digits > 3) { n = Math.trunc(n / 10); digits--; }
|
|
265
|
+
milli = n;
|
|
266
|
+
}
|
|
267
|
+
else if (nInd == 7) tzHour = n;
|
|
268
|
+
else if (nInd == 8) tzMin = n;
|
|
269
|
+
n = 0;
|
|
270
|
+
nInd++;
|
|
271
|
+
digits = 0;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (Porffor.fastOr(chr == 43, chr == 45) && nInd >= 6) {
|
|
275
|
+
offsetSign = chr == 43 ? 1 : -1;
|
|
276
|
+
nInd = 7;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
h -= offsetSign * tzHour;
|
|
281
|
+
min -= offsetSign * tzMin;`;
|
|
282
|
+
|
|
196
283
|
/**
|
|
197
284
|
* #56 — make the inbound request-body limit configurable.
|
|
198
285
|
*
|
|
@@ -856,6 +943,19 @@ async function patchCompilerArgs(root: string): Promise<void> {
|
|
|
856
943
|
if (changed) await writeFile(file, src);
|
|
857
944
|
}
|
|
858
945
|
|
|
946
|
+
export async function patchDateParser(root: string): Promise<void> {
|
|
947
|
+
const file = resolve(root, "compiler/builtins/date.ts");
|
|
948
|
+
const src = await readFile(file, "utf8");
|
|
949
|
+
if (src.includes(DATE_PARSER_MARKER)) return;
|
|
950
|
+
const start = src.indexOf("export const __ecma262_ParseDTSF");
|
|
951
|
+
const end = src.indexOf("// RFC 7231 or Date.prototype.toString() parser", start);
|
|
952
|
+
if (start < 0 || end < 0) throw new Error(`could not find Porffor's ISO date parser in ${file}`);
|
|
953
|
+
const parser = src.slice(start, end);
|
|
954
|
+
if (!parser.includes(DATE_PARSER_ANCHOR))
|
|
955
|
+
throw new Error(`could not patch Porffor's ISO date parser: anchor not found in ${file}`);
|
|
956
|
+
await writeFile(file, src.slice(0, start) + parser.replace(DATE_PARSER_ANCHOR, DATE_PARSER_INJECT) + src.slice(end));
|
|
957
|
+
}
|
|
958
|
+
|
|
859
959
|
export async function patchTypedArrayFrom(root: string): Promise<void> {
|
|
860
960
|
const file = resolve(root, "compiler/builtins/typedarray.js");
|
|
861
961
|
const src = await readFile(file, "utf8");
|
|
@@ -930,6 +1030,7 @@ export async function ensurePorfforPatched(root: string): Promise<void> {
|
|
|
930
1030
|
await patchCompilerArgs(root);
|
|
931
1031
|
await patchUwebsockets(root);
|
|
932
1032
|
await patchRenderJs(root);
|
|
1033
|
+
await patchDateParser(root);
|
|
933
1034
|
await patchTypedArrayFrom(root);
|
|
934
1035
|
done.add(root);
|
|
935
1036
|
}
|