diff --git a/TODO.md b/TODO.md new file mode 100644 index 000000000..2dcff4629 --- /dev/null +++ b/TODO.md @@ -0,0 +1,24 @@ +### tool resolutions + +- deterministic `node lts` +- updateable + +```sql +CREATE TABLE IF NOT EXISTS tool_resolutions ( + tool TEXT NOT NULL, + version TEXT NOT NULL, + resolved TEXT NOT NULL, + PRIMARY KEY (tool, version) +); +``` + +```console +$ pre-commit tools autoupdate [--only ...] # update config override versions +$ pre-commit tools resolve [--only ...] # update resolved versions +``` + +```yaml +# in .pre-commit-config.yaml +tool_resolution: + node: {lts: '24.2.0'} +``` diff --git a/pre_commit/all_tools.py b/pre_commit/all_tools.py new file mode 100644 index 000000000..6cdeab1e3 --- /dev/null +++ b/pre_commit/all_tools.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +from pre_commit.tool_base import Tool +from pre_commit.tools import go +from pre_commit.tools import node +from pre_commit.tools import python +from pre_commit.tools import rbenv +from pre_commit.tools import ruby +from pre_commit.tools import rust +from pre_commit.tools import rustup +from pre_commit.tools import uv + + +tools: dict[str, Tool] = { + 'go': go, + 'node': node, + 'python': python, + 'rbenv': rbenv, + 'ruby': ruby, + 'rust': rust, + 'rustup': rustup, + 'uv': uv, +} diff --git a/pre_commit/request.py b/pre_commit/request.py new file mode 100644 index 000000000..769ca1a08 --- /dev/null +++ b/pre_commit/request.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +import sys +import urllib.request +from typing import IO + +from pre_commit.constants import VERSION + + +def fetch(url: str) -> IO[bytes]: + pyver = '.'.join(str(v) for v in sys.version_info[:3]) + req = urllib.request.Request( + url, + headers={'User-Agent': f'pre-commit/{VERSION} python/{pyver}'}, + ) + return urllib.request.urlopen(req) diff --git a/pre_commit/tool_base.py b/pre_commit/tool_base.py new file mode 100644 index 000000000..5299cc760 --- /dev/null +++ b/pre_commit/tool_base.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from typing import Protocol + +from pre_commit.prefix import Prefix + + +class Tool(Protocol): + # "special" versions which can be resolved + @property + def RESOLVABLE(self) -> tuple[str, ...]: ... + # TODO: what if not resolvable? (no current examples?) + def resolve(self, version: str) -> str: ... + def install(self, prefix: Prefix, version: str) -> None: ... + def health_check(self, prefix: Prefix, version: str) -> str | None: ... + # TODO: how to env patch? diff --git a/pre_commit/tools/__init__.py b/pre_commit/tools/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pre_commit/tools/go.py b/pre_commit/tools/go.py new file mode 100644 index 000000000..e69de29bb diff --git a/pre_commit/tools/node.py b/pre_commit/tools/node.py new file mode 100644 index 000000000..789149d67 --- /dev/null +++ b/pre_commit/tools/node.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +import functools +import json + +from pre_commit.prefix import Prefix +from pre_commit.request import fetch + +RESOLVABLE = ('latest', 'lts') + + +@functools.cache +def _node_versions() -> dict[str, str]: + resp = fetch('https://nodejs.org/download/release/index.json') + contents = json.load(resp) + + ret = {'latest': contents[0]['version']} + for dct in contents: + if dct['lts']: + ret['lts'] = dct['version'] + break + else: + raise AssertionError('unreachable') + + return ret + + +def resolve(version: str) -> str: + return _node_versions()[version] + + +@functools.cache +def _target_platform() -> str: + # to support: + # linux-arm64, linux-ppc64le, linux-s390x, linux-x64 + # osx-arm64-tar, osx-x86-tar + # win-arm64-zip, win-x64-zip + # or fallback to `src` ? + raise NotImplementedError + + +def install(prefix: Prefix, version: str) -> None: + # TODO: download and extract to prefix + raise NotImplementedError + + +def health_check(prefix: Prefix, version: str) -> str | None: + # TODO: previously checked `node --version` + # TODO: but maybe just check that the installed os/arch is correct? + return None diff --git a/pre_commit/tools/python.py b/pre_commit/tools/python.py new file mode 100644 index 000000000..115587839 --- /dev/null +++ b/pre_commit/tools/python.py @@ -0,0 +1,5 @@ +from __future__ import annotations + +# TODO: how to get "latest" supported uv python? +# TODO: should this support `3.##` as resolvable? +# TODO: how would dynamic resolvables work... diff --git a/pre_commit/tools/rbenv.py b/pre_commit/tools/rbenv.py new file mode 100644 index 000000000..e69de29bb diff --git a/pre_commit/tools/ruby.py b/pre_commit/tools/ruby.py new file mode 100644 index 000000000..e69de29bb diff --git a/pre_commit/tools/rust.py b/pre_commit/tools/rust.py new file mode 100644 index 000000000..e69de29bb diff --git a/pre_commit/tools/rustup.py b/pre_commit/tools/rustup.py new file mode 100644 index 000000000..e69de29bb diff --git a/pre_commit/tools/uv.py b/pre_commit/tools/uv.py new file mode 100644 index 000000000..e69de29bb