1- import fs from "node:fs" ;
2- import os from "node:os" ;
31import path from "node:path" ;
42
3+ import { ensureGuestAssets } from "@earendil-works/gondolin" ;
4+
55import { CliUsageError } from "./cli-errors" ;
66
77export const TESTED_GONDOLIN_VERSION = "0.2.1" ;
@@ -13,163 +13,23 @@ export interface GondolinGuestAssets {
1313 rootfsPath : string ;
1414}
1515
16- type AssetFileNames = {
17- kernel : string ;
18- initramfs : string ;
19- rootfs : string ;
20- } ;
21-
22- const DEFAULT_FILE_NAMES : AssetFileNames = {
23- kernel : "vmlinuz-virt" ,
24- initramfs : "initramfs.cpio.lz4" ,
25- rootfs : "rootfs.ext4" ,
26- } ;
27-
28- export function resolveGondolinGuestAssets ( ) : GondolinGuestAssets {
29- const explicitDir = process . env . GONDOLIN_GUEST_DIR ;
30- if ( explicitDir && explicitDir . trim ( ) . length > 0 ) {
31- return loadAssetsFromDirectory ( path . resolve ( explicitDir ) , "GONDOLIN_GUEST_DIR" ) ;
32- }
33-
34- for ( const candidateDir of discoverCachedGuestAssetDirectories ( ) ) {
35- const loaded = tryLoadAssetsFromDirectory ( candidateDir ) ;
36- if ( loaded ) {
37- return loaded ;
38- }
39- }
40-
41- throw new CliUsageError ( "Gondolin guest assets were not found." , [
42- "Install gondolin CLI separately (tested with @earendil-works/gondolin@0.2.1)." ,
43- "Run once to populate guest assets: gondolin exec -- /bin/true" ,
44- "Or set GONDOLIN_GUEST_DIR to a directory containing: vmlinuz-virt, initramfs.cpio.lz4, rootfs.ext4." ,
45- "Expected cache location: ~/.cache/gondolin/<version>/" ,
46- ] ) ;
47- }
48-
49- function discoverCachedGuestAssetDirectories ( ) : string [ ] {
50- const cacheBase = process . env . XDG_CACHE_HOME ?? path . join ( os . homedir ( ) , ".cache" ) ;
51- const gondolinCacheRoot = path . resolve ( cacheBase , "gondolin" ) ;
52-
53- if ( ! fs . existsSync ( gondolinCacheRoot ) ) {
54- return [ ] ;
55- }
56-
57- const dirs = fs
58- . readdirSync ( gondolinCacheRoot , { withFileTypes : true } )
59- . filter ( ( entry ) => entry . isDirectory ( ) )
60- . map ( ( entry ) => path . join ( gondolinCacheRoot , entry . name ) ) ;
61-
62- dirs . sort ( ( a , b ) => compareCacheDirectoryPriority ( path . basename ( a ) , path . basename ( b ) ) ) ;
63-
64- return dirs ;
65- }
66-
67- function compareCacheDirectoryPriority ( a : string , b : string ) : number {
68- const parsedA = parseSemverTag ( a ) ;
69- const parsedB = parseSemverTag ( b ) ;
70-
71- if ( parsedA && parsedB ) {
72- for ( let i = 0 ; i < parsedA . length ; i += 1 ) {
73- if ( parsedA [ i ] !== parsedB [ i ] ) {
74- return parsedB [ i ] - parsedA [ i ] ;
75- }
76- }
77- return 0 ;
78- }
79-
80- if ( parsedA ) {
81- return - 1 ;
82- }
83-
84- if ( parsedB ) {
85- return 1 ;
86- }
87-
88- return b . localeCompare ( a ) ;
89- }
90-
91- function parseSemverTag ( value : string ) : [ number , number , number ] | null {
92- const match = / ^ v ? ( \d + ) \. ( \d + ) \. ( \d + ) $ / . exec ( value . trim ( ) ) ;
93- if ( ! match ) {
94- return null ;
95- }
96-
97- return [ Number ( match [ 1 ] ) , Number ( match [ 2 ] ) , Number ( match [ 3 ] ) ] ;
98- }
99-
100- function tryLoadAssetsFromDirectory ( candidateDir : string ) : GondolinGuestAssets | null {
101- try {
102- return loadAssetsFromDirectory ( candidateDir , "cache" ) ;
103- } catch {
104- return null ;
105- }
106- }
107-
108- function loadAssetsFromDirectory ( assetDir : string , source : "GONDOLIN_GUEST_DIR" | "cache" ) : GondolinGuestAssets {
109- const fileNames = resolveAssetFileNames ( assetDir ) ;
110-
111- const kernelPath = path . join ( assetDir , fileNames . kernel ) ;
112- const initrdPath = path . join ( assetDir , fileNames . initramfs ) ;
113- const rootfsPath = path . join ( assetDir , fileNames . rootfs ) ;
114-
115- const missing = [
116- [ fileNames . kernel , kernelPath ] ,
117- [ fileNames . initramfs , initrdPath ] ,
118- [ fileNames . rootfs , rootfsPath ] ,
119- ]
120- . filter ( ( [ , filePath ] ) => ! fs . existsSync ( filePath ) )
121- . map ( ( [ name ] ) => name ) ;
122-
123- if ( missing . length > 0 ) {
124- const hint =
125- source === "GONDOLIN_GUEST_DIR"
126- ? "Verify GONDOLIN_GUEST_DIR points to a valid gondolin guest asset directory."
127- : "Run 'gondolin exec -- /bin/true' to download guest assets into the cache." ;
128-
129- throw new CliUsageError ( "Gondolin guest assets are incomplete." , [
130- `Directory: ${ assetDir } ` ,
131- `Missing files: ${ missing . join ( ", " ) } ` ,
132- hint ,
133- ] ) ;
134- }
135-
136- return {
137- assetDir,
138- kernelPath,
139- initrdPath,
140- rootfsPath,
141- } ;
142- }
143-
144- function resolveAssetFileNames ( assetDir : string ) : AssetFileNames {
145- const manifestPath = path . join ( assetDir , "manifest.json" ) ;
146- if ( ! fs . existsSync ( manifestPath ) ) {
147- return DEFAULT_FILE_NAMES ;
148- }
149-
16+ export async function resolveGondolinGuestAssets ( ) : Promise < GondolinGuestAssets > {
15017 try {
151- const raw = fs . readFileSync ( manifestPath , "utf8" ) ;
152- const parsed = JSON . parse ( raw ) as {
153- assets ?: {
154- kernel ?: unknown ;
155- initramfs ?: unknown ;
156- rootfs ?: unknown ;
157- } ;
158- } ;
159-
160- const kernel = typeof parsed . assets ?. kernel === "string" ? parsed . assets . kernel : DEFAULT_FILE_NAMES . kernel ;
161- const initramfs =
162- typeof parsed . assets ?. initramfs === "string"
163- ? parsed . assets . initramfs
164- : DEFAULT_FILE_NAMES . initramfs ;
165- const rootfs = typeof parsed . assets ?. rootfs === "string" ? parsed . assets . rootfs : DEFAULT_FILE_NAMES . rootfs ;
18+ const assets = await ensureGuestAssets ( ) ;
16619
16720 return {
168- kernel,
169- initramfs,
170- rootfs,
21+ assetDir : path . dirname ( assets . rootfsPath ) ,
22+ kernelPath : assets . kernelPath ,
23+ initrdPath : assets . initrdPath ,
24+ rootfsPath : assets . rootfsPath ,
17125 } ;
172- } catch {
173- return DEFAULT_FILE_NAMES ;
26+ } catch ( error ) {
27+ const message = error instanceof Error ? error . message : String ( error ) ;
28+
29+ throw new CliUsageError ( "Failed to resolve Gondolin guest assets." , [
30+ message ,
31+ `Ensure @earendil-works/gondolin@${ TESTED_GONDOLIN_VERSION } is installed.` ,
32+ "To use custom assets, set GONDOLIN_GUEST_DIR to a directory containing kernel/initramfs/rootfs assets." ,
33+ ] ) ;
17434 }
17535}
0 commit comments