Skip to content
This repository was archived by the owner on Mar 1, 2025. It is now read-only.

Commit eec4426

Browse files
committed
feat: enhance emoji version handling and add draft version retrieval
1 parent d6f2e84 commit eec4426

File tree

1 file changed

+111
-7
lines changed

1 file changed

+111
-7
lines changed

src/utils.ts

Lines changed: 111 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { EmojiVersion } from "./lockfile";
12
import semver from "semver";
23
import { NO_EMOJI_VERSIONS } from "./constants";
34

@@ -37,10 +38,10 @@ export function slugify(val: string): string {
3738
* 4. Normalizes version numbers to valid semver format
3839
*
3940
* @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,
4142
* sorted according to semver rules
4243
*/
43-
export async function getAllEmojiVersions(): Promise<string[]> {
44+
export async function getAllEmojiVersions(): Promise<EmojiVersion[]> {
4445
const [rootResult, emojiResult] = await Promise.allSettled([
4546
"https://unicode.org/Public/",
4647
"https://unicode.org/Public/emoji/",
@@ -72,7 +73,9 @@ export async function getAllEmojiVersions(): Promise<string[]> {
7273

7374
const versionRegex = /href="(\d+\.\d+(?:\.\d+)?)\/?"/g;
7475

75-
const versions = new Set<string>();
76+
const draft = await getCurrentDraftVersion();
77+
78+
const versions: EmojiVersion[] = [];
7679

7780
for (const match of rootHtml.matchAll(versionRegex)) {
7881
if (match == null || match[1] == null) continue;
@@ -83,15 +86,23 @@ export async function getAllEmojiVersions(): Promise<string[]> {
8386
continue;
8487
}
8588

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+
});
8798
}
8899

89100
for (const match of emojiHtml.matchAll(versionRegex)) {
90101
if (match == null || match[1] == null) continue;
91102

92103
let version = match[1];
93104

94-
// for this emoji page, the versions is not valid semver.
105+
// for the emoji page, the versions is not valid semver.
95106
// so we will add the last 0 to the version.
96107
// handle both 5.0 and 12.0 -> 5.0.0 and 12.0.0
97108
if (version.length === 3 || version.length === 4) {
@@ -102,10 +113,36 @@ export async function getAllEmojiVersions(): Promise<string[]> {
102113
continue;
103114
}
104115

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+
});
106130
}
107131

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+
});
109146
}
110147

111148
/**
@@ -143,3 +180,70 @@ export async function isEmojiVersionValid(version: string): Promise<boolean> {
143180

144181
return true;
145182
}
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+
/Version (\d+\.\d+(?:\.\d+)?) of the Unicode Standard/, // Most explicit
235+
/Unicode(\d+\.\d+(?:\.\d+)?)/, // From URLs
236+
/Version (\d+\.\d+)(?!\.\d)/, // Bare major.minor format
237+
/Unicode Emoji, Version (\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

Comments
 (0)