Răsfoiți Sursa

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

Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
Dominik Schenk 4 ani în urmă
părinte
comite
908b703246

+ 5 - 0
.changes/pnpm-support.md

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

+ 4 - 0
tooling/cli.js/src/api/dependency-manager/managers/index.ts

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

+ 48 - 0
tooling/cli.js/src/api/dependency-manager/managers/npm-manager.ts

@@ -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', '')
+  }
+}

+ 48 - 0
tooling/cli.js/src/api/dependency-manager/managers/pnpm-manager.ts

@@ -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', '')
+  }
+}

+ 8 - 0
tooling/cli.js/src/api/dependency-manager/managers/types.ts

@@ -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
+}

+ 48 - 0
tooling/cli.js/src/api/dependency-manager/managers/yarn-manager.ts

@@ -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]
+  }
+}

+ 22 - 15
tooling/cli.js/src/api/dependency-manager/npm-packages.ts

@@ -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)

+ 4 - 0
tooling/cli.js/src/api/dependency-manager/types.ts

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

+ 17 - 58
tooling/cli.js/src/api/dependency-manager/util.ts

@@ -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,