@ilya-lesikov/pi-pi 0.12.0 → 0.12.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ilya-lesikov/pi-pi",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "description": "Pi-Pi Coding Agent: based on Pi Coding Agent, but twice as good",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1,13 +1,14 @@
1
1
  // Vendor-source decision: on non-Windows we invoke the existing bash `vendor.sh` UNCHANGED so
2
- // Linux/CI stay byte-for-byte identical; on Windows we SKIP the bash vendor step (a documented
3
- // Windows limitation) and rely on the npm-pack HTML fallback below. vendor.sh is still the single
4
- // source used by `npm run build`/CI this script does not fork its logic.
2
+ // Linux/CI stay byte-for-byte identical. On Windows bash is unavailable, so we reproduce
3
+ // vendor.sh's logic in Node (`vendorGeneratedNode`) from the same pinned sources shipped in this
4
+ // package's tarball, then recover the prebuilt HTML via the npm-pack fallback below. vendor.sh
5
+ // remains the single source used by `npm run build`/CI; the Node port must mirror it exactly.
5
6
  import { execFileSync, spawnSync } from "node:child_process";
6
- import { existsSync, mkdtempSync, rmSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
7
+ import { existsSync, mkdirSync, mkdtempSync, rmSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
7
8
  import { gunzipSync } from "node:zlib";
8
9
  import { tmpdir } from "node:os";
9
10
  import { dirname, join, resolve } from "node:path";
10
- import { fileURLToPath } from "node:url";
11
+ import { fileURLToPath, pathToFileURL } from "node:url";
11
12
 
12
13
  const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
13
14
  const REPO_ROOT = resolve(SCRIPT_DIR, "..");
@@ -21,13 +22,67 @@ function fail(msg) {
21
22
 
22
23
  function runVendor() {
23
24
  if (isWindows) {
24
- console.log(" ⚠ skipping plannotator vendor step on Windows (bash vendor.sh unavailable)");
25
+ console.log(" ⚠ bash vendor.sh unavailable on Windows vendoring generated/ with Node fallback");
26
+ vendorGeneratedNode();
25
27
  return;
26
28
  }
27
29
  const res = spawnSync("bash", [join(PI_EXT_DIR, "vendor.sh")], { cwd: REPO_ROOT, stdio: "inherit" });
28
30
  if (res.status !== 0) fail(`vendor.sh failed with exit code ${res.status ?? "unknown"}`);
29
31
  }
30
32
 
33
+ // Node port of vendor.sh: prepend the @generated header to each pinned source and rewrite the
34
+ // server/tour imports to the flat generated/ layout. MUST stay in lockstep with vendor.sh —
35
+ // same file lists, same header, same import rewrites — or Windows ships a divergent extension.
36
+ export function vendorGeneratedNode(genDir = join(PI_EXT_DIR, "generated")) {
37
+ const PKG_ROOT = join(REPO_ROOT, "3p", "pi-plannotator", "packages");
38
+ const GEN = genDir;
39
+
40
+ const SHARED = [
41
+ "feedback-templates", "prompts", "review-core", "diff-paths", "jj-core", "vcs-core", "review-args",
42
+ "storage", "draft", "project", "pr-types", "pr-provider", "pr-stack", "pr-github", "pr-gitlab",
43
+ "checklist", "integrations-common", "repo", "reference-common", "favicon", "code-file",
44
+ "resolve-file", "config", "external-annotation", "agent-jobs", "worktree", "worktree-pool",
45
+ "html-to-markdown", "url-to-markdown", "tour", "annotate-args", "at-reference",
46
+ "review-workspace-node", "review-workspace", "pfm-reminder", "improvement-hooks", "code-nav",
47
+ "data-dir", "semantic-diff-types", "semantic-diff",
48
+ ];
49
+ const SERVER = ["agent-review-message", "codex-review", "claude-review", "path-utils"];
50
+ const AI = ["index", "types", "provider", "session-manager", "endpoints", "context", "base-session"];
51
+ const PROVIDERS = ["claude-agent-sdk", "codex-sdk", "opencode-sdk", "command-path", "pi-sdk", "pi-sdk-node", "pi-events"];
52
+
53
+ const SERVER_REWRITES = [
54
+ ['from "./vcs"', 'from "./review-core.js"'],
55
+ ['from "./pr"', 'from "./pr-provider.js"'],
56
+ ['from "./path-utils"', 'from "./path-utils.js"'],
57
+ ['from "@plannotator/shared/review-workspace"', 'from "./review-workspace.js"'],
58
+ ['from "@plannotator/shared/data-dir"', 'from "./data-dir"'],
59
+ ];
60
+ const TOUR_REWRITES = [
61
+ ['from "../vcs"', 'from "./review-core.js"'],
62
+ ['from "../pr"', 'from "./pr-provider.js"'],
63
+ ['from "../agent-review-message"', 'from "./agent-review-message.js"'],
64
+ ['from "@plannotator/shared/tour"', 'from "./tour.js"'],
65
+ ['from "@plannotator/shared/data-dir"', 'from "./data-dir"'],
66
+ ];
67
+
68
+ const header = (rel) => `// @generated — DO NOT EDIT. Source: ${rel}\n`;
69
+ const apply = (src, rewrites) => rewrites.reduce((acc, [from, to]) => acc.split(from).join(to), src);
70
+ const emit = (srcAbs, rel, outAbs, rewrites) => {
71
+ if (!existsSync(srcAbs)) fail(`vendor source missing: ${rel}`);
72
+ const body = readFileSync(srcAbs, "utf8");
73
+ writeFileSync(outAbs, header(rel) + (rewrites ? apply(body, rewrites) : body));
74
+ };
75
+
76
+ rmSync(GEN, { recursive: true, force: true });
77
+ mkdirSync(join(GEN, "ai", "providers"), { recursive: true });
78
+
79
+ for (const f of SHARED) emit(join(PKG_ROOT, "shared", `${f}.ts`), `packages/shared/${f}.ts`, join(GEN, `${f}.ts`));
80
+ for (const f of SERVER) emit(join(PKG_ROOT, "server", `${f}.ts`), `packages/server/${f}.ts`, join(GEN, `${f}.ts`), SERVER_REWRITES);
81
+ emit(join(PKG_ROOT, "server", "tour", "tour-review.ts"), "packages/server/tour/tour-review.ts", join(GEN, "tour-review.ts"), TOUR_REWRITES);
82
+ for (const f of AI) emit(join(PKG_ROOT, "ai", `${f}.ts`), `packages/ai/${f}.ts`, join(GEN, "ai", `${f}.ts`));
83
+ for (const f of PROVIDERS) emit(join(PKG_ROOT, "ai", "providers", `${f}.ts`), `packages/ai/providers/${f}.ts`, join(GEN, "ai", "providers", `${f}.ts`));
84
+ }
85
+
31
86
  function htmlPresent() {
32
87
  return existsSync(join(PI_EXT_DIR, "plannotator.html")) && existsSync(join(PI_EXT_DIR, "review-editor.html"));
33
88
  }
@@ -86,8 +141,8 @@ function extractHtmlFallback() {
86
141
  }
87
142
  }
88
143
 
89
- runVendor();
90
-
91
- if (htmlPresent()) process.exit(0);
92
-
93
- extractHtmlFallback();
144
+ // Skip the install-time side effects when imported by a test; only run as the lifecycle script.
145
+ if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
146
+ runVendor();
147
+ if (!htmlPresent()) extractHtmlFallback();
148
+ }
@@ -0,0 +1,60 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { mkdtempSync, readdirSync, readFileSync, rmSync, statSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { dirname, join, relative, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { describe, expect, it } from "vitest";
7
+ import { vendorGeneratedNode } from "./postinstall.mjs";
8
+
9
+ const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
10
+ const EXT_DIR = join(REPO_ROOT, "3p", "pi-plannotator", "apps", "pi-extension");
11
+
12
+ function listFiles(root: string, dir: string = root): string[] {
13
+ const out: string[] = [];
14
+ for (const name of readdirSync(dir)) {
15
+ const full = join(dir, name);
16
+ if (statSync(full).isDirectory()) out.push(...listFiles(root, full));
17
+ else out.push(relative(root, full));
18
+ }
19
+ return out.sort();
20
+ }
21
+
22
+ // The Node port in postinstall.mjs reproduces vendor.sh for the Windows install path, where bash
23
+ // is unavailable. If the two ever diverge (a file added to vendor.sh's lists, a changed header or
24
+ // import rewrite), Windows silently ships a different extension than Linux/CI. This asserts they
25
+ // produce byte-for-byte identical generated/ trees. Bash-only, so skipped where it is absent.
26
+ describe("vendorGeneratedNode mirrors vendor.sh", () => {
27
+ const hasBash = (() => {
28
+ try {
29
+ execFileSync("bash", ["--version"], { stdio: "ignore" });
30
+ return true;
31
+ } catch {
32
+ return false;
33
+ }
34
+ })();
35
+
36
+ it.skipIf(!hasBash)("produces a byte-for-byte identical generated/ tree", () => {
37
+ const tmp = mkdtempSync(join(tmpdir(), "vendor-parity-"));
38
+ try {
39
+ // vendor.sh writes generated/ next to itself (gitignored, reproducible); snapshot it.
40
+ execFileSync("bash", ["vendor.sh"], { cwd: EXT_DIR, stdio: "ignore" });
41
+ const bashDir = join(EXT_DIR, "generated");
42
+
43
+ const nodeDir = join(tmp, "generated");
44
+ vendorGeneratedNode(nodeDir);
45
+
46
+ const bashFiles = listFiles(bashDir);
47
+ const nodeFiles = listFiles(nodeDir);
48
+ expect(bashFiles.length).toBeGreaterThan(0);
49
+ expect(nodeFiles).toEqual(bashFiles);
50
+
51
+ for (const rel of bashFiles) {
52
+ expect(readFileSync(join(nodeDir, rel), "utf8"), `content mismatch: ${rel}`).toBe(
53
+ readFileSync(join(bashDir, rel), "utf8"),
54
+ );
55
+ }
56
+ } finally {
57
+ rmSync(tmp, { recursive: true, force: true });
58
+ }
59
+ });
60
+ });