Skip to content

Commit cd0a8a9

Browse files
authored
Merge pull request #213 from devforth/create-cli
Create cli
2 parents 55a870b + c08ae0b commit cd0a8a9

File tree

13 files changed

+284
-189
lines changed

13 files changed

+284
-189
lines changed

adminforth/commands/createCustomComponent/configUpdater.js

Lines changed: 135 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ async function findResourceFilePath(resourceId) {
2626
throw new Error(`Failed to read resources directory ${resourcesDir}: ${error.message}`);
2727
}
2828

29-
console.log(chalk.dim(`Found .ts files to scan: ${tsFiles.join(', ') || 'None'}`));
3029

3130
for (const file of tsFiles) {
3231
const filePath = path.resolve(resourcesDir, file);
@@ -94,6 +93,7 @@ export async function updateResourceConfig(resourceId, columnName, fieldType, co
9493
console.log(chalk.dim(`Attempting to update resource config: ${filePath}`));
9594

9695
let content;
96+
let injectionLine = null;
9797
try {
9898
content = await fs.readFile(filePath, 'utf-8');
9999
} catch (error) {
@@ -176,11 +176,16 @@ export async function updateResourceConfig(resourceId, columnName, fieldType, co
176176
const newComponentValue = b.stringLiteral(componentPathForConfig);
177177

178178
if (fieldTypeProperty) {
179+
injectionLine = fieldTypeProperty.loc?.start.line ?? null;
179180
fieldTypeProperty.value = newComponentValue;
180181
console.log(chalk.dim(`Updated '${fieldType}' component path in column '${columnName}'.`));
181182
} else {
182183
fieldTypeProperty = b.objectProperty(b.identifier(fieldType), newComponentValue);
183184
componentsObject.properties.push(fieldTypeProperty);
185+
fieldTypeProperty = componentsObject.properties.find(p =>
186+
n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === fieldType
187+
);
188+
injectionLine = fieldTypeProperty.loc?.start.line ?? null;
184189
console.log(chalk.dim(`Added '${fieldType}' component path to column '${columnName}'.`));
185190
}
186191

@@ -197,7 +202,12 @@ export async function updateResourceConfig(resourceId, columnName, fieldType, co
197202
const outputCode = recast.print(ast).code;
198203

199204
await fs.writeFile(filePath, outputCode, 'utf-8');
200-
console.log(chalk.dim(`Successfully updated resource configuration file (preserving formatting): ${filePath}`));
205+
console.log(
206+
chalk.green(
207+
`✅ Successfully updated CRUD injection in resource file: ${filePath}` +
208+
(injectionLine !== null ? `:${injectionLine}` : '')
209+
)
210+
);
201211

202212
} catch (error) {
203213
console.error(chalk.red(`❌ Error processing resource file: ${filePath}`));
@@ -211,78 +221,106 @@ export async function injectLoginComponent(indexFilePath, componentPath) {
211221
console.log(chalk.dim(`Reading file: ${indexFilePath}`));
212222
const content = await fs.readFile(indexFilePath, 'utf-8');
213223
const ast = recast.parse(content, {
214-
parser: typescriptParser,
224+
parser: typescriptParser,
215225
});
216-
226+
217227
let updated = false;
218-
228+
let injectionLine = null;
229+
219230
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-
const customizationObj = b.objectExpression([]);
235-
customizationProp = b.objectProperty(b.identifier('customization'), customizationObj);
236-
configObject.properties.push(customizationProp);
237-
console.log(chalk.dim(`Added missing 'customization' property.`));
238-
}
239-
240-
const customizationValue = customizationProp.value;
241-
if (!n.ObjectExpression.check(customizationValue)) return false;
242-
243-
let loginPageInjections = customizationValue.properties.find(
244-
p => n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === 'loginPageInjections'
245-
);
246-
247-
if (!loginPageInjections) {
248-
const injectionsObj = b.objectExpression([]);
249-
loginPageInjections = b.objectProperty(b.identifier('loginPageInjections'), injectionsObj);
250-
customizationValue.properties.push(loginPageInjections);
251-
console.log(chalk.dim(`Added missing 'loginPageInjections'.`));
252-
}
253-
254-
const injectionsValue = loginPageInjections.value;
255-
if (!n.ObjectExpression.check(injectionsValue)) return false;
256-
257-
let underInputsProp = injectionsValue.properties.find(
258-
p => n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === 'underInputs'
259-
);
260-
261-
if (underInputsProp) {
262-
underInputsProp.value = b.stringLiteral(componentPath);
263-
console.log(chalk.dim(`Updated 'underInputs' to ${componentPath}`));
264-
} else {
265-
injectionsValue.properties.push(
266-
b.objectProperty(b.identifier('underInputs'), b.stringLiteral(componentPath))
267-
);
268-
console.log(chalk.dim(`Added 'underInputs': ${componentPath}`));
269-
}
270-
271-
updated = true;
272-
this.abort();
231+
visitNewExpression(path) {
232+
if (
233+
n.Identifier.check(path.node.callee) &&
234+
path.node.callee.name === 'AdminForth' &&
235+
path.node.arguments.length > 0 &&
236+
n.ObjectExpression.check(path.node.arguments[0])
237+
) {
238+
const configObject = path.node.arguments[0];
239+
240+
const getOrCreateProp = (obj, name) => {
241+
let prop = obj.properties.find(
242+
p => n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === name
243+
);
244+
if (!prop) {
245+
const newObj = b.objectExpression([]);
246+
prop = b.objectProperty(b.identifier(name), newObj);
247+
obj.properties.push(prop);
248+
console.log(chalk.dim(`Added missing '${name}' property.`));
273249
}
274-
return false;
250+
return prop.value;
251+
};
252+
253+
const customization = getOrCreateProp(configObject, 'customization');
254+
if (!n.ObjectExpression.check(customization)) return false;
255+
256+
const loginPageInjections = getOrCreateProp(customization, 'loginPageInjections');
257+
if (!n.ObjectExpression.check(loginPageInjections)) return false;
258+
259+
let underInputsProp = loginPageInjections.properties.find(
260+
p => n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === 'underInputs'
261+
);
262+
263+
if (underInputsProp) {
264+
const currentVal = underInputsProp.value;
265+
injectionLine = underInputsProp.loc?.start.line ?? null;
266+
if (n.StringLiteral.check(currentVal)) {
267+
if (currentVal.value !== componentPath) {
268+
underInputsProp.value = b.arrayExpression([
269+
b.stringLiteral(currentVal.value),
270+
b.stringLiteral(componentPath),
271+
]);
272+
console.log(chalk.dim(`Converted 'underInputs' to array with existing + new path.`));
273+
} else {
274+
console.log(chalk.dim(`Component path already present as string. Skipping.`));
275+
}
276+
} else if (n.ArrayExpression.check(currentVal)) {
277+
const exists = currentVal.elements.some(
278+
el => n.StringLiteral.check(el) && el.value === componentPath
279+
);
280+
if (!exists) {
281+
currentVal.elements.push(b.stringLiteral(componentPath));
282+
console.log(chalk.dim(`Appended new component path to existing 'underInputs' array.`));
283+
} else {
284+
console.log(chalk.dim(`Component path already present in array. Skipping.`));
285+
}
286+
} else {
287+
console.warn(chalk.yellow(`⚠️ 'underInputs' is not a string or array. Skipping.`));
288+
return false;
289+
}
290+
} else {
291+
const newProperty = b.objectProperty(
292+
b.identifier('underInputs'),
293+
b.stringLiteral(componentPath)
294+
);
295+
296+
if (newProperty.loc) {
297+
console.log(chalk.dim(`Adding 'underInputs' at line: ${newProperty.loc.start.line}`));
298+
}
299+
300+
loginPageInjections.properties.push(newProperty);
301+
console.log(chalk.dim(`Added 'underInputs': ${componentPath}`));
302+
}
303+
304+
updated = true;
305+
this.abort();
275306
}
307+
return false;
308+
}
276309
});
277-
310+
278311
if (!updated) {
279-
throw new Error(`Could not find AdminForth configuration in file: ${indexFilePath}`);
312+
throw new Error(`Could not find AdminForth configuration in file: ${indexFilePath}`);
280313
}
281-
314+
282315
const outputCode = recast.print(ast).code;
283316
await fs.writeFile(indexFilePath, outputCode, 'utf-8');
284-
console.log(chalk.green(`✅ Successfully updated login injection in: ${indexFilePath}`));
285-
}
317+
console.log(
318+
chalk.green(
319+
`✅ Successfully updated CRUD injection in resource file: ${indexFilePath}` +
320+
(injectionLine !== null ? `:${injectionLine}` : '')
321+
)
322+
);
323+
}
286324

287325

288326
export async function injectGlobalComponent(indexFilePath, injectionType, componentPath) {
@@ -293,7 +331,7 @@ export async function injectGlobalComponent(indexFilePath, injectionType, compon
293331
});
294332

295333
let updated = false;
296-
334+
let injectionLine = null;
297335
console.log(JSON.stringify(injectionType));
298336
recast.visit(ast, {
299337
visitNewExpression(path) {
@@ -315,7 +353,7 @@ export async function injectGlobalComponent(indexFilePath, injectionType, compon
315353
configObject.properties.push(customizationProp);
316354
console.log(chalk.dim(`Added missing 'customization' property.`));
317355
}
318-
356+
319357
const customizationValue = customizationProp.value;
320358
if (!n.ObjectExpression.check(customizationValue)) return false;
321359

@@ -338,7 +376,7 @@ export async function injectGlobalComponent(indexFilePath, injectionType, compon
338376
);
339377
if (injectionProp) {
340378
const currentValue = injectionProp.value;
341-
379+
injectionLine = injectionProp.loc?.start.line ?? null;
342380
if (n.ArrayExpression.check(currentValue)) {
343381
currentValue.elements.push(b.stringLiteral(componentPath));
344382
console.log(chalk.dim(`Added '${componentPath}' to existing array in '${injectionType}'`));
@@ -374,14 +412,20 @@ export async function injectGlobalComponent(indexFilePath, injectionType, compon
374412

375413
const outputCode = recast.print(ast).code;
376414
await fs.writeFile(indexFilePath, outputCode, 'utf-8');
377-
console.log(chalk.green(`✅ Successfully updated global injection '${injectionType}' in: ${indexFilePath}`));
415+
console.log(
416+
chalk.green(
417+
`✅ Successfully updated CRUD injection in resource file: ${indexFilePath}` +
418+
(injectionLine !== null ? `:${injectionLine}` : '')
419+
)
420+
);
378421
}
379422

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

384427
let content;
428+
let injectionLine = null;
385429
try {
386430
content = await fs.readFile(filePath, 'utf-8');
387431
} catch (error) {
@@ -439,7 +483,7 @@ export async function updateCrudInjectionConfig(resourceId, crudType, injectionP
439483
);
440484
pageInjections.properties.push(crudProp);
441485
}
442-
486+
injectionLine = crudProp.loc?.start.line ?? null;
443487
const crudValue = crudProp.value;
444488
if (!n.ObjectExpression.check(crudValue)) return false;
445489

@@ -458,11 +502,23 @@ export async function updateCrudInjectionConfig(resourceId, crudType, injectionP
458502
]);
459503

460504
if (injectionProp) {
461-
injectionProp.value = newInjectionObject;
462-
console.log(chalk.dim(`Updated '${injectionPosition}' injection for '${crudType}'.`));
505+
if (n.ArrayExpression.check(injectionProp.value)) {
506+
injectionProp.value.elements.push(newInjectionObject);
507+
console.log(chalk.dim(`Appended new injection to array at '${injectionPosition}' for '${crudType}'.`));
508+
}
509+
else if (n.ObjectExpression.check(injectionProp.value)) {
510+
injectionProp.value = b.arrayExpression([injectionProp.value, newInjectionObject]);
511+
console.log(chalk.dim(`Converted to array and added new injection at '${injectionPosition}' for '${crudType}'.`));
512+
}
513+
else {
514+
injectionProp.value = b.arrayExpression([newInjectionObject]);
515+
console.log(chalk.yellow(`⚠️ Replaced invalid injection at '${injectionPosition}' with array.`));
516+
}
463517
} else {
464-
crudValue.properties.push(b.objectProperty(b.identifier(injectionPosition), newInjectionObject));
465-
console.log(chalk.dim(`Added '${injectionPosition}' injection for '${crudType}'.`));
518+
crudValue.properties.push(
519+
b.objectProperty(b.identifier(injectionPosition), b.arrayExpression([newInjectionObject]))
520+
);
521+
console.log(chalk.dim(`Added new array of injections at '${injectionPosition}' for '${crudType}'.`));
466522
}
467523

468524
updateApplied = true;
@@ -477,7 +533,12 @@ export async function updateCrudInjectionConfig(resourceId, crudType, injectionP
477533

478534
const outputCode = recast.print(ast).code;
479535
await fs.writeFile(filePath, outputCode, 'utf-8');
480-
console.log(chalk.dim(`✅ Successfully updated CRUD injection in resource file: ${filePath}`));
536+
console.log(
537+
chalk.green(
538+
`✅ Successfully updated CRUD injection in resource file: ${filePath}` +
539+
(injectionLine !== null ? `:${injectionLine}` : '')
540+
)
541+
);
481542

482543
} catch (error) {
483544
console.error(chalk.red(`❌ Error processing resource file: ${filePath}`));

adminforth/commands/createCustomComponent/main.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,16 +170,26 @@ async function handleCrudPageInjectionCreation(config, resources) {
170170
});
171171
if (injectionPosition === '__BACK__') return handleCrudPageInjectionCreation(config, resources);
172172

173+
const additionalName = await input({
174+
message: 'Enter additional name (optional, e.g., "CustomExport"):',
175+
validate: (value) => {
176+
if (!value) return true;
177+
return /^[A-Za-z0-9_-]+$/.test(value) || 'Only alphanumeric characters, hyphens or underscores are allowed.';
178+
},
179+
});
180+
173181
const isThin = await select({
174182
message: 'Will this component be thin enough to fit on the same page with list (so list will still shrink)?',
175183
choices: [
176184
{ name: 'Yes', value: true },
177185
{ name: 'No', value: false },
178186
],
179187
});
180-
188+
const formattedAdditionalName = additionalName
189+
? additionalName[0].toUpperCase() + additionalName.slice(1)
190+
: '';
181191
const safeResourceLabel = sanitizeLabel(selectedResource.label)
182-
const componentFileName = `${safeResourceLabel}${crudType.charAt(0).toUpperCase() + crudType.slice(1)}${injectionPosition.charAt(0).toUpperCase() + injectionPosition.slice(1)}.vue`;
192+
const componentFileName = `${safeResourceLabel}${crudType.charAt(0).toUpperCase() + crudType.slice(1)}${injectionPosition.charAt(0).toUpperCase() + injectionPosition.slice(1) + formattedAdditionalName}.vue`;
183193
const componentPathForConfig = `@@/${componentFileName}`;
184194

185195
try {

adminforth/commands/createCustomComponent/templates/customCrud/afterBreadcrumbs.vue.hbs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
@click="handleClick"
66
class="text-white bg-blue-600 hover:bg-blue-700 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-500 dark:hover:bg-blue-600 dark:focus:ring-blue-800"
77
>
8-
Example
8+
\{{ $t('Example') }}
99
</button>
1010
</div>
1111
</template>
@@ -14,9 +14,17 @@
1414
import { onMounted } from 'vue';
1515
import { useI18n } from 'vue-i18n';
1616
import adminforth from '@/adminforth';
17+
import { AdminForthResourceCommon, AdminUser } from "@/types/Common";
1718
1819
const { t } = useI18n();
1920
21+
const props = defineProps<{
22+
record: any
23+
resource: AdminForthResourceCommon
24+
adminUser: AdminUser
25+
meta?: any
26+
}>();
27+
2028
onMounted(() => {
2129
// Logic on mount if needed
2230
});

adminforth/commands/createCustomComponent/templates/customCrud/beforeBreadcrumbs.vue.hbs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
@click="handleClick"
66
class="text-white bg-blue-600 hover:bg-blue-700 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-500 dark:hover:bg-blue-600 dark:focus:ring-blue-800"
77
>
8-
Example
8+
\{{ $t('Example') }}
99
</button>
1010
</div>
1111
</template>
@@ -14,9 +14,17 @@
1414
import { onMounted } from 'vue';
1515
import { useI18n } from 'vue-i18n';
1616
import adminforth from '@/adminforth';
17+
import { AdminForthResourceCommon, AdminUser } from "@/types/Common";
1718
1819
const { t } = useI18n();
1920
21+
const props = defineProps<{
22+
record: any
23+
resource: AdminForthResourceCommon
24+
adminUser: AdminUser
25+
meta?: any
26+
}>();
27+
2028
onMounted(() => {
2129
// Logic on mount if needed
2230
});

0 commit comments

Comments
 (0)