adminforth 1.3.54-next.3 β†’ 1.3.54-next.30

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 (135) hide show
  1. package/dist/auth.js +42 -56
  2. package/dist/auth.js.map +1 -0
  3. package/dist/basePlugin.js +1 -0
  4. package/dist/basePlugin.js.map +1 -0
  5. package/dist/dataConnectors/baseConnector.js +108 -122
  6. package/dist/dataConnectors/baseConnector.js.map +1 -0
  7. package/dist/dataConnectors/clickhouse.js +132 -150
  8. package/dist/dataConnectors/clickhouse.js.map +1 -0
  9. package/dist/dataConnectors/mongo.js +75 -101
  10. package/dist/dataConnectors/mongo.js.map +1 -0
  11. package/dist/dataConnectors/postgres.js +124 -143
  12. package/dist/dataConnectors/postgres.js.map +1 -0
  13. package/dist/dataConnectors/sqlite.js +113 -130
  14. package/dist/dataConnectors/sqlite.js.map +1 -0
  15. package/dist/index.js +197 -217
  16. package/dist/index.js.map +1 -0
  17. package/dist/modules/codeInjector.js +480 -486
  18. package/dist/modules/codeInjector.js.map +1 -0
  19. package/dist/modules/configValidator.js +31 -22
  20. package/dist/modules/configValidator.js.map +1 -0
  21. package/dist/modules/operationalResource.js +50 -70
  22. package/dist/modules/operationalResource.js.map +1 -0
  23. package/dist/modules/restApi.js +104 -116
  24. package/dist/modules/restApi.js.map +1 -0
  25. package/dist/modules/styleGenerator.js +1 -0
  26. package/dist/modules/styleGenerator.js.map +1 -0
  27. package/dist/modules/styles.js +1 -0
  28. package/dist/modules/styles.js.map +1 -0
  29. package/dist/modules/utils.js +1 -0
  30. package/dist/modules/utils.js.map +1 -0
  31. package/dist/plugins/audit-log/types.js +2 -0
  32. package/dist/plugins/audit-log/types.js.map +1 -0
  33. package/dist/plugins/chat-gpt/types.js +2 -0
  34. package/dist/plugins/chat-gpt/types.js.map +1 -0
  35. package/dist/plugins/email-password-reset/types.js +2 -0
  36. package/dist/plugins/email-password-reset/types.js.map +1 -0
  37. package/dist/plugins/foreign-inline-list/types.js +2 -0
  38. package/dist/plugins/foreign-inline-list/types.js.map +1 -0
  39. package/dist/plugins/import-export/types.js +2 -0
  40. package/dist/plugins/import-export/types.js.map +1 -0
  41. package/dist/plugins/rich-editor/custom/async-queue.js +29 -0
  42. package/dist/plugins/rich-editor/custom/async-queue.js.map +1 -0
  43. package/dist/plugins/rich-editor/dist/async-queue.js +41 -0
  44. package/dist/plugins/rich-editor/dist/custom/async-queue.js +29 -0
  45. package/dist/plugins/rich-editor/dist/custom/async-queue.js.map +1 -0
  46. package/dist/plugins/rich-editor/types.js +16 -0
  47. package/dist/plugins/rich-editor/types.js.map +1 -0
  48. package/dist/plugins/two-factors-auth/types.js +2 -0
  49. package/dist/plugins/two-factors-auth/types.js.map +1 -0
  50. package/dist/plugins/upload/types.js +2 -0
  51. package/dist/plugins/upload/types.js.map +1 -0
  52. package/dist/servers/express.js +30 -42
  53. package/dist/servers/express.js.map +1 -0
  54. package/dist/types/AdminForthConfig.js +1 -0
  55. package/dist/types/AdminForthConfig.js.map +1 -0
  56. package/dist/types/FrontendAPI.js +1 -0
  57. package/dist/types/FrontendAPI.js.map +1 -0
  58. package/package.json +7 -4
  59. package/auth.ts +0 -140
  60. package/basePlugin.ts +0 -70
  61. package/dataConnectors/baseConnector.ts +0 -216
  62. package/dataConnectors/clickhouse.ts +0 -341
  63. package/dataConnectors/mongo.ts +0 -202
  64. package/dataConnectors/postgres.ts +0 -306
  65. package/dataConnectors/sqlite.ts +0 -254
  66. package/index.ts +0 -428
  67. package/modules/codeInjector.ts +0 -736
  68. package/modules/configValidator.ts +0 -571
  69. package/modules/operationalResource.ts +0 -98
  70. package/modules/restApi.ts +0 -718
  71. package/modules/styleGenerator.ts +0 -55
  72. package/modules/styles.ts +0 -126
  73. package/modules/utils.ts +0 -472
  74. package/servers/express.ts +0 -259
  75. package/spa/.eslintrc.cjs +0 -14
  76. package/spa/README.md +0 -39
  77. package/spa/env.d.ts +0 -1
  78. package/spa/index.html +0 -23
  79. package/spa/package-lock.json +0 -4573
  80. package/spa/package.json +0 -49
  81. package/spa/postcss.config.js +0 -6
  82. package/spa/public/assets/favicon.png +0 -0
  83. package/spa/src/App.vue +0 -418
  84. package/spa/src/assets/base.css +0 -2
  85. package/spa/src/assets/logo.svg +0 -19
  86. package/spa/src/components/AcceptModal.vue +0 -45
  87. package/spa/src/components/Breadcrumbs.vue +0 -41
  88. package/spa/src/components/BreadcrumbsWithButtons.vue +0 -26
  89. package/spa/src/components/CustomDatePicker.vue +0 -176
  90. package/spa/src/components/CustomDateRangePicker.vue +0 -218
  91. package/spa/src/components/CustomRangePicker.vue +0 -156
  92. package/spa/src/components/Dropdown.vue +0 -168
  93. package/spa/src/components/Filters.vue +0 -222
  94. package/spa/src/components/HelloWorld.vue +0 -17
  95. package/spa/src/components/MenuLink.vue +0 -27
  96. package/spa/src/components/ResourceForm.vue +0 -290
  97. package/spa/src/components/ResourceListTable.vue +0 -460
  98. package/spa/src/components/SingleSkeletLoader.vue +0 -13
  99. package/spa/src/components/SkeleteLoader.vue +0 -23
  100. package/spa/src/components/ThreeDotsMenu.vue +0 -43
  101. package/spa/src/components/Toast.vue +0 -78
  102. package/spa/src/components/ValueRenderer.vue +0 -114
  103. package/spa/src/components/icons/IconCalendar.vue +0 -5
  104. package/spa/src/components/icons/IconCommunity.vue +0 -7
  105. package/spa/src/components/icons/IconDocumentation.vue +0 -7
  106. package/spa/src/components/icons/IconEcosystem.vue +0 -7
  107. package/spa/src/components/icons/IconSupport.vue +0 -7
  108. package/spa/src/components/icons/IconTime.vue +0 -5
  109. package/spa/src/components/icons/IconTooling.vue +0 -19
  110. package/spa/src/composables/useFrontendApi.ts +0 -26
  111. package/spa/src/composables/useStores.ts +0 -131
  112. package/spa/src/index.scss +0 -31
  113. package/spa/src/main.ts +0 -18
  114. package/spa/src/router/index.ts +0 -59
  115. package/spa/src/spa_types/core.ts +0 -53
  116. package/spa/src/stores/core.ts +0 -148
  117. package/spa/src/stores/filters.ts +0 -27
  118. package/spa/src/stores/modal.ts +0 -48
  119. package/spa/src/stores/toast.ts +0 -31
  120. package/spa/src/stores/user.ts +0 -72
  121. package/spa/src/utils.ts +0 -149
  122. package/spa/src/views/CreateView.vue +0 -167
  123. package/spa/src/views/EditView.vue +0 -170
  124. package/spa/src/views/ListView.vue +0 -279
  125. package/spa/src/views/LoginView.vue +0 -192
  126. package/spa/src/views/ResourceParent.vue +0 -17
  127. package/spa/src/views/ShowView.vue +0 -186
  128. package/spa/tailwind.config.js +0 -17
  129. package/spa/tsconfig.app.json +0 -14
  130. package/spa/tsconfig.json +0 -11
  131. package/spa/tsconfig.node.json +0 -19
  132. package/spa/vite.config.ts +0 -56
  133. package/tsconfig.json +0 -112
  134. package/types/AdminForthConfig.ts +0 -1762
  135. package/types/FrontendAPI.ts +0 -143
@@ -1,736 +0,0 @@
1
- import { exec, spawn } from 'child_process';
2
- import crypto from 'crypto';
3
- import filewatcher from 'filewatcher';
4
- import fs from 'fs';
5
- import fsExtra from 'fs-extra';
6
- import os from 'os';
7
- import path from 'path';
8
- import { promisify } from 'util';
9
- import AdminForth from '../index.js';
10
- import { ADMIN_FORTH_ABSOLUTE_PATH, getComponentNameFromPath, transformObject, deepMerge } from './utils.js';
11
- import { AdminForthConfigMenuItem, ICodeInjector } from '../types/AdminForthConfig.js';
12
- import { StylesGenerator } from './styleGenerator.js';
13
- import { title } from 'process';
14
-
15
-
16
- let TMP_DIR;
17
-
18
- try {
19
- TMP_DIR = os.tmpdir();
20
- } catch (e) {
21
- TMP_DIR = '/tmp';
22
- }
23
-
24
-
25
- function findHomePage(menuItem: AdminForthConfigMenuItem[]): AdminForthConfigMenuItem | undefined {
26
- for (const item of menuItem) {
27
- if (item.homepage) {
28
- return item;
29
- }
30
- if (item.children) {
31
- const found = findHomePage(item.children);
32
- if (found) {
33
- return found;
34
- }
35
- }
36
- }
37
- return undefined;
38
- }
39
- async function findFirstMenuItemWithResource(menuItem: AdminForthConfigMenuItem[]): Promise<AdminForthConfigMenuItem | undefined> {
40
- for (const item of menuItem) {
41
- if (item.path || item.resourceId) {
42
- return item;
43
- }
44
- if (item.children) {
45
- const found = await findFirstMenuItemWithResource(item.children);
46
- if (found) {
47
- return found;
48
- }
49
- }
50
- }
51
- return undefined;
52
- }
53
-
54
- const execAsync = promisify(exec);
55
-
56
- function hashify(obj) {
57
- return crypto.createHash
58
- ('sha256').update(JSON.stringify(obj)).digest('hex');
59
- }
60
-
61
- function notifyWatcherIssue(limit) {
62
- console.log('Ran out of file handles after watching %s files.', limit);
63
- console.log('Falling back to polling which uses more CPU.');
64
- console.log('Run ulimit -n 10000 to increase the limit for open files.');
65
- }
66
-
67
- class CodeInjector implements ICodeInjector {
68
-
69
- allWatchers = [];
70
- adminforth: AdminForth;
71
- allComponentNames: { [key: string]: string } = {};
72
- srcFoldersToSync: { [key: string]: string } = {};
73
-
74
- static SPA_TMP_PATH = path.join(TMP_DIR, 'adminforth', 'spa_tmp');
75
-
76
- cleanup() {
77
- console.log('Cleaning up...');
78
- this.allWatchers.forEach((watcher) => {
79
- watcher.removeAll();
80
- });
81
- }
82
- constructor(adminforth) {
83
- this.adminforth = adminforth;
84
-
85
- ['SIGINT', 'SIGTERM', 'SIGQUIT']
86
- .forEach(signal => process.on(signal, () => {
87
- this.cleanup();
88
- process.exit();
89
- }));
90
-
91
- }
92
-
93
- // async runShell({ command }) {
94
- // console.log(`βš™οΈ Running shell ${command}...`);
95
- // console.time(`${command} done in`);
96
- // const { stdout: out, stderr: err } = await execAsync(command);
97
- // console.timeEnd(`${command} done in`);
98
- // console.log(`Command ${command} output:`, out, err);
99
- // }
100
-
101
- async runNpmShell({command, cwd}) {
102
- const nodeBinary = process.execPath; // Path to the Node.js binary running this script
103
- const npmPath = path.join(path.dirname(nodeBinary), 'npm'); // Path to the npm executable
104
-
105
- const env = {
106
- VITE_ADMINFORTH_PUBLIC_PATH: this.adminforth.config.baseUrl,
107
- FORCE_COLOR: '1',
108
- ...process.env,
109
- };
110
-
111
- console.log(`βš™οΈ exec: npm ${command}...`);
112
- console.time(`npm ${command} done in`);
113
- const { stdout: out, stderr: err } = await execAsync(`${nodeBinary} ${npmPath} ${command}`, {
114
- cwd,
115
- env,
116
- });
117
- console.timeEnd(`npm ${command} done in`);
118
-
119
- process.env.HEAVY_DEBUG && console.log(`πŸͺ² npm ${command} output:`, out);
120
- if (err) {
121
- process.env.HEAVY_DEBUG && console.error(`πŸͺ²npm ${command} errors/warnings:`, err);
122
- }
123
- }
124
-
125
- async rmTmpDir() {
126
- // remove spa_tmp folder if it is exists
127
- try {
128
- await fs.promises.rm(CodeInjector.SPA_TMP_PATH, { recursive: true });
129
- } catch (e) {
130
- // ignore
131
- }
132
- }
133
-
134
- async packagesFromNpm(dir: string): Promise<[string, string[]]> {
135
- const usersPackagePath = path.join(dir, 'package.json');
136
- let packageContent: { dependencies: any, devDependencies: any } = null;
137
- let lockHash: string = '';
138
- let packages: string[] = [];
139
- try {
140
- packageContent = JSON.parse(await fs.promises.readFile(usersPackagePath, 'utf-8'));
141
- } catch (e) {
142
- // user package.json does not exist, user does not have custom components
143
- }
144
- if (packageContent) {
145
- const lockPath = path.join(dir, 'package-lock.json');
146
- let lock = null;
147
- try {
148
- lock = JSON.parse(await fs.promises.readFile(lockPath, 'utf-8'));
149
- } catch (e) {
150
- throw new Error(`Custom package-lock.json does not exist in ${dir}, but package.json does.
151
- We can't determine version of packages without package-lock.json. Please run npm install in ${dir}`);
152
- }
153
- lockHash = hashify(lock);
154
-
155
- packages = [
156
- ...Object.keys(packageContent.dependencies || []),
157
- ...Object.keys(packageContent.devDependencies || [])
158
- ].reduce(
159
- (acc, packageName) => {
160
- const pack = lock.packages[`node_modules/${packageName}`];
161
- if (!pack) {
162
- throw new Error(`Package ${packageName} is not in package-lock.json but is in package.json. Please run 'npm install' in ${dir}`);
163
- }
164
- const version = pack.version;
165
-
166
- acc.push(`${packageName}@${version}`);
167
- return acc;
168
- }, []
169
- );
170
-
171
- }
172
- return [lockHash, packages];
173
- }
174
-
175
- async prepareSources({ filesUpdated }: { filesUpdated?: string[] }) {
176
- // check SPA_TMP_PATH exists and create if not
177
- try {
178
- await fs.promises.access(CodeInjector.SPA_TMP_PATH, fs.constants.F_OK);
179
- } catch (e) {
180
- await fs.promises.mkdir(CodeInjector.SPA_TMP_PATH, { recursive: true });
181
- }
182
-
183
- const icons = [];
184
- let routes = '';
185
- let routerComponents = '';
186
-
187
- const collectAssetsFromMenu = (menu) => {
188
- menu.forEach((item) => {
189
- if (item.icon) {
190
- icons.push(item.icon);
191
- }
192
-
193
- if (item.component) {
194
- if(Object.keys(item).includes('isStaticRoute')) {
195
- if(!item.isStaticRoute) {
196
- routes += `{
197
- path: '${item.path}',
198
- name: '${item.path}',
199
- component: () => import('${item.component}'),
200
- meta: { title: '${item?.meta?.title || item.path.replace('/', '')}'}
201
- },\n`
202
- } else {
203
- routes += `{
204
- path: '${item.path}',
205
- name: '${item.path}',
206
- component: ${getComponentNameFromPath(item.component)},
207
- meta: { title: '${item?.meta?.title|| item.path.replace('/', '')}'}
208
- },\n`
209
- const componentName = `${getComponentNameFromPath(item.component)}`;
210
- routerComponents += `import ${componentName} from '${item.component}';\n`;
211
- }
212
- } else {
213
- if (item.homepage) {
214
- routes += `{
215
- path: '${item.path}',
216
- name: '${item.path}',
217
- component: ${getComponentNameFromPath(item.component)},
218
- meta: { title: '${item?.meta?.title || item.path.replace('/', '')}'}
219
- },\n`
220
- const componentName = `${getComponentNameFromPath(item.component)}`;
221
- routerComponents += `import ${componentName} from '${item.component}';\n`;
222
- } else {
223
- routes += `{
224
- path: '${item.path}',
225
- name: '${item.path}',
226
- component: () => import('${item.component}'),
227
- meta: { title: '${item?.meta?.title || item.path.replace('/', '')}'}
228
- },\n`
229
- }
230
- }
231
- }
232
- if (item.children) {
233
- collectAssetsFromMenu(item.children);
234
- }
235
- });
236
- };
237
- const registerCustomPages = (config) => {
238
- if (config.customization.customPages) {
239
- config.customization.customPages.forEach((page) => {
240
- routes += `{
241
- path: '${page.path}',
242
- name: '${page.path}',
243
- component: () => import('${page?.component?.file || page.component}'),
244
- meta: ${
245
- JSON.stringify({
246
- ...(page?.component?.meta || {}),
247
- title: page.meta?.title || page.path.replace('/', '')
248
- })
249
- }
250
- },`})
251
-
252
- }}
253
-
254
- registerCustomPages(this.adminforth.config);
255
- collectAssetsFromMenu(this.adminforth.config.menu);
256
-
257
- if (filesUpdated) {
258
- // copy only updated files
259
- await Promise.all(filesUpdated.map(async (file) => {
260
- const src = path.join(ADMIN_FORTH_ABSOLUTE_PATH, 'spa', file);
261
- const dest = path.join(CodeInjector.SPA_TMP_PATH, file);
262
-
263
- // overwrite:true can't be used to not destroy cache
264
- await fsExtra.copy(src, dest, {
265
- dereference: true, // needed to dereference types
266
- });
267
- if (process.env.HEAVY_DEBUG) {
268
- console.log('πŸͺ²βš™οΈ fsExtra.copy copy single file', src, dest);
269
- }
270
-
271
- }));
272
- } else {
273
- if (process.env.HEAVY_DEBUG) {
274
- console.log(`πŸͺ²βš™οΈ fsExtra.copy from ${path.join(ADMIN_FORTH_ABSOLUTE_PATH, 'spa')}, -> ${CodeInjector.SPA_TMP_PATH}`);
275
- }
276
-
277
- // try to rm SPA_TMP_PATH/src/types directory
278
- try {
279
- await fs.promises.rm(path.join(CodeInjector.SPA_TMP_PATH, 'src', 'types'), { recursive: true });
280
- } catch (e) {
281
- // ignore
282
- }
283
-
284
- // overwrite can't be used to not destroy cache
285
- await fsExtra.copy(path.join(ADMIN_FORTH_ABSOLUTE_PATH, 'spa'), CodeInjector.SPA_TMP_PATH, {
286
- filter: (src) => {
287
- const filterPasses = !src.includes('/adminforth/spa/node_modules') && !src.includes('/adminforth/spa/dist')
288
- if (process.env.HEAVY_DEBUG && !filterPasses) {
289
- console.log('πŸͺ²βš™οΈ fsExtra.copy filtered out', src);
290
- }
291
-
292
- return filterPasses
293
- },
294
- dereference: true, // needed to dereference types
295
- });
296
-
297
- // copy whole custom directory
298
- if (this.adminforth.config.customization?.customComponentsDir) {
299
- // resolve customComponentsDir to absolute path, so ./aa will be resolved to /path/to/current/dir/aa
300
- const customCompAbsPath = path.resolve(this.adminforth.config.customization.customComponentsDir);
301
- this.srcFoldersToSync[customCompAbsPath] = './'
302
- }
303
-
304
- // if this.adminforth.config.customization.favicon is set, copy it to assets
305
- const customFav = this.adminforth.config.customization?.favicon;
306
- if (customFav) {
307
-
308
- const faviconPath = path.join(this.adminforth.config.customization?.customComponentsDir, customFav.replace('@@/', ''));
309
- const dest = path.join(CodeInjector.SPA_TMP_PATH, 'public', 'assets', customFav.replace('@@/', ''));
310
- // make sure all folders in dest exist
311
- await fsExtra.ensureDir(path.dirname(dest));
312
-
313
- await fsExtra.copy(faviconPath, dest);
314
- }
315
-
316
- for (const [src, dest] of Object.entries(this.srcFoldersToSync)) {
317
- const to = path.join(CodeInjector.SPA_TMP_PATH, 'src', 'custom', dest);
318
- if (process.env.HEAVY_DEBUG) {
319
- console.log(`πŸͺ²βš™οΈ fsExtra.copy from ${src}, ${to}`);
320
- }
321
-
322
- await fsExtra.copy(src, to, {
323
- recursive: true,
324
- dereference: true,
325
- });
326
- }
327
- }
328
-
329
- //collect all 'icon' fields from resources bulkActions
330
- this.adminforth.config.resources.forEach((resource) => {
331
- if (resource.options?.bulkActions) {
332
- resource.options.bulkActions.forEach((action) => {
333
- if (action.icon) {
334
- icons.push(action.icon);
335
- }
336
- });
337
- }
338
- });
339
-
340
- const uniqueIcons = Array.from(new Set(icons));
341
-
342
- // icons are collectionName:iconName. Get list of all unique collection names:
343
- const collections = new Set(icons.map((icon) => icon.split(':')[0]));
344
-
345
- // package names @iconify-prerendered/vue-<collection name>
346
- const iconPackageNames = Array.from(collections).map((collection) => `@iconify-prerendered/vue-${collection}`);
347
-
348
- // for each icon generate import statement
349
- const iconImports = uniqueIcons.map((icon) => {
350
- const [ collection, iconName ] = icon.split(':');
351
- const PascalIconName = 'Icon' + iconName.split('-').map((part, index) => {
352
- return part[0].toUpperCase() + part.slice(1);
353
- }).join('');
354
- return `import { ${PascalIconName} } from '@iconify-prerendered/vue-${collection}';`;
355
- }).join('\n');
356
-
357
- // for each custom component generate import statement
358
- const customResourceComponents = [];
359
-
360
- function checkInjections(filePathes) {
361
- filePathes.forEach(({ file }) => {
362
- if (!customResourceComponents.includes(file)) {
363
- if (file === undefined) {
364
- throw new Error('file is undefined');
365
- }
366
- customResourceComponents.push(file);
367
- }
368
- });
369
- }
370
-
371
- this.adminforth.config.resources.forEach((resource) => {
372
- resource.columns.forEach((column) => {
373
- if (column.components) {
374
- Object.values(column.components).forEach(({ file }: {file: string}) => {
375
- if (!customResourceComponents.includes(file)) {
376
- if (file === undefined) {
377
- throw new Error('file is undefined from field.components, field:' + JSON.stringify(column));
378
- }
379
- customResourceComponents.push(file);
380
- }
381
- });
382
- }
383
- });
384
-
385
- (Object.values(resource.options?.pageInjections || {})).forEach((injection) => {
386
- Object.values(injection).forEach((filePathes: {file: string}[]) => {
387
- checkInjections(filePathes);
388
- });
389
- });
390
- });
391
-
392
- if (this.adminforth.config.customization?.globalInjections) {
393
- Object.values(this.adminforth.config.customization.globalInjections).forEach((injection) => {
394
- checkInjections(injection);
395
- });
396
- }
397
-
398
-
399
- customResourceComponents.forEach((filePath) => {
400
- const componentName = getComponentNameFromPath(filePath);
401
- this.allComponentNames[filePath] = componentName;
402
- });
403
-
404
- // console.log('πŸ”§ Injecting code into Vue sources...', this.allComponentNames);
405
-
406
- let customComponentsImports = '';
407
- for (const [targetPath, component] of Object.entries(this.allComponentNames)) {
408
- customComponentsImports += `import ${component} from '${targetPath}';\n`;
409
- }
410
-
411
-
412
- // Generate Vue.component statements for each icon
413
- const iconComponents = uniqueIcons.map((icon) => {
414
- const [ collection, iconName ] = icon.split(':');
415
- const PascalIconName = 'Icon' + iconName.split('-').map((part, index) => {
416
- return part[0].toUpperCase() + part.slice(1);
417
- }).join('');
418
- return `app.component('${PascalIconName}', ${PascalIconName});`;
419
- }).join('\n');
420
-
421
- // Generate Vue.component statements for each custom component
422
- let customComponentsComponents = '';
423
- for (const name of Object.values(this.allComponentNames)) {
424
- customComponentsComponents += `app.component('${name}', ${name});\n`;
425
- }
426
-
427
- let imports = iconImports + '\n';
428
- imports += customComponentsImports + '\n';
429
-
430
-
431
- if (this.adminforth.config.customization?.vueUsesFile) {
432
- imports += `import addCustomUses from '${this.adminforth.config.customization.vueUsesFile}';\n`;
433
- }
434
-
435
- // inject that code into spa_tmp/src/App.vue
436
- const appVuePath = path.join(CodeInjector.SPA_TMP_PATH, 'src', 'main.ts');
437
- let appVueContent = await fs.promises.readFile(appVuePath, 'utf-8');
438
- appVueContent = appVueContent.replace('/* IMPORTANT:ADMINFORTH IMPORTS */', imports);
439
- appVueContent = appVueContent.replace('/* IMPORTANT:ADMINFORTH COMPONENT REGISTRATIONS */', iconComponents + '\n' + customComponentsComponents + '\n');
440
- if (this.adminforth.config.customization?.vueUsesFile) {
441
- appVueContent = appVueContent.replace('/* IMPORTANT:ADMINFORTH CUSTOM USES */', 'addCustomUses(app);');
442
- }
443
- await fs.promises.writeFile(appVuePath, appVueContent);
444
-
445
- // generate tailwind extend styles
446
- const stylesGenerator = new StylesGenerator(this.adminforth.config.customization?.styles);
447
- const stylesText = JSON.stringify(stylesGenerator.mergeStyles(), null, 2).slice(1, -1);
448
- let tailwindConfigPath = path.join(CodeInjector.SPA_TMP_PATH, 'tailwind.config.js');
449
- let tailwindConfigContent = await fs.promises.readFile(tailwindConfigPath, 'utf-8');
450
- tailwindConfigContent = tailwindConfigContent.replace('/* IMPORTANT:ADMINFORTH TAILWIND STYLES */', stylesText);
451
- await fs.promises.writeFile(tailwindConfigPath, tailwindConfigContent);
452
-
453
-
454
- const routerVuePath = path.join(CodeInjector.SPA_TMP_PATH, 'src', 'router', 'index.ts');
455
-
456
- let routerVueContent = await fs.promises.readFile(routerVuePath, 'utf-8');
457
- routerVueContent = routerVueContent.replace('/* IMPORTANT:ADMINFORTH ROUTES IMPORTS */', routerComponents);
458
-
459
- // inject title to index.html
460
- const indexHtmlPath = path.join(CodeInjector.SPA_TMP_PATH, 'index.html');
461
- let indexHtmlContent = await fs.promises.readFile(indexHtmlPath, 'utf-8');
462
- indexHtmlContent = indexHtmlContent.replace('/* IMPORTANT:ADMINFORTH TITLE */', `${this.adminforth.config.customization.title || 'AdminForth'}`);
463
-
464
- indexHtmlContent = indexHtmlContent.replace(
465
- '/* IMPORTANT:ADMINFORTH FAVICON */',
466
- this.adminforth.config.customization.favicon?.replace('@@/', `${this.adminforth.baseUrlSlashed}assets/`)
467
- ||
468
- `${this.adminforth.baseUrlSlashed}assets/favicon.png`
469
- );
470
- await fs.promises.writeFile(indexHtmlPath, indexHtmlContent);
471
-
472
-
473
- /* generate custom routes */
474
-
475
-
476
- let homepageMenuItem: AdminForthConfigMenuItem = findHomePage(this.adminforth.config.menu);
477
- if (!homepageMenuItem) {
478
- // find first item with path or resourceId. If we face a menu item with children earlier then path/resourceId, we should search in children
479
- homepageMenuItem = await findFirstMenuItemWithResource(this.adminforth.config.menu);
480
- }
481
- if (!homepageMenuItem) {
482
- throw new Error('No homepage found in menu and no menu item with path/resourceId found. AdminForth can not generate routes');
483
- }
484
-
485
- let homePagePath = homepageMenuItem.path || `/resource/${homepageMenuItem.resourceId}`;
486
- if (!homePagePath) {
487
- homePagePath=this.adminforth.config.menu.filter((mi)=>mi.path)[0]?.path || `/resource/${this.adminforth.config.menu.filter((mi)=>mi.children)[0]?.resourceId}` ;
488
- }
489
-
490
- routes += `{
491
- path: '/',
492
- name: 'home',
493
- //redirect to login
494
- redirect: '${homePagePath}'
495
- },\n`;
496
- routerVueContent = routerVueContent.replace('/* IMPORTANT:ADMINFORTH ROUTES */', routes);
497
- await fs.promises.writeFile(routerVuePath, routerVueContent);
498
-
499
-
500
- /* hash checking */
501
- const spaPackageLockPath = path.join(CodeInjector.SPA_TMP_PATH, 'package-lock.json');
502
- const spaPackageLock = JSON.parse(await fs.promises.readFile(spaPackageLockPath, 'utf-8'));
503
- const spaLockHash = hashify(spaPackageLock);
504
-
505
- /* customPackageLock */
506
- let usersLockHash: string = '';
507
- let usersPackages: string[] = [];
508
-
509
-
510
- if (this.adminforth.config.customization?.customComponentsDir) {
511
- [usersLockHash, usersPackages] = await this.packagesFromNpm(this.adminforth.config.customization.customComponentsDir);
512
- }
513
-
514
- const pluginPackages: {
515
- pluginName: string,
516
- lockHash: string,
517
- packages: string[],
518
- }[] = [];
519
-
520
- // for every installed plugin generate packages
521
- for (const plugin of this.adminforth.activatedPlugins) {
522
- process.env.HEAVY_DEBUG && console.log('πŸ”§ Checking packages for plugin', plugin.constructor.name, plugin.customFolderPath);
523
- const [lockHash, packages] = await this.packagesFromNpm(plugin.customFolderPath);
524
- if (packages.length) {
525
- pluginPackages.push({
526
- pluginName: plugin.constructor.name,
527
- lockHash,
528
- packages,
529
- });
530
- }
531
- }
532
- // form string "pluginName:lockHash::pLugin2Name:lockHash"
533
- const pluginsLockHash = pluginPackages.map(({ pluginName, lockHash }) => `${pluginName}>${lockHash}`).join('::');
534
-
535
- const iconPackagesNamesHash = hashify(iconPackageNames);
536
-
537
- const fullHash = `spa>${spaLockHash}::icons>${iconPackagesNamesHash}::user/custom>${usersLockHash}::${pluginsLockHash}`;
538
- const hashPath = path.join(CodeInjector.SPA_TMP_PATH, 'node_modules', '.adminforth_hash');
539
-
540
- try {
541
- const existingHash = await fs.promises.readFile(hashPath, 'utf-8');
542
- if (existingHash === fullHash) {
543
- process.env.HEAVY_DEBUG && console.log(`πŸͺ²Hashes match, skipping npm ci/install, from file: ${existingHash}, actual: ${fullHash}`);
544
- return;
545
- } else {
546
- process.env.HEAVY_DEBUG && console.log(`πŸͺ² Hashes do not match: from file: ${existingHash} actual: ${fullHash}, proceeding with npm ci/install`);
547
- }
548
- } catch (e) {
549
- // ignore
550
- process.env.HEAVY_DEBUG && console.log('πŸͺ²Hash file does not exist, proceeding with npm ci/install');
551
- }
552
-
553
- await this.runNpmShell({command: 'ci', cwd: CodeInjector.SPA_TMP_PATH});
554
-
555
- if (iconPackageNames.length) {
556
- const npmInstallCommand = `install ${[
557
- ...iconPackageNames,
558
- ...usersPackages,
559
- ...pluginPackages.map(({ packages }) => packages.join(' ')),
560
- ].join(' ')}`;
561
- await this.runNpmShell({command: npmInstallCommand, cwd: CodeInjector.SPA_TMP_PATH});
562
- }
563
-
564
- await fs.promises.writeFile(hashPath, fullHash);
565
- }
566
-
567
- async watchForReprepare({}) {
568
- const spaPath = path.join(ADMIN_FORTH_ABSOLUTE_PATH, 'spa');
569
- // get list of all subdirectories in spa recursively
570
- const directories = [];
571
- const collectDirectories = async (dir) => {
572
- const files = await fs.promises.readdir(dir, { withFileTypes: true });
573
- for (const file of files) {
574
- if (['node_modules', 'dist'].includes(file.name)) {
575
- continue;
576
- }
577
- if (file.isDirectory()) {
578
- directories.push(path.join(dir, file.name));
579
- await collectDirectories(path.join(dir, file.name));
580
- }
581
- }
582
- };
583
- await collectDirectories(spaPath);
584
-
585
- if (process.env.HEAVY_DEBUG) {
586
- console.log('πŸͺ²πŸ”Ž Watch for:', directories.join(','));
587
- }
588
-
589
- const watcher = filewatcher();
590
- directories.forEach((dir) => {
591
- watcher.add(dir);
592
- });
593
-
594
- watcher.on(
595
- 'change',
596
- async (file, x) => {
597
- console.log(`File ${file} changed ${x}, preparing sources...`);
598
- await this.prepareSources({ filesUpdated: [file.replace(spaPath + '/', '')] });
599
- }
600
- )
601
- watcher.on('fallback', notifyWatcherIssue);
602
- this.allWatchers.push(watcher);
603
- }
604
-
605
- async watchCustomComponentsForCopy({ customComponentsDir, destination }) {
606
- if (!customComponentsDir) {
607
- return;
608
- }
609
- // check if folder exists
610
- try {
611
- await fs.promises.access(customComponentsDir, fs.constants.F_OK);
612
- } catch (e) {
613
- process.env.HEAVY_DEBUG && console.log(`πŸͺ²Custom components dir ${customComponentsDir} does not exist, skipping watching`);
614
- return;
615
- }
616
-
617
- // get all subdirs
618
- const directories = [];
619
- const files = []
620
- const collectDirectories = async (dir) => {
621
- if (['node_modules', 'dist'].includes(path.basename(dir))) {
622
- return;
623
- }
624
- directories.push(dir);
625
-
626
- const filesAndDirs = await fs.promises.readdir(dir, { withFileTypes: true });
627
- await Promise.all(
628
- filesAndDirs.map(
629
- async (file) => {
630
- const isDir = fs.lstatSync(path.join(dir, file.name)).isDirectory();
631
- if (isDir) {
632
- await collectDirectories(path.join(dir, file.name));
633
- } else {
634
- files.push(path.join(dir, file.name));
635
- }
636
- }
637
- )
638
- )
639
- };
640
-
641
- await collectDirectories(customComponentsDir);
642
-
643
- const watcher = filewatcher();
644
- files.forEach((file) => {
645
- process.env.HEAVY_DEBUG && console.log(`πŸͺ²πŸ”Ž Watch for file ${file}`);
646
- watcher.add(file);
647
- });
648
-
649
- if (process.env.HEAVY_DEBUG) {
650
- console.log('πŸͺ²πŸ”Ž Watch for:', directories.join(','));
651
- }
652
-
653
- watcher.on(
654
- 'change',
655
- async (fileOrDir) => {
656
- // copy one file
657
- const relativeFilename = fileOrDir.replace(customComponentsDir + '/', '');
658
- if (process.env.HEAVY_DEBUG) {
659
- console.log(`πŸ”Ž fileOrDir ${fileOrDir} changed`);
660
- console.log(`πŸ”Ž relativeFilename ${relativeFilename}`);
661
- console.log(`πŸ”Ž customComponentsDir ${customComponentsDir}`);
662
- console.log(`πŸ”Ž destination ${destination}`);
663
- }
664
- const isFile = fs.lstatSync(fileOrDir).isFile();
665
- if (isFile) {
666
- const destPath = path.join(CodeInjector.SPA_TMP_PATH, 'src', 'custom', destination, relativeFilename);
667
- process.env.HEAVY_DEBUG && console.log(`πŸ”Ž Copying file ${fileOrDir} to ${destPath}`);
668
- await fsExtra.copy(fileOrDir, destPath);
669
- return;
670
- } else {
671
- // for now do nothing
672
- }
673
- }
674
- );
675
-
676
- watcher.on('fallback', notifyWatcherIssue);
677
-
678
- this.allWatchers.push(watcher);
679
- }
680
-
681
- async bundleNow({ hotReload = false }: { hotReload: boolean }) {
682
- console.log(`AdminForth bundling ${hotReload ? ' and listening for changes (πŸ”₯ Hotreload)' : ' (no hot reload)'}`);
683
- this.adminforth.runningHotReload = hotReload;
684
-
685
- await this.prepareSources({});
686
-
687
- if (hotReload) {
688
- await Promise.all([
689
- this.watchForReprepare({}),
690
- ...Object.entries(this.srcFoldersToSync).map(async ([src, dest]) => {
691
-
692
- await this.watchCustomComponentsForCopy({
693
- customComponentsDir: src,
694
- destination: dest,
695
- });
696
- }),
697
- ]);
698
- }
699
-
700
- console.log('AdminForth bundling');
701
-
702
- const cwd = CodeInjector.SPA_TMP_PATH;
703
-
704
- if (!hotReload) {
705
- // probably add option to build with tsh check (plain 'build')
706
- await this.runNpmShell({command: 'run build-only', cwd});
707
- } else {
708
- const command = 'run dev';
709
- console.log(`πŸͺ²βš™οΈ spawn: npm ${command}...`);
710
- const nodeBinary = process.execPath;
711
- const npmPath = path.join(path.dirname(nodeBinary), 'npm');
712
- const env = {
713
- VITE_ADMINFORTH_PUBLIC_PATH: this.adminforth.config.baseUrl,
714
- FORCE_COLOR: '1',
715
- ...process.env,
716
- };
717
-
718
- const devServer = spawn(`${nodeBinary}`, [`${npmPath}`, ...command.split(' ')], {
719
- cwd,
720
- env,
721
- });
722
- devServer.stdout.on('data', (data) => {
723
- console.log(`[AdminForth SPA]:`);
724
- process.stdout.write(data);
725
- });
726
- devServer.stderr.on('data', (data) => {
727
- console.error(`[AdminForth SPA ERR]:`);
728
- process.stdout.write(data);
729
-
730
- });
731
-
732
- }
733
- }
734
- }
735
-
736
- export default CodeInjector;