Skip to content

Commit b341938

Browse files
feat: add Generator to edge functions (#2019)
* feat: updated funcmetadata to include edge * feat: add generator to edge * chore: prettier * feat: refactored nextpluginversion to use within writeEdge * fix: updated func causing tests fails * feat: added testing for edge function generator * chore: removed console.log * Update variable naming to match constants Co-authored-by: Nick Taylor <[email protected]> * fix: fixed naming * Update test/functionsMetaData.spec.ts Co-authored-by: Nick Taylor <[email protected]> * chore: refactoring test * chore: added functionManifest interface within tests to catch errors * chore: removed comment * chore: removed comment * chore: updated naming, refactored functionality, and removed tests * chore: added new tests and added generator to rsc in edge * chore: updated snapshot * fix: snapshot update with most recent changes * fix: new tests snapshot fix * fix: testing * fix: testing to see if tests pass after removing new test/snpshot * chore: adding new tests back in * chore: removed string literals * fix: updated tests to now sort manifest data * chore: renaming and adding gen to dev edge * fix: snapshot * fix: removed snapshot testing and updated rsc tests * chore: forgot to remove obsolete snapshots * chore: sort not needed --------- Co-authored-by: Nick Taylor <[email protected]>
1 parent 2d705f4 commit b341938

File tree

5 files changed

+94
-15
lines changed

5 files changed

+94
-15
lines changed

packages/runtime/src/helpers/edge.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import { outdent } from 'outdent'
1313
import { IMAGE_FUNCTION_NAME } from '../constants'
1414

1515
import { getRequiredServerFiles, NextConfig } from './config'
16+
import { getPluginVersion } from './functionsMetaData'
1617
import { makeLocaleOptional, stripLookahead, transformCaptureGroups } from './matchers'
1718
import { RoutesManifest } from './types'
18-
1919
// This is the format as of [email protected]
2020
interface EdgeFunctionDefinitionV1 {
2121
env: string[]
@@ -57,12 +57,14 @@ export interface FunctionManifest {
5757
name?: string
5858
path: string
5959
cache?: 'manual'
60+
generator: string
6061
}
6162
| {
6263
function: string
6364
name?: string
6465
pattern: string
6566
cache?: 'manual'
67+
generator: string
6668
}
6769
>
6870
layers?: Array<{ name: `https://${string}/mod.ts`; flag: string }>
@@ -221,15 +223,17 @@ const generateEdgeFunctionMiddlewareMatchers = ({
221223
const middlewareMatcherToEdgeFunctionDefinition = (
222224
matcher: MiddlewareMatcher,
223225
name: string,
226+
generator: string,
224227
cache?: 'manual',
225228
): {
226229
function: string
227230
name?: string
228231
pattern: string
229232
cache?: 'manual'
233+
generator: string
230234
} => {
231235
const pattern = transformCaptureGroups(stripLookahead(matcher.regexp))
232-
return { function: name, pattern, name, cache }
236+
return { function: name, pattern, name, cache, generator }
233237
}
234238

235239
export const cleanupEdgeFunctions = ({
@@ -239,12 +243,14 @@ export const cleanupEdgeFunctions = ({
239243
export const writeDevEdgeFunction = async ({
240244
INTERNAL_EDGE_FUNCTIONS_SRC = '.netlify/edge-functions',
241245
}: NetlifyPluginConstants) => {
246+
const generator = await getPluginVersion()
242247
const manifest: FunctionManifest = {
243248
functions: [
244249
{
245250
function: 'next-dev',
246251
name: 'netlify dev handler',
247252
path: '/*',
253+
generator,
248254
},
249255
],
250256
version: 1,
@@ -270,6 +276,7 @@ export const generateRscDataEdgeManifest = async ({
270276
prerenderManifest?: PrerenderManifest
271277
appPathRoutesManifest?: Record<string, string>
272278
}): Promise<FunctionManifest['functions']> => {
279+
const generator = await getPluginVersion()
273280
if (!prerenderManifest || !appPathRoutesManifest) {
274281
return []
275282
}
@@ -300,11 +307,13 @@ export const generateRscDataEdgeManifest = async ({
300307
function: 'rsc-data',
301308
name: 'RSC data routing',
302309
path,
310+
generator,
303311
})),
304312
...dynamicAppDirRoutes.map((pattern) => ({
305313
function: 'rsc-data',
306314
name: 'RSC data routing',
307315
pattern,
316+
generator,
308317
})),
309318
]
310319
}
@@ -344,6 +353,8 @@ export const writeEdgeFunctions = async ({
344353
netlifyConfig: NetlifyConfig
345354
routesManifest: RoutesManifest
346355
}) => {
356+
const generator = await getPluginVersion()
357+
347358
const manifest: FunctionManifest = {
348359
functions: [],
349360
layers: [],
@@ -402,7 +413,7 @@ export const writeEdgeFunctions = async ({
402413
})
403414

404415
manifest.functions.push(
405-
...matchers.map((matcher) => middlewareMatcherToEdgeFunctionDefinition(matcher, functionName)),
416+
...matchers.map((matcher) => middlewareMatcherToEdgeFunctionDefinition(matcher, functionName, generator)),
406417
)
407418
}
408419
// Functions (i.e. not middleware, but edge SSR and API routes)
@@ -442,6 +453,7 @@ export const writeEdgeFunctions = async ({
442453
pattern,
443454
// cache: "manual" is currently experimental, so we restrict it to sites that use experimental appDir
444455
cache: usesAppDir ? 'manual' : undefined,
456+
generator,
445457
})
446458
// pages-dir page routes also have a data route. If there's a match, add an entry mapping that to the function too
447459
const dataRoute = dataRoutesMap.get(edgeFunctionDefinition.page)
@@ -451,6 +463,7 @@ export const writeEdgeFunctions = async ({
451463
name: edgeFunctionDefinition.name,
452464
pattern: dataRoute,
453465
cache: usesAppDir ? 'manual' : undefined,
466+
generator,
454467
})
455468
}
456469
}
@@ -476,6 +489,7 @@ export const writeEdgeFunctions = async ({
476489
function: 'ipx',
477490
name: 'next/image handler',
478491
path: nextConfig.images.path || '/_next/image',
492+
generator,
479493
})
480494

481495
manifest.layers.push({
@@ -494,6 +508,5 @@ export const writeEdgeFunctions = async ({
494508
This feature is in beta. Please share your feedback here: https://ntl.fyi/next-netlify-edge
495509
`)
496510
}
497-
498511
await writeJson(join(edgeFunctionRoot, 'manifest.json'), manifest)
499512
}

packages/runtime/src/helpers/functionsMetaData.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,22 @@ const getNextRuntimeVersion = async (packageJsonPath: string, useNodeModulesPath
1515
return useNodeModulesPath ? packagePlugin.version : packagePlugin.dependencies[NEXT_PLUGIN]
1616
}
1717

18+
const PLUGIN_PACKAGE_PATH = '.netlify/plugins/package.json'
19+
20+
const nextPluginVersion = async () => {
21+
const moduleRoot = resolveModuleRoot(NEXT_PLUGIN)
22+
const nodeModulesPath = moduleRoot ? join(moduleRoot, 'package.json') : null
23+
24+
return (
25+
(await getNextRuntimeVersion(nodeModulesPath, true)) ||
26+
(await getNextRuntimeVersion(PLUGIN_PACKAGE_PATH, false)) ||
27+
// The runtime version should always be available, but if it's not, return 'unknown'
28+
'unknown'
29+
)
30+
}
31+
32+
export const getPluginVersion = async () => `${NEXT_PLUGIN_NAME}@${await nextPluginVersion()}`
33+
1834
// The information needed to create a function configuration file
1935
export interface FunctionInfo {
2036
// The name of the function, e.g. `___netlify-handler`
@@ -34,20 +50,12 @@ export interface FunctionInfo {
3450
*/
3551
export const writeFunctionConfiguration = async (functionInfo: FunctionInfo) => {
3652
const { functionName, functionTitle, functionsDir } = functionInfo
37-
const pluginPackagePath = '.netlify/plugins/package.json'
38-
const moduleRoot = resolveModuleRoot(NEXT_PLUGIN)
39-
const nodeModulesPath = moduleRoot ? join(moduleRoot, 'package.json') : null
40-
41-
const nextPluginVersion =
42-
(await getNextRuntimeVersion(nodeModulesPath, true)) ||
43-
(await getNextRuntimeVersion(pluginPackagePath, false)) ||
44-
// The runtime version should always be available, but if it's not, return 'unknown'
45-
'unknown'
53+
const generator = await getPluginVersion()
4654

4755
const metadata = {
4856
config: {
4957
name: functionTitle,
50-
generator: `${NEXT_PLUGIN_NAME}@${nextPluginVersion}`,
58+
generator,
5159
},
5260
version: 1,
5361
}

test/edge.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
import { generateRscDataEdgeManifest } from '../packages/runtime/src/helpers/edge'
22
import type { PrerenderManifest } from 'next/dist/build'
33

4+
jest.mock('../packages/runtime/src/helpers/functionsMetaData', () => {
5+
const { NEXT_PLUGIN_NAME } = require('../packages/runtime/src/constants')
6+
return {
7+
...jest.requireActual('../packages/runtime/src/helpers/functionsMetaData'),
8+
getPluginVersion: async () => `${NEXT_PLUGIN_NAME}@1.0.0`,
9+
}
10+
})
11+
412
const basePrerenderManifest: PrerenderManifest = {
513
version: 3,
614
routes: {},
@@ -33,11 +41,13 @@ describe('generateRscDataEdgeManifest', () => {
3341
expect(edgeManifest).toEqual([
3442
{
3543
function: 'rsc-data',
44+
generator: "@netlify/[email protected]",
3645
name: 'RSC data routing',
3746
path: '/',
3847
},
3948
{
4049
function: 'rsc-data',
50+
generator: "@netlify/[email protected]",
4151
name: 'RSC data routing',
4252
path: '/index.rsc',
4353
},
@@ -84,11 +94,13 @@ describe('generateRscDataEdgeManifest', () => {
8494
expect(edgeManifest).toEqual([
8595
{
8696
function: 'rsc-data',
97+
generator: "@netlify/[email protected]",
8798
name: 'RSC data routing',
8899
pattern: '^/blog/([^/]+?)(?:/)?$',
89100
},
90101
{
91102
function: 'rsc-data',
103+
generator: "@netlify/[email protected]",
92104
name: 'RSC data routing',
93105
pattern: '^/blog/([^/]+?)\\.rsc$',
94106
},

test/functionsMetaData.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,4 @@ describe('writeFunctionConfiguration', () => {
102102

103103
expect(actual).toEqual(expected)
104104
})
105-
})
105+
})

test/index.spec.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ jest.mock('../packages/runtime/src/helpers/utils', () => {
99
}
1010
})
1111

12+
jest.mock('../packages/runtime/src/helpers/functionsMetaData', () => {
13+
const { NEXT_PLUGIN_NAME } = require('../packages/runtime/src/constants')
14+
return {
15+
...jest.requireActual('../packages/runtime/src/helpers/functionsMetaData'),
16+
getPluginVersion: async () => `${NEXT_PLUGIN_NAME}@1.0.0`,
17+
}
18+
})
19+
1220
const Chance = require('chance')
1321
const {
1422
writeJSON,
@@ -730,6 +738,44 @@ describe('onBuild()', () => {
730738
expect(existsSync(path.join('.netlify', 'edge-functions', 'ipx', 'index.ts'))).toBeTruthy()
731739
})
732740

741+
test('generates edge-functions manifest', async () => {
742+
await moveNextDist()
743+
await nextRuntime.onBuild(defaultArgs)
744+
expect(existsSync(path.join('.netlify', 'edge-functions', 'manifest.json'))).toBeTruthy()
745+
})
746+
747+
test('generates generator field within the edge-functions manifest', async () => {
748+
await moveNextDist()
749+
await nextRuntime.onBuild(defaultArgs)
750+
const manifestPath = await readJson(path.resolve('.netlify/edge-functions/manifest.json'))
751+
const manifest = manifestPath.functions
752+
753+
expect(manifest).toEqual(
754+
expect.arrayContaining([
755+
expect.objectContaining({
756+
generator: '@netlify/[email protected]'
757+
})
758+
])
759+
)
760+
})
761+
762+
763+
test('generates generator field within the edge-functions manifest includes IPX', async () => {
764+
process.env.NEXT_FORCE_EDGE_IMAGES = '1'
765+
await moveNextDist()
766+
await nextRuntime.onBuild(defaultArgs)
767+
const manifestPath = await readJson(path.resolve('.netlify/edge-functions/manifest.json'))
768+
const manifest = manifestPath.functions
769+
770+
expect(manifest).toEqual(
771+
expect.arrayContaining([
772+
expect.objectContaining({
773+
generator: '@netlify/[email protected]'
774+
})
775+
])
776+
)
777+
})
778+
733779
test('does not generate an ipx function when DISABLE_IPX is set', async () => {
734780
process.env.DISABLE_IPX = '1'
735781
await moveNextDist()

0 commit comments

Comments
 (0)