@cyberismo/backend 0.0.15 → 0.0.17

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 (108) hide show
  1. package/dist/app.d.ts +21 -0
  2. package/dist/app.js +11 -3
  3. package/dist/app.js.map +1 -1
  4. package/dist/common/validationSchemas.d.ts +48 -0
  5. package/dist/domain/calculations/index.d.ts +15 -0
  6. package/dist/domain/calculations/schema.d.ts +16 -0
  7. package/dist/domain/calculations/service.d.ts +14 -0
  8. package/dist/domain/cardTypes/index.d.ts +15 -0
  9. package/dist/domain/cardTypes/schema.d.ts +17 -0
  10. package/dist/domain/cardTypes/service.d.ts +15 -0
  11. package/dist/domain/cards/index.d.ts +15 -0
  12. package/dist/domain/cards/index.js +22 -6
  13. package/dist/domain/cards/index.js.map +1 -1
  14. package/dist/domain/cards/lib.d.ts +29 -0
  15. package/dist/domain/cards/lib.js +73 -2
  16. package/dist/domain/cards/lib.js.map +1 -1
  17. package/dist/domain/cards/service.d.ts +61 -0
  18. package/dist/domain/cards/service.js +20 -12
  19. package/dist/domain/cards/service.js.map +1 -1
  20. package/dist/domain/fieldTypes/index.d.ts +15 -0
  21. package/dist/domain/fieldTypes/schema.d.ts +28 -0
  22. package/dist/domain/fieldTypes/service.d.ts +16 -0
  23. package/dist/domain/graphModels/index.d.ts +15 -0
  24. package/dist/domain/graphModels/schema.d.ts +16 -0
  25. package/dist/domain/graphModels/service.d.ts +14 -0
  26. package/dist/domain/graphViews/index.d.ts +15 -0
  27. package/dist/domain/graphViews/schema.d.ts +4 -0
  28. package/dist/domain/graphViews/service.d.ts +14 -0
  29. package/dist/domain/labels/index.d.ts +15 -0
  30. package/dist/domain/labels/index.js +33 -0
  31. package/dist/domain/labels/index.js.map +1 -0
  32. package/dist/domain/labels/service.d.ts +19 -0
  33. package/dist/domain/labels/service.js +21 -0
  34. package/dist/domain/labels/service.js.map +1 -0
  35. package/dist/domain/linkTypes/index.d.ts +15 -0
  36. package/dist/domain/linkTypes/schema.d.ts +16 -0
  37. package/dist/domain/linkTypes/service.d.ts +15 -0
  38. package/dist/domain/logicPrograms/index.d.ts +15 -0
  39. package/dist/domain/logicPrograms/service.d.ts +15 -0
  40. package/dist/domain/project/index.d.ts +15 -0
  41. package/dist/domain/project/index.js +42 -0
  42. package/dist/domain/project/index.js.map +1 -0
  43. package/dist/domain/project/schema.d.ts +20 -0
  44. package/dist/domain/project/schema.js +21 -0
  45. package/dist/domain/project/schema.js.map +1 -0
  46. package/dist/domain/project/service.d.ts +30 -0
  47. package/dist/domain/project/service.js +54 -0
  48. package/dist/domain/project/service.js.map +1 -0
  49. package/dist/domain/reports/index.d.ts +15 -0
  50. package/dist/domain/reports/schema.d.ts +16 -0
  51. package/dist/domain/reports/service.d.ts +14 -0
  52. package/dist/domain/resources/index.d.ts +15 -0
  53. package/dist/domain/resources/schema.d.ts +60 -0
  54. package/dist/domain/resources/service.d.ts +36 -0
  55. package/dist/domain/resources/service.js +60 -31
  56. package/dist/domain/resources/service.js.map +1 -1
  57. package/dist/domain/templates/index.d.ts +15 -0
  58. package/dist/domain/templates/index.js +1 -1
  59. package/dist/domain/templates/index.js.map +1 -1
  60. package/dist/domain/templates/schema.d.ts +22 -0
  61. package/dist/domain/templates/service.d.ts +16 -0
  62. package/dist/domain/tree/index.d.ts +15 -0
  63. package/dist/domain/tree/index.js +3 -2
  64. package/dist/domain/tree/index.js.map +1 -1
  65. package/dist/domain/tree/service.d.ts +22 -0
  66. package/dist/domain/tree/service.js +10 -2
  67. package/dist/domain/tree/service.js.map +1 -1
  68. package/dist/domain/workflows/index.d.ts +15 -0
  69. package/dist/domain/workflows/schema.d.ts +16 -0
  70. package/dist/domain/workflows/service.d.ts +14 -0
  71. package/dist/export.d.ts +42 -0
  72. package/dist/export.js +48 -152
  73. package/dist/export.js.map +1 -1
  74. package/dist/index.d.ts +13 -0
  75. package/dist/index.js.map +1 -1
  76. package/dist/main.d.ts +1 -0
  77. package/dist/middleware/commandManager.d.ts +20 -0
  78. package/dist/middleware/tree.d.ts +9 -0
  79. package/dist/middleware/tree.js +13 -0
  80. package/dist/middleware/tree.js.map +1 -0
  81. package/dist/middleware/zvalidator.d.ts +9 -0
  82. package/dist/public/THIRD-PARTY.txt +77 -83
  83. package/dist/public/assets/index-BrNy_4vV.js +163198 -0
  84. package/dist/public/index.html +1 -1
  85. package/dist/types.d.ts +29 -0
  86. package/dist/utils.d.ts +11 -0
  87. package/dist/utils.js +0 -32
  88. package/dist/utils.js.map +1 -1
  89. package/package.json +14 -8
  90. package/src/app.ts +13 -4
  91. package/src/domain/cards/index.ts +31 -6
  92. package/src/domain/cards/lib.ts +93 -3
  93. package/src/domain/cards/service.ts +36 -24
  94. package/src/domain/labels/index.ts +36 -0
  95. package/src/domain/labels/service.ts +23 -0
  96. package/src/domain/project/index.ts +58 -0
  97. package/src/domain/project/schema.ts +23 -0
  98. package/src/domain/project/service.ts +88 -0
  99. package/src/domain/resources/service.ts +87 -41
  100. package/src/domain/templates/index.ts +1 -1
  101. package/src/domain/tree/index.ts +10 -3
  102. package/src/domain/tree/service.ts +15 -1
  103. package/src/export.ts +59 -192
  104. package/src/index.ts +5 -1
  105. package/src/middleware/tree.ts +17 -0
  106. package/src/types.ts +13 -0
  107. package/src/utils.ts +0 -39
  108. package/dist/public/assets/index-Dtn1rQ-9.js +0 -163921
@@ -0,0 +1,88 @@
1
+ /**
2
+ Cyberismo
3
+ Copyright © Cyberismo Ltd and contributors 2025
4
+ This program is free software: you can redistribute it and/or modify it under
5
+ the terms of the GNU Affero General Public License version 3 as published by
6
+ the Free Software Foundation.
7
+ This program is distributed in the hope that it will be useful, but WITHOUT
8
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9
+ FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
10
+ details. You should have received a copy of the GNU Affero General Public
11
+ License along with this program. If not, see <https://www.gnu.org/licenses/>.
12
+ */
13
+
14
+ import { type CommandManager } from '@cyberismo/data-handler';
15
+
16
+ export interface ProjectModule {
17
+ name: string;
18
+ cardKeyPrefix: string;
19
+ }
20
+
21
+ export interface ProjectInfo {
22
+ name: string;
23
+ cardKeyPrefix: string;
24
+ modules: ProjectModule[];
25
+ }
26
+
27
+ export interface ProjectUpdatePayload {
28
+ name?: string;
29
+ cardKeyPrefix?: string;
30
+ }
31
+
32
+ async function toModuleInfo(
33
+ commands: CommandManager,
34
+ moduleName: string,
35
+ ): Promise<ProjectModule> {
36
+ try {
37
+ const data = await commands.showCmd.showModule(moduleName);
38
+ return {
39
+ name: data.name || moduleName,
40
+ cardKeyPrefix: data.cardKeyPrefix || moduleName,
41
+ };
42
+ } catch {
43
+ return {
44
+ name: moduleName,
45
+ cardKeyPrefix: moduleName,
46
+ };
47
+ }
48
+ }
49
+
50
+ export async function getProject(
51
+ commands: CommandManager,
52
+ ): Promise<ProjectInfo> {
53
+ const project = await commands.showCmd.showProject();
54
+ const modules = await commands.showCmd.showModules();
55
+ const moduleDetails = await Promise.all(
56
+ modules.map((mod) => toModuleInfo(commands, mod)),
57
+ );
58
+
59
+ return {
60
+ name: project.name,
61
+ cardKeyPrefix: project.prefix,
62
+ modules: moduleDetails,
63
+ };
64
+ }
65
+
66
+ export async function updateProject(
67
+ commands: CommandManager,
68
+ updates: ProjectUpdatePayload,
69
+ ): Promise<ProjectInfo> {
70
+ const { name, cardKeyPrefix } = updates;
71
+
72
+ if (cardKeyPrefix) {
73
+ await commands.renameCmd.rename(cardKeyPrefix);
74
+ }
75
+ if (name) {
76
+ await commands.project.configuration.setProjectName(name);
77
+ }
78
+
79
+ return getProject(commands);
80
+ }
81
+
82
+ export async function updateModule(commands: CommandManager, module: string) {
83
+ await commands.importCmd.updateModule(module);
84
+ }
85
+
86
+ export async function deleteModule(commands: CommandManager, module: string) {
87
+ await commands.removeCmd.remove('module', module);
88
+ }
@@ -11,7 +11,7 @@
11
11
  License along with this program. If not, see <https://www.gnu.org/licenses/>.
12
12
  */
13
13
 
14
- import type { ResourceContent } from '@cyberismo/data-handler/interfaces/resource-interfaces';
14
+ import type { AnyResourceContent } from '@cyberismo/data-handler/interfaces/resource-interfaces';
15
15
  import type {
16
16
  Card,
17
17
  CardWithChildrenCards,
@@ -40,12 +40,51 @@ const resourceTypes: ResourceFolderType[] = [
40
40
  'workflows',
41
41
  ];
42
42
 
43
+ async function getModules(commands: CommandManager) {
44
+ try {
45
+ const moduleNames = commands.showCmd.showModules();
46
+ return Promise.all(
47
+ moduleNames.map(async (moduleName) => {
48
+ try {
49
+ const module = await commands.showCmd.showModule(moduleName);
50
+ return { name: module.name, cardKeyPrefix: module.cardKeyPrefix };
51
+ } catch (error) {
52
+ return { name: moduleName, cardKeyPrefix: moduleName };
53
+ }
54
+ }),
55
+ );
56
+ } catch {
57
+ return [];
58
+ }
59
+ }
60
+
43
61
  export async function buildResourceTree(commands: CommandManager) {
44
62
  const project = await commands.showCmd.showProject();
45
63
  const tree: unknown[] = [];
46
- const allModuleResources: {
47
- [prefix: string]: { [type: string]: unknown[] };
48
- } = {};
64
+
65
+ const sortByDisplayName = (
66
+ nodes: { data: { displayName: string; name: string } }[],
67
+ ) =>
68
+ nodes.sort((a, b) =>
69
+ (a.data.displayName || a.data.name.split('/')[2] || '').localeCompare(
70
+ b.data.displayName || b.data.name.split('/')[2] || '',
71
+ ),
72
+ );
73
+
74
+ const modules = await getModules(commands);
75
+
76
+ // General section first (single node with project + modules metadata)
77
+ tree.push({
78
+ id: 'general-project',
79
+ type: 'general',
80
+ name: 'project',
81
+ data: {
82
+ name: project.name,
83
+ cardKeyPrefix: project.prefix,
84
+ modules,
85
+ },
86
+ readOnly: false,
87
+ });
49
88
 
50
89
  // Process each resource type
51
90
  for (const resourceType of resourceTypes) {
@@ -65,47 +104,54 @@ export async function buildResourceTree(commands: CommandManager) {
65
104
  ));
66
105
  }
67
106
 
68
- // Add root level resources
69
- if (rootResources.length > 0) {
107
+ // Sort resources by display name if present
108
+ sortByDisplayName(
109
+ rootResources as { data: { displayName: string; name: string } }[],
110
+ );
111
+
112
+ Object.values(moduleResources).forEach((resources) =>
113
+ sortByDisplayName(
114
+ resources as { data: { displayName: string; name: string } }[],
115
+ ),
116
+ );
117
+
118
+ const projectNode =
119
+ rootResources.length > 0
120
+ ? [
121
+ {
122
+ id: `${resourceType}-project`,
123
+ type: 'module',
124
+ name: 'project',
125
+ children: rootResources,
126
+ readOnly: false,
127
+ },
128
+ ]
129
+ : [];
130
+
131
+ const moduleNodes = Object.entries(moduleResources)
132
+ .map(([prefix, resources]) => ({
133
+ id: `${resourceType}-module-${prefix}`,
134
+ type: 'module',
135
+ name:
136
+ modules.find((module) => module.cardKeyPrefix === prefix)?.name ||
137
+ prefix,
138
+ prefix,
139
+ children: resources,
140
+ readOnly: true,
141
+ }))
142
+ .sort((a, b) => a.name.localeCompare(b.name));
143
+
144
+ const allResources = [...projectNode, ...moduleNodes];
145
+
146
+ // Add combined resources (project + modules nested under module nodes) under the same group
147
+ if (allResources.length > 0) {
70
148
  tree.push({
71
149
  id: resourceType,
72
150
  type: 'resourceGroup',
73
151
  name: resourceType,
74
- children: rootResources,
152
+ children: allResources,
75
153
  });
76
154
  }
77
-
78
- // Collect module resources
79
- Object.entries(moduleResources).forEach(([prefix, resources]) => {
80
- if (!allModuleResources[prefix]) {
81
- allModuleResources[prefix] = {};
82
- }
83
- allModuleResources[prefix][resourceType] = resources;
84
- });
85
- }
86
-
87
- // Build modules section
88
- if (Object.keys(allModuleResources).length > 0) {
89
- const modules = Object.entries(allModuleResources).map(
90
- ([prefix, resourcesByType]) => ({
91
- id: `modules-${prefix}`,
92
- type: 'module',
93
- name: prefix,
94
- children: Object.entries(resourcesByType).map(([type, resources]) => ({
95
- id: `modules-${prefix}-${type}`,
96
- type: 'resourceGroup',
97
- name: type,
98
- children: resources,
99
- })),
100
- }),
101
- );
102
-
103
- tree.push({
104
- id: 'modules',
105
- type: 'modulesGroup',
106
- name: 'modules',
107
- children: modules,
108
- });
109
155
  }
110
156
 
111
157
  return tree;
@@ -138,7 +184,7 @@ async function createResourceNode(
138
184
  id: string;
139
185
  type: ResourceFolderType;
140
186
  name: string;
141
- data: ResourceContent | undefined;
187
+ data: AnyResourceContent | undefined;
142
188
  children?: unknown[];
143
189
  readOnly?: boolean;
144
190
  }> {
@@ -147,7 +193,7 @@ async function createResourceNode(
147
193
  id: string;
148
194
  type: ResourceFolderType;
149
195
  name: string;
150
- data: ResourceContent | undefined;
196
+ data: AnyResourceContent | undefined;
151
197
  children?: unknown[];
152
198
  readOnly?: boolean;
153
199
  } = {
@@ -12,10 +12,10 @@
12
12
  */
13
13
 
14
14
  import { Hono } from 'hono';
15
- import { isSSGContext } from '../../export.js';
16
15
  import * as templateService from './service.js';
17
16
  import { createTemplateSchema, addTemplateCardSchema } from './schema.js';
18
17
  import { zValidator } from '../../middleware/zvalidator.js';
18
+ import { isSSGContext } from 'hono/ssg';
19
19
 
20
20
  const router = new Hono();
21
21
 
@@ -13,7 +13,8 @@
13
13
 
14
14
  import { Hono } from 'hono';
15
15
  import * as treeService from './service.js';
16
- import { isSSGContext } from '../../export.js';
16
+ import { isSSGContext } from 'hono/ssg';
17
+ import type { AppContext } from '../../types.js';
17
18
 
18
19
  const router = new Hono();
19
20
 
@@ -31,11 +32,17 @@ const router = new Hono();
31
32
  * 500:
32
33
  * description: project_path not set or other internal error
33
34
  */
34
- router.get('/', async (c) => {
35
+ router.get('/', async (c: AppContext) => {
35
36
  const commands = c.get('commands');
37
+ const tree = c.get('tree');
36
38
 
37
39
  try {
38
- const response = await treeService.getCardTree(commands, isSSGContext(c));
40
+ const response = await treeService.getCardTree(
41
+ commands,
42
+ isSSGContext(c),
43
+ tree?.cardKey,
44
+ tree?.recursive,
45
+ );
39
46
  return c.json(response);
40
47
  } catch (error) {
41
48
  return c.json(
@@ -13,10 +13,24 @@
13
13
 
14
14
  import type { CommandManager } from '@cyberismo/data-handler';
15
15
 
16
- export async function getCardTree(commands: CommandManager, isSsg: boolean) {
16
+ /**
17
+ * Returns the card tree of the project
18
+ * @param commands command manager used for the query
19
+ * @param isSsg whether the context is static site generation
20
+ * @param cardKey optional card key to start the tree from
21
+ * @param recursive if false, includes only the given card. Otherwise, includes all child cards recursively.
22
+ * @returns
23
+ */
24
+ export async function getCardTree(
25
+ commands: CommandManager,
26
+ isSsg: boolean,
27
+ cardKey?: string,
28
+ recursive?: boolean,
29
+ ): ReturnType<typeof commands.calculateCmd.runQuery> {
17
30
  await commands.calculateCmd.generate();
18
31
  return commands.calculateCmd.runQuery(
19
32
  'tree',
20
33
  isSsg ? 'exportedSite' : 'localApp',
34
+ { cardKey, recursive },
21
35
  );
22
36
  }
package/src/export.ts CHANGED
@@ -13,21 +13,22 @@
13
13
 
14
14
  import path from 'node:path';
15
15
 
16
- import { mkdir, readFile } from 'node:fs/promises';
16
+ import fs, { readFile } from 'node:fs/promises';
17
17
 
18
18
  import { CommandManager } from '@cyberismo/data-handler';
19
19
  import { createApp } from './app.js';
20
20
  import { cp, writeFile } from 'node:fs/promises';
21
- import {
22
- runCbSafely,
23
- runInParallel,
24
- staticFrontendDirRelative,
25
- } from './utils.js';
21
+ import { staticFrontendDirRelative } from './utils.js';
26
22
  import type { QueryResult } from '@cyberismo/data-handler/types/queries';
27
- import type { Context, Hono, MiddlewareHandler } from 'hono';
28
- import mime from 'mime-types';
23
+ import { toSSG } from 'hono/ssg';
24
+ import type { TreeOptions } from './types.js';
25
+ import {
26
+ findAllCards,
27
+ findRelevantAttachments,
28
+ } from './domain/cards/service.js';
29
29
 
30
30
  let _cardQueryPromise: Promise<QueryResult<'card'>[]> | null = null;
31
+ const OVERHEAD_CALLS = 6; // estimated number of overhead calls during export in addition to card exports
31
32
 
32
33
  /**
33
34
  * DO NO USE DIRECTLY. This resets the callOnce map, allowing you to redo the export.
@@ -73,19 +74,29 @@ export async function getCardQueryResult(
73
74
  * Export the site to a given directory.
74
75
  * Note: Do not call this function in parallel.
75
76
  * @param projectPath - Path to the project.
77
+ * @param options - Export options.
78
+ * @param options.recursive - Whether to export cards recursively.
79
+ * @param options.cardKey - Key of the card to export. If not provided, all cards will be exported.
76
80
  * @param exportDir - Directory to export to.
77
81
  * @param level - Log level for the operation.
78
82
  * @param onProgress - Optional progress callback function.
83
+ * @returns An object containing any errors that occurred during export.
79
84
  */
80
85
  export async function exportSite(
81
86
  projectPath: string,
82
87
  exportDir?: string,
88
+ options?: TreeOptions,
83
89
  level?: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal',
84
- onProgress?: (current?: number, total?: number) => void,
85
- ) {
90
+ onProgress?: (current: number, total: number) => void,
91
+ ): Promise<{ errors: string[] }> {
86
92
  exportDir = exportDir || 'static';
93
+ const opts = {
94
+ recursive: false,
95
+ cardKey: undefined,
96
+ ...options,
97
+ };
87
98
 
88
- const app = createApp(projectPath);
99
+ const app = createApp(projectPath, opts);
89
100
 
90
101
  // copy whole frontend to the same directory
91
102
  await cp(staticFrontendDirRelative, exportDir, { recursive: true });
@@ -101,192 +112,48 @@ export async function exportSite(
101
112
  const commands = await CommandManager.getInstance(projectPath, {
102
113
  logLevel: level,
103
114
  });
104
- await toSsg(app, commands, exportDir, onProgress);
105
- }
106
-
107
- async function getRoutes(app: Hono) {
108
- const routes = new Set<string>();
109
- for (const route of app.routes) {
110
- if (route.method === 'GET') routes.add(route.path);
111
- }
112
-
113
- // handles both routes with and without dynamic parameters
114
- const filteredRoutes = [];
115
- for (const route of routes) {
116
- if (!route.includes(':')) {
117
- filteredRoutes.push(route);
118
- continue;
119
- }
120
- const response = await createSsgRequest(app, route, true);
121
- if (response.ok) {
122
- const params = await response.json();
123
- if (Array.isArray(params) && params.length > 0) {
124
- for (const param of params) {
125
- let newRoute = route;
126
- for (const [key, value] of Object.entries(param)) {
127
- newRoute = newRoute.replace(`:${key}`, `${value}`);
128
- }
129
- filteredRoutes.push(newRoute);
130
- }
131
- }
132
- }
133
- }
134
-
135
- return filteredRoutes;
136
- }
137
115
 
138
- /**
139
- * This is similar to hono's ssg function, but it only calls middlewares once
140
- * @param app
141
- * @param onProgress
142
- */
143
- async function toSsg(
144
- app: Hono,
145
- commands: CommandManager,
146
- dir: string,
147
- onProgress?: (current?: number, total?: number) => void,
148
- ) {
149
116
  reset();
150
117
  await commands.project.calculationEngine.generate();
151
118
 
152
- const promises = [];
153
-
154
- const routes = await getRoutes(app);
155
- await runCbSafely(() => onProgress?.(0, routes.length));
156
-
157
- let processedFiles = 0;
158
- let failed = false;
159
- const errors: Error[] = [];
160
- const done = async (error?: Error) => {
161
- if (error) {
162
- failed = true;
163
- errors.push(error);
164
- }
165
- processedFiles++;
166
- await runCbSafely(() => onProgress?.(processedFiles, routes.length));
167
- };
168
- for (const route of routes) {
169
- promises.push(async () => {
170
- try {
171
- const response = await createSsgRequest(app, route, false);
172
- if (!response.ok) {
173
- const error = await response.json();
174
- if (typeof error === 'object' && error !== null && 'error' in error) {
175
- await done(
176
- new Error(`Failed to export route ${route}: ${error.error}`),
177
- );
178
- } else {
179
- await done(new Error(`Failed to export route ${route}`));
119
+ // estimate total based on the number of cards to export
120
+ const cards = await findAllCards(commands, opts);
121
+ const attachments = await findRelevantAttachments(commands, opts);
122
+ let total = cards.length + attachments.length + OVERHEAD_CALLS;
123
+
124
+ // Actual export with progress reporting
125
+ let done = 0;
126
+ onProgress?.(done, total);
127
+ const errors: string[] = [];
128
+ await toSSG(app, fs, {
129
+ dir: exportDir,
130
+ concurrency: 5,
131
+ plugins: [
132
+ {
133
+ afterResponseHook: async (response) => {
134
+ if (![200, 201, 204].includes(response.status)) {
135
+ const error = await response.json();
136
+ if (
137
+ typeof error === 'object' &&
138
+ error != null &&
139
+ 'error' in error &&
140
+ typeof error.error === 'string'
141
+ ) {
142
+ errors.push(error.error);
143
+ }
144
+ return false; // ignore route
180
145
  }
181
- return;
182
- }
183
- await writeFileToDir(dir, response, route);
184
- await done();
185
- } catch (error) {
186
- await done(error instanceof Error ? error : new Error(String(error)));
187
- }
188
- });
189
- }
190
-
191
- await runInParallel(promises, 5);
192
- if (failed) {
193
- const message = `Errors:\n${errors.map((e) => e.message).join('\n')}`;
194
- throw new Error(message);
195
- }
196
- }
197
-
198
- /**
199
- * Get the file content and file ending for a given response and route.
200
- * @param response - The response to get the file content and file ending for.
201
- * @param route - The route to get the file content and file ending for.
202
- * @returns The file content and file ending for the given response and route.
203
- * If the route already has a file ending, it will be returned as an empty string.
204
- */
205
- async function getFileContent(
206
- response: Response,
207
- route: string,
208
- ): Promise<{
209
- content: ArrayBuffer;
210
- fileEnding: string;
211
- }> {
212
- // Check if route already has an extension
213
- const routeExtension = path.extname(route);
214
- if (routeExtension) {
215
- // Trust the existing extension in the route
216
- const content = await response.arrayBuffer();
217
- return {
218
- content,
219
- fileEnding: '',
220
- };
221
- }
222
-
223
- // No extension in route, fall back to content type detection
224
- const contentType = response.headers.get('content-type');
225
- if (!contentType) {
226
- throw new Error('No content type');
227
- }
228
- const extension = mime.extension(contentType);
229
- if (!extension) {
230
- throw new Error('Unsupported content type');
231
- }
232
-
233
- // Use ArrayBuffer for all content types
234
- const content = await response.arrayBuffer();
235
- return {
236
- content,
237
- fileEnding: `.${extension}`,
238
- };
239
- }
240
-
241
- async function writeFileToDir(dir: string, response: Response, route: string) {
242
- const { content, fileEnding } = await getFileContent(response, route);
243
-
244
- let filePath = path.join(dir, route);
245
-
246
- // if route does not have a file ending, add it based on the content type
247
- if (!route.endsWith(fileEnding)) {
248
- filePath += fileEnding;
249
- }
250
-
251
- await mkdir(path.dirname(filePath), { recursive: true });
252
- await writeFile(filePath, Buffer.from(content));
253
- }
254
-
255
- // findroutes = if this request is used to find the routes in the app
256
- function createSsgRequest(
257
- app: Hono,
258
- route: string,
259
- findRoutes: boolean = true,
260
- ) {
261
- return app.request(route, {
262
- headers: new Headers({
263
- 'x-ssg': 'true',
264
- 'x-ssg-find': findRoutes ? 'true' : 'false',
265
- }),
146
+ done++;
147
+ if (done > total) {
148
+ total = done; // adjust total if underestimated
149
+ }
150
+ onProgress?.(done, total);
151
+ return response;
152
+ },
153
+ },
154
+ ],
266
155
  });
267
- }
268
-
269
- /**
270
- * Check if the request is a static site generation request.
271
- * @param c - The context of the request.
272
- * @returns True if the request is a static site generation request.
273
- */
274
- export function isSSGContext(c: Context) {
275
- return c.req.header('x-ssg') === 'true';
276
- }
277
-
278
- /**
279
- * This middleware is used to find the routes in the app.
280
- * @param fn - The function to call to get the parameters for the route.
281
- * @returns The middleware handler.
282
- */
283
- export function ssgParams(
284
- fn?: (c: Context) => Promise<unknown[]>,
285
- ): MiddlewareHandler {
286
- return async (c, next) => {
287
- if (c.req.header('x-ssg-find') === 'true') {
288
- return fn ? c.json(await fn(c)) : c.json([]);
289
- }
290
- return next();
156
+ return {
157
+ errors,
291
158
  };
292
159
  }
package/src/index.ts CHANGED
@@ -11,6 +11,7 @@
11
11
  License along with this program. If not, see <https://www.gnu.org/licenses/>.
12
12
  */
13
13
  import { serve } from '@hono/node-server';
14
+ import type { Env, Schema } from 'hono';
14
15
  import { Hono } from 'hono';
15
16
  import { serveStatic } from '@hono/node-server/serve-static';
16
17
  import path from 'node:path';
@@ -62,7 +63,10 @@ export async function startServer(
62
63
  startApp(app, port);
63
64
  }
64
65
 
65
- function startApp(app: Hono, port: number) {
66
+ function startApp<E extends Env, S extends Schema, P extends string>(
67
+ app: Hono<E, S, P>,
68
+ port: number,
69
+ ) {
66
70
  serve(
67
71
  {
68
72
  fetch: app.fetch,
@@ -0,0 +1,17 @@
1
+ import type { Next } from 'hono';
2
+ import type { AppContext, TreeOptions } from '../types.js';
3
+
4
+ /**
5
+ * Injects tree options into the context for each request
6
+ * @param opts Options to inject
7
+ * @returns Middleware function
8
+ */
9
+ const treeMiddleware =
10
+ (opts?: TreeOptions) => async (c: AppContext, next: Next) => {
11
+ if (opts) {
12
+ c.set('tree', { recursive: opts.recursive, cardKey: opts.cardKey });
13
+ }
14
+ await next();
15
+ };
16
+
17
+ export default treeMiddleware;
package/src/types.ts CHANGED
@@ -11,6 +11,8 @@
11
11
  License along with this program. If not, see <https://www.gnu.org/licenses/>.
12
12
  */
13
13
 
14
+ import type { Context } from 'hono';
15
+
14
16
  export interface ResourceFileContentResponse {
15
17
  content: string;
16
18
  }
@@ -18,3 +20,14 @@ export interface ResourceFileContentResponse {
18
20
  export interface ResourceValidationResponse {
19
21
  errors: string[];
20
22
  }
23
+
24
+ export interface TreeOptions {
25
+ recursive?: boolean;
26
+ cardKey?: string;
27
+ }
28
+
29
+ export interface AppVars {
30
+ tree?: TreeOptions;
31
+ }
32
+
33
+ export type AppContext = Context<{ Variables: AppVars }>;