Skip to content

Commit

Permalink
feat: support excludedPath in functions (#6717)
Browse files Browse the repository at this point in the history
  • Loading branch information
eduardoboucas authored Jun 20, 2024
1 parent 41294c0 commit 5981a35
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 12 deletions.
40 changes: 29 additions & 11 deletions src/lib/functions/netlify-function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Buffer } from 'buffer'
import { basename, extname } from 'path'
import { version as nodeVersion } from 'process'

import type { ExtendedRoute, Route } from '@netlify/zip-it-and-ship-it'
import CronParser from 'cron-parser'
import semver from 'semver'

Expand Down Expand Up @@ -267,35 +268,52 @@ export default class NetlifyFunction {

let path = rawPath !== '/' && rawPath.endsWith('/') ? rawPath.slice(0, -1) : rawPath
path = path.toLowerCase()
const { routes = [] } = this.buildData ?? {}
// @ts-expect-error TS(7031) FIXME: Binding element 'expression' implicitly has an 'an... Remove this comment to see the full error message
const route = routes.find(({ expression, literal, methods }) => {
if (methods.length !== 0 && !methods.includes(method)) {
const { excludedRoutes = [], routes = [] } = this.buildData ?? {}
const matchingRoute = routes.find((route: ExtendedRoute) => {
if (route.methods && route.methods.length !== 0 && !route.methods.includes(method)) {
return false
}

if (literal !== undefined) {
return path === literal
if ('literal' in route && route.literal !== undefined) {
return path === route.literal
}

if (expression !== undefined) {
const regex = new RegExp(expression)
if ('expression' in route && route.expression !== undefined) {
const regex = new RegExp(route.expression)

return regex.test(path)
}

return false
})

if (!route) {
if (!matchingRoute) {
return
}

if (route.prefer_static && (await hasStaticFile())) {
const isExcluded = excludedRoutes.some((excludedRoute: Route) => {
if ('literal' in excludedRoute && excludedRoute.literal !== undefined) {
return path === excludedRoute.literal
}

if ('expression' in excludedRoute && excludedRoute.expression !== undefined) {
const regex = new RegExp(excludedRoute.expression)

return regex.test(path)
}

return false
})

if (isExcluded) {
return
}

if (matchingRoute.prefer_static && (await hasStaticFile())) {
return
}

return route
return matchingRoute
}

get runtimeAPIVersion() {
Expand Down
13 changes: 12 additions & 1 deletion src/lib/functions/runtimes/js/builders/zisi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const buildFunction = async ({
const entryPath = functionDirectory === directory ? func.mainFile : functionDirectory
const {
entryFilename,
excludedRoutes,
includedFiles,
inputs,
mainFile,
Expand Down Expand Up @@ -96,7 +97,17 @@ const buildFunction = async ({

clearFunctionsCache(targetDirectory)

return { buildPath, includedFiles, outputModuleFormat, mainFile, routes, runtimeAPIVersion, srcFiles, schedule }
return {
buildPath,
excludedRoutes,
includedFiles,
outputModuleFormat,
mainFile,
routes,
runtimeAPIVersion,
srcFiles,
schedule,
}
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/utils/deploy/hash-fns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ const hashFns = async (
...funcs,
[curr.name]: {
display_name: curr.displayName,
excluded_routes: curr.excludedRoutes,
generator: curr.generator,
routes: curr.routes,
build_data: curr.buildData,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default async (req, context) => new Response(`Your product: ${context.params.sku}`)

export const config = {
path: '/custom-path-excluded/:sku',
excludedPath: ['/custom-path-excluded/jacket'],
}
11 changes: 11 additions & 0 deletions tests/integration/commands/dev/v2-api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,17 @@ describe.runIf(gte(version, '18.13.0')).concurrent('v2 api', () => {
expect(response.status).toBe(404)
})

test<FixtureTestContext>('respects excluded paths', async ({ devServer }) => {
const url1 = `http://localhost:${devServer.port}/custom-path-excluded/t-shirt`
const response1 = await fetch(url1)
expect(response1.status).toBe(200)
expect(await response1.text()).toBe(`Your product: t-shirt`)

const url2 = `http://localhost:${devServer.port}/custom-path-excluded/jacket`
const response2 = await fetch(url2)
expect(response2.status).toBe(404)
})

describe('handles rewrites to a function', () => {
test<FixtureTestContext>('rewrite to legacy URL format with `force: true`', async ({ devServer }) => {
const url = `http://localhost:${devServer.port}/v2-to-legacy-with-force`
Expand Down

2 comments on commit 5981a35

@github-actions
Copy link

Choose a reason for hiding this comment

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

📊 Benchmark results

  • Dependency count: 1,317
  • Package size: 480 MB
  • Number of ts-expect-error directives: 977

@github-actions
Copy link

Choose a reason for hiding this comment

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

📊 Benchmark results

  • Dependency count: 1,317
  • Package size: 480 MB
  • Number of ts-expect-error directives: 977

Please sign in to comment.