@staff0rd/assist 0.45.0 → 0.47.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/README.md +1 -0
- package/claude/settings.json +2 -0
- package/dist/commands/deploy/init/index.ts +47 -43
- package/dist/index.js +1424 -1035
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { Command } from "commander";
|
|
|
7
7
|
// package.json
|
|
8
8
|
var package_default = {
|
|
9
9
|
name: "@staff0rd/assist",
|
|
10
|
-
version: "0.
|
|
10
|
+
version: "0.47.0",
|
|
11
11
|
type: "module",
|
|
12
12
|
main: "dist/index.js",
|
|
13
13
|
bin: {
|
|
@@ -28,7 +28,7 @@ var package_default = {
|
|
|
28
28
|
"verify:types": "tsc --noEmit",
|
|
29
29
|
"verify:knip": "knip --no-progress --treat-config-hints-as-errors",
|
|
30
30
|
"verify:duplicate-code": "jscpd --format 'typescript,tsx' --exitCode 1 --ignore '**/*.test.*' -r consoleFull src",
|
|
31
|
-
"verify:maintainability": "assist complexity maintainability ./src --threshold
|
|
31
|
+
"verify:maintainability": "assist complexity maintainability ./src --threshold 70",
|
|
32
32
|
"verify:custom-lint": "assist lint"
|
|
33
33
|
},
|
|
34
34
|
keywords: [],
|
|
@@ -214,65 +214,96 @@ function commit(message) {
|
|
|
214
214
|
// src/commands/config/index.ts
|
|
215
215
|
import chalk2 from "chalk";
|
|
216
216
|
import { stringify as stringifyYaml2 } from "yaml";
|
|
217
|
-
|
|
218
|
-
|
|
217
|
+
|
|
218
|
+
// src/commands/config/getNestedValue.ts
|
|
219
|
+
function isTraversable(value) {
|
|
220
|
+
return value !== null && value !== void 0 && typeof value === "object";
|
|
221
|
+
}
|
|
222
|
+
function stepInto(current, key) {
|
|
223
|
+
return isTraversable(current) ? current[key] : void 0;
|
|
224
|
+
}
|
|
225
|
+
function getNestedValue(obj, path29) {
|
|
219
226
|
let current = obj;
|
|
220
|
-
for (const key of
|
|
221
|
-
if (current === null || current === void 0 || typeof current !== "object") {
|
|
222
|
-
return void 0;
|
|
223
|
-
}
|
|
224
|
-
current = current[key];
|
|
225
|
-
}
|
|
227
|
+
for (const key of path29.split(".")) current = stepInto(current, key);
|
|
226
228
|
return current;
|
|
227
229
|
}
|
|
228
|
-
function
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
230
|
+
function isPlainObject(val) {
|
|
231
|
+
return val !== null && typeof val === "object" && !Array.isArray(val);
|
|
232
|
+
}
|
|
233
|
+
function ensureNestedObject(current, key) {
|
|
234
|
+
current[key] = isPlainObject(current[key]) ? { ...current[key] } : {};
|
|
235
|
+
return current[key];
|
|
236
|
+
}
|
|
237
|
+
function buildNestedPath(root, keys) {
|
|
238
|
+
let current = root;
|
|
232
239
|
for (let i = 0; i < keys.length - 1; i++) {
|
|
233
|
-
|
|
234
|
-
current[key] = current[key] !== null && current[key] !== void 0 && typeof current[key] === "object" && !Array.isArray(current[key]) ? { ...current[key] } : {};
|
|
235
|
-
current = current[key];
|
|
240
|
+
current = ensureNestedObject(current, keys[i]);
|
|
236
241
|
}
|
|
237
|
-
current
|
|
242
|
+
return current;
|
|
243
|
+
}
|
|
244
|
+
function setNestedValue(obj, path29, value) {
|
|
245
|
+
const keys = path29.split(".");
|
|
246
|
+
const result = { ...obj };
|
|
247
|
+
buildNestedPath(result, keys)[keys[keys.length - 1]] = value;
|
|
238
248
|
return result;
|
|
239
249
|
}
|
|
250
|
+
|
|
251
|
+
// src/commands/config/index.ts
|
|
240
252
|
function coerceValue(value) {
|
|
241
253
|
if (value === "true") return true;
|
|
242
254
|
if (value === "false") return false;
|
|
243
255
|
return value;
|
|
244
256
|
}
|
|
245
|
-
function
|
|
246
|
-
|
|
247
|
-
|
|
257
|
+
function formatIssuePath(issue, key) {
|
|
258
|
+
return issue.path.length > 0 ? issue.path.join(".") : key;
|
|
259
|
+
}
|
|
260
|
+
function printValidationErrors(issues, key) {
|
|
261
|
+
for (const issue of issues) {
|
|
262
|
+
console.error(
|
|
263
|
+
chalk2.red(`${formatIssuePath(issue, key)}: ${issue.message}`)
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function exitValidationFailed(issues, key) {
|
|
268
|
+
printValidationErrors(issues, key);
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
function validateConfig(updated, key) {
|
|
272
|
+
const result = assistConfigSchema.safeParse(updated);
|
|
273
|
+
if (!result.success) return exitValidationFailed(result.error.issues, key);
|
|
274
|
+
return result.data;
|
|
275
|
+
}
|
|
276
|
+
function applyConfigSet(key, coerced) {
|
|
248
277
|
const updated = setNestedValue(
|
|
249
|
-
|
|
278
|
+
loadConfig(),
|
|
250
279
|
key,
|
|
251
280
|
coerced
|
|
252
281
|
);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
process.exit(1);
|
|
260
|
-
}
|
|
261
|
-
saveConfig(result.data);
|
|
282
|
+
saveConfig(validateConfig(updated, key));
|
|
283
|
+
}
|
|
284
|
+
function configSet(key, value) {
|
|
285
|
+
const coerced = coerceValue(value);
|
|
286
|
+
applyConfigSet(key, coerced);
|
|
262
287
|
console.log(chalk2.green(`Set ${key} = ${JSON.stringify(coerced)}`));
|
|
263
288
|
}
|
|
264
|
-
function
|
|
265
|
-
|
|
289
|
+
function formatOutput(value) {
|
|
290
|
+
return typeof value === "object" && value !== null ? JSON.stringify(value, null, 2) : String(value);
|
|
291
|
+
}
|
|
292
|
+
function exitKeyNotSet(key) {
|
|
293
|
+
console.error(chalk2.red(`Key "${key}" is not set`));
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
function requireNestedValue(config, key) {
|
|
266
297
|
const value = getNestedValue(config, key);
|
|
267
|
-
if (value === void 0)
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
298
|
+
if (value === void 0) return exitKeyNotSet(key);
|
|
299
|
+
return value;
|
|
300
|
+
}
|
|
301
|
+
function configGet(key) {
|
|
302
|
+
console.log(
|
|
303
|
+
formatOutput(
|
|
304
|
+
requireNestedValue(loadConfig(), key)
|
|
305
|
+
)
|
|
306
|
+
);
|
|
276
307
|
}
|
|
277
308
|
function configList() {
|
|
278
309
|
const config = loadConfig();
|
|
@@ -750,55 +781,95 @@ function detectExistingSetup(pkg) {
|
|
|
750
781
|
}
|
|
751
782
|
|
|
752
783
|
// src/commands/verify/init/getAvailableOptions.ts
|
|
753
|
-
function
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
784
|
+
function getBuildDescription(setup) {
|
|
785
|
+
if (setup.hasVite && setup.hasTypescript)
|
|
786
|
+
return "TypeScript + Vite build verification";
|
|
787
|
+
if (setup.hasVite) return "Vite build verification";
|
|
788
|
+
return "TypeScript type checking";
|
|
789
|
+
}
|
|
790
|
+
function shouldInclude(setup, def) {
|
|
791
|
+
return needsSetup(setup[def.toolKey]) && (def.extraCondition ?? true);
|
|
792
|
+
}
|
|
793
|
+
function toVerifyOption(setup, def) {
|
|
794
|
+
return {
|
|
795
|
+
name: `${def.label}${getStatusLabel(setup[def.toolKey])}`,
|
|
796
|
+
value: def.value,
|
|
797
|
+
description: def.description
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
var STATIC_OPTIONS = [
|
|
801
|
+
{
|
|
802
|
+
toolKey: "knip",
|
|
803
|
+
value: "knip",
|
|
804
|
+
label: "knip",
|
|
805
|
+
description: "Dead code and unused dependency detection"
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
toolKey: "biome",
|
|
809
|
+
value: "lint",
|
|
810
|
+
label: "lint",
|
|
811
|
+
description: "Code linting and formatting with Biome"
|
|
812
|
+
},
|
|
813
|
+
{
|
|
814
|
+
toolKey: "jscpd",
|
|
815
|
+
value: "duplicate-code",
|
|
816
|
+
label: "duplicate-code",
|
|
817
|
+
description: "Duplicate code detection with jscpd"
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
toolKey: "hardcodedColors",
|
|
821
|
+
value: "hardcoded-colors",
|
|
822
|
+
label: "hardcoded-colors",
|
|
823
|
+
description: "Detect hardcoded hex colors (use open-color instead)"
|
|
775
824
|
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
825
|
+
];
|
|
826
|
+
function getConditionalOptions(setup) {
|
|
827
|
+
return [
|
|
828
|
+
{
|
|
829
|
+
toolKey: "test",
|
|
779
830
|
value: "test",
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
name: `build${getStatusLabel(setup.build)}`,
|
|
831
|
+
label: "test",
|
|
832
|
+
description: "Run tests with vitest",
|
|
833
|
+
extraCondition: setup.test.hasPackage
|
|
834
|
+
},
|
|
835
|
+
{
|
|
836
|
+
toolKey: "build",
|
|
787
837
|
value: "build",
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
return
|
|
838
|
+
label: "build",
|
|
839
|
+
description: getBuildDescription(setup),
|
|
840
|
+
extraCondition: setup.hasTypescript || setup.hasVite
|
|
841
|
+
}
|
|
842
|
+
];
|
|
843
|
+
}
|
|
844
|
+
function getAllOptionDefs(setup) {
|
|
845
|
+
return [...STATIC_OPTIONS, ...getConditionalOptions(setup)];
|
|
846
|
+
}
|
|
847
|
+
function getAvailableOptions(setup) {
|
|
848
|
+
return getAllOptionDefs(setup).filter((def) => shouldInclude(setup, def)).map((def) => toVerifyOption(setup, def));
|
|
799
849
|
}
|
|
800
850
|
|
|
801
851
|
// src/commands/verify/init/index.ts
|
|
852
|
+
function getSetupHandlers(hasVite, hasTypescript, hasOpenColor) {
|
|
853
|
+
return {
|
|
854
|
+
knip: (p) => setupKnip(p),
|
|
855
|
+
lint: (p) => setupLint(p),
|
|
856
|
+
"duplicate-code": (p) => setupDuplicateCode(p),
|
|
857
|
+
test: (p) => setupTest(p),
|
|
858
|
+
build: (p) => setupBuild(p, hasVite, hasTypescript),
|
|
859
|
+
"hardcoded-colors": (p) => setupHardcodedColors(p, hasOpenColor)
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
async function runSelectedSetups(selected, packageJsonPath, handlers) {
|
|
863
|
+
for (const choice of selected) {
|
|
864
|
+
await handlers[choice]?.(packageJsonPath);
|
|
865
|
+
}
|
|
866
|
+
console.log(chalk14.green(`
|
|
867
|
+
Added ${selected.length} verify script(s):`));
|
|
868
|
+
for (const choice of selected) {
|
|
869
|
+
console.log(chalk14.green(` - verify:${choice}`));
|
|
870
|
+
}
|
|
871
|
+
console.log(chalk14.dim("\nRun 'assist verify' to run all verify scripts"));
|
|
872
|
+
}
|
|
802
873
|
async function init2() {
|
|
803
874
|
const { packageJsonPath, pkg } = requirePackageJson();
|
|
804
875
|
const setup = detectExistingSetup(pkg);
|
|
@@ -816,34 +887,12 @@ async function init2() {
|
|
|
816
887
|
console.log(chalk14.yellow("No scripts selected"));
|
|
817
888
|
return;
|
|
818
889
|
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
await setupLint(packageJsonPath);
|
|
826
|
-
break;
|
|
827
|
-
case "duplicate-code":
|
|
828
|
-
await setupDuplicateCode(packageJsonPath);
|
|
829
|
-
break;
|
|
830
|
-
case "test":
|
|
831
|
-
await setupTest(packageJsonPath);
|
|
832
|
-
break;
|
|
833
|
-
case "build":
|
|
834
|
-
await setupBuild(packageJsonPath, setup.hasVite, setup.hasTypescript);
|
|
835
|
-
break;
|
|
836
|
-
case "hardcoded-colors":
|
|
837
|
-
await setupHardcodedColors(packageJsonPath, setup.hasOpenColor);
|
|
838
|
-
break;
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
console.log(chalk14.green(`
|
|
842
|
-
Added ${selected.length} verify script(s):`));
|
|
843
|
-
for (const choice of selected) {
|
|
844
|
-
console.log(chalk14.green(` - verify:${choice}`));
|
|
845
|
-
}
|
|
846
|
-
console.log(chalk14.dim("\nRun 'assist verify' to run all verify scripts"));
|
|
890
|
+
const handlers = getSetupHandlers(
|
|
891
|
+
setup.hasVite,
|
|
892
|
+
setup.hasTypescript,
|
|
893
|
+
setup.hasOpenColor
|
|
894
|
+
);
|
|
895
|
+
await runSelectedSetups(selected, packageJsonPath, handlers);
|
|
847
896
|
}
|
|
848
897
|
|
|
849
898
|
// src/commands/vscode/init/index.ts
|
|
@@ -932,24 +981,33 @@ function detectVscodeSetup(pkg) {
|
|
|
932
981
|
}
|
|
933
982
|
|
|
934
983
|
// src/commands/vscode/init/index.ts
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
984
|
+
var SETUP_HANDLERS = {
|
|
985
|
+
launch: () => createLaunchJson(),
|
|
986
|
+
settings: () => {
|
|
987
|
+
createSettingsJson();
|
|
988
|
+
createExtensionsJson();
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
function getAvailableOptions2(setup) {
|
|
992
|
+
const options = [];
|
|
993
|
+
if (!setup.hasLaunchJson && setup.hasVite)
|
|
994
|
+
options.push({
|
|
941
995
|
name: "launch",
|
|
942
996
|
value: "launch",
|
|
943
997
|
description: "Debug configuration for Vite dev server"
|
|
944
998
|
});
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
availableOptions.push({
|
|
999
|
+
if (!setup.hasSettingsJson)
|
|
1000
|
+
options.push({
|
|
948
1001
|
name: "settings",
|
|
949
1002
|
value: "settings",
|
|
950
1003
|
description: "Biome formatter configuration"
|
|
951
1004
|
});
|
|
952
|
-
|
|
1005
|
+
return options;
|
|
1006
|
+
}
|
|
1007
|
+
async function init3() {
|
|
1008
|
+
const { pkg } = requirePackageJson();
|
|
1009
|
+
const setup = detectVscodeSetup(pkg);
|
|
1010
|
+
const availableOptions = getAvailableOptions2(setup);
|
|
953
1011
|
if (availableOptions.length === 0) {
|
|
954
1012
|
console.log(chalk16.green("VS Code configuration already exists!"));
|
|
955
1013
|
return;
|
|
@@ -965,17 +1023,7 @@ async function init3() {
|
|
|
965
1023
|
}
|
|
966
1024
|
removeVscodeFromGitignore();
|
|
967
1025
|
ensureVscodeFolder();
|
|
968
|
-
for (const choice of selected)
|
|
969
|
-
switch (choice) {
|
|
970
|
-
case "launch":
|
|
971
|
-
createLaunchJson();
|
|
972
|
-
break;
|
|
973
|
-
case "settings":
|
|
974
|
-
createSettingsJson();
|
|
975
|
-
createExtensionsJson();
|
|
976
|
-
break;
|
|
977
|
-
}
|
|
978
|
-
}
|
|
1026
|
+
for (const choice of selected) SETUP_HANDLERS[choice]?.();
|
|
979
1027
|
console.log(
|
|
980
1028
|
chalk16.green(`
|
|
981
1029
|
Added ${selected.length} VS Code configuration(s)`)
|
|
@@ -1231,50 +1279,29 @@ Created ${WORKFLOW_PATH}`));
|
|
|
1231
1279
|
}
|
|
1232
1280
|
|
|
1233
1281
|
// src/commands/deploy/init/index.ts
|
|
1234
|
-
async function
|
|
1235
|
-
console.log(chalk20.bold("Initializing Netlify deployment...\n"));
|
|
1236
|
-
const existingSiteId = getExistingSiteId();
|
|
1237
|
-
if (existingSiteId) {
|
|
1238
|
-
console.log(chalk20.dim(`Using existing site ID: ${existingSiteId}
|
|
1239
|
-
`));
|
|
1240
|
-
await updateWorkflow(existingSiteId);
|
|
1241
|
-
return;
|
|
1242
|
-
}
|
|
1243
|
-
console.log("Creating Netlify site...\n");
|
|
1282
|
+
async function ensureNetlifyCli() {
|
|
1244
1283
|
try {
|
|
1245
|
-
execSync5("netlify sites:create --disable-linking", {
|
|
1246
|
-
stdio: "inherit"
|
|
1247
|
-
});
|
|
1284
|
+
execSync5("netlify sites:create --disable-linking", { stdio: "inherit" });
|
|
1248
1285
|
} catch (error) {
|
|
1249
|
-
if (error instanceof Error
|
|
1250
|
-
console.error(chalk20.red("\nNetlify CLI is not installed.\n"));
|
|
1251
|
-
const install = await promptConfirm("Would you like to install it now?");
|
|
1252
|
-
if (install) {
|
|
1253
|
-
console.log(chalk20.dim("\nInstalling netlify-cli...\n"));
|
|
1254
|
-
execSync5("npm install -g netlify-cli", { stdio: "inherit" });
|
|
1255
|
-
console.log();
|
|
1256
|
-
execSync5("netlify sites:create --disable-linking", {
|
|
1257
|
-
stdio: "inherit"
|
|
1258
|
-
});
|
|
1259
|
-
} else {
|
|
1260
|
-
console.log(
|
|
1261
|
-
chalk20.yellow(
|
|
1262
|
-
"\nInstall it manually with: npm install -g netlify-cli\n"
|
|
1263
|
-
)
|
|
1264
|
-
);
|
|
1265
|
-
process.exit(1);
|
|
1266
|
-
}
|
|
1267
|
-
} else {
|
|
1286
|
+
if (!(error instanceof Error) || !error.message.includes("command not found"))
|
|
1268
1287
|
throw error;
|
|
1288
|
+
console.error(chalk20.red("\nNetlify CLI is not installed.\n"));
|
|
1289
|
+
const install = await promptConfirm("Would you like to install it now?");
|
|
1290
|
+
if (!install) {
|
|
1291
|
+
console.log(
|
|
1292
|
+
chalk20.yellow(
|
|
1293
|
+
"\nInstall it manually with: npm install -g netlify-cli\n"
|
|
1294
|
+
)
|
|
1295
|
+
);
|
|
1296
|
+
process.exit(1);
|
|
1269
1297
|
}
|
|
1298
|
+
console.log(chalk20.dim("\nInstalling netlify-cli...\n"));
|
|
1299
|
+
execSync5("npm install -g netlify-cli", { stdio: "inherit" });
|
|
1300
|
+
console.log();
|
|
1301
|
+
execSync5("netlify sites:create --disable-linking", { stdio: "inherit" });
|
|
1270
1302
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
name: "siteId",
|
|
1274
|
-
message: "Enter the Site ID from above:",
|
|
1275
|
-
validate: (value) => /^[a-f0-9-]+$/i.test(value) || "Invalid site ID format"
|
|
1276
|
-
});
|
|
1277
|
-
await updateWorkflow(siteId);
|
|
1303
|
+
}
|
|
1304
|
+
function printSetupInstructions() {
|
|
1278
1305
|
console.log(chalk20.bold("\nDeployment initialized successfully!"));
|
|
1279
1306
|
console.log(
|
|
1280
1307
|
chalk20.yellow("\nTo complete setup, create a personal access token at:")
|
|
@@ -1290,6 +1317,26 @@ async function init5() {
|
|
|
1290
1317
|
)
|
|
1291
1318
|
);
|
|
1292
1319
|
}
|
|
1320
|
+
async function init5() {
|
|
1321
|
+
console.log(chalk20.bold("Initializing Netlify deployment...\n"));
|
|
1322
|
+
const existingSiteId = getExistingSiteId();
|
|
1323
|
+
if (existingSiteId) {
|
|
1324
|
+
console.log(chalk20.dim(`Using existing site ID: ${existingSiteId}
|
|
1325
|
+
`));
|
|
1326
|
+
await updateWorkflow(existingSiteId);
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
console.log("Creating Netlify site...\n");
|
|
1330
|
+
await ensureNetlifyCli();
|
|
1331
|
+
const { siteId } = await enquirer3.prompt({
|
|
1332
|
+
type: "input",
|
|
1333
|
+
name: "siteId",
|
|
1334
|
+
message: "Enter the Site ID from above:",
|
|
1335
|
+
validate: (value) => /^[a-f0-9-]+$/i.test(value) || "Invalid site ID format"
|
|
1336
|
+
});
|
|
1337
|
+
await updateWorkflow(siteId);
|
|
1338
|
+
printSetupInstructions();
|
|
1339
|
+
}
|
|
1293
1340
|
|
|
1294
1341
|
// src/commands/new/newProject.ts
|
|
1295
1342
|
async function newProject() {
|
|
@@ -1445,6 +1492,9 @@ async function notify() {
|
|
|
1445
1492
|
console.log(`Notification sent: ${notification_type} for ${projectName}`);
|
|
1446
1493
|
}
|
|
1447
1494
|
|
|
1495
|
+
// src/commands/complexity/analyze.ts
|
|
1496
|
+
import chalk26 from "chalk";
|
|
1497
|
+
|
|
1448
1498
|
// src/commands/complexity/cyclomatic.ts
|
|
1449
1499
|
import chalk22 from "chalk";
|
|
1450
1500
|
|
|
@@ -1500,46 +1550,43 @@ function findSourceFiles2(pattern2, baseDir = ".") {
|
|
|
1500
1550
|
|
|
1501
1551
|
// src/commands/complexity/shared/getNodeName.ts
|
|
1502
1552
|
import ts from "typescript";
|
|
1553
|
+
var FUNCTION_TYPE_CHECKS = [
|
|
1554
|
+
ts.isFunctionDeclaration,
|
|
1555
|
+
ts.isFunctionExpression,
|
|
1556
|
+
ts.isArrowFunction,
|
|
1557
|
+
ts.isMethodDeclaration,
|
|
1558
|
+
ts.isGetAccessor,
|
|
1559
|
+
ts.isSetAccessor,
|
|
1560
|
+
ts.isConstructorDeclaration
|
|
1561
|
+
];
|
|
1562
|
+
function getIdentifierText(name) {
|
|
1563
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name)) return name.text;
|
|
1564
|
+
return "<computed>";
|
|
1565
|
+
}
|
|
1566
|
+
function getArrowFunctionName(node) {
|
|
1567
|
+
const { parent } = node;
|
|
1568
|
+
if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name))
|
|
1569
|
+
return parent.name.text;
|
|
1570
|
+
if (ts.isPropertyAssignment(parent) && ts.isIdentifier(parent.name))
|
|
1571
|
+
return parent.name.text;
|
|
1572
|
+
return "<arrow>";
|
|
1573
|
+
}
|
|
1503
1574
|
function getNodeName(node) {
|
|
1504
|
-
if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node))
|
|
1575
|
+
if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node))
|
|
1505
1576
|
return node.name?.text ?? "<anonymous>";
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
return node.name.text;
|
|
1510
|
-
}
|
|
1511
|
-
if (ts.isStringLiteral(node.name)) {
|
|
1512
|
-
return node.name.text;
|
|
1513
|
-
}
|
|
1514
|
-
return "<computed>";
|
|
1515
|
-
}
|
|
1516
|
-
if (ts.isArrowFunction(node)) {
|
|
1517
|
-
const parent = node.parent;
|
|
1518
|
-
if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) {
|
|
1519
|
-
return parent.name.text;
|
|
1520
|
-
}
|
|
1521
|
-
if (ts.isPropertyAssignment(parent) && ts.isIdentifier(parent.name)) {
|
|
1522
|
-
return parent.name.text;
|
|
1523
|
-
}
|
|
1524
|
-
return "<arrow>";
|
|
1525
|
-
}
|
|
1577
|
+
if (ts.isMethodDeclaration(node) || ts.isMethodSignature(node))
|
|
1578
|
+
return getIdentifierText(node.name);
|
|
1579
|
+
if (ts.isArrowFunction(node)) return getArrowFunctionName(node);
|
|
1526
1580
|
if (ts.isGetAccessor(node) || ts.isSetAccessor(node)) {
|
|
1527
1581
|
const prefix = ts.isGetAccessor(node) ? "get " : "set ";
|
|
1528
|
-
|
|
1529
|
-
return `${prefix}${node.name.text}`;
|
|
1530
|
-
}
|
|
1531
|
-
return `${prefix}<computed>`;
|
|
1532
|
-
}
|
|
1533
|
-
if (ts.isConstructorDeclaration(node)) {
|
|
1534
|
-
return "constructor";
|
|
1582
|
+
return `${prefix}${getIdentifierText(node.name)}`;
|
|
1535
1583
|
}
|
|
1584
|
+
if (ts.isConstructorDeclaration(node)) return "constructor";
|
|
1536
1585
|
return "<unknown>";
|
|
1537
1586
|
}
|
|
1538
1587
|
function hasFunctionBody(node) {
|
|
1539
|
-
if (
|
|
1540
|
-
|
|
1541
|
-
}
|
|
1542
|
-
return false;
|
|
1588
|
+
if (!FUNCTION_TYPE_CHECKS.some((check2) => check2(node))) return false;
|
|
1589
|
+
return node.body !== void 0;
|
|
1543
1590
|
}
|
|
1544
1591
|
|
|
1545
1592
|
// src/commands/complexity/shared/calculateCyclomaticComplexity.ts
|
|
@@ -1891,9 +1938,41 @@ Total: ${total} lines across ${files.length} files`)
|
|
|
1891
1938
|
});
|
|
1892
1939
|
}
|
|
1893
1940
|
|
|
1941
|
+
// src/commands/complexity/analyze.ts
|
|
1942
|
+
async function analyze(pattern2) {
|
|
1943
|
+
const searchPattern = pattern2.includes("*") || pattern2.includes("/") ? pattern2 : `**/${pattern2}`;
|
|
1944
|
+
const files = findSourceFiles2(searchPattern);
|
|
1945
|
+
if (files.length === 0) {
|
|
1946
|
+
console.log(chalk26.yellow("No files found matching pattern"));
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1949
|
+
if (files.length === 1) {
|
|
1950
|
+
const file = files[0];
|
|
1951
|
+
console.log(chalk26.bold.underline("SLOC"));
|
|
1952
|
+
await sloc(file);
|
|
1953
|
+
console.log();
|
|
1954
|
+
console.log(chalk26.bold.underline("Cyclomatic Complexity"));
|
|
1955
|
+
await cyclomatic(file);
|
|
1956
|
+
console.log();
|
|
1957
|
+
console.log(chalk26.bold.underline("Halstead Metrics"));
|
|
1958
|
+
await halstead(file);
|
|
1959
|
+
console.log();
|
|
1960
|
+
console.log(chalk26.bold.underline("Maintainability Index"));
|
|
1961
|
+
await maintainability(file);
|
|
1962
|
+
return;
|
|
1963
|
+
}
|
|
1964
|
+
await maintainability(searchPattern);
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1894
1967
|
// src/commands/registerComplexity.ts
|
|
1895
1968
|
function registerComplexity(program2) {
|
|
1896
|
-
const complexityCommand = program2.command("complexity").description("Analyze TypeScript code complexity metrics")
|
|
1969
|
+
const complexityCommand = program2.command("complexity").description("Analyze TypeScript code complexity metrics").argument("[pattern]").action((pattern2) => {
|
|
1970
|
+
if (!pattern2) {
|
|
1971
|
+
complexityCommand.help();
|
|
1972
|
+
return;
|
|
1973
|
+
}
|
|
1974
|
+
return analyze(pattern2);
|
|
1975
|
+
});
|
|
1897
1976
|
complexityCommand.command("cyclomatic [pattern]").description("Calculate cyclomatic complexity per function").option("--threshold <number>", "Max complexity threshold", Number.parseInt).action(cyclomatic);
|
|
1898
1977
|
complexityCommand.command("halstead [pattern]").description("Calculate Halstead metrics per function").option("--threshold <number>", "Max volume threshold", Number.parseInt).action(halstead);
|
|
1899
1978
|
complexityCommand.command("maintainability [pattern]").description("Calculate maintainability index per file").option(
|
|
@@ -1906,7 +1985,7 @@ function registerComplexity(program2) {
|
|
|
1906
1985
|
|
|
1907
1986
|
// src/commands/deploy/redirect.ts
|
|
1908
1987
|
import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
1909
|
-
import
|
|
1988
|
+
import chalk27 from "chalk";
|
|
1910
1989
|
var TRAILING_SLASH_SCRIPT = ` <script>
|
|
1911
1990
|
if (!window.location.pathname.endsWith('/')) {
|
|
1912
1991
|
window.location.href = \`\${window.location.pathname}/\${window.location.search}\${window.location.hash}\`;
|
|
@@ -1915,22 +1994,22 @@ var TRAILING_SLASH_SCRIPT = ` <script>
|
|
|
1915
1994
|
function redirect() {
|
|
1916
1995
|
const indexPath = "index.html";
|
|
1917
1996
|
if (!existsSync11(indexPath)) {
|
|
1918
|
-
console.log(
|
|
1997
|
+
console.log(chalk27.yellow("No index.html found"));
|
|
1919
1998
|
return;
|
|
1920
1999
|
}
|
|
1921
2000
|
const content = readFileSync9(indexPath, "utf-8");
|
|
1922
2001
|
if (content.includes("window.location.pathname.endsWith('/')")) {
|
|
1923
|
-
console.log(
|
|
2002
|
+
console.log(chalk27.dim("Trailing slash script already present"));
|
|
1924
2003
|
return;
|
|
1925
2004
|
}
|
|
1926
2005
|
const headCloseIndex = content.indexOf("</head>");
|
|
1927
2006
|
if (headCloseIndex === -1) {
|
|
1928
|
-
console.log(
|
|
2007
|
+
console.log(chalk27.red("Could not find </head> tag in index.html"));
|
|
1929
2008
|
return;
|
|
1930
2009
|
}
|
|
1931
2010
|
const newContent = content.slice(0, headCloseIndex) + TRAILING_SLASH_SCRIPT + "\n " + content.slice(headCloseIndex);
|
|
1932
2011
|
writeFileSync8(indexPath, newContent);
|
|
1933
|
-
console.log(
|
|
2012
|
+
console.log(chalk27.green("Added trailing slash redirect to index.html"));
|
|
1934
2013
|
}
|
|
1935
2014
|
|
|
1936
2015
|
// src/commands/registerDeploy.ts
|
|
@@ -1946,7 +2025,7 @@ import { basename as basename2 } from "path";
|
|
|
1946
2025
|
|
|
1947
2026
|
// src/commands/devlog/shared.ts
|
|
1948
2027
|
import { execSync as execSync7 } from "child_process";
|
|
1949
|
-
import
|
|
2028
|
+
import chalk28 from "chalk";
|
|
1950
2029
|
|
|
1951
2030
|
// src/commands/devlog/loadDevlogEntries.ts
|
|
1952
2031
|
import { readdirSync, readFileSync as readFileSync10 } from "fs";
|
|
@@ -2007,13 +2086,13 @@ function shouldIgnoreCommit(files, ignorePaths) {
|
|
|
2007
2086
|
}
|
|
2008
2087
|
function printCommitsWithFiles(commits, ignore2, verbose) {
|
|
2009
2088
|
for (const commit2 of commits) {
|
|
2010
|
-
console.log(` ${
|
|
2089
|
+
console.log(` ${chalk28.yellow(commit2.hash)} ${commit2.message}`);
|
|
2011
2090
|
if (verbose) {
|
|
2012
2091
|
const visibleFiles = commit2.files.filter(
|
|
2013
2092
|
(file) => !ignore2.some((p) => file.startsWith(p))
|
|
2014
2093
|
);
|
|
2015
2094
|
for (const file of visibleFiles) {
|
|
2016
|
-
console.log(` ${
|
|
2095
|
+
console.log(` ${chalk28.dim(file)}`);
|
|
2017
2096
|
}
|
|
2018
2097
|
}
|
|
2019
2098
|
}
|
|
@@ -2038,15 +2117,15 @@ function parseGitLogCommits(output, ignore2, afterDate) {
|
|
|
2038
2117
|
}
|
|
2039
2118
|
|
|
2040
2119
|
// src/commands/devlog/list/printDateHeader.ts
|
|
2041
|
-
import
|
|
2120
|
+
import chalk29 from "chalk";
|
|
2042
2121
|
function printDateHeader(date, isSkipped, entries) {
|
|
2043
2122
|
if (isSkipped) {
|
|
2044
|
-
console.log(`${
|
|
2123
|
+
console.log(`${chalk29.bold.blue(date)} ${chalk29.dim("skipped")}`);
|
|
2045
2124
|
} else if (entries && entries.length > 0) {
|
|
2046
|
-
const entryInfo = entries.map((e) => `${
|
|
2047
|
-
console.log(`${
|
|
2125
|
+
const entryInfo = entries.map((e) => `${chalk29.green(e.version)} ${e.title}`).join(" | ");
|
|
2126
|
+
console.log(`${chalk29.bold.blue(date)} ${entryInfo}`);
|
|
2048
2127
|
} else {
|
|
2049
|
-
console.log(`${
|
|
2128
|
+
console.log(`${chalk29.bold.blue(date)} ${chalk29.red("\u26A0 devlog missing")}`);
|
|
2050
2129
|
}
|
|
2051
2130
|
}
|
|
2052
2131
|
|
|
@@ -2085,10 +2164,6 @@ function list(options) {
|
|
|
2085
2164
|
}
|
|
2086
2165
|
}
|
|
2087
2166
|
|
|
2088
|
-
// src/commands/devlog/next/index.ts
|
|
2089
|
-
import { execSync as execSync10 } from "child_process";
|
|
2090
|
-
import chalk30 from "chalk";
|
|
2091
|
-
|
|
2092
2167
|
// src/commands/devlog/getLastVersionInfo.ts
|
|
2093
2168
|
import { execSync as execSync9 } from "child_process";
|
|
2094
2169
|
import semver from "semver";
|
|
@@ -2124,112 +2199,153 @@ function getLastVersionInfoFromGit() {
|
|
|
2124
2199
|
return null;
|
|
2125
2200
|
}
|
|
2126
2201
|
}
|
|
2202
|
+
function findLastDate(entries) {
|
|
2203
|
+
const dates = Array.from(entries.keys()).sort().reverse();
|
|
2204
|
+
return dates[0] ?? null;
|
|
2205
|
+
}
|
|
2127
2206
|
function getLastVersionInfo(repoName, config) {
|
|
2128
2207
|
const entries = loadDevlogEntries(repoName);
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
}
|
|
2132
|
-
const dates = Array.from(entries.keys()).sort().reverse();
|
|
2133
|
-
const lastDate = dates[0];
|
|
2134
|
-
if (!lastDate) {
|
|
2135
|
-
return null;
|
|
2136
|
-
}
|
|
2208
|
+
const lastDate = findLastDate(entries);
|
|
2209
|
+
if (!lastDate) return null;
|
|
2137
2210
|
if (config?.commit?.conventional) {
|
|
2138
2211
|
const gitInfo = getLastVersionInfoFromGit();
|
|
2139
|
-
if (gitInfo) {
|
|
2140
|
-
return { date: lastDate, version: gitInfo.version };
|
|
2141
|
-
}
|
|
2142
|
-
}
|
|
2143
|
-
const lastEntries = entries.get(lastDate);
|
|
2144
|
-
const lastVersion = lastEntries?.[0]?.version;
|
|
2145
|
-
if (!lastVersion) {
|
|
2146
|
-
return null;
|
|
2212
|
+
if (gitInfo) return { date: lastDate, version: gitInfo.version };
|
|
2147
2213
|
}
|
|
2148
|
-
|
|
2214
|
+
const lastVersion = entries.get(lastDate)?.[0]?.version;
|
|
2215
|
+
return lastVersion ? { date: lastDate, version: lastVersion } : null;
|
|
2216
|
+
}
|
|
2217
|
+
function cleanVersion(version2) {
|
|
2218
|
+
return semver.clean(version2) ?? semver.coerce(version2)?.version ?? null;
|
|
2149
2219
|
}
|
|
2150
2220
|
function bumpVersion(version2, type) {
|
|
2151
|
-
const cleaned =
|
|
2152
|
-
if (!cleaned)
|
|
2153
|
-
return version2;
|
|
2154
|
-
}
|
|
2221
|
+
const cleaned = cleanVersion(version2);
|
|
2222
|
+
if (!cleaned) return version2;
|
|
2155
2223
|
const bumped = semver.inc(cleaned, type);
|
|
2156
|
-
if (!bumped)
|
|
2157
|
-
|
|
2158
|
-
}
|
|
2159
|
-
if (type === "minor") {
|
|
2160
|
-
const parsed = semver.parse(bumped);
|
|
2161
|
-
return parsed ? `v${parsed.major}.${parsed.minor}` : `v${bumped}`;
|
|
2162
|
-
}
|
|
2224
|
+
if (!bumped) return version2;
|
|
2225
|
+
if (type === "minor") return stripToMinor(bumped);
|
|
2163
2226
|
return `v${bumped}`;
|
|
2164
2227
|
}
|
|
2165
2228
|
|
|
2166
|
-
// src/commands/devlog/next/
|
|
2167
|
-
import
|
|
2229
|
+
// src/commands/devlog/next/displayNextEntry/index.ts
|
|
2230
|
+
import { execSync as execSync10 } from "child_process";
|
|
2231
|
+
import chalk31 from "chalk";
|
|
2232
|
+
|
|
2233
|
+
// src/commands/devlog/next/displayNextEntry/displayVersion.ts
|
|
2234
|
+
import chalk30 from "chalk";
|
|
2168
2235
|
function displayVersion(conventional, firstHash, patchVersion, minorVersion) {
|
|
2169
2236
|
if (conventional && firstHash) {
|
|
2170
2237
|
const version2 = getVersionAtCommit(firstHash);
|
|
2171
2238
|
if (version2) {
|
|
2172
|
-
console.log(`${
|
|
2239
|
+
console.log(`${chalk30.bold("version:")} ${stripToMinor(version2)}`);
|
|
2173
2240
|
} else {
|
|
2174
|
-
console.log(`${
|
|
2241
|
+
console.log(`${chalk30.bold("version:")} ${chalk30.red("unknown")}`);
|
|
2175
2242
|
}
|
|
2176
2243
|
} else if (patchVersion && minorVersion) {
|
|
2177
2244
|
console.log(
|
|
2178
|
-
`${
|
|
2245
|
+
`${chalk30.bold("version:")} ${patchVersion} (patch) or ${minorVersion} (minor)`
|
|
2179
2246
|
);
|
|
2180
2247
|
} else {
|
|
2181
|
-
console.log(`${
|
|
2248
|
+
console.log(`${chalk30.bold("version:")} v0.1 (initial)`);
|
|
2182
2249
|
}
|
|
2183
2250
|
}
|
|
2184
2251
|
|
|
2185
|
-
// src/commands/devlog/next/index.ts
|
|
2186
|
-
function
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2252
|
+
// src/commands/devlog/next/displayNextEntry/index.ts
|
|
2253
|
+
function computeVersions(lastInfo) {
|
|
2254
|
+
if (!lastInfo) return { patch: null, minor: null };
|
|
2255
|
+
return {
|
|
2256
|
+
patch: bumpVersion(lastInfo.version, "patch"),
|
|
2257
|
+
minor: bumpVersion(lastInfo.version, "minor")
|
|
2258
|
+
};
|
|
2259
|
+
}
|
|
2260
|
+
function findTargetDate(commitsByDate, skipDays) {
|
|
2261
|
+
return Array.from(commitsByDate.keys()).filter((d) => !skipDays.has(d)).sort()[0];
|
|
2262
|
+
}
|
|
2263
|
+
function fetchCommitsByDate(ignore2, lastDate) {
|
|
2195
2264
|
const output = execSync10(
|
|
2196
2265
|
"git log --pretty=format:'%ad|%h|%s' --date=short -n 500",
|
|
2197
2266
|
{ encoding: "utf-8" }
|
|
2198
2267
|
);
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
if (lastInfo) {
|
|
2204
|
-
console.log(chalk30.dim("No commits after last versioned entry"));
|
|
2205
|
-
} else {
|
|
2206
|
-
console.log(chalk30.dim("No commits found"));
|
|
2207
|
-
}
|
|
2208
|
-
return;
|
|
2209
|
-
}
|
|
2210
|
-
const commits = commitsByDate.get(targetDate) ?? [];
|
|
2211
|
-
console.log(`${chalk30.bold("name:")} ${repoName}`);
|
|
2268
|
+
return parseGitLogCommits(output, ignore2, lastDate);
|
|
2269
|
+
}
|
|
2270
|
+
function printVersionInfo(config, lastInfo, firstHash) {
|
|
2271
|
+
const versions = computeVersions(lastInfo);
|
|
2212
2272
|
displayVersion(
|
|
2213
2273
|
!!config.commit?.conventional,
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2274
|
+
firstHash,
|
|
2275
|
+
versions.patch,
|
|
2276
|
+
versions.minor
|
|
2217
2277
|
);
|
|
2218
|
-
|
|
2219
|
-
|
|
2278
|
+
}
|
|
2279
|
+
function resolveIgnoreList(options, config) {
|
|
2280
|
+
return options.ignore ?? config.devlog?.ignore ?? [];
|
|
2281
|
+
}
|
|
2282
|
+
function resolveSkipDays(config) {
|
|
2283
|
+
return new Set(config.devlog?.skip?.days ?? []);
|
|
2284
|
+
}
|
|
2285
|
+
function getLastDate(lastInfo) {
|
|
2286
|
+
return lastInfo?.date ?? null;
|
|
2287
|
+
}
|
|
2288
|
+
function getCommitsForDate(commitsByDate, date) {
|
|
2289
|
+
return commitsByDate.get(date) ?? [];
|
|
2290
|
+
}
|
|
2291
|
+
function noCommitsMessage(hasLastInfo) {
|
|
2292
|
+
return hasLastInfo ? "No commits after last versioned entry" : "No commits found";
|
|
2293
|
+
}
|
|
2294
|
+
function logName(repoName) {
|
|
2295
|
+
console.log(`${chalk31.bold("name:")} ${repoName}`);
|
|
2296
|
+
}
|
|
2297
|
+
function displayNextEntry(ctx, targetDate, commits) {
|
|
2298
|
+
logName(ctx.repoName);
|
|
2299
|
+
printVersionInfo(ctx.config, ctx.lastInfo, commits[0]?.hash);
|
|
2300
|
+
console.log(chalk31.bold.blue(targetDate));
|
|
2301
|
+
printCommitsWithFiles(commits, ctx.ignore, ctx.verbose);
|
|
2302
|
+
}
|
|
2303
|
+
function logNoCommits(lastInfo) {
|
|
2304
|
+
console.log(chalk31.dim(noCommitsMessage(!!lastInfo)));
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
// src/commands/devlog/next/index.ts
|
|
2308
|
+
function resolveContextData(config, options) {
|
|
2309
|
+
const repoName = getRepoName();
|
|
2310
|
+
const lastInfo = getLastVersionInfo(repoName, config);
|
|
2311
|
+
return { repoName, lastInfo, ignore: resolveIgnoreList(options, config) };
|
|
2312
|
+
}
|
|
2313
|
+
function buildContext(options) {
|
|
2314
|
+
const config = loadConfig();
|
|
2315
|
+
const data = resolveContextData(config, options);
|
|
2316
|
+
return { config, ...data, verbose: options.verbose ?? false };
|
|
2317
|
+
}
|
|
2318
|
+
function fetchNextCommits(ctx) {
|
|
2319
|
+
const commitsByDate = fetchCommitsByDate(
|
|
2320
|
+
ctx.ignore,
|
|
2321
|
+
getLastDate(ctx.lastInfo)
|
|
2322
|
+
);
|
|
2323
|
+
const targetDate = findTargetDate(commitsByDate, resolveSkipDays(ctx.config));
|
|
2324
|
+
return targetDate ? { targetDate, commits: getCommitsForDate(commitsByDate, targetDate) } : null;
|
|
2325
|
+
}
|
|
2326
|
+
function showResult(ctx, found) {
|
|
2327
|
+
if (!found) {
|
|
2328
|
+
logNoCommits(ctx.lastInfo);
|
|
2329
|
+
return;
|
|
2330
|
+
}
|
|
2331
|
+
displayNextEntry(ctx, found.targetDate, found.commits);
|
|
2332
|
+
}
|
|
2333
|
+
function next(options) {
|
|
2334
|
+
const ctx = buildContext(options);
|
|
2335
|
+
showResult(ctx, fetchNextCommits(ctx));
|
|
2220
2336
|
}
|
|
2221
2337
|
|
|
2222
2338
|
// src/commands/devlog/skip.ts
|
|
2223
|
-
import
|
|
2339
|
+
import chalk32 from "chalk";
|
|
2224
2340
|
function skip(date) {
|
|
2225
2341
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
2226
|
-
console.log(
|
|
2342
|
+
console.log(chalk32.red("Invalid date format. Use YYYY-MM-DD"));
|
|
2227
2343
|
process.exit(1);
|
|
2228
2344
|
}
|
|
2229
2345
|
const config = loadConfig();
|
|
2230
2346
|
const skipDays = config.devlog?.skip?.days ?? [];
|
|
2231
2347
|
if (skipDays.includes(date)) {
|
|
2232
|
-
console.log(
|
|
2348
|
+
console.log(chalk32.yellow(`${date} is already in skip list`));
|
|
2233
2349
|
return;
|
|
2234
2350
|
}
|
|
2235
2351
|
skipDays.push(date);
|
|
@@ -2242,20 +2358,20 @@ function skip(date) {
|
|
|
2242
2358
|
}
|
|
2243
2359
|
};
|
|
2244
2360
|
saveConfig(config);
|
|
2245
|
-
console.log(
|
|
2361
|
+
console.log(chalk32.green(`Added ${date} to skip list`));
|
|
2246
2362
|
}
|
|
2247
2363
|
|
|
2248
2364
|
// src/commands/devlog/version.ts
|
|
2249
|
-
import
|
|
2365
|
+
import chalk33 from "chalk";
|
|
2250
2366
|
function version() {
|
|
2251
2367
|
const config = loadConfig();
|
|
2252
2368
|
const name = getRepoName();
|
|
2253
2369
|
const lastInfo = getLastVersionInfo(name, config);
|
|
2254
2370
|
const lastVersion = lastInfo?.version ?? null;
|
|
2255
2371
|
const nextVersion = lastVersion ? bumpVersion(lastVersion, "patch") : null;
|
|
2256
|
-
console.log(`${
|
|
2257
|
-
console.log(`${
|
|
2258
|
-
console.log(`${
|
|
2372
|
+
console.log(`${chalk33.bold("name:")} ${name}`);
|
|
2373
|
+
console.log(`${chalk33.bold("last:")} ${lastVersion ?? chalk33.dim("none")}`);
|
|
2374
|
+
console.log(`${chalk33.bold("next:")} ${nextVersion ?? chalk33.dim("none")}`);
|
|
2259
2375
|
}
|
|
2260
2376
|
|
|
2261
2377
|
// src/commands/registerDevlog.ts
|
|
@@ -2356,9 +2472,7 @@ function resolveThread(threadId) {
|
|
|
2356
2472
|
unlinkSync3(queryFile);
|
|
2357
2473
|
}
|
|
2358
2474
|
}
|
|
2359
|
-
function
|
|
2360
|
-
const prNumber = getCurrentPrNumber();
|
|
2361
|
-
const { org, repo } = getRepoInfo();
|
|
2475
|
+
function requireCache(prNumber) {
|
|
2362
2476
|
const cache = loadCommentsCache(prNumber);
|
|
2363
2477
|
if (!cache) {
|
|
2364
2478
|
console.error(
|
|
@@ -2366,27 +2480,37 @@ function resolveCommentWithReply(commentId, message) {
|
|
|
2366
2480
|
);
|
|
2367
2481
|
process.exit(1);
|
|
2368
2482
|
}
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
if (!comment.threadId) {
|
|
2377
|
-
console.error(
|
|
2483
|
+
return cache;
|
|
2484
|
+
}
|
|
2485
|
+
function findLineComment(comments, commentId) {
|
|
2486
|
+
return comments.find((c) => c.type === "line" && c.id === commentId);
|
|
2487
|
+
}
|
|
2488
|
+
function requireLineComment(cache, commentId) {
|
|
2489
|
+
const comment = findLineComment(cache.comments, commentId);
|
|
2490
|
+
if (!comment || comment.type !== "line" || !comment.threadId) {
|
|
2491
|
+
console.error(
|
|
2492
|
+
`Error: Comment #${commentId} not found or has no thread ID.`
|
|
2493
|
+
);
|
|
2378
2494
|
process.exit(1);
|
|
2379
2495
|
}
|
|
2496
|
+
return comment;
|
|
2497
|
+
}
|
|
2498
|
+
function cleanupCacheIfDone(cache, prNumber, commentId) {
|
|
2499
|
+
const hasRemaining = cache.comments.some(
|
|
2500
|
+
(c) => c.type === "line" && c.id !== commentId
|
|
2501
|
+
);
|
|
2502
|
+
if (!hasRemaining) deleteCommentsCache(prNumber);
|
|
2503
|
+
}
|
|
2504
|
+
function resolveCommentWithReply(commentId, message) {
|
|
2505
|
+
const prNumber = getCurrentPrNumber();
|
|
2506
|
+
const { org, repo } = getRepoInfo();
|
|
2507
|
+
const cache = requireCache(prNumber);
|
|
2508
|
+
const comment = requireLineComment(cache, commentId);
|
|
2380
2509
|
replyToComment(org, repo, prNumber, commentId, message);
|
|
2381
2510
|
console.log("Reply posted successfully.");
|
|
2382
2511
|
resolveThread(comment.threadId);
|
|
2383
2512
|
console.log("Thread resolved successfully.");
|
|
2384
|
-
|
|
2385
|
-
(c) => c.type === "line" && c.id !== commentId
|
|
2386
|
-
);
|
|
2387
|
-
if (remainingLineComments.length === 0) {
|
|
2388
|
-
deleteCommentsCache(prNumber);
|
|
2389
|
-
}
|
|
2513
|
+
cleanupCacheIfDone(cache, prNumber, commentId);
|
|
2390
2514
|
}
|
|
2391
2515
|
|
|
2392
2516
|
// src/commands/prs/fixed.ts
|
|
@@ -2449,86 +2573,79 @@ function fetchThreadIds(org, repo, prNumber) {
|
|
|
2449
2573
|
|
|
2450
2574
|
// src/commands/prs/listComments/fetchReviewComments.ts
|
|
2451
2575
|
import { execSync as execSync14 } from "child_process";
|
|
2452
|
-
function
|
|
2453
|
-
const result = execSync14(
|
|
2454
|
-
`gh api repos/${org}/${repo}/pulls/${prNumber}/reviews`,
|
|
2455
|
-
{ encoding: "utf-8" }
|
|
2456
|
-
);
|
|
2576
|
+
function fetchJson(endpoint) {
|
|
2577
|
+
const result = execSync14(`gh api ${endpoint}`, { encoding: "utf-8" });
|
|
2457
2578
|
if (!result.trim()) return [];
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2579
|
+
return JSON.parse(result);
|
|
2580
|
+
}
|
|
2581
|
+
function mapReview(r) {
|
|
2582
|
+
return {
|
|
2583
|
+
type: "review",
|
|
2584
|
+
id: r.id,
|
|
2585
|
+
user: r.user.login,
|
|
2586
|
+
state: r.state,
|
|
2587
|
+
body: r.body
|
|
2588
|
+
};
|
|
2589
|
+
}
|
|
2590
|
+
function fetchReviewComments(org, repo, prNumber) {
|
|
2591
|
+
const reviews = fetchJson(`repos/${org}/${repo}/pulls/${prNumber}/reviews`);
|
|
2592
|
+
return reviews.filter((r) => r.body).map(mapReview);
|
|
2593
|
+
}
|
|
2594
|
+
function mapLineComment(c, threadInfo) {
|
|
2595
|
+
const threadId = threadInfo.threadMap.get(c.id) ?? "";
|
|
2596
|
+
return {
|
|
2597
|
+
type: "line",
|
|
2598
|
+
id: c.id,
|
|
2599
|
+
threadId,
|
|
2600
|
+
user: c.user.login,
|
|
2601
|
+
path: c.path,
|
|
2602
|
+
line: c.line,
|
|
2603
|
+
body: c.body,
|
|
2604
|
+
diff_hunk: c.diff_hunk,
|
|
2605
|
+
html_url: c.html_url,
|
|
2606
|
+
resolved: threadInfo.resolvedThreadIds.has(threadId)
|
|
2607
|
+
};
|
|
2468
2608
|
}
|
|
2469
2609
|
function fetchLineComments(org, repo, prNumber, threadInfo) {
|
|
2470
|
-
const
|
|
2471
|
-
`gh api repos/${org}/${repo}/pulls/${prNumber}/comments`,
|
|
2472
|
-
{ encoding: "utf-8" }
|
|
2473
|
-
);
|
|
2474
|
-
if (!result.trim()) return [];
|
|
2475
|
-
const comments = JSON.parse(result);
|
|
2610
|
+
const comments = fetchJson(`repos/${org}/${repo}/pulls/${prNumber}/comments`);
|
|
2476
2611
|
return comments.map(
|
|
2477
|
-
(c) =>
|
|
2478
|
-
const threadId = threadInfo.threadMap.get(c.id) ?? "";
|
|
2479
|
-
return {
|
|
2480
|
-
type: "line",
|
|
2481
|
-
id: c.id,
|
|
2482
|
-
threadId,
|
|
2483
|
-
user: c.user.login,
|
|
2484
|
-
path: c.path,
|
|
2485
|
-
line: c.line,
|
|
2486
|
-
body: c.body,
|
|
2487
|
-
diff_hunk: c.diff_hunk,
|
|
2488
|
-
html_url: c.html_url,
|
|
2489
|
-
resolved: threadInfo.resolvedThreadIds.has(threadId)
|
|
2490
|
-
};
|
|
2491
|
-
}
|
|
2612
|
+
(c) => mapLineComment(c, threadInfo)
|
|
2492
2613
|
);
|
|
2493
2614
|
}
|
|
2494
2615
|
|
|
2495
2616
|
// src/commands/prs/listComments/formatForHuman.ts
|
|
2496
|
-
import
|
|
2617
|
+
import chalk34 from "chalk";
|
|
2497
2618
|
function formatForHuman(comment) {
|
|
2498
2619
|
if (comment.type === "review") {
|
|
2499
|
-
const stateColor = comment.state === "APPROVED" ?
|
|
2620
|
+
const stateColor = comment.state === "APPROVED" ? chalk34.green : comment.state === "CHANGES_REQUESTED" ? chalk34.red : chalk34.yellow;
|
|
2500
2621
|
return [
|
|
2501
|
-
`${
|
|
2622
|
+
`${chalk34.cyan("Review")} by ${chalk34.bold(comment.user)} ${stateColor(`[${comment.state}]`)}`,
|
|
2502
2623
|
comment.body,
|
|
2503
2624
|
""
|
|
2504
2625
|
].join("\n");
|
|
2505
2626
|
}
|
|
2506
2627
|
const location = comment.line ? `:${comment.line}` : "";
|
|
2507
2628
|
return [
|
|
2508
|
-
`${
|
|
2509
|
-
|
|
2629
|
+
`${chalk34.cyan("Line comment")} by ${chalk34.bold(comment.user)} on ${chalk34.dim(`${comment.path}${location}`)}`,
|
|
2630
|
+
chalk34.dim(comment.diff_hunk.split("\n").slice(-3).join("\n")),
|
|
2510
2631
|
comment.body,
|
|
2511
2632
|
""
|
|
2512
2633
|
].join("\n");
|
|
2513
2634
|
}
|
|
2514
2635
|
|
|
2515
2636
|
// src/commands/prs/listComments/index.ts
|
|
2637
|
+
function formatComment(comment) {
|
|
2638
|
+
return isClaudeCode() ? JSON.stringify(comment) : formatForHuman(comment);
|
|
2639
|
+
}
|
|
2516
2640
|
function printComments(comments) {
|
|
2517
2641
|
if (comments.length === 0) {
|
|
2518
2642
|
console.log("No comments found.");
|
|
2519
2643
|
return;
|
|
2520
2644
|
}
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
console.log(JSON.stringify(comment));
|
|
2524
|
-
}
|
|
2525
|
-
} else {
|
|
2526
|
-
for (const comment of comments) {
|
|
2527
|
-
console.log(formatForHuman(comment));
|
|
2528
|
-
}
|
|
2645
|
+
for (const comment of comments) {
|
|
2646
|
+
console.log(formatComment(comment));
|
|
2529
2647
|
}
|
|
2530
|
-
|
|
2531
|
-
if (lineComments.length === 0) {
|
|
2648
|
+
if (!comments.some((c) => c.type === "line")) {
|
|
2532
2649
|
console.log("No line comments to process.");
|
|
2533
2650
|
}
|
|
2534
2651
|
}
|
|
@@ -2545,6 +2662,25 @@ function writeCommentsCache(prNumber, comments) {
|
|
|
2545
2662
|
const cachePath = join12(assistDir, `pr-${prNumber}-comments.yaml`);
|
|
2546
2663
|
writeFileSync11(cachePath, stringify(cacheData));
|
|
2547
2664
|
}
|
|
2665
|
+
function handleKnownErrors(error) {
|
|
2666
|
+
if (isGhNotInstalled(error)) {
|
|
2667
|
+
console.error("Error: GitHub CLI (gh) is not installed.");
|
|
2668
|
+
console.error("Install it from https://cli.github.com/");
|
|
2669
|
+
return [];
|
|
2670
|
+
}
|
|
2671
|
+
if (isNotFound(error)) {
|
|
2672
|
+
console.error("Error: Pull request not found.");
|
|
2673
|
+
return [];
|
|
2674
|
+
}
|
|
2675
|
+
return null;
|
|
2676
|
+
}
|
|
2677
|
+
function updateCache(prNumber, comments) {
|
|
2678
|
+
if (comments.some((c) => c.type === "line")) {
|
|
2679
|
+
writeCommentsCache(prNumber, comments);
|
|
2680
|
+
} else {
|
|
2681
|
+
deleteCommentsCache(prNumber);
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2548
2684
|
async function listComments() {
|
|
2549
2685
|
try {
|
|
2550
2686
|
const prNumber = getCurrentPrNumber();
|
|
@@ -2554,22 +2690,11 @@ async function listComments() {
|
|
|
2554
2690
|
...fetchReviewComments(org, repo, prNumber),
|
|
2555
2691
|
...fetchLineComments(org, repo, prNumber, threadInfo)
|
|
2556
2692
|
];
|
|
2557
|
-
|
|
2558
|
-
writeCommentsCache(prNumber, allComments);
|
|
2559
|
-
} else {
|
|
2560
|
-
deleteCommentsCache(prNumber);
|
|
2561
|
-
}
|
|
2693
|
+
updateCache(prNumber, allComments);
|
|
2562
2694
|
return allComments;
|
|
2563
2695
|
} catch (error) {
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
console.error("Install it from https://cli.github.com/");
|
|
2567
|
-
return [];
|
|
2568
|
-
}
|
|
2569
|
-
if (isNotFound(error)) {
|
|
2570
|
-
console.error("Error: Pull request not found.");
|
|
2571
|
-
return [];
|
|
2572
|
-
}
|
|
2696
|
+
const handled = handleKnownErrors(error);
|
|
2697
|
+
if (handled !== null) return handled;
|
|
2573
2698
|
throw error;
|
|
2574
2699
|
}
|
|
2575
2700
|
}
|
|
@@ -2577,69 +2702,104 @@ async function listComments() {
|
|
|
2577
2702
|
// src/commands/prs/prs/index.ts
|
|
2578
2703
|
import { execSync as execSync15 } from "child_process";
|
|
2579
2704
|
|
|
2580
|
-
// src/commands/prs/prs/displayPaginated.ts
|
|
2581
|
-
import chalk34 from "chalk";
|
|
2705
|
+
// src/commands/prs/prs/displayPaginated/index.ts
|
|
2582
2706
|
import enquirer4 from "enquirer";
|
|
2583
|
-
|
|
2707
|
+
|
|
2708
|
+
// src/commands/prs/prs/displayPaginated/printPr.ts
|
|
2709
|
+
import chalk35 from "chalk";
|
|
2710
|
+
var STATUS_MAP = {
|
|
2711
|
+
MERGED: (pr) => pr.mergedAt ? { label: chalk35.magenta("merged"), date: pr.mergedAt } : null,
|
|
2712
|
+
CLOSED: (pr) => pr.closedAt ? { label: chalk35.red("closed"), date: pr.closedAt } : null
|
|
2713
|
+
};
|
|
2714
|
+
function defaultStatus(pr) {
|
|
2715
|
+
return { label: chalk35.green("opened"), date: pr.createdAt };
|
|
2716
|
+
}
|
|
2584
2717
|
function getStatus(pr) {
|
|
2585
|
-
|
|
2586
|
-
return { label: chalk34.magenta("merged"), date: pr.mergedAt };
|
|
2587
|
-
}
|
|
2588
|
-
if (pr.state === "CLOSED" && pr.closedAt) {
|
|
2589
|
-
return { label: chalk34.red("closed"), date: pr.closedAt };
|
|
2590
|
-
}
|
|
2591
|
-
return { label: chalk34.green("opened"), date: pr.createdAt };
|
|
2718
|
+
return STATUS_MAP[pr.state]?.(pr) ?? defaultStatus(pr);
|
|
2592
2719
|
}
|
|
2593
|
-
function
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2720
|
+
function formatDate(dateStr) {
|
|
2721
|
+
return new Date(dateStr).toISOString().split("T")[0];
|
|
2722
|
+
}
|
|
2723
|
+
function formatPrHeader(pr, status) {
|
|
2724
|
+
return `${chalk35.cyan(`#${pr.number}`)} ${pr.title} ${chalk35.dim(`(${pr.author.login},`)} ${status.label} ${chalk35.dim(`${formatDate(status.date)})`)}`;
|
|
2725
|
+
}
|
|
2726
|
+
function logPrDetails(pr) {
|
|
2597
2727
|
console.log(
|
|
2598
|
-
`
|
|
2599
|
-
Page ${page + 1} of ${totalPages} (${pullRequests.length} total)
|
|
2600
|
-
`
|
|
2728
|
+
chalk35.dim(` ${pr.changedFiles.toLocaleString()} files | ${pr.url}`)
|
|
2601
2729
|
);
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2730
|
+
console.log();
|
|
2731
|
+
}
|
|
2732
|
+
function printPr(pr) {
|
|
2733
|
+
console.log(formatPrHeader(pr, getStatus(pr)));
|
|
2734
|
+
logPrDetails(pr);
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
// src/commands/prs/prs/displayPaginated/index.ts
|
|
2738
|
+
var PAGE_SIZE = 10;
|
|
2739
|
+
function getPageSlice(pullRequests, page) {
|
|
2740
|
+
const start = page * PAGE_SIZE;
|
|
2741
|
+
return pullRequests.slice(start, start + PAGE_SIZE);
|
|
2742
|
+
}
|
|
2743
|
+
function pageHeader(page, totalPages, total) {
|
|
2744
|
+
return `
|
|
2745
|
+
Page ${page + 1} of ${totalPages} (${total} total)
|
|
2746
|
+
`;
|
|
2747
|
+
}
|
|
2748
|
+
function displayPage(pullRequests, totalPages, page) {
|
|
2749
|
+
console.log(pageHeader(page, totalPages, pullRequests.length));
|
|
2750
|
+
for (const pr of getPageSlice(pullRequests, page)) printPr(pr);
|
|
2751
|
+
}
|
|
2752
|
+
function hasNextPage(page, total) {
|
|
2753
|
+
return page < total - 1;
|
|
2754
|
+
}
|
|
2755
|
+
var NEXT_CHOICE = { name: "Next page", value: "next" };
|
|
2756
|
+
var PREV_CHOICE = { name: "Previous page", value: "prev" };
|
|
2757
|
+
function getOptionalChoices(currentPage, totalPages) {
|
|
2758
|
+
const choices = [];
|
|
2759
|
+
if (hasNextPage(currentPage, totalPages)) choices.push(NEXT_CHOICE);
|
|
2760
|
+
if (currentPage > 0) choices.push(PREV_CHOICE);
|
|
2761
|
+
return choices;
|
|
2762
|
+
}
|
|
2763
|
+
function buildNavChoices(currentPage, totalPages) {
|
|
2764
|
+
return [
|
|
2765
|
+
...getOptionalChoices(currentPage, totalPages),
|
|
2766
|
+
{ name: "Quit", value: "quit" }
|
|
2767
|
+
];
|
|
2768
|
+
}
|
|
2769
|
+
function parseAction(action) {
|
|
2770
|
+
if (action === "Next page") return 1;
|
|
2771
|
+
if (action === "Previous page") return -1;
|
|
2772
|
+
return 0;
|
|
2773
|
+
}
|
|
2774
|
+
async function promptNavigation(currentPage, totalPages) {
|
|
2775
|
+
const choices = buildNavChoices(currentPage, totalPages);
|
|
2776
|
+
const { action } = await enquirer4.prompt({
|
|
2777
|
+
type: "select",
|
|
2778
|
+
name: "action",
|
|
2779
|
+
message: "Navigate",
|
|
2780
|
+
choices
|
|
2781
|
+
});
|
|
2782
|
+
return parseAction(action);
|
|
2783
|
+
}
|
|
2784
|
+
function computeTotalPages(count) {
|
|
2785
|
+
return Math.ceil(count / PAGE_SIZE);
|
|
2786
|
+
}
|
|
2787
|
+
async function navigateAndDisplay(pullRequests, totalPages, currentPage) {
|
|
2788
|
+
const delta = await promptNavigation(currentPage, totalPages);
|
|
2789
|
+
if (delta === 0) return null;
|
|
2790
|
+
const next2 = currentPage + delta;
|
|
2791
|
+
displayPage(pullRequests, totalPages, next2);
|
|
2792
|
+
return next2;
|
|
2793
|
+
}
|
|
2794
|
+
async function paginationLoop(pullRequests, totalPages) {
|
|
2795
|
+
let page = 0;
|
|
2796
|
+
displayPage(pullRequests, totalPages, page);
|
|
2797
|
+
while (totalPages > 1 && page !== null) {
|
|
2798
|
+
page = await navigateAndDisplay(pullRequests, totalPages, page);
|
|
2611
2799
|
}
|
|
2612
2800
|
}
|
|
2613
2801
|
async function displayPaginated(pullRequests) {
|
|
2614
|
-
|
|
2615
|
-
let currentPage = 0;
|
|
2616
|
-
displayPage(pullRequests, totalPages, currentPage);
|
|
2617
|
-
if (totalPages <= 1) {
|
|
2618
|
-
return;
|
|
2619
|
-
}
|
|
2620
|
-
while (true) {
|
|
2621
|
-
const hasNext = currentPage < totalPages - 1;
|
|
2622
|
-
const hasPrev = currentPage > 0;
|
|
2623
|
-
const choices = [];
|
|
2624
|
-
if (hasNext) choices.push({ name: "Next page", value: "next" });
|
|
2625
|
-
if (hasPrev) choices.push({ name: "Previous page", value: "prev" });
|
|
2626
|
-
choices.push({ name: "Quit", value: "quit" });
|
|
2627
|
-
const { action } = await enquirer4.prompt({
|
|
2628
|
-
type: "select",
|
|
2629
|
-
name: "action",
|
|
2630
|
-
message: "Navigate",
|
|
2631
|
-
choices
|
|
2632
|
-
});
|
|
2633
|
-
if (action === "Next page") {
|
|
2634
|
-
currentPage++;
|
|
2635
|
-
displayPage(pullRequests, totalPages, currentPage);
|
|
2636
|
-
} else if (action === "Previous page") {
|
|
2637
|
-
currentPage--;
|
|
2638
|
-
displayPage(pullRequests, totalPages, currentPage);
|
|
2639
|
-
} else {
|
|
2640
|
-
break;
|
|
2641
|
-
}
|
|
2642
|
-
}
|
|
2802
|
+
await paginationLoop(pullRequests, computeTotalPages(pullRequests.length));
|
|
2643
2803
|
}
|
|
2644
2804
|
|
|
2645
2805
|
// src/commands/prs/prs/index.ts
|
|
@@ -2732,7 +2892,7 @@ import { spawn as spawn2 } from "child_process";
|
|
|
2732
2892
|
import * as path15 from "path";
|
|
2733
2893
|
|
|
2734
2894
|
// src/commands/refactor/logViolations.ts
|
|
2735
|
-
import
|
|
2895
|
+
import chalk36 from "chalk";
|
|
2736
2896
|
var DEFAULT_MAX_LINES = 100;
|
|
2737
2897
|
function logViolations(violations, maxLines = DEFAULT_MAX_LINES) {
|
|
2738
2898
|
if (violations.length === 0) {
|
|
@@ -2741,43 +2901,43 @@ function logViolations(violations, maxLines = DEFAULT_MAX_LINES) {
|
|
|
2741
2901
|
}
|
|
2742
2902
|
return;
|
|
2743
2903
|
}
|
|
2744
|
-
console.error(
|
|
2904
|
+
console.error(chalk36.red(`
|
|
2745
2905
|
Refactor check failed:
|
|
2746
2906
|
`));
|
|
2747
|
-
console.error(
|
|
2907
|
+
console.error(chalk36.red(` The following files exceed ${maxLines} lines:
|
|
2748
2908
|
`));
|
|
2749
2909
|
for (const violation of violations) {
|
|
2750
|
-
console.error(
|
|
2910
|
+
console.error(chalk36.red(` ${violation.file} (${violation.lines} lines)`));
|
|
2751
2911
|
}
|
|
2752
2912
|
console.error(
|
|
2753
|
-
|
|
2913
|
+
chalk36.yellow(
|
|
2754
2914
|
`
|
|
2755
2915
|
Each file needs to be sensibly refactored, or if there is no sensible
|
|
2756
2916
|
way to refactor it, ignore it with:
|
|
2757
2917
|
`
|
|
2758
2918
|
)
|
|
2759
2919
|
);
|
|
2760
|
-
console.error(
|
|
2920
|
+
console.error(chalk36.gray(` assist refactor ignore <file>
|
|
2761
2921
|
`));
|
|
2762
2922
|
if (process.env.CLAUDECODE) {
|
|
2763
|
-
console.error(
|
|
2923
|
+
console.error(chalk36.cyan(`
|
|
2764
2924
|
## Extracting Code to New Files
|
|
2765
2925
|
`));
|
|
2766
2926
|
console.error(
|
|
2767
|
-
|
|
2927
|
+
chalk36.cyan(
|
|
2768
2928
|
` When extracting logic from one file to another, consider where the extracted code belongs:
|
|
2769
2929
|
`
|
|
2770
2930
|
)
|
|
2771
2931
|
);
|
|
2772
2932
|
console.error(
|
|
2773
|
-
|
|
2933
|
+
chalk36.cyan(
|
|
2774
2934
|
` 1. Keep related logic together: If the extracted code is tightly coupled to the
|
|
2775
2935
|
original file's domain, create a new folder containing both the original and extracted files.
|
|
2776
2936
|
`
|
|
2777
2937
|
)
|
|
2778
2938
|
);
|
|
2779
2939
|
console.error(
|
|
2780
|
-
|
|
2940
|
+
chalk36.cyan(
|
|
2781
2941
|
` 2. Share common utilities: If the extracted code can be reused across multiple
|
|
2782
2942
|
domains, move it to a common/shared folder.
|
|
2783
2943
|
`
|
|
@@ -2874,46 +3034,45 @@ function getViolations(pattern2, options = {}, maxLines = DEFAULT_MAX_LINES) {
|
|
|
2874
3034
|
}
|
|
2875
3035
|
|
|
2876
3036
|
// src/commands/refactor/check/index.ts
|
|
3037
|
+
function runScript(script, cwd) {
|
|
3038
|
+
return new Promise((resolve) => {
|
|
3039
|
+
const child = spawn2("npm", ["run", script], {
|
|
3040
|
+
stdio: "pipe",
|
|
3041
|
+
shell: true,
|
|
3042
|
+
cwd
|
|
3043
|
+
});
|
|
3044
|
+
let output = "";
|
|
3045
|
+
child.stdout?.on("data", (data) => {
|
|
3046
|
+
output += data.toString();
|
|
3047
|
+
});
|
|
3048
|
+
child.stderr?.on("data", (data) => {
|
|
3049
|
+
output += data.toString();
|
|
3050
|
+
});
|
|
3051
|
+
child.on("close", (code) => {
|
|
3052
|
+
resolve({ script, code: code ?? 1, output });
|
|
3053
|
+
});
|
|
3054
|
+
});
|
|
3055
|
+
}
|
|
3056
|
+
function logFailures(failed) {
|
|
3057
|
+
for (const f of failed) {
|
|
3058
|
+
console.error(f.output);
|
|
3059
|
+
}
|
|
3060
|
+
console.error(`
|
|
3061
|
+
${failed.length} verify script(s) failed:`);
|
|
3062
|
+
for (const f of failed) {
|
|
3063
|
+
console.error(` - ${f.script} (exit code ${f.code})`);
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
2877
3066
|
async function runVerifyQuietly() {
|
|
2878
3067
|
const result = findPackageJsonWithVerifyScripts(process.cwd());
|
|
2879
|
-
if (!result)
|
|
2880
|
-
|
|
2881
|
-
}
|
|
2882
|
-
const { packageJsonPath, verifyScripts } = result;
|
|
2883
|
-
const packageDir = path15.dirname(packageJsonPath);
|
|
3068
|
+
if (!result) return true;
|
|
3069
|
+
const packageDir = path15.dirname(result.packageJsonPath);
|
|
2884
3070
|
const results = await Promise.all(
|
|
2885
|
-
verifyScripts.map(
|
|
2886
|
-
(script) => new Promise(
|
|
2887
|
-
(resolve) => {
|
|
2888
|
-
const child = spawn2("npm", ["run", script], {
|
|
2889
|
-
stdio: "pipe",
|
|
2890
|
-
shell: true,
|
|
2891
|
-
cwd: packageDir
|
|
2892
|
-
});
|
|
2893
|
-
let output = "";
|
|
2894
|
-
child.stdout?.on("data", (data) => {
|
|
2895
|
-
output += data.toString();
|
|
2896
|
-
});
|
|
2897
|
-
child.stderr?.on("data", (data) => {
|
|
2898
|
-
output += data.toString();
|
|
2899
|
-
});
|
|
2900
|
-
child.on("close", (code) => {
|
|
2901
|
-
resolve({ script, code: code ?? 1, output });
|
|
2902
|
-
});
|
|
2903
|
-
}
|
|
2904
|
-
)
|
|
2905
|
-
)
|
|
3071
|
+
result.verifyScripts.map((script) => runScript(script, packageDir))
|
|
2906
3072
|
);
|
|
2907
3073
|
const failed = results.filter((r) => r.code !== 0);
|
|
2908
3074
|
if (failed.length > 0) {
|
|
2909
|
-
|
|
2910
|
-
console.error(f.output);
|
|
2911
|
-
}
|
|
2912
|
-
console.error(`
|
|
2913
|
-
${failed.length} verify script(s) failed:`);
|
|
2914
|
-
for (const f of failed) {
|
|
2915
|
-
console.error(` - ${f.script} (exit code ${f.code})`);
|
|
2916
|
-
}
|
|
3075
|
+
logFailures(failed);
|
|
2917
3076
|
return false;
|
|
2918
3077
|
}
|
|
2919
3078
|
return true;
|
|
@@ -2934,11 +3093,11 @@ async function check(pattern2, options) {
|
|
|
2934
3093
|
|
|
2935
3094
|
// src/commands/refactor/ignore.ts
|
|
2936
3095
|
import fs16 from "fs";
|
|
2937
|
-
import
|
|
3096
|
+
import chalk37 from "chalk";
|
|
2938
3097
|
var REFACTOR_YML_PATH2 = "refactor.yml";
|
|
2939
3098
|
function ignore(file) {
|
|
2940
3099
|
if (!fs16.existsSync(file)) {
|
|
2941
|
-
console.error(
|
|
3100
|
+
console.error(chalk37.red(`Error: File does not exist: ${file}`));
|
|
2942
3101
|
process.exit(1);
|
|
2943
3102
|
}
|
|
2944
3103
|
const content = fs16.readFileSync(file, "utf-8");
|
|
@@ -2954,15 +3113,15 @@ function ignore(file) {
|
|
|
2954
3113
|
fs16.writeFileSync(REFACTOR_YML_PATH2, entry);
|
|
2955
3114
|
}
|
|
2956
3115
|
console.log(
|
|
2957
|
-
|
|
3116
|
+
chalk37.green(
|
|
2958
3117
|
`Added ${file} to refactor ignore list (max ${maxLines} lines)`
|
|
2959
3118
|
)
|
|
2960
3119
|
);
|
|
2961
3120
|
}
|
|
2962
3121
|
|
|
2963
3122
|
// src/commands/refactor/restructure/index.ts
|
|
2964
|
-
import
|
|
2965
|
-
import
|
|
3123
|
+
import path24 from "path";
|
|
3124
|
+
import chalk40 from "chalk";
|
|
2966
3125
|
|
|
2967
3126
|
// src/commands/refactor/restructure/buildImportGraph/index.ts
|
|
2968
3127
|
import path16 from "path";
|
|
@@ -2987,24 +3146,32 @@ function getImportSpecifiers(sourceFile) {
|
|
|
2987
3146
|
}
|
|
2988
3147
|
|
|
2989
3148
|
// src/commands/refactor/restructure/buildImportGraph/index.ts
|
|
2990
|
-
function
|
|
3149
|
+
function loadParsedConfig(tsConfigPath) {
|
|
2991
3150
|
const configFile = ts7.readConfigFile(tsConfigPath, ts7.sys.readFile);
|
|
2992
|
-
|
|
3151
|
+
return ts7.parseJsonConfigFileContent(
|
|
2993
3152
|
configFile.config,
|
|
2994
3153
|
ts7.sys,
|
|
2995
3154
|
path16.dirname(tsConfigPath)
|
|
2996
3155
|
);
|
|
2997
|
-
|
|
3156
|
+
}
|
|
3157
|
+
function addToSetMap(map, key, value) {
|
|
3158
|
+
let set = map.get(key);
|
|
3159
|
+
if (!set) {
|
|
3160
|
+
set = /* @__PURE__ */ new Set();
|
|
3161
|
+
map.set(key, set);
|
|
3162
|
+
}
|
|
3163
|
+
set.add(value);
|
|
3164
|
+
}
|
|
3165
|
+
function resolveImport(specifier, filePath, options) {
|
|
3166
|
+
if (!specifier.startsWith(".")) return null;
|
|
3167
|
+
const resolved = ts7.resolveModuleName(specifier, filePath, options, ts7.sys);
|
|
3168
|
+
const resolvedPath = resolved.resolvedModule?.resolvedFileName;
|
|
3169
|
+
if (!resolvedPath || resolvedPath.includes("node_modules")) return null;
|
|
3170
|
+
return path16.resolve(resolvedPath);
|
|
2998
3171
|
}
|
|
2999
3172
|
function buildImportGraph(candidateFiles, tsConfigPath) {
|
|
3000
|
-
const
|
|
3001
|
-
const
|
|
3002
|
-
const parsed = ts7.parseJsonConfigFileContent(
|
|
3003
|
-
configFile.config,
|
|
3004
|
-
ts7.sys,
|
|
3005
|
-
path16.dirname(tsConfigPath)
|
|
3006
|
-
);
|
|
3007
|
-
const program2 = ts7.createProgram(parsed.fileNames, options);
|
|
3173
|
+
const parsed = loadParsedConfig(tsConfigPath);
|
|
3174
|
+
const program2 = ts7.createProgram(parsed.fileNames, parsed.options);
|
|
3008
3175
|
const edges = [];
|
|
3009
3176
|
const importedBy = /* @__PURE__ */ new Map();
|
|
3010
3177
|
const imports = /* @__PURE__ */ new Map();
|
|
@@ -3012,23 +3179,11 @@ function buildImportGraph(candidateFiles, tsConfigPath) {
|
|
|
3012
3179
|
const filePath = path16.resolve(sourceFile.fileName);
|
|
3013
3180
|
if (filePath.includes("node_modules")) continue;
|
|
3014
3181
|
for (const specifier of getImportSpecifiers(sourceFile)) {
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
specifier,
|
|
3018
|
-
filePath,
|
|
3019
|
-
options,
|
|
3020
|
-
ts7.sys
|
|
3021
|
-
);
|
|
3022
|
-
const resolvedPath = resolved.resolvedModule?.resolvedFileName;
|
|
3023
|
-
if (!resolvedPath || resolvedPath.includes("node_modules")) continue;
|
|
3024
|
-
const absTarget = path16.resolve(resolvedPath);
|
|
3182
|
+
const absTarget = resolveImport(specifier, filePath, parsed.options);
|
|
3183
|
+
if (!absTarget) continue;
|
|
3025
3184
|
edges.push({ source: filePath, target: absTarget, specifier });
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
targetSet.add(filePath);
|
|
3029
|
-
const sourceSet = imports.get(filePath) ?? /* @__PURE__ */ new Set();
|
|
3030
|
-
if (!imports.has(filePath)) imports.set(filePath, sourceSet);
|
|
3031
|
-
sourceSet.add(absTarget);
|
|
3185
|
+
addToSetMap(importedBy, absTarget, filePath);
|
|
3186
|
+
addToSetMap(imports, filePath, absTarget);
|
|
3032
3187
|
}
|
|
3033
3188
|
}
|
|
3034
3189
|
return { files: candidateFiles, edges, importedBy, imports };
|
|
@@ -3109,109 +3264,150 @@ function clusterFiles(graph) {
|
|
|
3109
3264
|
return clusters;
|
|
3110
3265
|
}
|
|
3111
3266
|
|
|
3112
|
-
// src/commands/refactor/restructure/computeRewrites.ts
|
|
3113
|
-
import fs17 from "fs";
|
|
3267
|
+
// src/commands/refactor/restructure/computeRewrites/index.ts
|
|
3114
3268
|
import path19 from "path";
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
const newFile = moveMap.get(file) ?? file;
|
|
3123
|
-
const edgesFromFile = edges.filter((e) => e.source === file);
|
|
3124
|
-
for (const edge of edgesFromFile) {
|
|
3125
|
-
const newTarget = moveMap.get(edge.target);
|
|
3126
|
-
if (!newTarget && !moveMap.has(file)) continue;
|
|
3127
|
-
const targetPath = newTarget ?? edge.target;
|
|
3128
|
-
const newSpecifier = computeSpecifier(newFile, targetPath);
|
|
3129
|
-
if (newSpecifier === edge.specifier) continue;
|
|
3130
|
-
rewrites.push({
|
|
3131
|
-
file,
|
|
3132
|
-
oldSpecifier: edge.specifier,
|
|
3133
|
-
newSpecifier
|
|
3134
|
-
});
|
|
3135
|
-
}
|
|
3136
|
-
}
|
|
3137
|
-
return rewrites;
|
|
3269
|
+
|
|
3270
|
+
// src/commands/refactor/restructure/computeRewrites/applyRewrites.ts
|
|
3271
|
+
import fs17 from "fs";
|
|
3272
|
+
function getOrCreateList(map, key) {
|
|
3273
|
+
const list2 = map.get(key) ?? [];
|
|
3274
|
+
if (!map.has(key)) map.set(key, list2);
|
|
3275
|
+
return list2;
|
|
3138
3276
|
}
|
|
3139
|
-
function
|
|
3140
|
-
const
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
rel = rel.replace(/\.tsx?$/, "");
|
|
3144
|
-
if (rel.endsWith("/index")) {
|
|
3145
|
-
rel = rel.slice(0, -"/index".length);
|
|
3277
|
+
function groupByFile(rewrites) {
|
|
3278
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
3279
|
+
for (const rewrite of rewrites) {
|
|
3280
|
+
getOrCreateList(grouped, rewrite.file).push(rewrite);
|
|
3146
3281
|
}
|
|
3147
|
-
|
|
3148
|
-
|
|
3282
|
+
return grouped;
|
|
3283
|
+
}
|
|
3284
|
+
function rewriteSpecifier(content, oldSpecifier, newSpecifier) {
|
|
3285
|
+
const escaped = oldSpecifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3286
|
+
const pattern2 = new RegExp(`(from\\s+["'])${escaped}(["'])`, "g");
|
|
3287
|
+
return content.replace(pattern2, `$1${newSpecifier}$2`);
|
|
3288
|
+
}
|
|
3289
|
+
function applyFileRewrites(file, fileRewrites) {
|
|
3290
|
+
let content = fs17.readFileSync(file, "utf-8");
|
|
3291
|
+
for (const { oldSpecifier, newSpecifier } of fileRewrites) {
|
|
3292
|
+
content = rewriteSpecifier(content, oldSpecifier, newSpecifier);
|
|
3149
3293
|
}
|
|
3150
|
-
return
|
|
3294
|
+
return content;
|
|
3151
3295
|
}
|
|
3152
3296
|
function applyRewrites(rewrites) {
|
|
3153
|
-
const fileRewrites = /* @__PURE__ */ new Map();
|
|
3154
|
-
for (const rewrite of rewrites) {
|
|
3155
|
-
const existing = fileRewrites.get(rewrite.file) ?? [];
|
|
3156
|
-
if (!fileRewrites.has(rewrite.file))
|
|
3157
|
-
fileRewrites.set(rewrite.file, existing);
|
|
3158
|
-
existing.push(rewrite);
|
|
3159
|
-
}
|
|
3160
3297
|
const updatedContents = /* @__PURE__ */ new Map();
|
|
3161
|
-
for (const [file,
|
|
3162
|
-
|
|
3163
|
-
for (const { oldSpecifier, newSpecifier } of fileSpecificRewrites) {
|
|
3164
|
-
const escaped = oldSpecifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3165
|
-
const pattern2 = new RegExp(`(from\\s+["'])${escaped}(["'])`, "g");
|
|
3166
|
-
content = content.replace(pattern2, `$1${newSpecifier}$2`);
|
|
3167
|
-
}
|
|
3168
|
-
updatedContents.set(file, content);
|
|
3298
|
+
for (const [file, fileRewrites] of groupByFile(rewrites)) {
|
|
3299
|
+
updatedContents.set(file, applyFileRewrites(file, fileRewrites));
|
|
3169
3300
|
}
|
|
3170
3301
|
return updatedContents;
|
|
3171
3302
|
}
|
|
3172
3303
|
|
|
3304
|
+
// src/commands/refactor/restructure/computeRewrites/index.ts
|
|
3305
|
+
function buildMoveMap(moves) {
|
|
3306
|
+
const map = /* @__PURE__ */ new Map();
|
|
3307
|
+
for (const move of moves) map.set(move.from, move.to);
|
|
3308
|
+
return map;
|
|
3309
|
+
}
|
|
3310
|
+
function stripTrailingIndex(specifier) {
|
|
3311
|
+
return specifier.endsWith("/index") ? specifier.slice(0, -"/index".length) : specifier;
|
|
3312
|
+
}
|
|
3313
|
+
function ensureRelative(specifier) {
|
|
3314
|
+
return specifier.startsWith(".") ? specifier : `./${specifier}`;
|
|
3315
|
+
}
|
|
3316
|
+
function normalizeSpecifier(rel) {
|
|
3317
|
+
return ensureRelative(
|
|
3318
|
+
stripTrailingIndex(rel.replace(/\\/g, "/").replace(/\.tsx?$/, ""))
|
|
3319
|
+
);
|
|
3320
|
+
}
|
|
3321
|
+
function computeSpecifier(fromFile, toFile) {
|
|
3322
|
+
return normalizeSpecifier(path19.relative(path19.dirname(fromFile), toFile));
|
|
3323
|
+
}
|
|
3324
|
+
function isAffected(edge, moveMap) {
|
|
3325
|
+
return moveMap.has(edge.target) || moveMap.has(edge.source);
|
|
3326
|
+
}
|
|
3327
|
+
function resolveTarget(edge, moveMap) {
|
|
3328
|
+
return moveMap.get(edge.target) ?? edge.target;
|
|
3329
|
+
}
|
|
3330
|
+
function createRewrite(edge, newSpecifier) {
|
|
3331
|
+
return { file: edge.source, oldSpecifier: edge.specifier, newSpecifier };
|
|
3332
|
+
}
|
|
3333
|
+
function rewriteIfChanged(edge, newSpecifier) {
|
|
3334
|
+
return newSpecifier === edge.specifier ? null : createRewrite(edge, newSpecifier);
|
|
3335
|
+
}
|
|
3336
|
+
function rewriteEdge(edge, newFile, moveMap) {
|
|
3337
|
+
if (!isAffected(edge, moveMap)) return null;
|
|
3338
|
+
return rewriteIfChanged(
|
|
3339
|
+
edge,
|
|
3340
|
+
computeSpecifier(newFile, resolveTarget(edge, moveMap))
|
|
3341
|
+
);
|
|
3342
|
+
}
|
|
3343
|
+
function fileEdges(edges, file) {
|
|
3344
|
+
return edges.filter((e) => e.source === file);
|
|
3345
|
+
}
|
|
3346
|
+
function collectRewrites(edges, newFile, moveMap) {
|
|
3347
|
+
const rewrites = [];
|
|
3348
|
+
for (const edge of edges) {
|
|
3349
|
+
const rewrite = rewriteEdge(edge, newFile, moveMap);
|
|
3350
|
+
if (rewrite) rewrites.push(rewrite);
|
|
3351
|
+
}
|
|
3352
|
+
return rewrites;
|
|
3353
|
+
}
|
|
3354
|
+
function rewriteEdgesForFile(file, edges, moveMap) {
|
|
3355
|
+
const newFile = moveMap.get(file) ?? file;
|
|
3356
|
+
return collectRewrites(fileEdges(edges, file), newFile, moveMap);
|
|
3357
|
+
}
|
|
3358
|
+
function computeRewrites(moves, edges, allProjectFiles) {
|
|
3359
|
+
const moveMap = buildMoveMap(moves);
|
|
3360
|
+
return [...allProjectFiles].flatMap(
|
|
3361
|
+
(file) => rewriteEdgesForFile(file, edges, moveMap)
|
|
3362
|
+
);
|
|
3363
|
+
}
|
|
3364
|
+
|
|
3173
3365
|
// src/commands/refactor/restructure/displayPlan.ts
|
|
3174
3366
|
import path20 from "path";
|
|
3175
|
-
import
|
|
3176
|
-
function
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3367
|
+
import chalk38 from "chalk";
|
|
3368
|
+
function relPath(filePath) {
|
|
3369
|
+
return path20.relative(process.cwd(), filePath);
|
|
3370
|
+
}
|
|
3371
|
+
function displayMoves(plan) {
|
|
3372
|
+
if (plan.moves.length === 0) return;
|
|
3373
|
+
console.log(chalk38.bold("\nFile moves:"));
|
|
3374
|
+
for (const move of plan.moves) {
|
|
3375
|
+
console.log(
|
|
3376
|
+
` ${chalk38.red(relPath(move.from))} \u2192 ${chalk38.green(relPath(move.to))}`
|
|
3377
|
+
);
|
|
3378
|
+
console.log(chalk38.dim(` ${move.reason}`));
|
|
3182
3379
|
}
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3380
|
+
}
|
|
3381
|
+
function displayRewrites(rewrites) {
|
|
3382
|
+
if (rewrites.length === 0) return;
|
|
3383
|
+
const affectedFiles = new Set(rewrites.map((r) => r.file));
|
|
3384
|
+
console.log(chalk38.bold(`
|
|
3385
|
+
Import rewrites (${affectedFiles.size} files):`));
|
|
3386
|
+
for (const file of affectedFiles) {
|
|
3387
|
+
console.log(` ${chalk38.cyan(relPath(file))}:`);
|
|
3388
|
+
for (const { oldSpecifier, newSpecifier } of rewrites.filter(
|
|
3389
|
+
(r) => r.file === file
|
|
3390
|
+
)) {
|
|
3391
|
+
console.log(
|
|
3392
|
+
` ${chalk38.red(`"${oldSpecifier}"`)} \u2192 ${chalk38.green(`"${newSpecifier}"`)}`
|
|
3393
|
+
);
|
|
3187
3394
|
}
|
|
3188
3395
|
}
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
console.log(` ${chalk37.red(fromRel)} \u2192 ${chalk37.green(toRel)}`);
|
|
3195
|
-
console.log(chalk37.dim(` ${move.reason}`));
|
|
3196
|
-
}
|
|
3396
|
+
}
|
|
3397
|
+
function displayPlan(plan) {
|
|
3398
|
+
if (plan.warnings.length > 0) {
|
|
3399
|
+
console.log(chalk38.yellow("\nWarnings:"));
|
|
3400
|
+
for (const w of plan.warnings) console.log(chalk38.yellow(` ${w}`));
|
|
3197
3401
|
}
|
|
3198
|
-
if (plan.
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
for (const file of affectedFiles) {
|
|
3203
|
-
const fileRewrites = plan.rewrites.filter((r) => r.file === file);
|
|
3204
|
-
const rel = path20.relative(process.cwd(), file);
|
|
3205
|
-
console.log(` ${chalk37.cyan(rel)}:`);
|
|
3206
|
-
for (const { oldSpecifier, newSpecifier } of fileRewrites) {
|
|
3207
|
-
console.log(
|
|
3208
|
-
` ${chalk37.red(`"${oldSpecifier}"`)} \u2192 ${chalk37.green(`"${newSpecifier}"`)}`
|
|
3209
|
-
);
|
|
3210
|
-
}
|
|
3211
|
-
}
|
|
3402
|
+
if (plan.newDirectories.length > 0) {
|
|
3403
|
+
console.log(chalk38.bold("\nNew directories:"));
|
|
3404
|
+
for (const dir of plan.newDirectories)
|
|
3405
|
+
console.log(chalk38.green(` ${dir}/`));
|
|
3212
3406
|
}
|
|
3407
|
+
displayMoves(plan);
|
|
3408
|
+
displayRewrites(plan.rewrites);
|
|
3213
3409
|
console.log(
|
|
3214
|
-
|
|
3410
|
+
chalk38.dim(
|
|
3215
3411
|
`
|
|
3216
3412
|
Summary: ${plan.moves.length} file(s) moved, ${plan.rewrites.length} imports rewritten`
|
|
3217
3413
|
)
|
|
@@ -3221,18 +3417,18 @@ Summary: ${plan.moves.length} file(s) moved, ${plan.rewrites.length} imports rew
|
|
|
3221
3417
|
// src/commands/refactor/restructure/executePlan.ts
|
|
3222
3418
|
import fs18 from "fs";
|
|
3223
3419
|
import path21 from "path";
|
|
3224
|
-
import
|
|
3420
|
+
import chalk39 from "chalk";
|
|
3225
3421
|
function executePlan(plan) {
|
|
3226
3422
|
const updatedContents = applyRewrites(plan.rewrites);
|
|
3227
3423
|
for (const [file, content] of updatedContents) {
|
|
3228
3424
|
fs18.writeFileSync(file, content, "utf-8");
|
|
3229
3425
|
console.log(
|
|
3230
|
-
|
|
3426
|
+
chalk39.cyan(` Rewrote imports in ${path21.relative(process.cwd(), file)}`)
|
|
3231
3427
|
);
|
|
3232
3428
|
}
|
|
3233
3429
|
for (const dir of plan.newDirectories) {
|
|
3234
3430
|
fs18.mkdirSync(dir, { recursive: true });
|
|
3235
|
-
console.log(
|
|
3431
|
+
console.log(chalk39.green(` Created ${path21.relative(process.cwd(), dir)}/`));
|
|
3236
3432
|
}
|
|
3237
3433
|
for (const move of plan.moves) {
|
|
3238
3434
|
const targetDir = path21.dirname(move.to);
|
|
@@ -3241,7 +3437,7 @@ function executePlan(plan) {
|
|
|
3241
3437
|
}
|
|
3242
3438
|
fs18.renameSync(move.from, move.to);
|
|
3243
3439
|
console.log(
|
|
3244
|
-
|
|
3440
|
+
chalk39.white(
|
|
3245
3441
|
` Moved ${path21.relative(process.cwd(), move.from)} \u2192 ${path21.relative(process.cwd(), move.to)}`
|
|
3246
3442
|
)
|
|
3247
3443
|
);
|
|
@@ -3256,7 +3452,7 @@ function removeEmptyDirectories(dirs) {
|
|
|
3256
3452
|
if (entries.length === 0) {
|
|
3257
3453
|
fs18.rmdirSync(dir);
|
|
3258
3454
|
console.log(
|
|
3259
|
-
|
|
3455
|
+
chalk39.dim(
|
|
3260
3456
|
` Removed empty directory ${path21.relative(process.cwd(), dir)}`
|
|
3261
3457
|
)
|
|
3262
3458
|
);
|
|
@@ -3264,84 +3460,110 @@ function removeEmptyDirectories(dirs) {
|
|
|
3264
3460
|
}
|
|
3265
3461
|
}
|
|
3266
3462
|
|
|
3267
|
-
// src/commands/refactor/restructure/planFileMoves.ts
|
|
3463
|
+
// src/commands/refactor/restructure/planFileMoves/index.ts
|
|
3464
|
+
import fs20 from "fs";
|
|
3465
|
+
import path23 from "path";
|
|
3466
|
+
|
|
3467
|
+
// src/commands/refactor/restructure/planFileMoves/planDirectoryMoves.ts
|
|
3268
3468
|
import fs19 from "fs";
|
|
3269
3469
|
import path22 from "path";
|
|
3270
|
-
function
|
|
3271
|
-
|
|
3470
|
+
function collectEntry(results, dir, entry) {
|
|
3471
|
+
const full = path22.join(dir, entry.name);
|
|
3472
|
+
const items = entry.isDirectory() ? listFilesRecursive(full) : [full];
|
|
3473
|
+
results.push(...items);
|
|
3272
3474
|
}
|
|
3273
|
-
function
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
const newDir = path22.join(path22.dirname(parent), newDirName);
|
|
3279
|
-
if (fs19.existsSync(newDir)) {
|
|
3280
|
-
result.warnings.push(
|
|
3281
|
-
`Skipping ${parent}: directory ${newDir} already exists`
|
|
3282
|
-
);
|
|
3283
|
-
continue;
|
|
3284
|
-
}
|
|
3285
|
-
result.directories.push(newDir);
|
|
3286
|
-
result.moves.push({
|
|
3287
|
-
from: parent,
|
|
3288
|
-
to: path22.join(newDir, `index${path22.extname(parent)}`),
|
|
3289
|
-
reason: `Main module of new ${newDirName}/ directory`
|
|
3290
|
-
});
|
|
3291
|
-
for (const child of children) {
|
|
3292
|
-
result.moves.push({
|
|
3293
|
-
from: child,
|
|
3294
|
-
to: path22.join(newDir, path22.basename(child)),
|
|
3295
|
-
reason: `Only imported by ${parentBase}`
|
|
3296
|
-
});
|
|
3297
|
-
}
|
|
3475
|
+
function listFilesRecursive(dir) {
|
|
3476
|
+
if (!fs19.existsSync(dir)) return [];
|
|
3477
|
+
const results = [];
|
|
3478
|
+
for (const entry of fs19.readdirSync(dir, { withFileTypes: true })) {
|
|
3479
|
+
collectEntry(results, dir, entry);
|
|
3298
3480
|
}
|
|
3299
|
-
return
|
|
3481
|
+
return results;
|
|
3300
3482
|
}
|
|
3301
|
-
function
|
|
3302
|
-
const
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
const childName = path22.basename(childDir);
|
|
3306
|
-
const newLocation = path22.join(parentDir, childName);
|
|
3307
|
-
if (fs19.existsSync(newLocation)) {
|
|
3308
|
-
result.warnings.push(
|
|
3309
|
-
`Skipping ${childDir}: ${newLocation} already exists`
|
|
3310
|
-
);
|
|
3311
|
-
continue;
|
|
3312
|
-
}
|
|
3313
|
-
result.directories.push(newLocation);
|
|
3314
|
-
const files = listFilesRecursive(childDir);
|
|
3315
|
-
for (const file of files) {
|
|
3316
|
-
const rel = path22.relative(childDir, file);
|
|
3317
|
-
result.moves.push({
|
|
3318
|
-
from: file,
|
|
3319
|
-
to: path22.join(newLocation, rel),
|
|
3320
|
-
reason: `Directory only imported from ${path22.basename(parentDir)}/`
|
|
3321
|
-
});
|
|
3322
|
-
}
|
|
3323
|
-
}
|
|
3483
|
+
function addDirectoryFileMoves(moves, childDir, newLocation, reason) {
|
|
3484
|
+
for (const file of listFilesRecursive(childDir)) {
|
|
3485
|
+
const rel = path22.relative(childDir, file);
|
|
3486
|
+
moves.push({ from: file, to: path22.join(newLocation, rel), reason });
|
|
3324
3487
|
}
|
|
3325
|
-
return result;
|
|
3326
3488
|
}
|
|
3327
|
-
function
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3489
|
+
function resolveChildDest(parentDir, childDir) {
|
|
3490
|
+
return path22.join(parentDir, path22.basename(childDir));
|
|
3491
|
+
}
|
|
3492
|
+
function childMoveReason(parentDir) {
|
|
3493
|
+
return `Directory only imported from ${path22.basename(parentDir)}/`;
|
|
3494
|
+
}
|
|
3495
|
+
function registerDirectoryMove(result, childDir, dest, parentDir) {
|
|
3496
|
+
result.directories.push(dest);
|
|
3497
|
+
const reason = childMoveReason(parentDir);
|
|
3498
|
+
addDirectoryFileMoves(result.moves, childDir, dest, reason);
|
|
3499
|
+
}
|
|
3500
|
+
function planChildDirectoryMove(result, parentDir, childDir) {
|
|
3501
|
+
const dest = resolveChildDest(parentDir, childDir);
|
|
3502
|
+
if (checkDirConflict(result, childDir, dest)) return;
|
|
3503
|
+
registerDirectoryMove(result, childDir, dest, parentDir);
|
|
3504
|
+
}
|
|
3505
|
+
function processDirectoryCluster(result, parentDir, childDirs) {
|
|
3506
|
+
for (const childDir of childDirs)
|
|
3507
|
+
planChildDirectoryMove(result, parentDir, childDir);
|
|
3508
|
+
}
|
|
3509
|
+
function planDirectoryMoves(clusters) {
|
|
3510
|
+
const result = emptyResult();
|
|
3511
|
+
for (const [parentDir, childDirs] of clusters)
|
|
3512
|
+
processDirectoryCluster(result, parentDir, childDirs);
|
|
3513
|
+
return result;
|
|
3514
|
+
}
|
|
3515
|
+
|
|
3516
|
+
// src/commands/refactor/restructure/planFileMoves/index.ts
|
|
3517
|
+
function emptyResult() {
|
|
3518
|
+
return { moves: [], directories: [], warnings: [] };
|
|
3519
|
+
}
|
|
3520
|
+
function childMoveData(child, newDir, parentBase) {
|
|
3521
|
+
const to = path23.join(newDir, path23.basename(child));
|
|
3522
|
+
return { from: child, to, reason: `Only imported by ${parentBase}` };
|
|
3523
|
+
}
|
|
3524
|
+
function addChildMoves(moves, children, newDir, parentBase) {
|
|
3525
|
+
for (const child of children)
|
|
3526
|
+
moves.push(childMoveData(child, newDir, parentBase));
|
|
3527
|
+
}
|
|
3528
|
+
function checkDirConflict(result, label, dir) {
|
|
3529
|
+
if (!fs20.existsSync(dir)) return false;
|
|
3530
|
+
result.warnings.push(`Skipping ${label}: directory ${dir} already exists`);
|
|
3531
|
+
return true;
|
|
3532
|
+
}
|
|
3533
|
+
function getBaseName(filePath) {
|
|
3534
|
+
return path23.basename(filePath, path23.extname(filePath));
|
|
3535
|
+
}
|
|
3536
|
+
function resolveClusterDir(parent) {
|
|
3537
|
+
return path23.join(path23.dirname(parent), getBaseName(parent));
|
|
3538
|
+
}
|
|
3539
|
+
function createParentMove(parent, newDir) {
|
|
3540
|
+
return {
|
|
3541
|
+
from: parent,
|
|
3542
|
+
to: path23.join(newDir, `index${path23.extname(parent)}`),
|
|
3543
|
+
reason: `Main module of new ${getBaseName(parent)}/ directory`
|
|
3544
|
+
};
|
|
3545
|
+
}
|
|
3546
|
+
function registerClusterMoves(result, parent, newDir, children) {
|
|
3547
|
+
result.directories.push(newDir);
|
|
3548
|
+
result.moves.push(createParentMove(parent, newDir));
|
|
3549
|
+
addChildMoves(result.moves, children, newDir, getBaseName(parent));
|
|
3550
|
+
}
|
|
3551
|
+
function planClusterMoves(result, parent, children) {
|
|
3552
|
+
const newDir = resolveClusterDir(parent);
|
|
3553
|
+
if (checkDirConflict(result, parent, newDir)) return;
|
|
3554
|
+
registerClusterMoves(result, parent, newDir, children);
|
|
3555
|
+
}
|
|
3556
|
+
function planFileMoves(clusters) {
|
|
3557
|
+
const result = emptyResult();
|
|
3558
|
+
for (const [parent, children] of clusters) {
|
|
3559
|
+
planClusterMoves(result, parent, children);
|
|
3338
3560
|
}
|
|
3339
|
-
return
|
|
3561
|
+
return result;
|
|
3340
3562
|
}
|
|
3341
3563
|
|
|
3342
3564
|
// src/commands/refactor/restructure/index.ts
|
|
3343
3565
|
function buildPlan(candidateFiles, tsConfigPath) {
|
|
3344
|
-
const candidates = new Set(candidateFiles.map((f) =>
|
|
3566
|
+
const candidates = new Set(candidateFiles.map((f) => path24.resolve(f)));
|
|
3345
3567
|
const graph = buildImportGraph(candidates, tsConfigPath);
|
|
3346
3568
|
const allProjectFiles = /* @__PURE__ */ new Set([
|
|
3347
3569
|
...graph.importedBy.keys(),
|
|
@@ -3361,22 +3583,22 @@ async function restructure(pattern2, options = {}) {
|
|
|
3361
3583
|
const targetPattern = pattern2 ?? "src";
|
|
3362
3584
|
const files = findSourceFiles2(targetPattern);
|
|
3363
3585
|
if (files.length === 0) {
|
|
3364
|
-
console.log(
|
|
3586
|
+
console.log(chalk40.yellow("No files found matching pattern"));
|
|
3365
3587
|
return;
|
|
3366
3588
|
}
|
|
3367
|
-
const tsConfigPath =
|
|
3589
|
+
const tsConfigPath = path24.resolve("tsconfig.json");
|
|
3368
3590
|
const plan = buildPlan(files, tsConfigPath);
|
|
3369
3591
|
if (plan.moves.length === 0) {
|
|
3370
|
-
console.log(
|
|
3592
|
+
console.log(chalk40.green("No restructuring needed"));
|
|
3371
3593
|
return;
|
|
3372
3594
|
}
|
|
3373
3595
|
displayPlan(plan);
|
|
3374
3596
|
if (options.apply) {
|
|
3375
|
-
console.log(
|
|
3597
|
+
console.log(chalk40.bold("\nApplying changes..."));
|
|
3376
3598
|
executePlan(plan);
|
|
3377
|
-
console.log(
|
|
3599
|
+
console.log(chalk40.green("\nRestructuring complete"));
|
|
3378
3600
|
} else {
|
|
3379
|
-
console.log(
|
|
3601
|
+
console.log(chalk40.dim("\nDry run. Use --apply to execute."));
|
|
3380
3602
|
}
|
|
3381
3603
|
}
|
|
3382
3604
|
|
|
@@ -3414,38 +3636,31 @@ function getDatePrefix(daysOffset = 0) {
|
|
|
3414
3636
|
function isValidDatePrefix(filename) {
|
|
3415
3637
|
return DATE_PREFIX_REGEX.test(filename);
|
|
3416
3638
|
}
|
|
3417
|
-
function
|
|
3418
|
-
if (!existsSync14(dir))
|
|
3419
|
-
return [];
|
|
3420
|
-
}
|
|
3639
|
+
function collectFiles(dir, extension) {
|
|
3640
|
+
if (!existsSync14(dir)) return [];
|
|
3421
3641
|
const results = [];
|
|
3422
|
-
const
|
|
3423
|
-
for (const entry of entries) {
|
|
3642
|
+
for (const entry of readdirSync2(dir)) {
|
|
3424
3643
|
const fullPath = join13(dir, entry);
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
results.push(
|
|
3428
|
-
...findFilesRecursive(fullPath, baseDir, extension, createEntry)
|
|
3429
|
-
);
|
|
3644
|
+
if (statSync(fullPath).isDirectory()) {
|
|
3645
|
+
results.push(...collectFiles(fullPath, extension));
|
|
3430
3646
|
} else if (entry.endsWith(extension)) {
|
|
3431
|
-
results.push(
|
|
3647
|
+
results.push(fullPath);
|
|
3432
3648
|
}
|
|
3433
3649
|
}
|
|
3434
3650
|
return results;
|
|
3435
3651
|
}
|
|
3652
|
+
function toFileInfo(baseDir, fullPath) {
|
|
3653
|
+
return {
|
|
3654
|
+
absolutePath: fullPath,
|
|
3655
|
+
relativePath: relative(baseDir, fullPath),
|
|
3656
|
+
filename: basename3(fullPath)
|
|
3657
|
+
};
|
|
3658
|
+
}
|
|
3436
3659
|
function findVttFilesRecursive(dir, baseDir = dir) {
|
|
3437
|
-
return
|
|
3438
|
-
absolutePath: abs,
|
|
3439
|
-
relativePath: rel,
|
|
3440
|
-
filename: name
|
|
3441
|
-
}));
|
|
3660
|
+
return collectFiles(dir, ".vtt").map((f) => toFileInfo(baseDir, f));
|
|
3442
3661
|
}
|
|
3443
3662
|
function findMdFilesRecursive(dir, baseDir = dir) {
|
|
3444
|
-
return
|
|
3445
|
-
absolutePath: abs,
|
|
3446
|
-
relativePath: rel,
|
|
3447
|
-
filename: name
|
|
3448
|
-
}));
|
|
3663
|
+
return collectFiles(dir, ".md").map((f) => toFileInfo(baseDir, f));
|
|
3449
3664
|
}
|
|
3450
3665
|
function getTranscriptBaseName(transcriptFile) {
|
|
3451
3666
|
return basename3(transcriptFile, ".md").replace(/ Transcription$/, "");
|
|
@@ -3465,44 +3680,52 @@ function askQuestion(rl, question) {
|
|
|
3465
3680
|
}
|
|
3466
3681
|
|
|
3467
3682
|
// src/commands/transcript/configure.ts
|
|
3683
|
+
function buildPrompt(label, current) {
|
|
3684
|
+
return current ? `${label} [${current}]: ` : `${label}: `;
|
|
3685
|
+
}
|
|
3686
|
+
function printExisting(existing) {
|
|
3687
|
+
console.log("Current configuration:");
|
|
3688
|
+
console.log(` VTT directory: ${existing.vttDir}`);
|
|
3689
|
+
console.log(` Transcripts directory: ${existing.transcriptsDir}`);
|
|
3690
|
+
console.log(` Summary directory: ${existing.summaryDir}`);
|
|
3691
|
+
console.log();
|
|
3692
|
+
}
|
|
3693
|
+
async function promptDirectories(rl, existing) {
|
|
3694
|
+
const vttDir = await askQuestion(
|
|
3695
|
+
rl,
|
|
3696
|
+
buildPrompt("VTT directory", existing?.vttDir)
|
|
3697
|
+
);
|
|
3698
|
+
const transcriptsDir = await askQuestion(
|
|
3699
|
+
rl,
|
|
3700
|
+
buildPrompt("Transcripts directory", existing?.transcriptsDir)
|
|
3701
|
+
);
|
|
3702
|
+
const summaryDir = await askQuestion(
|
|
3703
|
+
rl,
|
|
3704
|
+
buildPrompt("Summary directory", existing?.summaryDir)
|
|
3705
|
+
);
|
|
3706
|
+
return {
|
|
3707
|
+
vttDir: vttDir || existing?.vttDir || "",
|
|
3708
|
+
transcriptsDir: transcriptsDir || existing?.transcriptsDir || "",
|
|
3709
|
+
summaryDir: summaryDir || existing?.summaryDir || ""
|
|
3710
|
+
};
|
|
3711
|
+
}
|
|
3712
|
+
function validateDirectories(transcript) {
|
|
3713
|
+
if (!transcript.vttDir || !transcript.transcriptsDir || !transcript.summaryDir) {
|
|
3714
|
+
console.error("\nError: All directories must be specified.");
|
|
3715
|
+
process.exit(1);
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3468
3718
|
async function configure() {
|
|
3469
3719
|
const rl = createReadlineInterface();
|
|
3470
3720
|
const config = loadConfig();
|
|
3721
|
+
const existing = config.transcript;
|
|
3471
3722
|
console.log("Configure transcript directories\n");
|
|
3472
|
-
if (
|
|
3473
|
-
console.log("Current configuration:");
|
|
3474
|
-
console.log(` VTT directory: ${config.transcript.vttDir}`);
|
|
3475
|
-
console.log(` Transcripts directory: ${config.transcript.transcriptsDir}`);
|
|
3476
|
-
console.log(` Summary directory: ${config.transcript.summaryDir}`);
|
|
3477
|
-
console.log();
|
|
3478
|
-
}
|
|
3723
|
+
if (existing) printExisting(existing);
|
|
3479
3724
|
try {
|
|
3480
|
-
const
|
|
3481
|
-
rl,
|
|
3482
|
-
`VTT directory${config.transcript?.vttDir ? ` [${config.transcript.vttDir}]` : ""}: `
|
|
3483
|
-
);
|
|
3484
|
-
const transcriptsDir = await askQuestion(
|
|
3485
|
-
rl,
|
|
3486
|
-
`Transcripts directory${config.transcript?.transcriptsDir ? ` [${config.transcript.transcriptsDir}]` : ""}: `
|
|
3487
|
-
);
|
|
3488
|
-
const summaryDir = await askQuestion(
|
|
3489
|
-
rl,
|
|
3490
|
-
`Summary directory${config.transcript?.summaryDir ? ` [${config.transcript.summaryDir}]` : ""}: `
|
|
3491
|
-
);
|
|
3725
|
+
const transcript = await promptDirectories(rl, existing);
|
|
3492
3726
|
rl.close();
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
transcript: {
|
|
3496
|
-
vttDir: vttDir || config.transcript?.vttDir || "",
|
|
3497
|
-
transcriptsDir: transcriptsDir || config.transcript?.transcriptsDir || "",
|
|
3498
|
-
summaryDir: summaryDir || config.transcript?.summaryDir || ""
|
|
3499
|
-
}
|
|
3500
|
-
};
|
|
3501
|
-
if (!newConfig.transcript.vttDir || !newConfig.transcript.transcriptsDir || !newConfig.transcript.summaryDir) {
|
|
3502
|
-
console.error("\nError: All directories must be specified.");
|
|
3503
|
-
process.exit(1);
|
|
3504
|
-
}
|
|
3505
|
-
saveConfig(newConfig);
|
|
3727
|
+
validateDirectories(transcript);
|
|
3728
|
+
saveConfig({ ...config, transcript });
|
|
3506
3729
|
console.log("\nConfiguration saved.");
|
|
3507
3730
|
} catch (error) {
|
|
3508
3731
|
rl.close();
|
|
@@ -3511,8 +3734,7 @@ async function configure() {
|
|
|
3511
3734
|
}
|
|
3512
3735
|
|
|
3513
3736
|
// src/commands/transcript/format/index.ts
|
|
3514
|
-
import { existsSync as
|
|
3515
|
-
import { basename as basename4, dirname as dirname11, join as join16 } from "path";
|
|
3737
|
+
import { existsSync as existsSync16 } from "fs";
|
|
3516
3738
|
|
|
3517
3739
|
// src/commands/transcript/format/fixInvalidDatePrefixes/index.ts
|
|
3518
3740
|
import { dirname as dirname10, join as join15 } from "path";
|
|
@@ -3520,6 +3742,24 @@ import { dirname as dirname10, join as join15 } from "path";
|
|
|
3520
3742
|
// src/commands/transcript/format/fixInvalidDatePrefixes/promptForDateFix.ts
|
|
3521
3743
|
import { renameSync } from "fs";
|
|
3522
3744
|
import { join as join14 } from "path";
|
|
3745
|
+
async function resolveDate(rl, choice) {
|
|
3746
|
+
if (choice === "1") return getDatePrefix(0);
|
|
3747
|
+
if (choice === "2") return getDatePrefix(-1);
|
|
3748
|
+
if (choice === "3") {
|
|
3749
|
+
const customDate = await askQuestion(rl, "Enter date (YYYY-MM-DD): ");
|
|
3750
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(customDate)) return customDate;
|
|
3751
|
+
console.log("Invalid date format. Cancelling.");
|
|
3752
|
+
return null;
|
|
3753
|
+
}
|
|
3754
|
+
console.log("Cancelled.");
|
|
3755
|
+
return null;
|
|
3756
|
+
}
|
|
3757
|
+
function renameWithPrefix(vttDir, vttFile, prefix) {
|
|
3758
|
+
const newFilename = `${prefix}.${vttFile}`;
|
|
3759
|
+
renameSync(join14(vttDir, vttFile), join14(vttDir, newFilename));
|
|
3760
|
+
console.log(`Renamed to: ${newFilename}`);
|
|
3761
|
+
return newFilename;
|
|
3762
|
+
}
|
|
3523
3763
|
async function promptForDateFix(vttFile, vttDir) {
|
|
3524
3764
|
const rl = createReadlineInterface();
|
|
3525
3765
|
console.log(
|
|
@@ -3533,40 +3773,9 @@ Error: File "${vttFile}" does not start with YYYY-MM-DD format.`
|
|
|
3533
3773
|
console.log(" 4. Cancel");
|
|
3534
3774
|
try {
|
|
3535
3775
|
const choice = await askQuestion(rl, "\nSelect an option (1/2/3/4): ");
|
|
3536
|
-
|
|
3537
|
-
switch (choice) {
|
|
3538
|
-
case "1":
|
|
3539
|
-
newPrefix = getDatePrefix(0);
|
|
3540
|
-
break;
|
|
3541
|
-
case "2":
|
|
3542
|
-
newPrefix = getDatePrefix(-1);
|
|
3543
|
-
break;
|
|
3544
|
-
case "3": {
|
|
3545
|
-
const customDate = await askQuestion(rl, "Enter date (YYYY-MM-DD): ");
|
|
3546
|
-
if (/^\d{4}-\d{2}-\d{2}$/.test(customDate)) {
|
|
3547
|
-
newPrefix = customDate;
|
|
3548
|
-
} else {
|
|
3549
|
-
console.log("Invalid date format. Cancelling.");
|
|
3550
|
-
rl.close();
|
|
3551
|
-
return null;
|
|
3552
|
-
}
|
|
3553
|
-
break;
|
|
3554
|
-
}
|
|
3555
|
-
default:
|
|
3556
|
-
console.log("Cancelled.");
|
|
3557
|
-
rl.close();
|
|
3558
|
-
return null;
|
|
3559
|
-
}
|
|
3776
|
+
const prefix = await resolveDate(rl, choice);
|
|
3560
3777
|
rl.close();
|
|
3561
|
-
|
|
3562
|
-
const newFilename = `${newPrefix}.${vttFile}`;
|
|
3563
|
-
const oldPath = join14(vttDir, vttFile);
|
|
3564
|
-
const newPath = join14(vttDir, newFilename);
|
|
3565
|
-
renameSync(oldPath, newPath);
|
|
3566
|
-
console.log(`Renamed to: ${newFilename}`);
|
|
3567
|
-
return newFilename;
|
|
3568
|
-
}
|
|
3569
|
-
return null;
|
|
3778
|
+
return prefix ? renameWithPrefix(vttDir, vttFile, prefix) : null;
|
|
3570
3779
|
} catch (error) {
|
|
3571
3780
|
rl.close();
|
|
3572
3781
|
throw error;
|
|
@@ -3598,6 +3807,10 @@ async function fixInvalidDatePrefixes(vttFiles) {
|
|
|
3598
3807
|
return vttFiles.filter((f) => f.absolutePath !== "");
|
|
3599
3808
|
}
|
|
3600
3809
|
|
|
3810
|
+
// src/commands/transcript/format/processVttFile/index.ts
|
|
3811
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync4, readFileSync as readFileSync12, writeFileSync as writeFileSync12 } from "fs";
|
|
3812
|
+
import { basename as basename4, dirname as dirname11, join as join16 } from "path";
|
|
3813
|
+
|
|
3601
3814
|
// src/commands/transcript/cleanText.ts
|
|
3602
3815
|
function cleanText(text) {
|
|
3603
3816
|
const words = text.split(/\s+/);
|
|
@@ -3619,59 +3832,81 @@ function cleanText(text) {
|
|
|
3619
3832
|
return cleaned.join(" ").replace(/\s+/g, " ").trim();
|
|
3620
3833
|
}
|
|
3621
3834
|
|
|
3622
|
-
// src/commands/transcript/format/parseVtt/deduplicateCues.ts
|
|
3835
|
+
// src/commands/transcript/format/parseVtt/deduplicateCues/removeSubstringDuplicates.ts
|
|
3836
|
+
function normalizeText(text) {
|
|
3837
|
+
return text.toLowerCase().trim();
|
|
3838
|
+
}
|
|
3839
|
+
function checkSubstringRelation(textI, textJ) {
|
|
3840
|
+
if (textI.includes(textJ) && textI.length > textJ.length)
|
|
3841
|
+
return "i-contains-j";
|
|
3842
|
+
if (textJ.includes(textI) && textJ.length > textI.length)
|
|
3843
|
+
return "j-contains-i";
|
|
3844
|
+
return null;
|
|
3845
|
+
}
|
|
3846
|
+
function isNearby(a, b) {
|
|
3847
|
+
return b.startMs - a.endMs <= 1e4;
|
|
3848
|
+
}
|
|
3849
|
+
function comparePair(cueI, cueJ) {
|
|
3850
|
+
if (cueI.speaker !== cueJ.speaker) return null;
|
|
3851
|
+
const relation = checkSubstringRelation(
|
|
3852
|
+
normalizeText(cueI.text),
|
|
3853
|
+
normalizeText(cueJ.text)
|
|
3854
|
+
);
|
|
3855
|
+
if (relation === "i-contains-j") return "remove-j";
|
|
3856
|
+
if (relation === "j-contains-i") return "remove-i";
|
|
3857
|
+
return null;
|
|
3858
|
+
}
|
|
3859
|
+
function scanForward(cues, i, toRemove) {
|
|
3860
|
+
for (let j = i + 1; j < cues.length; j++) {
|
|
3861
|
+
if (toRemove.has(j)) continue;
|
|
3862
|
+
if (!isNearby(cues[i], cues[j])) break;
|
|
3863
|
+
const action = comparePair(cues[i], cues[j]);
|
|
3864
|
+
if (action === "remove-j") toRemove.add(j);
|
|
3865
|
+
else if (action === "remove-i") return true;
|
|
3866
|
+
}
|
|
3867
|
+
return false;
|
|
3868
|
+
}
|
|
3623
3869
|
function removeSubstringDuplicates(cues) {
|
|
3624
3870
|
const toRemove = /* @__PURE__ */ new Set();
|
|
3625
3871
|
for (let i = 0; i < cues.length; i++) {
|
|
3626
3872
|
if (toRemove.has(i)) continue;
|
|
3627
|
-
|
|
3628
|
-
if (toRemove.has(j)) continue;
|
|
3629
|
-
if (cues[j].startMs - cues[i].endMs > 1e4) break;
|
|
3630
|
-
if (cues[i].speaker !== cues[j].speaker) continue;
|
|
3631
|
-
const textI = cues[i].text.toLowerCase().trim();
|
|
3632
|
-
const textJ = cues[j].text.toLowerCase().trim();
|
|
3633
|
-
if (textI.includes(textJ) && textI.length > textJ.length) {
|
|
3634
|
-
toRemove.add(j);
|
|
3635
|
-
} else if (textJ.includes(textI) && textJ.length > textI.length) {
|
|
3636
|
-
toRemove.add(i);
|
|
3637
|
-
break;
|
|
3638
|
-
}
|
|
3639
|
-
}
|
|
3873
|
+
if (scanForward(cues, i, toRemove)) toRemove.add(i);
|
|
3640
3874
|
}
|
|
3641
3875
|
return cues.filter((_, i) => !toRemove.has(i));
|
|
3642
3876
|
}
|
|
3877
|
+
|
|
3878
|
+
// src/commands/transcript/format/parseVtt/deduplicateCues/index.ts
|
|
3643
3879
|
function findWordOverlap(currentWords, nextWords) {
|
|
3644
3880
|
for (let j = Math.min(5, currentWords.length); j >= 1; j--) {
|
|
3645
3881
|
const suffix = currentWords.slice(-j).join(" ");
|
|
3646
3882
|
const prefix = nextWords.slice(0, j).join(" ");
|
|
3647
|
-
if (suffix === prefix)
|
|
3648
|
-
return j;
|
|
3649
|
-
}
|
|
3883
|
+
if (suffix === prefix) return j;
|
|
3650
3884
|
}
|
|
3651
3885
|
return 0;
|
|
3652
3886
|
}
|
|
3887
|
+
function joinWithOverlap(currentText, nextText) {
|
|
3888
|
+
const currentWords = currentText.toLowerCase().split(/\s+/);
|
|
3889
|
+
const nextWords = nextText.toLowerCase().split(/\s+/);
|
|
3890
|
+
const overlapIndex = findWordOverlap(currentWords, nextWords);
|
|
3891
|
+
if (overlapIndex > 0) {
|
|
3892
|
+
return `${currentText} ${nextText.split(/\s+/).slice(overlapIndex).join(" ")}`;
|
|
3893
|
+
}
|
|
3894
|
+
return currentText.includes(nextText) ? currentText : `${currentText} ${nextText}`;
|
|
3895
|
+
}
|
|
3896
|
+
function canMergeCues(current, next2) {
|
|
3897
|
+
return current.speaker === next2.speaker && next2.startMs <= current.endMs + 500;
|
|
3898
|
+
}
|
|
3653
3899
|
function mergeOverlappingCues(cues) {
|
|
3654
3900
|
if (cues.length === 0) return [];
|
|
3655
3901
|
const result = [];
|
|
3656
3902
|
let current = { ...cues[0] };
|
|
3657
3903
|
for (let i = 1; i < cues.length; i++) {
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
if (sameSpeaker && overlaps) {
|
|
3662
|
-
const currentWords = current.text.toLowerCase().split(/\s+/);
|
|
3663
|
-
const nextWords = next2.text.toLowerCase().split(/\s+/);
|
|
3664
|
-
const overlapIndex = findWordOverlap(currentWords, nextWords);
|
|
3665
|
-
if (overlapIndex > 0) {
|
|
3666
|
-
const nextOriginalWords = next2.text.split(/\s+/);
|
|
3667
|
-
current.text = `${current.text} ${nextOriginalWords.slice(overlapIndex).join(" ")}`;
|
|
3668
|
-
} else if (!current.text.includes(next2.text)) {
|
|
3669
|
-
current.text = `${current.text} ${next2.text}`;
|
|
3670
|
-
}
|
|
3671
|
-
current.endMs = Math.max(current.endMs, next2.endMs);
|
|
3904
|
+
if (canMergeCues(current, cues[i])) {
|
|
3905
|
+
current.text = joinWithOverlap(current.text, cues[i].text);
|
|
3906
|
+
current.endMs = Math.max(current.endMs, cues[i].endMs);
|
|
3672
3907
|
} else {
|
|
3673
3908
|
result.push(current);
|
|
3674
|
-
current = { ...
|
|
3909
|
+
current = { ...cues[i] };
|
|
3675
3910
|
}
|
|
3676
3911
|
}
|
|
3677
3912
|
result.push(current);
|
|
@@ -3689,59 +3924,76 @@ function deduplicateCues(cues) {
|
|
|
3689
3924
|
}
|
|
3690
3925
|
|
|
3691
3926
|
// src/commands/transcript/format/parseVtt/index.ts
|
|
3927
|
+
function parseHMS(h, m, s) {
|
|
3928
|
+
return Number.parseInt(h, 10) * 3600 + Number.parseInt(m, 10) * 60 + Number.parseFloat(s);
|
|
3929
|
+
}
|
|
3930
|
+
function parseMS(m, s) {
|
|
3931
|
+
return Number.parseInt(m, 10) * 60 + Number.parseFloat(s);
|
|
3932
|
+
}
|
|
3933
|
+
function toSeconds(parts) {
|
|
3934
|
+
if (parts.length === 3) return parseHMS(parts[0], parts[1], parts[2]);
|
|
3935
|
+
if (parts.length === 2) return parseMS(parts[0], parts[1]);
|
|
3936
|
+
return 0;
|
|
3937
|
+
}
|
|
3692
3938
|
function parseTimestamp(ts8) {
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
if (
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
}
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3939
|
+
return Math.round(toSeconds(ts8.split(":")) * 1e3);
|
|
3940
|
+
}
|
|
3941
|
+
function extractSpeaker(fullText) {
|
|
3942
|
+
const match = fullText.match(/^<v\s+([^>]+)>/);
|
|
3943
|
+
if (!match) return { speaker: null, text: fullText };
|
|
3944
|
+
return {
|
|
3945
|
+
speaker: match[1],
|
|
3946
|
+
text: fullText.replace(/<v\s+[^>]+>/, "").trim()
|
|
3947
|
+
};
|
|
3948
|
+
}
|
|
3949
|
+
function isTextLine(line) {
|
|
3950
|
+
return !!line.trim() && !line.includes("-->");
|
|
3951
|
+
}
|
|
3952
|
+
function scanTextLines(lines, start) {
|
|
3953
|
+
let i = start;
|
|
3954
|
+
while (i < lines.length && isTextLine(lines[i])) i++;
|
|
3955
|
+
return { texts: lines.slice(start, i).map((l) => l.trim()), end: i };
|
|
3956
|
+
}
|
|
3957
|
+
function collectTextLines(lines, startIndex) {
|
|
3958
|
+
const { texts, end } = scanTextLines(lines, startIndex);
|
|
3959
|
+
return { text: texts.join(" "), nextIndex: end };
|
|
3960
|
+
}
|
|
3961
|
+
function parseTimestampLine(line) {
|
|
3962
|
+
const [startStr, endStr] = line.trim().split("-->").map((s) => s.trim());
|
|
3963
|
+
return { startMs: parseTimestamp(startStr), endMs: parseTimestamp(endStr) };
|
|
3964
|
+
}
|
|
3965
|
+
function buildCue(startMs, endMs, fullText) {
|
|
3966
|
+
const { speaker, text } = extractSpeaker(fullText);
|
|
3967
|
+
return text ? { startMs, endMs, speaker, text } : null;
|
|
3968
|
+
}
|
|
3969
|
+
function parseCueLine(lines, i) {
|
|
3970
|
+
const { startMs, endMs } = parseTimestampLine(lines[i]);
|
|
3971
|
+
const { text, nextIndex } = collectTextLines(lines, i + 1);
|
|
3972
|
+
return { cue: buildCue(startMs, endMs, text), nextIndex };
|
|
3973
|
+
}
|
|
3974
|
+
function isCueSeparator(line) {
|
|
3975
|
+
return line.trim().includes("-->");
|
|
3976
|
+
}
|
|
3977
|
+
function skipHeader(lines) {
|
|
3978
|
+
let i = 0;
|
|
3979
|
+
while (i < lines.length && !isCueSeparator(lines[i])) i++;
|
|
3980
|
+
return i;
|
|
3981
|
+
}
|
|
3982
|
+
function processLine(cues, lines, i) {
|
|
3983
|
+
if (!isCueSeparator(lines[i])) return i + 1;
|
|
3984
|
+
const { cue, nextIndex } = parseCueLine(lines, i);
|
|
3985
|
+
if (cue) cues.push(cue);
|
|
3986
|
+
return nextIndex;
|
|
3706
3987
|
}
|
|
3707
3988
|
function parseVtt(content) {
|
|
3708
3989
|
const cues = [];
|
|
3709
3990
|
const lines = content.split(/\r?\n/);
|
|
3710
|
-
let i =
|
|
3711
|
-
while (i < lines.length
|
|
3712
|
-
i++;
|
|
3713
|
-
}
|
|
3714
|
-
while (i < lines.length) {
|
|
3715
|
-
const line = lines[i].trim();
|
|
3716
|
-
if (line.includes("-->")) {
|
|
3717
|
-
const [startStr, endStr] = line.split("-->").map((s) => s.trim());
|
|
3718
|
-
const startMs = parseTimestamp(startStr);
|
|
3719
|
-
const endMs = parseTimestamp(endStr);
|
|
3720
|
-
const textLines = [];
|
|
3721
|
-
i++;
|
|
3722
|
-
while (i < lines.length && lines[i].trim() && !lines[i].includes("-->")) {
|
|
3723
|
-
textLines.push(lines[i].trim());
|
|
3724
|
-
i++;
|
|
3725
|
-
}
|
|
3726
|
-
const fullText = textLines.join(" ");
|
|
3727
|
-
const speakerMatch = fullText.match(/^<v\s+([^>]+)>/);
|
|
3728
|
-
let speaker = null;
|
|
3729
|
-
let text = fullText;
|
|
3730
|
-
if (speakerMatch) {
|
|
3731
|
-
speaker = speakerMatch[1];
|
|
3732
|
-
text = fullText.replace(/<v\s+[^>]+>/, "").trim();
|
|
3733
|
-
}
|
|
3734
|
-
if (text) {
|
|
3735
|
-
cues.push({ startMs, endMs, speaker, text });
|
|
3736
|
-
}
|
|
3737
|
-
} else {
|
|
3738
|
-
i++;
|
|
3739
|
-
}
|
|
3740
|
-
}
|
|
3991
|
+
let i = skipHeader(lines);
|
|
3992
|
+
while (i < lines.length) i = processLine(cues, lines, i);
|
|
3741
3993
|
return cues;
|
|
3742
3994
|
}
|
|
3743
3995
|
|
|
3744
|
-
// src/commands/transcript/format/formatChatLog.ts
|
|
3996
|
+
// src/commands/transcript/format/processVttFile/formatChatLog.ts
|
|
3745
3997
|
function cuesToChatMessages(cues) {
|
|
3746
3998
|
const messages = [];
|
|
3747
3999
|
for (const cue of cues) {
|
|
@@ -3762,74 +4014,125 @@ function formatChatLog(messages) {
|
|
|
3762
4014
|
return messages.map((msg) => `${msg.speaker}: ${msg.text}`).join("\n\n");
|
|
3763
4015
|
}
|
|
3764
4016
|
|
|
3765
|
-
// src/commands/transcript/format/index.ts
|
|
3766
|
-
function
|
|
3767
|
-
|
|
3768
|
-
|
|
4017
|
+
// src/commands/transcript/format/processVttFile/index.ts
|
|
4018
|
+
function toMdFilename(vttFilename) {
|
|
4019
|
+
return `${basename4(vttFilename, ".vtt").replace(/\s*Transcription\s*/g, " ").trim()}.md`;
|
|
4020
|
+
}
|
|
4021
|
+
function resolveOutputDir(relativeDir, transcriptsDir) {
|
|
4022
|
+
return relativeDir === "." ? transcriptsDir : join16(transcriptsDir, relativeDir);
|
|
4023
|
+
}
|
|
4024
|
+
function buildOutputPaths(vttFile, transcriptsDir) {
|
|
4025
|
+
const mdFile = toMdFilename(vttFile.filename);
|
|
4026
|
+
const relativeDir = dirname11(vttFile.relativePath);
|
|
4027
|
+
const outputDir = resolveOutputDir(relativeDir, transcriptsDir);
|
|
4028
|
+
const outputPath = join16(outputDir, mdFile);
|
|
4029
|
+
return { outputDir, outputPath, mdFile, relativeDir };
|
|
4030
|
+
}
|
|
4031
|
+
function logSkipped(relativeDir, mdFile) {
|
|
4032
|
+
console.log(`Skipping (already exists): ${join16(relativeDir, mdFile)}`);
|
|
4033
|
+
return "skipped";
|
|
4034
|
+
}
|
|
4035
|
+
function ensureDirectory(dir, label) {
|
|
4036
|
+
if (!existsSync15(dir)) {
|
|
4037
|
+
mkdirSync4(dir, { recursive: true });
|
|
4038
|
+
console.log(`Created ${label}: ${dir}`);
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
function processCues(content) {
|
|
3769
4042
|
const cues = parseVtt(content);
|
|
3770
4043
|
console.log(`Parsed ${cues.length} cues`);
|
|
3771
4044
|
const dedupedCues = deduplicateCues(cues);
|
|
3772
4045
|
console.log(`After deduplication: ${dedupedCues.length} cues`);
|
|
4046
|
+
return { cues, dedupedCues };
|
|
4047
|
+
}
|
|
4048
|
+
function convertToMessages(dedupedCues) {
|
|
3773
4049
|
const chatMessages = cuesToChatMessages(dedupedCues);
|
|
3774
4050
|
console.log(`Consolidated to ${chatMessages.length} chat messages`);
|
|
3775
|
-
|
|
3776
|
-
|
|
4051
|
+
return chatMessages;
|
|
4052
|
+
}
|
|
4053
|
+
function logReduction(cueCount, messageCount) {
|
|
4054
|
+
console.log(`Reduction: ${cueCount} cues -> ${messageCount} messages
|
|
4055
|
+
`);
|
|
4056
|
+
}
|
|
4057
|
+
function readAndParseCues(inputPath) {
|
|
4058
|
+
console.log(`Reading: ${inputPath}`);
|
|
4059
|
+
return processCues(readFileSync12(inputPath, "utf-8"));
|
|
4060
|
+
}
|
|
4061
|
+
function writeFormatted(outputPath, content) {
|
|
4062
|
+
writeFileSync12(outputPath, content, "utf-8");
|
|
3777
4063
|
console.log(`Written: ${outputPath}`);
|
|
4064
|
+
}
|
|
4065
|
+
function convertVttToMarkdown(inputPath, outputPath) {
|
|
4066
|
+
const { cues, dedupedCues } = readAndParseCues(inputPath);
|
|
4067
|
+
const chatMessages = convertToMessages(dedupedCues);
|
|
4068
|
+
writeFormatted(outputPath, formatChatLog(chatMessages));
|
|
4069
|
+
logReduction(cues.length, chatMessages.length);
|
|
4070
|
+
}
|
|
4071
|
+
function tryProcessVtt(vttFile, paths) {
|
|
4072
|
+
if (existsSync15(paths.outputPath))
|
|
4073
|
+
return logSkipped(paths.relativeDir, paths.mdFile);
|
|
4074
|
+
convertVttToMarkdown(vttFile.absolutePath, paths.outputPath);
|
|
4075
|
+
return "processed";
|
|
4076
|
+
}
|
|
4077
|
+
function processVttFile(vttFile, transcriptsDir) {
|
|
4078
|
+
const paths = buildOutputPaths(vttFile, transcriptsDir);
|
|
4079
|
+
ensureDirectory(paths.outputDir, "output directory");
|
|
4080
|
+
return tryProcessVtt(vttFile, paths);
|
|
4081
|
+
}
|
|
4082
|
+
|
|
4083
|
+
// src/commands/transcript/format/index.ts
|
|
4084
|
+
function logSummary(counts) {
|
|
3778
4085
|
console.log(
|
|
3779
|
-
`
|
|
3780
|
-
`
|
|
4086
|
+
`
|
|
4087
|
+
Summary: ${counts.processed} processed, ${counts.skipped} skipped`
|
|
3781
4088
|
);
|
|
3782
4089
|
}
|
|
3783
|
-
|
|
3784
|
-
const {
|
|
3785
|
-
|
|
4090
|
+
function processAllFiles(vttFiles, transcriptsDir) {
|
|
4091
|
+
const counts = { processed: 0, skipped: 0 };
|
|
4092
|
+
for (const vttFile of vttFiles) {
|
|
4093
|
+
counts[processVttFile(vttFile, transcriptsDir)]++;
|
|
4094
|
+
}
|
|
4095
|
+
logSummary(counts);
|
|
4096
|
+
}
|
|
4097
|
+
function requireVttDir(vttDir) {
|
|
4098
|
+
if (!existsSync16(vttDir)) {
|
|
3786
4099
|
console.error(`VTT directory not found: ${vttDir}`);
|
|
3787
4100
|
process.exit(1);
|
|
3788
4101
|
}
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
}
|
|
3793
|
-
let vttFiles = findVttFilesRecursive(vttDir);
|
|
3794
|
-
if (vttFiles.length === 0) {
|
|
3795
|
-
console.log("No VTT files found in vtt directory.");
|
|
3796
|
-
return;
|
|
3797
|
-
}
|
|
3798
|
-
console.log(`Found ${vttFiles.length} VTT file(s) in ${vttDir}
|
|
4102
|
+
}
|
|
4103
|
+
function logFoundFiles(files, vttDir) {
|
|
4104
|
+
console.log(`Found ${files.length} VTT file(s) in ${vttDir}
|
|
3799
4105
|
`);
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
}
|
|
3822
|
-
console.log(`
|
|
3823
|
-
Summary: ${processed} processed, ${skipped} skipped`);
|
|
4106
|
+
}
|
|
4107
|
+
function logNoVttFiles() {
|
|
4108
|
+
console.log("No VTT files found in vtt directory.");
|
|
4109
|
+
return null;
|
|
4110
|
+
}
|
|
4111
|
+
function loadVttFiles(vttDir) {
|
|
4112
|
+
const files = findVttFilesRecursive(vttDir);
|
|
4113
|
+
if (files.length === 0) return logNoVttFiles();
|
|
4114
|
+
logFoundFiles(files, vttDir);
|
|
4115
|
+
return files;
|
|
4116
|
+
}
|
|
4117
|
+
async function formatLoadedFiles(vttFiles, transcriptsDir) {
|
|
4118
|
+
const fixed2 = await fixInvalidDatePrefixes(vttFiles);
|
|
4119
|
+
processAllFiles(fixed2, transcriptsDir);
|
|
4120
|
+
}
|
|
4121
|
+
async function format() {
|
|
4122
|
+
const { vttDir, transcriptsDir } = getTranscriptConfig();
|
|
4123
|
+
requireVttDir(vttDir);
|
|
4124
|
+
ensureDirectory(transcriptsDir, "output directory");
|
|
4125
|
+
const vttFiles = loadVttFiles(vttDir);
|
|
4126
|
+
if (vttFiles) await formatLoadedFiles(vttFiles, transcriptsDir);
|
|
3824
4127
|
}
|
|
3825
4128
|
|
|
3826
4129
|
// src/commands/transcript/summarise/index.ts
|
|
3827
|
-
import { existsSync as
|
|
4130
|
+
import { existsSync as existsSync18 } from "fs";
|
|
3828
4131
|
import { basename as basename5, dirname as dirname13, join as join18, relative as relative2 } from "path";
|
|
3829
4132
|
|
|
3830
4133
|
// src/commands/transcript/summarise/processStagedFile/index.ts
|
|
3831
4134
|
import {
|
|
3832
|
-
existsSync as
|
|
4135
|
+
existsSync as existsSync17,
|
|
3833
4136
|
mkdirSync as mkdirSync5,
|
|
3834
4137
|
readFileSync as readFileSync13,
|
|
3835
4138
|
renameSync as renameSync2,
|
|
@@ -3838,14 +4141,14 @@ import {
|
|
|
3838
4141
|
import { dirname as dirname12, join as join17 } from "path";
|
|
3839
4142
|
|
|
3840
4143
|
// src/commands/transcript/summarise/processStagedFile/validateStagedContent.ts
|
|
3841
|
-
import
|
|
4144
|
+
import chalk41 from "chalk";
|
|
3842
4145
|
var FULL_TRANSCRIPT_REGEX = /^\[Full Transcript\]\(([^)]+)\)/;
|
|
3843
4146
|
function validateStagedContent(filename, content) {
|
|
3844
4147
|
const firstLine = content.split("\n")[0];
|
|
3845
4148
|
const match = firstLine.match(FULL_TRANSCRIPT_REGEX);
|
|
3846
4149
|
if (!match) {
|
|
3847
4150
|
console.error(
|
|
3848
|
-
|
|
4151
|
+
chalk41.red(
|
|
3849
4152
|
`Staged file ${filename} missing [Full Transcript](<path>) link on first line.`
|
|
3850
4153
|
)
|
|
3851
4154
|
);
|
|
@@ -3854,7 +4157,7 @@ function validateStagedContent(filename, content) {
|
|
|
3854
4157
|
const contentAfterLink = content.slice(firstLine.length).trim();
|
|
3855
4158
|
if (!contentAfterLink) {
|
|
3856
4159
|
console.error(
|
|
3857
|
-
|
|
4160
|
+
chalk41.red(
|
|
3858
4161
|
`Staged file ${filename} has no summary content after the transcript link.`
|
|
3859
4162
|
)
|
|
3860
4163
|
);
|
|
@@ -3866,7 +4169,7 @@ function validateStagedContent(filename, content) {
|
|
|
3866
4169
|
// src/commands/transcript/summarise/processStagedFile/index.ts
|
|
3867
4170
|
var STAGING_DIR = join17(process.cwd(), ".assist", "transcript");
|
|
3868
4171
|
function processStagedFile() {
|
|
3869
|
-
if (!
|
|
4172
|
+
if (!existsSync17(STAGING_DIR)) {
|
|
3870
4173
|
return false;
|
|
3871
4174
|
}
|
|
3872
4175
|
const stagedFiles = findMdFilesRecursive(STAGING_DIR);
|
|
@@ -3890,7 +4193,7 @@ function processStagedFile() {
|
|
|
3890
4193
|
}
|
|
3891
4194
|
const destPath = join17(summaryDir, matchingTranscript.relativePath);
|
|
3892
4195
|
const destDir = dirname12(destPath);
|
|
3893
|
-
if (!
|
|
4196
|
+
if (!existsSync17(destDir)) {
|
|
3894
4197
|
mkdirSync5(destDir, { recursive: true });
|
|
3895
4198
|
}
|
|
3896
4199
|
renameSync2(stagedFile.absolutePath, destPath);
|
|
@@ -3902,10 +4205,22 @@ function processStagedFile() {
|
|
|
3902
4205
|
}
|
|
3903
4206
|
|
|
3904
4207
|
// src/commands/transcript/summarise/index.ts
|
|
4208
|
+
function buildRelativeKey(relativePath, baseName) {
|
|
4209
|
+
const relDir = dirname13(relativePath);
|
|
4210
|
+
return relDir === "." ? baseName : join18(relDir, baseName);
|
|
4211
|
+
}
|
|
4212
|
+
function buildSummaryIndex(summaryDir) {
|
|
4213
|
+
const summaryFiles = findMdFilesRecursive(summaryDir);
|
|
4214
|
+
return new Set(
|
|
4215
|
+
summaryFiles.map(
|
|
4216
|
+
(f) => buildRelativeKey(f.relativePath, basename5(f.filename, ".md"))
|
|
4217
|
+
)
|
|
4218
|
+
);
|
|
4219
|
+
}
|
|
3905
4220
|
function summarise() {
|
|
3906
4221
|
processStagedFile();
|
|
3907
4222
|
const { transcriptsDir, summaryDir } = getTranscriptConfig();
|
|
3908
|
-
if (!
|
|
4223
|
+
if (!existsSync18(transcriptsDir)) {
|
|
3909
4224
|
console.log("No transcripts directory found.");
|
|
3910
4225
|
return;
|
|
3911
4226
|
}
|
|
@@ -3914,23 +4229,12 @@ function summarise() {
|
|
|
3914
4229
|
console.log("No transcript files found.");
|
|
3915
4230
|
return;
|
|
3916
4231
|
}
|
|
3917
|
-
const
|
|
3918
|
-
const
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
return relDir === "." ? baseName : join18(relDir, baseName);
|
|
3923
|
-
})
|
|
4232
|
+
const summaryIndex = buildSummaryIndex(summaryDir);
|
|
4233
|
+
const missing = transcriptFiles.filter(
|
|
4234
|
+
(t) => !summaryIndex.has(
|
|
4235
|
+
buildRelativeKey(t.relativePath, getTranscriptBaseName(t.filename))
|
|
4236
|
+
)
|
|
3924
4237
|
);
|
|
3925
|
-
const missing = [];
|
|
3926
|
-
for (const transcript of transcriptFiles) {
|
|
3927
|
-
const transcriptBaseName = getTranscriptBaseName(transcript.filename);
|
|
3928
|
-
const relDir = dirname13(transcript.relativePath);
|
|
3929
|
-
const fullKey = relDir === "." ? transcriptBaseName : join18(relDir, transcriptBaseName);
|
|
3930
|
-
if (!summaryRelativePaths.has(fullKey)) {
|
|
3931
|
-
missing.push(transcript);
|
|
3932
|
-
}
|
|
3933
|
-
}
|
|
3934
4238
|
if (missing.length === 0) {
|
|
3935
4239
|
console.log("All transcripts have summaries.");
|
|
3936
4240
|
return;
|
|
@@ -3997,9 +4301,9 @@ Total: ${lines.length} hardcoded color(s)`);
|
|
|
3997
4301
|
|
|
3998
4302
|
// src/commands/verify/run/index.ts
|
|
3999
4303
|
import { spawn as spawn3 } from "child_process";
|
|
4000
|
-
import * as
|
|
4304
|
+
import * as path25 from "path";
|
|
4001
4305
|
|
|
4002
|
-
// src/commands/verify/run/printTaskStatuses.ts
|
|
4306
|
+
// src/commands/verify/run/createTimerCallback/printTaskStatuses.ts
|
|
4003
4307
|
function formatDuration(ms) {
|
|
4004
4308
|
if (ms < 1e3) {
|
|
4005
4309
|
return `${ms}ms`;
|
|
@@ -4022,55 +4326,96 @@ function printTaskStatuses(tasks) {
|
|
|
4022
4326
|
console.log("-------------------\n");
|
|
4023
4327
|
}
|
|
4024
4328
|
|
|
4025
|
-
// src/commands/verify/run/index.ts
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
console.
|
|
4031
|
-
return;
|
|
4032
|
-
}
|
|
4033
|
-
const { packageJsonPath, verifyScripts } = result;
|
|
4034
|
-
const packageDir = path24.dirname(packageJsonPath);
|
|
4035
|
-
console.log(`Running ${verifyScripts.length} verify script(s) in parallel:`);
|
|
4036
|
-
for (const script of verifyScripts) {
|
|
4037
|
-
console.log(` - ${script}`);
|
|
4329
|
+
// src/commands/verify/run/createTimerCallback/index.ts
|
|
4330
|
+
function logFailedScripts(failed) {
|
|
4331
|
+
console.error(`
|
|
4332
|
+
${failed.length} script(s) failed:`);
|
|
4333
|
+
for (const f of failed) {
|
|
4334
|
+
console.error(` - ${f.script} (exit code ${f.code})`);
|
|
4038
4335
|
}
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4336
|
+
}
|
|
4337
|
+
function createTimerCallback(taskStatuses, index) {
|
|
4338
|
+
return (exitCode) => {
|
|
4339
|
+
taskStatuses[index].endTime = Date.now();
|
|
4340
|
+
taskStatuses[index].code = exitCode;
|
|
4341
|
+
printTaskStatuses(taskStatuses);
|
|
4342
|
+
};
|
|
4343
|
+
}
|
|
4344
|
+
function initTaskStatuses(scripts) {
|
|
4345
|
+
return scripts.map((script) => ({ script, startTime: Date.now() }));
|
|
4346
|
+
}
|
|
4347
|
+
|
|
4348
|
+
// src/commands/verify/run/index.ts
|
|
4349
|
+
function spawnScript(script, cwd) {
|
|
4350
|
+
return spawn3("npm", ["run", script], { stdio: "inherit", shell: true, cwd });
|
|
4351
|
+
}
|
|
4352
|
+
function onScriptClose(script, onComplete, resolve) {
|
|
4353
|
+
return (code) => {
|
|
4354
|
+
const exitCode = code ?? 1;
|
|
4355
|
+
onComplete?.(exitCode);
|
|
4356
|
+
resolve({ script, code: exitCode });
|
|
4357
|
+
};
|
|
4358
|
+
}
|
|
4359
|
+
function runScript2(script, cwd, onComplete) {
|
|
4360
|
+
return new Promise((resolve) => {
|
|
4361
|
+
spawnScript(script, cwd).on(
|
|
4362
|
+
"close",
|
|
4363
|
+
onScriptClose(script, onComplete, resolve)
|
|
4364
|
+
);
|
|
4365
|
+
});
|
|
4366
|
+
}
|
|
4367
|
+
function runAllScripts(verifyScripts, packageDir, timer) {
|
|
4368
|
+
const taskStatuses = initTaskStatuses(verifyScripts);
|
|
4369
|
+
return Promise.all(
|
|
4044
4370
|
verifyScripts.map(
|
|
4045
|
-
(script, index) =>
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
});
|
|
4051
|
-
child.on("close", (code) => {
|
|
4052
|
-
const exitCode = code ?? 1;
|
|
4053
|
-
if (timer) {
|
|
4054
|
-
taskStatuses[index].endTime = Date.now();
|
|
4055
|
-
taskStatuses[index].code = exitCode;
|
|
4056
|
-
printTaskStatuses(taskStatuses);
|
|
4057
|
-
}
|
|
4058
|
-
resolve({ script, code: exitCode });
|
|
4059
|
-
});
|
|
4060
|
-
})
|
|
4371
|
+
(script, index) => runScript2(
|
|
4372
|
+
script,
|
|
4373
|
+
packageDir,
|
|
4374
|
+
timer ? createTimerCallback(taskStatuses, index) : void 0
|
|
4375
|
+
)
|
|
4061
4376
|
)
|
|
4062
4377
|
);
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
console.error(` - ${f.script} (exit code ${f.code})`);
|
|
4069
|
-
}
|
|
4070
|
-
process.exit(1);
|
|
4378
|
+
}
|
|
4379
|
+
function printScriptList(scripts) {
|
|
4380
|
+
console.log(`Running ${scripts.length} verify script(s) in parallel:`);
|
|
4381
|
+
for (const script of scripts) {
|
|
4382
|
+
console.log(` - ${script}`);
|
|
4071
4383
|
}
|
|
4384
|
+
}
|
|
4385
|
+
function exitIfFailed(failed) {
|
|
4386
|
+
if (failed.length === 0) return;
|
|
4387
|
+
logFailedScripts(failed);
|
|
4388
|
+
process.exit(1);
|
|
4389
|
+
}
|
|
4390
|
+
function handleResults(results, totalCount) {
|
|
4391
|
+
exitIfFailed(results.filter((r) => r.code !== 0));
|
|
4072
4392
|
console.log(`
|
|
4073
|
-
All ${
|
|
4393
|
+
All ${totalCount} verify script(s) passed`);
|
|
4394
|
+
}
|
|
4395
|
+
function resolveVerifyScripts() {
|
|
4396
|
+
const result = findPackageJsonWithVerifyScripts(process.cwd());
|
|
4397
|
+
if (!result) {
|
|
4398
|
+
console.log("No package.json with verify:* scripts found");
|
|
4399
|
+
return null;
|
|
4400
|
+
}
|
|
4401
|
+
return result;
|
|
4402
|
+
}
|
|
4403
|
+
function getPackageDir(found) {
|
|
4404
|
+
return path25.dirname(found.packageJsonPath);
|
|
4405
|
+
}
|
|
4406
|
+
async function executeVerifyScripts(found, timer) {
|
|
4407
|
+
printScriptList(found.verifyScripts);
|
|
4408
|
+
const results = await runAllScripts(
|
|
4409
|
+
found.verifyScripts,
|
|
4410
|
+
getPackageDir(found),
|
|
4411
|
+
timer
|
|
4412
|
+
);
|
|
4413
|
+
handleResults(results, found.verifyScripts.length);
|
|
4414
|
+
}
|
|
4415
|
+
async function run(options = {}) {
|
|
4416
|
+
const found = resolveVerifyScripts();
|
|
4417
|
+
if (!found) return;
|
|
4418
|
+
await executeVerifyScripts(found, options.timer ?? false);
|
|
4074
4419
|
}
|
|
4075
4420
|
|
|
4076
4421
|
// src/commands/registerVerify.ts
|
|
@@ -4080,70 +4425,114 @@ function registerVerify(program2) {
|
|
|
4080
4425
|
verifyCommand.command("hardcoded-colors").description("Check for hardcoded hex colors in src/").action(hardcodedColors);
|
|
4081
4426
|
}
|
|
4082
4427
|
|
|
4083
|
-
// src/commands/run.ts
|
|
4428
|
+
// src/commands/run/index.ts
|
|
4084
4429
|
import { spawn as spawn4 } from "child_process";
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4430
|
+
|
|
4431
|
+
// src/commands/run/add.ts
|
|
4432
|
+
function findAddIndex() {
|
|
4433
|
+
const addIndex = process.argv.indexOf("add");
|
|
4434
|
+
if (addIndex === -1 || addIndex + 2 >= process.argv.length) return -1;
|
|
4435
|
+
return addIndex;
|
|
4436
|
+
}
|
|
4437
|
+
function extractAddArgs(addIndex) {
|
|
4438
|
+
return {
|
|
4439
|
+
name: process.argv[addIndex + 1],
|
|
4440
|
+
command: process.argv[addIndex + 2],
|
|
4441
|
+
args: process.argv.slice(addIndex + 3)
|
|
4442
|
+
};
|
|
4443
|
+
}
|
|
4444
|
+
function parseAddArguments() {
|
|
4445
|
+
const addIndex = findAddIndex();
|
|
4446
|
+
return addIndex === -1 ? null : extractAddArgs(addIndex);
|
|
4447
|
+
}
|
|
4448
|
+
function buildRunEntry(name, command, args) {
|
|
4449
|
+
const entry = {
|
|
4450
|
+
name,
|
|
4451
|
+
command
|
|
4452
|
+
};
|
|
4453
|
+
if (args.length > 0) entry.args = args;
|
|
4454
|
+
return entry;
|
|
4455
|
+
}
|
|
4456
|
+
function ensureNoDuplicate(configs, name) {
|
|
4457
|
+
if (configs.find((r) => r.name === name)) {
|
|
4458
|
+
console.error(`Run configuration with name "${name}" already exists`);
|
|
4098
4459
|
process.exit(1);
|
|
4099
4460
|
}
|
|
4100
|
-
const command = runConfig.command.includes(" ") ? `"${runConfig.command}"` : runConfig.command;
|
|
4101
|
-
const allArgs = [...runConfig.args ?? [], ...args];
|
|
4102
|
-
const quotedArgs = allArgs.map(
|
|
4103
|
-
(arg) => arg.includes(" ") ? `"${arg}"` : arg
|
|
4104
|
-
);
|
|
4105
|
-
const fullCommand = [command, ...quotedArgs].join(" ");
|
|
4106
|
-
const child = spawn4(fullCommand, [], {
|
|
4107
|
-
stdio: "inherit",
|
|
4108
|
-
shell: true
|
|
4109
|
-
});
|
|
4110
|
-
child.on("close", (code) => {
|
|
4111
|
-
process.exit(code ?? 0);
|
|
4112
|
-
});
|
|
4113
|
-
child.on("error", (err) => {
|
|
4114
|
-
console.error(`Failed to execute command: ${err.message}`);
|
|
4115
|
-
process.exit(1);
|
|
4116
|
-
});
|
|
4117
4461
|
}
|
|
4118
|
-
function
|
|
4119
|
-
|
|
4120
|
-
|
|
4462
|
+
function formatDisplay(command, args) {
|
|
4463
|
+
return args.length > 0 ? `${command} ${args.join(" ")}` : command;
|
|
4464
|
+
}
|
|
4465
|
+
function requireParsedArgs() {
|
|
4466
|
+
const parsed = parseAddArguments();
|
|
4467
|
+
if (!parsed) {
|
|
4121
4468
|
console.error("Usage: assist run add <name> <command> [args...]");
|
|
4122
4469
|
process.exit(1);
|
|
4123
4470
|
}
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4471
|
+
return parsed;
|
|
4472
|
+
}
|
|
4473
|
+
function getOrInitRunList() {
|
|
4127
4474
|
const config = loadConfig();
|
|
4128
|
-
if (!config.run)
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
}
|
|
4136
|
-
const entry = {
|
|
4137
|
-
name,
|
|
4138
|
-
command
|
|
4139
|
-
};
|
|
4140
|
-
if (args.length > 0) {
|
|
4141
|
-
entry.args = args;
|
|
4142
|
-
}
|
|
4143
|
-
config.run.push(entry);
|
|
4475
|
+
if (!config.run) config.run = [];
|
|
4476
|
+
return { config, runList: config.run };
|
|
4477
|
+
}
|
|
4478
|
+
function saveNewRunConfig(name, command, args) {
|
|
4479
|
+
const { config, runList } = getOrInitRunList();
|
|
4480
|
+
ensureNoDuplicate(runList, name);
|
|
4481
|
+
runList.push(buildRunEntry(name, command, args));
|
|
4144
4482
|
saveConfig(config);
|
|
4145
|
-
|
|
4146
|
-
|
|
4483
|
+
}
|
|
4484
|
+
function add() {
|
|
4485
|
+
const { name, command, args } = requireParsedArgs();
|
|
4486
|
+
saveNewRunConfig(name, command, args);
|
|
4487
|
+
console.log(
|
|
4488
|
+
`Added run configuration: ${name} -> ${formatDisplay(command, args)}`
|
|
4489
|
+
);
|
|
4490
|
+
}
|
|
4491
|
+
|
|
4492
|
+
// src/commands/run/index.ts
|
|
4493
|
+
function quoteIfNeeded(arg) {
|
|
4494
|
+
return arg.includes(" ") ? `"${arg}"` : arg;
|
|
4495
|
+
}
|
|
4496
|
+
function buildCommand(command, configArgs, extraArgs) {
|
|
4497
|
+
const allArgs = [...configArgs, ...extraArgs];
|
|
4498
|
+
return [quoteIfNeeded(command), ...allArgs.map(quoteIfNeeded)].join(" ");
|
|
4499
|
+
}
|
|
4500
|
+
function printAvailableConfigs(configs) {
|
|
4501
|
+
console.error("Available configurations:");
|
|
4502
|
+
for (const r of configs) {
|
|
4503
|
+
console.error(` - ${r.name}`);
|
|
4504
|
+
}
|
|
4505
|
+
}
|
|
4506
|
+
function exitNoRunConfigs() {
|
|
4507
|
+
console.error("No run configurations found in assist.yml");
|
|
4508
|
+
process.exit(1);
|
|
4509
|
+
}
|
|
4510
|
+
function requireRunConfigs() {
|
|
4511
|
+
const { run: run3 } = loadConfig();
|
|
4512
|
+
if (!run3 || run3.length === 0) return exitNoRunConfigs();
|
|
4513
|
+
return run3;
|
|
4514
|
+
}
|
|
4515
|
+
function exitWithConfigNotFound(name, configs) {
|
|
4516
|
+
console.error(`No run configuration found with name: ${name}`);
|
|
4517
|
+
printAvailableConfigs(configs);
|
|
4518
|
+
process.exit(1);
|
|
4519
|
+
}
|
|
4520
|
+
function findRunConfig(name) {
|
|
4521
|
+
const configs = requireRunConfigs();
|
|
4522
|
+
return configs.find((r) => r.name === name) ?? exitWithConfigNotFound(name, configs);
|
|
4523
|
+
}
|
|
4524
|
+
function onSpawnError(err) {
|
|
4525
|
+
console.error(`Failed to execute command: ${err.message}`);
|
|
4526
|
+
process.exit(1);
|
|
4527
|
+
}
|
|
4528
|
+
function spawnCommand(fullCommand) {
|
|
4529
|
+
const child = spawn4(fullCommand, [], { stdio: "inherit", shell: true });
|
|
4530
|
+
child.on("close", (code) => process.exit(code ?? 0));
|
|
4531
|
+
child.on("error", onSpawnError);
|
|
4532
|
+
}
|
|
4533
|
+
function run2(name, args) {
|
|
4534
|
+
const runConfig = findRunConfig(name);
|
|
4535
|
+
spawnCommand(buildCommand(runConfig.command, runConfig.args ?? [], args));
|
|
4147
4536
|
}
|
|
4148
4537
|
|
|
4149
4538
|
// src/commands/statusLine.ts
|
|
@@ -4165,29 +4554,29 @@ async function statusLine() {
|
|
|
4165
4554
|
}
|
|
4166
4555
|
|
|
4167
4556
|
// src/commands/sync.ts
|
|
4168
|
-
import * as
|
|
4557
|
+
import * as fs23 from "fs";
|
|
4169
4558
|
import * as os from "os";
|
|
4170
|
-
import * as
|
|
4559
|
+
import * as path28 from "path";
|
|
4171
4560
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4172
4561
|
|
|
4173
4562
|
// src/commands/sync/syncClaudeMd.ts
|
|
4174
|
-
import * as
|
|
4175
|
-
import * as
|
|
4176
|
-
import
|
|
4563
|
+
import * as fs21 from "fs";
|
|
4564
|
+
import * as path26 from "path";
|
|
4565
|
+
import chalk42 from "chalk";
|
|
4177
4566
|
async function syncClaudeMd(claudeDir, targetBase) {
|
|
4178
|
-
const source =
|
|
4179
|
-
const target =
|
|
4180
|
-
const sourceContent =
|
|
4181
|
-
if (
|
|
4182
|
-
const targetContent =
|
|
4567
|
+
const source = path26.join(claudeDir, "CLAUDE.md");
|
|
4568
|
+
const target = path26.join(targetBase, "CLAUDE.md");
|
|
4569
|
+
const sourceContent = fs21.readFileSync(source, "utf-8");
|
|
4570
|
+
if (fs21.existsSync(target)) {
|
|
4571
|
+
const targetContent = fs21.readFileSync(target, "utf-8");
|
|
4183
4572
|
if (sourceContent !== targetContent) {
|
|
4184
4573
|
console.log(
|
|
4185
|
-
|
|
4574
|
+
chalk42.yellow("\n\u26A0\uFE0F Warning: CLAUDE.md differs from existing file")
|
|
4186
4575
|
);
|
|
4187
4576
|
console.log();
|
|
4188
4577
|
printDiff(targetContent, sourceContent);
|
|
4189
4578
|
const confirm = await promptConfirm(
|
|
4190
|
-
|
|
4579
|
+
chalk42.red("Overwrite existing CLAUDE.md?"),
|
|
4191
4580
|
false
|
|
4192
4581
|
);
|
|
4193
4582
|
if (!confirm) {
|
|
@@ -4196,30 +4585,30 @@ async function syncClaudeMd(claudeDir, targetBase) {
|
|
|
4196
4585
|
}
|
|
4197
4586
|
}
|
|
4198
4587
|
}
|
|
4199
|
-
|
|
4588
|
+
fs21.copyFileSync(source, target);
|
|
4200
4589
|
console.log("Copied CLAUDE.md to ~/.claude/CLAUDE.md");
|
|
4201
4590
|
}
|
|
4202
4591
|
|
|
4203
4592
|
// src/commands/sync/syncSettings.ts
|
|
4204
|
-
import * as
|
|
4205
|
-
import * as
|
|
4206
|
-
import
|
|
4593
|
+
import * as fs22 from "fs";
|
|
4594
|
+
import * as path27 from "path";
|
|
4595
|
+
import chalk43 from "chalk";
|
|
4207
4596
|
async function syncSettings(claudeDir, targetBase) {
|
|
4208
|
-
const source =
|
|
4209
|
-
const target =
|
|
4210
|
-
const sourceContent =
|
|
4597
|
+
const source = path27.join(claudeDir, "settings.json");
|
|
4598
|
+
const target = path27.join(targetBase, "settings.json");
|
|
4599
|
+
const sourceContent = fs22.readFileSync(source, "utf-8");
|
|
4211
4600
|
const normalizedSource = JSON.stringify(JSON.parse(sourceContent), null, 2);
|
|
4212
|
-
if (
|
|
4213
|
-
const targetContent =
|
|
4601
|
+
if (fs22.existsSync(target)) {
|
|
4602
|
+
const targetContent = fs22.readFileSync(target, "utf-8");
|
|
4214
4603
|
const normalizedTarget = JSON.stringify(JSON.parse(targetContent), null, 2);
|
|
4215
4604
|
if (normalizedSource !== normalizedTarget) {
|
|
4216
4605
|
console.log(
|
|
4217
|
-
|
|
4606
|
+
chalk43.yellow("\n\u26A0\uFE0F Warning: settings.json differs from existing file")
|
|
4218
4607
|
);
|
|
4219
4608
|
console.log();
|
|
4220
4609
|
printDiff(targetContent, sourceContent);
|
|
4221
4610
|
const confirm = await promptConfirm(
|
|
4222
|
-
|
|
4611
|
+
chalk43.red("Overwrite existing settings.json?"),
|
|
4223
4612
|
false
|
|
4224
4613
|
);
|
|
4225
4614
|
if (!confirm) {
|
|
@@ -4228,27 +4617,27 @@ async function syncSettings(claudeDir, targetBase) {
|
|
|
4228
4617
|
}
|
|
4229
4618
|
}
|
|
4230
4619
|
}
|
|
4231
|
-
|
|
4620
|
+
fs22.copyFileSync(source, target);
|
|
4232
4621
|
console.log("Copied settings.json to ~/.claude/settings.json");
|
|
4233
4622
|
}
|
|
4234
4623
|
|
|
4235
4624
|
// src/commands/sync.ts
|
|
4236
4625
|
var __filename2 = fileURLToPath3(import.meta.url);
|
|
4237
|
-
var __dirname4 =
|
|
4626
|
+
var __dirname4 = path28.dirname(__filename2);
|
|
4238
4627
|
async function sync() {
|
|
4239
|
-
const claudeDir =
|
|
4240
|
-
const targetBase =
|
|
4628
|
+
const claudeDir = path28.join(__dirname4, "..", "claude");
|
|
4629
|
+
const targetBase = path28.join(os.homedir(), ".claude");
|
|
4241
4630
|
syncCommands(claudeDir, targetBase);
|
|
4242
4631
|
await syncSettings(claudeDir, targetBase);
|
|
4243
4632
|
await syncClaudeMd(claudeDir, targetBase);
|
|
4244
4633
|
}
|
|
4245
4634
|
function syncCommands(claudeDir, targetBase) {
|
|
4246
|
-
const sourceDir =
|
|
4247
|
-
const targetDir =
|
|
4248
|
-
|
|
4249
|
-
const files =
|
|
4635
|
+
const sourceDir = path28.join(claudeDir, "commands");
|
|
4636
|
+
const targetDir = path28.join(targetBase, "commands");
|
|
4637
|
+
fs23.mkdirSync(targetDir, { recursive: true });
|
|
4638
|
+
const files = fs23.readdirSync(sourceDir);
|
|
4250
4639
|
for (const file of files) {
|
|
4251
|
-
|
|
4640
|
+
fs23.copyFileSync(path28.join(sourceDir, file), path28.join(targetDir, file));
|
|
4252
4641
|
console.log(`Copied ${file} to ${targetDir}`);
|
|
4253
4642
|
}
|
|
4254
4643
|
console.log(`Synced ${files.length} command(s) to ~/.claude/commands`);
|