Add monorepo util to notify slack, add improvements to calling utils and type clean up. (#38185)
This commit is contained in:
parent
b581db2a7e
commit
95ac08739b
|
@ -30,7 +30,7 @@
|
|||
"create-extension": "node ./tools/create-extension/index.js",
|
||||
"cherry-pick": "node ./tools/cherry-pick/bin/run",
|
||||
"sync-dependencies": "pnpm exec syncpack -- fix-mismatches",
|
||||
"utils": "./tools/monorepo-utils/bin/run"
|
||||
"utils": "node ./tools/monorepo-utils/dist/index.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.20.2",
|
||||
|
|
|
@ -3242,8 +3242,8 @@ importers:
|
|||
specifier: ^1.10.0
|
||||
version: 1.10.0
|
||||
'@commander-js/extra-typings':
|
||||
specifier: ^0.1.0
|
||||
version: 0.1.0(commander@9.4.0)
|
||||
specifier: ^10.0.3
|
||||
version: 10.0.3(commander@10.0.1)
|
||||
'@octokit/graphql':
|
||||
specifier: 4.8.0
|
||||
version: 4.8.0
|
||||
|
@ -3257,8 +3257,8 @@ importers:
|
|||
specifier: ^4.1.2
|
||||
version: 4.1.2
|
||||
commander:
|
||||
specifier: ^9.4.0
|
||||
version: 9.4.0
|
||||
specifier: ^10.0.1
|
||||
version: 10.0.1
|
||||
dotenv:
|
||||
specifier: ^10.0.0
|
||||
version: 10.0.0
|
||||
|
@ -3871,7 +3871,7 @@ packages:
|
|||
'@babel/core': ^7.0.0-0
|
||||
dependencies:
|
||||
'@babel/core': 7.21.3
|
||||
'@jridgewell/trace-mapping': 0.3.16
|
||||
'@jridgewell/trace-mapping': 0.3.17
|
||||
commander: 4.1.1
|
||||
convert-source-map: 1.8.0
|
||||
fs-readdir-recursive: 1.1.0
|
||||
|
@ -8720,9 +8720,9 @@ packages:
|
|||
'@babel/core': 7.21.3
|
||||
'@babel/helper-annotate-as-pure': 7.16.7
|
||||
'@babel/helper-module-imports': 7.16.7
|
||||
'@babel/helper-plugin-utils': 7.20.2
|
||||
'@babel/helper-plugin-utils': 7.18.9
|
||||
'@babel/plugin-syntax-jsx': 7.16.7(@babel/core@7.21.3)
|
||||
'@babel/types': 7.21.3
|
||||
'@babel/types': 7.17.0
|
||||
dev: true
|
||||
|
||||
/@babel/plugin-transform-react-jsx@7.19.0(@babel/core@7.12.9):
|
||||
|
@ -8951,8 +8951,8 @@ packages:
|
|||
dependencies:
|
||||
'@babel/core': 7.21.3
|
||||
'@babel/helper-module-imports': 7.16.0
|
||||
'@babel/helper-plugin-utils': 7.14.5
|
||||
babel-plugin-polyfill-corejs2: 0.3.0(@babel/core@7.21.3)
|
||||
'@babel/helper-plugin-utils': 7.20.2
|
||||
babel-plugin-polyfill-corejs2: 0.3.3(@babel/core@7.21.3)
|
||||
babel-plugin-polyfill-corejs3: 0.4.0(@babel/core@7.21.3)
|
||||
babel-plugin-polyfill-regenerator: 0.3.0(@babel/core@7.21.3)
|
||||
semver: 6.3.0
|
||||
|
@ -10461,6 +10461,14 @@ packages:
|
|||
commander: 9.4.0
|
||||
dev: false
|
||||
|
||||
/@commander-js/extra-typings@10.0.3(commander@10.0.1):
|
||||
resolution: {integrity: sha512-OIw28QV/GlP8k0B5CJTRsl8IyNvd0R8C8rfo54Yz9P388vCNDgdNrFlKxZTGqps+5j6lSw3Ss9JTQwcur1w1oA==}
|
||||
peerDependencies:
|
||||
commander: 10.0.x
|
||||
dependencies:
|
||||
commander: 10.0.1
|
||||
dev: false
|
||||
|
||||
/@cspotcode/source-map-support@0.8.1:
|
||||
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
|
||||
engines: {node: '>=12'}
|
||||
|
@ -11961,6 +11969,7 @@ packages:
|
|||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.0
|
||||
'@jridgewell/sourcemap-codec': 1.4.14
|
||||
dev: true
|
||||
|
||||
/@jridgewell/trace-mapping@0.3.17:
|
||||
resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==}
|
||||
|
@ -20854,8 +20863,8 @@ packages:
|
|||
peerDependencies:
|
||||
postcss: ^8.1.0
|
||||
dependencies:
|
||||
browserslist: 4.21.4
|
||||
caniuse-lite: 1.0.30001418
|
||||
browserslist: 4.20.2
|
||||
caniuse-lite: 1.0.30001352
|
||||
fraction.js: 4.2.0
|
||||
normalize-range: 0.1.2
|
||||
picocolors: 1.0.0
|
||||
|
@ -22398,7 +22407,6 @@ packages:
|
|||
escalade: 3.1.1
|
||||
node-releases: 2.0.6
|
||||
picocolors: 1.0.0
|
||||
dev: true
|
||||
|
||||
/browserslist@4.20.4:
|
||||
resolution: {integrity: sha512-ok1d+1WpnU24XYN7oC3QWgTyMhY/avPJ/r9T00xxvUOIparA/gc+UPUMaod3i+G6s+nI2nUb9xZ5k794uIwShw==}
|
||||
|
@ -23443,6 +23451,11 @@ packages:
|
|||
engines: {node: '>=14'}
|
||||
dev: true
|
||||
|
||||
/commander@10.0.1:
|
||||
resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
|
||||
engines: {node: '>=14'}
|
||||
dev: false
|
||||
|
||||
/commander@2.1.0:
|
||||
resolution: {integrity: sha512-J2wnb6TKniXNOtoHS8TSrG9IOQluPrsmyAJ8oCUJOBmv+uLBCyPYAZkD2jFvw2DCzIXNnISIM01NIvr35TkBMQ==}
|
||||
engines: {node: '>= 0.6.x'}
|
||||
|
|
|
@ -2,4 +2,19 @@
|
|||
|
||||
## Description
|
||||
|
||||
Monorepo utilities and tooling.
|
||||
A set of CLI tools and scripts for managing the WooCommerce monorepo.
|
||||
|
||||
## Usage
|
||||
|
||||
This command is built on postinstall and can be run from the root of the project.
|
||||
To see a list of available commands you can run this from project root:
|
||||
|
||||
```
|
||||
pnpm utils
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
During development you can watch for changes via `pnpm start` in this directory, this will
|
||||
update the script referenced at the root package.json so you will see immediate changes as you
|
||||
re-run the CLI.
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const { existsSync } = require( 'fs' );
|
||||
const chalk = require( 'chalk' );
|
||||
const path = require( 'path' );
|
||||
|
||||
const nodeModulesDirectory = path.join( __dirname, '../', 'node_modules' );
|
||||
|
||||
if ( ! existsSync( nodeModulesDirectory ) ) {
|
||||
console.log(
|
||||
chalk.red(
|
||||
'The @woocommerce/monorepo-utils must be built before running the CLI.'
|
||||
)
|
||||
);
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'run `pnpm run build` from the root of the monorepo-utils package. or `pnpm install --filter monorepo-utils` from project root.'
|
||||
)
|
||||
);
|
||||
process.exit( 1 );
|
||||
}
|
||||
|
||||
const { program } = require( '../dist/index' );
|
||||
|
||||
program.parse( process.argv );
|
|
@ -9,12 +9,12 @@
|
|||
"main": "dist/index.js",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.0",
|
||||
"@commander-js/extra-typings": "^0.1.0",
|
||||
"@commander-js/extra-typings": "^10.0.3",
|
||||
"@octokit/graphql": "4.8.0",
|
||||
"@octokit/graphql-schema": "^14.1.0",
|
||||
"@types/uuid": "^9.0.1",
|
||||
"chalk": "^4.1.2",
|
||||
"commander": "^9.4.0",
|
||||
"commander": "^10.0.1",
|
||||
"dotenv": "^10.0.0",
|
||||
"figlet": "^1.6.0",
|
||||
"graphql": "^16.6.0",
|
||||
|
|
|
@ -20,10 +20,12 @@ import {
|
|||
} from '../../../core/github/repo';
|
||||
import { WPIncrement } from '../../../core/version';
|
||||
import { Logger } from '../../../core/logger';
|
||||
import { Options } from './types';
|
||||
import { getEnvVar } from '../../../core/environment';
|
||||
|
||||
const getNextReleaseBranch = async ( options: Options ) => {
|
||||
const getNextReleaseBranch = async ( options: {
|
||||
owner?: string;
|
||||
name?: string;
|
||||
} ) => {
|
||||
const latestReleaseVersion = await getLatestGithubReleaseVersion( options );
|
||||
const nextReleaseVersion = WPIncrement( latestReleaseVersion );
|
||||
const parsedNextReleaseVersion = parse( nextReleaseVersion );
|
||||
|
@ -53,7 +55,7 @@ export const branchCommand = new Command( 'branch' )
|
|||
'Branch to create the release branch from. Default: trunk',
|
||||
'trunk'
|
||||
)
|
||||
.action( async ( options: Options ) => {
|
||||
.action( async ( options ) => {
|
||||
const { source, branch, owner, name, dryRun } = options;
|
||||
const isGithub = getEnvVar( 'CI' );
|
||||
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
export type Options = {
|
||||
github?: boolean;
|
||||
dryRun?: boolean;
|
||||
owner?: string;
|
||||
name?: string;
|
||||
branch?: string;
|
||||
source?: string;
|
||||
};
|
|
@ -11,7 +11,6 @@ import { getLatestGithubReleaseVersion } from '../../../core/github/repo';
|
|||
import { octokitWithAuth } from '../../../core/github/api';
|
||||
import { setGithubMilestoneOutputs } from './utils';
|
||||
import { WPIncrement } from '../../../core/version';
|
||||
import { Options } from './types';
|
||||
import { Logger } from '../../../core/logger';
|
||||
import { getEnvVar } from '../../../core/environment';
|
||||
|
||||
|
@ -32,7 +31,7 @@ export const milestoneCommand = new Command( 'milestone' )
|
|||
'-m --milestone <milestone>',
|
||||
'Milestone to create. Next milestone is gathered from Github if none is supplied'
|
||||
)
|
||||
.action( async ( options: Options ) => {
|
||||
.action( async ( options ) => {
|
||||
const { owner, name, dryRun, milestone } = options;
|
||||
const isGithub = getEnvVar( 'CI' );
|
||||
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
export type Options = {
|
||||
github?: boolean;
|
||||
dryRun?: boolean;
|
||||
owner?: string;
|
||||
name?: string;
|
||||
milestone?: string;
|
||||
};
|
|
@ -14,3 +14,7 @@ export const getEnvVar = ( varName: string, isRequired = false ) => {
|
|||
|
||||
return value || '';
|
||||
};
|
||||
|
||||
export const isGithubCI = () => {
|
||||
return process.env.GITHUB_ACTIONS === 'true';
|
||||
};
|
||||
|
|
|
@ -7,7 +7,7 @@ import chalk from 'chalk';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getEnvVar } from './environment';
|
||||
import { getEnvVar, isGithubCI } from './environment';
|
||||
|
||||
const LOGGING_LEVELS: Record< string, number > = {
|
||||
verbose: 3,
|
||||
|
@ -28,7 +28,7 @@ export class Logger {
|
|||
static error( err: unknown, failOnErr = true ) {
|
||||
if ( Logger.loggingLevel >= LOGGING_LEVELS.error ) {
|
||||
if ( err instanceof Error ) {
|
||||
error( chalk.red( err.message ) );
|
||||
error( chalk.red( `${ err.message }\n${ err.stack }` ) );
|
||||
} else if ( typeof err === 'string' ) {
|
||||
error( chalk.red( err ) );
|
||||
} else {
|
||||
|
@ -55,21 +55,26 @@ export class Logger {
|
|||
}
|
||||
|
||||
static startTask( message: string ) {
|
||||
if ( Logger.loggingLevel > LOGGING_LEVELS.silent ) {
|
||||
if ( Logger.loggingLevel > LOGGING_LEVELS.silent && ! isGithubCI() ) {
|
||||
const spinner = ora( chalk.green( `${ message }...` ) ).start();
|
||||
Logger.lastSpinner = spinner;
|
||||
} else if ( isGithubCI() ) {
|
||||
Logger.notice( message );
|
||||
}
|
||||
}
|
||||
|
||||
static endTask() {
|
||||
if (
|
||||
Logger.loggingLevel > LOGGING_LEVELS.silent &&
|
||||
Logger.lastSpinner
|
||||
Logger.lastSpinner &&
|
||||
! isGithubCI()
|
||||
) {
|
||||
Logger.lastSpinner.succeed(
|
||||
`${ Logger.lastSpinner.text } complete.`
|
||||
);
|
||||
Logger.lastSpinner = null;
|
||||
} else if ( isGithubCI() ) {
|
||||
Logger.notice( 'Task complete.' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,5 +3,48 @@
|
|||
*/
|
||||
import { promisify } from 'util';
|
||||
import { exec } from 'child_process';
|
||||
import { RequestOptions, request } from 'https';
|
||||
import { IncomingMessage } from 'http';
|
||||
|
||||
export const execAsync = promisify( exec );
|
||||
|
||||
// Map just the raw value types of IncomingMessage to a new type for the response which includes a body string.
|
||||
type HttpsResponse = {
|
||||
// I think it's fine to use this type this way just to exclude functions from the IncomingMessage type.
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
[ K in keyof IncomingMessage as IncomingMessage[ K ] extends Function
|
||||
? never
|
||||
: K ]: IncomingMessage[ K ];
|
||||
} & {
|
||||
body: string;
|
||||
};
|
||||
|
||||
// A wrapper around https.request that returns a promise encapulating the response body and other response attributes.
|
||||
export const requestAsync = (
|
||||
options: string | RequestOptions | URL,
|
||||
data?: string | Uint8Array | Buffer
|
||||
) => {
|
||||
return new Promise< HttpsResponse >( ( resolve, reject ) => {
|
||||
const req = request( options, ( res ) => {
|
||||
let body = '';
|
||||
res.setEncoding( 'utf8' );
|
||||
res.on( 'data', ( chunk ) => {
|
||||
body += chunk;
|
||||
} );
|
||||
res.on( 'end', () => {
|
||||
const httpsResponse: HttpsResponse = {
|
||||
...res,
|
||||
body,
|
||||
};
|
||||
resolve( httpsResponse );
|
||||
} );
|
||||
} );
|
||||
req.on( 'error', ( err ) => {
|
||||
reject( err );
|
||||
} );
|
||||
if ( data ) {
|
||||
req.write( data );
|
||||
}
|
||||
req.end();
|
||||
} );
|
||||
};
|
||||
|
|
|
@ -9,12 +9,39 @@ import chalk from 'chalk';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import CodeFreeze from './code-freeze/commands';
|
||||
import Slack from './slack/commands/slack';
|
||||
import { Logger } from './core/logger';
|
||||
import { isGithubCI } from './core/environment';
|
||||
|
||||
console.log(
|
||||
chalk.rgb( 150, 88, 138 ).bold( figlet.textSync( 'WooCommerce Utilities' ) )
|
||||
);
|
||||
if ( ! isGithubCI() ) {
|
||||
Logger.notice(
|
||||
chalk
|
||||
.rgb( 150, 88, 138 )
|
||||
.bold( figlet.textSync( 'WooCommerce \n Utils' ) )
|
||||
);
|
||||
}
|
||||
|
||||
export const program = new Command()
|
||||
const program = new Command()
|
||||
.name( 'utils' )
|
||||
.description( 'Monorepo utilities' )
|
||||
.addCommand( CodeFreeze );
|
||||
.addCommand( CodeFreeze )
|
||||
.addCommand( Slack );
|
||||
|
||||
program.exitOverride();
|
||||
|
||||
const run = async () => {
|
||||
try {
|
||||
// parseAsync handles cases where the action is async and not async.
|
||||
await program.parseAsync( process.argv );
|
||||
} catch ( e ) {
|
||||
// if github ci, always error
|
||||
if ( isGithubCI() ) {
|
||||
Logger.error( e );
|
||||
} else if ( e.code !== 'commander.help' ) {
|
||||
// if not github ci, only error if not help
|
||||
Logger.error( e );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
run();
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
# Slack Utilities
|
||||
|
||||
Utilities for automated posting to Slack.
|
||||
|
||||
To see available commands run `pnpm utils slack --help` from the project root.
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Command } from '@commander-js/extra-typings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { slackMessageCommand } from './slack-message';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
|
||||
const program = new Command( 'slack' )
|
||||
.description( 'Slack message sending utilities' )
|
||||
.addCommand( slackMessageCommand, { isDefault: true } );
|
||||
|
||||
export default program;
|
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Command } from '@commander-js/extra-typings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Logger } from '../../../core/logger';
|
||||
import { requestAsync } from '../../../core/util';
|
||||
|
||||
type SlackResponse = {
|
||||
ok: boolean;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
export const slackMessageCommand = new Command( 'message' )
|
||||
.description( 'Send a plain-text message to a slack channel' )
|
||||
.argument(
|
||||
'<token>',
|
||||
'Slack authentication token bearing required scopes.'
|
||||
)
|
||||
.argument( '<text>', 'Text based message to send to the slack channel.' )
|
||||
.argument(
|
||||
'<channels...>',
|
||||
'Slack channels to send the message to. Pass as many as you like.'
|
||||
)
|
||||
.option(
|
||||
'--dont-fail',
|
||||
'Do not fail the command if a message fails to send to any channel.'
|
||||
)
|
||||
.action( async ( token, text, channels, { dontFail } ) => {
|
||||
Logger.startTask(
|
||||
`Attempting to send message to Slack for channels: ${ channels.join(
|
||||
','
|
||||
) }`
|
||||
);
|
||||
|
||||
const shouldFail = ! dontFail;
|
||||
|
||||
for ( const channel of channels ) {
|
||||
// Define the request options
|
||||
const options = {
|
||||
hostname: 'slack.com',
|
||||
path: '/api/chat.postMessage',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${ token }`,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const { statusCode, body } = await requestAsync(
|
||||
options,
|
||||
JSON.stringify( { channel, text } )
|
||||
);
|
||||
|
||||
Logger.endTask();
|
||||
|
||||
const response = JSON.parse( body ) as SlackResponse;
|
||||
|
||||
if ( ! response.ok || statusCode !== 200 ) {
|
||||
Logger.error(
|
||||
`Slack API returned an error: ${ response?.error }, message failed to send to ${ channel }.`,
|
||||
shouldFail
|
||||
);
|
||||
} else {
|
||||
Logger.notice(
|
||||
`Slack message sent successfully to channel: ${ channel }`
|
||||
);
|
||||
}
|
||||
} catch ( e: unknown ) {
|
||||
Logger.error( e, shouldFail );
|
||||
}
|
||||
}
|
||||
} );
|
Loading…
Reference in New Issue