Skip to content

Kddimitrov/nsconfig app folder #3356

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Feb 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions PublicAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ const tns = require("nativescript");
* [projectService](#projectservice)
* [createProject](#createproject)
* [isValidNativeScriptProject](#isvalidnativescriptproject)
* [projectDataService](#projectdataservice)
* [getProjectData](#getprojectdata)
* [getProjectDataFromContent](#getprojectdatafromcontent)
* [extensibilityService](#extensibilityservice)
* [installExtension](#installextension)
* [uninstallExtension](#uninstallextension)
Expand Down Expand Up @@ -109,6 +112,81 @@ const isValidProject = tns.projectService.isValidNativeScriptProject("/tmp/myPro
console.log(isValidProject); // true or false
```

## projectDataService
`projectDataService` provides a way to get information about a NativeScript project.

A common interface describing the results of a method is `IProjectData`:

```TypeScript
interface IProjectData extends IProjectDir {
projectName: string;
platformsDir: string;
projectFilePath: string;
projectId?: string;
dependencies: any;
devDependencies: IStringDictionary;
appDirectoryPath: string;
appResourcesDirectoryPath: string;
projectType: string;
nsConfig: INsConfig;
/**
* Initializes project data with the given project directory. If none supplied defaults to cwd.
* @param {string} projectDir Project root directory.
* @returns {void}
*/
initializeProjectData(projectDir?: string): void;
/**
* Initializes project data with the given package.json, nsconfig.json content and project directory. If none supplied defaults to cwd.
* @param {string} packageJsonContent: string
* @param {string} nsconfigContent: string
* @param {string} projectDir Project root directory.
* @returns {void}
*/
initializeProjectDataFromContent(packageJsonContent: string, nsconfigContent: string, projectDir?: string): void;
getAppDirectoryPath(projectDir?: string): string;
getAppDirectoryRelativePath(): string;
getAppResourcesDirectoryPath(projectDir?: string): string;
getAppResourcesRelativeDirectoryPath(): string;
}

interface IProjectDir {
projectDir: string;
}

interface INsConfig {
appPath?: string;
appResourcesPath?:string;
}
```

### getProjectData
Returns an initialized IProjectData object containing data about the NativeScript project in the provided `projectDir`.

* Definition:
```TypeScript
/**
* Returns an initialized IProjectData object containing data about the NativeScript project in the provided projectDir
* @param {string} projectDir The path to the project
* @returns {IProjectData} Information about the NativeScript project
*/
getProjectData(projectDir: string): IProjectData
```

### getProjectDataFromContent
Returns an IProjectData object that is initialized with the provided package.json content, nsconfig.json content and `projectDir`.

* Definition:
```TypeScript
/**
* Returns an initialized IProjectData object containing data about the NativeScript project in the provided projectDir
* @param {string} packageJsonContent The content of the project.json file in the root of the project
* @param {string} nsconfigContent The content of the nsconfig.json file in the root of the project
* @param {string} projectDir The path to the project
* @returns {IProjectData} Information about the NativeScript project
*/
getProjectDataFromContent(packageJsonContent: string, nsconfigContent: string, projectDir?: string): IProjectData
```

## extensibilityService
`extensibilityService` module gives access to methods for working with CLI's extensions - list, install, uninstall, load them. The extensions add new functionality to CLI, so once an extension is loaded, all methods added to it's public API are accessible directly through CLI when it is used as a library. Extensions may also add new commands, so they are accessible through command line when using NativeScript CLI.

Expand Down
2 changes: 1 addition & 1 deletion lib/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ $injector.require("options", "./options");
$injector.require("nativescript-cli", "./nativescript-cli");

$injector.require("projectData", "./project-data");
$injector.require("projectDataService", "./services/project-data-service");
$injector.requirePublic("projectDataService", "./services/project-data-service");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you add a new method to PublicApi, you have to:

  1. Update the documentation: https://github.com/NativeScript/nativescript-cli/blob/master/PublicAPI.md
  2. Add the service and the exposed methods in the nativescript-cli-lib tests

NOTE: More information is available here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

$injector.requirePublic("projectService", "./services/project-service");
$injector.require("androidProjectService", "./services/android-project-service");
$injector.require("iOSEntitlementsService", "./services/ios-entitlements-service");
Expand Down
2 changes: 1 addition & 1 deletion lib/commands/test-init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class TestInitCommand implements ICommand {

await this.$pluginsService.add('nativescript-unit-test-runner', this.$projectData);

const testsDir = path.join(projectDir, 'app/tests');
const testsDir = path.join(this.$projectData.appDirectoryPath, 'tests');
let shouldCreateSampleTests = true;
if (this.$fs.exists(testsDir)) {
this.$logger.info('app/tests/ directory already exists, will not create an example test project.');
Expand Down
2 changes: 1 addition & 1 deletion lib/common
Submodule common updated 2 files
+2 −2 file-system.ts
+5 −0 helpers.ts
3 changes: 3 additions & 0 deletions lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export const BUILD_DIR = "build";
export const OUTPUTS_DIR = "outputs";
export const APK_DIR = "apk";
export const RESOURCES_DIR = "res";
export const CONFIG_NS_FILE_NAME = "nsconfig.json";
export const CONFIG_NS_APP_RESOURCES_ENTRY = "appResourcesPath";
export const CONFIG_NS_APP_ENTRY = "appPath";

export class PackageVersion {
static NEXT = "next";
Expand Down
2 changes: 1 addition & 1 deletion lib/definitions/livesync.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ interface IDebugLiveSyncService extends ILiveSyncService {
* @param {ILiveSyncInfo} liveSyncData Information needed for livesync - for example if bundle is passed or if a release build should be performed.
* @returns {Promise<string[]>} The glob patterns.
*/
getWatcherPatterns(liveSyncData: ILiveSyncInfo): Promise<string[]>;
getWatcherPatterns(liveSyncData: ILiveSyncInfo, projectData: IProjectData): Promise<string[]>;

/**
* Prints debug information.
Expand Down
11 changes: 11 additions & 0 deletions lib/definitions/project.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ interface IProjectService {
isValidNativeScriptProject(pathToProject?: string): boolean;
}

interface INsConfig {
appPath?: string;
appResourcesPath?:string;
}

interface IProjectData extends IProjectDir {
projectName: string;
platformsDir: string;
Expand All @@ -63,12 +68,18 @@ interface IProjectData extends IProjectDir {
appDirectoryPath: string;
appResourcesDirectoryPath: string;
projectType: string;
nsConfig: INsConfig;
/**
* Initializes project data with the given project directory. If none supplied defaults to --path option or cwd.
* @param {string} projectDir Project root directory.
* @returns {void}
*/
initializeProjectData(projectDir?: string): void;
initializeProjectDataFromContent(packageJsonContent: string, nsconfigContent: string, projectDir?: string): void;
getAppDirectoryPath(projectDir?: string): string;
getAppDirectoryRelativePath(): string;
getAppResourcesDirectoryPath(projectDir?: string): string;
getAppResourcesRelativeDirectoryPath(): string;
}

interface IProjectDataService {
Expand Down
138 changes: 112 additions & 26 deletions lib/project-data.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as constants from "./constants";
import * as path from "path";
import { parseJson } from "./common/helpers";
import { EOL } from "os";

interface IProjectType {
Expand Down Expand Up @@ -32,6 +33,7 @@ export class ProjectData implements IProjectData {
public projectFilePath: string;
public projectId: string;
public projectName: string;
public nsConfig: any;
public appDirectoryPath: string;
public appResourcesDirectoryPath: string;
public dependencies: any;
Expand All @@ -47,46 +49,130 @@ export class ProjectData implements IProjectData {

public initializeProjectData(projectDir?: string): void {
projectDir = projectDir || this.$projectHelper.projectDir;

// If no project found, projectDir should be null
if (projectDir) {
const projectFilePath = path.join(projectDir, this.$staticConfig.PROJECT_FILE_NAME);
let data: any = null;
const projectFilePath = this.getProjectFilePath(projectDir);

if (this.$fs.exists(projectFilePath)) {
let fileContent: any = null;
try {
fileContent = this.$fs.readJson(projectFilePath);
data = fileContent[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE];
} catch (err) {
this.$errors.failWithoutHelp(`The project file ${this.projectFilePath} is corrupted. ${EOL}` +
`Consider restoring an earlier version from your source control or backup.${EOL}` +
`Additional technical info: ${err.toString()}`);
}

if (data) {
this.projectDir = projectDir;
this.projectName = this.$projectHelper.sanitizeName(path.basename(projectDir));
this.platformsDir = path.join(projectDir, constants.PLATFORMS_DIR_NAME);
this.projectFilePath = projectFilePath;
this.appDirectoryPath = path.join(projectDir, constants.APP_FOLDER_NAME);
this.appResourcesDirectoryPath = path.join(projectDir, constants.APP_FOLDER_NAME, constants.APP_RESOURCES_FOLDER_NAME);
this.projectId = data.id;
this.dependencies = fileContent.dependencies;
this.devDependencies = fileContent.devDependencies;
this.projectType = this.getProjectType();

return;
}
const packageJsonContent = this.$fs.readText(projectFilePath);
const nsConfigContent = this.getNsConfigContent(projectDir);

this.initializeProjectDataFromContent(packageJsonContent, nsConfigContent, projectDir);
}

return;
}

this.errorInvalidProject(projectDir);
}

public initializeProjectDataFromContent(packageJsonContent: string, nsconfigContent: string, projectDir?: string): void {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sis0k0 is it ok to pass the content as string or you'll need a method that accepts the parsed json object (i.e. the parsed nsconfig.json) itself?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to pass the content as string and let the project data service do the parsing/validation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't nsconfigContent optional?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I have missed to make it optional as we don't use it in the codebase.

projectDir = projectDir || this.$projectHelper.projectDir || "";
const projectFilePath = this.getProjectFilePath(projectDir);
// If no project found, projectDir should be null
let nsData = null;
let nsConfig: INsConfig = null;
let packageJsonData = null;

try {
packageJsonData = parseJson(packageJsonContent);
nsData = packageJsonData[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE];
} catch (err) {
this.$errors.failWithoutHelp(`The project file ${this.projectFilePath} is corrupted. ${EOL}` +
`Consider restoring an earlier version from your source control or backup.${EOL}` +
`Additional technical info: ${err.toString()}`);
}

try {
nsConfig = nsconfigContent ? <INsConfig>parseJson(nsconfigContent) : null;
} catch (err) {
this.$errors.failWithoutHelp(`The NativeScript configuration file ${constants.CONFIG_NS_FILE_NAME} is corrupted. ${EOL}` +
`Consider restoring an earlier version from your source control or backup.${EOL}` +
`Additional technical info: ${err.toString()}`);
}

if (nsData) {
this.projectDir = projectDir;
this.projectName = this.$projectHelper.sanitizeName(path.basename(projectDir));
this.platformsDir = path.join(projectDir, constants.PLATFORMS_DIR_NAME);
this.projectFilePath = projectFilePath;
this.projectId = nsData.id;
this.dependencies = packageJsonData.dependencies;
this.devDependencies = packageJsonData.devDependencies;
this.projectType = this.getProjectType();
this.nsConfig = nsConfig;
this.appDirectoryPath = this.getAppDirectoryPath();
this.appResourcesDirectoryPath = this.getAppResourcesDirectoryPath();

return;
}

this.errorInvalidProject(projectDir);
}

private errorInvalidProject(projectDir: string): void {
const currentDir = path.resolve(".");
this.$logger.trace(`Unable to find project. projectDir: ${projectDir}, options.path: ${this.$options.path}, ${currentDir}`);

// This is the case when no project file found
this.$errors.fail("No project found at or above '%s' and neither was a --path specified.", projectDir || this.$options.path || currentDir);
}

private getProjectFilePath(projectDir: string): string {
return path.join(projectDir, this.$staticConfig.PROJECT_FILE_NAME);
}

public getAppResourcesDirectoryPath(projectDir?: string): string {
const appResourcesRelativePath = this.getAppResourcesRelativeDirectoryPath();

return this.resolveToProjectDir(appResourcesRelativePath, projectDir);
}

public getAppResourcesRelativeDirectoryPath(): string {
if (this.nsConfig && this.nsConfig[constants.CONFIG_NS_APP_RESOURCES_ENTRY]) {
return this.nsConfig[constants.CONFIG_NS_APP_RESOURCES_ENTRY];
}

return path.join(this.getAppDirectoryRelativePath(), constants.APP_RESOURCES_FOLDER_NAME);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can shorten this:

return this.nsConfig && this.nsConfig[constants.CONFIG_NS_APP_RESOURCES_ENTRY]
           || path.join(this.getAppDirectoryRelativePath(), constants.APP_RESOURCES_FOLDER_NAME);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I consider mine more readable and structured. Will change it if you insist.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, not a problem ;)

}

public getAppDirectoryPath(projectDir?: string): string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic in this method is very similiar to getAppResourcesDirectoryPath(). Can we try to extract the common part?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

const appRelativePath = this.getAppDirectoryRelativePath();

return this.resolveToProjectDir(appRelativePath, projectDir);
}

public getAppDirectoryRelativePath(): string {
if (this.nsConfig && this.nsConfig[constants.CONFIG_NS_APP_ENTRY]) {
return this.nsConfig[constants.CONFIG_NS_APP_ENTRY];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what will happen in case the path in the nsconfig.json is not relative?

Copy link
Contributor Author

@KristianDD KristianDD Feb 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should add validation for this as supporting paths not relative to the project will be very hard in cloud builds. Maybe we should also change the key inside nsconfig.json to appRelativePath or something in this lines.

}

return constants.APP_FOLDER_NAME;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above - you can shorten the code:

return this.nsConfig && this.nsConfig[constants.CONFIG_NS_APP_ENTRY] || constants.APP_FOLDER_NAME;

}

private getNsConfigContent(projectDir: string): string {
const configNSFilePath = path.join(projectDir, constants.CONFIG_NS_FILE_NAME);

if (!this.$fs.exists(configNSFilePath)) {
return null;
}

return this.$fs.readText(configNSFilePath);
}

private resolveToProjectDir(pathToResolve: string, projectDir?: string): string {
if (!projectDir) {
projectDir = this.projectDir;
}

if (!projectDir) {
return null;
}

return path.resolve(projectDir, pathToResolve);
}

private getProjectType(): string {
let detectedProjectType = _.find(ProjectData.PROJECT_TYPES, (projectType) => projectType.isDefaultProjectType).type;

Expand Down
16 changes: 9 additions & 7 deletions lib/providers/project-files-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,33 @@ import { ProjectFilesProviderBase } from "../common/services/project-files-provi
export class ProjectFilesProvider extends ProjectFilesProviderBase {
constructor(private $platformsData: IPlatformsData,
$mobileHelper: Mobile.IMobileHelper,
$options:IOptions) {
super($mobileHelper, $options);
$options: IOptions) {
super($mobileHelper, $options);
}

private static INTERNAL_NONPROJECT_FILES = [ "**/*.ts" ];
private static INTERNAL_NONPROJECT_FILES = ["**/*.ts"];

public mapFilePath(filePath: string, platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): string {
const platformData = this.$platformsData.getPlatformData(platform.toLowerCase(), projectData);
const parsedFilePath = this.getPreparedFilePath(filePath, projectFilesConfig);
let mappedFilePath = "";
let relativePath;
if (parsedFilePath.indexOf(constants.NODE_MODULES_FOLDER_NAME) > -1) {
const relativePath = path.relative(path.join(projectData.projectDir, constants.NODE_MODULES_FOLDER_NAME), parsedFilePath);
relativePath = path.relative(path.join(projectData.projectDir, constants.NODE_MODULES_FOLDER_NAME), parsedFilePath);
mappedFilePath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME, constants.TNS_MODULES_FOLDER_NAME, relativePath);
} else {
mappedFilePath = path.join(platformData.appDestinationDirectoryPath, path.relative(projectData.projectDir, parsedFilePath));
relativePath = path.relative(projectData.appDirectoryPath, parsedFilePath);
mappedFilePath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME, relativePath);
}

const appResourcesDirectoryPath = path.join(constants.APP_FOLDER_NAME, constants.APP_RESOURCES_FOLDER_NAME);
const appResourcesDirectoryPath = projectData.appResourcesDirectoryPath;
const platformSpecificAppResourcesDirectoryPath = path.join(appResourcesDirectoryPath, platformData.normalizedPlatformName);
if (parsedFilePath.indexOf(appResourcesDirectoryPath) > -1 && parsedFilePath.indexOf(platformSpecificAppResourcesDirectoryPath) === -1) {
return null;
}

if (parsedFilePath.indexOf(platformSpecificAppResourcesDirectoryPath) > -1) {
const appResourcesRelativePath = path.relative(path.join(projectData.projectDir, constants.APP_FOLDER_NAME, constants.APP_RESOURCES_FOLDER_NAME,
const appResourcesRelativePath = path.relative(path.join(projectData.appResourcesDirectoryPath,
platformData.normalizedPlatformName), parsedFilePath);
mappedFilePath = path.join(platformData.platformProjectService.getAppResourcesDestinationDirectoryPath(projectData), appResourcesRelativePath);
}
Expand Down
Loading