@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.js
CHANGED
|
@@ -652,6 +652,50 @@ var AccessControl = class {
|
|
|
652
652
|
}
|
|
653
653
|
};
|
|
654
654
|
|
|
655
|
+
// src/logger.ts
|
|
656
|
+
var noopLogger = {
|
|
657
|
+
debug() {
|
|
658
|
+
},
|
|
659
|
+
info() {
|
|
660
|
+
},
|
|
661
|
+
warn() {
|
|
662
|
+
},
|
|
663
|
+
error() {
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
function formatArgs(args) {
|
|
667
|
+
return args.map((arg) => {
|
|
668
|
+
if (typeof arg === "string") {
|
|
669
|
+
return arg;
|
|
670
|
+
}
|
|
671
|
+
try {
|
|
672
|
+
return JSON.stringify(arg);
|
|
673
|
+
} catch {
|
|
674
|
+
return String(arg);
|
|
675
|
+
}
|
|
676
|
+
}).join(" ");
|
|
677
|
+
}
|
|
678
|
+
function createConsoleLogger(prefix = "serviceme") {
|
|
679
|
+
return {
|
|
680
|
+
debug(message, ...args) {
|
|
681
|
+
process.stderr.write(`[${prefix}] DEBUG ${message} ${formatArgs(args)}
|
|
682
|
+
`);
|
|
683
|
+
},
|
|
684
|
+
info(message, ...args) {
|
|
685
|
+
process.stderr.write(`[${prefix}] INFO ${message} ${formatArgs(args)}
|
|
686
|
+
`);
|
|
687
|
+
},
|
|
688
|
+
warn(message, ...args) {
|
|
689
|
+
process.stderr.write(`[${prefix}] WARN ${message} ${formatArgs(args)}
|
|
690
|
+
`);
|
|
691
|
+
},
|
|
692
|
+
error(message, ...args) {
|
|
693
|
+
process.stderr.write(`[${prefix}] ERROR ${message} ${formatArgs(args)}
|
|
694
|
+
`);
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
|
|
655
699
|
// src/auth/AuthStateManager.ts
|
|
656
700
|
var import_node_events = require("events");
|
|
657
701
|
var AuthStateManager = class {
|
|
@@ -814,6 +858,7 @@ var AuthCore = class {
|
|
|
814
858
|
this.state = opts.stateManager ?? new AuthStateManager();
|
|
815
859
|
this.tokenStore = opts.tokenStore;
|
|
816
860
|
this.accessControl = opts.accessControl;
|
|
861
|
+
this.logger = opts.logger ?? noopLogger;
|
|
817
862
|
}
|
|
818
863
|
/** Snapshot of every account, the active provider, and the last error. */
|
|
819
864
|
status() {
|
|
@@ -835,16 +880,28 @@ var AuthCore = class {
|
|
|
835
880
|
*/
|
|
836
881
|
async login(provider, ui, shouldContinue) {
|
|
837
882
|
const providerImpl = this.registry.get(provider);
|
|
883
|
+
this.logger.info("[AuthCore] Starting device-flow login", { provider });
|
|
838
884
|
try {
|
|
839
885
|
const initial = await providerImpl.requestDeviceFlow();
|
|
840
886
|
await ui(initial);
|
|
887
|
+
if (!initial.deviceCode) {
|
|
888
|
+
this.logger.error("[AuthCore] Provider did not return deviceCode", provider);
|
|
889
|
+
throw new Error("Auth provider did not return deviceCode for device flow completion");
|
|
890
|
+
}
|
|
841
891
|
const session = await providerImpl.completeDeviceFlow(
|
|
842
|
-
initial.
|
|
843
|
-
shouldContinue
|
|
892
|
+
initial.deviceCode,
|
|
893
|
+
shouldContinue,
|
|
894
|
+
initial.pollIntervalMs
|
|
844
895
|
);
|
|
845
|
-
|
|
896
|
+
const result = await this.persistSession(providerImpl, session);
|
|
897
|
+
this.logger.info("[AuthCore] Device-flow login completed", { provider });
|
|
898
|
+
return result;
|
|
846
899
|
} catch (err) {
|
|
847
|
-
|
|
900
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
901
|
+
this.logger.error("[AuthCore] Device-flow login failed", message, {
|
|
902
|
+
provider
|
|
903
|
+
});
|
|
904
|
+
this.state.recordError(message);
|
|
848
905
|
throw err;
|
|
849
906
|
}
|
|
850
907
|
}
|
|
@@ -869,7 +926,10 @@ var AuthCore = class {
|
|
|
869
926
|
if (!provider) return null;
|
|
870
927
|
const account = this.state.getActiveAccount();
|
|
871
928
|
if (!account) return null;
|
|
872
|
-
const envelope = await this.tokenStore.get({
|
|
929
|
+
const envelope = await this.tokenStore.get({
|
|
930
|
+
provider,
|
|
931
|
+
accountId: account.id
|
|
932
|
+
});
|
|
873
933
|
if (!envelope) return null;
|
|
874
934
|
return { provider, account, token: envelope.token };
|
|
875
935
|
}
|
|
@@ -879,9 +939,15 @@ var AuthCore = class {
|
|
|
879
939
|
* every account.
|
|
880
940
|
*/
|
|
881
941
|
async logout(provider) {
|
|
942
|
+
this.logger.info("[AuthCore] Logout requested", {
|
|
943
|
+
provider: provider ?? "all"
|
|
944
|
+
});
|
|
882
945
|
if (!provider) {
|
|
883
946
|
for (const account of this.state.listAccounts()) {
|
|
884
|
-
await this.tokenStore.delete({
|
|
947
|
+
await this.tokenStore.delete({
|
|
948
|
+
provider: account.provider,
|
|
949
|
+
accountId: account.id
|
|
950
|
+
});
|
|
885
951
|
this.state.removeAccount(account.provider, account.id);
|
|
886
952
|
}
|
|
887
953
|
this.state.clearAll();
|
|
@@ -956,6 +1022,10 @@ var AuthCore = class {
|
|
|
956
1022
|
avatarUrl: meta.avatarUrl,
|
|
957
1023
|
expiresAt: meta.expiresAt ?? expiresAt
|
|
958
1024
|
};
|
|
1025
|
+
this.logger.debug("[AuthCore] Persisting session", {
|
|
1026
|
+
provider: meta.provider,
|
|
1027
|
+
accountId: meta.id
|
|
1028
|
+
});
|
|
959
1029
|
await this.tokenStore.set({ provider: meta.provider, accountId: meta.id }, session.token, {
|
|
960
1030
|
expiresAt
|
|
961
1031
|
});
|
|
@@ -1058,11 +1128,13 @@ var GitHubAuthProvider = class {
|
|
|
1058
1128
|
userUrl: config.userUrl ?? DEFAULT_USER_URL,
|
|
1059
1129
|
scope: config.scope ?? DEFAULT_SCOPE,
|
|
1060
1130
|
minPollIntervalMs: config.minPollIntervalMs ?? 1e3,
|
|
1061
|
-
maxPollIntervalMs: config.maxPollIntervalMs ??
|
|
1131
|
+
maxPollIntervalMs: config.maxPollIntervalMs ?? 6e4,
|
|
1062
1132
|
maxWaitMs: config.maxWaitMs,
|
|
1063
1133
|
fetchImpl: config.fetchImpl ?? fetch,
|
|
1064
1134
|
deviceCodeRetryBaseDelayMs: config.deviceCodeRetryBaseDelayMs ?? DEFAULT_DEVICE_CODE_RETRY_BASE_DELAY_MS
|
|
1065
1135
|
};
|
|
1136
|
+
this.logger = config.logger ?? noopLogger;
|
|
1137
|
+
this.sleepImpl = config.sleepImpl ?? sleep;
|
|
1066
1138
|
}
|
|
1067
1139
|
async requestDeviceFlow(opts) {
|
|
1068
1140
|
const body = JSON.stringify({
|
|
@@ -1071,6 +1143,10 @@ var GitHubAuthProvider = class {
|
|
|
1071
1143
|
});
|
|
1072
1144
|
let lastNetworkError;
|
|
1073
1145
|
for (let attempt = 1; attempt <= DEVICE_CODE_MAX_ATTEMPTS; attempt++) {
|
|
1146
|
+
this.logger.debug("[GitHubAuthProvider] Requesting device code", {
|
|
1147
|
+
attempt,
|
|
1148
|
+
maxAttempts: DEVICE_CODE_MAX_ATTEMPTS
|
|
1149
|
+
});
|
|
1074
1150
|
let resp;
|
|
1075
1151
|
try {
|
|
1076
1152
|
resp = await this.cfg.fetchImpl(this.cfg.deviceCodeUrl, {
|
|
@@ -1084,45 +1160,99 @@ var GitHubAuthProvider = class {
|
|
|
1084
1160
|
});
|
|
1085
1161
|
} catch (error) {
|
|
1086
1162
|
if (!isTransientNetworkError(error) || attempt === DEVICE_CODE_MAX_ATTEMPTS) {
|
|
1163
|
+
this.logger.error(
|
|
1164
|
+
"[GitHubAuthProvider] Device-code request failed (non-retryable)",
|
|
1165
|
+
error instanceof Error ? error.message : String(error),
|
|
1166
|
+
{ attempt }
|
|
1167
|
+
);
|
|
1087
1168
|
throw error;
|
|
1088
1169
|
}
|
|
1170
|
+
const delayMs = this.cfg.deviceCodeRetryBaseDelayMs * 2 ** (attempt - 1);
|
|
1171
|
+
this.logger.warn(
|
|
1172
|
+
"[GitHubAuthProvider] Transient network error requesting device code, retrying",
|
|
1173
|
+
{
|
|
1174
|
+
attempt,
|
|
1175
|
+
delayMs,
|
|
1176
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1177
|
+
}
|
|
1178
|
+
);
|
|
1089
1179
|
lastNetworkError = error;
|
|
1090
|
-
await
|
|
1180
|
+
await this.sleepImpl(delayMs);
|
|
1091
1181
|
continue;
|
|
1092
1182
|
}
|
|
1093
1183
|
if (!resp.ok) {
|
|
1184
|
+
this.logger.error(
|
|
1185
|
+
"[GitHubAuthProvider] Device-code request rejected by GitHub",
|
|
1186
|
+
`HTTP ${resp.status}`
|
|
1187
|
+
);
|
|
1094
1188
|
throw new Error(`GitHub device-code request failed: HTTP ${resp.status}`);
|
|
1095
1189
|
}
|
|
1096
1190
|
const data = await resp.json();
|
|
1097
1191
|
if (!data.device_code || !data.user_code || !data.verification_uri) {
|
|
1192
|
+
this.logger.error(
|
|
1193
|
+
"[GitHubAuthProvider] Device-code response missing required fields",
|
|
1194
|
+
JSON.stringify(Object.keys(data))
|
|
1195
|
+
);
|
|
1098
1196
|
throw new Error("GitHub device-code response missing required fields");
|
|
1099
1197
|
}
|
|
1198
|
+
this.logger.info("[GitHubAuthProvider] Device code obtained", {
|
|
1199
|
+
userCode: data.user_code,
|
|
1200
|
+
verificationUri: data.verification_uri,
|
|
1201
|
+
expiresInSec: data.expires_in,
|
|
1202
|
+
pollIntervalSec: data.interval
|
|
1203
|
+
});
|
|
1100
1204
|
return {
|
|
1101
1205
|
provider: this.providerId,
|
|
1206
|
+
deviceCode: data.device_code,
|
|
1102
1207
|
userCode: data.user_code,
|
|
1103
1208
|
verificationUrl: data.verification_uri,
|
|
1104
1209
|
expiresAt: Date.now() + data.expires_in * 1e3,
|
|
1210
|
+
pollIntervalMs: data.interval * 1e3,
|
|
1105
1211
|
message: `Open ${data.verification_uri} and enter ${data.user_code}`
|
|
1106
1212
|
};
|
|
1107
1213
|
}
|
|
1108
1214
|
throw lastNetworkError ?? new Error("GitHub device-code request failed");
|
|
1109
1215
|
}
|
|
1110
|
-
async completeDeviceFlow(deviceCode, shouldContinue) {
|
|
1216
|
+
async completeDeviceFlow(deviceCode, shouldContinue, initialPollIntervalMs) {
|
|
1111
1217
|
const start = Date.now();
|
|
1112
|
-
let pollIntervalMs = this.cfg.minPollIntervalMs;
|
|
1218
|
+
let pollIntervalMs = Math.max(this.cfg.minPollIntervalMs, initialPollIntervalMs ?? 0);
|
|
1113
1219
|
let consecutiveSlowDown = 0;
|
|
1220
|
+
let pollCount = 0;
|
|
1221
|
+
this.logger.info("[GitHubAuthProvider] Starting device-flow polling", {
|
|
1222
|
+
initialPollIntervalMs: pollIntervalMs,
|
|
1223
|
+
maxWaitMs: this.cfg.maxWaitMs
|
|
1224
|
+
});
|
|
1114
1225
|
while (true) {
|
|
1115
1226
|
if (shouldContinue && !shouldContinue()) {
|
|
1227
|
+
this.logger.info("[GitHubAuthProvider] Device flow cancelled by caller", {
|
|
1228
|
+
elapsedMs: Date.now() - start,
|
|
1229
|
+
pollCount
|
|
1230
|
+
});
|
|
1116
1231
|
throw new Error("GitHub device flow cancelled by caller");
|
|
1117
1232
|
}
|
|
1118
1233
|
const elapsed = Date.now() - start;
|
|
1119
1234
|
if (this.cfg.maxWaitMs !== void 0 && elapsed > this.cfg.maxWaitMs) {
|
|
1235
|
+
this.logger.warn("[GitHubAuthProvider] Device flow exceeded max wait time", {
|
|
1236
|
+
elapsedMs: elapsed,
|
|
1237
|
+
maxWaitMs: this.cfg.maxWaitMs,
|
|
1238
|
+
pollCount
|
|
1239
|
+
});
|
|
1120
1240
|
throw new Error("GitHub device flow exceeded max wait time");
|
|
1121
1241
|
}
|
|
1122
|
-
await
|
|
1242
|
+
await this.sleepImpl(pollIntervalMs);
|
|
1123
1243
|
if (shouldContinue && !shouldContinue()) {
|
|
1244
|
+
this.logger.info("[GitHubAuthProvider] Device flow cancelled by caller", {
|
|
1245
|
+
elapsedMs: Date.now() - start,
|
|
1246
|
+
pollCount
|
|
1247
|
+
});
|
|
1124
1248
|
throw new Error("GitHub device flow cancelled by caller");
|
|
1125
1249
|
}
|
|
1250
|
+
pollCount++;
|
|
1251
|
+
this.logger.debug("[GitHubAuthProvider] Polling for authorization", {
|
|
1252
|
+
pollCount,
|
|
1253
|
+
elapsedMs: Date.now() - start,
|
|
1254
|
+
pollIntervalMs
|
|
1255
|
+
});
|
|
1126
1256
|
let resp;
|
|
1127
1257
|
try {
|
|
1128
1258
|
resp = await this.cfg.fetchImpl(this.cfg.tokenUrl, {
|
|
@@ -1140,19 +1270,59 @@ var GitHubAuthProvider = class {
|
|
|
1140
1270
|
});
|
|
1141
1271
|
} catch (error) {
|
|
1142
1272
|
if (!isTransientNetworkError(error)) {
|
|
1273
|
+
this.logger.error(
|
|
1274
|
+
"[GitHubAuthProvider] Non-retryable error while polling token endpoint",
|
|
1275
|
+
error instanceof Error ? error.message : String(error),
|
|
1276
|
+
{ pollCount }
|
|
1277
|
+
);
|
|
1143
1278
|
throw error;
|
|
1144
1279
|
}
|
|
1145
|
-
|
|
1280
|
+
const nextPollIntervalMs = Math.min(
|
|
1281
|
+
Math.round(pollIntervalMs * 1.5),
|
|
1282
|
+
this.cfg.maxPollIntervalMs
|
|
1283
|
+
);
|
|
1284
|
+
this.logger.warn(
|
|
1285
|
+
"[GitHubAuthProvider] Transient network error while polling, backing off",
|
|
1286
|
+
{
|
|
1287
|
+
pollCount,
|
|
1288
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1289
|
+
previousPollIntervalMs: pollIntervalMs,
|
|
1290
|
+
nextPollIntervalMs
|
|
1291
|
+
}
|
|
1292
|
+
);
|
|
1293
|
+
pollIntervalMs = nextPollIntervalMs;
|
|
1146
1294
|
continue;
|
|
1147
1295
|
}
|
|
1148
1296
|
if (!resp.ok) {
|
|
1149
|
-
|
|
1297
|
+
const nextPollIntervalMs = Math.min(
|
|
1298
|
+
Math.round(pollIntervalMs * 1.5),
|
|
1299
|
+
this.cfg.maxPollIntervalMs
|
|
1300
|
+
);
|
|
1301
|
+
this.logger.warn(
|
|
1302
|
+
"[GitHubAuthProvider] Token endpoint returned non-OK status, backing off",
|
|
1303
|
+
{
|
|
1304
|
+
pollCount,
|
|
1305
|
+
status: resp.status,
|
|
1306
|
+
previousPollIntervalMs: pollIntervalMs,
|
|
1307
|
+
nextPollIntervalMs
|
|
1308
|
+
}
|
|
1309
|
+
);
|
|
1310
|
+
pollIntervalMs = nextPollIntervalMs;
|
|
1150
1311
|
continue;
|
|
1151
1312
|
}
|
|
1152
1313
|
const data = await resp.json();
|
|
1153
1314
|
if (data.access_token) {
|
|
1315
|
+
this.logger.info("[GitHubAuthProvider] Authorization granted, fetching user profile", {
|
|
1316
|
+
pollCount,
|
|
1317
|
+
elapsedMs: Date.now() - start
|
|
1318
|
+
});
|
|
1154
1319
|
const rawUser = await this.fetchGitHubUser(data.access_token);
|
|
1155
1320
|
const email = await this.resolveEmail(data.access_token, rawUser);
|
|
1321
|
+
this.logger.info("[GitHubAuthProvider] Device-flow login completed", {
|
|
1322
|
+
pollCount,
|
|
1323
|
+
elapsedMs: Date.now() - start,
|
|
1324
|
+
login: rawUser.login
|
|
1325
|
+
});
|
|
1156
1326
|
return {
|
|
1157
1327
|
token: data.access_token,
|
|
1158
1328
|
refreshToken: data.refresh_token,
|
|
@@ -1167,22 +1337,42 @@ var GitHubAuthProvider = class {
|
|
|
1167
1337
|
};
|
|
1168
1338
|
}
|
|
1169
1339
|
if (data.error === "authorization_pending") {
|
|
1340
|
+
this.logger.debug("[GitHubAuthProvider] Authorization still pending", {
|
|
1341
|
+
pollCount,
|
|
1342
|
+
elapsedMs: Date.now() - start
|
|
1343
|
+
});
|
|
1170
1344
|
continue;
|
|
1171
1345
|
}
|
|
1172
1346
|
if (data.error === "slow_down") {
|
|
1173
1347
|
consecutiveSlowDown++;
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1348
|
+
const nextPollIntervalMs = Math.min(pollIntervalMs + 5e3, this.cfg.maxPollIntervalMs);
|
|
1349
|
+
this.logger.warn("[GitHubAuthProvider] GitHub requested slower polling (slow_down)", {
|
|
1350
|
+
pollCount,
|
|
1351
|
+
consecutiveSlowDown,
|
|
1352
|
+
previousPollIntervalMs: pollIntervalMs,
|
|
1353
|
+
nextPollIntervalMs
|
|
1354
|
+
});
|
|
1355
|
+
pollIntervalMs = nextPollIntervalMs;
|
|
1178
1356
|
continue;
|
|
1179
1357
|
}
|
|
1180
1358
|
if (data.error === "access_denied") {
|
|
1359
|
+
this.logger.warn("[GitHubAuthProvider] User denied authorization", {
|
|
1360
|
+
pollCount
|
|
1361
|
+
});
|
|
1181
1362
|
throw new Error("User denied authorization");
|
|
1182
1363
|
}
|
|
1183
1364
|
if (data.error === "expired_token") {
|
|
1365
|
+
this.logger.warn(
|
|
1366
|
+
"[GitHubAuthProvider] Device code expired before authorization completed",
|
|
1367
|
+
{ pollCount, elapsedMs: Date.now() - start }
|
|
1368
|
+
);
|
|
1184
1369
|
throw new Error("GitHub device code expired \u2014 restart the flow");
|
|
1185
1370
|
}
|
|
1371
|
+
this.logger.error(
|
|
1372
|
+
"[GitHubAuthProvider] Unexpected device-flow error from token endpoint",
|
|
1373
|
+
data.error ?? "unknown",
|
|
1374
|
+
{ pollCount }
|
|
1375
|
+
);
|
|
1186
1376
|
throw new Error(`GitHub device flow error: ${data.error ?? "unknown"}`);
|
|
1187
1377
|
}
|
|
1188
1378
|
}
|
|
@@ -2057,10 +2247,20 @@ function isRecord(value) {
|
|
|
2057
2247
|
function isNodeError(value) {
|
|
2058
2248
|
return value instanceof Error && typeof value.code === "string";
|
|
2059
2249
|
}
|
|
2250
|
+
var LOCK_PID_FILE = "pid";
|
|
2251
|
+
function isProcessAlive(pid) {
|
|
2252
|
+
try {
|
|
2253
|
+
process.kill(pid, 0);
|
|
2254
|
+
return true;
|
|
2255
|
+
} catch {
|
|
2256
|
+
return false;
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2060
2259
|
var FileLock = class {
|
|
2061
2260
|
constructor(filePath, timeoutMs, retryMs) {
|
|
2062
2261
|
this.acquired = false;
|
|
2063
2262
|
this.dirPath = `${filePath}.lock`;
|
|
2263
|
+
this.pidFilePath = path3.join(this.dirPath, LOCK_PID_FILE);
|
|
2064
2264
|
this.timeoutMs = timeoutMs;
|
|
2065
2265
|
this.retryMs = retryMs;
|
|
2066
2266
|
}
|
|
@@ -2069,12 +2269,18 @@ var FileLock = class {
|
|
|
2069
2269
|
while (true) {
|
|
2070
2270
|
try {
|
|
2071
2271
|
await fsp.mkdir(this.dirPath, { mode: LOCK_DIR_MODE });
|
|
2272
|
+
await fsp.writeFile(this.pidFilePath, String(process.pid), "utf8").catch(() => void 0);
|
|
2072
2273
|
this.acquired = true;
|
|
2073
2274
|
return;
|
|
2074
2275
|
} catch (err) {
|
|
2075
2276
|
if (!isNodeError(err) || err.code !== "EEXIST") {
|
|
2076
2277
|
throw err;
|
|
2077
2278
|
}
|
|
2279
|
+
const stale = await this.isStaleLock();
|
|
2280
|
+
if (stale) {
|
|
2281
|
+
await fsp.rm(this.dirPath, { recursive: true, force: true });
|
|
2282
|
+
continue;
|
|
2283
|
+
}
|
|
2078
2284
|
if (Date.now() - start >= this.timeoutMs) {
|
|
2079
2285
|
throw new Error(`IdentityStore lock acquisition timed out for ${this.dirPath}`);
|
|
2080
2286
|
}
|
|
@@ -2082,6 +2288,16 @@ var FileLock = class {
|
|
|
2082
2288
|
}
|
|
2083
2289
|
}
|
|
2084
2290
|
}
|
|
2291
|
+
async isStaleLock() {
|
|
2292
|
+
try {
|
|
2293
|
+
const pidStr = await fsp.readFile(this.pidFilePath, "utf8");
|
|
2294
|
+
const pid = Number.parseInt(pidStr.trim(), 10);
|
|
2295
|
+
if (!Number.isFinite(pid) || pid <= 0) return true;
|
|
2296
|
+
return !isProcessAlive(pid);
|
|
2297
|
+
} catch {
|
|
2298
|
+
return true;
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2085
2301
|
async release() {
|
|
2086
2302
|
if (!this.acquired) return;
|
|
2087
2303
|
this.acquired = false;
|
|
@@ -2507,6 +2723,14 @@ var TOOL_CHECK_TIMEOUT_MS = {
|
|
|
2507
2723
|
pnpm: 15e3,
|
|
2508
2724
|
nrm: 15e3
|
|
2509
2725
|
};
|
|
2726
|
+
var POSIX_LOGIN_SHELL_FALLBACK_TOOLS = /* @__PURE__ */ new Set([
|
|
2727
|
+
"npm",
|
|
2728
|
+
"pnpm",
|
|
2729
|
+
"nrm",
|
|
2730
|
+
"node",
|
|
2731
|
+
"dotnet",
|
|
2732
|
+
"rtk"
|
|
2733
|
+
]);
|
|
2510
2734
|
var ERROR_CODE_NOT_FOUND = 127;
|
|
2511
2735
|
var ERROR_CODE_TIMEOUT = "ETIMEDOUT";
|
|
2512
2736
|
var EnvironmentInspector = class {
|
|
@@ -2550,6 +2774,23 @@ var EnvironmentInspector = class {
|
|
|
2550
2774
|
timeoutMs: this.getToolTimeout(toolName)
|
|
2551
2775
|
});
|
|
2552
2776
|
return result.stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
|
|
2777
|
+
} catch {
|
|
2778
|
+
if (this.shouldUsePosixLoginShellFallback(toolName)) {
|
|
2779
|
+
return this.getToolPathFromLoginShell(toolName);
|
|
2780
|
+
}
|
|
2781
|
+
return void 0;
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
async getToolPathFromLoginShell(toolName) {
|
|
2785
|
+
try {
|
|
2786
|
+
const result = await this.runCommandFn("/bin/bash", {
|
|
2787
|
+
args: [
|
|
2788
|
+
"-lc",
|
|
2789
|
+
'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; command -v ' + toolName
|
|
2790
|
+
],
|
|
2791
|
+
timeoutMs: this.getToolTimeout(toolName)
|
|
2792
|
+
});
|
|
2793
|
+
return result.stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
|
|
2553
2794
|
} catch {
|
|
2554
2795
|
return void 0;
|
|
2555
2796
|
}
|
|
@@ -2574,12 +2815,33 @@ var EnvironmentInspector = class {
|
|
|
2574
2815
|
}
|
|
2575
2816
|
}
|
|
2576
2817
|
async getToolVersion(toolName) {
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2818
|
+
try {
|
|
2819
|
+
const invocation = await this.getVersionInvocation(toolName);
|
|
2820
|
+
const result = await this.runCommandFn(invocation.command, {
|
|
2821
|
+
args: invocation.args,
|
|
2822
|
+
timeoutMs: this.getToolTimeout(toolName)
|
|
2823
|
+
});
|
|
2824
|
+
return this.parseVersion(toolName, result.stdout || result.stderr);
|
|
2825
|
+
} catch (error) {
|
|
2826
|
+
if (!this.shouldUsePosixLoginShellFallback(toolName)) {
|
|
2827
|
+
throw error;
|
|
2828
|
+
}
|
|
2829
|
+
const fallbackResult = await this.runCommandFn("/bin/bash", {
|
|
2830
|
+
args: [
|
|
2831
|
+
"-lc",
|
|
2832
|
+
`export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; ${toolName} --version`
|
|
2833
|
+
],
|
|
2834
|
+
timeoutMs: this.getToolTimeout(toolName)
|
|
2835
|
+
});
|
|
2836
|
+
const fallbackOutput = (fallbackResult.stdout || fallbackResult.stderr).trim();
|
|
2837
|
+
if (!fallbackOutput) {
|
|
2838
|
+
throw error;
|
|
2839
|
+
}
|
|
2840
|
+
return this.parseVersion(toolName, fallbackOutput);
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
shouldUsePosixLoginShellFallback(toolName) {
|
|
2844
|
+
return this.platform !== "win32" && POSIX_LOGIN_SHELL_FALLBACK_TOOLS.has(toolName);
|
|
2583
2845
|
}
|
|
2584
2846
|
async checkNvm() {
|
|
2585
2847
|
if (this.platform === "win32") {
|
|
@@ -2629,7 +2891,7 @@ var EnvironmentInspector = class {
|
|
|
2629
2891
|
};
|
|
2630
2892
|
}
|
|
2631
2893
|
try {
|
|
2632
|
-
const result = await this.runCommandFn(
|
|
2894
|
+
const result = await this.runCommandFn(dotnetPath, {
|
|
2633
2895
|
args: ["nuget", "list", "source"],
|
|
2634
2896
|
timeoutMs: this.getToolTimeout("nuget")
|
|
2635
2897
|
});
|
|
@@ -3160,50 +3422,6 @@ function detectIndent(text) {
|
|
|
3160
3422
|
return 2;
|
|
3161
3423
|
}
|
|
3162
3424
|
|
|
3163
|
-
// src/logger.ts
|
|
3164
|
-
var noopLogger = {
|
|
3165
|
-
debug() {
|
|
3166
|
-
},
|
|
3167
|
-
info() {
|
|
3168
|
-
},
|
|
3169
|
-
warn() {
|
|
3170
|
-
},
|
|
3171
|
-
error() {
|
|
3172
|
-
}
|
|
3173
|
-
};
|
|
3174
|
-
function formatArgs(args) {
|
|
3175
|
-
return args.map((arg) => {
|
|
3176
|
-
if (typeof arg === "string") {
|
|
3177
|
-
return arg;
|
|
3178
|
-
}
|
|
3179
|
-
try {
|
|
3180
|
-
return JSON.stringify(arg);
|
|
3181
|
-
} catch {
|
|
3182
|
-
return String(arg);
|
|
3183
|
-
}
|
|
3184
|
-
}).join(" ");
|
|
3185
|
-
}
|
|
3186
|
-
function createConsoleLogger(prefix = "serviceme") {
|
|
3187
|
-
return {
|
|
3188
|
-
debug(message, ...args) {
|
|
3189
|
-
process.stderr.write(`[${prefix}] DEBUG ${message} ${formatArgs(args)}
|
|
3190
|
-
`);
|
|
3191
|
-
},
|
|
3192
|
-
info(message, ...args) {
|
|
3193
|
-
process.stderr.write(`[${prefix}] INFO ${message} ${formatArgs(args)}
|
|
3194
|
-
`);
|
|
3195
|
-
},
|
|
3196
|
-
warn(message, ...args) {
|
|
3197
|
-
process.stderr.write(`[${prefix}] WARN ${message} ${formatArgs(args)}
|
|
3198
|
-
`);
|
|
3199
|
-
},
|
|
3200
|
-
error(message, ...args) {
|
|
3201
|
-
process.stderr.write(`[${prefix}] ERROR ${message} ${formatArgs(args)}
|
|
3202
|
-
`);
|
|
3203
|
-
}
|
|
3204
|
-
};
|
|
3205
|
-
}
|
|
3206
|
-
|
|
3207
3425
|
// src/paths/serverProxyGlobal.ts
|
|
3208
3426
|
var import_node_crypto4 = require("crypto");
|
|
3209
3427
|
var fs6 = __toESM(require("fs/promises"));
|
|
@@ -6853,10 +7071,19 @@ function coercePersistedToolbox(parsed) {
|
|
|
6853
7071
|
function isNodeError2(value) {
|
|
6854
7072
|
return value instanceof Error && typeof value.code === "string";
|
|
6855
7073
|
}
|
|
7074
|
+
function isProcessAlive2(pid) {
|
|
7075
|
+
try {
|
|
7076
|
+
process.kill(pid, 0);
|
|
7077
|
+
return true;
|
|
7078
|
+
} catch {
|
|
7079
|
+
return false;
|
|
7080
|
+
}
|
|
7081
|
+
}
|
|
6856
7082
|
var ToolboxFileLock = class {
|
|
6857
7083
|
constructor(filePath, timeoutMs, retryMs) {
|
|
6858
7084
|
this.acquired = false;
|
|
6859
7085
|
this.dirPath = `${filePath}.lock`;
|
|
7086
|
+
this.pidFilePath = path25.join(this.dirPath, "pid");
|
|
6860
7087
|
this.timeoutMs = timeoutMs;
|
|
6861
7088
|
this.retryMs = retryMs;
|
|
6862
7089
|
}
|
|
@@ -6865,10 +7092,16 @@ var ToolboxFileLock = class {
|
|
|
6865
7092
|
while (true) {
|
|
6866
7093
|
try {
|
|
6867
7094
|
await fsp2.mkdir(this.dirPath, { mode: LOCK_DIR_MODE2 });
|
|
7095
|
+
await fsp2.writeFile(this.pidFilePath, String(process.pid), "utf8").catch(() => void 0);
|
|
6868
7096
|
this.acquired = true;
|
|
6869
7097
|
return;
|
|
6870
7098
|
} catch (err) {
|
|
6871
7099
|
if (!isNodeError2(err) || err.code !== "EEXIST") throw err;
|
|
7100
|
+
const stale = await this.isStaleLock();
|
|
7101
|
+
if (stale) {
|
|
7102
|
+
await fsp2.rm(this.dirPath, { recursive: true, force: true });
|
|
7103
|
+
continue;
|
|
7104
|
+
}
|
|
6872
7105
|
if (Date.now() - start >= this.timeoutMs) {
|
|
6873
7106
|
throw new Error(`ToolboxStore lock acquisition timed out for ${this.dirPath}`);
|
|
6874
7107
|
}
|
|
@@ -6876,6 +7109,16 @@ var ToolboxFileLock = class {
|
|
|
6876
7109
|
}
|
|
6877
7110
|
}
|
|
6878
7111
|
}
|
|
7112
|
+
async isStaleLock() {
|
|
7113
|
+
try {
|
|
7114
|
+
const pidStr = await fsp2.readFile(this.pidFilePath, "utf8");
|
|
7115
|
+
const pid = Number.parseInt(pidStr.trim(), 10);
|
|
7116
|
+
if (!Number.isFinite(pid) || pid <= 0) return true;
|
|
7117
|
+
return !isProcessAlive2(pid);
|
|
7118
|
+
} catch {
|
|
7119
|
+
return true;
|
|
7120
|
+
}
|
|
7121
|
+
}
|
|
6879
7122
|
async release() {
|
|
6880
7123
|
if (!this.acquired) return;
|
|
6881
7124
|
this.acquired = false;
|