-
Notifications
You must be signed in to change notification settings - Fork 240
/
Copy pathpostinstall.js
executable file
·180 lines (155 loc) · 5.62 KB
/
postinstall.js
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#!/usr/bin/env node
// Ref 1: https://github.com/sanathkr/go-npm
// Ref 2: https://medium.com/xendit-engineering/how-we-repurposed-npm-to-publish-and-distribute-our-go-binaries-for-internal-cli-23981b80911b
"use strict";
import binLinks from "bin-links";
import { createHash } from "crypto";
import fs from "fs";
import fetch from "node-fetch";
import { Agent } from "https";
import { HttpsProxyAgent } from "https-proxy-agent";
import path from "path";
import { extract } from "tar";
import zlib from "zlib";
// Mapping from Node's `process.arch` to Golang's `$GOARCH`
const ARCH_MAPPING = {
x64: "amd64",
arm64: "arm64",
};
// Mapping between Node's `process.platform` to Golang's
const PLATFORM_MAPPING = {
darwin: "darwin",
linux: "linux",
win32: "windows",
};
const arch = ARCH_MAPPING[process.arch];
const platform = PLATFORM_MAPPING[process.platform];
// TODO: import pkg from "../package.json" assert { type: "json" };
const readPackageJson = async () => {
const contents = await fs.promises.readFile("package.json");
return JSON.parse(contents);
};
// Build the download url from package.json
const getDownloadUrl = (packageJson) => {
const pkgName = packageJson.name;
const version = packageJson.version;
const repo = packageJson.repository;
const url = `https://github.com/${repo}/releases/download/v${version}/${pkgName}_${platform}_${arch}.tar.gz`;
return url;
};
const fetchAndParseCheckSumFile = async (packageJson, agent) => {
const version = packageJson.version;
const pkgName = packageJson.name;
const repo = packageJson.repository;
const checksumFileUrl = `https://github.com/${repo}/releases/download/v${version}/${pkgName}_${version}_checksums.txt`;
// Fetch the checksum file
console.info("Downloading", checksumFileUrl);
const response = await fetch(checksumFileUrl, { agent });
if (response.ok) {
const checkSumContent = await response.text();
const lines = checkSumContent.split("\n");
const checksums = {};
for (const line of lines) {
const [checksum, packageName] = line.split(/\s+/);
checksums[packageName] = checksum;
}
return checksums;
} else {
console.error(
"Could not fetch checksum file",
response.status,
response.statusText
);
}
};
const errGlobal = `Installing Supabase CLI as a global module is not supported.
Please use one of the supported package managers: https://github.com/supabase/cli#install-the-cli
`;
const errChecksum = "Checksum mismatch. Downloaded data might be corrupted.";
const errUnsupported = `Installation is not supported for ${process.platform} ${process.arch}`;
/**
* Reads the configuration from application's package.json,
* downloads the binary from package url and stores at
* ./bin in the package's root.
*
* See: https://docs.npmjs.com/files/package.json#bin
*/
async function main() {
const yarnGlobal = JSON.parse(
process.env.npm_config_argv || "{}"
).original?.includes("global");
if (process.env.npm_config_global || yarnGlobal) {
throw errGlobal;
}
if (!arch || !platform) {
throw errUnsupported;
}
// Read from package.json and prepare for the installation.
const pkg = await readPackageJson();
if (platform === "windows") {
// Update bin path in package.json
pkg.bin[pkg.name] += ".exe";
}
// Prepare the installation path by creating the directory if it doesn't exist.
const binPath = pkg.bin[pkg.name];
const binDir = path.dirname(binPath);
await fs.promises.mkdir(binDir, { recursive: true });
// Create the agent that will be used for all the fetch requests later.
const proxyUrl =
process.env.npm_config_https_proxy ||
process.env.npm_config_http_proxy ||
process.env.npm_config_proxy;
// Keeps the TCP connection alive when sending multiple requests
// Ref: https://github.com/node-fetch/node-fetch/issues/1735
const agent = proxyUrl
? new HttpsProxyAgent(proxyUrl, { keepAlive: true })
: new Agent({ keepAlive: true });
// First, fetch the checksum map.
const checksumMap = await fetchAndParseCheckSumFile(pkg, agent);
// Then, download the binary.
const url = getDownloadUrl(pkg);
console.info("Downloading", url);
const resp = await fetch(url, { agent });
const hash = createHash("sha256");
const pkgNameWithPlatform = `${pkg.name}_${platform}_${arch}.tar.gz`;
// Then, decompress the binary -- we will first Un-GZip, then we will untar.
const ungz = zlib.createGunzip();
const binName = path.basename(binPath);
const untar = extract({ cwd: binDir }, [binName]);
// Update the hash with the binary data as it's being downloaded.
resp.body
.on("data", (chunk) => {
hash.update(chunk);
})
// Pipe the data to the ungz stream.
.pipe(ungz);
// After the ungz stream has ended, verify the checksum.
ungz
.on("end", () => {
const expectedChecksum = checksumMap?.[pkgNameWithPlatform];
// Skip verification if we can't find the file checksum
if (!expectedChecksum) {
console.warn("Skipping checksum verification");
return;
}
const calculatedChecksum = hash.digest("hex");
if (calculatedChecksum !== expectedChecksum) {
throw errChecksum;
}
console.info("Checksum verified.");
})
// Pipe the data to the untar stream.
.pipe(untar);
// Wait for the untar stream to finish.
await new Promise((resolve, reject) => {
untar.on("error", reject);
untar.on("end", () => resolve());
});
// Link the binaries in postinstall to support yarn
await binLinks({
path: path.resolve("."),
pkg: { ...pkg, bin: { [pkg.name]: binPath } },
});
console.info("Installed Supabase CLI successfully");
}
await main();