adminforth 2.4.0-next.3 → 2.4.0-next.300
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/callTsProxy.js +14 -4
- package/commands/cli.js +12 -4
- package/commands/createApp/templates/api.ts.hbs +10 -0
- package/commands/createApp/templates/custom/tsconfig.json.hbs +2 -3
- package/commands/createApp/templates/index.ts.hbs +13 -5
- package/commands/createApp/templates/package.json.hbs +1 -1
- package/commands/createApp/utils.js +45 -7
- package/commands/createCustomComponent/configLoader.js +17 -4
- package/commands/createCustomComponent/configUpdater.js +25 -21
- package/commands/createCustomComponent/fileGenerator.js +1 -1
- package/commands/createCustomComponent/main.js +15 -8
- package/commands/createCustomComponent/templates/customCrud/beforeActionButtons.vue.hbs +38 -0
- package/commands/createCustomComponent/templates/customCrud/saveButton.vue.hbs +28 -0
- package/commands/createCustomComponent/templates/login/beforeLogin.vue.hbs +18 -0
- package/commands/createPlugin/templates/custom/tsconfig.json.hbs +2 -5
- package/commands/createPlugin/templates/package.json.hbs +1 -1
- package/commands/generateModels.js +30 -22
- package/dist/auth.d.ts +9 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +21 -2
- package/dist/auth.js.map +1 -1
- package/dist/dataConnectors/baseConnector.d.ts +1 -1
- package/dist/dataConnectors/baseConnector.d.ts.map +1 -1
- package/dist/dataConnectors/baseConnector.js +69 -17
- package/dist/dataConnectors/baseConnector.js.map +1 -1
- package/dist/dataConnectors/clickhouse.d.ts.map +1 -1
- package/dist/dataConnectors/clickhouse.js +15 -0
- package/dist/dataConnectors/clickhouse.js.map +1 -1
- package/dist/dataConnectors/mongo.d.ts.map +1 -1
- package/dist/dataConnectors/mongo.js +50 -15
- package/dist/dataConnectors/mongo.js.map +1 -1
- package/dist/dataConnectors/mysql.d.ts.map +1 -1
- package/dist/dataConnectors/mysql.js +11 -0
- package/dist/dataConnectors/mysql.js.map +1 -1
- package/dist/dataConnectors/postgres.d.ts.map +1 -1
- package/dist/dataConnectors/postgres.js +43 -14
- package/dist/dataConnectors/postgres.js.map +1 -1
- package/dist/dataConnectors/sqlite.d.ts.map +1 -1
- package/dist/dataConnectors/sqlite.js +11 -0
- package/dist/dataConnectors/sqlite.js.map +1 -1
- package/dist/index.d.ts +12 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +42 -21
- 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 +67 -12
- package/dist/modules/codeInjector.js.map +1 -1
- package/dist/modules/configValidator.d.ts +6 -0
- package/dist/modules/configValidator.d.ts.map +1 -1
- package/dist/modules/configValidator.js +203 -25
- package/dist/modules/configValidator.js.map +1 -1
- package/dist/modules/restApi.d.ts +1 -1
- package/dist/modules/restApi.d.ts.map +1 -1
- package/dist/modules/restApi.js +172 -31
- package/dist/modules/restApi.js.map +1 -1
- package/dist/modules/styles.d.ts +499 -13
- package/dist/modules/styles.d.ts.map +1 -1
- package/dist/modules/styles.js +555 -31
- package/dist/modules/styles.js.map +1 -1
- package/dist/modules/utils.d.ts +7 -15
- package/dist/modules/utils.d.ts.map +1 -1
- package/dist/modules/utils.js +45 -68
- package/dist/modules/utils.js.map +1 -1
- package/dist/servers/express.d.ts +5 -0
- package/dist/servers/express.d.ts.map +1 -1
- package/dist/servers/express.js +40 -1
- package/dist/servers/express.js.map +1 -1
- package/dist/spa/index.html +1 -1
- package/dist/spa/package-lock.json +1208 -708
- package/dist/spa/package.json +34 -34
- package/dist/spa/src/App.vue +58 -173
- package/dist/spa/src/adminforth.ts +42 -18
- package/dist/spa/src/afcl/AreaChart.vue +0 -1
- package/dist/spa/src/afcl/BarChart.vue +2 -2
- package/dist/spa/src/afcl/Button.vue +6 -6
- package/dist/spa/src/afcl/ButtonGroup.vue +91 -0
- package/dist/spa/src/afcl/Card.vue +25 -0
- package/dist/spa/src/afcl/Checkbox.vue +21 -13
- package/dist/spa/src/afcl/CountryFlag.vue +4 -1
- package/dist/spa/src/{components/CustomDatePicker.vue → afcl/DatePicker.vue} +95 -9
- package/dist/spa/src/afcl/Dialog.vue +47 -27
- package/dist/spa/src/afcl/Dropzone.vue +103 -44
- package/dist/spa/src/afcl/Input.vue +18 -8
- package/dist/spa/src/afcl/JsonViewer.vue +25 -0
- package/dist/spa/src/afcl/Link.vue +1 -1
- package/dist/spa/src/afcl/LinkButton.vue +3 -3
- package/dist/spa/src/afcl/PieChart.vue +5 -5
- package/dist/spa/src/afcl/ProgressBar.vue +7 -7
- package/dist/spa/src/afcl/Select.vue +82 -34
- package/dist/spa/src/afcl/Skeleton.vue +6 -6
- package/dist/spa/src/afcl/Table.vue +309 -73
- package/dist/spa/src/afcl/Textarea.vue +31 -0
- package/dist/spa/src/afcl/Toggle.vue +32 -0
- package/dist/spa/src/afcl/Tooltip.vue +28 -18
- package/dist/spa/src/afcl/VerticalTabs.vue +16 -7
- package/dist/spa/src/afcl/index.ts +6 -3
- package/dist/spa/src/components/AcceptModal.vue +48 -14
- package/dist/spa/src/components/Breadcrumbs.vue +5 -5
- package/dist/spa/src/components/CallActionWrapper.vue +15 -0
- package/dist/spa/src/components/ColumnValueInput.vue +38 -18
- package/dist/spa/src/components/ColumnValueInputWrapper.vue +4 -3
- package/dist/spa/src/components/CustomDateRangePicker.vue +9 -8
- package/dist/spa/src/components/CustomRangePicker.vue +37 -21
- package/dist/spa/src/components/ErrorMessage.vue +21 -0
- package/dist/spa/src/components/Filters.vue +194 -131
- package/dist/spa/src/components/GroupsTable.vue +9 -8
- package/dist/spa/src/components/MenuLink.vue +90 -23
- package/dist/spa/src/components/ResourceForm.vue +94 -51
- package/dist/spa/src/components/ResourceListTable.vue +120 -90
- package/dist/spa/src/components/ResourceListTableVirtual.vue +118 -84
- package/dist/spa/src/components/ShowTable.vue +21 -15
- package/dist/spa/src/components/Sidebar.vue +470 -0
- package/dist/spa/src/components/SingleSkeletLoader.vue +6 -6
- package/dist/spa/src/components/SkeleteLoader.vue +3 -3
- package/dist/spa/src/components/ThreeDotsMenu.vue +84 -15
- package/dist/spa/src/components/Toast.vue +40 -29
- package/dist/spa/src/components/UserMenuSettingsButton.vue +69 -0
- package/dist/spa/src/components/ValueRenderer.vue +44 -17
- package/dist/spa/src/controls/BoolToggle.vue +34 -0
- package/dist/spa/src/i18n.ts +5 -3
- package/dist/spa/src/main.ts +1 -1
- package/dist/spa/src/renderers/CompactField.vue +1 -1
- package/dist/spa/src/renderers/CompactUUID.vue +1 -1
- package/dist/spa/src/router/index.ts +8 -0
- package/dist/spa/src/shims-vue.d.ts +5 -0
- package/dist/spa/src/spa_types/core.ts +13 -1
- package/dist/spa/src/stores/core.ts +13 -1
- package/dist/spa/src/stores/filters.ts +33 -2
- package/dist/spa/src/stores/modal.ts +6 -1
- package/dist/spa/src/stores/toast.ts +22 -3
- package/dist/spa/src/types/Back.ts +164 -23
- package/dist/spa/src/types/Common.ts +92 -32
- package/dist/spa/src/types/FrontendAPI.ts +31 -5
- package/dist/spa/src/types/adapters/CaptchaAdapter.ts +34 -0
- package/dist/spa/src/types/adapters/CompletionAdapter.ts +25 -0
- package/dist/spa/src/types/adapters/EmailAdapter.ts +27 -0
- package/dist/spa/src/types/adapters/ImageGenerationAdapter.ts +50 -0
- package/dist/spa/src/types/adapters/ImageVisionAdapter.ts +30 -0
- package/dist/spa/src/types/adapters/KeyValueAdapter.ts +16 -0
- package/dist/spa/src/types/adapters/OAuth2Adapter.ts +34 -0
- package/dist/spa/src/types/adapters/StorageAdapter.ts +73 -0
- package/dist/spa/src/types/adapters/index.ts +8 -0
- package/dist/spa/src/utils.ts +291 -11
- package/dist/spa/src/views/CreateView.vue +63 -21
- package/dist/spa/src/views/EditView.vue +55 -22
- package/dist/spa/src/views/ListView.vue +144 -87
- package/dist/spa/src/views/LoginView.vue +60 -55
- package/dist/spa/src/views/ResourceParent.vue +2 -2
- package/dist/spa/src/views/SettingsView.vue +121 -0
- package/dist/spa/src/views/ShowView.vue +83 -53
- package/dist/spa/src/websocket.ts +6 -1
- package/dist/spa/tsconfig.app.json +1 -1
- package/dist/spa/vite.config.ts +45 -2
- package/dist/types/Back.d.ts +147 -14
- package/dist/types/Back.d.ts.map +1 -1
- package/dist/types/Back.js +15 -0
- package/dist/types/Back.js.map +1 -1
- package/dist/types/Common.d.ts +107 -29
- package/dist/types/Common.d.ts.map +1 -1
- package/dist/types/Common.js.map +1 -1
- package/dist/types/FrontendAPI.d.ts +31 -3
- package/dist/types/FrontendAPI.d.ts.map +1 -1
- package/dist/types/FrontendAPI.js.map +1 -1
- package/dist/types/adapters/CaptchaAdapter.d.ts +30 -0
- package/dist/types/adapters/CaptchaAdapter.d.ts.map +1 -0
- package/dist/types/adapters/CaptchaAdapter.js +5 -0
- package/dist/types/adapters/CaptchaAdapter.js.map +1 -0
- package/dist/types/adapters/CompletionAdapter.d.ts +20 -0
- package/dist/types/adapters/CompletionAdapter.d.ts.map +1 -0
- package/dist/types/adapters/CompletionAdapter.js +2 -0
- package/dist/types/adapters/CompletionAdapter.js.map +1 -0
- package/dist/types/adapters/EmailAdapter.d.ts +20 -0
- package/dist/types/adapters/EmailAdapter.d.ts.map +1 -0
- package/dist/types/adapters/EmailAdapter.js +2 -0
- package/dist/types/adapters/EmailAdapter.js.map +1 -0
- package/dist/types/adapters/ImageGenerationAdapter.d.ts +37 -0
- package/dist/types/adapters/ImageGenerationAdapter.d.ts.map +1 -0
- package/dist/types/adapters/ImageGenerationAdapter.js +2 -0
- package/dist/types/adapters/ImageGenerationAdapter.js.map +1 -0
- package/dist/types/adapters/ImageVisionAdapter.d.ts +25 -0
- package/dist/types/adapters/ImageVisionAdapter.d.ts.map +1 -0
- package/dist/types/adapters/ImageVisionAdapter.js +2 -0
- package/dist/types/adapters/ImageVisionAdapter.js.map +1 -0
- package/dist/types/adapters/KeyValueAdapter.d.ts +10 -0
- package/dist/types/adapters/KeyValueAdapter.d.ts.map +1 -0
- package/dist/types/adapters/KeyValueAdapter.js +2 -0
- package/dist/types/adapters/KeyValueAdapter.js.map +1 -0
- package/dist/types/adapters/OAuth2Adapter.d.ts +32 -0
- package/dist/types/adapters/OAuth2Adapter.d.ts.map +1 -0
- package/dist/types/adapters/OAuth2Adapter.js +2 -0
- package/dist/types/adapters/OAuth2Adapter.js.map +1 -0
- package/dist/types/adapters/StorageAdapter.d.ts +63 -0
- package/dist/types/adapters/StorageAdapter.d.ts.map +1 -0
- package/dist/types/adapters/StorageAdapter.js +2 -0
- package/dist/types/adapters/StorageAdapter.js.map +1 -0
- package/dist/types/adapters/index.d.ts +9 -0
- package/dist/types/adapters/index.d.ts.map +1 -0
- package/dist/types/adapters/index.js +2 -0
- package/dist/types/adapters/index.js.map +1 -0
- package/package.json +4 -2
- package/dist/spa/src/types/Adapters.ts +0 -213
- package/dist/types/Adapters.d.ts +0 -168
- package/dist/types/Adapters.d.ts.map +0 -1
- package/dist/types/Adapters.js +0 -2
- package/dist/types/Adapters.js.map +0 -1
package/commands/callTsProxy.js
CHANGED
|
@@ -3,18 +3,28 @@ import { spawn } from "child_process";
|
|
|
3
3
|
import path from "path";
|
|
4
4
|
import fs from "fs";
|
|
5
5
|
import chalk from "chalk";
|
|
6
|
+
import dotenv from "dotenv";
|
|
6
7
|
|
|
7
8
|
const currentFilePath = import.meta.url;
|
|
8
9
|
const currentFileFolder = path.dirname(currentFilePath).replace("file:", "");
|
|
9
10
|
|
|
10
11
|
export function callTsProxy(tsCode, silent=false) {
|
|
11
12
|
|
|
13
|
+
const currentDirectory = process.cwd();
|
|
14
|
+
const envPath = path.resolve(currentDirectory, ".env");
|
|
15
|
+
const envLocalPath = path.resolve(currentDirectory, ".env.local");
|
|
16
|
+
if (fs.existsSync(envLocalPath)) {
|
|
17
|
+
dotenv.config({ path: envLocalPath, override: true });
|
|
18
|
+
}
|
|
19
|
+
if (fs.existsSync(envPath)) {
|
|
20
|
+
dotenv.config({ path: envPath, override: true });
|
|
21
|
+
}
|
|
22
|
+
|
|
12
23
|
process.env.HEAVY_DEBUG && console.log("🌐 Calling tsproxy with code:", path.join(currentFileFolder, "proxy.ts"));
|
|
13
24
|
return new Promise((resolve, reject) => {
|
|
14
|
-
const child = spawn("tsx", [
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
25
|
+
const child = spawn("tsx", [path.join(currentFileFolder, "proxy.ts")], {
|
|
26
|
+
env: process.env,
|
|
27
|
+
});
|
|
18
28
|
let stdout = "";
|
|
19
29
|
let stderr = "";
|
|
20
30
|
|
package/commands/cli.js
CHANGED
|
@@ -12,6 +12,7 @@ import createResource from "./createResource/main.js";
|
|
|
12
12
|
import chalk from "chalk";
|
|
13
13
|
import path from "path";
|
|
14
14
|
import fs from "fs";
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
15
16
|
|
|
16
17
|
function showHelp() {
|
|
17
18
|
console.log(
|
|
@@ -20,23 +21,30 @@ function showHelp() {
|
|
|
20
21
|
chalk.green(' create-plugin') + chalk.white(' Create a plugin for your AdminForth app\n') +
|
|
21
22
|
chalk.green(' generate-models') + chalk.white(' Generate TypeScript models from your databases\n') +
|
|
22
23
|
chalk.green(' bundle') + chalk.white(' Bundles your AdminForth app SPA for production\n') +
|
|
23
|
-
chalk.green(' component') + chalk.white(' Scaffold a custom Vue component\n')
|
|
24
|
+
chalk.green(' component') + chalk.white(' Scaffold a custom Vue component\n') +
|
|
25
|
+
chalk.green(' resource') + chalk.white(' Scaffold a custom resource\n')
|
|
24
26
|
);
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
function currentFileDir(importMetaUrl) {
|
|
28
|
-
const filePath = importMetaUrl
|
|
29
|
+
export function currentFileDir(importMetaUrl) {
|
|
30
|
+
const filePath = fileURLToPath(importMetaUrl);
|
|
29
31
|
const fileDir = path.dirname(filePath);
|
|
30
32
|
return fileDir;
|
|
31
33
|
}
|
|
32
34
|
|
|
33
|
-
function
|
|
35
|
+
export function getVersion() {
|
|
34
36
|
const ADMIN_FORTH_ABSOLUTE_PATH = path.join(currentFileDir(import.meta.url), '..');
|
|
35
37
|
|
|
36
38
|
const package_json = JSON.parse(fs.readFileSync(path.join(ADMIN_FORTH_ABSOLUTE_PATH, 'package.json'), 'utf8'));
|
|
37
39
|
|
|
38
40
|
const ADMINFORTH_VERSION = package_json.version;
|
|
39
41
|
|
|
42
|
+
return ADMINFORTH_VERSION;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function showVersion() {
|
|
46
|
+
const ADMINFORTH_VERSION = getVersion();
|
|
47
|
+
|
|
40
48
|
console.log(
|
|
41
49
|
chalk.white('AdminForth CLI version: ') +
|
|
42
50
|
chalk.cyan.bold(ADMINFORTH_VERSION)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Express } from "express";
|
|
2
|
+
import { IAdminForth } from "adminforth";
|
|
3
|
+
|
|
4
|
+
export function initApi(app: Express, admin: IAdminForth) {
|
|
5
|
+
app.get(`${admin.config.baseUrl}/api/hello/`,
|
|
6
|
+
(req, res) => {
|
|
7
|
+
res.json({ message: "Hello from AdminForth API!" });
|
|
8
|
+
}
|
|
9
|
+
);
|
|
10
|
+
}
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"baseUrl": ".",
|
|
4
4
|
"paths": {
|
|
5
|
-
"
|
|
6
|
-
"": "
|
|
7
|
-
"@@/*": "."
|
|
5
|
+
"@/*": ["../node_modules/adminforth/dist/spa/src/*"],
|
|
6
|
+
"@@/*": ["./*"]
|
|
8
7
|
}
|
|
9
8
|
}
|
|
10
9
|
}
|
|
@@ -3,6 +3,8 @@ import AdminForth from 'adminforth';
|
|
|
3
3
|
import usersResource from "./resources/adminuser.js";
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import path from 'path';
|
|
6
|
+
import { Filters } from 'adminforth';
|
|
7
|
+
import { initApi } from './api.js';
|
|
6
8
|
|
|
7
9
|
const ADMIN_BASE_URL = '';
|
|
8
10
|
|
|
@@ -15,6 +17,12 @@ export const admin = new AdminForth({
|
|
|
15
17
|
rememberMeDays: 30,
|
|
16
18
|
loginBackgroundImage: 'https://images.unsplash.com/photo-1534239697798-120952b76f2b?q=80&w=3389&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
|
|
17
19
|
loginBackgroundPosition: '1/2',
|
|
20
|
+
loginPromptHTML: async () => {
|
|
21
|
+
const adminforthUserExists = await admin.resource("adminuser").count(Filters.EQ('email', 'adminforth')) > 0;
|
|
22
|
+
if (adminforthUserExists) {
|
|
23
|
+
return "Please use <b>adminforth</b> as username and <b>adminforth</b> as password"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
18
26
|
},
|
|
19
27
|
customization: {
|
|
20
28
|
brandName: "{{appName}}",
|
|
@@ -23,7 +31,8 @@ export const admin = new AdminForth({
|
|
|
23
31
|
brandLogo: '@@/assets/logo.svg',
|
|
24
32
|
datesFormat: 'DD MMM',
|
|
25
33
|
timeFormat: 'HH:mm a',
|
|
26
|
-
|
|
34
|
+
showBrandNameInSidebar: true,
|
|
35
|
+
showBrandLogoInSidebar: true,
|
|
27
36
|
emptyFieldPlaceholder: '-',
|
|
28
37
|
styles: {
|
|
29
38
|
colors: {
|
|
@@ -57,13 +66,12 @@ export const admin = new AdminForth({
|
|
|
57
66
|
],
|
|
58
67
|
});
|
|
59
68
|
|
|
60
|
-
|
|
61
|
-
const executedFilePath = path.resolve(process.argv[1]);
|
|
62
|
-
|
|
63
|
-
if (currentFilePath === executedFilePath) {
|
|
69
|
+
if (fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
|
|
64
70
|
const app = express();
|
|
65
71
|
app.use(express.json());
|
|
66
72
|
|
|
73
|
+
initApi(app, admin);
|
|
74
|
+
|
|
67
75
|
const port = 3500;
|
|
68
76
|
|
|
69
77
|
admin.bundleNow({ hotReload: process.env.NODE_ENV === 'development' }).then(() => {
|
|
@@ -11,9 +11,31 @@ import { exec } from 'child_process';
|
|
|
11
11
|
|
|
12
12
|
import Handlebars from 'handlebars';
|
|
13
13
|
import { promisify } from 'util';
|
|
14
|
+
import { getVersion } from '../cli.js';
|
|
14
15
|
|
|
15
16
|
const execAsync = promisify(exec);
|
|
16
17
|
|
|
18
|
+
function detectAdminforthVersion() {
|
|
19
|
+
try {
|
|
20
|
+
const version = getVersion();
|
|
21
|
+
|
|
22
|
+
if (typeof version !== 'string') {
|
|
23
|
+
throw new Error('Invalid version format');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (version.includes('next')) {
|
|
27
|
+
return 'next';
|
|
28
|
+
}
|
|
29
|
+
return 'latest';
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.warn('⚠️ Could not detect AdminForth version, defaulting to "latest".');
|
|
32
|
+
return 'latest';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const adminforthVersion = detectAdminforthVersion();
|
|
37
|
+
|
|
38
|
+
|
|
17
39
|
export function parseArgumentsIntoOptions(rawArgs) {
|
|
18
40
|
const args = arg(
|
|
19
41
|
{
|
|
@@ -203,13 +225,21 @@ async function writeTemplateFiles(dirname, cwd, options) {
|
|
|
203
225
|
{
|
|
204
226
|
src: 'package.json.hbs',
|
|
205
227
|
dest: 'package.json',
|
|
206
|
-
data: {
|
|
228
|
+
data: {
|
|
229
|
+
appName,
|
|
230
|
+
adminforthVersion: adminforthVersion,
|
|
231
|
+
},
|
|
207
232
|
},
|
|
208
233
|
{
|
|
209
234
|
src: 'index.ts.hbs',
|
|
210
235
|
dest: 'index.ts',
|
|
211
236
|
data: { appName },
|
|
212
237
|
},
|
|
238
|
+
{
|
|
239
|
+
src: 'api.ts.hbs',
|
|
240
|
+
dest: 'api.ts',
|
|
241
|
+
data: {},
|
|
242
|
+
},
|
|
213
243
|
{
|
|
214
244
|
src: '.gitignore.hbs',
|
|
215
245
|
dest: '.gitignore',
|
|
@@ -285,13 +315,21 @@ async function writeTemplateFiles(dirname, cwd, options) {
|
|
|
285
315
|
|
|
286
316
|
async function installDependencies(ctx, cwd) {
|
|
287
317
|
const isWindows = process.platform === 'win32';
|
|
288
|
-
|
|
289
|
-
|
|
318
|
+
|
|
319
|
+
const nodeBinary = process.execPath;
|
|
320
|
+
const npmPath = path.join(path.dirname(nodeBinary), isWindows ? 'npm.cmd' : 'npm');
|
|
290
321
|
const customDir = ctx.customDir;
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
322
|
+
if (isWindows) {
|
|
323
|
+
const res = await Promise.all([
|
|
324
|
+
await execAsync(`npm install`, { cwd, env: { PATH: process.env.PATH } }),
|
|
325
|
+
await execAsync(`npm install`, { cwd: customDir, env: { PATH: process.env.PATH } }),
|
|
326
|
+
]);
|
|
327
|
+
} else {
|
|
328
|
+
const res = await Promise.all([
|
|
329
|
+
await execAsync(`${nodeBinary} ${npmPath} install`, { cwd, env: { PATH: process.env.PATH } }),
|
|
330
|
+
await execAsync(`${nodeBinary} ${npmPath} install`, { cwd: customDir, env: { PATH: process.env.PATH } }),
|
|
331
|
+
]);
|
|
332
|
+
}
|
|
295
333
|
// console.log(chalk.dim(`Dependencies installed in ${cwd} and ${customDir}: \n${res[0].stdout}${res[1].stdout}`));
|
|
296
334
|
}
|
|
297
335
|
|
|
@@ -2,18 +2,20 @@ import fs from 'fs/promises';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import jiti from 'jiti';
|
|
5
|
+
import dotenv, { config } from "dotenv";
|
|
5
6
|
|
|
7
|
+
dotenv.config({ path: '.env.local', override: true });
|
|
8
|
+
dotenv.config({ path: '.env', override: true });
|
|
6
9
|
|
|
7
|
-
export async function
|
|
10
|
+
export async function getAdminInstance() {
|
|
8
11
|
const configFileName = 'index.ts';
|
|
9
12
|
const configPath = path.resolve(process.cwd(), configFileName);
|
|
10
|
-
|
|
11
13
|
try {
|
|
12
14
|
await fs.access(configPath);
|
|
13
15
|
} catch (error) {
|
|
14
16
|
console.error(chalk.red(`\nError: Configuration file not found at ${configPath}`));
|
|
15
17
|
console.error(chalk.yellow(`Please ensure you are running this command from your project's root directory and the '${configFileName}' file exists.`));
|
|
16
|
-
|
|
18
|
+
return null;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
try {
|
|
@@ -26,8 +28,19 @@ export async function loadAdminForthConfig() {
|
|
|
26
28
|
const configModule = _require(configPath);
|
|
27
29
|
|
|
28
30
|
const adminInstance = configModule.admin || configModule.default?.admin;
|
|
31
|
+
return { adminInstance, configPath, configFileName };
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error(chalk.red(`\nError loading or parsing configuration file: ${configPath}`));
|
|
34
|
+
console.error(error);
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
29
38
|
|
|
39
|
+
export async function loadAdminForthConfig() {
|
|
40
|
+
|
|
41
|
+
const { adminInstance, configPath, configFileName } = await getAdminInstance();
|
|
30
42
|
|
|
43
|
+
try {
|
|
31
44
|
if (!adminInstance) {
|
|
32
45
|
throw new Error(`Could not find 'admin' export in ${configFileName}. Please ensure your config file exports the AdminForth instance like: 'export const admin = new AdminForth({...});'`);
|
|
33
46
|
}
|
|
@@ -50,7 +63,7 @@ export async function loadAdminForthConfig() {
|
|
|
50
63
|
return config;
|
|
51
64
|
|
|
52
65
|
} catch (error) {
|
|
53
|
-
console.error(chalk.red(`\nError loading or parsing configuration file: ${configPath}`));
|
|
66
|
+
console.error(chalk.red(`\nError loading or parsing configuration file: ${configPath}, error: ${error}`));
|
|
54
67
|
console.error(error);
|
|
55
68
|
process.exit(1);
|
|
56
69
|
}
|
|
@@ -254,7 +254,7 @@ export async function updateResourceConfig(resourceId, columnName, fieldType, co
|
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
|
|
257
|
-
export async function injectLoginComponent(indexFilePath, componentPath) {
|
|
257
|
+
export async function injectLoginComponent(indexFilePath, componentPath, injectionType) {
|
|
258
258
|
console.log(chalk.dim(`Reading file: ${indexFilePath}`));
|
|
259
259
|
const content = await fs.readFile(indexFilePath, 'utf-8');
|
|
260
260
|
const ast = recast.parse(content, {
|
|
@@ -263,6 +263,7 @@ export async function injectLoginComponent(indexFilePath, componentPath) {
|
|
|
263
263
|
|
|
264
264
|
let updated = false;
|
|
265
265
|
let injectionLine = null;
|
|
266
|
+
let targetProperty = null;
|
|
266
267
|
|
|
267
268
|
recast.visit(ast, {
|
|
268
269
|
visitNewExpression(path) {
|
|
@@ -293,20 +294,23 @@ export async function injectLoginComponent(indexFilePath, componentPath) {
|
|
|
293
294
|
const loginPageInjections = getOrCreateProp(customization, 'loginPageInjections');
|
|
294
295
|
if (!n.ObjectExpression.check(loginPageInjections)) return false;
|
|
295
296
|
|
|
296
|
-
|
|
297
|
-
|
|
297
|
+
// Determine target property based on injection type
|
|
298
|
+
targetProperty = injectionType === 'beforeLogin' ? 'panelHeader' : 'underInputs';
|
|
299
|
+
|
|
300
|
+
let targetProp = loginPageInjections.properties.find(
|
|
301
|
+
p => n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === targetProperty
|
|
298
302
|
);
|
|
299
303
|
|
|
300
|
-
if (
|
|
301
|
-
const currentVal =
|
|
302
|
-
injectionLine =
|
|
304
|
+
if (targetProp) {
|
|
305
|
+
const currentVal = targetProp.value;
|
|
306
|
+
injectionLine = targetProp.loc?.start.line ?? null;
|
|
303
307
|
if (n.StringLiteral.check(currentVal)) {
|
|
304
308
|
if (currentVal.value !== componentPath) {
|
|
305
|
-
|
|
309
|
+
targetProp.value = b.arrayExpression([
|
|
306
310
|
b.stringLiteral(currentVal.value),
|
|
307
311
|
b.stringLiteral(componentPath),
|
|
308
312
|
]);
|
|
309
|
-
console.log(chalk.dim(`Converted '
|
|
313
|
+
console.log(chalk.dim(`Converted '${targetProperty}' to array with existing + new path.`));
|
|
310
314
|
} else {
|
|
311
315
|
console.log(chalk.dim(`Component path already present as string. Skipping.`));
|
|
312
316
|
}
|
|
@@ -316,26 +320,26 @@ export async function injectLoginComponent(indexFilePath, componentPath) {
|
|
|
316
320
|
);
|
|
317
321
|
if (!exists) {
|
|
318
322
|
currentVal.elements.push(b.stringLiteral(componentPath));
|
|
319
|
-
console.log(chalk.dim(`Appended new component path to existing '
|
|
323
|
+
console.log(chalk.dim(`Appended new component path to existing '${targetProperty}' array.`));
|
|
320
324
|
} else {
|
|
321
325
|
console.log(chalk.dim(`Component path already present in array. Skipping.`));
|
|
322
326
|
}
|
|
323
327
|
} else {
|
|
324
|
-
console.warn(chalk.yellow(`⚠️ '
|
|
328
|
+
console.warn(chalk.yellow(`⚠️ '${targetProperty}' is not a string or array. Skipping.`));
|
|
325
329
|
return false;
|
|
326
330
|
}
|
|
327
331
|
} else {
|
|
328
332
|
const newProperty = b.objectProperty(
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
333
|
+
b.identifier(targetProperty),
|
|
334
|
+
b.stringLiteral(componentPath)
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
if (newProperty.loc) {
|
|
338
|
+
console.log(chalk.dim(`Adding '${targetProperty}' at line: ${newProperty.loc.start.line}`));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
loginPageInjections.properties.push(newProperty);
|
|
342
|
+
console.log(chalk.dim(`Added '${targetProperty}': ${componentPath}`));
|
|
339
343
|
}
|
|
340
344
|
|
|
341
345
|
updated = true;
|
|
@@ -353,7 +357,7 @@ export async function injectLoginComponent(indexFilePath, componentPath) {
|
|
|
353
357
|
await fs.writeFile(indexFilePath, outputCode, 'utf-8');
|
|
354
358
|
console.log(
|
|
355
359
|
chalk.green(
|
|
356
|
-
`✅ Successfully updated
|
|
360
|
+
`✅ Successfully updated login ${targetProperty} injection in: ${indexFilePath}` +
|
|
357
361
|
(injectionLine !== null ? `:${injectionLine}` : '')
|
|
358
362
|
)
|
|
359
363
|
);
|
|
@@ -129,7 +129,7 @@ export async function generateLoginOrGlobalComponentFile(componentFileName, inje
|
|
|
129
129
|
const __filename = fileURLToPath(import.meta.url);
|
|
130
130
|
const __dirname = path.dirname(__filename);
|
|
131
131
|
let templatePath;
|
|
132
|
-
if (injectionType === 'afterLogin') {
|
|
132
|
+
if (injectionType === 'afterLogin' || injectionType === 'beforeLogin') {
|
|
133
133
|
templatePath = path.join(__dirname, 'templates', 'login', `${injectionType}.vue.hbs`);
|
|
134
134
|
} else {
|
|
135
135
|
templatePath = path.join(__dirname, 'templates', 'global', `${injectionType}.vue.hbs`);
|
|
@@ -188,7 +188,11 @@ async function handleCrudPageInjectionCreation(config, resources) {
|
|
|
188
188
|
const injectionPosition = await select({
|
|
189
189
|
message: 'Where exactly do you want to inject the component?',
|
|
190
190
|
choices: [
|
|
191
|
+
...(crudType === 'create' || crudType === 'edit'
|
|
192
|
+
? [{ name: '💾 Save button on create/edit page', value: 'saveButton' }, new Separator()]
|
|
193
|
+
: []),
|
|
191
194
|
{ name: '⬆️ Before Breadcrumbs', value: 'beforeBreadcrumbs' },
|
|
195
|
+
{ name: '➡️ Before Action Buttons', value: 'beforeActionButtons' },
|
|
192
196
|
{ name: '⬇️ After Breadcrumbs', value: 'afterBreadcrumbs' },
|
|
193
197
|
{ name: '📄 After Page', value: 'bottom' },
|
|
194
198
|
{ name: '⋯ threeDotsDropdownItems', value: 'threeDotsDropdownItems' },
|
|
@@ -206,13 +210,15 @@ async function handleCrudPageInjectionCreation(config, resources) {
|
|
|
206
210
|
},
|
|
207
211
|
});
|
|
208
212
|
|
|
209
|
-
const isThin =
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
213
|
+
const isThin = crudType === 'list'
|
|
214
|
+
? await select({
|
|
215
|
+
message: 'Will this component be thin enough to fit on the same page with list (so list will still shrink)?',
|
|
216
|
+
choices: [
|
|
217
|
+
{ name: 'Yes', value: true },
|
|
218
|
+
{ name: 'No', value: false },
|
|
219
|
+
],
|
|
220
|
+
})
|
|
221
|
+
: false;
|
|
216
222
|
const formattedAdditionalName = additionalName
|
|
217
223
|
? additionalName[0].toUpperCase() + additionalName.slice(1)
|
|
218
224
|
: '';
|
|
@@ -257,6 +263,7 @@ async function handleLoginPageInjectionCreation(config) {
|
|
|
257
263
|
const injectionType = await select({
|
|
258
264
|
message: 'Select injection type:',
|
|
259
265
|
choices: [
|
|
266
|
+
{ name: 'Before Login and password inputs', value: 'beforeLogin' },
|
|
260
267
|
{ name: 'After Login and password inputs', value: 'afterLogin' },
|
|
261
268
|
{ name: '🔙 BACK', value: '__BACK__' },
|
|
262
269
|
],
|
|
@@ -287,7 +294,7 @@ async function handleLoginPageInjectionCreation(config) {
|
|
|
287
294
|
console.log(chalk.dim(`Component generation successful: ${absoluteComponentPath}`));
|
|
288
295
|
const configFilePath = path.resolve(process.cwd(), 'index.ts');
|
|
289
296
|
console.log(chalk.dim(`Injecting component: ${configFilePath}, ${componentFileName}`));
|
|
290
|
-
await injectLoginComponent(configFilePath, `@@/${componentFileName}
|
|
297
|
+
await injectLoginComponent(configFilePath, `@@/${componentFileName}`, injectionType);
|
|
291
298
|
|
|
292
299
|
console.log(
|
|
293
300
|
chalk.bold.greenBright('You can now open the component in your IDE:'),
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex items-center p-4 md:p-5 gap-2">
|
|
3
|
+
<button
|
|
4
|
+
type="button"
|
|
5
|
+
@click="handleClick"
|
|
6
|
+
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-4 py-2.5 text-center dark:bg-blue-500 dark:hover:bg-blue-600 dark:focus:ring-blue-800"
|
|
7
|
+
>
|
|
8
|
+
{{ '{{' }} $t('Example') {{ '}}' }}
|
|
9
|
+
</button>
|
|
10
|
+
</div>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script setup lang="ts">
|
|
14
|
+
import { onMounted } from 'vue';
|
|
15
|
+
import { useI18n } from 'vue-i18n';
|
|
16
|
+
import adminforth from '@/adminforth';
|
|
17
|
+
import { AdminForthResourceCommon, AdminUser } from "@/types/Common";
|
|
18
|
+
|
|
19
|
+
const { t } = useI18n();
|
|
20
|
+
|
|
21
|
+
const props = defineProps<{
|
|
22
|
+
record: any
|
|
23
|
+
resource: AdminForthResourceCommon
|
|
24
|
+
adminUser: AdminUser
|
|
25
|
+
meta?: any
|
|
26
|
+
}>();
|
|
27
|
+
|
|
28
|
+
onMounted(() => {
|
|
29
|
+
// Logic on mount if needed
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
function handleClick() {
|
|
33
|
+
adminforth.alert({
|
|
34
|
+
message: t('Confirmed'),
|
|
35
|
+
variant: 'success',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
</script>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button
|
|
3
|
+
class="px-4 py-2 border border-blue-500 text-blue-600 rounded hover:bg-blue-50 disabled:opacity-50"
|
|
4
|
+
:disabled="props.disabled || props.saving || !props.isValid"
|
|
5
|
+
@click="props.saveRecord()"
|
|
6
|
+
>
|
|
7
|
+
<span v-if="props.saving">Saving…</span>
|
|
8
|
+
<span v-else>Save</span>
|
|
9
|
+
</button>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script setup lang="ts">
|
|
13
|
+
|
|
14
|
+
const props = defineProps<{
|
|
15
|
+
record: any
|
|
16
|
+
resource: any
|
|
17
|
+
adminUser: any
|
|
18
|
+
meta: any
|
|
19
|
+
saving: boolean
|
|
20
|
+
validating: boolean
|
|
21
|
+
isValid: boolean
|
|
22
|
+
disabled: boolean
|
|
23
|
+
saveRecord: () => Promise<void>
|
|
24
|
+
}>();
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<style scoped>
|
|
28
|
+
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="text-center text-gray-500 text-sm mt-4">
|
|
3
|
+
Login Page Text
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup>
|
|
8
|
+
import { onMounted } from 'vue';
|
|
9
|
+
|
|
10
|
+
const props = defineProps<{
|
|
11
|
+
reason: String
|
|
12
|
+
}>();
|
|
13
|
+
|
|
14
|
+
onMounted(() => {
|
|
15
|
+
// Logic on mount if needed
|
|
16
|
+
});
|
|
17
|
+
</script>
|
|
18
|
+
|
|
@@ -3,15 +3,12 @@
|
|
|
3
3
|
"baseUrl": ".", // This should point to your project root
|
|
4
4
|
"paths": {
|
|
5
5
|
"@/*": [
|
|
6
|
-
|
|
7
|
-
"../../../spa/src/*"
|
|
6
|
+
"../node_modules/adminforth/dist/spa/src/*"
|
|
8
7
|
],
|
|
9
8
|
"*": [
|
|
10
|
-
|
|
11
|
-
"../../../spa/node_modules/*"
|
|
9
|
+
"../node_modules/adminforth/dist/spa/node_modules/*"
|
|
12
10
|
],
|
|
13
11
|
"@@/*": [
|
|
14
|
-
// "node_modules/adminforth/dist/spa/src/*"
|
|
15
12
|
"."
|
|
16
13
|
]
|
|
17
14
|
}
|
|
@@ -2,6 +2,8 @@ import fs from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { toPascalCase, mapToTypeScriptType, getInstance } from "./utils.js";
|
|
4
4
|
import dotenv from "dotenv";
|
|
5
|
+
import { callTsProxy } from "./callTsProxy.js";
|
|
6
|
+
import { getAdminInstance } from "../commands/createCustomComponent/configLoader.js";
|
|
5
7
|
|
|
6
8
|
const envFileArg = process.argv.find((arg) => arg.startsWith("--env-file="));
|
|
7
9
|
const envFilePath = envFileArg ? envFileArg.split("=")[1] : ".env";
|
|
@@ -23,39 +25,45 @@ async function generateModels() {
|
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
let modelContent = "// Generated model file\n\n";
|
|
26
|
-
const files = fs.readdirSync(currentDirectory);
|
|
27
28
|
let instanceFound = false;
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
30
|
+
const { adminInstance, configPath, configFileName } = await getAdminInstance();
|
|
31
|
+
if (adminInstance) {
|
|
32
|
+
await adminInstance.discoverDatabases();
|
|
33
|
+
instanceFound = true;
|
|
34
|
+
for (const resource of adminInstance.config.resources) {
|
|
35
|
+
if (resource.columns) {
|
|
36
|
+
const typeName = toPascalCase(resource.resourceId);
|
|
37
|
+
const tsCode = `
|
|
38
|
+
export async function exec() {
|
|
39
|
+
const columns = ${JSON.stringify(resource.columns)};
|
|
40
|
+
const typeName = "${typeName}";
|
|
41
|
+
function mapToTypeScriptType(type) {
|
|
42
|
+
const map = { "integer": "number", "varchar": "string", "boolean": "boolean", "date": "string", "datetime": "string", "decimal": "number", "float": "number", "json": "Record<string, any>", "text": "string", "string": "string", "time": "string" };
|
|
43
|
+
return map[type] || "any";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let typeStr = \`export type \${typeName} = {\\n\`;
|
|
47
|
+
for (const col of columns) {
|
|
48
|
+
if (col.name && col.type) {
|
|
49
|
+
typeStr += \` \${col.name}: \${mapToTypeScriptType(col.type)};\\n\`;
|
|
45
50
|
}
|
|
46
|
-
}
|
|
47
|
-
|
|
51
|
+
}
|
|
52
|
+
typeStr += "}\\n\\n";
|
|
53
|
+
return typeStr;
|
|
48
54
|
}
|
|
49
|
-
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
const result = await callTsProxy(tsCode);
|
|
58
|
+
modelContent += result;
|
|
50
59
|
}
|
|
51
|
-
}
|
|
60
|
+
};
|
|
52
61
|
}
|
|
53
62
|
|
|
54
63
|
if (!instanceFound) {
|
|
55
64
|
console.error("Error: No valid instance found to generate models.");
|
|
56
65
|
return;
|
|
57
66
|
}
|
|
58
|
-
|
|
59
67
|
fs.writeFileSync(modelFilePath, modelContent, "utf-8");
|
|
60
68
|
console.log(`Generated TypeScript model file: ${modelFilePath}`);
|
|
61
69
|
return true;
|