@sproutboat/toolchain 0.4.5 → 0.4.7

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @sproutboat/toolchain
2
2
 
3
+ ## 0.4.7
4
+
5
+ ### Patch Changes
6
+
7
+ - ba5a6a0: Keep the normal native-fetch request handler outside the optional direct R2 transfer compiler guard.
8
+
9
+ ## 0.4.6
10
+
11
+ ### Patch Changes
12
+
13
+ - a357b91: Compile direct R2 transfer support only into artifacts that declare an R2 binding.
14
+
3
15
  ## 0.4.5
4
16
 
5
17
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sproutboat/toolchain",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
@@ -28,7 +28,9 @@ async function fixture(): Promise<{ archive: string; sha256: string }> {
28
28
  " }\n" +
29
29
  " return -1;\n" +
30
30
  "}\n",
31
- "compiler/index.js": " '-xc', '-', '-c',\n uSocketsArchive,\n '-lm'\n",
31
+ "compiler/index.js":
32
+ " '-xc', '-', '-c',\n uSocketsArchive,\n '-lm'\n" +
33
+ " '-I', `${uwsDir}/uSockets/src`,\n",
32
34
  "compiler/uwebsockets.js":
33
35
  "static const size_t REQUEST_BODY_MAX_BYTES = 1024u * 1024u;\n" +
34
36
  "int porf_native_fetch_read_value(struct jsval value, const char** out_buf, size_t* out_len, char** out_owned);\n" +
package/src/patch.test.ts CHANGED
@@ -2,7 +2,8 @@ import { expect, test } from "bun:test";
2
2
  import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
- import { patchRenderJs, patchUwebsockets } from "./patch";
5
+ import { EMPTY_BINDINGS, wrapNativeFetchHandler } from "../../runtime/src/wrap";
6
+ import { patchCompilerArgs, patchRenderJs, patchUwebsockets } from "./patch";
6
7
 
7
8
  // The parts of Porffor's compiler/render.js the patch anchors to.
8
9
  const RENDER = `void porf_native_fetch_runtime_init(void) {
@@ -36,6 +37,16 @@ int porf_native_fetch_read_value(jsval value, const char** out_buf, size_t* out_
36
37
  }
37
38
  `;
38
39
 
40
+ const COMPILER_INDEX = `const compileArgs = [
41
+ '-xc', '-', '-c',
42
+ ];
43
+ const linkArgs = [
44
+ '-I', \`${"${uwsDir}"}/uSockets/src\`,
45
+ uSocketsArchive,
46
+ '-lm'
47
+ ];
48
+ `;
49
+
39
50
  // Porffor's compiler/uwebsockets.js, trimmed to the parts the patch touches:
40
51
  // body limit (#56), the #156 status-line switch, #163's collect_headers, and
41
52
  // #176's read_value forward decl / is_forbidden_response_header /
@@ -230,6 +241,86 @@ test("a drifted shim fails loudly instead of silently no-op'ing", async () => {
230
241
  }
231
242
  });
232
243
 
244
+ test("native capability flags reach both the generated C and socket-shim C++ compiles", async () => {
245
+ const root = await mkdtemp(join(tmpdir(), "sb-patch-compiler-"));
246
+ try {
247
+ await mkdir(join(root, "compiler"), { recursive: true });
248
+ await writeFile(join(root, "compiler/index.js"), COMPILER_INDEX);
249
+ await patchCompilerArgs(root);
250
+ const once = await readFile(join(root, "compiler/index.js"), "utf8");
251
+ expect(once.match(/SB_EXTRA_CFLAGS/g)).toHaveLength(4);
252
+ expect(once).toContain("SB_UWS_EXTRA_CFLAGS");
253
+ await patchCompilerArgs(root);
254
+ expect(await readFile(join(root, "compiler/index.js"), "utf8")).toBe(once);
255
+ } finally {
256
+ await rm(root, { recursive: true, force: true });
257
+ }
258
+ });
259
+
260
+ test("R2 builds define every direct transfer symbol the gated socket shim calls", async () => {
261
+ const root = await shimDir();
262
+ try {
263
+ await patchUwebsockets(root);
264
+ const shim = await readFile(join(root, "compiler/uwebsockets.js"), "utf8");
265
+ const abi = shim.match(/extern "C" \{([\s\S]*?)\n\}/)?.[1];
266
+ if (abi === undefined) throw new Error("patched shim did not contain the R2 transfer ABI");
267
+ const required = [...abi.matchAll(/\b(sb_r2_transfer_[a-z_]+)\s*\(/g)].map((match) => match[1]);
268
+ const wrapped = wrapNativeFetchHandler(
269
+ "export default { fetch() { return new Response('ok'); } }",
270
+ "",
271
+ {},
272
+ { ...EMPTY_BINDINGS, r2: ["OBJECTS"] },
273
+ 8080,
274
+ "2026-08-26",
275
+ "test",
276
+ undefined,
277
+ "broker",
278
+ );
279
+
280
+ for (const symbol of new Set(required)) {
281
+ expect(wrapped).toMatch(new RegExp(`\\b${symbol}\\s*\\([^)]*\\)\\s*\\{`));
282
+ }
283
+ } finally {
284
+ await rm(root, { recursive: true, force: true });
285
+ }
286
+ });
287
+
288
+ test("the direct R2 socket path is excluded unless the compiler enables it", async () => {
289
+ const root = await shimDir();
290
+ try {
291
+ await patchUwebsockets(root);
292
+ const shim = await readFile(join(root, "compiler/uwebsockets.js"), "utf8");
293
+ expect(shim).toContain("#ifdef SB_R2_TRANSFER");
294
+ expect(shim).toContain("#endif\n __porffor_js_enter();");
295
+ } finally {
296
+ await rm(root, { recursive: true, force: true });
297
+ }
298
+ });
299
+
300
+ test("an existing compiler cache upgrades to the gated R2 socket path", async () => {
301
+ const root = await shimDir();
302
+ try {
303
+ await patchUwebsockets(root);
304
+ const file = join(root, "compiler/uwebsockets.js");
305
+ const current = await readFile(file, "utf8");
306
+ const legacy = current
307
+ .replace('#ifdef SB_R2_TRANSFER\nextern "C" {', 'extern "C" {')
308
+ .replace("\n#endif\nstatic void on_request", "\nstatic void on_request")
309
+ .replace(
310
+ "#ifdef SB_R2_TRANSFER\n if (try_handle_r2_transfer(res, req, method)) return;\n#endif\n",
311
+ " if (try_handle_r2_transfer(res, req, method)) return;\n",
312
+ );
313
+ await writeFile(file, legacy);
314
+
315
+ await patchUwebsockets(root);
316
+ const upgraded = await readFile(file, "utf8");
317
+ expect(upgraded).toContain('#ifdef SB_R2_TRANSFER\nextern "C" {');
318
+ expect(upgraded).toContain("#endif\n __porffor_js_enter();");
319
+ } finally {
320
+ await rm(root, { recursive: true, force: true });
321
+ }
322
+ });
323
+
233
324
  test("#165: render.js routes console output to stderr, unbuffered, idempotently", async () => {
234
325
  const root = await mkdtemp(join(tmpdir(), "sb-patch-render-"));
235
326
  try {
package/src/patch.ts CHANGED
@@ -167,6 +167,14 @@ const CFLAGS_INJECT =
167
167
  " ...(process.env.SB_EXTRA_CFLAGS ? process.env.SB_EXTRA_CFLAGS.split(' ').filter(Boolean) : []),\n";
168
168
  const CFLAGS_MARKER = "SB_EXTRA_CFLAGS";
169
169
 
170
+ /** The native-fetch socket shim is a separately compiled C++ translation unit.
171
+ * It needs the same optional capability flags as the generated C object. */
172
+ const UWS_CFLAGS_ANCHOR = " '-I', `${uwsDir}/uSockets/src`,\n";
173
+ const UWS_CFLAGS_INJECT =
174
+ " // SB_UWS_EXTRA_CFLAGS\n" +
175
+ " ...(process.env.SB_EXTRA_CFLAGS ? process.env.SB_EXTRA_CFLAGS.split(' ').filter(Boolean) : []),\n";
176
+ const UWS_CFLAGS_MARKER = "SB_UWS_EXTRA_CFLAGS";
177
+
170
178
  /**
171
179
  * #56 — make the inbound request-body limit configurable.
172
180
  *
@@ -416,8 +424,10 @@ const WRITE_RESPONSE_MARKER = "porf_native_fetch_read_raw_bytes(body_value";
416
424
  // #195: reserve a direct-transfer ticket before native-fetch allocates the
417
425
  // ordinary in-memory PendingRequest. uWS then gives each wire chunk directly
418
426
  // to the runtime C ABI, which owns the temporary object file.
419
- const R2_TRANSFER_ANCHOR = "static void on_request(uWS::HttpResponse<false>* res, uWS::HttpRequest* req) {\n";
420
- const R2_TRANSFER_INJECT = `extern "C" {
427
+ const R2_TRANSFER_ANCHOR =
428
+ "static void on_request(uWS::HttpResponse<false>* res, uWS::HttpRequest* req) {\n";
429
+ const R2_TRANSFER_INJECT = `#ifdef SB_R2_TRANSFER
430
+ extern "C" {
421
431
  struct sb_r2_transfer_ctx;
422
432
  int sb_r2_transfer_open(const char*, const char*, size_t, sb_r2_transfer_ctx**);
423
433
  int sb_r2_transfer_write(sb_r2_transfer_ctx*, const char*, size_t);
@@ -532,13 +542,17 @@ static bool try_handle_r2_transfer(uWS::HttpResponse<false>* res, uWS::HttpReque
532
542
  });
533
543
  return true;
534
544
  }
545
+ #endif
535
546
  static void on_request(uWS::HttpResponse<false>* res, uWS::HttpRequest* req) {
536
547
  `;
537
548
  const R2_TRANSFER_MARKER = "sb_r2_transfer_path(std::string_view path";
538
- const R2_TRANSFER_CALL_ANCHOR = " __porffor_js_enter();\n const i32 url_ptr = alloc_request_url(req);";
549
+ const R2_TRANSFER_CALL_ANCHOR =
550
+ " __porffor_js_enter();\n const i32 url_ptr = alloc_request_url(req);";
539
551
  const R2_TRANSFER_CALL_INJECT =
540
- " if (try_handle_r2_transfer(res, req, method)) return;\n" + R2_TRANSFER_CALL_ANCHOR;
552
+ "#ifdef SB_R2_TRANSFER\n if (try_handle_r2_transfer(res, req, method)) return;\n#endif\n" +
553
+ R2_TRANSFER_CALL_ANCHOR;
541
554
  const R2_TRANSFER_CALL_MARKER = "try_handle_r2_transfer(res, req, method)";
555
+ const R2_GATE_MARKER = "#ifdef SB_R2_TRANSFER";
542
556
  const R2_DOWNLOAD_CACHE_MARKER = "sb_r2_transfer_download_v1";
543
557
  const R2_RANGE_CACHE_MARKER = "sb_r2_transfer_range_v1";
544
558
  const R2_CONDITIONAL_CACHE_MARKER = "sb_r2_transfer_conditional_v1";
@@ -552,7 +566,8 @@ const R2_DOWNLOAD_DECL_INJECT =
552
566
  " const char* sb_r2_transfer_download_etag(sb_r2_transfer_ctx*);\n" +
553
567
  " size_t sb_r2_transfer_download_read(sb_r2_transfer_ctx*, size_t, char*, size_t);\n" +
554
568
  " void sb_r2_transfer_download_close(sb_r2_transfer_ctx*);\n}";
555
- const R2_DOWNLOAD_BRANCH_ANCHOR = ' if (method != "PUT") { respond_with_error(res, "405 Method Not Allowed", "R2 transfer requires PUT", true); return true; }';
569
+ const R2_DOWNLOAD_BRANCH_ANCHOR =
570
+ ' if (method != "PUT") { respond_with_error(res, "405 Method Not Allowed", "R2 transfer requires PUT", true); return true; }';
556
571
  const R2_DOWNLOAD_BRANCH_INJECT = ` // sb_r2_transfer_download_v1
557
572
  // sb_r2_transfer_range_v1
558
573
  // sb_r2_transfer_range_header_v2
@@ -645,8 +660,18 @@ const UWS_EDITS = [
645
660
  WRITE_RESPONSE_INJECT,
646
661
  "detect x-sb-raw-body, steer the body read (#176)",
647
662
  ],
648
- [R2_TRANSFER_MARKER, R2_TRANSFER_ANCHOR, R2_TRANSFER_INJECT, "native direct R2 transfer ingress (#195)"],
649
- [R2_TRANSFER_CALL_MARKER, R2_TRANSFER_CALL_ANCHOR, R2_TRANSFER_CALL_INJECT, "direct R2 transfer dispatch (#195)"],
663
+ [
664
+ R2_TRANSFER_MARKER,
665
+ R2_TRANSFER_ANCHOR,
666
+ R2_TRANSFER_INJECT,
667
+ "native direct R2 transfer ingress (#195)",
668
+ ],
669
+ [
670
+ R2_TRANSFER_CALL_MARKER,
671
+ R2_TRANSFER_CALL_ANCHOR,
672
+ R2_TRANSFER_CALL_INJECT,
673
+ "direct R2 transfer dispatch (#195)",
674
+ ],
650
675
  ] as const;
651
676
 
652
677
  /** The uWebSockets shim source: body limit + the #156 status-line fix. Exported for tests. */
@@ -654,18 +679,41 @@ export async function patchUwebsockets(root: string): Promise<void> {
654
679
  const file = resolve(root, "compiler/uwebsockets.js");
655
680
  let src = await readFile(file, "utf8");
656
681
  let changed = false;
682
+ // Existing shared compiler caches already contain the direct-transfer patch.
683
+ // Upgrade them in place so a cache built before #202 does not keep linking
684
+ // the R2 path into every subsequent artifact.
685
+ if (src.includes(R2_TRANSFER_MARKER) && !src.includes(R2_GATE_MARKER)) {
686
+ const oldOpen = 'extern "C" {\n struct sb_r2_transfer_ctx;';
687
+ const newOpen = '#ifdef SB_R2_TRANSFER\nextern "C" {\n struct sb_r2_transfer_ctx;';
688
+ const oldClose = `}\n${R2_TRANSFER_ANCHOR}`;
689
+ const newClose = `}\n#endif\n${R2_TRANSFER_ANCHOR}`;
690
+ const oldCall =
691
+ " if (try_handle_r2_transfer(res, req, method)) return;\n" + R2_TRANSFER_CALL_ANCHOR;
692
+ if (!src.includes(oldOpen) || !src.includes(oldClose) || !src.includes(oldCall))
693
+ throw new Error(
694
+ `could not gate Porffor's direct R2 transfer bridge: anchor not found in ${file}.`,
695
+ );
696
+ src = src
697
+ .replace(oldOpen, newOpen)
698
+ .replace(oldClose, newClose)
699
+ .replace(oldCall, R2_TRANSFER_CALL_INJECT);
700
+ changed = true;
701
+ }
657
702
  // Upgrade already-patched caches from the initial upload-only bridge. New
658
703
  // caches receive the full bridge below; old ones need declarations plus the
659
704
  // GET dispatch grafted onto their existing transfer handler.
660
705
  if (src.includes(R2_TRANSFER_MARKER) && !src.includes(R2_DOWNLOAD_CACHE_MARKER)) {
661
706
  if (!src.includes(R2_DOWNLOAD_DECL_ANCHOR) || !src.includes(R2_DOWNLOAD_BRANCH_ANCHOR))
662
- throw new Error(`could not upgrade Porffor's direct R2 transfer bridge: anchor not found in ${file}.`);
707
+ throw new Error(
708
+ `could not upgrade Porffor's direct R2 transfer bridge: anchor not found in ${file}.`,
709
+ );
663
710
  src = src.replace(R2_DOWNLOAD_DECL_ANCHOR, R2_DOWNLOAD_DECL_INJECT);
664
711
  src = src.replace(R2_DOWNLOAD_BRANCH_ANCHOR, R2_DOWNLOAD_BRANCH_INJECT);
665
712
  changed = true;
666
713
  }
667
714
  if (src.includes(R2_DOWNLOAD_CACHE_MARKER) && !src.includes(R2_RANGE_CACHE_MARKER)) {
668
- const stateAnchor = " struct Download { sb_r2_transfer_ctx* ctx; uintmax_t size; bool closed = false; };\n auto state = std::make_shared<Download>(Download{download, sb_r2_transfer_download_size(download)});";
715
+ const stateAnchor =
716
+ " struct Download { sb_r2_transfer_ctx* ctx; uintmax_t size; bool closed = false; };\n auto state = std::make_shared<Download>(Download{download, sb_r2_transfer_download_size(download)});";
669
717
  const range = ` const uintmax_t full_size = sb_r2_transfer_download_size(download);
670
718
  uintmax_t first = 0, last = full_size ? full_size - 1 : 0;
671
719
  const std::string_view range = req->getHeader("range");
@@ -681,18 +729,30 @@ export async function patchUwebsockets(root: string): Promise<void> {
681
729
  res->writeStatus("206 Partial Content"); res->writeHeader("Content-Range", content_range);
682
730
  }
683
731
  `;
684
- const state = " struct Download { sb_r2_transfer_ctx* ctx; uintmax_t base; uintmax_t size; bool closed = false; };\n auto state = std::make_shared<Download>(Download{download, first, last - first + 1});";
685
- if (!src.includes(stateAnchor)) throw new Error(`could not upgrade Porffor's direct R2 range bridge: anchor not found in ${file}.`);
686
- src = src.replace(" // sb_r2_transfer_download_v1\n", " // sb_r2_transfer_download_v1\n // sb_r2_transfer_range_v1\n");
732
+ const state =
733
+ " struct Download { sb_r2_transfer_ctx* ctx; uintmax_t base; uintmax_t size; bool closed = false; };\n auto state = std::make_shared<Download>(Download{download, first, last - first + 1});";
734
+ if (!src.includes(stateAnchor))
735
+ throw new Error(
736
+ `could not upgrade Porffor's direct R2 range bridge: anchor not found in ${file}.`,
737
+ );
738
+ src = src.replace(
739
+ " // sb_r2_transfer_download_v1\n",
740
+ " // sb_r2_transfer_download_v1\n // sb_r2_transfer_range_v1\n",
741
+ );
687
742
  src = src.replace(stateAnchor, range + state);
688
- src = src.replace("sb_r2_transfer_download_read(state->ctx, (size_t)offset, chunk, sizeof(chunk))", "sb_r2_transfer_download_read(state->ctx, (size_t)(state->base + offset), chunk, sizeof(chunk))");
743
+ src = src.replace(
744
+ "sb_r2_transfer_download_read(state->ctx, (size_t)offset, chunk, sizeof(chunk))",
745
+ "sb_r2_transfer_download_read(state->ctx, (size_t)(state->base + offset), chunk, sizeof(chunk))",
746
+ );
689
747
  changed = true;
690
748
  }
691
749
  if (src.includes(R2_RANGE_CACHE_MARKER) && !src.includes(R2_CONDITIONAL_CACHE_MARKER)) {
692
750
  const declaration = " size_t sb_r2_transfer_download_size(sb_r2_transfer_ctx*);\n";
693
- const declarationReplacement = declaration + " const char* sb_r2_transfer_download_etag(sb_r2_transfer_ctx*);\n";
751
+ const declarationReplacement =
752
+ declaration + " const char* sb_r2_transfer_download_etag(sb_r2_transfer_ctx*);\n";
694
753
  const anchor = " const uintmax_t full_size = sb_r2_transfer_download_size(download);\n";
695
- const inject = ` // sb_r2_transfer_conditional_v1
754
+ const inject =
755
+ ` // sb_r2_transfer_conditional_v1
696
756
  const std::string etag = sb_r2_transfer_download_etag(download);
697
757
  res->writeHeader("ETag", etag); res->writeHeader("Accept-Ranges", "bytes");
698
758
  const std::string_view if_none_match = req->getHeader("if-none-match");
@@ -700,7 +760,10 @@ export async function patchUwebsockets(root: string): Promise<void> {
700
760
  sb_r2_transfer_download_close(download); res->writeStatus("304 Not Modified"); res->end(); return true;
701
761
  }
702
762
  ` + anchor;
703
- if (!src.includes(declaration) || !src.includes(anchor)) throw new Error(`could not upgrade Porffor's direct R2 conditional bridge: anchor not found in ${file}.`);
763
+ if (!src.includes(declaration) || !src.includes(anchor))
764
+ throw new Error(
765
+ `could not upgrade Porffor's direct R2 conditional bridge: anchor not found in ${file}.`,
766
+ );
704
767
  src = src.replace(declaration, declarationReplacement).replace(anchor, inject);
705
768
  changed = true;
706
769
  }
@@ -719,15 +782,28 @@ export async function patchUwebsockets(root: string): Promise<void> {
719
782
  if (result.second) { sb_r2_transfer_download_close(state->ctx); state->closed = true; return false; }
720
783
  if (!result.first) { res->onWritable([pump](uintmax_t) { return (*pump)(); }); return false; }
721
784
  }`;
722
- if (!src.includes(oldPump)) throw new Error(`could not upgrade Porffor's direct R2 streaming bridge: anchor not found in ${file}.`);
723
- src = src.replace(" // sb_r2_transfer_conditional_v1\n", " // sb_r2_transfer_conditional_v1\n // sb_r2_transfer_streaming_v2\n").replace(oldPump, newPump);
785
+ if (!src.includes(oldPump))
786
+ throw new Error(
787
+ `could not upgrade Porffor's direct R2 streaming bridge: anchor not found in ${file}.`,
788
+ );
789
+ src = src
790
+ .replace(
791
+ " // sb_r2_transfer_conditional_v1\n",
792
+ " // sb_r2_transfer_conditional_v1\n // sb_r2_transfer_streaming_v2\n",
793
+ )
794
+ .replace(oldPump, newPump);
724
795
  changed = true;
725
796
  }
726
797
  // A short-lived v2 cache marker was written before its pump fix. Repair
727
798
  // those caches as well as applying the current form to future upgrades.
728
- if (src.includes("state->base + offset") && src.includes("uintmax_t size; bool closed = false;")) {
729
- const oldState = "struct Download { sb_r2_transfer_ctx* ctx; uintmax_t base; uintmax_t size; bool closed = false; };";
730
- const newState = "struct Download { sb_r2_transfer_ctx* ctx; uintmax_t base; uintmax_t size; uintmax_t sent = 0; bool closed = false; };";
799
+ if (
800
+ src.includes("state->base + offset") &&
801
+ src.includes("uintmax_t size; bool closed = false;")
802
+ ) {
803
+ const oldState =
804
+ "struct Download { sb_r2_transfer_ctx* ctx; uintmax_t base; uintmax_t size; bool closed = false; };";
805
+ const newState =
806
+ "struct Download { sb_r2_transfer_ctx* ctx; uintmax_t base; uintmax_t size; uintmax_t sent = 0; bool closed = false; };";
731
807
  const oldPump = ` const uintmax_t offset = res->getWriteOffset(); char chunk[65536];
732
808
  const size_t bytes = sb_r2_transfer_download_read(state->ctx, (size_t)(state->base + offset), chunk, sizeof(chunk));
733
809
  const auto result = res->tryEnd(std::string_view(chunk, bytes), state->size);
@@ -741,7 +817,10 @@ export async function patchUwebsockets(root: string): Promise<void> {
741
817
  if (!result.first) { res->onWritable([pump, state](uintmax_t offset) { state->sent = offset; return (*pump)(); }); return false; }
742
818
  state->sent += bytes;
743
819
  }`;
744
- if (!src.includes(oldState) || !src.includes(oldPump)) throw new Error(`could not repair Porffor's direct R2 streaming bridge: anchor not found in ${file}.`);
820
+ if (!src.includes(oldState) || !src.includes(oldPump))
821
+ throw new Error(
822
+ `could not repair Porffor's direct R2 streaming bridge: anchor not found in ${file}.`,
823
+ );
745
824
  src = src.replace(oldState, newState).replace(oldPump, newPump);
746
825
  changed = true;
747
826
  }
@@ -756,37 +835,79 @@ export async function patchUwebsockets(root: string): Promise<void> {
756
835
  changed = true;
757
836
  }
758
837
  if (src.includes(R2_STREAMING_CACHE_MARKER) && !src.includes(R2_RANGE_HEADER_CACHE_MARKER)) {
759
- const rangeHeader = " const std::string_view range = req->getHeader(\"range\");\n";
760
- if (!src.includes(rangeHeader)) throw new Error(`could not upgrade Porffor's direct R2 range-header bridge: anchor not found in ${file}.`);
761
- src = src.replace(" // sb_r2_transfer_range_v1\n", " // sb_r2_transfer_range_v1\n // sb_r2_transfer_range_header_v2\n");
762
- src = src.replace(rangeHeader, " std::string_view range = req->getHeader(\"range\"); if (range.empty()) range = req->getHeader(\"Range\");\n");
838
+ const rangeHeader = ' const std::string_view range = req->getHeader("range");\n';
839
+ if (!src.includes(rangeHeader))
840
+ throw new Error(
841
+ `could not upgrade Porffor's direct R2 range-header bridge: anchor not found in ${file}.`,
842
+ );
843
+ src = src.replace(
844
+ " // sb_r2_transfer_range_v1\n",
845
+ " // sb_r2_transfer_range_v1\n // sb_r2_transfer_range_header_v2\n",
846
+ );
847
+ src = src.replace(
848
+ rangeHeader,
849
+ ' std::string_view range = req->getHeader("range"); if (range.empty()) range = req->getHeader("Range");\n',
850
+ );
763
851
  changed = true;
764
852
  }
765
- const earlyConditionalHeaders = " res->writeHeader(\"ETag\", etag); res->writeHeader(\"Accept-Ranges\", \"bytes\");\n";
766
- if (src.includes(earlyConditionalHeaders) && src.indexOf(earlyConditionalHeaders) < src.indexOf(" const uintmax_t full_size = sb_r2_transfer_download_size(download);")) {
767
- const old304 = " sb_r2_transfer_download_close(download); res->writeStatus(\"304 Not Modified\"); res->end(); return true;";
768
- const new304 = " sb_r2_transfer_download_close(download); res->writeStatus(\"304 Not Modified\"); res->writeHeader(\"ETag\", etag); res->end(); return true;";
769
- const rangeEnd = " res->writeStatus(\"206 Partial Content\"); res->writeHeader(\"Content-Range\", content_range);\n }\n";
770
- if (!src.includes(old304) || !src.includes(rangeEnd)) throw new Error(`could not repair Porffor's direct R2 response ordering: anchor not found in ${file}.`);
771
- src = src.replace(earlyConditionalHeaders, "").replace(old304, new304).replace(rangeEnd, rangeEnd + " res->writeHeader(\"ETag\", etag); res->writeHeader(\"Accept-Ranges\", \"bytes\");\n");
853
+ const earlyConditionalHeaders =
854
+ ' res->writeHeader("ETag", etag); res->writeHeader("Accept-Ranges", "bytes");\n';
855
+ if (
856
+ src.includes(earlyConditionalHeaders) &&
857
+ src.indexOf(earlyConditionalHeaders) <
858
+ src.indexOf(" const uintmax_t full_size = sb_r2_transfer_download_size(download);")
859
+ ) {
860
+ const old304 =
861
+ ' sb_r2_transfer_download_close(download); res->writeStatus("304 Not Modified"); res->end(); return true;';
862
+ const new304 =
863
+ ' sb_r2_transfer_download_close(download); res->writeStatus("304 Not Modified"); res->writeHeader("ETag", etag); res->end(); return true;';
864
+ const rangeEnd =
865
+ ' res->writeStatus("206 Partial Content"); res->writeHeader("Content-Range", content_range);\n }\n';
866
+ if (!src.includes(old304) || !src.includes(rangeEnd))
867
+ throw new Error(
868
+ `could not repair Porffor's direct R2 response ordering: anchor not found in ${file}.`,
869
+ );
870
+ src = src
871
+ .replace(earlyConditionalHeaders, "")
872
+ .replace(old304, new304)
873
+ .replace(
874
+ rangeEnd,
875
+ rangeEnd +
876
+ ' res->writeHeader("ETag", etag); res->writeHeader("Accept-Ranges", "bytes");\n',
877
+ );
772
878
  changed = true;
773
879
  }
774
880
  if (src.includes("state->sent") && src.includes(R2_DOWNLOAD_CACHE_MARKER)) {
775
881
  const stateStart = src.indexOf(" struct Download {", src.indexOf(R2_DOWNLOAD_CACHE_MARKER));
776
- const stateEnd = src.indexOf(" res->writeHeader(\"Content-Type\"", stateStart);
882
+ const stateEnd = src.indexOf(' res->writeHeader("Content-Type"', stateStart);
777
883
  const freshStart = R2_DOWNLOAD_BRANCH_INJECT.indexOf(" struct Download {");
778
- const freshEnd = R2_DOWNLOAD_BRANCH_INJECT.indexOf(" res->writeHeader(\"Content-Type\"", freshStart);
779
- if (stateStart < 0 || stateEnd < 0 || freshStart < 0 || freshEnd < 0) throw new Error(`could not replace Porffor's direct R2 stream pump: anchor not found in ${file}.`);
780
- src = src.slice(0, stateStart) + R2_DOWNLOAD_BRANCH_INJECT.slice(freshStart, freshEnd) + src.slice(stateEnd);
884
+ const freshEnd = R2_DOWNLOAD_BRANCH_INJECT.indexOf(
885
+ ' res->writeHeader("Content-Type"',
886
+ freshStart,
887
+ );
888
+ if (stateStart < 0 || stateEnd < 0 || freshStart < 0 || freshEnd < 0)
889
+ throw new Error(
890
+ `could not replace Porffor's direct R2 stream pump: anchor not found in ${file}.`,
891
+ );
892
+ src =
893
+ src.slice(0, stateStart) +
894
+ R2_DOWNLOAD_BRANCH_INJECT.slice(freshStart, freshEnd) +
895
+ src.slice(stateEnd);
781
896
  changed = true;
782
897
  }
783
898
  if (src.includes("writable_offset") && src.includes(R2_DOWNLOAD_CACHE_MARKER)) {
784
899
  const stateStart = src.indexOf(" struct Download {", src.indexOf(R2_DOWNLOAD_CACHE_MARKER));
785
- const stateEnd = src.indexOf(" res->writeHeader(\"Content-Type\"", stateStart);
900
+ const stateEnd = src.indexOf(' res->writeHeader("Content-Type"', stateStart);
786
901
  const freshStart = R2_TRANSFER_INJECT.indexOf(" struct Download {");
787
- const freshEnd = R2_TRANSFER_INJECT.indexOf(" res->writeHeader(\"Content-Type\"", freshStart);
788
- if (stateStart < 0 || stateEnd < 0 || freshStart < 0 || freshEnd < 0) throw new Error(`could not restore Porffor's direct R2 stream pump: anchor not found in ${file}.`);
789
- src = src.slice(0, stateStart) + R2_TRANSFER_INJECT.slice(freshStart, freshEnd) + src.slice(stateEnd);
902
+ const freshEnd = R2_TRANSFER_INJECT.indexOf(' res->writeHeader("Content-Type"', freshStart);
903
+ if (stateStart < 0 || stateEnd < 0 || freshStart < 0 || freshEnd < 0)
904
+ throw new Error(
905
+ `could not restore Porffor's direct R2 stream pump: anchor not found in ${file}.`,
906
+ );
907
+ src =
908
+ src.slice(0, stateStart) +
909
+ R2_TRANSFER_INJECT.slice(freshStart, freshEnd) +
910
+ src.slice(stateEnd);
790
911
  changed = true;
791
912
  }
792
913
  for (const [marker, anchor, inject, what] of UWS_EDITS) {
@@ -803,13 +924,14 @@ export async function patchUwebsockets(root: string): Promise<void> {
803
924
  if (changed) await writeFile(file, src);
804
925
  }
805
926
 
806
- async function patchCompilerArgs(root: string): Promise<void> {
927
+ export async function patchCompilerArgs(root: string): Promise<void> {
807
928
  const file = resolve(root, "compiler/index.js");
808
929
  let src = await readFile(file, "utf8");
809
930
  let changed = false;
810
931
  for (const [marker, anchor, inject, what] of [
811
932
  [LINK_MARKER, LINK_ANCHOR, LINK_INJECT, "extra link args"],
812
933
  [CFLAGS_MARKER, CFLAGS_ANCHOR, CFLAGS_INJECT, "extra compiler flags"],
934
+ [UWS_CFLAGS_MARKER, UWS_CFLAGS_ANCHOR, UWS_CFLAGS_INJECT, "native-fetch shim compiler flags"],
813
935
  ] as const) {
814
936
  if (src.includes(marker)) continue;
815
937
  const at = src.indexOf(anchor);
@@ -819,10 +941,10 @@ async function patchCompilerArgs(root: string): Promise<void> {
819
941
  "Porffor's native-fetch build changed — check patches/UPSTREAM.md.",
820
942
  );
821
943
  }
822
- // After the anchor for cflags (the args follow it), before it for the link
823
- // line (the object list ends with it).
944
+ // After the anchor for compiler flags (the args follow it), before it for
945
+ // the link line (the object list ends with it).
824
946
  src =
825
- marker === CFLAGS_MARKER
947
+ marker === CFLAGS_MARKER || marker === UWS_CFLAGS_MARKER
826
948
  ? src.slice(0, at + anchor.length) + inject + src.slice(at + anchor.length)
827
949
  : src.slice(0, at) + inject + src.slice(at);
828
950
  changed = true;