Skip to content

Commit caad61e

Browse files
committed
feat: add updated sources tab
1 parent 2f5e547 commit caad61e

4 files changed

Lines changed: 158 additions & 1 deletion

File tree

scripts/source.ts

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,124 @@
11
import { writeFileSync } from "node:fs"
22
import { join } from "node:path"
3+
import { execFileSync } from "node:child_process"
34
import { pinyin } from "@napi-rs/pinyin"
45
import { consola } from "consola"
56
import { projectDir } from "../shared/dir"
67
import { genSources } from "../shared/pre-sources"
8+
import packageJSON from "../package.json"
79

810
const sources = genSources()
11+
12+
function git(args: string[]) {
13+
return execFileSync("git", args, {
14+
cwd: projectDir,
15+
encoding: "utf8",
16+
stdio: ["ignore", "pipe", "ignore"],
17+
}).trim()
18+
}
19+
20+
function tagExists(tag: string) {
21+
try {
22+
git(["rev-parse", "--verify", "--quiet", tag])
23+
return true
24+
} catch {
25+
return false
26+
}
27+
}
28+
29+
function getTagCommit(tag: string) {
30+
return git(["rev-list", "-n", "1", tag])
31+
}
32+
33+
function isHead(tag: string) {
34+
try {
35+
return getTagCommit(tag) === git(["rev-parse", "HEAD"])
36+
} catch {
37+
return false
38+
}
39+
}
40+
41+
function getVersionTags() {
42+
return git(["tag", "--sort=-version:refname", "--list", "v[0-9]*"])
43+
.split("\n")
44+
.filter(Boolean)
45+
}
46+
47+
function getPreviousVersionTag(tag: string, offset = 1) {
48+
try {
49+
const tags = getVersionTags()
50+
const index = tags.indexOf(tag)
51+
return index === -1 ? undefined : tags[index + offset]
52+
} catch {}
53+
}
54+
55+
function getVersionBaseRef() {
56+
try {
57+
const tags = [`v${packageJSON.version}`, packageJSON.version]
58+
for (const tag of tags) {
59+
if (!tagExists(tag)) continue
60+
if (isHead(tag)) return getPreviousVersionTag(tag, 2) ?? getPreviousVersionTag(tag) ?? tag
61+
return getPreviousVersionTag(tag) ?? tag
62+
}
63+
64+
const tag = git(["describe", "--tags", "--abbrev=0"])
65+
return isHead(tag) ? getPreviousVersionTag(tag, 2) ?? getPreviousVersionTag(tag) ?? tag : tag
66+
} catch {}
67+
}
68+
69+
function setSourceUpdatedAt(target: Map<string, number>, sourceId: string, updatedAt: number) {
70+
const normalizedId = sourceId.replace(/^_/, "")
71+
Object.keys(sources).forEach((id) => {
72+
if (id === normalizedId || id.startsWith(`${normalizedId}-`)) {
73+
target.set(id, Math.max(target.get(id) ?? 0, updatedAt))
74+
}
75+
})
76+
}
77+
78+
function hasWorkingTreeChange(file: string) {
79+
try {
80+
git(["diff", "--quiet", "--", file])
81+
git(["diff", "--cached", "--quiet", "--", file])
82+
return false
83+
} catch {
84+
return true
85+
}
86+
}
87+
88+
function getFileUpdatedAt(baseRef: string, file: string) {
89+
if (hasWorkingTreeChange(file)) return Number.MAX_SAFE_INTEGER
90+
91+
const timestamp = git(["log", "-1", "--format=%ct", `${baseRef}..HEAD`, "--", file])
92+
return timestamp ? Number(timestamp) : 0
93+
}
94+
95+
function getUpdatedSourceIds() {
96+
try {
97+
const baseRef = getVersionBaseRef()
98+
const ids = new Map<string, number>()
99+
if (!baseRef) return []
100+
101+
const changedFiles = git(["diff", "--name-only", baseRef, "--", "server/sources"])
102+
.split("\n")
103+
.filter(Boolean)
104+
105+
changedFiles.forEach((file) => {
106+
const sourceFile = /^server\/sources\/(.+?)(?:\/index)?\.ts$/.exec(file)
107+
if (sourceFile) {
108+
setSourceUpdatedAt(ids, sourceFile[1].split("/")[0], getFileUpdatedAt(baseRef, file))
109+
}
110+
})
111+
112+
return [...ids.entries()]
113+
.filter(([id]) => sources[id as keyof typeof sources] && !sources[id as keyof typeof sources].redirect)
114+
.sort(([, m], [, n]) => n - m)
115+
.map(([id]) => id)
116+
} catch {
117+
consola.warn("Skip updated sources: failed to read git info.")
118+
return []
119+
}
120+
}
121+
9122
try {
10123
const pinyinMap = Object.fromEntries(Object.entries(sources)
11124
.filter(([, v]) => !v.redirect)
@@ -25,3 +138,11 @@ try {
25138
} catch {
26139
consola.error("Failed to generate sources.json")
27140
}
141+
142+
try {
143+
const updatedSourceIds = JSON.stringify(getUpdatedSourceIds(), undefined, 2)
144+
writeFileSync(join(projectDir, "./shared/updated-sources.ts"), `export const updatedSourceIds = ${updatedSourceIds} as const\n`)
145+
consola.info("Generated updated-sources.ts")
146+
} catch {
147+
consola.error("Failed to generate updated-sources.ts")
148+
}

shared/metadata.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { sources } from "./sources"
22
import { typeSafeObjectEntries, typeSafeObjectFromEntries } from "./type.util"
3+
import { updatedSourceIds as _updatedSourceIds } from "./updated-sources"
34
import type { ColumnID, HiddenColumnID, Metadata, SourceID } from "./types"
45

56
export const columns = {
@@ -27,9 +28,14 @@ export const columns = {
2728
hottest: {
2829
zh: "最热",
2930
},
31+
updated: {
32+
zh: "更新",
33+
},
3034
} as const
3135

32-
export const fixedColumnIds = ["focus", "hottest", "realtime"] as const satisfies Partial<ColumnID>[]
36+
const updatedSourceIds = [..._updatedSourceIds] as SourceID[]
37+
38+
export const fixedColumnIds = ["focus", "hottest", "realtime", "updated"] as const satisfies Partial<ColumnID>[]
3339
export const hiddenColumns = Object.keys(columns).filter(id => !fixedColumnIds.includes(id as any)) as HiddenColumnID[]
3440

3541
export const metadata: Metadata = typeSafeObjectFromEntries(typeSafeObjectEntries(columns).map(([k, v]) => {
@@ -49,6 +55,11 @@ export const metadata: Metadata = typeSafeObjectFromEntries(typeSafeObjectEntrie
4955
name: v.zh,
5056
sources: typeSafeObjectEntries(sources).filter(([, v]) => v.type === "realtime" && !v.redirect).map(([k]) => k),
5157
}]
58+
case "updated":
59+
return [k, {
60+
name: v.zh,
61+
sources: updatedSourceIds.filter(id => sources[id] && !sources[id].redirect),
62+
}]
5263
default:
5364
return [k, {
5465
name: v.zh,

shared/sources.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import _sources from "./sources.json"
2+
import type { Source, SourceID } from "./types"
23

34
export const sources = _sources as Record<SourceID, Source>
45
export default sources

shared/updated-sources.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
export const updatedSourceIds = [
2+
"dongqiudi",
3+
"producthunt",
4+
"aihot",
5+
"36kr-quick",
6+
"36kr-renqi",
7+
"cankaoxiaoxi",
8+
"cls-telegraph",
9+
"cls-depth",
10+
"cls-hot",
11+
"fastbull-express",
12+
"fastbull-news",
13+
"freebuf",
14+
"jin10",
15+
"kaopu",
16+
"mktnews-flash",
17+
"solidot",
18+
"weibo",
19+
"bilibili-hot-search",
20+
"bilibili-hot-video",
21+
"bilibili-ranking",
22+
"kuaishou",
23+
"toutiao",
24+
] as const

0 commit comments

Comments
 (0)