11import { writeFileSync } from "node:fs"
22import { join } from "node:path"
3+ import { execFileSync } from "node:child_process"
34import { pinyin } from "@napi-rs/pinyin"
45import { consola } from "consola"
56import { projectDir } from "../shared/dir"
67import { genSources } from "../shared/pre-sources"
8+ import packageJSON from "../package.json"
79
810const 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 = / ^ s e r v e r \/ s o u r c e s \/ ( .+ ?) (?: \/ i n d e x ) ? \. t s $ / . 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+
9122try {
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+ }
0 commit comments