Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(update): pnpm update support update corepack config #7235

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/three-llamas-cheer.md
@@ -0,0 +1,7 @@
---
'@pnpm/plugin-commands-installation': patch
'@pnpm/outdated': patch
'@pnpm/plugin-commands-outdated': patch
---

feat: support corepack update for pnpm update --interactive
1 change: 1 addition & 0 deletions packages/types/src/package.ts
Expand Up @@ -107,6 +107,7 @@ export interface BaseManifest {
author?: string
license?: string
exports?: Record<string, string>
packageManager?: string
}

export type DependencyManifest = BaseManifest & Required<Pick<BaseManifest, 'name' | 'version'>>
Expand Down
Expand Up @@ -8,6 +8,7 @@ import isEmpty from 'ramda/src/isEmpty'
export interface ChoiceRow {
name: string
value: string
path?: string
fireairforce marked this conversation as resolved.
Show resolved Hide resolved
disabled?: boolean
}

Expand Down Expand Up @@ -75,8 +76,8 @@ export function getUpdateChoices (outdatedPkgsOfProjects: OutdatedPackage[], wor
}
})

// To filter out selected "dependencies" or "devDependencies" in the final output,
// we rename it here to "[dependencies]" or "[devDependencies]",
// To filter out selected "dependencies", "devDependencies" and "packageManager" in the final output,
// we rename it here to "[dependencies]", "[devDependencies]" and "[packageManager]"
// which will be filtered out in the format function of the prompt.
finalChoices.push({ name: `[${depGroup}]`, choices, message: depGroup })

Expand Down
30 changes: 28 additions & 2 deletions pkg-manager/plugin-commands-installation/src/update/index.ts
Expand Up @@ -212,7 +212,7 @@ async function interactiveUpdate (
}
return 'All of your dependencies are already up to date inside the specified ranges. Use the --latest option to update the ranges in package.json'
}
const { updateDependencies } = await prompt({
let { updateDependencies } = await prompt({
choices,
footer: '\nEnter to start updating. Ctrl-c to cancel.',
indicator (state: any, choice: any) { // eslint-disable-line @typescript-eslint/no-explicit-any
Expand All @@ -232,7 +232,7 @@ async function interactiveUpdate (

if (Array.isArray(this.selected)) {
return this.selected
// The custom format function is used to filter out "[dependencies]" or "[devDependencies]" from the output.
// The custom format function is used to filter out "[dependencies]" or "[devDependencies]" or "[packageManager]" from the output.
// https://github.com/enquirer/enquirer/blob/master/lib/prompts/select.js#L98
.filter((choice: ChoiceRow) => !/^\[.+\]$/.test(choice.name))
.map((choice: ChoiceRow) => this.styles.primary(choice.name)).join(', ')
Expand Down Expand Up @@ -269,6 +269,16 @@ async function interactiveUpdate (
},
} as any) as any // eslint-disable-line @typescript-eslint/no-explicit-any

updateDependencies = (updateDependencies as ChoiceRow[]).filter(dep => !(dep.path && dep.path.startsWith('[packageManager]')))
const packageManagerManifest = outdatedPkgsOfProjects[0].filter(pkg => pkg.belongsTo === 'packageManager')

if (opts.save !== false && packageManagerManifest.length) {
await updateProjectCorepackConfig({
...opts,
updatedVersion: packageManagerManifest[0].wanted,
updatedPackage: packageManagerManifest[0].packageName,
})
}
const updatePkgNames = pluck('value', updateDependencies as ChoiceRow[])
return update(updatePkgNames, opts)
}
Expand Down Expand Up @@ -318,3 +328,19 @@ function makeIncludeDependenciesFromCLI (opts: {
optionalDependencies: opts.optional === true || (opts.production !== true && opts.dev !== true),
}
}

async function updateProjectCorepackConfig (opts: {
updatedVersion: string
updatedPackage: string
} & UpdateCommandOptions) {
if (!opts.allProjects) {
return
}
const { updatedPackage, updatedVersion } = opts

return Promise.all(opts.allProjects.map((project) => {
const updatedManifest = project.manifest
updatedManifest.packageManager = `${updatedPackage}@${updatedVersion}`
return project.writeProjectManifest(updatedManifest)
}))
}
Expand Up @@ -6,7 +6,7 @@ test('getUpdateChoices()', () => {
getUpdateChoices([
{
alias: 'foo',
belongsTo: 'dependencies' as const,
belongsTo: 'dependencies',
current: '1.0.0',
latestManifest: {
name: 'foo',
Expand All @@ -18,7 +18,7 @@ test('getUpdateChoices()', () => {
},
{
alias: 'foo',
belongsTo: 'devDependencies' as const,
belongsTo: 'devDependencies',
current: '1.0.0',
latestManifest: {
name: 'foo',
Expand All @@ -32,7 +32,7 @@ test('getUpdateChoices()', () => {
},
{
alias: 'qar',
belongsTo: 'devDependencies' as const,
belongsTo: 'devDependencies',
current: '1.0.0',
latestManifest: {
name: 'qar',
Expand All @@ -43,7 +43,7 @@ test('getUpdateChoices()', () => {
},
{
alias: 'zoo',
belongsTo: 'devDependencies' as const,
belongsTo: 'devDependencies',
current: '1.1.0',
latestManifest: {
name: 'zoo',
Expand All @@ -54,7 +54,7 @@ test('getUpdateChoices()', () => {
},
{
alias: 'qaz',
belongsTo: 'optionalDependencies' as const,
belongsTo: 'optionalDependencies',
current: '1.0.1',
latestManifest: {
name: 'qaz',
Expand All @@ -65,7 +65,7 @@ test('getUpdateChoices()', () => {
},
{
alias: 'qaz',
belongsTo: 'devDependencies' as const,
belongsTo: 'devDependencies',
current: '1.0.1',
latestManifest: {
name: 'qaz',
Expand All @@ -74,6 +74,17 @@ test('getUpdateChoices()', () => {
packageName: 'foo',
wanted: '1.0.1',
},
{
alias: 'pnpm',
belongsTo: 'packageManager',
current: '7.9.1',
latestManifest: {
name: 'pnpm',
version: '7.9.2',
},
packageName: 'pnpm',
wanted: '7.9.1',
},
], false))
.toStrictEqual([
{
Expand Down Expand Up @@ -137,5 +148,22 @@ test('getUpdateChoices()', () => {
},
],
},
{
name: '[packageManager]',
message: 'packageManager',
choices: [
{
name: 'Package Current Target URL ',
disabled: true,
hint: '',
value: '',
},
{
message: chalk`pnpm 7.9.1 ❯ 7.9.{greenBright.bold 2} `,
name: 'pnpm',
value: 'pnpm',
},
],
},
])
})
123 changes: 100 additions & 23 deletions pkg-manager/plugin-commands-installation/test/update/interactive.ts
Expand Up @@ -10,8 +10,7 @@ import * as enquirer from 'enquirer'

jest.mock('enquirer', () => ({ prompt: jest.fn() }))

// eslint-disable-next-line
const prompt = enquirer.prompt as any
const prompt = enquirer.prompt as any // eslint-disable-line

const REGISTRY_URL = `http://localhost:${REGISTRY_MOCK_PORT}`

Expand Down Expand Up @@ -311,27 +310,25 @@ test('interactively update should ignore dependencies from the ignoreDependencie
storeDir,
})

expect(prompt.mock.calls[0][0].choices).toStrictEqual(
[
{
choices: [
{
disabled: true,
hint: '',
name: 'Package Current Target URL ',
value: '',
},
{
message: chalk`micromatch 3.0.0 ❯ 3.{yellowBright.bold 1.10} `,
value: 'micromatch',
name: 'micromatch',
},
],
name: '[dependencies]',
message: 'dependencies',
},
]
)
expect(prompt.mock.calls[0][0].choices).toStrictEqual([
{
choices: [
{
disabled: true,
hint: '',
name: 'Package Current Target URL ',
value: '',
},
{
message: chalk`micromatch 3.0.0 ❯ 3.{yellowBright.bold 1.10} `,
value: 'micromatch',
name: 'micromatch',
},
],
name: '[dependencies]',
message: 'dependencies',
},
])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like here only the formatting changed. Revert it back.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i run pnpm run lint --fix at here, it will change automatally.


expect(prompt).toBeCalledWith(
expect.objectContaining({
Expand All @@ -354,3 +351,83 @@ test('interactively update should ignore dependencies from the ignoreDependencie
expect(lockfile.packages['is-positive@2.0.0']).toBeTruthy()
}
})

test('interactively update should update corepack config', async () => {
prepare({
name: 'project-1',
packageManager: 'pnpm@8.7.0',
})

const storeDir = path.resolve('pnpm-store')

await add.handler(
{
...DEFAULT_OPTIONS,
cacheDir: path.resolve('cache'),
dir: process.cwd(),
linkWorkspacePackages: true,
save: false,
storeDir,
},
['pnpm@8.9.2']
)

prompt.mockResolvedValue({
updateDependencies: [
{
value: 'pnpm',
name: chalk`pnpm 8.7.0 ❯ 8.9.2 https://pnpm.io/ `,
},
],
})

prompt.mockClear()
await update.handler({
...DEFAULT_OPTIONS,
cacheDir: path.resolve('cache'),
dir: process.cwd(),
interactive: true,
linkWorkspacePackages: true,
storeDir,
})

const promptStr = JSON.stringify(prompt.mock.calls[0][0].choices[0])
const latestVersionIndex = promptStr.indexOf('❯')
const latestVersion = promptStr.substring(
latestVersionIndex + 2,
latestVersionIndex + 10
)
const promptChoices = JSON.parse(promptStr.replace(latestVersion, '8.9.0'))

expect(promptChoices).toMatchInlineSnapshot(`
{
"choices": [
{
"disabled": true,
"hint": "",
"name": "Package Current Target URL ",
"value": "",
},
{
"message": "pnpm 8.7.0 ❯ 8.9.0 ",
"name": "pnpm",
"value": "pnpm",
},
],
"message": "packageManager",
"name": "[packageManager]",
}
`)
expect(prompt).toBeCalledWith(
expect.objectContaining({
footer: '\nEnter to start updating. Ctrl-c to cancel.',
message:
'Choose which packages to update ' +
`(Press ${chalk.cyan('<space>')} to select, ` +
`${chalk.cyan('<a>')} to toggle all, ` +
`${chalk.cyan('<i>')} to invert selection)`,
name: 'updateDependencies',
type: 'multiselect',
})
)
})
21 changes: 18 additions & 3 deletions reviewing/outdated/src/outdated.ts
Expand Up @@ -28,7 +28,7 @@ export type GetLatestManifestFunction = (packageName: string, rangeOrTag: string

export interface OutdatedPackage {
alias: string
belongsTo: DependenciesField
belongsTo: DependenciesField | 'packageManager'
current?: string // not defined means the package is not installed
latestManifest?: PackageManifest
packageName: string
Expand All @@ -51,7 +51,7 @@ export async function outdated (
wantedLockfile: Lockfile | null
}
): Promise<OutdatedPackage[]> {
if (packageHasNoDeps(opts.manifest)) return []
if (packageHasNoDeps(opts.manifest) && !opts.manifest.packageManager) return []
if (opts.wantedLockfile == null) {
throw new PnpmError('OUTDATED_NO_LOCKFILE', `No lockfile in directory "${opts.lockfileDir}". Run \`pnpm install\` to generate one.`)
}
Expand All @@ -75,6 +75,22 @@ export async function outdated (
const currentLockfile = opts.currentLockfile ?? { importers: { [importerId]: {} } }

const outdated: OutdatedPackage[] = []
if (typeof opts.manifest?.packageManager === 'string' && opts.manifest?.packageManager.startsWith('pnpm@')) {
const [pkgManager, pkgManagerVersion] = opts.manifest.packageManager.split('@')
fireairforce marked this conversation as resolved.
Show resolved Hide resolved
const latestPkgManifest = await opts.getLatestManifest(pkgManager, 'latest')

if (latestPkgManifest !== null && semver.lt(pkgManagerVersion, latestPkgManifest.version)) {
outdated.push({
alias: pkgManager,
belongsTo: 'packageManager',
current: pkgManagerVersion,
packageName: pkgManager,
latestManifest: latestPkgManifest,
wanted: latestPkgManifest.version,
workspace: opts.manifest.name,
})
}
}

const ignoreDependenciesMatcher = opts.ignoreDependencies?.length ? createMatcher(opts.ignoreDependencies) : undefined

Expand Down Expand Up @@ -170,7 +186,6 @@ export async function outdated (
packageName,
wanted,
workspace: opts.manifest.name,

})
}
})
Expand Down
2 changes: 1 addition & 1 deletion reviewing/plugin-commands-outdated/src/outdated.ts
Expand Up @@ -283,7 +283,7 @@ function renderOutdatedJSON (outdatedPackages: readonly OutdatedPackage[], opts:
latest: outdatedPkg.latestManifest?.version,
wanted: outdatedPkg.wanted,
isDeprecated: Boolean(outdatedPkg.latestManifest?.deprecated),
dependencyType: outdatedPkg.belongsTo,
dependencyType: outdatedPkg.belongsTo as DependenciesField,
}
if (opts.long) {
acc[outdatedPkg.packageName].latestManifest = outdatedPkg.latestManifest
Expand Down