@codyswann/lisa 1.85.3 → 1.85.5
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/cdk/copy-overwrite/tsconfig.cdk.json +1 -1
- package/dist/configs/eslint/base.d.ts +0 -1
- package/dist/configs/eslint/base.d.ts.map +1 -1
- package/dist/configs/eslint/base.js +0 -1
- package/dist/configs/eslint/base.js.map +1 -1
- package/dist/configs/eslint/cdk.d.ts +0 -2
- package/dist/configs/eslint/cdk.d.ts.map +1 -1
- package/dist/configs/eslint/cdk.js +0 -2
- package/dist/configs/eslint/cdk.js.map +1 -1
- package/dist/configs/eslint/expo.d.ts.map +1 -1
- package/dist/configs/eslint/expo.js +0 -1
- package/dist/configs/eslint/expo.js.map +1 -1
- package/dist/configs/eslint/index.d.ts +0 -1
- package/dist/configs/eslint/index.d.ts.map +1 -1
- package/dist/configs/eslint/index.js +0 -1
- package/dist/configs/eslint/index.js.map +1 -1
- package/dist/configs/eslint/nestjs.d.ts +0 -2
- package/dist/configs/eslint/nestjs.d.ts.map +1 -1
- package/dist/configs/eslint/nestjs.js +0 -2
- package/dist/configs/eslint/nestjs.js.map +1 -1
- package/dist/configs/eslint/slow.d.ts +0 -1
- package/dist/configs/eslint/slow.d.ts.map +1 -1
- package/dist/configs/eslint/slow.js +0 -2
- package/dist/configs/eslint/slow.js.map +1 -1
- package/dist/configs/eslint/typescript.d.ts +0 -1
- package/dist/configs/eslint/typescript.d.ts.map +1 -1
- package/dist/configs/eslint/typescript.js +0 -2
- package/dist/configs/eslint/typescript.js.map +1 -1
- package/dist/configs/jest/base.d.ts +0 -3
- package/dist/configs/jest/base.d.ts.map +1 -1
- package/dist/configs/jest/base.js +0 -2
- package/dist/configs/jest/base.js.map +1 -1
- package/dist/configs/jest/cdk.d.ts +0 -2
- package/dist/configs/jest/cdk.d.ts.map +1 -1
- package/dist/configs/jest/cdk.js +0 -1
- package/dist/configs/jest/cdk.js.map +1 -1
- package/dist/configs/jest/expo.d.ts +0 -2
- package/dist/configs/jest/expo.d.ts.map +1 -1
- package/dist/configs/jest/expo.js +0 -1
- package/dist/configs/jest/expo.js.map +1 -1
- package/dist/configs/jest/index.d.ts +0 -1
- package/dist/configs/jest/index.d.ts.map +1 -1
- package/dist/configs/jest/index.js +0 -1
- package/dist/configs/jest/index.js.map +1 -1
- package/dist/configs/jest/nestjs.d.ts +0 -2
- package/dist/configs/jest/nestjs.d.ts.map +1 -1
- package/dist/configs/jest/nestjs.js +0 -1
- package/dist/configs/jest/nestjs.js.map +1 -1
- package/dist/configs/jest/typescript.d.ts +0 -2
- package/dist/configs/jest/typescript.d.ts.map +1 -1
- package/dist/configs/jest/typescript.js +0 -1
- package/dist/configs/jest/typescript.js.map +1 -1
- package/dist/configs/vitest/base.d.ts +0 -4
- package/dist/configs/vitest/base.d.ts.map +1 -1
- package/dist/configs/vitest/base.js +0 -3
- package/dist/configs/vitest/base.js.map +1 -1
- package/dist/configs/vitest/cdk.d.ts +0 -2
- package/dist/configs/vitest/cdk.d.ts.map +1 -1
- package/dist/configs/vitest/cdk.js +0 -1
- package/dist/configs/vitest/cdk.js.map +1 -1
- package/dist/configs/vitest/index.d.ts +0 -1
- package/dist/configs/vitest/index.d.ts.map +1 -1
- package/dist/configs/vitest/index.js +0 -1
- package/dist/configs/vitest/index.js.map +1 -1
- package/dist/configs/vitest/nestjs.d.ts +0 -2
- package/dist/configs/vitest/nestjs.d.ts.map +1 -1
- package/dist/configs/vitest/nestjs.js +0 -1
- package/dist/configs/vitest/nestjs.js.map +1 -1
- package/dist/configs/vitest/typescript.d.ts +0 -2
- package/dist/configs/vitest/typescript.d.ts.map +1 -1
- package/dist/configs/vitest/typescript.js +0 -1
- package/dist/configs/vitest/typescript.js.map +1 -1
- package/dist/core/lisa.d.ts +34 -0
- package/dist/core/lisa.d.ts.map +1 -1
- package/dist/core/lisa.js +75 -1
- package/dist/core/lisa.js.map +1 -1
- package/dist/detection/detectors/rails.d.ts.map +1 -1
- package/dist/detection/detectors/rails.js +0 -1
- package/dist/detection/detectors/rails.js.map +1 -1
- package/dist/utils/fibonacci.d.ts +0 -3
- package/dist/utils/fibonacci.d.ts.map +1 -1
- package/dist/utils/fibonacci.js +0 -3
- package/dist/utils/fibonacci.js.map +1 -1
- package/dist/utils/postinstall-trampoline.d.ts +40 -0
- package/dist/utils/postinstall-trampoline.d.ts.map +1 -1
- package/dist/utils/postinstall-trampoline.js +216 -27
- package/dist/utils/postinstall-trampoline.js.map +1 -1
- package/expo/copy-overwrite/tsconfig.expo.json +1 -1
- package/nestjs/copy-overwrite/tsconfig.nestjs.json +1 -1
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/scripts/install-claude-plugins.sh +60 -8
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Known package managers whose lockfiles must be regenerated when Lisa's apply
|
|
3
|
+
* mutates package.json (e.g., adds/updates resolutions or overrides entries).
|
|
4
|
+
*/
|
|
5
|
+
export type PackageManager = "bun" | "npm" | "pnpm" | "yarn";
|
|
6
|
+
/**
|
|
7
|
+
* Description of a package manager's lockfile file + the command Lisa should run
|
|
8
|
+
* to rebuild the lockfile without running install scripts.
|
|
9
|
+
*/
|
|
10
|
+
export interface LockfileRegenPlan {
|
|
11
|
+
readonly pm: PackageManager;
|
|
12
|
+
readonly lockfile: string;
|
|
13
|
+
readonly command: string;
|
|
14
|
+
readonly args: readonly string[];
|
|
15
|
+
}
|
|
1
16
|
/**
|
|
2
17
|
* Determine whether this Lisa invocation is running as a package-manager lifecycle
|
|
3
18
|
* script (postinstall, prepare, etc.). Works across npm, bun, yarn, and pnpm since
|
|
@@ -44,6 +59,31 @@ export declare function shouldSchedulePostinstallReconciliation(dryRun: boolean)
|
|
|
44
59
|
* @returns Absolute path to the Lisa dist directory
|
|
45
60
|
*/
|
|
46
61
|
export declare function getLisaDistDir(moduleUrl: string): string;
|
|
62
|
+
/**
|
|
63
|
+
* Detect which package managers the project uses based on lockfile presence.
|
|
64
|
+
*
|
|
65
|
+
* A project may have more than one lockfile (the CDK dual-lockfile pattern
|
|
66
|
+
* keeps `bun.lock` for local dev while publishing `package-lock.json` for
|
|
67
|
+
* consumers), in which case every present lockfile must be regenerated so both
|
|
68
|
+
* stay in sync with package.json.
|
|
69
|
+
* @param projectDir - Absolute path to the project directory
|
|
70
|
+
* @returns Ordered list of detected package managers (possibly empty)
|
|
71
|
+
*/
|
|
72
|
+
export declare function detectPackageManagers(projectDir: string): readonly PackageManager[];
|
|
73
|
+
/**
|
|
74
|
+
* Get the regen plan (command/args/lockfile) for a given package manager.
|
|
75
|
+
* @param pm - Package manager to look up
|
|
76
|
+
* @returns Regen plan describing which command to spawn
|
|
77
|
+
*/
|
|
78
|
+
export declare function getLockfileRegenPlan(pm: PackageManager): LockfileRegenPlan;
|
|
79
|
+
/**
|
|
80
|
+
* Hash a file's contents (sha256, hex-encoded). Returns null if the file
|
|
81
|
+
* does not exist or cannot be read. Used to detect whether Lisa mutated
|
|
82
|
+
* package.json during its apply so we only regenerate lockfiles when needed.
|
|
83
|
+
* @param filePath - Absolute path to the file
|
|
84
|
+
* @returns Hex-encoded sha256 hash, or null if the file is unavailable
|
|
85
|
+
*/
|
|
86
|
+
export declare function hashFile(filePath: string): string | null;
|
|
47
87
|
/**
|
|
48
88
|
* Spawn a fully detached child process that waits for the parent package manager
|
|
49
89
|
* to exit, then re-runs Lisa to reconcile package.json. The child is detached
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postinstall-trampoline.d.ts","sourceRoot":"","sources":["../../src/utils/postinstall-trampoline.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"postinstall-trampoline.d.ts","sourceRoot":"","sources":["../../src/utils/postinstall-trampoline.ts"],"names":[],"mappings":"AAmCA;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAE7D;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,cAAc,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AA6DD;;;;;GAKG;AACH,wBAAgB,0BAA0B,IAAI,OAAO,CAEpD;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,OAAO,CAE/C;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,uCAAuC,CACrD,MAAM,EAAE,OAAO,GACd,OAAO,CAIT;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAIxD;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,GACjB,SAAS,cAAc,EAAE,CAI3B;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,cAAc,GAAG,iBAAiB,CAE1E;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOxD;AAED;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAChB,IAAI,CA6BN"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
4
|
import * as path from "node:path";
|
|
3
5
|
import { fileURLToPath } from "node:url";
|
|
4
6
|
/**
|
|
@@ -25,6 +27,45 @@ const POLL_INTERVAL_MS = 100;
|
|
|
25
27
|
* writes a moment to quiesce before Lisa re-applies its changes.
|
|
26
28
|
*/
|
|
27
29
|
const SETTLE_DELAY_MS = 250;
|
|
30
|
+
/**
|
|
31
|
+
* Per-PM lockfile + regen command mapping. The regen commands are all
|
|
32
|
+
* "sync lockfile without running scripts" variants — we do NOT want to
|
|
33
|
+
* re-run lifecycle scripts (that would re-trigger the trampoline).
|
|
34
|
+
*
|
|
35
|
+
* bun: `bun install` is the canonical way to sync bun.lock after package.json
|
|
36
|
+
* changes. As of bun 1.x there is no `--lockfile-only` flag; `bun install`
|
|
37
|
+
* is already fast when node_modules is up-to-date and will simply update
|
|
38
|
+
* bun.lock to match package.json. We pass `--ignore-scripts` to avoid re-running
|
|
39
|
+
* the parent PM's lifecycle hooks (which triggered this trampoline to begin with).
|
|
40
|
+
*/
|
|
41
|
+
const INSTALL = "install";
|
|
42
|
+
const IGNORE_SCRIPTS = "--ignore-scripts";
|
|
43
|
+
const LOCKFILE_REGEN_PLANS = {
|
|
44
|
+
bun: {
|
|
45
|
+
pm: "bun",
|
|
46
|
+
lockfile: "bun.lock",
|
|
47
|
+
command: "bun",
|
|
48
|
+
args: [INSTALL, IGNORE_SCRIPTS],
|
|
49
|
+
},
|
|
50
|
+
npm: {
|
|
51
|
+
pm: "npm",
|
|
52
|
+
lockfile: "package-lock.json",
|
|
53
|
+
command: "npm",
|
|
54
|
+
args: [INSTALL, "--package-lock-only", IGNORE_SCRIPTS],
|
|
55
|
+
},
|
|
56
|
+
pnpm: {
|
|
57
|
+
pm: "pnpm",
|
|
58
|
+
lockfile: "pnpm-lock.yaml",
|
|
59
|
+
command: "pnpm",
|
|
60
|
+
args: [INSTALL, "--lockfile-only", IGNORE_SCRIPTS],
|
|
61
|
+
},
|
|
62
|
+
yarn: {
|
|
63
|
+
pm: "yarn",
|
|
64
|
+
lockfile: "yarn.lock",
|
|
65
|
+
command: "yarn",
|
|
66
|
+
args: [INSTALL, "--mode", "update-lockfile"],
|
|
67
|
+
},
|
|
68
|
+
};
|
|
28
69
|
/**
|
|
29
70
|
* Read an env var by name without widening the project-wide process.env ban.
|
|
30
71
|
* Lisa's CLI isn't a Lambda handler or Nest service; it has to introspect its
|
|
@@ -100,6 +141,45 @@ export function getLisaDistDir(moduleUrl) {
|
|
|
100
141
|
// Walk from <dist>/utils/postinstall-trampoline.js → <dist>
|
|
101
142
|
return path.resolve(path.dirname(filename), "..");
|
|
102
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Detect which package managers the project uses based on lockfile presence.
|
|
146
|
+
*
|
|
147
|
+
* A project may have more than one lockfile (the CDK dual-lockfile pattern
|
|
148
|
+
* keeps `bun.lock` for local dev while publishing `package-lock.json` for
|
|
149
|
+
* consumers), in which case every present lockfile must be regenerated so both
|
|
150
|
+
* stay in sync with package.json.
|
|
151
|
+
* @param projectDir - Absolute path to the project directory
|
|
152
|
+
* @returns Ordered list of detected package managers (possibly empty)
|
|
153
|
+
*/
|
|
154
|
+
export function detectPackageManagers(projectDir) {
|
|
155
|
+
return Object.values(LOCKFILE_REGEN_PLANS)
|
|
156
|
+
.filter(plan => existsSync(path.join(projectDir, plan.lockfile)))
|
|
157
|
+
.map(plan => plan.pm);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Get the regen plan (command/args/lockfile) for a given package manager.
|
|
161
|
+
* @param pm - Package manager to look up
|
|
162
|
+
* @returns Regen plan describing which command to spawn
|
|
163
|
+
*/
|
|
164
|
+
export function getLockfileRegenPlan(pm) {
|
|
165
|
+
return LOCKFILE_REGEN_PLANS[pm];
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Hash a file's contents (sha256, hex-encoded). Returns null if the file
|
|
169
|
+
* does not exist or cannot be read. Used to detect whether Lisa mutated
|
|
170
|
+
* package.json during its apply so we only regenerate lockfiles when needed.
|
|
171
|
+
* @param filePath - Absolute path to the file
|
|
172
|
+
* @returns Hex-encoded sha256 hash, or null if the file is unavailable
|
|
173
|
+
*/
|
|
174
|
+
export function hashFile(filePath) {
|
|
175
|
+
try {
|
|
176
|
+
const contents = readFileSync(filePath);
|
|
177
|
+
return createHash("sha256").update(contents).digest("hex");
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
103
183
|
/**
|
|
104
184
|
* Spawn a fully detached child process that waits for the parent package manager
|
|
105
185
|
* to exit, then re-runs Lisa to reconcile package.json. The child is detached
|
|
@@ -121,6 +201,7 @@ export function scheduleReconciliationChild(projectDir, lisaDistDir, parentPid)
|
|
|
121
201
|
projectDir,
|
|
122
202
|
nodeBin,
|
|
123
203
|
trampolineEnvVar: TRAMPOLINE_ENV_VAR,
|
|
204
|
+
lockfileRegenPlans: LOCKFILE_REGEN_PLANS,
|
|
124
205
|
});
|
|
125
206
|
const child = spawn(nodeBin, ["-e", trampolineSource], {
|
|
126
207
|
cwd: projectDir,
|
|
@@ -151,58 +232,166 @@ function inheritedEnv() {
|
|
|
151
232
|
* Build the inline JS source that runs inside the detached trampoline child.
|
|
152
233
|
* The source is passed to `node -e` so it must be self-contained (no imports that
|
|
153
234
|
* require resolution via package.json, which is exactly the file we're racing).
|
|
235
|
+
*
|
|
236
|
+
* The trampoline now runs in two phases inside the child:
|
|
237
|
+
* 1. Wait for the parent PM to exit, then re-invoke Lisa to reconcile package.json.
|
|
238
|
+
* 2. If Lisa's re-invocation mutated package.json, regenerate whichever lockfiles
|
|
239
|
+
* are present in the project so `bun install --frozen-lockfile` / `npm ci` in
|
|
240
|
+
* downstream CI jobs do not fail with "lockfile had changes, but lockfile is
|
|
241
|
+
* frozen." Lockfile regen runs with `--ignore-scripts` so the parent PM's
|
|
242
|
+
* lifecycle hooks are not re-invoked (which would retrigger this trampoline).
|
|
154
243
|
* @param params - Embedded constants to inline into the trampoline source
|
|
155
244
|
* @returns JS source suitable for `node -e`
|
|
156
245
|
*/
|
|
157
246
|
function buildTrampolineSource(params) {
|
|
158
247
|
// JSON.stringify gives us safe inline literals for all primitive types.
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
248
|
+
const literals = {
|
|
249
|
+
parentPid: JSON.stringify(params.parentPid),
|
|
250
|
+
pollIntervalMs: JSON.stringify(params.pollIntervalMs),
|
|
251
|
+
maxWaitMs: JSON.stringify(params.maxWaitMs),
|
|
252
|
+
settleDelayMs: JSON.stringify(params.settleDelayMs),
|
|
253
|
+
lisaEntry: JSON.stringify(params.lisaEntry),
|
|
254
|
+
projectDir: JSON.stringify(params.projectDir),
|
|
255
|
+
nodeBin: JSON.stringify(params.nodeBin),
|
|
256
|
+
trampolineEnvVar: JSON.stringify(params.trampolineEnvVar),
|
|
257
|
+
lockfilePlans: JSON.stringify(params.lockfileRegenPlans),
|
|
258
|
+
};
|
|
259
|
+
return [
|
|
260
|
+
buildTrampolinePrelude(literals),
|
|
261
|
+
buildTrampolineHelpers(literals),
|
|
262
|
+
buildTrampolineMain(literals),
|
|
263
|
+
].join("\n");
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Inline `require` prelude + the lockfile plan table. Kept separate so each
|
|
267
|
+
* chunk of the trampoline source stays under the 75-line max-lines-per-function
|
|
268
|
+
* cap enforced by eslint.
|
|
269
|
+
* @param literals - Inlined JSON-safe literals
|
|
270
|
+
* @param literals.lockfilePlans - JSON-serialized lockfile plan table
|
|
271
|
+
* @returns JS source fragment
|
|
272
|
+
*/
|
|
273
|
+
function buildTrampolinePrelude(literals) {
|
|
167
274
|
return `
|
|
168
275
|
const { spawn } = require("node:child_process");
|
|
276
|
+
const { createHash } = require("node:crypto");
|
|
277
|
+
const { existsSync, readFileSync } = require("node:fs");
|
|
278
|
+
const path = require("node:path");
|
|
169
279
|
|
|
280
|
+
const LOCKFILE_PLANS = ${literals.lockfilePlans};
|
|
281
|
+
`;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Helper functions inlined into the trampoline child: parent-liveness probe,
|
|
285
|
+
* file hasher, package-manager detector, Lisa re-invoker, and best-effort
|
|
286
|
+
* lockfile regenerator. Each mirrors an exported TS helper in this module so
|
|
287
|
+
* the logic stays test-covered via the exported versions.
|
|
288
|
+
* @param literals - Inlined JSON-safe literals
|
|
289
|
+
* @param literals.parentPid - Parent package-manager PID for liveness probe
|
|
290
|
+
* @param literals.pollIntervalMs - Poll interval for parent-liveness checks
|
|
291
|
+
* @param literals.maxWaitMs - Max wait deadline before bailing out
|
|
292
|
+
* @param literals.nodeBin - Node binary path to re-invoke Lisa with
|
|
293
|
+
* @param literals.lisaEntry - Absolute path to Lisa's dist/index.js
|
|
294
|
+
* @param literals.projectDir - Project directory Lisa will reconcile
|
|
295
|
+
* @param literals.trampolineEnvVar - Env var name used to mark child as trampoline
|
|
296
|
+
* @returns JS source fragment
|
|
297
|
+
*/
|
|
298
|
+
function buildTrampolineHelpers(literals) {
|
|
299
|
+
return `
|
|
170
300
|
function isAlive(pid) {
|
|
171
301
|
if (!pid || pid <= 1) return false;
|
|
172
302
|
try { process.kill(pid, 0); return true; } catch { return false; }
|
|
173
303
|
}
|
|
174
304
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
305
|
+
function hashFile(p) {
|
|
306
|
+
try { return createHash("sha256").update(readFileSync(p)).digest("hex"); }
|
|
307
|
+
catch { return null; }
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function detectPackageManagers(dir) {
|
|
311
|
+
return Object.values(LOCKFILE_PLANS)
|
|
312
|
+
.filter((plan) => existsSync(path.join(dir, plan.lockfile)))
|
|
313
|
+
.map((plan) => plan.pm);
|
|
314
|
+
}
|
|
315
|
+
|
|
179
316
|
async function waitForParent() {
|
|
180
|
-
const deadline = Date.now() + ${maxWaitMs};
|
|
317
|
+
const deadline = Date.now() + ${literals.maxWaitMs};
|
|
181
318
|
while (Date.now() < deadline) {
|
|
182
|
-
if (!isAlive(${parentPid})) return true;
|
|
183
|
-
await new Promise((r) => setTimeout(r, ${pollIntervalMs}));
|
|
319
|
+
if (!isAlive(${literals.parentPid})) return true;
|
|
320
|
+
await new Promise((r) => setTimeout(r, ${literals.pollIntervalMs}));
|
|
184
321
|
}
|
|
185
322
|
return false;
|
|
186
323
|
}
|
|
187
324
|
|
|
325
|
+
function spawnChild(command, args) {
|
|
326
|
+
return new Promise((resolve) => {
|
|
327
|
+
try {
|
|
328
|
+
const child = spawn(command, args, {
|
|
329
|
+
cwd: ${literals.projectDir},
|
|
330
|
+
stdio: "ignore",
|
|
331
|
+
env: Object.assign({}, process.env, { [${literals.trampolineEnvVar}]: "1" }),
|
|
332
|
+
});
|
|
333
|
+
child.on("exit", (code) => resolve(code === 0));
|
|
334
|
+
child.on("error", () => resolve(false));
|
|
335
|
+
} catch {
|
|
336
|
+
resolve(false);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function runLisa() {
|
|
342
|
+
return spawnChild(${literals.nodeBin}, [${literals.lisaEntry}, "--yes", "--skip-git-check", ${literals.projectDir}]);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function regenerateLockfiles() {
|
|
346
|
+
for (const pm of detectPackageManagers(${literals.projectDir})) {
|
|
347
|
+
const plan = LOCKFILE_PLANS[pm];
|
|
348
|
+
if (!plan) continue;
|
|
349
|
+
// Best-effort: failures are intentionally swallowed so a missing PM
|
|
350
|
+
// binary (e.g., no global bun on the PATH) does not cascade into an
|
|
351
|
+
// install failure.
|
|
352
|
+
await spawnChild(plan.command, plan.args);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
`;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Top-level async IIFE that orchestrates the trampoline child:
|
|
359
|
+
* 1) wait for parent PM to exit,
|
|
360
|
+
* 2) hash package.json,
|
|
361
|
+
* 3) re-run Lisa,
|
|
362
|
+
* 4) if Lisa changed package.json, regenerate lockfiles.
|
|
363
|
+
*
|
|
364
|
+
* Timing out MUST NOT re-run Lisa — that would reintroduce the package.json
|
|
365
|
+
* race the trampoline is designed to avoid (parent PM still writing).
|
|
366
|
+
* @param literals - Inlined JSON-safe literals
|
|
367
|
+
* @param literals.settleDelayMs - Settle delay before re-invoking Lisa
|
|
368
|
+
* @param literals.projectDir - Project directory Lisa will reconcile
|
|
369
|
+
* @returns JS source fragment
|
|
370
|
+
*/
|
|
371
|
+
function buildTrampolineMain(literals) {
|
|
372
|
+
return `
|
|
188
373
|
(async () => {
|
|
189
374
|
try {
|
|
190
375
|
const parentExited = await waitForParent();
|
|
191
376
|
if (!parentExited) {
|
|
192
|
-
// Parent still running after max wait — bail rather than race the PM.
|
|
193
377
|
process.exit(0);
|
|
194
378
|
}
|
|
195
|
-
await new Promise((r) => setTimeout(r, ${settleDelayMs}));
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
379
|
+
await new Promise((r) => setTimeout(r, ${literals.settleDelayMs}));
|
|
380
|
+
|
|
381
|
+
const pkgPath = path.join(${literals.projectDir}, "package.json");
|
|
382
|
+
const preHash = hashFile(pkgPath);
|
|
383
|
+
|
|
384
|
+
const lisaOk = await runLisa();
|
|
385
|
+
|
|
386
|
+
const postHash = hashFile(pkgPath);
|
|
387
|
+
const packageJsonChanged =
|
|
388
|
+
lisaOk && preHash !== null && postHash !== null && preHash !== postHash;
|
|
389
|
+
|
|
390
|
+
if (packageJsonChanged) {
|
|
391
|
+
await regenerateLockfiles();
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
process.exit(0);
|
|
206
395
|
} catch {
|
|
207
396
|
process.exit(0);
|
|
208
397
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postinstall-trampoline.js","sourceRoot":"","sources":["../../src/utils/postinstall-trampoline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC;;;GAGG;AACH,MAAM,iBAAiB,GAAG,kBAAkB,CAAC;AAE7C;;;GAGG;AACH,MAAM,kBAAkB,GAAG,6BAA6B,CAAC;AAEzD;;;GAGG;AACH,MAAM,WAAW,GAAG,OAAO,CAAC;AAE5B;;GAEG;AACH,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAE7B;;;GAGG;AACH,MAAM,eAAe,GAAG,GAAG,CAAC;
|
|
1
|
+
{"version":3,"file":"postinstall-trampoline.js","sourceRoot":"","sources":["../../src/utils/postinstall-trampoline.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"lodash": ">=4.18.1"
|
|
79
79
|
},
|
|
80
80
|
"name": "@codyswann/lisa",
|
|
81
|
-
"version": "1.85.
|
|
81
|
+
"version": "1.85.5",
|
|
82
82
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
83
83
|
"main": "dist/index.js",
|
|
84
84
|
"exports": {
|
|
@@ -24,24 +24,76 @@ cd "$PROJECT_ROOT"
|
|
|
24
24
|
# explicit `lisa:update` (npx @codyswann/lisa@latest .) or the project's own
|
|
25
25
|
# postinstall script (defaults.scripts.postinstall in package.lisa.json).
|
|
26
26
|
|
|
27
|
-
# Strip
|
|
28
|
-
# (hooks moved to plugin.json;
|
|
27
|
+
# Strip only hook entries that reference deleted .claude/hooks/*.sh scripts
|
|
28
|
+
# (hooks moved to plugin.json; file-path hooks would produce "No such file or directory" errors).
|
|
29
|
+
# Preserve inline command hooks (e.g. `command -v entire ...`, `echo ...`) and stack-template hooks
|
|
30
|
+
# from rails/merge/.claude/settings.json.
|
|
29
31
|
SETTINGS_FILE="$PROJECT_ROOT/.claude/settings.json"
|
|
30
|
-
HOOKS_DIR="$PROJECT_ROOT/.claude/hooks"
|
|
31
32
|
if [ -f "$SETTINGS_FILE" ] && command -v python3 >/dev/null 2>&1; then
|
|
32
|
-
|
|
33
|
-
python3 - "$SETTINGS_FILE" <<'PYEOF'
|
|
33
|
+
python3 - "$SETTINGS_FILE" <<'PYEOF'
|
|
34
34
|
import json, sys
|
|
35
35
|
path = sys.argv[1]
|
|
36
36
|
with open(path) as f:
|
|
37
37
|
d = json.load(f)
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
|
|
39
|
+
hooks = d.get("hooks")
|
|
40
|
+
if not isinstance(hooks, dict):
|
|
41
|
+
sys.exit(0)
|
|
42
|
+
|
|
43
|
+
def is_stale(entry):
|
|
44
|
+
# Stale = hook entry whose command references the deleted .claude/hooks/ dir.
|
|
45
|
+
if not isinstance(entry, dict):
|
|
46
|
+
return False
|
|
47
|
+
cmd = entry.get("command", "")
|
|
48
|
+
return isinstance(cmd, str) and "$CLAUDE_PROJECT_DIR/.claude/hooks/" in cmd
|
|
49
|
+
|
|
50
|
+
changed = False
|
|
51
|
+
new_hooks = {}
|
|
52
|
+
for category, matchers in hooks.items():
|
|
53
|
+
if not isinstance(matchers, list):
|
|
54
|
+
new_hooks[category] = matchers
|
|
55
|
+
continue
|
|
56
|
+
new_matchers = []
|
|
57
|
+
for matcher in matchers:
|
|
58
|
+
if not isinstance(matcher, dict):
|
|
59
|
+
new_matchers.append(matcher)
|
|
60
|
+
continue
|
|
61
|
+
if "hooks" not in matcher:
|
|
62
|
+
new_matchers.append(matcher)
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
entries = matcher.get("hooks")
|
|
66
|
+
if isinstance(entries, list):
|
|
67
|
+
kept = [e for e in entries if not is_stale(e)]
|
|
68
|
+
if len(kept) != len(entries):
|
|
69
|
+
changed = True
|
|
70
|
+
if kept:
|
|
71
|
+
new_matcher = dict(matcher)
|
|
72
|
+
new_matcher["hooks"] = kept
|
|
73
|
+
new_matchers.append(new_matcher)
|
|
74
|
+
elif entries:
|
|
75
|
+
# drop matcher only when pruning emptied a previously non-empty hooks list
|
|
76
|
+
changed = True
|
|
77
|
+
else:
|
|
78
|
+
# preserve pre-existing empty matcher blocks unchanged
|
|
79
|
+
new_matchers.append(matcher)
|
|
80
|
+
else:
|
|
81
|
+
new_matchers.append(matcher)
|
|
82
|
+
if new_matchers:
|
|
83
|
+
new_hooks[category] = new_matchers
|
|
84
|
+
else:
|
|
85
|
+
# drop empty category
|
|
86
|
+
changed = True
|
|
87
|
+
|
|
88
|
+
if changed:
|
|
89
|
+
if new_hooks:
|
|
90
|
+
d["hooks"] = new_hooks
|
|
91
|
+
else:
|
|
92
|
+
del d["hooks"]
|
|
40
93
|
with open(path, "w") as f:
|
|
41
94
|
json.dump(d, f, indent=2)
|
|
42
95
|
f.write("\n")
|
|
43
96
|
PYEOF
|
|
44
|
-
fi
|
|
45
97
|
fi
|
|
46
98
|
|
|
47
99
|
# Install plugins only when claude CLI is available
|