From 8b030046a07acfe50d4b3a41cd5a600b741de1fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Mon, 5 Dec 2022 21:19:35 +0000 Subject: [PATCH 1/6] feat: save import map URL to manifest --- deno/lib/stage2.ts | 3 +- node/bundle.ts | 1 + node/bundler.test.ts | 152 ++++++----- node/bundler.ts | 1 + node/config.test.ts | 8 +- node/formats/eszip.ts | 27 +- node/import_map.test.ts | 17 +- node/import_map.ts | 48 +++- node/manifest.ts | 17 +- package-lock.json | 237 ++++++++++++++++++ package.json | 1 + shared/consts.ts | 1 + .../with_import_maps/functions/func1.ts | 2 +- .../functions/import_map.json | 2 +- test/fixtures/with_import_maps/helper.ts | 2 + test/util.ts | 24 +- 16 files changed, 448 insertions(+), 95 deletions(-) create mode 100644 shared/consts.ts create mode 100644 test/fixtures/with_import_maps/helper.ts diff --git a/deno/lib/stage2.ts b/deno/lib/stage2.ts index ae23154a..c6c796d6 100644 --- a/deno/lib/stage2.ts +++ b/deno/lib/stage2.ts @@ -3,7 +3,8 @@ import { build, LoadResponse } from 'https://deno.land/x/eszip@v0.28.0/mod.ts' import * as path from 'https://deno.land/std@0.127.0/path/mod.ts' import type { InputFunction, WriteStage2Options } from '../../shared/stage2.ts' -import { PUBLIC_SPECIFIER, STAGE2_SPECIFIER, virtualRoot } from './consts.ts' +import { virtualRoot } from '../../shared/consts.ts' +import { PUBLIC_SPECIFIER, STAGE2_SPECIFIER } from './consts.ts' import { inlineModule, loadFromVirtualRoot, loadWithRetry } from './common.ts' interface FunctionReference { diff --git a/node/bundle.ts b/node/bundle.ts index 522130a7..e578cb5e 100644 --- a/node/bundle.ts +++ b/node/bundle.ts @@ -7,4 +7,5 @@ export interface Bundle { extension: string format: BundleFormat hash: string + importMapURL?: string } diff --git a/node/bundler.test.ts b/node/bundler.test.ts index 20c74b91..193e93c5 100644 --- a/node/bundler.test.ts +++ b/node/bundler.test.ts @@ -6,60 +6,66 @@ import { deleteAsync } from 'del' import tmp from 'tmp-promise' import { test, expect } from 'vitest' -import { fixturesDir } from '../test/util.js' +import { fixturesDir, useFixture } from '../test/util.js' import { BundleError } from './bundle_error.js' import { bundle, BundleOptions } from './bundler.js' import { isNodeError } from './utils/error.js' test('Produces an ESZIP bundle', async () => { - const sourceDirectory = resolve(fixturesDir, 'with_import_maps', 'functions') - const tmpDir = await tmp.dir() + const { basePath, cleanup, distPath } = await useFixture('with_import_maps') const declarations = [ { function: 'func1', path: '/func1', }, ] - const result = await bundle([sourceDirectory], tmpDir.path, declarations, { - basePath: fixturesDir, + const sourceDirectory = join(basePath, 'functions') + const result = await bundle([sourceDirectory], distPath, declarations, { + basePath, configPath: join(sourceDirectory, 'config.json'), }) - const generatedFiles = await fs.readdir(tmpDir.path) + const generatedFiles = await fs.readdir(distPath) expect(result.functions.length).toBe(1) - expect(generatedFiles.length).toBe(2) - const manifestFile = await fs.readFile(resolve(tmpDir.path, 'manifest.json'), 'utf8') + // ESZIP, manifest and import map. + expect(generatedFiles.length).toBe(3) + + const manifestFile = await fs.readFile(resolve(distPath, 'manifest.json'), 'utf8') const manifest = JSON.parse(manifestFile) - const { bundles } = manifest + const { bundles, import_map: importMapURL } = manifest expect(bundles.length).toBe(1) expect(bundles[0].format).toBe('eszip2') expect(generatedFiles.includes(bundles[0].asset)).toBe(true) - await fs.rmdir(tmpDir.path, { recursive: true }) + expect(importMapURL).toBe('file:///root/.netlify/edge-functions-dist/import_map.json') + + await cleanup() }) test('Uses the vendored eszip module instead of fetching it from deno.land', async () => { - const sourceDirectory = resolve(fixturesDir, 'with_import_maps', 'functions') - const tmpDir = await tmp.dir() + const { basePath, cleanup, distPath } = await useFixture('with_import_maps') const declarations = [ { function: 'func1', path: '/func1', }, ] - const result = await bundle([sourceDirectory], tmpDir.path, declarations, { - basePath: fixturesDir, + const sourceDirectory = join(basePath, 'functions') + const result = await bundle([sourceDirectory], distPath, declarations, { + basePath, configPath: join(sourceDirectory, 'config.json'), }) - const generatedFiles = await fs.readdir(tmpDir.path) + const generatedFiles = await fs.readdir(distPath) expect(result.functions.length).toBe(1) - expect(generatedFiles.length).toBe(2) - const manifestFile = await fs.readFile(resolve(tmpDir.path, 'manifest.json'), 'utf8') + // ESZIP, manifest and import map. + expect(generatedFiles.length).toBe(3) + + const manifestFile = await fs.readFile(resolve(distPath, 'manifest.json'), 'utf8') const manifest = JSON.parse(manifestFile) const { bundles } = manifest @@ -67,14 +73,14 @@ test('Uses the vendored eszip module instead of fetching it from deno.land', asy expect(bundles[0].format).toBe('eszip2') expect(generatedFiles.includes(bundles[0].asset)).toBe(true) - await fs.rmdir(tmpDir.path, { recursive: true }) + await cleanup() }) test('Adds a custom error property to user errors during bundling', async () => { expect.assertions(2) - const sourceDirectory = resolve(fixturesDir, 'invalid_functions', 'functions') - const tmpDir = await tmp.dir() + const { basePath, cleanup, distPath } = await useFixture('invalid_functions') + const sourceDirectory = join(basePath, 'functions') const declarations = [ { function: 'func1', @@ -83,7 +89,7 @@ test('Adds a custom error property to user errors during bundling', async () => ] try { - await bundle([sourceDirectory], tmpDir.path, declarations) + await bundle([sourceDirectory], distPath, declarations, { basePath }) } catch (error) { expect(error).toBeInstanceOf(BundleError) expect((error as BundleError).customErrorInfo).toEqual({ @@ -93,14 +99,16 @@ test('Adds a custom error property to user errors during bundling', async () => }, type: 'functionsBundling', }) + } finally { + await cleanup() } }) test('Prints a nice error message when user tries importing NPM module', async () => { expect.assertions(2) - const sourceDirectory = resolve(fixturesDir, 'imports_npm_module', 'functions') - const tmpDir = await tmp.dir() + const { basePath, cleanup, distPath } = await useFixture('imports_npm_module') + const sourceDirectory = join(basePath, 'functions') const declarations = [ { function: 'func1', @@ -109,12 +117,14 @@ test('Prints a nice error message when user tries importing NPM module', async ( ] try { - await bundle([sourceDirectory], tmpDir.path, declarations) + await bundle([sourceDirectory], distPath, declarations, { basePath }) } catch (error) { expect(error).toBeInstanceOf(BundleError) expect((error as BundleError).message).toEqual( `It seems like you're trying to import an npm module. This is only supported in Deno via CDNs like esm.sh. Have you tried 'import mod from "https://esm.sh/p-retry"'?`, ) + } finally { + await cleanup() } }) @@ -123,7 +133,7 @@ test('Does not add a custom error property to system errors during bundling', as try { // @ts-expect-error Sending bad input to `bundle` to force a system error. - await bundle([123, 321], tmpDir.path, declarations) + await bundle([123, 321], '/some/directory', declarations) } catch (error) { expect(error).not.toBeInstanceOf(BundleError) } @@ -132,8 +142,8 @@ test('Does not add a custom error property to system errors during bundling', as test('Uses the cache directory as the `DENO_DIR` value if the `edge_functions_cache_deno_dir` feature flag is set', async () => { expect.assertions(6) - const sourceDirectory = resolve(fixturesDir, 'with_import_maps', 'functions') - const outDir = await tmp.dir() + const { basePath, cleanup, distPath } = await useFixture('with_import_maps') + const sourceDirectory = join(basePath, 'functions') const cacheDir = await tmp.dir() const declarations = [ { @@ -142,17 +152,19 @@ test('Uses the cache directory as the `DENO_DIR` value if the `edge_functions_ca }, ] const options: BundleOptions = { - basePath: fixturesDir, + basePath, cacheDirectory: cacheDir.path, configPath: join(sourceDirectory, 'config.json'), } // Run #1, feature flag off: The directory should not be populated. - const result1 = await bundle([sourceDirectory], outDir.path, declarations, options) - const outFiles1 = await fs.readdir(outDir.path) + const result1 = await bundle([sourceDirectory], distPath, declarations, options) + const outFiles1 = await fs.readdir(distPath) expect(result1.functions.length).toBe(1) - expect(outFiles1.length).toBe(2) + + // ESZIP, manifest and import map. + expect(outFiles1.length).toBe(3) try { await fs.readdir(join(cacheDir.path, 'deno_dir')) @@ -161,43 +173,47 @@ test('Uses the cache directory as the `DENO_DIR` value if the `edge_functions_ca } // Run #2, feature flag on: The directory should be populated. - const result2 = await bundle([sourceDirectory], outDir.path, declarations, { + const result2 = await bundle([sourceDirectory], distPath, declarations, { ...options, featureFlags: { edge_functions_cache_deno_dir: true, }, }) - const outFiles2 = await fs.readdir(outDir.path) + const outFiles2 = await fs.readdir(distPath) expect(result2.functions.length).toBe(1) - expect(outFiles2.length).toBe(2) + + // ESZIP, manifest and import map. + expect(outFiles2.length).toBe(3) const denoDir2 = await fs.readdir(join(cacheDir.path, 'deno_dir')) expect(denoDir2.includes('gen')).toBe(true) - await fs.rmdir(outDir.path, { recursive: true }) + await cleanup() }) test('Supports import maps with relative paths', async () => { - const sourceDirectory = resolve(fixturesDir, 'with_import_maps', 'functions') - const tmpDir = await tmp.dir() + const { basePath, cleanup, distPath } = await useFixture('with_import_maps') + const sourceDirectory = join(basePath, 'functions') const declarations = [ { function: 'func1', path: '/func1', }, ] - const result = await bundle([sourceDirectory], tmpDir.path, declarations, { - basePath: fixturesDir, + const result = await bundle([sourceDirectory], distPath, declarations, { + basePath, configPath: join(sourceDirectory, 'config.json'), }) - const generatedFiles = await fs.readdir(tmpDir.path) + const generatedFiles = await fs.readdir(distPath) expect(result.functions.length).toBe(1) - expect(generatedFiles.length).toBe(2) - const manifestFile = await fs.readFile(resolve(tmpDir.path, 'manifest.json'), 'utf8') + // ESZIP, manifest and import map. + expect(generatedFiles.length).toBe(3) + + const manifestFile = await fs.readFile(resolve(distPath, 'manifest.json'), 'utf8') const manifest = JSON.parse(manifestFile) const { bundles } = manifest @@ -205,12 +221,12 @@ test('Supports import maps with relative paths', async () => { expect(bundles[0].format).toBe('eszip2') expect(generatedFiles.includes(bundles[0].asset)).toBe(true) - await fs.rmdir(tmpDir.path, { recursive: true }) + await cleanup() }) test('Ignores any user-defined `deno.json` files', async () => { - const fixtureDir = join(fixturesDir, 'with_import_maps') - const tmpDir = await tmp.dir() + const { basePath, cleanup, distPath } = await useFixture('with_import_maps') + const sourceDirectory = join(basePath, 'functions') const declarations = [ { function: 'func1', @@ -253,18 +269,19 @@ test('Ignores any user-defined `deno.json` files', async () => { await fs.writeFile(denoConfigPath, JSON.stringify(denoConfig)) expect(() => - bundle([join(fixtureDir, 'functions')], tmpDir.path, declarations, { + bundle([sourceDirectory], distPath, declarations, { basePath: fixturesDir, - configPath: join(fixtureDir, 'functions', 'config.json'), + configPath: join(sourceDirectory, 'config.json'), }), ).not.toThrow() - await deleteAsync([tmpDir.path, denoConfigPath, importMapFile.path], { force: true }) + await cleanup() + await deleteAsync([denoConfigPath, importMapFile.path], { force: true }) }) test('Processes a function that imports a custom layer', async () => { - const sourceDirectory = resolve(fixturesDir, 'with_layers', 'functions') - const tmpDir = await tmp.dir() + const { basePath, cleanup, distPath } = await useFixture('with_layers') + const sourceDirectory = join(basePath, 'functions') const declarations = [ { function: 'func1', @@ -272,16 +289,18 @@ test('Processes a function that imports a custom layer', async () => { }, ] const layer = { name: 'layer:test', flag: 'edge-functions-layer-test' } - const result = await bundle([sourceDirectory], tmpDir.path, declarations, { - basePath: fixturesDir, + const result = await bundle([sourceDirectory], distPath, declarations, { + basePath, configPath: join(sourceDirectory, 'config.json'), }) - const generatedFiles = await fs.readdir(tmpDir.path) + const generatedFiles = await fs.readdir(distPath) expect(result.functions.length).toBe(1) - expect(generatedFiles.length).toBe(2) - const manifestFile = await fs.readFile(resolve(tmpDir.path, 'manifest.json'), 'utf8') + // ESZIP, manifest and import map. + expect(generatedFiles.length).toBe(3) + + const manifestFile = await fs.readFile(resolve(distPath, 'manifest.json'), 'utf8') const manifest = JSON.parse(manifestFile) const { bundles, layers } = manifest @@ -291,29 +310,30 @@ test('Processes a function that imports a custom layer', async () => { expect(layers).toEqual([layer]) - await fs.rmdir(tmpDir.path, { recursive: true }) + await cleanup() }) test('Loads declarations and import maps from the deploy configuration', async () => { - const fixtureDir = resolve(fixturesDir, 'with_deploy_config') - const tmpDir = await tmp.dir() + const { basePath, cleanup, distPath } = await useFixture('with_deploy_config') const declarations = [ { function: 'func1', path: '/func1', }, ] - const directories = [join(fixtureDir, 'netlify', 'edge-functions'), join(fixtureDir, '.netlify', 'edge-functions')] - const result = await bundle(directories, tmpDir.path, declarations, { - basePath: fixtureDir, - configPath: join(fixtureDir, '.netlify', 'edge-functions', 'config.json'), + const directories = [join(basePath, 'netlify', 'edge-functions'), join(basePath, '.netlify', 'edge-functions')] + const result = await bundle(directories, distPath, declarations, { + basePath, + configPath: join(basePath, '.netlify', 'edge-functions', 'config.json'), }) - const generatedFiles = await fs.readdir(tmpDir.path) + const generatedFiles = await fs.readdir(distPath) expect(result.functions.length).toBe(2) - expect(generatedFiles.length).toBe(2) - const manifestFile = await fs.readFile(resolve(tmpDir.path, 'manifest.json'), 'utf8') + // ESZIP, manifest and import map. + expect(generatedFiles.length).toBe(3) + + const manifestFile = await fs.readFile(resolve(distPath, 'manifest.json'), 'utf8') const manifest = JSON.parse(manifestFile) const { bundles } = manifest @@ -321,5 +341,5 @@ test('Loads declarations and import maps from the deploy configuration', async ( expect(bundles[0].format).toBe('eszip2') expect(generatedFiles.includes(bundles[0].asset)).toBe(true) - await fs.rmdir(tmpDir.path, { recursive: true }) + await cleanup() }) diff --git a/node/bundler.ts b/node/bundler.ts index 0b5c9e50..311c871e 100644 --- a/node/bundler.ts +++ b/node/bundler.ts @@ -126,6 +126,7 @@ const bundle = async ( declarations, distDirectory, functions, + importMapURL: functionBundle.importMapURL, layers: deployConfig.layers, }) diff --git a/node/config.test.ts b/node/config.test.ts index 627957c0..aecf3a65 100644 --- a/node/config.test.ts +++ b/node/config.test.ts @@ -158,7 +158,9 @@ test('Ignores function paths from the in-source `config` function if the feature const generatedFiles = await fs.readdir(tmpDir.path) expect(result.functions.length).toBe(6) - expect(generatedFiles.length).toBe(2) + + // ESZIP, manifest and import map. + expect(generatedFiles.length).toBe(3) const manifestFile = await fs.readFile(resolve(tmpDir.path, 'manifest.json'), 'utf8') const manifest = JSON.parse(manifestFile) @@ -196,7 +198,9 @@ test('Loads function paths from the in-source `config` function', async () => { const generatedFiles = await fs.readdir(tmpDir.path) expect(result.functions.length).toBe(6) - expect(generatedFiles.length).toBe(2) + + // ESZIP, manifest and import map. + expect(generatedFiles.length).toBe(3) const manifestFile = await fs.readFile(resolve(tmpDir.path, 'manifest.json'), 'utf8') const manifest = JSON.parse(manifestFile) diff --git a/node/formats/eszip.ts b/node/formats/eszip.ts index cb395f1f..a98a5f67 100644 --- a/node/formats/eszip.ts +++ b/node/formats/eszip.ts @@ -1,5 +1,6 @@ -import { join } from 'path' +import { join, relative } from 'path' +import { virtualRoot } from '../../shared/consts.js' import type { WriteStage2Options } from '../../shared/stage2.js' import { DenoBridge } from '../bridge.js' import { Bundle, BundleFormat } from '../bundle.js' @@ -36,23 +37,20 @@ const bundleESZIP = async ({ const extension = '.eszip' const destPath = join(distDirectory, `${buildID}${extension}`) const { bundler, importMap: bundlerImportMap } = getESZIPPaths() + const importMapURL = await createUserImportMap(importMap, basePath, distDirectory) const payload: WriteStage2Options = { basePath, destPath, externals, functions, - importMapURL: importMap.toDataURL(), + importMapURL, } - const flags = ['--allow-all', '--no-config'] + const flags = ['--allow-all', '--no-config', `--import-map=${bundlerImportMap}`] if (!debug) { flags.push('--quiet') } - // To actually vendor the eszip module, we need to supply the import map that - // redirects `https://deno.land/` URLs to the local files. - flags.push(`--import-map=${bundlerImportMap}`) - try { await deno.run(['run', ...flags, bundler, JSON.stringify(payload)], { pipeOutput: true }) } catch (error: unknown) { @@ -61,7 +59,20 @@ const bundleESZIP = async ({ const hash = await getFileHash(destPath) - return { extension, format: BundleFormat.ESZIP2, hash } + return { extension, format: BundleFormat.ESZIP2, hash, importMapURL } +} + +// Takes an import map, writes it to a file on disk, and gets its URL relative +// to the ESZIP root (i.e. using the virtual root prefix). +const createUserImportMap = async (importMap: ImportMap, basePath: string, distDirectory: string) => { + const destPath = join(distDirectory, 'import_map.json') + + await importMap.writeToFile(destPath) + + const relativePath = relative(basePath, destPath) + const importMapURL = new URL(relativePath, virtualRoot) + + return importMapURL.toString() } const getESZIPPaths = () => { diff --git a/node/import_map.test.ts b/node/import_map.test.ts index 0e73b3f6..1ee38ff8 100644 --- a/node/import_map.test.ts +++ b/node/import_map.test.ts @@ -24,7 +24,7 @@ test('Handles import maps with full URLs without specifying a base URL', () => { expect(imports['alias:pets']).toBe('https://petsofnetlify.com/') }) -test('Handles import maps with relative paths', () => { +test('Resolves relative paths to absolute paths if a root path is not provided', () => { const inputFile1 = { baseURL: new URL('file:///Users/jane-doe/my-site/import-map.json'), imports: { @@ -38,3 +38,18 @@ test('Handles import maps with relative paths', () => { expect(imports['netlify:edge']).toBe('https://edge.netlify.com/v1/index.ts') expect(imports['alias:pets']).toBe('file:///Users/jane-doe/my-site/heart/pets/') }) + +test('Transforms relative paths so that they use the root path as a base', () => { + const inputFile1 = { + baseURL: new URL('file:///Users/jane-doe/my-site/import-map.json'), + imports: { + 'alias:pets': './heart/pets/', + }, + } + + const map = new ImportMap([inputFile1]) + const { imports } = JSON.parse(map.getContents('/Users/jane-doe')) + + expect(imports['netlify:edge']).toBe('https://edge.netlify.com/v1/index.ts') + expect(imports['alias:pets']).toBe('./my-site/heart/pets') +}) diff --git a/node/import_map.ts b/node/import_map.ts index f4f4db93..fc730227 100644 --- a/node/import_map.ts +++ b/node/import_map.ts @@ -1,12 +1,12 @@ import { Buffer } from 'buffer' import { promises as fs } from 'fs' -import { dirname } from 'path' -import { pathToFileURL } from 'url' +import { dirname, isAbsolute, relative } from 'path' +import { fileURLToPath, pathToFileURL } from 'url' import { parse } from '@import-maps/resolve' const INTERNAL_IMPORTS = { - 'netlify:edge': new URL('https://edge.netlify.com/v1/index.ts'), + 'netlify:edge': 'https://edge.netlify.com/v1/index.ts', } interface ImportMapFile { @@ -28,22 +28,48 @@ class ImportMap { }) } - static resolve(importMapFile: ImportMapFile) { + // Transforms an import map by making any relative paths use a different path + // as a base. + static resolve(importMapFile: ImportMapFile, rootPath?: string) { const { baseURL, ...importMap } = importMapFile const parsedImportMap = parse(importMap, baseURL) + const { imports = {} } = parsedImportMap + const newImports: Record = {} - return parsedImportMap + Object.keys(imports).forEach((specifier) => { + const url = imports[specifier] + + // If there's no URL, don't even add the specifier to the final imports. + if (url === null) { + return + } + + // If this is a file URL, we might want to transform it to use another + // root path, as long as that root path is defined. + if (url.protocol === 'file:' && rootPath !== undefined) { + const path = relative(rootPath, fileURLToPath(url)) + const value = isAbsolute(path) ? path : `./${path}` + + newImports[specifier] = value + + return + } + + newImports[specifier] = url.toString() + }) + + return { ...parsedImportMap, imports: newImports } } add(file: ImportMapFile) { this.files.push(file) } - getContents() { - let imports: Record = {} + getContents(rootPath?: string) { + let imports: Record = {} this.files.forEach((file) => { - const importMap = ImportMap.resolve(file) + const importMap = ImportMap.resolve(file, rootPath) imports = { ...imports, ...importMap.imports } }) @@ -69,9 +95,11 @@ class ImportMap { } async writeToFile(path: string) { - await fs.mkdir(dirname(path), { recursive: true }) + const distDirectory = dirname(path) + + await fs.mkdir(distDirectory, { recursive: true }) - const contents = this.getContents() + const contents = this.getContents(distDirectory) await fs.writeFile(path, contents) } diff --git a/node/manifest.ts b/node/manifest.ts index 3151916c..80e8f8c5 100644 --- a/node/manifest.ts +++ b/node/manifest.ts @@ -15,6 +15,7 @@ interface GenerateManifestOptions { bundles?: Bundle[] declarations?: Declaration[] functions: EdgeFunction[] + importMapURL?: string layers?: Layer[] } @@ -22,9 +23,10 @@ interface GenerateManifestOptions { interface Manifest { bundler_version: string bundles: { asset: string; format: string }[] + import_map?: string + layers: { name: string; flag: string }[] routes: { function: string; name?: string; pattern: string }[] post_cache_routes: { function: string; name?: string; pattern: string }[] - layers: { name: string; flag: string }[] } /* eslint-enable camelcase */ @@ -34,7 +36,13 @@ interface Route { pattern: string } -const generateManifest = ({ bundles = [], declarations = [], functions, layers = [] }: GenerateManifestOptions) => { +const generateManifest = ({ + bundles = [], + declarations = [], + functions, + importMapURL, + layers = [], +}: GenerateManifestOptions) => { const preCacheRoutes: Route[] = [] const postCacheRoutes: Route[] = [] @@ -69,6 +77,7 @@ const generateManifest = ({ bundles = [], declarations = [], functions, layers = post_cache_routes: postCacheRoutes.filter(nonNullable), bundler_version: getPackageVersion(), layers, + import_map: importMapURL, } return manifest @@ -96,6 +105,7 @@ interface WriteManifestOptions { declarations: Declaration[] distDirectory: string functions: EdgeFunction[] + importMapURL?: string layers?: Layer[] } @@ -104,9 +114,10 @@ const writeManifest = async ({ declarations = [], distDirectory, functions, + importMapURL, layers, }: WriteManifestOptions) => { - const manifest = generateManifest({ bundles, declarations, functions, layers }) + const manifest = generateManifest({ bundles, declarations, functions, importMapURL, layers }) const manifestPath = join(distDirectory, 'manifest.json') await fs.writeFile(manifestPath, JSON.stringify(manifest)) diff --git a/package-lock.json b/package-lock.json index 9058bdc6..551cbb39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,7 @@ "@types/uuid": "^8.3.4", "@vitest/coverage-c8": "^0.25.0", "archiver": "^5.3.1", + "cpy": "^9.0.1", "cross-env": "^7.0.3", "husky": "^8.0.0", "nock": "^13.2.4", @@ -3109,6 +3110,77 @@ "typescript": ">=3" } }, + "node_modules/cp-file": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-9.1.0.tgz", + "integrity": "sha512-3scnzFj/94eb7y4wyXRWwvzLFaQp87yyfTnChIjlfYrVqp5lVO3E2hIJMeQIltUT0K2ZAB3An1qXcBmwGyvuwA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "nested-error-stacks": "^2.0.0", + "p-event": "^4.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cpy": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cpy/-/cpy-9.0.1.tgz", + "integrity": "sha512-D9U0DR5FjTCN3oMTcFGktanHnAG5l020yvOCR1zKILmAyPP7I/9pl6NFgRbDcmSENtbK1sQLBz1p9HIOlroiNg==", + "dev": true, + "dependencies": { + "arrify": "^3.0.0", + "cp-file": "^9.1.0", + "globby": "^13.1.1", + "junk": "^4.0.0", + "micromatch": "^4.0.4", + "nested-error-stacks": "^2.1.0", + "p-filter": "^3.0.0", + "p-map": "^5.3.0" + }, + "engines": { + "node": "^12.20.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cpy/node_modules/arrify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-3.0.0.tgz", + "integrity": "sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cpy/node_modules/globby": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.2.tgz", + "integrity": "sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==", + "dev": true, + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", @@ -5982,6 +6054,18 @@ "node": ">=4.0" } }, + "node_modules/junk": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/junk/-/junk-4.0.0.tgz", + "integrity": "sha512-ojtSU++zLJ3jQG9bAYjg94w+/DOJtRyD7nPaerMFrBhmdVmiV5/exYH5t4uHga4G/95nT6hr1OJoKIFbYbrW5w==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/kebab-case": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/kebab-case/-/kebab-case-1.0.2.tgz", @@ -6535,6 +6619,12 @@ "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", "dev": true }, + "node_modules/nested-error-stacks": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.1.tgz", + "integrity": "sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==", + "dev": true + }, "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -6955,6 +7045,57 @@ "node": ">= 0.8.0" } }, + "node_modules/p-event": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", + "integrity": "sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==", + "dev": true, + "dependencies": { + "p-timeout": "^3.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-event/node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dev": true, + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-3.0.0.tgz", + "integrity": "sha512-QtoWLjXAW++uTX67HZQz1dbTpqBfiidsB6VtQUC9iR85S120+s0T5sO6s+B5MLzFcZkrEd/DGMmCjR+f2Qpxwg==", + "dev": true, + "dependencies": { + "p-map": "^5.1.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/p-limit": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", @@ -11159,6 +11300,55 @@ "dev": true, "requires": {} }, + "cp-file": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-9.1.0.tgz", + "integrity": "sha512-3scnzFj/94eb7y4wyXRWwvzLFaQp87yyfTnChIjlfYrVqp5lVO3E2hIJMeQIltUT0K2ZAB3An1qXcBmwGyvuwA==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "nested-error-stacks": "^2.0.0", + "p-event": "^4.1.0" + } + }, + "cpy": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cpy/-/cpy-9.0.1.tgz", + "integrity": "sha512-D9U0DR5FjTCN3oMTcFGktanHnAG5l020yvOCR1zKILmAyPP7I/9pl6NFgRbDcmSENtbK1sQLBz1p9HIOlroiNg==", + "dev": true, + "requires": { + "arrify": "^3.0.0", + "cp-file": "^9.1.0", + "globby": "^13.1.1", + "junk": "^4.0.0", + "micromatch": "^4.0.4", + "nested-error-stacks": "^2.1.0", + "p-filter": "^3.0.0", + "p-map": "^5.3.0" + }, + "dependencies": { + "arrify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-3.0.0.tgz", + "integrity": "sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw==", + "dev": true + }, + "globby": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.2.tgz", + "integrity": "sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==", + "dev": true, + "requires": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + } + } + } + }, "crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", @@ -13105,6 +13295,12 @@ "object.assign": "^4.1.3" } }, + "junk": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/junk/-/junk-4.0.0.tgz", + "integrity": "sha512-ojtSU++zLJ3jQG9bAYjg94w+/DOJtRyD7nPaerMFrBhmdVmiV5/exYH5t4uHga4G/95nT6hr1OJoKIFbYbrW5w==", + "dev": true + }, "kebab-case": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/kebab-case/-/kebab-case-1.0.2.tgz", @@ -13544,6 +13740,12 @@ "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", "dev": true }, + "nested-error-stacks": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.1.tgz", + "integrity": "sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==", + "dev": true + }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -13841,6 +14043,41 @@ "word-wrap": "^1.2.3" } }, + "p-event": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", + "integrity": "sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==", + "dev": true, + "requires": { + "p-timeout": "^3.1.0" + }, + "dependencies": { + "p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dev": true, + "requires": { + "p-finally": "^1.0.0" + } + } + } + }, + "p-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-3.0.0.tgz", + "integrity": "sha512-QtoWLjXAW++uTX67HZQz1dbTpqBfiidsB6VtQUC9iR85S120+s0T5sO6s+B5MLzFcZkrEd/DGMmCjR+f2Qpxwg==", + "dev": true, + "requires": { + "p-map": "^5.1.0" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true + }, "p-limit": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", diff --git a/package.json b/package.json index 45961b7d..564af9b2 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "@types/uuid": "^8.3.4", "@vitest/coverage-c8": "^0.25.0", "archiver": "^5.3.1", + "cpy": "^9.0.1", "cross-env": "^7.0.3", "husky": "^8.0.0", "nock": "^13.2.4", diff --git a/shared/consts.ts b/shared/consts.ts new file mode 100644 index 00000000..b08c9d4c --- /dev/null +++ b/shared/consts.ts @@ -0,0 +1 @@ +export const virtualRoot = 'file:///root/' diff --git a/test/fixtures/with_import_maps/functions/func1.ts b/test/fixtures/with_import_maps/functions/func1.ts index 5c741a24..d9174f1b 100644 --- a/test/fixtures/with_import_maps/functions/func1.ts +++ b/test/fixtures/with_import_maps/functions/func1.ts @@ -1,6 +1,6 @@ import { greet } from 'alias:helper' -import { echo } from '../../helper.ts' +import { echo } from '../helper.ts' export default async () => { const greeting = greet(echo('Jane Doe')) diff --git a/test/fixtures/with_import_maps/functions/import_map.json b/test/fixtures/with_import_maps/functions/import_map.json index 7c467239..f7bdf228 100644 --- a/test/fixtures/with_import_maps/functions/import_map.json +++ b/test/fixtures/with_import_maps/functions/import_map.json @@ -1,5 +1,5 @@ { "imports": { - "alias:helper": "../../helper.ts" + "alias:helper": "../helper.ts" } } diff --git a/test/fixtures/with_import_maps/helper.ts b/test/fixtures/with_import_maps/helper.ts new file mode 100644 index 00000000..bbaf8c35 --- /dev/null +++ b/test/fixtures/with_import_maps/helper.ts @@ -0,0 +1,2 @@ +export const greet = (name: string) => `Hello, ${name}!` +export const echo = (name: string) => name diff --git a/test/util.ts b/test/util.ts index 39995ce3..37f7a49f 100644 --- a/test/util.ts +++ b/test/util.ts @@ -1,6 +1,10 @@ -import { resolve } from 'path' +import { promises as fs } from 'fs' +import { join, resolve } from 'path' import { fileURLToPath } from 'url' +import cpy from 'cpy' +import tmp from 'tmp-promise' + import { getLogger } from '../node/logger.js' // eslint-disable-next-line @typescript-eslint/no-empty-function @@ -10,4 +14,20 @@ const url = new URL(import.meta.url) const dirname = fileURLToPath(url) const fixturesDir = resolve(dirname, '..', 'fixtures') -export { fixturesDir, testLogger } +const useFixture = async (fixtureName: string) => { + const tmpDir = await tmp.dir() + const cleanup = () => fs.rmdir(tmpDir.path, { recursive: true }) + const fixtureDir = resolve(fixturesDir, fixtureName) + + await cpy(`${fixtureDir}/**`, tmpDir.path) + + const distPath = join(tmpDir.path, '.netlify', 'edge-functions-dist') + + return { + basePath: tmpDir.path, + cleanup, + distPath, + } +} + +export { fixturesDir, testLogger, useFixture } From cf308eafabff58e8b062f55bfd06d173613f2721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 6 Dec 2022 00:05:04 +0000 Subject: [PATCH 2/6] fix: use POSIX paths --- node/import_map.test.ts | 22 +++++++++++++++------- node/import_map.ts | 8 +++++--- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/node/import_map.test.ts b/node/import_map.test.ts index 1ee38ff8..bafe1f7a 100644 --- a/node/import_map.test.ts +++ b/node/import_map.test.ts @@ -1,16 +1,21 @@ +import { join } from 'path' +import { cwd } from 'process' +import { pathToFileURL } from 'url' + import { test, expect } from 'vitest' import { ImportMap } from './import_map.js' test('Handles import maps with full URLs without specifying a base URL', () => { + const basePath = join(cwd(), 'my-cool-site', 'import-map.json') const inputFile1 = { - baseURL: new URL('file:///some/path/import-map.json'), + baseURL: pathToFileURL(basePath), imports: { 'alias:jamstack': 'https://jamstack.org', }, } const inputFile2 = { - baseURL: new URL('file:///some/path/import-map.json'), + baseURL: pathToFileURL(basePath), imports: { 'alias:pets': 'https://petsofnetlify.com/', }, @@ -25,8 +30,9 @@ test('Handles import maps with full URLs without specifying a base URL', () => { }) test('Resolves relative paths to absolute paths if a root path is not provided', () => { + const basePath = join(cwd(), 'my-cool-site', 'import-map.json') const inputFile1 = { - baseURL: new URL('file:///Users/jane-doe/my-site/import-map.json'), + baseURL: pathToFileURL(basePath), imports: { 'alias:pets': './heart/pets/', }, @@ -34,22 +40,24 @@ test('Resolves relative paths to absolute paths if a root path is not provided', const map = new ImportMap([inputFile1]) const { imports } = JSON.parse(map.getContents()) + const expectedPath = join(cwd(), 'my-cool-site', 'heart', 'pets') expect(imports['netlify:edge']).toBe('https://edge.netlify.com/v1/index.ts') - expect(imports['alias:pets']).toBe('file:///Users/jane-doe/my-site/heart/pets/') + expect(imports['alias:pets']).toBe(`${pathToFileURL(expectedPath).toString()}/`) }) test('Transforms relative paths so that they use the root path as a base', () => { + const basePath = join(cwd(), 'my-cool-site', 'import-map.json') const inputFile1 = { - baseURL: new URL('file:///Users/jane-doe/my-site/import-map.json'), + baseURL: pathToFileURL(basePath), imports: { 'alias:pets': './heart/pets/', }, } const map = new ImportMap([inputFile1]) - const { imports } = JSON.parse(map.getContents('/Users/jane-doe')) + const { imports } = JSON.parse(map.getContents(cwd())) expect(imports['netlify:edge']).toBe('https://edge.netlify.com/v1/index.ts') - expect(imports['alias:pets']).toBe('./my-site/heart/pets') + expect(imports['alias:pets']).toBe('./my-cool-site/heart/pets') }) diff --git a/node/import_map.ts b/node/import_map.ts index fc730227..12041514 100644 --- a/node/import_map.ts +++ b/node/import_map.ts @@ -1,6 +1,6 @@ import { Buffer } from 'buffer' import { promises as fs } from 'fs' -import { dirname, isAbsolute, relative } from 'path' +import { dirname, isAbsolute, posix, relative, sep } from 'path' import { fileURLToPath, pathToFileURL } from 'url' import { parse } from '@import-maps/resolve' @@ -47,8 +47,10 @@ class ImportMap { // If this is a file URL, we might want to transform it to use another // root path, as long as that root path is defined. if (url.protocol === 'file:' && rootPath !== undefined) { - const path = relative(rootPath, fileURLToPath(url)) - const value = isAbsolute(path) ? path : `./${path}` + // We want to use POSIX paths for the import map regardless of the OS + // we're building in. + const path = relative(rootPath, fileURLToPath(url)).split(sep).join(posix.sep) + const value = isAbsolute(path) ? path : `.${posix.sep}${path}` newImports[specifier] = value From 34dbe46196b1711eb7c3e6bc3522b2ca00e94f64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 6 Dec 2022 00:50:36 +0000 Subject: [PATCH 3/6] chore: fix integration test --- test/integration/test.js | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/test/integration/test.js b/test/integration/test.js index 9cb62978..3f7e64d1 100644 --- a/test/integration/test.js +++ b/test/integration/test.js @@ -6,6 +6,7 @@ import process from 'process' import { fileURLToPath, pathToFileURL } from 'url' import { promisify } from 'util' +import cpy from 'cpy' import { deleteAsync } from 'del' import tar from 'tar' import tmp from 'tmp-promise' @@ -50,17 +51,31 @@ const bundleFunction = async (bundlerDir) => { const bundlerPath = require.resolve(bundlerDir) const bundlerURL = pathToFileURL(bundlerPath) + // eslint-disable-next-line import/no-dynamic-require const { bundle } = await import(bundlerURL) - const { path: destPath } = await tmp.dir() + const { path: basePath } = await tmp.dir() - pathsToCleanup.add(destPath) + console.log(`Copying test fixture to '${basePath}'...`) - console.log(`Bundling functions at '${functionsDir}'...`) + await cpy(`${functionsDir}/**`, join(basePath, 'functions')) - return await bundle([functionsDir], destPath, [{ function: 'func1', path: '/func1' }]) + pathsToCleanup.add(basePath) + + const destPath = join(basePath, '.netlify', 'edge-functions-dist') + + console.log(`Bundling functions at '${basePath}'...`) + + const bundleOutput = await bundle([join(basePath, 'functions')], destPath, [{ function: 'func1', path: '/func1' }], { + basePath, + }) + + return { + basePath, + bundleOutput, + } } -const runAssertions = (bundleOutput) => { +const runAssertions = ({ basePath, bundleOutput }) => { console.log('Running assertions on bundle output:') console.log(JSON.stringify(bundleOutput, null, 2)) @@ -68,7 +83,7 @@ const runAssertions = (bundleOutput) => { assert.equal(functions.length, 1) assert.equal(functions[0].name, 'func1') - assert.equal(functions[0].path, join(functionsDir, 'func1.ts')) + assert.equal(functions[0].path, join(basePath, 'functions', 'func1.ts')) } const cleanup = async () => { From fb378fedcb186e0782da866a7b6b7f1d0f874e0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 6 Dec 2022 01:00:04 +0000 Subject: [PATCH 4/6] chore: fix test --- node/config.test.ts | 34 +++++++++---------- .../.netlify/edge-functions/import_map.json | 2 +- test/fixtures/with_config/helper.ts | 2 ++ 3 files changed, 20 insertions(+), 18 deletions(-) create mode 100644 test/fixtures/with_config/helper.ts diff --git a/node/config.test.ts b/node/config.test.ts index aecf3a65..7e4936d0 100644 --- a/node/config.test.ts +++ b/node/config.test.ts @@ -6,7 +6,7 @@ import { deleteAsync } from 'del' import tmp from 'tmp-promise' import { test, expect, vi } from 'vitest' -import { fixturesDir } from '../test/util.js' +import { fixturesDir, useFixture } from '../test/util.js' import { DenoBridge } from './bridge.js' import { bundle } from './bundler.js' @@ -147,22 +147,22 @@ test('`getFunctionConfig` extracts configuration properties from function file', }) test('Ignores function paths from the in-source `config` function if the feature flag is off', async () => { - const userDirectory = resolve(fixturesDir, 'with_config', 'netlify', 'edge-functions') - const internalDirectory = resolve(fixturesDir, 'with_config', '.netlify', 'edge-functions') - const tmpDir = await tmp.dir() + const { basePath, cleanup, distPath } = await useFixture('with_config') + const userDirectory = resolve(basePath, 'netlify', 'edge-functions') + const internalDirectory = resolve(basePath, '.netlify', 'edge-functions') const declarations: Declaration[] = [] - const result = await bundle([internalDirectory, userDirectory], tmpDir.path, declarations, { - basePath: fixturesDir, + const result = await bundle([internalDirectory, userDirectory], distPath, declarations, { + basePath, configPath: join(internalDirectory, 'config.json'), }) - const generatedFiles = await fs.readdir(tmpDir.path) + const generatedFiles = await fs.readdir(distPath) expect(result.functions.length).toBe(6) // ESZIP, manifest and import map. expect(generatedFiles.length).toBe(3) - const manifestFile = await fs.readFile(resolve(tmpDir.path, 'manifest.json'), 'utf8') + const manifestFile = await fs.readFile(resolve(distPath, 'manifest.json'), 'utf8') const manifest = JSON.parse(manifestFile) const { bundles, routes } = manifest @@ -171,13 +171,13 @@ test('Ignores function paths from the in-source `config` function if the feature expect(generatedFiles.includes(bundles[0].asset)).toBe(true) expect(routes.length).toBe(0) - await fs.rmdir(tmpDir.path, { recursive: true }) + await cleanup() }) test('Loads function paths from the in-source `config` function', async () => { - const userDirectory = resolve(fixturesDir, 'with_config', 'netlify', 'edge-functions') - const internalDirectory = resolve(fixturesDir, 'with_config', '.netlify', 'edge-functions') - const tmpDir = await tmp.dir() + const { basePath, cleanup, distPath } = await useFixture('with_config') + const userDirectory = resolve(basePath, 'netlify', 'edge-functions') + const internalDirectory = resolve(basePath, '.netlify', 'edge-functions') const declarations: Declaration[] = [ { function: 'framework-func2', @@ -188,21 +188,21 @@ test('Loads function paths from the in-source `config` function', async () => { path: '/user-func2', }, ] - const result = await bundle([internalDirectory, userDirectory], tmpDir.path, declarations, { - basePath: fixturesDir, + const result = await bundle([internalDirectory, userDirectory], distPath, declarations, { + basePath, configPath: join(internalDirectory, 'config.json'), featureFlags: { edge_functions_config_export: true, }, }) - const generatedFiles = await fs.readdir(tmpDir.path) + const generatedFiles = await fs.readdir(distPath) expect(result.functions.length).toBe(6) // ESZIP, manifest and import map. expect(generatedFiles.length).toBe(3) - const manifestFile = await fs.readFile(resolve(tmpDir.path, 'manifest.json'), 'utf8') + const manifestFile = await fs.readFile(resolve(distPath, 'manifest.json'), 'utf8') const manifest = JSON.parse(manifestFile) const { bundles, routes, post_cache_routes: postCacheRoutes } = manifest @@ -220,7 +220,7 @@ test('Loads function paths from the in-source `config` function', async () => { expect(postCacheRoutes.length).toBe(1) expect(postCacheRoutes[0]).toEqual({ function: 'user-func4', pattern: '^/user-func4/?$' }) - await fs.rmdir(tmpDir.path, { recursive: true }) + await cleanup() }) test('Passes validation if default export exists and is a function', async () => { diff --git a/test/fixtures/with_config/.netlify/edge-functions/import_map.json b/test/fixtures/with_config/.netlify/edge-functions/import_map.json index aaef1126..7c467239 100644 --- a/test/fixtures/with_config/.netlify/edge-functions/import_map.json +++ b/test/fixtures/with_config/.netlify/edge-functions/import_map.json @@ -1,5 +1,5 @@ { "imports": { - "alias:helper": "../../../helper.ts" + "alias:helper": "../../helper.ts" } } diff --git a/test/fixtures/with_config/helper.ts b/test/fixtures/with_config/helper.ts new file mode 100644 index 00000000..bbaf8c35 --- /dev/null +++ b/test/fixtures/with_config/helper.ts @@ -0,0 +1,2 @@ +export const greet = (name: string) => `Hello, ${name}!` +export const echo = (name: string) => name From 079fa8dedbf5e9dbb36368ba524ae6dfdb126ce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 6 Dec 2022 01:01:13 +0000 Subject: [PATCH 5/6] chore: remove unused file --- test/fixtures/helper.ts | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 test/fixtures/helper.ts diff --git a/test/fixtures/helper.ts b/test/fixtures/helper.ts deleted file mode 100644 index bbaf8c35..00000000 --- a/test/fixtures/helper.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const greet = (name: string) => `Hello, ${name}!` -export const echo = (name: string) => name From 8262c799f999343470e09dbdbc38163b2e6faa01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 6 Dec 2022 01:07:07 +0000 Subject: [PATCH 6/6] chore: fix path in test --- node/bundler.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node/bundler.test.ts b/node/bundler.test.ts index 193e93c5..9cfa0f2d 100644 --- a/node/bundler.test.ts +++ b/node/bundler.test.ts @@ -6,7 +6,7 @@ import { deleteAsync } from 'del' import tmp from 'tmp-promise' import { test, expect } from 'vitest' -import { fixturesDir, useFixture } from '../test/util.js' +import { useFixture } from '../test/util.js' import { BundleError } from './bundle_error.js' import { bundle, BundleOptions } from './bundler.js' @@ -270,7 +270,7 @@ test('Ignores any user-defined `deno.json` files', async () => { expect(() => bundle([sourceDirectory], distPath, declarations, { - basePath: fixturesDir, + basePath, configPath: join(sourceDirectory, 'config.json'), }), ).not.toThrow()