noteconnection 1.6.3 → 1.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/dist/src/backend/utils/CrashLogger.js +16 -0
- package/dist/src/crashlogger.contract.test.js +13 -0
- package/dist/src/frontend/README.md +28 -0
- package/dist/src/notemd.api.contract.test.js +4 -2
- package/dist/src/pkg.snapshot.safety.contract.test.js +4 -2
- package/dist/src/reader_renderer.js +3 -43
- package/dist/src/reader_renderer.test.js +7 -0
- package/dist/src/server.js +76 -28
- package/dist/src/server.port.fallback.contract.test.js +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -319,6 +319,13 @@ For developers building from source, NoteConnection offers two build modes:
|
|
|
319
319
|
- Run mapping validation: `npm run docs:diataxis:check`.
|
|
320
320
|
- Run local docs site preview: `npm run docs:site:serve`.
|
|
321
321
|
- Build static docs site: `npm run docs:site:build`.
|
|
322
|
+
- EdgeOne Docs Portal (MkDocs deploy): `https://noteconnection-docs.edgeone.run`
|
|
323
|
+
- If you see `401 Authorization Required`, open the latest signed URL printed by deployment as `EDGEONE_DEPLOY_URL`, or disable preset-domain protection in EdgeOne Pages settings.
|
|
324
|
+
- Public mirror (fallback): `https://jacobinwwey.github.io/NoteConnection/`.
|
|
325
|
+
- Recommended lookup entry points:
|
|
326
|
+
- Users: `/diataxis/zh/tutorials/first-run/` or `/diataxis/en/tutorials/first-run/`
|
|
327
|
+
- Developers: `/diataxis/en/reference/interfaces-and-runtime/` and `/diataxis/en/reference/release-and-governance/`
|
|
328
|
+
- EdgeOne publish command (MkDocs site): `edgeone pages deploy build/mkdocs-site -n noteconnection-docs -e production -a global`.
|
|
322
329
|
- CI policy gate for docs mapping and site build: `.github/workflows/docs-diataxis-site.yml`.
|
|
323
330
|
|
|
324
331
|
## 🛠️ Hardware & Driver Requirements (AMDGPU)
|
|
@@ -334,6 +341,13 @@ For optimal performance with "GPU Optimised Rendering", especially on AMD RDNA c
|
|
|
334
341
|
|
|
335
342
|
## 📅 Changelog
|
|
336
343
|
|
|
344
|
+
### v1.6.5 - Documentation Portal Update (2026-03-26)
|
|
345
|
+
|
|
346
|
+
- Published MkDocs documentation to EdgeOne Pages project `noteconnection-docs`.
|
|
347
|
+
- Added bilingual README guidance for docs lookup paths (user/tutorial and developer/reference entry points).
|
|
348
|
+
- Standardized docs publish command for maintainers:
|
|
349
|
+
- `npm run docs:site:build`
|
|
350
|
+
- `edgeone pages deploy build/mkdocs-site -n noteconnection-docs -e production -a global`
|
|
337
351
|
### v1.6.0 - Unified Runtime, NoteMD Integration & Release Hardening (2026-03-23)
|
|
338
352
|
|
|
339
353
|
- **Tag Compare Snapshot (`v1.3.0..v1.6.0`)**:
|
|
@@ -1196,6 +1210,13 @@ sync_language = true
|
|
|
1196
1210
|
- 映射一致性校验:`npm run docs:diataxis:check`。
|
|
1197
1211
|
- 本地预览文档站点:`npm run docs:site:serve`。
|
|
1198
1212
|
- 构建静态文档站点:`npm run docs:site:build`。
|
|
1213
|
+
- EdgeOne 文档入口(MkDocs 发布):`https://noteconnection-docs.edgeone.run`
|
|
1214
|
+
- 若出现 `401 Authorization Required`,请使用最近一次部署输出中的 `EDGEONE_DEPLOY_URL` 签名链接,或在 EdgeOne Pages 项目中关闭预设域名访问保护。
|
|
1215
|
+
- 公共镜像(兜底):`https://jacobinwwey.github.io/NoteConnection/`。
|
|
1216
|
+
- 推荐查询入口:
|
|
1217
|
+
- 用户文档:`/diataxis/zh/tutorials/first-run/` 或 `/diataxis/en/tutorials/first-run/`
|
|
1218
|
+
- 开发文档:`/diataxis/en/reference/interfaces-and-runtime/` 与 `/diataxis/en/reference/release-and-governance/`
|
|
1219
|
+
- EdgeOne 发布命令(MkDocs 产物):`edgeone pages deploy build/mkdocs-site -n noteconnection-docs -e production -a global`。
|
|
1199
1220
|
- CI 文档治理工作流:`.github/workflows/docs-diataxis-site.yml`。
|
|
1200
1221
|
|
|
1201
1222
|
---
|
|
@@ -1204,6 +1225,13 @@ sync_language = true
|
|
|
1204
1225
|
|
|
1205
1226
|
## 更新日志 (Changelog)
|
|
1206
1227
|
|
|
1228
|
+
### v1.6.5 - �ĵ��Ż����£�2026-03-26��
|
|
1229
|
+
|
|
1230
|
+
- �ѽ� MkDocs �ĵ������� EdgeOne Pages ��Ŀ `noteconnection-docs`��
|
|
1231
|
+
- ���� README ������Ӣ�IJ�ѯָ���������û��̳�����뿪���ο���ڣ���
|
|
1232
|
+
- ά���߷�������ͳһΪ��
|
|
1233
|
+
- `npm run docs:site:build`
|
|
1234
|
+
- `edgeone pages deploy build/mkdocs-site -n noteconnection-docs -e production -a global`
|
|
1207
1235
|
### v1.6.0 - 单窗口运行时、NoteMD 集成与发布加固 (2026-03-23)
|
|
1208
1236
|
|
|
1209
1237
|
- **Tag 对比快照(`v1.3.0..v1.6.0`)**:
|
|
@@ -38,6 +38,11 @@ const fs = __importStar(require("fs"));
|
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const os = __importStar(require("os"));
|
|
40
40
|
class CrashLogger {
|
|
41
|
+
static isIgnorableProcessWriteError(error) {
|
|
42
|
+
const code = typeof error === 'object' && error ? String(error.code || '') : '';
|
|
43
|
+
const syscall = typeof error === 'object' && error ? String(error.syscall || '') : '';
|
|
44
|
+
return code === 'EPIPE' || code === 'ERR_STREAM_DESTROYED' || (code === 'EOF' && syscall === 'write');
|
|
45
|
+
}
|
|
41
46
|
static log(error, context = 'General') {
|
|
42
47
|
const timestamp = new Date().toISOString();
|
|
43
48
|
const errorMessage = error instanceof Error ? error.stack || error.message : String(error);
|
|
@@ -55,12 +60,22 @@ class CrashLogger {
|
|
|
55
60
|
}
|
|
56
61
|
}
|
|
57
62
|
static initGlobalHandlers() {
|
|
63
|
+
if (this.globalHandlersInstalled) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
this.globalHandlersInstalled = true;
|
|
58
67
|
process.on('uncaughtException', (error) => {
|
|
68
|
+
if (CrashLogger.isIgnorableProcessWriteError(error)) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
59
71
|
console.error('Uncaught Exception:', error);
|
|
60
72
|
CrashLogger.log(error, 'UncaughtException');
|
|
61
73
|
process.exit(1); // Exit is mandatory for uncaught exceptions to avoid undefined state
|
|
62
74
|
});
|
|
63
75
|
process.on('unhandledRejection', (reason, promise) => {
|
|
76
|
+
if (CrashLogger.isIgnorableProcessWriteError(reason)) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
64
79
|
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
65
80
|
CrashLogger.log(reason, 'UnhandledRejection');
|
|
66
81
|
});
|
|
@@ -68,3 +83,4 @@ class CrashLogger {
|
|
|
68
83
|
}
|
|
69
84
|
exports.CrashLogger = CrashLogger;
|
|
70
85
|
CrashLogger.logFilePath = path.join(process.cwd(), 'crash.log');
|
|
86
|
+
CrashLogger.globalHandlersInstalled = false;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const CrashLogger_1 = require("./backend/utils/CrashLogger");
|
|
4
|
+
describe('CrashLogger broken pipe policy', () => {
|
|
5
|
+
test('treats write-side EPIPE and destroyed stream errors as ignorable process shutdown noise', () => {
|
|
6
|
+
expect(CrashLogger_1.CrashLogger.isIgnorableProcessWriteError(Object.assign(new Error('write EPIPE'), { code: 'EPIPE', syscall: 'write' }))).toBe(true);
|
|
7
|
+
expect(CrashLogger_1.CrashLogger.isIgnorableProcessWriteError(Object.assign(new Error('stream destroyed'), { code: 'ERR_STREAM_DESTROYED' }))).toBe(true);
|
|
8
|
+
});
|
|
9
|
+
test('does not suppress unrelated runtime exceptions', () => {
|
|
10
|
+
expect(CrashLogger_1.CrashLogger.isIgnorableProcessWriteError(Object.assign(new Error('permission denied'), { code: 'EACCES', syscall: 'open' }))).toBe(false);
|
|
11
|
+
expect(CrashLogger_1.CrashLogger.isIgnorableProcessWriteError(new Error('boom'))).toBe(false);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -319,6 +319,13 @@ For developers building from source, NoteConnection offers two build modes:
|
|
|
319
319
|
- Run mapping validation: `npm run docs:diataxis:check`.
|
|
320
320
|
- Run local docs site preview: `npm run docs:site:serve`.
|
|
321
321
|
- Build static docs site: `npm run docs:site:build`.
|
|
322
|
+
- EdgeOne Docs Portal (MkDocs deploy): `https://noteconnection-docs.edgeone.run`
|
|
323
|
+
- If you see `401 Authorization Required`, open the latest signed URL printed by deployment as `EDGEONE_DEPLOY_URL`, or disable preset-domain protection in EdgeOne Pages settings.
|
|
324
|
+
- Public mirror (fallback): `https://jacobinwwey.github.io/NoteConnection/`.
|
|
325
|
+
- Recommended lookup entry points:
|
|
326
|
+
- Users: `/diataxis/zh/tutorials/first-run/` or `/diataxis/en/tutorials/first-run/`
|
|
327
|
+
- Developers: `/diataxis/en/reference/interfaces-and-runtime/` and `/diataxis/en/reference/release-and-governance/`
|
|
328
|
+
- EdgeOne publish command (MkDocs site): `edgeone pages deploy build/mkdocs-site -n noteconnection-docs -e production -a global`.
|
|
322
329
|
- CI policy gate for docs mapping and site build: `.github/workflows/docs-diataxis-site.yml`.
|
|
323
330
|
|
|
324
331
|
## 🛠️ Hardware & Driver Requirements (AMDGPU)
|
|
@@ -334,6 +341,13 @@ For optimal performance with "GPU Optimised Rendering", especially on AMD RDNA c
|
|
|
334
341
|
|
|
335
342
|
## 📅 Changelog
|
|
336
343
|
|
|
344
|
+
### v1.6.5 - Documentation Portal Update (2026-03-26)
|
|
345
|
+
|
|
346
|
+
- Published MkDocs documentation to EdgeOne Pages project `noteconnection-docs`.
|
|
347
|
+
- Added bilingual README guidance for docs lookup paths (user/tutorial and developer/reference entry points).
|
|
348
|
+
- Standardized docs publish command for maintainers:
|
|
349
|
+
- `npm run docs:site:build`
|
|
350
|
+
- `edgeone pages deploy build/mkdocs-site -n noteconnection-docs -e production -a global`
|
|
337
351
|
### v1.6.0 - Unified Runtime, NoteMD Integration & Release Hardening (2026-03-23)
|
|
338
352
|
|
|
339
353
|
- **Tag Compare Snapshot (`v1.3.0..v1.6.0`)**:
|
|
@@ -1196,6 +1210,13 @@ sync_language = true
|
|
|
1196
1210
|
- 映射一致性校验:`npm run docs:diataxis:check`。
|
|
1197
1211
|
- 本地预览文档站点:`npm run docs:site:serve`。
|
|
1198
1212
|
- 构建静态文档站点:`npm run docs:site:build`。
|
|
1213
|
+
- EdgeOne 文档入口(MkDocs 发布):`https://noteconnection-docs.edgeone.run`
|
|
1214
|
+
- 若出现 `401 Authorization Required`,请使用最近一次部署输出中的 `EDGEONE_DEPLOY_URL` 签名链接,或在 EdgeOne Pages 项目中关闭预设域名访问保护。
|
|
1215
|
+
- 公共镜像(兜底):`https://jacobinwwey.github.io/NoteConnection/`。
|
|
1216
|
+
- 推荐查询入口:
|
|
1217
|
+
- 用户文档:`/diataxis/zh/tutorials/first-run/` 或 `/diataxis/en/tutorials/first-run/`
|
|
1218
|
+
- 开发文档:`/diataxis/en/reference/interfaces-and-runtime/` 与 `/diataxis/en/reference/release-and-governance/`
|
|
1219
|
+
- EdgeOne 发布命令(MkDocs 产物):`edgeone pages deploy build/mkdocs-site -n noteconnection-docs -e production -a global`。
|
|
1199
1220
|
- CI 文档治理工作流:`.github/workflows/docs-diataxis-site.yml`。
|
|
1200
1221
|
|
|
1201
1222
|
---
|
|
@@ -1204,6 +1225,13 @@ sync_language = true
|
|
|
1204
1225
|
|
|
1205
1226
|
## 更新日志 (Changelog)
|
|
1206
1227
|
|
|
1228
|
+
### v1.6.5 - �ĵ��Ż����£�2026-03-26��
|
|
1229
|
+
|
|
1230
|
+
- �ѽ� MkDocs �ĵ������� EdgeOne Pages ��Ŀ `noteconnection-docs`��
|
|
1231
|
+
- ���� README ������Ӣ�IJ�ѯָ���������û��̳�����뿪���ο���ڣ���
|
|
1232
|
+
- ά���߷�������ͳһΪ��
|
|
1233
|
+
- `npm run docs:site:build`
|
|
1234
|
+
- `edgeone pages deploy build/mkdocs-site -n noteconnection-docs -e production -a global`
|
|
1207
1235
|
### v1.6.0 - 单窗口运行时、NoteMD 集成与发布加固 (2026-03-23)
|
|
1208
1236
|
|
|
1209
1237
|
- **Tag 对比快照(`v1.3.0..v1.6.0`)**:
|
|
@@ -57,8 +57,10 @@ describe('NoteMD API contract wiring', () => {
|
|
|
57
57
|
expect(serverSource).toContain(endpoint);
|
|
58
58
|
});
|
|
59
59
|
});
|
|
60
|
-
test('server persists NoteMD settings
|
|
61
|
-
expect(serverSource).toContain('
|
|
60
|
+
test('server persists NoteMD settings through app_config.toml helpers', () => {
|
|
61
|
+
expect(serverSource).toContain("from './notemd/AppConfigToml'");
|
|
62
|
+
expect(serverSource).toContain('loadAppConfigToml');
|
|
63
|
+
expect(serverSource).toContain('saveAppConfigToml');
|
|
62
64
|
expect(serverSource).toContain('persistNotemdSettings');
|
|
63
65
|
expect(serverSource).toContain('loadNotemdSettings');
|
|
64
66
|
});
|
|
@@ -50,11 +50,13 @@ describe('pkg snapshot safety contract', () => {
|
|
|
50
50
|
expect(source).not.toContain('eval(');
|
|
51
51
|
});
|
|
52
52
|
});
|
|
53
|
-
test('reader and source manager keep
|
|
53
|
+
test('reader and source manager keep packaging-safe Mermaid/runtime fallback paths', () => {
|
|
54
54
|
const readerRenderer = fs.readFileSync(runtimeEntryFiles[0], 'utf8');
|
|
55
55
|
const sourceManager = fs.readFileSync(runtimeEntryFiles[2], 'utf8');
|
|
56
56
|
expect(readerRenderer).toContain('loadMermaidModule');
|
|
57
|
-
expect(readerRenderer).toContain(
|
|
57
|
+
expect(readerRenderer).toContain('MERMAID_BROWSER_BUNDLE_BASE64');
|
|
58
|
+
expect(readerRenderer).toContain("createElement('script')");
|
|
59
|
+
expect(readerRenderer).not.toContain("require('mermaid')");
|
|
58
60
|
expect(sourceManager).toContain('parseGraphDataPayload');
|
|
59
61
|
expect(sourceManager).toContain('Fallback for assignment-based payloads without using runtime eval.');
|
|
60
62
|
});
|
|
@@ -1,37 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
var _a;
|
|
36
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
4
|
exports.renderMathSvg = renderMathSvg;
|
|
@@ -51,7 +18,6 @@ const { liteAdaptor } = require('mathjax-full/js/adaptors/liteAdaptor.js');
|
|
|
51
18
|
const { HandlerList } = require('mathjax-full/js/core/HandlerList.js');
|
|
52
19
|
const { HTMLHandler } = require('mathjax-full/js/handlers/html/HTMLHandler.js');
|
|
53
20
|
const { initWasm, Resvg } = require('@resvg/resvg-wasm');
|
|
54
|
-
const importMermaidModule = () => Promise.resolve().then(() => __importStar(require('mermaid')));
|
|
55
21
|
const MATH_TEXT_COLOR = '#eef4ff';
|
|
56
22
|
const MERMAID_BACKGROUND = 'transparent';
|
|
57
23
|
const MERMAID_PADDING = 28;
|
|
@@ -111,7 +77,6 @@ const mathDocument = mathHandlers.document('', {
|
|
|
111
77
|
OutputJax: svgOutput,
|
|
112
78
|
});
|
|
113
79
|
let mermaidEnvironmentPromise = null;
|
|
114
|
-
let mermaidModulePromise = null;
|
|
115
80
|
let mermaidRenderQueue = Promise.resolve();
|
|
116
81
|
let mermaidRenderCounter = 0;
|
|
117
82
|
let resvgInitPromise = null;
|
|
@@ -390,14 +355,9 @@ async function createMermaidEnvironment(theme) {
|
|
|
390
355
|
};
|
|
391
356
|
}
|
|
392
357
|
async function loadMermaidModule(window) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
const mermaidModule = await importMermaidModule();
|
|
397
|
-
return mermaidModule.default ?? mermaidModule;
|
|
398
|
-
})();
|
|
399
|
-
}
|
|
400
|
-
return mermaidModulePromise;
|
|
358
|
+
const existingMermaid = window.mermaid;
|
|
359
|
+
if (existingMermaid) {
|
|
360
|
+
return existingMermaid;
|
|
401
361
|
}
|
|
402
362
|
const script = window.document.createElement('script');
|
|
403
363
|
script.textContent = Buffer.from(mermaid_runtime_1.MERMAID_BROWSER_BUNDLE_BASE64, 'base64').toString('utf8');
|
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
6
7
|
const node_child_process_1 = require("node:child_process");
|
|
7
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
8
9
|
const jsdom_1 = require("jsdom");
|
|
@@ -82,6 +83,12 @@ describe('reader_renderer', () => {
|
|
|
82
83
|
stdio: 'inherit',
|
|
83
84
|
});
|
|
84
85
|
}, 120000);
|
|
86
|
+
it('keeps the compiled Mermaid loader on the embedded browser runtime path instead of requiring the ESM package directly', () => {
|
|
87
|
+
const compiledRenderer = node_fs_1.default.readFileSync(distRendererPath, 'utf8');
|
|
88
|
+
expect(compiledRenderer).not.toContain("require('mermaid')");
|
|
89
|
+
expect(compiledRenderer).toContain('MERMAID_BROWSER_BUNDLE_BASE64');
|
|
90
|
+
expect(compiledRenderer).toContain("createElement('script')");
|
|
91
|
+
});
|
|
85
92
|
it('renders display math to svg', () => {
|
|
86
93
|
const svg = runRenderer('renderMathSvg', 'E = mc^2', { displayMode: true });
|
|
87
94
|
expect(svg.startsWith('<svg')).toBe(true);
|
package/dist/src/server.js
CHANGED
|
@@ -39,6 +39,7 @@ exports.executeNotemdCliCommand = executeNotemdCliCommand;
|
|
|
39
39
|
const http = __importStar(require("http"));
|
|
40
40
|
const fs = __importStar(require("fs"));
|
|
41
41
|
const path = __importStar(require("path"));
|
|
42
|
+
const net = __importStar(require("net"));
|
|
42
43
|
const readline = __importStar(require("readline"));
|
|
43
44
|
const events_1 = require("events");
|
|
44
45
|
const index_1 = require("./index");
|
|
@@ -52,6 +53,53 @@ const reader_renderer_1 = require("./reader_renderer");
|
|
|
52
53
|
const native_clipboard_1 = require("./native_clipboard");
|
|
53
54
|
const notemd_1 = require("./notemd");
|
|
54
55
|
const AppConfigToml_1 = require("./notemd/AppConfigToml");
|
|
56
|
+
function installBrokenPipeGuard(stream) {
|
|
57
|
+
if (!stream || stream.__noteConnectionBrokenPipeGuardInstalled) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
stream.__noteConnectionBrokenPipeGuardInstalled = true;
|
|
61
|
+
stream.on('error', (error) => {
|
|
62
|
+
if (error?.code === 'EPIPE' || error?.code === 'ERR_STREAM_DESTROYED') {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
throw error;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
installBrokenPipeGuard(process.stdout);
|
|
69
|
+
installBrokenPipeGuard(process.stderr);
|
|
70
|
+
const IS_JEST_RUNTIME = String(process.env.JEST_WORKER_ID || '').trim().length > 0;
|
|
71
|
+
function logDiagnostic(...args) {
|
|
72
|
+
if (IS_JEST_RUNTIME) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
console.log(...args);
|
|
76
|
+
}
|
|
77
|
+
function warnDiagnostic(...args) {
|
|
78
|
+
if (IS_JEST_RUNTIME) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
console.warn(...args);
|
|
82
|
+
}
|
|
83
|
+
function installJestSocketBrokenPipeGuard() {
|
|
84
|
+
if (!IS_JEST_RUNTIME) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const socketPrototype = net.Socket.prototype;
|
|
88
|
+
if (socketPrototype.__noteConnectionBrokenPipeGuardInstalled) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const originalEmit = socketPrototype.emit;
|
|
92
|
+
socketPrototype.__noteConnectionBrokenPipeGuardInstalled = true;
|
|
93
|
+
socketPrototype.emit = function (event, ...args) {
|
|
94
|
+
if (event === 'error'
|
|
95
|
+
&& CrashLogger_1.CrashLogger.isIgnorableProcessWriteError(args[0])
|
|
96
|
+
&& (this.destroyed || this.writable === false)) {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
return originalEmit.call(this, event, ...args);
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
installJestSocketBrokenPipeGuard();
|
|
55
103
|
// Initialize Global Crash Handlers
|
|
56
104
|
CrashLogger_1.CrashLogger.initGlobalHandlers();
|
|
57
105
|
const LOOPBACK_HOST = '127.0.0.1';
|
|
@@ -124,16 +172,16 @@ function resolveBoundedMegabytesFromEnv(options) {
|
|
|
124
172
|
}
|
|
125
173
|
const parsedValue = Number(rawValue);
|
|
126
174
|
if (!Number.isFinite(parsedValue)) {
|
|
127
|
-
|
|
175
|
+
warnDiagnostic(`[Config] ${envKey} is not a number ("${rawValue}"). Using default ${defaultMb} MiB.`);
|
|
128
176
|
return defaultMb;
|
|
129
177
|
}
|
|
130
178
|
const normalizedMb = Math.floor(parsedValue);
|
|
131
179
|
if (normalizedMb < minMb) {
|
|
132
|
-
|
|
180
|
+
warnDiagnostic(`[Config] ${envKey}=${rawValue} is below minimum ${minMb} MiB. Clamping to ${minMb} MiB.`);
|
|
133
181
|
return minMb;
|
|
134
182
|
}
|
|
135
183
|
if (normalizedMb > maxMb) {
|
|
136
|
-
|
|
184
|
+
warnDiagnostic(`[Config] ${envKey}=${rawValue} exceeds maximum ${maxMb} MiB. Clamping to ${maxMb} MiB.`);
|
|
137
185
|
return maxMb;
|
|
138
186
|
}
|
|
139
187
|
return normalizedMb;
|
|
@@ -385,7 +433,7 @@ async function safeUnlink(filePath) {
|
|
|
385
433
|
}
|
|
386
434
|
catch (error) {
|
|
387
435
|
if (!isFsNotFoundError(error)) {
|
|
388
|
-
|
|
436
|
+
warnDiagnostic('[Sidecar] Failed to clean up temporary request body file:', error);
|
|
389
437
|
}
|
|
390
438
|
}
|
|
391
439
|
}
|
|
@@ -710,7 +758,7 @@ async function loadNotemdSettings() {
|
|
|
710
758
|
cachedNotemdSettings = normalizeNotemdSettings(parsedSettings);
|
|
711
759
|
}
|
|
712
760
|
catch (error) {
|
|
713
|
-
|
|
761
|
+
warnDiagnostic('[NoteMD] Failed to read settings from TOML, using defaults:', error);
|
|
714
762
|
cachedNotemdSettings = normalizeNotemdSettings(notemd_1.DEFAULT_SETTINGS);
|
|
715
763
|
}
|
|
716
764
|
return cloneNotemdSettings(cachedNotemdSettings);
|
|
@@ -894,7 +942,7 @@ async function writeSidecarRuntimeManifest(finalPort) {
|
|
|
894
942
|
}, null, 2), 'utf8');
|
|
895
943
|
}
|
|
896
944
|
catch (error) {
|
|
897
|
-
|
|
945
|
+
warnDiagnostic('[Sidecar] Failed to write runtime manifest:', error);
|
|
898
946
|
}
|
|
899
947
|
}
|
|
900
948
|
async function renderMermaidWithPreference(source, options) {
|
|
@@ -927,7 +975,7 @@ async function renderMermaidWithPreference(source, options) {
|
|
|
927
975
|
if (options.rendererPreference === 'frontend') {
|
|
928
976
|
throw error;
|
|
929
977
|
}
|
|
930
|
-
|
|
978
|
+
warnDiagnostic('[Reader] Frontend Mermaid render unavailable, falling back to local resvg:', error);
|
|
931
979
|
}
|
|
932
980
|
}
|
|
933
981
|
const localRendered = await (0, reader_renderer_1.renderMermaidPng)(source, {
|
|
@@ -1162,7 +1210,7 @@ if (process.env.NOTE_CONNECTION_GPU === 'true' || process.env.NOTE_CONNECTION_GP
|
|
|
1162
1210
|
cliOptions.enableGPULayout = true;
|
|
1163
1211
|
}
|
|
1164
1212
|
if (process.env.npm_config_static === 'true' || process.env.npm_config_static === '') {
|
|
1165
|
-
|
|
1213
|
+
logDiagnostic('[CLI] Static mode requested (via env).');
|
|
1166
1214
|
}
|
|
1167
1215
|
if (process.env.npm_config_workers) {
|
|
1168
1216
|
cliOptions.maxWorkers = parseInt(process.env.npm_config_workers);
|
|
@@ -1183,7 +1231,7 @@ for (let i = 0; i < args.length; i++) {
|
|
|
1183
1231
|
cliOptions.enableGPULayout = false;
|
|
1184
1232
|
}
|
|
1185
1233
|
else if (arg === '--static') {
|
|
1186
|
-
|
|
1234
|
+
logDiagnostic('[CLI] Static mode requested (Frontend auto-detects large graphs).');
|
|
1187
1235
|
}
|
|
1188
1236
|
else if (arg === '--workers' && args[i + 1]) {
|
|
1189
1237
|
cliOptions.maxWorkers = parseInt(args[++i]);
|
|
@@ -1204,7 +1252,7 @@ for (let i = 0; i < args.length; i++) {
|
|
|
1204
1252
|
}
|
|
1205
1253
|
}
|
|
1206
1254
|
}
|
|
1207
|
-
|
|
1255
|
+
logDiagnostic('[CLI] Parsed Options:', cliOptions);
|
|
1208
1256
|
function getCliFlagValue(argsList, flagName) {
|
|
1209
1257
|
const index = argsList.indexOf(flagName);
|
|
1210
1258
|
if (index < 0 || index + 1 >= argsList.length) {
|
|
@@ -1327,7 +1375,7 @@ const startServer = async (options = {}) => {
|
|
|
1327
1375
|
hasCliBuild = true;
|
|
1328
1376
|
}
|
|
1329
1377
|
else {
|
|
1330
|
-
|
|
1378
|
+
warnDiagnostic("[CLI] Warning: targetPath detected as 'true'. This usually means npm consumed the flag incorrectly. Please check your command syntax.");
|
|
1331
1379
|
delete cliOptions.targetPath;
|
|
1332
1380
|
hasCliBuild = false;
|
|
1333
1381
|
}
|
|
@@ -1352,14 +1400,14 @@ const startServer = async (options = {}) => {
|
|
|
1352
1400
|
// If we are required to not block, we should probably default to "Load" if exists, or "Gen" if not.
|
|
1353
1401
|
const latest = await findLatestCliBuildForKb(kbName);
|
|
1354
1402
|
if (latest) {
|
|
1355
|
-
|
|
1403
|
+
logDiagnostic(`\n[CLI] Found existing build for '${kbName}': ${latest}`);
|
|
1356
1404
|
// If specific options passed (embedded mode), default to Load to avoid blocking
|
|
1357
1405
|
// Otherwise use interactive prompt
|
|
1358
1406
|
if (options.targetPath) {
|
|
1359
1407
|
useExisting = true;
|
|
1360
1408
|
const suffix = latest.replace('data_cli_', '').replace('.js', '');
|
|
1361
1409
|
cliOptions.outputPrefix = suffix;
|
|
1362
|
-
|
|
1410
|
+
logDiagnostic(`[CLI] Auto-Loading existing data: ${latest}`);
|
|
1363
1411
|
}
|
|
1364
1412
|
else {
|
|
1365
1413
|
const rl = readline.createInterface({
|
|
@@ -1378,7 +1426,7 @@ const startServer = async (options = {}) => {
|
|
|
1378
1426
|
// suffix = kbName_time
|
|
1379
1427
|
const suffix = latest.replace('data_cli_', '').replace('.js', '');
|
|
1380
1428
|
cliOptions.outputPrefix = suffix;
|
|
1381
|
-
|
|
1429
|
+
logDiagnostic(`[CLI] Loading existing data: ${latest}`);
|
|
1382
1430
|
}
|
|
1383
1431
|
}
|
|
1384
1432
|
}
|
|
@@ -1386,10 +1434,10 @@ const startServer = async (options = {}) => {
|
|
|
1386
1434
|
const now = new Date();
|
|
1387
1435
|
const timeStr = now.toISOString().replace(/[-:T]/g, '').slice(0, 15);
|
|
1388
1436
|
cliOptions.outputPrefix = `${kbName}_${timeStr}`;
|
|
1389
|
-
|
|
1437
|
+
logDiagnostic(`[CLI] Generating new knowledge graph for: ${cliOptions.targetPath}`);
|
|
1390
1438
|
try {
|
|
1391
1439
|
await (0, index_1.buildGraph)(cliOptions);
|
|
1392
|
-
|
|
1440
|
+
logDiagnostic('[CLI] Generation complete.');
|
|
1393
1441
|
}
|
|
1394
1442
|
catch (e) {
|
|
1395
1443
|
console.error('[CLI] Build failed:', e);
|
|
@@ -1706,7 +1754,7 @@ const startServer = async (options = {}) => {
|
|
|
1706
1754
|
const restoreKey = `restore:${target}`;
|
|
1707
1755
|
const now = Date.now();
|
|
1708
1756
|
if (lastRestoreKey === restoreKey && (now - lastRestoreTs) < 3000) {
|
|
1709
|
-
|
|
1757
|
+
logDiagnostic(`[Cache] Duplicate restore suppressed for ${target}`);
|
|
1710
1758
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1711
1759
|
res.end(JSON.stringify({ success: true, deduped: true }));
|
|
1712
1760
|
return;
|
|
@@ -2554,7 +2602,7 @@ const startServer = async (options = {}) => {
|
|
|
2554
2602
|
try {
|
|
2555
2603
|
const payload = await readJsonBody(req);
|
|
2556
2604
|
const { target, maxWorkers, enableGPU, enableGPULayout, memorySavingMode, deepDebug } = payload;
|
|
2557
|
-
|
|
2605
|
+
logDiagnostic('Received build request for:', target, 'maxWorkers:', maxWorkers, 'enableGPU:', enableGPU, 'enableGPULayout:', enableGPULayout, 'memorySavingMode:', memorySavingMode, 'deepDebug:', deepDebug);
|
|
2558
2606
|
const buildKey = JSON.stringify({
|
|
2559
2607
|
target,
|
|
2560
2608
|
maxWorkers,
|
|
@@ -2566,7 +2614,7 @@ const startServer = async (options = {}) => {
|
|
|
2566
2614
|
// De-duplicate accidental double-submit from frontend.
|
|
2567
2615
|
if (activeBuildPromise) {
|
|
2568
2616
|
if (activeBuildKey === buildKey) {
|
|
2569
|
-
|
|
2617
|
+
logDiagnostic('[Build] Duplicate request detected. Waiting for in-flight build.');
|
|
2570
2618
|
await activeBuildPromise;
|
|
2571
2619
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2572
2620
|
res.end(JSON.stringify({
|
|
@@ -2644,7 +2692,7 @@ const startServer = async (options = {}) => {
|
|
|
2644
2692
|
return;
|
|
2645
2693
|
}
|
|
2646
2694
|
KB_ROOT = resolvedKbPath;
|
|
2647
|
-
|
|
2695
|
+
logDiagnostic(`[API] Knowledge Base Root updated to: ${KB_ROOT}`);
|
|
2648
2696
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2649
2697
|
res.end(JSON.stringify({ success: true, kbPath: KB_ROOT }));
|
|
2650
2698
|
}
|
|
@@ -2675,11 +2723,11 @@ const startServer = async (options = {}) => {
|
|
|
2675
2723
|
runtimePort = resolvedPort;
|
|
2676
2724
|
await ensureRuntimeDataDir();
|
|
2677
2725
|
await writeSidecarRuntimeManifest(resolvedPort);
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2726
|
+
logDiagnostic(`[Sidecar] Runtime Manifest: ${SIDECAR_RUNTIME_MANIFEST}`);
|
|
2727
|
+
logDiagnostic(`Server running at http://${LOOPBACK_HOST}:${resolvedPort}/`);
|
|
2728
|
+
logDiagnostic(`Knowledge Base Root: ${KB_ROOT}`);
|
|
2729
|
+
logDiagnostic(`Frontend Root: ${FRONTEND_DIR}`);
|
|
2730
|
+
logDiagnostic(`Runtime Data Root: ${RUNTIME_DATA_DIR}`);
|
|
2683
2731
|
// Initialize PathBridge
|
|
2684
2732
|
try {
|
|
2685
2733
|
pathBridge = new PathBridge_1.PathBridge({
|
|
@@ -2687,20 +2735,20 @@ const startServer = async (options = {}) => {
|
|
|
2687
2735
|
host: LOOPBACK_HOST,
|
|
2688
2736
|
authToken: AUTH_TOKEN,
|
|
2689
2737
|
});
|
|
2690
|
-
|
|
2738
|
+
logDiagnostic(`[Sidecar] PathBridge initialized on ws://${LOOPBACK_HOST}:${PATH_BRIDGE_PORT}`);
|
|
2691
2739
|
}
|
|
2692
2740
|
catch (e) {
|
|
2693
2741
|
console.error(`[Sidecar] Failed to initialize PathBridge:`, e);
|
|
2694
2742
|
}
|
|
2695
2743
|
if (hasCliBuild) {
|
|
2696
|
-
|
|
2744
|
+
logDiagnostic('[CLI] Ready.');
|
|
2697
2745
|
}
|
|
2698
2746
|
};
|
|
2699
2747
|
const attachListenHandlers = (targetPort) => {
|
|
2700
2748
|
const onError = (error) => {
|
|
2701
2749
|
server.off('listening', onListening);
|
|
2702
2750
|
if (error?.code === 'EADDRINUSE' && allowEphemeralFallback && targetPort === finalPort) {
|
|
2703
|
-
|
|
2751
|
+
warnDiagnostic(`[Sidecar] Port ${finalPort} is already in use. Retrying with an ephemeral loopback port.`);
|
|
2704
2752
|
attachListenHandlers(0);
|
|
2705
2753
|
return;
|
|
2706
2754
|
}
|
|
@@ -144,6 +144,12 @@ describe('server ephemeral port fallback policy contract', () => {
|
|
|
144
144
|
let tempProject = null;
|
|
145
145
|
let envRestorers = [];
|
|
146
146
|
let originalArgv = [];
|
|
147
|
+
test('server installs stdio broken-pipe guards before emitting fallback logs', () => {
|
|
148
|
+
const serverSource = fs.readFileSync(path.join(__dirname, 'server.ts'), 'utf8');
|
|
149
|
+
expect(serverSource).toContain('installBrokenPipeGuard(process.stdout');
|
|
150
|
+
expect(serverSource).toContain('installBrokenPipeGuard(process.stderr');
|
|
151
|
+
expect(serverSource).toContain("error?.code === 'EPIPE'");
|
|
152
|
+
});
|
|
147
153
|
beforeEach(() => {
|
|
148
154
|
tempProject = makeTempProject('noteconnection-port-policy');
|
|
149
155
|
const projectRoot = path.join(tempProject.root, 'project');
|