-
Notifications
You must be signed in to change notification settings - Fork 19
Expand file tree
/
Copy pathinstall.js
More file actions
120 lines (105 loc) · 3.54 KB
/
install.js
File metadata and controls
120 lines (105 loc) · 3.54 KB
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
#!/usr/bin/env node
const { execFileSync } = require("child_process");
const { createWriteStream, copyFileSync, chmodSync, unlinkSync, mkdtempSync, rmSync } = require("fs");
const os = require("os");
const https = require("https");
const http = require("http");
const path = require("path");
const { pipeline } = require("stream/promises");
const VERSION = require("./package.json").version;
const REPO = "naorpeled/aitutor";
const PLATFORM_MAP = {
darwin: "darwin",
linux: "linux",
win32: "windows",
};
const ARCH_MAP = {
x64: "amd64",
arm64: "arm64",
};
function getDownloadURL() {
const platform = PLATFORM_MAP[process.platform];
const arch = ARCH_MAP[process.arch];
if (!platform || !arch) {
throw new Error(
`Unsupported platform: ${process.platform} ${process.arch}\n` +
`Supported: darwin (amd64, arm64), linux (amd64, arm64), windows (amd64, arm64)`
);
}
const ext = process.platform === "win32" ? "zip" : "tar.gz";
return `https://github.com/${REPO}/releases/download/v${VERSION}/aitutor_${platform}_${arch}.${ext}`;
}
function getBinaryName() {
return process.platform === "win32" ? "aitutor.exe" : "aitutor";
}
function follow(url, depth = 0) {
if (depth > 10) {
return Promise.reject(new Error("Too many redirects"));
}
const mod = url.startsWith("https") ? https : http;
return new Promise((resolve, reject) => {
mod
.get(url, { headers: { "User-Agent": "aitutor-npm" } }, (res) => {
if (
res.statusCode >= 300 &&
res.statusCode < 400 &&
res.headers.location
) {
return follow(res.headers.location, depth + 1).then(resolve, reject);
}
if (res.statusCode !== 200) {
return reject(
new Error(`Download failed: HTTP ${res.statusCode} from ${url}`)
);
}
resolve(res);
})
.on("error", reject);
});
}
async function install() {
const url = getDownloadURL();
const binName = getBinaryName();
const binPath = path.join(__dirname, binName);
console.log(`Downloading aitutor v${VERSION}...`);
console.log(` ${url}`);
const res = await follow(url);
if (process.platform === "win32") {
const zipPath = path.join(__dirname, "aitutor.zip");
const tmpDir = mkdtempSync(path.join(os.tmpdir(), "aitutor-"));
try {
await pipeline(res, createWriteStream(zipPath));
const psEscape = (s) => s.replace(/'/g, "''");
const psPath = path.join(process.env.SystemRoot || "C:\\Windows", "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
execFileSync(psPath, [
"-NoProfile",
"-NonInteractive",
"-Command",
`Expand-Archive -LiteralPath '${psEscape(zipPath)}' -DestinationPath '${psEscape(tmpDir)}' -Force -ErrorAction Stop`,
]);
copyFileSync(path.join(tmpDir, binName), binPath);
} finally {
rmSync(tmpDir, { recursive: true, force: true });
try { unlinkSync(zipPath); } catch {}
}
} else {
const tarPath = path.join(__dirname, "aitutor.tar.gz");
try {
await pipeline(res, createWriteStream(tarPath));
execFileSync("tar", ["-xzf", tarPath, "-C", __dirname, binName], {
stdio: "ignore",
});
} finally {
try { unlinkSync(tarPath); } catch {}
}
}
chmodSync(binPath, 0o755);
console.log(`Installed aitutor to ${binPath}`);
}
install().catch((err) => {
console.error(`Failed to install aitutor: ${err.message}`);
console.error(
`\nYou can install manually with: go install github.com/naorpeled/aitutor@latest`
);
process.exit(1);
});