feat(cli.js): package managers interface, add pnpm support (#1743)

Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
This commit is contained in:
Dominik Schenk 2021-05-09 14:03:44 +02:00 committed by GitHub
parent bb8dafbe1e
commit 908b703246
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 204 additions and 73 deletions

5
.changes/pnpm-support.md Normal file
View File

@ -0,0 +1,5 @@
---
"cli.js": patch
---
Adds `pnpm` support.

View File

@ -0,0 +1,4 @@
export * from './yarn-manager'
export * from './npm-manager'
export * from './pnpm-manager'
export * from './types'

View File

@ -0,0 +1,48 @@
import { IManager } from './types'
import { sync as crossSpawnSync } from 'cross-spawn'
import { spawnSync } from '../../../helpers/spawn'
import { appDir } from '../../../helpers/app-paths'
export class NpmManager implements IManager {
type = 'npm'
installPackage(packageName: string): void {
spawnSync('npm', ['install', packageName], appDir)
}
installDevPackage(packageName: string): void {
spawnSync('npm', ['install', packageName, '--save-dev'], appDir)
}
updatePackage(packageName: string): void {
spawnSync('npm', ['install', `${packageName}@latest`], appDir)
}
getPackageVersion(packageName: string): string | null {
const child = crossSpawnSync(
'npm',
['list', packageName, 'version', '--depth', '0'],
{
cwd: appDir
}
)
const output = String(child.output[1])
// eslint-disable-next-line security/detect-non-literal-regexp
const matches = new RegExp(packageName + '@(\\S+)', 'g').exec(output)
if (matches?.[1]) {
return matches[1]
} else {
return null
}
}
getLatestVersion(packageName: string): string {
const child = crossSpawnSync('npm', ['show', packageName, 'version'], {
cwd: appDir
})
return String(child.output[1]).replace('\n', '')
}
}

View File

@ -0,0 +1,48 @@
import { IManager } from './types'
import { sync as crossSpawnSync } from 'cross-spawn'
import { spawnSync } from '../../../helpers/spawn'
import { appDir } from '../../../helpers/app-paths'
export class PnpmManager implements IManager {
type = 'pnpm'
installPackage(packageName: string): void {
spawnSync('pnpm', ['add', packageName], appDir)
}
installDevPackage(packageName: string): void {
spawnSync('pnpm', ['add', packageName, '--save-dev'], appDir)
}
updatePackage(packageName: string): void {
spawnSync('pnpm', ['add', `${packageName}@latest`], appDir)
}
getPackageVersion(packageName: string): string | null {
const child = crossSpawnSync(
'pnpm',
['list', packageName, 'version', '--depth', '0'],
{
cwd: appDir
}
)
const output = String(child.output[1])
// eslint-disable-next-line security/detect-non-literal-regexp
const matches = new RegExp(packageName + ' (\\S+)', 'g').exec(output)
if (matches?.[1]) {
return matches[1]
} else {
return null
}
}
getLatestVersion(packageName: string): string {
const child = crossSpawnSync('pnpm', ['info', packageName, 'version'], {
cwd: appDir
})
return String(child.output[1]).replace('\n', '')
}
}

View File

@ -0,0 +1,8 @@
export interface IManager {
type: string
installPackage: (packageName: string) => void
installDevPackage: (packageName: string) => void
updatePackage: (packageName: string) => void
getPackageVersion: (packageName: string) => string | null
getLatestVersion: (packageName: string) => string
}

View File

@ -0,0 +1,48 @@
import { IManager } from './types'
import { sync as crossSpawnSync } from 'cross-spawn'
import { spawnSync } from '../../../helpers/spawn'
import { appDir } from '../../../helpers/app-paths'
export class YarnManager implements IManager {
type = 'yarn'
installPackage(packageName: string): void {
spawnSync('yarn', ['add', packageName], appDir)
}
installDevPackage(packageName: string): void {
spawnSync('yarn', ['add', packageName, '--dev'], appDir)
}
updatePackage(packageName: string): void {
spawnSync('yarn', ['upgrade', packageName, '--latest'], appDir)
}
getPackageVersion(packageName: string): string | null {
const child = crossSpawnSync(
'yarn',
['list', '--pattern', packageName, '--depth', '0'],
{ cwd: appDir }
)
const output = String(child.output[1])
// eslint-disable-next-line security/detect-non-literal-regexp
const matches = new RegExp(packageName + '@(\\S+)', 'g').exec(output)
if (matches?.[1]) {
return matches[1]
} else {
return null
}
}
getLatestVersion(packageName: string): string {
const child = crossSpawnSync(
'yarn',
['info', packageName, 'versions', '--json'],
{ cwd: appDir }
)
const output = String(child.output[1])
const packageJson = JSON.parse(output) as { data: string[] }
return packageJson.data[packageJson.data.length - 1]
}
}

View File

@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
import { ManagementType, Result } from './types'
import { Answer, ManagementType, Result } from './types'
import {
getNpmLatestVersion,
getNpmPackageVersion,
@ -10,7 +10,7 @@ import {
installNpmDevPackage,
updateNpmPackage,
semverLt,
useYarn
getManager
} from './util'
import logger from '../../helpers/logger'
import { resolve } from '../../helpers/app-paths'
@ -29,37 +29,42 @@ async function manageDependencies(
const npmChild = crossSpawnSync('npm', ['--version'])
const yarnChild = crossSpawnSync('yarn', ['--version'])
const pnpmChild = crossSpawnSync('pnpm', ['--version'])
if (
(npmChild.status ?? npmChild.error) &&
(yarnChild.status ?? yarnChild.error)
(yarnChild.status ?? yarnChild.error) &&
(pnpmChild.status ?? pnpmChild.error)
) {
throw new Error(
'must have `npm` or `yarn` installed to manage dependenices'
'must have installed one of the following package managers `npm`, `yarn`, `pnpm` to manage dependenices'
)
}
if (existsSync(resolve.app('package.json'))) {
for (const dependency of dependencies) {
const currentVersion = getNpmPackageVersion(dependency)
const packageManager = getManager().type.toUpperCase()
if (currentVersion === null) {
log(`Installing ${dependency}...`)
if (
managementType === ManagementType.Install ||
managementType === ManagementType.InstallDev
) {
const packageManager = useYarn() ? 'YARN' : 'NPM'
const inquired = (await inquirer.prompt([
const prefix =
managementType === ManagementType.InstallDev
? ' as dev-dependency'
: ''
const inquired = await inquirer.prompt<Answer>([
{
type: 'confirm',
name: 'answer',
message: `[${packageManager}]: "Do you want to install ${dependency} ${
managementType === ManagementType.InstallDev
? 'as dev-dependency'
: ''
}?"`,
message: `[${packageManager}]: "Do you want to install ${dependency}${prefix}?"`,
default: false
}
])) as { answer: boolean }
])
if (inquired.answer) {
if (managementType === ManagementType.Install) {
installNpmPackage(dependency)
@ -71,15 +76,17 @@ async function manageDependencies(
}
} else if (managementType === ManagementType.Update) {
const latestVersion = getNpmLatestVersion(dependency)
if (semverLt(currentVersion, latestVersion)) {
const inquired = (await inquirer.prompt([
const inquired = await inquirer.prompt<Answer>([
{
type: 'confirm',
name: 'answer',
message: `[NPM]: "${dependency}" latest version is ${latestVersion}. Do you want to update?`,
message: `[${packageManager}]: "${dependency}" latest version is ${latestVersion}. Do you want to update?`,
default: false
}
])) as { answer: boolean }
])
if (inquired.answer) {
log(`Updating ${dependency}...`)
updateNpmPackage(dependency)

View File

@ -9,3 +9,7 @@ export enum ManagementType {
}
export type Result = Map<ManagementType, string[]>
export interface Answer {
answer: boolean
}

View File

@ -2,16 +2,21 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
import { spawnSync } from '../../helpers/spawn'
import { sync as crossSpawnSync } from 'cross-spawn'
import { appDir, resolve as appResolve } from '../../helpers/app-paths'
import { resolve as appResolve } from '../../helpers/app-paths'
import { existsSync } from 'fs'
import semver from 'semver'
import { IManager, NpmManager, YarnManager, PnpmManager } from './managers'
const useYarn = (): boolean =>
process.env.npm_execpath
? process.env.npm_execpath.includes('yarn')
: existsSync(appResolve.app('yarn.lock'))
const getManager = (): IManager => {
if (existsSync(appResolve.app('yarn.lock'))) {
return new YarnManager()
} else if (existsSync(appResolve.app('pnpm-lock.yaml'))) {
return new PnpmManager()
} else {
return new NpmManager()
}
}
function getCrateLatestVersion(crateName: string): string | null {
const child = crossSpawnSync('cargo', ['search', crateName, '--limit', '1'])
@ -26,69 +31,23 @@ function getCrateLatestVersion(crateName: string): string | null {
}
function getNpmLatestVersion(packageName: string): string {
if (useYarn()) {
const child = crossSpawnSync(
'yarn',
['info', packageName, 'versions', '--json'],
{
cwd: appDir
}
)
const output = String(child.output[1])
const packageJson = JSON.parse(output) as { data: string[] }
return packageJson.data[packageJson.data.length - 1]
} else {
const child = crossSpawnSync('npm', ['show', packageName, 'version'], {
cwd: appDir
})
return String(child.output[1]).replace('\n', '')
}
return getManager().getLatestVersion(packageName)
}
function getNpmPackageVersion(packageName: string): string | null {
const child = useYarn()
? crossSpawnSync(
'yarn',
['list', '--pattern', packageName, '--depth', '0'],
{
cwd: appDir
}
)
: crossSpawnSync('npm', ['list', packageName, 'version', '--depth', '0'], {
cwd: appDir
})
const output = String(child.output[1])
// eslint-disable-next-line security/detect-non-literal-regexp
const matches = new RegExp(packageName + '@(\\S+)', 'g').exec(output)
if (matches?.[1]) {
return matches[1]
} else {
return null
}
return getManager().getPackageVersion(packageName)
}
function installNpmPackage(packageName: string): void {
if (useYarn()) {
spawnSync('yarn', ['add', packageName], appDir)
} else {
spawnSync('npm', ['install', packageName], appDir)
}
return getManager().installPackage(packageName)
}
function installNpmDevPackage(packageName: string): void {
if (useYarn()) {
spawnSync('yarn', ['add', packageName, '--dev'], appDir)
} else {
spawnSync('npm', ['install', packageName, '--save-dev'], appDir)
}
return getManager().installDevPackage(packageName)
}
function updateNpmPackage(packageName: string): void {
if (useYarn()) {
spawnSync('yarn', ['upgrade', packageName, '--latest'], appDir)
} else {
spawnSync('npm', ['install', `${packageName}@latest`], appDir)
}
return getManager().updatePackage(packageName)
}
function padVersion(version: string): string {
@ -105,7 +64,7 @@ function semverLt(first: string, second: string): boolean {
}
export {
useYarn,
getManager,
getCrateLatestVersion,
getNpmLatestVersion,
getNpmPackageVersion,