Skip to content

Commit 2dbe503

Browse files
committed
release: v1.6.4
1 parent 65507d5 commit 2dbe503

10 files changed

Lines changed: 168 additions & 48 deletions

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "noteconnection",
3-
"version": "1.6.3",
3+
"version": "1.6.4",
44
"description": "Hierarchical Knowledge Graph Visualization System",
55
"main": "dist/src/server.js",
66
"bin": {

src/backend/utils/CrashLogger.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ import * as os from 'os';
44

55
export class CrashLogger {
66
private static logFilePath = path.join(process.cwd(), 'crash.log');
7+
private static globalHandlersInstalled = false;
8+
9+
static isIgnorableProcessWriteError(error: unknown): boolean {
10+
const code = typeof error === 'object' && error ? String((error as { code?: unknown }).code || '') : '';
11+
const syscall = typeof error === 'object' && error ? String((error as { syscall?: unknown }).syscall || '') : '';
12+
return code === 'EPIPE' || code === 'ERR_STREAM_DESTROYED' || (code === 'EOF' && syscall === 'write');
13+
}
714

815
static log(error: any, context: string = 'General') {
916
const timestamp = new Date().toISOString();
@@ -23,13 +30,24 @@ export class CrashLogger {
2330
}
2431

2532
static initGlobalHandlers() {
33+
if (this.globalHandlersInstalled) {
34+
return;
35+
}
36+
this.globalHandlersInstalled = true;
37+
2638
process.on('uncaughtException', (error) => {
39+
if (CrashLogger.isIgnorableProcessWriteError(error)) {
40+
return;
41+
}
2742
console.error('Uncaught Exception:', error);
2843
CrashLogger.log(error, 'UncaughtException');
2944
process.exit(1); // Exit is mandatory for uncaught exceptions to avoid undefined state
3045
});
3146

3247
process.on('unhandledRejection', (reason, promise) => {
48+
if (CrashLogger.isIgnorableProcessWriteError(reason)) {
49+
return;
50+
}
3351
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
3452
CrashLogger.log(reason, 'UnhandledRejection');
3553
});

src/crashlogger.contract.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { CrashLogger } from './backend/utils/CrashLogger';
2+
3+
describe('CrashLogger broken pipe policy', () => {
4+
test('treats write-side EPIPE and destroyed stream errors as ignorable process shutdown noise', () => {
5+
expect(
6+
CrashLogger.isIgnorableProcessWriteError(
7+
Object.assign(new Error('write EPIPE'), { code: 'EPIPE', syscall: 'write' })
8+
)
9+
).toBe(true);
10+
expect(
11+
CrashLogger.isIgnorableProcessWriteError(
12+
Object.assign(new Error('stream destroyed'), { code: 'ERR_STREAM_DESTROYED' })
13+
)
14+
).toBe(true);
15+
});
16+
17+
test('does not suppress unrelated runtime exceptions', () => {
18+
expect(
19+
CrashLogger.isIgnorableProcessWriteError(
20+
Object.assign(new Error('permission denied'), { code: 'EACCES', syscall: 'open' })
21+
)
22+
).toBe(false);
23+
expect(CrashLogger.isIgnorableProcessWriteError(new Error('boom'))).toBe(false);
24+
});
25+
});

src/notemd.api.contract.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ describe('NoteMD API contract wiring', () => {
2626
});
2727
});
2828

29-
test('server persists NoteMD settings in runtime data directory', () => {
30-
expect(serverSource).toContain('NOTEMD_CONFIG_FILE_NAME');
29+
test('server persists NoteMD settings through app_config.toml helpers', () => {
30+
expect(serverSource).toContain("from './notemd/AppConfigToml'");
31+
expect(serverSource).toContain('loadAppConfigToml');
32+
expect(serverSource).toContain('saveAppConfigToml');
3133
expect(serverSource).toContain('persistNotemdSettings');
3234
expect(serverSource).toContain('loadNotemdSettings');
3335
});
3436
});
35-

src/pkg.snapshot.safety.contract.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ describe('pkg snapshot safety contract', () => {
1818
});
1919
});
2020

21-
test('reader and source manager keep static import fallback paths for packaging', () => {
21+
test('reader and source manager keep packaging-safe Mermaid/runtime fallback paths', () => {
2222
const readerRenderer = fs.readFileSync(runtimeEntryFiles[0], 'utf8');
2323
const sourceManager = fs.readFileSync(runtimeEntryFiles[2], 'utf8');
2424

2525
expect(readerRenderer).toContain('loadMermaidModule');
26-
expect(readerRenderer).toContain("import('mermaid')");
26+
expect(readerRenderer).toContain('MERMAID_BROWSER_BUNDLE_BASE64');
27+
expect(readerRenderer).toContain("createElement('script')");
28+
expect(readerRenderer).not.toContain("require('mermaid')");
2729
expect(sourceManager).toContain('parseGraphDataPayload');
2830
expect(sourceManager).toContain('Fallback for assignment-based payloads without using runtime eval.');
2931
});

src/reader_renderer.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import fs from 'node:fs';
12
import { execFileSync } from 'node:child_process';
23
import path from 'node:path';
34
import { JSDOM } from 'jsdom';
@@ -96,6 +97,14 @@ describe('reader_renderer', () => {
9697
});
9798
}, 120000);
9899

100+
it('keeps the compiled Mermaid loader on the embedded browser runtime path instead of requiring the ESM package directly', () => {
101+
const compiledRenderer = fs.readFileSync(distRendererPath, 'utf8');
102+
103+
expect(compiledRenderer).not.toContain("require('mermaid')");
104+
expect(compiledRenderer).toContain('MERMAID_BROWSER_BUNDLE_BASE64');
105+
expect(compiledRenderer).toContain("createElement('script')");
106+
});
107+
99108
it('renders display math to svg', () => {
100109
const svg = runRenderer('renderMathSvg', 'E = mc^2', { displayMode: true });
101110

src/reader_renderer.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ const { HandlerList } = require('mathjax-full/js/core/HandlerList.js');
1212
const { HTMLHandler } = require('mathjax-full/js/handlers/html/HTMLHandler.js');
1313
const { initWasm, Resvg } = require('@resvg/resvg-wasm');
1414

15-
const importMermaidModule = (): Promise<any> => import('mermaid');
1615
const MATH_TEXT_COLOR = '#eef4ff';
1716
const MERMAID_BACKGROUND = 'transparent';
1817
const MERMAID_PADDING = 28;
@@ -104,7 +103,6 @@ const mathDocument = mathHandlers.document('', {
104103
});
105104

106105
let mermaidEnvironmentPromise: Promise<MermaidEnvironment> | null = null;
107-
let mermaidModulePromise: Promise<any> | null = null;
108106
let mermaidRenderQueue: Promise<unknown> = Promise.resolve();
109107
let mermaidRenderCounter = 0;
110108
let resvgInitPromise: Promise<void> | null = null;
@@ -468,14 +466,9 @@ async function createMermaidEnvironment(theme: 'dark' | 'default'): Promise<Merm
468466
}
469467

470468
async function loadMermaidModule(window: JSDOM['window']): Promise<any> {
471-
if (!(process as any).pkg) {
472-
if (!mermaidModulePromise) {
473-
mermaidModulePromise = (async () => {
474-
const mermaidModule = await importMermaidModule();
475-
return mermaidModule.default ?? mermaidModule;
476-
})();
477-
}
478-
return mermaidModulePromise;
469+
const existingMermaid = (window as any).mermaid;
470+
if (existingMermaid) {
471+
return existingMermaid;
479472
}
480473

481474
const script = window.document.createElement('script');
@@ -1604,5 +1597,3 @@ function isFiniteBounds(bounds: Bounds): boolean {
16041597

16051598

16061599

1607-
1608-

src/server.port.fallback.contract.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,14 @@ describe('server ephemeral port fallback policy contract', () => {
119119
let envRestorers: Array<() => void> = [];
120120
let originalArgv: string[] = [];
121121

122+
test('server installs stdio broken-pipe guards before emitting fallback logs', () => {
123+
const serverSource = fs.readFileSync(path.join(__dirname, 'server.ts'), 'utf8');
124+
125+
expect(serverSource).toContain('installBrokenPipeGuard(process.stdout');
126+
expect(serverSource).toContain('installBrokenPipeGuard(process.stderr');
127+
expect(serverSource).toContain("error?.code === 'EPIPE'");
128+
});
129+
122130
beforeEach(() => {
123131
tempProject = makeTempProject('noteconnection-port-policy');
124132
const projectRoot = path.join(tempProject.root, 'project');

0 commit comments

Comments
 (0)