Repository: ansible-collections/community.general

Issue:
Enable no-bin-links for community.general.npm
### Summary

I'm working on getting a vagrant development environment setup and am running into an issue where npm is having trouble installing on a synced folder. A solution would be to enable a flag to add the --no-bin-links setting to disable symlinking executables but there's no way to set this currently.

### Issue Type

Feature Idea

### Component Name

community.general.npm

### Additional Information

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)
- name: NPM install for Login Service
  community.general.npm:
    path: "{{ project_root }}"
    links: no
  become_user: vagrant
```


### Code of Conduct

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


Discussion:
Files identified in the description:
None

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->

Resolving patch:
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
@@ -82,6 +82,12 @@
     type: bool
     default: no
     version_added: 2.0.0
+  no_bin_links:
+    description:
+      - Use the C(--no-bin-links) flag when installing.
+    type: bool
+    default: no
+    version_added: 2.5.0
 requirements:
     - npm installed in bin path (recommended /usr/local/bin)
 '''
@@ -151,6 +157,7 @@ def __init__(self, module, **kwargs):
         self.unsafe_perm = kwargs['unsafe_perm']
         self.state = kwargs['state']
         self.no_optional = kwargs['no_optional']
+        self.no_bin_links = kwargs['no_bin_links']
 
         if kwargs['executable']:
             self.executable = kwargs['executable'].split(' ')
@@ -181,6 +188,8 @@ def _exec(self, args, run_in_check_mode=False, check_rc=True, add_package_name=T
                 cmd.append(self.registry)
             if self.no_optional:
                 cmd.append('--no-optional')
+            if self.no_bin_links:
+                cmd.append('--no-bin-links')
 
             # If path is specified, cd into that path and run the command.
             cwd = None
@@ -259,6 +268,7 @@ def main():
         unsafe_perm=dict(default=False, type='bool'),
         ci=dict(default=False, type='bool'),
         no_optional=dict(default=False, type='bool'),
+        no_bin_links=dict(default=False, type='bool'),
     )
     arg_spec['global'] = dict(default=False, type='bool')
     module = AnsibleModule(
@@ -278,6 +288,7 @@ def main():
     unsafe_perm = module.params['unsafe_perm']
     ci = module.params['ci']
     no_optional = module.params['no_optional']
+    no_bin_links = module.params['no_bin_links']
 
     if not path and not glbl:
         module.fail_json(msg='path must be specified when not using global')
@@ -286,7 +297,7 @@ def main():
 
     npm = Npm(module, name=name, path=path, version=version, glbl=glbl, production=production,
               executable=executable, registry=registry, ignore_scripts=ignore_scripts,
-              unsafe_perm=unsafe_perm, state=state, no_optional=no_optional)
+              unsafe_perm=unsafe_perm, state=state, no_optional=no_optional, no_bin_links=no_bin_links)
 
     changed = False
     if ci:


Test patch:
diff --git a/tests/integration/targets/npm/tasks/no_bin_links.yml b/tests/integration/targets/npm/tasks/no_bin_links.yml
new file mode 100644
--- /dev/null
+++ b/tests/integration/targets/npm/tasks/no_bin_links.yml
@@ -0,0 +1,64 @@
+---
+- name: 'Remove any node modules'
+  file:
+    path: '{{ remote_dir }}/node_modules'
+    state: absent
+
+- vars:
+    # sample: node-v8.2.0-linux-x64.tar.xz
+    node_path: '{{ remote_dir }}/{{ nodejs_path }}/bin'
+    package: 'ncp'
+  block:
+    - shell: npm --version
+      environment:
+        PATH: '{{ node_path }}:{{ ansible_env.PATH }}'
+      register: npm_version
+
+    - debug:
+        var: npm_version.stdout
+
+    - name: 'Install simple package with no_bin_links disabled'
+      npm:
+        path: '{{ remote_dir }}'
+        executable: '{{ node_path }}/npm'
+        state: present
+        name: '{{ package }}'
+        no_bin_links: false
+      environment:
+        PATH: '{{ node_path }}:{{ ansible_env.PATH }}'
+      register: npm_install_no_bin_links_disabled
+
+    - name: 'Make sure .bin folder has been created'
+      stat:
+        path: "{{ remote_dir }}/node_modules/.bin"
+      register: npm_dotbin_folder_disabled
+
+    - name: 'Remove any node modules'
+      file:
+        path: '{{ remote_dir }}/node_modules'
+        state: absent
+
+    - name: 'Install simple package with no_bin_links enabled'
+      npm:
+        path: '{{ remote_dir }}'
+        executable: '{{ node_path }}/npm'
+        state: present
+        name: '{{ package }}'
+        no_bin_links: true
+      environment:
+        PATH: '{{ node_path }}:{{ ansible_env.PATH }}'
+      register: npm_install_no_bin_links_enabled
+
+    - name: 'Make sure .bin folder has not been created'
+      stat:
+        path: "{{ remote_dir }}/node_modules/.bin"
+      register: npm_dotbin_folder_enabled
+
+    - assert:
+        that:
+          - npm_install_no_bin_links_disabled is success
+          - npm_install_no_bin_links_disabled is changed
+          - npm_install_no_bin_links_enabled is success
+          - npm_install_no_bin_links_enabled is changed
+          - npm_dotbin_folder_disabled.stat.exists
+          - not npm_dotbin_folder_enabled.stat.exists
diff --git a/tests/integration/targets/npm/tasks/run.yml b/tests/integration/targets/npm/tasks/run.yml
--- a/tests/integration/targets/npm/tasks/run.yml
+++ b/tests/integration/targets/npm/tasks/run.yml
@@ -1,2 +1,3 @@
 - include_tasks: setup.yml
 - include_tasks: test.yml
+- include_tasks: no_bin_links.yml