102 lines
3.8 KiB
JavaScript
Executable File
102 lines
3.8 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
||
'use strict';
|
||
|
||
const crypto = require('crypto');
|
||
|
||
function parseArgs(argv) {
|
||
const args = { json: false, markdown: false };
|
||
for (let i = 2; i < argv.length; i++) {
|
||
const a = argv[i];
|
||
if (a === '--json') args.json = true;
|
||
else if (a === '--markdown') args.markdown = true;
|
||
else if (a === '--help' || a === '-h') args.help = true;
|
||
else throw new Error(`未知参数:${a}`);
|
||
}
|
||
if (!args.json && !args.markdown) args.markdown = true;
|
||
return args;
|
||
}
|
||
|
||
function usage() {
|
||
return `用法:
|
||
node scripts/precheck_runtime.js --markdown
|
||
node scripts/precheck_runtime.js --json
|
||
|
||
说明:输出 Node.js 侧纯计算预检结果;浏览器侧需运行同类片段后人工比对。`;
|
||
}
|
||
|
||
function collect() {
|
||
const emoji = '中文🙂𠮷';
|
||
const params = new URLSearchParams();
|
||
params.append('b', '2');
|
||
params.append('a', '1 2');
|
||
params.append('emoji', '🙂');
|
||
const encoder = new TextEncoder();
|
||
const bytes = Array.from(encoder.encode(emoji));
|
||
const random = new Uint8Array(8);
|
||
if (globalThis.crypto && typeof globalThis.crypto.getRandomValues === 'function') globalThis.crypto.getRandomValues(random);
|
||
else random.set(crypto.randomBytes(8));
|
||
return {
|
||
node: { version: process.version, platform: process.platform, arch: process.arch, v8: process.versions.v8 },
|
||
math: {
|
||
imul: Math.imul(0x7fffffff, 31),
|
||
fround: Math.fround(0.1),
|
||
pow: Math.pow(2, 53) - 1,
|
||
trig: Number(Math.sin(Math.PI / 6).toFixed(12)),
|
||
},
|
||
stringUnicode: {
|
||
sample: emoji,
|
||
length: emoji.length,
|
||
codePoints: Array.from(emoji).map(ch => ch.codePointAt(0).toString(16)),
|
||
normalized: emoji.normalize('NFC'),
|
||
},
|
||
arrayObject: {
|
||
sort: ['10', '2', '1'].sort(),
|
||
numericSort: [10, 2, 1].sort((a, b) => a - b),
|
||
jsonOrder: JSON.stringify({ b: 2, a: 1, 1: 'one', 0: 'zero' }),
|
||
keys: Object.keys({ b: 2, a: 1, 1: 'one', 0: 'zero' }),
|
||
},
|
||
dateTimezone: {
|
||
timezoneOffset: new Date('2020-01-02T03:04:05Z').getTimezoneOffset(),
|
||
iso: new Date(1577934245000).toISOString(),
|
||
locale: new Date(1577934245000).toString(),
|
||
},
|
||
encoding: {
|
||
textEncoderBytes: bytes,
|
||
urlSearchParams: params.toString(),
|
||
encodeURIComponent: encodeURIComponent(emoji),
|
||
btoaAscii: Buffer.from('abc123', 'binary').toString('base64'),
|
||
atobAscii: Buffer.from('YWJjMTIz', 'base64').toString('binary'),
|
||
},
|
||
random: {
|
||
mathRandomType: typeof Math.random(),
|
||
cryptoBytesSample: Array.from(random),
|
||
note: '随机值只用于确认 API 可用;正式 fixtures 应固定随机源。',
|
||
},
|
||
};
|
||
}
|
||
|
||
function renderMarkdown(result) {
|
||
const lines = ['# Node.js 纯计算预检结果', '', `- Node.js:${result.node.version}`, `- 平台:${result.node.platform}-${result.node.arch}`, `- V8:${result.node.v8}`];
|
||
for (const [section, value] of Object.entries(result)) {
|
||
if (section === 'node') continue;
|
||
lines.push('', `## ${section}`);
|
||
for (const [k, v] of Object.entries(value)) lines.push(`- ${k}:${Array.isArray(v) ? JSON.stringify(v) : String(v)}`);
|
||
}
|
||
lines.push('', '## 下一步');
|
||
lines.push('- 在浏览器控制台运行同类预检片段,保存为 `notes/runtime-precheck-browser.json`。');
|
||
lines.push('- 若 Node 与浏览器基础计算一致,再继续排查环境对象、初始化参数和样本差异。');
|
||
return lines.join('\n') + '\n';
|
||
}
|
||
|
||
try {
|
||
const args = parseArgs(process.argv);
|
||
if (args.help) { console.log(usage()); process.exit(0); }
|
||
const result = collect();
|
||
if (args.json) console.log(JSON.stringify(result, null, 2));
|
||
if (args.markdown) process.stdout.write(renderMarkdown(result));
|
||
} catch (err) {
|
||
console.error(err.message || String(err));
|
||
console.error(usage());
|
||
process.exit(1);
|
||
}
|