@vzlentin/harness 0.2.1 → 0.2.3
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/README.md +2 -2
- package/dist/setup.js +46 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -57,6 +57,6 @@ XDG_STATE_HOME=/tmp/harness-dev node src/cli.ts list
|
|
|
57
57
|
`node src/cli.ts` runs the CLI of this checkout. Give it its own `XDG_STATE_HOME`, so its hosts do not share sockets and
|
|
58
58
|
stores with the hosts of the installed `harness`.
|
|
59
59
|
|
|
60
|
-
The checkout runs from its TypeScript source.
|
|
61
|
-
|
|
60
|
+
The checkout runs from its TypeScript source. The package contains `dist/`, the Pi extension in `pi/` and its
|
|
61
|
+
`postinstall` script. Change `version` in `package.json` and push to `main`: CI publishes it. `npm run check` builds `dist/` too, before the type check. The extension imports `../dist/`, so to try the checkout's
|
|
62
62
|
extension in Pi, run `npm run build`, then `pi install ./pi`, and build again after each change in `src/`.
|
package/dist/setup.js
CHANGED
|
@@ -6,10 +6,16 @@ import { spawnSync } from "node:child_process";
|
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
7
|
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
|
-
import { basename, delimiter, dirname, join, sep } from "node:path";
|
|
9
|
+
import { basename, delimiter, dirname, join, resolve, sep } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
10
11
|
import { Type } from "typebox";
|
|
11
12
|
import { Value } from "typebox/value";
|
|
12
13
|
const PackageJson = Type.Object({ name: Type.String(), version: Type.String() });
|
|
14
|
+
const PiSettings = Type.Object({
|
|
15
|
+
packages: Type.Optional(Type.Array(Type.Union([Type.String(), Type.Object({ source: Type.String() })]))),
|
|
16
|
+
});
|
|
17
|
+
/** The prefixes of the Pi package sources that are not local paths, as Pi reads them. */
|
|
18
|
+
const REMOTE = ["npm:", "git:", "github:", "http:", "https:", "ssh:", "builtin:"];
|
|
13
19
|
/**
|
|
14
20
|
* Registers `<packageDir>/pi` in Pi when `packageDir` is a top-level global npm install. It writes its messages to
|
|
15
21
|
* stderr, and it does not throw for a Pi failure: it writes a warning with the command to run. It writes the stamp
|
|
@@ -68,10 +74,15 @@ function writeStamp(installed) {
|
|
|
68
74
|
mkdirSync(dirname(path), { recursive: true });
|
|
69
75
|
writeFileSync(path, `${installed.dir}\n${installed.json.version}\n`);
|
|
70
76
|
}
|
|
71
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* False when `pi install` failed. It runs no `pi` when Pi already lists the extension, as `pi install` would write the
|
|
79
|
+
* entry again in its own form, relative to the agent directory.
|
|
80
|
+
*/
|
|
72
81
|
function registerExtension({ dir, json }) {
|
|
73
82
|
const label = basename(json.name);
|
|
74
83
|
const extension = join(dir, "pi");
|
|
84
|
+
if (piLists(extension))
|
|
85
|
+
return true;
|
|
75
86
|
return runPi(label, ["install", extension], `To add the Pi extension of ${label}, run: pi install ${extension}`);
|
|
76
87
|
}
|
|
77
88
|
/** Runs `pi`, with a warning when it is missing or fails. False when it failed; true when it worked or is missing. */
|
|
@@ -92,6 +103,39 @@ function runPi(label, args, later) {
|
|
|
92
103
|
}
|
|
93
104
|
return true;
|
|
94
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Whether the `packages` of Pi's global settings list the folder `extension` as a local path, absolute or relative to
|
|
108
|
+
* the agent directory, with `~` or as a `file:` URL, as Pi resolves them.
|
|
109
|
+
*/
|
|
110
|
+
function piLists(extension) {
|
|
111
|
+
const env = process.env.PI_CODING_AGENT_DIR;
|
|
112
|
+
const agentDir = env ? expandHome(env) : join(homedir(), ".pi", "agent");
|
|
113
|
+
let settings;
|
|
114
|
+
try {
|
|
115
|
+
settings = JSON.parse(readFileSync(join(agentDir, "settings.json"), "utf8"));
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
if (!Value.Check(PiSettings, settings))
|
|
121
|
+
return false;
|
|
122
|
+
return (settings.packages ?? []).some((item) => {
|
|
123
|
+
const source = (Value.Check(Type.String(), item) ? item : item.source).trim();
|
|
124
|
+
if (REMOTE.some((prefix) => source.startsWith(prefix)))
|
|
125
|
+
return false;
|
|
126
|
+
const path = source.startsWith("file://") ? fileURLToPath(source) : resolve(agentDir, expandHome(source));
|
|
127
|
+
try {
|
|
128
|
+
return realpathSync(path) === extension;
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/** `path` with a leading `~` replaced by the home directory. */
|
|
136
|
+
function expandHome(path) {
|
|
137
|
+
return path === "~" || path.startsWith("~/") ? join(homedir(), path.slice(1)) : path;
|
|
138
|
+
}
|
|
95
139
|
/** The `PATH` without the `node_modules/.bin` folders that npm adds: pi-coding-agent there has its own `pi`. */
|
|
96
140
|
function userPath() {
|
|
97
141
|
return (process.env.PATH ?? "")
|