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.
Files changed (206) hide show
  1. package/commands/callTsProxy.js +14 -4
  2. package/commands/cli.js +12 -4
  3. package/commands/createApp/templates/api.ts.hbs +10 -0
  4. package/commands/createApp/templates/custom/tsconfig.json.hbs +2 -3
  5. package/commands/createApp/templates/index.ts.hbs +13 -5
  6. package/commands/createApp/templates/package.json.hbs +1 -1
  7. package/commands/createApp/utils.js +45 -7
  8. package/commands/createCustomComponent/configLoader.js +17 -4
  9. package/commands/createCustomComponent/configUpdater.js +25 -21
  10. package/commands/createCustomComponent/fileGenerator.js +1 -1
  11. package/commands/createCustomComponent/main.js +15 -8
  12. package/commands/createCustomComponent/templates/customCrud/beforeActionButtons.vue.hbs +38 -0
  13. package/commands/createCustomComponent/templates/customCrud/saveButton.vue.hbs +28 -0
  14. package/commands/createCustomComponent/templates/login/beforeLogin.vue.hbs +18 -0
  15. package/commands/createPlugin/templates/custom/tsconfig.json.hbs +2 -5
  16. package/commands/createPlugin/templates/package.json.hbs +1 -1
  17. package/commands/generateModels.js +30 -22
  18. package/dist/auth.d.ts +9 -1
  19. package/dist/auth.d.ts.map +1 -1
  20. package/dist/auth.js +21 -2
  21. package/dist/auth.js.map +1 -1
  22. package/dist/dataConnectors/baseConnector.d.ts +1 -1
  23. package/dist/dataConnectors/baseConnector.d.ts.map +1 -1
  24. package/dist/dataConnectors/baseConnector.js +69 -17
  25. package/dist/dataConnectors/baseConnector.js.map +1 -1
  26. package/dist/dataConnectors/clickhouse.d.ts.map +1 -1
  27. package/dist/dataConnectors/clickhouse.js +15 -0
  28. package/dist/dataConnectors/clickhouse.js.map +1 -1
  29. package/dist/dataConnectors/mongo.d.ts.map +1 -1
  30. package/dist/dataConnectors/mongo.js +50 -15
  31. package/dist/dataConnectors/mongo.js.map +1 -1
  32. package/dist/dataConnectors/mysql.d.ts.map +1 -1
  33. package/dist/dataConnectors/mysql.js +11 -0
  34. package/dist/dataConnectors/mysql.js.map +1 -1
  35. package/dist/dataConnectors/postgres.d.ts.map +1 -1
  36. package/dist/dataConnectors/postgres.js +43 -14
  37. package/dist/dataConnectors/postgres.js.map +1 -1
  38. package/dist/dataConnectors/sqlite.d.ts.map +1 -1
  39. package/dist/dataConnectors/sqlite.js +11 -0
  40. package/dist/dataConnectors/sqlite.js.map +1 -1
  41. package/dist/index.d.ts +12 -2
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +42 -21
  44. package/dist/index.js.map +1 -1
  45. package/dist/modules/codeInjector.d.ts +2 -0
  46. package/dist/modules/codeInjector.d.ts.map +1 -1
  47. package/dist/modules/codeInjector.js +67 -12
  48. package/dist/modules/codeInjector.js.map +1 -1
  49. package/dist/modules/configValidator.d.ts +6 -0
  50. package/dist/modules/configValidator.d.ts.map +1 -1
  51. package/dist/modules/configValidator.js +203 -25
  52. package/dist/modules/configValidator.js.map +1 -1
  53. package/dist/modules/restApi.d.ts +1 -1
  54. package/dist/modules/restApi.d.ts.map +1 -1
  55. package/dist/modules/restApi.js +172 -31
  56. package/dist/modules/restApi.js.map +1 -1
  57. package/dist/modules/styles.d.ts +499 -13
  58. package/dist/modules/styles.d.ts.map +1 -1
  59. package/dist/modules/styles.js +555 -31
  60. package/dist/modules/styles.js.map +1 -1
  61. package/dist/modules/utils.d.ts +7 -15
  62. package/dist/modules/utils.d.ts.map +1 -1
  63. package/dist/modules/utils.js +45 -68
  64. package/dist/modules/utils.js.map +1 -1
  65. package/dist/servers/express.d.ts +5 -0
  66. package/dist/servers/express.d.ts.map +1 -1
  67. package/dist/servers/express.js +40 -1
  68. package/dist/servers/express.js.map +1 -1
  69. package/dist/spa/index.html +1 -1
  70. package/dist/spa/package-lock.json +1208 -708
  71. package/dist/spa/package.json +34 -34
  72. package/dist/spa/src/App.vue +58 -173
  73. package/dist/spa/src/adminforth.ts +42 -18
  74. package/dist/spa/src/afcl/AreaChart.vue +0 -1
  75. package/dist/spa/src/afcl/BarChart.vue +2 -2
  76. package/dist/spa/src/afcl/Button.vue +6 -6
  77. package/dist/spa/src/afcl/ButtonGroup.vue +91 -0
  78. package/dist/spa/src/afcl/Card.vue +25 -0
  79. package/dist/spa/src/afcl/Checkbox.vue +21 -13
  80. package/dist/spa/src/afcl/CountryFlag.vue +4 -1
  81. package/dist/spa/src/{components/CustomDatePicker.vue → afcl/DatePicker.vue} +95 -9
  82. package/dist/spa/src/afcl/Dialog.vue +47 -27
  83. package/dist/spa/src/afcl/Dropzone.vue +103 -44
  84. package/dist/spa/src/afcl/Input.vue +18 -8
  85. package/dist/spa/src/afcl/JsonViewer.vue +25 -0
  86. package/dist/spa/src/afcl/Link.vue +1 -1
  87. package/dist/spa/src/afcl/LinkButton.vue +3 -3
  88. package/dist/spa/src/afcl/PieChart.vue +5 -5
  89. package/dist/spa/src/afcl/ProgressBar.vue +7 -7
  90. package/dist/spa/src/afcl/Select.vue +82 -34
  91. package/dist/spa/src/afcl/Skeleton.vue +6 -6
  92. package/dist/spa/src/afcl/Table.vue +309 -73
  93. package/dist/spa/src/afcl/Textarea.vue +31 -0
  94. package/dist/spa/src/afcl/Toggle.vue +32 -0
  95. package/dist/spa/src/afcl/Tooltip.vue +28 -18
  96. package/dist/spa/src/afcl/VerticalTabs.vue +16 -7
  97. package/dist/spa/src/afcl/index.ts +6 -3
  98. package/dist/spa/src/components/AcceptModal.vue +48 -14
  99. package/dist/spa/src/components/Breadcrumbs.vue +5 -5
  100. package/dist/spa/src/components/CallActionWrapper.vue +15 -0
  101. package/dist/spa/src/components/ColumnValueInput.vue +38 -18
  102. package/dist/spa/src/components/ColumnValueInputWrapper.vue +4 -3
  103. package/dist/spa/src/components/CustomDateRangePicker.vue +9 -8
  104. package/dist/spa/src/components/CustomRangePicker.vue +37 -21
  105. package/dist/spa/src/components/ErrorMessage.vue +21 -0
  106. package/dist/spa/src/components/Filters.vue +194 -131
  107. package/dist/spa/src/components/GroupsTable.vue +9 -8
  108. package/dist/spa/src/components/MenuLink.vue +90 -23
  109. package/dist/spa/src/components/ResourceForm.vue +94 -51
  110. package/dist/spa/src/components/ResourceListTable.vue +120 -90
  111. package/dist/spa/src/components/ResourceListTableVirtual.vue +118 -84
  112. package/dist/spa/src/components/ShowTable.vue +21 -15
  113. package/dist/spa/src/components/Sidebar.vue +470 -0
  114. package/dist/spa/src/components/SingleSkeletLoader.vue +6 -6
  115. package/dist/spa/src/components/SkeleteLoader.vue +3 -3
  116. package/dist/spa/src/components/ThreeDotsMenu.vue +84 -15
  117. package/dist/spa/src/components/Toast.vue +40 -29
  118. package/dist/spa/src/components/UserMenuSettingsButton.vue +69 -0
  119. package/dist/spa/src/components/ValueRenderer.vue +44 -17
  120. package/dist/spa/src/controls/BoolToggle.vue +34 -0
  121. package/dist/spa/src/i18n.ts +5 -3
  122. package/dist/spa/src/main.ts +1 -1
  123. package/dist/spa/src/renderers/CompactField.vue +1 -1
  124. package/dist/spa/src/renderers/CompactUUID.vue +1 -1
  125. package/dist/spa/src/router/index.ts +8 -0
  126. package/dist/spa/src/shims-vue.d.ts +5 -0
  127. package/dist/spa/src/spa_types/core.ts +13 -1
  128. package/dist/spa/src/stores/core.ts +13 -1
  129. package/dist/spa/src/stores/filters.ts +33 -2
  130. package/dist/spa/src/stores/modal.ts +6 -1
  131. package/dist/spa/src/stores/toast.ts +22 -3
  132. package/dist/spa/src/types/Back.ts +164 -23
  133. package/dist/spa/src/types/Common.ts +92 -32
  134. package/dist/spa/src/types/FrontendAPI.ts +31 -5
  135. package/dist/spa/src/types/adapters/CaptchaAdapter.ts +34 -0
  136. package/dist/spa/src/types/adapters/CompletionAdapter.ts +25 -0
  137. package/dist/spa/src/types/adapters/EmailAdapter.ts +27 -0
  138. package/dist/spa/src/types/adapters/ImageGenerationAdapter.ts +50 -0
  139. package/dist/spa/src/types/adapters/ImageVisionAdapter.ts +30 -0
  140. package/dist/spa/src/types/adapters/KeyValueAdapter.ts +16 -0
  141. package/dist/spa/src/types/adapters/OAuth2Adapter.ts +34 -0
  142. package/dist/spa/src/types/adapters/StorageAdapter.ts +73 -0
  143. package/dist/spa/src/types/adapters/index.ts +8 -0
  144. package/dist/spa/src/utils.ts +291 -11
  145. package/dist/spa/src/views/CreateView.vue +63 -21
  146. package/dist/spa/src/views/EditView.vue +55 -22
  147. package/dist/spa/src/views/ListView.vue +144 -87
  148. package/dist/spa/src/views/LoginView.vue +60 -55
  149. package/dist/spa/src/views/ResourceParent.vue +2 -2
  150. package/dist/spa/src/views/SettingsView.vue +121 -0
  151. package/dist/spa/src/views/ShowView.vue +83 -53
  152. package/dist/spa/src/websocket.ts +6 -1
  153. package/dist/spa/tsconfig.app.json +1 -1
  154. package/dist/spa/vite.config.ts +45 -2
  155. package/dist/types/Back.d.ts +147 -14
  156. package/dist/types/Back.d.ts.map +1 -1
  157. package/dist/types/Back.js +15 -0
  158. package/dist/types/Back.js.map +1 -1
  159. package/dist/types/Common.d.ts +107 -29
  160. package/dist/types/Common.d.ts.map +1 -1
  161. package/dist/types/Common.js.map +1 -1
  162. package/dist/types/FrontendAPI.d.ts +31 -3
  163. package/dist/types/FrontendAPI.d.ts.map +1 -1
  164. package/dist/types/FrontendAPI.js.map +1 -1
  165. package/dist/types/adapters/CaptchaAdapter.d.ts +30 -0
  166. package/dist/types/adapters/CaptchaAdapter.d.ts.map +1 -0
  167. package/dist/types/adapters/CaptchaAdapter.js +5 -0
  168. package/dist/types/adapters/CaptchaAdapter.js.map +1 -0
  169. package/dist/types/adapters/CompletionAdapter.d.ts +20 -0
  170. package/dist/types/adapters/CompletionAdapter.d.ts.map +1 -0
  171. package/dist/types/adapters/CompletionAdapter.js +2 -0
  172. package/dist/types/adapters/CompletionAdapter.js.map +1 -0
  173. package/dist/types/adapters/EmailAdapter.d.ts +20 -0
  174. package/dist/types/adapters/EmailAdapter.d.ts.map +1 -0
  175. package/dist/types/adapters/EmailAdapter.js +2 -0
  176. package/dist/types/adapters/EmailAdapter.js.map +1 -0
  177. package/dist/types/adapters/ImageGenerationAdapter.d.ts +37 -0
  178. package/dist/types/adapters/ImageGenerationAdapter.d.ts.map +1 -0
  179. package/dist/types/adapters/ImageGenerationAdapter.js +2 -0
  180. package/dist/types/adapters/ImageGenerationAdapter.js.map +1 -0
  181. package/dist/types/adapters/ImageVisionAdapter.d.ts +25 -0
  182. package/dist/types/adapters/ImageVisionAdapter.d.ts.map +1 -0
  183. package/dist/types/adapters/ImageVisionAdapter.js +2 -0
  184. package/dist/types/adapters/ImageVisionAdapter.js.map +1 -0
  185. package/dist/types/adapters/KeyValueAdapter.d.ts +10 -0
  186. package/dist/types/adapters/KeyValueAdapter.d.ts.map +1 -0
  187. package/dist/types/adapters/KeyValueAdapter.js +2 -0
  188. package/dist/types/adapters/KeyValueAdapter.js.map +1 -0
  189. package/dist/types/adapters/OAuth2Adapter.d.ts +32 -0
  190. package/dist/types/adapters/OAuth2Adapter.d.ts.map +1 -0
  191. package/dist/types/adapters/OAuth2Adapter.js +2 -0
  192. package/dist/types/adapters/OAuth2Adapter.js.map +1 -0
  193. package/dist/types/adapters/StorageAdapter.d.ts +63 -0
  194. package/dist/types/adapters/StorageAdapter.d.ts.map +1 -0
  195. package/dist/types/adapters/StorageAdapter.js +2 -0
  196. package/dist/types/adapters/StorageAdapter.js.map +1 -0
  197. package/dist/types/adapters/index.d.ts +9 -0
  198. package/dist/types/adapters/index.d.ts.map +1 -0
  199. package/dist/types/adapters/index.js +2 -0
  200. package/dist/types/adapters/index.js.map +1 -0
  201. package/package.json +4 -2
  202. package/dist/spa/src/types/Adapters.ts +0 -213
  203. package/dist/types/Adapters.d.ts +0 -168
  204. package/dist/types/Adapters.d.ts.map +0 -1
  205. package/dist/types/Adapters.js +0 -2
  206. package/dist/types/Adapters.js.map +0 -1
@@ -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
- path.join(currentFileFolder, "proxy.ts")
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.replace("file://", "");
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 showVersion() {
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
- "@/": "../node_modules/adminforth/dist/spa/src/",
6
- "": "../node_modules/adminforth/dist/spa/node_modules/",
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
- showBrandNameInSidebar: true,
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
- const currentFilePath = fileURLToPath(import.meta.url);
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(() => {
@@ -22,7 +22,7 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "@dotenvx/dotenvx": "^1.34.0",
25
- "adminforth": "latest",
25
+ "adminforth": "{{adminforthVersion}}",
26
26
  "express": "latest-4"
27
27
  },
28
28
  "devDependencies": {
@@ -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: { appName },
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
- const npmCmd = isWindows ? 'npm.cmd' : 'npm';
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
- const res = await Promise.all([
292
- await execAsync(`${npmCmd} install`, { cwd, env: { PATH: process.env.PATH } }),
293
- await execAsync(`${npmCmd} install`, { cwd: customDir, env: { PATH: process.env.PATH } }),
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 loadAdminForthConfig() {
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
- process.exit(1);
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
- let underInputsProp = loginPageInjections.properties.find(
297
- p => n.ObjectProperty.check(p) && n.Identifier.check(p.key) && p.key.name === 'underInputs'
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 (underInputsProp) {
301
- const currentVal = underInputsProp.value;
302
- injectionLine = underInputsProp.loc?.start.line ?? null;
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
- underInputsProp.value = b.arrayExpression([
309
+ targetProp.value = b.arrayExpression([
306
310
  b.stringLiteral(currentVal.value),
307
311
  b.stringLiteral(componentPath),
308
312
  ]);
309
- console.log(chalk.dim(`Converted 'underInputs' to array with existing + new path.`));
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 'underInputs' array.`));
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(`⚠️ 'underInputs' is not a string or array. Skipping.`));
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
- b.identifier('underInputs'),
330
- b.stringLiteral(componentPath)
331
- );
332
-
333
- if (newProperty.loc) {
334
- console.log(chalk.dim(`Adding 'underInputs' at line: ${newProperty.loc.start.line}`));
335
- }
336
-
337
- loginPageInjections.properties.push(newProperty);
338
- console.log(chalk.dim(`Added 'underInputs': ${componentPath}`));
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 CRUD injection in resource file: ${indexFilePath}` +
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 = await select({
210
- message: 'Will this component be thin enough to fit on the same page with list (so list will still shrink)?',
211
- choices: [
212
- { name: 'Yes', value: true },
213
- { name: 'No', value: false },
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
- // "node_modules/adminforth/dist/spa/src/*"
7
- "../../../spa/src/*"
6
+ "../node_modules/adminforth/dist/spa/src/*"
8
7
  ],
9
8
  "*": [
10
- // "node_modules/adminforth/dist/spa/node_modules/*"
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
  }
@@ -5,7 +5,7 @@
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
7
7
  "scripts": {
8
- "build": "tsc && rsync -av --exclude 'node_modules' custom dist/ && npm version patch"
8
+ "build": "tsc && rsync -av --exclude 'node_modules' custom dist/"
9
9
  },
10
10
  "keywords": [],
11
11
  "author": "",
@@ -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
- for (const file of files) {
30
- if (file.endsWith(".js") || file.endsWith(".ts")) {
31
- const instance = await getInstance(file, currentDirectory);
32
- if (instance) {
33
- await instance.discoverDatabases();
34
- instanceFound = true;
35
- instance.config.resources.forEach((resource) => {
36
- if (resource.columns) {
37
- modelContent += `export type ${toPascalCase(
38
- resource.resourceId
39
- )} = {\n`;
40
- resource.columns.forEach((column) => {
41
- if (column.name && column.type) {
42
- modelContent += ` ${column.name}: ${mapToTypeScriptType(
43
- column.type
44
- )};\n`;
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
- modelContent += `}\n\n`;
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;