1
+ import type { EmojiVersion } from "./lockfile" ;
1
2
import semver from "semver" ;
2
3
import { NO_EMOJI_VERSIONS } from "./constants" ;
3
4
@@ -37,10 +38,10 @@ export function slugify(val: string): string {
37
38
* 4. Normalizes version numbers to valid semver format
38
39
*
39
40
* @throws {Error } When either the root or emoji page fetch fails
40
- * @returns {Promise<string []> } A promise that resolves to an array of emoji versions,
41
+ * @returns {Promise<EmojiVersion []> } A promise that resolves to an array of emoji versions,
41
42
* sorted according to semver rules
42
43
*/
43
- export async function getAllEmojiVersions ( ) : Promise < string [ ] > {
44
+ export async function getAllEmojiVersions ( ) : Promise < EmojiVersion [ ] > {
44
45
const [ rootResult , emojiResult ] = await Promise . allSettled ( [
45
46
"https://unicode.org/Public/" ,
46
47
"https://unicode.org/Public/emoji/" ,
@@ -72,7 +73,9 @@ export async function getAllEmojiVersions(): Promise<string[]> {
72
73
73
74
const versionRegex = / h r e f = " ( \d + \. \d + (?: \. \d + ) ? ) \/ ? " / g;
74
75
75
- const versions = new Set < string > ( ) ;
76
+ const draft = await getCurrentDraftVersion ( ) ;
77
+
78
+ const versions : EmojiVersion [ ] = [ ] ;
76
79
77
80
for ( const match of rootHtml . matchAll ( versionRegex ) ) {
78
81
if ( match == null || match [ 1 ] == null ) continue ;
@@ -83,15 +86,23 @@ export async function getAllEmojiVersions(): Promise<string[]> {
83
86
continue ;
84
87
}
85
88
86
- versions . add ( version ) ;
89
+ if ( versions . some ( ( v ) => v . unicode_version === version ) ) {
90
+ continue ;
91
+ }
92
+
93
+ versions . push ( {
94
+ emoji_version : null ,
95
+ unicode_version : version ,
96
+ draft : version === draft ,
97
+ } ) ;
87
98
}
88
99
89
100
for ( const match of emojiHtml . matchAll ( versionRegex ) ) {
90
101
if ( match == null || match [ 1 ] == null ) continue ;
91
102
92
103
let version = match [ 1 ] ;
93
104
94
- // for this emoji page, the versions is not valid semver.
105
+ // for the emoji page, the versions is not valid semver.
95
106
// so we will add the last 0 to the version.
96
107
// handle both 5.0 and 12.0 -> 5.0.0 and 12.0.0
97
108
if ( version . length === 3 || version . length === 4 ) {
@@ -102,10 +113,36 @@ export async function getAllEmojiVersions(): Promise<string[]> {
102
113
continue ;
103
114
}
104
115
105
- versions . add ( version ) ;
116
+ // check if the unicode_version already exists.
117
+ // if it does, we will update the emoji version.
118
+ const existing = versions . find ( ( v ) => v . unicode_version === version ) ;
119
+
120
+ if ( existing ) {
121
+ existing . emoji_version = match [ 1 ] ;
122
+ continue ;
123
+ }
124
+
125
+ versions . push ( {
126
+ emoji_version : match [ 1 ] ,
127
+ unicode_version : null ,
128
+ draft : version === draft ,
129
+ } ) ;
106
130
}
107
131
108
- return Array . from ( versions ) . sort ( semver . compare ) ;
132
+ return versions . sort ( ( a , b ) => {
133
+ // if unicode version is null, it means it is from the emoji page.
134
+ // which contains emoji versions, in major.minor format.
135
+ // so, we will add the last 0 to the version, to be able to compare them.
136
+ if ( a . unicode_version == null ) {
137
+ a . unicode_version = `${ a . emoji_version } .0` ;
138
+ }
139
+
140
+ if ( b . unicode_version == null ) {
141
+ b . unicode_version = `${ b . emoji_version } .0` ;
142
+ }
143
+
144
+ return semver . compare ( b . unicode_version , a . unicode_version ) ;
145
+ } ) ;
109
146
}
110
147
111
148
/**
@@ -143,3 +180,70 @@ export async function isEmojiVersionValid(version: string): Promise<boolean> {
143
180
144
181
return true ;
145
182
}
183
+
184
+ export async function getCurrentDraftVersion ( ) : Promise < string | null > {
185
+ const [ rootResult , emojiResult ] = await Promise . allSettled ( [
186
+ "https://unicode.org/Public/draft/ReadMe.txt" ,
187
+ "https://unicode.org/Public/draft/emoji/ReadMe.txt" ,
188
+ ] . map ( async ( url ) => {
189
+ const res = await fetch ( url ) ;
190
+
191
+ if ( ! res . ok ) {
192
+ throw new Error ( `failed to fetch ${ url } : ${ res . statusText } ` ) ;
193
+ }
194
+
195
+ return res . text ( ) ;
196
+ } ) ) ;
197
+
198
+ if ( rootResult == null || emojiResult == null ) {
199
+ throw new Error ( "failed to fetch draft readme or draft emoji readme" ) ;
200
+ }
201
+
202
+ if ( rootResult . status === "rejected" || emojiResult . status === "rejected" ) {
203
+ console . error ( {
204
+ root : rootResult . status === "rejected" ? rootResult . reason : "ok" ,
205
+ emoji : emojiResult . status === "rejected" ? emojiResult . reason : "ok" ,
206
+ } ) ;
207
+
208
+ throw new Error ( "failed to fetch draft readme or draft emoji readme" ) ;
209
+ }
210
+
211
+ const draftText = rootResult . value ;
212
+ const emojiText = emojiResult . value ;
213
+
214
+ const rootVersion = extractVersion ( draftText ) ;
215
+ const emojiVersion = extractVersion ( emojiText ) ;
216
+
217
+ if ( rootVersion == null || emojiVersion == null ) {
218
+ throw new Error ( "failed to extract draft version" ) ;
219
+ }
220
+
221
+ // the emoji version is only using major.minor format.
222
+ // so, we will need to add the last 0 to the version.
223
+
224
+ // if they don't match the major and minor version, we will throw an error.
225
+ if ( semver . major ( rootVersion ) !== semver . major ( `${ emojiVersion } .0` ) || semver . minor ( rootVersion ) !== semver . minor ( `${ emojiVersion } .0` ) ) {
226
+ throw new Error ( "draft versions do not match" ) ;
227
+ }
228
+
229
+ return rootVersion ;
230
+ }
231
+
232
+ function extractVersion ( text : string ) : string | null {
233
+ const patterns = [
234
+ / V e r s i o n ( \d + \. \d + (?: \. \d + ) ? ) o f t h e U n i c o d e S t a n d a r d / , // Most explicit
235
+ / U n i c o d e ( \d + \. \d + (?: \. \d + ) ? ) / , // From URLs
236
+ / V e r s i o n ( \d + \. \d + ) (? ! \. \d ) / , // Bare major.minor format
237
+ / U n i c o d e E m o j i , V e r s i o n ( \d + \. \d + (?: \. \d + ) ? ) / , // Emoji-specific version
238
+ ] ;
239
+
240
+ for ( const pattern of patterns ) {
241
+ const match = text . match ( pattern ) ;
242
+
243
+ if ( match == null || match [ 1 ] == null ) continue ;
244
+
245
+ return match [ 1 ] ;
246
+ }
247
+
248
+ return null ;
249
+ }
0 commit comments