Skip to content

Commit

Permalink
fix: don't delete functions generated by other build plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
pieh committed Jun 26, 2024
1 parent 4345795 commit 7195b1a
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 9 deletions.
55 changes: 49 additions & 6 deletions src/build/functions/edge.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
import { dirname, join } from 'node:path'
import { cp, mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises'
import { dirname, join, parse as parsePath } from 'node:path'

import type { Manifest, ManifestFunction } from '@netlify/edge-functions'
import { glob } from 'fast-glob'
Expand All @@ -8,9 +8,21 @@ import { pathToRegexp } from 'path-to-regexp'

import { EDGE_HANDLER_NAME, PluginContext } from '../plugin-context.js'

type ManifestFunctionWithGenerator = ManifestFunction & { generator?: string }

const getEdgeManifestPath = (ctx: PluginContext) => join(ctx.edgeFunctionsDir, 'manifest.json')

const writeEdgeManifest = async (ctx: PluginContext, manifest: Manifest) => {
await mkdir(ctx.edgeFunctionsDir, { recursive: true })
await writeFile(join(ctx.edgeFunctionsDir, 'manifest.json'), JSON.stringify(manifest, null, 2))
await writeFile(getEdgeManifestPath(ctx), JSON.stringify(manifest, null, 2))
}

const readEdgeManifest = async (ctx: PluginContext) => {
try {
return JSON.parse(await readFile(getEdgeManifestPath(ctx), 'utf-8')) as Manifest
} catch {
return null
}
}

const copyRuntime = async (ctx: PluginContext, handlerDirectory: string): Promise<void> => {
Expand Down Expand Up @@ -145,7 +157,7 @@ const getHandlerName = ({ name }: Pick<NextDefinition, 'name'>): string =>
const buildHandlerDefinition = (
ctx: PluginContext,
{ name, matchers, page }: NextDefinition,
): Array<ManifestFunction> => {
): Array<ManifestFunctionWithGenerator> => {
const fun = getHandlerName({ name })
const funName = name.endsWith('middleware')
? 'Next.js Middleware Handler'
Expand All @@ -162,8 +174,39 @@ const buildHandlerDefinition = (
}))
}

const clearStaleEdgeHandlers = async (ctx: PluginContext) => {
const previousManifest = await readEdgeManifest(ctx)
if (!previousManifest) {
return []
}

const uniqueNextRuntimeFunctions = new Set<string>()
const nonNextRuntimeFunctions: ManifestFunctionWithGenerator[] = []

for (const fn of previousManifest.functions as ManifestFunctionWithGenerator[]) {
if (fn?.generator?.startsWith(ctx.pluginName)) {
uniqueNextRuntimeFunctions.add(fn.function)
} else {
nonNextRuntimeFunctions.push(fn)
}
}

if (uniqueNextRuntimeFunctions.size === 0) {
return nonNextRuntimeFunctions
}

for (const fileOrDir of await readdir(ctx.edgeFunctionsDir, { withFileTypes: true })) {
const nameWithoutExtension = parsePath(fileOrDir.name).name

if (uniqueNextRuntimeFunctions.has(nameWithoutExtension)) {
await rm(join(ctx.edgeFunctionsDir, fileOrDir.name), { recursive: true, force: true })
}
}
return nonNextRuntimeFunctions
}

export const createEdgeHandlers = async (ctx: PluginContext) => {
await rm(ctx.edgeFunctionsDir, { recursive: true, force: true })
const nonNextRuntimeFunctions = await clearStaleEdgeHandlers(ctx)

const nextManifest = await ctx.getMiddlewareManifest()
const nextDefinitions = [
Expand All @@ -175,7 +218,7 @@ export const createEdgeHandlers = async (ctx: PluginContext) => {
const netlifyDefinitions = nextDefinitions.flatMap((def) => buildHandlerDefinition(ctx, def))
const netlifyManifest: Manifest = {
version: 1,
functions: netlifyDefinitions,
functions: [...nonNextRuntimeFunctions, ...netlifyDefinitions],
}
await writeEdgeManifest(ctx, netlifyManifest)
}
46 changes: 43 additions & 3 deletions src/build/functions/server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
import { join, relative } from 'node:path'
import { cp, mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises'
import { join, parse as parsePath, relative } from 'node:path'
import { join as posixJoin } from 'node:path/posix'

import { trace } from '@opentelemetry/api'
Expand Down Expand Up @@ -127,12 +127,52 @@ const writeHandlerFile = async (ctx: PluginContext) => {
await writeFile(join(ctx.serverHandlerRootDir, `${SERVER_HANDLER_NAME}.mjs`), handler)
}

const clearStaleServerHandlers = async (ctx: PluginContext) => {
const potentialServerlessFunctionConfigFiles = await glob('**/*.json', {
deep: 2,
cwd: ctx.serverFunctionsDir,
})

const toRemove = new Set<string>()

for (const potentialServerlessFunctionConfigFile of potentialServerlessFunctionConfigFiles) {
try {
const functionConfig = JSON.parse(
await readFile(
join(ctx.serverFunctionsDir, potentialServerlessFunctionConfigFile),
'utf-8',
),
)

if (functionConfig?.config?.generator?.startsWith(ctx.pluginName)) {
const parsedPath = parsePath(potentialServerlessFunctionConfigFile)

toRemove.add(parsedPath.dir || parsedPath.name)
}
} catch {
// this might be malformatted json or json that doesn't represent function configuration
// so we just skip it in case of errors
}
}

if (toRemove.size === 0) {
return
}

for (const fileOrDir of await readdir(ctx.serverFunctionsDir, { withFileTypes: true })) {
const nameWithoutExtension = parsePath(fileOrDir.name).name

if (toRemove.has(nameWithoutExtension)) {
await rm(join(ctx.serverFunctionsDir, fileOrDir.name), { recursive: true, force: true })
}
}
}
/**
* Create a Netlify function to run the Next.js server
*/
export const createServerHandler = async (ctx: PluginContext) => {
await tracer.withActiveSpan('createServerHandler', async () => {
await rm(ctx.serverFunctionsDir, { recursive: true, force: true })
await clearStaleServerHandlers(ctx)
await mkdir(join(ctx.serverHandlerDir, '.netlify'), { recursive: true })

await copyNextServerCode(ctx)
Expand Down

0 comments on commit 7195b1a

Please sign in to comment.