adminforth 2.25.1 → 2.26.0-next.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commands/bundle.js +2 -1
- package/commands/createApp/templates/Dockerfile.hbs +12 -0
- package/commands/createApp/templates/package.json.hbs +18 -6
- package/commands/createApp/templates/pnpm_templates/pnpm-lock.yaml.hbs +8 -0
- package/commands/createApp/templates/pnpm_templates/pnpm-workspace.yaml.hbs +2 -0
- package/commands/createApp/templates/readme.md.hbs +23 -4
- package/commands/createApp/utils.js +107 -26
- package/commands/createPlugin/utils.js +5 -6
- package/commands/postinstall.js +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js.map +1 -1
- package/dist/dataConnectors/clickhouse.d.ts +4 -0
- package/dist/dataConnectors/clickhouse.d.ts.map +1 -1
- package/dist/dataConnectors/clickhouse.js +14 -0
- package/dist/dataConnectors/clickhouse.js.map +1 -1
- package/dist/dataConnectors/mongo.d.ts +4 -0
- package/dist/dataConnectors/mongo.d.ts.map +1 -1
- package/dist/dataConnectors/mongo.js +9 -0
- package/dist/dataConnectors/mongo.js.map +1 -1
- package/dist/dataConnectors/mysql.d.ts +4 -0
- 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 +4 -0
- package/dist/dataConnectors/postgres.d.ts.map +1 -1
- package/dist/dataConnectors/postgres.js +11 -0
- package/dist/dataConnectors/postgres.js.map +1 -1
- package/dist/dataConnectors/sqlite.d.ts +4 -0
- 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 +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/modules/codeInjector.d.ts +11 -2
- package/dist/modules/codeInjector.d.ts.map +1 -1
- package/dist/modules/codeInjector.js +239 -82
- package/dist/modules/codeInjector.js.map +1 -1
- package/dist/modules/styles.d.ts +4 -0
- package/dist/modules/styles.d.ts.map +1 -1
- package/dist/modules/styles.js +4 -0
- package/dist/modules/styles.js.map +1 -1
- package/dist/modules/utils.d.ts.map +1 -1
- package/dist/servers/express.d.ts +0 -1
- package/dist/servers/express.d.ts.map +1 -1
- package/dist/spa/README.md +4 -4
- package/dist/spa/package-lock.json +1902 -1427
- package/dist/spa/package.json +4 -3
- package/dist/spa/pnpm-lock.yaml +3558 -0
- package/dist/spa/src/afcl/Button.vue +12 -5
- package/dist/spa/src/afcl/Dialog.vue +155 -126
- package/dist/spa/src/afcl/Modal.vue +31 -8
- package/dist/spa/src/afcl/Select.vue +6 -2
- package/dist/spa/src/components/AcceptModal.vue +31 -53
- package/dist/spa/src/components/MenuLink.vue +18 -4
- package/dist/spa/src/components/ResourceListTable.vue +11 -11
- package/dist/spa/src/components/ResourceListTableVirtual.vue +21 -26
- package/dist/spa/src/components/Sidebar.vue +1 -1
- package/dist/spa/src/components/ThreeDotsMenu.vue +1 -1
- package/dist/spa/src/components/ValueRenderer.vue +1 -1
- package/dist/spa/src/stores/core.ts +1 -2
- package/dist/spa/src/types/Back.ts +4 -3
- package/dist/spa/src/types/Common.ts +16 -0
- package/dist/spa/src/types/FrontendAPI.ts +0 -3
- package/dist/spa/src/utils/utils.ts +57 -2
- package/dist/spa/src/views/CreateView.vue +12 -11
- package/dist/spa/src/views/EditView.vue +9 -13
- package/dist/spa/vite.config.ts +29 -40
- package/dist/types/Back.d.ts +3 -4
- package/dist/types/Back.d.ts.map +1 -1
- package/dist/types/Back.js.map +1 -1
- package/dist/types/Common.d.ts +11 -0
- package/dist/types/Common.d.ts.map +1 -1
- package/dist/types/Common.js.map +1 -1
- package/package.json +14 -6
- package/scripts/postinstall.js +25 -0
|
@@ -5,9 +5,11 @@ import fsExtra from 'fs-extra';
|
|
|
5
5
|
import os from 'os';
|
|
6
6
|
import path from 'path';
|
|
7
7
|
import { promisify } from 'util';
|
|
8
|
+
import yaml from 'yaml';
|
|
8
9
|
import { ADMIN_FORTH_ABSOLUTE_PATH, getComponentNameFromPath, md5hash } from './utils.js';
|
|
9
10
|
import { StylesGenerator } from './styleGenerator.js';
|
|
10
11
|
import { afLogger } from '../modules/logger.js';
|
|
12
|
+
import { pathToFileURL } from 'url';
|
|
11
13
|
let TMP_DIR;
|
|
12
14
|
try {
|
|
13
15
|
TMP_DIR = os.tmpdir();
|
|
@@ -60,9 +62,9 @@ function isFulfilled(result) {
|
|
|
60
62
|
return result.status === 'fulfilled';
|
|
61
63
|
}
|
|
62
64
|
function notifyWatcherIssue(limit) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
console.log('Ran out of file handles after watching %s files.', limit);
|
|
66
|
+
console.log('Falling back to polling which uses more CPU.');
|
|
67
|
+
console.log('Run ulimit -n 10000 to increase the limit for open files.');
|
|
66
68
|
}
|
|
67
69
|
class CodeInjector {
|
|
68
70
|
spaTmpPath() {
|
|
@@ -76,7 +78,7 @@ class CodeInjector {
|
|
|
76
78
|
const uniqueIcons = Array.from(new Set(icons));
|
|
77
79
|
const collections = new Set(icons.map((icon) => icon.split(':')[0]));
|
|
78
80
|
const iconPackageNames = Array.from(collections).map((collection) => `@iconify-prerendered/vue-${collection}`);
|
|
79
|
-
const iconPackages = (await Promise.allSettled(iconPackageNames.map(async (pkg) => ({ pkg: await import(this.spaTmpPath()
|
|
81
|
+
const iconPackages = (await Promise.allSettled(iconPackageNames.map(async (pkg) => ({ pkg: await import(pathToFileURL(path.join(this.spaTmpPath(), 'node_modules', pkg)).href), name: pkg }))));
|
|
80
82
|
const loadedIconPackages = iconPackages.filter(isFulfilled).map((res) => res.value).reduce((acc, { pkg, name }) => {
|
|
81
83
|
acc[name.slice(`@iconify-prerendered/vue-`.length)] = pkg;
|
|
82
84
|
return acc;
|
|
@@ -99,7 +101,7 @@ class CodeInjector {
|
|
|
99
101
|
this.allComponentNames[filePath] = componentName;
|
|
100
102
|
}
|
|
101
103
|
cleanup() {
|
|
102
|
-
|
|
104
|
+
console.log('Cleaning up...');
|
|
103
105
|
this.allWatchers.forEach((watcher) => {
|
|
104
106
|
watcher.removeAll();
|
|
105
107
|
});
|
|
@@ -116,40 +118,68 @@ class CodeInjector {
|
|
|
116
118
|
process.exit();
|
|
117
119
|
}));
|
|
118
120
|
}
|
|
119
|
-
async
|
|
121
|
+
async doesUserHasPnpmLockFile(dir) {
|
|
122
|
+
const usersPackagePath = path.join(dir, 'package.json');
|
|
123
|
+
let packageContent = null;
|
|
124
|
+
try {
|
|
125
|
+
packageContent = JSON.parse(await fs.promises.readFile(usersPackagePath, 'utf-8'));
|
|
126
|
+
}
|
|
127
|
+
catch (e) {
|
|
128
|
+
// user package.json does not exist, user does not have custom components
|
|
129
|
+
}
|
|
130
|
+
if (packageContent) {
|
|
131
|
+
const lockPath = path.join(dir, 'pnpm-lock.yaml');
|
|
132
|
+
let lock = null;
|
|
133
|
+
try {
|
|
134
|
+
lock = yaml.parse(await fs.promises.readFile(lockPath, 'utf-8'));
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
async runPackageManagerShell({ command, cwd, envOverrides = {} }) {
|
|
120
144
|
const nodeBinary = process.execPath; // Path to the Node.js binary running this script
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
145
|
+
const doesUserHavePnpmLock = await this.doesUserHasPnpmLockFile(this.adminforth.config.customization.customComponentsDir);
|
|
146
|
+
// On Windows, npm/pnpm is npm/pnpm.cmd, on Unix systems it's npm/pnpm
|
|
147
|
+
let packageExecutable;
|
|
148
|
+
if (doesUserHavePnpmLock) {
|
|
149
|
+
process.env.HEAVY_DEBUG && console.log(`User has pnpm-lock.yaml, using pnpm for installing custom components`);
|
|
150
|
+
packageExecutable = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm';
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
process.env.HEAVY_DEBUG && console.log(`User does not have pnpm-lock.yaml, falling back to npm for installing custom components`);
|
|
154
|
+
packageExecutable = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
155
|
+
}
|
|
156
|
+
const packagePath = path.join(path.dirname(nodeBinary), packageExecutable); // Path to the package executable
|
|
124
157
|
const env = Object.assign(Object.assign({ VITE_ADMINFORTH_PUBLIC_PATH: this.adminforth.config.baseUrl, FORCE_COLOR: '1' }, process.env), envOverrides);
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
afLogger.trace(`npm ${command} done in`);
|
|
128
|
-
// On Windows, execute npm.cmd directly; on Unix, use node + npm
|
|
158
|
+
process.env.HEAVY_DEBUG && console.log(`⚙️ exec: ${packageExecutable} ${command}`);
|
|
159
|
+
process.env.HEAVY_DEBUG && console.log(`🪲 ${packageExecutable} ${command} cwd: ${cwd}`);
|
|
129
160
|
let execCommand;
|
|
130
161
|
if (process.platform === 'win32') {
|
|
131
162
|
// Quote path if it contains spaces
|
|
132
|
-
const
|
|
133
|
-
execCommand = `${
|
|
163
|
+
const quotedPackagePath = packagePath.includes(' ') ? `"${packagePath}"` : packagePath;
|
|
164
|
+
execCommand = `${quotedPackagePath} ${command}`;
|
|
134
165
|
}
|
|
135
166
|
else {
|
|
136
167
|
// Quote paths that contain spaces (for Unix systems)
|
|
137
168
|
const quotedNodeBinary = nodeBinary.includes(' ') ? `"${nodeBinary}"` : nodeBinary;
|
|
138
|
-
const
|
|
139
|
-
execCommand = `${quotedNodeBinary} ${
|
|
169
|
+
const quotedPackagePath = packagePath.includes(' ') ? `"${packagePath}"` : packagePath;
|
|
170
|
+
execCommand = `${quotedNodeBinary} ${quotedPackagePath} ${command}`;
|
|
140
171
|
}
|
|
141
172
|
const execOptions = {
|
|
142
173
|
cwd,
|
|
143
174
|
env,
|
|
144
175
|
};
|
|
145
|
-
// On Windows, use shell to execute .cmd files
|
|
146
176
|
if (process.platform === 'win32') {
|
|
147
177
|
execOptions.shell = true;
|
|
148
178
|
}
|
|
149
|
-
const {
|
|
150
|
-
|
|
179
|
+
const { stderr: err } = await execAsync(execCommand, execOptions);
|
|
180
|
+
process.env.HEAVY_DEBUG && console.log(`${packageExecutable} ${command} done in`);
|
|
151
181
|
if (err) {
|
|
152
|
-
|
|
182
|
+
process.env.HEAVY_DEBUG && console.log(`🪲${packageExecutable} ${command} errors/warnings: ${err}`);
|
|
153
183
|
}
|
|
154
184
|
}
|
|
155
185
|
async rmTmpDir() {
|
|
@@ -164,7 +194,8 @@ class CodeInjector {
|
|
|
164
194
|
getServeDir() {
|
|
165
195
|
return path.join(this.getSpaDir(), 'dist');
|
|
166
196
|
}
|
|
167
|
-
async
|
|
197
|
+
async packagesFromPnpm(dir) {
|
|
198
|
+
var _a;
|
|
168
199
|
const usersPackagePath = path.join(dir, 'package.json');
|
|
169
200
|
let packageContent = null;
|
|
170
201
|
let lockHash = '';
|
|
@@ -176,26 +207,55 @@ class CodeInjector {
|
|
|
176
207
|
// user package.json does not exist, user does not have custom components
|
|
177
208
|
}
|
|
178
209
|
if (packageContent) {
|
|
179
|
-
const lockPath = path.join(dir, '
|
|
210
|
+
const lockPath = path.join(dir, 'pnpm-lock.yaml');
|
|
180
211
|
let lock = null;
|
|
181
212
|
try {
|
|
182
|
-
lock =
|
|
213
|
+
lock = yaml.parse(await fs.promises.readFile(lockPath, 'utf-8'));
|
|
183
214
|
}
|
|
184
215
|
catch (e) {
|
|
185
|
-
|
|
186
|
-
|
|
216
|
+
const npmLockPath = path.join(dir, 'package-lock.json');
|
|
217
|
+
let npmLock = null;
|
|
218
|
+
try {
|
|
219
|
+
npmLock = JSON.parse(await fs.promises.readFile(npmLockPath, 'utf-8'));
|
|
220
|
+
}
|
|
221
|
+
catch (npmLockError) {
|
|
222
|
+
throw new Error(`Custom pnpm-lock.yaml or package-lock.json does not exist in ${dir}, but package.json does.
|
|
223
|
+
We can't determine version of packages without pnpm-lock.yaml or package-lock.json. Please run pnpm install or npm install in ${dir}`);
|
|
224
|
+
}
|
|
225
|
+
lockHash = hashify(npmLock);
|
|
226
|
+
packages = [
|
|
227
|
+
...Object.keys(packageContent.dependencies || {}),
|
|
228
|
+
...Object.keys(packageContent.devDependencies || {})
|
|
229
|
+
].reduce((acc, packageName) => {
|
|
230
|
+
var _a;
|
|
231
|
+
const pack = (_a = npmLock === null || npmLock === void 0 ? void 0 : npmLock.packages) === null || _a === void 0 ? void 0 : _a[`node_modules/${packageName}`];
|
|
232
|
+
if (!(pack === null || pack === void 0 ? void 0 : pack.version)) {
|
|
233
|
+
throw new Error(`Package ${packageName} is not in package-lock.json but is in package.json. Please run 'npm install' in ${dir}`);
|
|
234
|
+
}
|
|
235
|
+
acc.push(`${packageName}@${pack.version}`);
|
|
236
|
+
return acc;
|
|
237
|
+
}, []);
|
|
238
|
+
return [lockHash, packages];
|
|
187
239
|
}
|
|
188
240
|
lockHash = hashify(lock);
|
|
241
|
+
const importer = (_a = lock === null || lock === void 0 ? void 0 : lock.importers) === null || _a === void 0 ? void 0 : _a['.'];
|
|
242
|
+
if (!importer) {
|
|
243
|
+
throw new Error(`pnpm-lock.yaml in ${dir} does not contain importer ".". Please run pnpm install in ${dir}`);
|
|
244
|
+
}
|
|
245
|
+
const importerDeps = Object.assign(Object.assign(Object.assign({}, (importer.dependencies || {})), (importer.devDependencies || {})), (importer.optionalDependencies || {}));
|
|
189
246
|
packages = [
|
|
190
|
-
...Object.keys(packageContent.dependencies ||
|
|
191
|
-
...Object.keys(packageContent.devDependencies ||
|
|
247
|
+
...Object.keys(packageContent.dependencies || {}),
|
|
248
|
+
...Object.keys(packageContent.devDependencies || {})
|
|
192
249
|
].reduce((acc, packageName) => {
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
250
|
+
const depInfo = importerDeps[packageName];
|
|
251
|
+
const raw = typeof depInfo === 'string'
|
|
252
|
+
? depInfo
|
|
253
|
+
: ((depInfo === null || depInfo === void 0 ? void 0 : depInfo.version) || (depInfo === null || depInfo === void 0 ? void 0 : depInfo.specifier));
|
|
254
|
+
if (!raw) {
|
|
255
|
+
throw new Error(`Package ${packageName} is not in pnpm-lock.yaml but is in package.json. Please run 'pnpm install' in ${dir}`);
|
|
196
256
|
}
|
|
197
|
-
const
|
|
198
|
-
acc.push(`${packageName}@${
|
|
257
|
+
const cleaned = raw.includes('(') ? raw.split('(')[0] : raw;
|
|
258
|
+
acc.push(`${packageName}@${cleaned}`);
|
|
199
259
|
return acc;
|
|
200
260
|
}, []);
|
|
201
261
|
}
|
|
@@ -219,7 +279,7 @@ class CodeInjector {
|
|
|
219
279
|
dereference: true, // needed to dereference types
|
|
220
280
|
// preserveTimestamps: true, // needed to not invalidate any caches
|
|
221
281
|
});
|
|
222
|
-
|
|
282
|
+
process.env.HEAVY_DEBUG && console.log(`🪲⚙️ fsExtra.copy copy single file, ${src}, ${dest}`);
|
|
223
283
|
}));
|
|
224
284
|
}
|
|
225
285
|
async migrateLegacyCustomLayout(oldMeta) {
|
|
@@ -322,7 +382,7 @@ class CodeInjector {
|
|
|
322
382
|
collectAssetsFromMenu(this.adminforth.config.menu);
|
|
323
383
|
registerSettingPages(this.adminforth.config.auth.userMenuSettingsPages);
|
|
324
384
|
const spaDir = this.getSpaDir();
|
|
325
|
-
|
|
385
|
+
process.env.HEAVY_DEBUG && console.log(`🪲⚙️ fsExtra.copy from ${spaDir} -> ${this.spaTmpPath()}`);
|
|
326
386
|
// try to rm <spa tmp path>/src/types directory
|
|
327
387
|
try {
|
|
328
388
|
await fs.promises.rm(path.join(this.spaTmpPath(), 'src', 'types'), { recursive: true });
|
|
@@ -337,7 +397,7 @@ class CodeInjector {
|
|
|
337
397
|
const filterPasses = !src.includes(`${path.sep}adminforth${path.sep}spa${path.sep}node_modules`) && !src.includes(`${path.sep}adminforth${path.sep}spa${path.sep}dist`)
|
|
338
398
|
&& !src.includes(`${path.sep}dist${path.sep}spa${path.sep}node_modules`) && !src.includes(`${path.sep}dist${path.sep}spa${path.sep}dist`);
|
|
339
399
|
if (!filterPasses) {
|
|
340
|
-
|
|
400
|
+
process.env.HEAVY_DEBUG && console.log(`🪲⚙️ fsExtra.copy filtered out, ${src}`);
|
|
341
401
|
}
|
|
342
402
|
return filterPasses;
|
|
343
403
|
},
|
|
@@ -360,7 +420,7 @@ class CodeInjector {
|
|
|
360
420
|
}
|
|
361
421
|
for (const [src, dest] of Object.entries(this.srcFoldersToSync)) {
|
|
362
422
|
const to = path.join(this.spaTmpPath(), 'src', 'custom', dest);
|
|
363
|
-
|
|
423
|
+
process.env.HEAVY_DEBUG && console.log(`🪲⚙️ srcFoldersToSync: fsExtra.copy from ${src}, ${to}`);
|
|
364
424
|
await fsExtra.copy(src, to, {
|
|
365
425
|
recursive: true,
|
|
366
426
|
dereference: true,
|
|
@@ -555,20 +615,28 @@ class CodeInjector {
|
|
|
555
615
|
routerVueContent = routerVueContent.replace('/* IMPORTANT:ADMINFORTH ROUTES */', routes);
|
|
556
616
|
await fs.promises.writeFile(routerVuePath, routerVueContent);
|
|
557
617
|
/* hash checking */
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
618
|
+
let spaLockHash = '';
|
|
619
|
+
if (await this.doesUserHasPnpmLockFile(this.adminforth.config.customization.customComponentsDir)) {
|
|
620
|
+
const spaPnpmLockPath = path.join(this.spaTmpPath(), 'pnpm-lock.yaml');
|
|
621
|
+
const spaPnpmLock = yaml.parse(await fs.promises.readFile(spaPnpmLockPath, 'utf-8'));
|
|
622
|
+
spaLockHash = hashify(spaPnpmLock);
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
const spaNpmLockPath = path.join(this.spaTmpPath(), 'package-lock.json');
|
|
626
|
+
const spaNpmLock = JSON.parse(await fs.promises.readFile(spaNpmLockPath, 'utf-8'));
|
|
627
|
+
spaLockHash = hashify(spaNpmLock);
|
|
628
|
+
}
|
|
561
629
|
/* customPackageLock */
|
|
562
630
|
let usersLockHash = '';
|
|
563
631
|
let usersPackages = [];
|
|
564
632
|
if ((_q = this.adminforth.config.customization) === null || _q === void 0 ? void 0 : _q.customComponentsDir) {
|
|
565
|
-
[usersLockHash, usersPackages] = await this.
|
|
633
|
+
[usersLockHash, usersPackages] = await this.packagesFromPnpm(this.adminforth.config.customization.customComponentsDir);
|
|
566
634
|
}
|
|
567
635
|
const pluginPackages = [];
|
|
568
636
|
// for every installed plugin generate packages
|
|
569
637
|
for (const plugin of this.adminforth.activatedPlugins) {
|
|
570
|
-
|
|
571
|
-
const [lockHash, packages] = await this.
|
|
638
|
+
process.env.HEAVY_DEBUG && console.log(`🔧 Checking packages for plugin, ${plugin.constructor.name}, ${plugin.customFolderPath}`);
|
|
639
|
+
const [lockHash, packages] = await this.packagesFromPnpm(plugin.customFolderPath);
|
|
572
640
|
if (packages.length) {
|
|
573
641
|
pluginPackages.push({
|
|
574
642
|
pluginName: plugin.constructor.name,
|
|
@@ -586,19 +654,20 @@ class CodeInjector {
|
|
|
586
654
|
const existingHash = await fs.promises.readFile(hashPath, 'utf-8');
|
|
587
655
|
await this.checkIconNames(icons);
|
|
588
656
|
if (existingHash === fullHash) {
|
|
589
|
-
|
|
657
|
+
process.env.HEAVY_DEBUG && console.log(`🪲Hashes match, skipping pnpm install, from file: ${existingHash}, actual: ${fullHash}`);
|
|
590
658
|
return;
|
|
591
659
|
}
|
|
592
660
|
else {
|
|
593
|
-
|
|
661
|
+
process.env.HEAVY_DEBUG && console.log(`🪲 Hashes do not match: from file: ${existingHash} actual: ${fullHash}, proceeding with pnpm install`);
|
|
594
662
|
}
|
|
595
663
|
}
|
|
596
664
|
catch (e) {
|
|
597
665
|
// ignore
|
|
598
|
-
|
|
666
|
+
process.env.HEAVY_DEBUG && console.log(`🪲Hash file does not exist, proceeding with pnpm install, ${e}`);
|
|
599
667
|
}
|
|
600
|
-
|
|
601
|
-
|
|
668
|
+
// install --frozen-lockfile works for npm and pnpm
|
|
669
|
+
await this.runPackageManagerShell({ command: 'install --frozen-lockfile', cwd: this.spaTmpPath(), envOverrides: {
|
|
670
|
+
NODE_ENV: 'development' // otherwise it will not install devDependencies which we still need, e.g for extract
|
|
602
671
|
} });
|
|
603
672
|
const allPacks = [
|
|
604
673
|
...iconPackageNames,
|
|
@@ -614,11 +683,11 @@ class CodeInjector {
|
|
|
614
683
|
});
|
|
615
684
|
const allPacksUnique = Array.from(new Set(allPacksFiltered));
|
|
616
685
|
if (allPacks.length) {
|
|
617
|
-
const
|
|
618
|
-
await this.
|
|
619
|
-
command:
|
|
686
|
+
const packageManagerInstallCommand = `install ${allPacksUnique.join(' ')}`;
|
|
687
|
+
await this.runPackageManagerShell({
|
|
688
|
+
command: packageManagerInstallCommand, cwd: this.spaTmpPath(),
|
|
620
689
|
envOverrides: {
|
|
621
|
-
NODE_ENV: 'development' //
|
|
690
|
+
NODE_ENV: 'development' // otherwise it will not install devDependencies which we still need, e.g for extract
|
|
622
691
|
}
|
|
623
692
|
});
|
|
624
693
|
}
|
|
@@ -642,7 +711,7 @@ class CodeInjector {
|
|
|
642
711
|
}
|
|
643
712
|
};
|
|
644
713
|
await collectDirectories(spaPath);
|
|
645
|
-
|
|
714
|
+
process.env.HEAVY_DEBUG && console.log(`🪲🔎 Watch for: ${directories.join(',')}`);
|
|
646
715
|
const watcher = filewatcher({ debounce: 30 });
|
|
647
716
|
directories.forEach((dir) => {
|
|
648
717
|
// read directory files and add to watcher, only files not directories
|
|
@@ -650,13 +719,13 @@ class CodeInjector {
|
|
|
650
719
|
files.forEach((file) => {
|
|
651
720
|
const fullPath = path.join(dir, file);
|
|
652
721
|
if (fs.lstatSync(fullPath).isFile()) {
|
|
653
|
-
|
|
722
|
+
process.env.HEAVY_DEBUG && console.log(`🪲🔎 Watch for file ${fullPath}`);
|
|
654
723
|
watcher.add(fullPath);
|
|
655
724
|
}
|
|
656
725
|
});
|
|
657
726
|
});
|
|
658
727
|
watcher.on('change', async (file) => {
|
|
659
|
-
|
|
728
|
+
process.env.HEAVY_DEBUG && console.log(`🐛 File ${file} changed (SPA), preparing sources...`);
|
|
660
729
|
await this.updatePartials({ filesUpdated: [file.replace(spaPath + path.sep, '')] });
|
|
661
730
|
});
|
|
662
731
|
watcher.on('fallback', notifyWatcherIssue);
|
|
@@ -671,7 +740,7 @@ class CodeInjector {
|
|
|
671
740
|
await fs.promises.access(customComponentsDir, fs.constants.F_OK);
|
|
672
741
|
}
|
|
673
742
|
catch (e) {
|
|
674
|
-
|
|
743
|
+
process.env.HEAVY_DEBUG && console.log(`🪲Custom components dir ${customComponentsDir} does not exist, skipping watching`);
|
|
675
744
|
return;
|
|
676
745
|
}
|
|
677
746
|
// get all subdirs
|
|
@@ -696,21 +765,21 @@ class CodeInjector {
|
|
|
696
765
|
await collectDirectories(customComponentsDir);
|
|
697
766
|
const watcher = filewatcher({ debounce: 30 });
|
|
698
767
|
files.forEach((file) => {
|
|
699
|
-
|
|
768
|
+
process.env.HEAVY_DEBUG && console.log(`🪲🔎 Watch for file ${file}`);
|
|
700
769
|
watcher.add(file);
|
|
701
770
|
});
|
|
702
|
-
|
|
771
|
+
process.env.HEAVY_DEBUG && console.log(`🪲🔎 Watch for: ${directories.join(',')}`);
|
|
703
772
|
watcher.on('change', async (fileOrDir) => {
|
|
704
773
|
// copy one file
|
|
705
774
|
const relativeFilename = fileOrDir.replace(customComponentsDir + path.sep, '');
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
775
|
+
process.env.HEAVY_DEBUG && console.log(`🔎 fileOrDir ${fileOrDir} changed`);
|
|
776
|
+
process.env.HEAVY_DEBUG && console.log(`🔎 relativeFilename ${relativeFilename}`);
|
|
777
|
+
process.env.HEAVY_DEBUG && console.log(`🔎 customComponentsDir ${customComponentsDir}`);
|
|
778
|
+
process.env.HEAVY_DEBUG && console.log(`🔎 destination ${destination}`);
|
|
710
779
|
const isFile = fs.lstatSync(fileOrDir).isFile();
|
|
711
780
|
if (isFile) {
|
|
712
781
|
const destPath = path.join(this.spaTmpPath(), 'src', 'custom', destination, relativeFilename);
|
|
713
|
-
|
|
782
|
+
process.env.HEAVY_DEBUG && console.log(`🔎 Copying file ${fileOrDir} to ${destPath}`);
|
|
714
783
|
await fsExtra.copy(fileOrDir, destPath);
|
|
715
784
|
return;
|
|
716
785
|
}
|
|
@@ -728,7 +797,7 @@ class CodeInjector {
|
|
|
728
797
|
}
|
|
729
798
|
catch (e) {
|
|
730
799
|
// file does not exist
|
|
731
|
-
|
|
800
|
+
process.env.HEAVY_DEBUG && console.log(`🪲File ${filePath} does not exist, returning null`);
|
|
732
801
|
return null;
|
|
733
802
|
}
|
|
734
803
|
}
|
|
@@ -738,7 +807,9 @@ class CodeInjector {
|
|
|
738
807
|
const filePath = path.join(folderPath, file.name);
|
|
739
808
|
// 🚫 Skip big files or files which might be dynamic
|
|
740
809
|
if (file.name === 'node_modules' || file.name === 'dist' ||
|
|
741
|
-
file.name === 'i18n-messages.json' || file.name === 'i18n-empty.json'
|
|
810
|
+
file.name === 'i18n-messages.json' || file.name === 'i18n-empty.json' ||
|
|
811
|
+
file.name === 'hashes.json' || file.name === 'package.json' ||
|
|
812
|
+
file.name === 'pnpm-lock.yaml' || file.name === 'package-lock.json') {
|
|
742
813
|
return '';
|
|
743
814
|
}
|
|
744
815
|
allFiles.push(filePath);
|
|
@@ -752,8 +823,49 @@ class CodeInjector {
|
|
|
752
823
|
}));
|
|
753
824
|
return md5hash(hashes.join(''));
|
|
754
825
|
}
|
|
826
|
+
// Compute a map of file relative paths -> md5 hash of file contents and return it.
|
|
827
|
+
// Skips directories/files that are ignored by computeSourcesHash (node_modules, dist, i18n files).
|
|
828
|
+
async computeSourcesHashMap(folderPath = this.spaTmpPath(), rootFolder = this.spaTmpPath(), map = {}) {
|
|
829
|
+
const files = await fs.promises.readdir(folderPath, { withFileTypes: true });
|
|
830
|
+
await Promise.all(files.map(async (file) => {
|
|
831
|
+
const filePath = path.join(folderPath, file.name);
|
|
832
|
+
// 🚫 Skip big/dynamic folders or files
|
|
833
|
+
if (file.name === 'node_modules' || file.name === 'dist' ||
|
|
834
|
+
file.name === 'i18n-messages.json' || file.name === 'i18n-empty.json' ||
|
|
835
|
+
file.name === 'hashes.json' || file.name === 'package.json' ||
|
|
836
|
+
file.name === 'pnpm-lock.yaml' || file.name === 'package-lock.json') {
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
if (file.isDirectory()) {
|
|
840
|
+
return await this.computeSourcesHashMap(filePath, rootFolder, map);
|
|
841
|
+
}
|
|
842
|
+
else {
|
|
843
|
+
try {
|
|
844
|
+
const content = await fs.promises.readFile(filePath, 'utf-8');
|
|
845
|
+
const hash = md5hash(content);
|
|
846
|
+
// store relative path using forward slashes for portability
|
|
847
|
+
const rel = path.relative(rootFolder, filePath).split(path.sep).join('/');
|
|
848
|
+
map[rel] = hash;
|
|
849
|
+
}
|
|
850
|
+
catch (e) {
|
|
851
|
+
// If a file can't be read (binary or permission), log and continue
|
|
852
|
+
process.env.HEAVY_DEBUG && console.log(`🪲File ${filePath} read error: ${e}`);
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
}));
|
|
857
|
+
return map;
|
|
858
|
+
}
|
|
859
|
+
// Convenience helper: compute per-file hashes and save them into hashes.json in the spa tmp dir
|
|
860
|
+
async saveSourcesHashesToFile(outputFileName = 'hashes.json', hashMap = {}) {
|
|
861
|
+
const root = this.spaTmpPath();
|
|
862
|
+
const outPath = path.join(root, outputFileName);
|
|
863
|
+
await fs.promises.writeFile(outPath, JSON.stringify(hashMap, null, 2), 'utf-8');
|
|
864
|
+
process.env.HEAVY_DEBUG && console.log(`🪲 Saved sources hashes to ${outPath}`);
|
|
865
|
+
return outPath;
|
|
866
|
+
}
|
|
755
867
|
async bundleNow({ hotReload = false }) {
|
|
756
|
-
|
|
868
|
+
console.log(`${this.adminforth.formatAdminForth()} Bundling ${hotReload ? 'and listening for changes (🔥 Hotreload)' : ' (no hot reload)'}`);
|
|
757
869
|
this.adminforth.runningHotReload = hotReload;
|
|
758
870
|
await this.prepareSources();
|
|
759
871
|
if (hotReload) {
|
|
@@ -771,14 +883,12 @@ class CodeInjector {
|
|
|
771
883
|
const serveDir = this.getServeDir();
|
|
772
884
|
const allFiles = [];
|
|
773
885
|
const sourcesHash = await this.computeSourcesHash(this.spaTmpPath(), allFiles);
|
|
774
|
-
|
|
886
|
+
process.env.HEAVY_DEBUG && console.log(`🪲🪲 allFiles:, ${JSON.stringify(allFiles.sort((a, b) => a.localeCompare(b)), null, 1)}`);
|
|
775
887
|
const buildHash = await this.tryReadFile(path.join(serveDir, '.adminforth_build_hash'));
|
|
776
888
|
const messagesHash = await this.tryReadFile(path.join(serveDir, '.adminforth_messages_hash'));
|
|
777
889
|
const skipBuild = buildHash === sourcesHash;
|
|
778
890
|
const skipExtract = messagesHash === sourcesHash;
|
|
779
|
-
|
|
780
|
-
afLogger.trace(`🪲 SPA messages hash: ${messagesHash}`);
|
|
781
|
-
afLogger.trace(`🪲 SPA sources hash: ${sourcesHash}`);
|
|
891
|
+
process.env.HEAVY_DEBUG && console.log(`🪲 SPA messages hash: ${messagesHash}`);
|
|
782
892
|
if (!skipBuild) {
|
|
783
893
|
// remove serveDir if exists
|
|
784
894
|
try {
|
|
@@ -790,7 +900,7 @@ class CodeInjector {
|
|
|
790
900
|
await fs.promises.mkdir(serveDir, { recursive: true });
|
|
791
901
|
}
|
|
792
902
|
if (!skipExtract) {
|
|
793
|
-
await this.
|
|
903
|
+
await this.runPackageManagerShell({ command: 'run i18n:extract', cwd });
|
|
794
904
|
// create serveDir if not exists
|
|
795
905
|
await fs.promises.mkdir(serveDir, { recursive: true });
|
|
796
906
|
// copy i18n messages to serve dir
|
|
@@ -799,51 +909,98 @@ class CodeInjector {
|
|
|
799
909
|
await fs.promises.writeFile(path.join(serveDir, '.adminforth_messages_hash'), sourcesHash);
|
|
800
910
|
}
|
|
801
911
|
else {
|
|
802
|
-
|
|
912
|
+
console.log(`AdminForth i18n message extraction skipped — build already performed for the current sources.`);
|
|
803
913
|
}
|
|
804
914
|
if (!hotReload) {
|
|
805
915
|
if (!skipBuild) {
|
|
916
|
+
console.log(`🪲 Build cache miss or outdated, building SPA...`);
|
|
917
|
+
let oldHashForFiles = null;
|
|
918
|
+
try {
|
|
919
|
+
oldHashForFiles = await fs.promises.readFile(path.join(this.spaTmpPath(), 'hashes.json'), 'utf-8');
|
|
920
|
+
}
|
|
921
|
+
catch (e) {
|
|
922
|
+
// ignore if file doesn't exist, it is only for debugging
|
|
923
|
+
console.log(`Build cache not found, building now (downtime) please consider running npx adminforth bundle at build time to avoid downtimes at runtime`);
|
|
924
|
+
}
|
|
925
|
+
const root = this.spaTmpPath();
|
|
926
|
+
const hashMap = await this.computeSourcesHashMap(root, root, {});
|
|
927
|
+
if (oldHashForFiles) {
|
|
928
|
+
const parsedOldHashForFiles = JSON.parse(oldHashForFiles);
|
|
929
|
+
const logsToDisplay = [];
|
|
930
|
+
logsToDisplay.push(`Build cache exists but is outdated:`);
|
|
931
|
+
for (const [file, hash] of Object.entries(hashMap)) {
|
|
932
|
+
if (!parsedOldHashForFiles[file]) {
|
|
933
|
+
logsToDisplay.push(` - file ${file} - does not exist in cache but exists in runtime`);
|
|
934
|
+
}
|
|
935
|
+
else if (parsedOldHashForFiles[file] !== hash) {
|
|
936
|
+
logsToDisplay.push(` - file ${file} - content in cache is different then in runtime`);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Currently we can't detect, if file was removed,
|
|
941
|
+
* because we can only add files to the tpm folder but not remove them,
|
|
942
|
+
* so if file existed before and now doesn't exist, we will not detect it
|
|
943
|
+
*/
|
|
944
|
+
// for(const [file, hash] of Object.entries(parsedOldHashForFiles)) {
|
|
945
|
+
// console.log(`checking file ${file} in old hash: ${hash}`);
|
|
946
|
+
// console.log(`checking file ${file} in new hash: ${hashMap[file]}`);
|
|
947
|
+
// if (!hashMap[file]) {
|
|
948
|
+
// logsToDisplay.push(` - file ${file} - exists in cache but does not exist in runtime`);
|
|
949
|
+
// }
|
|
950
|
+
// }
|
|
951
|
+
logsToDisplay.push(`If you are running in production now, then the cache loss is a downtime issue.`);
|
|
952
|
+
logsToDisplay.push(`If you have npx adminforth bundle in build time, then this issue might be caused by conditional instantiation of plugins:`);
|
|
953
|
+
logsToDisplay.push(`Please avoid constructions like (process.env.SOME_KEY ? new Plugin(...) ) because if you will miss SOME_KEY in build time build cache and functionality fails.`);
|
|
954
|
+
if (logsToDisplay.length > 4) {
|
|
955
|
+
for (const log of logsToDisplay) {
|
|
956
|
+
console.log(log);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
806
960
|
// TODO probably add option to build with tsh check (plain 'build')
|
|
807
|
-
await this.
|
|
961
|
+
await this.runPackageManagerShell({ command: 'run build-only', cwd });
|
|
808
962
|
// coy dist to serveDir
|
|
809
963
|
await fsExtra.copy(path.join(cwd, 'dist'), serveDir, { recursive: true });
|
|
810
964
|
// save hash
|
|
811
965
|
await fs.promises.writeFile(path.join(serveDir, '.adminforth_build_hash'), sourcesHash);
|
|
966
|
+
// save sources hashes to file for later debugging if needed
|
|
967
|
+
await this.saveSourcesHashesToFile('hashes.json', hashMap);
|
|
812
968
|
}
|
|
813
969
|
else {
|
|
814
|
-
|
|
970
|
+
console.log(`Skipping AdminForth SPA bundling - already completed for the current sources.`);
|
|
815
971
|
}
|
|
816
972
|
}
|
|
817
973
|
else {
|
|
818
974
|
const command = 'run dev';
|
|
819
|
-
|
|
975
|
+
const usersPackageManager = await this.doesUserHasPnpmLockFile(this.adminforth.config.customization.customComponentsDir) ? 'pnpm' : 'npm';
|
|
976
|
+
console.log(`⚙️ spawn: ${usersPackageManager} ${command}...`);
|
|
820
977
|
if (process.env.VITE_ADMINFORTH_PUBLIC_PATH) {
|
|
821
|
-
|
|
978
|
+
console.log(`⚠️ Your VITE_ADMINFORTH_PUBLIC_PATH: ${process.env.VITE_ADMINFORTH_PUBLIC_PATH} has no effect`);
|
|
822
979
|
}
|
|
823
980
|
const env = Object.assign({ VITE_ADMINFORTH_PUBLIC_PATH: this.adminforth.config.baseUrl, FORCE_COLOR: '1' }, process.env);
|
|
824
981
|
const nodeBinary = process.execPath;
|
|
825
|
-
const
|
|
982
|
+
const packageManagerPath = path.join(path.dirname(nodeBinary), usersPackageManager);
|
|
826
983
|
let devServer;
|
|
827
984
|
if (process.platform === 'win32') {
|
|
828
|
-
devServer = spawn(
|
|
985
|
+
devServer = spawn(usersPackageManager, command.split(' '), { cwd, env, shell: true });
|
|
829
986
|
}
|
|
830
987
|
else {
|
|
831
|
-
devServer = spawn(`${nodeBinary}`, [`${
|
|
988
|
+
devServer = spawn(`${nodeBinary}`, [`${packageManagerPath}`, ...command.split(' ')], { cwd, env });
|
|
832
989
|
}
|
|
833
990
|
devServer.stdout.on('data', (data) => {
|
|
834
991
|
if (data.includes('➜')) {
|
|
835
992
|
// TODO: maybe better use our string "App port: 5174. HMR port: 5274", it is more reliable because vue might change their output
|
|
836
993
|
// parse port from message " ➜ Local: http://localhost:xyz/"
|
|
837
994
|
const s = stripAnsiCodes(data.toString());
|
|
838
|
-
|
|
995
|
+
process.env.HEAVY_DEBUG && console.log(`🪲 devServer stdout ➜ (port detect): ${s}`);
|
|
839
996
|
const portMatch = s.match(/.+?http:\/\/.+?:(\d+).+?/m);
|
|
840
997
|
if (portMatch) {
|
|
841
998
|
this.devServerPort = parseInt(portMatch[1]);
|
|
842
999
|
}
|
|
843
1000
|
}
|
|
844
1001
|
else {
|
|
845
|
-
|
|
846
|
-
|
|
1002
|
+
process.env.HEAVY_DEBUG && console.log(`[AdminForth SPA]:`);
|
|
1003
|
+
process.env.HEAVY_DEBUG && console.log(data.toString());
|
|
847
1004
|
}
|
|
848
1005
|
});
|
|
849
1006
|
devServer.stderr.on('data', (data) => {
|