@foldrun/core 1.4.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/tar.ts DELETED
@@ -1,125 +0,0 @@
1
- // Reading a .tar.gz, so a deploy can come from a GitHub tarball.
2
- //
3
- // `GET /repos/{owner}/{repo}/tarball/{sha}` is one HTTP call and needs no git
4
- // binary, no clone, and no working copy on the server — which is the whole
5
- // reason a markdown workspace can deploy from a webhook in a few hundred
6
- // milliseconds. What it returns is a tar.gz, and Node has gzip but not tar.
7
- //
8
- // Deliberately dependency-free, for the same reason the cron parser is: tar is
9
- // 512-byte blocks with an octal size field, and a reader is shorter than the
10
- // argument about which library to add.
11
-
12
- import zlib from "node:zlib";
13
- import { trimSlashes } from "./paths.ts";
14
-
15
- const BLOCK = 512;
16
-
17
- export interface TarEntry {
18
- path: string;
19
- content: Buffer;
20
- }
21
-
22
- const str = (b: Buffer, off: number, len: number) => {
23
- const raw = b.subarray(off, off + len);
24
- const end = raw.indexOf(0);
25
- return raw.subarray(0, end === -1 ? raw.length : end).toString("utf8");
26
- };
27
-
28
- // Sizes are octal, space- or NUL-padded. GNU also writes base-256 for large
29
- // files, flagged by the high bit — irrelevant for markdown, but a silent
30
- // misparse would be worse than a clear refusal.
31
- function size(b: Buffer, off: number): number {
32
- if (b[off] & 0x80) throw new Error("tar: base-256 size field is not supported");
33
- const raw = str(b, off, 12).trim();
34
- return raw ? parseInt(raw, 8) : 0;
35
- }
36
-
37
- /**
38
- * Every regular file in a gzipped tarball.
39
- *
40
- * Directories, symlinks, and pax/global headers are skipped: a workspace is
41
- * files, and following a link out of the archive is exactly the thing a deploy
42
- * from an untrusted repository must not do.
43
- */
44
- export function readTarGz(gz: Buffer): TarEntry[] {
45
- const buf = zlib.gunzipSync(gz);
46
- const out: TarEntry[] = [];
47
- let longName: string | null = null;
48
-
49
- for (let off = 0; off + BLOCK <= buf.length; ) {
50
- const header = buf.subarray(off, off + BLOCK);
51
- // Two consecutive zero blocks end the archive; one is enough to stop.
52
- if (header.every((b) => b === 0)) break;
53
-
54
- const name = str(header, 0, 100);
55
- const bytes = size(header, 124);
56
- const type = String.fromCharCode(header[156]) || "0";
57
- const prefix = str(header, 345, 155);
58
- off += BLOCK;
59
-
60
- const body = buf.subarray(off, off + bytes);
61
- off += Math.ceil(bytes / BLOCK) * BLOCK;
62
-
63
- // GNU long name: the next header's name lives in this entry's body.
64
- if (type === "L") {
65
- longName = body.toString("utf8").replace(/\0+$/, "");
66
- continue;
67
- }
68
- if (type !== "0" && type !== "\0") {
69
- longName = null;
70
- continue;
71
- }
72
-
73
- const full = longName ?? (prefix ? `${prefix}/${name}` : name);
74
- longName = null;
75
- if (full) out.push({ path: full, content: Buffer.from(body) });
76
- }
77
-
78
- return out;
79
- }
80
-
81
- /**
82
- * Drop the single wrapper directory an archive is rooted at.
83
- *
84
- * GitHub tarballs are rooted at `owner-repo-<sha>/`, which is not part of
85
- * anyone's workspace. Stripped only when *every* entry shares one first
86
- * segment, so an archive that is already flat is left alone.
87
- */
88
- export function stripRoot(entries: TarEntry[]): TarEntry[] {
89
- if (entries.length === 0) return entries;
90
- const first = entries[0].path.split("/")[0];
91
- if (!entries.every((e) => e.path.split("/")[0] === first && e.path.includes("/"))) {
92
- return entries;
93
- }
94
- return entries.map((e) => ({ ...e, path: e.path.slice(first.length + 1) }));
95
- }
96
-
97
- /**
98
- * Read a tarball as the file list a deploy ships.
99
- *
100
- * `subdir` picks a workspace living inside a larger repository — the common
101
- * case, since a repo usually holds more than one thing.
102
- */
103
- export function filesFromTarball(gz: Buffer, subdir = ""): { path: string; content: string }[] {
104
- const prefix = trimSlashes(subdir, "both");
105
- const out: { path: string; content: string }[] = [];
106
-
107
- for (const entry of stripRoot(readTarGz(gz))) {
108
- let rel = entry.path;
109
- if (prefix) {
110
- if (!rel.startsWith(`${prefix}/`)) continue;
111
- rel = rel.slice(prefix.length + 1);
112
- }
113
- // A tarball is untrusted input: an entry can name anything at all, and
114
- // `../` in an archive is the oldest way to write outside the destination.
115
- if (rel.split("/").includes("..") || rel.startsWith("/")) continue;
116
- // AppleDouble sidecars. macOS `tar` writes a `._name` beside every file to
117
- // carry metadata no other system wants; they are not part of anyone's
118
- // workspace, and letting one through means a deploy rejected for shipping
119
- // a file the author never wrote.
120
- if ((rel.split("/").pop() ?? "").startsWith("._")) continue;
121
- out.push({ path: rel, content: entry.content.toString("utf8") });
122
- }
123
-
124
- return out;
125
- }