chore: import upstream snapshot with attribution
docs / deploy (push) Has been cancelled
docs / changes (push) Has been cancelled
docs / check-and-build (push) Has been cancelled
build container image / cpu (push) Has been cancelled
build container image / cuda (push) Has been cancelled
build container image / rocm (push) Has been cancelled
frontend checks / frontend-checks (push) Has been cancelled
frontend tests / frontend-tests (push) Has been cancelled
lfs checks / lfs-check (push) Has been cancelled
python checks / python-checks (push) Has been cancelled
python tests / py3.12: macos-default (push) Has been cancelled
python tests / py3.11: windows-cpu (push) Has been cancelled
python tests / py3.12: windows-cpu (push) Has been cancelled
python tests / py3.11: linux-cpu (push) Has been cancelled
typegen checks / typegen-checks (push) Has been cancelled
uv lock checks / uv-lock-checks (push) Has been cancelled
openapi checks / openapi-checks (push) Has been cancelled
python tests / py3.11: macos-default (push) Has been cancelled
python tests / py3.12: linux-cpu (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:22:06 +08:00
commit cddb07a176
3370 changed files with 685519 additions and 0 deletions
@@ -0,0 +1,89 @@
# Cleans translations by removing unused keys
# Usage: python clean_translations.py
# Note: Must be run from invokeai/frontend/web/scripts directory
#
# After running the script, open `en.json` and check for empty objects (`{}`) and remove them manually.
# Also, the script does not handle keys with underscores. They need to be checked manually.
import json
import os
import re
from typing import TypeAlias, Union
from tqdm import tqdm
RecursiveDict: TypeAlias = dict[str, Union["RecursiveDict", str]]
class TranslationCleaner:
file_cache: dict[str, str] = {}
def _get_keys(self, obj: RecursiveDict, current_path: str = "", keys: list[str] | None = None):
if keys is None:
keys = []
for key in obj:
new_path = f"{current_path}.{key}" if current_path else key
next_ = obj[key]
if isinstance(next_, dict):
self._get_keys(next_, new_path, keys)
elif "_" in key:
# This typically means its a pluralized key
continue
else:
keys.append(new_path)
return keys
def _search_codebase(self, key: str):
for root, _dirs, files in os.walk("../src"):
for file in files:
if file.endswith(".ts") or file.endswith(".tsx"):
full_path = os.path.join(root, file)
if full_path in self.file_cache:
content = self.file_cache[full_path]
else:
with open(full_path, "r") as f:
content = f.read()
self.file_cache[full_path] = content
# match the whole key, surrounding by quotes
if re.search(r"['\"`]" + re.escape(key) + r"['\"`]", self.file_cache[full_path]):
return True
# math the stem of the key, with quotes at the end
if re.search(re.escape(key.split(".")[-1]) + r"['\"`]", self.file_cache[full_path]):
return True
return False
def _remove_key(self, obj: RecursiveDict, key: str):
path = key.split(".")
last_key = path[-1]
for k in path[:-1]:
obj = obj[k]
del obj[last_key]
def clean(self, obj: RecursiveDict) -> RecursiveDict:
keys = self._get_keys(obj)
pbar = tqdm(keys, desc="Checking keys")
for key in pbar:
if not self._search_codebase(key):
self._remove_key(obj, key)
return obj
def main():
try:
with open("../public/locales/en.json", "r") as f:
data = json.load(f)
except FileNotFoundError as e:
raise FileNotFoundError(
"Unable to find en.json file - must be run from invokeai/frontend/web/scripts directory"
) from e
cleaner = TranslationCleaner()
cleaned_data = cleaner.clean(data)
with open("../public/locales/en.json", "w") as f:
json.dump(cleaned_data, f, indent=4)
if __name__ == "__main__":
main()
@@ -0,0 +1,4 @@
{
"type": "module",
"packageManager": "pnpm@10.12.4"
}
+104
View File
@@ -0,0 +1,104 @@
/* eslint-disable no-console */
import fs from 'node:fs';
import openapiTS, { astToString } from 'openapi-typescript';
import ts from 'typescript';
const OPENAPI_URL = 'http://127.0.0.1:9090/openapi.json';
const OUTPUT_FILE = 'src/services/api/schema.ts';
async function generateTypes(schema) {
process.stdout.write(`Generating types ${OUTPUT_FILE}...`);
// Use https://ts-ast-viewer.com to figure out how to create these AST nodes - define a type and use the bottom-left pane's output
// `Blob` type
const BLOB = ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Blob'));
// `null` type
const NULL = ts.factory.createLiteralTypeNode(ts.factory.createNull());
// `Record<string, unknown>` type
const RECORD_STRING_UNKNOWN = ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Record'), [
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword),
]);
const types = await openapiTS(schema, {
exportType: true,
transform: (schemaObject) => {
if ('format' in schemaObject && schemaObject.format === 'binary') {
return schemaObject.nullable ? ts.factory.createUnionTypeNode([BLOB, NULL]) : BLOB;
}
if (schemaObject.title === 'MetadataField') {
// This is `Record<string, never>` by default, but it actually accepts any a dict of any valid JSON value.
return RECORD_STRING_UNKNOWN;
}
},
defaultNonNullable: false,
});
let output = astToString(types);
// Post-process: openapi-typescript sometimes computes enum types from `const`
// usage in discriminated unions rather than from the enum definition itself,
// dropping values that only appear in some union members. Patch the generated
// output to match the OpenAPI schema's actual enum definitions.
//
// The `schema` parameter is a parsed JSON object when piped from stdin, or
// a URL/Buffer when passed as an argument. We only patch in the JSON case.
if (schema && typeof schema === 'object' && !Buffer.isBuffer(schema)) {
const schemas = schema.components?.schemas;
if (schemas) {
// Collect all string enum types and their expected values from the OpenAPI schema
for (const [typeName, typeDef] of Object.entries(schemas)) {
if (typeDef && typeDef.type === 'string' && Array.isArray(typeDef.enum)) {
const expectedUnion = typeDef.enum.map((v) => `"${v}"`).join(' | ');
// Match the type definition line. These appear as:
// `TypeName: "val1" | "val2" | ...;`
// Use word boundary to avoid matching types that contain this
// type name as a substring (e.g. ModelType vs BaseModelType).
const regex = new RegExp(`(\\b${typeName}: )"[^;]+(;)`);
const match = output.match(regex);
if (match) {
output = output.replace(regex, `$1${expectedUnion}$2`);
}
}
}
}
}
fs.writeFileSync(OUTPUT_FILE, output);
process.stdout.write(`\nOK!\r\n`);
}
function main() {
const encoding = 'utf-8';
if (process.stdin.isTTY) {
// Handle generating types with an arg (e.g. URL or path to file)
if (process.argv.length > 3) {
console.error('Usage: typegen.js <openapi.json>');
process.exit(1);
}
if (process.argv[2]) {
const schema = new Buffer.from(process.argv[2], encoding);
generateTypes(schema);
} else {
generateTypes(OPENAPI_URL);
}
} else {
// Handle generating types from stdin
let schema = '';
process.stdin.setEncoding(encoding);
process.stdin.on('readable', function () {
const chunk = process.stdin.read();
if (chunk !== null) {
schema += chunk;
}
});
process.stdin.on('end', function () {
generateTypes(JSON.parse(schema));
});
}
}
main();