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