netisok 1.0.0

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 ADDED
@@ -0,0 +1,62 @@
1
+ # netisok
2
+
3
+ 网络测速命令行工具,参考 ZTools 插件 [speed-test-ztools](https://github.com/) 实现。通过 HTTP GET 并发请求各站点,测量响应时间。
4
+
5
+ ## 功能
6
+
7
+ - 默认测速:百度、Google、GitHub
8
+ - 并发请求,超时 4 秒(与 ZTools 插件一致)
9
+ - 支持自定义 URL、超时、顺序/并发模式
10
+ - 支持 JSON 输出,便于脚本集成
11
+
12
+ ## 安装
13
+
14
+ ```bash
15
+ pnpm install
16
+ pnpm build
17
+ ```
18
+
19
+ 全局链接(可选):
20
+
21
+ ```bash
22
+ pnpm link --global
23
+ ```
24
+
25
+ ## 使用
26
+
27
+ ```bash
28
+ # 默认测速
29
+ pnpm dev
30
+ # 或
31
+ netisok
32
+
33
+ # 指定 URL
34
+ netisok -u https://www.baidu.com/ -u https://github.com/
35
+
36
+ # 调整超时(秒)
37
+ netisok -t 10
38
+
39
+ # 顺序测速
40
+ netisok -s
41
+
42
+ # JSON 输出
43
+ netisok --json
44
+ ```
45
+
46
+ ## 项目结构
47
+
48
+ ```
49
+ netisok/
50
+ ├── src/
51
+ │ ├── cli.ts # Commander 入口
52
+ │ ├── speed-test.ts # 测速核心逻辑
53
+ │ ├── output.ts # 终端 / JSON 输出
54
+ │ └── types.ts
55
+ ├── package.json
56
+ └── tsconfig.json
57
+ ```
58
+
59
+ ## 退出码
60
+
61
+ - `0`:全部站点测速成功
62
+ - `1`:存在失败站点或运行错误
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { DEFAULT_SITES, DEFAULT_TIMEOUT_MS, summarizeResults, testWebsites, } from './speed-test.js';
4
+ import { printJson, printResults } from './output.js';
5
+ const program = new Command();
6
+ program
7
+ .name('netisok')
8
+ .description('网络测速命令行工具 - 测试各站点 HTTP 响应时间')
9
+ .version('1.0.0')
10
+ .option('-u, --url <urls...>', '指定测速 URL(可多次使用)')
11
+ .option('-t, --timeout <seconds>', '请求超时时间(秒)', String(DEFAULT_TIMEOUT_MS / 1000))
12
+ .option('-s, --sequential', '顺序测速(默认并发)')
13
+ .option('--json', '以 JSON 格式输出结果')
14
+ .action(async (options) => {
15
+ const timeoutSec = Number.parseFloat(options.timeout);
16
+ if (!Number.isFinite(timeoutSec) || timeoutSec <= 0) {
17
+ console.error('错误: --timeout 必须是大于 0 的数字');
18
+ process.exitCode = 1;
19
+ return;
20
+ }
21
+ const urls = options.url?.length ? options.url : DEFAULT_SITES;
22
+ const timeout = Math.round(timeoutSec * 1000);
23
+ try {
24
+ const results = await testWebsites({
25
+ urls,
26
+ timeout,
27
+ concurrent: !options.sequential,
28
+ });
29
+ const summary = summarizeResults(results);
30
+ if (options.json) {
31
+ printJson(results, summary);
32
+ }
33
+ else {
34
+ printResults(results);
35
+ }
36
+ if (summary.failCount > 0) {
37
+ process.exitCode = 1;
38
+ }
39
+ }
40
+ catch (err) {
41
+ console.error('测速失败:', err instanceof Error ? err.message : err);
42
+ process.exitCode = 1;
43
+ }
44
+ });
45
+ program.parse();
46
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,gBAAgB,EAChB,YAAY,GACb,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEtD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,SAAS,CAAC;KACf,WAAW,CAAC,6BAA6B,CAAC;KAC1C,OAAO,CAAC,OAAO,CAAC;KAChB,MAAM,CAAC,qBAAqB,EAAE,iBAAiB,CAAC;KAChD,MAAM,CAAC,yBAAyB,EAAE,WAAW,EAAE,MAAM,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC;KACjF,MAAM,CAAC,kBAAkB,EAAE,YAAY,CAAC;KACxC,MAAM,CAAC,QAAQ,EAAE,eAAe,CAAC;KACjC,MAAM,CAAC,KAAK,EAAE,OAKd,EAAE,EAAE;IACH,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,UAAU,IAAI,CAAC,EAAE,CAAC;QACpD,OAAO,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;QAC3C,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,OAAO;IACT,CAAC;IAED,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,aAAa,CAAC;IAC/D,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAE9C,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC;YACjC,IAAI;YACJ,OAAO;YACP,UAAU,EAAE,CAAC,OAAO,CAAC,UAAU;SAChC,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;QAE1C,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YACjB,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC9B,CAAC;aAAM,CAAC;YACN,YAAY,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC;QAED,IAAI,OAAO,CAAC,SAAS,GAAG,CAAC,EAAE,CAAC;YAC1B,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,EAAE,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { TestResult, TestSummary } from './types.js';
2
+ export declare function printResults(results: TestResult[]): void;
3
+ export declare function printJson(results: TestResult[], summary: TestSummary): void;
package/dist/output.js ADDED
@@ -0,0 +1,17 @@
1
+ export function printResults(results) {
2
+ results.forEach((result, index) => {
3
+ console.log(`${index + 1}. ${result.url}`);
4
+ if (result.success) {
5
+ console.log(` ✅ 成功 - 响应时间: ${result.responseTime}ms`);
6
+ }
7
+ else {
8
+ console.log(` ❌ 失败 - ${result.error}`);
9
+ console.log(` 已用时间: ${result.responseTime}ms`);
10
+ }
11
+ console.log('');
12
+ });
13
+ }
14
+ export function printJson(results, summary) {
15
+ console.log(JSON.stringify({ results, summary }, null, 2));
16
+ }
17
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,YAAY,CAAC,OAAqB;IAChD,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE;QAChC,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,GAAG,CAAC,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;QAC3C,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,OAAO,CAAC,GAAG,CAAC,mBAAmB,MAAM,CAAC,YAAY,IAAI,CAAC,CAAC;QAC1D,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,aAAa,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;YACzC,OAAO,CAAC,GAAG,CAAC,YAAY,MAAM,CAAC,YAAY,IAAI,CAAC,CAAC;QACnD,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAqB,EAAE,OAAoB;IACnE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAC7D,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { SpeedTestOptions, TestResult, TestSummary } from './types.js';
2
+ export declare const DEFAULT_SITES: string[];
3
+ export declare const DEFAULT_TIMEOUT_MS = 4000;
4
+ export declare function testWebsite(url: string, timeout?: number): Promise<TestResult>;
5
+ export declare function testWebsites(options: SpeedTestOptions): Promise<TestResult[]>;
6
+ export declare function summarizeResults(results: TestResult[]): TestSummary;
@@ -0,0 +1,87 @@
1
+ import http from 'node:http';
2
+ import https from 'node:https';
3
+ export const DEFAULT_SITES = [
4
+ 'https://www.baidu.com/',
5
+ 'https://www.google.com/',
6
+ 'https://github.com/',
7
+ ];
8
+ export const DEFAULT_TIMEOUT_MS = 4_000;
9
+ function requestOnce(targetUrl, timeout) {
10
+ return new Promise((resolve) => {
11
+ const start = Date.now();
12
+ const parsed = new URL(targetUrl);
13
+ const mod = parsed.protocol === 'https:' ? https : http;
14
+ const req = mod.get(targetUrl, { timeout }, (res) => {
15
+ const responseTime = Date.now() - start;
16
+ resolve({
17
+ url: targetUrl,
18
+ success: (res.statusCode ?? 500) < 500,
19
+ responseTime,
20
+ statusCode: res.statusCode,
21
+ });
22
+ res.destroy();
23
+ });
24
+ req.on('error', (err) => {
25
+ const responseTime = Date.now() - start;
26
+ resolve({
27
+ url: targetUrl,
28
+ success: false,
29
+ responseTime,
30
+ error: formatRequestError(err),
31
+ });
32
+ });
33
+ req.on('timeout', () => {
34
+ req.destroy();
35
+ resolve({
36
+ url: targetUrl,
37
+ success: false,
38
+ responseTime: Date.now() - start,
39
+ error: '请求超时',
40
+ });
41
+ });
42
+ });
43
+ }
44
+ function formatRequestError(err) {
45
+ if (err.code === 'ECONNABORTED')
46
+ return '请求超时';
47
+ if (err.code === 'ENOTFOUND' || err.code === 'ECONNREFUSED')
48
+ return '无法连接';
49
+ return err.message || '请求失败';
50
+ }
51
+ export async function testWebsite(url, timeout = DEFAULT_TIMEOUT_MS) {
52
+ return requestOnce(url, timeout);
53
+ }
54
+ export async function testWebsites(options) {
55
+ const { urls, timeout, concurrent } = options;
56
+ if (concurrent) {
57
+ return Promise.all(urls.map((url) => testWebsite(url, timeout)));
58
+ }
59
+ const results = [];
60
+ for (const url of urls) {
61
+ results.push(await testWebsite(url, timeout));
62
+ }
63
+ return results;
64
+ }
65
+ export function summarizeResults(results) {
66
+ const successResults = results.filter((r) => r.success);
67
+ const successCount = successResults.length;
68
+ const failCount = results.length - successCount;
69
+ const avgResponseTime = successCount > 0
70
+ ? successResults.reduce((sum, r) => sum + r.responseTime, 0) / successCount
71
+ : 0;
72
+ const fastest = successCount > 0
73
+ ? successResults.reduce((min, r) => (r.responseTime < min.responseTime ? r : min))
74
+ : undefined;
75
+ const slowest = successCount > 0
76
+ ? successResults.reduce((max, r) => (r.responseTime > max.responseTime ? r : max))
77
+ : undefined;
78
+ return {
79
+ total: results.length,
80
+ successCount,
81
+ failCount,
82
+ avgResponseTime,
83
+ fastest,
84
+ slowest,
85
+ };
86
+ }
87
+ //# sourceMappingURL=speed-test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"speed-test.js","sourceRoot":"","sources":["../src/speed-test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ export interface TestResult {
2
+ url: string;
3
+ success: boolean;
4
+ responseTime: number;
5
+ statusCode?: number;
6
+ error?: string;
7
+ }
8
+ export interface TestSummary {
9
+ total: number;
10
+ successCount: number;
11
+ failCount: number;
12
+ avgResponseTime: number;
13
+ fastest?: TestResult;
14
+ slowest?: TestResult;
15
+ }
16
+ export interface SpeedTestOptions {
17
+ urls: string[];
18
+ timeout: number;
19
+ concurrent: boolean;
20
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "netisok",
3
+ "version": "1.0.0",
4
+ "description": "网络测速命令行工具",
5
+ "type": "module",
6
+ "bin": {
7
+ "netisok": "dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "README.md"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc && chmod +x dist/cli.js",
15
+ "dev": "tsx src/cli.ts",
16
+ "start": "node dist/cli.js",
17
+ "typecheck": "tsc --noEmit",
18
+ "prepublishOnly": "pnpm run build",
19
+ "prepack": "pnpm run build",
20
+ "publish": "pnpm run build && npm publish --ignore-scripts",
21
+ "publish:dry": "npm pack --dry-run"
22
+ },
23
+ "engines": {
24
+ "node": ">=18"
25
+ },
26
+ "keywords": [
27
+ "network",
28
+ "speed-test",
29
+ "cli",
30
+ "netisok",
31
+ "ping"
32
+ ],
33
+ "license": "MIT",
34
+ "dependencies": {
35
+ "commander": "^13.1.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^22.15.21",
39
+ "tsx": "^4.19.4",
40
+ "typescript": "^5.8.3"
41
+ }
42
+ }