@sproutboat/toolchain 0.4.0 → 0.4.2
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 +25 -0
- package/package.json +6 -1
- package/src/acquire.test.ts +15 -0
- package/src/acquire.ts +40 -11
- package/src/patch.test.ts +18 -6
- package/src/patch.ts +25 -7
- package/src/pin.ts +5 -4
- package/vendor/porffor-038f415.tar.gz +0 -0
- package/vendor/porffor-1f4ae4a.tar.gz +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# @sproutboat/toolchain
|
|
2
2
|
|
|
3
|
+
## 0.4.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- fa09d90: Update the pinned Porffor compiler to alpha 6 and remove the superseded local promise-resolution workaround.
|
|
8
|
+
|
|
9
|
+
## 0.4.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 969add9: Fix (baronunread/sproutboat#168): a `fetch` handler that resolves a promise
|
|
14
|
+
with a plain object — the common shape for a JSON response — could wedge a
|
|
15
|
+
standalone/native-fetch build at 100% CPU after a handful of requests, with no
|
|
16
|
+
error and no log line. Root cause: `__Porffor_promise_resolve`'s duck-typing
|
|
17
|
+
probe for `.then` walks the value's prototype chain with no termination
|
|
18
|
+
guard, and under certain allocator conditions the walk hits a fixed point
|
|
19
|
+
instead of `null`/`undefined`, spinning forever.
|
|
20
|
+
|
|
21
|
+
`patchPromiseTs` now guards the probe the same way `_internal_object.ts`'s own
|
|
22
|
+
prototype-chain walks already do elsewhere in the runtime: track the previous
|
|
23
|
+
prototype and stop once the "next" pointer stops advancing, reading a
|
|
24
|
+
fixed-point chain as "no `.then` found" instead of spinning. Not specific to
|
|
25
|
+
`Date.prototype.toISOString()` — an earlier note wrongly correlated the bug
|
|
26
|
+
with that one call; see `sproutboat-cli/patches/UPSTREAM.md`.
|
|
27
|
+
|
|
3
28
|
## 0.4.0
|
|
4
29
|
|
|
5
30
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sproutboat/toolchain",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/baronunread/sproutboat-packages.git",
|
|
9
|
+
"directory": "packages/toolchain"
|
|
10
|
+
},
|
|
6
11
|
"main": "./src/index.ts",
|
|
7
12
|
"types": "./src/index.ts",
|
|
8
13
|
"exports": {
|
package/src/acquire.test.ts
CHANGED
|
@@ -94,6 +94,21 @@ async function fixture(): Promise<{ archive: string; sha256: string }> {
|
|
|
94
94
|
"\n" +
|
|
95
95
|
" if (body_owned) free(body_owned);\n" +
|
|
96
96
|
"}\n",
|
|
97
|
+
"compiler/builtins/promise.ts":
|
|
98
|
+
"export const __Porffor_promise_resolve = (value: any, promise: any): void => {\n" +
|
|
99
|
+
" if (Porffor.type(value) == Porffor.TYPES.object) {\n" +
|
|
100
|
+
" const thenHash: i32 = __Porffor_object_hash('then');\n" +
|
|
101
|
+
" let probe: any = value;\n" +
|
|
102
|
+
" while (Porffor.type(probe) == Porffor.TYPES.object) {\n" +
|
|
103
|
+
" if (Porffor.object.lookup(probe, 'then', thenHash) != 0) break;\n" +
|
|
104
|
+
" probe = __Porffor_object_getPrototype(probe);\n" +
|
|
105
|
+
" }\n" +
|
|
106
|
+
" if (Porffor.type(probe) != Porffor.TYPES.object) {\n" +
|
|
107
|
+
" __ecma262_FulfillPromise(promise, value);\n" +
|
|
108
|
+
" return;\n" +
|
|
109
|
+
" }\n" +
|
|
110
|
+
" }\n" +
|
|
111
|
+
"};\n",
|
|
97
112
|
};
|
|
98
113
|
for (const [file, contents] of Object.entries(files)) {
|
|
99
114
|
const path = join(source, file);
|
package/src/acquire.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { homedir } from "node:os";
|
|
|
5
5
|
import { resolve } from "node:path";
|
|
6
6
|
// @ts-expect-error Bun's file loader supplies a path; see the pinned-commit
|
|
7
7
|
// check below for why this is safe to trust unconditionally.
|
|
8
|
-
import vendoredPorfforArchive from "../vendor/porffor-
|
|
8
|
+
import vendoredPorfforArchive from "../vendor/porffor-038f415.tar.gz" with { type: "file" };
|
|
9
9
|
import { ensurePorfforPatched } from "./patch";
|
|
10
10
|
import { PORFFOR_ARCHIVE_SHA256, PORFFOR_ARCHIVE_URL, PORFFOR_COMMIT_FULL } from "./pin";
|
|
11
11
|
|
|
@@ -26,7 +26,12 @@ type AcquireOptions = {
|
|
|
26
26
|
timeoutMs?: number;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
const required = [
|
|
29
|
+
const required = [
|
|
30
|
+
"runtime/index.js",
|
|
31
|
+
"compiler/render.js",
|
|
32
|
+
"compiler/index.js",
|
|
33
|
+
"compiler/uwebsockets.js",
|
|
34
|
+
];
|
|
30
35
|
async function digest(path: string): Promise<string> {
|
|
31
36
|
return createHash("sha256")
|
|
32
37
|
.update(await readFile(path))
|
|
@@ -42,9 +47,11 @@ async function complete(dir: string, expectedArchive = PORFFOR_ARCHIVE_SHA256):
|
|
|
42
47
|
archiveSha256?: string;
|
|
43
48
|
files?: Record<string, string>;
|
|
44
49
|
};
|
|
45
|
-
if (manifest.commit !== PORFFOR_COMMIT_FULL || manifest.archiveSha256 !== expectedArchive)
|
|
50
|
+
if (manifest.commit !== PORFFOR_COMMIT_FULL || manifest.archiveSha256 !== expectedArchive)
|
|
51
|
+
return false;
|
|
46
52
|
for (const file of required)
|
|
47
|
-
if (!manifest.files?.[file] || (await digest(resolve(dir, file))) !== manifest.files[file])
|
|
53
|
+
if (!manifest.files?.[file] || (await digest(resolve(dir, file))) !== manifest.files[file])
|
|
54
|
+
return false;
|
|
48
55
|
return true;
|
|
49
56
|
} catch {
|
|
50
57
|
return false;
|
|
@@ -81,10 +88,16 @@ async function extract(archive: string, stage: string): Promise<void> {
|
|
|
81
88
|
});
|
|
82
89
|
const [code, stderr] = await Promise.all([child.exited, new Response(child.stderr).text()]);
|
|
83
90
|
if (code !== 0)
|
|
84
|
-
throw new PorfforToolchainError(
|
|
91
|
+
throw new PorfforToolchainError(
|
|
92
|
+
"archive",
|
|
93
|
+
`could not extract pinned Porffor archive: ${stderr.trim()}`,
|
|
94
|
+
);
|
|
85
95
|
for (const file of required)
|
|
86
96
|
if (!existsSync(resolve(stage, file)))
|
|
87
|
-
throw new PorfforToolchainError(
|
|
97
|
+
throw new PorfforToolchainError(
|
|
98
|
+
"archive",
|
|
99
|
+
`Porffor archive is missing required file ${file}`,
|
|
100
|
+
);
|
|
88
101
|
}
|
|
89
102
|
|
|
90
103
|
async function waitForPublisher(dir: string, lock: string): Promise<string | null> {
|
|
@@ -113,7 +126,9 @@ export async function ensurePorffor(options: AcquireOptions = {}): Promise<strin
|
|
|
113
126
|
return dir;
|
|
114
127
|
}
|
|
115
128
|
const root = resolve(
|
|
116
|
-
options.cacheRoot ??
|
|
129
|
+
options.cacheRoot ??
|
|
130
|
+
process.env.SPROUTBOAT_TOOLCHAIN_CACHE ??
|
|
131
|
+
resolve(homedir(), ".cache/sproutboat"),
|
|
117
132
|
);
|
|
118
133
|
const dir = resolve(root, `porffor-${PORFFOR_COMMIT_FULL}`);
|
|
119
134
|
const expected = options.expectedSha256 ?? PORFFOR_ARCHIVE_SHA256;
|
|
@@ -127,7 +142,10 @@ export async function ensurePorffor(options: AcquireOptions = {}): Promise<strin
|
|
|
127
142
|
if (published) return published;
|
|
128
143
|
return ensurePorffor(options);
|
|
129
144
|
}
|
|
130
|
-
const stage = resolve(
|
|
145
|
+
const stage = resolve(
|
|
146
|
+
root,
|
|
147
|
+
`.porffor-${PORFFOR_COMMIT_FULL}-${process.pid}-${crypto.randomUUID()}`,
|
|
148
|
+
);
|
|
131
149
|
try {
|
|
132
150
|
if (await complete(dir, expected)) return dir;
|
|
133
151
|
await rm(dir, { recursive: true, force: true });
|
|
@@ -139,9 +157,18 @@ export async function ensurePorffor(options: AcquireOptions = {}): Promise<strin
|
|
|
139
157
|
// left stale by a pin bump (checksum below still catches a wrong file
|
|
140
158
|
// rather than silently accepting it).
|
|
141
159
|
const noOverride = options.url === undefined && options.expectedSha256 === undefined;
|
|
142
|
-
const vendored =
|
|
160
|
+
const vendored =
|
|
161
|
+
noOverride &&
|
|
162
|
+
existsSync(vendoredPorfforArchive) &&
|
|
163
|
+
(await digest(vendoredPorfforArchive)) === expected;
|
|
143
164
|
if (vendored) await writeFile(archive, await readFile(vendoredPorfforArchive));
|
|
144
|
-
else
|
|
165
|
+
else
|
|
166
|
+
await download(
|
|
167
|
+
options.url ?? PORFFOR_ARCHIVE_URL,
|
|
168
|
+
archive,
|
|
169
|
+
options.fetcher ?? fetch,
|
|
170
|
+
options.timeoutMs ?? 30_000,
|
|
171
|
+
);
|
|
145
172
|
const actual = await digest(archive);
|
|
146
173
|
if (actual !== expected)
|
|
147
174
|
throw new PorfforToolchainError(
|
|
@@ -152,7 +179,9 @@ export async function ensurePorffor(options: AcquireOptions = {}): Promise<strin
|
|
|
152
179
|
await rm(archive, { force: true });
|
|
153
180
|
await ensurePorfforPatched(stage);
|
|
154
181
|
const files = Object.fromEntries(
|
|
155
|
-
await Promise.all(
|
|
182
|
+
await Promise.all(
|
|
183
|
+
required.map(async (file) => [file, await digest(resolve(stage, file))] as const),
|
|
184
|
+
),
|
|
156
185
|
);
|
|
157
186
|
await writeFile(
|
|
158
187
|
resolve(stage, ".sproutboat-complete"),
|
package/src/patch.test.ts
CHANGED
|
@@ -162,7 +162,9 @@ test("#156: status-line fallback synthesizes a line for unlisted codes, idempote
|
|
|
162
162
|
|
|
163
163
|
// #163: collect_headers takes res, drops a client-sent x-sb-remote-addr, and
|
|
164
164
|
// appends the real peer; the call site passes res through.
|
|
165
|
-
expect(once).toContain(
|
|
165
|
+
expect(once).toContain(
|
|
166
|
+
"static i32 collect_headers(uWS::HttpRequest* req, uWS::HttpResponse<false>* res) {",
|
|
167
|
+
);
|
|
166
168
|
expect(once).toContain("const i32 headers_ptr = collect_headers(req, res);");
|
|
167
169
|
expect(once).toContain('if (key == "x-sb-remote-addr") continue;');
|
|
168
170
|
expect(once).toContain("res->getRemoteAddressAsText()");
|
|
@@ -179,12 +181,22 @@ test("#156: status-line fallback synthesizes a line for unlisted codes, idempote
|
|
|
179
181
|
);
|
|
180
182
|
expect(once).toContain('key == "x-sb-raw-body"');
|
|
181
183
|
expect(once).toContain("bool raw_body = false;");
|
|
182
|
-
expect(once).toContain(
|
|
183
|
-
|
|
184
|
-
|
|
184
|
+
expect(once).toContain(
|
|
185
|
+
'if (std::string_view(scan_buf, scan_bytes) == "x-sb-raw-body") raw_body = true;',
|
|
186
|
+
);
|
|
187
|
+
expect(once).toContain(
|
|
188
|
+
"if (raw_body) porf_native_fetch_read_raw_bytes(body_value, &body_buf, &body_len);",
|
|
189
|
+
);
|
|
190
|
+
expect(once).toContain(
|
|
191
|
+
"else porf_native_fetch_read_value(body_value, &body_buf, &body_len, &body_owned);",
|
|
192
|
+
);
|
|
185
193
|
// Header reads are the original 4-arg call, unchanged.
|
|
186
|
-
expect(once).toContain(
|
|
187
|
-
|
|
194
|
+
expect(once).toContain(
|
|
195
|
+
"porf_native_fetch_read_value(name_value, &name_buf, &name_len, &name_owned);",
|
|
196
|
+
);
|
|
197
|
+
expect(once).toContain(
|
|
198
|
+
"porf_native_fetch_read_value(value_value, &value_buf, &value_len, &value_owned);",
|
|
199
|
+
);
|
|
188
200
|
|
|
189
201
|
await patchUwebsockets(root);
|
|
190
202
|
expect(await readFile(join(root, "compiler/uwebsockets.js"), "utf8")).toBe(once);
|
package/src/patch.ts
CHANGED
|
@@ -108,7 +108,8 @@ const BYTESTRING_MARKER = "sproutboat #172";
|
|
|
108
108
|
* below) calls it, gated on the reserved `x-sb-raw-body` response header the
|
|
109
109
|
* assets binding sets. Every existing call site is untouched.
|
|
110
110
|
*/
|
|
111
|
-
const READ_RAW_ANCHOR =
|
|
111
|
+
const READ_RAW_ANCHOR =
|
|
112
|
+
"int porf_native_fetch_read_value(jsval value, const char** out_buf, size_t* out_len, char** out_owned) {";
|
|
112
113
|
const READ_RAW_INJECT =
|
|
113
114
|
"// sproutboat #176: zero-copy passthrough for a bytestring that is already-\n" +
|
|
114
115
|
"// finished bytes (an asset read off disk), never a string to UTF-8 encode.\n" +
|
|
@@ -208,7 +209,8 @@ const STATUS_SIG_MARKER = "static std::string lookup_status_line(i32 status) {";
|
|
|
208
209
|
// 303 is the one this was reported for (POST-redirect-GET); give it the real
|
|
209
210
|
// reason phrase. Everything else unlisted rides the synthesized fallback below.
|
|
210
211
|
const STATUS_303_ANCHOR = ' case 302: return "302 Found";\n';
|
|
211
|
-
const STATUS_303_INJECT =
|
|
212
|
+
const STATUS_303_INJECT =
|
|
213
|
+
' case 302: return "302 Found";\n case 303: return "303 See Other";\n';
|
|
212
214
|
const STATUS_303_MARKER = 'case 303: return "303 See Other";';
|
|
213
215
|
const STATUS_DEFAULT_ANCHOR = " default: return {};";
|
|
214
216
|
const STATUS_DEFAULT_INJECT = ' default: return std::to_string(status) + " Status";';
|
|
@@ -225,7 +227,8 @@ const STATUS_DEFAULT_MARKER = 'std::to_string(status) + " Status"';
|
|
|
225
227
|
* `request.cf.clientIp` and resolves it against `SB_TRUSTED_PROXIES` if set.
|
|
226
228
|
*/
|
|
227
229
|
const HDR_SIG_ANCHOR = "static i32 collect_headers(uWS::HttpRequest* req) {\n";
|
|
228
|
-
const HDR_SIG_INJECT =
|
|
230
|
+
const HDR_SIG_INJECT =
|
|
231
|
+
"static i32 collect_headers(uWS::HttpRequest* req, uWS::HttpResponse<false>* res) {\n";
|
|
229
232
|
const HDR_SIG_MARKER = "collect_headers(uWS::HttpRequest* req, uWS::HttpResponse";
|
|
230
233
|
|
|
231
234
|
const HDR_CALL_ANCHOR = "const i32 headers_ptr = collect_headers(req);";
|
|
@@ -361,7 +364,7 @@ const WRITE_RESPONSE_INJECT =
|
|
|
361
364
|
" size_t scan_bytes = 0;\n" +
|
|
362
365
|
" char* scan_owned = nullptr;\n" +
|
|
363
366
|
" porf_native_fetch_read_value(scan_name, &scan_buf, &scan_bytes, &scan_owned);\n" +
|
|
364
|
-
|
|
367
|
+
' if (std::string_view(scan_buf, scan_bytes) == "x-sb-raw-body") raw_body = true;\n' +
|
|
365
368
|
" if (scan_owned) free(scan_owned);\n" +
|
|
366
369
|
" }\n" +
|
|
367
370
|
" }\n" +
|
|
@@ -417,15 +420,30 @@ const UWS_EDITS = [
|
|
|
417
420
|
[BODY_MARKER, BODY_ANCHOR, BODY_INJECT, "request body limit"],
|
|
418
421
|
[STATUS_SIG_MARKER, STATUS_SIG_ANCHOR, STATUS_SIG_INJECT, "status-line return type (#156)"],
|
|
419
422
|
[STATUS_303_MARKER, STATUS_303_ANCHOR, STATUS_303_INJECT, "status-line 303 case (#156)"],
|
|
420
|
-
[
|
|
423
|
+
[
|
|
424
|
+
STATUS_DEFAULT_MARKER,
|
|
425
|
+
STATUS_DEFAULT_ANCHOR,
|
|
426
|
+
STATUS_DEFAULT_INJECT,
|
|
427
|
+
"status-line fallback (#156)",
|
|
428
|
+
],
|
|
421
429
|
[HDR_SIG_MARKER, HDR_SIG_ANCHOR, HDR_SIG_INJECT, "collect_headers res arg (#163)"],
|
|
422
430
|
[HDR_CALL_MARKER, HDR_CALL_ANCHOR, HDR_CALL_INJECT, "collect_headers call site (#163)"],
|
|
423
431
|
[HDR_CAP_MARKER, HDR_CAP_ANCHOR, HDR_CAP_INJECT, "remote-addr header capacity (#163)"],
|
|
424
432
|
[HDR_SKIP_MARKER, HDR_SKIP_ANCHOR, HDR_SKIP_INJECT, "drop client-sent x-sb-remote-addr (#163)"],
|
|
425
433
|
[HDR_APPEND_MARKER, HDR_APPEND_ANCHOR, HDR_APPEND_INJECT, "append x-sb-remote-addr (#163)"],
|
|
426
434
|
[READ_RAW_DECL_MARKER, READ_RAW_DECL_ANCHOR, READ_RAW_DECL_INJECT, "read_raw_bytes decl (#176)"],
|
|
427
|
-
[
|
|
428
|
-
|
|
435
|
+
[
|
|
436
|
+
FORBIDDEN_HDR_MARKER,
|
|
437
|
+
FORBIDDEN_HDR_ANCHOR,
|
|
438
|
+
FORBIDDEN_HDR_INJECT,
|
|
439
|
+
"drop x-sb-raw-body from the wire (#176)",
|
|
440
|
+
],
|
|
441
|
+
[
|
|
442
|
+
WRITE_RESPONSE_MARKER,
|
|
443
|
+
WRITE_RESPONSE_ANCHOR,
|
|
444
|
+
WRITE_RESPONSE_INJECT,
|
|
445
|
+
"detect x-sb-raw-body, steer the body read (#176)",
|
|
446
|
+
],
|
|
429
447
|
] as const;
|
|
430
448
|
|
|
431
449
|
/** The uWebSockets shim source: body limit + the #156 status-line fix. Exported for tests. */
|
package/src/pin.ts
CHANGED
|
@@ -10,13 +10,14 @@
|
|
|
10
10
|
* uWebSockets archive in `sproutboat-cli/vendor/` — then run both repos'
|
|
11
11
|
* test + conformance suites. See sproutboat-cli/MIGRATION.md.
|
|
12
12
|
*/
|
|
13
|
-
export const PORFFOR_CHANNEL = "alpha-
|
|
14
|
-
export const PORFFOR_COMMIT_FULL = "
|
|
13
|
+
export const PORFFOR_CHANNEL = "alpha-6";
|
|
14
|
+
export const PORFFOR_COMMIT_FULL = "038f415e08efc5f87a6bfcb05a18824caa3a14f6";
|
|
15
15
|
export const PORFFOR_COMMIT = PORFFOR_COMMIT_FULL.slice(0, 7);
|
|
16
|
-
export const PORFFOR_ARCHIVE_SHA256 =
|
|
16
|
+
export const PORFFOR_ARCHIVE_SHA256 =
|
|
17
|
+
"61f65d8fac5f94a7910f0a3f046e8929a852addb0033a032b9f7e08d0b07df5c";
|
|
17
18
|
export const PORFFOR_ARCHIVE_URL = `https://codeload.github.com/CanadaHonk/porffor/tar.gz/${PORFFOR_COMMIT_FULL}`;
|
|
18
19
|
|
|
19
|
-
/** A compact identity string for a manifest / report (`alpha-
|
|
20
|
+
/** A compact identity string for a manifest / report (`alpha-6 (038f415)`). */
|
|
20
21
|
export function porfforVersion(): string {
|
|
21
22
|
return process.env.PORFFOR_VERSION || `${PORFFOR_CHANNEL} (${PORFFOR_COMMIT})`;
|
|
22
23
|
}
|
|
Binary file
|
|
Binary file
|