Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled
240 lines
6.3 KiB
TypeScript
240 lines
6.3 KiB
TypeScript
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 {
|
|
checkGitStatus,
|
|
onCancel,
|
|
TRANSFORMER_INQUIRER_CHOICES,
|
|
} from '../lib/utils'
|
|
|
|
function expandFilePathsIfNeeded(filesBeforeExpansion) {
|
|
const shouldExpandFiles = filesBeforeExpansion.some((file) =>
|
|
file.includes('*')
|
|
)
|
|
return shouldExpandFiles
|
|
? globby.sync(filesBeforeExpansion)
|
|
: filesBeforeExpansion
|
|
}
|
|
|
|
export const jscodeshiftExecutable = require.resolve('.bin/jscodeshift')
|
|
export const transformerDirectory = join(__dirname, '../', 'transforms')
|
|
|
|
export async function runTransform(
|
|
transform: string,
|
|
path: string,
|
|
options: any
|
|
) {
|
|
let transformer = transform
|
|
let directory = path
|
|
|
|
if (!options.dry) {
|
|
checkGitStatus(options.force)
|
|
}
|
|
|
|
if (
|
|
transform &&
|
|
!TRANSFORMER_INQUIRER_CHOICES.find((x) => x.value === transform)
|
|
) {
|
|
console.error('Invalid transform choice, pick one of:')
|
|
console.error(
|
|
TRANSFORMER_INQUIRER_CHOICES.map((x) => '- ' + x.value).join('\n')
|
|
)
|
|
process.exit(1)
|
|
}
|
|
|
|
if (!path) {
|
|
const res = await prompts(
|
|
{
|
|
type: 'text',
|
|
name: 'path',
|
|
message: 'On which files or directory should the codemods be applied?',
|
|
initial: '.',
|
|
},
|
|
{ onCancel }
|
|
)
|
|
|
|
directory = res.path
|
|
}
|
|
if (!transform) {
|
|
const res = await prompts(
|
|
{
|
|
type: 'select',
|
|
name: 'transformer',
|
|
message: 'Which transform would you like to apply?',
|
|
choices: TRANSFORMER_INQUIRER_CHOICES.reverse().map(
|
|
({ title, value, version }) => {
|
|
return {
|
|
title: `(v${version}) ${value}`,
|
|
description: title,
|
|
value,
|
|
}
|
|
}
|
|
),
|
|
},
|
|
{ onCancel }
|
|
)
|
|
|
|
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) {
|
|
console.log(`No files found matching "${directory}"`)
|
|
return null
|
|
}
|
|
|
|
const transformerPath = join(transformerDirectory, `${transformer}.js`)
|
|
|
|
if (transformer === 'cra-to-next') {
|
|
// cra-to-next transform doesn't use jscodeshift directly
|
|
return require(transformerPath).default(filesExpanded, options)
|
|
}
|
|
|
|
if (transformer === 'next-lint-to-eslint-cli') {
|
|
// next-lint-to-eslint-cli transform doesn't use jscodeshift directly
|
|
return require(transformerPath).default(filesExpanded, options)
|
|
}
|
|
|
|
let args = []
|
|
|
|
const { dry, print, runInBand, jscodeshift, verbose } = options
|
|
|
|
if (dry) {
|
|
args.push('--dry')
|
|
}
|
|
if (print) {
|
|
args.push('--print')
|
|
}
|
|
if (runInBand) {
|
|
args.push('--run-in-band')
|
|
}
|
|
if (verbose) {
|
|
args.push('--verbose=2')
|
|
}
|
|
args.push('--parser=tsx')
|
|
|
|
args.push('--ignore-pattern=**/node_modules/**')
|
|
args.push('--ignore-pattern=**/.next/**')
|
|
|
|
args.push('--extensions=tsx,ts,jsx,js')
|
|
|
|
args = args.concat(['--transform', transformerPath])
|
|
|
|
if (jscodeshift) {
|
|
args = args.concat(jscodeshift)
|
|
}
|
|
|
|
args = args.concat(filesExpanded)
|
|
|
|
console.log(`Executing command: jscodeshift ${args.join(' ')}`)
|
|
|
|
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' } : {},
|
|
})
|
|
|
|
// "\n" + "a\n" + "b\n"
|
|
let lastThreeLineBreaks = ''
|
|
|
|
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')
|
|
}
|
|
}
|
|
|
|
// 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'])
|
|
}
|
|
}
|