From a5605ac773491a91f77cb01b61bd38b48b2ac6e6 Mon Sep 17 00:00:00 2001 From: evanbacon Date: Fri, 27 Feb 2026 11:26:53 -0800 Subject: [PATCH 1/4] Fix XCLocalSwiftPackageReference type definition Remove incorrect `path` property and make `relativePath` required. The `path` property was based on CocoaPods/Xcodeproj which appears to have an inaccuracy - real pbxproj files only contain `relativePath`. Co-Authored-By: Claude Opus 4.5 --- src/json/types.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/json/types.ts b/src/json/types.ts index 06c4b2e..fa7ff19 100644 --- a/src/json/types.ts +++ b/src/json/types.ts @@ -604,10 +604,8 @@ export interface XCRemoteSwiftPackageReference export interface XCLocalSwiftPackageReference extends AbstractObject { - /** URL the Swift package was installed from. */ - path: string; /** Repository path where the package is located relative to the Xcode project. */ - relativePath?: string; + relativePath: string; } export interface PBXContainerItemProxy< From 521a952663c1b6192c566f9da2a082b92b6f0f68 Mon Sep 17 00:00:00 2001 From: evanbacon Date: Fri, 27 Feb 2026 11:29:15 -0800 Subject: [PATCH 2/4] Add proper typing for XCSwiftPackageVersionRequirement Replace `Record` with a discriminated union type that captures all version requirement variants: - upToNextMajorVersion - upToNextMinorVersion - versionRange - exactVersion - branch - revision Also fix repositoryURL to be optional per the Swift implementation. Co-Authored-By: Claude Opus 4.5 --- src/json/types.ts | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/json/types.ts b/src/json/types.ts index fa7ff19..0d39fef 100644 --- a/src/json/types.ts +++ b/src/json/types.ts @@ -594,12 +594,40 @@ export interface XCSwiftPackageProductDependency productName?: string; } +/** Version requirement for a remote Swift package. */ +export type XCSwiftPackageVersionRequirement = + | { + kind: "upToNextMajorVersion"; + minimumVersion: string; + } + | { + kind: "upToNextMinorVersion"; + minimumVersion: string; + } + | { + kind: "versionRange"; + minimumVersion: string; + maximumVersion: string; + } + | { + kind: "exactVersion"; + version: string; + } + | { + kind: "branch"; + branch: string; + } + | { + kind: "revision"; + revision: string; + }; + export interface XCRemoteSwiftPackageReference extends AbstractObject { /** URL the Swift package was installed from. */ - repositoryURL: string; + repositoryURL?: string; /** Version requirements. */ - requirement?: Record; + requirement?: XCSwiftPackageVersionRequirement; } export interface XCLocalSwiftPackageReference From 05fbf7f4d5513bfb0df0b48be299103ec8f3f73d Mon Sep 17 00:00:00 2001 From: evanbacon Date: Fri, 27 Feb 2026 11:32:01 -0800 Subject: [PATCH 3/4] Make productName required in XCSwiftPackageProductDependency productName is always present in real pbxproj files. Also added documentation about the "plugin:" prefix for plugin dependencies. Co-Authored-By: Claude Opus 4.5 --- src/json/types.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/json/types.ts b/src/json/types.ts index 0d39fef..7895584 100644 --- a/src/json/types.ts +++ b/src/json/types.ts @@ -591,7 +591,8 @@ export interface XCSwiftPackageProductDependency /** UUID for an object of type `XCRemoteSwiftPackageReference` or `XCLocalSwiftPackageReference` */ package?: TPackage; - productName?: string; + /** Name of the product from the Swift package. For plugins, prefixed with "plugin:" */ + productName: string; } /** Version requirement for a remote Swift package. */ From 6898018a779512e4af545e3ccfad4a8bdecd2d59 Mon Sep 17 00:00:00 2001 From: evanbacon Date: Fri, 27 Feb 2026 11:35:31 -0800 Subject: [PATCH 4/4] Add comprehensive Swift Package Manager tests Add 24 tests covering: - XCLocalSwiftPackageReference: create, getDisplayName, is() - XCRemoteSwiftPackageReference: parsing, all 6 version requirement types - XCSwiftPackageProductDependency: parsing, create with local/remote refs - Round-trip serialization for all SPM types Also fix XCSwiftPackageProductDependency.getObjectProps() to properly declare `package` as a reference property, enabling automatic inflation/deflation of package references. Co-Authored-By: Claude Opus 4.5 --- src/api/XCSwiftPackageProductDependency.ts | 4 +- src/api/__tests__/SwiftPackage.test.ts | 391 +++++++++++++++++++++ 2 files changed, 394 insertions(+), 1 deletion(-) create mode 100644 src/api/__tests__/SwiftPackage.test.ts diff --git a/src/api/XCSwiftPackageProductDependency.ts b/src/api/XCSwiftPackageProductDependency.ts index 3c609aa..feaaf0d 100644 --- a/src/api/XCSwiftPackageProductDependency.ts +++ b/src/api/XCSwiftPackageProductDependency.ts @@ -29,7 +29,9 @@ export class XCSwiftPackageProductDependency extends AbstractObject { + describe("create", () => { + it("creates with relativePath", () => { + const xcproj = XcodeProject.open(SPM_FIXTURE); + + const ref = XCLocalSwiftPackageReference.create(xcproj, { + relativePath: "../MyLocalPackage", + }); + + expect(ref.props.relativePath).toBe("../MyLocalPackage"); + expect(ref.isa).toBe("XCLocalSwiftPackageReference"); + }); + + it("creates with nested relativePath", () => { + const xcproj = XcodeProject.open(SPM_FIXTURE); + + const ref = XCLocalSwiftPackageReference.create(xcproj, { + relativePath: "Packages/MyLocalPackage", + }); + + expect(ref.props.relativePath).toBe("Packages/MyLocalPackage"); + }); + }); + + describe("getDisplayName", () => { + it("returns relativePath as display name", () => { + const xcproj = XcodeProject.open(SPM_FIXTURE); + + const ref = XCLocalSwiftPackageReference.create(xcproj, { + relativePath: "../MyLocalPackage", + }); + + expect(ref.getDisplayName()).toBe("../MyLocalPackage"); + }); + }); + + describe("is", () => { + it("returns true for XCLocalSwiftPackageReference", () => { + const xcproj = XcodeProject.open(SPM_FIXTURE); + + const ref = XCLocalSwiftPackageReference.create(xcproj, { + relativePath: "../MyLocalPackage", + }); + + expect(XCLocalSwiftPackageReference.is(ref)).toBe(true); + expect(XCRemoteSwiftPackageReference.is(ref)).toBe(false); + expect(XCSwiftPackageProductDependency.is(ref)).toBe(false); + }); + }); +}); + +describe("XCRemoteSwiftPackageReference", () => { + describe("parsing from fixture", () => { + it("parses existing remote package reference", () => { + const xcproj = XcodeProject.open(SPM_FIXTURE); + + const ref = xcproj.getObject( + "AC9C55BC2BD9246500041977" + ) as XCRemoteSwiftPackageReference; + + expect(ref.isa).toBe("XCRemoteSwiftPackageReference"); + expect(ref.props.repositoryURL).toBe( + "https://github.com/supabase/supabase-swift" + ); + expect(ref.props.requirement).toEqual({ + kind: "upToNextMajorVersion", + minimumVersion: "2.5.1", + }); + }); + }); + + describe("create", () => { + it("creates with upToNextMajorVersion requirement", () => { + const xcproj = XcodeProject.open(SPM_FIXTURE); + + const ref = XCRemoteSwiftPackageReference.create(xcproj, { + repositoryURL: "https://github.com/example/package", + requirement: { + kind: "upToNextMajorVersion", + minimumVersion: "1.0.0", + }, + }); + + expect(ref.props.repositoryURL).toBe("https://github.com/example/package"); + expect(ref.props.requirement).toEqual({ + kind: "upToNextMajorVersion", + minimumVersion: "1.0.0", + }); + }); + + it("creates with upToNextMinorVersion requirement", () => { + const xcproj = XcodeProject.open(SPM_FIXTURE); + + const ref = XCRemoteSwiftPackageReference.create(xcproj, { + repositoryURL: "https://github.com/example/package", + requirement: { + kind: "upToNextMinorVersion", + minimumVersion: "1.2.0", + }, + }); + + expect(ref.props.requirement).toEqual({ + kind: "upToNextMinorVersion", + minimumVersion: "1.2.0", + }); + }); + + it("creates with exactVersion requirement", () => { + const xcproj = XcodeProject.open(SPM_FIXTURE); + + const ref = XCRemoteSwiftPackageReference.create(xcproj, { + repositoryURL: "https://github.com/example/package", + requirement: { + kind: "exactVersion", + version: "2.3.4", + }, + }); + + expect(ref.props.requirement).toEqual({ + kind: "exactVersion", + version: "2.3.4", + }); + }); + + it("creates with versionRange requirement", () => { + const xcproj = XcodeProject.open(SPM_FIXTURE); + + const ref = XCRemoteSwiftPackageReference.create(xcproj, { + repositoryURL: "https://github.com/example/package", + requirement: { + kind: "versionRange", + minimumVersion: "1.0.0", + maximumVersion: "2.0.0", + }, + }); + + expect(ref.props.requirement).toEqual({ + kind: "versionRange", + minimumVersion: "1.0.0", + maximumVersion: "2.0.0", + }); + }); + + it("creates with branch requirement", () => { + const xcproj = XcodeProject.open(SPM_FIXTURE); + + const ref = XCRemoteSwiftPackageReference.create(xcproj, { + repositoryURL: "https://github.com/example/package", + requirement: { + kind: "branch", + branch: "main", + }, + }); + + expect(ref.props.requirement).toEqual({ + kind: "branch", + branch: "main", + }); + }); + + it("creates with revision requirement", () => { + const xcproj = XcodeProject.open(SPM_FIXTURE); + + const ref = XCRemoteSwiftPackageReference.create(xcproj, { + repositoryURL: "https://github.com/example/package", + requirement: { + kind: "revision", + revision: "abc123def456", + }, + }); + + expect(ref.props.requirement).toEqual({ + kind: "revision", + revision: "abc123def456", + }); + }); + + it("creates without requirement", () => { + const xcproj = XcodeProject.open(SPM_FIXTURE); + + const ref = XCRemoteSwiftPackageReference.create(xcproj, { + repositoryURL: "https://github.com/example/package", + }); + + expect(ref.props.repositoryURL).toBe("https://github.com/example/package"); + expect(ref.props.requirement).toBeUndefined(); + }); + }); + + describe("getDisplayName", () => { + it("returns repositoryURL as display name", () => { + const xcproj = XcodeProject.open(SPM_FIXTURE); + + const ref = XCRemoteSwiftPackageReference.create(xcproj, { + repositoryURL: "https://github.com/example/package", + }); + + expect(ref.getDisplayName()).toBe("https://github.com/example/package"); + }); + }); + + describe("is", () => { + it("returns true for XCRemoteSwiftPackageReference", () => { + const xcproj = XcodeProject.open(SPM_FIXTURE); + + const ref = XCRemoteSwiftPackageReference.create(xcproj, { + repositoryURL: "https://github.com/example/package", + }); + + expect(XCRemoteSwiftPackageReference.is(ref)).toBe(true); + expect(XCLocalSwiftPackageReference.is(ref)).toBe(false); + expect(XCSwiftPackageProductDependency.is(ref)).toBe(false); + }); + }); +}); + +describe("XCSwiftPackageProductDependency", () => { + describe("parsing from fixture", () => { + it("parses existing product dependency", () => { + const xcproj = XcodeProject.open(SPM_FIXTURE); + + const dep = xcproj.getObject( + "AC9C55BD2BD9246500041977" + ) as XCSwiftPackageProductDependency; + + expect(dep.isa).toBe("XCSwiftPackageProductDependency"); + expect(dep.props.productName).toBe("Supabase"); + // Package reference should be inflated + expect(dep.props.package).toBeDefined(); + }); + + it("has package reference linked to remote package", () => { + const xcproj = XcodeProject.open(SPM_FIXTURE); + + const dep = xcproj.getObject( + "AC9C55BD2BD9246500041977" + ) as XCSwiftPackageProductDependency; + + const packageRef = dep.props.package as XCRemoteSwiftPackageReference; + expect(packageRef.props.repositoryURL).toBe( + "https://github.com/supabase/supabase-swift" + ); + }); + }); + + describe("create", () => { + it("creates with remote package reference", () => { + const xcproj = XcodeProject.open(SPM_FIXTURE); + + const packageRef = XCRemoteSwiftPackageReference.create(xcproj, { + repositoryURL: "https://github.com/example/package", + requirement: { + kind: "upToNextMajorVersion", + minimumVersion: "1.0.0", + }, + }); + + const dep = XCSwiftPackageProductDependency.create(xcproj, { + productName: "ExamplePackage", + package: packageRef, + }); + + expect(dep.props.productName).toBe("ExamplePackage"); + expect(dep.props.package).toBe(packageRef); + }); + + it("creates with local package reference", () => { + const xcproj = XcodeProject.open(SPM_FIXTURE); + + const packageRef = XCLocalSwiftPackageReference.create(xcproj, { + relativePath: "../MyLocalPackage", + }); + + const dep = XCSwiftPackageProductDependency.create(xcproj, { + productName: "MyLocalPackage", + package: packageRef, + }); + + expect(dep.props.productName).toBe("MyLocalPackage"); + expect(dep.props.package).toBe(packageRef); + }); + + it("creates without package reference (for local packages in some cases)", () => { + const xcproj = XcodeProject.open(SPM_FIXTURE); + + const dep = XCSwiftPackageProductDependency.create(xcproj, { + productName: "SomeProduct", + }); + + expect(dep.props.productName).toBe("SomeProduct"); + expect(dep.props.package).toBeUndefined(); + }); + }); + + describe("getDisplayName", () => { + it("returns productName as display name", () => { + const xcproj = XcodeProject.open(SPM_FIXTURE); + + const dep = XCSwiftPackageProductDependency.create(xcproj, { + productName: "MyPackage", + }); + + expect(dep.getDisplayName()).toBe("MyPackage"); + }); + }); + + describe("is", () => { + it("returns true for XCSwiftPackageProductDependency", () => { + const xcproj = XcodeProject.open(SPM_FIXTURE); + + const dep = XCSwiftPackageProductDependency.create(xcproj, { + productName: "MyPackage", + }); + + expect(XCSwiftPackageProductDependency.is(dep)).toBe(true); + expect(XCRemoteSwiftPackageReference.is(dep)).toBe(false); + expect(XCLocalSwiftPackageReference.is(dep)).toBe(false); + }); + }); +}); + +describe("round-trip serialization", () => { + it("preserves XCRemoteSwiftPackageReference through toJSON", () => { + const xcproj = XcodeProject.open(SPM_FIXTURE); + + const ref = XCRemoteSwiftPackageReference.create(xcproj, { + repositoryURL: "https://github.com/example/package", + requirement: { + kind: "upToNextMajorVersion", + minimumVersion: "1.0.0", + }, + }); + + const json = ref.toJSON(); + + expect(json.isa).toBe("XCRemoteSwiftPackageReference"); + expect(json.repositoryURL).toBe("https://github.com/example/package"); + expect(json.requirement).toEqual({ + kind: "upToNextMajorVersion", + minimumVersion: "1.0.0", + }); + }); + + it("preserves XCLocalSwiftPackageReference through toJSON", () => { + const xcproj = XcodeProject.open(SPM_FIXTURE); + + const ref = XCLocalSwiftPackageReference.create(xcproj, { + relativePath: "../MyLocalPackage", + }); + + const json = ref.toJSON(); + + expect(json.isa).toBe("XCLocalSwiftPackageReference"); + expect(json.relativePath).toBe("../MyLocalPackage"); + }); + + it("preserves XCSwiftPackageProductDependency through toJSON", () => { + const xcproj = XcodeProject.open(SPM_FIXTURE); + + const packageRef = XCRemoteSwiftPackageReference.create(xcproj, { + repositoryURL: "https://github.com/example/package", + }); + + const dep = XCSwiftPackageProductDependency.create(xcproj, { + productName: "ExamplePackage", + package: packageRef, + }); + + const json = dep.toJSON(); + + expect(json.isa).toBe("XCSwiftPackageProductDependency"); + expect(json.productName).toBe("ExamplePackage"); + // Package should be deflated to UUID + expect(typeof json.package).toBe("string"); + expect(json.package).toBe(packageRef.uuid); + }); +});