ts-backend-check 1.0.0__py3-none-any.whl → 1.2.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.
- ts_backend_check/__init__.py +3 -5
- ts_backend_check/checker.py +9 -9
- ts_backend_check/cli/__init__.py +2 -6
- ts_backend_check/cli/check_blank.py +111 -0
- ts_backend_check/cli/config.py +164 -0
- ts_backend_check/cli/main.py +123 -35
- ts_backend_check/cli/upgrade.py +91 -0
- ts_backend_check/cli/version.py +87 -0
- ts_backend_check/parsers/__init__.py +0 -0
- ts_backend_check/parsers/django_parser.py +113 -0
- ts_backend_check/parsers/typescript_parser.py +105 -0
- ts_backend_check/utils.py +20 -3
- ts_backend_check-1.2.0.data/data/requirements.txt +26 -0
- {ts_backend_check-1.0.0.dist-info → ts_backend_check-1.2.0.dist-info}/METADATA +50 -28
- ts_backend_check-1.2.0.dist-info/RECORD +21 -0
- ts_backend_check-1.2.0.dist-info/entry_points.txt +2 -0
- ts_backend_check-1.0.0.data/data/requirements.txt +0 -6
- ts_backend_check-1.0.0.dist-info/RECORD +0 -14
- ts_backend_check-1.0.0.dist-info/entry_points.txt +0 -2
- {ts_backend_check-1.0.0.dist-info → ts_backend_check-1.2.0.dist-info}/WHEEL +0 -0
- {ts_backend_check-1.0.0.dist-info → ts_backend_check-1.2.0.dist-info}/licenses/LICENSE.txt +0 -0
- {ts_backend_check-1.0.0.dist-info → ts_backend_check-1.2.0.dist-info}/top_level.txt +0 -0
ts_backend_check/__init__.py
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
-
|
|
2
|
+
from ts_backend_check.checker import TypeChecker
|
|
3
|
+
from ts_backend_check.cli.main import main
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
from .cli import cli
|
|
6
|
-
|
|
7
|
-
__all__ = ["cli", "TypeChecker"]
|
|
5
|
+
__all__ = ["main", "TypeChecker"]
|
ts_backend_check/checker.py
CHANGED
|
@@ -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:
|
|
@@ -149,8 +149,8 @@ class TypeChecker:
|
|
|
149
149
|
"""
|
|
150
150
|
potential_names = TypeChecker._generate_potential_names(model_name)
|
|
151
151
|
return (
|
|
152
|
-
f"\nNo matching TypeScript interface found for model: {model_name}
|
|
153
|
-
f"
|
|
152
|
+
f"\nNo matching TypeScript interface found for model: {model_name}"
|
|
153
|
+
f"\nSearched for interfaces: {', '.join(potential_names)}"
|
|
154
154
|
)
|
|
155
155
|
|
|
156
156
|
@staticmethod
|
|
@@ -178,8 +178,8 @@ class TypeChecker:
|
|
|
178
178
|
"""
|
|
179
179
|
camel_field = snake_to_camel(input_str=field)
|
|
180
180
|
return (
|
|
181
|
-
f"\nField '{field}' (camelCase: '{camel_field}') from model '{model_name}' "
|
|
182
|
-
f"
|
|
183
|
-
f"
|
|
184
|
-
|
|
181
|
+
f"\nField '{field}' (camelCase: '{camel_field}') from model '{model_name}' is missing in TypeScript types."
|
|
182
|
+
f"\nExpected to find in interface(s): {', '.join(interfaces.keys())}"
|
|
183
|
+
f"\nTo ignore this field, add a comment that references it like: '// Note: {camel_field} is backend only'"
|
|
184
|
+
"\n"
|
|
185
185
|
)
|
ts_backend_check/cli/__init__.py
CHANGED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
"""
|
|
3
|
+
Functionality to check TypeScript interfaces for fields that should be optional based on Django models.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import ast
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Dict, Set
|
|
9
|
+
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
|
|
12
|
+
from ts_backend_check.parsers.django_parser import DjangoModelVisitor
|
|
13
|
+
|
|
14
|
+
ROOT_DIR = Path.cwd()
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BlankParser(DjangoModelVisitor):
|
|
19
|
+
"""
|
|
20
|
+
AST visitor to extract blank fields from Django models based on DjangoModelVisitor.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self) -> None:
|
|
24
|
+
super().__init__()
|
|
25
|
+
self.blank_models: Dict[str, Set[str]] = {}
|
|
26
|
+
|
|
27
|
+
def visit_Assign(self, node: ast.Assign) -> None:
|
|
28
|
+
"""
|
|
29
|
+
Check assignment statements within a class which accepts blank fields.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
node : ast.Assign
|
|
34
|
+
An assignment definition from Python AST (Abstract Syntax Tree).
|
|
35
|
+
It represents an assignment statement (e.g., x = 42).
|
|
36
|
+
"""
|
|
37
|
+
if not self.current_model:
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
for target in node.targets:
|
|
41
|
+
if (
|
|
42
|
+
(
|
|
43
|
+
isinstance(target, ast.Name)
|
|
44
|
+
and not target.id.startswith("_")
|
|
45
|
+
and isinstance(node.value, ast.Call)
|
|
46
|
+
and hasattr(node.value.func, "attr")
|
|
47
|
+
)
|
|
48
|
+
and any(
|
|
49
|
+
field_type in node.value.func.attr
|
|
50
|
+
for field_type in self.DJANGO_FIELD_TYPES
|
|
51
|
+
)
|
|
52
|
+
and any(
|
|
53
|
+
kw.arg == "blank"
|
|
54
|
+
and isinstance(kw.value, ast.Constant)
|
|
55
|
+
and kw.value.value is True
|
|
56
|
+
for kw in node.value.keywords
|
|
57
|
+
)
|
|
58
|
+
):
|
|
59
|
+
if self.current_model not in self.blank_models:
|
|
60
|
+
self.blank_models[self.current_model] = set()
|
|
61
|
+
|
|
62
|
+
self.blank_models[self.current_model].add(target.id)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def check_blank(file_path: str) -> Dict[str, Set[str]]:
|
|
66
|
+
"""
|
|
67
|
+
Function to extract fields from Django models file which accepts blank values.
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
file_path : str
|
|
72
|
+
A models.py file that defines Django models.
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
Dict[str, Set[str]]
|
|
77
|
+
The fields from the models file extracted into a dictionary for future processing.
|
|
78
|
+
"""
|
|
79
|
+
model_path = ROOT_DIR / file_path
|
|
80
|
+
|
|
81
|
+
if model_path.is_file():
|
|
82
|
+
with open(model_path, "r", encoding="utf-8") as f:
|
|
83
|
+
content = f.read().strip()
|
|
84
|
+
# Skip any empty lines at the beginning.
|
|
85
|
+
while content.startswith("\n"):
|
|
86
|
+
content = content[1:]
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
tree = ast.parse(content)
|
|
90
|
+
|
|
91
|
+
except SyntaxError as e:
|
|
92
|
+
raise SyntaxError(
|
|
93
|
+
f"Failed to parse {model_path}. Make sure it's a valid Python file. Error: {str(e)}"
|
|
94
|
+
) from e
|
|
95
|
+
|
|
96
|
+
parser = BlankParser()
|
|
97
|
+
parser.visit(tree)
|
|
98
|
+
|
|
99
|
+
if len(parser.blank_models) == 0:
|
|
100
|
+
console.print("[green]No models have any blank fields specified.[green]")
|
|
101
|
+
|
|
102
|
+
else:
|
|
103
|
+
for k, v in parser.blank_models.items():
|
|
104
|
+
console.print(
|
|
105
|
+
f"[yellow]Model {k} has fields {sorted(v)} set as optional."
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
else:
|
|
109
|
+
print("Check the path entered.")
|
|
110
|
+
|
|
111
|
+
return parser.blank_models
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
"""
|
|
3
|
+
Configure cli to run based on a YAML configuration file.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from yaml import dump
|
|
9
|
+
|
|
10
|
+
YAML_CONFIG_FILE_PATH = (
|
|
11
|
+
Path(__file__).parent.parent.parent.parent / ".ts-backend-check.yaml"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
PROJECT_ROOT_PATH = Path(__file__).parent.parent.parent.parent
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def configure_paths() -> None:
|
|
18
|
+
"""
|
|
19
|
+
Function to receive paths from user.
|
|
20
|
+
"""
|
|
21
|
+
config_options = {}
|
|
22
|
+
while True:
|
|
23
|
+
print("\n--- Adding new model/interface configuration ---")
|
|
24
|
+
|
|
25
|
+
key = input(
|
|
26
|
+
"Enter the model-interface type (eg: 'auth', 'orgs', 'groups'): "
|
|
27
|
+
).strip()
|
|
28
|
+
if not key:
|
|
29
|
+
print("Key cannot be empty. Please try again.")
|
|
30
|
+
continue
|
|
31
|
+
|
|
32
|
+
# Get backend path.
|
|
33
|
+
while True:
|
|
34
|
+
backend_path = input("Enter the path for Django models.py file: ").strip()
|
|
35
|
+
if not backend_path:
|
|
36
|
+
print("Path cannot be empty.")
|
|
37
|
+
continue
|
|
38
|
+
|
|
39
|
+
if path_exists(backend_path):
|
|
40
|
+
break
|
|
41
|
+
|
|
42
|
+
print(f"File not found: {PROJECT_ROOT_PATH / backend_path}")
|
|
43
|
+
print("Please check the path and try again.")
|
|
44
|
+
|
|
45
|
+
# Get frontend path.
|
|
46
|
+
while True:
|
|
47
|
+
frontend_path = input("Enter path to TypeScript interface file: ").strip()
|
|
48
|
+
if not frontend_path:
|
|
49
|
+
print("Path cannot be empty. Please try again.")
|
|
50
|
+
continue
|
|
51
|
+
|
|
52
|
+
if path_exists(frontend_path):
|
|
53
|
+
break
|
|
54
|
+
|
|
55
|
+
print(f"File not found: {PROJECT_ROOT_PATH / frontend_path}")
|
|
56
|
+
print("Please check the path and try again.")
|
|
57
|
+
|
|
58
|
+
config_options[key] = {
|
|
59
|
+
"backend_model_path": backend_path,
|
|
60
|
+
"frontend_interface_path": frontend_path,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
print(f"✓ Added configuration for '{key}'")
|
|
64
|
+
|
|
65
|
+
continue_config = input(
|
|
66
|
+
"Do you wish to add another model/interface configuration? (y/[n])"
|
|
67
|
+
).strip()
|
|
68
|
+
|
|
69
|
+
if continue_config.lower() in ["n", ""]:
|
|
70
|
+
if config_options:
|
|
71
|
+
write_config(config_options)
|
|
72
|
+
print(
|
|
73
|
+
f"\n✓ Configuration complete! Added {len(config_options)} configuration(s)."
|
|
74
|
+
)
|
|
75
|
+
break
|
|
76
|
+
|
|
77
|
+
if config_options:
|
|
78
|
+
write_config(config_options)
|
|
79
|
+
print(
|
|
80
|
+
f"\n✓ Configuration complete! Added {len(config_options)} configuration(s)."
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
else:
|
|
84
|
+
print("\nNo configurations added.")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def path_exists(path: str) -> bool:
|
|
88
|
+
"""
|
|
89
|
+
Check if path entered by the user exists withing the filesystem.
|
|
90
|
+
|
|
91
|
+
Parameters
|
|
92
|
+
----------
|
|
93
|
+
path : str
|
|
94
|
+
Path should be entered as a string from the user.
|
|
95
|
+
|
|
96
|
+
Returns
|
|
97
|
+
-------
|
|
98
|
+
bool
|
|
99
|
+
Return true or false based on if path exists.
|
|
100
|
+
"""
|
|
101
|
+
full_path = Path(__file__).parent.parent.parent.parent / path
|
|
102
|
+
if Path(full_path).is_file():
|
|
103
|
+
return True
|
|
104
|
+
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def write_config(config: dict[str, dict[str, str]]) -> None:
|
|
109
|
+
"""
|
|
110
|
+
Function to write into .ts-backend-check.yaml file.
|
|
111
|
+
|
|
112
|
+
Parameters
|
|
113
|
+
----------
|
|
114
|
+
config : dict[str, dict[str, str]]
|
|
115
|
+
Passing a dictionary as key str with another dict as value.
|
|
116
|
+
"""
|
|
117
|
+
try:
|
|
118
|
+
options = f"""# Configuration file for ts-backend-check validation.
|
|
119
|
+
# See https://github.com/activist-org/ts-backend-check for details.
|
|
120
|
+
|
|
121
|
+
# Paths:
|
|
122
|
+
{dump(config)}
|
|
123
|
+
|
|
124
|
+
"""
|
|
125
|
+
with open(YAML_CONFIG_FILE_PATH, "w") as file:
|
|
126
|
+
file.write(options)
|
|
127
|
+
|
|
128
|
+
except IOError as e:
|
|
129
|
+
print(f"Error while writing config file: {e}")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def create_config() -> None:
|
|
133
|
+
"""
|
|
134
|
+
Main function to create or update configuration.
|
|
135
|
+
"""
|
|
136
|
+
print("ts-backend-check Configuration Setup")
|
|
137
|
+
print("=" * 40)
|
|
138
|
+
|
|
139
|
+
if YAML_CONFIG_FILE_PATH.is_file():
|
|
140
|
+
reconfig_choice = input(
|
|
141
|
+
"Configuration file exists. Do you want to re-configure your .ts-backend-check.yaml file? (y/[n]) "
|
|
142
|
+
)
|
|
143
|
+
if reconfig_choice.lower() in ["n", ""]:
|
|
144
|
+
print("Exiting without changes.")
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
print("Reconfiguring...")
|
|
148
|
+
|
|
149
|
+
else:
|
|
150
|
+
print("Creating new configuration file...")
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
configure_paths()
|
|
154
|
+
|
|
155
|
+
except KeyboardInterrupt:
|
|
156
|
+
print("\n\nConfiguration cancelled by user.")
|
|
157
|
+
|
|
158
|
+
except Exception as e:
|
|
159
|
+
print(f"\nError during configuration: {e}")
|
|
160
|
+
print("Configuration cancelled.")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
if __name__ == "__main__":
|
|
164
|
+
create_config()
|
ts_backend_check/cli/main.py
CHANGED
|
@@ -3,56 +3,144 @@
|
|
|
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 argparse import ArgumentParser
|
|
9
|
+
from pathlib import Path
|
|
7
10
|
|
|
8
|
-
import
|
|
11
|
+
from rich import print as rprint
|
|
12
|
+
from rich.text import Text
|
|
9
13
|
|
|
10
|
-
from
|
|
14
|
+
from ts_backend_check.checker import TypeChecker
|
|
15
|
+
from ts_backend_check.cli.check_blank import check_blank
|
|
16
|
+
from ts_backend_check.cli.config import create_config
|
|
17
|
+
from ts_backend_check.cli.upgrade import upgrade_cli
|
|
18
|
+
from ts_backend_check.cli.version import get_version_message
|
|
11
19
|
|
|
20
|
+
ROOT_DIR = Path.cwd()
|
|
12
21
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def cli():
|
|
16
|
-
"""
|
|
17
|
-
TS Backend Check is a Python package used to check TypeScript types against their corresponding backend models.
|
|
22
|
+
|
|
23
|
+
def main() -> None:
|
|
18
24
|
"""
|
|
19
|
-
|
|
25
|
+
The main check function to compare a the methods within a backend model to a corresponding TypeScript file.
|
|
20
26
|
|
|
27
|
+
Notes
|
|
28
|
+
-----
|
|
29
|
+
The available command line arguments are:
|
|
30
|
+
- --backend-model-file (-bmf): Path to the backend model file (e.g. Python class)
|
|
31
|
+
- --typescript-file (-tsf): Path to the TypeScript interface/type file
|
|
21
32
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def check(backend_model: str, typescript_file: str):
|
|
33
|
+
Examples
|
|
34
|
+
--------
|
|
35
|
+
>>> ts-backend-check -bmf <backend-model-file> -tsf <typescript-file>
|
|
26
36
|
"""
|
|
27
|
-
|
|
37
|
+
# MARK: CLI Base
|
|
28
38
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
39
|
+
parser = ArgumentParser(
|
|
40
|
+
prog="ts-backend-check",
|
|
41
|
+
description="Checks the types in TypeScript files against the corresponding backend models.",
|
|
42
|
+
epilog="Visit the codebase at https://github.com/activist-org/ts-backend-check to learn more!",
|
|
43
|
+
formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=60),
|
|
44
|
+
)
|
|
32
45
|
|
|
33
|
-
|
|
34
|
-
----------
|
|
35
|
-
backend_model : str
|
|
36
|
-
The path to the backend model file (e.g. Python class).
|
|
46
|
+
parser._actions[0].help = "Show this help message and exit."
|
|
37
47
|
|
|
38
|
-
|
|
39
|
-
|
|
48
|
+
parser.add_argument(
|
|
49
|
+
"-v",
|
|
50
|
+
"--version",
|
|
51
|
+
action="version",
|
|
52
|
+
version=f"{get_version_message()}",
|
|
53
|
+
help="Show the version of the ts-backend-check CLI.",
|
|
54
|
+
)
|
|
40
55
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
56
|
+
parser.add_argument(
|
|
57
|
+
"-u",
|
|
58
|
+
"--upgrade",
|
|
59
|
+
action="store_true",
|
|
60
|
+
help="Upgrade the ts-backend-check CLI to the latest version.",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
parser.add_argument(
|
|
64
|
+
"-bmf",
|
|
65
|
+
"--backend-model-file",
|
|
66
|
+
help="Path to the backend model file (e.g. Python class).",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
parser.add_argument(
|
|
70
|
+
"-tsf",
|
|
71
|
+
"--typescript-file",
|
|
72
|
+
help="Path to the TypeScript interface/type file.",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
parser.add_argument(
|
|
76
|
+
"-c",
|
|
77
|
+
"--configure",
|
|
78
|
+
action="store_true",
|
|
79
|
+
help="Configure a YAML file to simplify your checks.",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
parser.add_argument(
|
|
83
|
+
"-cb",
|
|
84
|
+
"--check-blank",
|
|
85
|
+
help="Check for fields marked blank=True within Django models.",
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# MARK: Setup CLI
|
|
89
|
+
|
|
90
|
+
args = parser.parse_args(args=None if sys.argv[1:] else ["--help"])
|
|
91
|
+
|
|
92
|
+
if args.upgrade:
|
|
93
|
+
upgrade_cli()
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
if args.configure:
|
|
97
|
+
create_config()
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
if args.check_blank:
|
|
101
|
+
check_blank(args.check_blank)
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
# MARK: Run Check
|
|
105
|
+
|
|
106
|
+
backend_model_file_path = ROOT_DIR / args.backend_model_file
|
|
107
|
+
ts_file_path = ROOT_DIR / args.typescript_file
|
|
108
|
+
|
|
109
|
+
if not backend_model_file_path.is_file():
|
|
110
|
+
rprint(
|
|
111
|
+
f"[red]{args.backend_model_file} that should contain the backend models does not exist. Please check and try again.[/red]"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
elif not ts_file_path.is_file():
|
|
115
|
+
rprint(
|
|
116
|
+
f"[red]{args.typescript_file} file that should contain the TypeScript types does not exist. Please check and try again.[/red]"
|
|
51
117
|
)
|
|
52
|
-
sys.exit(1)
|
|
53
118
|
|
|
54
|
-
|
|
119
|
+
else:
|
|
120
|
+
checker = TypeChecker(
|
|
121
|
+
models_file=args.backend_model_file,
|
|
122
|
+
types_file=args.typescript_file,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
if missing := checker.check():
|
|
126
|
+
rprint(
|
|
127
|
+
"\n[red]❌ ts-backend-check error: Missing typescript fields found:[/red]\n"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Print each error message in red.
|
|
131
|
+
for msg in missing:
|
|
132
|
+
rprint(Text.from_markup(f"[red]{msg}[/red]"))
|
|
133
|
+
|
|
134
|
+
field_or_fields = "fields" if len(missing) > 1 else "field"
|
|
135
|
+
rprint(
|
|
136
|
+
f"\n[red]Please fix the {len(missing)} {field_or_fields} above to have the backend models synced with the typescript interfaces.[/red]"
|
|
137
|
+
)
|
|
138
|
+
sys.exit(1)
|
|
139
|
+
|
|
140
|
+
rprint(
|
|
141
|
+
"[green]✅ Success: All models are synced with their corresponding TypeScript interfaces.[/green]"
|
|
142
|
+
)
|
|
55
143
|
|
|
56
144
|
|
|
57
145
|
if __name__ == "__main__":
|
|
58
|
-
|
|
146
|
+
main()
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
"""
|
|
3
|
+
Functions to update the ts-backend-check CLI based on install method.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from packaging import version
|
|
10
|
+
from packaging.version import InvalidVersion
|
|
11
|
+
|
|
12
|
+
from ts_backend_check.cli.version import (
|
|
13
|
+
UNKNOWN_VERSION_NOT_FETCHED,
|
|
14
|
+
get_latest_version,
|
|
15
|
+
get_local_version,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def upgrade_cli() -> None:
|
|
20
|
+
"""
|
|
21
|
+
Upgrade the CLI tool to the latest available version on PyPI.
|
|
22
|
+
|
|
23
|
+
Raises
|
|
24
|
+
------
|
|
25
|
+
subprocess.CalledProcessError
|
|
26
|
+
If the installation of the latest version fails.
|
|
27
|
+
"""
|
|
28
|
+
local_version = get_local_version()
|
|
29
|
+
latest_version_message = get_latest_version()
|
|
30
|
+
|
|
31
|
+
if latest_version_message == UNKNOWN_VERSION_NOT_FETCHED:
|
|
32
|
+
print(
|
|
33
|
+
"Unable to fetch the latest version from GitHub. Please check the GitHub repository or your internet connection."
|
|
34
|
+
)
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
latest_version = latest_version_message.split("v")[-1]
|
|
38
|
+
local_version_clean = local_version.strip()
|
|
39
|
+
latest_version_clean = latest_version.replace("ts-backend-check", "").strip()
|
|
40
|
+
|
|
41
|
+
# Handle empty or invalid version strings.
|
|
42
|
+
try:
|
|
43
|
+
local_ver = (
|
|
44
|
+
version.parse(local_version_clean)
|
|
45
|
+
if local_version_clean
|
|
46
|
+
else version.parse("0.0.0")
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
except InvalidVersion:
|
|
50
|
+
# If local version is invalid, treat it as 0.0.0 to force upgrade.
|
|
51
|
+
local_ver = version.parse("0.0.0")
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
latest_ver = version.parse(latest_version_clean)
|
|
55
|
+
|
|
56
|
+
except InvalidVersion:
|
|
57
|
+
print("Unable to parse the latest version. Please check the GitHub repository.")
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
if local_ver == latest_ver:
|
|
61
|
+
print("You already have the latest version of ts-backend-check.")
|
|
62
|
+
|
|
63
|
+
elif local_ver > latest_ver:
|
|
64
|
+
print(
|
|
65
|
+
f"ts-backend-check v{local_version_clean} is higher than the currently released version ts-backend-check v{latest_version_clean}. Hopefully this is a development build, and if so, thanks for your work on ts-backend-check! If not, please report this to the team at https://github.com/activist-org/ts-backend-check/issues."
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
else:
|
|
69
|
+
print(f"Current version: {local_version}")
|
|
70
|
+
print(f"Latest version: {latest_version}")
|
|
71
|
+
print("Updating ts-backend-check with pip...")
|
|
72
|
+
try:
|
|
73
|
+
subprocess.check_call(
|
|
74
|
+
[
|
|
75
|
+
sys.executable,
|
|
76
|
+
"-m",
|
|
77
|
+
"pip",
|
|
78
|
+
"install",
|
|
79
|
+
"--upgrade",
|
|
80
|
+
"ts-backend-check",
|
|
81
|
+
]
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
except subprocess.CalledProcessError as e:
|
|
85
|
+
print(
|
|
86
|
+
f"Failed to install the latest version of ts-backend-check with error {e}. Please check the error message and report any issues to the team at https://github.com/activist-org/ts-backend-check/issues."
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
if __name__ == "__main__":
|
|
91
|
+
upgrade_cli()
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
"""
|
|
3
|
+
Functions for checking current version of the ts-backend-check CLI.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import importlib.metadata
|
|
7
|
+
from typing import Any, Dict
|
|
8
|
+
|
|
9
|
+
import requests
|
|
10
|
+
|
|
11
|
+
UNKNOWN_VERSION = "Unknown ts-backend-check version"
|
|
12
|
+
UNKNOWN_VERSION_NOT_PIP = f"{UNKNOWN_VERSION} (Not installed via pip)"
|
|
13
|
+
UNKNOWN_VERSION_NOT_FETCHED = f"{UNKNOWN_VERSION} (Unable to fetch version)"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_local_version() -> str:
|
|
17
|
+
"""
|
|
18
|
+
Get the local version of the ts-backend-check package.
|
|
19
|
+
|
|
20
|
+
Returns
|
|
21
|
+
-------
|
|
22
|
+
str
|
|
23
|
+
The version of the installed ts-backend-check package, or a message indicating
|
|
24
|
+
that the package is not installed via pip.
|
|
25
|
+
"""
|
|
26
|
+
try:
|
|
27
|
+
return importlib.metadata.version("ts-backend-check")
|
|
28
|
+
|
|
29
|
+
except importlib.metadata.PackageNotFoundError:
|
|
30
|
+
return UNKNOWN_VERSION_NOT_PIP
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_latest_version() -> Any:
|
|
34
|
+
"""
|
|
35
|
+
Get the latest version of the ts-backend-check package from GitHub.
|
|
36
|
+
|
|
37
|
+
Returns
|
|
38
|
+
-------
|
|
39
|
+
Any
|
|
40
|
+
The latest version of the ts-backend-check package, or a message indicating
|
|
41
|
+
that the version could not be fetched.
|
|
42
|
+
"""
|
|
43
|
+
try:
|
|
44
|
+
response = requests.get(
|
|
45
|
+
"https://api.github.com/repos/activist-org/ts-backend-check/releases/latest"
|
|
46
|
+
)
|
|
47
|
+
response_data: Dict[str, Any] = response.json()
|
|
48
|
+
return response_data["name"]
|
|
49
|
+
|
|
50
|
+
except Exception:
|
|
51
|
+
return UNKNOWN_VERSION_NOT_FETCHED
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_version_message() -> str:
|
|
55
|
+
"""
|
|
56
|
+
Get a message indicating the local and latest versions of the ts-backend-check package.
|
|
57
|
+
|
|
58
|
+
Returns
|
|
59
|
+
-------
|
|
60
|
+
str
|
|
61
|
+
A message indicating the local version, the latest version, and whether
|
|
62
|
+
an upgrade is available.
|
|
63
|
+
"""
|
|
64
|
+
local_version = get_local_version()
|
|
65
|
+
latest_version = get_latest_version()
|
|
66
|
+
|
|
67
|
+
if local_version == UNKNOWN_VERSION_NOT_PIP:
|
|
68
|
+
return UNKNOWN_VERSION_NOT_PIP
|
|
69
|
+
|
|
70
|
+
elif latest_version == UNKNOWN_VERSION_NOT_FETCHED:
|
|
71
|
+
return UNKNOWN_VERSION_NOT_FETCHED
|
|
72
|
+
|
|
73
|
+
local_version_clean = local_version.strip()
|
|
74
|
+
latest_version_clean = latest_version.replace("ts-backend-check", "").strip()
|
|
75
|
+
|
|
76
|
+
if local_version_clean == latest_version_clean:
|
|
77
|
+
return f"ts-backend-check v{local_version_clean}"
|
|
78
|
+
|
|
79
|
+
elif local_version_clean > latest_version_clean:
|
|
80
|
+
return f"ts-backend-check v{local_version_clean} is higher than the currently released version ts-backend-check v{latest_version_clean}. Hopefully this is a development build, and if so, thanks for your work on ts-backend-check! If not, please report this to the team at https://github.com/activist-org/ts-backend-check/issues."
|
|
81
|
+
|
|
82
|
+
else:
|
|
83
|
+
return f"ts-backend-check v{local_version_clean} (Upgrade available: ts-backend-check v{latest_version_clean}). To upgrade: ts-backend-check -u"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if __name__ == "__main__":
|
|
87
|
+
print(get_version_message())
|
|
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
|
-
|
|
22
|
-
|
|
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
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This file is autogenerated by pip-compile with Python 3.13
|
|
3
|
+
# by the following command:
|
|
4
|
+
#
|
|
5
|
+
# pip-compile requirements.in
|
|
6
|
+
#
|
|
7
|
+
certifi>=2025.11.12
|
|
8
|
+
# via requests
|
|
9
|
+
charset-normalizer>=3.4.4
|
|
10
|
+
# via requests
|
|
11
|
+
idna>=3.11
|
|
12
|
+
# via requests
|
|
13
|
+
markdown-it-py>=4.0.0
|
|
14
|
+
# via rich
|
|
15
|
+
mdurl>=0.1.2
|
|
16
|
+
# via markdown-it-py
|
|
17
|
+
pygments>=2.19.2
|
|
18
|
+
# via rich
|
|
19
|
+
pyyaml>=6.0.3
|
|
20
|
+
# via -r requirements.in
|
|
21
|
+
requests>=2.32.5
|
|
22
|
+
# via -r requirements.in
|
|
23
|
+
rich>=14.2.0
|
|
24
|
+
# via -r requirements.in
|
|
25
|
+
urllib3>=2.6.2
|
|
26
|
+
# via requests
|
|
@@ -1,25 +1,54 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ts-backend-check
|
|
3
|
-
Version: 1.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 1.2.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:
|
|
8
|
-
|
|
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.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Operating System :: OS Independent
|
|
21
|
+
Requires-Python: >=3.10
|
|
9
22
|
Description-Content-Type: text/markdown
|
|
10
23
|
License-File: LICENSE.txt
|
|
11
|
-
Requires-Dist:
|
|
24
|
+
Requires-Dist: certifi>=2025.11.12
|
|
25
|
+
Requires-Dist: charset-normalizer>=3.4.4
|
|
26
|
+
Requires-Dist: idna>=3.11
|
|
27
|
+
Requires-Dist: markdown-it-py>=4.0.0
|
|
28
|
+
Requires-Dist: mdurl>=0.1.2
|
|
29
|
+
Requires-Dist: pygments>=2.19.2
|
|
30
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
31
|
+
Requires-Dist: requests>=2.32.5
|
|
32
|
+
Requires-Dist: rich>=14.2.0
|
|
33
|
+
Requires-Dist: urllib3>=2.6.2
|
|
34
|
+
Dynamic: author
|
|
12
35
|
Dynamic: author-email
|
|
36
|
+
Dynamic: classifier
|
|
37
|
+
Dynamic: description
|
|
38
|
+
Dynamic: description-content-type
|
|
13
39
|
Dynamic: home-page
|
|
14
40
|
Dynamic: license-file
|
|
41
|
+
Dynamic: requires-dist
|
|
15
42
|
Dynamic: requires-python
|
|
43
|
+
Dynamic: summary
|
|
16
44
|
|
|
17
45
|
<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/
|
|
46
|
+
<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
47
|
</div>
|
|
20
48
|
|
|
21
49
|
[](http://ts-backend-check.readthedocs.io/en/latest/)
|
|
22
|
-
[](https://github.com/activist-org/ts-backend-check/actions/workflows/pr_ci.yaml)
|
|
51
|
+
[](https://github.com/activist-org/ts-backend-check/actions/workflows/python_package_ci.yaml)
|
|
23
52
|
[](https://github.com/activist-org/ts-backend-check/issues)
|
|
24
53
|
[](https://github.com/activist-org/ts-backend-check/blob/main/CONTRIBUTING.md)
|
|
25
54
|
[](https://pypi.org/project/ts-backend-check/)
|
|
@@ -38,10 +67,10 @@ Dynamic: requires-python
|
|
|
38
67
|
|
|
39
68
|
- [Usage](#usage-)
|
|
40
69
|
- [Contributing](#contributing-)
|
|
41
|
-
- [Environment setup](#environment-setup)
|
|
70
|
+
- [Environment setup](#environment-setup-)
|
|
42
71
|
- [Contributors](#contributors-)
|
|
43
72
|
|
|
44
|
-
<a id="usage"></a>
|
|
73
|
+
<a id="usage-"></a>
|
|
45
74
|
|
|
46
75
|
## Usage [`⇧`](#contents)
|
|
47
76
|
|
|
@@ -51,6 +80,11 @@ Dynamic: requires-python
|
|
|
51
80
|
pip install ts-backend-check
|
|
52
81
|
```
|
|
53
82
|
|
|
83
|
+
### Command Options
|
|
84
|
+
|
|
85
|
+
- `backend-model-file` (`bmf`): Path to the backend model file (e.g. Python class)
|
|
86
|
+
- `typescript-file` (`tsf`): Path to the TypeScript interface/type file
|
|
87
|
+
|
|
54
88
|
### Basic Usage
|
|
55
89
|
|
|
56
90
|
The CLI provides a simple interface to check TypeScript types against backend models:
|
|
@@ -60,31 +94,19 @@ The CLI provides a simple interface to check TypeScript types against backend mo
|
|
|
60
94
|
ts-backend-check --help
|
|
61
95
|
|
|
62
96
|
# Check a TypeScript type against a backend model:
|
|
63
|
-
ts-backend-check
|
|
97
|
+
ts-backend-check -bmf <backend-model-file> -tsf <typescript-file>
|
|
64
98
|
|
|
65
99
|
# Example command:
|
|
66
|
-
ts-backend-check
|
|
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
|
|
100
|
+
ts-backend-check -bmf src/models/user.py -tsf src/types/user.ts
|
|
79
101
|
```
|
|
80
102
|
|
|
81
|
-
<a id="contributing"></a>
|
|
103
|
+
<a id="contributing-"></a>
|
|
82
104
|
|
|
83
105
|
# Contributing [`⇧`](#contents)
|
|
84
106
|
|
|
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"
|
|
107
|
+
<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
108
|
|
|
87
|
-
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 :)
|
|
109
|
+
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 :) We'd suggest that you use the [Element](https://element.io/) client and [Element X](https://element.io/app) for a mobile app.
|
|
88
110
|
|
|
89
111
|
Please see the [contribution guidelines](CONTRIBUTING.md) if you are interested in contributing. Work that is in progress or could be implemented is tracked in the [issues](https://github.com/activist-org/ts-backend-check/issues) and [projects](https://github.com/activist-org/ts-backend-check/projects).
|
|
90
112
|
|
|
@@ -95,7 +117,7 @@ Also check the [`-next release-`](https://github.com/activist-org/ts-backend-che
|
|
|
95
117
|
|
|
96
118
|
We would be happy to discuss granting you further rights as a contributor after your first pull requests, with a maintainer role then being possible after continued interest in the project. activist seeks to be an inclusive, diverse and supportive organization. We'd love to have you on the team!
|
|
97
119
|
|
|
98
|
-
<a id="how-you-can-help"></a>
|
|
120
|
+
<a id="how-you-can-help-"></a>
|
|
99
121
|
|
|
100
122
|
## How you can help [`⇧`](#contents)
|
|
101
123
|
|
|
@@ -103,7 +125,7 @@ We would be happy to discuss granting you further rights as a contributor after
|
|
|
103
125
|
- Working with us on [new features](https://github.com/activist-org/ts-backend-check/issues?q=is%3Aissue+is%3Aopen+label%3Afeature) ✨
|
|
104
126
|
- [Documentation](https://github.com/activist-org/ts-backend-check/issues?q=is%3Aissue+is%3Aopen+label%3Adocumentation) for onboarding and project cohesion 📝
|
|
105
127
|
|
|
106
|
-
<a id="environment-setup"></a>
|
|
128
|
+
<a id="environment-setup-"></a>
|
|
107
129
|
|
|
108
130
|
# Environment setup [`⇧`](#contents)
|
|
109
131
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
ts_backend_check/__init__.py,sha256=HSiEqWvU3Q4a6D7tYsoq3-B-g1HdUOGngEeGV45j8ak,172
|
|
2
|
+
ts_backend_check/checker.py,sha256=ISPl_1H7dXb7DcOl6erdxEhx61xQBJxtHuYkUuEqrck,5792
|
|
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/check_blank.py,sha256=gg6v01UYNmzs8eLasxauCaBCfN9esUtiAwqhqNTzE3w,3377
|
|
8
|
+
ts_backend_check/cli/config.py,sha256=AgzjY3qbgW4ya5JJEdKaBQ9J8h0Ip-39xJNbAjFpA6s,4463
|
|
9
|
+
ts_backend_check/cli/main.py,sha256=6g9Rf_m5H2cWUoPYPU_HEmkoUl6SLLai1Ni2BpgDz6U,4222
|
|
10
|
+
ts_backend_check/cli/upgrade.py,sha256=P2LdIDCLdOFs-fOMuKLQoUMijQIyKEIA5PFZVsAUwDg,2952
|
|
11
|
+
ts_backend_check/cli/version.py,sha256=lsocMaxAfF-FHW9O-NGH1sAo6svjuLgdGFxaC-XVoZc,2845
|
|
12
|
+
ts_backend_check/parsers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
ts_backend_check/parsers/django_parser.py,sha256=U5OP6IyrSdwI9rDUGU4GbWTDfozwhretWg7lmF6QGB8,3242
|
|
14
|
+
ts_backend_check/parsers/typescript_parser.py,sha256=BqBGOjsf-Wlh6ceaK-G2vLm5AMiEiwX-XnJQrr8P-gQ,2885
|
|
15
|
+
ts_backend_check-1.2.0.data/data/requirements.txt,sha256=ixdwNPm86WRSX0oBBaNegCQL7HQP8evsCRAdEFHLxGk,540
|
|
16
|
+
ts_backend_check-1.2.0.dist-info/licenses/LICENSE.txt,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
17
|
+
ts_backend_check-1.2.0.dist-info/METADATA,sha256=FDqxUd_nslUpUNEtk5sAHxEkATcDVkUAJkBMVYkf0rA,10416
|
|
18
|
+
ts_backend_check-1.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
19
|
+
ts_backend_check-1.2.0.dist-info/entry_points.txt,sha256=QeY7RJu20otBnQlhMMZxPL8nB3mP3M3U3h3nOlSFWSU,68
|
|
20
|
+
ts_backend_check-1.2.0.dist-info/top_level.txt,sha256=VzfNWQ3fPNdl-OBdtUKsUWR8Asdd27E3OJNUg2oQU8Y,17
|
|
21
|
+
ts_backend_check-1.2.0.dist-info/RECORD,,
|
|
@@ -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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|