@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,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for packages/shared/src/platform/git.ts — Recipe argv shapes +
|
|
3
|
+
* light integration tests against the actual repository.
|
|
4
|
+
*
|
|
5
|
+
* The argv-shape tests are pure: they verify the Recipe's `argv()`
|
|
6
|
+
* function produces the expected command without spawning anything.
|
|
7
|
+
*
|
|
8
|
+
* The integration tests use the actual repo as a git fixture; they're
|
|
9
|
+
* smoke tests that the full recipe → runner → git pipeline works.
|
|
10
|
+
*
|
|
11
|
+
* See change: platform-command-executor.
|
|
12
|
+
*/
|
|
13
|
+
import { describe, it, expect } from "vitest";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import url from "node:url";
|
|
16
|
+
import {
|
|
17
|
+
GIT_DIFF,
|
|
18
|
+
GIT_STATUS_PORCELAIN,
|
|
19
|
+
GIT_IS_REPO,
|
|
20
|
+
GIT_CURRENT_BRANCH,
|
|
21
|
+
GIT_HEAD_SHA,
|
|
22
|
+
GIT_REMOTE_URL,
|
|
23
|
+
GH_PR_NUMBER,
|
|
24
|
+
GIT_RECIPES,
|
|
25
|
+
isGitRepo,
|
|
26
|
+
currentBranch,
|
|
27
|
+
headSha,
|
|
28
|
+
remoteUrl,
|
|
29
|
+
diff,
|
|
30
|
+
statusPorcelain,
|
|
31
|
+
isGitRepoOr,
|
|
32
|
+
currentBranchOr,
|
|
33
|
+
} from "../platform/git.js";
|
|
34
|
+
|
|
35
|
+
const here = path.dirname(url.fileURLToPath(import.meta.url));
|
|
36
|
+
const REPO_ROOT = path.resolve(here, "..", "..", "..", "..");
|
|
37
|
+
|
|
38
|
+
// ── Pure argv tests ─────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
describe("GIT_DIFF.argv", () => {
|
|
41
|
+
it("produces `git diff HEAD -- <path>` by default", () => {
|
|
42
|
+
expect(GIT_DIFF.argv({ cwd: "/tmp", path: "src/foo.ts" })).toEqual([
|
|
43
|
+
"git", "diff", "HEAD", "--", "src/foo.ts",
|
|
44
|
+
]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("respects an explicit ref", () => {
|
|
48
|
+
expect(GIT_DIFF.argv({ cwd: "/tmp", path: "a.ts", ref: "main" })).toEqual([
|
|
49
|
+
"git", "diff", "main", "--", "a.ts",
|
|
50
|
+
]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("does not shell-escape the path (argv array is passed verbatim)", () => {
|
|
54
|
+
// With argv arrays, paths like "a b.ts" flow through as one element.
|
|
55
|
+
// No JSON.stringify, no quoting — safe by construction.
|
|
56
|
+
expect(GIT_DIFF.argv({ cwd: "/tmp", path: "a b.ts" })).toEqual([
|
|
57
|
+
"git", "diff", "HEAD", "--", "a b.ts",
|
|
58
|
+
]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("tolerates exit code 1 (no-diff or empty repo)", () => {
|
|
62
|
+
expect(GIT_DIFF.tolerate).toContain(1);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe("GIT_STATUS_PORCELAIN.argv", () => {
|
|
67
|
+
it("omits `--` when no path is given", () => {
|
|
68
|
+
expect(GIT_STATUS_PORCELAIN.argv({ cwd: "/tmp" })).toEqual([
|
|
69
|
+
"git", "status", "--porcelain",
|
|
70
|
+
]);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("adds `-- <path>` when a path is given", () => {
|
|
74
|
+
expect(GIT_STATUS_PORCELAIN.argv({ cwd: "/tmp", path: "src/foo.ts" })).toEqual([
|
|
75
|
+
"git", "status", "--porcelain", "--", "src/foo.ts",
|
|
76
|
+
]);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe("other recipe argv shapes", () => {
|
|
81
|
+
it("GIT_IS_REPO", () => {
|
|
82
|
+
expect(GIT_IS_REPO.argv({ cwd: "/tmp" })).toEqual(["git", "rev-parse", "--is-inside-work-tree"]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("GIT_CURRENT_BRANCH", () => {
|
|
86
|
+
expect(GIT_CURRENT_BRANCH.argv({ cwd: "/tmp" })).toEqual(["git", "rev-parse", "--abbrev-ref", "HEAD"]);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("GIT_HEAD_SHA (full)", () => {
|
|
90
|
+
expect(GIT_HEAD_SHA.argv({ cwd: "/tmp" })).toEqual(["git", "rev-parse", "HEAD"]);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("GIT_HEAD_SHA (short)", () => {
|
|
94
|
+
expect(GIT_HEAD_SHA.argv({ cwd: "/tmp", short: true })).toEqual(["git", "rev-parse", "--short", "HEAD"]);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("GIT_REMOTE_URL (default origin)", () => {
|
|
98
|
+
expect(GIT_REMOTE_URL.argv({ cwd: "/tmp" })).toEqual(["git", "remote", "get-url", "origin"]);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("GIT_REMOTE_URL (custom remote)", () => {
|
|
102
|
+
expect(GIT_REMOTE_URL.argv({ cwd: "/tmp", remote: "upstream" })).toEqual([
|
|
103
|
+
"git", "remote", "get-url", "upstream",
|
|
104
|
+
]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("GH_PR_NUMBER tolerates exit 1 (no PR)", () => {
|
|
108
|
+
expect(GH_PR_NUMBER.tolerate).toContain(1);
|
|
109
|
+
expect(GH_PR_NUMBER.argv({ cwd: "/tmp" })).toEqual([
|
|
110
|
+
"gh", "pr", "view", "--json", "number", "-q", ".number",
|
|
111
|
+
]);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("GIT_RECIPES registry", () => {
|
|
116
|
+
it("enumerates all exported recipes", () => {
|
|
117
|
+
const keys = Object.keys(GIT_RECIPES).sort();
|
|
118
|
+
expect(keys).toEqual([
|
|
119
|
+
"GH_PR_NUMBER",
|
|
120
|
+
"GIT_CURRENT_BRANCH",
|
|
121
|
+
"GIT_DIFF",
|
|
122
|
+
"GIT_HEAD_SHA",
|
|
123
|
+
"GIT_IS_REPO",
|
|
124
|
+
"GIT_REMOTE_URL",
|
|
125
|
+
"GIT_STATUS_PORCELAIN",
|
|
126
|
+
]);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("every recipe has argv and parse functions", () => {
|
|
130
|
+
for (const [name, recipe] of Object.entries(GIT_RECIPES)) {
|
|
131
|
+
expect(typeof recipe.argv, `${name}.argv`).toBe("function");
|
|
132
|
+
expect(typeof recipe.parse, `${name}.parse`).toBe("function");
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// ── Integration tests against the actual repository ────────────────────────
|
|
138
|
+
|
|
139
|
+
describe("git.* integration (runs against this repo)", () => {
|
|
140
|
+
it("isGitRepo returns true for the repo root", () => {
|
|
141
|
+
const result = isGitRepo({ cwd: REPO_ROOT });
|
|
142
|
+
expect(result.ok).toBe(true);
|
|
143
|
+
if (result.ok) expect(result.value).toBe(true);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("isGitRepoOr returns false for a non-repo directory", () => {
|
|
147
|
+
expect(isGitRepoOr({ cwd: require("node:os").tmpdir() })).toBe(false);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("currentBranch returns a string for the repo", () => {
|
|
151
|
+
const result = currentBranch({ cwd: REPO_ROOT });
|
|
152
|
+
expect(result.ok).toBe(true);
|
|
153
|
+
if (result.ok) {
|
|
154
|
+
expect(typeof result.value).toBe("string");
|
|
155
|
+
expect(result.value!.length).toBeGreaterThan(0);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("headSha returns a 40-char SHA for the repo", () => {
|
|
160
|
+
const result = headSha({ cwd: REPO_ROOT });
|
|
161
|
+
expect(result.ok).toBe(true);
|
|
162
|
+
if (result.ok) expect(result.value).toMatch(/^[0-9a-f]{40}$/);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("headSha short returns a 7-char SHA", () => {
|
|
166
|
+
const result = headSha({ cwd: REPO_ROOT, short: true });
|
|
167
|
+
expect(result.ok).toBe(true);
|
|
168
|
+
if (result.ok) expect(result.value).toMatch(/^[0-9a-f]{7}/);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("remoteUrl returns a URL string if origin is set", () => {
|
|
172
|
+
// This repo has an origin remote; the value is non-empty.
|
|
173
|
+
const result = remoteUrl({ cwd: REPO_ROOT });
|
|
174
|
+
expect(result.ok).toBe(true);
|
|
175
|
+
if (result.ok) expect(result.value?.length ?? 0).toBeGreaterThan(0);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("statusPorcelain returns a string (possibly empty)", () => {
|
|
179
|
+
const result = statusPorcelain({ cwd: REPO_ROOT });
|
|
180
|
+
expect(result.ok).toBe(true);
|
|
181
|
+
if (result.ok) expect(typeof result.value).toBe("string");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("diff with exit-1 tolerance does not throw for a clean path", () => {
|
|
185
|
+
// Pick a file guaranteed to exist and typically be unchanged in test runs.
|
|
186
|
+
const result = diff({ cwd: REPO_ROOT, path: "package.json" });
|
|
187
|
+
// tolerate: [1] means no diff is still ok
|
|
188
|
+
expect(result.ok).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("currentBranchOr returns a fallback when cwd is not a repo", () => {
|
|
192
|
+
expect(currentBranchOr({ cwd: require("node:os").tmpdir() }, "no-repo")).toBe("no-repo");
|
|
193
|
+
});
|
|
194
|
+
});
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for packages/shared/src/platform/npm.ts — Recipe argv + parse.
|
|
3
|
+
* Live npm calls are out of scope (npm may or may not be on PATH in CI).
|
|
4
|
+
*
|
|
5
|
+
* See change: platform-command-executor.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
8
|
+
import {
|
|
9
|
+
NPM_ROOT_GLOBAL,
|
|
10
|
+
NPM_OUTDATED,
|
|
11
|
+
NPM_OUTDATED_GLOBAL,
|
|
12
|
+
NPM_INSTALL,
|
|
13
|
+
NPM_INSTALL_GLOBAL,
|
|
14
|
+
NPM_VIEW_VERSION,
|
|
15
|
+
NPM_RECIPES,
|
|
16
|
+
_resetNpmRootCache,
|
|
17
|
+
} from "../platform/npm.js";
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
_resetNpmRootCache();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("NPM_ROOT_GLOBAL", () => {
|
|
24
|
+
it("produces `npm root -g`", () => {
|
|
25
|
+
expect(NPM_ROOT_GLOBAL.argv({})).toEqual(["npm", "root", "-g"]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("trims stdout", () => {
|
|
29
|
+
expect(NPM_ROOT_GLOBAL.parse("/usr/lib/node_modules\n", {})).toBe("/usr/lib/node_modules");
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("NPM_OUTDATED", () => {
|
|
34
|
+
it("project-wide form", () => {
|
|
35
|
+
expect(NPM_OUTDATED.argv({})).toEqual(["npm", "outdated", "--json"]);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("scoped to a single package", () => {
|
|
39
|
+
expect(NPM_OUTDATED.argv({ pkg: "pi-web-access" })).toEqual([
|
|
40
|
+
"npm", "outdated", "pi-web-access", "--json",
|
|
41
|
+
]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("parses JSON stdout", () => {
|
|
45
|
+
const json = '{"pi-flows":{"current":"1.0.0","wanted":"1.1.0"}}';
|
|
46
|
+
expect(NPM_OUTDATED.parse(json, {})).toEqual({
|
|
47
|
+
"pi-flows": { current: "1.0.0", wanted: "1.1.0" },
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("returns null for empty or malformed stdout", () => {
|
|
52
|
+
expect(NPM_OUTDATED.parse("", {})).toBeNull();
|
|
53
|
+
expect(NPM_OUTDATED.parse("not json", {})).toBeNull();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("tolerates exit code 1 (npm exits 1 when updates exist)", () => {
|
|
57
|
+
expect(NPM_OUTDATED.tolerate).toContain(1);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("NPM_OUTDATED_GLOBAL", () => {
|
|
62
|
+
it("includes `-g` flag", () => {
|
|
63
|
+
expect(NPM_OUTDATED_GLOBAL.argv({})).toEqual(["npm", "outdated", "-g", "--json"]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("scoped form", () => {
|
|
67
|
+
expect(NPM_OUTDATED_GLOBAL.argv({ pkg: "typescript" })).toEqual([
|
|
68
|
+
"npm", "outdated", "-g", "typescript", "--json",
|
|
69
|
+
]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("tolerates exit 1", () => {
|
|
73
|
+
expect(NPM_OUTDATED_GLOBAL.tolerate).toContain(1);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("NPM_INSTALL", () => {
|
|
78
|
+
it("installs latest when version omitted", () => {
|
|
79
|
+
expect(NPM_INSTALL.argv({ pkg: "pi-flows" })).toEqual(["npm", "install", "pi-flows"]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("installs pinned version", () => {
|
|
83
|
+
expect(NPM_INSTALL.argv({ pkg: "pi-flows", version: "1.2.3" })).toEqual([
|
|
84
|
+
"npm", "install", "pi-flows@1.2.3",
|
|
85
|
+
]);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("has a long timeout for install", () => {
|
|
89
|
+
expect(NPM_INSTALL.timeout).toBeGreaterThanOrEqual(60_000);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe("NPM_INSTALL_GLOBAL", () => {
|
|
94
|
+
it("includes `-g` flag with version", () => {
|
|
95
|
+
expect(NPM_INSTALL_GLOBAL.argv({ pkg: "typescript", version: "5.0.0" })).toEqual([
|
|
96
|
+
"npm", "install", "-g", "typescript@5.0.0",
|
|
97
|
+
]);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("omits version suffix when not given", () => {
|
|
101
|
+
expect(NPM_INSTALL_GLOBAL.argv({ pkg: "typescript" })).toEqual([
|
|
102
|
+
"npm", "install", "-g", "typescript",
|
|
103
|
+
]);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("NPM_VIEW_VERSION", () => {
|
|
108
|
+
it("produces `npm view <pkg> version`", () => {
|
|
109
|
+
expect(NPM_VIEW_VERSION.argv({ pkg: "@blackbelt-technology/pi-agent-dashboard" })).toEqual([
|
|
110
|
+
"npm", "view", "@blackbelt-technology/pi-agent-dashboard", "version",
|
|
111
|
+
]);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("trims the version string", () => {
|
|
115
|
+
expect(NPM_VIEW_VERSION.parse("1.2.3\n", { pkg: "x" })).toBe("1.2.3");
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("NPM_RECIPES registry", () => {
|
|
120
|
+
it("enumerates all 6 recipes", () => {
|
|
121
|
+
expect(Object.keys(NPM_RECIPES).sort()).toEqual([
|
|
122
|
+
"NPM_INSTALL",
|
|
123
|
+
"NPM_INSTALL_GLOBAL",
|
|
124
|
+
"NPM_OUTDATED",
|
|
125
|
+
"NPM_OUTDATED_GLOBAL",
|
|
126
|
+
"NPM_ROOT_GLOBAL",
|
|
127
|
+
"NPM_VIEW_VERSION",
|
|
128
|
+
]);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("every recipe has argv and parse functions", () => {
|
|
132
|
+
for (const [name, recipe] of Object.entries(NPM_RECIPES)) {
|
|
133
|
+
expect(typeof recipe.argv, `${name}.argv`).toBe("function");
|
|
134
|
+
expect(typeof recipe.parse, `${name}.parse`).toBe("function");
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for packages/shared/src/platform/openspec.ts.
|
|
3
|
+
*
|
|
4
|
+
* Pure argv + parse tests; integration with a live openspec binary is
|
|
5
|
+
* out of scope for unit tests (openspec may not be on PATH in CI).
|
|
6
|
+
*
|
|
7
|
+
* See change: platform-command-executor.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, it, expect } from "vitest";
|
|
10
|
+
import {
|
|
11
|
+
OPENSPEC_LIST,
|
|
12
|
+
OPENSPEC_STATUS,
|
|
13
|
+
OPENSPEC_ARCHIVE_COMPLETED,
|
|
14
|
+
OPENSPEC_RECIPES,
|
|
15
|
+
} from "../platform/openspec.js";
|
|
16
|
+
|
|
17
|
+
describe("OPENSPEC_LIST", () => {
|
|
18
|
+
it("produces `openspec list --json`", () => {
|
|
19
|
+
expect(OPENSPEC_LIST.argv({ cwd: "/tmp" })).toEqual(["openspec", "list", "--json"]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("parses valid JSON output", () => {
|
|
23
|
+
const input = { cwd: "/tmp" };
|
|
24
|
+
const out = OPENSPEC_LIST.parse('{"changes":[{"name":"x"}]}', input);
|
|
25
|
+
expect(out).toEqual({ changes: [{ name: "x" }] });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("returns null for empty stdout", () => {
|
|
29
|
+
expect(OPENSPEC_LIST.parse("", { cwd: "/tmp" })).toBeNull();
|
|
30
|
+
expect(OPENSPEC_LIST.parse(" \n", { cwd: "/tmp" })).toBeNull();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("returns null for malformed JSON", () => {
|
|
34
|
+
expect(OPENSPEC_LIST.parse("not json", { cwd: "/tmp" })).toBeNull();
|
|
35
|
+
expect(OPENSPEC_LIST.parse("{broken", { cwd: "/tmp" })).toBeNull();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("OPENSPEC_STATUS", () => {
|
|
40
|
+
it("produces `openspec status --change <name> --json`", () => {
|
|
41
|
+
expect(OPENSPEC_STATUS.argv({ cwd: "/tmp", change: "add-feature" })).toEqual([
|
|
42
|
+
"openspec", "status", "--change", "add-feature", "--json",
|
|
43
|
+
]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("parses status result JSON", () => {
|
|
47
|
+
const input = { cwd: "/tmp", change: "x" };
|
|
48
|
+
const out = OPENSPEC_STATUS.parse('{"artifacts":[{"id":"proposal","status":"done"}]}', input);
|
|
49
|
+
expect(out).toEqual({ artifacts: [{ id: "proposal", status: "done" }] });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("accepts change names with special characters verbatim (no shell escaping needed)", () => {
|
|
53
|
+
// argv is an array, so special chars flow through unchanged
|
|
54
|
+
expect(OPENSPEC_STATUS.argv({ cwd: "/tmp", change: "a b/c" })).toEqual([
|
|
55
|
+
"openspec", "status", "--change", "a b/c", "--json",
|
|
56
|
+
]);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("OPENSPEC_ARCHIVE_COMPLETED", () => {
|
|
61
|
+
it("produces `openspec archive --completed`", () => {
|
|
62
|
+
expect(OPENSPEC_ARCHIVE_COMPLETED.argv({ cwd: "/tmp" })).toEqual([
|
|
63
|
+
"openspec", "archive", "--completed",
|
|
64
|
+
]);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("returns stdout verbatim (no JSON parsing)", () => {
|
|
68
|
+
expect(OPENSPEC_ARCHIVE_COMPLETED.parse("Archived 3 changes\n", { cwd: "/tmp" }))
|
|
69
|
+
.toBe("Archived 3 changes\n");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("has a longer timeout than list/status (archive can be slow)", () => {
|
|
73
|
+
expect(OPENSPEC_ARCHIVE_COMPLETED.timeout).toBeGreaterThanOrEqual(15_000);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("OPENSPEC_RECIPES registry", () => {
|
|
78
|
+
it("enumerates all exported recipes", () => {
|
|
79
|
+
expect(Object.keys(OPENSPEC_RECIPES).sort()).toEqual([
|
|
80
|
+
"OPENSPEC_ARCHIVE_COMPLETED",
|
|
81
|
+
"OPENSPEC_LIST",
|
|
82
|
+
"OPENSPEC_STATUS",
|
|
83
|
+
]);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("every recipe has argv and parse functions", () => {
|
|
87
|
+
for (const [name, recipe] of Object.entries(OPENSPEC_RECIPES)) {
|
|
88
|
+
expect(typeof recipe.argv, `${name}.argv`).toBe("function");
|
|
89
|
+
expect(typeof recipe.parse, `${name}.parse`).toBe("function");
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
});
|