@codebyplan/cli 3.1.0 → 3.2.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 +353 -350
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -37,7 +37,7 @@ var VERSION, PACKAGE_NAME;
|
|
|
37
37
|
var init_version = __esm({
|
|
38
38
|
"src/lib/version.ts"() {
|
|
39
39
|
"use strict";
|
|
40
|
-
VERSION = "3.
|
|
40
|
+
VERSION = "3.2.0";
|
|
41
41
|
PACKAGE_NAME = "@codebyplan/cli";
|
|
42
42
|
}
|
|
43
43
|
});
|
|
@@ -1063,35 +1063,89 @@ async function confirmProceed(message) {
|
|
|
1063
1063
|
rl.close();
|
|
1064
1064
|
}
|
|
1065
1065
|
}
|
|
1066
|
-
|
|
1066
|
+
function parseReviewAction(input, fallback) {
|
|
1067
|
+
const a = input.trim().toLowerCase();
|
|
1068
|
+
switch (a) {
|
|
1069
|
+
case "d":
|
|
1070
|
+
case "delete":
|
|
1071
|
+
return { action: "delete", all: false };
|
|
1072
|
+
case "p":
|
|
1073
|
+
case "pull":
|
|
1074
|
+
return { action: "pull", all: false };
|
|
1075
|
+
case "s":
|
|
1076
|
+
case "push":
|
|
1077
|
+
return { action: "push", all: false };
|
|
1078
|
+
case "k":
|
|
1079
|
+
case "skip":
|
|
1080
|
+
return { action: "skip", all: false };
|
|
1081
|
+
case "da":
|
|
1082
|
+
return { action: "delete", all: true };
|
|
1083
|
+
case "pa":
|
|
1084
|
+
return { action: "pull", all: true };
|
|
1085
|
+
case "sa":
|
|
1086
|
+
return { action: "push", all: true };
|
|
1087
|
+
case "ka":
|
|
1088
|
+
return { action: "skip", all: true };
|
|
1089
|
+
case "":
|
|
1090
|
+
return { action: fallback, all: false };
|
|
1091
|
+
default:
|
|
1092
|
+
return { action: fallback, all: false };
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
async function promptReviewMode() {
|
|
1067
1096
|
const rl = createInterface2({ input: stdin2, output: stdout2 });
|
|
1068
1097
|
try {
|
|
1069
|
-
const answer = await rl.question(
|
|
1098
|
+
const answer = await rl.question(" Review [o]ne-by-one or [f]older-by-folder? ");
|
|
1070
1099
|
const a = answer.trim().toLowerCase();
|
|
1071
|
-
return
|
|
1100
|
+
return a === "f" || a === "folder" ? "folder" : "file";
|
|
1072
1101
|
} finally {
|
|
1073
1102
|
rl.close();
|
|
1074
1103
|
}
|
|
1075
1104
|
}
|
|
1076
|
-
async function
|
|
1105
|
+
async function reviewFilesOneByOne(items, label, plannedAction) {
|
|
1077
1106
|
const rl = createInterface2({ input: stdin2, output: stdout2 });
|
|
1078
|
-
const
|
|
1107
|
+
const results = [];
|
|
1079
1108
|
try {
|
|
1109
|
+
let applyAll = null;
|
|
1080
1110
|
for (const item of items) {
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
accepted.push(item, ...items.slice(items.indexOf(item) + 1));
|
|
1085
|
-
break;
|
|
1086
|
-
}
|
|
1087
|
-
if (a === "y" || a === "yes" || a === "") {
|
|
1088
|
-
accepted.push(item);
|
|
1111
|
+
if (applyAll) {
|
|
1112
|
+
results.push(applyAll);
|
|
1113
|
+
continue;
|
|
1089
1114
|
}
|
|
1115
|
+
const planned = plannedAction(item);
|
|
1116
|
+
const answer = await rl.question(
|
|
1117
|
+
` ${label(item)} (${planned}) \u2014 [d]elete [p]ull pu[s]h s[k]ip: `
|
|
1118
|
+
);
|
|
1119
|
+
const { action, all } = parseReviewAction(answer, planned);
|
|
1120
|
+
results.push(action);
|
|
1121
|
+
if (all) applyAll = action;
|
|
1090
1122
|
}
|
|
1091
1123
|
} finally {
|
|
1092
1124
|
rl.close();
|
|
1093
1125
|
}
|
|
1094
|
-
return
|
|
1126
|
+
return results;
|
|
1127
|
+
}
|
|
1128
|
+
async function reviewFolder(folderName, items, label, plannedAction) {
|
|
1129
|
+
console.log(`
|
|
1130
|
+
${folderName} (${items.length} files):`);
|
|
1131
|
+
for (const item of items) {
|
|
1132
|
+
console.log(` ${label(item)} (${plannedAction(item)})`);
|
|
1133
|
+
}
|
|
1134
|
+
const rl = createInterface2({ input: stdin2, output: stdout2 });
|
|
1135
|
+
let answer;
|
|
1136
|
+
try {
|
|
1137
|
+
answer = await rl.question(
|
|
1138
|
+
` Action for all: [d]elete [p]ull pu[s]h s[k]ip [o]ne-by-one: `
|
|
1139
|
+
);
|
|
1140
|
+
} finally {
|
|
1141
|
+
rl.close();
|
|
1142
|
+
}
|
|
1143
|
+
const a = answer.trim().toLowerCase();
|
|
1144
|
+
if (a === "o" || a === "one-by-one") {
|
|
1145
|
+
return reviewFilesOneByOne(items, label, plannedAction);
|
|
1146
|
+
}
|
|
1147
|
+
const { action } = parseReviewAction(a, "skip");
|
|
1148
|
+
return items.map(() => action);
|
|
1095
1149
|
}
|
|
1096
1150
|
var init_confirm = __esm({
|
|
1097
1151
|
"src/cli/confirm.ts"() {
|
|
@@ -1100,7 +1154,7 @@ var init_confirm = __esm({
|
|
|
1100
1154
|
});
|
|
1101
1155
|
|
|
1102
1156
|
// src/lib/tech-detect.ts
|
|
1103
|
-
import { readFile as readFile6, readdir as readdir4
|
|
1157
|
+
import { readFile as readFile6, access, readdir as readdir4 } from "node:fs/promises";
|
|
1104
1158
|
import { join as join6 } from "node:path";
|
|
1105
1159
|
async function fileExists(filePath) {
|
|
1106
1160
|
try {
|
|
@@ -1110,14 +1164,56 @@ async function fileExists(filePath) {
|
|
|
1110
1164
|
return false;
|
|
1111
1165
|
}
|
|
1112
1166
|
}
|
|
1113
|
-
async function
|
|
1167
|
+
async function discoverMonorepoApps(projectPath) {
|
|
1168
|
+
const apps = [];
|
|
1169
|
+
const patterns = [];
|
|
1114
1170
|
try {
|
|
1115
|
-
const raw = await readFile6(
|
|
1171
|
+
const raw = await readFile6(join6(projectPath, "pnpm-workspace.yaml"), "utf-8");
|
|
1172
|
+
const matches = raw.match(/^\s*-\s*['"]?([^'"#\n]+)['"]?/gm);
|
|
1173
|
+
if (matches) {
|
|
1174
|
+
for (const m of matches) {
|
|
1175
|
+
const pattern = m.replace(/^\s*-\s*['"]?/, "").replace(/['"]?\s*$/, "").trim();
|
|
1176
|
+
if (pattern) patterns.push(pattern);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
} catch {
|
|
1180
|
+
}
|
|
1181
|
+
if (patterns.length === 0) {
|
|
1182
|
+
try {
|
|
1183
|
+
const raw = await readFile6(join6(projectPath, "package.json"), "utf-8");
|
|
1184
|
+
const pkg = JSON.parse(raw);
|
|
1185
|
+
const ws = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces?.packages;
|
|
1186
|
+
if (ws) patterns.push(...ws);
|
|
1187
|
+
} catch {
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
for (const pattern of patterns) {
|
|
1191
|
+
if (pattern.endsWith("/*")) {
|
|
1192
|
+
const dir = pattern.slice(0, -2);
|
|
1193
|
+
const absDir = join6(projectPath, dir);
|
|
1194
|
+
try {
|
|
1195
|
+
const entries = await readdir4(absDir, { withFileTypes: true });
|
|
1196
|
+
for (const entry of entries) {
|
|
1197
|
+
if (entry.isDirectory()) {
|
|
1198
|
+
const relPath = join6(dir, entry.name);
|
|
1199
|
+
const absPath = join6(absDir, entry.name);
|
|
1200
|
+
if (await fileExists(join6(absPath, "package.json"))) {
|
|
1201
|
+
apps.push({ name: entry.name, path: relPath, absPath });
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
} catch {
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
return apps;
|
|
1210
|
+
}
|
|
1211
|
+
async function detectFromDirectory(dirPath) {
|
|
1212
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1213
|
+
try {
|
|
1214
|
+
const raw = await readFile6(join6(dirPath, "package.json"), "utf-8");
|
|
1116
1215
|
const pkg = JSON.parse(raw);
|
|
1117
|
-
const allDeps = {
|
|
1118
|
-
...pkg.dependencies,
|
|
1119
|
-
...pkg.devDependencies
|
|
1120
|
-
};
|
|
1216
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1121
1217
|
for (const depName of Object.keys(allDeps)) {
|
|
1122
1218
|
const rule = PACKAGE_MAP[depName];
|
|
1123
1219
|
if (rule) {
|
|
@@ -1125,79 +1221,121 @@ async function scanPackageJson(pkgPath, seen) {
|
|
|
1125
1221
|
if (!seen.has(key)) {
|
|
1126
1222
|
seen.set(key, { name: rule.name, category: rule.category });
|
|
1127
1223
|
}
|
|
1224
|
+
continue;
|
|
1225
|
+
}
|
|
1226
|
+
for (const { prefix, rule: prefixRule } of PACKAGE_PREFIX_MAP) {
|
|
1227
|
+
if (depName.startsWith(prefix)) {
|
|
1228
|
+
const key = prefixRule.name.toLowerCase();
|
|
1229
|
+
if (!seen.has(key)) {
|
|
1230
|
+
seen.set(key, { name: prefixRule.name, category: prefixRule.category });
|
|
1231
|
+
}
|
|
1232
|
+
break;
|
|
1233
|
+
}
|
|
1128
1234
|
}
|
|
1129
1235
|
}
|
|
1130
1236
|
} catch {
|
|
1131
1237
|
}
|
|
1132
|
-
}
|
|
1133
|
-
async function scanConfigFiles(dir, seen) {
|
|
1134
1238
|
for (const { file, rule } of CONFIG_FILE_MAP) {
|
|
1135
1239
|
const key = rule.name.toLowerCase();
|
|
1136
|
-
if (!seen.has(key) && await fileExists(join6(
|
|
1240
|
+
if (!seen.has(key) && await fileExists(join6(dirPath, file))) {
|
|
1137
1241
|
seen.set(key, { name: rule.name, category: rule.category });
|
|
1138
1242
|
}
|
|
1139
1243
|
}
|
|
1244
|
+
return Array.from(seen.values()).sort((a, b) => {
|
|
1245
|
+
const catCmp = a.category.localeCompare(b.category);
|
|
1246
|
+
if (catCmp !== 0) return catCmp;
|
|
1247
|
+
return a.name.localeCompare(b.name);
|
|
1248
|
+
});
|
|
1140
1249
|
}
|
|
1141
|
-
async function
|
|
1142
|
-
const
|
|
1143
|
-
const
|
|
1144
|
-
|
|
1145
|
-
const
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
for (const entry of entries) {
|
|
1150
|
-
if (entry.isDirectory()) {
|
|
1151
|
-
dirs.push(join6(projectPath, parent, entry.name));
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
} catch {
|
|
1250
|
+
async function detectTechStack(projectPath) {
|
|
1251
|
+
const repo = await detectFromDirectory(projectPath);
|
|
1252
|
+
const discoveredApps = await discoverMonorepoApps(projectPath);
|
|
1253
|
+
const apps = [];
|
|
1254
|
+
for (const app of discoveredApps) {
|
|
1255
|
+
const stack = await detectFromDirectory(app.absPath);
|
|
1256
|
+
if (stack.length > 0) {
|
|
1257
|
+
apps.push({ name: app.name, path: app.path, stack });
|
|
1155
1258
|
}
|
|
1156
1259
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
const seen = /* @__PURE__ */ new Map();
|
|
1161
|
-
await scanPackageJson(join6(projectPath, "package.json"), seen);
|
|
1162
|
-
await scanConfigFiles(projectPath, seen);
|
|
1163
|
-
const workspaceDirs = await detectWorkspaceDirs(projectPath);
|
|
1164
|
-
for (const dir of workspaceDirs) {
|
|
1165
|
-
await scanPackageJson(join6(dir, "package.json"), seen);
|
|
1166
|
-
await scanConfigFiles(dir, seen);
|
|
1260
|
+
const flatMap = /* @__PURE__ */ new Map();
|
|
1261
|
+
for (const entry of repo) {
|
|
1262
|
+
flatMap.set(entry.name.toLowerCase(), entry);
|
|
1167
1263
|
}
|
|
1168
|
-
|
|
1264
|
+
for (const app of apps) {
|
|
1265
|
+
for (const entry of app.stack) {
|
|
1266
|
+
const key = entry.name.toLowerCase();
|
|
1267
|
+
if (!flatMap.has(key)) {
|
|
1268
|
+
flatMap.set(key, entry);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
const flat = Array.from(flatMap.values()).sort((a, b) => {
|
|
1169
1273
|
const catCmp = a.category.localeCompare(b.category);
|
|
1170
1274
|
if (catCmp !== 0) return catCmp;
|
|
1171
1275
|
return a.name.localeCompare(b.name);
|
|
1172
1276
|
});
|
|
1277
|
+
return { repo, apps, flat };
|
|
1173
1278
|
}
|
|
1174
1279
|
function mergeTechStack(remote, detected) {
|
|
1280
|
+
const remoteResult = Array.isArray(remote) ? { repo: remote, apps: [], flat: remote } : remote;
|
|
1175
1281
|
const seen = /* @__PURE__ */ new Map();
|
|
1176
|
-
for (const entry of
|
|
1282
|
+
for (const entry of remoteResult.flat) {
|
|
1177
1283
|
seen.set(entry.name.toLowerCase(), entry);
|
|
1178
1284
|
}
|
|
1179
1285
|
const added = [];
|
|
1180
|
-
for (const entry of detected) {
|
|
1286
|
+
for (const entry of detected.flat) {
|
|
1181
1287
|
const key = entry.name.toLowerCase();
|
|
1182
1288
|
if (!seen.has(key)) {
|
|
1183
1289
|
seen.set(key, entry);
|
|
1184
1290
|
added.push(entry);
|
|
1185
1291
|
}
|
|
1186
1292
|
}
|
|
1187
|
-
const
|
|
1293
|
+
const flat = Array.from(seen.values()).sort((a, b) => {
|
|
1188
1294
|
const catCmp = a.category.localeCompare(b.category);
|
|
1189
1295
|
if (catCmp !== 0) return catCmp;
|
|
1190
1296
|
return a.name.localeCompare(b.name);
|
|
1191
1297
|
});
|
|
1298
|
+
const merged = {
|
|
1299
|
+
repo: detected.repo,
|
|
1300
|
+
apps: detected.apps,
|
|
1301
|
+
flat
|
|
1302
|
+
};
|
|
1192
1303
|
return { merged, added };
|
|
1193
1304
|
}
|
|
1194
1305
|
function parseTechStack(raw) {
|
|
1306
|
+
if (Array.isArray(raw)) {
|
|
1307
|
+
return raw.filter(
|
|
1308
|
+
(item) => typeof item === "object" && item !== null && typeof item.name === "string" && typeof item.category === "string"
|
|
1309
|
+
);
|
|
1310
|
+
}
|
|
1311
|
+
if (typeof raw === "object" && raw !== null && "flat" in raw) {
|
|
1312
|
+
return parseTechStack(raw.flat);
|
|
1313
|
+
}
|
|
1314
|
+
return [];
|
|
1315
|
+
}
|
|
1316
|
+
function parseAppTechStacks(raw) {
|
|
1195
1317
|
if (!Array.isArray(raw)) return [];
|
|
1196
1318
|
return raw.filter(
|
|
1197
|
-
(item) => typeof item === "object" && item !== null && typeof item.name === "string" && typeof item.
|
|
1198
|
-
)
|
|
1319
|
+
(item) => typeof item === "object" && item !== null && typeof item.name === "string" && typeof item.path === "string" && Array.isArray(item.stack)
|
|
1320
|
+
).map((item) => ({
|
|
1321
|
+
name: item.name,
|
|
1322
|
+
path: item.path,
|
|
1323
|
+
stack: parseTechStack(item.stack)
|
|
1324
|
+
}));
|
|
1325
|
+
}
|
|
1326
|
+
function parseTechStackResult(raw) {
|
|
1327
|
+
if (typeof raw === "object" && raw !== null && !Array.isArray(raw) && "flat" in raw) {
|
|
1328
|
+
const obj = raw;
|
|
1329
|
+
return {
|
|
1330
|
+
repo: parseTechStack(obj.repo),
|
|
1331
|
+
apps: parseAppTechStacks(obj.apps),
|
|
1332
|
+
flat: parseTechStack(obj.flat)
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
const flat = parseTechStack(raw);
|
|
1336
|
+
return { repo: flat, apps: [], flat };
|
|
1199
1337
|
}
|
|
1200
|
-
var PACKAGE_MAP, CONFIG_FILE_MAP;
|
|
1338
|
+
var PACKAGE_MAP, PACKAGE_PREFIX_MAP, CONFIG_FILE_MAP;
|
|
1201
1339
|
var init_tech_detect = __esm({
|
|
1202
1340
|
"src/lib/tech-detect.ts"() {
|
|
1203
1341
|
"use strict";
|
|
@@ -1240,17 +1378,50 @@ var init_tech_detect = __esm({
|
|
|
1240
1378
|
playwright: { name: "Playwright", category: "testing" },
|
|
1241
1379
|
"@playwright/test": { name: "Playwright", category: "testing" },
|
|
1242
1380
|
cypress: { name: "Cypress", category: "testing" },
|
|
1381
|
+
supertest: { name: "Supertest", category: "testing" },
|
|
1243
1382
|
// Build tools
|
|
1244
1383
|
turbo: { name: "Turborepo", category: "build" },
|
|
1245
1384
|
vite: { name: "Vite", category: "build" },
|
|
1246
1385
|
webpack: { name: "Webpack", category: "build" },
|
|
1247
1386
|
esbuild: { name: "esbuild", category: "build" },
|
|
1248
1387
|
rollup: { name: "Rollup", category: "build" },
|
|
1388
|
+
nx: { name: "Nx", category: "build" },
|
|
1389
|
+
lerna: { name: "Lerna", category: "build" },
|
|
1390
|
+
tsup: { name: "tsup", category: "build" },
|
|
1391
|
+
"@swc/core": { name: "SWC", category: "build" },
|
|
1392
|
+
parcel: { name: "Parcel", category: "build" },
|
|
1249
1393
|
// Tools
|
|
1250
1394
|
eslint: { name: "ESLint", category: "tool" },
|
|
1251
1395
|
prettier: { name: "Prettier", category: "tool" },
|
|
1252
|
-
"@biomejs/biome": { name: "Biome", category: "tool" }
|
|
1253
|
-
|
|
1396
|
+
"@biomejs/biome": { name: "Biome", category: "tool" },
|
|
1397
|
+
storybook: { name: "Storybook", category: "tool" },
|
|
1398
|
+
// Component libs
|
|
1399
|
+
"@mui/material": { name: "MUI", category: "component-lib" },
|
|
1400
|
+
"@chakra-ui/react": { name: "Chakra UI", category: "component-lib" },
|
|
1401
|
+
"@mantine/core": { name: "Mantine", category: "component-lib" },
|
|
1402
|
+
// GraphQL
|
|
1403
|
+
graphql: { name: "GraphQL", category: "graphql" },
|
|
1404
|
+
"@apollo/client": { name: "Apollo Client", category: "graphql" },
|
|
1405
|
+
urql: { name: "urql", category: "graphql" },
|
|
1406
|
+
"graphql-request": { name: "graphql-request", category: "graphql" },
|
|
1407
|
+
// Documentation
|
|
1408
|
+
typedoc: { name: "TypeDoc", category: "documentation" },
|
|
1409
|
+
"@docusaurus/core": { name: "Docusaurus", category: "documentation" },
|
|
1410
|
+
vitepress: { name: "VitePress", category: "documentation" },
|
|
1411
|
+
// Code quality
|
|
1412
|
+
husky: { name: "Husky", category: "quality" },
|
|
1413
|
+
"lint-staged": { name: "lint-staged", category: "quality" },
|
|
1414
|
+
commitlint: { name: "commitlint", category: "quality" },
|
|
1415
|
+
"@commitlint/cli": { name: "commitlint", category: "quality" },
|
|
1416
|
+
// Mobile
|
|
1417
|
+
"react-native": { name: "React Native", category: "mobile" },
|
|
1418
|
+
expo: { name: "Expo", category: "mobile" }
|
|
1419
|
+
};
|
|
1420
|
+
PACKAGE_PREFIX_MAP = [
|
|
1421
|
+
{ prefix: "@radix-ui/", rule: { name: "Radix UI", category: "component-lib" } },
|
|
1422
|
+
{ prefix: "@storybook/", rule: { name: "Storybook", category: "tool" } },
|
|
1423
|
+
{ prefix: "@testing-library/", rule: { name: "Testing Library", category: "testing" } }
|
|
1424
|
+
];
|
|
1254
1425
|
CONFIG_FILE_MAP = [
|
|
1255
1426
|
{ file: "tsconfig.json", rule: { name: "TypeScript", category: "language" } },
|
|
1256
1427
|
{ file: "next.config.js", rule: { name: "Next.js", category: "framework" } },
|
|
@@ -1262,131 +1433,24 @@ var init_tech_detect = __esm({
|
|
|
1262
1433
|
{ file: "docker-compose.yml", rule: { name: "Docker", category: "deployment" } },
|
|
1263
1434
|
{ file: "docker-compose.yaml", rule: { name: "Docker", category: "deployment" } },
|
|
1264
1435
|
{ file: "Dockerfile", rule: { name: "Docker", category: "deployment" } },
|
|
1265
|
-
{ file: "vercel.json", rule: { name: "Vercel", category: "deployment" } }
|
|
1436
|
+
{ file: "vercel.json", rule: { name: "Vercel", category: "deployment" } },
|
|
1437
|
+
{ file: ".storybook/main.js", rule: { name: "Storybook", category: "tool" } },
|
|
1438
|
+
{ file: ".storybook/main.ts", rule: { name: "Storybook", category: "tool" } },
|
|
1439
|
+
{ file: ".storybook/main.mjs", rule: { name: "Storybook", category: "tool" } },
|
|
1440
|
+
{ file: "components.json", rule: { name: "shadcn/ui", category: "component-lib" } },
|
|
1441
|
+
{ file: "nx.json", rule: { name: "Nx", category: "build" } },
|
|
1442
|
+
{ file: "lerna.json", rule: { name: "Lerna", category: "build" } }
|
|
1266
1443
|
];
|
|
1267
1444
|
}
|
|
1268
1445
|
});
|
|
1269
1446
|
|
|
1270
|
-
// src/lib/server-detect.ts
|
|
1271
|
-
import { readFile as readFile7, readdir as readdir5, access as access2 } from "node:fs/promises";
|
|
1272
|
-
import { join as join7 } from "node:path";
|
|
1273
|
-
async function fileExists2(filePath) {
|
|
1274
|
-
try {
|
|
1275
|
-
await access2(filePath);
|
|
1276
|
-
return true;
|
|
1277
|
-
} catch {
|
|
1278
|
-
return false;
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
|
-
function detectPackageManager(dir) {
|
|
1282
|
-
return (async () => {
|
|
1283
|
-
if (await fileExists2(join7(dir, "pnpm-lock.yaml"))) return "pnpm";
|
|
1284
|
-
if (await fileExists2(join7(dir, "yarn.lock"))) return "yarn";
|
|
1285
|
-
return "npm";
|
|
1286
|
-
})();
|
|
1287
|
-
}
|
|
1288
|
-
function detectFramework(pkg) {
|
|
1289
|
-
const deps = pkg.dependencies ?? {};
|
|
1290
|
-
const devDeps = pkg.devDependencies ?? {};
|
|
1291
|
-
const hasDep = (name) => name in deps || name in devDeps;
|
|
1292
|
-
if (hasDep("next")) return "nextjs";
|
|
1293
|
-
if (hasDep("@tauri-apps/api") || hasDep("@tauri-apps/cli")) return "tauri";
|
|
1294
|
-
if (hasDep("expo")) return "expo";
|
|
1295
|
-
if (hasDep("vite")) return "vite";
|
|
1296
|
-
if (hasDep("express")) return "express";
|
|
1297
|
-
if (hasDep("@nestjs/core")) return "nestjs";
|
|
1298
|
-
return "custom";
|
|
1299
|
-
}
|
|
1300
|
-
function detectPortFromScripts(pkg) {
|
|
1301
|
-
const scripts = pkg.scripts;
|
|
1302
|
-
if (!scripts?.dev) return null;
|
|
1303
|
-
const parts = scripts.dev.split(/\s+/);
|
|
1304
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
1305
|
-
if (parts[i] === "--port" || parts[i] === "-p") {
|
|
1306
|
-
const next = parts[i + 1];
|
|
1307
|
-
if (next) {
|
|
1308
|
-
const port = parseInt(next, 10);
|
|
1309
|
-
if (!isNaN(port)) return port;
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
return null;
|
|
1314
|
-
}
|
|
1315
|
-
async function isMonorepo(dir) {
|
|
1316
|
-
return await fileExists2(join7(dir, "turbo.json")) || await fileExists2(join7(dir, "pnpm-workspace.yaml"));
|
|
1317
|
-
}
|
|
1318
|
-
async function detectServers(projectPath) {
|
|
1319
|
-
let pkg;
|
|
1320
|
-
try {
|
|
1321
|
-
const raw = await readFile7(join7(projectPath, "package.json"), "utf-8");
|
|
1322
|
-
pkg = JSON.parse(raw);
|
|
1323
|
-
} catch {
|
|
1324
|
-
return {
|
|
1325
|
-
name: "unknown",
|
|
1326
|
-
isMonorepo: false,
|
|
1327
|
-
packageManager: "npm",
|
|
1328
|
-
servers: []
|
|
1329
|
-
};
|
|
1330
|
-
}
|
|
1331
|
-
const rawName = pkg.name ?? "unknown";
|
|
1332
|
-
const name = rawName.startsWith("@") ? projectPath.split("/").pop() ?? rawName : rawName;
|
|
1333
|
-
const pkgManager = await detectPackageManager(projectPath);
|
|
1334
|
-
const mono = await isMonorepo(projectPath);
|
|
1335
|
-
const servers = [];
|
|
1336
|
-
if (mono) {
|
|
1337
|
-
const appsDir = join7(projectPath, "apps");
|
|
1338
|
-
try {
|
|
1339
|
-
const entries = await readdir5(appsDir, { withFileTypes: true });
|
|
1340
|
-
const sorted = [...entries].sort((a, b) => a.name.localeCompare(b.name));
|
|
1341
|
-
for (const entry of sorted) {
|
|
1342
|
-
if (!entry.isDirectory()) continue;
|
|
1343
|
-
const appPkgPath = join7(appsDir, entry.name, "package.json");
|
|
1344
|
-
try {
|
|
1345
|
-
const appRaw = await readFile7(appPkgPath, "utf-8");
|
|
1346
|
-
const appPkg = JSON.parse(appRaw);
|
|
1347
|
-
const appName = entry.name;
|
|
1348
|
-
const framework = detectFramework(appPkg);
|
|
1349
|
-
const port = detectPortFromScripts(appPkg);
|
|
1350
|
-
let command;
|
|
1351
|
-
switch (pkgManager) {
|
|
1352
|
-
case "pnpm":
|
|
1353
|
-
command = `pnpm --filter ${appName} dev`;
|
|
1354
|
-
break;
|
|
1355
|
-
case "yarn":
|
|
1356
|
-
command = `yarn workspace ${appName} dev`;
|
|
1357
|
-
break;
|
|
1358
|
-
default:
|
|
1359
|
-
command = `npm run dev -w apps/${appName}`;
|
|
1360
|
-
break;
|
|
1361
|
-
}
|
|
1362
|
-
servers.push({ label: appName, port, command, server_type: framework });
|
|
1363
|
-
} catch {
|
|
1364
|
-
}
|
|
1365
|
-
}
|
|
1366
|
-
} catch {
|
|
1367
|
-
}
|
|
1368
|
-
} else {
|
|
1369
|
-
const framework = detectFramework(pkg);
|
|
1370
|
-
const port = detectPortFromScripts(pkg);
|
|
1371
|
-
const command = `${pkgManager} run dev`;
|
|
1372
|
-
servers.push({ label: "dev", port, command, server_type: framework });
|
|
1373
|
-
}
|
|
1374
|
-
return { name, isMonorepo: mono, packageManager: pkgManager, servers };
|
|
1375
|
-
}
|
|
1376
|
-
var init_server_detect = __esm({
|
|
1377
|
-
"src/lib/server-detect.ts"() {
|
|
1378
|
-
"use strict";
|
|
1379
|
-
}
|
|
1380
|
-
});
|
|
1381
|
-
|
|
1382
1447
|
// src/cli/sync.ts
|
|
1383
1448
|
var sync_exports = {};
|
|
1384
1449
|
__export(sync_exports, {
|
|
1385
1450
|
runSync: () => runSync
|
|
1386
1451
|
});
|
|
1387
|
-
import { readFile as
|
|
1388
|
-
import {
|
|
1389
|
-
import { join as join8, dirname as dirname2 } from "node:path";
|
|
1452
|
+
import { readFile as readFile7, writeFile as writeFile3, mkdir as mkdir2, chmod as chmod2, unlink as unlink2 } from "node:fs/promises";
|
|
1453
|
+
import { join as join7, dirname as dirname2 } from "node:path";
|
|
1390
1454
|
async function runSync() {
|
|
1391
1455
|
const flags = parseFlags(3);
|
|
1392
1456
|
const dryRun = hasFlag("dry-run", 3);
|
|
@@ -1402,7 +1466,7 @@ async function runSync() {
|
|
|
1402
1466
|
if (force) console.log(` Mode: force`);
|
|
1403
1467
|
console.log();
|
|
1404
1468
|
console.log(" Reading local and remote state...");
|
|
1405
|
-
const claudeDir =
|
|
1469
|
+
const claudeDir = join7(projectPath, ".claude");
|
|
1406
1470
|
let localFiles = /* @__PURE__ */ new Map();
|
|
1407
1471
|
try {
|
|
1408
1472
|
localFiles = await scanLocalFiles(claudeDir, projectPath);
|
|
@@ -1432,7 +1496,7 @@ async function runSync() {
|
|
|
1432
1496
|
localContent: local.content,
|
|
1433
1497
|
remoteContent: null,
|
|
1434
1498
|
pushContent: reverseSubstituteVariables(local.content, repoData),
|
|
1435
|
-
filePath:
|
|
1499
|
+
filePath: getLocalFilePath(claudeDir, projectPath, { type: local.type, name: local.name, category: local.category }),
|
|
1436
1500
|
type: local.type,
|
|
1437
1501
|
name: local.name,
|
|
1438
1502
|
category: local.category,
|
|
@@ -1475,7 +1539,7 @@ async function runSync() {
|
|
|
1475
1539
|
action,
|
|
1476
1540
|
localContent: local.content,
|
|
1477
1541
|
remoteContent: resolvedRemote,
|
|
1478
|
-
pushContent:
|
|
1542
|
+
pushContent: reverseSubstituteVariables(local.content, repoData),
|
|
1479
1543
|
filePath: getLocalFilePath(claudeDir, projectPath, remote),
|
|
1480
1544
|
type: local.type,
|
|
1481
1545
|
name: local.name,
|
|
@@ -1505,74 +1569,87 @@ async function runSync() {
|
|
|
1505
1569
|
console.log(" All .claude/ files in sync.");
|
|
1506
1570
|
}
|
|
1507
1571
|
if (plan.length > 0 && !dryRun) {
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
Apply ${toPull.length} pull(s), ${pushes.length} push(es), ${toDelete.length} deletion(s)? [Y/n] `
|
|
1536
|
-
);
|
|
1537
|
-
if (!confirmed) {
|
|
1538
|
-
console.log(" Cancelled.\n");
|
|
1539
|
-
return;
|
|
1572
|
+
if (!force) {
|
|
1573
|
+
const agreed = await confirmProceed(`
|
|
1574
|
+
Agree with sync? [Y/n] `);
|
|
1575
|
+
if (!agreed) {
|
|
1576
|
+
const mode = await promptReviewMode();
|
|
1577
|
+
if (mode === "file") {
|
|
1578
|
+
const actions = await reviewFilesOneByOne(
|
|
1579
|
+
plan,
|
|
1580
|
+
(p) => p.displayPath,
|
|
1581
|
+
(p) => p.action
|
|
1582
|
+
);
|
|
1583
|
+
for (let i = 0; i < plan.length; i++) {
|
|
1584
|
+
plan[i].action = actions[i];
|
|
1585
|
+
}
|
|
1586
|
+
} else {
|
|
1587
|
+
const groups = groupByType(plan);
|
|
1588
|
+
for (const [typeName, items] of groups) {
|
|
1589
|
+
const actions = await reviewFolder(
|
|
1590
|
+
typeName,
|
|
1591
|
+
items,
|
|
1592
|
+
(p) => p.displayPath,
|
|
1593
|
+
(p) => p.action
|
|
1594
|
+
);
|
|
1595
|
+
for (let i = 0; i < items.length; i++) {
|
|
1596
|
+
items[i].action = actions[i];
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1540
1599
|
}
|
|
1541
1600
|
}
|
|
1542
1601
|
}
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1602
|
+
const toPull = plan.filter((p) => p.action === "pull");
|
|
1603
|
+
const toPush = plan.filter((p) => p.action === "push");
|
|
1604
|
+
const toDelete = plan.filter((p) => p.action === "delete");
|
|
1605
|
+
const skipped = plan.filter((p) => p.action === "skip");
|
|
1606
|
+
if (toPull.length + toPush.length + toDelete.length === 0) {
|
|
1607
|
+
console.log("\n All items skipped \u2014 no changes applied.");
|
|
1608
|
+
} else {
|
|
1609
|
+
for (const p of toPull) {
|
|
1610
|
+
if (p.filePath && p.remoteContent !== null) {
|
|
1611
|
+
await mkdir2(dirname2(p.filePath), { recursive: true });
|
|
1612
|
+
await writeFile3(p.filePath, p.remoteContent, "utf-8");
|
|
1613
|
+
if (p.isHook) await chmod2(p.filePath, 493);
|
|
1614
|
+
}
|
|
1548
1615
|
}
|
|
1549
|
-
|
|
1550
|
-
const toUpsert = pushes.filter((p) => p.pushContent !== null).map((p) => ({
|
|
1551
|
-
type: p.type,
|
|
1552
|
-
name: p.name,
|
|
1553
|
-
category: p.category,
|
|
1554
|
-
content: p.pushContent
|
|
1555
|
-
}));
|
|
1556
|
-
if (toUpsert.length > 0) {
|
|
1557
|
-
await apiPost("/sync/files", {
|
|
1558
|
-
repo_id: repoId,
|
|
1559
|
-
files: toUpsert
|
|
1560
|
-
});
|
|
1561
|
-
}
|
|
1562
|
-
if (toDelete.length > 0) {
|
|
1563
|
-
const deleteKeys = toDelete.map((p) => ({
|
|
1616
|
+
const toUpsert = toPush.filter((p) => p.pushContent !== null).map((p) => ({
|
|
1564
1617
|
type: p.type,
|
|
1565
1618
|
name: p.name,
|
|
1566
|
-
category: p.category
|
|
1619
|
+
category: p.category,
|
|
1620
|
+
content: p.pushContent
|
|
1567
1621
|
}));
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1622
|
+
if (toUpsert.length > 0) {
|
|
1623
|
+
await apiPost("/sync/files", {
|
|
1624
|
+
repo_id: repoId,
|
|
1625
|
+
files: toUpsert
|
|
1626
|
+
});
|
|
1627
|
+
}
|
|
1628
|
+
if (toDelete.length > 0) {
|
|
1629
|
+
const deleteKeys = toDelete.map((p) => ({
|
|
1630
|
+
type: p.type,
|
|
1631
|
+
name: p.name,
|
|
1632
|
+
category: p.category
|
|
1633
|
+
}));
|
|
1634
|
+
await apiPost("/sync/files", {
|
|
1635
|
+
repo_id: repoId,
|
|
1636
|
+
delete_keys: deleteKeys
|
|
1637
|
+
});
|
|
1638
|
+
for (const p of toDelete) {
|
|
1639
|
+
if (p.filePath) {
|
|
1640
|
+
try {
|
|
1641
|
+
await unlink2(p.filePath);
|
|
1642
|
+
} catch {
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
await apiPut(`/repos/${repoId}`, { claude_sync_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1648
|
+
console.log(
|
|
1649
|
+
`
|
|
1650
|
+
Applied: ${toPull.length} pulled, ${toPush.length} pushed, ${toDelete.length} deleted` + (skipped.length > 0 ? `, ${skipped.length} skipped` : "")
|
|
1651
|
+
);
|
|
1572
1652
|
}
|
|
1573
|
-
await apiPut(`/repos/${repoId}`, { claude_sync_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1574
|
-
console.log(`
|
|
1575
|
-
Applied: ${toPull.length} pulled, ${pushes.length} pushed, ${toDelete.length} deleted from DB`);
|
|
1576
1653
|
} else if (dryRun) {
|
|
1577
1654
|
console.log("\n (dry-run \u2014 no changes)");
|
|
1578
1655
|
}
|
|
@@ -1582,12 +1659,10 @@ async function runSync() {
|
|
|
1582
1659
|
await syncConfig(repoId, projectPath, dryRun);
|
|
1583
1660
|
console.log(" Tech stack...");
|
|
1584
1661
|
await syncTechStack(repoId, projectPath, dryRun);
|
|
1585
|
-
console.log(" Desktop server configs...");
|
|
1586
|
-
await syncDesktopConfigs(repoId, projectPath, repoData, dryRun);
|
|
1587
1662
|
console.log("\n Sync complete.\n");
|
|
1588
1663
|
}
|
|
1589
1664
|
async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun) {
|
|
1590
|
-
const settingsPath =
|
|
1665
|
+
const settingsPath = join7(claudeDir, "settings.json");
|
|
1591
1666
|
const globalSettingsFiles = syncData.global_settings ?? [];
|
|
1592
1667
|
let globalSettings = {};
|
|
1593
1668
|
for (const gf of globalSettingsFiles) {
|
|
@@ -1600,11 +1675,11 @@ async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun)
|
|
|
1600
1675
|
repoSettings = JSON.parse(substituteVariables(rf.content, repoData));
|
|
1601
1676
|
}
|
|
1602
1677
|
const combinedTemplate = mergeGlobalAndRepoSettings(globalSettings, repoSettings);
|
|
1603
|
-
const hooksDir =
|
|
1678
|
+
const hooksDir = join7(projectPath, ".claude", "hooks");
|
|
1604
1679
|
const discovered = await discoverHooks(hooksDir);
|
|
1605
1680
|
let localSettings = {};
|
|
1606
1681
|
try {
|
|
1607
|
-
const raw = await
|
|
1682
|
+
const raw = await readFile7(settingsPath, "utf-8");
|
|
1608
1683
|
localSettings = JSON.parse(raw);
|
|
1609
1684
|
} catch {
|
|
1610
1685
|
}
|
|
@@ -1619,7 +1694,7 @@ async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun)
|
|
|
1619
1694
|
const mergedContent = JSON.stringify(merged, null, 2) + "\n";
|
|
1620
1695
|
let currentContent = "";
|
|
1621
1696
|
try {
|
|
1622
|
-
currentContent = await
|
|
1697
|
+
currentContent = await readFile7(settingsPath, "utf-8");
|
|
1623
1698
|
} catch {
|
|
1624
1699
|
}
|
|
1625
1700
|
if (currentContent === mergedContent) {
|
|
@@ -1635,10 +1710,10 @@ async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun)
|
|
|
1635
1710
|
console.log(" Updated settings.json");
|
|
1636
1711
|
}
|
|
1637
1712
|
async function syncConfig(repoId, projectPath, dryRun) {
|
|
1638
|
-
const configPath =
|
|
1713
|
+
const configPath = join7(projectPath, ".codebyplan.json");
|
|
1639
1714
|
let currentConfig = {};
|
|
1640
1715
|
try {
|
|
1641
|
-
const raw = await
|
|
1716
|
+
const raw = await readFile7(configPath, "utf-8");
|
|
1642
1717
|
currentConfig = JSON.parse(raw);
|
|
1643
1718
|
} catch {
|
|
1644
1719
|
currentConfig = { repo_id: repoId };
|
|
@@ -1676,14 +1751,17 @@ async function syncConfig(repoId, projectPath, dryRun) {
|
|
|
1676
1751
|
async function syncTechStack(repoId, projectPath, dryRun) {
|
|
1677
1752
|
try {
|
|
1678
1753
|
const detected = await detectTechStack(projectPath);
|
|
1679
|
-
if (detected.length === 0) {
|
|
1754
|
+
if (detected.flat.length === 0) {
|
|
1680
1755
|
console.log(" No tech stack detected.");
|
|
1681
1756
|
return;
|
|
1682
1757
|
}
|
|
1683
1758
|
const repoRes = await apiGet(`/repos/${repoId}`);
|
|
1684
|
-
const remote =
|
|
1759
|
+
const remote = parseTechStackResult(repoRes.data.tech_stack);
|
|
1685
1760
|
const { merged, added } = mergeTechStack(remote, detected);
|
|
1686
|
-
console.log(` ${detected.length} detected${added.length > 0 ? ` (${added.length} new)` : ""}`);
|
|
1761
|
+
console.log(` ${detected.flat.length} detected${added.length > 0 ? ` (${added.length} new)` : ""}`);
|
|
1762
|
+
if (detected.apps.length > 0) {
|
|
1763
|
+
console.log(` Apps: ${detected.apps.map((a) => a.name).join(", ")}`);
|
|
1764
|
+
}
|
|
1687
1765
|
for (const entry of added) {
|
|
1688
1766
|
console.log(` + ${entry.name} (${entry.category})`);
|
|
1689
1767
|
}
|
|
@@ -1694,6 +1772,25 @@ async function syncTechStack(repoId, projectPath, dryRun) {
|
|
|
1694
1772
|
console.log(" Tech stack detection skipped.");
|
|
1695
1773
|
}
|
|
1696
1774
|
}
|
|
1775
|
+
function groupByType(items) {
|
|
1776
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1777
|
+
const typeLabels = {
|
|
1778
|
+
command: "Commands",
|
|
1779
|
+
agent: "Agents",
|
|
1780
|
+
skill: "Skills",
|
|
1781
|
+
rule: "Rules",
|
|
1782
|
+
hook: "Hooks",
|
|
1783
|
+
template: "Templates",
|
|
1784
|
+
settings: "Settings"
|
|
1785
|
+
};
|
|
1786
|
+
for (const item of items) {
|
|
1787
|
+
const label = typeLabels[item.type] ?? item.type;
|
|
1788
|
+
const group = groups.get(label) ?? [];
|
|
1789
|
+
group.push(item);
|
|
1790
|
+
groups.set(label, group);
|
|
1791
|
+
}
|
|
1792
|
+
return groups;
|
|
1793
|
+
}
|
|
1697
1794
|
function getLocalFilePath(claudeDir, projectPath, remote) {
|
|
1698
1795
|
const typeConfig2 = {
|
|
1699
1796
|
command: { dir: "commands", ext: ".md" },
|
|
@@ -1705,15 +1802,15 @@ function getLocalFilePath(claudeDir, projectPath, remote) {
|
|
|
1705
1802
|
claude_md: { dir: "", ext: "" },
|
|
1706
1803
|
settings: { dir: "", ext: "" }
|
|
1707
1804
|
};
|
|
1708
|
-
if (remote.type === "claude_md") return
|
|
1709
|
-
if (remote.type === "settings") return
|
|
1805
|
+
if (remote.type === "claude_md") return join7(projectPath, "CLAUDE.md");
|
|
1806
|
+
if (remote.type === "settings") return join7(claudeDir, "settings.json");
|
|
1710
1807
|
const cfg = typeConfig2[remote.type];
|
|
1711
|
-
if (!cfg) return
|
|
1712
|
-
const typeDir = remote.type === "command" ?
|
|
1713
|
-
if (cfg.subfolder) return
|
|
1714
|
-
if (remote.type === "command" && remote.category) return
|
|
1715
|
-
if (remote.type === "template") return
|
|
1716
|
-
return
|
|
1808
|
+
if (!cfg) return join7(claudeDir, remote.name);
|
|
1809
|
+
const typeDir = remote.type === "command" ? join7(claudeDir, cfg.dir, "cbp") : join7(claudeDir, cfg.dir);
|
|
1810
|
+
if (cfg.subfolder) return join7(typeDir, remote.name, `${cfg.subfolder}${cfg.ext}`);
|
|
1811
|
+
if (remote.type === "command" && remote.category) return join7(typeDir, remote.category, `${remote.name}${cfg.ext}`);
|
|
1812
|
+
if (remote.type === "template") return join7(typeDir, remote.name);
|
|
1813
|
+
return join7(typeDir, `${remote.name}${cfg.ext}`);
|
|
1717
1814
|
}
|
|
1718
1815
|
function flattenSyncData(data) {
|
|
1719
1816
|
const result = /* @__PURE__ */ new Map();
|
|
@@ -1741,99 +1838,6 @@ function flattenSyncData(data) {
|
|
|
1741
1838
|
}
|
|
1742
1839
|
return result;
|
|
1743
1840
|
}
|
|
1744
|
-
async function syncDesktopConfigs(repoId, projectPath, repoData, dryRun) {
|
|
1745
|
-
try {
|
|
1746
|
-
const cbpDir = join8(homedir2(), ".codebyplan");
|
|
1747
|
-
const configsPath = join8(cbpDir, "server-configs.json");
|
|
1748
|
-
let configFile = { repos: [] };
|
|
1749
|
-
try {
|
|
1750
|
-
const raw = await readFile8(configsPath, "utf-8");
|
|
1751
|
-
configFile = JSON.parse(raw);
|
|
1752
|
-
} catch {
|
|
1753
|
-
}
|
|
1754
|
-
const detection = await detectServers(projectPath);
|
|
1755
|
-
let portAllocations = [];
|
|
1756
|
-
try {
|
|
1757
|
-
const localConfigRaw = await readFile8(join8(projectPath, ".codebyplan.json"), "utf-8");
|
|
1758
|
-
const localConfig = JSON.parse(localConfigRaw);
|
|
1759
|
-
portAllocations = localConfig.port_allocations ?? [];
|
|
1760
|
-
} catch {
|
|
1761
|
-
}
|
|
1762
|
-
const rootAllocations = portAllocations.filter((a) => !a.worktree_id);
|
|
1763
|
-
const worktreeAllocations = portAllocations.filter((a) => a.worktree_id);
|
|
1764
|
-
const matchDetected = (alloc) => {
|
|
1765
|
-
let matched = detection.servers.find(
|
|
1766
|
-
(s) => alloc.label.toLowerCase().includes(s.label.toLowerCase())
|
|
1767
|
-
);
|
|
1768
|
-
if (!matched) {
|
|
1769
|
-
matched = detection.servers.find(
|
|
1770
|
-
(s) => s.server_type === alloc.server_type
|
|
1771
|
-
);
|
|
1772
|
-
}
|
|
1773
|
-
return {
|
|
1774
|
-
label: alloc.label,
|
|
1775
|
-
port: alloc.port,
|
|
1776
|
-
command: matched?.command ?? "",
|
|
1777
|
-
server_type: alloc.server_type,
|
|
1778
|
-
auto_start: alloc.auto_start
|
|
1779
|
-
};
|
|
1780
|
-
};
|
|
1781
|
-
let servers;
|
|
1782
|
-
if (rootAllocations.length > 0) {
|
|
1783
|
-
servers = rootAllocations.map(matchDetected);
|
|
1784
|
-
} else {
|
|
1785
|
-
servers = detection.servers.map((s) => ({
|
|
1786
|
-
label: s.label,
|
|
1787
|
-
port: s.port,
|
|
1788
|
-
command: s.command,
|
|
1789
|
-
server_type: s.server_type,
|
|
1790
|
-
auto_start: "off"
|
|
1791
|
-
}));
|
|
1792
|
-
}
|
|
1793
|
-
const worktreeGroups = /* @__PURE__ */ new Map();
|
|
1794
|
-
for (const alloc of worktreeAllocations) {
|
|
1795
|
-
const wId = alloc.worktree_id;
|
|
1796
|
-
if (!worktreeGroups.has(wId)) worktreeGroups.set(wId, []);
|
|
1797
|
-
worktreeGroups.get(wId).push(alloc);
|
|
1798
|
-
}
|
|
1799
|
-
const worktrees = Array.from(worktreeGroups.entries()).map(
|
|
1800
|
-
([worktreeId, allocs]) => {
|
|
1801
|
-
const firstLabel = allocs[0]?.label ?? "";
|
|
1802
|
-
const parenMatch = firstLabel.match(/\(([^)]+)\)/);
|
|
1803
|
-
const worktreeName = parenMatch?.[1] ?? worktreeId;
|
|
1804
|
-
return {
|
|
1805
|
-
name: worktreeName,
|
|
1806
|
-
path: "",
|
|
1807
|
-
// Path is managed by the desktop app
|
|
1808
|
-
cloud_id: worktreeId,
|
|
1809
|
-
servers: allocs.map(matchDetected)
|
|
1810
|
-
};
|
|
1811
|
-
}
|
|
1812
|
-
);
|
|
1813
|
-
const repoEntry = {
|
|
1814
|
-
name: repoData.name,
|
|
1815
|
-
path: projectPath,
|
|
1816
|
-
servers,
|
|
1817
|
-
cloud_id: repoId,
|
|
1818
|
-
...worktrees.length > 0 ? { worktrees } : {}
|
|
1819
|
-
};
|
|
1820
|
-
const existingIndex = configFile.repos.findIndex((r) => r.cloud_id === repoId);
|
|
1821
|
-
if (existingIndex >= 0) {
|
|
1822
|
-
configFile.repos[existingIndex] = repoEntry;
|
|
1823
|
-
} else {
|
|
1824
|
-
configFile.repos.push(repoEntry);
|
|
1825
|
-
}
|
|
1826
|
-
if (dryRun) {
|
|
1827
|
-
console.log(" Desktop server configs would be updated (dry-run).");
|
|
1828
|
-
return;
|
|
1829
|
-
}
|
|
1830
|
-
await mkdir2(cbpDir, { recursive: true });
|
|
1831
|
-
await writeFile3(configsPath, JSON.stringify(configFile, null, 2) + "\n", "utf-8");
|
|
1832
|
-
console.log(` Updated server-configs.json (${servers.length} server(s), ${worktrees.length} worktree(s))`);
|
|
1833
|
-
} catch {
|
|
1834
|
-
console.log(" Desktop server config sync skipped.");
|
|
1835
|
-
}
|
|
1836
|
-
}
|
|
1837
1841
|
var init_sync = __esm({
|
|
1838
1842
|
"src/cli/sync.ts"() {
|
|
1839
1843
|
"use strict";
|
|
@@ -1843,7 +1847,6 @@ var init_sync = __esm({
|
|
|
1843
1847
|
init_api();
|
|
1844
1848
|
init_variables();
|
|
1845
1849
|
init_tech_detect();
|
|
1846
|
-
init_server_detect();
|
|
1847
1850
|
init_settings_merge();
|
|
1848
1851
|
init_hook_registry();
|
|
1849
1852
|
}
|