docbrain 1.5.10 → 1.5.12

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.
Files changed (3) hide show
  1. package/install.js +22 -5
  2. package/package.json +2 -2
  3. package/run.js +38 -2
package/install.js CHANGED
@@ -40,14 +40,31 @@ function download(url) {
40
40
  reject(new Error(`Download failed: HTTP ${res.statusCode}`));
41
41
  return;
42
42
  }
43
- const file = fs.createWriteStream(dest);
43
+ // Download to a scratch path and rename into place. rename() is atomic
44
+ // within a filesystem, so a download that is interrupted — or two that
45
+ // race, now that run.js can trigger one on first use — can never leave a
46
+ // half-written file at `dest`. A truncated binary is the worst outcome
47
+ // available here: it exists, so nothing re-downloads it, and it fails at
48
+ // exec with an error that says nothing about the real cause.
49
+ const tmp = `${dest}.tmp-${process.pid}`;
50
+ const file = fs.createWriteStream(tmp);
44
51
  res.pipe(file);
45
52
  file.on("finish", () => {
46
- file.close();
47
- fs.chmodSync(dest, 0o755);
48
- resolve();
53
+ file.close(() => {
54
+ try {
55
+ fs.chmodSync(tmp, 0o755);
56
+ fs.renameSync(tmp, dest);
57
+ resolve();
58
+ } catch (err) {
59
+ try { fs.unlinkSync(tmp); } catch {}
60
+ reject(err);
61
+ }
62
+ });
63
+ });
64
+ file.on("error", (err) => {
65
+ try { fs.unlinkSync(tmp); } catch {}
66
+ reject(err);
49
67
  });
50
- file.on("error", reject);
51
68
  })
52
69
  .on("error", reject);
53
70
  });
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "docbrain",
3
- "version": "1.5.10",
3
+ "version": "1.5.12",
4
4
  "description": "AI-powered documentation intelligence CLI",
5
5
  "homepage": "https://github.com/docbrain-ai/docbrain",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/docbrain-ai/docbrain.git"
9
9
  },
10
- "license": "BSL-1.1",
10
+ "license": "MIT",
11
11
  "bin": {
12
12
  "docbrain": "run.js"
13
13
  },
package/run.js CHANGED
@@ -2,17 +2,53 @@
2
2
  "use strict";
3
3
 
4
4
  const { execFileSync } = require("child_process");
5
+ const fs = require("fs");
5
6
  const path = require("path");
6
7
 
7
8
  const binary = path.join(__dirname, "bin", "docbrain");
8
9
 
10
+ // npm 12 turned lifecycle scripts off by default, so the postinstall hook that
11
+ // fetches this binary may simply never have run — `npm install -g docbrain`
12
+ // reports success and leaves nothing behind. Fetching on first use covers that,
13
+ // and also covers an install that was interrupted before the download finished.
14
+ //
15
+ // The previous behaviour was to tell the user to reinstall. Under npm 12 that
16
+ // advice cannot work: the reinstall skips the same postinstall and fails
17
+ // identically, so following it loops forever.
18
+ function ensureBinary() {
19
+ if (fs.existsSync(binary)) return;
20
+
21
+ console.error("DocBrain CLI binary not found — downloading it now (one-time).");
22
+ try {
23
+ execFileSync(process.execPath, [path.join(__dirname, "install.js")], {
24
+ stdio: "inherit",
25
+ });
26
+ } catch {
27
+ // install.js has already explained what went wrong. Add the two things it
28
+ // cannot know: where the binary belongs, and that a permission error means
29
+ // this needs running as whoever owns the install directory.
30
+ console.error("");
31
+ console.error(`Could not place the binary at: ${binary}`);
32
+ console.error(
33
+ "If that path is not writable, run this once as the user that installed the package,"
34
+ );
35
+ console.error(
36
+ "or download the binary manually from https://github.com/docbrain-ai/docbrain/releases"
37
+ );
38
+ process.exit(1);
39
+ }
40
+ }
41
+
42
+ ensureBinary();
43
+
9
44
  try {
10
45
  execFileSync(binary, process.argv.slice(2), { stdio: "inherit" });
11
46
  } catch (err) {
12
- if (err.status !== null) {
47
+ // A non-null status means the CLI itself ran and chose that exit code, so
48
+ // pass it through untouched — callers and CI depend on it.
49
+ if (err.status !== null && err.status !== undefined) {
13
50
  process.exit(err.status);
14
51
  }
15
52
  console.error(`Failed to run DocBrain CLI: ${err.message}`);
16
- console.error("Try reinstalling: npm install -g docbrain");
17
53
  process.exit(1);
18
54
  }