@netlify/edge-bundler
Advanced tools
Comparing version 9.4.1 to 9.5.0
@@ -1,3 +0,3 @@ | ||
import { load } from "https://deno.land/x/eszip@v0.40.0/loader.ts"; | ||
import { LoadResponse } from "https://deno.land/x/eszip@v0.40.0/mod.ts"; | ||
import { load } from "https://deno.land/x/eszip@v0.55.2/loader.ts"; | ||
import { LoadResponse } from "https://deno.land/x/eszip@v0.55.2/mod.ts"; | ||
import * as path from "https://deno.land/std@0.177.0/path/mod.ts"; | ||
@@ -4,0 +4,0 @@ import { retryAsync } from "https://deno.land/x/retry@v2.0.0/mod.ts"; |
@@ -1,2 +0,2 @@ | ||
import { build, LoadResponse } from 'https://deno.land/x/eszip@v0.40.0/mod.ts' | ||
import { build, LoadResponse } from 'https://deno.land/x/eszip@v0.55.2/mod.ts' | ||
@@ -3,0 +3,0 @@ import * as path from 'https://deno.land/std@0.177.0/path/mod.ts' |
/// <reference types="node" /> | ||
import { ExecaChildProcess } from 'execa'; | ||
import { Logger } from './logger.js'; | ||
declare const DENO_VERSION_RANGE = "^1.32.5"; | ||
declare const DENO_VERSION_RANGE = "^1.37.0"; | ||
type OnBeforeDownloadHook = () => void | Promise<void>; | ||
@@ -6,0 +6,0 @@ type OnAfterDownloadHook = (error?: Error) => void | Promise<void>; |
@@ -14,3 +14,3 @@ import { promises as fs } from 'fs'; | ||
// build-image/buildbot does satisfy this range! | ||
const DENO_VERSION_RANGE = '^1.32.5'; | ||
const DENO_VERSION_RANGE = '^1.37.0'; | ||
class DenoBridge { | ||
@@ -17,0 +17,0 @@ constructor(options) { |
@@ -150,6 +150,3 @@ import { promises as fs } from 'fs'; | ||
}, {}); | ||
const safelyVendorNPMSpecifiers = async ({ basePath, featureFlags, functions, importMap, logger, vendorDirectory, }) => { | ||
if (!featureFlags.edge_functions_npm_modules) { | ||
return; | ||
} | ||
const safelyVendorNPMSpecifiers = async ({ basePath, functions, importMap, logger, vendorDirectory, }) => { | ||
try { | ||
@@ -156,0 +153,0 @@ return await vendorNPMSpecifiers({ |
@@ -108,28 +108,4 @@ import { access, readdir, readFile, rm, writeFile } from 'fs/promises'; | ||
}); | ||
test('Prints a nice error message when user tries importing an npm module and npm support is disabled', async () => { | ||
test('Prints a nice error message when user tries importing an npm module', async () => { | ||
expect.assertions(2); | ||
const { basePath, cleanup, distPath } = await useFixture('imports_npm_module'); | ||
const sourceDirectory = join(basePath, 'functions'); | ||
const declarations = [ | ||
{ | ||
function: 'func1', | ||
path: '/func1', | ||
}, | ||
]; | ||
try { | ||
await bundle([sourceDirectory], distPath, declarations, { | ||
basePath, | ||
importMapPaths: [join(basePath, 'import_map.json')], | ||
}); | ||
} | ||
catch (error) { | ||
expect(error).toBeInstanceOf(BundleError); | ||
expect(error.message).toEqual(`It seems like you're trying to import an npm module. This is only supported via CDNs like esm.sh. Have you tried 'import mod from "https://esm.sh/parent-1"'?`); | ||
} | ||
finally { | ||
await cleanup(); | ||
} | ||
}); | ||
test('Prints a nice error message when user tries importing an npm module and npm support is enabled', async () => { | ||
expect.assertions(2); | ||
const { basePath, cleanup, distPath } = await useFixture('imports_npm_module_scheme'); | ||
@@ -146,3 +122,2 @@ const sourceDirectory = join(basePath, 'functions'); | ||
basePath, | ||
featureFlags: { edge_functions_npm_modules: true }, | ||
}); | ||
@@ -158,23 +133,2 @@ } | ||
}); | ||
test('Prints a nice error message when user tries importing NPM module with npm: scheme', async () => { | ||
expect.assertions(2); | ||
const { basePath, cleanup, distPath } = await useFixture('imports_npm_module_scheme'); | ||
const sourceDirectory = join(basePath, 'functions'); | ||
const declarations = [ | ||
{ | ||
function: 'func1', | ||
path: '/func1', | ||
}, | ||
]; | ||
try { | ||
await bundle([sourceDirectory], distPath, declarations, { basePath }); | ||
} | ||
catch (error) { | ||
expect(error).toBeInstanceOf(BundleError); | ||
expect(error.message).toEqual(`It seems like you're trying to import an npm module. This is only supported via CDNs like esm.sh. Have you tried 'import mod from "https://esm.sh/p-retry"'?`); | ||
} | ||
finally { | ||
await cleanup(); | ||
} | ||
}); | ||
test('Does not add a custom error property to system errors during bundling', async () => { | ||
@@ -419,3 +373,2 @@ expect.assertions(1); | ||
basePath, | ||
featureFlags: { edge_functions_npm_modules: true }, | ||
importMapPaths: [join(basePath, 'import_map.json')], | ||
@@ -434,1 +387,23 @@ vendorDirectory: vendorDirectory.path, | ||
}); | ||
test('Loads JSON modules', async () => { | ||
const { basePath, cleanup, distPath } = await useFixture('imports_json'); | ||
const sourceDirectory = join(basePath, 'functions'); | ||
const declarations = [ | ||
{ | ||
function: 'func1', | ||
path: '/func1', | ||
}, | ||
]; | ||
const vendorDirectory = await tmp.dir(); | ||
await bundle([sourceDirectory], distPath, declarations, { | ||
basePath, | ||
vendorDirectory: vendorDirectory.path, | ||
}); | ||
const manifestFile = await readFile(resolve(distPath, 'manifest.json'), 'utf8'); | ||
const manifest = JSON.parse(manifestFile); | ||
const bundlePath = join(distPath, manifest.bundles[0].asset); | ||
const { func1 } = await runESZIP(bundlePath, vendorDirectory.path); | ||
expect(func1).toBe(`{"foo":"bar"}`); | ||
await cleanup(); | ||
await rm(vendorDirectory.path, { force: true, recursive: true }); | ||
}); |
@@ -1,12 +0,6 @@ | ||
declare const defaultFlags: { | ||
edge_functions_fail_unsupported_regex: boolean; | ||
edge_functions_npm_modules: boolean; | ||
}; | ||
declare const defaultFlags: {}; | ||
type FeatureFlag = keyof typeof defaultFlags; | ||
type FeatureFlags = Partial<Record<FeatureFlag, boolean>>; | ||
declare const getFlags: (input?: Record<string, boolean>, flags?: { | ||
edge_functions_fail_unsupported_regex: boolean; | ||
edge_functions_npm_modules: boolean; | ||
}) => FeatureFlags; | ||
declare const getFlags: (input?: Record<string, boolean>, flags?: {}) => FeatureFlags; | ||
export { defaultFlags, getFlags }; | ||
export type { FeatureFlag, FeatureFlags }; |
@@ -1,5 +0,2 @@ | ||
const defaultFlags = { | ||
edge_functions_fail_unsupported_regex: false, | ||
edge_functions_npm_modules: false, | ||
}; | ||
const defaultFlags = {}; | ||
const getFlags = (input = {}, flags = defaultFlags) => Object.entries(flags).reduce((result, [key, defaultValue]) => ({ | ||
@@ -6,0 +3,0 @@ ...result, |
@@ -18,3 +18,3 @@ import { DenoBridge } from '../bridge.js'; | ||
} | ||
declare const bundleESZIP: ({ basePath, buildID, debug, deno, distDirectory, externals, featureFlags, functions, importMap, vendorDirectory, }: BundleESZIPOptions) => Promise<Bundle>; | ||
declare const bundleESZIP: ({ basePath, buildID, debug, deno, distDirectory, externals, functions, importMap, vendorDirectory, }: BundleESZIPOptions) => Promise<Bundle>; | ||
export { bundleESZIP as bundle }; |
@@ -9,3 +9,3 @@ import { join } from 'path'; | ||
import { getFileHash } from '../utils/sha256.js'; | ||
const bundleESZIP = async ({ basePath, buildID, debug, deno, distDirectory, externals, featureFlags, functions, importMap, vendorDirectory, }) => { | ||
const bundleESZIP = async ({ basePath, buildID, debug, deno, distDirectory, externals, functions, importMap, vendorDirectory, }) => { | ||
const extension = '.eszip'; | ||
@@ -37,3 +37,3 @@ const destPath = join(distDirectory, `${buildID}${extension}`); | ||
catch (error) { | ||
throw wrapBundleError(wrapNpmImportError(error, Boolean(featureFlags.edge_functions_npm_modules)), { | ||
throw wrapBundleError(wrapNpmImportError(error), { | ||
format: 'eszip', | ||
@@ -40,0 +40,0 @@ }); |
@@ -1,2 +0,2 @@ | ||
import { mkdir, rm, writeFile } from 'fs/promises'; | ||
import { mkdir, writeFile } from 'fs/promises'; | ||
import { join } from 'path'; | ||
@@ -7,3 +7,2 @@ import { pathToFileURL } from 'url'; | ||
const generateStage2 = async ({ bootstrapURL, distDirectory, fileName, formatExportTypeError, formatImportError, functions, }) => { | ||
await rm(distDirectory, { force: true, recursive: true, maxRetries: 3 }); | ||
await mkdir(distDirectory, { recursive: true }); | ||
@@ -10,0 +9,0 @@ const entryPoint = getLocalEntryPoint(functions, { bootstrapURL, formatExportTypeError, formatImportError }); |
@@ -45,3 +45,3 @@ import type { Bundle } from './bundle.js'; | ||
} | ||
declare const generateManifest: ({ bundles, declarations, featureFlags, functions, userFunctionConfig, internalFunctionConfig, importMap, layers, }: GenerateManifestOptions) => Manifest; | ||
declare const generateManifest: ({ bundles, declarations, functions, userFunctionConfig, internalFunctionConfig, importMap, layers, }: GenerateManifestOptions) => Manifest; | ||
interface WriteManifestOptions extends GenerateManifestOptions { | ||
@@ -48,0 +48,0 @@ distDirectory: string; |
@@ -49,3 +49,3 @@ import { promises as fs } from 'fs'; | ||
}; | ||
const generateManifest = ({ bundles = [], declarations = [], featureFlags, functions, userFunctionConfig = {}, internalFunctionConfig = {}, importMap, layers = [], }) => { | ||
const generateManifest = ({ bundles = [], declarations = [], functions, userFunctionConfig = {}, internalFunctionConfig = {}, importMap, layers = [], }) => { | ||
const preCacheRoutes = []; | ||
@@ -75,4 +75,4 @@ const postCacheRoutes = []; | ||
} | ||
const pattern = getRegularExpression(declaration, featureFlags); | ||
const excludedPattern = getExcludedRegularExpressions(declaration, featureFlags); | ||
const pattern = getRegularExpression(declaration); | ||
const excludedPattern = getExcludedRegularExpressions(declaration); | ||
const route = { | ||
@@ -127,3 +127,3 @@ function: func.name, | ||
}; | ||
const getRegularExpression = (declaration, featureFlags) => { | ||
const getRegularExpression = (declaration) => { | ||
if ('pattern' in declaration) { | ||
@@ -134,8 +134,3 @@ try { | ||
catch (error) { | ||
// eslint-disable-next-line max-depth | ||
if (featureFlags === null || featureFlags === void 0 ? void 0 : featureFlags.edge_functions_fail_unsupported_regex) { | ||
throw new Error(`Could not parse path declaration of function '${declaration.function}': ${error.message}`); | ||
} | ||
console.warn(`Function '${declaration.function}' uses an unsupported regular expression and will not be invoked: ${error.message}`); | ||
return declaration.pattern; | ||
throw wrapBundleError(new Error(`Could not parse path declaration of function '${declaration.function}': ${error.message}`)); | ||
} | ||
@@ -145,3 +140,3 @@ } | ||
}; | ||
const getExcludedRegularExpressions = (declaration, featureFlags) => { | ||
const getExcludedRegularExpressions = (declaration) => { | ||
if ('excludedPattern' in declaration && declaration.excludedPattern) { | ||
@@ -156,7 +151,3 @@ const excludedPatterns = Array.isArray(declaration.excludedPattern) | ||
catch (error) { | ||
if (featureFlags === null || featureFlags === void 0 ? void 0 : featureFlags.edge_functions_fail_unsupported_regex) { | ||
throw new Error(`Could not parse path declaration of function '${declaration.function}': ${error.message}`); | ||
} | ||
console.warn(`Function '${declaration.function}' uses an unsupported regular expression and will therefore not be invoked: ${error.message}`); | ||
return excludedPattern; | ||
throw wrapBundleError(new Error(`Could not parse path declaration of function '${declaration.function}': ${error.message}`)); | ||
} | ||
@@ -163,0 +154,0 @@ }); |
import { env } from 'process'; | ||
import { test, expect, vi } from 'vitest'; | ||
import { test, expect } from 'vitest'; | ||
import { getRouteMatcher } from '../test/util.js'; | ||
@@ -389,25 +389,8 @@ import { BundleFormat } from './bundle.js'; | ||
}); | ||
test('Shows a warning if the regular expression contains a negative lookahead', () => { | ||
const mockConsoleWarn = vi.fn(); | ||
const consoleWarn = console.warn; | ||
console.warn = mockConsoleWarn; | ||
test('Throws an error if the regular expression contains a negative lookahead', () => { | ||
const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }]; | ||
const declarations = [{ function: 'func-1', pattern: '^/\\w+(?=\\d)$' }]; | ||
const manifest = generateManifest({ | ||
bundles: [], | ||
declarations, | ||
functions, | ||
}); | ||
console.warn = consoleWarn; | ||
expect(manifest.routes).toEqual([{ function: 'func-1', pattern: '^/\\w+(?=\\d)$', excluded_patterns: [] }]); | ||
expect(mockConsoleWarn).toHaveBeenCalledOnce(); | ||
expect(mockConsoleWarn).toHaveBeenCalledWith("Function 'func-1' uses an unsupported regular expression and will not be invoked: Regular expressions with lookaheads are not supported"); | ||
}); | ||
test('Throws an error if the regular expression contains a negative lookahead and the `edge_functions_fail_unsupported_regex` flag is set', () => { | ||
const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }]; | ||
const declarations = [{ function: 'func-1', pattern: '^/\\w+(?=\\d)$' }]; | ||
expect(() => generateManifest({ | ||
bundles: [], | ||
declarations, | ||
featureFlags: { edge_functions_fail_unsupported_regex: true }, | ||
functions, | ||
@@ -414,0 +397,0 @@ })).toThrowError(/^Could not parse path declaration of function 'func-1': Regular expressions with lookaheads are not supported$/); |
@@ -20,3 +20,4 @@ /// <reference types="node" /> | ||
npmSpecifiersWithExtraneousFiles: string[]; | ||
outputFiles: string[]; | ||
} | undefined>; | ||
export {}; |
@@ -202,2 +202,3 @@ import { promises as fs } from 'fs'; | ||
format: 'esm', | ||
mainFields: ['module', 'browser', 'main'], | ||
logLevel: 'error', | ||
@@ -259,3 +260,4 @@ nodePaths, | ||
npmSpecifiersWithExtraneousFiles, | ||
outputFiles: outputFiles.map((file) => file.path), | ||
}; | ||
}; |
declare class NPMImportError extends Error { | ||
constructor(originalError: Error, moduleName: string, supportsNPM: boolean); | ||
constructor(originalError: Error, moduleName: string); | ||
} | ||
declare const wrapNpmImportError: (input: unknown, supportsNPM: boolean) => unknown; | ||
declare const wrapNpmImportError: (input: unknown) => unknown; | ||
export { NPMImportError, wrapNpmImportError }; |
class NPMImportError extends Error { | ||
constructor(originalError, moduleName, supportsNPM) { | ||
let message = `It seems like you're trying to import an npm module. This is only supported via CDNs like esm.sh. Have you tried 'import mod from "https://esm.sh/${moduleName}"'?`; | ||
if (supportsNPM) { | ||
message = `There was an error when loading the '${moduleName}' npm module. Support for npm modules in edge functions is an experimental feature. Refer to https://ntl.fyi/edge-functions-npm for more information.`; | ||
} | ||
super(message); | ||
constructor(originalError, moduleName) { | ||
super(`There was an error when loading the '${moduleName}' npm module. Support for npm modules in edge functions is an experimental feature. Refer to https://ntl.fyi/edge-functions-npm for more information.`); | ||
this.name = 'NPMImportError'; | ||
@@ -14,3 +10,3 @@ this.stack = originalError.stack; | ||
} | ||
const wrapNpmImportError = (input, supportsNPM) => { | ||
const wrapNpmImportError = (input) => { | ||
if (input instanceof Error) { | ||
@@ -20,3 +16,3 @@ const match = input.message.match(/Relative import path "(.*)" not prefixed with/); | ||
const [, moduleName] = match; | ||
return new NPMImportError(input, moduleName, supportsNPM); | ||
return new NPMImportError(input, moduleName); | ||
} | ||
@@ -26,3 +22,3 @@ const schemeMatch = input.message.match(/Error: Module not found "npm:(.*)"/); | ||
const [, moduleName] = schemeMatch; | ||
return new NPMImportError(input, moduleName, supportsNPM); | ||
return new NPMImportError(input, moduleName); | ||
} | ||
@@ -29,0 +25,0 @@ } |
@@ -0,1 +1,3 @@ | ||
import { readdir, unlink } from 'fs/promises'; | ||
import { join } from 'path'; | ||
import { DenoBridge } from '../bridge.js'; | ||
@@ -9,3 +11,13 @@ import { getFunctionConfig } from '../config.js'; | ||
import { killProcess, waitForServer } from './util.js'; | ||
const prepareServer = ({ basePath, bootstrapURL, deno, distDirectory, distImportMapPath, featureFlags, flags: denoFlags, formatExportTypeError, formatImportError, importMap: baseImportMap, logger, port, }) => { | ||
/** | ||
* Cleans up a directory, except for the files specified in the `except` array. | ||
* Both should be given as absolute paths. | ||
* Assumes the directory doesn't contain any nested directories. | ||
*/ | ||
const cleanDirectory = async (directory, except) => { | ||
const files = await readdir(directory); | ||
const toBeDeleted = files.filter((file) => !except.includes(join(directory, file))); | ||
await Promise.all(toBeDeleted.map((file) => unlink(join(directory, file)))); | ||
}; | ||
const prepareServer = ({ basePath, bootstrapURL, deno, distDirectory, distImportMapPath, flags: denoFlags, formatExportTypeError, formatImportError, importMap: baseImportMap, logger, port, }) => { | ||
const processRef = {}; | ||
@@ -28,17 +40,19 @@ const startServer = async (functions, env = {}, options = {}) => { | ||
const npmSpecifiersWithExtraneousFiles = []; | ||
if (featureFlags === null || featureFlags === void 0 ? void 0 : featureFlags.edge_functions_npm_modules) { | ||
const vendor = await vendorNPMSpecifiers({ | ||
basePath, | ||
directory: distDirectory, | ||
functions: functions.map(({ path }) => path), | ||
importMap, | ||
logger, | ||
referenceTypes: true, | ||
}); | ||
if (vendor) { | ||
features.npmModules = true; | ||
importMap.add(vendor.importMap); | ||
npmSpecifiersWithExtraneousFiles.push(...vendor.npmSpecifiersWithExtraneousFiles); | ||
} | ||
// we keep track of the files that are relevant to the user's code, so we can clean up leftovers from past executions later | ||
const relevantFiles = [stage2Path]; | ||
const vendor = await vendorNPMSpecifiers({ | ||
basePath, | ||
directory: distDirectory, | ||
functions: functions.map(({ path }) => path), | ||
importMap, | ||
logger, | ||
referenceTypes: true, | ||
}); | ||
if (vendor) { | ||
features.npmModules = true; | ||
importMap.add(vendor.importMap); | ||
npmSpecifiersWithExtraneousFiles.push(...vendor.npmSpecifiersWithExtraneousFiles); | ||
relevantFiles.push(...vendor.outputFiles); | ||
} | ||
await cleanDirectory(distDirectory, relevantFiles); | ||
try { | ||
@@ -45,0 +59,0 @@ // This command will print a JSON object with all the modules found in |
@@ -24,5 +24,2 @@ import { readFile } from 'fs/promises'; | ||
servePath, | ||
featureFlags: { | ||
edge_functions_npm_modules: true, | ||
}, | ||
}); | ||
@@ -29,0 +26,0 @@ const functions = [ |
@@ -58,3 +58,3 @@ import { promises as fs } from 'fs'; | ||
'--allow-all', | ||
'https://deno.land/x/eszip@v0.40.0/eszip.ts', | ||
'https://deno.land/x/eszip@v0.55.2/eszip.ts', | ||
'x', | ||
@@ -61,0 +61,0 @@ eszipPath, |
{ | ||
"name": "@netlify/edge-bundler", | ||
"version": "9.4.1", | ||
"version": "9.5.0", | ||
"description": "Intelligently prepare Netlify Edge Functions for deployment", | ||
@@ -5,0 +5,0 @@ "type": "module", |
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
3775484
158
8960
32
9