Skip to content

Commit 27e0b50

Browse files
committed
update: add generateSchemaMixin + node22 build fix
1 parent faf537c commit 27e0b50

File tree

8 files changed

+837
-439
lines changed

8 files changed

+837
-439
lines changed

dist/js/generateSchemaMixin.d.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Generates mixins for multiple schemas
4+
* @param outputPaths - Object mapping schema IDs to output file paths
5+
* @param skipFields - Array of field names to skip during generation
6+
* @returns - Object with success and error counts
7+
*/
8+
declare function generateShemaMixin(outputPaths: Record<string, string>, skipFields?: string[]): {
9+
successCount: number;
10+
errorCount: number;
11+
};
12+
/**
13+
* @example
14+
* ```ts
15+
* import generateShemaMixin from "@mat3ra/code/dist/js/generateSchemaMixin";
16+
*
17+
* const result = generateShemaMixin(OUTPUT_PATHS, SKIP_FIELDS);
18+
*
19+
* if (result.errorCount > 0) {
20+
* process.exit(1);
21+
* }
22+
* ```
23+
*/
24+
export default generateShemaMixin;

dist/js/generateSchemaMixin.js

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
#!/usr/bin/env node
2+
"use strict";
3+
/* eslint-disable no-restricted-syntax */
4+
var __importDefault = (this && this.__importDefault) || function (mod) {
5+
return (mod && mod.__esModule) ? mod : { "default": mod };
6+
};
7+
Object.defineProperty(exports, "__esModule", { value: true });
8+
/**
9+
* Script to generate mixin properties from JSON schema
10+
*
11+
* This script generates mixin functions for property/holder, property/meta_holder,
12+
* and property/proto_holder schemas automatically.
13+
*
14+
* Usage:
15+
* node scripts/generate-mixin-properties.js
16+
*/
17+
const JSONSchemasInterface_1 = __importDefault(require("@mat3ra/esse/dist/js/esse/JSONSchemasInterface"));
18+
const schemas_json_1 = __importDefault(require("@mat3ra/esse/dist/js/schemas.json"));
19+
const child_process_1 = require("child_process");
20+
const fs_1 = __importDefault(require("fs"));
21+
/**
22+
* Determines if a property should use requiredProp() or prop()
23+
* @param propertyName - Name of the property
24+
* @param requiredProperties - Array of required property names
25+
* @returns - True if property is required
26+
*/
27+
function isRequiredProperty(propertyName, requiredProperties) {
28+
return requiredProperties.includes(propertyName);
29+
}
30+
/**
31+
* Generates TypeScript type annotation for a property
32+
* @param propertyName - The property name
33+
* @param schemaName - Name of the schema (for type reference)
34+
* @returns - TypeScript type annotation
35+
*/
36+
function generateTypeAnnotation(propertyName, schemaName) {
37+
return `${schemaName}["${propertyName}"]`;
38+
}
39+
/**
40+
* Extracts properties from a schema, handling allOf if present
41+
* @param schema - The JSON schema
42+
* @returns - Object with properties and required fields
43+
*/
44+
function extractSchemaProperties(schema) {
45+
let properties = {};
46+
let required = [];
47+
// Handle allOf by merging properties from all schemas
48+
if (schema.allOf && Array.isArray(schema.allOf)) {
49+
for (const subSchema of schema.allOf) {
50+
const extracted = extractSchemaProperties(subSchema);
51+
properties = { ...properties, ...extracted.properties };
52+
required = [...required, ...extracted.required];
53+
}
54+
}
55+
// Add properties from current schema
56+
if (schema.properties) {
57+
properties = { ...properties, ...schema.properties };
58+
}
59+
if (schema.required) {
60+
required = [...required, ...schema.required];
61+
}
62+
return { properties, required };
63+
}
64+
/**
65+
* Generates the complete mixin function
66+
* @param schema - The JSON schema
67+
* @param schemaName - Name of the schema
68+
* @param mixinTypeName - Name of the mixin type
69+
* @param entityTypeName - Name of the entity type
70+
* @param skipFields - Array of field names to skip
71+
* @returns - Generated TypeScript code
72+
*/
73+
function generateMixinFunction(schema, schemaName, mixinTypeName, entityTypeName, skipFields = []) {
74+
// Convert mixin type name to camelCase for function name
75+
const functionName = mixinTypeName.charAt(0).toLowerCase() + mixinTypeName.slice(1);
76+
// Extract properties, handling allOf if present
77+
const { properties, required } = extractSchemaProperties(schema);
78+
if (Object.keys(properties).length === 0) {
79+
throw new Error("No properties found in schema");
80+
}
81+
// Filter out skip fields
82+
const propertyEntries = Object.entries(properties).filter(([propertyName]) => !skipFields.includes(propertyName));
83+
let code = `import type { InMemoryEntity } from "@mat3ra/code/dist/js/entity";\n`;
84+
code += `import type { ${schemaName} } from "@mat3ra/esse/dist/js/types";\n\n`;
85+
// Generate the mixin type using Omit utility
86+
const skipFieldNames = skipFields.map((field) => `"${field}"`).join(" | ");
87+
code += `export type ${mixinTypeName} = Omit<${schemaName}, ${skipFieldNames}>;\n\n`;
88+
// Generate the entity type
89+
code += `export type ${entityTypeName} = InMemoryEntity & ${mixinTypeName};\n\n`;
90+
code += `export function ${functionName}(item: InMemoryEntity) {\n`;
91+
code += ` // @ts-expect-error\n`;
92+
code += ` const properties: InMemoryEntity & ${mixinTypeName} = {\n`;
93+
for (let i = 0; i < propertyEntries.length; i++) {
94+
const [propertyName] = propertyEntries[i];
95+
const isRequired = isRequiredProperty(propertyName, required);
96+
const methodName = isRequired ? "requiredProp" : "prop";
97+
const typeAnnotation = generateTypeAnnotation(propertyName, schemaName);
98+
code += `get ${propertyName}() {\n`;
99+
code += `return this.${methodName}<${typeAnnotation}>("${propertyName}");\n`;
100+
code += `}`;
101+
// Add comma for all properties except the last one
102+
if (i < propertyEntries.length - 1) {
103+
code += `,\n`;
104+
}
105+
else {
106+
code += `,\n`;
107+
}
108+
}
109+
code += ` };\n\n`;
110+
code += ` Object.defineProperties(item, Object.getOwnPropertyDescriptors(properties));\n`;
111+
code += `}\n`;
112+
return code;
113+
}
114+
/**
115+
* Generates mixin function for a given schema ID
116+
* @param schemaId - The schema ID (e.g., "property/holder")
117+
* @param outputPath - The output file path
118+
* @param skipFields - Array of field names to skip
119+
* @returns - Generated TypeScript code
120+
*/
121+
function generateMixinFromSchemaId(schemaId, outputPath, skipFields = []) {
122+
var _a, _b;
123+
// Get the resolved schema by ID
124+
const schema = JSONSchemasInterface_1.default.getSchemaById(schemaId);
125+
if (!schema) {
126+
throw new Error(`Schema not found with ID: ${schemaId}`);
127+
}
128+
// Extract schema name from title for import
129+
let schemaName;
130+
if (schema.title) {
131+
// Convert title to proper schema name
132+
schemaName = schema.title
133+
.split(/\s+/)
134+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
135+
.join("");
136+
}
137+
else {
138+
// Convert schema ID to proper schema name
139+
schemaName =
140+
schemaId
141+
.split(/[/-]/)
142+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
143+
.join("") + "Schema";
144+
}
145+
// Extract type names from output file path
146+
const fileName = (_b = (_a = outputPath.split("/").pop()) === null || _a === void 0 ? void 0 : _a.replace(".ts", "")) !== null && _b !== void 0 ? _b : "";
147+
if (!fileName) {
148+
throw new Error(`Invalid output path: ${outputPath}`);
149+
}
150+
const mixinTypeName = fileName;
151+
const entityTypeName = fileName.replace("SchemaMixin", "InMemoryEntity");
152+
// Generate the complete mixin function
153+
return generateMixinFunction(schema, schemaName, mixinTypeName, entityTypeName, skipFields);
154+
}
155+
/**
156+
* Runs ESLint autofix on generated files
157+
* @param filePaths - Array of file paths to fix
158+
*/
159+
function runESLintAutofix(filePaths) {
160+
if (filePaths.length === 0)
161+
return;
162+
try {
163+
console.log("Running ESLint autofix on generated files...");
164+
const filesToFix = filePaths.join(" ");
165+
(0, child_process_1.execSync)(`npx eslint --fix ${filesToFix}`, { stdio: "inherit" });
166+
console.log("✓ ESLint autofix completed successfully");
167+
}
168+
catch (error) {
169+
console.warn("⚠ ESLint autofix failed:", error instanceof Error ? error.message : String(error));
170+
// Don't fail the entire process if ESLint autofix fails
171+
}
172+
}
173+
/**
174+
* Generates mixins for multiple schemas
175+
* @param outputPaths - Object mapping schema IDs to output file paths
176+
* @param skipFields - Array of field names to skip during generation
177+
* @returns - Object with success and error counts
178+
*/
179+
function generateShemaMixin(outputPaths, skipFields = []) {
180+
// Setup schemas
181+
JSONSchemasInterface_1.default.setSchemas(schemas_json_1.default);
182+
console.log("Generating mixin properties for all schemas...");
183+
const schemaIds = Object.keys(outputPaths);
184+
let successCount = 0;
185+
let errorCount = 0;
186+
const generatedFiles = [];
187+
for (const schemaId of schemaIds) {
188+
try {
189+
console.log(`\nProcessing schema: ${schemaId}`);
190+
const outputPath = outputPaths[schemaId];
191+
if (!outputPath) {
192+
throw new Error(`No output path defined for schema: ${schemaId}`);
193+
}
194+
const generatedCode = generateMixinFromSchemaId(schemaId, outputPath, skipFields);
195+
// Ensure the directory exists
196+
const dir = outputPath.substring(0, outputPath.lastIndexOf("/"));
197+
if (!fs_1.default.existsSync(dir)) {
198+
fs_1.default.mkdirSync(dir, { recursive: true });
199+
}
200+
fs_1.default.writeFileSync(outputPath, generatedCode);
201+
console.log(`✓ Generated mixin written to: ${outputPath}`);
202+
generatedFiles.push(outputPath);
203+
successCount += 1;
204+
}
205+
catch (error) {
206+
console.error(`✗ Error processing schema ${schemaId}: ${error instanceof Error ? error.message : String(error)}`);
207+
errorCount += 1;
208+
}
209+
}
210+
// Run ESLint autofix on generated files
211+
if (generatedFiles.length > 0) {
212+
runESLintAutofix(generatedFiles);
213+
}
214+
console.log(`\n=== Summary ===`);
215+
console.log(`Successfully generated: ${successCount} mixins`);
216+
if (errorCount > 0) {
217+
console.log(`Errors: ${errorCount} schemas failed`);
218+
}
219+
else {
220+
console.log("All mixins generated successfully!");
221+
}
222+
return { successCount, errorCount };
223+
}
224+
/**
225+
* @example
226+
* ```ts
227+
* import generateShemaMixin from "@mat3ra/code/dist/js/generateSchemaMixin";
228+
*
229+
* const result = generateShemaMixin(OUTPUT_PATHS, SKIP_FIELDS);
230+
*
231+
* if (result.errorCount > 0) {
232+
* process.exit(1);
233+
* }
234+
* ```
235+
*/
236+
exports.default = generateShemaMixin;

0 commit comments

Comments
 (0)