rork-xcode 0.1.0 → 0.3.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/dist/index.js CHANGED
@@ -14,7 +14,7 @@ function isDictionary(value) {
14
14
  * Field access on parsed documents goes through this because any field of
15
15
  * an untrusted document can hold any value type.
16
16
  */
17
- function asString(value) {
17
+ function asString$1(value) {
18
18
  return typeof value === "string" ? value : void 0;
19
19
  }
20
20
  /**
@@ -70,33 +70,33 @@ function createReferenceComments(root) {
70
70
  for (const ownerId of Object.keys(objects)) {
71
71
  const owner = objects[ownerId];
72
72
  if (!isDictionary(owner)) continue;
73
- const isa = asString(owner["isa"]) ?? "";
73
+ const isa = asString$1(owner["isa"]) ?? "";
74
74
  if (isa.endsWith("BuildPhase")) indexFirst(fileToPhase, owner["files"], {
75
75
  isa,
76
- name: asString(owner["name"])
76
+ name: asString$1(owner["name"])
77
77
  });
78
78
  else if (isa === "PBXContainerItemProxy") {
79
- const portal = asString(owner["containerPortal"]);
80
- const remoteInfo = asString(owner["remoteInfo"]);
79
+ const portal = asString$1(owner["containerPortal"]);
80
+ const remoteInfo = asString$1(owner["remoteInfo"]);
81
81
  if (portal != null && remoteInfo != null && !proxyRemoteInfoByPortal.has(portal)) proxyRemoteInfoByPortal.set(portal, remoteInfo);
82
82
  }
83
83
  indexFirst(syncGroupByExceptionSet, owner["exceptions"], owner);
84
84
  indexFirst(targetByBuildPhase, owner["buildPhases"], owner);
85
- const listId = asString(owner["buildConfigurationList"]);
85
+ const listId = asString$1(owner["buildConfigurationList"]);
86
86
  if (listId != null && !configurationListOwners.has(listId)) configurationListOwners.set(listId, [ownerId, owner]);
87
87
  }
88
88
  /**
89
89
  * The name an object displays as when nothing more specific applies:
90
90
  * `name`, then `productName`, then `path`, then its isa.
91
91
  */
92
- const defaultName = (object, isa) => asString(object["name"]) ?? asString(object["productName"]) ?? asString(object["path"]) ?? isa;
92
+ const defaultName = (object, isa) => asString$1(object["name"]) ?? asString$1(object["productName"]) ?? asString$1(object["path"]) ?? isa;
93
93
  /**
94
94
  * The display name of the synchronized folder an exception set belongs
95
95
  * to, found through the sync group whose `exceptions` array lists it.
96
96
  */
97
97
  const exceptionSetFolderName = (id) => {
98
98
  const group = syncGroupByExceptionSet.get(id);
99
- return group == null ? void 0 : asString(group["name"]) ?? asString(group["path"]);
99
+ return group == null ? void 0 : asString$1(group["name"]) ?? asString$1(group["path"]);
100
100
  };
101
101
  /**
102
102
  * Comment for a `PBXFileSystemSynchronizedBuildFileExceptionSet`,
@@ -106,10 +106,10 @@ function createReferenceComments(root) {
106
106
  */
107
107
  const buildFileExceptionSetComment = (id, set) => {
108
108
  const folder = exceptionSetFolderName(id);
109
- const targetId = asString(set["target"]);
109
+ const targetId = asString$1(set["target"]);
110
110
  const target = targetId == null ? void 0 : objects[targetId];
111
111
  if (folder == null || !isDictionary(target)) return;
112
- const targetName = asString(target["name"]) ?? asString(target["productName"]) ?? asString(target["path"]);
112
+ const targetName = asString$1(target["name"]) ?? asString$1(target["productName"]) ?? asString$1(target["path"]);
113
113
  return targetName == null ? void 0 : `Exceptions for "${folder}" folder in "${targetName}" target`;
114
114
  };
115
115
  /**
@@ -120,12 +120,12 @@ function createReferenceComments(root) {
120
120
  */
121
121
  const membershipExceptionSetComment = (id, set) => {
122
122
  const folder = exceptionSetFolderName(id);
123
- const phaseId = asString(set["buildPhase"]);
123
+ const phaseId = asString$1(set["buildPhase"]);
124
124
  const phase = phaseId == null ? void 0 : objects[phaseId];
125
125
  if (folder == null || phaseId == null || !isDictionary(phase)) return;
126
- const phaseName = asString(phase["name"]) ?? defaultBuildPhaseName(asString(phase["isa"]) ?? "");
126
+ const phaseName = asString$1(phase["name"]) ?? defaultBuildPhaseName(asString$1(phase["isa"]) ?? "");
127
127
  const target = targetByBuildPhase.get(phaseId);
128
- const targetName = target == null ? void 0 : asString(target["name"]) ?? asString(target["productName"]) ?? asString(target["path"]);
128
+ const targetName = target == null ? void 0 : asString$1(target["name"]) ?? asString$1(target["productName"]) ?? asString$1(target["path"]);
129
129
  if (phaseName == null || targetName == null) return;
130
130
  return `Exceptions for "${folder}" folder in "${phaseName}" phase from "${targetName}" target`;
131
131
  };
@@ -136,7 +136,7 @@ function createReferenceComments(root) {
136
136
  const buildFileComment = (id, buildFile) => {
137
137
  const phase = fileToPhase.get(id);
138
138
  const phaseName = phase == null ? "[missing build phase]" : phase.name ?? defaultBuildPhaseName(phase.isa) ?? "";
139
- const refId = asString(buildFile["fileRef"]) ?? asString(buildFile["productRef"]);
139
+ const refId = asString$1(buildFile["fileRef"]) ?? asString$1(buildFile["productRef"]);
140
140
  const referenced = refId == null ? void 0 : objects[refId];
141
141
  return `${(refId != null && isDictionary(referenced) ? commentFor(refId, referenced) : void 0) ?? "(null)"} in ${phaseName}`;
142
142
  };
@@ -148,15 +148,15 @@ function createReferenceComments(root) {
148
148
  const ownerEntry = configurationListOwners.get(id);
149
149
  if (ownerEntry == null) return "Build configuration list for [unknown]";
150
150
  const [ownerId, owner] = ownerEntry;
151
- const isa = asString(owner["isa"]) ?? "";
152
- const ownName = asString(owner["name"]) ?? asString(owner["path"]) ?? asString(owner["productName"]);
151
+ const isa = asString$1(owner["isa"]) ?? "";
152
+ const ownName = asString$1(owner["name"]) ?? asString$1(owner["path"]) ?? asString$1(owner["productName"]);
153
153
  if (ownName != null) return `Build configuration list for ${isa} "${ownName}"`;
154
154
  const targets = owner["targets"];
155
155
  if (Array.isArray(targets)) {
156
156
  const firstTargetId = targets.find((target) => typeof target === "string");
157
157
  const firstTarget = firstTargetId == null ? void 0 : objects[firstTargetId];
158
158
  if (isDictionary(firstTarget)) {
159
- const targetName = asString(firstTarget["productName"]) ?? asString(firstTarget["name"]);
159
+ const targetName = asString$1(firstTarget["productName"]) ?? asString$1(firstTarget["name"]);
160
160
  if (targetName != null) return `Build configuration list for ${isa} "${targetName}"`;
161
161
  }
162
162
  }
@@ -172,7 +172,7 @@ function createReferenceComments(root) {
172
172
  const commentFor = (id, object) => {
173
173
  const cached = cache.get(id);
174
174
  if (cached != null) return cached;
175
- const isa = asString(object["isa"]);
175
+ const isa = asString$1(object["isa"]);
176
176
  if (isa == null) return;
177
177
  if (inProgress.has(id)) return;
178
178
  inProgress.add(id);
@@ -180,17 +180,17 @@ function createReferenceComments(root) {
180
180
  if (isa === "PBXBuildFile") comment = buildFileComment(id, object);
181
181
  else if (isa === "XCConfigurationList") comment = configurationListComment(id);
182
182
  else if (isa === "XCRemoteSwiftPackageReference") {
183
- const repoUrl = asString(object["repositoryURL"]);
183
+ const repoUrl = asString$1(object["repositoryURL"]);
184
184
  comment = repoUrl == null ? isa : `${isa} "${repoNameFromUrl(repoUrl)}"`;
185
185
  } else if (isa === "XCLocalSwiftPackageReference") {
186
- const relativePath = asString(object["relativePath"]);
186
+ const relativePath = asString$1(object["relativePath"]);
187
187
  comment = relativePath == null ? isa : `${isa} "${relativePath}"`;
188
188
  } else if (isa === "PBXProject") comment = "Project object";
189
189
  else if (isa === "PBXFileSystemSynchronizedBuildFileExceptionSet") comment = buildFileExceptionSetComment(id, object) ?? isa;
190
190
  else if (isa === "PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet") comment = membershipExceptionSetComment(id, object) ?? isa;
191
191
  else if (isa === "PBXTargetDependency") comment = isa;
192
- else if (isa.endsWith("BuildPhase")) comment = asString(object["name"]) ?? defaultBuildPhaseName(isa) ?? "";
193
- else if (isa === "PBXGroup" && asString(object["name"]) == null && asString(object["path"]) == null) comment = "";
192
+ else if (isa.endsWith("BuildPhase")) comment = asString$1(object["name"]) ?? defaultBuildPhaseName(isa) ?? "";
193
+ else if (isa === "PBXGroup" && asString$1(object["name"]) == null && asString$1(object["path"]) == null) comment = "";
194
194
  else comment = defaultName(object, isa);
195
195
  inProgress.delete(id);
196
196
  cache.set(id, comment);
@@ -259,6 +259,70 @@ var PbxprojParseError = class extends Error {
259
259
  }
260
260
  };
261
261
  /**
262
+ * Thrown when the source text is not a well-formed scheme document (the
263
+ * XML dialect of `.xcscheme` files).
264
+ *
265
+ * The message always embeds the line and column of the failure, and the
266
+ * same information is available in structured form on {@link position}
267
+ * for programmatic use.
268
+ */
269
+ var XcschemeParseError = class extends Error {
270
+ /** Where in the source text parsing failed. */
271
+ position;
272
+ /**
273
+ * @param message Failure description without location; the location is
274
+ * appended automatically.
275
+ * @param source Full source text, used to compute the position.
276
+ * @param offset Character offset of the failure inside `source`.
277
+ */
278
+ constructor(message, source, offset) {
279
+ const position = positionAt(source, offset);
280
+ super(`${message} (line ${position.line}, column ${position.column})`);
281
+ this.name = "XcschemeParseError";
282
+ this.position = position;
283
+ }
284
+ };
285
+ /**
286
+ * Thrown when a scheme element cannot be written as XML.
287
+ *
288
+ * Raised for element and attribute names that are not valid XML names and
289
+ * for attribute values carrying control characters XML 1.0 cannot encode.
290
+ * The {@link path} pinpoints the offending element inside the tree.
291
+ */
292
+ var XcschemeBuildError = class extends Error {
293
+ /** Path to the offending element from the root, e.g. `Scheme.BuildAction[0]`. */
294
+ path;
295
+ /**
296
+ * @param message Failure description without location; the element path
297
+ * is appended automatically.
298
+ * @param path Path to the offending element from the root.
299
+ */
300
+ constructor(message, path) {
301
+ super(`${message} (at ${path})`);
302
+ this.name = "XcschemeBuildError";
303
+ this.path = path;
304
+ }
305
+ };
306
+ /**
307
+ * Thrown by the object model when an operation cannot proceed: the
308
+ * document lacks the structure the operation needs (no objects dictionary,
309
+ * no root project object), a view's object was removed from the document,
310
+ * or a creation request names a product type the model cannot scaffold.
311
+ *
312
+ * Read paths in the model return `undefined` for merely malformed data;
313
+ * this error marks the cases where continuing would corrupt the document
314
+ * or hide a programming mistake.
315
+ */
316
+ var XcodeModelError = class extends Error {
317
+ /**
318
+ * @param message Description of the failed operation.
319
+ */
320
+ constructor(message) {
321
+ super(message);
322
+ this.name = "XcodeModelError";
323
+ }
324
+ };
325
+ /**
262
326
  * Thrown when a value cannot be represented in a `project.pbxproj` document.
263
327
  *
264
328
  * Raised for `null`, `undefined`, booleans, bigints, functions, symbols,
@@ -727,6 +791,578 @@ function buildPbxproj(root) {
727
791
  return new Writer(root).toString();
728
792
  }
729
793
  //#endregion
794
+ //#region src/model/isa.ts
795
+ /**
796
+ * Names and well-known values of the pbxproj object vocabulary.
797
+ *
798
+ * Everything here mirrors strings Xcode itself writes; nothing is invented.
799
+ * Centralizing them keeps the object model free of string literals and
800
+ * gives call sites one place to import from.
801
+ *
802
+ * @module
803
+ */
804
+ /**
805
+ * The `isa` names the object model works with. The parser and serializer
806
+ * accept any isa; this list only covers the kinds the model creates or
807
+ * gives typed access to.
808
+ */
809
+ const Isa = {
810
+ aggregateTarget: "PBXAggregateTarget",
811
+ buildFile: "PBXBuildFile",
812
+ buildRule: "PBXBuildRule",
813
+ containerItemProxy: "PBXContainerItemProxy",
814
+ copyFilesBuildPhase: "PBXCopyFilesBuildPhase",
815
+ fileReference: "PBXFileReference",
816
+ fileSystemSynchronizedBuildFileExceptionSet: "PBXFileSystemSynchronizedBuildFileExceptionSet",
817
+ fileSystemSynchronizedRootGroup: "PBXFileSystemSynchronizedRootGroup",
818
+ frameworksBuildPhase: "PBXFrameworksBuildPhase",
819
+ group: "PBXGroup",
820
+ legacyTarget: "PBXLegacyTarget",
821
+ nativeTarget: "PBXNativeTarget",
822
+ project: "PBXProject",
823
+ referenceProxy: "PBXReferenceProxy",
824
+ resourcesBuildPhase: "PBXResourcesBuildPhase",
825
+ sourcesBuildPhase: "PBXSourcesBuildPhase",
826
+ targetDependency: "PBXTargetDependency",
827
+ variantGroup: "PBXVariantGroup",
828
+ buildConfiguration: "XCBuildConfiguration",
829
+ configurationList: "XCConfigurationList",
830
+ localSwiftPackageReference: "XCLocalSwiftPackageReference",
831
+ remoteSwiftPackageReference: "XCRemoteSwiftPackageReference",
832
+ swiftPackageProductDependency: "XCSwiftPackageProductDependency",
833
+ versionGroup: "XCVersionGroup"
834
+ };
835
+ /**
836
+ * Product type identifiers of the targets the model creates or reasons
837
+ * about. Other product types pass through untouched.
838
+ */
839
+ const ProductType = {
840
+ application: "com.apple.product-type.application",
841
+ messagesApplication: "com.apple.product-type.application.messages",
842
+ appExtension: "com.apple.product-type.app-extension",
843
+ messagesExtension: "com.apple.product-type.app-extension.messages",
844
+ extensionKitExtension: "com.apple.product-type.extensionkit-extension",
845
+ onDemandInstallCapableApplication: "com.apple.product-type.application.on-demand-install-capable",
846
+ watchApp: "com.apple.product-type.application.watchapp2"
847
+ };
848
+ /**
849
+ * How a product type's build product appears on disk. Each entry carries
850
+ * the wrapper file extension and the `explicitFileType` of the product
851
+ * file reference.
852
+ */
853
+ const PRODUCT_FILE_INFO = {
854
+ [ProductType.application]: {
855
+ extension: ".app",
856
+ fileType: "wrapper.application"
857
+ },
858
+ [ProductType.messagesApplication]: {
859
+ extension: ".app",
860
+ fileType: "wrapper.application"
861
+ },
862
+ [ProductType.onDemandInstallCapableApplication]: {
863
+ extension: ".app",
864
+ fileType: "wrapper.application"
865
+ },
866
+ [ProductType.watchApp]: {
867
+ extension: ".app",
868
+ fileType: "wrapper.application"
869
+ },
870
+ [ProductType.appExtension]: {
871
+ extension: ".appex",
872
+ fileType: "wrapper.app-extension"
873
+ },
874
+ [ProductType.messagesExtension]: {
875
+ extension: ".appex",
876
+ fileType: "wrapper.app-extension"
877
+ },
878
+ [ProductType.extensionKitExtension]: {
879
+ extension: ".appex",
880
+ fileType: "wrapper.app-extension"
881
+ }
882
+ };
883
+ /**
884
+ * The build-setting key that carries each platform's deployment target.
885
+ * A target (or its project) sets exactly one of these per platform it
886
+ * builds for, which is what makes them usable as a platform signal.
887
+ */
888
+ const DEPLOYMENT_TARGET_KEY = {
889
+ ios: "IPHONEOS_DEPLOYMENT_TARGET",
890
+ macos: "MACOSX_DEPLOYMENT_TARGET",
891
+ tvos: "TVOS_DEPLOYMENT_TARGET",
892
+ watchos: "WATCHOS_DEPLOYMENT_TARGET",
893
+ visionos: "XROS_DEPLOYMENT_TARGET"
894
+ };
895
+ /**
896
+ * `dstSubfolderSpec` values for copy-files build phases, named after the
897
+ * destination each selects.
898
+ */
899
+ const CopyFilesDestination = {
900
+ plugins: 13,
901
+ productsDirectory: 16
902
+ };
903
+ /**
904
+ * Resolves the embed phase destination for an extension-like product type.
905
+ * Watch applications are embedded by product type here; callers that only
906
+ * know build settings should check the deployment-target key instead.
907
+ */
908
+ function embedDestinationFor(productType) {
909
+ switch (productType) {
910
+ case ProductType.onDemandInstallCapableApplication: return {
911
+ phaseName: "Embed App Clips",
912
+ dstSubfolderSpec: CopyFilesDestination.productsDirectory,
913
+ dstPath: "$(CONTENTS_FOLDER_PATH)/AppClips"
914
+ };
915
+ case ProductType.watchApp:
916
+ case ProductType.application: return {
917
+ phaseName: "Embed Watch Content",
918
+ dstSubfolderSpec: CopyFilesDestination.productsDirectory,
919
+ dstPath: "$(CONTENTS_FOLDER_PATH)/Watch"
920
+ };
921
+ case ProductType.extensionKitExtension: return {
922
+ phaseName: "Embed ExtensionKit Extensions",
923
+ dstSubfolderSpec: CopyFilesDestination.productsDirectory,
924
+ dstPath: "$(EXTENSIONS_FOLDER_PATH)"
925
+ };
926
+ default: return {
927
+ phaseName: "Embed Foundation Extensions",
928
+ dstSubfolderSpec: CopyFilesDestination.plugins,
929
+ dstPath: ""
930
+ };
931
+ }
932
+ }
933
+ /**
934
+ * `lastKnownFileType` values for file references created by path, keyed by
935
+ * file extension. Extensions outside the map create references without a
936
+ * type, which Xcode tolerates and re-derives.
937
+ */
938
+ const FILE_TYPE_BY_EXTENSION = {
939
+ ".swift": "sourcecode.swift",
940
+ ".m": "sourcecode.c.objc",
941
+ ".mm": "sourcecode.cpp.objcpp",
942
+ ".h": "sourcecode.c.h",
943
+ ".c": "sourcecode.c.c",
944
+ ".cpp": "sourcecode.cpp.cpp",
945
+ ".plist": "text.plist.xml",
946
+ ".entitlements": "text.plist.entitlements",
947
+ ".xcconfig": "text.xcconfig",
948
+ ".storyboard": "file.storyboard",
949
+ ".xib": "file.xib",
950
+ ".xcassets": "folder.assetcatalog",
951
+ ".framework": "wrapper.framework",
952
+ ".xcdatamodeld": "wrapper.xcdatamodeld",
953
+ ".metal": "sourcecode.metal",
954
+ ".md": "net.daringfireball.markdown",
955
+ ".json": "text.json"
956
+ };
957
+ //#endregion
958
+ //#region src/model/values.ts
959
+ /**
960
+ * Returns the value when it is a string, and `undefined` otherwise.
961
+ */
962
+ function asString(value) {
963
+ return typeof value === "string" ? value : void 0;
964
+ }
965
+ /**
966
+ * Returns the value when it is a dictionary, and `undefined` otherwise.
967
+ *
968
+ * Arrays and `Uint8Array` data are objects at runtime too, so a bare
969
+ * `typeof` check is not enough.
970
+ */
971
+ function asDictionary(value) {
972
+ if (typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Uint8Array)) return value;
973
+ }
974
+ /**
975
+ * Returns the array under `key`, creating an empty one when the key is
976
+ * absent or holds a non-array value.
977
+ *
978
+ * Mutating methods use this to append into list fields (`children`,
979
+ * `dependencies`, `exceptions`) without separate existence checks at every
980
+ * call site.
981
+ */
982
+ function ensureArray(object, key) {
983
+ const existing = object[key];
984
+ if (Array.isArray(existing)) return existing;
985
+ const created = [];
986
+ object[key] = created;
987
+ return created;
988
+ }
989
+ /**
990
+ * Collects the string items of a possibly absent, possibly mixed array.
991
+ *
992
+ * Reference lists in well-formed documents contain only id strings, but a
993
+ * malformed document can mix in anything; non-strings are skipped rather
994
+ * than thrown on, matching the library's soft-failure stance on reads.
995
+ */
996
+ function stringItems(value) {
997
+ if (!Array.isArray(value)) return [];
998
+ const items = [];
999
+ for (const item of value) if (typeof item === "string") items.push(item);
1000
+ return items;
1001
+ }
1002
+ //#endregion
1003
+ //#region src/model/object.ts
1004
+ /**
1005
+ * The base view class of the object model.
1006
+ *
1007
+ * Views are lightweight, identity-mapped facades over entries of the
1008
+ * document's `objects` dictionary. All state lives in the parsed document
1009
+ * itself; a view holds only its id and a reference back to the project, so
1010
+ * document text produced from the model reflects every mutation with no
1011
+ * separate serialization step.
1012
+ *
1013
+ * @module
1014
+ */
1015
+ /**
1016
+ * A typed handle on one object of the document.
1017
+ *
1018
+ * Two lookups of the same id return the same view instance (the project
1019
+ * keeps an identity map), so views compare with `===`.
1020
+ *
1021
+ * The type parameter describes the properties of a well-formed object of
1022
+ * this kind. Subclasses fix it to their kind's interface, which gives
1023
+ * `properties` autocompletion. Treat the shape as a description rather
1024
+ * than a guarantee. A malformed document can hold anything, so the model
1025
+ * itself always reads through the narrowing accessors.
1026
+ */
1027
+ var XcodeObject = class {
1028
+ /** The project this object belongs to. */
1029
+ project;
1030
+ /** The object's 24-character identifier (its key in `objects`). */
1031
+ id;
1032
+ /**
1033
+ * Views are created by the project's identity map; use
1034
+ * {@link XcodeProject.get} or a typed query instead of constructing
1035
+ * views directly.
1036
+ */
1037
+ constructor(project, id) {
1038
+ this.project = project;
1039
+ this.id = id;
1040
+ }
1041
+ /**
1042
+ * The object's raw dictionary inside the document. Mutations through the
1043
+ * model write here, and direct writes are equally valid; the model adds
1044
+ * no caching over these properties.
1045
+ *
1046
+ * The typed shape is asserted, not checked. It describes what a
1047
+ * well-formed object of this kind carries (see `properties.ts`).
1048
+ */
1049
+ get properties() {
1050
+ return this.project.propertiesOf(this.id);
1051
+ }
1052
+ /**
1053
+ * The object's `isa` kind name, or the empty string when the field is
1054
+ * missing or malformed.
1055
+ */
1056
+ get isa() {
1057
+ return asString(this.properties["isa"]) ?? "";
1058
+ }
1059
+ /**
1060
+ * Reads a property expected to be a string. Returns `undefined` when the
1061
+ * property is absent or holds another type.
1062
+ */
1063
+ getString(key) {
1064
+ return asString(this.properties[key]);
1065
+ }
1066
+ /**
1067
+ * Writes one property of the object.
1068
+ *
1069
+ * The write goes through the untyped document dictionary. TypeScript
1070
+ * does not allow writes through a generic index, and the typed shape on
1071
+ * `properties` is descriptive anyway.
1072
+ */
1073
+ set(key, value) {
1074
+ this.project.propertiesOf(this.id)[key] = value;
1075
+ }
1076
+ /**
1077
+ * The views of the objects referenced by an id-list property, resolved in
1078
+ * list order. Dangling ids and non-string entries of malformed documents
1079
+ * are skipped.
1080
+ */
1081
+ referencedViews(key) {
1082
+ const items = this.properties[key];
1083
+ if (!Array.isArray(items)) return [];
1084
+ const views = [];
1085
+ for (const item of items) {
1086
+ const view = typeof item === "string" ? this.project.get(item) : void 0;
1087
+ if (view != null) views.push(view);
1088
+ }
1089
+ return views;
1090
+ }
1091
+ /**
1092
+ * Removes the object from the document and strips every reference to it:
1093
+ * string properties naming the id are deleted, id lists drop it, and
1094
+ * nested dictionaries keyed by object id (such as the root project's
1095
+ * `TargetAttributes`) drop its entry.
1096
+ *
1097
+ * This is the low-level removal; it does not cascade to objects that only
1098
+ * made sense alongside this one. Higher-level operations like
1099
+ * {@link XcodeProject.removeTarget} compose it into full teardowns.
1100
+ */
1101
+ removeFromProject() {
1102
+ this.project.removeObject(this.id);
1103
+ }
1104
+ };
1105
+ //#endregion
1106
+ //#region src/model/objects.ts
1107
+ /**
1108
+ * Typed views over the document object kinds that are not targets, from
1109
+ * groups and build phases to version groups and reference proxies.
1110
+ *
1111
+ * Each class adds the accessors and mutations its kind supports; everything
1112
+ * ultimately reads and writes the raw dictionaries through the base class,
1113
+ * so mixing model calls with direct property access stays safe.
1114
+ *
1115
+ * @module
1116
+ */
1117
+ /**
1118
+ * A `PBXGroup` is a folder in Xcode's navigator, holding references to
1119
+ * files and other groups. Variant groups (`PBXVariantGroup`) share this
1120
+ * view. The type parameter lets subclasses carry a more specific property
1121
+ * shape.
1122
+ */
1123
+ var Group = class Group extends XcodeObject {
1124
+ /**
1125
+ * Ids of the group's children, in navigator order. Non-string entries of
1126
+ * a malformed document are skipped.
1127
+ */
1128
+ get childIds() {
1129
+ return stringItems(this.properties["children"]);
1130
+ }
1131
+ /**
1132
+ * The views of the group's children, in navigator order.
1133
+ */
1134
+ children() {
1135
+ return this.referencedViews("children");
1136
+ }
1137
+ /**
1138
+ * Adds an existing object (a file reference or another group) to the end
1139
+ * of the group's children. Adding a child the group already lists is a
1140
+ * no-op.
1141
+ */
1142
+ addChild(child) {
1143
+ const children = ensureArray(this.properties, "children");
1144
+ if (!children.includes(child.id)) children.push(child.id);
1145
+ }
1146
+ /**
1147
+ * Returns the descendant group for a `/`-separated path, creating any
1148
+ * missing groups along the way. Each component matches a child group by
1149
+ * its `path`, falling back to its `name`; created groups carry the
1150
+ * component as their `path` so they mirror folders on disk.
1151
+ *
1152
+ * ```ts
1153
+ * const generated = mainGroup.ensureGroup("Sources/Generated");
1154
+ * generated.createFile("Config.swift");
1155
+ * ```
1156
+ */
1157
+ ensureGroup(path) {
1158
+ return path.split("/").filter((component) => component !== "").reduce((parent, component) => {
1159
+ const existing = parent.children().find((child) => child instanceof Group && (child.getString("path") ?? child.getString("name")) === component);
1160
+ if (existing != null) return existing;
1161
+ const created = this.project.add(Isa.group, {
1162
+ children: [],
1163
+ path: component,
1164
+ sourceTree: "<group>"
1165
+ }, `${Isa.group} ${parent.id} ${component}`);
1166
+ parent.addChild(created);
1167
+ return created;
1168
+ }, this);
1169
+ }
1170
+ /**
1171
+ * Creates a `PBXFileReference` for a path relative to this group and adds
1172
+ * it to the group's children.
1173
+ *
1174
+ * The reference's `lastKnownFileType` derives from the file extension
1175
+ * when it is a known kind; otherwise the reference carries no type and
1176
+ * Xcode re-derives one on open.
1177
+ *
1178
+ * @param path File path relative to the group, for example
1179
+ * `Demo/Config.swift`.
1180
+ * @returns The view of the created file reference.
1181
+ */
1182
+ createFile(path) {
1183
+ const extensionStart = path.lastIndexOf(".");
1184
+ const fileType = extensionStart === -1 ? void 0 : FILE_TYPE_BY_EXTENSION[path.slice(extensionStart)];
1185
+ const reference = this.project.add(Isa.fileReference, {
1186
+ ...fileType == null ? {} : { lastKnownFileType: fileType },
1187
+ path,
1188
+ sourceTree: "<group>"
1189
+ }, `${Isa.fileReference} ${path}`);
1190
+ this.addChild(reference);
1191
+ return reference;
1192
+ }
1193
+ };
1194
+ /**
1195
+ * A build phase is an ordered list of build files processed by one step
1196
+ * of a target's build. Every `PBX*BuildPhase` kind shares this view.
1197
+ */
1198
+ var BuildPhase = class extends XcodeObject {
1199
+ /**
1200
+ * The phase's display name, when it carries an explicit one. Xcode names
1201
+ * copy-files and shell-script phases; the standard phases derive their
1202
+ * names from their isa.
1203
+ */
1204
+ get name() {
1205
+ return this.getString("name");
1206
+ }
1207
+ /**
1208
+ * Ids of the phase's `PBXBuildFile` entries, in build order.
1209
+ */
1210
+ get buildFileIds() {
1211
+ return stringItems(this.properties["files"]);
1212
+ }
1213
+ /**
1214
+ * Whether the phase already lists the build file.
1215
+ */
1216
+ containsBuildFile(buildFileId) {
1217
+ return this.buildFileIds.includes(buildFileId);
1218
+ }
1219
+ /**
1220
+ * Appends an existing build file to the phase unless it is already
1221
+ * listed.
1222
+ */
1223
+ appendBuildFile(buildFileId) {
1224
+ const files = ensureArray(this.properties, "files");
1225
+ if (!files.includes(buildFileId)) files.push(buildFileId);
1226
+ }
1227
+ /**
1228
+ * Removes a build file from the phase. Removing an id the phase does not
1229
+ * list is a no-op.
1230
+ */
1231
+ removeBuildFile(buildFileId) {
1232
+ const files = this.properties["files"];
1233
+ if (Array.isArray(files)) this.properties["files"] = files.filter((file) => file !== buildFileId);
1234
+ }
1235
+ /**
1236
+ * Ensures the phase carries a `PBXBuildFile` for the referenced object,
1237
+ * creating one when no existing build file in this phase points at it.
1238
+ *
1239
+ * @param reference The file reference or package product the build file
1240
+ * should point at.
1241
+ * @param options.referenceKey Which build-file field carries the
1242
+ * reference: `fileRef` for file references (the default) or
1243
+ * `productRef` for Swift package products.
1244
+ * @param options.settings Optional per-file settings, for example
1245
+ * `{ ATTRIBUTES: ["RemoveHeadersOnCopy"] }`.
1246
+ * @returns The view of the phase's build file for the reference.
1247
+ */
1248
+ ensureBuildFile(reference, options = {}) {
1249
+ const referenceKey = options.referenceKey ?? "fileRef";
1250
+ for (const buildFileId of this.buildFileIds) {
1251
+ const existing = this.project.get(buildFileId);
1252
+ if (existing?.getString(referenceKey) === reference.id) return existing;
1253
+ }
1254
+ const buildFile = this.project.add(Isa.buildFile, {
1255
+ [referenceKey]: reference.id,
1256
+ ...options.settings == null ? {} : { settings: options.settings }
1257
+ }, `${Isa.buildFile} ${this.id} ${reference.id}`);
1258
+ this.appendBuildFile(buildFile.id);
1259
+ return buildFile;
1260
+ }
1261
+ };
1262
+ /**
1263
+ * A `PBXFileSystemSynchronizedRootGroup` is an Xcode 16 folder whose
1264
+ * members are synchronized from disk instead of listed individually.
1265
+ */
1266
+ var SyncRootGroup = class extends XcodeObject {
1267
+ /**
1268
+ * The group's on-disk folder path, when present.
1269
+ */
1270
+ get path() {
1271
+ return this.getString("path");
1272
+ }
1273
+ /**
1274
+ * Excludes files from a target's membership in this synchronized folder
1275
+ * through a `PBXFileSystemSynchronizedBuildFileExceptionSet` linked into
1276
+ * the group's exceptions.
1277
+ *
1278
+ * Xcode keeps one exception set per target and folder, so when this
1279
+ * group already carries a set for the target, the file names merge into
1280
+ * it instead of creating a second set; names already excluded are not
1281
+ * duplicated.
1282
+ *
1283
+ * The standard use is keeping a scaffolded `Info.plist` from being
1284
+ * double-copied: the build already processes it through the target's
1285
+ * `INFOPLIST_FILE` setting.
1286
+ *
1287
+ * @param target The target whose membership the exceptions restrict.
1288
+ * @param membershipExceptions File names inside the folder to exclude.
1289
+ * @returns The view of the target's exception set for this folder.
1290
+ */
1291
+ addMembershipExceptions(target, membershipExceptions) {
1292
+ for (const id of stringItems(this.properties["exceptions"])) {
1293
+ const existing = this.project.get(id);
1294
+ if (existing?.isa === Isa.fileSystemSynchronizedBuildFileExceptionSet && existing.getString("target") === target.id) {
1295
+ const names = ensureArray(existing.properties, "membershipExceptions");
1296
+ for (const name of membershipExceptions) if (!names.includes(name)) names.push(name);
1297
+ return existing;
1298
+ }
1299
+ }
1300
+ const exceptionSet = this.project.add(Isa.fileSystemSynchronizedBuildFileExceptionSet, {
1301
+ membershipExceptions,
1302
+ target: target.id
1303
+ }, `${Isa.fileSystemSynchronizedBuildFileExceptionSet} ${this.id} ${target.id}`);
1304
+ ensureArray(this.properties, "exceptions").push(exceptionSet.id);
1305
+ return exceptionSet;
1306
+ }
1307
+ };
1308
+ /**
1309
+ * A `PBXBuildRule` tells a target which compiler or script processes a
1310
+ * kind of file.
1311
+ */
1312
+ var BuildRule = class extends XcodeObject {
1313
+ /**
1314
+ * The rule's script, when it is a script rule rather than a reference
1315
+ * to a compiler specification.
1316
+ */
1317
+ get script() {
1318
+ return this.getString("script");
1319
+ }
1320
+ };
1321
+ /**
1322
+ * An `XCVersionGroup` contains a versioned Core Data model
1323
+ * (`.xcdatamodeld`). Its children are the model versions and
1324
+ * `currentVersion` names the active one.
1325
+ */
1326
+ var VersionGroup = class extends Group {
1327
+ /**
1328
+ * The view of the active model version's file reference, when the group
1329
+ * names one.
1330
+ */
1331
+ currentVersion() {
1332
+ const id = this.getString("currentVersion");
1333
+ return id == null ? void 0 : this.project.get(id);
1334
+ }
1335
+ /**
1336
+ * Makes a model version the active one, adding it to the group's
1337
+ * children when it is not listed yet.
1338
+ */
1339
+ setCurrentVersion(reference) {
1340
+ this.addChild(reference);
1341
+ this.properties["currentVersion"] = reference.id;
1342
+ }
1343
+ };
1344
+ /**
1345
+ * A `PBXReferenceProxy` stands in for a product built by a target of
1346
+ * another project referenced from this one.
1347
+ */
1348
+ var ReferenceProxy = class extends XcodeObject {
1349
+ /**
1350
+ * The proxy's product path inside the other project's build directory,
1351
+ * when present.
1352
+ */
1353
+ get path() {
1354
+ return this.getString("path");
1355
+ }
1356
+ /**
1357
+ * The view of the container item proxy that names the remote target,
1358
+ * when the reference resolves.
1359
+ */
1360
+ remoteReference() {
1361
+ const id = this.getString("remoteRef");
1362
+ return id == null ? void 0 : this.project.get(id);
1363
+ }
1364
+ };
1365
+ //#endregion
730
1366
  //#region src/escape.ts
731
1367
  /**
732
1368
  * Escape handling for quoted strings.
@@ -1004,11 +1640,11 @@ const IS_LITERAL_CHAR = (() => {
1004
1640
  for (const ch of "_$/:.-") table[ch.charCodeAt(0)] = 1;
1005
1641
  return table;
1006
1642
  })();
1007
- const CODE_TAB = 9;
1008
- const CODE_LINE_FEED = 10;
1009
- const CODE_CARRIAGE_RETURN = 13;
1010
- const CODE_SPACE = 32;
1011
- const CODE_QUOTE = 34;
1643
+ const CODE_TAB$1 = 9;
1644
+ const CODE_LINE_FEED$1 = 10;
1645
+ const CODE_CARRIAGE_RETURN$1 = 13;
1646
+ const CODE_SPACE$1 = 32;
1647
+ const CODE_QUOTE$1 = 34;
1012
1648
  const CODE_SINGLE_QUOTE = 39;
1013
1649
  const CODE_OPEN_PAREN = 40;
1014
1650
  const CODE_CLOSE_PAREN = 41;
@@ -1016,13 +1652,13 @@ const CODE_ASTERISK = 42;
1016
1652
  const CODE_COMMA = 44;
1017
1653
  const CODE_MINUS = 45;
1018
1654
  const CODE_DOT = 46;
1019
- const CODE_SLASH = 47;
1655
+ const CODE_SLASH$1 = 47;
1020
1656
  const CODE_ZERO = 48;
1021
1657
  const CODE_NINE = 57;
1022
1658
  const CODE_SEMICOLON = 59;
1023
- const CODE_LESS_THAN = 60;
1024
- const CODE_EQUALS = 61;
1025
- const CODE_GREATER_THAN = 62;
1659
+ const CODE_LESS_THAN$1 = 60;
1660
+ const CODE_EQUALS$1 = 61;
1661
+ const CODE_GREATER_THAN$1 = 62;
1026
1662
  const CODE_BACKSLASH = 92;
1027
1663
  const CODE_OPEN_BRACE = 123;
1028
1664
  const CODE_CLOSE_BRACE = 125;
@@ -1034,10 +1670,10 @@ const CODE_CLOSE_BRACE = 125;
1034
1670
  */
1035
1671
  const IS_WHITESPACE = (() => {
1036
1672
  const table = /* @__PURE__ */ new Uint8Array(256);
1037
- table[CODE_SPACE] = 1;
1038
- table[CODE_TAB] = 1;
1039
- table[CODE_CARRIAGE_RETURN] = 1;
1040
- table[CODE_LINE_FEED] = 1;
1673
+ table[CODE_SPACE$1] = 1;
1674
+ table[CODE_TAB$1] = 1;
1675
+ table[CODE_CARRIAGE_RETURN$1] = 1;
1676
+ table[CODE_LINE_FEED$1] = 1;
1041
1677
  return table;
1042
1678
  })();
1043
1679
  /**
@@ -1053,7 +1689,7 @@ function isDigit(code) {
1053
1689
  * through the `read*` and `parse*` methods; there is no separate tokenizer
1054
1690
  * stage and no token objects.
1055
1691
  */
1056
- var Parser = class {
1692
+ var Parser$1 = class {
1057
1693
  /** Source text of the document being parsed. */
1058
1694
  input;
1059
1695
  /** Cursor position as a UTF-16 code unit offset into {@link input}. */
@@ -1100,7 +1736,7 @@ var Parser = class {
1100
1736
  const length = input.length;
1101
1737
  let pos = this.pos;
1102
1738
  while (pos < length && IS_WHITESPACE[input.charCodeAt(pos)] === 1) pos++;
1103
- if (pos < length && input.charCodeAt(pos) === CODE_SLASH) pos = this.skipCommentedTrivia(pos);
1739
+ if (pos < length && input.charCodeAt(pos) === CODE_SLASH$1) pos = this.skipCommentedTrivia(pos);
1104
1740
  this.pos = pos;
1105
1741
  }
1106
1742
  /**
@@ -1120,7 +1756,7 @@ var Parser = class {
1120
1756
  const length = input.length;
1121
1757
  for (;;) {
1122
1758
  const next = input.charCodeAt(pos + 1);
1123
- if (next === CODE_SLASH) {
1759
+ if (next === CODE_SLASH$1) {
1124
1760
  const lineEnd = input.indexOf("\n", pos + 2);
1125
1761
  pos = lineEnd === -1 ? length : lineEnd;
1126
1762
  } else if (next === CODE_ASTERISK) {
@@ -1132,7 +1768,7 @@ var Parser = class {
1132
1768
  pos = commentEnd + 2;
1133
1769
  } else return pos;
1134
1770
  while (pos < length && IS_WHITESPACE[input.charCodeAt(pos)] === 1) pos++;
1135
- if (pos >= length || input.charCodeAt(pos) !== CODE_SLASH) return pos;
1771
+ if (pos >= length || input.charCodeAt(pos) !== CODE_SLASH$1) return pos;
1136
1772
  }
1137
1773
  }
1138
1774
  /**
@@ -1215,7 +1851,7 @@ var Parser = class {
1215
1851
  const input = this.input;
1216
1852
  const length = input.length;
1217
1853
  const start = ++this.pos;
1218
- while (this.pos < length && input.charCodeAt(this.pos) !== CODE_GREATER_THAN) this.pos++;
1854
+ while (this.pos < length && input.charCodeAt(this.pos) !== CODE_GREATER_THAN$1) this.pos++;
1219
1855
  if (this.pos >= length) this.fail("Unterminated data run", start - 1);
1220
1856
  let hex = "";
1221
1857
  for (let i = start; i < this.pos; i++) {
@@ -1258,10 +1894,10 @@ var Parser = class {
1258
1894
  return result;
1259
1895
  }
1260
1896
  let key;
1261
- if (code === CODE_QUOTE || code === CODE_SINGLE_QUOTE) key = this.readQuotedString();
1897
+ if (code === CODE_QUOTE$1 || code === CODE_SINGLE_QUOTE) key = this.readQuotedString();
1262
1898
  else if (IS_LITERAL_CHAR[code] === 1) key = this.readLiteral();
1263
1899
  else this.fail(`Expected a key but found '${input[this.pos]}'`);
1264
- this.expect(CODE_EQUALS, "=");
1900
+ this.expect(CODE_EQUALS$1, "=");
1265
1901
  const value = this.parseValue();
1266
1902
  this.expect(CODE_SEMICOLON, ";");
1267
1903
  if (key === "__proto__") Object.defineProperty(result, key, {
@@ -1314,9 +1950,9 @@ var Parser = class {
1314
1950
  const code = this.input.charCodeAt(this.pos);
1315
1951
  if (code === CODE_OPEN_BRACE) return this.parseObject();
1316
1952
  if (code === CODE_OPEN_PAREN) return this.parseArray();
1317
- if (code === CODE_QUOTE || code === CODE_SINGLE_QUOTE) return this.readQuotedString();
1953
+ if (code === CODE_QUOTE$1 || code === CODE_SINGLE_QUOTE) return this.readQuotedString();
1318
1954
  if (IS_LITERAL_CHAR[code] === 1) return interpretLiteral(this.readLiteral());
1319
- if (code === CODE_LESS_THAN) return this.readData();
1955
+ if (code === CODE_LESS_THAN$1) return this.readData();
1320
1956
  this.fail(`Expected a value but found '${this.input[this.pos]}'`);
1321
1957
  }
1322
1958
  };
@@ -1370,7 +2006,1918 @@ function interpretLiteral(literal) {
1370
2006
  * carries the line and column of the failure.
1371
2007
  */
1372
2008
  function parsePbxproj(text) {
1373
- return new Parser(text).parseDocument();
2009
+ return new Parser$1(text).parseDocument();
2010
+ }
2011
+ //#endregion
2012
+ //#region src/md5.ts
2013
+ /**
2014
+ * Embedded MD5, implemented from RFC 1321.
2015
+ *
2016
+ * Deterministic object ids hash their seed text (see `uuid.ts`), and the
2017
+ * library runs in every JavaScript runtime without depending on a crypto
2018
+ * module, so the digest is implemented here. MD5 is used strictly as a
2019
+ * stable text-to-bits mapping for identifier generation; nothing security
2020
+ * relevant derives from it.
2021
+ *
2022
+ * @module
2023
+ */
2024
+ /**
2025
+ * Per-round left-rotation amounts, in the order the 64 steps apply them
2026
+ * (RFC 1321 section 3.4).
2027
+ */
2028
+ const SHIFTS = [
2029
+ 7,
2030
+ 12,
2031
+ 17,
2032
+ 22,
2033
+ 7,
2034
+ 12,
2035
+ 17,
2036
+ 22,
2037
+ 7,
2038
+ 12,
2039
+ 17,
2040
+ 22,
2041
+ 7,
2042
+ 12,
2043
+ 17,
2044
+ 22,
2045
+ 5,
2046
+ 9,
2047
+ 14,
2048
+ 20,
2049
+ 5,
2050
+ 9,
2051
+ 14,
2052
+ 20,
2053
+ 5,
2054
+ 9,
2055
+ 14,
2056
+ 20,
2057
+ 5,
2058
+ 9,
2059
+ 14,
2060
+ 20,
2061
+ 4,
2062
+ 11,
2063
+ 16,
2064
+ 23,
2065
+ 4,
2066
+ 11,
2067
+ 16,
2068
+ 23,
2069
+ 4,
2070
+ 11,
2071
+ 16,
2072
+ 23,
2073
+ 4,
2074
+ 11,
2075
+ 16,
2076
+ 23,
2077
+ 6,
2078
+ 10,
2079
+ 15,
2080
+ 21,
2081
+ 6,
2082
+ 10,
2083
+ 15,
2084
+ 21,
2085
+ 6,
2086
+ 10,
2087
+ 15,
2088
+ 21,
2089
+ 6,
2090
+ 10,
2091
+ 15,
2092
+ 21
2093
+ ];
2094
+ /**
2095
+ * Step constants for the 64 steps: the integer parts of
2096
+ * `abs(sin(i + 1)) * 2^32`, as RFC 1321 section 3.4 tabulates them. The
2097
+ * values are fixed by the specification rather than derived through
2098
+ * `Math.sin` at load, because the digest feeds deterministic identifiers
2099
+ * and floating-point transcendentals are the one spot where runtimes could
2100
+ * legally disagree.
2101
+ */
2102
+ const SINES = new Uint32Array([
2103
+ 3614090360,
2104
+ 3905402710,
2105
+ 606105819,
2106
+ 3250441966,
2107
+ 4118548399,
2108
+ 1200080426,
2109
+ 2821735955,
2110
+ 4249261313,
2111
+ 1770035416,
2112
+ 2336552879,
2113
+ 4294925233,
2114
+ 2304563134,
2115
+ 1804603682,
2116
+ 4254626195,
2117
+ 2792965006,
2118
+ 1236535329,
2119
+ 4129170786,
2120
+ 3225465664,
2121
+ 643717713,
2122
+ 3921069994,
2123
+ 3593408605,
2124
+ 38016083,
2125
+ 3634488961,
2126
+ 3889429448,
2127
+ 568446438,
2128
+ 3275163606,
2129
+ 4107603335,
2130
+ 1163531501,
2131
+ 2850285829,
2132
+ 4243563512,
2133
+ 1735328473,
2134
+ 2368359562,
2135
+ 4294588738,
2136
+ 2272392833,
2137
+ 1839030562,
2138
+ 4259657740,
2139
+ 2763975236,
2140
+ 1272893353,
2141
+ 4139469664,
2142
+ 3200236656,
2143
+ 681279174,
2144
+ 3936430074,
2145
+ 3572445317,
2146
+ 76029189,
2147
+ 3654602809,
2148
+ 3873151461,
2149
+ 530742520,
2150
+ 3299628645,
2151
+ 4096336452,
2152
+ 1126891415,
2153
+ 2878612391,
2154
+ 4237533241,
2155
+ 1700485571,
2156
+ 2399980690,
2157
+ 4293915773,
2158
+ 2240044497,
2159
+ 1873313359,
2160
+ 4264355552,
2161
+ 2734768916,
2162
+ 1309151649,
2163
+ 4149444226,
2164
+ 3174756917,
2165
+ 718787259,
2166
+ 3951481745
2167
+ ]);
2168
+ /**
2169
+ * Encodes text as UTF-8 bytes with RFC 1321 padding applied: a `0x80`
2170
+ * terminator, zero fill to 56 bytes mod 64, then the bit length as a
2171
+ * little-endian 64-bit integer.
2172
+ */
2173
+ function paddedUtf8(text) {
2174
+ const bytes = new TextEncoder().encode(text);
2175
+ const paddedLength = (Math.floor((bytes.length + 8) / 64) + 1) * 64;
2176
+ const padded = new Uint8Array(paddedLength);
2177
+ padded.set(bytes);
2178
+ padded[bytes.length] = 128;
2179
+ const bitLength = bytes.length * 8;
2180
+ padded[paddedLength - 8] = bitLength & 255;
2181
+ padded[paddedLength - 7] = bitLength >>> 8 & 255;
2182
+ padded[paddedLength - 6] = bitLength >>> 16 & 255;
2183
+ padded[paddedLength - 5] = bitLength >>> 24 & 255;
2184
+ return padded;
2185
+ }
2186
+ /**
2187
+ * Adds two 32-bit values with wraparound, keeping intermediates inside the
2188
+ * 32-bit range JavaScript bitwise operators preserve. `Math.trunc` is not
2189
+ * a substitute here: the bitwise coercion is what performs the modular
2190
+ * wrap the algorithm requires.
2191
+ */
2192
+ function add32(a, b) {
2193
+ return a + b | 0;
2194
+ }
2195
+ /**
2196
+ * Computes the MD5 digest of the text (as UTF-8) and returns it as 32
2197
+ * uppercase hexadecimal characters.
2198
+ *
2199
+ * Uppercase matches the identifier alphabet Xcode uses, which is what the
2200
+ * digest feeds (see `uuid.ts`).
2201
+ */
2202
+ function md5Hex(text) {
2203
+ const message = paddedUtf8(text);
2204
+ let a0 = 1732584193;
2205
+ let b0 = 4023233417;
2206
+ let c0 = 2562383102;
2207
+ let d0 = 271733878;
2208
+ const words = /* @__PURE__ */ new Uint32Array(16);
2209
+ for (let offset = 0; offset < message.length; offset += 64) {
2210
+ for (let i = 0; i < 16; i++) {
2211
+ const base = offset + i * 4;
2212
+ words[i] = message[base] | message[base + 1] << 8 | message[base + 2] << 16 | message[base + 3] << 24;
2213
+ }
2214
+ let a = a0;
2215
+ let b = b0;
2216
+ let c = c0;
2217
+ let d = d0;
2218
+ for (let step = 0; step < 64; step++) {
2219
+ let mix;
2220
+ let wordIndex;
2221
+ if (step < 16) {
2222
+ mix = b & c | ~b & d;
2223
+ wordIndex = step;
2224
+ } else if (step < 32) {
2225
+ mix = d & b | ~d & c;
2226
+ wordIndex = (5 * step + 1) % 16;
2227
+ } else if (step < 48) {
2228
+ mix = b ^ c ^ d;
2229
+ wordIndex = (3 * step + 5) % 16;
2230
+ } else {
2231
+ mix = c ^ (b | ~d);
2232
+ wordIndex = 7 * step % 16;
2233
+ }
2234
+ const rotated = add32(add32(a, mix), add32(SINES[step], words[wordIndex]));
2235
+ const shift = SHIFTS[step];
2236
+ const next = add32(b, rotated << shift | rotated >>> 32 - shift);
2237
+ a = d;
2238
+ d = c;
2239
+ c = b;
2240
+ b = next;
2241
+ }
2242
+ a0 = add32(a0, a);
2243
+ b0 = add32(b0, b);
2244
+ c0 = add32(c0, c);
2245
+ d0 = add32(d0, d);
2246
+ }
2247
+ let hex = "";
2248
+ for (const word of [
2249
+ a0,
2250
+ b0,
2251
+ c0,
2252
+ d0
2253
+ ]) for (let shift = 0; shift < 32; shift += 8) hex += (word >>> shift & 255).toString(16).toUpperCase().padStart(2, "0");
2254
+ return hex;
2255
+ }
2256
+ //#endregion
2257
+ //#region src/uuid.ts
2258
+ /**
2259
+ * Deterministic object identifiers for generated pbxproj objects.
2260
+ *
2261
+ * Xcode identifies every object with 24 hexadecimal characters. Generated
2262
+ * ids here are deterministic: the same seed always produces the same id, so
2263
+ * programmatic edits are reproducible and diffs stay minimal across runs.
2264
+ * The format is `XX` + the first 20 characters of `md5(seed)` + `XX`, which
2265
+ * is a valid identifier that remains recognizable as generated.
2266
+ *
2267
+ * @module
2268
+ */
2269
+ /**
2270
+ * Formats the deterministic id for a seed without collision handling.
2271
+ */
2272
+ function idForSeed(seed) {
2273
+ return `XX${md5Hex(seed).slice(0, 20)}XX`;
1374
2274
  }
2275
+ /**
2276
+ * Generates a deterministic 24-character object id from a seed string.
2277
+ *
2278
+ * When the id already exists in `existing`, the seed is retried with a
2279
+ * space appended until it is free, so ids stay deterministic (the same
2280
+ * seeds in the same order produce the same ids) while never colliding
2281
+ * within a document.
2282
+ *
2283
+ * @param seed Any text that identifies the object being created, for
2284
+ * example `PBXNativeTarget DemoWidget`.
2285
+ * @param existing Ids already present in the document.
2286
+ * @returns An id not contained in `existing`; the caller records it.
2287
+ */
2288
+ function generateObjectId(seed, existing) {
2289
+ let currentSeed = seed;
2290
+ for (;;) {
2291
+ const id = idForSeed(currentSeed);
2292
+ if (!existing.has(id)) return id;
2293
+ currentSeed += " ";
2294
+ }
2295
+ }
2296
+ //#endregion
2297
+ //#region src/model/doctor.ts
2298
+ /**
2299
+ * Structural validation and cleanup for project documents.
2300
+ *
2301
+ * Real projects accumulate damage. References outlive the objects they
2302
+ * point at, orphans pile up, and entries lose their kind. Xcode tolerates
2303
+ * some of that silently and breaks on the rest. {@link validateProject}
2304
+ * reports the problems, and {@link pruneOrphanObjects} removes the
2305
+ * unreachable objects.
2306
+ *
2307
+ * @module
2308
+ */
2309
+ /**
2310
+ * Object properties that hold a single reference to another object of the
2311
+ * same document. `remoteGlobalIDString` and `TestTargetID` are absent by
2312
+ * design. They reference objects of another container, so they cannot be
2313
+ * resolved here.
2314
+ */
2315
+ const SCALAR_REFERENCE_PROPERTIES = [
2316
+ "baseConfigurationReference",
2317
+ "buildConfigurationList",
2318
+ "buildPhase",
2319
+ "containerPortal",
2320
+ "currentVersion",
2321
+ "fileRef",
2322
+ "mainGroup",
2323
+ "package",
2324
+ "productRef",
2325
+ "productRefGroup",
2326
+ "productReference",
2327
+ "remoteRef",
2328
+ "target",
2329
+ "targetProxy"
2330
+ ];
2331
+ /**
2332
+ * Object properties that hold a list of references to other objects of the
2333
+ * same document.
2334
+ */
2335
+ const LIST_REFERENCE_PROPERTIES = [
2336
+ "buildConfigurations",
2337
+ "buildPhases",
2338
+ "buildRules",
2339
+ "children",
2340
+ "dependencies",
2341
+ "exceptions",
2342
+ "files",
2343
+ "fileSystemSynchronizedGroups",
2344
+ "packageProductDependencies",
2345
+ "packageReferences",
2346
+ "targets"
2347
+ ];
2348
+ /**
2349
+ * Collects the ids of every object reachable from the document's root
2350
+ * object.
2351
+ *
2352
+ * Reachability follows every string that names an existing object,
2353
+ * anywhere in an object's properties (including nested dictionaries, their
2354
+ * keys, and arrays). This is deliberately broader than the known reference
2355
+ * schema. An unknown-but-real reference keeps its object alive, so pruning
2356
+ * stays conservative.
2357
+ */
2358
+ function reachableObjectIds(project) {
2359
+ const reachable = /* @__PURE__ */ new Set();
2360
+ const rootId = project.document["rootObject"];
2361
+ if (typeof rootId !== "string" || project.propertiesOfOptional(rootId) == null) return reachable;
2362
+ const pending = [rootId];
2363
+ const visitValue = (value) => {
2364
+ if (typeof value === "string") {
2365
+ if (!reachable.has(value) && project.propertiesOfOptional(value) != null) pending.push(value);
2366
+ return;
2367
+ }
2368
+ if (Array.isArray(value)) {
2369
+ for (const item of value) visitValue(item);
2370
+ return;
2371
+ }
2372
+ const nested = asDictionary(value);
2373
+ if (nested != null) for (const key of Object.keys(nested)) {
2374
+ visitValue(key);
2375
+ visitValue(nested[key]);
2376
+ }
2377
+ };
2378
+ while (pending.length > 0) {
2379
+ const id = pending.pop();
2380
+ if (reachable.has(id)) continue;
2381
+ reachable.add(id);
2382
+ const properties = project.propertiesOfOptional(id);
2383
+ if (properties != null) for (const key of Object.keys(properties)) visitValue(properties[key]);
2384
+ }
2385
+ return reachable;
2386
+ }
2387
+ /**
2388
+ * Reports one dangling reference when the id does not resolve.
2389
+ */
2390
+ function checkReference(project, issues, objectId, property, id) {
2391
+ if (project.propertiesOfOptional(id) == null) issues.push({
2392
+ kind: "dangling-reference",
2393
+ message: `${objectId}.${property} references ${id}, which does not exist in the document`,
2394
+ objectId
2395
+ });
2396
+ }
2397
+ /**
2398
+ * Validates the document's object graph and returns every problem found.
2399
+ * An empty array means the graph is structurally sound. The checks cover
2400
+ * the root object, object kinds, the known reference schema, and
2401
+ * reachability. See {@link ProjectIssueKind} for the meanings.
2402
+ */
2403
+ function validateProject(project) {
2404
+ const issues = [];
2405
+ const rootId = project.document["rootObject"];
2406
+ const rootProperties = typeof rootId === "string" ? project.propertiesOfOptional(rootId) : void 0;
2407
+ if (rootProperties == null || rootProperties["isa"] !== "PBXProject") issues.push({
2408
+ kind: "dangling-root",
2409
+ message: typeof rootId === "string" ? `rootObject references ${rootId}, which is not a PBXProject in the document` : "The document has no rootObject"
2410
+ });
2411
+ for (const [id, view] of project.objects()) {
2412
+ const properties = view.properties;
2413
+ if (view.isa === "") issues.push({
2414
+ kind: "missing-isa",
2415
+ message: `${id} has no isa, so Xcode cannot read it`,
2416
+ objectId: id
2417
+ });
2418
+ for (const property of SCALAR_REFERENCE_PROPERTIES) {
2419
+ const value = properties[property];
2420
+ if (typeof value === "string") checkReference(project, issues, id, property, value);
2421
+ }
2422
+ for (const property of LIST_REFERENCE_PROPERTIES) {
2423
+ const value = properties[property];
2424
+ const items = Array.isArray(value) ? value : [];
2425
+ for (const item of items) if (typeof item === "string") checkReference(project, issues, id, property, item);
2426
+ }
2427
+ }
2428
+ const reachable = reachableObjectIds(project);
2429
+ if (reachable.size > 0) {
2430
+ for (const [id] of project.objects()) if (!reachable.has(id)) issues.push({
2431
+ kind: "unreachable-object",
2432
+ message: `${id} is unreachable from the root object`,
2433
+ objectId: id
2434
+ });
2435
+ }
2436
+ return issues;
2437
+ }
2438
+ /**
2439
+ * Removes every object unreachable from the document's root object and
2440
+ * returns the removed ids, in document order. A document whose root is
2441
+ * missing prunes nothing, since reachability is undefined there.
2442
+ *
2443
+ * Reachability is the conservative walk of {@link reachableObjectIds}.
2444
+ * Any real reference keeps an object alive, even when the reference
2445
+ * property is outside the known schema.
2446
+ */
2447
+ function pruneOrphanObjects(project) {
2448
+ const reachable = reachableObjectIds(project);
2449
+ if (reachable.size === 0) return [];
2450
+ const orphanIds = [];
2451
+ for (const [id] of project.objects()) if (!reachable.has(id)) orphanIds.push(id);
2452
+ for (const id of orphanIds) project.removeObject(id);
2453
+ return orphanIds;
2454
+ }
2455
+ //#endregion
2456
+ //#region src/model/settings.ts
2457
+ /**
2458
+ * Build-configuration helpers shared by targets and the root project
2459
+ * object, both of which own an `XCConfigurationList`.
2460
+ *
2461
+ * @module
2462
+ */
2463
+ /**
2464
+ * The views of a configuration list's build configurations, in list order.
2465
+ * Dangling ids and non-dictionary entries of malformed documents are
2466
+ * skipped.
2467
+ */
2468
+ function configurationsOf(project, configurationListId) {
2469
+ const list = asDictionary(project.propertiesOfOptional(configurationListId));
2470
+ const configurations = [];
2471
+ for (const id of stringItems(list?.["buildConfigurations"])) {
2472
+ const configuration = project.get(id);
2473
+ if (configuration != null) configurations.push(configuration);
2474
+ }
2475
+ return configurations;
2476
+ }
2477
+ /**
2478
+ * The settings dictionary of a configuration list's default configuration:
2479
+ * the one named by `defaultConfigurationName`, falling back to the first
2480
+ * configuration. Returns `undefined` when the list has no configurations
2481
+ * or the default carries no settings dictionary.
2482
+ */
2483
+ function defaultConfigurationSettingsOf(project, configurationListId) {
2484
+ const list = asDictionary(project.propertiesOfOptional(configurationListId));
2485
+ const configurations = configurationsOf(project, configurationListId);
2486
+ const defaultName = asString(list?.["defaultConfigurationName"]);
2487
+ return asDictionary((configurations.find((configuration) => configuration.getString("name") === defaultName) ?? configurations[0])?.properties["buildSettings"]);
2488
+ }
2489
+ //#endregion
2490
+ //#region src/model/target.ts
2491
+ /**
2492
+ * Views of the project's targets. The behavior all target kinds share
2493
+ * lives on the {@link Target} base class, and {@link NativeTarget}
2494
+ * extends it with products, embedding, synchronized folders, Swift
2495
+ * packages, and system frameworks.
2496
+ *
2497
+ * Reads are deliberately soft: user-generated projects can be malformed,
2498
+ * so lookups return `undefined` instead of throwing wherever a document
2499
+ * could legally or illegally omit something. Mutations create any missing
2500
+ * structure they need.
2501
+ *
2502
+ * @module
2503
+ */
2504
+ /**
2505
+ * The behavior every target kind shares. A target of any kind carries
2506
+ * build configurations and settings, build phases, and dependencies, and
2507
+ * this class holds their accessors and mutations. `PBXNativeTarget`,
2508
+ * `PBXAggregateTarget`, and `PBXLegacyTarget` all extend it, so code that
2509
+ * walks or rewires targets can accept any of them.
2510
+ */
2511
+ var Target = class extends XcodeObject {
2512
+ /**
2513
+ * The target's name, when present.
2514
+ */
2515
+ get name() {
2516
+ return this.getString("name");
2517
+ }
2518
+ /**
2519
+ * The views of the target's build configurations, in list order.
2520
+ */
2521
+ buildConfigurations() {
2522
+ return configurationsOf(this.project, this.getString("buildConfigurationList"));
2523
+ }
2524
+ /**
2525
+ * The settings dictionary of the target's default configuration: the one
2526
+ * named by the list's `defaultConfigurationName`, falling back to the
2527
+ * first configuration. Returns `undefined` when the target has no
2528
+ * configurations or the default carries no settings dictionary.
2529
+ */
2530
+ defaultConfigurationSettings() {
2531
+ return defaultConfigurationSettingsOf(this.project, this.getString("buildConfigurationList"));
2532
+ }
2533
+ /**
2534
+ * Reads a build setting from the target's default configuration,
2535
+ * inheriting from the project-level configuration when the target omits
2536
+ * the key. This mirrors how Xcode resolves settings hierarchically;
2537
+ * generated app templates set values like `SDKROOT` only at the project
2538
+ * level.
2539
+ *
2540
+ * Only string values are returned; a list- or number-valued setting reads
2541
+ * as `undefined`.
2542
+ */
2543
+ getBuildSetting(key) {
2544
+ const targetSettings = this.defaultConfigurationSettings();
2545
+ if (targetSettings != null && key in targetSettings) return asString(targetSettings[key]);
2546
+ return asString(this.project.rootProject.defaultConfigurationSettings()?.[key]);
2547
+ }
2548
+ /**
2549
+ * Writes a build setting on every configuration of the target, so Debug
2550
+ * and Release stay consistent.
2551
+ */
2552
+ setBuildSetting(key, value) {
2553
+ for (const configuration of this.buildConfigurations()) {
2554
+ const settings = asDictionary(configuration.properties["buildSettings"]);
2555
+ if (settings == null) configuration.properties["buildSettings"] = { [key]: value };
2556
+ else settings[key] = value;
2557
+ }
2558
+ }
2559
+ /**
2560
+ * Removes a build setting from every configuration of the target.
2561
+ */
2562
+ removeBuildSetting(key) {
2563
+ for (const configuration of this.buildConfigurations()) {
2564
+ const settings = asDictionary(configuration.properties["buildSettings"]);
2565
+ if (settings != null) delete settings[key];
2566
+ }
2567
+ }
2568
+ /**
2569
+ * The views of the target's dependency objects
2570
+ * (`PBXTargetDependency`), in declaration order. Resolve a dependency's
2571
+ * target through its `target` property.
2572
+ */
2573
+ dependencies() {
2574
+ return this.referencedViews("dependencies");
2575
+ }
2576
+ /**
2577
+ * The views of the target's build phases, in build order.
2578
+ */
2579
+ buildPhases() {
2580
+ const phases = [];
2581
+ for (const id of stringItems(this.properties["buildPhases"])) {
2582
+ const phase = this.project.get(id);
2583
+ if (phase instanceof BuildPhase) phases.push(phase);
2584
+ }
2585
+ return phases;
2586
+ }
2587
+ /**
2588
+ * Finds the target's first build phase with the given isa, and, when
2589
+ * `name` is provided, the given display name.
2590
+ */
2591
+ findBuildPhase(isa, name) {
2592
+ return this.buildPhases().find((phase) => phase.isa === isa && (name == null || phase.name === name));
2593
+ }
2594
+ /**
2595
+ * Returns the target's build phase with the given isa and properties,
2596
+ * creating and appending it when missing. The properties apply only on
2597
+ * creation; an existing phase is returned as is.
2598
+ *
2599
+ * The match key is the isa plus the `name` property when one is given,
2600
+ * so differently named copy-files phases coexist.
2601
+ */
2602
+ ensureBuildPhase(isa, properties = {}) {
2603
+ const name = asString(properties["name"]);
2604
+ const existing = this.findBuildPhase(isa, name);
2605
+ if (existing != null) return existing;
2606
+ const phase = this.project.add(isa, {
2607
+ isa,
2608
+ buildActionMask: 2147483647,
2609
+ files: [],
2610
+ runOnlyForDeploymentPostprocessing: 0,
2611
+ ...properties
2612
+ }, `${isa} ${this.id} ${name ?? ""}`);
2613
+ ensureArray(this.properties, "buildPhases").push(phase.id);
2614
+ return phase;
2615
+ }
2616
+ /**
2617
+ * The target's shell-script phase with the given name, created with the
2618
+ * usual defaults (`/bin/sh`, empty input and output lists) when missing.
2619
+ * The script and other properties apply only on creation.
2620
+ *
2621
+ * @param name Display name of the phase, which is also its match key.
2622
+ * @param properties Phase properties, most usefully `shellScript`.
2623
+ */
2624
+ ensureShellScriptPhase(name, properties = {}) {
2625
+ return this.ensureBuildPhase("PBXShellScriptBuildPhase", {
2626
+ inputFileListPaths: [],
2627
+ inputPaths: [],
2628
+ name,
2629
+ outputFileListPaths: [],
2630
+ outputPaths: [],
2631
+ shellPath: "/bin/sh",
2632
+ shellScript: "",
2633
+ ...properties
2634
+ });
2635
+ }
2636
+ /**
2637
+ * Adds a dependency on another target of the same project, wiring the
2638
+ * `PBXContainerItemProxy` and `PBXTargetDependency` pair Xcode uses to
2639
+ * express it. Adding an existing dependency is a no-op.
2640
+ *
2641
+ * @returns The view of the target dependency object.
2642
+ */
2643
+ addDependency(dependency) {
2644
+ for (const id of stringItems(this.properties["dependencies"])) {
2645
+ const existing = this.project.get(id);
2646
+ if (existing?.getString("target") === dependency.id) return existing;
2647
+ }
2648
+ const proxy = this.project.add(Isa.containerItemProxy, {
2649
+ containerPortal: this.project.rootProject.id,
2650
+ proxyType: 1,
2651
+ remoteGlobalIDString: dependency.id,
2652
+ remoteInfo: dependency.name ?? dependency.id
2653
+ }, `${Isa.containerItemProxy} ${this.id} ${dependency.id}`);
2654
+ const targetDependency = this.project.add(Isa.targetDependency, {
2655
+ target: dependency.id,
2656
+ targetProxy: proxy.id
2657
+ }, `${Isa.targetDependency} ${this.id} ${dependency.id}`);
2658
+ ensureArray(this.properties, "dependencies").push(targetDependency.id);
2659
+ return targetDependency;
2660
+ }
2661
+ };
2662
+ /**
2663
+ * A `PBXNativeTarget` is a product the project compiles and packages
2664
+ * itself, such as an application or an app extension.
2665
+ */
2666
+ var NativeTarget = class extends Target {
2667
+ /**
2668
+ * The target's product type identifier, for example
2669
+ * `com.apple.product-type.application`.
2670
+ */
2671
+ get productType() {
2672
+ return this.getString("productType");
2673
+ }
2674
+ /**
2675
+ * Rewrites the target's product type. Used by packaging repairs that
2676
+ * convert foundation extensions into ExtensionKit extensions.
2677
+ */
2678
+ set productType(value) {
2679
+ this.properties["productType"] = value;
2680
+ }
2681
+ /**
2682
+ * The view of the target's product file reference, when the target has
2683
+ * one.
2684
+ */
2685
+ get productReference() {
2686
+ const id = this.getString("productReference");
2687
+ return id == null ? void 0 : this.project.get(id);
2688
+ }
2689
+ /**
2690
+ * Whether the target builds for watchOS, decided by its product type or
2691
+ * its watchOS deployment-target setting.
2692
+ */
2693
+ isWatchOS() {
2694
+ if (this.productType === ProductType.watchApp) return true;
2695
+ const defaultSettings = this.defaultConfigurationSettings();
2696
+ return defaultSettings != null && "WATCHOS_DEPLOYMENT_TARGET" in defaultSettings;
2697
+ }
2698
+ /**
2699
+ * The views of the target's Swift package product dependencies, in
2700
+ * declaration order.
2701
+ */
2702
+ packageProductDependencies() {
2703
+ return this.referencedViews("packageProductDependencies");
2704
+ }
2705
+ /**
2706
+ * The views of the target's file-system-synchronized folders, in
2707
+ * declaration order.
2708
+ */
2709
+ syncGroups() {
2710
+ return this.referencedViews("fileSystemSynchronizedGroups").filter((view) => view instanceof SyncRootGroup);
2711
+ }
2712
+ /**
2713
+ * The views of the target's build rules, in evaluation order. Empty for
2714
+ * targets without custom rules, which is nearly all of them.
2715
+ */
2716
+ buildRules() {
2717
+ return this.referencedViews("buildRules").filter((view) => view instanceof BuildRule);
2718
+ }
2719
+ /**
2720
+ * The target's sources phase, created when missing.
2721
+ */
2722
+ ensureSourcesPhase() {
2723
+ return this.ensureBuildPhase(Isa.sourcesBuildPhase);
2724
+ }
2725
+ /**
2726
+ * The target's frameworks phase, created when missing.
2727
+ */
2728
+ ensureFrameworksPhase() {
2729
+ return this.ensureBuildPhase(Isa.frameworksBuildPhase);
2730
+ }
2731
+ /**
2732
+ * The target's resources phase, created when missing.
2733
+ */
2734
+ ensureResourcesPhase() {
2735
+ return this.ensureBuildPhase(Isa.resourcesBuildPhase);
2736
+ }
2737
+ /**
2738
+ * Embeds another target's product into this target through the
2739
+ * copy-files phase its product type calls for ("Embed Foundation
2740
+ * Extensions", "Embed App Clips", "Embed Watch Content", or "Embed
2741
+ * ExtensionKit Extensions").
2742
+ *
2743
+ * An existing phase with the same name is reused, its destination is
2744
+ * repaired to the type's canonical values, and the product's build file
2745
+ * is deduplicated, so embedding is idempotent.
2746
+ *
2747
+ * @returns The view of the embed phase, or `undefined` when the embedded
2748
+ * target has no product reference to embed.
2749
+ */
2750
+ embed(extension) {
2751
+ const product = extension.productReference;
2752
+ if (product == null) return;
2753
+ const destination = embedDestinationFor(extension.isWatchOS() ? ProductType.watchApp : extension.productType);
2754
+ const phase = this.ensureBuildPhase(Isa.copyFilesBuildPhase, { name: destination.phaseName });
2755
+ phase.set("dstPath", destination.dstPath);
2756
+ phase.set("dstSubfolderSpec", destination.dstSubfolderSpec);
2757
+ phase.ensureBuildFile(product, { settings: { ATTRIBUTES: ["RemoveHeadersOnCopy"] } });
2758
+ return phase;
2759
+ }
2760
+ /**
2761
+ * The on-disk folder paths of the file-system-synchronized groups linked
2762
+ * to this target. Empty for targets without synchronized folders
2763
+ * (projects predating Xcode 16).
2764
+ */
2765
+ syncGroupPaths() {
2766
+ const paths = [];
2767
+ for (const id of stringItems(this.properties["fileSystemSynchronizedGroups"])) {
2768
+ const group = this.project.get(id);
2769
+ const path = group instanceof SyncRootGroup ? group.path : void 0;
2770
+ if (path != null) paths.push(path);
2771
+ }
2772
+ return paths;
2773
+ }
2774
+ /**
2775
+ * Creates a file-system-synchronized folder for an on-disk path, links it
2776
+ * to this target, and registers it in the project's main group so Xcode
2777
+ * shows it in the navigator. When the target already links a folder with
2778
+ * the same path, that folder is returned instead, so re-running a
2779
+ * scaffold step cannot duplicate groups.
2780
+ *
2781
+ * @param path Folder path relative to the project root, for example the
2782
+ * target's name.
2783
+ * @returns The view of the target's synchronized folder for the path.
2784
+ */
2785
+ addSyncGroup(path) {
2786
+ for (const id of stringItems(this.properties["fileSystemSynchronizedGroups"])) {
2787
+ const existing = this.project.get(id);
2788
+ if (existing instanceof SyncRootGroup && existing.path === path) return existing;
2789
+ }
2790
+ const group = this.project.add(Isa.fileSystemSynchronizedRootGroup, {
2791
+ path,
2792
+ sourceTree: "<group>"
2793
+ }, `${Isa.fileSystemSynchronizedRootGroup} ${path}`);
2794
+ ensureArray(this.properties, "fileSystemSynchronizedGroups").push(group.id);
2795
+ this.project.rootProject.mainGroup()?.addChild(group);
2796
+ return group;
2797
+ }
2798
+ /**
2799
+ * Links a Swift package product to this target. It creates the
2800
+ * `XCSwiftPackageProductDependency`, registers it on the target, and
2801
+ * ensures the frameworks phase carries its build file. Linking an
2802
+ * already linked product is a no-op.
2803
+ *
2804
+ * @param options.productName The product to link, as the package
2805
+ * manifest names it.
2806
+ * @param options.packageReference The package reference view returned by
2807
+ * {@link XcodeProject.addSwiftPackage} or
2808
+ * {@link XcodeProject.findSwiftPackage}.
2809
+ * @returns The view of the product dependency.
2810
+ */
2811
+ addSwiftPackageProduct(options) {
2812
+ for (const id of stringItems(this.properties["packageProductDependencies"])) {
2813
+ const existing = this.project.get(id);
2814
+ if (existing?.getString("productName") === options.productName && existing.getString("package") === options.packageReference.id) return existing;
2815
+ }
2816
+ const productDependency = this.project.add(Isa.swiftPackageProductDependency, {
2817
+ package: options.packageReference.id,
2818
+ productName: options.productName
2819
+ }, `${Isa.swiftPackageProductDependency} ${this.id} ${options.productName}`);
2820
+ ensureArray(this.properties, "packageProductDependencies").push(productDependency.id);
2821
+ this.ensureFrameworksPhase().ensureBuildFile(productDependency, { referenceKey: "productRef" });
2822
+ return productDependency;
2823
+ }
2824
+ /**
2825
+ * Links a system framework (for example `Messages`) to this target. It
2826
+ * reuses or creates the file reference under the SDK's frameworks
2827
+ * directory and makes sure the frameworks phase carries its build file.
2828
+ * Linking an already linked framework is a no-op.
2829
+ *
2830
+ * @param name Framework name without the `.framework` suffix.
2831
+ * @returns The view of the framework's file reference.
2832
+ */
2833
+ addSystemFramework(name) {
2834
+ const path = `System/Library/Frameworks/${name}.framework`;
2835
+ let reference;
2836
+ for (const [, view] of this.project.objects()) if (view.isa === Isa.fileReference && view.getString("path") === path) {
2837
+ reference = view;
2838
+ break;
2839
+ }
2840
+ reference ??= this.project.add(Isa.fileReference, {
2841
+ lastKnownFileType: "wrapper.framework",
2842
+ name: `${name}.framework`,
2843
+ path,
2844
+ sourceTree: "SDKROOT"
2845
+ }, `${Isa.fileReference} ${path}`);
2846
+ this.ensureFrameworksPhase().ensureBuildFile(reference);
2847
+ return reference;
2848
+ }
2849
+ };
2850
+ /**
2851
+ * An aggregate target produces nothing itself. It exists to group other
2852
+ * targets through its dependencies and to run script or copy-files
2853
+ * phases, and the shared target surface covers everything it carries.
2854
+ */
2855
+ var AggregateTarget = class extends Target {};
2856
+ /**
2857
+ * A legacy target shells out to an external build tool such as make
2858
+ * instead of using Xcode's build system.
2859
+ */
2860
+ var LegacyTarget = class extends Target {
2861
+ /**
2862
+ * The build tool the target invokes, as an absolute path.
2863
+ */
2864
+ get buildToolPath() {
2865
+ return this.getString("buildToolPath");
2866
+ }
2867
+ /**
2868
+ * The arguments passed to the build tool, as one shell-style string.
2869
+ */
2870
+ get buildArgumentsString() {
2871
+ return this.getString("buildArgumentsString");
2872
+ }
2873
+ /**
2874
+ * The working directory the build tool runs in, when the target sets
2875
+ * one.
2876
+ */
2877
+ get buildWorkingDirectory() {
2878
+ return this.getString("buildWorkingDirectory");
2879
+ }
2880
+ };
2881
+ //#endregion
2882
+ //#region src/model/project.ts
2883
+ /**
2884
+ * The project document model. It gives typed, mutable access to a parsed
2885
+ * `project.pbxproj`.
2886
+ *
2887
+ * The model is a set of lightweight views over the plain parsed document.
2888
+ * All state lives in the document itself; views hold only an id and a
2889
+ * project reference, so model mutations and direct dictionary writes
2890
+ * compose freely and {@link XcodeProject.build} always serializes the
2891
+ * current state. New objects receive deterministic identifiers (see
2892
+ * `uuid.ts`), so programmatic edits are reproducible run to run.
2893
+ *
2894
+ * @module
2895
+ */
2896
+ /**
2897
+ * The `PBXProject` object at the document root. It owns the target list,
2898
+ * the main group, and the project-level configurations.
2899
+ */
2900
+ var RootProject = class extends XcodeObject {
2901
+ /**
2902
+ * Ids of the project's targets, in project order.
2903
+ */
2904
+ targetIds() {
2905
+ return stringItems(this.properties["targets"]);
2906
+ }
2907
+ /**
2908
+ * The view of the project's main group, when the document has one. The
2909
+ * main group is the root of Xcode's navigator tree.
2910
+ */
2911
+ mainGroup() {
2912
+ const group = this.project.get(this.getString("mainGroup"));
2913
+ return group instanceof Group ? group : void 0;
2914
+ }
2915
+ /**
2916
+ * The settings dictionary of the project-level default configuration.
2917
+ * Targets inherit from these settings; see
2918
+ * {@link NativeTarget.getBuildSetting}.
2919
+ */
2920
+ defaultConfigurationSettings() {
2921
+ return defaultConfigurationSettingsOf(this.project, this.getString("buildConfigurationList"));
2922
+ }
2923
+ /**
2924
+ * The views of the project's Swift package references, remote and local,
2925
+ * in declaration order.
2926
+ */
2927
+ packageReferences() {
2928
+ return this.referencedViews("packageReferences");
2929
+ }
2930
+ /**
2931
+ * The group product references live in, creating it (and registering it
2932
+ * as the project's `productRefGroup`) when missing.
2933
+ */
2934
+ ensureProductsGroup() {
2935
+ const existing = this.project.get(this.getString("productRefGroup"));
2936
+ if (existing instanceof Group) return existing;
2937
+ const group = this.project.add(Isa.group, {
2938
+ children: [],
2939
+ name: "Products",
2940
+ sourceTree: "<group>"
2941
+ }, `${Isa.group} Products`);
2942
+ this.mainGroup()?.addChild(group);
2943
+ this.set("productRefGroup", group.id);
2944
+ return group;
2945
+ }
2946
+ };
2947
+ /**
2948
+ * A parsed `project.pbxproj` with typed, mutable access to its objects.
2949
+ *
2950
+ * ```ts
2951
+ * const project = XcodeProject.parse(pbxprojText);
2952
+ * const app = project.findMainAppTarget("ios");
2953
+ * app?.setBuildSetting("MARKETING_VERSION", "1.2.0");
2954
+ * const text = project.build();
2955
+ * ```
2956
+ */
2957
+ var XcodeProject = class XcodeProject {
2958
+ /** The parsed document this model wraps. */
2959
+ document;
2960
+ /** The document's `objects` dictionary. */
2961
+ objectsDictionary;
2962
+ /**
2963
+ * Identity map of object views, one view per id, created on first
2964
+ * access, so views of the same object compare with `===`.
2965
+ */
2966
+ views = /* @__PURE__ */ new Map();
2967
+ constructor(document) {
2968
+ const objects = asDictionary(document["objects"]);
2969
+ if (objects == null) throw new XcodeModelError("The document has no objects dictionary");
2970
+ this.document = document;
2971
+ this.objectsDictionary = objects;
2972
+ }
2973
+ /**
2974
+ * Parses pbxproj text and wraps it in a model.
2975
+ *
2976
+ * @throws PbxprojParseError when the text is malformed.
2977
+ * @throws XcodeModelError when the document has no objects dictionary.
2978
+ */
2979
+ static parse(text) {
2980
+ const document = asDictionary(parsePbxproj(text));
2981
+ if (document == null) throw new XcodeModelError("The document root is not a dictionary");
2982
+ return new XcodeProject(document);
2983
+ }
2984
+ /**
2985
+ * Wraps an already parsed document in a model. The document is used in
2986
+ * place, not copied; model mutations write into it.
2987
+ */
2988
+ static fromDocument(document) {
2989
+ return new XcodeProject(document);
2990
+ }
2991
+ /**
2992
+ * Serializes the current document state to pbxproj text in Xcode's
2993
+ * canonical layout.
2994
+ */
2995
+ build() {
2996
+ return buildPbxproj(this.document);
2997
+ }
2998
+ /**
2999
+ * The raw properties dictionary of an object.
3000
+ *
3001
+ * @throws XcodeModelError when no object with the id exists; views use
3002
+ * this accessor, so a view of a deleted object fails loudly instead of
3003
+ * resurrecting the entry.
3004
+ */
3005
+ propertiesOf(id) {
3006
+ const properties = asDictionary(this.objectsDictionary[id]);
3007
+ if (properties == null) throw new XcodeModelError(`No object with id ${id} exists in the document`);
3008
+ return properties;
3009
+ }
3010
+ /**
3011
+ * The raw properties dictionary of an object, or `undefined` when the id
3012
+ * is absent, dangling, or not a dictionary.
3013
+ */
3014
+ propertiesOfOptional(id) {
3015
+ return id == null ? void 0 : asDictionary(this.objectsDictionary[id]);
3016
+ }
3017
+ /**
3018
+ * The view of an object by id, or `undefined` when the id is absent or
3019
+ * its entry is not a dictionary. Repeated lookups return the same view
3020
+ * instance.
3021
+ */
3022
+ get(id) {
3023
+ if (id == null) return;
3024
+ const existing = this.views.get(id);
3025
+ if (existing != null) return existing;
3026
+ const properties = asDictionary(this.objectsDictionary[id]);
3027
+ if (properties == null) return;
3028
+ const view = this.createView(id, asString(properties["isa"]) ?? "");
3029
+ this.views.set(id, view);
3030
+ return view;
3031
+ }
3032
+ /**
3033
+ * Iterates `[id, view]` over every well-formed object in the document,
3034
+ * in document order. Entries that are not dictionaries are skipped.
3035
+ */
3036
+ *objects() {
3037
+ for (const id of Object.keys(this.objectsDictionary)) {
3038
+ const view = this.get(id);
3039
+ if (view != null) yield [id, view];
3040
+ }
3041
+ }
3042
+ /**
3043
+ * Generates a deterministic 24-character id from a seed, avoiding every
3044
+ * id the document currently contains. The id is not reserved; adding an
3045
+ * object with it (see {@link add}) is what claims it.
3046
+ */
3047
+ generateId(seed) {
3048
+ return generateObjectId(seed, new Set(Object.keys(this.objectsDictionary)));
3049
+ }
3050
+ /**
3051
+ * Adds an object to the document and returns its view.
3052
+ *
3053
+ * @param isa The object's kind; written as the `isa` property.
3054
+ * @param properties The object's remaining properties. The dictionary is
3055
+ * stored as passed (not copied), with `isa` written first so the
3056
+ * serialized entry leads with it.
3057
+ * @param seed Seed for the deterministic id; defaults to the isa, which
3058
+ * is only sensible for singleton objects.
3059
+ * @returns The view of the created object.
3060
+ */
3061
+ add(isa, properties, seed) {
3062
+ const id = this.generateId(seed ?? isa);
3063
+ this.objectsDictionary[id] = {
3064
+ isa,
3065
+ ...properties
3066
+ };
3067
+ const view = this.createView(id, isa);
3068
+ this.views.set(id, view);
3069
+ return view;
3070
+ }
3071
+ /**
3072
+ * The view of the document's root `PBXProject` object.
3073
+ *
3074
+ * @throws XcodeModelError when `rootObject` is missing or dangling,
3075
+ * since without it no project-level operation is meaningful.
3076
+ */
3077
+ get rootProject() {
3078
+ const view = this.get(asString(this.document["rootObject"]));
3079
+ if (!(view instanceof RootProject)) throw new XcodeModelError("The document's rootObject does not reference a PBXProject");
3080
+ return view;
3081
+ }
3082
+ /**
3083
+ * The views of the project's targets of every kind (native, aggregate,
3084
+ * and legacy), in project order.
3085
+ */
3086
+ targets() {
3087
+ const targets = [];
3088
+ for (const id of this.rootProject.targetIds()) {
3089
+ const view = this.get(id);
3090
+ if (view instanceof Target) targets.push(view);
3091
+ }
3092
+ return targets;
3093
+ }
3094
+ /**
3095
+ * The views of the project's native targets, in project order. Targets
3096
+ * of other kinds (aggregate and legacy targets) are not included.
3097
+ */
3098
+ nativeTargets() {
3099
+ return this.targets().filter((target) => target instanceof NativeTarget);
3100
+ }
3101
+ /**
3102
+ * Finds a native target by name.
3103
+ */
3104
+ findTarget(name) {
3105
+ return this.nativeTargets().find((target) => target.name === name);
3106
+ }
3107
+ /**
3108
+ * Finds the main application target for a platform. It prefers the
3109
+ * application target whose own configurations carry the platform's
3110
+ * deployment-target key, and falls back to the first application target
3111
+ * in project order. Returns `undefined` when the project has no
3112
+ * application target.
3113
+ */
3114
+ findMainAppTarget(platform = "ios") {
3115
+ const deploymentKey = DEPLOYMENT_TARGET_KEY[platform];
3116
+ const applications = this.nativeTargets().filter((target) => target.productType === ProductType.application);
3117
+ return applications.find((target) => target.buildConfigurations().some((configuration) => {
3118
+ const settings = asDictionary(configuration.properties["buildSettings"]);
3119
+ return settings != null && deploymentKey in settings;
3120
+ })) ?? applications[0];
3121
+ }
3122
+ /**
3123
+ * Creates a native target with its build configurations, product file
3124
+ * reference in the Products group, and the standard Sources, Frameworks,
3125
+ * and Resources build phases, and registers it on the project.
3126
+ *
3127
+ * @throws XcodeModelError for product types the model cannot create a
3128
+ * product reference for; see {@link AddNativeTargetOptions.productType}.
3129
+ */
3130
+ addNativeTarget(options) {
3131
+ const productInfo = PRODUCT_FILE_INFO[options.productType];
3132
+ if (productInfo == null) throw new XcodeModelError(`Cannot create a product reference for product type ${options.productType}`);
3133
+ const configurationIds = (options.configurationNames ?? ["Debug", "Release"]).map((configurationName) => this.add(Isa.buildConfiguration, {
3134
+ buildSettings: { ...options.buildSettings },
3135
+ name: configurationName
3136
+ }, `${Isa.buildConfiguration} ${options.name} ${configurationName}`).id);
3137
+ const configurationList = this.add(Isa.configurationList, {
3138
+ buildConfigurations: configurationIds,
3139
+ defaultConfigurationIsVisible: 0,
3140
+ defaultConfigurationName: options.defaultConfigurationName ?? "Release"
3141
+ }, `${Isa.configurationList} ${options.name}`);
3142
+ const productReference = this.add(Isa.fileReference, {
3143
+ explicitFileType: productInfo.fileType,
3144
+ includeInIndex: 0,
3145
+ path: `${options.name}${productInfo.extension}`,
3146
+ sourceTree: "BUILT_PRODUCTS_DIR"
3147
+ }, `${Isa.fileReference} ${options.name}${productInfo.extension}`);
3148
+ this.rootProject.ensureProductsGroup().addChild(productReference);
3149
+ const target = this.add(Isa.nativeTarget, {
3150
+ buildConfigurationList: configurationList.id,
3151
+ buildPhases: [],
3152
+ buildRules: [],
3153
+ dependencies: [],
3154
+ name: options.name,
3155
+ productName: options.name,
3156
+ productReference: productReference.id,
3157
+ productType: options.productType
3158
+ }, `${Isa.nativeTarget} ${options.name}`);
3159
+ ensureArray(this.rootProject.properties, "targets").push(target.id);
3160
+ const nativeTarget = target;
3161
+ nativeTarget.ensureSourcesPhase();
3162
+ nativeTarget.ensureFrameworksPhase();
3163
+ nativeTarget.ensureResourcesPhase();
3164
+ return nativeTarget;
3165
+ }
3166
+ /**
3167
+ * Finds the remote Swift package reference for a repository URL.
3168
+ */
3169
+ findSwiftPackage(repositoryUrl) {
3170
+ for (const id of stringItems(this.rootProject.properties["packageReferences"])) {
3171
+ const reference = this.get(id);
3172
+ if (reference?.getString("repositoryURL") === repositoryUrl) return reference;
3173
+ }
3174
+ }
3175
+ /**
3176
+ * Adds a remote Swift package reference and registers it on the project.
3177
+ * When the project already references the repository, the existing
3178
+ * reference is returned unchanged, requirement included; adjust an
3179
+ * existing requirement through the reference's properties. Link products
3180
+ * to targets with {@link NativeTarget.addSwiftPackageProduct}.
3181
+ *
3182
+ * @param options.repositoryURL The package's git URL.
3183
+ * @param options.requirement The version requirement dictionary as Xcode
3184
+ * stores it, for example
3185
+ * `{ kind: "upToNextMajorVersion", minimumVersion: "5.0.0" }`.
3186
+ * @returns The view of the package reference for the repository.
3187
+ */
3188
+ addSwiftPackage(options) {
3189
+ const existing = this.findSwiftPackage(options.repositoryURL);
3190
+ if (existing != null) return existing;
3191
+ const reference = this.add(Isa.remoteSwiftPackageReference, {
3192
+ repositoryURL: options.repositoryURL,
3193
+ requirement: options.requirement
3194
+ }, `${Isa.remoteSwiftPackageReference} ${options.repositoryURL}`);
3195
+ ensureArray(this.rootProject.properties, "packageReferences").push(reference.id);
3196
+ return reference;
3197
+ }
3198
+ /**
3199
+ * Finds the local Swift package reference for a directory path.
3200
+ */
3201
+ findLocalSwiftPackage(relativePath) {
3202
+ return this.rootProject.packageReferences().find((reference) => reference.isa === Isa.localSwiftPackageReference && reference.getString("relativePath") === relativePath);
3203
+ }
3204
+ /**
3205
+ * Adds a local (path-based) Swift package reference and registers it on
3206
+ * the project, returning the existing reference when the path is already
3207
+ * registered. Products link to targets the same way as remote packages,
3208
+ * through {@link NativeTarget.addSwiftPackageProduct}.
3209
+ *
3210
+ * @param relativePath The package directory, relative to the project.
3211
+ * @returns The view of the package reference for the path.
3212
+ */
3213
+ addLocalSwiftPackage(relativePath) {
3214
+ const existing = this.findLocalSwiftPackage(relativePath);
3215
+ if (existing != null) return existing;
3216
+ const reference = this.add(Isa.localSwiftPackageReference, { relativePath }, `${Isa.localSwiftPackageReference} ${relativePath}`);
3217
+ ensureArray(this.rootProject.properties, "packageReferences").push(reference.id);
3218
+ return reference;
3219
+ }
3220
+ /**
3221
+ * The views of every `PBXBuildFile` that points at the referenced object
3222
+ * through `fileRef` or `productRef`. Useful for relocating a product
3223
+ * between copy phases.
3224
+ */
3225
+ buildFilesReferencing(reference) {
3226
+ const buildFiles = [];
3227
+ for (const [, view] of this.objects()) if (view.isa === Isa.buildFile && (view.getString("fileRef") === reference.id || view.getString("productRef") === reference.id)) buildFiles.push(view);
3228
+ return buildFiles;
3229
+ }
3230
+ /**
3231
+ * Validates the document's object graph and returns every problem
3232
+ * found. An empty array means the graph is structurally sound. The
3233
+ * checks cover the root object, object kinds, known references, and
3234
+ * reachability.
3235
+ */
3236
+ validate() {
3237
+ return validateProject(this);
3238
+ }
3239
+ /**
3240
+ * Removes every object unreachable from the root object and returns the
3241
+ * removed ids. Reachability is conservative. Any real reference keeps
3242
+ * an object alive, even through properties outside the known schema.
3243
+ */
3244
+ pruneOrphans() {
3245
+ return pruneOrphanObjects(this);
3246
+ }
3247
+ /**
3248
+ * The views of every object that references the id anywhere in its
3249
+ * properties. A reference is a string property naming the id, an id
3250
+ * list containing it, or a nested dictionary carrying it as a key or
3251
+ * string value.
3252
+ *
3253
+ * The scan is linear over the document; removal flows call it once per
3254
+ * removed object, which keeps teardown proportional to what is actually
3255
+ * removed.
3256
+ */
3257
+ referrersOf(id) {
3258
+ const referrers = [];
3259
+ for (const [referrerId, view] of this.objects()) if (referrerId !== id && objectReferences(view.properties, id)) referrers.push(view);
3260
+ return referrers;
3261
+ }
3262
+ /**
3263
+ * Removes an object from the document and strips every reference to it
3264
+ * from the remaining objects. String properties naming the id are
3265
+ * deleted, id lists drop it, and nested dictionaries keyed by object id
3266
+ * (such as `TargetAttributes`) drop its entry.
3267
+ *
3268
+ * Removing an id the document does not contain is a no-op. This is the
3269
+ * low-level removal; {@link removeTarget} composes it into a full
3270
+ * teardown.
3271
+ */
3272
+ removeObject(id) {
3273
+ if (!(id in this.objectsDictionary)) return;
3274
+ delete this.objectsDictionary[id];
3275
+ this.views.delete(id);
3276
+ for (const [, view] of this.objects()) stripReferences(view.properties, id);
3277
+ }
3278
+ /**
3279
+ * Removes a target and everything that exists only for its sake. That
3280
+ * covers its build phases and their build files, its configuration list
3281
+ * and configurations, its product reference and the build files
3282
+ * embedding it, dependency objects and container proxies in both
3283
+ * directions (other targets' dependencies on it, and its own
3284
+ * dependencies on others), its membership exception sets, and
3285
+ * synchronized folders no remaining target links.
3286
+ *
3287
+ * On-disk sources are untouched; the removal is document-only, like
3288
+ * deleting a target in Xcode and keeping its folder.
3289
+ *
3290
+ * @throws XcodeModelError when the target belongs to another project,
3291
+ * since removing by another document's ids would tear down unrelated
3292
+ * objects that happen to share them.
3293
+ */
3294
+ removeTarget(target) {
3295
+ if (target.project !== this) throw new XcodeModelError("Cannot remove a target that belongs to another project");
3296
+ const ownedIds = /* @__PURE__ */ new Set();
3297
+ for (const phase of target.buildPhases()) {
3298
+ for (const buildFileId of phase.buildFileIds) ownedIds.add(buildFileId);
3299
+ ownedIds.add(phase.id);
3300
+ }
3301
+ const configurationListId = target.getString("buildConfigurationList");
3302
+ if (configurationListId != null) {
3303
+ for (const configuration of target.buildConfigurations()) ownedIds.add(configuration.id);
3304
+ ownedIds.add(configurationListId);
3305
+ }
3306
+ const product = this.get(target.getString("productReference"));
3307
+ if (product != null) {
3308
+ for (const buildFile of this.buildFilesReferencing(product)) ownedIds.add(buildFile.id);
3309
+ ownedIds.add(product.id);
3310
+ }
3311
+ for (const dependency of target.dependencies()) {
3312
+ const proxyId = dependency.getString("targetProxy");
3313
+ if (proxyId != null) ownedIds.add(proxyId);
3314
+ ownedIds.add(dependency.id);
3315
+ }
3316
+ for (const [, view] of this.objects()) {
3317
+ if (view.isa === Isa.targetDependency && view.getString("target") === target.id) {
3318
+ const proxyId = view.getString("targetProxy");
3319
+ if (proxyId != null) ownedIds.add(proxyId);
3320
+ ownedIds.add(view.id);
3321
+ }
3322
+ if (view.isa === Isa.fileSystemSynchronizedBuildFileExceptionSet && view.getString("target") === target.id) ownedIds.add(view.id);
3323
+ }
3324
+ const syncGroupIds = stringItems(target.properties["fileSystemSynchronizedGroups"]);
3325
+ this.removeObject(target.id);
3326
+ for (const id of ownedIds) this.removeObject(id);
3327
+ for (const groupId of syncGroupIds) if (!this.nativeTargets().some((remaining) => stringItems(remaining.properties["fileSystemSynchronizedGroups"]).includes(groupId))) this.removeObject(groupId);
3328
+ }
3329
+ /**
3330
+ * Finds a file reference by its project-relative path, resolving each
3331
+ * reference's location through the group tree from the main group
3332
+ * (nested group `path` components join with `/`).
3333
+ *
3334
+ * Members of file-system-synchronized folders are not listed in the
3335
+ * document and therefore cannot be found; check
3336
+ * {@link NativeTarget.syncGroupPaths} for folder-level containment
3337
+ * instead.
3338
+ */
3339
+ findFileReference(projectRelativePath) {
3340
+ const mainGroup = this.rootProject.mainGroup();
3341
+ if (mainGroup == null) return;
3342
+ const visited = /* @__PURE__ */ new Set();
3343
+ const search = (group, prefix) => {
3344
+ if (visited.has(group.id)) return;
3345
+ visited.add(group.id);
3346
+ for (const childId of group.childIds) {
3347
+ const child = this.get(childId);
3348
+ if (child == null) continue;
3349
+ const childPath = child.getString("path");
3350
+ if (child instanceof Group) {
3351
+ const found = search(child, childPath == null ? prefix : joinPath(prefix, childPath));
3352
+ if (found != null) return found;
3353
+ } else if (child.isa === Isa.fileReference && childPath != null && joinPath(prefix, childPath) === projectRelativePath) return child;
3354
+ }
3355
+ };
3356
+ return search(mainGroup, "");
3357
+ }
3358
+ /**
3359
+ * Creates the typed view for an object, dispatching on its isa. Objects
3360
+ * outside the typed vocabulary get the generic base view.
3361
+ */
3362
+ createView(id, isa) {
3363
+ switch (isa) {
3364
+ case Isa.nativeTarget: return new NativeTarget(this, id);
3365
+ case Isa.aggregateTarget: return new AggregateTarget(this, id);
3366
+ case Isa.legacyTarget: return new LegacyTarget(this, id);
3367
+ case Isa.project: return new RootProject(this, id);
3368
+ case Isa.group:
3369
+ case Isa.variantGroup: return new Group(this, id);
3370
+ case Isa.versionGroup: return new VersionGroup(this, id);
3371
+ case Isa.fileSystemSynchronizedRootGroup: return new SyncRootGroup(this, id);
3372
+ case Isa.buildRule: return new BuildRule(this, id);
3373
+ case Isa.referenceProxy: return new ReferenceProxy(this, id);
3374
+ default: return isa.endsWith("BuildPhase") ? new BuildPhase(this, id) : new XcodeObject(this, id);
3375
+ }
3376
+ }
3377
+ };
3378
+ /**
3379
+ * Joins two path segments with a `/`, treating an empty prefix as the
3380
+ * project root.
3381
+ */
3382
+ function joinPath(prefix, segment) {
3383
+ return prefix === "" ? segment : `${prefix}/${segment}`;
3384
+ }
3385
+ /**
3386
+ * Whether a value references the id anywhere in its structure. A
3387
+ * reference is a string equal to it, an array containing it at any depth,
3388
+ * or a dictionary carrying it as a key or somewhere in its values.
3389
+ */
3390
+ function valueReferences(value, id) {
3391
+ if (typeof value === "string") return value === id;
3392
+ if (Array.isArray(value)) return value.some((item) => valueReferences(item, id));
3393
+ const nested = asDictionary(value);
3394
+ if (nested != null) return id in nested || objectReferences(nested, id);
3395
+ return false;
3396
+ }
3397
+ /**
3398
+ * Whether an object's properties reference the id anywhere. See
3399
+ * {@link valueReferences} for the shapes considered.
3400
+ */
3401
+ function objectReferences(properties, id) {
3402
+ for (const key of Object.keys(properties)) if (valueReferences(properties[key], id)) return true;
3403
+ return false;
3404
+ }
3405
+ /**
3406
+ * Strips every reference to the id inside a value and returns the value
3407
+ * to keep. Strings equal to the id become `undefined`, arrays drop
3408
+ * matching items at any depth, and dictionaries drop entries keyed by it
3409
+ * and recurse into their values.
3410
+ */
3411
+ function stripValue(value, id) {
3412
+ if (typeof value === "string") return value === id ? void 0 : value;
3413
+ if (Array.isArray(value)) {
3414
+ let changed = false;
3415
+ const kept = [];
3416
+ for (const item of value) {
3417
+ const stripped = stripValue(item, id);
3418
+ if (stripped == null || stripped !== item) changed = true;
3419
+ if (stripped != null) kept.push(stripped);
3420
+ }
3421
+ return changed ? kept : value;
3422
+ }
3423
+ const nested = asDictionary(value);
3424
+ if (nested != null) {
3425
+ if (id in nested) delete nested[id];
3426
+ stripReferences(nested, id);
3427
+ }
3428
+ return value;
3429
+ }
3430
+ /**
3431
+ * Strips every reference to the id from an object's properties; see
3432
+ * {@link stripValue} for the shapes handled. String properties naming the
3433
+ * id are deleted rather than left empty.
3434
+ */
3435
+ function stripReferences(properties, id) {
3436
+ for (const key of Object.keys(properties)) {
3437
+ const value = properties[key];
3438
+ if (value == null) continue;
3439
+ const stripped = stripValue(value, id);
3440
+ if (stripped == null) delete properties[key];
3441
+ else if (stripped !== value) properties[key] = stripped;
3442
+ }
3443
+ }
3444
+ //#endregion
3445
+ //#region src/scheme/types.ts
3446
+ /**
3447
+ * Whether a node is an element rather than a comment.
3448
+ */
3449
+ function isXcschemeElement(node) {
3450
+ return "name" in node;
3451
+ }
3452
+ //#endregion
3453
+ //#region src/scheme/build.ts
3454
+ /**
3455
+ * Serializer for the `.xcscheme` XML dialect.
3456
+ *
3457
+ * The output reproduces Xcode's own layout byte for byte: the UTF-8
3458
+ * declaration, three-space indentation, one attribute per line with
3459
+ * spaces around the equals sign, the closing angle bracket glued to the
3460
+ * last attribute, and an explicit close tag on every element. Parsing an
3461
+ * Xcode-written scheme and building it back yields the identical file,
3462
+ * and any input reaches that canonical form in one build.
3463
+ *
3464
+ * @module
3465
+ */
3466
+ const XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
3467
+ /** One indentation step of Xcode's scheme writer. */
3468
+ const INDENT = " ";
3469
+ /**
3470
+ * Matches the names Xcode's scheme vocabulary uses; a stray non-name
3471
+ * (an empty string, spaces, XML syntax) must fail rather than produce a
3472
+ * document no parser accepts.
3473
+ */
3474
+ const NAME_PATTERN = /^[A-Za-z_:][A-Za-z0-9_:.-]*$/u;
3475
+ /**
3476
+ * Matches characters that cannot appear in an attribute value: XML 1.0
3477
+ * has no representation for control characters other than tab, line
3478
+ * feed, and carriage return.
3479
+ */
3480
+ const UNENCODABLE_PATTERN = /[\u0000-\u0008\u000B\u000C\u000E-\u001F]/u;
3481
+ /**
3482
+ * Serializes a scheme document to the text of a `.xcscheme` file.
3483
+ *
3484
+ * @throws XcschemeBuildError for element or attribute names that are not
3485
+ * XML names and for attribute values carrying unencodable control
3486
+ * characters, with the path of the offending node.
3487
+ */
3488
+ function buildXcscheme(document) {
3489
+ let output = XML_DECLARATION;
3490
+ for (const comment of document.leading) output += `<!--${comment.comment}-->\n`;
3491
+ output += renderElement(document.root, 0, document.root.name);
3492
+ for (const comment of document.trailing) output += `<!--${comment.comment}-->\n`;
3493
+ return output;
3494
+ }
3495
+ /**
3496
+ * Renders one element with Xcode's layout: attributes each on their own
3497
+ * line indented one step past the tag, `>` glued to the last attribute,
3498
+ * children indented one step, and always an explicit close tag.
3499
+ */
3500
+ function renderElement(element, depth, path) {
3501
+ if (!NAME_PATTERN.test(element.name)) throw new XcschemeBuildError(`Element name ${JSON.stringify(element.name)} is not a valid XML name`, path);
3502
+ const indent = INDENT.repeat(depth);
3503
+ const names = Object.keys(element.attributes);
3504
+ let output;
3505
+ if (names.length === 0) output = `${indent}<${element.name}>\n`;
3506
+ else {
3507
+ output = `${indent}<${element.name}\n`;
3508
+ const attributeIndent = indent + INDENT;
3509
+ for (let i = 0; i < names.length; i++) {
3510
+ const name = names[i];
3511
+ if (!NAME_PATTERN.test(name)) throw new XcschemeBuildError(`Attribute name ${JSON.stringify(name)} is not a valid XML name`, path);
3512
+ const value = escapeAttribute(element.attributes[name], path, name);
3513
+ const terminator = i === names.length - 1 ? ">" : "";
3514
+ output += `${attributeIndent}${name} = "${value}"${terminator}\n`;
3515
+ }
3516
+ }
3517
+ output += renderChildren(element.children, depth + 1, path);
3518
+ output += `${indent}</${element.name}>\n`;
3519
+ return output;
3520
+ }
3521
+ /**
3522
+ * Renders an element's children in document order, numbering repeated
3523
+ * element names in the error path so a failure points at one node.
3524
+ */
3525
+ function renderChildren(children, depth, path) {
3526
+ let output = "";
3527
+ const seen = /* @__PURE__ */ new Map();
3528
+ for (const child of children) if (isXcschemeElement(child)) {
3529
+ const index = seen.get(child.name) ?? 0;
3530
+ seen.set(child.name, index + 1);
3531
+ output += renderElement(child, depth, `${path}.${child.name}[${index}]`);
3532
+ } else output += `${INDENT.repeat(depth)}<!--${child.comment}-->\n`;
3533
+ return output;
3534
+ }
3535
+ /**
3536
+ * Escapes an attribute value the way Xcode's writer does: the five named
3537
+ * entities for XML syntax characters, and character references for tab,
3538
+ * line feed, and carriage return so they survive attribute-value
3539
+ * normalization on the next parse.
3540
+ */
3541
+ function escapeAttribute(value, path, attributeName) {
3542
+ const unencodable = UNENCODABLE_PATTERN.exec(value);
3543
+ if (unencodable != null) throw new XcschemeBuildError(`Attribute ${attributeName} contains the control character U+${unencodable[0].charCodeAt(0).toString(16).padStart(4, "0").toUpperCase()}, which XML cannot encode`, path);
3544
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;").replaceAll(" ", "&#9;").replaceAll("\n", "&#10;").replaceAll("\r", "&#13;");
3545
+ }
3546
+ //#endregion
3547
+ //#region src/scheme/model.ts
3548
+ /**
3549
+ * Helpers over the scheme node tree: element queries and the default
3550
+ * scheme factory.
3551
+ *
3552
+ * The tree itself is plain data, so most edits are direct property
3553
+ * writes. What this module adds is the recursive query that editing
3554
+ * flows start from and a factory producing the scheme Xcode's own "New
3555
+ * Scheme" action writes for an application target.
3556
+ *
3557
+ * @module
3558
+ */
3559
+ /**
3560
+ * Collects elements of the given name anywhere in the tree, in document
3561
+ * order, the subtree root included. Passing no name collects every
3562
+ * element.
3563
+ *
3564
+ * ```ts
3565
+ * for (const reference of xcschemeElements(scheme.root, "BuildableReference")) {
3566
+ * reference.attributes["BlueprintName"] = "RenamedApp";
3567
+ * }
3568
+ * ```
3569
+ */
3570
+ function xcschemeElements(root, name) {
3571
+ const found = [];
3572
+ const visit = (element) => {
3573
+ if (name == null || element.name === name) found.push(element);
3574
+ for (const child of element.children) if (isXcschemeElement(child)) visit(child);
3575
+ };
3576
+ visit(root);
3577
+ return found;
3578
+ }
3579
+ /**
3580
+ * Creates the scheme Xcode writes for an application target: build,
3581
+ * launch, profile, analyze, and archive actions wired to the app product,
3582
+ * with Xcode's default configuration choices (Debug for development
3583
+ * actions, Release for profiling and archiving).
3584
+ */
3585
+ function createXcscheme(options) {
3586
+ const xcodeprojName = options.xcodeprojName ?? `${options.appName}.xcodeproj`;
3587
+ /**
3588
+ * Buildable references appear once per action; each site gets its own
3589
+ * element so a mutation through one action cannot alias into another.
3590
+ */
3591
+ const buildableReference = () => ({
3592
+ name: "BuildableReference",
3593
+ attributes: {
3594
+ BuildableIdentifier: "primary",
3595
+ BlueprintIdentifier: options.blueprintIdentifier ?? "",
3596
+ BuildableName: `${options.appName}.app`,
3597
+ BlueprintName: options.appName,
3598
+ ReferencedContainer: `container:${xcodeprojName}`
3599
+ },
3600
+ children: []
3601
+ });
3602
+ return {
3603
+ leading: [],
3604
+ root: {
3605
+ name: "Scheme",
3606
+ attributes: { version: "1.7" },
3607
+ children: [
3608
+ {
3609
+ name: "BuildAction",
3610
+ attributes: {
3611
+ parallelizeBuildables: "YES",
3612
+ buildImplicitDependencies: "YES"
3613
+ },
3614
+ children: [{
3615
+ name: "BuildActionEntries",
3616
+ attributes: {},
3617
+ children: [{
3618
+ name: "BuildActionEntry",
3619
+ attributes: {
3620
+ buildForTesting: "YES",
3621
+ buildForRunning: "YES",
3622
+ buildForProfiling: "YES",
3623
+ buildForArchiving: "YES",
3624
+ buildForAnalyzing: "YES"
3625
+ },
3626
+ children: [buildableReference()]
3627
+ }]
3628
+ }]
3629
+ },
3630
+ {
3631
+ name: "LaunchAction",
3632
+ attributes: {
3633
+ buildConfiguration: "Debug",
3634
+ selectedDebuggerIdentifier: "Xcode.DebuggerFoundation.Debugger.LLDB",
3635
+ selectedLauncherIdentifier: "Xcode.DebuggerFoundation.Launcher.LLDB",
3636
+ launchStyle: "0",
3637
+ useCustomWorkingDirectory: "NO",
3638
+ ignoresPersistentStateOnLaunch: "NO",
3639
+ debugDocumentVersioning: "YES",
3640
+ debugServiceExtension: "internal",
3641
+ allowLocationSimulation: "YES"
3642
+ },
3643
+ children: [{
3644
+ name: "BuildableProductRunnable",
3645
+ attributes: { runnableDebuggingMode: "0" },
3646
+ children: [buildableReference()]
3647
+ }]
3648
+ },
3649
+ {
3650
+ name: "ProfileAction",
3651
+ attributes: {
3652
+ buildConfiguration: "Release",
3653
+ shouldUseLaunchSchemeArgsEnv: "YES",
3654
+ savedToolIdentifier: "",
3655
+ useCustomWorkingDirectory: "NO",
3656
+ debugDocumentVersioning: "YES"
3657
+ },
3658
+ children: [{
3659
+ name: "BuildableProductRunnable",
3660
+ attributes: { runnableDebuggingMode: "0" },
3661
+ children: [buildableReference()]
3662
+ }]
3663
+ },
3664
+ {
3665
+ name: "AnalyzeAction",
3666
+ attributes: { buildConfiguration: "Debug" },
3667
+ children: []
3668
+ },
3669
+ {
3670
+ name: "ArchiveAction",
3671
+ attributes: {
3672
+ buildConfiguration: "Release",
3673
+ revealArchiveInOrganizer: "YES"
3674
+ },
3675
+ children: []
3676
+ }
3677
+ ]
3678
+ },
3679
+ trailing: []
3680
+ };
3681
+ }
3682
+ //#endregion
3683
+ //#region src/scheme/parse.ts
3684
+ /**
3685
+ * Parser for the `.xcscheme` XML dialect.
3686
+ *
3687
+ * Scheme files are XML with a narrow shape: elements with attributes and
3688
+ * child elements, no text content, no namespaces, no DOCTYPE. The parser
3689
+ * accepts that shape from any writer (attribute order, quoting style, and
3690
+ * whitespace vary across tools), resolves character and entity references
3691
+ * in attribute values, and preserves comments. Anything outside the shape
3692
+ * fails loudly with a position, in line with the pbxproj parser.
3693
+ *
3694
+ * @module
3695
+ */
3696
+ const CODE_TAB = 9;
3697
+ const CODE_LINE_FEED = 10;
3698
+ const CODE_CARRIAGE_RETURN = 13;
3699
+ const CODE_SPACE = 32;
3700
+ const CODE_BANG = 33;
3701
+ const CODE_QUOTE = 34;
3702
+ const CODE_AMPERSAND = 38;
3703
+ const CODE_APOSTROPHE = 39;
3704
+ const CODE_HYPHEN = 45;
3705
+ const CODE_SLASH = 47;
3706
+ const CODE_LESS_THAN = 60;
3707
+ const CODE_EQUALS = 61;
3708
+ const CODE_GREATER_THAN = 62;
3709
+ const CODE_QUESTION = 63;
3710
+ const CODE_HASH = 35;
3711
+ const CODE_BOM = 65279;
3712
+ /**
3713
+ * Whether a code unit is XML whitespace (space, tab, line feed, or
3714
+ * carriage return).
3715
+ */
3716
+ function isWhitespace(code) {
3717
+ return code === CODE_SPACE || code === CODE_TAB || code === CODE_LINE_FEED || code === CODE_CARRIAGE_RETURN;
3718
+ }
3719
+ /**
3720
+ * Whether a code unit can start an XML name. The scheme vocabulary is
3721
+ * ASCII, so the accepted alphabet is letters, underscore, and colon.
3722
+ */
3723
+ function isNameStart(code) {
3724
+ return code >= 65 && code <= 90 || code >= 97 && code <= 122 || code === 95 || code === 58;
3725
+ }
3726
+ /**
3727
+ * Whether a code unit can continue an XML name.
3728
+ */
3729
+ function isNameChar(code) {
3730
+ return isNameStart(code) || code >= 48 && code <= 57 || code === CODE_HYPHEN || code === 46;
3731
+ }
3732
+ /** The named character entities XML 1.0 predefines. */
3733
+ const NAMED_ENTITIES = {
3734
+ amp: "&",
3735
+ apos: "'",
3736
+ gt: ">",
3737
+ lt: "<",
3738
+ quot: "\""
3739
+ };
3740
+ /**
3741
+ * Parses the text of a `.xcscheme` file into its node tree.
3742
+ *
3743
+ * @throws XcschemeParseError when the text is not a well-formed scheme
3744
+ * document, with the line and column of the failure.
3745
+ */
3746
+ function parseXcscheme(text) {
3747
+ return new Parser(text).parseDocument();
3748
+ }
3749
+ var Parser = class {
3750
+ input;
3751
+ pos = 0;
3752
+ constructor(input) {
3753
+ this.input = input;
3754
+ if (input.charCodeAt(0) === CODE_BOM) this.pos = 1;
3755
+ }
3756
+ parseDocument() {
3757
+ this.skipWhitespace();
3758
+ this.skipDeclaration();
3759
+ const leading = [];
3760
+ const trailing = [];
3761
+ this.skipWhitespace();
3762
+ while (this.peekIsCommentStart()) {
3763
+ leading.push(this.parseComment());
3764
+ this.skipWhitespace();
3765
+ }
3766
+ const root = this.parseElement();
3767
+ this.skipWhitespace();
3768
+ while (this.peekIsCommentStart()) {
3769
+ trailing.push(this.parseComment());
3770
+ this.skipWhitespace();
3771
+ }
3772
+ if (this.pos < this.input.length) this.fail("Expected end of document after the root element");
3773
+ return {
3774
+ leading,
3775
+ root,
3776
+ trailing
3777
+ };
3778
+ }
3779
+ /**
3780
+ * Skips the `<?xml ... ?>` declaration when present. Its attributes are
3781
+ * not retained: the writer always emits the canonical UTF-8 declaration.
3782
+ */
3783
+ skipDeclaration() {
3784
+ if (this.input.charCodeAt(this.pos) !== CODE_LESS_THAN || this.input.charCodeAt(this.pos + 1) !== CODE_QUESTION) return;
3785
+ const end = this.input.indexOf("?>", this.pos + 2);
3786
+ if (end === -1) this.fail("Unterminated XML declaration");
3787
+ this.pos = end + 2;
3788
+ }
3789
+ parseElement() {
3790
+ if (this.input.charCodeAt(this.pos) !== CODE_LESS_THAN) this.fail("Expected an element");
3791
+ this.pos++;
3792
+ const name = this.parseName("element name");
3793
+ const attributes = Object.create(null);
3794
+ for (;;) {
3795
+ const hadWhitespace = this.skipWhitespace();
3796
+ const code = this.input.charCodeAt(this.pos);
3797
+ if (code === CODE_GREATER_THAN) {
3798
+ this.pos++;
3799
+ break;
3800
+ }
3801
+ if (code === CODE_SLASH && this.input.charCodeAt(this.pos + 1) === CODE_GREATER_THAN) {
3802
+ this.pos += 2;
3803
+ return {
3804
+ name,
3805
+ attributes,
3806
+ children: []
3807
+ };
3808
+ }
3809
+ if (Number.isNaN(code)) this.fail(`Unterminated <${name}> tag`);
3810
+ if (!hadWhitespace) this.fail("Expected whitespace before an attribute");
3811
+ const attributeName = this.parseName("attribute name");
3812
+ this.skipWhitespace();
3813
+ if (this.input.charCodeAt(this.pos) !== CODE_EQUALS) this.fail(`Expected = after attribute ${attributeName}`);
3814
+ this.pos++;
3815
+ this.skipWhitespace();
3816
+ if (attributeName in attributes) this.fail(`Duplicate attribute ${attributeName} on <${name}>`);
3817
+ attributes[attributeName] = this.parseAttributeValue();
3818
+ }
3819
+ const children = [];
3820
+ for (;;) {
3821
+ this.skipWhitespace();
3822
+ const code = this.input.charCodeAt(this.pos);
3823
+ if (Number.isNaN(code)) this.fail(`Missing </${name}> close tag`);
3824
+ if (code !== CODE_LESS_THAN) this.fail("Unexpected text content inside an element");
3825
+ if (this.input.charCodeAt(this.pos + 1) === CODE_SLASH) {
3826
+ this.pos += 2;
3827
+ const closeName = this.parseName("close tag name");
3828
+ if (closeName !== name) this.fail(`Expected </${name}> but found </${closeName}>`);
3829
+ this.skipWhitespace();
3830
+ if (this.input.charCodeAt(this.pos) !== CODE_GREATER_THAN) this.fail(`Malformed </${closeName}> close tag`);
3831
+ this.pos++;
3832
+ return {
3833
+ name,
3834
+ attributes,
3835
+ children
3836
+ };
3837
+ }
3838
+ if (this.peekIsCommentStart()) children.push(this.parseComment());
3839
+ else children.push(this.parseElement());
3840
+ }
3841
+ }
3842
+ peekIsCommentStart() {
3843
+ return this.input.charCodeAt(this.pos) === CODE_LESS_THAN && this.input.charCodeAt(this.pos + 1) === CODE_BANG && this.input.charCodeAt(this.pos + 2) === CODE_HYPHEN && this.input.charCodeAt(this.pos + 3) === CODE_HYPHEN;
3844
+ }
3845
+ parseComment() {
3846
+ const end = this.input.indexOf("-->", this.pos + 4);
3847
+ if (end === -1) this.fail("Unterminated comment");
3848
+ const comment = this.input.slice(this.pos + 4, end);
3849
+ this.pos = end + 3;
3850
+ return { comment };
3851
+ }
3852
+ parseName(what) {
3853
+ const start = this.pos;
3854
+ if (!isNameStart(this.input.charCodeAt(this.pos))) this.fail(`Expected ${what}`);
3855
+ this.pos++;
3856
+ while (isNameChar(this.input.charCodeAt(this.pos))) this.pos++;
3857
+ return this.input.slice(start, this.pos);
3858
+ }
3859
+ parseAttributeValue() {
3860
+ const quote = this.input.charCodeAt(this.pos);
3861
+ if (quote !== CODE_QUOTE && quote !== CODE_APOSTROPHE) this.fail("Expected a quoted attribute value");
3862
+ this.pos++;
3863
+ let value = "";
3864
+ let runStart = this.pos;
3865
+ for (;;) {
3866
+ const code = this.input.charCodeAt(this.pos);
3867
+ if (Number.isNaN(code)) this.fail("Unterminated attribute value");
3868
+ if (code === quote) {
3869
+ value += this.input.slice(runStart, this.pos);
3870
+ this.pos++;
3871
+ return value;
3872
+ }
3873
+ if (code === CODE_LESS_THAN) this.fail("Attribute values cannot contain a raw <");
3874
+ if (code === CODE_AMPERSAND) {
3875
+ value += this.input.slice(runStart, this.pos);
3876
+ value += this.parseReference();
3877
+ runStart = this.pos;
3878
+ } else this.pos++;
3879
+ }
3880
+ }
3881
+ /**
3882
+ * Resolves one `&...;` reference with the cursor on the ampersand. The
3883
+ * accepted forms are the five XML named entities and decimal or hex
3884
+ * character references, which covers everything observed in
3885
+ * Xcode-written schemes (`&quot;`, `&amp;`, `&apos;`, `&lt;`, `&gt;`,
3886
+ * and `&#10;`-style whitespace).
3887
+ */
3888
+ parseReference() {
3889
+ const start = this.pos;
3890
+ const end = this.input.indexOf(";", start + 1);
3891
+ if (end === -1 || end - start > 12) this.fail("Unterminated character reference");
3892
+ const body = this.input.slice(start + 1, end);
3893
+ this.pos = end + 1;
3894
+ if (body.charCodeAt(0) === CODE_HASH) {
3895
+ const isHex = body.charCodeAt(1) === 120 || body.charCodeAt(1) === 88;
3896
+ const digits = body.slice(isHex ? 2 : 1);
3897
+ const radix = isHex ? 16 : 10;
3898
+ const codePoint = Number.parseInt(digits, radix);
3899
+ if (digits.length === 0 || Number.isNaN(codePoint) || codePoint > 1114111) this.failAt(`Invalid character reference &${body};`, start);
3900
+ return String.fromCodePoint(codePoint);
3901
+ }
3902
+ const named = Object.hasOwn(NAMED_ENTITIES, body) ? NAMED_ENTITIES[body] : void 0;
3903
+ if (named == null) this.failAt(`Unknown entity &${body};`, start);
3904
+ return named;
3905
+ }
3906
+ /**
3907
+ * Skips whitespace and reports whether any was consumed, which attribute
3908
+ * parsing uses to require separation between attributes.
3909
+ */
3910
+ skipWhitespace() {
3911
+ const start = this.pos;
3912
+ while (isWhitespace(this.input.charCodeAt(this.pos))) this.pos++;
3913
+ return this.pos > start;
3914
+ }
3915
+ fail(message) {
3916
+ this.failAt(message, this.pos);
3917
+ }
3918
+ failAt(message, offset) {
3919
+ throw new XcschemeParseError(message, this.input, offset);
3920
+ }
3921
+ };
1375
3922
  //#endregion
1376
- export { PbxprojBuildError, PbxprojParseError, buildPbxproj, parsePbxproj };
3923
+ export { AggregateTarget, BuildPhase, BuildRule, CopyFilesDestination, Group, Isa, LegacyTarget, NativeTarget, PbxprojBuildError, PbxprojParseError, ProductType, ReferenceProxy, RootProject, SyncRootGroup, Target, VersionGroup, XcodeModelError, XcodeObject, XcodeProject, XcschemeBuildError, XcschemeParseError, buildPbxproj, buildXcscheme, createXcscheme, generateObjectId, isXcschemeElement, parsePbxproj, parseXcscheme, xcschemeElements };