codebyplan 1.3.0 → 1.4.0
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 +742 -27
- 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.0";
|
|
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);
|
|
@@ -2224,6 +2894,10 @@ async function runSyncInner(repoId, projectPath, dryRun, force, fix = false) {
|
|
|
2224
2894
|
claudeFileId: null
|
|
2225
2895
|
});
|
|
2226
2896
|
} else if (!local && remote) {
|
|
2897
|
+
const remoteScope = remote.scope ?? "shared";
|
|
2898
|
+
if (remoteScope.startsWith("local:") && remoteScope !== `local:${repoData.name}`) {
|
|
2899
|
+
continue;
|
|
2900
|
+
}
|
|
2227
2901
|
const resolvedContent = substituteVariables(remote.content, repoData);
|
|
2228
2902
|
const hadSyncedThisFile = remote.id ? fileRepoByClaudeFileId.has(remote.id) : fileRepoHashes.has(key);
|
|
2229
2903
|
const recommended = hadSyncedThisFile ? "delete" : "pull";
|
|
@@ -2244,6 +2918,10 @@ async function runSyncInner(repoId, projectPath, dryRun, force, fix = false) {
|
|
|
2244
2918
|
claudeFileId: remote.id ?? null
|
|
2245
2919
|
});
|
|
2246
2920
|
} else if (local && remote) {
|
|
2921
|
+
const remoteScope = remote.scope ?? "shared";
|
|
2922
|
+
if (remoteScope.startsWith("local:") && remoteScope !== `local:${repoData.name}`) {
|
|
2923
|
+
continue;
|
|
2924
|
+
}
|
|
2247
2925
|
const resolvedRemote = substituteVariables(remote.content, repoData);
|
|
2248
2926
|
if (local.content === resolvedRemote) {
|
|
2249
2927
|
continue;
|
|
@@ -2373,7 +3051,7 @@ async function runSyncInner(repoId, projectPath, dryRun, force, fix = false) {
|
|
|
2373
3051
|
for (const p of toPull) {
|
|
2374
3052
|
if (p.filePath && p.remoteContent !== null) {
|
|
2375
3053
|
await mkdir2(dirname2(p.filePath), { recursive: true });
|
|
2376
|
-
await
|
|
3054
|
+
await writeFile4(p.filePath, p.remoteContent, "utf-8");
|
|
2377
3055
|
if (p.isHook) await chmod2(p.filePath, 493);
|
|
2378
3056
|
}
|
|
2379
3057
|
}
|
|
@@ -2517,12 +3195,14 @@ async function runSyncInner(repoId, projectPath, dryRun, force, fix = false) {
|
|
|
2517
3195
|
await syncConfig(repoId, projectPath, dryRun);
|
|
2518
3196
|
console.log(" Tech stack...");
|
|
2519
3197
|
await syncTechStack(repoId, projectPath, dryRun);
|
|
3198
|
+
console.log(" ESLint config...");
|
|
3199
|
+
await syncEslintDriftCheck(repoId, projectPath);
|
|
2520
3200
|
console.log(" Port verification...");
|
|
2521
3201
|
await syncPortVerification(repoId, projectPath, dryRun, fix);
|
|
2522
3202
|
console.log("\n Sync complete.\n");
|
|
2523
3203
|
}
|
|
2524
3204
|
async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun) {
|
|
2525
|
-
const settingsPath =
|
|
3205
|
+
const settingsPath = join8(claudeDir, "settings.json");
|
|
2526
3206
|
const globalSettingsFiles = syncData.global_settings ?? [];
|
|
2527
3207
|
let globalSettings = {};
|
|
2528
3208
|
for (const gf of globalSettingsFiles) {
|
|
@@ -2542,11 +3222,11 @@ async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun)
|
|
|
2542
3222
|
globalSettings,
|
|
2543
3223
|
repoSettings
|
|
2544
3224
|
);
|
|
2545
|
-
const hooksDir =
|
|
3225
|
+
const hooksDir = join8(projectPath, ".claude", "hooks");
|
|
2546
3226
|
const discovered = await discoverHooks(hooksDir);
|
|
2547
3227
|
let localSettings = {};
|
|
2548
3228
|
try {
|
|
2549
|
-
const raw = await
|
|
3229
|
+
const raw = await readFile9(settingsPath, "utf-8");
|
|
2550
3230
|
localSettings = JSON.parse(raw);
|
|
2551
3231
|
} catch {
|
|
2552
3232
|
}
|
|
@@ -2561,7 +3241,7 @@ async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun)
|
|
|
2561
3241
|
const mergedContent = JSON.stringify(merged, null, 2) + "\n";
|
|
2562
3242
|
let currentContent = "";
|
|
2563
3243
|
try {
|
|
2564
|
-
currentContent = await
|
|
3244
|
+
currentContent = await readFile9(settingsPath, "utf-8");
|
|
2565
3245
|
} catch {
|
|
2566
3246
|
}
|
|
2567
3247
|
if (currentContent === mergedContent) {
|
|
@@ -2573,14 +3253,14 @@ async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun)
|
|
|
2573
3253
|
return;
|
|
2574
3254
|
}
|
|
2575
3255
|
await mkdir2(dirname2(settingsPath), { recursive: true });
|
|
2576
|
-
await
|
|
3256
|
+
await writeFile4(settingsPath, mergedContent, "utf-8");
|
|
2577
3257
|
console.log(" Updated settings.json");
|
|
2578
3258
|
}
|
|
2579
3259
|
async function syncConfig(repoId, projectPath, dryRun) {
|
|
2580
|
-
const configPath =
|
|
3260
|
+
const configPath = join8(projectPath, ".codebyplan.json");
|
|
2581
3261
|
let currentConfig = {};
|
|
2582
3262
|
try {
|
|
2583
|
-
const raw = await
|
|
3263
|
+
const raw = await readFile9(configPath, "utf-8");
|
|
2584
3264
|
currentConfig = JSON.parse(raw);
|
|
2585
3265
|
} catch {
|
|
2586
3266
|
currentConfig = { repo_id: repoId };
|
|
@@ -2649,7 +3329,7 @@ async function syncConfig(repoId, projectPath, dryRun) {
|
|
|
2649
3329
|
console.log(" Config would be updated (dry-run).");
|
|
2650
3330
|
return;
|
|
2651
3331
|
}
|
|
2652
|
-
await
|
|
3332
|
+
await writeFile4(configPath, newJson + "\n", "utf-8");
|
|
2653
3333
|
console.log(" Updated .codebyplan.json");
|
|
2654
3334
|
}
|
|
2655
3335
|
async function syncTechStack(repoId, projectPath, dryRun) {
|
|
@@ -2687,6 +3367,20 @@ async function syncTechStack(repoId, projectPath, dryRun) {
|
|
|
2687
3367
|
console.log(" Tech stack detection skipped.");
|
|
2688
3368
|
}
|
|
2689
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
|
+
}
|
|
2690
3384
|
async function syncPortVerification(repoId, projectPath, dryRun, fix) {
|
|
2691
3385
|
try {
|
|
2692
3386
|
const portsRes = await apiGet(
|
|
@@ -2783,28 +3477,28 @@ function getLocalFilePath(claudeDir, projectPath, remote) {
|
|
|
2783
3477
|
hook: { dir: "hooks", ext: ".sh" },
|
|
2784
3478
|
template: { dir: "templates", ext: "" },
|
|
2785
3479
|
context: { dir: "context", ext: ".md" },
|
|
2786
|
-
docs_stack: { dir:
|
|
3480
|
+
docs_stack: { dir: join8("docs", "stack"), ext: ".md" },
|
|
2787
3481
|
docs: { dir: "docs", ext: ".md" },
|
|
2788
3482
|
claude_md: { dir: "", ext: "" },
|
|
2789
3483
|
settings: { dir: "", ext: "" }
|
|
2790
3484
|
};
|
|
2791
|
-
if (remote.type === "claude_md") return
|
|
2792
|
-
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");
|
|
2793
3487
|
const cfg = typeConfig2[remote.type];
|
|
2794
|
-
if (!cfg) return
|
|
2795
|
-
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);
|
|
2796
3490
|
if (cfg.subfolder)
|
|
2797
|
-
return
|
|
3491
|
+
return join8(typeDir, remote.name, `${cfg.subfolder}${cfg.ext}`);
|
|
2798
3492
|
if (remote.type === "command" && remote.category)
|
|
2799
|
-
return
|
|
2800
|
-
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);
|
|
2801
3495
|
if (remote.category && (remote.type === "context" || remote.type === "docs_stack" || remote.type === "docs"))
|
|
2802
|
-
return
|
|
2803
|
-
return
|
|
3496
|
+
return join8(typeDir, remote.category, `${remote.name}${cfg.ext}`);
|
|
3497
|
+
return join8(typeDir, `${remote.name}${cfg.ext}`);
|
|
2804
3498
|
}
|
|
2805
3499
|
function getSyncVersion() {
|
|
2806
3500
|
try {
|
|
2807
|
-
return "1.
|
|
3501
|
+
return "1.4.0";
|
|
2808
3502
|
} catch {
|
|
2809
3503
|
return "unknown";
|
|
2810
3504
|
}
|
|
@@ -2834,7 +3528,8 @@ function flattenSyncData(data) {
|
|
|
2834
3528
|
content: file.content,
|
|
2835
3529
|
category: file.category,
|
|
2836
3530
|
updated_at: file.updated_at,
|
|
2837
|
-
content_hash: file.content_hash
|
|
3531
|
+
content_hash: file.content_hash,
|
|
3532
|
+
scope: file.scope
|
|
2838
3533
|
});
|
|
2839
3534
|
}
|
|
2840
3535
|
}
|
|
@@ -2852,6 +3547,7 @@ var init_sync = __esm({
|
|
|
2852
3547
|
init_settings_merge();
|
|
2853
3548
|
init_hook_registry();
|
|
2854
3549
|
init_port_verify();
|
|
3550
|
+
init_eslint();
|
|
2855
3551
|
}
|
|
2856
3552
|
});
|
|
2857
3553
|
|
|
@@ -2904,6 +3600,20 @@ if (arg === "sync") {
|
|
|
2904
3600
|
}
|
|
2905
3601
|
process.exit(0);
|
|
2906
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
|
+
}
|
|
2907
3617
|
if (arg === "help" || arg === "--help" || arg === "-h" || arg === void 0) {
|
|
2908
3618
|
console.log(`
|
|
2909
3619
|
CodeByPlan CLI v${VERSION}
|
|
@@ -2911,6 +3621,7 @@ if (arg === "help" || arg === "--help" || arg === "-h" || arg === void 0) {
|
|
|
2911
3621
|
Usage:
|
|
2912
3622
|
codebyplan setup Interactive setup (API key + project init + first sync)
|
|
2913
3623
|
codebyplan sync Bidirectional sync (pull + push + config)
|
|
3624
|
+
codebyplan eslint ESLint config management (init, sync)
|
|
2914
3625
|
codebyplan help Show this help message
|
|
2915
3626
|
codebyplan --version Print version
|
|
2916
3627
|
|
|
@@ -2921,6 +3632,10 @@ if (arg === "help" || arg === "--help" || arg === "-h" || arg === void 0) {
|
|
|
2921
3632
|
--force Skip confirmation and conflict prompts
|
|
2922
3633
|
--fix Auto-create missing port allocations
|
|
2923
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
|
+
|
|
2924
3639
|
MCP Server:
|
|
2925
3640
|
Claude Code connects to CodeByPlan via remote MCP:
|
|
2926
3641
|
URL: https://codebyplan.com/mcp
|