@neat.is/core 0.3.1 → 0.3.2
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/cli.cjs +76 -27
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +76 -27
- package/dist/cli.js.map +1 -1
- package/dist/neatd.cjs +14 -2
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +14 -2
- package/dist/neatd.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -45,9 +45,10 @@ import {
|
|
|
45
45
|
|
|
46
46
|
// src/cli.ts
|
|
47
47
|
import path4 from "path";
|
|
48
|
-
import { promises as
|
|
48
|
+
import { promises as fs4 } from "fs";
|
|
49
49
|
|
|
50
50
|
// src/watch.ts
|
|
51
|
+
import fs from "fs";
|
|
51
52
|
import path from "path";
|
|
52
53
|
import chokidar from "chokidar";
|
|
53
54
|
|
|
@@ -143,6 +144,16 @@ async function runExtractPhases(graph, scanPath, phases, project = DEFAULT_PROJE
|
|
|
143
144
|
durationMs: Date.now() - started
|
|
144
145
|
};
|
|
145
146
|
}
|
|
147
|
+
var IGNORED_WATCH_GLOBS = [
|
|
148
|
+
"**/node_modules/**",
|
|
149
|
+
"**/.git/**",
|
|
150
|
+
"**/dist/**",
|
|
151
|
+
"**/build/**",
|
|
152
|
+
"**/.turbo/**",
|
|
153
|
+
"**/.next/**",
|
|
154
|
+
"**/neat-out/**",
|
|
155
|
+
"**/.DS_Store"
|
|
156
|
+
];
|
|
146
157
|
var IGNORED_WATCH_PATHS = [
|
|
147
158
|
/(?:^|[\\/])node_modules[\\/]/,
|
|
148
159
|
/(?:^|[\\/])\.git[\\/]/,
|
|
@@ -156,6 +167,35 @@ var IGNORED_WATCH_PATHS = [
|
|
|
156
167
|
function shouldIgnore(absPath) {
|
|
157
168
|
return IGNORED_WATCH_PATHS.some((re) => re.test(absPath));
|
|
158
169
|
}
|
|
170
|
+
var DARWIN_POLLING_DIR_THRESHOLD = 400;
|
|
171
|
+
function countWatchableDirs(scanPath, limit) {
|
|
172
|
+
let count = 0;
|
|
173
|
+
const visit = (dir, depth) => {
|
|
174
|
+
if (count >= limit) return;
|
|
175
|
+
let entries;
|
|
176
|
+
try {
|
|
177
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
178
|
+
} catch {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
for (const e of entries) {
|
|
182
|
+
if (count >= limit) return;
|
|
183
|
+
if (!e.isDirectory()) continue;
|
|
184
|
+
if (IGNORED_WATCH_PATHS.some((re) => re.test(path.join(dir, e.name) + path.sep))) continue;
|
|
185
|
+
count++;
|
|
186
|
+
if (depth < 2) visit(path.join(dir, e.name), depth + 1);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
visit(scanPath, 0);
|
|
190
|
+
return count;
|
|
191
|
+
}
|
|
192
|
+
function shouldUsePolling(scanPath) {
|
|
193
|
+
const env = process.env.NEAT_WATCH_POLLING;
|
|
194
|
+
if (env === "1" || env === "true") return true;
|
|
195
|
+
if (env === "0" || env === "false") return false;
|
|
196
|
+
if (process.platform !== "darwin") return false;
|
|
197
|
+
return countWatchableDirs(scanPath, DARWIN_POLLING_DIR_THRESHOLD) >= DARWIN_POLLING_DIR_THRESHOLD;
|
|
198
|
+
}
|
|
159
199
|
async function startWatch(graph, opts) {
|
|
160
200
|
const debounceMs = opts.debounceMs ?? 1e3;
|
|
161
201
|
const projectName = opts.project ?? DEFAULT_PROJECT;
|
|
@@ -325,10 +365,19 @@ async function startWatch(graph, opts) {
|
|
|
325
365
|
}
|
|
326
366
|
schedule();
|
|
327
367
|
};
|
|
368
|
+
const usePolling = shouldUsePolling(opts.scanPath);
|
|
369
|
+
if (usePolling) {
|
|
370
|
+
const reason = process.env.NEAT_WATCH_POLLING === "1" || process.env.NEAT_WATCH_POLLING === "true" ? "NEAT_WATCH_POLLING env override" : "darwin heuristic \u2014 large scan root, kqueue cap risk";
|
|
371
|
+
console.log(`[${projectName}] watch: usePolling=true (${reason})`);
|
|
372
|
+
}
|
|
328
373
|
const watcher = chokidar.watch(opts.scanPath, {
|
|
329
374
|
ignoreInitial: true,
|
|
330
|
-
|
|
375
|
+
// Glob array prunes at descent time (#233) so chokidar never opens a
|
|
376
|
+
// kqueue handle for `node_modules` and friends. The function backstop
|
|
377
|
+
// catches any path that slipped through and matches the regex set.
|
|
378
|
+
ignored: [...IGNORED_WATCH_GLOBS, (p) => shouldIgnore(p)],
|
|
331
379
|
persistent: true,
|
|
380
|
+
usePolling,
|
|
332
381
|
awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 }
|
|
333
382
|
});
|
|
334
383
|
watcher.on("add", onPath);
|
|
@@ -360,7 +409,7 @@ async function startWatch(graph, opts) {
|
|
|
360
409
|
}
|
|
361
410
|
|
|
362
411
|
// src/installers/javascript.ts
|
|
363
|
-
import { promises as
|
|
412
|
+
import { promises as fs2 } from "fs";
|
|
364
413
|
import path2 from "path";
|
|
365
414
|
var SDK_PACKAGES = [
|
|
366
415
|
{ name: "@opentelemetry/api", version: "^1.9.0" },
|
|
@@ -377,7 +426,7 @@ var OTEL_ENV = {
|
|
|
377
426
|
};
|
|
378
427
|
async function readPackageJson(serviceDir) {
|
|
379
428
|
try {
|
|
380
|
-
const raw = await
|
|
429
|
+
const raw = await fs2.readFile(path2.join(serviceDir, "package.json"), "utf8");
|
|
381
430
|
return JSON.parse(raw);
|
|
382
431
|
} catch {
|
|
383
432
|
return null;
|
|
@@ -443,7 +492,7 @@ async function apply(installPlan) {
|
|
|
443
492
|
const originals = /* @__PURE__ */ new Map();
|
|
444
493
|
for (const file of touched) {
|
|
445
494
|
try {
|
|
446
|
-
originals.set(file, await
|
|
495
|
+
originals.set(file, await fs2.readFile(file, "utf8"));
|
|
447
496
|
} catch {
|
|
448
497
|
}
|
|
449
498
|
}
|
|
@@ -469,8 +518,8 @@ async function apply(installPlan) {
|
|
|
469
518
|
}
|
|
470
519
|
const newRaw = JSON.stringify(pkg, null, 2) + "\n";
|
|
471
520
|
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
472
|
-
await
|
|
473
|
-
await
|
|
521
|
+
await fs2.writeFile(tmp, newRaw, "utf8");
|
|
522
|
+
await fs2.rename(tmp, file);
|
|
474
523
|
}
|
|
475
524
|
} catch (err) {
|
|
476
525
|
await rollback(installPlan, originals);
|
|
@@ -481,7 +530,7 @@ async function rollback(installPlan, originals) {
|
|
|
481
530
|
const restored = [];
|
|
482
531
|
for (const [file, raw] of originals.entries()) {
|
|
483
532
|
try {
|
|
484
|
-
await
|
|
533
|
+
await fs2.writeFile(file, raw, "utf8");
|
|
485
534
|
restored.push(file);
|
|
486
535
|
} catch {
|
|
487
536
|
}
|
|
@@ -496,7 +545,7 @@ async function rollback(installPlan, originals) {
|
|
|
496
545
|
""
|
|
497
546
|
];
|
|
498
547
|
const rollbackPath = path2.join(installPlan.serviceDir, "neat-rollback.patch");
|
|
499
|
-
await
|
|
548
|
+
await fs2.writeFile(rollbackPath, lines.join("\n"), "utf8");
|
|
500
549
|
}
|
|
501
550
|
var javascriptInstaller = {
|
|
502
551
|
name: "javascript",
|
|
@@ -506,7 +555,7 @@ var javascriptInstaller = {
|
|
|
506
555
|
};
|
|
507
556
|
|
|
508
557
|
// src/installers/python.ts
|
|
509
|
-
import { promises as
|
|
558
|
+
import { promises as fs3 } from "fs";
|
|
510
559
|
import path3 from "path";
|
|
511
560
|
var SDK_PACKAGES2 = [
|
|
512
561
|
{ name: "opentelemetry-distro", version: ">=0.49b0" },
|
|
@@ -519,7 +568,7 @@ var OTEL_ENV2 = {
|
|
|
519
568
|
};
|
|
520
569
|
async function exists(p) {
|
|
521
570
|
try {
|
|
522
|
-
await
|
|
571
|
+
await fs3.stat(p);
|
|
523
572
|
return true;
|
|
524
573
|
} catch {
|
|
525
574
|
return false;
|
|
@@ -540,7 +589,7 @@ function reqPackageName(line) {
|
|
|
540
589
|
async function planRequirementsTxtEdits(serviceDir) {
|
|
541
590
|
const file = path3.join(serviceDir, "requirements.txt");
|
|
542
591
|
if (!await exists(file)) return null;
|
|
543
|
-
const raw = await
|
|
592
|
+
const raw = await fs3.readFile(file, "utf8");
|
|
544
593
|
const presentNames = new Set(
|
|
545
594
|
raw.split(/\r?\n/).map(reqPackageName).filter((n) => n.length > 0)
|
|
546
595
|
);
|
|
@@ -550,7 +599,7 @@ async function planRequirementsTxtEdits(serviceDir) {
|
|
|
550
599
|
async function planProcfileEdits(serviceDir) {
|
|
551
600
|
const procfile = path3.join(serviceDir, "Procfile");
|
|
552
601
|
if (!await exists(procfile)) return [];
|
|
553
|
-
const raw = await
|
|
602
|
+
const raw = await fs3.readFile(procfile, "utf8");
|
|
554
603
|
const edits = [];
|
|
555
604
|
for (const line of raw.split(/\r?\n/)) {
|
|
556
605
|
if (line.length === 0) continue;
|
|
@@ -602,8 +651,8 @@ async function applyRequirementsTxt(manifest, edits, original) {
|
|
|
602
651
|
const next = `${original}${trailing}${newlines.join("\n")}
|
|
603
652
|
`;
|
|
604
653
|
const tmp = `${manifest}.${process.pid}.${Date.now()}.tmp`;
|
|
605
|
-
await
|
|
606
|
-
await
|
|
654
|
+
await fs3.writeFile(tmp, next, "utf8");
|
|
655
|
+
await fs3.rename(tmp, manifest);
|
|
607
656
|
}
|
|
608
657
|
async function applyProcfile(procfile, edits, original) {
|
|
609
658
|
let next = original;
|
|
@@ -612,8 +661,8 @@ async function applyProcfile(procfile, edits, original) {
|
|
|
612
661
|
next = next.replace(e.before, e.after);
|
|
613
662
|
}
|
|
614
663
|
const tmp = `${procfile}.${process.pid}.${Date.now()}.tmp`;
|
|
615
|
-
await
|
|
616
|
-
await
|
|
664
|
+
await fs3.writeFile(tmp, next, "utf8");
|
|
665
|
+
await fs3.rename(tmp, procfile);
|
|
617
666
|
}
|
|
618
667
|
async function apply2(installPlan) {
|
|
619
668
|
const touched = /* @__PURE__ */ new Set();
|
|
@@ -623,7 +672,7 @@ async function apply2(installPlan) {
|
|
|
623
672
|
const originals = /* @__PURE__ */ new Map();
|
|
624
673
|
for (const file of touched) {
|
|
625
674
|
try {
|
|
626
|
-
originals.set(file, await
|
|
675
|
+
originals.set(file, await fs3.readFile(file, "utf8"));
|
|
627
676
|
} catch {
|
|
628
677
|
}
|
|
629
678
|
}
|
|
@@ -651,7 +700,7 @@ async function rollback2(installPlan, originals) {
|
|
|
651
700
|
const restored = [];
|
|
652
701
|
for (const [file, raw] of originals.entries()) {
|
|
653
702
|
try {
|
|
654
|
-
await
|
|
703
|
+
await fs3.writeFile(file, raw, "utf8");
|
|
655
704
|
restored.push(file);
|
|
656
705
|
} catch {
|
|
657
706
|
}
|
|
@@ -666,7 +715,7 @@ async function rollback2(installPlan, originals) {
|
|
|
666
715
|
""
|
|
667
716
|
];
|
|
668
717
|
const rollbackPath = path3.join(installPlan.serviceDir, "neat-rollback.patch");
|
|
669
|
-
await
|
|
718
|
+
await fs3.writeFile(rollbackPath, lines.join("\n"), "utf8");
|
|
670
719
|
}
|
|
671
720
|
var pythonInstaller = {
|
|
672
721
|
name: "python",
|
|
@@ -1500,7 +1549,7 @@ async function buildPatchSections(services) {
|
|
|
1500
1549
|
}
|
|
1501
1550
|
async function runInit(opts) {
|
|
1502
1551
|
const written = [];
|
|
1503
|
-
const stat = await
|
|
1552
|
+
const stat = await fs4.stat(opts.scanPath).catch(() => null);
|
|
1504
1553
|
if (!stat || !stat.isDirectory()) {
|
|
1505
1554
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
1506
1555
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -1511,7 +1560,7 @@ async function runInit(opts) {
|
|
|
1511
1560
|
const patch = renderPatch(sections);
|
|
1512
1561
|
const patchPath = path4.join(opts.scanPath, "neat.patch");
|
|
1513
1562
|
if (opts.dryRun) {
|
|
1514
|
-
await
|
|
1563
|
+
await fs4.writeFile(patchPath, patch, "utf8");
|
|
1515
1564
|
written.push(patchPath);
|
|
1516
1565
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
1517
1566
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -1551,7 +1600,7 @@ async function runInit(opts) {
|
|
|
1551
1600
|
console.log("patch applied. Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
1552
1601
|
}
|
|
1553
1602
|
} else {
|
|
1554
|
-
await
|
|
1603
|
+
await fs4.writeFile(patchPath, patch, "utf8");
|
|
1555
1604
|
written.push(patchPath);
|
|
1556
1605
|
}
|
|
1557
1606
|
}
|
|
@@ -1605,7 +1654,7 @@ async function runSkill(opts) {
|
|
|
1605
1654
|
const target = claudeConfigPath();
|
|
1606
1655
|
let existing = {};
|
|
1607
1656
|
try {
|
|
1608
|
-
existing = JSON.parse(await
|
|
1657
|
+
existing = JSON.parse(await fs4.readFile(target, "utf8"));
|
|
1609
1658
|
} catch (err) {
|
|
1610
1659
|
if (err.code !== "ENOENT") {
|
|
1611
1660
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -1617,8 +1666,8 @@ async function runSkill(opts) {
|
|
|
1617
1666
|
...existing,
|
|
1618
1667
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
1619
1668
|
};
|
|
1620
|
-
await
|
|
1621
|
-
await
|
|
1669
|
+
await fs4.mkdir(path4.dirname(target), { recursive: true });
|
|
1670
|
+
await fs4.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
1622
1671
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
1623
1672
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
1624
1673
|
return { exitCode: 0 };
|
|
@@ -1678,7 +1727,7 @@ async function main() {
|
|
|
1678
1727
|
process.exit(2);
|
|
1679
1728
|
}
|
|
1680
1729
|
const scanPath = path4.resolve(target);
|
|
1681
|
-
const stat = await
|
|
1730
|
+
const stat = await fs4.stat(scanPath).catch(() => null);
|
|
1682
1731
|
if (!stat || !stat.isDirectory()) {
|
|
1683
1732
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
1684
1733
|
process.exit(2);
|