diff options
Diffstat (limited to 'tools/esmify/mach_commands.py')
-rw-r--r-- | tools/esmify/mach_commands.py | 908 |
1 files changed, 0 insertions, 908 deletions
diff --git a/tools/esmify/mach_commands.py b/tools/esmify/mach_commands.py deleted file mode 100644 index 7b72c7b0e3..0000000000 --- a/tools/esmify/mach_commands.py +++ /dev/null @@ -1,908 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -import json -import logging -import os -import pathlib -import re -import subprocess -import sys - -from mach.decorators import Command, CommandArgument - - -def path_sep_to_native(path_str): - """Make separators in the path OS native.""" - return pathlib.os.sep.join(path_str.split("/")) - - -def path_sep_from_native(path): - """Make separators in the path OS native.""" - return "/".join(str(path).split(pathlib.os.sep)) - - -excluded_from_convert_prefix = list( - map( - path_sep_to_native, - [ - # Testcases for actors. - "toolkit/actors/TestProcessActorChild.jsm", - "toolkit/actors/TestProcessActorParent.jsm", - "toolkit/actors/TestWindowChild.jsm", - "toolkit/actors/TestWindowParent.jsm", - "js/xpconnect/tests/unit/", - # Testcase for build system. - "python/mozbuild/mozbuild/test/", - ], - ) -) - - -def is_excluded_from_convert(path): - """Returns true if the JSM file shouldn't be converted to ESM.""" - path_str = str(path) - for prefix in excluded_from_convert_prefix: - if path_str.startswith(prefix): - return True - - return False - - -excluded_from_imports_prefix = list( - map( - path_sep_to_native, - [ - # Vendored or auto-generated files. - "browser/components/pocket/content/panels/js/vendor.bundle.js", - "devtools/client/debugger/dist/parser-worker.js", - "devtools/client/debugger/test/mochitest/examples/react/build/main.js", - "devtools/client/debugger/test/mochitest/examples/sourcemapped/polyfill-bundle.js", - "devtools/client/inspector/markup/test/shadowdom_open_debugger.min.js", - "devtools/client/shared/source-map-loader/test/browser/fixtures/bundle.js", - "layout/style/test/property_database.js", - "services/fxaccounts/FxAccountsPairingChannel.js", - "testing/web-platform/", - # Unrelated testcases that has edge case syntax. - "browser/components/sessionstore/test/unit/data/", - "devtools/client/debugger/src/workers/parser/tests/fixtures/", - "devtools/client/debugger/test/mochitest/examples/sourcemapped/fixtures/", - "devtools/client/webconsole/test/browser/test-syntaxerror-worklet.js", - "devtools/server/tests/xpcshell/test_framebindings-03.js", - "devtools/server/tests/xpcshell/test_framebindings-04.js", - "devtools/shared/tests/xpcshell/test_eventemitter_basic.js", - "devtools/shared/tests/xpcshell/test_eventemitter_static.js", - "dom/base/crashtests/module-with-syntax-error.js", - "dom/base/test/file_bug687859-16.js", - "dom/base/test/file_bug687859-16.js", - "dom/base/test/file_js_cache_syntax_error.js", - "dom/base/test/jsmodules/module_badSyntax.js", - "dom/canvas/test/reftest/webgl-utils.js", - "dom/encoding/test/file_utf16_be_bom.js", - "dom/encoding/test/file_utf16_le_bom.js", - "dom/html/test/bug649134/file_bug649134-1.sjs", - "dom/html/test/bug649134/file_bug649134-2.sjs", - "dom/media/webrtc/tests/mochitests/identity/idp-bad.js", - "dom/serviceworkers/test/file_js_cache_syntax_error.js", - "dom/serviceworkers/test/parse_error_worker.js", - "dom/workers/test/importScripts_worker_imported3.js", - "dom/workers/test/invalid.js", - "dom/workers/test/threadErrors_worker1.js", - "dom/xhr/tests/browser_blobFromFile.js", - "image/test/browser/browser_image.js", - "js/xpconnect/tests/chrome/test_bug732665_meta.js", - "js/xpconnect/tests/mochitest/class_static_worker.js", - "js/xpconnect/tests/unit/bug451678_subscript.js", - "js/xpconnect/tests/unit/error_other.sys.mjs", - "js/xpconnect/tests/unit/es6module_parse_error.js", - "js/xpconnect/tests/unit/recursive_importA.jsm", - "js/xpconnect/tests/unit/recursive_importB.jsm", - "js/xpconnect/tests/unit/syntax_error.jsm", - "js/xpconnect/tests/unit/test_defineModuleGetter.js", - "js/xpconnect/tests/unit/test_import.js", - "js/xpconnect/tests/unit/test_import_shim.js", - "js/xpconnect/tests/unit/test_recursive_import.js", - "js/xpconnect/tests/unit/test_unload.js", - "modules/libpref/test/unit/data/testParser.js", - "python/mozbuild/mozbuild/test/", - "remote/shared/messagehandler/test/browser/resources/modules/root/invalid.sys.mjs", - "testing/talos/talos/startup_test/sessionrestore/profile-manywindows/sessionstore.js", - "testing/talos/talos/startup_test/sessionrestore/profile/sessionstore.js", - "toolkit/components/reader/Readerable.sys.mjs", - "toolkit/components/workerloader/tests/moduleF-syntax-error.js", - "tools/lint/test/", - "tools/update-packaging/test/", - # SpiderMonkey internals. - "js/examples/", - "js/src/", - # Files has macro. - "browser/app/profile/firefox.js", - "browser/branding/official/pref/firefox-branding.js", - "browser/components/enterprisepolicies/schemas/schema.sys.mjs", - "browser/locales/en-US/firefox-l10n.js", - "mobile/android/app/geckoview-prefs.js", - "mobile/android/locales/en-US/mobile-l10n.js", - "modules/libpref/greprefs.js", - "modules/libpref/init/all.js", - "testing/condprofile/condprof/tests/profile/user.js", - "testing/mozbase/mozprofile/tests/files/prefs_with_comments.js", - "toolkit/modules/AppConstants.sys.mjs", - "toolkit/mozapps/update/tests/data/xpcshellConstantsPP.js", - # Uniffi templates - "toolkit/components/uniffi-bindgen-gecko-js/src/templates/js/", - ], - ) -) - -EXCLUSION_FILES = [ - os.path.join("tools", "rewriting", "Generated.txt"), - os.path.join("tools", "rewriting", "ThirdPartyPaths.txt"), -] - - -def load_exclusion_files(): - for path in EXCLUSION_FILES: - with open(path, "r") as f: - for line in f: - p = path_sep_to_native(re.sub(r"\*$", "", line.strip())) - excluded_from_imports_prefix.append(p) - - -def is_excluded_from_imports(path): - """Returns true if the JS file content shouldn't be handled by - jscodeshift. - - This filter is necessary because jscodeshift cannot handle some - syntax edge cases and results in unexpected rewrite.""" - path_str = str(path) - for prefix in excluded_from_imports_prefix: - if path_str.startswith(prefix): - return True - - return False - - -# Wrapper for hg/git operations -class VCSUtils: - def run(self, cmd): - # Do not pass check=True because the pattern can match no file. - lines = subprocess.run(cmd, stdout=subprocess.PIPE).stdout.decode() - return filter(lambda x: x != "", lines.split("\n")) - - -class HgUtils(VCSUtils): - def is_available(): - return pathlib.Path(".hg").exists() - - def rename(self, before, after): - cmd = ["hg", "rename", before, after] - subprocess.run(cmd, check=True) - - def find_jsms(self, path): - jsms = [] - - # NOTE: `set:glob:` syntax does not accept backslash on windows. - path = path_sep_from_native(path) - - cmd = ["hg", "files", f'set:glob:"{path}/**/*.jsm"'] - for line in self.run(cmd): - jsm = pathlib.Path(line) - if is_excluded_from_convert(jsm): - continue - jsms.append(jsm) - - cmd = [ - "hg", - "files", - rf"set:grep('EXPORTED_SYMBOLS = \[') and glob:\"{path}/**/*.js\"", - ] - for line in self.run(cmd): - jsm = pathlib.Path(line) - if is_excluded_from_convert(jsm): - continue - jsms.append(jsm) - - return jsms - - def find_all_jss(self, path): - jss = [] - - # NOTE: `set:glob:` syntax does not accept backslash on windows. - path = path_sep_from_native(path) - - cmd = [ - "hg", - "files", - f'set:glob:"{path}/**/*.jsm" or glob:"{path}/**/*.js" or ' - + f'glob:"{path}/**/*.mjs" or glob:"{path}/**/*.sjs"', - ] - for line in self.run(cmd): - js = pathlib.Path(line) - if is_excluded_from_imports(js): - continue - jss.append(js) - - return jss - - -class GitUtils(VCSUtils): - def is_available(): - return pathlib.Path(".git").exists() - - def rename(self, before, after): - cmd = ["git", "mv", before, after] - subprocess.run(cmd, check=True) - - def find_jsms(self, path): - jsms = [] - - cmd = ["git", "ls-files", f"{path}/*.jsm"] - for line in self.run(cmd): - jsm = pathlib.Path(line) - if is_excluded_from_convert(jsm): - continue - jsms.append(jsm) - - handled = {} - cmd = ["git", "grep", "EXPORTED_SYMBOLS = \\[", f"{path}/*.js"] - for line in self.run(cmd): - m = re.search("^([^:]+):", line) - if not m: - continue - filename = m.group(1) - if filename in handled: - continue - handled[filename] = True - jsm = pathlib.Path(filename) - if is_excluded_from_convert(jsm): - continue - jsms.append(jsm) - - return jsms - - def find_all_jss(self, path): - jss = [] - - cmd = [ - "git", - "ls-files", - f"{path}/*.jsm", - f"{path}/*.js", - f"{path}/*.mjs", - f"{path}/*.sjs", - ] - for line in self.run(cmd): - js = pathlib.Path(line) - if is_excluded_from_imports(js): - continue - jss.append(js) - - return jss - - -class Summary: - def __init__(self): - self.convert_errors = [] - self.import_errors = [] - self.rename_errors = [] - self.no_refs = [] - - -@Command( - "esmify", - category="misc", - description="ESMify JSM files.", -) -@CommandArgument( - "path", - nargs=1, - help="Path to the JSM file to ESMify, or the directory that contains " - "JSM files and/or JS files that imports ESM-ified JSM.", -) -@CommandArgument( - "--convert", - action="store_true", - help="Only perform the step 1 = convert part", -) -@CommandArgument( - "--imports", - action="store_true", - help="Only perform the step 2 = import calls part", -) -@CommandArgument( - "--prefix", - default="", - help="Restrict the target of import in the step 2 to ESM-ified JSM, by the " - "prefix match for the JSM file's path. e.g. 'browser/'.", -) -def esmify(command_context, path=None, convert=False, imports=False, prefix=""): - """ - This command does the following 2 steps: - 1. Convert the JSM file specified by `path` to ESM file, or the JSM files - inside the directory specified by `path` to ESM files, and also - fix references in build files and test definitions - 2. Convert import calls inside file(s) specified by `path` for ESM-ified - files to use new APIs - - Example 1: - # Convert all JSM files inside `browser/components/pagedata` directory, - # and replace all references for ESM-ified files in the entire tree to use - # new APIs - - $ ./mach esmify --convert browser/components/pagedata - $ ./mach esmify --imports . --prefix=browser/components/pagedata - - Example 2: - # Convert all JSM files inside `browser` directory, and replace all - # references for the JSM files inside `browser` directory to use - # new APIs - - $ ./mach esmify browser - """ - - def error(text): - command_context.log(logging.ERROR, "esmify", {}, f"[ERROR] {text}") - - def warn(text): - command_context.log(logging.WARN, "esmify", {}, f"[WARN] {text}") - - def info(text): - command_context.log(logging.INFO, "esmify", {}, f"[INFO] {text}") - - # If no options is specified, perform both. - if not convert and not imports: - convert = True - imports = True - - path = pathlib.Path(path[0]) - - if not verify_path(command_context, path): - return 1 - - if HgUtils.is_available(): - vcs_utils = HgUtils() - elif GitUtils.is_available(): - vcs_utils = GitUtils() - else: - error( - "This script needs to be run inside mozilla-central " - "checkout of either mercurial or git." - ) - return 1 - - load_exclusion_files() - - info("Setting up jscodeshift...") - setup_jscodeshift() - - is_single_file = path.is_file() - - modified_files = [] - summary = Summary() - - if convert: - info("Searching files to convert to ESM...") - if is_single_file: - jsms = [path] - else: - jsms = vcs_utils.find_jsms(path) - - info(f"Found {len(jsms)} file(s) to convert to ESM.") - - info("Converting to ESM...") - jsms = convert_module(jsms, summary) - if jsms is None: - error("Failed to rewrite exports.") - return 1 - - info("Renaming...") - esms = rename_jsms(command_context, vcs_utils, jsms, summary) - - modified_files += esms - - if imports: - info("Searching files to rewrite imports...") - - if is_single_file: - if convert: - # Already converted above - jss = esms - else: - jss = [path] - else: - jss = vcs_utils.find_all_jss(path) - - info(f"Checking {len(jss)} JS file(s). Rewriting any matching imports...") - - result = rewrite_imports(jss, prefix, summary) - if result is None: - return 1 - - info(f"Rewritten {len(result)} file(s).") - - # Only modified files needs eslint fix - modified_files += result - - modified_files = list(set(modified_files)) - - info(f"Applying eslint --fix for {len(modified_files)} file(s)...") - eslint_fix(command_context, modified_files) - - def print_files(f, errors): - for [path, message] in errors: - f(f" * {path}") - if message: - f(f" {message}") - - if len(summary.convert_errors): - error("========") - error("Following files are not converted into ESM due to error:") - print_files(error, summary.convert_errors) - - if len(summary.import_errors): - warn("========") - warn("Following files are not rewritten to import ESMs due to error:") - warn( - "(NOTE: Errors related to 'private names' are mostly due to " - " preprocessor macros in the file):" - ) - print_files(warn, summary.import_errors) - - if len(summary.rename_errors): - error("========") - error("Following files are not renamed due to error:") - print_files(error, summary.rename_errors) - - if len(summary.no_refs): - warn("========") - warn("Following files are not found in any build files.") - warn("Please update references to those files manually:") - print_files(warn, summary.rename_errors) - - return 0 - - -def verify_path(command_context, path): - """Check if the path passed to the command is valid relative path.""" - - def error(text): - command_context.log(logging.ERROR, "esmify", {}, f"[ERROR] {text}") - - if not path.exists(): - error(f"{path} does not exist.") - return False - - if path.is_absolute(): - error("Path must be a relative path from mozilla-central checkout.") - return False - - return True - - -def find_file(path, target): - """Find `target` file in ancestor of path.""" - target_path = path.parent / target - if not target_path.exists(): - if path.parent == path: - return None - - return find_file(path.parent, target) - - return target_path - - -def try_rename_in(command_context, path, target, jsm_name, esm_name, jsm_path): - """Replace the occurrences of `jsm_name` with `esm_name` in `target` - file.""" - - def info(text): - command_context.log(logging.INFO, "esmify", {}, f"[INFO] {text}") - - if type(target) is str: - # Target is specified by filename, that may exist somewhere in - # the jsm's directory or ancestor directories. - target_path = find_file(path, target) - if not target_path: - return False - - # JSM should be specified with relative path in the file. - # - # Single moz.build or jar.mn can contain multiple files with same name. - # Search for relative path. - jsm_relative_path = jsm_path.relative_to(target_path.parent) - jsm_path_str = path_sep_from_native(str(jsm_relative_path)) - else: - # Target is specified by full path. - target_path = target - - # JSM should be specified with full path in the file. - jsm_path_str = path_sep_from_native(str(jsm_path)) - - jsm_path_re = re.compile(r"\b" + jsm_path_str.replace(".", r"\.") + r"\b") - jsm_name_re = re.compile(r"\b" + jsm_name.replace(".", r"\.") + r"\b") - - modified = False - content = "" - with open(target_path, "r") as f: - for line in f: - if jsm_path_re.search(line): - modified = True - line = jsm_name_re.sub(esm_name, line) - - content += line - - if modified: - info(f" {str(target_path)}") - info(f" {jsm_name} => {esm_name}") - with open(target_path, "w", newline="\n") as f: - f.write(content) - - return True - - -def try_rename_uri_in(command_context, target, jsm_name, esm_name, jsm_uri, esm_uri): - """Replace the occurrences of `jsm_uri` with `esm_uri` in `target` file.""" - - def info(text): - command_context.log(logging.INFO, "esmify", {}, f"[INFO] {text}") - - modified = False - content = "" - with open(target, "r") as f: - for line in f: - if jsm_uri in line: - modified = True - line = line.replace(jsm_uri, esm_uri) - - content += line - - if modified: - info(f" {str(target)}") - info(f" {jsm_name} => {esm_name}") - with open(target, "w", newline="\n") as f: - f.write(content) - - return True - - -def try_rename_components_conf(command_context, path, jsm_name, esm_name): - """Replace the occurrences of `jsm_name` with `esm_name` in components.conf - file.""" - - def info(text): - command_context.log(logging.INFO, "esmify", {}, f"[INFO] {text}") - - target_path = find_file(path, "components.conf") - if not target_path: - return False - - # Unlike try_rename_in, components.conf contains the URL instead of - # relative path, and also there are no known files with same name. - # Simply replace the filename. - - with open(target_path, "r") as f: - content = f.read() - - prop_re = re.compile( - "[\"']jsm[\"']:(.*)" + r"\b" + jsm_name.replace(".", r"\.") + r"\b" - ) - - if not prop_re.search(content): - return False - - info(f" {str(target_path)}") - info(f" {jsm_name} => {esm_name}") - - content = prop_re.sub(r"'esModule':\1" + esm_name, content) - with open(target_path, "w", newline="\n") as f: - f.write(content) - - return True - - -def esmify_name(name): - return re.sub(r"\.(jsm|js|jsm\.js)$", ".sys.mjs", name) - - -def esmify_path(jsm_path): - jsm_name = jsm_path.name - esm_name = re.sub(r"\.(jsm|js|jsm\.js)$", ".sys.mjs", jsm_name) - esm_path = jsm_path.parent / esm_name - return esm_path - - -path_to_uri_map = None - - -def load_path_to_uri_map(): - global path_to_uri_map - - if path_to_uri_map: - return - - if "ESMIFY_MAP_JSON" in os.environ: - json_map = pathlib.Path(os.environ["ESMIFY_MAP_JSON"]) - else: - json_map = pathlib.Path(__file__).parent / "map.json" - - with open(json_map, "r") as f: - uri_to_path_map = json.loads(f.read()) - - path_to_uri_map = dict() - - for uri, paths in uri_to_path_map.items(): - if type(paths) is str: - paths = [paths] - - for path in paths: - path_to_uri_map[path] = uri - - -def find_jsm_uri(jsm_path): - load_path_to_uri_map() - - path = path_sep_from_native(jsm_path) - - if path in path_to_uri_map: - return path_to_uri_map[path] - - return None - - -def rename_single_file(command_context, vcs_utils, jsm_path, summary): - """Rename `jsm_path` to .sys.mjs, and fix references to the file in build - and test definitions.""" - - def info(text): - command_context.log(logging.INFO, "esmify", {}, f"[INFO] {text}") - - esm_path = esmify_path(jsm_path) - - jsm_name = jsm_path.name - esm_name = esm_path.name - - target_files = [ - ".eslintignore", - "moz.build", - "jar.mn", - "browser.toml", - "browser-common.toml", - "chrome.toml", - "mochitest.toml", - "xpcshell.toml", - "xpcshell-child-process.toml", - "xpcshell-common.toml", - "xpcshell-parent-process.toml", - pathlib.Path("tools", "lint", "eslint.yml"), - pathlib.Path("tools", "lint", "rejected-words.yml"), - ] - - info(f"{jsm_path} => {esm_path}") - - renamed = False - for target in target_files: - if try_rename_in( - command_context, jsm_path, target, jsm_name, esm_name, jsm_path - ): - renamed = True - - if try_rename_components_conf(command_context, jsm_path, jsm_name, esm_name): - renamed = True - - uri_target_files = [ - pathlib.Path( - "browser", "base", "content", "test", "performance", "browser_startup.js" - ), - pathlib.Path( - "browser", - "base", - "content", - "test", - "performance", - "browser_startup_content.js", - ), - pathlib.Path( - "browser", - "base", - "content", - "test", - "performance", - "browser_startup_content_subframe.js", - ), - pathlib.Path( - "toolkit", - "components", - "backgroundtasks", - "tests", - "browser", - "browser_xpcom_graph_wait.js", - ), - ] - - jsm_uri = find_jsm_uri(jsm_path) - if jsm_uri: - esm_uri = re.sub(r"\.(jsm|js|jsm\.js)$", ".sys.mjs", jsm_uri) - - for target in uri_target_files: - if try_rename_uri_in( - command_context, target, jsm_uri, esm_uri, jsm_name, esm_name - ): - renamed = True - - if not renamed: - summary.no_refs.append([jsm_path, None]) - - if not esm_path.exists(): - vcs_utils.rename(jsm_path, esm_path) - else: - summary.rename_errors.append([jsm_path, f"{esm_path} already exists"]) - - return esm_path - - -def rename_jsms(command_context, vcs_utils, jsms, summary): - esms = [] - for jsm in jsms: - esm = rename_single_file(command_context, vcs_utils, jsm, summary) - esms.append(esm) - - return esms - - -npm_prefix = pathlib.Path("tools") / "esmify" -path_from_npm_prefix = pathlib.Path("..") / ".." - - -def setup_jscodeshift(): - """Install jscodeshift.""" - cmd = [ - sys.executable, - "./mach", - "npm", - "install", - "jscodeshift", - "--save-dev", - "--prefix", - str(npm_prefix), - ] - subprocess.run(cmd, check=True) - - -def run_npm_command(args, env, stdin): - cmd = [ - sys.executable, - "./mach", - "npm", - "run", - ] + args - p = subprocess.Popen(cmd, env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE) - p.stdin.write(stdin) - p.stdin.close() - - ok_files = [] - errors = [] - while True: - line = p.stdout.readline() - if not line: - break - line = line.rstrip().decode() - - if line.startswith(" NOC "): - continue - - print(line) - - m = re.search(r"^ (OKK|ERR) ([^ ]+)(?: (.+))?", line) - if not m: - continue - - result = m.group(1) - # NOTE: path is written from `tools/esmify`. - path = pathlib.Path(m.group(2)).relative_to(path_from_npm_prefix) - error = m.group(3) - - if result == "OKK": - ok_files.append(path) - - if result == "ERR": - errors.append([path, error]) - - if p.wait() != 0: - return [None, None] - - return ok_files, errors - - -def convert_module(jsms, summary): - """Replace EXPORTED_SYMBOLS with export declarations, and replace - ChromeUtils.importESModule with static import as much as possible, - and return the list of successfully rewritten files.""" - - if len(jsms) == 0: - return [] - - env = os.environ.copy() - - stdin = "\n".join(map(str, paths_from_npm_prefix(jsms))).encode() - - ok_files, errors = run_npm_command( - [ - "convert_module", - "--prefix", - str(npm_prefix), - ], - env=env, - stdin=stdin, - ) - - if ok_files is None and errors is None: - return None - - summary.convert_errors.extend(errors) - - return ok_files - - -def rewrite_imports(jss, prefix, summary): - """Replace import calls for JSM with import calls for ESM or static import - for ESM.""" - - if len(jss) == 0: - return [] - - env = os.environ.copy() - env["ESMIFY_TARGET_PREFIX"] = prefix - - stdin = "\n".join(map(str, paths_from_npm_prefix(jss))).encode() - - ok_files, errors = run_npm_command( - [ - "rewrite_imports", - "--prefix", - str(npm_prefix), - ], - env=env, - stdin=stdin, - ) - - if ok_files is None and errors is None: - return None - - summary.import_errors.extend(errors) - - return ok_files - - -def paths_from_npm_prefix(paths): - """Convert relative path from mozilla-central to relative path from - tools/esmify.""" - return list(map(lambda path: path_from_npm_prefix / path, paths)) - - -def eslint_fix(command_context, files): - """Auto format files.""" - - def info(text): - command_context.log(logging.INFO, "esmify", {}, f"[INFO] {text}") - - if len(files) == 0: - return - - remaining = files[0:] - - # There can be too many files for single command line, perform by chunk. - max_files = 16 - while len(remaining) > max_files: - info(f"{len(remaining)} files remaining") - - chunk = remaining[0:max_files] - remaining = remaining[max_files:] - - cmd = [sys.executable, "./mach", "eslint", "--fix"] + chunk - subprocess.run(cmd, check=True) - - info(f"{len(remaining)} files remaining") - chunk = remaining - cmd = [sys.executable, "./mach", "eslint", "--fix"] + chunk - subprocess.run(cmd, check=True) |