-
-
Notifications
You must be signed in to change notification settings - Fork 197
remove npm2 dependency #2209
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
remove npm2 dependency #2209
Conversation
23c9293
to
6c50fb5
Compare
} | ||
return diff; | ||
} catch (err) { | ||
if (err.code === "EPEERINVALID") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we're not using npm as a library anymore, will we actually get an exception with an EPEERINVALID code
from the child process? Seems like we should either delete this code or handle invalid peer dependencies in another way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right I hadn't thought about that, will make adjustments
EDIT: @hdeshev after some research it turns out we throw exception on exec and this catch is executed, so we're safe
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With new implementation we handle the EPEERINVALID error same as before.
let subDirsBefore:Array<string> = this.$fs.exists(path.join(pathToSave, "node_modules")).wait() ? this.$fs.readDirectory(path.join(pathToSave, "node_modules")).wait() : []; | ||
|
||
let flags = this.getFlagsString(config); | ||
this.$childProcess.exec(`npm install ${packageName} ${flags}`, { cwd: pathToSave }).wait(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This exec
invocation looks like it's eating the child process stdout. I think we should inherit stdio here so that npm-related errors are visible to the user. Hmm, yes, we need stdin too -- I've heard rumors on the internet there are packages that block the console asking for email addresses on installation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hdeshev inside childProcess.exec we do a throw which is caught by the catch, so we don't consume the error: https://github.com/telerik/mobile-cli-lib/blob/16a4c64e6f1ac2c58d21d92a5da11bc73854451c/child-process.ts#L14
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the new version i use $childProcess.spawnFromEvent(...) so we can handle the stdout and stderr to keep previous behavior.
}).future<boolean>()(); | ||
} | ||
|
||
// Is this used? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest we kill this commented out code block..
assert.strictEqual(path.basename(actualPathToTemplate), expectedTemplatePath); | ||
assert.strictEqual(isDeleteDirectoryCalledForNodeModulesDir, true, "When correct path is returned, template's node_modules directory should be deleted."); | ||
}); | ||
//there is no tmp directory anymore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Delete commented-out code.
Squash those commits, please. |
67619c9
to
bc808aa
Compare
} | ||
}); | ||
return future; | ||
public install(packageName: string, pathToSave: string, config?: any): any { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this method has async operation in it, so it should return IFuture. Also it's a good practice to avoid setting return type to any. As far as I can see, the return type here should be IFuture<string[]>
return; | ||
let flags = this.getFlagsString(config, true); | ||
let params = ["install", packageName]; | ||
for(let index in flags) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can directly concat the params
and flags
arrays:
let params = [ "install", packageName ].concat(flags);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tried, didn't work :D
if (this.$options.ignoreScripts) { | ||
config = config || {}; | ||
config["ignore-scripts"] = true; | ||
let res = this.$childProcess.spawnFromEvent("npm", params, "close", {}, { throwError: false }).wait(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not entirely sure about this, but we've used to pass null
for the fourth argument instead of empty object. This way the default childProcess options will be used.
config["ignore-scripts"] = true; | ||
let res = this.$childProcess.spawnFromEvent("npm", params, "close", {}, { throwError: false }).wait(); | ||
if(res.stderr) { | ||
throw {stderr: res.stderr}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this way the error will not have message. Most of the cosumers which print errors use the message property of the error. You can use:
throw new Error(res.stderr);
and later you can check if err.message.indexOf("EPEERINVALID") !== -1
* The solution is to traverse node_modules before and after install and get the name of the installed package, | ||
* even if it's installed through local path or URL. If command installes more than one package, all package names are returned. | ||
*/ | ||
let diff = subDirsAfter.filter(x => subDirsBefore.indexOf(x) === -1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case I want to install a single package, via .tgz, but it has dependencies, npm 3 will install all of them on the root level and current code will return list of all of them. But the method should have installed a single package and return its name. Is this behavior expected?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rosen-vladimirov, yes this behavior is expected
} | ||
|
||
/** | ||
* Reads package.json/nativescript/tns-(android/ios)/template if any and installes it. | ||
* We need to remove this, because it may override the template already present in app if someone decides to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
platform template is the template used for platforms/<platform>/
directory. It cannot override anything in app dir.
} | ||
} else { | ||
this.updatePlatformCore(platformData, currentVersion, newVersion, canUpdate).wait(); | ||
this.$errors.fail("Native Platfrom cannot be updated."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Platfrom -> Platform
Using $errors.fail
will print command's help after the error, is this required here or failWithoutHelp
is more appropriate?
//npmInstallResultStream is not very usefull (doesn't contain all the package information), so we need to find the real one inside node_modules | ||
let foundValidPlugin = false; | ||
let realNpmPackageJson:any; | ||
for(let index in installedModuleNames) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can use of
instead of in
which will give you the name directly:
for(let name of installedModuleNames) {
...
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of
for(let index in installedModuleNames) { | ||
let name = installedModuleNames[index]; | ||
let pathToRealNpmPackageJson = `${this.$projectData.projectDir}/node_modules/${name}/package.json`; | ||
realNpmPackageJson = require(pathToRealNpmPackageJson); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's safer to use this.$fs.readJson(pathToRealNpmPackageJson).wait()
as require caches results and also it does not work in case the file has BOM header
/** | ||
* Provides a platform specific update logic for the specified runtime versions. | ||
* @return true in cases when the update procedure should continue. | ||
*/ | ||
updatePlatform(currentVersion: string, newVersion: string, canUpdate: boolean, addPlatform?: Function, removePlatform?: (platforms: string[]) => IFuture<void>): IFuture<boolean>; | ||
// updatePlatform(currentVersion: string, newVersion: string, canUpdate: boolean, addPlatform?: Function, removePlatform?: (platforms: string[]) => IFuture<void>): IFuture<boolean>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case the method is commented, can you please delete it. Also the implementation should be removed.
} | ||
|
||
public search(filter: string[], silent: boolean): IFuture<any> { | ||
let args = (<any[]>([filter] || [])).concat(silent); | ||
return this.loadAndExecute("search", args); | ||
return this.$childProcess.exec(`npm search ${args}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As args
is array, it's toString representation will be the elements, separated by ,
. For example in case the args is array: [ "a", "b", "c" ], the execed process will be:
npm search a,b,c
In fact we would like the command to be:
npm search a b c
(note the results of the two search commands are totally different).
So I would advise you to use:
return this.$childProcess.exec(`npm search ${args.join(" ")}`);
@@ -81,66 +88,39 @@ export class NodePackageManager implements INodePackageManager { | |||
} | |||
|
|||
public uninstall(packageName: string, config?: any, path?: string): IFuture<any> { | |||
return this.loadAndExecute("uninstall", [[packageName]], { config, path }); | |||
let flags = this.getFlagsString(config, false); | |||
return this.$childProcess.exec(`npm uninstall ${packageName} ${flags}`, { cwd: path }); | |||
} | |||
|
|||
public search(filter: string[], silent: boolean): IFuture<any> { | |||
let args = (<any[]>([filter] || [])).concat(silent); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
silent is boolean, so the args passed to npm search will contain the value true or false. Maybe you should add --silent
only in case the silent
argument is true.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if silent is passed getFlagsString
function will add it as a flag, so this case is covered.
let foundValidPlugin = false; | ||
let realNpmPackageJson:any; | ||
for(let name of installedModuleNames) { | ||
let pathToRealNpmPackageJson = `${this.$projectData.projectDir}/node_modules/${name}/package.json`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you should use path.join
when constructing file path instead of directly adding /
as it may no work on all OSes
} | ||
|
||
return this.parseNpmCommandResult(result); | ||
return this.parseNpmCommandResult(npmCommandArguments); | ||
}).future<string>()(); | ||
} | ||
|
||
private parseNpmCommandResult(npmCommandResult: string): string { // [[name@version, node_modules/name]] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe the comment is no longer correct, can you please remove it.
* The solution is to traverse node_modules before and after install and get the name of the installed package, | ||
* even if it's installed through local path or URL. If command installes more than one package, all package names are returned. | ||
*/ | ||
let diff = dependenciesAfter.filter(x => dependenciesBefore.indexOf(x) === -1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've found a case where this approach will not work. Consider the case where a plugin is already installed in package.json, but user wants to update it's version (or install it from .tgz this time). In this case the difference will be empty array and the plugin will not be installed. I would suggest to compare both the name of the plugin and the version. For example this code could do the job:
let diff = _(jsonContentAfter.dependencies)
.omitBy((val: string, key: string) => jsonContentBefore && jsonContentBefore.dependencies[key] && jsonContentBefore.dependencies[key] === val)
.keys()
.value();
dd9e8dc
to
e777918
Compare
@@ -48,30 +24,54 @@ export class NodePackageManager implements INodePackageManager { | |||
} | |||
|
|||
try { | |||
return this.loadAndExecute("install", [pathToSave, packageName], { config: config }).wait(); | |||
let jsonContentBefore = this.$fs.readJson(path.join(pathToSave, "package.json")).wait(); | |||
let dependenciesBefore: Array<string> = []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you do not have to iterate over the dependencies in order to get the keys. You can use lodash here:
let dependenciesBefore = _.keys(jsonContentBefore.dependencies);
Lodash's keys method will return empty array in case there's no dependencies property in jsonContentBefore
this.$childProcess.spawnFromEvent("npm", params, "close", { cwd: pathToSave }).wait(); | ||
|
||
let jsonContentAfter = this.$fs.readJson(path.join(pathToSave, "package.json")).wait(); | ||
let dependenciesAfter: Array<string> = []; |
There was a problem hiding this comment.
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 skip the if and use _.keys(jsonContentAfter.dependencies);
} catch (err) { | ||
if (err.code === "EPEERINVALID") { | ||
if (err.message && err.message.indexOf("EPEERINVALID") !== -1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case npm install
fails with EPEERINVALID error, the method will show it as warning and will skip all of the checks regarding jsonContentBefore and jsonContentAfter. Also the method will not return anything. Is this expected?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not, I'll fix it
// this package has not been published to npmjs.org yet - perhaps manually added via --framework-path | ||
this.$logger.trace(`Checking shasum of package ${packageName}@${version}: skipped because the package was not found in npmjs.org`); | ||
return true; | ||
if(version === constants.PackageVersion.NEXT) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do you need special code for next
version. In case we decide to use separate tag (for example nextNext
) we shouldn't add it here. Npm supports installing of package directly by tag, for example npm install nativescript@next
will work as expected. Is there a particular reason to have special methods for next
and latest
tags?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've missed removing, this if, i was testing something out, and I've seem to forgot to remove it. The idea behind the next and last tags is readability only.
} | ||
let installedModuleNames = this.npmInstall(packageName, pathToSave, version, dependencyType).wait(); | ||
let installedPackageName = installedModuleNames[0]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why [0]
- is it possible to return more than one packages? In case yes - why do we get only the first one. In case not - why does the method return array?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On all the places we use node-package-manager, we pass a single package to be installed, and expect a single package to be installed. My thinking was for a later refactoring, where we could be able to use the node-package-manager everywhere or removing it completely and using npm service instead. That means it could install one or multiple packages, hence saving the array result.
} | ||
} else { | ||
this.updatePlatformCore(platformData, currentVersion, newVersion, canUpdate).wait(); | ||
this.$errors.failWithoutHelp("Native Platfrom cannot be updated."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Platfrom -> Platform
params.push(packageName); //because npm install ${pwd} on mac tries to install itself as a dependency (windows and linux have no such issues) | ||
} | ||
params = params.concat(flags); | ||
this.$childProcess.spawnFromEvent("npm", params, "close", { cwd: pathToSave }).wait(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does this code work on Windows? On Mac and Linux the executable name is npm
, but on Windows it's npm.cmd
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I'm not calling a direct path to executable, I expect npm to be found in path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no npm
executable in PATH on Windows. There's npm.cmd
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be more precise in the explanation:
- on Windows, the default command prompt is able to resolve npm to
npm.cmd
when you executenpm install
- childProcess.spawn method does not launch new shell, it just starts executable in PATH (or passed with full path)
In the current case, childProcess is trying to start a new process called npm
but it cannot be resolved as the real name of the executable on Windows is npm.cmd
.
We have such code, you can take a look at it here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand, I'll fix it, thank you.
6b73294
to
92de5c7
Compare
run CI |
10484fd
to
e384c4b
Compare
@hdeshev @rosen-vladimirov, 5 tests concerning |
fixed broken test … test was broken because of npm3 node_modules structure test logic is same
e384c4b
to
fd15624
Compare
…/remove_npm2_dependency"" This reverts commit a60eabd.
What does this PR do?
PR removes the local npm2 dependency from CLI. Fix for: #1845
Description
Cache functionality is removed from
lib/node-package-manager.ts
andlib/npm-installation-manager
. Other changes are made along the way to accommodate the lack of the cache add cache unpack functionality.How did it work till now?
tmp
folder in some cases and to cache in others, and then are extracted to app folder. If Template is not default, the default template was downloaded because it's the only one that hasApp_Resources
folder. TheApp_Resources
folder is copied to new template.platforms/
foldernode-inspector
, package is saved and extracted to cache.How will it work after this PR
app/
and toplatforms/
folders respectively. All templates will be expected to have anApp_Resources
folder with them.Expected
Because not all templates have an
App_Resource
folder yet, there are tests that are expected to fail with:Project's app/App_Resources must contain Android and iOS directories.
Related PR
These PR add
App_Resource
folder to templates that don't have it. From now on all templates will have to carry with them anApp_Resource
folder.https://github.com/NativeScript/template-hello-world-ts/pull/21
https://github.com/NativeScript/template-hello-world-ng/pull/34
ping: @NativeScript/android-runtime @rosen-vladimirov @hshristov @tzraikov @hdeshev @KristinaKoeva