kempo 4.2.19 → 4.2.20

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 (115) hide show
  1. package/app-public/CATCH.page.html +15 -0
  2. package/app-public/kempo-global.global.html +0 -6
  3. package/dist/admin/accounts/groups/[name]/index.page.html +112 -2
  4. package/dist/admin/accounts/groups/index.page.html +87 -2
  5. package/dist/admin/accounts/permissions/index.page.html +84 -2
  6. package/dist/admin/accounts/users/[id]/index.page.html +117 -3
  7. package/dist/admin/accounts/users/index.page.html +116 -2
  8. package/dist/admin/components/tableControls/InstallExtension.js +2 -2
  9. package/dist/admin/components/tableControls/InstallExtension.js.map +1 -1
  10. package/dist/admin/components/tableControls/UninstallExtension.js +2 -2
  11. package/dist/admin/components/tableControls/UninstallExtension.js.map +1 -1
  12. package/dist/admin/content/fragments/edit/index.page.html +8 -1
  13. package/dist/admin/content/fragments/index.page.html +87 -2
  14. package/dist/admin/content/globals/edit/index.page.html +8 -1
  15. package/dist/admin/content/globals/index.page.html +102 -7
  16. package/dist/admin/content/pages/edit/index.page.html +8 -1
  17. package/dist/admin/content/pages/index.page.html +224 -27
  18. package/dist/admin/content/templates/edit/index.page.html +8 -1
  19. package/dist/admin/content/templates/index.page.html +116 -2
  20. package/dist/admin/default.template.html +20 -1
  21. package/dist/admin/extensions/index.page.html +59 -2
  22. package/dist/admin/icons/extension_add.svg +1 -0
  23. package/dist/admin/icons/extension_remove.svg +1 -0
  24. package/dist/admin/index.page.html +5 -1
  25. package/dist/admin/nav.fragment.html +84 -2
  26. package/dist/admin/settings/[owner]/[name]/index.page.html +110 -4
  27. package/dist/admin/settings/index.page.html +87 -2
  28. package/dist/kempo/api/fragments/DELETE.js +1 -1
  29. package/dist/kempo/api/fragments/DELETE.js.map +1 -1
  30. package/dist/kempo/api/fragments/GET.js +1 -1
  31. package/dist/kempo/api/fragments/GET.js.map +1 -1
  32. package/dist/kempo/api/fragments/POST.js +1 -1
  33. package/dist/kempo/api/fragments/POST.js.map +1 -1
  34. package/dist/kempo/api/fragments/disable/PUT.js +1 -1
  35. package/dist/kempo/api/fragments/disable/PUT.js.map +1 -1
  36. package/dist/kempo/api/fragments/enable/PUT.js +1 -1
  37. package/dist/kempo/api/fragments/enable/PUT.js.map +1 -1
  38. package/dist/kempo/api/fragments/file/GET.js +1 -1
  39. package/dist/kempo/api/fragments/file/GET.js.map +1 -1
  40. package/dist/kempo/api/fragments/file/PATCH.js +1 -1
  41. package/dist/kempo/api/fragments/file/PATCH.js.map +1 -1
  42. package/dist/kempo/api/fragments/file/PUT.js +1 -1
  43. package/dist/kempo/api/fragments/file/PUT.js.map +1 -1
  44. package/dist/kempo/api/fragments/lock/PUT.js +1 -1
  45. package/dist/kempo/api/fragments/lock/PUT.js.map +1 -1
  46. package/dist/kempo/api/globals/DELETE.js +1 -1
  47. package/dist/kempo/api/globals/DELETE.js.map +1 -1
  48. package/dist/kempo/api/globals/GET.js +1 -1
  49. package/dist/kempo/api/globals/GET.js.map +1 -1
  50. package/dist/kempo/api/globals/POST.js +1 -1
  51. package/dist/kempo/api/globals/POST.js.map +1 -1
  52. package/dist/kempo/api/globals/disable/PUT.js +1 -1
  53. package/dist/kempo/api/globals/disable/PUT.js.map +1 -1
  54. package/dist/kempo/api/globals/enable/PUT.js +1 -1
  55. package/dist/kempo/api/globals/enable/PUT.js.map +1 -1
  56. package/dist/kempo/api/globals/entry/GET.js +1 -1
  57. package/dist/kempo/api/globals/entry/GET.js.map +1 -1
  58. package/dist/kempo/api/globals/entry/PUT.js +1 -1
  59. package/dist/kempo/api/globals/entry/PUT.js.map +1 -1
  60. package/dist/kempo/api/globals/lock/PUT.js +1 -1
  61. package/dist/kempo/api/globals/lock/PUT.js.map +1 -1
  62. package/dist/kempo/api/pages/DELETE.js +1 -1
  63. package/dist/kempo/api/pages/DELETE.js.map +1 -1
  64. package/dist/kempo/api/pages/GET.js +1 -1
  65. package/dist/kempo/api/pages/GET.js.map +1 -1
  66. package/dist/kempo/api/pages/POST.js +1 -1
  67. package/dist/kempo/api/pages/POST.js.map +1 -1
  68. package/dist/kempo/api/pages/directories/GET.js +1 -1
  69. package/dist/kempo/api/pages/directories/GET.js.map +1 -1
  70. package/dist/kempo/api/pages/disable/PUT.js +1 -1
  71. package/dist/kempo/api/pages/disable/PUT.js.map +1 -1
  72. package/dist/kempo/api/pages/enable/PUT.js +1 -1
  73. package/dist/kempo/api/pages/enable/PUT.js.map +1 -1
  74. package/dist/kempo/api/pages/file/GET.js +1 -1
  75. package/dist/kempo/api/pages/file/GET.js.map +1 -1
  76. package/dist/kempo/api/pages/file/PATCH.js +1 -1
  77. package/dist/kempo/api/pages/file/PATCH.js.map +1 -1
  78. package/dist/kempo/api/pages/file/PUT.js +1 -1
  79. package/dist/kempo/api/pages/file/PUT.js.map +1 -1
  80. package/dist/kempo/api/pages/lock/PUT.js +1 -1
  81. package/dist/kempo/api/pages/lock/PUT.js.map +1 -1
  82. package/dist/kempo/api/pages/rescan/POST.js +2 -0
  83. package/dist/kempo/api/pages/rescan/POST.js.map +1 -0
  84. package/dist/kempo/api/pages/search/GET.js +1 -1
  85. package/dist/kempo/api/pages/search/GET.js.map +1 -1
  86. package/dist/kempo/api/pages/templates/GET.js +1 -1
  87. package/dist/kempo/api/pages/templates/GET.js.map +1 -1
  88. package/dist/kempo/api/templates/DELETE.js +1 -1
  89. package/dist/kempo/api/templates/DELETE.js.map +1 -1
  90. package/dist/kempo/api/templates/GET.js +1 -1
  91. package/dist/kempo/api/templates/GET.js.map +1 -1
  92. package/dist/kempo/api/templates/POST.js +1 -1
  93. package/dist/kempo/api/templates/POST.js.map +1 -1
  94. package/dist/kempo/api/templates/disable/PUT.js +1 -1
  95. package/dist/kempo/api/templates/disable/PUT.js.map +1 -1
  96. package/dist/kempo/api/templates/enable/PUT.js +1 -1
  97. package/dist/kempo/api/templates/enable/PUT.js.map +1 -1
  98. package/dist/kempo/api/templates/file/GET.js +1 -1
  99. package/dist/kempo/api/templates/file/GET.js.map +1 -1
  100. package/dist/kempo/api/templates/file/PATCH.js +1 -1
  101. package/dist/kempo/api/templates/file/PATCH.js.map +1 -1
  102. package/dist/kempo/api/templates/file/PUT.js +1 -1
  103. package/dist/kempo/api/templates/file/PUT.js.map +1 -1
  104. package/dist/kempo/api/templates/lock/PUT.js +1 -1
  105. package/dist/kempo/api/templates/lock/PUT.js.map +1 -1
  106. package/dist/kempo/sdk.js +1 -1
  107. package/dist/kempo/sdk.js.map +1 -1
  108. package/middleware/kempo.js +50 -1
  109. package/package.json +3 -3
  110. package/server/utils/fragments/listFragments.js +10 -1
  111. package/server/utils/hooks/triggerHook.js +7 -12
  112. package/server/utils/pages/listPages.js +57 -7
  113. package/app-public/kempo-global.global-disabled.html +0 -0
  114. package/app-public/new-page.page.html +0 -17
  115. package/app-public/test.page.html +0 -21
@@ -1,28 +1,21 @@
1
1
  import db from '../../db/index.js';
2
2
  import { hook } from '../../db/schema.js';
3
3
  import { eq } from 'drizzle-orm';
4
- import { createRequire } from 'module';
5
- import { dirname, join } from 'path';
4
+ import { join } from 'path';
5
+ import { pathToFileURL } from 'url';
6
6
 
7
7
  const handlerCache = new Map();
8
8
 
9
9
  export const clearHandlerCache = () => handlerCache.clear();
10
10
 
11
- const requireFromProject = createRequire(join(process.cwd(), 'package.json'));
12
-
13
11
  const resolveHandler = (owner, callback) => {
14
12
  if(callback.startsWith('./') || callback.startsWith('../')){
15
- try {
16
- const pkgPath = requireFromProject.resolve(`${owner}/package.json`);
17
- return join(dirname(pkgPath), callback);
18
- } catch {
19
- return null;
20
- }
13
+ return join(process.cwd(), 'node_modules', owner, callback);
21
14
  }
22
15
  return callback;
23
16
  };
24
17
 
25
- export default async (event, data = {}) => {
18
+ export default async (event, data = {}, { bail = false } = {}) => {
26
19
  if(!event){
27
20
  return [{ code: 400, msg: 'Event is required' }, null];
28
21
  }
@@ -38,7 +31,7 @@ export default async (event, data = {}) => {
38
31
  let handler = handlerCache.get(handlerPath);
39
32
  if(!handler){
40
33
  try {
41
- const mod = await import(handlerPath);
34
+ const mod = await import(pathToFileURL(handlerPath).href);
42
35
  handler = mod.default || mod;
43
36
  handlerCache.set(handlerPath, handler);
44
37
  } catch {
@@ -50,12 +43,14 @@ export default async (event, data = {}) => {
50
43
  const result = await handler(data);
51
44
  results.push({ hookId: h.id, owner: h.owner, result });
52
45
  } catch(err) {
46
+ if(bail) throw err;
53
47
  results.push({ hookId: h.id, owner: h.owner, error: err.message });
54
48
  }
55
49
  }
56
50
 
57
51
  return [null, { results }];
58
52
  } catch(error) {
53
+ if(bail) throw error;
59
54
  return [{ code: 500, msg: 'Failed to trigger hooks' }, null];
60
55
  }
61
56
  };
@@ -2,14 +2,17 @@ import { readFile } from 'fs/promises';
2
2
  import { relative } from 'path';
3
3
  import scanDir from '../fs/scanDir.js';
4
4
  import parseFrontmatter from '../fs/parseFrontmatter.js';
5
+ import getSetting from '../settings/getSetting.js';
5
6
 
6
- const RESERVED_KEYS = new Set(['owner', 'name', 'author', 'created', 'updated', 'locked', 'title', 'description']);
7
+ const RESERVED_KEYS = new Set(['owner', 'name', 'author', 'created', 'updated', 'locked', 'hidden', 'title', 'description']);
7
8
 
8
- export default async ({ rootDir, owner }) => {
9
- if(!rootDir){
10
- return [{ code: 400, msg: 'Root directory is required' }, null];
11
- }
9
+ /*
10
+ In-memory cache keyed by rootDir.
11
+ TTL is read from the 'system:pages:cacheTtl' setting (milliseconds, default 60000).
12
+ */
13
+ const cache = new Map();
12
14
 
15
+ const scan = async (rootDir) => {
13
16
  const pages = await scanDir(rootDir, rootDir, async (fullPath, entry, root) => {
14
17
  const isDisabled = entry.name.endsWith('.page-disabled.html');
15
18
  if(entry.isDirectory() || (!entry.name.endsWith('.page.html') && !isDisabled)) return null;
@@ -32,6 +35,7 @@ export default async ({ rootDir, owner }) => {
32
35
  title: meta.title || '',
33
36
  owner: meta.owner || 'custom',
34
37
  locked: meta.locked === 'true',
38
+ hidden: meta.hidden === 'true',
35
39
  disabled: isDisabled,
36
40
  author: meta.author || '',
37
41
  description: meta.description || '',
@@ -41,8 +45,54 @@ export default async ({ rootDir, owner }) => {
41
45
  };
42
46
  });
43
47
 
44
- const filtered = owner ? pages.filter(page => page.owner === owner) : pages;
48
+ const scannedAt = Date.now();
49
+ cache.set(rootDir, { pages, scannedAt });
50
+ return scannedAt;
51
+ };
52
+
53
+ export default async ({ rootDir, owner, forceRescan = false, showHidden = true, offset = 0, limit = 0 }) => {
54
+ if(!rootDir){
55
+ return [{ code: 400, msg: 'Root directory is required' }, null];
56
+ }
57
+
58
+ const [, cacheTtl] = await getSetting('system', 'pages:cacheTtl', 60000);
59
+ const cached = cache.get(rootDir);
60
+
61
+ if(!forceRescan && cached && (Date.now() - cached.scannedAt < cacheTtl)){
62
+ return [null, buildResult(cached.pages, cached.scannedAt, { owner, showHidden, offset, limit })];
63
+ }
64
+
65
+ const scannedAt = await scan(rootDir);
66
+ const { pages } = cache.get(rootDir);
67
+ return [null, buildResult(pages, scannedAt, { owner, showHidden, offset, limit })];
68
+ };
69
+
70
+ const buildResult = (allPages, scannedAt, { owner, showHidden, offset, limit }) => {
71
+ let filtered = showHidden ? allPages : allPages.filter(p => !p.hidden);
72
+
73
+ if(owner === 'extension'){
74
+ filtered = filtered.filter(p => p.owner !== 'custom' && p.owner !== 'system');
75
+ } else if(owner){
76
+ filtered = filtered.filter(p => p.owner === owner);
77
+ }
78
+
79
+ filtered = filtered.slice().sort((a, b) => {
80
+ const aPath = a.file.replace(/\.page(-disabled)?\.html$/, '');
81
+ const bPath = b.file.replace(/\.page(-disabled)?\.html$/, '');
82
+ const aDir = aPath.includes('/') ? aPath.split('/').slice(0, -1).join('/') : '';
83
+ const bDir = bPath.includes('/') ? bPath.split('/').slice(0, -1).join('/') : '';
84
+ if(aPath === 'index' || aPath === '') return -1;
85
+ if(bPath === 'index' || bPath === '') return 1;
86
+ if(aDir !== bDir){
87
+ if(!aDir) return -1;
88
+ if(!bDir) return 1;
89
+ return aDir.localeCompare(bDir);
90
+ }
91
+ return aPath.localeCompare(bPath);
92
+ });
45
93
 
46
- return [null, { pages: filtered, total: filtered.length }];
94
+ const total = filtered.length;
95
+ const pages = limit > 0 ? filtered.slice(offset, offset + limit) : filtered.slice(offset);
96
+ return { pages, total, scannedAt };
47
97
  };
48
98
 
File without changes
@@ -1,17 +0,0 @@
1
- <!--
2
- owner: custom
3
- name: new page
4
- author:
5
- created: 2026-04-16T06:21:26.074Z
6
- updated: 2026-04-19T20:54:05.204Z
7
- title: new page
8
- description:
9
- -->
10
- <page template="split" title="new page">
11
- <content>
12
- <h1>New Page</h1>
13
- </content>
14
- <content location="left">
15
- <p>Left</p>
16
- </content>
17
- </page>
@@ -1,21 +0,0 @@
1
- <!--
2
- owner: custom
3
- name: test
4
- author: Dustin
5
- created: 2026-04-11T06:13:14.666Z
6
- updated: 2026-04-16T21:13:12.153Z
7
- title: test
8
- description:
9
- locked: true
10
- -->
11
- <page template="split" title="test">
12
- <content location="left">
13
- <p>Left Side MEnu</p>
14
- </content>
15
- <content>
16
- <h1>Test</h1>
17
- </content>
18
- <content location="scripts">
19
- <p><br></p>
20
- </content>
21
- </page>