Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

mock runtime: node-parity gaps left open by #1494 (Buffer/fs shim)

Aperta
#1,495 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
3/5
Tempo stimato
Mezza giornata
Idoneità per principianti
68/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
node.js, typescript
Ambito
testing, tooling

Direzione di ricerca

Start with src/commands/build/internal/mockSite/assets/runtime-core.js and the existing mock runtime specs. For gap 1, check the Buffer.from(arrayBuffer) branch and add a spec that mutates the Buffer and reads the ArrayBuffer; for gap 2, trace binary _SASJS_WEBOUT_HEADERS handling and add a Buffer-written header spec. Done when both cases match the server behavior and the specs pass.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Context

#1494 (merged as 0cc25294) made a mocked streaming service produce a real download: _SASJS_WEBOUT_HEADERS is applied, a byte body is served as bytes, and a project-supplied archive, image or xlsx is seeded onto the browser drive.

Reviewing it turned into a long audit of the node shim underneath (Buffer, fs, sw.js header handling), because that shim is what makes a browser-hosted mock behave like the server. Most findings were fixed in the PR. This issue records what was deliberately left out, one finding raised at the final head, and the deviations that are not defects - so they are not re-filed and can be sequenced on their own merit.

Gaps to close

1. Buffer.from(arrayBuffer) copies instead of sharing memory

src/commands/build/internal/mockSite/assets/runtime-core.js:493 returns asBuffer(new Uint8Array(value, start, end - start)), and asBuffer (:409) does decorate(new Uint8Array(bytes)), which copies. Node returns a view over that ArrayBuffer for this overload, and copies only for a Uint8Array input - the split the shim already honours for slice() and subarray().

const ab = new Uint8Array([1, 2, 3, 4]).buffer
const b = Buffer.from(ab)
b[0] = 9
new Uint8Array(ab)[0]   // node: 9        mock: 1

Impact. Silent wrong bytes. A service that builds a body from an ArrayBuffer and then patches it in place - a zip end-of-central-directory record, a length prefix fixed up after the fact, a checksum written back - serves the unpatched buffer from the mock and the patched one from the server, with HTTP 200 either way. Nothing in the mock's contract reports it, and the failure surfaces only as a corrupt download the user has to open.

Reachability. Low: most services mutate the Buffer they built, which behaves identically in both hosts. Reached only by code that keeps the ArrayBuffer and mutates it after Buffer.from.

Fix. Route the ArrayBuffer branch through decorate instead of asBuffer; keep asBuffer for Uint8Array and other inputs, which node copies. One line plus a spec that mutates through the Buffer and reads the ArrayBuffer back.

2. A binary _SASJS_WEBOUT_HEADERS drops every declared header

runtime-core.js:1004 reads the headers reply with headers: entryText(FILES.get(headersPath) || ''), which only removes the text escape. A service that writes the headers file as bytes - fs.writeFileSync(_SASJS_WEBOUT_HEADERS, Buffer.from('Content-type: text/csv\n...')), or a string with a non-UTF-8 encoding - leaves a byte entry on the drive, so headers becomes the raw \u0000base64:... string. parseHeaders (sw.js:84) then splits on newlines, finds a colon only inside the marker, matches no allowed header name, and drops everything.

Impact. Silent loss of the whole channel: the reply keeps its default text/plain and no Content-Disposition, so a download either does not happen or is saved under the wrong name. No error, no warning. This is the channel that decides a download's name and type, so when it is hit the feature the PR added does nothing at all.

Reachability. Low: writing headers as bytes is unusual, though valid on the server (where the file is read as text regardless of how it was written).

Fix. Decode the headers entry the way the body is decoded - isBinary(contents) ? utf8 decode of the entry : entryText(contents) - so the two channels cannot diverge, with a spec that writes the headers as a Buffer.

3. The seed marker format is two copies of the same constants

runtime-core.js:95 (BINARY_PREFIX = '\u0000base64:', TEXT_PREFIX = '\u0000text:') and mockSite.ts:78 (DRIVE_BINARY_PREFIX, DRIVE_TEXT_PREFIX) hold the same storage format independently. Nothing fails at the point of the edit if one side changes.

Impact. Latent, and quiet when it bites: the build writes a marker the runtime no longer recognises, so a seeded byte file reads as text (a large mojibake string, served or written back by a service) or a runtime-written entry fails to decode in the worker (500 per entry). Today the build-side output is read back through the runtime by a spec (mockSite.spec.ts, seeds a project file the runtime reads back as its bytes), so a one-sided edit that changes decoding fails CI - but only for the seeded byte path, and only because a test happens to exercise both sides.

Fix. One shared module for the format, or a version/format field in vfs-seed.json that the runtime checks on load.

Deviations that stay (no change proposed)

  • fs.readFileSync(p) returns a string for a text entry and a Buffer for a byte entry. Node always returns a Buffer. The drive's convention is deliberate - a service reading a config or a CSV gets text, as it does through the server's own text paths - and it is the documented contract in runtime-core.js. A service that calls .length, .toString('base64') or other Buffer methods on a text file without an encoding gets a character count (accented characters count 1, not 2) instead of a byte count. Worth stating in the mock documentation; changing it would be a breaking behaviour change for existing mocks, so it belongs to a future major if it is wanted.
  • Buffer methods the shim does not carry (write, equals, compare, indexOf, includes, copy, fill, the read*/write* accessors, swap*) throw Buffer.<name> is not implemented in the browser host. Loud and immediate, by design: better a named gap than x.write is not a function. Implement equals/indexOf/copy/read* if a service is found that needs them.

Not defects - do not re-file

  • Buffer.alloc(size, fill) and Buffer.from([...]) mask rather than reject out-of-range values. Verified on node v18.20.8, v22.23.4 and v26.5.1: Buffer.alloc(4, 300) is 2c2c2c2c, Buffer.from([256]) is 00, Buffer.from([1.5]) is 01. The shim matches; adding a RangeError would create a divergence.
  • Header values containing 0x7F or 0x80-0x9F are refused rather than applied. The browser's Headers implementation strips those bytes from a value silently (new Response('x', { headers: { 'content-disposition': 'a\u0080b' } }) comes back as "ab"), so applying the header would save a file under a name with characters missing. The value is dropped and the reply keeps its default delivery instead.
  • A repeated header line takes the last value, and a value that fails validation removes the header entirely. That is what the server does in api/src/utils/extractHeaders.ts.

Impact summary

# Item Observable effect Failure mode Severity
1 ArrayBuffer copy patched bytes missing from the served body silent medium-low
2 Binary headers file download typed and named by default; no declared headers silent medium
3 Marker constants duplicated seeded bytes read as text, or a 500 per entry silent (guarded by one spec) low
4 readFileSync text-as-text character count instead of byte count on text silent, documented low
5 Missing Buffer methods named throw at the call site loud low

Suggested order

  1. #1 and #2 are both small, single-file changes with a spec each; neither needs a design decision.
  2. #3 needs a decision (shared module vs seed format version) and can follow.
  3. #4 and #5 are documentation until there is evidence of a service that needs them.
Lingua principale
TypeScript
Stelle
40
Fork
6
Merge medio
11h 23m
PR unite (30g)
15

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di sasjs/cli

Tutte le issue di sasjs/cli

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.