jsql-neo 6.5.26 → 6.5.28
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 +26 -1
- package/bin/jsql +32 -0
- package/index.d.ts +8 -1
- package/index.js +5 -1
- package/lib/database.js +5 -1
- package/lib/mongo_server.js +21 -5
- package/lib/mysql_compat.js +1 -1
- package/lib/sql.js +52 -1
- package/lib/table.js +139 -38
- package/package.json +3 -3
- package/postinstall.js +136 -6
- package/test/coverage.js +3 -1
- package/test/finalize-bigint.test.js +55 -0
- package/test/issue-44.test.js +92 -0
- package/test/issue-45.test.js +62 -0
- package/test/issue-46.test.js +42 -0
- package/test/issue-47.test.js +65 -0
- package/test/issue-48.test.js +39 -0
- package/test/native-cli.test.js +122 -0
- package/test/native-install.test.js +93 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/* ---- CLI: jsql native status|refresh ---- */
|
|
2
|
+
/*
|
|
3
|
+
* 把 postinstall 的 native 版本自检暴露到命令行后,必须验证:
|
|
4
|
+
* - 匹配 / 错配 / 未记录 三态的退出码(0/1,可直接当 CI 门禁)
|
|
5
|
+
* - 未知 action 要报错而不是静默成功
|
|
6
|
+
*
|
|
7
|
+
* 全程用临时目录 + JSQL_NATIVE_PATH,并且显式关掉 star 提示、
|
|
8
|
+
* 把 ~/.jsqlmr 覆盖到临时路径 —— 绝不污染用户级配置。
|
|
9
|
+
*/
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const os = require('os');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { spawnSync } = require('child_process');
|
|
14
|
+
|
|
15
|
+
const ROOT = path.join(__dirname, '..');
|
|
16
|
+
const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'jsql-native-cli-'));
|
|
17
|
+
const NATIVE = path.join(TMP, 'native', 'jsql-neo-native.node');
|
|
18
|
+
const META = path.join(TMP, 'native', '.installed.json');
|
|
19
|
+
fs.mkdirSync(path.dirname(NATIVE), { recursive: true });
|
|
20
|
+
fs.writeFileSync(NATIVE, 'FAKE-NATIVE-FOR-CLI-TEST');
|
|
21
|
+
|
|
22
|
+
const version = require(path.join(ROOT, 'package.json')).version;
|
|
23
|
+
|
|
24
|
+
let pass = 0, fail = 0;
|
|
25
|
+
function ok(name, cond, got) {
|
|
26
|
+
if (cond) { pass++; console.log('[OK] ' + name); }
|
|
27
|
+
else { fail++; console.log('[FAIL] ' + name + ' -> ' + JSON.stringify(got)); }
|
|
28
|
+
}
|
|
29
|
+
function run(action, extra) {
|
|
30
|
+
const env = Object.assign({}, process.env, {
|
|
31
|
+
JSQL_NO_STAR_NAG: '1',
|
|
32
|
+
JSQL_MR_PATH: path.join(TMP, 'jsqlmr'),
|
|
33
|
+
JSQL_NATIVE_PATH: NATIVE,
|
|
34
|
+
});
|
|
35
|
+
const args = [path.join(ROOT, 'bin', 'jsql'), 'native'];
|
|
36
|
+
if (action) args.push(action);
|
|
37
|
+
if (extra) args.push(...extra);
|
|
38
|
+
return spawnSync(process.execPath, args, { encoding: 'utf8', env, timeout: 30000 });
|
|
39
|
+
}
|
|
40
|
+
function writeMeta(tag) {
|
|
41
|
+
fs.writeFileSync(META, JSON.stringify({ tag, sha256: 'deadbeef', installedAt: new Date().toISOString() }, null, 2));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
writeMeta(`v${version}`);
|
|
46
|
+
let r = run('status');
|
|
47
|
+
ok('匹配时 exit=0 且报 OK',
|
|
48
|
+
r.status === 0 && /STATUS\s*:\s*OK/.test(r.stdout || ''),
|
|
49
|
+
{ code: r.status, out: r.stdout, err: r.stderr });
|
|
50
|
+
|
|
51
|
+
writeMeta('v0.0.1');
|
|
52
|
+
r = run('status');
|
|
53
|
+
ok('版本错配时 exit=1 且报 MISMATCH',
|
|
54
|
+
r.status === 1 && /MISMATCH/.test(r.stdout || ''),
|
|
55
|
+
{ code: r.status, out: r.stdout, err: r.stderr });
|
|
56
|
+
|
|
57
|
+
fs.unlinkSync(META);
|
|
58
|
+
r = run('status');
|
|
59
|
+
ok('无元数据时 exit=1 且报 UNVERIFIED',
|
|
60
|
+
r.status === 1 && /UNVERIFIED/.test(r.stdout || ''),
|
|
61
|
+
{ code: r.status, out: r.stdout, err: r.stderr });
|
|
62
|
+
|
|
63
|
+
r = run(null);
|
|
64
|
+
ok('不带 action 时默认走 status',
|
|
65
|
+
r.status === 1 && /STATUS/.test(r.stdout || ''),
|
|
66
|
+
{ code: r.status, out: r.stdout, err: r.stderr });
|
|
67
|
+
|
|
68
|
+
r = run('bogus');
|
|
69
|
+
ok('未知 action 报 Usage 且 exit=1',
|
|
70
|
+
r.status === 1 && /Unknown native action/.test((r.stdout || '') + (r.stderr || '')),
|
|
71
|
+
{ code: r.status, out: r.stdout, err: r.stderr });
|
|
72
|
+
|
|
73
|
+
// --- verify:内容级校验(status 只看 tag,发现不了文件被替换/损坏)---
|
|
74
|
+
const crypto = require('crypto');
|
|
75
|
+
const sha = (s) => crypto.createHash('sha256').update(s).digest('hex');
|
|
76
|
+
fs.writeFileSync(NATIVE, 'AAA');
|
|
77
|
+
fs.writeFileSync(META, JSON.stringify({ tag: `v${version}`, sha256: sha('AAA') }));
|
|
78
|
+
r = run('verify');
|
|
79
|
+
ok('verify 校验和一致 → OK 且 exit=0',
|
|
80
|
+
r.status === 0 && /VERIFY\s*:\s*OK/.test(r.stdout || ''),
|
|
81
|
+
{ code: r.status, out: r.stdout, err: r.stderr });
|
|
82
|
+
|
|
83
|
+
fs.writeFileSync(NATIVE, 'BBB'); // 元数据不动,只换文件
|
|
84
|
+
r = run('verify');
|
|
85
|
+
ok('verify 文件被替换 → CORRUPT 且 exit=1',
|
|
86
|
+
r.status === 1 && /CORRUPT/.test(r.stdout || ''),
|
|
87
|
+
{ code: r.status, out: r.stdout, err: r.stderr });
|
|
88
|
+
|
|
89
|
+
r = run('verify', ['--json']);
|
|
90
|
+
let j = null;
|
|
91
|
+
try { j = JSON.parse(r.stdout); } catch (_) {}
|
|
92
|
+
ok('verify --json 输出结构化结果',
|
|
93
|
+
!!j && j.state === 'CORRUPT' && j.actualSha256 === sha('BBB') && j.present === true,
|
|
94
|
+
{ code: r.status, out: r.stdout });
|
|
95
|
+
|
|
96
|
+
fs.writeFileSync(NATIVE, 'AAA');
|
|
97
|
+
fs.writeFileSync(META, JSON.stringify({ tag: 'v0.0.1', sha256: sha('AAA') }));
|
|
98
|
+
r = run('verify');
|
|
99
|
+
ok('verify 内容对但 tag 错配 → MISMATCH 且 exit=1',
|
|
100
|
+
r.status === 1 && /MISMATCH/.test(r.stdout || ''),
|
|
101
|
+
{ code: r.status, out: r.stdout, err: r.stderr });
|
|
102
|
+
|
|
103
|
+
fs.unlinkSync(META);
|
|
104
|
+
r = run('verify');
|
|
105
|
+
ok('verify 无安装元数据 → UNVERIFIED 且 exit=1',
|
|
106
|
+
r.status === 1 && /UNVERIFIED/.test(r.stdout || ''),
|
|
107
|
+
{ code: r.status, out: r.stdout, err: r.stderr });
|
|
108
|
+
|
|
109
|
+
fs.unlinkSync(NATIVE);
|
|
110
|
+
r = run('verify');
|
|
111
|
+
ok('verify native 文件缺失 → MISSING 且 exit=1',
|
|
112
|
+
r.status === 1 && /MISSING/.test(r.stdout || ''),
|
|
113
|
+
{ code: r.status, out: r.stdout, err: r.stderr });
|
|
114
|
+
|
|
115
|
+
ok('没有污染用户级配置',
|
|
116
|
+
!fs.existsSync(path.join(TMP, 'jsqlmr')), fs.readdirSync(TMP));
|
|
117
|
+
} finally {
|
|
118
|
+
try { fs.rmSync(TMP, { recursive: true, force: true }); } catch (_) {}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
console.log(`\n${pass} passed, ${fail} failed`);
|
|
122
|
+
process.exit(fail === 0 ? 0 : 1);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/* ---- postinstall:native 覆盖安装 + 版本状态自检的回归 ---- */
|
|
2
|
+
/*
|
|
3
|
+
* 背景(两个真实故障):
|
|
4
|
+
*
|
|
5
|
+
* 1. Windows 上 installDownloadedModule 的 EPERM fallback 会 unlinkSync(旧.node)
|
|
6
|
+
* 并把失败 catch 掉,第二次 rename 必然再失败 —— 下载成功、校验通过,
|
|
7
|
+
* 却静默留着旧二进制。issue-38 的 bigint 用例因此一直挂。
|
|
8
|
+
* 2. 失败时只打一句 "keeping the existing local native module",用户无从察觉
|
|
9
|
+
* 版本可能错配。新增 --status 就是要让这件事可查。
|
|
10
|
+
*
|
|
11
|
+
* 全部用临时目录 + JSQL_NATIVE_PATH,绝不碰仓库里真实的 .node。
|
|
12
|
+
*/
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const os = require('os');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'jsql-native-test-'));
|
|
18
|
+
const NATIVE = path.join(TMP, 'native', 'jsql-neo-native.node');
|
|
19
|
+
const META = path.join(TMP, 'native', '.installed.json');
|
|
20
|
+
fs.mkdirSync(path.dirname(NATIVE), { recursive: true });
|
|
21
|
+
|
|
22
|
+
// 必须在 require 之前设置:NATIVE_PATH 是模块加载时算出来的常量
|
|
23
|
+
process.env.JSQL_NATIVE_PATH = NATIVE;
|
|
24
|
+
const PI = require(path.join(__dirname, '..', 'postinstall.js'));
|
|
25
|
+
|
|
26
|
+
let pass = 0, fail = 0;
|
|
27
|
+
function ok(name, cond, got) {
|
|
28
|
+
if (cond) { pass++; console.log('[OK] ' + name); }
|
|
29
|
+
else { fail++; console.log('[FAIL] ' + name + ' -> ' + JSON.stringify(got)); }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 调用 nativeStatus 时把输出收起来,别污染测试日志
|
|
33
|
+
function status() {
|
|
34
|
+
const orig = console.log;
|
|
35
|
+
const lines = [];
|
|
36
|
+
console.log = (...a) => lines.push(a.join(' '));
|
|
37
|
+
let code;
|
|
38
|
+
try { code = PI.nativeStatus(); }
|
|
39
|
+
finally { console.log = orig; }
|
|
40
|
+
return { code, text: lines.join('\n') };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
// --- 1) 目标 .node 已存在时必须能覆盖(EPERM 修复的核心) ---
|
|
45
|
+
fs.writeFileSync(NATIVE, 'OLD-NATIVE');
|
|
46
|
+
const tmp = path.join(TMP, 'incoming.download');
|
|
47
|
+
fs.writeFileSync(tmp, 'NEW-NATIVE');
|
|
48
|
+
let installed;
|
|
49
|
+
try { installed = PI.installDownloadedModule(tmp); }
|
|
50
|
+
catch (e) { installed = { error: e.message }; }
|
|
51
|
+
ok('目标已存在时覆盖成功',
|
|
52
|
+
fs.readFileSync(NATIVE, 'utf8') === 'NEW-NATIVE', installed);
|
|
53
|
+
ok('覆盖前为旧模块创建了回滚备份',
|
|
54
|
+
!!(installed && installed.backup) && fs.existsSync(installed.backup), installed);
|
|
55
|
+
|
|
56
|
+
// --- 2) 没有 .installed.json 时必须报 UNVERIFIED,不能假装 OK ---
|
|
57
|
+
if (fs.existsSync(META)) fs.unlinkSync(META);
|
|
58
|
+
let r = status();
|
|
59
|
+
ok('无安装元数据 → UNVERIFIED 且 exit=1',
|
|
60
|
+
r.code === 1 && /UNVERIFIED/.test(r.text), r);
|
|
61
|
+
|
|
62
|
+
// --- 3) 元数据与包版本一致 → OK ---
|
|
63
|
+
fs.writeFileSync(META, JSON.stringify({
|
|
64
|
+
tag: `v${require('../package.json').version}`,
|
|
65
|
+
sha256: 'deadbeef',
|
|
66
|
+
installedAt: new Date().toISOString(),
|
|
67
|
+
}, null, 2));
|
|
68
|
+
r = status();
|
|
69
|
+
ok('元数据匹配包版本 → OK 且 exit=0',
|
|
70
|
+
r.code === 0 && /STATUS\s*:\s*OK/.test(r.text), r);
|
|
71
|
+
|
|
72
|
+
// --- 4) 版本错配 → MISMATCH,且指路刷新命令 ---
|
|
73
|
+
const meta = JSON.parse(fs.readFileSync(META, 'utf8'));
|
|
74
|
+
meta.tag = 'v0.0.1';
|
|
75
|
+
fs.writeFileSync(META, JSON.stringify(meta));
|
|
76
|
+
r = status();
|
|
77
|
+
ok('元数据版本错配 → MISMATCH 且 exit=1',
|
|
78
|
+
r.code === 1 && /MISMATCH/.test(r.text), r);
|
|
79
|
+
ok('MISMATCH 时提示刷新命令',
|
|
80
|
+
/node postinstall\.js/.test(r.text), r.text);
|
|
81
|
+
|
|
82
|
+
// --- 5) native 文件缺失时不能报 OK ---
|
|
83
|
+
fs.unlinkSync(NATIVE);
|
|
84
|
+
r = status();
|
|
85
|
+
ok('native 文件缺失 → 不报 OK',
|
|
86
|
+
r.code === 1 && /MISSING/.test(r.text), r);
|
|
87
|
+
} finally {
|
|
88
|
+
try { fs.rmSync(TMP, { recursive: true, force: true }); } catch (_) {}
|
|
89
|
+
delete process.env.JSQL_NATIVE_PATH;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log(`\n${pass} passed, ${fail} failed`);
|
|
93
|
+
process.exit(fail === 0 ? 0 : 1);
|