Skip to content

Commit 111967c

Browse files
author
Lukas Holzer
authoredJul 15, 2024··
fix: use heuristics for build and deploy command as well (#6729)
* fix: use heuristics for build and deploy command as well * chore: fix case where no plugins recommended are in the settings * chore: fix comment * chore: add integration tests for build and deploy * chore: prepare test fixture on ci * chore: update min node version for next.js
1 parent e45d378 commit 111967c

File tree

24 files changed

+1149
-52
lines changed

24 files changed

+1149
-52
lines changed
 

‎.github/workflows/integration-tests.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ jobs:
1515
matrix:
1616
os: [ubuntu-latest, macOS-latest, windows-latest]
1717
# Pinning 20.x version as a temporary workaround due to this https://github.com/nodejs/node/issues/52884
18-
node-version: ['18.14.0', '20.12.2', '22']
18+
node-version: ['18.17.0', '20.12.2', '22']
1919
shard: ['1/4', '2/4', '3/4', '4/4']
2020

2121
exclude:
2222
- os: macOS-latest
23-
node-version: '18.14.0'
23+
node-version: '18.17.0'
2424
- os: windows-latest
25-
node-version: '18.14.0'
25+
node-version: '18.17.0'
2626
- os: windows-latest
2727
node-version: '22'
2828
fail-fast: false

‎package-lock.json

+112
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"test:init:cli-help": "npm run start -- --help",
5151
"test:init:eleventy-deps": "cd tests/integration/__fixtures__/eleventy-site && pnpm install --frozen-lockfile",
5252
"test:init:hugo-deps": "npm ci --prefix tests/integration/__fixtures__/hugo-site --no-audit",
53+
"test:init:next-deps": "npm ci --prefix tests/integration/__fixtures__/next-app-without-config --no-audit",
5354
"test:dev:vitest": "vitest run tests/unit/ && vitest run tests/integration",
5455
"test:ci:vitest:unit": "vitest run --coverage tests/unit/",
5556
"test:ci:vitest:integration": "vitest run --coverage tests/integration/",
@@ -201,6 +202,7 @@
201202
"@types/ws": "8.5.10",
202203
"@vitest/coverage-v8": "1.6.0",
203204
"c8": "9.1.0",
205+
"cheerio": "^1.0.0-rc.12",
204206
"eslint-plugin-sort-destructure-keys": "2.0.0",
205207
"eslint-plugin-workspace": "file:./tools/lint-rules",
206208
"fast-glob": "3.3.2",

‎src/commands/build/build.ts

+2-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { OptionValues } from 'commander'
22

33
import { getBuildOptions, runBuild } from '../../lib/build.js'
4-
import { detectFrameworkSettings } from '../../utils/build-info.js'
4+
import { detectFrameworkSettings, getDefaultConfig } from '../../utils/build-info.js'
55
import { error, exit, getToken } from '../../utils/command-helpers.js'
66
import { getEnvelopeEnv } from '../../utils/env/index.js'
77
import BaseCommand from '../base-command.js'
@@ -31,14 +31,9 @@ export const build = async (options: OptionValues, command: BaseCommand) => {
3131
const [token] = await getToken()
3232
const settings = await detectFrameworkSettings(command, 'build')
3333

34-
// override the build command with the detection result if no command is specified through the config
35-
if (!cachedConfig.config.build.command) {
36-
cachedConfig.config.build.command = settings?.buildCommand
37-
cachedConfig.config.build.commandOrigin = 'heuristics'
38-
}
39-
4034
const buildOptions = await getBuildOptions({
4135
cachedConfig,
36+
defaultConfig: getDefaultConfig(settings),
4237
packagePath: command.workspacePackage,
4338
currentDir: command.workingDir,
4439
token,

‎src/commands/deploy/deploy.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { featureFlags as edgeFunctionsFeatureFlags } from '../../lib/edge-functi
1818
import { normalizeFunctionsConfig } from '../../lib/functions/config.js'
1919
import { BACKGROUND_FUNCTIONS_WARNING } from '../../lib/log.js'
2020
import { startSpinner, stopSpinner } from '../../lib/spinner.js'
21+
import { detectFrameworkSettings, getDefaultConfig } from '../../utils/build-info.js'
2122
import {
2223
NETLIFYDEV,
2324
NETLIFYDEVERR,
@@ -558,14 +559,15 @@ const runDeploy = async ({
558559
* @returns
559560
*/
560561
// @ts-expect-error TS(7031) FIXME: Binding element 'cachedConfig' implicitly has an '... Remove this comment to see the full error message
561-
const handleBuild = async ({ cachedConfig, currentDir, deployHandler, options, packagePath }) => {
562+
const handleBuild = async ({ cachedConfig, currentDir, defaultConfig, deployHandler, options, packagePath }) => {
562563
if (!options.build) {
563564
return {}
564565
}
565566
// @ts-expect-error TS(2554) FIXME: Expected 1 arguments, but got 0.
566567
const [token] = await getToken()
567568
const resolvedOptions = await getBuildOptions({
568569
cachedConfig,
570+
defaultConfig,
569571
packagePath,
570572
token,
571573
options,
@@ -784,6 +786,7 @@ export const deploy = async (options: OptionValues, command: BaseCommand) => {
784786
const { workingDir } = command
785787
const { api, site, siteInfo } = command.netlify
786788
const alias = options.alias || options.branch
789+
const settings = await detectFrameworkSettings(command, 'build')
787790

788791
command.setAnalyticsPayload({ open: options.open, prod: options.prod, json: options.json, alias: Boolean(alias) })
789792

@@ -847,6 +850,7 @@ export const deploy = async (options: OptionValues, command: BaseCommand) => {
847850
await handleBuild({
848851
packagePath: command.workspacePackage,
849852
cachedConfig: command.netlify.cachedConfig,
853+
defaultConfig: getDefaultConfig(settings),
850854
currentDir: command.workingDir,
851855
options,
852856
// @ts-expect-error TS(7031) FIXME: Binding element 'netlifyConfig' implicitly has an ... Remove this comment to see the full error message

‎src/lib/build.ts

+3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ export const getBuildOptions = async ({
3535
cachedConfig,
3636
// @ts-expect-error TS(7031) FIXME: Binding element 'currentDir' implicitly has an 'an... Remove this comment to see the full error message
3737
currentDir,
38+
// @ts-expect-error TS(7031) FIXME: Binding element 'defaultConfig' implicitly has an '... Remove this comment to see the full error message
39+
defaultConfig,
3840
// @ts-expect-error TS(7031) FIXME: Binding element 'deployHandler' implicitly has an ... Remove this comment to see the full error message
3941
deployHandler,
4042
// @ts-expect-error TS(7031) FIXME: Binding element 'context' implicitly has an 'any' ... Remove this comment to see the full error message
@@ -71,6 +73,7 @@ export const getBuildOptions = async ({
7173

7274
return {
7375
cachedConfig,
76+
defaultConfig,
7477
siteId: cachedConfig.siteInfo.id,
7578
packagePath,
7679
token,

‎src/utils/build-info.ts

+29
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import fuzzy from 'fuzzy'
44
import inquirer from 'inquirer'
55

66
import BaseCommand from '../commands/base-command.js'
7+
import { $TSFixMe } from '../commands/types.js'
78

89
import { chalk, log } from './command-helpers.js'
910

@@ -122,3 +123,31 @@ command = "${chosenSettings.devCommand}"
122123
return chosenSettings
123124
}
124125
}
126+
127+
/**
128+
* Generates a defaultConfig for @netlify/build based on the settings from the heuristics
129+
* Returns the defaultConfig in the format that @netlify/build expects (json version of toml)
130+
* @param settings The settings from the heuristics
131+
*/
132+
export const getDefaultConfig = (settings?: Settings): $TSFixMe | undefined => {
133+
if (!settings) {
134+
return undefined
135+
}
136+
137+
// TODO: We need proper types for the netlify configuration
138+
const config: $TSFixMe = { build: {} }
139+
140+
if (settings.buildCommand) {
141+
config.build.command = settings.buildCommand
142+
config.build.commandOrigin = 'default'
143+
}
144+
145+
if (settings.dist) {
146+
config.build.publish = settings.dist
147+
config.build.publishOrigin = 'default'
148+
}
149+
150+
config.plugins = settings.plugins_recommended?.map((plugin) => ({ package: plugin })) || []
151+
152+
return config
153+
}

‎src/utils/deploy/deploy-site.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,9 @@ export const deploySite = async (
139139
}
140140

141141
if (functionsWithNativeModules.length !== 0) {
142-
const functionsWithNativeModulesMessage = functionsWithNativeModules.map(({ name }) => `- ${name}`).join('\n')
142+
const functionsWithNativeModulesMessage = functionsWithNativeModules
143+
.map(({ name }: { name: string }) => `- ${name}`)
144+
.join('\n')
143145
warn(`Modules with native dependencies\n
144146
${functionsWithNativeModulesMessage}
145147

‎src/utils/deploy/hash-fns.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ const hashFns = async (
125125
statusCb: $TSFixMe
126126
tmpDir: $TSFixMe
127127
},
128-
) => {
128+
): Promise<$TSFixMe> => {
129129
const {
130130
assetType = 'function',
131131
concurrentHash,
@@ -166,8 +166,8 @@ const hashFns = async (
166166
priority,
167167
runtime,
168168
runtimeVersion,
169-
trafficRules,
170169
timeout,
170+
trafficRules,
171171
}) => ({
172172
filepath: functionPath,
173173
root: tmpDir,

‎src/utils/feature-flags.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@ export const isFeatureFlagEnabled = (flagName: string, siteInfo): boolean =>
2020
export const getFeatureFlagsFromSiteInfo = (siteInfo: {
2121
feature_flags?: Record<string, boolean | string | number>
2222
}): FeatureFlags => ({
23-
...(siteInfo.feature_flags || {}),
23+
...siteInfo.feature_flags,
2424
// see https://github.com/netlify/pod-dev-foundations/issues/581#issuecomment-1731022753
2525
zisi_golang_use_al2: isFeatureFlagEnabled('cli_golang_use_al2', siteInfo),
2626
netlify_build_frameworks_api: true,
27+
project_ceruledge_ui: true,
2728
})
2829

2930
export type FeatureFlags = Record<string, boolean | string | number>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
.yarn/install-state.gz
8+
9+
# testing
10+
/coverage
11+
12+
# next.js
13+
/.next/
14+
/out/
15+
16+
# production
17+
/build
18+
19+
# misc
20+
.DS_Store
21+
*.pem
22+
23+
# debug
24+
npm-debug.log*
25+
yarn-debug.log*
26+
yarn-error.log*
27+
28+
# local env files
29+
.env*.local
30+
31+
# vercel
32+
.vercel
33+
34+
# typescript
35+
*.tsbuildinfo
36+
next-env.d.ts
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
:root {
2+
--max-width: 1100px;
3+
--border-radius: 12px;
4+
--font-mono: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono",
5+
"Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro",
6+
"Fira Mono", "Droid Sans Mono", "Courier New", monospace;
7+
8+
--foreground-rgb: 0, 0, 0;
9+
--background-start-rgb: 214, 219, 220;
10+
--background-end-rgb: 255, 255, 255;
11+
12+
--primary-glow: conic-gradient(
13+
from 180deg at 50% 50%,
14+
#16abff33 0deg,
15+
#0885ff33 55deg,
16+
#54d6ff33 120deg,
17+
#0071ff33 160deg,
18+
transparent 360deg
19+
);
20+
--secondary-glow: radial-gradient(
21+
rgba(255, 255, 255, 1),
22+
rgba(255, 255, 255, 0)
23+
);
24+
25+
--tile-start-rgb: 239, 245, 249;
26+
--tile-end-rgb: 228, 232, 233;
27+
--tile-border: conic-gradient(
28+
#00000080,
29+
#00000040,
30+
#00000030,
31+
#00000020,
32+
#00000010,
33+
#00000010,
34+
#00000080
35+
);
36+
37+
--callout-rgb: 238, 240, 241;
38+
--callout-border-rgb: 172, 175, 176;
39+
--card-rgb: 180, 185, 188;
40+
--card-border-rgb: 131, 134, 135;
41+
}
42+
43+
@media (prefers-color-scheme: dark) {
44+
:root {
45+
--foreground-rgb: 255, 255, 255;
46+
--background-start-rgb: 0, 0, 0;
47+
--background-end-rgb: 0, 0, 0;
48+
49+
--primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0));
50+
--secondary-glow: linear-gradient(
51+
to bottom right,
52+
rgba(1, 65, 255, 0),
53+
rgba(1, 65, 255, 0),
54+
rgba(1, 65, 255, 0.3)
55+
);
56+
57+
--tile-start-rgb: 2, 13, 46;
58+
--tile-end-rgb: 2, 5, 19;
59+
--tile-border: conic-gradient(
60+
#ffffff80,
61+
#ffffff40,
62+
#ffffff30,
63+
#ffffff20,
64+
#ffffff10,
65+
#ffffff10,
66+
#ffffff80
67+
);
68+
69+
--callout-rgb: 20, 20, 20;
70+
--callout-border-rgb: 108, 108, 108;
71+
--card-rgb: 100, 100, 100;
72+
--card-border-rgb: 200, 200, 200;
73+
}
74+
}
75+
76+
* {
77+
box-sizing: border-box;
78+
padding: 0;
79+
margin: 0;
80+
}
81+
82+
html,
83+
body {
84+
max-width: 100vw;
85+
overflow-x: hidden;
86+
}
87+
88+
body {
89+
color: rgb(var(--foreground-rgb));
90+
background: linear-gradient(
91+
to bottom,
92+
transparent,
93+
rgb(var(--background-end-rgb))
94+
)
95+
rgb(var(--background-start-rgb));
96+
}
97+
98+
a {
99+
color: inherit;
100+
text-decoration: none;
101+
}
102+
103+
@media (prefers-color-scheme: dark) {
104+
html {
105+
color-scheme: dark;
106+
}
107+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Inter } from 'next/font/google'
2+
import './globals.css'
3+
4+
const inter = Inter({ subsets: ['latin'] })
5+
6+
export const metadata = {
7+
title: 'Create Next App',
8+
description: 'Generated by create next app',
9+
}
10+
11+
export default function RootLayout({ children }) {
12+
return (
13+
<html lang="en">
14+
<body className={inter.className}>{children}</body>
15+
</html>
16+
)
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import Image from 'next/image'
2+
import styles from './page.module.css'
3+
4+
export default function Home() {
5+
return (
6+
<main className={styles.main}>
7+
<div className={styles.description}>
8+
<p>
9+
Get started by editing&nbsp;
10+
<code className={styles.code}>app/page.js</code>
11+
</p>
12+
<div>
13+
<a
14+
href="https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
15+
target="_blank"
16+
rel="noopener noreferrer"
17+
>
18+
By{' '}
19+
<Image src="/vercel.svg" alt="Vercel Logo" className={styles.vercelLogo} width={100} height={24} priority />
20+
</a>
21+
</div>
22+
</div>
23+
24+
<div className={styles.center}>
25+
<Image className={styles.logo} src="/next.svg" alt="Next.js Logo" width={180} height={37} priority />
26+
</div>
27+
28+
<div className={styles.grid}>
29+
<a
30+
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
31+
className={styles.card}
32+
target="_blank"
33+
rel="noopener noreferrer"
34+
>
35+
<h2>
36+
Docs <span>-&gt;</span>
37+
</h2>
38+
<p>Find in-depth information about Next.js features and API.</p>
39+
</a>
40+
41+
<a
42+
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
43+
className={styles.card}
44+
target="_blank"
45+
rel="noopener noreferrer"
46+
>
47+
<h2>
48+
Learn <span>-&gt;</span>
49+
</h2>
50+
<p>Learn about Next.js in an interactive course with&nbsp;quizzes!</p>
51+
</a>
52+
53+
<a
54+
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
55+
className={styles.card}
56+
target="_blank"
57+
rel="noopener noreferrer"
58+
>
59+
<h2>
60+
Templates <span>-&gt;</span>
61+
</h2>
62+
<p>Explore starter templates for Next.js.</p>
63+
</a>
64+
65+
<a
66+
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
67+
className={styles.card}
68+
target="_blank"
69+
rel="noopener noreferrer"
70+
>
71+
<h2>
72+
Deploy <span>-&gt;</span>
73+
</h2>
74+
<p>Instantly deploy your Next.js site to a shareable URL with Vercel.</p>
75+
</a>
76+
</div>
77+
</main>
78+
)
79+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
.main {
2+
display: flex;
3+
flex-direction: column;
4+
justify-content: space-between;
5+
align-items: center;
6+
padding: 6rem;
7+
min-height: 100vh;
8+
}
9+
10+
.description {
11+
display: inherit;
12+
justify-content: inherit;
13+
align-items: inherit;
14+
font-size: 0.85rem;
15+
max-width: var(--max-width);
16+
width: 100%;
17+
z-index: 2;
18+
font-family: var(--font-mono);
19+
}
20+
21+
.description a {
22+
display: flex;
23+
justify-content: center;
24+
align-items: center;
25+
gap: 0.5rem;
26+
}
27+
28+
.description p {
29+
position: relative;
30+
margin: 0;
31+
padding: 1rem;
32+
background-color: rgba(var(--callout-rgb), 0.5);
33+
border: 1px solid rgba(var(--callout-border-rgb), 0.3);
34+
border-radius: var(--border-radius);
35+
}
36+
37+
.code {
38+
font-weight: 700;
39+
font-family: var(--font-mono);
40+
}
41+
42+
.grid {
43+
display: grid;
44+
grid-template-columns: repeat(4, minmax(25%, auto));
45+
max-width: 100%;
46+
width: var(--max-width);
47+
}
48+
49+
.card {
50+
padding: 1rem 1.2rem;
51+
border-radius: var(--border-radius);
52+
background: rgba(var(--card-rgb), 0);
53+
border: 1px solid rgba(var(--card-border-rgb), 0);
54+
transition: background 200ms, border 200ms;
55+
}
56+
57+
.card span {
58+
display: inline-block;
59+
transition: transform 200ms;
60+
}
61+
62+
.card h2 {
63+
font-weight: 600;
64+
margin-bottom: 0.7rem;
65+
}
66+
67+
.card p {
68+
margin: 0;
69+
opacity: 0.6;
70+
font-size: 0.9rem;
71+
line-height: 1.5;
72+
max-width: 30ch;
73+
text-wrap: balance;
74+
}
75+
76+
.center {
77+
display: flex;
78+
justify-content: center;
79+
align-items: center;
80+
position: relative;
81+
padding: 4rem 0;
82+
}
83+
84+
.center::before {
85+
background: var(--secondary-glow);
86+
border-radius: 50%;
87+
width: 480px;
88+
height: 360px;
89+
margin-left: -400px;
90+
}
91+
92+
.center::after {
93+
background: var(--primary-glow);
94+
width: 240px;
95+
height: 180px;
96+
z-index: -1;
97+
}
98+
99+
.center::before,
100+
.center::after {
101+
content: "";
102+
left: 50%;
103+
position: absolute;
104+
filter: blur(45px);
105+
transform: translateZ(0);
106+
}
107+
108+
.logo {
109+
position: relative;
110+
}
111+
/* Enable hover only on non-touch devices */
112+
@media (hover: hover) and (pointer: fine) {
113+
.card:hover {
114+
background: rgba(var(--card-rgb), 0.1);
115+
border: 1px solid rgba(var(--card-border-rgb), 0.15);
116+
}
117+
118+
.card:hover span {
119+
transform: translateX(4px);
120+
}
121+
}
122+
123+
@media (prefers-reduced-motion) {
124+
.card:hover span {
125+
transform: none;
126+
}
127+
}
128+
129+
/* Mobile */
130+
@media (max-width: 700px) {
131+
.content {
132+
padding: 4rem;
133+
}
134+
135+
.grid {
136+
grid-template-columns: 1fr;
137+
margin-bottom: 120px;
138+
max-width: 320px;
139+
text-align: center;
140+
}
141+
142+
.card {
143+
padding: 1rem 2.5rem;
144+
}
145+
146+
.card h2 {
147+
margin-bottom: 0.5rem;
148+
}
149+
150+
.center {
151+
padding: 8rem 0 6rem;
152+
}
153+
154+
.center::before {
155+
transform: none;
156+
height: 300px;
157+
}
158+
159+
.description {
160+
font-size: 0.8rem;
161+
}
162+
163+
.description a {
164+
padding: 1rem;
165+
}
166+
167+
.description p,
168+
.description div {
169+
display: flex;
170+
justify-content: center;
171+
position: fixed;
172+
width: 100%;
173+
}
174+
175+
.description p {
176+
align-items: center;
177+
inset: 0 0 auto;
178+
padding: 2rem 1rem 1.4rem;
179+
border-radius: 0;
180+
border: none;
181+
border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
182+
background: linear-gradient(
183+
to bottom,
184+
rgba(var(--background-start-rgb), 1),
185+
rgba(var(--callout-rgb), 0.5)
186+
);
187+
background-clip: padding-box;
188+
backdrop-filter: blur(24px);
189+
}
190+
191+
.description div {
192+
align-items: flex-end;
193+
pointer-events: none;
194+
inset: auto 0 0;
195+
padding: 2rem;
196+
height: 200px;
197+
background: linear-gradient(
198+
to bottom,
199+
transparent 0%,
200+
rgb(var(--background-end-rgb)) 40%
201+
);
202+
z-index: 1;
203+
}
204+
}
205+
206+
/* Tablet and Smaller Desktop */
207+
@media (min-width: 701px) and (max-width: 1120px) {
208+
.grid {
209+
grid-template-columns: repeat(2, 50%);
210+
}
211+
}
212+
213+
@media (prefers-color-scheme: dark) {
214+
.vercelLogo {
215+
filter: invert(1);
216+
}
217+
218+
.logo {
219+
filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
220+
}
221+
}
222+
223+
@keyframes rotate {
224+
from {
225+
transform: rotate(360deg);
226+
}
227+
to {
228+
transform: rotate(0deg);
229+
}
230+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"compilerOptions": {
3+
"paths": {
4+
"@/*": ["./*"]
5+
}
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/** @type {import('next').NextConfig} */
2+
const nextConfig = {}
3+
4+
export default nextConfig

‎tests/integration/__fixtures__/next-app-without-config/package-lock.json

+399
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "next-app-without-config",
3+
"version": "0.1.0",
4+
"private": true,
5+
"scripts": {
6+
"dev": "next dev",
7+
"build": "next build",
8+
"start": "next start",
9+
"lint": "next lint"
10+
},
11+
"dependencies": {
12+
"react": "^18",
13+
"react-dom": "^18",
14+
"next": "14.2.5"
15+
}
16+
}
Loading
Loading

‎tests/integration/commands/build/build.test.js ‎tests/integration/commands/build/build.test.ts

+26-3
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import path from 'path'
22
import process from 'process'
33

44
import execa from 'execa'
5-
import { describe, test } from 'vitest'
5+
import { describe, test, expect } from 'vitest'
66

7+
import { callCli } from '../../utils/call-cli.js'
78
import { cliPath } from '../../utils/cli-path.js'
9+
import { FixtureTestContext, setupFixtureTests } from '../../utils/fixture.ts'
810
import { withMockApi } from '../../utils/mock-api.js'
911
import { withSiteBuilder } from '../../utils/site-builder.ts'
1012

@@ -19,8 +21,15 @@ const defaultEnvs = {
1921
const runBuildCommand = async function (
2022
t,
2123
cwd,
22-
{ apiUrl, env = defaultEnvs, exitCode: expectedExitCode = 0, flags = [], output: outputs } = {},
24+
options: Partial<{
25+
exitCode: number
26+
flags: string[]
27+
output: any
28+
env: Record<string, string>
29+
apiUrl: string
30+
}> = {},
2331
) {
32+
let { apiUrl, env = defaultEnvs, exitCode: expectedExitCode = 0, flags = [], output: outputs } = options
2433
const { all, exitCode } = await execa(cliPath, ['build', ...flags], {
2534
reject: false,
2635
cwd,
@@ -42,7 +51,7 @@ const runBuildCommand = async function (
4251
if (output instanceof RegExp) {
4352
t.expect(all).toMatch(output)
4453
} else {
45-
t.expect(all.includes(output), `Output of build command does not include '${output}'`).toBe(true)
54+
t.expect(all?.includes(output), `Output of build command does not include '${output}'`).toBe(true)
4655
}
4756
})
4857
t.expect(exitCode).toBe(expectedExitCode)
@@ -313,4 +322,18 @@ describe.concurrent('command/build', () => {
313322
})
314323
})
315324
})
325+
326+
setupFixtureTests('next-app-without-config', () => {
327+
test<FixtureTestContext>('should run build without any netlify specific configuration and install auto detected plugins', async ({
328+
fixture,
329+
}) => {
330+
const output = await callCli(['build', '--offline'], { cwd: fixture.directory })
331+
332+
// expect on the output that it installed the next runtime (auto detected the plugin + the build command and therefore had functions to bundle)
333+
expect(output).toMatch(/ Using Next.js Runtime -/)
334+
expect(output).toMatch(/\$ npm run build/)
335+
expect(output).toMatch(/Functions bundling completed/)
336+
expect(output).toMatch(/Edge Functions bundling completed/)
337+
})
338+
})
316339
})

‎tests/integration/commands/deploy/deploy.test.js ‎tests/integration/commands/deploy/deploy.test.ts

+63-34
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import path from 'path'
22
import process from 'process'
33
import { fileURLToPath } from 'url'
44

5+
import { load } from 'cheerio'
56
import execa from 'execa'
67
import fetch from 'node-fetch'
7-
import { afterAll, beforeAll, describe, test } from 'vitest'
8+
import { afterAll, beforeAll, describe, expect, test } from 'vitest'
89

910
import { callCli } from '../../utils/call-cli.js'
1011
import { createLiveTestSite, generateSiteName } from '../../utils/create-live-test-site.js'
12+
import { FixtureTestContext, setupFixtureTests } from '../../utils/fixture.js'
1113
import { pause } from '../../utils/pause.js'
1214
import { withSiteBuilder } from '../../utils/site-builder.ts'
1315

@@ -16,29 +18,41 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url))
1618

1719
const SITE_NAME = generateSiteName('netlify-test-deploy-')
1820

19-
// eslint-disable-next-line no-shadow
20-
const validateContent = async ({ content, path, siteUrl, t }) => {
21-
const response = await fetch(`${siteUrl}${path}`)
21+
const validateContent = async ({ content, path: pathname, siteUrl }) => {
22+
const response = await fetch(`${siteUrl}${pathname}`)
2223
const body = await response.text()
2324
if (content === undefined) {
24-
t.expect(response.status).toBe(404)
25+
expect(response.status).toBe(404)
2526
return
2627
}
27-
t.expect(response.status, `status should be 200. request id: ${response.headers.get('x-nf-request-id')}`).toBe(200)
28-
t.expect(body, `body should be as expected. request id: ${response.headers.get('x-nf-request-id')}`).toEqual(content)
28+
expect(response.status, `status should be 200. request id: ${response.headers.get('x-nf-request-id')}`).toBe(200)
29+
expect(body, `body should be as expected. request id: ${response.headers.get('x-nf-request-id')}`).toEqual(content)
2930
}
3031

31-
const validateDeploy = async ({ content, contentMessage, deploy, siteName, t }) => {
32-
t.expect(deploy.site_name).toBeTruthy()
33-
t.expect(deploy.deploy_url).toBeTruthy()
34-
t.expect(deploy.deploy_id).toBeTruthy()
35-
t.expect(deploy.logs).toBeTruthy()
36-
t.expect(deploy.site_name, contentMessage).toEqual(siteName)
37-
38-
await validateContent({ siteUrl: deploy.deploy_url, path: '', content, t })
32+
const validateDeploy = async ({
33+
content,
34+
contentMessage,
35+
deploy,
36+
siteName,
37+
}: {
38+
contentMessage?: string
39+
siteName: string
40+
content?: string
41+
deploy: { site_name: string; deploy_url: string; deploy_id: string; logs: string }
42+
}) => {
43+
expect(deploy.site_name).toBeTruthy()
44+
expect(deploy.deploy_url).toBeTruthy()
45+
expect(deploy.deploy_id).toBeTruthy()
46+
expect(deploy.logs).toBeTruthy()
47+
expect(deploy.site_name, contentMessage).toEqual(siteName)
48+
49+
await validateContent({ siteUrl: deploy.deploy_url, path: '', content })
3950
}
4051

41-
const context = {}
52+
const context: { account: unknown; siteId: string } = {
53+
siteId: '',
54+
account: undefined,
55+
}
4256

4357
describe.skipIf(process.env.NETLIFY_TEST_DISABLE_LIVE === 'true').concurrent('commands/deploy', () => {
4458
beforeAll(async () => {
@@ -68,7 +82,7 @@ describe.skipIf(process.env.NETLIFY_TEST_DISABLE_LIVE === 'true').concurrent('co
6882
env: { NETLIFY_SITE_ID: context.siteId },
6983
}).then((output) => JSON.parse(output))
7084

71-
await validateDeploy({ deploy, siteName: SITE_NAME, content, t })
85+
await validateDeploy({ deploy, siteName: SITE_NAME, content })
7286
})
7387
})
7488

@@ -92,7 +106,7 @@ describe.skipIf(process.env.NETLIFY_TEST_DISABLE_LIVE === 'true').concurrent('co
92106
cwd: builder.directory,
93107
}).then((output) => JSON.parse(output))
94108

95-
await validateDeploy({ deploy, siteName: SITE_NAME, content, t })
109+
await validateDeploy({ deploy, siteName: SITE_NAME, content })
96110
})
97111
})
98112

@@ -117,7 +131,7 @@ describe.skipIf(process.env.NETLIFY_TEST_DISABLE_LIVE === 'true').concurrent('co
117131
env: { NETLIFY_SITE_ID: context.siteId },
118132
}).then((output) => JSON.parse(output))
119133

120-
await validateDeploy({ deploy, siteName: SITE_NAME, content, t })
134+
await validateDeploy({ deploy, siteName: SITE_NAME, content })
121135
})
122136
})
123137

@@ -158,7 +172,6 @@ describe.skipIf(process.env.NETLIFY_TEST_DISABLE_LIVE === 'true').concurrent('co
158172
siteName: SITE_NAME,
159173
content: 'Edge Function works',
160174
contentMessage: 'Edge function did not execute correctly or was not deployed correctly',
161-
t,
162175
})
163176
})
164177
})
@@ -205,7 +218,6 @@ describe.skipIf(process.env.NETLIFY_TEST_DISABLE_LIVE === 'true').concurrent('co
205218
siteName: SITE_NAME,
206219
content: 'Edge Function works',
207220
contentMessage: 'Edge function did not execute correctly or was not deployed correctly',
208-
t,
209221
})
210222
})
211223
})
@@ -228,7 +240,7 @@ describe.skipIf(process.env.NETLIFY_TEST_DISABLE_LIVE === 'true').concurrent('co
228240
name: 'log-env',
229241
plugin: {
230242
async onSuccess() {
231-
// eslint-disable-next-line n/global-require, no-undef
243+
// eslint-disable-next-line n/global-require, @typescript-eslint/no-var-requires
232244
const { DEPLOY_ID, DEPLOY_URL } = require('process').env
233245
console.log(`DEPLOY_ID: ${DEPLOY_ID}`)
234246
console.log(`DEPLOY_URL: ${DEPLOY_URL}`)
@@ -310,24 +322,21 @@ describe.skipIf(process.env.NETLIFY_TEST_DISABLE_LIVE === 'true').concurrent('co
310322
env: { NETLIFY_SITE_ID: context.siteId },
311323
}).then((output) => JSON.parse(output))
312324

313-
await validateDeploy({ deploy, siteName: SITE_NAME, content: 'index', t })
325+
await validateDeploy({ deploy, siteName: SITE_NAME, content: 'index' })
314326
await validateContent({
315327
siteUrl: deploy.deploy_url,
316328
content: undefined,
317329
path: '/.hidden-file',
318-
t,
319330
})
320331
await validateContent({
321332
siteUrl: deploy.deploy_url,
322333
content: undefined,
323334
path: '/.hidden-dir',
324-
t,
325335
})
326336
await validateContent({
327337
siteUrl: deploy.deploy_url,
328338
content: undefined,
329339
path: '/__MACOSX',
330-
t,
331340
})
332341
})
333342
})
@@ -358,12 +367,11 @@ describe.skipIf(process.env.NETLIFY_TEST_DISABLE_LIVE === 'true').concurrent('co
358367
env: { NETLIFY_SITE_ID: context.siteId },
359368
}).then((output) => JSON.parse(output))
360369

361-
await validateDeploy({ deploy, siteName: SITE_NAME, content: 'index', t })
370+
await validateDeploy({ deploy, siteName: SITE_NAME, content: 'index' })
362371
await validateContent({
363372
siteUrl: deploy.deploy_url,
364373
content: undefined,
365374
path: '/node_modules/package.json',
366-
t,
367375
})
368376
})
369377
})
@@ -394,12 +402,11 @@ describe.skipIf(process.env.NETLIFY_TEST_DISABLE_LIVE === 'true').concurrent('co
394402
env: { NETLIFY_SITE_ID: context.siteId },
395403
}).then((output) => JSON.parse(output))
396404

397-
await validateDeploy({ deploy, siteName: SITE_NAME, content: 'index', t })
405+
await validateDeploy({ deploy, siteName: SITE_NAME, content: 'index' })
398406
await validateContent({
399407
siteUrl: deploy.deploy_url,
400408
content: '{}',
401409
path: '/node_modules/package.json',
402-
t,
403410
})
404411
})
405412
})
@@ -414,7 +421,7 @@ describe.skipIf(process.env.NETLIFY_TEST_DISABLE_LIVE === 'true').concurrent('co
414421
env: { NETLIFY_SITE_ID: context.siteId },
415422
})
416423
} catch (error) {
417-
t.expect(error.stderr.includes('Error: No files or functions to deploy')).toBe(true)
424+
expect(error.stderr.includes('Error: No files or functions to deploy')).toBe(true)
418425
}
419426
})
420427
})
@@ -437,7 +444,7 @@ describe.skipIf(process.env.NETLIFY_TEST_DISABLE_LIVE === 'true').concurrent('co
437444
name: 'mutator',
438445
plugin: {
439446
onPreBuild: async ({ netlifyConfig }) => {
440-
// eslint-disable-next-line no-undef, n/global-require
447+
// eslint-disable-next-line n/global-require, @typescript-eslint/no-var-requires
441448
const { mkdir, writeFile } = require('fs/promises')
442449

443450
const generatedFunctionsDir = 'new_functions'
@@ -690,7 +697,7 @@ describe.skipIf(process.env.NETLIFY_TEST_DISABLE_LIVE === 'true').concurrent('co
690697
const redirectsMessage = fullDeploy.summary.messages.find(({ title }) => title === '3 redirect rules processed')
691698
t.expect(redirectsMessage.description).toEqual('All redirect rules deployed without errors.')
692699

693-
await validateDeploy({ deploy, siteName: SITE_NAME, content, t })
700+
await validateDeploy({ deploy, siteName: SITE_NAME, content })
694701

695702
const [pluginRedirectResponse, _redirectsResponse, netlifyTomResponse] = await Promise.all([
696703
fetch(`${deploy.deploy_url}/other-api/hello`).then((res) => res.text()),
@@ -762,7 +769,7 @@ describe.skipIf(process.env.NETLIFY_TEST_DISABLE_LIVE === 'true').concurrent('co
762769
true,
763770
)
764771
const response = await fetch(`${deployUrl}/.netlify/functions/bundled-function-1`).then((res) => res.text())
765-
t.expect(response).toEqual('Pre-bundled')
772+
expect(response).toEqual('Pre-bundled')
766773
})
767774
})
768775

@@ -945,4 +952,26 @@ describe.skipIf(process.env.NETLIFY_TEST_DISABLE_LIVE === 'true').concurrent('co
945952
t.expect(response).toEqual('hello from the blob')
946953
})
947954
})
955+
956+
setupFixtureTests('next-app-without-config', () => {
957+
test<FixtureTestContext>('should run deploy with --build without any netlify specific configuration', async ({
958+
fixture,
959+
}) => {
960+
const { deploy_url: deployUrl } = await callCli(
961+
['deploy', '--build', '--json'],
962+
{
963+
cwd: fixture.directory,
964+
env: { NETLIFY_SITE_ID: context.siteId },
965+
},
966+
true,
967+
)
968+
969+
const html = await fetch(deployUrl).then((res) => res.text())
970+
// eslint-disable-next-line id-length
971+
const $ = load(html)
972+
973+
expect($('title').text()).toEqual('Create Next App')
974+
expect($('img[alt="Next.js Logo"]').attr('src')).toBe('/next.svg')
975+
})
976+
})
948977
})

2 commit comments

Comments
 (2)

github-actions[bot] commented on Jul 15, 2024

@github-actions[bot]

📊 Benchmark results

  • Dependency count: 1,212
  • Package size: 313 MB
  • Number of ts-expect-error directives: 977

github-actions[bot] commented on Jul 15, 2024

@github-actions[bot]

📊 Benchmark results

  • Dependency count: 1,212
  • Package size: 313 MB
  • Number of ts-expect-error directives: 977
Please sign in to comment.