@@ -96,93 +96,116 @@ async function main( force ) {
9696 }
9797
9898 // Skip download if the gutenberg directory already exists and --force is not set.
99+ let downloaded = false ;
99100 if ( ! force && fs . existsSync ( gutenbergDir ) ) {
100- console . log ( '\n✅ The gutenberg directory already exists. Use --force to re-download.' ) ;
101- return ;
102- }
101+ console . log ( '\nℹ️ The `gutenberg` directory already exists. Use `npm run grunt gutenberg-download -- --force` to download a fresh copy.' ) ;
102+ } else {
103+ downloaded = true ;
104+ const zipName = `gutenberg-${ sha } .zip` ;
105+ const zipPath = path . join ( rootDir , zipName ) ;
106+
107+ // Step 1: Get an anonymous GHCR token for pulling.
108+ console . log ( '\n🔑 Fetching GHCR token...' ) ;
109+ let token ;
110+ try {
111+ const tokenJson = await exec ( 'curl' , [
112+ '--silent' ,
113+ '--fail' ,
114+ `https://ghcr.io/token?scope=repository:${ ghcrRepo } :pull&service=ghcr.io` ,
115+ ] , { captureOutput : true } ) ;
116+ token = JSON . parse ( tokenJson ) . token ;
117+ if ( ! token ) {
118+ throw new Error ( 'No token in response' ) ;
119+ }
120+ console . log ( '✅ Token acquired' ) ;
121+ } catch ( error ) {
122+ console . error ( '❌ Failed to fetch token:' , error . message ) ;
123+ process . exit ( 1 ) ;
124+ }
103125
104- const zipName = `gutenberg-${ sha } .zip` ;
105- const zipPath = path . join ( rootDir , zipName ) ;
126+ // Step 2: Get the manifest to find the blob digest.
127+ console . log ( `\n📋 Fetching manifest for ${ sha } ...` ) ;
128+ let digest ;
129+ try {
130+ const manifestJson = await exec ( 'curl' , [
131+ '--silent' ,
132+ '--fail' ,
133+ '--header' , `Authorization: Bearer ${ token } ` ,
134+ '--header' , 'Accept: application/vnd.oci.image.manifest.v1+json' ,
135+ `https://ghcr.io/v2/${ ghcrRepo } /manifests/${ sha } ` ,
136+ ] , { captureOutput : true } ) ;
137+ const manifest = JSON . parse ( manifestJson ) ;
138+ digest = manifest ?. layers ?. [ 0 ] ?. digest ;
139+ if ( ! digest ) {
140+ throw new Error ( 'No layer digest found in manifest' ) ;
141+ }
142+ console . log ( `✅ Blob digest: ${ digest } ` ) ;
143+ } catch ( error ) {
144+ console . error ( '❌ Failed to fetch manifest:' , error . message ) ;
145+ process . exit ( 1 ) ;
146+ }
106147
107- // Step 1: Get an anonymous GHCR token for pulling.
108- console . log ( '\n🔑 Fetching GHCR token...' ) ;
109- let token ;
110- try {
111- const tokenJson = await exec ( 'curl' , [
112- '--silent' ,
113- '--fail' ,
114- `https://ghcr.io/token?scope=repository:${ ghcrRepo } :pull&service=ghcr.io` ,
115- ] , { captureOutput : true } ) ;
116- token = JSON . parse ( tokenJson ) . token ;
117- if ( ! token ) {
118- throw new Error ( 'No token in response' ) ;
148+ // Step 3: Download the blob (the zip file).
149+ console . log ( `\n📥 Downloading ${ zipName } ...` ) ;
150+ try {
151+ await exec ( 'curl' , [
152+ '--fail' ,
153+ '--location' ,
154+ '--header' , `Authorization: Bearer ${ token } ` ,
155+ '--output' , zipPath ,
156+ `https://ghcr.io/v2/${ ghcrRepo } /blobs/${ digest } ` ,
157+ ] ) ;
158+ console . log ( '✅ Download complete' ) ;
159+ } catch ( error ) {
160+ console . error ( '❌ Download failed:' , error . message ) ;
161+ process . exit ( 1 ) ;
119162 }
120- console . log ( '✅ Token acquired' ) ;
121- } catch ( error ) {
122- console . error ( '❌ Failed to fetch token:' , error . message ) ;
123- process . exit ( 1 ) ;
124- }
125163
126- // Step 2: Get the manifest to find the blob digest.
127- console . log ( `\n📋 Fetching manifest for ${ sha } ...` ) ;
128- let digest ;
129- try {
130- const manifestJson = await exec ( 'curl' , [
131- '--silent' ,
132- '--fail' ,
133- '--header' , `Authorization: Bearer ${ token } ` ,
134- '--header' , 'Accept: application/vnd.oci.image.manifest.v1+json' ,
135- `https://ghcr.io/v2/${ ghcrRepo } /manifests/${ sha } ` ,
136- ] , { captureOutput : true } ) ;
137- const manifest = JSON . parse ( manifestJson ) ;
138- digest = manifest ?. layers ?. [ 0 ] ?. digest ;
139- if ( ! digest ) {
140- throw new Error ( 'No layer digest found in manifest' ) ;
164+ // Remove existing gutenberg directory so the unzip is clean.
165+ if ( fs . existsSync ( gutenbergDir ) ) {
166+ console . log ( '\n🗑️ Removing existing gutenberg directory...' ) ;
167+ fs . rmSync ( gutenbergDir , { recursive : true , force : true } ) ;
141168 }
142- console . log ( `✅ Blob digest: ${ digest } ` ) ;
143- } catch ( error ) {
144- console . error ( '❌ Failed to fetch manifest:' , error . message ) ;
145- process . exit ( 1 ) ;
146- }
147169
148- // Step 3: Download the blob (the zip file).
149- console . log ( `\n📥 Downloading ${ zipName } ...` ) ;
150- try {
151- await exec ( 'curl' , [
152- '--fail' ,
153- '--location' ,
154- '--header' , `Authorization: Bearer ${ token } ` ,
155- '--output' , zipPath ,
156- `https://ghcr.io/v2/${ ghcrRepo } /blobs/${ digest } ` ,
157- ] ) ;
158- console . log ( '✅ Download complete' ) ;
159- } catch ( error ) {
160- console . error ( '❌ Download failed:' , error . message ) ;
161- process . exit ( 1 ) ;
162- }
170+ fs . mkdirSync ( gutenbergDir , { recursive : true } ) ;
163171
164- // Remove existing gutenberg directory so the unzip is clean.
165- if ( fs . existsSync ( gutenbergDir ) ) {
166- console . log ( '\n🗑️ Removing existing gutenberg directory...' ) ;
167- fs . rmSync ( gutenbergDir , { recursive : true , force : true } ) ;
168- }
172+ // Extract the zip into ./gutenberg.
173+ console . log ( `\n📦 Extracting ${ zipName } into ./gutenberg...` ) ;
174+ try {
175+ await exec ( 'unzip' , [ '-q' , zipPath , '-d' , gutenbergDir ] ) ;
176+ console . log ( '✅ Extraction complete' ) ;
177+ } catch ( error ) {
178+ console . error ( '❌ Extraction failed:' , error . message ) ;
179+ process . exit ( 1 ) ;
180+ }
169181
170- fs . mkdirSync ( gutenbergDir , { recursive : true } ) ;
182+ // Clean up the zip file.
183+ fs . rmSync ( zipPath ) ;
184+ }
171185
172- // Extract the zip into ./gutenberg.
173- console . log ( `\n📦 Extracting ${ zipName } into ./gutenberg...` ) ;
186+ // Verify the downloaded version matches the expected SHA.
187+ console . log ( '\n🔍 Verifying Gutenberg version...' ) ;
188+ const hashFilePath = path . join ( gutenbergDir , '.gutenberg-hash' ) ;
174189 try {
175- await exec ( 'unzip' , [ '-q' , zipPath , '-d' , gutenbergDir ] ) ;
176- console . log ( '✅ Extraction complete' ) ;
190+ const installedHash = fs . readFileSync ( hashFilePath , 'utf8' ) . trim ( ) ;
191+ if ( installedHash !== sha ) {
192+ throw new Error (
193+ `SHA mismatch: expected ${ sha } but found ${ installedHash } . Run \`npm run grunt gutenberg-download -- --force\` to download the correct version.`
194+ ) ;
195+ }
196+ console . log ( '✅ Version verified' ) ;
177197 } catch ( error ) {
178- console . error ( '❌ Extraction failed:' , error . message ) ;
198+ if ( error . code === 'ENOENT' ) {
199+ console . error ( `❌ ${ hashFilePath } not found. The downloaded artifact may be malformed.` ) ;
200+ } else {
201+ console . error ( `❌ ${ error . message } ` ) ;
202+ }
179203 process . exit ( 1 ) ;
180204 }
181205
182- // Clean up the zip file.
183- fs . rmSync ( zipPath ) ;
184-
185- console . log ( '\n✅ Gutenberg download complete!' ) ;
206+ if ( downloaded ) {
207+ console . log ( '\n✅ Gutenberg download complete!' ) ;
208+ }
186209}
187210
188211// Run main function
0 commit comments