Skip to content

Commit

Permalink
feat: fail build when netlify form detected (#2512)
Browse files Browse the repository at this point in the history
* feat: verify no netlify forms

* test: ensure build is failed when netlify forms detected

* chore: format with prettier

* feat: make verification passive by outputing warning

* feat: include static content for forms verification

* test: skip forms verification test until we are failing the build

* feat: also verify no netlify forms for APP_PAGE

* Update tests/integration/netlify-forms.test.ts

Co-authored-by: Michal Piechowiak <[email protected]>

---------

Co-authored-by: orinokai <[email protected]>
Co-authored-by: Michal Piechowiak <[email protected]>
  • Loading branch information
3 people authored Jun 27, 2024
1 parent 62ab214 commit 195b8b2
Show file tree
Hide file tree
Showing 11 changed files with 134 additions and 7 deletions.
6 changes: 6 additions & 0 deletions src/build/content/prerendered.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type {
NetlifyIncrementalCacheValue,
} from '../../shared/cache-types.cjs'
import type { PluginContext } from '../plugin-context.js'
import { verifyNoNetlifyForms } from '../verification.js'

const tracer = wrapTracer(trace.getTracer('Next runtime'))

Expand Down Expand Up @@ -169,6 +170,11 @@ export const copyPrerenderedContent = async (ctx: PluginContext): Promise<void>
throw new Error(`Unrecognized content: ${route}`)
}

// Netlify Forms are not support and require a workaround
if (value.kind === 'PAGE' || value.kind === 'APP_PAGE') {
verifyNoNetlifyForms(ctx, value.html)
}

await writeCacheEntry(key, value, lastModified, ctx)
}),
),
Expand Down
11 changes: 6 additions & 5 deletions src/build/content/static.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { existsSync } from 'node:fs'
import { cp, mkdir, rename, rm } from 'node:fs/promises'
import { cp, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises'
import { basename, join } from 'node:path'

import { trace } from '@opentelemetry/api'
Expand All @@ -8,6 +8,7 @@ import glob from 'fast-glob'

import { encodeBlobKey } from '../../shared/blobkey.js'
import { PluginContext } from '../plugin-context.js'
import { verifyNoNetlifyForms } from '../verification.js'

const tracer = wrapTracer(trace.getTracer('Next runtime'))

Expand All @@ -25,14 +26,14 @@ export const copyStaticContent = async (ctx: PluginContext): Promise<void> => {
})

try {
await mkdir(destDir, { recursive: true })
await Promise.all(
paths
.filter((path) => !paths.includes(`${path.slice(0, -5)}.json`))
.map(async (path): Promise<void> => {
await cp(join(srcDir, path), join(destDir, await encodeBlobKey(path)), {
recursive: true,
force: true,
})
const html = await readFile(join(srcDir, path), 'utf-8')
verifyNoNetlifyForms(ctx, html)
await writeFile(join(destDir, await encodeBlobKey(path)), html, 'utf-8')
}),
)
} catch (error) {
Expand Down
11 changes: 11 additions & 0 deletions src/build/verification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import type { PluginContext } from './plugin-context.js'

const SUPPORTED_NEXT_VERSIONS = '>=13.5.0'

const warnings = new Set<string>()

export function verifyPublishDir(ctx: PluginContext) {
if (!existsSync(ctx.publishDir)) {
ctx.failBuild(
Expand Down Expand Up @@ -85,3 +87,12 @@ export async function verifyNoAdvancedAPIRoutes(ctx: PluginContext) {
)
}
}

export function verifyNoNetlifyForms(ctx: PluginContext, html: string) {
if (!warnings.has('netlifyForms') && /<form[^>]*?\s(netlify|data-netlify)[=>\s]/.test(html)) {
console.warn(
'@netlify/plugin-next@5 does not support Netlify Forms. Refer to https://ntl.fyi/next-runtime-forms-migration for migration example.',
)
warnings.add('netlifyForms')
}
}
12 changes: 12 additions & 0 deletions tests/fixtures/netlify-forms/app/layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export const metadata = {
title: 'Netlify Forms',
description: 'Test for verifying Netlify Forms',
}

export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
7 changes: 7 additions & 0 deletions tests/fixtures/netlify-forms/app/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function Page() {
return (
<form data-netlify="true">
<button type="submit">Send</button>
</form>
)
}
5 changes: 5 additions & 0 deletions tests/fixtures/netlify-forms/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
10 changes: 10 additions & 0 deletions tests/fixtures/netlify-forms/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
eslint: {
ignoreDuringBuilds: true,
},
generateBuildId: () => 'build-id',
}

module.exports = nextConfig
19 changes: 19 additions & 0 deletions tests/fixtures/netlify-forms/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "netlify-forms",
"version": "0.1.0",
"private": true,
"scripts": {
"postinstall": "next build",
"dev": "next dev",
"build": "next build"
},
"dependencies": {
"@netlify/functions": "^2.7.0",
"next": "latest",
"react": "18.2.0",
"react-dom": "18.2.0"
},
"devDependencies": {
"@types/react": "18.2.75"
}
}
23 changes: 23 additions & 0 deletions tests/fixtures/netlify-forms/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"noEmit": true,
"incremental": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"plugins": [
{
"name": "next"
}
]
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
5 changes: 3 additions & 2 deletions tests/integration/advanced-api-routes.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { getLogger } from 'lambda-local'
import { v4 } from 'uuid'
import { beforeEach, vi, it, expect } from 'vitest'
import { createFixture, runPlugin, type FixtureTestContext } from '../utils/fixture.js'
import { beforeEach, expect, it, vi } from 'vitest'
import { type FixtureTestContext } from '../utils/contexts.js'
import { createFixture, runPlugin } from '../utils/fixture.js'
import { generateRandomObjectID, startMockBlobStore } from '../utils/helpers.js'

getLogger().level = 'alert'
Expand Down
32 changes: 32 additions & 0 deletions tests/integration/netlify-forms.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { getLogger } from 'lambda-local'
import { v4 } from 'uuid'
import { beforeEach, expect, it, vi } from 'vitest'
import { type FixtureTestContext } from '../utils/contexts.js'
import { createFixture, runPlugin } from '../utils/fixture.js'
import { generateRandomObjectID, startMockBlobStore } from '../utils/helpers.js'

getLogger().level = 'alert'

beforeEach<FixtureTestContext>(async (ctx) => {
// set for each test a new deployID and siteID
ctx.deployID = generateRandomObjectID()
ctx.siteID = v4()
vi.stubEnv('SITE_ID', ctx.siteID)
vi.stubEnv('DEPLOY_ID', ctx.deployID)
vi.stubEnv('NETLIFY_PURGE_API_TOKEN', 'fake-token')
// hide debug logs in tests
// vi.spyOn(console, 'debug').mockImplementation(() => {})

await startMockBlobStore(ctx)
})

// test skipped until we actually start failing builds - right now we are just showing a warning
it.skip<FixtureTestContext>('should fail build when netlify forms are used', async (ctx) => {
await createFixture('netlify-forms', ctx)

const runPluginPromise = runPlugin(ctx)

await expect(runPluginPromise).rejects.toThrow(
'@netlify/plugin-next@5 does not support Netlify Forms',
)
})

0 comments on commit 195b8b2

Please sign in to comment.