codebyplan 1.3.1 → 1.4.1
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.js +732 -26
- package/package.json +1 -1
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.
|
|
17
|
+
VERSION = "1.4.1";
|
|
18
18
|
PACKAGE_NAME = "codebyplan";
|
|
19
19
|
}
|
|
20
20
|
});
|
|
@@ -2074,16 +2074,686 @@ var init_port_verify = __esm({
|
|
|
2074
2074
|
}
|
|
2075
2075
|
});
|
|
2076
2076
|
|
|
2077
|
+
// src/lib/eslint-generator.ts
|
|
2078
|
+
import { createHash } from "node:crypto";
|
|
2079
|
+
function parseFragment(fragment) {
|
|
2080
|
+
if (!fragment) return { imports: [], configComments: [] };
|
|
2081
|
+
const lines = fragment.split("\n");
|
|
2082
|
+
const imports = [];
|
|
2083
|
+
const configComments = [];
|
|
2084
|
+
for (const line of lines) {
|
|
2085
|
+
const trimmed = line.trim();
|
|
2086
|
+
if (!trimmed) continue;
|
|
2087
|
+
if (trimmed.startsWith("import ") || trimmed.startsWith("const ") || trimmed.startsWith("// @ts-check")) {
|
|
2088
|
+
imports.push(trimmed);
|
|
2089
|
+
} else if (trimmed.startsWith("//")) {
|
|
2090
|
+
configComments.push(trimmed);
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
return { imports, configComments };
|
|
2094
|
+
}
|
|
2095
|
+
function importKey(line) {
|
|
2096
|
+
const fromMatch = line.match(/from\s+["']([^"']+)["']/);
|
|
2097
|
+
if (fromMatch) return fromMatch[1];
|
|
2098
|
+
const requireMatch = line.match(/require\(["']([^"']+)["']\)/);
|
|
2099
|
+
if (requireMatch) return requireMatch[1];
|
|
2100
|
+
return line;
|
|
2101
|
+
}
|
|
2102
|
+
function deduplicateImports(allImports) {
|
|
2103
|
+
const seen = /* @__PURE__ */ new Map();
|
|
2104
|
+
for (const imp of allImports) {
|
|
2105
|
+
const key = importKey(imp);
|
|
2106
|
+
if (!seen.has(key)) {
|
|
2107
|
+
seen.set(key, imp);
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
return Array.from(seen.values());
|
|
2111
|
+
}
|
|
2112
|
+
function collectDependencies(presets) {
|
|
2113
|
+
const deps = /* @__PURE__ */ new Map();
|
|
2114
|
+
for (const preset of presets) {
|
|
2115
|
+
const presetDeps = preset.dependencies;
|
|
2116
|
+
if (!presetDeps || typeof presetDeps !== "object") continue;
|
|
2117
|
+
for (const [pkg, version] of Object.entries(presetDeps)) {
|
|
2118
|
+
if (typeof version === "string" && !deps.has(pkg)) {
|
|
2119
|
+
deps.set(pkg, version);
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
return deps;
|
|
2124
|
+
}
|
|
2125
|
+
function hashConfig(content) {
|
|
2126
|
+
return createHash("sha256").update(content).digest("hex");
|
|
2127
|
+
}
|
|
2128
|
+
function buildRules(presets, userOverrides) {
|
|
2129
|
+
const merged = {};
|
|
2130
|
+
for (const preset of presets) {
|
|
2131
|
+
const rules = preset.rules;
|
|
2132
|
+
if (rules && typeof rules === "object") {
|
|
2133
|
+
Object.assign(merged, rules);
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
if (userOverrides) {
|
|
2137
|
+
Object.assign(merged, userOverrides);
|
|
2138
|
+
}
|
|
2139
|
+
return merged;
|
|
2140
|
+
}
|
|
2141
|
+
function formatRules(rules, indent) {
|
|
2142
|
+
const entries = Object.entries(rules);
|
|
2143
|
+
if (entries.length === 0) return "{}";
|
|
2144
|
+
const lines = entries.map(([key, value]) => {
|
|
2145
|
+
const formattedValue = JSON.stringify(value);
|
|
2146
|
+
return `${indent} "${key}": ${formattedValue},`;
|
|
2147
|
+
});
|
|
2148
|
+
return `{
|
|
2149
|
+
${lines.join("\n")}
|
|
2150
|
+
${indent}}`;
|
|
2151
|
+
}
|
|
2152
|
+
function generateEslintConfig(input) {
|
|
2153
|
+
const { presets, ruleOverrides, tsconfigRootDir, ignorePatterns } = input;
|
|
2154
|
+
const allImports = [];
|
|
2155
|
+
const allConfigComments = [];
|
|
2156
|
+
for (const preset of presets) {
|
|
2157
|
+
const parsed = parseFragment(preset.config_fragment);
|
|
2158
|
+
allImports.push(...parsed.imports);
|
|
2159
|
+
allConfigComments.push(
|
|
2160
|
+
...parsed.configComments.map((c) => ` ${c} (from: ${preset.name})`)
|
|
2161
|
+
);
|
|
2162
|
+
}
|
|
2163
|
+
const dedupedImports = deduplicateImports(allImports);
|
|
2164
|
+
const tsCheck = dedupedImports.find((i) => i === "// @ts-check");
|
|
2165
|
+
const importLines = dedupedImports.filter((i) => i !== "// @ts-check");
|
|
2166
|
+
const rules = buildRules(presets, ruleOverrides);
|
|
2167
|
+
const defaultIgnores = [
|
|
2168
|
+
"eslint.config.mjs",
|
|
2169
|
+
"node_modules/**",
|
|
2170
|
+
"dist/**",
|
|
2171
|
+
".next/**",
|
|
2172
|
+
"coverage/**"
|
|
2173
|
+
];
|
|
2174
|
+
const ignores = [.../* @__PURE__ */ new Set([...defaultIgnores, ...ignorePatterns ?? []])];
|
|
2175
|
+
const rootDir = tsconfigRootDir ?? "import.meta.dirname";
|
|
2176
|
+
const rootDirValue = rootDir === "import.meta.dirname" ? "import.meta.dirname" : `"${rootDir}"`;
|
|
2177
|
+
const hasNextJs = presets.some((p) => p.is_system && p.name === "nextjs");
|
|
2178
|
+
const hasReact = presets.some((p) => p.is_system && p.name === "react");
|
|
2179
|
+
const hasNode = presets.some((p) => p.is_system && p.name === "node");
|
|
2180
|
+
const hasTesting = presets.some((p) => p.is_system && p.name === "testing");
|
|
2181
|
+
const hasTestingReact = presets.some(
|
|
2182
|
+
(p) => p.is_system && p.name === "testing-react"
|
|
2183
|
+
);
|
|
2184
|
+
const hasTestingE2e = presets.some(
|
|
2185
|
+
(p) => p.is_system && p.name === "testing-e2e"
|
|
2186
|
+
);
|
|
2187
|
+
const hasCli = presets.some((p) => p.is_system && p.name === "cli");
|
|
2188
|
+
const sections = [];
|
|
2189
|
+
if (tsCheck) {
|
|
2190
|
+
sections.push("// @ts-check");
|
|
2191
|
+
}
|
|
2192
|
+
sections.push(
|
|
2193
|
+
"/**",
|
|
2194
|
+
" * ESLint flat config \u2014 generated by CodeByPlan CLI.",
|
|
2195
|
+
" * Edit rule overrides via the web UI, then run `codebyplan eslint sync`.",
|
|
2196
|
+
" * Manual edits will be detected as drift.",
|
|
2197
|
+
" */",
|
|
2198
|
+
""
|
|
2199
|
+
);
|
|
2200
|
+
for (const imp of importLines) {
|
|
2201
|
+
sections.push(imp);
|
|
2202
|
+
}
|
|
2203
|
+
const hasGlobalsImport = importLines.some((i) => i.includes("globals"));
|
|
2204
|
+
if ((hasNode || hasReact || hasNextJs) && !hasGlobalsImport) {
|
|
2205
|
+
sections.push('import globals from "globals";');
|
|
2206
|
+
}
|
|
2207
|
+
sections.push("");
|
|
2208
|
+
sections.push("export default [");
|
|
2209
|
+
sections.push(` { ignores: ${JSON.stringify(ignores)} },`);
|
|
2210
|
+
sections.push("");
|
|
2211
|
+
const hasBase = presets.some((p) => p.is_system && p.name === "base");
|
|
2212
|
+
if (hasBase) {
|
|
2213
|
+
sections.push(" // Base: TypeScript + security + Prettier");
|
|
2214
|
+
sections.push(" eslint.configs.recommended,");
|
|
2215
|
+
sections.push(" ...tseslint.configs.recommendedTypeChecked,");
|
|
2216
|
+
sections.push(" security.configs.recommended,");
|
|
2217
|
+
sections.push(' { plugins: { "no-secrets": noSecrets } },');
|
|
2218
|
+
sections.push(" {");
|
|
2219
|
+
sections.push(" languageOptions: {");
|
|
2220
|
+
sections.push(" parserOptions: {");
|
|
2221
|
+
sections.push(" projectService: true,");
|
|
2222
|
+
sections.push(` tsconfigRootDir: ${rootDirValue},`);
|
|
2223
|
+
sections.push(" },");
|
|
2224
|
+
sections.push(" },");
|
|
2225
|
+
sections.push(" },");
|
|
2226
|
+
sections.push("");
|
|
2227
|
+
}
|
|
2228
|
+
if (hasNextJs) {
|
|
2229
|
+
sections.push(" // Next.js: Core Web Vitals + TypeScript");
|
|
2230
|
+
sections.push(" ...nextCoreWebVitals,");
|
|
2231
|
+
sections.push(" ...nextTypescript,");
|
|
2232
|
+
sections.push(" { rules: jsxA11y.flatConfigs.strict.rules },");
|
|
2233
|
+
sections.push(' { plugins: { "react-compiler": reactCompiler } },');
|
|
2234
|
+
sections.push("");
|
|
2235
|
+
}
|
|
2236
|
+
if (hasReact && !hasNextJs) {
|
|
2237
|
+
sections.push(" // React: hooks + compiler + a11y");
|
|
2238
|
+
sections.push(" {");
|
|
2239
|
+
sections.push(' files: ["**/*.{ts,tsx,jsx}"],');
|
|
2240
|
+
sections.push(" plugins: {");
|
|
2241
|
+
sections.push(" react,");
|
|
2242
|
+
sections.push(' "react-hooks": reactHooks,');
|
|
2243
|
+
sections.push(' "react-compiler": reactCompiler,');
|
|
2244
|
+
sections.push(" },");
|
|
2245
|
+
sections.push(" languageOptions: {");
|
|
2246
|
+
sections.push(" parserOptions: { ecmaFeatures: { jsx: true } },");
|
|
2247
|
+
sections.push(" globals: { ...globals.browser },");
|
|
2248
|
+
sections.push(" },");
|
|
2249
|
+
sections.push(' settings: { react: { version: "detect" } },');
|
|
2250
|
+
sections.push(" rules: {");
|
|
2251
|
+
sections.push(" ...react.configs.flat.recommended.rules,");
|
|
2252
|
+
sections.push(' ...react.configs.flat["jsx-runtime"].rules,');
|
|
2253
|
+
sections.push(" },");
|
|
2254
|
+
sections.push(" },");
|
|
2255
|
+
sections.push(" jsxA11y.flatConfigs.strict,");
|
|
2256
|
+
sections.push("");
|
|
2257
|
+
}
|
|
2258
|
+
if (hasNode) {
|
|
2259
|
+
sections.push(" // Node.js globals");
|
|
2260
|
+
sections.push(" {");
|
|
2261
|
+
sections.push(" languageOptions: {");
|
|
2262
|
+
sections.push(" globals: { ...globals.node },");
|
|
2263
|
+
sections.push(' sourceType: "module",');
|
|
2264
|
+
sections.push(" },");
|
|
2265
|
+
sections.push(" },");
|
|
2266
|
+
sections.push("");
|
|
2267
|
+
}
|
|
2268
|
+
if (hasBase) {
|
|
2269
|
+
sections.push(" // Prettier (must be last base config)");
|
|
2270
|
+
sections.push(" prettier,");
|
|
2271
|
+
sections.push("");
|
|
2272
|
+
}
|
|
2273
|
+
if (Object.keys(rules).length > 0) {
|
|
2274
|
+
sections.push(" // Rule overrides");
|
|
2275
|
+
sections.push(" {");
|
|
2276
|
+
sections.push(` rules: ${formatRules(rules, " ")},`);
|
|
2277
|
+
sections.push(" },");
|
|
2278
|
+
sections.push("");
|
|
2279
|
+
}
|
|
2280
|
+
if (hasCli) {
|
|
2281
|
+
sections.push(" // CLI overrides");
|
|
2282
|
+
sections.push(" {");
|
|
2283
|
+
sections.push(" rules: {");
|
|
2284
|
+
sections.push(' "no-console": "off",');
|
|
2285
|
+
sections.push(' "security/detect-non-literal-fs-filename": "off",');
|
|
2286
|
+
sections.push(' "security/detect-object-injection": "off",');
|
|
2287
|
+
sections.push(" },");
|
|
2288
|
+
sections.push(" },");
|
|
2289
|
+
sections.push("");
|
|
2290
|
+
}
|
|
2291
|
+
if (hasTesting) {
|
|
2292
|
+
sections.push(" // Testing: Vitest");
|
|
2293
|
+
sections.push(" {");
|
|
2294
|
+
sections.push(
|
|
2295
|
+
' files: ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx"],'
|
|
2296
|
+
);
|
|
2297
|
+
sections.push(" plugins: { vitest },");
|
|
2298
|
+
sections.push(" rules: {");
|
|
2299
|
+
sections.push(" ...vitest.configs.recommended.rules,");
|
|
2300
|
+
sections.push(' "@typescript-eslint/no-explicit-any": "off",');
|
|
2301
|
+
sections.push(' "@typescript-eslint/no-unsafe-assignment": "off",');
|
|
2302
|
+
sections.push(' "@typescript-eslint/no-unsafe-member-access": "off",');
|
|
2303
|
+
sections.push(' "@typescript-eslint/no-unsafe-call": "off",');
|
|
2304
|
+
sections.push(' "@typescript-eslint/no-unsafe-argument": "off",');
|
|
2305
|
+
sections.push(' "@typescript-eslint/no-unsafe-return": "off",');
|
|
2306
|
+
sections.push(" },");
|
|
2307
|
+
sections.push(" },");
|
|
2308
|
+
sections.push("");
|
|
2309
|
+
}
|
|
2310
|
+
if (hasTestingReact) {
|
|
2311
|
+
sections.push(" // Testing: React Testing Library + jest-dom");
|
|
2312
|
+
sections.push(" {");
|
|
2313
|
+
sections.push(
|
|
2314
|
+
' files: ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx"],'
|
|
2315
|
+
);
|
|
2316
|
+
sections.push(
|
|
2317
|
+
' plugins: { "testing-library": testingLibrary, "jest-dom": jestDom },'
|
|
2318
|
+
);
|
|
2319
|
+
sections.push(" rules: {");
|
|
2320
|
+
sections.push(' ...testingLibrary.configs["flat/react"].rules,');
|
|
2321
|
+
sections.push(' ...jestDom.configs["flat/recommended"].rules,');
|
|
2322
|
+
sections.push(" },");
|
|
2323
|
+
sections.push(" },");
|
|
2324
|
+
sections.push("");
|
|
2325
|
+
}
|
|
2326
|
+
if (hasTestingE2e) {
|
|
2327
|
+
sections.push(" // Testing: Playwright E2E");
|
|
2328
|
+
sections.push(" {");
|
|
2329
|
+
sections.push(' files: ["e2e/**"],');
|
|
2330
|
+
sections.push(" plugins: { playwright },");
|
|
2331
|
+
sections.push(" rules: {");
|
|
2332
|
+
sections.push(' ...playwright.configs["flat/recommended"].rules,');
|
|
2333
|
+
sections.push(' "no-console": "off",');
|
|
2334
|
+
sections.push(" },");
|
|
2335
|
+
sections.push(" },");
|
|
2336
|
+
sections.push("");
|
|
2337
|
+
}
|
|
2338
|
+
sections.push("];");
|
|
2339
|
+
sections.push("");
|
|
2340
|
+
return sections.join("\n");
|
|
2341
|
+
}
|
|
2342
|
+
var init_eslint_generator = __esm({
|
|
2343
|
+
"src/lib/eslint-generator.ts"() {
|
|
2344
|
+
"use strict";
|
|
2345
|
+
}
|
|
2346
|
+
});
|
|
2347
|
+
|
|
2348
|
+
// src/cli/eslint.ts
|
|
2349
|
+
var eslint_exports = {};
|
|
2350
|
+
__export(eslint_exports, {
|
|
2351
|
+
checkEslintDrift: () => checkEslintDrift,
|
|
2352
|
+
eslintInit: () => eslintInit,
|
|
2353
|
+
eslintSync: () => eslintSync,
|
|
2354
|
+
runEslint: () => runEslint
|
|
2355
|
+
});
|
|
2356
|
+
import { readFile as readFile8, writeFile as writeFile3, access as access2 } from "node:fs/promises";
|
|
2357
|
+
import { join as join7, relative as relative2 } from "node:path";
|
|
2358
|
+
async function fileExists2(filePath) {
|
|
2359
|
+
try {
|
|
2360
|
+
await access2(filePath);
|
|
2361
|
+
return true;
|
|
2362
|
+
} catch {
|
|
2363
|
+
return false;
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
function detectPackageManager(projectPath) {
|
|
2367
|
+
return (async () => {
|
|
2368
|
+
if (await fileExists2(join7(projectPath, "pnpm-lock.yaml"))) return "pnpm";
|
|
2369
|
+
if (await fileExists2(join7(projectPath, "yarn.lock"))) return "yarn";
|
|
2370
|
+
return "npm";
|
|
2371
|
+
})();
|
|
2372
|
+
}
|
|
2373
|
+
async function getInstalledDeps(pkgJsonPath) {
|
|
2374
|
+
try {
|
|
2375
|
+
const raw = await readFile8(pkgJsonPath, "utf-8");
|
|
2376
|
+
const pkg = JSON.parse(raw);
|
|
2377
|
+
const all = /* @__PURE__ */ new Set();
|
|
2378
|
+
for (const name of Object.keys(pkg.dependencies ?? {})) all.add(name);
|
|
2379
|
+
for (const name of Object.keys(pkg.devDependencies ?? {})) all.add(name);
|
|
2380
|
+
return all;
|
|
2381
|
+
} catch {
|
|
2382
|
+
return /* @__PURE__ */ new Set();
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
function buildInstallCommand(pm, packages, workspaceRoot) {
|
|
2386
|
+
const pkgStr = packages.join(" ");
|
|
2387
|
+
switch (pm) {
|
|
2388
|
+
case "pnpm":
|
|
2389
|
+
return workspaceRoot ? `pnpm add -D -w ${pkgStr}` : `pnpm add -D ${pkgStr}`;
|
|
2390
|
+
case "yarn":
|
|
2391
|
+
return workspaceRoot ? `yarn add -D -W ${pkgStr}` : `yarn add -D ${pkgStr}`;
|
|
2392
|
+
case "npm":
|
|
2393
|
+
return `npm install -D ${pkgStr}`;
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
async function resolvePresetsForTechStack(techNames) {
|
|
2397
|
+
const techParam = techNames.join(",");
|
|
2398
|
+
const res = await apiGet("/eslint-presets", {
|
|
2399
|
+
tech_stack: techParam
|
|
2400
|
+
});
|
|
2401
|
+
return res.data ?? [];
|
|
2402
|
+
}
|
|
2403
|
+
async function eslintInit(repoId, projectPath) {
|
|
2404
|
+
console.log("\n ESLint Init");
|
|
2405
|
+
console.log(` Repo: ${repoId}`);
|
|
2406
|
+
console.log(` Path: ${projectPath}
|
|
2407
|
+
`);
|
|
2408
|
+
const apps = await discoverMonorepoApps(projectPath);
|
|
2409
|
+
const isMonorepo = apps.length > 0;
|
|
2410
|
+
const targets = [];
|
|
2411
|
+
if (isMonorepo) {
|
|
2412
|
+
console.log(` Monorepo detected: ${apps.length} apps/packages
|
|
2413
|
+
`);
|
|
2414
|
+
for (const app of apps) {
|
|
2415
|
+
const detected = await detectTechStack(app.absPath);
|
|
2416
|
+
targets.push({
|
|
2417
|
+
name: app.name,
|
|
2418
|
+
sourcePath: app.path,
|
|
2419
|
+
absPath: app.absPath,
|
|
2420
|
+
techStack: detected.flat
|
|
2421
|
+
});
|
|
2422
|
+
}
|
|
2423
|
+
} else {
|
|
2424
|
+
const detected = await detectTechStack(projectPath);
|
|
2425
|
+
targets.push({
|
|
2426
|
+
name: "root",
|
|
2427
|
+
sourcePath: ".",
|
|
2428
|
+
absPath: projectPath,
|
|
2429
|
+
techStack: detected.flat
|
|
2430
|
+
});
|
|
2431
|
+
}
|
|
2432
|
+
const allRequiredDeps = /* @__PURE__ */ new Map();
|
|
2433
|
+
const configsToWrite = [];
|
|
2434
|
+
for (const target of targets) {
|
|
2435
|
+
const techNames = target.techStack.map((t) => t.name);
|
|
2436
|
+
console.log(
|
|
2437
|
+
` ${target.name}: ${techNames.length > 0 ? techNames.join(", ") : "(no tech detected)"}`
|
|
2438
|
+
);
|
|
2439
|
+
if (techNames.length === 0) {
|
|
2440
|
+
console.log(` Skipping \u2014 no tech stack detected.
|
|
2441
|
+
`);
|
|
2442
|
+
continue;
|
|
2443
|
+
}
|
|
2444
|
+
const presets = await resolvePresetsForTechStack(techNames);
|
|
2445
|
+
if (presets.length === 0) {
|
|
2446
|
+
console.log(` No matching presets found.
|
|
2447
|
+
`);
|
|
2448
|
+
continue;
|
|
2449
|
+
}
|
|
2450
|
+
console.log(` Presets: ${presets.map((p) => p.name).join(", ")}`);
|
|
2451
|
+
let userOverrides;
|
|
2452
|
+
try {
|
|
2453
|
+
const configRes = await apiGet(`/repos/${repoId}/eslint-config`);
|
|
2454
|
+
const existing = configRes.data?.find(
|
|
2455
|
+
(c) => c.source_path === target.sourcePath
|
|
2456
|
+
);
|
|
2457
|
+
if (existing?.rule_overrides) {
|
|
2458
|
+
const overrides = existing.rule_overrides;
|
|
2459
|
+
if (Object.keys(overrides).length > 0) {
|
|
2460
|
+
userOverrides = overrides;
|
|
2461
|
+
console.log(
|
|
2462
|
+
` User overrides: ${Object.keys(overrides).length} rule(s)`
|
|
2463
|
+
);
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
} catch {
|
|
2467
|
+
}
|
|
2468
|
+
const content = generateEslintConfig({
|
|
2469
|
+
presets,
|
|
2470
|
+
ruleOverrides: userOverrides
|
|
2471
|
+
});
|
|
2472
|
+
const hash = hashConfig(content);
|
|
2473
|
+
const configPath = join7(target.absPath, "eslint.config.mjs");
|
|
2474
|
+
configsToWrite.push({
|
|
2475
|
+
target,
|
|
2476
|
+
presets,
|
|
2477
|
+
content,
|
|
2478
|
+
hash,
|
|
2479
|
+
configPath,
|
|
2480
|
+
userOverrides
|
|
2481
|
+
});
|
|
2482
|
+
const deps = collectDependencies(presets);
|
|
2483
|
+
for (const [pkg, version] of deps) {
|
|
2484
|
+
if (!allRequiredDeps.has(pkg)) {
|
|
2485
|
+
allRequiredDeps.set(pkg, version);
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
console.log("");
|
|
2489
|
+
}
|
|
2490
|
+
if (configsToWrite.length === 0) {
|
|
2491
|
+
console.log(" No configs to generate.\n");
|
|
2492
|
+
return;
|
|
2493
|
+
}
|
|
2494
|
+
const pm = await detectPackageManager(projectPath);
|
|
2495
|
+
const rootPkgJsonPath = join7(projectPath, "package.json");
|
|
2496
|
+
const installed = await getInstalledDeps(rootPkgJsonPath);
|
|
2497
|
+
if (isMonorepo) {
|
|
2498
|
+
for (const { target } of configsToWrite) {
|
|
2499
|
+
const appPkgJson = join7(target.absPath, "package.json");
|
|
2500
|
+
const appDeps = await getInstalledDeps(appPkgJson);
|
|
2501
|
+
for (const dep of appDeps) {
|
|
2502
|
+
installed.add(dep);
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
const missingPkgs = [];
|
|
2507
|
+
for (const [pkg] of allRequiredDeps) {
|
|
2508
|
+
if (!installed.has(pkg)) {
|
|
2509
|
+
missingPkgs.push(pkg);
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
if (missingPkgs.length > 0) {
|
|
2513
|
+
console.log(` Missing npm packages (${missingPkgs.length}):`);
|
|
2514
|
+
for (const pkg of missingPkgs) {
|
|
2515
|
+
console.log(` - ${pkg}`);
|
|
2516
|
+
}
|
|
2517
|
+
const installCmd = buildInstallCommand(pm, missingPkgs, isMonorepo);
|
|
2518
|
+
console.log(`
|
|
2519
|
+
Install command: ${installCmd}`);
|
|
2520
|
+
const confirmed = await confirmProceed(
|
|
2521
|
+
`
|
|
2522
|
+
Install ${missingPkgs.length} missing packages? [Y/n] `
|
|
2523
|
+
);
|
|
2524
|
+
if (confirmed) {
|
|
2525
|
+
const { execSync } = await import("node:child_process");
|
|
2526
|
+
try {
|
|
2527
|
+
execSync(installCmd, { cwd: projectPath, stdio: "inherit" });
|
|
2528
|
+
console.log(" Packages installed.\n");
|
|
2529
|
+
} catch (err) {
|
|
2530
|
+
console.error(
|
|
2531
|
+
` Package installation failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2532
|
+
);
|
|
2533
|
+
console.log(" You can install manually and re-run.\n");
|
|
2534
|
+
}
|
|
2535
|
+
} else {
|
|
2536
|
+
console.log(
|
|
2537
|
+
" Skipping installation. Generated configs may not work without these packages.\n"
|
|
2538
|
+
);
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
for (const {
|
|
2542
|
+
target,
|
|
2543
|
+
presets,
|
|
2544
|
+
content,
|
|
2545
|
+
hash,
|
|
2546
|
+
configPath,
|
|
2547
|
+
userOverrides
|
|
2548
|
+
} of configsToWrite) {
|
|
2549
|
+
if (await fileExists2(configPath)) {
|
|
2550
|
+
try {
|
|
2551
|
+
const existing = await readFile8(configPath, "utf-8");
|
|
2552
|
+
const existingHash = hashConfig(existing);
|
|
2553
|
+
if (existingHash === hash) {
|
|
2554
|
+
console.log(
|
|
2555
|
+
` ${target.name}: eslint.config.mjs already up to date.`
|
|
2556
|
+
);
|
|
2557
|
+
continue;
|
|
2558
|
+
}
|
|
2559
|
+
} catch {
|
|
2560
|
+
}
|
|
2561
|
+
const overwrite = await confirmProceed(
|
|
2562
|
+
` ${target.name}: eslint.config.mjs already exists. Overwrite? [Y/n] `
|
|
2563
|
+
);
|
|
2564
|
+
if (!overwrite) {
|
|
2565
|
+
console.log(` ${target.name}: Skipped.
|
|
2566
|
+
`);
|
|
2567
|
+
continue;
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
try {
|
|
2571
|
+
await writeFile3(configPath, content, "utf-8");
|
|
2572
|
+
} catch (err) {
|
|
2573
|
+
console.error(
|
|
2574
|
+
` ${target.name}: Failed to write config: ${err instanceof Error ? err.message : String(err)}`
|
|
2575
|
+
);
|
|
2576
|
+
continue;
|
|
2577
|
+
}
|
|
2578
|
+
console.log(` ${target.name}: wrote ${relative2(projectPath, configPath)}`);
|
|
2579
|
+
try {
|
|
2580
|
+
await apiPut(`/repos/${repoId}/eslint-config`, {
|
|
2581
|
+
source_path: target.sourcePath,
|
|
2582
|
+
preset_ids: presets.map((p) => p.id),
|
|
2583
|
+
rule_overrides: userOverrides ?? {},
|
|
2584
|
+
generated_hash: hash
|
|
2585
|
+
});
|
|
2586
|
+
} catch (err) {
|
|
2587
|
+
console.error(
|
|
2588
|
+
` Warning: Failed to save config to server: ${err instanceof Error ? err.message : String(err)}`
|
|
2589
|
+
);
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
console.log("\n ESLint init complete.\n");
|
|
2593
|
+
}
|
|
2594
|
+
async function eslintSync(repoId, projectPath) {
|
|
2595
|
+
console.log("\n ESLint Sync");
|
|
2596
|
+
console.log(` Repo: ${repoId}`);
|
|
2597
|
+
console.log(` Path: ${projectPath}
|
|
2598
|
+
`);
|
|
2599
|
+
let configs;
|
|
2600
|
+
try {
|
|
2601
|
+
const res = await apiGet(
|
|
2602
|
+
`/repos/${repoId}/eslint-config`
|
|
2603
|
+
);
|
|
2604
|
+
configs = res.data ?? [];
|
|
2605
|
+
} catch {
|
|
2606
|
+
console.log(
|
|
2607
|
+
" No existing ESLint config found. Run `codebyplan eslint init` first.\n"
|
|
2608
|
+
);
|
|
2609
|
+
return;
|
|
2610
|
+
}
|
|
2611
|
+
if (configs.length === 0) {
|
|
2612
|
+
console.log(
|
|
2613
|
+
" No ESLint configs registered. Run `codebyplan eslint init` first.\n"
|
|
2614
|
+
);
|
|
2615
|
+
return;
|
|
2616
|
+
}
|
|
2617
|
+
let updatedCount = 0;
|
|
2618
|
+
let skippedCount = 0;
|
|
2619
|
+
let driftCount = 0;
|
|
2620
|
+
for (const config of configs) {
|
|
2621
|
+
const absPath = config.source_path === "." ? projectPath : join7(projectPath, config.source_path);
|
|
2622
|
+
const configPath = join7(absPath, "eslint.config.mjs");
|
|
2623
|
+
const detected = await detectTechStack(absPath);
|
|
2624
|
+
const techNames = detected.flat.map((t) => t.name);
|
|
2625
|
+
const currentPresets = await resolvePresetsForTechStack(techNames);
|
|
2626
|
+
const currentPresetIds = currentPresets.map((p) => p.id).sort();
|
|
2627
|
+
const savedPresetIds = [...config.active_preset_ids ?? []].sort();
|
|
2628
|
+
const presetsChanged = currentPresetIds.length !== savedPresetIds.length || currentPresetIds.some((id) => !savedPresetIds.includes(id));
|
|
2629
|
+
if (!presetsChanged) {
|
|
2630
|
+
if (await fileExists2(configPath)) {
|
|
2631
|
+
try {
|
|
2632
|
+
const currentContent = await readFile8(configPath, "utf-8");
|
|
2633
|
+
const currentHash = hashConfig(currentContent);
|
|
2634
|
+
if (config.generated_hash && currentHash !== config.generated_hash) {
|
|
2635
|
+
console.log(
|
|
2636
|
+
` ${config.source_path}: drift detected (manually edited). Not overwriting.`
|
|
2637
|
+
);
|
|
2638
|
+
driftCount++;
|
|
2639
|
+
continue;
|
|
2640
|
+
}
|
|
2641
|
+
skippedCount++;
|
|
2642
|
+
continue;
|
|
2643
|
+
} catch {
|
|
2644
|
+
console.warn(
|
|
2645
|
+
` ${config.source_path}: config file unreadable, regenerating...`
|
|
2646
|
+
);
|
|
2647
|
+
}
|
|
2648
|
+
} else {
|
|
2649
|
+
console.log(
|
|
2650
|
+
` ${config.source_path}: config file missing, regenerating...`
|
|
2651
|
+
);
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
console.log(` ${config.source_path}: presets changed, regenerating...`);
|
|
2655
|
+
const userOverrides = config.rule_overrides;
|
|
2656
|
+
const content = generateEslintConfig({
|
|
2657
|
+
presets: currentPresets,
|
|
2658
|
+
ruleOverrides: userOverrides && Object.keys(userOverrides).length > 0 ? userOverrides : void 0
|
|
2659
|
+
});
|
|
2660
|
+
try {
|
|
2661
|
+
await writeFile3(configPath, content, "utf-8");
|
|
2662
|
+
} catch (err) {
|
|
2663
|
+
console.error(
|
|
2664
|
+
` ${config.source_path}: Failed to write config: ${err instanceof Error ? err.message : String(err)}`
|
|
2665
|
+
);
|
|
2666
|
+
continue;
|
|
2667
|
+
}
|
|
2668
|
+
const newHash = hashConfig(content);
|
|
2669
|
+
try {
|
|
2670
|
+
await apiPut(`/repos/${repoId}/eslint-config`, {
|
|
2671
|
+
source_path: config.source_path,
|
|
2672
|
+
preset_ids: currentPresetIds,
|
|
2673
|
+
rule_overrides: userOverrides ?? {},
|
|
2674
|
+
generated_hash: newHash
|
|
2675
|
+
});
|
|
2676
|
+
} catch (err) {
|
|
2677
|
+
console.error(
|
|
2678
|
+
` Warning: Failed to update server: ${err instanceof Error ? err.message : String(err)}`
|
|
2679
|
+
);
|
|
2680
|
+
}
|
|
2681
|
+
updatedCount++;
|
|
2682
|
+
}
|
|
2683
|
+
console.log(
|
|
2684
|
+
`
|
|
2685
|
+
Sync: ${updatedCount} updated, ${skippedCount} unchanged, ${driftCount} drift detected.
|
|
2686
|
+
`
|
|
2687
|
+
);
|
|
2688
|
+
}
|
|
2689
|
+
async function checkEslintDrift(repoId, projectPath) {
|
|
2690
|
+
try {
|
|
2691
|
+
const res = await apiGet(
|
|
2692
|
+
`/repos/${repoId}/eslint-config`
|
|
2693
|
+
);
|
|
2694
|
+
const configs = res.data ?? [];
|
|
2695
|
+
for (const config of configs) {
|
|
2696
|
+
if (!config.generated_hash) continue;
|
|
2697
|
+
const absPath = config.source_path === "." ? projectPath : join7(projectPath, config.source_path);
|
|
2698
|
+
const configPath = join7(absPath, "eslint.config.mjs");
|
|
2699
|
+
if (!await fileExists2(configPath)) continue;
|
|
2700
|
+
try {
|
|
2701
|
+
const content = await readFile8(configPath, "utf-8");
|
|
2702
|
+
const currentHash = hashConfig(content);
|
|
2703
|
+
if (currentHash !== config.generated_hash) {
|
|
2704
|
+
return true;
|
|
2705
|
+
}
|
|
2706
|
+
} catch {
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
return false;
|
|
2710
|
+
} catch {
|
|
2711
|
+
return false;
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
async function runEslint() {
|
|
2715
|
+
const subcommand = process.argv[3];
|
|
2716
|
+
const flags = parseFlags(4);
|
|
2717
|
+
validateApiKey();
|
|
2718
|
+
const config = await resolveConfig(flags);
|
|
2719
|
+
const { repoId, projectPath } = config;
|
|
2720
|
+
switch (subcommand) {
|
|
2721
|
+
case "init":
|
|
2722
|
+
await eslintInit(repoId, projectPath);
|
|
2723
|
+
break;
|
|
2724
|
+
case "sync":
|
|
2725
|
+
await eslintSync(repoId, projectPath);
|
|
2726
|
+
break;
|
|
2727
|
+
default:
|
|
2728
|
+
console.log(`
|
|
2729
|
+
Usage:
|
|
2730
|
+
codebyplan eslint init Detect tech stack, resolve presets, generate eslint.config.mjs
|
|
2731
|
+
codebyplan eslint sync Regenerate if presets changed, detect drift
|
|
2732
|
+
`);
|
|
2733
|
+
break;
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
var init_eslint = __esm({
|
|
2737
|
+
"src/cli/eslint.ts"() {
|
|
2738
|
+
"use strict";
|
|
2739
|
+
init_config();
|
|
2740
|
+
init_confirm();
|
|
2741
|
+
init_api();
|
|
2742
|
+
init_tech_detect();
|
|
2743
|
+
init_eslint_generator();
|
|
2744
|
+
}
|
|
2745
|
+
});
|
|
2746
|
+
|
|
2077
2747
|
// src/cli/sync.ts
|
|
2078
2748
|
var sync_exports = {};
|
|
2079
2749
|
__export(sync_exports, {
|
|
2080
2750
|
runSync: () => runSync
|
|
2081
2751
|
});
|
|
2082
|
-
import { createHash } from "node:crypto";
|
|
2083
|
-
import { readFile as
|
|
2084
|
-
import { join as
|
|
2752
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
2753
|
+
import { readFile as readFile9, writeFile as writeFile4, mkdir as mkdir2, chmod as chmod2, unlink as unlink2 } from "node:fs/promises";
|
|
2754
|
+
import { join as join8, dirname as dirname2 } from "node:path";
|
|
2085
2755
|
function contentHash(content) {
|
|
2086
|
-
return
|
|
2756
|
+
return createHash2("sha256").update(content).digest("hex");
|
|
2087
2757
|
}
|
|
2088
2758
|
async function runSync() {
|
|
2089
2759
|
const flags = parseFlags(3);
|
|
@@ -2149,7 +2819,7 @@ async function runSync() {
|
|
|
2149
2819
|
}
|
|
2150
2820
|
async function runSyncInner(repoId, projectPath, dryRun, force, fix = false) {
|
|
2151
2821
|
console.log(" Reading local and remote state...");
|
|
2152
|
-
const claudeDir =
|
|
2822
|
+
const claudeDir = join8(projectPath, ".claude");
|
|
2153
2823
|
let localFiles = /* @__PURE__ */ new Map();
|
|
2154
2824
|
try {
|
|
2155
2825
|
localFiles = await scanLocalFiles(claudeDir, projectPath);
|
|
@@ -2381,7 +3051,7 @@ async function runSyncInner(repoId, projectPath, dryRun, force, fix = false) {
|
|
|
2381
3051
|
for (const p of toPull) {
|
|
2382
3052
|
if (p.filePath && p.remoteContent !== null) {
|
|
2383
3053
|
await mkdir2(dirname2(p.filePath), { recursive: true });
|
|
2384
|
-
await
|
|
3054
|
+
await writeFile4(p.filePath, p.remoteContent, "utf-8");
|
|
2385
3055
|
if (p.isHook) await chmod2(p.filePath, 493);
|
|
2386
3056
|
}
|
|
2387
3057
|
}
|
|
@@ -2525,12 +3195,14 @@ async function runSyncInner(repoId, projectPath, dryRun, force, fix = false) {
|
|
|
2525
3195
|
await syncConfig(repoId, projectPath, dryRun);
|
|
2526
3196
|
console.log(" Tech stack...");
|
|
2527
3197
|
await syncTechStack(repoId, projectPath, dryRun);
|
|
3198
|
+
console.log(" ESLint config...");
|
|
3199
|
+
await syncEslintDriftCheck(repoId, projectPath);
|
|
2528
3200
|
console.log(" Port verification...");
|
|
2529
3201
|
await syncPortVerification(repoId, projectPath, dryRun, fix);
|
|
2530
3202
|
console.log("\n Sync complete.\n");
|
|
2531
3203
|
}
|
|
2532
3204
|
async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun) {
|
|
2533
|
-
const settingsPath =
|
|
3205
|
+
const settingsPath = join8(claudeDir, "settings.json");
|
|
2534
3206
|
const globalSettingsFiles = syncData.global_settings ?? [];
|
|
2535
3207
|
let globalSettings = {};
|
|
2536
3208
|
for (const gf of globalSettingsFiles) {
|
|
@@ -2550,11 +3222,11 @@ async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun)
|
|
|
2550
3222
|
globalSettings,
|
|
2551
3223
|
repoSettings
|
|
2552
3224
|
);
|
|
2553
|
-
const hooksDir =
|
|
3225
|
+
const hooksDir = join8(projectPath, ".claude", "hooks");
|
|
2554
3226
|
const discovered = await discoverHooks(hooksDir);
|
|
2555
3227
|
let localSettings = {};
|
|
2556
3228
|
try {
|
|
2557
|
-
const raw = await
|
|
3229
|
+
const raw = await readFile9(settingsPath, "utf-8");
|
|
2558
3230
|
localSettings = JSON.parse(raw);
|
|
2559
3231
|
} catch {
|
|
2560
3232
|
}
|
|
@@ -2569,7 +3241,7 @@ async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun)
|
|
|
2569
3241
|
const mergedContent = JSON.stringify(merged, null, 2) + "\n";
|
|
2570
3242
|
let currentContent = "";
|
|
2571
3243
|
try {
|
|
2572
|
-
currentContent = await
|
|
3244
|
+
currentContent = await readFile9(settingsPath, "utf-8");
|
|
2573
3245
|
} catch {
|
|
2574
3246
|
}
|
|
2575
3247
|
if (currentContent === mergedContent) {
|
|
@@ -2581,14 +3253,14 @@ async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun)
|
|
|
2581
3253
|
return;
|
|
2582
3254
|
}
|
|
2583
3255
|
await mkdir2(dirname2(settingsPath), { recursive: true });
|
|
2584
|
-
await
|
|
3256
|
+
await writeFile4(settingsPath, mergedContent, "utf-8");
|
|
2585
3257
|
console.log(" Updated settings.json");
|
|
2586
3258
|
}
|
|
2587
3259
|
async function syncConfig(repoId, projectPath, dryRun) {
|
|
2588
|
-
const configPath =
|
|
3260
|
+
const configPath = join8(projectPath, ".codebyplan.json");
|
|
2589
3261
|
let currentConfig = {};
|
|
2590
3262
|
try {
|
|
2591
|
-
const raw = await
|
|
3263
|
+
const raw = await readFile9(configPath, "utf-8");
|
|
2592
3264
|
currentConfig = JSON.parse(raw);
|
|
2593
3265
|
} catch {
|
|
2594
3266
|
currentConfig = { repo_id: repoId };
|
|
@@ -2657,7 +3329,7 @@ async function syncConfig(repoId, projectPath, dryRun) {
|
|
|
2657
3329
|
console.log(" Config would be updated (dry-run).");
|
|
2658
3330
|
return;
|
|
2659
3331
|
}
|
|
2660
|
-
await
|
|
3332
|
+
await writeFile4(configPath, newJson + "\n", "utf-8");
|
|
2661
3333
|
console.log(" Updated .codebyplan.json");
|
|
2662
3334
|
}
|
|
2663
3335
|
async function syncTechStack(repoId, projectPath, dryRun) {
|
|
@@ -2695,6 +3367,20 @@ async function syncTechStack(repoId, projectPath, dryRun) {
|
|
|
2695
3367
|
console.log(" Tech stack detection skipped.");
|
|
2696
3368
|
}
|
|
2697
3369
|
}
|
|
3370
|
+
async function syncEslintDriftCheck(repoId, projectPath) {
|
|
3371
|
+
try {
|
|
3372
|
+
const hasDrift = await checkEslintDrift(repoId, projectPath);
|
|
3373
|
+
if (hasDrift) {
|
|
3374
|
+
console.log(
|
|
3375
|
+
" ESLint config drift detected. Run `codebyplan eslint sync` to update."
|
|
3376
|
+
);
|
|
3377
|
+
} else {
|
|
3378
|
+
console.log(" ESLint configs up to date.");
|
|
3379
|
+
}
|
|
3380
|
+
} catch (error) {
|
|
3381
|
+
console.warn(" ESLint drift check skipped:", error);
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
2698
3384
|
async function syncPortVerification(repoId, projectPath, dryRun, fix) {
|
|
2699
3385
|
try {
|
|
2700
3386
|
const portsRes = await apiGet(
|
|
@@ -2791,28 +3477,28 @@ function getLocalFilePath(claudeDir, projectPath, remote) {
|
|
|
2791
3477
|
hook: { dir: "hooks", ext: ".sh" },
|
|
2792
3478
|
template: { dir: "templates", ext: "" },
|
|
2793
3479
|
context: { dir: "context", ext: ".md" },
|
|
2794
|
-
docs_stack: { dir:
|
|
3480
|
+
docs_stack: { dir: join8("docs", "stack"), ext: ".md" },
|
|
2795
3481
|
docs: { dir: "docs", ext: ".md" },
|
|
2796
3482
|
claude_md: { dir: "", ext: "" },
|
|
2797
3483
|
settings: { dir: "", ext: "" }
|
|
2798
3484
|
};
|
|
2799
|
-
if (remote.type === "claude_md") return
|
|
2800
|
-
if (remote.type === "settings") return
|
|
3485
|
+
if (remote.type === "claude_md") return join8(projectPath, "CLAUDE.md");
|
|
3486
|
+
if (remote.type === "settings") return join8(claudeDir, "settings.json");
|
|
2801
3487
|
const cfg = typeConfig2[remote.type];
|
|
2802
|
-
if (!cfg) return
|
|
2803
|
-
const typeDir = remote.type === "command" ?
|
|
3488
|
+
if (!cfg) return join8(claudeDir, remote.name);
|
|
3489
|
+
const typeDir = remote.type === "command" ? join8(claudeDir, cfg.dir, "cbp") : join8(claudeDir, cfg.dir);
|
|
2804
3490
|
if (cfg.subfolder)
|
|
2805
|
-
return
|
|
3491
|
+
return join8(typeDir, remote.name, `${cfg.subfolder}${cfg.ext}`);
|
|
2806
3492
|
if (remote.type === "command" && remote.category)
|
|
2807
|
-
return
|
|
2808
|
-
if (remote.type === "template") return
|
|
3493
|
+
return join8(typeDir, remote.category, `${remote.name}${cfg.ext}`);
|
|
3494
|
+
if (remote.type === "template") return join8(typeDir, remote.name);
|
|
2809
3495
|
if (remote.category && (remote.type === "context" || remote.type === "docs_stack" || remote.type === "docs"))
|
|
2810
|
-
return
|
|
2811
|
-
return
|
|
3496
|
+
return join8(typeDir, remote.category, `${remote.name}${cfg.ext}`);
|
|
3497
|
+
return join8(typeDir, `${remote.name}${cfg.ext}`);
|
|
2812
3498
|
}
|
|
2813
3499
|
function getSyncVersion() {
|
|
2814
3500
|
try {
|
|
2815
|
-
return "1.
|
|
3501
|
+
return "1.4.1";
|
|
2816
3502
|
} catch {
|
|
2817
3503
|
return "unknown";
|
|
2818
3504
|
}
|
|
@@ -2861,6 +3547,7 @@ var init_sync = __esm({
|
|
|
2861
3547
|
init_settings_merge();
|
|
2862
3548
|
init_hook_registry();
|
|
2863
3549
|
init_port_verify();
|
|
3550
|
+
init_eslint();
|
|
2864
3551
|
}
|
|
2865
3552
|
});
|
|
2866
3553
|
|
|
@@ -2913,6 +3600,20 @@ if (arg === "sync") {
|
|
|
2913
3600
|
}
|
|
2914
3601
|
process.exit(0);
|
|
2915
3602
|
}
|
|
3603
|
+
if (arg === "eslint") {
|
|
3604
|
+
const { runEslint: runEslint2 } = await Promise.resolve().then(() => (init_eslint(), eslint_exports));
|
|
3605
|
+
const { SyncCancelledError: SyncCancelledError2 } = await Promise.resolve().then(() => (init_confirm(), confirm_exports));
|
|
3606
|
+
try {
|
|
3607
|
+
await runEslint2();
|
|
3608
|
+
} catch (err) {
|
|
3609
|
+
if (err instanceof SyncCancelledError2) {
|
|
3610
|
+
console.log("\n Cancelled.\n");
|
|
3611
|
+
process.exit(0);
|
|
3612
|
+
}
|
|
3613
|
+
throw err;
|
|
3614
|
+
}
|
|
3615
|
+
process.exit(0);
|
|
3616
|
+
}
|
|
2916
3617
|
if (arg === "help" || arg === "--help" || arg === "-h" || arg === void 0) {
|
|
2917
3618
|
console.log(`
|
|
2918
3619
|
CodeByPlan CLI v${VERSION}
|
|
@@ -2920,6 +3621,7 @@ if (arg === "help" || arg === "--help" || arg === "-h" || arg === void 0) {
|
|
|
2920
3621
|
Usage:
|
|
2921
3622
|
codebyplan setup Interactive setup (API key + project init + first sync)
|
|
2922
3623
|
codebyplan sync Bidirectional sync (pull + push + config)
|
|
3624
|
+
codebyplan eslint ESLint config management (init, sync)
|
|
2923
3625
|
codebyplan help Show this help message
|
|
2924
3626
|
codebyplan --version Print version
|
|
2925
3627
|
|
|
@@ -2930,6 +3632,10 @@ if (arg === "help" || arg === "--help" || arg === "-h" || arg === void 0) {
|
|
|
2930
3632
|
--force Skip confirmation and conflict prompts
|
|
2931
3633
|
--fix Auto-create missing port allocations
|
|
2932
3634
|
|
|
3635
|
+
ESLint commands:
|
|
3636
|
+
codebyplan eslint init Detect tech stack, resolve presets, generate configs
|
|
3637
|
+
codebyplan eslint sync Regenerate if presets changed, detect drift
|
|
3638
|
+
|
|
2933
3639
|
MCP Server:
|
|
2934
3640
|
Claude Code connects to CodeByPlan via remote MCP:
|
|
2935
3641
|
URL: https://codebyplan.com/mcp
|