From 3e834293d47ab2761a7aa013916e8371871efb7f Mon Sep 17 00:00:00 2001
From: Erika <3019731+Princesseuh@users.noreply.github.com>
Date: Fri, 18 Aug 2023 15:13:35 +0200
Subject: [PATCH] feat: add polyfills for stackblitz (#8130)

* feat: add polyfills for Stackblitz

* chore: changeset
---
 .changeset/thin-ants-repeat.md      |  6 +++
 packages/astro/astro.js             |  6 ++-
 packages/astro/package.json         |  3 +-
 packages/astro/src/core/polyfill.ts | 57 +++++++++++++++++++++++++++++
 packages/telemetry/package.json     |  3 +-
 packages/telemetry/src/post.ts      |  1 +
 pnpm-lock.yaml                      | 16 ++++++--
 7 files changed, 86 insertions(+), 6 deletions(-)
 create mode 100644 .changeset/thin-ants-repeat.md

diff --git a/.changeset/thin-ants-repeat.md b/.changeset/thin-ants-repeat.md
new file mode 100644
index 0000000000..8a896b268f
--- /dev/null
+++ b/.changeset/thin-ants-repeat.md
@@ -0,0 +1,6 @@
+---
+'@astrojs/telemetry': patch
+'astro': patch
+---
+
+Add some polyfills for Stackblitz until they support Node 18. Running Astro on Node 16 is still not officially supported, however.
diff --git a/packages/astro/astro.js b/packages/astro/astro.js
index 631a7b9c8e..ef5349854a 100755
--- a/packages/astro/astro.js
+++ b/packages/astro/astro.js
@@ -16,11 +16,15 @@ const CI_INSTRUCTIONS = {
 const engines = '>=18.14.1';
 const skipSemverCheckIfAbove = 19;
 
+// HACK (2023-08-18) Stackblitz does not support Node 18 yet, so we'll fake Node 16 support for some time until it's supported
+// TODO: Remove when Node 18 is supported on Stackblitz
+const isStackblitz = process.env.SHELL === '/bin/jsh' && process.versions.webcontainer != null;
+
 /** `astro *` */
 async function main() {
 	const version = process.versions.node;
 	// Fast-path for higher Node.js versions
-	if ((parseInt(version) || 0) <= skipSemverCheckIfAbove) {
+	if (!isStackblitz && (parseInt(version) || 0) <= skipSemverCheckIfAbove) {
 		try {
 			const semver = await import('semver');
 			if (!semver.satisfies(version, engines)) {
diff --git a/packages/astro/package.json b/packages/astro/package.json
index caf49c64ea..d8fd5aada7 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -147,8 +147,8 @@
     "fast-glob": "^3.2.12",
     "github-slugger": "^2.0.0",
     "gray-matter": "^4.0.3",
-    "http-cache-semantics": "^4.1.1",
     "html-escaper": "^3.0.3",
+    "http-cache-semantics": "^4.1.1",
     "js-yaml": "^4.1.0",
     "kleur": "^4.1.4",
     "magic-string": "^0.30.2",
@@ -166,6 +166,7 @@
     "string-width": "^5.1.2",
     "strip-ansi": "^7.1.0",
     "tsconfig-resolver": "^3.0.1",
+    "undici": "^5.23.0",
     "unist-util-visit": "^4.1.2",
     "vfile": "^5.3.7",
     "vite": "^4.4.6",
diff --git a/packages/astro/src/core/polyfill.ts b/packages/astro/src/core/polyfill.ts
index daceb53e22..9c0a3b1a2d 100644
--- a/packages/astro/src/core/polyfill.ts
+++ b/packages/astro/src/core/polyfill.ts
@@ -1,10 +1,67 @@
 import { File } from 'node:buffer';
 import crypto from 'node:crypto';
+import {
+	ByteLengthQueuingStrategy,
+	CountQueuingStrategy,
+	ReadableByteStreamController,
+	ReadableStream,
+	ReadableStreamBYOBReader,
+	ReadableStreamBYOBRequest,
+	ReadableStreamDefaultController,
+	ReadableStreamDefaultReader,
+	TransformStream,
+	WritableStream,
+	WritableStreamDefaultController,
+	WritableStreamDefaultWriter,
+} from 'node:stream/web';
+import { FormData, Headers, Request, Response, fetch, File as undiciFile } from 'undici';
 
 // NOTE: This file does not intend to polyfill everything that exists, its main goal is to make life easier
 // for users deploying to runtime that do support these features. In the future, we hope for this file to disappear.
 
+// HACK (2023-08-18) Stackblitz does not support Node 18 yet, so we'll fake Node 16 support for some time until it's supported
+// TODO: Remove when Node 18 is supported on Stackblitz
+const isStackblitz = process.env.SHELL === '/bin/jsh' && process.versions.webcontainer != null;
+
 export function apply() {
+	if (isStackblitz) {
+		const neededPolyfills = [
+			ByteLengthQueuingStrategy,
+			CountQueuingStrategy,
+			ReadableByteStreamController,
+			ReadableStream,
+			ReadableStreamBYOBReader,
+			ReadableStreamBYOBRequest,
+			ReadableStreamDefaultController,
+			ReadableStreamDefaultReader,
+			TransformStream,
+			WritableStream,
+			WritableStreamDefaultController,
+			WritableStreamDefaultWriter,
+			undiciFile,
+			FormData,
+			Headers,
+			Request,
+			Response,
+			fetch,
+		];
+
+		for (let polyfillName of Object.keys(neededPolyfills)) {
+			if (Object.hasOwnProperty.call(globalThis, polyfillName)) continue;
+
+			// Add polyfill to globalThis
+			Object.defineProperty(globalThis, polyfillName, {
+				configurable: true,
+				enumerable: true,
+				writable: true,
+				value:
+					neededPolyfills[
+						(polyfillName === 'undiciFile' ? 'File' : polyfillName) as keyof typeof neededPolyfills
+					],
+			});
+		}
+	}
+
 	// Remove when Node 18 is dropped for Node 20
 	if (!globalThis.crypto) {
 		Object.defineProperty(globalThis, 'crypto', {
diff --git a/packages/telemetry/package.json b/packages/telemetry/package.json
index 6e54d1d374..25d79a506d 100644
--- a/packages/telemetry/package.json
+++ b/packages/telemetry/package.json
@@ -35,7 +35,8 @@
     "dset": "^3.1.2",
     "is-docker": "^3.0.0",
     "is-wsl": "^2.2.0",
-    "which-pm-runs": "^1.1.0"
+    "which-pm-runs": "^1.1.0",
+    "undici": "^5.23.0"
   },
   "devDependencies": {
     "@types/debug": "^4.1.8",
diff --git a/packages/telemetry/src/post.ts b/packages/telemetry/src/post.ts
index 6aef03bc91..1c1bd83b25 100644
--- a/packages/telemetry/src/post.ts
+++ b/packages/telemetry/src/post.ts
@@ -1,4 +1,5 @@
 const ASTRO_TELEMETRY_ENDPOINT = `https://telemetry.astro.build/api/v1/record`;
+import { fetch } from 'undici';
 
 export function post(body: Record<string, any>): Promise<any> {
 	return fetch(ASTRO_TELEMETRY_ENDPOINT, {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3b79ac9a71..e8a4a530ee 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -623,6 +623,9 @@ importers:
       tsconfig-resolver:
         specifier: ^3.0.1
         version: 3.0.1
+      undici:
+        specifier: ^5.23.0
+        version: 5.23.0
       unist-util-visit:
         specifier: ^4.1.2
         version: 4.1.2
@@ -4991,6 +4994,9 @@ importers:
       is-wsl:
         specifier: ^2.2.0
         version: 2.2.0
+      undici:
+        specifier: ^5.23.0
+        version: 5.23.0
       which-pm-runs:
         specifier: ^1.1.0
         version: 1.1.0
@@ -9859,7 +9865,6 @@ packages:
     engines: {node: '>=10.16.0'}
     dependencies:
       streamsearch: 1.1.0
-    dev: true
 
   /bytes@3.1.2:
     resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
@@ -13795,7 +13800,7 @@ packages:
       set-cookie-parser: 2.6.0
       source-map-support: 0.5.21
       stoppable: 1.1.0
-      undici: 5.22.1
+      undici: 5.23.0
       workerd: 1.20230814.1
       ws: 8.13.0
       youch: 3.2.3
@@ -16189,7 +16194,6 @@ packages:
   /streamsearch@1.1.0:
     resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
     engines: {node: '>=10.0.0'}
-    dev: true
 
   /string-width@4.2.3:
     resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
@@ -16899,6 +16903,12 @@ packages:
       busboy: 1.6.0
     dev: true
 
+  /undici@5.23.0:
+    resolution: {integrity: sha512-1D7w+fvRsqlQ9GscLBwcAJinqcZGHUKjbOmXdlE/v8BvEGXjeWAax+341q44EuTcHXXnfyKNbKRq4Lg7OzhMmg==}
+    engines: {node: '>=14.0'}
+    dependencies:
+      busboy: 1.6.0
+
   /unherit@3.0.1:
     resolution: {integrity: sha512-akOOQ/Yln8a2sgcLj4U0Jmx0R5jpIg2IUyRrWOzmEbjBtGzBdHtSeFKgoEcoH4KYIG/Pb8GQ/BwtYm0GCq1Sqg==}
     dev: false