xcode-build-queue 0.1.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.
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.enqueueAndWait = enqueueAndWait;
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
6
+ const config_js_1 = require("./config.js");
7
+ const snapshot_js_1 = require("./snapshot.js");
8
+ const utils_js_1 = require("./utils.js");
9
+ /**
10
+ * Enqueue a job, wait for result, and return it.
11
+ */
12
+ async function enqueueAndWait(opts) {
13
+ (0, utils_js_1.ensureDirs)();
14
+ const config = (0, config_js_1.loadConfig)();
15
+ // Ensure daemon is running
16
+ ensureDaemonRunning();
17
+ // Determine build strategy
18
+ const jobId = (0, utils_js_1.generateJobId)();
19
+ let branch = opts.branch;
20
+ let snapshotSha;
21
+ if (!branch) {
22
+ if ((0, snapshot_js_1.isInWorktree)()) {
23
+ snapshotSha = (0, snapshot_js_1.createSnapshot)();
24
+ utils_js_1.log.ok(`Snapshot: ${snapshotSha.slice(0, 8)}`);
25
+ }
26
+ else {
27
+ utils_js_1.log.error("Not in a worktree. Specify --branch or run from a worktree.");
28
+ process.exit(1);
29
+ }
30
+ }
31
+ // Create job
32
+ const job = {
33
+ id: jobId,
34
+ action: opts.action,
35
+ branch,
36
+ snapshot_sha: snapshotSha,
37
+ scheme: opts.scheme || config.default_scheme,
38
+ test_plan: opts.action === "test" ? (opts.testPlan || config.default_test_plan || undefined) : undefined,
39
+ destination: opts.destination || config.default_destination,
40
+ backend: opts.backend || config.backend,
41
+ submitted_at: new Date().toISOString(),
42
+ submitted_by: detectWorktreeName(),
43
+ };
44
+ // Write to queue
45
+ const jobFile = (0, node_path_1.join)(utils_js_1.BQ_QUEUE_DIR, `${job.id}.json`);
46
+ (0, node_fs_1.writeFileSync)(jobFile, JSON.stringify(job, null, 2) + "\n");
47
+ // Show queue position
48
+ const queueSize = (0, node_fs_1.readdirSync)(utils_js_1.BQ_QUEUE_DIR).filter(f => f.endsWith(".json")).length;
49
+ if (queueSize > 1) {
50
+ utils_js_1.log.status(`Queued (position: ${queueSize})`);
51
+ }
52
+ else {
53
+ utils_js_1.log.status("Queued (next up)");
54
+ }
55
+ // Wait for result
56
+ const timeout = opts.timeout || 1800; // 30 min
57
+ return waitForResult(job.id, timeout);
58
+ }
59
+ function ensureDaemonRunning() {
60
+ if ((0, node_fs_1.existsSync)(utils_js_1.BQ_PID_FILE)) {
61
+ const pid = parseInt((0, node_fs_1.readFileSync)(utils_js_1.BQ_PID_FILE, "utf-8").trim());
62
+ if ((0, utils_js_1.isProcessAlive)(pid))
63
+ return;
64
+ }
65
+ // Auto-start daemon in background
66
+ utils_js_1.log.info("Starting daemon...");
67
+ const { spawn } = require("node:child_process");
68
+ const daemonProcess = spawn(process.argv[0], // node
69
+ [process.argv[1], "daemon", "start"], {
70
+ detached: true,
71
+ stdio: "ignore",
72
+ });
73
+ daemonProcess.unref();
74
+ // Wait briefly for daemon to start
75
+ const start = Date.now();
76
+ while (Date.now() - start < 3000) {
77
+ if ((0, node_fs_1.existsSync)(utils_js_1.BQ_PID_FILE)) {
78
+ const pid = parseInt((0, node_fs_1.readFileSync)(utils_js_1.BQ_PID_FILE, "utf-8").trim());
79
+ if ((0, utils_js_1.isProcessAlive)(pid)) {
80
+ utils_js_1.log.ok("Daemon started");
81
+ return;
82
+ }
83
+ }
84
+ require("node:child_process").execSync("sleep 0.2");
85
+ }
86
+ utils_js_1.log.warn("Daemon may not have started — will proceed anyway");
87
+ }
88
+ async function waitForResult(jobId, timeoutSec) {
89
+ const resultFile = (0, node_path_1.join)(utils_js_1.BQ_RESULTS_DIR, `${jobId}.json`);
90
+ const startTime = Date.now();
91
+ let lastStatus = "";
92
+ while (true) {
93
+ const elapsed = Math.round((Date.now() - startTime) / 1000);
94
+ if (elapsed > timeoutSec) {
95
+ utils_js_1.log.error(`Timed out after ${timeoutSec}s`);
96
+ return {
97
+ id: jobId,
98
+ status: "error",
99
+ duration_seconds: elapsed,
100
+ summary: `Timed out after ${timeoutSec}s`,
101
+ failures: [],
102
+ build_errors: ["Job timed out"],
103
+ warnings: [],
104
+ log_path: "",
105
+ };
106
+ }
107
+ if ((0, node_fs_1.existsSync)(resultFile)) {
108
+ const result = JSON.parse((0, node_fs_1.readFileSync)(resultFile, "utf-8"));
109
+ return result;
110
+ }
111
+ // Periodic status update (every 10s)
112
+ if (elapsed > 0 && elapsed % 10 === 0) {
113
+ const status = `Waiting... (${elapsed}s)`;
114
+ if (status !== lastStatus) {
115
+ utils_js_1.log.status(status);
116
+ lastStatus = status;
117
+ }
118
+ }
119
+ await new Promise(resolve => setTimeout(resolve, 1000));
120
+ }
121
+ }
122
+ function detectWorktreeName() {
123
+ try {
124
+ const cwd = process.cwd();
125
+ const match = cwd.match(/worktrees?\/([^/]+)/);
126
+ if (match)
127
+ return match[1];
128
+ return cwd.split("/").pop() || "unknown";
129
+ }
130
+ catch {
131
+ return "unknown";
132
+ }
133
+ }
@@ -0,0 +1,5 @@
1
+ import { type Job, type JobResult } from "./types.js";
2
+ /**
3
+ * Execute a single job: apply patch or checkout branch, build/test, return results.
4
+ */
5
+ export declare function executeJob(job: Job): Promise<JobResult>;
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.executeJob = executeJob;
4
+ const config_js_1 = require("./config.js");
5
+ const snapshot_js_1 = require("./snapshot.js");
6
+ const utils_js_1 = require("./utils.js");
7
+ const mcp_js_1 = require("./backends/mcp.js");
8
+ const xcodebuild_js_1 = require("./backends/xcodebuild.js");
9
+ /**
10
+ * Execute a single job: apply patch or checkout branch, build/test, return results.
11
+ */
12
+ async function executeJob(job) {
13
+ const config = (0, config_js_1.loadConfig)();
14
+ const repoPath = (0, utils_js_1.expandPath)(config.main_repo);
15
+ const workspace = config.workspace;
16
+ // 1. Stash any uncommitted changes in main repo
17
+ const stashed = stashIfDirty(repoPath);
18
+ try {
19
+ if (job.snapshot_sha) {
20
+ // Snapshot mode: detached HEAD checkout
21
+ (0, snapshot_js_1.applySnapshot)(repoPath, job.snapshot_sha);
22
+ }
23
+ else if (job.branch) {
24
+ // Branch mode: fetch and checkout
25
+ utils_js_1.log.info("Fetching latest changes...");
26
+ (0, utils_js_1.run)("git fetch --all --prune", { cwd: repoPath, quiet: true });
27
+ utils_js_1.log.info(`Checking out branch: ${job.branch}`);
28
+ checkoutBranch(repoPath, job.branch);
29
+ }
30
+ else {
31
+ throw new Error("Job has neither snapshot_sha nor branch");
32
+ }
33
+ // Restore file timestamps for incremental builds
34
+ if (config.git_restore_mtime) {
35
+ restoreMtime(repoPath);
36
+ }
37
+ // Run build/test via selected backend
38
+ const backend = job.backend || config.backend;
39
+ utils_js_1.log.info(`Backend: ${backend}`);
40
+ if (backend === "mcp") {
41
+ try {
42
+ return await (0, mcp_js_1.executeWithMCP)(job, repoPath, workspace);
43
+ }
44
+ catch (err) {
45
+ if (config.xcodebuild_fallback) {
46
+ utils_js_1.log.warn(`MCP failed, falling back to xcodebuild: ${err}`);
47
+ return await (0, xcodebuild_js_1.executeWithXcodebuild)(job, repoPath, workspace);
48
+ }
49
+ throw err;
50
+ }
51
+ }
52
+ else {
53
+ return await (0, xcodebuild_js_1.executeWithXcodebuild)(job, repoPath, workspace);
54
+ }
55
+ }
56
+ catch (err) {
57
+ const message = err instanceof Error ? err.message : String(err);
58
+ utils_js_1.log.error(`Job failed: ${message}`);
59
+ return {
60
+ id: job.id,
61
+ status: "error",
62
+ duration_seconds: 0,
63
+ summary: message,
64
+ failures: [],
65
+ build_errors: [message],
66
+ warnings: [],
67
+ log_path: "",
68
+ };
69
+ }
70
+ finally {
71
+ // Return to default branch after snapshot checkout
72
+ if (job.snapshot_sha) {
73
+ (0, snapshot_js_1.cleanSnapshot)(repoPath);
74
+ }
75
+ // Restore stashed changes
76
+ if (stashed) {
77
+ try {
78
+ (0, utils_js_1.run)("git stash pop", { cwd: repoPath, quiet: true });
79
+ }
80
+ catch {
81
+ utils_js_1.log.warn("Could not restore stashed changes in main repo");
82
+ }
83
+ }
84
+ }
85
+ }
86
+ function stashIfDirty(repoPath) {
87
+ const status = (0, utils_js_1.run)("git status --porcelain", { cwd: repoPath, quiet: true });
88
+ if (status.length > 0) {
89
+ utils_js_1.log.warn("Main repo has uncommitted changes — stashing");
90
+ (0, utils_js_1.run)("git stash push -m 'xbq: auto-stash before build'", { cwd: repoPath, quiet: true });
91
+ return true;
92
+ }
93
+ return false;
94
+ }
95
+ function checkoutBranch(repoPath, branch) {
96
+ // Try local branch first
97
+ try {
98
+ (0, utils_js_1.run)(`git checkout ${branch}`, { cwd: repoPath, quiet: true });
99
+ return;
100
+ }
101
+ catch {
102
+ // Branch might not exist locally
103
+ }
104
+ // Try tracking remote branch
105
+ try {
106
+ (0, utils_js_1.run)(`git checkout -b ${branch} origin/${branch}`, { cwd: repoPath, quiet: true });
107
+ return;
108
+ }
109
+ catch {
110
+ // Already exists or other error
111
+ }
112
+ // Force update local branch to match remote
113
+ try {
114
+ (0, utils_js_1.run)(`git checkout ${branch}`, { cwd: repoPath, quiet: true });
115
+ (0, utils_js_1.run)(`git reset --hard origin/${branch}`, { cwd: repoPath, quiet: true });
116
+ }
117
+ catch (err) {
118
+ throw new Error(`Failed to checkout branch '${branch}': ${err}`);
119
+ }
120
+ }
121
+ /**
122
+ * Restore file modification times from git history.
123
+ * This helps Xcode's incremental build system avoid unnecessary recompilation.
124
+ */
125
+ function restoreMtime(repoPath) {
126
+ try {
127
+ (0, utils_js_1.run)("which git-restore-mtime", { quiet: true });
128
+ utils_js_1.log.dim("Restoring file timestamps...");
129
+ (0, utils_js_1.run)("git-restore-mtime --skip-missing", { cwd: repoPath, quiet: true });
130
+ }
131
+ catch {
132
+ // git-restore-mtime not installed, skip silently
133
+ }
134
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Inject xbq instructions into CLAUDE.md at a given path.
3
+ * If CLAUDE.md exists, appends the snippet (or updates if already present).
4
+ * If CLAUDE.md doesn't exist, creates it.
5
+ */
6
+ export declare function setupClaude(targetDir?: string): void;
7
+ /**
8
+ * Remove xbq instructions from CLAUDE.md.
9
+ */
10
+ export declare function removeClaude(targetDir?: string): void;
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setupClaude = setupClaude;
4
+ exports.removeClaude = removeClaude;
5
+ const node_fs_1 = require("node:fs");
6
+ const node_path_1 = require("node:path");
7
+ const utils_js_1 = require("./utils.js");
8
+ const MARKER_START = "<!-- xbq:start -->";
9
+ const MARKER_END = "<!-- xbq:end -->";
10
+ const SNIPPET = `${MARKER_START}
11
+ ## Build Queue (xbq)
12
+
13
+ This project uses \`xbq\` (Xcode Build Queue) for all build and test operations. You are working in a **lightweight worktree** — do NOT build or run tests directly here.
14
+
15
+ ### Rules
16
+
17
+ 1. **NEVER run \`xcodebuild\` directly** in this worktree. Always use \`xbq\`.
18
+ 2. **NEVER run Xcode MCP build/test tools** directly. Route through \`xbq\`.
19
+
20
+ ### Workflow
21
+
22
+ \`\`\`bash
23
+ # 1. Build (automatically captures your changes via diff)
24
+ xbq build
25
+
26
+ # 2. Test
27
+ xbq test
28
+
29
+ # 3. Test with specific plan
30
+ xbq test --test-plan All
31
+
32
+ # 4. Build with specific destination
33
+ xbq build --destination "platform=iOS Simulator,name=iPhone 16,OS=18.0"
34
+
35
+ # 5. Check queue status
36
+ xbq status
37
+ \`\`\`
38
+
39
+ ### Important
40
+
41
+ - Changes are captured automatically — no need to commit before building
42
+ - \`xbq\` is a serial queue — your job may wait if another session is building
43
+ - Results include build errors and test failures in structured output
44
+ - For full logs: \`xbq logs <job-id>\`
45
+ - Exit code is 0 on success, 1 on failure — use it to determine next steps
46
+ ${MARKER_END}`;
47
+ /**
48
+ * Inject xbq instructions into CLAUDE.md at a given path.
49
+ * If CLAUDE.md exists, appends the snippet (or updates if already present).
50
+ * If CLAUDE.md doesn't exist, creates it.
51
+ */
52
+ function setupClaude(targetDir) {
53
+ const dir = targetDir || process.cwd();
54
+ const claudeMd = (0, node_path_1.join)(dir, "CLAUDE.md");
55
+ if (!(0, node_fs_1.existsSync)(dir)) {
56
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true });
57
+ }
58
+ if ((0, node_fs_1.existsSync)(claudeMd)) {
59
+ const content = (0, node_fs_1.readFileSync)(claudeMd, "utf-8");
60
+ if (content.includes(MARKER_START)) {
61
+ // Update existing snippet
62
+ const regex = new RegExp(`${escapeRegex(MARKER_START)}[\\s\\S]*?${escapeRegex(MARKER_END)}`, "g");
63
+ const updated = content.replace(regex, SNIPPET);
64
+ (0, node_fs_1.writeFileSync)(claudeMd, updated);
65
+ utils_js_1.log.ok(`Updated xbq section in ${claudeMd}`);
66
+ }
67
+ else {
68
+ // Append
69
+ (0, node_fs_1.appendFileSync)(claudeMd, "\n\n" + SNIPPET + "\n");
70
+ utils_js_1.log.ok(`Added xbq section to ${claudeMd}`);
71
+ }
72
+ }
73
+ else {
74
+ (0, node_fs_1.writeFileSync)(claudeMd, SNIPPET + "\n");
75
+ utils_js_1.log.ok(`Created ${claudeMd} with xbq instructions`);
76
+ }
77
+ }
78
+ /**
79
+ * Remove xbq instructions from CLAUDE.md.
80
+ */
81
+ function removeClaude(targetDir) {
82
+ const dir = targetDir || process.cwd();
83
+ const claudeMd = (0, node_path_1.join)(dir, "CLAUDE.md");
84
+ if (!(0, node_fs_1.existsSync)(claudeMd)) {
85
+ utils_js_1.log.warn("No CLAUDE.md found");
86
+ return;
87
+ }
88
+ const content = (0, node_fs_1.readFileSync)(claudeMd, "utf-8");
89
+ if (!content.includes(MARKER_START)) {
90
+ utils_js_1.log.warn("No xbq section found in CLAUDE.md");
91
+ return;
92
+ }
93
+ const regex = new RegExp(`\\n*${escapeRegex(MARKER_START)}[\\s\\S]*?${escapeRegex(MARKER_END)}\\n*`, "g");
94
+ const updated = content.replace(regex, "\n");
95
+ (0, node_fs_1.writeFileSync)(claudeMd, updated.trim() + "\n");
96
+ utils_js_1.log.ok(`Removed xbq section from ${claudeMd}`);
97
+ }
98
+ function escapeRegex(s) {
99
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
100
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Detect the default branch (master, main, develop) for a repo.
3
+ */
4
+ export declare function getDefaultBranch(repoPath: string): string;
5
+ /**
6
+ * Check if the current directory is inside a git worktree (not the main repo).
7
+ */
8
+ export declare function isInWorktree(): boolean;
9
+ /**
10
+ * Create a snapshot commit of the current worktree state (committed + uncommitted
11
+ * + untracked) without modifying any refs. Returns a SHA that the main repo can
12
+ * checkout via detached HEAD (shared object store across worktrees).
13
+ */
14
+ export declare function createSnapshot(): string;
15
+ /**
16
+ * Apply a snapshot by checking out the SHA as a detached HEAD in the main repo.
17
+ * Detached HEAD bypasses the worktree branch lock.
18
+ */
19
+ export declare function applySnapshot(repoPath: string, sha: string): void;
20
+ /**
21
+ * Clean up the main repo by returning to the default branch.
22
+ */
23
+ export declare function cleanSnapshot(repoPath: string): void;
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getDefaultBranch = getDefaultBranch;
4
+ exports.isInWorktree = isInWorktree;
5
+ exports.createSnapshot = createSnapshot;
6
+ exports.applySnapshot = applySnapshot;
7
+ exports.cleanSnapshot = cleanSnapshot;
8
+ const utils_js_1 = require("./utils.js");
9
+ /**
10
+ * Detect the default branch (master, main, develop) for a repo.
11
+ */
12
+ function getDefaultBranch(repoPath) {
13
+ try {
14
+ const ref = (0, utils_js_1.run)("git symbolic-ref refs/remotes/origin/HEAD", { cwd: repoPath, quiet: true });
15
+ return ref.replace("refs/remotes/origin/", "");
16
+ }
17
+ catch {
18
+ for (const branch of ["master", "main", "develop"]) {
19
+ try {
20
+ (0, utils_js_1.run)(`git rev-parse --verify origin/${branch}`, { cwd: repoPath, quiet: true });
21
+ return branch;
22
+ }
23
+ catch {
24
+ continue;
25
+ }
26
+ }
27
+ return "master";
28
+ }
29
+ }
30
+ /**
31
+ * Check if the current directory is inside a git worktree (not the main repo).
32
+ */
33
+ function isInWorktree() {
34
+ try {
35
+ const commonDir = (0, utils_js_1.run)("git rev-parse --git-common-dir", { quiet: true }).replace(/\/$/, "");
36
+ const gitDir = (0, utils_js_1.run)("git rev-parse --git-dir", { quiet: true }).replace(/\/$/, "");
37
+ return commonDir !== gitDir;
38
+ }
39
+ catch {
40
+ return false;
41
+ }
42
+ }
43
+ /**
44
+ * Create a snapshot commit of the current worktree state (committed + uncommitted
45
+ * + untracked) without modifying any refs. Returns a SHA that the main repo can
46
+ * checkout via detached HEAD (shared object store across worktrees).
47
+ */
48
+ function createSnapshot() {
49
+ const cwd = process.cwd();
50
+ // Check if there are uncommitted changes (staged, unstaged, or untracked)
51
+ const status = (0, utils_js_1.run)("git status --porcelain", { cwd, quiet: true });
52
+ if (!status) {
53
+ // No uncommitted changes — use HEAD directly
54
+ const sha = (0, utils_js_1.run)("git rev-parse HEAD", { cwd, quiet: true });
55
+ utils_js_1.log.dim("No uncommitted changes — using HEAD");
56
+ return sha;
57
+ }
58
+ // Stage everything (including untracked files) to capture full state
59
+ (0, utils_js_1.run)("git add -A", { cwd, quiet: true });
60
+ try {
61
+ // Write the current index as a tree object
62
+ const tree = (0, utils_js_1.run)("git write-tree", { cwd, quiet: true });
63
+ // Create a commit object pointing to this tree (no ref update)
64
+ const head = (0, utils_js_1.run)("git rev-parse HEAD", { cwd, quiet: true });
65
+ const sha = (0, utils_js_1.run)(`git commit-tree ${tree} -p ${head} -m "xbq snapshot"`, { cwd, quiet: true });
66
+ utils_js_1.log.dim(`Snapshot: ${sha.slice(0, 8)}`);
67
+ return sha;
68
+ }
69
+ finally {
70
+ // Always restore the index to its original state
71
+ (0, utils_js_1.run)("git reset", { cwd, quiet: true });
72
+ }
73
+ }
74
+ /**
75
+ * Apply a snapshot by checking out the SHA as a detached HEAD in the main repo.
76
+ * Detached HEAD bypasses the worktree branch lock.
77
+ */
78
+ function applySnapshot(repoPath, sha) {
79
+ utils_js_1.log.info(`Checking out snapshot ${sha.slice(0, 8)}...`);
80
+ try {
81
+ (0, utils_js_1.run)(`git checkout ${sha}`, { cwd: repoPath, quiet: true });
82
+ }
83
+ catch (err) {
84
+ throw new Error(`Failed to checkout snapshot ${sha.slice(0, 8)}: ${err}`);
85
+ }
86
+ utils_js_1.log.ok("Snapshot applied");
87
+ }
88
+ /**
89
+ * Clean up the main repo by returning to the default branch.
90
+ */
91
+ function cleanSnapshot(repoPath) {
92
+ const defaultBranch = getDefaultBranch(repoPath);
93
+ try {
94
+ (0, utils_js_1.run)(`git checkout ${defaultBranch}`, { cwd: repoPath, quiet: true });
95
+ }
96
+ catch {
97
+ utils_js_1.log.warn(`Could not return to ${defaultBranch}`);
98
+ }
99
+ }
@@ -0,0 +1,33 @@
1
+ export interface BQConfig {
2
+ main_repo: string;
3
+ workspace: string;
4
+ default_scheme: string;
5
+ default_test_plan: string;
6
+ default_destination: string;
7
+ backend: "mcp" | "xcodebuild";
8
+ xcodebuild_fallback: boolean;
9
+ git_restore_mtime: boolean;
10
+ }
11
+ export interface Job {
12
+ id: string;
13
+ action: "build" | "test";
14
+ branch?: string;
15
+ snapshot_sha?: string;
16
+ scheme: string;
17
+ test_plan?: string;
18
+ destination?: string;
19
+ backend: "mcp" | "xcodebuild";
20
+ submitted_at: string;
21
+ submitted_by: string;
22
+ }
23
+ export interface JobResult {
24
+ id: string;
25
+ status: "passed" | "failed" | "error";
26
+ duration_seconds: number;
27
+ summary: string;
28
+ failures: string[];
29
+ build_errors: string[];
30
+ warnings: string[];
31
+ log_path: string;
32
+ }
33
+ export declare const DEFAULT_CONFIG: BQConfig;
package/dist/types.js ADDED
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_CONFIG = void 0;
4
+ exports.DEFAULT_CONFIG = {
5
+ main_repo: "",
6
+ workspace: "",
7
+ default_scheme: "",
8
+ default_test_plan: "",
9
+ default_destination: "platform=iOS Simulator,name=iPhone 16",
10
+ backend: "mcp",
11
+ xcodebuild_fallback: true,
12
+ git_restore_mtime: true,
13
+ };
@@ -0,0 +1,41 @@
1
+ export declare const BQ_HOME: string;
2
+ export declare const BQ_CONFIG_PATH: string;
3
+ export declare const BQ_QUEUE_DIR: string;
4
+ export declare const BQ_ACTIVE_DIR: string;
5
+ export declare const BQ_RESULTS_DIR: string;
6
+ export declare const BQ_LOGS_DIR: string;
7
+ export declare const BQ_PID_FILE: string;
8
+ export declare const BQ_LOCK_FILE: string;
9
+ export declare const log: {
10
+ info: (msg: string) => void;
11
+ ok: (msg: string) => void;
12
+ warn: (msg: string) => void;
13
+ error: (msg: string) => void;
14
+ status: (msg: string) => void;
15
+ dim: (msg: string) => void;
16
+ };
17
+ export declare function ensureDirs(): void;
18
+ export declare function generateJobId(): string;
19
+ export declare function expandPath(p: string): string;
20
+ /**
21
+ * Run a command and return stdout. Throws on non-zero exit.
22
+ */
23
+ export declare function run(cmd: string, opts?: {
24
+ cwd?: string;
25
+ quiet?: boolean;
26
+ }): string;
27
+ /**
28
+ * Spawn a long-running process and stream output to a log file + optional callback.
29
+ */
30
+ export declare function spawnWithLog(cmd: string, args: string[], opts: {
31
+ cwd?: string;
32
+ logPath: string;
33
+ onLine?: (line: string) => void;
34
+ }): Promise<{
35
+ exitCode: number;
36
+ output: string;
37
+ }>;
38
+ /**
39
+ * Check if a process with the given PID is alive.
40
+ */
41
+ export declare function isProcessAlive(pid: number): boolean;