From 1057121c40e6288c600766d61dd0b059be6aca6a Mon Sep 17 00:00:00 2001 From: Erisu Date: Wed, 24 Jun 2026 16:27:26 +0900 Subject: [PATCH 1/2] fix(addBuildPhase): adding of PBXBuildFile entries to lookup table - Add PBXBuildFile entry to filePathToBuildFile lookup table when a valid identifier is present. - Valid identifier is determined when the fileReference has either a path or name property value. - The path property takes priority when both path and name are defined. - When the path and name properties are not defined the entry is not added to the lookup table. --- lib/pbxProject.js | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/lib/pbxProject.js b/lib/pbxProject.js index 25bd8f3..b19b61d 100644 --- a/lib/pbxProject.js +++ b/lib/pbxProject.js @@ -896,18 +896,33 @@ PBXProject.prototype.addBuildPhase = function (filePathsArray, buildPhaseType, c const buildFile = buildFileSection[buildFileKey]; const fileReference = fileReferenceSection[buildFile.fileRef]; - if (!fileReference) continue; + // If a file reference does not define either the name or path property, + // it will not be included to the filePathToBuildFile lookup table. + // If the file reference defines both properies, the path property will + // take priority. + const fileIdentifier = fileReference?.path || fileReference?.name; - const pbxFileObj = new PBXFile(fileReference.path); + if (!fileIdentifier) { + continue; + } - filePathToBuildFile[fileReference.path] = { uuid: buildFileKey, basename: pbxFileObj.basename, group: pbxFileObj.group }; + const pbxFileObj = new PBXFile(fileIdentifier); + + // Creates a lookup table of existing PBXBuildFile entries keyed by fileIdentifier. + // Used later when adding files to the build phase to reuse the existing PBXBuildFile + // instead of generating new PBXBuildFile. + filePathToBuildFile[fileIdentifier] = { + uuid: buildFileKey, + basename: pbxFileObj.basename, + group: pbxFileObj.group + }; } for (let index = 0; index < filePathsArray.length; index++) { const filePath = filePathsArray[index]; const filePathQuoted = '"' + filePath + '"'; - const file = new PBXFile(filePath); + // Checks for an existing PBXBuildFile entry from the filePathToBuildFile lookup table. if (filePathToBuildFile[filePath]) { buildPhase.files.push(pbxBuildPhaseObj(filePathToBuildFile[filePath])); continue; @@ -916,10 +931,16 @@ PBXProject.prototype.addBuildPhase = function (filePathsArray, buildPhaseType, c continue; } + // If no PBXBuildFile entry is found in the lookup table, a new entry is created + // with a new UUID. It is then: + // 1. Added to the PBXFileReferenceSection + // 2. Added to the PBXBuildFileSection + // 3. Pushed onto the build phase file list. + const file = new PBXFile(filePath); file.uuid = this.generateUuid(); file.fileRef = this.generateUuid(); - this.addToPbxFileReferenceSection(file); // PBXFileReference - this.addToPbxBuildFileSection(file); // PBXBuildFile + this.addToPbxFileReferenceSection(file); + this.addToPbxBuildFileSection(file); buildPhase.files.push(pbxBuildPhaseObj(file)); } From 1a38327c3807cd07d838c2a2bfaec51bf454fb46 Mon Sep 17 00:00:00 2001 From: Erisu Date: Wed, 24 Jun 2026 18:34:33 +0900 Subject: [PATCH 2/2] doc(addBuildPhase): add notes & supporting typedef --- lib/pbxProject.js | 69 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/lib/pbxProject.js b/lib/pbxProject.js index b19b61d..2cd6619 100644 --- a/lib/pbxProject.js +++ b/lib/pbxProject.js @@ -30,6 +30,56 @@ const parser = require('./parser/pbxproj'); const plist = require('simple-plist'); const COMMENT_KEY = /_comment$/; +// MARK: Start of Typings + +/** + * A list of known build phase types. + * @typedef {'PBXCopyFilesBuildPhase' | + * 'PBXResourcesBuildPhase' | + * 'PBXFrameworksBuildPhase' | + * 'PBXHeadersBuildPhase' | + * 'PBXShellScriptBuildPhase' | + * 'PBXSourcesBuildPhase' + * } BuildPhaseType + */ + +/** + * The result object returned when adding a build phase. + * @typedef {object} AddBuildPhaseResults + * @property {string} uuid Build Phase UUID + * @property {object} buildPhase Build Phase object + */ + +/** + * A list of valid target types used for copy files build configurations. + * @typedef {'application' + * | 'app_extension' + * | 'bundle' + * | 'command_line_tool' + * | 'dynamic_library' + * | 'framework' + * | 'frameworks' + * | 'static_library' + * | 'unit_test_bundle' + * | 'watch_app' + * | 'watch2_app' + * | 'watch_extension' + * | 'watch2_extension' + * } CopyFilesTargetType + */ + +/** + * Options used when adding a PBXShellScriptBuildPhase. + * @typedef {object} ShellScriptOptions + * @property {string[]} inputPaths input paths + * @property {string[]} outputPaths output Paths + * @property {string} shellPath shell paths + * @property {0 | 1} runOnlyForDeploymentPostprocessing run script for install builds only + * @property {1} [alwaysOutOfDate] disables dependency analysis when set to true + */ + +// MARK: End of Typings + function PBXProject (filename) { if (!(this instanceof PBXProject)) { return new PBXProject(filename); } @@ -851,6 +901,25 @@ PBXProject.prototype.addTargetDependency = function (target, dependencyTargets) return { uuid: target, target: nativeTargets[target] }; }; +/** + * Add files to specific build phase by defining the build phase type. + * + * @example + * // Adding a file to Resource Build Phase. + * myProject.addBuildPhase( + * ['MyAssets.xcassets'], + * 'PBXResourcesBuildPhase', + * 'Resources' + * ); + * + * @param {string[]} filePathsArray collection of file paths + * @param {BuildPhaseType} buildPhaseType target build phase + * @param {string} comment name of the build phase. (e.g. "Copy My Files") + * @param {string} target UUID of the PBXNativeTarget (defaults to first target) + * @param {CopyFilesTargetType|ShellScriptOptions} optionsOrFolderType either sell script options or copy files target type + * @param {string} subfolderPath copy files subfolder path (default: "") + * @returns {AddBuildPhaseResults} object containing the build phase & uuid + */ PBXProject.prototype.addBuildPhase = function (filePathsArray, buildPhaseType, comment, target, optionsOrFolderType, subfolderPath) { let buildPhaseSection; const fileReferenceSection = this.pbxFileReferenceSection();