Skip to content

Commit 9f844ea

Browse files
feat: add --config option (#5554)
* feat: `config` option to set custom `nativescript.config` file name (relative, absolute, with or without .ts or .js) read `ignoredNativeDependencies` from `nativescript.config``nativescript.config` * chore: cleanup * fix: edge cases/refactor config lookup code add more tests * fix: use env variables to explicitly set config * fix: correctly look up config files * chore: remove console.log * test: fix missing injection Co-authored-by: Igor Randjelovic <[email protected]>
1 parent 4e94ad4 commit 9f844ea

File tree

10 files changed

+215
-19
lines changed

10 files changed

+215
-19
lines changed

docs/man_pages/start.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,6 @@ Option | Description
7575
-------|---------
7676
--help, -h, /? | Prints help about the selected command in the console.
7777
--path `<Directory>` | Specifies the directory that contains the project. If not set, the project is searched for in the current directory and all directories above it.
78+
--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).
7879
--version | Prints the client version.
7980
--log trace | Prints a detailed diagnostic log for the execution of the current command.

lib/declarations.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -616,10 +616,10 @@ interface IOptions
616616
/**
617617
* Project Configuration
618618
*/
619-
config: string[];
620619
log: string;
621620
verbose: boolean;
622621
path: string;
622+
config: string;
623623
version: boolean;
624624
help: boolean;
625625
json: boolean;

lib/definitions/project.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ interface INsConfig {
145145
webpackConfigPath?: string;
146146
ios?: INsConfigIOS;
147147
android?: INsConfigAndroid;
148+
ignoredNativeDependencies?: string[];
148149
}
149150

150151
interface IProjectData extends ICreateProjectData {

lib/options.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export class Options {
3030
profileDir: { type: OptionType.String, hasSensitiveValue: true },
3131
analyticsClient: { type: OptionType.String, hasSensitiveValue: false },
3232
path: { type: OptionType.String, alias: "p", hasSensitiveValue: true },
33+
config: { type: OptionType.String, alias: "c", hasSensitiveValue: true },
3334
// This will parse all non-hyphenated values as strings.
3435
_: { type: OptionType.String, hasSensitiveValue: false },
3536
};

lib/project-data.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export class ProjectData implements IProjectData {
8888
public appResourcesDirectoryPath: string;
8989
public dependencies: any;
9090
public devDependencies: IStringDictionary;
91+
public ignoredDependencies: string[];
9192
public projectType: string;
9293
public androidManifestPath: string;
9394
public infoPlistPath: string;
@@ -172,6 +173,7 @@ export class ProjectData implements IProjectData {
172173
this.devDependencies = packageJsonData.devDependencies;
173174
this.projectType = this.getProjectType();
174175
this.nsConfig = nsConfig;
176+
this.ignoredDependencies = nsConfig?.ignoredNativeDependencies;
175177
this.appDirectoryPath = this.getAppDirectoryPath();
176178
this.appResourcesDirectoryPath = this.getAppResourcesDirectoryPath();
177179
this.androidManifestPath = this.getPathToAndroidManifest(

lib/services/project-config-service.ts

Lines changed: 103 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -93,23 +93,108 @@ export default {
9393
);
9494
}
9595

96+
private getConfigPathsFromPossiblePaths(paths: {
97+
[key: string]: string[];
98+
}): any {
99+
const {
100+
possibleTSConfigPaths,
101+
possibleJSConfigPaths,
102+
possibleNSConfigPaths,
103+
} = paths;
104+
105+
let TSConfigPath;
106+
let JSConfigPath;
107+
let NSConfigPath;
108+
109+
// look up a ts config first
110+
TSConfigPath = possibleTSConfigPaths
111+
.filter(Boolean)
112+
.find((path) => this.$fs.exists(path));
113+
114+
// if not found, look up a JS config
115+
if (!TSConfigPath) {
116+
JSConfigPath = possibleJSConfigPaths
117+
.filter(Boolean)
118+
.find((path) => this.$fs.exists(path));
119+
}
120+
121+
// lastly look for nsconfig/json config
122+
if (!TSConfigPath && !JSConfigPath) {
123+
NSConfigPath = possibleNSConfigPaths
124+
.filter(Boolean)
125+
.find((path) => this.$fs.exists(path));
126+
}
127+
128+
return {
129+
TSConfigPath,
130+
JSConfigPath,
131+
NSConfigPath,
132+
found: TSConfigPath || JSConfigPath || NSConfigPath,
133+
};
134+
}
135+
96136
public detectProjectConfigs(projectDir?: string): IProjectConfigInformation {
97-
const JSConfigPath = path.join(
98-
projectDir || this.projectHelper.projectDir,
99-
CONFIG_FILE_NAME_JS
100-
);
101-
const TSConfigPath = path.join(
102-
projectDir || this.projectHelper.projectDir,
103-
CONFIG_FILE_NAME_TS
104-
);
105-
const NSConfigPath = path.join(
106-
projectDir || this.projectHelper.projectDir,
107-
CONFIG_NS_FILE_NAME
108-
);
137+
const possibleTSConfigPaths = [];
138+
const possibleJSConfigPaths = [];
139+
const possibleNSConfigPaths = [];
140+
let paths;
141+
142+
// allow overriding config name with env variable or --config (or -c)
143+
const configFilename =
144+
process.env.NATIVESCRIPT_CONFIG_NAME ?? this.$options.config;
145+
if (configFilename) {
146+
const fullPath = this.$fs.isRelativePath(configFilename)
147+
? path.join(projectDir || this.projectHelper.projectDir, configFilename)
148+
: configFilename;
149+
150+
possibleTSConfigPaths.unshift(
151+
fullPath.endsWith(".ts") ? fullPath : `${fullPath}.ts`
152+
);
153+
possibleJSConfigPaths.unshift(
154+
fullPath.endsWith(".js") ? fullPath : `${fullPath}.js`
155+
);
156+
possibleNSConfigPaths.unshift(
157+
fullPath.endsWith(".json") ? fullPath : `${fullPath}.json`
158+
);
109159

110-
const hasTSConfig = this.$fs.exists(TSConfigPath);
111-
const hasJSConfig = this.$fs.exists(JSConfigPath);
112-
const hasNSConfig = this.$fs.exists(NSConfigPath);
160+
paths = this.getConfigPathsFromPossiblePaths({
161+
possibleTSConfigPaths,
162+
possibleJSConfigPaths,
163+
possibleNSConfigPaths,
164+
});
165+
}
166+
167+
// look up default paths if no path found yet
168+
if (!paths?.found) {
169+
possibleTSConfigPaths.push(
170+
path.join(
171+
projectDir || this.projectHelper.projectDir,
172+
CONFIG_FILE_NAME_TS
173+
)
174+
);
175+
possibleJSConfigPaths.push(
176+
path.join(
177+
projectDir || this.projectHelper.projectDir,
178+
CONFIG_FILE_NAME_JS
179+
)
180+
);
181+
possibleNSConfigPaths.push(
182+
path.join(
183+
projectDir || this.projectHelper.projectDir,
184+
CONFIG_NS_FILE_NAME
185+
)
186+
);
187+
188+
paths = this.getConfigPathsFromPossiblePaths({
189+
possibleTSConfigPaths,
190+
possibleJSConfigPaths,
191+
possibleNSConfigPaths,
192+
});
193+
}
194+
195+
const hasTSConfig = !!paths.TSConfigPath;
196+
const hasJSConfig = !!paths.JSConfigPath;
197+
const hasNSConfig = !!paths.NSConfigPath;
113198
const usingNSConfig = !(hasTSConfig || hasJSConfig);
114199

115200
if (hasTSConfig && hasJSConfig) {
@@ -123,9 +208,9 @@ export default {
123208
hasJSConfig,
124209
hasNSConfig,
125210
usingNSConfig,
126-
TSConfigPath,
127-
JSConfigPath,
128-
NSConfigPath,
211+
TSConfigPath: paths.TSConfigPath,
212+
JSConfigPath: paths.JSConfigPath,
213+
NSConfigPath: paths.NSConfigPath,
129214
};
130215
}
131216

lib/services/webpack/webpack-compiler-service.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import {
1414
IPackageManager,
1515
IPackageInstallationManager,
16+
IOptions,
1617
} from "../../declarations";
1718
import { IPlatformData } from "../../definitions/platform";
1819
import { IProjectData } from "../../definitions/project";
@@ -48,6 +49,7 @@ export class WebpackCompilerService
4849
private expectedHashes: IStringDictionary = {};
4950

5051
constructor(
52+
private $options: IOptions,
5153
private $errors: IErrors,
5254
private $childProcess: IChildProcess,
5355
public $fs: IFileSystem,
@@ -368,6 +370,13 @@ export class WebpackCompilerService
368370

369371
envData.verbose = envData.verbose || this.$logger.isVerbose();
370372
envData.production = envData.production || prepareData.release;
373+
374+
// add the config file name to the env data so the webpack process can read the
375+
// correct config file when resolving the CLI lib and the config service
376+
// we are explicityly setting it to false to force using the defaults
377+
envData.config =
378+
process.env.NATIVESCRIPT_CONFIG_NAME ?? this.$options.config ?? "false";
379+
371380
// The snapshot generation is wrongly located in the Webpack plugin.
372381
// It should be moved in the Native Prepare of the CLI or a Gradle task in the Runtime.
373382
// As a workaround, we skip the mksnapshot, xxd and android-ndk calls based on skipNativePrepare.

test/options.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,24 @@ describe("options", () => {
107107
assert.isTrue(isExecutionStopped);
108108
});
109109

110+
it("does not break execution when valid option has correct value", () => {
111+
process.argv.push("--config");
112+
process.argv.push("SomeFilename");
113+
const options = createOptions(testInjector);
114+
options.validateOptions();
115+
process.argv.pop();
116+
process.argv.pop();
117+
assert.isFalse(isExecutionStopped);
118+
});
119+
120+
it("breaks execution when valid option has empty string value", () => {
121+
process.argv.push("--config");
122+
const options = createOptions(testInjector);
123+
options.validateOptions();
124+
process.argv.pop();
125+
assert.isTrue(isExecutionStopped);
126+
});
127+
110128
it("breaks execution when valid option has value with spaces only", () => {
111129
process.argv.push("--path");
112130
process.argv.push(" ");

test/services/project-config-service.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ const createTestInjector = (
4545

4646
readJson: (filePath: string): any => null,
4747

48+
isRelativePath: (filePath: string): any => true,
49+
4850
enumerateFilesInDirectorySync: (
4951
directoryPath: string,
5052
filterCallback?: (_file: string, _stat: IFsStats) => boolean,
@@ -141,6 +143,82 @@ describe("projectConfigService", () => {
141143
assert.deepStrictEqual(actualValue, "--expose-gc");
142144
});
143145

146+
it("can read a named JS config file when passing --config", async () => {
147+
const testInjector = createTestInjector(
148+
(filename) => sampleJSConfig,
149+
(filePath) => basename(filePath) === "custom.config.js"
150+
);
151+
152+
// mock "--config custom.config.js"
153+
const options: Options = testInjector.resolve("options") as Options;
154+
// @ts-ignore
155+
options.config = "custom.config.js";
156+
157+
const projectConfigService: IProjectConfigService = testInjector.resolve(
158+
"projectConfigService"
159+
);
160+
161+
const actualValue = projectConfigService.getValue("id");
162+
assert.deepStrictEqual(actualValue, "io.test.app");
163+
});
164+
165+
it("can read a named TS config file when passing --config", async () => {
166+
const testInjector = createTestInjector(
167+
(filename) => sampleTSConfig,
168+
(filePath) => basename(filePath) === "custom.config.ts"
169+
);
170+
171+
// mock "--config custom.config.ts"
172+
const options: Options = testInjector.resolve("options") as Options;
173+
// @ts-ignore
174+
options.config = "custom.config.ts";
175+
176+
const projectConfigService: IProjectConfigService = testInjector.resolve(
177+
"projectConfigService"
178+
);
179+
180+
const actualValue = projectConfigService.getValue("id");
181+
assert.deepStrictEqual(actualValue, "io.test.app");
182+
});
183+
184+
it("can read a named JS config file when passing --config without extension", async () => {
185+
const testInjector = createTestInjector(
186+
(filename) => sampleJSConfig,
187+
(filePath) => basename(filePath) === "custom.config.js"
188+
);
189+
190+
// mock "--config custom.config"
191+
const options: Options = testInjector.resolve("options") as Options;
192+
// @ts-ignore
193+
options.config = "custom.config";
194+
195+
const projectConfigService: IProjectConfigService = testInjector.resolve(
196+
"projectConfigService"
197+
);
198+
199+
const actualValue = projectConfigService.getValue("id");
200+
assert.deepStrictEqual(actualValue, "io.test.app");
201+
});
202+
203+
it("can read a named TS config file when passing --config without extension", async () => {
204+
const testInjector = createTestInjector(
205+
(filename) => sampleTSConfig,
206+
(filePath) => basename(filePath) === "custom.config.ts"
207+
);
208+
209+
// mock "--config custom.config"
210+
const options: Options = testInjector.resolve("options") as Options;
211+
// @ts-ignore
212+
options.config = "custom.config";
213+
214+
const projectConfigService: IProjectConfigService = testInjector.resolve(
215+
"projectConfigService"
216+
);
217+
218+
const actualValue = projectConfigService.getValue("id");
219+
assert.deepStrictEqual(actualValue, "io.test.app");
220+
});
221+
144222
// it("Throws error if no config file found", () => {
145223
// const testInjector = createTestInjector(
146224
// (filename) => null,

test/services/webpack/webpack-compiler-service.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ function createTestInjector(): IInjector {
2727
testInjector.register("childProcess", {});
2828
testInjector.register("hooksService", {});
2929
testInjector.register("hostInfo", {});
30+
testInjector.register("options", {});
3031
testInjector.register("logger", {});
3132
testInjector.register("errors", ErrorsStub);
3233
testInjector.register("packageInstallationManager", {});

0 commit comments

Comments
 (0)