From 4b98c0604651457ee3ea7eefefcc5b2d1545e4ce Mon Sep 17 00:00:00 2001 From: Paolo Tesei Date: Mon, 3 Aug 2026 16:22:09 +0200 Subject: [PATCH 1/3] perf remove window load --- src/faq/script.js | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/faq/script.js b/src/faq/script.js index 9e2a33f..f4101d7 100644 --- a/src/faq/script.js +++ b/src/faq/script.js @@ -1,17 +1,26 @@ -import { Accordion } from '@beapi/be-a11y'; +import { Accordion } from "@beapi/be-a11y"; // eslint-disable-next-line no-undef const accordionConfig = beapiFaqBlock.accordionConfig; -window.addEventListener( 'load', function () { +/** + * Initialize FAQ accordion instances. + */ +const init = () => { Accordion.init( - '.wp-block-blockparty-faq:has(button.wp-block-blockparty-faq-trigger)', - accordionConfig + ".wp-block-blockparty-faq:has(button.wp-block-blockparty-faq-trigger)", + accordionConfig, ); - Accordion.init( '.wp-block-blockparty-faq:has(button.faq__trigger)', { + Accordion.init(".wp-block-blockparty-faq:has(button.faq__trigger)", { ...accordionConfig, - panelSelector: '.faq__panel', - triggerSelector: '.faq__trigger', - } ); -} ); + panelSelector: ".faq__panel", + triggerSelector: ".faq__trigger", + }); +}; + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); +} else { + init(); +} From 0501ea4a2eef6e851fb6591ad148fe67c25522cb Mon Sep 17 00:00:00 2001 From: Paolo Tesei Date: Tue, 4 Aug 2026 09:26:36 +0200 Subject: [PATCH 2/3] fix format script --- src/faq/script.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/faq/script.js b/src/faq/script.js index f4101d7..bd9b564 100644 --- a/src/faq/script.js +++ b/src/faq/script.js @@ -1,4 +1,4 @@ -import { Accordion } from "@beapi/be-a11y"; +import { Accordion } from '@beapi/be-a11y'; // eslint-disable-next-line no-undef const accordionConfig = beapiFaqBlock.accordionConfig; @@ -8,19 +8,19 @@ const accordionConfig = beapiFaqBlock.accordionConfig; */ const init = () => { Accordion.init( - ".wp-block-blockparty-faq:has(button.wp-block-blockparty-faq-trigger)", - accordionConfig, + '.wp-block-blockparty-faq:has(button.wp-block-blockparty-faq-trigger)', + accordionConfig ); - Accordion.init(".wp-block-blockparty-faq:has(button.faq__trigger)", { + Accordion.init( '.wp-block-blockparty-faq:has(button.faq__trigger)', { ...accordionConfig, - panelSelector: ".faq__panel", - triggerSelector: ".faq__trigger", - }); + panelSelector: '.faq__panel', + triggerSelector: '.faq__trigger', + } ); }; -if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", init); +if ( document.readyState === 'loading' ) { + document.addEventListener( 'DOMContentLoaded', init ); } else { init(); } From 8680969c08be7e4ce91a8c19adbffa27f9312ba3 Mon Sep 17 00:00:00 2001 From: Paolo Tesei Date: Tue, 4 Aug 2026 09:51:58 +0200 Subject: [PATCH 3/3] add bumb version script --- .distignore | 1 + .gitattributes | 2 +- README.md | 1 + bin/bump-version.js | 428 ++++++++++++++++++++++++++++++++++++++++++++ package.json | 5 +- 5 files changed, 434 insertions(+), 3 deletions(-) create mode 100755 bin/bump-version.js diff --git a/.distignore b/.distignore index 00f7f99..1c86fb2 100644 --- a/.distignore +++ b/.distignore @@ -4,6 +4,7 @@ /config /node_modules /tests +/bin .distignore .editorconfig diff --git a/.gitattributes b/.gitattributes index d8401c1..c2d7c0f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,7 +4,7 @@ /node_modules export-ignore -diff /tests export-ignore /vendor export-ignore -diff - +/bin export-ignore /.distignore export-ignore /.editorconfig export-ignore /.gitattributes export-ignore diff --git a/README.md b/README.md index aec715d..d76df81 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ A Gutenberg block for SEO friendly FAQ in an accessible accordion. - `npm run start` - Start the development server with hot reload - `npm run env:start` - Start the WordPress environment (wp-env) - `npm run env:stop` - Stop the WordPress environment +- `npm run bump -- ` - Bump the plugin version across package, blocks, PHP, and docs ### Note diff --git a/bin/bump-version.js b/bin/bump-version.js new file mode 100755 index 0000000..f51e122 --- /dev/null +++ b/bin/bump-version.js @@ -0,0 +1,428 @@ +#!/usr/bin/env node +/** + * Bump the plugin version across package, block metadata, PHP, and docs. + * + * Usage: + * npm run bump -- patch + * npm run bump -- minor + * npm run bump -- major + * npm run bump -- 2.2.0 + * + * @package blockparty-faq + */ + +const fs = require( 'fs' ); +const path = require( 'path' ); + +const ROOT = path.resolve( __dirname, '..' ); + +const SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)$/; + +/** + * Resolve the absolute path of a project-relative file. + * + * @param {string} relativePath Relative path from the project root. + * @return {string} Absolute file path. + */ +const resolvePath = ( relativePath ) => path.join( ROOT, relativePath ); + +/** + * Read a UTF-8 file from the project root. + * + * @param {string} relativePath Relative path from the project root. + * @return {string} File contents. + */ +const readFile = ( relativePath ) => + fs.readFileSync( resolvePath( relativePath ), 'utf8' ); + +/** + * Write a UTF-8 file to the project root. + * + * @param {string} relativePath Relative path from the project root. + * @param {string} contents File contents. + * @return {void} + */ +const writeFile = ( relativePath, contents ) => { + fs.writeFileSync( resolvePath( relativePath ), contents, 'utf8' ); +}; + +/** + * Parse a JSON file from the project root. + * + * @param {string} relativePath Relative path from the project root. + * @return {Object} Parsed JSON object. + */ +const readJson = ( relativePath ) => JSON.parse( readFile( relativePath ) ); + +/** + * Write a JSON file with tab indentation (project convention). + * + * @param {string} relativePath Relative path from the project root. + * @param {Object} data Data to serialize. + * @return {void} + */ +const writeJson = ( relativePath, data ) => { + writeFile( relativePath, `${ JSON.stringify( data, null, '\t' ) }\n` ); +}; + +/** + * Print usage help and exit. + * + * @param {number} exitCode Process exit code. + * @return {void} + */ +const printUsage = ( exitCode = 1 ) => { + console.error( ` +Usage: + npm run bump -- + +Examples: + npm run bump -- patch + npm run bump -- minor + npm run bump -- 2.2.0 +`.trim() ); + process.exit( exitCode ); +}; + +/** + * Compute the next semantic version. + * + * @param {string} current Current version (x.y.z). + * @param {string} bump Bump type or explicit version. + * @return {string} Next version. + */ +const getNextVersion = ( current, bump ) => { + if ( SEMVER_RE.test( bump ) ) { + return bump; + } + + const match = current.match( SEMVER_RE ); + if ( ! match ) { + throw new Error( `Invalid current version: ${ current }` ); + } + + let major = Number( match[ 1 ] ); + let minor = Number( match[ 2 ] ); + let patch = Number( match[ 3 ] ); + + switch ( bump ) { + case 'major': + major += 1; + minor = 0; + patch = 0; + break; + case 'minor': + minor += 1; + patch = 0; + break; + case 'patch': + patch += 1; + break; + default: + throw new Error( + `Unknown bump type "${ bump }". Use patch, minor, major, or x.y.z.` + ); + } + + return `${ major }.${ minor }.${ patch }`; +}; + +/** + * Format today's date as YYYY-MM-DD (local timezone). + * + * @return {string} ISO date string without time. + */ +const today = () => { + const now = new Date(); + const year = now.getFullYear(); + const month = String( now.getMonth() + 1 ).padStart( 2, '0' ); + const day = String( now.getDate() ).padStart( 2, '0' ); + return `${ year }-${ month }-${ day }`; +}; + +/** + * Replace the first occurrence of a pattern or throw. + * + * @param {string} contents File contents. + * @param {RegExp|string} search Search pattern. + * @param {string} replace Replacement. + * @param {string} label Human-readable label for errors. + * @return {string} Updated contents. + */ +const replaceOnce = ( contents, search, replace, label ) => { + if ( typeof search === 'string' ) { + if ( ! contents.includes( search ) ) { + throw new Error( `Could not find ${ label }.` ); + } + return contents.replace( search, replace ); + } + + if ( ! search.test( contents ) ) { + throw new Error( `Could not find ${ label }.` ); + } + + return contents.replace( search, replace ); +}; + +/** + * Update version fields in a JSON file that has a top-level "version" key. + * + * @param {string} relativePath Relative path from the project root. + * @param {string} version New version. + * @return {void} + */ +const updateJsonVersion = ( relativePath, version ) => { + const data = readJson( relativePath ); + if ( typeof data.version !== 'string' ) { + throw new Error( `Missing "version" in ${ relativePath }.` ); + } + data.version = version; + writeJson( relativePath, data ); +}; + +/** + * Update package-lock.json root package version fields. + * + * @param {string} version New version. + * @return {void} + */ +const updatePackageLock = ( version ) => { + const lockPath = 'package-lock.json'; + if ( ! fs.existsSync( resolvePath( lockPath ) ) ) { + return; + } + + const lock = readJson( lockPath ); + lock.version = version; + if ( lock.packages && lock.packages[ '' ] ) { + lock.packages[ '' ].version = version; + } + writeJson( lockPath, lock ); +}; + +/** + * Update version strings in the main plugin PHP file. + * + * @param {string} currentVersion Current version. + * @param {string} nextVersion New version. + * @return {void} + */ +const updatePluginPhp = ( currentVersion, nextVersion ) => { + const relativePath = 'blockparty-faq.php'; + let contents = readFile( relativePath ); + + contents = replaceOnce( + contents, + new RegExp( + `( \\* Version:\\s+)${ escapeRegExp( currentVersion ) }` + ), + `$1${ nextVersion }`, + `plugin header Version in ${ relativePath }` + ); + + contents = replaceOnce( + contents, + `define( 'BLOCKPARTY_FAQ_VERSION', '${ currentVersion }' );`, + `define( 'BLOCKPARTY_FAQ_VERSION', '${ nextVersion }' );`, + `BLOCKPARTY_FAQ_VERSION in ${ relativePath }` + ); + + writeFile( relativePath, contents ); +}; + +/** + * Escape a string for safe use inside a RegExp. + * + * @param {string} value Raw string. + * @return {string} Escaped string. + */ +const escapeRegExp = ( value ) => + value.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ); + +/** + * Update Stable tag and changelog stubs in readme.txt. + * + * @param {string} currentVersion Current version. + * @param {string} nextVersion New version. + * @param {string} date Release date (YYYY-MM-DD). + * @return {void} + */ +const updateReadmeTxt = ( currentVersion, nextVersion, date ) => { + const relativePath = 'readme.txt'; + let contents = readFile( relativePath ); + + contents = replaceOnce( + contents, + new RegExp( + `(Stable tag:\\s+)${ escapeRegExp( currentVersion ) }` + ), + `$1${ nextVersion }`, + `Stable tag in ${ relativePath }` + ); + + const changelogStub = `= ${ nextVersion } - ${ date } =\n* TBD\n\n`; + contents = replaceOnce( + contents, + `= ${ currentVersion }`, + `${ changelogStub }= ${ currentVersion }`, + `changelog section in ${ relativePath }` + ); + + const upgradeStub = `= ${ nextVersion } =\nTBD\n\n`; + contents = replaceOnce( + contents, + '== Upgrade Notice ==\n\n', + `== Upgrade Notice ==\n\n${ upgradeStub }`, + `Upgrade Notice in ${ relativePath }` + ); + + writeFile( relativePath, contents ); +}; + +/** + * Prepend a Keep a Changelog stub in CHANGELOG.md. + * + * @param {string} nextVersion New version. + * @param {string} date Release date (YYYY-MM-DD). + * @return {void} + */ +const updateChangelogMd = ( nextVersion, date ) => { + const relativePath = 'CHANGELOG.md'; + let contents = readFile( relativePath ); + + if ( contents.includes( `## [${ nextVersion }]` ) ) { + throw new Error( + `CHANGELOG.md already contains an entry for ${ nextVersion }.` + ); + } + + const stub = `## [${ nextVersion }] - ${ date } + +### Changed + +- TBD + +`; + + contents = replaceOnce( + contents, + 'and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n', + `and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n${ stub }`, + `Semantic Versioning intro in ${ relativePath }` + ); + + writeFile( relativePath, contents ); +}; + +/** + * Prepend a changelog stub in README.md. + * + * @param {string} nextVersion New version. + * @param {string} date Release date (YYYY-MM-DD). + * @return {void} + */ +const updateReadmeMd = ( nextVersion, date ) => { + const relativePath = 'README.md'; + let contents = readFile( relativePath ); + + if ( contents.includes( `### ${ nextVersion } -` ) ) { + throw new Error( + `README.md already contains an entry for ${ nextVersion }.` + ); + } + + const stub = `### ${ nextVersion } - ${ date } + +- TBD + +`; + + contents = replaceOnce( + contents, + '## Changelog\n\n', + `## Changelog\n\n${ stub }`, + `Changelog heading in ${ relativePath }` + ); + + writeFile( relativePath, contents ); +}; + +/** + * Main entry point. + * + * @return {void} + */ +const main = () => { + const bumpArg = process.argv[ 2 ]; + + if ( ! bumpArg || bumpArg === '-h' || bumpArg === '--help' ) { + printUsage( bumpArg ? 0 : 1 ); + } + + const pluginData = readJson( '.plugin-data' ); + const packageJson = readJson( 'package.json' ); + + const currentVersion = pluginData.version || packageJson.version; + if ( ! SEMVER_RE.test( currentVersion ) ) { + throw new Error( `Invalid current version: ${ currentVersion }` ); + } + + const nextVersion = getNextVersion( currentVersion, bumpArg ); + if ( nextVersion === currentVersion ) { + throw new Error( + `Next version ${ nextVersion } is identical to the current version.` + ); + } + + const date = today(); + const updated = []; + + updateJsonVersion( '.plugin-data', nextVersion ); + updated.push( '.plugin-data' ); + + updateJsonVersion( 'package.json', nextVersion ); + updated.push( 'package.json' ); + + updatePackageLock( nextVersion ); + if ( fs.existsSync( resolvePath( 'package-lock.json' ) ) ) { + updated.push( 'package-lock.json' ); + } + + const blockJsonFiles = [ + 'src/faq/block.json', + 'src/faq-answer/block.json', + 'src/faq-item/block.json', + 'src/faq-question/block.json', + ]; + + blockJsonFiles.forEach( ( file ) => { + updateJsonVersion( file, nextVersion ); + updated.push( file ); + } ); + + updatePluginPhp( currentVersion, nextVersion ); + updated.push( 'blockparty-faq.php' ); + + updateReadmeTxt( currentVersion, nextVersion, date ); + updated.push( 'readme.txt' ); + + updateChangelogMd( nextVersion, date ); + updated.push( 'CHANGELOG.md' ); + + updateReadmeMd( nextVersion, date ); + updated.push( 'README.md' ); + + console.log( `Bumped version ${ currentVersion } → ${ nextVersion }` ); + console.log( 'Updated files:' ); + updated.forEach( ( file ) => console.log( ` - ${ file }` ) ); + console.log( + '\nFill in the TBD changelog entries before releasing.' + ); +}; + +try { + main(); +} catch ( error ) { + console.error( `Error: ${ error.message }` ); + process.exit( 1 ); +} diff --git a/package.json b/package.json index a8d1b72..4c8df2d 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "make-pot": "wp i18n make-pot . languages/blockparty-faq.pot --exclude=\"src\" --domain=blockparty-faq", "make-json": "wp i18n make-json languages/blockparty-faq-fr_FR.po languages/ --no-purge", "env:start": "wp-env start", - "env:stop": "wp-env stop" + "env:stop": "wp-env stop", + "bump": "node bin/bump-version.js" }, "dependencies": { "@beapi/be-a11y": "^1.7.2", @@ -34,4 +35,4 @@ "volta": { "node": "20.12.0" } -} +} \ No newline at end of file