Skip to content

Commit

Permalink
feat: support pnpm with create-next-app (#34947)
Browse files Browse the repository at this point in the history
* feat: support `pnpm` with `create-next-app`

* test: add `--use-pnpm` tests

* docs: mention `--use-pnpm` flag in docs

* test: remove `only`

* Update test/integration/create-next-app/index.test.ts

Co-authored-by: Steven <steven@ceriously.com>

* chore: add pnpm action setup to tests

* chore: use latest pnpm

* chore: debug

* chore: debug

* fix: fall back to `yarn` instead of `npm`

* test: run all tests

Co-authored-by: Steven <steven@ceriously.com>
  • Loading branch information
balazsorban44 and styfle committed Mar 3, 2022
1 parent 2c7f8b3 commit 62c33c1
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 70 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/build_test_deploy.yml
Expand Up @@ -413,6 +413,10 @@ jobs:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }}

- uses: pnpm/action-setup@v2.2.1
with:
version: 6.32.2

- uses: actions/download-artifact@v2
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
with:
Expand Down
1 change: 1 addition & 0 deletions docs/api-reference/create-next-app.md
Expand Up @@ -28,6 +28,7 @@ yarn create next-app --typescript
- **-e, --example [name]|[github-url]** - An example to bootstrap the app with. You can use an example name from the [Next.js repo](https://github.com/vercel/next.js/tree/canary/examples) or a GitHub URL. The URL can use any branch and/or subdirectory.
- **--example-path [path-to-example]** - In a rare case, your GitHub URL might contain a branch name with a slash (e.g. bug/fix-1) and the path to the example (e.g. foo/bar). In this case, you must specify the path to the example separately: `--example-path foo/bar`
- **--use-npm** - Explicitly tell the CLI to bootstrap the app using npm. To bootstrap using yarn we recommend running `yarn create next-app`
- **--use-pnpm** - Explicitly tell the CLI to bootstrap the app using pnpm. To bootstrap using yarn we recommend running `yarn create next-app`

### Why use Create Next App?

Expand Down
1 change: 1 addition & 0 deletions packages/create-next-app/README.md
Expand Up @@ -26,6 +26,7 @@ npx create-next-app blog-app
- **-e, --example [name]|[github-url]** - An example to bootstrap the app with. You can use an example name from the [Next.js repo](https://github.com/vercel/next.js/tree/canary/examples) or a GitHub URL. The URL can use any branch and/or subdirectory.
- **--example-path &lt;path-to-example&gt;** - In a rare case, your GitHub URL might contain a branch name with a slash (e.g. bug/fix-1) and the path to the example (e.g. foo/bar). In this case, you must specify the path to the example separately: `--example-path foo/bar`
- **--use-npm** - Explicitly tell the CLI to bootstrap the app using npm. To bootstrap using yarn we recommend to run `yarn create next-app`
- **--use-pnpm** - Explicitly tell the CLI to bootstrap the app using pnpm. To bootstrap using yarn we recommend running `yarn create next-app`

## Why use Create Next App?

Expand Down
23 changes: 11 additions & 12 deletions packages/create-next-app/create-app.ts
Expand Up @@ -18,20 +18,20 @@ import { tryGitInit } from './helpers/git'
import { install } from './helpers/install'
import { isFolderEmpty } from './helpers/is-folder-empty'
import { getOnline } from './helpers/is-online'
import { shouldUseYarn } from './helpers/should-use-yarn'
import { isWriteable } from './helpers/is-writeable'
import type { PackageManager } from './helpers/get-pkg-manager'

export class DownloadError extends Error {}

export async function createApp({
appPath,
useNpm,
packageManager,
example,
examplePath,
typescript,
}: {
appPath: string
useNpm: boolean
packageManager: PackageManager
example?: string
examplePath?: string
typescript?: boolean
Expand Down Expand Up @@ -119,11 +119,10 @@ export async function createApp({
process.exit(1)
}

const useYarn = useNpm ? false : shouldUseYarn()
const useYarn = packageManager === 'yarn'
const isOnline = !useYarn || (await getOnline())
const originalDirectory = process.cwd()

const displayedCommand = useYarn ? 'yarn' : 'npm'
console.log(`Creating a new Next.js app in ${chalk.green(root)}.`)
console.log()

Expand Down Expand Up @@ -189,14 +188,14 @@ export async function createApp({
console.log('Installing packages. This might take a couple of minutes.')
console.log()

await install(root, null, { useYarn, isOnline })
await install(root, null, { packageManager, isOnline })
console.log()
} else {
/**
* Otherwise, if an example repository is not provided for cloning, proceed
* by installing from a template.
*/
console.log(chalk.bold(`Using ${displayedCommand}.`))
console.log(chalk.bold(`Using ${packageManager}.`))
/**
* Create a package.json for the new project.
*/
Expand All @@ -221,7 +220,7 @@ export async function createApp({
/**
* These flags will be passed to `install()`.
*/
const installFlags = { useYarn, isOnline }
const installFlags = { packageManager, isOnline }
/**
* Default dependencies.
*/
Expand Down Expand Up @@ -309,20 +308,20 @@ export async function createApp({
console.log(`${chalk.green('Success!')} Created ${appName} at ${appPath}`)
console.log('Inside that directory, you can run several commands:')
console.log()
console.log(chalk.cyan(` ${displayedCommand} ${useYarn ? '' : 'run '}dev`))
console.log(chalk.cyan(` ${packageManager} ${useYarn ? '' : 'run '}dev`))
console.log(' Starts the development server.')
console.log()
console.log(chalk.cyan(` ${displayedCommand} ${useYarn ? '' : 'run '}build`))
console.log(chalk.cyan(` ${packageManager} ${useYarn ? '' : 'run '}build`))
console.log(' Builds the app for production.')
console.log()
console.log(chalk.cyan(` ${displayedCommand} start`))
console.log(chalk.cyan(` ${packageManager} start`))
console.log(' Runs the built app in production mode.')
console.log()
console.log('We suggest that you begin by typing:')
console.log()
console.log(chalk.cyan(' cd'), cdpath)
console.log(
` ${chalk.cyan(`${displayedCommand} ${useYarn ? '' : 'run '}dev`)}`
` ${chalk.cyan(`${packageManager} ${useYarn ? '' : 'run '}dev`)}`
)
console.log()
}
25 changes: 25 additions & 0 deletions packages/create-next-app/helpers/get-pkg-manager.ts
@@ -0,0 +1,25 @@
import { execSync } from 'child_process'

export type PackageManager = 'npm' | 'pnpm' | 'yarn'

export function getPkgManager(): PackageManager {
try {
const userAgent = process.env.npm_config_user_agent
if (userAgent) {
if (userAgent.startsWith('yarn')) {
return 'yarn'
} else if (userAgent.startsWith('pnpm')) {
return 'pnpm'
}
}
try {
execSync('yarn --version', { stdio: 'ignore' })
return 'yarn'
} catch {
execSync('pnpm --version', { stdio: 'ignore' })
return 'pnpm'
}
} catch {
return 'npm'
}
}
18 changes: 10 additions & 8 deletions packages/create-next-app/helpers/install.ts
@@ -1,12 +1,13 @@
/* eslint-disable import/no-extraneous-dependencies */
import chalk from 'chalk'
import spawn from 'cross-spawn'
import type { PackageManager } from './get-pkg-manager'

interface InstallArgs {
/**
* Indicate whether to install packages using Yarn.
* Indicate whether to install packages using npm, pnpm or Yarn.
*/
useYarn: boolean
packageManager: PackageManager
/**
* Indicate whether there is an active Internet connection.
*/
Expand All @@ -25,10 +26,10 @@ interface InstallArgs {
export function install(
root: string,
dependencies: string[] | null,
{ useYarn, isOnline, devDependencies }: InstallArgs
{ packageManager, isOnline, devDependencies }: InstallArgs
): Promise<void> {
/**
* NPM-specific command-line flags.
* (p)npm-specific command-line flags.
*/
const npmFlags: string[] = []
/**
Expand All @@ -40,11 +41,12 @@ export function install(
*/
return new Promise((resolve, reject) => {
let args: string[]
let command: string = useYarn ? 'yarnpkg' : 'npm'
let command = packageManager
const useYarn = packageManager === 'yarn'

if (dependencies && dependencies.length) {
/**
* If there are dependencies, run a variation of `{displayCommand} add`.
* If there are dependencies, run a variation of `{packageManager} add`.
*/
if (useYarn) {
/**
Expand All @@ -57,15 +59,15 @@ export function install(
args.push(...dependencies)
} else {
/**
* Call `npm install [--save|--save-dev] ...`.
* Call `(p)npm install [--save|--save-dev] ...`.
*/
args = ['install', '--save-exact']
args.push(devDependencies ? '--save-dev' : '--save')
args.push(...dependencies)
}
} else {
/**
* If there are no dependencies, run a variation of `{displayCommand}
* If there are no dependencies, run a variation of `{packageManager}
* install`.
*/
args = ['install']
Expand Down
14 changes: 0 additions & 14 deletions packages/create-next-app/helpers/should-use-yarn.ts

This file was deleted.

26 changes: 19 additions & 7 deletions packages/create-next-app/index.ts
Expand Up @@ -6,7 +6,7 @@ import path from 'path'
import prompts from 'prompts'
import checkForUpdate from 'update-check'
import { createApp, DownloadError } from './create-app'
import { shouldUseYarn } from './helpers/should-use-yarn'
import { getPkgManager } from './helpers/get-pkg-manager'
import { validateNpmName } from './helpers/validate-pkg'
import packageJson from './package.json'

Expand All @@ -31,6 +31,13 @@ const program = new Commander.Command(packageJson.name)
`
Explicitly tell the CLI to bootstrap the app using npm
`
)
.option(
'--use-pnpm',
`
Explicitly tell the CLI to bootstrap the app using pnpm
`
)
.option(
Expand Down Expand Up @@ -116,14 +123,19 @@ async function run(): Promise<void> {
'Please provide an example name or url, otherwise remove the example option.'
)
process.exit(1)
return
}

const packageManager = !!program.useNpm
? 'npm'
: !!program.usePnpm
? 'pnpm'
: 'yarn'

const example = typeof program.example === 'string' && program.example.trim()
try {
await createApp({
appPath: resolvedProjectPath,
useNpm: !!program.useNpm,
packageManager,
example: example && example !== 'default' ? example : undefined,
examplePath: program.examplePath,
typescript: program.typescript,
Expand All @@ -147,7 +159,7 @@ async function run(): Promise<void> {

await createApp({
appPath: resolvedProjectPath,
useNpm: !!program.useNpm,
packageManager,
typescript: program.typescript,
})
}
Expand All @@ -159,7 +171,7 @@ async function notifyUpdate(): Promise<void> {
try {
const res = await update
if (res?.latest) {
const isYarn = shouldUseYarn()
const pkgManager = getPkgManager()

console.log()
console.log(
Expand All @@ -168,9 +180,9 @@ async function notifyUpdate(): Promise<void> {
console.log(
'You can update by running: ' +
chalk.cyan(
isYarn
pkgManager === 'yarn'
? 'yarn global add create-next-app'
: 'npm i -g create-next-app'
: `${pkgManager} install --global create-next-app`
)
)
console.log()
Expand Down

0 comments on commit 62c33c1

Please sign in to comment.