Skip to content

Commit 3f4f2ca

Browse files
2 parents 7c872c5 + 474a396 commit 3f4f2ca

6 files changed

Lines changed: 151 additions & 137 deletions

File tree

.github/workflows/install-testing.yml

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,17 +88,13 @@ jobs:
8888
- db-version: '5.0'
8989
- db-version: '5.1'
9090
- db-version: '5.5'
91-
# The PHP <= 7.3/MySQL 8.4 jobs currently fail due to mysql_native_password being disabled by default. See https://core.trac.wordpress.org/ticket/61218.
92-
- php: '7.2'
93-
db-version: '8.4'
94-
- php: '7.3'
95-
db-version: '8.4'
9691
# Only test the latest innovation release.
9792
- db-version: '9.0'
9893
- db-version: '9.1'
9994
- db-version: '9.2'
10095
- db-version: '9.3'
10196
- db-version: '9.4'
97+
- db-version: '9.5'
10298
# MySQL 9.0+ will not work on PHP 7.2 & 7.3. See https://core.trac.wordpress.org/ticket/61218.
10399
- php: '7.2'
104100
db-version: '9.6'
@@ -118,7 +114,7 @@ jobs:
118114
-e MYSQL_ROOT_PASSWORD="root"
119115
-e MYSQL_DATABASE="test_db"
120116
--entrypoint sh ${{ matrix.db-type }}:${{ matrix.db-version }}
121-
-c "exec docker-entrypoint.sh mysqld${{ matrix.db-type == 'mysql' && contains( fromJSON('["7.2", "7.3"]'), matrix.php ) && ' --default-authentication-plugin=mysql_native_password' || '' }}"
117+
-c "exec docker-entrypoint.sh mysqld${{ matrix.db-type == 'mysql' && contains( fromJSON('["5.4", "5.5", "5.6", "7.0", "7.1", "7.2", "7.3"]'), matrix.php ) && ( matrix.db-version == '8.4' && ' --mysql-native-password=ON --authentication-policy=mysql_native_password' || ' --default-authentication-plugin=mysql_native_password' ) || '' }}"
122118
123119
steps:
124120
- name: Set up PHP ${{ matrix.php }}

Gruntfile.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1589,13 +1589,9 @@ module.exports = function(grunt) {
15891589

15901590
grunt.registerTask( 'gutenberg:download', 'Downloads the built Gutenberg artifact.', function() {
15911591
const done = this.async();
1592-
const args = [ 'tools/gutenberg/download.js' ];
1593-
if ( grunt.option( 'force' ) ) {
1594-
args.push( '--force' );
1595-
}
15961592
grunt.util.spawn( {
15971593
cmd: 'node',
1598-
args,
1594+
args: [ 'tools/gutenberg/download.js' ],
15991595
opts: { stdio: 'inherit' }
16001596
}, function( error ) {
16011597
done( ! error );

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@
112112
"wicg-inert": "3.1.3"
113113
},
114114
"scripts": {
115-
"postinstall": "npm run gutenberg:download",
115+
"postinstall": "npm run gutenberg:verify",
116116
"build": "grunt build",
117117
"build:dev": "grunt build --dev",
118118
"build:gutenberg": "grunt build:gutenberg",
@@ -140,6 +140,7 @@
140140
"test:visual": "wp-scripts test-playwright --config tests/visual-regression/playwright.config.js",
141141
"typecheck:php": "node ./tools/local-env/scripts/docker.js run --rm php composer phpstan",
142142
"gutenberg:copy": "node tools/gutenberg/copy.js",
143+
"gutenberg:verify": "node tools/gutenberg/utils.js",
143144
"gutenberg:download": "node tools/gutenberg/download.js",
144145
"vendor:copy": "node tools/vendors/copy-vendors.js",
145146
"sync-gutenberg-packages": "grunt sync-gutenberg-packages",

src/wp-admin/includes/bookmark.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
*
1212
* @since 2.0.0
1313
*
14-
* @return int|WP_Error Value 0 or WP_Error on failure. The link ID on success.
14+
* @return int The link ID on success. The value 0 on failure.
1515
*/
1616
function add_link() {
1717
return edit_link();
@@ -23,7 +23,7 @@ function add_link() {
2323
* @since 2.0.0
2424
*
2525
* @param int $link_id Optional. ID of the link to edit. Default 0.
26-
* @return int|WP_Error Value 0 or WP_Error on failure. The link ID on success.
26+
* @return int The link ID on success. The value 0 on failure.
2727
*/
2828
function edit_link( $link_id = 0 ) {
2929
if ( ! current_user_can( 'manage_links' ) ) {
@@ -169,7 +169,7 @@ function get_link_to_edit( $link ) {
169169
* If empty, uses default link category.
170170
* }
171171
* @param bool $wp_error Optional. Whether to return a WP_Error object on failure. Default false.
172-
* @return int|WP_Error Value 0 or WP_Error on failure. The link ID on success.
172+
* @return int|WP_Error The link ID on success. The value 0 or WP_Error on failure.
173173
*/
174174
function wp_insert_link( $linkdata, $wp_error = false ) {
175175
global $wpdb;
@@ -295,7 +295,7 @@ function wp_set_link_cats( $link_id = 0, $link_categories = array() ) {
295295
* @since 2.0.0
296296
*
297297
* @param array $linkdata Link data to update. See wp_insert_link() for accepted arguments.
298-
* @return int|WP_Error Value 0 or WP_Error on failure. The updated link ID on success.
298+
* @return int The updated link ID on success. The value 0 on failure.
299299
*/
300300
function wp_update_link( $linkdata ) {
301301
$link_id = (int) $linkdata['link_id'];

tools/gutenberg/download.js

Lines changed: 97 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
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' );
1718
const fs = require( 'fs' );
1819
const { Writable } = require( 'stream' );
1920
const { pipeline } = require( 'stream/promises' );
20-
const path = require( 'path' );
2121
const 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

Comments
 (0)