diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index f96994ea8d..7449f90183 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -85,7 +85,7 @@ template. Your help and contribution make ScanCode docs better, we love hearing
The ScanCode documentation is hosted at `scancode-toolkit.readthedocs.io `_.
-If you want to contribute to Scancode Documentation, you'll find `this guide here https://scancode-toolkit.readthedocs.io/en/latest/getting-started/contribute/contributing-docs.html`_ helpful.
+If you want to contribute to Scancode Documentation, you'll find `this guide here `_ helpful.
Development
===========
@@ -123,7 +123,7 @@ To set up ScanCode for local development:
git checkout -b name-of-your-bugfix-or-feature
-4. Check out the Contributing to Code Development `documentation `_, as it contains more in-depth guide for contributing code and documentation.
+4. Check out the Contributing to Code Development `documentation `_, as it contains more in-depth guide for contributing code and documentation.
5. To configure your local environment for development, locate to the main
directory of the local repository, and run the configure script.
diff --git a/requirements.txt b/requirements.txt
index 3b59481345..b713c93152 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -42,6 +42,7 @@ jsonstreams==0.6.0
keyring==23.7.0
license-expression==30.4.4
lxml==6.0.2
+luaparser==4.0.0
MarkupSafe==3.0.3
more-itertools==10.8.0
multiregex==2.0.3
diff --git a/setup.cfg b/setup.cfg
index 7c45f388fd..0a0e1946d5 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -91,6 +91,7 @@ install_requires =
jsonstreams >= 0.5.0
license_expression >= 30.4.4
lxml >= 5.4.0
+ luaparser == 4.0.0
MarkupSafe >= 2.1.2
multiregex >= 2.0.3
normality <= 2.6.1
diff --git a/src/packagedcode/__init__.py b/src/packagedcode/__init__.py
index d3c48b6e25..67320a3057 100644
--- a/src/packagedcode/__init__.py
+++ b/src/packagedcode/__init__.py
@@ -35,6 +35,7 @@
from packagedcode import pubspec
from packagedcode import pypi
from packagedcode import readme
+from packagedcode import rockspec
from packagedcode import rpm
from packagedcode import rubygems
from packagedcode import swift
@@ -202,6 +203,8 @@
rubygems.GemspecInExtractedGemHandler,
rubygems.GemspecHandler,
+ rockspec.RockspecHandler,
+
swift.SwiftManifestJsonHandler,
swift.SwiftPackageResolvedHandler,
swift.SwiftShowDependenciesDepLockHandler,
diff --git a/src/packagedcode/rockspec.py b/src/packagedcode/rockspec.py
new file mode 100644
index 0000000000..121e05fad9
--- /dev/null
+++ b/src/packagedcode/rockspec.py
@@ -0,0 +1,617 @@
+import os
+import sys
+import logging
+import re
+import traceback
+from packageurl import PackageURL
+
+from luaparser import ast
+from packagedcode import models
+
+# Debug configuration - set via environment variables
+SCANCODE_DEBUG_PACKAGE = os.environ.get('SCANCODE_DEBUG_PACKAGE', False)
+TRACE = SCANCODE_DEBUG_PACKAGE
+
+
+def logger_debug(*args):
+ """Dummy function that does nothing by default."""
+ pass
+
+
+logger = logging.getLogger(__name__)
+
+# Configure logging when debug is enabled
+if TRACE:
+ logging.basicConfig(stream=sys.stdout)
+ logger.setLevel(logging.DEBUG)
+
+ def logger_debug(*args):
+ """Redefine to actually log debug messages."""
+ return logger.debug(
+ ' '.join(isinstance(a, str) and a or repr(a) for a in args)
+ )
+
+
+class RockspecHandler(models.DatafileHandler):
+ datasource_id = 'luarocks_rockspec'
+ path_patterns = ('*.rockspec',)
+ default_package_type = 'luarocks'
+ default_primary_language = 'Lua'
+ description = 'LuaRocks rockspec file'
+ documentation_url = 'https://github.com/luarocks/luarocks/blob/main/docs/rockspec_format.md'
+
+ @classmethod
+ def parse(cls, location, package_only=False):
+ """
+ Parse a rockspec file and return a PackageData object.
+ """
+ parser = RockspecParser(location)
+ parsed_data = parser.parse()
+
+ # mandatory fields in rockspec files
+ name = parsed_data.get('package')
+ version = parsed_data.get('version')
+ vcs_url = parsed_data.get('vcs_url')
+
+ # Extract optional fields
+ description = parsed_data.get('description')
+ homepage_url = parsed_data.get('homepage_url')
+ extracted_license_statement = parsed_data.get('license')
+
+ parsed_dependencies = parsed_data.get('dependencies') or []
+
+ if parsed_dependencies:
+ dependencies = cls._build_dependent_packages(parsed_dependencies)
+ else:
+ dependencies = []
+
+ extra_data = cls._build_extra_data(parsed_data)
+
+ package_data = dict(
+ datasource_id=cls.datasource_id,
+ type=cls.default_package_type,
+ name=name,
+ version=version,
+ primary_language=cls.default_primary_language,
+ description=description,
+ homepage_url=homepage_url,
+ vcs_url=vcs_url,
+ extracted_license_statement=extracted_license_statement,
+ dependencies=dependencies,
+ extra_data=extra_data,
+ )
+
+ yield models.PackageData.from_data(package_data, package_only)
+
+ @classmethod
+ def _build_dependent_packages(cls, parsed_dependencies):
+ """
+ Convert parsed dependency dicts into DependentPackage objects.
+
+ Args:
+ parsed_dependencies: List of dicts with 'name' and 'version_spec' keys
+ (already parsed by RockspecParser)
+
+ Returns:
+ List of DependentPackage objects
+ """
+ dependencies = []
+
+ for dep_dict in parsed_dependencies:
+ dep_obj = cls._create_dependent_package(dep_dict)
+ dependencies.append(dep_obj)
+
+ return dependencies
+
+ @classmethod
+ def _create_dependent_package(cls, dep_components):
+ """
+ Create a DependentPackage object from parsed dependency components.
+
+ Args:
+ dep_components: Dict with 'name', 'version_number', and 'version_spec'
+ (already parsed by RockspecParser.parse_dependency)
+
+ Returns:
+ DependentPackage object
+ """
+ name = dep_components.get('name')
+ version_number = dep_components.get('version_number')
+ version_spec = dep_components.get('version_spec')
+
+ purl_str = cls._create_purl_string(name, version_number)
+ # Determine if pinned (exact version with == operator)
+ is_pinned = bool(version_spec and '==' in str(version_spec))
+
+ return models.DependentPackage(
+ purl=purl_str,
+ extracted_requirement=version_spec,
+ scope='dependencies',
+ is_runtime=True,
+ is_optional=False,
+ is_pinned=is_pinned,
+ is_direct=True,
+ )
+
+ @classmethod
+ def _build_extra_data(cls, parsed_data):
+ """
+ Build extra_data dict from optional rockspec metadata.
+
+ Args:
+ parsed_data: Dict with parsed rockspec fields
+
+ Returns:
+ Dict with extra metadata (extensible for future fields)
+ """
+ extra_data = {}
+
+ rockspec_format = parsed_data.get('rockspec_format')
+ if rockspec_format:
+ extra_data['rockspec_format'] = rockspec_format
+
+ platforms = parsed_data.get('supported_platforms')
+ if platforms:
+ extra_data['supported_platforms'] = platforms
+
+ # Future fields can be added here
+ # e.g., build_backend, build_requires, etc.
+
+ return extra_data
+
+ @classmethod
+ def _create_purl_string(cls, package_name, package_version):
+ """
+ Create a PURL string for a luarocks package.
+
+ Args:
+ package_name: Name of the package
+ package_version: Optional version string (without operators)
+
+ Returns:
+ PURL string (e.g., "pkg:luarocks/luasocket" or "pkg:luarocks/luasocket@3.1.3")
+
+ Raises:
+ ValueError if package_name is empty
+ """
+ if not package_name:
+ raise ValueError('Package name is required for PURL creation')
+
+ purl = PackageURL(
+ type=cls.default_package_type,
+ name=package_name,
+ version=package_version
+ )
+ return purl.to_string()
+
+
+
+class ParseError:
+ """Structured error representation."""
+
+ ERROR_MANDATORY_FIELD_MISSING = 'mandatory_field_missing'
+ ERROR_PARSE_FAILED = 'parse_failed'
+ ERROR_TABLE_EXTRACTION = 'table_extraction_failed'
+
+ def __init__(self, error_type, field, message):
+ self.error_type = error_type
+ self.field = field
+ self.message = message
+
+ def __str__(self):
+ return self.message
+
+ def __repr__(self):
+ return f"ParseError({self.error_type}, {self.field}: {self.message})"
+
+
+class RockspecParser:
+ """Parse LuaRocks rockspec files using Lua AST."""
+
+ def __init__(self, rockspec_path):
+ self.rockspec_path = rockspec_path
+ self.ast_tree = None
+ self.errors = []
+
+ def parse(self):
+ """Main parsing orchestration. Reads file, parses AST, extracts all fields."""
+ try:
+ code = self._read_file()
+ self.ast_tree = self._parse_lua(code)
+
+ data = {
+ 'package': self._extract_package(),
+ 'version': self._extract_version(),
+ 'rockspec_format': self._extract_rockspec_format(),
+ 'supported_platforms': self._extract_supported_platforms(),
+ 'vcs_url': self._extract_source_url(),
+ 'description': self._extract_description(),
+ 'license': self._extract_license(),
+ 'homepage_url': self._extract_homepage(),
+ 'dependencies': self._extract_dependencies(),
+ }
+ return data
+ except Exception as e:
+ self.errors.append(ParseError(ParseError.ERROR_PARSE_FAILED, 'parse', str(e)))
+ traceback.print_exc()
+ return {}
+
+ def _read_file(self):
+ """Read rockspec file and return content."""
+ try:
+ with open(self.rockspec_path, 'r') as f:
+ return f.read()
+ except FileNotFoundError:
+ raise FileNotFoundError(f"File not found: {self.rockspec_path}")
+ except IOError as e:
+ raise IOError(f"Error reading file: {e}")
+
+ def _parse_lua(self, code):
+ """Parse Lua code to AST."""
+ try:
+ return ast.parse(code)
+ except Exception as e:
+ raise RuntimeError(f"Lua parse error: {e}")
+
+ def _find_assignment(self, var_name):
+ """Find assignment node for a variable, return (target_node, value_node)."""
+ if not self.ast_tree:
+ return None
+
+ for node in ast.walk(self.ast_tree):
+ # Skip nodes that aren't assignments
+ if not hasattr(node, 'targets') or not hasattr(node, 'values'): # type: ignore
+ continue
+
+ # Skip if no targets in this assignment
+ if not node.targets: # type: ignore
+ continue
+
+ # Check each target to find our variable
+ for idx, target in enumerate(node.targets): # type: ignore
+ # Skip if target doesn't have an id attribute
+ if not hasattr(target, 'id'):
+ continue
+
+ # Found the variable we're looking for
+ if target.id == var_name: # type: ignore
+ # Get the corresponding value (or first value if not enough values)
+ value = node.values[idx] if idx < len(node.values) else (node.values[0] if node.values else None) # type: ignore
+ return (target, value)
+
+ return None
+
+
+ def _extract_string_value(self, node):
+ """Extract string value from String node."""
+ if not node or type(node).__name__ != 'String':
+ return None
+
+ s_val = node.s if hasattr(node, 's') else None
+ if isinstance(s_val, bytes):
+ return s_val.decode('utf-8')
+ return str(s_val) if s_val else None
+
+ def _extract_table_values(self, table_node):
+ """Extract key-value pairs from Table node."""
+ result = {}
+
+ if not table_node or type(table_node).__name__ != 'Table':
+ return result
+
+ if not hasattr(table_node, 'fields'):
+ return result
+
+ try:
+ for field in table_node.fields:
+ field_type = type(field).__name__
+
+ # Only process Field type nodes because they represent key-value pairs or array entries
+ if field_type != 'Field':
+ continue
+
+ key_node = field.key if hasattr(field, 'key') else None
+ value_node = field.value if hasattr(field, 'value') else None
+
+ # Skip fields without values
+ if not value_node:
+ continue
+
+ # Extract value (works for both hash-style and array-style)
+ extracted_value = self._extract_value(value_node)
+ if extracted_value is None:
+ continue
+
+ # Hash-style field: {key = value}
+ if key_node:
+ key = self._extract_key(key_node)
+ if key is not None:
+ result[key] = extracted_value
+ # Array-style field: {value}
+ else:
+ result[len(result)] = extracted_value
+
+ except Exception as e:
+ error_msg = f'Error extracting table values: {e}'
+ self.errors.append(ParseError(ParseError.ERROR_TABLE_EXTRACTION, 'table', error_msg))
+
+ return result
+
+ def _extract_key(self, key_node):
+ """Extract key from field key node."""
+ if not key_node:
+ return None
+
+ node_type = type(key_node).__name__
+
+ if node_type == 'String':
+ return self._extract_string_value(key_node)
+ elif node_type == 'Name' or node_type == 'Id':
+ return key_node.id if hasattr(key_node, 'id') else None
+ elif node_type == 'Number':
+ n_val = key_node.n if hasattr(key_node, 'n') else None
+ return str(n_val) if n_val is not None else None
+
+ return None
+
+ def _extract_value(self, node):
+ """Extract value from any AST node."""
+ if node is None:
+ return None
+
+ node_type = type(node).__name__
+
+ # Handle each node type
+ if node_type == 'String':
+ return self._extract_string_value(node)
+
+ elif node_type == 'Number':
+ number_value = node.n if hasattr(node, 'n') else None
+ return number_value
+
+ elif node_type == 'Boolean':
+ bool_value = node.value if hasattr(node, 'value') else None
+ return bool_value
+
+ elif node_type == 'Table':
+ return self._extract_table_values(node)
+ # special concat case found in the some rockspec files in the wild
+ elif node_type == 'Concat':
+ return self._extract_concat(node)
+
+ elif node_type == 'Name':
+ var_name = node.id if hasattr(node, 'id') else None
+ if var_name:
+ return self._get_variable_value(var_name)
+
+ # Unknown node type
+ return None
+
+ def _get_variable_value(self, var_name):
+ """Look up a variable and return its value."""
+ assignment = self._find_assignment(var_name)
+ if not assignment:
+ return None
+
+ _, value = assignment
+ return self._extract_value(value)
+
+ def _extract_concat(self, concat_node):
+ """
+ Extract value from Concat node (string concatenation).
+ Recursively processes: left .. right
+ """
+ if not concat_node or type(concat_node).__name__ != 'Concat':
+ return None
+
+ left_node = concat_node.left if hasattr(concat_node, 'left') else None
+ right_node = concat_node.right if hasattr(concat_node, 'right') else None
+
+ # Recursively extract values from both sides
+ left_value = self._extract_value(left_node)
+ right_value = self._extract_value(right_node)
+
+ # Build result from available values
+ has_left = left_value is not None
+ has_right = right_value is not None
+
+ if has_left and has_right:
+ return str(left_value) + str(right_value)
+ elif has_left:
+ return str(left_value)
+ elif has_right:
+ return str(right_value)
+ else:
+ return None
+
+ def _extract_package(self):
+ """Extract package name (mandatory)."""
+ assignment = self._find_assignment('package')
+ if not assignment:
+ self.errors.append(ParseError(ParseError.ERROR_MANDATORY_FIELD_MISSING, 'package', 'Missing mandatory field: package'))
+ return None
+
+ _, value = assignment
+ result = self._extract_value(value)
+ return str(result) if result else None
+
+ def _extract_version(self):
+ """Extract version (mandatory)."""
+ assignment = self._find_assignment('version')
+ if not assignment:
+ self.errors.append(ParseError(ParseError.ERROR_MANDATORY_FIELD_MISSING, 'version', 'Missing mandatory field: version'))
+ return None
+
+ _, value = assignment
+ result = self._extract_value(value)
+ return str(result) if result else None
+
+ def _extract_rockspec_format(self):
+ """Extract rockspec_format (optional)."""
+ assignment = self._find_assignment('rockspec_format')
+ if not assignment:
+ return None
+
+ _, value = assignment
+ result = self._extract_value(value)
+ return str(result) if result else None
+
+ def _extract_supported_platforms(self):
+ """Extract supported_platforms as list (optional table)."""
+ assignment = self._find_assignment('supported_platforms')
+ if not assignment:
+ return []
+
+ _, platform_table_node = assignment
+ platform_dict = self._extract_table_values(platform_table_node)
+
+ # Sort platforms by numeric index order
+ return self._sort_by_numeric_index(platform_dict)
+
+ def _extract_source_url(self):
+ """Extract VCS URL from source table (url is mandatory)."""
+ assignment = self._find_assignment('source')
+ if not assignment:
+ self.errors.append(ParseError(ParseError.ERROR_MANDATORY_FIELD_MISSING, 'source', 'Missing mandatory field: source'))
+ return None
+
+ _, value = assignment
+ source_table = self._extract_table_values(value)
+
+ source_url = source_table.get('url')
+ if not source_url:
+ self.errors.append(ParseError(ParseError.ERROR_MANDATORY_FIELD_MISSING, 'source.url', 'Missing mandatory field: source.url'))
+ return None
+
+ return str(source_url)
+
+ def _extract_description(self):
+ """Extract description summary from description table (optional)."""
+ assignment = self._find_assignment('description')
+ if not assignment:
+ return None
+
+ _, value = assignment
+ desc_table = self._extract_table_values(value)
+
+ summary = desc_table.get('summary')
+ return str(summary) if summary else None
+
+ def _extract_license(self):
+ """Extract license from description table (optional)."""
+ assignment = self._find_assignment('description')
+ if not assignment:
+ return None
+
+ _, value = assignment
+ desc_table = self._extract_table_values(value)
+
+ license_val = desc_table.get('license')
+ return str(license_val) if license_val else None
+
+ def _extract_homepage(self):
+ """Extract homepage URL from description table (optional)."""
+ assignment = self._find_assignment('description')
+ if not assignment:
+ return None
+
+ _, value = assignment
+ desc_table = self._extract_table_values(value)
+
+ homepage = desc_table.get('homepage')
+ return str(homepage) if homepage else None
+
+ def _extract_dependencies(self):
+ """Extract dependencies as list of parsed dicts (optional table)."""
+ assignment = self._find_assignment('dependencies')
+ if not assignment:
+ return []
+
+ _, dependency_table_node = assignment
+ dependency_strings = self._extract_table_values(dependency_table_node)
+
+ if not dependency_strings:
+ return []
+
+ sorted_strings = self._sort_by_numeric_index(dependency_strings)
+
+ return [
+ parsed
+ for parsed in (self.parse_dependency(dep_string) for dep_string in sorted_strings)
+ if parsed is not None
+ ]
+
+ def _sort_by_numeric_index(self, table_dict):
+ """Sort a table dict by numeric keys and return values as strings."""
+ try:
+ # Sort by numeric key index
+ sorted_items = sorted(
+ table_dict.items(),
+ key=lambda x: self._numeric_key_value(x[0])
+ )
+ return [str(value) for _, value in sorted_items]
+ except Exception:
+ # Fallback: return values in dict order
+ return [str(v) for v in table_dict.values()]
+
+ def _numeric_key_value(self, key):
+ """Convert key to numeric value for sorting. Non-numeric keys sort to end."""
+ if isinstance(key, int):
+ return key
+ if isinstance(key, str) and key.isdigit():
+ return int(key)
+ return float('inf') # Non-numeric keys sort to the end
+
+ def parse_dependency(self, dep_string):
+ """
+ Parse a Lua dependency string into name and version spec.
+
+ Lua RockSpecs format: "package_name [operator version]"
+ Examples:
+ "inspect == 3.1.3"
+ "luasec == 1.3.1"
+ "binaryheap >= 0.4"
+ "somedep" (no version)
+
+ Returns dict with keys:
+ - name: Package name
+ - version_number: Clean version number (e.g. "3.1.3") or None
+ - version_spec: Full version specification with operator (e.g. "== 3.1.3") or None
+ - raw: Original input string
+ """
+ if not dep_string:
+ return None
+
+ dep_string = str(dep_string).strip()
+ pattern = r'([a-zA-Z0-9_-]+)\s*(?:([>=<~=]+)\s*)?(.+)?'
+ match = re.match(pattern, dep_string)
+
+ if not match:
+ return None
+
+ name = match.group(1)
+ operator = match.group(2)
+ version_raw = match.group(3)
+
+ version_number = None
+ version_spec = None
+
+ if version_raw:
+ version_raw = version_raw.strip()
+ version_match = re.search(r'([0-9][0-9.]*)', version_raw)
+ if version_match:
+ version_number = version_match.group(1)
+ if operator:
+ version_spec = operator + ' ' + version_number
+ else:
+ version_spec = version_number
+
+ return {
+ 'name': name,
+ 'version_number': version_number,
+ 'version_spec': version_spec,
+ 'raw': dep_string
+ }
+
+
+
diff --git a/tests/packagedcode/data/plugin/plugins_list_linux.txt b/tests/packagedcode/data/plugin/plugins_list_linux.txt
index eb4763d6c7..a8e9ad0de7 100755
--- a/tests/packagedcode/data/plugin/plugins_list_linux.txt
+++ b/tests/packagedcode/data/plugin/plugins_list_linux.txt
@@ -545,6 +545,13 @@ Package type: linux-distro
description: Linux OS release metadata file
path_patterns: '*etc/os-release', '*usr/lib/os-release'
--------------------------------------------
+Package type: luarocks
+ datasource_id: luarocks_rockspec
+ documentation URL: https://github.com/luarocks/luarocks/blob/main/docs/rockspec_format.md
+ primary language: Lua
+ description: LuaRocks rockspec file
+ path_patterns: '*.rockspec'
+--------------------------------------------
Package type: maven
datasource_id: build_gradle
documentation URL: None
diff --git a/tests/packagedcode/data/rockspec/test.rockspec b/tests/packagedcode/data/rockspec/test.rockspec
new file mode 100644
index 0000000000..b4fc2ef98b
--- /dev/null
+++ b/tests/packagedcode/data/rockspec/test.rockspec
@@ -0,0 +1,60 @@
+package = "lua-cjson"
+version = "2.1.0.16-1"
+
+source = {
+ url = "git+https://github.com/openresty/lua-cjson",
+ tag = "2.1.0.16",
+}
+
+description = {
+ summary = "A fast JSON encoding/parsing module",
+ detailed = [[
+ The Lua CJSON module provides JSON support for Lua. It features:
+ - Fast, standards compliant encoding/parsing routines
+ - Full support for JSON with UTF-8, including decoding surrogate pairs
+ - Optional run-time support for common exceptions to the JSON specification
+ (infinity, NaN,..)
+ - No dependencies on other libraries
+ ]],
+ homepage = "http://www.kyne.com.au/~mark/software/lua-cjson.php",
+ license = "MIT"
+}
+
+dependencies = {
+ "lua >= 5.1"
+}
+
+build = {
+ type = "builtin",
+ modules = {
+ cjson = {
+ sources = { "lua_cjson.c", "strbuf.c", "fpconv.c" },
+ defines = {
+-- LuaRocks does not support platform specific configuration for Solaris.
+-- Uncomment the line below on Solaris platforms if required.
+-- "USE_INTERNAL_ISINF"
+ }
+ },
+ ["cjson.safe"] = {
+ sources = { "lua_cjson.c", "strbuf.c", "fpconv.c" }
+ }
+ },
+ install = {
+ lua = {
+ ["cjson.util"] = "lua/cjson/util.lua"
+ },
+ bin = {
+ json2lua = "lua/json2lua.lua",
+ lua2json = "lua/lua2json.lua"
+ }
+ },
+ -- Override default build options (per platform)
+ platforms = {
+ win32 = { modules = { cjson = { defines = {
+ "DISABLE_INVALID_NUMBERS", "USE_INTERNAL_ISINF"
+ } } } }
+ },
+ copy_directories = { "tests" }
+}
+
+-- vi:ai et sw=4 ts=4:
\ No newline at end of file
diff --git a/tests/packagedcode/data/rockspec/test1.rockspec b/tests/packagedcode/data/rockspec/test1.rockspec
new file mode 100644
index 0000000000..8eef1f1a7a
--- /dev/null
+++ b/tests/packagedcode/data/rockspec/test1.rockspec
@@ -0,0 +1,530 @@
+package = "kong"
+version = "3.3.0-0"
+rockspec_format = "3.0"
+supported_platforms = {"linux", "macosx"}
+source = {
+ url = "git+https://github.com/Kong/kong.git",
+ tag = "3.3.0"
+}
+description = {
+ summary = "Kong is a scalable and customizable API Management Layer built on top of Nginx.",
+ homepage = "https://konghq.com",
+ license = "Apache 2.0"
+}
+dependencies = {
+ "inspect == 3.1.3",
+ "luasec == 1.3.1",
+ "luasocket == 3.0-rc1",
+ "penlight == 1.13.1",
+ "lua-resty-http == 0.17.1",
+ "lua-resty-jit-uuid == 0.0.7",
+ "lua-ffi-zlib == 0.5",
+ "multipart == 0.5.9",
+ "version == 1.0.1",
+ "kong-lapis == 1.8.3.1",
+ "lua-cassandra == 1.5.2",
+ "pgmoon == 1.16.0",
+ "luatz == 0.4",
+ "lua_system_constants == 0.1.4",
+ "lyaml == 6.2.8",
+ "luasyslog == 2.0.1",
+ "lua_pack == 2.0.0",
+ "binaryheap >= 0.4",
+ "luaxxhash >= 1.0",
+ "lua-protobuf == 0.5.0",
+ "lua-resty-healthcheck == 1.6.2",
+ "lua-resty-mlcache == 2.6.0",
+ "lua-messagepack == 0.5.2",
+ "lua-resty-openssl == 0.8.20",
+ "lua-resty-counter == 0.2.1",
+ "lua-resty-ipmatcher == 0.6.1",
+ "lua-resty-acme == 0.11.0",
+ "lua-resty-session == 4.0.3",
+ "lua-resty-timer-ng == 0.2.5",
+ "lpeg == 1.0.2",
+}
+build = {
+ type = "builtin",
+ modules = {
+ ["kong"] = "kong/init.lua",
+ ["kong.meta"] = "kong/meta.lua",
+ ["kong.cache"] = "kong/cache/init.lua",
+ ["kong.cache.warmup"] = "kong/cache/warmup.lua",
+ ["kong.cache.marshall"] = "kong/cache/marshall.lua",
+ ["kong.global"] = "kong/global.lua",
+ ["kong.router"] = "kong/router/init.lua",
+ ["kong.router.traditional"] = "kong/router/traditional.lua",
+ ["kong.router.compat"] = "kong/router/compat.lua",
+ ["kong.router.expressions"] = "kong/router/expressions.lua",
+ ["kong.router.atc"] = "kong/router/atc.lua",
+ ["kong.router.utils"] = "kong/router/utils.lua",
+ ["kong.reports"] = "kong/reports.lua",
+ ["kong.constants"] = "kong/constants.lua",
+ ["kong.concurrency"] = "kong/concurrency.lua",
+ ["kong.deprecation"] = "kong/deprecation.lua",
+ ["kong.globalpatches"] = "kong/globalpatches.lua",
+ ["kong.error_handlers"] = "kong/error_handlers.lua",
+ ["kong.hooks"] = "kong/hooks.lua",
+
+ ["kong.conf_loader"] = "kong/conf_loader/init.lua",
+ ["kong.conf_loader.listeners"] = "kong/conf_loader/listeners.lua",
+
+ ["kong.clustering"] = "kong/clustering/init.lua",
+ ["kong.clustering.data_plane"] = "kong/clustering/data_plane.lua",
+ ["kong.clustering.control_plane"] = "kong/clustering/control_plane.lua",
+ ["kong.clustering.utils"] = "kong/clustering/utils.lua",
+ ["kong.clustering.events"] = "kong/clustering/events.lua",
+ ["kong.clustering.compat"] = "kong/clustering/compat/init.lua",
+ ["kong.clustering.compat.version"] = "kong/clustering/compat/version.lua",
+ ["kong.clustering.compat.removed_fields"] = "kong/clustering/compat/removed_fields.lua",
+ ["kong.clustering.compat.checkers"] = "kong/clustering/compat/checkers.lua",
+ ["kong.clustering.config_helper"] = "kong/clustering/config_helper.lua",
+ ["kong.clustering.tls"] = "kong/clustering/tls.lua",
+
+ ["kong.cluster_events"] = "kong/cluster_events/init.lua",
+ ["kong.cluster_events.strategies.cassandra"] = "kong/cluster_events/strategies/cassandra.lua",
+ ["kong.cluster_events.strategies.postgres"] = "kong/cluster_events/strategies/postgres.lua",
+ ["kong.cluster_events.strategies.off"] = "kong/cluster_events/strategies/off.lua",
+
+ ["kong.templates.nginx"] = "kong/templates/nginx.lua",
+ ["kong.templates.nginx_kong"] = "kong/templates/nginx_kong.lua",
+ ["kong.templates.nginx_kong_stream"] = "kong/templates/nginx_kong_stream.lua",
+ ["kong.templates.kong_defaults"] = "kong/templates/kong_defaults.lua",
+ ["kong.templates.kong_yml"] = "kong/templates/kong_yml.lua",
+
+ ["kong.resty.dns.client"] = "kong/resty/dns/client.lua",
+ ["kong.resty.dns.utils"] = "kong/resty/dns/utils.lua",
+ ["kong.resty.ctx"] = "kong/resty/ctx.lua",
+
+ ["kong.cmd"] = "kong/cmd/init.lua",
+ ["kong.cmd.roar"] = "kong/cmd/roar.lua",
+ ["kong.cmd.stop"] = "kong/cmd/stop.lua",
+ ["kong.cmd.quit"] = "kong/cmd/quit.lua",
+ ["kong.cmd.start"] = "kong/cmd/start.lua",
+ ["kong.cmd.check"] = "kong/cmd/check.lua",
+ ["kong.cmd.config"] = "kong/cmd/config.lua",
+ ["kong.cmd.reload"] = "kong/cmd/reload.lua",
+ ["kong.cmd.restart"] = "kong/cmd/restart.lua",
+ ["kong.cmd.prepare"] = "kong/cmd/prepare.lua",
+ ["kong.cmd.migrations"] = "kong/cmd/migrations.lua",
+ ["kong.cmd.health"] = "kong/cmd/health.lua",
+ ["kong.cmd.vault"] = "kong/cmd/vault.lua",
+ ["kong.cmd.version"] = "kong/cmd/version.lua",
+ ["kong.cmd.hybrid"] = "kong/cmd/hybrid.lua",
+ ["kong.cmd.utils.log"] = "kong/cmd/utils/log.lua",
+ ["kong.cmd.utils.kill"] = "kong/cmd/utils/kill.lua",
+ ["kong.cmd.utils.env"] = "kong/cmd/utils/env.lua",
+ ["kong.cmd.utils.migrations"] = "kong/cmd/utils/migrations.lua",
+ ["kong.cmd.utils.tty"] = "kong/cmd/utils/tty.lua",
+ ["kong.cmd.utils.nginx_signals"] = "kong/cmd/utils/nginx_signals.lua",
+ ["kong.cmd.utils.prefix_handler"] = "kong/cmd/utils/prefix_handler.lua",
+ ["kong.cmd.utils.process_secrets"] = "kong/cmd/utils/process_secrets.lua",
+
+ ["kong.api"] = "kong/api/init.lua",
+ ["kong.api.api_helpers"] = "kong/api/api_helpers.lua",
+ ["kong.api.arguments"] = "kong/api/arguments.lua",
+ ["kong.api.endpoints"] = "kong/api/endpoints.lua",
+ ["kong.api.routes.kong"] = "kong/api/routes/kong.lua",
+ ["kong.api.routes.health"] = "kong/api/routes/health.lua",
+ ["kong.api.routes.config"] = "kong/api/routes/config.lua",
+ ["kong.api.routes.consumers"] = "kong/api/routes/consumers.lua",
+ ["kong.api.routes.plugins"] = "kong/api/routes/plugins.lua",
+ ["kong.api.routes.cache"] = "kong/api/routes/cache.lua",
+ ["kong.api.routes.upstreams"] = "kong/api/routes/upstreams.lua",
+ ["kong.api.routes.targets"] = "kong/api/routes/targets.lua",
+ ["kong.api.routes.certificates"] = "kong/api/routes/certificates.lua",
+ ["kong.api.routes.snis"] = "kong/api/routes/snis.lua",
+ ["kong.api.routes.tags"] = "kong/api/routes/tags.lua",
+ ["kong.api.routes.clustering"] = "kong/api/routes/clustering.lua",
+ ["kong.api.routes.debug"] = "kong/api/routes/debug.lua",
+
+ ["kong.status"] = "kong/status/init.lua",
+ ["kong.status.ready"] = "kong/status/ready.lua",
+
+ ["kong.tools.dns"] = "kong/tools/dns.lua",
+ ["kong.tools.grpc"] = "kong/tools/grpc.lua",
+ ["kong.tools.utils"] = "kong/tools/utils.lua",
+ ["kong.tools.timestamp"] = "kong/tools/timestamp.lua",
+ ["kong.tools.stream_api"] = "kong/tools/stream_api.lua",
+ ["kong.tools.queue"] = "kong/tools/queue.lua",
+ ["kong.tools.queue_schema"] = "kong/tools/queue_schema.lua",
+ ["kong.tools.sandbox"] = "kong/tools/sandbox.lua",
+ ["kong.tools.uri"] = "kong/tools/uri.lua",
+ ["kong.tools.kong-lua-sandbox"] = "kong/tools/kong-lua-sandbox.lua",
+ ["kong.tools.protobuf"] = "kong/tools/protobuf.lua",
+ ["kong.tools.mime_type"] = "kong/tools/mime_type.lua",
+
+ ["kong.runloop.handler"] = "kong/runloop/handler.lua",
+ ["kong.runloop.events"] = "kong/runloop/events.lua",
+ ["kong.runloop.certificate"] = "kong/runloop/certificate.lua",
+ ["kong.runloop.plugins_iterator"] = "kong/runloop/plugins_iterator.lua",
+ ["kong.runloop.balancer"] = "kong/runloop/balancer/init.lua",
+ ["kong.runloop.balancer.balancers"] = "kong/runloop/balancer/balancers.lua",
+ ["kong.runloop.balancer.consistent_hashing"] = "kong/runloop/balancer/consistent_hashing.lua",
+ ["kong.runloop.balancer.healthcheckers"] = "kong/runloop/balancer/healthcheckers.lua",
+ ["kong.runloop.balancer.least_connections"] = "kong/runloop/balancer/least_connections.lua",
+ ["kong.runloop.balancer.latency"] = "kong/runloop/balancer/latency.lua",
+ ["kong.runloop.balancer.round_robin"] = "kong/runloop/balancer/round_robin.lua",
+ ["kong.runloop.balancer.targets"] = "kong/runloop/balancer/targets.lua",
+ ["kong.runloop.balancer.upstreams"] = "kong/runloop/balancer/upstreams.lua",
+ ["kong.runloop.plugin_servers"] = "kong/runloop/plugin_servers/init.lua",
+ ["kong.runloop.plugin_servers.process"] = "kong/runloop/plugin_servers/process.lua",
+ ["kong.runloop.plugin_servers.mp_rpc"] = "kong/runloop/plugin_servers/mp_rpc.lua",
+ ["kong.runloop.plugin_servers.pb_rpc"] = "kong/runloop/plugin_servers/pb_rpc.lua",
+
+ ["kong.workspaces"] = "kong/workspaces/init.lua",
+
+ ["kong.db"] = "kong/db/init.lua",
+ ["kong.db.errors"] = "kong/db/errors.lua",
+ ["kong.db.iteration"] = "kong/db/iteration.lua",
+ ["kong.db.dao"] = "kong/db/dao/init.lua",
+ ["kong.db.dao.certificates"] = "kong/db/dao/certificates.lua",
+ ["kong.db.dao.snis"] = "kong/db/dao/snis.lua",
+ ["kong.db.dao.targets"] = "kong/db/dao/targets.lua",
+ ["kong.db.dao.plugins"] = "kong/db/dao/plugins.lua",
+ ["kong.db.dao.tags"] = "kong/db/dao/tags.lua",
+ ["kong.db.dao.vaults"] = "kong/db/dao/vaults.lua",
+ ["kong.db.dao.workspaces"] = "kong/db/dao/workspaces.lua",
+ ["kong.db.declarative"] = "kong/db/declarative/init.lua",
+ ["kong.db.declarative.marshaller"] = "kong/db/declarative/marshaller.lua",
+ ["kong.db.declarative.export"] = "kong/db/declarative/export.lua",
+ ["kong.db.declarative.import"] = "kong/db/declarative/import.lua",
+ ["kong.db.schema"] = "kong/db/schema/init.lua",
+ ["kong.db.dao.keys"] = "kong/db/dao/keys.lua",
+ ["kong.db.dao.key_sets"] = "kong/db/dao/key_sets.lua",
+ ["kong.db.schema.entities.keys"] = "kong/db/schema/entities/keys.lua",
+ ["kong.db.schema.entities.key_sets"] = "kong/db/schema/entities/key_sets.lua",
+ ["kong.db.schema.entities.consumers"] = "kong/db/schema/entities/consumers.lua",
+ ["kong.db.schema.entities.routes"] = "kong/db/schema/entities/routes.lua",
+ ["kong.db.schema.entities.routes_subschemas"] = "kong/db/schema/entities/routes_subschemas.lua",
+ ["kong.db.schema.entities.services"] = "kong/db/schema/entities/services.lua",
+ ["kong.db.schema.entities.certificates"] = "kong/db/schema/entities/certificates.lua",
+ ["kong.db.schema.entities.snis"] = "kong/db/schema/entities/snis.lua",
+ ["kong.db.schema.entities.upstreams"] = "kong/db/schema/entities/upstreams.lua",
+ ["kong.db.schema.entities.targets"] = "kong/db/schema/entities/targets.lua",
+ ["kong.db.schema.entities.plugins"] = "kong/db/schema/entities/plugins.lua",
+ ["kong.db.schema.entities.tags"] = "kong/db/schema/entities/tags.lua",
+ ["kong.db.schema.entities.ca_certificates"] = "kong/db/schema/entities/ca_certificates.lua",
+ ["kong.db.schema.entities.vaults"] = "kong/db/schema/entities/vaults.lua",
+ ["kong.db.schema.entities.workspaces"] = "kong/db/schema/entities/workspaces.lua",
+ ["kong.db.schema.entities.clustering_data_planes"] = "kong/db/schema/entities/clustering_data_planes.lua",
+ ["kong.db.schema.entities.parameters"] = "kong/db/schema/entities/parameters.lua",
+ ["kong.db.schema.others.migrations"] = "kong/db/schema/others/migrations.lua",
+ ["kong.db.schema.others.declarative_config"] = "kong/db/schema/others/declarative_config.lua",
+ ["kong.db.schema.entity"] = "kong/db/schema/entity.lua",
+ ["kong.db.schema.metaschema"] = "kong/db/schema/metaschema.lua",
+ ["kong.db.schema.typedefs"] = "kong/db/schema/typedefs.lua",
+ ["kong.db.schema.plugin_loader"] = "kong/db/schema/plugin_loader.lua",
+ ["kong.db.schema.vault_loader"] = "kong/db/schema/vault_loader.lua",
+ ["kong.db.schema.topological_sort"] = "kong/db/schema/topological_sort.lua",
+ ["kong.db.strategies"] = "kong/db/strategies/init.lua",
+ ["kong.db.strategies.connector"] = "kong/db/strategies/connector.lua",
+ ["kong.db.strategies.cassandra"] = "kong/db/strategies/cassandra/init.lua",
+ ["kong.db.strategies.cassandra.connector"] = "kong/db/strategies/cassandra/connector.lua",
+ ["kong.db.strategies.cassandra.tags"] = "kong/db/strategies/cassandra/tags.lua",
+ ["kong.db.strategies.postgres"] = "kong/db/strategies/postgres/init.lua",
+ ["kong.db.strategies.postgres.connector"] = "kong/db/strategies/postgres/connector.lua",
+ ["kong.db.strategies.postgres.tags"] = "kong/db/strategies/postgres/tags.lua",
+ ["kong.db.strategies.off"] = "kong/db/strategies/off/init.lua",
+ ["kong.db.strategies.off.connector"] = "kong/db/strategies/off/connector.lua",
+ ["kong.db.strategies.off.tags"] = "kong/db/strategies/off/tags.lua",
+
+ ["kong.db.migrations.state"] = "kong/db/migrations/state.lua",
+ ["kong.db.migrations.subsystems"] = "kong/db/migrations/subsystems.lua",
+ ["kong.db.migrations.core"] = "kong/db/migrations/core/init.lua",
+ ["kong.db.migrations.core.000_base"] = "kong/db/migrations/core/000_base.lua",
+ ["kong.db.migrations.core.003_100_to_110"] = "kong/db/migrations/core/003_100_to_110.lua",
+ ["kong.db.migrations.core.004_110_to_120"] = "kong/db/migrations/core/004_110_to_120.lua",
+ ["kong.db.migrations.core.005_120_to_130"] = "kong/db/migrations/core/005_120_to_130.lua",
+ ["kong.db.migrations.core.006_130_to_140"] = "kong/db/migrations/core/006_130_to_140.lua",
+ ["kong.db.migrations.core.007_140_to_150"] = "kong/db/migrations/core/007_140_to_150.lua",
+ ["kong.db.migrations.core.008_150_to_200"] = "kong/db/migrations/core/008_150_to_200.lua",
+ ["kong.db.migrations.core.009_200_to_210"] = "kong/db/migrations/core/009_200_to_210.lua",
+ ["kong.db.migrations.core.010_210_to_211"] = "kong/db/migrations/core/010_210_to_211.lua",
+ ["kong.db.migrations.core.011_212_to_213"] = "kong/db/migrations/core/011_212_to_213.lua",
+ ["kong.db.migrations.core.012_213_to_220"] = "kong/db/migrations/core/012_213_to_220.lua",
+ ["kong.db.migrations.core.013_220_to_230"] = "kong/db/migrations/core/013_220_to_230.lua",
+ ["kong.db.migrations.core.014_230_to_270"] = "kong/db/migrations/core/014_230_to_270.lua",
+ ["kong.db.migrations.core.015_270_to_280"] = "kong/db/migrations/core/015_270_to_280.lua",
+ ["kong.db.migrations.core.016_280_to_300"] = "kong/db/migrations/core/016_280_to_300.lua",
+ ["kong.db.migrations.core.017_300_to_310"] = "kong/db/migrations/core/017_300_to_310.lua",
+ ["kong.db.migrations.core.018_310_to_320"] = "kong/db/migrations/core/018_310_to_320.lua",
+ ["kong.db.migrations.core.019_320_to_330"] = "kong/db/migrations/core/019_320_to_330.lua",
+ ["kong.db.migrations.operations.200_to_210"] = "kong/db/migrations/operations/200_to_210.lua",
+ ["kong.db.migrations.operations.210_to_211"] = "kong/db/migrations/operations/210_to_211.lua",
+ ["kong.db.migrations.operations.212_to_213"] = "kong/db/migrations/operations/212_to_213.lua",
+ ["kong.db.migrations.operations.280_to_300"] = "kong/db/migrations/operations/280_to_300.lua",
+ ["kong.db.migrations.migrate_path_280_300"] = "kong/db/migrations/migrate_path_280_300.lua",
+ ["kong.db.declarative.migrations"] = "kong/db/declarative/migrations/init.lua",
+ ["kong.db.declarative.migrations.route_path"] = "kong/db/declarative/migrations/route_path.lua",
+
+ ["kong.pdk"] = "kong/pdk/init.lua",
+ ["kong.pdk.private.checks"] = "kong/pdk/private/checks.lua",
+ ["kong.pdk.private.phases"] = "kong/pdk/private/phases.lua",
+ ["kong.pdk.private.node"] = "kong/pdk/private/node.lua",
+ ["kong.pdk.client"] = "kong/pdk/client.lua",
+ ["kong.pdk.client.tls"] = "kong/pdk/client/tls.lua",
+ ["kong.pdk.ctx"] = "kong/pdk/ctx.lua",
+ ["kong.pdk.ip"] = "kong/pdk/ip.lua",
+ ["kong.pdk.log"] = "kong/pdk/log.lua",
+ ["kong.pdk.service"] = "kong/pdk/service.lua",
+ ["kong.pdk.service.request"] = "kong/pdk/service/request.lua",
+ ["kong.pdk.service.response"] = "kong/pdk/service/response.lua",
+ ["kong.pdk.router"] = "kong/pdk/router.lua",
+ ["kong.pdk.request"] = "kong/pdk/request.lua",
+ ["kong.pdk.response"] = "kong/pdk/response.lua",
+ ["kong.pdk.table"] = "kong/pdk/table.lua",
+ ["kong.pdk.node"] = "kong/pdk/node.lua",
+ ["kong.pdk.nginx"] = "kong/pdk/nginx.lua",
+ ["kong.pdk.cluster"] = "kong/pdk/cluster.lua",
+ ["kong.pdk.vault"] = "kong/pdk/vault.lua",
+ ["kong.pdk.tracing"] = "kong/pdk/tracing.lua",
+ ["kong.pdk.plugin"] = "kong/pdk/plugin.lua",
+
+ ["kong.plugins.basic-auth.migrations"] = "kong/plugins/basic-auth/migrations/init.lua",
+ ["kong.plugins.basic-auth.migrations.000_base_basic_auth"] = "kong/plugins/basic-auth/migrations/000_base_basic_auth.lua",
+ ["kong.plugins.basic-auth.migrations.002_130_to_140"] = "kong/plugins/basic-auth/migrations/002_130_to_140.lua",
+ ["kong.plugins.basic-auth.migrations.003_200_to_210"] = "kong/plugins/basic-auth/migrations/003_200_to_210.lua",
+ ["kong.plugins.basic-auth.crypto"] = "kong/plugins/basic-auth/crypto.lua",
+ ["kong.plugins.basic-auth.handler"] = "kong/plugins/basic-auth/handler.lua",
+ ["kong.plugins.basic-auth.access"] = "kong/plugins/basic-auth/access.lua",
+ ["kong.plugins.basic-auth.schema"] = "kong/plugins/basic-auth/schema.lua",
+ ["kong.plugins.basic-auth.daos"] = "kong/plugins/basic-auth/daos.lua",
+
+ ["kong.plugins.key-auth.migrations"] = "kong/plugins/key-auth/migrations/init.lua",
+ ["kong.plugins.key-auth.migrations.000_base_key_auth"] = "kong/plugins/key-auth/migrations/000_base_key_auth.lua",
+ ["kong.plugins.key-auth.migrations.002_130_to_140"] = "kong/plugins/key-auth/migrations/002_130_to_140.lua",
+ ["kong.plugins.key-auth.migrations.003_200_to_210"] = "kong/plugins/key-auth/migrations/003_200_to_210.lua",
+ ["kong.plugins.key-auth.migrations.004_320_to_330"] = "kong/plugins/key-auth/migrations/004_320_to_330.lua",
+ ["kong.plugins.key-auth.handler"] = "kong/plugins/key-auth/handler.lua",
+ ["kong.plugins.key-auth.schema"] = "kong/plugins/key-auth/schema.lua",
+ ["kong.plugins.key-auth.daos"] = "kong/plugins/key-auth/daos.lua",
+
+ ["kong.plugins.oauth2.migrations"] = "kong/plugins/oauth2/migrations/init.lua",
+ ["kong.plugins.oauth2.migrations.000_base_oauth2"] = "kong/plugins/oauth2/migrations/000_base_oauth2.lua",
+ ["kong.plugins.oauth2.migrations.003_130_to_140"] = "kong/plugins/oauth2/migrations/003_130_to_140.lua",
+ ["kong.plugins.oauth2.migrations.004_200_to_210"] = "kong/plugins/oauth2/migrations/004_200_to_210.lua",
+ ["kong.plugins.oauth2.migrations.005_210_to_211"] = "kong/plugins/oauth2/migrations/005_210_to_211.lua",
+ ["kong.plugins.oauth2.migrations.006_320_to_330"] = "kong/plugins/oauth2/migrations/006_320_to_330.lua",
+ ["kong.plugins.oauth2.migrations.007_320_to_330"] = "kong/plugins/oauth2/migrations/007_320_to_330.lua",
+ ["kong.plugins.oauth2.handler"] = "kong/plugins/oauth2/handler.lua",
+ ["kong.plugins.oauth2.secret"] = "kong/plugins/oauth2/secret.lua",
+ ["kong.plugins.oauth2.access"] = "kong/plugins/oauth2/access.lua",
+ ["kong.plugins.oauth2.schema"] = "kong/plugins/oauth2/schema.lua",
+ ["kong.plugins.oauth2.daos"] = "kong/plugins/oauth2/daos.lua",
+ ["kong.plugins.oauth2.daos.oauth2_tokens"] = "kong/plugins/oauth2/daos/oauth2_tokens.lua",
+
+ ["kong.plugins.tcp-log.handler"] = "kong/plugins/tcp-log/handler.lua",
+ ["kong.plugins.tcp-log.schema"] = "kong/plugins/tcp-log/schema.lua",
+
+ ["kong.plugins.udp-log.handler"] = "kong/plugins/udp-log/handler.lua",
+ ["kong.plugins.udp-log.schema"] = "kong/plugins/udp-log/schema.lua",
+
+ ["kong.plugins.http-log.handler"] = "kong/plugins/http-log/handler.lua",
+ ["kong.plugins.http-log.schema"] = "kong/plugins/http-log/schema.lua",
+ ["kong.plugins.http-log.migrations"] = "kong/plugins/http-log/migrations/init.lua",
+ ["kong.plugins.http-log.migrations.001_280_to_300"] = "kong/plugins/http-log/migrations/001_280_to_300.lua",
+
+ ["kong.plugins.file-log.handler"] = "kong/plugins/file-log/handler.lua",
+ ["kong.plugins.file-log.schema"] = "kong/plugins/file-log/schema.lua",
+
+ ["kong.plugins.rate-limiting.migrations"] = "kong/plugins/rate-limiting/migrations/init.lua",
+ ["kong.plugins.rate-limiting.migrations.000_base_rate_limiting"] = "kong/plugins/rate-limiting/migrations/000_base_rate_limiting.lua",
+ ["kong.plugins.rate-limiting.migrations.003_10_to_112"] = "kong/plugins/rate-limiting/migrations/003_10_to_112.lua",
+ ["kong.plugins.rate-limiting.migrations.004_200_to_210"] = "kong/plugins/rate-limiting/migrations/004_200_to_210.lua",
+ ["kong.plugins.rate-limiting.migrations.005_320_to_330"] = "kong/plugins/rate-limiting/migrations/005_320_to_330.lua",
+ ["kong.plugins.rate-limiting.expiration"] = "kong/plugins/rate-limiting/expiration.lua",
+ ["kong.plugins.rate-limiting.handler"] = "kong/plugins/rate-limiting/handler.lua",
+ ["kong.plugins.rate-limiting.schema"] = "kong/plugins/rate-limiting/schema.lua",
+ ["kong.plugins.rate-limiting.daos"] = "kong/plugins/rate-limiting/daos.lua",
+ ["kong.plugins.rate-limiting.policies"] = "kong/plugins/rate-limiting/policies/init.lua",
+ ["kong.plugins.rate-limiting.policies.cluster"] = "kong/plugins/rate-limiting/policies/cluster.lua",
+
+ ["kong.plugins.response-ratelimiting.migrations"] = "kong/plugins/response-ratelimiting/migrations/init.lua",
+ ["kong.plugins.response-ratelimiting.migrations.000_base_response_rate_limiting"] = "kong/plugins/response-ratelimiting/migrations/000_base_response_rate_limiting.lua",
+ ["kong.plugins.response-ratelimiting.handler"] = "kong/plugins/response-ratelimiting/handler.lua",
+ ["kong.plugins.response-ratelimiting.access"] = "kong/plugins/response-ratelimiting/access.lua",
+ ["kong.plugins.response-ratelimiting.header_filter"] = "kong/plugins/response-ratelimiting/header_filter.lua",
+ ["kong.plugins.response-ratelimiting.log"] = "kong/plugins/response-ratelimiting/log.lua",
+ ["kong.plugins.response-ratelimiting.schema"] = "kong/plugins/response-ratelimiting/schema.lua",
+ ["kong.plugins.response-ratelimiting.policies"] = "kong/plugins/response-ratelimiting/policies/init.lua",
+ ["kong.plugins.response-ratelimiting.policies.cluster"] = "kong/plugins/response-ratelimiting/policies/cluster.lua",
+
+ ["kong.plugins.request-size-limiting.handler"] = "kong/plugins/request-size-limiting/handler.lua",
+ ["kong.plugins.request-size-limiting.schema"] = "kong/plugins/request-size-limiting/schema.lua",
+
+ ["kong.plugins.response-transformer.handler"] = "kong/plugins/response-transformer/handler.lua",
+ ["kong.plugins.response-transformer.body_transformer"] = "kong/plugins/response-transformer/body_transformer.lua",
+ ["kong.plugins.response-transformer.header_transformer"] = "kong/plugins/response-transformer/header_transformer.lua",
+ ["kong.plugins.response-transformer.schema"] = "kong/plugins/response-transformer/schema.lua",
+
+ ["kong.plugins.cors.handler"] = "kong/plugins/cors/handler.lua",
+ ["kong.plugins.cors.schema"] = "kong/plugins/cors/schema.lua",
+
+ ["kong.plugins.ip-restriction.handler"] = "kong/plugins/ip-restriction/handler.lua",
+ ["kong.plugins.ip-restriction.schema"] = "kong/plugins/ip-restriction/schema.lua",
+ ["kong.plugins.ip-restriction.migrations"] = "kong/plugins/ip-restriction/migrations/init.lua",
+ ["kong.plugins.ip-restriction.migrations.001_200_to_210"] = "kong/plugins/ip-restriction/migrations/001_200_to_210.lua",
+
+ ["kong.plugins.acl.migrations"] = "kong/plugins/acl/migrations/init.lua",
+ ["kong.plugins.acl.migrations.000_base_acl"] = "kong/plugins/acl/migrations/000_base_acl.lua",
+ ["kong.plugins.acl.migrations.002_130_to_140"] = "kong/plugins/acl/migrations/002_130_to_140.lua",
+ ["kong.plugins.acl.migrations.003_200_to_210"] = "kong/plugins/acl/migrations/003_200_to_210.lua",
+ ["kong.plugins.acl.migrations.004_212_to_213"] = "kong/plugins/acl/migrations/004_212_to_213.lua",
+ ["kong.plugins.acl.handler"] = "kong/plugins/acl/handler.lua",
+ ["kong.plugins.acl.schema"] = "kong/plugins/acl/schema.lua",
+ ["kong.plugins.acl.daos"] = "kong/plugins/acl/daos.lua",
+ ["kong.plugins.acl.groups"] = "kong/plugins/acl/groups.lua",
+ ["kong.plugins.acl.acls"] = "kong/plugins/acl/acls.lua",
+ ["kong.plugins.acl.api"] = "kong/plugins/acl/api.lua",
+
+ ["kong.plugins.correlation-id.handler"] = "kong/plugins/correlation-id/handler.lua",
+ ["kong.plugins.correlation-id.schema"] = "kong/plugins/correlation-id/schema.lua",
+
+ ["kong.plugins.jwt.migrations"] = "kong/plugins/jwt/migrations/init.lua",
+ ["kong.plugins.jwt.migrations.000_base_jwt"] = "kong/plugins/jwt/migrations/000_base_jwt.lua",
+ ["kong.plugins.jwt.migrations.002_130_to_140"] = "kong/plugins/jwt/migrations/002_130_to_140.lua",
+ ["kong.plugins.jwt.migrations.003_200_to_210"] = "kong/plugins/jwt/migrations/003_200_to_210.lua",
+ ["kong.plugins.jwt.handler"] = "kong/plugins/jwt/handler.lua",
+ ["kong.plugins.jwt.schema"] = "kong/plugins/jwt/schema.lua",
+ ["kong.plugins.jwt.daos"] = "kong/plugins/jwt/daos.lua",
+ ["kong.plugins.jwt.jwt_parser"] = "kong/plugins/jwt/jwt_parser.lua",
+
+ ["kong.plugins.hmac-auth.migrations"] = "kong/plugins/hmac-auth/migrations/init.lua",
+ ["kong.plugins.hmac-auth.migrations.000_base_hmac_auth"] = "kong/plugins/hmac-auth/migrations/000_base_hmac_auth.lua",
+ ["kong.plugins.hmac-auth.migrations.002_130_to_140"] = "kong/plugins/hmac-auth/migrations/002_130_to_140.lua",
+ ["kong.plugins.hmac-auth.migrations.003_200_to_210"] = "kong/plugins/hmac-auth/migrations/003_200_to_210.lua",
+ ["kong.plugins.hmac-auth.handler"] = "kong/plugins/hmac-auth/handler.lua",
+ ["kong.plugins.hmac-auth.access"] = "kong/plugins/hmac-auth/access.lua",
+ ["kong.plugins.hmac-auth.schema"] = "kong/plugins/hmac-auth/schema.lua",
+ ["kong.plugins.hmac-auth.daos"] = "kong/plugins/hmac-auth/daos.lua",
+
+ ["kong.plugins.ldap-auth.handler"] = "kong/plugins/ldap-auth/handler.lua",
+ ["kong.plugins.ldap-auth.access"] = "kong/plugins/ldap-auth/access.lua",
+ ["kong.plugins.ldap-auth.schema"] = "kong/plugins/ldap-auth/schema.lua",
+ ["kong.plugins.ldap-auth.ldap"] = "kong/plugins/ldap-auth/ldap.lua",
+ ["kong.plugins.ldap-auth.asn1"] = "kong/plugins/ldap-auth/asn1.lua",
+
+ ["kong.plugins.syslog.handler"] = "kong/plugins/syslog/handler.lua",
+ ["kong.plugins.syslog.schema"] = "kong/plugins/syslog/schema.lua",
+
+ ["kong.plugins.loggly.handler"] = "kong/plugins/loggly/handler.lua",
+ ["kong.plugins.loggly.schema"] = "kong/plugins/loggly/schema.lua",
+
+ ["kong.plugins.datadog.handler"] = "kong/plugins/datadog/handler.lua",
+ ["kong.plugins.datadog.schema"] = "kong/plugins/datadog/schema.lua",
+ ["kong.plugins.datadog.statsd_logger"] = "kong/plugins/datadog/statsd_logger.lua",
+
+ ["kong.plugins.statsd.constants"] = "kong/plugins/statsd/constants.lua",
+ ["kong.plugins.statsd.handler"] = "kong/plugins/statsd/handler.lua",
+ ["kong.plugins.statsd.log"] = "kong/plugins/statsd/log.lua",
+ ["kong.plugins.statsd.schema"] = "kong/plugins/statsd/schema.lua",
+ ["kong.plugins.statsd.statsd_logger"] = "kong/plugins/statsd/statsd_logger.lua",
+
+ ["kong.plugins.bot-detection.handler"] = "kong/plugins/bot-detection/handler.lua",
+ ["kong.plugins.bot-detection.schema"] = "kong/plugins/bot-detection/schema.lua",
+ ["kong.plugins.bot-detection.rules"] = "kong/plugins/bot-detection/rules.lua",
+ ["kong.plugins.bot-detection.migrations"] = "kong/plugins/bot-detection/migrations/init.lua",
+ ["kong.plugins.bot-detection.migrations.001_200_to_210"] = "kong/plugins/bot-detection/migrations/001_200_to_210.lua",
+
+ ["kong.plugins.request-termination.handler"] = "kong/plugins/request-termination/handler.lua",
+ ["kong.plugins.request-termination.schema"] = "kong/plugins/request-termination/schema.lua",
+
+ ["kong.plugins.aws-lambda.aws-serializer"] = "kong/plugins/aws-lambda/aws-serializer.lua",
+ ["kong.plugins.aws-lambda.handler"] = "kong/plugins/aws-lambda/handler.lua",
+ ["kong.plugins.aws-lambda.iam-ec2-credentials"] = "kong/plugins/aws-lambda/iam-ec2-credentials.lua",
+ ["kong.plugins.aws-lambda.iam-ecs-credentials"] = "kong/plugins/aws-lambda/iam-ecs-credentials.lua",
+ ["kong.plugins.aws-lambda.iam-sts-credentials"] = "kong/plugins/aws-lambda/iam-sts-credentials.lua",
+ ["kong.plugins.aws-lambda.schema"] = "kong/plugins/aws-lambda/schema.lua",
+ ["kong.plugins.aws-lambda.v4"] = "kong/plugins/aws-lambda/v4.lua",
+ ["kong.plugins.aws-lambda.request-util"] = "kong/plugins/aws-lambda/request-util.lua",
+
+ ["kong.plugins.grpc-gateway.deco"] = "kong/plugins/grpc-gateway/deco.lua",
+ ["kong.plugins.grpc-gateway.handler"] = "kong/plugins/grpc-gateway/handler.lua",
+ ["kong.plugins.grpc-gateway.schema"] = "kong/plugins/grpc-gateway/schema.lua",
+
+ ["kong.plugins.acme.api"] = "kong/plugins/acme/api.lua",
+ ["kong.plugins.acme.client"] = "kong/plugins/acme/client.lua",
+ ["kong.plugins.acme.daos"] = "kong/plugins/acme/daos.lua",
+ ["kong.plugins.acme.handler"] = "kong/plugins/acme/handler.lua",
+ ["kong.plugins.acme.migrations.000_base_acme"] = "kong/plugins/acme/migrations/000_base_acme.lua",
+ ["kong.plugins.acme.migrations.001_280_to_300"] = "kong/plugins/acme/migrations/001_280_to_300.lua",
+ ["kong.plugins.acme.migrations.002_320_to_330"] = "kong/plugins/acme/migrations/002_320_to_330.lua",
+ ["kong.plugins.acme.migrations"] = "kong/plugins/acme/migrations/init.lua",
+ ["kong.plugins.acme.schema"] = "kong/plugins/acme/schema.lua",
+ ["kong.plugins.acme.storage.kong"] = "kong/plugins/acme/storage/kong.lua",
+ ["kong.plugins.acme.reserved_words"] = "kong/plugins/acme/reserved_words.lua",
+
+ ["kong.plugins.prometheus.api"] = "kong/plugins/prometheus/api.lua",
+ ["kong.plugins.prometheus.status_api"] = "kong/plugins/prometheus/status_api.lua",
+ ["kong.plugins.prometheus.exporter"] = "kong/plugins/prometheus/exporter.lua",
+ ["kong.plugins.prometheus.handler"] = "kong/plugins/prometheus/handler.lua",
+ ["kong.plugins.prometheus.prometheus"] = "kong/plugins/prometheus/prometheus.lua",
+ ["kong.plugins.prometheus.serve"] = "kong/plugins/prometheus/serve.lua",
+ ["kong.plugins.prometheus.schema"] = "kong/plugins/prometheus/schema.lua",
+
+ ["kong.plugins.session.handler"] = "kong/plugins/session/handler.lua",
+ ["kong.plugins.session.schema"] = "kong/plugins/session/schema.lua",
+ ["kong.plugins.session.access"] = "kong/plugins/session/access.lua",
+ ["kong.plugins.session.header_filter"] = "kong/plugins/session/header_filter.lua",
+ ["kong.plugins.session.session"] = "kong/plugins/session/session.lua",
+ ["kong.plugins.session.daos"] = "kong/plugins/session/daos.lua",
+ ["kong.plugins.session.storage.kong"] = "kong/plugins/session/storage/kong.lua",
+ ["kong.plugins.session.migrations.000_base_session"] = "kong/plugins/session/migrations/000_base_session.lua",
+ ["kong.plugins.session.migrations.001_add_ttl_index"] = "kong/plugins/session/migrations/001_add_ttl_index.lua",
+ ["kong.plugins.session.migrations.002_320_to_330"] = "kong/plugins/session/migrations/002_320_to_330.lua",
+ ["kong.plugins.session.migrations"] = "kong/plugins/session/migrations/init.lua",
+
+ ["kong.plugins.proxy-cache.handler"] = "kong/plugins/proxy-cache/handler.lua",
+ ["kong.plugins.proxy-cache.cache_key"] = "kong/plugins/proxy-cache/cache_key.lua",
+ ["kong.plugins.proxy-cache.schema"] = "kong/plugins/proxy-cache/schema.lua",
+ ["kong.plugins.proxy-cache.api"] = "kong/plugins/proxy-cache/api.lua",
+ ["kong.plugins.proxy-cache.strategies"] = "kong/plugins/proxy-cache/strategies/init.lua",
+ ["kong.plugins.proxy-cache.strategies.memory"] = "kong/plugins/proxy-cache/strategies/memory.lua",
+
+ ["kong.plugins.grpc-web.deco"] = "kong/plugins/grpc-web/deco.lua",
+ ["kong.plugins.grpc-web.handler"] = "kong/plugins/grpc-web/handler.lua",
+ ["kong.plugins.grpc-web.schema"] = "kong/plugins/grpc-web/schema.lua",
+
+ ["kong.plugins.pre-function._handler"] = "kong/plugins/pre-function/_handler.lua",
+ ["kong.plugins.pre-function._schema"] = "kong/plugins/pre-function/_schema.lua",
+ ["kong.plugins.pre-function.migrations._001_280_to_300"] = "kong/plugins/pre-function/migrations/_001_280_to_300.lua",
+
+ ["kong.plugins.pre-function.handler"] = "kong/plugins/pre-function/handler.lua",
+ ["kong.plugins.pre-function.schema"] = "kong/plugins/pre-function/schema.lua",
+ ["kong.plugins.pre-function.migrations"] = "kong/plugins/pre-function/migrations/init.lua",
+ ["kong.plugins.pre-function.migrations.001_280_to_300"] = "kong/plugins/pre-function/migrations/001_280_to_300.lua",
+
+ ["kong.plugins.post-function.handler"] = "kong/plugins/post-function/handler.lua",
+ ["kong.plugins.post-function.schema"] = "kong/plugins/post-function/schema.lua",
+ ["kong.plugins.post-function.migrations"] = "kong/plugins/post-function/migrations/init.lua",
+ ["kong.plugins.post-function.migrations.001_280_to_300"] = "kong/plugins/post-function/migrations/001_280_to_300.lua",
+
+ ["kong.plugins.zipkin.handler"] = "kong/plugins/zipkin/handler.lua",
+ ["kong.plugins.zipkin.reporter"] = "kong/plugins/zipkin/reporter.lua",
+ ["kong.plugins.zipkin.span"] = "kong/plugins/zipkin/span.lua",
+ ["kong.plugins.zipkin.schema"] = "kong/plugins/zipkin/schema.lua",
+ ["kong.plugins.zipkin.request_tags"] = "kong/plugins/zipkin/request_tags.lua",
+
+ ["kong.plugins.request-transformer.migrations.cassandra"] = "kong/plugins/request-transformer/migrations/cassandra.lua",
+ ["kong.plugins.request-transformer.migrations.postgres"] = "kong/plugins/request-transformer/migrations/postgres.lua",
+ ["kong.plugins.request-transformer.migrations.common"] = "kong/plugins/request-transformer/migrations/common.lua",
+ ["kong.plugins.request-transformer.handler"] = "kong/plugins/request-transformer/handler.lua",
+ ["kong.plugins.request-transformer.access"] = "kong/plugins/request-transformer/access.lua",
+ ["kong.plugins.request-transformer.schema"] = "kong/plugins/request-transformer/schema.lua",
+
+ ["kong.plugins.azure-functions.handler"] = "kong/plugins/azure-functions/handler.lua",
+ ["kong.plugins.azure-functions.schema"] = "kong/plugins/azure-functions/schema.lua",
+
+ ["kong.plugins.opentelemetry.handler"] = "kong/plugins/opentelemetry/handler.lua",
+ ["kong.plugins.opentelemetry.schema"] = "kong/plugins/opentelemetry/schema.lua",
+ ["kong.plugins.opentelemetry.proto"] = "kong/plugins/opentelemetry/proto.lua",
+ ["kong.plugins.opentelemetry.otlp"] = "kong/plugins/opentelemetry/otlp.lua",
+
+ ["kong.vaults.env"] = "kong/vaults/env/init.lua",
+ ["kong.vaults.env.schema"] = "kong/vaults/env/schema.lua",
+
+ ["kong.tracing.instrumentation"] = "kong/tracing/instrumentation.lua",
+ ["kong.tracing.propagation"] = "kong/tracing/propagation.lua",
+ }
+}
diff --git a/tests/packagedcode/data/rockspec/test2.rockspec b/tests/packagedcode/data/rockspec/test2.rockspec
new file mode 100644
index 0000000000..66826a3efd
--- /dev/null
+++ b/tests/packagedcode/data/rockspec/test2.rockspec
@@ -0,0 +1,135 @@
+package = "LuaSocket"
+version = "scm-3"
+source = {
+ url = "git+https://github.com/lunarmodules/luasocket.git",
+ branch = "master"
+}
+description = {
+ summary = "Network support for the Lua language",
+ detailed = [[
+ LuaSocket is a Lua extension library composed of two parts: a set of C
+ modules that provide support for the TCP and UDP transport layers, and a
+ set of Lua modules that provide functions commonly needed by applications
+ that deal with the Internet.
+ ]],
+ homepage = "https://github.com/lunarmodules/luasocket",
+ license = "MIT"
+}
+dependencies = {
+ "lua >= 5.1"
+}
+
+local function make_plat(plat)
+ local defines = {
+ unix = {
+ "LUASOCKET_DEBUG"
+ },
+ macosx = {
+ "LUASOCKET_DEBUG",
+ "UNIX_HAS_SUN_LEN"
+ },
+ win32 = {
+ "LUASOCKET_DEBUG",
+ "NDEBUG"
+ },
+ mingw32 = {
+ "LUASOCKET_DEBUG",
+ -- "LUASOCKET_INET_PTON",
+ "WINVER=0x0501"
+ }
+ }
+ local modules = {
+ ["socket.core"] = {
+ sources = {
+ "src/luasocket.c"
+ , "src/timeout.c"
+ , "src/buffer.c"
+ , "src/io.c"
+ , "src/auxiliar.c"
+ , "src/options.c"
+ , "src/inet.c"
+ , "src/except.c"
+ , "src/select.c"
+ , "src/tcp.c"
+ , "src/udp.c"
+ , "src/compat.c" },
+ defines = defines[plat],
+ incdir = "/src"
+ },
+ ["mime.core"] = {
+ sources = { "src/mime.c", "src/compat.c" },
+ defines = defines[plat],
+ incdir = "/src"
+ },
+ ["socket.http"] = "src/http.lua",
+ ["socket.url"] = "src/url.lua",
+ ["socket.tp"] = "src/tp.lua",
+ ["socket.ftp"] = "src/ftp.lua",
+ ["socket.headers"] = "src/headers.lua",
+ ["socket.smtp"] = "src/smtp.lua",
+ ltn12 = "src/ltn12.lua",
+ socket = "src/socket.lua",
+ mbox = "src/mbox.lua",
+ mime = "src/mime.lua"
+ }
+ if plat == "unix"
+ or plat == "macosx"
+ or plat == "haiku"
+ then
+ modules["socket.core"].sources[#modules["socket.core"].sources+1] = "src/usocket.c"
+ if plat == "haiku" then
+ modules["socket.core"].libraries = {"network"}
+ end
+ modules["socket.unix"] = {
+ sources = {
+ "src/buffer.c"
+ , "src/compat.c"
+ , "src/auxiliar.c"
+ , "src/options.c"
+ , "src/timeout.c"
+ , "src/io.c"
+ , "src/usocket.c"
+ , "src/unix.c"
+ , "src/unixdgram.c"
+ , "src/unixstream.c" },
+ defines = defines[plat],
+ incdir = "/src"
+ }
+ modules["socket.serial"] = {
+ sources = {
+ "src/buffer.c"
+ , "src/compat.c"
+ , "src/auxiliar.c"
+ , "src/options.c"
+ , "src/timeout.c"
+ , "src/io.c"
+ , "src/usocket.c"
+ , "src/serial.c" },
+ defines = defines[plat],
+ incdir = "/src"
+ }
+ end
+ if plat == "win32"
+ or plat == "mingw32"
+ then
+ modules["socket.core"].sources[#modules["socket.core"].sources+1] = "src/wsocket.c"
+ modules["socket.core"].libraries = { "ws2_32" }
+ modules["socket.core"].libdirs = {}
+ end
+ return { modules = modules }
+end
+
+build = {
+ type = "builtin",
+ platforms = {
+ unix = make_plat("unix"),
+ macosx = make_plat("macosx"),
+ haiku = make_plat("haiku"),
+ win32 = make_plat("win32"),
+ mingw32 = make_plat("mingw32")
+ },
+ copy_directories = {
+ "docs"
+ , "samples"
+ , "test" }
+}
\ No newline at end of file
diff --git a/tests/packagedcode/data/rockspec/test3.rockspec b/tests/packagedcode/data/rockspec/test3.rockspec
new file mode 100644
index 0000000000..8fde441812
--- /dev/null
+++ b/tests/packagedcode/data/rockspec/test3.rockspec
@@ -0,0 +1,55 @@
+rockspec_format = "3.0"
+package = "vdsl"
+version = "0.1.0-1"
+
+source = {
+ url = "git+https://github.com/ynishi/vdsl.git",
+ tag = "v0.1.0",
+}
+
+description = {
+ summary = "Visual DSL for ComfyUI",
+ detailed = [[
+ vdsl transforms semantic scene composition into ComfyUI node graphs.
+ Pure Lua. Zero dependencies.
+ Images become portable project files through PNG-embedded recipes.
+ ]],
+ homepage = "https://github.com/ynishi/vdsl",
+ license = "MIT",
+ labels = { "comfyui", "dsl", "image-generation", "stable-diffusion" },
+}
+
+dependencies = {
+ "lua >= 5.1",
+}
+
+build = {
+ type = "builtin",
+ modules = {
+ ["vdsl"] = "lua/vdsl/init.lua",
+ ["vdsl.entity"] = "lua/vdsl/entity.lua",
+ ["vdsl.trait"] = "lua/vdsl/trait.lua",
+ ["vdsl.subject"] = "lua/vdsl/subject.lua",
+ ["vdsl.weight"] = "lua/vdsl/weight.lua",
+ ["vdsl.world"] = "lua/vdsl/world.lua",
+ ["vdsl.cast"] = "lua/vdsl/cast.lua",
+ ["vdsl.stage"] = "lua/vdsl/stage.lua",
+ ["vdsl.post"] = "lua/vdsl/post.lua",
+ ["vdsl.catalog"] = "lua/vdsl/catalog.lua",
+ ["vdsl.theme"] = "lua/vdsl/theme.lua",
+ ["vdsl.compiler"] = "lua/vdsl/compiler.lua",
+ ["vdsl.decode"] = "lua/vdsl/decode.lua",
+ ["vdsl.graph"] = "lua/vdsl/graph.lua",
+ ["vdsl.json"] = "lua/vdsl/json.lua",
+ ["vdsl.matcher"] = "lua/vdsl/matcher.lua",
+ ["vdsl.png"] = "lua/vdsl/png.lua",
+ ["vdsl.recipe"] = "lua/vdsl/recipe.lua",
+ ["vdsl.registry"] = "lua/vdsl/registry.lua",
+ ["vdsl.transport"] = "lua/vdsl/transport/init.lua",
+ ["vdsl.transport.curl"] = "lua/vdsl/transport/curl.lua",
+ ["vdsl.themes.cinema"] = "lua/vdsl/themes/cinema.lua",
+ ["vdsl.themes.anime"] = "lua/vdsl/themes/anime.lua",
+ ["vdsl.themes.architecture"] = "lua/vdsl/themes/architecture.lua",
+ },
+ copy_directories = { "examples", "tests" },
+}
\ No newline at end of file
diff --git a/tests/packagedcode/data/rockspec/test4.rockspec b/tests/packagedcode/data/rockspec/test4.rockspec
new file mode 100644
index 0000000000..f49f6f9095
--- /dev/null
+++ b/tests/packagedcode/data/rockspec/test4.rockspec
@@ -0,0 +1,32 @@
+---@diagnostic disable: lowercase-global
+
+local _MODREV, _SPECREV = "scm", "-1"
+rockspec_format = "3.0"
+version = _MODREV .. _SPECREV
+
+local user = "S1M0N38"
+package = "claude.nvim"
+
+description = {
+ summary = "A simple plugin to integrate Claude Code in Neovim",
+ detailed = [[
+claude.nvim is a simple plugin to integrate Claude Code in Neovim.
+ ]],
+ labels = { "neovim", "plugin", "lua", "claude", "ai" },
+ homepage = "https://github.com/" .. user .. "/" .. package,
+ license = "MIT",
+}
+
+dependencies = {
+ "lua >= 5.1",
+}
+
+
+source = {
+ url = "git://github.com/" .. user .. "/" .. package,
+}
+
+build = {
+ type = "builtin",
+ copy_directories = { "plugin", "doc", "scripts" },
+}
\ No newline at end of file
diff --git a/tests/packagedcode/test_rockspec.py b/tests/packagedcode/test_rockspec.py
new file mode 100644
index 0000000000..fef3daee0f
--- /dev/null
+++ b/tests/packagedcode/test_rockspec.py
@@ -0,0 +1,322 @@
+#
+# Copyright (c) nexB Inc. and others. All rights reserved.
+# ScanCode is a trademark of nexB Inc.
+# SPDX-License-Identifier: Apache-2.0
+# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
+# See https://github.com/nexB/scancode-toolkit for support or download.
+# See https://aboutcode.org for more information about nexB OSS projects.
+
+
+import json
+import os
+import tempfile
+
+from packagedcode import rockspec
+from packages_test_utils import PackageTester
+from scancode.cli_test_utils import run_scan_click
+
+
+class TestRockspecParser(PackageTester):
+ """Tests for RockspecParser following ScanCode's testing patterns."""
+
+ test_data_dir = os.path.join(os.path.dirname(__file__), 'data')
+
+ def test_mandatory_fields_test_1(self):
+ """Test extraction of mandatory fields from test1 rockspec."""
+ test_file = self.get_test_loc('rockspec/test1.rockspec')
+ parser = rockspec.RockspecParser(test_file)
+ data = parser.parse()
+
+ assert data['package'] == 'kong'
+ assert data['version'] == '3.3.0-0'
+ assert data['vcs_url'] == 'git+https://github.com/Kong/kong.git'
+ assert len(parser.errors) == 0
+
+ def test_optional_fields_test_1(self):
+ """Test extraction of optional fields from test1 rockspec."""
+ test_file = self.get_test_loc('rockspec/test1.rockspec')
+ parser = rockspec.RockspecParser(test_file)
+ data = parser.parse()
+
+ assert data['description'] is not None
+ assert 'Kong is a scalable' in data['description']
+ assert data['license'] == 'Apache 2.0'
+ assert data['homepage_url'] == 'https://konghq.com'
+
+ def test_metadata_fields_test_1(self):
+ """Test extraction of metadata fields from test1 rockspec."""
+ test_file = self.get_test_loc('rockspec/test1.rockspec')
+ parser = rockspec.RockspecParser(test_file)
+ data = parser.parse()
+
+ assert data['rockspec_format'] == '3.0'
+ assert isinstance(data['supported_platforms'], list)
+ assert len(data['supported_platforms']) == 2
+ assert 'linux' in data['supported_platforms']
+ assert 'macosx' in data['supported_platforms']
+
+ def test_dependencies_test_1(self):
+ """Test extraction of dependencies from test1 rockspec.
+
+ Dependencies are now returned as parsed dicts {name, version_spec, raw}
+ instead of raw strings.
+ """
+ test_file = self.get_test_loc('rockspec/test1.rockspec')
+ parser = rockspec.RockspecParser(test_file)
+ data = parser.parse()
+
+ assert isinstance(data['dependencies'], list)
+ assert len(data['dependencies']) == 30
+
+ # Dependencies are now parsed dicts
+ dep_names = [dep['name'] for dep in data['dependencies']]
+ dep_raws = [dep['raw'] for dep in data['dependencies']]
+
+ assert 'inspect' in dep_names
+ assert 'luasec' in dep_names
+ assert 'inspect == 3.1.3' in dep_raws
+ assert 'luasec == 1.3.1' in dep_raws
+
+ def test_concatenation_variables_test4(self):
+ """Test extraction with variable concatenation in test4.rockspec."""
+ test_file = self.get_test_loc('rockspec/test4.rockspec')
+ parser = rockspec.RockspecParser(test_file)
+ data = parser.parse()
+
+ # version = _MODREV .. _SPECREV should resolve to "scm-1"
+ assert data['package'] == 'claude.nvim'
+ assert data['version'] == 'scm-1'
+
+ # URL concatenation should resolve all variables
+ assert 'github.com' in data['vcs_url']
+ assert 'S1M0N38' in data['vcs_url']
+ assert 'claude.nvim' in data['vcs_url']
+
+ # Homepage concatenation
+ assert 'github.com' in data['homepage_url']
+ assert 'S1M0N38' in data['homepage_url']
+ assert 'claude.nvim' in data['homepage_url']
+
+ assert data['license'] == 'MIT'
+ assert len(parser.errors) == 0
+
+ def test_error_missing_package(self):
+ """Test error handling when package field is missing."""
+ rockspec_content = 'version = "1.0.0"\nsource = { url = "git://test" }'
+
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.rockspec', delete=False) as f:
+ f.write(rockspec_content)
+ f.flush()
+ temp_file = f.name
+
+ try:
+ parser = rockspec.RockspecParser(temp_file)
+ data = parser.parse()
+
+ assert data['package'] is None
+ assert any(err.field == 'package' for err in parser.errors)
+ finally:
+ os.unlink(temp_file)
+
+ def test_error_missing_version(self):
+ """Test error handling when version field is missing."""
+ rockspec_content = 'package = "test"\nsource = { url = "git://test" }'
+
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.rockspec', delete=False) as f:
+ f.write(rockspec_content)
+ f.flush()
+ temp_file = f.name
+
+ try:
+ parser = rockspec.RockspecParser(temp_file)
+ data = parser.parse()
+
+ assert data['version'] is None
+ assert any(err.field == 'version' for err in parser.errors)
+ finally:
+ os.unlink(temp_file)
+
+ def test_error_missing_source_url(self):
+ """Test error handling when source.url is missing."""
+ rockspec_content = 'package = "test"\nversion = "1.0"\nsource = { tag = "v1" }'
+
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.rockspec', delete=False) as f:
+ f.write(rockspec_content)
+ f.flush()
+ temp_file = f.name
+
+ try:
+ parser = rockspec.RockspecParser(temp_file)
+ data = parser.parse()
+
+ assert data['vcs_url'] is None
+ assert any(err.field == 'source.url' for err in parser.errors)
+ finally:
+ os.unlink(temp_file)
+
+ def test_error_file_not_found(self):
+ """Test error handling when rockspec file does not exist."""
+ parser = rockspec.RockspecParser('/nonexistent/rockspec/path.rockspec')
+ data = parser.parse()
+
+ assert data == {}
+ assert len(parser.errors) > 0
+
+
+class TestRockspecHandlerIntegration(PackageTester):
+ """Test RockspecHandler integration with ScanCode."""
+
+ test_data_dir = os.path.join(os.path.dirname(__file__), 'data')
+
+ def test_is_datafile_rockspec(self):
+ """Test that is_datafile recognizes .rockspec files."""
+ test_file = self.get_test_loc('rockspec/test1.rockspec')
+ assert rockspec.RockspecHandler.is_datafile(test_file)
+
+ def test_is_datafile_non_rockspec(self):
+ """Test that is_datafile rejects non-.rockspec files."""
+ test_file = self.get_test_loc('rockspec/test1.rockspec')
+ # Just verify the handler has the correct path_patterns
+ assert '*.rockspec' in rockspec.RockspecHandler.path_patterns
+
+ def test_handler_is_registered(self):
+ """Test that RockspecHandler is registered in the system."""
+ from packagedcode import APPLICATION_PACKAGE_DATAFILE_HANDLERS
+ handlers = [h for h in APPLICATION_PACKAGE_DATAFILE_HANDLERS
+ if h.datasource_id == 'luarocks_rockspec']
+ assert len(handlers) == 1, f"Expected 1 RockspecHandler, found {len(handlers)}"
+ assert handlers[0] == rockspec.RockspecHandler
+
+ def test_handler_in_datasource_registry(self):
+ """Test that handler is in the HANDLER_BY_DATASOURCE_ID registry."""
+ from packagedcode import HANDLER_BY_DATASOURCE_ID
+ handler = HANDLER_BY_DATASOURCE_ID.get('luarocks_rockspec')
+ assert handler is not None
+ assert handler == rockspec.RockspecHandler
+
+ def test_handler_attributes(self):
+ """Test that handler has required attributes."""
+ assert rockspec.RockspecHandler.datasource_id == 'luarocks_rockspec'
+ assert rockspec.RockspecHandler.path_patterns == ('*.rockspec',)
+ assert rockspec.RockspecHandler.default_package_type == 'luarocks'
+ assert rockspec.RockspecHandler.default_primary_language == 'Lua'
+ assert rockspec.RockspecHandler.description is not None
+
+ def test_debug_is_datafile_direct(self):
+ """Debug test: directly check if is_datafile works for the test file."""
+ test_file = self.get_test_loc('rockspec/test1.rockspec')
+
+ # This should return True if the handler can recognize the file
+ is_match = rockspec.RockspecHandler.is_datafile(test_file)
+ assert is_match, f"is_datafile() returned False for {test_file}"
+
+ # Also verify parse works directly
+ packages = list(rockspec.RockspecHandler.parse(test_file))
+ assert len(packages) > 0, f"parse() returned no packages for {test_file}"
+
+ def test_end2end_rockspec_scan_with_package_flag(self):
+ """End-to-end test: scan a rockspec file with --package flag."""
+ test_file = self.get_test_loc('rockspec/test1.rockspec')
+ result_file = self.get_temp_file('results.json')
+ run_scan_click(['--package', test_file, '--json', result_file])
+
+ # Parse results
+ with open(result_file) as f:
+ results = json.load(f)
+
+ # Check that packages were found
+ packages = results.get('packages', [])
+ assert len(packages) > 0, f"No packages found in scan results. Got: {json.dumps(results, indent=2)}"
+
+ # Verify package data
+ pkg = packages[0]
+ assert pkg['name'] == 'kong'
+ assert pkg['version'] == '3.3.0-0'
+ assert pkg['type'] == 'luarocks'
+ assert 'luarocks_rockspec' in pkg.get('datasource_ids', [])
+
+ # Check dependencies from the top-level dependencies array
+ # (not in the Package object itself)
+ package_uid = pkg.get('package_uid')
+ dependencies = results.get('dependencies', [])
+ pkg_dependencies = [dep for dep in dependencies if dep.get('for_package_uid') == package_uid]
+ assert len(pkg_dependencies) == 30, f"Expected 30 dependencies, got {len(pkg_dependencies)}"
+
+
+
+
+class TestDependencyParsing(PackageTester):
+ """Test parse_dependency helper method."""
+
+ test_data_dir = os.path.join(os.path.dirname(__file__), 'data')
+
+ def test_dependency_with_equals_operator(self):
+ """Test parsing dependency with == operator."""
+ test_file = self.get_test_loc('rockspec/test1.rockspec')
+ parser = rockspec.RockspecParser(test_file)
+ result = parser.parse_dependency('inspect == 3.1.3')
+
+ assert result is not None
+ assert result['name'] == 'inspect'
+ assert result['version_spec'] == '== 3.1.3'
+
+ def test_dependency_with_gte_operator(self):
+ """Test parsing dependency with >= operator."""
+ test_file = self.get_test_loc('rockspec/test1.rockspec')
+ parser = rockspec.RockspecParser(test_file)
+ result = parser.parse_dependency('binaryheap >= 0.4')
+
+ assert result is not None
+ assert result['name'] == 'binaryheap'
+ assert result['version_spec'] == '>= 0.4'
+
+ def test_dependency_without_version(self):
+ """Test parsing dependency without version spec."""
+ test_file = self.get_test_loc('rockspec/test1.rockspec')
+ parser = rockspec.RockspecParser(test_file)
+ result = parser.parse_dependency('somedep')
+
+ assert result is not None
+ assert result['name'] == 'somedep'
+ assert result['version_spec'] is None
+
+ def test_dependency_empty_string(self):
+ """Test parsing empty dependency string."""
+ test_file = self.get_test_loc('rockspec/test1.rockspec')
+ parser = rockspec.RockspecParser(test_file)
+ result = parser.parse_dependency('')
+
+ assert result is None
+ def test_handler_parse_returns_package_data(self):
+ """Test that RockspecHandler.parse returns proper PackageData objects."""
+ test_file = self.get_test_loc('rockspec/test1.rockspec')
+ packages = list(rockspec.RockspecHandler.parse(test_file))
+
+ assert len(packages) == 1
+ pkg = packages[0]
+
+ assert isinstance(pkg, rockspec.models.PackageData)
+ assert pkg.name == 'kong'
+ assert pkg.version == '3.3.0-0'
+ assert pkg.type == 'luarocks'
+ assert pkg.datasource_id == 'luarocks_rockspec'
+ assert pkg.vcs_url == 'git+https://github.com/Kong/kong.git'
+ assert len(pkg.dependencies) == 30
+
+ def test_handler_creates_dependent_packages(self):
+ """Test that dependencies are converted to DependentPackage objects."""
+ test_file = self.get_test_loc('rockspec/test1.rockspec')
+ packages = list(rockspec.RockspecHandler.parse(test_file))
+
+ pkg = packages[0]
+ assert len(pkg.dependencies) > 0
+
+ for dep in pkg.dependencies:
+ assert isinstance(dep, rockspec.models.DependentPackage)
+ assert dep.scope == 'dependencies'
+ assert dep.is_runtime is True
+
+
+if __name__ == '__main__':
+ import pytest
+ pytest.main([__file__, '-v'])