@karmaniverous/jeeves-server 3.0.0-0
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/.env.local +13 -0
- package/.env.local.template +13 -0
- package/.tsbuildinfo +1 -0
- package/CHANGELOG.md +450 -0
- package/about.md +82 -0
- package/client/README.md +73 -0
- package/client/eslint.config.js +23 -0
- package/client/index.html +14 -0
- package/client/package-lock.json +5181 -0
- package/client/package.json +60 -0
- package/client/public/vite.svg +1 -0
- package/client/src/App.tsx +22 -0
- package/client/src/components/AccountMenu.tsx +167 -0
- package/client/src/components/ActionDropdown.tsx +120 -0
- package/client/src/components/CodeEditor.tsx +143 -0
- package/client/src/components/CodeViewer.tsx +113 -0
- package/client/src/components/ConfirmDialog.tsx +32 -0
- package/client/src/components/DirectoryRow.tsx +62 -0
- package/client/src/components/DirectoryTable.tsx +42 -0
- package/client/src/components/DownloadDropdown.tsx +116 -0
- package/client/src/components/DriveList.tsx +54 -0
- package/client/src/components/EmbeddedDiagramPanzoom.ts +28 -0
- package/client/src/components/FileContentView.tsx +155 -0
- package/client/src/components/InlineSvgPanzoom.ts +60 -0
- package/client/src/components/LazyDiagram.ts +93 -0
- package/client/src/components/LinkDropdown.tsx +134 -0
- package/client/src/components/MarkdownView.tsx +115 -0
- package/client/src/components/MermaidViewer.tsx +21 -0
- package/client/src/components/PlantUmlViewer.tsx +21 -0
- package/client/src/components/SearchModal.tsx +424 -0
- package/client/src/components/SvgViewer.tsx +107 -0
- package/client/src/components/TabBar.tsx +96 -0
- package/client/src/components/layout/Header.tsx +270 -0
- package/client/src/components/panzoom.ts +203 -0
- package/client/src/components/renderableUtils.ts +15 -0
- package/client/src/components/runner/JobTable.tsx +153 -0
- package/client/src/components/runner/RunHistory.tsx +140 -0
- package/client/src/components/runner/StatsBar.tsx +43 -0
- package/client/src/components/runner/StatusPill.tsx +27 -0
- package/client/src/components/runner/jobTableUtils.ts +65 -0
- package/client/src/components/scrollUtils.ts +39 -0
- package/client/src/components/ui/alert-dialog.tsx +107 -0
- package/client/src/components/ui/button.tsx +40 -0
- package/client/src/components/ui/dropdown-menu.tsx +79 -0
- package/client/src/components/ui/input.tsx +26 -0
- package/client/src/components/useActionState.ts +43 -0
- package/client/src/hooks/useFileBrowser.ts +102 -0
- package/client/src/hooks/useFileData.ts +78 -0
- package/client/src/hooks/useScrollAnchor.ts +70 -0
- package/client/src/hooks/useShareSettings.ts +22 -0
- package/client/src/hooks/useTopBar.ts +27 -0
- package/client/src/index.css +281 -0
- package/client/src/lib/AuthContext.ts +27 -0
- package/client/src/lib/api.ts +239 -0
- package/client/src/lib/auth.tsx +50 -0
- package/client/src/lib/codeBlockCm6.ts +129 -0
- package/client/src/lib/codeBlockCopy.ts +43 -0
- package/client/src/lib/codemirror.ts +77 -0
- package/client/src/lib/runner-api.ts +172 -0
- package/client/src/lib/svg.ts +50 -0
- package/client/src/lib/theme.ts +34 -0
- package/client/src/lib/utils.ts +6 -0
- package/client/src/main.tsx +11 -0
- package/client/src/pages/FileBrowser.tsx +135 -0
- package/client/src/pages/Home.tsx +46 -0
- package/client/src/pages/Runner.tsx +151 -0
- package/client/src/pages/RunnerJob.tsx +170 -0
- package/client/tsconfig.app.json +32 -0
- package/client/tsconfig.json +7 -0
- package/client/tsconfig.node.json +26 -0
- package/client/vite.config.ts +35 -0
- package/content/privacy.md +61 -0
- package/content/terms.md +41 -0
- package/dist/client/assets/CodeEditor-0XHVI8Nu.js +1 -0
- package/dist/client/assets/CodeViewer-CykMVsfX.js +1 -0
- package/dist/client/assets/index--MBieNJA.js +1 -0
- package/dist/client/assets/index-BENeXQI_.js +1 -0
- package/dist/client/assets/index-BbBpoOxz.js +1 -0
- package/dist/client/assets/index-BdV9g5AM.js +6 -0
- package/dist/client/assets/index-BjAilRri.js +2 -0
- package/dist/client/assets/index-BqbhWo2I.js +3 -0
- package/dist/client/assets/index-CVbycZ0H.js +1 -0
- package/dist/client/assets/index-Cs5oz2oJ.js +5 -0
- package/dist/client/assets/index-D8KZVveX.js +1 -0
- package/dist/client/assets/index-DC4HMHxY.js +13 -0
- package/dist/client/assets/index-DbMebkkd.css +1 -0
- package/dist/client/assets/index-DcY2RXqX.js +1 -0
- package/dist/client/assets/index-Duy-tZYV.js +1 -0
- package/dist/client/assets/index-Dw7rDFmE.js +7 -0
- package/dist/client/assets/index-FlCUvrjv.js +2 -0
- package/dist/client/assets/index-K6OVmfhg.js +1 -0
- package/dist/client/assets/index-LjwgzZ7F.js +62 -0
- package/dist/client/assets/index-MLwyFRN0.js +1 -0
- package/dist/client/assets/index-OpqBpSjn.js +1 -0
- package/dist/client/assets/index-SsHei0HE.js +1 -0
- package/dist/client/assets/index-uQa2yckk.js +1 -0
- package/dist/client/assets/index-udkXoIER.js +1 -0
- package/dist/client/index.html +15 -0
- package/dist/client/vite.svg +1 -0
- package/dist/src/auth/google.js +57 -0
- package/dist/src/auth/keys.js +185 -0
- package/dist/src/auth/resolve.js +102 -0
- package/dist/src/auth/session.js +57 -0
- package/dist/src/cli/commands/config.js +100 -0
- package/dist/src/cli/commands/config.test.js +84 -0
- package/dist/src/cli/commands/service.js +93 -0
- package/dist/src/cli/commands/start.js +24 -0
- package/dist/src/cli/index.js +20 -0
- package/dist/src/config/index.js +90 -0
- package/dist/src/config/loadConfig.test.js +127 -0
- package/dist/src/config/resolve.js +134 -0
- package/dist/src/config/resolve.test.js +148 -0
- package/dist/src/config/schema.js +159 -0
- package/dist/src/config/substituteEnvVars.js +45 -0
- package/dist/src/config/substituteEnvVars.test.js +51 -0
- package/dist/src/config/types.js +5 -0
- package/dist/src/routes/api/auth-status.js +56 -0
- package/dist/src/routes/api/diagrams.js +35 -0
- package/dist/src/routes/api/directory.js +93 -0
- package/dist/src/routes/api/drives.js +15 -0
- package/dist/src/routes/api/export.js +218 -0
- package/dist/src/routes/api/fileContent.js +286 -0
- package/dist/src/routes/api/index.js +33 -0
- package/dist/src/routes/api/linkInfo.js +71 -0
- package/dist/src/routes/api/linkInfo.test.js +104 -0
- package/dist/src/routes/api/middleware.js +117 -0
- package/dist/src/routes/api/raw.js +38 -0
- package/dist/src/routes/api/runner.js +59 -0
- package/dist/src/routes/api/search.js +236 -0
- package/dist/src/routes/api/sharing.js +203 -0
- package/dist/src/routes/api/status.js +68 -0
- package/dist/src/routes/api/status.test.js +62 -0
- package/dist/src/routes/auth.js +99 -0
- package/dist/src/routes/event.js +77 -0
- package/dist/src/routes/event.test.js +206 -0
- package/dist/src/routes/health.js +10 -0
- package/dist/src/routes/keys.js +129 -0
- package/dist/src/routes/path/index.js +17 -0
- package/dist/src/routes/static.js +30 -0
- package/dist/src/server.js +90 -0
- package/dist/src/services/deepShareLinks.js +163 -0
- package/dist/src/services/diagramCache.js +104 -0
- package/dist/src/services/embeddedDiagrams.js +136 -0
- package/dist/src/services/eventLog.js +55 -0
- package/dist/src/services/eventLog.test.js +113 -0
- package/dist/src/services/eventQueue.js +154 -0
- package/dist/src/services/eventQueue.test.js +104 -0
- package/dist/src/services/export.js +220 -0
- package/dist/src/services/exportCache.js +196 -0
- package/dist/src/services/markdown.js +147 -0
- package/dist/src/services/mermaid.js +97 -0
- package/dist/src/services/plantuml.js +145 -0
- package/dist/src/services/puppeteer.js +156 -0
- package/dist/src/util/breadcrumbs.js +22 -0
- package/dist/src/util/crypto.js +56 -0
- package/dist/src/util/crypto.test.js +99 -0
- package/dist/src/util/fileDetection.js +66 -0
- package/dist/src/util/fileDetection.test.js +89 -0
- package/dist/src/util/formatters.js +43 -0
- package/dist/src/util/formatters.test.js +83 -0
- package/dist/src/util/packageVersion.js +25 -0
- package/dist/src/util/platform.js +148 -0
- package/dist/src/util/state.js +46 -0
- package/dist/vitest.config.js +12 -0
- package/favicon.svg +3 -0
- package/guides/access-decision-flow.mmd +24 -0
- package/guides/access-decision-flow.svg +1 -0
- package/guides/api-integration.md +236 -0
- package/guides/deployment.md +287 -0
- package/guides/event-gateway.md +204 -0
- package/guides/event-gateway.mmd +17 -0
- package/guides/event-gateway.svg +1 -0
- package/guides/exports.md +239 -0
- package/guides/setup.md +313 -0
- package/guides/sharing.md +204 -0
- package/jeeves-server.config.template.json +25 -0
- package/package.json +124 -0
- package/scripts/download-plantuml.js +70 -0
- package/src/auth/google.ts +93 -0
- package/src/auth/keys.ts +252 -0
- package/src/auth/resolve.ts +157 -0
- package/src/auth/session.ts +77 -0
- package/src/cli/commands/config.test.ts +107 -0
- package/src/cli/commands/config.ts +113 -0
- package/src/cli/commands/service.ts +129 -0
- package/src/cli/commands/start.ts +27 -0
- package/src/cli/index.ts +25 -0
- package/src/config/index.ts +113 -0
- package/src/config/loadConfig.test.ts +155 -0
- package/src/config/resolve.test.ts +192 -0
- package/src/config/resolve.ts +173 -0
- package/src/config/schema.ts +179 -0
- package/src/config/substituteEnvVars.test.ts +64 -0
- package/src/config/substituteEnvVars.ts +52 -0
- package/src/config/types.ts +129 -0
- package/src/routes/api/auth-status.ts +85 -0
- package/src/routes/api/diagrams.ts +53 -0
- package/src/routes/api/directory.ts +123 -0
- package/src/routes/api/drives.ts +23 -0
- package/src/routes/api/export.ts +314 -0
- package/src/routes/api/fileContent.ts +414 -0
- package/src/routes/api/index.ts +37 -0
- package/src/routes/api/linkInfo.test.ts +132 -0
- package/src/routes/api/linkInfo.ts +83 -0
- package/src/routes/api/middleware.ts +156 -0
- package/src/routes/api/raw.ts +54 -0
- package/src/routes/api/runner.ts +107 -0
- package/src/routes/api/search.ts +321 -0
- package/src/routes/api/sharing.ts +259 -0
- package/src/routes/api/status.test.ts +72 -0
- package/src/routes/api/status.ts +82 -0
- package/src/routes/auth.ts +143 -0
- package/src/routes/event.test.ts +248 -0
- package/src/routes/event.ts +109 -0
- package/src/routes/health.ts +13 -0
- package/src/routes/keys.ts +192 -0
- package/src/routes/path/index.ts +24 -0
- package/src/routes/static.ts +54 -0
- package/src/server.ts +104 -0
- package/src/services/deepShareLinks.ts +203 -0
- package/src/services/diagramCache.ts +128 -0
- package/src/services/embeddedDiagrams.ts +168 -0
- package/src/services/eventLog.test.ts +144 -0
- package/src/services/eventLog.ts +68 -0
- package/src/services/eventQueue.test.ts +127 -0
- package/src/services/eventQueue.ts +196 -0
- package/src/services/export.ts +267 -0
- package/src/services/exportCache.ts +216 -0
- package/src/services/markdown.ts +189 -0
- package/src/services/mermaid.ts +113 -0
- package/src/services/plantuml.ts +172 -0
- package/src/services/puppeteer.ts +188 -0
- package/src/types/fastify.d.ts +13 -0
- package/src/types/jsonmap.d.ts +10 -0
- package/src/types/plantuml-encoder.d.ts +4 -0
- package/src/util/breadcrumbs.ts +33 -0
- package/src/util/crypto.test.ts +132 -0
- package/src/util/crypto.ts +79 -0
- package/src/util/fileDetection.test.ts +115 -0
- package/src/util/fileDetection.ts +70 -0
- package/src/util/formatters.test.ts +105 -0
- package/src/util/formatters.ts +44 -0
- package/src/util/packageVersion.ts +30 -0
- package/src/util/platform.ts +178 -0
- package/src/util/state.ts +55 -0
- package/test-docs/diagram-retry-test.md +18 -0
- package/test-docs/embedded-diagrams.md +52 -0
- package/test-docs/lazy-diagrams-test.md +333 -0
- package/test-docs/page-a.md +7 -0
- package/test-docs/page-b.md +7 -0
- package/test-docs/page-c.md +7 -0
- package/test-docs/sub/page-d.md +7 -0
- package/test-docs/test-diagram.puml +13 -0
- package/test-docs/validate-deep-share.js +318 -0
- package/tsconfig.json +37 -0
- package/tsdoc.json +13 -0
- package/vendor/.plantuml-version +1 -0
- package/vendor/plantuml.jar +0 -0
- package/vitest.config.js +12 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate deep share link behavior end-to-end.
|
|
3
|
+
*
|
|
4
|
+
* Tests:
|
|
5
|
+
* 1. Internal links work up to depth limit and not beyond
|
|
6
|
+
* 2. Directory option limits directory access and includes dirs in depth
|
|
7
|
+
* 3. External links always work (never rewritten)
|
|
8
|
+
*
|
|
9
|
+
* Usage: node test-docs/validate-deep-share.js
|
|
10
|
+
* Requires dev server running on port 3457.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const BASE = 'http://localhost:3457';
|
|
14
|
+
const INSIDER_KEY = '90db6073c9ee033c10745aa794f70fc5';
|
|
15
|
+
|
|
16
|
+
let pass = 0;
|
|
17
|
+
let fail = 0;
|
|
18
|
+
|
|
19
|
+
function assert(condition, label) {
|
|
20
|
+
if (condition) {
|
|
21
|
+
console.log(` ✅ ${label}`);
|
|
22
|
+
pass++;
|
|
23
|
+
} else {
|
|
24
|
+
console.log(` ❌ ${label}`);
|
|
25
|
+
fail++;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function fetchJson(url) {
|
|
30
|
+
const res = await fetch(url, { redirect: 'manual' });
|
|
31
|
+
return { status: res.status, data: res.ok ? await res.json() : null, headers: res.headers };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function getShareLink(path, depth, dirs) {
|
|
35
|
+
const res = await fetch(`${BASE}/api/share?key=${INSIDER_KEY}`, {
|
|
36
|
+
method: 'POST',
|
|
37
|
+
headers: { 'Content-Type': 'application/json' },
|
|
38
|
+
body: JSON.stringify({ path, depth, dirs }),
|
|
39
|
+
});
|
|
40
|
+
return res.json();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Parse a share URL into its components */
|
|
44
|
+
function parseShareUrl(url) {
|
|
45
|
+
const u = new URL(url, BASE);
|
|
46
|
+
return {
|
|
47
|
+
path: u.pathname.replace('/browse', ''),
|
|
48
|
+
key: u.searchParams.get('key'),
|
|
49
|
+
d: u.searchParams.get('d'),
|
|
50
|
+
dirs: u.searchParams.get('dirs'),
|
|
51
|
+
s: u.searchParams.get('s'),
|
|
52
|
+
exp: u.searchParams.get('exp'),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Build API file URL from share params */
|
|
57
|
+
function apiFileUrl(params) {
|
|
58
|
+
let url = `${BASE}/api/file${params.path}?key=${params.key}`;
|
|
59
|
+
if (params.d !== null) url += `&d=${params.d}`;
|
|
60
|
+
if (params.dirs !== null) url += `&dirs=${params.dirs}`;
|
|
61
|
+
if (params.s !== null) url += `&s=${params.s}`;
|
|
62
|
+
if (params.exp !== null) url += `&exp=${params.exp}`;
|
|
63
|
+
return url;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Build API path (directory) URL from share params */
|
|
67
|
+
function apiDirUrl(dirPath, params) {
|
|
68
|
+
let url = `${BASE}/api/path${dirPath}?key=${params.key}`;
|
|
69
|
+
if (params.d !== null) url += `&d=${params.d}`;
|
|
70
|
+
if (params.dirs !== null) url += `&dirs=${params.dirs}`;
|
|
71
|
+
if (params.s !== null) url += `&s=${params.s}`;
|
|
72
|
+
if (params.exp !== null) url += `&exp=${params.exp}`;
|
|
73
|
+
return url;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Extract internal links (rewritten /browse/ links) from HTML */
|
|
77
|
+
function extractInternalLinks(html) {
|
|
78
|
+
const links = [];
|
|
79
|
+
const re = /<a\s+[^>]*?href="(\/browse\/[^"]*)"[^>]*>([\s\S]*?)<\/a>/gi;
|
|
80
|
+
let m;
|
|
81
|
+
while ((m = re.exec(html)) !== null) {
|
|
82
|
+
links.push({ url: m[1], text: m[2] });
|
|
83
|
+
}
|
|
84
|
+
return links;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Extract external links from HTML */
|
|
88
|
+
function extractExternalLinks(html) {
|
|
89
|
+
const links = [];
|
|
90
|
+
const re = /<a\s+[^>]*?href="(https?:\/\/[^"]*)"[^>]*>([\s\S]*?)<\/a>/gi;
|
|
91
|
+
let m;
|
|
92
|
+
while ((m = re.exec(html)) !== null) {
|
|
93
|
+
links.push({ url: m[1], text: m[2] });
|
|
94
|
+
}
|
|
95
|
+
return links;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Extract dead (stripped) link texts — text that was a link but is now plain */
|
|
99
|
+
function extractDeadLinkTexts(html) {
|
|
100
|
+
// Dead links become plain text (no <a> wrapper). We look for known link texts
|
|
101
|
+
// that are NOT inside <a> tags.
|
|
102
|
+
return html;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function runTests() {
|
|
106
|
+
console.log('\n🔗 Deep Share Link Validation\n');
|
|
107
|
+
|
|
108
|
+
// ================================================================
|
|
109
|
+
// TEST 1: Depth 0 — no internal links should work
|
|
110
|
+
// ================================================================
|
|
111
|
+
console.log('━━━ Test 1: Depth 0 (no link following) ━━━');
|
|
112
|
+
{
|
|
113
|
+
const share = await getShareLink('/e/dev/karmaniverous/jeeves-server/test-docs/page-a.md', 0, false);
|
|
114
|
+
const params = parseShareUrl(share.url);
|
|
115
|
+
|
|
116
|
+
// Page A should load
|
|
117
|
+
const fileRes = await fetchJson(apiFileUrl(params));
|
|
118
|
+
assert(fileRes.status === 200, 'Page A loads with depth-0 key');
|
|
119
|
+
assert(fileRes.data?.isInsider === false, 'Page A shows as outsider');
|
|
120
|
+
|
|
121
|
+
// HTML should have NO internal links (all stripped)
|
|
122
|
+
const html = fileRes.data?.html || '';
|
|
123
|
+
const internalLinks = extractInternalLinks(html);
|
|
124
|
+
assert(internalLinks.length === 0, `No internal links at depth 0 (found ${internalLinks.length})`);
|
|
125
|
+
|
|
126
|
+
// External links should still be present
|
|
127
|
+
const externalLinks = extractExternalLinks(html);
|
|
128
|
+
assert(externalLinks.length > 0, `External links preserved (found ${externalLinks.length})`);
|
|
129
|
+
assert(externalLinks.some(l => l.url.includes('google.com')), 'Google link preserved');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ================================================================
|
|
133
|
+
// TEST 2: Depth 1 — one hop of internal links
|
|
134
|
+
// ================================================================
|
|
135
|
+
console.log('\n━━━ Test 2: Depth 1 (one hop) ━━━');
|
|
136
|
+
{
|
|
137
|
+
const share = await getShareLink('/e/dev/karmaniverous/jeeves-server/test-docs/page-a.md', 1, false);
|
|
138
|
+
const params = parseShareUrl(share.url);
|
|
139
|
+
|
|
140
|
+
// Page A should load
|
|
141
|
+
const fileRes = await fetchJson(apiFileUrl(params));
|
|
142
|
+
assert(fileRes.status === 200, 'Page A loads with depth-1 key');
|
|
143
|
+
|
|
144
|
+
const html = fileRes.data?.html || '';
|
|
145
|
+
const internalLinks = extractInternalLinks(html);
|
|
146
|
+
|
|
147
|
+
// Page B link should be live (depth 1)
|
|
148
|
+
const pageBLink = internalLinks.find(l => l.text.includes('Page B'));
|
|
149
|
+
assert(!!pageBLink, 'Page B link is live at depth 1');
|
|
150
|
+
|
|
151
|
+
// Sub Page (page-d in subdirectory) should be dead if dirs=false
|
|
152
|
+
const subPageLink = internalLinks.find(l => l.text.includes('Sub Page'));
|
|
153
|
+
// Actually sub/page-d.md is a file, not a directory — it should be live
|
|
154
|
+
// The dirs flag controls directory listings, not files in subdirectories
|
|
155
|
+
// Let's check what we get
|
|
156
|
+
console.log(` ℹ️ Sub Page link present: ${!!subPageLink}`);
|
|
157
|
+
|
|
158
|
+
// External links always present
|
|
159
|
+
const externalLinks = extractExternalLinks(html);
|
|
160
|
+
assert(externalLinks.some(l => l.url.includes('google.com')), 'External links preserved');
|
|
161
|
+
|
|
162
|
+
// Follow Page B link — should work
|
|
163
|
+
if (pageBLink) {
|
|
164
|
+
const pageBParams = parseShareUrl(pageBLink.url);
|
|
165
|
+
const pageBRes = await fetchJson(apiFileUrl(pageBParams));
|
|
166
|
+
assert(pageBRes.status === 200, 'Page B loads via depth-1 link');
|
|
167
|
+
|
|
168
|
+
// Page B's internal links should be dead (depth exhausted)
|
|
169
|
+
const pageBHtml = pageBRes.data?.html || '';
|
|
170
|
+
const pageBInternalLinks = extractInternalLinks(pageBHtml);
|
|
171
|
+
assert(pageBInternalLinks.length === 0, `Page B has no live internal links (depth exhausted, found ${pageBInternalLinks.length})`);
|
|
172
|
+
|
|
173
|
+
// Page B's external links should still work
|
|
174
|
+
const pageBExternalLinks = extractExternalLinks(pageBHtml);
|
|
175
|
+
assert(pageBExternalLinks.some(l => l.url.includes('github.com')), 'Page B external links preserved');
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ================================================================
|
|
180
|
+
// TEST 3: Depth 2 — two hops
|
|
181
|
+
// ================================================================
|
|
182
|
+
console.log('\n━━━ Test 3: Depth 2 (two hops) ━━━');
|
|
183
|
+
{
|
|
184
|
+
const share = await getShareLink('/e/dev/karmaniverous/jeeves-server/test-docs/page-a.md', 2, false);
|
|
185
|
+
const params = parseShareUrl(share.url);
|
|
186
|
+
|
|
187
|
+
const fileRes = await fetchJson(apiFileUrl(params));
|
|
188
|
+
assert(fileRes.status === 200, 'Page A loads with depth-2 key');
|
|
189
|
+
|
|
190
|
+
const html = fileRes.data?.html || '';
|
|
191
|
+
const internalLinks = extractInternalLinks(html);
|
|
192
|
+
const pageBLink = internalLinks.find(l => l.text.includes('Page B'));
|
|
193
|
+
assert(!!pageBLink, 'Page B link is live at depth 2');
|
|
194
|
+
|
|
195
|
+
// Follow to Page B
|
|
196
|
+
if (pageBLink) {
|
|
197
|
+
const pageBParams = parseShareUrl(pageBLink.url);
|
|
198
|
+
const pageBRes = await fetchJson(apiFileUrl(pageBParams));
|
|
199
|
+
assert(pageBRes.status === 200, 'Page B loads (hop 1)');
|
|
200
|
+
|
|
201
|
+
const pageBHtml = pageBRes.data?.html || '';
|
|
202
|
+
const pageBLinks = extractInternalLinks(pageBHtml);
|
|
203
|
+
const pageCLink = pageBLinks.find(l => l.text.includes('Page C'));
|
|
204
|
+
assert(!!pageCLink, 'Page C link is live from Page B (hop 2)');
|
|
205
|
+
|
|
206
|
+
// Follow to Page C
|
|
207
|
+
if (pageCLink) {
|
|
208
|
+
const pageCParams = parseShareUrl(pageCLink.url);
|
|
209
|
+
const pageCRes = await fetchJson(apiFileUrl(pageCParams));
|
|
210
|
+
assert(pageCRes.status === 200, 'Page C loads (hop 2)');
|
|
211
|
+
|
|
212
|
+
// Page C's internal links should be dead (depth exhausted)
|
|
213
|
+
const pageCHtml = pageCRes.data?.html || '';
|
|
214
|
+
const pageCLinks = extractInternalLinks(pageCHtml);
|
|
215
|
+
assert(pageCLinks.length === 0, `Page C has no live internal links (depth exhausted, found ${pageCLinks.length})`);
|
|
216
|
+
|
|
217
|
+
// External links on Page C
|
|
218
|
+
const pageCExternalLinks = extractExternalLinks(pageCHtml);
|
|
219
|
+
assert(pageCExternalLinks.some(l => l.url.includes('wikipedia.org')), 'Page C external links preserved');
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ================================================================
|
|
225
|
+
// TEST 4: Depth 1, dirs=false — directory listing blocked
|
|
226
|
+
// ================================================================
|
|
227
|
+
console.log('\n━━━ Test 4: Depth 1, dirs=false (directory access blocked) ━━━');
|
|
228
|
+
{
|
|
229
|
+
const share = await getShareLink('/e/dev/karmaniverous/jeeves-server/test-docs/page-a.md', 1, false);
|
|
230
|
+
const params = parseShareUrl(share.url);
|
|
231
|
+
|
|
232
|
+
// Try to access the directory listing
|
|
233
|
+
const dirRes = await fetchJson(apiDirUrl('/e/dev/karmaniverous/jeeves-server/test-docs/', params));
|
|
234
|
+
assert(dirRes.status === 401, `Directory listing blocked with dirs=false (got ${dirRes.status})`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ================================================================
|
|
238
|
+
// TEST 5: Depth 1, dirs=true — directory listing allowed
|
|
239
|
+
// ================================================================
|
|
240
|
+
console.log('\n━━━ Test 5: Depth 1, dirs=true (directory access) ━━━');
|
|
241
|
+
{
|
|
242
|
+
const share = await getShareLink('/e/dev/karmaniverous/jeeves-server/test-docs/page-a.md', 1, true);
|
|
243
|
+
const params = parseShareUrl(share.url);
|
|
244
|
+
|
|
245
|
+
// Page A should load
|
|
246
|
+
const fileRes = await fetchJson(apiFileUrl(params));
|
|
247
|
+
assert(fileRes.status === 200, 'Page A loads with dirs=true');
|
|
248
|
+
|
|
249
|
+
// Internal file links should still be live
|
|
250
|
+
const html = fileRes.data?.html || '';
|
|
251
|
+
const internalLinks = extractInternalLinks(html);
|
|
252
|
+
const pageBLink = internalLinks.find(l => l.text.includes('Page B'));
|
|
253
|
+
assert(!!pageBLink, 'Page B link is live with dirs=true');
|
|
254
|
+
|
|
255
|
+
// Parent directory listing should work
|
|
256
|
+
const parentDirRes = await fetchJson(apiDirUrl('/e/dev/karmaniverous/jeeves-server/test-docs', params));
|
|
257
|
+
assert(parentDirRes.status === 200, 'Parent directory listing works with dirs=true');
|
|
258
|
+
|
|
259
|
+
// Subdirectory listing should work
|
|
260
|
+
const subDirRes = await fetchJson(apiDirUrl('/e/dev/karmaniverous/jeeves-server/test-docs/sub', params));
|
|
261
|
+
assert(subDirRes.status === 200, 'Subdirectory listing works with dirs=true');
|
|
262
|
+
|
|
263
|
+
// Any directory accessible (scoped only by sharer's access)
|
|
264
|
+
const ancestorDirRes = await fetchJson(apiDirUrl('/e/dev', params));
|
|
265
|
+
assert(ancestorDirRes.status === 200, `Ancestor dir accessible with dirs=true (got ${ancestorDirRes.status})`);
|
|
266
|
+
|
|
267
|
+
// Sibling directory accessible too
|
|
268
|
+
const siblingDirRes = await fetchJson(apiDirUrl('/e/dev/karmaniverous/jeeves-server/src', params));
|
|
269
|
+
assert(siblingDirRes.status === 200, `Sibling dir accessible with dirs=true (got ${siblingDirRes.status})`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ================================================================
|
|
273
|
+
// TEST 6: Backward compatibility — no depth/dirs params
|
|
274
|
+
// ================================================================
|
|
275
|
+
console.log('\n━━━ Test 6: Backward compat (no depth/dirs) ━━━');
|
|
276
|
+
{
|
|
277
|
+
const share = await getShareLink('/e/dev/karmaniverous/jeeves-server/test-docs/page-a.md', 0, false);
|
|
278
|
+
const params = parseShareUrl(share.url);
|
|
279
|
+
|
|
280
|
+
// Should be a legacy share link (no d/dirs/s params)
|
|
281
|
+
assert(params.d === null, 'No d param in legacy share');
|
|
282
|
+
assert(params.s === null, 'No s param in legacy share');
|
|
283
|
+
|
|
284
|
+
const fileRes = await fetchJson(apiFileUrl(params));
|
|
285
|
+
assert(fileRes.status === 200, 'Legacy share link works');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ================================================================
|
|
289
|
+
// TEST 7: Cross-page key isolation — key from Page A can't access Page B directly
|
|
290
|
+
// ================================================================
|
|
291
|
+
console.log('\n━━━ Test 7: Key isolation (depth-0 key can\'t access other pages) ━━━');
|
|
292
|
+
{
|
|
293
|
+
const share = await getShareLink('/e/dev/karmaniverous/jeeves-server/test-docs/page-a.md', 0, false);
|
|
294
|
+
const params = parseShareUrl(share.url);
|
|
295
|
+
|
|
296
|
+
// Try to use Page A's key to access Page B directly
|
|
297
|
+
const pageBUrl = `${BASE}/api/file/e/dev/karmaniverous/jeeves-server/test-docs/page-b.md?key=${params.key}`;
|
|
298
|
+
const pageBRes = await fetchJson(pageBUrl);
|
|
299
|
+
assert(pageBRes.status === 401, `Page A's depth-0 key can't access Page B (got ${pageBRes.status})`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ================================================================
|
|
303
|
+
// Summary
|
|
304
|
+
// ================================================================
|
|
305
|
+
console.log(`\n${'━'.repeat(50)}`);
|
|
306
|
+
console.log(`Results: ${pass} passed, ${fail} failed out of ${pass + fail} total`);
|
|
307
|
+
if (fail > 0) {
|
|
308
|
+
console.log('⚠️ Some tests failed!');
|
|
309
|
+
process.exit(1);
|
|
310
|
+
} else {
|
|
311
|
+
console.log('✅ All tests passed!');
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
runTests().catch((err) => {
|
|
316
|
+
console.error('Fatal error:', err);
|
|
317
|
+
process.exit(1);
|
|
318
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"esModuleInterop": true,
|
|
4
|
+
"forceConsistentCasingInFileNames": true,
|
|
5
|
+
"incremental": true,
|
|
6
|
+
"isolatedModules": true,
|
|
7
|
+
"lib": [
|
|
8
|
+
"ES2022",
|
|
9
|
+
"DOM"
|
|
10
|
+
],
|
|
11
|
+
"module": "ESNext",
|
|
12
|
+
"moduleResolution": "Bundler",
|
|
13
|
+
"outDir": "dist",
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
"skipLibCheck": true,
|
|
16
|
+
"strict": true,
|
|
17
|
+
"target": "ES2022",
|
|
18
|
+
"tsBuildInfoFile": "./.tsbuildinfo",
|
|
19
|
+
"types": [
|
|
20
|
+
"node",
|
|
21
|
+
"vitest/globals"
|
|
22
|
+
],
|
|
23
|
+
"rootDir": "."
|
|
24
|
+
},
|
|
25
|
+
"exclude": [
|
|
26
|
+
"coverage",
|
|
27
|
+
"dist",
|
|
28
|
+
"node_modules",
|
|
29
|
+
"logs",
|
|
30
|
+
"server.js",
|
|
31
|
+
"jeeves.config.template.ts"
|
|
32
|
+
],
|
|
33
|
+
"include": [
|
|
34
|
+
"src/**/*",
|
|
35
|
+
"*.config.ts"
|
|
36
|
+
]
|
|
37
|
+
}
|
package/tsdoc.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
v1.2026.2
|
|
Binary file
|
package/vitest.config.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
export default defineConfig({
|
|
3
|
+
test: {
|
|
4
|
+
globals: true,
|
|
5
|
+
environment: 'happy-dom',
|
|
6
|
+
exclude: ['dist/**', '**/node_modules/**'],
|
|
7
|
+
coverage: {
|
|
8
|
+
provider: 'v8',
|
|
9
|
+
reporter: ['text', 'lcov'],
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
});
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
globals: true,
|
|
6
|
+
environment: 'happy-dom',
|
|
7
|
+
exclude: ['dist/**', '**/node_modules/**'],
|
|
8
|
+
coverage: {
|
|
9
|
+
provider: 'v8',
|
|
10
|
+
reporter: ['text', 'lcov'],
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
});
|