adminforth 1.18.0 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commands/bundle.js +15 -21
- package/commands/callTsProxy.js +121 -0
- package/commands/cli.js +52 -2
- package/commands/createApp/templates/.dockerignore.hbs +4 -0
- package/commands/createApp/templates/.env.prod.hbs +6 -0
- package/commands/createApp/templates/Dockerfile.hbs +7 -0
- package/commands/createApp/templates/index.ts.hbs +2 -2
- package/commands/createApp/templates/package.json.hbs +8 -5
- package/commands/createApp/templates/readme.md.hbs +14 -2
- package/commands/createApp/utils.js +38 -13
- package/commands/createCustomComponent/configLoader.js +57 -0
- package/commands/createCustomComponent/configUpdater.js +207 -0
- package/commands/createCustomComponent/fileGenerator.js +72 -0
- package/commands/createCustomComponent/main.js +138 -0
- package/commands/createCustomComponent/templates/customFields/create.vue.hbs +41 -0
- package/commands/createCustomComponent/templates/customFields/edit.vue.hbs +41 -0
- package/commands/createCustomComponent/templates/customFields/list.vue.hbs +18 -0
- package/commands/createCustomComponent/templates/customFields/show.vue.hbs +17 -0
- package/commands/proxy.ts +60 -0
- package/dist/commands/proxy.d.ts +2 -0
- package/dist/commands/proxy.d.ts.map +1 -0
- package/dist/commands/proxy.js +71 -0
- package/dist/commands/proxy.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modules/codeInjector.d.ts +2 -0
- package/dist/modules/codeInjector.d.ts.map +1 -1
- package/dist/modules/codeInjector.js +64 -13
- package/dist/modules/codeInjector.js.map +1 -1
- package/dist/spa/src/afcl/Dropzone.vue +14 -3
- package/dist/spa/src/renderers/CompactField.vue +3 -1
- package/dist/spa/src/renderers/CompactUUID.vue +3 -1
- package/dist/spa/src/views/EditView.vue +3 -1
- package/package.json +4 -1
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import recast from 'recast'; // Import recast
|
|
5
|
+
import * as typescriptParser from 'recast/parsers/typescript.js'; // Import the parser using ESM and include the .js extension
|
|
6
|
+
|
|
7
|
+
const b = recast.types.builders; // Like t.* in babel/types
|
|
8
|
+
const n = recast.types.namedTypes; // Like t.is* in babel/types
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
async function findResourceFilePath(resourceId) {
|
|
12
|
+
const projectRoot = process.cwd();
|
|
13
|
+
const resourcesDir = path.resolve(projectRoot, 'resources');
|
|
14
|
+
console.log(chalk.dim(`Scanning for resource files in: ${resourcesDir}`));
|
|
15
|
+
|
|
16
|
+
let tsFiles = [];
|
|
17
|
+
try {
|
|
18
|
+
const entries = await fs.readdir(resourcesDir, { withFileTypes: true });
|
|
19
|
+
tsFiles = entries
|
|
20
|
+
.filter(dirent => dirent.isFile() && dirent.name.endsWith('.ts') && !dirent.name.endsWith('.d.ts'))
|
|
21
|
+
.map(dirent => dirent.name);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (error.code === 'ENOENT') {
|
|
24
|
+
throw new Error(`Resources directory not found at ${resourcesDir}. Please ensure it exists.`);
|
|
25
|
+
}
|
|
26
|
+
throw new Error(`Failed to read resources directory ${resourcesDir}: ${error.message}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log(chalk.dim(`Found .ts files to scan: ${tsFiles.join(', ') || 'None'}`));
|
|
30
|
+
|
|
31
|
+
for (const file of tsFiles) {
|
|
32
|
+
const filePath = path.resolve(resourcesDir, file);
|
|
33
|
+
console.log(chalk.dim(`Attempting to process file: ${file}`));
|
|
34
|
+
try {
|
|
35
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
36
|
+
const ast = recast.parse(content, {
|
|
37
|
+
parser: typescriptParser
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
let foundResourceId = null;
|
|
41
|
+
|
|
42
|
+
recast.visit(ast, {
|
|
43
|
+
visitExportDefaultDeclaration(path) {
|
|
44
|
+
if (foundResourceId !== null) return false; // Stop visiting deeper if already found
|
|
45
|
+
|
|
46
|
+
const declaration = path.node.declaration;
|
|
47
|
+
let objectExpressionNode = null;
|
|
48
|
+
|
|
49
|
+
if (n.TSAsExpression.check(declaration) && n.ObjectExpression.check(declaration.expression)) {
|
|
50
|
+
objectExpressionNode = declaration.expression;
|
|
51
|
+
}
|
|
52
|
+
else if (n.ObjectExpression.check(declaration)) {
|
|
53
|
+
objectExpressionNode = declaration;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (objectExpressionNode) {
|
|
57
|
+
const resourceIdProp = objectExpressionNode.properties.find(prop =>
|
|
58
|
+
n.ObjectProperty.check(prop) &&
|
|
59
|
+
n.Identifier.check(prop.key) &&
|
|
60
|
+
prop.key.name === 'resourceId' &&
|
|
61
|
+
n.StringLiteral.check(prop.value)
|
|
62
|
+
);
|
|
63
|
+
if (resourceIdProp) {
|
|
64
|
+
foundResourceId = resourceIdProp.value.value; // Get the string value
|
|
65
|
+
console.log(chalk.dim(` Extracted resourceId '${foundResourceId}' from ${file}`));
|
|
66
|
+
this.abort(); // Stop traversal for this file once found
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
console.log(chalk.dim(` Finished processing ${file}. Found resourceId: ${foundResourceId || 'null'}`));
|
|
74
|
+
|
|
75
|
+
if (foundResourceId === resourceId) {
|
|
76
|
+
console.log(chalk.dim(` Match found! Returning path: ${filePath}`));
|
|
77
|
+
return filePath;
|
|
78
|
+
}
|
|
79
|
+
} catch (parseError) {
|
|
80
|
+
if (parseError.message.includes('require is not defined')) {
|
|
81
|
+
console.error(chalk.red(`❌ Internal Error: Failed to load Recast parser in ESM context for ${file}.`));
|
|
82
|
+
} else {
|
|
83
|
+
console.warn(chalk.yellow(`⚠️ Warning: Could not process file ${file}. Skipping. Error: ${parseError.message}`));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
throw new Error(`Could not find a resource file in '${resourcesDir}' with resourceId: '${resourceId}'`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
export async function updateResourceConfig(resourceId, columnName, fieldType, componentPathForConfig) {
|
|
93
|
+
const filePath = await findResourceFilePath(resourceId);
|
|
94
|
+
console.log(chalk.dim(`Attempting to update resource config: ${filePath}`));
|
|
95
|
+
|
|
96
|
+
let content;
|
|
97
|
+
try {
|
|
98
|
+
content = await fs.readFile(filePath, 'utf-8');
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error(chalk.red(`❌ Error reading resource file: ${filePath}`));
|
|
101
|
+
console.error(error);
|
|
102
|
+
throw new Error(`Could not read resource file ${filePath}.`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const ast = recast.parse(content, {
|
|
107
|
+
parser: typescriptParser
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
let updateApplied = false;
|
|
111
|
+
|
|
112
|
+
recast.visit(ast, {
|
|
113
|
+
visitExportDefaultDeclaration(path) {
|
|
114
|
+
const declaration = path.node.declaration;
|
|
115
|
+
let objectExpressionNode = null;
|
|
116
|
+
|
|
117
|
+
if (n.TSAsExpression.check(declaration) && n.ObjectExpression.check(declaration.expression)) {
|
|
118
|
+
objectExpressionNode = declaration.expression;
|
|
119
|
+
} else if (n.ObjectExpression.check(declaration)) {
|
|
120
|
+
objectExpressionNode = declaration;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!objectExpressionNode) {
|
|
124
|
+
console.warn(chalk.yellow(`Warning: Default export in ${filePath} is not a recognized ObjectExpression or TSAsExpression containing one. Skipping update.`));
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const properties = objectExpressionNode.properties;
|
|
129
|
+
const columnsProperty = properties.find(prop =>
|
|
130
|
+
n.ObjectProperty.check(prop) && n.Identifier.check(prop.key) && prop.key.name === 'columns'
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
if (!columnsProperty || !n.ArrayExpression.check(columnsProperty.value)) {
|
|
134
|
+
console.warn(chalk.yellow(`Warning: Could not find 'columns' array in the default export of ${filePath}. Skipping update.`));
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const columnsArray = columnsProperty.value.elements;
|
|
139
|
+
const targetColumn = columnsArray.find(col => {
|
|
140
|
+
if (n.ObjectExpression.check(col)) {
|
|
141
|
+
const nameProp = col.properties.find(p =>
|
|
142
|
+
n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === 'name' &&
|
|
143
|
+
n.StringLiteral.check(p.value) && p.value.value === columnName
|
|
144
|
+
);
|
|
145
|
+
return !!nameProp;
|
|
146
|
+
}
|
|
147
|
+
return false;
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
if (!targetColumn || !n.ObjectExpression.check(targetColumn)) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
let componentsProperty = targetColumn.properties.find(p =>
|
|
155
|
+
n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === 'components'
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
if (!componentsProperty) {
|
|
159
|
+
const newComponentsObject = b.objectExpression([]);
|
|
160
|
+
componentsProperty = b.objectProperty(b.identifier('components'), newComponentsObject);
|
|
161
|
+
|
|
162
|
+
const nameIndex = targetColumn.properties.findIndex(p => n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === 'name');
|
|
163
|
+
targetColumn.properties.splice(nameIndex !== -1 ? nameIndex + 1 : targetColumn.properties.length, 0, componentsProperty);
|
|
164
|
+
console.log(chalk.dim(`Added 'components' object to column '${columnName}'.`));
|
|
165
|
+
|
|
166
|
+
} else if (!n.ObjectExpression.check(componentsProperty.value)) {
|
|
167
|
+
console.warn(chalk.yellow(`Warning: 'components' property in column '${columnName}' is not an object. Skipping update.`));
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const componentsObject = componentsProperty.value;
|
|
172
|
+
let fieldTypeProperty = componentsObject.properties.find(p =>
|
|
173
|
+
n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === fieldType
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const newComponentValue = b.stringLiteral(componentPathForConfig);
|
|
177
|
+
|
|
178
|
+
if (fieldTypeProperty) {
|
|
179
|
+
fieldTypeProperty.value = newComponentValue;
|
|
180
|
+
console.log(chalk.dim(`Updated '${fieldType}' component path in column '${columnName}'.`));
|
|
181
|
+
} else {
|
|
182
|
+
fieldTypeProperty = b.objectProperty(b.identifier(fieldType), newComponentValue);
|
|
183
|
+
componentsObject.properties.push(fieldTypeProperty);
|
|
184
|
+
console.log(chalk.dim(`Added '${fieldType}' component path to column '${columnName}'.`));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
updateApplied = true;
|
|
188
|
+
this.abort();
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
if (!updateApplied) {
|
|
194
|
+
throw new Error(`Could not find column '${columnName}' or apply update within the default export's 'columns' array in ${filePath}.`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const outputCode = recast.print(ast).code;
|
|
198
|
+
|
|
199
|
+
await fs.writeFile(filePath, outputCode, 'utf-8');
|
|
200
|
+
console.log(chalk.dim(`Successfully updated resource configuration file (preserving formatting): ${filePath}`));
|
|
201
|
+
|
|
202
|
+
} catch (error) {
|
|
203
|
+
console.error(chalk.red(`❌ Error processing resource file: ${filePath}`));
|
|
204
|
+
console.error(error);
|
|
205
|
+
throw new Error(`Failed to update resource file ${path.basename(filePath)}: ${error.message}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import Handlebars from 'handlebars';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
|
|
7
|
+
async function renderHBSTemplate(templatePath, data) {
|
|
8
|
+
try {
|
|
9
|
+
const templateContent = await fs.readFile(templatePath, 'utf-8');
|
|
10
|
+
const compiled = Handlebars.compile(templateContent);
|
|
11
|
+
return compiled(data);
|
|
12
|
+
} catch (error) {
|
|
13
|
+
console.error(chalk.red(`❌ Error reading or compiling template: ${templatePath}`));
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function generateVueContent(fieldType, { resource, column }) {
|
|
19
|
+
const componentName = `${resource.label}${column.label}${fieldType.charAt(0).toUpperCase() + fieldType.slice(1)}`;
|
|
20
|
+
const columnName = column.name;
|
|
21
|
+
const resourceId = resource.resourceId;
|
|
22
|
+
|
|
23
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
24
|
+
const __dirname = path.dirname(__filename);
|
|
25
|
+
const templatePath = path.join(__dirname, 'templates', 'customFields', `${fieldType}.vue.hbs`);
|
|
26
|
+
|
|
27
|
+
console.log(chalk.dim(`Using template: ${templatePath}`));
|
|
28
|
+
|
|
29
|
+
const context = {
|
|
30
|
+
componentName,
|
|
31
|
+
columnName,
|
|
32
|
+
resourceId,
|
|
33
|
+
resource,
|
|
34
|
+
column
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const fileContent = await renderHBSTemplate(templatePath, context);
|
|
39
|
+
return fileContent;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error(chalk.red(`❌ Failed to generate content for ${componentName}.vue`));
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function generateComponentFile(componentFileName, fieldType, context, config) {
|
|
47
|
+
|
|
48
|
+
const customDirRelative = 'custom';
|
|
49
|
+
|
|
50
|
+
const projectRoot = process.cwd();
|
|
51
|
+
const customDirPath = path.resolve(projectRoot, customDirRelative);
|
|
52
|
+
const absoluteComponentPath = path.resolve(customDirPath, componentFileName);
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
await fs.mkdir(customDirPath, { recursive: true });
|
|
56
|
+
console.log(chalk.dim(`Ensured custom directory exists: ${customDirPath}`));
|
|
57
|
+
|
|
58
|
+
const fileContent = await generateVueContent(fieldType, context);
|
|
59
|
+
|
|
60
|
+
await fs.writeFile(absoluteComponentPath, fileContent, 'utf-8');
|
|
61
|
+
console.log(chalk.green(`✅ Generated component file: ${absoluteComponentPath}`));
|
|
62
|
+
|
|
63
|
+
return absoluteComponentPath;
|
|
64
|
+
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error(chalk.red(`❌ Error creating component file at ${absoluteComponentPath}:`));
|
|
67
|
+
if (!error.message.includes('template')) {
|
|
68
|
+
console.error(error);
|
|
69
|
+
}
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { select, confirm, Separator } from '@inquirer/prompts';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import path from 'path'; // Import path
|
|
4
|
+
import { loadAdminForthConfig } from './configLoader.js'; // Helper to load config
|
|
5
|
+
import { generateComponentFile } from './fileGenerator.js'; // Helper to create the .vue file
|
|
6
|
+
import { updateResourceConfig } from './configUpdater.js'; // Helper to modify resource .ts file
|
|
7
|
+
// import { openFileInIde } from './ideHelper.js'; // Helper to open file
|
|
8
|
+
|
|
9
|
+
export default async function createComponent(args) {
|
|
10
|
+
console.log('This command will help you to generate boilerplate for component.\n');
|
|
11
|
+
|
|
12
|
+
const config = await loadAdminForthConfig();
|
|
13
|
+
const resources = config.resources;
|
|
14
|
+
|
|
15
|
+
const componentType = await select({
|
|
16
|
+
message: 'What component type would you like to add?',
|
|
17
|
+
choices: [
|
|
18
|
+
{ name: `🔤 Custom fields ${chalk.grey('fields')}`, value: 'fields' },
|
|
19
|
+
{ name: `➖ CRUD page injections ${chalk.grey('crudPage')}`, value: 'crudPage' },
|
|
20
|
+
{ name: `🔐 Login page injections ${chalk.grey('login')}`, value: 'login' },
|
|
21
|
+
{ name: `🌐 Global Injections ${chalk.grey('global')}`, value: 'global' },
|
|
22
|
+
],
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (componentType === 'fields') {
|
|
26
|
+
await handleFieldComponentCreation(config, resources);
|
|
27
|
+
} else if (componentType === 'crudPage') {
|
|
28
|
+
await handleCrudPageInjectionCreation(config, resources);
|
|
29
|
+
} else if (componentType === 'login') {
|
|
30
|
+
await handleLoginPageInjectionCreation(config);
|
|
31
|
+
} else if (componentType === 'global') {
|
|
32
|
+
await handleGlobalInjectionCreation(config);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function handleFieldComponentCreation(config, resources) {
|
|
37
|
+
console.log(chalk.grey('Selected ❯ 🔤 Custom fields'));
|
|
38
|
+
|
|
39
|
+
const fieldType = await select({
|
|
40
|
+
message: 'What view component would you like to add?',
|
|
41
|
+
choices: [
|
|
42
|
+
{ name: '🔸 list', value: 'list' },
|
|
43
|
+
{ name: '📃 show', value: 'show' },
|
|
44
|
+
{ name: '✏️ edit', value: 'edit' },
|
|
45
|
+
{ name: '➕ create', value: 'create' },
|
|
46
|
+
new Separator(),
|
|
47
|
+
{ name: '🔙 BACK', value: '__BACK__' },
|
|
48
|
+
]
|
|
49
|
+
});
|
|
50
|
+
if (fieldType === '__BACK__') return createComponent([]); // Go back to main menu
|
|
51
|
+
|
|
52
|
+
console.log(chalk.grey(`Selected ❯ 🔤 Custom fields ❯ ${fieldType}`));
|
|
53
|
+
|
|
54
|
+
const resourceId = await select({
|
|
55
|
+
message: 'Select resource for which you want to change component:',
|
|
56
|
+
choices: [
|
|
57
|
+
...resources.map(r => ({ name: `${r.label} ${chalk.grey(`${r.resourceId}`)}`, value: r.resourceId })),
|
|
58
|
+
new Separator(),
|
|
59
|
+
{ name: '🔙 BACK', value: '__BACK__' },
|
|
60
|
+
]
|
|
61
|
+
});
|
|
62
|
+
if (resourceId === '__BACK__') return handleFieldComponentCreation(config, resources); // Pass config back
|
|
63
|
+
|
|
64
|
+
const selectedResource = resources.find(r => r.resourceId === resourceId);
|
|
65
|
+
console.log(chalk.grey(`Selected ❯ 🔤 Custom fields ❯ ${fieldType} ❯ ${selectedResource.label}`));
|
|
66
|
+
|
|
67
|
+
const columnName = await select({
|
|
68
|
+
message: 'Select column for which you want to create component:',
|
|
69
|
+
choices: [
|
|
70
|
+
...selectedResource.columns.map(c => ({ name: `${c.label} ${chalk.grey(`${c.name}`)}`, value: c.name })),
|
|
71
|
+
new Separator(),
|
|
72
|
+
{ name: '🔙 BACK', value: '__BACK__' },
|
|
73
|
+
]
|
|
74
|
+
});
|
|
75
|
+
if (columnName === '__BACK__') return handleFieldComponentCreation(config, resources); // Pass config back
|
|
76
|
+
|
|
77
|
+
const selectedColumn = selectedResource.columns.find(c => c.name === columnName);
|
|
78
|
+
console.log(chalk.grey(`Selected ❯ 🔤 Custom fields ❯ ${fieldType} ❯ ${selectedResource.label} ❯ ${selectedColumn.label}`));
|
|
79
|
+
console.log(chalk.dim(`One-line alternative: |adminforth component fields.${fieldType}.${resourceId}.${columnName}|`));
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
const existingComponentPath = null;
|
|
83
|
+
|
|
84
|
+
if (existingComponentPath) {
|
|
85
|
+
const action = await select({
|
|
86
|
+
message: 'You already have a component for this field, open it in editor?',
|
|
87
|
+
choices: [
|
|
88
|
+
{ name: '✏️ Open in IDE', value: 'open' },
|
|
89
|
+
{ name: '🔙 BACK', value: '__BACK__' },
|
|
90
|
+
{ name: '🚪 Exit', value: '__EXIT__' },
|
|
91
|
+
]
|
|
92
|
+
});
|
|
93
|
+
if (action === 'open') {
|
|
94
|
+
// await openFileInIde(existingComponentPath); // Needs absolute path
|
|
95
|
+
console.log(`Opening ${existingComponentPath}... (Implementation needed)`);
|
|
96
|
+
} else if (action === '__BACK__') {
|
|
97
|
+
return handleFieldComponentCreation(config, resources); // Pass config back
|
|
98
|
+
} else {
|
|
99
|
+
process.exit(0);
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
const safeResourceLabel = selectedResource.label.replace(/[^a-zA-Z0-9]/g, '');
|
|
103
|
+
const safeColumnLabel = selectedColumn.label.replace(/[^a-zA-Z0-9]/g, '');
|
|
104
|
+
const componentFileName = `${safeResourceLabel}${safeColumnLabel}${fieldType.charAt(0).toUpperCase() + fieldType.slice(1)}.vue`; // e.g., UserEmailShow.vue
|
|
105
|
+
const componentPathForConfig = `@@/${componentFileName}`; // Path relative to custom dir for config
|
|
106
|
+
|
|
107
|
+
let absoluteComponentPath;
|
|
108
|
+
try {
|
|
109
|
+
absoluteComponentPath = await generateComponentFile(
|
|
110
|
+
componentFileName,
|
|
111
|
+
fieldType,
|
|
112
|
+
{ resource: selectedResource, column: selectedColumn },
|
|
113
|
+
config
|
|
114
|
+
);
|
|
115
|
+
console.log(chalk.dim(`Component generation successful: ${absoluteComponentPath}`));
|
|
116
|
+
|
|
117
|
+
await updateResourceConfig(selectedResource.resourceId, columnName, fieldType, componentPathForConfig);
|
|
118
|
+
console.log(chalk.green(`\n✅ Successfully created component ${componentPathForConfig} and updated configuration.`));
|
|
119
|
+
|
|
120
|
+
const openNow = await confirm({
|
|
121
|
+
message: 'Open the new component file in your IDE?',
|
|
122
|
+
default: true
|
|
123
|
+
});
|
|
124
|
+
if (openNow) { // await openFileInIde(absoluteComponentPath); // Use the absolute path here
|
|
125
|
+
console.log(`Opening ${absoluteComponentPath}... (Implementation needed)`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error(chalk.red('\n❌ Component creation failed. Please check the errors above.'));
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// --- TODO: Implement similar handlers for other component types (pass config) ---
|
|
136
|
+
async function handleCrudPageInjectionCreation(config, resources) { console.log('CRUD Page Injection creation not implemented yet.'); }
|
|
137
|
+
async function handleLoginPageInjectionCreation(config) { console.log('Login Page Injection creation not implemented yet.'); }
|
|
138
|
+
async function handleGlobalInjectionCreation(config) { console.log('Global Injection creation not implemented yet.'); }
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Select
|
|
3
|
+
class="w-full"
|
|
4
|
+
:options="column.enum"
|
|
5
|
+
:model-value="record[column.name]"
|
|
6
|
+
@update:model-value="emit('update:value', $event)"
|
|
7
|
+
>
|
|
8
|
+
<template #item="{option}">
|
|
9
|
+
<span class="text-xl inline-flex">\{{ getCountryFlag(option.value) }}</span> \{{ option.label }}
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<template #selected-item="{option}">
|
|
13
|
+
<span class="text-xl inline-flex">\{{ getCountryFlag(option.value) }}</span> \{{ option.label }}
|
|
14
|
+
</template>
|
|
15
|
+
</Select>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<script setup lang="ts">
|
|
19
|
+
import Select from "@/afcl/Select.vue";
|
|
20
|
+
import type {
|
|
21
|
+
AdminForthResourceColumnCommon,
|
|
22
|
+
AdminForthResourceCommon,
|
|
23
|
+
AdminUser,
|
|
24
|
+
} from "@/types/Common";
|
|
25
|
+
|
|
26
|
+
const props = defineProps<{
|
|
27
|
+
column: AdminForthResourceColumnCommon;
|
|
28
|
+
record: any;
|
|
29
|
+
meta: any;
|
|
30
|
+
resource: AdminForthResourceCommon;
|
|
31
|
+
adminUser: AdminUser;
|
|
32
|
+
}>();
|
|
33
|
+
|
|
34
|
+
const emit = defineEmits(["update:value"]);
|
|
35
|
+
|
|
36
|
+
function getCountryFlag(countryCode: string) {
|
|
37
|
+
return countryCode?.toUpperCase()
|
|
38
|
+
.replace(/./g, (char) => String.fromCodePoint(char.charCodeAt(0) + 127397));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
</script>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Select
|
|
3
|
+
class="w-full"
|
|
4
|
+
:options="column.enum"
|
|
5
|
+
:model-value="record[column.name]"
|
|
6
|
+
@update:model-value="emit('update:value', $event)"
|
|
7
|
+
>
|
|
8
|
+
<template #item="{option}">
|
|
9
|
+
<span class="text-xl inline-flex">\{{ getCountryFlag(option.value) }}</span> \{{ option.label }}
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<template #selected-item="{option}">
|
|
13
|
+
<span class="text-xl inline-flex">\{{ getCountryFlag(option.value) }}</span> \{{ option.label }}
|
|
14
|
+
</template>
|
|
15
|
+
</Select>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<script setup lang="ts">
|
|
19
|
+
import Select from "@/afcl/Select.vue";
|
|
20
|
+
import type {
|
|
21
|
+
AdminForthResourceColumnCommon,
|
|
22
|
+
AdminForthResourceCommon,
|
|
23
|
+
AdminUser,
|
|
24
|
+
} from "@/types/Common";
|
|
25
|
+
|
|
26
|
+
const props = defineProps<{
|
|
27
|
+
column: AdminForthResourceColumnCommon;
|
|
28
|
+
record: any;
|
|
29
|
+
meta: any;
|
|
30
|
+
resource: AdminForthResourceCommon;
|
|
31
|
+
adminUser: AdminUser;
|
|
32
|
+
}>();
|
|
33
|
+
|
|
34
|
+
const emit = defineEmits(["update:value"]);
|
|
35
|
+
|
|
36
|
+
function getCountryFlag(countryCode: string) {
|
|
37
|
+
return countryCode?.toUpperCase()
|
|
38
|
+
.replace(/./g, (char) => String.fromCodePoint(char.charCodeAt(0) + 127397));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
</script>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div v-if='record[column.name]'
|
|
3
|
+
class="text-red-500"
|
|
4
|
+
>\{{ record[column.name] }}</div>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup>
|
|
8
|
+
|
|
9
|
+
defineProps({
|
|
10
|
+
record: Object,
|
|
11
|
+
resource: Object,
|
|
12
|
+
adminUser: Object,
|
|
13
|
+
meta: Object,
|
|
14
|
+
column: Object
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
</script>
|
|
18
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div v-if='record[column.name]'
|
|
3
|
+
class="text-red-500"
|
|
4
|
+
>\{{ record[column.name] }}</div>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup>
|
|
8
|
+
|
|
9
|
+
defineProps({
|
|
10
|
+
record: Object,
|
|
11
|
+
resource: Object,
|
|
12
|
+
adminUser: Object,
|
|
13
|
+
meta: Object,
|
|
14
|
+
column: Object
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
</script>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// tsproxy.ts
|
|
2
|
+
import { writeFile, unlink } from 'fs/promises';
|
|
3
|
+
import { randomUUID } from 'crypto';
|
|
4
|
+
import { pathToFileURL } from 'url';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
|
|
7
|
+
(async () => {
|
|
8
|
+
const chunks: Buffer[] = [];
|
|
9
|
+
|
|
10
|
+
for await (const chunk of process.stdin) {
|
|
11
|
+
chunks.push(chunk);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const code = Buffer.concat(chunks).toString();
|
|
15
|
+
|
|
16
|
+
const tmpFileName = `.tmp-tsproxy-${randomUUID()}.ts`;
|
|
17
|
+
|
|
18
|
+
const tmpFile = path.join(process.cwd(), tmpFileName);
|
|
19
|
+
|
|
20
|
+
const origLog = console.log;
|
|
21
|
+
let capturedLogs: any[] = [];
|
|
22
|
+
console.log = (...args: any[]) => {
|
|
23
|
+
capturedLogs.push(args);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
process.env.HEAVY_DEBUG && console.log(`🪲 TMP proxy file: ${tmpFile}`);
|
|
27
|
+
process.env.HEAVY_DEBUG && console.log(`🪲 Current working directory: ${process.cwd()}`);
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
// Save code to a temp file
|
|
31
|
+
await writeFile(tmpFile, code);
|
|
32
|
+
|
|
33
|
+
// Dynamically import the file
|
|
34
|
+
const module = await import(pathToFileURL(tmpFile).href);
|
|
35
|
+
|
|
36
|
+
if (typeof module.exec !== 'function') {
|
|
37
|
+
throw new Error("Module does not export an 'exec' function");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const result = await Promise.resolve(module.exec());
|
|
41
|
+
|
|
42
|
+
// Restore original console.log
|
|
43
|
+
console.log = origLog;
|
|
44
|
+
console.log(JSON.stringify({
|
|
45
|
+
result,
|
|
46
|
+
capturedLogs,
|
|
47
|
+
error: null
|
|
48
|
+
}));
|
|
49
|
+
} catch (error: any) {
|
|
50
|
+
// Restore original console.log
|
|
51
|
+
console.log = origLog;
|
|
52
|
+
console.log(JSON.stringify({
|
|
53
|
+
error: error.message,
|
|
54
|
+
stack: error.stack,
|
|
55
|
+
capturedLogs
|
|
56
|
+
}));
|
|
57
|
+
} finally {
|
|
58
|
+
await unlink(tmpFile).catch(() => {});
|
|
59
|
+
}
|
|
60
|
+
})();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../commands/proxy.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
2
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
3
|
+
var m = o[Symbol.asyncIterator], i;
|
|
4
|
+
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
5
|
+
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
6
|
+
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
7
|
+
};
|
|
8
|
+
// tsproxy.ts
|
|
9
|
+
import { writeFile, unlink } from 'fs/promises';
|
|
10
|
+
import { randomUUID } from 'crypto';
|
|
11
|
+
import { pathToFileURL } from 'url';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
(async () => {
|
|
14
|
+
var _a, e_1, _b, _c;
|
|
15
|
+
const chunks = [];
|
|
16
|
+
try {
|
|
17
|
+
for (var _d = true, _e = __asyncValues(process.stdin), _f; _f = await _e.next(), _a = _f.done, !_a; _d = true) {
|
|
18
|
+
_c = _f.value;
|
|
19
|
+
_d = false;
|
|
20
|
+
const chunk = _c;
|
|
21
|
+
chunks.push(chunk);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
25
|
+
finally {
|
|
26
|
+
try {
|
|
27
|
+
if (!_d && !_a && (_b = _e.return)) await _b.call(_e);
|
|
28
|
+
}
|
|
29
|
+
finally { if (e_1) throw e_1.error; }
|
|
30
|
+
}
|
|
31
|
+
const code = Buffer.concat(chunks).toString();
|
|
32
|
+
const tmpFileName = `.tmp-tsproxy-${randomUUID()}.ts`;
|
|
33
|
+
const tmpFile = path.join(process.cwd(), tmpFileName);
|
|
34
|
+
const origLog = console.log;
|
|
35
|
+
let capturedLogs = [];
|
|
36
|
+
console.log = (...args) => {
|
|
37
|
+
capturedLogs.push(args);
|
|
38
|
+
};
|
|
39
|
+
process.env.HEAVY_DEBUG && console.log(`🪲 TMP proxy file: ${tmpFile}`);
|
|
40
|
+
process.env.HEAVY_DEBUG && console.log(`🪲 Current working directory: ${process.cwd()}`);
|
|
41
|
+
try {
|
|
42
|
+
// Save code to a temp file
|
|
43
|
+
await writeFile(tmpFile, code);
|
|
44
|
+
// Dynamically import the file
|
|
45
|
+
const module = await import(pathToFileURL(tmpFile).href);
|
|
46
|
+
if (typeof module.exec !== 'function') {
|
|
47
|
+
throw new Error("Module does not export an 'exec' function");
|
|
48
|
+
}
|
|
49
|
+
const result = await Promise.resolve(module.exec());
|
|
50
|
+
// Restore original console.log
|
|
51
|
+
console.log = origLog;
|
|
52
|
+
console.log(JSON.stringify({
|
|
53
|
+
result,
|
|
54
|
+
capturedLogs,
|
|
55
|
+
error: null
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
// Restore original console.log
|
|
60
|
+
console.log = origLog;
|
|
61
|
+
console.log(JSON.stringify({
|
|
62
|
+
error: error.message,
|
|
63
|
+
stack: error.stack,
|
|
64
|
+
capturedLogs
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
await unlink(tmpFile).catch(() => { });
|
|
69
|
+
}
|
|
70
|
+
})();
|
|
71
|
+
//# sourceMappingURL=proxy.js.map
|