@gjermundgaraba/upup 0.2.0 → 0.4.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/README.md +49 -37
- package/dist/{Tui-PjGIvm7J.mjs → Tui-DV_6wbrQ.mjs} +1925 -1690
- package/dist/grep-worker.mjs +204 -0
- package/dist/io-BGJ4Yp7u.mjs +19 -0
- package/dist/main.mjs +11618 -10529
- package/native/dist/upup-sparkle.json +5 -0
- package/native/dist/upup-sparkle.zip +0 -0
- package/package.json +21 -13
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { n as reason, t as openFlags } from "./io-BGJ4Yp7u.mjs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { closeSync, fstatSync, openSync, readSync, readdirSync } from "node:fs";
|
|
4
|
+
import { parentPort } from "node:worker_threads";
|
|
5
|
+
// src/audit/grep-worker.ts
|
|
6
|
+
var GrepError = class extends Error {
|
|
7
|
+
constructor(message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "GrepError";
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
const tooLarge = "grep result exceeds 256 KiB; narrow the path or pattern";
|
|
13
|
+
const decode = (bytes) => {
|
|
14
|
+
try {
|
|
15
|
+
return new TextDecoder("utf-8", {
|
|
16
|
+
fatal: true,
|
|
17
|
+
ignoreBOM: true
|
|
18
|
+
}).decode(bytes);
|
|
19
|
+
} catch {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const utf8Length = (text, from, to) => {
|
|
24
|
+
let bytes = 0;
|
|
25
|
+
for (let i = from; i < to; i += 1) {
|
|
26
|
+
const point = text.codePointAt(i) ?? 0;
|
|
27
|
+
if (point < 128) bytes += 1;
|
|
28
|
+
else if (point < 2048) bytes += 2;
|
|
29
|
+
else if (point > 65535 || point >= 55296 && point <= 56319) bytes += 4;
|
|
30
|
+
else if (point < 56320 || point > 57343) bytes += 3;
|
|
31
|
+
}
|
|
32
|
+
return bytes;
|
|
33
|
+
};
|
|
34
|
+
const runeStart = (bytes, at) => {
|
|
35
|
+
let i = at;
|
|
36
|
+
while (i > 0 && i < bytes.length && ((bytes[i] ?? 0) & 192) === 128) i -= 1;
|
|
37
|
+
return i;
|
|
38
|
+
};
|
|
39
|
+
const grepLine = ({ expression, limits }, every, out, name, number, lineAt, bytes, text) => {
|
|
40
|
+
if (bytes.length <= limits.lineBytes) {
|
|
41
|
+
if (expression.test(text)) out.push(`${name}:${number}:${text}\n`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
every.lastIndex = 0;
|
|
45
|
+
let unit = 0;
|
|
46
|
+
let byte = 0;
|
|
47
|
+
let previousEnd = -1;
|
|
48
|
+
let count = 0;
|
|
49
|
+
while (count < limits.longLineMatches) {
|
|
50
|
+
const match = every.exec(text);
|
|
51
|
+
if (match === null) break;
|
|
52
|
+
const startUnit = match.index;
|
|
53
|
+
const endUnit = startUnit + match[0].length;
|
|
54
|
+
if (startUnit === endUnit) {
|
|
55
|
+
every.lastIndex = endUnit + 1;
|
|
56
|
+
if (startUnit === previousEnd) continue;
|
|
57
|
+
}
|
|
58
|
+
previousEnd = endUnit;
|
|
59
|
+
const startByte = byte + utf8Length(text, unit, startUnit);
|
|
60
|
+
const endByte = startByte + utf8Length(text, startUnit, endUnit);
|
|
61
|
+
unit = endUnit;
|
|
62
|
+
byte = endByte;
|
|
63
|
+
const from = runeStart(bytes, Math.max(0, startByte - limits.contextBytes));
|
|
64
|
+
const to = runeStart(bytes, Math.min(bytes.length, endByte + limits.contextBytes, startByte + limits.lineBytes));
|
|
65
|
+
out.push(`${name}:${number}:byte ${lineAt + startByte}:${bytes.toString("utf-8", from, to)}\n`);
|
|
66
|
+
count += 1;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const scan = (job, every, fd, name, used) => {
|
|
70
|
+
const { limits } = job;
|
|
71
|
+
let buffer = Buffer.allocUnsafe(limits.sniffBytes * 8);
|
|
72
|
+
let start = 0;
|
|
73
|
+
let end = 0;
|
|
74
|
+
let offset = 0;
|
|
75
|
+
let eof = false;
|
|
76
|
+
const fill = () => {
|
|
77
|
+
if (start > 0) {
|
|
78
|
+
buffer.copyWithin(0, start, end);
|
|
79
|
+
end -= start;
|
|
80
|
+
start = 0;
|
|
81
|
+
}
|
|
82
|
+
if (end === buffer.length) {
|
|
83
|
+
if (buffer.length >= limits.scanLineBytes) return false;
|
|
84
|
+
const grown = Buffer.allocUnsafe(Math.min(buffer.length * 2, limits.scanLineBytes));
|
|
85
|
+
buffer.copy(grown, 0, 0, end);
|
|
86
|
+
buffer = grown;
|
|
87
|
+
}
|
|
88
|
+
const count = readSync(fd, buffer, end, buffer.length - end, null);
|
|
89
|
+
if (count === 0) eof = true;
|
|
90
|
+
end += count;
|
|
91
|
+
return true;
|
|
92
|
+
};
|
|
93
|
+
while (end < limits.sniffBytes) {
|
|
94
|
+
fill();
|
|
95
|
+
if (eof) break;
|
|
96
|
+
}
|
|
97
|
+
if (buffer.subarray(0, Math.min(end, limits.sniffBytes)).includes(0)) return { skip: true };
|
|
98
|
+
const out = [];
|
|
99
|
+
let outSize = 0;
|
|
100
|
+
let number = 1;
|
|
101
|
+
for (;;) {
|
|
102
|
+
const newline = buffer.subarray(start, end).indexOf(10);
|
|
103
|
+
let line;
|
|
104
|
+
const lineAt = offset;
|
|
105
|
+
if (newline !== -1) {
|
|
106
|
+
line = buffer.subarray(start, start + newline);
|
|
107
|
+
start += newline + 1;
|
|
108
|
+
offset += newline + 1;
|
|
109
|
+
} else if (eof) {
|
|
110
|
+
if (start === end) break;
|
|
111
|
+
line = buffer.subarray(start, end);
|
|
112
|
+
offset += end - start;
|
|
113
|
+
start = end;
|
|
114
|
+
} else {
|
|
115
|
+
if (!fill()) return {
|
|
116
|
+
matches: out,
|
|
117
|
+
tooLong: number
|
|
118
|
+
};
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (line.length > 0 && line.at(-1) === 13) line = line.subarray(0, -1);
|
|
122
|
+
const text = line.includes(0) ? void 0 : decode(line);
|
|
123
|
+
if (text === void 0) return { notText: true };
|
|
124
|
+
const before = out.length;
|
|
125
|
+
grepLine(job, every, out, name, number, lineAt, line, text);
|
|
126
|
+
for (const found of out.slice(before)) outSize += Buffer.byteLength(found);
|
|
127
|
+
if (used + outSize > limits.resultBytes) throw new GrepError(tooLarge);
|
|
128
|
+
number += 1;
|
|
129
|
+
}
|
|
130
|
+
return { matches: out };
|
|
131
|
+
};
|
|
132
|
+
const runGrep = (job) => {
|
|
133
|
+
const { limits } = job;
|
|
134
|
+
const every = new RegExp(job.expression, "g");
|
|
135
|
+
let result = "";
|
|
136
|
+
let size = 0;
|
|
137
|
+
const add = (text) => {
|
|
138
|
+
result += text;
|
|
139
|
+
size += Buffer.byteLength(text);
|
|
140
|
+
};
|
|
141
|
+
const grepFile = (absolute, name) => {
|
|
142
|
+
let fd;
|
|
143
|
+
try {
|
|
144
|
+
fd = openSync(absolute, openFlags);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
add(`skipped: open ${name}: ${reason(error)}\n`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
if (!fstatSync(fd).isFile()) return;
|
|
151
|
+
let outcome;
|
|
152
|
+
try {
|
|
153
|
+
outcome = scan(job, every, fd, name, size);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
if (error instanceof GrepError) throw error;
|
|
156
|
+
add(`skipped: read ${name}: ${reason(error)}\n`);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if ("skip" in outcome) return;
|
|
160
|
+
if ("notText" in outcome) {
|
|
161
|
+
add(`skipped: ${name} is not UTF-8 text; use read\n`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
add(outcome.matches.join(""));
|
|
165
|
+
if (outcome.tooLong !== void 0) add(`skipped: ${name} from line ${outcome.tooLong}, which exceeds 16 MiB; use read\n`);
|
|
166
|
+
} finally {
|
|
167
|
+
closeSync(fd);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
const walk = (absolute, name) => {
|
|
171
|
+
let entries;
|
|
172
|
+
try {
|
|
173
|
+
entries = readdirSync(absolute, { withFileTypes: true });
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (name === job.name) throw new GrepError(`walk ${name}: ${reason(error)}`);
|
|
176
|
+
add(`skipped: open ${name}: ${reason(error)}\n`);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
entries.sort((left, right) => {
|
|
180
|
+
if (left.name < right.name) return -1;
|
|
181
|
+
return left.name > right.name ? 1 : 0;
|
|
182
|
+
});
|
|
183
|
+
for (const entry of entries) {
|
|
184
|
+
const child = name === "." ? entry.name : `${name}/${entry.name}`;
|
|
185
|
+
const absoluteChild = path.join(absolute, entry.name);
|
|
186
|
+
if (entry.isDirectory()) walk(absoluteChild, child);
|
|
187
|
+
else if (entry.isFile()) grepFile(absoluteChild, child);
|
|
188
|
+
if (size > limits.resultBytes) throw new GrepError(tooLarge);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
try {
|
|
192
|
+
if (job.kind === "directory") walk(job.root, job.name);
|
|
193
|
+
else if (job.kind === "file") grepFile(job.root, job.name);
|
|
194
|
+
if (size > limits.resultBytes) throw new GrepError(tooLarge);
|
|
195
|
+
return { text: size === 0 ? "no matches" : result };
|
|
196
|
+
} catch (error) {
|
|
197
|
+
if (!(error instanceof GrepError)) throw error;
|
|
198
|
+
return { error: `grep workspace: ${error.message}` };
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
parentPort?.once("message", (job) => {
|
|
202
|
+
parentPort?.postMessage(runGrep(job));
|
|
203
|
+
});
|
|
204
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Predicate } from "effect";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
// src/audit/io.ts
|
|
4
|
+
const reasons = /* @__PURE__ */ new Map([
|
|
5
|
+
["ENOENT", "no such file or directory"],
|
|
6
|
+
["ENOTDIR", "not a directory"],
|
|
7
|
+
["EISDIR", "is a directory"],
|
|
8
|
+
["EACCES", "permission denied"],
|
|
9
|
+
["EPERM", "operation not permitted"],
|
|
10
|
+
["ELOOP", "too many levels of symbolic links"],
|
|
11
|
+
["EMFILE", "too many open files"],
|
|
12
|
+
["EIO", "input/output error"]
|
|
13
|
+
]);
|
|
14
|
+
const reason = (cause) => {
|
|
15
|
+
const code = cause instanceof Error && "code" in cause && Predicate.isString(cause.code) ? cause.code : void 0;
|
|
16
|
+
return code === void 0 ? "I/O error" : reasons.get(code) ?? code;
|
|
17
|
+
};
|
|
18
|
+
const openFlags = constants.O_RDONLY | constants.O_NONBLOCK | constants.O_NOFOLLOW;
|
|
19
|
+
export { reason as n, openFlags as t };
|