@ilya-lesikov/pi-pi 0.11.0 → 0.12.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/3p/pi-subagents/src/agent-manager.ts +1 -0
- package/3p/pi-subagents/src/index.ts +1 -0
- package/3p/pi-subagents/src/types.ts +8 -0
- package/extensions/orchestrator/agents/advisor.ts +2 -1
- package/extensions/orchestrator/agents/code-reviewer.ts +1 -0
- package/extensions/orchestrator/agents/constraints.test.ts +43 -10
- package/extensions/orchestrator/agents/constraints.ts +26 -5
- package/extensions/orchestrator/agents/deep-debugger.ts +7 -6
- package/extensions/orchestrator/agents/explore.ts +1 -1
- package/extensions/orchestrator/agents/librarian.ts +1 -1
- package/extensions/orchestrator/agents/planner.ts +1 -1
- package/extensions/orchestrator/agents/prompts.test.ts +135 -1
- package/extensions/orchestrator/agents/reviewer.ts +2 -2
- package/extensions/orchestrator/agents/task.ts +6 -2
- package/extensions/orchestrator/agents/tool-routing.ts +22 -6
- package/extensions/orchestrator/assumptions.test.ts +77 -0
- package/extensions/orchestrator/assumptions.ts +85 -0
- package/extensions/orchestrator/cbm.ts +4 -1
- package/extensions/orchestrator/cbm.which.test.ts +92 -0
- package/extensions/orchestrator/command-handlers.ts +9 -1
- package/extensions/orchestrator/config.test.ts +1 -3
- package/extensions/orchestrator/config.ts +3 -4
- package/extensions/orchestrator/context.test.ts +2 -7
- package/extensions/orchestrator/context.ts +3 -3
- package/extensions/orchestrator/doctor.test.ts +35 -0
- package/extensions/orchestrator/doctor.ts +4 -2
- package/extensions/orchestrator/event-handlers.more.test.ts +185 -0
- package/extensions/orchestrator/event-handlers.test.ts +22 -2
- package/extensions/orchestrator/event-handlers.ts +156 -18
- package/extensions/orchestrator/flant-infra.test.ts +0 -3
- package/extensions/orchestrator/flant-infra.ts +0 -1
- package/extensions/orchestrator/integration.test.ts +355 -173
- package/extensions/orchestrator/orchestrator.ts +3 -9
- package/extensions/orchestrator/phases/brainstorm.test.ts +25 -7
- package/extensions/orchestrator/phases/brainstorm.ts +35 -71
- package/extensions/orchestrator/phases/implementation.test.ts +22 -0
- package/extensions/orchestrator/phases/implementation.ts +6 -0
- package/extensions/orchestrator/phases/machine.test.ts +0 -28
- package/extensions/orchestrator/phases/machine.ts +0 -38
- package/extensions/orchestrator/phases/planning.test.ts +27 -1
- package/extensions/orchestrator/phases/planning.ts +1 -1
- package/extensions/orchestrator/phases/review-task.test.ts +7 -0
- package/extensions/orchestrator/phases/review-task.ts +1 -1
- package/extensions/orchestrator/phases/review.test.ts +47 -10
- package/extensions/orchestrator/phases/review.ts +36 -25
- package/extensions/orchestrator/pp-menu.from.test.ts +65 -0
- package/extensions/orchestrator/pp-menu.leaves.test.ts +17 -0
- package/extensions/orchestrator/pp-menu.test.ts +106 -49
- package/extensions/orchestrator/pp-menu.ts +279 -133
- package/extensions/orchestrator/pp-state-tools.ts +1 -1
- package/extensions/orchestrator/rate-limit-fallback-flow.test.ts +2 -2
- package/extensions/orchestrator/rate-limit-fallback.ts +1 -1
- package/extensions/orchestrator/state.test.ts +17 -17
- package/extensions/orchestrator/state.ts +35 -12
- package/package.json +2 -2
- package/scripts/postinstall.mjs +93 -0
- package/scripts/postinstall.sh +0 -18
|
@@ -138,7 +138,7 @@ function validateStateContent(taskDir: string, absolute: string, content: string
|
|
|
138
138
|
const res = validatePlan(content);
|
|
139
139
|
return res.ok
|
|
140
140
|
? { ok: true }
|
|
141
|
-
: { ok: false, errors: res.errors, hint: "Keep exactly: # Plan, ## Scope, ## Checklist (items need 'Done when:'), ## Blockers (optional)." };
|
|
141
|
+
: { ok: false, errors: res.errors, hint: "Keep exactly: # Plan, ## Scope, ## Checklist (items need 'Done when:'), ## Pattern constraints (optional), ## Blockers (optional)." };
|
|
142
142
|
}
|
|
143
143
|
return { ok: true };
|
|
144
144
|
}
|
|
@@ -30,14 +30,14 @@ import { handleMainRateLimit, handleSubagentRateLimit } from "./rate-limit-fallb
|
|
|
30
30
|
|
|
31
31
|
function makeOrchestrator() {
|
|
32
32
|
return {
|
|
33
|
-
active: { state: { phase: "
|
|
33
|
+
active: { state: { phase: "implement" } },
|
|
34
34
|
activeTaskToken: 1,
|
|
35
35
|
subFallbackActive: false,
|
|
36
36
|
subFallbackDialogPending: false,
|
|
37
37
|
subFallbackPendingDecision: false,
|
|
38
38
|
subFallbackModelId: null as string | null,
|
|
39
39
|
subSwitchBackTimer: null as any,
|
|
40
|
-
config: { agents: { orchestrators: {
|
|
40
|
+
config: { agents: { orchestrators: { implement: { thinking: "high" } } } },
|
|
41
41
|
switchModel: vi.fn().mockResolvedValue(true),
|
|
42
42
|
sendUserMessageWhenIdle: vi.fn(),
|
|
43
43
|
cancelPendingRetry: vi.fn(),
|
|
@@ -160,7 +160,7 @@ async function activateFallback(
|
|
|
160
160
|
function currentThinking(orchestrator: Orchestrator): string {
|
|
161
161
|
const phase = orchestrator.active?.state.phase;
|
|
162
162
|
const orchestrators = orchestrator.config?.agents?.orchestrators as Record<string, { thinking?: string }> | undefined;
|
|
163
|
-
const key = phase === "
|
|
163
|
+
const key = phase === "brainstorm" || phase === "review" || phase === "quick" ? phase : "implement";
|
|
164
164
|
return orchestrators?.[key]?.thinking ?? "high";
|
|
165
165
|
}
|
|
166
166
|
|
|
@@ -52,15 +52,6 @@ describe("createTask", () => {
|
|
|
52
52
|
expect(raw.endsWith("\n")).toBe(true);
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
it("uses debug as initial phase for debug", () => {
|
|
56
|
-
const cwd = makeCwd();
|
|
57
|
-
const taskDir = createTask(cwd, "debug", "Fix timeout issue");
|
|
58
|
-
const state = loadTask(taskDir);
|
|
59
|
-
expect(state.phase).toBe("debug");
|
|
60
|
-
expect(state.step).toBe("llm_work");
|
|
61
|
-
expect(state.reviewCycle).toBeNull();
|
|
62
|
-
expect(state.reviewPass).toBe(0);
|
|
63
|
-
});
|
|
64
55
|
|
|
65
56
|
it("uses brainstorm as initial phase for brainstorm", () => {
|
|
66
57
|
const cwd = makeCwd();
|
|
@@ -103,6 +94,16 @@ describe("loadTask", () => {
|
|
|
103
94
|
|
|
104
95
|
expect(() => loadTask(taskDir)).toThrowError(/Failed to parse .*state\.json:/);
|
|
105
96
|
});
|
|
97
|
+
|
|
98
|
+
it("throws for a legacy debug-phase task", () => {
|
|
99
|
+
const cwd = makeCwd();
|
|
100
|
+
const taskDir = createTask(cwd, "implement", "Legacy debug");
|
|
101
|
+
const state = loadTask(taskDir);
|
|
102
|
+
(state as any).phase = "debug";
|
|
103
|
+
saveTask(taskDir, state);
|
|
104
|
+
|
|
105
|
+
expect(() => loadTask(taskDir)).toThrowError(/Debug tasks are no longer supported/);
|
|
106
|
+
});
|
|
106
107
|
});
|
|
107
108
|
|
|
108
109
|
describe("saveTask", () => {
|
|
@@ -134,18 +135,18 @@ describe("listTasks", () => {
|
|
|
134
135
|
it("lists only non-done tasks and respects type filter", () => {
|
|
135
136
|
const cwd = makeCwd();
|
|
136
137
|
const implementTask = createTask(cwd, "implement", "Implement feature");
|
|
137
|
-
const
|
|
138
|
+
const reviewTask = createTask(cwd, "review", "Review crash");
|
|
138
139
|
const brainstormTask = createTask(cwd, "brainstorm", "Idea storm");
|
|
139
140
|
|
|
140
|
-
const doneState = loadTask(
|
|
141
|
+
const doneState = loadTask(reviewTask);
|
|
141
142
|
doneState.phase = "done";
|
|
142
|
-
saveTask(
|
|
143
|
+
saveTask(reviewTask, doneState);
|
|
143
144
|
|
|
144
145
|
const all = listTasks(cwd);
|
|
145
146
|
const allDirs = all.map((t) => t.dir);
|
|
146
147
|
expect(allDirs).toContain(implementTask);
|
|
147
148
|
expect(allDirs).toContain(brainstormTask);
|
|
148
|
-
expect(allDirs).not.toContain(
|
|
149
|
+
expect(allDirs).not.toContain(reviewTask);
|
|
149
150
|
|
|
150
151
|
const brainstormOnly = listTasks(cwd, "brainstorm");
|
|
151
152
|
expect(brainstormOnly).toHaveLength(1);
|
|
@@ -384,7 +385,7 @@ describe("getActiveTask", () => {
|
|
|
384
385
|
it("returns null when multiple unlocked tasks exist (ambiguous)", () => {
|
|
385
386
|
const cwd = makeCwd();
|
|
386
387
|
createTask(cwd, "implement", "First abandoned");
|
|
387
|
-
createTask(cwd, "
|
|
388
|
+
createTask(cwd, "review", "Second abandoned");
|
|
388
389
|
vi.spyOn(lockfile, "checkSync").mockReturnValue(false);
|
|
389
390
|
|
|
390
391
|
expect(getActiveTask(cwd)).toBeNull();
|
|
@@ -393,7 +394,7 @@ describe("getActiveTask", () => {
|
|
|
393
394
|
it("returns the single unlocked task when others are locked", () => {
|
|
394
395
|
const cwd = makeCwd();
|
|
395
396
|
const lockedTask = createTask(cwd, "implement", "Locked by other");
|
|
396
|
-
const unlockedTask = createTask(cwd, "
|
|
397
|
+
const unlockedTask = createTask(cwd, "review", "Abandoned");
|
|
397
398
|
|
|
398
399
|
vi.spyOn(lockfile, "checkSync").mockImplementation((path: string, _options?: { stale?: number }) => {
|
|
399
400
|
if (path === join(lockedTask, "state.json")) return true;
|
|
@@ -434,7 +435,7 @@ describe("getActiveTaskStatus", () => {
|
|
|
434
435
|
it("reports ambiguous with all tasks when multiple are unlocked", () => {
|
|
435
436
|
const cwd = makeCwd();
|
|
436
437
|
createTask(cwd, "implement", "First");
|
|
437
|
-
createTask(cwd, "
|
|
438
|
+
createTask(cwd, "review", "Second");
|
|
438
439
|
vi.spyOn(lockfile, "checkSync").mockReturnValue(false);
|
|
439
440
|
|
|
440
441
|
const status = getActiveTaskStatus(cwd);
|
|
@@ -447,7 +448,6 @@ describe("getFirstPhase", () => {
|
|
|
447
448
|
it("returns expected first phase for each task type", () => {
|
|
448
449
|
expect(getFirstPhase("implement")).toBe("brainstorm");
|
|
449
450
|
expect(getFirstPhase("brainstorm")).toBe("brainstorm");
|
|
450
|
-
expect(getFirstPhase("debug")).toBe("debug");
|
|
451
451
|
expect(getFirstPhase("review")).toBe("review");
|
|
452
452
|
expect(getFirstPhase("quick")).toBe("quick");
|
|
453
453
|
});
|
|
@@ -28,14 +28,13 @@ export interface AutonomousConfig {
|
|
|
28
28
|
phases: Record<string, AutonomousPhaseConfig>;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
export type TaskType = "implement" | "
|
|
31
|
+
export type TaskType = "implement" | "brainstorm" | "review" | "quick";
|
|
32
32
|
|
|
33
33
|
export type ImplementPhase = "brainstorm" | "plan" | "implement" | "done";
|
|
34
|
-
export type DebugPhase = "debug" | "plan" | "implement" | "done";
|
|
35
34
|
export type BrainstormPhase = "brainstorm" | "plan" | "implement" | "done";
|
|
36
35
|
export type ReviewPhase = "review" | "plan" | "implement" | "done";
|
|
37
36
|
export type QuickPhase = "quick";
|
|
38
|
-
export type Phase = ImplementPhase |
|
|
37
|
+
export type Phase = ImplementPhase | BrainstormPhase | ReviewPhase | QuickPhase;
|
|
39
38
|
|
|
40
39
|
export interface TaskState {
|
|
41
40
|
phase: Phase;
|
|
@@ -76,10 +75,24 @@ export interface TaskState {
|
|
|
76
75
|
// the guided/autonomous transition or the autonomous terminal handoff), so the
|
|
77
76
|
// hooks never run twice for the same completion.
|
|
78
77
|
afterImplementRan?: boolean;
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
|
|
78
|
+
// State-freshness reconcile markers (see maybePromptReconcile). Absent on legacy
|
|
79
|
+
// tasks. reconcilePending forces a re-prompt after a pause/complete that could
|
|
80
|
+
// not reconcile in-line.
|
|
81
|
+
reconcilePromptedPhase?: string;
|
|
82
|
+
reconciledPhase?: string;
|
|
83
|
+
reconcilePending?: boolean;
|
|
84
|
+
// Per-repo Plannotator review cursor. repoPaths is the ordered set of repos to
|
|
85
|
+
// review; index is read only by the single-repo path (retained for
|
|
86
|
+
// backward-compatible deserialization of older cursors). For multi-repo reviews
|
|
87
|
+
// the user drives repo selection via a status picker; `status` records each
|
|
88
|
+
// repo's outcome (absent = unreviewed) and `feedback` holds pending
|
|
89
|
+
// changes-requested feedback not yet applied. Undefined = no active loop.
|
|
90
|
+
plannotatorCursor?: {
|
|
91
|
+
repoPaths: string[];
|
|
92
|
+
index: number;
|
|
93
|
+
status?: Record<string, "approved" | "changes-requested" | "fixes-applied">;
|
|
94
|
+
feedback?: Record<string, string>;
|
|
95
|
+
};
|
|
83
96
|
// The phase the task was in when it was marked done. Recorded so the Resume
|
|
84
97
|
// "reopen a done task" flow (#2) can restore the actual last working phase
|
|
85
98
|
// (done itself carries no phase history). Absent on legacy done tasks.
|
|
@@ -102,7 +115,6 @@ function taskStatePath(taskDir: string): string {
|
|
|
102
115
|
|
|
103
116
|
export function getFirstPhase(type: TaskType): Exclude<Phase, "done"> {
|
|
104
117
|
if (type === "implement" || type === "brainstorm") return "brainstorm";
|
|
105
|
-
if (type === "debug") return "debug";
|
|
106
118
|
if (type === "review") return "review";
|
|
107
119
|
return "quick";
|
|
108
120
|
}
|
|
@@ -115,7 +127,7 @@ export function getEffectiveMode(state: TaskState): TaskMode | undefined {
|
|
|
115
127
|
// drives them, so they never inherit the task's autonomous mode even when one is set.
|
|
116
128
|
// plan/implement are autonomous-capable regardless of whether they are the task's initial phase.
|
|
117
129
|
export function getEffectivePhaseMode(state: TaskState): TaskMode {
|
|
118
|
-
if (state.phase === "brainstorm" || state.phase === "
|
|
130
|
+
if (state.phase === "brainstorm" || state.phase === "review") return "guided";
|
|
119
131
|
return getEffectiveMode(state) ?? "guided";
|
|
120
132
|
}
|
|
121
133
|
|
|
@@ -162,8 +174,19 @@ export function createTask(cwd: string, type: TaskType, description: string, mod
|
|
|
162
174
|
export function loadTask(taskDir: string): TaskState {
|
|
163
175
|
const sp = taskStatePath(taskDir);
|
|
164
176
|
const raw = readFileSync(sp, "utf-8");
|
|
177
|
+
let parsed: TaskState;
|
|
178
|
+
try {
|
|
179
|
+
parsed = JSON.parse(raw) as TaskState;
|
|
180
|
+
} catch (err: any) {
|
|
181
|
+
throw new Error(`Failed to parse ${sp}: ${err.message}`);
|
|
182
|
+
}
|
|
183
|
+
if ((parsed as any).phase === "debug") {
|
|
184
|
+
throw new Error(
|
|
185
|
+
"Debug tasks are no longer supported; use /pp → Implement → From to reuse their artifacts.",
|
|
186
|
+
);
|
|
187
|
+
}
|
|
165
188
|
try {
|
|
166
|
-
const state =
|
|
189
|
+
const state = parsed;
|
|
167
190
|
if ((state as any).phase === "planning") {
|
|
168
191
|
state.phase = "plan";
|
|
169
192
|
}
|
|
@@ -203,7 +226,7 @@ export function listTasks(cwd: string, typeOrOptions?: TaskType | ListTasksOptio
|
|
|
203
226
|
const base = stateDir(cwd);
|
|
204
227
|
if (!existsSync(base)) return [];
|
|
205
228
|
|
|
206
|
-
const types: TaskType[] = options.type ? [options.type] : ["implement", "
|
|
229
|
+
const types: TaskType[] = options.type ? [options.type] : ["implement", "brainstorm", "review", "quick"];
|
|
207
230
|
const results: TaskInfo[] = [];
|
|
208
231
|
|
|
209
232
|
for (const t of types) {
|
|
@@ -343,7 +366,7 @@ function firstMarkdownContent(path: string): string | null {
|
|
|
343
366
|
export function taskFullName(taskDir: string, state: TaskState): string {
|
|
344
367
|
let desc = state.description ?? "";
|
|
345
368
|
|
|
346
|
-
if (["implement", "
|
|
369
|
+
if (["implement", "brainstorm", "review", "quick"].includes(desc)) {
|
|
347
370
|
const fallback =
|
|
348
371
|
firstMarkdownContent(join(taskDir, "USER_REQUEST.md")) ??
|
|
349
372
|
firstMarkdownContent(join(taskDir, "RESEARCH.md")) ??
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ilya-lesikov/pi-pi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Pi-Pi Coding Agent: based on Pi Coding Agent, but twice as good",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"AGENTS.md"
|
|
34
34
|
],
|
|
35
35
|
"scripts": {
|
|
36
|
-
"postinstall": "
|
|
36
|
+
"postinstall": "node scripts/postinstall.mjs",
|
|
37
37
|
"test": "vitest run --exclude '3p/**'",
|
|
38
38
|
"test:watch": "vitest --exclude '3p/**'",
|
|
39
39
|
"test:3p": "bash scripts/test-3p.sh",
|
|
@@ -0,0 +1,93 @@
|
|
|
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.
|
|
5
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
6
|
+
import { existsSync, mkdtempSync, rmSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { gunzipSync } from "node:zlib";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { dirname, join, resolve } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
|
|
12
|
+
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const REPO_ROOT = resolve(SCRIPT_DIR, "..");
|
|
14
|
+
const PI_EXT_DIR = join(REPO_ROOT, "3p", "pi-plannotator", "apps", "pi-extension");
|
|
15
|
+
const isWindows = process.platform === "win32";
|
|
16
|
+
|
|
17
|
+
function fail(msg) {
|
|
18
|
+
console.error(` ✗ ${msg}`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function runVendor() {
|
|
23
|
+
if (isWindows) {
|
|
24
|
+
console.log(" ⚠ skipping plannotator vendor step on Windows (bash vendor.sh unavailable)");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const res = spawnSync("bash", [join(PI_EXT_DIR, "vendor.sh")], { cwd: REPO_ROOT, stdio: "inherit" });
|
|
28
|
+
if (res.status !== 0) fail(`vendor.sh failed with exit code ${res.status ?? "unknown"}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function htmlPresent() {
|
|
32
|
+
return existsSync(join(PI_EXT_DIR, "plannotator.html")) && existsSync(join(PI_EXT_DIR, "review-editor.html"));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Extract a specific file from an npm tarball (gzipped POSIX/ustar tar) using only
|
|
36
|
+
// Node builtins — no external `tar` executable, which is not guaranteed on native
|
|
37
|
+
// Windows. Returns the file's bytes, or null when the entry is absent.
|
|
38
|
+
function extractFromTar(tarBytes, wantPath) {
|
|
39
|
+
let off = 0;
|
|
40
|
+
while (off + 512 <= tarBytes.length) {
|
|
41
|
+
const header = tarBytes.subarray(off, off + 512);
|
|
42
|
+
const name = header.subarray(0, 100).toString("utf-8").replace(/\0.*$/, "");
|
|
43
|
+
if (name === "") break;
|
|
44
|
+
const size = parseInt(header.subarray(124, 136).toString("utf-8").replace(/\0.*$/, "").trim() || "0", 8);
|
|
45
|
+
const dataStart = off + 512;
|
|
46
|
+
if (name === wantPath) return tarBytes.subarray(dataStart, dataStart + size);
|
|
47
|
+
off = dataStart + Math.ceil(size / 512) * 512;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Run `npm pack` without depending on PATH/PATHEXT resolution of `npm`, which on
|
|
53
|
+
// Windows is `npm.cmd` and cannot be spawned by execFileSync without a shell
|
|
54
|
+
// (Node rejects .cmd without shell:true). In a lifecycle script npm exports
|
|
55
|
+
// npm_execpath (the CLI's JS entry); run it with the current node. Fall back to a
|
|
56
|
+
// shell-resolved `npm`/`npm.cmd` only if that env var is somehow absent.
|
|
57
|
+
function runNpmPack(destDir) {
|
|
58
|
+
const npmCli = process.env.npm_execpath;
|
|
59
|
+
if (npmCli) {
|
|
60
|
+
execFileSync(process.execPath, [npmCli, "pack", "@plannotator/pi-extension", "--pack-destination", destDir, "--silent"], {
|
|
61
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
62
|
+
});
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
66
|
+
execFileSync(npmCmd, ["pack", "@plannotator/pi-extension", "--pack-destination", destDir, "--silent"], {
|
|
67
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
68
|
+
shell: true,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function extractHtmlFallback() {
|
|
73
|
+
const tmp = mkdtempSync(join(tmpdir(), "pi-pi-plannotator-"));
|
|
74
|
+
try {
|
|
75
|
+
runNpmPack(tmp);
|
|
76
|
+
const tgz = readdirSync(tmp).find((name) => name.endsWith(".tgz"));
|
|
77
|
+
if (!tgz) fail("npm pack produced no tarball for @plannotator/pi-extension");
|
|
78
|
+
const tarBytes = gunzipSync(readFileSync(join(tmp, tgz)));
|
|
79
|
+
for (const file of ["plannotator.html", "review-editor.html"]) {
|
|
80
|
+
const bytes = extractFromTar(tarBytes, `package/${file}`);
|
|
81
|
+
if (!bytes) fail(`@plannotator/pi-extension tarball is missing ${file}`);
|
|
82
|
+
writeFileSync(join(PI_EXT_DIR, file), bytes);
|
|
83
|
+
}
|
|
84
|
+
} finally {
|
|
85
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
runVendor();
|
|
90
|
+
|
|
91
|
+
if (htmlPresent()) process.exit(0);
|
|
92
|
+
|
|
93
|
+
extractHtmlFallback();
|
package/scripts/postinstall.sh
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
cd "$(dirname "$0")/.."
|
|
4
|
-
|
|
5
|
-
bash 3p/pi-plannotator/apps/pi-extension/vendor.sh
|
|
6
|
-
|
|
7
|
-
PI_EXT_DIR="3p/pi-plannotator/apps/pi-extension"
|
|
8
|
-
if [ -f "$PI_EXT_DIR/plannotator.html" ] && [ -f "$PI_EXT_DIR/review-editor.html" ]; then
|
|
9
|
-
exit 0
|
|
10
|
-
fi
|
|
11
|
-
|
|
12
|
-
TMPDIR=$(mktemp -d)
|
|
13
|
-
trap 'rm -rf "$TMPDIR"' EXIT
|
|
14
|
-
|
|
15
|
-
npm pack @plannotator/pi-extension --pack-destination "$TMPDIR" --silent 2>/dev/null
|
|
16
|
-
tar -xzf "$TMPDIR"/*.tgz -C "$TMPDIR"
|
|
17
|
-
cp "$TMPDIR/package/plannotator.html" "$PI_EXT_DIR/plannotator.html"
|
|
18
|
-
cp "$TMPDIR/package/review-editor.html" "$PI_EXT_DIR/review-editor.html"
|