Skip to content

Commit af5fa9d

Browse files
authored
Merge branch 'main' into mk/matcher-magic
2 parents 9fe2fe6 + 9635bcf commit af5fa9d

32 files changed

+810
-39
lines changed

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"packages/runtime": "4.26.0",
2+
"packages/runtime": "4.27.1",
33
"packages/next": "1.3.1"
44
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
describe('Extended API routes', () => {
2+
it('returns HTTP 202 Accepted for background route', () => {
3+
cy.request('/api/hello-background').then((response) => {
4+
expect(response.status).to.equal(202)
5+
})
6+
})
7+
it('correctly returns 404 for scheduled route', () => {
8+
cy.request({ url: '/api/hello-scheduled', failOnStatusCode: false }).its('status').should('equal', 404)
9+
})
10+
})

cypress/integration/default/preview.spec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
describe('Preview Mode', () => {
22
it('enters and exits preview mode', () => {
3+
Cypress.Cookies.debug(true)
4+
cy.getCookies().then((cookie) => cy.log('cookies', cookie))
35
// preview mode is off by default
46
cy.visit('/previewTest')
5-
cy.findByText('Is preview? No', {selector: 'h1'})
7+
cy.findByText('Is preview? No', { selector: 'h1' })
68

79
// enter preview mode
810
cy.request('/api/enterPreview').then((response) => {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export default (req, res) => {
2+
res.setHeader('Content-Type', 'application/json')
3+
res.status(200)
4+
res.json({ message: 'hello world :)' })
5+
}
6+
7+
export const config = {
8+
type: 'experimental-background',
9+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export default (req, res) => {
2+
res.setHeader('Content-Type', 'application/json')
3+
res.status(200)
4+
res.json({ message: 'hello world :)' })
5+
}
6+
7+
export const config = {
8+
type: 'experimental-scheduled',
9+
schedule: '@hourly',
10+
}

packages/runtime/CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# Changelog
22

3+
## [4.27.1](https://github.com/netlify/next-runtime/compare/plugin-nextjs-v4.27.0...plugin-nextjs-v4.27.1) (2022-10-18)
4+
5+
6+
### Bug Fixes
7+
8+
* gracefully handle missing static analysis tools ([#1691](https://github.com/netlify/next-runtime/issues/1691)) ([34a039e](https://github.com/netlify/next-runtime/commit/34a039ec80a7c7f050fb5f2dab6f4b8ffddda38a))
9+
10+
## [4.27.0](https://github.com/netlify/next-runtime/compare/plugin-nextjs-v4.26.0...plugin-nextjs-v4.27.0) (2022-10-17)
11+
12+
13+
### Features
14+
15+
* split api routes into separate functions ([#1495](https://github.com/netlify/next-runtime/issues/1495)) ([654c6de](https://github.com/netlify/next-runtime/commit/654c6defa99d33de5178409d43827b57a29821d8))
16+
317
## [4.26.0](https://github.com/netlify/next-runtime/compare/plugin-nextjs-v4.25.0...plugin-nextjs-v4.26.0) (2022-10-17)
418

519

packages/runtime/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@netlify/plugin-nextjs",
3-
"version": "4.26.0",
3+
"version": "4.27.1",
44
"description": "Run Next.js seamlessly on Netlify",
55
"main": "index.js",
66
"files": [
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import fs, { existsSync } from 'fs'
2+
3+
import { relative } from 'pathe'
4+
5+
// I have no idea what eslint is up to here but it gives an error
6+
// eslint-disable-next-line no-shadow
7+
export const enum ApiRouteType {
8+
SCHEDULED = 'experimental-scheduled',
9+
BACKGROUND = 'experimental-background',
10+
}
11+
12+
export interface ApiStandardConfig {
13+
type?: never
14+
runtime?: 'nodejs' | 'experimental-edge'
15+
schedule?: never
16+
}
17+
18+
export interface ApiScheduledConfig {
19+
type: ApiRouteType.SCHEDULED
20+
runtime?: 'nodejs'
21+
schedule: string
22+
}
23+
24+
export interface ApiBackgroundConfig {
25+
type: ApiRouteType.BACKGROUND
26+
runtime?: 'nodejs'
27+
schedule?: never
28+
}
29+
30+
export type ApiConfig = ApiStandardConfig | ApiScheduledConfig | ApiBackgroundConfig
31+
32+
export const validateConfigValue = (config: ApiConfig, apiFilePath: string): config is ApiConfig => {
33+
if (config.type === ApiRouteType.SCHEDULED) {
34+
if (!config.schedule) {
35+
console.error(
36+
`Invalid config value in ${relative(process.cwd(), apiFilePath)}: schedule is required when type is "${
37+
ApiRouteType.SCHEDULED
38+
}"`,
39+
)
40+
return false
41+
}
42+
if ((config as ApiConfig).runtime === 'experimental-edge') {
43+
console.error(
44+
`Invalid config value in ${relative(
45+
process.cwd(),
46+
apiFilePath,
47+
)}: edge runtime is not supported for scheduled functions`,
48+
)
49+
return false
50+
}
51+
return true
52+
}
53+
54+
if (!config.type || config.type === ApiRouteType.BACKGROUND) {
55+
if (config.schedule) {
56+
console.error(
57+
`Invalid config value in ${relative(process.cwd(), apiFilePath)}: schedule is not allowed unless type is "${
58+
ApiRouteType.SCHEDULED
59+
}"`,
60+
)
61+
return false
62+
}
63+
if (config.type && (config as ApiConfig).runtime === 'experimental-edge') {
64+
console.error(
65+
`Invalid config value in ${relative(
66+
process.cwd(),
67+
apiFilePath,
68+
)}: edge runtime is not supported for background functions`,
69+
)
70+
return false
71+
}
72+
return true
73+
}
74+
console.error(
75+
`Invalid config value in ${relative(process.cwd(), apiFilePath)}: type ${
76+
(config as ApiConfig).type
77+
} is not supported`,
78+
)
79+
return false
80+
}
81+
82+
let extractConstValue
83+
let parseModule
84+
let hasWarnedAboutNextVersion = false
85+
/**
86+
* Uses Next's swc static analysis to extract the config values from a file.
87+
*/
88+
export const extractConfigFromFile = async (apiFilePath: string): Promise<ApiConfig> => {
89+
if (!apiFilePath || !existsSync(apiFilePath)) {
90+
return {}
91+
}
92+
93+
try {
94+
if (!extractConstValue) {
95+
extractConstValue = require('next/dist/build/analysis/extract-const-value')
96+
}
97+
if (!parseModule) {
98+
// eslint-disable-next-line prefer-destructuring, @typescript-eslint/no-var-requires
99+
parseModule = require('next/dist/build/analysis/parse-module').parseModule
100+
}
101+
} catch (error) {
102+
if (error.code === 'MODULE_NOT_FOUND') {
103+
if (!hasWarnedAboutNextVersion) {
104+
console.log("This version of Next.js doesn't support advanced API routes. Skipping...")
105+
hasWarnedAboutNextVersion = true
106+
}
107+
// Old Next.js version
108+
return {}
109+
}
110+
throw error
111+
}
112+
113+
const { extractExportedConstValue, UnsupportedValueError } = extractConstValue
114+
115+
const fileContent = await fs.promises.readFile(apiFilePath, 'utf8')
116+
// No need to parse if there's no "config"
117+
if (!fileContent.includes('config')) {
118+
return {}
119+
}
120+
const ast = await parseModule(apiFilePath, fileContent)
121+
122+
let config: ApiConfig
123+
try {
124+
config = extractExportedConstValue(ast, 'config')
125+
} catch (error) {
126+
if (UnsupportedValueError && error instanceof UnsupportedValueError) {
127+
console.warn(`Unsupported config value in ${relative(process.cwd(), apiFilePath)}`)
128+
}
129+
return {}
130+
}
131+
if (validateConfigValue(config, apiFilePath)) {
132+
return config
133+
}
134+
throw new Error(`Unsupported config value in ${relative(process.cwd(), apiFilePath)}`)
135+
}

packages/runtime/src/helpers/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export const configureHandlerFunctions = async ({ netlifyConfig, publish, ignore
9292
}
9393

9494
/* eslint-enable no-underscore-dangle */
95-
;[HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME].forEach((functionName) => {
95+
;[HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME, '_api_*'].forEach((functionName) => {
9696
netlifyConfig.functions[functionName] ||= { included_files: [], external_node_modules: [] }
9797
netlifyConfig.functions[functionName].node_bundler = 'nft'
9898
netlifyConfig.functions[functionName].included_files ||= []

packages/runtime/src/helpers/files.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import globby from 'globby'
88
import { PrerenderManifest } from 'next/dist/build'
99
import { outdent } from 'outdent'
1010
import pLimit from 'p-limit'
11-
import { join } from 'pathe'
11+
import { join, resolve } from 'pathe'
1212
import slash from 'slash'
1313

1414
import { MINIMUM_REVALIDATE_SECONDS, DIVIDER } from '../constants'
@@ -18,6 +18,7 @@ import { Rewrites, RoutesManifest } from './types'
1818
import { findModuleFromBase } from './utils'
1919

2020
const TEST_ROUTE = /(|\/)\[[^/]+?](\/|\.html|$)/
21+
const SOURCE_FILE_EXTENSIONS = ['js', 'jsx', 'ts', 'tsx']
2122

2223
export const isDynamicRoute = (route) => TEST_ROUTE.test(route)
2324

@@ -333,6 +334,31 @@ const getServerFile = (root: string, includeBase = true) => {
333334
return findModuleFromBase({ candidates, paths: [root] })
334335
}
335336

337+
/**
338+
* Find the source file for a given page route
339+
*/
340+
export const getSourceFileForPage = (page: string, root: string) => {
341+
for (const extension of SOURCE_FILE_EXTENSIONS) {
342+
const file = join(root, `${page}.${extension}`)
343+
if (existsSync(file)) {
344+
return file
345+
}
346+
}
347+
console.log('Could not find source file for page', page)
348+
}
349+
350+
/**
351+
* Reads the node file trace file for a given file, and resolves the dependencies
352+
*/
353+
export const getDependenciesOfFile = async (file: string) => {
354+
const nft = `${file}.nft.json`
355+
if (!existsSync(nft)) {
356+
return []
357+
}
358+
const dependencies = await readJson(nft, 'utf8')
359+
return dependencies.files.map((dep) => resolve(file, dep))
360+
}
361+
336362
const baseServerReplacements: Array<[string, string]> = [
337363
// force manual revalidate during cache fetches
338364
[

0 commit comments

Comments
 (0)