@cyberismo/backend 0.0.6 → 0.0.7
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 -0
- package/dist/app.js +65 -0
- package/dist/app.js.map +1 -0
- package/dist/export.js +228 -0
- package/dist/export.js.map +1 -0
- package/dist/index.js +41 -76
- package/dist/index.js.map +1 -1
- package/dist/main.js +19 -1
- package/dist/main.js.map +1 -1
- package/dist/public/THIRD-PARTY.txt +47 -47
- package/dist/public/assets/index-BngW8o1w.css +1 -0
- package/dist/public/assets/index-D5kiRHuF.js +111171 -0
- package/dist/public/config.json +3 -0
- package/dist/public/index.html +11 -2
- package/dist/routes/cards.js +48 -20
- package/dist/routes/cards.js.map +1 -1
- package/dist/routes/fieldTypes.js +3 -1
- package/dist/routes/fieldTypes.js.map +1 -1
- package/dist/routes/linkTypes.js +3 -1
- package/dist/routes/linkTypes.js.map +1 -1
- package/dist/routes/templates.js +8 -1
- package/dist/routes/templates.js.map +1 -1
- package/dist/routes/tree.js +3 -1
- package/dist/routes/tree.js.map +1 -1
- package/dist/utils.js +87 -0
- package/dist/utils.js.map +1 -0
- package/package.json +7 -3
- package/src/app.ts +79 -0
- package/src/export.ts +288 -0
- package/src/index.ts +47 -91
- package/src/main.ts +18 -1
- package/src/routes/cards.ts +121 -69
- package/src/routes/fieldTypes.ts +4 -2
- package/src/routes/linkTypes.ts +6 -1
- package/src/routes/templates.ts +11 -1
- package/src/routes/tree.ts +6 -1
- package/src/utils.ts +100 -0
- package/dist/public/assets/index-DqBrVRB4.js +0 -605
- package/dist/public/assets/index-ImgtZ9pq.css +0 -1
- package/dist/routes/cardTypes.js +0 -49
- package/dist/routes/cardTypes.js.map +0 -1
- package/src/routes/cardTypes.ts +0 -54
package/src/routes/cards.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { Hono } from 'hono';
|
|
13
|
+
import { Context, Hono } from 'hono';
|
|
14
14
|
import Processor from '@asciidoctor/core';
|
|
15
15
|
import {
|
|
16
16
|
Card,
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
} from '@cyberismo/data-handler/interfaces/project-interfaces';
|
|
21
21
|
import { CommandManager, evaluateMacros } from '@cyberismo/data-handler';
|
|
22
22
|
import { ContentfulStatusCode } from 'hono/utils/http-status';
|
|
23
|
+
import { getCardQueryResult, isSSGContext, ssgParams } from '../export.js';
|
|
23
24
|
|
|
24
25
|
const router = new Hono();
|
|
25
26
|
|
|
@@ -70,6 +71,7 @@ router.get('/', async (c) => {
|
|
|
70
71
|
async function getCardDetails(
|
|
71
72
|
commands: CommandManager,
|
|
72
73
|
key: string,
|
|
74
|
+
staticMode?: boolean,
|
|
73
75
|
): Promise<any> {
|
|
74
76
|
const fetchCardDetails: ProjectFetchCardDetails = {
|
|
75
77
|
attachments: true,
|
|
@@ -97,11 +99,15 @@ async function getCardDetails(
|
|
|
97
99
|
|
|
98
100
|
let asciidocContent = '';
|
|
99
101
|
try {
|
|
100
|
-
asciidocContent = await evaluateMacros(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
asciidocContent = await evaluateMacros(
|
|
103
|
+
cardDetailsResponse.content || '',
|
|
104
|
+
{
|
|
105
|
+
mode: staticMode ? 'static' : 'inject',
|
|
106
|
+
project: commands.project,
|
|
107
|
+
cardKey: key,
|
|
108
|
+
},
|
|
109
|
+
commands.calculateCmd,
|
|
110
|
+
);
|
|
105
111
|
} catch (error) {
|
|
106
112
|
asciidocContent = `Macro error: ${error instanceof Error ? error.message : 'Unknown error'}\n\n${asciidocContent}`;
|
|
107
113
|
}
|
|
@@ -116,13 +122,16 @@ async function getCardDetails(
|
|
|
116
122
|
})
|
|
117
123
|
.toString();
|
|
118
124
|
|
|
119
|
-
// always parse for now
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
cardKey: key,
|
|
124
|
-
});
|
|
125
|
+
// always parse for now if not in export mode
|
|
126
|
+
if (!staticMode) {
|
|
127
|
+
await commands.calculateCmd.generate();
|
|
128
|
+
}
|
|
125
129
|
|
|
130
|
+
const card = staticMode
|
|
131
|
+
? await getCardQueryResult(commands.project.basePath, key)
|
|
132
|
+
: await commands.calculateCmd.runQuery('card', {
|
|
133
|
+
cardKey: key,
|
|
134
|
+
});
|
|
126
135
|
if (card.length !== 1) {
|
|
127
136
|
throw new Error('Query failed. Check card-query syntax');
|
|
128
137
|
}
|
|
@@ -157,22 +166,44 @@ async function getCardDetails(
|
|
|
157
166
|
* 500:
|
|
158
167
|
* description: project_path not set.
|
|
159
168
|
*/
|
|
160
|
-
router.get(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
169
|
+
router.get(
|
|
170
|
+
'/:key',
|
|
171
|
+
ssgParams(async (c: Context) => {
|
|
172
|
+
const commands = c.get('commands');
|
|
173
|
+
const fetchedCards = await commands.showCmd.showCards(
|
|
174
|
+
CardLocation.projectOnly,
|
|
175
|
+
);
|
|
176
|
+
const projectCards = fetchedCards.find(
|
|
177
|
+
(cardContainer) => cardContainer.type === 'project',
|
|
178
|
+
);
|
|
179
|
+
if (!projectCards) {
|
|
180
|
+
throw new Error('Data handler did not return project cards');
|
|
181
|
+
}
|
|
182
|
+
return projectCards.cards.map((key) => ({ key }));
|
|
183
|
+
}),
|
|
184
|
+
async (c) => {
|
|
185
|
+
const key = c.req.param('key');
|
|
186
|
+
if (!key) {
|
|
187
|
+
return c.text('No search key', 400);
|
|
188
|
+
}
|
|
165
189
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return c.text(
|
|
171
|
-
result.message || 'Unknown error',
|
|
172
|
-
result.status as ContentfulStatusCode,
|
|
190
|
+
const result = await getCardDetails(
|
|
191
|
+
c.get('commands'),
|
|
192
|
+
key,
|
|
193
|
+
isSSGContext(c),
|
|
173
194
|
);
|
|
174
|
-
|
|
175
|
-
|
|
195
|
+
if (result.status === 200) {
|
|
196
|
+
return c.json(result.data);
|
|
197
|
+
} else {
|
|
198
|
+
return c.json(
|
|
199
|
+
{
|
|
200
|
+
error: result.message || 'Unknown error',
|
|
201
|
+
},
|
|
202
|
+
result.status as ContentfulStatusCode,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
);
|
|
176
207
|
|
|
177
208
|
/**
|
|
178
209
|
* @swagger
|
|
@@ -271,8 +302,10 @@ router.patch('/:key', async (c) => {
|
|
|
271
302
|
if (result.status === 200) {
|
|
272
303
|
return c.json(result.data);
|
|
273
304
|
} else {
|
|
274
|
-
return c.
|
|
275
|
-
|
|
305
|
+
return c.json(
|
|
306
|
+
{
|
|
307
|
+
error: result.message || 'Unknown error',
|
|
308
|
+
},
|
|
276
309
|
result.status as ContentfulStatusCode,
|
|
277
310
|
);
|
|
278
311
|
}
|
|
@@ -309,8 +342,10 @@ router.delete('/:key', async (c) => {
|
|
|
309
342
|
await commands.removeCmd.remove('card', key);
|
|
310
343
|
return new Response(null, { status: 204 });
|
|
311
344
|
} catch (error) {
|
|
312
|
-
return c.
|
|
313
|
-
|
|
345
|
+
return c.json(
|
|
346
|
+
{
|
|
347
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
348
|
+
},
|
|
314
349
|
400,
|
|
315
350
|
);
|
|
316
351
|
}
|
|
@@ -361,9 +396,9 @@ router.post('/:key', async (c) => {
|
|
|
361
396
|
return c.json(result);
|
|
362
397
|
} catch (error) {
|
|
363
398
|
if (error instanceof Error) {
|
|
364
|
-
return c.
|
|
399
|
+
return c.json({ error: error.message }, 400);
|
|
365
400
|
}
|
|
366
|
-
return c.
|
|
401
|
+
return c.json({ error: 'Unknown error occurred' }, 500);
|
|
367
402
|
}
|
|
368
403
|
});
|
|
369
404
|
|
|
@@ -563,11 +598,15 @@ router.post('/:key/parse', async (c) => {
|
|
|
563
598
|
try {
|
|
564
599
|
let asciidocContent = '';
|
|
565
600
|
try {
|
|
566
|
-
asciidocContent = await evaluateMacros(
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
601
|
+
asciidocContent = await evaluateMacros(
|
|
602
|
+
content,
|
|
603
|
+
{
|
|
604
|
+
mode: 'inject',
|
|
605
|
+
project: commands.project,
|
|
606
|
+
cardKey: key,
|
|
607
|
+
},
|
|
608
|
+
commands.calculateCmd,
|
|
609
|
+
);
|
|
571
610
|
} catch (error) {
|
|
572
611
|
asciidocContent = `Macro error: ${error instanceof Error ? error.message : 'Unknown error'}\n\n${content}`;
|
|
573
612
|
}
|
|
@@ -725,43 +764,56 @@ router.delete('/:key/links', async (c) => {
|
|
|
725
764
|
* 500:
|
|
726
765
|
* description: project_path not set.
|
|
727
766
|
*/
|
|
728
|
-
router.get(
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
767
|
+
router.get(
|
|
768
|
+
'/:key/a/:attachment',
|
|
769
|
+
ssgParams(async (c: Context) => {
|
|
770
|
+
const commands = c.get('commands');
|
|
771
|
+
const attachments = await commands.showCmd.showAttachments();
|
|
772
|
+
return attachments.map((attachment) => ({
|
|
773
|
+
key: attachment.card,
|
|
774
|
+
attachment: attachment.fileName,
|
|
775
|
+
}));
|
|
776
|
+
}),
|
|
777
|
+
async (c) => {
|
|
778
|
+
const commands = c.get('commands');
|
|
779
|
+
const { key, attachment } = c.req.param();
|
|
780
|
+
const filename = decodeURI(attachment);
|
|
781
|
+
|
|
782
|
+
if (!filename || !key) {
|
|
783
|
+
return c.text('Missing cardKey or filename', 400);
|
|
784
|
+
}
|
|
732
785
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
786
|
+
try {
|
|
787
|
+
const attachmentResponse = await commands.showCmd.showAttachment(
|
|
788
|
+
key,
|
|
789
|
+
filename,
|
|
790
|
+
);
|
|
736
791
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
792
|
+
if (!attachmentResponse) {
|
|
793
|
+
return c.text(
|
|
794
|
+
`No attachment found from card ${key} and filename ${filename}`,
|
|
795
|
+
404,
|
|
796
|
+
);
|
|
797
|
+
}
|
|
742
798
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
799
|
+
const payload = attachmentResponse as any;
|
|
800
|
+
|
|
801
|
+
return new Response(payload.fileBuffer, {
|
|
802
|
+
headers: {
|
|
803
|
+
'Content-Type': payload.mimeType,
|
|
804
|
+
'Content-Disposition': `attachment; filename="${filename}"`,
|
|
805
|
+
'Cache-Control': 'no-store',
|
|
806
|
+
},
|
|
807
|
+
});
|
|
808
|
+
} catch {
|
|
809
|
+
return c.json(
|
|
810
|
+
{
|
|
811
|
+
error: `No attachment found from card ${key} and filename ${filename}`,
|
|
812
|
+
},
|
|
746
813
|
404,
|
|
747
814
|
);
|
|
748
815
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
return new Response(payload.fileBuffer, {
|
|
753
|
-
headers: {
|
|
754
|
-
'Content-Type': payload.mimeType,
|
|
755
|
-
'Content-Disposition': `attachment; filename="${filename}"`,
|
|
756
|
-
'Cache-Control': 'no-store',
|
|
757
|
-
},
|
|
758
|
-
});
|
|
759
|
-
} catch {
|
|
760
|
-
return c.text(
|
|
761
|
-
`No attachment found from card ${key} and filename ${filename}`,
|
|
762
|
-
404,
|
|
763
|
-
);
|
|
764
|
-
}
|
|
765
|
-
});
|
|
816
|
+
},
|
|
817
|
+
);
|
|
766
818
|
|
|
767
819
|
export default router;
|
package/src/routes/fieldTypes.ts
CHANGED
|
@@ -41,8 +41,10 @@ router.get('/', async (c) => {
|
|
|
41
41
|
|
|
42
42
|
return c.json(fieldTypes);
|
|
43
43
|
} else {
|
|
44
|
-
return c.
|
|
45
|
-
|
|
44
|
+
return c.json(
|
|
45
|
+
{
|
|
46
|
+
error: `No field types found from path ${c.get('projectPath')}`,
|
|
47
|
+
},
|
|
46
48
|
500,
|
|
47
49
|
);
|
|
48
50
|
}
|
package/src/routes/linkTypes.ts
CHANGED
|
@@ -41,7 +41,12 @@ router.get('/', async (c) => {
|
|
|
41
41
|
|
|
42
42
|
return c.json(linkTypes);
|
|
43
43
|
} else {
|
|
44
|
-
return c.
|
|
44
|
+
return c.json(
|
|
45
|
+
{
|
|
46
|
+
error: `No link types found from path ${c.get('projectPath')}`,
|
|
47
|
+
},
|
|
48
|
+
500,
|
|
49
|
+
);
|
|
45
50
|
}
|
|
46
51
|
});
|
|
47
52
|
|
package/src/routes/templates.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { Hono } from 'hono';
|
|
14
|
+
import { isSSGContext } from 'hono/ssg';
|
|
14
15
|
|
|
15
16
|
const router = new Hono();
|
|
16
17
|
|
|
@@ -29,13 +30,22 @@ const router = new Hono();
|
|
|
29
30
|
* description: project_path not set or other internal error
|
|
30
31
|
*/
|
|
31
32
|
router.get('/', async (c) => {
|
|
33
|
+
// We do not need templates in ssg context
|
|
34
|
+
if (isSSGContext(c)) {
|
|
35
|
+
return c.json([]);
|
|
36
|
+
}
|
|
32
37
|
const commands = c.get('commands');
|
|
33
38
|
|
|
34
39
|
const response = await commands.showCmd.showTemplatesWithDetails();
|
|
35
40
|
if (response) {
|
|
36
41
|
return c.json(response);
|
|
37
42
|
} else {
|
|
38
|
-
return c.
|
|
43
|
+
return c.json(
|
|
44
|
+
{
|
|
45
|
+
error: `No templates found from path ${c.get('projectPath')}`,
|
|
46
|
+
},
|
|
47
|
+
500,
|
|
48
|
+
);
|
|
39
49
|
}
|
|
40
50
|
});
|
|
41
51
|
|
package/src/routes/tree.ts
CHANGED
|
@@ -33,7 +33,12 @@ router.get('/', async (c) => {
|
|
|
33
33
|
const tree = await commands.calculateCmd.runQuery('tree');
|
|
34
34
|
return c.json(tree);
|
|
35
35
|
} catch (e) {
|
|
36
|
-
return c.
|
|
36
|
+
return c.json(
|
|
37
|
+
{
|
|
38
|
+
error: (e instanceof Error && e.message) || '',
|
|
39
|
+
},
|
|
40
|
+
500,
|
|
41
|
+
);
|
|
37
42
|
}
|
|
38
43
|
});
|
|
39
44
|
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
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
|
+
import path from 'node:path';
|
|
14
|
+
import { createServer } from 'node:net';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Runs promises in parallel, but only maxConcurrent at a time.
|
|
18
|
+
* @param promises - Array of promises to run in parallel
|
|
19
|
+
* @param maxConcurrent - Maximum number of promises to run at a time
|
|
20
|
+
* @returns - Promise that resolves when all promises have resolved
|
|
21
|
+
*/
|
|
22
|
+
export async function runInParallel<T>(
|
|
23
|
+
promises: (() => Promise<unknown>)[],
|
|
24
|
+
maxConcurrent: number = 2,
|
|
25
|
+
) {
|
|
26
|
+
const waitingPromises: (() => Promise<unknown>)[] = [];
|
|
27
|
+
const wrappedPromises = promises.map((fn) => async () => {
|
|
28
|
+
await fn();
|
|
29
|
+
const next = waitingPromises.shift();
|
|
30
|
+
if (next) {
|
|
31
|
+
await next();
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const runningPromises = wrappedPromises.slice(0, maxConcurrent);
|
|
36
|
+
waitingPromises.push(...wrappedPromises.slice(maxConcurrent));
|
|
37
|
+
|
|
38
|
+
return Promise.all(runningPromises.map((p) => p()));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Runs a callback and returns the result or undefined if it throws.
|
|
42
|
+
* @param cb - The callback to run.
|
|
43
|
+
* @returns The result of the callback or undefined if it throws.
|
|
44
|
+
*/
|
|
45
|
+
export async function runCbSafely<T>(
|
|
46
|
+
cb: () => Promise<T> | T,
|
|
47
|
+
): Promise<T | undefined> {
|
|
48
|
+
try {
|
|
49
|
+
return await cb();
|
|
50
|
+
} catch {}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The relative path to the static frontend directory.
|
|
55
|
+
*/
|
|
56
|
+
export const staticFrontendDirRelative = path.relative(
|
|
57
|
+
process.cwd(),
|
|
58
|
+
path.resolve(import.meta.dirname, 'public'),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Finds a free port.
|
|
63
|
+
* @param port - The port to start looking for.
|
|
64
|
+
* @param maxAttempts - The maximum number of attempts to find a free port.
|
|
65
|
+
* @returns The free port.
|
|
66
|
+
*/
|
|
67
|
+
export async function findFreePort(
|
|
68
|
+
minPort: number,
|
|
69
|
+
maxPort: number,
|
|
70
|
+
): Promise<number> {
|
|
71
|
+
for (let i = minPort; i < maxPort; i++) {
|
|
72
|
+
try {
|
|
73
|
+
await testPort(i);
|
|
74
|
+
return i;
|
|
75
|
+
} catch (err) {
|
|
76
|
+
if (err instanceof Error && err.message.includes('EADDRINUSE')) {
|
|
77
|
+
console.log(`Port ${i} is already in use, trying next port...`);
|
|
78
|
+
} else {
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
throw new Error('Failed to find free port');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function testPort(port: number) {
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
const server = createServer();
|
|
89
|
+
server.listen(port, () => {
|
|
90
|
+
server.close();
|
|
91
|
+
resolve(true);
|
|
92
|
+
});
|
|
93
|
+
server.on('error', (err) => {
|
|
94
|
+
reject(err);
|
|
95
|
+
});
|
|
96
|
+
setTimeout(() => {
|
|
97
|
+
reject(new Error('Timed out waiting for port to be free'));
|
|
98
|
+
}, 2000);
|
|
99
|
+
});
|
|
100
|
+
}
|