git-format-staged 3.1.2 → 3.1.3
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.
- package/git-format-staged +242 -125
- package/package.json +1 -1
package/git-format-staged
CHANGED
|
@@ -20,60 +20,93 @@ from gettext import gettext as _
|
|
|
20
20
|
import os
|
|
21
21
|
import re
|
|
22
22
|
import shlex
|
|
23
|
+
import signal
|
|
23
24
|
import subprocess
|
|
24
25
|
import sys
|
|
25
26
|
|
|
26
|
-
# The string 3.1.
|
|
27
|
-
VERSION =
|
|
27
|
+
# The string 3.1.3 is replaced during the publish process.
|
|
28
|
+
VERSION = "3.1.3"
|
|
28
29
|
PROG = sys.argv[0]
|
|
29
30
|
|
|
31
|
+
|
|
30
32
|
def info(msg):
|
|
31
33
|
print(msg, file=sys.stdout)
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
|
|
36
|
+
def info_stderr(msg: str):
|
|
37
|
+
print(msg, file=sys.stderr)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def warn(msg: str):
|
|
41
|
+
print("{}: warning: {}".format(PROG, msg), file=sys.stderr)
|
|
42
|
+
|
|
35
43
|
|
|
36
44
|
def fatal(msg):
|
|
37
|
-
print(
|
|
45
|
+
print("{}: error: {}".format(PROG, msg), file=sys.stderr)
|
|
38
46
|
exit(1)
|
|
39
47
|
|
|
40
|
-
|
|
48
|
+
|
|
49
|
+
def format_staged_files(
|
|
50
|
+
file_patterns,
|
|
51
|
+
formatter,
|
|
52
|
+
git_root,
|
|
53
|
+
update_working_tree=True,
|
|
54
|
+
write=True,
|
|
55
|
+
verbose=False,
|
|
56
|
+
):
|
|
41
57
|
common_opts = [
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
58
|
+
"--cached",
|
|
59
|
+
"--diff-filter=AM", # select only file additions and modifications
|
|
60
|
+
"--no-renames",
|
|
61
|
+
"HEAD",
|
|
46
62
|
]
|
|
47
63
|
|
|
48
64
|
try:
|
|
49
65
|
staged_files = {
|
|
50
|
-
path.decode(
|
|
51
|
-
for path in subprocess.check_output(
|
|
66
|
+
path.decode("utf-8")
|
|
67
|
+
for path in subprocess.check_output(
|
|
68
|
+
["git", "diff", "--name-only"] + common_opts
|
|
69
|
+
).splitlines()
|
|
52
70
|
}
|
|
53
71
|
|
|
54
|
-
output = subprocess.check_output([
|
|
72
|
+
output = subprocess.check_output(["git", "diff-index"] + common_opts)
|
|
55
73
|
for line in output.splitlines():
|
|
56
|
-
entry = parse_diff(line.decode(
|
|
57
|
-
entry_path = normalize_path(entry[
|
|
58
|
-
if entry[
|
|
74
|
+
entry = parse_diff(line.decode("utf-8"))
|
|
75
|
+
entry_path = normalize_path(entry["src_path"], relative_to=git_root)
|
|
76
|
+
if entry["dst_mode"] == "120000":
|
|
59
77
|
# Do not process symlinks
|
|
60
78
|
continue
|
|
61
79
|
if not (matches_some_path(file_patterns, entry_path)):
|
|
62
80
|
continue
|
|
63
|
-
if
|
|
81
|
+
if (
|
|
82
|
+
entry["src_mode"] is None
|
|
83
|
+
and object_is_empty(entry["dst_hash"])
|
|
84
|
+
and entry["src_path"] not in staged_files
|
|
85
|
+
):
|
|
64
86
|
# File is not staged, it's tracked only with `--intent-to-add` and won't get committed
|
|
65
87
|
continue
|
|
66
|
-
if format_file_in_index(
|
|
67
|
-
|
|
88
|
+
if format_file_in_index(
|
|
89
|
+
formatter,
|
|
90
|
+
entry,
|
|
91
|
+
update_working_tree=update_working_tree,
|
|
92
|
+
write=write,
|
|
93
|
+
verbose=verbose,
|
|
94
|
+
):
|
|
95
|
+
info("Reformatted {} with {}".format(entry["src_path"], formatter))
|
|
68
96
|
except Exception as err:
|
|
69
97
|
fatal(str(err))
|
|
70
98
|
|
|
99
|
+
|
|
71
100
|
# Run formatter on file in the git index. Creates a new git object with the
|
|
72
101
|
# result, and replaces the content of the file in the index with that object.
|
|
73
102
|
# Returns hash of the new object if formatting produced any changes.
|
|
74
|
-
def format_file_in_index(
|
|
75
|
-
|
|
76
|
-
|
|
103
|
+
def format_file_in_index(
|
|
104
|
+
formatter, diff_entry, update_working_tree=True, write=True, verbose=False
|
|
105
|
+
):
|
|
106
|
+
orig_hash = diff_entry["dst_hash"]
|
|
107
|
+
new_hash = format_object(
|
|
108
|
+
formatter, orig_hash, diff_entry["src_path"], verbose=verbose
|
|
109
|
+
)
|
|
77
110
|
|
|
78
111
|
# If the new hash is the same then the formatter did not make any changes.
|
|
79
112
|
if not write or new_hash == orig_hash:
|
|
@@ -89,130 +122,210 @@ def format_file_in_index(formatter, diff_entry, update_working_tree=True, write=
|
|
|
89
122
|
|
|
90
123
|
if update_working_tree:
|
|
91
124
|
try:
|
|
92
|
-
patch_working_file(diff_entry[
|
|
125
|
+
patch_working_file(diff_entry["src_path"], orig_hash, new_hash)
|
|
93
126
|
except Exception as err:
|
|
94
127
|
# Errors patching working tree files are not fatal
|
|
95
128
|
warn(str(err))
|
|
96
129
|
|
|
97
130
|
return new_hash
|
|
98
131
|
|
|
132
|
+
|
|
99
133
|
# Match {}, and to avoid breaking quoting from shlex also match and remove surrounding quotes. This
|
|
100
134
|
# is important for backward compatibility because previous version of git-format-staged did not use
|
|
101
135
|
# shlex quoting, and required manual quoting.
|
|
102
136
|
file_path_placeholder = re.compile(r"(['\"]?)\{\}(\1)")
|
|
103
137
|
|
|
138
|
+
|
|
104
139
|
# Run formatter on a git blob identified by its hash. Writes output to a new git
|
|
105
140
|
# blob, and returns the hash of the new blob.
|
|
106
141
|
def format_object(formatter, object_hash, file_path, verbose=False):
|
|
107
142
|
get_content = subprocess.Popen(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
143
|
+
["git", "cat-file", "-p", object_hash], stdout=subprocess.PIPE
|
|
144
|
+
)
|
|
145
|
+
|
|
111
146
|
command = re.sub(file_path_placeholder, shlex.quote(file_path), formatter)
|
|
112
147
|
if verbose:
|
|
113
|
-
|
|
148
|
+
info_stderr(command)
|
|
114
149
|
format_content = subprocess.Popen(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
stdin=get_content.stdout,
|
|
118
|
-
stdout=subprocess.PIPE
|
|
119
|
-
)
|
|
120
|
-
write_object = subprocess.Popen(
|
|
121
|
-
['git', 'hash-object', '-w', '--stdin'],
|
|
122
|
-
stdin=format_content.stdout,
|
|
123
|
-
stdout=subprocess.PIPE
|
|
124
|
-
)
|
|
150
|
+
command, shell=True, stdin=get_content.stdout, stdout=subprocess.PIPE
|
|
151
|
+
)
|
|
125
152
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
153
|
+
write_object = subprocess.Popen(
|
|
154
|
+
["git", "hash-object", "-w", "--stdin"],
|
|
155
|
+
stdin=format_content.stdout,
|
|
156
|
+
stdout=subprocess.PIPE,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# Close the parent process reference to stdout, leaving only references in the child processes.
|
|
160
|
+
# This way if the downstream process terminates while format_content is still running,
|
|
161
|
+
# format_content will be terminated with a SIGPIPE signal.
|
|
162
|
+
format_content.stdout.close() # pyright: ignore[reportOptionalMemberAccess]
|
|
163
|
+
|
|
164
|
+
# On the other hand we don't close get_content.stdout() so that we can check if there is unread
|
|
165
|
+
# data left after the formatter has finished.
|
|
166
|
+
|
|
167
|
+
# Read output from the last process in the pipe, and block until that process has completed.
|
|
168
|
+
# It's important to block on the last process completing before waiting for the other sub
|
|
169
|
+
# processes to finish.
|
|
170
|
+
new_hash, _err = write_object.communicate()
|
|
171
|
+
|
|
172
|
+
# The first two pipe processes should have completed by now. Block to verify that we have exit
|
|
173
|
+
# statuses from them.
|
|
174
|
+
try:
|
|
175
|
+
# Use communicate() to check for any unread output from get_content.
|
|
176
|
+
get_content_unread_stdout, _ = get_content.communicate(timeout=5)
|
|
177
|
+
get_content.stdout.close() # pyright: ignore[reportOptionalMemberAccess]
|
|
178
|
+
format_content_exit_status = format_content.wait(timeout=5)
|
|
179
|
+
except subprocess.TimeoutExpired as exception:
|
|
180
|
+
raise Exception(
|
|
181
|
+
"the formatter command did not terminate as expected"
|
|
182
|
+
) from exception
|
|
183
|
+
|
|
184
|
+
# An error from format_content is most relevant to the user, so prioritize displaying this error
|
|
185
|
+
# message in case multiple things went wrong.
|
|
186
|
+
if format_content_exit_status != 0:
|
|
187
|
+
raise Exception(
|
|
188
|
+
f"formatter exited with non-zero status ({format_content_exit_status}) while processing {file_path}"
|
|
189
|
+
)
|
|
129
190
|
|
|
130
|
-
|
|
131
|
-
|
|
191
|
+
# If the formatter exited before reading all input then get_content might have been terminated
|
|
192
|
+
# by a SIGPIPE signal. This is probably incorrect behavior from the formatter command, but is
|
|
193
|
+
# allowed by design (for now). So we emit a warning, but will not fail.
|
|
194
|
+
|
|
195
|
+
# If there was unread output from get_content that's an indication that the formatter command is
|
|
196
|
+
# probably not configured correctly. But program design allows the formatter command do do what
|
|
197
|
+
# it wants. So this is a warning, not a hard error.
|
|
198
|
+
#
|
|
199
|
+
# A SIGPIPE termination to get_content would also indicate unread output. This should not
|
|
200
|
+
# happen, but it doesn't hurt to check.
|
|
201
|
+
if len(get_content_unread_stdout) > 0 or get_content.returncode == -signal.SIGPIPE:
|
|
202
|
+
warn(
|
|
203
|
+
f"the formatter command exited before reading all content from {file_path}"
|
|
204
|
+
)
|
|
132
205
|
|
|
133
|
-
if
|
|
134
|
-
|
|
206
|
+
if get_content.returncode != 0 and get_content.returncode != -signal.SIGPIPE:
|
|
207
|
+
if verbose:
|
|
208
|
+
info_stderr(
|
|
209
|
+
f"non-zero exit status from `git cat-file -p {object_hash}`\n"
|
|
210
|
+
+ f"exit status: {get_content.returncode}\n"
|
|
211
|
+
+ f"file path: {file_path}\n"
|
|
212
|
+
)
|
|
213
|
+
raise ValueError(
|
|
214
|
+
f"unable to read file content for {file_path} from object database."
|
|
215
|
+
)
|
|
135
216
|
|
|
136
217
|
if write_object.returncode != 0:
|
|
137
|
-
|
|
218
|
+
if verbose:
|
|
219
|
+
info_stderr(
|
|
220
|
+
f"non-zero exit status from `git hash-object -w --stdin`\n"
|
|
221
|
+
+ f"exit status: {write_object.returncode}\n"
|
|
222
|
+
+ f"file path: {file_path}\n"
|
|
223
|
+
)
|
|
224
|
+
raise Exception("unable to write formatted content to object database")
|
|
225
|
+
|
|
226
|
+
return new_hash.decode("utf-8").rstrip()
|
|
138
227
|
|
|
139
|
-
return new_hash.decode('utf-8').rstrip()
|
|
140
228
|
|
|
141
229
|
def object_is_empty(object_hash):
|
|
142
230
|
get_content = subprocess.Popen(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
)
|
|
231
|
+
["git", "cat-file", "-p", object_hash], stdout=subprocess.PIPE
|
|
232
|
+
)
|
|
146
233
|
content, err = get_content.communicate()
|
|
147
234
|
|
|
148
235
|
if get_content.returncode != 0:
|
|
149
|
-
raise Exception(
|
|
236
|
+
raise Exception("unable to verify content of formatted object")
|
|
150
237
|
|
|
151
238
|
return not content
|
|
152
239
|
|
|
240
|
+
|
|
153
241
|
def replace_file_in_index(diff_entry, new_object_hash):
|
|
154
|
-
subprocess.check_call(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
242
|
+
subprocess.check_call(
|
|
243
|
+
[
|
|
244
|
+
"git",
|
|
245
|
+
"update-index",
|
|
246
|
+
"--cacheinfo",
|
|
247
|
+
"{},{},{}".format(
|
|
248
|
+
diff_entry["dst_mode"], new_object_hash, diff_entry["src_path"]
|
|
249
|
+
),
|
|
250
|
+
]
|
|
251
|
+
)
|
|
252
|
+
|
|
160
253
|
|
|
161
254
|
def patch_working_file(path, orig_object_hash, new_object_hash):
|
|
162
255
|
patch = subprocess.check_output(
|
|
163
|
-
|
|
164
|
-
|
|
256
|
+
[
|
|
257
|
+
"git",
|
|
258
|
+
"diff",
|
|
259
|
+
"--no-ext-diff",
|
|
260
|
+
"--color=never",
|
|
261
|
+
orig_object_hash,
|
|
262
|
+
new_object_hash,
|
|
263
|
+
]
|
|
264
|
+
)
|
|
165
265
|
|
|
166
266
|
# Substitute object hashes in patch header with path to working tree file
|
|
167
|
-
patch_b = patch.replace(orig_object_hash.encode(), path.encode()).replace(
|
|
267
|
+
patch_b = patch.replace(orig_object_hash.encode(), path.encode()).replace(
|
|
268
|
+
new_object_hash.encode(), path.encode()
|
|
269
|
+
)
|
|
168
270
|
|
|
169
271
|
apply_patch = subprocess.Popen(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
272
|
+
["git", "apply", "-"],
|
|
273
|
+
stdin=subprocess.PIPE,
|
|
274
|
+
stdout=subprocess.PIPE,
|
|
275
|
+
stderr=subprocess.PIPE,
|
|
276
|
+
)
|
|
175
277
|
|
|
176
278
|
output, err = apply_patch.communicate(input=patch_b)
|
|
177
279
|
|
|
178
280
|
if apply_patch.returncode != 0:
|
|
179
|
-
raise Exception(
|
|
281
|
+
raise Exception(
|
|
282
|
+
"could not apply formatting changes to working tree file {}".format(path)
|
|
283
|
+
)
|
|
284
|
+
|
|
180
285
|
|
|
181
286
|
# Format: src_mode dst_mode src_hash dst_hash status/score? src_path dst_path?
|
|
182
|
-
diff_pat = re.compile(
|
|
287
|
+
diff_pat = re.compile(
|
|
288
|
+
r"^:(\d+) (\d+) ([a-f0-9]+) ([a-f0-9]+) ([A-Z])(\d+)?\t([^\t]+)(?:\t([^\t]+))?$"
|
|
289
|
+
)
|
|
290
|
+
|
|
183
291
|
|
|
184
292
|
# Parse output from `git diff-index`
|
|
185
293
|
def parse_diff(diff):
|
|
186
294
|
m = diff_pat.match(diff)
|
|
187
295
|
if not m:
|
|
188
|
-
raise ValueError(
|
|
296
|
+
raise ValueError("Failed to parse diff-index line: " + diff)
|
|
189
297
|
return {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
298
|
+
"src_mode": unless_zeroed(m.group(1)),
|
|
299
|
+
"dst_mode": unless_zeroed(m.group(2)),
|
|
300
|
+
"src_hash": unless_zeroed(m.group(3)),
|
|
301
|
+
"dst_hash": unless_zeroed(m.group(4)),
|
|
302
|
+
"status": m.group(5),
|
|
303
|
+
"score": int(m.group(6)) if m.group(6) else None,
|
|
304
|
+
"src_path": m.group(7),
|
|
305
|
+
"dst_path": m.group(8),
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
zeroed_pat = re.compile(r"^0+$")
|
|
310
|
+
|
|
201
311
|
|
|
202
312
|
# Returns the argument unless the argument is a string of zeroes, in which case
|
|
203
313
|
# returns `None`
|
|
204
314
|
def unless_zeroed(s):
|
|
205
315
|
return s if not zeroed_pat.match(s) else None
|
|
206
316
|
|
|
317
|
+
|
|
207
318
|
def get_git_root():
|
|
208
|
-
return
|
|
209
|
-
|
|
210
|
-
|
|
319
|
+
return (
|
|
320
|
+
subprocess.check_output(["git", "rev-parse", "--show-toplevel"])
|
|
321
|
+
.decode("utf-8")
|
|
322
|
+
.rstrip()
|
|
323
|
+
)
|
|
324
|
+
|
|
211
325
|
|
|
212
326
|
def normalize_path(p, relative_to=None):
|
|
213
|
-
return os.path.abspath(
|
|
214
|
-
|
|
215
|
-
)
|
|
327
|
+
return os.path.abspath(os.path.join(relative_to, p) if relative_to else p)
|
|
328
|
+
|
|
216
329
|
|
|
217
330
|
def matches_some_path(patterns, target):
|
|
218
331
|
is_match = False
|
|
@@ -222,70 +335,74 @@ def matches_some_path(patterns, target):
|
|
|
222
335
|
is_match = is_pattern_positive
|
|
223
336
|
return is_match
|
|
224
337
|
|
|
338
|
+
|
|
225
339
|
# Checks for a '!' as the first character of a pattern, returns the rest of the
|
|
226
340
|
# pattern in a tuple. The tuple takes the form (is_pattern_positive, pattern).
|
|
227
341
|
# For example:
|
|
228
342
|
# from_signed_pattern('!pat') == (False, 'pat')
|
|
229
343
|
# from_signed_pattern('pat') == (True, 'pat')
|
|
230
344
|
def from_signed_pattern(pattern):
|
|
231
|
-
if pattern[0] ==
|
|
345
|
+
if pattern[0] == "!":
|
|
232
346
|
return (False, pattern[1:])
|
|
233
347
|
else:
|
|
234
348
|
return (True, pattern)
|
|
235
349
|
|
|
350
|
+
|
|
236
351
|
class CustomArgumentParser(argparse.ArgumentParser):
|
|
237
352
|
def parse_args(self, args=None, namespace=None):
|
|
238
353
|
args, argv = self.parse_known_args(args, namespace)
|
|
239
354
|
if argv:
|
|
240
355
|
msg = argparse._(
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
self.error(msg %
|
|
356
|
+
"unrecognized arguments: %s. Do you need to quote your formatter command?"
|
|
357
|
+
)
|
|
358
|
+
self.error(msg % " ".join(argv))
|
|
244
359
|
return args
|
|
245
360
|
|
|
246
|
-
|
|
361
|
+
|
|
362
|
+
if __name__ == "__main__":
|
|
247
363
|
parser = CustomArgumentParser(
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
364
|
+
description="Transform staged files using a formatting command that accepts content via stdin and produces a result via stdout.",
|
|
365
|
+
epilog='Example: %(prog)s --formatter "prettier --stdin-filepath {}" "src/*.js" "test/*.js"',
|
|
366
|
+
)
|
|
251
367
|
parser.add_argument(
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
368
|
+
"--formatter",
|
|
369
|
+
"-f",
|
|
370
|
+
required=True,
|
|
371
|
+
help='Shell command to format files, will run once per file. Occurrences of the placeholder `{}` will be replaced with a path to the file being formatted (with appropriate quoting). (Example: "prettier --stdin-filepath {}")',
|
|
372
|
+
)
|
|
256
373
|
parser.add_argument(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
374
|
+
"--no-update-working-tree",
|
|
375
|
+
action="store_true",
|
|
376
|
+
help="By default formatting changes made to staged file content will also be applied to working tree files via a patch. This option disables that behavior, leaving working tree files untouched.",
|
|
377
|
+
)
|
|
261
378
|
parser.add_argument(
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
379
|
+
"--no-write",
|
|
380
|
+
action="store_true",
|
|
381
|
+
help='Prevents %(prog)s from modifying staged or working tree files. You can use this option to check staged changes with a linter instead of formatting. With this option stdout from the formatter command is ignored. Example: %(prog)s --no-write -f "eslint --stdin --stdin-filename {} >&2" "*.js"',
|
|
382
|
+
)
|
|
266
383
|
parser.add_argument(
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
384
|
+
"--version",
|
|
385
|
+
action="version",
|
|
386
|
+
version="%(prog)s version {}".format(VERSION),
|
|
387
|
+
help="Display version of %(prog)s",
|
|
388
|
+
)
|
|
272
389
|
parser.add_argument(
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
390
|
+
"--verbose",
|
|
391
|
+
help="Show the formatting commands that are running",
|
|
392
|
+
action="store_true",
|
|
393
|
+
)
|
|
277
394
|
parser.add_argument(
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
395
|
+
"files",
|
|
396
|
+
nargs="+",
|
|
397
|
+
help='Patterns that specify files to format. The formatter will only transform staged files that are given here. Patterns may be literal file paths, or globs which will be tested against staged file paths using Python\'s fnmatch function. For example "src/*.js" will match all files with a .js extension in src/ and its subdirectories. Patterns may be negated to exclude files using a "!" character. Patterns are evaluated left-to-right. (Example: "main.js" "src/*.js" "test/*.js" "!test/todo/*")',
|
|
398
|
+
)
|
|
282
399
|
args = parser.parse_args()
|
|
283
|
-
files = vars(args)[
|
|
400
|
+
files = vars(args)["files"]
|
|
284
401
|
format_staged_files(
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
402
|
+
file_patterns=files,
|
|
403
|
+
formatter=vars(args)["formatter"],
|
|
404
|
+
git_root=get_git_root(),
|
|
405
|
+
update_working_tree=not vars(args)["no_update_working_tree"],
|
|
406
|
+
write=not vars(args)["no_write"],
|
|
407
|
+
verbose=vars(args)["verbose"],
|
|
408
|
+
)
|
package/package.json
CHANGED