Skip to content

Commit 6dde0b2

Browse files
author
Fatme
authored
Merge pull request #3720 from NativeScript/fatme/merge-release-into-master
chore: merge release into master
2 parents 7b16e18 + 4eb69e1 commit 6dde0b2

15 files changed

+957
-136
lines changed

lib/bootstrap.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,3 +169,5 @@ $injector.requirePublic("assetsGenerationService", "./services/assets-generation
169169
$injector.require("filesHashService", "./services/files-hash-service");
170170
$injector.require("iOSLogParserService", "./services/ios-log-parser-service");
171171
$injector.require("iOSDebuggerPortService", "./services/ios-debugger-port-service");
172+
173+
$injector.require("pacoteService", "./services/pacote-service");

lib/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ export const VERSION_STRING = "version";
116116
export const INSPECTOR_CACHE_DIRNAME = "ios-inspector";
117117
export const POST_INSTALL_COMMAND_NAME = "post-install-cli";
118118
export const ANDROID_RELEASE_BUILD_ERROR_MESSAGE = "When producing a release build, you need to specify all --key-store-* options.";
119+
export const CACACHE_DIRECTORY_NAME = "_cacache";
119120

120121
export class DebugCommandErrors {
121122
public static UNABLE_TO_USE_FOR_DEVICE_AND_EMULATOR = "The options --for-device and --emulator cannot be used simultaneously. Please use only one of them.";

lib/declarations.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ interface INodePackageManager {
4646
* @returns {any} The full data from registry.npmjs.org for this package.
4747
*/
4848
getRegistryPackageData(packageName: string): Promise<any>;
49+
50+
/**
51+
* Gets the path to npm cache directory.
52+
* @returns {string} The full path to npm cache directory
53+
*/
54+
getCachePath(): Promise<string>;
4955
}
5056

5157
interface INpmInstallationManager {

lib/definitions/pacote-service.d.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import * as pacote from "pacote";
2+
import * as tar from "tar";
3+
4+
declare global {
5+
6+
interface IPacoteService {
7+
/**
8+
* Returns the package's json file content
9+
* @param packageName The name of the package
10+
* @param options The provided options can control which properties from package.json file will be returned. In case when fullMetadata option is provided, all data from package.json file will be returned.
11+
*/
12+
manifest(packageName: string, options: IPacoteManifestOptions): Promise<any>;
13+
/**
14+
* Downloads the specified package and extracts it in specified destination directory
15+
* @param packageName The name of the package
16+
* @param destinationDirectory The path to directory where the downloaded tarball will be extracted.
17+
*/
18+
extractPackage(packageName: string, destinationDirectory: string, options?: IPacoteExtractOptions): Promise<void>;
19+
}
20+
21+
interface IPacoteBaseOptions {
22+
/**
23+
* The path to npm cache
24+
*/
25+
cache?: string;
26+
}
27+
28+
interface IPacoteManifestOptions extends IPacoteBaseOptions {
29+
/**
30+
* If true, the whole package.json data will be returned
31+
*/
32+
fullMetadata?: boolean;
33+
}
34+
35+
interface IPacoteExtractOptions {
36+
filter?: (path: string, stat: tar.FileStat) => boolean;
37+
}
38+
}

lib/definitions/pacote.d.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
declare module "pacote" {
2+
import * as stream from "stream";
3+
4+
export function manifest(packageName: string, options: IPacoteManifestOptions): Promise<any>;
5+
export var tarball: IPacoteTarballResult;
6+
7+
interface IPacoteTarballResult {
8+
stream: (packageName: string, options: IPacoteBaseOptions) => IPacoteTarballStreamResult;
9+
toFile: (packageName: string) => IPacoteTarballFileResult;
10+
}
11+
12+
interface IPacoteTarballStreamResult extends stream.Readable { }
13+
interface IPacoteTarballFileResult { }
14+
}

lib/definitions/project.d.ts

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,41 @@ interface IImageDefinitionsStructure {
210210
}
211211

212212
interface ITemplateData {
213+
/**
214+
* The normalized template name
215+
* In case no --template option is provided, use default template name
216+
* In case --template <templateName> option is provided, use <templateName>
217+
* In case --template <templateName>@<version> is provided, use <templateName>
218+
* In case --ng option is provided, use default angular template name
219+
* In case --ts option is provided, use default typescript template name
220+
*/
221+
templateName: string;
222+
/**
223+
* The path to the template.
224+
* In case template is v1, will be {pathToProjectDir}/node_modules/{templateName}.
225+
* In case template is v2, will be null.
226+
*/
213227
templatePath: string;
228+
/**
229+
* The templateVersion property from nativescript section inside package.json file
230+
* "nativescript": {
231+
"id": "org.nativescript.app6",
232+
"templateVersion": "v2"
233+
}
234+
*/
214235
templateVersion: string;
236+
/**
237+
* The whole content of package.json file
238+
*/
239+
templatePackageJsonContent: ITemplatePackageJsonContent;
240+
}
241+
242+
interface ITemplatePackageJsonContent extends IBasePluginData {
243+
dependencies: IStringDictionary;
244+
devDependencies: IStringDictionary;
245+
nativescript?: {
246+
templateVersion?: string;
247+
}
215248
}
216249

217250
/**
@@ -227,14 +260,6 @@ interface IProjectTemplatesService {
227260
* @return {ITemplateData} Data describing the template - location where it is installed and its NativeScript version.
228261
*/
229262
prepareTemplate(templateName: string, projectDir: string): Promise<ITemplateData>;
230-
231-
/**
232-
* Gives information for the nativescript specific version of the template, for example v1, v2, etc.
233-
* Defaults to v1 in case there's no version specified.
234-
* @param {string} templatePath Full path to the template.
235-
* @returns {string} The version, for example v1 or v2.
236-
*/
237-
getTemplateVersion(templatePath: string): string;
238263
}
239264

240265
interface IPlatformProjectServiceBase {

lib/node-package-manager.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as path from "path";
2-
import { exported } from "./common/decorators";
2+
import { exported, cache } from "./common/decorators";
33
import { isInteractive } from "./common/helpers";
4+
import { CACACHE_DIRECTORY_NAME } from "./constants";
45

56
export class NodePackageManager implements INodePackageManager {
67
private static SCOPED_DEPENDENCY_REGEXP = /^(@.+?)(?:@(.+?))?$/;
@@ -128,6 +129,12 @@ export class NodePackageManager implements INodePackageManager {
128129
return jsonData;
129130
}
130131

132+
@cache()
133+
public async getCachePath(): Promise<string> {
134+
const cachePath = await this.$childProcess.exec(`npm config get cache`);
135+
return path.join(cachePath.trim(), CACACHE_DIRECTORY_NAME);
136+
}
137+
131138
private getNpmExecutableName(): string {
132139
let npmExecutableName = "npm";
133140

lib/services/pacote-service.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import * as pacote from "pacote";
2+
import * as tar from "tar";
3+
4+
export class PacoteService implements IPacoteService {
5+
constructor(private $npm: INodePackageManager) { }
6+
7+
public async manifest(packageName: string, options?: IPacoteManifestOptions): Promise<any> {
8+
// In case `tns create myapp --template https://github.com/NativeScript/template-hello-world.git` command is executed, pacote module throws an error if cache option is not provided.
9+
const manifestOptions = { cache: await this.$npm.getCachePath() };
10+
if (options) {
11+
_.extend(manifestOptions, options);
12+
}
13+
14+
return pacote.manifest(packageName, manifestOptions);
15+
}
16+
17+
public async extractPackage(packageName: string, destinationDirectory: string, options?: IPacoteExtractOptions): Promise<void> {
18+
// strip: Remove the specified number of leading path elements. Pathnames with fewer elements will be silently skipped. More info: https://github.com/npm/node-tar/blob/e89c4d37519b1c20133a9f49d5f6b85fa34c203b/README.md
19+
// C: Create an archive
20+
const extractOptions = { strip: 1, C: destinationDirectory };
21+
if (options) {
22+
_.extend(extractOptions, options);
23+
}
24+
25+
const source = pacote.tarball.stream(packageName, { cache: await this.$npm.getCachePath() });
26+
const destination = tar.x(extractOptions);
27+
source.pipe(destination);
28+
29+
return new Promise<void>((resolve, reject) => {
30+
destination.on("error", (err: Error) => reject(err));
31+
destination.on("finish", () => resolve());
32+
});
33+
}
34+
}
35+
$injector.register("pacoteService", PacoteService);

lib/services/project-service.ts

Lines changed: 41 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as constants from "../constants";
22
import * as path from "path";
33
import * as shelljs from "shelljs";
4+
import { format } from "util";
45
import { exported } from "../common/decorators";
56
import { Hooks } from "../constants";
67

@@ -11,6 +12,7 @@ export class ProjectService implements IProjectService {
1112
private $errors: IErrors,
1213
private $fs: IFileSystem,
1314
private $logger: ILogger,
15+
private $pacoteService: IPacoteService,
1416
private $projectDataService: IProjectDataService,
1517
private $projectHelper: IProjectHelper,
1618
private $projectNameService: IProjectNameService,
@@ -21,7 +23,6 @@ export class ProjectService implements IProjectService {
2123
@exported("projectService")
2224
public async createProject(projectOptions: IProjectSettings): Promise<ICreateProjectData> {
2325
let projectName = projectOptions.projectName;
24-
let selectedTemplate = projectOptions.template;
2526

2627
if (!projectName) {
2728
this.$errors.fail("You must specify <App name> when creating a new project.");
@@ -41,11 +42,8 @@ export class ProjectService implements IProjectService {
4142
const appId = projectOptions.appId || this.$projectHelper.generateDefaultAppId(projectName, constants.DEFAULT_APP_IDENTIFIER_PREFIX);
4243
this.createPackageJson(projectDir, appId);
4344
this.$logger.trace(`Creating a new NativeScript project with name ${projectName} and id ${appId} at location ${projectDir}`);
44-
if (!selectedTemplate) {
45-
selectedTemplate = constants.RESERVED_TEMPLATE_NAMES["default"];
46-
}
4745

48-
const projectCreationData = await this.createProjectCore({ template: selectedTemplate, projectDir, ignoreScripts: projectOptions.ignoreScripts, appId: appId, projectName });
46+
const projectCreationData = await this.createProjectCore({ template: projectOptions.template, projectDir, ignoreScripts: projectOptions.ignoreScripts, appId: appId, projectName });
4947

5048
this.$logger.printMarkdown("Project `%s` was successfully created.", projectCreationData.projectName);
5149

@@ -56,20 +54,25 @@ export class ProjectService implements IProjectService {
5654
const { template, projectDir, appId, projectName, ignoreScripts } = projectCreationSettings;
5755

5856
try {
59-
const { templatePath, templateVersion } = await this.$projectTemplatesService.prepareTemplate(template, projectDir);
60-
await this.extractTemplate(projectDir, templatePath, templateVersion);
57+
const templateData = await this.$projectTemplatesService.prepareTemplate(template, projectDir);
58+
const templatePackageJsonContent = templateData.templatePackageJsonContent;
59+
const templateVersion = templateData.templateVersion;
6160

62-
await this.ensureAppResourcesExist(projectDir);
61+
await this.extractTemplate(projectDir, templateData);
6362

64-
const templatePackageJsonData = this.getDataFromJson(templatePath);
63+
if (templateVersion === constants.TemplateVersions.v2) {
64+
this.alterPackageJsonData(projectDir, appId);
65+
}
6566

66-
if (!(templatePackageJsonData && templatePackageJsonData.dependencies && templatePackageJsonData.dependencies[constants.TNS_CORE_MODULES_NAME])) {
67-
await this.$npmInstallationManager.install(constants.TNS_CORE_MODULES_NAME, projectDir, { dependencyType: "save" });
67+
await this.ensureAppResourcesExist(projectDir);
68+
69+
if (!(templatePackageJsonContent && templatePackageJsonContent.dependencies && templatePackageJsonContent.dependencies[constants.TNS_CORE_MODULES_NAME])) {
70+
await this.addTnsCoreModules(projectDir);
6871
}
6972

7073
if (templateVersion === constants.TemplateVersions.v1) {
71-
this.mergeProjectAndTemplateProperties(projectDir, templatePackageJsonData); // merging dependencies from template (dev && prod)
72-
this.removeMergedDependencies(projectDir, templatePackageJsonData);
74+
this.mergeProjectAndTemplateProperties(projectDir, templatePackageJsonContent); // merging dependencies from template (dev && prod)
75+
this.removeMergedDependencies(projectDir, templatePackageJsonContent);
7376
}
7477

7578
// Install devDependencies and execute all scripts:
@@ -79,12 +82,8 @@ export class ProjectService implements IProjectService {
7982
ignoreScripts
8083
});
8184

82-
const templatePackageJsonPath = templateVersion === constants.TemplateVersions.v2 ? path.join(projectDir, constants.PACKAGE_JSON_FILE_NAME) : path.join(templatePath, constants.PACKAGE_JSON_FILE_NAME);
83-
const templatePackageJson = this.$fs.readJson(templatePackageJsonPath);
84-
85-
await this.$npm.uninstall(templatePackageJson.name, { save: true }, projectDir);
86-
if (templateVersion === constants.TemplateVersions.v2) {
87-
this.alterPackageJsonData(projectDir, appId);
85+
if (templateVersion === constants.TemplateVersions.v1) {
86+
await this.$npm.uninstall(templatePackageJsonContent.name, { save: true }, projectDir);
8887
}
8988
} catch (err) {
9089
this.$fs.deleteDirectory(projectDir);
@@ -109,40 +108,27 @@ export class ProjectService implements IProjectService {
109108
}
110109
}
111110

112-
private getDataFromJson(templatePath: string): any {
113-
const templatePackageJsonPath = path.join(templatePath, constants.PACKAGE_JSON_FILE_NAME);
114-
if (this.$fs.exists(templatePackageJsonPath)) {
115-
const templatePackageJsonData = this.$fs.readJson(templatePackageJsonPath);
116-
return templatePackageJsonData;
117-
} else {
118-
this.$logger.trace(`Template ${templatePath} does not have ${constants.PACKAGE_JSON_FILE_NAME} file.`);
119-
}
120-
121-
return null;
122-
}
123-
124-
private async extractTemplate(projectDir: string, realTemplatePath: string, templateVersion: string): Promise<void> {
111+
private async extractTemplate(projectDir: string, templateData: ITemplateData): Promise<void> {
125112
this.$fs.ensureDirectoryExists(projectDir);
126113

127-
this.$logger.trace(`Template version is ${templateVersion}`);
128-
let destinationDir = "";
129-
switch (templateVersion) {
114+
switch (templateData.templateVersion) {
130115
case constants.TemplateVersions.v1:
131116
const projectData = this.$projectDataService.getProjectData(projectDir);
132-
const appDestinationPath = projectData.getAppDirectoryPath(projectDir);
133-
this.$fs.createDirectory(appDestinationPath);
134-
destinationDir = appDestinationPath;
117+
const destinationDirectory = projectData.getAppDirectoryPath(projectDir);
118+
this.$fs.createDirectory(destinationDirectory);
119+
120+
this.$logger.trace(`Copying application from '${templateData.templatePath}' into '${destinationDirectory}'.`);
121+
shelljs.cp('-R', path.join(templateData.templatePath, "*"), destinationDirectory);
122+
123+
this.$fs.createDirectory(path.join(projectDir, "platforms"));
135124
break;
136125
case constants.TemplateVersions.v2:
126+
await this.$pacoteService.extractPackage(templateData.templateName, projectDir);
127+
break;
137128
default:
138-
destinationDir = projectDir;
129+
this.$errors.failWithoutHelp(format(constants.ProjectTemplateErrors.InvalidTemplateVersionStringFormat, templateData.templateName, templateData.templateVersion));
139130
break;
140131
}
141-
142-
this.$logger.trace(`Copying application from '${realTemplatePath}' into '${destinationDir}'.`);
143-
shelljs.cp('-R', path.join(realTemplatePath, "*"), destinationDir);
144-
145-
this.$fs.createDirectory(path.join(projectDir, "platforms"));
146132
}
147133

148134
private async ensureAppResourcesExist(projectDir: string): Promise<void> {
@@ -154,36 +140,7 @@ export class ProjectService implements IProjectService {
154140
this.$fs.createDirectory(appResourcesDestinationPath);
155141

156142
// the template installed doesn't have App_Resources -> get from a default template
157-
const defaultTemplateName = constants.RESERVED_TEMPLATE_NAMES["default"];
158-
await this.$npm.install(defaultTemplateName, projectDir, {
159-
save: true,
160-
disableNpmInstall: false,
161-
frameworkPath: null,
162-
ignoreScripts: false
163-
});
164-
165-
const defaultTemplatePath = path.join(projectDir, constants.NODE_MODULES_FOLDER_NAME, defaultTemplateName);
166-
const defaultTemplateVersion = this.$projectTemplatesService.getTemplateVersion(defaultTemplatePath);
167-
168-
let defaultTemplateAppResourcesPath: string = null;
169-
switch (defaultTemplateVersion) {
170-
case constants.TemplateVersions.v1:
171-
defaultTemplateAppResourcesPath = path.join(projectDir,
172-
constants.NODE_MODULES_FOLDER_NAME,
173-
defaultTemplateName,
174-
constants.APP_RESOURCES_FOLDER_NAME);
175-
break;
176-
case constants.TemplateVersions.v2:
177-
default:
178-
const defaultTemplateProjectData = this.$projectDataService.getProjectData(defaultTemplatePath);
179-
defaultTemplateAppResourcesPath = defaultTemplateProjectData.appResourcesDirectoryPath;
180-
}
181-
182-
if (defaultTemplateAppResourcesPath && this.$fs.exists(defaultTemplateAppResourcesPath)) {
183-
shelljs.cp('-R', defaultTemplateAppResourcesPath, appPath);
184-
}
185-
186-
await this.$npm.uninstall(defaultTemplateName, { save: true }, projectDir);
143+
await this.$pacoteService.extractPackage(constants.RESERVED_TEMPLATE_NAMES["default"], appPath, { filter: (name: string, entry: any) => entry.path.indexOf(constants.APP_RESOURCES_FOLDER_NAME) !== -1 });
187144
}
188145
}
189146

@@ -271,5 +228,15 @@ export class ProjectService implements IProjectService {
271228
private setAppId(projectDir: string, projectId: string): void {
272229
this.$projectDataService.setNSValue(projectDir, "id", projectId);
273230
}
231+
232+
private async addTnsCoreModules(projectDir: string): Promise<void> {
233+
const projectFilePath = path.join(projectDir, this.$staticConfig.PROJECT_FILE_NAME);
234+
const packageJsonData = this.$fs.readJson(projectFilePath);
235+
236+
const version = await this.$npmInstallationManager.getLatestCompatibleVersion(constants.TNS_CORE_MODULES_NAME);
237+
packageJsonData.dependencies[constants.TNS_CORE_MODULES_NAME] = version;
238+
239+
this.$fs.writeJson(projectFilePath, packageJsonData);
240+
}
274241
}
275242
$injector.register("projectService", ProjectService);

0 commit comments

Comments
 (0)