Skip to content

Commit dad7d59

Browse files
committed
Add verification of the SHA value in hash file.
1 parent cb16f88 commit dad7d59

2 files changed

Lines changed: 96 additions & 73 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
},
99
"gutenberg": {
1010
"sha": "e0c5fc81de25a4f837c063ca2c2db32d74698a49",
11-
"ghcrRepo": "Gdesrosj/gutenberg/gutenberg-build"
11+
"ghcrRepo": "desrosj/gutenberg/gutenberg-build"
1212
},
1313
"engines": {
1414
"node": ">=20.10.0",

tools/gutenberg/download-gutenberg.js

Lines changed: 95 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)