kempo 4.5.7 → 4.5.8
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/README.md +1 -1
- package/middleware/kempo.js +9 -6
- package/package.json +1 -1
- package/server/utils/extensions/getExtension.js +4 -3
- package/server/utils/extensions/installExtension.js +2 -6
- package/server/utils/extensions/listExtensions.js +8 -7
- package/server/utils/extensions/readKempoConfig.js +20 -0
- package/server/utils/extensions/updateExtension.js +9 -7
package/README.md
CHANGED
|
@@ -359,7 +359,7 @@ Extensions can push live updates to browsers: declare a channel, publish to it f
|
|
|
359
359
|
|
|
360
360
|
## Admin Dashboard
|
|
361
361
|
|
|
362
|
-
The admin home page is a grid of widgets one to four blocks wide and one to three tall. Kempo provides widgets for pages, content, users, notifications and extensions; extensions add their own with `kempo.
|
|
362
|
+
The admin home page is a grid of widgets one to four blocks wide and one to three tall. Kempo provides widgets for pages, content, users, notifications and extensions; extensions add their own with `widgets` in `kempo-config.json` or `registerDashboardWidget`. Each person chooses which are shown, their order and size, kept in their browser's localStorage, and a widget only loads its script and data once it is shown. Existing sites need `npx drizzle-kit push` to add the `dashboardWidget` table (until then only Kempo's own widgets appear). See [docs/dashboard.md](docs/dashboard.md).
|
|
363
363
|
|
|
364
364
|
## Notifications
|
|
365
365
|
|
package/middleware/kempo.js
CHANGED
|
@@ -6,6 +6,7 @@ import getSession from '../server/utils/auth/getSession.js';
|
|
|
6
6
|
import currentUserHasPermission from '../server/utils/permissions/currentUserHasPermission.js';
|
|
7
7
|
import { LEXICAL_BASE, bundleFileName as lexicalBundleFileName } from '../server/utils/lexical/packages.js';
|
|
8
8
|
import { getEnabledExtensions } from '../server/utils/extensions/scopeCache.js';
|
|
9
|
+
import readKempoConfig from '../server/utils/extensions/readKempoConfig.js';
|
|
9
10
|
import { extensionAdminDirs, extensionPublicDirs } from '../server/utils/extensions/contentDirs.js';
|
|
10
11
|
import triggerHook from '../server/utils/hooks/triggerHook.js';
|
|
11
12
|
import { ADMIN_GLOBALS_DIR } from '../server/utils/admin-global-content/helpers.js';
|
|
@@ -394,19 +395,21 @@ export default config => {
|
|
|
394
395
|
const publicContentDirs = await extensionPublicDirs();
|
|
395
396
|
|
|
396
397
|
for(const ext of extensions){
|
|
397
|
-
|
|
398
|
+
const extRoot = join(NODE_MODULES, ext.name);
|
|
399
|
+
let kempo;
|
|
398
400
|
try {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
401
|
+
({ kempo } = await readKempoConfig(extRoot));
|
|
402
|
+
} catch {
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
402
405
|
|
|
403
|
-
const scope =
|
|
406
|
+
const scope = kempo['public-scope'];
|
|
404
407
|
if(!scope) continue;
|
|
405
408
|
|
|
406
409
|
const scopePrefix = `/${scope}`;
|
|
407
410
|
if(url !== scopePrefix && !url.startsWith(`${scopePrefix}/`)) continue;
|
|
408
411
|
|
|
409
|
-
const publicDir = join(
|
|
412
|
+
const publicDir = join(extRoot, 'public');
|
|
410
413
|
const subPath = url.slice(scopePrefix.length) || '/';
|
|
411
414
|
const method = request.method?.toUpperCase() || 'GET';
|
|
412
415
|
const resolveDir = buildResolveDir(PROJECT_PUBLIC, url);
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@ import db from '../../db/index.js';
|
|
|
2
2
|
import { extension } from '../../db/schema.js';
|
|
3
3
|
import { eq } from 'drizzle-orm';
|
|
4
4
|
import { join } from 'path';
|
|
5
|
-
import
|
|
5
|
+
import readKempoConfig from './readKempoConfig.js';
|
|
6
6
|
|
|
7
7
|
const NODE_MODULES = join(process.cwd(), 'node_modules');
|
|
8
8
|
|
|
@@ -18,15 +18,16 @@ export default async ({ name }) => {
|
|
|
18
18
|
}
|
|
19
19
|
const ext = results[0];
|
|
20
20
|
let pkg = {};
|
|
21
|
+
let kempo = {};
|
|
21
22
|
try {
|
|
22
|
-
pkg =
|
|
23
|
+
({ pkg, kempo } = await readKempoConfig(join(NODE_MODULES, name)));
|
|
23
24
|
} catch { /* package may not be installed */ }
|
|
24
25
|
return [null, {
|
|
25
26
|
...ext,
|
|
26
27
|
description: pkg.description || null,
|
|
27
28
|
author: pkg.author || null,
|
|
28
29
|
license: pkg.license || null,
|
|
29
|
-
docs:
|
|
30
|
+
docs: kempo.docs || null,
|
|
30
31
|
}];
|
|
31
32
|
} catch(error) {
|
|
32
33
|
return [{ code: 500, msg: 'Failed to get extension' }, null];
|
|
@@ -4,7 +4,6 @@ import { eq } from 'drizzle-orm';
|
|
|
4
4
|
import { join } from 'path';
|
|
5
5
|
import { pathToFileURL } from 'url';
|
|
6
6
|
import { existsSync } from 'fs';
|
|
7
|
-
import { readFile } from 'fs/promises';
|
|
8
7
|
import { randomUUID } from 'crypto';
|
|
9
8
|
import triggerHook, { clearHandlerCache } from '../hooks/triggerHook.js';
|
|
10
9
|
import createHook from '../hooks/createHook.js';
|
|
@@ -12,6 +11,7 @@ import registerDashboardWidget from '../dashboard/registerDashboardWidget.js';
|
|
|
12
11
|
import setSetting from '../settings/setSetting.js';
|
|
13
12
|
import declaredValue from '../settings/declaredValue.js';
|
|
14
13
|
import createTablesFromSchema from './createTablesFromSchema.js';
|
|
14
|
+
import readKempoConfig from './readKempoConfig.js';
|
|
15
15
|
import { invalidateScopeCache } from './scopeCache.js';
|
|
16
16
|
import { getUnmetDependencies } from './dependencies.js';
|
|
17
17
|
|
|
@@ -31,7 +31,6 @@ export default async ({ name }) => {
|
|
|
31
31
|
|
|
32
32
|
const extRoot = join(process.cwd(), 'node_modules', name);
|
|
33
33
|
const extPkgPath = join(extRoot, 'package.json');
|
|
34
|
-
const kempoConfigPath = join(extRoot, 'kempo-config.json');
|
|
35
34
|
|
|
36
35
|
if(!existsSync(extPkgPath)){
|
|
37
36
|
return [{ code: 404, msg: `Extension package "${name}" not found` }, null];
|
|
@@ -39,10 +38,7 @@ export default async ({ name }) => {
|
|
|
39
38
|
|
|
40
39
|
let pkg, kempoConfig;
|
|
41
40
|
try {
|
|
42
|
-
pkg =
|
|
43
|
-
kempoConfig = existsSync(kempoConfigPath)
|
|
44
|
-
? JSON.parse(await readFile(kempoConfigPath, 'utf-8'))
|
|
45
|
-
: {};
|
|
41
|
+
({ pkg, kempo: kempoConfig } = await readKempoConfig(extRoot));
|
|
46
42
|
} catch {
|
|
47
43
|
return [{ code: 500, msg: `Failed to read extension package files` }, null];
|
|
48
44
|
}
|
|
@@ -2,15 +2,16 @@ import db from '../../db/index.js';
|
|
|
2
2
|
import { extension } from '../../db/schema.js';
|
|
3
3
|
import { sql } from 'drizzle-orm';
|
|
4
4
|
import { join } from 'path';
|
|
5
|
-
import
|
|
5
|
+
import readKempoConfig from './readKempoConfig.js';
|
|
6
6
|
|
|
7
7
|
const NODE_MODULES = join(process.cwd(), 'node_modules');
|
|
8
8
|
|
|
9
|
-
const
|
|
9
|
+
const readConfig = async name => {
|
|
10
10
|
try {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
return await readKempoConfig(join(NODE_MODULES, name));
|
|
12
|
+
} catch {
|
|
13
|
+
return { pkg: {}, kempo: {} };
|
|
14
|
+
}
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
export default async ({ limit = 50, offset = 0 } = {}) => {
|
|
@@ -20,13 +21,13 @@ export default async ({ limit = 50, offset = 0 } = {}) => {
|
|
|
20
21
|
db.select({ count: sql`count(*)` }).from(extension),
|
|
21
22
|
]);
|
|
22
23
|
const enriched = await Promise.all(items.map(async (ext) => {
|
|
23
|
-
const pkg = await
|
|
24
|
+
const { pkg, kempo } = await readConfig(ext.name);
|
|
24
25
|
return {
|
|
25
26
|
...ext,
|
|
26
27
|
description: pkg.description || null,
|
|
27
28
|
author: pkg.author || null,
|
|
28
29
|
license: pkg.license || null,
|
|
29
|
-
docs:
|
|
30
|
+
docs: kempo.docs || null,
|
|
30
31
|
};
|
|
31
32
|
}));
|
|
32
33
|
return [null, { items: enriched, total: Number(count), limit, offset }];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { join } from 'path';
|
|
2
|
+
import { readFile } from 'fs/promises';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
What an installed extension declares to kempo: its `public-scope`, `docs`, `git`, `schema`, `hooks`,
|
|
6
|
+
`permissions`, `settings`, `groups`, `dependencies` and `widgets`.
|
|
7
|
+
|
|
8
|
+
It lives in the extension's kempo-config.json, which is the one place it is written. package.json's own
|
|
9
|
+
`kempo` field is still read, as the fallback for an older extension that put everything there, and
|
|
10
|
+
kempo-config.json wins field by field. Nothing should add new keys to package.json.
|
|
11
|
+
|
|
12
|
+
Reads both files fresh each time, so an extension replaced on disk is seen without a restart.
|
|
13
|
+
Throws when package.json cannot be read, which is how a caller knows the package is not installed.
|
|
14
|
+
*/
|
|
15
|
+
export default async extRoot => {
|
|
16
|
+
const pkg = JSON.parse(await readFile(join(extRoot, 'package.json'), 'utf-8'));
|
|
17
|
+
const config = await readFile(join(extRoot, 'kempo-config.json'), 'utf-8').then(JSON.parse, () => ({}));
|
|
18
|
+
|
|
19
|
+
return { pkg, kempo: { ...(pkg.kempo || {}), ...config } };
|
|
20
|
+
};
|
|
@@ -3,7 +3,6 @@ import { extension, hook, permission, group, groupPermission } from '../../db/sc
|
|
|
3
3
|
import { eq, and } from 'drizzle-orm';
|
|
4
4
|
import { createRequire } from 'module';
|
|
5
5
|
import { dirname, join } from 'path';
|
|
6
|
-
import { readFile } from 'fs/promises';
|
|
7
6
|
import { randomUUID } from 'crypto';
|
|
8
7
|
import { execSync } from 'child_process';
|
|
9
8
|
import triggerHook, { clearHandlerCache } from '../hooks/triggerHook.js';
|
|
@@ -13,6 +12,7 @@ import unregisterDashboardWidget from '../dashboard/unregisterDashboardWidget.js
|
|
|
13
12
|
import setSetting from '../settings/setSetting.js';
|
|
14
13
|
import declaredValue from '../settings/declaredValue.js';
|
|
15
14
|
import createTablesFromSchema from './createTablesFromSchema.js';
|
|
15
|
+
import readKempoConfig from './readKempoConfig.js';
|
|
16
16
|
|
|
17
17
|
const requireFromProject = createRequire(join(process.cwd(), 'package.json'));
|
|
18
18
|
|
|
@@ -43,17 +43,19 @@ export default async ({ name, version = 'latest' }) => {
|
|
|
43
43
|
return [{ code: 500, msg: `Failed to update package: ${error.message}` }, null];
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
/*
|
|
47
|
+
What the new version declares comes from its kempo-config.json (package.json's own `kempo` field is
|
|
48
|
+
only the fallback for an older extension). Reading package.json alone meant that no extension's new
|
|
49
|
+
permissions, settings, groups, hooks or widgets ever reached the database through this update.
|
|
50
|
+
*/
|
|
51
|
+
let extRoot, pkg, newKempo;
|
|
47
52
|
try {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
pkg = JSON.parse(await readFile(pkgPath, 'utf-8'));
|
|
53
|
+
extRoot = dirname(requireFromProject.resolve(`${name}/package.json`));
|
|
54
|
+
({ pkg, kempo: newKempo } = await readKempoConfig(extRoot));
|
|
51
55
|
} catch {
|
|
52
56
|
return [{ code: 404, msg: `Updated package "${name}" not found` }, null];
|
|
53
57
|
}
|
|
54
58
|
|
|
55
|
-
const newKempo = pkg.kempo || {};
|
|
56
|
-
|
|
57
59
|
/*
|
|
58
60
|
Create any new tables from schema
|
|
59
61
|
*/
|