From 2e979e0dc81ccc1c55ad22067f90ba13a205925f Mon Sep 17 00:00:00 2001 From: Ashley Gibson Date: Tue, 25 Mar 2025 15:36:10 +0000 Subject: [PATCH 01/16] Add support for non-interactive mode --- helpers/arguments.js | 4 +++ tasks/deploy.js | 36 ++++++++++++++++---- tasks/github.js | 81 +++++++++++++++++++++++++++++++++++++------- tasks/prompt.js | 12 ++++++- 4 files changed, 113 insertions(+), 20 deletions(-) create mode 100644 helpers/arguments.js diff --git a/helpers/arguments.js b/helpers/arguments.js new file mode 100644 index 0000000..173c6be --- /dev/null +++ b/helpers/arguments.js @@ -0,0 +1,4 @@ +export function isNonInteractive() +{ + return process.argv.includes('--non-interactive'); +} diff --git a/tasks/deploy.js b/tasks/deploy.js index a7d8fd4..fc88d6d 100644 --- a/tasks/deploy.js +++ b/tasks/deploy.js @@ -17,7 +17,7 @@ import { gitHubCreateDocsIssueTask, gitHubCreateReleaseTask, gitHubGetReleaseIssueTask, - gitHubGetWcIssuesTask + gitHubGetWcIssuesTask, gitHubUploadZipToReleaseTask } from './github.js' import { shellGitEnsureCleanWorkingCopyTask, @@ -33,6 +33,7 @@ import { zipTask } from './zip.js' import { validateReadmeHeadersTask } from './validate.js' import { lintScriptsTask, lintStylesTask } from './lint.js' import { copyWcRepoTask, copyWpAssetsTask, copyWpTagTask, copyWpTrunkTask } from './copy.js' +import { isNonInteractive } from '../helpers/arguments.js' let validatedEnvVariables = false @@ -79,7 +80,13 @@ const deployTask = (done) => { fetchLatestWpWcVersionsTask, bumpMinReqsTask, // prompt for the version to deploy as - promptDeployTask, + function (cb) { + if (! isNonInteractive()) { + return promptDeployTask() + } else { + return cb() + } + }, function (cb) { if (sake.options.version === 'skip') { log.error(chalk.red('Deploy skipped!')) @@ -88,7 +95,13 @@ const deployTask = (done) => { cb() }, // replace version number & date - replaceVersionTask, + function (cb) { + if (! isNonInteractive()) { + return replaceVersionTask() + } else { + return cb() + } + }, // delete prerelease, if any cleanPrereleaseTask, // build the plugin - compiles and copies to build dir @@ -103,7 +116,13 @@ const deployTask = (done) => { cb() }, // git commit & push - shellGitPushUpdateTask, + function (cb) { + if (! isNonInteractive()) { + return shellGitPushUpdateTask() + } else { + return cb() + } + }, // create the zip, which will be attached to the releases zipTask, // create releases, attaching the zip @@ -270,10 +289,15 @@ const deployCreateReleasesTask = (done) => { sake.options.repo = sake.config.deploy.dev.name sake.options.prefix_release_tag = sake.config.multiPluginRepo cb() - }, - gitHubCreateReleaseTask + } ] + if (isNonInteractive()) { + tasks.push(gitHubUploadZipToReleaseTask) + } else { + tasks.push(gitHubCreateReleaseTask) + } + return gulp.series(tasks)(done) } deployCreateReleasesTask.displayName = 'deploy_create_releases' diff --git a/tasks/github.js b/tasks/github.js index ca6e7a5..0b0fda3 100644 --- a/tasks/github.js +++ b/tasks/github.js @@ -9,6 +9,8 @@ import dateFormat from 'dateformat' import log from 'fancy-log' import sake from '../lib/sake.js' import gulp from 'gulp' +import minimist from 'minimist'; +import { isNonInteractive } from '../helpers/arguments.js' let githubInstances = {} @@ -131,6 +133,10 @@ gitHubGetWcIssuesTask.displayName = 'github:get_wc_issues' * Creates a docs issue for the plugin */ const gitHubCreateDocsIssueTask = (done) => { + if (isNonInteractive()) { + return done() + } + if (! sake.config.deploy.docs) { log.warn(chalk.yellow('No docs repo configured for ' + sake.getPluginName() + ', skipping')) return done() @@ -195,8 +201,8 @@ const gitHubCreateReleaseTask = (done) => { let github = getGithub(sake.options.owner === sake.config.deploy.production.owner ? 'production' : 'dev') let version = sake.getPluginVersion() - let zipName = `${sake.config.plugin.id}.${version}.zip` - let zipPath = path.join(process.cwd(), sake.config.paths.build, zipName) + const [zipName, zipPath] = getZipNameAndPath() + let tasks = [] // prepare a zip if it doesn't already exist @@ -218,17 +224,9 @@ const gitHubCreateReleaseTask = (done) => { sake.options.release_url = result.data.html_url - github.repos.uploadReleaseAsset({ - url: result.data.upload_url, - name: zipName, - data: fs.readFileSync(zipPath), - headers: { - 'content-type': 'application/zip', - 'content-length': fs.statSync(zipPath).size - } - }).then(() => { - log('Plugin zip uploaded') - cb() + uploadZipToRelease(zipPath, zipName, result.data.upload_url).then(() => { + log('Plugin zip uploaded') + cb() }).catch((err) => { sake.throwError('Uploading release ZIP failed: ' + err.toString()) }) @@ -241,6 +239,62 @@ const gitHubCreateReleaseTask = (done) => { } gitHubCreateReleaseTask.displayName = 'github:create_release' +const gitHubUploadZipToReleaseTask = (done) => { + const argv = minimist(process.argv.slice(2)) + const releaseUrl = argv.release || null; + + if (! releaseUrl) { + sake.throwError('No release provided') + } + + const [zipName, zipPath] = getZipNameAndPath() + + log(`Uploading zip ${zipName} to release ${releaseUrl}`) + + let tasks = [] + + // prepare a zip if it doesn't already exist + if (! fs.existsSync(zipPath)) { + tasks.push(sake.options.deploy ? 'compress' : 'zip') + } + + tasks.push(function (cb) { + uploadZipToRelease(zipPath, zipName, releaseUrl).then(() => { + log('Plugin zip uploaded') + cb() + }).catch((err) => { + sake.throwError('Uploading release ZIP failed: ' + err.toString()) + }) + }) + + gulp.series(tasks)(done) +} +gitHubUploadZipToReleaseTask.displayName = 'github:upload_zip_to_release' + +function getZipNameAndPath() +{ + let version = sake.getPluginVersion() + let zipName = `${sake.config.plugin.id}.${version}.zip` + let zipPath = path.join(process.cwd(), sake.config.paths.build, zipName) + + return [zipName, zipPath] +} + +function uploadZipToRelease(zipPath, zipName, releaseUrl) +{ + let github = getGithub(sake.options.owner === sake.config.deploy.production.owner ? 'production' : 'dev') + + return github.repos.uploadReleaseAsset({ + url: releaseUrl, + name: zipName, + data: fs.readFileSync(zipPath), + headers: { + 'content-type': 'application/zip', + 'content-length': fs.statSync(zipPath).size + } + }) +} + /** * Create release milestones for each Tuesday */ @@ -337,6 +391,7 @@ export { gitHubGetWcIssuesTask, gitHubCreateDocsIssueTask, gitHubCreateReleaseTask, + gitHubUploadZipToReleaseTask, gitHubCreateReleaseMilestonesTask, gitHubCreateMonthMilestonesTask } diff --git a/tasks/prompt.js b/tasks/prompt.js index 94f4392..f6ee85f 100644 --- a/tasks/prompt.js +++ b/tasks/prompt.js @@ -6,6 +6,7 @@ import _ from 'lodash' import sake from '../lib/sake.js' import gulp from 'gulp' import { wcDeployTask } from './wc.js' +import { isNonInteractive } from '../helpers/arguments.js' function filterIncrement (value) { if (value[1] === 'custom') { @@ -102,13 +103,18 @@ promptDeployTask.displayName = 'prompt:deploy' * Internal task for prompting whether to upload the plugin to WooCommerce */ const promptWcUploadTask = (done) => { + const uploadSeries = gulp.series(wcDeployTask) + if (isNonInteractive()) { + return uploadSeries(done) + } + inquirer.prompt([{ type: 'confirm', name: 'upload_to_wc', message: 'Upload plugin to WooCommerce.com?' }]).then((answers) => { if (answers.upload_to_wc) { - gulp.series(wcDeployTask)(done) + uploadSeries(done) } else { log.error(chalk.red('Skipped uploading to WooCommerce.com')) done() @@ -121,6 +127,10 @@ promptWcUploadTask.displayName = 'prompt:wc_upload' * Internal task for prompting whether the release has been tested */ const promptTestedReleaseZipTask = (done) => { + if (isNonInteractive()) { + done() + } + inquirer.prompt([{ type: 'confirm', name: 'tested_release_zip', From 079bb2065e03e0e76aefa205a5f164a6f1a3c3c8 Mon Sep 17 00:00:00 2001 From: Ashley Gibson Date: Tue, 25 Mar 2025 15:41:15 +0000 Subject: [PATCH 02/16] Create hasGitRelease helper --- helpers/arguments.js | 18 ++++++++++++++++++ tasks/deploy.js | 8 ++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/helpers/arguments.js b/helpers/arguments.js index 173c6be..e7403c0 100644 --- a/helpers/arguments.js +++ b/helpers/arguments.js @@ -1,4 +1,22 @@ +import minimist from 'minimist' + +/** + * Determines if the command is being run in "non-interactive mode". If true, we should never present with prompts. + * @returns {boolean} + */ export function isNonInteractive() { return process.argv.includes('--non-interactive'); } + +/** + * Whether we already have a GitHub release for this deployment. If we don't, we'll be creating one. + * @returns {boolean} + */ +export function hasGitRelease() +{ + const argv = minimist(process.argv.slice(2)) + const releaseUrl = argv.release || null; + + return !! releaseUrl; +} diff --git a/tasks/deploy.js b/tasks/deploy.js index fc88d6d..e4cec88 100644 --- a/tasks/deploy.js +++ b/tasks/deploy.js @@ -33,7 +33,7 @@ import { zipTask } from './zip.js' import { validateReadmeHeadersTask } from './validate.js' import { lintScriptsTask, lintStylesTask } from './lint.js' import { copyWcRepoTask, copyWpAssetsTask, copyWpTagTask, copyWpTrunkTask } from './copy.js' -import { isNonInteractive } from '../helpers/arguments.js' +import { hasGitRelease, isNonInteractive } from '../helpers/arguments.js' let validatedEnvVariables = false @@ -96,7 +96,7 @@ const deployTask = (done) => { }, // replace version number & date function (cb) { - if (! isNonInteractive()) { + if (! hasGitRelease()) { return replaceVersionTask() } else { return cb() @@ -117,7 +117,7 @@ const deployTask = (done) => { }, // git commit & push function (cb) { - if (! isNonInteractive()) { + if (! hasGitRelease()) { return shellGitPushUpdateTask() } else { return cb() @@ -125,7 +125,7 @@ const deployTask = (done) => { }, // create the zip, which will be attached to the releases zipTask, - // create releases, attaching the zip + // create the release if it doesn't already exist, and attach the zip deployCreateReleasesTask, ] From ef6a28cfd62fbf168e01ecb622c5d9b560ac48d0 Mon Sep 17 00:00:00 2001 From: Ashley Gibson Date: Wed, 26 Mar 2025 12:33:29 +0000 Subject: [PATCH 03/16] Set up more options for dry runs, etc. --- helpers/arguments.js | 20 +++++++++++++++ tasks/deploy.js | 58 ++++++++++++++++++++++++++++++++------------ tasks/prompt.js | 2 +- 3 files changed, 63 insertions(+), 17 deletions(-) diff --git a/helpers/arguments.js b/helpers/arguments.js index e7403c0..604e91e 100644 --- a/helpers/arguments.js +++ b/helpers/arguments.js @@ -20,3 +20,23 @@ export function hasGitRelease() return !! releaseUrl; } + +/** + * Whether this is a dry run deployment. If true, the deploy will not actually happen. + * @returns {boolean} + */ +export function isDryRunDeploy() +{ + return process.argv.includes('--dry-run'); +} + +/** + * If specified, then no changes will be made/committed to the code base during a deployment. This should be used if + * you're specifying an _exact_ release to deploy, rather than having Sake create the release for you. The expectation + * here is that prior to deployment the code has already had all the versions/min-reqs bumped. + * @returns {boolean} + */ +export function withoutCodeChanges() +{ + return process.argv.includes('--without-code-changes'); +} diff --git a/tasks/deploy.js b/tasks/deploy.js index e4cec88..5195af0 100644 --- a/tasks/deploy.js +++ b/tasks/deploy.js @@ -33,7 +33,12 @@ import { zipTask } from './zip.js' import { validateReadmeHeadersTask } from './validate.js' import { lintScriptsTask, lintStylesTask } from './lint.js' import { copyWcRepoTask, copyWpAssetsTask, copyWpTagTask, copyWpTrunkTask } from './copy.js' -import { hasGitRelease, isNonInteractive } from '../helpers/arguments.js' +import { + hasGitRelease, + isDryRunDeploy, + isNonInteractive, + withoutCodeChanges +} from '../helpers/arguments.js' let validatedEnvVariables = false @@ -61,7 +66,9 @@ function validateEnvVariables () { const deployTask = (done) => { validateEnvVariables() - if (!sake.isDeployable()) { + // we only validate if a release hasn't been provided to us + // if we are provided a release then we have to assume version numbers, etc. have already been adjusted + if (! hasGitRelease() && !sake.isDeployable()) { sake.throwError('Plugin is not deployable: \n * ' + sake.getChangelogErrors().join('\n * ')) } @@ -77,8 +84,13 @@ const deployTask = (done) => { // ensure version is bumped bumpTask, // fetch the latest WP/WC versions & bump the "tested up to" values - fetchLatestWpWcVersionsTask, - bumpMinReqsTask, + function (cb) { + if (withoutCodeChanges()) { + return cb() + } + + return gulp.series(fetchLatestWpWcVersionsTask, bumpMinReqsTask) + }, // prompt for the version to deploy as function (cb) { if (! isNonInteractive()) { @@ -96,11 +108,11 @@ const deployTask = (done) => { }, // replace version number & date function (cb) { - if (! hasGitRelease()) { - return replaceVersionTask() - } else { + if (withoutCodeChanges()) { return cb() } + + return replaceVersionTask() }, // delete prerelease, if any cleanPrereleaseTask, @@ -117,24 +129,38 @@ const deployTask = (done) => { }, // git commit & push function (cb) { - if (! hasGitRelease()) { - return shellGitPushUpdateTask() - } else { + if (withoutCodeChanges() || isDryRunDeploy()) { return cb() } + + return shellGitPushUpdateTask() }, // create the zip, which will be attached to the releases zipTask, // create the release if it doesn't already exist, and attach the zip - deployCreateReleasesTask, + function (cb) { + if (! isDryRunDeploy()) { + return deployCreateReleasesTask() + } else { + return cb() + } + }, ] - if (sake.config.deploy.wooId && sake.config.deploy.type === 'wc') { - tasks.push(promptWcUploadTask) - } + if (isDryRunDeploy()) { + tasks.push(function(cb) { + log.info('Dry run deployment successful') - if (sake.config.deploy.type === 'wp') { - tasks.push(deployToWpRepoTask) + return cb() + }) + } else { + if (sake.config.deploy.wooId && sake.config.deploy.type === 'wc') { + tasks.push(promptWcUploadTask) + } + + if (sake.config.deploy.type === 'wp') { + tasks.push(deployToWpRepoTask) + } } // finally, create a docs issue, if necessary diff --git a/tasks/prompt.js b/tasks/prompt.js index f6ee85f..fef571a 100644 --- a/tasks/prompt.js +++ b/tasks/prompt.js @@ -128,7 +128,7 @@ promptWcUploadTask.displayName = 'prompt:wc_upload' */ const promptTestedReleaseZipTask = (done) => { if (isNonInteractive()) { - done() + return done() } inquirer.prompt([{ From d059ca45ae68075ef2b11f0643ee594c64eaae5f Mon Sep 17 00:00:00 2001 From: Ashley Gibson Date: Wed, 26 Mar 2025 12:48:11 +0000 Subject: [PATCH 04/16] Reorganize logic --- tasks/deploy.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tasks/deploy.js b/tasks/deploy.js index 5195af0..6f127ac 100644 --- a/tasks/deploy.js +++ b/tasks/deploy.js @@ -139,11 +139,11 @@ const deployTask = (done) => { zipTask, // create the release if it doesn't already exist, and attach the zip function (cb) { - if (! isDryRunDeploy()) { - return deployCreateReleasesTask() - } else { + if (withoutCodeChanges() || isDryRunDeploy()) { return cb() } + + return deployCreateReleasesTask() }, ] From 89cf78e8f1ee7e5de3f5c5ca0bcda3ca884ed9ab Mon Sep 17 00:00:00 2001 From: Ashley Gibson Date: Wed, 26 Mar 2025 12:51:37 +0000 Subject: [PATCH 05/16] Skip validation if no code changes --- tasks/deploy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/deploy.js b/tasks/deploy.js index 6f127ac..d929b4a 100644 --- a/tasks/deploy.js +++ b/tasks/deploy.js @@ -68,7 +68,7 @@ const deployTask = (done) => { // we only validate if a release hasn't been provided to us // if we are provided a release then we have to assume version numbers, etc. have already been adjusted - if (! hasGitRelease() && !sake.isDeployable()) { + if (! hasGitRelease() && ! withoutCodeChanges() && !sake.isDeployable()) { sake.throwError('Plugin is not deployable: \n * ' + sake.getChangelogErrors().join('\n * ')) } From 081d5ae9111c59e5789cd267e2d542e4d100fb21 Mon Sep 17 00:00:00 2001 From: Ashley Gibson Date: Wed, 26 Mar 2025 15:18:33 +0000 Subject: [PATCH 06/16] Adjust variable validation --- tasks/deploy.js | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/tasks/deploy.js b/tasks/deploy.js index d929b4a..8c247ea 100644 --- a/tasks/deploy.js +++ b/tasks/deploy.js @@ -47,10 +47,10 @@ let validatedEnvVariables = false function validateEnvVariables () { if (validatedEnvVariables) return - let variables = ['GITHUB_API_KEY', 'GITHUB_USERNAME', 'SAKE_PRE_RELEASE_PATH'] + let variables = ['GITHUB_API_KEY'] if (sake.config.deploy.type === 'wc') { - variables = variables.concat(['WC_CONSUMER_KEY', 'WC_CONSUMER_SECRET']) + variables = variables.concat(['WC_USERNAME', 'WC_APPLICATION_PASSWORD']) } if (sake.config.deploy.type === 'wp') { @@ -138,13 +138,7 @@ const deployTask = (done) => { // create the zip, which will be attached to the releases zipTask, // create the release if it doesn't already exist, and attach the zip - function (cb) { - if (withoutCodeChanges() || isDryRunDeploy()) { - return cb() - } - - return deployCreateReleasesTask() - }, + deployCreateReleasesTask ] if (isDryRunDeploy()) { @@ -318,10 +312,14 @@ const deployCreateReleasesTask = (done) => { } ] - if (isNonInteractive()) { + if (hasGitRelease()) { tasks.push(gitHubUploadZipToReleaseTask) - } else { + } else if (! isDryRunDeploy()) { tasks.push(gitHubCreateReleaseTask) + } else { + // if it wasn't a dry run we would have created a release + log.info('Dry run - skipping creation of release') + return done() } return gulp.series(tasks)(done) From f5c7a3bb39922b3c7f4c149ad8a976b7af892bd5 Mon Sep 17 00:00:00 2001 From: Ashley Gibson Date: Wed, 26 Mar 2025 15:18:56 +0000 Subject: [PATCH 07/16] Add one to test validatione rrors --- tasks/deploy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/deploy.js b/tasks/deploy.js index 8c247ea..032c4cc 100644 --- a/tasks/deploy.js +++ b/tasks/deploy.js @@ -47,7 +47,7 @@ let validatedEnvVariables = false function validateEnvVariables () { if (validatedEnvVariables) return - let variables = ['GITHUB_API_KEY'] + let variables = ['GITHUB_API_KEY', 'TO_TEST_VALIDATION_ERRORS'] if (sake.config.deploy.type === 'wc') { variables = variables.concat(['WC_USERNAME', 'WC_APPLICATION_PASSWORD']) From a6cf75f1138533d5e9a910a3ee87f7aa338fffd6 Mon Sep 17 00:00:00 2001 From: Ashley Gibson Date: Wed, 26 Mar 2025 16:00:17 +0000 Subject: [PATCH 08/16] Set exit code on errors --- lib/sake.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/sake.js b/lib/sake.js index bf774b9..99c1ae9 100644 --- a/lib/sake.js +++ b/lib/sake.js @@ -579,6 +579,7 @@ function initializeSake(config, options) { * Throws an error without the stack trace in gulp. */ exports.throwError = (message) => { + process.exitCode = 1; let err = new Error(chalk.red(message)) err.showStack = false throw err @@ -591,6 +592,7 @@ function initializeSake(config, options) { * see https://stackoverflow.com/a/30741722 */ exports.throwDeferredError = (message) => { + process.exitCode = 1; setTimeout(() => { exports.throwError(message) }) From b704b76fbfa5b74ecae9563c08b4995c6baaf955 Mon Sep 17 00:00:00 2001 From: Ashley Gibson Date: Thu, 27 Mar 2025 12:38:59 +0000 Subject: [PATCH 09/16] Propagate child process exit code up to main process --- bin/sake.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bin/sake.js b/bin/sake.js index b8e856d..254434c 100755 --- a/bin/sake.js +++ b/bin/sake.js @@ -28,4 +28,9 @@ const args = [ ])) // fire up gulp -spawn('node', args, { cwd: process.cwd(), stdio: 'inherit' }) +const child = spawn('node', args, { cwd: process.cwd(), stdio: 'inherit' }) + +// we need the exit code of the child process to propagate up to the main process +child.on('exit', function(code) { + process.exitCode = code +}) From f66ddbd4315117d47084b44493a7511611640ac2 Mon Sep 17 00:00:00 2001 From: Ashley Gibson Date: Thu, 27 Mar 2025 12:45:32 +0000 Subject: [PATCH 10/16] Remove test variable --- tasks/deploy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/deploy.js b/tasks/deploy.js index 032c4cc..8c247ea 100644 --- a/tasks/deploy.js +++ b/tasks/deploy.js @@ -47,7 +47,7 @@ let validatedEnvVariables = false function validateEnvVariables () { if (validatedEnvVariables) return - let variables = ['GITHUB_API_KEY', 'TO_TEST_VALIDATION_ERRORS'] + let variables = ['GITHUB_API_KEY'] if (sake.config.deploy.type === 'wc') { variables = variables.concat(['WC_USERNAME', 'WC_APPLICATION_PASSWORD']) From 3d4b6709719de41c33734d049bdcb8b8c7d89978 Mon Sep 17 00:00:00 2001 From: Ashley Gibson Date: Thu, 27 Mar 2025 12:53:05 +0000 Subject: [PATCH 11/16] Rework upload URL param --- helpers/arguments.js | 7 +++++-- tasks/github.js | 5 ++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/helpers/arguments.js b/helpers/arguments.js index 604e91e..3fdc2f1 100644 --- a/helpers/arguments.js +++ b/helpers/arguments.js @@ -15,10 +15,13 @@ export function isNonInteractive() */ export function hasGitRelease() { + return !! gitReleaseUploadUrl; +} + +export const gitReleaseUploadUrl = () => { const argv = minimist(process.argv.slice(2)) - const releaseUrl = argv.release || null; - return !! releaseUrl; + return argv['release-upload-url'] || null; } /** diff --git a/tasks/github.js b/tasks/github.js index 0b0fda3..1263609 100644 --- a/tasks/github.js +++ b/tasks/github.js @@ -10,7 +10,7 @@ import log from 'fancy-log' import sake from '../lib/sake.js' import gulp from 'gulp' import minimist from 'minimist'; -import { isNonInteractive } from '../helpers/arguments.js' +import { gitReleaseUploadUrl, isNonInteractive } from '../helpers/arguments.js' let githubInstances = {} @@ -240,8 +240,7 @@ const gitHubCreateReleaseTask = (done) => { gitHubCreateReleaseTask.displayName = 'github:create_release' const gitHubUploadZipToReleaseTask = (done) => { - const argv = minimist(process.argv.slice(2)) - const releaseUrl = argv.release || null; + const releaseUrl = gitReleaseUploadUrl() if (! releaseUrl) { sake.throwError('No release provided') From 9b194f2d322b6c45cb05f549ee7facba54c1306f Mon Sep 17 00:00:00 2001 From: Ashley Gibson Date: Thu, 27 Mar 2025 13:27:31 +0000 Subject: [PATCH 12/16] Account for pre-release path being undefined --- lib/sake.js | 2 +- tasks/clean.js | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/sake.js b/lib/sake.js index 99c1ae9..fa1c353 100644 --- a/lib/sake.js +++ b/lib/sake.js @@ -537,7 +537,7 @@ function initializeSake(config, options) { } exports.getPrereleasesPath = function () { - return this.resolvePath(process.env.SAKE_PRE_RELEASE_PATH) + return process.env.SAKE_PRE_RELEASE_PATH ? this.resolvePath(process.env.SAKE_PRE_RELEASE_PATH) : null } exports.normalizePath = function (p) { diff --git a/tasks/clean.js b/tasks/clean.js index 8988120..a503cf9 100644 --- a/tasks/clean.js +++ b/tasks/clean.js @@ -1,6 +1,7 @@ import path from 'node:path'; import del from 'del'; import sake from '../lib/sake.js' +import log from 'fancy-log'; /** * Clean dev directory from map files @@ -48,6 +49,11 @@ cleanWcRepoTask.displayName = 'clean:wc_repo' * Delete prerelease */ const cleanPrereleaseTask = (done) => { + if (sake.getPrereleasesPath() === null) { + log.info('No pre-release path defined -- skipping clean') + return done() + } + return del([ sake.getPrereleasesPath() + sake.config.plugin.id + '*.zip', sake.getPrereleasesPath() + sake.config.plugin.id + '*.txt' From b1a42e5a166d4daf4ace7d17f92c40a46d7107a3 Mon Sep 17 00:00:00 2001 From: Ashley Gibson Date: Thu, 27 Mar 2025 14:17:16 +0000 Subject: [PATCH 13/16] Add support for skipping linting --- helpers/arguments.js | 4 ++++ tasks/compile.js | 8 +++++++- tasks/scripts.js | 5 +++-- tasks/styles.js | 3 ++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/helpers/arguments.js b/helpers/arguments.js index 3fdc2f1..ca3e8e8 100644 --- a/helpers/arguments.js +++ b/helpers/arguments.js @@ -43,3 +43,7 @@ export function withoutCodeChanges() { return process.argv.includes('--without-code-changes'); } + +export const skipLinting = () => { + return process.argv.includes('--skip-linting'); +} diff --git a/tasks/compile.js b/tasks/compile.js index 1fb1de0..2214466 100644 --- a/tasks/compile.js +++ b/tasks/compile.js @@ -18,6 +18,7 @@ import { lintPhpTask } from './lint.js' import { minifyImagesTask } from './imagemin.js' import { makepotTask } from './makepot.js' import { stylesTask } from './styles.js' +import { skipLinting } from '../helpers/arguments.js' const sass = gulpSaas(dartSaas); /************************** Scripts */ @@ -152,7 +153,12 @@ compileStyles.displayName = 'compile:styles' // Compile all plugin assets const compile = (done) => { // default compile tasks - let tasks = [lintPhpTask, 'scripts', stylesTask, minifyImagesTask] // NOTE: do not import the `scripts` constant here, otherwise it creates a circular dependency + let tasks = ['scripts', stylesTask, minifyImagesTask] // NOTE: do not import the `scripts` constant here, otherwise it creates a circular dependency + + // lint PHP unless told not to + if (! skipLinting()) { + tasks.push(lintPhpTask) + } // unless exclusively told not to, generate the POT file as well if (!sake.options.skip_pot) { diff --git a/tasks/scripts.js b/tasks/scripts.js index dab4199..7e8ad2e 100644 --- a/tasks/scripts.js +++ b/tasks/scripts.js @@ -2,6 +2,7 @@ import gulp from 'gulp' import { lintCoffeeTask, lintJsTask, lintScriptsTask } from './lint.js' import { compileBlocksTask, compileCoffeeTask, compileJsTask, compileScripts } from './compile.js' import sake from '../lib/sake.js' +import { skipLinting } from '../helpers/arguments.js' /** * The main task @@ -9,8 +10,8 @@ import sake from '../lib/sake.js' const scriptsTask = (done) => { let tasks = [lintScriptsTask, compileScripts] - // don't lint styles if they have already been linted, unless we're watching - if (! sake.isWatching && gulp.lastRun(lintScriptsTask)) { + // don't lint scripts if they have already been linted, unless we're watching + if ((! sake.isWatching && gulp.lastRun(lintScriptsTask)) || skipLinting()) { tasks.shift() } diff --git a/tasks/styles.js b/tasks/styles.js index 14a3af3..7206dda 100644 --- a/tasks/styles.js +++ b/tasks/styles.js @@ -2,12 +2,13 @@ import gulp from 'gulp' import sake from '../lib/sake.js' import { lintStylesTask } from './lint.js' import { compileStyles } from './compile.js' +import { skipLinting } from '../helpers/arguments.js' const stylesTask = (done) => { let tasks = [lintStylesTask, compileStyles] // don't lint styles if they have already been linted, unless we're watching - if (!sake.isWatching && gulp.lastRun(lintStylesTask)) { + if ((!sake.isWatching && gulp.lastRun(lintStylesTask)) || skipLinting()) { tasks.shift() } From 72757abd49f58fc29d4a59011dc59906b7551677 Mon Sep 17 00:00:00 2001 From: Ashley Gibson Date: Thu, 27 Mar 2025 15:23:04 +0000 Subject: [PATCH 14/16] Create a new separate deploy task --- helpers/arguments.js | 10 ++++++ tasks/deploy.js | 53 +++++++++++++++++++++++++++-- tasks/github.js | 81 +++++++++++++++++++++++++++++++------------- 3 files changed, 119 insertions(+), 25 deletions(-) diff --git a/helpers/arguments.js b/helpers/arguments.js index ca3e8e8..00b096d 100644 --- a/helpers/arguments.js +++ b/helpers/arguments.js @@ -24,6 +24,16 @@ export const gitReleaseUploadUrl = () => { return argv['release-upload-url'] || null; } +/** + * Gets the name of the GitHub "release tag" to deploy. + * @returns {string|null} + */ +export const gitReleaseTag = () => { + const argv = minimist(process.argv.slice(2)) + + return argv['release-tag'] || null; +} + /** * Whether this is a dry run deployment. If true, the deploy will not actually happen. * @returns {boolean} diff --git a/tasks/deploy.js b/tasks/deploy.js index 8c247ea..d6a1115 100644 --- a/tasks/deploy.js +++ b/tasks/deploy.js @@ -11,7 +11,7 @@ import replace from 'gulp-replace' import replaceTask from 'gulp-replace-task' import { promptDeployTask, promptTestedReleaseZipTask, promptWcUploadTask } from './prompt.js' import { bumpMinReqsTask, bumpTask } from './bump.js' -import { cleanPrereleaseTask, cleanWcRepoTask, cleanWpAssetsTask, cleanWpTrunkTask } from './clean.js' +import { cleanBuildTask, cleanPrereleaseTask, cleanWcRepoTask, cleanWpAssetsTask, cleanWpTrunkTask } from './clean.js' import { buildTask } from './build.js' import { gitHubCreateDocsIssueTask, @@ -32,8 +32,9 @@ import { import { zipTask } from './zip.js' import { validateReadmeHeadersTask } from './validate.js' import { lintScriptsTask, lintStylesTask } from './lint.js' -import { copyWcRepoTask, copyWpAssetsTask, copyWpTagTask, copyWpTrunkTask } from './copy.js' +import { copyBuildTask, copyWcRepoTask, copyWpAssetsTask, copyWpTagTask, copyWpTrunkTask } from './copy.js' import { + gitReleaseTag, hasGitRelease, isDryRunDeploy, isNonInteractive, @@ -60,6 +61,53 @@ function validateEnvVariables () { sake.validateEnvironmentVariables(variables) } +/** + * Deploys the plugin, using a specific Git release + * This differs from {@see deployTask()} in that this task does NOT do any code changes to your working copy. + * It simply bundles up your code as-is, zips it, uploads it to the release you provided, and deploys it. + * It's expected that the plugin is already "fully built" at the time you run this. + */ +const deployFromReleaseTask = (done) => { + validateEnvVariables() + + if (! gitReleaseTag()) { + sake.throwError('Missing required GitHub release tag') + } + + // indicate that we are deploying + sake.options.deploy = true + + let tasks = [ + // clean the build directory + cleanBuildTask, + // copy plugin files to the build directory + copyBuildTask, + // create the zip, which will be attached to the releases + zipTask, + // upload the zip to the release, + gitHubUploadZipToReleaseTask + ] + + if (isDryRunDeploy()) { + tasks.push(function(cb) { + log.info('Dry run deployment successful') + + return cb() + }) + } else { + if (sake.config.deploy.wooId && sake.config.deploy.type === 'wc') { + tasks.push(promptWcUploadTask) + } + + if (sake.config.deploy.type === 'wp') { + tasks.push(deployToWpRepoTask) + } + } + + return gulp.series(tasks)(done) +} +deployFromReleaseTask.displayName = 'deploy:git-release' + /** * Deploy the plugin */ @@ -491,6 +539,7 @@ const fetchLatestWpWcVersionsTask = (done) => { fetchLatestWpWcVersionsTask.displayName = 'fetch_latest_wp_wc_versions' export { + deployFromReleaseTask, deployTask, deployPreflightTask, deployValidateFrameworkVersionTask, diff --git a/tasks/github.js b/tasks/github.js index 1263609..fd82bf1 100644 --- a/tasks/github.js +++ b/tasks/github.js @@ -10,7 +10,7 @@ import log from 'fancy-log' import sake from '../lib/sake.js' import gulp from 'gulp' import minimist from 'minimist'; -import { gitReleaseUploadUrl, isNonInteractive } from '../helpers/arguments.js' +import { gitReleaseTag, gitReleaseUploadUrl, isNonInteractive } from '../helpers/arguments.js' let githubInstances = {} @@ -201,7 +201,7 @@ const gitHubCreateReleaseTask = (done) => { let github = getGithub(sake.options.owner === sake.config.deploy.production.owner ? 'production' : 'dev') let version = sake.getPluginVersion() - const [zipName, zipPath] = getZipNameAndPath() + const {zipName, zipPath} = getZipNameAndPath() let tasks = [] @@ -240,43 +240,78 @@ const gitHubCreateReleaseTask = (done) => { gitHubCreateReleaseTask.displayName = 'github:create_release' const gitHubUploadZipToReleaseTask = (done) => { - const releaseUrl = gitReleaseUploadUrl() - - if (! releaseUrl) { - sake.throwError('No release provided') + if (! gitReleaseTag()) { + sake.throwError('No --release-tag provided.') } - const [zipName, zipPath] = getZipNameAndPath() + // why do we have to do this? lol + sake.options.owner = sake.config.deploy.dev.owner + sake.options.repo = sake.config.deploy.dev.name - log(`Uploading zip ${zipName} to release ${releaseUrl}`) + getGitHubReleaseFromTagName(gitReleaseTag()) + .then(response => { + const releaseUploadUrl = response.data.upload_url - let tasks = [] + const {zipName, zipPath} = getZipNameAndPath() - // prepare a zip if it doesn't already exist - if (! fs.existsSync(zipPath)) { - tasks.push(sake.options.deploy ? 'compress' : 'zip') - } + log(`Uploading zip ${zipName} to release ${releaseUploadUrl}`) - tasks.push(function (cb) { - uploadZipToRelease(zipPath, zipName, releaseUrl).then(() => { - log('Plugin zip uploaded') - cb() - }).catch((err) => { - sake.throwError('Uploading release ZIP failed: ' + err.toString()) - }) - }) + let tasks = [] - gulp.series(tasks)(done) + // prepare a zip if it doesn't already exist + if (! fs.existsSync(zipPath)) { + tasks.push(sake.options.deploy ? 'compress' : 'zip') + } + + tasks.push(function (cb) { + uploadZipToRelease(zipPath, zipName, releaseUploadUrl).then(() => { + log('Plugin zip uploaded') + cb() + }).catch((err) => { + sake.throwError('Uploading release ZIP failed: ' + err.toString()) + }) + }) + + gulp.series(tasks)(done) + }) + .catch((err) => { + sake.throwError('Failed to upload zip to release: '.err.toString()) + }) } gitHubUploadZipToReleaseTask.displayName = 'github:upload_zip_to_release' +/** + * + * @param {string} tagName + * @returns {Promise} + */ +function getGitHubReleaseFromTagName(tagName) +{ + let github = getGithub(sake.options.owner === sake.config.deploy.production.owner ? 'production' : 'dev') + const owner = sake.options.owner + const repo = sake.options.repo || sake.config.plugin.id + + if (! owner || ! repo) { + sake.throwError(chalk.yellow('The owner or the slug of the repo for ' + sake.getPluginName() + ' are missing.')) + } + + return github.request('GET /repos/{owner}/{repo}/releases/tags/{tag}', { + owner: owner, + repo: repo, + tag: tagName + }) +} + function getZipNameAndPath() { let version = sake.getPluginVersion() let zipName = `${sake.config.plugin.id}.${version}.zip` let zipPath = path.join(process.cwd(), sake.config.paths.build, zipName) - return [zipName, zipPath] + return { + zipName: zipName, + zipPath: zipPath + } } function uploadZipToRelease(zipPath, zipName, releaseUrl) From e335b09becbdf63109267fda013cd9de6f5e6e8f Mon Sep 17 00:00:00 2001 From: Ashley Gibson Date: Thu, 27 Mar 2025 16:39:36 +0000 Subject: [PATCH 15/16] Create a series to fetch and bump --- tasks/deploy.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tasks/deploy.js b/tasks/deploy.js index d6a1115..abb6b4e 100644 --- a/tasks/deploy.js +++ b/tasks/deploy.js @@ -132,13 +132,7 @@ const deployTask = (done) => { // ensure version is bumped bumpTask, // fetch the latest WP/WC versions & bump the "tested up to" values - function (cb) { - if (withoutCodeChanges()) { - return cb() - } - - return gulp.series(fetchLatestWpWcVersionsTask, bumpMinReqsTask) - }, + fetchAndBumpLatestWpWcVersions, // prompt for the version to deploy as function (cb) { if (! isNonInteractive()) { @@ -538,6 +532,9 @@ const fetchLatestWpWcVersionsTask = (done) => { } fetchLatestWpWcVersionsTask.displayName = 'fetch_latest_wp_wc_versions' +const fetchAndBumpLatestWpWcVersions = gulp.series(fetchLatestWpWcVersionsTask, bumpMinReqsTask) +fetchAndBumpLatestWpWcVersions.displayName = 'fetch_and_bump_latest_wp_wc_versions' + export { deployFromReleaseTask, deployTask, @@ -553,5 +550,6 @@ export { updateWcRepoTask, deployToWpRepoTask, copyToWpRepoTask, - fetchLatestWpWcVersionsTask + fetchLatestWpWcVersionsTask, + fetchAndBumpLatestWpWcVersions } From a03ef46bcba6a99ac2fe6cc0b8cf8f76ebf9dcce Mon Sep 17 00:00:00 2001 From: Ashley Gibson Date: Fri, 28 Mar 2025 12:38:09 +0000 Subject: [PATCH 16/16] Allow s pecifying what the new plugin version is --- helpers/arguments.js | 6 ++++++ tasks/deploy.js | 8 +++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/helpers/arguments.js b/helpers/arguments.js index 00b096d..72e9c5b 100644 --- a/helpers/arguments.js +++ b/helpers/arguments.js @@ -57,3 +57,9 @@ export function withoutCodeChanges() export const skipLinting = () => { return process.argv.includes('--skip-linting'); } + +export const newPluginVersion = () => { + const argv = minimist(process.argv.slice(2)) + + return argv['new-version'] || null; +} diff --git a/tasks/deploy.js b/tasks/deploy.js index abb6b4e..d519d8e 100644 --- a/tasks/deploy.js +++ b/tasks/deploy.js @@ -37,7 +37,7 @@ import { gitReleaseTag, hasGitRelease, isDryRunDeploy, - isNonInteractive, + isNonInteractive, newPluginVersion, withoutCodeChanges } from '../helpers/arguments.js' @@ -260,13 +260,15 @@ searchWtUpdateKeyTask.displayName = 'search:wt_update_key' * Internal task for replacing the version and date when deploying */ const replaceVersionTask = (done) => { - if (!sake.getVersionBump()) { + const bumpVersion = newPluginVersion() || sake.getVersionBump() + + if (!bumpVersion) { sake.throwError('No version replacement specified') } const versions = sake.getPrereleaseVersions(sake.getPluginVersion()) const versionReplacements = versions.map(version => { - return { match: version, replacement: () => sake.getVersionBump() } + return { match: version, replacement: () => bumpVersion } }) const filterChangelog = filter('**/{readme.md,readme.txt,changelog.txt}', { restore: true })