rssany 0.3.1 → 0.3.3

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.
Files changed (119) hide show
  1. package/.env.example +52 -52
  2. package/README.md +156 -147
  3. package/app/plugins/builtin/email.rssany.js +84 -84
  4. package/app/plugins/builtin/rss.rssany.js +164 -164
  5. package/app/plugins/builtin/xiaohongshu.rssany.js +59 -2
  6. package/app/statics/README.md +7 -7
  7. package/app/webui/build/200.html +36 -36
  8. package/app/webui/build/_app/immutable/assets/0.BLOTwIuF.css +1 -0
  9. package/app/webui/build/_app/immutable/assets/10.CmGYYZFR.css +1 -0
  10. package/app/webui/build/_app/immutable/assets/11.Dkz3VS_N.css +1 -0
  11. package/app/webui/build/_app/immutable/assets/14.BCCBoMGj.css +1 -0
  12. package/app/webui/build/_app/immutable/assets/6.Cm_jpHOq.css +1 -0
  13. package/app/webui/build/_app/immutable/assets/7.CJ3BjogD.css +1 -0
  14. package/app/webui/build/_app/immutable/assets/9.CATKVZ-n.css +1 -0
  15. package/app/webui/build/_app/immutable/assets/{SourcesList.D5Lso0bo.css → SourcesList.ke66uOSi.css} +1 -1
  16. package/app/webui/build/_app/immutable/assets/chevron-down.CV-KWLNP.css +1 -0
  17. package/app/webui/build/_app/immutable/chunks/{CGCMIfh3.js → 4TuV_psf.js} +1 -1
  18. package/app/webui/build/_app/immutable/chunks/{DAdOEnFb.js → B0czyjwj.js} +1 -1
  19. package/app/webui/build/_app/immutable/chunks/{CFwxUBGi.js → B553hBXT.js} +1 -1
  20. package/app/webui/build/_app/immutable/chunks/B8StT3Do.js +6 -0
  21. package/app/webui/build/_app/immutable/chunks/BI_ale1m.js +1 -0
  22. package/app/webui/build/_app/immutable/chunks/BK0ygNWX.js +2 -0
  23. package/app/webui/build/_app/immutable/chunks/BKm6QCwp.js +1 -0
  24. package/app/webui/build/_app/immutable/chunks/BT6b4LcZ.js +36 -0
  25. package/app/webui/build/_app/immutable/chunks/BZY5aksi.js +36 -0
  26. package/app/webui/build/_app/immutable/chunks/{C8umpVpB.js → BnqaikL8.js} +1 -1
  27. package/app/webui/build/_app/immutable/chunks/BsQ08Wq_.js +1 -0
  28. package/app/webui/build/_app/immutable/chunks/C9wTDiHH.js +1 -0
  29. package/app/webui/build/_app/immutable/chunks/{B-CeeY89.js → CAKuIoAf.js} +1 -1
  30. package/app/webui/build/_app/immutable/chunks/CEWi_rGa.js +1 -0
  31. package/app/webui/build/_app/immutable/chunks/{ChUctqXA.js → Cc7aBSsN.js} +1 -1
  32. package/app/webui/build/_app/immutable/chunks/{BAJAS8BI.js → D8G961Hm.js} +1 -1
  33. package/app/webui/build/_app/immutable/chunks/{CS53ooo0.js → DIeahUKq.js} +1 -1
  34. package/app/webui/build/_app/immutable/chunks/DO5OXNYS.js +1 -0
  35. package/app/webui/build/_app/immutable/chunks/Dg_D3pjF.js +1 -0
  36. package/app/webui/build/_app/immutable/chunks/{Dyvi1wBH.js → DptdhtA1.js} +1 -1
  37. package/app/webui/build/_app/immutable/chunks/{ClknbeNl.js → FDS7fbwH.js} +1 -1
  38. package/app/webui/build/_app/immutable/chunks/{CqYSO3Dx.js → GeNMTUn1.js} +1 -1
  39. package/app/webui/build/_app/immutable/chunks/{DCEayuDt.js → IhDlsCxD.js} +1 -1
  40. package/app/webui/build/_app/immutable/chunks/Nd0ktDhd.js +1 -0
  41. package/app/webui/build/_app/immutable/chunks/{D6kzEN_P.js → SvdgnirT.js} +1 -1
  42. package/app/webui/build/_app/immutable/chunks/WW6La7Nt.js +2 -0
  43. package/app/webui/build/_app/immutable/chunks/{DsxvjlCC.js → pd_p3yYy.js} +5 -5
  44. package/app/webui/build/_app/immutable/chunks/rNwPv4DZ.js +1 -0
  45. package/app/webui/build/_app/immutable/entry/app.BKLBG-4w.js +2 -0
  46. package/app/webui/build/_app/immutable/entry/start.D-X6pVtx.js +1 -0
  47. package/app/webui/build/_app/immutable/nodes/{0.DK_mcVDm.js → 0.CJDC_3s9.js} +3 -3
  48. package/app/webui/build/_app/immutable/nodes/1.DsKocFSb.js +1 -0
  49. package/app/webui/build/_app/immutable/nodes/10.BeejAn8z.js +1 -0
  50. package/app/webui/build/_app/immutable/nodes/11.D--uwkk0.js +3 -0
  51. package/app/webui/build/_app/immutable/nodes/12.BLyQ6rUu.js +1 -0
  52. package/app/webui/build/_app/immutable/nodes/13.Cl0WQK13.js +1 -0
  53. package/app/webui/build/_app/immutable/nodes/14.T9l5Rh19.js +1 -0
  54. package/app/webui/build/_app/immutable/nodes/15.DHfwIlBx.js +1 -0
  55. package/app/webui/build/_app/immutable/nodes/{16.zfSe93Ab.js → 16.BKDfR-KV.js} +2 -2
  56. package/app/webui/build/_app/immutable/nodes/17.DofB8HQB.js +1 -0
  57. package/app/webui/build/_app/immutable/nodes/2.BOYqXdCa.js +1 -0
  58. package/app/webui/build/_app/immutable/nodes/3.B9ucbp_W.js +1 -0
  59. package/app/webui/build/_app/immutable/nodes/5.9zgwFV6I.js +2 -0
  60. package/app/webui/build/_app/immutable/nodes/6.Bs32Ieii.js +2 -0
  61. package/app/webui/build/_app/immutable/nodes/7.Cigxrk0v.js +1 -0
  62. package/app/webui/build/_app/immutable/nodes/8.pG10rCF0.js +1 -0
  63. package/app/webui/build/_app/immutable/nodes/9.Bzqb3xHY.js +1 -0
  64. package/app/webui/build/_app/version.json +1 -1
  65. package/bin/rssany.js +55 -3
  66. package/dist/index.js +361 -99
  67. package/dist/index.js.map +1 -1
  68. package/package.json +107 -103
  69. package/scripts/dev.mjs +5 -1
  70. package/scripts/postinstall.mjs +44 -0
  71. package/scripts/reset.mjs +137 -135
  72. package/scripts/user-dir.mjs +52 -0
  73. package/app/webui/build/_app/immutable/assets/0.DsKls1SN.css +0 -1
  74. package/app/webui/build/_app/immutable/assets/10.Dj8_pmut.css +0 -1
  75. package/app/webui/build/_app/immutable/assets/13.Qu_tY6H9.css +0 -1
  76. package/app/webui/build/_app/immutable/assets/5.B-dPiwB7.css +0 -1
  77. package/app/webui/build/_app/immutable/assets/6.B27N7pdA.css +0 -1
  78. package/app/webui/build/_app/immutable/assets/8.Cgji2b15.css +0 -1
  79. package/app/webui/build/_app/immutable/assets/9.BsCIAvn3.css +0 -1
  80. package/app/webui/build/_app/immutable/chunks/6prdYIKP.js +0 -1
  81. package/app/webui/build/_app/immutable/chunks/B2cyTHdf.js +0 -2
  82. package/app/webui/build/_app/immutable/chunks/B6WG2Sd3.js +0 -1
  83. package/app/webui/build/_app/immutable/chunks/BA4Gucnq.js +0 -1
  84. package/app/webui/build/_app/immutable/chunks/BkD3yAYe.js +0 -1
  85. package/app/webui/build/_app/immutable/chunks/C4uF_YIK.js +0 -1
  86. package/app/webui/build/_app/immutable/chunks/CBY2biv-.js +0 -1
  87. package/app/webui/build/_app/immutable/chunks/CVW0ymE1.js +0 -1
  88. package/app/webui/build/_app/immutable/chunks/DJ2e04vK.js +0 -36
  89. package/app/webui/build/_app/immutable/chunks/DL3Q5sfb.js +0 -1
  90. package/app/webui/build/_app/immutable/chunks/DVa8Y-mQ.js +0 -1
  91. package/app/webui/build/_app/immutable/chunks/DkamXS6W.js +0 -36
  92. package/app/webui/build/_app/immutable/chunks/DoRPmqLn.js +0 -2
  93. package/app/webui/build/_app/immutable/chunks/_qj9U-za.js +0 -1
  94. package/app/webui/build/_app/immutable/chunks/vtBo8kBV.js +0 -1
  95. package/app/webui/build/_app/immutable/entry/app.RFfWi3_i.js +0 -2
  96. package/app/webui/build/_app/immutable/entry/start.DU_kyeGS.js +0 -1
  97. package/app/webui/build/_app/immutable/nodes/1.0PRrU2uQ.js +0 -1
  98. package/app/webui/build/_app/immutable/nodes/10.CsxzlUER.js +0 -1
  99. package/app/webui/build/_app/immutable/nodes/11.D-PkhIRW.js +0 -1
  100. package/app/webui/build/_app/immutable/nodes/12.GGf-JLUY.js +0 -1
  101. package/app/webui/build/_app/immutable/nodes/13.DWWcH27k.js +0 -6
  102. package/app/webui/build/_app/immutable/nodes/14.COwSLwDN.js +0 -1
  103. package/app/webui/build/_app/immutable/nodes/15.nDN_AHrs.js +0 -1
  104. package/app/webui/build/_app/immutable/nodes/2.AJd2163d.js +0 -1
  105. package/app/webui/build/_app/immutable/nodes/3.CEVEHuaH.js +0 -1
  106. package/app/webui/build/_app/immutable/nodes/4.BT_N8pCh.js +0 -2
  107. package/app/webui/build/_app/immutable/nodes/5.BZScQ2CH.js +0 -2
  108. package/app/webui/build/_app/immutable/nodes/6.CkFk8X--.js +0 -1
  109. package/app/webui/build/_app/immutable/nodes/7.CuQJk7te.js +0 -1
  110. package/app/webui/build/_app/immutable/nodes/8.DIavWJnU.js +0 -1
  111. package/app/webui/build/_app/immutable/nodes/9.Db30M8x0.js +0 -1
  112. /package/app/webui/build/_app/immutable/assets/{11.qYZMiTb0.css → 12.qYZMiTb0.css} +0 -0
  113. /package/app/webui/build/_app/immutable/assets/{12.DfJcfUWl.css → 13.DfJcfUWl.css} +0 -0
  114. /package/app/webui/build/_app/immutable/assets/{14.DfMfOrS3.css → 15.DfMfOrS3.css} +0 -0
  115. /package/app/webui/build/_app/immutable/assets/{15.nNGjXhCQ.css → 17.nNGjXhCQ.css} +0 -0
  116. /package/app/webui/build/_app/immutable/assets/{4.Di6rvlY-.css → 5.Di6rvlY-.css} +0 -0
  117. /package/app/webui/build/_app/immutable/assets/{7.CrNxmd8B.css → 8.CrNxmd8B.css} +0 -0
  118. /package/app/webui/build/_app/immutable/nodes/{17.BtYZF6FM.js → 18.BtYZF6FM.js} +0 -0
  119. /package/app/webui/build/_app/immutable/nodes/{18.BIzqhTqv.js → 4.BIzqhTqv.js} +0 -0
package/package.json CHANGED
@@ -1,103 +1,107 @@
1
- {
2
- "name": "rssany",
3
- "version": "0.3.1",
4
- "description": "Universal RSS/Atom/JSON Feed pipeline — fetches, extracts, parses and converts any web content into consumable feeds with plugin support",
5
- "author": "Joo",
6
- "type": "module",
7
- "main": "./dist/index.js",
8
- "exports": {
9
- ".": "./dist/index.js"
10
- },
11
- "bin": {
12
- "rssany": "./bin/rssany.js"
13
- },
14
- "files": [
15
- "dist",
16
- "bin",
17
- "app/plugins/builtin",
18
- "app/plugins/site.rssany.js",
19
- "statics",
20
- "app/webui/build",
21
- ".env.example",
22
- "README.md",
23
- "init",
24
- "scripts/reset.mjs",
25
- "scripts/dev.mjs"
26
- ],
27
- "engines": {
28
- "node": ">=20 <24"
29
- },
30
- "scripts": {
31
- "build": "vite build",
32
- "dev": "node scripts/dev.mjs",
33
- "dev:backend": "cross-env PORT=3999 tsx app/index.ts",
34
- "start": "node dist/index.js",
35
- "serve:route": "node scripts/serve-route.mjs",
36
- "serve:app": "npx tsx app/index.ts",
37
- "test": "vitest",
38
- "test:run": "vitest run",
39
- "lint": "eslint .",
40
- "lint:fix": "eslint . --fix",
41
- "typecheck": "tsc --noEmit",
42
- "reset": "node scripts/reset.mjs",
43
- "proxy-browser": "tsx scripts/proxy-browser.ts",
44
- "webui:install": "cd app/webui && npm install",
45
- "webui:dev": "cd app/webui && npm run build:watch",
46
- "webui:build": "cd app/webui && npm run build",
47
- "webui:watch": "cd app/webui && npm run build:watch",
48
- "dev:all": "npm run dev",
49
- "build:all": "npm run build && npm run webui:build",
50
- "prepublishOnly": "npm run build:all",
51
- "docker:build": "bash scripts/docker-build.sh",
52
- "docker:build:tag": "bash scripts/docker-build.sh",
53
- "landing:install": "cd landing && npm install",
54
- "landing:dev": "cd landing && npm run dev",
55
- "landing:build": "cd landing && npm run build",
56
- "deploy": "node scripts/deploy-landing.mjs",
57
- "deploy:landing": "node scripts/deploy-landing.mjs"
58
- },
59
- "keywords": [
60
- "rss",
61
- "atom",
62
- "json-feed",
63
- "subscription",
64
- "pipeline"
65
- ],
66
- "license": "MIT",
67
- "devDependencies": {
68
- "@eslint/js": "^9.15.0",
69
- "@types/jsdom": "^28.0.3",
70
- "@types/mailparser": "^3.4.6",
71
- "@types/node": "^25.2.0",
72
- "@types/node-cron": "^3.0.11",
73
- "@types/nodemailer": "^7.0.11",
74
- "concurrently": "^9.2.1",
75
- "cross-env": "^7.0.3",
76
- "eslint": "^9.15.0",
77
- "globals": "^15.12.0",
78
- "ssh2": "^1.17.0",
79
- "tsx": "^4.19.0",
80
- "typescript": "~5.6.0",
81
- "typescript-eslint": "^8.15.0",
82
- "vite": "^6.4.2",
83
- "vitest": "^2.1.0"
84
- },
85
- "dependencies": {
86
- "@hono/node-server": "^1.19.10",
87
- "@mozilla/readability": "^0.6.0",
88
- "cron-parser": "^5.0.0",
89
- "dotenv": "^16.4.7",
90
- "hono": "^4.12.12",
91
- "https-proxy-agent": "^7.0.6",
92
- "imapflow": "^1.2.10",
93
- "jsdom": "^29.1.1",
94
- "mailparser": "^3.9.3",
95
- "marked": "^17.0.3",
96
- "node-cron": "^4.2.1",
97
- "node-html-parser": "^7.0.2",
98
- "nodemailer": "^8.0.2",
99
- "openai": "^6.42.0",
100
- "puppeteer-core": "^24.36.0",
101
- "rss-parser": "^3.13.0"
102
- }
103
- }
1
+ {
2
+ "name": "rssany",
3
+ "version": "0.3.3",
4
+ "description": "Universal RSS/Atom/JSON Feed pipeline — fetches, extracts, parses and converts any web content into consumable feeds with plugin support",
5
+ "author": "Joo",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "exports": {
9
+ ".": "./dist/index.js"
10
+ },
11
+ "bin": {
12
+ "rssany": "./bin/rssany.js"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "bin",
17
+ "scripts/user-dir.mjs",
18
+ "app/plugins/builtin",
19
+ "app/plugins/site.rssany.js",
20
+ "statics",
21
+ "app/webui/build",
22
+ ".env.example",
23
+ "README.md",
24
+ "init",
25
+ "scripts/reset.mjs",
26
+ "scripts/postinstall.mjs",
27
+ "scripts/user-dir.mjs",
28
+ "scripts/dev.mjs"
29
+ ],
30
+ "engines": {
31
+ "node": ">=20 <24"
32
+ },
33
+ "scripts": {
34
+ "build": "vite build",
35
+ "dev": "node scripts/dev.mjs",
36
+ "dev:backend": "cross-env PORT=3999 tsx app/index.ts",
37
+ "start": "node dist/index.js",
38
+ "serve:route": "node scripts/serve-route.mjs",
39
+ "serve:app": "npx tsx app/index.ts",
40
+ "test": "vitest",
41
+ "test:run": "vitest run --passWithNoTests",
42
+ "lint": "eslint .",
43
+ "lint:fix": "eslint . --fix",
44
+ "typecheck": "tsc --noEmit",
45
+ "reset": "node scripts/reset.mjs",
46
+ "postinstall": "node scripts/postinstall.mjs",
47
+ "proxy-browser": "tsx scripts/proxy-browser.ts",
48
+ "webui:install": "cd app/webui && npm install",
49
+ "webui:dev": "cd app/webui && npm run build:watch",
50
+ "webui:build": "cd app/webui && npm run build",
51
+ "webui:watch": "cd app/webui && npm run build:watch",
52
+ "dev:all": "npm run dev",
53
+ "build:all": "npm run build && npm run webui:build",
54
+ "prepublishOnly": "npm run build:all",
55
+ "docker:build": "bash scripts/docker-build.sh",
56
+ "docker:build:tag": "bash scripts/docker-build.sh",
57
+ "landing:install": "cd landing && npm install",
58
+ "landing:dev": "cd landing && npm run dev",
59
+ "landing:build": "cd landing && npm run build",
60
+ "deploy": "node scripts/deploy-landing.mjs",
61
+ "deploy:landing": "node scripts/deploy-landing.mjs"
62
+ },
63
+ "keywords": [
64
+ "rss",
65
+ "atom",
66
+ "json-feed",
67
+ "subscription",
68
+ "pipeline"
69
+ ],
70
+ "license": "MIT",
71
+ "devDependencies": {
72
+ "@eslint/js": "^9.15.0",
73
+ "@types/jsdom": "^28.0.3",
74
+ "@types/mailparser": "^3.4.6",
75
+ "@types/node": "^25.2.0",
76
+ "@types/node-cron": "^3.0.11",
77
+ "@types/nodemailer": "^7.0.11",
78
+ "concurrently": "^9.2.1",
79
+ "cross-env": "^7.0.3",
80
+ "eslint": "^9.15.0",
81
+ "globals": "^15.12.0",
82
+ "ssh2": "^1.17.0",
83
+ "tsx": "^4.19.0",
84
+ "typescript": "~5.6.0",
85
+ "typescript-eslint": "^8.15.0",
86
+ "vite": "^6.4.3",
87
+ "vitest": "^4.1.8"
88
+ },
89
+ "dependencies": {
90
+ "@hono/node-server": "^1.19.10",
91
+ "@mozilla/readability": "^0.6.0",
92
+ "cron-parser": "^5.0.0",
93
+ "dotenv": "^16.4.7",
94
+ "hono": "^4.12.12",
95
+ "https-proxy-agent": "^7.0.6",
96
+ "imapflow": "^1.2.10",
97
+ "jsdom": "^29.1.1",
98
+ "mailparser": "^3.9.3",
99
+ "marked": "^17.0.3",
100
+ "node-cron": "^4.2.1",
101
+ "node-html-parser": "^7.0.2",
102
+ "nodemailer": "^8.0.2",
103
+ "openai": "^6.42.0",
104
+ "puppeteer-core": "^24.36.0",
105
+ "rss-parser": "^3.13.0"
106
+ }
107
+ }
package/scripts/dev.mjs CHANGED
@@ -73,7 +73,11 @@ function waitForInitialWebuiBuild(child) {
73
73
  }, 500);
74
74
  child.stdout?.on("data", (chunk) => {
75
75
  const text = chunk.toString();
76
- if (text.includes('Wrote site to "build"') || text.includes("Wrote site to 'build'")) {
76
+ if (
77
+ text.includes('Wrote site to "build"') ||
78
+ text.includes("Wrote site to 'build'") ||
79
+ text.includes("[webui:watch] build complete")
80
+ ) {
77
81
  finish();
78
82
  }
79
83
  });
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 全局安装后输出用户数据目录位置({npm prefix}/var/rssany)。
4
+ */
5
+
6
+ import { execSync } from "node:child_process";
7
+ import { dirname, join } from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ import { isGlobalNpmInstall, resolveDefaultUserDir, resolveNpmPrefixFromPackageRoot } from "./user-dir.mjs";
10
+
11
+ const scriptDir = dirname(fileURLToPath(import.meta.url));
12
+ const packageRoot = join(scriptDir, "..");
13
+
14
+ function getGlobalNpmPrefix() {
15
+ try {
16
+ return execSync("npm prefix -g", { encoding: "utf8", env: process.env }).trim();
17
+ } catch {
18
+ return null;
19
+ }
20
+ }
21
+
22
+ function isSystemOwnedPrefix(prefix) {
23
+ if (!prefix) return false;
24
+ const normalized = prefix.replace(/\\/g, "/");
25
+ return normalized === "/usr/local" || normalized.startsWith("/usr/local/");
26
+ }
27
+
28
+ async function main() {
29
+ if (!isGlobalNpmInstall(packageRoot)) return;
30
+
31
+ const userDir = resolveDefaultUserDir(packageRoot);
32
+ const npmPrefix = resolveNpmPrefixFromPackageRoot(packageRoot) ?? getGlobalNpmPrefix();
33
+ console.log(`[rssany] 用户数据目录: ${userDir}`);
34
+ if (npmPrefix) {
35
+ console.log(`[rssany] npm 全局 prefix: ${npmPrefix}`);
36
+ }
37
+ if (isSystemOwnedPrefix(getGlobalNpmPrefix())) {
38
+ console.log(
39
+ "[rssany] 若此前 npm install -g 报 EACCES,请先执行: npm config set prefix \"$HOME/.local\",并将 \"$HOME/.local/bin\" 加入 PATH",
40
+ );
41
+ }
42
+ }
43
+
44
+ await main();
package/scripts/reset.mjs CHANGED
@@ -1,136 +1,138 @@
1
- #!/usr/bin/env node
2
- /**
3
- * 停止占用 HTTP 服务端口的进程,并删除用户数据目录(与 app 中 PORT / RSSANY_USER_DIR 约定一致)。
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 停止占用 HTTP 服务端口的进程,并删除用户数据目录(与 app 中 PORT / RSSANY_USER_DIR 约定一致)。
4
4
  * 用法:npm run reset 或 PORT=3000 npm run reset
5
- */
6
-
7
- import { execFileSync } from "node:child_process";
8
- import { existsSync, rmSync } from "node:fs";
9
- import { homedir } from "node:os";
10
- import { join } from "node:path";
11
- import { config } from "dotenv";
12
-
13
- config({ path: join(process.cwd(), ".env") });
14
-
15
- const DEFAULT_PORT = 18473;
16
- const port = Number(process.env.PORT) || DEFAULT_PORT;
17
- const userDirRaw = process.env.RSSANY_USER_DIR?.trim();
18
- const userDir =
19
- userDirRaw && userDirRaw.length > 0 ? userDirRaw : join(homedir(), ".rssany");
20
-
21
- function pidsListeningWin32(p) {
22
- const cmd = `Get-NetTCPConnection -LocalPort ${p} -State Listen -ErrorAction SilentlyContinue | ForEach-Object { $_.OwningProcess } | Sort-Object -Unique`;
23
- try {
24
- const out = execFileSync("powershell.exe", ["-NoProfile", "-Command", cmd], {
25
- encoding: "utf8",
26
- windowsHide: true,
27
- });
28
- return [
29
- ...new Set(
30
- out
31
- .trim()
32
- .split(/\r?\n/)
33
- .map((s) => s.trim())
34
- .filter(Boolean)
35
- .map(Number)
36
- .filter((n) => Number.isFinite(n) && n > 0),
37
- ),
38
- ];
39
- } catch {
40
- return [];
41
- }
42
- }
43
-
44
- function pidsListeningNetstatWin32(p) {
45
- const pids = new Set();
46
- try {
47
- const out = execFileSync("netstat", ["-ano"], { encoding: "utf8", windowsHide: true });
48
- const needle = `:${p}`;
49
- for (const line of out.split(/\r?\n/)) {
50
- if (!line.includes("LISTENING") || !line.includes(needle)) continue;
51
- const m = line.trim().match(/LISTENING\s+(\d+)\s*$/);
52
- if (m) pids.add(Number(m[1]));
53
- }
54
- } catch {
55
- // ignore
56
- }
57
- return [...pids];
58
- }
59
-
60
- function pidsListeningUnix(p) {
61
- const argsList = [
62
- ["-nP", `-iTCP:${p}`, "-sTCP:LISTEN", "-t"],
63
- ["-ti", `:${p}`],
64
- ];
65
- for (const args of argsList) {
66
- try {
67
- const out = execFileSync("lsof", args, { encoding: "utf8" });
68
- const pids = out
69
- .trim()
70
- .split(/\n/)
71
- .map((s) => Number(s.trim()))
72
- .filter((n) => Number.isFinite(n) && n > 0);
73
- if (pids.length) return [...new Set(pids)];
74
- } catch {
75
- // try next
76
- }
77
- }
78
- return [];
79
- }
80
-
81
- function killPidsWin32(pids) {
82
- for (const pid of pids) {
83
- if (pid === process.pid) continue;
84
- try {
85
- execFileSync("taskkill", ["/F", "/PID", String(pid)], { stdio: "inherit", windowsHide: true });
86
- console.log(`已结束进程 PID ${pid}`);
87
- } catch {
88
- console.warn(`无法结束进程 PID ${pid}(可能已退出)`);
89
- }
90
- }
91
- }
92
-
93
- function killPidsUnix(pids) {
94
- for (const pid of pids) {
95
- if (pid === process.pid) continue;
96
- try {
97
- process.kill(pid, "SIGTERM");
98
- console.log(`已发送 SIGTERM 至 PID ${pid}`);
99
- } catch {
100
- try {
101
- process.kill(pid, "SIGKILL");
102
- console.log(`已发送 SIGKILL 至 PID ${pid}`);
103
- } catch {
104
- console.warn(`无法结束进程 PID ${pid}`);
105
- }
106
- }
107
- }
108
- }
109
-
110
- function main() {
111
- console.log(`端口: ${port}(来自 PORT 或默认 ${DEFAULT_PORT})`);
112
- console.log(`用户数据目录: ${userDir}`);
113
-
114
- let pids =
115
- process.platform === "win32" ? pidsListeningWin32(port) : pidsListeningUnix(port);
116
- if (process.platform === "win32" && pids.length === 0) {
117
- pids = pidsListeningNetstatWin32(port);
118
- }
119
-
120
- if (pids.length === 0) {
121
- console.log("未发现占用该端口的监听进程。");
122
- } else {
123
- console.log(`将结束占用端口的进程: ${pids.join(", ")}`);
124
- if (process.platform === "win32") killPidsWin32(pids);
125
- else killPidsUnix(pids);
126
- }
127
-
128
- if (!existsSync(userDir)) {
129
- console.log("用户数据目录不存在,跳过删除。");
130
- return;
131
- }
132
- rmSync(userDir, { recursive: true, force: true });
133
- console.log("已删除用户数据目录。");
134
- }
135
-
136
- main();
5
+ */
6
+
7
+ import { execFileSync } from "node:child_process";
8
+ import { existsSync, rmSync } from "node:fs";
9
+ import { join, dirname } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+ import { config } from "dotenv";
12
+ import { resolveDefaultUserDir } from "./user-dir.mjs";
13
+
14
+ config({ path: join(process.cwd(), ".env") });
15
+
16
+ const scriptDir = dirname(fileURLToPath(import.meta.url));
17
+ const packageRoot = join(scriptDir, "..");
18
+
19
+ const DEFAULT_PORT = 18473;
20
+ const port = Number(process.env.PORT) || DEFAULT_PORT;
21
+ const userDir = resolveDefaultUserDir(packageRoot);
22
+
23
+ function pidsListeningWin32(p) {
24
+ const cmd = `Get-NetTCPConnection -LocalPort ${p} -State Listen -ErrorAction SilentlyContinue | ForEach-Object { $_.OwningProcess } | Sort-Object -Unique`;
25
+ try {
26
+ const out = execFileSync("powershell.exe", ["-NoProfile", "-Command", cmd], {
27
+ encoding: "utf8",
28
+ windowsHide: true,
29
+ });
30
+ return [
31
+ ...new Set(
32
+ out
33
+ .trim()
34
+ .split(/\r?\n/)
35
+ .map((s) => s.trim())
36
+ .filter(Boolean)
37
+ .map(Number)
38
+ .filter((n) => Number.isFinite(n) && n > 0),
39
+ ),
40
+ ];
41
+ } catch {
42
+ return [];
43
+ }
44
+ }
45
+
46
+ function pidsListeningNetstatWin32(p) {
47
+ const pids = new Set();
48
+ try {
49
+ const out = execFileSync("netstat", ["-ano"], { encoding: "utf8", windowsHide: true });
50
+ const needle = `:${p}`;
51
+ for (const line of out.split(/\r?\n/)) {
52
+ if (!line.includes("LISTENING") || !line.includes(needle)) continue;
53
+ const m = line.trim().match(/LISTENING\s+(\d+)\s*$/);
54
+ if (m) pids.add(Number(m[1]));
55
+ }
56
+ } catch {
57
+ // ignore
58
+ }
59
+ return [...pids];
60
+ }
61
+
62
+ function pidsListeningUnix(p) {
63
+ const argsList = [
64
+ ["-nP", `-iTCP:${p}`, "-sTCP:LISTEN", "-t"],
65
+ ["-ti", `:${p}`],
66
+ ];
67
+ for (const args of argsList) {
68
+ try {
69
+ const out = execFileSync("lsof", args, { encoding: "utf8" });
70
+ const pids = out
71
+ .trim()
72
+ .split(/\n/)
73
+ .map((s) => Number(s.trim()))
74
+ .filter((n) => Number.isFinite(n) && n > 0);
75
+ if (pids.length) return [...new Set(pids)];
76
+ } catch {
77
+ // try next
78
+ }
79
+ }
80
+ return [];
81
+ }
82
+
83
+ function killPidsWin32(pids) {
84
+ for (const pid of pids) {
85
+ if (pid === process.pid) continue;
86
+ try {
87
+ execFileSync("taskkill", ["/F", "/PID", String(pid)], { stdio: "inherit", windowsHide: true });
88
+ console.log(`已结束进程 PID ${pid}`);
89
+ } catch {
90
+ console.warn(`无法结束进程 PID ${pid}(可能已退出)`);
91
+ }
92
+ }
93
+ }
94
+
95
+ function killPidsUnix(pids) {
96
+ for (const pid of pids) {
97
+ if (pid === process.pid) continue;
98
+ try {
99
+ process.kill(pid, "SIGTERM");
100
+ console.log(`已发送 SIGTERM 至 PID ${pid}`);
101
+ } catch {
102
+ try {
103
+ process.kill(pid, "SIGKILL");
104
+ console.log(`已发送 SIGKILL 至 PID ${pid}`);
105
+ } catch {
106
+ console.warn(`无法结束进程 PID ${pid}`);
107
+ }
108
+ }
109
+ }
110
+ }
111
+
112
+ function main() {
113
+ console.log(`端口: ${port}(来自 PORT 或默认 ${DEFAULT_PORT})`);
114
+ console.log(`用户数据目录: ${userDir}`);
115
+
116
+ let pids =
117
+ process.platform === "win32" ? pidsListeningWin32(port) : pidsListeningUnix(port);
118
+ if (process.platform === "win32" && pids.length === 0) {
119
+ pids = pidsListeningNetstatWin32(port);
120
+ }
121
+
122
+ if (pids.length === 0) {
123
+ console.log("未发现占用该端口的监听进程。");
124
+ } else {
125
+ console.log(`将结束占用端口的进程: ${pids.join(", ")}`);
126
+ if (process.platform === "win32") killPidsWin32(pids);
127
+ else killPidsUnix(pids);
128
+ }
129
+
130
+ if (!existsSync(userDir)) {
131
+ console.log("用户数据目录不存在,跳过删除。");
132
+ return;
133
+ }
134
+ rmSync(userDir, { recursive: true, force: true });
135
+ console.log("已删除用户数据目录。");
136
+ }
137
+
138
+ main();
@@ -0,0 +1,52 @@
1
+ // 与 app/config/userDir.ts 保持同步(bin/、scripts/ 运行时直接加载)
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+
5
+ const LEGACY_HOME_DIR = ".rssany";
6
+
7
+ export function resolveNpmPrefixFromPackageRoot(packageRoot) {
8
+ const normalized = packageRoot.replace(/\\/g, "/");
9
+ const libSuffix = "/lib/node_modules/rssany";
10
+ if (normalized.endsWith(libSuffix)) {
11
+ return packageRoot.slice(0, packageRoot.length - libSuffix.length);
12
+ }
13
+ const flatSuffix = "/node_modules/rssany";
14
+ if (normalized.endsWith(flatSuffix)) {
15
+ return packageRoot.slice(0, packageRoot.length - flatSuffix.length);
16
+ }
17
+ return null;
18
+ }
19
+
20
+ export function isGlobalNpmInstall(packageRoot) {
21
+ const normalized = packageRoot.replace(/\\/g, "/");
22
+ if (normalized.endsWith("/lib/node_modules/rssany")) return true;
23
+ const globalPatterns = [
24
+ /\/npm\/node_modules\/rssany$/,
25
+ /\/\.local\/lib\/node_modules\/rssany$/,
26
+ /\/\.local\/node_modules\/rssany$/,
27
+ /\/\.npm-global\/lib\/node_modules\/rssany$/,
28
+ /\/\.nvm\/versions\/node\/[^/]+\/lib\/node_modules\/rssany$/,
29
+ /\/\.fnm\/node-versions\/[^/]+\/installation\/lib\/node_modules\/rssany$/,
30
+ ];
31
+ return globalPatterns.some((pattern) => pattern.test(normalized));
32
+ }
33
+
34
+ export function resolveDefaultUserDir(packageRoot) {
35
+ const env = process.env.RSSANY_USER_DIR?.trim();
36
+ if (env) return env;
37
+
38
+ const npmPrefix = resolveNpmPrefixFromPackageRoot(packageRoot);
39
+ if (npmPrefix && isGlobalNpmInstall(packageRoot)) {
40
+ return join(npmPrefix, "var", "rssany");
41
+ }
42
+
43
+ if (!packageRoot.replace(/\\/g, "/").includes("/node_modules/")) {
44
+ return join(packageRoot, ".rssany");
45
+ }
46
+
47
+ return join(homedir(), LEGACY_HOME_DIR);
48
+ }
49
+
50
+ export function getLegacyHomeUserDir() {
51
+ return join(homedir(), LEGACY_HOME_DIR);
52
+ }