rork-xcode 0.8.0 → 0.10.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
@@ -206,6 +206,17 @@ if (widget) project.removeTarget(widget);
206
206
  project.referrersOf(app.id); // every object referencing an id, for custom teardowns
207
207
  ```
208
208
 
209
+ ### Renaming
210
+
211
+ `renameTarget` renames a target and every place the document knows it by name. That covers the target's `name` and `productName`, its product file reference, the `remoteInfo` of container proxies pointing at it, `TEST_TARGET_NAME` values naming it, and the path segments of `TEST_HOST` and `BUNDLE_LOADER` that name the target or its product. Names match whole, so renaming `DemoApp` leaves `DemoAppTests` alone, and a `PRODUCT_NAME` of `$(TARGET_NAME)` follows by itself. On-disk renames (source folders, entitlements files) and the group paths pointing at those folders stay with the caller.
212
+
213
+ ```ts
214
+ const app = project.findMainAppTarget("ios");
215
+ if (app) project.renameTarget(app, "RenamedApp");
216
+ ```
217
+
218
+ Scheme files live next to the project rather than inside it, so the scheme model carries the counterpart (see [Schemes](#schemes)).
219
+
209
220
  ### Escape hatch
210
221
 
211
222
  Every view exposes its raw dictionary, so anything the typed surface does not cover stays one property away, and `project.objects()` iterates every object with its typed view:
@@ -235,25 +246,21 @@ for (const [id, object] of project.objects()) {
235
246
 
236
247
  `.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.
237
248
 
238
- `Xcscheme` is the model. Buildable references are the elements editing flows touch, and they come back as typed views:
249
+ `Xcscheme` is the model. Renames are one call per move. `renameTarget` is the scheme-file side of the project model's `renameTarget` and touches only the named target's references, and `renameContainer` follows a rename of the `.xcodeproj` directory itself. Both return whether anything changed, so callers can skip rewriting untouched files:
239
250
 
240
251
  ```ts
241
252
  import { Xcscheme } from "rork-xcode";
242
253
 
243
254
  const scheme = Xcscheme.parse(xcschemeText);
244
255
 
245
- // Rename the app while keeping each product's own shape, so a testable
246
- // like DemoAppTests.xctest stays a test bundle.
247
- for (const reference of scheme.buildableReferences()) {
248
- const { blueprintName, buildableName } = reference;
249
- if (blueprintName) reference.blueprintName = blueprintName.replace("DemoApp", "RenamedApp");
250
- if (buildableName) reference.buildableName = buildableName.replace("DemoApp", "RenamedApp");
251
- reference.referencedContainer = "container:RenamedApp.xcodeproj";
252
- }
256
+ scheme.renameTarget("DemoApp", "RenamedApp"); // DemoAppTests stays untouched
257
+ scheme.renameContainer("DemoApp", "RenamedApp"); // container:RenamedApp.xcodeproj
253
258
 
254
259
  const text = scheme.build();
255
260
  ```
256
261
 
262
+ For anything else, buildable references come back as typed views through `scheme.buildableReferences()`, with property access to the blueprint name, buildable name, container, and identifier.
263
+
257
264
  `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:
258
265
 
259
266
  ```ts
@@ -266,6 +273,47 @@ const text = scheme.build();
266
273
 
267
274
  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.
268
275
 
276
+ ## Xcconfig files
277
+
278
+ Build settings do not only live in the pbxproj. Projects push them into `.xcconfig` files referenced through `baseConfigurationReference`, and the xcconfig module reads and writes that format with the fidelity the rest of the library promises. The format is hand-authored with no canonical layout, so the contract here is losslessness. Parsing and building an untouched file reproduces it byte for byte, including comments, blank lines, column alignment, and line endings. Malformed lines fail loudly with a typed error carrying line and column rather than being dropped, so a file the parser accepts is a file it fully understood.
279
+
280
+ `Xcconfig` is the model. Reads follow the file top to bottom the way Xcode does, and writes edit single lines while leaving every other byte alone:
281
+
282
+ ```ts
283
+ import { Xcconfig } from "rork-xcode";
284
+
285
+ const config = Xcconfig.parse(xcconfigText);
286
+
287
+ config.get("PRODUCT_BUNDLE_IDENTIFIER"); // last unconditional assignment wins
288
+ config.set("MARKETING_VERSION", "1.2.0"); // rewrites in place, appends when new
289
+ const settings = config.settings(); // flattened, the way Xcode reads the file
290
+
291
+ const text = config.build();
292
+ ```
293
+
294
+ `#include` directives are exposed as data because the library never touches the filesystem. Flattening resolves them through a caller-supplied lookup and applies each file at its directive's position, cycle-safe, so lines after an include override it exactly like textual inclusion. Position matters: an include hoisted or reordered changes what the file means, so the model never moves one.
295
+
296
+ ```ts
297
+ const settings = config.settings({
298
+ resolveInclude: (path, optional) => loadedConfigs.get(path),
299
+ });
300
+ ```
301
+
302
+ Conditional assignments like `OTHER_LDFLAGS[sdk=iphoneos*][arch=arm64]` are parsed structurally with their conditions preserved verbatim on round-trip, unknown condition names included. Passing a build context applies them during flattening, with every condition required to match and trailing `*` wildcards honored; without a context they stay out, which mirrors reading the file with no build in mind. `$(inherited)` references splice in the value accumulated earlier in the chain and stay literal when there is none, so lower layers can still resolve them:
303
+
304
+ ```ts
305
+ const settings = config.settings({
306
+ context: { sdk: "iphoneos", arch: "arm64", config: "Release" },
307
+ });
308
+ ```
309
+
310
+ Registering a file on the project makes `getBuildSetting` resolve through it in Xcode's order of target settings, then the target's xcconfig, then project settings, then the project's xcconfig:
311
+
312
+ ```ts
313
+ project.registerXcconfig(reference, Xcconfig.parse(text));
314
+ app.getBuildSetting("SDKROOT"); // now sees values the xcconfig defines
315
+ ```
316
+
269
317
  ## Performance
270
318
 
271
319
  `rork-xcode` is measured against the pbxproj parsers on npm, [`@bacons/xcode`](https://www.npmjs.com/package/@bacons/xcode) (its `/json` parse/build entry point) and [`xcode`](https://www.npmjs.com/package/xcode) (the long-standing package used by native build tooling), on three documents: two real Xcode-written projects from the test suite and a deterministically generated five-target app with 800 source files. It is the fastest at both operations on every document, with zero dependencies.
@@ -295,9 +343,9 @@ Measured on an Apple M5 Max, Node.js 24, single thread, with `@bacons/xcode` 1.0
295
343
  ## Verification
296
344
 
297
345
  - The committed fixture corpus spans project generations from Xcode 3 to Xcode 16, captured from real projects with identifiers neutralized: synchronized folders with both exception-set kinds, classic groups, variant groups, aggregate and legacy targets, reference proxies, build rules, Swift packages, and a ~100 KiB multiplatform framework project.
298
- - Documents already in current Xcode's layout must round-trip byte for byte; documents from other tool generations must normalize to a byte-stable fixed point with unchanged values.
346
+ - Documents already in current Xcode's layout must round-trip byte for byte, and documents from other tool generations must normalize to a byte-stable fixed point with unchanged values.
299
347
  - On macOS, the suite cross-validates every fixture and its rebuilt form with `plutil`, Apple's own property list parser and the empirical ground truth for what Apple tooling accepts.
300
- - A corpus sweep (`pnpm corpus`) walks every Xcode project and scheme on the machine, verifies each one parses and reaches a byte-stable fixed point, exercises the object model against every project, and cross-validates a sample against plutil's own reading.
348
+ - A corpus sweep (`pnpm corpus`) walks every Xcode project, scheme, and xcconfig on the machine, verifies each one parses and reaches a byte-stable fixed point (byte-exact losslessness for xcconfig, which has no canonical layout), exercises the object model against every project, and cross-validates a sample against plutil's own reading.
301
349
  - CI runs the full gate on Linux and macOS, and executes the built artifact on the oldest supported Node to enforce the `engines` floor.
302
350
 
303
351
  ## Releasing