diff --git a/changelogs/fragments/11943-yarn-nodejs-runtime-warnings.yml b/changelogs/fragments/11943-yarn-nodejs-runtime-warnings.yml new file mode 100644 index 0000000000..06f633dd1d --- /dev/null +++ b/changelogs/fragments/11943-yarn-nodejs-runtime-warnings.yml @@ -0,0 +1,2 @@ +bugfixes: + - yarn - skip Node.js runtime warning lines (starting with ``(node:``) in stderr before JSON parsing, fixing failures with Node.js 24 which emits ``DeprecationWarning`` to stderr. The warnings are passed on to the user (https://github.com/ansible-collections/community.general/pull/11943). diff --git a/plugins/modules/yarn.py b/plugins/modules/yarn.py index 437104f4b1..406a6c8487 100644 --- a/plugins/modules/yarn.py +++ b/plugins/modules/yarn.py @@ -187,8 +187,13 @@ class Yarn: def _process_yarn_error(self, err): try: - # We need to filter for errors, since Yarn warnings are included in stderr + # We need to filter for errors, since Yarn warnings are included in stderr. + # Non-JSON lines (e.g. Node.js runtime warnings) are surfaced via module.warn() + # rather than treated as errors, since their meaning is unknown. for line in err.splitlines(): + if not line.startswith("{"): + self.module.warn(f"yarn stderr: {line}") + continue if json.loads(line)["type"] == "error": self.module.fail_json(msg=err) except Exception: diff --git a/tests/integration/targets/yarn/tasks/main.yml b/tests/integration/targets/yarn/tasks/main.yml index df6cc50411..8180d375eb 100644 --- a/tests/integration/targets/yarn/tasks/main.yml +++ b/tests/integration/targets/yarn/tasks/main.yml @@ -11,6 +11,9 @@ # ============================================================ +- include_tasks: run_alpine.yml + when: ansible_facts.os_family == 'Alpine' + - include_tasks: run.yml vars: nodejs_version: '{{ item.node_version }}' @@ -22,4 +25,4 @@ with_items: - {node_version: 16.20.2, yarn_version: 1.22.22} # oldest node version with macOS arm64 support when: - - not (ansible_facts.os_family == 'Alpine') # TODO + - not (ansible_facts.os_family == 'Alpine') diff --git a/tests/integration/targets/yarn/tasks/run.yml b/tests/integration/targets/yarn/tasks/run.yml index 21857b6a3e..ef90715654 100644 --- a/tests/integration/targets/yarn/tasks/run.yml +++ b/tests/integration/targets/yarn/tasks/run.yml @@ -35,203 +35,8 @@ path: '{{remote_tmp_dir}}/node_modules' state: absent -# Set vars for our test harness -- vars: - # node_bin_path: "/usr/local/lib/nodejs/node-v{{nodejs_version}}/bin" +- include_tasks: tests.yml + vars: node_bin_path: "/usr/local/lib/nodejs/{{ nodejs_path }}/bin" yarn_bin_path: "{{ remote_tmp_dir }}/yarn-v{{ yarn_version }}/bin" package: 'iconv-lite' - environment: - PATH: "{{ node_bin_path }}:{{ansible_facts.env.PATH}}" - YARN_IGNORE_ENGINES: true - block: - - # Get the version of Yarn and register to a variable - - shell: '{{ yarn_bin_path }}/yarn --version' - environment: - PATH: '{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' - register: yarn_version - - - name: 'Create dummy package.json' - template: - src: package.j2 - dest: '{{ remote_tmp_dir }}/package.json' - - - name: 'Install all packages.' - yarn: - path: '{{ remote_tmp_dir }}' - executable: '{{ yarn_bin_path }}/yarn' - state: present - environment: - PATH: '{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' - - - name: 'Install the same package from package.json again.' - yarn: - path: '{{ remote_tmp_dir }}' - executable: '{{ yarn_bin_path }}/yarn' - name: '{{ package }}' - state: present - environment: - PATH: '{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' - register: yarn_install - - - assert: - that: - - not (yarn_install is changed) - - - name: 'Install all packages in check mode.' - yarn: - path: '{{ remote_tmp_dir }}' - executable: '{{ yarn_bin_path }}/yarn' - state: present - environment: - PATH: '{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' - check_mode: true - register: yarn_install_check - - - name: verify test yarn global installation in check mode - assert: - that: - - yarn_install_check.err is defined - - yarn_install_check.out is defined - - yarn_install_check.err is none - - yarn_install_check.out is none - - - name: 'Install package with explicit version (older version of package)' - yarn: - path: '{{ remote_tmp_dir }}' - executable: '{{ yarn_bin_path }}/yarn' - name: left-pad - version: 1.1.0 - state: present - environment: - PATH: '{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' - register: yarn_install_old_package - - - assert: - that: - - yarn_install_old_package is changed - - - name: 'Again but without explicit executable path' - yarn: - path: '{{ remote_tmp_dir }}' - name: left-pad - version: 1.1.0 - state: present - environment: - PATH: '{{ yarn_bin_path }}:{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' - - - name: 'Upgrade old package' - yarn: - path: '{{ remote_tmp_dir }}' - executable: '{{ yarn_bin_path }}/yarn' - name: left-pad - state: latest - environment: - PATH: '{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' - register: yarn_update_old_package - - - assert: - that: - - yarn_update_old_package is changed - - - name: 'Remove a package' - yarn: - path: '{{ remote_tmp_dir }}' - executable: '{{ yarn_bin_path }}/yarn' - name: '{{ package }}' - state: absent - environment: - PATH: '{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' - register: yarn_uninstall_package - - - name: 'Assert package removed' - assert: - that: - - yarn_uninstall_package is changed - - - name: 'Global install binary with explicit version (older version of package)' - yarn: - global: true - executable: '{{ yarn_bin_path }}/yarn' - name: prettier - version: 2.0.0 - state: present - environment: - PATH: '{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' - register: yarn_global_install_old_binary - - - assert: - that: - - yarn_global_install_old_binary is changed - - - name: 'Global upgrade old binary' - yarn: - global: true - executable: '{{ yarn_bin_path }}/yarn' - name: prettier - state: latest - environment: - PATH: '{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' - register: yarn_global_update_old_binary - - - assert: - that: - - yarn_global_update_old_binary is changed - - - name: 'Global remove a binary' - yarn: - global: true - executable: '{{ yarn_bin_path }}/yarn' - name: prettier - state: absent - environment: - PATH: '{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' - register: yarn_global_uninstall_binary - - - assert: - that: - - yarn_global_uninstall_binary is changed - - - name: 'Global install package with no binary with explicit version (older version of package)' - yarn: - global: true - executable: '{{ yarn_bin_path }}/yarn' - name: left-pad - version: 1.1.0 - state: present - environment: - PATH: '{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' - register: yarn_global_install_old_package - - - assert: - that: - - yarn_global_install_old_package is changed - - - name: 'Global upgrade old package with no binary' - yarn: - global: true - executable: '{{ yarn_bin_path }}/yarn' - name: left-pad - state: latest - environment: - PATH: '{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' - register: yarn_global_update_old_package - - - assert: - that: - - yarn_global_update_old_package is changed - - - name: 'Global remove a package with no binary' - yarn: - global: true - executable: '{{ yarn_bin_path }}/yarn' - name: left-pad - state: absent - environment: - PATH: '{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' - register: yarn_global_uninstall_package - - - assert: - that: - - yarn_global_uninstall_package is changed diff --git a/tests/integration/targets/yarn/tasks/run_alpine.yml b/tests/integration/targets/yarn/tasks/run_alpine.yml new file mode 100644 index 0000000000..e76a72c951 --- /dev/null +++ b/tests/integration/targets/yarn/tasks/run_alpine.yml @@ -0,0 +1,25 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install nodejs and yarn via apk + community.general.apk: + name: + - sqlite-libs + - nodejs + - yarn + state: latest + update_cache: true + +# Clean up before running tests +- name: Remove any previous Nodejs modules + file: + path: '{{ remote_tmp_dir }}/node_modules' + state: absent + +- include_tasks: tests.yml + vars: + node_bin_path: /usr/bin + yarn_bin_path: /usr/bin + package: 'iconv-lite' diff --git a/tests/integration/targets/yarn/tasks/tests.yml b/tests/integration/targets/yarn/tasks/tests.yml new file mode 100644 index 0000000000..8710c2afc4 --- /dev/null +++ b/tests/integration/targets/yarn/tasks/tests.yml @@ -0,0 +1,201 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# Expects: node_bin_path, yarn_bin_path, package + +- environment: + PATH: "{{ node_bin_path }}:{{ ansible_facts.env.PATH }}" + YARN_IGNORE_ENGINES: true + block: + + # Get the version of Yarn and register to a variable + - shell: '{{ yarn_bin_path }}/yarn --version' + environment: + PATH: '{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' + register: yarn_installed_version + + - name: 'Create dummy package.json' + template: + src: package.j2 + dest: '{{ remote_tmp_dir }}/package.json' + + - name: 'Install all packages.' + yarn: + path: '{{ remote_tmp_dir }}' + executable: '{{ yarn_bin_path }}/yarn' + state: present + environment: + PATH: '{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' + + - name: 'Install the same package from package.json again.' + yarn: + path: '{{ remote_tmp_dir }}' + executable: '{{ yarn_bin_path }}/yarn' + name: '{{ package }}' + state: present + environment: + PATH: '{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' + register: yarn_install + + - assert: + that: + - not (yarn_install is changed) + + - name: 'Install all packages in check mode.' + yarn: + path: '{{ remote_tmp_dir }}' + executable: '{{ yarn_bin_path }}/yarn' + state: present + environment: + PATH: '{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' + check_mode: true + register: yarn_install_check + + - name: verify test yarn global installation in check mode + assert: + that: + - yarn_install_check.err is defined + - yarn_install_check.out is defined + - yarn_install_check.err is none + - yarn_install_check.out is none + + - name: 'Install package with explicit version (older version of package)' + yarn: + path: '{{ remote_tmp_dir }}' + executable: '{{ yarn_bin_path }}/yarn' + name: left-pad + version: 1.1.0 + state: present + environment: + PATH: '{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' + register: yarn_install_old_package + + - assert: + that: + - yarn_install_old_package is changed + + - name: 'Again but without explicit executable path' + yarn: + path: '{{ remote_tmp_dir }}' + name: left-pad + version: 1.1.0 + state: present + environment: + PATH: '{{ yarn_bin_path }}:{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' + + - name: 'Upgrade old package' + yarn: + path: '{{ remote_tmp_dir }}' + executable: '{{ yarn_bin_path }}/yarn' + name: left-pad + state: latest + environment: + PATH: '{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' + register: yarn_update_old_package + + - assert: + that: + - yarn_update_old_package is changed + + - name: 'Remove a package' + yarn: + path: '{{ remote_tmp_dir }}' + executable: '{{ yarn_bin_path }}/yarn' + name: '{{ package }}' + state: absent + environment: + PATH: '{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' + register: yarn_uninstall_package + + - name: 'Assert package removed' + assert: + that: + - yarn_uninstall_package is changed + + - name: 'Global install binary with explicit version (older version of package)' + yarn: + global: true + executable: '{{ yarn_bin_path }}/yarn' + name: prettier + version: 2.0.0 + state: present + environment: + PATH: '{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' + register: yarn_global_install_old_binary + + - assert: + that: + - yarn_global_install_old_binary is changed + + - name: 'Global upgrade old binary' + yarn: + global: true + executable: '{{ yarn_bin_path }}/yarn' + name: prettier + state: latest + environment: + PATH: '{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' + register: yarn_global_update_old_binary + + - assert: + that: + - yarn_global_update_old_binary is changed + + - name: 'Global remove a binary' + yarn: + global: true + executable: '{{ yarn_bin_path }}/yarn' + name: prettier + state: absent + environment: + PATH: '{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' + register: yarn_global_uninstall_binary + + - assert: + that: + - yarn_global_uninstall_binary is changed + + - name: 'Global install package with no binary with explicit version (older version of package)' + yarn: + global: true + executable: '{{ yarn_bin_path }}/yarn' + name: left-pad + version: 1.1.0 + state: present + environment: + PATH: '{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' + register: yarn_global_install_old_package + + - assert: + that: + - yarn_global_install_old_package is changed + + - name: 'Global upgrade old package with no binary' + yarn: + global: true + executable: '{{ yarn_bin_path }}/yarn' + name: left-pad + state: latest + environment: + PATH: '{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' + register: yarn_global_update_old_package + + - assert: + that: + - yarn_global_update_old_package is changed + + - name: 'Global remove a package with no binary' + yarn: + global: true + executable: '{{ yarn_bin_path }}/yarn' + name: left-pad + state: absent + environment: + PATH: '{{ node_bin_path }}:{{ ansible_facts.env.PATH }}' + register: yarn_global_uninstall_package + + - assert: + that: + - yarn_global_uninstall_package is changed