UNPKG

nativescript

Version:

Command-line interface for building NativeScript projects

354 lines (353 loc) • 16.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.ProjectConfigService = void 0; const constants = require("../constants"); const constants_1 = require("../constants"); const path = require("path"); const _ = require("lodash"); const ts = require("typescript"); const config_transformer_1 = require("../tools/config-manipulation/config-transformer"); const yok_1 = require("../common/yok"); const os_1 = require("os"); const prettier_1 = require("prettier"); const decorators_1 = require("../common/decorators"); const semver = require("semver/preload"); class ProjectConfigService { constructor($fs, $logger, $injector, $options, $cleanupService) { this.$fs = $fs; this.$logger = $logger; this.$injector = $injector; this.$options = $options; this.$cleanupService = $cleanupService; this.forceUsingNewConfig = false; this.forceUsingLegacyConfig = false; } setForceUsingNewConfig(force) { return (this.forceUsingNewConfig = force); } setForceUsingLegacyConfig(force) { return (this.forceUsingLegacyConfig = force); } requireFromString(src, filename) { // @ts-ignore const m = new module.constructor(); m.paths = module.paths; m._compile(src, filename); return m.exports; } get projectHelper() { return this.$injector.resolve("projectHelper"); } getDefaultTSConfig(appId = "org.nativescript.app", appPath = "app") { return `import { NativeScriptConfig } from '@nativescript/core'; export default { id: '${appId}', appPath: '${appPath}', appResourcesPath: 'App_Resources', android: { v8Flags: '--expose_gc', markingMode: 'none' } } as NativeScriptConfig;`.trim(); } warnUsingLegacyNSConfig() { // todo: remove hack const isMigrate = _.get(this.$options, "argv._[0]") === "migrate"; if (isMigrate) { return; } this.$logger.warn(`You are using the deprecated ${constants_1.CONFIG_NS_FILE_NAME} file. Just be aware that NativeScript now has an improved ${constants_1.CONFIG_FILE_NAME_DISPLAY} file for when you're ready to upgrade this project.`); } detectProjectConfigs(projectDir) { var _a; // allow overriding config name with env variable or --config (or -c) let configName = (_a = process.env.NATIVESCRIPT_CONFIG_NAME) !== null && _a !== void 0 ? _a : this.$options.config; if (configName === "false") { configName = false; } const possibleConfigPaths = [ configName && ((configName === null || configName === void 0 ? void 0 : configName.endsWith(".ts")) ? configName : `${configName}.ts`), configName && ((configName === null || configName === void 0 ? void 0 : configName.endsWith(".js")) ? configName : `${configName}.js`), configName && ((configName === null || configName === void 0 ? void 0 : configName.endsWith(".json")) ? configName : `${configName}.json`), constants_1.CONFIG_FILE_NAME_TS, constants_1.CONFIG_FILE_NAME_JS, constants_1.CONFIG_NS_FILE_NAME, ] .filter(Boolean) .map((c) => { if (this.$fs.isRelativePath(c)) { const dir = projectDir || this.projectHelper.projectDir; if (!dir) { return c; } return path.join(dir, c); } return c; }); const existingConfigs = possibleConfigPaths.filter((path) => { return this.$fs.exists(path); }); // push the first possible config into the "existing" list const hasExistingConfig = !!existingConfigs.length; if (!hasExistingConfig) { this.$logger.trace(`No config file found - falling back to ${possibleConfigPaths[0]}.`); existingConfigs.push(possibleConfigPaths[0]); } const TSConfigPath = existingConfigs.find((config) => config.endsWith(".ts")); const JSConfigPath = existingConfigs.find((config) => config.endsWith(".js")); const NSConfigPath = existingConfigs.find((config) => config.endsWith(".json")); const hasTSConfig = !!TSConfigPath && hasExistingConfig; const hasJSConfig = !!JSConfigPath && hasExistingConfig; const hasNSConfig = !!NSConfigPath && hasExistingConfig; const usingNSConfig = !(hasTSConfig || hasJSConfig); if (hasTSConfig && hasJSConfig) { this.$logger.warn(`You have both a ${constants_1.CONFIG_FILE_NAME_JS} and ${constants_1.CONFIG_FILE_NAME_TS} file. Defaulting to ${constants_1.CONFIG_FILE_NAME_TS}.`); } return { hasTSConfig, hasJSConfig, hasNSConfig, usingNSConfig, TSConfigPath, JSConfigPath, NSConfigPath, }; } readConfig(projectDir) { const info = this.detectProjectConfigs(projectDir); if (this.forceUsingLegacyConfig || (info.usingNSConfig && !this.forceUsingNewConfig)) { this.$logger.trace("Project Config Service using legacy configuration..."); if (!this.forceUsingLegacyConfig && info.hasNSConfig) { this.warnUsingLegacyNSConfig(); } return this.fallbackToLegacyNSConfig(info); } let config; if (info.hasTSConfig) { const rawSource = this.$fs.readText(info.TSConfigPath); const transpiledSource = ts.transpileModule(rawSource, { compilerOptions: { module: ts.ModuleKind.CommonJS }, }); const result = this.requireFromString(transpiledSource.outputText, info.TSConfigPath); config = result["default"] ? result["default"] : result; } else if (info.hasJSConfig) { const rawSource = this.$fs.readText(info.JSConfigPath); config = this.requireFromString(rawSource, info.JSConfigPath); } return config; } getValue(key, defaultValue) { return _.get(this.readConfig(), key, defaultValue); } async setValue(key, value) { const { hasTSConfig, hasNSConfig, TSConfigPath, JSConfigPath, usingNSConfig, NSConfigPath, } = this.detectProjectConfigs(); const configFilePath = TSConfigPath || JSConfigPath; if (this.forceUsingLegacyConfig || (usingNSConfig && !this.forceUsingNewConfig)) { try { this.$logger.trace("Project Config Service -> setValue writing to legacy config."); const NSConfig = hasNSConfig ? this.$fs.readJson(NSConfigPath) : {}; _.set(NSConfig, key, value); this.$fs.writeJson(NSConfigPath, NSConfig); return true; } catch (error) { this.$logger.trace(`Failed to setValue on legacy config. Error is ${error.message}`, error); return false; } } if (!this.$fs.exists(configFilePath)) { this.writeDefaultConfig(this.projectHelper.projectDir); } if (!Array.isArray(value) && typeof value === "object") { let allSuccessful = true; for (const prop of this.flattenObjectToPaths(value)) { if (!(await this.setValue(prop.key, prop.value))) { allSuccessful = false; } } return allSuccessful; } const configContent = this.$fs.readText(configFilePath); try { const transformer = new config_transformer_1.ConfigTransformer(configContent); const newContent = transformer.setValue(key, value); const prettierOptions = (await (0, prettier_1.resolveConfig)(this.projectHelper.projectDir, { editorconfig: true })) || { semi: false, singleQuote: true, }; this.$logger.trace("updating config, prettier options: ", prettierOptions); this.$fs.writeFile(configFilePath, await (0, prettier_1.format)(newContent, { ...prettierOptions, parser: "typescript", // note: we don't use plugins here, since we are only formatting ts files, and they are supported by default // and this also causes issues with certain plugins, like prettier-plugin-tailwindcss. plugins: [], })); } catch (error) { this.$logger.error(`Failed to update config.` + error); } finally { // verify config is updated correctly if (!Array.isArray(this.getValue(key)) && this.getValue(key) !== value) { this.$logger.error(`${os_1.EOL}Failed to update ${hasTSConfig ? constants_1.CONFIG_FILE_NAME_TS : constants_1.CONFIG_FILE_NAME_JS}.${os_1.EOL}`); this.$logger.printMarkdown(`Please manually update \`${hasTSConfig ? constants_1.CONFIG_FILE_NAME_TS : constants_1.CONFIG_FILE_NAME_JS}\` and set \`${key}\` to \`${value}\`.${os_1.EOL}`); // restore original content this.$fs.writeFile(configFilePath, configContent); return false; } return true; } } writeDefaultConfig(projectDir, appId) { const TSConfigPath = path.resolve(projectDir, constants_1.CONFIG_FILE_NAME_TS); if (this.$fs.exists(TSConfigPath)) { return false; } const possibleAppPaths = [ path.resolve(projectDir, constants.SRC_DIR), path.resolve(projectDir, constants.APP_FOLDER_NAME), ]; let appPath = possibleAppPaths.find((possiblePath) => this.$fs.exists(possiblePath)); if (appPath) { appPath = path.relative(projectDir, appPath).replace(path.sep, "/"); } this.$fs.writeFile(TSConfigPath, this.getDefaultTSConfig(appId, appPath)); return TSConfigPath; } fallbackToLegacyNSConfig(info) { const additionalData = []; const NSConfig = info.hasNSConfig ? this.$fs.readJson(info.NSConfigPath) : {}; try { // injecting here to avoid circular dependency const projectData = this.$injector.resolve("projectData"); const embeddedPackageJsonPath = path.resolve(this.projectHelper.projectDir, projectData.getAppDirectoryRelativePath(), constants.PACKAGE_JSON_FILE_NAME); const embeddedPackageJson = this.$fs.readJson(embeddedPackageJsonPath); // filter only the supported keys additionalData.push(_.pick(embeddedPackageJson, [ "android", "ios", "profiling", "cssParser", "discardUncaughtJsExceptions", "main", ])); } catch (err) { this.$logger.trace("failed to add embedded package.json data to config", err); // ignore if the file doesn't exist } try { const packageJson = this.$fs.readJson(path.join(this.projectHelper.projectDir, "package.json")); // add app id to additionalData for backwards compatibility if (!NSConfig.id && packageJson && packageJson.nativescript && packageJson.nativescript.id) { const ids = packageJson.nativescript.id; if (typeof ids === "string") { additionalData.push({ id: packageJson.nativescript.id, }); } else if (typeof ids === "object") { for (const platform of Object.keys(ids)) { additionalData.push({ [platform]: { id: packageJson.nativescript.id[platform], }, }); } } } } catch (err) { this.$logger.trace("failed to read package.json data for config", err); // ignore if the file doesn't exist } return _.defaultsDeep({}, ...additionalData, NSConfig); // return Object.assign({}, ...additionalData, NSConfig); } async writeLegacyNSConfigIfNeeded(projectDir, runtimePackage) { const { usingNSConfig } = this.detectProjectConfigs(projectDir); if (usingNSConfig) { return; } if (runtimePackage.version && semver.gte(semver.coerce(runtimePackage.version), "7.0.0-rc.5")) { // runtimes >= 7.0.0-rc.5 support passing appPath and appResourcesPath through gradle project flags // so writing an nsconfig is not necessary. return; } const runtimePackageDisplay = `${runtimePackage.name}${runtimePackage.version ? " v" + runtimePackage.version : ""}`; this.$logger.info(); this.$logger.printMarkdown(` Using __${runtimePackageDisplay}__ which requires \`nsconfig.json\` to be present. Writing \`nsconfig.json\` based on the values set in \`${constants_1.CONFIG_FILE_NAME_DISPLAY}\`. You may add \`nsconfig.json\` to \`.gitignore\` as the CLI will regenerate it as necessary.`); const nsConfigPath = path.join(projectDir || this.projectHelper.projectDir, "nsconfig.json"); this.$fs.writeJson(nsConfigPath, { _info1: `Auto Generated for backwards compatibility with the currently used runtime.`, _info2: `Do not edit this file manually, as any changes will be ignored.`, _info3: `Config changes should be done in ${constants_1.CONFIG_FILE_NAME_DISPLAY} instead.`, appPath: this.getValue("appPath"), appResourcesPath: this.getValue("appResourcesPath"), }); // mark the file for cleanup after the CLI exits await this.$cleanupService.addCleanupDeleteAction(nsConfigPath); } // todo: move into config manipulation flattenObjectToPaths(obj, basePath) { const toPath = (key) => [basePath, key].filter(Boolean).join("."); return Object.keys(obj).reduce((all, key) => { if (Array.isArray(obj[key])) { return [ ...all, { key: toPath(key), value: obj[key], // Preserve arrays as they are }, ]; } else if (typeof obj[key] === "object" && obj[key] !== null) { return [...all, ...this.flattenObjectToPaths(obj[key], toPath(key))]; } return [ ...all, { key: toPath(key), value: obj[key], }, ]; }, []); } } exports.ProjectConfigService = ProjectConfigService; __decorate([ (0, decorators_1.cache)() // @cache should prevent the message being printed multiple times ], ProjectConfigService.prototype, "warnUsingLegacyNSConfig", null); __decorate([ (0, decorators_1.exported)("projectConfigService") ], ProjectConfigService.prototype, "readConfig", null); __decorate([ (0, decorators_1.exported)("projectConfigService") ], ProjectConfigService.prototype, "getValue", null); __decorate([ (0, decorators_1.exported)("projectConfigService") ], ProjectConfigService.prototype, "setValue", null); yok_1.injector.register("projectConfigService", ProjectConfigService); //# sourceMappingURL=project-config-service.js.map