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();