-
Notifications
You must be signed in to change notification settings - Fork 39
/
_utils.ts
138 lines (121 loc) · 3.61 KB
/
_utils.ts
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
import { createWriteStream, existsSync } from "node:fs";
import { pipeline } from "node:stream";
import { spawnSync } from "node:child_process";
import { readFile, writeFile } from "node:fs/promises";
import { homedir } from "node:os";
import { promisify } from "node:util";
import type { Agent } from "node:http";
import { relative, resolve } from "pathe";
import { fetch } from "node-fetch-native/proxy";
import type { GitInfo } from "./types";
export async function download(
url: string,
filePath: string,
options: { headers?: Record<string, string | undefined> } = {},
) {
const infoPath = filePath + ".json";
const info: { etag?: string } = JSON.parse(
await readFile(infoPath, "utf8").catch(() => "{}"),
);
// eslint-disable-next-line unicorn/no-useless-undefined
const headResponse = await sendFetch(url, {
method: "HEAD",
headers: options.headers,
}).catch(() => undefined);
const etag = headResponse?.headers.get("etag");
if (info.etag === etag && existsSync(filePath)) {
// Already downloaded
return;
}
if (typeof etag === "string") {
info.etag = etag;
}
const response = await sendFetch(url, { headers: options.headers });
if (response.status >= 400) {
throw new Error(
`Failed to download ${url}: ${response.status} ${response.statusText}`,
);
}
const stream = createWriteStream(filePath);
await promisify(pipeline)(response.body as any, stream);
await writeFile(infoPath, JSON.stringify(info), "utf8");
}
const inputRegex =
/^(?<repo>[\w.-]+\/[\w.-]+)(?<subdir>[^#]+)?(?<ref>#[\w./@-]+)?/;
export function parseGitURI(input: string): GitInfo {
const m = input.match(inputRegex)?.groups || {};
return <GitInfo>{
repo: m.repo,
subdir: m.subdir || "/",
ref: m.ref ? m.ref.slice(1) : "main",
};
}
export function debug(...args: unknown[]) {
if (process.env.DEBUG) {
console.debug("[giget]", ...args);
}
}
// eslint-disable-next-line no-undef
interface InternalFetchOptions extends Omit<RequestInit, "headers"> {
headers?: Record<string, string | undefined>;
agent?: Agent;
validateStatus?: boolean;
}
export async function sendFetch(
url: string,
options: InternalFetchOptions = {},
) {
// https://github.com/nodejs/undici/issues/1305
if (options.headers?.["sec-fetch-mode"]) {
options.mode = options.headers["sec-fetch-mode"] as any;
}
const res = await fetch(url, {
...options,
headers: normalizeHeaders(options.headers),
}).catch((error: any) => {
throw new Error(`Failed to download ${url}: ${error}`, { cause: error });
});
if (options.validateStatus && res.status >= 400) {
throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`);
}
return res;
}
export function cacheDirectory() {
return process.env.XDG_CACHE_HOME
? resolve(process.env.XDG_CACHE_HOME, "giget")
: resolve(homedir(), ".cache/giget");
}
export function normalizeHeaders(
headers: Record<string, string | undefined> = {},
) {
const normalized: Record<string, string> = {};
for (const [key, value] of Object.entries(headers)) {
if (!value) {
continue;
}
normalized[key.toLowerCase()] = value;
}
return normalized;
}
// -- Experimental --
export function currentShell() {
if (process.env.SHELL) {
return process.env.SHELL;
}
if (process.platform === "win32") {
return "cmd.exe";
}
return "/bin/bash";
}
export function startShell(cwd: string) {
cwd = resolve(cwd);
const shell = currentShell();
console.info(
`(experimental) Opening shell in ${relative(process.cwd(), cwd)}...`,
);
spawnSync(shell, [], {
cwd,
shell: true,
stdio: "inherit",
});
}