@xcodekit/xcode 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Evgenii Mozharovskii
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,231 @@
1
+ # @xcodekit/xcode
2
+
3
+ A fast, native Xcode `.pbxproj` parser and serializer for Node.js, written in Rust.
4
+
5
+ Drop-in replacement for the low-level API of [`@bacons/xcode`](https://github.com/EvanBacon/xcode) — same `parse()` and `build()` interface, **2-12x faster parsing**, byte-identical output, shipped as a single native binary per platform.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @xcodekit/xcode
11
+ ```
12
+
13
+ Platform-specific binaries are installed automatically via `optionalDependencies`.
14
+
15
+ ## Quick Start
16
+
17
+ ```js
18
+ import { parse, build } from "@xcodekit/xcode";
19
+ import { readFileSync, writeFileSync } from "fs";
20
+
21
+ // Parse a .pbxproj file
22
+ const text = readFileSync("project.pbxproj", "utf8");
23
+ const project = parse(text);
24
+
25
+ // Modify it
26
+ project.objects["YOUR_UUID"] = {
27
+ isa: "PBXFileReference",
28
+ path: "NewFile.swift",
29
+ sourceTree: "<group>",
30
+ };
31
+
32
+ // Write it back
33
+ const output = build(project);
34
+ writeFileSync("project.pbxproj", output);
35
+ ```
36
+
37
+ ## API
38
+
39
+ ### Low-Level (JSON)
40
+
41
+ #### `parse(text: string): object`
42
+
43
+ Parse a `.pbxproj` string into a JSON-compatible object. Matches the output of `@bacons/xcode/json`'s `parse()`.
44
+
45
+ #### `build(project: object): string`
46
+
47
+ Serialize a JSON object back to `.pbxproj` format. Produces byte-identical output to `@bacons/xcode/json`'s `build()`.
48
+
49
+ #### `buildFromJSON(json: string): string`
50
+
51
+ Same as `build()` but accepts `JSON.stringify(project)` directly. Faster because it avoids napi's recursive JS-to-Rust object marshalling.
52
+
53
+ ```js
54
+ const output = buildFromJSON(JSON.stringify(project));
55
+ ```
56
+
57
+ #### `parseAndBuild(text: string): string`
58
+
59
+ Parse and immediately re-serialize. Stays entirely in Rust with zero JS/Rust marshalling — the fastest possible round-trip path.
60
+
61
+ ```js
62
+ const output = parseAndBuild(readFileSync("project.pbxproj", "utf8"));
63
+ ```
64
+
65
+ ### High-Level (XcodeProject)
66
+
67
+ #### `XcodeProject.open(filePath: string): XcodeProject`
68
+
69
+ Open and parse a `.pbxproj` file from disk.
70
+
71
+ ```js
72
+ import { XcodeProject } from "@xcodekit/xcode";
73
+
74
+ const project = XcodeProject.open("ios/MyApp.xcodeproj/project.pbxproj");
75
+
76
+ // Properties
77
+ project.archiveVersion; // 1
78
+ project.objectVersion; // 46
79
+ project.filePath; // the path it was opened from
80
+
81
+ // Targets
82
+ const targets = project.getNativeTargets(); // UUID[]
83
+ const mainApp = project.findMainAppTarget("ios"); // UUID | null
84
+
85
+ // Build settings
86
+ project.getBuildSetting(targetUuid, "PRODUCT_BUNDLE_IDENTIFIER");
87
+ project.setBuildSetting(targetUuid, "SWIFT_VERSION", "5.0");
88
+ project.removeBuildSetting(targetUuid, "CODE_SIGN_IDENTITY");
89
+
90
+ // Serialize
91
+ const pbxproj = project.toBuild(); // string
92
+ const json = project.toJSON(); // object
93
+
94
+ // Write back to disk
95
+ project.save();
96
+
97
+ // Deterministic UUID generation
98
+ const uuid = project.getUniqueId("my-seed-string"); // 24-char hex
99
+ ```
100
+
101
+ ## Performance
102
+
103
+ Benchmarked on Apple M4 Pro, Node.js v24. Median of 200 iterations.
104
+
105
+ ### Parse
106
+
107
+ | Fixture | Rust | TypeScript | Speedup |
108
+ |---------|------|-----------|---------|
109
+ | swift-protobuf (257 KB) | 3.7 ms | 43.5 ms | **11.6x** |
110
+ | Cocoa-Application (166 KB) | 3.2 ms | 17.3 ms | **5.5x** |
111
+ | AFNetworking (99 KB) | 1.8 ms | 6.6 ms | **3.8x** |
112
+ | watch (48 KB) | 0.9 ms | 2.1 ms | **2.2x** |
113
+ | project (19 KB) | 0.4 ms | 0.8 ms | **2.2x** |
114
+
115
+ ### Round-Trip (parse + build)
116
+
117
+ | Fixture | Rust | TypeScript | Speedup |
118
+ |---------|------|-----------|---------|
119
+ | swift-protobuf (257 KB) | 9.1 ms | 63.1 ms | **6.9x** |
120
+ | Cocoa-Application (166 KB) | 8.0 ms | 22.2 ms | **2.8x** |
121
+ | AFNetworking (99 KB) | 4.2 ms | 9.3 ms | **2.2x** |
122
+ | watch (48 KB) | 2.1 ms | 2.7 ms | **1.3x** |
123
+ | project (19 KB) | 0.8 ms | 1.0 ms | **1.2x** |
124
+
125
+ ### parseAndBuild (zero marshalling)
126
+
127
+ | Fixture | Rust | TypeScript | Speedup |
128
+ |---------|------|-----------|---------|
129
+ | swift-protobuf (257 KB) | 4.6 ms | 62.9 ms | **13.6x** |
130
+ | Cocoa-Application (166 KB) | 3.8 ms | 22.0 ms | **5.8x** |
131
+ | AFNetworking (99 KB) | 1.9 ms | 9.1 ms | **4.9x** |
132
+ | watch (48 KB) | 0.9 ms | 2.6 ms | **2.8x** |
133
+ | project (19 KB) | 0.4 ms | 1.0 ms | **2.7x** |
134
+
135
+ ### Package Size
136
+
137
+ | | @bacons/xcode | @xcodekit/xcode |
138
+ |-|--------------|-------------------|
139
+ | Unpacked | 1.1 MB | 559 KB |
140
+ | Gzipped | ~400 KB | ~270 KB |
141
+
142
+ Run benchmarks yourself:
143
+
144
+ ```bash
145
+ make bench # both Rust + JS
146
+ make bench-rust # pure Rust (no napi overhead)
147
+ make bench-js # Rust vs TypeScript head-to-head
148
+ ```
149
+
150
+ ## Choosing the Right Function
151
+
152
+ | Use case | Function | Notes |
153
+ |----------|----------|-------|
154
+ | Parse only | `parse(text)` | 2-12x faster than TS |
155
+ | Build from JS object | `build(obj)` | Fastest on large files (>100 KB) |
156
+ | Build from JSON string | `buildFromJSON(json)` | Faster than `build()` on all sizes |
157
+ | Full round-trip | `parseAndBuild(text)` | Fastest path, zero JS/Rust overhead |
158
+ | Project manipulation | `XcodeProject.open()` | Stays in Rust, use `.toBuild()` to serialize |
159
+
160
+ ## Compatibility
161
+
162
+ - Full feature parity with `@bacons/xcode/json` (parse/build)
163
+ - 13/13 round-trip fixtures produce **byte-identical** output
164
+ - All escape sequences: standard (`\n`, `\t`, etc.), Unicode (`\Uxxxx`), octal, NeXTSTEP (128 entries)
165
+ - Xcode 16 file system synchronized groups
166
+ - Swift Package Manager references
167
+ - 106 tests (84 Rust + 22 JS)
168
+
169
+ ## Supported Platforms
170
+
171
+ | Platform | Architecture |
172
+ |----------|-------------|
173
+ | macOS | arm64 (Apple Silicon), x64 (Intel) |
174
+ | Linux | x64 (glibc), arm64 (glibc) |
175
+ | Windows | x64 (MSVC) |
176
+
177
+ ## Development
178
+
179
+ ```bash
180
+ # Prerequisites
181
+ # - Rust toolchain (cargo)
182
+ # - Node.js >= 18
183
+
184
+ # Install dependencies
185
+ npm install
186
+
187
+ # Run tests
188
+ make test # Rust + JS tests
189
+ make test-rust # Rust tests only (fast, no Node needed)
190
+ make test-js # JS tests (builds debug binary first)
191
+
192
+ # Build
193
+ make build # Release build
194
+ make build-debug # Debug build (faster compilation)
195
+
196
+ # Other
197
+ make check # Type-check without building
198
+ make fmt # cargo fmt
199
+ make lint # cargo clippy
200
+ make clean # Remove all artifacts
201
+ ```
202
+
203
+ ### Project Structure
204
+
205
+ ```
206
+ src/
207
+ lib.rs # napi exports: parse, build, XcodeProject
208
+ parser/
209
+ lexer.rs # Fast byte-scanning tokenizer
210
+ parser.rs # Recursive descent parser → PlistValue
211
+ escape.rs # String unescape (standard, Unicode, octal, NeXTSTEP)
212
+ writer/
213
+ serializer.rs # PlistValue → .pbxproj (section sorting, inline formatting)
214
+ comments.rs # UUID → inline comment generation
215
+ quotes.rs # String quoting/escaping
216
+ types/
217
+ plist.rs # PlistValue enum (String, Integer, Float, Data, Object, Array)
218
+ isa.rs # ISA enum (29 variants)
219
+ constants.rs # File type mappings, SDK versions, default build settings
220
+ project/
221
+ xcode_project.rs # High-level project container
222
+ uuid.rs # Deterministic MD5-based UUID generation
223
+ paths.rs # sourceTree path resolution
224
+ build_settings.rs # $(VARIABLE:transform) resolver
225
+ objects/
226
+ mod.rs # PbxObject + PbxObjectExt trait
227
+ ```
228
+
229
+ ## License
230
+
231
+ MIT
package/index.d.ts ADDED
@@ -0,0 +1,54 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+
4
+ /* auto-generated by NAPI-RS */
5
+
6
+ /** Parse a .pbxproj string into a JSON-compatible object. */
7
+ export declare function parse(text: string): any
8
+ /** Serialize a JSON object back to .pbxproj format. */
9
+ export declare function build(project: any): string
10
+ /**
11
+ * Serialize a JSON string back to .pbxproj format.
12
+ * Faster than `build()` — accepts `JSON.stringify(project)` directly,
13
+ * avoiding napi's recursive JS→Rust object marshalling.
14
+ */
15
+ export declare function buildFromJSON(json: string): string
16
+ /**
17
+ * Parse and immediately re-serialize a .pbxproj string.
18
+ * Fastest path — stays entirely in Rust, zero JS↔Rust marshalling.
19
+ */
20
+ export declare function parseAndBuild(text: string): string
21
+ /** XcodeProject class for high-level API. */
22
+ export declare class XcodeProject {
23
+ /** Open and parse a .pbxproj file from disk. */
24
+ static open(filePath: string): XcodeProject
25
+ /** Convert the project to a JSON-compatible object. */
26
+ toJSON(): any
27
+ /** Serialize the project back to .pbxproj format. */
28
+ toBuild(): string
29
+ /** Write the project back to its original file. */
30
+ save(): void
31
+ /** Get the file path this project was loaded from. */
32
+ get filePath(): string | null
33
+ /** Get the archive version. */
34
+ get archiveVersion(): number
35
+ /** Get the object version. */
36
+ get objectVersion(): number
37
+ /** Get all native target UUIDs. */
38
+ getNativeTargets(): Array<string>
39
+ /** Get a build setting value from a target. */
40
+ getBuildSetting(targetUuid: string, key: string): any
41
+ /** Set a build setting on all configurations for a target. */
42
+ setBuildSetting(targetUuid: string, key: string, value: string): boolean
43
+ /** Remove a build setting from all configurations for a target. */
44
+ removeBuildSetting(targetUuid: string, key: string): boolean
45
+ /**
46
+ * Find orphaned references (UUIDs referenced but not present in objects).
47
+ * Returns array of { referrerUuid, referrerIsa, property, orphanUuid }.
48
+ */
49
+ findOrphanedReferences(): Array<any>
50
+ /** Find the main app target UUID. */
51
+ findMainAppTarget(platform?: string | undefined | null): string | null
52
+ /** Generate a unique UUID. */
53
+ getUniqueId(seed: string): string
54
+ }
package/index.js ADDED
@@ -0,0 +1,319 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ /* prettier-ignore */
4
+
5
+ /* auto-generated by NAPI-RS */
6
+
7
+ const { existsSync, readFileSync } = require('fs')
8
+ const { join } = require('path')
9
+
10
+ const { platform, arch } = process
11
+
12
+ let nativeBinding = null
13
+ let localFileExisted = false
14
+ let loadError = null
15
+
16
+ function isMusl() {
17
+ // For Node 10
18
+ if (!process.report || typeof process.report.getReport !== 'function') {
19
+ try {
20
+ const lddPath = require('child_process').execSync('which ldd').toString().trim()
21
+ return readFileSync(lddPath, 'utf8').includes('musl')
22
+ } catch (e) {
23
+ return true
24
+ }
25
+ } else {
26
+ const { glibcVersionRuntime } = process.report.getReport().header
27
+ return !glibcVersionRuntime
28
+ }
29
+ }
30
+
31
+ switch (platform) {
32
+ case 'android':
33
+ switch (arch) {
34
+ case 'arm64':
35
+ localFileExisted = existsSync(join(__dirname, 'xcode.android-arm64.node'))
36
+ try {
37
+ if (localFileExisted) {
38
+ nativeBinding = require('./xcode.android-arm64.node')
39
+ } else {
40
+ nativeBinding = require('@xcodekit/xcode-android-arm64')
41
+ }
42
+ } catch (e) {
43
+ loadError = e
44
+ }
45
+ break
46
+ case 'arm':
47
+ localFileExisted = existsSync(join(__dirname, 'xcode.android-arm-eabi.node'))
48
+ try {
49
+ if (localFileExisted) {
50
+ nativeBinding = require('./xcode.android-arm-eabi.node')
51
+ } else {
52
+ nativeBinding = require('@xcodekit/xcode-android-arm-eabi')
53
+ }
54
+ } catch (e) {
55
+ loadError = e
56
+ }
57
+ break
58
+ default:
59
+ throw new Error(`Unsupported architecture on Android ${arch}`)
60
+ }
61
+ break
62
+ case 'win32':
63
+ switch (arch) {
64
+ case 'x64':
65
+ localFileExisted = existsSync(
66
+ join(__dirname, 'xcode.win32-x64-msvc.node')
67
+ )
68
+ try {
69
+ if (localFileExisted) {
70
+ nativeBinding = require('./xcode.win32-x64-msvc.node')
71
+ } else {
72
+ nativeBinding = require('@xcodekit/xcode-win32-x64-msvc')
73
+ }
74
+ } catch (e) {
75
+ loadError = e
76
+ }
77
+ break
78
+ case 'ia32':
79
+ localFileExisted = existsSync(
80
+ join(__dirname, 'xcode.win32-ia32-msvc.node')
81
+ )
82
+ try {
83
+ if (localFileExisted) {
84
+ nativeBinding = require('./xcode.win32-ia32-msvc.node')
85
+ } else {
86
+ nativeBinding = require('@xcodekit/xcode-win32-ia32-msvc')
87
+ }
88
+ } catch (e) {
89
+ loadError = e
90
+ }
91
+ break
92
+ case 'arm64':
93
+ localFileExisted = existsSync(
94
+ join(__dirname, 'xcode.win32-arm64-msvc.node')
95
+ )
96
+ try {
97
+ if (localFileExisted) {
98
+ nativeBinding = require('./xcode.win32-arm64-msvc.node')
99
+ } else {
100
+ nativeBinding = require('@xcodekit/xcode-win32-arm64-msvc')
101
+ }
102
+ } catch (e) {
103
+ loadError = e
104
+ }
105
+ break
106
+ default:
107
+ throw new Error(`Unsupported architecture on Windows: ${arch}`)
108
+ }
109
+ break
110
+ case 'darwin':
111
+ localFileExisted = existsSync(join(__dirname, 'xcode.darwin-universal.node'))
112
+ try {
113
+ if (localFileExisted) {
114
+ nativeBinding = require('./xcode.darwin-universal.node')
115
+ } else {
116
+ nativeBinding = require('@xcodekit/xcode-darwin-universal')
117
+ }
118
+ break
119
+ } catch {}
120
+ switch (arch) {
121
+ case 'x64':
122
+ localFileExisted = existsSync(join(__dirname, 'xcode.darwin-x64.node'))
123
+ try {
124
+ if (localFileExisted) {
125
+ nativeBinding = require('./xcode.darwin-x64.node')
126
+ } else {
127
+ nativeBinding = require('@xcodekit/xcode-darwin-x64')
128
+ }
129
+ } catch (e) {
130
+ loadError = e
131
+ }
132
+ break
133
+ case 'arm64':
134
+ localFileExisted = existsSync(
135
+ join(__dirname, 'xcode.darwin-arm64.node')
136
+ )
137
+ try {
138
+ if (localFileExisted) {
139
+ nativeBinding = require('./xcode.darwin-arm64.node')
140
+ } else {
141
+ nativeBinding = require('@xcodekit/xcode-darwin-arm64')
142
+ }
143
+ } catch (e) {
144
+ loadError = e
145
+ }
146
+ break
147
+ default:
148
+ throw new Error(`Unsupported architecture on macOS: ${arch}`)
149
+ }
150
+ break
151
+ case 'freebsd':
152
+ if (arch !== 'x64') {
153
+ throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
154
+ }
155
+ localFileExisted = existsSync(join(__dirname, 'xcode.freebsd-x64.node'))
156
+ try {
157
+ if (localFileExisted) {
158
+ nativeBinding = require('./xcode.freebsd-x64.node')
159
+ } else {
160
+ nativeBinding = require('@xcodekit/xcode-freebsd-x64')
161
+ }
162
+ } catch (e) {
163
+ loadError = e
164
+ }
165
+ break
166
+ case 'linux':
167
+ switch (arch) {
168
+ case 'x64':
169
+ if (isMusl()) {
170
+ localFileExisted = existsSync(
171
+ join(__dirname, 'xcode.linux-x64-musl.node')
172
+ )
173
+ try {
174
+ if (localFileExisted) {
175
+ nativeBinding = require('./xcode.linux-x64-musl.node')
176
+ } else {
177
+ nativeBinding = require('@xcodekit/xcode-linux-x64-musl')
178
+ }
179
+ } catch (e) {
180
+ loadError = e
181
+ }
182
+ } else {
183
+ localFileExisted = existsSync(
184
+ join(__dirname, 'xcode.linux-x64-gnu.node')
185
+ )
186
+ try {
187
+ if (localFileExisted) {
188
+ nativeBinding = require('./xcode.linux-x64-gnu.node')
189
+ } else {
190
+ nativeBinding = require('@xcodekit/xcode-linux-x64-gnu')
191
+ }
192
+ } catch (e) {
193
+ loadError = e
194
+ }
195
+ }
196
+ break
197
+ case 'arm64':
198
+ if (isMusl()) {
199
+ localFileExisted = existsSync(
200
+ join(__dirname, 'xcode.linux-arm64-musl.node')
201
+ )
202
+ try {
203
+ if (localFileExisted) {
204
+ nativeBinding = require('./xcode.linux-arm64-musl.node')
205
+ } else {
206
+ nativeBinding = require('@xcodekit/xcode-linux-arm64-musl')
207
+ }
208
+ } catch (e) {
209
+ loadError = e
210
+ }
211
+ } else {
212
+ localFileExisted = existsSync(
213
+ join(__dirname, 'xcode.linux-arm64-gnu.node')
214
+ )
215
+ try {
216
+ if (localFileExisted) {
217
+ nativeBinding = require('./xcode.linux-arm64-gnu.node')
218
+ } else {
219
+ nativeBinding = require('@xcodekit/xcode-linux-arm64-gnu')
220
+ }
221
+ } catch (e) {
222
+ loadError = e
223
+ }
224
+ }
225
+ break
226
+ case 'arm':
227
+ if (isMusl()) {
228
+ localFileExisted = existsSync(
229
+ join(__dirname, 'xcode.linux-arm-musleabihf.node')
230
+ )
231
+ try {
232
+ if (localFileExisted) {
233
+ nativeBinding = require('./xcode.linux-arm-musleabihf.node')
234
+ } else {
235
+ nativeBinding = require('@xcodekit/xcode-linux-arm-musleabihf')
236
+ }
237
+ } catch (e) {
238
+ loadError = e
239
+ }
240
+ } else {
241
+ localFileExisted = existsSync(
242
+ join(__dirname, 'xcode.linux-arm-gnueabihf.node')
243
+ )
244
+ try {
245
+ if (localFileExisted) {
246
+ nativeBinding = require('./xcode.linux-arm-gnueabihf.node')
247
+ } else {
248
+ nativeBinding = require('@xcodekit/xcode-linux-arm-gnueabihf')
249
+ }
250
+ } catch (e) {
251
+ loadError = e
252
+ }
253
+ }
254
+ break
255
+ case 'riscv64':
256
+ if (isMusl()) {
257
+ localFileExisted = existsSync(
258
+ join(__dirname, 'xcode.linux-riscv64-musl.node')
259
+ )
260
+ try {
261
+ if (localFileExisted) {
262
+ nativeBinding = require('./xcode.linux-riscv64-musl.node')
263
+ } else {
264
+ nativeBinding = require('@xcodekit/xcode-linux-riscv64-musl')
265
+ }
266
+ } catch (e) {
267
+ loadError = e
268
+ }
269
+ } else {
270
+ localFileExisted = existsSync(
271
+ join(__dirname, 'xcode.linux-riscv64-gnu.node')
272
+ )
273
+ try {
274
+ if (localFileExisted) {
275
+ nativeBinding = require('./xcode.linux-riscv64-gnu.node')
276
+ } else {
277
+ nativeBinding = require('@xcodekit/xcode-linux-riscv64-gnu')
278
+ }
279
+ } catch (e) {
280
+ loadError = e
281
+ }
282
+ }
283
+ break
284
+ case 's390x':
285
+ localFileExisted = existsSync(
286
+ join(__dirname, 'xcode.linux-s390x-gnu.node')
287
+ )
288
+ try {
289
+ if (localFileExisted) {
290
+ nativeBinding = require('./xcode.linux-s390x-gnu.node')
291
+ } else {
292
+ nativeBinding = require('@xcodekit/xcode-linux-s390x-gnu')
293
+ }
294
+ } catch (e) {
295
+ loadError = e
296
+ }
297
+ break
298
+ default:
299
+ throw new Error(`Unsupported architecture on Linux: ${arch}`)
300
+ }
301
+ break
302
+ default:
303
+ throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
304
+ }
305
+
306
+ if (!nativeBinding) {
307
+ if (loadError) {
308
+ throw loadError
309
+ }
310
+ throw new Error(`Failed to load native binding`)
311
+ }
312
+
313
+ const { parse, build, buildFromJSON, parseAndBuild, XcodeProject } = nativeBinding
314
+
315
+ module.exports.parse = parse
316
+ module.exports.build = build
317
+ module.exports.buildFromJSON = buildFromJSON
318
+ module.exports.parseAndBuild = parseAndBuild
319
+ module.exports.XcodeProject = XcodeProject
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@xcodekit/xcode",
3
+ "version": "0.1.0",
4
+ "description": "Parse, manipulate, and serialize Xcode .pbxproj files — native Rust rewrite",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "napi": {
8
+ "name": "xcode",
9
+ "triples": {
10
+ "defaults": true,
11
+ "additional": [
12
+ "aarch64-apple-darwin",
13
+ "aarch64-unknown-linux-gnu"
14
+ ]
15
+ }
16
+ },
17
+ "license": "MIT",
18
+ "optionalDependencies": {
19
+ "@xcodekit/xcode-darwin-arm64": "0.1.0",
20
+ "@xcodekit/xcode-darwin-x64": "0.1.0",
21
+ "@xcodekit/xcode-linux-arm64-gnu": "0.1.0",
22
+ "@xcodekit/xcode-linux-x64-gnu": "0.1.0",
23
+ "@xcodekit/xcode-win32-x64-msvc": "0.1.0"
24
+ },
25
+ "devDependencies": {
26
+ "@napi-rs/cli": "^2.18.0",
27
+ "ava": "^6.0.0"
28
+ },
29
+ "scripts": {
30
+ "artifacts": "napi artifacts",
31
+ "build": "napi build --platform --release",
32
+ "build:debug": "napi build --platform",
33
+ "test": "ava",
34
+ "universal": "napi universal",
35
+ "version": "napi version"
36
+ },
37
+ "ava": {
38
+ "timeout": "60000"
39
+ },
40
+ "files": [
41
+ "index.js",
42
+ "index.d.ts",
43
+ "types.d.ts"
44
+ ],
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "https://github.com/nickmozharovsky/xcode"
48
+ },
49
+ "keywords": [
50
+ "xcode",
51
+ "pbxproj",
52
+ "ios",
53
+ "apple",
54
+ "parser",
55
+ "native",
56
+ "napi",
57
+ "rust"
58
+ ]
59
+ }
package/types.d.ts ADDED
@@ -0,0 +1,258 @@
1
+ /**
2
+ * Supplemental type definitions for @xcodekit/xcode.
3
+ *
4
+ * These provide rich types for the parsed .pbxproj JSON structure
5
+ * beyond what napi-rs auto-generates.
6
+ */
7
+
8
+ /** ISA types for all known Xcode project object types. */
9
+ export type ISA =
10
+ | "PBXBuildFile"
11
+ | "PBXAppleScriptBuildPhase"
12
+ | "PBXCopyFilesBuildPhase"
13
+ | "PBXFrameworksBuildPhase"
14
+ | "PBXHeadersBuildPhase"
15
+ | "PBXResourcesBuildPhase"
16
+ | "PBXShellScriptBuildPhase"
17
+ | "PBXSourcesBuildPhase"
18
+ | "PBXRezBuildPhase"
19
+ | "PBXContainerItemProxy"
20
+ | "PBXFileReference"
21
+ | "PBXGroup"
22
+ | "PBXVariantGroup"
23
+ | "XCVersionGroup"
24
+ | "PBXFileSystemSynchronizedRootGroup"
25
+ | "PBXFileSystemSynchronizedBuildFileExceptionSet"
26
+ | "PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet"
27
+ | "PBXNativeTarget"
28
+ | "PBXAggregateTarget"
29
+ | "PBXLegacyTarget"
30
+ | "PBXProject"
31
+ | "PBXTargetDependency"
32
+ | "XCBuildConfiguration"
33
+ | "XCConfigurationList"
34
+ | "PBXBuildRule"
35
+ | "PBXReferenceProxy"
36
+ | "XCSwiftPackageProductDependency"
37
+ | "XCRemoteSwiftPackageReference"
38
+ | "XCLocalSwiftPackageReference";
39
+
40
+ /** A 24-character hexadecimal UUID string. */
41
+ export type UUID = string;
42
+
43
+ /** Boolean as 0 | 1 integer. */
44
+ export type BoolNumber = 0 | 1;
45
+
46
+ /** Boolean as YES/NO string. */
47
+ export type BoolString = "YES" | "NO" | "YES_ERROR" | "YES_AGGRESSIVE";
48
+
49
+ /** Source tree reference types. */
50
+ export type SourceTree =
51
+ | "BUILT_PRODUCTS_DIR"
52
+ | "DEVELOPER_DIR"
53
+ | "SOURCE_ROOT"
54
+ | "SDKROOT"
55
+ | "<group>"
56
+ | "<absolute>";
57
+
58
+ /** CopyFilesBuildPhase destination subfolder spec. */
59
+ export enum SubFolder {
60
+ absolutePath = 0,
61
+ wrapper = 1,
62
+ executables = 6,
63
+ resources = 7,
64
+ frameworks = 10,
65
+ sharedFrameworks = 11,
66
+ sharedSupport = 12,
67
+ plugins = 13,
68
+ javaResources = 15,
69
+ productsDirectory = 16,
70
+ }
71
+
72
+ /** Container item proxy type. */
73
+ export enum ProxyType {
74
+ targetReference = 1,
75
+ reference = 2,
76
+ }
77
+
78
+ /** Common file type UTIs. */
79
+ export type FileType =
80
+ | "sourcecode.swift"
81
+ | "sourcecode.c.c"
82
+ | "sourcecode.c.h"
83
+ | "sourcecode.c.objc"
84
+ | "sourcecode.cpp.cpp"
85
+ | "sourcecode.cpp.objcpp"
86
+ | "sourcecode.javascript"
87
+ | "wrapper.application"
88
+ | "wrapper.framework"
89
+ | "wrapper.app-extension"
90
+ | "wrapper.plug-in"
91
+ | "wrapper.xcframework"
92
+ | "compiled.mach-o.dylib"
93
+ | "archive.ar"
94
+ | "folder.assetcatalog"
95
+ | "text.plist.xml"
96
+ | "text.plist.strings"
97
+ | "text.plist.entitlements"
98
+ | "text.xcconfig"
99
+ | "file.storyboard"
100
+ | "file.xib"
101
+ | "file.intentdefinition"
102
+ | "image.png"
103
+ | "image.jpeg"
104
+ | "net.daringfireball.markdown"
105
+ | string;
106
+
107
+ /** Common product type UTIs. */
108
+ export type ProductType =
109
+ | "com.apple.product-type.application"
110
+ | "com.apple.product-type.application.on-demand-install-capable"
111
+ | "com.apple.product-type.app-extension"
112
+ | "com.apple.product-type.bundle"
113
+ | "com.apple.product-type.framework"
114
+ | "com.apple.product-type.library.dynamic"
115
+ | "com.apple.product-type.library.static"
116
+ | "com.apple.product-type.tool"
117
+ | "com.apple.product-type.unit-test-bundle"
118
+ | "com.apple.product-type.ui-testing-bundle"
119
+ | "com.apple.product-type.application.watchapp"
120
+ | "com.apple.product-type.application.watchapp2"
121
+ | "com.apple.product-type.watchkit-extension"
122
+ | "com.apple.product-type.extensionkit-extension"
123
+ | string;
124
+
125
+ /** Build settings dictionary. */
126
+ export interface BuildSettings {
127
+ ALWAYS_SEARCH_USER_PATHS?: BoolString;
128
+ ASSETCATALOG_COMPILER_APPICON_NAME?: string;
129
+ CLANG_ENABLE_MODULES?: BoolString;
130
+ CLANG_ENABLE_OBJC_ARC?: BoolString;
131
+ CODE_SIGN_ENTITLEMENTS?: string;
132
+ CODE_SIGN_IDENTITY?: string;
133
+ CODE_SIGN_STYLE?: "Automatic" | "Manual";
134
+ CURRENT_PROJECT_VERSION?: string;
135
+ DEBUG_INFORMATION_FORMAT?: "dwarf" | "dwarf-with-dsym";
136
+ DEVELOPMENT_TEAM?: string;
137
+ GCC_OPTIMIZATION_LEVEL?: string;
138
+ GCC_PREPROCESSOR_DEFINITIONS?: string | string[];
139
+ GENERATE_INFOPLIST_FILE?: BoolString;
140
+ INFOPLIST_FILE?: string;
141
+ INFOPLIST_KEY_CFBundleDisplayName?: string;
142
+ IPHONEOS_DEPLOYMENT_TARGET?: string;
143
+ MACOSX_DEPLOYMENT_TARGET?: string;
144
+ TVOS_DEPLOYMENT_TARGET?: string;
145
+ WATCHOS_DEPLOYMENT_TARGET?: string;
146
+ MARKETING_VERSION?: string;
147
+ PRODUCT_BUNDLE_IDENTIFIER?: string;
148
+ PRODUCT_NAME?: string;
149
+ SWIFT_VERSION?: string;
150
+ TARGETED_DEVICE_FAMILY?: string;
151
+ [key: string]: string | string[] | number | undefined;
152
+ }
153
+
154
+ /** Base object with isa field. */
155
+ export interface PBXObjectBase {
156
+ isa: ISA;
157
+ [key: string]: any;
158
+ }
159
+
160
+ /** PBXBuildFile object. */
161
+ export interface PBXBuildFile extends PBXObjectBase {
162
+ isa: "PBXBuildFile";
163
+ fileRef?: UUID;
164
+ productRef?: UUID;
165
+ settings?: Record<string, any>;
166
+ platformFilter?: string;
167
+ platformFilters?: string[];
168
+ }
169
+
170
+ /** PBXFileReference object. */
171
+ export interface PBXFileReference extends PBXObjectBase {
172
+ isa: "PBXFileReference";
173
+ fileEncoding?: number;
174
+ lastKnownFileType?: FileType;
175
+ explicitFileType?: FileType;
176
+ includeInIndex?: BoolNumber;
177
+ name?: string;
178
+ path?: string;
179
+ sourceTree?: SourceTree;
180
+ }
181
+
182
+ /** PBXGroup object. */
183
+ export interface PBXGroup extends PBXObjectBase {
184
+ isa: "PBXGroup";
185
+ children: UUID[];
186
+ name?: string;
187
+ path?: string;
188
+ sourceTree?: SourceTree;
189
+ }
190
+
191
+ /** Build phase (shared fields for all 8 types). */
192
+ export interface AbstractBuildPhase extends PBXObjectBase {
193
+ buildActionMask?: number;
194
+ files: UUID[];
195
+ runOnlyForDeploymentPostprocessing?: BoolNumber;
196
+ }
197
+
198
+ /** PBXNativeTarget object. */
199
+ export interface PBXNativeTarget extends PBXObjectBase {
200
+ isa: "PBXNativeTarget";
201
+ buildConfigurationList: UUID;
202
+ buildPhases: UUID[];
203
+ buildRules: UUID[];
204
+ dependencies: UUID[];
205
+ name: string;
206
+ productName?: string;
207
+ productReference?: UUID;
208
+ productType: ProductType;
209
+ packageProductDependencies?: UUID[];
210
+ fileSystemSynchronizedGroups?: UUID[];
211
+ }
212
+
213
+ /** PBXProject (root object). */
214
+ export interface PBXProject extends PBXObjectBase {
215
+ isa: "PBXProject";
216
+ buildConfigurationList: UUID;
217
+ compatibilityVersion: string;
218
+ developmentRegion: string;
219
+ hasScannedForEncodings: BoolNumber;
220
+ knownRegions: string[];
221
+ mainGroup: UUID;
222
+ productRefGroup?: UUID;
223
+ projectDirPath: string;
224
+ projectRoot: string;
225
+ targets: UUID[];
226
+ packageReferences?: UUID[];
227
+ attributes?: {
228
+ LastSwiftUpdateCheck?: string;
229
+ LastUpgradeCheck?: string;
230
+ TargetAttributes?: Record<UUID, Record<string, any>>;
231
+ [key: string]: any;
232
+ };
233
+ }
234
+
235
+ /** XCBuildConfiguration object. */
236
+ export interface XCBuildConfiguration extends PBXObjectBase {
237
+ isa: "XCBuildConfiguration";
238
+ name: string;
239
+ buildSettings: BuildSettings;
240
+ baseConfigurationReference?: UUID;
241
+ }
242
+
243
+ /** XCConfigurationList object. */
244
+ export interface XCConfigurationList extends PBXObjectBase {
245
+ isa: "XCConfigurationList";
246
+ buildConfigurations: UUID[];
247
+ defaultConfigurationIsVisible?: BoolNumber;
248
+ defaultConfigurationName?: string;
249
+ }
250
+
251
+ /** The top-level parsed .pbxproj structure. */
252
+ export interface ParsedProject {
253
+ archiveVersion: number;
254
+ classes: Record<string, any>;
255
+ objectVersion: number;
256
+ objects: Record<UUID, PBXObjectBase>;
257
+ rootObject: UUID;
258
+ }