@danypops/pi-packed 0.19.7 → 0.19.10
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/dist/client.d.ts +109 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +1 -0
- package/dist/protocol.d.ts +221 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +1 -0
- package/extension/src/{permission.ts → approval/permission.ts} +1 -1
- package/extension/src/index.ts +1 -1
- package/extension/src/packed.ts +2 -2
- package/extension/src/{discover.ts → tabs/discover.ts} +4 -4
- package/extension/src/{resource-config.ts → tabs/resource-config.ts} +4 -4
- package/extension/src/{security-tui.ts → tabs/security-tui.ts} +3 -3
- package/extension/src/tool-output.ts +1 -1
- package/extension/src/tools.ts +2 -2
- package/extension/src/tui.ts +4 -4
- package/package.json +31 -8
- package/service/schema/pi-setup-v1.schema.json +70 -0
- package/service/setup/danypops-ecosystem.pi-setup.json +15 -0
- package/service/src/adoption/advisories.ts +268 -0
- package/service/src/adoption/check.ts +872 -0
- package/service/src/adoption/commit-freshness.ts +167 -0
- package/service/src/adoption/doctor.ts +135 -0
- package/service/src/adoption/install-validation.ts +187 -0
- package/service/src/adoption/pack.ts +291 -0
- package/service/src/adoption/score.ts +466 -0
- package/service/src/adoption/smoke-child.ts +113 -0
- package/service/src/adoption/smoke.ts +282 -0
- package/service/src/cli/cli.ts +926 -0
- package/service/src/daemon/cleanup.ts +76 -0
- package/service/src/daemon/client.ts +412 -0
- package/service/src/daemon/daemon-service.ts +249 -0
- package/service/src/daemon/daemon.ts +110 -0
- package/service/src/daemon/service.ts +664 -0
- package/service/src/daemon/watcher.ts +92 -0
- package/service/src/index/build-index.ts +256 -0
- package/service/src/packages/catalog.ts +61 -0
- package/service/src/packages/db.ts +224 -0
- package/service/src/packages/install.ts +60 -0
- package/service/src/packages/installed.ts +123 -0
- package/service/src/packages/package.ts +141 -0
- package/service/src/packages/resources.ts +203 -0
- package/service/src/pi/pi-version.ts +171 -0
- package/service/src/public/atomic-json.ts +32 -0
- package/service/src/public/client.ts +277 -0
- package/service/src/public/protocol.ts +169 -0
- package/service/src/publish/publish.ts +855 -0
- package/service/src/registry/registry.ts +246 -0
- package/service/src/security/security.ts +128 -0
- package/service/src/self-update/self-update.ts +148 -0
- package/service/src/setup/setup.ts +761 -0
- package/service/src/shared/atomic-json.ts +33 -0
- package/service/src/shared/cache.ts +21 -0
- package/service/src/shared/constants.ts +73 -0
- package/service/src/shared/log.ts +21 -0
- package/service/src/shared/paths.ts +88 -0
- package/service/src/shared/state.ts +15 -0
- package/service/src/shared/version.ts +46 -0
- package/service/test/advisories.test.ts +287 -0
- package/service/test/check.test.ts +368 -0
- package/service/test/cleanup.test.ts +220 -0
- package/service/test/cli.test.ts +1303 -0
- package/service/test/core.test.ts +181 -0
- package/service/test/daemon-kit-migration.test.ts +181 -0
- package/service/test/daemon-service.test.ts +238 -0
- package/service/test/db.test.ts +178 -0
- package/service/test/doctor.test.ts +234 -0
- package/service/test/domain.test.ts +291 -0
- package/service/test/fixtures/install-validation/broken-package/extension/index.ts +3 -0
- package/service/test/fixtures/install-validation/broken-package/package.json +8 -0
- package/service/test/fixtures/install-validation/healthy-package/extension/index.ts +3 -0
- package/service/test/fixtures/install-validation/healthy-package/package.json +8 -0
- package/service/test/fixtures/install-validation/no-manifest-package/package.json +5 -0
- package/service/test/index.test.ts +353 -0
- package/service/test/install-validation.test.ts +114 -0
- package/service/test/install.test.ts +113 -0
- package/service/test/log.test.ts +42 -0
- package/service/test/pack-score.test.ts +513 -0
- package/service/test/pi-version.test.ts +318 -0
- package/service/test/public-boundary.test.ts +54 -0
- package/service/test/public-client.test.ts +127 -0
- package/service/test/public-consumer.ts +8 -0
- package/service/test/publish.test.ts +333 -0
- package/service/test/registry-contract.test.ts +148 -0
- package/service/test/resources.test.ts +255 -0
- package/service/test/security.test.ts +89 -0
- package/service/test/self-update.test.ts +257 -0
- package/service/test/service.test.ts +555 -0
- package/service/test/setup.test.ts +375 -0
- package/service/test/smoke.test.ts +118 -0
- package/service/test/version.test.ts +37 -0
- package/service/tsconfig.consumer.json +13 -0
- package/service/tsconfig.public.json +12 -0
- /package/extension/src/{reload.ts → approval/reload.ts} +0 -0
- /package/extension/src/{discover-model.ts → tabs/discover-model.ts} +0 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
2
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
export type SmokeStatus = "ok" | "timeout" | "crash" | "capability-denied" | "output-limit" | "sandbox-unavailable";
|
|
6
|
+
|
|
7
|
+
export interface SmokeRegistrations {
|
|
8
|
+
tools: string[];
|
|
9
|
+
commands: string[];
|
|
10
|
+
shortcuts: string[];
|
|
11
|
+
flags: string[];
|
|
12
|
+
providers: string[];
|
|
13
|
+
events: string[];
|
|
14
|
+
renderers: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ExtensionSmokeResult {
|
|
18
|
+
path: string;
|
|
19
|
+
status: SmokeStatus;
|
|
20
|
+
durationMs: number;
|
|
21
|
+
registrations: SmokeRegistrations;
|
|
22
|
+
message?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface SmokeOptions {
|
|
26
|
+
timeoutMs?: number;
|
|
27
|
+
maxOutputBytes?: number;
|
|
28
|
+
maxProcesses?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const DEFAULT_TIMEOUT_MS = 3_000;
|
|
32
|
+
const DEFAULT_MAX_OUTPUT_BYTES = 64 * 1024;
|
|
33
|
+
const DEFAULT_MAX_PROCESSES = 16;
|
|
34
|
+
const MAX_TIMEOUT_MS = 10_000;
|
|
35
|
+
const MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
36
|
+
const MAX_PROCESSES = 32;
|
|
37
|
+
const EMPTY_REGISTRATIONS: SmokeRegistrations = {
|
|
38
|
+
tools: [],
|
|
39
|
+
commands: [],
|
|
40
|
+
shortcuts: [],
|
|
41
|
+
flags: [],
|
|
42
|
+
providers: [],
|
|
43
|
+
events: [],
|
|
44
|
+
renderers: [],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function bounded(value: number | undefined, fallback: number, maximum: number): number {
|
|
48
|
+
if (!Number.isFinite(value) || value === undefined || value <= 0) return fallback;
|
|
49
|
+
return Math.min(Math.floor(value), maximum);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function addReadOnlyBind(args: string[], path: string): void {
|
|
53
|
+
if (existsSync(path)) args.push("--ro-bind", path, path);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function sandboxCommand(packageRoot: string, extensionPath: string, maxProcesses: number): string[] | undefined {
|
|
57
|
+
if (process.platform !== "linux" || !existsSync("/usr/bin/bwrap") || !existsSync("/usr/bin/prlimit")) return undefined;
|
|
58
|
+
const serviceRoot = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
59
|
+
const packageDirectory = dirname(serviceRoot);
|
|
60
|
+
const childHarness = join(serviceRoot, "src/adoption/smoke-child.ts");
|
|
61
|
+
const runnerModules = join(packageDirectory, "node_modules");
|
|
62
|
+
const jitiModules = realpathSync(join(runnerModules, "jiti"));
|
|
63
|
+
const bun = realpathSync(process.execPath);
|
|
64
|
+
const relativeExtension = relative(packageRoot, extensionPath).split(sep).join("/");
|
|
65
|
+
const args = [
|
|
66
|
+
"/usr/bin/bwrap",
|
|
67
|
+
"--unshare-user",
|
|
68
|
+
"--disable-userns",
|
|
69
|
+
"--unshare-pid",
|
|
70
|
+
"--unshare-net",
|
|
71
|
+
"--unshare-ipc",
|
|
72
|
+
"--unshare-uts",
|
|
73
|
+
"--cap-drop",
|
|
74
|
+
"ALL",
|
|
75
|
+
"--die-with-parent",
|
|
76
|
+
"--new-session",
|
|
77
|
+
];
|
|
78
|
+
addReadOnlyBind(args, "/usr");
|
|
79
|
+
addReadOnlyBind(args, "/lib");
|
|
80
|
+
addReadOnlyBind(args, "/lib64");
|
|
81
|
+
args.push(
|
|
82
|
+
"--dir",
|
|
83
|
+
"/runner",
|
|
84
|
+
"--dir",
|
|
85
|
+
"/runner/src",
|
|
86
|
+
"--ro-bind",
|
|
87
|
+
childHarness,
|
|
88
|
+
"/runner/src/smoke-child.ts",
|
|
89
|
+
"--dir",
|
|
90
|
+
"/runner/node_modules",
|
|
91
|
+
"--ro-bind",
|
|
92
|
+
jitiModules,
|
|
93
|
+
"/runner/node_modules/jiti",
|
|
94
|
+
"--ro-bind",
|
|
95
|
+
packageRoot,
|
|
96
|
+
"/package",
|
|
97
|
+
"--ro-bind",
|
|
98
|
+
bun,
|
|
99
|
+
"/bun",
|
|
100
|
+
"--dev",
|
|
101
|
+
"/dev",
|
|
102
|
+
"--proc",
|
|
103
|
+
"/proc",
|
|
104
|
+
"--size",
|
|
105
|
+
"16777216",
|
|
106
|
+
"--tmpfs",
|
|
107
|
+
"/tmp",
|
|
108
|
+
"--dir",
|
|
109
|
+
"/tmp/home",
|
|
110
|
+
"--remount-ro",
|
|
111
|
+
"/",
|
|
112
|
+
"--clearenv",
|
|
113
|
+
"--setenv",
|
|
114
|
+
"HOME",
|
|
115
|
+
"/tmp/home",
|
|
116
|
+
"--setenv",
|
|
117
|
+
"TMPDIR",
|
|
118
|
+
"/tmp",
|
|
119
|
+
"--setenv",
|
|
120
|
+
"PATH",
|
|
121
|
+
"/usr/bin:/bin",
|
|
122
|
+
"--setenv",
|
|
123
|
+
"NODE_PATH",
|
|
124
|
+
"/runner/node_modules",
|
|
125
|
+
"--chdir",
|
|
126
|
+
"/package",
|
|
127
|
+
"--",
|
|
128
|
+
"/usr/bin/prlimit",
|
|
129
|
+
`--nproc=${maxProcesses}`,
|
|
130
|
+
"--nofile=64",
|
|
131
|
+
"--fsize=16777216",
|
|
132
|
+
"--as=8589934592",
|
|
133
|
+
"--cpu=5",
|
|
134
|
+
"--",
|
|
135
|
+
"/bun",
|
|
136
|
+
"/runner/src/smoke-child.ts",
|
|
137
|
+
`/package/${relativeExtension}`,
|
|
138
|
+
);
|
|
139
|
+
return args;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function readBounded(stream: ReadableStream<Uint8Array>, maxBytes: number, onLimit: () => void): Promise<string> {
|
|
143
|
+
const reader = stream.getReader();
|
|
144
|
+
const chunks: Uint8Array[] = [];
|
|
145
|
+
let bytes = 0;
|
|
146
|
+
try {
|
|
147
|
+
while (true) {
|
|
148
|
+
const { done, value } = await reader.read();
|
|
149
|
+
if (done) break;
|
|
150
|
+
bytes += value.byteLength;
|
|
151
|
+
if (bytes > maxBytes) {
|
|
152
|
+
onLimit();
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
chunks.push(value);
|
|
156
|
+
}
|
|
157
|
+
} finally {
|
|
158
|
+
await reader.cancel().catch(() => undefined);
|
|
159
|
+
}
|
|
160
|
+
const output = new Uint8Array(Math.min(bytes, maxBytes));
|
|
161
|
+
let offset = 0;
|
|
162
|
+
for (const chunk of chunks) {
|
|
163
|
+
const remaining = output.byteLength - offset;
|
|
164
|
+
if (remaining <= 0) break;
|
|
165
|
+
output.set(chunk.subarray(0, remaining), offset);
|
|
166
|
+
offset += Math.min(chunk.byteLength, remaining);
|
|
167
|
+
}
|
|
168
|
+
return new TextDecoder().decode(output.subarray(0, offset));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function normalizeRegistrations(value: unknown): SmokeRegistrations {
|
|
172
|
+
const record = typeof value === "object" && value !== null ? (value as Record<string, unknown>) : {};
|
|
173
|
+
let remaining = 100;
|
|
174
|
+
const names = (kind: keyof SmokeRegistrations): string[] => {
|
|
175
|
+
if (!Array.isArray(record[kind]) || remaining === 0) return [];
|
|
176
|
+
const result = record[kind]
|
|
177
|
+
.filter((item): item is string => typeof item === "string")
|
|
178
|
+
.slice(0, remaining)
|
|
179
|
+
.map((item) => item.slice(0, 128));
|
|
180
|
+
remaining -= result.length;
|
|
181
|
+
return result;
|
|
182
|
+
};
|
|
183
|
+
return {
|
|
184
|
+
tools: names("tools"),
|
|
185
|
+
commands: names("commands"),
|
|
186
|
+
shortcuts: names("shortcuts"),
|
|
187
|
+
flags: names("flags"),
|
|
188
|
+
providers: names("providers"),
|
|
189
|
+
events: names("events"),
|
|
190
|
+
renderers: names("renderers"),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export async function runExtensionSmoke(
|
|
195
|
+
packagePath: string,
|
|
196
|
+
extensionFile: string,
|
|
197
|
+
options: SmokeOptions = {},
|
|
198
|
+
): Promise<ExtensionSmokeResult> {
|
|
199
|
+
const startedAt = Date.now();
|
|
200
|
+
const packageRoot = realpathSync(resolve(packagePath));
|
|
201
|
+
const extensionPath = realpathSync(resolve(extensionFile));
|
|
202
|
+
const relativePath = relative(packageRoot, extensionPath).split(sep).join("/");
|
|
203
|
+
if (relativePath.startsWith("../") || relativePath === "..")
|
|
204
|
+
return {
|
|
205
|
+
path: relativePath,
|
|
206
|
+
status: "capability-denied",
|
|
207
|
+
durationMs: Date.now() - startedAt,
|
|
208
|
+
registrations: { ...EMPTY_REGISTRATIONS },
|
|
209
|
+
message: "extension resolves outside the package",
|
|
210
|
+
};
|
|
211
|
+
const timeoutMs = bounded(options.timeoutMs, DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS);
|
|
212
|
+
const maxOutputBytes = bounded(options.maxOutputBytes, DEFAULT_MAX_OUTPUT_BYTES, MAX_OUTPUT_BYTES);
|
|
213
|
+
const maxProcesses = bounded(options.maxProcesses, DEFAULT_MAX_PROCESSES, MAX_PROCESSES);
|
|
214
|
+
const command = sandboxCommand(packageRoot, extensionPath, maxProcesses);
|
|
215
|
+
if (!command)
|
|
216
|
+
return {
|
|
217
|
+
path: relativePath,
|
|
218
|
+
status: "sandbox-unavailable",
|
|
219
|
+
durationMs: Date.now() - startedAt,
|
|
220
|
+
registrations: { ...EMPTY_REGISTRATIONS },
|
|
221
|
+
message: "bubblewrap and prlimit are required for smoke mode",
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
let timedOut = false;
|
|
225
|
+
let outputLimited = false;
|
|
226
|
+
const process = Bun.spawn(command, { stdin: "ignore", stdout: "pipe", stderr: "pipe" });
|
|
227
|
+
const kill = () => {
|
|
228
|
+
try {
|
|
229
|
+
process.kill("SIGKILL");
|
|
230
|
+
} catch {
|
|
231
|
+
/* already exited */
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
const timer = setTimeout(() => {
|
|
235
|
+
timedOut = true;
|
|
236
|
+
kill();
|
|
237
|
+
}, timeoutMs);
|
|
238
|
+
const stdoutPromise = readBounded(process.stdout, maxOutputBytes, () => {
|
|
239
|
+
outputLimited = true;
|
|
240
|
+
kill();
|
|
241
|
+
});
|
|
242
|
+
const stderrPromise = readBounded(process.stderr, maxOutputBytes, () => {
|
|
243
|
+
outputLimited = true;
|
|
244
|
+
kill();
|
|
245
|
+
});
|
|
246
|
+
const [exitCode, stdout, stderr] = await Promise.all([process.exited, stdoutPromise, stderrPromise]);
|
|
247
|
+
clearTimeout(timer);
|
|
248
|
+
const durationMs = Date.now() - startedAt;
|
|
249
|
+
if (timedOut)
|
|
250
|
+
return {
|
|
251
|
+
path: relativePath,
|
|
252
|
+
status: "timeout",
|
|
253
|
+
durationMs,
|
|
254
|
+
registrations: { ...EMPTY_REGISTRATIONS },
|
|
255
|
+
message: `extension exceeded ${timeoutMs}ms`,
|
|
256
|
+
};
|
|
257
|
+
if (outputLimited)
|
|
258
|
+
return {
|
|
259
|
+
path: relativePath,
|
|
260
|
+
status: "output-limit",
|
|
261
|
+
durationMs,
|
|
262
|
+
registrations: { ...EMPTY_REGISTRATIONS },
|
|
263
|
+
message: `extension exceeded ${maxOutputBytes} output bytes`,
|
|
264
|
+
};
|
|
265
|
+
const line = stdout.trim().split("\n").at(-1) ?? "";
|
|
266
|
+
try {
|
|
267
|
+
const payload = JSON.parse(line) as { status?: unknown; message?: unknown; registrations?: unknown };
|
|
268
|
+
const status =
|
|
269
|
+
payload.status === "ok" || payload.status === "capability-denied" || payload.status === "crash" ? payload.status : "crash";
|
|
270
|
+
return {
|
|
271
|
+
path: relativePath,
|
|
272
|
+
status,
|
|
273
|
+
durationMs,
|
|
274
|
+
registrations: normalizeRegistrations(payload.registrations),
|
|
275
|
+
...(typeof payload.message === "string" ? { message: payload.message.slice(0, 1_000) } : {}),
|
|
276
|
+
};
|
|
277
|
+
} catch {
|
|
278
|
+
const message = (stderr.trim() || `smoke child exited ${exitCode}`).slice(0, 1_000);
|
|
279
|
+
const status: SmokeStatus = /^(?:bwrap:|prlimit:)/i.test(message) ? "sandbox-unavailable" : "crash";
|
|
280
|
+
return { path: relativePath, status, durationMs, registrations: { ...EMPTY_REGISTRATIONS }, message };
|
|
281
|
+
}
|
|
282
|
+
}
|