├── .gitignore
├── LICENSE
├── MANIFEST.in
├── README.md
├── frappe_types
├── __init__.py
├── commands
│ └── __init__.py
├── config
│ ├── __init__.py
│ ├── desktop.py
│ └── docs.py
├── frappe_types
│ ├── __init__.py
│ ├── doctype
│ │ ├── __init__.py
│ │ ├── app_type_generation_paths
│ │ │ ├── __init__.py
│ │ │ ├── app_type_generation_paths.json
│ │ │ └── app_type_generation_paths.py
│ │ └── type_generation_settings
│ │ │ ├── __init__.py
│ │ │ ├── test_type_generation_settings.py
│ │ │ ├── type_generation_settings.js
│ │ │ ├── type_generation_settings.json
│ │ │ └── type_generation_settings.py
│ ├── type_generator.py
│ └── utils.py
├── hooks.py
├── modules.txt
├── patches.txt
├── public
│ └── .gitkeep
└── templates
│ ├── __init__.py
│ └── pages
│ └── __init__.py
├── license.txt
├── requirements.txt
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.pyc
3 | *.egg-info
4 | *.swp
5 | tags
6 | frappe_types/docs/current
7 | node_modules/
8 | .vscode/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Nikhil Kothari
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.
22 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include MANIFEST.in
2 | include requirements.txt
3 | include *.json
4 | include *.md
5 | include *.py
6 | include *.txt
7 | recursive-include frappe_types *.css
8 | recursive-include frappe_types *.csv
9 | recursive-include frappe_types *.html
10 | recursive-include frappe_types *.ico
11 | recursive-include frappe_types *.js
12 | recursive-include frappe_types *.json
13 | recursive-include frappe_types *.md
14 | recursive-include frappe_types *.png
15 | recursive-include frappe_types *.py
16 | recursive-include frappe_types *.svg
17 | recursive-include frappe_types *.txt
18 | recursive-exclude frappe_types *.pyc
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Frappe Typescript Type Generator
2 |
3 | Typescript type definition generator for Frappe DocTypes.
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | ## Usage
14 |
15 | To use the app, install it on your local development bench:
16 |
17 | ```bash
18 | $ bench get-app https://github.com/The-Commit-Company/frappe-types
19 | $ bench --site mysite.test install-app frappe_types
20 | ```
21 |
22 | After installing the app, search for "Type Generation Settings" in Desk using the Awesomebar. You need to add the app name and path where you want to save your Typescript type definition files. frappe-types will only run on those app whose app name and path are added in these settings.
23 |
24 |
25 |
26 | That's it.
27 |
28 | Now whenever you create or update any DocType on your local machine, the app will generate `.ts` files under at the following path: `app/src/types//.ts`.
29 |
30 |
31 |
32 | ## Features
33 |
34 | 1. Supports most Frappe field types
35 | 2. Runs automatically whenever you save/update a DocType
36 | 3. Adds JSDoc comments for every field in the interface
37 | 4. Support CLI command to run type generation on existing DocTypes without having to update them.
38 |
39 |
40 |
41 | ## CLI Command
42 |
43 | You can also run the type generation command from the bench CLI. This will generate types for all DocTypes in the system.
44 | This CLI Command works for all frappe-bench apps, and can generate types of any DocType .
45 |
46 | 1. Generate types for DocType.
47 |
48 | ```bash
49 | $ bench --site generate-types-for-doctype --app --doctype [--generate_child_tables] [--custom_fields]
50 |
51 | # or just Answer the prompts
52 | $ bench --site generate-types-for-doctype
53 | ```
54 |
55 | 2. Generate types for Module.
56 |
57 | ```bash
58 | $ bench --site generate-types-for-module --app --module [--generate_child_tables]
59 |
60 | # or just Answer the prompts
61 | $ bench --site generate-types-for-module
62 | ```
63 |
64 | Note: No need to mention --site if current site is same site where module/doctype existed app installed in that site.
65 |
66 | 1. `--app` - the app name included in `Type Generation Settings` doctype and where you want to save type files.
67 | 2. `--doctype` - the doctype name for which you want to generate types.
68 | 3. `--module` - the module name for which you want to generate types.
69 | 4. `--generate_child_tables` - if you want to generate types for child tables of the doctype (default=False).
70 | 5. `--custom_fields` - if you want to generate types for custom fields of the doctype (Default=False).
71 |
72 |
73 |
74 | ## Example
75 |
76 | Let's say you create a DocType in a module called "Project Management" called "Projects" and Child Table called "Project User Table" with the following fields:
77 |
78 |
79 |
80 |
81 |
82 |
83 | The app will automatically create a file called `Projects.ts` and `ProjectUserTable.ts` at the path `/types/ProjectManagement` like this:
84 |
85 | (Notice that spaces in the Module and DocType names will be removed)
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | ## Where can you use this?
97 |
98 | If you are developing custom Frappe apps with a Frappe backend and a frontend single-page app using React/Vue/other frameworks, you can use this app to generate TypeScript definitions to be used in your frontend app.
99 |
100 |
101 |
102 | ## What features will we add next?
103 |
104 | 1. Looking at how to improve speed so that DocType saving does not take a lot of time.
105 |
106 |
107 |
108 | ## Maintainers
109 |
110 | | Maintainer | GitHub | Social |
111 | | -------------- | ----------------------------------------------- | ---------------------------------------------------------------- |
112 | | Nikhil Kothari | [nikkothari22](https://github.com/nikkothari22) | [@nik_kothari22](https://twitter.com/nik_kothari22) |
113 | | Sumit Jain | [sumitjain236](https://github.com/sumitjain236) | [@sumit_jain](https://www.linkedin.com/in/sumit-jain-66bb5719a/) |
114 |
115 |
116 |
117 | #### License
118 |
119 | MIT
120 |
--------------------------------------------------------------------------------
/frappe_types/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | __version__ = '0.0.1'
3 |
4 |
--------------------------------------------------------------------------------
/frappe_types/commands/__init__.py:
--------------------------------------------------------------------------------
1 | import click
2 | from frappe_types.frappe_types.type_generator import generate_types_for_doctype, generate_types_for_module
3 | from frappe.commands import pass_context
4 | import frappe
5 |
6 |
7 | @click.command("generate-types-for-doctype")
8 | @click.option("--app", prompt="App Name")
9 | @click.option("--doctype", prompt="Doctype Name")
10 | @click.option(
11 | "--generate_child_tables",
12 | default=False,
13 | is_flag=True,
14 | prompt="Do you want to generate types for child tables too?",
15 | help="It will generate Types for child tables includes in the doctype",
16 | )
17 | @click.option(
18 | "--custom_fields",
19 | default=False,
20 | is_flag=True,
21 | prompt="Do you want to generate types for custom fields too if exists?",
22 | help="It will generate Types for custom fields includes in the doctype",
23 | )
24 | @pass_context
25 | def generate_types_file_from_doctype(context, app, doctype, generate_child_tables, custom_fields):
26 | """Generate types file from doctype"""
27 | if not app:
28 | click.echo("Please provide an app with --app")
29 | return
30 | print(f"Generating types file for {doctype} in {app}")
31 |
32 | for site in context.sites:
33 | frappe.connect(site=site)
34 | try:
35 | generate_types_for_doctype(
36 | doctype, app, generate_child_tables, custom_fields)
37 | finally:
38 | frappe.destroy()
39 | if not context.sites:
40 | raise frappe.SiteNotSpecifiedError
41 |
42 |
43 | @click.command("generate-types-for-module")
44 | @click.option("--app", prompt="App Name")
45 | @click.option("--module", prompt="Module Name")
46 | @click.option('--generate_child_tables', default=False, is_flag=True, prompt='Do you want to generate types for child tables too?', help='It will generate Types for child tables includes in the doctype')
47 | @pass_context
48 | def generate_types_file_from_module(context, app, module, generate_child_tables):
49 | """Generate types file from module"""
50 | if not app:
51 | click.echo("Please provide an app with --app")
52 | return
53 | print(f"Generating types file for {module} in {app}")
54 |
55 | for site in context.sites:
56 | frappe.connect(site=site)
57 | try:
58 | generate_types_for_module(module, app, generate_child_tables)
59 | finally:
60 | frappe.destroy()
61 | if not context.sites:
62 | raise SiteNotSpecifiedError
63 |
64 |
65 | commands = [generate_types_file_from_doctype, generate_types_file_from_module]
66 |
--------------------------------------------------------------------------------
/frappe_types/config/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/The-Commit-Company/frappe-types/2ee42ac22417fb26b1d617378174978180d69402/frappe_types/config/__init__.py
--------------------------------------------------------------------------------
/frappe_types/config/desktop.py:
--------------------------------------------------------------------------------
1 | from frappe import _
2 |
3 | def get_data():
4 | return [
5 | {
6 | "module_name": "Frappe Types",
7 | "type": "module",
8 | "label": _("Frappe Types")
9 | }
10 | ]
11 |
--------------------------------------------------------------------------------
/frappe_types/config/docs.py:
--------------------------------------------------------------------------------
1 | """
2 | Configuration for docs
3 | """
4 |
5 | # source_link = "https://github.com/[org_name]/frappe_types"
6 | # headline = "App that does everything"
7 | # sub_heading = "Yes, you got that right the first time, everything"
8 |
9 | def get_context(context):
10 | context.brand_html = "Frappe Types"
11 |
--------------------------------------------------------------------------------
/frappe_types/frappe_types/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/The-Commit-Company/frappe-types/2ee42ac22417fb26b1d617378174978180d69402/frappe_types/frappe_types/__init__.py
--------------------------------------------------------------------------------
/frappe_types/frappe_types/doctype/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/The-Commit-Company/frappe-types/2ee42ac22417fb26b1d617378174978180d69402/frappe_types/frappe_types/doctype/__init__.py
--------------------------------------------------------------------------------
/frappe_types/frappe_types/doctype/app_type_generation_paths/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/The-Commit-Company/frappe-types/2ee42ac22417fb26b1d617378174978180d69402/frappe_types/frappe_types/doctype/app_type_generation_paths/__init__.py
--------------------------------------------------------------------------------
/frappe_types/frappe_types/doctype/app_type_generation_paths/app_type_generation_paths.json:
--------------------------------------------------------------------------------
1 | {
2 | "actions": [],
3 | "autoname": "autoincrement",
4 | "creation": "2022-09-15 12:43:46.781969",
5 | "doctype": "DocType",
6 | "editable_grid": 1,
7 | "engine": "InnoDB",
8 | "field_order": [
9 | "app_name",
10 | "app_path"
11 | ],
12 | "fields": [
13 | {
14 | "fieldname": "app_name",
15 | "fieldtype": "Data",
16 | "in_list_view": 1,
17 | "label": "App Name",
18 | "reqd": 1
19 | },
20 | {
21 | "fieldname": "app_path",
22 | "fieldtype": "Data",
23 | "in_list_view": 1,
24 | "label": "App Path",
25 | "reqd": 1
26 | }
27 | ],
28 | "index_web_pages_for_search": 1,
29 | "istable": 1,
30 | "links": [],
31 | "modified": "2023-06-19 11:58:35.450734",
32 | "modified_by": "Administrator",
33 | "module": "Frappe Types",
34 | "name": "App Type Generation Paths",
35 | "naming_rule": "Autoincrement",
36 | "owner": "Administrator",
37 | "permissions": [],
38 | "sort_field": "modified",
39 | "sort_order": "DESC",
40 | "states": []
41 | }
--------------------------------------------------------------------------------
/frappe_types/frappe_types/doctype/app_type_generation_paths/app_type_generation_paths.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2022, Nikhil Kothari and contributors
2 | # For license information, please see license.txt
3 |
4 | # import frappe
5 | from frappe.model.document import Document
6 |
7 | class AppTypeGenerationPaths(Document):
8 | pass
9 |
--------------------------------------------------------------------------------
/frappe_types/frappe_types/doctype/type_generation_settings/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/The-Commit-Company/frappe-types/2ee42ac22417fb26b1d617378174978180d69402/frappe_types/frappe_types/doctype/type_generation_settings/__init__.py
--------------------------------------------------------------------------------
/frappe_types/frappe_types/doctype/type_generation_settings/test_type_generation_settings.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2023, Nikhil Kothari and Contributors
2 | # See license.txt
3 |
4 | # import frappe
5 | from frappe.tests.utils import FrappeTestCase
6 |
7 |
8 | class TestTypeGenerationSettings(FrappeTestCase):
9 | pass
10 |
--------------------------------------------------------------------------------
/frappe_types/frappe_types/doctype/type_generation_settings/type_generation_settings.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2022, Nikhil Kothari and contributors
2 | // For license information, please see license.txt
3 |
4 | frappe.ui.form.on('Type Generation Settings', {
5 | // refresh: function(frm) {
6 |
7 | // }
8 | });
9 |
--------------------------------------------------------------------------------
/frappe_types/frappe_types/doctype/type_generation_settings/type_generation_settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "actions": [],
3 | "allow_rename": 1,
4 | "creation": "2022-09-14 19:44:07.791947",
5 | "doctype": "DocType",
6 | "editable_grid": 1,
7 | "engine": "InnoDB",
8 | "field_order": [
9 | "type_settings"
10 | ],
11 | "fields": [
12 | {
13 | "fieldname": "type_settings",
14 | "fieldtype": "Table",
15 | "label": "Type Settings",
16 | "options": "App Type Generation Paths"
17 | }
18 | ],
19 | "index_web_pages_for_search": 1,
20 | "issingle": 1,
21 | "links": [],
22 | "modified": "2023-06-09 13:55:18.286294",
23 | "modified_by": "Administrator",
24 | "module": "Frappe Types",
25 | "name": "Type Generation Settings",
26 | "owner": "Administrator",
27 | "permissions": [
28 | {
29 | "create": 1,
30 | "delete": 1,
31 | "email": 1,
32 | "print": 1,
33 | "read": 1,
34 | "role": "System Manager",
35 | "share": 1,
36 | "write": 1
37 | }
38 | ],
39 | "sort_field": "modified",
40 | "sort_order": "DESC",
41 | "states": []
42 | }
--------------------------------------------------------------------------------
/frappe_types/frappe_types/doctype/type_generation_settings/type_generation_settings.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2022, Nikhil Kothari and contributors
2 | # For license information, please see license.txt
3 |
4 | # import frappe
5 | from frappe.model.document import Document
6 |
7 | class TypeGenerationSettings(Document):
8 | pass
9 |
--------------------------------------------------------------------------------
/frappe_types/frappe_types/type_generator.py:
--------------------------------------------------------------------------------
1 | import frappe
2 | from pathlib import Path
3 | from .utils import create_file
4 | import subprocess
5 |
6 |
7 | def create_type_definition_file(doc, method=None):
8 |
9 | # Check if type generation is paused
10 | common_site_config = frappe.get_conf()
11 |
12 | frappe_types_pause_generation = common_site_config.get(
13 | "frappe_types_pause_generation", 0)
14 |
15 | if frappe_types_pause_generation:
16 | print("Frappe Types is paused")
17 | return
18 |
19 | if frappe.flags.in_patch or frappe.flags.in_migrate or frappe.flags.in_install or frappe.flags.in_setup_wizard:
20 | print("Skipping type generation in patch, migrate, install or setup wizard")
21 | return
22 |
23 | doctype = doc
24 |
25 | if is_developer_mode_enabled() and is_valid_doctype(doctype):
26 | print("Generating type definition file for " + doctype.name)
27 | module_name = doctype.module
28 | app_name = frappe.db.get_value('Module Def', module_name, 'app_name')
29 |
30 | if app_name == "frappe" or app_name == "erpnext":
31 | print("Ignoring core app DocTypes")
32 | return
33 |
34 | app_path: Path = Path("../apps") / app_name
35 | if not app_path.exists():
36 | print("App path does not exist - ignoring type generation")
37 | return
38 |
39 | # Fetch Type Generation Settings Document
40 | type_generation_settings = frappe.get_doc(
41 | 'Type Generation Settings'
42 | ).as_dict().type_settings
43 |
44 | # Checking if app is existed in type generation settings
45 | for type_setting in type_generation_settings:
46 | if app_name == type_setting.app_name:
47 | # Types folder is created in the app
48 | type_path: Path = app_path / type_setting.app_path / "types"
49 |
50 | if not type_path.exists():
51 | type_path.mkdir()
52 |
53 | module_path: Path = type_path / module_name.replace(" ", "")
54 | if not module_path.exists():
55 | module_path.mkdir()
56 |
57 | generate_type_definition_file(
58 | doctype, module_path, generate_child_tables=False)
59 |
60 | def generate_type_definition_file(doctype, module_path, generate_child_tables=False):
61 |
62 | doctype_name = doctype.name.replace(" ", "")
63 | type_file_path = module_path / (doctype_name + ".ts")
64 | type_file_content = generate_type_definition_content(
65 | doctype, module_path, generate_child_tables)
66 |
67 | create_file(type_file_path, type_file_content)
68 |
69 |
70 | def generate_type_definition_content(doctype, module_path, generate_child_tables):
71 | import_statement = ""
72 |
73 | content = "export interface " + doctype.name.replace(" ", "") + "{\n"
74 |
75 | # Boilerplate types for all documents
76 | name_field_type = "string"
77 | if doctype.naming_rule == "Autoincrement":
78 | name_field_type = "number"
79 | content += f"\tname: {name_field_type}\n\tcreation: string\n\tmodified: string\n\towner: string\n\tmodified_by: string\n\tdocstatus: 0 | 1 | 2\n\tparent?: string\n\tparentfield?: string\n\tparenttype?: string\n\tidx?: number\n"
80 |
81 | for field in doctype.fields:
82 | if field.fieldtype in ["Section Break", "Column Break", "HTML", "Button", "Fold", "Heading", "Tab Break", "Break"]:
83 | continue
84 | content += get_field_comment(field)
85 |
86 | file_defination, statement = get_field_type_definition(
87 | field, doctype, module_path, generate_child_tables)
88 |
89 | if statement and import_statement.find(statement) == -1:
90 | import_statement += statement
91 |
92 | content += "\t" + file_defination + "\n"
93 |
94 | content += "}"
95 |
96 | return import_statement + "\n" + content
97 |
98 | def get_field_comment(field):
99 | desc = field.description
100 | if field.fieldtype in ["Link", "Table", "Table MultiSelect"]:
101 | desc = field.options + \
102 | (" - " + field.description if field.description else "")
103 | return "\t/**\t" + (field.label if field.label else '') + " : " + field.fieldtype + ((" - " + desc) if desc else "") + "\t*/\n"
104 |
105 |
106 | def get_field_type_definition(field, doctype, module_path, generate_child_tables):
107 | field_type,import_statement = get_field_type(field, doctype, module_path, generate_child_tables)
108 | return field.fieldname + get_required(field) + ": " + field_type , import_statement
109 |
110 |
111 | def get_field_type(field, doctype, module_path, generate_child_tables):
112 |
113 | basic_fieldtypes = {
114 | "Data": "string",
115 | "Small Text": "string",
116 | "Text Editor": "string",
117 | "Text": "string",
118 | "Code": "string",
119 | "Link": "string",
120 | "Dynamic Link": "string",
121 | "Read Only": "string",
122 | "Password": "string",
123 | "Text Editor": "string",
124 | "Check": "0 | 1",
125 | "Int": "number",
126 | "Float": "number",
127 | "Currency": "number",
128 | "Percent": "number",
129 | "Attach Image": "string",
130 | "Attach": "string",
131 | "HTML Editor": "string",
132 | "Image": "string",
133 | "Duration": "string",
134 | "Small Text": "string",
135 | "Date": "string",
136 | "Datetime": "string",
137 | "Time": "string",
138 | "Phone": "string",
139 | "Color": "string",
140 | "Long Text": "string",
141 | "Markdown Editor": "string",
142 | }
143 |
144 | if field.fieldtype in ["Table", "Table MultiSelect"]:
145 |
146 | return get_imports_for_table_fields(field, doctype, module_path, generate_child_tables)
147 |
148 | if field.fieldtype == "Select":
149 | if (field.options):
150 | options = field.options.split("\n")
151 | t = ""
152 | for option in options:
153 | t += "\"" + option + "\" | "
154 | if t.endswith(" | "):
155 | t = t[:-3]
156 | return t, None
157 | else:
158 | return 'string',None
159 |
160 | if field.fieldtype in basic_fieldtypes:
161 | return basic_fieldtypes[field.fieldtype], None
162 | else:
163 | return "any", None
164 |
165 |
166 | def get_imports_for_table_fields(field, doctype, module_path, generate_child_tables):
167 | if field.fieldtype == "Table" or field.fieldtype == "Table MultiSelect":
168 | doctype_module_name = doctype.module
169 | table_doc = frappe.get_doc('DocType', field.options)
170 | table_module_name = table_doc.module
171 | should_import = False
172 | import_statement = ""
173 |
174 | # check if table doctype type file is already generated and exists
175 |
176 | if doctype_module_name == table_module_name:
177 |
178 | table_file_path: Path = module_path / \
179 | (table_doc.name.replace(" ", "") + ".ts")
180 | if not table_file_path.exists():
181 | if generate_child_tables:
182 | generate_type_definition_file(table_doc, module_path)
183 |
184 | should_import = True
185 |
186 | else:
187 | should_import = True
188 |
189 | import_statement = ("import { " + field.options.replace(" ", "") + " } from './" +
190 | field.options.replace(" ", "") + "'") + "\n" if should_import else ''
191 |
192 | else:
193 |
194 | table_module_path: Path = module_path.parent / \
195 | table_module_name.replace(" ", "")
196 | if not table_module_path.exists():
197 | table_module_path.mkdir()
198 |
199 | table_file_path: Path = table_module_path / \
200 | (table_doc.name.replace(" ", "") + ".ts")
201 |
202 | if not table_file_path.exists():
203 | if generate_child_tables:
204 | generate_type_definition_file(table_doc, table_module_path)
205 |
206 | should_import = True
207 |
208 | else:
209 | should_import = True
210 |
211 | import_statement = ("import { " + field.options.replace(" ", "") + " } from '../" +
212 | table_module_name.replace(" ", "") + "/" + field.options.replace(" ", "") + "'") + "\n" if should_import else ''
213 |
214 | return field.options.replace(" ", "") + "[]" if should_import else 'any', import_statement
215 | return "",None
216 |
217 |
218 | def get_required(field):
219 | if field.reqd:
220 | return ""
221 | else:
222 | return "?"
223 |
224 |
225 | def is_valid_doctype(doctype):
226 | if (doctype.custom):
227 | print("Custom DocType - ignoring type generation")
228 | return False
229 |
230 | if (doctype.is_virtual):
231 | print("Virtual DocType - ignoring type generation")
232 | return False
233 |
234 | return True
235 |
236 |
237 | def is_developer_mode_enabled():
238 | if not frappe.conf.get("developer_mode"):
239 | print("Developer mode not enabled - ignoring type generation")
240 | return False
241 | return True
242 |
243 |
244 | def before_migrate():
245 | # print("Before migrate")
246 | subprocess.run(
247 | ["bench", "config", "set-common-config", "-c", "frappe_types_pause_generation", "1"])
248 |
249 |
250 | def after_migrate():
251 | # print("After migrate")
252 | subprocess.run(["bench", "config", "set-common-config",
253 | "-c", "frappe_types_pause_generation", "0"])
254 |
255 |
256 | @frappe.whitelist()
257 | def generate_types_for_doctype(doctype, app_name, generate_child_tables=False, custom_fields=False):
258 |
259 | try:
260 | # custom_fields True means that the generate .ts file for custom fields with original fields
261 | doc = frappe.get_meta(doctype) if custom_fields else frappe.get_doc(
262 | 'DocType', doctype)
263 |
264 | # Check if type generation is paused
265 | common_site_config = frappe.get_conf()
266 |
267 | frappe_types_pause_generation = common_site_config.get(
268 | "frappe_types_pause_generation", 0)
269 |
270 | if frappe_types_pause_generation:
271 | print("Frappe Types is paused")
272 | return
273 |
274 | if is_developer_mode_enabled() and is_valid_doctype(doc):
275 | print("Generating type definition file for " + doc.name)
276 | module_name = doc.module
277 |
278 | app_path: Path = Path("../apps") / app_name
279 | if not app_path.exists():
280 | print("App path does not exist - ignoring type generation")
281 | return
282 |
283 | # Fetch Type Generation Settings Document
284 | type_generation_settings = frappe.get_doc(
285 | 'Type Generation Settings'
286 | ).as_dict().type_settings
287 |
288 | # Checking if app is existed in type generation settings
289 | for type_setting in type_generation_settings:
290 | if app_name == type_setting.app_name:
291 | # Types folder is created in the app
292 | # path: Path = type_setting.app_path / "types"
293 | type_path: Path = app_path / type_setting.app_path / "types"
294 | if not type_path.exists():
295 | type_path.mkdir()
296 |
297 | module_path: Path = type_path / \
298 | module_name.replace(" ", "")
299 | if not module_path.exists():
300 | module_path.mkdir()
301 |
302 | generate_type_definition_file(
303 | doc, module_path, generate_child_tables)
304 |
305 | except Exception as e:
306 | err_msg = f": {str(e)}\n{frappe.get_traceback()}"
307 | print(
308 | f"An error occurred while generating type for {doctype} {err_msg}")
309 |
310 |
311 | @frappe.whitelist()
312 | def generate_types_for_module(module, app_name, generate_child_tables=False):
313 | try:
314 | child_tables = [doctype['name'] for doctype in frappe.get_list(
315 | 'DocType', filters={'module': module, 'istable': 1})]
316 | if len(child_tables) > 0:
317 | for child_table in child_tables:
318 | generate_types_for_doctype(
319 | child_table, app_name, generate_child_tables)
320 |
321 | doctypes = [doctype['name'] for doctype in frappe.get_list(
322 | 'DocType', filters={'module': module, 'istable': 0})]
323 |
324 | if len(doctypes) > 0:
325 | for doctype in doctypes:
326 | generate_types_for_doctype(
327 | doctype, app_name, generate_child_tables)
328 | except Exception as e:
329 | err_msg = f": {str(e)}\n{frappe.get_traceback()}"
330 | print(
331 | f"An error occurred while generating type for {module} {err_msg}")
332 |
--------------------------------------------------------------------------------
/frappe_types/frappe_types/utils.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 |
4 | def create_file(path: Path, content: str = None):
5 | # Create the file if not exists
6 | if not path.exists():
7 | path.touch()
8 |
9 | # Write the contents (if any)
10 | if content:
11 | with path.open("w") as f:
12 | f.write(content)
13 |
--------------------------------------------------------------------------------
/frappe_types/hooks.py:
--------------------------------------------------------------------------------
1 | from . import __version__ as app_version
2 |
3 | app_name = "frappe_types"
4 | app_title = "Frappe Types"
5 | app_publisher = "Nikhil Kothari"
6 | app_description = "Typescript type definition generator for Frappe DocTypes"
7 | app_email = "nik.kothari22@live.com"
8 | app_license = "MIT"
9 |
10 | # Includes in
11 | # ------------------
12 |
13 | # include js, css files in header of desk.html
14 | # app_include_css = "/assets/frappe_types/css/frappe_types.css"
15 | # app_include_js = "/assets/frappe_types/js/frappe_types.js"
16 |
17 | # include js, css files in header of web template
18 | # web_include_css = "/assets/frappe_types/css/frappe_types.css"
19 | # web_include_js = "/assets/frappe_types/js/frappe_types.js"
20 |
21 | # include custom scss in every website theme (without file extension ".scss")
22 | # website_theme_scss = "frappe_types/public/scss/website"
23 |
24 | # include js, css files in header of web form
25 | # webform_include_js = {"doctype": "public/js/doctype.js"}
26 | # webform_include_css = {"doctype": "public/css/doctype.css"}
27 |
28 | # include js in page
29 | # page_js = {"page" : "public/js/file.js"}
30 |
31 | # include js in doctype views
32 | # doctype_js = {"doctype" : "public/js/doctype.js"}
33 | # doctype_list_js = {"doctype" : "public/js/doctype_list.js"}
34 | # doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"}
35 | # doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"}
36 |
37 | # Home Pages
38 | # ----------
39 |
40 | # application home page (will override Website Settings)
41 | # home_page = "login"
42 |
43 | # website user home page (by Role)
44 | # role_home_page = {
45 | # "Role": "home_page"
46 | # }
47 |
48 | # Generators
49 | # ----------
50 |
51 | # automatically create page for each record of this doctype
52 | # website_generators = ["Web Page"]
53 |
54 | # Jinja
55 | # ----------
56 |
57 | # add methods and filters to jinja environment
58 | # jinja = {
59 | # "methods": "frappe_types.utils.jinja_methods",
60 | # "filters": "frappe_types.utils.jinja_filters"
61 | # }
62 |
63 | # Installation
64 | # ------------
65 |
66 | before_install = "frappe_types.frappe_types.type_generator.before_migrate"
67 | after_install = "frappe_types.frappe_types.type_generator.after_migrate"
68 |
69 | # Migration
70 |
71 | before_migrate = "frappe_types.frappe_types.type_generator.before_migrate"
72 | after_migrate = "frappe_types.frappe_types.type_generator.after_migrate"
73 |
74 | # Uninstallation
75 | # ------------
76 |
77 | # before_uninstall = "frappe_types.uninstall.before_uninstall"
78 | # after_uninstall = "frappe_types.uninstall.after_uninstall"
79 |
80 | # Desk Notifications
81 | # ------------------
82 | # See frappe.core.notifications.get_notification_config
83 |
84 | # notification_config = "frappe_types.notifications.get_notification_config"
85 |
86 | # Permissions
87 | # -----------
88 | # Permissions evaluated in scripted ways
89 |
90 | # permission_query_conditions = {
91 | # "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions",
92 | # }
93 | #
94 | # has_permission = {
95 | # "Event": "frappe.desk.doctype.event.event.has_permission",
96 | # }
97 |
98 | # DocType Class
99 | # ---------------
100 | # Override standard doctype classes
101 |
102 | # override_doctype_class = {
103 | # "ToDo": "custom_app.overrides.CustomToDo"
104 | # }
105 |
106 | # Document Events
107 | # ---------------
108 | # Hook on document methods and events
109 |
110 |
111 | doc_events = {
112 | "DocType": {
113 | "on_update": "frappe_types.frappe_types.type_generator.create_type_definition_file"
114 | }
115 | }
116 |
117 | # Scheduled Tasks
118 | # ---------------
119 |
120 | # scheduler_events = {
121 | # "all": [
122 | # "frappe_types.tasks.all"
123 | # ],
124 | # "daily": [
125 | # "frappe_types.tasks.daily"
126 | # ],
127 | # "hourly": [
128 | # "frappe_types.tasks.hourly"
129 | # ],
130 | # "weekly": [
131 | # "frappe_types.tasks.weekly"
132 | # ],
133 | # "monthly": [
134 | # "frappe_types.tasks.monthly"
135 | # ],
136 | # }
137 |
138 | # Testing
139 | # -------
140 |
141 | # before_tests = "frappe_types.install.before_tests"
142 |
143 | # Overriding Methods
144 | # ------------------------------
145 | #
146 | # override_whitelisted_methods = {
147 | # "frappe.desk.doctype.event.event.get_events": "frappe_types.event.get_events"
148 | # }
149 | #
150 | # each overriding function accepts a `data` argument;
151 | # generated from the base implementation of the doctype dashboard,
152 | # along with any modifications made in other Frappe apps
153 | # override_doctype_dashboards = {
154 | # "Task": "frappe_types.task.get_dashboard_data"
155 | # }
156 |
157 | # exempt linked doctypes from being automatically cancelled
158 | #
159 | # auto_cancel_exempted_doctypes = ["Auto Repeat"]
160 |
161 |
162 | # User Data Protection
163 | # --------------------
164 |
165 | # user_data_fields = [
166 | # {
167 | # "doctype": "{doctype_1}",
168 | # "filter_by": "{filter_by}",
169 | # "redact_fields": ["{field_1}", "{field_2}"],
170 | # "partial": 1,
171 | # },
172 | # {
173 | # "doctype": "{doctype_2}",
174 | # "filter_by": "{filter_by}",
175 | # "partial": 1,
176 | # },
177 | # {
178 | # "doctype": "{doctype_3}",
179 | # "strict": False,
180 | # },
181 | # {
182 | # "doctype": "{doctype_4}"
183 | # }
184 | # ]
185 |
186 | # Authentication and authorization
187 | # --------------------------------
188 |
189 | # auth_hooks = [
190 | # "frappe_types.auth.validate"
191 | # ]
192 |
--------------------------------------------------------------------------------
/frappe_types/modules.txt:
--------------------------------------------------------------------------------
1 | Frappe Types
--------------------------------------------------------------------------------
/frappe_types/patches.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/The-Commit-Company/frappe-types/2ee42ac22417fb26b1d617378174978180d69402/frappe_types/patches.txt
--------------------------------------------------------------------------------
/frappe_types/public/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/The-Commit-Company/frappe-types/2ee42ac22417fb26b1d617378174978180d69402/frappe_types/public/.gitkeep
--------------------------------------------------------------------------------
/frappe_types/templates/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/The-Commit-Company/frappe-types/2ee42ac22417fb26b1d617378174978180d69402/frappe_types/templates/__init__.py
--------------------------------------------------------------------------------
/frappe_types/templates/pages/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/The-Commit-Company/frappe-types/2ee42ac22417fb26b1d617378174978180d69402/frappe_types/templates/pages/__init__.py
--------------------------------------------------------------------------------
/license.txt:
--------------------------------------------------------------------------------
1 | License: MIT
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | # frappe -- https://github.com/frappe/frappe is installed via 'bench init'
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 | with open("requirements.txt") as f:
4 | install_requires = f.read().strip().split("\n")
5 |
6 | # get version from __version__ variable in frappe_types/__init__.py
7 | from frappe_types import __version__ as version
8 |
9 | setup(
10 | name="frappe_types",
11 | version=version,
12 | description="Typescript type definition generator for Frappe DocTypes",
13 | author="Nikhil Kothari",
14 | author_email="nik.kothari22@live.com",
15 | packages=find_packages(),
16 | zip_safe=False,
17 | include_package_data=True,
18 | install_requires=install_requires
19 | )
20 |
--------------------------------------------------------------------------------