CoolFace
Datasetpublic

basant307/AI_Governance_Project

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
0likes48downloads
installationInfo.ts331 linesDownload Raw Back to utils
1/**2 * @license3 * Copyright 2025 Google LLC4 * SPDX-License-Identifier: Apache-2.05 */6 7import { createDebugLogger, isGitRepository } from '@qwen-code/qwen-code-core';8import * as fs from 'node:fs';9import * as path from 'node:path';10import * as childProcess from 'node:child_process';11 12export enum PackageManager {13  NPM = 'npm',14  YARN = 'yarn',15  PNPM = 'pnpm',16  PNPX = 'pnpx',17  BUN = 'bun',18  BUNX = 'bunx',19  HOMEBREW = 'homebrew',20  STANDALONE = 'standalone',21  NPX = 'npx',22  UNKNOWN = 'unknown',23}24 25const debugLogger = createDebugLogger('INSTALLATION_INFO');26const STANDALONE_UNIX_INSTALLER =27  'https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen-standalone.sh';28const STANDALONE_WINDOWS_INSTALLER =29  'https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen-standalone.ps1';30 31export interface InstallationInfo {32  packageManager: PackageManager;33  isGlobal: boolean;34  isStandalone?: boolean;35  standaloneDir?: string;36  updateCommand?: string;37  updateMessage?: string;38}39 40export function getInstallationInfo(41  projectRoot: string,42  isAutoUpdateEnabled: boolean,43): InstallationInfo {44  const cliPath = process.argv[1];45  if (!cliPath) {46    return { packageManager: PackageManager.UNKNOWN, isGlobal: false };47  }48 49  try {50    // Normalize path separators to forward slashes for consistent matching.51    const realPath = fs.realpathSync(cliPath).replace(/\\/g, '/');52    const normalizedProjectRoot = projectRoot?.replace(/\\/g, '/');53    const isGit = isGitRepository(process.cwd());54 55    // Check for local git clone first56    if (57      isGit &&58      normalizedProjectRoot &&59      isSamePathOrInside(realPath, normalizedProjectRoot) &&60      !realPath.includes('/node_modules/')61    ) {62      return {63        packageManager: PackageManager.UNKNOWN, // Not managed by a package manager in this sense64        isGlobal: false,65        updateMessage:66          'Running from a local git clone. Please update with "git pull".',67      };68    }69 70    // Check for npx/pnpx71    if (realPath.includes('/.npm/_npx') || realPath.includes('/npm/_npx')) {72      return {73        packageManager: PackageManager.NPX,74        isGlobal: false,75        updateMessage: 'Running via npx, update not applicable.',76      };77    }78    if (realPath.includes('/.pnpm/_pnpx')) {79      return {80        packageManager: PackageManager.PNPX,81        isGlobal: false,82        updateMessage: 'Running via pnpx, update not applicable.',83      };84    }85 86    const standaloneInfo = getStandaloneInstallInfo(87      realPath,88      isAutoUpdateEnabled,89    );90    if (standaloneInfo) {91      return standaloneInfo;92    }93 94    // Check for Homebrew95    if (process.platform === 'darwin') {96      try {97        // We do not support homebrew for now, keep forward compatibility for future use98        childProcess.execSync('brew list -1 | grep -q "^qwen-code$"', {99          stdio: 'ignore',100        });101        return {102          packageManager: PackageManager.HOMEBREW,103          isGlobal: true,104          updateMessage:105            'Installed via Homebrew. Please update with "brew upgrade".',106        };107      } catch (_error) {108        // continue to the next check109      }110    }111 112    // Check for pnpm113    if (realPath.includes('/.pnpm/global')) {114      const updateCommand = 'pnpm add -g @qwen-code/qwen-code@latest';115      return {116        packageManager: PackageManager.PNPM,117        isGlobal: true,118        updateCommand,119        updateMessage: isAutoUpdateEnabled120          ? 'Installed with pnpm. Attempting to automatically update now...'121          : `Please run ${updateCommand} to update`,122      };123    }124 125    // Check for yarn126    if (realPath.includes('/.yarn/global')) {127      const updateCommand = 'yarn global add @qwen-code/qwen-code@latest';128      return {129        packageManager: PackageManager.YARN,130        isGlobal: true,131        updateCommand,132        updateMessage: isAutoUpdateEnabled133          ? 'Installed with yarn. Attempting to automatically update now...'134          : `Please run ${updateCommand} to update`,135      };136    }137 138    // Check for bun139    if (realPath.includes('/.bun/install/cache')) {140      return {141        packageManager: PackageManager.BUNX,142        isGlobal: false,143        updateMessage: 'Running via bunx, update not applicable.',144      };145    }146    if (realPath.includes('/.bun/bin')) {147      const updateCommand = 'bun add -g @qwen-code/qwen-code@latest';148      return {149        packageManager: PackageManager.BUN,150        isGlobal: true,151        updateCommand,152        updateMessage: isAutoUpdateEnabled153          ? 'Installed with bun. Attempting to automatically update now...'154          : `Please run ${updateCommand} to update`,155      };156    }157 158    // Check for local install159    if (160      normalizedProjectRoot &&161      isSamePathOrInside(realPath, `${normalizedProjectRoot}/node_modules`)162    ) {163      let pm = PackageManager.NPM;164      if (fs.existsSync(path.join(projectRoot, 'yarn.lock'))) {165        pm = PackageManager.YARN;166      } else if (fs.existsSync(path.join(projectRoot, 'pnpm-lock.yaml'))) {167        pm = PackageManager.PNPM;168      } else if (fs.existsSync(path.join(projectRoot, 'bun.lockb'))) {169        pm = PackageManager.BUN;170      }171      return {172        packageManager: pm,173        isGlobal: false,174        updateMessage:175          "Locally installed. Please update via your project's package.json.",176      };177    }178 179    // Check if the npm global package directory is writable to determine180    // whether `npm install -g` would require sudo.181    const npmPackageDir = path.dirname(path.dirname(realPath));182    let npmPrefixWritable = false;183    try {184      fs.accessSync(npmPackageDir, fs.constants.W_OK);185      npmPrefixWritable = true;186    } catch {187      // Not writable (e.g., /usr/local/lib/node_modules owned by root)188    }189 190    if (!npmPrefixWritable) {191      // The npm global prefix requires sudo. Do NOT silently migrate to the192      // standalone installer here: that swaps in a bundled Node runtime which193      // can be incompatible with the host (e.g. an older glibc), breaking users194      // who were updating fine via npm. Keep npm installs on npm and ask the195      // user to update with sudo instead. No updateCommand is returned so the196      // auto-updater does not attempt an unattended sudo.197      return {198        packageManager: PackageManager.NPM,199        isGlobal: true,200        updateMessage:201          'Update requires sudo. Please run: sudo npm install -g @qwen-code/qwen-code@latest',202      };203    }204 205    const updateCommand = 'npm install -g @qwen-code/qwen-code@latest';206    return {207      packageManager: PackageManager.NPM,208      isGlobal: true,209      updateCommand,210      updateMessage: isAutoUpdateEnabled211        ? 'Installed with npm. Attempting to automatically update now...'212        : `Please run ${updateCommand} to update`,213    };214  } catch (error) {215    debugLogger.error('Failed to detect installation info:', error);216    return { packageManager: PackageManager.UNKNOWN, isGlobal: false };217  }218}219 220function stripTrailingSlashes(value: string): string {221  return value.replace(/\/+$/, '') || '/';222}223 224function isSamePathOrInside(candidate: string, parent: string): boolean {225  const normalizedCandidate = stripTrailingSlashes(candidate);226  const normalizedParent = stripTrailingSlashes(parent);227  if (normalizedParent === '/') {228    return normalizedCandidate === '/' || normalizedCandidate.startsWith('/');229  }230  return (231    normalizedCandidate === normalizedParent ||232    normalizedCandidate.startsWith(`${normalizedParent}/`)233  );234}235 236function getStandaloneInstallInfo(237  realPath: string,238  isAutoUpdateEnabled: boolean,239): InstallationInfo | null {240  const installDir = standaloneInstallDirForCliPath(realPath);241  if (!installDir || !isStandaloneInstallDir(installDir)) {242    return null;243  }244 245  const updateCommand =246    process.platform === 'win32'247      ? `powershell -ExecutionPolicy Bypass -c "irm ${STANDALONE_WINDOWS_INSTALLER} | iex"`248      : `curl -fsSL ${STANDALONE_UNIX_INSTALLER} | bash`;249 250  return {251    packageManager: PackageManager.STANDALONE,252    isGlobal: true,253    isStandalone: true,254    standaloneDir: installDir,255    updateMessage: isAutoUpdateEnabled256      ? 'Standalone install detected. Attempting to automatically update now...'257      : `Standalone install detected. Please rerun the standalone installer to update: ${updateCommand}`,258  };259}260 261function standaloneInstallDirForCliPath(realPath: string): string | null {262  const normalized = realPath.replace(/\\/g, '/');263  const suffix = '/lib/cli.js';264  if (!normalized.endsWith(suffix)) {265    return null;266  }267  return realPath.slice(0, -suffix.length);268}269 270function isStandaloneInstallDir(installDir: string): boolean {271  try {272    const manifestPath = path.join(installDir, 'manifest.json');273    if (!fs.existsSync(manifestPath)) {274      return false;275    }276 277    const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as {278      name?: unknown;279      target?: unknown;280    };281    // Manifest format is produced by writeManifest in create-standalone-package.js.282    if (283      manifest.name !== '@qwen-code/qwen-code' ||284      typeof manifest.target !== 'string' ||285      !isStandaloneTargetForCurrentPlatform(manifest.target)286    ) {287      return false;288    }289 290    const qwenBin =291      process.platform === 'win32'292        ? path.join(installDir, 'bin', 'qwen.cmd')293        : path.join(installDir, 'bin', 'qwen');294    const nodeBin =295      process.platform === 'win32'296        ? path.join(installDir, 'node', 'node.exe')297        : path.join(installDir, 'node', 'bin', 'node');298 299    return (300      fs.existsSync(qwenBin) &&301      fs.existsSync(nodeBin) &&302      isStandaloneRuntimeFile(qwenBin) &&303      isStandaloneRuntimeFile(nodeBin)304    );305  } catch (err) {306    debugLogger.error('Standalone detection failed:', installDir, err);307    return false;308  }309}310 311function isStandaloneTargetForCurrentPlatform(target: string): boolean {312  switch (process.platform) {313    case 'darwin':314      return /^darwin-(arm64|x64)$/.test(target);315    case 'linux':316      return /^linux-(arm64|x64)$/.test(target);317    case 'win32':318      return /^win-(arm64|x64)$/.test(target);319    default:320      return false;321  }322}323 324function isStandaloneRuntimeFile(filePath: string): boolean {325  const stats = fs.lstatSync(filePath);326  if (!stats.isFile() || stats.isSymbolicLink()) {327    return false;328  }329  return process.platform === 'win32' || (stats.mode & 0o111) !== 0;330}331 
basant307/AI_Governance_Project · CoolFace