mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
New module: pnpm package manager (#6741)
* (feat) New module pnpm added A new module for pnpm is added. Necessary entries were written to BOTMETA.yml. * (feat) Basic tests added * (feat) reduced nesting of ifs * (fix) trying to fix up CI pipelines * (fix) incorrect indentation of alias fixed * (feat) fixed further indentations * Apply suggestions from code review Co-authored-by: Felix Fontein <felix@fontein.de> * (fix) various linting and CI errors fixed/ignored * (feat) reduced restriction, new install method Some restrictions on OS are reduced. New installation method, similar to the official installation method, is provided. * (fix) ignoring CentOS 6 for CI. * retrigger checks --------- Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
parent
58d89ce442
commit
568814fc3e
7 changed files with 833 additions and 0 deletions
3
.github/BOTMETA.yml
vendored
3
.github/BOTMETA.yml
vendored
|
@ -980,6 +980,9 @@ files:
|
||||||
maintainers: $team_solaris dermute
|
maintainers: $team_solaris dermute
|
||||||
$modules/pmem.py:
|
$modules/pmem.py:
|
||||||
maintainers: mizumm
|
maintainers: mizumm
|
||||||
|
$modules/pnpm.py:
|
||||||
|
ignore: chrishoffman
|
||||||
|
maintainers: aretrosen
|
||||||
$modules/portage.py:
|
$modules/portage.py:
|
||||||
ignore: sayap
|
ignore: sayap
|
||||||
labels: portage
|
labels: portage
|
||||||
|
|
463
plugins/modules/pnpm.py
Normal file
463
plugins/modules/pnpm.py
Normal file
|
@ -0,0 +1,463 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2023 Aritra Sen <aretrosen@proton.me>
|
||||||
|
# Copyright (c) 2017 Chris Hoffman <christopher.hoffman@gmail.com>
|
||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
|
DOCUMENTATION = """
|
||||||
|
---
|
||||||
|
module: pnpm
|
||||||
|
short_description: Manage node.js packages with pnpm
|
||||||
|
version_added: 7.4.0
|
||||||
|
description:
|
||||||
|
- Manage node.js packages with the L(pnpm package manager, https://pnpm.io/).
|
||||||
|
author:
|
||||||
|
- "Aritra Sen (@aretrosen)"
|
||||||
|
- "Chris Hoffman (@chrishoffman), creator of NPM Ansible module"
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- community.general.attributes
|
||||||
|
attributes:
|
||||||
|
check_mode:
|
||||||
|
support: full
|
||||||
|
diff_mode:
|
||||||
|
support: none
|
||||||
|
options:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- The name of a node.js library to install.
|
||||||
|
- All packages in package.json are installed if not provided.
|
||||||
|
type: str
|
||||||
|
required: false
|
||||||
|
alias:
|
||||||
|
description:
|
||||||
|
- Alias of the node.js library.
|
||||||
|
type: str
|
||||||
|
required: false
|
||||||
|
path:
|
||||||
|
description:
|
||||||
|
- The base path to install the node.js libraries.
|
||||||
|
type: path
|
||||||
|
required: false
|
||||||
|
version:
|
||||||
|
description:
|
||||||
|
- The version of the library to be installed, in semver format.
|
||||||
|
type: str
|
||||||
|
required: false
|
||||||
|
global:
|
||||||
|
description:
|
||||||
|
- Install the node.js library globally.
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
type: bool
|
||||||
|
executable:
|
||||||
|
description:
|
||||||
|
- The executable location for pnpm.
|
||||||
|
- The default location it searches for is E(PATH), fails if not set.
|
||||||
|
type: path
|
||||||
|
required: false
|
||||||
|
ignore_scripts:
|
||||||
|
description:
|
||||||
|
- Use the C(--ignore-scripts) flag when installing.
|
||||||
|
required: false
|
||||||
|
type: bool
|
||||||
|
default: false
|
||||||
|
no_optional:
|
||||||
|
description:
|
||||||
|
- Do not install optional packages, equivalent to C(--no-optional).
|
||||||
|
required: false
|
||||||
|
type: bool
|
||||||
|
default: false
|
||||||
|
production:
|
||||||
|
description:
|
||||||
|
- Install dependencies in production mode.
|
||||||
|
- Pnpm will ignore any dependencies under C(devDependencies) in package.json.
|
||||||
|
required: false
|
||||||
|
type: bool
|
||||||
|
default: false
|
||||||
|
dev:
|
||||||
|
description:
|
||||||
|
- Install dependencies in development mode.
|
||||||
|
- Pnpm will ignore any regular dependencies in C(package.json).
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
type: bool
|
||||||
|
optional:
|
||||||
|
description:
|
||||||
|
- Install dependencies in optional mode.
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
type: bool
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- Installation state of the named node.js library.
|
||||||
|
- If V(absent) is selected, a name option must be provided.
|
||||||
|
type: str
|
||||||
|
required: false
|
||||||
|
default: present
|
||||||
|
choices: ["present", "absent", "latest"]
|
||||||
|
requirements:
|
||||||
|
- Pnpm executable present in E(PATH).
|
||||||
|
"""
|
||||||
|
|
||||||
|
EXAMPLES = """
|
||||||
|
- name: Install "tailwindcss" node.js package.
|
||||||
|
community.general.pnpm:
|
||||||
|
name: tailwindcss
|
||||||
|
path: /app/location
|
||||||
|
|
||||||
|
- name: Install "tailwindcss" node.js package on version 3.3.2
|
||||||
|
community.general.pnpm:
|
||||||
|
name: tailwindcss
|
||||||
|
version: 3.3.2
|
||||||
|
path: /app/location
|
||||||
|
|
||||||
|
- name: Install "tailwindcss" node.js package globally.
|
||||||
|
community.general.pnpm:
|
||||||
|
name: tailwindcss
|
||||||
|
global: true
|
||||||
|
|
||||||
|
- name: Install "tailwindcss" node.js package as dev dependency.
|
||||||
|
community.general.pnpm:
|
||||||
|
name: tailwindcss
|
||||||
|
path: /app/location
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
- name: Install "tailwindcss" node.js package as optional dependency.
|
||||||
|
community.general.pnpm:
|
||||||
|
name: tailwindcss
|
||||||
|
path: /app/location
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
- name: Install "tailwindcss" node.js package version 0.1.3 as tailwind-1
|
||||||
|
community.general.pnpm:
|
||||||
|
name: tailwindcss
|
||||||
|
alias: tailwind-1
|
||||||
|
version: 0.1.3
|
||||||
|
path: /app/location
|
||||||
|
|
||||||
|
- name: Remove the globally-installed package "tailwindcss".
|
||||||
|
community.general.pnpm:
|
||||||
|
name: tailwindcss
|
||||||
|
global: true
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: Install packages based on package.json.
|
||||||
|
community.general.pnpm:
|
||||||
|
path: /app/location
|
||||||
|
|
||||||
|
- name: Update all packages in package.json to their latest version.
|
||||||
|
community.general.pnpm:
|
||||||
|
path: /app/location
|
||||||
|
state: latest
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible.module_utils.common.text.converters import to_native
|
||||||
|
|
||||||
|
|
||||||
|
class Pnpm(object):
|
||||||
|
def __init__(self, module, **kwargs):
|
||||||
|
self.module = module
|
||||||
|
self.name = kwargs["name"]
|
||||||
|
self.alias = kwargs["alias"]
|
||||||
|
self.version = kwargs["version"]
|
||||||
|
self.path = kwargs["path"]
|
||||||
|
self.globally = kwargs["globally"]
|
||||||
|
self.executable = kwargs["executable"]
|
||||||
|
self.ignore_scripts = kwargs["ignore_scripts"]
|
||||||
|
self.no_optional = kwargs["no_optional"]
|
||||||
|
self.production = kwargs["production"]
|
||||||
|
self.dev = kwargs["dev"]
|
||||||
|
self.optional = kwargs["optional"]
|
||||||
|
|
||||||
|
self.alias_name_ver = None
|
||||||
|
|
||||||
|
if self.alias is not None:
|
||||||
|
self.alias_name_ver = self.alias + "@npm:"
|
||||||
|
|
||||||
|
if self.name is not None:
|
||||||
|
self.alias_name_ver = (self.alias_name_ver or "") + self.name
|
||||||
|
if self.version is not None:
|
||||||
|
self.alias_name_ver = self.alias_name_ver + "@" + str(self.version)
|
||||||
|
|
||||||
|
def _exec(self, args, run_in_check_mode=False, check_rc=True):
|
||||||
|
if not self.module.check_mode or (self.module.check_mode and run_in_check_mode):
|
||||||
|
cmd = self.executable + args
|
||||||
|
|
||||||
|
if self.globally:
|
||||||
|
cmd.append("-g")
|
||||||
|
|
||||||
|
if self.ignore_scripts:
|
||||||
|
cmd.append("--ignore-scripts")
|
||||||
|
|
||||||
|
if self.no_optional:
|
||||||
|
cmd.append("--no-optional")
|
||||||
|
|
||||||
|
if self.production:
|
||||||
|
cmd.append("-P")
|
||||||
|
|
||||||
|
if self.dev:
|
||||||
|
cmd.append("-D")
|
||||||
|
|
||||||
|
if self.name and self.optional:
|
||||||
|
cmd.append("-O")
|
||||||
|
|
||||||
|
# If path is specified, cd into that path and run the command.
|
||||||
|
cwd = None
|
||||||
|
if self.path:
|
||||||
|
if not os.path.exists(self.path):
|
||||||
|
os.makedirs(self.path)
|
||||||
|
|
||||||
|
if not os.path.isdir(self.path):
|
||||||
|
self.module.fail_json(msg="Path %s is not a directory" % self.path)
|
||||||
|
|
||||||
|
if not self.alias_name_ver and not os.path.isfile(
|
||||||
|
os.path.join(self.path, "package.json")
|
||||||
|
):
|
||||||
|
self.module.fail_json(
|
||||||
|
msg="package.json does not exist in provided path"
|
||||||
|
)
|
||||||
|
|
||||||
|
cwd = self.path
|
||||||
|
|
||||||
|
_rc, out, err = self.module.run_command(cmd, check_rc=check_rc, cwd=cwd)
|
||||||
|
return out, err
|
||||||
|
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
def missing(self):
|
||||||
|
if not os.path.isfile(os.path.join(self.path, "pnpm-lock.yaml")):
|
||||||
|
return True
|
||||||
|
|
||||||
|
cmd = ["list", "--json"]
|
||||||
|
|
||||||
|
if self.name is not None:
|
||||||
|
cmd.append(self.name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
out, err = self._exec(cmd, True, False)
|
||||||
|
if err is not None and err != "":
|
||||||
|
raise Exception(out)
|
||||||
|
|
||||||
|
data = json.loads(out)
|
||||||
|
except Exception as e:
|
||||||
|
self.module.fail_json(
|
||||||
|
msg="Failed to parse pnpm output with error %s" % to_native(e)
|
||||||
|
)
|
||||||
|
|
||||||
|
if "error" in data:
|
||||||
|
return True
|
||||||
|
|
||||||
|
data = data[0]
|
||||||
|
|
||||||
|
for typedep in [
|
||||||
|
"dependencies",
|
||||||
|
"devDependencies",
|
||||||
|
"optionalDependencies",
|
||||||
|
"unsavedDependencies",
|
||||||
|
]:
|
||||||
|
if typedep not in data:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for dep, prop in data[typedep].items():
|
||||||
|
if self.alias is not None and self.alias != dep:
|
||||||
|
continue
|
||||||
|
|
||||||
|
name = prop["from"] if self.alias is not None else dep
|
||||||
|
if self.name != name:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if self.version is None or self.version == prop["version"]:
|
||||||
|
return False
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def install(self):
|
||||||
|
if self.alias_name_ver is not None:
|
||||||
|
return self._exec(["add", self.alias_name_ver])
|
||||||
|
return self._exec(["install"])
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
return self._exec(["update", "--latest"])
|
||||||
|
|
||||||
|
def uninstall(self):
|
||||||
|
if self.alias is not None:
|
||||||
|
return self._exec(["remove", self.alias])
|
||||||
|
return self._exec(["remove", self.name])
|
||||||
|
|
||||||
|
def list_outdated(self):
|
||||||
|
if not os.path.isfile(os.path.join(self.path, "pnpm-lock.yaml")):
|
||||||
|
return list()
|
||||||
|
|
||||||
|
cmd = ["outdated", "--format", "json"]
|
||||||
|
try:
|
||||||
|
out, err = self._exec(cmd, True, False)
|
||||||
|
|
||||||
|
# BUG: It will not show correct error sometimes, like when it has
|
||||||
|
# plain text output intermingled with a {}
|
||||||
|
if err is not None and err != "":
|
||||||
|
raise Exception(out)
|
||||||
|
|
||||||
|
# HACK: To fix the above bug, the following hack is implemented
|
||||||
|
data_lines = out.splitlines(True)
|
||||||
|
|
||||||
|
out = None
|
||||||
|
for line in data_lines:
|
||||||
|
if len(line) > 0 and line[0] == "{":
|
||||||
|
out = line
|
||||||
|
continue
|
||||||
|
|
||||||
|
if len(line) > 0 and line[0] == "}":
|
||||||
|
out += line
|
||||||
|
break
|
||||||
|
|
||||||
|
if out is not None:
|
||||||
|
out += line
|
||||||
|
|
||||||
|
data = json.loads(out)
|
||||||
|
except Exception as e:
|
||||||
|
self.module.fail_json(
|
||||||
|
msg="Failed to parse pnpm output with error %s" % to_native(e)
|
||||||
|
)
|
||||||
|
|
||||||
|
return data.keys()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
arg_spec = dict(
|
||||||
|
name=dict(default=None),
|
||||||
|
alias=dict(default=None),
|
||||||
|
path=dict(default=None, type="path"),
|
||||||
|
version=dict(default=None),
|
||||||
|
executable=dict(default=None, type="path"),
|
||||||
|
ignore_scripts=dict(default=False, type="bool"),
|
||||||
|
no_optional=dict(default=False, type="bool"),
|
||||||
|
production=dict(default=False, type="bool"),
|
||||||
|
dev=dict(default=False, type="bool"),
|
||||||
|
optional=dict(default=False, type="bool"),
|
||||||
|
state=dict(default="present", choices=["present", "absent", "latest"]),
|
||||||
|
)
|
||||||
|
arg_spec["global"] = dict(default=False, type="bool")
|
||||||
|
module = AnsibleModule(argument_spec=arg_spec, supports_check_mode=True)
|
||||||
|
|
||||||
|
name = module.params["name"]
|
||||||
|
alias = module.params["alias"]
|
||||||
|
path = module.params["path"]
|
||||||
|
version = module.params["version"]
|
||||||
|
globally = module.params["global"]
|
||||||
|
ignore_scripts = module.params["ignore_scripts"]
|
||||||
|
no_optional = module.params["no_optional"]
|
||||||
|
production = module.params["production"]
|
||||||
|
dev = module.params["dev"]
|
||||||
|
optional = module.params["optional"]
|
||||||
|
state = module.params["state"]
|
||||||
|
|
||||||
|
if module.params["executable"]:
|
||||||
|
executable = module.params["executable"].split(" ")
|
||||||
|
else:
|
||||||
|
executable = [module.get_bin_path("pnpm", True)]
|
||||||
|
|
||||||
|
if name is None and version is not None:
|
||||||
|
module.fail_json(msg="version is meaningless when name is not provided")
|
||||||
|
|
||||||
|
if name is None and alias is not None:
|
||||||
|
module.fail_json(msg="alias is meaningless when name is not provided")
|
||||||
|
|
||||||
|
if path is None and not globally:
|
||||||
|
module.fail_json(msg="path must be specified when not using global")
|
||||||
|
elif path is not None and globally:
|
||||||
|
module.fail_json(msg="Cannot specify path when doing global installation")
|
||||||
|
|
||||||
|
if globally and (production or dev or optional):
|
||||||
|
module.fail_json(
|
||||||
|
msg="Options production, dev, and optional is meaningless when installing packages globally"
|
||||||
|
)
|
||||||
|
|
||||||
|
if name is not None and path is not None and globally:
|
||||||
|
module.fail_json(msg="path should not be mentioned when installing globally")
|
||||||
|
|
||||||
|
if production and dev and optional:
|
||||||
|
module.fail_json(
|
||||||
|
msg="Options production and dev and optional don't go together"
|
||||||
|
)
|
||||||
|
|
||||||
|
if production and dev:
|
||||||
|
module.fail_json(msg="Options production and dev don't go together")
|
||||||
|
|
||||||
|
if production and optional:
|
||||||
|
module.fail_json(msg="Options production and optional don't go together")
|
||||||
|
|
||||||
|
if dev and optional:
|
||||||
|
module.fail_json(msg="Options dev and optional don't go together")
|
||||||
|
|
||||||
|
if name is not None and name[0:4] == "http" and version is not None:
|
||||||
|
module.fail_json(msg="Semver not supported on remote url downloads")
|
||||||
|
|
||||||
|
if name is None and optional:
|
||||||
|
module.fail_json(
|
||||||
|
msg="Optional not available when package name not provided, use no_optional instead"
|
||||||
|
)
|
||||||
|
|
||||||
|
if state == "absent" and name is None:
|
||||||
|
module.fail_json(msg="Package name is required for uninstalling")
|
||||||
|
|
||||||
|
if state == "latest":
|
||||||
|
version = "latest"
|
||||||
|
|
||||||
|
if globally:
|
||||||
|
_rc, out, _err = module.run_command(executable + ["root", "-g"], check_rc=True)
|
||||||
|
path, _tail = os.path.split(out.strip())
|
||||||
|
|
||||||
|
pnpm = Pnpm(
|
||||||
|
module,
|
||||||
|
name=name,
|
||||||
|
alias=alias,
|
||||||
|
path=path,
|
||||||
|
version=version,
|
||||||
|
globally=globally,
|
||||||
|
executable=executable,
|
||||||
|
ignore_scripts=ignore_scripts,
|
||||||
|
no_optional=no_optional,
|
||||||
|
production=production,
|
||||||
|
dev=dev,
|
||||||
|
optional=optional,
|
||||||
|
)
|
||||||
|
|
||||||
|
changed = False
|
||||||
|
out = ""
|
||||||
|
err = ""
|
||||||
|
if state == "present":
|
||||||
|
if pnpm.missing():
|
||||||
|
changed = True
|
||||||
|
out, err = pnpm.install()
|
||||||
|
elif state == "latest":
|
||||||
|
outdated = pnpm.list_outdated()
|
||||||
|
if name is not None:
|
||||||
|
if pnpm.missing() or name in outdated:
|
||||||
|
changed = True
|
||||||
|
out, err = pnpm.install()
|
||||||
|
elif len(outdated):
|
||||||
|
changed = True
|
||||||
|
out, err = pnpm.update()
|
||||||
|
else: # absent
|
||||||
|
if not pnpm.missing():
|
||||||
|
changed = True
|
||||||
|
out, err = pnpm.uninstall()
|
||||||
|
|
||||||
|
module.exit_json(changed=changed, out=out, err=err)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
8
tests/integration/targets/pnpm/aliases
Normal file
8
tests/integration/targets/pnpm/aliases
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# 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
|
||||||
|
|
||||||
|
azp/posix/2
|
||||||
|
destructive
|
||||||
|
skip/macos
|
||||||
|
skip/freebsd
|
9
tests/integration/targets/pnpm/meta/main.yml
Normal file
9
tests/integration/targets/pnpm/meta/main.yml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
# 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
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- setup_pkg_mgr
|
||||||
|
- setup_gnutar
|
||||||
|
- setup_remote_tmp_dir
|
26
tests/integration/targets/pnpm/tasks/main.yml
Normal file
26
tests/integration/targets/pnpm/tasks/main.yml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
####################################################################
|
||||||
|
# WARNING: These are designed specifically for Ansible tests #
|
||||||
|
# and should not be used as examples of how to write Ansible roles #
|
||||||
|
####################################################################
|
||||||
|
|
||||||
|
# test code for the pnpm module
|
||||||
|
# Copyright (c) 2023 Aritra Sen <aretrosen@proton.me>
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
# Setup steps
|
||||||
|
|
||||||
|
- name: Run tests on OSes
|
||||||
|
include_tasks: run.yml
|
||||||
|
vars:
|
||||||
|
ansible_system_os: "{{ ansible_system | lower }}"
|
||||||
|
nodejs_version: "{{ item.node_version }}"
|
||||||
|
nodejs_path: "node-v{{ nodejs_version }}-{{ ansible_system_os }}-x{{ ansible_userspace_bits }}"
|
||||||
|
pnpm_version: "{{ item.pnpm_version }}"
|
||||||
|
pnpm_path: "pnpm-{{ 'macos' if ansible_system_os == 'darwin' else 'linuxstatic' }}-x{{ ansible_userspace_bits }}"
|
||||||
|
with_items:
|
||||||
|
- { node_version: 16.20.0, pnpm_version: 8.7.0 }
|
||||||
|
when:
|
||||||
|
- not(ansible_distribution == 'Alpine') and not(ansible_distribution == 'CentOS' and ansible_distribution_major_version == '6')
|
311
tests/integration/targets/pnpm/tasks/run.yml
Normal file
311
tests/integration/targets/pnpm/tasks/run.yml
Normal file
|
@ -0,0 +1,311 @@
|
||||||
|
---
|
||||||
|
# Copyright (c) 2023 Aritra Sen <aretrosen@proton.me>
|
||||||
|
# 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: Download nodejs
|
||||||
|
ansible.builtin.unarchive:
|
||||||
|
src: "https://nodejs.org/dist/v{{ nodejs_version }}/{{ nodejs_path }}.tar.gz"
|
||||||
|
dest: "{{ remote_tmp_dir }}"
|
||||||
|
remote_src: true
|
||||||
|
creates: "{{ remote_tmp_dir }}/{{ nodejs_path }}.tar.gz"
|
||||||
|
|
||||||
|
- name: Create a temporary directory for pnpm binary
|
||||||
|
ansible.builtin.tempfile:
|
||||||
|
state: directory
|
||||||
|
register: tmp_dir
|
||||||
|
|
||||||
|
- name: Download pnpm binary to the temporary directory
|
||||||
|
ansible.builtin.get_url:
|
||||||
|
url: "https://github.com/pnpm/pnpm/releases/download/v{{ pnpm_version }}/{{ pnpm_path }}"
|
||||||
|
dest: "{{ tmp_dir.path }}/pnpm"
|
||||||
|
mode: "755"
|
||||||
|
|
||||||
|
- name: Setting up pnpm via command
|
||||||
|
ansible.builtin.command: "{{ tmp_dir.path }}/pnpm setup --force"
|
||||||
|
environment:
|
||||||
|
PNPM_HOME: "{{ ansible_env.HOME }}/.local/share/pnpm"
|
||||||
|
SHELL: /bin/sh
|
||||||
|
ENV: "{{ ansible_env.HOME }}/.shrc"
|
||||||
|
|
||||||
|
- name: Remove the temporary directory
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ tmp_dir.path }}"
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: Remove any previous Nodejs modules
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ remote_tmp_dir }}/node_modules"
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: CI tests to run
|
||||||
|
vars:
|
||||||
|
node_bin_path: "{{ remote_tmp_dir }}/{{ nodejs_path }}/bin"
|
||||||
|
pnpm_bin_path: "{{ ansible_env.HOME }}/.local/share/pnpm"
|
||||||
|
package: "tailwindcss"
|
||||||
|
environment:
|
||||||
|
PATH: "{{ node_bin_path }}:{{ ansible_env.PATH }}"
|
||||||
|
|
||||||
|
block:
|
||||||
|
- name: Create dummy package.json
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: package.j2
|
||||||
|
dest: "{{ remote_tmp_dir }}/package.json"
|
||||||
|
mode: "644"
|
||||||
|
|
||||||
|
- name: Install reading-time package via package.json
|
||||||
|
pnpm:
|
||||||
|
path: "{{ remote_tmp_dir }}"
|
||||||
|
executable: "{{ pnpm_bin_path }}/pnpm"
|
||||||
|
state: present
|
||||||
|
environment:
|
||||||
|
PATH: "{{ node_bin_path }}:{{ ansible_env.PATH }}"
|
||||||
|
|
||||||
|
- name: Install the same package from package.json again
|
||||||
|
pnpm:
|
||||||
|
path: "{{ remote_tmp_dir }}"
|
||||||
|
executable: "{{ pnpm_bin_path }}/pnpm"
|
||||||
|
name: "reading-time"
|
||||||
|
state: present
|
||||||
|
environment:
|
||||||
|
PATH: "{{ node_bin_path }}:{{ ansible_env.PATH }}"
|
||||||
|
register: pnpm_install
|
||||||
|
|
||||||
|
- name: Assert that result is not changed
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- not (pnpm_install is changed)
|
||||||
|
|
||||||
|
- name: Install all packages in check mode
|
||||||
|
pnpm:
|
||||||
|
path: "{{ remote_tmp_dir }}"
|
||||||
|
executable: "{{ pnpm_bin_path }}/pnpm"
|
||||||
|
state: present
|
||||||
|
environment:
|
||||||
|
PATH: "{{ node_bin_path }}:{{ ansible_env.PATH }}"
|
||||||
|
check_mode: true
|
||||||
|
register: pnpm_install_check
|
||||||
|
|
||||||
|
- name: Verify test pnpm global installation in check mode
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- pnpm_install_check.err is defined
|
||||||
|
- pnpm_install_check.out is defined
|
||||||
|
- pnpm_install_check.err is none
|
||||||
|
- pnpm_install_check.out is none
|
||||||
|
|
||||||
|
- name: Install package without dependency
|
||||||
|
pnpm:
|
||||||
|
path: "{{ remote_tmp_dir }}"
|
||||||
|
executable: "{{ pnpm_bin_path }}/pnpm"
|
||||||
|
state: present
|
||||||
|
name: "{{ package }}"
|
||||||
|
environment:
|
||||||
|
PATH: "{{ node_bin_path }}:{{ ansible_env.PATH }}"
|
||||||
|
register: pnpm_install
|
||||||
|
|
||||||
|
- name: Assert that result is changed and successful
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- pnpm_install is success
|
||||||
|
- pnpm_install is changed
|
||||||
|
|
||||||
|
- name: Reinstall package without dependency
|
||||||
|
pnpm:
|
||||||
|
path: "{{ remote_tmp_dir }}"
|
||||||
|
executable: "{{ pnpm_bin_path }}/pnpm"
|
||||||
|
state: present
|
||||||
|
name: "{{ package }}"
|
||||||
|
environment:
|
||||||
|
PATH: "{{ node_bin_path }}:{{ ansible_env.PATH }}"
|
||||||
|
register: pnpm_reinstall
|
||||||
|
|
||||||
|
- name: Assert that there is no change
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- pnpm_reinstall is success
|
||||||
|
- not (pnpm_reinstall is changed)
|
||||||
|
|
||||||
|
- name: Manually delete package
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ remote_tmp_dir }}/node_modules/{{ package }}"
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: Reinstall package
|
||||||
|
pnpm:
|
||||||
|
path: "{{ remote_tmp_dir }}"
|
||||||
|
executable: "{{ pnpm_bin_path }}/pnpm"
|
||||||
|
state: latest
|
||||||
|
name: "{{ package }}"
|
||||||
|
environment:
|
||||||
|
PATH: "{{ node_bin_path }}:{{ ansible_env.PATH }}"
|
||||||
|
register: pnpm_fix_install
|
||||||
|
|
||||||
|
- name: Assert that result is changed and successful
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- pnpm_fix_install is success
|
||||||
|
- pnpm_fix_install is changed
|
||||||
|
|
||||||
|
- name: Install package with version, without executable path
|
||||||
|
pnpm:
|
||||||
|
name: "{{ package }}"
|
||||||
|
version: 0.1.3
|
||||||
|
path: "{{ remote_tmp_dir }}"
|
||||||
|
state: present
|
||||||
|
environment:
|
||||||
|
PATH: "{{ pnpm_bin_path }}:{{ node_bin_path }}:{{ ansible_env.PATH }}"
|
||||||
|
register: pnpm_install
|
||||||
|
|
||||||
|
- name: Assert that package with version is installed
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- pnpm_install is success
|
||||||
|
- pnpm_install is changed
|
||||||
|
|
||||||
|
- name: Reinstall package with version, without explicit executable path
|
||||||
|
pnpm:
|
||||||
|
name: "{{ package }}"
|
||||||
|
version: 0.1.3
|
||||||
|
path: "{{ remote_tmp_dir }}"
|
||||||
|
state: present
|
||||||
|
environment:
|
||||||
|
PATH: "{{ pnpm_bin_path }}:{{ node_bin_path }}:{{ ansible_env.PATH }}"
|
||||||
|
register: pnpm_reinstall
|
||||||
|
|
||||||
|
- name: Assert that there is no change
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- pnpm_reinstall is success
|
||||||
|
- not (pnpm_reinstall is changed)
|
||||||
|
|
||||||
|
- name: Update package, without executable path
|
||||||
|
pnpm:
|
||||||
|
name: "{{ package }}"
|
||||||
|
path: "{{ remote_tmp_dir }}"
|
||||||
|
state: latest
|
||||||
|
environment:
|
||||||
|
PATH: "{{ pnpm_bin_path }}:{{ node_bin_path }}:{{ ansible_env.PATH }}"
|
||||||
|
register: pnpm_update
|
||||||
|
|
||||||
|
- name: Assert that result is changed and successful
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- pnpm_update is success
|
||||||
|
- pnpm_update is changed
|
||||||
|
|
||||||
|
- name: Remove package, without executable path
|
||||||
|
pnpm:
|
||||||
|
name: "{{ package }}"
|
||||||
|
path: "{{ remote_tmp_dir }}"
|
||||||
|
state: absent
|
||||||
|
environment:
|
||||||
|
PATH: "{{ pnpm_bin_path }}:{{ node_bin_path }}:{{ ansible_env.PATH }}"
|
||||||
|
register: pnpm_absent
|
||||||
|
|
||||||
|
- name: Assert that result is changed and successful
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- pnpm_absent is success
|
||||||
|
- pnpm_absent is changed
|
||||||
|
|
||||||
|
- name: Install package with version and alias, without executable path
|
||||||
|
pnpm:
|
||||||
|
name: "{{ package }}"
|
||||||
|
alias: tailwind-1
|
||||||
|
version: 0.1.3
|
||||||
|
path: "{{ remote_tmp_dir }}"
|
||||||
|
state: present
|
||||||
|
environment:
|
||||||
|
PATH: "{{ pnpm_bin_path }}:{{ node_bin_path }}:{{ ansible_env.PATH }}"
|
||||||
|
register: pnpm_install
|
||||||
|
|
||||||
|
- name: Assert that package with version and alias is installed
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- pnpm_install is success
|
||||||
|
- pnpm_install is changed
|
||||||
|
|
||||||
|
- name: Reinstall package with version and alias, without explicit executable path
|
||||||
|
pnpm:
|
||||||
|
name: "{{ package }}"
|
||||||
|
alias: tailwind-1
|
||||||
|
version: 0.1.3
|
||||||
|
path: "{{ remote_tmp_dir }}"
|
||||||
|
state: present
|
||||||
|
environment:
|
||||||
|
PATH: "{{ pnpm_bin_path }}:{{ node_bin_path }}:{{ ansible_env.PATH }}"
|
||||||
|
register: pnpm_reinstall
|
||||||
|
|
||||||
|
- name: Assert that there is no change
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- pnpm_reinstall is success
|
||||||
|
- not (pnpm_reinstall is changed)
|
||||||
|
|
||||||
|
- name: Remove package with alias, without executable path
|
||||||
|
pnpm:
|
||||||
|
name: tailwindcss
|
||||||
|
alias: tailwind-1
|
||||||
|
path: "{{ remote_tmp_dir }}"
|
||||||
|
state: absent
|
||||||
|
environment:
|
||||||
|
PATH: "{{ pnpm_bin_path }}:{{ node_bin_path }}:{{ ansible_env.PATH }}"
|
||||||
|
register: pnpm_absent
|
||||||
|
|
||||||
|
- name: Assert that result is changed and successful
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- pnpm_absent is success
|
||||||
|
- pnpm_absent is changed
|
||||||
|
|
||||||
|
- name: Install package without dependency globally
|
||||||
|
pnpm:
|
||||||
|
name: "{{ package }}"
|
||||||
|
executable: "{{ pnpm_bin_path }}/pnpm"
|
||||||
|
state: present
|
||||||
|
global: true
|
||||||
|
environment:
|
||||||
|
PATH: "{{ pnpm_bin_path }}:{{ node_bin_path }}:{{ ansible_env.PATH }}"
|
||||||
|
PNPM_HOME: "{{ pnpm_bin_path }}"
|
||||||
|
register: pnpm_install
|
||||||
|
|
||||||
|
- name: Assert that result is changed and successful
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- pnpm_install is success
|
||||||
|
- pnpm_install is changed
|
||||||
|
|
||||||
|
- name: Reinstall package globally, without explicit executable path
|
||||||
|
pnpm:
|
||||||
|
name: "{{ package }}"
|
||||||
|
state: present
|
||||||
|
global: true
|
||||||
|
environment:
|
||||||
|
PATH: "{{ pnpm_bin_path }}:{{ node_bin_path }}:{{ ansible_env.PATH }}"
|
||||||
|
PNPM_HOME: "{{ pnpm_bin_path }}"
|
||||||
|
register: pnpm_reinstall
|
||||||
|
|
||||||
|
- name: Assert that there is no change
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- pnpm_reinstall is success
|
||||||
|
- not (pnpm_reinstall is changed)
|
||||||
|
|
||||||
|
- name: Remove package without dependency globally
|
||||||
|
pnpm:
|
||||||
|
name: "{{ package }}"
|
||||||
|
executable: "{{ pnpm_bin_path }}/pnpm"
|
||||||
|
global: true
|
||||||
|
state: absent
|
||||||
|
environment:
|
||||||
|
PATH: "{{ pnpm_bin_path }}:{{ node_bin_path }}:{{ ansible_env.PATH }}"
|
||||||
|
PNPM_HOME: "{{ pnpm_bin_path }}"
|
||||||
|
register: pnpm_absent
|
||||||
|
|
||||||
|
- name: Assert that result is changed and successful
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- pnpm_absent is success
|
||||||
|
- pnpm_absent is changed
|
13
tests/integration/targets/pnpm/templates/package.j2
Normal file
13
tests/integration/targets/pnpm/templates/package.j2
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{#
|
||||||
|
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": "ansible-pnpm-testing",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"reading-time": "^1.5.0"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue