lunel-cli 0.1.36 → 0.1.39

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 (2) hide show
  1. package/dist/index.js +106 -2
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -220,6 +220,21 @@ function assertSafePath(requestedPath) {
220
220
  // ============================================================================
221
221
  // File System Handlers
222
222
  // ============================================================================
223
+ function isLikelyBinaryBuffer(buffer) {
224
+ if (buffer.length === 0)
225
+ return false;
226
+ if (buffer.includes(0x00))
227
+ return true;
228
+ let suspicious = 0;
229
+ for (let i = 0; i < buffer.length; i++) {
230
+ const byte = buffer[i];
231
+ const isPrintableAscii = byte >= 0x20 && byte <= 0x7e;
232
+ const isCommonControl = byte === 0x09 || byte === 0x0a || byte === 0x0d;
233
+ if (!isPrintableAscii && !isCommonControl)
234
+ suspicious++;
235
+ }
236
+ return suspicious / buffer.length > 0.3;
237
+ }
223
238
  async function handleFsLs(payload) {
224
239
  const reqPath = payload.path || ".";
225
240
  const safePath = assertSafePath(reqPath);
@@ -258,6 +273,25 @@ async function handleFsStat(payload) {
258
273
  mtime: stat.mtimeMs,
259
274
  mode: stat.mode,
260
275
  };
276
+ if (stat.isFile()) {
277
+ try {
278
+ const fd = await fs.open(safePath, "r");
279
+ try {
280
+ const sampleSize = Math.min(stat.size, 8192);
281
+ const sample = Buffer.alloc(sampleSize);
282
+ if (sampleSize > 0) {
283
+ await fd.read(sample, 0, sampleSize, 0);
284
+ }
285
+ result.isBinary = isLikelyBinaryBuffer(sample);
286
+ }
287
+ finally {
288
+ await fd.close();
289
+ }
290
+ }
291
+ catch {
292
+ // Keep stat resilient even if sampling fails
293
+ }
294
+ }
261
295
  return result;
262
296
  }
263
297
  async function handleFsRead(payload) {
@@ -268,8 +302,8 @@ async function handleFsRead(payload) {
268
302
  // Check if binary
269
303
  const stat = await fs.stat(safePath);
270
304
  const content = await fs.readFile(safePath);
271
- // Try to detect if binary
272
- const isBinary = content.includes(0x00);
305
+ // Detect if binary
306
+ const isBinary = isLikelyBinaryBuffer(content.subarray(0, 8192));
273
307
  if (isBinary) {
274
308
  return {
275
309
  path: reqPath,
@@ -570,6 +604,73 @@ async function handleGitLog(payload) {
570
604
  });
571
605
  return { commits };
572
606
  }
607
+ async function handleGitCommitDetails(payload) {
608
+ const hash = payload.hash?.trim();
609
+ if (!hash)
610
+ throw Object.assign(new Error("hash is required"), { code: "EINVAL" });
611
+ try {
612
+ const commitResult = await runGit(["show", "-s", "--format=%H%n%s%n%an%n%at", hash]);
613
+ if (commitResult.code !== 0 || !commitResult.stdout.trim()) {
614
+ throw Object.assign(new Error("Commit not found"), { code: "EGIT" });
615
+ }
616
+ const commitLines = commitResult.stdout.split(/\r?\n/);
617
+ const fullHash = commitLines[0]?.trim() || "";
618
+ const message = commitLines[1] ?? "";
619
+ const author = commitLines[2] ?? "";
620
+ const timestamp = Number.parseInt(commitLines[3] ?? "0", 10);
621
+ if (!fullHash) {
622
+ throw Object.assign(new Error("Commit not found"), { code: "EGIT" });
623
+ }
624
+ const filesResult = await runGit(["show", "--name-status", "--format=", hash]);
625
+ if (filesResult.code !== 0) {
626
+ throw Object.assign(new Error(filesResult.stderr || "git show failed"), { code: "EGIT" });
627
+ }
628
+ const filesRaw = filesResult.stdout;
629
+ const files = filesRaw
630
+ .split(/\r?\n/)
631
+ .map((line) => line.trim())
632
+ .filter(Boolean)
633
+ .map((line) => {
634
+ const parts = line.split("\t");
635
+ const status = parts[0] || "?";
636
+ // Handles regular + rename/copy name-status output.
637
+ const path = parts[2] || parts[1] || "";
638
+ return { status, path };
639
+ })
640
+ .filter((entry) => !!entry.path);
641
+ const diffResult = await runGit(["show", "--patch", "--format=", hash]);
642
+ if (diffResult.code !== 0) {
643
+ throw Object.assign(new Error(diffResult.stderr || "git show failed"), { code: "EGIT" });
644
+ }
645
+ const diff = diffResult.stdout;
646
+ const fileDiffs = {};
647
+ const fileChunks = diff.split(/^diff --git /m).filter(Boolean);
648
+ for (const chunk of fileChunks) {
649
+ const patch = `diff --git ${chunk}`;
650
+ const firstLine = chunk.split(/\r?\n/, 1)[0] || "";
651
+ const match = firstLine.match(/^a\/(.+?) b\/(.+)$/);
652
+ if (match?.[2]) {
653
+ fileDiffs[match[2]] = patch;
654
+ }
655
+ }
656
+ return {
657
+ commit: {
658
+ hash: fullHash.substring(0, 7),
659
+ fullHash,
660
+ message,
661
+ author,
662
+ date: timestamp * 1000,
663
+ },
664
+ files,
665
+ diff,
666
+ fileDiffs,
667
+ };
668
+ }
669
+ catch (err) {
670
+ const message = err instanceof Error ? err.message : "git show failed";
671
+ throw Object.assign(new Error(message), { code: "EGIT" });
672
+ }
673
+ }
573
674
  async function handleGitDiff(payload) {
574
675
  const filepath = payload.path;
575
676
  const staged = payload.staged === true;
@@ -2110,6 +2211,9 @@ async function processMessage(message) {
2110
2211
  case "log":
2111
2212
  result = await handleGitLog(payload);
2112
2213
  break;
2214
+ case "commitDetails":
2215
+ result = await handleGitCommitDetails(payload);
2216
+ break;
2113
2217
  case "diff":
2114
2218
  result = await handleGitDiff(payload);
2115
2219
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lunel-cli",
3
- "version": "0.1.36",
3
+ "version": "0.1.39",
4
4
  "author": [
5
5
  {
6
6
  "name": "Soham Bharambe",
@@ -14,7 +14,7 @@
14
14
  "license": "Functional Source License, Version 1.1, Apache 2.0 Future License",
15
15
  "type": "module",
16
16
  "bin": {
17
- "lunel-cli": "./dist/index.js"
17
+ "lunel-cli": "dist/index.js"
18
18
  },
19
19
  "files": [
20
20
  "dist",