Skip to content
Merged

Next #205

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
27c2ed2
feat: add component generation and injection capabilities for CRUD an…
NoOne7135 Apr 28, 2025
37504b7
feat: enhance component injection logic and add label sanitization fo…
NoOne7135 Apr 29, 2025
8978d82
refactor: clean up console logs and remove commented code in componen…
NoOne7135 Apr 29, 2025
9a7bac9
docs: support output size
ivictbor Apr 29, 2025
36d0940
add output size to dev demo
ivictbor Apr 29, 2025
62b8f79
fix: upd tailwind for latest 3 version
ivictbor Apr 29, 2025
df9527c
fix(afcl): make showValues and showProgress of progressbar optional
ivictbor Apr 29, 2025
43ecca7
docs: simplify deployment posts (no buildx separate HCL file)
ivictbor Apr 30, 2025
a36a930
docs: minor improvements
ivictbor Apr 30, 2025
8695bb8
fix: update flowbite
ivictbor Apr 30, 2025
f998a0d
Merge pull request #204 from devforth/create-cli
ivictbor Apr 30, 2025
92d9093
fix(dataapi): allow to perform searching with IN on empty arrays (lik…
NoOne7135 Apr 30, 2025
6f7bfb7
Merge branch 'next' of https://github.com/devforth/adminforth into next
NoOne7135 Apr 30, 2025
a20eecb
Merge branch 'next' of github.com:devforth/adminforth into next
ivictbor Apr 30, 2025
8208272
dev-demo, change countToGenerate
ivictbor Apr 30, 2025
a1047c1
docs: improve documentation for ai-blog setup
NoOne7135 Apr 30, 2025
38a4a22
Merge branch 'next' of https://github.com/devforth/adminforth into next
NoOne7135 Apr 30, 2025
2ec3bf4
docs: update ai-blog setup instructions and improve Terraform configu…
NoOne7135 Apr 30, 2025
3a01a51
docs: update ai-blog setup instructions
NoOne7135 Apr 30, 2025
c17aa12
fix: add storage adapter interface, add comments to existing adapters
ivictbor May 1, 2025
104ccca
Merge branch 'next' of github.com:devforth/adminforth into next
ivictbor May 1, 2025
9c5f591
improve comments in adapter
ivictbor May 1, 2025
2233e95
fix: improve adapter for storage to cover various types of download URLs
ivictbor May 1, 2025
22afc27
docs: explicitly metion -f compose.yml in deploy guids
ivictbor May 1, 2025
4a91c0e
fix: update adapter for return structured upload URL and extra parame…
NoOne7135 May 1, 2025
7fb5238
docs: imporove storage adapter interface comments
ivictbor May 2, 2025
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
279 changes: 279 additions & 0 deletions adminforth/commands/createCustomComponent/configUpdater.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,282 @@ export async function updateResourceConfig(resourceId, columnName, fieldType, co
throw new Error(`Failed to update resource file ${path.basename(filePath)}: ${error.message}`);
}
}


export async function injectLoginComponent(indexFilePath, componentPath) {
console.log(chalk.dim(`Reading file: ${indexFilePath}`));
const content = await fs.readFile(indexFilePath, 'utf-8');
const ast = recast.parse(content, {
parser: typescriptParser,
});

let updated = false;

recast.visit(ast, {
visitNewExpression(path) {
if (
n.Identifier.check(path.node.callee) &&
path.node.callee.name === 'AdminForth' &&
path.node.arguments.length > 0 &&
n.ObjectExpression.check(path.node.arguments[0])
) {
const configObject = path.node.arguments[0];

let customizationProp = configObject.properties.find(
p => n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === 'customization'
);

if (!customizationProp) {
const customizationObj = b.objectExpression([]);
customizationProp = b.objectProperty(b.identifier('customization'), customizationObj);
configObject.properties.push(customizationProp);
console.log(chalk.dim(`Added missing 'customization' property.`));
}

const customizationValue = customizationProp.value;
if (!n.ObjectExpression.check(customizationValue)) return false;

let loginPageInjections = customizationValue.properties.find(
p => n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === 'loginPageInjections'
);

if (!loginPageInjections) {
const injectionsObj = b.objectExpression([]);
loginPageInjections = b.objectProperty(b.identifier('loginPageInjections'), injectionsObj);
customizationValue.properties.push(loginPageInjections);
console.log(chalk.dim(`Added missing 'loginPageInjections'.`));
}

const injectionsValue = loginPageInjections.value;
if (!n.ObjectExpression.check(injectionsValue)) return false;

let underInputsProp = injectionsValue.properties.find(
p => n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === 'underInputs'
);

if (underInputsProp) {
underInputsProp.value = b.stringLiteral(componentPath);
console.log(chalk.dim(`Updated 'underInputs' to ${componentPath}`));
} else {
injectionsValue.properties.push(
b.objectProperty(b.identifier('underInputs'), b.stringLiteral(componentPath))
);
console.log(chalk.dim(`Added 'underInputs': ${componentPath}`));
}

updated = true;
this.abort();
}
return false;
}
});

if (!updated) {
throw new Error(`Could not find AdminForth configuration in file: ${indexFilePath}`);
}

const outputCode = recast.print(ast).code;
await fs.writeFile(indexFilePath, outputCode, 'utf-8');
console.log(chalk.green(`✅ Successfully updated login injection in: ${indexFilePath}`));
}


export async function injectGlobalComponent(indexFilePath, injectionType, componentPath) {
console.log(chalk.dim(`Reading file: ${indexFilePath}`));
const content = await fs.readFile(indexFilePath, 'utf-8');
const ast = recast.parse(content, {
parser: typescriptParser,
});

let updated = false;

console.log(JSON.stringify(injectionType));
recast.visit(ast, {
visitNewExpression(path) {
if (
n.Identifier.check(path.node.callee) &&
path.node.callee.name === 'AdminForth' &&
path.node.arguments.length > 0 &&
n.ObjectExpression.check(path.node.arguments[0])
) {
const configObject = path.node.arguments[0];

let customizationProp = configObject.properties.find(
p => n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === 'customization'
);

if (!customizationProp) {
const customizationObj = b.objectExpression([]);
customizationProp = b.objectProperty(b.identifier('customization'), customizationObj);
configObject.properties.push(customizationProp);
console.log(chalk.dim(`Added missing 'customization' property.`));
}

const customizationValue = customizationProp.value;
if (!n.ObjectExpression.check(customizationValue)) return false;

let globalInjections = customizationValue.properties.find(
p => n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === 'globalInjections'
);

if (!globalInjections) {
const injectionsObj = b.objectExpression([]);
globalInjections = b.objectProperty(b.identifier('globalInjections'), injectionsObj);
customizationValue.properties.push(globalInjections);
console.log(chalk.dim(`Added missing 'globalInjections'.`));
}

const injectionsValue = globalInjections.value;
if (!n.ObjectExpression.check(injectionsValue)) return false;
console.log(JSON.stringify(injectionType));
let injectionProp = injectionsValue.properties.find(
p => n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === injectionType
);
if (injectionProp) {
const currentValue = injectionProp.value;

if (n.ArrayExpression.check(currentValue)) {
currentValue.elements.push(b.stringLiteral(componentPath));
console.log(chalk.dim(`Added '${componentPath}' to existing array in '${injectionType}'`));
} else if (n.StringLiteral.check(currentValue)) {
injectionProp.value = b.arrayExpression([
b.stringLiteral(currentValue.value),
b.stringLiteral(componentPath)
]);
console.log(chalk.dim(`Converted '${injectionType}' from string to array and added '${componentPath}'`));
} else {
throw new Error(`Unsupported value type for '${injectionType}'. Must be string or array.`);
}
} else {
injectionsValue.properties.push(
b.objectProperty(
b.identifier(injectionType),
b.arrayExpression([b.stringLiteral(componentPath)])
)
);
console.log(chalk.dim(`Added new array for '${injectionType}' with '${componentPath}'`));
}

updated = true;
this.abort();
}
return false;
}
});

if (!updated) {
throw new Error(`Could not find AdminForth configuration in file: ${indexFilePath}`);
}

const outputCode = recast.print(ast).code;
await fs.writeFile(indexFilePath, outputCode, 'utf-8');
console.log(chalk.green(`✅ Successfully updated global injection '${injectionType}' in: ${indexFilePath}`));
}

export async function updateCrudInjectionConfig(resourceId, crudType, injectionPosition, componentPathForConfig, isThin) {
const filePath = await findResourceFilePath(resourceId);
console.log(chalk.dim(`Attempting to update resource CRUD injection: ${filePath}`));

let content;
try {
content = await fs.readFile(filePath, 'utf-8');
} catch (error) {
console.error(chalk.red(`❌ Error reading resource file: ${filePath}`));
throw new Error(`Could not read resource file ${filePath}.`);
}

try {
const ast = recast.parse(content, {
parser: typescriptParser
});

let updateApplied = false;

recast.visit(ast, {
visitExportDefaultDeclaration(path) {
const declaration = path.node.declaration;
let objectExpressionNode = null;

if (n.TSAsExpression.check(declaration) && n.ObjectExpression.check(declaration.expression)) {
objectExpressionNode = declaration.expression;
} else if (n.ObjectExpression.check(declaration)) {
objectExpressionNode = declaration;
}

if (!objectExpressionNode) {
console.warn(chalk.yellow(`Warning: Default export in ${filePath} is not an ObjectExpression. Skipping update.`));
return false;
}

const getOrCreateObjectProp = (obj, propName) => {
let prop = obj.properties.find(p => n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === propName);
if (!prop) {
const newObject = b.objectExpression([]);
prop = b.objectProperty(b.identifier(propName), newObject);
obj.properties.push(prop);
}
return prop.value;
};

const options = getOrCreateObjectProp(objectExpressionNode, 'options');
if (!n.ObjectExpression.check(options)) return false;

const pageInjections = getOrCreateObjectProp(options, 'pageInjections');
if (!n.ObjectExpression.check(pageInjections)) return false;

let crudProp = pageInjections.properties.find(p =>
n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === crudType
);

if (!crudProp) {
crudProp = b.objectProperty(
b.identifier(crudType),
b.objectExpression([])
);
pageInjections.properties.push(crudProp);
}

const crudValue = crudProp.value;
if (!n.ObjectExpression.check(crudValue)) return false;

let injectionProp = crudValue.properties.find(p =>
n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === injectionPosition
);

const newInjectionObject = b.objectExpression([
b.objectProperty(b.identifier('file'), b.stringLiteral(componentPathForConfig)),
b.objectProperty(
b.identifier('meta'),
b.objectExpression([
b.objectProperty(b.identifier('thinEnoughToShrinkTable'), b.booleanLiteral(!!isThin)),
])
),
]);

if (injectionProp) {
injectionProp.value = newInjectionObject;
console.log(chalk.dim(`Updated '${injectionPosition}' injection for '${crudType}'.`));
} else {
crudValue.properties.push(b.objectProperty(b.identifier(injectionPosition), newInjectionObject));
console.log(chalk.dim(`Added '${injectionPosition}' injection for '${crudType}'.`));
}

updateApplied = true;
this.abort();
return false;
}
});

if (!updateApplied) {
throw new Error(`Could not inject CRUD component in resource ${resourceId}.`);
}

const outputCode = recast.print(ast).code;
await fs.writeFile(filePath, outputCode, 'utf-8');
console.log(chalk.dim(`✅ Successfully updated CRUD injection in resource file: ${filePath}`));

} catch (error) {
console.error(chalk.red(`❌ Error processing resource file: ${filePath}`));
throw new Error(`Failed to inject CRUD component in ${path.basename(filePath)}: ${error.message}`);
}
}
Loading