@mcesystems/usbmuxd-instance-manager 1.0.73 → 1.0.75
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 +11 -8
- package/dist/cli.js +952 -437
- package/dist/cli.js.map +4 -4
- package/dist/cli.mjs +980 -437
- package/dist/cli.mjs.map +4 -4
- package/dist/index.js +518 -399
- package/dist/index.js.map +4 -4
- package/dist/index.mjs +518 -399
- package/dist/index.mjs.map +4 -4
- package/dist/types/InstanceManager.d.ts +19 -45
- package/dist/types/InstanceManager.d.ts.map +1 -1
- package/dist/types/UsbmuxdService.d.ts +0 -10
- package/dist/types/UsbmuxdService.d.ts.map +1 -1
- package/dist/types/WindowsPrerequisites.d.ts +21 -0
- package/dist/types/WindowsPrerequisites.d.ts.map +1 -0
- package/dist/types/types/index.d.ts +1 -13
- package/dist/types/types/index.d.ts.map +1 -1
- package/dist/types/types/usbipd.d.ts +11 -0
- package/dist/types/types/usbipd.d.ts.map +1 -0
- package/dist/types/usbipd.d.ts +26 -0
- package/dist/types/usbipd.d.ts.map +1 -0
- package/dist/types/wsl.d.ts +70 -0
- package/dist/types/wsl.d.ts.map +1 -0
- package/package.json +4 -3
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
10
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
11
|
+
};
|
|
9
12
|
var __copyProps = (to, from, except, desc) => {
|
|
10
13
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
14
|
for (let key of __getOwnPropNames(from))
|
|
@@ -23,192 +26,860 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
26
|
mod
|
|
24
27
|
));
|
|
25
28
|
|
|
29
|
+
// ../../node_modules/.pnpm/dotenv@17.2.3/node_modules/dotenv/package.json
|
|
30
|
+
var require_package = __commonJS({
|
|
31
|
+
"../../node_modules/.pnpm/dotenv@17.2.3/node_modules/dotenv/package.json"(exports2, module2) {
|
|
32
|
+
module2.exports = {
|
|
33
|
+
name: "dotenv",
|
|
34
|
+
version: "17.2.3",
|
|
35
|
+
description: "Loads environment variables from .env file",
|
|
36
|
+
main: "lib/main.js",
|
|
37
|
+
types: "lib/main.d.ts",
|
|
38
|
+
exports: {
|
|
39
|
+
".": {
|
|
40
|
+
types: "./lib/main.d.ts",
|
|
41
|
+
require: "./lib/main.js",
|
|
42
|
+
default: "./lib/main.js"
|
|
43
|
+
},
|
|
44
|
+
"./config": "./config.js",
|
|
45
|
+
"./config.js": "./config.js",
|
|
46
|
+
"./lib/env-options": "./lib/env-options.js",
|
|
47
|
+
"./lib/env-options.js": "./lib/env-options.js",
|
|
48
|
+
"./lib/cli-options": "./lib/cli-options.js",
|
|
49
|
+
"./lib/cli-options.js": "./lib/cli-options.js",
|
|
50
|
+
"./package.json": "./package.json"
|
|
51
|
+
},
|
|
52
|
+
scripts: {
|
|
53
|
+
"dts-check": "tsc --project tests/types/tsconfig.json",
|
|
54
|
+
lint: "standard",
|
|
55
|
+
pretest: "npm run lint && npm run dts-check",
|
|
56
|
+
test: "tap run tests/**/*.js --allow-empty-coverage --disable-coverage --timeout=60000",
|
|
57
|
+
"test:coverage": "tap run tests/**/*.js --show-full-coverage --timeout=60000 --coverage-report=text --coverage-report=lcov",
|
|
58
|
+
prerelease: "npm test",
|
|
59
|
+
release: "standard-version"
|
|
60
|
+
},
|
|
61
|
+
repository: {
|
|
62
|
+
type: "git",
|
|
63
|
+
url: "git://github.com/motdotla/dotenv.git"
|
|
64
|
+
},
|
|
65
|
+
homepage: "https://github.com/motdotla/dotenv#readme",
|
|
66
|
+
funding: "https://dotenvx.com",
|
|
67
|
+
keywords: [
|
|
68
|
+
"dotenv",
|
|
69
|
+
"env",
|
|
70
|
+
".env",
|
|
71
|
+
"environment",
|
|
72
|
+
"variables",
|
|
73
|
+
"config",
|
|
74
|
+
"settings"
|
|
75
|
+
],
|
|
76
|
+
readmeFilename: "README.md",
|
|
77
|
+
license: "BSD-2-Clause",
|
|
78
|
+
devDependencies: {
|
|
79
|
+
"@types/node": "^18.11.3",
|
|
80
|
+
decache: "^4.6.2",
|
|
81
|
+
sinon: "^14.0.1",
|
|
82
|
+
standard: "^17.0.0",
|
|
83
|
+
"standard-version": "^9.5.0",
|
|
84
|
+
tap: "^19.2.0",
|
|
85
|
+
typescript: "^4.8.4"
|
|
86
|
+
},
|
|
87
|
+
engines: {
|
|
88
|
+
node: ">=12"
|
|
89
|
+
},
|
|
90
|
+
browser: {
|
|
91
|
+
fs: false
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// ../../node_modules/.pnpm/dotenv@17.2.3/node_modules/dotenv/lib/main.js
|
|
98
|
+
var require_main = __commonJS({
|
|
99
|
+
"../../node_modules/.pnpm/dotenv@17.2.3/node_modules/dotenv/lib/main.js"(exports2, module2) {
|
|
100
|
+
var fs = require("fs");
|
|
101
|
+
var path = require("path");
|
|
102
|
+
var os = require("os");
|
|
103
|
+
var crypto = require("crypto");
|
|
104
|
+
var packageJson = require_package();
|
|
105
|
+
var version = packageJson.version;
|
|
106
|
+
var TIPS = [
|
|
107
|
+
"\u{1F510} encrypt with Dotenvx: https://dotenvx.com",
|
|
108
|
+
"\u{1F510} prevent committing .env to code: https://dotenvx.com/precommit",
|
|
109
|
+
"\u{1F510} prevent building .env in docker: https://dotenvx.com/prebuild",
|
|
110
|
+
"\u{1F4E1} add observability to secrets: https://dotenvx.com/ops",
|
|
111
|
+
"\u{1F465} sync secrets across teammates & machines: https://dotenvx.com/ops",
|
|
112
|
+
"\u{1F5C2}\uFE0F backup and recover secrets: https://dotenvx.com/ops",
|
|
113
|
+
"\u2705 audit secrets and track compliance: https://dotenvx.com/ops",
|
|
114
|
+
"\u{1F504} add secrets lifecycle management: https://dotenvx.com/ops",
|
|
115
|
+
"\u{1F511} add access controls to secrets: https://dotenvx.com/ops",
|
|
116
|
+
"\u{1F6E0}\uFE0F run anywhere with `dotenvx run -- yourcommand`",
|
|
117
|
+
"\u2699\uFE0F specify custom .env file path with { path: '/custom/path/.env' }",
|
|
118
|
+
"\u2699\uFE0F enable debug logging with { debug: true }",
|
|
119
|
+
"\u2699\uFE0F override existing env vars with { override: true }",
|
|
120
|
+
"\u2699\uFE0F suppress all logs with { quiet: true }",
|
|
121
|
+
"\u2699\uFE0F write to custom object with { processEnv: myObject }",
|
|
122
|
+
"\u2699\uFE0F load multiple .env files with { path: ['.env.local', '.env'] }"
|
|
123
|
+
];
|
|
124
|
+
function _getRandomTip() {
|
|
125
|
+
return TIPS[Math.floor(Math.random() * TIPS.length)];
|
|
126
|
+
}
|
|
127
|
+
function parseBoolean(value) {
|
|
128
|
+
if (typeof value === "string") {
|
|
129
|
+
return !["false", "0", "no", "off", ""].includes(value.toLowerCase());
|
|
130
|
+
}
|
|
131
|
+
return Boolean(value);
|
|
132
|
+
}
|
|
133
|
+
function supportsAnsi() {
|
|
134
|
+
return process.stdout.isTTY;
|
|
135
|
+
}
|
|
136
|
+
function dim(text) {
|
|
137
|
+
return supportsAnsi() ? `\x1B[2m${text}\x1B[0m` : text;
|
|
138
|
+
}
|
|
139
|
+
var LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;
|
|
140
|
+
function parse(src) {
|
|
141
|
+
const obj = {};
|
|
142
|
+
let lines = src.toString();
|
|
143
|
+
lines = lines.replace(/\r\n?/mg, "\n");
|
|
144
|
+
let match;
|
|
145
|
+
while ((match = LINE.exec(lines)) != null) {
|
|
146
|
+
const key = match[1];
|
|
147
|
+
let value = match[2] || "";
|
|
148
|
+
value = value.trim();
|
|
149
|
+
const maybeQuote = value[0];
|
|
150
|
+
value = value.replace(/^(['"`])([\s\S]*)\1$/mg, "$2");
|
|
151
|
+
if (maybeQuote === '"') {
|
|
152
|
+
value = value.replace(/\\n/g, "\n");
|
|
153
|
+
value = value.replace(/\\r/g, "\r");
|
|
154
|
+
}
|
|
155
|
+
obj[key] = value;
|
|
156
|
+
}
|
|
157
|
+
return obj;
|
|
158
|
+
}
|
|
159
|
+
function _parseVault(options2) {
|
|
160
|
+
options2 = options2 || {};
|
|
161
|
+
const vaultPath = _vaultPath(options2);
|
|
162
|
+
options2.path = vaultPath;
|
|
163
|
+
const result = DotenvModule.configDotenv(options2);
|
|
164
|
+
if (!result.parsed) {
|
|
165
|
+
const err = new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`);
|
|
166
|
+
err.code = "MISSING_DATA";
|
|
167
|
+
throw err;
|
|
168
|
+
}
|
|
169
|
+
const keys = _dotenvKey(options2).split(",");
|
|
170
|
+
const length = keys.length;
|
|
171
|
+
let decrypted;
|
|
172
|
+
for (let i = 0; i < length; i++) {
|
|
173
|
+
try {
|
|
174
|
+
const key = keys[i].trim();
|
|
175
|
+
const attrs = _instructions(result, key);
|
|
176
|
+
decrypted = DotenvModule.decrypt(attrs.ciphertext, attrs.key);
|
|
177
|
+
break;
|
|
178
|
+
} catch (error) {
|
|
179
|
+
if (i + 1 >= length) {
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return DotenvModule.parse(decrypted);
|
|
185
|
+
}
|
|
186
|
+
function _warn(message) {
|
|
187
|
+
console.error(`[dotenv@${version}][WARN] ${message}`);
|
|
188
|
+
}
|
|
189
|
+
function _debug(message) {
|
|
190
|
+
console.log(`[dotenv@${version}][DEBUG] ${message}`);
|
|
191
|
+
}
|
|
192
|
+
function _log(message) {
|
|
193
|
+
console.log(`[dotenv@${version}] ${message}`);
|
|
194
|
+
}
|
|
195
|
+
function _dotenvKey(options2) {
|
|
196
|
+
if (options2 && options2.DOTENV_KEY && options2.DOTENV_KEY.length > 0) {
|
|
197
|
+
return options2.DOTENV_KEY;
|
|
198
|
+
}
|
|
199
|
+
if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) {
|
|
200
|
+
return process.env.DOTENV_KEY;
|
|
201
|
+
}
|
|
202
|
+
return "";
|
|
203
|
+
}
|
|
204
|
+
function _instructions(result, dotenvKey) {
|
|
205
|
+
let uri;
|
|
206
|
+
try {
|
|
207
|
+
uri = new URL(dotenvKey);
|
|
208
|
+
} catch (error) {
|
|
209
|
+
if (error.code === "ERR_INVALID_URL") {
|
|
210
|
+
const err = new Error("INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=development");
|
|
211
|
+
err.code = "INVALID_DOTENV_KEY";
|
|
212
|
+
throw err;
|
|
213
|
+
}
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
const key = uri.password;
|
|
217
|
+
if (!key) {
|
|
218
|
+
const err = new Error("INVALID_DOTENV_KEY: Missing key part");
|
|
219
|
+
err.code = "INVALID_DOTENV_KEY";
|
|
220
|
+
throw err;
|
|
221
|
+
}
|
|
222
|
+
const environment = uri.searchParams.get("environment");
|
|
223
|
+
if (!environment) {
|
|
224
|
+
const err = new Error("INVALID_DOTENV_KEY: Missing environment part");
|
|
225
|
+
err.code = "INVALID_DOTENV_KEY";
|
|
226
|
+
throw err;
|
|
227
|
+
}
|
|
228
|
+
const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`;
|
|
229
|
+
const ciphertext = result.parsed[environmentKey];
|
|
230
|
+
if (!ciphertext) {
|
|
231
|
+
const err = new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`);
|
|
232
|
+
err.code = "NOT_FOUND_DOTENV_ENVIRONMENT";
|
|
233
|
+
throw err;
|
|
234
|
+
}
|
|
235
|
+
return { ciphertext, key };
|
|
236
|
+
}
|
|
237
|
+
function _vaultPath(options2) {
|
|
238
|
+
let possibleVaultPath = null;
|
|
239
|
+
if (options2 && options2.path && options2.path.length > 0) {
|
|
240
|
+
if (Array.isArray(options2.path)) {
|
|
241
|
+
for (const filepath of options2.path) {
|
|
242
|
+
if (fs.existsSync(filepath)) {
|
|
243
|
+
possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
} else {
|
|
247
|
+
possibleVaultPath = options2.path.endsWith(".vault") ? options2.path : `${options2.path}.vault`;
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
possibleVaultPath = path.resolve(process.cwd(), ".env.vault");
|
|
251
|
+
}
|
|
252
|
+
if (fs.existsSync(possibleVaultPath)) {
|
|
253
|
+
return possibleVaultPath;
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
function _resolveHome(envPath) {
|
|
258
|
+
return envPath[0] === "~" ? path.join(os.homedir(), envPath.slice(1)) : envPath;
|
|
259
|
+
}
|
|
260
|
+
function _configVault(options2) {
|
|
261
|
+
const debug = parseBoolean(process.env.DOTENV_CONFIG_DEBUG || options2 && options2.debug);
|
|
262
|
+
const quiet = parseBoolean(process.env.DOTENV_CONFIG_QUIET || options2 && options2.quiet);
|
|
263
|
+
if (debug || !quiet) {
|
|
264
|
+
_log("Loading env from encrypted .env.vault");
|
|
265
|
+
}
|
|
266
|
+
const parsed = DotenvModule._parseVault(options2);
|
|
267
|
+
let processEnv = process.env;
|
|
268
|
+
if (options2 && options2.processEnv != null) {
|
|
269
|
+
processEnv = options2.processEnv;
|
|
270
|
+
}
|
|
271
|
+
DotenvModule.populate(processEnv, parsed, options2);
|
|
272
|
+
return { parsed };
|
|
273
|
+
}
|
|
274
|
+
function configDotenv(options2) {
|
|
275
|
+
const dotenvPath = path.resolve(process.cwd(), ".env");
|
|
276
|
+
let encoding = "utf8";
|
|
277
|
+
let processEnv = process.env;
|
|
278
|
+
if (options2 && options2.processEnv != null) {
|
|
279
|
+
processEnv = options2.processEnv;
|
|
280
|
+
}
|
|
281
|
+
let debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || options2 && options2.debug);
|
|
282
|
+
let quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || options2 && options2.quiet);
|
|
283
|
+
if (options2 && options2.encoding) {
|
|
284
|
+
encoding = options2.encoding;
|
|
285
|
+
} else {
|
|
286
|
+
if (debug) {
|
|
287
|
+
_debug("No encoding is specified. UTF-8 is used by default");
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
let optionPaths = [dotenvPath];
|
|
291
|
+
if (options2 && options2.path) {
|
|
292
|
+
if (!Array.isArray(options2.path)) {
|
|
293
|
+
optionPaths = [_resolveHome(options2.path)];
|
|
294
|
+
} else {
|
|
295
|
+
optionPaths = [];
|
|
296
|
+
for (const filepath of options2.path) {
|
|
297
|
+
optionPaths.push(_resolveHome(filepath));
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
let lastError;
|
|
302
|
+
const parsedAll = {};
|
|
303
|
+
for (const path2 of optionPaths) {
|
|
304
|
+
try {
|
|
305
|
+
const parsed = DotenvModule.parse(fs.readFileSync(path2, { encoding }));
|
|
306
|
+
DotenvModule.populate(parsedAll, parsed, options2);
|
|
307
|
+
} catch (e) {
|
|
308
|
+
if (debug) {
|
|
309
|
+
_debug(`Failed to load ${path2} ${e.message}`);
|
|
310
|
+
}
|
|
311
|
+
lastError = e;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
const populated = DotenvModule.populate(processEnv, parsedAll, options2);
|
|
315
|
+
debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || debug);
|
|
316
|
+
quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || quiet);
|
|
317
|
+
if (debug || !quiet) {
|
|
318
|
+
const keysCount = Object.keys(populated).length;
|
|
319
|
+
const shortPaths = [];
|
|
320
|
+
for (const filePath of optionPaths) {
|
|
321
|
+
try {
|
|
322
|
+
const relative = path.relative(process.cwd(), filePath);
|
|
323
|
+
shortPaths.push(relative);
|
|
324
|
+
} catch (e) {
|
|
325
|
+
if (debug) {
|
|
326
|
+
_debug(`Failed to load ${filePath} ${e.message}`);
|
|
327
|
+
}
|
|
328
|
+
lastError = e;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
_log(`injecting env (${keysCount}) from ${shortPaths.join(",")} ${dim(`-- tip: ${_getRandomTip()}`)}`);
|
|
332
|
+
}
|
|
333
|
+
if (lastError) {
|
|
334
|
+
return { parsed: parsedAll, error: lastError };
|
|
335
|
+
} else {
|
|
336
|
+
return { parsed: parsedAll };
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
function config2(options2) {
|
|
340
|
+
if (_dotenvKey(options2).length === 0) {
|
|
341
|
+
return DotenvModule.configDotenv(options2);
|
|
342
|
+
}
|
|
343
|
+
const vaultPath = _vaultPath(options2);
|
|
344
|
+
if (!vaultPath) {
|
|
345
|
+
_warn(`You set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}. Did you forget to build it?`);
|
|
346
|
+
return DotenvModule.configDotenv(options2);
|
|
347
|
+
}
|
|
348
|
+
return DotenvModule._configVault(options2);
|
|
349
|
+
}
|
|
350
|
+
function decrypt(encrypted, keyStr) {
|
|
351
|
+
const key = Buffer.from(keyStr.slice(-64), "hex");
|
|
352
|
+
let ciphertext = Buffer.from(encrypted, "base64");
|
|
353
|
+
const nonce = ciphertext.subarray(0, 12);
|
|
354
|
+
const authTag = ciphertext.subarray(-16);
|
|
355
|
+
ciphertext = ciphertext.subarray(12, -16);
|
|
356
|
+
try {
|
|
357
|
+
const aesgcm = crypto.createDecipheriv("aes-256-gcm", key, nonce);
|
|
358
|
+
aesgcm.setAuthTag(authTag);
|
|
359
|
+
return `${aesgcm.update(ciphertext)}${aesgcm.final()}`;
|
|
360
|
+
} catch (error) {
|
|
361
|
+
const isRange = error instanceof RangeError;
|
|
362
|
+
const invalidKeyLength = error.message === "Invalid key length";
|
|
363
|
+
const decryptionFailed = error.message === "Unsupported state or unable to authenticate data";
|
|
364
|
+
if (isRange || invalidKeyLength) {
|
|
365
|
+
const err = new Error("INVALID_DOTENV_KEY: It must be 64 characters long (or more)");
|
|
366
|
+
err.code = "INVALID_DOTENV_KEY";
|
|
367
|
+
throw err;
|
|
368
|
+
} else if (decryptionFailed) {
|
|
369
|
+
const err = new Error("DECRYPTION_FAILED: Please check your DOTENV_KEY");
|
|
370
|
+
err.code = "DECRYPTION_FAILED";
|
|
371
|
+
throw err;
|
|
372
|
+
} else {
|
|
373
|
+
throw error;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
function populate(processEnv, parsed, options2 = {}) {
|
|
378
|
+
const debug = Boolean(options2 && options2.debug);
|
|
379
|
+
const override = Boolean(options2 && options2.override);
|
|
380
|
+
const populated = {};
|
|
381
|
+
if (typeof parsed !== "object") {
|
|
382
|
+
const err = new Error("OBJECT_REQUIRED: Please check the processEnv argument being passed to populate");
|
|
383
|
+
err.code = "OBJECT_REQUIRED";
|
|
384
|
+
throw err;
|
|
385
|
+
}
|
|
386
|
+
for (const key of Object.keys(parsed)) {
|
|
387
|
+
if (Object.prototype.hasOwnProperty.call(processEnv, key)) {
|
|
388
|
+
if (override === true) {
|
|
389
|
+
processEnv[key] = parsed[key];
|
|
390
|
+
populated[key] = parsed[key];
|
|
391
|
+
}
|
|
392
|
+
if (debug) {
|
|
393
|
+
if (override === true) {
|
|
394
|
+
_debug(`"${key}" is already defined and WAS overwritten`);
|
|
395
|
+
} else {
|
|
396
|
+
_debug(`"${key}" is already defined and was NOT overwritten`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
} else {
|
|
400
|
+
processEnv[key] = parsed[key];
|
|
401
|
+
populated[key] = parsed[key];
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return populated;
|
|
405
|
+
}
|
|
406
|
+
var DotenvModule = {
|
|
407
|
+
configDotenv,
|
|
408
|
+
_configVault,
|
|
409
|
+
_parseVault,
|
|
410
|
+
config: config2,
|
|
411
|
+
decrypt,
|
|
412
|
+
parse,
|
|
413
|
+
populate
|
|
414
|
+
};
|
|
415
|
+
module2.exports.configDotenv = DotenvModule.configDotenv;
|
|
416
|
+
module2.exports._configVault = DotenvModule._configVault;
|
|
417
|
+
module2.exports._parseVault = DotenvModule._parseVault;
|
|
418
|
+
module2.exports.config = DotenvModule.config;
|
|
419
|
+
module2.exports.decrypt = DotenvModule.decrypt;
|
|
420
|
+
module2.exports.parse = DotenvModule.parse;
|
|
421
|
+
module2.exports.populate = DotenvModule.populate;
|
|
422
|
+
module2.exports = DotenvModule;
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
|
|
26
426
|
// src/cli.ts
|
|
27
|
-
var
|
|
427
|
+
var import_tool_debug_g45 = require("@mcesystems/tool-debug-g4");
|
|
428
|
+
var import_dotenv = __toESM(require_main());
|
|
28
429
|
|
|
29
430
|
// src/UsbmuxdService.ts
|
|
30
431
|
var import_node_events2 = require("node:events");
|
|
31
|
-
var
|
|
432
|
+
var import_tool_debug_g44 = require("@mcesystems/tool-debug-g4");
|
|
32
433
|
var import_usb_device_listener = __toESM(require("@mcesystems/usb-device-listener"));
|
|
33
434
|
|
|
34
435
|
// src/InstanceManager.ts
|
|
35
|
-
var import_node_child_process2 = require("node:child_process");
|
|
36
436
|
var import_node_events = require("node:events");
|
|
37
|
-
var
|
|
38
|
-
var import_tool_debug_g42 = require("@mcesystems/tool-debug-g4");
|
|
437
|
+
var import_tool_debug_g43 = require("@mcesystems/tool-debug-g4");
|
|
39
438
|
|
|
40
|
-
// src/
|
|
439
|
+
// src/usbipd.ts
|
|
41
440
|
var import_node_child_process = require("node:child_process");
|
|
42
|
-
var import_node_fs = require("node:fs");
|
|
43
|
-
var import_node_path = require("node:path");
|
|
44
441
|
var import_node_util = require("node:util");
|
|
45
442
|
var import_tool_debug_g4 = require("@mcesystems/tool-debug-g4");
|
|
46
|
-
var { logInfo, logWarning } = (0, import_tool_debug_g4.createLoggers)("
|
|
443
|
+
var { logInfo, logWarning } = (0, import_tool_debug_g4.createLoggers)("usbipd");
|
|
47
444
|
var execAsync = (0, import_node_util.promisify)(import_node_child_process.exec);
|
|
48
|
-
var
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
async
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
`
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
|
|
445
|
+
var Usbipd = class {
|
|
446
|
+
constructor(usbipdPath) {
|
|
447
|
+
this.usbipdPath = usbipdPath;
|
|
448
|
+
}
|
|
449
|
+
async unbindAllDevicesFromWsl() {
|
|
450
|
+
try {
|
|
451
|
+
await execAsync(`"${this.usbipdPath}" unbind -a`);
|
|
452
|
+
logInfo("All devices unbound from WSL");
|
|
453
|
+
} catch (error) {
|
|
454
|
+
logWarning(`Failed to unbind all devices from WSL: ${error}`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
async detachAllDevicesFromWsl() {
|
|
458
|
+
try {
|
|
459
|
+
await execAsync(`"${this.usbipdPath}" detach -a`);
|
|
460
|
+
logInfo("All devices detached from WSL");
|
|
461
|
+
} catch (error) {
|
|
462
|
+
logWarning(`Failed to detach all devices from WSL: ${error}`);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Detach a device from WSL via usbipd
|
|
467
|
+
*/
|
|
468
|
+
async detachDeviceFromWsl(busId) {
|
|
469
|
+
try {
|
|
470
|
+
await execAsync(`"${this.usbipdPath}" detach --busid=${busId}`);
|
|
471
|
+
logInfo(`Device ${busId} detached from WSL`);
|
|
472
|
+
} catch (error) {
|
|
473
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
474
|
+
if (message.includes("no device with busid") || message.includes("There is no device")) {
|
|
475
|
+
logInfo(`Device ${busId} already detached`);
|
|
476
|
+
} else {
|
|
477
|
+
logWarning(`Failed to detach device ${busId}: ${error}`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Attach a device to WSL via usbipd
|
|
483
|
+
* Note: This requires administrator privileges
|
|
484
|
+
*/
|
|
485
|
+
async attachDeviceToWsl(busId, wsl, distro) {
|
|
486
|
+
try {
|
|
487
|
+
await wsl.ensureWslRunning();
|
|
488
|
+
logInfo(`Binding device ${busId}...`);
|
|
489
|
+
await execAsync(`"${this.usbipdPath}" bind --busid ${busId} --force`);
|
|
490
|
+
if (distro) {
|
|
491
|
+
logInfo(`Attaching device ${busId} to WSL distribution ${distro}...`);
|
|
492
|
+
await execAsync(`"${this.usbipdPath}" attach --wsl=${distro} --busid=${busId}`);
|
|
493
|
+
} else {
|
|
494
|
+
logInfo(`Attaching device ${busId} to default WSL...`);
|
|
495
|
+
await execAsync(`"${this.usbipdPath}" attach --wsl --busid=${busId}`);
|
|
496
|
+
}
|
|
497
|
+
logInfo(`Device ${busId} attached to WSL successfully`);
|
|
498
|
+
return true;
|
|
499
|
+
} catch (error) {
|
|
500
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
501
|
+
if (message.includes("already attached to a client")) {
|
|
502
|
+
logInfo(`Device ${busId} is already attached to WSL, continuing`);
|
|
503
|
+
return true;
|
|
504
|
+
}
|
|
505
|
+
logWarning(`Failed to attach device ${busId} to WSL: ${error}`);
|
|
506
|
+
return false;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Parse usbipd list output to extract device information
|
|
511
|
+
*/
|
|
512
|
+
parseUsbipdList(output) {
|
|
513
|
+
const devices = [];
|
|
514
|
+
const lines = output.split(/\r?\n/);
|
|
515
|
+
for (const line of lines) {
|
|
516
|
+
const match = line.match(/^(\d+-\d+)\s+([0-9a-f]{4}):([0-9a-f]{4})\s+(.+)$/i);
|
|
517
|
+
if (match) {
|
|
518
|
+
const rest = match[4].trim();
|
|
519
|
+
const stateMatch = rest.match(/^(.+?)\s{2,}(\S.*)$/);
|
|
520
|
+
const description = stateMatch ? stateMatch[1].trim() : rest;
|
|
521
|
+
const state = stateMatch ? stateMatch[2].trim() : "Unknown";
|
|
522
|
+
devices.push({
|
|
523
|
+
busId: match[1],
|
|
524
|
+
vid: match[2].toUpperCase(),
|
|
525
|
+
pid: match[3].toUpperCase(),
|
|
526
|
+
description,
|
|
527
|
+
state
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return devices;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Find the usbipd bus ID for a device by matching VID/PID
|
|
535
|
+
*/
|
|
536
|
+
async findBusIdForDevice(device) {
|
|
537
|
+
try {
|
|
538
|
+
const { stdout } = await execAsync(`"${this.usbipdPath}" list`);
|
|
539
|
+
const usbipdDevices = this.parseUsbipdList(stdout);
|
|
540
|
+
const deviceVid = device.vid.toString(16).toUpperCase().padStart(4, "0");
|
|
541
|
+
const devicePid = device.pid.toString(16).toUpperCase().padStart(4, "0");
|
|
542
|
+
logInfo(`Looking for device with VID:PID ${deviceVid}:${devicePid}`);
|
|
543
|
+
logInfo(`Found ${usbipdDevices.length} devices from usbipd list`);
|
|
544
|
+
const match = usbipdDevices.find((d) => d.vid === deviceVid && d.pid === devicePid);
|
|
545
|
+
if (match) {
|
|
546
|
+
logInfo(
|
|
547
|
+
`Found usbipd bus ID ${match.busId} for device ${device.deviceId} (${deviceVid}:${devicePid})`
|
|
548
|
+
);
|
|
549
|
+
return match.busId;
|
|
550
|
+
}
|
|
551
|
+
throw new Error(
|
|
552
|
+
`Could not find usbipd bus ID for device ${device.deviceId} (${deviceVid}:${devicePid})`
|
|
553
|
+
);
|
|
554
|
+
} catch (error) {
|
|
555
|
+
throw new Error(`Failed to run usbipd list: ${error}`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
// src/wsl.ts
|
|
561
|
+
var import_node_child_process2 = require("node:child_process");
|
|
562
|
+
var import_node_fs = require("node:fs");
|
|
563
|
+
var import_node_path = require("node:path");
|
|
564
|
+
var import_node_util2 = require("node:util");
|
|
565
|
+
var import_tool_debug_g42 = require("@mcesystems/tool-debug-g4");
|
|
566
|
+
var { logInfo: logInfo2, logWarning: logWarning2, logDetail } = (0, import_tool_debug_g42.createLoggers)("wsl");
|
|
567
|
+
var execAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.exec);
|
|
568
|
+
var Wsl2 = class {
|
|
569
|
+
constructor(wslDistribution) {
|
|
570
|
+
this.wslDistribution = wslDistribution;
|
|
571
|
+
}
|
|
572
|
+
ALPINE_LOCKDOWN_DIR = "/var/lib/lockdown";
|
|
573
|
+
SYSTEM_CONFIG_PLIST = "SystemConfiguration.plist";
|
|
574
|
+
wslIpAddress = null;
|
|
575
|
+
/**
|
|
576
|
+
* Detect the WSL2 IP address for the configured distribution
|
|
577
|
+
* This IP is needed to connect from Windows to services inside WSL
|
|
578
|
+
*/
|
|
579
|
+
async detectWslIpAddress() {
|
|
580
|
+
if (this.wslIpAddress) {
|
|
581
|
+
return this.wslIpAddress;
|
|
582
|
+
}
|
|
583
|
+
const distro = this.wslDistribution || "alpine-usbmuxd-build";
|
|
584
|
+
try {
|
|
585
|
+
const { stdout } = await execAsync2(`wsl -d ${distro} -- ip -4 addr show eth0`);
|
|
586
|
+
const match = stdout.match(/inet\s+(\d+\.\d+\.\d+\.\d+)/);
|
|
587
|
+
if (match) {
|
|
588
|
+
this.wslIpAddress = match[1];
|
|
589
|
+
logInfo2(`Detected WSL IP address: ${this.wslIpAddress}`);
|
|
590
|
+
return this.wslIpAddress;
|
|
591
|
+
}
|
|
592
|
+
} catch (error) {
|
|
593
|
+
logWarning2(`Failed to detect WSL IP via ip addr: ${error}`);
|
|
594
|
+
}
|
|
595
|
+
try {
|
|
596
|
+
const { stdout } = await execAsync2(`wsl -d ${distro} -- hostname -I`);
|
|
597
|
+
const ip = stdout.trim().split(/\s+/)[0];
|
|
598
|
+
if (ip && /^\d+\.\d+\.\d+\.\d+$/.test(ip)) {
|
|
599
|
+
this.wslIpAddress = ip;
|
|
600
|
+
logInfo2(`Detected WSL IP address (hostname): ${this.wslIpAddress}`);
|
|
601
|
+
return this.wslIpAddress;
|
|
602
|
+
}
|
|
603
|
+
} catch (error) {
|
|
604
|
+
logWarning2(`Failed to detect WSL IP via hostname: ${error}`);
|
|
605
|
+
}
|
|
606
|
+
logWarning2("Could not detect WSL IP, falling back to localhost");
|
|
607
|
+
this.wslIpAddress = "127.0.0.1";
|
|
608
|
+
return this.wslIpAddress;
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Ensure the WSL distribution is running
|
|
612
|
+
*/
|
|
613
|
+
async ensureWslRunning() {
|
|
614
|
+
const distro = this.wslDistribution;
|
|
615
|
+
try {
|
|
616
|
+
if (distro) {
|
|
617
|
+
logInfo2(`Starting WSL distribution: ${distro}...`);
|
|
618
|
+
await execAsync2(`wsl -d ${distro} -- echo "WSL started"`);
|
|
619
|
+
} else {
|
|
620
|
+
logInfo2("Starting default WSL distribution...");
|
|
621
|
+
await execAsync2(`wsl -- echo "WSL started"`);
|
|
622
|
+
}
|
|
623
|
+
return true;
|
|
624
|
+
} catch (error) {
|
|
625
|
+
logWarning2(`Failed to start WSL: ${error}`);
|
|
626
|
+
return false;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Create a new usbmuxd instance
|
|
631
|
+
*/
|
|
632
|
+
async createInstance({
|
|
633
|
+
id,
|
|
634
|
+
basePort,
|
|
635
|
+
verboseLogging,
|
|
636
|
+
usbmuxdPath,
|
|
637
|
+
onLog,
|
|
638
|
+
onExit
|
|
639
|
+
}) {
|
|
640
|
+
const port = basePort + id - 1;
|
|
641
|
+
logInfo2(`Creating instance ${id} on port ${port}`);
|
|
642
|
+
const host = await this.detectWslIpAddress();
|
|
643
|
+
const usbmuxdArgs = [
|
|
644
|
+
"-f",
|
|
645
|
+
// Foreground
|
|
646
|
+
"-v",
|
|
647
|
+
// Verbose (if enabled)
|
|
648
|
+
"-S",
|
|
649
|
+
`0.0.0.0:${port}`,
|
|
650
|
+
// Listen on all interfaces (for Windows → WSL2)
|
|
651
|
+
"--pidfile",
|
|
652
|
+
"NONE"
|
|
653
|
+
];
|
|
654
|
+
if (!verboseLogging) {
|
|
655
|
+
usbmuxdArgs.splice(1, 1);
|
|
656
|
+
}
|
|
657
|
+
const wslArgs = [
|
|
658
|
+
"-d",
|
|
659
|
+
this.wslDistribution || "alpine-usbmuxd-build",
|
|
660
|
+
usbmuxdPath,
|
|
661
|
+
...usbmuxdArgs
|
|
662
|
+
];
|
|
663
|
+
const process2 = (0, import_node_child_process2.spawn)("wsl", wslArgs, {
|
|
664
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
665
|
+
detached: true,
|
|
666
|
+
windowsHide: false
|
|
667
|
+
});
|
|
668
|
+
process2.stdout?.on("data", (data) => {
|
|
669
|
+
onLog(data.toString().trim());
|
|
670
|
+
});
|
|
671
|
+
process2.stderr?.on("data", (data) => {
|
|
672
|
+
onLog(data.toString().trim());
|
|
673
|
+
});
|
|
674
|
+
process2.on("exit", onExit);
|
|
675
|
+
const pid = process2.pid;
|
|
676
|
+
if (pid === void 0) {
|
|
677
|
+
process2.kill("SIGKILL");
|
|
678
|
+
throw new Error("Failed to get PID for usbmuxd instance");
|
|
679
|
+
}
|
|
680
|
+
const instance = {
|
|
681
|
+
id,
|
|
682
|
+
host,
|
|
683
|
+
port,
|
|
684
|
+
pid,
|
|
685
|
+
deviceUdids: [],
|
|
686
|
+
startedAt: /* @__PURE__ */ new Date()
|
|
687
|
+
};
|
|
688
|
+
return { instance, process: process2 };
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Convert a Windows path to the equivalent path inside WSL (e.g. C:\foo\bar -> /mnt/c/foo/bar).
|
|
692
|
+
*/
|
|
693
|
+
windowsPathToWsl(windowsPath) {
|
|
694
|
+
const normalized = windowsPath.replace(/\\/g, "/").trim();
|
|
695
|
+
const driveMatch = normalized.match(/^([a-zA-Z]):\/?(.*)$/);
|
|
696
|
+
if (driveMatch) {
|
|
697
|
+
const drive = driveMatch[1].toLowerCase();
|
|
698
|
+
const rest = driveMatch[2] || "";
|
|
699
|
+
return `/mnt/${drive}${rest ? `/${rest}` : ""}`;
|
|
700
|
+
}
|
|
701
|
+
return normalized;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Get the native Apple lockdown directory path for the current platform.
|
|
705
|
+
* This is where Apple/iTunes stores pairing records natively.
|
|
706
|
+
*/
|
|
707
|
+
getAppleLockdownPath() {
|
|
708
|
+
if (process.platform === "win32") {
|
|
709
|
+
return (0, import_node_path.join)(process.env.ProgramData ?? "C:\\ProgramData", "Apple", "Lockdown");
|
|
710
|
+
}
|
|
711
|
+
if (process.platform === "darwin") {
|
|
712
|
+
return "/var/db/lockdown";
|
|
713
|
+
}
|
|
714
|
+
if (process.platform === "linux") {
|
|
715
|
+
return "/var/lib/lockdown";
|
|
716
|
+
}
|
|
717
|
+
return null;
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Copy a single device's lockdown plist from the native Apple lockdown directory
|
|
721
|
+
* to Alpine so usbmuxd can use it (skip pairing).
|
|
722
|
+
*
|
|
723
|
+
* Uses the platform-specific Apple/iTunes lockdown directory
|
|
724
|
+
* (e.g. C:\ProgramData\Apple\Lockdown on Windows, /var/db/lockdown on macOS).
|
|
725
|
+
*
|
|
726
|
+
* No-op if the lockdown directory cannot be determined or files don't exist.
|
|
727
|
+
*/
|
|
728
|
+
async syncToAlpine(udid) {
|
|
729
|
+
const lockdownDir = this.getAppleLockdownPath();
|
|
730
|
+
if (!lockdownDir) {
|
|
731
|
+
logWarning2("Lockdown sync to Alpine: unsupported platform, skipping");
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
logDetail(`Lockdown sync to Alpine: source dir = ${lockdownDir}, udid = ${udid}`);
|
|
735
|
+
const distro = this.wslDistribution || "alpine-usbmuxd-build";
|
|
736
|
+
const wslDestDir = this.ALPINE_LOCKDOWN_DIR;
|
|
737
|
+
const devicePlistFile = `${udid}.plist`;
|
|
738
|
+
const devicePlistPath = (0, import_node_path.join)(lockdownDir, devicePlistFile);
|
|
739
|
+
if ((0, import_node_fs.existsSync)(devicePlistPath)) {
|
|
740
|
+
await this.copyFileToAlpine(
|
|
741
|
+
this.windowsPathToWsl(devicePlistPath),
|
|
742
|
+
wslDestDir,
|
|
743
|
+
distro,
|
|
744
|
+
devicePlistFile
|
|
745
|
+
);
|
|
746
|
+
} else {
|
|
747
|
+
logDetail(
|
|
748
|
+
`Lockdown sync to Alpine: ${devicePlistFile} not found at ${devicePlistPath}, skipping`
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
const systemConfigPath = (0, import_node_path.join)(lockdownDir, this.SYSTEM_CONFIG_PLIST);
|
|
752
|
+
if ((0, import_node_fs.existsSync)(systemConfigPath)) {
|
|
753
|
+
await this.copyFileToAlpine(
|
|
754
|
+
this.windowsPathToWsl(systemConfigPath),
|
|
755
|
+
wslDestDir,
|
|
756
|
+
distro,
|
|
757
|
+
this.SYSTEM_CONFIG_PLIST
|
|
758
|
+
);
|
|
759
|
+
} else {
|
|
760
|
+
logDetail(
|
|
761
|
+
`Lockdown sync to Alpine: ${this.SYSTEM_CONFIG_PLIST} not found at ${systemConfigPath}, skipping`
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Copy a single file into the Alpine lockdown directory.
|
|
767
|
+
* Falls back to sudo if the initial copy fails.
|
|
768
|
+
*/
|
|
769
|
+
async copyFileToAlpine(wslSource, wslDestDir, distro, fileName) {
|
|
770
|
+
try {
|
|
771
|
+
await execAsync2(
|
|
772
|
+
`wsl -d ${distro} -- sh -c "mkdir -p ${wslDestDir} && cp '${wslSource}' '${wslDestDir}/'"`
|
|
773
|
+
);
|
|
774
|
+
logInfo2(`Lockdown sync: copied ${fileName} to Alpine`);
|
|
775
|
+
} catch (error) {
|
|
776
|
+
try {
|
|
777
|
+
await execAsync2(
|
|
778
|
+
`wsl -d ${distro} -- sh -c "mkdir -p ${wslDestDir} && sudo cp '${wslSource}' '${wslDestDir}/'"`
|
|
779
|
+
);
|
|
780
|
+
logInfo2(`Lockdown sync: copied ${fileName} to Alpine (via sudo)`);
|
|
781
|
+
} catch (sudoError) {
|
|
782
|
+
logWarning2(
|
|
783
|
+
`Lockdown sync to Alpine failed for ${fileName}: ${error}. Sudo fallback failed: ${sudoError}. Ensure /var/lib/lockdown is writable or use passwordless sudo.`
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Copy lockdown plists from Alpine back to the native Apple lockdown directory
|
|
790
|
+
* (for devices that were assigned this session).
|
|
791
|
+
* Also copies SystemConfiguration.plist if present in Alpine.
|
|
792
|
+
*
|
|
793
|
+
* No-op if the lockdown directory cannot be determined.
|
|
794
|
+
*/
|
|
795
|
+
async syncFromAlpine(udids) {
|
|
796
|
+
logInfo2(`Lockdown sync from Alpine: starting (${udids.length} device(s))`);
|
|
797
|
+
const lockdownDir = this.getAppleLockdownPath();
|
|
798
|
+
if (!lockdownDir) {
|
|
799
|
+
logWarning2("Lockdown sync: unsupported platform, skipping");
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
logDetail(`Lockdown sync: target dir = ${lockdownDir}`);
|
|
803
|
+
try {
|
|
804
|
+
(0, import_node_fs.mkdirSync)(lockdownDir, { recursive: true });
|
|
805
|
+
} catch (error) {
|
|
806
|
+
logWarning2(`Lockdown sync: could not create lockdown dir ${lockdownDir}: ${error}`);
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
const distro = this.wslDistribution || "alpine-usbmuxd-build";
|
|
810
|
+
const wslDestDir = this.windowsPathToWsl(lockdownDir);
|
|
78
811
|
try {
|
|
79
|
-
await
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
logWarning(
|
|
85
|
-
`Lockdown sync to Alpine failed for ${udid}: ${error}. Sudo fallback failed: ${sudoError}. Ensure /var/lib/lockdown is writable or use passwordless sudo.`
|
|
86
|
-
);
|
|
812
|
+
const { stdout } = await execAsync2(`wsl -d ${distro} -- ls -la ${this.ALPINE_LOCKDOWN_DIR}/`);
|
|
813
|
+
logDetail(`Lockdown sync: Alpine ${this.ALPINE_LOCKDOWN_DIR} contents:
|
|
814
|
+
${stdout}`);
|
|
815
|
+
} catch (error) {
|
|
816
|
+
logWarning2(`Lockdown sync: could not list Alpine lockdown dir: ${error}`);
|
|
87
817
|
}
|
|
818
|
+
const filesToSync = [...udids.map((udid) => `${udid}.plist`), this.SYSTEM_CONFIG_PLIST];
|
|
819
|
+
logDetail(`Lockdown sync: files to sync = [${filesToSync.join(", ")}]`);
|
|
820
|
+
for (const fileName of filesToSync) {
|
|
821
|
+
await this.copyFileFromAlpine(fileName, wslDestDir, distro, lockdownDir);
|
|
822
|
+
}
|
|
823
|
+
logInfo2("Lockdown sync from Alpine: done");
|
|
88
824
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
(0, import_node_fs.mkdirSync)(windowsDir, { recursive: true });
|
|
97
|
-
} catch (error) {
|
|
98
|
-
logWarning(`Lockdown sync: could not create Windows dir ${windowsDir}: ${error}`);
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
const distro = config2.wslDistribution || "alpine-usbmuxd-build";
|
|
102
|
-
const wslDestDir = windowsPathToWsl(windowsDir);
|
|
103
|
-
for (const udid of udids) {
|
|
104
|
-
const plist = `${udid}.plist`;
|
|
825
|
+
/**
|
|
826
|
+
* Copy a single file from the Alpine lockdown directory back to Windows.
|
|
827
|
+
* Skips if the file does not exist in Alpine; logs a warning if the copy fails.
|
|
828
|
+
*/
|
|
829
|
+
async copyFileFromAlpine(fileName, wslDestDir, distro, lockdownDir) {
|
|
830
|
+
const alpinePath = `${this.ALPINE_LOCKDOWN_DIR}/${fileName}`;
|
|
105
831
|
try {
|
|
106
|
-
await
|
|
107
|
-
`wsl -d ${distro} -- sh -c "test -f '${ALPINE_LOCKDOWN_DIR}/${plist}' && cp '${ALPINE_LOCKDOWN_DIR}/${plist}' '${wslDestDir}/'"`
|
|
108
|
-
);
|
|
109
|
-
logInfo(`Lockdown sync: copied ${plist} from Alpine to Windows`);
|
|
832
|
+
await execAsync2(`wsl -d ${distro} -- test -f '${alpinePath}'`);
|
|
110
833
|
} catch {
|
|
834
|
+
logDetail(`Lockdown sync: ${fileName} not found in Alpine (${alpinePath}), skipping`);
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
logDetail(`Lockdown sync: copying ${fileName} \u2192 ${wslDestDir}/`);
|
|
838
|
+
try {
|
|
839
|
+
await execAsync2(`wsl -d ${distro} -- cp -f '${alpinePath}' '${wslDestDir}/'`);
|
|
840
|
+
logInfo2(`Lockdown sync: copied ${fileName} from Alpine to ${lockdownDir}`);
|
|
841
|
+
} catch (error) {
|
|
842
|
+
logWarning2(`Lockdown sync: failed to copy ${fileName} from Alpine to ${lockdownDir}: ${error}`);
|
|
111
843
|
}
|
|
112
844
|
}
|
|
113
|
-
|
|
114
|
-
await execAsync(
|
|
115
|
-
`wsl -d ${distro} -- sh -c "test -f '${ALPINE_LOCKDOWN_DIR}/${SYSTEM_CONFIG_PLIST}' && cp '${ALPINE_LOCKDOWN_DIR}/${SYSTEM_CONFIG_PLIST}' '${wslDestDir}/'"`
|
|
116
|
-
);
|
|
117
|
-
logInfo(`Lockdown sync: copied ${SYSTEM_CONFIG_PLIST} from Alpine to Windows`);
|
|
118
|
-
} catch {
|
|
119
|
-
}
|
|
120
|
-
}
|
|
845
|
+
};
|
|
121
846
|
|
|
122
847
|
// src/InstanceManager.ts
|
|
123
|
-
var {
|
|
124
|
-
var execAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.exec);
|
|
125
|
-
var USBIPD_PATH = '"C:\\Program Files\\usbipd-win\\usbipd.exe"';
|
|
126
|
-
function parseUsbipdList(output) {
|
|
127
|
-
const devices = [];
|
|
128
|
-
const lines = output.split(/\r?\n/);
|
|
129
|
-
for (const line of lines) {
|
|
130
|
-
const match = line.match(/^(\d+-\d+)\s+([0-9a-f]{4}):([0-9a-f]{4})\s+(.+)$/i);
|
|
131
|
-
if (match) {
|
|
132
|
-
const rest = match[4].trim();
|
|
133
|
-
const stateMatch = rest.match(/^(.+?)\s{2,}(\S.*)$/);
|
|
134
|
-
const description = stateMatch ? stateMatch[1].trim() : rest;
|
|
135
|
-
const state = stateMatch ? stateMatch[2].trim() : "Unknown";
|
|
136
|
-
devices.push({
|
|
137
|
-
busId: match[1],
|
|
138
|
-
vid: match[2].toUpperCase(),
|
|
139
|
-
pid: match[3].toUpperCase(),
|
|
140
|
-
description,
|
|
141
|
-
state
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return devices;
|
|
146
|
-
}
|
|
848
|
+
var { logWarning: logWarning3, logDetail: logDetail2, logDataObject, logTask } = (0, import_tool_debug_g43.createLoggers)("instance-manager");
|
|
147
849
|
var DEFAULT_CONFIG = {
|
|
148
850
|
batchSize: 4,
|
|
149
851
|
basePort: 27015,
|
|
150
|
-
maxInstances:
|
|
852
|
+
maxInstances: 5,
|
|
151
853
|
usbmuxdPath: "usbmuxd",
|
|
152
854
|
// Path inside WSL2
|
|
153
855
|
wslDistribution: "alpine-usbmuxd-build",
|
|
154
856
|
// Alpine WSL2 distribution name
|
|
155
857
|
verboseLogging: true,
|
|
156
|
-
appleVendorId: "05AC"
|
|
157
|
-
lockdownWindowsPath: "C:\\ProgramData\\mce\\lockdown",
|
|
158
|
-
lockdownSyncEnabled: true
|
|
858
|
+
appleVendorId: "05AC"
|
|
159
859
|
};
|
|
160
860
|
var InstanceManager = class extends import_node_events.EventEmitter {
|
|
161
|
-
|
|
861
|
+
/** Map of usbmuxd instance IDs to instances */
|
|
162
862
|
instances = /* @__PURE__ */ new Map();
|
|
863
|
+
/** Map of device IDs to device mappings host:port */
|
|
163
864
|
deviceMappings = /* @__PURE__ */ new Map();
|
|
865
|
+
/** Map of usbmuxd instance IDs to child processes running it on wsl2*/
|
|
164
866
|
processes = /* @__PURE__ */ new Map();
|
|
165
|
-
nextInstanceId = 1;
|
|
166
|
-
startedAt = null;
|
|
167
|
-
isRunning = false;
|
|
168
867
|
/** Tracks which devices have been attached to WSL */
|
|
169
868
|
attachedDevices = /* @__PURE__ */ new Set();
|
|
170
|
-
/** Device IDs currently in the attach flow
|
|
869
|
+
/** Device IDs currently in the attach flow
|
|
870
|
+
* (ignore disconnect until attach completes) */
|
|
171
871
|
pendingAttachDevices = /* @__PURE__ */ new Set();
|
|
172
|
-
|
|
173
|
-
|
|
872
|
+
config;
|
|
873
|
+
nextInstanceId = 1;
|
|
874
|
+
startedAt = null;
|
|
875
|
+
isRunning = false;
|
|
876
|
+
wsl;
|
|
877
|
+
usbipd;
|
|
174
878
|
constructor(config2 = {}) {
|
|
175
879
|
super();
|
|
176
880
|
this.config = { ...DEFAULT_CONFIG, ...config2 };
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
* Detect the WSL2 IP address for the configured distribution
|
|
180
|
-
* This IP is needed to connect from Windows to services inside WSL
|
|
181
|
-
*/
|
|
182
|
-
async detectWslIpAddress() {
|
|
183
|
-
if (this.wslIpAddress) {
|
|
184
|
-
return this.wslIpAddress;
|
|
185
|
-
}
|
|
186
|
-
const distro = this.config.wslDistribution || "alpine-usbmuxd-build";
|
|
187
|
-
try {
|
|
188
|
-
const { stdout } = await execAsync2(`wsl -d ${distro} -- ip -4 addr show eth0`);
|
|
189
|
-
const match = stdout.match(/inet\s+(\d+\.\d+\.\d+\.\d+)/);
|
|
190
|
-
if (match) {
|
|
191
|
-
this.wslIpAddress = match[1];
|
|
192
|
-
logInfo2(`Detected WSL IP address: ${this.wslIpAddress}`);
|
|
193
|
-
return this.wslIpAddress;
|
|
194
|
-
}
|
|
195
|
-
} catch (error) {
|
|
196
|
-
logWarning2(`Failed to detect WSL IP via ip addr: ${error}`);
|
|
197
|
-
}
|
|
198
|
-
try {
|
|
199
|
-
const { stdout } = await execAsync2(`wsl -d ${distro} -- hostname -I`);
|
|
200
|
-
const ip = stdout.trim().split(/\s+/)[0];
|
|
201
|
-
if (ip && /^\d+\.\d+\.\d+\.\d+$/.test(ip)) {
|
|
202
|
-
this.wslIpAddress = ip;
|
|
203
|
-
logInfo2(`Detected WSL IP address (hostname): ${this.wslIpAddress}`);
|
|
204
|
-
return this.wslIpAddress;
|
|
205
|
-
}
|
|
206
|
-
} catch (error) {
|
|
207
|
-
logWarning2(`Failed to detect WSL IP via hostname: ${error}`);
|
|
208
|
-
}
|
|
209
|
-
logWarning2("Could not detect WSL IP, falling back to localhost");
|
|
210
|
-
this.wslIpAddress = "127.0.0.1";
|
|
211
|
-
return this.wslIpAddress;
|
|
881
|
+
this.wsl = new Wsl2(this.config.wslDistribution);
|
|
882
|
+
this.usbipd = new Usbipd(process.env.USBIPD_PATH ?? "usbipd");
|
|
212
883
|
}
|
|
213
884
|
/**
|
|
214
885
|
* Start the instance manager
|
|
@@ -219,7 +890,6 @@ var InstanceManager = class extends import_node_events.EventEmitter {
|
|
|
219
890
|
}
|
|
220
891
|
this.isRunning = true;
|
|
221
892
|
this.startedAt = /* @__PURE__ */ new Date();
|
|
222
|
-
this.emit("started");
|
|
223
893
|
}
|
|
224
894
|
/**
|
|
225
895
|
* Stop the instance manager and all instances
|
|
@@ -230,147 +900,122 @@ var InstanceManager = class extends import_node_events.EventEmitter {
|
|
|
230
900
|
}
|
|
231
901
|
this.isRunning = false;
|
|
232
902
|
const udids = Array.from(this.deviceMappings.keys());
|
|
233
|
-
await syncFromAlpine(udids
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
);
|
|
237
|
-
await Promise.all(detachPromises);
|
|
903
|
+
await this.wsl.syncFromAlpine(udids);
|
|
904
|
+
await this.usbipd.detachAllDevicesFromWsl();
|
|
905
|
+
await this.usbipd.unbindAllDevicesFromWsl();
|
|
238
906
|
this.attachedDevices.clear();
|
|
239
907
|
const stopPromises = Array.from(this.instances.keys()).map((id) => this.stopInstance(id));
|
|
240
908
|
await Promise.all(stopPromises);
|
|
241
909
|
this.instances.clear();
|
|
242
910
|
this.deviceMappings.clear();
|
|
243
911
|
this.processes.clear();
|
|
244
|
-
this.emit("stopped");
|
|
245
912
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const devicePid = device.pid.toString(16).toUpperCase().padStart(4, "0");
|
|
255
|
-
logInfo2(`Looking for device with VID:PID ${deviceVid}:${devicePid}`);
|
|
256
|
-
logInfo2(`Found ${usbipdDevices.length} devices from usbipd list`);
|
|
257
|
-
const match = usbipdDevices.find((d) => d.vid === deviceVid && d.pid === devicePid);
|
|
258
|
-
if (match) {
|
|
259
|
-
logInfo2(
|
|
260
|
-
`Found usbipd bus ID ${match.busId} for device ${device.deviceId} (${deviceVid}:${devicePid})`
|
|
261
|
-
);
|
|
262
|
-
return match.busId;
|
|
913
|
+
async attachToWsl({
|
|
914
|
+
busId,
|
|
915
|
+
deviceId
|
|
916
|
+
}) {
|
|
917
|
+
if (!this.attachedDevices.has(busId)) {
|
|
918
|
+
this.pendingAttachDevices.add(deviceId);
|
|
919
|
+
if (!this.config.wslDistribution) {
|
|
920
|
+
throw new Error("WSL distribution not configured");
|
|
263
921
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
`Could not find usbipd bus ID for device ${device.deviceId} (${deviceVid}:${devicePid})`
|
|
922
|
+
const attached = await this.usbipd.attachDeviceToWsl(
|
|
923
|
+
busId,
|
|
924
|
+
this.wsl,
|
|
925
|
+
this.config.wslDistribution
|
|
269
926
|
);
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
return null;
|
|
927
|
+
if (attached) {
|
|
928
|
+
this.attachedDevices.add(busId);
|
|
929
|
+
}
|
|
274
930
|
}
|
|
275
931
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
await execAsync2(`wsl -d ${distro} -- echo "WSL started"`);
|
|
285
|
-
} else {
|
|
286
|
-
logInfo2("Starting default WSL distribution...");
|
|
287
|
-
await execAsync2(`wsl -- echo "WSL started"`);
|
|
288
|
-
}
|
|
289
|
-
return true;
|
|
290
|
-
} catch (error) {
|
|
291
|
-
logWarning2(`Failed to start WSL: ${error}`);
|
|
292
|
-
return false;
|
|
932
|
+
async removeDevice(mapping) {
|
|
933
|
+
const instance = await this.detachFromWsl({ deviceId: mapping.udid });
|
|
934
|
+
if (!instance) {
|
|
935
|
+
throw new Error(`Instance ${mapping.instanceId} not found`);
|
|
936
|
+
}
|
|
937
|
+
if (mapping.busId) {
|
|
938
|
+
await this.usbipd.detachDeviceFromWsl(mapping.busId);
|
|
939
|
+
this.attachedDevices.delete(mapping.busId);
|
|
293
940
|
}
|
|
941
|
+
const deviceIndex = instance.deviceUdids.indexOf(mapping.udid);
|
|
942
|
+
if (deviceIndex > -1) {
|
|
943
|
+
instance.deviceUdids.splice(deviceIndex, 1);
|
|
944
|
+
}
|
|
945
|
+
this.deviceMappings.delete(mapping.udid);
|
|
946
|
+
return instance;
|
|
294
947
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
} else {
|
|
309
|
-
logInfo2(`Attaching device ${busId} to default WSL...`);
|
|
310
|
-
await execAsync2(`${USBIPD_PATH} attach --wsl --busid=${busId}`);
|
|
311
|
-
}
|
|
312
|
-
logInfo2(`Device ${busId} attached to WSL successfully`);
|
|
313
|
-
return true;
|
|
314
|
-
} catch (error) {
|
|
315
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
316
|
-
if (message.includes("already attached to a client")) {
|
|
317
|
-
logInfo2(`Device ${busId} is already attached to WSL, continuing`);
|
|
318
|
-
return true;
|
|
948
|
+
async onUsbmuxdInstanceEnd({
|
|
949
|
+
instanceId,
|
|
950
|
+
code,
|
|
951
|
+
signal
|
|
952
|
+
}) {
|
|
953
|
+
this.emit("instance-exited", {
|
|
954
|
+
instanceId,
|
|
955
|
+
code,
|
|
956
|
+
signal
|
|
957
|
+
});
|
|
958
|
+
for (const [_, mapping] of this.deviceMappings.entries()) {
|
|
959
|
+
if (mapping.instanceId === instanceId) {
|
|
960
|
+
this.removeDevice(mapping);
|
|
319
961
|
}
|
|
320
|
-
logWarning2(`Failed to attach device ${busId} to WSL: ${error}`);
|
|
321
|
-
return false;
|
|
322
962
|
}
|
|
963
|
+
this.instances.delete(instanceId);
|
|
964
|
+
this.processes.get(instanceId)?.kill();
|
|
965
|
+
this.processes.delete(instanceId);
|
|
323
966
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
967
|
+
async onUsbmuxdInstanceStart({
|
|
968
|
+
instance,
|
|
969
|
+
process: process2
|
|
970
|
+
}) {
|
|
971
|
+
this.instances.set(instance.id, instance);
|
|
972
|
+
this.processes.set(instance.id, process2);
|
|
973
|
+
this.emit("instance-started", {
|
|
974
|
+
instanceId: instance.id
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
async getInstance() {
|
|
978
|
+
let targetInstance = this.findInstanceWithCapacity();
|
|
979
|
+
if (!targetInstance) {
|
|
980
|
+
if (this.instances.size >= this.config.maxInstances) {
|
|
981
|
+
throw new Error(`Maximum number of instances (${this.config.maxInstances}) reached`);
|
|
337
982
|
}
|
|
983
|
+
const newInstanceId = this.nextInstanceId++;
|
|
984
|
+
let onExit = (code, signal) => {
|
|
985
|
+
this.onUsbmuxdInstanceEnd({ instanceId: newInstanceId, code, signal });
|
|
986
|
+
};
|
|
987
|
+
onExit = onExit.bind(this);
|
|
988
|
+
const { instance, process: process2 } = await this.wsl.createInstance({
|
|
989
|
+
id: newInstanceId,
|
|
990
|
+
basePort: this.config.basePort,
|
|
991
|
+
verboseLogging: this.config.verboseLogging,
|
|
992
|
+
usbmuxdPath: this.config.usbmuxdPath,
|
|
993
|
+
onLog: (message) => this.emit("instance-log", {
|
|
994
|
+
instanceId: newInstanceId,
|
|
995
|
+
level: "info",
|
|
996
|
+
message
|
|
997
|
+
}),
|
|
998
|
+
onExit
|
|
999
|
+
});
|
|
1000
|
+
this.onUsbmuxdInstanceStart({ instance, process: process2 });
|
|
1001
|
+
targetInstance = instance;
|
|
338
1002
|
}
|
|
1003
|
+
return targetInstance;
|
|
339
1004
|
}
|
|
340
1005
|
/**
|
|
341
|
-
* Handle device connection
|
|
1006
|
+
* Handle device connection only for not attached devices
|
|
342
1007
|
* Attaches device to WSL, then assigns to an existing instance or creates a new one
|
|
343
1008
|
*/
|
|
344
1009
|
async onDeviceConnected(device) {
|
|
345
|
-
|
|
346
|
-
|
|
1010
|
+
const busId = await this.usbipd.findBusIdForDevice(device);
|
|
1011
|
+
if (this.attachedDevices.has(busId)) {
|
|
1012
|
+
logTask(`Device ${device.deviceId} is already attached. skipping onDeviceConnected...`);
|
|
347
1013
|
return;
|
|
348
1014
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
if (busId && !this.attachedDevices.has(busId)) {
|
|
355
|
-
this.pendingAttachDevices.add(device.deviceId);
|
|
356
|
-
try {
|
|
357
|
-
const attached = await this.attachDeviceToWsl(busId);
|
|
358
|
-
if (attached) {
|
|
359
|
-
this.attachedDevices.add(busId);
|
|
360
|
-
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
361
|
-
}
|
|
362
|
-
} finally {
|
|
363
|
-
this.pendingAttachDevices.delete(device.deviceId);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
let targetInstance = this.findInstanceWithCapacity();
|
|
367
|
-
if (!targetInstance) {
|
|
368
|
-
if (this.instances.size >= this.config.maxInstances) {
|
|
369
|
-
throw new Error(`Maximum number of instances (${this.config.maxInstances}) reached`);
|
|
370
|
-
}
|
|
371
|
-
targetInstance = await this.createInstance();
|
|
372
|
-
}
|
|
373
|
-
targetInstance.deviceUdids.push(device.deviceId);
|
|
1015
|
+
logDataObject("Device connected", { device });
|
|
1016
|
+
await this.wsl.syncToAlpine(device.deviceId);
|
|
1017
|
+
await this.attachToWsl({ busId, deviceId: device.deviceId });
|
|
1018
|
+
const targetInstance = await this.getInstance();
|
|
374
1019
|
const mapping = {
|
|
375
1020
|
udid: device.deviceId,
|
|
376
1021
|
instanceId: targetInstance.id,
|
|
@@ -380,77 +1025,52 @@ var InstanceManager = class extends import_node_events.EventEmitter {
|
|
|
380
1025
|
busId: busId ?? void 0
|
|
381
1026
|
};
|
|
382
1027
|
this.deviceMappings.set(device.deviceId, mapping);
|
|
1028
|
+
targetInstance.deviceUdids.push(device.deviceId);
|
|
383
1029
|
this.emit("device-assigned", {
|
|
384
1030
|
device,
|
|
385
1031
|
instance: targetInstance,
|
|
386
1032
|
mapping
|
|
387
1033
|
});
|
|
1034
|
+
this.pendingAttachDevices.delete(device.deviceId);
|
|
388
1035
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
* This is required once per device before most commands will work.
|
|
392
|
-
* The pairing record is stored in WSL and persists across restarts.
|
|
393
|
-
*
|
|
394
|
-
* @param udid Device UDID to pair
|
|
395
|
-
* @param goIosPath Optional path to go-ios binary (defaults to "ios")
|
|
396
|
-
* @returns true if pairing succeeded, false otherwise
|
|
397
|
-
*/
|
|
398
|
-
async pairDevice(udid, goIosPath = "ios") {
|
|
399
|
-
const mapping = this.deviceMappings.get(udid);
|
|
1036
|
+
async detachFromWsl({ deviceId }) {
|
|
1037
|
+
const mapping = this.deviceMappings.get(deviceId);
|
|
400
1038
|
if (!mapping) {
|
|
401
|
-
|
|
402
|
-
return false;
|
|
1039
|
+
throw new Error(`Device ${deviceId} was not tracked`);
|
|
403
1040
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
const { stderr } = await execAsync2(`"${goIosPath}" pair --udid=${udid}`, {
|
|
408
|
-
env: { ...process.env, USBMUXD_SOCKET_ADDRESS: socketAddress }
|
|
409
|
-
});
|
|
410
|
-
if (stderr?.includes("error")) {
|
|
411
|
-
logWarning2(`Pairing warning for ${udid}: ${stderr}`);
|
|
412
|
-
}
|
|
413
|
-
logInfo2(`Device ${udid} paired successfully`);
|
|
414
|
-
this.emit("device-paired", { udid, mapping });
|
|
415
|
-
return true;
|
|
416
|
-
} catch (error) {
|
|
417
|
-
logWarning2(`Failed to pair device ${udid}: ${error}`);
|
|
418
|
-
return false;
|
|
1041
|
+
if (mapping.busId) {
|
|
1042
|
+
await this.usbipd.detachDeviceFromWsl(mapping.busId);
|
|
1043
|
+
this.attachedDevices.delete(mapping.busId);
|
|
419
1044
|
}
|
|
1045
|
+
const instance = this.instances.get(mapping.instanceId);
|
|
1046
|
+
if (!instance) {
|
|
1047
|
+
logWarning3(`Instance ${mapping.instanceId} not found`);
|
|
1048
|
+
this.deviceMappings.delete(deviceId);
|
|
1049
|
+
throw new Error(`Instance ${mapping.instanceId} not found`);
|
|
1050
|
+
}
|
|
1051
|
+
return instance;
|
|
420
1052
|
}
|
|
421
1053
|
/**
|
|
422
|
-
* Handle device disconnection
|
|
1054
|
+
* Handle device disconnection only for attached devices
|
|
423
1055
|
* Detaches from WSL, removes device from instance, and stops instance if empty
|
|
424
1056
|
*/
|
|
425
1057
|
async onDeviceDisconnected(device) {
|
|
426
|
-
if (this.
|
|
1058
|
+
if (!this.attachedDevices.has(device.deviceId)) {
|
|
1059
|
+
logTask(`Device ${device.deviceId} is not attached. skipping onDeviceDisconnected...`);
|
|
427
1060
|
return;
|
|
428
1061
|
}
|
|
1062
|
+
logDataObject("Device disconnected", { device });
|
|
429
1063
|
const mapping = this.deviceMappings.get(device.deviceId);
|
|
430
1064
|
if (!mapping) {
|
|
431
|
-
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
if (mapping.busId) {
|
|
435
|
-
await this.detachDeviceFromWsl(mapping.busId);
|
|
436
|
-
this.attachedDevices.delete(mapping.busId);
|
|
437
|
-
}
|
|
438
|
-
const instance = this.instances.get(mapping.instanceId);
|
|
439
|
-
if (!instance) {
|
|
440
|
-
logWarning2(`Instance ${mapping.instanceId} not found`);
|
|
441
|
-
this.deviceMappings.delete(device.deviceId);
|
|
442
|
-
return;
|
|
1065
|
+
throw new Error(`Device ${device.deviceId} was not tracked`);
|
|
443
1066
|
}
|
|
444
|
-
const
|
|
445
|
-
if (deviceIndex > -1) {
|
|
446
|
-
instance.deviceUdids.splice(deviceIndex, 1);
|
|
447
|
-
}
|
|
448
|
-
this.deviceMappings.delete(device.deviceId);
|
|
1067
|
+
const instance = await this.removeDevice(mapping);
|
|
449
1068
|
this.emit("device-removed", {
|
|
450
1069
|
device,
|
|
451
1070
|
instance
|
|
452
1071
|
});
|
|
453
1072
|
if (instance.deviceUdids.length === 0) {
|
|
1073
|
+
logDetail2(`Instance ${instance.id} is empty, stopping...`);
|
|
454
1074
|
await this.stopInstance(instance.id);
|
|
455
1075
|
}
|
|
456
1076
|
}
|
|
@@ -465,104 +1085,18 @@ var InstanceManager = class extends import_node_events.EventEmitter {
|
|
|
465
1085
|
}
|
|
466
1086
|
return null;
|
|
467
1087
|
}
|
|
468
|
-
/**
|
|
469
|
-
* Create a new usbmuxd instance
|
|
470
|
-
*/
|
|
471
|
-
async createInstance() {
|
|
472
|
-
const instanceId = this.nextInstanceId++;
|
|
473
|
-
const port = this.config.basePort + instanceId - 1;
|
|
474
|
-
const host = await this.detectWslIpAddress();
|
|
475
|
-
const usbmuxdArgs = [
|
|
476
|
-
"-f",
|
|
477
|
-
// Foreground
|
|
478
|
-
"-v",
|
|
479
|
-
// Verbose (if enabled)
|
|
480
|
-
"-S",
|
|
481
|
-
`0.0.0.0:${port}`,
|
|
482
|
-
// Listen on all interfaces (for Windows → WSL2)
|
|
483
|
-
"--pidfile",
|
|
484
|
-
"NONE"
|
|
485
|
-
];
|
|
486
|
-
if (!this.config.verboseLogging) {
|
|
487
|
-
usbmuxdArgs.splice(1, 1);
|
|
488
|
-
}
|
|
489
|
-
const wslArgs = [
|
|
490
|
-
"-d",
|
|
491
|
-
this.config.wslDistribution || "alpine-usbmuxd-build",
|
|
492
|
-
this.config.usbmuxdPath,
|
|
493
|
-
...usbmuxdArgs
|
|
494
|
-
];
|
|
495
|
-
const process2 = (0, import_node_child_process2.spawn)("wsl", wslArgs, {
|
|
496
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
497
|
-
windowsHide: false
|
|
498
|
-
// Show console for debugging
|
|
499
|
-
});
|
|
500
|
-
process2.stdout?.on("data", (data) => {
|
|
501
|
-
this.emit("instance-log", {
|
|
502
|
-
instanceId,
|
|
503
|
-
level: "info",
|
|
504
|
-
message: data.toString().trim()
|
|
505
|
-
});
|
|
506
|
-
});
|
|
507
|
-
process2.stderr?.on("data", (data) => {
|
|
508
|
-
this.emit("instance-log", {
|
|
509
|
-
instanceId,
|
|
510
|
-
level: "error",
|
|
511
|
-
message: data.toString().trim()
|
|
512
|
-
});
|
|
513
|
-
});
|
|
514
|
-
process2.on("exit", (code, signal) => {
|
|
515
|
-
this.emit("instance-exited", {
|
|
516
|
-
instanceId,
|
|
517
|
-
code,
|
|
518
|
-
signal
|
|
519
|
-
});
|
|
520
|
-
if (this.instances.has(instanceId)) {
|
|
521
|
-
this.instances.delete(instanceId);
|
|
522
|
-
this.processes.delete(instanceId);
|
|
523
|
-
}
|
|
524
|
-
});
|
|
525
|
-
const pid = process2.pid;
|
|
526
|
-
if (pid === void 0) {
|
|
527
|
-
process2.kill("SIGKILL");
|
|
528
|
-
throw new Error("Failed to get PID for usbmuxd instance");
|
|
529
|
-
}
|
|
530
|
-
const instance = {
|
|
531
|
-
id: instanceId,
|
|
532
|
-
host,
|
|
533
|
-
port,
|
|
534
|
-
pid,
|
|
535
|
-
deviceUdids: [],
|
|
536
|
-
startedAt: /* @__PURE__ */ new Date()
|
|
537
|
-
};
|
|
538
|
-
this.instances.set(instanceId, instance);
|
|
539
|
-
this.processes.set(instanceId, process2);
|
|
540
|
-
this.emit("instance-started", instance);
|
|
541
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
542
|
-
return instance;
|
|
543
|
-
}
|
|
544
1088
|
/**
|
|
545
1089
|
* Stop a specific instance
|
|
546
1090
|
*/
|
|
547
1091
|
async stopInstance(instanceId) {
|
|
548
1092
|
const instance = this.instances.get(instanceId);
|
|
549
|
-
const
|
|
550
|
-
if (!instance || !
|
|
1093
|
+
const childProcess = this.processes.get(instanceId);
|
|
1094
|
+
if (!instance || !childProcess) {
|
|
551
1095
|
return;
|
|
552
1096
|
}
|
|
553
|
-
process2.
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
if (!process2.killed) {
|
|
557
|
-
process2.kill("SIGKILL");
|
|
558
|
-
}
|
|
559
|
-
resolve();
|
|
560
|
-
}, 5e3);
|
|
561
|
-
process2.once("exit", () => {
|
|
562
|
-
clearTimeout(timeout);
|
|
563
|
-
resolve();
|
|
564
|
-
});
|
|
565
|
-
});
|
|
1097
|
+
for (const process2 of this.processes.values()) {
|
|
1098
|
+
process2.kill("SIGKILL");
|
|
1099
|
+
}
|
|
566
1100
|
this.instances.delete(instanceId);
|
|
567
1101
|
this.processes.delete(instanceId);
|
|
568
1102
|
for (const [udid, mapping] of this.deviceMappings.entries()) {
|
|
@@ -614,7 +1148,7 @@ var InstanceManager = class extends import_node_events.EventEmitter {
|
|
|
614
1148
|
};
|
|
615
1149
|
|
|
616
1150
|
// src/UsbmuxdService.ts
|
|
617
|
-
var { logInfo: logInfo3, logError } = (0,
|
|
1151
|
+
var { logInfo: logInfo3, logError, logDataObject: logDataObject2 } = (0, import_tool_debug_g44.createLoggers)("usbmuxd-service");
|
|
618
1152
|
var UsbmuxdService = class extends import_node_events2.EventEmitter {
|
|
619
1153
|
manager;
|
|
620
1154
|
usbListener;
|
|
@@ -638,6 +1172,9 @@ var UsbmuxdService = class extends import_node_events2.EventEmitter {
|
|
|
638
1172
|
this.manager.on(
|
|
639
1173
|
"device-assigned",
|
|
640
1174
|
(payload) => {
|
|
1175
|
+
logInfo3(
|
|
1176
|
+
`Device ${payload.device.deviceId} connected to usbmuxd instance at ${payload.mapping.host}:${payload.mapping.port}`
|
|
1177
|
+
);
|
|
641
1178
|
this.emit("device-assigned", payload);
|
|
642
1179
|
}
|
|
643
1180
|
);
|
|
@@ -673,12 +1210,7 @@ var UsbmuxdService = class extends import_node_events2.EventEmitter {
|
|
|
673
1210
|
}
|
|
674
1211
|
const config2 = this.manager.getConfig();
|
|
675
1212
|
const appleVid = Number.parseInt(config2.appleVendorId, 16);
|
|
676
|
-
|
|
677
|
-
logInfo3(`Batch size: ${config2.batchSize} devices per instance`);
|
|
678
|
-
logInfo3(`Base port: ${config2.basePort}`);
|
|
679
|
-
logInfo3(`Max instances: ${config2.maxInstances}`);
|
|
680
|
-
logInfo3(`usbmuxd path: ${config2.usbmuxdPath}`);
|
|
681
|
-
logInfo3(`Monitoring Apple devices (VID: ${config2.appleVendorId})`);
|
|
1213
|
+
logDataObject2("Starting service...", { config: config2 });
|
|
682
1214
|
this.usbListener.onDeviceAdd(async (device) => {
|
|
683
1215
|
if (device.vid !== appleVid) {
|
|
684
1216
|
return;
|
|
@@ -759,22 +1291,11 @@ var UsbmuxdService = class extends import_node_events2.EventEmitter {
|
|
|
759
1291
|
getConfig() {
|
|
760
1292
|
return this.manager.getConfig();
|
|
761
1293
|
}
|
|
762
|
-
/**
|
|
763
|
-
* Pair a device with the usbmuxd host.
|
|
764
|
-
* This is required once per device before most commands will work.
|
|
765
|
-
* The pairing record is stored in WSL and persists across restarts.
|
|
766
|
-
*
|
|
767
|
-
* @param udid Device UDID to pair
|
|
768
|
-
* @param goIosPath Optional path to go-ios binary
|
|
769
|
-
* @returns true if pairing succeeded, false otherwise
|
|
770
|
-
*/
|
|
771
|
-
async pairDevice(udid, goIosPath) {
|
|
772
|
-
return this.manager.pairDevice(udid, goIosPath);
|
|
773
|
-
}
|
|
774
1294
|
};
|
|
775
1295
|
|
|
776
1296
|
// src/cli.ts
|
|
777
|
-
|
|
1297
|
+
import_dotenv.default.config();
|
|
1298
|
+
var { logInfo: logInfo4, logError: logError2, logHeader, logDataObject: logDataObject3 } = (0, import_tool_debug_g45.createLoggers)("usbmuxd-cli");
|
|
778
1299
|
var args = process.argv.slice(2);
|
|
779
1300
|
var options = {};
|
|
780
1301
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -796,31 +1317,25 @@ var config = {
|
|
|
796
1317
|
appleVendorId: options.appleVid || "05AC"
|
|
797
1318
|
};
|
|
798
1319
|
logHeader("usbmuxd Instance Manager");
|
|
799
|
-
|
|
1320
|
+
logDataObject3("Multi-instance manager for iOS device connections", { config });
|
|
800
1321
|
var service = new UsbmuxdService(config);
|
|
801
|
-
|
|
1322
|
+
async function shutdown(eventName) {
|
|
802
1323
|
logInfo4("");
|
|
803
|
-
logInfo4(
|
|
1324
|
+
logInfo4(`Received ${eventName}, shutting down gracefully...`);
|
|
804
1325
|
try {
|
|
805
1326
|
await service.stop();
|
|
806
1327
|
logInfo4("Shutdown complete");
|
|
807
1328
|
process.exit(0);
|
|
808
1329
|
} catch (error) {
|
|
809
|
-
logError2(
|
|
1330
|
+
logError2(`Error during ${eventName}:`, error);
|
|
810
1331
|
process.exit(1);
|
|
811
1332
|
}
|
|
1333
|
+
}
|
|
1334
|
+
process.on("SIGINT", async () => {
|
|
1335
|
+
void shutdown("SIGINT");
|
|
812
1336
|
});
|
|
813
1337
|
process.on("SIGTERM", async () => {
|
|
814
|
-
|
|
815
|
-
logInfo4("Received SIGTERM, shutting down gracefully...");
|
|
816
|
-
try {
|
|
817
|
-
await service.stop();
|
|
818
|
-
logInfo4("Shutdown complete");
|
|
819
|
-
process.exit(0);
|
|
820
|
-
} catch (error) {
|
|
821
|
-
logError2("Error during shutdown:", error);
|
|
822
|
-
process.exit(1);
|
|
823
|
-
}
|
|
1338
|
+
void shutdown("SIGTERM");
|
|
824
1339
|
});
|
|
825
1340
|
setInterval(() => {
|
|
826
1341
|
const stats = service.getStats();
|
|
@@ -829,7 +1344,7 @@ setInterval(() => {
|
|
|
829
1344
|
`Uptime: ${stats.uptimeSeconds}s | Instances: ${stats.instanceCount} | Devices: ${stats.deviceCount}`
|
|
830
1345
|
);
|
|
831
1346
|
if (instances.length > 0) {
|
|
832
|
-
|
|
1347
|
+
logDataObject3("Active instances:", { instances });
|
|
833
1348
|
}
|
|
834
1349
|
}, 3e4);
|
|
835
1350
|
if (args.includes("--help") || args.includes("-h")) {
|
|
@@ -853,7 +1368,7 @@ if (args.includes("--help") || args.includes("-h")) {
|
|
|
853
1368
|
try {
|
|
854
1369
|
service.start();
|
|
855
1370
|
} catch (error) {
|
|
856
|
-
logError2("Failed to start service
|
|
857
|
-
|
|
1371
|
+
logError2("Failed to start service", error);
|
|
1372
|
+
shutdown("Failed to start service");
|
|
858
1373
|
}
|
|
859
1374
|
//# sourceMappingURL=cli.js.map
|