44 * Download Gutenberg Repository Script.
55 *
66 * This script downloads a pre-built Gutenberg tar.gz artifact from the GitHub
7- * Container Registry and extracts it into the ./gutenberg directory.
7+ * Container Registry and extracts it into the ./gutenberg directory. Any
8+ * existing gutenberg directory is removed before extraction.
89 *
910 * The artifact is identified by the "gutenberg.sha" value in the root
1011 * package.json, which is used as the OCI image tag for the gutenberg-build
@@ -17,16 +18,13 @@ const { spawn } = require( 'child_process' );
1718const fs = require ( 'fs' ) ;
1819const { Writable } = require ( 'stream' ) ;
1920const { pipeline } = require ( 'stream/promises' ) ;
20- const path = require ( 'path' ) ;
2121const zlib = require ( 'zlib' ) ;
22- const { gutenbergDir, readGutenbergConfig, verifyGutenbergVersion } = require ( './utils' ) ;
22+ const { gutenbergDir, readGutenbergConfig } = require ( './utils' ) ;
2323
2424/**
2525 * Main execution function.
26- *
27- * @param {boolean } force - Whether to force a fresh download even if the gutenberg directory exists.
2826 */
29- async function main ( force ) {
27+ async function main ( ) {
3028 console . log ( '🔍 Checking Gutenberg configuration...' ) ;
3129
3230 /*
@@ -45,129 +43,115 @@ async function main( force ) {
4543 process . exit ( 1 ) ;
4644 }
4745
48- // Skip download if the gutenberg directory already exists and --force is not set.
49- let downloaded = false ;
50- if ( ! force && fs . existsSync ( gutenbergDir ) ) {
51- console . log ( '\nℹ️ The `gutenberg` directory already exists. Use `npm run grunt gutenberg:download -- --force` to download a fresh copy.' ) ;
52- } else {
53- downloaded = true ;
54-
55- // Step 1: Get an anonymous GHCR token for pulling.
56- console . log ( '\n🔑 Fetching GHCR token...' ) ;
57- let token ;
58- try {
59- const response = await fetch ( `https://ghcr.io/token?scope=repository:${ ghcrRepo } :pull&service=ghcr.io` ) ;
60- if ( ! response . ok ) {
61- throw new Error ( `Failed to fetch token: ${ response . status } ${ response . statusText } ` ) ;
62- }
63- const data = await response . json ( ) ;
64- token = data . token ;
65- if ( ! token ) {
66- throw new Error ( 'No token in response' ) ;
67- }
68- console . log ( '✅ Token acquired' ) ;
69- } catch ( error ) {
70- console . error ( '❌ Failed to fetch token:' , error . message ) ;
71- process . exit ( 1 ) ;
46+ // Step 1: Get an anonymous GHCR token for pulling.
47+ console . log ( '\n🔑 Fetching GHCR token...' ) ;
48+ let token ;
49+ try {
50+ const response = await fetch ( `https://ghcr.io/token?scope=repository:${ ghcrRepo } :pull&service=ghcr.io` ) ;
51+ if ( ! response . ok ) {
52+ throw new Error ( `Failed to fetch token: ${ response . status } ${ response . statusText } ` ) ;
7253 }
73-
74- // Step 2: Get the manifest to find the blob digest.
75- console . log ( `\n📋 Fetching manifest for ${ sha } ...` ) ;
76- let digest ;
77- try {
78- const response = await fetch ( `https://ghcr.io/v2/${ ghcrRepo } /manifests/${ sha } ` , {
79- headers : {
80- Authorization : `Bearer ${ token } ` ,
81- Accept : 'application/vnd.oci.image.manifest.v1+json' ,
82- } ,
83- } ) ;
84- if ( ! response . ok ) {
85- throw new Error ( `Failed to fetch manifest: ${ response . status } ${ response . statusText } ` ) ;
86- }
87- const manifest = await response . json ( ) ;
88- digest = manifest ?. layers ?. [ 0 ] ?. digest ;
89- if ( ! digest ) {
90- throw new Error ( 'No layer digest found in manifest' ) ;
91- }
92- console . log ( `✅ Blob digest: ${ digest } ` ) ;
93- } catch ( error ) {
94- console . error ( '❌ Failed to fetch manifest:' , error . message ) ;
95- process . exit ( 1 ) ;
54+ const data = await response . json ( ) ;
55+ token = data . token ;
56+ if ( ! token ) {
57+ throw new Error ( 'No token in response' ) ;
9658 }
59+ console . log ( '✅ Token acquired' ) ;
60+ } catch ( error ) {
61+ console . error ( '❌ Failed to fetch token:' , error . message ) ;
62+ process . exit ( 1 ) ;
63+ }
9764
98- // Remove existing gutenberg directory so the extraction is clean.
99- if ( fs . existsSync ( gutenbergDir ) ) {
100- console . log ( '\n🗑️ Removing existing gutenberg directory...' ) ;
101- fs . rmSync ( gutenbergDir , { recursive : true , force : true } ) ;
65+ // Step 2: Get the manifest to find the blob digest.
66+ console . log ( `\n📋 Fetching manifest for ${ sha } ...` ) ;
67+ let digest ;
68+ try {
69+ const response = await fetch ( `https://ghcr.io/v2/${ ghcrRepo } /manifests/${ sha } ` , {
70+ headers : {
71+ Authorization : `Bearer ${ token } ` ,
72+ Accept : 'application/vnd.oci.image.manifest.v1+json' ,
73+ } ,
74+ } ) ;
75+ if ( ! response . ok ) {
76+ throw new Error ( `Failed to fetch manifest: ${ response . status } ${ response . statusText } ` ) ;
10277 }
78+ const manifest = await response . json ( ) ;
79+ digest = manifest ?. layers ?. [ 0 ] ?. digest ;
80+ if ( ! digest ) {
81+ throw new Error ( 'No layer digest found in manifest' ) ;
82+ }
83+ console . log ( `✅ Blob digest: ${ digest } ` ) ;
84+ } catch ( error ) {
85+ console . error ( '❌ Failed to fetch manifest:' , error . message ) ;
86+ process . exit ( 1 ) ;
87+ }
10388
104- fs . mkdirSync ( gutenbergDir , { recursive : true } ) ;
89+ // Remove existing gutenberg directory so the extraction is clean.
90+ if ( fs . existsSync ( gutenbergDir ) ) {
91+ console . log ( '\n🗑️ Removing existing gutenberg directory...' ) ;
92+ fs . rmSync ( gutenbergDir , { recursive : true , force : true } ) ;
93+ }
94+
95+ fs . mkdirSync ( gutenbergDir , { recursive : true } ) ;
96+
97+ /*
98+ * Step 3: Stream the blob directly through gunzip into tar, writing
99+ * into ./gutenberg with no temporary file on disk.
100+ */
101+ console . log ( `\n📥 Downloading and extracting artifact...` ) ;
102+ try {
103+ const response = await fetch ( `https://ghcr.io/v2/${ ghcrRepo } /blobs/${ digest } ` , {
104+ headers : {
105+ Authorization : `Bearer ${ token } ` ,
106+ } ,
107+ } ) ;
108+ if ( ! response . ok ) {
109+ throw new Error ( `Failed to download blob: ${ response . status } ${ response . statusText } ` ) ;
110+ }
105111
106112 /*
107- * Step 3: Stream the blob directly through gunzip into tar, writing
108- * into ./gutenberg with no temporary file on disk .
113+ * Spawn tar to read from stdin and extract into gutenbergDir.
114+ * `tar` is available on macOS, Linux, and Windows 10+ .
109115 */
110- console . log ( `\n📥 Downloading and extracting artifact...` ) ;
111- try {
112- const response = await fetch ( `https://ghcr.io/v2/${ ghcrRepo } /blobs/${ digest } ` , {
113- headers : {
114- Authorization : `Bearer ${ token } ` ,
115- } ,
116- } ) ;
117- if ( ! response . ok ) {
118- throw new Error ( `Failed to download blob: ${ response . status } ${ response . statusText } ` ) ;
119- }
120-
121- /*
122- * Spawn tar to read from stdin and extract into gutenbergDir.
123- * `tar` is available on macOS, Linux, and Windows 10+.
124- */
125- const tar = spawn ( 'tar' , [ '-x' , '-C' , gutenbergDir ] , {
126- stdio : [ 'pipe' , 'inherit' , 'inherit' ] ,
127- } ) ;
128-
129- const tarDone = new Promise ( ( resolve , reject ) => {
130- tar . on ( 'close' , ( code ) => {
131- if ( code !== 0 ) {
132- reject ( new Error ( `tar exited with code ${ code } ` ) ) ;
133- } else {
134- resolve ( ) ;
135- }
136- } ) ;
137- tar . on ( 'error' , reject ) ;
116+ const tar = spawn ( 'tar' , [ '-x' , '-C' , gutenbergDir ] , {
117+ stdio : [ 'pipe' , 'inherit' , 'inherit' ] ,
118+ } ) ;
119+
120+ const tarDone = new Promise ( ( resolve , reject ) => {
121+ tar . on ( 'close' , ( code ) => {
122+ if ( code !== 0 ) {
123+ reject ( new Error ( `tar exited with code ${ code } ` ) ) ;
124+ } else {
125+ resolve ( ) ;
126+ }
138127 } ) ;
128+ tar . on ( 'error' , reject ) ;
129+ } ) ;
139130
140- /*
141- * Pipe: fetch body → gunzip → tar stdin.
142- * Decompressing in Node keeps the pipeline error handling
143- * consistent and means tar only sees plain tar data on stdin.
144- */
145- await pipeline (
146- response . body ,
147- zlib . createGunzip ( ) ,
148- Writable . toWeb ( tar . stdin ) ,
149- ) ;
150-
151- await tarDone ;
152-
153- console . log ( '✅ Download and extraction complete' ) ;
154- } catch ( error ) {
155- console . error ( '❌ Download/extraction failed:' , error . message ) ;
156- process . exit ( 1 ) ;
157- }
158- }
131+ /*
132+ * Pipe: fetch body → gunzip → tar stdin.
133+ * Decompressing in Node keeps the pipeline error handling
134+ * consistent and means tar only sees plain tar data on stdin.
135+ */
136+ await pipeline (
137+ response . body ,
138+ zlib . createGunzip ( ) ,
139+ Writable . toWeb ( tar . stdin ) ,
140+ ) ;
159141
160- // Verify the downloaded version matches the expected SHA.
161- verifyGutenbergVersion ( ) ;
142+ await tarDone ;
162143
163- if ( downloaded ) {
164- console . log ( '\n✅ Gutenberg download complete!' ) ;
144+ console . log ( '✅ Download and extraction complete' ) ;
145+ } catch ( error ) {
146+ console . error ( '❌ Download/extraction failed:' , error . message ) ;
147+ process . exit ( 1 ) ;
165148 }
149+
150+ console . log ( '\n✅ Gutenberg download complete!' ) ;
166151}
167152
168153// Run main function.
169- const force = process . argv . includes ( '--force' ) ;
170- main ( force ) . catch ( ( error ) => {
154+ main ( ) . catch ( ( error ) => {
171155 console . error ( '❌ Unexpected error:' , error ) ;
172156 process . exit ( 1 ) ;
173157} ) ;
0 commit comments