drupal-scout 3.0.2__tar.gz → 3.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {drupal_scout-3.0.2/drupal_scout.egg-info → drupal_scout-3.1.0}/PKG-INFO +19 -8
- {drupal_scout-3.0.2 → drupal_scout-3.1.0}/README.md +18 -7
- {drupal_scout-3.0.2 → drupal_scout-3.1.0}/drupal_scout/application.py +155 -65
- drupal_scout-3.1.0/drupal_scout/deep_scan.py +344 -0
- {drupal_scout-3.0.2 → drupal_scout-3.1.0}/drupal_scout/exceptions.py +7 -2
- {drupal_scout-3.0.2 → drupal_scout-3.1.0}/drupal_scout/formatters/formatter.py +2 -0
- {drupal_scout-3.0.2 → drupal_scout-3.1.0}/drupal_scout/formatters/formatterfactory.py +4 -4
- drupal_scout-3.1.0/drupal_scout/formatters/jsonformatter.py +56 -0
- {drupal_scout-3.0.2 → drupal_scout-3.1.0}/drupal_scout/formatters/suggestformatter.py +14 -10
- drupal_scout-3.1.0/drupal_scout/formatters/tableformatter.py +199 -0
- {drupal_scout-3.0.2 → drupal_scout-3.1.0}/drupal_scout/mcp_server.py +36 -21
- drupal_scout-3.1.0/drupal_scout/module.py +43 -0
- {drupal_scout-3.0.2 → drupal_scout-3.1.0}/drupal_scout/output.py +36 -16
- drupal_scout-3.1.0/drupal_scout/tests/conftest.py +121 -0
- drupal_scout-3.1.0/drupal_scout/tests/test_application.py +1030 -0
- drupal_scout-3.1.0/drupal_scout/tests/test_deep_scan.py +346 -0
- {drupal_scout-3.0.2 → drupal_scout-3.1.0}/drupal_scout/tests/test_exceptions.py +17 -7
- {drupal_scout-3.0.2 → drupal_scout-3.1.0}/drupal_scout/tests/test_formatterfactory.py +5 -5
- drupal_scout-3.1.0/drupal_scout/tests/test_jsonformatter.py +142 -0
- {drupal_scout-3.0.2 → drupal_scout-3.1.0}/drupal_scout/tests/test_mcp_server.py +141 -17
- {drupal_scout-3.0.2 → drupal_scout-3.1.0}/drupal_scout/tests/test_suggestformatter.py +26 -26
- drupal_scout-3.1.0/drupal_scout/tests/test_tableformatter.py +157 -0
- drupal_scout-3.1.0/drupal_scout/tests/test_worker.py +462 -0
- {drupal_scout-3.0.2 → drupal_scout-3.1.0}/drupal_scout/tests/test_workers_manager.py +22 -8
- drupal_scout-3.1.0/drupal_scout/worker.py +270 -0
- {drupal_scout-3.0.2 → drupal_scout-3.1.0}/drupal_scout/workers_manager.py +24 -9
- {drupal_scout-3.0.2 → drupal_scout-3.1.0/drupal_scout.egg-info}/PKG-INFO +19 -8
- {drupal_scout-3.0.2 → drupal_scout-3.1.0}/drupal_scout.egg-info/SOURCES.txt +2 -0
- {drupal_scout-3.0.2 → drupal_scout-3.1.0}/pyproject.toml +1 -1
- drupal_scout-3.0.2/drupal_scout/formatters/jsonformatter.py +0 -39
- drupal_scout-3.0.2/drupal_scout/formatters/tableformatter.py +0 -50
- drupal_scout-3.0.2/drupal_scout/module.py +0 -18
- drupal_scout-3.0.2/drupal_scout/tests/conftest.py +0 -20
- drupal_scout-3.0.2/drupal_scout/tests/test_application.py +0 -668
- drupal_scout-3.0.2/drupal_scout/tests/test_jsonformatter.py +0 -101
- drupal_scout-3.0.2/drupal_scout/tests/test_tableformatter.py +0 -78
- drupal_scout-3.0.2/drupal_scout/tests/test_worker.py +0 -360
- drupal_scout-3.0.2/drupal_scout/worker.py +0 -158
- {drupal_scout-3.0.2 → drupal_scout-3.1.0}/LICENSE.txt +0 -0
- {drupal_scout-3.0.2 → drupal_scout-3.1.0}/bin/drupal-scout +0 -0
- {drupal_scout-3.0.2 → drupal_scout-3.1.0}/drupal_scout/__init__.py +0 -0
- {drupal_scout-3.0.2 → drupal_scout-3.1.0}/drupal_scout/formatters/__init__.py +0 -0
- {drupal_scout-3.0.2 → drupal_scout-3.1.0}/drupal_scout/tests/__init__.py +0 -0
- {drupal_scout-3.0.2 → drupal_scout-3.1.0}/drupal_scout.egg-info/dependency_links.txt +0 -0
- {drupal_scout-3.0.2 → drupal_scout-3.1.0}/drupal_scout.egg-info/entry_points.txt +0 -0
- {drupal_scout-3.0.2 → drupal_scout-3.1.0}/drupal_scout.egg-info/requires.txt +0 -0
- {drupal_scout-3.0.2 → drupal_scout-3.1.0}/drupal_scout.egg-info/top_level.txt +0 -0
- {drupal_scout-3.0.2 → drupal_scout-3.1.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: drupal-scout
|
|
3
|
-
Version: 3.0
|
|
3
|
+
Version: 3.1.0
|
|
4
4
|
Summary: Scout out for transitive versions of Drupal modules for the upgrade of the core.
|
|
5
5
|
Author-email: "Andrew [R-Tech] Tsyhaniuk" <in0mad91@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -127,6 +127,8 @@ drupal-scout [-h] [-v] [-d DIRECTORY] [-n] [-l LIMIT] [-f {table,json,suggest}]
|
|
|
127
127
|
- `-s, --save-dump`: Use with `--format suggest` to save the suggested `composer.json` to disk.
|
|
128
128
|
- `-c CORE, --core CORE`: Optional Drupal core version override (e.g., `10.0.0`).
|
|
129
129
|
- `-m MODULES [MODULES ...], --modules MODULES [MODULES ...]`: Scan only specific modules, skipping full project discovery.
|
|
130
|
+
- `--deep-scan[=MODE]`: Perform a read-only local deep scan for global or targeted scans. Optional mode: `all` (default, Git + Patches), `patches` (Composer patches only), or `git` (Git index + history only).
|
|
131
|
+
|
|
130
132
|
|
|
131
133
|
### Subcommands
|
|
132
134
|
|
|
@@ -146,22 +148,31 @@ Or with `uvx`:
|
|
|
146
148
|
uvx --from drupal-scout drupal-scout-mcp
|
|
147
149
|
```
|
|
148
150
|
|
|
149
|
-
###
|
|
151
|
+
### Deep Scan & Audit Examples
|
|
152
|
+
|
|
153
|
+
Perform a complete deep scan across all project modules (global scan):
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
drupal-scout --deep-scan
|
|
157
|
+
```
|
|
150
158
|
|
|
151
|
-
|
|
159
|
+
Perform a Composer patch-only scan:
|
|
152
160
|
|
|
153
161
|
```bash
|
|
154
|
-
drupal-scout --
|
|
162
|
+
drupal-scout --deep-scan=patches
|
|
155
163
|
```
|
|
156
164
|
|
|
157
|
-
|
|
165
|
+
Perform a Git audit scan for targeted modules:
|
|
158
166
|
|
|
159
167
|
```bash
|
|
160
|
-
drupal-scout --
|
|
168
|
+
drupal-scout --directory /path/to/drupal --modules drupal/webform drupal/workbench --deep-scan=git
|
|
161
169
|
```
|
|
162
170
|
|
|
163
|
-
|
|
171
|
+
Perform a deep scan and output JSON:
|
|
164
172
|
|
|
165
173
|
```bash
|
|
166
|
-
drupal-scout --directory /path/to/drupal --
|
|
174
|
+
drupal-scout --directory /path/to/drupal --deep-scan --format json
|
|
167
175
|
```
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
@@ -69,6 +69,8 @@ drupal-scout [-h] [-v] [-d DIRECTORY] [-n] [-l LIMIT] [-f {table,json,suggest}]
|
|
|
69
69
|
- `-s, --save-dump`: Use with `--format suggest` to save the suggested `composer.json` to disk.
|
|
70
70
|
- `-c CORE, --core CORE`: Optional Drupal core version override (e.g., `10.0.0`).
|
|
71
71
|
- `-m MODULES [MODULES ...], --modules MODULES [MODULES ...]`: Scan only specific modules, skipping full project discovery.
|
|
72
|
+
- `--deep-scan[=MODE]`: Perform a read-only local deep scan for global or targeted scans. Optional mode: `all` (default, Git + Patches), `patches` (Composer patches only), or `git` (Git index + history only).
|
|
73
|
+
|
|
72
74
|
|
|
73
75
|
### Subcommands
|
|
74
76
|
|
|
@@ -88,22 +90,31 @@ Or with `uvx`:
|
|
|
88
90
|
uvx --from drupal-scout drupal-scout-mcp
|
|
89
91
|
```
|
|
90
92
|
|
|
91
|
-
###
|
|
93
|
+
### Deep Scan & Audit Examples
|
|
94
|
+
|
|
95
|
+
Perform a complete deep scan across all project modules (global scan):
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
drupal-scout --deep-scan
|
|
99
|
+
```
|
|
92
100
|
|
|
93
|
-
|
|
101
|
+
Perform a Composer patch-only scan:
|
|
94
102
|
|
|
95
103
|
```bash
|
|
96
|
-
drupal-scout --
|
|
104
|
+
drupal-scout --deep-scan=patches
|
|
97
105
|
```
|
|
98
106
|
|
|
99
|
-
|
|
107
|
+
Perform a Git audit scan for targeted modules:
|
|
100
108
|
|
|
101
109
|
```bash
|
|
102
|
-
drupal-scout --
|
|
110
|
+
drupal-scout --directory /path/to/drupal --modules drupal/webform drupal/workbench --deep-scan=git
|
|
103
111
|
```
|
|
104
112
|
|
|
105
|
-
|
|
113
|
+
Perform a deep scan and output JSON:
|
|
106
114
|
|
|
107
115
|
```bash
|
|
108
|
-
drupal-scout --directory /path/to/drupal --
|
|
116
|
+
drupal-scout --directory /path/to/drupal --deep-scan --format json
|
|
109
117
|
```
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
from sys import exit
|
|
2
|
-
import asyncio
|
|
3
1
|
import json
|
|
4
2
|
import os
|
|
5
3
|
from argparse import ArgumentParser
|
|
4
|
+
from sys import exit
|
|
5
|
+
|
|
6
6
|
import jq
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
from .deep_scan import audit_modules_async
|
|
8
9
|
from .exceptions import *
|
|
10
|
+
from .formatters.formatterfactory import FormatterFactory
|
|
9
11
|
from .module import Module
|
|
10
|
-
from .workers_manager import WorkersManager
|
|
11
12
|
from .output import ConsoleOutputHandler, logger
|
|
13
|
+
from .workers_manager import WorkersManager
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
class Application:
|
|
@@ -19,7 +21,9 @@ class Application:
|
|
|
19
21
|
def __init__(self, output_handler=None):
|
|
20
22
|
self.output = output_handler or ConsoleOutputHandler()
|
|
21
23
|
self.__modules = {}
|
|
22
|
-
self.__drupal_core_version =
|
|
24
|
+
self.__drupal_core_version = (
|
|
25
|
+
"8.8" # default and minimal supported Drupal core version for upgrade
|
|
26
|
+
)
|
|
23
27
|
|
|
24
28
|
@property
|
|
25
29
|
def modules(self) -> dict:
|
|
@@ -61,7 +65,11 @@ class Application:
|
|
|
61
65
|
# Full environment scan
|
|
62
66
|
await self._run_environment_scan(args)
|
|
63
67
|
|
|
64
|
-
except (
|
|
68
|
+
except (
|
|
69
|
+
ComposerV1Exception,
|
|
70
|
+
DirectoryNotFoundException,
|
|
71
|
+
NoComposerJSONFileException,
|
|
72
|
+
) as e:
|
|
65
73
|
logger.warning(e.message)
|
|
66
74
|
exit(1)
|
|
67
75
|
|
|
@@ -90,10 +98,17 @@ class Application:
|
|
|
90
98
|
current_core=self.__drupal_core_version,
|
|
91
99
|
use_lock_version=use_lock_version,
|
|
92
100
|
concurrency_limit=args.limit,
|
|
93
|
-
output=self.output
|
|
101
|
+
output=self.output,
|
|
94
102
|
)
|
|
95
103
|
await workers_manager.run()
|
|
96
104
|
|
|
105
|
+
deep_scan_mode = getattr(args, "deep_scan", None)
|
|
106
|
+
if deep_scan_mode:
|
|
107
|
+
mode_str = "all" if deep_scan_mode is True else str(deep_scan_mode)
|
|
108
|
+
await audit_modules_async(
|
|
109
|
+
list(self.__modules.values()), args.directory, mode=mode_str
|
|
110
|
+
)
|
|
111
|
+
|
|
97
112
|
formatter = FormatterFactory.get_formatter(args)
|
|
98
113
|
if formatter:
|
|
99
114
|
self.output.print(formatter.format(list(self.__modules.values())))
|
|
@@ -124,7 +139,9 @@ class Application:
|
|
|
124
139
|
"""Scan all modules discovered from the Drupal project's composer files."""
|
|
125
140
|
# check if the directory exists and whether the composer.json file exists in it
|
|
126
141
|
if not os.path.isdir(args.directory):
|
|
127
|
-
raise DirectoryNotFoundException(
|
|
142
|
+
raise DirectoryNotFoundException(
|
|
143
|
+
f"The directory {args.directory} does not exist."
|
|
144
|
+
)
|
|
128
145
|
|
|
129
146
|
# check if the directory contains the composer.json file
|
|
130
147
|
if not os.path.isfile(os.path.join(args.directory, "composer.json")):
|
|
@@ -141,10 +158,14 @@ class Application:
|
|
|
141
158
|
self.get_required_modules(args)
|
|
142
159
|
|
|
143
160
|
if len(self.__modules) > 0:
|
|
144
|
-
if not args.no_lock and os.path.isfile(
|
|
161
|
+
if not args.no_lock and os.path.isfile(
|
|
162
|
+
os.path.join(args.directory, "composer.lock")
|
|
163
|
+
):
|
|
145
164
|
self.determine_module_versions(args)
|
|
146
165
|
elif args.no_lock:
|
|
147
|
-
logger.warning(
|
|
166
|
+
logger.warning(
|
|
167
|
+
"The composer.lock file was not used to determine the installed versions of the modules."
|
|
168
|
+
)
|
|
148
169
|
logger.warning(
|
|
149
170
|
"The only Drupal core version will be use to determine the transitive versions of the modules."
|
|
150
171
|
)
|
|
@@ -155,14 +176,22 @@ class Application:
|
|
|
155
176
|
current_core=self.__drupal_core_version,
|
|
156
177
|
use_lock_version=not args.no_lock,
|
|
157
178
|
concurrency_limit=args.limit,
|
|
158
|
-
output=self.output
|
|
179
|
+
output=self.output,
|
|
159
180
|
)
|
|
160
181
|
await workers_manager.run()
|
|
161
182
|
|
|
183
|
+
deep_scan_mode = getattr(args, "deep_scan", None)
|
|
184
|
+
if deep_scan_mode:
|
|
185
|
+
mode_str = "all" if deep_scan_mode is True else str(deep_scan_mode)
|
|
186
|
+
await audit_modules_async(
|
|
187
|
+
list(self.__modules.values()), args.directory, mode=mode_str
|
|
188
|
+
)
|
|
189
|
+
|
|
162
190
|
# output the results
|
|
163
191
|
formatter = FormatterFactory.get_formatter(args)
|
|
164
192
|
if formatter:
|
|
165
193
|
self.output.print(formatter.format(list(self.__modules.values())))
|
|
194
|
+
|
|
166
195
|
else:
|
|
167
196
|
logger.warning("No modules were found in the composer.json file.")
|
|
168
197
|
|
|
@@ -178,14 +207,14 @@ class Application:
|
|
|
178
207
|
"-v",
|
|
179
208
|
"--version",
|
|
180
209
|
action="version",
|
|
181
|
-
version="drupal-scout {
|
|
210
|
+
version=f"drupal-scout {self.get_version()}",
|
|
182
211
|
)
|
|
183
212
|
parser.add_argument(
|
|
184
213
|
"-d",
|
|
185
214
|
"--directory",
|
|
186
215
|
help="The directory of the Drupal installation.",
|
|
187
216
|
type=str,
|
|
188
|
-
default="."
|
|
217
|
+
default=".",
|
|
189
218
|
)
|
|
190
219
|
# special argument to support the composer.lock file
|
|
191
220
|
parser.add_argument(
|
|
@@ -193,15 +222,15 @@ class Application:
|
|
|
193
222
|
"--no-lock",
|
|
194
223
|
help="Do not use the composer.lock file to determine the installed versions of the modules.",
|
|
195
224
|
action="store_true",
|
|
196
|
-
default=False
|
|
225
|
+
default=False,
|
|
197
226
|
)
|
|
198
227
|
parser.add_argument(
|
|
199
228
|
"-l",
|
|
200
229
|
"--limit",
|
|
201
230
|
help="Maximum number of concurrent network requests. This prevents overwhelming the upstream API "
|
|
202
|
-
|
|
231
|
+
"and hitting rate limits. Default: 10.",
|
|
203
232
|
type=int,
|
|
204
|
-
default=10
|
|
233
|
+
default=10,
|
|
205
234
|
)
|
|
206
235
|
|
|
207
236
|
# "table" format is for human-readable output in the console
|
|
@@ -213,39 +242,51 @@ class Application:
|
|
|
213
242
|
"--format",
|
|
214
243
|
help="The output format. By default, the application will use the table format.",
|
|
215
244
|
choices=["table", "json", "suggest"],
|
|
216
|
-
default="table"
|
|
245
|
+
default="table",
|
|
217
246
|
)
|
|
218
247
|
|
|
219
248
|
# the following argument is only available for the "suggest" format
|
|
220
249
|
parser.add_argument(
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
help=
|
|
250
|
+
"-s",
|
|
251
|
+
"--save-dump",
|
|
252
|
+
help="Use in pair with --format suggest to dump the suggested composer.json file to the specified path.",
|
|
224
253
|
default=False,
|
|
225
|
-
action=
|
|
254
|
+
action="store_true",
|
|
226
255
|
)
|
|
227
256
|
|
|
228
257
|
parser.add_argument(
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
help=
|
|
232
|
-
|
|
258
|
+
"-c",
|
|
259
|
+
"--core",
|
|
260
|
+
help="Optional Drupal core version override for targeted module scans (e.g. 10.0.0). "
|
|
261
|
+
"If omitted, the core version is auto-detected from composer.lock/composer.json in --directory.",
|
|
233
262
|
type=str,
|
|
234
|
-
default=None
|
|
263
|
+
default=None,
|
|
235
264
|
)
|
|
236
265
|
|
|
237
266
|
parser.add_argument(
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
nargs=
|
|
241
|
-
help=
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
default=[]
|
|
267
|
+
"-m",
|
|
268
|
+
"--modules",
|
|
269
|
+
nargs="+",
|
|
270
|
+
help="One or more specific Drupal module names to scan (e.g. drupal/webform drupal/ctools). "
|
|
271
|
+
"When provided, the full environment scan is skipped. The tool still uses composer.lock from "
|
|
272
|
+
"--directory for installed-version protection when available.",
|
|
273
|
+
default=[],
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
parser.add_argument(
|
|
277
|
+
"--deep-scan",
|
|
278
|
+
dest="deep_scan",
|
|
279
|
+
nargs="?",
|
|
280
|
+
const="all",
|
|
281
|
+
default=None,
|
|
282
|
+
choices=["all", "patches", "git"],
|
|
283
|
+
help="Perform a read-only local deep scan (Git index, commit history, and Composer patches). Optional mode: all (default), patches, git.",
|
|
245
284
|
)
|
|
246
285
|
|
|
247
286
|
subparsers = parser.add_subparsers(dest="command")
|
|
248
|
-
|
|
287
|
+
subparsers.add_parser(
|
|
288
|
+
"info", help="Diagnostic information about the tool and environment"
|
|
289
|
+
)
|
|
249
290
|
|
|
250
291
|
return parser
|
|
251
292
|
|
|
@@ -256,14 +297,16 @@ class Application:
|
|
|
256
297
|
"""
|
|
257
298
|
import importlib.metadata
|
|
258
299
|
import re
|
|
259
|
-
|
|
300
|
+
|
|
260
301
|
try:
|
|
261
|
-
return importlib.metadata.version(
|
|
302
|
+
return importlib.metadata.version("drupal-scout")
|
|
262
303
|
except importlib.metadata.PackageNotFoundError:
|
|
263
304
|
try:
|
|
264
305
|
# relative path to find pyproject.toml in the source tree
|
|
265
306
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
266
|
-
pyproject_path = os.path.join(
|
|
307
|
+
pyproject_path = os.path.join(
|
|
308
|
+
os.path.dirname(current_dir), "pyproject.toml"
|
|
309
|
+
)
|
|
267
310
|
with open(pyproject_path, "r") as f:
|
|
268
311
|
content = f.read()
|
|
269
312
|
match = re.search(r'version\s*=\s*"([^"]+)"', content)
|
|
@@ -281,30 +324,55 @@ class Application:
|
|
|
281
324
|
|
|
282
325
|
version = self.get_version()
|
|
283
326
|
|
|
284
|
-
|
|
285
327
|
jq_status = "NOT FOUND OR NOT FUNCTIONAL"
|
|
286
328
|
try:
|
|
287
|
-
subprocess.run(
|
|
329
|
+
subprocess.run(
|
|
330
|
+
["jq", "--version"],
|
|
331
|
+
stdout=subprocess.DEVNULL,
|
|
332
|
+
stderr=subprocess.DEVNULL,
|
|
333
|
+
check=True,
|
|
334
|
+
)
|
|
288
335
|
jq_status = "FOUND and FUNCTIONAL"
|
|
289
336
|
except (subprocess.SubprocessError, FileNotFoundError):
|
|
290
337
|
pass
|
|
291
338
|
|
|
292
|
-
composer_json_str =
|
|
293
|
-
|
|
294
|
-
|
|
339
|
+
composer_json_str = (
|
|
340
|
+
"DETECTED"
|
|
341
|
+
if os.path.isfile(os.path.join(args.directory, "composer.json"))
|
|
342
|
+
else "NOT DETECTED"
|
|
343
|
+
)
|
|
344
|
+
composer_lock_str = (
|
|
345
|
+
"DETECTED"
|
|
346
|
+
if os.path.isfile(os.path.join(args.directory, "composer.lock"))
|
|
347
|
+
else "NOT DETECTED"
|
|
348
|
+
)
|
|
349
|
+
|
|
295
350
|
composer2_status_str = "DETECTED" if self.is_composer2(args) else "NOT DETECTED"
|
|
296
|
-
|
|
351
|
+
|
|
297
352
|
core_version = "[Unknown]"
|
|
298
353
|
if composer_json_str == "DETECTED":
|
|
299
354
|
try:
|
|
300
355
|
if composer_lock_str == "DETECTED":
|
|
301
|
-
args_mock = type(
|
|
356
|
+
args_mock = type(
|
|
357
|
+
"Args",
|
|
358
|
+
(object,),
|
|
359
|
+
{"no_lock": False, "directory": args.directory},
|
|
360
|
+
)()
|
|
302
361
|
else:
|
|
303
|
-
args_mock = type(
|
|
304
|
-
|
|
362
|
+
args_mock = type(
|
|
363
|
+
"Args",
|
|
364
|
+
(object,),
|
|
365
|
+
{"no_lock": True, "directory": args.directory},
|
|
366
|
+
)()
|
|
367
|
+
|
|
305
368
|
# Use contextlib for cleaner stream suppression
|
|
306
|
-
import contextlib
|
|
307
|
-
|
|
369
|
+
import contextlib
|
|
370
|
+
import io
|
|
371
|
+
|
|
372
|
+
with (
|
|
373
|
+
contextlib.redirect_stdout(io.StringIO()),
|
|
374
|
+
contextlib.redirect_stderr(io.StringIO()),
|
|
375
|
+
):
|
|
308
376
|
self.determine_drupal_core_version(args_mock)
|
|
309
377
|
# Use public property to avoid __ direct access if possible
|
|
310
378
|
core_version = self.drupal_core_version
|
|
@@ -317,7 +385,7 @@ class Application:
|
|
|
317
385
|
"composer.json": composer_json_str,
|
|
318
386
|
"composer.lock": composer_lock_str,
|
|
319
387
|
"Composer 2": composer2_status_str,
|
|
320
|
-
"Drupal Core Version": core_version
|
|
388
|
+
"Drupal Core Version": core_version,
|
|
321
389
|
}
|
|
322
390
|
self.output.render_info_table(f"Drupal Scout v{version} Status", status_data)
|
|
323
391
|
|
|
@@ -331,10 +399,11 @@ class Application:
|
|
|
331
399
|
"""
|
|
332
400
|
# check whether the vendor directory exists and has a composer/platform_check.php file
|
|
333
401
|
# because this clue is only available in Composer 2
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
402
|
+
return os.path.isdir(
|
|
403
|
+
os.path.join(args.directory, "vendor")
|
|
404
|
+
) and os.path.isfile(
|
|
405
|
+
os.path.join(args.directory, "vendor", "composer", "platform_check.php")
|
|
406
|
+
)
|
|
338
407
|
|
|
339
408
|
def determine_drupal_core_version(self, args):
|
|
340
409
|
"""
|
|
@@ -346,8 +415,13 @@ class Application:
|
|
|
346
415
|
if not args.no_lock:
|
|
347
416
|
with open(os.path.join(args.directory, "composer.lock"), "r") as f:
|
|
348
417
|
composer_lock = json.load(f)
|
|
349
|
-
self.__drupal_core_version =
|
|
350
|
-
.
|
|
418
|
+
self.__drupal_core_version = (
|
|
419
|
+
jq.compile(
|
|
420
|
+
'.packages[] | select(.name == "drupal/core") | .version'
|
|
421
|
+
)
|
|
422
|
+
.input(composer_lock)
|
|
423
|
+
.first()
|
|
424
|
+
)
|
|
351
425
|
else:
|
|
352
426
|
with open(os.path.join(args.directory, "composer.json"), "r") as f:
|
|
353
427
|
composer_json = json.load(f)
|
|
@@ -355,12 +429,18 @@ class Application:
|
|
|
355
429
|
# or within the "drupal/core-recommended" requirement
|
|
356
430
|
if "drupal/core" in composer_json["require"]:
|
|
357
431
|
# clear special characters from the version
|
|
358
|
-
self.__drupal_core_version =
|
|
359
|
-
|
|
432
|
+
self.__drupal_core_version = (
|
|
433
|
+
composer_json["require"]["drupal/core"]
|
|
434
|
+
.replace("^", "")
|
|
435
|
+
.replace("~", "")
|
|
436
|
+
)
|
|
360
437
|
elif "drupal/core-recommended" in composer_json["require"]:
|
|
361
438
|
# clear special characters from the version
|
|
362
|
-
self.__drupal_core_version =
|
|
363
|
-
|
|
439
|
+
self.__drupal_core_version = (
|
|
440
|
+
composer_json["require"]["drupal/core-recommended"]
|
|
441
|
+
.replace("^", "")
|
|
442
|
+
.replace("~", "")
|
|
443
|
+
)
|
|
364
444
|
logger.warning("The Drupal core version is: " + self.__drupal_core_version)
|
|
365
445
|
|
|
366
446
|
def get_required_modules(self, args):
|
|
@@ -374,8 +454,14 @@ class Application:
|
|
|
374
454
|
with open(os.path.join(args.directory, "composer.json"), "r") as f:
|
|
375
455
|
composer_json = json.load(f)
|
|
376
456
|
# load required modules, but only with drupal/* prefix and exclude modules with drupal/core prefix
|
|
377
|
-
found_modules =
|
|
378
|
-
|
|
457
|
+
found_modules = (
|
|
458
|
+
jq.compile(
|
|
459
|
+
'.require | keys | map(select(startswith("drupal/"))) | map(select('
|
|
460
|
+
'startswith("drupal/core") | not))'
|
|
461
|
+
)
|
|
462
|
+
.input(composer_json)
|
|
463
|
+
.first()
|
|
464
|
+
)
|
|
379
465
|
for module in found_modules:
|
|
380
466
|
self.__modules[module] = Module(module)
|
|
381
467
|
|
|
@@ -392,16 +478,20 @@ class Application:
|
|
|
392
478
|
|
|
393
479
|
with open(os.path.join(args.directory, "composer.lock"), "r") as f:
|
|
394
480
|
composer_lock = json.load(f)
|
|
395
|
-
for module_name in self.__modules
|
|
481
|
+
for module_name in self.__modules:
|
|
396
482
|
module = self.__modules.get(module_name)
|
|
397
483
|
if module is None:
|
|
398
484
|
continue
|
|
399
485
|
# look for the module with name in the packages array
|
|
400
486
|
module_version = None
|
|
401
487
|
try:
|
|
402
|
-
module_version =
|
|
403
|
-
|
|
404
|
-
|
|
488
|
+
module_version = (
|
|
489
|
+
jq.compile(
|
|
490
|
+
f'.packages | map(select(.name == "{module.name}")) | .[].version'
|
|
491
|
+
)
|
|
492
|
+
.input(composer_lock)
|
|
493
|
+
.first()
|
|
494
|
+
)
|
|
405
495
|
except StopIteration:
|
|
406
496
|
module_version = None
|
|
407
497
|
# save the module name and version in the versioned_modules array
|