getculpa 1.0.2 → 1.0.4

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/lib/start.mjs CHANGED
@@ -415,10 +415,15 @@ async function runPosixStateMachine({ appDir, liveCompose, platform, spawnSync,
415
415
  };
416
416
  }
417
417
 
418
- // No collectord build/staging exists for macOS/Linux yet (task scope) —
419
- // degrade honestly rather than fail the whole launch over capture.
418
+ // V102 T-V102-14 (M4, D-113): the old line here said "no collector build
419
+ // ships for macOS/Linux yet" — verified FALSE on real hardware (the arm64
420
+ // collectord ships, was staged executable, install-state recorded
421
+ // collectorStaged: true). What IS true: the collector is not auto-started
422
+ // on this platform yet, while the server-side coding-tool log sweep works
423
+ // (proven live on the Mac: calls landed unattended, ~45 min cadence). Say
424
+ // exactly that — neither direction of the old overclaim.
420
425
  stagesRun.push(STAGES.START_CULPA);
421
- log("getculpa: capture is OFF on this platform for now (no collector build ships for macOS/Linux yet) - everything else works normally.");
426
+ log("getculpa: your local coding-tool usage is captured by the built-in log sweep. The production traffic collector is not started automatically on this platform yet - everything else works normally.");
422
427
 
423
428
  stagesRun.push(STAGES.OPEN_UI);
424
429
  if (pinnedVersion) writeFileSync(path.join(appDir, "last-boot-version.txt"), `${pinnedVersion}\n`);
package/lib/uninstall.mjs CHANGED
@@ -62,12 +62,36 @@ async function promptPgdataRemoval(createInterface, isTTY) {
62
62
  if (!isTTY) return false;
63
63
  const rl = createInterface({ input: process.stdin, output: process.stdout });
64
64
  const answer = await new Promise((resolve) => {
65
+ // V102 T-V102-16 (D-113): EOF (Ctrl-D, or a stdin that ends mid-prompt)
66
+ // fires `close` and NEVER invokes question()'s callback — this promise
67
+ // used to hang forever at the one prompt guarding DELETION of the cost
68
+ // database. An unanswered question is a NO, exactly like tty.mjs.
69
+ // Optional-call: older injected fakes without `once` keep working.
70
+ rl.once?.("close", () => resolve(""));
65
71
  rl.question("Also DELETE all recorded cost data? This cannot be undone. (y/N) ", resolve);
66
72
  });
67
73
  rl.close();
68
74
  return answer.trim().toLowerCase() === "y";
69
75
  }
70
76
 
77
+ // V102 T-V102-16 (M11/W2, D-113): macOS uninstall exited 0 leaving ~7.3 MB
78
+ // (launcher, collector, compose, register.mjs) silently behind — the Windows
79
+ // ps1 removes the app dir and is "the more complete of the two" (W2). Remove
80
+ // the dir ONLY when it is provably a Culpa install dir (install-state.json,
81
+ // which every install writes); a mistargeted CULPA_APP_DIR is kept and said.
82
+ // Truthful beats a surprise rm -rf in both directions.
83
+ function removeAppDir(appDir) {
84
+ if (!existsSync(appDir)) return;
85
+ if (existsSync(path.join(appDir, "install-state.json"))) {
86
+ rmSync(appDir, { recursive: true, force: true });
87
+ console.log(`Removed: ${appDir} (launcher, collector, compose and capture shims).`);
88
+ return;
89
+ }
90
+ console.log(
91
+ `Kept: ${appDir} - no install-state.json found there, so it is not provably a Culpa install directory. Remove it yourself if you are sure.`,
92
+ );
93
+ }
94
+
71
95
  export async function uninstall(opts = {}) {
72
96
  const appDir = opts.appDir ?? getAppDir();
73
97
  const platform = opts.platform ?? process.platform;
@@ -82,13 +106,28 @@ export async function uninstall(opts = {}) {
82
106
  if (!ok) console.error("getculpa: uninstall did not complete cleanly - see the output above.");
83
107
  } else {
84
108
  ok = composeDown(appDir, spawnSync);
85
- removeShortcutEquivalents(homedir);
86
- const wipe = await promptPgdataRemoval(createInterface, isTTY);
87
- if (wipe) {
88
- spawnSync("docker", ["volume", "rm", "culpa_pgdata"], { stdio: "inherit" });
89
- console.log("Data volume removed.");
109
+ // Review-4 IMPORTANT + CodeRabbit PR#19 [7] (Major): a FAILED stop skips
110
+ // EVERY destructive step — the ps1's $stackStopped gate, whose comment
111
+ // records the prior real incident (CR-PR5 round 3). Not even the pgdata
112
+ // PROMPT is offered: after a partial teardown the volume can be
113
+ // unreferenced, so a user confirming deletion here could lose the cost
114
+ // database while being told the stack is still running. Nothing is
115
+ // removed; the one honest instruction is to stop the stack and rerun.
116
+ if (!ok) {
117
+ console.log(
118
+ `Files kept: ${appDir} - the stack is still running (\`docker compose down\` failed above). Nothing was removed. Stop the stack, then run this again.`,
119
+ );
90
120
  } else {
91
- console.log("Data kept (volume culpa_pgdata). Reinstalling later will find it again.");
121
+ removeShortcutEquivalents(homedir);
122
+ const wipe = await promptPgdataRemoval(createInterface, isTTY);
123
+ if (wipe) {
124
+ spawnSync("docker", ["volume", "rm", "culpa_pgdata"], { stdio: "inherit" });
125
+ console.log("Data volume removed.");
126
+ } else {
127
+ console.log("Data kept (volume culpa_pgdata). Reinstalling later will find it again.");
128
+ }
129
+ // M11/W2: after the data decision, remove the app dir itself (ps1 parity)
130
+ removeAppDir(appDir);
92
131
  }
93
132
  }
94
133
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getculpa",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Culpa CLI: `npm i -g getculpa` provisions the full Culpa install (Windows-installer parity) and leaves it dormant. `getculpa` wakes the stack.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "bin": {
@@ -119,11 +119,15 @@ async function main() {
119
119
  // `getculpa` dead-ends — the Mac tester's exact experience. Best-effort by
120
120
  // construction: any failure leaves the payload deferred and the summary
121
121
  // below names `getculpa update`, which is where we already were.
122
- const { stageInitialPayload, vendoredLauncherPath } = await import("../lib/bootstrap.mjs");
123
- const { staged: payloadStaged } = stageInitialPayload({
122
+ const { readPayloadState, stageInitialPayload, vendoredLauncherPath } = await import("../lib/bootstrap.mjs");
123
+ stageInitialPayload({
124
124
  launcherPath: vendoredLauncherPath(path.join(__dirname, "..")),
125
125
  env: process.env,
126
126
  });
127
+ // V102 T-V102-15 (M3): the summary reports what updates/state.json actually
128
+ // says, never the updater's exit code — "installed" was claimed on a Mac
129
+ // whose state read current: null while doctor FAILed the same install.
130
+ const payloadState = readPayloadState({ env: process.env });
127
131
 
128
132
  // CF20-T6: the completion line names what actually happened — an upgrade
129
133
  // is STAGED only; the new version applies at the next `getculpa`, not here.
@@ -139,7 +143,7 @@ async function main() {
139
143
  dockerState,
140
144
  imagesStaged,
141
145
  collectorStaged,
142
- payloadStaged,
146
+ payloadState,
143
147
  platform: process.platform,
144
148
  isTTY: Boolean(process.stdout.isTTY),
145
149
  env: process.env,
@@ -14,6 +14,9 @@ const pkgRoot = path.join(__dirname, "..");
14
14
  const repoRoot = path.join(pkgRoot, "..", "..");
15
15
  const assetsDir = path.join(pkgRoot, "assets");
16
16
 
17
+ // An entry is either the repo-relative path parts (staged under its own
18
+ // basename) or { src: [...parts], as: "name" } when the canonical file's
19
+ // basename is too generic to sit in a customer's install folder.
17
20
  const SOURCES = [
18
21
  ["installers", "culpa-compose.yml"],
19
22
  ["installers", "windows", "install-culpa.ps1"],
@@ -21,6 +24,14 @@ const SOURCES = [
21
24
  ["installers", "windows", "uninstall-culpa.ps1"],
22
25
  ["installers", "windows", "culpa-collector.ps1"],
23
26
  ["collector", "node", "register.mjs"],
27
+ // ISS-V103-9: the dashboard's Fly relay tab prints
28
+ // `flyctl deploy --config <this file>` and tells the customer to run it from
29
+ // their Culpa install folder. It shipped in NEITHER the install folder nor
30
+ // the npm package — it existed only in the source repo — so the Fly relay
31
+ // path could not be completed by anyone who installed via npm. Staged under
32
+ // an explicit name because a bare `fly.toml` in the install folder would
33
+ // read as the customer's OWN app config.
34
+ { src: ["deploy", "relay", "fly.toml"], as: "culpa-relay-fly.toml" },
24
35
  ];
25
36
 
26
37
  // CF20-R: clear assets/ before restaging — otherwise a canonical source file
@@ -29,13 +40,14 @@ const SOURCES = [
29
40
  // hand-edited, so nothing else would ever remove it).
30
41
  fs.rmSync(assetsDir, { recursive: true, force: true });
31
42
  fs.mkdirSync(assetsDir, { recursive: true });
32
- for (const parts of SOURCES) {
43
+ for (const entry of SOURCES) {
44
+ const parts = Array.isArray(entry) ? entry : entry.src;
33
45
  const src = path.join(repoRoot, ...parts);
34
46
  if (!fs.existsSync(src)) {
35
47
  console.error(`prepack: missing canonical source ${src}`);
36
48
  process.exit(1);
37
49
  }
38
- const dest = path.join(assetsDir, path.basename(src));
39
- fs.copyFileSync(src, dest);
40
- console.log(`prepack: staged ${path.basename(src)}`);
50
+ const name = Array.isArray(entry) ? path.basename(src) : entry.as;
51
+ fs.copyFileSync(src, path.join(assetsDir, name));
52
+ console.log(`prepack: staged ${name}`);
41
53
  }