Package release script: Prepare packages (#33515)
This commit is contained in:
parent
59427def84
commit
6dd78f0f62
|
@ -1753,6 +1753,33 @@ importers:
|
|||
tslib: 2.3.1
|
||||
typescript: 4.4.4
|
||||
|
||||
tools/package-release:
|
||||
specifiers:
|
||||
'@oclif/core': ^1
|
||||
'@oclif/plugin-help': ^5
|
||||
'@oclif/plugin-plugins': ^2.0.1
|
||||
'@types/node': ^16.9.4
|
||||
'@woocommerce/eslint-plugin': workspace:*
|
||||
globby: ^11
|
||||
oclif: ^2
|
||||
shx: ^0.3.3
|
||||
ts-node: ^10.2.1
|
||||
tslib: ^2.3.1
|
||||
typescript: ^4.4.3
|
||||
dependencies:
|
||||
'@oclif/core': 1.3.4
|
||||
'@oclif/plugin-help': 5.1.11
|
||||
'@oclif/plugin-plugins': 2.1.0
|
||||
devDependencies:
|
||||
'@types/node': 16.10.3
|
||||
'@woocommerce/eslint-plugin': link:../../packages/js/eslint-plugin
|
||||
globby: 11.1.0
|
||||
oclif: 2.4.5
|
||||
shx: 0.3.4
|
||||
ts-node: 10.5.0_506ca6ef959d35afcce359030b1bc9ff
|
||||
tslib: 2.3.1
|
||||
typescript: 4.6.2
|
||||
|
||||
packages:
|
||||
|
||||
/@ampproject/remapping/2.1.2:
|
||||
|
|
|
@ -5,3 +5,4 @@ packages:
|
|||
- 'tools/monorepo-merge'
|
||||
- 'tools/code-analyzer'
|
||||
- 'tools/create-extension'
|
||||
- 'tools/package-release'
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
/dist
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": [ "plugin:@woocommerce/eslint-plugin/recommended" ]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
/oclif.manifest.json
|
|
@ -0,0 +1,17 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const oclif = require('@oclif/core')
|
||||
|
||||
const path = require('path')
|
||||
const project = path.join(__dirname, '..', 'tsconfig.json')
|
||||
|
||||
// In dev mode -> use ts-node and dev plugins
|
||||
process.env.NODE_ENV = 'development'
|
||||
|
||||
require('ts-node').register({project})
|
||||
|
||||
// In dev mode, always show stack traces
|
||||
oclif.settings.debug = true;
|
||||
|
||||
// Start the CLI
|
||||
oclif.run().then(oclif.flush).catch(oclif.Errors.handle)
|
|
@ -0,0 +1,3 @@
|
|||
@echo off
|
||||
|
||||
node "%~dp0\dev" %*
|
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const oclif = require('@oclif/core')
|
||||
|
||||
oclif.run().then(require('@oclif/core/flush')).catch(require('@oclif/core/handle'))
|
|
@ -0,0 +1,3 @@
|
|||
@echo off
|
||||
|
||||
node "%~dp0\run" %*
|
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
"name": "package-release",
|
||||
"version": "0.1.0",
|
||||
"description": "A tool to Monorepo JS packages.",
|
||||
"author": "Automattic",
|
||||
"bin": {
|
||||
"package-release": "./bin/run"
|
||||
},
|
||||
"homepage": "https://github.com/woocommerce/woocommerce",
|
||||
"license": "GPLv2",
|
||||
"main": "dist/index.js",
|
||||
"repository": "woocommerce/woocommerce",
|
||||
"files": [
|
||||
"/bin",
|
||||
"/dist",
|
||||
"/npm-shrinkwrap.json",
|
||||
"/oclif.manifest.json"
|
||||
],
|
||||
"dependencies": {
|
||||
"@oclif/core": "^1",
|
||||
"@oclif/plugin-help": "^5",
|
||||
"@oclif/plugin-plugins": "^2.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.9.4",
|
||||
"@woocommerce/eslint-plugin": "workspace:*",
|
||||
"globby": "^11",
|
||||
"oclif": "^2",
|
||||
"shx": "^0.3.3",
|
||||
"ts-node": "^10.2.1",
|
||||
"tslib": "^2.3.1",
|
||||
"typescript": "^4.4.3"
|
||||
},
|
||||
"oclif": {
|
||||
"bin": "package-release",
|
||||
"dirname": "package-release",
|
||||
"commands": "./dist/commands",
|
||||
"plugins": [
|
||||
"@oclif/plugin-help",
|
||||
"@oclif/plugin-plugins"
|
||||
],
|
||||
"topicSeparator": " ",
|
||||
"topics": {
|
||||
"package-release": {
|
||||
"description": "Releases JS packages"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "shx rm -rf dist && tsc -b",
|
||||
"lint": "eslint . --ext .ts --config .eslintrc",
|
||||
"postpack": "shx rm -f oclif.manifest.json",
|
||||
"posttest": "pnpm lint",
|
||||
"prepack": "pnpm build && oclif manifest"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"types": "dist/index.d.ts"
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { execSync } from 'child_process';
|
||||
import { readdirSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getFilepathFromPackageName } from './validate';
|
||||
|
||||
/**
|
||||
* Call changelogger's next version function to get the version for the next release.
|
||||
*
|
||||
* @param {string} name Package name.
|
||||
* @return {string} Next release version.
|
||||
*/
|
||||
export const getNextVersion = ( name: string ) => {
|
||||
try {
|
||||
const cwd = getFilepathFromPackageName( name );
|
||||
return execSync( './vendor/bin/changelogger version next', {
|
||||
cwd,
|
||||
encoding: 'utf-8',
|
||||
} ).trim();
|
||||
} catch ( e ) {
|
||||
let message = '';
|
||||
if ( e instanceof Error ) {
|
||||
message = e.message;
|
||||
throw new Error( message );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Call Changelogger's validate function on changelog entries.
|
||||
*
|
||||
* @param {string} name
|
||||
* @return {Error|void} Output of changelogger exec.
|
||||
*/
|
||||
export const validateChangelogEntries = ( name: string ) => {
|
||||
try {
|
||||
const cwd = getFilepathFromPackageName( name );
|
||||
return execSync( './vendor/bin/changelogger validate', {
|
||||
cwd,
|
||||
encoding: 'utf-8',
|
||||
} );
|
||||
} catch ( e ) {
|
||||
let message = '';
|
||||
if ( e instanceof Error ) {
|
||||
message = e.message;
|
||||
throw new Error( message );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Write the changelog.
|
||||
*
|
||||
* @param {string} name Package name.
|
||||
*/
|
||||
export const writeChangelog = ( name: string ) => {
|
||||
try {
|
||||
const cwd = getFilepathFromPackageName( name );
|
||||
execSync( './vendor/bin/changelogger write', {
|
||||
cwd,
|
||||
encoding: 'utf-8',
|
||||
} );
|
||||
} catch ( e ) {
|
||||
let message = '';
|
||||
if ( e instanceof Error ) {
|
||||
message = e.message;
|
||||
throw new Error(
|
||||
message + ' - Package may not have changelog entries.'
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine if a package has changelogs to release.
|
||||
*
|
||||
* @param {string} name Package name.
|
||||
* @return {boolean} If there are changelogs.
|
||||
*/
|
||||
export const hasChangelogs = ( name: string ): boolean | void => {
|
||||
try {
|
||||
const changelogDir = join(
|
||||
getFilepathFromPackageName( name ),
|
||||
'changelog'
|
||||
);
|
||||
const changelogDirContents = readdirSync( changelogDir, {
|
||||
encoding: 'utf-8',
|
||||
} );
|
||||
|
||||
return (
|
||||
changelogDirContents.filter( ( entry ) => entry !== '.gitkeep' )
|
||||
.length > 0
|
||||
);
|
||||
} catch ( e ) {
|
||||
let message = '';
|
||||
if ( e instanceof Error ) {
|
||||
message = e.message;
|
||||
throw new Error( message );
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,130 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { CliUx, Command, Flags } from '@oclif/core';
|
||||
import { readFileSync, writeFileSync } from 'fs';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
getAllPackges,
|
||||
validatePackage,
|
||||
getFilepathFromPackageName,
|
||||
} from '../../validate';
|
||||
import {
|
||||
getNextVersion,
|
||||
validateChangelogEntries,
|
||||
writeChangelog,
|
||||
hasChangelogs,
|
||||
} from '../../changelogger';
|
||||
|
||||
/**
|
||||
* PackageRelease class
|
||||
*/
|
||||
export default class PackageRelease extends Command {
|
||||
/**
|
||||
* CLI description
|
||||
*/
|
||||
static description = 'Release Monorepo JS packages';
|
||||
|
||||
/**
|
||||
* CLI arguments
|
||||
*/
|
||||
static args = [
|
||||
{
|
||||
name: 'packages',
|
||||
description:
|
||||
'Package to release, or packages to release separated by commas.',
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* CLI flags.
|
||||
*/
|
||||
static flags = {
|
||||
all: Flags.boolean( {
|
||||
char: 'a',
|
||||
default: false,
|
||||
description: 'Perform prepare function on all packages.',
|
||||
} ),
|
||||
};
|
||||
|
||||
/**
|
||||
* This method is called to execute the command
|
||||
*/
|
||||
async run(): Promise< void > {
|
||||
const { args, flags } = await this.parse( PackageRelease );
|
||||
|
||||
if ( ! args.packages && ! flags.all ) {
|
||||
this.error( 'No packages supplied.' );
|
||||
}
|
||||
|
||||
if ( flags.all ) {
|
||||
this.preparePackages( getAllPackges() );
|
||||
return;
|
||||
}
|
||||
|
||||
const packages = args.packages.split( ',' );
|
||||
|
||||
packages.forEach( ( name: string ) =>
|
||||
validatePackage( name, ( e: string ): void => this.error( e ) )
|
||||
);
|
||||
|
||||
this.preparePackages( packages );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare packages for release by creating the changelog and bumping version.
|
||||
*
|
||||
* @param {Array<string>} packages Packages to prepare.
|
||||
*/
|
||||
private preparePackages( packages: Array< string > ) {
|
||||
packages.forEach( ( name ) => {
|
||||
CliUx.ux.action.start( `Preparing ${ name }` );
|
||||
|
||||
try {
|
||||
if ( hasChangelogs( name ) ) {
|
||||
validateChangelogEntries( name );
|
||||
const nextVersion = getNextVersion( name );
|
||||
writeChangelog( name );
|
||||
if ( nextVersion ) {
|
||||
this.bumpPackageVersion( name, nextVersion );
|
||||
}
|
||||
} else {
|
||||
this.log( `Skipping ${ name }, no changelogs available.` );
|
||||
}
|
||||
} catch ( e ) {
|
||||
if ( e instanceof Error ) {
|
||||
this.error( e.message );
|
||||
}
|
||||
}
|
||||
|
||||
CliUx.ux.action.stop();
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the version number in package.json.
|
||||
*
|
||||
* @param {string} name Package name.
|
||||
* @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;
|
||||
writeFileSync(
|
||||
packageJsonFilepath,
|
||||
JSON.stringify( packageJson, null, '\t' ) + '\n'
|
||||
);
|
||||
} catch ( e ) {
|
||||
this.error( `Can't bump version for ${ name }.` );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { dirname } from 'path';
|
||||
|
||||
// Escape from ./tools/package-release/src
|
||||
export const MONOREPO_ROOT = dirname( dirname( dirname( __dirname ) ) );
|
||||
|
||||
// Packages that are not meant to be released by monorepo team for whatever reason.
|
||||
export const excludedPackages = [
|
||||
'@woocommerce/admin-e2e-tests',
|
||||
'@woocommerce/api',
|
||||
'@woocommerce/api-core-tests',
|
||||
'@woocommerce/e2e-core-tests',
|
||||
'@woocommerce/e2e-environment',
|
||||
'@woocommerce/e2e-utils',
|
||||
];
|
|
@ -0,0 +1 @@
|
|||
export { run } from '@oclif/core';
|
|
@ -0,0 +1,113 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { existsSync, readFileSync, readdirSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { MONOREPO_ROOT, excludedPackages } from './const';
|
||||
|
||||
/**
|
||||
* 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', '' ) );
|
||||
|
||||
/**
|
||||
* Check if package is valid and can be deployed to NPM.
|
||||
*
|
||||
* @param {string} name package name.
|
||||
* @return {boolean} true if the package is private.
|
||||
*/
|
||||
export const isValidPackage = ( name: string ): boolean => {
|
||||
const filepath = getFilepathFromPackageName( name );
|
||||
const packageJsonFilepath = `${ filepath }/package.json`;
|
||||
const packageJsonExists = existsSync( packageJsonFilepath );
|
||||
if ( ! packageJsonExists ) {
|
||||
return false;
|
||||
}
|
||||
const packageJson = JSON.parse(
|
||||
readFileSync( packageJsonFilepath, 'utf8' )
|
||||
);
|
||||
|
||||
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
|
||||
) => {
|
||||
const filepath = getFilepathFromPackageName( name );
|
||||
|
||||
try {
|
||||
const exists = existsSync( filepath );
|
||||
if ( ! exists ) {
|
||||
throw new Error();
|
||||
}
|
||||
} catch ( e ) {
|
||||
error( `${ name } does not exist as a package.` );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all releaseable package names.
|
||||
*
|
||||
* @return {Array<string>} 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 );
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate a package.
|
||||
*
|
||||
* @param {string} name package name.
|
||||
* @param {Function} error Error logging function.
|
||||
*/
|
||||
export const validatePackage = (
|
||||
name: string,
|
||||
error: ( s: string ) => void
|
||||
) => {
|
||||
validatePackageName( name, error );
|
||||
|
||||
if ( ! isValidPackage( name ) ) {
|
||||
error(
|
||||
`${ name } is not a valid package. It may be private or incorrectly configured.`
|
||||
);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"importHelpers": true,
|
||||
"module": "commonjs",
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"target": "es2019",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
],
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue