From db909d70f840bd46bab8bb42afdf52fe8ab38073 Mon Sep 17 00:00:00 2001 From: Paul Sealock Date: Thu, 8 Feb 2024 22:23:13 +1300 Subject: [PATCH] Package Release: Prepare PHP packages for release (#44420) --- .../src/commands/prepare/index.ts | 38 +++-- .../src/commands/publish/index.ts | 9 +- tools/package-release/src/validate.ts | 142 ++++++++++-------- 3 files changed, 113 insertions(+), 76 deletions(-) diff --git a/tools/package-release/src/commands/prepare/index.ts b/tools/package-release/src/commands/prepare/index.ts index 62a73797ad9..661a7989885 100644 --- a/tools/package-release/src/commands/prepare/index.ts +++ b/tools/package-release/src/commands/prepare/index.ts @@ -2,15 +2,18 @@ * External dependencies */ import { CliUx, Command, Flags } from '@oclif/core'; -import { readFileSync, writeFileSync } from 'fs'; +import { writeFileSync } from 'fs'; /** * Internal dependencies */ import { - getAllPackges, + getAllPackages, validatePackage, getFilepathFromPackageName, + getPackageJson, + getComposerJson, + getPackageType, } from '../../validate'; import { getNextVersion, @@ -66,7 +69,7 @@ export default class PackagePrepare extends Command { } if ( flags.all ) { - await this.preparePackages( getAllPackges() ); + await this.preparePackages( getAllPackages() ); return; } @@ -105,6 +108,7 @@ export default class PackagePrepare extends Command { writeChangelog( name ); } else { if ( initialRelease ) { + // @todo: When the composer.json versioning is "wordpress" as is for plugins, this value needs to be 1.0 nextVersion = '1.0.0'; } else { throw new Error( @@ -138,16 +142,28 @@ export default class PackagePrepare extends Command { * @param {string} version Next version. */ private bumpPackageVersion( name: string, version: string ) { - const filepath = getFilepathFromPackageName( name ); - const packageJsonFilepath = `${ filepath }/package.json`; try { - const packageJson = JSON.parse( - readFileSync( packageJsonFilepath, 'utf8' ) - ); - packageJson.version = version; + const filepath = getFilepathFromPackageName( name ); + const packageType = getPackageType( name ); + let jsonFilepath; + let json; + + if ( packageType === 'js' ) { + jsonFilepath = `${ filepath }/package.json`; + json = getPackageJson( name ); + } else if ( packageType === 'php' ) { + jsonFilepath = `${ filepath }/composer.json`; + json = getComposerJson( name ); + } else { + this.error( + `Can't bump version for ${ name }. The package does not exist.` + ); + } + + json.version = version; writeFileSync( - packageJsonFilepath, - JSON.stringify( packageJson, null, '\t' ) + '\n' + jsonFilepath, + JSON.stringify( json, null, '\t' ) + '\n' ); } catch ( e ) { this.error( `Can't bump version for ${ name }.` ); diff --git a/tools/package-release/src/commands/publish/index.ts b/tools/package-release/src/commands/publish/index.ts index 9a12656244d..cdf3775bada 100644 --- a/tools/package-release/src/commands/publish/index.ts +++ b/tools/package-release/src/commands/publish/index.ts @@ -8,7 +8,7 @@ import { execSync } from 'child_process'; * Internal dependencies */ import { - getAllPackges, + getAllPackages, validatePackage, getFilepathFromPackageName, isValidUpdate, @@ -95,7 +95,7 @@ export default class PackageRelease extends Command { } if ( flags.all ) { - this.publishPackages( getAllPackges(), flags ); + this.publishPackages( getAllPackages(), flags ); return; } @@ -128,6 +128,11 @@ export default class PackageRelease extends Command { CliUx.ux.action.start( `${ verb } ${ name }` ); if ( isValidUpdate( name, initialRelease ) ) { const cwd = getFilepathFromPackageName( name ); + if ( cwd.includes( 'packages/php' ) ) { + this.error( + 'Publishing PHP packages is not supported just yet.' + ); + } execSync( `pnpm publish ${ dryRun ? '--dry-run' : '' diff --git a/tools/package-release/src/validate.ts b/tools/package-release/src/validate.ts index c860930d3a5..979dcf460af 100644 --- a/tools/package-release/src/validate.ts +++ b/tools/package-release/src/validate.ts @@ -1,8 +1,7 @@ /** * External dependencies */ -import { existsSync, readFileSync, readdirSync } from 'fs'; -import { join } from 'path'; +import { existsSync, readFileSync } from 'fs'; import { execSync } from 'child_process'; import { gt as greaterVersionThan } from 'semver'; @@ -11,14 +10,64 @@ import { gt as greaterVersionThan } from 'semver'; */ import { MONOREPO_ROOT, excludedPackages } from './const'; +/** + * Get pnpm's package data. + * + * @param {string} name package name. + * @return {Object|Array} A package object or an array of package objects. + */ +const getPackageData = ( name?: string ) => { + try { + const rawData = execSync( 'pnpm m ls --json --depth=-1', { + cwd: MONOREPO_ROOT, + encoding: 'utf-8', + } ); + + const data = JSON.parse( rawData ); + + if ( ! name ) { + return data; + } + + return data.find( ( p: { name: string } ) => p.name === name ); + } catch ( e ) { + if ( e instanceof Error ) { + // eslint-disable-next-line no-console + console.log( e ); + throw e; + } + } +}; + +/** + * Determine if package is JS or PHP. + * + * @param {string} name package name. + * @return {undefined|string} Package type js or php. + */ +export const getPackageType = ( name: string ) => { + const packageData = getPackageData( name ); + if ( ! packageData ) { + return; + } + if ( packageData.path.includes( 'packages/js' ) ) { + return 'js'; + } + if ( packageData.path.includes( 'packages/php' ) ) { + return 'php'; + } +}; + /** * Get filepath for a given package name. * * @param {string} name package name. * @return {string} Absolute path for the package. */ -export const getFilepathFromPackageName = ( name: string ): string => - join( MONOREPO_ROOT, 'packages/js', name.replace( '@woocommerce', '' ) ); +export const getFilepathFromPackageName = ( name: string ): string => { + const packageData = getPackageData( name ); + return packageData?.path; +}; /** * Get a package's package.json file in JSON format. @@ -38,51 +87,20 @@ export const getPackageJson = ( name: string ) => { }; /** - * Check if package is valid and can be deployed to NPM. + * Get a package's composer.json file in JSON format. * * @param {string} name package name. - * @return {boolean} true if the package is private. + * @return {Object|false} JSON object or false if it fails. */ -export const isValidPackage = ( name: string ): boolean => { - const packageJson = getPackageJson( name ); - - if ( ! packageJson ) { - return false; - } - - if ( name !== packageJson.name ) { - return false; - } - - const isPrivatePackage = !! packageJson.private; - - if ( isPrivatePackage ) { - return false; - } - - return true; -}; - -/** - * Validate package name. - * - * @param {string} name package name. - * @param {Function} error Error logging function. - */ -export const validatePackageName = ( - name: string, - error: ( s: string ) => void -) => { +export const getComposerJson = ( name: string ) => { const filepath = getFilepathFromPackageName( name ); - - try { - const exists = existsSync( filepath ); - if ( ! exists ) { - throw new Error(); - } - } catch ( e ) { - error( `${ name } does not exist as a package.` ); + const composerJsonFilepath = `${ filepath }/composer.json`; + const composerJsonExists = existsSync( composerJsonFilepath ); + if ( ! composerJsonExists ) { + return false; } + + return JSON.parse( readFileSync( composerJsonFilepath, 'utf8' ) ); }; /** @@ -90,22 +108,14 @@ export const validatePackageName = ( * * @return {Array} Package names. */ -export const getAllPackges = (): Array< string > => { - const jsPackageFolders = readdirSync( - join( MONOREPO_ROOT, 'packages/js' ), - { - encoding: 'utf-8', - } - ); - - return jsPackageFolders - .map( ( folder ) => '@woocommerce/' + folder ) - .filter( ( name ) => { - if ( excludedPackages.includes( name ) ) { - return false; - } - return isValidPackage( name ); - } ); +export const getAllPackages = (): Array< string > => { + const packageData = getPackageData(); + if ( ! packageData ) { + return []; + } + return packageData + .map( ( p: { name: string } ) => p.name ) + .filter( ( name: string ) => ! excludedPackages.includes( name ) ); }; /** @@ -118,13 +128,19 @@ export const validatePackage = ( name: string, error: ( s: string ) => void ) => { - validatePackageName( name, error ); + const packageData = getPackageData( name ); - if ( ! isValidPackage( name ) ) { + if ( ! packageData ) { + error( `${ name } is not a valid package.` ); + } + + if ( packageData.private ) { error( - `${ name } is not a valid package. It may be private or incorrectly configured.` + `${ name } is a private package, no need to prepare for a release.` ); } + + return true; }; /**