@bacons/xcode

中文文档
1.0.0-alpha.33发布于 2026/03/03MITTypeScriptCommonJS近 30 天下载 339.8万

pbxproj parser

原始使用文档
文档目录31 节
  1. Performance
  2. Key Performance Features
  3. Format Comparison
  4. API
  5. XCScheme Support
  6. Low-level API
  7. High-level API
  8. Scheme Management
  9. XCWorkspace Support
  10. Low-level API
  11. High-level API
  12. Location Types
  13. IDEWorkspaceChecks
  14. XCConfig Support
  15. Low-level API
  16. Conditional Settings
  17. Include Directives
  18. Variable Expansion
  19. XCSharedData Support
  20. High-level API
  21. Breakpoints API
  22. Workspace Settings API
  23. Swift Package Manager Support
  24. Adding Remote Packages
  25. Adding Local Packages
  26. What Gets Wired Up
  27. Managing Package Dependencies
  28. Version Requirements
  29. Solution
  30. File Path Resolution
  31. Versioning

@bacons/xcode

The fastest and most accurate parser for Xcode project files (.pbxproj). 10-28x faster than alternatives (xcode, XcodeProj, xcodeproj) with better error messages and full spec compliance.

bun add @bacons/xcode

Performance

Run benchmarks with bun run bench or bun run bench:compare for cross-language comparison.

Xnapper-2026-02-28-09 06 57
ParserLanguageTime (29KB)Time (263KB)Relative
@bacons/xcodeTypeScript0.15ms0.81ms1x
xcode (legacy)JavaScript1.54mscrashes10x slower
XcodeProj (Tuist)Swift2.00ms11.2ms13x slower
xcodeproj (CocoaPods)Ruby3.63ms22.5ms24x slower

Key Performance Features

  • 10-28x faster than alternatives (xcode, XcodeProj, xcodeproj)
  • Single-pass parsing with no intermediate representation
  • Pre-computed lookup tables for character classification
  • Handles files that crash the legacy parser

Here is a diagram of the grammar used for parsing:

Screen Shot 2022-04-25 at 12 39 27 PM

Why

The most popular solution for parsing pbxproj files is a very old package by Cordova called xcode.

But xcode has some major issues:

  • Inaccurate parsing: strings can be quoted incorrectly very often, lists often don't work.
  • Outdated: values for App Clips, iMessage Sticker packs, etc are missing.
  • Untyped: TypeScript is a crutch I proudly support.
  • Slow: PEG.js is not very fast (benchmark).
  • Feature Incomplete: Missing the Data type (<xx xx xx>).

Format Comparison

Consider the following format comparison.

Input .pbxproj

307D28A1123043350040C0FA /* app-icon.png */ = {
  isa = PBXFileReference;
  lastKnownFileType = image.png;
  path = "app-icon.png";
  sourceTree = "<group>";
};

xcode output (old)

{
  "307D28A1123043350040C0FA_comment": "app-icon.png",
  "308D052E1370CCF300D202BF": {
    "isa": "PBXFileReference",
    "lastKnownFileType": "image.png",
    "path": "\"app-icon.png\"",
    "sourceTree": "\"<group>\""
  }
}

That same object would look like this in @bacons/xcode:

@bacons/xcode output (NEW)

{
  "308D052E1370CCF300D202BF": {
    "isa": "PBXFileReference",
    "lastKnownFileType": "image.png",
    "path": "app-icon.png",
    "sourceTree": "<group>"
  }
}

Notice how you don't need to strip or reapply quotes, you also don't need to filter out comments because the default visitor ignores comments in favor of regenerating them dynamically like Xcode does.

API

There's a mutable-graph layer which makes it much easier to work with pbxproj.

import {
  PBXAggregateTarget,
  PBXFrameworksBuildPhase,
  PBXLegacyTarget,
  PBXNativeTarget,
  XcodeProject,
} from "@bacons/xcode";

const project = XcodeProject.open("/path/to/project.pbxproj");

// Get all targets:
project.rootObject.props.targets;

Create a Swift file:

import { PBXBuildFile, PBXFileReference } from "@bacons/xcode";
import path from "path";

// Get `project` from XcodeProject.

const file = PBXBuildFile.create(project, {
  fileRef: PBXFileReference.create(project, {
    path: "MyFile.swift",
    sourceTree: "<group>",
  }),
});

// The file and fileRef will now be injected in the pbxproj `objects` dict.

XCScheme Support

Parse and manipulate Xcode scheme files (.xcscheme). Schemes define how targets are built, run, tested, profiled, analyzed, and archived.

Low-level API

import * as scheme from "@bacons/xcode/scheme";
import fs from "fs";

// Parse an xcscheme file
const xml = fs.readFileSync(
  "/path/to/Project.xcodeproj/xcshareddata/xcschemes/App.xcscheme",
  "utf-8",
);
const xcscheme = scheme.parse(xml);

// Modify the scheme
xcscheme.buildAction.parallelizeBuildables = true;
xcscheme.launchAction.environmentVariables = [
  { key: "DEBUG", value: "1", isEnabled: true },
];

// Serialize back to XML
const outputXml = scheme.build(xcscheme);
fs.writeFileSync("/path/to/App.xcscheme", outputXml);

High-level API

import { XcodeProject, XCScheme } from "@bacons/xcode";

// Load project and get schemes
const project = XcodeProject.open("/path/to/project.pbxproj");
const schemes = project.getSchemes();
const appScheme = project.getScheme("App");

// Create a new scheme
const newScheme = XCScheme.create("MyNewScheme");
newScheme.addBuildTarget({
  buildableIdentifier: "primary",
  blueprintIdentifier: "ABC123", // Target UUID
  buildableName: "App.app",
  blueprintName: "App",
  referencedContainer: "container:Project.xcodeproj",
});
newScheme.setLaunchEnvironmentVariable("API_URL", "https://api.example.com");
newScheme.addLaunchArgument("-verbose");

// Save to disk
project.saveScheme(newScheme);

// Create scheme for an existing target
const mainTarget = project.rootObject.props.targets[0];
const targetScheme = project.createSchemeForTarget(mainTarget);
project.saveScheme(targetScheme);

Scheme Management

Parse and build xcschememanagement.plist files that control scheme visibility and ordering:

import * as scheme from "@bacons/xcode/scheme";
import fs from "fs";

const plist = fs.readFileSync("/path/to/xcschememanagement.plist", "utf-8");
const management = scheme.parseManagement(plist);

// Check scheme visibility
console.log(management.SchemeUserState?.["App.xcscheme"]?.isShown);

// Rebuild plist
const output = scheme.buildManagement(management);

XCWorkspace Support

Parse and manipulate Xcode workspace files (.xcworkspace). Workspaces group multiple Xcode projects together, commonly used with CocoaPods, monorepos, and multi-project setups.

Low-level API

import * as workspace from "@bacons/xcode/workspace";
import fs from "fs";

// Parse a workspace file
const xml = fs.readFileSync(
  "/path/to/MyApp.xcworkspace/contents.xcworkspacedata",
  "utf-8",
);
const ws = workspace.parse(xml);

// Access project references
console.log(ws.fileRefs); // [{ location: "group:App.xcodeproj" }, ...]

// Modify the workspace
ws.fileRefs?.push({ location: "group:NewProject.xcodeproj" });

// Serialize back to XML
const outputXml = workspace.build(ws);
fs.writeFileSync("/path/to/contents.xcworkspacedata", outputXml);

High-level API

import { XCWorkspace } from "@bacons/xcode";

// Open an existing workspace
const workspace = XCWorkspace.open("/path/to/MyApp.xcworkspace");

// Get all project paths
const projects = workspace.getProjectPaths();
console.log(projects); // ["group:App.xcodeproj", "group:Pods/Pods.xcodeproj"]

// Check if workspace contains a project
if (!workspace.hasProject("NewProject.xcodeproj")) {
  workspace.addProject("NewProject.xcodeproj");
}

// Remove a project
workspace.removeProject("OldProject.xcodeproj");

// Work with groups
const group = workspace.addGroup("Libraries", "group:Libraries");
group.fileRefs?.push({ location: "group:Libraries/MyLib.xcodeproj" });

// Save changes
workspace.save();

// Create a new workspace
const newWorkspace = XCWorkspace.create("MyWorkspace");
newWorkspace.addProject("App.xcodeproj");
newWorkspace.addProject("Pods/Pods.xcodeproj");
newWorkspace.save("/path/to/MyWorkspace.xcworkspace");

Location Types

Workspace file references use location specifiers:

  • group:path - Relative path from workspace root (most common)
  • self: - Self-reference (embedded workspace inside .xcodeproj)
  • container:path - Absolute container reference (rare)
  • absolute:path - Absolute file path

IDEWorkspaceChecks

Manage IDEWorkspaceChecks.plist files that store workspace check states. Introduced in Xcode 9.3, these files prevent Xcode from recomputing checks each time a workspace is opened.

The primary use is suppressing the macOS 32-bit deprecation warning:

import { XCWorkspace, IDEWorkspaceChecks } from "@bacons/xcode";

// Suppress the 32-bit deprecation warning
const workspace = XCWorkspace.open("/path/to/MyApp.xcworkspace");
workspace.setMac32BitWarningComputed();

// Or work with IDEWorkspaceChecks directly
const checks = IDEWorkspaceChecks.openOrCreate("/path/to/MyApp.xcworkspace");
checks.mac32BitWarningComputed = true;
checks.save();

// Low-level API
import * as workspace from "@bacons/xcode/workspace";

const plist = workspace.parseChecks(plistString);
console.log(plist.IDEDidComputeMac32BitWarning); // true

const output = workspace.buildChecks({ IDEDidComputeMac32BitWarning: true });

XCConfig Support

Parse and manipulate Xcode configuration files (.xcconfig). These files define build settings that can be shared across targets and configurations.

Low-level API

import * as xcconfig from "@bacons/xcode/xcconfig";
import fs from "fs";

// Parse an xcconfig string
const config = xcconfig.parse(`
  #include "Base.xcconfig"
  PRODUCT_NAME = MyApp
  OTHER_LDFLAGS[sdk=iphoneos*] = -framework UIKit
`);

// Parse from file (resolves #include directives)
const config = xcconfig.parseFile("/path/to/Project.xcconfig");

// Flatten build settings (merges includes, applies conditions)
const allSettings = xcconfig.flattenBuildSettings(config);

// Filter by platform conditions
const iosSettings = xcconfig.flattenBuildSettings(config, {
  sdk: "iphoneos",
  arch: "arm64",
  config: "Release",
});

// Serialize back to xcconfig format
const output = xcconfig.build(config);
fs.writeFileSync("/path/to/Project.xcconfig", output);

Conditional Settings

XCConfig supports conditional settings based on SDK, architecture, and configuration:

// SDK-specific settings
OTHER_LDFLAGS[sdk=iphoneos*] = -framework UIKit
OTHER_LDFLAGS[sdk=macosx*] = -framework AppKit

// Architecture-specific settings
ARCHS[arch=arm64] = arm64
ARCHS[arch=x86_64] = x86_64

// Configuration-specific settings
GCC_OPTIMIZATION_LEVEL[config=Debug] = 0
GCC_OPTIMIZATION_LEVEL[config=Release] = s

// Combined conditions
LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64] = /usr/lib/arm64

Include Directives

// Required include - throws if file not found
#include "Base.xcconfig"

// Optional include - silently ignored if file not found
#include? "Optional.xcconfig"

Variable Expansion

XCConfig supports variable references and the $(inherited) keyword:

// Reference other settings
PRODUCT_BUNDLE_IDENTIFIER = $(BUNDLE_ID_PREFIX).$(PRODUCT_NAME:lower)

// Inherit from included files
OTHER_LDFLAGS = $(inherited) -framework UIKit

XCSharedData Support

Access and manipulate shared data directories (xcshareddata) which contain schemes, breakpoints, and workspace settings that are intended for version control.

High-level API

import { XcodeProject, XCSharedData } from "@bacons/xcode";

// Get shared data from a project
const project = XcodeProject.open("/path/to/project.pbxproj");
const sharedData = project.getSharedData();

// Access schemes
const schemes = sharedData.getSchemes();
const appScheme = sharedData.getScheme("App");

// Access breakpoints
if (sharedData.breakpoints) {
  console.log(sharedData.breakpoints.breakpoints?.length);
}

// Access workspace settings
if (sharedData.workspaceSettings) {
  console.log(sharedData.workspaceSettings.PreviewsEnabled);
}

// Modify and save
sharedData.workspaceSettings = {
  PreviewsEnabled: true,
  IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded: false,
};
sharedData.save();

Breakpoints API

Parse and build Xcode breakpoint files (Breakpoints_v2.xcbkptlist):

import * as breakpoints from "@bacons/xcode/breakpoints";
import fs from "fs";

// Parse breakpoint file
const xml = fs.readFileSync(
  "/path/to/xcshareddata/xcdebugger/Breakpoints_v2.xcbkptlist",
  "utf-8",
);
const list = breakpoints.parse(xml);

// Access breakpoints
for (const bp of list.breakpoints ?? []) {
  console.log(bp.breakpointExtensionID); // "Xcode.Breakpoint.FileBreakpoint"
  console.log(bp.breakpointContent?.filePath);
  console.log(bp.breakpointContent?.startingLineNumber);
}

// Add a new breakpoint
list.breakpoints?.push({
  breakpointExtensionID: "Xcode.Breakpoint.FileBreakpoint",
  breakpointContent: {
    uuid: "new-uuid",
    shouldBeEnabled: true,
    filePath: "MyApp/ViewController.swift",
    startingLineNumber: "42",
    endingLineNumber: "42",
    actions: [
      {
        actionExtensionID: "Xcode.BreakpointAction.DebuggerCommand",
        actionContent: { consoleCommand: "po self" },
      },
    ],
  },
});

// Serialize back to XML
const outputXml = breakpoints.build(list);

Workspace Settings API

Parse and build workspace settings files (WorkspaceSettings.xcsettings):

import * as settings from "@bacons/xcode/settings";
import fs from "fs";

// Parse settings file
const plist = fs.readFileSync(
  "/path/to/xcshareddata/WorkspaceSettings.xcsettings",
  "utf-8",
);
const config = settings.parse(plist);

console.log(config.BuildSystemType); // "Original" or "New"
console.log(config.PreviewsEnabled); // true/false

// Modify and save
config.PreviewsEnabled = true;
config.IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded = false;

const outputPlist = settings.build(config);
fs.writeFileSync("/path/to/WorkspaceSettings.xcsettings", outputPlist);

Swift Package Manager Support

Add Swift Package Manager dependencies to your Xcode projects with full wiring handled automatically.

Adding Remote Packages

import { XcodeProject } from "@bacons/xcode";

const project = XcodeProject.open("/path/to/project.pbxproj");
const rootProject = project.rootObject;

// Add a remote Swift package to the project
const packageRef = rootProject.addRemoteSwiftPackage({
  repositoryURL: "https://github.com/apple/swift-collections",
  requirement: {
    kind: "upToNextMajorVersion",
    minimumVersion: "1.0.0",
  },
});

// Add the package product to a target
const target = rootProject.getMainAppTarget("ios");
const productDep = target.addSwiftPackageProduct({
  productName: "Collections",
  package: packageRef,
});

// Save the project
fs.writeFileSync("/path/to/project.pbxproj", build(project.toJSON()));

Adding Local Packages

// Add a local Swift package (e.g., from a monorepo)
const localPackage = rootProject.addLocalSwiftPackage({
  relativePath: "../Packages/MyFeature",
});

// Add the product to your target
target.addSwiftPackageProduct({
  productName: "MyFeature",
  package: localPackage,
});

What Gets Wired Up

When you call target.addSwiftPackageProduct(), the following is handled automatically:

  1. Creates XCSwiftPackageProductDependency and adds it to target's packageProductDependencies
  2. Creates PBXBuildFile with productRef pointing to the dependency
  3. Adds the build file to the target's Frameworks build phase

Managing Package Dependencies

// Get all package product dependencies for a target
const deps = target.getSwiftPackageProductDependencies();

// Find an existing package by URL or path
const existing = rootProject.getPackageReference(
  "https://github.com/apple/swift-collections"
);

// Remove a package product from a target (cleans up build file too)
target.removeSwiftPackageProduct(productDep);

Version Requirements

Remote packages support various version requirement types:

// Up to next major version (e.g., 1.0.0 to 2.0.0)
{ kind: "upToNextMajorVersion", minimumVersion: "1.0.0" }

// Up to next minor version (e.g., 1.2.0 to 1.3.0)
{ kind: "upToNextMinorVersion", minimumVersion: "1.2.0" }

// Exact version
{ kind: "exactVersion", version: "1.2.3" }

// Version range
{ kind: "versionRange", minimumVersion: "1.0.0", maximumVersion: "2.0.0" }

// Branch
{ kind: "branch", branch: "main" }

// Revision (commit hash)
{ kind: "revision", revision: "abc123def456" }

Solution

  • Uses a hand-optimized single-pass parser that is 11x faster than the legacy xcode package (which uses PEG.js).
  • This project supports the Data type <xx xx xx>.
  • Better error messages with line and column numbers.
  • This implementation is more stable since we follow the best guess pbxproj spec.
  • String parsing is the trickiest part. This package uses a port of the actual CFOldStylePlist parser which is an approach first used at scale by the CocoaPods team (originally credited to Samantha Marshall).

How

The parsing is very simple (simplicity is the key).

pbxproj is an "old-style plist" (or ASCII Plist), this means it should be possible to represent it as any other static configuration file type like JSON or XML.

We support the following types: Object, Array, Data, String. Notably, we avoid dealing with Integer, Double, Boolean since they appear to not exist in the format.

TODO

  • Reading.
  • Writing.
  • Escaping scripts and header search paths.
  • Generating UUIDs.
  • Reference-type API.
  • Build setting parsing.
  • xcscheme support.
  • Benchmarks (bun run bench).
  • xcworkspace support.
  • XCConfig Parsing: .xcconfig file parsing with #include support and build settings flattening.
  • XCSharedData: Shared project data directory (schemes, breakpoints, workspace settings).
  • XCSchemeManagement: Scheme ordering, visibility, and management plist.
  • WorkspaceSettings: (xcshareddata/WorkspaceSettings.xcsettings) Derived data location, build system version, auto-create schemes setting.
  • XCBreakpointList: (xcshareddata/xcdebugger/Breakpoints_v2.xcbkptlist) Shared debugger breakpoints (file, symbolic, exception breakpoints).
  • Create robust xcode projects from scratch.
  • Skills.
  • Import from other tools.
  • XCUserData: (xcuserdata/<user>.xcuserdatad/) Per-user schemes, breakpoints, UI state.
  • IDEWorkspaceChecks: (xcshareddata/IDEWorkspaceChecks.plist) Workspace check state storage (e.g., 32-bit deprecation warning).
  • Swift Package Manager: Add remote and local SPM dependencies with automatic wiring.

Docs

Docs are in the works. For now, you can refer to the types and the estimated pbxproj spec.

The API will change in the future, for now we have two methods:

import {
  /** Given a stringified `pbxproj`, return a JSON representation of the object. */
  parse,
  /** Given a JSON representation of a `pbxproj`, return a `.pbxproj` string that can be parsed by Xcode. */
  build,
} from "@bacons/xcode/json";

import fs from "fs";
import path from "path";

const pbxproj = parse(fs.readFileSync("/path/to/project.pbxproj"));

const pbxprojString = build(pbxproj);
  • PBXVariantGroup is a localized PBXGroup.

File Path Resolution

Files will have an attribute sourceTree which indicates how the file path should be resolved.

  • BUILT_PRODUCTS_DIR: Paths are relative to the built products directory.
  • DEVELOPER_DIR: Paths are relative to the developer directory.
  • SOURCE_ROOT: Paths are relative to the project.
  • SDKROOT: Paths are relative to the SDK directory.
  • <group>: Paths are relative to the group.
  • <absolute>: Source is an absolute path.

For example, a file object like:

{
  "isa": "PBXFileReference",
  "name": "AppDelegate.m",
  "path": "multitarget/AppDelegate.m",
  "sourceTree": "<group>"
}

Indicates that the path "multitarget/AppDelegate.m" is relative to sourceTree "". We need to check the containing PBXGroup's path (only defined when the group is linked to a directory in the file system). Groups can live inside of other groups so this process is recursive.

Versioning

Certain values loosely map to each other. For instance the top-level objectVersion (which indicates the versioning used for the objects in the top-level objects dictionary), maps to the rootObject -> PBXProject's compatibilityVersion string. Here is an up-to-date mapping (May 2022):

PBXProject.compatibilityVersionXcodeProject.objectVersion
'Xcode 16.0'70
'Xcode 15.0'60
'Xcode 14.0'56
'Xcode 13.0'55
'Xcode 12.0'54
'Xcode 11.4'53
'Xcode 11.0'52
'Xcode 10.0'51
'Xcode 9.3'50
'Xcode 8.0'48
'Xcode 6.3'47
'Xcode 3.2'46
'Xcode 3.1'45

Attribution

版本与发布记录

@bacons/xcode 共发布 33 个版本,首次发布于 2023/06/06;过去 12 个月发布了 6 个版本。

版本发布日期距今
1.0.0-alpha.33latest2026/03/038 个月前
1.0.0-alpha.322026/03/018 个月前
1.0.0-alpha.312026/02/288 个月前
1.0.0-alpha.302026/02/288 个月前
1.0.0-alpha.292026/01/139 个月前
1.0.0-alpha.282025/12/1010 个月前

关于 @bacons/xcode 的常见问题

@bacons/xcode 是什么?

pbxproj parser

如何安装 @bacons/xcode?

在项目目录运行 npm install @bacons/xcode,或使用 pnpm add / yarn add / bun add。

@bacons/xcode 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@bacons/xcode 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@bacons/xcode 可以商用吗?

@bacons/xcode 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@bacons/xcode 的下载量有多少?

根据 npm 官方下载统计,@bacons/xcode 在 2026/10/08 – 2026/11/06 这 30 天内被下载 3,397,657 次,日均约 113,255 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@bacons/xcode 还在维护吗?

最新版本 1.0.0-alpha.33 发布于 2026/03/03(8 个月前);至今共发布 33 个版本,其中过去 12 个月发布了 6 个;首次发布于 2023/06/06。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。