Package release script: Prepare packages (#33515)
This commit is contained in:
parent
59427def84
commit
6dd78f0f62
|
@ -1753,6 +1753,33 @@ importers:
|
||||||
tslib: 2.3.1
|
tslib: 2.3.1
|
||||||
typescript: 4.4.4
|
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:
|
packages:
|
||||||
|
|
||||||
/@ampproject/remapping/2.1.2:
|
/@ampproject/remapping/2.1.2:
|
||||||
|
|
|
@ -5,3 +5,4 @@ packages:
|
||||||
- 'tools/monorepo-merge'
|
- 'tools/monorepo-merge'
|
||||||
- 'tools/code-analyzer'
|
- 'tools/code-analyzer'
|
||||||
- 'tools/create-extension'
|
- '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