Skip to content

Commit 27c2ed2

Browse files
committed
feat: add component generation and injection capabilities for CRUD and global/login components
1 parent 0d10ea9 commit 27c2ed2

File tree

12 files changed

+817
-72
lines changed

12 files changed

+817
-72
lines changed

adminforth/commands/createCustomComponent/configUpdater.js

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,3 +205,269 @@ export async function updateResourceConfig(resourceId, columnName, fieldType, co
205205
throw new Error(`Failed to update resource file ${path.basename(filePath)}: ${error.message}`);
206206
}
207207
}
208+
209+
210+
export async function injectLoginComponent(indexFilePath, componentPath) {
211+
console.log(chalk.dim(`Reading file: ${indexFilePath}`));
212+
const content = await fs.readFile(indexFilePath, 'utf-8');
213+
const ast = recast.parse(content, {
214+
parser: typescriptParser,
215+
});
216+
217+
let updated = false;
218+
219+
recast.visit(ast, {
220+
visitNewExpression(path) {
221+
if (
222+
n.Identifier.check(path.node.callee) &&
223+
path.node.callee.name === 'AdminForth' &&
224+
path.node.arguments.length > 0 &&
225+
n.ObjectExpression.check(path.node.arguments[0])
226+
) {
227+
const configObject = path.node.arguments[0];
228+
229+
let customizationProp = configObject.properties.find(
230+
p => n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === 'customization'
231+
);
232+
233+
if (!customizationProp) {
234+
// Добавляем customization: {}
235+
const customizationObj = b.objectExpression([]);
236+
customizationProp = b.objectProperty(b.identifier('customization'), customizationObj);
237+
configObject.properties.push(customizationProp);
238+
console.log(chalk.dim(`Added missing 'customization' property.`));
239+
}
240+
241+
const customizationValue = customizationProp.value;
242+
if (!n.ObjectExpression.check(customizationValue)) return false;
243+
244+
let loginPageInjections = customizationValue.properties.find(
245+
p => n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === 'loginPageInjections'
246+
);
247+
248+
if (!loginPageInjections) {
249+
const injectionsObj = b.objectExpression([]);
250+
loginPageInjections = b.objectProperty(b.identifier('loginPageInjections'), injectionsObj);
251+
customizationValue.properties.push(loginPageInjections);
252+
console.log(chalk.dim(`Added missing 'loginPageInjections'.`));
253+
}
254+
255+
const injectionsValue = loginPageInjections.value;
256+
if (!n.ObjectExpression.check(injectionsValue)) return false;
257+
258+
let underInputsProp = injectionsValue.properties.find(
259+
p => n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === 'underInputs'
260+
);
261+
262+
if (underInputsProp) {
263+
underInputsProp.value = b.stringLiteral(componentPath);
264+
console.log(chalk.dim(`Updated 'underInputs' to ${componentPath}`));
265+
} else {
266+
injectionsValue.properties.push(
267+
b.objectProperty(b.identifier('underInputs'), b.stringLiteral(componentPath))
268+
);
269+
console.log(chalk.dim(`Added 'underInputs': ${componentPath}`));
270+
}
271+
272+
updated = true;
273+
this.abort();
274+
}
275+
return false;
276+
}
277+
});
278+
279+
if (!updated) {
280+
throw new Error(`Could not find AdminForth configuration in file: ${indexFilePath}`);
281+
}
282+
283+
const outputCode = recast.print(ast).code;
284+
await fs.writeFile(indexFilePath, outputCode, 'utf-8');
285+
console.log(chalk.green(`✅ Successfully updated login injection in: ${indexFilePath}`));
286+
}
287+
288+
289+
export async function injectGlobalComponent(indexFilePath, injectionType, componentPath) {
290+
console.log(chalk.dim(`Reading file: ${indexFilePath}`));
291+
const content = await fs.readFile(indexFilePath, 'utf-8');
292+
const ast = recast.parse(content, {
293+
parser: typescriptParser,
294+
});
295+
296+
let updated = false;
297+
298+
recast.visit(ast, {
299+
visitNewExpression(path) {
300+
if (
301+
n.Identifier.check(path.node.callee) &&
302+
path.node.callee.name === 'AdminForth' &&
303+
path.node.arguments.length > 0 &&
304+
n.ObjectExpression.check(path.node.arguments[0])
305+
) {
306+
const configObject = path.node.arguments[0];
307+
308+
let customizationProp = configObject.properties.find(
309+
p => n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === 'customization'
310+
);
311+
312+
if (!customizationProp) {
313+
const customizationObj = b.objectExpression([]);
314+
customizationProp = b.objectProperty(b.identifier('customization'), customizationObj);
315+
configObject.properties.push(customizationProp);
316+
console.log(chalk.dim(`Added missing 'customization' property.`));
317+
}
318+
319+
const customizationValue = customizationProp.value;
320+
if (!n.ObjectExpression.check(customizationValue)) return false;
321+
322+
let globalInjections = customizationValue.properties.find(
323+
p => n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === 'globalInjections'
324+
);
325+
326+
if (!globalInjections) {
327+
const injectionsObj = b.objectExpression([]);
328+
globalInjections = b.objectProperty(b.identifier('globalInjections'), injectionsObj);
329+
customizationValue.properties.push(globalInjections);
330+
console.log(chalk.dim(`Added missing 'globalInjections'.`));
331+
}
332+
333+
const injectionsValue = globalInjections.value;
334+
if (!n.ObjectExpression.check(injectionsValue)) return false;
335+
336+
let injectionProp = injectionsValue.properties.find(
337+
p => n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === injectionType
338+
);
339+
340+
if (injectionProp) {
341+
const injectionArray = injectionProp.value.elements;
342+
injectionArray.push(b.stringLiteral(componentPath));
343+
console.log(chalk.dim(`Added '${componentPath}' to '${injectionType}'`));
344+
} else {
345+
injectionsValue.properties.push(
346+
b.objectProperty(b.identifier(injectionType), b.arrayExpression([b.stringLiteral(componentPath)]))
347+
);
348+
console.log(chalk.dim(`Added '${injectionType}': [${componentPath}]`));
349+
}
350+
351+
updated = true;
352+
this.abort();
353+
}
354+
return false;
355+
}
356+
});
357+
358+
if (!updated) {
359+
throw new Error(`Could not find AdminForth configuration in file: ${indexFilePath}`);
360+
}
361+
362+
const outputCode = recast.print(ast).code;
363+
await fs.writeFile(indexFilePath, outputCode, 'utf-8');
364+
console.log(chalk.green(`✅ Successfully updated global injection '${injectionType}' in: ${indexFilePath}`));
365+
}
366+
367+
export async function updateCrudInjectionConfig(resourceId, crudType, injectionPosition, componentPathForConfig, isThin) {
368+
const filePath = await findResourceFilePath(resourceId);
369+
console.log(chalk.dim(`Attempting to update resource CRUD injection: ${filePath}`));
370+
371+
let content;
372+
try {
373+
content = await fs.readFile(filePath, 'utf-8');
374+
} catch (error) {
375+
console.error(chalk.red(`❌ Error reading resource file: ${filePath}`));
376+
throw new Error(`Could not read resource file ${filePath}.`);
377+
}
378+
379+
try {
380+
const ast = recast.parse(content, {
381+
parser: typescriptParser
382+
});
383+
384+
let updateApplied = false;
385+
386+
recast.visit(ast, {
387+
visitExportDefaultDeclaration(path) {
388+
const declaration = path.node.declaration;
389+
let objectExpressionNode = null;
390+
391+
if (n.TSAsExpression.check(declaration) && n.ObjectExpression.check(declaration.expression)) {
392+
objectExpressionNode = declaration.expression;
393+
} else if (n.ObjectExpression.check(declaration)) {
394+
objectExpressionNode = declaration;
395+
}
396+
397+
if (!objectExpressionNode) {
398+
console.warn(chalk.yellow(`Warning: Default export in ${filePath} is not an ObjectExpression. Skipping update.`));
399+
return false;
400+
}
401+
402+
const getOrCreateObjectProp = (obj, propName) => {
403+
let prop = obj.properties.find(p => n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === propName);
404+
if (!prop) {
405+
const newObject = b.objectExpression([]);
406+
prop = b.objectProperty(b.identifier(propName), newObject);
407+
obj.properties.push(prop);
408+
}
409+
return prop.value;
410+
};
411+
412+
const options = getOrCreateObjectProp(objectExpressionNode, 'options');
413+
if (!n.ObjectExpression.check(options)) return false;
414+
415+
const pageInjections = getOrCreateObjectProp(options, 'pageInjections');
416+
if (!n.ObjectExpression.check(pageInjections)) return false;
417+
418+
let crudProp = pageInjections.properties.find(p =>
419+
n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === crudType
420+
);
421+
422+
if (!crudProp) {
423+
crudProp = b.objectProperty(
424+
b.identifier(crudType),
425+
b.objectExpression([])
426+
);
427+
pageInjections.properties.push(crudProp);
428+
}
429+
430+
const crudValue = crudProp.value;
431+
if (!n.ObjectExpression.check(crudValue)) return false;
432+
433+
let injectionProp = crudValue.properties.find(p =>
434+
n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === injectionPosition
435+
);
436+
437+
const newInjectionObject = b.objectExpression([
438+
b.objectProperty(b.identifier('file'), b.stringLiteral(componentPathForConfig)),
439+
b.objectProperty(
440+
b.identifier('meta'),
441+
b.objectExpression([
442+
b.objectProperty(b.identifier('thinEnoughToShrinkTable'), b.booleanLiteral(!!isThin)),
443+
])
444+
),
445+
]);
446+
447+
if (injectionProp) {
448+
injectionProp.value = newInjectionObject;
449+
console.log(chalk.dim(`Updated '${injectionPosition}' injection for '${crudType}'.`));
450+
} else {
451+
crudValue.properties.push(b.objectProperty(b.identifier(injectionPosition), newInjectionObject));
452+
console.log(chalk.dim(`Added '${injectionPosition}' injection for '${crudType}'.`));
453+
}
454+
455+
updateApplied = true;
456+
this.abort();
457+
return false;
458+
}
459+
});
460+
461+
if (!updateApplied) {
462+
throw new Error(`Could not inject CRUD component in resource ${resourceId}.`);
463+
}
464+
465+
const outputCode = recast.print(ast).code;
466+
await fs.writeFile(filePath, outputCode, 'utf-8');
467+
console.log(chalk.dim(`✅ Successfully updated CRUD injection in resource file: ${filePath}`));
468+
469+
} catch (error) {
470+
console.error(chalk.red(`❌ Error processing resource file: ${filePath}`));
471+
throw new Error(`Failed to inject CRUD component in ${path.basename(filePath)}: ${error.message}`);
472+
}
473+
}

0 commit comments

Comments
 (0)