@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,178 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { existsSync, mkdtempSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { syncCatalog } from "../src/packages/catalog.ts";
|
|
6
|
+
import { catalogList, dbPath, getSyncMeta, openDb, replaceAll, searchLocal } from "../src/packages/db.ts";
|
|
7
|
+
import type { Pkg, PkgInfo, Registry, SearchPage } from "../src/packages/package.ts";
|
|
8
|
+
import { HttpRegistry } from "../src/registry/registry.ts";
|
|
9
|
+
|
|
10
|
+
const PKGS = [
|
|
11
|
+
{ name: "pi-lsp", version: "0.18.0", description: "LSP tools for pi" },
|
|
12
|
+
{ name: "@llblab/pi-telegram", version: "0.23.1", description: "Telegram runtime adapter" },
|
|
13
|
+
{ name: "pi-lsp-lite", version: "1.0.0", description: "lightweight LSP" },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
describe("db (local registry index)", () => {
|
|
17
|
+
it("creates schema and replaces the catalog atomically", () => {
|
|
18
|
+
const db = openDb(":memory:");
|
|
19
|
+
replaceAll(db, PKGS, "test-source");
|
|
20
|
+
expect(catalogList(db)).toHaveLength(3);
|
|
21
|
+
expect(catalogList(db)[0]?.packageEvidence).toMatchObject({ shape: "keyword-only", verified: false });
|
|
22
|
+
// Second sync replaces, not appends
|
|
23
|
+
replaceAll(db, [PKGS[0]!], "test-source");
|
|
24
|
+
expect(catalogList(db)).toHaveLength(1);
|
|
25
|
+
db.close();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("roundtrips verified Pi shape and publication evidence", () => {
|
|
29
|
+
const db = openDb(":memory:");
|
|
30
|
+
replaceAll(
|
|
31
|
+
db,
|
|
32
|
+
[
|
|
33
|
+
{
|
|
34
|
+
name: "pi-verified",
|
|
35
|
+
version: "1",
|
|
36
|
+
packageEvidence: { shape: "conventional", verified: true, evidence: ["extensions"] },
|
|
37
|
+
publication: { provenanceUrl: "https://registry.example/attestation", trustedPublisher: "verified" },
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
"snapshot",
|
|
41
|
+
);
|
|
42
|
+
expect(catalogList(db)[0]).toMatchObject({
|
|
43
|
+
packageEvidence: { shape: "conventional", verified: true, evidence: ["extensions"] },
|
|
44
|
+
publication: { provenanceUrl: "https://registry.example/attestation", trustedPublisher: "verified" },
|
|
45
|
+
});
|
|
46
|
+
db.close();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("searchLocal: FTS with AND semantics, scoped names, ranking", () => {
|
|
50
|
+
const db = openDb(":memory:");
|
|
51
|
+
replaceAll(db, PKGS, "test");
|
|
52
|
+
expect(searchLocal(db, "lsp").map((p) => p.name)).toContain("pi-lsp");
|
|
53
|
+
const and = searchLocal(db, "lsp lightweight");
|
|
54
|
+
expect(and.map((p) => p.name)).toEqual(["pi-lsp-lite"]);
|
|
55
|
+
expect(searchLocal(db, "telegram").map((p) => p.name)).toEqual(["@llblab/pi-telegram"]);
|
|
56
|
+
expect(searchLocal(db, "nonexistent")).toEqual([]);
|
|
57
|
+
db.close();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("ranks verified Pi-shaped packages ahead of keyword-only candidates", () => {
|
|
61
|
+
const db = openDb(":memory:");
|
|
62
|
+
replaceAll(
|
|
63
|
+
db,
|
|
64
|
+
[
|
|
65
|
+
{ name: "candidate", version: "1", description: "shared package" },
|
|
66
|
+
{
|
|
67
|
+
name: "verified",
|
|
68
|
+
version: "1",
|
|
69
|
+
description: "shared package",
|
|
70
|
+
packageEvidence: { shape: "manifest", verified: true, evidence: ["pi.extensions"] },
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
"snapshot",
|
|
74
|
+
);
|
|
75
|
+
expect(searchLocal(db, "shared").map((item) => item.name)).toEqual(["verified", "candidate"]);
|
|
76
|
+
db.close();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("searchLocal survives FTS-hostile input (LIKE fallback)", () => {
|
|
80
|
+
const db = openDb(":memory:");
|
|
81
|
+
replaceAll(db, PKGS, "test");
|
|
82
|
+
expect(() => searchLocal(db, 'lsp "broken')).not.toThrow();
|
|
83
|
+
expect(searchLocal(db, "lsp*")).toContainEqual(expect.objectContaining({ name: "pi-lsp" }));
|
|
84
|
+
db.close();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("sync meta carries source, time, count, and a payload checksum", () => {
|
|
88
|
+
const db = openDb(":memory:");
|
|
89
|
+
replaceAll(db, PKGS, "npm");
|
|
90
|
+
const meta = getSyncMeta(db)!;
|
|
91
|
+
expect(meta.source).toBe("npm");
|
|
92
|
+
expect(meta.packageCount).toBe(3);
|
|
93
|
+
expect(meta.fetchedAt).toBeTruthy();
|
|
94
|
+
expect(meta.sha256).toMatch(/^[0-9a-f]{64}$/);
|
|
95
|
+
db.close();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("dbPath lives in the state dir", () => {
|
|
99
|
+
expect(dbPath("/tmp/x")).toBe(join("/tmp/x", "packed.db"));
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// The sync pipeline: paginated upstream → SQLite mirror (apt update analog).
|
|
104
|
+
class PagedRegistry implements Registry {
|
|
105
|
+
constructor(
|
|
106
|
+
private pages: Record<number, Pkg[]>,
|
|
107
|
+
private total: number,
|
|
108
|
+
) {}
|
|
109
|
+
async search(): Promise<SearchPage> {
|
|
110
|
+
return { results: [], total: 0 };
|
|
111
|
+
}
|
|
112
|
+
async searchPage(_q: string, from: number, _size?: number): Promise<SearchPage> {
|
|
113
|
+
return { results: this.pages[from] ?? [], total: this.total };
|
|
114
|
+
}
|
|
115
|
+
async searchAll(q: string): Promise<Pkg[]> {
|
|
116
|
+
const out: Pkg[] = [];
|
|
117
|
+
let from = 0;
|
|
118
|
+
for (;;) {
|
|
119
|
+
const { results, total } = await this.searchPage(q, from, 0);
|
|
120
|
+
out.push(...results);
|
|
121
|
+
from += results.length;
|
|
122
|
+
if (results.length === 0 || from >= total) return out;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async info(name: string): Promise<PkgInfo> {
|
|
126
|
+
return { name, version: "1" };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
describe("syncCatalog → SQLite", () => {
|
|
131
|
+
it("accumulates pages into the DB and records sync meta", async () => {
|
|
132
|
+
const dir = mkdtempSync(join(tmpdir(), "packed-"));
|
|
133
|
+
const reg = new PagedRegistry({ 0: PKGS.slice(0, 2), 2: PKGS.slice(2) }, 3);
|
|
134
|
+
expect(await syncCatalog(reg, dir)).toBe(3);
|
|
135
|
+
expect(existsSync(dbPath(dir))).toBe(true);
|
|
136
|
+
|
|
137
|
+
const db = openDb(dbPath(dir));
|
|
138
|
+
expect(catalogList(db).map((p) => p.name)).toEqual([...PKGS.map((p) => p.name)].sort());
|
|
139
|
+
expect(getSyncMeta(db)!.packageCount).toBe(3);
|
|
140
|
+
db.close();
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("unstable upstream pagination", () => {
|
|
145
|
+
it("replaceAll tolerates duplicate names (INSERT OR REPLACE)", () => {
|
|
146
|
+
const db = openDb(":memory:");
|
|
147
|
+
const dupes = [
|
|
148
|
+
{ name: "pi-lsp", version: "0.18.0" },
|
|
149
|
+
{ name: "a", version: "1" },
|
|
150
|
+
{ name: "pi-lsp", version: "0.19.0" }, // shifted onto a second page
|
|
151
|
+
];
|
|
152
|
+
expect(() => replaceAll(db, dupes, "test")).not.toThrow();
|
|
153
|
+
expect(catalogList(db)).toHaveLength(2);
|
|
154
|
+
db.close();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("searchAll dedupes packages that shift across pages", async () => {
|
|
158
|
+
const reg = new HttpRegistry("http://unused", 250, 0, 1);
|
|
159
|
+
reg.searchPage = async (_q: string, from: number) =>
|
|
160
|
+
from === 0
|
|
161
|
+
? {
|
|
162
|
+
results: [
|
|
163
|
+
{ name: "a", version: "1" },
|
|
164
|
+
{ name: "b", version: "1" },
|
|
165
|
+
],
|
|
166
|
+
total: 3,
|
|
167
|
+
}
|
|
168
|
+
: {
|
|
169
|
+
results: [
|
|
170
|
+
{ name: "b", version: "1" },
|
|
171
|
+
{ name: "c", version: "1" },
|
|
172
|
+
],
|
|
173
|
+
total: 3,
|
|
174
|
+
};
|
|
175
|
+
const all = await reg.searchAll("keywords:pi-package");
|
|
176
|
+
expect(all.map((p: Pkg) => p.name)).toEqual(["a", "b", "c"]);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from "bun:test";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { AuthenticatedRpcClient } from "@danypops/vehicle-client/rpc-client";
|
|
7
|
+
import { runDoctor } from "../src/adoption/doctor.ts";
|
|
8
|
+
import { createApp, type OperationInputs, type OperationName, type OperationOutputs } from "../src/daemon/service.ts";
|
|
9
|
+
import type { Installer, PkgInfo, Registry, SearchPage } from "../src/packages/package.ts";
|
|
10
|
+
|
|
11
|
+
const roots: string[] = [];
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// Same sandbox-availability probe as smoke.test.ts: binary presence alone
|
|
17
|
+
// doesn't prove bwrap actually works under this host's user namespaces.
|
|
18
|
+
function bwrapUsable(): boolean {
|
|
19
|
+
if (!existsSync("/usr/bin/bwrap")) return false;
|
|
20
|
+
const probe = spawnSync("/usr/bin/bwrap", ["--ro-bind", "/", "/", "--unshare-all", "--", "/bin/true"], { timeout: 5_000 });
|
|
21
|
+
return probe.status === 0;
|
|
22
|
+
}
|
|
23
|
+
const describeIfSandboxed = bwrapUsable() ? describe : describe.skip;
|
|
24
|
+
|
|
25
|
+
function piHome(settingsPackages: unknown[]): string {
|
|
26
|
+
const root = mkdtempSync(join(tmpdir(), "packed-doctor-"));
|
|
27
|
+
roots.push(root);
|
|
28
|
+
writeFileSync(join(root, "settings.json"), JSON.stringify({ packages: settingsPackages }, null, 2));
|
|
29
|
+
return root;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function installNpmPackage(home: string, name: string, pi: Record<string, unknown>, files: Record<string, string>): void {
|
|
33
|
+
const dir = join(home, "npm", "node_modules", name);
|
|
34
|
+
mkdirSync(dir, { recursive: true });
|
|
35
|
+
writeFileSync(join(dir, "package.json"), JSON.stringify({ name, version: "1.0.0", pi }, null, 2));
|
|
36
|
+
for (const [path, content] of Object.entries(files)) {
|
|
37
|
+
mkdirSync(join(dir, path, ".."), { recursive: true });
|
|
38
|
+
writeFileSync(join(dir, path), content);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describeIfSandboxed("runDoctor", () => {
|
|
43
|
+
it("reports no conflicts when every package's tools are distinct", async () => {
|
|
44
|
+
const home = piHome(["npm:pi-a", "npm:pi-b"]);
|
|
45
|
+
installNpmPackage(
|
|
46
|
+
home,
|
|
47
|
+
"pi-a",
|
|
48
|
+
{ extensions: ["extension/index.ts"] },
|
|
49
|
+
{
|
|
50
|
+
"extension/index.ts": 'export default function (pi: any) { pi.registerTool({ name: "alpha" }); }',
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
installNpmPackage(
|
|
54
|
+
home,
|
|
55
|
+
"pi-b",
|
|
56
|
+
{ extensions: ["extension/index.ts"] },
|
|
57
|
+
{
|
|
58
|
+
"extension/index.ts": 'export default function (pi: any) { pi.registerTool({ name: "beta" }); }',
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const report = await runDoctor(home);
|
|
63
|
+
|
|
64
|
+
expect(report.ok).toBe(true);
|
|
65
|
+
expect(report.conflicts).toEqual([]);
|
|
66
|
+
expect(report.scanned).toBe(2);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("reproduces the real jittor incident: a stale project-scoped package re-registering a globally-loaded package's tool names", async () => {
|
|
70
|
+
const home = piHome(["npm:pi-papyrus"]);
|
|
71
|
+
installNpmPackage(
|
|
72
|
+
home,
|
|
73
|
+
"pi-papyrus",
|
|
74
|
+
{ extensions: ["extension/index.ts"] },
|
|
75
|
+
{
|
|
76
|
+
"extension/index.ts":
|
|
77
|
+
'export default function (pi: any) { pi.registerTool({ name: "tasks" }); pi.registerTool({ name: "docs" }); }',
|
|
78
|
+
},
|
|
79
|
+
);
|
|
80
|
+
const projectRoot = mkdtempSync(join(tmpdir(), "packed-doctor-project-"));
|
|
81
|
+
roots.push(projectRoot);
|
|
82
|
+
const projectHome = join(projectRoot, ".pi");
|
|
83
|
+
mkdirSync(projectHome, { recursive: true });
|
|
84
|
+
writeFileSync(join(projectHome, "settings.json"), JSON.stringify({ packages: ["npm:papyrus"] }));
|
|
85
|
+
installNpmPackage(
|
|
86
|
+
projectHome,
|
|
87
|
+
"papyrus",
|
|
88
|
+
{ extensions: ["extension/index.ts"] },
|
|
89
|
+
{
|
|
90
|
+
"extension/index.ts": 'export default function (pi: any) { pi.registerTool({ name: "tasks" }); }',
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const report = await runDoctor(home, projectRoot);
|
|
95
|
+
|
|
96
|
+
expect(report.ok).toBe(false);
|
|
97
|
+
expect(report.conflicts).toHaveLength(1);
|
|
98
|
+
const conflict = report.conflicts[0]!;
|
|
99
|
+
expect(conflict).toMatchObject({ kind: "tool", name: "tasks" });
|
|
100
|
+
expect(conflict.claimants).toHaveLength(2);
|
|
101
|
+
expect(conflict.claimants.map((c) => c.scope).sort()).toEqual(["global", "project"]);
|
|
102
|
+
expect(conflict.claimants.map((c) => c.name).sort()).toEqual(["papyrus", "pi-papyrus"]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("never treats a disabled extension as a claimant", async () => {
|
|
106
|
+
const home = piHome([{ source: "npm:pi-a", extensions: [] }, "npm:pi-b"]);
|
|
107
|
+
installNpmPackage(
|
|
108
|
+
home,
|
|
109
|
+
"pi-a",
|
|
110
|
+
{ extensions: ["extension/index.ts"] },
|
|
111
|
+
{
|
|
112
|
+
"extension/index.ts": 'export default function (pi: any) { pi.registerTool({ name: "shared" }); }',
|
|
113
|
+
},
|
|
114
|
+
);
|
|
115
|
+
installNpmPackage(
|
|
116
|
+
home,
|
|
117
|
+
"pi-b",
|
|
118
|
+
{ extensions: ["extension/index.ts"] },
|
|
119
|
+
{
|
|
120
|
+
"extension/index.ts": 'export default function (pi: any) { pi.registerTool({ name: "shared" }); }',
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const report = await runDoctor(home);
|
|
125
|
+
|
|
126
|
+
expect(report.ok).toBe(true);
|
|
127
|
+
expect(report.conflicts).toEqual([]);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("surfaces a genuinely crashing extension as a non-ok result without treating it as a conflict", async () => {
|
|
131
|
+
const home = piHome(["npm:pi-broken"]);
|
|
132
|
+
installNpmPackage(
|
|
133
|
+
home,
|
|
134
|
+
"pi-broken",
|
|
135
|
+
{ extensions: ["extension/index.ts"] },
|
|
136
|
+
{
|
|
137
|
+
"extension/index.ts": 'export default function () { throw new Error("boom"); }',
|
|
138
|
+
},
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const report = await runDoctor(home);
|
|
142
|
+
|
|
143
|
+
expect(report.ok).toBe(false);
|
|
144
|
+
expect(report.conflicts).toEqual([]);
|
|
145
|
+
expect(report.extensions).toHaveLength(1);
|
|
146
|
+
expect(report.extensions[0]?.status).toBe("crash");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("is unaffected by a missing project settings file", async () => {
|
|
150
|
+
const home = piHome(["npm:pi-a"]);
|
|
151
|
+
installNpmPackage(
|
|
152
|
+
home,
|
|
153
|
+
"pi-a",
|
|
154
|
+
{ extensions: ["extension/index.ts"] },
|
|
155
|
+
{
|
|
156
|
+
"extension/index.ts": 'export default function (pi: any) { pi.registerTool({ name: "alpha" }); }',
|
|
157
|
+
},
|
|
158
|
+
);
|
|
159
|
+
const projectRoot = mkdtempSync(join(tmpdir(), "packed-doctor-project-"));
|
|
160
|
+
roots.push(projectRoot);
|
|
161
|
+
|
|
162
|
+
const report = await runDoctor(home, projectRoot);
|
|
163
|
+
|
|
164
|
+
expect(report.ok).toBe(true);
|
|
165
|
+
expect(report.scanned).toBe(1);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
class NoopRegistry implements Registry {
|
|
170
|
+
async search(): Promise<SearchPage> {
|
|
171
|
+
return { results: [], total: 0 };
|
|
172
|
+
}
|
|
173
|
+
async searchPage(): Promise<SearchPage> {
|
|
174
|
+
return { results: [], total: 0 };
|
|
175
|
+
}
|
|
176
|
+
async searchAll() {
|
|
177
|
+
return [];
|
|
178
|
+
}
|
|
179
|
+
async info(name: string): Promise<PkgInfo> {
|
|
180
|
+
return { name, version: "1.0.0" };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
class NoopInstaller implements Installer {
|
|
185
|
+
async install() {
|
|
186
|
+
return "ok";
|
|
187
|
+
}
|
|
188
|
+
async remove() {
|
|
189
|
+
return "ok";
|
|
190
|
+
}
|
|
191
|
+
async update() {
|
|
192
|
+
return { output: "ok", reloadRequired: false, alreadyUpToDate: true, pinned: false };
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
describeIfSandboxed("doctor.run (daemon RPC wiring)", () => {
|
|
197
|
+
it("reports the same conflict through the authenticated RPC operation as the pure domain function", async () => {
|
|
198
|
+
const home = piHome(["npm:pi-a", "npm:pi-b"]);
|
|
199
|
+
installNpmPackage(
|
|
200
|
+
home,
|
|
201
|
+
"pi-a",
|
|
202
|
+
{ extensions: ["extension/index.ts"] },
|
|
203
|
+
{
|
|
204
|
+
"extension/index.ts": 'export default function (pi: any) { pi.registerTool({ name: "shared" }); }',
|
|
205
|
+
},
|
|
206
|
+
);
|
|
207
|
+
installNpmPackage(
|
|
208
|
+
home,
|
|
209
|
+
"pi-b",
|
|
210
|
+
{ extensions: ["extension/index.ts"] },
|
|
211
|
+
{
|
|
212
|
+
"extension/index.ts": 'export default function (pi: any) { pi.registerTool({ name: "shared" }); }',
|
|
213
|
+
},
|
|
214
|
+
);
|
|
215
|
+
const app = createApp({
|
|
216
|
+
reg: new NoopRegistry(),
|
|
217
|
+
inst: new NoopInstaller(),
|
|
218
|
+
token: "test-token",
|
|
219
|
+
stateDir: mkdtempSync(join(tmpdir(), "packed-doctor-state-")),
|
|
220
|
+
dataDir: mkdtempSync(join(tmpdir(), "packed-doctor-data-")),
|
|
221
|
+
piHome: home,
|
|
222
|
+
});
|
|
223
|
+
const client = new AuthenticatedRpcClient<OperationName, OperationInputs, OperationOutputs>("http://packed.test", "test-token", {
|
|
224
|
+
label: "Packed",
|
|
225
|
+
transport: (request) => app.fetch(request),
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const result = await client.call("doctor.run", {});
|
|
229
|
+
|
|
230
|
+
expect(result).toEqual(await runDoctor(home));
|
|
231
|
+
expect(result.ok).toBe(false);
|
|
232
|
+
expect(result.conflicts).toHaveLength(1);
|
|
233
|
+
});
|
|
234
|
+
});
|