Skip to content

Commit 7164b79

Browse files
RandomBytemaxreichmann
authored andcommitted
refactor(fs): Handle legacy streams lacking Symbol.asyncIterator in Resource.setStream
Streams from older userland packages like readable-stream@2 (used by replacestream) have .pipe but no Symbol.asyncIterator, causing "stream is not async iterable" errors when the resource content is later consumed by node:stream/consumers. Also handle WHATWG TransformStream objects by unwrapping to their readable side.
1 parent 697e3be commit 7164b79

3 files changed

Lines changed: 63 additions & 8 deletions

File tree

internal/e2e-tests/test/build.js

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -183,13 +183,6 @@ describe("ui5 build", () => {
183183
await fs.writeFile(newControllerPath, newControllerContent, "utf-8");
184184

185185
// #2 Build
186-
// FIXME: Currently failing here for IB (https://github.com/UI5/cli/pull/1267), April 02 2026 - aa3a2c1c04f7a5cd27650335cde37a798baacf2a
187-
// Error message:
188-
// ("Minification failed with error: Unexpected token punc «{», expected punc «,»
189-
// in file /resources/application/a/controller/New.controller.js (line 4, col 16, pos 114)")
190-
//
191-
// -> Probably, the string replacement doesn't get executed as very first middleware
192-
// (minify happens earlier unexpectedly)
193186
await fixtureHelper.build(assert, ui5YamlName);
194187

195188
// Test: the placeholder in the source file is replaced in the dist output

packages/fs/lib/Resource.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Readable} from "node:stream";
1+
import {Readable, PassThrough} from "node:stream";
22
import {buffer as streamToBuffer} from "node:stream/consumers";
33
import ssri from "ssri";
44
import clone from "clone";
@@ -528,6 +528,15 @@ class Resource {
528528
this.#createStreamFactory = stream;
529529
this.#contentType = CONTENT_TYPES.FACTORY;
530530
} else {
531+
if (stream instanceof TransformStream) {
532+
stream = Readable.fromWeb(stream.readable);
533+
} else if (!(Symbol.asyncIterator in stream)) {
534+
// Legacy streams (e.g. from readable-stream@2) that lack Symbol.asyncIterator
535+
// need to be wrapped in a native stream to be consumed by stream/consumers
536+
const wrapper = new PassThrough();
537+
stream.pipe(wrapper);
538+
stream = wrapper;
539+
}
531540
this.#content = stream;
532541
this.#contentType = CONTENT_TYPES.STREAM;
533542
}

packages/fs/test/lib/Resource.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,59 @@ test("Resource: clone resource with stream", async (t) => {
743743
t.is(clonedResourceContent, "Content", "Cloned resource has correct content string");
744744
});
745745

746+
test("Resource: clone resource with WHATWG TransformStream", async (t) => {
747+
const ts = new TransformStream({
748+
transform(chunk, controller) {
749+
controller.enqueue(chunk);
750+
}
751+
});
752+
753+
const writer = ts.writable.getWriter();
754+
writer.write(Buffer.from("TransformStream Content"));
755+
writer.close();
756+
757+
const resource = new Resource({
758+
path: "/my/path/to/resource"
759+
});
760+
resource.setStream(ts);
761+
762+
const clonedResource = await resource.clone();
763+
t.pass("Resource cloned");
764+
765+
const clonedResourceContent = await clonedResource.getString();
766+
t.is(clonedResourceContent, "TransformStream Content", "Cloned resource has correct content string");
767+
});
768+
769+
test("Resource: clone resource with legacy stream lacking Symbol.asyncIterator", async (t) => {
770+
// Simulate a stream from readable-stream@2 (e.g. replacestream) which has .pipe but no Symbol.asyncIterator
771+
const stream = new Stream.Transform({
772+
transform(chunk, enc, cb) {
773+
cb(null, chunk.toString().replace("old", "new"));
774+
}
775+
});
776+
777+
// Remove Symbol.asyncIterator to simulate legacy readable-stream@2 behavior
778+
stream[Symbol.asyncIterator] = undefined;
779+
delete stream[Symbol.asyncIterator];
780+
781+
const input = new Stream.Readable();
782+
input._read = function() {};
783+
input.push("old content");
784+
input.push(null);
785+
input.pipe(stream);
786+
787+
const resource = new Resource({
788+
path: "/my/path/to/resource"
789+
});
790+
resource.setStream(stream);
791+
792+
const clonedResource = await resource.clone();
793+
t.pass("Resource cloned");
794+
795+
const clonedResourceContent = await clonedResource.getString();
796+
t.is(clonedResourceContent, "new content", "Cloned resource has correct content string");
797+
});
798+
746799
test("Resource: clone resource with createBuffer factory", async (t) => {
747800
const resource = new Resource({
748801
path: "/my/path/to/resource",

0 commit comments

Comments
 (0)