sessix-server 0.3.9 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +536 -45
- package/dist/server.js +530 -39
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -24,10 +24,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// src/index.ts
|
|
27
|
-
var
|
|
27
|
+
var import_node_os9 = require("os");
|
|
28
28
|
var import_node_fs4 = require("fs");
|
|
29
|
-
var
|
|
30
|
-
var
|
|
29
|
+
var import_node_path8 = require("path");
|
|
30
|
+
var import_node_child_process11 = require("child_process");
|
|
31
31
|
|
|
32
32
|
// src/i18n/locales/zh.ts
|
|
33
33
|
var zh = {
|
|
@@ -302,12 +302,12 @@ function t(key, params) {
|
|
|
302
302
|
}
|
|
303
303
|
|
|
304
304
|
// src/server.ts
|
|
305
|
-
var
|
|
306
|
-
var
|
|
307
|
-
var
|
|
308
|
-
var
|
|
309
|
-
var
|
|
310
|
-
var
|
|
305
|
+
var import_uuid7 = require("uuid");
|
|
306
|
+
var import_promises5 = require("fs/promises");
|
|
307
|
+
var import_node_os8 = require("os");
|
|
308
|
+
var import_node_path7 = require("path");
|
|
309
|
+
var import_node_child_process10 = require("child_process");
|
|
310
|
+
var import_node_util2 = require("util");
|
|
311
311
|
|
|
312
312
|
// src/providers/ProcessProvider.ts
|
|
313
313
|
var import_child_process = require("child_process");
|
|
@@ -3126,9 +3126,26 @@ process.stdin.on('end', async () => {
|
|
|
3126
3126
|
signal: AbortSignal.timeout(320000),
|
|
3127
3127
|
})
|
|
3128
3128
|
const data = await res.json()
|
|
3129
|
-
|
|
3129
|
+
const decision = data.decision === 'deny' ? 'deny' : 'allow'
|
|
3130
|
+
const output = {
|
|
3131
|
+
hookSpecificOutput: {
|
|
3132
|
+
hookEventName: 'PreToolUse',
|
|
3133
|
+
permissionDecision: decision,
|
|
3134
|
+
},
|
|
3135
|
+
}
|
|
3136
|
+
if (decision === 'deny' && data.reason) {
|
|
3137
|
+
output.hookSpecificOutput.permissionDecisionReason = String(data.reason)
|
|
3138
|
+
}
|
|
3139
|
+
process.stdout.write(JSON.stringify(output))
|
|
3140
|
+
process.exit(0)
|
|
3130
3141
|
} catch {
|
|
3131
|
-
// Sessix \u670D\u52A1\u5668\u4E0D\u53EF\u7528\uFF0C\u9ED8\u8BA4\u653E\u884C
|
|
3142
|
+
// Sessix \u670D\u52A1\u5668\u4E0D\u53EF\u7528\uFF0C\u9ED8\u8BA4\u653E\u884C\uFF08\u8F93\u51FA\u663E\u5F0F allow\uFF0C\u907F\u514D\u843D\u5230\u7EC8\u7AEF\u63D0\u793A\uFF09
|
|
3143
|
+
process.stdout.write(JSON.stringify({
|
|
3144
|
+
hookSpecificOutput: {
|
|
3145
|
+
hookEventName: 'PreToolUse',
|
|
3146
|
+
permissionDecision: 'allow',
|
|
3147
|
+
},
|
|
3148
|
+
}))
|
|
3132
3149
|
process.exit(0)
|
|
3133
3150
|
}
|
|
3134
3151
|
})
|
|
@@ -3171,15 +3188,19 @@ var HookInstaller = class {
|
|
|
3171
3188
|
console.log("[HookInstaller] Hook uninstalled");
|
|
3172
3189
|
}
|
|
3173
3190
|
/**
|
|
3174
|
-
* 检查 hook
|
|
3175
|
-
*
|
|
3191
|
+
* 检查 hook 是否已安装且为最新版本
|
|
3192
|
+
*
|
|
3193
|
+
* 必须同时满足:
|
|
3194
|
+
* 1. 两个脚本文件都存在
|
|
3195
|
+
* 2. settings.json 中有 Sessix hook 配置
|
|
3196
|
+
* 3. approval-hook.js 包含最新的 permissionDecision 输出协议
|
|
3197
|
+
* (旧版仅用 exit code,会导致 ExitPlanMode 等工具卡住)
|
|
3176
3198
|
*/
|
|
3177
3199
|
async isInstalled() {
|
|
3178
|
-
let
|
|
3200
|
+
let approvalScriptContent = "";
|
|
3179
3201
|
let permissionScriptExists = false;
|
|
3180
3202
|
try {
|
|
3181
|
-
await (0, import_promises2.
|
|
3182
|
-
approvalScriptExists = true;
|
|
3203
|
+
approvalScriptContent = await (0, import_promises2.readFile)(HOOK_SCRIPT_PATH, "utf-8");
|
|
3183
3204
|
} catch {
|
|
3184
3205
|
}
|
|
3185
3206
|
try {
|
|
@@ -3187,9 +3208,10 @@ var HookInstaller = class {
|
|
|
3187
3208
|
permissionScriptExists = true;
|
|
3188
3209
|
} catch {
|
|
3189
3210
|
}
|
|
3211
|
+
const isLatestVersion = approvalScriptContent.includes("permissionDecision");
|
|
3190
3212
|
const settings = await this.readClaudeSettings();
|
|
3191
3213
|
const configExists = this.hasHookConfig(settings);
|
|
3192
|
-
return
|
|
3214
|
+
return isLatestVersion && permissionScriptExists && configExists;
|
|
3193
3215
|
}
|
|
3194
3216
|
// ============================================
|
|
3195
3217
|
// 内部方法
|
|
@@ -4363,7 +4385,7 @@ var AuthManager = class extends import_events3.EventEmitter {
|
|
|
4363
4385
|
};
|
|
4364
4386
|
|
|
4365
4387
|
// src/server.ts
|
|
4366
|
-
var
|
|
4388
|
+
var import_promises6 = require("fs/promises");
|
|
4367
4389
|
|
|
4368
4390
|
// src/terminal/TerminalExecutor.ts
|
|
4369
4391
|
var import_node_child_process7 = require("child_process");
|
|
@@ -4452,8 +4474,422 @@ var TerminalExecutor = class {
|
|
|
4452
4474
|
}
|
|
4453
4475
|
};
|
|
4454
4476
|
|
|
4455
|
-
// src/
|
|
4477
|
+
// src/xcode/XcodeBuildExecutor.ts
|
|
4456
4478
|
var import_node_child_process8 = require("child_process");
|
|
4479
|
+
var import_node_util = require("util");
|
|
4480
|
+
var import_promises4 = require("fs/promises");
|
|
4481
|
+
var import_node_path6 = require("path");
|
|
4482
|
+
var import_node_os7 = require("os");
|
|
4483
|
+
var import_uuid6 = require("uuid");
|
|
4484
|
+
var execAsync = (0, import_node_util.promisify)(import_node_child_process8.exec);
|
|
4485
|
+
var BUILD_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
4486
|
+
var INSTALL_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
4487
|
+
var CONFIG_FILE = (0, import_node_path6.join)((0, import_node_os7.homedir)(), ".sessix", "xcode-config.json");
|
|
4488
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
4489
|
+
"node_modules",
|
|
4490
|
+
".git",
|
|
4491
|
+
"DerivedData",
|
|
4492
|
+
"Pods",
|
|
4493
|
+
".build",
|
|
4494
|
+
"build",
|
|
4495
|
+
"dist",
|
|
4496
|
+
"__pycache__",
|
|
4497
|
+
".next",
|
|
4498
|
+
"vendor",
|
|
4499
|
+
".expo",
|
|
4500
|
+
"android",
|
|
4501
|
+
".gradle",
|
|
4502
|
+
"Carthage",
|
|
4503
|
+
"xcarchive"
|
|
4504
|
+
]);
|
|
4505
|
+
var MAX_SCAN_DEPTH = 4;
|
|
4506
|
+
var XcodeBuildExecutor = class {
|
|
4507
|
+
builds = /* @__PURE__ */ new Map();
|
|
4508
|
+
installs = /* @__PURE__ */ new Map();
|
|
4509
|
+
eventCallbacks = [];
|
|
4510
|
+
configCache = null;
|
|
4511
|
+
onEvent(callback) {
|
|
4512
|
+
this.eventCallbacks.push(callback);
|
|
4513
|
+
return () => {
|
|
4514
|
+
const idx = this.eventCallbacks.indexOf(callback);
|
|
4515
|
+
if (idx !== -1) this.eventCallbacks.splice(idx, 1);
|
|
4516
|
+
};
|
|
4517
|
+
}
|
|
4518
|
+
emit(event) {
|
|
4519
|
+
for (const cb of this.eventCallbacks) {
|
|
4520
|
+
try {
|
|
4521
|
+
cb(event);
|
|
4522
|
+
} catch (err) {
|
|
4523
|
+
console.error("[XcodeBuildExecutor] Event callback error:", err);
|
|
4524
|
+
}
|
|
4525
|
+
}
|
|
4526
|
+
}
|
|
4527
|
+
// ============================================
|
|
4528
|
+
// 配置持久化
|
|
4529
|
+
// ============================================
|
|
4530
|
+
async loadConfigs() {
|
|
4531
|
+
if (this.configCache) return this.configCache;
|
|
4532
|
+
try {
|
|
4533
|
+
const raw = await (0, import_promises4.readFile)(CONFIG_FILE, "utf8");
|
|
4534
|
+
this.configCache = JSON.parse(raw);
|
|
4535
|
+
} catch {
|
|
4536
|
+
this.configCache = {};
|
|
4537
|
+
}
|
|
4538
|
+
return this.configCache;
|
|
4539
|
+
}
|
|
4540
|
+
async writeConfigs(store) {
|
|
4541
|
+
await (0, import_promises4.mkdir)((0, import_node_path6.join)((0, import_node_os7.homedir)(), ".sessix"), { recursive: true });
|
|
4542
|
+
await (0, import_promises4.writeFile)(CONFIG_FILE, JSON.stringify(store, null, 2), "utf8");
|
|
4543
|
+
this.configCache = store;
|
|
4544
|
+
}
|
|
4545
|
+
async getSavedConfig(projectPath) {
|
|
4546
|
+
const store = await this.loadConfigs();
|
|
4547
|
+
return store[projectPath];
|
|
4548
|
+
}
|
|
4549
|
+
async saveConfig(projectPath, config) {
|
|
4550
|
+
const store = await this.loadConfigs();
|
|
4551
|
+
store[projectPath] = config;
|
|
4552
|
+
await this.writeConfigs(store);
|
|
4553
|
+
}
|
|
4554
|
+
// ============================================
|
|
4555
|
+
// 递归扫描 Xcode 工程
|
|
4556
|
+
// ============================================
|
|
4557
|
+
async findAllContainers(projectPath) {
|
|
4558
|
+
const results = [];
|
|
4559
|
+
await this.scanDir(projectPath, projectPath, 0, results);
|
|
4560
|
+
results.sort((a, b) => a.path.split("/").length - b.path.split("/").length);
|
|
4561
|
+
return results;
|
|
4562
|
+
}
|
|
4563
|
+
async scanDir(rootPath, currentPath, depth, results) {
|
|
4564
|
+
if (depth > MAX_SCAN_DEPTH) return;
|
|
4565
|
+
let entries;
|
|
4566
|
+
try {
|
|
4567
|
+
entries = await (0, import_promises4.readdir)(currentPath);
|
|
4568
|
+
} catch {
|
|
4569
|
+
return;
|
|
4570
|
+
}
|
|
4571
|
+
let foundWorkspace;
|
|
4572
|
+
let foundProject;
|
|
4573
|
+
for (const name of entries) {
|
|
4574
|
+
if (name.endsWith(".xcworkspace") && !name.endsWith("project.xcworkspace")) {
|
|
4575
|
+
foundWorkspace = foundWorkspace ?? name;
|
|
4576
|
+
} else if (name.endsWith(".xcodeproj")) {
|
|
4577
|
+
foundProject = foundProject ?? name;
|
|
4578
|
+
}
|
|
4579
|
+
}
|
|
4580
|
+
if (foundWorkspace || foundProject) {
|
|
4581
|
+
const relDir = currentPath === rootPath ? "" : currentPath.slice(rootPath.length + 1);
|
|
4582
|
+
const containerName = foundWorkspace ? foundWorkspace.replace(/\.xcworkspace$/, "") : foundProject.replace(/\.xcodeproj$/, "");
|
|
4583
|
+
results.push({
|
|
4584
|
+
path: relDir,
|
|
4585
|
+
name: containerName,
|
|
4586
|
+
workspace: foundWorkspace ? relDir ? `${relDir}/${foundWorkspace}` : foundWorkspace : void 0,
|
|
4587
|
+
project: foundProject ? relDir ? `${relDir}/${foundProject}` : foundProject : void 0
|
|
4588
|
+
});
|
|
4589
|
+
}
|
|
4590
|
+
for (const name of entries) {
|
|
4591
|
+
if (SKIP_DIRS.has(name)) continue;
|
|
4592
|
+
if (name.startsWith(".")) continue;
|
|
4593
|
+
if (name.endsWith(".xcodeproj") || name.endsWith(".xcworkspace")) continue;
|
|
4594
|
+
const childPath = (0, import_node_path6.join)(currentPath, name);
|
|
4595
|
+
await this.scanDir(rootPath, childPath, depth + 1, results);
|
|
4596
|
+
}
|
|
4597
|
+
}
|
|
4598
|
+
// ============================================
|
|
4599
|
+
// 探测 schemes(针对指定 container)
|
|
4600
|
+
// ============================================
|
|
4601
|
+
async detect(projectPath) {
|
|
4602
|
+
if (process.platform !== "darwin") {
|
|
4603
|
+
return { available: false, error: "Xcode \u6784\u5EFA\u53EA\u652F\u6301 macOS", containers: [], schemes: [] };
|
|
4604
|
+
}
|
|
4605
|
+
const containers = await this.findAllContainers(projectPath);
|
|
4606
|
+
if (containers.length === 0) {
|
|
4607
|
+
return { available: false, error: "\u672A\u627E\u5230 .xcworkspace \u6216 .xcodeproj", containers: [], schemes: [] };
|
|
4608
|
+
}
|
|
4609
|
+
const firstContainer = containers[0];
|
|
4610
|
+
const schemes = await this.getSchemesForContainer(projectPath, firstContainer);
|
|
4611
|
+
const saved = await this.getSavedConfig(projectPath);
|
|
4612
|
+
return {
|
|
4613
|
+
available: true,
|
|
4614
|
+
containers,
|
|
4615
|
+
schemes,
|
|
4616
|
+
saved
|
|
4617
|
+
};
|
|
4618
|
+
}
|
|
4619
|
+
async getSchemesForContainer(projectPath, container) {
|
|
4620
|
+
const args = container.workspace ? ["-workspace", container.workspace, "-list", "-json"] : ["-project", container.project, "-list", "-json"];
|
|
4621
|
+
try {
|
|
4622
|
+
const { stdout } = await execAsync(
|
|
4623
|
+
`xcodebuild ${args.map(shellQuote).join(" ")}`,
|
|
4624
|
+
{ cwd: projectPath, timeout: 3e4, maxBuffer: 4 * 1024 * 1024 }
|
|
4625
|
+
);
|
|
4626
|
+
const parsed = JSON.parse(stdout);
|
|
4627
|
+
return parsed.workspace?.schemes ?? parsed.project?.schemes ?? [];
|
|
4628
|
+
} catch {
|
|
4629
|
+
return [];
|
|
4630
|
+
}
|
|
4631
|
+
}
|
|
4632
|
+
// ============================================
|
|
4633
|
+
// 列举 destinations
|
|
4634
|
+
// ============================================
|
|
4635
|
+
async listDestinations(projectPath, scheme, container) {
|
|
4636
|
+
if (process.platform !== "darwin") return [];
|
|
4637
|
+
const args = [
|
|
4638
|
+
...container.workspace ? ["-workspace", container.workspace] : ["-project", container.project],
|
|
4639
|
+
"-scheme",
|
|
4640
|
+
scheme,
|
|
4641
|
+
"-showdestinations"
|
|
4642
|
+
];
|
|
4643
|
+
try {
|
|
4644
|
+
const { stdout, stderr } = await execAsync(
|
|
4645
|
+
`xcodebuild ${args.map(shellQuote).join(" ")}`,
|
|
4646
|
+
{ cwd: projectPath, timeout: 6e4, maxBuffer: 4 * 1024 * 1024 }
|
|
4647
|
+
);
|
|
4648
|
+
return parseDestinations(stdout + "\n" + stderr);
|
|
4649
|
+
} catch (err) {
|
|
4650
|
+
const e = err;
|
|
4651
|
+
const parsed = parseDestinations(`${e.stdout ?? ""}
|
|
4652
|
+
${e.stderr ?? ""}`);
|
|
4653
|
+
if (parsed.length > 0) return parsed;
|
|
4654
|
+
throw err;
|
|
4655
|
+
}
|
|
4656
|
+
}
|
|
4657
|
+
// ============================================
|
|
4658
|
+
// 构建
|
|
4659
|
+
// ============================================
|
|
4660
|
+
async build(sessionId, projectPath, override) {
|
|
4661
|
+
if (process.platform !== "darwin") {
|
|
4662
|
+
this.emitBuildError(sessionId, "", "Xcode \u6784\u5EFA\u4EC5\u652F\u6301 macOS\n");
|
|
4663
|
+
return null;
|
|
4664
|
+
}
|
|
4665
|
+
const config = override ?? await this.getSavedConfig(projectPath);
|
|
4666
|
+
if (!config) {
|
|
4667
|
+
this.emitBuildError(sessionId, "", "\u672A\u914D\u7F6E Xcode \u6784\u5EFA\u53C2\u6570\uFF0C\u8BF7\u5148\u9009\u62E9 scheme \u4E0E\u76EE\u6807\u8BBE\u5907\n");
|
|
4668
|
+
return null;
|
|
4669
|
+
}
|
|
4670
|
+
if (override) await this.saveConfig(projectPath, override);
|
|
4671
|
+
const buildId = (0, import_uuid6.v4)();
|
|
4672
|
+
const args = buildArgs(config);
|
|
4673
|
+
const proc = (0, import_node_child_process8.spawn)("xcodebuild", args, {
|
|
4674
|
+
cwd: projectPath,
|
|
4675
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
4676
|
+
env: { ...process.env, NSUnbufferedIO: "YES" }
|
|
4677
|
+
});
|
|
4678
|
+
this.builds.set(buildId, proc);
|
|
4679
|
+
this.emit({
|
|
4680
|
+
type: "xcode_build_output",
|
|
4681
|
+
sessionId,
|
|
4682
|
+
buildId,
|
|
4683
|
+
stream: "stdout",
|
|
4684
|
+
data: `$ xcodebuild ${args.map((a) => a.includes(" ") ? `"${a}"` : a).join(" ")}
|
|
4685
|
+
cwd: ${projectPath}
|
|
4686
|
+
destination: ${config.destinationName}
|
|
4687
|
+
|
|
4688
|
+
`
|
|
4689
|
+
});
|
|
4690
|
+
proc.stdout?.on("data", (chunk) => {
|
|
4691
|
+
this.emit({ type: "xcode_build_output", sessionId, buildId, stream: "stdout", data: chunk.toString() });
|
|
4692
|
+
});
|
|
4693
|
+
proc.stderr?.on("data", (chunk) => {
|
|
4694
|
+
this.emit({ type: "xcode_build_output", sessionId, buildId, stream: "stderr", data: chunk.toString() });
|
|
4695
|
+
});
|
|
4696
|
+
proc.on("error", (err) => {
|
|
4697
|
+
this.emit({ type: "xcode_build_output", sessionId, buildId, stream: "stderr", data: `[spawn error] ${err.message}
|
|
4698
|
+
` });
|
|
4699
|
+
});
|
|
4700
|
+
const timer = setTimeout(() => {
|
|
4701
|
+
if (this.builds.has(buildId)) killProcessCrossPlatform(proc);
|
|
4702
|
+
}, BUILD_TIMEOUT_MS);
|
|
4703
|
+
proc.on("exit", (code, signal) => {
|
|
4704
|
+
clearTimeout(timer);
|
|
4705
|
+
this.builds.delete(buildId);
|
|
4706
|
+
this.emit({ type: "xcode_build_exit", sessionId, buildId, code, signal });
|
|
4707
|
+
});
|
|
4708
|
+
console.log(`[XcodeBuildExecutor] build ${buildId} scheme=${config.scheme} dest=${config.destinationName}`);
|
|
4709
|
+
return buildId;
|
|
4710
|
+
}
|
|
4711
|
+
killBuild(buildId) {
|
|
4712
|
+
const proc = this.builds.get(buildId);
|
|
4713
|
+
if (proc) {
|
|
4714
|
+
killProcessCrossPlatform(proc);
|
|
4715
|
+
console.log(`[XcodeBuildExecutor] kill build ${buildId}`);
|
|
4716
|
+
}
|
|
4717
|
+
}
|
|
4718
|
+
emitBuildError(sessionId, buildId, msg) {
|
|
4719
|
+
this.emit({ type: "xcode_build_output", sessionId, buildId, stream: "stderr", data: msg });
|
|
4720
|
+
this.emit({ type: "xcode_build_exit", sessionId, buildId, code: 1, signal: null });
|
|
4721
|
+
}
|
|
4722
|
+
// ============================================
|
|
4723
|
+
// 安装
|
|
4724
|
+
// ============================================
|
|
4725
|
+
async install(sessionId, projectPath) {
|
|
4726
|
+
if (process.platform !== "darwin") {
|
|
4727
|
+
this.emitInstallError(sessionId, "", "Xcode \u5B89\u88C5\u4EC5\u652F\u6301 macOS\n");
|
|
4728
|
+
return null;
|
|
4729
|
+
}
|
|
4730
|
+
const config = await this.getSavedConfig(projectPath);
|
|
4731
|
+
if (!config) {
|
|
4732
|
+
this.emitInstallError(sessionId, "", "\u672A\u627E\u5230\u6784\u5EFA\u914D\u7F6E\uFF0C\u8BF7\u5148\u6784\u5EFA\u4E00\u6B21\n");
|
|
4733
|
+
return null;
|
|
4734
|
+
}
|
|
4735
|
+
const installId = (0, import_uuid6.v4)();
|
|
4736
|
+
let appPath;
|
|
4737
|
+
try {
|
|
4738
|
+
appPath = await this.getAppPath(projectPath, config);
|
|
4739
|
+
} catch (err) {
|
|
4740
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4741
|
+
this.emitInstallError(sessionId, installId, `\u65E0\u6CD5\u5B9A\u4F4D\u6784\u5EFA\u4EA7\u7269: ${msg}
|
|
4742
|
+
`);
|
|
4743
|
+
return null;
|
|
4744
|
+
}
|
|
4745
|
+
const { destinationKind, destinationId, destinationName } = config;
|
|
4746
|
+
let installCmd;
|
|
4747
|
+
if (destinationKind === "simulator") {
|
|
4748
|
+
installCmd = ["xcrun", "simctl", "install", destinationId, appPath];
|
|
4749
|
+
} else if (destinationKind === "device") {
|
|
4750
|
+
installCmd = ["xcrun", "devicectl", "device", "install", "app", "--device-id", destinationId, appPath];
|
|
4751
|
+
} else if (destinationKind === "mac") {
|
|
4752
|
+
installCmd = ["open", appPath];
|
|
4753
|
+
} else {
|
|
4754
|
+
this.emitInstallError(sessionId, installId, `\u672A\u77E5\u76EE\u6807\u7C7B\u578B\uFF0C\u65E0\u6CD5\u81EA\u52A8\u5B89\u88C5
|
|
4755
|
+
`);
|
|
4756
|
+
return null;
|
|
4757
|
+
}
|
|
4758
|
+
this.emit({
|
|
4759
|
+
type: "xcode_install_output",
|
|
4760
|
+
sessionId,
|
|
4761
|
+
installId,
|
|
4762
|
+
stream: "stdout",
|
|
4763
|
+
data: `$ ${installCmd.join(" ")}
|
|
4764
|
+
destination: ${destinationName}
|
|
4765
|
+
app: ${appPath}
|
|
4766
|
+
|
|
4767
|
+
`
|
|
4768
|
+
});
|
|
4769
|
+
const proc = (0, import_node_child_process8.spawn)(installCmd[0], installCmd.slice(1), {
|
|
4770
|
+
cwd: projectPath,
|
|
4771
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
4772
|
+
});
|
|
4773
|
+
this.installs.set(installId, proc);
|
|
4774
|
+
proc.stdout?.on("data", (chunk) => {
|
|
4775
|
+
this.emit({ type: "xcode_install_output", sessionId, installId, stream: "stdout", data: chunk.toString() });
|
|
4776
|
+
});
|
|
4777
|
+
proc.stderr?.on("data", (chunk) => {
|
|
4778
|
+
this.emit({ type: "xcode_install_output", sessionId, installId, stream: "stderr", data: chunk.toString() });
|
|
4779
|
+
});
|
|
4780
|
+
proc.on("error", (err) => {
|
|
4781
|
+
this.emit({ type: "xcode_install_output", sessionId, installId, stream: "stderr", data: `[spawn error] ${err.message}
|
|
4782
|
+
` });
|
|
4783
|
+
});
|
|
4784
|
+
const timer = setTimeout(() => {
|
|
4785
|
+
if (this.installs.has(installId)) killProcessCrossPlatform(proc);
|
|
4786
|
+
}, INSTALL_TIMEOUT_MS);
|
|
4787
|
+
proc.on("exit", (code, signal) => {
|
|
4788
|
+
clearTimeout(timer);
|
|
4789
|
+
this.installs.delete(installId);
|
|
4790
|
+
this.emit({ type: "xcode_install_exit", sessionId, installId, code, signal });
|
|
4791
|
+
});
|
|
4792
|
+
console.log(`[XcodeBuildExecutor] install ${installId} dest=${destinationName} kind=${destinationKind}`);
|
|
4793
|
+
return installId;
|
|
4794
|
+
}
|
|
4795
|
+
killInstall(installId) {
|
|
4796
|
+
const proc = this.installs.get(installId);
|
|
4797
|
+
if (proc) {
|
|
4798
|
+
killProcessCrossPlatform(proc);
|
|
4799
|
+
console.log(`[XcodeBuildExecutor] kill install ${installId}`);
|
|
4800
|
+
}
|
|
4801
|
+
}
|
|
4802
|
+
emitInstallError(sessionId, installId, msg) {
|
|
4803
|
+
this.emit({ type: "xcode_install_output", sessionId, installId, stream: "stderr", data: msg });
|
|
4804
|
+
this.emit({ type: "xcode_install_exit", sessionId, installId, code: 1, signal: null });
|
|
4805
|
+
}
|
|
4806
|
+
/** 通过 xcodebuild -showBuildSettings 定位 .app 路径 */
|
|
4807
|
+
async getAppPath(projectPath, config) {
|
|
4808
|
+
const args = [
|
|
4809
|
+
...buildArgs(config).filter((a) => a !== "build"),
|
|
4810
|
+
"-showBuildSettings"
|
|
4811
|
+
];
|
|
4812
|
+
const { stdout } = await execAsync(
|
|
4813
|
+
`xcodebuild ${args.map(shellQuote).join(" ")}`,
|
|
4814
|
+
{ cwd: projectPath, timeout: 3e4, maxBuffer: 4 * 1024 * 1024 }
|
|
4815
|
+
);
|
|
4816
|
+
const builtDir = extractBuildSetting(stdout, "BUILT_PRODUCTS_DIR");
|
|
4817
|
+
const productName = extractBuildSetting(stdout, "FULL_PRODUCT_NAME");
|
|
4818
|
+
if (!builtDir || !productName) {
|
|
4819
|
+
throw new Error("\u65E0\u6CD5\u4ECE -showBuildSettings \u4E2D\u8BFB\u53D6 BUILT_PRODUCTS_DIR / FULL_PRODUCT_NAME");
|
|
4820
|
+
}
|
|
4821
|
+
return (0, import_node_path6.join)(builtDir, productName);
|
|
4822
|
+
}
|
|
4823
|
+
// ============================================
|
|
4824
|
+
// 清理
|
|
4825
|
+
// ============================================
|
|
4826
|
+
destroy() {
|
|
4827
|
+
for (const [id, proc] of this.builds) {
|
|
4828
|
+
killProcessCrossPlatform(proc);
|
|
4829
|
+
console.log(`[XcodeBuildExecutor] cleanup build ${id}`);
|
|
4830
|
+
}
|
|
4831
|
+
for (const [id, proc] of this.installs) {
|
|
4832
|
+
killProcessCrossPlatform(proc);
|
|
4833
|
+
console.log(`[XcodeBuildExecutor] cleanup install ${id}`);
|
|
4834
|
+
}
|
|
4835
|
+
this.builds.clear();
|
|
4836
|
+
this.installs.clear();
|
|
4837
|
+
this.eventCallbacks.length = 0;
|
|
4838
|
+
}
|
|
4839
|
+
};
|
|
4840
|
+
function shellQuote(s) {
|
|
4841
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
4842
|
+
}
|
|
4843
|
+
function buildArgs(config) {
|
|
4844
|
+
const container = config.workspace ? ["-workspace", config.workspace] : config.project ? ["-project", config.project] : [];
|
|
4845
|
+
return [
|
|
4846
|
+
...container,
|
|
4847
|
+
"-scheme",
|
|
4848
|
+
config.scheme,
|
|
4849
|
+
"-destination",
|
|
4850
|
+
`id=${config.destinationId}`,
|
|
4851
|
+
"-configuration",
|
|
4852
|
+
config.configuration ?? "Debug",
|
|
4853
|
+
"build"
|
|
4854
|
+
];
|
|
4855
|
+
}
|
|
4856
|
+
function extractBuildSetting(output, key) {
|
|
4857
|
+
const match = new RegExp(`^\\s*${key}\\s*=\\s*(.+)$`, "m").exec(output);
|
|
4858
|
+
return match?.[1]?.trim();
|
|
4859
|
+
}
|
|
4860
|
+
function parseDestinations(text) {
|
|
4861
|
+
const results = [];
|
|
4862
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4863
|
+
const lineRegex = /\{\s*([^{}]+)\s*\}/g;
|
|
4864
|
+
let match;
|
|
4865
|
+
while ((match = lineRegex.exec(text)) !== null) {
|
|
4866
|
+
const fields = {};
|
|
4867
|
+
for (const part of match[1].split(",")) {
|
|
4868
|
+
const colon = part.indexOf(":");
|
|
4869
|
+
if (colon === -1) continue;
|
|
4870
|
+
const key = part.slice(0, colon).trim();
|
|
4871
|
+
const value = part.slice(colon + 1).trim();
|
|
4872
|
+
if (key) fields[key] = value;
|
|
4873
|
+
}
|
|
4874
|
+
const { id, name, platform: platform2 } = fields;
|
|
4875
|
+
if (!id || !name || !platform2) continue;
|
|
4876
|
+
if (seen.has(id)) continue;
|
|
4877
|
+
seen.add(id);
|
|
4878
|
+
let kind = "unknown";
|
|
4879
|
+
if (platform2.includes("Simulator")) kind = "simulator";
|
|
4880
|
+
else if (platform2 === "iOS" || platform2 === "watchOS" || platform2 === "tvOS" || platform2 === "visionOS") kind = "device";
|
|
4881
|
+
else if (platform2 === "macOS") kind = "mac";
|
|
4882
|
+
results.push({ id, name, platform: platform2, os: fields.OS, kind });
|
|
4883
|
+
}
|
|
4884
|
+
results.sort((a, b) => kindOrder(a.kind) - kindOrder(b.kind));
|
|
4885
|
+
return results;
|
|
4886
|
+
}
|
|
4887
|
+
function kindOrder(k) {
|
|
4888
|
+
return k === "device" ? 0 : k === "simulator" ? 1 : k === "mac" ? 2 : 3;
|
|
4889
|
+
}
|
|
4890
|
+
|
|
4891
|
+
// src/utils/cliCapabilities.ts
|
|
4892
|
+
var import_node_child_process9 = require("child_process");
|
|
4457
4893
|
var DEFAULT_CAPABILITIES = {
|
|
4458
4894
|
effortLevels: ["low", "medium", "high", "xhigh", "max"]
|
|
4459
4895
|
};
|
|
@@ -4481,7 +4917,7 @@ async function parseCliCapabilities() {
|
|
|
4481
4917
|
}
|
|
4482
4918
|
function runCli(path2, args) {
|
|
4483
4919
|
return new Promise((resolve) => {
|
|
4484
|
-
(0,
|
|
4920
|
+
(0, import_node_child_process9.execFile)(path2, args, { timeout: 5e3 }, (err, stdout) => {
|
|
4485
4921
|
if (err) {
|
|
4486
4922
|
console.warn(`[CliCapabilities] Failed to run ${path2} ${args.join(" ")}:`, err.message);
|
|
4487
4923
|
resolve(null);
|
|
@@ -4495,11 +4931,11 @@ function runCli(path2, args) {
|
|
|
4495
4931
|
// src/server.ts
|
|
4496
4932
|
var WS_PORT = 3745;
|
|
4497
4933
|
var HTTP_PORT = 3746;
|
|
4498
|
-
var
|
|
4934
|
+
var execAsync2 = (0, import_node_util2.promisify)(import_node_child_process10.exec);
|
|
4499
4935
|
async function killPortProcess(port) {
|
|
4500
4936
|
try {
|
|
4501
4937
|
if (isWindows) {
|
|
4502
|
-
const { stdout } = await
|
|
4938
|
+
const { stdout } = await execAsync2(
|
|
4503
4939
|
`netstat -ano | findstr :${port} | findstr LISTENING`
|
|
4504
4940
|
);
|
|
4505
4941
|
const pids = /* @__PURE__ */ new Set();
|
|
@@ -4509,14 +4945,14 @@ async function killPortProcess(port) {
|
|
|
4509
4945
|
if (pid && /^\d+$/.test(pid) && pid !== "0") pids.add(pid);
|
|
4510
4946
|
}
|
|
4511
4947
|
for (const pid of pids) {
|
|
4512
|
-
await
|
|
4948
|
+
await execAsync2(`taskkill /PID ${pid} /F`).catch(() => {
|
|
4513
4949
|
});
|
|
4514
4950
|
}
|
|
4515
4951
|
} else {
|
|
4516
|
-
const { stdout } = await
|
|
4952
|
+
const { stdout } = await execAsync2(`lsof -ti :${port}`);
|
|
4517
4953
|
const pids = stdout.trim().split("\n").filter((p) => p && /^\d+$/.test(p));
|
|
4518
4954
|
if (pids.length > 0) {
|
|
4519
|
-
await
|
|
4955
|
+
await execAsync2(`kill -9 ${pids.join(" ")}`);
|
|
4520
4956
|
}
|
|
4521
4957
|
}
|
|
4522
4958
|
await new Promise((resolve) => setTimeout(resolve, 600));
|
|
@@ -4537,8 +4973,8 @@ async function createWithRetry(label, port, factory) {
|
|
|
4537
4973
|
}
|
|
4538
4974
|
}
|
|
4539
4975
|
async function start(opts = {}) {
|
|
4540
|
-
const configDir = (0,
|
|
4541
|
-
const tokenFile = (0,
|
|
4976
|
+
const configDir = (0, import_node_path7.join)((0, import_node_os8.homedir)(), ".sessix");
|
|
4977
|
+
const tokenFile = (0, import_node_path7.join)(configDir, "token");
|
|
4542
4978
|
let token;
|
|
4543
4979
|
if (opts.token !== void 0) {
|
|
4544
4980
|
token = opts.token;
|
|
@@ -4548,17 +4984,23 @@ async function start(opts = {}) {
|
|
|
4548
4984
|
token = envToken;
|
|
4549
4985
|
} else {
|
|
4550
4986
|
try {
|
|
4551
|
-
token = (await (0,
|
|
4987
|
+
token = (await (0, import_promises5.readFile)(tokenFile, "utf8")).trim();
|
|
4552
4988
|
} catch {
|
|
4553
|
-
token = (0,
|
|
4554
|
-
await (0,
|
|
4555
|
-
await (0,
|
|
4989
|
+
token = (0, import_uuid7.v4)();
|
|
4990
|
+
await (0, import_promises5.mkdir)(configDir, { recursive: true });
|
|
4991
|
+
await (0, import_promises5.writeFile)(tokenFile, token, "utf8");
|
|
4556
4992
|
}
|
|
4557
4993
|
}
|
|
4558
4994
|
}
|
|
4559
4995
|
const providerFactory = new ProviderFactory();
|
|
4560
4996
|
const sessionManager = new SessionManager(providerFactory);
|
|
4561
4997
|
const terminalExecutor = new TerminalExecutor();
|
|
4998
|
+
const xcodeBuildExecutor = new XcodeBuildExecutor();
|
|
4999
|
+
const approvalProxy = await createWithRetry(
|
|
5000
|
+
"ApprovalProxy",
|
|
5001
|
+
HTTP_PORT,
|
|
5002
|
+
() => ApprovalProxy.create({ port: HTTP_PORT, token })
|
|
5003
|
+
);
|
|
4562
5004
|
const wsBridge = await createWithRetry(
|
|
4563
5005
|
"WsBridge",
|
|
4564
5006
|
WS_PORT,
|
|
@@ -4589,15 +5031,10 @@ async function start(opts = {}) {
|
|
|
4589
5031
|
const sessionFileWatcher = new SessionFileWatcher((event) => {
|
|
4590
5032
|
wsBridge.broadcast(event);
|
|
4591
5033
|
});
|
|
4592
|
-
const approvalProxy = await createWithRetry(
|
|
4593
|
-
"ApprovalProxy",
|
|
4594
|
-
HTTP_PORT,
|
|
4595
|
-
() => ApprovalProxy.create({ port: HTTP_PORT, token })
|
|
4596
|
-
);
|
|
4597
5034
|
let mdnsService = null;
|
|
4598
5035
|
const pairingManager = new PairingManager({
|
|
4599
5036
|
token,
|
|
4600
|
-
serverName: (0,
|
|
5037
|
+
serverName: (0, import_node_os8.hostname)(),
|
|
4601
5038
|
version: "0.2.0",
|
|
4602
5039
|
onStateChange: (state) => mdnsService?.updatePairingState(state)
|
|
4603
5040
|
});
|
|
@@ -4651,7 +5088,7 @@ async function start(opts = {}) {
|
|
|
4651
5088
|
try {
|
|
4652
5089
|
switch (event.type) {
|
|
4653
5090
|
case "create_session": {
|
|
4654
|
-
await (0,
|
|
5091
|
+
await (0, import_promises5.mkdir)(event.projectPath, { recursive: true });
|
|
4655
5092
|
const resumeId = event.resumeSessionId ?? event.newSessionId;
|
|
4656
5093
|
if (resumeId) sessionFileWatcher.unwatch(resumeId);
|
|
4657
5094
|
await sessionManager.createSession(
|
|
@@ -4839,7 +5276,7 @@ async function start(opts = {}) {
|
|
|
4839
5276
|
if (!isStreaming) {
|
|
4840
5277
|
const filePath = getSessionFilePath(event.projectPath, event.sessionId);
|
|
4841
5278
|
try {
|
|
4842
|
-
const fileStat = await (0,
|
|
5279
|
+
const fileStat = await (0, import_promises6.stat)(filePath);
|
|
4843
5280
|
sessionFileWatcher.watch(event.sessionId, filePath, fileStat.size);
|
|
4844
5281
|
} catch {
|
|
4845
5282
|
}
|
|
@@ -4948,6 +5385,56 @@ async function start(opts = {}) {
|
|
|
4948
5385
|
wsBridge.send(ws, { type: "agent_list", agents });
|
|
4949
5386
|
break;
|
|
4950
5387
|
}
|
|
5388
|
+
case "xcode_detect": {
|
|
5389
|
+
const info = await xcodeBuildExecutor.detect(event.projectPath);
|
|
5390
|
+
wsBridge.send(ws, { type: "xcode_info", sessionId: event.sessionId, info });
|
|
5391
|
+
break;
|
|
5392
|
+
}
|
|
5393
|
+
case "xcode_list_schemes": {
|
|
5394
|
+
const schemes = await xcodeBuildExecutor.getSchemesForContainer(event.projectPath, event.container);
|
|
5395
|
+
wsBridge.send(ws, {
|
|
5396
|
+
type: "xcode_info",
|
|
5397
|
+
sessionId: event.sessionId,
|
|
5398
|
+
info: {
|
|
5399
|
+
available: schemes.length > 0,
|
|
5400
|
+
containers: [event.container],
|
|
5401
|
+
schemes
|
|
5402
|
+
}
|
|
5403
|
+
});
|
|
5404
|
+
break;
|
|
5405
|
+
}
|
|
5406
|
+
case "xcode_list_destinations": {
|
|
5407
|
+
try {
|
|
5408
|
+
const destinations = await xcodeBuildExecutor.listDestinations(event.projectPath, event.scheme, event.container);
|
|
5409
|
+
wsBridge.send(ws, { type: "xcode_destinations", sessionId: event.sessionId, scheme: event.scheme, destinations });
|
|
5410
|
+
} catch (err) {
|
|
5411
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5412
|
+
wsBridge.send(ws, {
|
|
5413
|
+
type: "xcode_destinations",
|
|
5414
|
+
sessionId: event.sessionId,
|
|
5415
|
+
scheme: event.scheme,
|
|
5416
|
+
destinations: [],
|
|
5417
|
+
error: message.split("\n")[0]
|
|
5418
|
+
});
|
|
5419
|
+
}
|
|
5420
|
+
break;
|
|
5421
|
+
}
|
|
5422
|
+
case "xcode_save_config": {
|
|
5423
|
+
await xcodeBuildExecutor.saveConfig(event.projectPath, event.config);
|
|
5424
|
+
break;
|
|
5425
|
+
}
|
|
5426
|
+
case "xcode_build": {
|
|
5427
|
+
await xcodeBuildExecutor.build(event.sessionId, event.projectPath, event.config);
|
|
5428
|
+
break;
|
|
5429
|
+
}
|
|
5430
|
+
case "xcode_build_kill": {
|
|
5431
|
+
xcodeBuildExecutor.killBuild(event.buildId);
|
|
5432
|
+
break;
|
|
5433
|
+
}
|
|
5434
|
+
case "xcode_install": {
|
|
5435
|
+
await xcodeBuildExecutor.install(event.sessionId, event.projectPath);
|
|
5436
|
+
break;
|
|
5437
|
+
}
|
|
4951
5438
|
default: {
|
|
4952
5439
|
wsBridge.send(ws, {
|
|
4953
5440
|
type: "error",
|
|
@@ -4983,6 +5470,9 @@ async function start(opts = {}) {
|
|
|
4983
5470
|
terminalExecutor.onEvent((event) => {
|
|
4984
5471
|
wsBridge.broadcast(event);
|
|
4985
5472
|
});
|
|
5473
|
+
xcodeBuildExecutor.onEvent((event) => {
|
|
5474
|
+
wsBridge.broadcast(event);
|
|
5475
|
+
});
|
|
4986
5476
|
wsBridge.onDisconnect(() => {
|
|
4987
5477
|
if (wsBridge.getConnectionCount() === 0 && approvalProxy.getPendingCount() > 0) {
|
|
4988
5478
|
approvalProxy.approveAll(t("server.phoneDisconnected"));
|
|
@@ -5090,6 +5580,7 @@ async function start(opts = {}) {
|
|
|
5090
5580
|
await attempt(() => approvalProxy.close(), "ApprovalProxy");
|
|
5091
5581
|
await attempt(() => sessionManager.destroy(), "SessionManager");
|
|
5092
5582
|
await attempt(() => terminalExecutor.destroy(), "TerminalExecutor");
|
|
5583
|
+
await attempt(() => xcodeBuildExecutor.destroy(), "XcodeBuildExecutor");
|
|
5093
5584
|
await attempt(() => notificationService.destroy(), "NotificationService");
|
|
5094
5585
|
await attempt(() => sessionFileWatcher.destroy(), "SessionFileWatcher");
|
|
5095
5586
|
if (errors.length > 0) {
|
|
@@ -5118,9 +5609,9 @@ async function start(opts = {}) {
|
|
|
5118
5609
|
openPairing: (duration) => pairingManager.open(duration),
|
|
5119
5610
|
closePairing: () => pairingManager.close(),
|
|
5120
5611
|
regenerateToken: async () => {
|
|
5121
|
-
const newToken = (0,
|
|
5122
|
-
await (0,
|
|
5123
|
-
await (0,
|
|
5612
|
+
const newToken = (0, import_uuid7.v4)();
|
|
5613
|
+
await (0, import_promises5.mkdir)(configDir, { recursive: true });
|
|
5614
|
+
await (0, import_promises5.writeFile)(tokenFile, newToken, "utf8");
|
|
5124
5615
|
instance.token = newToken;
|
|
5125
5616
|
wsBridge.updateToken(newToken);
|
|
5126
5617
|
approvalProxy.updateToken(newToken);
|
|
@@ -5137,7 +5628,7 @@ async function start(opts = {}) {
|
|
|
5137
5628
|
var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
|
|
5138
5629
|
function getPackageVersion() {
|
|
5139
5630
|
try {
|
|
5140
|
-
const pkg = JSON.parse((0, import_node_fs4.readFileSync)((0,
|
|
5631
|
+
const pkg = JSON.parse((0, import_node_fs4.readFileSync)((0, import_node_path8.join)(__dirname, "..", "package.json"), "utf8"));
|
|
5141
5632
|
return pkg.version ?? "0.0.0";
|
|
5142
5633
|
} catch {
|
|
5143
5634
|
return "0.0.0";
|
|
@@ -5175,7 +5666,7 @@ async function autoUpdateIfNeeded() {
|
|
|
5175
5666
|
console.log(` \u{1F4E6} ${t("startup.autoUpdating", { current: PKG_VERSION, latest })}`);
|
|
5176
5667
|
console.log();
|
|
5177
5668
|
try {
|
|
5178
|
-
(0,
|
|
5669
|
+
(0, import_node_child_process11.execFileSync)("npx", [`sessix-server@${latest}`], {
|
|
5179
5670
|
stdio: "inherit",
|
|
5180
5671
|
env: { ...process.env, __SESSIX_UPDATED: "1" }
|
|
5181
5672
|
});
|
|
@@ -5281,7 +5772,7 @@ ${t("startup.pairingReopened")}`);
|
|
|
5281
5772
|
}
|
|
5282
5773
|
}
|
|
5283
5774
|
function getLocalIp() {
|
|
5284
|
-
const interfaces = (0,
|
|
5775
|
+
const interfaces = (0, import_node_os9.networkInterfaces)();
|
|
5285
5776
|
for (const iface of Object.values(interfaces)) {
|
|
5286
5777
|
for (const addr of iface ?? []) {
|
|
5287
5778
|
if (addr.family === "IPv4" && !addr.internal) {
|