@blackbelt-technology/pi-agent-dashboard 0.3.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/AGENTS.md +67 -116
- package/README.md +93 -7
- package/docs/architecture.md +408 -9
- package/package.json +6 -4
- package/packages/extension/package.json +11 -3
- package/packages/extension/src/__tests__/enrich-model-metadata.test.ts +201 -0
- package/packages/extension/src/__tests__/git-info.test.ts +67 -55
- package/packages/extension/src/__tests__/openspec-poller.test.ts +101 -96
- package/packages/extension/src/__tests__/process-scanner-kill.test.ts +61 -0
- package/packages/extension/src/__tests__/provider-register-reload.test.ts +394 -0
- package/packages/extension/src/__tests__/server-auto-start.test.ts +95 -4
- package/packages/extension/src/__tests__/server-launcher.test.ts +16 -0
- package/packages/extension/src/bridge.ts +69 -2
- package/packages/extension/src/dev-build.ts +1 -1
- package/packages/extension/src/git-info.ts +9 -19
- package/packages/extension/src/pi-env.d.ts +1 -0
- package/packages/extension/src/process-scanner.ts +72 -38
- package/packages/extension/src/provider-register.ts +304 -16
- package/packages/extension/src/server-auto-start.ts +27 -1
- package/packages/extension/src/server-launcher.ts +71 -27
- package/packages/server/package.json +16 -2
- package/packages/server/src/__tests__/bootstrap-queue.test.ts +120 -0
- package/packages/server/src/__tests__/bootstrap-routes.test.ts +125 -0
- package/packages/server/src/__tests__/bootstrap-state.test.ts +119 -0
- package/packages/server/src/__tests__/browse-endpoint.test.ts +17 -0
- package/packages/server/src/__tests__/cli-parse.test.ts +11 -0
- package/packages/server/src/__tests__/concurrent-launch.test.ts +110 -0
- package/packages/server/src/__tests__/config-api.test.ts +68 -0
- package/packages/server/src/__tests__/crash-recovery.test.ts +88 -0
- package/packages/server/src/__tests__/directory-service.test.ts +234 -8
- package/packages/server/src/__tests__/editor-registry.test.ts +28 -15
- package/packages/server/src/__tests__/extension-register-appimage.test.ts +5 -1
- package/packages/server/src/__tests__/extension-register.test.ts +3 -1
- package/packages/server/src/__tests__/find-port-holders.test.ts +94 -0
- package/packages/server/src/__tests__/force-kill-handler.test.ts +57 -8
- package/packages/server/src/__tests__/home-lock-escape-hatch.test.ts +60 -0
- package/packages/server/src/__tests__/home-lock-release.test.ts +85 -0
- package/packages/server/src/__tests__/home-lock.test.ts +308 -0
- package/packages/server/src/__tests__/is-pi-process.test.ts +36 -0
- package/packages/server/src/__tests__/node-guard.test.ts +85 -0
- package/packages/server/src/__tests__/package-manager-wrapper-resolve.test.ts +5 -1
- package/packages/server/src/__tests__/package-manager-wrapper.test.ts +45 -10
- package/packages/server/src/__tests__/pi-version-skew.test.ts +165 -0
- package/packages/server/src/__tests__/preferences-store.test.ts +73 -4
- package/packages/server/src/__tests__/process-manager.test.ts +45 -18
- package/packages/server/src/__tests__/provider-probe.test.ts +287 -0
- package/packages/server/src/__tests__/provider-test-route.test.ts +149 -0
- package/packages/server/src/__tests__/restart-helper.test.ts +83 -0
- package/packages/server/src/__tests__/session-action-handler-headless-reload.test.ts +467 -0
- package/packages/server/src/__tests__/session-action-handler-reload-predicate.test.ts +73 -0
- package/packages/server/src/__tests__/session-action-handler-spawn-error.test.ts +74 -0
- package/packages/server/src/__tests__/terminal-manager.test.ts +41 -1
- package/packages/server/src/__tests__/tool-routes.test.ts +277 -0
- package/packages/server/src/__tests__/trusted-networks-config.test.ts +19 -0
- package/packages/server/src/__tests__/trusted-networks-no-oauth-roundtrip.test.ts +126 -0
- package/packages/server/src/__tests__/tunnel-cleanup.test.ts +90 -0
- package/packages/server/src/__tests__/tunnel.test.ts +13 -7
- package/packages/server/src/__tests__/wsl-tmux-probe-cache.test.ts +44 -0
- package/packages/server/src/bootstrap-queue.ts +130 -0
- package/packages/server/src/bootstrap-state.ts +131 -0
- package/packages/server/src/browse.ts +8 -3
- package/packages/server/src/browser-handlers/directory-handler.ts +23 -8
- package/packages/server/src/browser-handlers/session-action-handler.ts +213 -79
- package/packages/server/src/browser-handlers/session-action-helpers.ts +36 -0
- package/packages/server/src/cli.ts +256 -32
- package/packages/server/src/config-api.ts +16 -0
- package/packages/server/src/directory-service.ts +270 -39
- package/packages/server/src/editor-detection.ts +12 -9
- package/packages/server/src/editor-manager.ts +19 -4
- package/packages/server/src/editor-pid-registry.ts +9 -8
- package/packages/server/src/editor-registry.ts +22 -25
- package/packages/server/src/git-operations.ts +1 -1
- package/packages/server/src/headless-pid-registry.ts +7 -20
- package/packages/server/src/home-lock-release.ts +72 -0
- package/packages/server/src/home-lock.ts +389 -0
- package/packages/server/src/node-guard.ts +52 -0
- package/packages/server/src/package-manager-wrapper.ts +207 -47
- package/packages/server/src/pi-core-checker.ts +1 -1
- package/packages/server/src/pi-core-updater.ts +7 -1
- package/packages/server/src/pi-resource-scanner.ts +5 -8
- package/packages/server/src/pi-version-skew.ts +196 -0
- package/packages/server/src/preferences-store.ts +17 -3
- package/packages/server/src/process-manager.ts +403 -222
- package/packages/server/src/provider-probe.ts +234 -0
- package/packages/server/src/restart-helper.ts +130 -0
- package/packages/server/src/routes/bootstrap-routes.ts +88 -0
- package/packages/server/src/routes/openspec-routes.ts +25 -1
- package/packages/server/src/routes/pi-core-routes.ts +24 -1
- package/packages/server/src/routes/provider-auth-routes.ts +8 -8
- package/packages/server/src/routes/provider-routes.ts +43 -0
- package/packages/server/src/routes/recommended-routes.ts +10 -12
- package/packages/server/src/routes/system-routes.ts +20 -33
- package/packages/server/src/routes/tool-routes.ts +153 -0
- package/packages/server/src/server-pid.ts +5 -9
- package/packages/server/src/server.ts +211 -10
- package/packages/server/src/session-api.ts +77 -8
- package/packages/server/src/session-bootstrap.ts +17 -3
- package/packages/server/src/session-diff.ts +21 -21
- package/packages/server/src/terminal-manager.ts +61 -20
- package/packages/server/src/tunnel.ts +42 -28
- package/packages/shared/package.json +10 -3
- package/packages/shared/src/__tests__/{tool-resolver.test.ts → binary-lookup.test.ts} +32 -12
- package/packages/shared/src/__tests__/bootstrap/README.md +133 -0
- package/packages/shared/src/__tests__/bootstrap/__snapshots__/cube.test.ts.snap +370 -0
- package/packages/shared/src/__tests__/bootstrap/assertions.ts +136 -0
- package/packages/shared/src/__tests__/bootstrap/cube.test.ts +47 -0
- package/packages/shared/src/__tests__/bootstrap/cube.ts +66 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/a-electron.test.ts.snap +83 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/b-npm-global.test.ts.snap +89 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/c-dev-monorepo.test.ts.snap +33 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/d-overrides.test.ts.snap +20 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/e-stale-partial.test.ts.snap +61 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/f-cwd-variants.test.ts.snap +33 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/g-windows-specifics.test.ts.snap +46 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/j-path-gui-minimal.test.ts.snap +12 -0
- package/packages/shared/src/__tests__/bootstrap/families/a-electron.test.ts +156 -0
- package/packages/shared/src/__tests__/bootstrap/families/b-npm-global.test.ts +157 -0
- package/packages/shared/src/__tests__/bootstrap/families/c-dev-monorepo.test.ts +102 -0
- package/packages/shared/src/__tests__/bootstrap/families/d-overrides.test.ts +76 -0
- package/packages/shared/src/__tests__/bootstrap/families/e-stale-partial.test.ts +94 -0
- package/packages/shared/src/__tests__/bootstrap/families/f-cwd-variants.test.ts +87 -0
- package/packages/shared/src/__tests__/bootstrap/families/g-windows-specifics.test.ts +143 -0
- package/packages/shared/src/__tests__/bootstrap/families/h-home-drift.test.ts +64 -0
- package/packages/shared/src/__tests__/bootstrap/families/i-malformed-settings.test.ts +77 -0
- package/packages/shared/src/__tests__/bootstrap/families/index.ts +19 -0
- package/packages/shared/src/__tests__/bootstrap/families/j-path-gui-minimal.test.ts +61 -0
- package/packages/shared/src/__tests__/bootstrap/families/k-dashboard-absent.test.ts +50 -0
- package/packages/shared/src/__tests__/bootstrap/families/l-instance-coordination.test.ts +272 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/dev-monorepo.ts +58 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/electron-layout.ts +84 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/index.ts +9 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/managed-install.ts +85 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/npm-global-layout.ts +122 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/pi-versions.ts +36 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/settings-json.ts +39 -0
- package/packages/shared/src/__tests__/bootstrap/harness.smoke.test.ts +220 -0
- package/packages/shared/src/__tests__/bootstrap/harness.ts +413 -0
- package/packages/shared/src/__tests__/bootstrap/scenarios-skipped.ts +125 -0
- package/packages/shared/src/__tests__/bootstrap/scenarios.ts +132 -0
- package/packages/shared/src/__tests__/bridge-register.test.ts +29 -6
- package/packages/shared/src/__tests__/config-openspec.test.ts +106 -0
- package/packages/shared/src/__tests__/config.test.ts +56 -0
- package/packages/shared/src/__tests__/detached-spawn.test.ts +243 -0
- package/packages/shared/src/__tests__/managed-paths.test.ts +60 -0
- package/packages/shared/src/__tests__/no-direct-child-process.test.ts +112 -0
- package/packages/shared/src/__tests__/no-direct-platform-branch.test.ts +174 -0
- package/packages/shared/src/__tests__/no-direct-process-kill.test.ts +105 -0
- package/packages/shared/src/__tests__/platform-commands.test.ts +108 -0
- package/packages/shared/src/__tests__/platform-exec.test.ts +103 -0
- package/packages/shared/src/__tests__/platform-git.test.ts +194 -0
- package/packages/shared/src/__tests__/platform-npm.test.ts +137 -0
- package/packages/shared/src/__tests__/platform-openspec.test.ts +92 -0
- package/packages/shared/src/__tests__/platform-paths.test.ts +284 -0
- package/packages/shared/src/__tests__/platform-process-scan.test.ts +55 -0
- package/packages/shared/src/__tests__/platform-process.test.ts +160 -0
- package/packages/shared/src/__tests__/platform-runner.test.ts +173 -0
- package/packages/shared/src/__tests__/platform-shell.test.ts +74 -0
- package/packages/shared/src/__tests__/process-identify.test.ts +113 -0
- package/packages/shared/src/__tests__/recommended-extensions.test.ts +40 -7
- package/packages/shared/src/__tests__/resolve-jiti.test.ts +43 -7
- package/packages/shared/src/__tests__/semaphore.test.ts +119 -0
- package/packages/shared/src/__tests__/spawn-mechanism.test.ts +131 -0
- package/packages/shared/src/__tests__/tool-registry-definitions.test.ts +239 -0
- package/packages/shared/src/__tests__/tool-registry-overrides.test.ts +137 -0
- package/packages/shared/src/__tests__/tool-registry-registry.test.ts +343 -0
- package/packages/shared/src/bootstrap-install.ts +212 -0
- package/packages/shared/src/bridge-register.ts +87 -20
- package/packages/shared/src/browser-protocol.ts +71 -1
- package/packages/shared/src/config.ts +87 -15
- package/packages/shared/src/managed-paths.ts +31 -4
- package/packages/shared/src/openspec-poller.ts +63 -46
- package/packages/shared/src/{tool-resolver.ts → platform/binary-lookup.ts} +125 -25
- package/packages/shared/src/platform/commands.ts +100 -0
- package/packages/shared/src/platform/detached-spawn.ts +305 -0
- package/packages/shared/src/platform/exec.ts +220 -0
- package/packages/shared/src/platform/git.ts +155 -0
- package/packages/shared/src/platform/index.ts +15 -0
- package/packages/shared/src/platform/npm.ts +162 -0
- package/packages/shared/src/platform/openspec.ts +91 -0
- package/packages/shared/src/platform/paths.ts +276 -0
- package/packages/shared/src/platform/process-identify.ts +126 -0
- package/packages/shared/src/platform/process-scan.ts +94 -0
- package/packages/shared/src/platform/process.ts +168 -0
- package/packages/shared/src/platform/runner.ts +369 -0
- package/packages/shared/src/platform/shell.ts +44 -0
- package/packages/shared/src/platform/spawn-mechanism.ts +124 -0
- package/packages/shared/src/platform/subprocess-adapter.ts +124 -0
- package/packages/shared/src/recommended-extensions.ts +18 -2
- package/packages/shared/src/resolve-jiti.ts +62 -3
- package/packages/shared/src/rest-api.ts +26 -0
- package/packages/shared/src/semaphore.ts +83 -0
- package/packages/shared/src/tool-registry/definitions.ts +342 -0
- package/packages/shared/src/tool-registry/index.ts +56 -0
- package/packages/shared/src/tool-registry/overrides.ts +118 -0
- package/packages/shared/src/tool-registry/registry.ts +262 -0
- package/packages/shared/src/tool-registry/strategies.ts +198 -0
- package/packages/shared/src/tool-registry/types.ts +180 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixture: Electron packaged resources layout.
|
|
3
|
+
*
|
|
4
|
+
* macOS: /Applications/PI Dashboard.app/Contents/Resources/server/...
|
|
5
|
+
* Linux: /usr/lib/pi-dashboard/resources/server/...
|
|
6
|
+
* Windows: C:\Program Files\PI Dashboard\resources\server\...
|
|
7
|
+
* AppImage: /tmp/.mount_PIxxxx/resources/server/... (temp, unstable)
|
|
8
|
+
*
|
|
9
|
+
* The bundled extension lives alongside at
|
|
10
|
+
* `<resourcesPath>/server/packages/extension/`.
|
|
11
|
+
*/
|
|
12
|
+
import posix from "node:path/posix";
|
|
13
|
+
import win32 from "node:path/win32";
|
|
14
|
+
import type { FsRecord } from "../harness.js";
|
|
15
|
+
|
|
16
|
+
export interface ElectronLayoutSpec {
|
|
17
|
+
platform: NodeJS.Platform;
|
|
18
|
+
/** If true, simulate AppImage temp-mount path. */
|
|
19
|
+
appimage?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function resourcesRoot(spec: ElectronLayoutSpec): string {
|
|
23
|
+
if (spec.appimage) {
|
|
24
|
+
return "/tmp/.mount_PIxxxx/resources";
|
|
25
|
+
}
|
|
26
|
+
switch (spec.platform) {
|
|
27
|
+
case "darwin":
|
|
28
|
+
return "/Applications/PI Dashboard.app/Contents/Resources";
|
|
29
|
+
case "win32":
|
|
30
|
+
return "C:\\Program Files\\PI Dashboard\\resources";
|
|
31
|
+
default:
|
|
32
|
+
return "/usr/lib/pi-dashboard/resources";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function electronPackaged(spec: ElectronLayoutSpec): FsRecord {
|
|
37
|
+
const p = spec.platform === "win32" ? win32 : posix;
|
|
38
|
+
const resources = resourcesRoot(spec);
|
|
39
|
+
const serverDir = p.join(resources, "server");
|
|
40
|
+
const extensionDir = p.join(serverDir, "packages", "extension");
|
|
41
|
+
const out: Record<string, string> = {};
|
|
42
|
+
|
|
43
|
+
// Bundled server package.json
|
|
44
|
+
out[p.join(serverDir, "package.json")] = JSON.stringify({
|
|
45
|
+
name: "@blackbelt-technology/pi-agent-dashboard-root",
|
|
46
|
+
version: "0.4.0",
|
|
47
|
+
private: true,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Bundled server CLI source
|
|
51
|
+
out[p.join(serverDir, "packages", "server", "package.json")] = JSON.stringify({
|
|
52
|
+
name: "@blackbelt-technology/pi-dashboard-server",
|
|
53
|
+
version: "0.4.0",
|
|
54
|
+
});
|
|
55
|
+
out[p.join(serverDir, "packages", "server", "src", "cli.ts")] = "// cli";
|
|
56
|
+
|
|
57
|
+
// Bundled bridge extension
|
|
58
|
+
out[p.join(extensionDir, "package.json")] = JSON.stringify({
|
|
59
|
+
name: "@blackbelt-technology/pi-dashboard-extension",
|
|
60
|
+
version: "0.4.0",
|
|
61
|
+
});
|
|
62
|
+
out[p.join(extensionDir, "src", "bridge.ts")] = "// bridge";
|
|
63
|
+
|
|
64
|
+
// Bundled Node.js (minimal)
|
|
65
|
+
const nodeBin = spec.platform === "win32"
|
|
66
|
+
? p.join(resources, "node", "bin", "node.exe")
|
|
67
|
+
: p.join(resources, "node", "bin", "node");
|
|
68
|
+
out[nodeBin] = "\x7fELF"; // binary-ish marker
|
|
69
|
+
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Returns the resolved `resourcesPath` + extension path for assertions. */
|
|
74
|
+
export function electronPaths(spec: ElectronLayoutSpec): {
|
|
75
|
+
resources: string;
|
|
76
|
+
serverDir: string;
|
|
77
|
+
extensionDir: string;
|
|
78
|
+
} {
|
|
79
|
+
const p = spec.platform === "win32" ? win32 : posix;
|
|
80
|
+
const resources = resourcesRoot(spec);
|
|
81
|
+
const serverDir = p.join(resources, "server");
|
|
82
|
+
const extensionDir = p.join(serverDir, "packages", "extension");
|
|
83
|
+
return { resources, serverDir, extensionDir };
|
|
84
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Barrel export for bootstrap fixtures. Import as `import * as fixtures`.
|
|
3
|
+
*/
|
|
4
|
+
export * from "./pi-versions.js";
|
|
5
|
+
export * from "./managed-install.js";
|
|
6
|
+
export * from "./npm-global-layout.js";
|
|
7
|
+
export * from "./electron-layout.js";
|
|
8
|
+
export * from "./dev-monorepo.js";
|
|
9
|
+
export * from "./settings-json.js";
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixture: managed install at `<homedir>/.pi-dashboard/`.
|
|
3
|
+
*
|
|
4
|
+
* Produces the fs layout a user gets after running the Electron wizard
|
|
5
|
+
* or proposal-2's CLI first-run bootstrap: node_modules/@mariozechner/
|
|
6
|
+
* pi-coding-agent + node_modules/.bin shims.
|
|
7
|
+
*/
|
|
8
|
+
import posix from "node:path/posix";
|
|
9
|
+
import win32 from "node:path/win32";
|
|
10
|
+
import type { FsRecord } from "../harness.js";
|
|
11
|
+
import { openspecPackageJson, piPackageJson, type PiVersionSpec } from "./pi-versions.js";
|
|
12
|
+
|
|
13
|
+
export interface ManagedInstallSpec {
|
|
14
|
+
homedir: string;
|
|
15
|
+
platform: NodeJS.Platform;
|
|
16
|
+
pi?: PiVersionSpec | false;
|
|
17
|
+
openspec?: string | false;
|
|
18
|
+
tsx?: string | false;
|
|
19
|
+
/**
|
|
20
|
+
* If `true`, write just the package.json for pi — no `dist/cli.js`.
|
|
21
|
+
* Simulates an install that was interrupted mid-extract (scenario E2).
|
|
22
|
+
*/
|
|
23
|
+
piPartial?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function managedInstall(spec: ManagedInstallSpec): FsRecord {
|
|
27
|
+
const p = spec.platform === "win32" ? win32 : posix;
|
|
28
|
+
const out: Record<string, string> = {};
|
|
29
|
+
const managedDir = p.join(spec.homedir, ".pi-dashboard");
|
|
30
|
+
const nodeModules = p.join(managedDir, "node_modules");
|
|
31
|
+
const binDir = p.join(nodeModules, ".bin");
|
|
32
|
+
|
|
33
|
+
out[p.join(managedDir, "package.json")] = JSON.stringify({
|
|
34
|
+
name: "pi-dashboard-managed",
|
|
35
|
+
private: true,
|
|
36
|
+
type: "module",
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (spec.pi !== false) {
|
|
40
|
+
const piSpec = spec.pi ?? {};
|
|
41
|
+
const piDir = p.join(nodeModules, "@mariozechner", "pi-coding-agent");
|
|
42
|
+
out[p.join(piDir, "package.json")] = piPackageJson(piSpec);
|
|
43
|
+
if (!spec.piPartial) {
|
|
44
|
+
out[p.join(piDir, "dist", "cli.js")] = "#!/usr/bin/env node\n// pi cli stub";
|
|
45
|
+
// bin shim
|
|
46
|
+
if (spec.platform === "win32") {
|
|
47
|
+
out[p.join(binDir, "pi.cmd")] = "@node %~dp0\\..\\@mariozechner\\pi-coding-agent\\dist\\cli.js %*";
|
|
48
|
+
} else {
|
|
49
|
+
out[p.join(binDir, "pi")] = "#!/bin/sh\nexec node ../@mariozechner/pi-coding-agent/dist/cli.js \"$@\"";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (spec.openspec !== false) {
|
|
55
|
+
const v = spec.openspec ?? "0.4.1";
|
|
56
|
+
const dir = p.join(nodeModules, "openspec");
|
|
57
|
+
out[p.join(dir, "package.json")] = openspecPackageJson(v);
|
|
58
|
+
out[p.join(dir, "dist", "cli.js")] = "#!/usr/bin/env node";
|
|
59
|
+
out[p.join(dir, "dist", "index.js")] = "module.exports = {};";
|
|
60
|
+
if (spec.platform === "win32") {
|
|
61
|
+
out[p.join(binDir, "openspec.cmd")] = "@node %~dp0\\..\\openspec\\dist\\cli.js %*";
|
|
62
|
+
} else {
|
|
63
|
+
out[p.join(binDir, "openspec")] = "#!/bin/sh\nexec node ../openspec/dist/cli.js \"$@\"";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (spec.tsx !== false) {
|
|
68
|
+
const v = spec.tsx ?? "4.20.0";
|
|
69
|
+
const dir = p.join(nodeModules, "tsx");
|
|
70
|
+
out[p.join(dir, "package.json")] = JSON.stringify({
|
|
71
|
+
name: "tsx",
|
|
72
|
+
version: v,
|
|
73
|
+
main: "dist/cli.mjs",
|
|
74
|
+
bin: { tsx: "dist/cli.mjs" },
|
|
75
|
+
});
|
|
76
|
+
out[p.join(dir, "dist", "cli.mjs")] = "#!/usr/bin/env node";
|
|
77
|
+
if (spec.platform === "win32") {
|
|
78
|
+
out[p.join(binDir, "tsx.cmd")] = "@node %~dp0\\..\\tsx\\dist\\cli.mjs %*";
|
|
79
|
+
} else {
|
|
80
|
+
out[p.join(binDir, "tsx")] = "#!/bin/sh\nexec node ../tsx/dist/cli.mjs \"$@\"";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixture: global npm install layout.
|
|
3
|
+
*
|
|
4
|
+
* Posix: /usr/lib/node_modules/<pkg>/...
|
|
5
|
+
* Windows: %APPDATA%\Roaming\npm\node_modules\<pkg>\...
|
|
6
|
+
* with pi.cmd / openspec.cmd shims in %APPDATA%\Roaming\npm\
|
|
7
|
+
*
|
|
8
|
+
* For the "%ProgramFiles%\nodejs" variant on Windows, see
|
|
9
|
+
* `npmGlobalWindowsProgramFiles` — npm is installed there when users pick
|
|
10
|
+
* "Add to PATH" during Node.js installer.
|
|
11
|
+
*/
|
|
12
|
+
import posix from "node:path/posix";
|
|
13
|
+
import win32 from "node:path/win32";
|
|
14
|
+
import type { FsRecord } from "../harness.js";
|
|
15
|
+
import { openspecPackageJson, piPackageJson, type PiVersionSpec } from "./pi-versions.js";
|
|
16
|
+
|
|
17
|
+
interface PosixSpec {
|
|
18
|
+
pi?: PiVersionSpec | false;
|
|
19
|
+
openspec?: string | false;
|
|
20
|
+
/** Install root; defaults to `/usr/lib/node_modules`. */
|
|
21
|
+
root?: string;
|
|
22
|
+
binDir?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface WindowsSpec {
|
|
26
|
+
pi?: PiVersionSpec | false;
|
|
27
|
+
openspec?: string | false;
|
|
28
|
+
dashboard?: boolean;
|
|
29
|
+
/** Where %APPDATA%\Roaming\npm lives. */
|
|
30
|
+
npmDir?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Unix global npm install. Root defaults to `/usr/lib/node_modules`,
|
|
35
|
+
* binaries at `/usr/local/bin`.
|
|
36
|
+
*/
|
|
37
|
+
export function npmGlobalUnix(spec: PosixSpec = {}): FsRecord {
|
|
38
|
+
const p = posix;
|
|
39
|
+
const root = spec.root ?? "/usr/lib/node_modules";
|
|
40
|
+
const binDir = spec.binDir ?? "/usr/local/bin";
|
|
41
|
+
const out: Record<string, string> = {};
|
|
42
|
+
|
|
43
|
+
if (spec.pi !== false) {
|
|
44
|
+
const piSpec = spec.pi ?? {};
|
|
45
|
+
const piDir = p.join(root, "@mariozechner", "pi-coding-agent");
|
|
46
|
+
out[p.join(piDir, "package.json")] = piPackageJson(piSpec);
|
|
47
|
+
out[p.join(piDir, "dist", "cli.js")] = "#!/usr/bin/env node";
|
|
48
|
+
out[p.join(binDir, "pi")] = "#!/bin/sh\nexec node ...";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (spec.openspec !== false) {
|
|
52
|
+
// openspec tool definition uses package `@fission-ai/openspec`
|
|
53
|
+
// with entry `bin/openspec.js` (see definitions.ts).
|
|
54
|
+
const dir = p.join(root, "@fission-ai", "openspec");
|
|
55
|
+
out[p.join(dir, "package.json")] = JSON.stringify({
|
|
56
|
+
name: "@fission-ai/openspec",
|
|
57
|
+
version: spec.openspec ?? "0.4.1",
|
|
58
|
+
bin: { openspec: "bin/openspec.js" },
|
|
59
|
+
});
|
|
60
|
+
out[p.join(dir, "bin", "openspec.js")] = "#!/usr/bin/env node";
|
|
61
|
+
out[p.join(binDir, "openspec")] = "#!/bin/sh\nexec node ...";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Windows AppData\Roaming\npm layout — the default for MSI installs of
|
|
69
|
+
* Node.js. `npmDir` overrides the location.
|
|
70
|
+
*/
|
|
71
|
+
export function npmGlobalWindowsAppData(
|
|
72
|
+
homedir: string,
|
|
73
|
+
spec: WindowsSpec = {},
|
|
74
|
+
): FsRecord {
|
|
75
|
+
const p = win32;
|
|
76
|
+
const npmDir = spec.npmDir ?? p.join(homedir, "AppData", "Roaming", "npm");
|
|
77
|
+
const nodeModules = p.join(npmDir, "node_modules");
|
|
78
|
+
const out: Record<string, string> = {};
|
|
79
|
+
|
|
80
|
+
if (spec.pi !== false) {
|
|
81
|
+
const piSpec = spec.pi ?? {};
|
|
82
|
+
const piDir = p.join(nodeModules, "@mariozechner", "pi-coding-agent");
|
|
83
|
+
out[p.join(piDir, "package.json")] = piPackageJson(piSpec);
|
|
84
|
+
out[p.join(piDir, "dist", "cli.js")] = "#!/usr/bin/env node";
|
|
85
|
+
out[p.join(npmDir, "pi.cmd")] = "@node %~dp0\\node_modules\\@mariozechner\\pi-coding-agent\\dist\\cli.js %*";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (spec.openspec !== false) {
|
|
89
|
+
const dir = p.join(nodeModules, "@fission-ai", "openspec");
|
|
90
|
+
out[p.join(dir, "package.json")] = JSON.stringify({
|
|
91
|
+
name: "@fission-ai/openspec",
|
|
92
|
+
version: spec.openspec ?? "0.4.1",
|
|
93
|
+
bin: { openspec: "bin/openspec.js" },
|
|
94
|
+
});
|
|
95
|
+
out[p.join(dir, "bin", "openspec.js")] = "#!/usr/bin/env node";
|
|
96
|
+
out[p.join(npmDir, "openspec.cmd")] = "@node %~dp0\\node_modules\\@fission-ai\\openspec\\bin\\openspec.js %*";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (spec.dashboard) {
|
|
100
|
+
const dir = p.join(nodeModules, "@blackbelt-technology", "pi-agent-dashboard");
|
|
101
|
+
out[p.join(dir, "package.json")] = JSON.stringify({
|
|
102
|
+
name: "@blackbelt-technology/pi-agent-dashboard",
|
|
103
|
+
version: "0.4.0",
|
|
104
|
+
bin: { "pi-dashboard": "packages/server/dist/cli.js" },
|
|
105
|
+
});
|
|
106
|
+
out[p.join(dir, "packages", "server", "dist", "cli.js")] = "#!/usr/bin/env node";
|
|
107
|
+
out[p.join(npmDir, "pi-dashboard.cmd")] = "@node %~dp0\\node_modules\\@blackbelt-technology\\pi-agent-dashboard\\packages\\server\\dist\\cli.js %*";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Windows %ProgramFiles%\nodejs\node_modules layout — picked when Node
|
|
115
|
+
* installer chose "install as system tool."
|
|
116
|
+
*/
|
|
117
|
+
export function npmGlobalWindowsProgramFiles(spec: WindowsSpec = {}): FsRecord {
|
|
118
|
+
return npmGlobalWindowsAppData("C:\\Program Files\\nodejs_", {
|
|
119
|
+
...spec,
|
|
120
|
+
npmDir: "C:\\Program Files\\nodejs",
|
|
121
|
+
});
|
|
122
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixture: stamp a specific version into a pi-coding-agent package.json.
|
|
3
|
+
* Returns the package.json JSON string ready for FsRecord insertion.
|
|
4
|
+
*/
|
|
5
|
+
export interface PiVersionSpec {
|
|
6
|
+
name?: string;
|
|
7
|
+
version?: string;
|
|
8
|
+
bin?: Record<string, string>;
|
|
9
|
+
main?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function piPackageJson(spec: PiVersionSpec = {}): string {
|
|
13
|
+
return JSON.stringify(
|
|
14
|
+
{
|
|
15
|
+
name: spec.name ?? "@mariozechner/pi-coding-agent",
|
|
16
|
+
version: spec.version ?? "0.6.3",
|
|
17
|
+
main: spec.main ?? "dist/cli.js",
|
|
18
|
+
bin: spec.bin ?? { pi: "dist/cli.js" },
|
|
19
|
+
},
|
|
20
|
+
null,
|
|
21
|
+
2,
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function openspecPackageJson(version = "0.4.1"): string {
|
|
26
|
+
return JSON.stringify(
|
|
27
|
+
{
|
|
28
|
+
name: "openspec",
|
|
29
|
+
version,
|
|
30
|
+
main: "dist/index.js",
|
|
31
|
+
bin: { openspec: "dist/cli.js" },
|
|
32
|
+
},
|
|
33
|
+
null,
|
|
34
|
+
2,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixture: `<homedir>/.pi/agent/settings.json` — pi's extension registry.
|
|
3
|
+
*
|
|
4
|
+
* Supports:
|
|
5
|
+
* - valid { packages: [...] }
|
|
6
|
+
* - empty/missing (return no entry)
|
|
7
|
+
* - malformed (broken JSON)
|
|
8
|
+
* - extra non-dashboard packages (preservation test)
|
|
9
|
+
*/
|
|
10
|
+
import posix from "node:path/posix";
|
|
11
|
+
import win32 from "node:path/win32";
|
|
12
|
+
import type { FsRecord } from "../harness.js";
|
|
13
|
+
|
|
14
|
+
export interface SettingsJsonSpec {
|
|
15
|
+
homedir: string;
|
|
16
|
+
platform: NodeJS.Platform;
|
|
17
|
+
packages?: readonly string[];
|
|
18
|
+
/** If true, write broken JSON instead of a valid object. */
|
|
19
|
+
malformed?: boolean;
|
|
20
|
+
/** If true, omit the file entirely. */
|
|
21
|
+
missing?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function settingsJsonPath(homedir: string, platform: NodeJS.Platform): string {
|
|
25
|
+
const p = platform === "win32" ? win32 : posix;
|
|
26
|
+
return p.join(homedir, ".pi", "agent", "settings.json");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function settingsJson(spec: SettingsJsonSpec): FsRecord {
|
|
30
|
+
if (spec.missing) return {};
|
|
31
|
+
const out: Record<string, string> = {};
|
|
32
|
+
const path = settingsJsonPath(spec.homedir, spec.platform);
|
|
33
|
+
if (spec.malformed) {
|
|
34
|
+
out[path] = "{broken json here";
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
out[path] = JSON.stringify({ packages: spec.packages ?? [] }, null, 2) + "\n";
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smoke tests — validate the harness itself, not any real scenario.
|
|
3
|
+
* Full scenario families (A–K) land in later tasks.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
import { withFakeEnv, layer, toMemfsPath } from "./harness.js";
|
|
7
|
+
import {
|
|
8
|
+
registerDefaultTools,
|
|
9
|
+
} from "../../tool-registry/definitions.js";
|
|
10
|
+
import { registerBridgeExtension } from "../../bridge-register.js";
|
|
11
|
+
|
|
12
|
+
describe("harness smoke", () => {
|
|
13
|
+
it("withFakeEnv runs callback with HarnessContext (posix)", async () => {
|
|
14
|
+
const result = await withFakeEnv(
|
|
15
|
+
{
|
|
16
|
+
platform: "linux",
|
|
17
|
+
homedir: "/home/robson",
|
|
18
|
+
env: { PATH: "/usr/bin:/usr/local/bin" },
|
|
19
|
+
fs: { "/home/robson/.pi/config": "x" },
|
|
20
|
+
},
|
|
21
|
+
(ctx) => {
|
|
22
|
+
expect(ctx.platform).toBe("linux");
|
|
23
|
+
expect(ctx.homedir).toBe("/home/robson");
|
|
24
|
+
expect(ctx.pathEntries).toEqual(["/usr/bin", "/usr/local/bin"]);
|
|
25
|
+
expect(ctx.fs.existsSync("/home/robson/.pi/config")).toBe(true);
|
|
26
|
+
return 42;
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
expect(result).toBe(42);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("withFakeEnv supports win32 paths via translation", async () => {
|
|
33
|
+
await withFakeEnv(
|
|
34
|
+
{
|
|
35
|
+
platform: "win32",
|
|
36
|
+
homedir: "C:\\Users\\Robert",
|
|
37
|
+
env: { PATH: "C:\\Windows\\System32;C:\\Program Files\\nodejs" },
|
|
38
|
+
fs: {
|
|
39
|
+
"C:\\Users\\Robert\\.pi-dashboard\\node_modules\\.bin\\pi.cmd":
|
|
40
|
+
"@node pi",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
(ctx) => {
|
|
44
|
+
expect(ctx.pathEntries).toEqual([
|
|
45
|
+
"C:\\Windows\\System32",
|
|
46
|
+
"C:\\Program Files\\nodejs",
|
|
47
|
+
]);
|
|
48
|
+
// existsSync via the wrapped fs should accept win32-style paths
|
|
49
|
+
expect(
|
|
50
|
+
ctx.fs.existsSync(
|
|
51
|
+
"C:\\Users\\Robert\\.pi-dashboard\\node_modules\\.bin\\pi.cmd",
|
|
52
|
+
),
|
|
53
|
+
).toBe(true);
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("toMemfsPath translates Windows paths to posix keys", () => {
|
|
59
|
+
expect(toMemfsPath("C:\\Users\\Robert\\.pi")).toBe("/C:/Users/Robert/.pi");
|
|
60
|
+
expect(toMemfsPath("/already/posix")).toBe("/already/posix");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("which() walks fake PATH and finds binaries with .cmd on win32", async () => {
|
|
64
|
+
await withFakeEnv(
|
|
65
|
+
{
|
|
66
|
+
platform: "win32",
|
|
67
|
+
homedir: "C:\\Users\\R",
|
|
68
|
+
env: { PATH: "C:\\bin" },
|
|
69
|
+
fs: { "C:\\bin\\pi.cmd": "@echo off" },
|
|
70
|
+
},
|
|
71
|
+
(ctx) => {
|
|
72
|
+
const deps = ctx.createStrategyDeps();
|
|
73
|
+
expect(deps.which("pi")).toBe("C:\\bin\\pi.cmd");
|
|
74
|
+
expect(deps.which("nonexistent")).toBe(null);
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("which() on posix finds binary without extension", async () => {
|
|
80
|
+
await withFakeEnv(
|
|
81
|
+
{
|
|
82
|
+
platform: "linux",
|
|
83
|
+
homedir: "/home/r",
|
|
84
|
+
env: { PATH: "/usr/local/bin" },
|
|
85
|
+
fs: { "/usr/local/bin/pi": "#!/bin/sh\nexec node pi.js" },
|
|
86
|
+
},
|
|
87
|
+
(ctx) => {
|
|
88
|
+
const deps = ctx.createStrategyDeps();
|
|
89
|
+
expect(deps.which("pi")).toBe("/usr/local/bin/pi");
|
|
90
|
+
},
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("resolveModule() walks node_modules ancestor chain", async () => {
|
|
95
|
+
await withFakeEnv(
|
|
96
|
+
{
|
|
97
|
+
platform: "linux",
|
|
98
|
+
homedir: "/home/r",
|
|
99
|
+
fs: {
|
|
100
|
+
"/home/r/project/src/index.ts": "x",
|
|
101
|
+
"/home/r/project/node_modules/@mariozechner/pi/package.json":
|
|
102
|
+
JSON.stringify({ name: "@mariozechner/pi", main: "dist/cli.js" }),
|
|
103
|
+
"/home/r/project/node_modules/@mariozechner/pi/dist/cli.js":
|
|
104
|
+
"#!/usr/bin/env node",
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
(ctx) => {
|
|
108
|
+
const deps = ctx.createStrategyDeps();
|
|
109
|
+
const resolved = deps.resolveModule(
|
|
110
|
+
"@mariozechner/pi",
|
|
111
|
+
"/home/r/project/src/index.ts",
|
|
112
|
+
);
|
|
113
|
+
expect(resolved).toBe(
|
|
114
|
+
"/home/r/project/node_modules/@mariozechner/pi/dist/cli.js",
|
|
115
|
+
);
|
|
116
|
+
},
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("resolveModule() returns null when package absent", async () => {
|
|
121
|
+
await withFakeEnv(
|
|
122
|
+
{
|
|
123
|
+
platform: "linux",
|
|
124
|
+
homedir: "/home/r",
|
|
125
|
+
fs: { "/home/r/project/src/index.ts": "x" },
|
|
126
|
+
},
|
|
127
|
+
(ctx) => {
|
|
128
|
+
const deps = ctx.createStrategyDeps();
|
|
129
|
+
expect(
|
|
130
|
+
deps.resolveModule("@mariozechner/pi", "/home/r/project/src/index.ts"),
|
|
131
|
+
).toBe(null);
|
|
132
|
+
},
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("layer() merges fs records (later overrides earlier)", () => {
|
|
137
|
+
const merged = layer(
|
|
138
|
+
{ "/a": "1", "/b": "2" },
|
|
139
|
+
{ "/b": "3", "/c": "4" },
|
|
140
|
+
);
|
|
141
|
+
expect(merged).toEqual({ "/a": "1", "/b": "3", "/c": "4" });
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("createRegistry wires env and platform through to strategies (linux, managed bin)", async () => {
|
|
145
|
+
// On Unix, pi resolves via <managedBin>/pi (the `.bin` shim).
|
|
146
|
+
await withFakeEnv(
|
|
147
|
+
{
|
|
148
|
+
platform: "linux",
|
|
149
|
+
homedir: "/home/r",
|
|
150
|
+
fs: {
|
|
151
|
+
"/home/r/.pi-dashboard/node_modules/.bin/pi":
|
|
152
|
+
"#!/usr/bin/env node",
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
(ctx) => {
|
|
156
|
+
const registry = ctx.createRegistry();
|
|
157
|
+
const deps = ctx.createStrategyDeps();
|
|
158
|
+
registerDefaultTools(registry, deps);
|
|
159
|
+
const res = registry.resolve("pi");
|
|
160
|
+
expect(res.ok).toBe(true);
|
|
161
|
+
expect(res.source).toBe("managed");
|
|
162
|
+
expect(res.path).toBe("/home/r/.pi-dashboard/node_modules/.bin/pi");
|
|
163
|
+
},
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("createRegistry resolves pi via managed module on win32", async () => {
|
|
168
|
+
// On Windows, pi's chain includes managedModuleStrategy for
|
|
169
|
+
// pi-coding-agent — finds dist/cli.js directly.
|
|
170
|
+
await withFakeEnv(
|
|
171
|
+
{
|
|
172
|
+
platform: "win32",
|
|
173
|
+
homedir: "C:\\Users\\R",
|
|
174
|
+
fs: {
|
|
175
|
+
"C:\\Users\\R\\.pi-dashboard\\node_modules\\@mariozechner\\pi-coding-agent\\dist\\cli.js":
|
|
176
|
+
"#!/usr/bin/env node",
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
(ctx) => {
|
|
180
|
+
const registry = ctx.createRegistry();
|
|
181
|
+
const deps = ctx.createStrategyDeps();
|
|
182
|
+
registerDefaultTools(registry, deps);
|
|
183
|
+
const res = registry.resolve("pi");
|
|
184
|
+
expect(res.ok).toBe(true);
|
|
185
|
+
expect(res.source).toBe("managed");
|
|
186
|
+
},
|
|
187
|
+
);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("readSettings returns null when settings.json absent", async () => {
|
|
191
|
+
await withFakeEnv(
|
|
192
|
+
{ platform: "linux", homedir: "/home/r", fs: {} },
|
|
193
|
+
(ctx) => {
|
|
194
|
+
expect(ctx.readSettings()).toBe(null);
|
|
195
|
+
},
|
|
196
|
+
);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("bridge registration writes settings.json under fake HOME", async () => {
|
|
200
|
+
await withFakeEnv(
|
|
201
|
+
{
|
|
202
|
+
platform: "linux",
|
|
203
|
+
homedir: "/home/r",
|
|
204
|
+
fs: {
|
|
205
|
+
"/opt/extension/package.json": JSON.stringify({ name: "ext" }),
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
(ctx) => {
|
|
209
|
+
// Register bridge using the homedir override, but the real
|
|
210
|
+
// implementation uses `node:fs` — which means this assertion
|
|
211
|
+
// verifies the API signature compiles. Actual fake-fs bridge
|
|
212
|
+
// writes land in a follow-up task (bridge-register needs to
|
|
213
|
+
// accept an injectable fs impl; out of scope for smoke).
|
|
214
|
+
expect(() =>
|
|
215
|
+
registerBridgeExtension("/opt/extension", { homedir: "/tmp/nope" }),
|
|
216
|
+
).not.toThrow();
|
|
217
|
+
},
|
|
218
|
+
);
|
|
219
|
+
});
|
|
220
|
+
});
|