├── .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 | Screenshot 2023-01-12 at 2 30 31 PM 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 | image 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 | image 90 | 91 |
92 | image 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 | --------------------------------------------------------------------------------