Skip to content

Releases

0.13.0

Choose a tag to compare

@mozharovsky mozharovsky released this 03 Oct 22:56
6fdd1b2

Added

  • Support for Xcode 27's JSON project format. parseXcproj reads any JSON5 project.xcproj into plain JSON values, accepting comments, unquoted keys, and single-quoted strings the way Xcode reads hand-edited files. buildXcproj writes them back in the exact layout Xcode uses, deriving the key order of each record kind, the sorting of settings and sets, and the choice between single-line and multi-line containers from the project schema. A file Xcode wrote rebuilds byte for byte, and any other input reaches the same bytes in one build. Malformed input fails with the new XcprojParseError carrying line and column, and values Xcode could not read back, such as U+0000 and unpaired surrogates, fail with the new XcprojBuildError and the path of the offending value.
  • Xcproj is the object model over the format, with the document-first design of XcodeProject. getBuildSetting and resolveBuildSetting read per configuration through the target's settings, the target's xcconfig, the project's settings, and the project's xcconfig, and list settings read as arrays. setBuildSetting and removeBuildSetting work for every configuration or for one, folding values into plain and [config=Name] keys the way Xcode saves them. files(), references(), and findReference walk the file tree by name path, and findTarget and findMainAppTarget find targets.
  • renameTarget and removeTarget on Xcproj follow every place the format names a target, which covers dependencies, test hosts, build-phase references in file memberships, folder memberships and exception sets, the product and the target's path to it, and the TEST_TARGET_NAME, TEST_HOST, and BUNDLE_LOADER settings. Name paths switch between their string and array encodings as names gain or lose a /, and a rename the format cannot represent is refused without changing anything. Both reproduce Xcode's own saves of the same edits byte for byte.
  • The views XcprojTarget and XcprojReference, the value types XcprojValue, XcprojObject, XcprojArray, and XcprojBuildSettingValue, and the options XcprojBuildSettingOptions and XcprojResolveBuildSettingOptions ship as public API.

Changed

  • The corpus sweep (pnpm corpus) audits project.xcproj files next to the other formats, and caps each format at the limit on its own, so a root rich in one format no longer hides the others.

Full Changelog: 0.12.0...0.13.0

0.12.0

Choose a tag to compare

@mozharovsky mozharovsky released this 15 Jul 20:20
517a4ca

Added

  • Xcworkspace — a model over contents.xcworkspacedata, the file listing the projects a workspace shows. projectFilePaths() resolves every referenced .xcodeproj relative to the workspace's directory, with group locations composing through their enclosing groups, container locations anchoring at the workspace's directory, and absolute paths passing through, so tooling resolves the real project set instead of globbing directory trees. fileRefs() returns typed views with location access, addFileRef and removeFileRef edit the tree, parseWorkspaceLocation splits a location into kind and path, and Xcworkspace.create produces the document Xcode writes.
  • parseXcworkspace and buildXcworkspace — the same contract as schemes. An Xcode-written file rebuilds byte-identically, any other input reaches the canonical layout in one build, and malformed input fails with the new XcworkspaceParseError and XcworkspaceBuildError carrying line, column, and node path.
  • The shared XML node types ship as XmlDocument, XmlElement, XmlComment, and XmlNode, with isXmlElement and xmlElements. The Xcscheme* type names remain as aliases, so existing annotations keep compiling.
  • TextPosition — the XML-neutral position type every parse error now carries, with PbxprojErrorPosition kept as a documented alias.
  • Cross-library benchmarks for the XML dialect files in pnpm bench:compare and the README, measured against the one other npm package that parses and writes them.

Changed

  • Scheme and workspace files share one XML core, now strict about what XML cannot carry, symmetrically on parse and build. Only a real <?xml declaration is consumed, numeric character references validate their full digit run and the decoded code point against XML's character set, comment text rejects -- and a trailing -, and attribute values reject raw control characters, unpaired surrogates, and the U+FFFE and U+FFFF noncharacters. Every input the parser accepts is one the serializer reproduces, and the corpus sweep over hundreds of real files confirms the strictness rejects nothing Xcode writes.
  • The serializer validates and escapes attribute values in a single scan, returns the near-universal value with nothing to escape untouched, memoizes name validity and indentation, and resolves error paths only when a build actually fails. Scheme and workspace operations now measure fastest across the board, with parsing 3.9 to 4.3 times faster and building 1.1 to 1.4 times faster than the alternative.
  • Parse-error positions count all three line-ending conventions, so CR-only and CRLF sources report correct lines and columns.

Full Changelog: 0.11.0...0.12.0

0.11.0

Choose a tag to compare

@mozharovsky mozharovsky released this 12 Jul 00:00
d2affb1

Added

  • expandBuildSettingReferences(value, lookup) — evaluates Xcode's $(NAME) and ${NAME} build-setting reference syntax in a value through a caller-supplied lookup. Substituted text expands recursively, reference names may themselves contain references ($(SETTING_$(VARIANT)), resolved innermost first), and cycles stay finite. A lookup answer of undefined leaves the reference verbatim, so partial resolution loses no information, while an empty string expands to nothing the way Xcode treats empty settings. The : operators are honored for lower, upper, rfc1034identifier, c99extidentifier, and default=, and a reference carrying any other operator stays verbatim rather than expanding to a wrongly transformed value. The supported operator names ship as the BuildSettingOperator union.
  • Target.resolveBuildSetting(key, options?) — reads a setting through the same layers as getBuildSetting (target settings, target xcconfig, project settings, project xcconfig) and expands the references in it. Referenced settings resolve through the full chain, $(inherited) and a setting referencing itself continue from the next layer down the way Xcode composes values, and $(TARGET_NAME) falls back to the target's name, so the template's PRODUCT_NAME = "$(TARGET_NAME)" and imported projects' com.example.$(PRODUCT_NAME:rfc1034identifier) bundle identifiers resolve with zero setup. An optional lookup answers references the document cannot, such as $(BUILT_PRODUCTS_DIR).

Full Changelog: 0.10.0...0.11.0

0.10.0

Choose a tag to compare

@mozharovsky mozharovsky released this 10 Jul 20:11
2536120

Added

  • XcodeProject.renameTarget(target, newName) — renames a target and every place the pbxproj knows it by name: the target's name and productName, the literal PRODUCT_NAME setting, the product file reference (Old.app becomes New.app, whatever the extension), remoteInfo on container item proxies pointing at the target by id, 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 per path segment, so renaming DemoApp never touches DemoAppTests, and a PRODUCT_NAME of $(TARGET_NAME) follows by itself. Renaming to the same name is a true no-op, and a target of another project throws XcodeModelError. File-system renames (source folders, entitlements files) stay with the caller.
  • Xcscheme.renameTarget(oldName, newName) — the scheme-file counterpart. Blueprint names match whole and buildable names match by stem, so Old.app renames while OldTests.xctest, a different target's product, stays untouched. Returns whether anything changed, so callers can skip rewriting untouched files.
  • Xcscheme.renameContainer(oldProjectName, newProjectName) — follows a rename of the .xcodeproj directory itself, rewriting container:Old.xcodeproj references across every buildable reference. Returns whether anything changed.
  • The corpus sweep gained a rename probe — every parsed project with an app target renames to a probe name and back, with byte-stable fixed-point checks after each step.

Changed

  • Every comment, JSDoc, and doc in the repository rewritten as plain flowing prose. No code changes, and parse and build benchmarks are unchanged.

Full Changelog: 0.9.0...0.10.0

0.9.0

Choose a tag to compare

@mozharovsky mozharovsky released this 09 Jul 00:36
ee5d946

Added

  • .xcconfig support end to end. parseXcconfig and buildXcconfig treat the hand-authored format losslessly — an untouched file rebuilds byte for byte, comments, blank lines, alignment, and line endings included — and malformed lines fail with the new XcconfigParseError carrying line and column instead of being dropped.
  • Xcconfig model — parse/create/build, get/set/remove editing single lines in place, keys, assignments, includes, and settings() flattening that applies #include directives at their position through a caller-supplied resolver. A file included again later re-applies like pasted text, while true cycles terminate through recursion-stack guards that work with both cached and fresh-instance resolvers.
  • Build-context flattening — settings({ context: { sdk, arch, config } }) applies conditional assignments when every condition matches, with trailing * wildcards. $(inherited) and ${inherited} splice in the value accumulated earlier in the chain and stay literal when there is none, so lower layers can still resolve them.
  • Build-setting layering — BuildConfiguration.baseConfigurationReference(), XcodeProject.registerXcconfig(reference, config), and Target.getBuildSetting resolving in Xcode's order: target settings, target xcconfig, project settings, project xcconfig.
  • Four neutralized .xcconfig fixtures captured from real file shapes run in CI, and the corpus sweep now covers every .xcconfig on the machine with a byte-exact losslessness bar.

Full Changelog: 0.8.0...0.9.0

0.8.0

Choose a tag to compare

@mozharovsky mozharovsky released this 08 Jul 17:01
800b255

Added

  • parseApplePlatform(value) — normalizes a platform name to the ApplePlatform union. Casing and punctuation are ignored, so spellings like tvOS, TV_OS, and visionos all resolve, and the SDK identifiers Xcode writes into SDKROOT (iphoneos, appletvos, xros, macosx, and their simulator variants) resolve to the platform they build for. Unknown input returns undefined, leaving the default to the caller.

Full Changelog: 0.7.0...0.8.0

0.7.0

Choose a tag to compare

@mozharovsky mozharovsky released this 07 Jul 20:08
58eb3f7

Added

  • NativeTarget.embeddedTargets() — the read-side counterpart of embed(). Resolves which targets' products this target's copy-files phases carry and returns them as target views in phase and file order, deduplicated and excluding self. Targets embedding further targets of their own can be walked recursively through the accessor.
  • CopyFilesBuildPhase.dstSubfolderSpec — a typed getter for the destination folder code alongside dstPath, comparable against the exported CopyFilesDestination constants.

Full Changelog: 0.6.0...0.7.0

0.6.0

Choose a tag to compare

@mozharovsky mozharovsky released this 07 Jul 14:39
2db07de

Added

  • Isa literals type the returns — project.add(Isa.fileReference, ...) is a FileReference, target.ensureBuildPhase(Isa.copyFilesBuildPhase, ...) is a CopyFilesBuildPhase, and target.findBuildPhase(Isa.shellScriptBuildPhase) is a ShellScriptBuildPhase | undefined, with no cast at the call site. A dynamic string falls back to the generic view types as before. The mapping is derived from the same VIEWS registry the view factory dispatches on, so the promised type and the created class cannot drift apart.
  • Exported IsaValue, BuildPhaseIsa, ViewByIsa, ViewOf, and BuildPhaseOf types for building typed helpers on top of the model.

Changed

  • ensureBuildPhase narrows its parameter from string to BuildPhaseIsa, the union of the eight build phase isas, so creating a phase with a non-phase isa is now a compile error. Code passing a plain string variable needs a literal or a narrowing check.

Full Changelog: 0.5.0...0.6.0

0.5.0

Choose a tag to compare

@mozharovsky mozharovsky released this 07 Jul 13:01
2361c56

Added

  • A view class for every isa Xcode writes. ExceptionSet splits into BuildFileExceptionSet and BuildPhaseMembershipExceptionSet (with a typed buildPhase() accessor), SwiftPackageReference splits into RemoteSwiftPackageReference (repositoryURL) and LocalSwiftPackageReference (relativePath), and every build phase kind gets its own class: SourcesBuildPhase, FrameworksBuildPhase, ResourcesBuildPhase, HeadersBuildPhase, CopyFilesBuildPhase (dstPath), and ShellScriptBuildPhase (shellScript, shellPath). Code that had to compare object.isa after an is() check now narrows with the specific class alone.
  • Legacy kinds — PBXRezBuildPhase, PBXAppleScriptBuildPhase, and PBXBuildStyle get classes, and buildStyles joins the doctor's reference vocabulary, so decades-old documents validate and traverse typed.
  • Tighter return types across the model: ensureSourcesPhase, ensureFrameworksPhase, ensureResourcesPhase, ensureShellScriptPhase, and embed return their specific phase classes, and the Swift package helpers return RemoteSwiftPackageReference or LocalSwiftPackageReference.

Changed

  • Each view class now declares the single isa it models through static readonly isa (previously a plural isas array), and the view factory registry derives from the class declarations. Behavior-only bases such as Target declare null and never route.

Full Changelog: 0.4.0...0.5.0

0.4.0

Choose a tag to compare

@mozharovsky mozharovsky released this 07 Jul 11:51
6e79d74

Added

  • Scheme object model with Xcscheme and BuildableReference — Xcscheme wraps a parsed scheme document the way XcodeProject wraps a pbxproj, with parse, create, build, root, and elements(name), and buildableReferences() returns typed views whose blueprintIdentifier, blueprintName, buildableName, and referencedContainer properties read and write the underlying element's attributes directly. The tree stays the single source of truth, so typed edits and direct tree edits compose freely and byte-fidelity guarantees are unchanged.
  • is() narrowing on every view class — a static type guard for discriminating mixed objects, so walking a document reads as if (NativeTarget.is(object)) and narrows the way instanceof does, subclasses included.
  • Typed views for three more kinds — BuildConfiguration exposes its name and a live buildSettings dictionary typed as BuildSettings (writes land in the document), FileReference exposes path and name, and ContainerItemProxy exposes remoteInfo. All three come out of the factory and the property queries automatically. BuildSettings also gains the TEST_HOST and TEST_TARGET_NAME keys rename flows touch.
  • A documentation pass over the scheme module: the parser class, its fields, and every previously undocumented method now carry doc comments.

Full Changelog: 0.3.0...0.4.0