UNPKG

nativescript

Version:

Command-line interface for building NativeScript projects

485 lines • 23.3 kB
"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.ProjectDataService = void 0; const path = require("path"); const project_data_1 = require("../project-data"); const constants = require("../constants"); const constants_1 = require("../constants"); const helpers_1 = require("../common/helpers"); const decorators_1 = require("../common/decorators"); const _ = require("lodash"); const yok_1 = require("../common/yok"); const semver = require("semver"); const package_path_helper_1 = require("../helpers/package-path-helper"); class ProjectDataService { constructor($fs, $staticConfig, $logger, $devicePlatformsConstants, $androidResourcesMigrationService, $injector) { this.$fs = $fs; this.$staticConfig = $staticConfig; this.$logger = $logger; this.$devicePlatformsConstants = $devicePlatformsConstants; this.$androidResourcesMigrationService = $androidResourcesMigrationService; this.$injector = $injector; this.projectDataCache = {}; try { // add the ProjectData of the default projectDir to the projectData cache const projectData = this.$injector.resolve("projectData"); projectData.initializeProjectData(); this.defaultProjectDir = projectData.projectDir; this.projectDataCache[this.defaultProjectDir] = projectData; } catch (e) { // the CLI is required as a lib from a non-project folder } } get $pluginsService() { return this.$injector.resolve("pluginsService"); } getNSValue(projectDir, propertyName) { return this.getValue(projectDir, this.getNativeScriptPropertyName(propertyName)); } getNSValueFromContent(jsonData, propertyName) { try { return this.getPropertyValueFromJson(jsonData, this.getNativeScriptPropertyName(propertyName)); } catch (e) { this.$logger.trace("Failed to get NS property value from JSON project data."); } return null; } setNSValue(projectDir, key, value) { this.setValue(projectDir, this.getNativeScriptPropertyName(key), value); } removeNSProperty(projectDir, propertyName) { this.removeProperty(projectDir, this.getNativeScriptPropertyName(propertyName)); } removeDependency(projectDir, dependencyName) { const projectFileInfo = this.getProjectFileData(projectDir); delete projectFileInfo.projectData[ProjectDataService.DEPENDENCIES_KEY_NAME][dependencyName]; this.$fs.writeJson(projectFileInfo.projectFilePath, projectFileInfo.projectData); } // TODO: Add tests // TODO: Remove $projectData and replace it with $projectDataService.getProjectData getProjectData(projectDir) { projectDir = projectDir || this.defaultProjectDir; this.projectDataCache[projectDir] = this.projectDataCache[projectDir] || this.$injector.resolve(project_data_1.ProjectData); this.projectDataCache[projectDir].initializeProjectData(projectDir); return this.projectDataCache[projectDir]; } getProjectDataFromContent(packageJsonContent, projectDir) { projectDir = projectDir || this.defaultProjectDir; this.projectDataCache[projectDir] = this.projectDataCache[projectDir] || this.$injector.resolve(project_data_1.ProjectData); this.projectDataCache[projectDir].initializeProjectDataFromContent(packageJsonContent, projectDir); return this.projectDataCache[projectDir]; } async getAssetsStructure(opts) { const iOSAssetStructure = await this.getIOSAssetsStructure(opts); const androidAssetStructure = await this.getAndroidAssetsStructure(opts); this.$logger.trace("iOS Assets structure:", JSON.stringify(iOSAssetStructure, null, 2)); this.$logger.trace("Android Assets structure:", JSON.stringify(androidAssetStructure, null, 2)); return { ios: iOSAssetStructure, android: androidAssetStructure, }; } async getIOSAssetsStructure(opts) { const projectDir = opts.projectDir; const projectData = this.getProjectData(projectDir); const basePath = path.join(projectData.appResourcesDirectoryPath, this.$devicePlatformsConstants.iOS, constants_1.AssetConstants.iOSAssetsDirName); const pathToIcons = path.join(basePath, constants_1.AssetConstants.iOSIconsDirName); const icons = await this.getIOSAssetSubGroup(pathToIcons); const pathToSplashBackgrounds = path.join(basePath, constants_1.AssetConstants.iOSSplashBackgroundsDirName); const splashBackgrounds = await this.getIOSAssetSubGroup(pathToSplashBackgrounds); const pathToSplashCenterImages = path.join(basePath, constants_1.AssetConstants.iOSSplashCenterImagesDirName); const splashCenterImages = await this.getIOSAssetSubGroup(pathToSplashCenterImages); const pathToSplashImages = path.join(basePath, constants_1.AssetConstants.iOSSplashImagesDirName); const splashImages = await this.getIOSAssetSubGroup(pathToSplashImages); return { icons, splashBackgrounds, splashCenterImages, splashImages, }; } removeNSConfigProperty(projectDir, propertyName) { this.$logger.trace(`Removing "${propertyName}" property from nsconfig.`); this.updateNsConfigValue(projectDir, null, [propertyName]); this.$logger.trace(`"${propertyName}" property successfully removed.`); } async getAndroidAssetsStructure(opts) { // TODO: Use image-size package to get the width and height of an image. // TODO: Parse the splash_screen.xml in nodpi directory and get from it the names of the background and center image. // TODO: Parse the AndroidManifest.xml to get the name of the icon. // This way we'll not use the image-definitions.json and the method will return the real android structure. const projectDir = opts.projectDir; const projectData = this.getProjectData(projectDir); const pathToAndroidDir = path.join(projectData.appResourcesDirectoryPath, this.$devicePlatformsConstants.Android); const hasMigrated = this.$androidResourcesMigrationService.hasMigrated(projectData.appResourcesDirectoryPath); const basePath = hasMigrated ? path.join(pathToAndroidDir, constants_1.SRC_DIR, constants_1.MAIN_DIR, constants_1.RESOURCES_DIR) : pathToAndroidDir; let useLegacy = false; try { const manifest = this.$fs.readText(path.resolve(basePath, "../AndroidManifest.xml")); useLegacy = !manifest.includes(`android:icon="@mipmap/ic_launcher"`); } catch (err) { // ignore } const content = this.getImageDefinitions()[useLegacy ? "android_legacy" : "android"]; return { icons: this.getAndroidAssetSubGroup(content.icons, basePath), splashBackgrounds: this.getAndroidAssetSubGroup(content.splashBackgrounds, basePath), splashCenterImages: this.getAndroidAssetSubGroup(content.splashCenterImages, basePath), splashImages: null, }; } getAppExecutableFiles(projectDir) { const projectData = this.getProjectData(projectDir); let supportedFileExtension = ".js"; if (projectData.projectType === constants_1.ProjectTypes.NgFlavorName || projectData.projectType === constants_1.ProjectTypes.TsFlavorName) { supportedFileExtension = ".ts"; } const pathToProjectNodeModules = path.join(projectDir, constants_1.NODE_MODULES_FOLDER_NAME); const files = this.$fs.enumerateFilesInDirectorySync(projectData.appDirectoryPath, (filePath, fstat) => { if (filePath.indexOf(projectData.appResourcesDirectoryPath) !== -1) { return false; } if (fstat.isDirectory()) { if (filePath === pathToProjectNodeModules) { // we do not want to get the files from node_modules directory of the project. // We'll get here only when you have nativescript.config with appDirectoryPath set to "." return false; } return true; } return path.extname(filePath) === supportedFileExtension; }); return files; } refreshProjectData(projectDir) { if (this.projectDataCache[projectDir]) { this.projectDataCache[projectDir].initializeProjectData(projectDir); } } updateNsConfigValue(projectDir, updateObject, propertiesToRemove) { // todo: figure out a way to update js/ts configs // most likely needs an ast parser/writer // should be delegated to the config service const nsConfigPath = path.join(projectDir, constants.CONFIG_FILE_NAME_JS); const currentNsConfig = this.getNsConfig(nsConfigPath); let newNsConfig = currentNsConfig; if (updateObject) { newNsConfig = _.assign(newNsConfig || this.getNsConfigDefaultObject(), updateObject); } if (newNsConfig && propertiesToRemove && propertiesToRemove.length) { newNsConfig = _.omit(newNsConfig, propertiesToRemove); } if (newNsConfig) { this.$fs.writeJson(nsConfigPath, newNsConfig); this.refreshProjectData(projectDir); } } getNsConfig(nsConfigPath) { let result = null; if (this.$fs.exists(nsConfigPath)) { const nsConfigContent = this.$fs.readText(nsConfigPath); try { result = (0, helpers_1.parseJson)(nsConfigContent); } catch (e) { this.$logger.trace("The `nsconfig` content is not a valid JSON. Parse error: ", e); } } return result; } getImageDefinitions() { const pathToImageDefinitions = path.join(__dirname, "..", "..", constants_1.CLI_RESOURCES_DIR_NAME, constants_1.AssetConstants.assets, constants_1.AssetConstants.imageDefinitionsFileName); const imageDefinitions = this.$fs.readJson(pathToImageDefinitions); return imageDefinitions; } async getIOSAssetSubGroup(dirPath) { const pathToContentJson = path.join(dirPath, constants_1.AssetConstants.iOSResourcesFileName); const content = (this.$fs.exists(pathToContentJson) && this.$fs.readJson(pathToContentJson)) || { images: [] }; const finalContent = { images: [] }; const imageDefinitions = this.getImageDefinitions().ios; _.each(content && content.images, (image) => { let foundMatchingDefinition = false; // In some cases the image may not be available, it will just be described. // When this happens, the filename will be empty. // So we'll keep the path empty as well. if (image.filename) { image.path = path.join(dirPath, image.filename); } if (image.size) { // size is basically <width>x<height> const [width, height] = image.size .toString() .split(constants_1.AssetConstants.sizeDelimiter); if (width && height) { image.width = +width; image.height = +height; } } // Find the image size based on the hardcoded values in the image-definitions.json _.each(imageDefinitions, (assetSubGroup) => { const assetItem = _.find(assetSubGroup, (assetElement) => assetElement.filename === image.filename && path.basename(assetElement.directory) === path.basename(dirPath)); if (assetItem) { foundMatchingDefinition = true; if (!image.width || !image.height) { image.width = assetItem.width; image.height = assetItem.height; image.size = image.size || `${assetItem.width}${constants_1.AssetConstants.sizeDelimiter}${assetItem.height}`; } image.resizeOperation = image.resizeOperation || assetItem.resizeOperation; image.overlayImageScale = image.overlayImageScale || assetItem.overlayImageScale; image.scale = image.scale || assetItem.scale; image.rgba = assetItem.rgba; finalContent.images.push(image); // break each return false; } }); if (!foundMatchingDefinition) { if (image.height && image.width) { this.$logger.trace("Missing data for image", image, " in CLI's resource file, but we will try to generate images based on the size from Contents.json"); finalContent.images.push(image); } else if (image.filename) { this.$logger.warn(`Didn't find a matching image definition for file ${path.join(path.basename(dirPath), image.filename)}. This file will be skipped from resources generation.`); } else { this.$logger.trace(`Unable to detect data for image generation of image`, image); } } }); return finalContent; } getAndroidAssetSubGroup(assetItems, basePath) { const assetSubGroup = { images: [], }; _.each(assetItems, (assetItem) => { const imagePath = path.join(basePath, assetItem.directory, assetItem.filename); assetItem.path = imagePath; if (assetItem.width && assetItem.height) { assetItem.size = `${assetItem.width}${constants_1.AssetConstants.sizeDelimiter}${assetItem.height}`; } assetSubGroup.images.push(assetItem); }); return assetSubGroup; } getValue(projectDir, propertyName) { const projectData = this.getProjectFileData(projectDir).projectData; if (projectData) { try { return this.getPropertyValueFromJson(projectData, propertyName); } catch (err) { this.$logger.trace(`Error while trying to get property ${propertyName} from ${projectDir}. Error is:`, err); } } return null; } getNativeScriptPropertyName(propertyName) { return `${this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE}${constants_1.NATIVESCRIPT_PROPS_INTERNAL_DELIMITER}${propertyName}`; } getPropertyValueFromJson(jsonData, dottedPropertyName) { const props = dottedPropertyName.split(constants_1.NATIVESCRIPT_PROPS_INTERNAL_DELIMITER); let result = jsonData[props.shift()]; for (const prop of props) { result = result[prop]; } return result; } setValue(projectDir, key, value) { const projectFileInfo = this.getProjectFileData(projectDir); const props = key.split(constants_1.NATIVESCRIPT_PROPS_INTERNAL_DELIMITER); const data = projectFileInfo.projectData; let currentData = data; _.each(props, (prop, index) => { if (index === props.length - 1) { currentData[prop] = value; } else { currentData[prop] = currentData[prop] || Object.create(null); } currentData = currentData[prop]; }); this.$fs.writeJson(projectFileInfo.projectFilePath, data); } removeProperty(projectDir, propertyName) { const projectFileInfo = this.getProjectFileData(projectDir); const data = projectFileInfo.projectData; let currentData = data; const props = propertyName.split(constants_1.NATIVESCRIPT_PROPS_INTERNAL_DELIMITER); const propertyToDelete = props.splice(props.length - 1, 1)[0]; _.each(props, (prop) => { currentData = currentData[prop]; }); delete currentData[propertyToDelete]; this.$fs.writeJson(projectFileInfo.projectFilePath, data); } getProjectFileData(projectDir) { const projectFilePath = path.join(projectDir, this.$staticConfig.PROJECT_FILE_NAME); const projectFileContent = this.$fs.readText(projectFilePath); const projectData = projectFileContent ? JSON.parse(projectFileContent) : Object.create(null); return { projectData, projectFilePath, }; } getNsConfigDefaultObject(data) { const config = {}; Object.assign(config, data); return config; } getRuntimePackage(projectDir, platform) { platform = platform.toLowerCase(); const packageJson = this.$fs.readJson(path.join(projectDir, constants.PACKAGE_JSON_FILE_NAME)); const runtimeName = platform === "android" /* PlatformTypes.android */ ? constants.TNS_ANDROID_RUNTIME_NAME : constants.TNS_IOS_RUNTIME_NAME; if (packageJson && packageJson.nativescript && packageJson.nativescript[runtimeName] && packageJson.nativescript[runtimeName].version) { // if we have a nativescript key with a runtime version in package.json // that means we are dealing with a legacy project, and should respect // that information return { name: runtimeName, version: packageJson.nativescript[runtimeName].version, }; } return this.getInstalledRuntimePackage(projectDir, platform); } getInstalledRuntimePackage(projectDir, platform) { const runtimePackage = this.$pluginsService .getDependenciesFromPackageJson(projectDir) .devDependencies.find((d) => { if (platform === "ios" /* constants.PlatformTypes.ios */) { return [ constants.SCOPED_IOS_RUNTIME_NAME, constants.TNS_IOS_RUNTIME_NAME, ].includes(d.name); } else if (platform === "android" /* constants.PlatformTypes.android */) { return [ constants.SCOPED_ANDROID_RUNTIME_NAME, constants.TNS_ANDROID_RUNTIME_NAME, ].includes(d.name); } else if (platform === "visionos" /* constants.PlatformTypes.visionos */) { return d.name === constants.SCOPED_VISIONOS_RUNTIME_NAME; } }); if (runtimePackage) { const coerced = semver.coerce(runtimePackage.version); const isRange = !!coerced && coerced.version !== runtimePackage.version; const isTag = !coerced; // in case we are using a local tgz for the runtime or a range like ~8.0.0, ^8.0.0 etc. or a tag like JSC if (runtimePackage.version.includes("tgz") || isRange || isTag) { try { const runtimePackageJsonPath = (0, package_path_helper_1.resolvePackageJSONPath)(runtimePackage.name, { paths: [projectDir], }); if (!runtimePackageJsonPath) { // caught below throw new Error("Runtime package.json not found."); } runtimePackage.version = this.$fs.readJson(runtimePackageJsonPath).version; } catch (err) { if (isRange) { runtimePackage.version = semver.coerce(runtimePackage.version).version; runtimePackage._coerced = true; } else { runtimePackage.version = null; } } } return runtimePackage; } // default to the scoped runtimes this.$logger.trace("Could not find an installed runtime, falling back to default runtimes"); if (platform === "ios" /* constants.PlatformTypes.ios */) { return { name: constants.SCOPED_IOS_RUNTIME_NAME, version: null, }; } else if (platform === "android" /* constants.PlatformTypes.android */) { return { name: constants.SCOPED_ANDROID_RUNTIME_NAME, version: null, }; } else if (platform === "visionos" /* constants.PlatformTypes.visionos */) { return { name: constants.SCOPED_VISIONOS_RUNTIME_NAME, version: null, }; } } getNsConfigDefaultContent(data) { const config = this.getNsConfigDefaultObject(data); return JSON.stringify(config); } } exports.ProjectDataService = ProjectDataService; ProjectDataService.DEPENDENCIES_KEY_NAME = "dependencies"; __decorate([ (0, decorators_1.exported)("projectDataService") ], ProjectDataService.prototype, "getProjectData", null); __decorate([ (0, decorators_1.exported)("projectDataService") ], ProjectDataService.prototype, "getProjectDataFromContent", null); __decorate([ (0, decorators_1.exported)("projectDataService") ], ProjectDataService.prototype, "getAssetsStructure", null); __decorate([ (0, decorators_1.exported)("projectDataService") ], ProjectDataService.prototype, "getIOSAssetsStructure", null); __decorate([ (0, decorators_1.exported)("projectDataService") ], ProjectDataService.prototype, "getAndroidAssetsStructure", null); __decorate([ (0, decorators_1.memoize)({ hashFn(projectDir, platform) { return projectDir + ":" + platform; }, shouldCache(result) { // don't cache coerced versions if (result._coerced) { return false; } // only cache if version is defined return !!result.version; }, }) ], ProjectDataService.prototype, "getInstalledRuntimePackage", null); __decorate([ (0, decorators_1.exported)("projectDataService") ], ProjectDataService.prototype, "getNsConfigDefaultContent", null); yok_1.injector.register("projectDataService", ProjectDataService); //# sourceMappingURL=project-data-service.js.map