rork-xcode 0.3.0 → 0.5.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 +24 -21
- package/dist/index.d.ts +518 -72
- package/dist/index.js +804 -235
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,11 +25,11 @@ const text = buildPbxproj(project); // byte-stable, Xcode-canonical layout
|
|
|
25
25
|
|
|
26
26
|
`rork-xcode` is designed for exactly that situation:
|
|
27
27
|
|
|
28
|
-
- **Zero dependencies.** The pbxproj grammar is a small OpenStep-style property list dialect
|
|
28
|
+
- **Zero dependencies.** The pbxproj grammar is a small OpenStep-style property list dialect of dictionaries, arrays, strings, and hex data runs. A dedicated scanner covers it completely, with no general-purpose parser stack, no native addon, and no WASM blob.
|
|
29
29
|
- **One artifact, one code path.** A single ESM file with named exports. No environment-conditional entry points, no reliance on ambient globals like `Buffer`. What you test locally is what runs in production, whatever the bundler.
|
|
30
30
|
- **Xcode-canonical output.** The serializer reproduces the layout Xcode itself writes (tab indentation, per-isa object sections in sorted order, single-line build-file entries, and derived reference comments like `13B07F86… /* AppDelegate.swift in Sources */`), so diffs against Xcode-saved projects stay minimal and Xcode does not rewrite the file on next save.
|
|
31
31
|
- **Round-trip faithful.** Parse → build is byte-identical for Xcode-canonical documents and a fixed point for everything else. Lexical subtleties that plain number conversion would destroy (leading-zero values like `0755`, trailing-zero versions like `5.0`, digit runs longer than the double-precision safe range) are preserved as strings by design.
|
|
32
|
-
- **Loud failure modes.** Malformed documents fail with a typed error carrying line and column
|
|
32
|
+
- **Loud failure modes.** Malformed documents fail with a typed error carrying line and column, and unrepresentable values (`null`, booleans, non-finite numbers) fail with the exact path of the offending value. Nothing is silently dropped.
|
|
33
33
|
|
|
34
34
|
## Install
|
|
35
35
|
|
|
@@ -222,45 +222,48 @@ for (const [id, object] of project.objects()) {
|
|
|
222
222
|
|
|
223
223
|
### Semantics
|
|
224
224
|
|
|
225
|
-
- **
|
|
225
|
+
- **A view class for every kind.** Every `isa` Xcode writes has its own class, and each class declares its `isa` through a static field the view factory dispatches on. Targets of every kind, groups, variant and version groups, synchronized folders and their exception sets, every build phase kind, build rules, build files, build styles, configurations and configuration lists, file references, target dependencies, container item proxies, reference proxies, and Swift package references and products all come back typed. Kinds outside the vocabulary fall back to a generic `XcodeObject` with the same read and write access, so nothing in a document is out of reach.
|
|
226
|
+
- **`is()` narrowing.** Every view class carries a static type guard for discriminating mixed objects: `if (NativeTarget.is(object))` narrows the way `instanceof` does, subclasses included. Relationships resolve typed too, from `dependency.target()` and `buildFile.productDependency()` to `configuration.buildSettings` as a live typed dictionary.
|
|
226
227
|
- **Typed, open property shapes.** Known keys autocomplete (`target.properties.productType`) and the shape stays open, so keys like `INFOPLIST_KEY_*` settings remain first-class. The shapes describe well-formed documents. When reading untrusted input, use the narrowing accessors, which never trust them.
|
|
227
|
-
- **Two verb families.** `add*` wires something to its owner (a dependency, a package, a framework, a synchronized folder) and is idempotent
|
|
228
|
+
- **Two verb families.** `add*` wires something to its owner (a dependency, a package, a framework, a synchronized folder) and is idempotent, so re-adding returns the existing wiring. `ensure*` returns a structural container, creating it when missing (a build phase, a group chain, the Products group). Both families can therefore run unconditionally in scaffold and repair flows.
|
|
228
229
|
- **Deterministic identifiers.** New objects get ids derived from what they are (`XX` + 20 digest characters + `XX`, from an embedded hash), so programmatic edits are reproducible run to run and diffs stay minimal. Collisions within a document resolve deterministically, and identical edit sequences produce byte-identical documents.
|
|
229
230
|
- **Soft reads, loud writes.** Real-world projects can be malformed, so lookups return `undefined` where a document could omit something. Operations that cannot proceed without structure (no root project object, an unknown product type, a view whose object was deleted) throw `XcodeModelError`.
|
|
230
231
|
- **Identity-mapped views.** Two lookups of the same id return the same instance, so views compare with `===`.
|
|
231
232
|
|
|
232
233
|
## Schemes
|
|
233
234
|
|
|
234
|
-
`.xcscheme` files describe how Xcode builds, runs, tests, and archives a target. They are not property lists but a small XML dialect of their own, and
|
|
235
|
+
`.xcscheme` files describe how Xcode builds, runs, tests, and archives a target. They are not property lists but a small XML dialect of their own, and the scheme module covers it with the same contract as the pbxproj functions. An Xcode-written scheme rebuilds byte for byte, any other input reaches Xcode's canonical layout in one build, and malformed input fails with a typed error carrying line and column.
|
|
236
|
+
|
|
237
|
+
`Xcscheme` is the model. Buildable references are the elements editing flows touch, and they come back as typed views:
|
|
235
238
|
|
|
236
239
|
```ts
|
|
237
|
-
import {
|
|
240
|
+
import { Xcscheme } from "rork-xcode";
|
|
238
241
|
|
|
239
|
-
const scheme =
|
|
242
|
+
const scheme = Xcscheme.parse(xcschemeText);
|
|
240
243
|
|
|
241
|
-
//
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
reference.
|
|
244
|
+
// Rename the app while keeping each product's own shape, so a testable
|
|
245
|
+
// like DemoAppTests.xctest stays a test bundle.
|
|
246
|
+
for (const reference of scheme.buildableReferences()) {
|
|
247
|
+
const { blueprintName, buildableName } = reference;
|
|
248
|
+
if (blueprintName) reference.blueprintName = blueprintName.replace("DemoApp", "RenamedApp");
|
|
249
|
+
if (buildableName) reference.buildableName = buildableName.replace("DemoApp", "RenamedApp");
|
|
250
|
+
reference.referencedContainer = "container:RenamedApp.xcodeproj";
|
|
246
251
|
}
|
|
247
252
|
|
|
248
|
-
const text =
|
|
253
|
+
const text = scheme.build();
|
|
249
254
|
```
|
|
250
255
|
|
|
251
|
-
`
|
|
256
|
+
`Xcscheme.create` produces the scheme Xcode's own "New Scheme" action writes for an application target, wired to the target's object id from the project document:
|
|
252
257
|
|
|
253
258
|
```ts
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const scheme = createXcscheme({
|
|
259
|
+
const scheme = Xcscheme.create({
|
|
257
260
|
appName: "DemoApp",
|
|
258
261
|
blueprintIdentifier: app.id,
|
|
259
262
|
});
|
|
260
|
-
const text =
|
|
263
|
+
const text = scheme.build();
|
|
261
264
|
```
|
|
262
265
|
|
|
263
|
-
|
|
266
|
+
Underneath, the document is a plain tree of elements with ordered attributes and children, reachable through `scheme.root` and `scheme.elements(name)`, so anything the typed surface does not cover stays one property away. `parseXcscheme` and `buildXcscheme` remain available for working with the tree directly. Attribute order is preserved and meaningful, which is how byte-identical round-trips fall out. Comments are kept, attribute values resolve the character references Xcode writes (`"`, `&`, ` ` and friends), and the writer re-escapes them identically.
|
|
264
267
|
|
|
265
268
|
## Performance
|
|
266
269
|
|
|
@@ -283,8 +286,8 @@ Measured on an Apple M5 Max, Node.js 24, single thread, with `@bacons/xcode` 1.0
|
|
|
283
286
|
|
|
284
287
|
### Key performance features
|
|
285
288
|
|
|
286
|
-
- **Single-pass scanner.** One cursor over the input string with table-driven character classification
|
|
287
|
-
- **Comments skip in bulk.** Reference comments are a sizable share of a canonical document's bytes
|
|
289
|
+
- **Single-pass scanner.** One cursor over the input string with table-driven character classification. There is no tokenizer stage and no intermediate token objects.
|
|
290
|
+
- **Comments skip in bulk.** Reference comments are a sizable share of a canonical document's bytes, so comment bodies are jumped with `indexOf` instead of being scanned per character.
|
|
288
291
|
- **Linear comment derivation.** Building the `/* … */` annotations uses reverse indexes over the object graph (build file → phase, configuration list → owner), so serialization stays linear on projects with thousands of objects.
|
|
289
292
|
- **Memoized rendering.** Quoting decisions for the repeated key vocabulary and rendered uuid references are cached per document, halving the quote scans on reference-heavy sections.
|
|
290
293
|
|