From b0bab6eb20921d1738fe9096b11a854051a3bc54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Fri, 13 Dec 2024 14:10:32 +0000 Subject: [PATCH 01/33] feat: deploy edge functions as tarballs --- package-lock.json | 184 ++++++++++++- packages/edge-bundler/node/bridge.ts | 14 +- packages/edge-bundler/node/bundle.ts | 1 + packages/edge-bundler/node/bundler.test.ts | 63 ++++- packages/edge-bundler/node/bundler.ts | 50 +++- packages/edge-bundler/node/config.ts | 2 + packages/edge-bundler/node/feature_flags.ts | 4 +- packages/edge-bundler/node/formats/eszip.ts | 2 +- packages/edge-bundler/node/formats/tarball.ts | 255 ++++++++++++++++++ packages/edge-bundler/node/import_map.ts | 23 ++ packages/edge-bundler/node/server/server.ts | 4 +- packages/edge-bundler/node/utils/sha256.ts | 42 ++- packages/edge-bundler/package.json | 4 +- .../imports_npm_module/functions/func1.ts | 10 +- .../functions/{lib => helpers}/util.ts | 0 .../imports_npm_module/import_map.json | 3 +- .../node_modules/grandchild-1/index.js | 4 +- .../node_modules/parent-4/index.js | 4 + .../node_modules/parent-4/package.json | 5 + .../fixtures/imports_npm_module/package.json | 6 +- packages/edge-bundler/test/util.ts | 83 +++++- 21 files changed, 694 insertions(+), 69 deletions(-) create mode 100644 packages/edge-bundler/node/formats/tarball.ts rename packages/edge-bundler/test/fixtures/imports_npm_module/functions/{lib => helpers}/util.ts (100%) create mode 100644 packages/edge-bundler/test/fixtures/imports_npm_module/node_modules/parent-4/index.js create mode 100644 packages/edge-bundler/test/fixtures/imports_npm_module/node_modules/parent-4/package.json diff --git a/package-lock.json b/package-lock.json index 0f6dd25ab8..05d0ccdce4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1769,6 +1769,25 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@isaacs/fs-minipass/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/@isaacs/string-locale-compare": { "version": "1.1.0", "dev": true, @@ -11834,7 +11853,8 @@ }, "node_modules/common-path-prefix": { "version": "3.0.0", - "license": "ISC" + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==" }, "node_modules/commondir": { "version": "1.0.1", @@ -21669,6 +21689,11 @@ "node": ">=8" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" + }, "node_modules/pacote": { "version": "13.6.2", "dev": true, @@ -21841,15 +21866,15 @@ "license": "MIT" }, "node_modules/path-scurry": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", - "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -26801,13 +26826,14 @@ "p-wait-for": "^4.1.0", "path-key": "^4.0.0", "semver": "^7.3.8", + "tar": "^7.4.3", "tmp-promise": "^3.0.3", "urlpattern-polyfill": "8.0.2", "uuid": "^9.0.0" }, "devDependencies": { "@types/glob-to-regexp": "^0.4.1", - "@types/node": "^14.18.32", + "@types/node": "^16.18.122", "@types/semver": "^7.3.9", "@types/uuid": "^9.0.0", "@vitest/coverage-v8": "^0.34.0", @@ -26816,7 +26842,6 @@ "cpy": "^9.0.1", "cross-env": "^7.0.3", "nock": "^13.2.4", - "tar": "^6.1.11", "typescript": "^5.0.0", "vitest": "^0.34.0" }, @@ -26824,6 +26849,12 @@ "node": "^14.16.0 || >=16.0.0" } }, + "packages/edge-bundler/node_modules/@types/node": { + "version": "16.18.122", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.122.tgz", + "integrity": "sha512-rF6rUBS80n4oK16EW8nE75U+9fw0SSUgoPtWSvHhPXdT7itbvmS7UjB/jyM8i3AkvI6yeSM5qCwo+xN0npGDHg==", + "dev": true + }, "packages/edge-bundler/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -26909,6 +26940,14 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "packages/edge-bundler/node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "engines": { + "node": ">=18" + } + }, "packages/edge-bundler/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -26971,6 +27010,32 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "packages/edge-bundler/node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "packages/edge-bundler/node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "packages/edge-bundler/node_modules/human-signals": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz", @@ -26996,6 +27061,54 @@ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true }, + "packages/edge-bundler/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "packages/edge-bundler/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "packages/edge-bundler/node_modules/minizlib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.1.tgz", + "integrity": "sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==", + "dependencies": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + }, + "engines": { + "node": ">= 18" + } + }, + "packages/edge-bundler/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "packages/edge-bundler/node_modules/npm-run-path": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", @@ -27024,6 +27137,39 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "packages/edge-bundler/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "packages/edge-bundler/node_modules/rimraf/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "packages/edge-bundler/node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -27062,6 +27208,22 @@ "node": ">=8" } }, + "packages/edge-bundler/node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "packages/edge-bundler/node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", @@ -27078,6 +27240,14 @@ "node": ">=6" } }, + "packages/edge-bundler/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "engines": { + "node": ">=18" + } + }, "packages/edge-bundler/node_modules/zip-stream": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", diff --git a/packages/edge-bundler/node/bridge.ts b/packages/edge-bundler/node/bridge.ts index a597bd3275..34567022d6 100644 --- a/packages/edge-bundler/node/bridge.ts +++ b/packages/edge-bundler/node/bridge.ts @@ -13,10 +13,11 @@ import { getBinaryExtension } from './platform.js' const DENO_VERSION_FILE = 'version.txt' -// When updating DENO_VERSION_RANGE, ensure that the deno version -// on the netlify/buildbot build image satisfies this range! -// https://github.com/netlify/buildbot/blob/f9c03c9dcb091d6570e9d0778381560d469e78ad/build-image/noble/Dockerfile#L410 -const DENO_VERSION_RANGE = '1.39.0 - 1.46.3' +// When updating DENO_VERSION_RANGE, ensure that the deno version installed in the +// build-image/buildbot does satisfy this range! +// We're pinning the range because of an issue with v1.45.0 of the Deno CLI: +// https://linear.app/netlify/issue/FRP-775/deno-cli-v1450-causing-issues +const DENO_VERSION_RANGE = '^2.1.4' type OnBeforeDownloadHook = () => void | Promise type OnAfterDownloadHook = (error?: Error) => void | Promise @@ -37,6 +38,7 @@ interface ProcessRef { } interface RunOptions { + cwd?: string env?: NodeJS.ProcessEnv extendEnv?: boolean pipeOutput?: boolean @@ -241,11 +243,11 @@ class DenoBridge { // process, awaiting its execution. async run( args: string[], - { env: inputEnv, extendEnv = true, rejectOnExitCode = true, stderr, stdout }: RunOptions = {}, + { cwd, env: inputEnv, extendEnv = true, rejectOnExitCode = true, stderr, stdout }: RunOptions = {}, ) { const { path: binaryPath } = await this.getBinaryPath() const env = this.getEnvironmentVariables(inputEnv) - const options: Options = { env, extendEnv, reject: rejectOnExitCode } + const options: Options = { cwd, env, extendEnv, reject: rejectOnExitCode } return DenoBridge.runWithBinary(binaryPath, args, { options, stderr, stdout }) } diff --git a/packages/edge-bundler/node/bundle.ts b/packages/edge-bundler/node/bundle.ts index 522130a704..9f460bebd5 100644 --- a/packages/edge-bundler/node/bundle.ts +++ b/packages/edge-bundler/node/bundle.ts @@ -1,6 +1,7 @@ export enum BundleFormat { ESZIP2 = 'eszip2', JS = 'js', + TARBALL = 'tar', } export interface Bundle { diff --git a/packages/edge-bundler/node/bundler.test.ts b/packages/edge-bundler/node/bundler.test.ts index b1e23a57fa..96855b522e 100644 --- a/packages/edge-bundler/node/bundler.test.ts +++ b/packages/edge-bundler/node/bundler.test.ts @@ -5,10 +5,10 @@ import process from 'process' import { pathToFileURL } from 'url' import tmp from 'tmp-promise' -import { test, expect, vi } from 'vitest' +import { test, expect, vi, describe } from 'vitest' import { importMapSpecifier } from '../shared/consts.js' -import { runESZIP, useFixture } from '../test/util.js' +import { runESZIP, runTarball, useFixture } from '../test/util.js' import { BundleError } from './bundle_error.js' import { bundle, BundleOptions } from './bundler.js' @@ -105,15 +105,9 @@ test('Adds a custom error property to user errors during bundling', async () => } catch (error) { expect(error).toBeInstanceOf(BundleError) const [messageBeforeStack] = (error as BundleError).message.split('at (file://') - expect(messageBeforeStack).toMatchInlineSnapshot(` - "error: Uncaught (in promise) Error: The module's source code could not be parsed: Unexpected eof at file:///root/functions/func1.ts:1:27 - - export default async () => - ~ - const ret = new Error(getStringFromWasm0(arg0, arg1)); - ^ - " - `) + expect(messageBeforeStack).toContain( + `The module's source code could not be parsed: Unexpected eof at file:///root/functions/func1.ts:1:27`, + ) expect((error as BundleError).customErrorInfo).toEqual({ location: { format: 'eszip', @@ -502,7 +496,7 @@ test('Loads npm modules from bare specifiers', async () => { const { func1 } = await runESZIP(bundlePath, vendorDirectory.path) expect(func1).toBe( - `JavaScript, APIs${process.cwd()}, Markup${process.cwd()}`, + `JavaScript, APIs${process.platform}, Markup${process.platform}, TmV0bGlmeQ==`, ) await cleanup() @@ -641,3 +635,48 @@ test('Loads edge functions from the Frameworks API', async () => { await cleanup() }) + +describe('Produces a tarball bundle', () => { + test('Using npm modules', async () => { + const systemLogger = vi.fn() + const { basePath, cleanup, distPath } = await useFixture('imports_npm_module', { copyDirectory: true }) + const sourceDirectory = join(basePath, 'functions') + const declarations: Declaration[] = [ + { + function: 'func1', + path: '/func1', + }, + ] + const vendorDirectory = await tmp.dir() + + await bundle([sourceDirectory], distPath, declarations, { + basePath, + featureFlags: { + edge_bundler_generate_tarball: true, + }, + importMapPaths: [join(basePath, 'import_map.json')], + vendorDirectory: vendorDirectory.path, + systemLogger, + }) + + expect( + systemLogger.mock.calls.find((call) => call[0] === 'Could not track dependencies in edge function:'), + ).toBeUndefined() + + const expectedOutput = `JavaScript, APIs${process.platform}, Markup${process.platform}, TmV0bGlmeQ==` + + const manifestFile = await readFile(resolve(distPath, 'manifest.json'), 'utf8') + const manifest = JSON.parse(manifestFile) + + const tarballPath = join(distPath, manifest.bundles[0].asset) + const tarballResult = await runTarball(tarballPath) + expect(tarballResult.func1).toBe(expectedOutput) + + const eszipPath = join(distPath, manifest.bundles[1].asset) + const eszipResult = await runESZIP(eszipPath, vendorDirectory.path) + expect(eszipResult.func1).toBe(expectedOutput) + + await cleanup() + await rm(vendorDirectory.path, { force: true, recursive: true }) + }) +}, 10_000) diff --git a/packages/edge-bundler/node/bundler.ts b/packages/edge-bundler/node/bundler.ts index 4c001a4e62..cec14c3183 100644 --- a/packages/edge-bundler/node/bundler.ts +++ b/packages/edge-bundler/node/bundler.ts @@ -15,6 +15,7 @@ import { EdgeFunction } from './edge_function.js' import { FeatureFlags, getFlags } from './feature_flags.js' import { findFunctions } from './finder.js' import { bundle as bundleESZIP } from './formats/eszip.js' +import { bundle as bundleTarball } from './formats/tarball.js' import { ImportMap } from './import_map.js' import { getLogger, LogFunction, Logger } from './logger.js' import { writeManifest } from './manifest.js' @@ -114,27 +115,48 @@ export const bundle = async ( vendorDirectory, }) + const functionBundles: Bundle[] = [] + + if (featureFlags.edge_bundler_generate_tarball) { + functionBundles.push( + await bundleTarball({ + basePath, + buildID, + debug, + deno, + distDirectory, + externals, + functions, + featureFlags, + importMap: importMap.clone(), + vendorDirectory: vendor?.directory, + }), + ) + } + if (vendor) { importMap.add(vendor.importMap) } - const functionBundle = await bundleESZIP({ - basePath, - buildID, - debug, - deno, - distDirectory, - externals, - functions, - featureFlags, - importMap, - vendorDirectory: vendor?.directory, - }) + functionBundles.push( + await bundleESZIP({ + basePath, + buildID, + debug, + deno, + distDirectory, + externals, + functions, + featureFlags, + importMap, + vendorDirectory: vendor?.directory, + }), + ) // The final file name of the bundles contains a SHA256 hash of the contents, // which we can only compute now that the files have been generated. So let's // rename the bundles to their permanent names. - await createFinalBundles([functionBundle], distDirectory, buildID) + await createFinalBundles(functionBundles, distDirectory, buildID) // Retrieving a configuration object for each function. // Run `getFunctionConfig` in parallel as it is a non-trivial operation and spins up deno @@ -165,7 +187,7 @@ export const bundle = async ( }) const manifest = await writeManifest({ - bundles: [functionBundle], + bundles: functionBundles, declarations, distDirectory, featureFlags, diff --git a/packages/edge-bundler/node/config.ts b/packages/edge-bundler/node/config.ts index 40b865b299..02164baff5 100644 --- a/packages/edge-bundler/node/config.ts +++ b/packages/edge-bundler/node/config.ts @@ -99,12 +99,14 @@ export const getFunctionConfig = async ({ [ 'run', '--allow-env', + '--allow-import', '--allow-net', '--allow-read', `--allow-write=${collector.path}`, '--quiet', `--import-map=${importMap.toDataURL()}`, '--no-config', + '--no-lock', '--node-modules-dir=false', extractorPath, pathToFileURL(func.path).href, diff --git a/packages/edge-bundler/node/feature_flags.ts b/packages/edge-bundler/node/feature_flags.ts index bbb1af7173..eb27ce1528 100644 --- a/packages/edge-bundler/node/feature_flags.ts +++ b/packages/edge-bundler/node/feature_flags.ts @@ -1,4 +1,6 @@ -const defaultFlags = {} +const defaultFlags = { + edge_bundler_generate_tarball: false, +} type FeatureFlag = keyof typeof defaultFlags type FeatureFlags = Partial> diff --git a/packages/edge-bundler/node/formats/eszip.ts b/packages/edge-bundler/node/formats/eszip.ts index 432c07ff36..09df0229de 100644 --- a/packages/edge-bundler/node/formats/eszip.ts +++ b/packages/edge-bundler/node/formats/eszip.ts @@ -57,7 +57,7 @@ const bundleESZIP = async ({ importMapData, vendorDirectory, } - const flags = ['--allow-all', '--no-config', `--import-map=${bundlerImportMap}`] + const flags = ['--allow-all', '--no-config', '--no-lock', `--import-map=${bundlerImportMap}`] if (!debug) { flags.push('--quiet') diff --git a/packages/edge-bundler/node/formats/tarball.ts b/packages/edge-bundler/node/formats/tarball.ts new file mode 100644 index 0000000000..296e782e2e --- /dev/null +++ b/packages/edge-bundler/node/formats/tarball.ts @@ -0,0 +1,255 @@ +import fs from 'node:fs/promises' +import path from 'node:path' +import { fileURLToPath, pathToFileURL } from 'node:url' + +import { resolve as importMapResolve } from '@import-maps/resolve' +import { nodeFileTrace, resolve as nftResolve } from '@vercel/nft' +import commonPathPrefix from 'common-path-prefix' +import { build } from 'esbuild' +import * as tar from 'tar' +import tmp from 'tmp-promise' + +import { DenoBridge } from '../bridge.js' +import { Bundle, BundleFormat } from '../bundle.js' +import { EdgeFunction } from '../edge_function.js' +import { FeatureFlags } from '../feature_flags.js' +import { ImportMap } from '../import_map.js' +import { getStringHash, readFileAndHash } from '../utils/sha256.js' +import { ModuleGraphJson } from '../vendor/module_graph/module_graph.js' + +const TARBALL_EXTENSION = '.tar' +const TARBALL_SRC_DIRECTORY = 'src' +const TYPESCRIPT_EXTENSIONS = new Set(['.ts', '.tsx', '.cts', '.ctsx', '.mts', '.mtsx']) + +interface Manifest { + functions: Record + version: number +} + +interface DenoInfoOptions { + basePath: string + deno: DenoBridge + denoDir: string + entrypoints: string[] + importMap: ImportMap +} + +const getDenoInfo = async ({ basePath, deno, denoDir, entrypoints, importMap }: DenoInfoOptions) => { + const { stdout } = await deno.run( + ['info', '--no-lock', '--import-map', importMap.toDataURL(), '--json', ...entrypoints], + { + cwd: basePath, + env: { + DENO_DIR: denoDir, + }, + }, + ) + + return JSON.parse(stdout) as ModuleGraphJson +} + +interface BundleTarballOptions { + basePath: string + buildID: string + debug?: boolean + deno: DenoBridge + distDirectory: string + featureFlags: FeatureFlags + functions: EdgeFunction[] + importMap: ImportMap + vendorDirectory?: string +} + +const resolveHTTPSSpecifier = (moduleGraph: ModuleGraphJson, specifier: string) => { + for (const mod of moduleGraph.modules) { + if (mod.specifier === specifier && mod.local) { + return { + localPath: mod.local, + isTypeScript: TYPESCRIPT_EXTENSIONS.has(path.extname(specifier)), + } + } + } +} + +const getUnixPath = (input: string) => input.split(path.sep).join('/') + +export const bundle = async ({ + basePath, + buildID, + deno, + distDirectory, + functions, + importMap, + vendorDirectory, +}: BundleTarballOptions): Promise => { + const hashes = new Map() + const sideFilesDir = await tmp.dir({ unsafeCleanup: true }) + const cleanup = [sideFilesDir.cleanup] + + let denoDir = vendorDirectory ? path.join(vendorDirectory, 'deno_dir') : undefined + + if (!denoDir) { + const tmpDir = await tmp.dir({ unsafeCleanup: true }) + + denoDir = tmpDir.path + + cleanup.push(tmpDir.cleanup) + } + + const manifest: Manifest = { + functions: {}, + version: 1, + } + const entrypoints: string[] = [] + + for (const func of functions) { + entrypoints.push(func.path) + + manifest.functions[func.name] = getUnixPath(path.relative(basePath, func.path)) + } + + const manifestPath = path.join(sideFilesDir.path, 'manifest.json') + const manifestContents = JSON.stringify(manifest) + hashes.set('manifest', getStringHash(manifestContents)) + await fs.writeFile(manifestPath, manifestContents) + + const denoConfigPath = path.join(sideFilesDir.path, 'deno.json') + const denoConfigContents = JSON.stringify(importMap.getContentsWithRelativePaths()) + hashes.set('config', getStringHash(denoConfigContents)) + await fs.writeFile(denoConfigPath, denoConfigContents) + + const tsPaths = new Set() + const rootDirectory = commonPathPrefix([basePath, denoDir]) + const moduleGraph = await getDenoInfo({ + basePath, + deno, + denoDir, + entrypoints, + importMap, + }) + + const baseURL = pathToFileURL(basePath) + const { fileList } = await nodeFileTrace(entrypoints, { + base: rootDirectory, + processCwd: basePath, + readFile: async (filePath: string) => { + if (TYPESCRIPT_EXTENSIONS.has(path.extname(filePath)) || tsPaths.has(filePath)) { + const transpiled = ( + await build({ + bundle: false, + entryPoints: [filePath], + loader: { + // esbuild uses the extension of each entrypoint to determine the + // right loader to use. This doesn't work with the internal files + // from the Deno cache, so we must tell it to use the TypeScript + // loader for any files without an extension. + '': 'ts', + }, + logLevel: 'silent', + platform: 'node', + write: false, + }) + ).outputFiles[0].text + + hashes.set(filePath, getStringHash(transpiled)) + + return transpiled + } + + const { contents, hash } = await readFileAndHash(filePath) + + hashes.set(filePath, hash) + + return contents + }, + resolve: async (initialSpecifier, ...args) => { + let specifier = initialSpecifier + + // Start by checking whether the specifier matches any import map defined + // by the user. + const { matched, resolvedImport } = importMapResolve( + initialSpecifier, + importMap.getContentsWithURLObjects(), + baseURL, + ) + + // If it does, the resolved import is the specifier we'll evaluate going + // forward. + if (matched && resolvedImport.protocol === 'file:') { + specifier = fileURLToPath(resolvedImport).replace(/\\/g, '/') + } + + // If the specifier is an HTTPS import, we need to resolve it to a local + // file first. + if (specifier.startsWith('https://')) { + const resolved = resolveHTTPSSpecifier(moduleGraph, specifier) + + if (resolved) { + if (resolved.isTypeScript) { + tsPaths.add(resolved.localPath) + } + + specifier = resolved.localPath + } + } + + return nftResolve(specifier, ...args) + }, + }) + + // Computing a stable hash of the file list. + const hash = getStringHash( + [...hashes.keys()] + .sort() + .map((path) => hashes.get(path)) + .filter(Boolean) + .join(','), + ) + + const absolutePaths = [...fileList].map((relativePath) => path.resolve(rootDirectory, relativePath)) + const tarballPath = path.join(distDirectory, buildID + TARBALL_EXTENSION) + + await fs.mkdir(path.dirname(tarballPath), { recursive: true }) + + await tar.create( + { + cwd: rootDirectory, + file: tarballPath, + preservePaths: true, + onWriteEntry(entry) { + if (entry.path === denoConfigPath) { + entry.path = `./${TARBALL_SRC_DIRECTORY}/deno.json` + + return + } + + if (entry.path === manifestPath) { + entry.path = `./${TARBALL_SRC_DIRECTORY}/___netlify-edge-functions.json` + + return + } + + if (entry.path.startsWith(denoDir)) { + const tarPath = getUnixPath(path.relative(denoDir, entry.path)) + + entry.path = `./deno_dir/${tarPath}` + + return + } + + const tarPath = getUnixPath(path.relative(basePath, entry.path)) + + entry.path = `./${TARBALL_SRC_DIRECTORY}/${tarPath}` + }, + }, + [...absolutePaths, manifestPath, denoConfigPath], + ) + + await Promise.all(cleanup) + + return { + extension: TARBALL_EXTENSION, + format: BundleFormat.TARBALL, + hash, + } +} diff --git a/packages/edge-bundler/node/import_map.ts b/packages/edge-bundler/node/import_map.ts index c3cabfe75d..d31edd7247 100644 --- a/packages/edge-bundler/node/import_map.ts +++ b/packages/edge-bundler/node/import_map.ts @@ -200,6 +200,29 @@ export class ImportMap { } } + getContentsWithRelativePaths() { + let imports: Imports = {} + let scopes: Record = {} + + this.sources.forEach((file) => { + imports = { ...imports, ...file.imports } + scopes = { ...scopes, ...file.scopes } + }) + + // Internal imports must come last, because we need to guarantee that + // `netlify:edge` isn't user-defined. + Object.entries(INTERNAL_IMPORTS).forEach((internalImport) => { + const [specifier, url] = internalImport + + imports[specifier] = url + }) + + return { + imports, + scopes, + } + } + // The same as `getContents`, but the URLs are represented as URL objects // instead of strings. This is compatible with the `ParsedImportMap` type // from the `@import-maps/resolve` library. diff --git a/packages/edge-bundler/node/server/server.ts b/packages/edge-bundler/node/server/server.ts index cd2c8af690..bfa2ddedca 100644 --- a/packages/edge-bundler/node/server/server.ts +++ b/packages/edge-bundler/node/server/server.ts @@ -125,7 +125,7 @@ const prepareServer = ({ // the `stage2Path` file as well as all of their dependencies. // Consumers such as the CLI can use this information to watch all the // relevant files and issue an isolate restart when one of them changes. - const { stdout } = await deno.run(['info', '--json', stage2Path]) + const { stdout } = await deno.run(['info', '--no-lock', '--json', stage2Path]) graph = JSON.parse(stdout) } catch { @@ -320,7 +320,7 @@ export const serve = async ({ // Downloading latest types if needed. await ensureLatestTypes(deno, logger) - const flags = ['--allow-all', '--no-config'] + const flags = ['--allow-all', '--no-config', '--no-lock'] if (certificatePath) { flags.push(`--cert=${certificatePath}`) diff --git a/packages/edge-bundler/node/utils/sha256.ts b/packages/edge-bundler/node/utils/sha256.ts index 03c835e64d..135baee5c0 100644 --- a/packages/edge-bundler/node/utils/sha256.ts +++ b/packages/edge-bundler/node/utils/sha256.ts @@ -1,7 +1,8 @@ -import crypto from 'crypto' -import fs from 'fs' +import { Buffer } from 'node:buffer' +import crypto from 'node:crypto' +import fs from 'node:fs' -const getFileHash = (path: string): Promise => { +export const getFileHash = (path: string): Promise => { const hash = crypto.createHash('sha256') hash.setEncoding('hex') @@ -20,4 +21,37 @@ const getFileHash = (path: string): Promise => { }) } -export { getFileHash } +export const getStringHash = (input: string) => { + const hash = crypto.createHash('sha256') + + hash.setEncoding('hex') + hash.update(input) + + return hash.digest('hex') +} + +export const readFileAndHash = (path: string) => { + const file = fs.createReadStream(path) + const hash = crypto.createHash('sha256') + const chunks: Uint8Array[] = [] + + hash.setEncoding('hex') + + return new Promise<{ contents: string; hash: string }>((resolve, reject) => { + file + .on('data', (chunk) => { + chunks.push(Buffer.from(chunk)) + + hash.update(chunk.toString()) + }) + .on('error', reject) + .on('end', () => { + const contents = Buffer.concat(chunks).toString('utf8') + + return resolve({ + contents, + hash: hash.digest('hex'), + }) + }) + }) +} diff --git a/packages/edge-bundler/package.json b/packages/edge-bundler/package.json index bf04eac2f6..2f7c7940cb 100644 --- a/packages/edge-bundler/package.json +++ b/packages/edge-bundler/package.json @@ -43,7 +43,7 @@ }, "devDependencies": { "@types/glob-to-regexp": "^0.4.1", - "@types/node": "^14.18.32", + "@types/node": "^16.18.122", "@types/semver": "^7.3.9", "@types/uuid": "^9.0.0", "@vitest/coverage-v8": "^0.34.0", @@ -52,7 +52,6 @@ "cpy": "^9.0.1", "cross-env": "^7.0.3", "nock": "^13.2.4", - "tar": "^6.1.11", "typescript": "^5.0.0", "vitest": "^0.34.0" }, @@ -80,6 +79,7 @@ "p-wait-for": "^4.1.0", "path-key": "^4.0.0", "semver": "^7.3.8", + "tar": "^7.4.3", "tmp-promise": "^3.0.3", "urlpattern-polyfill": "8.0.2", "uuid": "^9.0.0" diff --git a/packages/edge-bundler/test/fixtures/imports_npm_module/functions/func1.ts b/packages/edge-bundler/test/fixtures/imports_npm_module/functions/func1.ts index cb73fdbf0d..e673b6dea1 100644 --- a/packages/edge-bundler/test/fixtures/imports_npm_module/functions/func1.ts +++ b/packages/edge-bundler/test/fixtures/imports_npm_module/functions/func1.ts @@ -1,14 +1,10 @@ import parent1 from 'parent-1' -import parent3 from './lib/util.ts' +import parent3 from './helpers/util.ts' import { echo, parent2 } from 'alias:helper' -import { HTMLRewriter } from 'html-rewriter' - -await Promise.resolve() - -new HTMLRewriter() +import { encode as base64Encode } from "https://deno.land/std@0.194.0/encoding/base64.ts"; export default async () => { - const text = [parent1('JavaScript'), parent2('APIs'), parent3('Markup')].join(', ') + const text = [parent1('JavaScript'), parent2('APIs'), parent3('Markup'), base64Encode("Netlify")].join(', ') return new Response(echo(text)) } diff --git a/packages/edge-bundler/test/fixtures/imports_npm_module/functions/lib/util.ts b/packages/edge-bundler/test/fixtures/imports_npm_module/functions/helpers/util.ts similarity index 100% rename from packages/edge-bundler/test/fixtures/imports_npm_module/functions/lib/util.ts rename to packages/edge-bundler/test/fixtures/imports_npm_module/functions/helpers/util.ts diff --git a/packages/edge-bundler/test/fixtures/imports_npm_module/import_map.json b/packages/edge-bundler/test/fixtures/imports_npm_module/import_map.json index 5ef2121b01..a72e1a7d9f 100644 --- a/packages/edge-bundler/test/fixtures/imports_npm_module/import_map.json +++ b/packages/edge-bundler/test/fixtures/imports_npm_module/import_map.json @@ -1,6 +1,5 @@ { "imports": { - "alias:helper": "./helper.ts", - "html-rewriter": "https://ghuc.cc/worker-tools/html-rewriter/index.ts" + "alias:helper": "./helper.ts" } } diff --git a/packages/edge-bundler/test/fixtures/imports_npm_module/node_modules/grandchild-1/index.js b/packages/edge-bundler/test/fixtures/imports_npm_module/node_modules/grandchild-1/index.js index 59c1e941b1..6a415726e9 100644 --- a/packages/edge-bundler/test/fixtures/imports_npm_module/node_modules/grandchild-1/index.js +++ b/packages/edge-bundler/test/fixtures/imports_npm_module/node_modules/grandchild-1/index.js @@ -1,3 +1,3 @@ -import { cwd } from "process" +import { platform } from "process" -export default (input) => `${input}${cwd()}` \ No newline at end of file +export default (input) => `${input}${platform}` \ No newline at end of file diff --git a/packages/edge-bundler/test/fixtures/imports_npm_module/node_modules/parent-4/index.js b/packages/edge-bundler/test/fixtures/imports_npm_module/node_modules/parent-4/index.js new file mode 100644 index 0000000000..35a9b77a4d --- /dev/null +++ b/packages/edge-bundler/test/fixtures/imports_npm_module/node_modules/parent-4/index.js @@ -0,0 +1,4 @@ +export default () => { + throw new Error("I should not be loaded") +} + diff --git a/packages/edge-bundler/test/fixtures/imports_npm_module/node_modules/parent-4/package.json b/packages/edge-bundler/test/fixtures/imports_npm_module/node_modules/parent-4/package.json new file mode 100644 index 0000000000..483924df90 --- /dev/null +++ b/packages/edge-bundler/test/fixtures/imports_npm_module/node_modules/parent-4/package.json @@ -0,0 +1,5 @@ +{ + "name": "parent-4", + "version": "1.0.0", + "main": "index.js" +} \ No newline at end of file diff --git a/packages/edge-bundler/test/fixtures/imports_npm_module/package.json b/packages/edge-bundler/test/fixtures/imports_npm_module/package.json index 3dbc1ca591..3541812a7b 100644 --- a/packages/edge-bundler/test/fixtures/imports_npm_module/package.json +++ b/packages/edge-bundler/test/fixtures/imports_npm_module/package.json @@ -1,3 +1,7 @@ { - "type": "module" + "type": "module", + "dependencies": { + "parent-1": "1.0.0", + "parent-3": "1.0.0" + } } diff --git a/packages/edge-bundler/test/util.ts b/packages/edge-bundler/test/util.ts index 2b8d204772..2269a26466 100644 --- a/packages/edge-bundler/test/util.ts +++ b/packages/edge-bundler/test/util.ts @@ -1,9 +1,10 @@ -import { promises as fs } from 'fs' +import fs from 'node:fs/promises' import { join, resolve } from 'path' import { stderr, stdout } from 'process' import { fileURLToPath, pathToFileURL } from 'url' import { execa } from 'execa' +import * as tar from 'tar' import tmp from 'tmp-promise' import { getLogger } from '../node/logger.js' @@ -17,19 +18,35 @@ const url = new URL(import.meta.url) const dirname = fileURLToPath(url) const fixturesDir = resolve(dirname, '..', 'fixtures') -const useFixture = async (fixtureName: string) => { - const tmpDir = await tmp.dir({ unsafeCleanup: true }) +interface UseFixtureOptions { + copyDirectory?: boolean +} + +const useFixture = async (fixtureName: string, { copyDirectory }: UseFixtureOptions = {}) => { + const tmpDistDir = await tmp.dir({ unsafeCleanup: true }) const fixtureDir = resolve(fixturesDir, fixtureName) - const distPath = join(tmpDir.path, '.netlify', 'edge-functions-dist') + const distPath = join(tmpDistDir.path, '.netlify', 'edge-functions-dist') + + if (copyDirectory) { + const tmpFixtureDir = await tmp.dir({ unsafeCleanup: true }) + + await fs.cp(fixtureDir, tmpFixtureDir.path, { recursive: true }) + + return { + basePath: tmpFixtureDir.path, + cleanup: () => Promise.allSettled([tmpDistDir.cleanup, tmpFixtureDir.cleanup]), + distPath, + } + } return { basePath: fixtureDir, - cleanup: tmpDir.cleanup, + cleanup: tmpDistDir.cleanup, distPath, } } -const inspectFunction = (path: string) => ` +const inspectESZIPFunction = (path: string) => ` import { functions } from "${pathToFileURL(path)}.js"; const responses = {}; @@ -44,6 +61,25 @@ const inspectFunction = (path: string) => ` console.log(JSON.stringify(responses)); ` +const inspectTarballFunction = () => ` +import path from "node:path"; +import { pathToFileURL } from "node:url"; +import manifest from "./___netlify-edge-functions.json" with { type: "json" }; + +const responses = {}; + +for (const functionName in manifest.functions) { + const req = new Request("https://test.netlify"); + const entrypoint = path.resolve(manifest.functions[functionName]); + const func = await import(pathToFileURL(entrypoint)) + const res = await func.default(req); + + responses[functionName] = await res.text(); +} + +console.log(JSON.stringify(responses)); +` + const getRouteMatcher = (manifest: Manifest) => (candidate: string) => manifest.routes.find((route) => { const regex = new RegExp(route.pattern) @@ -69,6 +105,7 @@ const runESZIP = async (eszipPath: string, vendorDirectory?: string) => { const extractCommand = execa('deno', [ 'run', '--allow-all', + '--no-lock', 'https://deno.land/x/eszip@v0.55.2/eszip.ts', 'x', eszipPath, @@ -99,7 +136,37 @@ const runESZIP = async (eszipPath: string, vendorDirectory?: string) => { await fs.rename(stage2Path, `${stage2Path}.js`) // Run function that imports the extracted stage 2 and invokes each function. - const evalCommand = execa('deno', ['eval', '--no-check', '--import-map', importMapPath, inspectFunction(stage2Path)]) + const evalCommand = execa('deno', [ + 'eval', + '--no-check', + '--import-map', + importMapPath, + inspectESZIPFunction(stage2Path), + ]) + + evalCommand.stderr?.pipe(stderr) + + const result = await evalCommand + + await tmpDir.cleanup() + + return JSON.parse(result.stdout) +} + +const runTarball = async (tarballPath: string) => { + const tmpDir = await tmp.dir({ unsafeCleanup: true }) + + await tar.extract({ + cwd: tmpDir.path, + file: tarballPath, + }) + + const evalCommand = execa('deno', ['eval', inspectTarballFunction()], { + cwd: join(tmpDir.path, 'src'), + env: { + DENO_DIR: '../deno_dir', + }, + }) evalCommand.stderr?.pipe(stderr) @@ -110,4 +177,4 @@ const runESZIP = async (eszipPath: string, vendorDirectory?: string) => { return JSON.parse(result.stdout) } -export { fixturesDir, getRouteMatcher, testLogger, runESZIP, useFixture } +export { fixturesDir, getRouteMatcher, testLogger, runESZIP, runTarball, useFixture } From d959b545facda77ded5358d521c1d0d9aed45c6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Fri, 13 Dec 2024 14:17:04 +0000 Subject: [PATCH 02/33] chore: cleanup --- packages/edge-bundler/node/bundler.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/edge-bundler/node/bundler.ts b/packages/edge-bundler/node/bundler.ts index cec14c3183..0548a0676d 100644 --- a/packages/edge-bundler/node/bundler.ts +++ b/packages/edge-bundler/node/bundler.ts @@ -115,17 +115,16 @@ export const bundle = async ( vendorDirectory, }) - const functionBundles: Bundle[] = [] + const bundles: Bundle[] = [] if (featureFlags.edge_bundler_generate_tarball) { - functionBundles.push( + bundles.push( await bundleTarball({ basePath, buildID, debug, deno, distDirectory, - externals, functions, featureFlags, importMap: importMap.clone(), @@ -138,7 +137,7 @@ export const bundle = async ( importMap.add(vendor.importMap) } - functionBundles.push( + bundles.push( await bundleESZIP({ basePath, buildID, @@ -156,7 +155,7 @@ export const bundle = async ( // The final file name of the bundles contains a SHA256 hash of the contents, // which we can only compute now that the files have been generated. So let's // rename the bundles to their permanent names. - await createFinalBundles(functionBundles, distDirectory, buildID) + await createFinalBundles(bundles, distDirectory, buildID) // Retrieving a configuration object for each function. // Run `getFunctionConfig` in parallel as it is a non-trivial operation and spins up deno @@ -187,7 +186,7 @@ export const bundle = async ( }) const manifest = await writeManifest({ - bundles: functionBundles, + bundles, declarations, distDirectory, featureFlags, From 780b95a463e807b12b6292cf5e72a4035a7f0555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Fri, 13 Dec 2024 14:24:14 +0000 Subject: [PATCH 03/33] refactor: abstract TS transpilation logic --- packages/edge-bundler/node/formats/tarball.ts | 20 ++--------------- .../edge-bundler/node/npm_dependencies.ts | 13 ++--------- .../edge-bundler/node/utils/typescript.ts | 22 +++++++++++++++++++ 3 files changed, 26 insertions(+), 29 deletions(-) create mode 100644 packages/edge-bundler/node/utils/typescript.ts diff --git a/packages/edge-bundler/node/formats/tarball.ts b/packages/edge-bundler/node/formats/tarball.ts index 296e782e2e..96c84509bf 100644 --- a/packages/edge-bundler/node/formats/tarball.ts +++ b/packages/edge-bundler/node/formats/tarball.ts @@ -5,7 +5,6 @@ import { fileURLToPath, pathToFileURL } from 'node:url' import { resolve as importMapResolve } from '@import-maps/resolve' import { nodeFileTrace, resolve as nftResolve } from '@vercel/nft' import commonPathPrefix from 'common-path-prefix' -import { build } from 'esbuild' import * as tar from 'tar' import tmp from 'tmp-promise' @@ -15,11 +14,11 @@ import { EdgeFunction } from '../edge_function.js' import { FeatureFlags } from '../feature_flags.js' import { ImportMap } from '../import_map.js' import { getStringHash, readFileAndHash } from '../utils/sha256.js' +import { transpile, TYPESCRIPT_EXTENSIONS } from '../utils/typescript.js' import { ModuleGraphJson } from '../vendor/module_graph/module_graph.js' const TARBALL_EXTENSION = '.tar' const TARBALL_SRC_DIRECTORY = 'src' -const TYPESCRIPT_EXTENSIONS = new Set(['.ts', '.tsx', '.cts', '.ctsx', '.mts', '.mtsx']) interface Manifest { functions: Record @@ -134,22 +133,7 @@ export const bundle = async ({ processCwd: basePath, readFile: async (filePath: string) => { if (TYPESCRIPT_EXTENSIONS.has(path.extname(filePath)) || tsPaths.has(filePath)) { - const transpiled = ( - await build({ - bundle: false, - entryPoints: [filePath], - loader: { - // esbuild uses the extension of each entrypoint to determine the - // right loader to use. This doesn't work with the internal files - // from the Deno cache, so we must tell it to use the TypeScript - // loader for any files without an extension. - '': 'ts', - }, - logLevel: 'silent', - platform: 'node', - write: false, - }) - ).outputFiles[0].text + const transpiled = await transpile(filePath) hashes.set(filePath, getStringHash(transpiled)) diff --git a/packages/edge-bundler/node/npm_dependencies.ts b/packages/edge-bundler/node/npm_dependencies.ts index f3e4aa6cbc..a1152b90fc 100644 --- a/packages/edge-bundler/node/npm_dependencies.ts +++ b/packages/edge-bundler/node/npm_dependencies.ts @@ -13,8 +13,7 @@ import tmp from 'tmp-promise' import { ImportMap } from './import_map.js' import { Logger } from './logger.js' import { pathsBetween } from './utils/fs.js' - -const TYPESCRIPT_EXTENSIONS = new Set(['.ts', '.tsx', '.cts', '.ctsx', '.mts', '.mtsx']) +import { transpile, TYPESCRIPT_EXTENSIONS } from './utils/typescript.js' const slugifyPackageName = (specifier: string) => { if (!specifier.startsWith('@')) return specifier @@ -118,15 +117,7 @@ const getNPMSpecifiers = async ({ basePath, functions, importMap, environment, r // If this is a TypeScript file, we need to compile in before we can // parse it. if (TYPESCRIPT_EXTENSIONS.has(path.extname(filePath))) { - const compiled = await build({ - bundle: false, - entryPoints: [filePath], - logLevel: 'silent', - platform: 'node', - write: false, - }) - - return compiled.outputFiles[0].text + return transpile(filePath) } return fs.readFile(filePath, 'utf8') diff --git a/packages/edge-bundler/node/utils/typescript.ts b/packages/edge-bundler/node/utils/typescript.ts new file mode 100644 index 0000000000..c0ffe8e2b2 --- /dev/null +++ b/packages/edge-bundler/node/utils/typescript.ts @@ -0,0 +1,22 @@ +import { build } from 'esbuild' + +export const TYPESCRIPT_EXTENSIONS = new Set(['.ts', '.tsx', '.cts', '.ctsx', '.mts', '.mtsx']) + +export const transpile = async (filePath: string) => { + const result = await build({ + bundle: false, + entryPoints: [filePath], + loader: { + // esbuild uses the extension of each entrypoint to determine the + // right loader to use. This doesn't work with the internal files + // from the Deno cache, so we must tell it to use the TypeScript + // loader for any files without an extension. + '': 'ts', + }, + logLevel: 'silent', + platform: 'node', + write: false, + }) + + return result.outputFiles[0].text +} From d3e77d0f265095dbe09c065df1c2398fdbf14541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Fri, 13 Dec 2024 14:58:47 +0000 Subject: [PATCH 04/33] chore: remove comment --- packages/edge-bundler/node/bridge.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/edge-bundler/node/bridge.ts b/packages/edge-bundler/node/bridge.ts index 34567022d6..7b435fc0ad 100644 --- a/packages/edge-bundler/node/bridge.ts +++ b/packages/edge-bundler/node/bridge.ts @@ -12,11 +12,6 @@ import { getLogger, Logger } from './logger.js' import { getBinaryExtension } from './platform.js' const DENO_VERSION_FILE = 'version.txt' - -// When updating DENO_VERSION_RANGE, ensure that the deno version installed in the -// build-image/buildbot does satisfy this range! -// We're pinning the range because of an issue with v1.45.0 of the Deno CLI: -// https://linear.app/netlify/issue/FRP-775/deno-cli-v1450-causing-issues const DENO_VERSION_RANGE = '^2.1.4' type OnBeforeDownloadHook = () => void | Promise From eb616ac23fbeccb0cef6aac99fde52c792bf338e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Fri, 13 Dec 2024 16:17:07 +0000 Subject: [PATCH 05/33] chore: fix test --- packages/edge-bundler/test/integration/test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edge-bundler/test/integration/test.js b/packages/edge-bundler/test/integration/test.js index 57e244abd3..227e46bfde 100644 --- a/packages/edge-bundler/test/integration/test.js +++ b/packages/edge-bundler/test/integration/test.js @@ -8,7 +8,7 @@ import { fileURLToPath, pathToFileURL } from 'url' import { promisify } from 'util' import cpy from 'cpy' -import tar from 'tar' +import * as tar from 'tar' import tmp from 'tmp-promise' const exec = promisify(childProcess.exec) From 828c05d45bafbb8625d8c0b29afc93c76aaa91cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 17 Dec 2024 13:59:37 +0000 Subject: [PATCH 06/33] chore: update CI versions --- .github/workflows/workflow.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 4c60515f1b..fe6183d955 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -52,11 +52,11 @@ jobs: os: [ubuntu-latest, macOS-latest, windows-latest] node-version: ['*'] # Must include the minimum deno version from the `DENO_VERSION_RANGE` constant in `node/bridge.ts`. - deno-version: ['v1.39.0', 'v1.46.3'] + deno-version: ['v2.1.4'] include: - os: ubuntu-latest node-version: '14.16.0' - deno-version: 'v1.46.3' + deno-version: 'v2.1.4' fail-fast: false steps: # Increasing the maximum number of open files. See: @@ -169,7 +169,7 @@ jobs: - name: Setup Deno uses: denoland/setup-deno@v1 with: - deno-version: v1.46.3 + deno-version: v2.1.4 if: ${{ !steps.release-check.outputs.IS_RELEASE }} - name: Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 From d22e87d881b6dc0e0dcb8b2c267572ec1bf4ae2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 17 Dec 2024 16:29:03 +0000 Subject: [PATCH 07/33] chore: remove node prefix --- packages/edge-bundler/node/formats/tarball.ts | 6 +++--- packages/edge-bundler/test/util.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/edge-bundler/node/formats/tarball.ts b/packages/edge-bundler/node/formats/tarball.ts index 96c84509bf..811bd6e862 100644 --- a/packages/edge-bundler/node/formats/tarball.ts +++ b/packages/edge-bundler/node/formats/tarball.ts @@ -1,6 +1,6 @@ -import fs from 'node:fs/promises' -import path from 'node:path' -import { fileURLToPath, pathToFileURL } from 'node:url' +import { promises as fs } from 'fs' +import path from 'path' +import { fileURLToPath, pathToFileURL } from 'url' import { resolve as importMapResolve } from '@import-maps/resolve' import { nodeFileTrace, resolve as nftResolve } from '@vercel/nft' diff --git a/packages/edge-bundler/test/util.ts b/packages/edge-bundler/test/util.ts index 2269a26466..71850c6ad9 100644 --- a/packages/edge-bundler/test/util.ts +++ b/packages/edge-bundler/test/util.ts @@ -1,4 +1,4 @@ -import fs from 'node:fs/promises' +import { promises as fs } from 'fs' import { join, resolve } from 'path' import { stderr, stdout } from 'process' import { fileURLToPath, pathToFileURL } from 'url' From 840776a264b4c579bc22f4086e0e1eb38df0146f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Thu, 19 Dec 2024 09:30:03 +0000 Subject: [PATCH 08/33] fix: add type check --- packages/edge-bundler/deno/lib/common.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edge-bundler/deno/lib/common.ts b/packages/edge-bundler/deno/lib/common.ts index a3f6ced471..104bedabe5 100644 --- a/packages/edge-bundler/deno/lib/common.ts +++ b/packages/edge-bundler/deno/lib/common.ts @@ -42,7 +42,7 @@ const loadWithRetry = (specifier: string, delay = 1000, maxTry = 3) => { maxTry, }); } catch (error) { - if (isTooManyTries(error)) { + if (error instanceof Error && isTooManyTries(error)) { console.error(`Loading ${specifier} failed after ${maxTry} tries.`); } throw error; From ebcf3de995a631bc879211cc507d625d890fa3f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Thu, 19 Dec 2024 09:49:46 +0000 Subject: [PATCH 09/33] chore: support Node 14 --- packages/edge-bundler/test/util.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/edge-bundler/test/util.ts b/packages/edge-bundler/test/util.ts index 71850c6ad9..b430c11c68 100644 --- a/packages/edge-bundler/test/util.ts +++ b/packages/edge-bundler/test/util.ts @@ -3,6 +3,7 @@ import { join, resolve } from 'path' import { stderr, stdout } from 'process' import { fileURLToPath, pathToFileURL } from 'url' +import cpy from 'cpy' import { execa } from 'execa' import * as tar from 'tar' import tmp from 'tmp-promise' @@ -30,7 +31,8 @@ const useFixture = async (fixtureName: string, { copyDirectory }: UseFixtureOpti if (copyDirectory) { const tmpFixtureDir = await tmp.dir({ unsafeCleanup: true }) - await fs.cp(fixtureDir, tmpFixtureDir.path, { recursive: true }) + // TODO: Replace with `fs.cp` once we drop support for Node 14. + await cpy(`${fixtureDir}/**`, tmpFixtureDir.path) return { basePath: tmpFixtureDir.path, From ccb5bcc342cfa81ffa32d0d1758c212ffee9cb73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 22 Jul 2025 16:49:16 +0100 Subject: [PATCH 10/33] chore: update test --- packages/edge-bundler/test/integration/test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edge-bundler/test/integration/test.js b/packages/edge-bundler/test/integration/test.js index c806b2d6f6..af7a66874e 100644 --- a/packages/edge-bundler/test/integration/test.js +++ b/packages/edge-bundler/test/integration/test.js @@ -8,7 +8,7 @@ import { fileURLToPath, pathToFileURL } from 'url' import { promisify } from 'util' import cpy from 'cpy' -import * as tar from 'tar' +import { x as tarExtract } from 'tar' import tmp from 'tmp-promise' const exec = promisify(childProcess.exec) From 107b5bc9ec1194ae7c69f0ab73c2d0401138e76e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 22 Jul 2025 17:12:37 +0100 Subject: [PATCH 11/33] fix: fix test --- packages/edge-bundler/test/util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edge-bundler/test/util.ts b/packages/edge-bundler/test/util.ts index 5850e1882d..dfc0db5022 100644 --- a/packages/edge-bundler/test/util.ts +++ b/packages/edge-bundler/test/util.ts @@ -138,7 +138,7 @@ const runESZIP = async (eszipPath: string, vendorDirectory?: string) => { await fs.rename(stage2Path, `${stage2Path}.js`) // Run function that imports the extracted stage 2 and invokes each function. - const evalCommand = execa('deno', ['eval', '--import-map', importMapPath, inspectFunction(stage2Path)]) + const evalCommand = execa('deno', ['eval', '--import-map', importMapPath, inspectESZIPFunction(stage2Path)]) evalCommand.stderr?.pipe(stderr) From aba6353dbf99788575b4941b5ad7be5f8a77ef7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Wed, 23 Jul 2025 15:20:34 +0100 Subject: [PATCH 12/33] fix: fix lint issue --- packages/edge-bundler/node/formats/tarball.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edge-bundler/node/formats/tarball.ts b/packages/edge-bundler/node/formats/tarball.ts index 811bd6e862..6d0e68283c 100644 --- a/packages/edge-bundler/node/formats/tarball.ts +++ b/packages/edge-bundler/node/formats/tarball.ts @@ -159,7 +159,7 @@ export const bundle = async ({ // If it does, the resolved import is the specifier we'll evaluate going // forward. - if (matched && resolvedImport.protocol === 'file:') { + if (matched && resolvedImport?.protocol === 'file:') { specifier = fileURLToPath(resolvedImport).replace(/\\/g, '/') } From 1bd36cfe0f0df2ac882c9cb1eb729f3b27ad511a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Thu, 24 Jul 2025 16:23:52 +0100 Subject: [PATCH 13/33] refactor: use deno bundle --- package-lock.json | 8 +- packages/edge-bundler/deno.lock | 111 ++++++++++++ packages/edge-bundler/node/bridge.ts | 2 +- packages/edge-bundler/node/bundler.test.ts | 57 +++++- packages/edge-bundler/node/formats/tarball.ts | 168 ++++-------------- .../edge-bundler/node/npm_dependencies.ts | 2 +- packages/edge-bundler/node/utils/sha256.ts | 32 +++- packages/edge-bundler/test/util.ts | 5 +- 8 files changed, 235 insertions(+), 150 deletions(-) create mode 100644 packages/edge-bundler/deno.lock diff --git a/package-lock.json b/package-lock.json index 54c02f97f7..ddc5542b72 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24349,6 +24349,7 @@ "parse-imports": "^2.2.1", "path-key": "^4.0.0", "semver": "^7.3.8", + "tar": "^7.4.3", "tmp-promise": "^3.0.3", "urlpattern-polyfill": "8.0.2", "uuid": "^11.0.0" @@ -24363,7 +24364,6 @@ "cpy": "^11.1.0", "nock": "^14.0.0", "npm-run-all2": "^6.0.0", - "tar": "^7.0.0", "typescript": "^5.0.0", "vitest": "^3.0.0" }, @@ -24527,7 +24527,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=18" @@ -24573,7 +24572,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" @@ -24583,7 +24581,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", - "dev": true, "license": "MIT", "dependencies": { "minipass": "^7.1.2" @@ -24596,7 +24593,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "dev": true, "license": "MIT", "bin": { "mkdirp": "dist/cjs/src/bin.js" @@ -24644,7 +24640,6 @@ "version": "7.4.3", "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", - "dev": true, "license": "ISC", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", @@ -24780,7 +24775,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=18" diff --git a/packages/edge-bundler/deno.lock b/packages/edge-bundler/deno.lock new file mode 100644 index 0000000000..03c7fe213a --- /dev/null +++ b/packages/edge-bundler/deno.lock @@ -0,0 +1,111 @@ +{ + "version": "3", + "remote": { + "https://edge.netlify.com/": "fd941d61d88673d5f28aab283fb86fcc50f08a3bc80ee5470498fcfa88c65cfb", + "https://edge.netlify.com/bootstrap/account.ts": "192cb0b87d19640058bb28e7a9d46c59ba77e0bf41b89708e23938cca2644e06", + "https://edge.netlify.com/bootstrap/blobs.ts": "3b46e2af986f4675e119810304ed681e66b8ea49adb407f38f76c5ff4bb4f05f", + "https://edge.netlify.com/bootstrap/bypass.ts": "cf3423a7e2b9f136e84b93dfedbe7d27887c75e68e20619b2ffd891919f27c3e", + "https://edge.netlify.com/bootstrap/cache.ts": "82af3afd301482592a02bdc7fced77620b42e4dd9414bf60510f559a5af15c84", + "https://edge.netlify.com/bootstrap/config.ts": "6a2ce0e544e15e8f8883a5c18da5948e37fd0f2619f68cb31f3af53c51817025", + "https://edge.netlify.com/bootstrap/context.ts": "c6e9de479234f3ac500d2fc1544dadf1925e335d0d5551b1a71b7ce14423a683", + "https://edge.netlify.com/bootstrap/cookie.ts": "8b0baae708989ca183c6f3b4ab3d029e6abcbc2e43f93edeb0ff447b3bbc3a05", + "https://edge.netlify.com/bootstrap/cookie_store.ts": "2a1da3b09a35f8b31c82f91f182cf4dd43bc03c3e48036563d3803b7dd5b52c4", + "https://edge.netlify.com/bootstrap/deno-fs.ts": "ccd0a9335de14e01d77e8937e2320c99a110f5f2a473b80126750e3884ef952a", + "https://edge.netlify.com/bootstrap/edge_function.ts": "b8253e86aa83c67341f5cfedeba5049d77fbf84dcab7eceff7566b7728ae9b39", + "https://edge.netlify.com/bootstrap/environment.ts": "6d8bfb2359f170db4a1e1513904e9610ed047d7c1187cc0ceb4f0356231fbdde", + "https://edge.netlify.com/bootstrap/feature_flags.ts": "efab0245acb8436e897b245d0cc5f3226c364395d3dca8d65442cc1676fa35c6", + "https://edge.netlify.com/bootstrap/function_chain.ts": "7d2b6692385fa7f69507f50904423fe99bc66b4236e8d3dc32a0011208d64a39", + "https://edge.netlify.com/bootstrap/geo.ts": "f5c8edca3a460c82c2e9300270b2d270531efa995cc8c2c0b239713775bf6fe5", + "https://edge.netlify.com/bootstrap/globals/implementation.ts": "dbfb9b73fcbd2b7ce56260ea7b6062075702dcbab75403fcd8ea454fb7bfb5c4", + "https://edge.netlify.com/bootstrap/globals/types.ts": "eaa6148ded3121d8dee62dd91c86e7fe76601df0f3ca8d7962243a30f4c8935f", + "https://edge.netlify.com/bootstrap/handler.ts": "52732f96e7fd3a7a275ada1b83702fe724afb83d0458af47a740e267d7941372", + "https://edge.netlify.com/bootstrap/headers.ts": "f18295c406bcd22bbc751b4da02e36854dda837ab1cf02b03b44f475a660a4b8", + "https://edge.netlify.com/bootstrap/index-combined.ts": "92dc4703e8c779b24da845f4279353716a903c91f4fa406640d917303dcd6875", + "https://edge.netlify.com/bootstrap/invocation_metadata.ts": "457e73cb43dbe7695b32065f24a3d1e8e935efa517a2f25e2fd79977ea437f57", + "https://edge.netlify.com/bootstrap/log/instrumented_log.ts": "2331dce64d32b51e301d5139fc403792162638ff603e482c381ac27e932d7f95", + "https://edge.netlify.com/bootstrap/log/logger.ts": "74b7e7fb58654678a2eaddb3257f279056a5cbec884b355e5b62ac9e582496fa", + "https://edge.netlify.com/bootstrap/metrics.ts": "a585daf2bffa339b3be707e6f9e8d9c65a78a820513557f7b0806f3895e1c295", + "https://edge.netlify.com/bootstrap/path_parameters.ts": "8266dd6cbb4dbf859aa3536b1250b4daace8f00b2c82e7042006d8d0e448dd52", + "https://edge.netlify.com/bootstrap/request.ts": "8d5d23b9c4936751d7df6d6da92d9d77b7226c1615ac4cb1acb3f47f5fb0ae2e", + "https://edge.netlify.com/bootstrap/response.ts": "4e0d2537f810028ddbe91138b3526dae3f4c3fb30ff17673342ca971207c21b7", + "https://edge.netlify.com/bootstrap/retry.ts": "196357ed0af0b77a6919bcc6c10c42a22742dd099a7343c1227eb35b1fdebc5c", + "https://edge.netlify.com/bootstrap/router.ts": "d753b1b1c561ddd25bf4076450b2cf667fa5ec77187de5dd9842757977a6ba35", + "https://edge.netlify.com/bootstrap/server.ts": "4744e7ba8b2bc689eced7770eb43af875a5f26c9bd2fbf891220cb1c5c90f4ad", + "https://edge.netlify.com/bootstrap/site.ts": "ae2fe4267a6101fa93c1ccd36dfa4a7b084d66aeeded337fa2683b8904718ee1", + "https://edge.netlify.com/bootstrap/stage_2.ts": "a90258046f480d1e423c140d5cdbf305c860283e021d2bcf5eff36d5d6929114", + "https://edge.netlify.com/bootstrap/util/errors.ts": "8e14c65504f204c1773342a68a376995fe45d9397b6e3f453d19940270ebf707", + "https://edge.netlify.com/bootstrap/util/execution_context.ts": "a8cb042ab7f91d41cb88940c0c9a3a44ef931516be7084f80d2b429f5649dbc7", + "https://edge.netlify.com/bootstrap/util/fetch.ts": "4c12d853804c7594754a110f83f8144e7c91e52eec5e9693c6b3642ac59dc044", + "https://edge.netlify.com/bootstrap/util/patch_globals.ts": "70ccedcf3790aa97e3570efaca93973b2ef281c59cfb9e173073913553c81872", + "https://edge.netlify.com/bootstrap/util/redirect.ts": "30c76d79832f9b3af21fbe31e29c841f9c3ab85fb34cac80e1e6a5ab7de55551", + "https://edge.netlify.com/bootstrap/util/stack_tracer.ts": "20e47bfb3c365be712067dd62457ac20b6204420ca7250d9e607665c2b4a2823", + "https://edge.netlify.com/vendor/deno.land/std@0.170.0/_util/asserts.ts": "d0844e9b62510f89ce1f9878b046f6a57bf88f208a10304aab50efcb48365272", + "https://edge.netlify.com/vendor/deno.land/std@0.170.0/_util/os.ts": "8a33345f74990e627b9dfe2de9b040004b08ea5146c7c9e8fe9a29070d193934", + "https://edge.netlify.com/vendor/deno.land/std@0.170.0/async/abortable.ts": "80b2ac399f142cc528f95a037a7d0e653296352d95c681e284533765961de409", + "https://edge.netlify.com/vendor/deno.land/std@0.170.0/async/deadline.ts": "2c2deb53c7c28ca1dda7a3ad81e70508b1ebc25db52559de6b8636c9278fd41f", + "https://edge.netlify.com/vendor/deno.land/std@0.170.0/async/debounce.ts": "60301ffb37e730cd2d6f9dadfd0ecb2a38857681bd7aaf6b0a106b06e5210a98", + "https://edge.netlify.com/vendor/deno.land/std@0.170.0/async/deferred.ts": "77d3f84255c3627f1cc88699d8472b664d7635990d5358c4351623e098e917d6", + "https://edge.netlify.com/vendor/deno.land/std@0.170.0/async/delay.ts": "5a9bfba8de38840308a7a33786a0155a7f6c1f7a859558ddcec5fe06e16daf57", + "https://edge.netlify.com/vendor/deno.land/std@0.170.0/async/mod.ts": "7809ad4bb223e40f5fdc043e5c7ca04e0e25eed35c32c3c32e28697c553fa6d9", + "https://edge.netlify.com/vendor/deno.land/std@0.170.0/async/mux_async_iterator.ts": "770a0ff26c59f8bbbda6b703a2235f04e379f73238e8d66a087edc68c2a2c35f", + "https://edge.netlify.com/vendor/deno.land/std@0.170.0/async/pool.ts": "6854d8cd675a74c73391c82005cbbe4cc58183bddcd1fbbd7c2bcda42b61cf69", + "https://edge.netlify.com/vendor/deno.land/std@0.170.0/async/retry.ts": "e8e5173623915bbc0ddc537698fa418cf875456c347eda1ed453528645b42e67", + "https://edge.netlify.com/vendor/deno.land/std@0.170.0/async/tee.ts": "3a47cc4e9a940904fd4341f0224907e199121c80b831faa5ec2b054c6d2eff5e", + "https://edge.netlify.com/vendor/deno.land/std@0.170.0/datetime/to_imf.ts": "bdb0704a986a9bb48fba220722b3d4681e4b8c9c162562ee545ef2eb90b6edcd", + "https://edge.netlify.com/vendor/deno.land/std@0.170.0/encoding/base64.ts": "8605e018e49211efc767686f6f687827d7f5fd5217163e981d8d693105640d7a", + "https://edge.netlify.com/vendor/deno.land/std@0.170.0/flags/mod.ts": "4f50ec6383c02684db35de38b3ffb2cd5b9fcfcc0b1147055d1980c49e82521c", + "https://edge.netlify.com/vendor/deno.land/std@0.170.0/http/cookie.ts": "581464551b3d91739f699fbd6a429e6e8701ae252cf295efcda7379e8410d204", + "https://edge.netlify.com/vendor/deno.land/std@0.170.0/http/http_status.ts": "ed24048cc0d06066c944da59b0301da3ae2f990564bb4ad79bb52a09cf8e9b30", + "https://edge.netlify.com/vendor/deno.land/std@0.170.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3", + "https://edge.netlify.com/vendor/deno.land/std@0.170.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09", + "https://edge.netlify.com/vendor/deno.land/std@0.170.0/path/_util.ts": "d16be2a16e1204b65f9d0dfc54a9bc472cafe5f4a190b3c8471ec2016ccd1677", + "https://edge.netlify.com/vendor/deno.land/std@0.170.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633", + "https://edge.netlify.com/vendor/deno.land/std@0.170.0/path/glob.ts": "81cc6c72be002cd546c7a22d1f263f82f63f37fe0035d9726aa96fc8f6e4afa1", + "https://edge.netlify.com/vendor/deno.land/std@0.170.0/path/mod.ts": "cf7cec7ac11b7048bb66af8ae03513e66595c279c65cfa12bfc07d9599608b78", + "https://edge.netlify.com/vendor/deno.land/std@0.170.0/path/posix.ts": "b859684bc4d80edfd4cad0a82371b50c716330bed51143d6dcdbe59e6278b30c", + "https://edge.netlify.com/vendor/deno.land/std@0.170.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9", + "https://edge.netlify.com/vendor/deno.land/std@0.170.0/path/win32.ts": "7cebd2bda6657371adc00061a1d23fdd87bcdf64b4843bb148b0b24c11b40f69", + "https://edge.netlify.com/vendor/esm.sh/@netlify/cache@1.7.1/denonext/dist/bootstrap/main.mjs": "e5cb61f205526ccec8e47f79bb1e94c20ca5a5e1078f1de4b9ed262ac18cf4ae", + "https://edge.netlify.com/vendor/v1-8-0--edge-utils.netlify.app/logger/logger.ts": "8bd7a5e28b9be06f806f429be853ef865055d100a63f21ebfa2522a5bb3ab23a", + "https://edge.netlify.com/vendor/v1-8-0--edge-utils.netlify.app/logger/mod.ts": "669a5ec2dceede2926b0e1a7471c449a445f957c1deac559e07fb4c2af390ed1" + }, + "workspace": { + "packageJson": { + "dependencies": [ + "npm:@import-maps/resolve@^2.0.0", + "npm:@types/node@^18.19.111", + "npm:@types/semver@^7.3.9", + "npm:@types/uuid@^10.0.0", + "npm:@vitest/coverage-v8@^3.0.0", + "npm:ajv-errors@^3.0.0", + "npm:ajv@^8.11.2", + "npm:archiver@^7.0.0", + "npm:better-ajv-errors@^1.2.0", + "npm:chalk@^5.4.0", + "npm:common-path-prefix@^3.0.0", + "npm:cpy@^11.1.0", + "npm:env-paths@^3.0.0", + "npm:esbuild@0.25.6", + "npm:execa@^8.0.0", + "npm:find-up@^7.0.0", + "npm:get-package-name@^2.2.0", + "npm:get-port@^7.0.0", + "npm:is-path-inside@^4.0.0", + "npm:nock@^14.0.0", + "npm:node-stream-zip@^1.15.0", + "npm:npm-run-all2@^6.0.0", + "npm:p-retry@^6.0.0", + "npm:p-wait-for@^5.0.0", + "npm:parse-imports@^2.2.1", + "npm:path-key@^4.0.0", + "npm:semver@^7.3.8", + "npm:tar@^7.4.3", + "npm:tmp-promise@^3.0.3", + "npm:typescript@^5.0.0", + "npm:urlpattern-polyfill@8.0.2", + "npm:uuid@^11.0.0", + "npm:vitest@^3.0.0" + ] + } + } +} diff --git a/packages/edge-bundler/node/bridge.ts b/packages/edge-bundler/node/bridge.ts index c57653b5ca..fb593d2193 100644 --- a/packages/edge-bundler/node/bridge.ts +++ b/packages/edge-bundler/node/bridge.ts @@ -16,7 +16,7 @@ const DENO_VERSION_FILE = 'version.txt' // When updating DENO_VERSION_RANGE, ensure that the deno version // on the netlify/buildbot build image satisfies this range! // https://github.com/netlify/buildbot/blob/f9c03c9dcb091d6570e9d0778381560d469e78ad/build-image/noble/Dockerfile#L410 -const DENO_VERSION_RANGE = '1.39.0 - 2.2.4' +const DENO_VERSION_RANGE = '^2.4.2' type OnBeforeDownloadHook = () => void | Promise type OnAfterDownloadHook = (error?: Error) => void | Promise diff --git a/packages/edge-bundler/node/bundler.test.ts b/packages/edge-bundler/node/bundler.test.ts index 890e0ae801..ea0f6000e5 100644 --- a/packages/edge-bundler/node/bundler.test.ts +++ b/packages/edge-bundler/node/bundler.test.ts @@ -694,7 +694,62 @@ test('Loads edge functions from the Frameworks API', async () => { }) describe('Produces a tarball bundle', () => { - test('Using npm modules', async () => { + test('With only local imports', async () => { + const systemLogger = vi.fn() + const { basePath, cleanup, distPath } = await useFixture('with_config', { copyDirectory: true }) + const declarations: Declaration[] = [ + { + function: 'func1', + path: '/func1', + }, + ] + const vendorDirectory = await tmp.dir() + + await bundle( + [join(basePath, 'netlify/edge-functions'), join(basePath, '.netlify/edge-functions')], + distPath, + declarations, + { + basePath, + featureFlags: { + edge_bundler_generate_tarball: true, + }, + importMapPaths: [join(basePath, '.netlify/edge-functions/import_map.json')], + vendorDirectory: vendorDirectory.path, + systemLogger, + }, + ) + + expect( + systemLogger.mock.calls.find((call) => call[0] === 'Could not track dependencies in edge function:'), + ).toBeUndefined() + + const expectedOutput = { + 'user-func1': 'Hello, user function 1!', + 'user-func2': 'Hello, user function 2!', + 'user-func3': 'Hello from user function 3', + 'user-func4': 'Hello from user function 4. I will be cached!', + 'user-func5': 'Hello from user function 5.', + 'user-func6': 'Hello from user function 6.', + 'user-func7': 'Hello from user function 7.', + 'framework-func1': 'Hello, framework function 1!', + 'framework-func2': 'Hello, framework function 2!', + 'framework-func3': 'Hello, framework function 3!', + 'framework-func4': 'Hello, framework function 4!', + } + + const manifestFile = await readFile(resolve(distPath, 'manifest.json'), 'utf8') + const manifest = JSON.parse(manifestFile) + + const tarballPath = join(distPath, manifest.bundles[0].asset) + const tarballResult = await runTarball(tarballPath) + expect(tarballResult).toStrictEqual(expectedOutput) + + await cleanup() + await rm(vendorDirectory.path, { force: true, recursive: true }) + }) + + test.todo('Using npm modules', async () => { const systemLogger = vi.fn() const { basePath, cleanup, distPath } = await useFixture('imports_npm_module', { copyDirectory: true }) const sourceDirectory = join(basePath, 'functions') diff --git a/packages/edge-bundler/node/formats/tarball.ts b/packages/edge-bundler/node/formats/tarball.ts index 6d0e68283c..93b07179f6 100644 --- a/packages/edge-bundler/node/formats/tarball.ts +++ b/packages/edge-bundler/node/formats/tarball.ts @@ -1,10 +1,6 @@ import { promises as fs } from 'fs' import path from 'path' -import { fileURLToPath, pathToFileURL } from 'url' -import { resolve as importMapResolve } from '@import-maps/resolve' -import { nodeFileTrace, resolve as nftResolve } from '@vercel/nft' -import commonPathPrefix from 'common-path-prefix' import * as tar from 'tar' import tmp from 'tmp-promise' @@ -13,40 +9,15 @@ import { Bundle, BundleFormat } from '../bundle.js' import { EdgeFunction } from '../edge_function.js' import { FeatureFlags } from '../feature_flags.js' import { ImportMap } from '../import_map.js' -import { getStringHash, readFileAndHash } from '../utils/sha256.js' -import { transpile, TYPESCRIPT_EXTENSIONS } from '../utils/typescript.js' -import { ModuleGraphJson } from '../vendor/module_graph/module_graph.js' +import { getDirectoryHash, getStringHash } from '../utils/sha256.js' const TARBALL_EXTENSION = '.tar' -const TARBALL_SRC_DIRECTORY = 'src' interface Manifest { functions: Record version: number } -interface DenoInfoOptions { - basePath: string - deno: DenoBridge - denoDir: string - entrypoints: string[] - importMap: ImportMap -} - -const getDenoInfo = async ({ basePath, deno, denoDir, entrypoints, importMap }: DenoInfoOptions) => { - const { stdout } = await deno.run( - ['info', '--no-lock', '--import-map', importMap.toDataURL(), '--json', ...entrypoints], - { - cwd: basePath, - env: { - DENO_DIR: denoDir, - }, - }, - ) - - return JSON.parse(stdout) as ModuleGraphJson -} - interface BundleTarballOptions { basePath: string buildID: string @@ -59,17 +30,6 @@ interface BundleTarballOptions { vendorDirectory?: string } -const resolveHTTPSSpecifier = (moduleGraph: ModuleGraphJson, specifier: string) => { - for (const mod of moduleGraph.modules) { - if (mod.specifier === specifier && mod.local) { - return { - localPath: mod.local, - isTypeScript: TYPESCRIPT_EXTENSIONS.has(path.extname(specifier)), - } - } - } -} - const getUnixPath = (input: string) => input.split(path.sep).join('/') export const bundle = async ({ @@ -100,13 +60,41 @@ export const bundle = async ({ version: 1, } const entrypoints: string[] = [] + const bundledPaths = new Map() for (const func of functions) { + const relativePath = path.relative(basePath, func.path) + const bundledPath = path.format({ + ...path.parse(relativePath), + base: undefined, + ext: '.js', + }) + + bundledPaths.set(func.path, bundledPath) entrypoints.push(func.path) - manifest.functions[func.name] = getUnixPath(path.relative(basePath, func.path)) + manifest.functions[func.name] = getUnixPath(bundledPath) } + await deno.run( + [ + 'bundle', + '--import-map', + importMap.toDataURL(), + '--quiet', + '--code-splitting', + '--outdir', + distDirectory, + ...functions.map((func) => func.path), + ], + { + cwd: basePath, + env: { + DENO_DIR: denoDir, + }, + }, + ) + const manifestPath = path.join(sideFilesDir.path, 'manifest.json') const manifestContents = JSON.stringify(manifest) hashes.set('manifest', getStringHash(manifestContents)) @@ -117,116 +105,32 @@ export const bundle = async ({ hashes.set('config', getStringHash(denoConfigContents)) await fs.writeFile(denoConfigPath, denoConfigContents) - const tsPaths = new Set() - const rootDirectory = commonPathPrefix([basePath, denoDir]) - const moduleGraph = await getDenoInfo({ - basePath, - deno, - denoDir, - entrypoints, - importMap, - }) - - const baseURL = pathToFileURL(basePath) - const { fileList } = await nodeFileTrace(entrypoints, { - base: rootDirectory, - processCwd: basePath, - readFile: async (filePath: string) => { - if (TYPESCRIPT_EXTENSIONS.has(path.extname(filePath)) || tsPaths.has(filePath)) { - const transpiled = await transpile(filePath) - - hashes.set(filePath, getStringHash(transpiled)) - - return transpiled - } - - const { contents, hash } = await readFileAndHash(filePath) - - hashes.set(filePath, hash) - - return contents - }, - resolve: async (initialSpecifier, ...args) => { - let specifier = initialSpecifier - - // Start by checking whether the specifier matches any import map defined - // by the user. - const { matched, resolvedImport } = importMapResolve( - initialSpecifier, - importMap.getContentsWithURLObjects(), - baseURL, - ) - - // If it does, the resolved import is the specifier we'll evaluate going - // forward. - if (matched && resolvedImport?.protocol === 'file:') { - specifier = fileURLToPath(resolvedImport).replace(/\\/g, '/') - } - - // If the specifier is an HTTPS import, we need to resolve it to a local - // file first. - if (specifier.startsWith('https://')) { - const resolved = resolveHTTPSSpecifier(moduleGraph, specifier) - - if (resolved) { - if (resolved.isTypeScript) { - tsPaths.add(resolved.localPath) - } - - specifier = resolved.localPath - } - } - - return nftResolve(specifier, ...args) - }, - }) - - // Computing a stable hash of the file list. - const hash = getStringHash( - [...hashes.keys()] - .sort() - .map((path) => hashes.get(path)) - .filter(Boolean) - .join(','), - ) - - const absolutePaths = [...fileList].map((relativePath) => path.resolve(rootDirectory, relativePath)) + const rootLevel = await fs.readdir(distDirectory) + const hash = await getDirectoryHash(distDirectory) const tarballPath = path.join(distDirectory, buildID + TARBALL_EXTENSION) await fs.mkdir(path.dirname(tarballPath), { recursive: true }) await tar.create( { - cwd: rootDirectory, + cwd: distDirectory, file: tarballPath, preservePaths: true, onWriteEntry(entry) { if (entry.path === denoConfigPath) { - entry.path = `./${TARBALL_SRC_DIRECTORY}/deno.json` + entry.path = `./deno.json` return } if (entry.path === manifestPath) { - entry.path = `./${TARBALL_SRC_DIRECTORY}/___netlify-edge-functions.json` + entry.path = `./___netlify-edge-functions.json` return } - - if (entry.path.startsWith(denoDir)) { - const tarPath = getUnixPath(path.relative(denoDir, entry.path)) - - entry.path = `./deno_dir/${tarPath}` - - return - } - - const tarPath = getUnixPath(path.relative(basePath, entry.path)) - - entry.path = `./${TARBALL_SRC_DIRECTORY}/${tarPath}` }, }, - [...absolutePaths, manifestPath, denoConfigPath], + [...rootLevel, manifestPath, denoConfigPath], ) await Promise.all(cleanup) diff --git a/packages/edge-bundler/node/npm_dependencies.ts b/packages/edge-bundler/node/npm_dependencies.ts index 0fdc0306e4..4c72a927f0 100644 --- a/packages/edge-bundler/node/npm_dependencies.ts +++ b/packages/edge-bundler/node/npm_dependencies.ts @@ -12,7 +12,7 @@ import tmp from 'tmp-promise' import { ImportMap } from './import_map.js' import { Logger } from './logger.js' import { pathsBetween } from './utils/fs.js' -import { transpile, TYPESCRIPT_EXTENSIONS } from './utils/typescript.js' +import { TYPESCRIPT_EXTENSIONS } from './utils/typescript.js' const slugifyFileName = (specifier: string) => { return specifier.replace(/\//g, '_') diff --git a/packages/edge-bundler/node/utils/sha256.ts b/packages/edge-bundler/node/utils/sha256.ts index 135baee5c0..464349e882 100644 --- a/packages/edge-bundler/node/utils/sha256.ts +++ b/packages/edge-bundler/node/utils/sha256.ts @@ -1,6 +1,30 @@ import { Buffer } from 'node:buffer' import crypto from 'node:crypto' -import fs from 'node:fs' +import { createReadStream, promises as fs } from 'node:fs' +import path from 'node:path' + +export const getDirectoryHash = async (dirPath: string): Promise => { + const entries: string[] = [] + + async function walk(currentPath: string): Promise { + const dirents = await fs.readdir(currentPath, { withFileTypes: true }) + for (const dirent of dirents) { + const fullPath = path.join(currentPath, dirent.name) + const relativePath = path.relative(dirPath, fullPath) + + if (dirent.isDirectory()) { + await walk(fullPath) + } else if (dirent.isFile()) { + const fileHash = await getFileHash(fullPath) + entries.push(`${relativePath}:${fileHash}`) + } + } + } + + await walk(dirPath) + + return getStringHash(entries.join('\n')) +} export const getFileHash = (path: string): Promise => { const hash = crypto.createHash('sha256') @@ -8,7 +32,7 @@ export const getFileHash = (path: string): Promise => { hash.setEncoding('hex') return new Promise((resolve, reject) => { - const file = fs.createReadStream(path) + const file = createReadStream(path) file.on('end', () => { hash.end() @@ -31,7 +55,7 @@ export const getStringHash = (input: string) => { } export const readFileAndHash = (path: string) => { - const file = fs.createReadStream(path) + const file = createReadStream(path) const hash = crypto.createHash('sha256') const chunks: Uint8Array[] = [] @@ -48,7 +72,7 @@ export const readFileAndHash = (path: string) => { .on('end', () => { const contents = Buffer.concat(chunks).toString('utf8') - return resolve({ + resolve({ contents, hash: hash.digest('hex'), }) diff --git a/packages/edge-bundler/test/util.ts b/packages/edge-bundler/test/util.ts index dfc0db5022..01c99ab5f1 100644 --- a/packages/edge-bundler/test/util.ts +++ b/packages/edge-bundler/test/util.ts @@ -158,10 +158,7 @@ const runTarball = async (tarballPath: string) => { }) const evalCommand = execa('deno', ['eval', inspectTarballFunction()], { - cwd: join(tmpDir.path, 'src'), - env: { - DENO_DIR: '../deno_dir', - }, + cwd: tmpDir.path, }) evalCommand.stderr?.pipe(stderr) From 06e5536fe3ebcac649ba9c3dbff02365fde2e4e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Thu, 24 Jul 2025 16:24:34 +0100 Subject: [PATCH 14/33] chore: add comment --- deno.lock | 456 +++++++++++++++++++++ packages/edge-bundler/node/bundler.test.ts | 1 + 2 files changed, 457 insertions(+) create mode 100644 deno.lock diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000000..6883d2c327 --- /dev/null +++ b/deno.lock @@ -0,0 +1,456 @@ +{ + "version": "5", + "redirects": { + "https://ghuc.cc/worker-tools/html-rewriter/index.ts": "https://raw.githubusercontent.com/worker-tools/html-rewriter/master/index.ts", + "https://ghuc.cc/worker-tools/resolvable-promise/index.ts": "https://raw.githubusercontent.com/worker-tools/resolvable-promise/master/index.ts" + }, + "remote": { + "https://deno.land/std@0.134.0/fmt/colors.ts": "30455035d6d728394781c10755351742dd731e3db6771b1843f9b9e490104d37", + "https://deno.land/std@0.134.0/testing/_diff.ts": "9d849cd6877694152e01775b2d93f9d6b7aef7e24bfe3bfafc4d7a1ac8e9f392", + "https://deno.land/std@0.134.0/testing/asserts.ts": "b0ef969032882b1f7eb1c7571e313214baa1485f7b61cf35807b2434e254365c", + "https://deno.land/std@0.194.0/encoding/base64.ts": "144ae6234c1fbe5b68666c711dc15b1e9ee2aef6d42b3b4345bf9a6c91d70d0d", + "https://edge.netlify.com/": "fd941d61d88673d5f28aab283fb86fcc50f08a3bc80ee5470498fcfa88c65cfb", + "https://edge.netlify.com/bootstrap/config.ts": "6a2ce0e544e15e8f8883a5c18da5948e37fd0f2619f68cb31f3af53c51817025", + "https://edge.netlify.com/bootstrap/context.ts": "c6e9de479234f3ac500d2fc1544dadf1925e335d0d5551b1a71b7ce14423a683", + "https://edge.netlify.com/bootstrap/cookie.ts": "8b0baae708989ca183c6f3b4ab3d029e6abcbc2e43f93edeb0ff447b3bbc3a05", + "https://edge.netlify.com/bootstrap/edge_function.ts": "b8253e86aa83c67341f5cfedeba5049d77fbf84dcab7eceff7566b7728ae9b39", + "https://edge.netlify.com/bootstrap/globals/types.ts": "eaa6148ded3121d8dee62dd91c86e7fe76601df0f3ca8d7962243a30f4c8935f", + "https://raw.githubusercontent.com/worker-tools/html-rewriter/master/index.ts": "4e287b8929fbe8dba02de604f9d01b07323c81d1c62fef4094ccd10452493b11", + "https://raw.githubusercontent.com/worker-tools/html-rewriter/master/vendor/asyncify.js": "a70e36d0143de2de2e813917d740259ee71bcf1df75ceeaac0c1bc0ef7cc9a29", + "https://raw.githubusercontent.com/worker-tools/html-rewriter/master/vendor/html_rewriter.js": "32338151908217b07edd036ec81374df79b5aefd3b4069f04220ec330332cc2c", + "https://raw.githubusercontent.com/worker-tools/resolvable-promise/master/index.ts": "67d11f839d8e831074668e29e22c582d0ec0bbfcbf49d639ed33d9baffefed36" + }, + "workspace": { + "packageJson": { + "dependencies": [ + "npm:@commitlint/cli@19", + "npm:@commitlint/config-conventional@19", + "npm:@eslint/compat@^1.2.9", + "npm:@eslint/js@^9.28.0", + "npm:@typescript-eslint/eslint-plugin@^8.34.0", + "npm:@vitest/eslint-plugin@^1.2.1", + "npm:eslint-config-prettier@^10.1.5", + "npm:eslint-plugin-ava@^15.0.1", + "npm:eslint-plugin-import@^2.31.0", + "npm:eslint-plugin-n@^17.19.0", + "npm:eslint@^9.28.0", + "npm:execa@^8.0.1", + "npm:lerna@^8.2.2", + "npm:prettier@3", + "npm:typescript-eslint@^8.34.0" + ] + }, + "members": { + "packages/build": { + "packageJson": { + "dependencies": [ + "npm:@bugsnag/js@8", + "npm:@netlify/blobs@^10.0.6", + "npm:@netlify/cache-utils@^6.0.3", + "npm:@netlify/config@^23.2.0", + "npm:@netlify/edge-bundler@14.2.2", + "npm:@netlify/functions-utils@^6.2.0", + "npm:@netlify/git-utils@^6.0.2", + "npm:@netlify/nock-udp@^5.0.1", + "npm:@netlify/opentelemetry-utils@^2.0.1", + "npm:@netlify/plugins-list@^6.80.0", + "npm:@netlify/run-utils@^6.0.2", + "npm:@netlify/zip-it-and-ship-it@14.1.0", + "npm:@opentelemetry/api@1.8", + "npm:@opentelemetry/sdk-trace-base@1.24", + "npm:@sindresorhus/slugify@2", + "npm:@types/node@^18.19.111", + "npm:ansi-escapes@7", + "npm:atob@^2.1.2", + "npm:ava@5", + "npm:c8@10", + "npm:chalk@5", + "npm:clean-stack@5", + "npm:cpy-cli@5", + "npm:cpy@11", + "npm:execa@8", + "npm:fdir@^6.0.1", + "npm:figures@6", + "npm:filter-obj@6", + "npm:get-node@^14.2.1", + "npm:get-port@7", + "npm:has-ansi@6", + "npm:hot-shots@11.1.0", + "npm:indent-string@5", + "npm:is-plain-obj@4", + "npm:keep-func-props@6", + "npm:locate-path@7", + "npm:log-process-errors@11", + "npm:map-obj@5", + "npm:memoize-one@6", + "npm:minimatch@^9.0.4", + "npm:moize@6", + "npm:npm-run-all2@6", + "npm:os-name@6", + "npm:p-event@6", + "npm:p-every@2", + "npm:p-filter@4", + "npm:p-locate@6", + "npm:p-map@7", + "npm:p-reduce@3", + "npm:package-directory@8", + "npm:path-exists@5", + "npm:path-type@6", + "npm:pretty-ms@9", + "npm:process-exists@5", + "npm:ps-list@8", + "npm:read-package-up@11", + "npm:readdirp@4", + "npm:resolve@^2.0.0-next.5", + "npm:rfdc@^1.3.0", + "npm:safe-json-stringify@^1.2.0", + "npm:semver@^7.3.8", + "npm:sinon@21", + "npm:string-width@7", + "npm:strip-ansi@7", + "npm:supports-color@10", + "npm:terminal-link@4", + "npm:tmp-promise@^3.0.2", + "npm:ts-node@^10.9.1", + "npm:tsd@0.32", + "npm:typescript@5", + "npm:uuid@11", + "npm:vitest@3", + "npm:yaml@^2.8.0", + "npm:yargs@^17.6.0", + "npm:yarn@^1.22.22" + ] + } + }, + "packages/build-info": { + "packageJson": { + "dependencies": [ + "npm:@bugsnag/js@8", + "npm:@iarna/toml@^2.2.5", + "npm:@playwright/test@^1.53.1", + "npm:@types/node@^18.19.111", + "npm:@types/semver@^7.3.13", + "npm:@vitest/ui@3", + "npm:dot-prop@9", + "npm:execa@8", + "npm:find-up@7", + "npm:memfs@4", + "npm:minimatch@9", + "npm:read-pkg@9", + "npm:semver@^7.3.8", + "npm:typescript@5", + "npm:unionfs@^4.4.0", + "npm:vitest@3", + "npm:yaml@^2.8.0", + "npm:yargs@^17.6.0" + ] + } + }, + "packages/cache-utils": { + "packageJson": { + "dependencies": [ + "npm:@types/node@^18.19.111", + "npm:cpy@11", + "npm:get-stream@9", + "npm:globby@14", + "npm:junk@4", + "npm:locate-path@7", + "npm:move-file@3", + "npm:path-exists@5", + "npm:readdirp@4", + "npm:tmp-promise@3", + "npm:typescript@5", + "npm:vitest@3" + ] + } + }, + "packages/config": { + "packageJson": { + "dependencies": [ + "npm:@iarna/toml@^2.2.5", + "npm:@netlify/api@^14.0.3", + "npm:@netlify/headers-parser@^9.0.1", + "npm:@netlify/redirect-parser@^15.0.2", + "npm:@types/node@^18.19.111", + "npm:ava@5", + "npm:c8@10", + "npm:chalk@5", + "npm:cron-parser@^4.1.0", + "npm:deepmerge@^4.2.2", + "npm:dot-prop@9", + "npm:execa@8", + "npm:fast-safe-stringify@^2.0.7", + "npm:figures@6", + "npm:filter-obj@6", + "npm:find-up@7", + "npm:has-ansi@6", + "npm:indent-string@5", + "npm:is-ci@4", + "npm:is-plain-obj@4", + "npm:map-obj@5", + "npm:omit.js@^2.0.2", + "npm:p-locate@6", + "npm:path-type@6", + "npm:read-package-up@11", + "npm:tmp-promise@^3.0.2", + "npm:tomlify-j0.4@3", + "npm:typescript@5", + "npm:validate-npm-package-name@5", + "npm:yaml@^2.8.0", + "npm:yargs@^17.6.0" + ] + } + }, + "packages/edge-bundler": { + "packageJson": { + "dependencies": [ + "npm:@import-maps/resolve@2", + "npm:@types/node@^18.19.111", + "npm:@types/semver@^7.3.9", + "npm:@types/uuid@10", + "npm:@vitest/coverage-v8@3", + "npm:ajv-errors@3", + "npm:ajv@^8.11.2", + "npm:archiver@7", + "npm:better-ajv-errors@^1.2.0", + "npm:chalk@^5.4.0", + "npm:common-path-prefix@3", + "npm:cpy@^11.1.0", + "npm:env-paths@3", + "npm:esbuild@0.25.6", + "npm:execa@8", + "npm:find-up@7", + "npm:get-package-name@^2.2.0", + "npm:get-port@7", + "npm:is-path-inside@4", + "npm:nock@14", + "npm:node-stream-zip@^1.15.0", + "npm:npm-run-all2@6", + "npm:p-retry@6", + "npm:p-wait-for@5", + "npm:parse-imports@^2.2.1", + "npm:path-key@4", + "npm:semver@^7.3.8", + "npm:tar@^7.4.3", + "npm:tmp-promise@^3.0.3", + "npm:typescript@5", + "npm:urlpattern-polyfill@8.0.2", + "npm:uuid@11", + "npm:vitest@3" + ] + } + }, + "packages/functions-utils": { + "packageJson": { + "dependencies": [ + "npm:@netlify/zip-it-and-ship-it@14.1.0", + "npm:@types/node@^18.19.111", + "npm:cpy@11", + "npm:path-exists@5", + "npm:sort-on@6", + "npm:tmp-promise@3", + "npm:typescript@5", + "npm:vitest@3" + ] + } + }, + "packages/git-utils": { + "packageJson": { + "dependencies": [ + "npm:@types/node@^18.19.111", + "npm:execa@8", + "npm:map-obj@5", + "npm:micromatch@^4.0.2", + "npm:moize@^6.1.3", + "npm:typescript@5", + "npm:vitest@3" + ] + } + }, + "packages/headers-parser": { + "packageJson": { + "dependencies": [ + "npm:@iarna/toml@^2.2.5", + "npm:@types/node@^18.19.111", + "npm:escape-string-regexp@5", + "npm:fast-safe-stringify@^2.0.7", + "npm:is-plain-obj@4", + "npm:map-obj@5", + "npm:path-exists@5", + "npm:typescript@5", + "npm:vitest@3" + ] + } + }, + "packages/js-client": { + "packageJson": { + "dependencies": [ + "npm:@netlify/open-api@^2.37.0", + "npm:@types/lodash-es@^4.17.6", + "npm:@types/node@^18.19.111", + "npm:from2-string@^1.1.0", + "npm:lodash-es@^4.17.21", + "npm:nock@13", + "npm:node-fetch@3", + "npm:p-wait-for@5", + "npm:qs@^6.9.6", + "npm:ts-node@^10.9.1", + "npm:typescript@5", + "npm:uuid@11", + "npm:vitest@^3.2.3" + ] + } + }, + "packages/nock-udp": { + "packageJson": { + "dependencies": [ + "npm:@types/node@^18.19.111", + "npm:typescript@5", + "npm:vitest@3" + ] + } + }, + "packages/opentelemetry-sdk-setup": { + "packageJson": { + "dependencies": [ + "npm:@honeycombio/opentelemetry-node@~0.7.1", + "npm:@netlify/opentelemetry-utils@~2.0.1", + "npm:@opentelemetry/api@1.8", + "npm:@opentelemetry/core@1.24", + "npm:@opentelemetry/resources@1.24", + "npm:@opentelemetry/sdk-trace-base@1.24", + "npm:@opentelemetry/semantic-conventions@1.24", + "npm:@types/node@^18.19.111", + "npm:@vitest/ui@3", + "npm:read-package-up@11", + "npm:typescript@5", + "npm:vitest@3", + "npm:yargs-parser@^21.1.1" + ] + } + }, + "packages/opentelemetry-utils": { + "packageJson": { + "dependencies": [ + "npm:@opentelemetry/api@1.8", + "npm:@opentelemetry/sdk-trace-base@1.24", + "npm:@opentelemetry/sdk-trace-node@1.24", + "npm:@types/node@^18.19.111", + "npm:@vitest/ui@3", + "npm:typescript@5", + "npm:vitest@3" + ] + } + }, + "packages/redirect-parser": { + "packageJson": { + "dependencies": [ + "npm:@iarna/toml@^2.2.5", + "npm:@types/node@^18.19.111", + "npm:fast-safe-stringify@^2.1.1", + "npm:filter-obj@6", + "npm:is-plain-obj@4", + "npm:path-exists@5", + "npm:typescript@5", + "npm:vitest@3" + ] + } + }, + "packages/run-utils": { + "packageJson": { + "dependencies": [ + "npm:@types/node@^18.19.111", + "npm:execa@8", + "npm:semver@^7.3.8", + "npm:typescript@5", + "npm:vitest@3" + ] + } + }, + "packages/testing": { + "packageJson": { + "dependencies": [ + "npm:@netlify/build@*", + "npm:@netlify/config@*", + "npm:@types/lodash-es@^4.17.6", + "npm:@types/node@^18.19.111", + "npm:ava@5", + "npm:c8@10", + "npm:cpy@11", + "npm:execa@8", + "npm:fast-safe-stringify@^2.0.7", + "npm:figures@6", + "npm:get-bin-path@11", + "npm:get-port@7", + "npm:get-stream@9", + "npm:is-plain-obj@4", + "npm:lodash-es@^4.17.21", + "npm:path-key@4", + "npm:strip-ansi@7", + "npm:tmp-promise@^3.0.2", + "npm:typescript@5" + ] + } + }, + "packages/zip-it-and-ship-it": { + "packageJson": { + "dependencies": [ + "npm:@babel/parser@^7.22.5", + "npm:@babel/types@7.28.1", + "npm:@netlify/binary-info@1", + "npm:@netlify/serverless-functions-api@^2.1.3", + "npm:@types/archiver@6.0.3", + "npm:@types/is-ci@3.0.4", + "npm:@types/node@20.12.11", + "npm:@types/normalize-path@3.0.2", + "npm:@types/resolve@1.20.6", + "npm:@types/semver@7.7.0", + "npm:@types/unixify@1.0.2", + "npm:@types/yargs@17.0.33", + "npm:@vercel/nft@0.29.4", + "npm:@vitest/coverage-v8@3", + "npm:archiver@7", + "npm:browserslist@4.25.1", + "npm:cardinal@2.1.1", + "npm:common-path-prefix@3", + "npm:copy-file@11", + "npm:cpy@11.1.0", + "npm:decompress@4.2.1", + "npm:deepmerge@^4.3.1", + "npm:es-module-lexer@1", + "npm:esbuild@0.25.6", + "npm:execa@8", + "npm:fast-glob@^3.3.3", + "npm:filter-obj@6", + "npm:find-up@7", + "npm:get-stream@9.0.1", + "npm:is-builtin-module@^3.1.0", + "npm:is-ci@4.1.0", + "npm:is-path-inside@4", + "npm:junk@4", + "npm:lambda-local@2.2.0", + "npm:locate-path@7", + "npm:merge-options@^3.0.4", + "npm:minimatch@9", + "npm:normalize-path@3", + "npm:p-map@7", + "npm:path-exists@5", + "npm:precinct@12", + "npm:require-package-name@^2.0.1", + "npm:resolve@^2.0.0-next.1", + "npm:semver@^7.3.8", + "npm:source-map-support@0.5.21", + "npm:tmp-promise@^3.0.2", + "npm:toml@3", + "npm:typescript@5.8.3", + "npm:unixify@1", + "npm:urlpattern-polyfill@8.0.2", + "npm:vitest@3", + "npm:yargs@17", + "npm:zod@^3.23.8" + ] + } + } + } + } +} diff --git a/packages/edge-bundler/node/bundler.test.ts b/packages/edge-bundler/node/bundler.test.ts index ea0f6000e5..3cfe53ef23 100644 --- a/packages/edge-bundler/node/bundler.test.ts +++ b/packages/edge-bundler/node/bundler.test.ts @@ -749,6 +749,7 @@ describe('Produces a tarball bundle', () => { await rm(vendorDirectory.path, { force: true, recursive: true }) }) + // TODO: https://github.com/denoland/deno/issues/30187 test.todo('Using npm modules', async () => { const systemLogger = vi.fn() const { basePath, cleanup, distPath } = await useFixture('imports_npm_module', { copyDirectory: true }) From 17c7d4e8089c356375abea723e45391673624586 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Thu, 24 Jul 2025 16:29:11 +0100 Subject: [PATCH 15/33] chore: update Deno version --- .github/workflows/workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 7c6f926dc1..db786dbf6b 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -50,12 +50,12 @@ jobs: os: [ubuntu-24.04, macos-14, windows-2025] node-version: ['22'] # Must include the minimum deno version from the `DENO_VERSION_RANGE` constant in `node/bridge.ts`. - deno-version: ['v1.39.0', 'v2.2.4'] + deno-version: ['v2.4.2'] include: - os: ubuntu-24.04 # Earliest supported version node-version: '18.14.0' - deno-version: 'v2.2.4' + deno-version: 'v2.4.2' fail-fast: false steps: # Sets an output parameter if this is a release PR From 5091196ee44255ea667d1ba02e285c59e198501b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Fri, 25 Jul 2025 11:20:20 +0100 Subject: [PATCH 16/33] refactor: remove unused files --- packages/edge-bundler/deno.lock | 111 ------------------ packages/edge-bundler/node/utils/sha256.ts | 27 ----- .../edge-bundler/node/utils/typescript.ts | 21 ---- 3 files changed, 159 deletions(-) delete mode 100644 packages/edge-bundler/deno.lock diff --git a/packages/edge-bundler/deno.lock b/packages/edge-bundler/deno.lock deleted file mode 100644 index 03c7fe213a..0000000000 --- a/packages/edge-bundler/deno.lock +++ /dev/null @@ -1,111 +0,0 @@ -{ - "version": "3", - "remote": { - "https://edge.netlify.com/": "fd941d61d88673d5f28aab283fb86fcc50f08a3bc80ee5470498fcfa88c65cfb", - "https://edge.netlify.com/bootstrap/account.ts": "192cb0b87d19640058bb28e7a9d46c59ba77e0bf41b89708e23938cca2644e06", - "https://edge.netlify.com/bootstrap/blobs.ts": "3b46e2af986f4675e119810304ed681e66b8ea49adb407f38f76c5ff4bb4f05f", - "https://edge.netlify.com/bootstrap/bypass.ts": "cf3423a7e2b9f136e84b93dfedbe7d27887c75e68e20619b2ffd891919f27c3e", - "https://edge.netlify.com/bootstrap/cache.ts": "82af3afd301482592a02bdc7fced77620b42e4dd9414bf60510f559a5af15c84", - "https://edge.netlify.com/bootstrap/config.ts": "6a2ce0e544e15e8f8883a5c18da5948e37fd0f2619f68cb31f3af53c51817025", - "https://edge.netlify.com/bootstrap/context.ts": "c6e9de479234f3ac500d2fc1544dadf1925e335d0d5551b1a71b7ce14423a683", - "https://edge.netlify.com/bootstrap/cookie.ts": "8b0baae708989ca183c6f3b4ab3d029e6abcbc2e43f93edeb0ff447b3bbc3a05", - "https://edge.netlify.com/bootstrap/cookie_store.ts": "2a1da3b09a35f8b31c82f91f182cf4dd43bc03c3e48036563d3803b7dd5b52c4", - "https://edge.netlify.com/bootstrap/deno-fs.ts": "ccd0a9335de14e01d77e8937e2320c99a110f5f2a473b80126750e3884ef952a", - "https://edge.netlify.com/bootstrap/edge_function.ts": "b8253e86aa83c67341f5cfedeba5049d77fbf84dcab7eceff7566b7728ae9b39", - "https://edge.netlify.com/bootstrap/environment.ts": "6d8bfb2359f170db4a1e1513904e9610ed047d7c1187cc0ceb4f0356231fbdde", - "https://edge.netlify.com/bootstrap/feature_flags.ts": "efab0245acb8436e897b245d0cc5f3226c364395d3dca8d65442cc1676fa35c6", - "https://edge.netlify.com/bootstrap/function_chain.ts": "7d2b6692385fa7f69507f50904423fe99bc66b4236e8d3dc32a0011208d64a39", - "https://edge.netlify.com/bootstrap/geo.ts": "f5c8edca3a460c82c2e9300270b2d270531efa995cc8c2c0b239713775bf6fe5", - "https://edge.netlify.com/bootstrap/globals/implementation.ts": "dbfb9b73fcbd2b7ce56260ea7b6062075702dcbab75403fcd8ea454fb7bfb5c4", - "https://edge.netlify.com/bootstrap/globals/types.ts": "eaa6148ded3121d8dee62dd91c86e7fe76601df0f3ca8d7962243a30f4c8935f", - "https://edge.netlify.com/bootstrap/handler.ts": "52732f96e7fd3a7a275ada1b83702fe724afb83d0458af47a740e267d7941372", - "https://edge.netlify.com/bootstrap/headers.ts": "f18295c406bcd22bbc751b4da02e36854dda837ab1cf02b03b44f475a660a4b8", - "https://edge.netlify.com/bootstrap/index-combined.ts": "92dc4703e8c779b24da845f4279353716a903c91f4fa406640d917303dcd6875", - "https://edge.netlify.com/bootstrap/invocation_metadata.ts": "457e73cb43dbe7695b32065f24a3d1e8e935efa517a2f25e2fd79977ea437f57", - "https://edge.netlify.com/bootstrap/log/instrumented_log.ts": "2331dce64d32b51e301d5139fc403792162638ff603e482c381ac27e932d7f95", - "https://edge.netlify.com/bootstrap/log/logger.ts": "74b7e7fb58654678a2eaddb3257f279056a5cbec884b355e5b62ac9e582496fa", - "https://edge.netlify.com/bootstrap/metrics.ts": "a585daf2bffa339b3be707e6f9e8d9c65a78a820513557f7b0806f3895e1c295", - "https://edge.netlify.com/bootstrap/path_parameters.ts": "8266dd6cbb4dbf859aa3536b1250b4daace8f00b2c82e7042006d8d0e448dd52", - "https://edge.netlify.com/bootstrap/request.ts": "8d5d23b9c4936751d7df6d6da92d9d77b7226c1615ac4cb1acb3f47f5fb0ae2e", - "https://edge.netlify.com/bootstrap/response.ts": "4e0d2537f810028ddbe91138b3526dae3f4c3fb30ff17673342ca971207c21b7", - "https://edge.netlify.com/bootstrap/retry.ts": "196357ed0af0b77a6919bcc6c10c42a22742dd099a7343c1227eb35b1fdebc5c", - "https://edge.netlify.com/bootstrap/router.ts": "d753b1b1c561ddd25bf4076450b2cf667fa5ec77187de5dd9842757977a6ba35", - "https://edge.netlify.com/bootstrap/server.ts": "4744e7ba8b2bc689eced7770eb43af875a5f26c9bd2fbf891220cb1c5c90f4ad", - "https://edge.netlify.com/bootstrap/site.ts": "ae2fe4267a6101fa93c1ccd36dfa4a7b084d66aeeded337fa2683b8904718ee1", - "https://edge.netlify.com/bootstrap/stage_2.ts": "a90258046f480d1e423c140d5cdbf305c860283e021d2bcf5eff36d5d6929114", - "https://edge.netlify.com/bootstrap/util/errors.ts": "8e14c65504f204c1773342a68a376995fe45d9397b6e3f453d19940270ebf707", - "https://edge.netlify.com/bootstrap/util/execution_context.ts": "a8cb042ab7f91d41cb88940c0c9a3a44ef931516be7084f80d2b429f5649dbc7", - "https://edge.netlify.com/bootstrap/util/fetch.ts": "4c12d853804c7594754a110f83f8144e7c91e52eec5e9693c6b3642ac59dc044", - "https://edge.netlify.com/bootstrap/util/patch_globals.ts": "70ccedcf3790aa97e3570efaca93973b2ef281c59cfb9e173073913553c81872", - "https://edge.netlify.com/bootstrap/util/redirect.ts": "30c76d79832f9b3af21fbe31e29c841f9c3ab85fb34cac80e1e6a5ab7de55551", - "https://edge.netlify.com/bootstrap/util/stack_tracer.ts": "20e47bfb3c365be712067dd62457ac20b6204420ca7250d9e607665c2b4a2823", - "https://edge.netlify.com/vendor/deno.land/std@0.170.0/_util/asserts.ts": "d0844e9b62510f89ce1f9878b046f6a57bf88f208a10304aab50efcb48365272", - "https://edge.netlify.com/vendor/deno.land/std@0.170.0/_util/os.ts": "8a33345f74990e627b9dfe2de9b040004b08ea5146c7c9e8fe9a29070d193934", - "https://edge.netlify.com/vendor/deno.land/std@0.170.0/async/abortable.ts": "80b2ac399f142cc528f95a037a7d0e653296352d95c681e284533765961de409", - "https://edge.netlify.com/vendor/deno.land/std@0.170.0/async/deadline.ts": "2c2deb53c7c28ca1dda7a3ad81e70508b1ebc25db52559de6b8636c9278fd41f", - "https://edge.netlify.com/vendor/deno.land/std@0.170.0/async/debounce.ts": "60301ffb37e730cd2d6f9dadfd0ecb2a38857681bd7aaf6b0a106b06e5210a98", - "https://edge.netlify.com/vendor/deno.land/std@0.170.0/async/deferred.ts": "77d3f84255c3627f1cc88699d8472b664d7635990d5358c4351623e098e917d6", - "https://edge.netlify.com/vendor/deno.land/std@0.170.0/async/delay.ts": "5a9bfba8de38840308a7a33786a0155a7f6c1f7a859558ddcec5fe06e16daf57", - "https://edge.netlify.com/vendor/deno.land/std@0.170.0/async/mod.ts": "7809ad4bb223e40f5fdc043e5c7ca04e0e25eed35c32c3c32e28697c553fa6d9", - "https://edge.netlify.com/vendor/deno.land/std@0.170.0/async/mux_async_iterator.ts": "770a0ff26c59f8bbbda6b703a2235f04e379f73238e8d66a087edc68c2a2c35f", - "https://edge.netlify.com/vendor/deno.land/std@0.170.0/async/pool.ts": "6854d8cd675a74c73391c82005cbbe4cc58183bddcd1fbbd7c2bcda42b61cf69", - "https://edge.netlify.com/vendor/deno.land/std@0.170.0/async/retry.ts": "e8e5173623915bbc0ddc537698fa418cf875456c347eda1ed453528645b42e67", - "https://edge.netlify.com/vendor/deno.land/std@0.170.0/async/tee.ts": "3a47cc4e9a940904fd4341f0224907e199121c80b831faa5ec2b054c6d2eff5e", - "https://edge.netlify.com/vendor/deno.land/std@0.170.0/datetime/to_imf.ts": "bdb0704a986a9bb48fba220722b3d4681e4b8c9c162562ee545ef2eb90b6edcd", - "https://edge.netlify.com/vendor/deno.land/std@0.170.0/encoding/base64.ts": "8605e018e49211efc767686f6f687827d7f5fd5217163e981d8d693105640d7a", - "https://edge.netlify.com/vendor/deno.land/std@0.170.0/flags/mod.ts": "4f50ec6383c02684db35de38b3ffb2cd5b9fcfcc0b1147055d1980c49e82521c", - "https://edge.netlify.com/vendor/deno.land/std@0.170.0/http/cookie.ts": "581464551b3d91739f699fbd6a429e6e8701ae252cf295efcda7379e8410d204", - "https://edge.netlify.com/vendor/deno.land/std@0.170.0/http/http_status.ts": "ed24048cc0d06066c944da59b0301da3ae2f990564bb4ad79bb52a09cf8e9b30", - "https://edge.netlify.com/vendor/deno.land/std@0.170.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3", - "https://edge.netlify.com/vendor/deno.land/std@0.170.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09", - "https://edge.netlify.com/vendor/deno.land/std@0.170.0/path/_util.ts": "d16be2a16e1204b65f9d0dfc54a9bc472cafe5f4a190b3c8471ec2016ccd1677", - "https://edge.netlify.com/vendor/deno.land/std@0.170.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633", - "https://edge.netlify.com/vendor/deno.land/std@0.170.0/path/glob.ts": "81cc6c72be002cd546c7a22d1f263f82f63f37fe0035d9726aa96fc8f6e4afa1", - "https://edge.netlify.com/vendor/deno.land/std@0.170.0/path/mod.ts": "cf7cec7ac11b7048bb66af8ae03513e66595c279c65cfa12bfc07d9599608b78", - "https://edge.netlify.com/vendor/deno.land/std@0.170.0/path/posix.ts": "b859684bc4d80edfd4cad0a82371b50c716330bed51143d6dcdbe59e6278b30c", - "https://edge.netlify.com/vendor/deno.land/std@0.170.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9", - "https://edge.netlify.com/vendor/deno.land/std@0.170.0/path/win32.ts": "7cebd2bda6657371adc00061a1d23fdd87bcdf64b4843bb148b0b24c11b40f69", - "https://edge.netlify.com/vendor/esm.sh/@netlify/cache@1.7.1/denonext/dist/bootstrap/main.mjs": "e5cb61f205526ccec8e47f79bb1e94c20ca5a5e1078f1de4b9ed262ac18cf4ae", - "https://edge.netlify.com/vendor/v1-8-0--edge-utils.netlify.app/logger/logger.ts": "8bd7a5e28b9be06f806f429be853ef865055d100a63f21ebfa2522a5bb3ab23a", - "https://edge.netlify.com/vendor/v1-8-0--edge-utils.netlify.app/logger/mod.ts": "669a5ec2dceede2926b0e1a7471c449a445f957c1deac559e07fb4c2af390ed1" - }, - "workspace": { - "packageJson": { - "dependencies": [ - "npm:@import-maps/resolve@^2.0.0", - "npm:@types/node@^18.19.111", - "npm:@types/semver@^7.3.9", - "npm:@types/uuid@^10.0.0", - "npm:@vitest/coverage-v8@^3.0.0", - "npm:ajv-errors@^3.0.0", - "npm:ajv@^8.11.2", - "npm:archiver@^7.0.0", - "npm:better-ajv-errors@^1.2.0", - "npm:chalk@^5.4.0", - "npm:common-path-prefix@^3.0.0", - "npm:cpy@^11.1.0", - "npm:env-paths@^3.0.0", - "npm:esbuild@0.25.6", - "npm:execa@^8.0.0", - "npm:find-up@^7.0.0", - "npm:get-package-name@^2.2.0", - "npm:get-port@^7.0.0", - "npm:is-path-inside@^4.0.0", - "npm:nock@^14.0.0", - "npm:node-stream-zip@^1.15.0", - "npm:npm-run-all2@^6.0.0", - "npm:p-retry@^6.0.0", - "npm:p-wait-for@^5.0.0", - "npm:parse-imports@^2.2.1", - "npm:path-key@^4.0.0", - "npm:semver@^7.3.8", - "npm:tar@^7.4.3", - "npm:tmp-promise@^3.0.3", - "npm:typescript@^5.0.0", - "npm:urlpattern-polyfill@8.0.2", - "npm:uuid@^11.0.0", - "npm:vitest@^3.0.0" - ] - } - } -} diff --git a/packages/edge-bundler/node/utils/sha256.ts b/packages/edge-bundler/node/utils/sha256.ts index 464349e882..fdce5853dc 100644 --- a/packages/edge-bundler/node/utils/sha256.ts +++ b/packages/edge-bundler/node/utils/sha256.ts @@ -1,4 +1,3 @@ -import { Buffer } from 'node:buffer' import crypto from 'node:crypto' import { createReadStream, promises as fs } from 'node:fs' import path from 'node:path' @@ -53,29 +52,3 @@ export const getStringHash = (input: string) => { return hash.digest('hex') } - -export const readFileAndHash = (path: string) => { - const file = createReadStream(path) - const hash = crypto.createHash('sha256') - const chunks: Uint8Array[] = [] - - hash.setEncoding('hex') - - return new Promise<{ contents: string; hash: string }>((resolve, reject) => { - file - .on('data', (chunk) => { - chunks.push(Buffer.from(chunk)) - - hash.update(chunk.toString()) - }) - .on('error', reject) - .on('end', () => { - const contents = Buffer.concat(chunks).toString('utf8') - - resolve({ - contents, - hash: hash.digest('hex'), - }) - }) - }) -} diff --git a/packages/edge-bundler/node/utils/typescript.ts b/packages/edge-bundler/node/utils/typescript.ts index c0ffe8e2b2..be8bf82295 100644 --- a/packages/edge-bundler/node/utils/typescript.ts +++ b/packages/edge-bundler/node/utils/typescript.ts @@ -1,22 +1 @@ -import { build } from 'esbuild' - export const TYPESCRIPT_EXTENSIONS = new Set(['.ts', '.tsx', '.cts', '.ctsx', '.mts', '.mtsx']) - -export const transpile = async (filePath: string) => { - const result = await build({ - bundle: false, - entryPoints: [filePath], - loader: { - // esbuild uses the extension of each entrypoint to determine the - // right loader to use. This doesn't work with the internal files - // from the Deno cache, so we must tell it to use the TypeScript - // loader for any files without an extension. - '': 'ts', - }, - logLevel: 'silent', - platform: 'node', - write: false, - }) - - return result.outputFiles[0].text -} From 56eb72554423ac28527fe138ebc305d037f31dee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Fri, 25 Jul 2025 11:25:36 +0100 Subject: [PATCH 17/33] chore: remove lock file --- deno.lock | 456 ------------------------------------------------------ 1 file changed, 456 deletions(-) delete mode 100644 deno.lock diff --git a/deno.lock b/deno.lock deleted file mode 100644 index 6883d2c327..0000000000 --- a/deno.lock +++ /dev/null @@ -1,456 +0,0 @@ -{ - "version": "5", - "redirects": { - "https://ghuc.cc/worker-tools/html-rewriter/index.ts": "https://raw.githubusercontent.com/worker-tools/html-rewriter/master/index.ts", - "https://ghuc.cc/worker-tools/resolvable-promise/index.ts": "https://raw.githubusercontent.com/worker-tools/resolvable-promise/master/index.ts" - }, - "remote": { - "https://deno.land/std@0.134.0/fmt/colors.ts": "30455035d6d728394781c10755351742dd731e3db6771b1843f9b9e490104d37", - "https://deno.land/std@0.134.0/testing/_diff.ts": "9d849cd6877694152e01775b2d93f9d6b7aef7e24bfe3bfafc4d7a1ac8e9f392", - "https://deno.land/std@0.134.0/testing/asserts.ts": "b0ef969032882b1f7eb1c7571e313214baa1485f7b61cf35807b2434e254365c", - "https://deno.land/std@0.194.0/encoding/base64.ts": "144ae6234c1fbe5b68666c711dc15b1e9ee2aef6d42b3b4345bf9a6c91d70d0d", - "https://edge.netlify.com/": "fd941d61d88673d5f28aab283fb86fcc50f08a3bc80ee5470498fcfa88c65cfb", - "https://edge.netlify.com/bootstrap/config.ts": "6a2ce0e544e15e8f8883a5c18da5948e37fd0f2619f68cb31f3af53c51817025", - "https://edge.netlify.com/bootstrap/context.ts": "c6e9de479234f3ac500d2fc1544dadf1925e335d0d5551b1a71b7ce14423a683", - "https://edge.netlify.com/bootstrap/cookie.ts": "8b0baae708989ca183c6f3b4ab3d029e6abcbc2e43f93edeb0ff447b3bbc3a05", - "https://edge.netlify.com/bootstrap/edge_function.ts": "b8253e86aa83c67341f5cfedeba5049d77fbf84dcab7eceff7566b7728ae9b39", - "https://edge.netlify.com/bootstrap/globals/types.ts": "eaa6148ded3121d8dee62dd91c86e7fe76601df0f3ca8d7962243a30f4c8935f", - "https://raw.githubusercontent.com/worker-tools/html-rewriter/master/index.ts": "4e287b8929fbe8dba02de604f9d01b07323c81d1c62fef4094ccd10452493b11", - "https://raw.githubusercontent.com/worker-tools/html-rewriter/master/vendor/asyncify.js": "a70e36d0143de2de2e813917d740259ee71bcf1df75ceeaac0c1bc0ef7cc9a29", - "https://raw.githubusercontent.com/worker-tools/html-rewriter/master/vendor/html_rewriter.js": "32338151908217b07edd036ec81374df79b5aefd3b4069f04220ec330332cc2c", - "https://raw.githubusercontent.com/worker-tools/resolvable-promise/master/index.ts": "67d11f839d8e831074668e29e22c582d0ec0bbfcbf49d639ed33d9baffefed36" - }, - "workspace": { - "packageJson": { - "dependencies": [ - "npm:@commitlint/cli@19", - "npm:@commitlint/config-conventional@19", - "npm:@eslint/compat@^1.2.9", - "npm:@eslint/js@^9.28.0", - "npm:@typescript-eslint/eslint-plugin@^8.34.0", - "npm:@vitest/eslint-plugin@^1.2.1", - "npm:eslint-config-prettier@^10.1.5", - "npm:eslint-plugin-ava@^15.0.1", - "npm:eslint-plugin-import@^2.31.0", - "npm:eslint-plugin-n@^17.19.0", - "npm:eslint@^9.28.0", - "npm:execa@^8.0.1", - "npm:lerna@^8.2.2", - "npm:prettier@3", - "npm:typescript-eslint@^8.34.0" - ] - }, - "members": { - "packages/build": { - "packageJson": { - "dependencies": [ - "npm:@bugsnag/js@8", - "npm:@netlify/blobs@^10.0.6", - "npm:@netlify/cache-utils@^6.0.3", - "npm:@netlify/config@^23.2.0", - "npm:@netlify/edge-bundler@14.2.2", - "npm:@netlify/functions-utils@^6.2.0", - "npm:@netlify/git-utils@^6.0.2", - "npm:@netlify/nock-udp@^5.0.1", - "npm:@netlify/opentelemetry-utils@^2.0.1", - "npm:@netlify/plugins-list@^6.80.0", - "npm:@netlify/run-utils@^6.0.2", - "npm:@netlify/zip-it-and-ship-it@14.1.0", - "npm:@opentelemetry/api@1.8", - "npm:@opentelemetry/sdk-trace-base@1.24", - "npm:@sindresorhus/slugify@2", - "npm:@types/node@^18.19.111", - "npm:ansi-escapes@7", - "npm:atob@^2.1.2", - "npm:ava@5", - "npm:c8@10", - "npm:chalk@5", - "npm:clean-stack@5", - "npm:cpy-cli@5", - "npm:cpy@11", - "npm:execa@8", - "npm:fdir@^6.0.1", - "npm:figures@6", - "npm:filter-obj@6", - "npm:get-node@^14.2.1", - "npm:get-port@7", - "npm:has-ansi@6", - "npm:hot-shots@11.1.0", - "npm:indent-string@5", - "npm:is-plain-obj@4", - "npm:keep-func-props@6", - "npm:locate-path@7", - "npm:log-process-errors@11", - "npm:map-obj@5", - "npm:memoize-one@6", - "npm:minimatch@^9.0.4", - "npm:moize@6", - "npm:npm-run-all2@6", - "npm:os-name@6", - "npm:p-event@6", - "npm:p-every@2", - "npm:p-filter@4", - "npm:p-locate@6", - "npm:p-map@7", - "npm:p-reduce@3", - "npm:package-directory@8", - "npm:path-exists@5", - "npm:path-type@6", - "npm:pretty-ms@9", - "npm:process-exists@5", - "npm:ps-list@8", - "npm:read-package-up@11", - "npm:readdirp@4", - "npm:resolve@^2.0.0-next.5", - "npm:rfdc@^1.3.0", - "npm:safe-json-stringify@^1.2.0", - "npm:semver@^7.3.8", - "npm:sinon@21", - "npm:string-width@7", - "npm:strip-ansi@7", - "npm:supports-color@10", - "npm:terminal-link@4", - "npm:tmp-promise@^3.0.2", - "npm:ts-node@^10.9.1", - "npm:tsd@0.32", - "npm:typescript@5", - "npm:uuid@11", - "npm:vitest@3", - "npm:yaml@^2.8.0", - "npm:yargs@^17.6.0", - "npm:yarn@^1.22.22" - ] - } - }, - "packages/build-info": { - "packageJson": { - "dependencies": [ - "npm:@bugsnag/js@8", - "npm:@iarna/toml@^2.2.5", - "npm:@playwright/test@^1.53.1", - "npm:@types/node@^18.19.111", - "npm:@types/semver@^7.3.13", - "npm:@vitest/ui@3", - "npm:dot-prop@9", - "npm:execa@8", - "npm:find-up@7", - "npm:memfs@4", - "npm:minimatch@9", - "npm:read-pkg@9", - "npm:semver@^7.3.8", - "npm:typescript@5", - "npm:unionfs@^4.4.0", - "npm:vitest@3", - "npm:yaml@^2.8.0", - "npm:yargs@^17.6.0" - ] - } - }, - "packages/cache-utils": { - "packageJson": { - "dependencies": [ - "npm:@types/node@^18.19.111", - "npm:cpy@11", - "npm:get-stream@9", - "npm:globby@14", - "npm:junk@4", - "npm:locate-path@7", - "npm:move-file@3", - "npm:path-exists@5", - "npm:readdirp@4", - "npm:tmp-promise@3", - "npm:typescript@5", - "npm:vitest@3" - ] - } - }, - "packages/config": { - "packageJson": { - "dependencies": [ - "npm:@iarna/toml@^2.2.5", - "npm:@netlify/api@^14.0.3", - "npm:@netlify/headers-parser@^9.0.1", - "npm:@netlify/redirect-parser@^15.0.2", - "npm:@types/node@^18.19.111", - "npm:ava@5", - "npm:c8@10", - "npm:chalk@5", - "npm:cron-parser@^4.1.0", - "npm:deepmerge@^4.2.2", - "npm:dot-prop@9", - "npm:execa@8", - "npm:fast-safe-stringify@^2.0.7", - "npm:figures@6", - "npm:filter-obj@6", - "npm:find-up@7", - "npm:has-ansi@6", - "npm:indent-string@5", - "npm:is-ci@4", - "npm:is-plain-obj@4", - "npm:map-obj@5", - "npm:omit.js@^2.0.2", - "npm:p-locate@6", - "npm:path-type@6", - "npm:read-package-up@11", - "npm:tmp-promise@^3.0.2", - "npm:tomlify-j0.4@3", - "npm:typescript@5", - "npm:validate-npm-package-name@5", - "npm:yaml@^2.8.0", - "npm:yargs@^17.6.0" - ] - } - }, - "packages/edge-bundler": { - "packageJson": { - "dependencies": [ - "npm:@import-maps/resolve@2", - "npm:@types/node@^18.19.111", - "npm:@types/semver@^7.3.9", - "npm:@types/uuid@10", - "npm:@vitest/coverage-v8@3", - "npm:ajv-errors@3", - "npm:ajv@^8.11.2", - "npm:archiver@7", - "npm:better-ajv-errors@^1.2.0", - "npm:chalk@^5.4.0", - "npm:common-path-prefix@3", - "npm:cpy@^11.1.0", - "npm:env-paths@3", - "npm:esbuild@0.25.6", - "npm:execa@8", - "npm:find-up@7", - "npm:get-package-name@^2.2.0", - "npm:get-port@7", - "npm:is-path-inside@4", - "npm:nock@14", - "npm:node-stream-zip@^1.15.0", - "npm:npm-run-all2@6", - "npm:p-retry@6", - "npm:p-wait-for@5", - "npm:parse-imports@^2.2.1", - "npm:path-key@4", - "npm:semver@^7.3.8", - "npm:tar@^7.4.3", - "npm:tmp-promise@^3.0.3", - "npm:typescript@5", - "npm:urlpattern-polyfill@8.0.2", - "npm:uuid@11", - "npm:vitest@3" - ] - } - }, - "packages/functions-utils": { - "packageJson": { - "dependencies": [ - "npm:@netlify/zip-it-and-ship-it@14.1.0", - "npm:@types/node@^18.19.111", - "npm:cpy@11", - "npm:path-exists@5", - "npm:sort-on@6", - "npm:tmp-promise@3", - "npm:typescript@5", - "npm:vitest@3" - ] - } - }, - "packages/git-utils": { - "packageJson": { - "dependencies": [ - "npm:@types/node@^18.19.111", - "npm:execa@8", - "npm:map-obj@5", - "npm:micromatch@^4.0.2", - "npm:moize@^6.1.3", - "npm:typescript@5", - "npm:vitest@3" - ] - } - }, - "packages/headers-parser": { - "packageJson": { - "dependencies": [ - "npm:@iarna/toml@^2.2.5", - "npm:@types/node@^18.19.111", - "npm:escape-string-regexp@5", - "npm:fast-safe-stringify@^2.0.7", - "npm:is-plain-obj@4", - "npm:map-obj@5", - "npm:path-exists@5", - "npm:typescript@5", - "npm:vitest@3" - ] - } - }, - "packages/js-client": { - "packageJson": { - "dependencies": [ - "npm:@netlify/open-api@^2.37.0", - "npm:@types/lodash-es@^4.17.6", - "npm:@types/node@^18.19.111", - "npm:from2-string@^1.1.0", - "npm:lodash-es@^4.17.21", - "npm:nock@13", - "npm:node-fetch@3", - "npm:p-wait-for@5", - "npm:qs@^6.9.6", - "npm:ts-node@^10.9.1", - "npm:typescript@5", - "npm:uuid@11", - "npm:vitest@^3.2.3" - ] - } - }, - "packages/nock-udp": { - "packageJson": { - "dependencies": [ - "npm:@types/node@^18.19.111", - "npm:typescript@5", - "npm:vitest@3" - ] - } - }, - "packages/opentelemetry-sdk-setup": { - "packageJson": { - "dependencies": [ - "npm:@honeycombio/opentelemetry-node@~0.7.1", - "npm:@netlify/opentelemetry-utils@~2.0.1", - "npm:@opentelemetry/api@1.8", - "npm:@opentelemetry/core@1.24", - "npm:@opentelemetry/resources@1.24", - "npm:@opentelemetry/sdk-trace-base@1.24", - "npm:@opentelemetry/semantic-conventions@1.24", - "npm:@types/node@^18.19.111", - "npm:@vitest/ui@3", - "npm:read-package-up@11", - "npm:typescript@5", - "npm:vitest@3", - "npm:yargs-parser@^21.1.1" - ] - } - }, - "packages/opentelemetry-utils": { - "packageJson": { - "dependencies": [ - "npm:@opentelemetry/api@1.8", - "npm:@opentelemetry/sdk-trace-base@1.24", - "npm:@opentelemetry/sdk-trace-node@1.24", - "npm:@types/node@^18.19.111", - "npm:@vitest/ui@3", - "npm:typescript@5", - "npm:vitest@3" - ] - } - }, - "packages/redirect-parser": { - "packageJson": { - "dependencies": [ - "npm:@iarna/toml@^2.2.5", - "npm:@types/node@^18.19.111", - "npm:fast-safe-stringify@^2.1.1", - "npm:filter-obj@6", - "npm:is-plain-obj@4", - "npm:path-exists@5", - "npm:typescript@5", - "npm:vitest@3" - ] - } - }, - "packages/run-utils": { - "packageJson": { - "dependencies": [ - "npm:@types/node@^18.19.111", - "npm:execa@8", - "npm:semver@^7.3.8", - "npm:typescript@5", - "npm:vitest@3" - ] - } - }, - "packages/testing": { - "packageJson": { - "dependencies": [ - "npm:@netlify/build@*", - "npm:@netlify/config@*", - "npm:@types/lodash-es@^4.17.6", - "npm:@types/node@^18.19.111", - "npm:ava@5", - "npm:c8@10", - "npm:cpy@11", - "npm:execa@8", - "npm:fast-safe-stringify@^2.0.7", - "npm:figures@6", - "npm:get-bin-path@11", - "npm:get-port@7", - "npm:get-stream@9", - "npm:is-plain-obj@4", - "npm:lodash-es@^4.17.21", - "npm:path-key@4", - "npm:strip-ansi@7", - "npm:tmp-promise@^3.0.2", - "npm:typescript@5" - ] - } - }, - "packages/zip-it-and-ship-it": { - "packageJson": { - "dependencies": [ - "npm:@babel/parser@^7.22.5", - "npm:@babel/types@7.28.1", - "npm:@netlify/binary-info@1", - "npm:@netlify/serverless-functions-api@^2.1.3", - "npm:@types/archiver@6.0.3", - "npm:@types/is-ci@3.0.4", - "npm:@types/node@20.12.11", - "npm:@types/normalize-path@3.0.2", - "npm:@types/resolve@1.20.6", - "npm:@types/semver@7.7.0", - "npm:@types/unixify@1.0.2", - "npm:@types/yargs@17.0.33", - "npm:@vercel/nft@0.29.4", - "npm:@vitest/coverage-v8@3", - "npm:archiver@7", - "npm:browserslist@4.25.1", - "npm:cardinal@2.1.1", - "npm:common-path-prefix@3", - "npm:copy-file@11", - "npm:cpy@11.1.0", - "npm:decompress@4.2.1", - "npm:deepmerge@^4.3.1", - "npm:es-module-lexer@1", - "npm:esbuild@0.25.6", - "npm:execa@8", - "npm:fast-glob@^3.3.3", - "npm:filter-obj@6", - "npm:find-up@7", - "npm:get-stream@9.0.1", - "npm:is-builtin-module@^3.1.0", - "npm:is-ci@4.1.0", - "npm:is-path-inside@4", - "npm:junk@4", - "npm:lambda-local@2.2.0", - "npm:locate-path@7", - "npm:merge-options@^3.0.4", - "npm:minimatch@9", - "npm:normalize-path@3", - "npm:p-map@7", - "npm:path-exists@5", - "npm:precinct@12", - "npm:require-package-name@^2.0.1", - "npm:resolve@^2.0.0-next.1", - "npm:semver@^7.3.8", - "npm:source-map-support@0.5.21", - "npm:tmp-promise@^3.0.2", - "npm:toml@3", - "npm:typescript@5.8.3", - "npm:unixify@1", - "npm:urlpattern-polyfill@8.0.2", - "npm:vitest@3", - "npm:yargs@17", - "npm:zod@^3.23.8" - ] - } - } - } - } -} From 19f8e025f73a7040acae37fd27f51a97ea69b16b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Fri, 25 Jul 2025 11:27:05 +0100 Subject: [PATCH 18/33] refactor: revert lock flag --- packages/edge-bundler/node/formats/eszip.ts | 2 +- packages/edge-bundler/node/server/server.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/edge-bundler/node/formats/eszip.ts b/packages/edge-bundler/node/formats/eszip.ts index 09df0229de..432c07ff36 100644 --- a/packages/edge-bundler/node/formats/eszip.ts +++ b/packages/edge-bundler/node/formats/eszip.ts @@ -57,7 +57,7 @@ const bundleESZIP = async ({ importMapData, vendorDirectory, } - const flags = ['--allow-all', '--no-config', '--no-lock', `--import-map=${bundlerImportMap}`] + const flags = ['--allow-all', '--no-config', `--import-map=${bundlerImportMap}`] if (!debug) { flags.push('--quiet') diff --git a/packages/edge-bundler/node/server/server.ts b/packages/edge-bundler/node/server/server.ts index e290e63bf2..43f420bcc8 100644 --- a/packages/edge-bundler/node/server/server.ts +++ b/packages/edge-bundler/node/server/server.ts @@ -317,7 +317,7 @@ export const serve = async ({ // Downloading latest types if needed. await ensureLatestTypes(deno, logger) - const flags = ['--allow-all', '--no-config', '--no-lock'] + const flags = ['--allow-all', '--no-config'] if (certificatePath) { flags.push(`--cert=${certificatePath}`) From e6c83ffe11c2773c6dedadffd778456345b7ebe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Fri, 25 Jul 2025 15:09:51 +0100 Subject: [PATCH 19/33] fix: fix test --- packages/edge-bundler/node/bundler.test.ts | 39 +++++++------------ packages/edge-bundler/node/formats/tarball.ts | 32 +++++++-------- packages/edge-bundler/node/import_map.ts | 26 +++++++++++-- 3 files changed, 48 insertions(+), 49 deletions(-) diff --git a/packages/edge-bundler/node/bundler.test.ts b/packages/edge-bundler/node/bundler.test.ts index 3cfe53ef23..7e26f44b7d 100644 --- a/packages/edge-bundler/node/bundler.test.ts +++ b/packages/edge-bundler/node/bundler.test.ts @@ -48,7 +48,6 @@ test('Produces an ESZIP bundle', async () => { expect(importMapURL).toBe(importMapSpecifier) const bundlePath = join(distPath, bundles[0].asset) - const { func1, func2, func3 } = await runESZIP(bundlePath) expect(func1).toBe('HELLO, JANE DOE!') @@ -696,7 +695,7 @@ test('Loads edge functions from the Frameworks API', async () => { describe('Produces a tarball bundle', () => { test('With only local imports', async () => { const systemLogger = vi.fn() - const { basePath, cleanup, distPath } = await useFixture('with_config', { copyDirectory: true }) + const { basePath, cleanup, distPath } = await useFixture('imports_node_builtin', { copyDirectory: true }) const declarations: Declaration[] = [ { function: 'func1', @@ -705,37 +704,21 @@ describe('Produces a tarball bundle', () => { ] const vendorDirectory = await tmp.dir() - await bundle( - [join(basePath, 'netlify/edge-functions'), join(basePath, '.netlify/edge-functions')], - distPath, - declarations, - { - basePath, - featureFlags: { - edge_bundler_generate_tarball: true, - }, - importMapPaths: [join(basePath, '.netlify/edge-functions/import_map.json')], - vendorDirectory: vendorDirectory.path, - systemLogger, + await bundle([join(basePath, 'netlify/edge-functions')], distPath, declarations, { + basePath, + configPath: join(basePath, '.netlify/edge-functions/config.json'), + featureFlags: { + edge_bundler_generate_tarball: true, }, - ) + systemLogger, + }) expect( systemLogger.mock.calls.find((call) => call[0] === 'Could not track dependencies in edge function:'), ).toBeUndefined() const expectedOutput = { - 'user-func1': 'Hello, user function 1!', - 'user-func2': 'Hello, user function 2!', - 'user-func3': 'Hello from user function 3', - 'user-func4': 'Hello from user function 4. I will be cached!', - 'user-func5': 'Hello from user function 5.', - 'user-func6': 'Hello from user function 6.', - 'user-func7': 'Hello from user function 7.', - 'framework-func1': 'Hello, framework function 1!', - 'framework-func2': 'Hello, framework function 2!', - 'framework-func3': 'Hello, framework function 3!', - 'framework-func4': 'Hello, framework function 4!', + func1: 'ok', } const manifestFile = await readFile(resolve(distPath, 'manifest.json'), 'utf8') @@ -745,6 +728,10 @@ describe('Produces a tarball bundle', () => { const tarballResult = await runTarball(tarballPath) expect(tarballResult).toStrictEqual(expectedOutput) + const eszipPath = join(distPath, manifest.bundles[1].asset) + const eszipResult = await runESZIP(eszipPath) + expect(eszipResult).toStrictEqual(expectedOutput) + await cleanup() await rm(vendorDirectory.path, { force: true, recursive: true }) }) diff --git a/packages/edge-bundler/node/formats/tarball.ts b/packages/edge-bundler/node/formats/tarball.ts index 93b07179f6..09b4aa7a77 100644 --- a/packages/edge-bundler/node/formats/tarball.ts +++ b/packages/edge-bundler/node/formats/tarball.ts @@ -1,6 +1,7 @@ import { promises as fs } from 'fs' import path from 'path' +import commonPathPrefix from 'common-path-prefix' import * as tar from 'tar' import tmp from 'tmp-promise' @@ -41,7 +42,6 @@ export const bundle = async ({ importMap, vendorDirectory, }: BundleTarballOptions): Promise => { - const hashes = new Map() const sideFilesDir = await tmp.dir({ unsafeCleanup: true }) const cleanup = [sideFilesDir.cleanup] @@ -59,19 +59,16 @@ export const bundle = async ({ functions: {}, version: 1, } - const entrypoints: string[] = [] - const bundledPaths = new Map() + const entryPoints = functions.map((func) => func.path) - for (const func of functions) { - const relativePath = path.relative(basePath, func.path) - const bundledPath = path.format({ - ...path.parse(relativePath), - base: undefined, - ext: '.js', - }) + // `deno bundle` does not return the paths of the files it emits, so we have + // to infer them. When multiple entry points are supplied, it will find the + // common path prefix and use that as the base directory in `outdir`. + const commonPath = commonPathPrefix(entryPoints) - bundledPaths.set(func.path, bundledPath) - entrypoints.push(func.path) + for (const func of functions) { + const filename = path.basename(func.path, path.extname(func.path)) + const bundledPath = path.join(commonPath, `${filename}.js`) manifest.functions[func.name] = getUnixPath(bundledPath) } @@ -80,7 +77,7 @@ export const bundle = async ({ [ 'bundle', '--import-map', - importMap.toDataURL(), + importMap.withNodeBuiltins().toDataURL(), '--quiet', '--code-splitting', '--outdir', @@ -89,20 +86,15 @@ export const bundle = async ({ ], { cwd: basePath, - env: { - DENO_DIR: denoDir, - }, }, ) const manifestPath = path.join(sideFilesDir.path, 'manifest.json') const manifestContents = JSON.stringify(manifest) - hashes.set('manifest', getStringHash(manifestContents)) await fs.writeFile(manifestPath, manifestContents) const denoConfigPath = path.join(sideFilesDir.path, 'deno.json') const denoConfigContents = JSON.stringify(importMap.getContentsWithRelativePaths()) - hashes.set('config', getStringHash(denoConfigContents)) await fs.writeFile(denoConfigPath, denoConfigContents) const rootLevel = await fs.readdir(distDirectory) @@ -135,9 +127,11 @@ export const bundle = async ({ await Promise.all(cleanup) + const finalHash = [hash, getStringHash(manifestContents), getStringHash(denoConfigContents)].join('') + return { extension: TARBALL_EXTENSION, format: BundleFormat.TARBALL, - hash, + hash: finalHash, } } diff --git a/packages/edge-bundler/node/import_map.ts b/packages/edge-bundler/node/import_map.ts index d31edd7247..10dc64776f 100644 --- a/packages/edge-bundler/node/import_map.ts +++ b/packages/edge-bundler/node/import_map.ts @@ -1,7 +1,8 @@ -import { Buffer } from 'buffer' -import { promises as fs } from 'fs' -import { dirname, relative } from 'path' -import { fileURLToPath, pathToFileURL } from 'url' +import { Buffer } from 'node:buffer' +import { promises as fs } from 'node:fs' +import { builtinModules } from 'node:module' +import { dirname, relative } from 'node:path' +import { fileURLToPath, pathToFileURL } from 'node:url' import { parse, ParsedImportMap } from '@import-maps/resolve' @@ -279,6 +280,23 @@ export class ImportMap { return `data:application/json;base64,${encodedImportMap}` } + // Adds an import map source mapping Node.js built-in modules to their prefixed + // version (e.g. "path" => "node:path"). + withNodeBuiltins() { + const imports: Record = {} + + for (const name of builtinModules) { + imports[name] = `node:${name}` + } + + this.sources.push({ + baseURL: new URL(import.meta.url), + imports, + }) + + return this + } + async writeToFile(path: string) { const distDirectory = dirname(path) From d8b704b1ab0f7c790b1e62ff165be51cfcdd2d0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Fri, 25 Jul 2025 15:11:53 +0100 Subject: [PATCH 20/33] chore: update Deno --- .github/workflows/workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index db786dbf6b..6b1f7243c7 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -149,7 +149,7 @@ jobs: - name: Setup Deno uses: denoland/setup-deno@v1 with: - deno-version: v2.2.4 + deno-version: v2.4.2 if: ${{ !steps.release-check.outputs.IS_RELEASE }} - name: Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 From db6a971a24c16aec978fb69e654ab2b5c741acbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Fri, 25 Jul 2025 16:43:33 +0100 Subject: [PATCH 21/33] refactor: revert lock flag --- packages/edge-bundler/node/config.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/edge-bundler/node/config.ts b/packages/edge-bundler/node/config.ts index 2c6bf054ae..cc64c1df12 100644 --- a/packages/edge-bundler/node/config.ts +++ b/packages/edge-bundler/node/config.ts @@ -111,7 +111,6 @@ export const getFunctionConfig = async ({ `--allow-write=${collector.path}`, `--import-map=${importMap.toDataURL()}`, '--no-config', - '--no-lock', '--node-modules-dir=false', '--quiet', extractorPath, From b89cb5ebaca52eea379a08bf9dd71d58fe558f79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Fri, 25 Jul 2025 16:47:41 +0100 Subject: [PATCH 22/33] chore: add debug log --- packages/edge-bundler/test/util.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/edge-bundler/test/util.ts b/packages/edge-bundler/test/util.ts index 01c99ab5f1..4fd36989f3 100644 --- a/packages/edge-bundler/test/util.ts +++ b/packages/edge-bundler/test/util.ts @@ -157,6 +157,8 @@ const runTarball = async (tarballPath: string) => { file: tarballPath, }) + console.log('-> Tarball contents:', await fs.readdir(tmpDir.path)) + const evalCommand = execa('deno', ['eval', inspectTarballFunction()], { cwd: tmpDir.path, }) From 638ae47f65846a2238d98bdfe7e30bf31ddea4f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Fri, 25 Jul 2025 17:07:23 +0100 Subject: [PATCH 23/33] chore: add more debug --- packages/edge-bundler/node/formats/tarball.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/edge-bundler/node/formats/tarball.ts b/packages/edge-bundler/node/formats/tarball.ts index 09b4aa7a77..767dab1215 100644 --- a/packages/edge-bundler/node/formats/tarball.ts +++ b/packages/edge-bundler/node/formats/tarball.ts @@ -89,6 +89,8 @@ export const bundle = async ({ }, ) + console.log('-> Tarball bundle', { functions, distDirectory, entryPoints, commonPath }) + const manifestPath = path.join(sideFilesDir.path, 'manifest.json') const manifestContents = JSON.stringify(manifest) await fs.writeFile(manifestPath, manifestContents) From a6b233345beea0d58af6dfe3060f12a56a234d6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Mon, 28 Jul 2025 10:18:01 +0100 Subject: [PATCH 24/33] refactor: fix path generation --- packages/edge-bundler/node/formats/tarball.ts | 52 +++++++++++++------ 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/packages/edge-bundler/node/formats/tarball.ts b/packages/edge-bundler/node/formats/tarball.ts index 767dab1215..20173fce76 100644 --- a/packages/edge-bundler/node/formats/tarball.ts +++ b/packages/edge-bundler/node/formats/tarball.ts @@ -63,12 +63,18 @@ export const bundle = async ({ // `deno bundle` does not return the paths of the files it emits, so we have // to infer them. When multiple entry points are supplied, it will find the - // common path prefix and use that as the base directory in `outdir`. - const commonPath = commonPathPrefix(entryPoints) + // common path prefix and use that as the base directory in `outdir`. When + // using a single entry point, `commonPathPrefix` returns an empty string, + // so we use the path of the first entry point's directory. + const commonPath = commonPathPrefix(entryPoints) || path.dirname(entryPoints[0]) for (const func of functions) { - const filename = path.basename(func.path, path.extname(func.path)) - const bundledPath = path.join(commonPath, `${filename}.js`) + const relativePath = path.relative(commonPath, func.path) + const bundledPath = path.format({ + ...path.parse(relativePath), + base: undefined, + ext: '.js', + }) manifest.functions[func.name] = getUnixPath(bundledPath) } @@ -89,8 +95,6 @@ export const bundle = async ({ }, ) - console.log('-> Tarball bundle', { functions, distDirectory, entryPoints, commonPath }) - const manifestPath = path.join(sideFilesDir.path, 'manifest.json') const manifestContents = JSON.stringify(manifest) await fs.writeFile(manifestPath, manifestContents) @@ -105,26 +109,40 @@ export const bundle = async ({ await fs.mkdir(path.dirname(tarballPath), { recursive: true }) + // Adding all the bundled files. await tar.create( { cwd: distDirectory, file: tarballPath, - preservePaths: true, onWriteEntry(entry) { - if (entry.path === denoConfigPath) { - entry.path = `./deno.json` - - return - } + entry.path = getUnixPath(`./${entry.path}`) + }, + }, + rootLevel, + ) - if (entry.path === manifestPath) { - entry.path = `./___netlify-edge-functions.json` + // Adding `deno.json`. + await tar.update( + { + cwd: distDirectory, + file: tarballPath, + onWriteEntry(entry) { + entry.path = './deno.json' + }, + }, + [denoConfigPath], + ) - return - } + // Adding the manifest file. + await tar.update( + { + cwd: distDirectory, + file: tarballPath, + onWriteEntry(entry) { + entry.path = './___netlify-edge-functions.json' }, }, - [...rootLevel, manifestPath, denoConfigPath], + [manifestPath], ) await Promise.all(cleanup) From 7700a456880c551afc0af5fb2648668280a4790d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Mon, 28 Jul 2025 10:53:04 +0100 Subject: [PATCH 25/33] chore: clean up test util --- packages/edge-bundler/test/util.ts | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/edge-bundler/test/util.ts b/packages/edge-bundler/test/util.ts index 4fd36989f3..0b54dc55d1 100644 --- a/packages/edge-bundler/test/util.ts +++ b/packages/edge-bundler/test/util.ts @@ -11,19 +11,19 @@ import tmp from 'tmp-promise' import { getLogger } from '../node/logger.js' import type { Manifest } from '../node/manifest.js' -const testLogger = getLogger(() => { +export const testLogger = getLogger(() => { // no-op }) const url = new URL(import.meta.url) const dirname = fileURLToPath(url) -const fixturesDir = resolve(dirname, '..', 'fixtures') +export const fixturesDir = resolve(dirname, '..', 'fixtures') interface UseFixtureOptions { copyDirectory?: boolean } -const useFixture = async (fixtureName: string, { copyDirectory }: UseFixtureOptions = {}) => { +export const useFixture = async (fixtureName: string, { copyDirectory }: UseFixtureOptions = {}) => { const tmpDistDir = await tmp.dir({ unsafeCleanup: true }) const fixtureDir = resolve(fixturesDir, fixtureName) const distPath = join(tmpDistDir.path, '.netlify', 'edge-functions-dist') @@ -31,7 +31,7 @@ const useFixture = async (fixtureName: string, { copyDirectory }: UseFixtureOpti if (copyDirectory) { const tmpFixtureDir = await tmp.dir({ unsafeCleanup: true }) - // TODO: Replace with `fs.cp` once we drop support for Node 14. + // TODO: Replace with `fs.cp` once the Node.js version range allows. await cpy(`${fixtureDir}/**`, tmpFixtureDir.path) return { @@ -82,7 +82,7 @@ for (const functionName in manifest.functions) { console.log(JSON.stringify(responses)); ` -const getRouteMatcher = (manifest: Manifest) => (candidate: string) => +export const getRouteMatcher = (manifest: Manifest) => (candidate: string) => manifest.routes.find((route) => { const regex = new RegExp(route.pattern) @@ -100,7 +100,7 @@ const getRouteMatcher = (manifest: Manifest) => (candidate: string) => return !isExcluded }) -const runESZIP = async (eszipPath: string, vendorDirectory?: string) => { +export const runESZIP = async (eszipPath: string, vendorDirectory?: string) => { const tmpDir = await tmp.dir({ unsafeCleanup: true }) // Extract ESZIP into temporary directory. @@ -149,7 +149,7 @@ const runESZIP = async (eszipPath: string, vendorDirectory?: string) => { return JSON.parse(result.stdout) } -const runTarball = async (tarballPath: string) => { +export const runTarball = async (tarballPath: string) => { const tmpDir = await tmp.dir({ unsafeCleanup: true }) await tar.extract({ @@ -157,8 +157,6 @@ const runTarball = async (tarballPath: string) => { file: tarballPath, }) - console.log('-> Tarball contents:', await fs.readdir(tmpDir.path)) - const evalCommand = execa('deno', ['eval', inspectTarballFunction()], { cwd: tmpDir.path, }) @@ -171,5 +169,3 @@ const runTarball = async (tarballPath: string) => { return JSON.parse(result.stdout) } - -export { fixturesDir, getRouteMatcher, testLogger, runESZIP, runTarball, useFixture } From bc9e874797e3ce0d806b0f2e070687ba4b57651e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Mon, 28 Jul 2025 17:11:35 +0100 Subject: [PATCH 26/33] refactor: revert version bump --- .github/workflows/workflow.yml | 8 +- packages/edge-bundler/node/bridge.ts | 21 +-- packages/edge-bundler/node/bundler.test.ts | 183 +++++++++++---------- packages/edge-bundler/node/bundler.ts | 1 + 4 files changed, 115 insertions(+), 98 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 6b1f7243c7..4369a922b6 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -50,12 +50,14 @@ jobs: os: [ubuntu-24.04, macos-14, windows-2025] node-version: ['22'] # Must include the minimum deno version from the `DENO_VERSION_RANGE` constant in `node/bridge.ts`. - deno-version: ['v2.4.2'] + # We're adding v2.4.2 here because it's needed for the upcoming nimble release, so we can test + # those workflows ahead of time before we can update the base version across the board. + deno-version: ['v1.39.0', 'v2.2.4', 'v2.4.2'] include: - os: ubuntu-24.04 # Earliest supported version node-version: '18.14.0' - deno-version: 'v2.4.2' + deno-version: 'v2.2.4' fail-fast: false steps: # Sets an output parameter if this is a release PR @@ -149,7 +151,7 @@ jobs: - name: Setup Deno uses: denoland/setup-deno@v1 with: - deno-version: v2.4.2 + deno-version: v2.2.4 if: ${{ !steps.release-check.outputs.IS_RELEASE }} - name: Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 diff --git a/packages/edge-bundler/node/bridge.ts b/packages/edge-bundler/node/bridge.ts index fb593d2193..2b0bd29899 100644 --- a/packages/edge-bundler/node/bridge.ts +++ b/packages/edge-bundler/node/bridge.ts @@ -7,6 +7,7 @@ import pathKey from 'path-key' import semver from 'semver' import { download } from './downloader.js' +import { FeatureFlags } from './feature_flags.js' import { getPathInHome } from './home_path.js' import { getLogger, Logger } from './logger.js' import { getBinaryExtension } from './platform.js' @@ -16,15 +17,18 @@ const DENO_VERSION_FILE = 'version.txt' // When updating DENO_VERSION_RANGE, ensure that the deno version // on the netlify/buildbot build image satisfies this range! // https://github.com/netlify/buildbot/blob/f9c03c9dcb091d6570e9d0778381560d469e78ad/build-image/noble/Dockerfile#L410 -const DENO_VERSION_RANGE = '^2.4.2' +export const DENO_VERSION_RANGE = '1.39.0 - 2.2.4' -type OnBeforeDownloadHook = () => void | Promise -type OnAfterDownloadHook = (error?: Error) => void | Promise +const NEXT_DENO_VERSION_RANGE = '^2.4.2' -interface DenoOptions { +export type OnBeforeDownloadHook = () => void | Promise +export type OnAfterDownloadHook = (error?: Error) => void | Promise + +export interface DenoOptions { cacheDirectory?: string debug?: boolean denoDir?: string + featureFlags?: FeatureFlags logger?: Logger onAfterDownload?: OnAfterDownloadHook onBeforeDownload?: OnBeforeDownloadHook @@ -32,7 +36,7 @@ interface DenoOptions { versionRange?: string } -interface ProcessRef { +export interface ProcessRef { ps?: ExecaChildProcess } @@ -46,7 +50,7 @@ interface RunOptions { rejectOnExitCode?: boolean } -class DenoBridge { +export class DenoBridge { cacheDirectory: string currentDownload?: ReturnType debug: boolean @@ -65,7 +69,7 @@ class DenoBridge { this.onAfterDownload = options.onAfterDownload this.onBeforeDownload = options.onBeforeDownload this.useGlobal = options.useGlobal ?? true - this.versionRange = options.versionRange ?? DENO_VERSION_RANGE + this.versionRange = options.versionRange ?? (options.featureFlags ? NEXT_DENO_VERSION_RANGE : DENO_VERSION_RANGE) } private async downloadBinary() { @@ -272,6 +276,3 @@ class DenoBridge { } } } - -export { DENO_VERSION_RANGE, DenoBridge } -export type { DenoOptions, OnAfterDownloadHook, OnBeforeDownloadHook, ProcessRef } diff --git a/packages/edge-bundler/node/bundler.test.ts b/packages/edge-bundler/node/bundler.test.ts index 7e26f44b7d..7780f3954a 100644 --- a/packages/edge-bundler/node/bundler.test.ts +++ b/packages/edge-bundler/node/bundler.test.ts @@ -4,12 +4,14 @@ import { join, resolve } from 'path' import process from 'process' import { pathToFileURL } from 'url' +import { lt } from 'semver' import tmp from 'tmp-promise' import { test, expect, vi, describe } from 'vitest' import { importMapSpecifier } from '../shared/consts.js' import { runESZIP, runTarball, useFixture } from '../test/util.js' +import { DenoBridge } from './bridge.js' import { BundleError } from './bundle_error.js' import { bundle, BundleOptions } from './bundler.js' import { Declaration } from './declaration.js' @@ -692,91 +694,102 @@ test('Loads edge functions from the Frameworks API', async () => { await cleanup() }) -describe('Produces a tarball bundle', () => { - test('With only local imports', async () => { - const systemLogger = vi.fn() - const { basePath, cleanup, distPath } = await useFixture('imports_node_builtin', { copyDirectory: true }) - const declarations: Declaration[] = [ - { - function: 'func1', - path: '/func1', - }, - ] - const vendorDirectory = await tmp.dir() - - await bundle([join(basePath, 'netlify/edge-functions')], distPath, declarations, { - basePath, - configPath: join(basePath, '.netlify/edge-functions/config.json'), - featureFlags: { - edge_bundler_generate_tarball: true, - }, - systemLogger, +// @ts-expect-error This is temporary, just so we can conditionally run this +// suite of tests only if we're on the next version of Deno. TypeScript is +// complaining about the fact that we're using a top-level await without +// the right `module` format in the config, but this is just a test and the +// format of the produced module has no effect here. +const denoVersion = (await new DenoBridge({}).getBinaryVersion('deno')) ?? '' + +describe.skipIf(lt(denoVersion, '2.4.2'))( + 'Produces a tarball bundle', + () => { + test('With only local imports', async () => { + const systemLogger = vi.fn() + const { basePath, cleanup, distPath } = await useFixture('imports_node_builtin', { copyDirectory: true }) + const declarations: Declaration[] = [ + { + function: 'func1', + path: '/func1', + }, + ] + const vendorDirectory = await tmp.dir() + + await bundle([join(basePath, 'netlify/edge-functions')], distPath, declarations, { + basePath, + configPath: join(basePath, '.netlify/edge-functions/config.json'), + featureFlags: { + edge_bundler_generate_tarball: true, + }, + systemLogger, + }) + + expect( + systemLogger.mock.calls.find((call) => call[0] === 'Could not track dependencies in edge function:'), + ).toBeUndefined() + + const expectedOutput = { + func1: 'ok', + } + + const manifestFile = await readFile(resolve(distPath, 'manifest.json'), 'utf8') + const manifest = JSON.parse(manifestFile) + + const tarballPath = join(distPath, manifest.bundles[0].asset) + const tarballResult = await runTarball(tarballPath) + expect(tarballResult).toStrictEqual(expectedOutput) + + const eszipPath = join(distPath, manifest.bundles[1].asset) + const eszipResult = await runESZIP(eszipPath) + expect(eszipResult).toStrictEqual(expectedOutput) + + await cleanup() + await rm(vendorDirectory.path, { force: true, recursive: true }) }) - expect( - systemLogger.mock.calls.find((call) => call[0] === 'Could not track dependencies in edge function:'), - ).toBeUndefined() - - const expectedOutput = { - func1: 'ok', - } - - const manifestFile = await readFile(resolve(distPath, 'manifest.json'), 'utf8') - const manifest = JSON.parse(manifestFile) - - const tarballPath = join(distPath, manifest.bundles[0].asset) - const tarballResult = await runTarball(tarballPath) - expect(tarballResult).toStrictEqual(expectedOutput) - - const eszipPath = join(distPath, manifest.bundles[1].asset) - const eszipResult = await runESZIP(eszipPath) - expect(eszipResult).toStrictEqual(expectedOutput) - - await cleanup() - await rm(vendorDirectory.path, { force: true, recursive: true }) - }) - - // TODO: https://github.com/denoland/deno/issues/30187 - test.todo('Using npm modules', async () => { - const systemLogger = vi.fn() - const { basePath, cleanup, distPath } = await useFixture('imports_npm_module', { copyDirectory: true }) - const sourceDirectory = join(basePath, 'functions') - const declarations: Declaration[] = [ - { - function: 'func1', - path: '/func1', - }, - ] - const vendorDirectory = await tmp.dir() - - await bundle([sourceDirectory], distPath, declarations, { - basePath, - featureFlags: { - edge_bundler_generate_tarball: true, - }, - importMapPaths: [join(basePath, 'import_map.json')], - vendorDirectory: vendorDirectory.path, - systemLogger, + // TODO: https://github.com/denoland/deno/issues/30187 + test.todo('Using npm modules', async () => { + const systemLogger = vi.fn() + const { basePath, cleanup, distPath } = await useFixture('imports_npm_module', { copyDirectory: true }) + const sourceDirectory = join(basePath, 'functions') + const declarations: Declaration[] = [ + { + function: 'func1', + path: '/func1', + }, + ] + const vendorDirectory = await tmp.dir() + + await bundle([sourceDirectory], distPath, declarations, { + basePath, + featureFlags: { + edge_bundler_generate_tarball: true, + }, + importMapPaths: [join(basePath, 'import_map.json')], + vendorDirectory: vendorDirectory.path, + systemLogger, + }) + + expect( + systemLogger.mock.calls.find((call) => call[0] === 'Could not track dependencies in edge function:'), + ).toBeUndefined() + + const expectedOutput = `JavaScript, APIs${process.platform}, Markup${process.platform}, TmV0bGlmeQ==` + + const manifestFile = await readFile(resolve(distPath, 'manifest.json'), 'utf8') + const manifest = JSON.parse(manifestFile) + + const tarballPath = join(distPath, manifest.bundles[0].asset) + const tarballResult = await runTarball(tarballPath) + expect(tarballResult.func1).toBe(expectedOutput) + + const eszipPath = join(distPath, manifest.bundles[1].asset) + const eszipResult = await runESZIP(eszipPath, vendorDirectory.path) + expect(eszipResult.func1).toBe(expectedOutput) + + await cleanup() + await rm(vendorDirectory.path, { force: true, recursive: true }) }) - - expect( - systemLogger.mock.calls.find((call) => call[0] === 'Could not track dependencies in edge function:'), - ).toBeUndefined() - - const expectedOutput = `JavaScript, APIs${process.platform}, Markup${process.platform}, TmV0bGlmeQ==` - - const manifestFile = await readFile(resolve(distPath, 'manifest.json'), 'utf8') - const manifest = JSON.parse(manifestFile) - - const tarballPath = join(distPath, manifest.bundles[0].asset) - const tarballResult = await runTarball(tarballPath) - expect(tarballResult.func1).toBe(expectedOutput) - - const eszipPath = join(distPath, manifest.bundles[1].asset) - const eszipResult = await runESZIP(eszipPath, vendorDirectory.path) - expect(eszipResult.func1).toBe(expectedOutput) - - await cleanup() - await rm(vendorDirectory.path, { force: true, recursive: true }) - }) -}, 10_000) + }, + 10_000, +) diff --git a/packages/edge-bundler/node/bundler.ts b/packages/edge-bundler/node/bundler.ts index a6da8c1856..20e309ec37 100644 --- a/packages/edge-bundler/node/bundler.ts +++ b/packages/edge-bundler/node/bundler.ts @@ -67,6 +67,7 @@ export const bundle = async ( const options: DenoOptions = { debug, cacheDirectory, + featureFlags, logger, onAfterDownload, onBeforeDownload, From 9d818e2ba302d6485b42393a6668338d4d7c889e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Mon, 28 Jul 2025 21:33:57 +0100 Subject: [PATCH 27/33] chore: simplify test --- packages/edge-bundler/node/bundler.test.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/edge-bundler/node/bundler.test.ts b/packages/edge-bundler/node/bundler.test.ts index 7780f3954a..00648d9f47 100644 --- a/packages/edge-bundler/node/bundler.test.ts +++ b/packages/edge-bundler/node/bundler.test.ts @@ -1,4 +1,5 @@ import { Buffer } from 'buffer' +import { execSync } from 'node:child_process' import { access, readdir, readFile, rm, writeFile } from 'fs/promises' import { join, resolve } from 'path' import process from 'process' @@ -11,7 +12,6 @@ import { test, expect, vi, describe } from 'vitest' import { importMapSpecifier } from '../shared/consts.js' import { runESZIP, runTarball, useFixture } from '../test/util.js' -import { DenoBridge } from './bridge.js' import { BundleError } from './bundle_error.js' import { bundle, BundleOptions } from './bundler.js' import { Declaration } from './declaration.js' @@ -694,12 +694,7 @@ test('Loads edge functions from the Frameworks API', async () => { await cleanup() }) -// @ts-expect-error This is temporary, just so we can conditionally run this -// suite of tests only if we're on the next version of Deno. TypeScript is -// complaining about the fact that we're using a top-level await without -// the right `module` format in the config, but this is just a test and the -// format of the produced module has no effect here. -const denoVersion = (await new DenoBridge({}).getBinaryVersion('deno')) ?? '' +const denoVersion = execSync('deno eval "console.log(Deno.version.deno)"').toString() describe.skipIf(lt(denoVersion, '2.4.2'))( 'Produces a tarball bundle', From 261772c2505abace7566bce26bd6ba68259d80ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Mon, 28 Jul 2025 21:40:03 +0100 Subject: [PATCH 28/33] chore: do not use lock file --- packages/edge-bundler/node/bundler.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edge-bundler/node/bundler.test.ts b/packages/edge-bundler/node/bundler.test.ts index 00648d9f47..c35d81690e 100644 --- a/packages/edge-bundler/node/bundler.test.ts +++ b/packages/edge-bundler/node/bundler.test.ts @@ -694,7 +694,7 @@ test('Loads edge functions from the Frameworks API', async () => { await cleanup() }) -const denoVersion = execSync('deno eval "console.log(Deno.version.deno)"').toString() +const denoVersion = execSync('deno eval --no-lock "console.log(Deno.version.deno)"').toString() describe.skipIf(lt(denoVersion, '2.4.2'))( 'Produces a tarball bundle', From 4ceec14987edb48e8e170685b01b800bdf9874f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Mon, 28 Jul 2025 23:44:30 +0100 Subject: [PATCH 29/33] fix: read feature flag --- packages/edge-bundler/node/bridge.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/edge-bundler/node/bridge.ts b/packages/edge-bundler/node/bridge.ts index 2b0bd29899..85fb386d1f 100644 --- a/packages/edge-bundler/node/bridge.ts +++ b/packages/edge-bundler/node/bridge.ts @@ -69,7 +69,9 @@ export class DenoBridge { this.onAfterDownload = options.onAfterDownload this.onBeforeDownload = options.onBeforeDownload this.useGlobal = options.useGlobal ?? true - this.versionRange = options.versionRange ?? (options.featureFlags ? NEXT_DENO_VERSION_RANGE : DENO_VERSION_RANGE) + this.versionRange = + options.versionRange ?? + (options.featureFlags?.edge_bundler_generate_tarball ? NEXT_DENO_VERSION_RANGE : DENO_VERSION_RANGE) } private async downloadBinary() { From 2204a2c08d9f15e28bed0eb50d94c0a844915e34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 29 Jul 2025 00:00:39 +0100 Subject: [PATCH 30/33] refactor: revert fixture change --- packages/edge-bundler/node/bundler.test.ts | 4 ++-- .../imports_npm_module/node_modules/grandchild-1/index.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/edge-bundler/node/bundler.test.ts b/packages/edge-bundler/node/bundler.test.ts index c35d81690e..15fc9c6659 100644 --- a/packages/edge-bundler/node/bundler.test.ts +++ b/packages/edge-bundler/node/bundler.test.ts @@ -500,7 +500,7 @@ test('Loads npm modules from bare specifiers', async () => { const { func1 } = await runESZIP(bundlePath, vendorDirectory.path) expect(func1).toBe( - `JavaScript, APIs${process.platform}, Markup${process.platform}, TmV0bGlmeQ==`, + `JavaScript, APIs${process.cwd()}, Markup${process.cwd()}, TmV0bGlmeQ==`, ) await cleanup() @@ -769,7 +769,7 @@ describe.skipIf(lt(denoVersion, '2.4.2'))( systemLogger.mock.calls.find((call) => call[0] === 'Could not track dependencies in edge function:'), ).toBeUndefined() - const expectedOutput = `JavaScript, APIs${process.platform}, Markup${process.platform}, TmV0bGlmeQ==` + const expectedOutput = `JavaScript, APIs${process.cwd()}, Markup${process.cwd()}, TmV0bGlmeQ==` const manifestFile = await readFile(resolve(distPath, 'manifest.json'), 'utf8') const manifest = JSON.parse(manifestFile) diff --git a/packages/edge-bundler/test/fixtures/imports_npm_module/node_modules/grandchild-1/index.js b/packages/edge-bundler/test/fixtures/imports_npm_module/node_modules/grandchild-1/index.js index 6a415726e9..59c1e941b1 100644 --- a/packages/edge-bundler/test/fixtures/imports_npm_module/node_modules/grandchild-1/index.js +++ b/packages/edge-bundler/test/fixtures/imports_npm_module/node_modules/grandchild-1/index.js @@ -1,3 +1,3 @@ -import { platform } from "process" +import { cwd } from "process" -export default (input) => `${input}${platform}` \ No newline at end of file +export default (input) => `${input}${cwd()}` \ No newline at end of file From 92603bb17a86c174eebc13c820cb194b9fc46c49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 29 Jul 2025 13:44:33 +0100 Subject: [PATCH 31/33] fix: use stable hash --- packages/edge-bundler/node/utils/sha256.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/edge-bundler/node/utils/sha256.ts b/packages/edge-bundler/node/utils/sha256.ts index fdce5853dc..835dc6df97 100644 --- a/packages/edge-bundler/node/utils/sha256.ts +++ b/packages/edge-bundler/node/utils/sha256.ts @@ -13,7 +13,7 @@ export const getDirectoryHash = async (dirPath: string): Promise => { if (dirent.isDirectory()) { await walk(fullPath) - } else if (dirent.isFile()) { + } else { const fileHash = await getFileHash(fullPath) entries.push(`${relativePath}:${fileHash}`) } @@ -22,7 +22,7 @@ export const getDirectoryHash = async (dirPath: string): Promise => { await walk(dirPath) - return getStringHash(entries.join('\n')) + return getStringHash(entries.sort().join('\n')) } export const getFileHash = (path: string): Promise => { From b1a719b69db6a0f650a0ce2923b2c71e4ed672b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 29 Jul 2025 14:00:51 +0100 Subject: [PATCH 32/33] fix: fix file type check --- packages/edge-bundler/node/utils/sha256.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edge-bundler/node/utils/sha256.ts b/packages/edge-bundler/node/utils/sha256.ts index 835dc6df97..76c4933d12 100644 --- a/packages/edge-bundler/node/utils/sha256.ts +++ b/packages/edge-bundler/node/utils/sha256.ts @@ -13,7 +13,7 @@ export const getDirectoryHash = async (dirPath: string): Promise => { if (dirent.isDirectory()) { await walk(fullPath) - } else { + } else if (dirent.isFile() || dirent.isSymbolicLink()) { const fileHash = await getFileHash(fullPath) entries.push(`${relativePath}:${fileHash}`) } From 719b9c173245ac9360e0a83ce09f3cbe5cd57f73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 29 Jul 2025 14:12:58 +0100 Subject: [PATCH 33/33] fix: improve sorting --- packages/edge-bundler/node/utils/sha256.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edge-bundler/node/utils/sha256.ts b/packages/edge-bundler/node/utils/sha256.ts index 76c4933d12..7c27316ba8 100644 --- a/packages/edge-bundler/node/utils/sha256.ts +++ b/packages/edge-bundler/node/utils/sha256.ts @@ -22,7 +22,7 @@ export const getDirectoryHash = async (dirPath: string): Promise => { await walk(dirPath) - return getStringHash(entries.sort().join('\n')) + return getStringHash(entries.sort((a, b) => a.localeCompare(b)).join('\n')) } export const getFileHash = (path: string): Promise => {