Skip to content

Commit

Permalink
feat: add support for negative patterns (#261)
Browse files Browse the repository at this point in the history
* feat: update generateManifest to support excluded paths / patterns

* fix: ensure excludePath in deploy config is respected

* fix: test

* test: toml-defined declarations are respected

* fix: prettier

* Update node/manifest.test.ts

Co-authored-by: Eduardo Bouças <mail@eduardoboucas.com>

* refactor: exclude -> excluded

* refactor: address eduardo's conditionals feedback

* refactor: revert type guard

Co-authored-by: Eduardo Bouças <mail@eduardoboucas.com>
  • Loading branch information
Skn0tt and eduardoboucas committed Dec 20, 2022
1 parent 5b2b430 commit 646631a
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 13 deletions.
5 changes: 4 additions & 1 deletion node/bundler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,12 +335,15 @@ test('Loads declarations and import maps from the deploy configuration', async (

const manifestFile = await fs.readFile(resolve(distPath, 'manifest.json'), 'utf8')
const manifest = JSON.parse(manifestFile)
const { bundles } = manifest
const { bundles, routes } = manifest

expect(bundles.length).toBe(1)
expect(bundles[0].format).toBe('eszip2')
expect(generatedFiles.includes(bundles[0].asset)).toBe(true)

// respects excludedPath from deploy config
expect(routes[1].excluded_pattern).toEqual('^/func2/skip/?$')

await cleanup()
})

Expand Down
12 changes: 12 additions & 0 deletions node/declaration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,15 @@ test('In-source config works if path property is an empty array with cache value

expect(getDeclarationsFromConfig(tomlConfig, funcConfig, deployConfig)).toEqual(expectedDeclarations)
})

test('netlify.toml-defined excludedPath are respected', () => {
const tomlConfig = [{ function: 'geolocation', path: '/geo/*', excludedPath: '/geo/exclude' }]

const funcConfig = {}

const expectedDeclarations = [{ function: 'geolocation', path: '/geo/*', excludedPath: '/geo/exclude' }]

const declarations = getDeclarationsFromConfig(tomlConfig, funcConfig, deployConfig)

expect(declarations).toEqual(expectedDeclarations)
})
2 changes: 2 additions & 0 deletions node/declaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ interface BaseDeclaration {

type DeclarationWithPath = BaseDeclaration & {
path: string
excludedPath?: string
}

type DeclarationWithPattern = BaseDeclaration & {
pattern: string
excludedPattern?: string
}

type Declaration = DeclarationWithPath | DeclarationWithPattern
Expand Down
21 changes: 21 additions & 0 deletions node/manifest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { env } from 'process'
import { test, expect } from 'vitest'

import { BundleFormat } from './bundle.js'
import { Declaration } from './declaration.js'
import { generateManifest } from './manifest.js'

test('Generates a manifest with different bundles', () => {
Expand Down Expand Up @@ -51,6 +52,26 @@ test('Generates a manifest with display names', () => {
expect(manifest.bundler_version).toBe(env.npm_package_version as string)
})

test('Generates a manifest with excluded paths and patterns', () => {
const functions = [
{ name: 'func-1', path: '/path/to/func-1.ts' },
{ name: 'func-2', path: '/path/to/func-2.ts' },
]
const declarations: Declaration[] = [
{ function: 'func-1', name: 'Display Name', path: '/f1/*', excludedPath: '/f1/exclude' },
{ function: 'func-2', pattern: '^/f2/.*/?$', excludedPattern: '^/f2/exclude$' },
]
const manifest = generateManifest({ bundles: [], declarations, functions })

const expectedRoutes = [
{ function: 'func-1', name: 'Display Name', pattern: '^/f1/.*/?$', excluded_pattern: '^/f1/exclude/?$' },
{ function: 'func-2', pattern: '^/f2/.*/?$', excluded_pattern: '^/f2/exclude$' },
]

expect(manifest.routes).toEqual(expectedRoutes)
expect(manifest.bundler_version).toBe(env.npm_package_version as string)
})

test('Excludes functions for which there are function files but no matching config declarations', () => {
const bundle1 = {
extension: '.ext2',
Expand Down
47 changes: 36 additions & 11 deletions node/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,19 @@ interface GenerateManifestOptions {
}

/* eslint-disable camelcase */
interface Route {
function: string
name?: string
pattern: string
excluded_pattern?: string
}
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 }[]
routes: Route[]
post_cache_routes: Route[]
}
/* eslint-enable camelcase */

Expand All @@ -36,6 +42,8 @@ interface Route {
pattern: string
}

const serializePattern = (regex: RegExp) => regex.source.replace(/\\\//g, '/')

const generateManifest = ({
bundles = [],
declarations = [],
Expand All @@ -54,11 +62,14 @@ const generateManifest = ({
}

const pattern = getRegularExpression(declaration)
const serializablePattern = pattern.source.replace(/\\\//g, '/')
const route = {
const route: Route = {
function: func.name,
name: declaration.name,
pattern: serializablePattern,
pattern: serializePattern(pattern),
}
const excludedPattern = getExcludedRegularExpression(declaration)
if (excludedPattern) {
route.excluded_pattern = serializePattern(excludedPattern)
}

if (declaration.cache === Cache.Manual) {
Expand All @@ -83,14 +94,10 @@ const generateManifest = ({
return manifest
}

const getRegularExpression = (declaration: Declaration) => {
if ('pattern' in declaration) {
return new RegExp(declaration.pattern)
}

const pathToRegularExpression = (path: string) => {
// We use the global flag so that `globToRegExp` will not wrap the expression
// with `^` and `$`. We'll do that ourselves.
const regularExpression = globToRegExp(declaration.path, { flags: 'g' })
const regularExpression = globToRegExp(path, { flags: 'g' })

// Wrapping the expression source with `^` and `$`. Also, adding an optional
// trailing slash, so that a declaration of `path: "/foo"` matches requests
Expand All @@ -100,6 +107,24 @@ const getRegularExpression = (declaration: Declaration) => {
return new RegExp(normalizedSource)
}

const getRegularExpression = (declaration: Declaration) => {
if ('pattern' in declaration) {
return new RegExp(declaration.pattern)
}

return pathToRegularExpression(declaration.path)
}

const getExcludedRegularExpression = (declaration: Declaration) => {
if ('pattern' in declaration && declaration.excludedPattern) {
return new RegExp(declaration.excludedPattern)
}

if ('path' in declaration && declaration.excludedPath) {
return pathToRegularExpression(declaration.excludedPath)
}
}

interface WriteManifestOptions {
bundles: Bundle[]
declarations: Declaration[]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"functions": [
{
"function": "func2",
"path": "/func2"
"path": "/func2/*",
"excludedPath": "/func2/skip"
}
],
"import_map": "import_map.json",
Expand Down

0 comments on commit 646631a

Please sign in to comment.