ts-backend-check 1.0.0__py3-none-any.whl → 1.1.0__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.
@@ -1,7 +1,5 @@
1
1
  # SPDX-License-Identifier: AGPL-3.0-or-later
2
- __version__ = "1.0.0"
2
+ from ts_backend_check.checker import TypeChecker
3
+ from ts_backend_check.cli.main import main
3
4
 
4
- from .checker import TypeChecker
5
- from .cli import cli
6
-
7
- __all__ = ["cli", "TypeChecker"]
5
+ __all__ = ["main", "TypeChecker"]
@@ -5,9 +5,9 @@ Main module for checking Django models against TypeScript types.
5
5
 
6
6
  from typing import Dict, List, Set
7
7
 
8
- from .django_parser import extract_model_fields
9
- from .typescript_parser import TypeScriptParser
10
- from .utils import snake_to_camel
8
+ from ts_backend_check.parsers.django_parser import extract_model_fields
9
+ from ts_backend_check.parsers.typescript_parser import TypeScriptParser
10
+ from ts_backend_check.utils import snake_to_camel
11
11
 
12
12
 
13
13
  class TypeChecker:
@@ -1,8 +1,4 @@
1
1
  # SPDX-License-Identifier: AGPL-3.0-or-later
2
- """
3
- CLI package for ts-backend-check.
4
- """
2
+ from ts_backend_check.cli.main import main
5
3
 
6
- from .main import cli
7
-
8
- __all__ = ["cli"]
4
+ __all__ = ["main"]
@@ -3,56 +3,84 @@
3
3
  Setup and commands for the ts-backend-check command line interface.
4
4
  """
5
5
 
6
+ import argparse
6
7
  import sys
8
+ from pathlib import Path
7
9
 
8
- import click
10
+ from ts_backend_check.checker import TypeChecker
9
11
 
10
- from ..checker import TypeChecker
11
12
 
12
-
13
- @click.group()
14
- @click.version_option()
15
- def cli():
16
- """
17
- TS Backend Check is a Python package used to check TypeScript types against their corresponding backend models.
13
+ def main() -> None:
18
14
  """
19
- pass
15
+ The main check function to compare a the methods within a backend model to a corresponding TypeScript file.
20
16
 
17
+ Notes
18
+ -----
19
+ The available command line arguments are:
20
+ - --backend-model-file (-bmf): Path to the backend model file (e.g. Python class)
21
+ - --typescript-file (-tsf): Path to the TypeScript interface/type file
21
22
 
22
- @cli.command()
23
- @click.argument("backend_model", type=click.Path(exists=True))
24
- @click.argument("typescript_file", type=click.Path(exists=True))
25
- def check(backend_model: str, typescript_file: str):
23
+ Examples
24
+ --------
25
+ >>> ts-backend-check -bmf <backend-model-file> -tsf <typescript-file>
26
26
  """
27
- Check TypeScript types against backend models.
27
+ # MARK: CLI Base
28
28
 
29
- This command checks if all fields from the backend model are properly represented
30
- in the TypeScript types file. It supports marking fields as backend-only using
31
- special comments in the TypeScript file.
29
+ ROOT_DIR = Path(__file__).cwd()
30
+ parser = argparse.ArgumentParser(
31
+ prog="ts-backend-check",
32
+ description="Checks the types in TypeScript files against the corresponding backend models.",
33
+ epilog="Visit the codebase at https://github.com/activist-org/ts-backend-check to learn more!",
34
+ formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=60),
35
+ )
32
36
 
33
- Parameters
34
- ----------
35
- backend_model : str
36
- The path to the backend model file (e.g. Python class).
37
+ parser._actions[0].help = "Show this help message and exit."
37
38
 
38
- typescript_file : str
39
- The path to the TypeScript interface/type file.
39
+ parser.add_argument(
40
+ "-bmf",
41
+ "--backend-model-file",
42
+ help="Path to the backend model file (e.g. Python class).",
43
+ )
44
+ parser.add_argument(
45
+ "-tsf",
46
+ "--typescript-file",
47
+ help="Path to the TypeScript interface/type file.",
48
+ )
40
49
 
41
- Examples
42
- --------
43
- ts-backend-check check src/models/user.py src/types/user.ts
44
- """
45
- checker = TypeChecker(models_file=backend_model, types_file=typescript_file)
46
- if missing := checker.check():
47
- click.echo("Missing TypeScript fields found:")
48
- click.echo("\n".join(missing))
49
- click.echo(
50
- f"\nPlease correct the {len(missing)} fields above to have backend models and frontend types fully synced."
50
+ # MARK: Setup CLI
51
+
52
+ args = parser.parse_args()
53
+ backend_model_file_path = ROOT_DIR / args.backend_model_file
54
+ ts_file_path = ROOT_DIR / args.typescript_file
55
+
56
+ if not backend_model_file_path.is_file():
57
+ print(
58
+ f"{args.backend_model_file} that should contain the backend models does not exist. Please check and try again."
59
+ )
60
+
61
+ elif not ts_file_path.is_file():
62
+ print(
63
+ f"{args.typescript_file} file that should contain the TypeScript types does not exist. Please check and try again."
51
64
  )
52
- sys.exit(1)
53
65
 
54
- click.echo("All model fields are properly typed in TypeScript!")
66
+ else:
67
+ checker = TypeChecker(
68
+ models_file=args.backend_model_file,
69
+ types_file=args.typescript_file,
70
+ )
71
+
72
+ if missing := checker.check():
73
+ print("Missing typescript fields found: ")
74
+ print("\n".join(missing))
75
+
76
+ field_or_fields = "fields" if len(missing) > 1 else "field"
77
+ print(
78
+ f"\nPlease fix the {len(missing)} {field_or_fields} to have the backend models synced with the typescript interfaces."
79
+ )
80
+ sys.exit(1)
81
+
82
+ print("All models are synced with their corresponding TypeScript interfaces.")
55
83
 
56
84
 
57
85
  if __name__ == "__main__":
58
- cli()
86
+ main()
File without changes
@@ -0,0 +1,113 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+ """
3
+ Module for parsing Django models and extracting field information.
4
+ """
5
+
6
+ import ast
7
+ from typing import Dict, Set
8
+
9
+
10
+ class DjangoModelVisitor(ast.NodeVisitor):
11
+ """
12
+ AST visitor to extract fields from Django models.
13
+ """
14
+
15
+ DJANGO_FIELD_TYPES = {
16
+ "Field",
17
+ "CharField",
18
+ "TextField",
19
+ "IntegerField",
20
+ "BooleanField",
21
+ "DateTimeField",
22
+ "ForeignKey",
23
+ "ManyToManyField",
24
+ "OneToOneField",
25
+ "EmailField",
26
+ "URLField",
27
+ "FileField",
28
+ "ImageField",
29
+ "DecimalField",
30
+ "AutoField",
31
+ }
32
+
33
+ def __init__(self) -> None:
34
+ self.models: Dict[str, Set[str]] = {}
35
+ self.current_model: str | None = None
36
+
37
+ def visit_ClassDef(self, node: ast.ClassDef) -> None:
38
+ """
39
+ Check class definitions, specifically those that inherit from other classes.
40
+
41
+ Parameters
42
+ ----------
43
+ node : ast.ClassDef
44
+ A class definition from Python AST (Abstract Syntax Tree).
45
+ It contains information about the class, such as its name, base classes, body, decorators, etc.
46
+ """
47
+ # Only process classes that inherit from something.
48
+ if node.bases:
49
+ self.current_model = node.name
50
+ if self.current_model not in self.models:
51
+ self.models[self.current_model] = set()
52
+
53
+ self.generic_visit(node)
54
+
55
+ self.current_model = None
56
+
57
+ def visit_Assign(self, node: ast.Assign) -> None:
58
+ """
59
+ Check assignment statements within a class.
60
+
61
+ Parameters
62
+ ----------
63
+ node : ast.Assign
64
+ An assignment definition from Python AST (Abstract Syntax Tree).
65
+ It represents an assignment statement (e.g., x = 42).
66
+ """
67
+ if not self.current_model:
68
+ return
69
+
70
+ for target in node.targets:
71
+ if (
72
+ isinstance(target, ast.Name)
73
+ and not target.id.startswith("_")
74
+ and isinstance(node.value, ast.Call)
75
+ and hasattr(node.value.func, "attr")
76
+ ) and any(
77
+ field_type in node.value.func.attr
78
+ for field_type in self.DJANGO_FIELD_TYPES
79
+ ):
80
+ self.models[self.current_model].add(target.id)
81
+
82
+
83
+ def extract_model_fields(models_file: str) -> Dict[str, Set[str]]:
84
+ """
85
+ Extract fields from Django models file.
86
+
87
+ Parameters
88
+ ----------
89
+ models_file : str
90
+ A models.py file that defines Django models.
91
+
92
+ Returns
93
+ -------
94
+ Dict[str, Set[str]]
95
+ The fields from the models file extracted into a dictionary for future processing.
96
+ """
97
+ with open(models_file, "r", encoding="utf-8") as f:
98
+ content = f.read().strip()
99
+ # Skip any empty lines at the beginning
100
+ while content.startswith("\n"):
101
+ content = content[1:]
102
+
103
+ try:
104
+ tree = ast.parse(content)
105
+ except SyntaxError as e:
106
+ raise SyntaxError(
107
+ f"Failed to parse {models_file}. Make sure it's a valid Python file. Error: {str(e)}"
108
+ ) from e
109
+
110
+ visitor = DjangoModelVisitor()
111
+ visitor.visit(tree)
112
+
113
+ return visitor.models
@@ -0,0 +1,105 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+ """
3
+ Module for parsing TypeScript interfaces and types.
4
+ """
5
+
6
+ import re
7
+ from dataclasses import dataclass
8
+ from typing import Dict, List, Set
9
+
10
+
11
+ @dataclass
12
+ class TypeScriptInterface:
13
+ """
14
+ Represents a TypeScript interface with its fields and parent interfaces.
15
+ """
16
+
17
+ name: str
18
+ fields: Set[str]
19
+ parents: List[str]
20
+
21
+
22
+ class TypeScriptParser:
23
+ """
24
+ Parser for TypeScript interface files.
25
+
26
+ Parameters
27
+ ----------
28
+ file_path : str
29
+ The file path for the TypeScript file to parse.
30
+ """
31
+
32
+ def __init__(self, file_path: str) -> None:
33
+ self.file_path = file_path
34
+ with open(file_path, "r", encoding="utf-8") as f:
35
+ self.content = f.read()
36
+
37
+ def parse_interfaces(self) -> Dict[str, TypeScriptInterface]:
38
+ """
39
+ Parse TypeScript interfaces from the file.
40
+
41
+ Returns
42
+ -------
43
+ Dict[str, TypeScriptInterface]
44
+ The interface parsed into a dictionary for future processing.
45
+ """
46
+ interfaces = {}
47
+ interface_pattern = (
48
+ r"(?:export\s+|declare\s+)?interface\s+(\w+)"
49
+ r"(?:\s+extends\s+([^{]+))?\s*{([\s\S]*?)}"
50
+ )
51
+
52
+ for match in re.finditer(interface_pattern, self.content):
53
+ name = match.group(1)
54
+ parents = (
55
+ [p.strip() for p in match.group(2).split(",")] if match.group(2) else []
56
+ )
57
+ fields = self._extract_fields(match.group(3))
58
+
59
+ interfaces[name] = TypeScriptInterface(name, fields, parents)
60
+
61
+ return interfaces
62
+
63
+ def get_backend_only_fields(self) -> Set[str]:
64
+ """
65
+ Extract fields marked as backend-only in comments.
66
+
67
+ Returns
68
+ -------
69
+ Set[str]
70
+ The field names that are marked with a backend-only identifier to ignore them.
71
+ """
72
+ patterns = [
73
+ r"//.*?Note:\s*(\w+)\s+is\s+backend\s+only",
74
+ r"//.*?(\w+)\s+is\s+backend\s+only",
75
+ r"//\s*@backend-only\s+(\w+)",
76
+ r"//.*?backend-only:\s*(\w+)",
77
+ ]
78
+ return {
79
+ match for pattern in patterns for match in re.findall(pattern, self.content)
80
+ }
81
+
82
+ @staticmethod
83
+ def _extract_fields(interface_body: str) -> Set[str]:
84
+ """
85
+ Extract field names from interface body.
86
+
87
+ Parameters
88
+ ----------
89
+ interface_body : str
90
+ A string representation of the interface body of the model.
91
+
92
+ Returns
93
+ -------
94
+ Set[str]
95
+ The field names from the model interface body.
96
+ """
97
+ fields = set()
98
+
99
+ # Regular fields
100
+ fields.update(re.findall(r"(?://[^\n]*\n)*\s*(\w+)\s*[?]?\s*:", interface_body))
101
+
102
+ # Readonly fields
103
+ fields.update(re.findall(r"readonly\s+(\w+)\s*[?]?\s*:", interface_body))
104
+
105
+ return fields
ts_backend_check/utils.py CHANGED
@@ -6,7 +6,7 @@ Utility functions for ts-backend-check.
6
6
 
7
7
  def snake_to_camel(input_str: str) -> str:
8
8
  """
9
- Convert snake_case to camelCase.
9
+ Convert snake_case to camelCase while preserving existing camelCase components.
10
10
 
11
11
  Parameters
12
12
  ----------
@@ -17,6 +17,23 @@ def snake_to_camel(input_str: str) -> str:
17
17
  -------
18
18
  str
19
19
  The camelCase version of the input string.
20
+
21
+ Examples
22
+ --------
23
+ hello_world -> helloWorld, alreadyCamelCase -> alreadyCamelCase
20
24
  """
21
- components = input_str.split("_")
22
- return components[0] + "".join(x.title() for x in components[1:])
25
+ if not input_str or input_str.startswith("_"):
26
+ return input_str
27
+
28
+ words = input_str.split("_")
29
+ result = words[0].lower()
30
+
31
+ for word in words[1:]:
32
+ if word:
33
+ if any(c.isupper() for c in word[1:]):
34
+ result += word[0].upper() + word[1:]
35
+
36
+ else:
37
+ result += word[0].upper() + word[1:].lower()
38
+
39
+ return result
@@ -1,21 +1,39 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ts-backend-check
3
- Version: 1.0.0
4
- Summary: ts-backend-check is a Python package used to check TypeScript types against their corresponding backend models to assure that all fields have been accounted for.
3
+ Version: 1.1.0
4
+ Summary: Check TypeScript types against their corresponding backend models to assure that all fields have been accounted for.
5
5
  Home-page: https://github.com/activist-org/ts-backend-check
6
6
  Author: ts-backend-check developers
7
- Author-email: andrew.t.mcallister@gmail.com
7
+ Author-email: team@activist.org
8
+ License: : OSI Approved :: GNU License
9
+ Keywords: backend,typescript,validation,ci,cli
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
13
+ Classifier: Programming Language :: Python
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Operating System :: OS Independent
8
22
  Requires-Python: >=3.8
9
23
  Description-Content-Type: text/markdown
10
24
  License-File: LICENSE.txt
11
- Requires-Dist: click>=8.1
25
+ Dynamic: author
12
26
  Dynamic: author-email
27
+ Dynamic: classifier
28
+ Dynamic: description
29
+ Dynamic: description-content-type
13
30
  Dynamic: home-page
14
31
  Dynamic: license-file
15
32
  Dynamic: requires-python
33
+ Dynamic: summary
16
34
 
17
35
  <div align="center">
18
- <a href="https://github.com/activist-org/ts-backend-check"><img src="https://raw.githubusercontent.com/activist-org/ts-backend-check/main/.github/resources/TSBECheckGitHubBanner.png" width=1024 alt="TSBE Check logo"></a>
36
+ <a href="https://github.com/activist-org/ts-backend-check"><img src="https://raw.githubusercontent.com/activist-org/ts-backend-check/main/.github/resources/TSBackendCheckGitHubBanner.png" width=1024 alt="TS Backend Check logo"></a>
19
37
  </div>
20
38
 
21
39
  [![rtd](https://img.shields.io/readthedocs/ts-backend-check.svg?label=%20&logo=read-the-docs&logoColor=ffffff)](http://ts-backend-check.readthedocs.io/en/latest/)
@@ -51,6 +69,11 @@ Dynamic: requires-python
51
69
  pip install ts-backend-check
52
70
  ```
53
71
 
72
+ ### Command Options
73
+
74
+ - `backend-model-file` (`bmf`): Path to the backend model file (e.g. Python class)
75
+ - `typescript-file` (`tsf`): Path to the TypeScript interface/type file
76
+
54
77
  ### Basic Usage
55
78
 
56
79
  The CLI provides a simple interface to check TypeScript types against backend models:
@@ -60,29 +83,17 @@ The CLI provides a simple interface to check TypeScript types against backend mo
60
83
  ts-backend-check --help
61
84
 
62
85
  # Check a TypeScript type against a backend model:
63
- ts-backend-check check <typescript_file> <backend_model>
86
+ ts-backend-check -bmf <backend-model-file> -tsf <typescript-file>
64
87
 
65
88
  # Example command:
66
- ts-backend-check check src/types/user.ts src/models/user.py
67
- ```
68
-
69
- ### Command Options
70
-
71
- - `check`: Compare TypeScript types with backend models
72
- - `typescript_file`: Path to the TypeScript interface/type file
73
- - `backend_model`: Path to the backend model file (e.g. Python class)
74
-
75
- ### Version Information
76
-
77
- ```bash
78
- ts-backend-check --version
89
+ ts-backend-check -bmf src/models/user.py -tsf src/types/user.ts
79
90
  ```
80
91
 
81
92
  <a id="contributing"></a>
82
93
 
83
94
  # Contributing [`⇧`](#contents)
84
95
 
85
- <a href="https://matrix.to/#/#activist_community:matrix.org"><img src="https://raw.githubusercontent.com/activist-org/Organization/main/resources/images/logos/MatrixLogoGrey.png" height="50" alt="Public Matrix Chat" align="right"></a>
96
+ <a href="https://matrix.to/#/#activist_community:matrix.org"><img src="https://raw.githubusercontent.com/activist-org/Organization/main/resources/images/logos/MatrixLogoGrey.png" width="175" alt="Public Matrix Chat" align="right"></a>
86
97
 
87
98
  activist uses [Matrix](https://matrix.org/) for internal communication. You're more than welcome to [join us in our public chat rooms](https://matrix.to/#/#activist_community:matrix.org) to share ideas, ask questions or just say hi to the team :)
88
99
 
@@ -0,0 +1,17 @@
1
+ ts_backend_check/__init__.py,sha256=HSiEqWvU3Q4a6D7tYsoq3-B-g1HdUOGngEeGV45j8ak,172
2
+ ts_backend_check/checker.py,sha256=svfIJ9lNaSSy-zSGsM4wYD9Z9JK_xL5l6JA-w9pdf_o,5791
3
+ ts_backend_check/django_parser.py,sha256=CFg4-zV5BsVhuq-nVQEYD9nS25JSaHbIs2Xr3LE3KWY,3241
4
+ ts_backend_check/typescript_parser.py,sha256=BqBGOjsf-Wlh6ceaK-G2vLm5AMiEiwX-XnJQrr8P-gQ,2885
5
+ ts_backend_check/utils.py,sha256=N9_25_wW2g3dkmGGLWHQj_AHsXI9Rx2XqRnYxT5i2Rk,897
6
+ ts_backend_check/cli/__init__.py,sha256=wJK9tO9MtI10L0xRjrk7WP_qZZRBjbFw1U9jJbIbX7I,108
7
+ ts_backend_check/cli/main.py,sha256=C3JCGv4QNo-xlp-MlCK49nzGAzuwWYDYLBi-OS7bvXQ,2712
8
+ ts_backend_check/parsers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ ts_backend_check/parsers/django_parser.py,sha256=CFg4-zV5BsVhuq-nVQEYD9nS25JSaHbIs2Xr3LE3KWY,3241
10
+ ts_backend_check/parsers/typescript_parser.py,sha256=BqBGOjsf-Wlh6ceaK-G2vLm5AMiEiwX-XnJQrr8P-gQ,2885
11
+ ts_backend_check-1.1.0.data/data/requirements.txt,sha256=0FbFr7FTG31FEs_FMsN3MrAIFJIeGdDC6POOOb2FeA0,134
12
+ ts_backend_check-1.1.0.dist-info/licenses/LICENSE.txt,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
13
+ ts_backend_check-1.1.0.dist-info/METADATA,sha256=XSvF56GNwNfAJa1e8l1pa42Jz_n_9hG8TmR6yDx-N_A,9734
14
+ ts_backend_check-1.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
+ ts_backend_check-1.1.0.dist-info/entry_points.txt,sha256=QeY7RJu20otBnQlhMMZxPL8nB3mP3M3U3h3nOlSFWSU,68
16
+ ts_backend_check-1.1.0.dist-info/top_level.txt,sha256=VzfNWQ3fPNdl-OBdtUKsUWR8Asdd27E3OJNUg2oQU8Y,17
17
+ ts_backend_check-1.1.0.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ts-backend-check = ts_backend_check.cli.main:main
@@ -1,14 +0,0 @@
1
- ts_backend_check/__init__.py,sha256=5Y_YDmaFl9cjVy1GqGeml-QaPUpcJyWgjCYe_vJoHvw,156
2
- ts_backend_check/checker.py,sha256=8mLtLBt6ok2cDgt-3u8yb3hClf-3ojU8zIsqL2z_GdQ,5727
3
- ts_backend_check/django_parser.py,sha256=CFg4-zV5BsVhuq-nVQEYD9nS25JSaHbIs2Xr3LE3KWY,3241
4
- ts_backend_check/typescript_parser.py,sha256=BqBGOjsf-Wlh6ceaK-G2vLm5AMiEiwX-XnJQrr8P-gQ,2885
5
- ts_backend_check/utils.py,sha256=Kfa6ZvjnsGrGJJX18FhfzmQtjMrjPwfECZiZL4zgGRQ,475
6
- ts_backend_check/cli/__init__.py,sha256=bxThVZZgFRXoWgZ4pasljY6ATZPTJVh_KeBebweDJb0,129
7
- ts_backend_check/cli/main.py,sha256=RUJsonWBUnCqg93ayuDo0LJYhCLKuVb9V9Ycx5cbts4,1636
8
- ts_backend_check-1.0.0.data/data/requirements.txt,sha256=0FbFr7FTG31FEs_FMsN3MrAIFJIeGdDC6POOOb2FeA0,134
9
- ts_backend_check-1.0.0.dist-info/licenses/LICENSE.txt,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
10
- ts_backend_check-1.0.0.dist-info/METADATA,sha256=1hA_CrBvSqePiw3G0ZBAvm5kEmGBRSYHGlUOmsagJnw,9084
11
- ts_backend_check-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
- ts_backend_check-1.0.0.dist-info/entry_points.txt,sha256=6_OttJoqgUr1BZYjy6Rcx5g0iYcwJD2o1c5q_6OcXzw,58
13
- ts_backend_check-1.0.0.dist-info/top_level.txt,sha256=VzfNWQ3fPNdl-OBdtUKsUWR8Asdd27E3OJNUg2oQU8Y,17
14
- ts_backend_check-1.0.0.dist-info/RECORD,,
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- ts-backend-check = ts_backend_check:cli