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.
- package/README.md +266 -0
- package/dist/backends/mcp.d.ts +5 -0
- package/dist/backends/mcp.js +224 -0
- package/dist/backends/xcodebuild.d.ts +5 -0
- package/dist/backends/xcodebuild.js +83 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +244 -0
- package/dist/config.d.ts +15 -0
- package/dist/config.js +114 -0
- package/dist/daemon.d.ts +8 -0
- package/dist/daemon.js +186 -0
- package/dist/enqueue.d.ts +14 -0
- package/dist/enqueue.js +133 -0
- package/dist/executor.d.ts +5 -0
- package/dist/executor.js +134 -0
- package/dist/setup-claude.d.ts +10 -0
- package/dist/setup-claude.js +100 -0
- package/dist/snapshot.d.ts +23 -0
- package/dist/snapshot.js +99 -0
- package/dist/types.d.ts +33 -0
- package/dist/types.js +13 -0
- package/dist/utils.d.ts +41 -0
- package/dist/utils.js +118 -0
- package/dist/worktree.d.ts +17 -0
- package/dist/worktree.js +239 -0
- package/package.json +43 -0
package/dist/utils.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.log = exports.BQ_LOCK_FILE = exports.BQ_PID_FILE = exports.BQ_LOGS_DIR = exports.BQ_RESULTS_DIR = exports.BQ_ACTIVE_DIR = exports.BQ_QUEUE_DIR = exports.BQ_CONFIG_PATH = exports.BQ_HOME = void 0;
|
|
4
|
+
exports.ensureDirs = ensureDirs;
|
|
5
|
+
exports.generateJobId = generateJobId;
|
|
6
|
+
exports.expandPath = expandPath;
|
|
7
|
+
exports.run = run;
|
|
8
|
+
exports.spawnWithLog = spawnWithLog;
|
|
9
|
+
exports.isProcessAlive = isProcessAlive;
|
|
10
|
+
const node_fs_1 = require("node:fs");
|
|
11
|
+
const node_os_1 = require("node:os");
|
|
12
|
+
const node_path_1 = require("node:path");
|
|
13
|
+
const node_child_process_1 = require("node:child_process");
|
|
14
|
+
// Paths
|
|
15
|
+
const home = (0, node_os_1.homedir)();
|
|
16
|
+
exports.BQ_HOME = process.env.BQ_HOME || (0, node_path_1.join)(home, ".bq");
|
|
17
|
+
exports.BQ_CONFIG_PATH = (0, node_path_1.join)(exports.BQ_HOME, "config.json");
|
|
18
|
+
exports.BQ_QUEUE_DIR = (0, node_path_1.join)(exports.BQ_HOME, "queue");
|
|
19
|
+
exports.BQ_ACTIVE_DIR = (0, node_path_1.join)(exports.BQ_HOME, "active");
|
|
20
|
+
exports.BQ_RESULTS_DIR = (0, node_path_1.join)(exports.BQ_HOME, "results");
|
|
21
|
+
exports.BQ_LOGS_DIR = (0, node_path_1.join)(exports.BQ_HOME, "logs");
|
|
22
|
+
exports.BQ_PID_FILE = (0, node_path_1.join)(exports.BQ_HOME, "daemon.pid");
|
|
23
|
+
exports.BQ_LOCK_FILE = (0, node_path_1.join)(exports.BQ_HOME, "daemon.lock");
|
|
24
|
+
// Colors
|
|
25
|
+
// Prefix for all log/status output
|
|
26
|
+
const PREFIX = "xbq";
|
|
27
|
+
const c = {
|
|
28
|
+
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
29
|
+
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
30
|
+
yellow: (s) => `\x1b[33m${s}\x1b[0m`,
|
|
31
|
+
blue: (s) => `\x1b[34m${s}\x1b[0m`,
|
|
32
|
+
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
33
|
+
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
|
34
|
+
};
|
|
35
|
+
exports.log = {
|
|
36
|
+
info: (msg) => console.log(`${c.blue(`[${PREFIX}]`)} ${msg}`),
|
|
37
|
+
ok: (msg) => console.log(`${c.green(`[${PREFIX}]`)} ${msg}`),
|
|
38
|
+
warn: (msg) => console.log(`${c.yellow(`[${PREFIX}]`)} ${msg}`),
|
|
39
|
+
error: (msg) => console.error(`${c.red(`[${PREFIX}]`)} ${msg}`),
|
|
40
|
+
status: (msg) => console.log(`${c.cyan(`[${PREFIX}]`)} ${msg}`),
|
|
41
|
+
dim: (msg) => console.log(`${c.dim(`[${PREFIX}] ${msg}`)}`),
|
|
42
|
+
};
|
|
43
|
+
function ensureDirs() {
|
|
44
|
+
for (const dir of [exports.BQ_HOME, exports.BQ_QUEUE_DIR, exports.BQ_ACTIVE_DIR, exports.BQ_RESULTS_DIR, exports.BQ_LOGS_DIR]) {
|
|
45
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function generateJobId() {
|
|
49
|
+
const now = new Date();
|
|
50
|
+
const ts = now.toISOString().replace(/[-:T]/g, "").slice(0, 14);
|
|
51
|
+
const rand = Math.random().toString(36).slice(2, 8);
|
|
52
|
+
return `${ts}-${rand}`;
|
|
53
|
+
}
|
|
54
|
+
function expandPath(p) {
|
|
55
|
+
return p.startsWith("~") ? (0, node_path_1.join)(home, p.slice(1)) : p;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Run a command and return stdout. Throws on non-zero exit.
|
|
59
|
+
*/
|
|
60
|
+
function run(cmd, opts) {
|
|
61
|
+
try {
|
|
62
|
+
return (0, node_child_process_1.execSync)(cmd, {
|
|
63
|
+
cwd: opts?.cwd,
|
|
64
|
+
encoding: "utf-8",
|
|
65
|
+
stdio: opts?.quiet ? ["pipe", "pipe", "pipe"] : ["pipe", "pipe", "inherit"],
|
|
66
|
+
}).trim();
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
const e = err;
|
|
70
|
+
throw new Error(`Command failed: ${cmd}\n${e.stderr || e.message}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Spawn a long-running process and stream output to a log file + optional callback.
|
|
75
|
+
*/
|
|
76
|
+
function spawnWithLog(cmd, args, opts) {
|
|
77
|
+
return new Promise((resolve) => {
|
|
78
|
+
const logStream = require("node:fs").createWriteStream(opts.logPath, { flags: "a" });
|
|
79
|
+
const child = (0, node_child_process_1.spawn)(cmd, args, {
|
|
80
|
+
cwd: opts.cwd,
|
|
81
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
82
|
+
});
|
|
83
|
+
let output = "";
|
|
84
|
+
const handleData = (data) => {
|
|
85
|
+
const text = data.toString();
|
|
86
|
+
output += text;
|
|
87
|
+
logStream.write(text);
|
|
88
|
+
if (opts.onLine) {
|
|
89
|
+
for (const line of text.split("\n").filter(Boolean)) {
|
|
90
|
+
opts.onLine(line);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
child.stdout?.on("data", handleData);
|
|
95
|
+
child.stderr?.on("data", handleData);
|
|
96
|
+
child.on("close", (code) => {
|
|
97
|
+
logStream.end();
|
|
98
|
+
resolve({ exitCode: code ?? 1, output });
|
|
99
|
+
});
|
|
100
|
+
child.on("error", (err) => {
|
|
101
|
+
logStream.end();
|
|
102
|
+
output += `\nProcess error: ${err.message}`;
|
|
103
|
+
resolve({ exitCode: 1, output });
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Check if a process with the given PID is alive.
|
|
109
|
+
*/
|
|
110
|
+
function isProcessAlive(pid) {
|
|
111
|
+
try {
|
|
112
|
+
process.kill(pid, 0);
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a new worktree and optionally start Claude Code in it.
|
|
3
|
+
*/
|
|
4
|
+
export declare function createWorktree(name?: string, opts?: {
|
|
5
|
+
startClaude?: boolean;
|
|
6
|
+
}): string;
|
|
7
|
+
/**
|
|
8
|
+
* List all worktrees.
|
|
9
|
+
*/
|
|
10
|
+
export declare function listWorktrees(): void;
|
|
11
|
+
/**
|
|
12
|
+
* Clean up worktrees that have been merged or are stale.
|
|
13
|
+
*/
|
|
14
|
+
export declare function cleanWorktrees(opts?: {
|
|
15
|
+
force?: boolean;
|
|
16
|
+
maxAgeDays?: number;
|
|
17
|
+
}): void;
|
package/dist/worktree.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createWorktree = createWorktree;
|
|
4
|
+
exports.listWorktrees = listWorktrees;
|
|
5
|
+
exports.cleanWorktrees = cleanWorktrees;
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const node_child_process_1 = require("node:child_process");
|
|
9
|
+
const config_js_1 = require("./config.js");
|
|
10
|
+
const setup_claude_js_1 = require("./setup-claude.js");
|
|
11
|
+
const utils_js_1 = require("./utils.js");
|
|
12
|
+
const WORKTREE_DIR_NAME = "worktrees";
|
|
13
|
+
function getWorktreeBase() {
|
|
14
|
+
const mainRepo = (0, config_js_1.getMainRepo)();
|
|
15
|
+
const parent = (0, node_path_1.join)(mainRepo, "..");
|
|
16
|
+
const repoName = (0, node_path_1.basename)(mainRepo);
|
|
17
|
+
return (0, node_path_1.join)(parent, `${repoName}-worktrees`);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create a new worktree and optionally start Claude Code in it.
|
|
21
|
+
*/
|
|
22
|
+
function createWorktree(name, opts) {
|
|
23
|
+
const mainRepo = (0, config_js_1.getMainRepo)();
|
|
24
|
+
const base = getWorktreeBase();
|
|
25
|
+
// Generate name if not provided
|
|
26
|
+
if (!name) {
|
|
27
|
+
const ts = new Date().toISOString().replace(/[-:T]/g, "").slice(0, 12);
|
|
28
|
+
const rand = Math.random().toString(36).slice(2, 6);
|
|
29
|
+
name = `session-${ts}-${rand}`;
|
|
30
|
+
}
|
|
31
|
+
const worktreePath = (0, node_path_1.join)(base, name);
|
|
32
|
+
if ((0, node_fs_1.existsSync)(worktreePath)) {
|
|
33
|
+
utils_js_1.log.warn(`Worktree already exists: ${worktreePath}`);
|
|
34
|
+
if (opts?.startClaude) {
|
|
35
|
+
launchClaude(worktreePath);
|
|
36
|
+
}
|
|
37
|
+
return worktreePath;
|
|
38
|
+
}
|
|
39
|
+
// Fetch latest first
|
|
40
|
+
utils_js_1.log.info("Fetching latest...");
|
|
41
|
+
(0, utils_js_1.run)("git fetch --all --prune", { cwd: mainRepo, quiet: true });
|
|
42
|
+
// Get default branch
|
|
43
|
+
const defaultBranch = getDefaultBranch(mainRepo);
|
|
44
|
+
// Create worktree with a new branch based on default branch
|
|
45
|
+
const branchName = name;
|
|
46
|
+
utils_js_1.log.info(`Creating worktree: ${name} (from ${defaultBranch})`);
|
|
47
|
+
try {
|
|
48
|
+
(0, utils_js_1.run)(`git worktree add -b ${branchName} "${worktreePath}" origin/${defaultBranch}`, { cwd: mainRepo, quiet: true });
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// Branch might already exist
|
|
52
|
+
try {
|
|
53
|
+
(0, utils_js_1.run)(`git worktree add "${worktreePath}" ${branchName}`, { cwd: mainRepo, quiet: true });
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
utils_js_1.log.error(`Failed to create worktree: ${err}`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
utils_js_1.log.ok(`Worktree created: ${worktreePath}`);
|
|
61
|
+
utils_js_1.log.dim(`Branch: ${branchName}`);
|
|
62
|
+
// Auto-inject xbq instructions into CLAUDE.md
|
|
63
|
+
(0, setup_claude_js_1.setupClaude)(worktreePath);
|
|
64
|
+
if (opts?.startClaude) {
|
|
65
|
+
launchClaude(worktreePath);
|
|
66
|
+
}
|
|
67
|
+
return worktreePath;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* List all worktrees.
|
|
71
|
+
*/
|
|
72
|
+
function listWorktrees() {
|
|
73
|
+
const mainRepo = (0, config_js_1.getMainRepo)();
|
|
74
|
+
const worktrees = getWorktrees(mainRepo);
|
|
75
|
+
if (worktrees.length === 0) {
|
|
76
|
+
utils_js_1.log.info("No worktrees found");
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
console.log();
|
|
80
|
+
for (const wt of worktrees) {
|
|
81
|
+
const icon = wt.isMain ? "📦" : "🌿";
|
|
82
|
+
const age = getAge(wt.path);
|
|
83
|
+
const status = wt.isMain ? "(main)" : age;
|
|
84
|
+
console.log(` ${icon} ${wt.branch.padEnd(30)} ${wt.path}`);
|
|
85
|
+
console.log(` ${wt.head.slice(0, 8)} ${status}`);
|
|
86
|
+
}
|
|
87
|
+
console.log();
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Clean up worktrees that have been merged or are stale.
|
|
91
|
+
*/
|
|
92
|
+
function cleanWorktrees(opts) {
|
|
93
|
+
const mainRepo = (0, config_js_1.getMainRepo)();
|
|
94
|
+
const defaultBranch = getDefaultBranch(mainRepo);
|
|
95
|
+
const worktrees = getWorktrees(mainRepo).filter(wt => !wt.isMain);
|
|
96
|
+
if (worktrees.length === 0) {
|
|
97
|
+
utils_js_1.log.info("No worktrees to clean");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const maxAge = (opts?.maxAgeDays ?? 7) * 86400 * 1000;
|
|
101
|
+
let cleaned = 0;
|
|
102
|
+
for (const wt of worktrees) {
|
|
103
|
+
const shouldClean = shouldCleanWorktree(wt, mainRepo, defaultBranch, maxAge, opts?.force);
|
|
104
|
+
if (shouldClean.clean) {
|
|
105
|
+
utils_js_1.log.info(`Removing: ${wt.branch} — ${shouldClean.reason}`);
|
|
106
|
+
try {
|
|
107
|
+
(0, utils_js_1.run)(`git worktree remove "${wt.path}" --force`, { cwd: mainRepo, quiet: true });
|
|
108
|
+
// Also delete the branch if it was merged
|
|
109
|
+
if (shouldClean.reason.includes("merged")) {
|
|
110
|
+
try {
|
|
111
|
+
(0, utils_js_1.run)(`git branch -d ${wt.branch}`, { cwd: mainRepo, quiet: true });
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Branch might not exist or can't be deleted
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
cleaned++;
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
utils_js_1.log.warn(`Could not remove ${wt.branch}: ${err}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (cleaned > 0) {
|
|
125
|
+
utils_js_1.log.ok(`Cleaned ${cleaned} worktree(s)`);
|
|
126
|
+
// Prune stale worktree refs
|
|
127
|
+
(0, utils_js_1.run)("git worktree prune", { cwd: mainRepo, quiet: true });
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
utils_js_1.log.info("Nothing to clean");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function shouldCleanWorktree(wt, mainRepo, defaultBranch, maxAge, force) {
|
|
134
|
+
// Check if branch is merged into default branch
|
|
135
|
+
try {
|
|
136
|
+
const merged = (0, utils_js_1.run)(`git branch --merged origin/${defaultBranch}`, { cwd: mainRepo, quiet: true });
|
|
137
|
+
if (merged.split("\n").some(b => b.trim() === wt.branch)) {
|
|
138
|
+
return { clean: true, reason: "merged into " + defaultBranch };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// Ignore
|
|
143
|
+
}
|
|
144
|
+
// Check if worktree has no uncommitted changes and is old
|
|
145
|
+
if (force) {
|
|
146
|
+
return { clean: true, reason: "force clean" };
|
|
147
|
+
}
|
|
148
|
+
// Check age
|
|
149
|
+
try {
|
|
150
|
+
const stat = (0, node_fs_1.statSync)(wt.path);
|
|
151
|
+
const age = Date.now() - stat.mtimeMs;
|
|
152
|
+
if (age > maxAge) {
|
|
153
|
+
// Check for uncommitted changes
|
|
154
|
+
const status = (0, utils_js_1.run)("git status --porcelain", { cwd: wt.path, quiet: true });
|
|
155
|
+
if (status.length === 0) {
|
|
156
|
+
return { clean: true, reason: `stale (${Math.round(age / 86400000)}d old, no changes)` };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// Can't stat, probably already gone
|
|
162
|
+
return { clean: true, reason: "path inaccessible" };
|
|
163
|
+
}
|
|
164
|
+
return { clean: false, reason: "" };
|
|
165
|
+
}
|
|
166
|
+
function getWorktrees(mainRepo) {
|
|
167
|
+
const output = (0, utils_js_1.run)("git worktree list --porcelain", { cwd: mainRepo, quiet: true });
|
|
168
|
+
const worktrees = [];
|
|
169
|
+
let current = {};
|
|
170
|
+
for (const line of output.split("\n")) {
|
|
171
|
+
if (line.startsWith("worktree ")) {
|
|
172
|
+
if (current.path)
|
|
173
|
+
worktrees.push(current);
|
|
174
|
+
current = { path: line.slice(9), isMain: false };
|
|
175
|
+
}
|
|
176
|
+
else if (line.startsWith("HEAD ")) {
|
|
177
|
+
current.head = line.slice(5);
|
|
178
|
+
}
|
|
179
|
+
else if (line.startsWith("branch ")) {
|
|
180
|
+
current.branch = line.slice(7).replace("refs/heads/", "");
|
|
181
|
+
}
|
|
182
|
+
else if (line === "bare") {
|
|
183
|
+
current.isMain = true;
|
|
184
|
+
current.branch = current.branch || "(bare)";
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (current.path)
|
|
188
|
+
worktrees.push(current);
|
|
189
|
+
// Mark the main repo
|
|
190
|
+
if (worktrees.length > 0) {
|
|
191
|
+
worktrees[0].isMain = true;
|
|
192
|
+
}
|
|
193
|
+
return worktrees;
|
|
194
|
+
}
|
|
195
|
+
function getDefaultBranch(repoPath) {
|
|
196
|
+
try {
|
|
197
|
+
const ref = (0, utils_js_1.run)("git symbolic-ref refs/remotes/origin/HEAD", { cwd: repoPath, quiet: true });
|
|
198
|
+
return ref.replace("refs/remotes/origin/", "");
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
// Try common defaults
|
|
202
|
+
for (const branch of ["master", "main", "develop"]) {
|
|
203
|
+
try {
|
|
204
|
+
(0, utils_js_1.run)(`git rev-parse --verify origin/${branch}`, { cwd: repoPath, quiet: true });
|
|
205
|
+
return branch;
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return "master";
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function getAge(path) {
|
|
215
|
+
try {
|
|
216
|
+
const stat = (0, node_fs_1.statSync)(path);
|
|
217
|
+
const days = Math.round((Date.now() - stat.mtimeMs) / 86400000);
|
|
218
|
+
if (days === 0)
|
|
219
|
+
return "today";
|
|
220
|
+
if (days === 1)
|
|
221
|
+
return "1 day ago";
|
|
222
|
+
return `${days} days ago`;
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
return "unknown";
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function launchClaude(worktreePath) {
|
|
229
|
+
utils_js_1.log.info(`Starting Claude Code in ${worktreePath}`);
|
|
230
|
+
try {
|
|
231
|
+
(0, node_child_process_1.execSync)(`claude`, {
|
|
232
|
+
cwd: worktreePath,
|
|
233
|
+
stdio: "inherit",
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
// Claude exited, that's fine
|
|
238
|
+
}
|
|
239
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "xcode-build-queue",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Serial build queue for Xcode projects with git worktrees",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/a-ulkhan/xbq.git"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"xcode",
|
|
12
|
+
"build",
|
|
13
|
+
"queue",
|
|
14
|
+
"worktree",
|
|
15
|
+
"swift",
|
|
16
|
+
"ios"
|
|
17
|
+
],
|
|
18
|
+
"bin": {
|
|
19
|
+
"xbq": "dist/cli.js"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist/**/*.js",
|
|
23
|
+
"dist/**/*.d.ts",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc",
|
|
28
|
+
"prepare": "npm run build",
|
|
29
|
+
"dev": "tsx src/cli.ts",
|
|
30
|
+
"watch": "tsc --watch"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"commander": "^12.1.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^22.0.0",
|
|
37
|
+
"tsx": "^4.19.0",
|
|
38
|
+
"typescript": "^5.6.0"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18"
|
|
42
|
+
}
|
|
43
|
+
}
|