We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
#446 broke npm state=latest for missing packages
##### Issue Type:
- Bug Report (`npm` module)
##### Ansible Version:

Running against devel:

``` console
$ ansible --version
ansible 2.1.0 (devel be5488cb60) last updated 2015/12/15 09:36:59 (GMT -400)
  lib/ansible/modules/core: (devel 6b13da738b) last updated 2015/12/15 09:38:18 (GMT -400)
  lib/ansible/modules/extras: (devel f3251de29c) last updated 2015/12/15 09:38:42 (GMT -400)
  config file = /home/tomxtobin/.ansible.cfg
  configured module search path = Default w/o overrides
```
##### Ansible Configuration:

``` ini
[defaults]
hostfile = ~/ansible/hosts
nocows = 1
```
##### Environment:

N/A (but it's Arch Linux)
##### Summary:

It looks like PR #446 broke `npm: name=foo state=latest` for a missing package `foo` (i.e., `foo` isn't present on the system yet).

Suggested fix: for `state == 'latest'`, actually differentiate between the result of checking `len(missing)` and `len(outdated)` to see whether the package is installed or not, and run either `npm.install()` or `npm.update()` as appropriate.
##### Steps To Reproduce:

Let's use the `gulp` package as an example.

On a system that doesn't already have `gulp` installed globally:

``` console
$ ansible example-host -m command -a 'npm ls -g -depth 0 gulp' 
example-host | FAILED | rc=1 >>
/usr/lib
└── (empty)npm ERR! code 1
```

``` console
$ ansible example-host -m command -a 'gulp --version' 
example-host | FAILED | rc=2 >>
[Errno 2] No such file or directory
```

Run a task against such system to install `gulp` globally:

``` console
$ ansible example-host -m npm -a 'name=gulp state=latest global=yes'
example-host | SUCCESS => {
    "changed": true
}
```
##### Expected Results:

The module (`gulp`, above) actually gets installed on the system(s) I'm running that task against.

Against such a system, I can run something like:

``` console
$ ansible example-host -m command -a 'npm ls -g -depth 0 gulp' 
example-host | SUCCESS | rc=0 >>
/usr/lib
└── gulp@3.9.0 
```

``` console
$ ansible example-host -m command -a 'gulp --version'
example-host | SUCCESS | rc=0 >>
[15:24:28] CLI version 3.9.0
```

(Assuming the latest version of `gulp` happened to be `3.9.0`.)
##### Actual Results:

Ansible claims it succeeds in running the task, but it doesn't actually install `gulp` on the system(s) in question.

On such a system:

``` console
$ ansible example-host -m command -a 'npm ls -g -depth 0 gulp' 
example-host | FAILED | rc=1 >>
/usr/lib
└── (empty)npm ERR! code 1
```

``` console
$ ansible example-host -m command -a 'gulp --version' 
example-host | FAILED | rc=2 >>
[Errno 2] No such file or directory
```

You can actually keep re-running the task over and over, and Ansible will keep claiming to successfully install `gulp`:

``` console
$ ansible example-host -m npm -a 'name=gulp state=latest global=yes'
example-host | SUCCESS => {
    "changed": true
}
$ ansible example-host -m npm -a 'name=gulp state=latest global=yes'
example-host | SUCCESS => {
    "changed": true
}
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `packaging/language/npm.py`
Content:
```
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3 
4 # (c) 2013, Chris Hoffman <christopher.hoffman@gmail.com>
5 #
6 # This file is part of Ansible
7 #
8 # Ansible is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # Ansible is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
20 
21 DOCUMENTATION = '''
22 ---
23 module: npm
24 short_description: Manage node.js packages with npm
25 description:
26   - Manage node.js packages with Node Package Manager (npm)
27 version_added: 1.2
28 author: "Chris Hoffman (@chrishoffman)"
29 options:
30   name:
31     description:
32       - The name of a node.js library to install
33     required: false
34   path:
35     description:
36       - The base path where to install the node.js libraries
37     required: false
38   version:
39     description:
40       - The version to be installed
41     required: false
42   global:
43     description:
44       - Install the node.js library globally
45     required: false
46     default: no
47     choices: [ "yes", "no" ]
48   executable:
49     description:
50       - The executable location for npm.
51       - This is useful if you are using a version manager, such as nvm
52     required: false
53   ignore_scripts:
54     description:
55       - Use the --ignore-scripts flag when installing.
56     required: false
57     choices: [ "yes", "no" ]
58     default: no
59     version_added: "1.8"
60   production:
61     description:
62       - Install dependencies in production mode, excluding devDependencies
63     required: false
64     choices: [ "yes", "no" ]
65     default: no
66   registry:
67     description:
68       - The registry to install modules from.
69     required: false
70     version_added: "1.6"
71   state:
72     description:
73       - The state of the node.js library
74     required: false
75     default: present
76     choices: [ "present", "absent", "latest" ]
77 '''
78 
79 EXAMPLES = '''
80 description: Install "coffee-script" node.js package.
81 - npm: name=coffee-script path=/app/location
82 
83 description: Install "coffee-script" node.js package on version 1.6.1.
84 - npm: name=coffee-script version=1.6.1 path=/app/location
85 
86 description: Install "coffee-script" node.js package globally.
87 - npm: name=coffee-script global=yes
88 
89 description: Remove the globally package "coffee-script".
90 - npm: name=coffee-script global=yes state=absent
91 
92 description: Install "coffee-script" node.js package from custom registry.
93 - npm: name=coffee-script registry=http://registry.mysite.com
94 
95 description: Install packages based on package.json.
96 - npm: path=/app/location
97 
98 description: Update packages based on package.json to their latest version.
99 - npm: path=/app/location state=latest
100 
101 description: Install packages based on package.json using the npm installed with nvm v0.10.1.
102 - npm: path=/app/location executable=/opt/nvm/v0.10.1/bin/npm state=present
103 '''
104 
105 import os
106 
107 try:
108     import json
109 except ImportError:
110     try:
111         import simplejson as json
112     except ImportError:
113         # Let snippet from module_utils/basic.py return a proper error in this case
114         pass
115 
116 
117 class Npm(object):
118     def __init__(self, module, **kwargs):
119         self.module = module
120         self.glbl = kwargs['glbl']
121         self.name = kwargs['name']
122         self.version = kwargs['version']
123         self.path = kwargs['path']
124         self.registry = kwargs['registry']
125         self.production = kwargs['production']
126         self.ignore_scripts = kwargs['ignore_scripts']
127 
128         if kwargs['executable']:
129             self.executable = kwargs['executable'].split(' ')
130         else:
131             self.executable = [module.get_bin_path('npm', True)]
132 
133         if kwargs['version']:
134             self.name_version = self.name + '@' + str(self.version)
135         else:
136             self.name_version = self.name
137 
138     def _exec(self, args, run_in_check_mode=False, check_rc=True):
139         if not self.module.check_mode or (self.module.check_mode and run_in_check_mode):
140             cmd = self.executable + args
141 
142             if self.glbl:
143                 cmd.append('--global')
144             if self.production:
145                 cmd.append('--production')
146             if self.ignore_scripts:
147                 cmd.append('--ignore-scripts')
148             if self.name:
149                 cmd.append(self.name_version)
150             if self.registry:
151                 cmd.append('--registry')
152                 cmd.append(self.registry)
153 
154             #If path is specified, cd into that path and run the command.
155             cwd = None
156             if self.path:
157                 if not os.path.exists(self.path):
158                     os.makedirs(self.path)
159                 if not os.path.isdir(self.path):
160                     self.module.fail_json(msg="path %s is not a directory" % self.path)
161                 cwd = self.path
162 
163             rc, out, err = self.module.run_command(cmd, check_rc=check_rc, cwd=cwd)
164             return out
165         return ''
166 
167     def list(self):
168         cmd = ['list', '--json']
169 
170         installed = list()
171         missing = list()
172         data = json.loads(self._exec(cmd, True, False))
173         if 'dependencies' in data:
174             for dep in data['dependencies']:
175                 if 'missing' in data['dependencies'][dep] and data['dependencies'][dep]['missing']:
176                     missing.append(dep)
177                 elif 'invalid' in data['dependencies'][dep] and data['dependencies'][dep]['invalid']:
178                     missing.append(dep)
179                 else:
180                     installed.append(dep)
181             if self.name and self.name not in installed:
182                 missing.append(self.name)
183         #Named dependency not installed
184         else:
185             missing.append(self.name)
186 
187         return installed, missing
188 
189     def install(self):
190         return self._exec(['install'])
191 
192     def update(self):
193         return self._exec(['update'])
194 
195     def uninstall(self):
196         return self._exec(['uninstall'])
197 
198     def list_outdated(self):
199         outdated = list()
200         data = self._exec(['outdated'], True, False)
201         for dep in data.splitlines():
202             if dep:
203                 # node.js v0.10.22 changed the `npm outdated` module separator
204                 # from "@" to " ". Split on both for backwards compatibility.
205                 pkg, other = re.split('\s|@', dep, 1)
206                 outdated.append(pkg)
207 
208         return outdated
209 
210 
211 def main():
212     arg_spec = dict(
213         name=dict(default=None),
214         path=dict(default=None, type='path'),
215         version=dict(default=None),
216         production=dict(default='no', type='bool'),
217         executable=dict(default=None, type='path'),
218         registry=dict(default=None),
219         state=dict(default='present', choices=['present', 'absent', 'latest']),
220         ignore_scripts=dict(default=False, type='bool'),
221     )
222     arg_spec['global'] = dict(default='no', type='bool')
223     module = AnsibleModule(
224         argument_spec=arg_spec,
225         supports_check_mode=True
226     )
227 
228     name = module.params['name']
229     path = module.params['path']
230     version = module.params['version']
231     glbl = module.params['global']
232     production = module.params['production']
233     executable = module.params['executable']
234     registry = module.params['registry']
235     state = module.params['state']
236     ignore_scripts = module.params['ignore_scripts']
237 
238     if not path and not glbl:
239         module.fail_json(msg='path must be specified when not using global')
240     if state == 'absent' and not name:
241         module.fail_json(msg='uninstalling a package is only available for named packages')
242 
243     npm = Npm(module, name=name, path=path, version=version, glbl=glbl, production=production, \
244               executable=executable, registry=registry, ignore_scripts=ignore_scripts)
245 
246     changed = False
247     if state == 'present':
248         installed, missing = npm.list()
249         if len(missing):
250             changed = True
251             npm.install()
252     elif state == 'latest':
253         installed, missing = npm.list()
254         outdated = npm.list_outdated()
255         if len(missing) or len(outdated):
256             changed = True
257             npm.update()
258     else: #absent
259         installed, missing = npm.list()
260         if name in installed:
261             changed = True
262             npm.uninstall()
263 
264     module.exit_json(changed=changed)
265 
266 # import module snippets
267 from ansible.module_utils.basic import *
268 main()
269
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/packaging/language/npm.py b/packaging/language/npm.py
--- a/packaging/language/npm.py
+++ b/packaging/language/npm.py
@@ -252,7 +252,10 @@
     elif state == 'latest':
         installed, missing = npm.list()
         outdated = npm.list_outdated()
-        if len(missing) or len(outdated):
+        if len(missing):
+            changed = True
+            npm.install()
+        if len(outdated):
             changed = True
             npm.update()
     else: #absent

{"golden_diff": "diff --git a/packaging/language/npm.py b/packaging/language/npm.py\n--- a/packaging/language/npm.py\n+++ b/packaging/language/npm.py\n@@ -252,7 +252,10 @@\n     elif state == 'latest':\n         installed, missing = npm.list()\n         outdated = npm.list_outdated()\n-        if len(missing) or len(outdated):\n+        if len(missing):\n+            changed = True\n+            npm.install()\n+        if len(outdated):\n             changed = True\n             npm.update()\n     else: #absent\n", "issue": "#446 broke npm state=latest for missing packages\n##### Issue Type:\n- Bug Report (`npm` module)\n##### Ansible Version:\n\nRunning against devel:\n\n``` console\n$ ansible --version\nansible 2.1.0 (devel be5488cb60) last updated 2015/12/15 09:36:59 (GMT -400)\n  lib/ansible/modules/core: (devel 6b13da738b) last updated 2015/12/15 09:38:18 (GMT -400)\n  lib/ansible/modules/extras: (devel f3251de29c) last updated 2015/12/15 09:38:42 (GMT -400)\n  config file = /home/tomxtobin/.ansible.cfg\n  configured module search path = Default w/o overrides\n```\n##### Ansible Configuration:\n\n``` ini\n[defaults]\nhostfile = ~/ansible/hosts\nnocows = 1\n```\n##### Environment:\n\nN/A (but it's Arch Linux)\n##### Summary:\n\nIt looks like PR #446 broke `npm: name=foo state=latest` for a missing package `foo` (i.e., `foo` isn't present on the system yet).\n\nSuggested fix: for `state == 'latest'`, actually differentiate between the result of checking `len(missing)` and `len(outdated)` to see whether the package is installed or not, and run either `npm.install()` or `npm.update()` as appropriate.\n##### Steps To Reproduce:\n\nLet's use the `gulp` package as an example.\n\nOn a system that doesn't already have `gulp` installed globally:\n\n``` console\n$ ansible example-host -m command -a 'npm ls -g -depth 0 gulp' \nexample-host | FAILED | rc=1 >>\n/usr/lib\n\u2514\u2500\u2500 (empty)npm ERR! code 1\n```\n\n``` console\n$ ansible example-host -m command -a 'gulp --version' \nexample-host | FAILED | rc=2 >>\n[Errno 2] No such file or directory\n```\n\nRun a task against such system to install `gulp` globally:\n\n``` console\n$ ansible example-host -m npm -a 'name=gulp state=latest global=yes'\nexample-host | SUCCESS => {\n    \"changed\": true\n}\n```\n##### Expected Results:\n\nThe module (`gulp`, above) actually gets installed on the system(s) I'm running that task against.\n\nAgainst such a system, I can run something like:\n\n``` console\n$ ansible example-host -m command -a 'npm ls -g -depth 0 gulp' \nexample-host | SUCCESS | rc=0 >>\n/usr/lib\n\u2514\u2500\u2500 gulp@3.9.0 \n```\n\n``` console\n$ ansible example-host -m command -a 'gulp --version'\nexample-host | SUCCESS | rc=0 >>\n[15:24:28] CLI version 3.9.0\n```\n\n(Assuming the latest version of `gulp` happened to be `3.9.0`.)\n##### Actual Results:\n\nAnsible claims it succeeds in running the task, but it doesn't actually install `gulp` on the system(s) in question.\n\nOn such a system:\n\n``` console\n$ ansible example-host -m command -a 'npm ls -g -depth 0 gulp' \nexample-host | FAILED | rc=1 >>\n/usr/lib\n\u2514\u2500\u2500 (empty)npm ERR! code 1\n```\n\n``` console\n$ ansible example-host -m command -a 'gulp --version' \nexample-host | FAILED | rc=2 >>\n[Errno 2] No such file or directory\n```\n\nYou can actually keep re-running the task over and over, and Ansible will keep claiming to successfully install `gulp`:\n\n``` console\n$ ansible example-host -m npm -a 'name=gulp state=latest global=yes'\nexample-host | SUCCESS => {\n    \"changed\": true\n}\n$ ansible example-host -m npm -a 'name=gulp state=latest global=yes'\nexample-host | SUCCESS => {\n    \"changed\": true\n}\n```\n\n", "before_files": [{"content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\n# (c) 2013, Chris Hoffman <christopher.hoffman@gmail.com>\n#\n# This file is part of Ansible\n#\n# Ansible is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# Ansible is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.\n\nDOCUMENTATION = '''\n---\nmodule: npm\nshort_description: Manage node.js packages with npm\ndescription:\n  - Manage node.js packages with Node Package Manager (npm)\nversion_added: 1.2\nauthor: \"Chris Hoffman (@chrishoffman)\"\noptions:\n  name:\n    description:\n      - The name of a node.js library to install\n    required: false\n  path:\n    description:\n      - The base path where to install the node.js libraries\n    required: false\n  version:\n    description:\n      - The version to be installed\n    required: false\n  global:\n    description:\n      - Install the node.js library globally\n    required: false\n    default: no\n    choices: [ \"yes\", \"no\" ]\n  executable:\n    description:\n      - The executable location for npm.\n      - This is useful if you are using a version manager, such as nvm\n    required: false\n  ignore_scripts:\n    description:\n      - Use the --ignore-scripts flag when installing.\n    required: false\n    choices: [ \"yes\", \"no\" ]\n    default: no\n    version_added: \"1.8\"\n  production:\n    description:\n      - Install dependencies in production mode, excluding devDependencies\n    required: false\n    choices: [ \"yes\", \"no\" ]\n    default: no\n  registry:\n    description:\n      - The registry to install modules from.\n    required: false\n    version_added: \"1.6\"\n  state:\n    description:\n      - The state of the node.js library\n    required: false\n    default: present\n    choices: [ \"present\", \"absent\", \"latest\" ]\n'''\n\nEXAMPLES = '''\ndescription: Install \"coffee-script\" node.js package.\n- npm: name=coffee-script path=/app/location\n\ndescription: Install \"coffee-script\" node.js package on version 1.6.1.\n- npm: name=coffee-script version=1.6.1 path=/app/location\n\ndescription: Install \"coffee-script\" node.js package globally.\n- npm: name=coffee-script global=yes\n\ndescription: Remove the globally package \"coffee-script\".\n- npm: name=coffee-script global=yes state=absent\n\ndescription: Install \"coffee-script\" node.js package from custom registry.\n- npm: name=coffee-script registry=http://registry.mysite.com\n\ndescription: Install packages based on package.json.\n- npm: path=/app/location\n\ndescription: Update packages based on package.json to their latest version.\n- npm: path=/app/location state=latest\n\ndescription: Install packages based on package.json using the npm installed with nvm v0.10.1.\n- npm: path=/app/location executable=/opt/nvm/v0.10.1/bin/npm state=present\n'''\n\nimport os\n\ntry:\n    import json\nexcept ImportError:\n    try:\n        import simplejson as json\n    except ImportError:\n        # Let snippet from module_utils/basic.py return a proper error in this case\n        pass\n\n\nclass Npm(object):\n    def __init__(self, module, **kwargs):\n        self.module = module\n        self.glbl = kwargs['glbl']\n        self.name = kwargs['name']\n        self.version = kwargs['version']\n        self.path = kwargs['path']\n        self.registry = kwargs['registry']\n        self.production = kwargs['production']\n        self.ignore_scripts = kwargs['ignore_scripts']\n\n        if kwargs['executable']:\n            self.executable = kwargs['executable'].split(' ')\n        else:\n            self.executable = [module.get_bin_path('npm', True)]\n\n        if kwargs['version']:\n            self.name_version = self.name + '@' + str(self.version)\n        else:\n            self.name_version = self.name\n\n    def _exec(self, args, run_in_check_mode=False, check_rc=True):\n        if not self.module.check_mode or (self.module.check_mode and run_in_check_mode):\n            cmd = self.executable + args\n\n            if self.glbl:\n                cmd.append('--global')\n            if self.production:\n                cmd.append('--production')\n            if self.ignore_scripts:\n                cmd.append('--ignore-scripts')\n            if self.name:\n                cmd.append(self.name_version)\n            if self.registry:\n                cmd.append('--registry')\n                cmd.append(self.registry)\n\n            #If path is specified, cd into that path and run the command.\n            cwd = None\n            if self.path:\n                if not os.path.exists(self.path):\n                    os.makedirs(self.path)\n                if not os.path.isdir(self.path):\n                    self.module.fail_json(msg=\"path %s is not a directory\" % self.path)\n                cwd = self.path\n\n            rc, out, err = self.module.run_command(cmd, check_rc=check_rc, cwd=cwd)\n            return out\n        return ''\n\n    def list(self):\n        cmd = ['list', '--json']\n\n        installed = list()\n        missing = list()\n        data = json.loads(self._exec(cmd, True, False))\n        if 'dependencies' in data:\n            for dep in data['dependencies']:\n                if 'missing' in data['dependencies'][dep] and data['dependencies'][dep]['missing']:\n                    missing.append(dep)\n                elif 'invalid' in data['dependencies'][dep] and data['dependencies'][dep]['invalid']:\n                    missing.append(dep)\n                else:\n                    installed.append(dep)\n            if self.name and self.name not in installed:\n                missing.append(self.name)\n        #Named dependency not installed\n        else:\n            missing.append(self.name)\n\n        return installed, missing\n\n    def install(self):\n        return self._exec(['install'])\n\n    def update(self):\n        return self._exec(['update'])\n\n    def uninstall(self):\n        return self._exec(['uninstall'])\n\n    def list_outdated(self):\n        outdated = list()\n        data = self._exec(['outdated'], True, False)\n        for dep in data.splitlines():\n            if dep:\n                # node.js v0.10.22 changed the `npm outdated` module separator\n                # from \"@\" to \" \". Split on both for backwards compatibility.\n                pkg, other = re.split('\\s|@', dep, 1)\n                outdated.append(pkg)\n\n        return outdated\n\n\ndef main():\n    arg_spec = dict(\n        name=dict(default=None),\n        path=dict(default=None, type='path'),\n        version=dict(default=None),\n        production=dict(default='no', type='bool'),\n        executable=dict(default=None, type='path'),\n        registry=dict(default=None),\n        state=dict(default='present', choices=['present', 'absent', 'latest']),\n        ignore_scripts=dict(default=False, type='bool'),\n    )\n    arg_spec['global'] = dict(default='no', type='bool')\n    module = AnsibleModule(\n        argument_spec=arg_spec,\n        supports_check_mode=True\n    )\n\n    name = module.params['name']\n    path = module.params['path']\n    version = module.params['version']\n    glbl = module.params['global']\n    production = module.params['production']\n    executable = module.params['executable']\n    registry = module.params['registry']\n    state = module.params['state']\n    ignore_scripts = module.params['ignore_scripts']\n\n    if not path and not glbl:\n        module.fail_json(msg='path must be specified when not using global')\n    if state == 'absent' and not name:\n        module.fail_json(msg='uninstalling a package is only available for named packages')\n\n    npm = Npm(module, name=name, path=path, version=version, glbl=glbl, production=production, \\\n              executable=executable, registry=registry, ignore_scripts=ignore_scripts)\n\n    changed = False\n    if state == 'present':\n        installed, missing = npm.list()\n        if len(missing):\n            changed = True\n            npm.install()\n    elif state == 'latest':\n        installed, missing = npm.list()\n        outdated = npm.list_outdated()\n        if len(missing) or len(outdated):\n            changed = True\n            npm.update()\n    else: #absent\n        installed, missing = npm.list()\n        if name in installed:\n            changed = True\n            npm.uninstall()\n\n    module.exit_json(changed=changed)\n\n# import module snippets\nfrom ansible.module_utils.basic import *\nmain()\n", "path": "packaging/language/npm.py"}], "after_files": [{"content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\n# (c) 2013, Chris Hoffman <christopher.hoffman@gmail.com>\n#\n# This file is part of Ansible\n#\n# Ansible is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# Ansible is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.\n\nDOCUMENTATION = '''\n---\nmodule: npm\nshort_description: Manage node.js packages with npm\ndescription:\n  - Manage node.js packages with Node Package Manager (npm)\nversion_added: 1.2\nauthor: \"Chris Hoffman (@chrishoffman)\"\noptions:\n  name:\n    description:\n      - The name of a node.js library to install\n    required: false\n  path:\n    description:\n      - The base path where to install the node.js libraries\n    required: false\n  version:\n    description:\n      - The version to be installed\n    required: false\n  global:\n    description:\n      - Install the node.js library globally\n    required: false\n    default: no\n    choices: [ \"yes\", \"no\" ]\n  executable:\n    description:\n      - The executable location for npm.\n      - This is useful if you are using a version manager, such as nvm\n    required: false\n  ignore_scripts:\n    description:\n      - Use the --ignore-scripts flag when installing.\n    required: false\n    choices: [ \"yes\", \"no\" ]\n    default: no\n    version_added: \"1.8\"\n  production:\n    description:\n      - Install dependencies in production mode, excluding devDependencies\n    required: false\n    choices: [ \"yes\", \"no\" ]\n    default: no\n  registry:\n    description:\n      - The registry to install modules from.\n    required: false\n    version_added: \"1.6\"\n  state:\n    description:\n      - The state of the node.js library\n    required: false\n    default: present\n    choices: [ \"present\", \"absent\", \"latest\" ]\n'''\n\nEXAMPLES = '''\ndescription: Install \"coffee-script\" node.js package.\n- npm: name=coffee-script path=/app/location\n\ndescription: Install \"coffee-script\" node.js package on version 1.6.1.\n- npm: name=coffee-script version=1.6.1 path=/app/location\n\ndescription: Install \"coffee-script\" node.js package globally.\n- npm: name=coffee-script global=yes\n\ndescription: Remove the globally package \"coffee-script\".\n- npm: name=coffee-script global=yes state=absent\n\ndescription: Install \"coffee-script\" node.js package from custom registry.\n- npm: name=coffee-script registry=http://registry.mysite.com\n\ndescription: Install packages based on package.json.\n- npm: path=/app/location\n\ndescription: Update packages based on package.json to their latest version.\n- npm: path=/app/location state=latest\n\ndescription: Install packages based on package.json using the npm installed with nvm v0.10.1.\n- npm: path=/app/location executable=/opt/nvm/v0.10.1/bin/npm state=present\n'''\n\nimport os\n\ntry:\n    import json\nexcept ImportError:\n    try:\n        import simplejson as json\n    except ImportError:\n        # Let snippet from module_utils/basic.py return a proper error in this case\n        pass\n\n\nclass Npm(object):\n    def __init__(self, module, **kwargs):\n        self.module = module\n        self.glbl = kwargs['glbl']\n        self.name = kwargs['name']\n        self.version = kwargs['version']\n        self.path = kwargs['path']\n        self.registry = kwargs['registry']\n        self.production = kwargs['production']\n        self.ignore_scripts = kwargs['ignore_scripts']\n\n        if kwargs['executable']:\n            self.executable = kwargs['executable'].split(' ')\n        else:\n            self.executable = [module.get_bin_path('npm', True)]\n\n        if kwargs['version']:\n            self.name_version = self.name + '@' + str(self.version)\n        else:\n            self.name_version = self.name\n\n    def _exec(self, args, run_in_check_mode=False, check_rc=True):\n        if not self.module.check_mode or (self.module.check_mode and run_in_check_mode):\n            cmd = self.executable + args\n\n            if self.glbl:\n                cmd.append('--global')\n            if self.production:\n                cmd.append('--production')\n            if self.ignore_scripts:\n                cmd.append('--ignore-scripts')\n            if self.name:\n                cmd.append(self.name_version)\n            if self.registry:\n                cmd.append('--registry')\n                cmd.append(self.registry)\n\n            #If path is specified, cd into that path and run the command.\n            cwd = None\n            if self.path:\n                if not os.path.exists(self.path):\n                    os.makedirs(self.path)\n                if not os.path.isdir(self.path):\n                    self.module.fail_json(msg=\"path %s is not a directory\" % self.path)\n                cwd = self.path\n\n            rc, out, err = self.module.run_command(cmd, check_rc=check_rc, cwd=cwd)\n            return out\n        return ''\n\n    def list(self):\n        cmd = ['list', '--json']\n\n        installed = list()\n        missing = list()\n        data = json.loads(self._exec(cmd, True, False))\n        if 'dependencies' in data:\n            for dep in data['dependencies']:\n                if 'missing' in data['dependencies'][dep] and data['dependencies'][dep]['missing']:\n                    missing.append(dep)\n                elif 'invalid' in data['dependencies'][dep] and data['dependencies'][dep]['invalid']:\n                    missing.append(dep)\n                else:\n                    installed.append(dep)\n            if self.name and self.name not in installed:\n                missing.append(self.name)\n        #Named dependency not installed\n        else:\n            missing.append(self.name)\n\n        return installed, missing\n\n    def install(self):\n        return self._exec(['install'])\n\n    def update(self):\n        return self._exec(['update'])\n\n    def uninstall(self):\n        return self._exec(['uninstall'])\n\n    def list_outdated(self):\n        outdated = list()\n        data = self._exec(['outdated'], True, False)\n        for dep in data.splitlines():\n            if dep:\n                # node.js v0.10.22 changed the `npm outdated` module separator\n                # from \"@\" to \" \". Split on both for backwards compatibility.\n                pkg, other = re.split('\\s|@', dep, 1)\n                outdated.append(pkg)\n\n        return outdated\n\n\ndef main():\n    arg_spec = dict(\n        name=dict(default=None),\n        path=dict(default=None, type='path'),\n        version=dict(default=None),\n        production=dict(default='no', type='bool'),\n        executable=dict(default=None, type='path'),\n        registry=dict(default=None),\n        state=dict(default='present', choices=['present', 'absent', 'latest']),\n        ignore_scripts=dict(default=False, type='bool'),\n    )\n    arg_spec['global'] = dict(default='no', type='bool')\n    module = AnsibleModule(\n        argument_spec=arg_spec,\n        supports_check_mode=True\n    )\n\n    name = module.params['name']\n    path = module.params['path']\n    version = module.params['version']\n    glbl = module.params['global']\n    production = module.params['production']\n    executable = module.params['executable']\n    registry = module.params['registry']\n    state = module.params['state']\n    ignore_scripts = module.params['ignore_scripts']\n\n    if not path and not glbl:\n        module.fail_json(msg='path must be specified when not using global')\n    if state == 'absent' and not name:\n        module.fail_json(msg='uninstalling a package is only available for named packages')\n\n    npm = Npm(module, name=name, path=path, version=version, glbl=glbl, production=production, \\\n              executable=executable, registry=registry, ignore_scripts=ignore_scripts)\n\n    changed = False\n    if state == 'present':\n        installed, missing = npm.list()\n        if len(missing):\n            changed = True\n            npm.install()\n    elif state == 'latest':\n        installed, missing = npm.list()\n        outdated = npm.list_outdated()\n        if len(missing):\n            changed = True\n            npm.install()\n        if len(outdated):\n            changed = True\n            npm.update()\n    else: #absent\n        installed, missing = npm.list()\n        if name in installed:\n            changed = True\n            npm.uninstall()\n\n    module.exit_json(changed=changed)\n\n# import module snippets\nfrom ansible.module_utils.basic import *\nmain()\n", "path": "packaging/language/npm.py"}]}