@danypops/pi-packed 0.28.12 → 0.28.14
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/daemon/daemon-service.d.ts +3 -0
- package/dist/daemon/daemon-service.d.ts.map +1 -1
- package/extension/src/doctor-overlay.ts +5 -0
- package/extension/src/index.ts +60 -47
- package/extension/src/packed.ts +1 -1
- package/extension/src/startup.ts +89 -0
- package/package.json +5 -4
- package/service/src/daemon/daemon-service.ts +8 -2
- package/service/src/daemon/service.ts +6 -1
- package/service/test/daemon-service.test.ts +4 -2
- package/service/test/vehicle-registration.test.ts +18 -0
|
@@ -5,6 +5,8 @@ import { type DaemonPackageMaterializer } from "./isolated-service-install.ts";
|
|
|
5
5
|
export interface DaemonServiceManifest {
|
|
6
6
|
/** Relative to the installed package's own root directory. */
|
|
7
7
|
binPath: string;
|
|
8
|
+
/** Executes the entrypoint through Packed's absolute Bun runtime instead of its PATH-dependent shebang. */
|
|
9
|
+
runtime?: "bun";
|
|
8
10
|
args?: string[];
|
|
9
11
|
/** Defaults to the bare npm package name (scope stripped). */
|
|
10
12
|
name?: string;
|
|
@@ -28,6 +30,7 @@ export type ResolveDaemonServiceResult = {
|
|
|
28
30
|
/** A resolved daemon entrypoint, already an absolute binPath -- the common shape both an explicit manifest and detection converge to before a ServiceSpec is built. */
|
|
29
31
|
interface ResolvedDaemonEntrypoint {
|
|
30
32
|
binPath: string;
|
|
33
|
+
runtime?: "bun";
|
|
31
34
|
args?: string[];
|
|
32
35
|
name: string;
|
|
33
36
|
displayName?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon-service.d.ts","sourceRoot":"","sources":["../../service/src/daemon/daemon-service.ts"],"names":[],"mappings":"AAgCA,OAAO,EAA0B,KAAK,gBAAgB,EAAE,KAAK,0BAA0B,EAAE,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpI,OAAO,EAGN,KAAK,oBAAoB,EACzB,KAAK,WAAW,EAEhB,MAAM,kCAAkC,CAAC;AAE1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D,OAAO,EACN,KAAK,yBAAyB,EAE9B,MAAM,+BAA+B,CAAC;AAEvC,MAAM,WAAW,qBAAqB;IACrC,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,8DAA8D;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,0BAA0B,GACnC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE;AACjC,qbAAqb;GACnb;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAOvD,uKAAuK;AACvK,UAAU,wBAAwB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;
|
|
1
|
+
{"version":3,"file":"daemon-service.d.ts","sourceRoot":"","sources":["../../service/src/daemon/daemon-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -79,6 +79,11 @@ export class DoctorOverlay {
|
|
|
79
79
|
this.registered = false;
|
|
80
80
|
this.tui = undefined;
|
|
81
81
|
}
|
|
82
|
+
try {
|
|
83
|
+
this.render();
|
|
84
|
+
} catch {
|
|
85
|
+
// Cached doctor state is best-effort UI and must not affect session startup.
|
|
86
|
+
}
|
|
82
87
|
}
|
|
83
88
|
|
|
84
89
|
/** Never throws: called from a poll timer and from session_start, neither of which should turn
|
package/extension/src/index.ts
CHANGED
|
@@ -1,66 +1,79 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* pi-packed — Pi extension seam.
|
|
3
|
-
*
|
|
4
|
-
* Thin by design: registers agent tools (pkg_search/pkg_info/pkg_install/pkg_update/pkg_remove),
|
|
5
|
-
* the /packed command, and a session_start update notification. ALL logic
|
|
6
|
-
* lives in the Bun service (src/): registry access, caching, watcher,
|
|
7
|
-
* catalog sync, install execution.
|
|
8
|
-
*
|
|
9
|
-
* Install: pi install git:github.com/DanyPops/pi-packed
|
|
10
|
-
*/
|
|
11
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
12
|
-
import { DoctorOverlay } from "./doctor-overlay.js";
|
|
13
|
-
import { formatUpdateNotice } from "./model.js";
|
|
14
2
|
import { createNatives } from "./packed.js";
|
|
15
3
|
import { registerProfiles } from "./profile.js";
|
|
16
|
-
import {
|
|
17
|
-
import { handleResourceConfigCommand } from "./tabs/resource-config.js";
|
|
4
|
+
import { registerDeferredStartup } from "./startup.js";
|
|
18
5
|
import { registerTools } from "./tools.js";
|
|
19
|
-
import { showPackedPanel } from "./tui.js";
|
|
20
|
-
import { registerPackedVehicle } from "./vehicle-tools.js";
|
|
21
6
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
7
|
+
const VEHICLE_PRIORITY = 900;
|
|
8
|
+
const UPDATE_NOTICE_PRIORITY = 500;
|
|
9
|
+
const DOCTOR_OVERLAY_PRIORITY = 100;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Registers only turn-one capabilities synchronously. Daemon-backed status,
|
|
13
|
+
* diagnostics, and large UI modules load after Pi's startup turn, ordered by
|
|
14
|
+
* user value rather than package import order.
|
|
15
|
+
*/
|
|
16
|
+
export default function (pi: ExtensionAPI) {
|
|
25
17
|
registerProfiles(pi);
|
|
26
|
-
const natives =
|
|
18
|
+
const natives = createNatives();
|
|
27
19
|
registerTools(pi, natives);
|
|
28
20
|
|
|
29
21
|
pi.registerCommand("packed", {
|
|
30
22
|
description:
|
|
31
23
|
"Browse and manage installed Pi packages -- i inspect, u/U update, x remove, d disable, c config, f find, s settings -- or run setup plan/apply or config directly",
|
|
32
24
|
handler: async (args, ctx) => {
|
|
25
|
+
const [{ handleSetupCommand }, { handleResourceConfigCommand }, { showPackedPanel }] = await Promise.all([
|
|
26
|
+
import("./setup-command.js"),
|
|
27
|
+
import("./tabs/resource-config.js"),
|
|
28
|
+
import("./tui.js"),
|
|
29
|
+
]);
|
|
33
30
|
if (await handleSetupCommand(args, ctx, natives)) return;
|
|
34
31
|
if (await handleResourceConfigCommand(args, ctx, natives, showPackedPanel)) return;
|
|
35
32
|
await showPackedPanel(ctx, natives);
|
|
36
33
|
},
|
|
37
34
|
});
|
|
38
35
|
|
|
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
|
-
|
|
36
|
+
let doctorOverlay: import("./doctor-overlay.js").DoctorOverlay | undefined;
|
|
37
|
+
registerDeferredStartup(pi, [
|
|
38
|
+
{
|
|
39
|
+
id: "vehicle-tools",
|
|
40
|
+
priority: VEHICLE_PRIORITY,
|
|
41
|
+
async run() {
|
|
42
|
+
const { registerPackedVehicle } = await import("./vehicle-tools.js");
|
|
43
|
+
await registerPackedVehicle(pi);
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "update-notice",
|
|
48
|
+
priority: UPDATE_NOTICE_PRIORITY,
|
|
49
|
+
async run(ctx, isCurrent) {
|
|
50
|
+
const updates = await natives.updates();
|
|
51
|
+
if (!isCurrent() || updates.length === 0) return;
|
|
52
|
+
const { formatUpdateNotice } = await import("./model.js");
|
|
53
|
+
if (isCurrent()) ctx.ui.notify(`${formatUpdateNotice(updates)} — /packed to review`, "info");
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: "doctor-overlay",
|
|
58
|
+
priority: DOCTOR_OVERLAY_PRIORITY,
|
|
59
|
+
async run(ctx, isCurrent) {
|
|
60
|
+
if (!ctx.hasUI) return;
|
|
61
|
+
const { DoctorOverlay } = await import("./doctor-overlay.js");
|
|
62
|
+
const overlay = new DoctorOverlay();
|
|
63
|
+
await overlay.refresh();
|
|
64
|
+
if (!isCurrent()) {
|
|
65
|
+
overlay.dispose();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
doctorOverlay?.dispose();
|
|
69
|
+
doctorOverlay = overlay;
|
|
70
|
+
overlay.setUI(ctx.ui);
|
|
71
|
+
overlay.startPolling();
|
|
72
|
+
},
|
|
73
|
+
dispose() {
|
|
74
|
+
doctorOverlay?.dispose();
|
|
75
|
+
doctorOverlay = undefined;
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
]);
|
|
66
79
|
}
|
package/extension/src/packed.ts
CHANGED
|
@@ -80,7 +80,7 @@ async function connectDefaultDaemon(): Promise<PackageDaemonPort> {
|
|
|
80
80
|
return ensurePackedClient();
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
export
|
|
83
|
+
export function createNatives(connect: PackageDaemonConnector = connectDefaultDaemon): Natives {
|
|
84
84
|
// connectRetry:true (vehicle-client's own bounded background retry budget) covers a
|
|
85
85
|
// daemon that crashed and is mid systemd-restart -- without it, the very first call
|
|
86
86
|
// during that window fails immediately instead of waiting the ~2s restart out.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
export interface DeferredStartupTask {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
/** Higher priorities start first. Tasks run concurrently so one slow task cannot hold lower-priority work. */
|
|
6
|
+
readonly priority: number;
|
|
7
|
+
run(ctx: ExtensionContext, isCurrent: () => boolean): Promise<void> | void;
|
|
8
|
+
dispose?(): Promise<void> | void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface DeferredStartupHandle {
|
|
12
|
+
/** Waits for the most recently scheduled deferred batch. Primarily useful for tests and diagnostics. */
|
|
13
|
+
waitForDeferred(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface DeferredStartupOptions {
|
|
17
|
+
/** Schedules work after Pi's current startup turn. Defaults to a zero-delay timer. */
|
|
18
|
+
schedule?: (callback: () => void) => () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function defaultSchedule(callback: () => void): () => void {
|
|
22
|
+
const timer = setTimeout(callback, 0);
|
|
23
|
+
return () => clearTimeout(timer);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Registers lazy.nvim-style "VeryLazy" work: session_start only schedules a
|
|
28
|
+
* priority-ordered batch and returns immediately. The batch starts on the next
|
|
29
|
+
* timer turn, and session replacement invalidates work before it can touch a
|
|
30
|
+
* stale ExtensionContext.
|
|
31
|
+
*/
|
|
32
|
+
export function registerDeferredStartup(
|
|
33
|
+
pi: ExtensionAPI,
|
|
34
|
+
tasks: readonly DeferredStartupTask[],
|
|
35
|
+
options: DeferredStartupOptions = {},
|
|
36
|
+
): DeferredStartupHandle {
|
|
37
|
+
const ordered = [...tasks].sort((left, right) => right.priority - left.priority || left.id.localeCompare(right.id));
|
|
38
|
+
const schedule = options.schedule ?? defaultSchedule;
|
|
39
|
+
let generation = 0;
|
|
40
|
+
let cancelScheduled: (() => void) | undefined;
|
|
41
|
+
let settleScheduled: (() => void) | undefined;
|
|
42
|
+
let latestBatch = Promise.resolve();
|
|
43
|
+
|
|
44
|
+
function disposeTasks(): void {
|
|
45
|
+
for (const task of ordered) {
|
|
46
|
+
try {
|
|
47
|
+
void Promise.resolve(task.dispose?.()).catch(() => {});
|
|
48
|
+
} catch {
|
|
49
|
+
// Deferred cleanup is best-effort and must not delay session teardown.
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
pi.on("session_start", (_event, ctx) => {
|
|
55
|
+
cancelScheduled?.();
|
|
56
|
+
settleScheduled?.();
|
|
57
|
+
disposeTasks();
|
|
58
|
+
const currentGeneration = ++generation;
|
|
59
|
+
latestBatch = new Promise<void>((resolve) => {
|
|
60
|
+
settleScheduled = resolve;
|
|
61
|
+
});
|
|
62
|
+
cancelScheduled = schedule(() => {
|
|
63
|
+
cancelScheduled = undefined;
|
|
64
|
+
const settle = settleScheduled;
|
|
65
|
+
settleScheduled = undefined;
|
|
66
|
+
const isCurrent = () => generation === currentGeneration;
|
|
67
|
+
const pending = ordered.map((task) =>
|
|
68
|
+
Promise.resolve()
|
|
69
|
+
.then(() => {
|
|
70
|
+
if (!isCurrent()) return;
|
|
71
|
+
return task.run(ctx, isCurrent);
|
|
72
|
+
})
|
|
73
|
+
.catch(() => {}),
|
|
74
|
+
);
|
|
75
|
+
void Promise.all(pending).then(() => settle?.());
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
pi.on("session_shutdown", () => {
|
|
80
|
+
generation++;
|
|
81
|
+
cancelScheduled?.();
|
|
82
|
+
cancelScheduled = undefined;
|
|
83
|
+
settleScheduled?.();
|
|
84
|
+
settleScheduled = undefined;
|
|
85
|
+
disposeTasks();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return { waitForDeferred: () => latestBatch };
|
|
89
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danypops/pi-packed",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.14",
|
|
4
4
|
"description": "Pi package lifecycle, validation, daemon, tools, profiles, and TUI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"name": "pi-packed",
|
|
21
21
|
"displayName": "Packed package service",
|
|
22
22
|
"binPath": "service/src/cli/cli.ts",
|
|
23
|
+
"runtime": "bun",
|
|
23
24
|
"args": ["serve"],
|
|
24
25
|
"handleFilename": "handle.json",
|
|
25
26
|
"restartOnFailure": true,
|
|
@@ -35,9 +36,9 @@
|
|
|
35
36
|
"@danypops/armada": "^0.5.1",
|
|
36
37
|
"@danypops/packed": "^0.7.0",
|
|
37
38
|
"@danypops/pi-extension-harness": "^0.7.0",
|
|
38
|
-
"@danypops/vehicle-client": "^0.10.
|
|
39
|
-
"@danypops/vehicle-core": "^0.19.
|
|
40
|
-
"@danypops/vehicle-server": "^0.27.
|
|
39
|
+
"@danypops/vehicle-client": "^0.10.8",
|
|
40
|
+
"@danypops/vehicle-core": "^0.19.1",
|
|
41
|
+
"@danypops/vehicle-server": "^0.27.1",
|
|
41
42
|
"jiti": "^2.7.0",
|
|
42
43
|
"malevich-tui-components": "^0.32.1",
|
|
43
44
|
"publint": "0.3.22",
|
|
@@ -50,6 +50,8 @@ import {
|
|
|
50
50
|
export interface DaemonServiceManifest {
|
|
51
51
|
/** Relative to the installed package's own root directory. */
|
|
52
52
|
binPath: string;
|
|
53
|
+
/** Executes the entrypoint through Packed's absolute Bun runtime instead of its PATH-dependent shebang. */
|
|
54
|
+
runtime?: "bun";
|
|
53
55
|
args?: string[];
|
|
54
56
|
/** Defaults to the bare npm package name (scope stripped). */
|
|
55
57
|
name?: string;
|
|
@@ -74,6 +76,7 @@ function unscopedName(packageName: string): string {
|
|
|
74
76
|
/** A resolved daemon entrypoint, already an absolute binPath -- the common shape both an explicit manifest and detection converge to before a ServiceSpec is built. */
|
|
75
77
|
interface ResolvedDaemonEntrypoint {
|
|
76
78
|
binPath: string;
|
|
79
|
+
runtime?: "bun";
|
|
77
80
|
args?: string[];
|
|
78
81
|
name: string;
|
|
79
82
|
displayName?: string;
|
|
@@ -164,6 +167,7 @@ function resolveDependencyCandidate(depDir: string, depName: string): ResolvedDa
|
|
|
164
167
|
if (depManifest && typeof depManifest.binPath === "string" && depManifest.binPath.length > 0 && dep.version) {
|
|
165
168
|
return {
|
|
166
169
|
binPath: join(depDir, depManifest.binPath),
|
|
170
|
+
runtime: depManifest.runtime,
|
|
167
171
|
args: depManifest.args,
|
|
168
172
|
name: depManifest.name ?? unscopedName(dep.name ?? depName),
|
|
169
173
|
displayName: depManifest.displayName,
|
|
@@ -281,13 +285,14 @@ function buildSpec(entry: ResolvedDaemonEntrypoint): ServiceSpec {
|
|
|
281
285
|
handleFilename: entry.handleFilename ?? DEFAULT_DETECTED_HANDLE_FILENAME,
|
|
282
286
|
systemdUnitName: `${entry.name}.service`,
|
|
283
287
|
});
|
|
288
|
+
const daemonArgs = entry.args ?? [];
|
|
284
289
|
return {
|
|
285
290
|
name: entry.name,
|
|
286
291
|
displayName: entry.displayName,
|
|
287
292
|
version: entry.version,
|
|
288
293
|
...(entry.contentSignature === undefined ? {} : { contentSignature: entry.contentSignature }),
|
|
289
|
-
binPath: entry.binPath,
|
|
290
|
-
args: entry.args,
|
|
294
|
+
binPath: entry.runtime === "bun" ? process.execPath : entry.binPath,
|
|
295
|
+
args: entry.runtime === "bun" ? [entry.binPath, ...daemonArgs] : entry.args,
|
|
291
296
|
handlePath: paths.handle,
|
|
292
297
|
workingDirectory: entry.workingDirectory,
|
|
293
298
|
...(entry.restartOnFailure === undefined ? {} : { restartOnFailure: entry.restartOnFailure }),
|
|
@@ -316,6 +321,7 @@ export function resolveDaemonServiceSpec(piHome: string, source: string): Resolv
|
|
|
316
321
|
ok: true,
|
|
317
322
|
spec: buildSpec({
|
|
318
323
|
binPath: join(packageDir, manifest.binPath),
|
|
324
|
+
runtime: manifest.runtime,
|
|
319
325
|
args: manifest.args,
|
|
320
326
|
name: manifest.name ?? unscopedName(packageName),
|
|
321
327
|
displayName: manifest.displayName,
|
|
@@ -302,7 +302,12 @@ export function createApp(deps: Deps): { fetch: (req: Request) => Promise<Respon
|
|
|
302
302
|
vehicleRegistry.useExecutionMiddleware(createVehicleMetricsMiddleware(deps.vehicleMetrics, "packed"));
|
|
303
303
|
registerVehicleMetricsOperations(vehicleRegistry, deps.vehicleMetrics, "packed");
|
|
304
304
|
}
|
|
305
|
-
const
|
|
305
|
+
const permissions = [...new Set(vehicleRegistry.manifest().operations.flatMap((operation) => operation.permissions))];
|
|
306
|
+
const vehicleApp = createVehicleHttpApp({
|
|
307
|
+
registry: vehicleRegistry,
|
|
308
|
+
token: deps.token,
|
|
309
|
+
invocationAuthority: { mode: "attested", resolve: () => ({ permissions, principal: { id: "packed-authenticated-client" } }) },
|
|
310
|
+
});
|
|
306
311
|
|
|
307
312
|
function authorize(operation: PackageOperation, approved: boolean): Response | undefined {
|
|
308
313
|
try {
|
|
@@ -48,11 +48,13 @@ describe("resolveDaemonServiceSpec", () => {
|
|
|
48
48
|
const piHome = fakePiHome();
|
|
49
49
|
writePackage(piHome, "@danypops/web-spider-daemon", {
|
|
50
50
|
binPath: "dist/cli.js",
|
|
51
|
+
runtime: "bun",
|
|
51
52
|
args: ["serve"],
|
|
52
53
|
restartOnFailure: true,
|
|
53
54
|
restartSec: 2,
|
|
54
55
|
});
|
|
55
56
|
|
|
57
|
+
const daemonEntry = join(piHome, "npm", "node_modules", "@danypops/web-spider-daemon", "dist/cli.js");
|
|
56
58
|
const result = resolveDaemonServiceSpec(piHome, "npm:@danypops/web-spider-daemon");
|
|
57
59
|
expect(result).toEqual({
|
|
58
60
|
ok: true,
|
|
@@ -61,8 +63,8 @@ describe("resolveDaemonServiceSpec", () => {
|
|
|
61
63
|
displayName: undefined,
|
|
62
64
|
version: "1.0.0",
|
|
63
65
|
contentSignature: expect.any(String) as unknown as string,
|
|
64
|
-
binPath:
|
|
65
|
-
args: ["serve"],
|
|
66
|
+
binPath: process.execPath,
|
|
67
|
+
args: [daemonEntry, "serve"],
|
|
66
68
|
handlePath: expect.stringContaining("web-spider-daemon") as unknown as string,
|
|
67
69
|
workingDirectory: undefined,
|
|
68
70
|
restartOnFailure: true,
|
|
@@ -97,6 +97,24 @@ describe("packed's daemon operation surface, through the real Vehicle wire proto
|
|
|
97
97
|
expect(byName.get("setup.update")).toBe(false);
|
|
98
98
|
});
|
|
99
99
|
|
|
100
|
+
it("derives Vehicle permissions from authenticated server state", async () => {
|
|
101
|
+
const app = createApp(deps());
|
|
102
|
+
const response = await app.fetch(
|
|
103
|
+
new Request("http://packed.internal/vehicle/invoke", {
|
|
104
|
+
method: "POST",
|
|
105
|
+
headers: { authorization: "Bearer test-token", "content-type": "application/json" },
|
|
106
|
+
body: JSON.stringify({
|
|
107
|
+
name: "package.search",
|
|
108
|
+
version: 1,
|
|
109
|
+
input: { query: "lsp", limit: 10 },
|
|
110
|
+
permissions: [],
|
|
111
|
+
principal: { id: "forged-caller" },
|
|
112
|
+
}),
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
expect(response.status).toBe(200);
|
|
116
|
+
});
|
|
117
|
+
|
|
100
118
|
it("package.search round-trips through /vehicle/invoke, matching /api/v1/ops's own shape", async () => {
|
|
101
119
|
const app = createApp(deps());
|
|
102
120
|
const result = await invoke(app, "package.search", { query: "lsp", limit: 10 });
|