codebyplan 1.4.1 → 1.4.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/README.md +8 -7
- package/dist/cli.js +582 -157
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ npx codebyplan setup
|
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
This will:
|
|
12
|
+
|
|
12
13
|
1. Prompt for your API key (get one at [codebyplan.com/settings/api-keys](https://codebyplan.com/settings/api-keys))
|
|
13
14
|
2. Configure Claude Code to connect via remote MCP
|
|
14
15
|
3. Optionally link a repository and run the first sync
|
|
@@ -25,13 +26,13 @@ Bidirectional sync of `.claude/` infrastructure files between your local project
|
|
|
25
26
|
|
|
26
27
|
**Options:**
|
|
27
28
|
|
|
28
|
-
| Flag
|
|
29
|
-
|
|
30
|
-
| `--path <dir>`
|
|
29
|
+
| Flag | Description |
|
|
30
|
+
| ------------------ | ------------------------------------------------ |
|
|
31
|
+
| `--path <dir>` | Project root directory (default: cwd) |
|
|
31
32
|
| `--repo-id <uuid>` | Repository ID (default: from `.codebyplan.json`) |
|
|
32
|
-
| `--dry-run`
|
|
33
|
-
| `--force`
|
|
34
|
-
| `--fix`
|
|
33
|
+
| `--dry-run` | Preview changes without writing |
|
|
34
|
+
| `--force` | Skip confirmation and conflict prompts |
|
|
35
|
+
| `--fix` | Auto-create missing port allocations |
|
|
35
36
|
|
|
36
37
|
### `codebyplan help`
|
|
37
38
|
|
|
@@ -49,7 +50,7 @@ Claude Code connects to CodeByPlan via a remote MCP server. The `setup` command
|
|
|
49
50
|
{
|
|
50
51
|
"mcpServers": {
|
|
51
52
|
"codebyplan": {
|
|
52
|
-
"url": "https://codebyplan.com/mcp",
|
|
53
|
+
"url": "https://www.codebyplan.com/mcp",
|
|
53
54
|
"headers": { "x-api-key": "your-api-key" }
|
|
54
55
|
}
|
|
55
56
|
}
|
package/dist/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ var VERSION, PACKAGE_NAME;
|
|
|
14
14
|
var init_version = __esm({
|
|
15
15
|
"src/lib/version.ts"() {
|
|
16
16
|
"use strict";
|
|
17
|
-
VERSION = "1.4.
|
|
17
|
+
VERSION = "1.4.2";
|
|
18
18
|
PACKAGE_NAME = "codebyplan";
|
|
19
19
|
}
|
|
20
20
|
});
|
|
@@ -52,7 +52,7 @@ function isRetryable(err) {
|
|
|
52
52
|
return false;
|
|
53
53
|
}
|
|
54
54
|
function delay(ms) {
|
|
55
|
-
return new Promise((
|
|
55
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
56
56
|
}
|
|
57
57
|
async function request(method, path, options) {
|
|
58
58
|
const url = buildUrl(path, options?.params);
|
|
@@ -126,7 +126,7 @@ var init_api = __esm({
|
|
|
126
126
|
"use strict";
|
|
127
127
|
init_version();
|
|
128
128
|
API_KEY = process.env.CODEBYPLAN_API_KEY ?? "";
|
|
129
|
-
BASE_URL = (process.env.CODEBYPLAN_API_URL ?? "https://codebyplan.com").replace(/\/$/, "");
|
|
129
|
+
BASE_URL = (process.env.CODEBYPLAN_API_URL ?? "https://www.codebyplan.com").replace(/\/$/, "");
|
|
130
130
|
REQUEST_TIMEOUT_MS = 12e4;
|
|
131
131
|
MAX_RETRIES = 3;
|
|
132
132
|
BASE_DELAY_MS = 1e3;
|
|
@@ -211,11 +211,14 @@ var init_settings_merge = __esm({
|
|
|
211
211
|
import { readdir, readFile } from "node:fs/promises";
|
|
212
212
|
import { join } from "node:path";
|
|
213
213
|
function parseHookMeta(content) {
|
|
214
|
-
const
|
|
215
|
-
if (!
|
|
214
|
+
const lineMatch = content.match(/^#\s*@hook:(.*)$/m);
|
|
215
|
+
if (!lineMatch) return null;
|
|
216
|
+
const parts = lineMatch[1].trim().split(/\s+/);
|
|
217
|
+
const event = parts[0];
|
|
218
|
+
if (!event) return null;
|
|
216
219
|
return {
|
|
217
|
-
event
|
|
218
|
-
matcher:
|
|
220
|
+
event,
|
|
221
|
+
matcher: parts.slice(1).join(" ")
|
|
219
222
|
};
|
|
220
223
|
}
|
|
221
224
|
async function discoverHooks(hooksDir) {
|
|
@@ -550,7 +553,7 @@ async function executeSyncToLocal(options) {
|
|
|
550
553
|
const substituted = substituteVariables(remote.content, repoData);
|
|
551
554
|
remotePathMap.set(relPath, {
|
|
552
555
|
content: substituted,
|
|
553
|
-
name: `${remote.category
|
|
556
|
+
name: remote.category ? `${remote.category}/${remote.name}` : remote.name
|
|
554
557
|
});
|
|
555
558
|
}
|
|
556
559
|
for (const [relPath, { content, name }] of remotePathMap) {
|
|
@@ -793,7 +796,7 @@ async function readConfig(path) {
|
|
|
793
796
|
}
|
|
794
797
|
}
|
|
795
798
|
function buildMcpEntry(apiKey) {
|
|
796
|
-
const baseUrl = process.env.CODEBYPLAN_API_URL ?? "https://codebyplan.com";
|
|
799
|
+
const baseUrl = process.env.CODEBYPLAN_API_URL ?? "https://www.codebyplan.com";
|
|
797
800
|
return {
|
|
798
801
|
url: `${baseUrl}/mcp`,
|
|
799
802
|
headers: { "x-api-key": apiKey }
|
|
@@ -835,7 +838,7 @@ async function runSetup() {
|
|
|
835
838
|
return;
|
|
836
839
|
}
|
|
837
840
|
console.log("\n Validating API key...");
|
|
838
|
-
const baseUrl = process.env.CODEBYPLAN_API_URL ?? "https://codebyplan.com";
|
|
841
|
+
const baseUrl = process.env.CODEBYPLAN_API_URL ?? "https://www.codebyplan.com";
|
|
839
842
|
const res = await fetch(`${baseUrl}/api/repos`, {
|
|
840
843
|
headers: { "x-api-key": apiKey },
|
|
841
844
|
signal: AbortSignal.timeout(1e4)
|
|
@@ -887,7 +890,7 @@ async function runSetup() {
|
|
|
887
890
|
`
|
|
888
891
|
);
|
|
889
892
|
console.log(
|
|
890
|
-
` { "url": "https://codebyplan.com/mcp", "headers": { "x-api-key": "${apiKey}" } }
|
|
893
|
+
` { "url": "https://www.codebyplan.com/mcp", "headers": { "x-api-key": "${apiKey}" } }
|
|
891
894
|
`
|
|
892
895
|
);
|
|
893
896
|
}
|
|
@@ -971,14 +974,30 @@ var init_setup = __esm({
|
|
|
971
974
|
|
|
972
975
|
// src/cli/config.ts
|
|
973
976
|
import { readFile as readFile4 } from "node:fs/promises";
|
|
974
|
-
import { join as join4 } from "node:path";
|
|
977
|
+
import { join as join4, resolve } from "node:path";
|
|
978
|
+
async function findCodebyplanConfig(startDir, maxDepth = 20) {
|
|
979
|
+
let cursor = resolve(startDir);
|
|
980
|
+
for (let depth = 0; depth < maxDepth; depth++) {
|
|
981
|
+
const configPath = join4(cursor, ".codebyplan.json");
|
|
982
|
+
try {
|
|
983
|
+
const raw = await readFile4(configPath, "utf-8");
|
|
984
|
+
const parsed = JSON.parse(raw);
|
|
985
|
+
return { path: configPath, contents: parsed };
|
|
986
|
+
} catch {
|
|
987
|
+
}
|
|
988
|
+
const parent = resolve(cursor, "..");
|
|
989
|
+
if (parent === cursor) return null;
|
|
990
|
+
cursor = parent;
|
|
991
|
+
}
|
|
992
|
+
return null;
|
|
993
|
+
}
|
|
975
994
|
function parseFlags(startIndex) {
|
|
976
995
|
const flags = {};
|
|
977
996
|
const args = process.argv.slice(startIndex);
|
|
978
997
|
for (let i = 0; i < args.length; i++) {
|
|
979
|
-
const
|
|
980
|
-
if (
|
|
981
|
-
const key =
|
|
998
|
+
const arg = args[i];
|
|
999
|
+
if (arg.startsWith("--") && i + 1 < args.length) {
|
|
1000
|
+
const key = arg.slice(2);
|
|
982
1001
|
flags[key] = args[++i];
|
|
983
1002
|
}
|
|
984
1003
|
}
|
|
@@ -992,13 +1011,10 @@ async function resolveConfig(flags) {
|
|
|
992
1011
|
let repoId = flags["repo-id"] ?? process.env.CODEBYPLAN_REPO_ID;
|
|
993
1012
|
let worktreeId = flags["worktree-id"] ?? process.env.CODEBYPLAN_WORKTREE_ID;
|
|
994
1013
|
if (!repoId || !worktreeId) {
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
if (!repoId) repoId = config.repo_id;
|
|
1000
|
-
if (!worktreeId) worktreeId = config.worktree_id;
|
|
1001
|
-
} catch {
|
|
1014
|
+
const found = await findCodebyplanConfig(projectPath);
|
|
1015
|
+
if (found) {
|
|
1016
|
+
if (!repoId) repoId = found.contents.repo_id;
|
|
1017
|
+
if (!worktreeId) worktreeId = found.contents.worktree_id;
|
|
1002
1018
|
}
|
|
1003
1019
|
}
|
|
1004
1020
|
if (!repoId) {
|
|
@@ -1633,12 +1649,77 @@ async function discoverMonorepoApps(projectPath) {
|
|
|
1633
1649
|
}
|
|
1634
1650
|
return apps;
|
|
1635
1651
|
}
|
|
1652
|
+
async function hasJsxFile(dir, depth = 0) {
|
|
1653
|
+
if (depth > 6) return false;
|
|
1654
|
+
try {
|
|
1655
|
+
const entries = await readdir4(dir, { withFileTypes: true });
|
|
1656
|
+
for (const entry of entries) {
|
|
1657
|
+
const name = entry.name;
|
|
1658
|
+
if (entry.isDirectory()) {
|
|
1659
|
+
if (SKIP_DIRS.has(name) || JSX_SKIP_DIRS.has(name)) continue;
|
|
1660
|
+
if (await hasJsxFile(join6(dir, name), depth + 1)) return true;
|
|
1661
|
+
} else if (entry.isFile()) {
|
|
1662
|
+
if (JSX_TEST_PATTERN.test(name)) continue;
|
|
1663
|
+
if (name.endsWith(".tsx") || name.endsWith(".jsx")) return true;
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
} catch (err) {
|
|
1667
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
1668
|
+
return false;
|
|
1669
|
+
}
|
|
1670
|
+
console.error(
|
|
1671
|
+
`detectCapabilities: readdir failed for ${dir}: ${err instanceof Error ? err.message : String(err)}`
|
|
1672
|
+
);
|
|
1673
|
+
}
|
|
1674
|
+
return false;
|
|
1675
|
+
}
|
|
1676
|
+
async function detectCapabilities(dirPath, pkgJson) {
|
|
1677
|
+
const caps = /* @__PURE__ */ new Set();
|
|
1678
|
+
for (const sub of JSX_SCAN_DIRS) {
|
|
1679
|
+
if (await hasJsxFile(join6(dirPath, sub))) {
|
|
1680
|
+
caps.add("jsx");
|
|
1681
|
+
break;
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
if (pkgJson) {
|
|
1685
|
+
const allDeps = {
|
|
1686
|
+
...pkgJson.dependencies ?? {},
|
|
1687
|
+
...pkgJson.devDependencies ?? {}
|
|
1688
|
+
};
|
|
1689
|
+
for (const dep of Object.keys(allDeps)) {
|
|
1690
|
+
if (SERVER_FRAMEWORK_DEPS.has(dep)) {
|
|
1691
|
+
caps.add("node-server");
|
|
1692
|
+
break;
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
if (!caps.has("node-server") && typeof pkgJson.main === "string") {
|
|
1696
|
+
if (SERVER_MAIN_ENTRIES.has(pkgJson.main.trim())) {
|
|
1697
|
+
caps.add("node-server");
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
if (!caps.has("node-server") && await fileExists(join6(dirPath, "src", "main.ts"))) {
|
|
1702
|
+
caps.add("node-server");
|
|
1703
|
+
}
|
|
1704
|
+
if (pkgJson && pkgJson.bin) {
|
|
1705
|
+
if (typeof pkgJson.bin === "string" && pkgJson.bin.trim().length > 0) {
|
|
1706
|
+
caps.add("cli-bin");
|
|
1707
|
+
} else if (typeof pkgJson.bin === "object" && pkgJson.bin !== null && Object.keys(pkgJson.bin).length > 0) {
|
|
1708
|
+
caps.add("cli-bin");
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
return caps;
|
|
1712
|
+
}
|
|
1636
1713
|
async function detectFromDirectory(dirPath) {
|
|
1637
1714
|
const seen = /* @__PURE__ */ new Map();
|
|
1715
|
+
let pkgJson = null;
|
|
1638
1716
|
try {
|
|
1639
1717
|
const raw = await readFile6(join6(dirPath, "package.json"), "utf-8");
|
|
1640
|
-
|
|
1641
|
-
const allDeps = {
|
|
1718
|
+
pkgJson = JSON.parse(raw);
|
|
1719
|
+
const allDeps = {
|
|
1720
|
+
...pkgJson.dependencies ?? {},
|
|
1721
|
+
...pkgJson.devDependencies ?? {}
|
|
1722
|
+
};
|
|
1642
1723
|
for (const depName of Object.keys(allDeps)) {
|
|
1643
1724
|
const rule = PACKAGE_MAP[depName];
|
|
1644
1725
|
if (rule) {
|
|
@@ -1669,11 +1750,40 @@ async function detectFromDirectory(dirPath) {
|
|
|
1669
1750
|
seen.set(key, { name: rule.name, category: rule.category });
|
|
1670
1751
|
}
|
|
1671
1752
|
}
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1753
|
+
const capabilities = await detectCapabilities(dirPath, pkgJson);
|
|
1754
|
+
const capsArray = Array.from(capabilities).sort();
|
|
1755
|
+
const entries = Array.from(seen.values()).map((entry) => {
|
|
1756
|
+
const isBearer = CAPABILITY_BEARER_NAMES.has(entry.name.toLowerCase());
|
|
1757
|
+
return {
|
|
1758
|
+
...entry,
|
|
1759
|
+
capabilities: isBearer ? capsArray : []
|
|
1760
|
+
};
|
|
1676
1761
|
});
|
|
1762
|
+
if (capsArray.length > 0) {
|
|
1763
|
+
const hasBearerWithCaps = entries.some(
|
|
1764
|
+
(e) => CAPABILITY_BEARER_NAMES.has(e.name.toLowerCase()) && (e.capabilities?.some((c) => c.length > 0) ?? false)
|
|
1765
|
+
);
|
|
1766
|
+
if (!hasBearerWithCaps) {
|
|
1767
|
+
entries.push({
|
|
1768
|
+
name: SYNTHETIC_CARRIER_NAME,
|
|
1769
|
+
category: "tool",
|
|
1770
|
+
capabilities: capsArray
|
|
1771
|
+
});
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
return entries.sort(compareByCategoryThenName);
|
|
1775
|
+
}
|
|
1776
|
+
function collectCapabilities(entries) {
|
|
1777
|
+
const set = /* @__PURE__ */ new Set();
|
|
1778
|
+
for (const entry of entries) {
|
|
1779
|
+
if (!entry.capabilities) continue;
|
|
1780
|
+
for (const cap of entry.capabilities) {
|
|
1781
|
+
if (typeof cap === "string" && cap.length > 0) {
|
|
1782
|
+
set.add(cap.toLowerCase());
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
return Array.from(set).sort();
|
|
1677
1787
|
}
|
|
1678
1788
|
async function detectTechStack(projectPath) {
|
|
1679
1789
|
const repo = await detectFromDirectory(projectPath);
|
|
@@ -1692,40 +1802,80 @@ async function detectTechStack(projectPath) {
|
|
|
1692
1802
|
for (const app of apps) {
|
|
1693
1803
|
for (const entry of app.stack) {
|
|
1694
1804
|
const key = entry.name.toLowerCase();
|
|
1695
|
-
|
|
1805
|
+
const existing = flatMap.get(key);
|
|
1806
|
+
if (!existing) {
|
|
1696
1807
|
flatMap.set(key, entry);
|
|
1808
|
+
} else if (entry.capabilities?.length) {
|
|
1809
|
+
const merged = Array.from(
|
|
1810
|
+
/* @__PURE__ */ new Set([...existing.capabilities ?? [], ...entry.capabilities])
|
|
1811
|
+
).sort();
|
|
1812
|
+
flatMap.set(key, { ...existing, capabilities: merged });
|
|
1697
1813
|
}
|
|
1698
1814
|
}
|
|
1699
1815
|
}
|
|
1700
|
-
const
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1816
|
+
const repoCleaned = stripSyntheticIfCovered(repo).sort(
|
|
1817
|
+
compareByCategoryThenName
|
|
1818
|
+
);
|
|
1819
|
+
const appsCleaned = apps.map((a) => ({
|
|
1820
|
+
...a,
|
|
1821
|
+
stack: stripSyntheticIfCovered(a.stack).sort(compareByCategoryThenName)
|
|
1822
|
+
}));
|
|
1823
|
+
const flat = stripSyntheticIfCovered(Array.from(flatMap.values())).sort(
|
|
1824
|
+
compareByCategoryThenName
|
|
1825
|
+
);
|
|
1826
|
+
return { repo: repoCleaned, apps: appsCleaned, flat };
|
|
1827
|
+
}
|
|
1828
|
+
function stripSyntheticIfCovered(entries) {
|
|
1829
|
+
const synth = entries.find((e) => e.name === SYNTHETIC_CARRIER_NAME);
|
|
1830
|
+
if (!synth?.capabilities?.length) return entries;
|
|
1831
|
+
const realBearerCaps = /* @__PURE__ */ new Set();
|
|
1832
|
+
for (const e of entries) {
|
|
1833
|
+
if (e.name === SYNTHETIC_CARRIER_NAME) continue;
|
|
1834
|
+
for (const c of e.capabilities ?? []) realBearerCaps.add(c);
|
|
1835
|
+
}
|
|
1836
|
+
if (synth.capabilities.every((c) => realBearerCaps.has(c))) {
|
|
1837
|
+
return entries.filter((e) => e.name !== SYNTHETIC_CARRIER_NAME);
|
|
1838
|
+
}
|
|
1839
|
+
return entries;
|
|
1706
1840
|
}
|
|
1707
1841
|
function mergeTechStack(remote, detected) {
|
|
1708
|
-
const
|
|
1842
|
+
const stripCarrier = (e) => e.name !== SYNTHETIC_CARRIER_NAME;
|
|
1843
|
+
const cleanDetected = {
|
|
1844
|
+
repo: detected.repo.filter(stripCarrier),
|
|
1845
|
+
apps: detected.apps.map((a) => ({
|
|
1846
|
+
...a,
|
|
1847
|
+
stack: a.stack.filter(stripCarrier)
|
|
1848
|
+
})),
|
|
1849
|
+
flat: detected.flat.filter(stripCarrier)
|
|
1850
|
+
};
|
|
1851
|
+
const remoteResult = Array.isArray(remote) ? {
|
|
1852
|
+
repo: remote.filter(stripCarrier),
|
|
1853
|
+
apps: [],
|
|
1854
|
+
flat: remote.filter(stripCarrier)
|
|
1855
|
+
} : {
|
|
1856
|
+
repo: remote.repo.filter(stripCarrier),
|
|
1857
|
+
apps: remote.apps.map((a) => ({
|
|
1858
|
+
...a,
|
|
1859
|
+
stack: a.stack.filter(stripCarrier)
|
|
1860
|
+
})),
|
|
1861
|
+
flat: remote.flat.filter(stripCarrier)
|
|
1862
|
+
};
|
|
1709
1863
|
const seen = /* @__PURE__ */ new Map();
|
|
1710
1864
|
for (const entry of remoteResult.flat) {
|
|
1711
1865
|
seen.set(entry.name.toLowerCase(), entry);
|
|
1712
1866
|
}
|
|
1713
1867
|
const added = [];
|
|
1714
|
-
for (const entry of
|
|
1868
|
+
for (const entry of cleanDetected.flat) {
|
|
1715
1869
|
const key = entry.name.toLowerCase();
|
|
1716
1870
|
if (!seen.has(key)) {
|
|
1717
1871
|
seen.set(key, entry);
|
|
1718
1872
|
added.push(entry);
|
|
1719
1873
|
}
|
|
1720
1874
|
}
|
|
1721
|
-
const flat = Array.from(seen.values()).sort(
|
|
1722
|
-
const catCmp = a.category.localeCompare(b.category);
|
|
1723
|
-
if (catCmp !== 0) return catCmp;
|
|
1724
|
-
return a.name.localeCompare(b.name);
|
|
1725
|
-
});
|
|
1875
|
+
const flat = Array.from(seen.values()).sort(compareByCategoryThenName);
|
|
1726
1876
|
const merged = {
|
|
1727
|
-
repo:
|
|
1728
|
-
apps:
|
|
1877
|
+
repo: cleanDetected.repo,
|
|
1878
|
+
apps: cleanDetected.apps,
|
|
1729
1879
|
flat
|
|
1730
1880
|
};
|
|
1731
1881
|
return { merged, added };
|
|
@@ -1825,7 +1975,7 @@ async function scanAllDependencies(projectPath) {
|
|
|
1825
1975
|
}
|
|
1826
1976
|
return { dependencies };
|
|
1827
1977
|
}
|
|
1828
|
-
var PACKAGE_MAP, PACKAGE_PREFIX_MAP, CONFIG_FILE_MAP, SKIP_DIRS;
|
|
1978
|
+
var PACKAGE_MAP, PACKAGE_PREFIX_MAP, CONFIG_FILE_MAP, SYNTHETIC_CARRIER_NAME, CAPABILITY_BEARER_NAMES, JSX_TEST_PATTERN, JSX_SKIP_DIRS, JSX_SCAN_DIRS, SERVER_MAIN_ENTRIES, SERVER_FRAMEWORK_DEPS, compareByCategoryThenName, SKIP_DIRS;
|
|
1829
1979
|
var init_tech_detect = __esm({
|
|
1830
1980
|
"src/lib/tech-detect.ts"() {
|
|
1831
1981
|
"use strict";
|
|
@@ -1841,6 +1991,7 @@ var init_tech_detect = __esm({
|
|
|
1841
1991
|
svelte: { name: "Svelte", category: "framework" },
|
|
1842
1992
|
astro: { name: "Astro", category: "framework" },
|
|
1843
1993
|
"@angular/core": { name: "Angular", category: "framework" },
|
|
1994
|
+
"@nestjs/core": { name: "NestJS", category: "framework" },
|
|
1844
1995
|
// Libraries (UI)
|
|
1845
1996
|
react: { name: "React", category: "framework" },
|
|
1846
1997
|
vue: { name: "Vue", category: "framework" },
|
|
@@ -1955,6 +2106,47 @@ var init_tech_detect = __esm({
|
|
|
1955
2106
|
{ file: "nx.json", rule: { name: "Nx", category: "build" } },
|
|
1956
2107
|
{ file: "lerna.json", rule: { name: "Lerna", category: "build" } }
|
|
1957
2108
|
];
|
|
2109
|
+
SYNTHETIC_CARRIER_NAME = "__capabilities__";
|
|
2110
|
+
CAPABILITY_BEARER_NAMES = /* @__PURE__ */ new Set([
|
|
2111
|
+
"react",
|
|
2112
|
+
"next.js",
|
|
2113
|
+
"vue",
|
|
2114
|
+
"svelte",
|
|
2115
|
+
"solid",
|
|
2116
|
+
"preact",
|
|
2117
|
+
"remix",
|
|
2118
|
+
"astro",
|
|
2119
|
+
"angular",
|
|
2120
|
+
"nestjs",
|
|
2121
|
+
"nuxt",
|
|
2122
|
+
"gatsby",
|
|
2123
|
+
"express",
|
|
2124
|
+
"fastify",
|
|
2125
|
+
"hono",
|
|
2126
|
+
"react native",
|
|
2127
|
+
"expo"
|
|
2128
|
+
]);
|
|
2129
|
+
JSX_TEST_PATTERN = /\.(test|spec)\.(tsx|jsx)$/;
|
|
2130
|
+
JSX_SKIP_DIRS = /* @__PURE__ */ new Set(["__tests__", "test", "tests"]);
|
|
2131
|
+
JSX_SCAN_DIRS = ["src", "app", "pages"];
|
|
2132
|
+
SERVER_MAIN_ENTRIES = /* @__PURE__ */ new Set([
|
|
2133
|
+
"dist/main.js",
|
|
2134
|
+
"dist/server.js",
|
|
2135
|
+
"dist/index.js",
|
|
2136
|
+
"main.js",
|
|
2137
|
+
"server.js"
|
|
2138
|
+
]);
|
|
2139
|
+
SERVER_FRAMEWORK_DEPS = /* @__PURE__ */ new Set([
|
|
2140
|
+
"@nestjs/core",
|
|
2141
|
+
"express",
|
|
2142
|
+
"fastify",
|
|
2143
|
+
"hono"
|
|
2144
|
+
]);
|
|
2145
|
+
compareByCategoryThenName = (a, b) => {
|
|
2146
|
+
const catCmp = a.category.localeCompare(b.category);
|
|
2147
|
+
if (catCmp !== 0) return catCmp;
|
|
2148
|
+
return a.name.localeCompare(b.name);
|
|
2149
|
+
};
|
|
1958
2150
|
SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
1959
2151
|
"node_modules",
|
|
1960
2152
|
".next",
|
|
@@ -2032,30 +2224,65 @@ async function verifyPorts(projectPath, portAllocations) {
|
|
|
2032
2224
|
}
|
|
2033
2225
|
return mismatches;
|
|
2034
2226
|
}
|
|
2227
|
+
function isDevServerScript(pkg) {
|
|
2228
|
+
const scripts = pkg.scripts;
|
|
2229
|
+
const raw = scripts?.dev;
|
|
2230
|
+
if (!raw || typeof raw !== "string") return false;
|
|
2231
|
+
const script = raw.trim().toLowerCase();
|
|
2232
|
+
if (!script) return false;
|
|
2233
|
+
for (const pattern of DEV_SERVER_BIN_PATTERNS) {
|
|
2234
|
+
if (pattern.test(script)) return true;
|
|
2235
|
+
}
|
|
2236
|
+
const tokens = script.split(/\s+/);
|
|
2237
|
+
for (const token of tokens) {
|
|
2238
|
+
if (token === "--port" || token === "-p") return true;
|
|
2239
|
+
if (token.startsWith("--port=")) return true;
|
|
2240
|
+
}
|
|
2241
|
+
return false;
|
|
2242
|
+
}
|
|
2243
|
+
function labelMatchesAppName(label, appName) {
|
|
2244
|
+
if (!label || !appName) return false;
|
|
2245
|
+
const normalize = (s) => s.toLowerCase().replace(/-/g, " ").replace(/[()]/g, " ").replace(/\s+/g, " ").trim();
|
|
2246
|
+
const labelTokens = normalize(label).split(" ").filter(Boolean);
|
|
2247
|
+
const appToken = normalize(appName);
|
|
2248
|
+
if (!appToken) return false;
|
|
2249
|
+
const appTokens = appToken.split(" ").filter(Boolean);
|
|
2250
|
+
if (appTokens.length === 1) {
|
|
2251
|
+
return labelTokens.includes(appTokens[0]);
|
|
2252
|
+
}
|
|
2253
|
+
for (let i = 0; i <= labelTokens.length - appTokens.length; i++) {
|
|
2254
|
+
if (appTokens.every((t, j) => labelTokens[i + j] === t)) return true;
|
|
2255
|
+
}
|
|
2256
|
+
return false;
|
|
2257
|
+
}
|
|
2035
2258
|
async function findUnallocatedApps(projectPath, portAllocations) {
|
|
2036
2259
|
const apps = await discoverMonorepoApps(projectPath);
|
|
2037
2260
|
if (apps.length === 0) {
|
|
2038
2261
|
return [];
|
|
2039
2262
|
}
|
|
2040
|
-
const allocatedLabels = new Set(portAllocations.map((a) => a.label));
|
|
2041
2263
|
const unallocated = [];
|
|
2042
2264
|
for (const app of apps) {
|
|
2043
|
-
if (
|
|
2265
|
+
if (portAllocations.some((a) => labelMatchesAppName(a.label ?? "", app.name))) {
|
|
2266
|
+
continue;
|
|
2267
|
+
}
|
|
2268
|
+
let pkg;
|
|
2044
2269
|
try {
|
|
2045
2270
|
const raw = await readFile7(`${app.absPath}/package.json`, "utf-8");
|
|
2046
|
-
|
|
2047
|
-
const framework = detectFramework(pkg);
|
|
2048
|
-
const detectedPort = detectPortFromScripts(pkg);
|
|
2049
|
-
const command = `pnpm --filter ${app.name} dev`;
|
|
2050
|
-
unallocated.push({
|
|
2051
|
-
name: app.name,
|
|
2052
|
-
path: app.path,
|
|
2053
|
-
framework,
|
|
2054
|
-
detectedPort,
|
|
2055
|
-
command
|
|
2056
|
-
});
|
|
2271
|
+
pkg = JSON.parse(raw);
|
|
2057
2272
|
} catch {
|
|
2273
|
+
continue;
|
|
2058
2274
|
}
|
|
2275
|
+
if (!isDevServerScript(pkg)) continue;
|
|
2276
|
+
const framework = detectFramework(pkg);
|
|
2277
|
+
const detectedPort = detectPortFromScripts(pkg);
|
|
2278
|
+
const command = `pnpm --filter ${app.name} dev`;
|
|
2279
|
+
unallocated.push({
|
|
2280
|
+
name: app.name,
|
|
2281
|
+
path: app.path,
|
|
2282
|
+
framework,
|
|
2283
|
+
detectedPort,
|
|
2284
|
+
command
|
|
2285
|
+
});
|
|
2059
2286
|
}
|
|
2060
2287
|
return unallocated;
|
|
2061
2288
|
}
|
|
@@ -2066,16 +2293,46 @@ function getAppLabel(relativePath) {
|
|
|
2066
2293
|
}
|
|
2067
2294
|
return "root";
|
|
2068
2295
|
}
|
|
2296
|
+
var DEV_SERVER_BIN_PATTERNS;
|
|
2069
2297
|
var init_port_verify = __esm({
|
|
2070
2298
|
"src/lib/port-verify.ts"() {
|
|
2071
2299
|
"use strict";
|
|
2072
2300
|
init_tech_detect();
|
|
2073
2301
|
init_server_detect();
|
|
2302
|
+
DEV_SERVER_BIN_PATTERNS = [
|
|
2303
|
+
/\bnext\s+dev\b/,
|
|
2304
|
+
/\bnest\s+start\b/,
|
|
2305
|
+
/\bvite\s+(?:dev|serve)\b/,
|
|
2306
|
+
/\bvite\s+preview\b/,
|
|
2307
|
+
/\bnuxt\s+dev\b/,
|
|
2308
|
+
/\b(?:svelte-kit|sveltekit)\s+dev\b/,
|
|
2309
|
+
/\bexpo\s+start\b/
|
|
2310
|
+
];
|
|
2074
2311
|
}
|
|
2075
2312
|
});
|
|
2076
2313
|
|
|
2077
2314
|
// src/lib/eslint-generator.ts
|
|
2078
2315
|
import { createHash } from "node:crypto";
|
|
2316
|
+
function importedIdentifiers(importLines) {
|
|
2317
|
+
const names = /* @__PURE__ */ new Set();
|
|
2318
|
+
for (const line of importLines) {
|
|
2319
|
+
let m = line.match(/^import\s+([A-Za-z_$][\w$]*)\s+from/);
|
|
2320
|
+
if (m) names.add(m[1]);
|
|
2321
|
+
m = line.match(/^import\s+\*\s+as\s+([A-Za-z_$][\w$]*)\s+from/);
|
|
2322
|
+
if (m) names.add(m[1]);
|
|
2323
|
+
m = line.match(/^import\s*\{([^}]*)\}\s*from/);
|
|
2324
|
+
if (m) {
|
|
2325
|
+
for (const entry of m[1].split(",")) {
|
|
2326
|
+
const parts = entry.trim().split(/\s+as\s+/);
|
|
2327
|
+
const n = (parts[1] ?? parts[0]).trim();
|
|
2328
|
+
if (n) names.add(n);
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
m = line.match(/^const\s+([A-Za-z_$][\w$]*)\s*=\s*require/);
|
|
2332
|
+
if (m) names.add(m[1]);
|
|
2333
|
+
}
|
|
2334
|
+
return names;
|
|
2335
|
+
}
|
|
2079
2336
|
function parseFragment(fragment) {
|
|
2080
2337
|
if (!fragment) return { imports: [], configComments: [] };
|
|
2081
2338
|
const lines = fragment.split("\n");
|
|
@@ -2125,7 +2382,7 @@ function collectDependencies(presets) {
|
|
|
2125
2382
|
function hashConfig(content) {
|
|
2126
2383
|
return createHash("sha256").update(content).digest("hex");
|
|
2127
2384
|
}
|
|
2128
|
-
function buildRules(presets
|
|
2385
|
+
function buildRules(presets) {
|
|
2129
2386
|
const merged = {};
|
|
2130
2387
|
for (const preset of presets) {
|
|
2131
2388
|
const rules = preset.rules;
|
|
@@ -2133,11 +2390,30 @@ function buildRules(presets, userOverrides) {
|
|
|
2133
2390
|
Object.assign(merged, rules);
|
|
2134
2391
|
}
|
|
2135
2392
|
}
|
|
2136
|
-
if (userOverrides) {
|
|
2137
|
-
Object.assign(merged, userOverrides);
|
|
2138
|
-
}
|
|
2139
2393
|
return merged;
|
|
2140
2394
|
}
|
|
2395
|
+
function splitRulesByPlugin(rules, hasNextJs) {
|
|
2396
|
+
const reactHooks = {};
|
|
2397
|
+
const reactOrA11y = {};
|
|
2398
|
+
const importPlugin = {};
|
|
2399
|
+
const generic = {};
|
|
2400
|
+
for (const [key, value] of Object.entries(rules)) {
|
|
2401
|
+
if (key.startsWith("react-hooks/") || key.startsWith("react-compiler/")) {
|
|
2402
|
+
reactHooks[key] = value;
|
|
2403
|
+
} else if (key.startsWith("react/") || key.startsWith("jsx-a11y/")) {
|
|
2404
|
+
reactOrA11y[key] = value;
|
|
2405
|
+
} else if (key.startsWith("import/")) {
|
|
2406
|
+
if (hasNextJs) {
|
|
2407
|
+
importPlugin[key] = value;
|
|
2408
|
+
} else {
|
|
2409
|
+
generic[key] = value;
|
|
2410
|
+
}
|
|
2411
|
+
} else {
|
|
2412
|
+
generic[key] = value;
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
return { reactHooks, reactOrA11y, importPlugin, generic };
|
|
2416
|
+
}
|
|
2141
2417
|
function formatRules(rules, indent) {
|
|
2142
2418
|
const entries = Object.entries(rules);
|
|
2143
2419
|
if (entries.length === 0) return "{}";
|
|
@@ -2151,6 +2427,7 @@ ${indent}}`;
|
|
|
2151
2427
|
}
|
|
2152
2428
|
function generateEslintConfig(input) {
|
|
2153
2429
|
const { presets, ruleOverrides, tsconfigRootDir, ignorePatterns } = input;
|
|
2430
|
+
const hasNextJs = presets.some((p) => p.is_system && p.name === "nextjs");
|
|
2154
2431
|
const allImports = [];
|
|
2155
2432
|
const allConfigComments = [];
|
|
2156
2433
|
for (const preset of presets) {
|
|
@@ -2163,7 +2440,14 @@ function generateEslintConfig(input) {
|
|
|
2163
2440
|
const dedupedImports = deduplicateImports(allImports);
|
|
2164
2441
|
const tsCheck = dedupedImports.find((i) => i === "// @ts-check");
|
|
2165
2442
|
const importLines = dedupedImports.filter((i) => i !== "// @ts-check");
|
|
2166
|
-
const
|
|
2443
|
+
const presetRules = buildRules(presets);
|
|
2444
|
+
const userOverrides = ruleOverrides ?? {};
|
|
2445
|
+
const presetRulesClean = Object.fromEntries(
|
|
2446
|
+
Object.entries(presetRules).filter(
|
|
2447
|
+
([k]) => !Object.hasOwn(userOverrides, k)
|
|
2448
|
+
)
|
|
2449
|
+
);
|
|
2450
|
+
const splitRules = splitRulesByPlugin(presetRulesClean, hasNextJs);
|
|
2167
2451
|
const defaultIgnores = [
|
|
2168
2452
|
"eslint.config.mjs",
|
|
2169
2453
|
"node_modules/**",
|
|
@@ -2174,7 +2458,24 @@ function generateEslintConfig(input) {
|
|
|
2174
2458
|
const ignores = [.../* @__PURE__ */ new Set([...defaultIgnores, ...ignorePatterns ?? []])];
|
|
2175
2459
|
const rootDir = tsconfigRootDir ?? "import.meta.dirname";
|
|
2176
2460
|
const rootDirValue = rootDir === "import.meta.dirname" ? "import.meta.dirname" : `"${rootDir}"`;
|
|
2177
|
-
const
|
|
2461
|
+
const KNOWN_SYSTEM_PRESETS = [
|
|
2462
|
+
"base",
|
|
2463
|
+
"react",
|
|
2464
|
+
"nextjs",
|
|
2465
|
+
"node",
|
|
2466
|
+
"cli",
|
|
2467
|
+
"testing",
|
|
2468
|
+
"testing-react",
|
|
2469
|
+
"testing-e2e"
|
|
2470
|
+
];
|
|
2471
|
+
for (const preset of presets) {
|
|
2472
|
+
if (preset.is_system && !KNOWN_SYSTEM_PRESETS.includes(preset.name) && !warnedPresetNames.has(preset.name)) {
|
|
2473
|
+
console.warn(
|
|
2474
|
+
`Unknown system preset: ${preset.name} \u2014 skipping structural emission`
|
|
2475
|
+
);
|
|
2476
|
+
warnedPresetNames.add(preset.name);
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2178
2479
|
const hasReact = presets.some((p) => p.is_system && p.name === "react");
|
|
2179
2480
|
const hasNode = presets.some((p) => p.is_system && p.name === "node");
|
|
2180
2481
|
const hasTesting = presets.some((p) => p.is_system && p.name === "testing");
|
|
@@ -2204,8 +2505,9 @@ function generateEslintConfig(input) {
|
|
|
2204
2505
|
if ((hasNode || hasReact || hasNextJs) && !hasGlobalsImport) {
|
|
2205
2506
|
sections.push('import globals from "globals";');
|
|
2206
2507
|
}
|
|
2508
|
+
sections.push('import { defineConfig } from "eslint/config";');
|
|
2207
2509
|
sections.push("");
|
|
2208
|
-
sections.push("export default [");
|
|
2510
|
+
sections.push("export default defineConfig([");
|
|
2209
2511
|
sections.push(` { ignores: ${JSON.stringify(ignores)} },`);
|
|
2210
2512
|
sections.push("");
|
|
2211
2513
|
const hasBase = presets.some((p) => p.is_system && p.name === "base");
|
|
@@ -2226,11 +2528,43 @@ function generateEslintConfig(input) {
|
|
|
2226
2528
|
sections.push("");
|
|
2227
2529
|
}
|
|
2228
2530
|
if (hasNextJs) {
|
|
2531
|
+
const bindings = importedIdentifiers(importLines);
|
|
2532
|
+
const nextFusedRules = {
|
|
2533
|
+
...splitRules.reactHooks,
|
|
2534
|
+
...splitRules.importPlugin
|
|
2535
|
+
};
|
|
2229
2536
|
sections.push(" // Next.js: Core Web Vitals + TypeScript");
|
|
2230
2537
|
sections.push(" ...nextCoreWebVitals,");
|
|
2231
2538
|
sections.push(" ...nextTypescript,");
|
|
2232
|
-
|
|
2233
|
-
|
|
2539
|
+
if (bindings.has("jsxA11y")) {
|
|
2540
|
+
sections.push(" { rules: jsxA11y.flatConfigs.strict.rules },");
|
|
2541
|
+
} else {
|
|
2542
|
+
console.warn(
|
|
2543
|
+
"eslint-generator: skipping `jsxA11y.flatConfigs.strict.rules` emission \u2014 `jsxA11y` binding missing from nextjs preset config_fragment. See CHK-087 TASK-4/TASK-5."
|
|
2544
|
+
);
|
|
2545
|
+
}
|
|
2546
|
+
if (Object.keys(nextFusedRules).length > 0) {
|
|
2547
|
+
sections.push(" {");
|
|
2548
|
+
sections.push(' plugins: { "react-compiler": reactCompiler },');
|
|
2549
|
+
sections.push(` rules: ${formatRules(nextFusedRules, " ")},`);
|
|
2550
|
+
sections.push(" },");
|
|
2551
|
+
} else {
|
|
2552
|
+
sections.push(' { plugins: { "react-compiler": reactCompiler } },');
|
|
2553
|
+
}
|
|
2554
|
+
if (Object.keys(splitRules.reactOrA11y).length > 0) {
|
|
2555
|
+
if (bindings.has("react") && bindings.has("jsxA11y")) {
|
|
2556
|
+
sections.push(" {");
|
|
2557
|
+
sections.push(' plugins: { react, "jsx-a11y": jsxA11y },');
|
|
2558
|
+
sections.push(
|
|
2559
|
+
` rules: ${formatRules(splitRules.reactOrA11y, " ")},`
|
|
2560
|
+
);
|
|
2561
|
+
sections.push(" },");
|
|
2562
|
+
} else {
|
|
2563
|
+
console.warn(
|
|
2564
|
+
"eslint-generator: skipping defensive reactOrA11y block \u2014 requires both `react` and `jsxA11y` bindings from nextjs preset config_fragment. See CHK-087 TASK-4/TASK-5."
|
|
2565
|
+
);
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2234
2568
|
sections.push("");
|
|
2235
2569
|
}
|
|
2236
2570
|
if (hasReact && !hasNextJs) {
|
|
@@ -2250,6 +2584,12 @@ function generateEslintConfig(input) {
|
|
|
2250
2584
|
sections.push(" rules: {");
|
|
2251
2585
|
sections.push(" ...react.configs.flat.recommended.rules,");
|
|
2252
2586
|
sections.push(' ...react.configs.flat["jsx-runtime"].rules,');
|
|
2587
|
+
for (const [key, value] of Object.entries(splitRules.reactHooks)) {
|
|
2588
|
+
sections.push(` "${key}": ${JSON.stringify(value)},`);
|
|
2589
|
+
}
|
|
2590
|
+
for (const [key, value] of Object.entries(splitRules.reactOrA11y)) {
|
|
2591
|
+
sections.push(` "${key}": ${JSON.stringify(value)},`);
|
|
2592
|
+
}
|
|
2253
2593
|
sections.push(" },");
|
|
2254
2594
|
sections.push(" },");
|
|
2255
2595
|
sections.push(" jsxA11y.flatConfigs.strict,");
|
|
@@ -2270,10 +2610,14 @@ function generateEslintConfig(input) {
|
|
|
2270
2610
|
sections.push(" prettier,");
|
|
2271
2611
|
sections.push("");
|
|
2272
2612
|
}
|
|
2273
|
-
|
|
2613
|
+
const overridesCombined = {
|
|
2614
|
+
...splitRules.generic,
|
|
2615
|
+
...userOverrides
|
|
2616
|
+
};
|
|
2617
|
+
if (Object.keys(overridesCombined).length > 0) {
|
|
2274
2618
|
sections.push(" // Rule overrides");
|
|
2275
2619
|
sections.push(" {");
|
|
2276
|
-
sections.push(` rules: ${formatRules(
|
|
2620
|
+
sections.push(` rules: ${formatRules(overridesCombined, " ")},`);
|
|
2277
2621
|
sections.push(" },");
|
|
2278
2622
|
sections.push("");
|
|
2279
2623
|
}
|
|
@@ -2335,25 +2679,28 @@ function generateEslintConfig(input) {
|
|
|
2335
2679
|
sections.push(" },");
|
|
2336
2680
|
sections.push("");
|
|
2337
2681
|
}
|
|
2338
|
-
sections.push("];");
|
|
2682
|
+
sections.push("]);");
|
|
2339
2683
|
sections.push("");
|
|
2340
2684
|
return sections.join("\n");
|
|
2341
2685
|
}
|
|
2686
|
+
var warnedPresetNames;
|
|
2342
2687
|
var init_eslint_generator = __esm({
|
|
2343
2688
|
"src/lib/eslint-generator.ts"() {
|
|
2344
2689
|
"use strict";
|
|
2690
|
+
warnedPresetNames = /* @__PURE__ */ new Set();
|
|
2345
2691
|
}
|
|
2346
2692
|
});
|
|
2347
2693
|
|
|
2348
2694
|
// src/cli/eslint.ts
|
|
2349
2695
|
var eslint_exports = {};
|
|
2350
2696
|
__export(eslint_exports, {
|
|
2697
|
+
autoDetectIgnorePatterns: () => autoDetectIgnorePatterns,
|
|
2351
2698
|
checkEslintDrift: () => checkEslintDrift,
|
|
2352
2699
|
eslintInit: () => eslintInit,
|
|
2353
2700
|
eslintSync: () => eslintSync,
|
|
2354
2701
|
runEslint: () => runEslint
|
|
2355
2702
|
});
|
|
2356
|
-
import { readFile as readFile8, writeFile as writeFile3, access as access2 } from "node:fs/promises";
|
|
2703
|
+
import { readFile as readFile8, writeFile as writeFile3, access as access2, readdir as readdir5 } from "node:fs/promises";
|
|
2357
2704
|
import { join as join7, relative as relative2 } from "node:path";
|
|
2358
2705
|
async function fileExists2(filePath) {
|
|
2359
2706
|
try {
|
|
@@ -2363,6 +2710,46 @@ async function fileExists2(filePath) {
|
|
|
2363
2710
|
return false;
|
|
2364
2711
|
}
|
|
2365
2712
|
}
|
|
2713
|
+
async function autoDetectIgnorePatterns(absPath) {
|
|
2714
|
+
const patterns = [];
|
|
2715
|
+
if (await fileExists2(join7(absPath, "esbuild.js"))) {
|
|
2716
|
+
patterns.push("esbuild.js");
|
|
2717
|
+
}
|
|
2718
|
+
let entries = [];
|
|
2719
|
+
try {
|
|
2720
|
+
entries = await readdir5(absPath);
|
|
2721
|
+
} catch (err) {
|
|
2722
|
+
console.error(
|
|
2723
|
+
` autoDetectIgnorePatterns: failed to read ${absPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
2724
|
+
);
|
|
2725
|
+
entries = [];
|
|
2726
|
+
}
|
|
2727
|
+
const esbuildVariant = /^esbuild\.[^.]+\.(mjs|cjs|js)$/;
|
|
2728
|
+
for (const name of entries) {
|
|
2729
|
+
if (esbuildVariant.test(name)) {
|
|
2730
|
+
patterns.push(name);
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
for (const ext of ["ts", "mts", "js", "mjs"]) {
|
|
2734
|
+
const candidate = `vitest.config.${ext}`;
|
|
2735
|
+
if (await fileExists2(join7(absPath, candidate))) {
|
|
2736
|
+
patterns.push(candidate);
|
|
2737
|
+
break;
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
for (const ext of ["ts", "mts", "js", "mjs"]) {
|
|
2741
|
+
const candidate = `vite.config.${ext}`;
|
|
2742
|
+
if (await fileExists2(join7(absPath, candidate))) {
|
|
2743
|
+
patterns.push(candidate);
|
|
2744
|
+
break;
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
if (await fileExists2(join7(absPath, "tauri.conf.json"))) {
|
|
2748
|
+
patterns.push("src-tauri/**");
|
|
2749
|
+
patterns.push("**/*.d.ts");
|
|
2750
|
+
}
|
|
2751
|
+
return patterns;
|
|
2752
|
+
}
|
|
2366
2753
|
function detectPackageManager(projectPath) {
|
|
2367
2754
|
return (async () => {
|
|
2368
2755
|
if (await fileExists2(join7(projectPath, "pnpm-lock.yaml"))) return "pnpm";
|
|
@@ -2393,11 +2780,13 @@ function buildInstallCommand(pm, packages, workspaceRoot) {
|
|
|
2393
2780
|
return `npm install -D ${pkgStr}`;
|
|
2394
2781
|
}
|
|
2395
2782
|
}
|
|
2396
|
-
async function resolvePresetsForTechStack(techNames) {
|
|
2783
|
+
async function resolvePresetsForTechStack(techNames, capabilities = []) {
|
|
2397
2784
|
const techParam = techNames.join(",");
|
|
2398
|
-
const
|
|
2399
|
-
|
|
2400
|
-
|
|
2785
|
+
const query = { tech_stack: techParam };
|
|
2786
|
+
if (capabilities.length > 0) {
|
|
2787
|
+
query.capabilities = capabilities.join(",");
|
|
2788
|
+
}
|
|
2789
|
+
const res = await apiGet("/eslint-presets", query);
|
|
2401
2790
|
return res.data ?? [];
|
|
2402
2791
|
}
|
|
2403
2792
|
async function eslintInit(repoId, projectPath) {
|
|
@@ -2432,19 +2821,27 @@ async function eslintInit(repoId, projectPath) {
|
|
|
2432
2821
|
const allRequiredDeps = /* @__PURE__ */ new Map();
|
|
2433
2822
|
const configsToWrite = [];
|
|
2434
2823
|
for (const target of targets) {
|
|
2435
|
-
const techNames = target.techStack.map((t) => t.name);
|
|
2824
|
+
const techNames = target.techStack.map((t) => t.name).filter((n) => n !== SYNTHETIC_CARRIER_NAME);
|
|
2825
|
+
const capabilities = collectCapabilities(target.techStack);
|
|
2436
2826
|
console.log(
|
|
2437
2827
|
` ${target.name}: ${techNames.length > 0 ? techNames.join(", ") : "(no tech detected)"}`
|
|
2438
2828
|
);
|
|
2439
|
-
if (
|
|
2440
|
-
console.log(`
|
|
2441
|
-
|
|
2829
|
+
if (capabilities.length > 0) {
|
|
2830
|
+
console.log(` Capabilities: ${capabilities.join(", ")}`);
|
|
2831
|
+
}
|
|
2832
|
+
if (techNames.length === 0 && capabilities.length === 0) {
|
|
2833
|
+
console.log(
|
|
2834
|
+
` Skipping ${target.name}: no tech or capabilities detected
|
|
2835
|
+
`
|
|
2836
|
+
);
|
|
2442
2837
|
continue;
|
|
2443
2838
|
}
|
|
2444
|
-
const presets = await resolvePresetsForTechStack(techNames);
|
|
2839
|
+
const presets = await resolvePresetsForTechStack(techNames, capabilities);
|
|
2445
2840
|
if (presets.length === 0) {
|
|
2446
|
-
console.log(
|
|
2447
|
-
`)
|
|
2841
|
+
console.log(
|
|
2842
|
+
` No preset matches ${target.name}; skipping (override via --force-preset if needed)
|
|
2843
|
+
`
|
|
2844
|
+
);
|
|
2448
2845
|
continue;
|
|
2449
2846
|
}
|
|
2450
2847
|
console.log(` Presets: ${presets.map((p) => p.name).join(", ")}`);
|
|
@@ -2465,9 +2862,14 @@ async function eslintInit(repoId, projectPath) {
|
|
|
2465
2862
|
}
|
|
2466
2863
|
} catch {
|
|
2467
2864
|
}
|
|
2865
|
+
const detectedIgnores = await autoDetectIgnorePatterns(target.absPath);
|
|
2866
|
+
if (detectedIgnores.length > 0) {
|
|
2867
|
+
console.log(` Auto-ignore: ${detectedIgnores.join(", ")}`);
|
|
2868
|
+
}
|
|
2468
2869
|
const content = generateEslintConfig({
|
|
2469
2870
|
presets,
|
|
2470
|
-
ruleOverrides: userOverrides
|
|
2871
|
+
ruleOverrides: userOverrides,
|
|
2872
|
+
ignorePatterns: detectedIgnores
|
|
2471
2873
|
});
|
|
2472
2874
|
const hash = hashConfig(content);
|
|
2473
2875
|
const configPath = join7(target.absPath, "eslint.config.mjs");
|
|
@@ -2621,8 +3023,12 @@ async function eslintSync(repoId, projectPath) {
|
|
|
2621
3023
|
const absPath = config.source_path === "." ? projectPath : join7(projectPath, config.source_path);
|
|
2622
3024
|
const configPath = join7(absPath, "eslint.config.mjs");
|
|
2623
3025
|
const detected = await detectTechStack(absPath);
|
|
2624
|
-
const techNames = detected.flat.map((t) => t.name);
|
|
2625
|
-
const
|
|
3026
|
+
const techNames = detected.flat.map((t) => t.name).filter((n) => n !== SYNTHETIC_CARRIER_NAME);
|
|
3027
|
+
const capabilities = collectCapabilities(detected.flat);
|
|
3028
|
+
const currentPresets = await resolvePresetsForTechStack(
|
|
3029
|
+
techNames,
|
|
3030
|
+
capabilities
|
|
3031
|
+
);
|
|
2626
3032
|
const currentPresetIds = currentPresets.map((p) => p.id).sort();
|
|
2627
3033
|
const savedPresetIds = [...config.active_preset_ids ?? []].sort();
|
|
2628
3034
|
const presetsChanged = currentPresetIds.length !== savedPresetIds.length || currentPresetIds.some((id) => !savedPresetIds.includes(id));
|
|
@@ -2651,11 +3057,15 @@ async function eslintSync(repoId, projectPath) {
|
|
|
2651
3057
|
);
|
|
2652
3058
|
}
|
|
2653
3059
|
}
|
|
2654
|
-
|
|
3060
|
+
if (presetsChanged) {
|
|
3061
|
+
console.log(` ${config.source_path}: presets changed, regenerating...`);
|
|
3062
|
+
}
|
|
2655
3063
|
const userOverrides = config.rule_overrides;
|
|
3064
|
+
const detectedIgnores = await autoDetectIgnorePatterns(absPath);
|
|
2656
3065
|
const content = generateEslintConfig({
|
|
2657
3066
|
presets: currentPresets,
|
|
2658
|
-
ruleOverrides: userOverrides && Object.keys(userOverrides).length > 0 ? userOverrides : void 0
|
|
3067
|
+
ruleOverrides: userOverrides && Object.keys(userOverrides).length > 0 ? userOverrides : void 0,
|
|
3068
|
+
ignorePatterns: detectedIgnores
|
|
2659
3069
|
});
|
|
2660
3070
|
try {
|
|
2661
3071
|
await writeFile3(configPath, content, "utf-8");
|
|
@@ -2823,7 +3233,10 @@ async function runSyncInner(repoId, projectPath, dryRun, force, fix = false) {
|
|
|
2823
3233
|
let localFiles = /* @__PURE__ */ new Map();
|
|
2824
3234
|
try {
|
|
2825
3235
|
localFiles = await scanLocalFiles(claudeDir, projectPath);
|
|
2826
|
-
} catch {
|
|
3236
|
+
} catch (err) {
|
|
3237
|
+
console.warn(
|
|
3238
|
+
` Local file scan incomplete: ${err instanceof Error ? err.message : String(err)}`
|
|
3239
|
+
);
|
|
2827
3240
|
}
|
|
2828
3241
|
const [defaultsRes, repoSyncRes, repoRes, , fileReposRes] = await Promise.all(
|
|
2829
3242
|
[
|
|
@@ -3142,7 +3555,10 @@ async function runSyncInner(repoId, projectPath, dryRun, force, fix = false) {
|
|
|
3142
3555
|
repo_id: repoId,
|
|
3143
3556
|
file_repos: fileRepoUpdates
|
|
3144
3557
|
});
|
|
3145
|
-
} catch {
|
|
3558
|
+
} catch (err) {
|
|
3559
|
+
console.warn(
|
|
3560
|
+
` Warning: failed to update file-repo tracking for ${fileRepoUpdates.length} files: ${err instanceof Error ? err.message : String(err)}`
|
|
3561
|
+
);
|
|
3146
3562
|
}
|
|
3147
3563
|
}
|
|
3148
3564
|
console.log(
|
|
@@ -3298,7 +3714,10 @@ async function syncConfig(repoId, projectPath, dryRun) {
|
|
|
3298
3714
|
}
|
|
3299
3715
|
return clean;
|
|
3300
3716
|
});
|
|
3301
|
-
} catch {
|
|
3717
|
+
} catch (err) {
|
|
3718
|
+
console.warn(
|
|
3719
|
+
` Warning: failed to fetch port allocations: ${err instanceof Error ? err.message : String(err)}`
|
|
3720
|
+
);
|
|
3302
3721
|
}
|
|
3303
3722
|
const worktreeId = currentConfig.worktree_id;
|
|
3304
3723
|
const matchingAlloc = portAllocations[0];
|
|
@@ -3363,8 +3782,10 @@ async function syncTechStack(repoId, projectPath, dryRun) {
|
|
|
3363
3782
|
}
|
|
3364
3783
|
}
|
|
3365
3784
|
}
|
|
3366
|
-
} catch {
|
|
3367
|
-
console.
|
|
3785
|
+
} catch (err) {
|
|
3786
|
+
console.warn(
|
|
3787
|
+
` Tech stack detection skipped: ${err instanceof Error ? err.message : String(err)}`
|
|
3788
|
+
);
|
|
3368
3789
|
}
|
|
3369
3790
|
}
|
|
3370
3791
|
async function syncEslintDriftCheck(repoId, projectPath) {
|
|
@@ -3442,8 +3863,10 @@ async function syncPortVerification(repoId, projectPath, dryRun, fix) {
|
|
|
3442
3863
|
if (mismatches.length === 0 && unallocated.length === 0) {
|
|
3443
3864
|
console.log(" Ports verified.");
|
|
3444
3865
|
}
|
|
3445
|
-
} catch {
|
|
3446
|
-
console.
|
|
3866
|
+
} catch (err) {
|
|
3867
|
+
console.warn(
|
|
3868
|
+
` Port verification skipped: ${err instanceof Error ? err.message : String(err)}`
|
|
3869
|
+
);
|
|
3447
3870
|
}
|
|
3448
3871
|
}
|
|
3449
3872
|
function groupByType(items) {
|
|
@@ -3498,7 +3921,7 @@ function getLocalFilePath(claudeDir, projectPath, remote) {
|
|
|
3498
3921
|
}
|
|
3499
3922
|
function getSyncVersion() {
|
|
3500
3923
|
try {
|
|
3501
|
-
return "1.4.
|
|
3924
|
+
return "1.4.2";
|
|
3502
3925
|
} catch {
|
|
3503
3926
|
return "unknown";
|
|
3504
3927
|
}
|
|
@@ -3554,68 +3977,69 @@ var init_sync = __esm({
|
|
|
3554
3977
|
// src/index.ts
|
|
3555
3978
|
init_version();
|
|
3556
3979
|
import { readFileSync } from "node:fs";
|
|
3557
|
-
import { resolve } from "node:path";
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
const
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3980
|
+
import { resolve as resolve2 } from "node:path";
|
|
3981
|
+
void (async () => {
|
|
3982
|
+
if (!process.env.CODEBYPLAN_API_KEY) {
|
|
3983
|
+
try {
|
|
3984
|
+
const envPath = resolve2(process.cwd(), ".env.local");
|
|
3985
|
+
const content = readFileSync(envPath, "utf-8");
|
|
3986
|
+
for (const line of content.split("\n")) {
|
|
3987
|
+
const trimmed = line.trim();
|
|
3988
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
3989
|
+
const eq = trimmed.indexOf("=");
|
|
3990
|
+
if (eq === -1) continue;
|
|
3991
|
+
const key = trimmed.slice(0, eq).trim();
|
|
3992
|
+
const val = trimmed.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
|
|
3993
|
+
if (!process.env[key]) process.env[key] = val;
|
|
3994
|
+
}
|
|
3995
|
+
} catch {
|
|
3570
3996
|
}
|
|
3571
|
-
} catch {
|
|
3572
3997
|
}
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3998
|
+
if (process.env.CODEBYPLAN_API_KEY?.startsWith("CODEBYPLAN_API_KEY=")) {
|
|
3999
|
+
process.env.CODEBYPLAN_API_KEY = process.env.CODEBYPLAN_API_KEY.slice(
|
|
4000
|
+
"CODEBYPLAN_API_KEY=".length
|
|
4001
|
+
);
|
|
4002
|
+
}
|
|
4003
|
+
const arg = process.argv[2];
|
|
4004
|
+
if (arg === "--version" || arg === "-v") {
|
|
4005
|
+
console.log(VERSION);
|
|
4006
|
+
process.exit(0);
|
|
4007
|
+
}
|
|
4008
|
+
if (arg === "setup") {
|
|
4009
|
+
const { runSetup: runSetup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
|
|
4010
|
+
await runSetup2();
|
|
4011
|
+
process.exit(0);
|
|
4012
|
+
}
|
|
4013
|
+
if (arg === "sync") {
|
|
4014
|
+
const { runSync: runSync2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
|
|
4015
|
+
const { SyncCancelledError: SyncCancelledError2 } = await Promise.resolve().then(() => (init_confirm(), confirm_exports));
|
|
4016
|
+
try {
|
|
4017
|
+
await runSync2();
|
|
4018
|
+
} catch (err) {
|
|
4019
|
+
if (err instanceof SyncCancelledError2) {
|
|
4020
|
+
console.log("\n Sync cancelled.\n");
|
|
4021
|
+
process.exit(0);
|
|
4022
|
+
}
|
|
4023
|
+
throw err;
|
|
3598
4024
|
}
|
|
3599
|
-
|
|
4025
|
+
process.exit(0);
|
|
3600
4026
|
}
|
|
3601
|
-
|
|
3602
|
-
}
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
4027
|
+
if (arg === "eslint") {
|
|
4028
|
+
const { runEslint: runEslint2 } = await Promise.resolve().then(() => (init_eslint(), eslint_exports));
|
|
4029
|
+
const { SyncCancelledError: SyncCancelledError2 } = await Promise.resolve().then(() => (init_confirm(), confirm_exports));
|
|
4030
|
+
try {
|
|
4031
|
+
await runEslint2();
|
|
4032
|
+
} catch (err) {
|
|
4033
|
+
if (err instanceof SyncCancelledError2) {
|
|
4034
|
+
console.log("\n Cancelled.\n");
|
|
4035
|
+
process.exit(0);
|
|
4036
|
+
}
|
|
4037
|
+
throw err;
|
|
3612
4038
|
}
|
|
3613
|
-
|
|
4039
|
+
process.exit(0);
|
|
3614
4040
|
}
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
if (arg === "help" || arg === "--help" || arg === "-h" || arg === void 0) {
|
|
3618
|
-
console.log(`
|
|
4041
|
+
if (arg === "help" || arg === "--help" || arg === "-h" || arg === void 0) {
|
|
4042
|
+
console.log(`
|
|
3619
4043
|
CodeByPlan CLI v${VERSION}
|
|
3620
4044
|
|
|
3621
4045
|
Usage:
|
|
@@ -3638,13 +4062,14 @@ if (arg === "help" || arg === "--help" || arg === "-h" || arg === void 0) {
|
|
|
3638
4062
|
|
|
3639
4063
|
MCP Server:
|
|
3640
4064
|
Claude Code connects to CodeByPlan via remote MCP:
|
|
3641
|
-
URL: https://codebyplan.com/mcp
|
|
4065
|
+
URL: https://www.codebyplan.com/mcp
|
|
3642
4066
|
Auth: x-api-key header (configured during setup)
|
|
3643
4067
|
|
|
3644
4068
|
Learn more: https://codebyplan.com
|
|
3645
4069
|
`);
|
|
3646
|
-
|
|
3647
|
-
}
|
|
3648
|
-
console.error(`Unknown command: ${arg}`);
|
|
3649
|
-
console.error("Run 'codebyplan help' for usage.");
|
|
3650
|
-
process.exit(1);
|
|
4070
|
+
process.exit(0);
|
|
4071
|
+
}
|
|
4072
|
+
console.error(`Unknown command: ${arg}`);
|
|
4073
|
+
console.error("Run 'codebyplan help' for usage.");
|
|
4074
|
+
process.exit(1);
|
|
4075
|
+
})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codebyplan",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
4
4
|
"description": "CLI for CodeByPlan — AI-powered development planning and tracking",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"README.md"
|
|
12
12
|
],
|
|
13
13
|
"scripts": {
|
|
14
|
-
"build": "tsc",
|
|
14
|
+
"build": "tsc --project tsconfig.build.json",
|
|
15
15
|
"build:npm": "node esbuild.npm.mjs",
|
|
16
16
|
"prepublishOnly": "npm run build:npm",
|
|
17
17
|
"lint": "eslint",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"@eslint/js": "^9.18.0",
|
|
48
48
|
"@types/node": "^20",
|
|
49
49
|
"@vitest/eslint-plugin": "^1.1.44",
|
|
50
|
-
"esbuild": "^0.
|
|
50
|
+
"esbuild": "^0.28",
|
|
51
51
|
"eslint": "^9.18.0",
|
|
52
52
|
"eslint-config-prettier": "^10.0.1",
|
|
53
53
|
"eslint-plugin-no-secrets": "^2.2.1",
|