nativescript
Version:
Command-line interface for building NativeScript projects
354 lines (353 loc) • 16.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.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
;