github.com/supabase/cli@v1.168.1/scripts/postinstall.js (about)

     1  #!/usr/bin/env node
     2  
     3  // Ref 1: https://github.com/sanathkr/go-npm
     4  // Ref 2: https://blog.xendit.engineer/how-we-repurposed-npm-to-publish-and-distribute-our-go-binaries-for-internal-cli-23981b80911b
     5  "use strict";
     6  
     7  import binLinks from "bin-links";
     8  import { createHash } from "crypto";
     9  import fs from "fs";
    10  import fetch from "node-fetch";
    11  import { Agent } from "https";
    12  import { HttpsProxyAgent } from "https-proxy-agent";
    13  import path from "path";
    14  import { extract } from "tar";
    15  import zlib from "zlib";
    16  
    17  // Mapping from Node's `process.arch` to Golang's `$GOARCH`
    18  const ARCH_MAPPING = {
    19    x64: "amd64",
    20    arm64: "arm64",
    21  };
    22  
    23  // Mapping between Node's `process.platform` to Golang's
    24  const PLATFORM_MAPPING = {
    25    darwin: "darwin",
    26    linux: "linux",
    27    win32: "windows",
    28  };
    29  
    30  const arch = ARCH_MAPPING[process.arch];
    31  const platform = PLATFORM_MAPPING[process.platform];
    32  
    33  // TODO: import pkg from "../package.json" assert { type: "json" };
    34  const readPackageJson = async () => {
    35    const contents = await fs.promises.readFile("package.json");
    36    return JSON.parse(contents);
    37  };
    38  
    39  // Build the download url from package.json
    40  const getDownloadUrl = (packageJson) => {
    41    const pkgName = packageJson.name;
    42    const version = packageJson.version;
    43    const repo = packageJson.repository;
    44    const url = `https://github.com/${repo}/releases/download/v${version}/${pkgName}_${platform}_${arch}.tar.gz`;
    45    return url;
    46  };
    47  
    48  const fetchAndParseCheckSumFile = async (packageJson, agent) => {
    49    const version = packageJson.version;
    50    const pkgName = packageJson.name;
    51    const repo = packageJson.repository;
    52    const checksumFileUrl = `https://github.com/${repo}/releases/download/v${version}/${pkgName}_${version}_checksums.txt`;
    53  
    54    // Fetch the checksum file
    55    console.info("Downloading", checksumFileUrl);
    56    const response = await fetch(checksumFileUrl, { agent });
    57    if (response.ok) {
    58      const checkSumContent = await response.text();
    59      const lines = checkSumContent.split("\n");
    60  
    61      const checksums = {};
    62      for (const line of lines) {
    63        const [checksum, packageName] = line.split(/\s+/);
    64        checksums[packageName] = checksum;
    65      }
    66  
    67      return checksums;
    68    } else {
    69      console.error(
    70        "Could not fetch checksum file",
    71        response.status,
    72        response.statusText
    73      );
    74    }
    75  };
    76  
    77  const errGlobal = `Installing Supabase CLI as a global module is not supported.
    78  Please use one of the supported package managers: https://github.com/supabase/cli#install-the-cli
    79  `;
    80  const errChecksum = "Checksum mismatch. Downloaded data might be corrupted.";
    81  const errUnsupported = `Installation is not supported for ${process.platform} ${process.arch}`;
    82  
    83  /**
    84   * Reads the configuration from application's package.json,
    85   * downloads the binary from package url and stores at
    86   * ./bin in the package's root.
    87   *
    88   *  See: https://docs.npmjs.com/files/package.json#bin
    89   */
    90  async function main() {
    91    const yarnGlobal = JSON.parse(
    92      process.env.npm_config_argv || "{}"
    93    ).original?.includes("global");
    94    if (process.env.npm_config_global || yarnGlobal) {
    95      throw errGlobal;
    96    }
    97    if (!arch || !platform) {
    98      throw errUnsupported;
    99    }
   100  
   101    const pkg = await readPackageJson();
   102    if (platform === "windows") {
   103      // Update bin path in package.json
   104      pkg.bin[pkg.name] += ".exe";
   105    }
   106  
   107    const binPath = pkg.bin[pkg.name];
   108    const binDir = path.dirname(binPath);
   109    await fs.promises.mkdir(binDir, { recursive: true });
   110  
   111    // First we will Un-GZip, then we will untar.
   112    const ungz = zlib.createGunzip();
   113    const binName = path.basename(binPath);
   114    const untar = extract({ cwd: binDir }, [binName]);
   115  
   116    const url = getDownloadUrl(pkg);
   117    console.info("Downloading", url);
   118    const proxyUrl =
   119      process.env.npm_config_https_proxy ||
   120      process.env.npm_config_http_proxy ||
   121      process.env.npm_config_proxy;
   122  
   123    // Keeps the TCP connection alive when sending multiple requests
   124    // Ref: https://github.com/node-fetch/node-fetch/issues/1735
   125    const agent = proxyUrl
   126      ? new HttpsProxyAgent(proxyUrl, { keepAlive: true })
   127      : new Agent({ keepAlive: true });
   128    const resp = await fetch(url, { agent });
   129  
   130    const hash = createHash("sha256");
   131    const pkgNameWithPlatform = `${pkg.name}_${platform}_${arch}.tar.gz`;
   132    const checksumMap = await fetchAndParseCheckSumFile(pkg, agent);
   133  
   134    resp.body
   135      .on("data", (chunk) => {
   136        hash.update(chunk);
   137      })
   138      .pipe(ungz);
   139  
   140    ungz
   141      .on("end", () => {
   142        const expectedChecksum = checksumMap?.[pkgNameWithPlatform];
   143        // Skip verification if we can't find the file checksum
   144        if (!expectedChecksum) {
   145          console.warn("Skipping checksum verification");
   146          return;
   147        }
   148        const calculatedChecksum = hash.digest("hex");
   149        if (calculatedChecksum !== expectedChecksum) {
   150          throw errChecksum;
   151        }
   152        console.info("Checksum verified.");
   153      })
   154      .pipe(untar);
   155  
   156    await new Promise((resolve, reject) => {
   157      untar.on("error", reject);
   158      untar.on("end", () => resolve());
   159    });
   160  
   161    // Link the binaries in postinstall to support yarn
   162    await binLinks({
   163      path: path.resolve("."),
   164      pkg: { ...pkg, bin: { [pkg.name]: binPath } },
   165    });
   166  
   167    console.info("Installed Supabase CLI successfully");
   168  }
   169  
   170  await main();