nativescript
Version:
Command-line interface for building NativeScript projects
889 lines • 54 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.IOSProjectService = exports.VisionSimulatorPlatformSdkName = exports.VisionDevicePlatformSdkName = exports.SimulatorPlatformSdkName = exports.DevicePlatformSdkName = void 0;
const path = require("path");
const shell = require("shelljs");
const _ = require("lodash");
const constants = require("../constants");
const constants_1 = require("../common/constants");
const helpers = require("../common/helpers");
const helpers_1 = require("../common/helpers");
const projectServiceBaseLib = require("./platform-project-service-base");
const plist_merge_patch_1 = require("plist-merge-patch");
const os_1 = require("os");
const plist = require("plist");
const fastGlob = require("fast-glob");
const constants_2 = require("../constants");
const helpers_2 = require("../common/helpers");
const yok_1 = require("../common/yok");
exports.DevicePlatformSdkName = "iphoneos";
exports.SimulatorPlatformSdkName = "iphonesimulator";
exports.VisionDevicePlatformSdkName = "xros";
exports.VisionSimulatorPlatformSdkName = "xrsimulator";
const FRAMEWORK_EXTENSIONS = [".framework", ".xcframework"];
const getPlatformSdkName = (buildData) => {
const forDevice = !buildData || buildData.buildForDevice || buildData.buildForAppStore;
const isvisionOS = yok_1.injector
.resolve("devicePlatformsConstants")
.isvisionOS(buildData.platform);
if (isvisionOS) {
return forDevice
? exports.VisionDevicePlatformSdkName
: exports.VisionSimulatorPlatformSdkName;
}
return forDevice ? exports.DevicePlatformSdkName : exports.SimulatorPlatformSdkName;
};
const getConfigurationName = (release) => {
return release ? constants_1.Configurations.Release : constants_1.Configurations.Debug;
};
class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase {
constructor($fs, $options, $childProcess, $cocoapodsService, $errors, $logger, $injector, $projectDataService, $devicePlatformsConstants, $hostInfo, $xcprojService, $iOSProvisionService, $iOSSigningService, $pbxprojDomXcode, $xcode, $iOSEntitlementsService, $platformEnvironmentRequirements, $plistParser, $xcconfigService, $xcodebuildService, $iOSExtensionsService, $iOSWatchAppService, $iOSNativeTargetService, $sysInfo, $tempService, $spmService, $mobileHelper, $projectConfigService) {
super($fs, $projectDataService);
this.$options = $options;
this.$childProcess = $childProcess;
this.$cocoapodsService = $cocoapodsService;
this.$errors = $errors;
this.$logger = $logger;
this.$injector = $injector;
this.$devicePlatformsConstants = $devicePlatformsConstants;
this.$hostInfo = $hostInfo;
this.$xcprojService = $xcprojService;
this.$iOSProvisionService = $iOSProvisionService;
this.$iOSSigningService = $iOSSigningService;
this.$pbxprojDomXcode = $pbxprojDomXcode;
this.$xcode = $xcode;
this.$iOSEntitlementsService = $iOSEntitlementsService;
this.$platformEnvironmentRequirements = $platformEnvironmentRequirements;
this.$plistParser = $plistParser;
this.$xcconfigService = $xcconfigService;
this.$xcodebuildService = $xcodebuildService;
this.$iOSExtensionsService = $iOSExtensionsService;
this.$iOSWatchAppService = $iOSWatchAppService;
this.$iOSNativeTargetService = $iOSNativeTargetService;
this.$sysInfo = $sysInfo;
this.$tempService = $tempService;
this.$spmService = $spmService;
this.$mobileHelper = $mobileHelper;
this.$projectConfigService = $projectConfigService;
this._platformsDirCache = null;
this._platformData = null;
}
getPlatformData(projectData) {
var _a;
if (!projectData && !this._platformData) {
throw new Error("First call of getPlatformData without providing projectData.");
}
if (projectData &&
projectData.platformsDir &&
this._platformsDirCache !== projectData.platformsDir) {
const platform = this.$mobileHelper.normalizePlatformName((_a = this.$options.platformOverride) !== null && _a !== void 0 ? _a : this.$devicePlatformsConstants.iOS);
const projectRoot = this.$options.hostProjectPath
? this.$options.hostProjectPath
: path.join(projectData.platformsDir, platform.toLowerCase());
const runtimePackage = this.$projectDataService.getRuntimePackage(projectData.projectDir, platform.toLowerCase());
this._platformData = {
frameworkPackageName: runtimePackage.name,
normalizedPlatformName: platform,
platformNameLowerCase: platform.toLowerCase(),
appDestinationDirectoryPath: path.join(projectRoot, projectData.projectName),
platformProjectService: this,
projectRoot: projectRoot,
getBuildOutputPath: (options) => {
const config = getConfigurationName(!options || options.release);
return path.join(projectRoot, constants.BUILD_DIR, `${config}-${getPlatformSdkName(options)}`);
},
getValidBuildOutputData: (buildOptions) => {
const forDevice = !buildOptions ||
!!buildOptions.buildForDevice ||
!!buildOptions.buildForAppStore;
if (forDevice) {
const ipaFileName = _.find(this.$fs.readDirectory(this._platformData.getBuildOutputPath(buildOptions)), (entry) => path.extname(entry) === ".ipa");
return {
packageNames: [ipaFileName, `${projectData.projectName}.ipa`],
};
}
return {
packageNames: [
`${projectData.projectName}.app`,
`${projectData.projectName}.zip`,
],
};
},
frameworkDirectoriesExtensions: FRAMEWORK_EXTENSIONS,
frameworkDirectoriesNames: [
"Metadata",
"metadataGenerator",
"NativeScript",
"internal",
],
targetedOS: ["darwin"],
configurationFileName: constants.INFO_PLIST_FILE_NAME,
configurationFilePath: path.join(projectRoot, projectData.projectName, projectData.projectName + `-${constants.INFO_PLIST_FILE_NAME}`),
relativeToFrameworkConfigurationFilePath: path.join("__PROJECT_NAME__", "__PROJECT_NAME__-Info.plist"),
fastLivesyncFileExtensions: [
".tiff",
".tif",
".jpg",
"jpeg",
"gif",
".png",
".bmp",
".BMPf",
".ico",
".cur",
".xbm",
], // https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIImage_Class/
};
}
return this._platformData;
}
async validateOptions(projectId, provision, teamId) {
if (provision && teamId) {
this.$errors.fail("The options --provision and --teamId are mutually exclusive.");
}
if (provision === true) {
await this.$iOSProvisionService.listProvisions(projectId);
this.$errors.fail("Please provide provisioning profile uuid or name with the --provision option.");
}
if (teamId === true) {
await this.$iOSProvisionService.listTeams();
this.$errors.fail("Please provide team id or team name with the --teamId options.");
}
return true;
}
getAppResourcesDestinationDirectoryPath(projectData) {
return path.join(this.getPlatformData(projectData).projectRoot, projectData.projectName, "Resources");
}
async validate(projectData, options, notConfiguredEnvOptions) {
if (!this.$hostInfo.isDarwin) {
return;
}
const checkEnvironmentRequirementsOutput = await this.$platformEnvironmentRequirements.checkEnvironmentRequirements({
platform: this.getPlatformData(projectData).normalizedPlatformName,
projectDir: projectData.projectDir,
options,
notConfiguredEnvOptions,
});
if (checkEnvironmentRequirementsOutput &&
checkEnvironmentRequirementsOutput.canExecute) {
const xcodeWarning = await this.$sysInfo.getXcodeWarning();
if (xcodeWarning) {
this.$logger.warn(xcodeWarning);
}
}
return {
checkEnvironmentRequirementsOutput,
};
}
async createProject(frameworkDir, frameworkVersion, projectData) {
this.$fs.ensureDirectoryExists(path.join(this.getPlatformData(projectData).projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER));
shell.cp("-R", path.join(frameworkDir, "*"), this.getPlatformData(projectData).projectRoot);
}
//TODO: plamen5kov: revisit this method, might have unnecessary/obsolete logic
async interpolateData(projectData) {
const projectRootFilePath = path.join(this.getPlatformData(projectData).projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER);
// Starting with NativeScript for iOS 1.6.0, the project Info.plist file resides not in the platform project,
// but in the hello-world app template as a platform specific resource.
if (this.$fs.exists(path.join(projectRootFilePath, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER + "-Info.plist"))) {
this.replaceFileName("-Info.plist", projectRootFilePath, projectData);
}
this.replaceFileName("-Prefix.pch", projectRootFilePath, projectData);
if (this.$fs.exists(path.join(projectRootFilePath, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER + ".entitlements"))) {
this.replaceFileName(".entitlements", projectRootFilePath, projectData);
}
const xcschemeDirPath = path.join(this.getPlatformData(projectData).projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER +
constants_2.IosProjectConstants.XcodeProjExtName, "xcshareddata/xcschemes");
const xcschemeFilePath = path.join(xcschemeDirPath, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER +
constants_2.IosProjectConstants.XcodeSchemeExtName);
if (this.$fs.exists(xcschemeFilePath)) {
this.$logger.trace("Found shared scheme at xcschemeFilePath, renaming to match project name.");
this.$logger.trace("Checkpoint 0");
this.replaceFileContent(xcschemeFilePath, projectData);
this.$logger.trace("Checkpoint 1");
this.replaceFileName(constants_2.IosProjectConstants.XcodeSchemeExtName, xcschemeDirPath, projectData);
this.$logger.trace("Checkpoint 2");
}
else {
this.$logger.trace("Copying xcscheme from template not found at " + xcschemeFilePath);
}
this.replaceFileName(constants_2.IosProjectConstants.XcodeProjExtName, this.getPlatformData(projectData).projectRoot, projectData);
const pbxprojFilePath = this.getPbxProjPath(projectData);
this.replaceFileContent(pbxprojFilePath, projectData);
const internalDirPath = path.join(projectRootFilePath, "..", "internal");
const xcframeworksFilePath = path.join(internalDirPath, "XCFrameworks.zip");
if (this.$fs.exists(xcframeworksFilePath)) {
await this.$fs.unzip(xcframeworksFilePath, internalDirPath);
this.$fs.deleteFile(xcframeworksFilePath);
}
}
interpolateConfigurationFile(projectData) {
return undefined;
}
async cleanProject(projectRoot, projectData) {
return null;
}
afterCreateProject(projectRoot, projectData) {
this.$fs.rename(path.join(projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER), path.join(projectRoot, projectData.projectName));
}
async buildProject(projectRoot, projectData, buildData) {
const platformData = this.getPlatformData(projectData);
const handler = (data) => {
this.emit(constants.BUILD_OUTPUT_EVENT_NAME, data);
};
if (buildData.buildForDevice) {
await this.$iOSSigningService.setupSigningForDevice(projectRoot, projectData, buildData);
await (0, helpers_1.attachAwaitDetach)(constants.BUILD_OUTPUT_EVENT_NAME, this.$childProcess, handler, this.$xcodebuildService.buildForDevice(platformData, projectData, buildData));
}
else if (buildData.buildForAppStore) {
await (0, helpers_1.attachAwaitDetach)(constants.BUILD_OUTPUT_EVENT_NAME, this.$childProcess, handler, this.$xcodebuildService.buildForAppStore(platformData, projectData, buildData));
}
else {
await (0, helpers_1.attachAwaitDetach)(constants.BUILD_OUTPUT_EVENT_NAME, this.$childProcess, handler, this.$xcodebuildService.buildForSimulator(platformData, projectData, buildData));
}
this.validateApplicationIdentifier(projectData);
}
isPlatformPrepared(projectRoot, projectData) {
return this.$fs.exists(path.join(projectRoot, projectData.projectName, constants.APP_FOLDER_NAME));
}
cleanDeviceTempFolder(deviceIdentifier) {
return Promise.resolve();
}
async isDynamicFramework(frameworkPath) {
const isDynamicFrameworkBundle = async (bundlePath, frameworkName) => {
const frameworkBinaryPath = path.join(bundlePath, frameworkName);
const fileResult = (await this.$childProcess.spawnFromEvent("file", [frameworkBinaryPath], "close")).stdout;
const isDynamicallyLinked = _.includes(fileResult, "dynamically linked");
return isDynamicallyLinked;
};
if (path.extname(frameworkPath) === ".xcframework") {
let isDynamic = true;
const plistJson = this.$plistParser.parseFileSync(path.join(frameworkPath, "Info.plist"));
for (const library of plistJson.AvailableLibraries) {
const singlePlatformFramework = path.join(frameworkPath, library.LibraryIdentifier, library.LibraryPath);
if (this.$fs.exists(singlePlatformFramework)) {
const frameworkName = path.basename(singlePlatformFramework, path.extname(singlePlatformFramework));
isDynamic = await isDynamicFrameworkBundle(singlePlatformFramework, frameworkName);
break;
}
}
return isDynamic;
}
else {
const frameworkName = path.basename(frameworkPath, path.extname(frameworkPath));
return await isDynamicFrameworkBundle(frameworkPath, frameworkName);
}
}
async addFramework(frameworkPath, projectData) {
if (this.$hostInfo.isWindows) {
return;
}
this.validateFramework(frameworkPath);
const project = this.createPbxProj(projectData);
const frameworkAddOptions = { customFramework: true };
const dynamic = await this.isDynamicFramework(frameworkPath);
if (dynamic) {
frameworkAddOptions["embed"] = true;
frameworkAddOptions["sign"] = true;
}
if (this.$options.hostProjectPath) {
// always mark xcframeworks for embedding
frameworkAddOptions["embed"] = true;
frameworkAddOptions["sign"] = false;
}
// Note: we used to prepend "$(SRCROOT)/" to the framework path, but seems like it's not needed anymore
// "$(SRCROOT)/" +
const frameworkRelativePath = this.getLibSubpathRelativeToProjectPath(frameworkPath, projectData);
project.addFramework(frameworkRelativePath, frameworkAddOptions);
// filePathsArray, buildPhaseType, comment, target, optionsOrFolderType, subfolderPath
// project.addBuildPhase(
// [],
// "PBXShellScriptBuildPhase",
// "Debug SRCROOT",
// undefined,
// {
// shellPath: "/bin/sh",
// shellScript: `echo "SRCROOT: $SRCROOT"`,
// }
// );
this.savePbxProj(project, projectData);
}
async addStaticLibrary(staticLibPath, projectData) {
// Copy files to lib folder.
const libraryName = path.basename(staticLibPath, ".a");
const headersSubpath = path.join(path.dirname(staticLibPath), "include", libraryName);
// Add static library to project file and setup header search paths
const project = this.createPbxProj(projectData);
const relativeStaticLibPath = this.getLibSubpathRelativeToProjectPath(staticLibPath, projectData);
project.addFramework(relativeStaticLibPath);
const relativeHeaderSearchPath = path.join(this.getLibSubpathRelativeToProjectPath(headersSubpath, projectData));
project.addToHeaderSearchPaths({ relativePath: relativeHeaderSearchPath });
this.generateModulemap(headersSubpath, libraryName);
this.savePbxProj(project, projectData);
}
async prepareProject(projectData, prepareData) {
const projectRoot = this.$options.hostProjectPath
? this.$options.hostProjectPath
: path.join(projectData.platformsDir, this.$devicePlatformsConstants.iOS.toLowerCase());
const platformData = this.getPlatformData(projectData);
const pluginsData = this.getAllProductionPlugins(projectData);
const pbxProjPath = this.getPbxProjPath(projectData);
this.$iOSExtensionsService.removeExtensions({ pbxProjPath });
await this.addExtensions(projectData, pluginsData);
const resourcesDirectoryPath = projectData.getAppResourcesDirectoryPath();
const provision = prepareData && prepareData.provision;
const teamId = prepareData && prepareData.teamId;
if (provision) {
await this.$iOSSigningService.setupSigningFromProvision(projectRoot, projectData, provision, prepareData.mobileProvisionData);
}
if (teamId) {
await this.$iOSSigningService.setupSigningFromTeam(projectRoot, projectData, teamId);
}
const project = this.createPbxProj(projectData);
if (this.$options.hostProjectPath) {
try {
project.addPbxGroup([], "NativeScript", "NativeScript", null, {
isMain: true,
filesRelativeToProject: true,
uuid: "NATIVESCRIPTNATIVESCRIPT",
});
/**
* 1. Add platforms/ios/{projectname}/app build to the host app
*/
// Note: allow customization of this targetFolderName
// const targetFolderName = "app";
const buildFolderPath = path.join(this.$options.hostProjectPath, projectData.projectName);
project.addResourceFile(buildFolderPath, {}, "NATIVESCRIPTNATIVESCRIPT");
// filePathsArray, buildPhaseType, comment, target, optionsOrFolderType, subfolderPath
// project.addBuildPhase(
// [],
// "PBXShellScriptBuildPhase",
// "Copy Metadata (DEBUG)",
// undefined,
// {
// shellPath: "/bin/sh",
// shellScript: `cp ./platforms/ios/build/Debug-iphonesimulator/metadata-arm64.bin $CONFIGURATION_BUILD_DIR`,
// outputPaths: [
// JSON.stringify("$(CONFIGURATION_BUILD_DIR)/metadata-arm64.bin"),
// // JSON.stringify("$(CONFIGURATION_BUILD_DIR)/metadata-arm64e.bin"),
// // JSON.stringify("$(CONFIGURATION_BUILD_DIR)/metadata-i386.bin"),
// // JSON.stringify("$(CONFIGURATION_BUILD_DIR)/metadata-x86_64.bin"),
// ],
// }
// );
const metadataPath = path.relative(this.$options.hostProjectPath, buildFolderPath);
project.addToOtherLinkerFlags(JSON.stringify(`-sectcreate __DATA __TNSMetadata "${metadataPath}/metadata-arm64.bin"`));
// // no shorthand way to get UUID of build phase that i can tell
// // methods return the phase as an object but ommitted the actual key (uuid we need)
// // this does it same way nativescript-dev-xcode does but gets the uuid we need
// const resourcesBuildPhaseKeys = Object.keys(
// project.hash.project.objects["PBXResourcesBuildPhase"]
// );
// // console.log('resourcesBuildPhaseKeys:', resourcesBuildPhaseKeys);
// const buildPhaseUUID = resourcesBuildPhaseKeys[0];
// const comment = `${targetFolderName} in Resources`;
// project.hash.project.objects["PBXResourcesBuildPhase"][
// buildPhaseUUID
// ].files.forEach((f: any) => {
// console.log(f);
// });
// if (
// !project.hash.project.objects["PBXResourcesBuildPhase"][
// buildPhaseUUID
// ].files.find((f: any) => f.comment === comment)
// ) {
// project.addResourceFile(buildFolderPath, {}, buildPhaseUUID);
// }
/**
* 2. Ensure metadata is copied as a file
* The apps metadata-{arch}.bin should be added as a file reference.
*/
// TODO
this.savePbxProj(project, projectData);
}
catch (err) {
this.$logger.trace("Error adding NativeScript group to host project", err);
}
}
const resources = project.pbxGroupByName("Resources");
if (resources && !this.$options.hostProjectPath) {
const references = project.pbxFileReferenceSection();
const xcodeProjectImages = _.map(resources.children, (resource) => this.replace(references[resource.value].name));
this.$logger.trace("Images from Xcode project");
this.$logger.trace(xcodeProjectImages);
const appResourcesImages = this.$fs.readDirectory(this.getAppResourcesDestinationDirectoryPath(projectData));
this.$logger.trace("Current images from App_Resources");
this.$logger.trace(appResourcesImages);
const imagesToAdd = _.difference(appResourcesImages, xcodeProjectImages);
this.$logger.trace(`New images to add into xcode project: ${imagesToAdd.join(", ")}`);
_.each(imagesToAdd, (image) => project.addResourceFile(path.relative(this.getPlatformData(projectData).projectRoot, path.join(this.getAppResourcesDestinationDirectoryPath(projectData), image))));
const imagesToRemove = _.difference(xcodeProjectImages, appResourcesImages);
this.$logger.trace(`Images to remove from xcode project: ${imagesToRemove.join(", ")}`);
_.each(imagesToRemove, (image) => project.removeResourceFile(path.join(this.getAppResourcesDestinationDirectoryPath(projectData), image)));
this.savePbxProj(project, projectData);
let resourcesNativeCodePath = path.join(resourcesDirectoryPath, platformData.normalizedPlatformName, constants.NATIVE_SOURCE_FOLDER);
if (!this.$fs.exists(resourcesNativeCodePath)) {
resourcesNativeCodePath = path.join(resourcesDirectoryPath, this.$devicePlatformsConstants.iOS, constants.NATIVE_SOURCE_FOLDER);
}
await this.prepareNativeSourceCode(constants.TNS_NATIVE_SOURCE_GROUP_NAME, resourcesNativeCodePath, projectData);
const nativeSource = this.$projectConfigService.getValue(`${this._platformData.platformNameLowerCase}.NativeSource`, []);
if (nativeSource === null || nativeSource === void 0 ? void 0 : nativeSource.length) {
for (const source of nativeSource) {
await this.prepareNativeSourceCode(source.name, source.path, projectData);
}
}
}
this.$iOSWatchAppService.removeWatchApp({ pbxProjPath });
const addedWatchApp = await this.$iOSWatchAppService.addWatchAppFromPath({
watchAppFolderPath: path.join(resourcesDirectoryPath, platformData.normalizedPlatformName),
projectData,
platformData,
pbxProjPath,
});
if (addedWatchApp) {
this.$logger.warn("The support for Apple Watch App is currently in Beta. For more information about the current development state and any known issues, please check the relevant GitHub issue: https://github.com/NativeScript/nativescript-cli/issues/4589");
}
}
prepareAppResources(projectData) {
const platformData = this.getPlatformData(projectData);
const projectAppResourcesPath = projectData.getAppResourcesDirectoryPath(projectData.projectDir);
const platformsAppResourcesPath = this.getAppResourcesDestinationDirectoryPath(projectData);
this.$fs.deleteDirectory(platformsAppResourcesPath);
this.$fs.ensureDirectoryExists(platformsAppResourcesPath);
const platformAppResourcesPath = path.join(projectAppResourcesPath, platformData.normalizedPlatformName);
// this allows App_Resources/visionOS
if (this.$fs.exists(platformAppResourcesPath)) {
this.$fs.copyFile(path.join(platformAppResourcesPath, "*"), platformsAppResourcesPath);
}
else {
// otherwise falls back to App_Resources/iOS
this.$fs.copyFile(path.join(projectAppResourcesPath, this.$devicePlatformsConstants.iOS, "*"), platformsAppResourcesPath);
}
this.$fs.deleteFile(path.join(platformsAppResourcesPath, platformData.configurationFileName));
this.$fs.deleteFile(path.join(platformsAppResourcesPath, constants.PODFILE_NAME));
this.$fs.deleteDirectory(path.join(platformsAppResourcesPath, constants.NATIVE_SOURCE_FOLDER));
this.$fs.deleteDirectory(path.join(platformsAppResourcesPath, constants.NATIVE_EXTENSION_FOLDER));
this.$fs.deleteDirectory(path.join(platformsAppResourcesPath, "watchapp"));
this.$fs.deleteDirectory(path.join(platformsAppResourcesPath, "watchextension"));
}
async processConfigurationFilesFromAppResources(projectData, opts) {
await this.mergeInfoPlists(projectData, opts);
await this.$iOSEntitlementsService.merge(projectData);
await this.mergeProjectXcconfigFiles(projectData);
}
ensureConfigurationFileInAppResources() {
return null;
}
async mergeInfoPlists(projectData, buildOptions) {
const projectDir = projectData.projectDir;
const infoPlistPath = path.join(projectData.appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, this.getPlatformData(projectData).configurationFileName);
this.ensureConfigurationFileInAppResources();
const reporterTraceMessage = "Info.plist:";
const reporter = {
log: (txt) => this.$logger.trace(`${reporterTraceMessage} ${txt}`),
warn: (txt) => this.$logger.warn(`${reporterTraceMessage} ${txt}`),
};
const session = new plist_merge_patch_1.PlistSession(reporter);
const makePatch = (plistPath) => {
if (!this.$fs.exists(plistPath)) {
this.$logger.trace("No plist found at: " + plistPath);
return;
}
this.$logger.trace("Schedule merge plist at: " + plistPath);
session.patch({
name: path.relative(projectDir, plistPath),
read: () => this.$fs.readText(plistPath),
});
};
const allPlugins = this.getAllProductionPlugins(projectData);
for (const plugin of allPlugins) {
const pluginInfoPlistPath = path.join(plugin.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME), this.getPlatformData(projectData).configurationFileName);
makePatch(pluginInfoPlistPath);
}
makePatch(infoPlistPath);
if (projectData.projectIdentifiers && projectData.projectIdentifiers.ios) {
session.patch({
name: "CFBundleIdentifier from package.json nativescript.id",
read: () => `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</dict>
</plist>`,
});
}
if (!buildOptions.release &&
projectData.projectIdentifiers &&
projectData.projectIdentifiers.ios) {
session.patch({
name: "CFBundleURLTypes from package.json nativescript.id",
read: () => `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>${projectData.projectIdentifiers.ios.replace(/[^A-Za-z0-9]/g, "")}</string>
</array>
</dict>
</array>
</dict>
</plist>`,
});
}
const plistContent = session.build();
this.$logger.trace("Info.plist: Write to: " +
this.getPlatformData(projectData).configurationFilePath);
this.$fs.writeFile(this.getPlatformData(projectData).configurationFilePath, plistContent);
}
getAllProductionPlugins(projectData) {
return (this.$injector.resolve("pluginsService")).getAllProductionPlugins(projectData, this.getPlatformData(projectData).platformNameLowerCase);
}
replace(name) {
if (_.startsWith(name, '"')) {
name = name.substr(1, name.length - 2);
}
return name.replace(/\\\"/g, '"');
}
getLibSubpathRelativeToProjectPath(targetPath, projectData) {
const projectRoot = this.getPlatformData(projectData).projectRoot;
const frameworkPath = path.relative(projectRoot, targetPath);
// console.log({
// targetPath,
// projectRoot,
// frameworkPath,
// resolved: path.resolve(projectRoot, frameworkPath),
// });
return frameworkPath;
}
getPbxProjPath(projectData) {
if (this.$options.hostProjectPath) {
let xcodeProjectPath = this.$xcprojService.findXcodeProject(this.$options.hostProjectPath);
if (!xcodeProjectPath) {
this.$errors.fail("Xcode project not found at the specified directory");
}
return path.join(xcodeProjectPath, "project.pbxproj");
}
return path.join(this.$xcprojService.getXcodeprojPath(projectData, this.getPlatformData(projectData).projectRoot), "project.pbxproj");
}
createPbxProj(projectData) {
const project = new this.$xcode.project(this.getPbxProjPath(projectData));
project.parseSync();
return project;
}
savePbxProj(project, projectData, omitEmptyValues) {
return this.$fs.writeFile(this.getPbxProjPath(projectData), project.writeSync({ omitEmptyValues }));
}
async preparePluginNativeCode(pluginData, projectData, opts) {
const pluginPlatformsFolderPath = pluginData.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME);
const sourcePath = path.join(pluginPlatformsFolderPath, "src");
await this.prepareNativeSourceCode(pluginData.name, sourcePath, projectData);
await this.prepareResources(pluginPlatformsFolderPath, pluginData, projectData);
await this.prepareFrameworks(pluginPlatformsFolderPath, pluginData, projectData);
await this.prepareStaticLibs(pluginPlatformsFolderPath, pluginData, projectData);
}
async removePluginNativeCode(pluginData, projectData) {
const pluginPlatformsFolderPath = pluginData.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME);
this.removeNativeSourceCode(pluginPlatformsFolderPath, pluginData, projectData);
this.removeFrameworks(pluginPlatformsFolderPath, pluginData, projectData);
this.removeStaticLibs(pluginPlatformsFolderPath, pluginData, projectData);
const projectRoot = this.getPlatformData(projectData).projectRoot;
this.$cocoapodsService.removePodfileFromProject(pluginData.name, this.$cocoapodsService.getPluginPodfilePath(pluginData), projectData, projectRoot);
}
async handleNativeDependenciesChange(projectData, opts) {
const platformData = this.getPlatformData(projectData);
const pluginsData = this.getAllProductionPlugins(projectData);
this.setProductBundleIdentifier(projectData);
await this.applyPluginsCocoaPods(pluginsData, projectData, platformData);
await this.$cocoapodsService.applyPodfileFromAppResources(projectData, platformData);
await this.$cocoapodsService.applyPodfileArchExclusions(projectData, platformData);
await this.$cocoapodsService.applyPodfileFromExtensions(projectData, platformData);
const projectPodfilePath = this.$cocoapodsService.getProjectPodfilePath(platformData.projectRoot);
if (this.$fs.exists(projectPodfilePath)) {
await this.$cocoapodsService.executePodInstall(platformData.projectRoot, this.$xcprojService.getXcodeprojPath(projectData, platformData.projectRoot));
// The `pod install` command adds a new target to the .pbxproject. This target adds additional build phases to Xcode project.
// Some of these phases relies on env variables (like PODS_PODFILE_DIR_PATH or PODS_ROOT).
// These variables are produced from merge of pod's xcconfig file and project's xcconfig file.
// So the correct order is `pod install` to be executed before merging pod's xcconfig file.
await this.$cocoapodsService.mergePodXcconfigFile(projectData, platformData, opts);
}
const pluginSpmPackages = [];
for (const plugin of pluginsData) {
if (plugin.fullPath) {
const pluginConfigPath = path.join(plugin.fullPath, constants.CONFIG_FILE_NAME_TS);
if (this.$fs.exists(pluginConfigPath)) {
const config = this.$projectConfigService.readConfig(plugin.fullPath);
const packages = _.get(config, `${platformData.platformNameLowerCase}.SPMPackages`, []);
if (packages.length) {
pluginSpmPackages.push(...packages);
}
}
}
}
await this.$spmService.applySPMPackages(platformData, projectData, pluginSpmPackages);
}
beforePrepareAllPlugins(projectData, dependencies) {
return Promise.resolve(dependencies);
}
async checkForChanges(changesInfo, prepareData, projectData) {
const { provision, teamId } = prepareData;
const hasProvision = provision !== undefined;
const hasTeamId = teamId !== undefined;
if (hasProvision || hasTeamId) {
// Check if the native project's signing is set to the provided provision...
const pbxprojPath = this.getPbxProjPath(projectData);
if (this.$fs.exists(pbxprojPath)) {
const xcode = this.$pbxprojDomXcode.Xcode.open(pbxprojPath);
const signing = xcode.getSigning(projectData.projectName);
if (hasProvision) {
if (signing && signing.style === "Manual") {
for (const name in signing.configurations) {
const config = signing.configurations[name];
if (config.uuid !== provision && config.name !== provision) {
changesInfo.signingChanged = true;
break;
}
}
}
else {
// Specifying provisioning profile requires "Manual" signing style.
// If the current signing style was not "Manual" it was probably "Automatic" or,
// it was not uniform for the debug and release build configurations.
changesInfo.signingChanged = true;
}
}
if (hasTeamId) {
if (signing && signing.style === "Automatic") {
if (signing.team !== teamId) {
const teamIdsForName = await this.$iOSProvisionService.getTeamIdsWithName(teamId);
if (!teamIdsForName.some((id) => id === signing.team)) {
changesInfo.signingChanged = true;
}
}
}
else {
// Specifying team id or name requires "Automatic" signing style.
// If the current signing style was not "Automatic" it was probably "Manual".
changesInfo.signingChanged = true;
}
}
}
else {
changesInfo.signingChanged = true;
}
}
}
getDeploymentTarget(projectData) {
const target = this.$xcconfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "IPHONEOS_DEPLOYMENT_TARGET");
return target;
}
setProductBundleIdentifier(projectData) {
const project = this.createPbxProj(projectData);
this.$iOSNativeTargetService.setXcodeTargetBuildConfigurationProperties([
{
name: "PRODUCT_BUNDLE_IDENTIFIER",
value: `"${projectData.projectIdentifiers.ios}"`,
},
], projectData.projectName, project);
this.savePbxProj(project, projectData);
}
getAllLibsForPluginWithFileExtension(pluginData, fileExtension) {
const fileExtensions = _.isArray(fileExtension)
? fileExtension
: [fileExtension];
const filterCallback = (fileName, pluginPlatformsFolderPath) => fileExtensions.indexOf(path.extname(fileName)) !== -1;
return this.getAllNativeLibrariesForPlugin(pluginData, IOSProjectService.IOS_PLATFORM_NAME, filterCallback);
}
validateFramework(libraryPath) {
const infoPlistPath = path.join(libraryPath, constants.INFO_PLIST_FILE_NAME);
if (!this.$fs.exists(infoPlistPath)) {
this.$errors.fail("The bundle at %s does not contain an Info.plist file.", libraryPath);
}
const plistJson = this.$plistParser.parseFileSync(infoPlistPath);
const packageType = plistJson["CFBundlePackageType"];
if (packageType !== "FMWK" && packageType !== "XFWK") {
this.$errors.fail("The bundle at %s does not appear to be a dynamic framework.", libraryPath);
}
}
replaceFileContent(file, projectData) {
const fileContent = this.$fs.readText(file);
const replacedContent = helpers.stringReplaceAll(fileContent, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER, projectData.projectName);
this.$fs.writeFile(file, replacedContent);
}
replaceFileName(fileNamePart, fileRootLocation, projectData) {
const oldFileName = IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER + fileNamePart;
const newFileName = projectData.projectName + fileNamePart;
this.$fs.rename(path.join(fileRootLocation, oldFileName), path.join(fileRootLocation, newFileName));
}
async prepareNativeSourceCode(groupName, sourceFolderPath, projectData) {
const project = this.createPbxProj(projectData);
const group = await this.getRootGroup(groupName, sourceFolderPath);
project.addPbxGroup(group.files, group.name, group.path, null, {
isMain: true,
filesRelativeToProject: true,
});
project.addToHeaderSearchPaths(group.path);
const headerFiles = this.$fs.exists(sourceFolderPath)
? this.$fs.enumerateFilesInDirectorySync(sourceFolderPath, (file, stat) => stat.isDirectory() || path.extname(file) === ".h")
: [];
if (headerFiles.length > 0 &&
!this.$fs.exists(path.join(sourceFolderPath, "module.modulemap"))) {
this.$logger.warn(`warning: Directory ${sourceFolderPath} with native iOS source code doesn't contain a modulemap file. Metadata for it will not be generated and it will not be accessible from JavaScript. To learn more see https://docs.nativescript.org/guides/ios-source-code`);
}
this.savePbxProj(project, projectData);
}
async addExtensions(projectData, pluginsData) {
const resorcesExtensionsPath = path.join(projectData.getAppResourcesDirectoryPath(), this.getPlatformData(projectData).normalizedPlatformName, constants.NATIVE_EXTENSION_FOLDER);
const platformData = this.getPlatformData(projectData);
const pbxProjPath = this.getPbxProjPath(projectData);
const addedExtensionsFromResources = await this.$iOSExtensionsService.addExtensionsFromPath({
extensionsFolderPath: resorcesExtensionsPath,
projectData,
platformData,
pbxProjPath,
});
let addedExtensionsFromPlugins = false;
for (const pluginIndex in pluginsData) {
const pluginData = pluginsData[pluginIndex];
const pluginPlatformsFolderPath = pluginData.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME);
const extensionPath = path.join(pluginPlatformsFolderPath, constants.NATIVE_EXTENSION_FOLDER);
const addedExtensionFromPlugin = await this.$iOSExtensionsService.addExtensionsFromPath({
extensionsFolderPath: extensionPath,
projectData,
platformData,
pbxProjPath,
});
addedExtensionsFromPlugins =
addedExtensionsFromPlugins || addedExtensionFromPlugin;
}
if (addedExtensionsFromResources || addedExtensionsFromPlugins) {
this.$logger.warn("Let us know if there are other Extension features you'd like! https://github.com/NativeScript/NativeScript/issues");
}
}
async getRootGroup(name, rootPath) {
const filePathsArr = [];
const rootGroup = {
name: name,
files: filePathsArr,
path: rootPath,
};
if (fastGlob.isDynamicPattern(rootPath)) {
const projectRoot = this.$projectDataService.getProjectData().projectDir;
const filePaths = await fastGlob(rootPath);
for (const filePath of filePaths) {
const sourceFilePath = path.normalize(path.join(projectRoot, filePath));
filePathsArr.push(sourceFilePath);
}
}
else {
if (this.$fs.exists(rootPath)) {
const stats = this.$fs.getFsStats(rootPath);
if (stats.isDirectory() && !this.$fs.isEmptyDir(rootPath)) {
this.$fs.readDirectory(rootPath).forEach((fileName) => {
const filePath = path.join(rootGroup.path, fileName);
filePathsArr.push(filePath);
});
}
}
}
return rootGroup;
}
async prepareResources(pluginPlatformsFolderPath, pluginData, projectData) {
const project = this.createPbxProj(projectData);
const resourcesPath = path.join(pluginPlatformsFolderPath, "Resources");
if (this.$fs.exists(resourcesPath) && !this.$fs.isEmptyDir(resourcesPath)) {
for (const fileName of this.$fs.readDirectory(resourcesPath)) {
const filePath = path.join(resourcesPath, fileName);
project.addResourceFile(filePath);
}
}
this.savePbxProj(project, projectData);
}
async prepareFrameworks(pluginPlatformsFolderPath, pluginData, projectData) {
for (const fileName of this.getAllLibsForPluginWithFileExtension(pluginData, FRAMEWORK_EXTENSIONS)) {
await this.addFramework(path.join(pluginPlatformsFolderPath, fileName), projectData);
}
}
async prepareStaticLibs(pluginPlatformsFolderPath, pluginData, projectData) {
for (const fileName of this.getAllLibsForPluginWithFileExtension(pluginData, ".a")) {
await this.addStaticLibrary(path.join(pluginPlatformsFolderPath, fileName), projectData);
}
}
async removeNativeSourceCode(pluginPlatformsFolderPath, pluginData, projectData) {
const project = this.createPbxProj(projectData);
const group = await this.getRootGroup(pluginData.name, pluginPlatformsFolderPath);
project.removePbxGroup(group.name, group.path);
project.removeFromHeaderSearchPaths(group.path);
this.savePbxProj(project, projectData);
}
removeFrameworks(pluginPlatformsFolderPath, pluginData, projectData) {
const project = this.createPbxProj(projectData);
_.each(this.getAllLibsForPluginWithFileExtension(pluginData, FRAMEWORK_EXTENSIONS), (fileName) => {
const relativeFrameworkPath = this.getLibSubpathRelativeToProjectPath(fileName, projectData);
project.removeFramework(relativeFrameworkPath, {
customFramework: true,
embed: true,
});
});
this.savePbxProj(project, projectData);
}
removeStaticLibs(pluginPlatformsFolderPath, pluginData, projectData) {
const project = this.createPbxProj(projectData);
_.each(this.getAllLibsForPluginWithFileExtension(pluginData, ".a"), (fileName) => {
const staticLibPath = path.join(pluginPlatformsFolderPath, fileName);
const relativeStaticLibPath = this.getLibSubpathRelativeToProjectPath(path.basename(staticLibPath), projectData);
project.removeFramework(relativeStaticLibPath);
const headersSubpath = path.join("include", path.basename(staticLibPath, ".a"));
const relativeHeaderSearchPath = path.join(this.getLibSubpathRelativeToProjectPath(headersSubpath, projectData));
project.removeFromHeaderSearchPaths({
relativePath: relativeHeaderSearchPath,
});
});
this.savePbxProj(project, projectData);
}
generateModulemap(headersFolderPath, libraryName) {
const headersFilter = (fileName, containingFolderPath) => path.extname(fileName) === ".h" &&
this.$fs.getFsStats(path.join(containingFolderPath, fileName)).isFile();
const headersFolderContents = this.$fs.readDirectory(headersFolderPath);
let headers = _(headersFolderContents)
.filter((item) => headersFilter(item, headersFolderPath))
.value();
if (!headers.length) {
this.$fs.deleteFile(path.join(headersFolderPath, "module.modulemap"));
return;
}
headers = _.map(headers, (value) => `header "${value}"`);
const modulemap = `module ${libraryName} { explicit module ${libraryName} { ${headers.join(" ")} } }`;
this.$fs.writeFile(path.join(headersFolderPath, "module.modulemap"), modulemap);
}
async mergeProjectXcconfigFiles(projectData) {
const platformData = this.getPlatformData(projectData);
const pluginsXcconfigFilePaths = _.values(this.$xcconfigService.getPluginsXcconfigFilePaths(platformData.projectRoot));
for (const pluginsXcconfigFilePath of pluginsXcconfigFilePaths) {
this.$fs.deleteFile(pluginsXcconfigFilePath);
}
const allPlugins = this.getAllProductionPlugins(projectData);
for (const plugin of allPlugins) {
const pluginPlatformsFolderPath = plugin.pluginPlatformsFolderPath(IOSProjectService.IOS_PLATFORM_NAME);
const pluginXcconfigFilePath = path.join(pluginPlatformsFolderPath, constants_2.BUILD_XCCONFIG_FILE_NAME);
if (this.$fs.exists(pluginXcconfigFilePath)) {
for (const pluginsXcconfigFilePath of pluginsXcconfigFilePaths) {
await this.$xcconfigService.mergeFiles(pluginXcconfigFilePath, pluginsXcconfigFilePath);
}
}
}
const appResourcesXcconfigPath = path.join(projectData.appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, constants_2.BUILD_XCCONFIG_FILE_NAME);
if (this.$fs.exists(appResourcesXcconfigPath)) {
for (const pluginsXcconfigFilePath