Skip to content

feat: add generator meta data for framework generated Netlify Functions #1973

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 28 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
bd84f28
feat: create generator file for functions
taty2010 Mar 9, 2023
7439f6b
fix: prettier update
taty2010 Mar 9, 2023
da735ec
fix: adding required version number and making sure file name matches
taty2010 Mar 9, 2023
b8a95ad
feat: create generator field for ipx
taty2010 Mar 9, 2023
ef18051
fix: spacing
taty2010 Mar 9, 2023
0569c82
fix: need to use functionsDir instead of functionDir
taty2010 Mar 9, 2023
c2cf826
fix: refactoring writeGeneratorField
taty2010 Mar 9, 2023
c610f63
fix: prettier
taty2010 Mar 9, 2023
7abf64e
fix: remove check for packageplugin
taty2010 Mar 10, 2023
ecf8ef0
feat: udated functionality to help prevent slow build
taty2010 Mar 10, 2023
528832a
fix: removed extra param
taty2010 Mar 10, 2023
5064473
fix: check for version in node modules instead of package
taty2010 Mar 15, 2023
a2a1ded
fix: changed not found message and added xtra check
taty2010 Mar 15, 2023
8982ab6
fix: needing to check if nodemodule first
taty2010 Mar 15, 2023
33e506b
using resolvemoduleroot to access correct package
taty2010 Mar 16, 2023
9e9209b
chore: added ava testing framework
nickytonline Mar 16, 2023
dc61d8a
chore: moved writeFunctionConfiguration to a separate utils folder
nickytonline Mar 16, 2023
db3051f
test: added a test for writeFunctionConfiguration
nickytonline Mar 16, 2023
082704f
test: added more tests for writeFunctionConfiguration
nickytonline Mar 17, 2023
e51ef70
chore: merged latest from main
nickytonline Mar 17, 2023
3021f37
chore: removed unused deps that were added
nickytonline Mar 17, 2023
27d408e
chore: added mock-fs package
nickytonline Mar 17, 2023
999831b
chore: renamed and shuffled some files around
nickytonline Mar 17, 2023
7af73c9
chore: refactored some tests
nickytonline Mar 17, 2023
54797bd
Merge branch 'main' into tn/generator-serverless
taty2010 Mar 17, 2023
30d848e
chore: sorting out npm install issues
nickytonline Mar 17, 2023
76ec9c7
Revert "chore: sorting out npm install issues"
nickytonline Mar 17, 2023
60d15af
Revert "Merge branch 'main' into tn/generator-serverless"
nickytonline Mar 17, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11,202 changes: 2,361 additions & 8,841 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"@babel/preset-env": "^7.15.8",
"@babel/preset-typescript": "^7.16.0",
"@delucis/if-env": "^1.1.2",
"@netlify/build": "^29.7.0",
"@netlify/build": "^29.6.10",
"@netlify/eslint-config-node": "^7.0.1",
"@testing-library/cypress": "^8.0.1",
"@types/fs-extra": "^9.0.13",
Expand All @@ -75,6 +75,7 @@
"jest-extended": "^3.2.0",
"jest-fetch-mock": "^3.0.3",
"jest-junit": "^14.0.1",
"mock-fs": "^5.2.0",
"netlify-plugin-cypress": "^2.2.1",
"npm-run-all": "^4.1.5",
"playwright-chromium": "^1.26.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
},
"devDependencies": {
"@delucis/if-env": "^1.1.2",
"@netlify/build": "^29.7.0",
"@netlify/build": "^29.6.10",
"@types/fs-extra": "^9.0.13",
"@types/jest": "^27.4.1",
"@types/merge-stream": "^1.1.2",
Expand Down
6 changes: 5 additions & 1 deletion packages/runtime/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
export const HANDLER_FUNCTION_NAME = '___netlify-handler'
export const ODB_FUNCTION_NAME = '___netlify-odb-handler'
export const IMAGE_FUNCTION_NAME = '_ipx'

export const NEXT_PLUGIN_NAME = '@netlify/next-runtime'
export const NEXT_PLUGIN = '@netlify/plugin-nextjs'
export const HANDLER_FUNCTION_TITLE = 'Next.js SSR handler'
export const ODB_FUNCTION_TITLE = 'Next.js ISR handler'
export const IMAGE_FUNCTION_TITLE = "'next/image handler'"
// These are paths in .next that shouldn't be publicly accessible
export const HIDDEN_PATHS = [
'/cache/*',
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/src/helpers/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const updateRequiredServerFiles = async (publish: string, modifiedConfig:
await writeJSON(configFile, modifiedConfig)
}

const resolveModuleRoot = (moduleName) => {
export const resolveModuleRoot = (moduleName) => {
try {
return dirname(relative(process.cwd(), require.resolve(`${moduleName}/package.json`, { paths: [process.cwd()] })))
} catch {
Expand Down
19 changes: 15 additions & 4 deletions packages/runtime/src/helpers/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,22 @@ import type { ImageConfigComplete, RemotePattern } from 'next/dist/shared/lib/im
import { outdent } from 'outdent'
import { join, relative, resolve } from 'pathe'

import { HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME, IMAGE_FUNCTION_NAME, DEFAULT_FUNCTIONS_SRC } from '../constants'
import {
HANDLER_FUNCTION_NAME,
ODB_FUNCTION_NAME,
IMAGE_FUNCTION_NAME,
DEFAULT_FUNCTIONS_SRC,
HANDLER_FUNCTION_TITLE,
ODB_FUNCTION_TITLE,
IMAGE_FUNCTION_TITLE,
} from '../constants'
import { getApiHandler } from '../templates/getApiHandler'
import { getHandler } from '../templates/getHandler'
import { getResolverForPages, getResolverForSourceFiles } from '../templates/getPageResolver'

import { ApiConfig, ApiRouteType, extractConfigFromFile } from './analysis'
import { getSourceFileForPage } from './files'
import { writeFunctionConfiguration } from './functionsMetaData'
import { getFunctionNameForPage } from './utils'

export interface ApiRouteConfig {
Expand Down Expand Up @@ -62,7 +71,7 @@ export const generateFunctions = async (
await writeFile(join(functionsDir, functionName, 'pages.js'), resolverSource)
}

const writeHandler = async (functionName: string, isODB: boolean) => {
const writeHandler = async (functionName: string, functionTitle: string, isODB: boolean) => {
const handlerSource = await getHandler({ isODB, publishDir, appDir: relative(functionDir, appDir) })
await ensureDir(join(functionsDir, functionName))
await writeFile(join(functionsDir, functionName, `${functionName}.js`), handlerSource)
Expand All @@ -71,10 +80,11 @@ export const generateFunctions = async (
join(__dirname, '..', '..', 'lib', 'templates', 'handlerUtils.js'),
join(functionsDir, functionName, 'handlerUtils.js'),
)
writeFunctionConfiguration(functionName, functionTitle, functionsDir)
}

await writeHandler(HANDLER_FUNCTION_NAME, false)
await writeHandler(ODB_FUNCTION_NAME, true)
await writeHandler(HANDLER_FUNCTION_NAME, HANDLER_FUNCTION_TITLE, false)
await writeHandler(ODB_FUNCTION_NAME, ODB_FUNCTION_TITLE, true)
}

/**
Expand Down Expand Up @@ -138,6 +148,7 @@ export const setupImageFunction = async ({
})

await copyFile(join(__dirname, '..', '..', 'lib', 'templates', 'ipx.js'), join(functionDirectory, functionName))
writeFunctionConfiguration(functionName.replace('.js', ''), IMAGE_FUNCTION_TITLE, functionsPath)

// If we have edge functions then the request will have already been rewritten
// so this won't match. This is matched if edge is disabled or unavailable.
Expand Down
44 changes: 44 additions & 0 deletions packages/runtime/src/helpers/functionsMetaData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { existsSync, readJSON, writeFile } from 'fs-extra'
import { join } from 'pathe'

import { NEXT_PLUGIN, NEXT_PLUGIN_NAME } from '../constants'

import { resolveModuleRoot } from './config'

const checkForPackage = async (packageDir: string, nodeModule: boolean) => {
const packagePlugin = existsSync(packageDir) ? await readJSON(packageDir) : null
let nextPlugin
if (!nodeModule && packagePlugin) {
nextPlugin = packagePlugin.dependencies[NEXT_PLUGIN] ? packagePlugin.dependencies[NEXT_PLUGIN] : null
} else if (nodeModule && packagePlugin) {
nextPlugin = packagePlugin.version ? packagePlugin.version : null
}

return nextPlugin
}

/**
* Creates a function configuration file for the given function
*
* @param functionName The name of the function, e.g. `___netlify-handler`
* @param functionTitle The name of the function that will be displayed in logs, e.g. `Next.js SSR handler`
* @param functionsDir The directory where the function is located, e.g. `.netlify/functions`
*/
export const writeFunctionConfiguration = async (functionName: string, functionTitle: string, functionsDir: string) => {
const pluginPackagePath = '.netlify/plugins/package.json'
const ProjDir = resolveModuleRoot(NEXT_PLUGIN)
const nodeModulesPath = `${ProjDir}/package.json`

const nextPluginVersion =
(await checkForPackage(nodeModulesPath, true)) || (await checkForPackage(pluginPackagePath, false))

const metadata = {
config: {
name: functionTitle,
generator: nextPluginVersion ? `${NEXT_PLUGIN_NAME}@${nextPluginVersion}` : 'Next Runtime Version Not Found',
},
version: 1,
}

await writeFile(join(functionsDir, functionName, `${functionName}.json`), JSON.stringify(metadata))
}
105 changes: 105 additions & 0 deletions test/functionsMetaData.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { readJSON } from 'fs-extra'
import mock from 'mock-fs'
import { join } from 'pathe'
import { NEXT_PLUGIN_NAME } from '../packages/runtime/src/constants'
import { writeFunctionConfiguration } from '../packages/runtime/src/helpers/functionsMetaData'

describe('writeFunctionConfiguration', () => {
afterEach(() => {
mock.restore()
})

it('should write the configuration for a function using node modules version of @netlify/plugin-nextjs', async () => {
const nextRuntimeVersion = '23.4.5'

mock({
'.netlify/plugins/package.json': JSON.stringify({
name: 'test',
version: '1.0.0',
dependencies: {
'@netlify/plugin-nextjs': '29.3.4',
},
}),
'node_modules/@netlify/plugin-nextjs/package.json': JSON.stringify({
name: '@netlify/plugin-nextjs',
version: nextRuntimeVersion,
}),
'.netlify/functions/some-folder/someFunctionName': {},
})

const functionName = 'someFunctionName'
const functionTitle = 'some function title'
const functionsDir = '.netlify/functions/some-folder'

const expected = {
config: {
name: functionTitle,
generator: `${NEXT_PLUGIN_NAME}@${nextRuntimeVersion}`,
},
version: 1,
}

const filePathToSaveTo = join(functionsDir, functionName, `${functionName}.json`)
await writeFunctionConfiguration(functionName, functionTitle, functionsDir)
const actual = await readJSON(filePathToSaveTo)

expect(actual).toEqual(expected)
})

it('should write the configuration for a function using version of @netlify/plugin-nextjs in package.json', async () => {
const nextRuntimeVersion = '23.4.5'

mock({
'.netlify/plugins/package.json': JSON.stringify({
name: 'test',
version: '1.0.0',
dependencies: {
'@netlify/plugin-nextjs': nextRuntimeVersion,
},
}),
'.netlify/functions/some-folder/someFunctionName': {},
})

const functionName = 'someFunctionName'
const functionTitle = 'some function title'
const functionsDir = '.netlify/functions/some-folder'

const expected = {
config: {
name: functionTitle,
generator: `${NEXT_PLUGIN_NAME}@${nextRuntimeVersion}`,
},
version: 1,
}

const filePathToSaveTo = join(functionsDir, functionName, `${functionName}.json`)
await writeFunctionConfiguration(functionName, functionTitle, functionsDir)
const actual = await readJSON(filePathToSaveTo)

expect(actual).toEqual(expected)
})

it('should write the configuration for a function with runtime version not found', async () => {
mock({
'.netlify/functions/some-folder/someFunctionName': {},
})

const functionName = 'someFunctionName'
const functionTitle = 'some function title'
const functionsDir = '.netlify/functions/some-folder'

const expected = {
config: {
name: functionTitle,
generator: 'Next Runtime Version Not Found',
},
version: 1,
}

const filePathToSaveTo = join(functionsDir, functionName, `${functionName}.json`)
await writeFunctionConfiguration(functionName, functionTitle, functionsDir)
const actual = await readJSON(filePathToSaveTo)

expect(actual).toEqual(expected)
})
})