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 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: 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.
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; unrepresentable values (`null`, booleans, non-finite numbers) fail with the exact path of the offending value. Nothing is silently dropped.
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
- - **Typed vocabulary, generic fallback.** The typed views cover targets of every kind, groups and variant groups, Xcode 16 synchronized folders and their exception sets, build phases and build rules, Core Data version groups, and cross-project reference proxies. Every other kind is a generic `XcodeObject` with the same read and write access, so nothing in a document is out of reach.
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: 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
+ - **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 `parseXcscheme` and `buildXcscheme` cover 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.
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 { buildXcscheme, parseXcscheme, xcschemeElements } from "rork-xcode";
240
+ import { Xcscheme } from "rork-xcode";
238
241
 
239
- const scheme = parseXcscheme(xcschemeText);
242
+ const scheme = Xcscheme.parse(xcschemeText);
240
243
 
241
- // The tree is plain data: elements with ordered attributes and children.
242
- for (const reference of xcschemeElements(scheme.root, "BuildableReference")) {
243
- reference.attributes["BlueprintName"] = "RenamedApp";
244
- reference.attributes["BuildableName"] = "RenamedApp.app";
245
- reference.attributes["ReferencedContainer"] = "container:RenamedApp.xcodeproj";
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 = buildXcscheme(scheme);
253
+ const text = scheme.build();
249
254
  ```
250
255
 
251
- `createXcscheme` 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:
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
- import { createXcscheme } from "rork-xcode";
255
-
256
- const scheme = createXcscheme({
259
+ const scheme = Xcscheme.create({
257
260
  appName: "DemoApp",
258
261
  blueprintIdentifier: app.id,
259
262
  });
260
- const text = buildXcscheme(scheme);
263
+ const text = scheme.build();
261
264
  ```
262
265
 
263
- Attribute order is preserved and meaningful: the writer emits attributes in insertion order, 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.
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; no tokenizer stage, no intermediate token objects.
287
- - **Comments skip in bulk.** Reference comments are a sizable share of a canonical document's bytes; comment bodies are jumped with `indexOf` instead of being scanned per character.
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