@geminilight/mindos 0.6.71 → 0.6.73
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/_standalone/.mindos-build-version +1 -1
- package/_standalone/.next/BUILD_ID +1 -1
- package/_standalone/.next/app-path-routes-manifest.json +27 -27
- package/_standalone/.next/build-manifest.json +3 -3
- package/_standalone/.next/cache/.previewinfo +1 -1
- package/_standalone/.next/cache/.rscinfo +1 -1
- package/_standalone/.next/cache/config.json +3 -3
- package/_standalone/.next/prerender-manifest.json +3 -3
- package/_standalone/.next/react-loadable-manifest.json +4 -4
- package/_standalone/.next/server/app/.well-known/agent-card.json/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/_global-error.html +2 -2
- package/_standalone/.next/server/app/_global-error.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/_standalone/.next/server/app/_not-found/page.js +1 -1
- package/_standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/agents/[agentKey]/page.js +1 -1
- package/_standalone/.next/server/app/agents/[agentKey]/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/agents/[agentKey]/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/agents/page.js +1 -1
- package/_standalone/.next/server/app/agents/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/agents/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/agents/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/delegations/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/discover/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/config/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/detect/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/install/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/registry/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/session/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/agent-activity/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/agents/copy-skill/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/agents/custom/detect/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/agents/custom/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/ask/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/ask-sessions/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/auth/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/backlinks/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/bootstrap/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/changes/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/channels/verify/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/connect/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/embedding/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/export/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/extract-pdf/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/file/import/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/file/raw/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/file/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/graph/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/im/activity/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/im/config/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/im/status/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/im/test/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/im/webhook/feishu/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/im/webhook-status/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/inbox/clip/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/inbox/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/init/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/lint/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/agents/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/direct-tools/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/install/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/install-skill/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/restart/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/status/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/tools/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/uninstall/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/monitoring/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/recent-files/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/restart/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/list-models/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/reset-token/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/test-key/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/check-path/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/check-port/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/generate-token/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/ls/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/space-overview/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/sync/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/tree-version/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/uninstall/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/update-check/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/update-status/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/workflows/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/changelog/page.js +1 -1
- package/_standalone/.next/server/app/changelog/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/changelog/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/changes/page.js +1 -1
- package/_standalone/.next/server/app/changes/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/changes/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/echo/[segment]/page.js +1 -1
- package/_standalone/.next/server/app/echo/[segment]/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/echo/[segment]/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/echo/page.js +1 -1
- package/_standalone/.next/server/app/echo/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/echo/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/explore/page.js +1 -1
- package/_standalone/.next/server/app/explore/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/explore/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/help/page.js +1 -1
- package/_standalone/.next/server/app/help/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/help/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/inbox/history/page.js +1 -1
- package/_standalone/.next/server/app/inbox/history/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/inbox/history/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/login/page.js +1 -1
- package/_standalone/.next/server/app/login/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/page.js +1 -1
- package/_standalone/.next/server/app/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/setup/page.js +1 -1
- package/_standalone/.next/server/app/setup/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/trash/page.js +2 -2
- package/_standalone/.next/server/app/trash/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/view/[...path]/page.js +2 -2
- package/_standalone/.next/server/app/view/[...path]/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/view/[...path]/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app-paths-manifest.json +27 -27
- package/_standalone/.next/server/chunks/{3311.js → 2449.js} +2 -2
- package/_standalone/.next/server/chunks/5299.js +1 -1
- package/_standalone/.next/server/chunks/6022.js +34 -34
- package/_standalone/.next/server/middleware-build-manifest.js +1 -1
- package/_standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/_standalone/.next/server/pages/500.html +2 -2
- package/_standalone/.next/server/server-reference-manifest.js +1 -1
- package/_standalone/.next/server/server-reference-manifest.json +1 -1
- package/_standalone/.next/static/chunks/{7143.879daa87569c5b02.js → 4094.09364c01df411380.js} +1 -1
- package/_standalone/.next/static/chunks/{5795.d9099a1afecd6047.js → 5331.c89084fd7f67887d.js} +2 -2
- package/_standalone/.next/static/chunks/app/{layout-a344709b8447be75.js → layout-fcbde5bee626d21a.js} +63 -63
- package/_standalone/.next/static/chunks/app/trash/page-e623ff0ab35de002.js +1 -0
- package/_standalone/.next/static/chunks/app/view/[...path]/page-49c4eff6ffdb5168.js +12 -0
- package/_standalone/.next/static/chunks/{webpack-2f2787d3469d3df1.js → webpack-dc486b68118d1328.js} +1 -1
- package/_standalone/.next/trace +72 -72
- package/_standalone/package-lock.json +2 -2
- package/_standalone/package.json +1 -1
- package/app/package.json +1 -1
- package/package.json +1 -1
- package/_standalone/.next/static/chunks/app/trash/page-0907fdd06a4467de.js +0 -1
- package/_standalone/.next/static/chunks/app/view/[...path]/page-f53ce199b4a4bbb5.js +0 -12
- package/browser-extension/README.md +0 -160
- package/browser-extension/build.mjs +0 -63
- package/browser-extension/extension/background/service-worker.js +0 -1
- package/browser-extension/extension/content/extractor.js +0 -2
- package/browser-extension/extension/icons/icon-128.png +0 -0
- package/browser-extension/extension/icons/icon-128.svg +0 -4
- package/browser-extension/extension/icons/icon-16.png +0 -0
- package/browser-extension/extension/icons/icon-16.svg +0 -4
- package/browser-extension/extension/icons/icon-32.png +0 -0
- package/browser-extension/extension/icons/icon-32.svg +0 -4
- package/browser-extension/extension/icons/icon-48.png +0 -0
- package/browser-extension/extension/icons/icon-48.svg +0 -4
- package/browser-extension/extension/manifest.json +0 -47
- package/browser-extension/extension/popup/popup.css +0 -510
- package/browser-extension/extension/popup/popup.html +0 -128
- package/browser-extension/extension/popup/popup.js +0 -73
- package/browser-extension/package-lock.json +0 -617
- package/browser-extension/package.json +0 -21
- package/browser-extension/scripts/gen-icons.sh +0 -38
- package/browser-extension/src/background/service-worker.ts +0 -27
- package/browser-extension/src/content/extractor.ts +0 -44
- package/browser-extension/src/icons/icon-128.png +0 -0
- package/browser-extension/src/icons/icon-128.svg +0 -4
- package/browser-extension/src/icons/icon-16.png +0 -0
- package/browser-extension/src/icons/icon-16.svg +0 -4
- package/browser-extension/src/icons/icon-32.png +0 -0
- package/browser-extension/src/icons/icon-32.svg +0 -4
- package/browser-extension/src/icons/icon-48.png +0 -0
- package/browser-extension/src/icons/icon-48.svg +0 -4
- package/browser-extension/src/lib/api.ts +0 -146
- package/browser-extension/src/lib/markdown.ts +0 -68
- package/browser-extension/src/lib/storage.ts +0 -37
- package/browser-extension/src/lib/types.ts +0 -42
- package/browser-extension/src/manifest.json +0 -47
- package/browser-extension/src/popup/popup.css +0 -510
- package/browser-extension/src/popup/popup.html +0 -128
- package/browser-extension/src/popup/popup.ts +0 -416
- package/browser-extension/tsconfig.json +0 -16
- package/tests/e2e/README.md +0 -25
- package/tests/e2e/navigation.spec.ts +0 -14
- package/tests/e2e/playwright.config.ts +0 -14
- package/tests/integration/README.md +0 -25
- package/tests/integration/mcp-contract.test.ts +0 -57
- package/tests/integration/mcp-transport.test.ts +0 -361
- package/tests/integration/package-lock.json +0 -1463
- package/tests/integration/package.json +0 -8
- package/tests/integration/vitest.config.ts +0 -11
- package/tests/security-hardening.test.ts +0 -456
- package/tests/unit/build-integrity.test.ts +0 -137
- package/tests/unit/cli-build.test.ts +0 -180
- package/tests/unit/cli-config.test.ts +0 -257
- package/tests/unit/cli-mcp-install-toml.test.ts +0 -586
- package/tests/unit/cli-mcp-install.test.ts +0 -123
- package/tests/unit/cli-mcp-stdio-default.test.ts +0 -180
- package/tests/unit/cli-modules-load.test.ts +0 -64
- package/tests/unit/cli-port.test.ts +0 -87
- package/tests/unit/cli-skill-auto-copy.test.ts +0 -260
- package/tests/unit/cli-smoke.test.ts +0 -88
- package/tests/unit/cli-uninstall.test.ts +0 -218
- package/tests/unit/cli-update-root.test.ts +0 -89
- package/tests/unit/cli-user-flow-sim.test.ts +0 -506
- package/tests/unit/cli-wait-hint.test.ts +0 -86
- package/tests/unit/custom-agents.test.ts +0 -478
- package/tests/unit/dep-safety.test.ts +0 -126
- package/tests/unit/detect-system-lang.test.ts +0 -122
- package/tests/unit/mcp-build.test.ts +0 -162
- package/tests/unit/setup-needs-restart.test.ts +0 -139
- package/tests/unit/stop-restart.test.ts +0 -393
- package/tests/unit/vitest.config.ts +0 -8
- /package/_standalone/.next/static/{w5bqzZbd2_vdoPRB0JQ_I → Dn8EHqUedSzanCfrM8WWS}/_buildManifest.js +0 -0
- /package/_standalone/.next/static/{w5bqzZbd2_vdoPRB0JQ_I → Dn8EHqUedSzanCfrM8WWS}/_ssgManifest.js +0 -0
|
@@ -1,456 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Security hardening test suite for MindOS update system.
|
|
3
|
-
* Tests cover:
|
|
4
|
-
* - Symlink attack prevention
|
|
5
|
-
* - Path traversal prevention
|
|
6
|
-
* - Race condition detection
|
|
7
|
-
* - Atomic operation verification
|
|
8
|
-
* - Data loss prevention
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
12
|
-
import path from 'path';
|
|
13
|
-
import os from 'os';
|
|
14
|
-
import {
|
|
15
|
-
mkdirSync, writeFileSync, readFileSync, unlinkSync, symlinkSync,
|
|
16
|
-
existsSync, statSync, rmdirSync, rmSync, realpathSync,
|
|
17
|
-
} from 'fs';
|
|
18
|
-
import {
|
|
19
|
-
isSymlink, assertNotSymlink, assertNoSymlinksInPath,
|
|
20
|
-
safeRmSync, safeMkdir, getSafeStats, assessDeletionRisk,
|
|
21
|
-
} from '../../desktop/src/safe-rm';
|
|
22
|
-
import {
|
|
23
|
-
validateRuntimePath, getRuntimePaths, isValidDirName, sanitizeDirName,
|
|
24
|
-
} from '../../desktop/src/safe-paths';
|
|
25
|
-
|
|
26
|
-
describe('Security Hardening - Symlink Protection', () => {
|
|
27
|
-
let tempDir: string;
|
|
28
|
-
|
|
29
|
-
beforeEach(() => {
|
|
30
|
-
tempDir = path.join(os.tmpdir(), `mindos-test-${Date.now()}`);
|
|
31
|
-
mkdirSync(tempDir, { recursive: true });
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
afterEach(() => {
|
|
35
|
-
try {
|
|
36
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
37
|
-
} catch { /* ignore */ }
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
describe('Symlink Detection', () => {
|
|
41
|
-
it('should detect direct symlinks', () => {
|
|
42
|
-
const targetDir = path.join(tempDir, 'target');
|
|
43
|
-
const linkDir = path.join(tempDir, 'link');
|
|
44
|
-
|
|
45
|
-
mkdirSync(targetDir);
|
|
46
|
-
symlinkSync(targetDir, linkDir);
|
|
47
|
-
|
|
48
|
-
expect(isSymlink(linkDir)).toBe(true);
|
|
49
|
-
expect(isSymlink(targetDir)).toBe(false);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('should return false for non-existent paths', () => {
|
|
53
|
-
const nonExistent = path.join(tempDir, 'does-not-exist');
|
|
54
|
-
expect(isSymlink(nonExistent)).toBe(false);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('should detect symlinks pointing to files', () => {
|
|
58
|
-
const file = path.join(tempDir, 'file.txt');
|
|
59
|
-
const link = path.join(tempDir, 'link.txt');
|
|
60
|
-
|
|
61
|
-
writeFileSync(file, 'content');
|
|
62
|
-
symlinkSync(file, link);
|
|
63
|
-
|
|
64
|
-
expect(isSymlink(link)).toBe(true);
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
describe('Assertion: assertNotSymlink', () => {
|
|
69
|
-
it('should throw when path is a symlink', () => {
|
|
70
|
-
const targetDir = path.join(tempDir, 'target');
|
|
71
|
-
const linkDir = path.join(tempDir, 'link');
|
|
72
|
-
|
|
73
|
-
mkdirSync(targetDir);
|
|
74
|
-
symlinkSync(targetDir, linkDir);
|
|
75
|
-
|
|
76
|
-
expect(() => {
|
|
77
|
-
assertNotSymlink(linkDir);
|
|
78
|
-
}).toThrow(/SECURITY.*symlink/i);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('should not throw for regular directories', () => {
|
|
82
|
-
const regularDir = path.join(tempDir, 'regular');
|
|
83
|
-
mkdirSync(regularDir);
|
|
84
|
-
|
|
85
|
-
expect(() => {
|
|
86
|
-
assertNotSymlink(regularDir);
|
|
87
|
-
}).not.toThrow();
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('should not throw for non-existent paths', () => {
|
|
91
|
-
const nonExistent = path.join(tempDir, 'does-not-exist');
|
|
92
|
-
expect(() => {
|
|
93
|
-
assertNotSymlink(nonExistent);
|
|
94
|
-
}).not.toThrow();
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
describe('Path Chain Symlink Check', () => {
|
|
99
|
-
it('should detect symlinks in parent chain', () => {
|
|
100
|
-
const realDir = path.join(tempDir, 'real');
|
|
101
|
-
const symlinkParent = path.join(tempDir, 'symlink-parent');
|
|
102
|
-
const childPath = path.join(symlinkParent, 'child');
|
|
103
|
-
|
|
104
|
-
mkdirSync(realDir);
|
|
105
|
-
symlinkSync(realDir, symlinkParent);
|
|
106
|
-
|
|
107
|
-
expect(() => {
|
|
108
|
-
assertNoSymlinksInPath(childPath, tempDir);
|
|
109
|
-
}).toThrow(/SECURITY.*symlink/i);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('should allow clean parent chains', () => {
|
|
113
|
-
const dir1 = path.join(tempDir, 'dir1');
|
|
114
|
-
const dir2 = path.join(dir1, 'dir2');
|
|
115
|
-
|
|
116
|
-
mkdirSync(dir2, { recursive: true });
|
|
117
|
-
|
|
118
|
-
expect(() => {
|
|
119
|
-
assertNoSymlinksInPath(dir2, tempDir);
|
|
120
|
-
}).not.toThrow();
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
describe('Security Hardening - Safe Deletion', () => {
|
|
126
|
-
let tempDir: string;
|
|
127
|
-
|
|
128
|
-
beforeEach(() => {
|
|
129
|
-
tempDir = path.join(os.tmpdir(), `mindos-saferm-${Date.now()}`);
|
|
130
|
-
mkdirSync(tempDir, { recursive: true });
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
afterEach(() => {
|
|
134
|
-
try {
|
|
135
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
136
|
-
} catch { /* ignore */ }
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
describe('safeRmSync', () => {
|
|
140
|
-
it('should refuse to delete symlinks', () => {
|
|
141
|
-
const targetDir = path.join(tempDir, 'target');
|
|
142
|
-
const linkDir = path.join(tempDir, 'link');
|
|
143
|
-
|
|
144
|
-
mkdirSync(targetDir);
|
|
145
|
-
symlinkSync(targetDir, linkDir);
|
|
146
|
-
|
|
147
|
-
expect(() => {
|
|
148
|
-
safeRmSync(linkDir);
|
|
149
|
-
}).toThrow(/SECURITY.*symlink/i);
|
|
150
|
-
|
|
151
|
-
// Verify link still exists
|
|
152
|
-
expect(existsSync(linkDir)).toBe(true);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('should safely delete regular directories', () => {
|
|
156
|
-
const dir = path.join(tempDir, 'regular-dir');
|
|
157
|
-
mkdirSync(dir);
|
|
158
|
-
writeFileSync(path.join(dir, 'file.txt'), 'content');
|
|
159
|
-
|
|
160
|
-
expect(() => {
|
|
161
|
-
safeRmSync(dir, { recursive: true });
|
|
162
|
-
}).not.toThrow();
|
|
163
|
-
|
|
164
|
-
expect(existsSync(dir)).toBe(false);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it('should be idempotent for non-existent paths', () => {
|
|
168
|
-
const nonExistent = path.join(tempDir, 'does-not-exist');
|
|
169
|
-
|
|
170
|
-
expect(() => {
|
|
171
|
-
safeRmSync(nonExistent);
|
|
172
|
-
}).not.toThrow();
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
it('should refuse to delete non-directories without recursive flag', () => {
|
|
176
|
-
const file = path.join(tempDir, 'file.txt');
|
|
177
|
-
writeFileSync(file, 'content');
|
|
178
|
-
|
|
179
|
-
// Should work for files
|
|
180
|
-
expect(() => {
|
|
181
|
-
safeRmSync(file);
|
|
182
|
-
}).not.toThrow();
|
|
183
|
-
|
|
184
|
-
expect(existsSync(file)).toBe(false);
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
describe('safeMkdir', () => {
|
|
189
|
-
it('should refuse to create in symlinked parents', () => {
|
|
190
|
-
const realDir = path.join(tempDir, 'real');
|
|
191
|
-
const symlinkParent = path.join(tempDir, 'symlink-parent');
|
|
192
|
-
const newDir = path.join(symlinkParent, 'newdir');
|
|
193
|
-
|
|
194
|
-
mkdirSync(realDir);
|
|
195
|
-
symlinkSync(realDir, symlinkParent);
|
|
196
|
-
|
|
197
|
-
expect(() => {
|
|
198
|
-
safeMkdir(newDir);
|
|
199
|
-
}).toThrow(/SECURITY.*symlink/i);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it('should create directories in clean paths', () => {
|
|
203
|
-
const dir = path.join(tempDir, 'a', 'b', 'c');
|
|
204
|
-
|
|
205
|
-
expect(() => {
|
|
206
|
-
safeMkdir(dir);
|
|
207
|
-
}).not.toThrow();
|
|
208
|
-
|
|
209
|
-
expect(existsSync(dir)).toBe(true);
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
describe('Risk Assessment', () => {
|
|
214
|
-
it('should identify symlink risks', () => {
|
|
215
|
-
const targetDir = path.join(tempDir, 'target');
|
|
216
|
-
const linkDir = path.join(tempDir, 'link');
|
|
217
|
-
|
|
218
|
-
mkdirSync(targetDir);
|
|
219
|
-
symlinkSync(targetDir, linkDir);
|
|
220
|
-
|
|
221
|
-
const risks = assessDeletionRisk(linkDir, tempDir);
|
|
222
|
-
expect(risks.isSymlink).toBe(true);
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
it('should identify system path risks', () => {
|
|
226
|
-
const externalPath = '/etc/passwd';
|
|
227
|
-
const risks = assessDeletionRisk(externalPath, tempDir);
|
|
228
|
-
expect(risks.isSystemPath).toBe(true);
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it('should clean paths without risks', () => {
|
|
232
|
-
const dir = path.join(tempDir, 'safe-dir');
|
|
233
|
-
mkdirSync(dir);
|
|
234
|
-
|
|
235
|
-
const risks = assessDeletionRisk(dir, tempDir);
|
|
236
|
-
expect(risks.isSymlink).toBe(false);
|
|
237
|
-
expect(risks.hasSymlinkParent).toBe(false);
|
|
238
|
-
expect(risks.isSystemPath).toBe(false);
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
describe('Security Hardening - Path Validation', () => {
|
|
244
|
-
const mockConfigDir = path.join(os.tmpdir(), '.mindos');
|
|
245
|
-
|
|
246
|
-
beforeEach(() => {
|
|
247
|
-
mkdirSync(mockConfigDir, { recursive: true });
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
afterEach(() => {
|
|
251
|
-
try {
|
|
252
|
-
rmSync(mockConfigDir, { recursive: true, force: true });
|
|
253
|
-
} catch { /* ignore */ }
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
describe('Path Traversal Prevention', () => {
|
|
257
|
-
it('should reject paths with ..', () => {
|
|
258
|
-
expect(() => {
|
|
259
|
-
validateRuntimePath('../../etc/passwd');
|
|
260
|
-
}).toThrow(/path traversal/i);
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
it('should reject absolute paths outside .mindos', () => {
|
|
264
|
-
expect(() => {
|
|
265
|
-
validateRuntimePath('/etc/passwd');
|
|
266
|
-
}).toThrow(/outside/i);
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it('should accept safe subdirectories', () => {
|
|
270
|
-
const safePath = 'runtime';
|
|
271
|
-
expect(() => {
|
|
272
|
-
validateRuntimePath(safePath);
|
|
273
|
-
}).not.toThrow();
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
it('should reject null bytes', () => {
|
|
277
|
-
expect(() => {
|
|
278
|
-
validateRuntimePath('runtime\0etc/passwd');
|
|
279
|
-
}).toThrow(/null byte/i);
|
|
280
|
-
});
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
describe('Directory Name Validation', () => {
|
|
284
|
-
it('should reject directory names with path separators', () => {
|
|
285
|
-
expect(isValidDirName('dir/name')).toBe(false);
|
|
286
|
-
expect(isValidDirName('dir\\name')).toBe(false);
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
it('should reject . and ..', () => {
|
|
290
|
-
expect(isValidDirName('.')).toBe(false);
|
|
291
|
-
expect(isValidDirName('..')).toBe(false);
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
it('should reject hidden directories (starting with .)', () => {
|
|
295
|
-
expect(isValidDirName('.hidden')).toBe(false);
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
it('should accept normal directory names', () => {
|
|
299
|
-
expect(isValidDirName('runtime')).toBe(true);
|
|
300
|
-
expect(isValidDirName('runtime-old')).toBe(true);
|
|
301
|
-
expect(isValidDirName('runtime-downloading')).toBe(true);
|
|
302
|
-
});
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
describe('Directory Name Sanitization', () => {
|
|
306
|
-
it('should sanitize path separators', () => {
|
|
307
|
-
const sanitized = sanitizeDirName('dir/name');
|
|
308
|
-
expect(sanitized).not.toContain('/');
|
|
309
|
-
expect(isValidDirName(sanitized)).toBe(true);
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
it('should sanitize null bytes', () => {
|
|
313
|
-
const sanitized = sanitizeDirName('name\0etc');
|
|
314
|
-
expect(sanitized).not.toContain('\0');
|
|
315
|
-
expect(isValidDirName(sanitized)).toBe(true);
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
it('should sanitize .. patterns', () => {
|
|
319
|
-
const sanitized = sanitizeDirName('name..etc');
|
|
320
|
-
expect(sanitized).toContain('__');
|
|
321
|
-
expect(isValidDirName(sanitized)).toBe(true);
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
it('should reject if sanitization fails', () => {
|
|
325
|
-
expect(() => {
|
|
326
|
-
sanitizeDirName('\0\0\0');
|
|
327
|
-
}).toThrow();
|
|
328
|
-
});
|
|
329
|
-
});
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
describe('Security Hardening - Atomic Operations', () => {
|
|
333
|
-
let tempDir: string;
|
|
334
|
-
|
|
335
|
-
beforeEach(() => {
|
|
336
|
-
tempDir = path.join(os.tmpdir(), `mindos-atomic-${Date.now()}`);
|
|
337
|
-
mkdirSync(tempDir, { recursive: true });
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
afterEach(() => {
|
|
341
|
-
try {
|
|
342
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
343
|
-
} catch { /* ignore */ }
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
it('should handle update rollback scenario', () => {
|
|
347
|
-
const runtimeDir = path.join(tempDir, 'runtime');
|
|
348
|
-
const downloadDir = path.join(tempDir, 'runtime-downloading');
|
|
349
|
-
const oldDir = path.join(tempDir, 'runtime-old');
|
|
350
|
-
|
|
351
|
-
// Simulate current runtime
|
|
352
|
-
mkdirSync(runtimeDir);
|
|
353
|
-
writeFileSync(path.join(runtimeDir, 'version.txt'), '1.0.0');
|
|
354
|
-
|
|
355
|
-
// Simulate new download
|
|
356
|
-
mkdirSync(downloadDir);
|
|
357
|
-
writeFileSync(path.join(downloadDir, 'version.txt'), '1.1.0');
|
|
358
|
-
|
|
359
|
-
// Simulate atomic apply
|
|
360
|
-
try {
|
|
361
|
-
// Move old → backup
|
|
362
|
-
if (existsSync(runtimeDir)) {
|
|
363
|
-
if (existsSync(oldDir)) {
|
|
364
|
-
safeRmSync(oldDir, { recursive: true });
|
|
365
|
-
}
|
|
366
|
-
// Simulate rename
|
|
367
|
-
const tempBackup = path.join(tempDir, 'runtime-backup-temp');
|
|
368
|
-
require('fs').renameSync(runtimeDir, tempBackup);
|
|
369
|
-
require('fs').renameSync(tempBackup, oldDir);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// Move new → current
|
|
373
|
-
require('fs').renameSync(downloadDir, runtimeDir);
|
|
374
|
-
|
|
375
|
-
// Verify new runtime is in place
|
|
376
|
-
expect(existsSync(runtimeDir)).toBe(true);
|
|
377
|
-
expect(readFileSync(path.join(runtimeDir, 'version.txt'), 'utf-8')).toBe('1.1.0');
|
|
378
|
-
} catch (err) {
|
|
379
|
-
// On error, verify rollback
|
|
380
|
-
expect(existsSync(runtimeDir)).toBe(true);
|
|
381
|
-
expect(readFileSync(path.join(runtimeDir, 'version.txt'), 'utf-8')).toBe('1.0.0');
|
|
382
|
-
}
|
|
383
|
-
});
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
describe('End-to-End Security Scenarios', () => {
|
|
387
|
-
let tempDir: string;
|
|
388
|
-
|
|
389
|
-
beforeEach(() => {
|
|
390
|
-
tempDir = path.join(os.tmpdir(), `mindos-e2e-${Date.now()}`);
|
|
391
|
-
mkdirSync(tempDir, { recursive: true });
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
afterEach(() => {
|
|
395
|
-
try {
|
|
396
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
397
|
-
} catch { /* ignore */ }
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
it('should prevent deletion via symlink to user data', () => {
|
|
401
|
-
// Setup: Create user data directory with marker
|
|
402
|
-
const userDataDir = path.join(tempDir, 'mind');
|
|
403
|
-
const guardFile = path.join(userDataDir, '.mindos-guard');
|
|
404
|
-
mkdirSync(userDataDir);
|
|
405
|
-
writeFileSync(guardFile, 'USER DATA');
|
|
406
|
-
|
|
407
|
-
// Attack: Create symlink pointing to user data
|
|
408
|
-
const symlinkToUserData = path.join(tempDir, 'runtime-old');
|
|
409
|
-
symlinkSync(userDataDir, symlinkToUserData);
|
|
410
|
-
|
|
411
|
-
// Defense: Should refuse deletion
|
|
412
|
-
expect(() => {
|
|
413
|
-
assertNotSymlink(symlinkToUserData);
|
|
414
|
-
}).toThrow();
|
|
415
|
-
|
|
416
|
-
// Verify user data still exists
|
|
417
|
-
expect(readFileSync(guardFile, 'utf-8')).toBe('USER DATA');
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
it('should prevent deletion via path traversal', () => {
|
|
421
|
-
// Setup: Create structure
|
|
422
|
-
const runtimeDir = path.join(tempDir, '.mindos', 'runtime');
|
|
423
|
-
mkdirSync(runtimeDir, { recursive: true });
|
|
424
|
-
|
|
425
|
-
// Attack: Try to validate malicious path
|
|
426
|
-
const maliciousPath = path.join(
|
|
427
|
-
tempDir,
|
|
428
|
-
'.mindos',
|
|
429
|
-
'runtime',
|
|
430
|
-
'../../../../etc/passwd'
|
|
431
|
-
);
|
|
432
|
-
|
|
433
|
-
// Defense: Should reject path traversal
|
|
434
|
-
expect(() => {
|
|
435
|
-
validateRuntimePath(maliciousPath);
|
|
436
|
-
}).toThrow();
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
it('should maintain consistency on update failure', () => {
|
|
440
|
-
// This test verifies the update system can recover from failures
|
|
441
|
-
// Setup initial state
|
|
442
|
-
const runtimeDir = path.join(tempDir, 'runtime');
|
|
443
|
-
mkdirSync(runtimeDir);
|
|
444
|
-
writeFileSync(path.join(runtimeDir, 'marker.txt'), 'original');
|
|
445
|
-
|
|
446
|
-
// Simulate interrupted update
|
|
447
|
-
// (Download starts but never completes)
|
|
448
|
-
const downloadDir = path.join(tempDir, 'runtime-downloading');
|
|
449
|
-
mkdirSync(downloadDir);
|
|
450
|
-
|
|
451
|
-
// After cleanup, original should be intact
|
|
452
|
-
safeRmSync(downloadDir, { recursive: true });
|
|
453
|
-
expect(existsSync(runtimeDir)).toBe(true);
|
|
454
|
-
expect(readFileSync(path.join(runtimeDir, 'marker.txt'), 'utf-8')).toBe('original');
|
|
455
|
-
});
|
|
456
|
-
});
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Build integrity tests — ensure the production build output is self-consistent,
|
|
7
|
-
* so users can actually load the GUI without broken CSS/JS references.
|
|
8
|
-
*
|
|
9
|
-
* These tests inspect the `.next` build output directly (no running server needed).
|
|
10
|
-
* They catch the class of bug where HTML references assets with hash X but the
|
|
11
|
-
* actual files have hash Y (e.g. after a partial rebuild or cache corruption).
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const APP_DIR = path.resolve(__dirname, '..', '..', 'app');
|
|
15
|
-
const NEXT_DIR = path.join(APP_DIR, '.next');
|
|
16
|
-
|
|
17
|
-
// Skip all tests if no complete build output exists (e.g. local dev without build)
|
|
18
|
-
const hasBuild = fs.existsSync(path.join(NEXT_DIR, 'build-manifest.json'));
|
|
19
|
-
|
|
20
|
-
describe.skipIf(!hasBuild)('build integrity', () => {
|
|
21
|
-
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
22
|
-
|
|
23
|
-
/** Recursively collect all files matching a predicate */
|
|
24
|
-
function walkFiles(dir: string, filter: (f: string) => boolean): string[] {
|
|
25
|
-
const results: string[] = [];
|
|
26
|
-
if (!fs.existsSync(dir)) return results;
|
|
27
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
28
|
-
const full = path.join(dir, entry.name);
|
|
29
|
-
if (entry.isDirectory()) {
|
|
30
|
-
results.push(...walkFiles(full, filter));
|
|
31
|
-
} else if (filter(entry.name)) {
|
|
32
|
-
results.push(full);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return results;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** Extract /_next/static/... references from file content */
|
|
39
|
-
function extractStaticRefs(content: string): string[] {
|
|
40
|
-
const refs: string[] = [];
|
|
41
|
-
// Match /_next/static/chunks/xxx.css and /_next/static/chunks/xxx.js
|
|
42
|
-
const pattern = /\/_next\/static\/[^\s"'`)]+?\.(css|js)/g;
|
|
43
|
-
let m: RegExpExecArray | null;
|
|
44
|
-
while ((m = pattern.exec(content)) !== null) {
|
|
45
|
-
refs.push(m[0]);
|
|
46
|
-
}
|
|
47
|
-
return refs;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// ── Tests ──────────────────────────────────────────────────────────────────
|
|
51
|
-
|
|
52
|
-
it('build output directory exists with required subdirs', () => {
|
|
53
|
-
expect(fs.existsSync(path.join(NEXT_DIR, 'static'))).toBe(true);
|
|
54
|
-
expect(fs.existsSync(path.join(NEXT_DIR, 'server'))).toBe(true);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('has at least one CSS file in static output', () => {
|
|
58
|
-
const cssFiles = walkFiles(path.join(NEXT_DIR, 'static'), f => f.endsWith('.css'));
|
|
59
|
-
expect(cssFiles.length).toBeGreaterThanOrEqual(1);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('has at least one JS chunk in static output', () => {
|
|
63
|
-
const jsFiles = walkFiles(path.join(NEXT_DIR, 'static'), f => f.endsWith('.js'));
|
|
64
|
-
expect(jsFiles.length).toBeGreaterThanOrEqual(1);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('all /_next/static references in server output resolve to actual files', () => {
|
|
68
|
-
// Scan server-rendered HTML/RSC files for /_next/static/... references
|
|
69
|
-
const serverFiles = walkFiles(path.join(NEXT_DIR, 'server'), f =>
|
|
70
|
-
f.endsWith('.html') || f.endsWith('.body') || f.endsWith('.rsc') || f.endsWith('.meta') || f.endsWith('.js')
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
const allRefs = new Set<string>();
|
|
74
|
-
const missing: string[] = [];
|
|
75
|
-
|
|
76
|
-
for (const file of serverFiles) {
|
|
77
|
-
const content = fs.readFileSync(file, 'utf-8');
|
|
78
|
-
for (const ref of extractStaticRefs(content)) {
|
|
79
|
-
allRefs.add(ref);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
for (const ref of allRefs) {
|
|
84
|
-
// /_next/static/chunks/xxx.css → .next/static/chunks/xxx.css
|
|
85
|
-
const onDisk = path.join(NEXT_DIR, ref.replace(/^\/_next\//, ''));
|
|
86
|
-
if (!fs.existsSync(onDisk)) {
|
|
87
|
-
missing.push(ref);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// If we found references, none should be missing
|
|
92
|
-
if (allRefs.size > 0) {
|
|
93
|
-
expect(missing).toEqual([]);
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('CSS files are non-empty and contain valid content', () => {
|
|
98
|
-
const cssFiles = walkFiles(path.join(NEXT_DIR, 'static'), f => f.endsWith('.css'));
|
|
99
|
-
for (const file of cssFiles) {
|
|
100
|
-
const stat = fs.statSync(file);
|
|
101
|
-
expect(stat.size, `CSS file should not be empty: ${path.basename(file)}`).toBeGreaterThan(0);
|
|
102
|
-
// Basic sanity: should contain CSS-like content (selectors, properties)
|
|
103
|
-
const content = fs.readFileSync(file, 'utf-8');
|
|
104
|
-
expect(
|
|
105
|
-
content.includes('{') && content.includes('}'),
|
|
106
|
-
`CSS file should contain valid CSS rules: ${path.basename(file)}`
|
|
107
|
-
).toBe(true);
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('build manifest exists and is valid JSON', () => {
|
|
112
|
-
const manifestPath = path.join(NEXT_DIR, 'build-manifest.json');
|
|
113
|
-
expect(fs.existsSync(manifestPath)).toBe(true);
|
|
114
|
-
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
115
|
-
expect(manifest).toHaveProperty('pages');
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it('build manifest page assets all exist on disk', () => {
|
|
119
|
-
const manifestPath = path.join(NEXT_DIR, 'build-manifest.json');
|
|
120
|
-
if (!fs.existsSync(manifestPath)) return;
|
|
121
|
-
|
|
122
|
-
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
123
|
-
const missing: string[] = [];
|
|
124
|
-
|
|
125
|
-
for (const [page, assets] of Object.entries(manifest.pages || {})) {
|
|
126
|
-
for (const asset of (assets as string[])) {
|
|
127
|
-
// Assets in manifest are relative paths like _next/static/chunks/xxx.js
|
|
128
|
-
const onDisk = path.join(NEXT_DIR, asset.replace(/^_next\//, ''));
|
|
129
|
-
if (!fs.existsSync(onDisk)) {
|
|
130
|
-
missing.push(`${page} → ${asset}`);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
expect(missing).toEqual([]);
|
|
136
|
-
});
|
|
137
|
-
});
|