Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dev: Fix json2ts codegen #4590

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 110 additions & 3 deletions dev/code-generation/gen_ts_types.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import re
from pathlib import Path

from jinja2 import Template
Expand Down Expand Up @@ -64,7 +65,112 @@ def write_template(text: str) -> None:
# Pydantic To Typescript Generator


def generate_typescript_types() -> None:
def generate_typescript_types() -> None: # noqa: C901
def contains_number(s: str) -> bool:
return bool(re.search(r"\d", s))

def remove_numbers(s: str) -> str:
return re.sub(r"\d", "", s)

def extract_type_name(line: str) -> str:
# Looking for "export type EnumName = enumVal1 | enumVal2 | ..."
if not (line.startswith("export type") and "=" in line):
return ""

return line.split(" ")[2]

def extract_property_type_name(line: str) -> str:
# Looking for " fieldName: FieldType;" or " fieldName: FieldType & string;"
if not (line.startswith(" ") and ":" in line):
return ""

return line.split(":")[1].strip().split(";")[0]

def extract_interface_name(line: str) -> str:
# Looking for "export interface InterfaceName {"
if not (line.startswith("export interface") and "{" in line):
return ""

return line.split(" ")[2]

def is_comment_line(line: str) -> bool:
s = line.strip()
return s.startswith("/*") or s.startswith("*")

def clean_output_file(file: Path) -> None:
"""
json2ts generates duplicate types off of our enums and appends a number to the end of the type name.
Our Python code (hopefully) doesn't have any duplicate enum names, or types with numbers in them,
so we can safely remove the numbers.

To do this, we read the output line-by-line and replace any type names that contain numbers with
the same type name, but without the numbers.

Note: the issue arrises from the JSON package json2ts, not the Python package pydantic2ts,
otherwise we could just fix pydantic2ts.
"""

# First pass: build a map of type names to their numberless counterparts and lines to skip
replacement_map = {}
lines_to_skip = set()
wait_for_semicolon = False
wait_for_close_bracket = False
skip_comments = False
with open(file) as f:
for i, line in enumerate(f.readlines()):
if wait_for_semicolon:
if ";" in line:
wait_for_semicolon = False
lines_to_skip.add(i)
continue
if wait_for_close_bracket:
if "}" in line:
wait_for_close_bracket = False
lines_to_skip.add(i)
continue

if type_name := extract_type_name(line):
if not contains_number(type_name):
continue

replacement_map[type_name] = remove_numbers(type_name)
if ";" not in line:
wait_for_semicolon = True
lines_to_skip.add(i)

elif type_name := extract_interface_name(line):
if not contains_number(type_name):
continue

replacement_map[type_name] = remove_numbers(type_name)
if "}" not in line:
wait_for_close_bracket = True
lines_to_skip.add(i)

elif skip_comments and is_comment_line(line):
lines_to_skip.add(i)

# we've passed the opening comments and empty line at the header
elif not skip_comments and not line.strip():
skip_comments = True

# Second pass: rewrite or remove lines as needed.
# We have to do two passes here because definitions don't always appear in the same order as their usage.
lines = []
with open(file) as f:
for i, line in enumerate(f.readlines()):
if i in lines_to_skip:
continue

if type_name := extract_property_type_name(line):
if type_name in replacement_map:
line = line.replace(type_name, replacement_map[type_name])

lines.append(line)

with open(file, "w") as f:
f.writelines(lines)

def path_to_module(path: Path):
str_path: str = str(path)

Expand Down Expand Up @@ -98,9 +204,10 @@ def path_to_module(path: Path):
try:
path_as_module = path_to_module(module)
generate_typescript_defs(path_as_module, str(out_path), exclude=("MealieModel")) # type: ignore
except Exception as e:
clean_output_file(out_path)
except Exception:
failed_modules.append(module)
log.error(f"Module Error: {e}")
log.exception(f"Module Error: {module}")

log.debug("\n📁 Skipped Directories:")
for skipped_dir in skipped_dirs:
Expand Down
1 change: 1 addition & 0 deletions frontend/lib/api/types/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ export interface RecipeTool {
name: string;
slug: string;
onHand?: boolean;
[k: string]: unknown;
}
export interface CustomPageImport {
name: string;
Expand Down
14 changes: 7 additions & 7 deletions frontend/lib/api/types/cookbook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,19 @@ export interface CreateCookBook {
slug?: string | null;
position?: number;
public?: boolean;
queryFilterString: string;
queryFilterString?: string;
}
export interface ReadCookBook {
name: string;
description?: string;
slug?: string | null;
position?: number;
public?: boolean;
queryFilterString: string;
queryFilterString?: string;
groupId: string;
householdId: string;
id: string;
queryFilter: QueryFilterJSON;
queryFilter?: QueryFilterJSON;
}
export interface QueryFilterJSON {
parts?: QueryFilterJSONPart[];
Expand All @@ -47,11 +47,11 @@ export interface RecipeCookBook {
slug?: string | null;
position?: number;
public?: boolean;
queryFilterString: string;
queryFilterString?: string;
groupId: string;
householdId: string;
id: string;
queryFilter: QueryFilterJSON;
queryFilter?: QueryFilterJSON;
recipes: RecipeSummary[];
}
export interface RecipeSummary {
Expand Down Expand Up @@ -106,7 +106,7 @@ export interface SaveCookBook {
slug?: string | null;
position?: number;
public?: boolean;
queryFilterString: string;
queryFilterString?: string;
groupId: string;
householdId: string;
}
Expand All @@ -116,7 +116,7 @@ export interface UpdateCookBook {
slug?: string | null;
position?: number;
public?: boolean;
queryFilterString: string;
queryFilterString?: string;
groupId: string;
householdId: string;
id: string;
Expand Down
21 changes: 8 additions & 13 deletions frontend/lib/api/types/household.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ export interface CreateHouseholdPreferences {
}
export interface CreateInviteToken {
uses: number;
groupId?: string | null;
householdId?: string | null;
}
export interface CreateWebhook {
enabled?: boolean;
name?: string;
url?: string;
webhookType?: WebhookType & string;
webhookType?: WebhookType;
scheduledTime: string;
}
export interface EmailInitationResponse {
Expand All @@ -46,10 +48,6 @@ export interface GroupEventNotifierCreate {
name: string;
appriseUrl?: string | null;
}
/**
* These events are in-sync with the EventTypes found in the EventBusService.
* If you modify this, make sure to update the EventBusService as well.
*/
export interface GroupEventNotifierOptions {
testMessage?: boolean;
webhookTask?: boolean;
Expand Down Expand Up @@ -204,7 +202,7 @@ export interface ReadWebhook {
enabled?: boolean;
name?: string;
url?: string;
webhookType?: WebhookType & string;
webhookType?: WebhookType;
scheduledTime: string;
groupId: string;
householdId: string;
Expand Down Expand Up @@ -263,7 +261,7 @@ export interface SaveWebhook {
enabled?: boolean;
name?: string;
url?: string;
webhookType?: WebhookType & string;
webhookType?: WebhookType;
scheduledTime: string;
groupId: string;
householdId: string;
Expand Down Expand Up @@ -486,9 +484,6 @@ export interface ShoppingListItemUpdate {
} | null;
recipeReferences?: (ShoppingListItemRecipeRefCreate | ShoppingListItemRecipeRefUpdate)[];
}
/**
* Only used for bulk update operations where the shopping list item id isn't already supplied
*/
export interface ShoppingListItemUpdateBulk {
quantity?: number;
unit?: IngredientUnit | CreateIngredientUnit | null;
Expand All @@ -509,9 +504,6 @@ export interface ShoppingListItemUpdateBulk {
recipeReferences?: (ShoppingListItemRecipeRefCreate | ShoppingListItemRecipeRefUpdate)[];
id: string;
}
/**
* Container for bulk shopping list item changes
*/
export interface ShoppingListItemsCollectionOut {
createdItems?: ShoppingListItemOut[];
updatedItems?: ShoppingListItemOut[];
Expand Down Expand Up @@ -565,6 +557,8 @@ export interface RecipeSummary {
name?: string | null;
slug?: string;
image?: unknown;
recipeServings?: number;
recipeYieldQuantity?: number;
recipeYield?: string | null;
totalTime?: string | null;
prepTime?: string | null;
Expand Down Expand Up @@ -599,6 +593,7 @@ export interface RecipeTool {
name: string;
slug: string;
onHand?: boolean;
[k: string]: unknown;
}
export interface ShoppingListRemoveRecipeParams {
recipeDecrementQuantity?: number;
Expand Down
41 changes: 18 additions & 23 deletions frontend/lib/api/types/meal-plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,16 @@ export type LogicalOperator = "AND" | "OR";
export type RelationalKeyword = "IS" | "IS NOT" | "IN" | "NOT IN" | "CONTAINS ALL" | "LIKE" | "NOT LIKE";
export type RelationalOperator = "=" | "<>" | ">" | "<" | ">=" | "<=";

export interface Category {
id: string;
name: string;
slug: string;
}
export interface CreatePlanEntry {
date: string;
entryType?: PlanEntryType & string;
entryType?: PlanEntryType;
title?: string;
text?: string;
recipeId?: string | null;
}
export interface CreateRandomEntry {
date: string;
entryType?: PlanEntryType & string;
entryType?: PlanEntryType;
}
export interface ListItem {
title?: string | null;
Expand All @@ -35,18 +30,18 @@ export interface ListItem {
checked?: boolean;
}
export interface PlanRulesCreate {
day?: PlanRulesDay & string;
entryType?: PlanRulesType & string;
queryFilterString: string;
day?: PlanRulesDay;
entryType?: PlanRulesType;
queryFilterString?: string;
}
export interface PlanRulesOut {
day?: PlanRulesDay & string;
entryType?: PlanRulesType & string;
queryFilterString: string;
day?: PlanRulesDay;
entryType?: PlanRulesType;
queryFilterString?: string;
groupId: string;
householdId: string;
id: string;
queryFilter: QueryFilterJSON;
queryFilter?: QueryFilterJSON;
}
export interface QueryFilterJSON {
parts?: QueryFilterJSONPart[];
Expand All @@ -61,21 +56,21 @@ export interface QueryFilterJSONPart {
[k: string]: unknown;
}
export interface PlanRulesSave {
day?: PlanRulesDay & string;
entryType?: PlanRulesType & string;
queryFilterString: string;
day?: PlanRulesDay;
entryType?: PlanRulesType;
queryFilterString?: string;
groupId: string;
householdId: string;
}
export interface ReadPlanEntry {
date: string;
entryType?: PlanEntryType & string;
entryType?: PlanEntryType;
title?: string;
text?: string;
recipeId?: string | null;
id: number;
groupId: string;
userId?: string | null;
userId: string;
householdId: string;
recipe?: RecipeSummary | null;
}
Expand Down Expand Up @@ -127,12 +122,12 @@ export interface RecipeTool {
}
export interface SavePlanEntry {
date: string;
entryType?: PlanEntryType & string;
entryType?: PlanEntryType;
title?: string;
text?: string;
recipeId?: string | null;
groupId: string;
userId?: string | null;
userId: string;
}
export interface ShoppingListIn {
name: string;
Expand All @@ -147,11 +142,11 @@ export interface ShoppingListOut {
}
export interface UpdatePlanEntry {
date: string;
entryType?: PlanEntryType & string;
entryType?: PlanEntryType;
title?: string;
text?: string;
recipeId?: string | null;
id: number;
groupId: string;
userId?: string | null;
userId: string;
}
Loading
Loading