# All files qualify-install.ts

60.46% Statements 26/43
64.7% Branches 11/17
28.57% Functions 2/7
61.9% Lines 26/42

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    2x
    2x
    2x
    2x

    2x
    2x

    3x

    3x
    3x
    2x

    1x

    1x

    1x

    1x

    1x
    1x

    1x

    3x

    7x
    7x

    7x

    7x
    2x

    5x

    5x
    5x

     import { warning } from "ci-log"
    import escapeRegex from "escape-string-regexp"
    import { execa } from "execa"
    import { getAptEnv } from "./apt-env.js"
    import { getApt } from "./get-apt.js"
    import type { AptPackage } from "./install.js"
    import { isAptPackInstalled } from "./is-installed.js"
    import { updateAptReposMemoized, updatedRepos } from "./update.js"

    /**
     * Filter out the packages that are already installed and qualify the packages into a full package name/version
     */
    export async function filterAndQualifyAptPackages(packages: AptPackage[], apt: string = getApt()) {
      return (await Promise.all(packages.map((pack) => qualifiedNeededAptPackage(pack, apt))))
        .filter((pack) => pack !== undefined)
    }

    /**
     * Qualify the package into full package name/version.
     * If the package is not installed, return the full package name/version.
     * If the package is already installed, return undefined
     */
    export async function qualifiedNeededAptPackage(pack: AptPackage, apt: string = getApt()) {
      const info = await findAptPackageInfo(pack.name, pack.version, apt)
      Iif (info === undefined) {
        throw new Error(`Could not find package ${pack.name} ${pack.version}`)
      }

      // Qualify the package into full package name/version
      const qualified = getAptArg(info)

      // filter out the package that are already installed
      return (await isAptPackInstalled(qualified)) ? undefined : qualified
    }

    function getAptArg(info: AptPackageInfo) {
      return `${info.name}=${info.version}`
    }

    export type AptPackageInfo = {
      name: string
      version: string
    }

    /**
     * Get the version of the package from apt-cache show
     * If the version is not found, check if apt-cache search can find the version
     * If the version is still not found, update the repos and try again
     */
    export async function findAptPackageInfo(
      name: string,
      version: string | undefined,
      apt: string = getApt(),
    ): Promise<AptPackageInfo | undefined> {
      const info = await aptCacheShow(name, version, apt)
      if (info !== undefined) {
        return info
      }

      if (version !== undefined) {
        // check if apt-cache search can find the version
        const { stdout } = await execa("apt-cache", [
          "search",
          "--names-only",
          `^${escapeRegex(name)}-${escapeRegex(version)}$`,
        ], { env: getAptEnv(apt), stdio: "pipe" })

        const stdOutTrim = (stdout as string).trim()

        if (stdOutTrim !== "") {
          // get the package name by splitting the first line by " - "
          const packages = stdOutTrim.split("\n")
          const actualName = packages[0].split(" - ")[0]

          // get the version from apt-cache show
          return await aptCacheShow(actualName, undefined, apt)
        }
      }

      // If apt-cache fails, update the repos and try again
      Iif (!updatedRepos) {
        updateAptReposMemoized(apt)
        return findAptPackageInfo(apt, name, version)
      }

      return undefined
    }

    /**
     * Get the info about a package from apt-cache show
     *
     * @param name The name of the package
     * @param version The version of the package
     * @param apt The apt command to use
     *
     * @returns The package info or undefined if the package is not found
     */
    export async function aptCacheShow(
      name: string,
      version: string | undefined,
      apt: string = getApt(),
    ): Promise<AptPackageInfo | undefined> {
      try {
        const { stdout } = await execa("apt-cache", [
          "show",
          version !== undefined && version !== ""
            ? `${name}=${version}`
            : name,
        ], { env: getAptEnv(apt), stdout: "pipe" })

        const stdoutTrim = (stdout as string).trim()

        if (stdoutTrim === "") {
          return undefined
        }

        // parse the version from the output
        // Version: 4:11.2.0-1ubuntu1
        const versionMatch = stdoutTrim.match(/^Version: (.*)$/m)

        if (versionMatch !== null) {
          return {
            name,
            version: versionMatch[1],
          }
        }

        return undefined
      } catch {
        // ignore
      }

      return undefined
    }