Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3b6fd58
feat: update deno version range to include v2
mrstork Mar 12, 2025
5b70819
fix: use --allow-import flag
mrstork Mar 20, 2025
2f28aa7
chore: sort command line arguments
mrstork Mar 21, 2025
dd4fe19
test: run tests with --no-check to get around isTooManyTries typecheck
mrstork Mar 21, 2025
6c8d22a
test: update snapshot
mrstork Mar 21, 2025
fb244c5
fix: reintroduce typechecking and resolve isTooManyTries type error
mrstork Mar 25, 2025
244612e
fix: remove allow-import flag
mrstork Mar 25, 2025
3dc077a
chore: update deno range to 2.2.4
mrstork Mar 25, 2025
c581470
chore: update deno range to 2.2.5
mrstork Mar 25, 2025
c61416e
fix: re-add --allow-import flag
JakeChampion Mar 25, 2025
a76d54f
fix: go back to version 2.2.4 as the max allowed because the deno gh …
JakeChampion Mar 25, 2025
cfeb63e
chore: fix snapshot
JakeChampion Mar 25, 2025
2308d5b
fix: add --allow-import only when using deno 2 or greater
JakeChampion Mar 25, 2025
bfb14cf
chore: fix snapshot
JakeChampion Mar 25, 2025
005c36f
chore: maybe we need to remove the empty flag
JakeChampion Mar 25, 2025
252fab1
chore: fix snapshot
JakeChampion Mar 25, 2025
6f0e94e
chore: remove ansi escape sequences from the snapshot output
JakeChampion Mar 26, 2025
749b8be
chore: fix snapshots
JakeChampion Mar 26, 2025
09c01fa
test: update monitor snapshots
mrstork Mar 26, 2025
5f05556
chore: remove deno.lock file
mrstork Mar 26, 2025
6c42058
refactor: filter the modules before checking the functions
JakeChampion Mar 26, 2025
17cdb7c
fix: use file url instead of file path to generate module graph
pieh Mar 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@ jobs:
os: [ubuntu-24.04, macos-14, windows-2022]
node-version: ['22']
# 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: ['v1.39.0', 'v2.2.4']
include:
- os: ubuntu-24.04
# Earliest supported version
node-version: '14.16.0'
deno-version: 'v1.46.3'
deno-version: 'v2.2.4'
fail-fast: false
steps:
# Sets an output parameter if this is a release PR
Expand Down Expand Up @@ -159,7 +159,7 @@ jobs:
- name: Setup Deno
uses: denoland/setup-deno@v1
with:
deno-version: v1.46.3
deno-version: v2.2.4
if: ${{ !steps.release-check.outputs.IS_RELEASE }}
- name: Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

53 changes: 0 additions & 53 deletions packages/build/tests/edge_functions/snapshots/tests.js.md
Original file line number Diff line number Diff line change
Expand Up @@ -511,59 +511,6 @@ Generated by [AVA](https://avajs.dev).
publish: packages/build/tests/edge_functions/fixtures/functions_invalid␊
publishOrigin: default`

## handles failure when validating Edge Functions

> Snapshot 1

`␊
Netlify Build ␊
β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€βŠ
␊
> Version␊
@netlify/build 1.0.0␊
␊
> Flags␊
debug: false␊
␊
> Current directory␊
packages/build/tests/edge_functions/fixtures/functions_validation_failed␊
␊
> Config file␊
No config file was defined: using default values.␊
␊
> Context␊
production␊
␊
1. Edge Functions bundling ␊
β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€βŠ
␊
Packaging Edge Functions from netlify/edge-functions directory:␊
- functions-1␊
␊
Bundling of edge function failed ␊
β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€βŠ
␊
Error message␊
Validation of Edge Functions manifest failed␊
FORMAT pattern needs to be a regex that starts with ^ followed by / and ends with $ without any additional slashes before and afterwards␊
␊
13 | {␊
14 | "function": "functions-1",␊
> 15 | "pattern": "^hello//?$"␊
| ^^^^^^^^^^^^ πŸ‘ˆπŸ½ format pattern needs to be a regex that starts with ^ followed by / and ends with $ without any additional slashes before and afterwards␊
16 | }␊
17 | ],␊
18 | "post_cache_routes": [],␊
␊
Error location␊
While bundling edge function␊
␊
Resolved config␊
build:␊
edge_functions: packages/build/tests/edge_functions/fixtures/functions_validation_failed/netlify/edge-functions␊
publish: packages/build/tests/edge_functions/fixtures/functions_validation_failed␊
publishOrigin: default`

## bundles Edge Functions via runCoreSteps function

> Snapshot 1
Expand Down
Binary file modified packages/build/tests/edge_functions/snapshots/tests.js.snap
Binary file not shown.
2 changes: 1 addition & 1 deletion packages/build/tests/monitor/snapshots/tests.js.md
Original file line number Diff line number Diff line change
Expand Up @@ -1606,7 +1606,7 @@ Generated by [AVA](https://avajs.dev).
Error monitoring payload:␊
{␊
"errorClass": "functionsBundling",␊
"errorMessage": "Command failed with exit code 1: deno run --allow-all --no-config --import-map=packages/edge-bundler/deno/vendor/import_map.json --quiet packages/edge-bundler/deno/bundle.ts {/"basePath/":/"packages/build/tests/monitor/fixtures/edge_function_error",/"destPath/":/"packages/build/tests/monitor/fixtures/edge_function_error/.netlify/edge-functions-dist/HEXADECIMAL_ID.eszip",/"externals/":[],/"functions/":[{/"name/":/"trouble/",/"path/":/"packages/build/tests/monitor/fixtures/edge_function_error/netlify/edge-functions/trouble.ts"}],/"importMapData/":/"{//"imports//":{//"builtins//"://"node:builtins//",//"@netlify/edge-functions//"://"https://edge.netlify.com/v1.0.0/index.ts//",//"netlify:edge//"://"https://edge.netlify.com/v1.0.0/index.ts?v=legacy//"},//"scopes//":{}}/",/"vendorDirectory/":/"/external/path"}/nerror: Uncaught (in promise) Error: Error: Could not find file: packages/build/tests/monitor/fixtures/edge_function_error/netlify/edge-functions/file.ts/n const ret = new Error(getStringFromWasm0(arg0, arg1));/n ^/n at __wbg_new_HEXADECIMAL_ID (packages/edge-bundler/deno/vendor/deno.land/x/[email protected]/eszip_wasm.generated.js:80:80)/n at <anonymous> (packages/edge-bundler/deno/vendor/deno.land/x/[email protected]/eszip_wasm_bg.wasm:1:80)/n at <anonymous> (packages/edge-bundler/deno/vendor/deno.land/x/[email protected]/eszip_wasm_bg.wasm:1:80)/n at <anonymous> (packages/edge-bundler/deno/vendor/deno.land/x/[email protected]/eszip_wasm_bg.wasm:1:80)/n at __wbg_adapter_40 (packages/edge-bundler/deno/vendor/deno.land/x/[email protected]/eszip_wasm.generated.js:80:8)/n at real (packages/edge-bundler/deno/vendor/deno.land/x/[email protected]/eszip_wasm.generated.js:80:80)/n at eventLoopTick (ext:core/01_core.js:80:7)",␊
"errorMessage": "Command failed with exit code 1: deno run --allow-all --no-config --import-map=packages/edge-bundler/deno/vendor/import_map.json --quiet packages/edge-bundler/deno/bundle.ts {/"basePath/":/"packages/build/tests/monitor/fixtures/edge_function_error",/"destPath/":/"packages/build/tests/monitor/fixtures/edge_function_error/.netlify/edge-functions-dist/HEXADECIMAL_ID.eszip",/"externals/":[],/"functions/":[{/"name/":/"trouble/",/"path/":/"packages/build/tests/monitor/fixtures/edge_function_error/netlify/edge-functions/trouble.ts"}],/"importMapData/":/"{//"imports//":{//"builtins//"://"node:builtins//",//"@netlify/edge-functions//"://"https://edge.netlify.com/v1.0.0/index.ts//",//"netlify:edge//"://"https://edge.netlify.com/v1.0.0/index.ts?v=legacy//"},//"scopes//":{}}/",/"vendorDirectory/":/"/external/path"}/nerror: Uncaught (in promise) Error: Error: Could not find file: packages/build/tests/monitor/fixtures/edge_function_error/netlify/edge-functions/file.ts/n const ret = new Error(getStringFromWasm0(arg0, arg1));/n ^/n at __wbg_new_HEXADECIMAL_ID (packages/edge-bundler/deno/vendor/deno.land/x/[email protected]/eszip_wasm.generated.js:80:80)/n at <anonymous> (wasm://wasm/HEXADECIMAL_ID:1:80)/n at <anonymous> (wasm://wasm/HEXADECIMAL_ID:1:80)/n at <anonymous> (wasm://wasm/HEXADECIMAL_ID:1:80)/n at __wbg_adapter_40 (packages/edge-bundler/deno/vendor/deno.land/x/[email protected]/eszip_wasm.generated.js:80:8)/n at real (packages/edge-bundler/deno/vendor/deno.land/x/[email protected]/eszip_wasm.generated.js:80:80)/n at eventLoopTick (ext:core/01_core.js:80:7)",␊
"context": "Bundling of edge function failed",␊
"groupingHash": "Bundling of edge function failed/nCommand failed with exit code 0: deno run --allow-all --no-config /external/path --quiet /external/path {/"/":/"/",/"/":/"/",/"/":[],/"/":[{/"/":/"/",/"/":/"/"}],/"/":/"/"/external/path"/"/external/path"/"/external/path"/"/external/path"/"/external/path"/"/external/path"/"/external/path"/"/external/path"/",/"/":/"/"}/nerror: Uncaught (in promise) Error: Error: Could not find file: /external/path const ret = new Error(getStringFromWasm0(arg0, arg0));/n ^/n at __wbg_new_hex /external/path at <anonymous> /external/path at <anonymous> /external/path at <anonymous> /external/path at __wbg_adapter_0 /external/path at real /external/path at eventLoopTick /external/path",␊
"severity": "info",␊
Expand Down
Binary file modified packages/build/tests/monitor/snapshots/tests.js.snap
Binary file not shown.
2 changes: 1 addition & 1 deletion packages/edge-bundler/deno/lib/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const loadWithRetry = (specifier: string, delay = 1000, maxTry = 3) => {
maxTry,
});
} catch (error) {
if (isTooManyTries(error)) {
if (isTooManyTries(error as Error)) {
console.error(`Loading ${specifier} failed after ${maxTry} tries.`);
}
throw error;
Expand Down
11 changes: 11 additions & 0 deletions packages/edge-bundler/node/__snapshots__/bundler.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`Adds a custom error property to user errors during bundling 1`] = `
"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));
^
at <anonymous> (file://build/packages/edge-bundler/deno/vendor/deno.land/x/[email protected]/eszip_wasm.generated.js:443:19)"
`;
14 changes: 9 additions & 5 deletions packages/edge-bundler/node/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 - 1.46.3'
const DENO_VERSION_RANGE = '1.39.0 - 2.2.4'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do note, that if system doesn't have deno installed, or installed version doesn't match the range, we will continue to download 1.39.0 for our use due to way this is implemented:

const getLatestVersionForRange = async (range: string) => {
const minimumVersion = semver.minVersion(range)?.version
// We should never get here, because it means that `DENO_VERSION_RANGE` is
// a malformed semver range. If this does happen, let's throw an error so
// that the tests catch it.
if (minimumVersion === undefined) {
throw new Error('Incorrect version range specified by Edge Bundler')
}
const latestVersion = await getLatestVersion()
if (latestVersion === undefined || !semver.satisfies(latestVersion, range)) {
return minimumVersion
}
return latestVersion
}

And fact that actually latest version of Deno today is 2.2.6 (and will continue to be bumped), so that latest version is not in our range and the function falling back to min version.

I think this might be desirable to be the case at least temporarily to possibly migrate with slower pace to catch things we didn't catch here, but I just want to at least flag it here


type OnBeforeDownloadHook = () => void | Promise<void>
type OnAfterDownloadHook = (error?: Error) => void | Promise<void>
Expand Down Expand Up @@ -99,7 +99,7 @@ class DenoBridge {
return binaryPath
}

private async getBinaryVersion(binaryPath: string) {
async getBinaryVersion(binaryPath: string) {
try {
const { stdout } = await execa(binaryPath, ['--version'])
const version = stdout.match(/^deno ([\d.]+)/)
Expand Down Expand Up @@ -202,19 +202,23 @@ class DenoBridge {
await fs.mkdir(this.cacheDirectory, { recursive: true })
}

async getBinaryPath() {
async getBinaryPath(options?: { silent?: boolean }) {
const globalPath = await this.getGlobalBinary()

if (globalPath !== undefined) {
this.logger.system('Using global installation of Deno CLI')
if (!options?.silent) {
this.logger.system('Using global installation of Deno CLI')
}

return { global: true, path: globalPath }
}

const cachedPath = await this.getCachedBinary()

if (cachedPath !== undefined) {
this.logger.system('Using cached Deno CLI from', cachedPath)
if (!options?.silent) {
this.logger.system('Using cached Deno CLI from', cachedPath)
}

return { global: false, path: cachedPath }
}
Expand Down
17 changes: 7 additions & 10 deletions packages/edge-bundler/node/bundler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,16 +104,13 @@ test('Adds a custom error property to user errors during bundling', async () =>
await bundle([sourceDirectory], distPath, declarations, { basePath })
} catch (error) {
expect(error).toBeInstanceOf(BundleError)
const [messageBeforeStack] = (error as BundleError).message.split('at <anonymous> (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));
^
"
`)
const messageBeforeStack = (error as BundleError).message
expect(
messageBeforeStack
.replace(/file:\/\/\/(.*?\/)(build\/packages\/edge-bundler\/deno\/vendor\/deno\.land\/x\/eszip.*)/, 'file://$2')
// eslint-disable-next-line no-control-regex
.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, ''),
).toMatchSnapshot()
expect((error as BundleError).customErrorInfo).toEqual({
location: {
format: 'eszip',
Expand Down
8 changes: 6 additions & 2 deletions packages/edge-bundler/node/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { promises as fs } from 'fs'
import { join } from 'path'
import { pathToFileURL } from 'url'

import { SemVer } from 'semver'
import tmp from 'tmp-promise'

import { DenoBridge } from './bridge.js'
Expand Down Expand Up @@ -95,22 +96,25 @@ export const getFunctionConfig = async ({
// The extractor will use its exit code to signal different error scenarios,
// based on the list of exit codes we send as an argument. We then capture
// the exit code to know exactly what happened and guide people accordingly.
const version = new SemVer((await deno.getBinaryVersion((await deno.getBinaryPath({ silent: true })).path)) || '')

const { exitCode, stderr, stdout } = await deno.run(
[
'run',
'--allow-env',
version.major >= 2 ? '--allow-import' : '',
'--allow-net',
'--allow-read',
`--allow-write=${collector.path}`,
'--quiet',
`--import-map=${importMap.toDataURL()}`,
'--no-config',
'--node-modules-dir=false',
'--quiet',
extractorPath,
pathToFileURL(func.path).href,
pathToFileURL(collector.path).href,
JSON.stringify(ConfigExitCode),
],
].filter(Boolean),
{ rejectOnExitCode: false },
)

Expand Down
5 changes: 2 additions & 3 deletions packages/edge-bundler/node/server/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,9 @@ test('Starts a server and serves requests for edge functions', async () => {
expect(functionsConfig).toEqual([{ path: '/my-function' }, {}, { path: '/global-netlify' }])
expect(npmSpecifiersWithExtraneousFiles).toEqual(['dictionary'])

const modules = graph?.modules.filter(({ kind, mediaType }) => kind === 'esm' && mediaType === 'TypeScript')
for (const key in functions) {
const graphEntry = graph?.modules.some(
({ kind, mediaType, local }) => kind === 'esm' && mediaType === 'TypeScript' && local === functions[key].path,
)
const graphEntry = modules?.some(({ local }) => local === functions[key].path)

expect(graphEntry).toBe(true)
}
Expand Down
3 changes: 2 additions & 1 deletion packages/edge-bundler/node/server/server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { WriteStream } from 'fs'
import { readdir, unlink } from 'fs/promises'
import { join } from 'path'
import { pathToFileURL } from 'url'

import { DenoBridge, OnAfterDownloadHook, OnBeforeDownloadHook, ProcessRef } from '../bridge.js'
import { getFunctionConfig, FunctionConfig } from '../config.js'
Expand Down Expand Up @@ -125,7 +126,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', '--json', pathToFileURL(stage2Path).href])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we already were using this setup in

const { exitCode, stderr, stdout } = await deno.run(
[
'run',
'--allow-env',
'--allow-net',
'--allow-read',
`--allow-write=${collector.path}`,
'--quiet',
`--import-map=${importMap.toDataURL()}`,
'--no-config',
'--node-modules-dir=false',
extractorPath,
pathToFileURL(func.path).href,
pathToFileURL(collector.path).href,
JSON.stringify(ConfigExitCode),
],
for example

Without it, deno 2 fails this with this kind of errors on windows:

{
  version: 1,
  roots: [
    'c:\\Users\\misiek\\dev\\netlify-build\\packages\\edge-bundler\\test\\fixtures\\serve_test\\.netlify\\edge-functions-serve\\dev.js'
  ],
  modules: [
    {
      specifier: 'c:\\Users\\misiek\\dev\\netlify-build\\packages\\edge-bundler\\test\\fixtures\\serve_test\\.netlify\\edge-functions-serve\\dev.js',
      error: 'Unsupported scheme "c" for module "c:\\Users\\misiek\\dev\\netlify-build\\packages\\edge-bundler\\test\\fixtures\\serve_test\\.netlify\\edge-functions-serve\\dev.js". Supported schemes:\n' +
        ' - "blob"\n' +
        ' - "data"\n' +
        ' - "file"\n' +
        ' - "http"\n' +
        ' - "https"\n' +
        ' - "jsr"\n' +
        ' - "npm"'
    }
  ],
  redirects: {}
}


graph = JSON.parse(stdout)
} catch {
Expand Down
2 changes: 1 addition & 1 deletion packages/edge-bundler/test/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,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', '--no-check', '--import-map', importMapPath, inspectFunction(stage2Path)])
const evalCommand = execa('deno', ['eval', '--import-map', importMapPath, inspectFunction(stage2Path)])

evalCommand.stderr?.pipe(stderr)

Expand Down
Loading