nativescript
Version:
Command-line interface for building NativeScript projects
485 lines • 23.3 kB
JavaScript
;
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