Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
b12750a
test(main-processor): add test case for folder wide search helpers
Ashminita28 May 25, 2026
38c118b
fix(main-processor): validate Os file path
Ashminita28 May 26, 2026
bc7cd6f
feat(watcher): add unlink and error handler in file watcher
Ashminita28 May 26, 2026
e617f6a
Merge branch 'dev' into feature/folder-search
Ashminita28 May 26, 2026
fe62280
feat(main-processor): add debounce timer and its test case
Ashminita28 May 26, 2026
5fd185d
fix(toc): fix alignment of sidebar contents & deep collapsing
Ashminita28 May 27, 2026
683374c
fix: fix TOC sidebar auto-collapse logic and matching tests
Ashminita28 May 27, 2026
91fa58c
test(toc): refactor & toc tests for toc extraction using marked AST
Ashminita28 May 27, 2026
db4b62a
refactor(toc): update toc extraction from regex methor to marked AST …
Ashminita28 May 27, 2026
9ff5ed0
test(main-processor): mock electron app in setup
Ashminita28 May 27, 2026
4deef58
test(main-processor): add recursive depth limit test case
Ashminita28 May 27, 2026
91ff7cd
refactor(main-processor): add depth limit to avoid stack failures on …
Ashminita28 May 27, 2026
3a3fab3
test(file): add test for debounce cleanup afer file unwatch
Ashminita28 May 28, 2026
0e1ac62
feat: replace default vite icon, update prettier ignore, add prepush
Ashminita28 May 28, 2026
9e186a5
feat(ci): add dependency audit and filesystem vulnerability scan in ci
Ashminita28 May 28, 2026
c1cf730
refactor(ci): setup pnpm before node
Ashminita28 May 28, 2026
a0778f2
chore: update package to fix high vulnerabiliy
Ashminita28 May 28, 2026
bb99e05
test(settings): add validation tests for validate settings
Ashminita28 May 28, 2026
7f57594
feat(shared): add app settings type and add type module to shared types
Ashminita28 May 28, 2026
384fcbc
fix(settings): fix setting font size test
Ashminita28 May 28, 2026
4a93345
feat(settings): add settings ipc handler & functionality in main proc…
Ashminita28 May 28, 2026
7e815c2
refactor(shared): add settings menu constants & update markdown API
Ashminita28 May 28, 2026
12498ef
feat(renderer): connect settings feature to renderer
Ashminita28 May 28, 2026
ffd9cd2
feat(main-processor): add menu missing Theme submenu and dev tools hi…
Ashminita28 May 29, 2026
5b3f66f
refactor(shared): modify ipc type for menu event
Ashminita28 May 29, 2026
ca53017
feat(renderer): add os theme detection & connect theme submenu to ren…
Ashminita28 May 29, 2026
1873167
fix(export): fix embedding image issue
Ashminita28 May 31, 2026
83ac13a
fix: code rabbit issues
Ashminita28 May 31, 2026
8fed99d
feat: add unsigned install notice
Ashminita28 May 31, 2026
3b16643
fix(ci): change pnpm version and add secific version to security
Ashminita28 May 31, 2026
f325be6
fix(ci): fix pipeline issue by removing defined version of pnpm
Ashminita28 May 31, 2026
3583cc1
fix(security): update trivy action version
Ashminita28 May 31, 2026
64b6baf
refactor: modify test folder name
Ashminita28 May 31, 2026
7472b7b
feat: add folder serach types & props
Ashminita28 May 31, 2026
6373040
feat(renderer): add folder search menu events
Ashminita28 May 31, 2026
edc99ea
feat(main-processor): add folder search ipc handler and main processor
Ashminita28 May 31, 2026
be79e36
feat(renderer): add folder search to search bar component
Ashminita28 May 31, 2026
ee8ad9b
fix(test): fix name in test file
Ashminita28 May 31, 2026
c6a18d5
fix: fix error handling and props passing issues
Ashminita28 May 31, 2026
fe9e236
test: add failure test case
Ashminita28 May 31, 2026
d29a2a5
fix: menu events hook
Ashminita28 May 31, 2026
5276c12
refactor(renderer): fix folder search state and TOC handling
Ashminita28 May 31, 2026
67d9232
test(settings): add test validate all settings fields
Ashminita28 Jun 1, 2026
1a442a4
test(export): add temp directory clean up
Ashminita28 Jun 1, 2026
f94d8e2
refactor(settings): remove fallback process.cwd()
Ashminita28 Jun 1, 2026
039c105
refactor(types): separate default settings type, modify import
Ashminita28 Jun 1, 2026
8d58a35
refactor(settings): add keyboard dismissal and focus management
Ashminita28 Jun 1, 2026
618b7dd
fix(preload): add constrain onMenuEvent to known menu channels
Ashminita28 Jun 1, 2026
952d91e
fix(settings): add atomic writes to prevent settings corruption
Ashminita28 Jun 1, 2026
06f2341
fix(search): invalidate pending searches & rename test sentence
Ashminita28 Jun 1, 2026
0bad2a8
fix(search): remover folder search UI from depending on document sear…
Ashminita28 Jun 1, 2026
2fed732
fix(search): fix folder search query propagation and race conditions
Ashminita28 Jun 1, 2026
41c4480
Merge branch 'fix/system-hardening' into feature/folder-search
Ashminita28 Jun 1, 2026
4d3fb41
fix: code rabbit issues
Ashminita28 Jun 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 33 additions & 4 deletions .github/workflows/development.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ jobs:
node-version: 22

- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 10
uses: pnpm/action-setup@v4
Comment thread
Ashminita28 marked this conversation as resolved.

- name: Install dependencies
run: pnpm install --frozen-lockfile
Expand All @@ -40,4 +38,35 @@ jobs:
run: pnpm test:coverage

- name: Build Electron app
run: pnpm dist
run: pnpm dist

security:
name: Security Audit
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: pnpm audit
run: pnpm audit --audit-level=high

- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@v0.36.0
with:
scan-type: fs
scan-ref: .
severity: CRITICAL,HIGH
format: table
exit-code: 1
37 changes: 35 additions & 2 deletions .github/workflows/production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
node-version: 22

- name: Setup pnpm
uses: pnpm/action-setup@v3
uses: pnpm/action-setup@v4
with:
version: 10

Expand All @@ -40,4 +40,37 @@ jobs:
run: pnpm test:coverage

- name: Build Electron app
run: pnpm dist
run: pnpm dist

security:
name: Security Audit
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v4
Comment thread
Ashminita28 marked this conversation as resolved.
with:
version: 10

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: pnpm audit
run: pnpm audit --audit-level=high

- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@v0.36.0
with:
scan-type: fs
scan-ref: .
severity: CRITICAL,HIGH
format: table
exit-code: 1
46 changes: 39 additions & 7 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,40 @@
node_modules
dist
build
# Dependencies
node_modules/

# Build outputs
out/
dist/
build/
release/
*.tgz
.vite/

# Electron builder
.cache/

# Environment
.env
out
coverage
release
.vite/
.env.local
.env.*.local

# Logs
*.log
npm-debug.log*
pnpm-debug.log*

# OS artifacts
.DS_Store
Thumbs.db

# IDE
.vscode/
.idea/
*.swp
*.swo

# Test coverage
coverage/

# Docusaurus
docs/.docusaurus/
docs/build/
1 change: 1 addition & 0 deletions .husky/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pnpm test
7 changes: 6 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
node_modules
dist
.next
build
build
out/
release/
coverage/
docs/.docusaurus/
docs/build/
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { describe, it, expect } from 'vitest';
import { mkdtemp, writeFile, rm } from 'node:fs/promises';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { buildDocument } from '../src/export/buildDocument';
import { sanitizeCss } from '../src/export/sanitizeCss';
import { getImage } from '../src/export/getImage';
import { inlineImages } from '../src/export/inlineImage';

describe('build html document', () => {
it('wraps content in a valid HTML5 document shell', () => {
Expand Down Expand Up @@ -50,3 +54,17 @@ describe('get image of mime type', () => {
expect(getImage('image.webp')).toBe('image/webp');
});
});

describe('inline images for HTML export', () => {
it('keeps local images as base 64 data URIs', async () => {
const dir = await mkdtemp(join(tmpdir(), 'markdown-reader-export-'));
try {
const imagePath = join(dir, 'image.png');
await writeFile(imagePath, Buffer.from([137, 80, 78, 71]));
const html = await inlineImages(`<img src="${imagePath}"/>`);
expect(html).toContain('src="data:image/png;base64,');
} finally {
await rm(dir, { recursive: true, force: true });
}
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { vi, describe, expect, it, beforeAll, afterAll } from 'vitest';
import { writeFileSync, unlinkSync, mkdirSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { getWatcherDiagnostics } from '../src/file';

import { readFile, watchFile, unWatchFile } from '../src/file';

Expand Down Expand Up @@ -47,6 +48,7 @@ describe('File watcher', () => {
writeFileSync(TEST_FILE, 'change after unwatch');
await new Promise((r) => setTimeout(r, 200));
expect(cb).not.toHaveBeenCalled();
expect(getWatcherDiagnostics().debounceTimers).toBe(0);
});

it('should protect from double call', async () => {
Expand All @@ -61,4 +63,23 @@ describe('File watcher', () => {
it('should not crash if I unwatch a file which is not watched', async () => {
await expect(unWatchFile('fake-file.txt')).resolves.toBeUndefined();
});

it('should clean debounce timer references after unwatching', async () => {
const cb = vi.fn();
await watchFile(TEST_FILE, cb);
writeFileSync(TEST_FILE, 'change with pending debounce');
await new Promise((r) => setTimeout(r, 25));
await unWatchFile(TEST_FILE);
expect(getWatcherDiagnostics().debounceTimers).toBe(0);
});

it('does not call callback for a file unwatched during debounce', async () => {
const cb = vi.fn();
await watchFile(TEST_FILE, cb);
writeFileSync(TEST_FILE, 'change before immediate unwatch');
await new Promise((r) => setTimeout(r, 25));
await unWatchFile(TEST_FILE);
await new Promise((r) => setTimeout(r, 200));
expect(cb).not.toHaveBeenCalled();
});
});
48 changes: 48 additions & 0 deletions apps/main-processor/__tests__/folder-search.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { writeFileSync, mkdirSync, rmSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { searchFolder } from '../src/folder-search';

const DIR = join(tmpdir(), 'md-search-test');

beforeAll(() => {
mkdirSync(DIR, { recursive: true });
writeFileSync(join(DIR, 'README.md'), '# Project\nThis project uses React.\nReact is great.');
writeFileSync(join(DIR, 'CHANGELOG.md'), '# Changes\nVersion 1.0 released.');
writeFileSync(join(DIR, 'notes.txt'), 'not a markdown file');
});
afterAll(() => {
rmSync(DIR, { recursive: true, force: true });
});
Comment thread
Ashminita28 marked this conversation as resolved.

describe('search folder', () => {
it('should find matches in .md files and returns results', async () => {
const results = await searchFolder(DIR, 'React');
expect(results.length).toBeGreaterThan(0);
expect(results.some((r) => r.filePath.endsWith('README.md'))).toBe(true);
});

it('does not search non .md files', async () => {
const results = await searchFolder(DIR, 'not a markdown');
expect(results.every((r) => r.filePath.endsWith('.md'))).toBe(true);
});

it('should return empty array when query matches nothing', async () => {
const results = await searchFolder(DIR, 'xyznotfound');
expect(results).toHaveLength(0);
});

it('has search case insensitive', async () => {
const lower = await searchFolder(DIR, 'react');
const upper = await searchFolder(DIR, 'REACT');
expect(lower.length).toBe(upper.length);
});

it('returns an empty array if the directory does not exist', async () => {
const nonExistentDir = join(DIR, 'does-not-exist-folder');

const results = await searchFolder(nonExistentDir, 'React');
expect(results).toEqual([]);
});
});
Comment thread
Ashminita28 marked this conversation as resolved.
22 changes: 22 additions & 0 deletions apps/main-processor/__tests__/folder.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { describe, expect, it } from 'vitest';
import { mkdtemp, mkdir, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { getFolder } from '../src/folder';

describe('folder reader testing', async () => {
it('should enforce the recursion depth', async () => {
const root = await mkdtemp(join(tmpdir(), 'markdown-reader-folder-'));
let current = root;
for (let index = 0; index < 12; index += 1) {
current = join(current, `level-${index}`);
await mkdir(current);
await writeFile(join(current, `file-${index}.md`), '# test', 'utf-8');
}

const tree = await getFolder(root, 2);
const first = tree.children?.find((child) => child.isDir);
const second = first?.children?.find((child) => child.isDir);
expect(second?.children).toEqual([]);
});
Comment thread
Ashminita28 marked this conversation as resolved.
});
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import { vi, describe, it, expect } from 'vitest';
vi.mock('electron', () => ({
app: {
isPackaged: false,
},
}));
import { describe, it, expect } from 'vitest';
import { RecentFile } from '@package/shared-types';
import { addToRecentList } from '../src/recent/addToRecentList';
import { getUniqueRecentFile } from '../src/recent/getUniqueRecentFile';
Expand Down
60 changes: 60 additions & 0 deletions apps/main-processor/__tests__/settings.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { describe, expect, it } from 'vitest';
import { validateSettings } from '../src/utils/helper/setting-helper';
import { READING_WIDTHS } from '../src/utils/constants/setting-constants';
import { THEMES } from '@package/shared-constants';
describe('settings validation', () => {
it('accepts valid settings', () => {
expect(validateSettings({ fontSize: 18 })).toEqual({ fontSize: 18 });
});

it('rejects unknown keys', () => {
expect(() => validateSettings({ unexpected: true } as never)).toThrow('Unknown settings key');
});

it('rejects invalid values', () => {
expect(() => validateSettings({ fontSize: 100 })).toThrow('Invalid fontSize');
});

it('validates theme values', () => {
expect(validateSettings({ theme: THEMES[0] })).toEqual({ theme: THEMES[0] });
expect(() => validateSettings({ theme: 'unknown-theme' as never })).toThrow('Invalid theme');
});

it('validates readingWidth values', () => {
expect(READING_WIDTHS.has('default')).toBe(true);
expect(validateSettings({ readingWidth: 'default' })).toEqual({ readingWidth: 'default' });
expect(() => validateSettings({ readingWidth: 'full' as never })).toThrow(
'Invalid readingWidth'
);
});

it('validates boolean settings', () => {
expect(validateSettings({ lineNumbers: true })).toEqual({ lineNumbers: true });
expect(validateSettings({ showHiddenFiles: false })).toEqual({ showHiddenFiles: false });
expect(() => validateSettings({ lineNumbers: 'yes' as never })).toThrow('Invalid lineNumbers');
expect(() => validateSettings({ showHiddenFiles: 'no' as never })).toThrow(
'Invalid showHiddenFiles'
);
});

it('validates customCss values', () => {
expect(validateSettings({ customCss: '.markdown-body { color: red; }' })).toEqual({
customCss: '.markdown-body { color: red; }',
});
expect(() => validateSettings({ customCss: 42 as never })).toThrow('Invalid customCss');
});

it('validates zoom range', () => {
expect(validateSettings({ zoom: 50 })).toEqual({ zoom: 50 });
expect(validateSettings({ zoom: 200 })).toEqual({ zoom: 200 });
expect(() => validateSettings({ zoom: 49 })).toThrow('Invalid zoom');
expect(() => validateSettings({ zoom: 201 })).toThrow('Invalid zoom');
});

it('validates recentFilesLimit range', () => {
expect(validateSettings({ recentFilesLimit: 1 })).toEqual({ recentFilesLimit: 1 });
expect(validateSettings({ recentFilesLimit: 50 })).toEqual({ recentFilesLimit: 50 });
expect(() => validateSettings({ recentFilesLimit: 0 })).toThrow('Invalid recentFilesLimit');
expect(() => validateSettings({ recentFilesLimit: 51 })).toThrow('Invalid recentFilesLimit');
});
});
8 changes: 8 additions & 0 deletions apps/main-processor/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { vi } from 'vitest';

vi.mock('electron', () => ({
app: {
isPackaged: false,
getPath: vi.fn(),
},
}));
2 changes: 1 addition & 1 deletion apps/main-processor/src/export/inlineImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export async function inlineImages(html: string): Promise<string> {

for (const match of matches) {
const fullMatch = match[0];
const src = match[1];
const src = match[2];

if (!src) continue;

Expand Down
Loading
Loading