Skip to content

Commit edd0103

Browse files
committed
Use full version tags (v0.16.0, v0.16.4) instead of minor versions
1 parent c8fe63b commit edd0103

7 files changed

Lines changed: 106 additions & 100 deletions

File tree

scripts/extract-versions.py

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,15 @@ def version_gte(version: str, min_version: str) -> bool:
101101

102102

103103
def get_cached_versions(package_id: str) -> set[str]:
104-
"""Get set of already cached version strings for a package."""
104+
"""Get set of already cached version tags for a package."""
105105
package_dir = VERSIONS_DIR / package_id
106106
if not package_dir.exists():
107107
return set()
108108

109109
cached = set()
110110
for f in package_dir.glob("v*.json"):
111-
match = re.match(r"^v(\d+\.\d+)\.json$", f.name)
111+
# Match full version tags like v0.16.0.json or v0.16.4.json
112+
match = re.match(r"^(v\d+\.\d+\.\d+)\.json$", f.name)
112113
if match:
113114
cached.add(match.group(1))
114115
return cached
@@ -243,67 +244,68 @@ def extract_package_versions(
243244
print(f" Historical vX.Y.0 versions: {sorted(supported_historical.keys(), key=parse_minor_version, reverse=True)}")
244245

245246
# Determine which versions to extract
247+
# Now using full tags (e.g., v0.16.0, v0.16.4) not just minor versions
246248
cached = get_cached_versions(package_id)
247249

248-
# Build extraction list
249-
versions_to_extract = []
250+
# Build extraction list: all vX.Y.0 tags + latest patch if different
251+
versions_to_extract = [] # List of (tag, is_latest) tuples
250252

251253
if extract_all:
252-
# Extract all historical vX.Y.0 versions
254+
# Extract all vX.Y.0 versions
253255
for minor_version, tag in sorted(supported_historical.items(), key=lambda x: parse_minor_version(x[0]), reverse=True):
254-
# Skip if this is the latest minor (we'll add the actual latest tag separately)
255-
if minor_version != latest_minor:
256-
versions_to_extract.append((minor_version, tag))
257-
print(f" Mode: Extract ALL historical ({len(versions_to_extract)} versions) + latest")
256+
versions_to_extract.append((tag, False))
257+
# Add latest if it's a patch release (not .0)
258+
if latest_tag not in [t for t, _ in versions_to_extract]:
259+
versions_to_extract.append((latest_tag, True))
260+
print(f" Mode: Extract ALL ({len(versions_to_extract)} versions)")
258261
else:
259-
# Smart mode: only missing historical + latest
262+
# Smart mode: only missing .0 versions + always re-extract latest
260263
for minor_version, tag in sorted(supported_historical.items(), key=lambda x: parse_minor_version(x[0]), reverse=True):
261-
# Skip if this is the latest minor (we'll add the actual latest tag separately)
262-
if minor_version == latest_minor:
263-
continue
264-
if minor_version not in cached:
265-
versions_to_extract.append((minor_version, tag))
264+
if tag not in cached:
265+
versions_to_extract.append((tag, False))
266266

267-
missing_count = len(versions_to_extract)
268-
print(f" Mode: Smart extraction")
269-
print(f" Cached: {len(cached)} versions")
270-
print(f" Missing historical: {missing_count} versions")
267+
# Always add latest (will be re-extracted)
268+
# Mark as latest so we know to always extract it
269+
versions_to_extract.append((latest_tag, True))
271270

272-
# Always add the latest tag (uses actual latest, not necessarily vX.Y.0)
273-
versions_to_extract.append((latest_minor, latest_tag))
274-
print(f" Latest ({latest_tag}): will {'re-' if latest_minor in cached else ''}extract")
275-
print(f" Total to extract: {len(versions_to_extract)} versions")
271+
missing_count = len([t for t, is_latest in versions_to_extract if not is_latest])
272+
print(f" Mode: Smart extraction")
273+
print(f" Cached: {len(cached)} tags")
274+
print(f" Missing .0 releases: {missing_count}")
275+
print(f" Latest ({latest_tag}): will {'re-' if latest_tag in cached else ''}extract")
276+
print(f" Total to extract: {len(versions_to_extract)} versions")
276277

277278
extracted = []
278279
output_dir = VERSIONS_DIR / package_id
279280
output_dir.mkdir(parents=True, exist_ok=True)
280281

281-
for minor_version, tag in versions_to_extract:
282-
is_latest = minor_version == latest_minor
283-
is_cached = minor_version in cached
282+
for tag, is_latest in versions_to_extract:
283+
is_cached = tag in cached
284284
status = "LATEST" if is_latest else ("NEW" if not is_cached else "cached")
285+
# Extract version string from tag (e.g., "v0.16.4" -> "0.16.4")
286+
version_str = tag[1:] if tag.startswith('v') else tag
285287

286-
print(f"\n Version {minor_version} (tag: {tag}) [{status}]")
288+
print(f"\n {tag} [{status}]")
287289

288290
if dry_run:
289-
print(f" Would extract to: {output_dir / f'v{minor_version}.json'}")
291+
print(f" Would extract to: {output_dir / f'{tag}.json'}")
290292
extracted.append({
291-
"version": minor_version,
292293
"tag": tag,
293294
"released": "",
295+
"latest": is_latest,
294296
})
295297
continue
296298

297299
# Checkout the tag
298300
if not git_checkout(repo_path, tag):
299301
continue
300302

301-
# Run extraction
302-
if run_extraction(package_id, minor_version, VERSIONS_DIR):
303+
# Run extraction - pass full version string (e.g., "0.16.4")
304+
if run_extraction(package_id, version_str, VERSIONS_DIR):
303305
extracted.append({
304-
"version": minor_version,
305306
"tag": tag,
306307
"released": get_tag_date(repo_path, tag),
308+
"latest": is_latest,
307309
})
308310
print(f" ✓ Extracted successfully")
309311
else:

scripts/generate-manifests.py

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,27 +30,27 @@
3030
}
3131

3232

33-
def parse_version(version: str) -> tuple[int, int]:
34-
"""Parse version string into (major, minor) tuple."""
35-
match = re.match(r"^(\d+)\.(\d+)$", version)
33+
def parse_version(version: str) -> tuple[int, int, int]:
34+
"""Parse version string into (major, minor, patch) tuple."""
35+
match = re.match(r"^(\d+)\.(\d+)\.(\d+)$", version)
3636
if not match:
37-
return (0, 0)
38-
return (int(match.group(1)), int(match.group(2)))
37+
return (0, 0, 0)
38+
return (int(match.group(1)), int(match.group(2)), int(match.group(3)))
3939

4040

4141
def get_version_files(package_dir: Path) -> list[str]:
4242
"""Get all version JSON files in a package directory."""
4343
if not package_dir.exists():
4444
return []
4545

46-
versions = []
46+
tags = []
4747
for f in package_dir.glob("v*.json"):
48-
# Extract version from filename (e.g., "v0.16.json" -> "0.16")
49-
match = re.match(r"^v(\d+\.\d+)\.json$", f.name)
48+
# Extract full tag from filename (e.g., "v0.16.4.json" -> "v0.16.4")
49+
match = re.match(r"^(v\d+\.\d+\.\d+)\.json$", f.name)
5050
if match:
51-
versions.append(match.group(1))
51+
tags.append(match.group(1))
5252

53-
return versions
53+
return tags
5454

5555

5656
def get_tag_for_version(repo_path: Path, version: str) -> str:
@@ -105,36 +105,38 @@ def get_tag_date(repo_path: Path, tag: str) -> str:
105105
def generate_manifest(package_id: str, dry_run: bool = False) -> dict[str, Any] | None:
106106
"""Generate manifest for a single package."""
107107
package_dir = VERSIONS_DIR / package_id
108-
versions = get_version_files(package_dir)
108+
tags = get_version_files(package_dir)
109109

110-
if not versions:
110+
if not tags:
111111
print(f" No version files found in {package_dir}")
112112
return None
113113

114-
# Sort versions (newest first)
115-
sorted_versions = sorted(versions, key=parse_version, reverse=True)
116-
latest_version = sorted_versions[0]
114+
# Sort tags by version (newest first)
115+
def tag_to_version(tag: str) -> tuple[int, int, int]:
116+
version_str = tag[1:] if tag.startswith('v') else tag
117+
return parse_version(version_str)
117118

118-
print(f" Found {len(versions)} versions: {sorted_versions}")
119-
print(f" Latest: {latest_version}")
119+
sorted_tags = sorted(tags, key=tag_to_version, reverse=True)
120+
latest_tag = sorted_tags[0]
121+
122+
print(f" Found {len(tags)} versions: {sorted_tags}")
123+
print(f" Latest: {latest_tag}")
120124

121125
# Build version info list
122126
repo_path = PACKAGE_REPOS.get(package_id)
123127
version_infos = []
124128

125-
for version in sorted_versions:
126-
tag = get_tag_for_version(repo_path, version) if repo_path else f"v{version}.0"
129+
for tag in sorted_tags:
127130
released = get_tag_date(repo_path, tag) if repo_path else ""
128131

129132
version_infos.append({
130-
"version": version,
131133
"tag": tag,
132134
"released": released,
133135
})
134136

135137
manifest = {
136138
"package": package_id,
137-
"latestVersion": latest_version,
139+
"latestTag": latest_tag,
138140
"versions": version_infos,
139141
}
140142

src/lib/api/versions.ts

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@ import { base } from '$app/paths';
33
import type { APIPackage } from './generated';
44

55
export interface VersionInfo {
6-
version: string;
76
tag: string;
87
released: string;
98
}
109

1110
export interface PackageManifest {
1211
package: string;
13-
latestVersion: string;
12+
latestTag: string;
1413
versions: VersionInfo[];
1514
}
1615

@@ -32,39 +31,41 @@ export async function getVersionManifest(
3231
}
3332

3433
/**
35-
* Fetch versioned API data for a specific package version
34+
* Fetch versioned API data for a specific package version tag
3635
*/
3736
export async function getVersionedApiData(
3837
packageId: string,
39-
version: string,
38+
tag: string,
4039
fetch: typeof globalThis.fetch
4140
): Promise<APIPackage> {
42-
const url = `${base}/api/versions/${packageId}/v${version}.json`;
41+
// Ensure tag has 'v' prefix
42+
const normalizedTag = tag.startsWith('v') ? tag : `v${tag}`;
43+
const url = `${base}/api/versions/${packageId}/${normalizedTag}.json`;
4344
const response = await fetch(url);
4445

4546
if (!response.ok) {
46-
throw new Error(`Failed to load API data for ${packageId} v${version}: ${response.status}`);
47+
throw new Error(`Failed to load API data for ${packageId} ${normalizedTag}: ${response.status}`);
4748
}
4849

4950
return response.json();
5051
}
5152

5253
/**
53-
* Resolve a version string to an actual version number
54-
* - 'latest' -> actual latest version from manifest
55-
* - undefined -> actual latest version from manifest
56-
* - 'v0.16' or '0.16' -> '0.16'
54+
* Resolve a version/tag string to an actual tag
55+
* - 'latest' -> actual latest tag from manifest
56+
* - undefined -> actual latest tag from manifest
57+
* - 'v0.16.4' or '0.16.4' -> 'v0.16.4'
5758
*/
58-
export function resolveVersion(version: string | undefined, manifest: PackageManifest): string {
59-
if (!version || version === 'latest') {
60-
return manifest.latestVersion;
59+
export function resolveTag(tagOrVersion: string | undefined, manifest: PackageManifest): string {
60+
if (!tagOrVersion || tagOrVersion === 'latest') {
61+
return manifest.latestTag;
6162
}
6263

63-
// Strip leading 'v' if present
64-
const normalized = version.startsWith('v') ? version.slice(1) : version;
64+
// Normalize to have 'v' prefix
65+
const normalized = tagOrVersion.startsWith('v') ? tagOrVersion : `v${tagOrVersion}`;
6566

66-
// Validate the version exists in manifest
67-
const exists = manifest.versions.some((v) => v.version === normalized);
67+
// Validate the tag exists in manifest
68+
const exists = manifest.versions.some((v) => v.tag === normalized);
6869
if (!exists) {
6970
throw new Error(`Version ${normalized} not found for ${manifest.package}`);
7071
}
@@ -73,18 +74,19 @@ export function resolveVersion(version: string | undefined, manifest: PackageMan
7374
}
7475

7576
/**
76-
* Check if a version is the latest version
77+
* Check if a tag is the latest tag
7778
*/
78-
export function isLatestVersion(version: string, manifest: PackageManifest): boolean {
79-
return version === manifest.latestVersion;
79+
export function isLatestTag(tag: string, manifest: PackageManifest): boolean {
80+
const normalized = tag.startsWith('v') ? tag : `v${tag}`;
81+
return normalized === manifest.latestTag;
8082
}
8183

8284
/**
83-
* Get the display label for a version
85+
* Get the display label for a tag
8486
*/
85-
export function getVersionLabel(version: string, manifest: PackageManifest): string {
86-
if (isLatestVersion(version, manifest)) {
87-
return `v${version} (latest)`;
87+
export function getTagLabel(tag: string, manifest: PackageManifest): string {
88+
if (isLatestTag(tag, manifest)) {
89+
return `${tag} (latest)`;
8890
}
89-
return `v${version}`;
91+
return tag;
9092
}

src/lib/components/api/VersionSelector.svelte

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
import { base } from '$app/paths';
44
import Icon from '$lib/components/common/Icon.svelte';
55
import type { PackageManifest } from '$lib/api/versions';
6-
import { isLatestVersion } from '$lib/api/versions';
6+
import { isLatestTag } from '$lib/api/versions';
77
88
interface Props {
99
packageId: string;
10-
currentVersion: string;
10+
currentTag: string;
1111
manifest: PackageManifest;
1212
}
1313
14-
let { packageId, currentVersion, manifest }: Props = $props();
14+
let { packageId, currentTag, manifest }: Props = $props();
1515
1616
let isOpen = $state(false);
1717
let dropdownRef: HTMLDivElement | null = $state(null);
@@ -20,14 +20,15 @@
2020
isOpen = !isOpen;
2121
}
2222
23-
function selectVersion(version: string) {
23+
function selectTag(tag: string) {
2424
isOpen = false;
2525
2626
// Preserve current hash if it exists
2727
const hash = typeof window !== 'undefined' ? window.location.hash : '';
2828
29-
// Navigate to the new version
30-
const versionPath = isLatestVersion(version, manifest) ? '' : `/v${version}`;
29+
// Navigate to the new version (strip 'v' prefix for URL)
30+
const versionStr = tag.startsWith('v') ? tag.slice(1) : tag;
31+
const versionPath = isLatestTag(tag, manifest) ? '' : `/${tag}`;
3132
goto(`${base}/${packageId}/api${versionPath}${hash}`);
3233
}
3334
@@ -56,21 +57,20 @@
5657
</script>
5758

5859
<div class="version-selector" bind:this={dropdownRef}>
59-
{@const currentInfo = manifest.versions.find(v => v.version === currentVersion)}
6060
<button class="version-trigger" onclick={toggleDropdown} aria-expanded={isOpen}>
61-
<span class="version-text">{currentInfo?.tag ?? `v${currentVersion}`}</span>
61+
<span class="version-text">{currentTag}</span>
6262
<Icon name="chevron-down" size={10} />
6363
</button>
6464

6565
{#if isOpen}
6666
<div class="dropdown">
6767
{#each manifest.versions as v}
68-
{@const isSelected = v.version === currentVersion}
69-
{@const isLatest = isLatestVersion(v.version, manifest)}
68+
{@const isSelected = v.tag === currentTag}
69+
{@const isLatest = isLatestTag(v.tag, manifest)}
7070
<button
7171
class="dropdown-item"
7272
class:selected={isSelected}
73-
onclick={() => selectVersion(v.version)}
73+
onclick={() => selectTag(v.tag)}
7474
>
7575
<span class="dropdown-version">{v.tag}</span>
7676
{#if isLatest}

0 commit comments

Comments
 (0)