We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
npm: version specific updates are broken
### Summary

Reported by @shaharmor in https://github.com/ansible-collections/community.general/pull/1625#discussion_r579845916 about #1625:

>  This change breaks all version specific updates because this was the way npm was checking if a specific version is installed.
> 
> Before:
> `npm list --json --long pkg@1.2.3` would return `{}` if `pkg` was not installed with version `1.2.3`
> 
> After
> `npm list --json --long` would return the full `pkg` info regardless of which version is installed, and this will make ansible think that the module is installed.
 

### Issue Type

Bug Report

### Component Name

npm

### Ansible Version

```console (paste below)
devel
```


### Configuration

_No response_

### OS / Environment

_No response_

### Steps to Reproduce

...

### Expected Results

...

### Actual Results

_No response_

### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `plugins/modules/packaging/language/npm.py`
Content:
```
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3 # Copyright (c) 2017 Chris Hoffman <christopher.hoffman@gmail.com>
4 # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5 
6 from __future__ import absolute_import, division, print_function
7 __metaclass__ = type
8 
9 
10 DOCUMENTATION = r'''
11 ---
12 module: npm
13 short_description: Manage node.js packages with npm
14 description:
15   - Manage node.js packages with Node Package Manager (npm).
16 author: "Chris Hoffman (@chrishoffman)"
17 options:
18   name:
19     description:
20       - The name of a node.js library to install.
21     type: str
22     required: false
23   path:
24     description:
25       - The base path where to install the node.js libraries.
26     type: path
27     required: false
28   version:
29     description:
30       - The version to be installed.
31     type: str
32     required: false
33   global:
34     description:
35       - Install the node.js library globally.
36     required: false
37     default: no
38     type: bool
39   executable:
40     description:
41       - The executable location for npm.
42       - This is useful if you are using a version manager, such as nvm.
43     type: path
44     required: false
45   ignore_scripts:
46     description:
47       - Use the C(--ignore-scripts) flag when installing.
48     required: false
49     type: bool
50     default: no
51   unsafe_perm:
52     description:
53       - Use the C(--unsafe-perm) flag when installing.
54     type: bool
55     default: no
56   ci:
57     description:
58       - Install packages based on package-lock file, same as running C(npm ci).
59     type: bool
60     default: no
61   production:
62     description:
63       - Install dependencies in production mode, excluding devDependencies.
64     required: false
65     type: bool
66     default: no
67   registry:
68     description:
69       - The registry to install modules from.
70     required: false
71     type: str
72   state:
73     description:
74       - The state of the node.js library.
75     required: false
76     type: str
77     default: present
78     choices: [ "present", "absent", "latest" ]
79   no_optional:
80     description:
81       - Use the C(--no-optional) flag when installing.
82     type: bool
83     default: no
84     version_added: 2.0.0
85   no_bin_links:
86     description:
87       - Use the C(--no-bin-links) flag when installing.
88     type: bool
89     default: no
90     version_added: 2.5.0
91 requirements:
92     - npm installed in bin path (recommended /usr/local/bin)
93 '''
94 
95 EXAMPLES = r'''
96 - name: Install "coffee-script" node.js package.
97   community.general.npm:
98     name: coffee-script
99     path: /app/location
100 
101 - name: Install "coffee-script" node.js package on version 1.6.1.
102   community.general.npm:
103     name: coffee-script
104     version: '1.6.1'
105     path: /app/location
106 
107 - name: Install "coffee-script" node.js package globally.
108   community.general.npm:
109     name: coffee-script
110     global: yes
111 
112 - name: Remove the globally package "coffee-script".
113   community.general.npm:
114     name: coffee-script
115     global: yes
116     state: absent
117 
118 - name: Install "coffee-script" node.js package from custom registry.
119   community.general.npm:
120     name: coffee-script
121     registry: 'http://registry.mysite.com'
122 
123 - name: Install packages based on package.json.
124   community.general.npm:
125     path: /app/location
126 
127 - name: Update packages based on package.json to their latest version.
128   community.general.npm:
129     path: /app/location
130     state: latest
131 
132 - name: Install packages based on package.json using the npm installed with nvm v0.10.1.
133   community.general.npm:
134     path: /app/location
135     executable: /opt/nvm/v0.10.1/bin/npm
136     state: present
137 '''
138 
139 import json
140 import os
141 import re
142 
143 from ansible.module_utils.basic import AnsibleModule
144 from ansible.module_utils._text import to_native
145 
146 
147 class Npm(object):
148     def __init__(self, module, **kwargs):
149         self.module = module
150         self.glbl = kwargs['glbl']
151         self.name = kwargs['name']
152         self.version = kwargs['version']
153         self.path = kwargs['path']
154         self.registry = kwargs['registry']
155         self.production = kwargs['production']
156         self.ignore_scripts = kwargs['ignore_scripts']
157         self.unsafe_perm = kwargs['unsafe_perm']
158         self.state = kwargs['state']
159         self.no_optional = kwargs['no_optional']
160         self.no_bin_links = kwargs['no_bin_links']
161 
162         if kwargs['executable']:
163             self.executable = kwargs['executable'].split(' ')
164         else:
165             self.executable = [module.get_bin_path('npm', True)]
166 
167         if kwargs['version'] and self.state != 'absent':
168             self.name_version = self.name + '@' + str(self.version)
169         else:
170             self.name_version = self.name
171 
172     def _exec(self, args, run_in_check_mode=False, check_rc=True, add_package_name=True):
173         if not self.module.check_mode or (self.module.check_mode and run_in_check_mode):
174             cmd = self.executable + args
175 
176             if self.glbl:
177                 cmd.append('--global')
178             if self.production and ('install' in cmd or 'update' in cmd):
179                 cmd.append('--production')
180             if self.ignore_scripts:
181                 cmd.append('--ignore-scripts')
182             if self.unsafe_perm:
183                 cmd.append('--unsafe-perm')
184             if self.name and add_package_name:
185                 cmd.append(self.name_version)
186             if self.registry:
187                 cmd.append('--registry')
188                 cmd.append(self.registry)
189             if self.no_optional:
190                 cmd.append('--no-optional')
191             if self.no_bin_links:
192                 cmd.append('--no-bin-links')
193 
194             # If path is specified, cd into that path and run the command.
195             cwd = None
196             if self.path:
197                 if not os.path.exists(self.path):
198                     os.makedirs(self.path)
199                 if not os.path.isdir(self.path):
200                     self.module.fail_json(msg="path %s is not a directory" % self.path)
201                 cwd = self.path
202 
203             rc, out, err = self.module.run_command(cmd, check_rc=check_rc, cwd=cwd)
204             return out
205         return ''
206 
207     def list(self):
208         cmd = ['list', '--json', '--long']
209 
210         installed = list()
211         missing = list()
212         data = {}
213         try:
214             data = json.loads(self._exec(cmd, True, False, False) or '{}')
215         except (getattr(json, 'JSONDecodeError', ValueError)) as e:
216             self.module.fail_json(msg="Failed to parse NPM output with error %s" % to_native(e))
217         if 'dependencies' in data:
218             for dep in data['dependencies']:
219                 if 'missing' in data['dependencies'][dep] and data['dependencies'][dep]['missing']:
220                     missing.append(dep)
221                 elif 'invalid' in data['dependencies'][dep] and data['dependencies'][dep]['invalid']:
222                     missing.append(dep)
223                 else:
224                     installed.append(dep)
225             if self.name and self.name not in installed:
226                 missing.append(self.name)
227         # Named dependency not installed
228         else:
229             missing.append(self.name)
230 
231         return installed, missing
232 
233     def install(self):
234         return self._exec(['install'])
235 
236     def ci_install(self):
237         return self._exec(['ci'])
238 
239     def update(self):
240         return self._exec(['update'])
241 
242     def uninstall(self):
243         return self._exec(['uninstall'])
244 
245     def list_outdated(self):
246         outdated = list()
247         data = self._exec(['outdated'], True, False)
248         for dep in data.splitlines():
249             if dep:
250                 # node.js v0.10.22 changed the `npm outdated` module separator
251                 # from "@" to " ". Split on both for backwards compatibility.
252                 pkg, other = re.split(r'\s|@', dep, 1)
253                 outdated.append(pkg)
254 
255         return outdated
256 
257 
258 def main():
259     arg_spec = dict(
260         name=dict(default=None, type='str'),
261         path=dict(default=None, type='path'),
262         version=dict(default=None, type='str'),
263         production=dict(default=False, type='bool'),
264         executable=dict(default=None, type='path'),
265         registry=dict(default=None, type='str'),
266         state=dict(default='present', choices=['present', 'absent', 'latest']),
267         ignore_scripts=dict(default=False, type='bool'),
268         unsafe_perm=dict(default=False, type='bool'),
269         ci=dict(default=False, type='bool'),
270         no_optional=dict(default=False, type='bool'),
271         no_bin_links=dict(default=False, type='bool'),
272     )
273     arg_spec['global'] = dict(default=False, type='bool')
274     module = AnsibleModule(
275         argument_spec=arg_spec,
276         supports_check_mode=True
277     )
278 
279     name = module.params['name']
280     path = module.params['path']
281     version = module.params['version']
282     glbl = module.params['global']
283     production = module.params['production']
284     executable = module.params['executable']
285     registry = module.params['registry']
286     state = module.params['state']
287     ignore_scripts = module.params['ignore_scripts']
288     unsafe_perm = module.params['unsafe_perm']
289     ci = module.params['ci']
290     no_optional = module.params['no_optional']
291     no_bin_links = module.params['no_bin_links']
292 
293     if not path and not glbl:
294         module.fail_json(msg='path must be specified when not using global')
295     if state == 'absent' and not name:
296         module.fail_json(msg='uninstalling a package is only available for named packages')
297 
298     npm = Npm(module, name=name, path=path, version=version, glbl=glbl, production=production,
299               executable=executable, registry=registry, ignore_scripts=ignore_scripts,
300               unsafe_perm=unsafe_perm, state=state, no_optional=no_optional, no_bin_links=no_bin_links)
301 
302     changed = False
303     if ci:
304         npm.ci_install()
305         changed = True
306     elif state == 'present':
307         installed, missing = npm.list()
308         if missing:
309             changed = True
310             npm.install()
311     elif state == 'latest':
312         installed, missing = npm.list()
313         outdated = npm.list_outdated()
314         if missing:
315             changed = True
316             npm.install()
317         if outdated:
318             changed = True
319             npm.update()
320     else:  # absent
321         installed, missing = npm.list()
322         if name in installed:
323             changed = True
324             npm.uninstall()
325 
326     module.exit_json(changed=changed)
327 
328 
329 if __name__ == '__main__':
330     main()
331
```
--- 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/plugins/modules/packaging/language/npm.py b/plugins/modules/packaging/language/npm.py
--- a/plugins/modules/packaging/language/npm.py
+++ b/plugins/modules/packaging/language/npm.py
@@ -181,7 +181,7 @@
                 cmd.append('--ignore-scripts')
             if self.unsafe_perm:
                 cmd.append('--unsafe-perm')
-            if self.name and add_package_name:
+            if self.name_version and add_package_name:
                 cmd.append(self.name_version)
             if self.registry:
                 cmd.append('--registry')
@@ -215,14 +215,17 @@
         except (getattr(json, 'JSONDecodeError', ValueError)) as e:
             self.module.fail_json(msg="Failed to parse NPM output with error %s" % to_native(e))
         if 'dependencies' in data:
-            for dep in data['dependencies']:
-                if 'missing' in data['dependencies'][dep] and data['dependencies'][dep]['missing']:
+            for dep, props in data['dependencies'].items():
+                dep_version = dep + '@' + str(props['version'])
+
+                if 'missing' in props and props['missing']:
                     missing.append(dep)
-                elif 'invalid' in data['dependencies'][dep] and data['dependencies'][dep]['invalid']:
+                elif 'invalid' in props and props['invalid']:
                     missing.append(dep)
                 else:
                     installed.append(dep)
-            if self.name and self.name not in installed:
+                    installed.append(dep_version)
+            if self.name_version and self.name_version not in installed:
                 missing.append(self.name)
         # Named dependency not installed
         else:

{"golden_diff": "diff --git a/plugins/modules/packaging/language/npm.py b/plugins/modules/packaging/language/npm.py\n--- a/plugins/modules/packaging/language/npm.py\n+++ b/plugins/modules/packaging/language/npm.py\n@@ -181,7 +181,7 @@\n                 cmd.append('--ignore-scripts')\n             if self.unsafe_perm:\n                 cmd.append('--unsafe-perm')\n-            if self.name and add_package_name:\n+            if self.name_version and add_package_name:\n                 cmd.append(self.name_version)\n             if self.registry:\n                 cmd.append('--registry')\n@@ -215,14 +215,17 @@\n         except (getattr(json, 'JSONDecodeError', ValueError)) as e:\n             self.module.fail_json(msg=\"Failed to parse NPM output with error %s\" % to_native(e))\n         if 'dependencies' in data:\n-            for dep in data['dependencies']:\n-                if 'missing' in data['dependencies'][dep] and data['dependencies'][dep]['missing']:\n+            for dep, props in data['dependencies'].items():\n+                dep_version = dep + '@' + str(props['version'])\n+\n+                if 'missing' in props and props['missing']:\n                     missing.append(dep)\n-                elif 'invalid' in data['dependencies'][dep] and data['dependencies'][dep]['invalid']:\n+                elif 'invalid' in props and props['invalid']:\n                     missing.append(dep)\n                 else:\n                     installed.append(dep)\n-            if self.name and self.name not in installed:\n+                    installed.append(dep_version)\n+            if self.name_version and self.name_version not in installed:\n                 missing.append(self.name)\n         # Named dependency not installed\n         else:\n", "issue": "npm: version specific updates are broken\n### Summary\n\nReported by @shaharmor in https://github.com/ansible-collections/community.general/pull/1625#discussion_r579845916 about #1625:\r\n\r\n>  This change breaks all version specific updates because this was the way npm was checking if a specific version is installed.\r\n> \r\n> Before:\r\n> `npm list --json --long pkg@1.2.3` would return `{}` if `pkg` was not installed with version `1.2.3`\r\n> \r\n> After\r\n> `npm list --json --long` would return the full `pkg` info regardless of which version is installed, and this will make ansible think that the module is installed.\r\n \n\n### Issue Type\n\nBug Report\n\n### Component Name\n\nnpm\n\n### Ansible Version\n\n```console (paste below)\r\ndevel\r\n```\r\n\n\n### Configuration\n\n_No response_\n\n### OS / Environment\n\n_No response_\n\n### Steps to Reproduce\n\n...\n\n### Expected Results\n\n...\n\n### Actual Results\n\n_No response_\n\n### Code of Conduct\n\n- [X] I agree to follow the Ansible Code of Conduct\n", "before_files": [{"content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n# Copyright (c) 2017 Chris Hoffman <christopher.hoffman@gmail.com>\n# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)\n\nfrom __future__ import absolute_import, division, print_function\n__metaclass__ = type\n\n\nDOCUMENTATION = r'''\n---\nmodule: npm\nshort_description: Manage node.js packages with npm\ndescription:\n  - Manage node.js packages with Node Package Manager (npm).\nauthor: \"Chris Hoffman (@chrishoffman)\"\noptions:\n  name:\n    description:\n      - The name of a node.js library to install.\n    type: str\n    required: false\n  path:\n    description:\n      - The base path where to install the node.js libraries.\n    type: path\n    required: false\n  version:\n    description:\n      - The version to be installed.\n    type: str\n    required: false\n  global:\n    description:\n      - Install the node.js library globally.\n    required: false\n    default: no\n    type: bool\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    type: path\n    required: false\n  ignore_scripts:\n    description:\n      - Use the C(--ignore-scripts) flag when installing.\n    required: false\n    type: bool\n    default: no\n  unsafe_perm:\n    description:\n      - Use the C(--unsafe-perm) flag when installing.\n    type: bool\n    default: no\n  ci:\n    description:\n      - Install packages based on package-lock file, same as running C(npm ci).\n    type: bool\n    default: no\n  production:\n    description:\n      - Install dependencies in production mode, excluding devDependencies.\n    required: false\n    type: bool\n    default: no\n  registry:\n    description:\n      - The registry to install modules from.\n    required: false\n    type: str\n  state:\n    description:\n      - The state of the node.js library.\n    required: false\n    type: str\n    default: present\n    choices: [ \"present\", \"absent\", \"latest\" ]\n  no_optional:\n    description:\n      - Use the C(--no-optional) flag when installing.\n    type: bool\n    default: no\n    version_added: 2.0.0\n  no_bin_links:\n    description:\n      - Use the C(--no-bin-links) flag when installing.\n    type: bool\n    default: no\n    version_added: 2.5.0\nrequirements:\n    - npm installed in bin path (recommended /usr/local/bin)\n'''\n\nEXAMPLES = r'''\n- name: Install \"coffee-script\" node.js package.\n  community.general.npm:\n    name: coffee-script\n    path: /app/location\n\n- name: Install \"coffee-script\" node.js package on version 1.6.1.\n  community.general.npm:\n    name: coffee-script\n    version: '1.6.1'\n    path: /app/location\n\n- name: Install \"coffee-script\" node.js package globally.\n  community.general.npm:\n    name: coffee-script\n    global: yes\n\n- name: Remove the globally package \"coffee-script\".\n  community.general.npm:\n    name: coffee-script\n    global: yes\n    state: absent\n\n- name: Install \"coffee-script\" node.js package from custom registry.\n  community.general.npm:\n    name: coffee-script\n    registry: 'http://registry.mysite.com'\n\n- name: Install packages based on package.json.\n  community.general.npm:\n    path: /app/location\n\n- name: Update packages based on package.json to their latest version.\n  community.general.npm:\n    path: /app/location\n    state: latest\n\n- name: Install packages based on package.json using the npm installed with nvm v0.10.1.\n  community.general.npm:\n    path: /app/location\n    executable: /opt/nvm/v0.10.1/bin/npm\n    state: present\n'''\n\nimport json\nimport os\nimport re\n\nfrom ansible.module_utils.basic import AnsibleModule\nfrom ansible.module_utils._text import to_native\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        self.unsafe_perm = kwargs['unsafe_perm']\n        self.state = kwargs['state']\n        self.no_optional = kwargs['no_optional']\n        self.no_bin_links = kwargs['no_bin_links']\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'] and self.state != 'absent':\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, add_package_name=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 and ('install' in cmd or 'update' in cmd):\n                cmd.append('--production')\n            if self.ignore_scripts:\n                cmd.append('--ignore-scripts')\n            if self.unsafe_perm:\n                cmd.append('--unsafe-perm')\n            if self.name and add_package_name:\n                cmd.append(self.name_version)\n            if self.registry:\n                cmd.append('--registry')\n                cmd.append(self.registry)\n            if self.no_optional:\n                cmd.append('--no-optional')\n            if self.no_bin_links:\n                cmd.append('--no-bin-links')\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', '--long']\n\n        installed = list()\n        missing = list()\n        data = {}\n        try:\n            data = json.loads(self._exec(cmd, True, False, False) or '{}')\n        except (getattr(json, 'JSONDecodeError', ValueError)) as e:\n            self.module.fail_json(msg=\"Failed to parse NPM output with error %s\" % to_native(e))\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 ci_install(self):\n        return self._exec(['ci'])\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(r'\\s|@', dep, 1)\n                outdated.append(pkg)\n\n        return outdated\n\n\ndef main():\n    arg_spec = dict(\n        name=dict(default=None, type='str'),\n        path=dict(default=None, type='path'),\n        version=dict(default=None, type='str'),\n        production=dict(default=False, type='bool'),\n        executable=dict(default=None, type='path'),\n        registry=dict(default=None, type='str'),\n        state=dict(default='present', choices=['present', 'absent', 'latest']),\n        ignore_scripts=dict(default=False, type='bool'),\n        unsafe_perm=dict(default=False, type='bool'),\n        ci=dict(default=False, type='bool'),\n        no_optional=dict(default=False, type='bool'),\n        no_bin_links=dict(default=False, type='bool'),\n    )\n    arg_spec['global'] = dict(default=False, 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    unsafe_perm = module.params['unsafe_perm']\n    ci = module.params['ci']\n    no_optional = module.params['no_optional']\n    no_bin_links = module.params['no_bin_links']\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              unsafe_perm=unsafe_perm, state=state, no_optional=no_optional, no_bin_links=no_bin_links)\n\n    changed = False\n    if ci:\n        npm.ci_install()\n        changed = True\n    elif state == 'present':\n        installed, missing = npm.list()\n        if missing:\n            changed = True\n            npm.install()\n    elif state == 'latest':\n        installed, missing = npm.list()\n        outdated = npm.list_outdated()\n        if missing:\n            changed = True\n            npm.install()\n        if 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\nif __name__ == '__main__':\n    main()\n", "path": "plugins/modules/packaging/language/npm.py"}], "after_files": [{"content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n# Copyright (c) 2017 Chris Hoffman <christopher.hoffman@gmail.com>\n# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)\n\nfrom __future__ import absolute_import, division, print_function\n__metaclass__ = type\n\n\nDOCUMENTATION = r'''\n---\nmodule: npm\nshort_description: Manage node.js packages with npm\ndescription:\n  - Manage node.js packages with Node Package Manager (npm).\nauthor: \"Chris Hoffman (@chrishoffman)\"\noptions:\n  name:\n    description:\n      - The name of a node.js library to install.\n    type: str\n    required: false\n  path:\n    description:\n      - The base path where to install the node.js libraries.\n    type: path\n    required: false\n  version:\n    description:\n      - The version to be installed.\n    type: str\n    required: false\n  global:\n    description:\n      - Install the node.js library globally.\n    required: false\n    default: no\n    type: bool\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    type: path\n    required: false\n  ignore_scripts:\n    description:\n      - Use the C(--ignore-scripts) flag when installing.\n    required: false\n    type: bool\n    default: no\n  unsafe_perm:\n    description:\n      - Use the C(--unsafe-perm) flag when installing.\n    type: bool\n    default: no\n  ci:\n    description:\n      - Install packages based on package-lock file, same as running C(npm ci).\n    type: bool\n    default: no\n  production:\n    description:\n      - Install dependencies in production mode, excluding devDependencies.\n    required: false\n    type: bool\n    default: no\n  registry:\n    description:\n      - The registry to install modules from.\n    required: false\n    type: str\n  state:\n    description:\n      - The state of the node.js library.\n    required: false\n    type: str\n    default: present\n    choices: [ \"present\", \"absent\", \"latest\" ]\n  no_optional:\n    description:\n      - Use the C(--no-optional) flag when installing.\n    type: bool\n    default: no\n    version_added: 2.0.0\n  no_bin_links:\n    description:\n      - Use the C(--no-bin-links) flag when installing.\n    type: bool\n    default: no\n    version_added: 2.5.0\nrequirements:\n    - npm installed in bin path (recommended /usr/local/bin)\n'''\n\nEXAMPLES = r'''\n- name: Install \"coffee-script\" node.js package.\n  community.general.npm:\n    name: coffee-script\n    path: /app/location\n\n- name: Install \"coffee-script\" node.js package on version 1.6.1.\n  community.general.npm:\n    name: coffee-script\n    version: '1.6.1'\n    path: /app/location\n\n- name: Install \"coffee-script\" node.js package globally.\n  community.general.npm:\n    name: coffee-script\n    global: yes\n\n- name: Remove the globally package \"coffee-script\".\n  community.general.npm:\n    name: coffee-script\n    global: yes\n    state: absent\n\n- name: Install \"coffee-script\" node.js package from custom registry.\n  community.general.npm:\n    name: coffee-script\n    registry: 'http://registry.mysite.com'\n\n- name: Install packages based on package.json.\n  community.general.npm:\n    path: /app/location\n\n- name: Update packages based on package.json to their latest version.\n  community.general.npm:\n    path: /app/location\n    state: latest\n\n- name: Install packages based on package.json using the npm installed with nvm v0.10.1.\n  community.general.npm:\n    path: /app/location\n    executable: /opt/nvm/v0.10.1/bin/npm\n    state: present\n'''\n\nimport json\nimport os\nimport re\n\nfrom ansible.module_utils.basic import AnsibleModule\nfrom ansible.module_utils._text import to_native\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        self.unsafe_perm = kwargs['unsafe_perm']\n        self.state = kwargs['state']\n        self.no_optional = kwargs['no_optional']\n        self.no_bin_links = kwargs['no_bin_links']\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'] and self.state != 'absent':\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, add_package_name=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 and ('install' in cmd or 'update' in cmd):\n                cmd.append('--production')\n            if self.ignore_scripts:\n                cmd.append('--ignore-scripts')\n            if self.unsafe_perm:\n                cmd.append('--unsafe-perm')\n            if self.name_version and add_package_name:\n                cmd.append(self.name_version)\n            if self.registry:\n                cmd.append('--registry')\n                cmd.append(self.registry)\n            if self.no_optional:\n                cmd.append('--no-optional')\n            if self.no_bin_links:\n                cmd.append('--no-bin-links')\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', '--long']\n\n        installed = list()\n        missing = list()\n        data = {}\n        try:\n            data = json.loads(self._exec(cmd, True, False, False) or '{}')\n        except (getattr(json, 'JSONDecodeError', ValueError)) as e:\n            self.module.fail_json(msg=\"Failed to parse NPM output with error %s\" % to_native(e))\n        if 'dependencies' in data:\n            for dep, props in data['dependencies'].items():\n                dep_version = dep + '@' + str(props['version'])\n\n                if 'missing' in props and props['missing']:\n                    missing.append(dep)\n                elif 'invalid' in props and props['invalid']:\n                    missing.append(dep)\n                else:\n                    installed.append(dep)\n                    installed.append(dep_version)\n            if self.name_version and self.name_version 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 ci_install(self):\n        return self._exec(['ci'])\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(r'\\s|@', dep, 1)\n                outdated.append(pkg)\n\n        return outdated\n\n\ndef main():\n    arg_spec = dict(\n        name=dict(default=None, type='str'),\n        path=dict(default=None, type='path'),\n        version=dict(default=None, type='str'),\n        production=dict(default=False, type='bool'),\n        executable=dict(default=None, type='path'),\n        registry=dict(default=None, type='str'),\n        state=dict(default='present', choices=['present', 'absent', 'latest']),\n        ignore_scripts=dict(default=False, type='bool'),\n        unsafe_perm=dict(default=False, type='bool'),\n        ci=dict(default=False, type='bool'),\n        no_optional=dict(default=False, type='bool'),\n        no_bin_links=dict(default=False, type='bool'),\n    )\n    arg_spec['global'] = dict(default=False, 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    unsafe_perm = module.params['unsafe_perm']\n    ci = module.params['ci']\n    no_optional = module.params['no_optional']\n    no_bin_links = module.params['no_bin_links']\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              unsafe_perm=unsafe_perm, state=state, no_optional=no_optional, no_bin_links=no_bin_links)\n\n    changed = False\n    if ci:\n        npm.ci_install()\n        changed = True\n    elif state == 'present':\n        installed, missing = npm.list()\n        if missing:\n            changed = True\n            npm.install()\n    elif state == 'latest':\n        installed, missing = npm.list()\n        outdated = npm.list_outdated()\n        if missing:\n            changed = True\n            npm.install()\n        if 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\nif __name__ == '__main__':\n    main()\n", "path": "plugins/modules/packaging/language/npm.py"}]}