diff --git a/docs/man_pages/start.md b/docs/man_pages/start.md index b051aac65d..22e9b7d004 100644 --- a/docs/man_pages/start.md +++ b/docs/man_pages/start.md @@ -75,5 +75,6 @@ Option | Description -------|--------- --help, -h, /? | Prints help about the selected command in the console. --path `` | Specifies the directory that contains the project. If not set, the project is searched for in the current directory and all directories above it. +--config | Specifies the name of the Nativescript configuration file to load (relative to the project directory). The default is `nativescript.config.ts` or `nativescript.config.js` (as a fallback). --version | Prints the client version. --log trace | Prints a detailed diagnostic log for the execution of the current command. diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 67aeacc1af..e9c7bc40ff 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -616,10 +616,10 @@ interface IOptions /** * Project Configuration */ - config: string[]; log: string; verbose: boolean; path: string; + config: string; version: boolean; help: boolean; json: boolean; diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 186d77734b..a7467b90eb 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -145,6 +145,7 @@ interface INsConfig { webpackConfigPath?: string; ios?: INsConfigIOS; android?: INsConfigAndroid; + ignoredNativeDependencies?: string[]; } interface IProjectData extends ICreateProjectData { diff --git a/lib/options.ts b/lib/options.ts index 2c7fdee082..39294f21a5 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -30,6 +30,7 @@ export class Options { profileDir: { type: OptionType.String, hasSensitiveValue: true }, analyticsClient: { type: OptionType.String, hasSensitiveValue: false }, path: { type: OptionType.String, alias: "p", hasSensitiveValue: true }, + config: { type: OptionType.String, alias: "c", hasSensitiveValue: true }, // This will parse all non-hyphenated values as strings. _: { type: OptionType.String, hasSensitiveValue: false }, }; diff --git a/lib/project-data.ts b/lib/project-data.ts index b72a9c6b32..1916e58fee 100644 --- a/lib/project-data.ts +++ b/lib/project-data.ts @@ -88,6 +88,7 @@ export class ProjectData implements IProjectData { public appResourcesDirectoryPath: string; public dependencies: any; public devDependencies: IStringDictionary; + public ignoredDependencies: string[]; public projectType: string; public androidManifestPath: string; public infoPlistPath: string; @@ -172,6 +173,7 @@ export class ProjectData implements IProjectData { this.devDependencies = packageJsonData.devDependencies; this.projectType = this.getProjectType(); this.nsConfig = nsConfig; + this.ignoredDependencies = nsConfig?.ignoredNativeDependencies; this.appDirectoryPath = this.getAppDirectoryPath(); this.appResourcesDirectoryPath = this.getAppResourcesDirectoryPath(); this.androidManifestPath = this.getPathToAndroidManifest( diff --git a/lib/services/project-config-service.ts b/lib/services/project-config-service.ts index b44aa0b8bd..6fd7829aed 100644 --- a/lib/services/project-config-service.ts +++ b/lib/services/project-config-service.ts @@ -93,23 +93,108 @@ export default { ); } + private getConfigPathsFromPossiblePaths(paths: { + [key: string]: string[]; + }): any { + const { + possibleTSConfigPaths, + possibleJSConfigPaths, + possibleNSConfigPaths, + } = paths; + + let TSConfigPath; + let JSConfigPath; + let NSConfigPath; + + // look up a ts config first + TSConfigPath = possibleTSConfigPaths + .filter(Boolean) + .find((path) => this.$fs.exists(path)); + + // if not found, look up a JS config + if (!TSConfigPath) { + JSConfigPath = possibleJSConfigPaths + .filter(Boolean) + .find((path) => this.$fs.exists(path)); + } + + // lastly look for nsconfig/json config + if (!TSConfigPath && !JSConfigPath) { + NSConfigPath = possibleNSConfigPaths + .filter(Boolean) + .find((path) => this.$fs.exists(path)); + } + + return { + TSConfigPath, + JSConfigPath, + NSConfigPath, + found: TSConfigPath || JSConfigPath || NSConfigPath, + }; + } + public detectProjectConfigs(projectDir?: string): IProjectConfigInformation { - const JSConfigPath = path.join( - projectDir || this.projectHelper.projectDir, - CONFIG_FILE_NAME_JS - ); - const TSConfigPath = path.join( - projectDir || this.projectHelper.projectDir, - CONFIG_FILE_NAME_TS - ); - const NSConfigPath = path.join( - projectDir || this.projectHelper.projectDir, - CONFIG_NS_FILE_NAME - ); + const possibleTSConfigPaths = []; + const possibleJSConfigPaths = []; + const possibleNSConfigPaths = []; + let paths; + + // allow overriding config name with env variable or --config (or -c) + const configFilename = + process.env.NATIVESCRIPT_CONFIG_NAME ?? this.$options.config; + if (configFilename) { + const fullPath = this.$fs.isRelativePath(configFilename) + ? path.join(projectDir || this.projectHelper.projectDir, configFilename) + : configFilename; + + possibleTSConfigPaths.unshift( + fullPath.endsWith(".ts") ? fullPath : `${fullPath}.ts` + ); + possibleJSConfigPaths.unshift( + fullPath.endsWith(".js") ? fullPath : `${fullPath}.js` + ); + possibleNSConfigPaths.unshift( + fullPath.endsWith(".json") ? fullPath : `${fullPath}.json` + ); - const hasTSConfig = this.$fs.exists(TSConfigPath); - const hasJSConfig = this.$fs.exists(JSConfigPath); - const hasNSConfig = this.$fs.exists(NSConfigPath); + paths = this.getConfigPathsFromPossiblePaths({ + possibleTSConfigPaths, + possibleJSConfigPaths, + possibleNSConfigPaths, + }); + } + + // look up default paths if no path found yet + if (!paths?.found) { + possibleTSConfigPaths.push( + path.join( + projectDir || this.projectHelper.projectDir, + CONFIG_FILE_NAME_TS + ) + ); + possibleJSConfigPaths.push( + path.join( + projectDir || this.projectHelper.projectDir, + CONFIG_FILE_NAME_JS + ) + ); + possibleNSConfigPaths.push( + path.join( + projectDir || this.projectHelper.projectDir, + CONFIG_NS_FILE_NAME + ) + ); + + paths = this.getConfigPathsFromPossiblePaths({ + possibleTSConfigPaths, + possibleJSConfigPaths, + possibleNSConfigPaths, + }); + } + + const hasTSConfig = !!paths.TSConfigPath; + const hasJSConfig = !!paths.JSConfigPath; + const hasNSConfig = !!paths.NSConfigPath; const usingNSConfig = !(hasTSConfig || hasJSConfig); if (hasTSConfig && hasJSConfig) { @@ -123,9 +208,9 @@ export default { hasJSConfig, hasNSConfig, usingNSConfig, - TSConfigPath, - JSConfigPath, - NSConfigPath, + TSConfigPath: paths.TSConfigPath, + JSConfigPath: paths.JSConfigPath, + NSConfigPath: paths.NSConfigPath, }; } diff --git a/lib/services/webpack/webpack-compiler-service.ts b/lib/services/webpack/webpack-compiler-service.ts index 2161e071e9..91cc5e2ea5 100644 --- a/lib/services/webpack/webpack-compiler-service.ts +++ b/lib/services/webpack/webpack-compiler-service.ts @@ -13,6 +13,7 @@ import { import { IPackageManager, IPackageInstallationManager, + IOptions, } from "../../declarations"; import { IPlatformData } from "../../definitions/platform"; import { IProjectData } from "../../definitions/project"; @@ -48,6 +49,7 @@ export class WebpackCompilerService private expectedHashes: IStringDictionary = {}; constructor( + private $options: IOptions, private $errors: IErrors, private $childProcess: IChildProcess, public $fs: IFileSystem, @@ -368,6 +370,13 @@ export class WebpackCompilerService envData.verbose = envData.verbose || this.$logger.isVerbose(); envData.production = envData.production || prepareData.release; + + // add the config file name to the env data so the webpack process can read the + // correct config file when resolving the CLI lib and the config service + // we are explicityly setting it to false to force using the defaults + envData.config = + process.env.NATIVESCRIPT_CONFIG_NAME ?? this.$options.config ?? "false"; + // The snapshot generation is wrongly located in the Webpack plugin. // It should be moved in the Native Prepare of the CLI or a Gradle task in the Runtime. // As a workaround, we skip the mksnapshot, xxd and android-ndk calls based on skipNativePrepare. diff --git a/test/options.ts b/test/options.ts index fa3dfb6d6d..e5c5f5e8a5 100644 --- a/test/options.ts +++ b/test/options.ts @@ -107,6 +107,24 @@ describe("options", () => { assert.isTrue(isExecutionStopped); }); + it("does not break execution when valid option has correct value", () => { + process.argv.push("--config"); + process.argv.push("SomeFilename"); + const options = createOptions(testInjector); + options.validateOptions(); + process.argv.pop(); + process.argv.pop(); + assert.isFalse(isExecutionStopped); + }); + + it("breaks execution when valid option has empty string value", () => { + process.argv.push("--config"); + const options = createOptions(testInjector); + options.validateOptions(); + process.argv.pop(); + assert.isTrue(isExecutionStopped); + }); + it("breaks execution when valid option has value with spaces only", () => { process.argv.push("--path"); process.argv.push(" "); diff --git a/test/services/project-config-service.ts b/test/services/project-config-service.ts index 5bb7f775ce..d4e3d09b19 100644 --- a/test/services/project-config-service.ts +++ b/test/services/project-config-service.ts @@ -45,6 +45,8 @@ const createTestInjector = ( readJson: (filePath: string): any => null, + isRelativePath: (filePath: string): any => true, + enumerateFilesInDirectorySync: ( directoryPath: string, filterCallback?: (_file: string, _stat: IFsStats) => boolean, @@ -141,6 +143,82 @@ describe("projectConfigService", () => { assert.deepStrictEqual(actualValue, "--expose-gc"); }); + it("can read a named JS config file when passing --config", async () => { + const testInjector = createTestInjector( + (filename) => sampleJSConfig, + (filePath) => basename(filePath) === "custom.config.js" + ); + + // mock "--config custom.config.js" + const options: Options = testInjector.resolve("options") as Options; + // @ts-ignore + options.config = "custom.config.js"; + + const projectConfigService: IProjectConfigService = testInjector.resolve( + "projectConfigService" + ); + + const actualValue = projectConfigService.getValue("id"); + assert.deepStrictEqual(actualValue, "io.test.app"); + }); + + it("can read a named TS config file when passing --config", async () => { + const testInjector = createTestInjector( + (filename) => sampleTSConfig, + (filePath) => basename(filePath) === "custom.config.ts" + ); + + // mock "--config custom.config.ts" + const options: Options = testInjector.resolve("options") as Options; + // @ts-ignore + options.config = "custom.config.ts"; + + const projectConfigService: IProjectConfigService = testInjector.resolve( + "projectConfigService" + ); + + const actualValue = projectConfigService.getValue("id"); + assert.deepStrictEqual(actualValue, "io.test.app"); + }); + + it("can read a named JS config file when passing --config without extension", async () => { + const testInjector = createTestInjector( + (filename) => sampleJSConfig, + (filePath) => basename(filePath) === "custom.config.js" + ); + + // mock "--config custom.config" + const options: Options = testInjector.resolve("options") as Options; + // @ts-ignore + options.config = "custom.config"; + + const projectConfigService: IProjectConfigService = testInjector.resolve( + "projectConfigService" + ); + + const actualValue = projectConfigService.getValue("id"); + assert.deepStrictEqual(actualValue, "io.test.app"); + }); + + it("can read a named TS config file when passing --config without extension", async () => { + const testInjector = createTestInjector( + (filename) => sampleTSConfig, + (filePath) => basename(filePath) === "custom.config.ts" + ); + + // mock "--config custom.config" + const options: Options = testInjector.resolve("options") as Options; + // @ts-ignore + options.config = "custom.config"; + + const projectConfigService: IProjectConfigService = testInjector.resolve( + "projectConfigService" + ); + + const actualValue = projectConfigService.getValue("id"); + assert.deepStrictEqual(actualValue, "io.test.app"); + }); + // it("Throws error if no config file found", () => { // const testInjector = createTestInjector( // (filename) => null, diff --git a/test/services/webpack/webpack-compiler-service.ts b/test/services/webpack/webpack-compiler-service.ts index cb48eacab5..d15cccc430 100644 --- a/test/services/webpack/webpack-compiler-service.ts +++ b/test/services/webpack/webpack-compiler-service.ts @@ -27,6 +27,7 @@ function createTestInjector(): IInjector { testInjector.register("childProcess", {}); testInjector.register("hooksService", {}); testInjector.register("hostInfo", {}); + testInjector.register("options", {}); testInjector.register("logger", {}); testInjector.register("errors", ErrorsStub); testInjector.register("packageInstallationManager", {});