codiedev 0.9.64 → 0.9.66
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/dist/captureLog.d.ts +1 -1
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +14 -35
- package/dist/commands/doctor.js +0 -34
- package/dist/hook.js +16 -1
- package/dist/launcher.d.ts +20 -0
- package/dist/launcher.js +167 -0
- package/dist/mcp.js +0 -337
- package/dist/selfUpdate.d.ts +49 -0
- package/dist/selfUpdate.js +390 -0
- package/dist/utils.d.ts +4 -4
- package/dist/utils.js +84 -8
- package/package.json +1 -1
- package/dist/commands/promote.d.ts +0 -1
- package/dist/commands/promote.js +0 -53
- package/dist/commands/react.d.ts +0 -2
- package/dist/commands/react.js +0 -40
- package/dist/commands/reverseTicket.d.ts +0 -2
- package/dist/commands/reverseTicket.js +0 -477
- package/dist/prUrl.d.ts +0 -9
- package/dist/prUrl.js +0 -33
package/dist/captureLog.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as fs from "fs";
|
|
|
2
2
|
export declare const CAPTURE_LOG_MAX_BYTES: number;
|
|
3
3
|
export declare const CAPTURE_LOG_PATH: string;
|
|
4
4
|
export interface CaptureLogEntry {
|
|
5
|
-
stage: "capture-finish" | "upload" | "upload-deferred" | "stderr" | "limit";
|
|
5
|
+
stage: "capture-finish" | "upload" | "upload-deferred" | "stderr" | "limit" | "self-update";
|
|
6
6
|
sessionId: string | undefined;
|
|
7
7
|
outcome: string;
|
|
8
8
|
detail?: string;
|
package/dist/cli.d.ts
CHANGED
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
* codiedev inbox — list pings (use --unread to filter)
|
|
11
11
|
* codiedev read <ping-id> — mark read + show full message
|
|
12
12
|
* codiedev note "<text>" — capture a thought
|
|
13
|
-
* codiedev promote <artifact-id> — promote an extracted artifact to authored
|
|
14
13
|
* codiedev help — show this help
|
|
15
14
|
*/
|
|
16
15
|
export {};
|
package/dist/cli.js
CHANGED
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
* codiedev inbox — list pings (use --unread to filter)
|
|
12
12
|
* codiedev read <ping-id> — mark read + show full message
|
|
13
13
|
* codiedev note "<text>" — capture a thought
|
|
14
|
-
* codiedev promote <artifact-id> — promote an extracted artifact to authored
|
|
15
14
|
* codiedev help — show this help
|
|
16
15
|
*/
|
|
17
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -23,15 +22,12 @@ const ask_1 = require("./commands/ask");
|
|
|
23
22
|
const ping_1 = require("./commands/ping");
|
|
24
23
|
const inbox_1 = require("./commands/inbox");
|
|
25
24
|
const note_1 = require("./commands/note");
|
|
26
|
-
const promote_1 = require("./commands/promote");
|
|
27
|
-
const reverseTicket_1 = require("./commands/reverseTicket");
|
|
28
25
|
const createTicket_1 = require("./commands/createTicket");
|
|
29
26
|
const doctor_1 = require("./commands/doctor");
|
|
30
27
|
const search_1 = require("./commands/search");
|
|
31
28
|
const library_1 = require("./commands/library");
|
|
32
29
|
const share_1 = require("./commands/share");
|
|
33
30
|
const send_1 = require("./commands/send");
|
|
34
|
-
const react_1 = require("./commands/react");
|
|
35
31
|
const capture_1 = require("./commands/capture");
|
|
36
32
|
const version_1 = require("./version");
|
|
37
33
|
const HELP = `
|
|
@@ -64,14 +60,6 @@ Artifacts:
|
|
|
64
60
|
team's collective memory. Returns an
|
|
65
61
|
LLM-synthesized answer with citations
|
|
66
62
|
pointing at sessions + artifacts.
|
|
67
|
-
codiedev promote <artifact-id> Promote an auto-extracted artifact
|
|
68
|
-
to an authored one
|
|
69
|
-
codiedev reverse-ticket [<pr-url>] Generate a Jira ticket draft.
|
|
70
|
-
No URL = branch mode (uses your current
|
|
71
|
-
local git branch, no PR needed).
|
|
72
|
-
Draft lands in /portal/agentic-ticketing.
|
|
73
|
-
codiedev reverse-ticket --base <ref> Override the base ref in branch mode
|
|
74
|
-
(defaults to origin/main).
|
|
75
63
|
codiedev create-ticket [<prompt>] Forward-write a scoped ticket plan.
|
|
76
64
|
Claude investigates the codebase, asks
|
|
77
65
|
"single repo or multiple? where?", then
|
|
@@ -97,9 +85,6 @@ Discovery:
|
|
|
97
85
|
[--type T] [--tag X] [--limit N]
|
|
98
86
|
List artifacts in your workspace
|
|
99
87
|
|
|
100
|
-
Feed:
|
|
101
|
-
codiedev react <postId> <emoji> Toggle a reaction on a feed post
|
|
102
|
-
|
|
103
88
|
Privacy:
|
|
104
89
|
codiedev ignore <path> Never capture this folder or its children
|
|
105
90
|
codiedev unignore <path> Stop ignoring a folder
|
|
@@ -270,20 +255,6 @@ codiedev note — capture a passing thought, linked to your session
|
|
|
270
255
|
unignore: IGNORE_HELP,
|
|
271
256
|
ignored: IGNORE_HELP,
|
|
272
257
|
capture: IGNORE_HELP,
|
|
273
|
-
promote: `
|
|
274
|
-
codiedev promote — promote an auto-extracted artifact to authored
|
|
275
|
-
|
|
276
|
-
Usage:
|
|
277
|
-
codiedev promote <artifact-id> [--key filename.md]
|
|
278
|
-
|
|
279
|
-
The session-capture pipeline auto-extracts spec/bugfix/decision
|
|
280
|
-
artifacts from each agent session. Promote turns one of those into
|
|
281
|
-
a first-class authored artifact your team can ping, pull, and
|
|
282
|
-
update versions of.
|
|
283
|
-
|
|
284
|
-
Find artifact-ids in the portal under Knowledge → Memory, or via
|
|
285
|
-
the portal search.
|
|
286
|
-
`.trim(),
|
|
287
258
|
"create-ticket": `
|
|
288
259
|
codiedev create-ticket — forward-write a scoped ticket plan
|
|
289
260
|
|
|
@@ -409,6 +380,18 @@ Run 'codiedev help <command>' for each, or see the quick list:
|
|
|
409
380
|
|
|
410
381
|
Tokens are per-user. Rotate any time by revoking + creating a new one.
|
|
411
382
|
`.trim();
|
|
383
|
+
// Retired commands: say so instead of "Unknown command", since agent
|
|
384
|
+
// instructions written by older installs may still reference them.
|
|
385
|
+
const REMOVED_COMMANDS = {
|
|
386
|
+
promote: "Share an auto-saved spec with 'Promote to team' in the portal's My artifacts list.",
|
|
387
|
+
"reverse-ticket": "Use 'codiedev create-ticket' to draft Jira tickets.",
|
|
388
|
+
reverseTicket: "Use 'codiedev create-ticket' to draft Jira tickets.",
|
|
389
|
+
react: "React to feed posts in the portal.",
|
|
390
|
+
};
|
|
391
|
+
function removedCommand(command) {
|
|
392
|
+
console.error(`'codiedev ${command}' has been removed. ${REMOVED_COMMANDS[command] ?? ""}`.trim());
|
|
393
|
+
process.exit(1);
|
|
394
|
+
}
|
|
412
395
|
async function main() {
|
|
413
396
|
const [, , command, ...rest] = process.argv;
|
|
414
397
|
if (!command || command === "--help" || command === "-h") {
|
|
@@ -486,11 +469,10 @@ async function main() {
|
|
|
486
469
|
await (0, note_1.runNote)(rest);
|
|
487
470
|
return;
|
|
488
471
|
case "promote":
|
|
489
|
-
await (0, promote_1.runPromote)(rest);
|
|
490
|
-
return;
|
|
491
472
|
case "reverse-ticket":
|
|
492
473
|
case "reverseTicket":
|
|
493
|
-
|
|
474
|
+
case "react":
|
|
475
|
+
removedCommand(command);
|
|
494
476
|
return;
|
|
495
477
|
case "create-ticket":
|
|
496
478
|
case "createTicket":
|
|
@@ -509,9 +491,6 @@ async function main() {
|
|
|
509
491
|
case "send":
|
|
510
492
|
await (0, send_1.runSend)(rest);
|
|
511
493
|
return;
|
|
512
|
-
case "react":
|
|
513
|
-
await (0, react_1.runReact)(rest);
|
|
514
|
-
return;
|
|
515
494
|
case "ignore":
|
|
516
495
|
case "unignore":
|
|
517
496
|
case "ignored":
|
package/dist/commands/doctor.js
CHANGED
|
@@ -132,24 +132,6 @@ function hasCodiedevInstructions(instructionsPath) {
|
|
|
132
132
|
return false;
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
|
-
function hasGhCli() {
|
|
136
|
-
try {
|
|
137
|
-
(0, child_process_1.execSync)("gh --version", { stdio: ["pipe", "pipe", "pipe"] });
|
|
138
|
-
return true;
|
|
139
|
-
}
|
|
140
|
-
catch {
|
|
141
|
-
return false;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
function hasGlabCli() {
|
|
145
|
-
try {
|
|
146
|
-
(0, child_process_1.execSync)("glab --version", { stdio: ["pipe", "pipe", "pipe"] });
|
|
147
|
-
return true;
|
|
148
|
-
}
|
|
149
|
-
catch {
|
|
150
|
-
return false;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
135
|
async function runDoctor(_args) {
|
|
154
136
|
const checks = [];
|
|
155
137
|
console.log("\nCodiedev doctor — checking your setup…\n");
|
|
@@ -514,22 +496,6 @@ async function runDoctor(_args) {
|
|
|
514
496
|
else {
|
|
515
497
|
checks.push({ name: "GitHub Copilot", status: "warn", detail: "not installed on this machine" });
|
|
516
498
|
}
|
|
517
|
-
// 8. gh / glab CLIs (optional — only needed for reverse-ticket from a
|
|
518
|
-
// specific PR/MR URL; branch mode works without either).
|
|
519
|
-
checks.push({
|
|
520
|
-
name: "GitHub CLI (`gh`) for reverse-ticket from a PR url",
|
|
521
|
-
status: hasGhCli() ? "pass" : "warn",
|
|
522
|
-
detail: hasGhCli()
|
|
523
|
-
? "installed"
|
|
524
|
-
: "not installed — only needed for `codiedev reverse-ticket <github-pr-url>`",
|
|
525
|
-
});
|
|
526
|
-
checks.push({
|
|
527
|
-
name: "GitLab CLI (`glab`) for reverse-ticket from an MR url",
|
|
528
|
-
status: hasGlabCli() ? "pass" : "warn",
|
|
529
|
-
detail: hasGlabCli()
|
|
530
|
-
? "installed"
|
|
531
|
-
: "not installed — only needed for `codiedev reverse-ticket <gitlab-mr-url>`",
|
|
532
|
-
});
|
|
533
499
|
report(checks);
|
|
534
500
|
const failures = checks.filter((c) => c.status === "fail");
|
|
535
501
|
if (failures.length > 0) {
|
package/dist/hook.js
CHANGED
|
@@ -48,6 +48,7 @@ const limitEvent_1 = require("./limitEvent");
|
|
|
48
48
|
const cursorLifecycle_1 = require("./cursorLifecycle");
|
|
49
49
|
const codexLimitScan_1 = require("./codexLimitScan");
|
|
50
50
|
const launchContext_1 = require("./launchContext");
|
|
51
|
+
const selfUpdate_1 = require("./selfUpdate");
|
|
51
52
|
const version_1 = require("./version");
|
|
52
53
|
const captureLog_1 = require("./captureLog");
|
|
53
54
|
const subagents_1 = require("./subagents");
|
|
@@ -527,6 +528,20 @@ async function main() {
|
|
|
527
528
|
}
|
|
528
529
|
process.exit(0);
|
|
529
530
|
}
|
|
531
|
+
// Detached daily self-upgrade of this install (see selfUpdate.ts).
|
|
532
|
+
if (process.argv[2] === "self-update") {
|
|
533
|
+
await (0, selfUpdate_1.runSelfUpdate)(process.argv[1]);
|
|
534
|
+
process.exit(0);
|
|
535
|
+
}
|
|
536
|
+
// A hook reached directly (not via the stable launcher) means some harness
|
|
537
|
+
// config predates the launcher: move them all onto it once. Steady state is
|
|
538
|
+
// every call arriving via the launcher, which skips this entirely.
|
|
539
|
+
const migrated = process.env.CODIEDEV_VIA_LAUNCHER ? [] : (0, utils_1.migrateHooksToLauncher)();
|
|
540
|
+
// Every harness reaches this point, so one check covers all of them. Right
|
|
541
|
+
// after a migration, wait for the next call: it arrives via the launcher,
|
|
542
|
+
// which is what makes a pnpm install safe to upgrade.
|
|
543
|
+
if (migrated.length === 0)
|
|
544
|
+
(0, selfUpdate_1.maybeSpawnSelfUpdate)(process.argv[1]);
|
|
530
545
|
let hookInput = {};
|
|
531
546
|
try {
|
|
532
547
|
const raw = await readStdin();
|
|
@@ -651,7 +666,7 @@ async function main() {
|
|
|
651
666
|
// Same detached capture-finish child as SessionEnd, marked non-final so the
|
|
652
667
|
// server renews the session lease instead of summarizing immediately.
|
|
653
668
|
if (process.argv[2] === "backup" || process.argv[2] === "backup-cursor" || process.argv[2] === "backup-copilot") {
|
|
654
|
-
if (transcript_path) {
|
|
669
|
+
if (transcript_path && !(0, utils_1.isCodexRolloutPath)(transcript_path)) {
|
|
655
670
|
try {
|
|
656
671
|
(0, utils_1.dispatchCaptureFinish)({
|
|
657
672
|
mode: process.argv[2] === "backup-cursor"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare const LAUNCHER_FILE = "codiedev-hook-launcher.cjs";
|
|
2
|
+
export interface InstallRecord {
|
|
3
|
+
hookScript: string;
|
|
4
|
+
version?: string;
|
|
5
|
+
recordedAt: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function launcherPath(): string;
|
|
8
|
+
/** True iff `cmd` invokes the stable launcher (from any state dir). */
|
|
9
|
+
export declare function usesLauncher(cmd: string | undefined): boolean;
|
|
10
|
+
export declare function readInstallRecord(): InstallRecord | null;
|
|
11
|
+
/** Point the launcher at `hookScript`. Atomic, so a hook mid-launch never reads half a file. */
|
|
12
|
+
export declare function recordCurrentInstall(hookScript: string): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Make sure the launcher exists and forwards to `hookScript`, returning the
|
|
15
|
+
* launcher path to bake into hook configs — or null if the state dir is not
|
|
16
|
+
* writable, in which case callers keep baking the direct path. Memoized:
|
|
17
|
+
* one connect writes ~20 hook entries.
|
|
18
|
+
*/
|
|
19
|
+
export declare function ensureLauncher(hookScript: string): string | null;
|
|
20
|
+
export declare function resetLauncherCacheForTests(): void;
|
package/dist/launcher.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Stable hook launcher.
|
|
3
|
+
//
|
|
4
|
+
// Harness configs used to bake the absolute path of whichever install ran
|
|
5
|
+
// `connect` (…/node_modules/codiedev/dist/hook.js). That path is only stable
|
|
6
|
+
// for npm/bun globals: pnpm puts every global install in a fresh hashed dir,
|
|
7
|
+
// and repo checkouts, Homebrew cellar moves, etc. can strand it too. Instead,
|
|
8
|
+
// every hook config points at one file we own —
|
|
9
|
+
// <codiedevStateDir()>/bin/codiedev-hook-launcher.cjs — which forwards to the
|
|
10
|
+
// install recorded in <codiedevStateDir()>/install.json. Moving to a new
|
|
11
|
+
// install (self-update, re-connect) rewrites one JSON file, never the
|
|
12
|
+
// harness configs, so Codex hook trust and IT-managed dotfiles stay put.
|
|
13
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
+
}
|
|
19
|
+
Object.defineProperty(o, k2, desc);
|
|
20
|
+
}) : (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
o[k2] = m[k];
|
|
23
|
+
}));
|
|
24
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
+
}) : function(o, v) {
|
|
27
|
+
o["default"] = v;
|
|
28
|
+
});
|
|
29
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
+
var ownKeys = function(o) {
|
|
31
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
+
var ar = [];
|
|
33
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
+
return ar;
|
|
35
|
+
};
|
|
36
|
+
return ownKeys(o);
|
|
37
|
+
};
|
|
38
|
+
return function (mod) {
|
|
39
|
+
if (mod && mod.__esModule) return mod;
|
|
40
|
+
var result = {};
|
|
41
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
+
__setModuleDefault(result, mod);
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
})();
|
|
46
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
exports.LAUNCHER_FILE = void 0;
|
|
48
|
+
exports.launcherPath = launcherPath;
|
|
49
|
+
exports.usesLauncher = usesLauncher;
|
|
50
|
+
exports.readInstallRecord = readInstallRecord;
|
|
51
|
+
exports.recordCurrentInstall = recordCurrentInstall;
|
|
52
|
+
exports.ensureLauncher = ensureLauncher;
|
|
53
|
+
exports.resetLauncherCacheForTests = resetLauncherCacheForTests;
|
|
54
|
+
const fs = __importStar(require("fs"));
|
|
55
|
+
const path = __importStar(require("path"));
|
|
56
|
+
const configDir_1 = require("./configDir");
|
|
57
|
+
exports.LAUNCHER_FILE = "codiedev-hook-launcher.cjs";
|
|
58
|
+
// Bump when LAUNCHER_SOURCE changes so existing launchers get rewritten.
|
|
59
|
+
const LAUNCHER_REVISION = 1;
|
|
60
|
+
function launcherPath() {
|
|
61
|
+
return path.join((0, configDir_1.codiedevStateDir)(), "bin", exports.LAUNCHER_FILE);
|
|
62
|
+
}
|
|
63
|
+
function installRecordPath() {
|
|
64
|
+
return path.join((0, configDir_1.codiedevStateDir)(), "install.json");
|
|
65
|
+
}
|
|
66
|
+
/** True iff `cmd` invokes the stable launcher (from any state dir). */
|
|
67
|
+
function usesLauncher(cmd) {
|
|
68
|
+
return typeof cmd === "string" && cmd.includes(exports.LAUNCHER_FILE);
|
|
69
|
+
}
|
|
70
|
+
// Plain CommonJS with no dependencies: it runs under whatever node the harness
|
|
71
|
+
// config names, before any of our modules load. `fallback` is the install the
|
|
72
|
+
// launcher was written for, used if install.json is missing or points at a
|
|
73
|
+
// directory that no longer exists. If neither exists it exits 0 so a stranded
|
|
74
|
+
// hook can never fail the developer's session.
|
|
75
|
+
function launcherSource(fallback) {
|
|
76
|
+
return `// codiedev-hook-launcher revision ${LAUNCHER_REVISION}. Managed by codiedev; safe to delete (re-run \`codiedev connect\`).
|
|
77
|
+
"use strict";
|
|
78
|
+
const fs = require("fs");
|
|
79
|
+
const path = require("path");
|
|
80
|
+
const FALLBACK = ${JSON.stringify(fallback)};
|
|
81
|
+
let target = null;
|
|
82
|
+
try {
|
|
83
|
+
const rec = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "install.json"), "utf8"));
|
|
84
|
+
if (rec && typeof rec.hookScript === "string" && fs.existsSync(rec.hookScript)) target = rec.hookScript;
|
|
85
|
+
} catch {}
|
|
86
|
+
if (!target && fs.existsSync(FALLBACK)) target = FALLBACK;
|
|
87
|
+
if (!target) process.exit(0);
|
|
88
|
+
process.env.CODIEDEV_VIA_LAUNCHER = "1";
|
|
89
|
+
process.argv[1] = target;
|
|
90
|
+
require(target);
|
|
91
|
+
`;
|
|
92
|
+
}
|
|
93
|
+
function writeAtomic(target, contents) {
|
|
94
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
95
|
+
const tmp = `${target}.${process.pid}.tmp`;
|
|
96
|
+
fs.writeFileSync(tmp, contents, "utf8");
|
|
97
|
+
fs.renameSync(tmp, target);
|
|
98
|
+
}
|
|
99
|
+
function readInstallRecord() {
|
|
100
|
+
try {
|
|
101
|
+
const parsed = JSON.parse(fs.readFileSync(installRecordPath(), "utf8"));
|
|
102
|
+
return typeof parsed.hookScript === "string" ? parsed : null;
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function packageVersionFor(hookScript) {
|
|
109
|
+
try {
|
|
110
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(path.dirname(path.dirname(hookScript)), "package.json"), "utf8"));
|
|
111
|
+
return typeof pkg.version === "string" ? pkg.version : undefined;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/** Point the launcher at `hookScript`. Atomic, so a hook mid-launch never reads half a file. */
|
|
118
|
+
function recordCurrentInstall(hookScript) {
|
|
119
|
+
try {
|
|
120
|
+
const record = {
|
|
121
|
+
hookScript,
|
|
122
|
+
version: packageVersionFor(hookScript),
|
|
123
|
+
recordedAt: Date.now(),
|
|
124
|
+
};
|
|
125
|
+
writeAtomic(installRecordPath(), JSON.stringify(record, null, 2));
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
let ensuredFor;
|
|
133
|
+
/**
|
|
134
|
+
* Make sure the launcher exists and forwards to `hookScript`, returning the
|
|
135
|
+
* launcher path to bake into hook configs — or null if the state dir is not
|
|
136
|
+
* writable, in which case callers keep baking the direct path. Memoized:
|
|
137
|
+
* one connect writes ~20 hook entries.
|
|
138
|
+
*/
|
|
139
|
+
function ensureLauncher(hookScript) {
|
|
140
|
+
if (ensuredFor !== undefined && ensuredFor === hookScript)
|
|
141
|
+
return launcherPath();
|
|
142
|
+
try {
|
|
143
|
+
const target = launcherPath();
|
|
144
|
+
const source = launcherSource(hookScript);
|
|
145
|
+
let current = null;
|
|
146
|
+
try {
|
|
147
|
+
current = fs.readFileSync(target, "utf8");
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// first install
|
|
151
|
+
}
|
|
152
|
+
if (current !== source)
|
|
153
|
+
writeAtomic(target, source);
|
|
154
|
+
if (readInstallRecord()?.hookScript !== hookScript && !recordCurrentInstall(hookScript)) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
ensuredFor = hookScript;
|
|
158
|
+
return target;
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Test-only: the memo lives for the process lifetime.
|
|
165
|
+
function resetLauncherCacheForTests() {
|
|
166
|
+
ensuredFor = undefined;
|
|
167
|
+
}
|