Skip to content

Commit

Permalink
next-upgrade: prompt (un)install only when there's a change (#71308)
Browse files Browse the repository at this point in the history
### Why?

When running next-upgrade, `next-request-geo-ip` requires to install
`@vercel/functions` and `built-in-next-font` makes `@next/font` no
longer needed. We added a prompt to ask user whether to (un)install it
at #71038 but it prompts even if
there were no transform occurred.

This is unnecessary layer so we only prompt when there's a change caused
from the codemod.

### No Modification


https://github.com/user-attachments/assets/3acfdb24-c8e7-4189-9c81-4765ce748822

### With Modification


https://github.com/user-attachments/assets/f44a8b1c-c75a-4446-afc8-f74111de939d

---------
  • Loading branch information
devjiwonchoi authored Oct 21, 2024
1 parent 19e2edc commit 26a2bab
Show file tree
Hide file tree
Showing 10 changed files with 138 additions and 30 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ packages/react-dev-overlay/lib/**
.github/actions/next-stats-action/.work
packages/next-codemod/transforms/__testfixtures__/**/*
packages/next-codemod/transforms/__tests__/**/*
packages/next-codemod/bin/__testfixtures__/**/*
packages/next-codemod/**/*.js
packages/next-codemod/**/*.d.ts
packages/next-env/**/*.d.ts
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Installs `@vercel/functions`
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// @ts-nocheck
import { type NextRequest, NextResponse } from 'next/server'

export function GET(request: NextRequest) {
const geo = request.geo
const ip = request.ip
return NextResponse.json({ geo, ip })
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "geo-ip-usage",
"scripts": {
"dev": "next dev --turbo"
},
"dependencies": {
"next": "15.0.0-canary.152",
"react": "19.0.0-rc-94e652d5-20240912",
"react-dom": "19.0.0-rc-94e652d5-20240912"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Should not install `@vercel/functions`
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Page() {
return null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "no-geo-ip-usage",
"scripts": {
"dev": "next dev --turbo"
},
"dependencies": {
"next": "15.0.0-canary.152",
"react": "19.0.0-rc-94e652d5-20240912",
"react-dom": "19.0.0-rc-94e652d5-20240912"
}
}
117 changes: 93 additions & 24 deletions packages/next-codemod/bin/transform.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import execa from 'execa'
import globby from 'globby'
import prompts from 'prompts'
import stripAnsi from 'strip-ansi'
import { join } from 'node:path'
import { installPackages, uninstallPackage } from '../lib/handle-package'
import {
Expand Down Expand Up @@ -79,6 +80,25 @@ export async function runTransform(
transformer = res.transformer
}

if (transformer === 'next-request-geo-ip') {
const { isAppDeployedToVercel } = await prompts(
{
type: 'confirm',
name: 'isAppDeployedToVercel',
message:
'Is your app deployed to Vercel? (Required to apply the selected codemod)',
initial: true,
},
{ onCancel }
)
if (!isAppDeployedToVercel) {
console.log(
'Skipping codemod "next-request-geo-ip" as your app is not deployed to Vercel.'
)
return
}
}

const filesExpanded = expandFilePathsIfNeeded([directory])

if (!filesExpanded.length) {
Expand Down Expand Up @@ -126,40 +146,89 @@ export async function runTransform(

console.log(`Executing command: jscodeshift ${args.join(' ')}`)

const result = execa.sync(jscodeshiftExecutable, args, {
stdio: 'inherit',
stripFinalNewline: false,
const execaChildProcess = execa(jscodeshiftExecutable, args, {
// include ANSI color codes
// Note: execa merges env with existing env by default.
env: process.stdout.isTTY ? { FORCE_COLOR: 'true' } : {},
})

if (result.failed) {
throw new Error(`jscodeshift exited with code ${result.exitCode}`)
}
// "\n" + "a\n" + "b\n"
let lastThreeLineBreaks = ''

if (!dry && transformer === 'built-in-next-font') {
const { uninstallNextFont } = await prompts({
type: 'confirm',
name: 'uninstallNextFont',
message: 'Do you want to uninstall `@next/font`?',
initial: true,
if (execaChildProcess.stdout) {
execaChildProcess.stdout.pipe(process.stdout)
execaChildProcess.stderr.pipe(process.stderr)

// The last two lines contain the successful transformation count as "N ok".
// To save memory, we "slide the window" to keep only the last three line breaks.
// We save three line breaks because the EOL is always "\n".
execaChildProcess.stdout.on('data', (chunk) => {
lastThreeLineBreaks += chunk.toString('utf-8')

let cutoff = lastThreeLineBreaks.length

// Note: the stdout ends with "\n".
// "foo\n" + "bar\n" + "baz\n" -> "\nbar\nbaz\n"
// "\n" + "foo\n" + "bar\n" -> "\nfoo\nbar\n"

for (let i = 0; i < 3; i++) {
cutoff = lastThreeLineBreaks.lastIndexOf('\n', cutoff) - 1
}

if (cutoff > 0 && cutoff < lastThreeLineBreaks.length) {
lastThreeLineBreaks = lastThreeLineBreaks.slice(cutoff + 1)
}
})
}

try {
const result = await execaChildProcess

if (result.failed) {
throw new Error(`jscodeshift exited with code ${result.exitCode}`)
}
} catch (error) {
throw error
}

// With ANSI color codes, it will be "\x1B[39m\x1B[32m0 ok".
// Without, it will be "0 ok".
const targetOkLine = lastThreeLineBreaks.split('\n').at(-3)

if (!targetOkLine.endsWith('ok')) {
throw new Error(
`Failed to parse the successful transformation count "${targetOkLine}". This is a bug in the codemod tool.`
)
}

const stripped = stripAnsi(targetOkLine)
// "N ok" -> "N"
const parsedNum = parseInt(stripped.split(' ')[0], 10)
const hasChanges = parsedNum > 0

if (!dry && transformer === 'built-in-next-font' && hasChanges) {
const { uninstallNextFont } = await prompts(
{
type: 'confirm',
name: 'uninstallNextFont',
message:
'`built-in-next-font` should have removed all usages of `@next/font`. Do you want to uninstall `@next/font`?',
initial: true,
},
{ onCancel }
)

if (uninstallNextFont) {
console.log('Uninstalling `@next/font`')
uninstallPackage('@next/font')
}
}

if (!dry && transformer === 'next-request-geo-ip') {
const { installVercelFunctions } = await prompts({
type: 'confirm',
name: 'installVercelFunctions',
message: 'Do you want to install `@vercel/functions`?',
initial: true,
})

if (installVercelFunctions) {
console.log('Installing `@vercel/functions`...')
installPackages(['@vercel/functions'])
}
// When has changes, it requires `@vercel/functions`, so skip prompt.
if (!dry && transformer === 'next-request-geo-ip' && hasChanges) {
console.log(
'Installing `@vercel/functions` because the `next-request-geo-ip` made changes.'
)
installPackages(['@vercel/functions'])
}
}
3 changes: 2 additions & 1 deletion packages/next-codemod/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"jscodeshift": "17.0.0",
"picocolors": "1.0.0",
"prompts": "2.4.2",
"semver": "7.6.3"
"semver": "7.6.3",
"strip-ansi": "6.0.0"
},
"files": [
"transforms/*.js",
Expand Down
12 changes: 7 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 26a2bab

Please sign in to comment.