resolvescript 0.1.2__py3-none-any.whl

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.
Files changed (49) hide show
  1. resolve_script/__init__.py +3 -0
  2. resolve_script/analyze.py +277 -0
  3. resolve_script/cli.py +748 -0
  4. resolve_script/config.py +62 -0
  5. resolve_script/consolidate.py +604 -0
  6. resolve_script/fetch.py +90 -0
  7. resolve_script/install/__init__.py +49 -0
  8. resolve_script/install/discovery.py +57 -0
  9. resolve_script/install/installer.py +397 -0
  10. resolve_script/install/registry.py +106 -0
  11. resolve_script/manifest/__init__.py +1 -0
  12. resolve_script/manifest/json_reader.py +40 -0
  13. resolve_script/manifest/model.py +316 -0
  14. resolve_script/manifest/validation.py +81 -0
  15. resolve_script/manifest/xml_reader.py +162 -0
  16. resolve_script/package.py +103 -0
  17. resolve_script/resolver.py +204 -0
  18. resolve_script/sandbox/__init__.py +38 -0
  19. resolve_script/sandbox/api.py +393 -0
  20. resolve_script/sandbox/env.py +82 -0
  21. resolve_script/sandbox/loader.py +72 -0
  22. resolve_script/sandbox/repl.py +57 -0
  23. resolve_script/sandbox/smoke.py +104 -0
  24. resolve_script/scaffold.py +126 -0
  25. resolve_script/semver.py +236 -0
  26. resolve_script/sources/__init__.py +15 -0
  27. resolve_script/sources/archive.py +82 -0
  28. resolve_script/sources/git.py +107 -0
  29. resolve_script/sources/known.py +47 -0
  30. resolve_script/sources/release.py +55 -0
  31. resolve_script/spec.py +137 -0
  32. resolve_script/templates/extension/@NAME@/__init__.py +7 -0
  33. resolve_script/templates/extension/@NAME@/menu.py +12 -0
  34. resolve_script/templates/extension/@NAME@.py +13 -0
  35. resolve_script/templates/extension/README.md +20 -0
  36. resolve_script/templates/extension/conftest.py +13 -0
  37. resolve_script/templates/extension/manifest.json.j2 +23 -0
  38. resolve_script/templates/extension/manifest.xml.j2 +24 -0
  39. resolve_script/templates/extension/tests/test_smoke.py +26 -0
  40. resolve_script/templates/inapp/register.py +28 -0
  41. resolve_script/testing/__init__.py +6 -0
  42. resolve_script/testing/fixtures.py +47 -0
  43. resolve_script/workspace.py +66 -0
  44. resolvescript-0.1.2.dist-info/METADATA +146 -0
  45. resolvescript-0.1.2.dist-info/RECORD +49 -0
  46. resolvescript-0.1.2.dist-info/WHEEL +5 -0
  47. resolvescript-0.1.2.dist-info/entry_points.txt +2 -0
  48. resolvescript-0.1.2.dist-info/licenses/LICENSE +21 -0
  49. resolvescript-0.1.2.dist-info/top_level.txt +1 -0
resolve_script/spec.py ADDED
@@ -0,0 +1,137 @@
1
+ """Specifier parsing: ``add``/``install`` arguments -> :class:`Spec`."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import re
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+
10
+ from .sources.archive import is_archive_path
11
+
12
+
13
+ class SpecError(ValueError):
14
+ pass
15
+
16
+
17
+ @dataclass
18
+ class Spec:
19
+ raw: str
20
+ kind: str # path | file-archive | archive | manifest | github | name
21
+ source: str # canonical source string recorded in the registry / deps
22
+ location: Path | None = None
23
+ url: str | None = None
24
+ owner: str | None = None
25
+ repo: str | None = None
26
+ ref: str | None = None
27
+ range_text: str | None = None
28
+
29
+ @property
30
+ def name_hint(self) -> str:
31
+ if self.owner and self.repo:
32
+ return self.repo
33
+ if self.location:
34
+ return self.location.name
35
+ return self.raw
36
+
37
+
38
+ def _split_fragment(text: str) -> tuple[str, str | None]:
39
+ index = text.find("#")
40
+ if index == -1:
41
+ return text, None
42
+ return text[:index], text[index + 1 :]
43
+
44
+
45
+ def _looks_like_windows_path(text: str) -> bool:
46
+ return bool(
47
+ re.match(r"^[a-zA-Z]:[\\/]", text)
48
+ )
49
+
50
+
51
+ def parse_specifier(raw: str, *, cwd: Path | None = None) -> Spec:
52
+ """Classify a raw specifier string into a :class:`Spec`."""
53
+ cwd = cwd or Path.cwd()
54
+ text = raw.strip()
55
+ if not text:
56
+ raise SpecError("empty specifier")
57
+
58
+ # file: scheme (archive or directory)
59
+ if text.startswith("file:"):
60
+ location = Path(text[len("file:") :])
61
+ if not location.is_absolute():
62
+ location = (cwd / location).resolve()
63
+ if is_archive_path(str(location)):
64
+ return Spec(raw=raw, kind="file-archive", source=f"file:{location}", location=location)
65
+ return Spec(raw=raw, kind="path", source=f"file:{location}", location=location)
66
+
67
+ # https?:// URLs
68
+ lower = text.lower()
69
+ if lower.startswith("http://") or lower.startswith("https://"):
70
+ if is_archive_path(text):
71
+ return Spec(raw=raw, kind="archive", source=text, url=text)
72
+ return Spec(raw=raw, kind="manifest", source=text, url=text)
73
+
74
+ # github:owner/repo[...]
75
+ if text.startswith("github:") or text.startswith("gh:"):
76
+ body, fragment = _split_fragment(text.split(":", 1)[1])
77
+ owner, repo = _split_owner_repo(body)
78
+ return _github_spec(raw, owner, repo, fragment)
79
+
80
+ # paths: relative markers, absolute, existing, or windows paths
81
+ if (
82
+ text in (".", "..")
83
+ or text.startswith("./")
84
+ or text.startswith("../")
85
+ or text.startswith("\\")
86
+ or _looks_like_windows_path(text)
87
+ or Path(text).is_absolute()
88
+ or Path(cwd / text).exists()
89
+ ):
90
+ location = (cwd / text).resolve()
91
+ if is_archive_path(str(location)):
92
+ return Spec(raw=raw, kind="file-archive", source=f"file:{location}", location=location)
93
+ return Spec(raw=raw, kind="path", source=str(location), location=location)
94
+
95
+ # owner/repo
96
+ if "/" in text:
97
+ body, fragment = _split_fragment(text)
98
+ owner, repo = _split_owner_repo(body)
99
+ return _github_spec(raw, owner, repo, fragment)
100
+
101
+ # bare name
102
+ return Spec(raw=raw, kind="name", source=text)
103
+
104
+
105
+ def _split_owner_repo(body: str) -> tuple[str, str]:
106
+ parts = body.split("/")
107
+ if len(parts) == 2 and parts[0] and parts[1]:
108
+ return parts[0], parts[1]
109
+ if len(parts) > 2:
110
+ raise SpecError(f"invalid github specifier {body!r} (expected owner/repo)")
111
+ owner = os.environ.get("RESOLVESCRIPT_USER", "")
112
+ if not owner:
113
+ raise SpecError(
114
+ f"cannot infer github owner for {body!r} (set RESOLVESCRIPT_USER)"
115
+ )
116
+ return owner, body
117
+
118
+
119
+ def _github_spec(raw: str, owner: str, repo: str, fragment: str | None) -> Spec:
120
+ source = f"github:{owner}/{repo}"
121
+ ref: str | None = None
122
+ range_text: str | None = None
123
+ if fragment:
124
+ if fragment.startswith("semver:"):
125
+ range_text = fragment[len("semver:") :] or "*"
126
+ else:
127
+ ref = fragment
128
+ source = f"{source}#{'semver:' + range_text if range_text else ref}"
129
+ return Spec(
130
+ raw=raw,
131
+ kind="github",
132
+ source=source,
133
+ owner=owner,
134
+ repo=repo,
135
+ ref=ref,
136
+ range_text=range_text,
137
+ )
@@ -0,0 +1,7 @@
1
+ """@DESCRIPTION@."""
2
+
3
+ __version__ = "@VERSION@"
4
+
5
+
6
+ def hello() -> str:
7
+ return "Hello from @NAME@!"
@@ -0,0 +1,12 @@
1
+ """Sample menu UI for @NAME@.
2
+
3
+ ``run`` is the entry the sandbox and smoke tests exercise; it works against
4
+ either the mock API (``resolvescript dev``/``test``) or the real Resolve API.
5
+ """
6
+
7
+
8
+ def run(resolve) -> str:
9
+ project = resolve.GetProjectManager().GetCurrentProject()
10
+ if project is None:
11
+ return "@NAME@: no project is open"
12
+ return f"@NAME@ ready — project: {project.GetName()}"
@@ -0,0 +1,13 @@
1
+ """In-app entry point for @NAME@.
2
+
3
+ Run inside DaVinci Resolve (Workspace → Scripts) after installing.
4
+ The mock API mirrors ``DaVinciResolveScript`` so this also runs in the
5
+ sandbox via ``resolvescript dev --editor``.
6
+ """
7
+
8
+ import DaVinciResolveScript as dvr_script
9
+
10
+ from @NAME@.menu import run
11
+
12
+ resolve = dvr_script.scriptapp("Resolve")
13
+ print(run(resolve))
@@ -0,0 +1,20 @@
1
+ # @NAME@
2
+
3
+ @DESCRIPTION@
4
+
5
+ Built with [ResolveScript](https://pypi.org/project/resolvescript/).
6
+
7
+ ## Development
8
+
9
+ ```
10
+ resolvescript dev # sandboxed REPL against the mock Resolve API
11
+ resolvescript test # run tests against the mock API
12
+ resolvescript analyze # static checks on the project
13
+ resolvescript build # consolidate the package into dist/@NAME@.py
14
+ resolvescript install # install into DaVinci Resolve (author mode)
15
+ ```
16
+
17
+ ## Usage in Resolve
18
+
19
+ After `resolvescript install`, restart DaVinci Resolve and run the script from
20
+ **Workspace → Scripts → Comp → @NAME@**.
@@ -0,0 +1,13 @@
1
+ """Shared pytest setup for the @NAME@ project.
2
+
3
+ Installs the mock DaVinciResolveScript sandbox (via the ResolveScript CLI's own
4
+ fixtures) and puts the project root on ``sys.path`` so ``import @NAME@`` works
5
+ from ``tests/``.
6
+ """
7
+
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
12
+
13
+ pytest_plugins = ["resolve_script.testing.fixtures"]
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@NAME@",
3
+ "version": "@VERSION@",
4
+ "author": "@AUTHOR@",
5
+ "description": "@DESCRIPTION@",
6
+ "python": "@NAME@",
7
+ "targets": [
8
+ "Comp",
9
+ "Utility"
10
+ ],
11
+ "consolidate": {
12
+ "enabled": true,
13
+ "output": "@NAME@.py",
14
+ "entry": "@NAME@/__init__.py",
15
+ "exclude": ["tests"],
16
+ "no_comment": ["sys", "os"]
17
+ },
18
+ "install": {
19
+ "as_directory": true,
20
+ "include": ["@NAME@.py", "@NAME@/**", "manifest.json"],
21
+ "exclude": ["**/__pycache__/**"]
22
+ }
23
+ }
@@ -0,0 +1,24 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <manifest>
3
+ <name>@NAME@</name>
4
+ <version>@VERSION@</version>
5
+ <author>@AUTHOR@</author>
6
+ <description>@DESCRIPTION@</description>
7
+ <python>@NAME@</python>
8
+ <targets>
9
+ <target>Comp</target>
10
+ <target>Utility</target>
11
+ </targets>
12
+ <consolidate enabled="true" output="@NAME@.py">
13
+ <entry>@NAME@/__init__.py</entry>
14
+ <exclude>tests</exclude>
15
+ <no_comment>sys</no_comment>
16
+ <no_comment>os</no_comment>
17
+ </consolidate>
18
+ <install as_directory="true">
19
+ <include>@NAME@.py</include>
20
+ <include>@NAME@/**</include>
21
+ <include>manifest.json</include>
22
+ <exclude>**/__pycache__/**</exclude>
23
+ </install>
24
+ </manifest>
@@ -0,0 +1,26 @@
1
+ """Smoke tests that pass in the mock sandbox with zero setup."""
2
+
3
+ import @NAME@
4
+
5
+
6
+ def test_version_is_a_string() -> None:
7
+ assert isinstance(@NAME@.__version__, str)
8
+
9
+
10
+ def test_hello() -> None:
11
+ assert @NAME@.hello() == "Hello from @NAME@!"
12
+
13
+
14
+ def test_menu_against_mock_resolve() -> None:
15
+ """menu.run works against the injected mock DaVinciResolveScript.
16
+
17
+ The sandbox fixture (M4) installs a FakeResolve with a project named
18
+ 'Demo Project'.
19
+ """
20
+ import DaVinciResolveScript as dvr_script
21
+
22
+ from @NAME@.menu import run
23
+
24
+ resolve = dvr_script.scriptapp("Resolve")
25
+ message = run(resolve)
26
+ assert "@NAME@" in message
@@ -0,0 +1,28 @@
1
+ """In-app helper: register the @NAME@ extension into the open Resolve project.
2
+
3
+ Paste this into Resolve's Console (Workspace > Console) or run it as a Utility
4
+ script to wire the installed @NAME@ script into the current project's Tools or
5
+ Edit menus for one session. The package must already be installed via
6
+ ``resolvescript install`` (or on ``sys.path``).
7
+
8
+ Using ``addMenuItem`` is the standard non-persistent way to surface a script in
9
+ the Resolve UI menus.
10
+ """
11
+
12
+ import DaVinciResolveScript as dvr_script
13
+
14
+
15
+ def _register() -> None:
16
+ resolve = dvr_script.scriptapp("Resolve")
17
+ project = resolve.GetProjectManager().GetCurrentProject()
18
+ if project is None:
19
+ print("@NAME@: no project is open")
20
+ return
21
+
22
+ import @NAME@ # installed under the Scripts root
23
+
24
+ project.AddMenuItem("Tools", "@NAME@", lambda: @NAME@.menu.run(resolve))
25
+
26
+
27
+ if __name__ == "__main__":
28
+ _register()
@@ -0,0 +1,6 @@
1
+ """Pytest helpers and fixtures for developing Resolve scripts.
2
+
3
+ Load the shared fixtures in a project's ``conftest.py`` with::
4
+
5
+ pytest_plugins = ["resolve_script.testing.fixtures"]
6
+ """
@@ -0,0 +1,47 @@
1
+ """Pytest fixtures for developing Resolve scripts against the mock API.
2
+
3
+ The ``sandbox`` fixture installs the fake ``DaVinciResolveScript`` module and
4
+ yields the mock Resolve object. ``load_source_module`` and ``load_built_module``
5
+ import the extension under test either as a source package or as a consolidated
6
+ single-file build.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from collections.abc import Callable
12
+ from pathlib import Path
13
+ from types import ModuleType
14
+
15
+ import pytest
16
+
17
+ from ..sandbox.env import install_fake_resolve
18
+ from ..sandbox.loader import load_built_module as _load_built_module
19
+ from ..sandbox.loader import load_source_module as _load_source_module
20
+
21
+
22
+ @pytest.fixture(scope="session", autouse=True)
23
+ def _sandbox_installed() -> None:
24
+ """Install the mock Resolve API once per test session."""
25
+ install_fake_resolve()
26
+
27
+
28
+ @pytest.fixture
29
+ def sandbox() -> ModuleType:
30
+ """Install (idempotently) and return the fake DaVinciResolveScript module."""
31
+ return install_fake_resolve()
32
+
33
+
34
+ @pytest.fixture
35
+ def load_source_module() -> Callable[[str, Path | str], ModuleType]:
36
+ def _load(module_name: str, package_dir: Path | str = ".") -> ModuleType:
37
+ return _load_source_module(module_name, Path(package_dir))
38
+
39
+ return _load
40
+
41
+
42
+ @pytest.fixture
43
+ def load_built_module() -> Callable[[str, Path | str], ModuleType]:
44
+ def _load(module_name: str, built_file: Path | str) -> ModuleType:
45
+ return _load_built_module(module_name, Path(built_file))
46
+
47
+ return _load
@@ -0,0 +1,66 @@
1
+ """Workspace file management: ``resolvescript.json`` dependency list."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ WORKSPACE_FILE = "resolvescript.json"
9
+
10
+
11
+ class WorkspaceError(RuntimeError):
12
+ pass
13
+
14
+
15
+ def workspace_path(cwd: Path | None = None) -> Path:
16
+ return Path(cwd or Path.cwd()) / WORKSPACE_FILE
17
+
18
+
19
+ def has_workspace(cwd: Path | None = None) -> bool:
20
+ return workspace_path(cwd).is_file()
21
+
22
+
23
+ def read_workspace(cwd: Path | None = None) -> dict:
24
+ path = workspace_path(cwd)
25
+ if not path.is_file():
26
+ return {"dependencies": {}}
27
+ try:
28
+ data = json.loads(path.read_text("utf-8"))
29
+ except json.JSONDecodeError as exc:
30
+ raise WorkspaceError(f"{path}: invalid JSON: {exc}") from exc
31
+ if not isinstance(data, dict):
32
+ raise WorkspaceError(f"{path}: expected a JSON object")
33
+ if "dependencies" not in data:
34
+ data["dependencies"] = {}
35
+ if not isinstance(data["dependencies"], dict):
36
+ raise WorkspaceError(f"{path}: 'dependencies' must be an object")
37
+ return {k: v for k, v in data.items() if v is not None}
38
+
39
+
40
+ def write_workspace(data: dict, cwd: Path | None = None) -> Path:
41
+ path = workspace_path(cwd)
42
+ path.parent.mkdir(parents=True, exist_ok=True)
43
+ tmp = path.with_name(f"{path.name}.tmp")
44
+ tmp.write_text(json.dumps(data, indent=2) + "\n", "utf-8")
45
+ _atomic_replace(tmp, path)
46
+ return path
47
+
48
+
49
+ def add_dependency(name: str, spec_text: str, cwd: Path | None = None) -> dict:
50
+ data = read_workspace(cwd)
51
+ data.setdefault("dependencies", {})[name] = spec_text
52
+ return data
53
+
54
+
55
+ def remove_dependency(name: str, cwd: Path | None = None) -> dict:
56
+ data = read_workspace(cwd)
57
+ data.setdefault("dependencies", {}).pop(name, None)
58
+ return data
59
+
60
+
61
+ def save(data: dict, cwd: Path | None = None) -> Path:
62
+ return write_workspace(data, cwd)
63
+
64
+
65
+ def _atomic_replace(src: Path, dest: Path) -> None:
66
+ src.replace(dest)
@@ -0,0 +1,146 @@
1
+ Metadata-Version: 2.4
2
+ Name: resolvescript
3
+ Version: 0.1.2
4
+ Summary: CLI framework for building, coding, testing, packaging and installing DaVinci Resolve scripts.
5
+ License: MIT
6
+ Classifier: Development Status :: 3 - Alpha
7
+ Classifier: Environment :: Console
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Multimedia :: Video
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest>=7.0; extra == "dev"
21
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
22
+ Dynamic: license-file
23
+
24
+ # resolvescript
25
+
26
+ Build, test, package and install [DaVinci Resolve](https://www.blackmagicdesign.com/products/davinciresolve) Python scripts and plugins: a small CLI with an npm-style workflow for the Resolve `Scripts/` tree.
27
+
28
+ - `create` scaffolds a project with a `manifest.json` and smoke tests
29
+ - `dev` / `test` run against a mock `DaVinciResolveScript` API - no Resolve needed
30
+ - `build` consolidates the multi-file package into a single `.py` for distribution
31
+ - `add` / `install` / `update` resolve dependencies from GitHub, URLs, archives or local folders and record them in `resolvescript.json`
32
+ - `package` emits `dist/<name>-<version>.tar.gz` plus `SHA256SUMS.txt`
33
+
34
+ Install with pip: `pip install resolvescript` (Python 3.12+).
35
+
36
+ ## Quickstart
37
+
38
+ ```console
39
+ $ resolvescript create my-cool-tool
40
+ $ cd my-cool-tool
41
+ $ resolvescript dev # sandboxed dev loop against the mock API
42
+ $ resolvescript test # run the smoke tests
43
+ $ resolvescript build # single-file build -> dist/my_cool_tool.py
44
+ $ resolvescript analyze # static checks (imports, manifest, API usage)
45
+ $ resolvescript package # dist/my-cool-tool-0.1.0.tar.gz + SHA256SUMS.txt
46
+
47
+ # elsewhere, consume it:
48
+ $ resolvescript add github:example/my-cool-tool
49
+ $ resolvescript install # materialize all recorded deps into Resolve
50
+ ```
51
+
52
+ ## Authoring
53
+
54
+ ### Directory
55
+
56
+ ```
57
+ my-cool-tool/
58
+ manifest.json # project manifest (or manifest.xml)
59
+ my_cool_tool/ # your Python package (module name from manifest.name/python)
60
+ __init__.py
61
+ export.py
62
+ tests/
63
+ test_smoke.py
64
+ ```
65
+
66
+ ### Commands
67
+
68
+ | Command | Purpose |
69
+ | --- | --- |
70
+ | `create <name>` | Scaffold a new project (`--json`/`--xml`, `--template minimal\|toolkit`, `--dir`) |
71
+ | `dev` | Sandboxed dev loop / REPL against the mock Resolve API (`--built` for the consolidated file, `--repl`, `--editor`) |
72
+ | `test` | Run the project's pytest suite against the mock API (`--built`, `-k pattern`, `--api-coverage`) |
73
+ | `analyze` | Static checks: missing entrypoint/module, syntax, unused imports, unmocked API methods, attribute typing (`--json`) |
74
+ | `build` | Consolidate into a single file (`--output`, or the manifest `consolidate.output`) |
75
+ | `package` | Assemble `dist/<name>-<version>.tar.gz` + `SHA256SUMS.txt` (`--dist DIR`) |
76
+ | `add <spec>` | Resolve, install a dependency and record it (`--no-save`, `--target`, `--scripts-root`) |
77
+ | `install [<spec>]` | One-off install, local project, **or** materialize recorded deps (`--locked`, `--dry-run`) |
78
+ | `update [<name>]` | Re-resolve recorded deps within their ranges (`--precise X.Y.Z`, `--fix`) |
79
+ | `remove <name>` | Uninstall and unrecord (`--no-save`) |
80
+ | `search <query>` | Search the known sources table |
81
+ | `manage list\|remove` | Low-level registry operations (`--json`, `--all`) |
82
+
83
+ Exit codes: `0` ok, `1` error, `2` usage.
84
+
85
+ ### Specifiers
86
+
87
+ `add` / `install <spec>` accept:
88
+
89
+ - `name` - a name from the built-in known-sources table (`resolvescript search`)
90
+ - `owner/repo` or `github:owner/repo[#ref]` - GitHub repo (ref = branch/tag/commit, or `#semver:^1.2` for a range)
91
+ - `https://...tar.gz` / `.zip` - direct archive URL
92
+ - `https://...` - a URL whose root carries a `manifest.json` (GitHub Pages-style hosting)
93
+ - `file:path`, `./dir`, an absolute path - local directory or archive
94
+ - `.tar.gz` / `.zip` files and local directories
95
+
96
+ Recorded specs live in `resolvescript.json`; use `resolvescript install --locked` for a CI/npm-ci style check that recorded artifacts still satisfy the recorded ranges.
97
+
98
+ ### Manifest reference
99
+
100
+ `manifest.json` (or `manifest.xml`, same shape):
101
+
102
+ ```jsonc
103
+ {
104
+ "name": "my-cool-tool", // required
105
+ "version": "0.1.0", // required, semver
106
+ "author": "You",
107
+ "description": "...",
108
+ "python": "my_cool_tool", // package/module name (defaults to "name")
109
+ "entrypoint": "export.py", // recommended CLI/bootstrap module
110
+ "kind": "script", // "script" (default) | "extension" (plugin)
111
+ "targets": ["Comp"], // Scripts subfolders: Comp, Utility, Tool, Render,
112
+ // Deliver, Edit, WorkflowIntegrations, Fusion, root
113
+ "compat": { "resolve": "18.6.4", "python": "3.12" },
114
+ "release": { "owner": "you", "repo": "my-cool-tool" }, // for `add` discovery
115
+ "scripts_root": "", // per-OS override for the Resolve Scripts root
116
+ "consolidate": { "enabled": true, "output": "dist/my_tool.py", "exclude": [], "no_comment": [] },
117
+ "install": { "as_directory": true, "include": [], "exclude": [], "to": "resolve" },
118
+ "dependencies": ["github:example/dep"] // resolved on install
119
+ }
120
+ ```
121
+
122
+ ### Install model
123
+
124
+ Installs drop into each OS's Resolve Scripts root under the manifest `targets`. Directories install as a single folder; `.py`-only packages can opt into `install.as_directory: false` multi-file installs. Every install is recorded in `<ScriptsRoot>/.resolvescript/install.json` (schema v1):
125
+
126
+ - keys are `<name>:<target>` (directory) or `<name>:<target>:<relpath>` (file-based)
127
+ - each entry carries `version`, `targets`, `as_directory`, `files`, `source`, `resolved` and `integrity`
128
+ - `install --locked` verifies registry entries against recorded specs before reusing them
129
+
130
+ `--scripts-root` (or `RESOLVESCRIPT_SCRIPTS_ROOT`) overrides OS detection.
131
+
132
+ ## Frameworks
133
+
134
+ `resolvescript` ships a mock `DaVinciResolveScript` module with a faithful-enough Resolve color/editing/page object model for testing, plus `resolvescript test --api-coverage` to report which mock methods your tests actually exercise.
135
+
136
+ ## Development
137
+
138
+ ```console
139
+ python -m venv .venv
140
+ .\.venv\Scripts\activate # Windows; source .venv/bin/activate elsewhere
141
+ pip install -e ".[dev]"
142
+ pytest
143
+ ruff check .
144
+ ```
145
+
146
+ Milestones and the full plan live in [`TODO.md`](TODO.md). CI runs the shared `OseMine/workflows` action; cutting a `vX.Y.Z` tag runs the release pipeline (sdist+wheel, checksums, GitHub release, optional PyPI publishing).
@@ -0,0 +1,49 @@
1
+ resolve_script/__init__.py,sha256=4yBp0AZtzy-XdhHBCiy-Wc_oEjzK3GqVcnAp47Rhz4Y,90
2
+ resolve_script/analyze.py,sha256=bE8HyosA5FbV3L1diiX1Yi_koI1nLSBhgGInlpU1mSs,8425
3
+ resolve_script/cli.py,sha256=q2EYBWbgErMzvtmdeF70UzhOib53tAMFfWk0zurq48Q,29402
4
+ resolve_script/config.py,sha256=DFG_ro9QZYPEtAfGaZ95L3dbWyhUZUT1w5mLqxQ2rfo,2002
5
+ resolve_script/consolidate.py,sha256=NNjEU5f3xhQq814G_xvbzREBVSJ91t1v8d7RBzCOcbc,21749
6
+ resolve_script/fetch.py,sha256=0NkbdbOdQolSUTQUIwbh6LfRu1q5ikORvHBiVa-eouM,2526
7
+ resolve_script/package.py,sha256=pL4uahXK3tEB4ta07ThJNchu9PXzKzgQpnolaMY36uA,3424
8
+ resolve_script/resolver.py,sha256=lv1GGn1OAOdCBAlTGGOrcwCCTFWd5msfHht6w2KxIck,6711
9
+ resolve_script/scaffold.py,sha256=6pX3jh2CHSFfTgjV-QXEXoia4c2JJfVV1wY9bMcjvn4,3967
10
+ resolve_script/semver.py,sha256=a8Y7DBoS6qIzLc-ZGWKwqideDFc9EY9cDHDCf0XbBQA,7554
11
+ resolve_script/spec.py,sha256=vO2-n5NzGOp6idEBGErVTj3u90Rfbd9tCn4J8prwusk,4293
12
+ resolve_script/workspace.py,sha256=WqsXDm8P1tAMYZLbwnykGd7_QErXDm-RMh0EHcZtM1U,1942
13
+ resolve_script/install/__init__.py,sha256=nCqt4aU_7AQ85G9U7VoYiVODLp7joTHFqmziYt4kUt4,1059
14
+ resolve_script/install/discovery.py,sha256=cqEjV6ubE3bYIpAhMJa4HknnxQjGt8kifoWKrAW4nnU,2024
15
+ resolve_script/install/installer.py,sha256=mnVK2ejOBNZuDQaBJ2Hk6480pble-bSNSRtyEArZKXo,14689
16
+ resolve_script/install/registry.py,sha256=s7KCZaTmnP0GnUyhYbJd9wi8suul23afsY8sQXIWHuM,3652
17
+ resolve_script/manifest/__init__.py,sha256=rfEekP2vJmnH0w8wYqdv1grsPh99Rt6Rmr43kdt-EQ8,57
18
+ resolve_script/manifest/json_reader.py,sha256=cgmQj2AmXSMBajamgmMrY9giR3Jq3PTcOg672Q4axEY,1383
19
+ resolve_script/manifest/model.py,sha256=G1XuQWm6ryZYbnOYuEZdzJvs0Nj3CorteKy1xPS0gbs,10944
20
+ resolve_script/manifest/validation.py,sha256=MJjL09XHgV5AVTSypltPBIc-UtrUXXWxODGrg5sFkEc,3048
21
+ resolve_script/manifest/xml_reader.py,sha256=quA460IyBkOqg1N-ZomCdb4jkn8SG8NXmLZShhCjPXM,5048
22
+ resolve_script/sandbox/__init__.py,sha256=vxhUZ36WzYFXmq7n9Aka6DNJyIppYenc9RqFrnSe7EI,969
23
+ resolve_script/sandbox/api.py,sha256=fAihpJqnm7QgO8zI2w8l9DCljtGxxt5UAiC9Qi9odek,11165
24
+ resolve_script/sandbox/env.py,sha256=0NVI4Jh02AnLj3xo1cY7XALR7vIAmwFpD4cYGoqg_cI,2479
25
+ resolve_script/sandbox/loader.py,sha256=30sN5Wz4y2Jo9juP4ksHL1Qj1pnnnOX_c1E83WL-pd4,2729
26
+ resolve_script/sandbox/repl.py,sha256=njYE4RfouXQOYj7KpN9DFt6f9P_fkjVV18wCgxQzLIw,2130
27
+ resolve_script/sandbox/smoke.py,sha256=3fwjZp15bzF5TjEAQoLGla2I1uB3OgmyEmuUN-KdpDI,3406
28
+ resolve_script/sources/__init__.py,sha256=HMtm-0m6HwL7UNpCInRbbqMmTVKZRBVDt92QNYCHkAs,687
29
+ resolve_script/sources/archive.py,sha256=9Tvxu4TfM7SbYOSPCCLMfuuI-J7qBbclnrjWpfTF1Tk,2690
30
+ resolve_script/sources/git.py,sha256=ZO3NRYKI_NY1ryKeyxKQU_hdcFOI6nBccniQk2KKtM0,3474
31
+ resolve_script/sources/known.py,sha256=QLjhDfI9Eh7IRh5N-neVWDuoI7EkW2pIe118zT8Vcj0,1452
32
+ resolve_script/sources/release.py,sha256=I8UgY-YAa0SJYKHvBIgPeoXidotUMNSIh7nutc-T3L0,1644
33
+ resolve_script/templates/extension/@NAME@.py,sha256=Qs1l8LT80x_l_XH0TCD-Sxh1yT59NJtEeV979CYXTXQ,357
34
+ resolve_script/templates/extension/README.md,sha256=uaLe4Kl1dkSxtavnyzC5enNrBpU_4Ei6QTjRVSt-I7g,587
35
+ resolve_script/templates/extension/conftest.py,sha256=vZCPJO38FhciZsPFmndvjkUWPP-PU3jYFwFxJa5sl8c,376
36
+ resolve_script/templates/extension/manifest.json.j2,sha256=czshE3YukuYCG7ql8GNut7X4pUOnKm_GzhNI_wwAMXk,482
37
+ resolve_script/templates/extension/manifest.xml.j2,sha256=iOAoX8r_uMPFS8uzmdYgIzRTNjSZrFZ4672NUhiVqWU,679
38
+ resolve_script/templates/extension/@NAME@/__init__.py,sha256=f-HFxk2izvb7Ag_AFUTb8rkv5XkcSD23nzhWnN8TT3M,101
39
+ resolve_script/templates/extension/@NAME@/menu.py,sha256=2TsZZb7hxHQBeFerFvuwf3-ibP3sns0-ooLkGFIsIFc,405
40
+ resolve_script/templates/extension/tests/test_smoke.py,sha256=30PLOZBfo_p73xL8N7tZnHd17JR16Jk2PwqixvYSkv0,650
41
+ resolve_script/templates/inapp/register.py,sha256=BeA2h2njP4u95oJvdhR2Z7DPzw32bIWreJgj0TjRZyo,901
42
+ resolve_script/testing/__init__.py,sha256=D9b0xmoOdHe-41Xcq-KNdRYcgMMv_-oyYzGOTNkVCnY,189
43
+ resolve_script/testing/fixtures.py,sha256=P_GXPtByPYLjV_Eo8hNOpRbKO25FCv_JanZSZ1OJ5aw,1491
44
+ resolvescript-0.1.2.dist-info/licenses/LICENSE,sha256=JuDRkpJ1tG2YXCjsW0iF9Ob8IH0K2Sd3c9WMTBPfO7o,1055
45
+ resolvescript-0.1.2.dist-info/METADATA,sha256=hHN2CQ22PoGdLBxsC14MDvx1ddwxeUJYdWoZRYb99yc,6792
46
+ resolvescript-0.1.2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
47
+ resolvescript-0.1.2.dist-info/entry_points.txt,sha256=HVgNxbCSeCttwc2HqLE2B4-oaOodDwDbLAfl-YDu4kg,58
48
+ resolvescript-0.1.2.dist-info/top_level.txt,sha256=RwDBHbSlI6h3pRARRAXkbmPGvHaRA0mMI-cq2Tgmv4Q,15
49
+ resolvescript-0.1.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ resolvescript = resolve_script.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ resolve_script