@serviceme/devtools-core 0.3.2 → 0.3.3
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/auth.d.mts +2 -592
- package/dist/auth.d.ts +2 -592
- package/dist/auth.js +175 -17
- package/dist/auth.js.map +1 -1
- package/dist/auth.mjs +175 -17
- package/dist/auth.mjs.map +1 -1
- package/dist/device.js +26 -0
- package/dist/device.js.map +1 -1
- package/dist/device.mjs +26 -0
- package/dist/device.mjs.map +1 -1
- package/dist/index-87-_JvQi.d.mts +626 -0
- package/dist/index-87-_JvQi.d.ts +626 -0
- package/dist/index.d.mts +24 -31
- package/dist/index.d.ts +24 -31
- package/dist/index.js +311 -68
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +317 -74
- package/dist/index.mjs.map +1 -1
- package/dist/toolbox.js +25 -0
- package/dist/toolbox.js.map +1 -1
- package/dist/toolbox.mjs +25 -0
- package/dist/toolbox.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -454,6 +454,50 @@ var AccessControl = class {
|
|
|
454
454
|
}
|
|
455
455
|
};
|
|
456
456
|
|
|
457
|
+
// src/logger.ts
|
|
458
|
+
var noopLogger = {
|
|
459
|
+
debug() {
|
|
460
|
+
},
|
|
461
|
+
info() {
|
|
462
|
+
},
|
|
463
|
+
warn() {
|
|
464
|
+
},
|
|
465
|
+
error() {
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
function formatArgs(args) {
|
|
469
|
+
return args.map((arg) => {
|
|
470
|
+
if (typeof arg === "string") {
|
|
471
|
+
return arg;
|
|
472
|
+
}
|
|
473
|
+
try {
|
|
474
|
+
return JSON.stringify(arg);
|
|
475
|
+
} catch {
|
|
476
|
+
return String(arg);
|
|
477
|
+
}
|
|
478
|
+
}).join(" ");
|
|
479
|
+
}
|
|
480
|
+
function createConsoleLogger(prefix = "serviceme") {
|
|
481
|
+
return {
|
|
482
|
+
debug(message, ...args) {
|
|
483
|
+
process.stderr.write(`[${prefix}] DEBUG ${message} ${formatArgs(args)}
|
|
484
|
+
`);
|
|
485
|
+
},
|
|
486
|
+
info(message, ...args) {
|
|
487
|
+
process.stderr.write(`[${prefix}] INFO ${message} ${formatArgs(args)}
|
|
488
|
+
`);
|
|
489
|
+
},
|
|
490
|
+
warn(message, ...args) {
|
|
491
|
+
process.stderr.write(`[${prefix}] WARN ${message} ${formatArgs(args)}
|
|
492
|
+
`);
|
|
493
|
+
},
|
|
494
|
+
error(message, ...args) {
|
|
495
|
+
process.stderr.write(`[${prefix}] ERROR ${message} ${formatArgs(args)}
|
|
496
|
+
`);
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
457
501
|
// src/auth/AuthStateManager.ts
|
|
458
502
|
import { EventEmitter } from "events";
|
|
459
503
|
var AuthStateManager = class {
|
|
@@ -616,6 +660,7 @@ var AuthCore = class {
|
|
|
616
660
|
this.state = opts.stateManager ?? new AuthStateManager();
|
|
617
661
|
this.tokenStore = opts.tokenStore;
|
|
618
662
|
this.accessControl = opts.accessControl;
|
|
663
|
+
this.logger = opts.logger ?? noopLogger;
|
|
619
664
|
}
|
|
620
665
|
/** Snapshot of every account, the active provider, and the last error. */
|
|
621
666
|
status() {
|
|
@@ -637,16 +682,28 @@ var AuthCore = class {
|
|
|
637
682
|
*/
|
|
638
683
|
async login(provider, ui, shouldContinue) {
|
|
639
684
|
const providerImpl = this.registry.get(provider);
|
|
685
|
+
this.logger.info("[AuthCore] Starting device-flow login", { provider });
|
|
640
686
|
try {
|
|
641
687
|
const initial = await providerImpl.requestDeviceFlow();
|
|
642
688
|
await ui(initial);
|
|
689
|
+
if (!initial.deviceCode) {
|
|
690
|
+
this.logger.error("[AuthCore] Provider did not return deviceCode", provider);
|
|
691
|
+
throw new Error("Auth provider did not return deviceCode for device flow completion");
|
|
692
|
+
}
|
|
643
693
|
const session = await providerImpl.completeDeviceFlow(
|
|
644
|
-
initial.
|
|
645
|
-
shouldContinue
|
|
694
|
+
initial.deviceCode,
|
|
695
|
+
shouldContinue,
|
|
696
|
+
initial.pollIntervalMs
|
|
646
697
|
);
|
|
647
|
-
|
|
698
|
+
const result = await this.persistSession(providerImpl, session);
|
|
699
|
+
this.logger.info("[AuthCore] Device-flow login completed", { provider });
|
|
700
|
+
return result;
|
|
648
701
|
} catch (err) {
|
|
649
|
-
|
|
702
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
703
|
+
this.logger.error("[AuthCore] Device-flow login failed", message, {
|
|
704
|
+
provider
|
|
705
|
+
});
|
|
706
|
+
this.state.recordError(message);
|
|
650
707
|
throw err;
|
|
651
708
|
}
|
|
652
709
|
}
|
|
@@ -671,7 +728,10 @@ var AuthCore = class {
|
|
|
671
728
|
if (!provider) return null;
|
|
672
729
|
const account = this.state.getActiveAccount();
|
|
673
730
|
if (!account) return null;
|
|
674
|
-
const envelope = await this.tokenStore.get({
|
|
731
|
+
const envelope = await this.tokenStore.get({
|
|
732
|
+
provider,
|
|
733
|
+
accountId: account.id
|
|
734
|
+
});
|
|
675
735
|
if (!envelope) return null;
|
|
676
736
|
return { provider, account, token: envelope.token };
|
|
677
737
|
}
|
|
@@ -681,9 +741,15 @@ var AuthCore = class {
|
|
|
681
741
|
* every account.
|
|
682
742
|
*/
|
|
683
743
|
async logout(provider) {
|
|
744
|
+
this.logger.info("[AuthCore] Logout requested", {
|
|
745
|
+
provider: provider ?? "all"
|
|
746
|
+
});
|
|
684
747
|
if (!provider) {
|
|
685
748
|
for (const account of this.state.listAccounts()) {
|
|
686
|
-
await this.tokenStore.delete({
|
|
749
|
+
await this.tokenStore.delete({
|
|
750
|
+
provider: account.provider,
|
|
751
|
+
accountId: account.id
|
|
752
|
+
});
|
|
687
753
|
this.state.removeAccount(account.provider, account.id);
|
|
688
754
|
}
|
|
689
755
|
this.state.clearAll();
|
|
@@ -758,6 +824,10 @@ var AuthCore = class {
|
|
|
758
824
|
avatarUrl: meta.avatarUrl,
|
|
759
825
|
expiresAt: meta.expiresAt ?? expiresAt
|
|
760
826
|
};
|
|
827
|
+
this.logger.debug("[AuthCore] Persisting session", {
|
|
828
|
+
provider: meta.provider,
|
|
829
|
+
accountId: meta.id
|
|
830
|
+
});
|
|
761
831
|
await this.tokenStore.set({ provider: meta.provider, accountId: meta.id }, session.token, {
|
|
762
832
|
expiresAt
|
|
763
833
|
});
|
|
@@ -860,11 +930,13 @@ var GitHubAuthProvider = class {
|
|
|
860
930
|
userUrl: config.userUrl ?? DEFAULT_USER_URL,
|
|
861
931
|
scope: config.scope ?? DEFAULT_SCOPE,
|
|
862
932
|
minPollIntervalMs: config.minPollIntervalMs ?? 1e3,
|
|
863
|
-
maxPollIntervalMs: config.maxPollIntervalMs ??
|
|
933
|
+
maxPollIntervalMs: config.maxPollIntervalMs ?? 6e4,
|
|
864
934
|
maxWaitMs: config.maxWaitMs,
|
|
865
935
|
fetchImpl: config.fetchImpl ?? fetch,
|
|
866
936
|
deviceCodeRetryBaseDelayMs: config.deviceCodeRetryBaseDelayMs ?? DEFAULT_DEVICE_CODE_RETRY_BASE_DELAY_MS
|
|
867
937
|
};
|
|
938
|
+
this.logger = config.logger ?? noopLogger;
|
|
939
|
+
this.sleepImpl = config.sleepImpl ?? sleep;
|
|
868
940
|
}
|
|
869
941
|
async requestDeviceFlow(opts) {
|
|
870
942
|
const body = JSON.stringify({
|
|
@@ -873,6 +945,10 @@ var GitHubAuthProvider = class {
|
|
|
873
945
|
});
|
|
874
946
|
let lastNetworkError;
|
|
875
947
|
for (let attempt = 1; attempt <= DEVICE_CODE_MAX_ATTEMPTS; attempt++) {
|
|
948
|
+
this.logger.debug("[GitHubAuthProvider] Requesting device code", {
|
|
949
|
+
attempt,
|
|
950
|
+
maxAttempts: DEVICE_CODE_MAX_ATTEMPTS
|
|
951
|
+
});
|
|
876
952
|
let resp;
|
|
877
953
|
try {
|
|
878
954
|
resp = await this.cfg.fetchImpl(this.cfg.deviceCodeUrl, {
|
|
@@ -886,45 +962,99 @@ var GitHubAuthProvider = class {
|
|
|
886
962
|
});
|
|
887
963
|
} catch (error) {
|
|
888
964
|
if (!isTransientNetworkError(error) || attempt === DEVICE_CODE_MAX_ATTEMPTS) {
|
|
965
|
+
this.logger.error(
|
|
966
|
+
"[GitHubAuthProvider] Device-code request failed (non-retryable)",
|
|
967
|
+
error instanceof Error ? error.message : String(error),
|
|
968
|
+
{ attempt }
|
|
969
|
+
);
|
|
889
970
|
throw error;
|
|
890
971
|
}
|
|
972
|
+
const delayMs = this.cfg.deviceCodeRetryBaseDelayMs * 2 ** (attempt - 1);
|
|
973
|
+
this.logger.warn(
|
|
974
|
+
"[GitHubAuthProvider] Transient network error requesting device code, retrying",
|
|
975
|
+
{
|
|
976
|
+
attempt,
|
|
977
|
+
delayMs,
|
|
978
|
+
error: error instanceof Error ? error.message : String(error)
|
|
979
|
+
}
|
|
980
|
+
);
|
|
891
981
|
lastNetworkError = error;
|
|
892
|
-
await
|
|
982
|
+
await this.sleepImpl(delayMs);
|
|
893
983
|
continue;
|
|
894
984
|
}
|
|
895
985
|
if (!resp.ok) {
|
|
986
|
+
this.logger.error(
|
|
987
|
+
"[GitHubAuthProvider] Device-code request rejected by GitHub",
|
|
988
|
+
`HTTP ${resp.status}`
|
|
989
|
+
);
|
|
896
990
|
throw new Error(`GitHub device-code request failed: HTTP ${resp.status}`);
|
|
897
991
|
}
|
|
898
992
|
const data = await resp.json();
|
|
899
993
|
if (!data.device_code || !data.user_code || !data.verification_uri) {
|
|
994
|
+
this.logger.error(
|
|
995
|
+
"[GitHubAuthProvider] Device-code response missing required fields",
|
|
996
|
+
JSON.stringify(Object.keys(data))
|
|
997
|
+
);
|
|
900
998
|
throw new Error("GitHub device-code response missing required fields");
|
|
901
999
|
}
|
|
1000
|
+
this.logger.info("[GitHubAuthProvider] Device code obtained", {
|
|
1001
|
+
userCode: data.user_code,
|
|
1002
|
+
verificationUri: data.verification_uri,
|
|
1003
|
+
expiresInSec: data.expires_in,
|
|
1004
|
+
pollIntervalSec: data.interval
|
|
1005
|
+
});
|
|
902
1006
|
return {
|
|
903
1007
|
provider: this.providerId,
|
|
1008
|
+
deviceCode: data.device_code,
|
|
904
1009
|
userCode: data.user_code,
|
|
905
1010
|
verificationUrl: data.verification_uri,
|
|
906
1011
|
expiresAt: Date.now() + data.expires_in * 1e3,
|
|
1012
|
+
pollIntervalMs: data.interval * 1e3,
|
|
907
1013
|
message: `Open ${data.verification_uri} and enter ${data.user_code}`
|
|
908
1014
|
};
|
|
909
1015
|
}
|
|
910
1016
|
throw lastNetworkError ?? new Error("GitHub device-code request failed");
|
|
911
1017
|
}
|
|
912
|
-
async completeDeviceFlow(deviceCode, shouldContinue) {
|
|
1018
|
+
async completeDeviceFlow(deviceCode, shouldContinue, initialPollIntervalMs) {
|
|
913
1019
|
const start = Date.now();
|
|
914
|
-
let pollIntervalMs = this.cfg.minPollIntervalMs;
|
|
1020
|
+
let pollIntervalMs = Math.max(this.cfg.minPollIntervalMs, initialPollIntervalMs ?? 0);
|
|
915
1021
|
let consecutiveSlowDown = 0;
|
|
1022
|
+
let pollCount = 0;
|
|
1023
|
+
this.logger.info("[GitHubAuthProvider] Starting device-flow polling", {
|
|
1024
|
+
initialPollIntervalMs: pollIntervalMs,
|
|
1025
|
+
maxWaitMs: this.cfg.maxWaitMs
|
|
1026
|
+
});
|
|
916
1027
|
while (true) {
|
|
917
1028
|
if (shouldContinue && !shouldContinue()) {
|
|
1029
|
+
this.logger.info("[GitHubAuthProvider] Device flow cancelled by caller", {
|
|
1030
|
+
elapsedMs: Date.now() - start,
|
|
1031
|
+
pollCount
|
|
1032
|
+
});
|
|
918
1033
|
throw new Error("GitHub device flow cancelled by caller");
|
|
919
1034
|
}
|
|
920
1035
|
const elapsed = Date.now() - start;
|
|
921
1036
|
if (this.cfg.maxWaitMs !== void 0 && elapsed > this.cfg.maxWaitMs) {
|
|
1037
|
+
this.logger.warn("[GitHubAuthProvider] Device flow exceeded max wait time", {
|
|
1038
|
+
elapsedMs: elapsed,
|
|
1039
|
+
maxWaitMs: this.cfg.maxWaitMs,
|
|
1040
|
+
pollCount
|
|
1041
|
+
});
|
|
922
1042
|
throw new Error("GitHub device flow exceeded max wait time");
|
|
923
1043
|
}
|
|
924
|
-
await
|
|
1044
|
+
await this.sleepImpl(pollIntervalMs);
|
|
925
1045
|
if (shouldContinue && !shouldContinue()) {
|
|
1046
|
+
this.logger.info("[GitHubAuthProvider] Device flow cancelled by caller", {
|
|
1047
|
+
elapsedMs: Date.now() - start,
|
|
1048
|
+
pollCount
|
|
1049
|
+
});
|
|
926
1050
|
throw new Error("GitHub device flow cancelled by caller");
|
|
927
1051
|
}
|
|
1052
|
+
pollCount++;
|
|
1053
|
+
this.logger.debug("[GitHubAuthProvider] Polling for authorization", {
|
|
1054
|
+
pollCount,
|
|
1055
|
+
elapsedMs: Date.now() - start,
|
|
1056
|
+
pollIntervalMs
|
|
1057
|
+
});
|
|
928
1058
|
let resp;
|
|
929
1059
|
try {
|
|
930
1060
|
resp = await this.cfg.fetchImpl(this.cfg.tokenUrl, {
|
|
@@ -942,19 +1072,59 @@ var GitHubAuthProvider = class {
|
|
|
942
1072
|
});
|
|
943
1073
|
} catch (error) {
|
|
944
1074
|
if (!isTransientNetworkError(error)) {
|
|
1075
|
+
this.logger.error(
|
|
1076
|
+
"[GitHubAuthProvider] Non-retryable error while polling token endpoint",
|
|
1077
|
+
error instanceof Error ? error.message : String(error),
|
|
1078
|
+
{ pollCount }
|
|
1079
|
+
);
|
|
945
1080
|
throw error;
|
|
946
1081
|
}
|
|
947
|
-
|
|
1082
|
+
const nextPollIntervalMs = Math.min(
|
|
1083
|
+
Math.round(pollIntervalMs * 1.5),
|
|
1084
|
+
this.cfg.maxPollIntervalMs
|
|
1085
|
+
);
|
|
1086
|
+
this.logger.warn(
|
|
1087
|
+
"[GitHubAuthProvider] Transient network error while polling, backing off",
|
|
1088
|
+
{
|
|
1089
|
+
pollCount,
|
|
1090
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1091
|
+
previousPollIntervalMs: pollIntervalMs,
|
|
1092
|
+
nextPollIntervalMs
|
|
1093
|
+
}
|
|
1094
|
+
);
|
|
1095
|
+
pollIntervalMs = nextPollIntervalMs;
|
|
948
1096
|
continue;
|
|
949
1097
|
}
|
|
950
1098
|
if (!resp.ok) {
|
|
951
|
-
|
|
1099
|
+
const nextPollIntervalMs = Math.min(
|
|
1100
|
+
Math.round(pollIntervalMs * 1.5),
|
|
1101
|
+
this.cfg.maxPollIntervalMs
|
|
1102
|
+
);
|
|
1103
|
+
this.logger.warn(
|
|
1104
|
+
"[GitHubAuthProvider] Token endpoint returned non-OK status, backing off",
|
|
1105
|
+
{
|
|
1106
|
+
pollCount,
|
|
1107
|
+
status: resp.status,
|
|
1108
|
+
previousPollIntervalMs: pollIntervalMs,
|
|
1109
|
+
nextPollIntervalMs
|
|
1110
|
+
}
|
|
1111
|
+
);
|
|
1112
|
+
pollIntervalMs = nextPollIntervalMs;
|
|
952
1113
|
continue;
|
|
953
1114
|
}
|
|
954
1115
|
const data = await resp.json();
|
|
955
1116
|
if (data.access_token) {
|
|
1117
|
+
this.logger.info("[GitHubAuthProvider] Authorization granted, fetching user profile", {
|
|
1118
|
+
pollCount,
|
|
1119
|
+
elapsedMs: Date.now() - start
|
|
1120
|
+
});
|
|
956
1121
|
const rawUser = await this.fetchGitHubUser(data.access_token);
|
|
957
1122
|
const email = await this.resolveEmail(data.access_token, rawUser);
|
|
1123
|
+
this.logger.info("[GitHubAuthProvider] Device-flow login completed", {
|
|
1124
|
+
pollCount,
|
|
1125
|
+
elapsedMs: Date.now() - start,
|
|
1126
|
+
login: rawUser.login
|
|
1127
|
+
});
|
|
958
1128
|
return {
|
|
959
1129
|
token: data.access_token,
|
|
960
1130
|
refreshToken: data.refresh_token,
|
|
@@ -969,22 +1139,42 @@ var GitHubAuthProvider = class {
|
|
|
969
1139
|
};
|
|
970
1140
|
}
|
|
971
1141
|
if (data.error === "authorization_pending") {
|
|
1142
|
+
this.logger.debug("[GitHubAuthProvider] Authorization still pending", {
|
|
1143
|
+
pollCount,
|
|
1144
|
+
elapsedMs: Date.now() - start
|
|
1145
|
+
});
|
|
972
1146
|
continue;
|
|
973
1147
|
}
|
|
974
1148
|
if (data.error === "slow_down") {
|
|
975
1149
|
consecutiveSlowDown++;
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1150
|
+
const nextPollIntervalMs = Math.min(pollIntervalMs + 5e3, this.cfg.maxPollIntervalMs);
|
|
1151
|
+
this.logger.warn("[GitHubAuthProvider] GitHub requested slower polling (slow_down)", {
|
|
1152
|
+
pollCount,
|
|
1153
|
+
consecutiveSlowDown,
|
|
1154
|
+
previousPollIntervalMs: pollIntervalMs,
|
|
1155
|
+
nextPollIntervalMs
|
|
1156
|
+
});
|
|
1157
|
+
pollIntervalMs = nextPollIntervalMs;
|
|
980
1158
|
continue;
|
|
981
1159
|
}
|
|
982
1160
|
if (data.error === "access_denied") {
|
|
1161
|
+
this.logger.warn("[GitHubAuthProvider] User denied authorization", {
|
|
1162
|
+
pollCount
|
|
1163
|
+
});
|
|
983
1164
|
throw new Error("User denied authorization");
|
|
984
1165
|
}
|
|
985
1166
|
if (data.error === "expired_token") {
|
|
1167
|
+
this.logger.warn(
|
|
1168
|
+
"[GitHubAuthProvider] Device code expired before authorization completed",
|
|
1169
|
+
{ pollCount, elapsedMs: Date.now() - start }
|
|
1170
|
+
);
|
|
986
1171
|
throw new Error("GitHub device code expired \u2014 restart the flow");
|
|
987
1172
|
}
|
|
1173
|
+
this.logger.error(
|
|
1174
|
+
"[GitHubAuthProvider] Unexpected device-flow error from token endpoint",
|
|
1175
|
+
data.error ?? "unknown",
|
|
1176
|
+
{ pollCount }
|
|
1177
|
+
);
|
|
988
1178
|
throw new Error(`GitHub device flow error: ${data.error ?? "unknown"}`);
|
|
989
1179
|
}
|
|
990
1180
|
}
|
|
@@ -1866,10 +2056,20 @@ function isRecord(value) {
|
|
|
1866
2056
|
function isNodeError(value) {
|
|
1867
2057
|
return value instanceof Error && typeof value.code === "string";
|
|
1868
2058
|
}
|
|
2059
|
+
var LOCK_PID_FILE = "pid";
|
|
2060
|
+
function isProcessAlive(pid) {
|
|
2061
|
+
try {
|
|
2062
|
+
process.kill(pid, 0);
|
|
2063
|
+
return true;
|
|
2064
|
+
} catch {
|
|
2065
|
+
return false;
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
1869
2068
|
var FileLock = class {
|
|
1870
2069
|
constructor(filePath, timeoutMs, retryMs) {
|
|
1871
2070
|
this.acquired = false;
|
|
1872
2071
|
this.dirPath = `${filePath}.lock`;
|
|
2072
|
+
this.pidFilePath = path3.join(this.dirPath, LOCK_PID_FILE);
|
|
1873
2073
|
this.timeoutMs = timeoutMs;
|
|
1874
2074
|
this.retryMs = retryMs;
|
|
1875
2075
|
}
|
|
@@ -1878,12 +2078,18 @@ var FileLock = class {
|
|
|
1878
2078
|
while (true) {
|
|
1879
2079
|
try {
|
|
1880
2080
|
await fsp.mkdir(this.dirPath, { mode: LOCK_DIR_MODE });
|
|
2081
|
+
await fsp.writeFile(this.pidFilePath, String(process.pid), "utf8").catch(() => void 0);
|
|
1881
2082
|
this.acquired = true;
|
|
1882
2083
|
return;
|
|
1883
2084
|
} catch (err) {
|
|
1884
2085
|
if (!isNodeError(err) || err.code !== "EEXIST") {
|
|
1885
2086
|
throw err;
|
|
1886
2087
|
}
|
|
2088
|
+
const stale = await this.isStaleLock();
|
|
2089
|
+
if (stale) {
|
|
2090
|
+
await fsp.rm(this.dirPath, { recursive: true, force: true });
|
|
2091
|
+
continue;
|
|
2092
|
+
}
|
|
1887
2093
|
if (Date.now() - start >= this.timeoutMs) {
|
|
1888
2094
|
throw new Error(`IdentityStore lock acquisition timed out for ${this.dirPath}`);
|
|
1889
2095
|
}
|
|
@@ -1891,6 +2097,16 @@ var FileLock = class {
|
|
|
1891
2097
|
}
|
|
1892
2098
|
}
|
|
1893
2099
|
}
|
|
2100
|
+
async isStaleLock() {
|
|
2101
|
+
try {
|
|
2102
|
+
const pidStr = await fsp.readFile(this.pidFilePath, "utf8");
|
|
2103
|
+
const pid = Number.parseInt(pidStr.trim(), 10);
|
|
2104
|
+
if (!Number.isFinite(pid) || pid <= 0) return true;
|
|
2105
|
+
return !isProcessAlive(pid);
|
|
2106
|
+
} catch {
|
|
2107
|
+
return true;
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
1894
2110
|
async release() {
|
|
1895
2111
|
if (!this.acquired) return;
|
|
1896
2112
|
this.acquired = false;
|
|
@@ -2319,6 +2535,14 @@ var TOOL_CHECK_TIMEOUT_MS = {
|
|
|
2319
2535
|
pnpm: 15e3,
|
|
2320
2536
|
nrm: 15e3
|
|
2321
2537
|
};
|
|
2538
|
+
var POSIX_LOGIN_SHELL_FALLBACK_TOOLS = /* @__PURE__ */ new Set([
|
|
2539
|
+
"npm",
|
|
2540
|
+
"pnpm",
|
|
2541
|
+
"nrm",
|
|
2542
|
+
"node",
|
|
2543
|
+
"dotnet",
|
|
2544
|
+
"rtk"
|
|
2545
|
+
]);
|
|
2322
2546
|
var ERROR_CODE_NOT_FOUND = 127;
|
|
2323
2547
|
var ERROR_CODE_TIMEOUT = "ETIMEDOUT";
|
|
2324
2548
|
var EnvironmentInspector = class {
|
|
@@ -2362,6 +2586,23 @@ var EnvironmentInspector = class {
|
|
|
2362
2586
|
timeoutMs: this.getToolTimeout(toolName)
|
|
2363
2587
|
});
|
|
2364
2588
|
return result.stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
|
|
2589
|
+
} catch {
|
|
2590
|
+
if (this.shouldUsePosixLoginShellFallback(toolName)) {
|
|
2591
|
+
return this.getToolPathFromLoginShell(toolName);
|
|
2592
|
+
}
|
|
2593
|
+
return void 0;
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
async getToolPathFromLoginShell(toolName) {
|
|
2597
|
+
try {
|
|
2598
|
+
const result = await this.runCommandFn("/bin/bash", {
|
|
2599
|
+
args: [
|
|
2600
|
+
"-lc",
|
|
2601
|
+
'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; command -v ' + toolName
|
|
2602
|
+
],
|
|
2603
|
+
timeoutMs: this.getToolTimeout(toolName)
|
|
2604
|
+
});
|
|
2605
|
+
return result.stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
|
|
2365
2606
|
} catch {
|
|
2366
2607
|
return void 0;
|
|
2367
2608
|
}
|
|
@@ -2386,12 +2627,33 @@ var EnvironmentInspector = class {
|
|
|
2386
2627
|
}
|
|
2387
2628
|
}
|
|
2388
2629
|
async getToolVersion(toolName) {
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2630
|
+
try {
|
|
2631
|
+
const invocation = await this.getVersionInvocation(toolName);
|
|
2632
|
+
const result = await this.runCommandFn(invocation.command, {
|
|
2633
|
+
args: invocation.args,
|
|
2634
|
+
timeoutMs: this.getToolTimeout(toolName)
|
|
2635
|
+
});
|
|
2636
|
+
return this.parseVersion(toolName, result.stdout || result.stderr);
|
|
2637
|
+
} catch (error) {
|
|
2638
|
+
if (!this.shouldUsePosixLoginShellFallback(toolName)) {
|
|
2639
|
+
throw error;
|
|
2640
|
+
}
|
|
2641
|
+
const fallbackResult = await this.runCommandFn("/bin/bash", {
|
|
2642
|
+
args: [
|
|
2643
|
+
"-lc",
|
|
2644
|
+
`export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; ${toolName} --version`
|
|
2645
|
+
],
|
|
2646
|
+
timeoutMs: this.getToolTimeout(toolName)
|
|
2647
|
+
});
|
|
2648
|
+
const fallbackOutput = (fallbackResult.stdout || fallbackResult.stderr).trim();
|
|
2649
|
+
if (!fallbackOutput) {
|
|
2650
|
+
throw error;
|
|
2651
|
+
}
|
|
2652
|
+
return this.parseVersion(toolName, fallbackOutput);
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
shouldUsePosixLoginShellFallback(toolName) {
|
|
2656
|
+
return this.platform !== "win32" && POSIX_LOGIN_SHELL_FALLBACK_TOOLS.has(toolName);
|
|
2395
2657
|
}
|
|
2396
2658
|
async checkNvm() {
|
|
2397
2659
|
if (this.platform === "win32") {
|
|
@@ -2441,7 +2703,7 @@ var EnvironmentInspector = class {
|
|
|
2441
2703
|
};
|
|
2442
2704
|
}
|
|
2443
2705
|
try {
|
|
2444
|
-
const result = await this.runCommandFn(
|
|
2706
|
+
const result = await this.runCommandFn(dotnetPath, {
|
|
2445
2707
|
args: ["nuget", "list", "source"],
|
|
2446
2708
|
timeoutMs: this.getToolTimeout("nuget")
|
|
2447
2709
|
});
|
|
@@ -2977,50 +3239,6 @@ function detectIndent(text) {
|
|
|
2977
3239
|
return 2;
|
|
2978
3240
|
}
|
|
2979
3241
|
|
|
2980
|
-
// src/logger.ts
|
|
2981
|
-
var noopLogger = {
|
|
2982
|
-
debug() {
|
|
2983
|
-
},
|
|
2984
|
-
info() {
|
|
2985
|
-
},
|
|
2986
|
-
warn() {
|
|
2987
|
-
},
|
|
2988
|
-
error() {
|
|
2989
|
-
}
|
|
2990
|
-
};
|
|
2991
|
-
function formatArgs(args) {
|
|
2992
|
-
return args.map((arg) => {
|
|
2993
|
-
if (typeof arg === "string") {
|
|
2994
|
-
return arg;
|
|
2995
|
-
}
|
|
2996
|
-
try {
|
|
2997
|
-
return JSON.stringify(arg);
|
|
2998
|
-
} catch {
|
|
2999
|
-
return String(arg);
|
|
3000
|
-
}
|
|
3001
|
-
}).join(" ");
|
|
3002
|
-
}
|
|
3003
|
-
function createConsoleLogger(prefix = "serviceme") {
|
|
3004
|
-
return {
|
|
3005
|
-
debug(message, ...args) {
|
|
3006
|
-
process.stderr.write(`[${prefix}] DEBUG ${message} ${formatArgs(args)}
|
|
3007
|
-
`);
|
|
3008
|
-
},
|
|
3009
|
-
info(message, ...args) {
|
|
3010
|
-
process.stderr.write(`[${prefix}] INFO ${message} ${formatArgs(args)}
|
|
3011
|
-
`);
|
|
3012
|
-
},
|
|
3013
|
-
warn(message, ...args) {
|
|
3014
|
-
process.stderr.write(`[${prefix}] WARN ${message} ${formatArgs(args)}
|
|
3015
|
-
`);
|
|
3016
|
-
},
|
|
3017
|
-
error(message, ...args) {
|
|
3018
|
-
process.stderr.write(`[${prefix}] ERROR ${message} ${formatArgs(args)}
|
|
3019
|
-
`);
|
|
3020
|
-
}
|
|
3021
|
-
};
|
|
3022
|
-
}
|
|
3023
|
-
|
|
3024
3242
|
// src/paths/serverProxyGlobal.ts
|
|
3025
3243
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3026
3244
|
import * as fs6 from "fs/promises";
|
|
@@ -3176,7 +3394,7 @@ import {
|
|
|
3176
3394
|
// src/utils/fileUtils.ts
|
|
3177
3395
|
import { constants, createWriteStream } from "fs";
|
|
3178
3396
|
import { access as access5, copyFile, lstat, mkdir as mkdir5, readdir as readdir3, rename as rename4, rm as rm3 } from "fs/promises";
|
|
3179
|
-
import { dirname as dirname7, join as
|
|
3397
|
+
import { dirname as dirname7, join as join8 } from "path";
|
|
3180
3398
|
import yauzl from "yauzl";
|
|
3181
3399
|
var unzipFile = (zipPath, dest) => {
|
|
3182
3400
|
return new Promise((resolve3, reject) => {
|
|
@@ -3186,11 +3404,11 @@ var unzipFile = (zipPath, dest) => {
|
|
|
3186
3404
|
zipfile.readEntry();
|
|
3187
3405
|
zipfile.on("entry", (entry) => {
|
|
3188
3406
|
if (/\/$/.test(entry.fileName)) {
|
|
3189
|
-
void mkdir5(
|
|
3407
|
+
void mkdir5(join8(dest, entry.fileName), { recursive: true }).then(() => {
|
|
3190
3408
|
zipfile.readEntry();
|
|
3191
3409
|
}).catch(reject);
|
|
3192
3410
|
} else {
|
|
3193
|
-
const outputPath =
|
|
3411
|
+
const outputPath = join8(dest, entry.fileName);
|
|
3194
3412
|
void mkdir5(dirname7(outputPath), { recursive: true }).then(() => {
|
|
3195
3413
|
zipfile.openReadStream(
|
|
3196
3414
|
entry,
|
|
@@ -3239,7 +3457,7 @@ var mergeEntry = async (sourcePath, destPath, overwrite) => {
|
|
|
3239
3457
|
await mkdir5(destPath, { recursive: true });
|
|
3240
3458
|
const children = await readdir3(sourcePath);
|
|
3241
3459
|
for (const child of children) {
|
|
3242
|
-
await mergeEntry(
|
|
3460
|
+
await mergeEntry(join8(sourcePath, child), join8(destPath, child), overwrite);
|
|
3243
3461
|
}
|
|
3244
3462
|
await rm3(sourcePath, { recursive: true, force: true });
|
|
3245
3463
|
return;
|
|
@@ -3262,8 +3480,8 @@ var moveFiles = async (sourceDir, destDir, overwrite = false) => {
|
|
|
3262
3480
|
await mkdir5(destDir, { recursive: true });
|
|
3263
3481
|
const files = await readdir3(sourceDir);
|
|
3264
3482
|
for (const file of files) {
|
|
3265
|
-
const sourceFile =
|
|
3266
|
-
const destFile =
|
|
3483
|
+
const sourceFile = join8(sourceDir, file);
|
|
3484
|
+
const destFile = join8(destDir, file);
|
|
3267
3485
|
if (!overwrite) {
|
|
3268
3486
|
try {
|
|
3269
3487
|
await access5(destFile, constants.F_OK);
|
|
@@ -6678,10 +6896,19 @@ function coercePersistedToolbox(parsed) {
|
|
|
6678
6896
|
function isNodeError2(value) {
|
|
6679
6897
|
return value instanceof Error && typeof value.code === "string";
|
|
6680
6898
|
}
|
|
6899
|
+
function isProcessAlive2(pid) {
|
|
6900
|
+
try {
|
|
6901
|
+
process.kill(pid, 0);
|
|
6902
|
+
return true;
|
|
6903
|
+
} catch {
|
|
6904
|
+
return false;
|
|
6905
|
+
}
|
|
6906
|
+
}
|
|
6681
6907
|
var ToolboxFileLock = class {
|
|
6682
6908
|
constructor(filePath, timeoutMs, retryMs) {
|
|
6683
6909
|
this.acquired = false;
|
|
6684
6910
|
this.dirPath = `${filePath}.lock`;
|
|
6911
|
+
this.pidFilePath = path25.join(this.dirPath, "pid");
|
|
6685
6912
|
this.timeoutMs = timeoutMs;
|
|
6686
6913
|
this.retryMs = retryMs;
|
|
6687
6914
|
}
|
|
@@ -6690,10 +6917,16 @@ var ToolboxFileLock = class {
|
|
|
6690
6917
|
while (true) {
|
|
6691
6918
|
try {
|
|
6692
6919
|
await fsp2.mkdir(this.dirPath, { mode: LOCK_DIR_MODE2 });
|
|
6920
|
+
await fsp2.writeFile(this.pidFilePath, String(process.pid), "utf8").catch(() => void 0);
|
|
6693
6921
|
this.acquired = true;
|
|
6694
6922
|
return;
|
|
6695
6923
|
} catch (err) {
|
|
6696
6924
|
if (!isNodeError2(err) || err.code !== "EEXIST") throw err;
|
|
6925
|
+
const stale = await this.isStaleLock();
|
|
6926
|
+
if (stale) {
|
|
6927
|
+
await fsp2.rm(this.dirPath, { recursive: true, force: true });
|
|
6928
|
+
continue;
|
|
6929
|
+
}
|
|
6697
6930
|
if (Date.now() - start >= this.timeoutMs) {
|
|
6698
6931
|
throw new Error(`ToolboxStore lock acquisition timed out for ${this.dirPath}`);
|
|
6699
6932
|
}
|
|
@@ -6701,6 +6934,16 @@ var ToolboxFileLock = class {
|
|
|
6701
6934
|
}
|
|
6702
6935
|
}
|
|
6703
6936
|
}
|
|
6937
|
+
async isStaleLock() {
|
|
6938
|
+
try {
|
|
6939
|
+
const pidStr = await fsp2.readFile(this.pidFilePath, "utf8");
|
|
6940
|
+
const pid = Number.parseInt(pidStr.trim(), 10);
|
|
6941
|
+
if (!Number.isFinite(pid) || pid <= 0) return true;
|
|
6942
|
+
return !isProcessAlive2(pid);
|
|
6943
|
+
} catch {
|
|
6944
|
+
return true;
|
|
6945
|
+
}
|
|
6946
|
+
}
|
|
6704
6947
|
async release() {
|
|
6705
6948
|
if (!this.acquired) return;
|
|
6706
6949
|
this.acquired = false;
|