Make dist-git the upstream repo

Since the scripts and tools in this package are really Fedora
(and derivatives) specific, it doesn't really make a lot of
sense to maintain them in a separate upstream with tarball
releases.

This moves all the files into dist-git and builds the RPM from
there.

Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
This commit is contained in:
Stephen Gallagher 2020-09-18 15:47:37 -04:00
parent c68c968e27
commit 8de3f914f4
26 changed files with 1589 additions and 28 deletions

35
.gitignore vendored
View File

@ -1,27 +1,14 @@
*~
*.swp
__pycache__/
*.pyc
nodejs_req.py
test/*/package.json
test/*/nodejs.prov.err
test/*/nodejs.prov.out
test/*/nodejs.req.err
test/*/nodejs.req.out
*.rpm
.build-*.log
noarch/
nodejs-packaging-fedora-*/
/nodejs-packaging-fedora-2.tar.xz
/nodejs-packaging-fedora-3.tar.xz
/nodejs-packaging-fedora-4.tar.xz
/nodejs-packaging-fedora-6.tar.xz
/nodejs-packaging-fedora-7.tar.xz
/nodejs-packaging-fedora-8.tar.xz
/nodejs-packaging-fedora-9.tar.xz
/nodejs-packaging-fedora-10.tar.xz
/nodejs-packaging-fedora-11.tar.xz
/nodejs-packaging-fedora-12.tar.xz
/nodejs-packaging-fedora-13.tar.xz
/nodejs-packaging-fedora-14.tar.xz
/nodejs-packaging-fedora-15.tar.xz
/nodejs-packaging-fedora-16.tar.xz
/nodejs-packaging-fedora-17.tar.xz
/nodejs-packaging-fedora-18.tar.xz
/nodejs-packaging-fedora-19.tar.xz
/nodejs-packaging-fedora-20.tar.xz
/nodejs-packaging-fedora-21.tar.xz
/nodejs-packaging-fedora-22.tar.xz
/nodejs-packaging-fedora-23.tar.xz
/nodejs-packaging-fedora-24.tar.xz
/nodejs-packaging-fedora-25.tar.xz
/test.tar.gz

19
LICENSE Normal file
View File

@ -0,0 +1,19 @@
Copyright 2012, 2013 T.C. Hollingsworth <tchollingsworth@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.

137
README.md Normal file
View File

@ -0,0 +1,137 @@
# How to update Node.js in Fedora
## Determine the Node.js version
Monitor the [Node.js Blog](https://nodejs.org/en/blog/) to be notified of
available updates.
For simplicity and copy-and-paste of instructions below, set some variables
here:
```
NODEJS_MAJOR=12
NODEJS_VERSION=12.9.0
```
## Clone the Fedora package repository
These steps assume that you are a comaintainer of Node.js or a provenpackager
in Fedora.
```
fedpkg clone nodejs nodejs-fedora
```
Next, switch to the major version branch you are going to update. We'll use
Node.js 12.9.0 in this document. Adjust the versions appropriately for the
version you are working on.
```
pushd nodejs-fedora
fedpkg switch-branch $NODEJS_MAJOR
popd
```
## Clone the Fedora Module repository
```
fedpkg clone modules/nodejs nodejs-fedora-module
```
## Clone the upstream Node.js repository
```
git clone -o upstream git://github.com/nodejs/node.git nodejs-upstream
```
## Rebase the Fedora patches atop the latest release
```
pushd nodejs-upstream
git checkout -b fedora-v$NODEJS_VERSION v$NODEJS_VERSION
git am -3 ../nodejs-fedora/*.patch
```
If the patches do not apply cleanly, resolve the merges appropriately. Once
they have all been applied, output them again:
```
git format-patch -M --patience --full-index -o ../nodejs-fedora v$NODEJS_VERSION..HEAD
popd
```
## Update the Node.js tarball and specfile
```
pushd nodejs-fedora
./nodejs-tarball.sh $NODEJS_VERSION
```
Note that this command will also output all of the versions for the software
bundled with Node.js. You will need to edit `nodejs.spec` and update the
%global values near the top of that file to include the appropriate values
matching the dependencies. Make sure to also update the Node.js versions too!
Note that if libuv is updated, you need to ensure that the libuv in each
buildroot is of a sufficient version. If not, you may need to update that
package first and submit a buildroot override.
Update the RPM spec %changelog appropriately.
## (Preferred) Perform a scratch-build on at least one architecture
```
fedpkg scratch-build [--arches x86_64] --srpm
```
Verify that it built successfully.
## Push the changes up to Fedora
```
fedpkg commit -cs
fedpkg push
popd
```
## (Optional) Build for Fedora releases
If this major version is the default for one or more Fedora releases, build it
for them. (Note: this step will go away in the future, once module default
streams are available in the non-modular buildroot.)
In the case of Node.js 12.x, this is the default version for Fedora 31 and 32.
```
pushd nodejs-fedora
fedpkg switch-branch [master|31]
git merge $NODEJS_MAJOR
fedpkg push
fedpkg build
popd
```
## Build module stream
```
pushd nodejs-fedora-module
fedpkg switch-branch $NODEJS_MAJOR
```
If the module has changed any package dependencies (such as added a dep on a
new shared library), you may need to modify nodejs.yaml here. If not, you can
simply run:
```
git commit --allow-empty -sm "Update to $NODEJS_VERSION"
fedpkg push
fedpkg module-build
popd
```
## Submit built packages to Bodhi
Follow the usual processes for stable/branched releases to submit builds for
testing.

37
macros.nodejs Normal file
View File

@ -0,0 +1,37 @@
# nodejs binary
%__nodejs %{_bindir}/node
# nodejs library directory
%nodejs_sitelib %{_prefix}/lib/node_modules
#arch specific library directory
#for future-proofing only; we don't do multilib
%nodejs_sitearch %{nodejs_sitelib}
# currently installed nodejs version
%nodejs_version %(%{__nodejs} -v | sed s/v//)
# symlink dependencies so `npm link` works
# this should be run in every module's %%install section
# pass --check to work in the current directory instead of the buildroot
# pass --no-devdeps to ignore devDependencies when --check is used
%nodejs_symlink_deps %{_rpmconfigdir}/nodejs-symlink-deps %{nodejs_sitelib}
# patch package.json to fix a dependency
# see `man npm-json` for details on writing dependencies for package.json files
# e.g. `%%nodejs_fixdep frobber` makes any version of frobber do
# `%%nodejs_fixdep frobber '>1.0'` requires frobber > 1.0
# `%%nodejs_fixdep -r frobber removes the frobber dep
%nodejs_fixdep %{_rpmconfigdir}/nodejs-fixdep
# patch package.json to set the package version
# e.g. `%%nodejs_setversion 1.2.3`
%nodejs_setversion %{_rpmconfigdir}/nodejs-setversion
# macro to filter unwanted provides from Node.js binary native modules
%nodejs_default_filter %{expand: \
%global __provides_exclude_from ^%{nodejs_sitearch}/.*\\.node$
}
# no-op macro to allow spec compatibility with EPEL
%nodejs_find_provides_and_requires %{nil}

3
multiver_modules Normal file
View File

@ -0,0 +1,3 @@
uglify-js
inherits
nan

117
nodejs-fixdep Executable file
View File

@ -0,0 +1,117 @@
#!/usr/bin/python3
"""Modify a dependency listed in a package.json file"""
# Copyright 2013 T.C. Hollingsworth <tchollingsworth@gmail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
import json
import optparse
import os
import re
import shutil
import sys
RE_VERSION = re.compile(r'\s*v?([<>=~^]{0,2})\s*([0-9][0-9\.\-]*)\s*')
p = optparse.OptionParser(
description='Modifies dependency entries in package.json files')
p.add_option('-r', '--remove', action='store_true')
p.add_option('-m', '--move', action='store_true')
p.add_option('--dev', action='store_const', const='devDependencies',
dest='deptype', help='affect devDependencies')
p.add_option('--optional', action='store_const', const='optionalDependencies',
dest='deptype', help='affect optionalDependencies')
p.add_option('--caret', action='store_true',
help='convert all or specified dependencies to use the caret operator')
options, args = p.parse_args()
if not os.path.exists('package.json~'):
shutil.copy2('package.json', 'package.json~')
md = json.load(open('package.json'))
deptype = options.deptype if options.deptype is not None else 'dependencies'
if deptype not in md:
md[deptype] = {}
# convert alternate JSON dependency representations to a dictionary
if not options.caret and not isinstance(md[deptype], dict):
if isinstance(md[deptype], list):
deps = md[deptype]
md[deptype] = {}
for dep in deps:
md[deptype][dep] = '*'
elif isinstance(md[deptype], str):
md[deptype] = { md[deptype] : '*' }
if options.remove:
dep = args[0]
del md[deptype][dep]
elif options.move:
dep = args[0]
ver = None
for fromtype in ['dependencies', 'optionalDependencies', 'devDependencies']:
if fromtype in md:
if isinstance(md[fromtype], dict) and dep in md[fromtype]:
ver = md[fromtype][dep]
del md[fromtype][dep]
elif isinstance(md[fromtype], list) and md[fromtype].count(dep) > 0:
ver = '*'
md[fromtype].remove(dep)
elif isinstance(md[fromtype], str) and md[fromtype] == dep:
ver = '*'
del md[fromtype]
if ver != None:
md[deptype][dep] = ver
elif options.caret:
if not isinstance(md[deptype], dict):
sys.stderr.write('All dependencies are unversioned. Unable to apply ' +
'caret operator.\n')
sys.exit(2)
deps = args if len(args) > 0 else md[deptype].keys()
for dep in deps:
if md[deptype][dep][0] == '^':
continue
elif md[deptype][dep][0] in ('~','0','1','2','3','4','5','6','7','8','9'):
ver = re.match(RE_VERSION, md[deptype][dep]).group(2)
md[deptype][dep] = '^' + ver
else:
sys.stderr.write('Attempted to convert non-numeric or tilde ' +
'dependency to caret. This is not permitted.\n')
sys.exit(1)
else:
dep = args[0]
if len(args) > 1:
ver = args[1]
else:
ver = '*'
md[deptype][dep] = ver
fh = open('package.json', 'w')
data = json.JSONEncoder(indent=4).encode(md)
fh.write(data)
fh.close()

View File

@ -1,15 +1,28 @@
%global macrosdir %(d=%{_rpmconfigdir}/macros.d; [ -d $d ] || d=%{_sysconfdir}/rpm; echo $d)
Name: nodejs-packaging
Version: 25
Version: 2020.09
Release: 1%{?dist}
Summary: RPM Macros and Utilities for Node.js Packaging
BuildArch: noarch
License: MIT
URL: https://fedoraproject.org/wiki/Node.js/Packagers
Source0: https://releases.pagure.org/%{name}/%{name}-fedora-%{version}.tar.xz
ExclusiveArch: %{nodejs_arches} noarch
Source0001: LICENSE
Source0002: README.md
Source0003: macros.nodejs
Source0004: multiver_modules
Source0005: nodejs-fixdep
Source0006: nodejs-setversion
Source0007: nodejs-symlink-deps
Source0008: nodejs.attr
Source0009: nodejs.prov
Source0010: nodejs.req
# Created with `tar cfz test.tar.gz test`
Source0101: test.tar.gz
BuildRequires: python3
Requires: redhat-rpm-config
@ -20,7 +33,10 @@ Node.js modules and applications in RPM-based distributions.
%prep
%autosetup -p 1 -n %{name}-fedora-%{version}
pushd %{_topdir}/BUILD
cp -da %{_sourcedir}/* .
tar xvf test.tar.gz
popd
%build
@ -51,6 +67,9 @@ install -Dpm0644 multiver_modules %{buildroot}%{_datadir}/node/multiver_modules
%changelog
* Fri Sep 18 2020 Stephen Gallagher <sgallagh@redhat.com> - 2020.09-1
- Move to dist-git as the upstream
* Wed Sep 02 2020 Stephen Gallagher <sgallagh@redhat.com> - 25-1
- Fix incorrect bundled library detection for Requires

43
nodejs-setversion Executable file
View File

@ -0,0 +1,43 @@
#!/usr/bin/python3
"""Set a package version in a package.json file"""
# Copyright 2018 Tom Hughes <tom@compton.nu>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
import json
import os
import shutil
import sys
if not os.path.exists('package.json~'):
shutil.copy2('package.json', 'package.json~')
md = json.load(open('package.json'))
if 'version' in md and sys.argv[1] != md['version']:
raise RuntimeError('Version is already set to {0}'.format(md['version']))
else:
md['version'] = sys.argv[1]
fh = open('package.json', 'w')
data = json.JSONEncoder(indent=4).encode(md)
fh.write(data)
fh.close()

141
nodejs-symlink-deps Executable file
View File

@ -0,0 +1,141 @@
#!/usr/bin/python3
"""Symlink a node module's dependencies into the node_modules directory so users
can `npm link` RPM-installed modules into their personal projects."""
# Copyright 2012, 2013 T.C. Hollingsworth <tchollingsworth@gmail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
import json
import os
import re
import shutil
import sys
def symlink(source, dest):
try:
os.symlink(source, dest)
except OSError:
if os.path.islink(dest) and os.path.realpath(dest) == os.path.normpath(source):
sys.stderr.write("""
WARNING: the symlink for dependency "{0}" already exists
This could mean that the dependency exists in both devDependencies and
dependencies, which may cause trouble for people using this module with npm.
Please report this to upstream. For more information, see:
<https://github.com/tchollingsworth/nodejs-packaging/pull/1>
""".format(dest))
elif '--force' in sys.argv:
if os.path.isdir(dest):
shutil.rmtree(dest)
else:
os.unlink(dest)
os.symlink(source, dest)
else:
sys.stderr.write("""
ERROR: the path for dependency "{0}" already exists
This could mean that bundled modules are being installed. Bundled libraries are
forbidden in Fedora. For more information, see:
<https://fedoraproject.org/wiki/Packaging:No_Bundled_Libraries>
It is generally reccomended to remove the entire "node_modules" directory in
%prep when it exists. For more information, see:
<https://fedoraproject.org/wiki/Packaging:Node.js#Removing_bundled_modules>
If you have obtained permission from the Fedora Packaging Committee to bundle
libraries, please use `%nodejs_fixdep -r` in %prep to remove the dependency on
the bundled module. This will prevent an unnecessary dependency on the system
version of the module and eliminate this error.
""".format(dest))
sys.exit(1)
def symlink_deps(deps, check):
if isinstance(deps, dict):
#read in the list of mutiple-versioned packages
mvpkgs = open('/usr/share/node/multiver_modules').read().split('\n')
for dep, ver in deps.items():
if dep in mvpkgs and ver != '' and ver != '*' and ver != 'latest':
depver = re.sub('^ *(~|\^|=|>=|<=) *', '', ver).split('.')[0]
target = os.path.join(sitelib, '{0}@{1}'.format(dep, depver))
else:
target = os.path.join(sitelib, dep)
if not check or os.path.exists(target):
symlink(target, dep)
elif isinstance(deps, list):
for dep in deps:
target = os.path.join(sitelib, dep)
if not check or os.path.exists(target):
symlink(target, dep)
elif isinstance(deps, str):
target = os.path.join(sitelib, deps)
if not check or os.path.exists(target):
symlink(target, deps)
else:
raise TypeError("Invalid package.json: dependencies weren't a recognized type")
#the %nodejs_symlink_deps macro passes %nodejs_sitelib as the first argument
sitelib = sys.argv[1]
if '--check' in sys.argv or '--build' in sys.argv:
check = True
modules = [os.getcwd()]
else:
check = False
br_sitelib = os.path.join(os.environ['RPM_BUILD_ROOT'], sitelib.lstrip('/'))
modules = [os.path.join(br_sitelib, module) for module in os.listdir(br_sitelib)]
if '--optional' in sys.argv:
optional = True
else:
optional = False
for path in modules:
os.chdir(path)
md = json.load(open('package.json'))
if 'dependencies' in md or (check and 'devDependencies' in md) or (optional and 'optionalDependencies' in md):
try:
os.mkdir('node_modules')
except OSError:
sys.stderr.write('WARNING: node_modules already exists. Make sure you have ' +
'no bundled dependencies.\n')
os.chdir('node_modules')
if 'dependencies' in md:
symlink_deps(md['dependencies'], check)
if check and '--no-devdeps' not in sys.argv and 'devDependencies' in md:
symlink_deps(md['devDependencies'], check)
if optional and 'optionalDependencies' in md:
symlink_deps(md['optionalDependencies'], check)

4
nodejs.attr Normal file
View File

@ -0,0 +1,4 @@
%__nodejs_provides %{_rpmconfigdir}/nodejs.prov
%__nodejs_requires %{_rpmconfigdir}/nodejs.req
%__nodejs_suggests %{_rpmconfigdir}/nodejs.req --optional
%__nodejs_path ^/usr/lib(64)?/node_modules/[^/]+/package\\.json$

121
nodejs.prov Executable file
View File

@ -0,0 +1,121 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Copyright 2012 T.C. Hollingsworth <tchollingsworth@gmail.com>
# Copyright 2017 Tomas Tomecek <ttomecek@redhat.com>
# Copyright 2019 Jan Staněk <jstanek@redhat.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
"""Automatic provides generator for Node.js libraries.
Metadata taken from package.json. See `man npm-json` for details.
"""
from __future__ import print_function, with_statement
import json
import os
import sys
from itertools import chain, groupby
DEPENDENCY_TEMPLATE = "npm(%(name)s) = %(version)s"
BUNDLED_TEMPLATE = "bundled(nodejs-%(name)s) = %(version)s"
NODE_MODULES = {"node_modules", "node_modules_prod"}
class PrivatePackage(RuntimeError):
"""Private package metadata that should not be listed."""
#: Something is wrong with the ``package.json`` file
_INVALID_METADATA_FILE = (IOError, PrivatePackage, KeyError)
def format_metadata(metadata, bundled=False):
"""Format ``package.json``-like metadata into RPM dependency.
Arguments:
metadata (dict): Package metadata, presumably read from ``package.json``.
bundled (bool): Should the bundled dependency format be used?
Returns:
str: RPM dependency (i.e. ``npm(example) = 1.0.0``)
Raises:
KeyError: Expected key (i.e. ``name``, ``version``) missing in metadata.
PrivatePackage: The metadata indicate private (unlisted) package.
"""
# Skip private packages
if metadata.get("private", False):
raise PrivatePackage(metadata)
template = BUNDLED_TEMPLATE if bundled else DEPENDENCY_TEMPLATE
return template % metadata
def generate_dependencies(module_path, module_dir_set=NODE_MODULES):
"""Generate RPM dependency for a module and all it's dependencies.
Arguments:
module_path (str): Path to a module directory or it's ``package.json``
module_dir_set (set): Base names of directories to look into
for bundled dependencies.
Yields:
str: RPM dependency for the module and each of it's (public) bundled dependencies.
Raises:
ValueError: module_path is not valid module or ``package.json`` file
"""
# Determine paths to root module directory and package.json
if os.path.isdir(module_path):
root_dir = module_path
elif os.path.basename(module_path) == "package.json":
root_dir = os.path.dirname(module_path)
else: # Invalid metadata path
raise ValueError("Invalid module path '%s'" % module_path)
for dir_path, subdir_list, __ in os.walk(root_dir):
# Currently in node_modules (or similar), continue to subdirs
if os.path.basename(dir_path) in module_dir_set:
continue
# Read and format metadata
metadata_path = os.path.join(dir_path, "package.json")
bundled = dir_path != root_dir
try:
with open(metadata_path, mode="r") as metadata_file:
metadata = json.load(metadata_file)
yield format_metadata(metadata, bundled=bundled)
except _INVALID_METADATA_FILE:
pass # Ignore
# Only visit subdirectories in module_dir_set
subdir_list[:] = list(module_dir_set & set(subdir_list))
if __name__ == "__main__":
module_paths = (path.strip() for path in sys.stdin)
provides = chain.from_iterable(generate_dependencies(m) for m in module_paths)
# sort|uniq
for provide, __ in groupby(sorted(provides)):
print(provide)

707
nodejs.req Executable file
View File

@ -0,0 +1,707 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Copyright 2012, 2013 T.C. Hollingsworth <tchollingsworth@gmail.com>
# Copyright 2019 Jan Staněk <jstanek@redat.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
""" Automatic dependency generator for Node.js libraries.
Metadata parsed from package.json. See `man npm-json` for details.
"""
from __future__ import print_function, with_statement
import json
import operator
import os
import re
import sys
from collections import namedtuple
from itertools import chain
from itertools import takewhile
# Python version detection
_PY2 = sys.version_info[0] <= 2
_PY3 = sys.version_info[0] >= 3
if _PY2:
from future_builtins import map, filter
#: Name format of the requirements
REQUIREMENT_NAME_TEMPLATE = "npm({name})"
#: ``simple`` product of the NPM semver grammar.
RANGE_SPECIFIER_SIMPLE = re.compile(
r"""
(?P<operator>
<= | >= | < | > | = # primitive
| ~ | \^ # tilde/caret operators
)?
\s*(?P<version>\S+)\s* # version specifier
""",
flags=re.VERBOSE,
)
class UnsupportedVersionToken(ValueError):
"""Version specifier contains token unsupported by the parser."""
class Version(tuple):
"""Normalized RPM/NPM version.
The version has up to 3 components major, minor, patch.
Any part set to None is treated as unspecified.
::
1.2.3 == Version(1, 2, 3)
1.2 == Version(1, 2)
1 == Version(1)
* == Version()
"""
__slots__ = ()
#: Version part meaning 'Any'
#: ``xr`` in https://docs.npmjs.com/misc/semver#range-grammar
_PART_ANY = re.compile(r"^[xX*]$")
#: Numeric version part
#: ``nr`` in https://docs.npmjs.com/misc/semver#range-grammar
_PART_NUMERIC = re.compile(r"0|[1-9]\d*")
def __new__(cls, *args):
"""Create new version.
Arguments:
Version components in the order of "major", "minor", "patch".
All parts are optional::
>>> Version(1, 2, 3)
Version(1, 2, 3)
>>> Version(1)
Version(1)
>>> Version()
Version()
Returns:
New Version.
"""
if len(args) > 3:
raise ValueError("Version has maximum of 3 components")
return super(Version, cls).__new__(cls, map(int, args))
def __repr__(self):
"""Pretty debugging format."""
return "{0}({1})".format(self.__class__.__name__, ", ".join(map(str, self)))
def __str__(self):
"""RPM version format."""
return ".".join(format(part, "d") for part in self)
@property
def major(self):
"""Major version number, if any."""
return self[0] if len(self) > 0 else None
@property
def minor(self):
"""Major version number, if any."""
return self[1] if len(self) > 1 else None
@property
def patch(self):
"""Major version number, if any."""
return self[2] if len(self) > 2 else None
@property
def empty(self):
"""True if the version contains nothing but zeroes."""
return not any(self)
@classmethod
def parse(cls, version_string):
"""Parse individual version string (like ``1.2.3``) into Version.
This is the ``partial`` production in the grammar:
https://docs.npmjs.com/misc/semver#range-grammar
Examples::
>>> Version.parse("1.2.3")
Version(1, 2, 3)
>>> Version.parse("v2.x")
Version(2)
>>> Version.parse("")
Version()
Arguments:
version_string (str): The version_string to parse.
Returns:
Version: Parsed result.
"""
# Ignore leading ``v``, if any
version_string = version_string.lstrip("v")
part_list = version_string.split(".", 2)
# Use only parts up to first "Any" indicator
part_list = list(takewhile(lambda p: not cls._PART_ANY.match(p), part_list))
if not part_list:
return cls()
# Strip off and discard any pre-release or build qualifiers at the end.
# We can get away with this, because there is no sane way to represent
# these kinds of version requirements in RPM, and we generally expect
# the distro will only carry proper releases anyway.
try:
part_list[-1] = cls._PART_NUMERIC.match(part_list[-1]).group()
except AttributeError: # no match
part_list.pop()
# Extend with ``None``s at the end, if necessary
return cls(*part_list)
def incremented(self):
"""Increment the least significant part of the version::
>>> Version(1, 2, 3).incremented()
Version(1, 2, 4)
>>> Version(1, 2).incremented()
Version(1, 3)
>>> Version(1).incremented()
Version(2)
>>> Version().incremented()
Version()
Returns:
Version: New incremented Version.
"""
if len(self) == 0:
return self.__class__()
else:
args = self[:-1] + (self[-1] + 1,)
return self.__class__(*args)
class VersionBoundary(namedtuple("VersionBoundary", ("version", "operator"))):
"""Normalized version range boundary."""
__slots__ = ()
#: Ordering of primitive operators.
#: Operators not listed here are handled specially; see __compare below.
#: Convention: Lower boundary < 0, Upper boundary > 0
_OPERATOR_ORDER = {"<": 2, "<=": 1, ">=": -1, ">": -2}
def __str__(self):
"""Pretty-print the boundary"""
return "{0.operator}{0.version}".format(self)
def __compare(self, other, operator):
"""Compare two boundaries with provided operator.
Boundaries compare same as (version, operator_order) tuple.
In case the boundary operator is not listed in _OPERATOR_ORDER,
it's order is treated as 0.
Arguments:
other (VersionBoundary): The other boundary to compare with.
operator (Callable[[VersionBoundary, VersionBoundary], bool]):
Comparison operator to delegate to.
Returns:
bool: The result of the operator's comparison.
"""
ORDER = self._OPERATOR_ORDER
lhs = self.version, ORDER.get(self.operator, 0)
rhs = other.version, ORDER.get(other.operator, 0)
return operator(lhs, rhs)
def __eq__(self, other):
return self.__compare(other, operator.eq)
def __lt__(self, other):
return self.__compare(other, operator.lt)
def __le__(self, other):
return self.__compare(other, operator.le)
def __gt__(self, other):
return self.__compare(other, operator.gt)
def __ge__(self, other):
return self.__compare(other, operator.ge)
@property
def upper(self):
"""True if self is upper boundary."""
return self._OPERATOR_ORDER.get(self.operator, 0) > 0
@property
def lower(self):
"""True if self is lower boundary."""
return self._OPERATOR_ORDER.get(self.operator, 0) < 0
@classmethod
def equal(cls, version):
"""Normalize single samp:`={version}` into equivalent x-range::
>>> empty = VersionBoundary.equal(Version()); tuple(map(str, empty))
()
>>> patch = VersionBoundary.equal(Version(1, 2, 3)); tuple(map(str, patch))
('>=1.2.3', '<1.2.4')
>>> minor = VersionBoundary.equal(Version(1, 2)); tuple(map(str, minor))
('>=1.2', '<1.3')
>>> major = VersionBoundary.equal(Version(1)); tuple(map(str, major))
('>=1', '<2')
See `X-Ranges <https://docs.npmjs.com/misc/semver#x-ranges-12x-1x-12->`_
for details.
Arguments:
version (Version): The version the x-range should be equal to.
Returns:
(VersionBoundary, VersionBoundary):
Lower and upper bound of the x-range.
(): Empty tuple in case version is empty (any version matches).
"""
if version:
return (
cls(version=version, operator=">="),
cls(version=version.incremented(), operator="<"),
)
else:
return ()
@classmethod
def tilde(cls, version):
"""Normalize :samp:`~{version}` into equivalent range.
Tilde allows patch-level changes if a minor version is specified.
Allows minor-level changes if not::
>>> with_minor = VersionBoundary.tilde(Version(1, 2, 3)); tuple(map(str, with_minor))
('>=1.2.3', '<1.3')
>>> no_minor = VersionBoundary.tilde(Version(1)); tuple(map(str, no_minor))
('>=1', '<2')
Arguments:
version (Version): The version to tilde-expand.
Returns:
(VersionBoundary, VersionBoundary):
The lower and upper boundary of the tilde range.
"""
# Fail on ``~*`` or similar nonsense specifier
assert version.major is not None, "Nonsense '~*' specifier"
lower_boundary = cls(version=version, operator=">=")
if version.minor is None:
upper_boundary = cls(version=Version(version.major + 1), operator="<")
else:
upper_boundary = cls(
version=Version(version.major, version.minor + 1), operator="<"
)
return lower_boundary, upper_boundary
@classmethod
def caret(cls, version):
"""Normalize :samp:`^{version}` into equivalent range.
Caret allows changes that do not modify the left-most non-zero digit
in the ``(major, minor, patch)`` tuple.
In other words, this allows
patch and minor updates for versions 1.0.0 and above,
patch updates for versions 0.X >=0.1.0,
and no updates for versions 0.0.X::
>>> major = VersionBoundary.caret(Version(1, 2, 3)); tuple(map(str, major))
('>=1.2.3', '<2')
>>> minor = VersionBoundary.caret(Version(0, 2, 3)); tuple(map(str, minor))
('>=0.2.3', '<0.3')
>>> patch = VersionBoundary.caret(Version(0, 0, 3)); tuple(map(str, patch))
('>=0.0.3', '<0.0.4')
When parsing caret ranges, a missing patch value desugars to the number 0,
but will allow flexibility within that value,
even if the major and minor versions are both 0::
>>> rel = VersionBoundary.caret(Version(1, 2)); tuple(map(str, rel))
('>=1.2', '<2')
>>> pre = VersionBoundary.caret(Version(0, 0)); tuple(map(str, pre))
('>=0.0', '<0.1')
A missing minor and patch values will desugar to zero,
but also allow flexibility within those values,
even if the major version is zero::
>>> rel = VersionBoundary.caret(Version(1)); tuple(map(str, rel))
('>=1', '<2')
>>> pre = VersionBoundary.caret(Version(0)); tuple(map(str, pre))
('>=0', '<1')
Arguments:
version (Version): The version to range-expand.
Returns:
(VersionBoundary, VersionBoundary):
The lower and upper boundary of caret-range.
"""
# Fail on ^* or similar nonsense specifier
assert len(version) != 0, "Nonsense '^*' specifier"
lower_boundary = cls(version=version, operator=">=")
# Increment left-most non-zero part
for idx, part in enumerate(version):
if part != 0:
upper_version = Version(*(version[:idx] + (part + 1,)))
break
else: # No non-zero found; increment last specified part
upper_version = version.incremented()
upper_boundary = cls(version=upper_version, operator="<")
return lower_boundary, upper_boundary
@classmethod
def hyphen(cls, lower_version, upper_version):
"""Construct hyphen range (inclusive set)::
>>> full = VersionBoundary.hyphen(Version(1, 2, 3), Version(2, 3, 4)); tuple(map(str, full))
('>=1.2.3', '<=2.3.4')
If a partial version is provided as the first version in the inclusive range,
then the missing pieces are treated as zeroes::
>>> part = VersionBoundary.hyphen(Version(1, 2), Version(2, 3, 4)); tuple(map(str, part))
('>=1.2', '<=2.3.4')
If a partial version is provided as the second version in the inclusive range,
then all versions that start with the supplied parts of the tuple are accepted,
but nothing that would be greater than the provided tuple parts::
>>> part = VersionBoundary.hyphen(Version(1, 2, 3), Version(2, 3)); tuple(map(str, part))
('>=1.2.3', '<2.4')
>>> part = VersionBoundary.hyphen(Version(1, 2, 3), Version(2)); tuple(map(str, part))
('>=1.2.3', '<3')
Arguments:
lower_version (Version): Version on the lower range boundary.
upper_version (Version): Version on the upper range boundary.
Returns:
(VersionBoundary, VersionBoundary):
Lower and upper boundaries of the hyphen range.
"""
lower_boundary = cls(version=lower_version, operator=">=")
if len(upper_version) < 3:
upper_boundary = cls(version=upper_version.incremented(), operator="<")
else:
upper_boundary = cls(version=upper_version, operator="<=")
return lower_boundary, upper_boundary
def parse_simple_seq(specifier_string):
"""Parse all specifiers from a space-separated string::
>>> single = parse_simple_seq(">=1.2.3"); list(map(str, single))
['>=1.2.3']
>>> multi = parse_simple_seq("~1.2.0 <1.2.5"); list(map(str, multi))
['>=1.2.0', '<1.3', '<1.2.5']
This method implements the ``simple (' ' simple)*`` part of the grammar:
https://docs.npmjs.com/misc/semver#range-grammar.
Arguments:
specifier_string (str): Space-separated string of simple version specifiers.
Yields:
VersionBoundary: Parsed boundaries.
"""
# Per-operator dispatch table
# API: Callable[[Version], Iterable[VersionBoundary]]
handler = {
">": lambda v: [VersionBoundary(version=v, operator=">")],
">=": lambda v: [VersionBoundary(version=v, operator=">=")],
"<=": lambda v: [VersionBoundary(version=v, operator="<=")],
"<": lambda v: [VersionBoundary(version=v, operator="<")],
"=": VersionBoundary.equal,
"~": VersionBoundary.tilde,
"^": VersionBoundary.caret,
None: VersionBoundary.equal,
}
for match in RANGE_SPECIFIER_SIMPLE.finditer(specifier_string):
operator, version_string = match.group("operator", "version")
for boundary in handler[operator](Version.parse(version_string)):
yield boundary
def parse_range(range_string):
"""Parse full NPM version range specification::
>>> empty = parse_range(""); list(map(str, empty))
[]
>>> simple = parse_range("^1.0"); list(map(str, simple))
['>=1.0', '<2']
>>> hyphen = parse_range("1.0 - 2.0"); list(map(str, hyphen))
['>=1.0', '<2.1']
This method implements the ``range`` part of the grammar:
https://docs.npmjs.com/misc/semver#range-grammar.
Arguments:
range_string (str): The range specification to parse.
Returns:
Iterable[VersionBoundary]: Parsed boundaries.
Raises:
UnsupportedVersionToken: ``||`` is present in range_string.
"""
HYPHEN = " - "
# FIXME: rpm should be able to process OR in dependencies
# This error reporting kept for backward compatibility
if "||" in range_string:
raise UnsupportedVersionToken(range_string)
if HYPHEN in range_string:
version_pair = map(Version.parse, range_string.split(HYPHEN, 2))
return VersionBoundary.hyphen(*version_pair)
elif range_string != "":
return parse_simple_seq(range_string)
else:
return []
def unify_range(boundary_iter):
"""Calculate largest allowed continuous version range from a set of boundaries::
>>> unify_range([])
()
>>> _ = unify_range(parse_range("=1.2.3 <2")); tuple(map(str, _))
('>=1.2.3', '<1.2.4')
>>> _ = unify_range(parse_range("~1.2 <1.2.5")); tuple(map(str, _))
('>=1.2', '<1.2.5')
Arguments:
boundary_iter (Iterable[VersionBoundary]): The version boundaries to unify.
Returns:
(VersionBoundary, VersionBoundary):
Lower and upper boundary of the unified range.
"""
# Drop boundaries with empty version
boundary_iter = (
boundary for boundary in boundary_iter if not boundary.version.empty
)
# Split input sequence into upper/lower boundaries
lower_list, upper_list = [], []
for boundary in boundary_iter:
if boundary.lower:
lower_list.append(boundary)
elif boundary.upper:
upper_list.append(boundary)
else:
msg = "Unsupported boundary for unify_range: {0}".format(boundary)
raise ValueError(msg)
# Select maximum from lower boundaries and minimum from upper boundaries
intermediate = (
max(lower_list) if lower_list else None,
min(upper_list) if upper_list else None,
)
return tuple(filter(None, intermediate))
def rpm_format(requirement, version_spec="*"):
"""Format requirement as RPM boolean dependency::
>>> rpm_format("nodejs(engine)")
'nodejs(engine)'
>>> rpm_format("npm(foo)", ">=1.0.0")
'npm(foo) >= 1.0.0'
>>> rpm_format("npm(bar)", "~1.2")
'(npm(bar) >= 1.2 with npm(bar) < 1.3)'
Arguments:
requirement (str): The name of the requirement.
version_spec (str): The NPM version specification for the requirement.
Returns:
str: Formatted requirement.
"""
TEMPLATE = "{name} {boundary.operator} {boundary.version!s}"
try:
boundary_tuple = unify_range(parse_range(version_spec))
except UnsupportedVersionToken:
# FIXME: Typos and print behavior kept for backward compatibility
warning_lines = [
"WARNING: The {requirement} dependency contains an OR (||) dependency: '{version_spec}.",
"Please manually include a versioned dependency in your spec file if necessary",
]
warning = "\n".join(warning_lines).format(
requirement=requirement, version_spec=version_spec
)
print(warning, end="", file=sys.stderr)
return requirement
formatted = [
TEMPLATE.format(name=requirement, boundary=boundary)
for boundary in boundary_tuple
]
if len(formatted) > 1:
return "({0})".format(" with ".join(formatted))
elif len(formatted) == 1:
return formatted[0]
else:
return requirement
def has_only_bundled_dependencies(module_dir_path):
"""Determines if the module contains only bundled dependencies.
Dependencies are considered un-bundled when they are symlinks
pointing outside the root module's tree.
Arguments:
module_dir_path (str):
Path to the module directory (directory with ``package.json``).
Returns:
bool: True if all dependencies are bundled, False otherwise.
"""
module_root_path = os.path.abspath(module_dir_path)
dependency_root_path = os.path.join(module_root_path, "node_modules")
try:
dependency_path_iter = (
os.path.join(dependency_root_path, basename)
for basename in os.listdir(dependency_root_path)
)
bundled_dependency_iter = (
os.path.realpath(path)
for path in dependency_path_iter
if not os.path.islink(path) or path.startswith(module_root_path)
)
return any(bundled_dependency_iter)
except OSError: # node_modules does not exist
return False
def extract_dependencies(metadata_path, optional=False):
"""Extract all dependencies in RPM format from package metadata.
Arguments:
metadata_path (str): Path to package metadata (``package.json``).
optional (bool):
If True, extract ``optionalDependencies``
instead of ``dependencies``.
Yields:
RPM-formatted dependencies.
Raises:
TypeError: Invalid dependency data type.
"""
if has_only_bundled_dependencies(os.path.dirname(metadata_path)):
return # skip
# Read metadata
try:
with open(metadata_path, mode="r") as metadata_file:
metadata = json.load(metadata_file)
except OSError: # Invalid metadata file
return # skip
# Report required NodeJS version with required dependencies
if not optional:
try:
yield rpm_format("nodejs(engine)", metadata["engines"]["node"])
except KeyError: # NodeJS engine version unspecified
yield rpm_format("nodejs(engine)")
# Report listed dependencies
kind = "optionalDependencies" if optional else "dependencies"
container = metadata.get(kind, {})
if isinstance(container, dict):
for name, version_spec in container.items():
yield rpm_format(REQUIREMENT_NAME_TEMPLATE.format(name=name), version_spec)
elif isinstance(container, list):
for name in container:
yield rpm_format(REQUIREMENT_NAME_TEMPLATE.format(name=name))
elif isinstance(container, str):
yield rpm_format(REQUIREMENT_NAME_TEMPLATE.format(name=name))
else:
raise TypeError("invalid package.json: dependencies not a valid type")
if __name__ == "__main__":
nested = (
extract_dependencies(path.strip(), optional="--optional" in sys.argv)
for path in sys.stdin
)
flat = chain.from_iterable(nested)
# Ignore parentheses around the requirements when sorting
ordered = sorted(flat, key=lambda s: s.strip("()"))
print(*ordered, sep="\n")

View File

@ -1 +1 @@
SHA512 (nodejs-packaging-fedora-25.tar.xz) = ba82999a2ac41114f2925be7a8fe9602e9ac17474b4c4b29de888f2eec9f9ca4ec7fad0e830f763426e1c5026b212bd1d2ddc1d19dbad74ac13fb77d36f85b80
SHA512 (test.tar.gz) = dfbda67b8741f1ca36bf63b2e842f81ba07381b3d92e75fa7e29f8e456543b4cae55e95785902f31a14ae4d0b7e89161ba04c0c10c2fff617b4ae9607c91e599

4
test/bundled/node_modules/test100/package.json generated vendored Normal file
View File

@ -0,0 +1,4 @@
{
"name": "test100",
"version": "1.3.5"
}

4
test/bundled/node_modules/test101/package.json generated vendored Normal file
View File

@ -0,0 +1,4 @@
{
"name": "test101",
"version": "2.1.4"
}

View File

View File

@ -0,0 +1,3 @@
bundled(nodejs-test100) = 1.3.5
bundled(nodejs-test101) = 2.1.4
npm(test) = 4.5.6

View File

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,11 @@
{
"name": "test",
"version": "4.5.6",
"engines": {
"node": ">=6 <10"
},
"dependencies": {
"test100": "^1.2.3",
"test101": ">=2.1"
}
}

18
test/run Executable file
View File

@ -0,0 +1,18 @@
#!/bin/sh
ln -sf nodejs.req nodejs_req.py
"$(command -v python2 || echo :)" -m doctest nodejs_req.py || exit 1
"$(command -v python3 || echo :)" -m doctest nodejs_req.py || exit 1
for test in unbundled bundled
do
sed -e "s|//.*$||" < test/$test/package.json.in > test/$test/package.json
echo test/$test/package.json | ./nodejs.prov test/$test/package.json > test/$test/nodejs.prov.out 2> test/$test/nodejs.prov.err
diff -uw test/$test/nodejs.prov.err.exp test/$test/nodejs.prov.err || exit 1
diff -uw test/$test/nodejs.prov.out.exp test/$test/nodejs.prov.out || exit 1
echo test/$test/package.json | ./nodejs.req test/$test/package.json > test/$test/nodejs.req.out 2> test/$test/nodejs.req.err
diff -uw test/$test/nodejs.req.err.exp test/$test/nodejs.req.err || exit 1
diff -uw test/$test/nodejs.req.out.exp test/$test/nodejs.req.out || exit 1
done

View File

View File

@ -0,0 +1 @@
npm(test) = 4.5.6

View File

@ -0,0 +1,2 @@
WARNING: The npm(test900) dependency contains an OR (||) dependency: '^1.2 || ^2.2.
Please manually include a versioned dependency in your spec file if necessary

View File

@ -0,0 +1,74 @@
(nodejs(engine) >= 6 with nodejs(engine) < 10)
(npm(test100) >= 1 with npm(test100) < 2)
(npm(test101) >= 1 with npm(test101) < 2)
(npm(test102) >= 1 with npm(test102) < 2)
(npm(test103) >= 1 with npm(test103) < 2)
(npm(test104) >= 1.2 with npm(test104) < 1.3)
(npm(test105) >= 1.2 with npm(test105) < 1.3)
(npm(test106) >= 1.2 with npm(test106) < 1.3)
(npm(test107) >= 1.2 with npm(test107) < 1.3)
(npm(test108) >= 1.2.3 with npm(test108) < 1.2.4)
(npm(test109) >= 1.2.3 with npm(test109) < 1.2.4)
(npm(test110) >= 1.2.3 with npm(test110) < 1.2.4)
(npm(test111) >= 1.2.3 with npm(test111) < 1.2.4)
npm(test200) > 1
npm(test201) > 1.2
npm(test202) > 1.2.3
npm(test203) >= 1
npm(test204) >= 1.2
npm(test205) >= 1.2.3
npm(test206) < 2
npm(test207) < 2.3
npm(test208) < 2.3.4
npm(test209) <= 2
npm(test210) <= 2.3
npm(test211) <= 2.3.4
(npm(test300) > 1 with npm(test300) < 2)
(npm(test301) > 1.2 with npm(test301) < 2.3)
(npm(test302) > 1.2.3 with npm(test302) < 2.3.4)
(npm(test303) >= 1 with npm(test303) <= 2)
(npm(test304) >= 1.2 with npm(test304) <= 2.3)
(npm(test305) >= 1.2.3 with npm(test305) <= 2.3.4)
(npm(test306) > 1 with npm(test306) < 2)
(npm(test307) > 1.2 with npm(test307) < 2.3)
(npm(test308) > 1.2.3 with npm(test308) < 2.3.4)
(npm(test309) >= 1 with npm(test309) <= 2)
(npm(test310) >= 1.2 with npm(test310) <= 2.3)
(npm(test311) >= 1.2.3 with npm(test311) <= 2.3.4)
(npm(test400) >= 1.2.3 with npm(test400) <= 2.3.4)
(npm(test401) >= 1.2.3 with npm(test401) < 2.4)
(npm(test402) >= 1.2.3 with npm(test402) < 3)
(npm(test403) >= 1.2 with npm(test403) <= 2.3.4)
(npm(test404) >= 1 with npm(test404) <= 2.3.4)
(npm(test405) >= 1.2 with npm(test405) < 2.4)
(npm(test406) >= 1.2 with npm(test406) < 3)
(npm(test407) >= 1 with npm(test407) < 2.4)
(npm(test408) >= 1 with npm(test408) < 3)
(npm(test500) >= 1.2 with npm(test500) < 1.3)
(npm(test501) >= 1.2 with npm(test501) < 1.3)
(npm(test502) >= 1 with npm(test502) < 2)
(npm(test503) >= 1 with npm(test503) < 2)
npm(test504)
npm(test505)
(npm(test600) >= 1.2.3 with npm(test600) < 1.3)
(npm(test601) >= 1.2 with npm(test601) < 1.3)
(npm(test602) >= 1.2 with npm(test602) < 1.3)
(npm(test603) >= 1 with npm(test603) < 2)
(npm(test604) >= 1 with npm(test604) < 2)
(npm(test700) >= 1.2.3 with npm(test700) < 2)
(npm(test701) >= 0.2.3 with npm(test701) < 0.3)
(npm(test702) >= 0.0.3 with npm(test702) < 0.0.4)
(npm(test703) >= 1.2 with npm(test703) < 2)
(npm(test704) >= 1.2 with npm(test704) < 2)
(npm(test705) >= 0.1 with npm(test705) < 0.2)
(npm(test706) >= 0.1 with npm(test706) < 0.2)
(npm(test707) >= 1 with npm(test707) < 2)
(npm(test708) >= 1 with npm(test708) < 2)
npm(test709) < 0.1
npm(test710) < 0.1
npm(test711) < 1
npm(test712) < 1
npm(test750) >= 0.10
(npm(test751) >= 0.10 with npm(test751) <= 6)
(npm(test800) > 1.2 with npm(test800) < 1.9)
npm(test900)

View File

@ -0,0 +1,108 @@
{
"name": "test",
"version": "4.5.6",
"engines": {
"node": ">=6 <10"
},
"dependencies": {
// Single version
"test100": "1",
"test101": "=1",
"test102": "v1",
"test103": "=v1",
"test104": "1.2",
"test105": "=1.2",
"test106": "v1.2",
"test107": "=v1.2",
"test108": "1.2.3",
"test109": "=1.2.3",
"test110": "v1.2.3",
"test111": "=v1.2.3",
// Ranges with one comparator
"test200": ">1",
"test201": ">1.2",
"test202": ">1.2.3",
"test203": ">=1",
"test204": ">=1.2",
"test205": ">=1.2.3",
"test206": "<2",
"test207": "<2.3",
"test208": "<2.3.4",
"test209": "<=2",
"test210": "<=2.3",
"test211": "<=2.3.4",
// Ranges with two comparators
"test300": ">1 <2",
"test301": ">1.2 <2.3",
"test302": ">1.2.3 <2.3.4",
"test303": ">=1 <=2",
"test304": ">=1.2 <=2.3",
"test305": ">=1.2.3 <=2.3.4",
"test306": "<2 >1",
"test307": "<2.3 >1.2",
"test308": "<2.3.4 >1.2.3",
"test309": "<=2 >=1",
"test310": "<=2.3 >=1.2",
"test311": "<=2.3.4 >=1.2.3",
// Hyphen ranges
"test400": "1.2.3 - 2.3.4",
"test401": "1.2.3 - 2.3",
"test402": "1.2.3 - 2",
"test403": "1.2 - 2.3.4",
"test404": "1 - 2.3.4",
"test405": "1.2 - 2.3",
"test406": "1.2 - 2",
"test407": "1 - 2.3",
"test408": "1 - 2",
// X-Ranges
"test500": "1.2.x",
"test501": "1.2.*",
"test502": "1.x",
"test503": "1.*",
"test504": "*",
"test505": "",
// Tilde ranges
"test600": "~1.2.3",
"test601": "~1.2.x",
"test602": "~1.2",
"test603": "~1.x",
"test604": "~1",
// Caret ranges
"test700": "^1.2.3",
"test701": "^0.2.3",
"test702": "^0.0.3",
"test703": "^1.2.x",
"test704": "^1.2",
"test705": "^0.1.x",
"test706": "^0.1",
"test707": "^1.x",
"test708": "^1",
"test709": "^0.0.x",
"test710": "^0.0",
"test711": "^0.x",
"test712": "^0",
// Space after the operator
// (the grammar does not permit this, but it is accepted in practice)
"test750": ">= 0.10",
"test751": ">= 0.10 <= 6",
// More than two comparators in a set
// (no reason for this to ever appear, but it is permitted)
"test800": ">1.2 <2.0 <1.9",
// The following cases are not implemented currently...
// Multiple comparator sets separated by ||
"test900": "^1.2 || ^2.2"
// The whole pre-release stuff: https://docs.npmjs.com/misc/semver//prerelease-tags
// which is not even enumerated here because it is so complex.
}
}