diff --git a/packages/build/src/plugins_core/functions/index.ts b/packages/build/src/plugins_core/functions/index.ts index b4d391ad28..c1b0bf54e9 100644 --- a/packages/build/src/plugins_core/functions/index.ts +++ b/packages/build/src/plugins_core/functions/index.ts @@ -3,6 +3,7 @@ import { resolve } from 'path' import { NodeBundlerName, RUNTIME, zipFunctions } from '@netlify/zip-it-and-ship-it' import { pathExists } from 'path-exists' +import { addErrorInfo } from '../../error/info.js' import { log } from '../../log/logger.js' import { logBundleResults, logFunctionsNonExistingDir, logFunctionsToBundle } from '../../log/messages/core_steps.js' @@ -19,6 +20,45 @@ const getBundlers = (results: Awaited> = []) => .filter(Boolean) as NodeBundlerName[], ) +// see https://docs.netlify.com/functions/trigger-on-events/#available-triggers +const eventTriggeredFunctions = new Set([ + 'deploy-building', + 'deploy-succeeded', + 'deploy-failed', + 'deploy-deleted', + 'deploy-locked', + 'deploy-unlocked', + 'submission-created', + 'split-test-activated', + 'split-test-deactivated', + 'split-test-modified', + 'identity-validate', + 'identity-signup', + 'identity-login', +]) + +const validateCustomRoutes = function (functions: Awaited>) { + for (const { routes, name, schedule } of functions) { + if (!routes || routes.length === 0) continue + + if (schedule) { + const error = new Error( + `Scheduled functions must not specify a custom path. Please remove the "path" configuration. Learn more about scheduled functions at https://ntl.fyi/custom-path-scheduled-functions.`, + ) + addErrorInfo(error, { type: 'resolveConfig' }) + throw error + } + + if (eventTriggeredFunctions.has(name.toLowerCase().replace('-background', ''))) { + const error = new Error( + `Event-triggered functions must not specify a custom path. Please remove the "path" configuration or pick a different name for the function. Learn more about event-triggered functions at https://ntl.fyi/custom-path-event-triggered-functions.`, + ) + addErrorInfo(error, { type: 'resolveConfig' }) + throw error + } + } +} + const zipFunctionsAndLogResults = async ({ buildDir, childEnv, @@ -53,6 +93,8 @@ const zipFunctionsAndLogResults = async ({ const sourceDirectories = [internalFunctionsSrc, functionsSrc].filter(Boolean) const results = await zipItAndShipIt.zipFunctions(sourceDirectories, functionsDist, zisiParameters) + validateCustomRoutes(results) + const bundlers = Array.from(getBundlers(results)) logBundleResults({ logs, results }) diff --git a/packages/build/tests/functions/fixtures/custom_path_event_triggered/netlify/functions/deploy-succeeded.mjs b/packages/build/tests/functions/fixtures/custom_path_event_triggered/netlify/functions/deploy-succeeded.mjs new file mode 100644 index 0000000000..0b89aa5393 --- /dev/null +++ b/packages/build/tests/functions/fixtures/custom_path_event_triggered/netlify/functions/deploy-succeeded.mjs @@ -0,0 +1,5 @@ +export default () => new Response('foo') + +export const config = { + path: "/deploy-succeeded", +} \ No newline at end of file diff --git a/packages/build/tests/functions/fixtures/custom_path_scheduled/netlify/functions/daily.mjs b/packages/build/tests/functions/fixtures/custom_path_scheduled/netlify/functions/daily.mjs new file mode 100644 index 0000000000..5b5058f8f2 --- /dev/null +++ b/packages/build/tests/functions/fixtures/custom_path_scheduled/netlify/functions/daily.mjs @@ -0,0 +1,6 @@ +export default () => new Response('Hello, world!') + +export const config = { + path: "/daily", + schedule: "@daily" +} \ No newline at end of file diff --git a/packages/build/tests/functions/tests.js b/packages/build/tests/functions/tests.js index 73d3c2e0db..107d5a5acd 100644 --- a/packages/build/tests/functions/tests.js +++ b/packages/build/tests/functions/tests.js @@ -67,3 +67,13 @@ test('Functions: --functionsDistDir', async (t) => { await removeDir(functionsDistDir) } }) + +test('Functions: custom path on scheduled function', async (t) => { + const output = await new Fixture('./fixtures/custom_path_scheduled').runWithBuild() + t.true(output.includes('Scheduled functions must not specify a custom path.')) +}) + +test('Functions: custom path on event-triggered function', async (t) => { + const output = await new Fixture('./fixtures/custom_path_event_triggered').runWithBuild() + t.true(output.includes('Event-triggered functions must not specify a custom path.')) +})