kempo 4.2.20 → 4.2.21
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/bin/cli.js +4 -21
- package/dist/admin/accounts/groups/[name]/index.page.html +24 -24
- package/dist/admin/accounts/groups/index.page.html +30 -30
- package/dist/admin/accounts/permissions/index.page.html +30 -30
- package/dist/admin/accounts/users/[id]/index.page.html +17 -17
- package/dist/admin/accounts/users/index.page.html +18 -18
- package/dist/admin/components/AddGroupControl.js +1 -5
- package/dist/admin/components/AddGroupControl.js.map +1 -1
- package/dist/admin/components/AdminTableControl.js +2 -0
- package/dist/admin/components/AdminTableControl.js.map +1 -0
- package/dist/admin/components/ClearExpiredSessionsControl.js +1 -5
- package/dist/admin/components/ClearExpiredSessionsControl.js.map +1 -1
- package/dist/admin/components/GroupDeleteSelected.js +1 -5
- package/dist/admin/components/GroupDeleteSelected.js.map +1 -1
- package/dist/admin/components/GroupRemoveRecord.js +1 -5
- package/dist/admin/components/GroupRemoveRecord.js.map +1 -1
- package/dist/admin/components/TableRowDelete.js +1 -3
- package/dist/admin/components/TableRowDelete.js.map +1 -1
- package/dist/admin/components/TableRowLink.js +1 -7
- package/dist/admin/components/TableRowLink.js.map +1 -1
- package/dist/admin/components/tableControls/AddMember.js +1 -5
- package/dist/admin/components/tableControls/AddMember.js.map +1 -1
- package/dist/admin/components/tableControls/AddPermission.js +1 -5
- package/dist/admin/components/tableControls/AddPermission.js.map +1 -1
- package/dist/admin/components/tableControls/InstallExtension.js +1 -5
- package/dist/admin/components/tableControls/InstallExtension.js.map +1 -1
- package/dist/admin/components/tableControls/UninstallExtension.js +1 -5
- package/dist/admin/components/tableControls/UninstallExtension.js.map +1 -1
- package/dist/admin/content/fragments/index.page.html +28 -28
- package/dist/admin/content/globals/index.page.html +28 -28
- package/dist/admin/content/pages/index.page.html +28 -28
- package/dist/admin/content/templates/index.page.html +28 -28
- package/dist/admin/extensions/index.page.html +18 -18
- package/dist/admin/init.js.map +1 -1
- package/dist/admin/settings/index.page.html +30 -30
- package/dist/kempo/api/admin-globals/DELETE.js +1 -1
- package/dist/kempo/api/admin-globals/DELETE.js.map +1 -1
- package/dist/kempo/api/admin-globals/GET.js +1 -1
- package/dist/kempo/api/admin-globals/GET.js.map +1 -1
- package/dist/kempo/api/admin-globals/POST.js +1 -1
- package/dist/kempo/api/admin-globals/POST.js.map +1 -1
- package/dist/kempo/api/admin-globals/[id]/GET.js +1 -1
- package/dist/kempo/api/admin-globals/[id]/GET.js.map +1 -1
- package/dist/kempo/api/admin-globals/[id]/PUT.js +1 -1
- package/dist/kempo/api/admin-globals/[id]/PUT.js.map +1 -1
- package/dist/kempo/api/admin-globals/[id]/disable/PUT.js +1 -1
- package/dist/kempo/api/admin-globals/[id]/disable/PUT.js.map +1 -1
- package/dist/kempo/api/admin-globals/[id]/enable/PUT.js +1 -1
- package/dist/kempo/api/admin-globals/[id]/enable/PUT.js.map +1 -1
- package/dist/kempo/api/extensions/DELETE.js +1 -1
- package/dist/kempo/api/extensions/DELETE.js.map +1 -1
- package/dist/kempo/api/extensions/POST.js +1 -1
- package/dist/kempo/api/extensions/POST.js.map +1 -1
- package/dist/kempo/api/extensions/[name]/disable/POST.js +1 -1
- package/dist/kempo/api/extensions/[name]/disable/POST.js.map +1 -1
- package/dist/kempo/api/extensions/[name]/enable/POST.js +1 -1
- package/dist/kempo/api/extensions/[name]/enable/POST.js.map +1 -1
- package/dist/kempo/api/extensions/[name]/update/POST.js +1 -1
- package/dist/kempo/api/extensions/[name]/update/POST.js.map +1 -1
- package/dist/kempo/api/extensions/[name]/update-check/GET.js +1 -1
- package/dist/kempo/api/extensions/[name]/update-check/GET.js.map +1 -1
- package/dist/kempo/api/pages/search/GET.js +1 -1
- package/dist/kempo/api/pages/search/GET.js.map +1 -1
- package/dist/kempo/api/user/[userid]/name/GET.js +2 -0
- package/dist/kempo/api/user/[userid]/name/GET.js.map +1 -0
- package/dist/kempo/components/UserDisplayName.js +2 -0
- package/dist/kempo/components/UserDisplayName.js.map +1 -0
- package/dist/kempo/sdk.js +1 -1
- package/dist/kempo/sdk.js.map +1 -1
- package/middleware/kempo.js +70 -45
- package/package.json +15 -8
- package/server/utils/admin-global-content/createAdminGlobalContent.js +4 -3
- package/server/utils/admin-global-content/deleteAdminGlobalContent.js +2 -2
- package/server/utils/admin-global-content/deleteAdminGlobalContentByOwner.js +2 -2
- package/server/utils/admin-global-content/disableAdminGlobalContent.js +2 -2
- package/server/utils/admin-global-content/enableAdminGlobalContent.js +2 -2
- package/server/utils/admin-global-content/getAdminGlobalContent.js +2 -2
- package/server/utils/admin-global-content/helpers.js +12 -1
- package/server/utils/admin-global-content/listAdminGlobalContent.js +2 -2
- package/server/utils/admin-global-content/updateAdminGlobalContent.js +2 -2
- package/server/utils/email/sendEmail.js +9 -3
- package/server/utils/extensions/uninstallExtension.js +54 -21
- package/server/utils/hooks/triggerHook.js +8 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { readFile, writeFile } from 'fs/promises';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { existsSync } from 'fs';
|
|
4
|
-
import { ADMIN_GLOBAL_FILE, parseEntries, serializeEntries } from './helpers.js';
|
|
4
|
+
import { ADMIN_GLOBAL_FILE, parseEntries, serializeEntries, ADMIN_GLOBALS_DIR } from './helpers.js';
|
|
5
5
|
|
|
6
|
-
export default async ({ adminDir, ids }) => {
|
|
6
|
+
export default async ({ adminDir = ADMIN_GLOBALS_DIR, ids }) => {
|
|
7
7
|
if(!adminDir){
|
|
8
8
|
return [{ code: 400, msg: 'Admin directory is required' }, null];
|
|
9
9
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { readFile, writeFile } from 'fs/promises';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { existsSync } from 'fs';
|
|
4
|
-
import { ADMIN_GLOBAL_FILE, parseEntries, serializeEntries } from './helpers.js';
|
|
4
|
+
import { ADMIN_GLOBAL_FILE, parseEntries, serializeEntries, ADMIN_GLOBALS_DIR } from './helpers.js';
|
|
5
5
|
|
|
6
|
-
export default async ({ adminDir, owner }) => {
|
|
6
|
+
export default async ({ adminDir = ADMIN_GLOBALS_DIR, owner }) => {
|
|
7
7
|
if(!adminDir){
|
|
8
8
|
return [{ code: 400, msg: 'Admin directory is required' }, null];
|
|
9
9
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { readFile, writeFile } from 'fs/promises';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { existsSync } from 'fs';
|
|
4
|
-
import { ADMIN_GLOBAL_FILE, parseEntries, serializeEntries } from './helpers.js';
|
|
4
|
+
import { ADMIN_GLOBAL_FILE, parseEntries, serializeEntries, ADMIN_GLOBALS_DIR } from './helpers.js';
|
|
5
5
|
|
|
6
|
-
export default async ({ adminDir, id }) => {
|
|
6
|
+
export default async ({ adminDir = ADMIN_GLOBALS_DIR, id }) => {
|
|
7
7
|
if(!adminDir){
|
|
8
8
|
return [{ code: 400, msg: 'Admin directory is required' }, null];
|
|
9
9
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { readFile, writeFile } from 'fs/promises';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { existsSync } from 'fs';
|
|
4
|
-
import { ADMIN_GLOBAL_FILE, parseEntries, serializeEntries } from './helpers.js';
|
|
4
|
+
import { ADMIN_GLOBAL_FILE, parseEntries, serializeEntries, ADMIN_GLOBALS_DIR } from './helpers.js';
|
|
5
5
|
|
|
6
|
-
export default async ({ adminDir, id }) => {
|
|
6
|
+
export default async ({ adminDir = ADMIN_GLOBALS_DIR, id }) => {
|
|
7
7
|
if(!adminDir){
|
|
8
8
|
return [{ code: 400, msg: 'Admin directory is required' }, null];
|
|
9
9
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { readFile } from 'fs/promises';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { existsSync } from 'fs';
|
|
4
|
-
import { ADMIN_GLOBAL_FILE, parseEntries } from './helpers.js';
|
|
4
|
+
import { ADMIN_GLOBAL_FILE, parseEntries, ADMIN_GLOBALS_DIR } from './helpers.js';
|
|
5
5
|
|
|
6
|
-
export default async ({ adminDir, id }) => {
|
|
6
|
+
export default async ({ adminDir = ADMIN_GLOBALS_DIR, id }) => {
|
|
7
7
|
if(!adminDir){
|
|
8
8
|
return [{ code: 400, msg: 'Admin directory is required' }, null];
|
|
9
9
|
}
|
|
@@ -4,6 +4,17 @@ import { existsSync } from 'fs';
|
|
|
4
4
|
|
|
5
5
|
const ADMIN_GLOBAL_FILE = 'kempo-global.global.html';
|
|
6
6
|
|
|
7
|
+
/*
|
|
8
|
+
Admin global content created through the admin UI is per-installation state, so it lives in the
|
|
9
|
+
consumer's project rather than in kempo's dist/admin — `npm run build` deletes dist/ wholesale and
|
|
10
|
+
reinstalling or upgrading kempo replaces it, either of which would silently discard it.
|
|
11
|
+
|
|
12
|
+
Extension-owned admin globals are NOT stored here. Each extension ships its own *.global.html
|
|
13
|
+
inside its package and the middleware scans it at render time, the same way extension pages are
|
|
14
|
+
resolved, so there is nothing to write on install or clean up on uninstall.
|
|
15
|
+
*/
|
|
16
|
+
const ADMIN_GLOBALS_DIR = join(process.cwd(), '.kempo', 'admin-globals');
|
|
17
|
+
|
|
7
18
|
const parseEntries = raw => {
|
|
8
19
|
const entries = [];
|
|
9
20
|
const regex = /<content\s([^>]*)>([\s\S]*?)<\/content>/g;
|
|
@@ -44,4 +55,4 @@ const serializeEntries = entries => entries.map(e => {
|
|
|
44
55
|
return `<content ${attrs}>\n${e.markup}\n</content>`;
|
|
45
56
|
}).join('\n');
|
|
46
57
|
|
|
47
|
-
export { ADMIN_GLOBAL_FILE, parseEntries, serializeEntries };
|
|
58
|
+
export { ADMIN_GLOBAL_FILE, ADMIN_GLOBALS_DIR, parseEntries, serializeEntries };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { readFile } from 'fs/promises';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { existsSync } from 'fs';
|
|
4
|
-
import { ADMIN_GLOBAL_FILE, parseEntries } from './helpers.js';
|
|
4
|
+
import { ADMIN_GLOBAL_FILE, parseEntries, ADMIN_GLOBALS_DIR } from './helpers.js';
|
|
5
5
|
|
|
6
|
-
export default async ({ adminDir }) => {
|
|
6
|
+
export default async ({ adminDir = ADMIN_GLOBALS_DIR } = {}) => {
|
|
7
7
|
if(!adminDir){
|
|
8
8
|
return [{ code: 400, msg: 'Admin directory is required' }, null];
|
|
9
9
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { readFile, writeFile } from 'fs/promises';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { existsSync } from 'fs';
|
|
4
|
-
import { ADMIN_GLOBAL_FILE, parseEntries, serializeEntries } from './helpers.js';
|
|
4
|
+
import { ADMIN_GLOBAL_FILE, parseEntries, serializeEntries, ADMIN_GLOBALS_DIR } from './helpers.js';
|
|
5
5
|
|
|
6
|
-
export default async ({ adminDir, id, name, location, priority, markup }) => {
|
|
6
|
+
export default async ({ adminDir = ADMIN_GLOBALS_DIR, id, name, location, priority, markup }) => {
|
|
7
7
|
if(!adminDir){
|
|
8
8
|
return [{ code: 400, msg: 'Admin directory is required' }, null];
|
|
9
9
|
}
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { Resend } from 'resend';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/*
|
|
4
|
+
Constructed on first send, not at import. The Resend constructor throws when the key is missing,
|
|
5
|
+
and this module is reachable from the server SDK, so building it at module scope made an unset
|
|
6
|
+
RESEND_API_KEY fail every route that transitively imports it rather than just disabling email.
|
|
7
|
+
*/
|
|
8
|
+
let resend = null;
|
|
9
|
+
const client = () => (resend ??= new Resend(process.env.RESEND_API_KEY));
|
|
4
10
|
|
|
5
11
|
export default async ({ to, subject, html }) => {
|
|
6
12
|
if(!process.env.RESEND_API_KEY){
|
|
7
13
|
return [{ code: 500, msg: 'RESEND_API_KEY not configured' }, null];
|
|
8
14
|
}
|
|
9
|
-
|
|
15
|
+
|
|
10
16
|
if(!to){
|
|
11
17
|
return [{ code: 400, msg: 'Recipient email is required' }, null];
|
|
12
18
|
}
|
|
@@ -22,7 +28,7 @@ export default async ({ to, subject, html }) => {
|
|
|
22
28
|
const from = process.env.SMTP_FROM || 'onboarding@resend.dev';
|
|
23
29
|
|
|
24
30
|
try {
|
|
25
|
-
const { data, error } = await
|
|
31
|
+
const { data, error } = await client().emails.send({
|
|
26
32
|
from,
|
|
27
33
|
to,
|
|
28
34
|
subject,
|
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
import db from '../../db/index.js';
|
|
2
|
-
import { extension, hook, permission, setting, group } from '../../db/schema.js';
|
|
3
|
-
import { eq, inArray } from 'drizzle-orm';
|
|
2
|
+
import { extension, hook, permission, setting, group, groupPermission, userGroup } from '../../db/schema.js';
|
|
3
|
+
import { eq, inArray, like } from 'drizzle-orm';
|
|
4
4
|
import { join } from 'path';
|
|
5
5
|
import { pathToFileURL } from 'url';
|
|
6
6
|
import { sql } from 'drizzle-orm';
|
|
7
7
|
import triggerHook, { clearHandlerCache } from '../hooks/triggerHook.js';
|
|
8
8
|
import { invalidateScopeCache } from './scopeCache.js';
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
/*
|
|
11
|
+
Uninstall an extension.
|
|
12
|
+
|
|
13
|
+
`purgeData` false (the default) deregisters the extension but leaves everything it created in
|
|
14
|
+
place — its tables and their rows, group memberships, permissions, groups and settings — so
|
|
15
|
+
reinstalling restores the site exactly as it was. This is the right mode for reinstalling to pick
|
|
16
|
+
up a fix.
|
|
17
|
+
|
|
18
|
+
`purgeData` true additionally destroys all of that. For an extension like kempo-blog that means
|
|
19
|
+
every post and comment, so it must always be a deliberate choice rather than a default.
|
|
20
|
+
|
|
21
|
+
Hooks and the extension row are removed either way. A registered hook whose module is no longer
|
|
22
|
+
installed cannot be tolerated: `middleware:before_page` is triggered with `bail: true`, so a
|
|
23
|
+
handler that fails to import now aborts the render and every page 404s.
|
|
24
|
+
*/
|
|
25
|
+
export default async ({ name, purgeData = false }) => {
|
|
11
26
|
if(!name){
|
|
12
27
|
return [{ code: 400, msg: 'Extension name is required' }, null];
|
|
13
28
|
}
|
|
@@ -32,9 +47,9 @@ export default async ({ name }) => {
|
|
|
32
47
|
} catch {}
|
|
33
48
|
|
|
34
49
|
/*
|
|
35
|
-
Drop declarative schema tables
|
|
50
|
+
Drop declarative schema tables. Only when purging — these hold the extension's actual content.
|
|
36
51
|
*/
|
|
37
|
-
if(kempoConfig.schema){
|
|
52
|
+
if(purgeData && kempoConfig.schema){
|
|
38
53
|
try {
|
|
39
54
|
const extRoot = join(process.cwd(), 'node_modules', name);
|
|
40
55
|
const schemaModule = await import(pathToFileURL(join(extRoot, kempoConfig.schema)).href);
|
|
@@ -48,25 +63,43 @@ export default async ({ name }) => {
|
|
|
48
63
|
}
|
|
49
64
|
|
|
50
65
|
/*
|
|
51
|
-
Remove declarative groups
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if(groupNames.length){
|
|
55
|
-
await db.delete(group).where(inArray(group.name, groupNames)).catch(() => {});
|
|
56
|
-
}
|
|
66
|
+
Remove declarative groups, permissions and settings. Only when purging: these are what a
|
|
67
|
+
reinstall would otherwise restore, and group memberships in particular represent real user
|
|
68
|
+
state — every registered user of kempo-blog belongs to kempo-blog:member.
|
|
57
69
|
|
|
58
|
-
|
|
59
|
-
|
|
70
|
+
userGroup and groupPermission both reference group.name, and groupPermission also references
|
|
71
|
+
permission.name, none of them cascading. The membership and grant rows therefore have to go
|
|
72
|
+
first or these deletes fail the foreign key — which they used to do silently, leaving every
|
|
73
|
+
uninstalled extension's groups and permissions behind forever.
|
|
60
74
|
*/
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
75
|
+
if(purgeData){
|
|
76
|
+
const groupNames = (kempoConfig.groups || []).map(g => g.name);
|
|
77
|
+
const permNames = (kempoConfig.permissions || []).map(p => p.name);
|
|
65
78
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
79
|
+
try {
|
|
80
|
+
if(groupNames.length){
|
|
81
|
+
await db.delete(userGroup).where(inArray(userGroup.groupName, groupNames));
|
|
82
|
+
await db.delete(groupPermission).where(inArray(groupPermission.groupName, groupNames));
|
|
83
|
+
}
|
|
84
|
+
if(permNames.length){
|
|
85
|
+
await db.delete(groupPermission).where(inArray(groupPermission.permissionName, permNames));
|
|
86
|
+
}
|
|
87
|
+
if(groupNames.length){
|
|
88
|
+
await db.delete(group).where(inArray(group.name, groupNames));
|
|
89
|
+
}
|
|
90
|
+
if(permNames.length){
|
|
91
|
+
await db.delete(permission).where(inArray(permission.name, permNames));
|
|
92
|
+
}
|
|
93
|
+
} catch(error) {
|
|
94
|
+
return [{ code: 500, msg: `Failed to remove extension groups and permissions: ${error.message}` }, null];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/*
|
|
98
|
+
Ownership is encoded in the setting name as "extname:settingname" — the setting table has no
|
|
99
|
+
owner column, so these have to be matched by prefix.
|
|
100
|
+
*/
|
|
101
|
+
await db.delete(setting).where(like(setting.name, `${name}:%`));
|
|
102
|
+
}
|
|
70
103
|
|
|
71
104
|
try {
|
|
72
105
|
await db.delete(hook).where(eq(hook.owner, name));
|
|
@@ -34,7 +34,14 @@ export default async (event, data = {}, { bail = false } = {}) => {
|
|
|
34
34
|
const mod = await import(pathToFileURL(handlerPath).href);
|
|
35
35
|
handler = mod.default || mod;
|
|
36
36
|
handlerCache.set(handlerPath, handler);
|
|
37
|
-
} catch {
|
|
37
|
+
} catch(err) {
|
|
38
|
+
/*
|
|
39
|
+
Never fail silently: a hook that cannot load is indistinguishable from one that
|
|
40
|
+
allowed the request. For bailing events (guards) that would fail open, so rethrow.
|
|
41
|
+
*/
|
|
42
|
+
console.error(`[kempo] Hook handler failed to load for "${event}" (${h.owner}): ${err.message}`);
|
|
43
|
+
if(bail) throw err;
|
|
44
|
+
results.push({ hookId: h.id, owner: h.owner, error: err.message });
|
|
38
45
|
continue;
|
|
39
46
|
}
|
|
40
47
|
}
|