edsger 0.72.4 → 0.72.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +90 -89
- package/dist/system/session-manager.js +5 -0
- package/dist/utils/validation.d.ts +32 -0
- package/dist/utils/validation.js +44 -26
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -58,7 +58,7 @@ import { runWorkflow } from './commands/workflow/index.js';
|
|
|
58
58
|
import { DEFAULT_MAX_FILES as FIND_ARCHITECTURE_DEFAULT_MAX_FILES } from './phases/find-architecture/index.js';
|
|
59
59
|
import { DEFAULT_MAX_FILES as FIND_SMELLS_DEFAULT_MAX_FILES } from './phases/find-smells/index.js';
|
|
60
60
|
import { SMELL_CATEGORIES, } from './phases/find-smells/types.js';
|
|
61
|
-
import { deregisterSession, registerSession, } from './system/session-manager.js';
|
|
61
|
+
import { deregisterSession, registerSession, startHeartbeat, } from './system/session-manager.js';
|
|
62
62
|
import { logError, logInfo } from './utils/logger.js';
|
|
63
63
|
// Get package.json version dynamically
|
|
64
64
|
// eslint-disable-next-line @typescript-eslint/naming-convention -- ESM __filename/__dirname polyfill
|
|
@@ -89,6 +89,10 @@ program
|
|
|
89
89
|
program.hook('preAction', async (_thisCommand, actionCommand) => {
|
|
90
90
|
if (process.env.EDSGER_PROCESS_KEY) {
|
|
91
91
|
await registerSession({ command: actionCommand.name() });
|
|
92
|
+
// Keep last_heartbeat fresh while the run is alive, so consumers of the
|
|
93
|
+
// cli_sessions row (the desktop's tab views) can tell a live session from
|
|
94
|
+
// a stale one left behind by a crash / SIGKILL.
|
|
95
|
+
startHeartbeat();
|
|
92
96
|
}
|
|
93
97
|
});
|
|
94
98
|
program.hook('postAction', async () => {
|
|
@@ -96,6 +100,46 @@ program.hook('postAction', async () => {
|
|
|
96
100
|
await deregisterSession();
|
|
97
101
|
}
|
|
98
102
|
});
|
|
103
|
+
/**
|
|
104
|
+
* Mark the session row stopped (bounded, best-effort) and exit non-zero.
|
|
105
|
+
*
|
|
106
|
+
* Every command's catch block used to call `process.exit(1)` directly, which
|
|
107
|
+
* skips the postAction hook above — the cli_sessions row stayed 'running'
|
|
108
|
+
* forever and the desktop kept showing a ghost in-progress run that no Stop
|
|
109
|
+
* button could clear.
|
|
110
|
+
*/
|
|
111
|
+
function exitWithError(error) {
|
|
112
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
113
|
+
void Promise.race([
|
|
114
|
+
deregisterSession(),
|
|
115
|
+
new Promise((resolve) => {
|
|
116
|
+
setTimeout(resolve, 3000);
|
|
117
|
+
}),
|
|
118
|
+
]).finally(() => process.exit(1));
|
|
119
|
+
}
|
|
120
|
+
// The desktop's Stop button sends SIGTERM (escalating to SIGKILL after 5s).
|
|
121
|
+
// Without a handler the process dies with its cli_sessions row still
|
|
122
|
+
// 'running' — same ghost-run problem as the error path above. Finalize the
|
|
123
|
+
// row (bounded) before exiting. Commands that install their own handlers
|
|
124
|
+
// (build, agent-workflow, task-worker, chat-serve, ...) own the process
|
|
125
|
+
// lifetime; in that case only the session row is finalized here.
|
|
126
|
+
for (const signal of ['SIGTERM', 'SIGINT']) {
|
|
127
|
+
const onTermination = () => {
|
|
128
|
+
const finalize = Promise.race([
|
|
129
|
+
deregisterSession(),
|
|
130
|
+
new Promise((resolve) => {
|
|
131
|
+
setTimeout(resolve, 3000);
|
|
132
|
+
}),
|
|
133
|
+
]);
|
|
134
|
+
const others = process
|
|
135
|
+
.listeners(signal)
|
|
136
|
+
.filter((listener) => listener !== onTermination);
|
|
137
|
+
if (others.length === 0) {
|
|
138
|
+
void finalize.finally(() => process.exit(signal === 'SIGINT' ? 130 : 143));
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
process.on(signal, onTermination);
|
|
142
|
+
}
|
|
99
143
|
// ============================================================
|
|
100
144
|
// Subcommand: edsger login
|
|
101
145
|
// ============================================================
|
|
@@ -107,8 +151,7 @@ program
|
|
|
107
151
|
await runLogin();
|
|
108
152
|
}
|
|
109
153
|
catch (error) {
|
|
110
|
-
|
|
111
|
-
process.exit(1);
|
|
154
|
+
exitWithError(error);
|
|
112
155
|
}
|
|
113
156
|
});
|
|
114
157
|
// ============================================================
|
|
@@ -122,8 +165,7 @@ program
|
|
|
122
165
|
runLogout();
|
|
123
166
|
}
|
|
124
167
|
catch (error) {
|
|
125
|
-
|
|
126
|
-
process.exit(1);
|
|
168
|
+
exitWithError(error);
|
|
127
169
|
}
|
|
128
170
|
});
|
|
129
171
|
// ============================================================
|
|
@@ -137,8 +179,7 @@ program
|
|
|
137
179
|
runStatus();
|
|
138
180
|
}
|
|
139
181
|
catch (error) {
|
|
140
|
-
|
|
141
|
-
process.exit(1);
|
|
182
|
+
exitWithError(error);
|
|
142
183
|
}
|
|
143
184
|
});
|
|
144
185
|
// ============================================================
|
|
@@ -190,8 +231,7 @@ program
|
|
|
190
231
|
});
|
|
191
232
|
}
|
|
192
233
|
catch (error) {
|
|
193
|
-
|
|
194
|
-
process.exit(1);
|
|
234
|
+
exitWithError(error);
|
|
195
235
|
}
|
|
196
236
|
});
|
|
197
237
|
// ============================================================
|
|
@@ -217,8 +257,7 @@ program
|
|
|
217
257
|
});
|
|
218
258
|
}
|
|
219
259
|
catch (error) {
|
|
220
|
-
|
|
221
|
-
process.exit(1);
|
|
260
|
+
exitWithError(error);
|
|
222
261
|
}
|
|
223
262
|
});
|
|
224
263
|
// ============================================================
|
|
@@ -244,8 +283,7 @@ program
|
|
|
244
283
|
});
|
|
245
284
|
}
|
|
246
285
|
catch (error) {
|
|
247
|
-
|
|
248
|
-
process.exit(1);
|
|
286
|
+
exitWithError(error);
|
|
249
287
|
}
|
|
250
288
|
});
|
|
251
289
|
// ============================================================
|
|
@@ -271,8 +309,7 @@ program
|
|
|
271
309
|
});
|
|
272
310
|
}
|
|
273
311
|
catch (error) {
|
|
274
|
-
|
|
275
|
-
process.exit(1);
|
|
312
|
+
exitWithError(error);
|
|
276
313
|
}
|
|
277
314
|
});
|
|
278
315
|
// ============================================================
|
|
@@ -298,8 +335,7 @@ program
|
|
|
298
335
|
});
|
|
299
336
|
}
|
|
300
337
|
catch (error) {
|
|
301
|
-
|
|
302
|
-
process.exit(1);
|
|
338
|
+
exitWithError(error);
|
|
303
339
|
}
|
|
304
340
|
});
|
|
305
341
|
const diagramSubcommands = [
|
|
@@ -349,8 +385,7 @@ for (const sub of diagramSubcommands) {
|
|
|
349
385
|
});
|
|
350
386
|
}
|
|
351
387
|
catch (error) {
|
|
352
|
-
|
|
353
|
-
process.exit(1);
|
|
388
|
+
exitWithError(error);
|
|
354
389
|
}
|
|
355
390
|
});
|
|
356
391
|
}
|
|
@@ -373,8 +408,7 @@ program
|
|
|
373
408
|
});
|
|
374
409
|
}
|
|
375
410
|
catch (error) {
|
|
376
|
-
|
|
377
|
-
process.exit(1);
|
|
411
|
+
exitWithError(error);
|
|
378
412
|
}
|
|
379
413
|
});
|
|
380
414
|
// ============================================================
|
|
@@ -394,8 +428,7 @@ program
|
|
|
394
428
|
});
|
|
395
429
|
}
|
|
396
430
|
catch (error) {
|
|
397
|
-
|
|
398
|
-
process.exit(1);
|
|
431
|
+
exitWithError(error);
|
|
399
432
|
}
|
|
400
433
|
});
|
|
401
434
|
// ============================================================
|
|
@@ -425,8 +458,7 @@ program
|
|
|
425
458
|
});
|
|
426
459
|
}
|
|
427
460
|
catch (error) {
|
|
428
|
-
|
|
429
|
-
process.exit(1);
|
|
461
|
+
exitWithError(error);
|
|
430
462
|
}
|
|
431
463
|
});
|
|
432
464
|
// ============================================================
|
|
@@ -452,8 +484,7 @@ program
|
|
|
452
484
|
});
|
|
453
485
|
}
|
|
454
486
|
catch (error) {
|
|
455
|
-
|
|
456
|
-
process.exit(1);
|
|
487
|
+
exitWithError(error);
|
|
457
488
|
}
|
|
458
489
|
});
|
|
459
490
|
// ============================================================
|
|
@@ -487,8 +518,7 @@ program
|
|
|
487
518
|
});
|
|
488
519
|
}
|
|
489
520
|
catch (error) {
|
|
490
|
-
|
|
491
|
-
process.exit(1);
|
|
521
|
+
exitWithError(error);
|
|
492
522
|
}
|
|
493
523
|
});
|
|
494
524
|
// ============================================================
|
|
@@ -518,8 +548,7 @@ program
|
|
|
518
548
|
});
|
|
519
549
|
}
|
|
520
550
|
catch (error) {
|
|
521
|
-
|
|
522
|
-
process.exit(1);
|
|
551
|
+
exitWithError(error);
|
|
523
552
|
}
|
|
524
553
|
});
|
|
525
554
|
// ============================================================
|
|
@@ -537,8 +566,7 @@ program
|
|
|
537
566
|
});
|
|
538
567
|
}
|
|
539
568
|
catch (error) {
|
|
540
|
-
|
|
541
|
-
process.exit(1);
|
|
569
|
+
exitWithError(error);
|
|
542
570
|
}
|
|
543
571
|
});
|
|
544
572
|
// ============================================================
|
|
@@ -556,8 +584,7 @@ program
|
|
|
556
584
|
});
|
|
557
585
|
}
|
|
558
586
|
catch (error) {
|
|
559
|
-
|
|
560
|
-
process.exit(1);
|
|
587
|
+
exitWithError(error);
|
|
561
588
|
}
|
|
562
589
|
});
|
|
563
590
|
// ============================================================
|
|
@@ -572,8 +599,7 @@ program
|
|
|
572
599
|
await runIssueAnalysisCommand({ issueId, verbose: opts.verbose });
|
|
573
600
|
}
|
|
574
601
|
catch (error) {
|
|
575
|
-
|
|
576
|
-
process.exit(1);
|
|
602
|
+
exitWithError(error);
|
|
577
603
|
}
|
|
578
604
|
});
|
|
579
605
|
// ============================================================
|
|
@@ -605,8 +631,7 @@ program
|
|
|
605
631
|
});
|
|
606
632
|
}
|
|
607
633
|
catch (error) {
|
|
608
|
-
|
|
609
|
-
process.exit(1);
|
|
634
|
+
exitWithError(error);
|
|
610
635
|
}
|
|
611
636
|
});
|
|
612
637
|
// ============================================================
|
|
@@ -633,8 +658,7 @@ program
|
|
|
633
658
|
});
|
|
634
659
|
}
|
|
635
660
|
catch (error) {
|
|
636
|
-
|
|
637
|
-
process.exit(1);
|
|
661
|
+
exitWithError(error);
|
|
638
662
|
}
|
|
639
663
|
});
|
|
640
664
|
// ============================================================
|
|
@@ -649,8 +673,7 @@ program
|
|
|
649
673
|
await runChatServeCommand({ verbose: opts.verbose });
|
|
650
674
|
}
|
|
651
675
|
catch (error) {
|
|
652
|
-
|
|
653
|
-
process.exit(1);
|
|
676
|
+
exitWithError(error);
|
|
654
677
|
}
|
|
655
678
|
});
|
|
656
679
|
// ============================================================
|
|
@@ -665,8 +688,7 @@ program
|
|
|
665
688
|
await runUserStoriesAnalysisCommand({ issueId, verbose: opts.verbose });
|
|
666
689
|
}
|
|
667
690
|
catch (error) {
|
|
668
|
-
|
|
669
|
-
process.exit(1);
|
|
691
|
+
exitWithError(error);
|
|
670
692
|
}
|
|
671
693
|
});
|
|
672
694
|
// ============================================================
|
|
@@ -681,8 +703,7 @@ program
|
|
|
681
703
|
await runTestCasesAnalysisCommand({ issueId, verbose: opts.verbose });
|
|
682
704
|
}
|
|
683
705
|
catch (error) {
|
|
684
|
-
|
|
685
|
-
process.exit(1);
|
|
706
|
+
exitWithError(error);
|
|
686
707
|
}
|
|
687
708
|
});
|
|
688
709
|
// ============================================================
|
|
@@ -697,8 +718,7 @@ program
|
|
|
697
718
|
await runTechnicalDesignCommand({ issueId, verbose: opts.verbose });
|
|
698
719
|
}
|
|
699
720
|
catch (error) {
|
|
700
|
-
|
|
701
|
-
process.exit(1);
|
|
721
|
+
exitWithError(error);
|
|
702
722
|
}
|
|
703
723
|
});
|
|
704
724
|
// ============================================================
|
|
@@ -716,8 +736,7 @@ program
|
|
|
716
736
|
});
|
|
717
737
|
}
|
|
718
738
|
catch (error) {
|
|
719
|
-
|
|
720
|
-
process.exit(1);
|
|
739
|
+
exitWithError(error);
|
|
721
740
|
}
|
|
722
741
|
});
|
|
723
742
|
// ============================================================
|
|
@@ -737,8 +756,7 @@ program
|
|
|
737
756
|
});
|
|
738
757
|
}
|
|
739
758
|
catch (error) {
|
|
740
|
-
|
|
741
|
-
process.exit(1);
|
|
759
|
+
exitWithError(error);
|
|
742
760
|
}
|
|
743
761
|
});
|
|
744
762
|
// ============================================================
|
|
@@ -755,8 +773,7 @@ program
|
|
|
755
773
|
await runPRReview(productId, opts);
|
|
756
774
|
}
|
|
757
775
|
catch (error) {
|
|
758
|
-
|
|
759
|
-
process.exit(1);
|
|
776
|
+
exitWithError(error);
|
|
760
777
|
}
|
|
761
778
|
});
|
|
762
779
|
// ============================================================
|
|
@@ -780,8 +797,7 @@ program
|
|
|
780
797
|
await runFindBugs(productId, opts);
|
|
781
798
|
}
|
|
782
799
|
catch (error) {
|
|
783
|
-
|
|
784
|
-
process.exit(1);
|
|
800
|
+
exitWithError(error);
|
|
785
801
|
}
|
|
786
802
|
});
|
|
787
803
|
// ============================================================
|
|
@@ -807,8 +823,7 @@ program
|
|
|
807
823
|
await runQualityBenchmarkCli(productId ?? '', opts);
|
|
808
824
|
}
|
|
809
825
|
catch (error) {
|
|
810
|
-
|
|
811
|
-
process.exit(1);
|
|
826
|
+
exitWithError(error);
|
|
812
827
|
}
|
|
813
828
|
});
|
|
814
829
|
// ============================================================
|
|
@@ -823,8 +838,7 @@ program
|
|
|
823
838
|
await runSyncGithubIssues(productId, opts);
|
|
824
839
|
}
|
|
825
840
|
catch (error) {
|
|
826
|
-
|
|
827
|
-
process.exit(1);
|
|
841
|
+
exitWithError(error);
|
|
828
842
|
}
|
|
829
843
|
});
|
|
830
844
|
// ============================================================
|
|
@@ -840,8 +854,7 @@ program
|
|
|
840
854
|
await runSyncOrgRepos(teamId, opts);
|
|
841
855
|
}
|
|
842
856
|
catch (error) {
|
|
843
|
-
|
|
844
|
-
process.exit(1);
|
|
857
|
+
exitWithError(error);
|
|
845
858
|
}
|
|
846
859
|
});
|
|
847
860
|
// ============================================================
|
|
@@ -859,8 +872,7 @@ program
|
|
|
859
872
|
await runSyncSentryIssues(productId, opts);
|
|
860
873
|
}
|
|
861
874
|
catch (error) {
|
|
862
|
-
|
|
863
|
-
process.exit(1);
|
|
875
|
+
exitWithError(error);
|
|
864
876
|
}
|
|
865
877
|
});
|
|
866
878
|
// ============================================================
|
|
@@ -878,8 +890,7 @@ program
|
|
|
878
890
|
await runSyncDatadog(teamId, opts);
|
|
879
891
|
}
|
|
880
892
|
catch (error) {
|
|
881
|
-
|
|
882
|
-
process.exit(1);
|
|
893
|
+
exitWithError(error);
|
|
883
894
|
}
|
|
884
895
|
});
|
|
885
896
|
// ============================================================
|
|
@@ -904,8 +915,7 @@ program
|
|
|
904
915
|
});
|
|
905
916
|
}
|
|
906
917
|
catch (error) {
|
|
907
|
-
|
|
908
|
-
process.exit(1);
|
|
918
|
+
exitWithError(error);
|
|
909
919
|
}
|
|
910
920
|
});
|
|
911
921
|
// ============================================================
|
|
@@ -925,8 +935,7 @@ program
|
|
|
925
935
|
await runDiscover(teamId, runId, opts);
|
|
926
936
|
}
|
|
927
937
|
catch (error) {
|
|
928
|
-
|
|
929
|
-
process.exit(1);
|
|
938
|
+
exitWithError(error);
|
|
930
939
|
}
|
|
931
940
|
});
|
|
932
941
|
// Subcommand: edsger sync-terraform <teamId>
|
|
@@ -945,8 +954,7 @@ program
|
|
|
945
954
|
await runSyncTerraform(teamId, opts);
|
|
946
955
|
}
|
|
947
956
|
catch (error) {
|
|
948
|
-
|
|
949
|
-
process.exit(1);
|
|
957
|
+
exitWithError(error);
|
|
950
958
|
}
|
|
951
959
|
});
|
|
952
960
|
// ============================================================
|
|
@@ -978,8 +986,7 @@ program
|
|
|
978
986
|
await runFindFeatures(productId, opts);
|
|
979
987
|
}
|
|
980
988
|
catch (error) {
|
|
981
|
-
|
|
982
|
-
process.exit(1);
|
|
989
|
+
exitWithError(error);
|
|
983
990
|
}
|
|
984
991
|
});
|
|
985
992
|
// ============================================================
|
|
@@ -1004,8 +1011,7 @@ program
|
|
|
1004
1011
|
await runFindSmells(productId, opts);
|
|
1005
1012
|
}
|
|
1006
1013
|
catch (error) {
|
|
1007
|
-
|
|
1008
|
-
process.exit(1);
|
|
1014
|
+
exitWithError(error);
|
|
1009
1015
|
}
|
|
1010
1016
|
});
|
|
1011
1017
|
// ============================================================
|
|
@@ -1029,8 +1035,7 @@ program
|
|
|
1029
1035
|
await runFindArchitecture(productId, opts);
|
|
1030
1036
|
}
|
|
1031
1037
|
catch (error) {
|
|
1032
|
-
|
|
1033
|
-
process.exit(1);
|
|
1038
|
+
exitWithError(error);
|
|
1034
1039
|
}
|
|
1035
1040
|
});
|
|
1036
1041
|
// ============================================================
|
|
@@ -1046,8 +1051,7 @@ program
|
|
|
1046
1051
|
await runProductTestCases(productId, opts);
|
|
1047
1052
|
}
|
|
1048
1053
|
catch (error) {
|
|
1049
|
-
|
|
1050
|
-
process.exit(1);
|
|
1054
|
+
exitWithError(error);
|
|
1051
1055
|
}
|
|
1052
1056
|
});
|
|
1053
1057
|
// ============================================================
|
|
@@ -1073,8 +1077,7 @@ program
|
|
|
1073
1077
|
});
|
|
1074
1078
|
}
|
|
1075
1079
|
catch (error) {
|
|
1076
|
-
|
|
1077
|
-
process.exit(1);
|
|
1080
|
+
exitWithError(error);
|
|
1078
1081
|
}
|
|
1079
1082
|
});
|
|
1080
1083
|
// ============================================================
|
|
@@ -1092,8 +1095,7 @@ program
|
|
|
1092
1095
|
await runPRResolve(productId, opts);
|
|
1093
1096
|
}
|
|
1094
1097
|
catch (error) {
|
|
1095
|
-
|
|
1096
|
-
process.exit(1);
|
|
1098
|
+
exitWithError(error);
|
|
1097
1099
|
}
|
|
1098
1100
|
});
|
|
1099
1101
|
// ============================================================
|
|
@@ -1117,16 +1119,15 @@ program.action(async (options, command) => {
|
|
|
1117
1119
|
// bugs look like the agent inexplicably running with the wrong intent.
|
|
1118
1120
|
// Treat any leftover positional arg as an unknown command and abort.
|
|
1119
1121
|
if (command.args.length > 0) {
|
|
1120
|
-
logError(`Unknown command: ${command.args[0]}`);
|
|
1121
1122
|
logError(`Run 'edsger --help' to see available commands.`);
|
|
1122
|
-
|
|
1123
|
+
exitWithError(new Error(`Unknown command: ${command.args[0]}`));
|
|
1124
|
+
return;
|
|
1123
1125
|
}
|
|
1124
1126
|
try {
|
|
1125
1127
|
await runEdsger(options);
|
|
1126
1128
|
}
|
|
1127
1129
|
catch (error) {
|
|
1128
|
-
|
|
1129
|
-
process.exit(1);
|
|
1130
|
+
exitWithError(error);
|
|
1130
1131
|
}
|
|
1131
1132
|
});
|
|
1132
1133
|
export const runEdsger = async (options) => {
|
|
@@ -215,11 +215,16 @@ export async function sendHeartbeat(currentIssueId, currentIssueName) {
|
|
|
215
215
|
* Start periodic heartbeats
|
|
216
216
|
*/
|
|
217
217
|
export function startHeartbeat() {
|
|
218
|
+
if (heartbeatTimer) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
218
221
|
heartbeatTimer = setInterval(() => {
|
|
219
222
|
sendHeartbeat().catch(() => {
|
|
220
223
|
/* noop */
|
|
221
224
|
});
|
|
222
225
|
}, HEARTBEAT_INTERVAL);
|
|
226
|
+
// The heartbeat alone must never keep a finished command's process alive.
|
|
227
|
+
heartbeatTimer.unref?.();
|
|
223
228
|
}
|
|
224
229
|
/**
|
|
225
230
|
* Stop periodic heartbeats
|
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
import { loadConfig } from '../config.js';
|
|
2
2
|
import { type CliOptions } from '../types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Determine the correct npm install target for optional dependencies.
|
|
5
|
+
*
|
|
6
|
+
* `Function('return import("pkg")')()` resolves from the edsger module's
|
|
7
|
+
* own location, walking up the node_modules chain. We need to install
|
|
8
|
+
* packages into a directory that sits on that resolution path.
|
|
9
|
+
*
|
|
10
|
+
* - Source checkout / monorepo → `npm install` in the edsger package dir
|
|
11
|
+
* - Project dependency → `npm install` in the project root (has package.json)
|
|
12
|
+
* - Global-style install → `npm install -g --prefix <prefix>`
|
|
13
|
+
*
|
|
14
|
+
* The global-style case covers both a plain `npm install -g edsger` and the
|
|
15
|
+
* desktop app's managed install (`npm install -g edsger --prefix <userData>/cli`).
|
|
16
|
+
* In both, edsger lives at `<prefix>/lib/node_modules/edsger` (or
|
|
17
|
+
* `<prefix>/node_modules/edsger` on Windows) and there is NO package.json at
|
|
18
|
+
* the directory that owns that node_modules. Running a *project-local*
|
|
19
|
+
* `npm install` there would treat every already-installed package — including
|
|
20
|
+
* edsger and all its dependencies — as extraneous and PRUNE them, destroying
|
|
21
|
+
* the install. We must install with `--prefix` into the same prefix instead.
|
|
22
|
+
*/
|
|
23
|
+
export interface NpmInstallTarget {
|
|
24
|
+
global: boolean;
|
|
25
|
+
cwd?: string;
|
|
26
|
+
prefix?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Pure resolution of the npm install target, given edsger's own directory and
|
|
30
|
+
* a predicate for testing whether a path exists. Separated from
|
|
31
|
+
* {@link getNpmInstallTarget} so it can be unit-tested without touching the
|
|
32
|
+
* real filesystem or `import.meta.url`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveNpmInstallTarget(edsgerDir: string, fileExists: (p: string) => boolean): NpmInstallTarget;
|
|
3
35
|
export interface ValidationResult {
|
|
4
36
|
config: ReturnType<typeof loadConfig>;
|
|
5
37
|
mcpServerUrl: string;
|
package/dist/utils/validation.js
CHANGED
|
@@ -1,9 +1,37 @@
|
|
|
1
1
|
import { execSync, spawnSync } from 'child_process';
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { basename, dirname, join, resolve, sep } from 'path';
|
|
3
4
|
import { fileURLToPath } from 'url';
|
|
4
5
|
import { getMcpServerUrl, getMcpToken } from '../auth/auth-store.js';
|
|
5
6
|
import { loadConfig, validateConfig } from '../config.js';
|
|
6
7
|
import { logInfo, logWarning } from './logger.js';
|
|
8
|
+
/**
|
|
9
|
+
* Pure resolution of the npm install target, given edsger's own directory and
|
|
10
|
+
* a predicate for testing whether a path exists. Separated from
|
|
11
|
+
* {@link getNpmInstallTarget} so it can be unit-tested without touching the
|
|
12
|
+
* real filesystem or `import.meta.url`.
|
|
13
|
+
*/
|
|
14
|
+
export function resolveNpmInstallTarget(edsgerDir, fileExists) {
|
|
15
|
+
const nodeModulesSegment = `${sep}node_modules${sep}`;
|
|
16
|
+
if (!edsgerDir.includes(nodeModulesSegment)) {
|
|
17
|
+
// Source checkout / monorepo — install in edsger's own package dir
|
|
18
|
+
return { global: false, cwd: edsgerDir };
|
|
19
|
+
}
|
|
20
|
+
// edsger is inside some node_modules tree. The directory that owns the
|
|
21
|
+
// outermost node_modules containing edsger is the candidate install root.
|
|
22
|
+
const installRoot = edsgerDir.split(nodeModulesSegment)[0];
|
|
23
|
+
// A real project dependency has a package.json at that root, so a
|
|
24
|
+
// project-local install safely adds to its dependencies without pruning.
|
|
25
|
+
if (fileExists(join(installRoot, 'package.json'))) {
|
|
26
|
+
return { global: false, cwd: installRoot };
|
|
27
|
+
}
|
|
28
|
+
// No package.json → global-style install. Derive the npm prefix so the
|
|
29
|
+
// package lands on edsger's resolution path. Unix global installs nest
|
|
30
|
+
// under `<prefix>/lib`; Windows installs put node_modules directly under
|
|
31
|
+
// `<prefix>`.
|
|
32
|
+
const prefix = basename(installRoot) === 'lib' ? dirname(installRoot) : installRoot;
|
|
33
|
+
return { global: true, prefix };
|
|
34
|
+
}
|
|
7
35
|
/**
|
|
8
36
|
* Determine the correct npm install target for optional dependencies.
|
|
9
37
|
*
|
|
@@ -11,33 +39,22 @@ import { logInfo, logWarning } from './logger.js';
|
|
|
11
39
|
* own location, walking up the node_modules chain. We need to install
|
|
12
40
|
* packages into a directory that sits on that resolution path.
|
|
13
41
|
*
|
|
14
|
-
* -
|
|
15
|
-
* - Project dependency → `npm install` in the project root
|
|
16
|
-
* -
|
|
42
|
+
* - Source checkout / monorepo → `npm install` in the edsger package dir
|
|
43
|
+
* - Project dependency → `npm install` in the project root (has package.json)
|
|
44
|
+
* - Global-style install → `npm install -g --prefix <prefix>`
|
|
45
|
+
*
|
|
46
|
+
* The global-style case covers both a plain `npm install -g edsger` and the
|
|
47
|
+
* desktop app's managed install (`npm install -g edsger --prefix <userData>/cli`).
|
|
48
|
+
* In both, edsger lives at `<prefix>/lib/node_modules/edsger` (or
|
|
49
|
+
* `<prefix>/node_modules/edsger` on Windows) and there is NO package.json at
|
|
50
|
+
* the directory that owns that node_modules. Running a *project-local*
|
|
51
|
+
* `npm install` there would treat every already-installed package — including
|
|
52
|
+
* edsger and all its dependencies — as extraneous and PRUNE them, destroying
|
|
53
|
+
* the install. We must install with `--prefix` into the same prefix instead.
|
|
17
54
|
*/
|
|
18
55
|
function getNpmInstallTarget() {
|
|
19
56
|
const edsgerDir = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
|
|
20
|
-
|
|
21
|
-
if (!edsgerDir.includes(nodeModulesSegment)) {
|
|
22
|
-
// Source checkout / monorepo — install in edsger's own package dir
|
|
23
|
-
return { global: false, cwd: edsgerDir };
|
|
24
|
-
}
|
|
25
|
-
// edsger is inside some node_modules tree
|
|
26
|
-
try {
|
|
27
|
-
const globalPrefix = execSync('npm config get prefix', {
|
|
28
|
-
encoding: 'utf-8',
|
|
29
|
-
stdio: ['pipe', 'pipe', 'ignore'],
|
|
30
|
-
}).trim();
|
|
31
|
-
if (edsgerDir.startsWith(globalPrefix)) {
|
|
32
|
-
return { global: true };
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
// ignore — fall through to project-local
|
|
37
|
-
}
|
|
38
|
-
// Project dependency — install at the project root (parent of node_modules)
|
|
39
|
-
const projectRoot = edsgerDir.split(nodeModulesSegment)[0];
|
|
40
|
-
return { global: false, cwd: projectRoot };
|
|
57
|
+
return resolveNpmInstallTarget(edsgerDir, existsSync);
|
|
41
58
|
}
|
|
42
59
|
/**
|
|
43
60
|
* Common configuration validation for all CLI commands
|
|
@@ -106,7 +123,8 @@ const restartProcess = (envFlag) => {
|
|
|
106
123
|
function npmInstall(packageName) {
|
|
107
124
|
const target = getNpmInstallTarget();
|
|
108
125
|
const globalFlag = target.global ? ' -g' : '';
|
|
109
|
-
const
|
|
126
|
+
const prefixFlag = target.prefix ? ` --prefix "${target.prefix}"` : '';
|
|
127
|
+
const cmd = `npm install${globalFlag}${prefixFlag} ${packageName}`;
|
|
110
128
|
logInfo(` $ ${cmd}${target.cwd ? ` (in ${target.cwd})` : ''}`);
|
|
111
129
|
execSync(cmd, { cwd: target.cwd, stdio: 'inherit' });
|
|
112
130
|
}
|