diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 60dc3ba84..d753ae19e 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -60,6 +60,7 @@ lane :notarize_binary do api_key_path: APPLE_API_KEY_PATH ) end + Dir[File.join(BUILDS_FOLDER, '**', 'Studio-*.dmg')].each do |path| notarize( bundle_id: APPLE_BUNDLE_IDENTIFIER, @@ -113,6 +114,19 @@ def aws_upload_to_s3(bucket:, key:, file:, if_exists:, auto_prefix: false, dry_r ) end +def get_commit_count + # Get the most recent release tag (excluding pre-release tags) + tags = `git tag --sort=-v:refname`.strip.split("\n") + latest_tag = tags.find { |tag| !tag.include?('-') } rescue '' + + # Get commit count since the last tag + if latest_tag.empty? + `git rev-list --count HEAD`.strip.to_i + else + `git rev-list #{latest_tag}..HEAD --count`.strip.to_i + end +end + def distribute_builds( commit_hash: last_git_commit[:abbreviated_commit_hash], build_number: get_required_env('BUILDKITE_BUILD_NUMBER'), @@ -121,7 +135,8 @@ def distribute_builds( UI.message("Running in #{DRY_RUN ? 'DRY RUN' : 'NORMAL'} mode") # If we are distributing a build without a tag, i.e. a development build, we also want to update the latest build reference for distribution. update_latest = release_tag.nil? - suffix = release_tag.nil? ? "v#{PACKAGE_VERSION}-#{commit_hash}" : release_tag + commit_count = get_commit_count + suffix = release_tag.nil? ? "v#{PACKAGE_VERSION}-dev#{commit_count}" : release_tag filename_root = 'studio' bucket_folder = 'studio' diff --git a/scripts/generate-releases-manifest.mjs b/scripts/generate-releases-manifest.mjs index e3e64008b..c3c7dd475 100644 --- a/scripts/generate-releases-manifest.mjs +++ b/scripts/generate-releases-manifest.mjs @@ -6,20 +6,20 @@ // "darwin": { // "universal": { // "sha": "30a8251", -// "url": "https://cdn.a8c-ci.services/studio/studio-darwin-universal-30a8251.app.zip" +// "url": "https://cdn.a8c-ci.services/studio/studio-darwin-universal-v1.2.3-42.app.zip" // }, // "arm64": { // "sha": "30a8251", -// "url": "https://cdn.a8c-ci.services/studio/studio-darwin-arm64-30a8251.app.zip" +// "url": "https://cdn.a8c-ci.services/studio/studio-darwin-arm64-v1.2.3-42.app.zip" // }, // "x64": { // "sha": "30a8251", -// "url": "https://cdn.a8c-ci.services/studio/studio-darwin-x64-30a8251.app.zip" +// "url": "https://cdn.a8c-ci.services/studio/studio-darwin-x64-v1.2.3-42.app.zip" // } // }, // "win32": { // "sha": "30a8251", -// "url": "https://cdn.a8c-ci.services/studio/studio-win32-30a8251.exe.zip" +// "url": "https://cdn.a8c-ci.services/studio/studio-win32-v1.2.3-42-full.nupkg" // } // }, // "1.0.0": { @@ -45,6 +45,7 @@ import https from 'https'; import path from 'path'; import { fileURLToPath } from 'url'; import packageJson from '../package.json' assert { type: 'json' }; +import { getLatestTag, getCommitCount } from './lib/git-utils.mjs'; const cdnURL = 'https://cdn.a8c-ci.services/studio'; const baseName = 'studio'; @@ -52,12 +53,15 @@ const baseName = 'studio'; const currentCommit = child_process.execSync( 'git rev-parse --short HEAD' ).toString().trim(); const { version } = packageJson; const isDevBuild = process.env.IS_DEV_BUILD; +const latestTag = getLatestTag(); +const commitCount = getCommitCount( latestTag ); const __dirname = path.dirname( fileURLToPath( import.meta.url ) ); console.log( `Version: ${ version }` ); console.log( `Is dev build: ${ isDevBuild }` ); console.log( `Current commit: ${ currentCommit }` ); +console.log( `Commit count since ${ latestTag || 'start' }: ${ commitCount }` ); console.log( 'Downloading current manifest ...' ); @@ -123,11 +127,11 @@ const releasesData = JSON.parse( await fs.readFile( releasesPath, 'utf8' ) ); if ( isDevBuild ) { console.log( 'Overriding latest dev release ...' ); - if ( ! currentCommit ) { - // Without the latest commit hash we can't determine what the zip filename will be. + if ( ! commitCount ) { + // Without the commit count we can't determine what the zip filename will be. // Are you sure you're running this script in a CI environment? // You can develop locally by setting the GITHUB_SHA envvar before running this script. - throw new Error( 'Missing latest commit hash' ); + throw new Error( 'Missing commit count' ); } releasesData[ 'dev' ] = releasesData[ 'dev' ] ?? {}; @@ -136,22 +140,22 @@ if ( isDevBuild ) { releasesData[ 'dev' ][ 'darwin' ] = releasesData[ 'dev' ][ 'darwin' ] ?? {}; releasesData[ 'dev' ][ 'darwin' ][ 'universal' ] = { sha: currentCommit, - url: `${ cdnURL }/${ baseName }-darwin-universal-v${ version }-${ currentCommit }.app.zip`, + url: `${ cdnURL }/${ baseName }-darwin-universal-v${ version }-dev${ commitCount }.app.zip`, }; releasesData[ 'dev' ][ 'darwin' ][ 'x64' ] = { sha: currentCommit, - url: `${ cdnURL }/${ baseName }-darwin-x64-v${ version }-${ currentCommit }.app.zip`, + url: `${ cdnURL }/${ baseName }-darwin-x64-v${ version }-dev${ commitCount }.app.zip`, }; releasesData[ 'dev' ][ 'darwin' ][ 'arm64' ] = { sha: currentCommit, - url: `${ cdnURL }/${ baseName }-darwin-arm64-v${ version }-${ currentCommit }.app.zip`, + url: `${ cdnURL }/${ baseName }-darwin-arm64-v${ version }-dev${ commitCount }.app.zip`, }; // Windows const windowsReleaseInfo = await getWindowsReleaseInfo(); releasesData[ 'dev' ][ 'win32' ] = { sha: windowsReleaseInfo.sha1, - url: `${ cdnURL }/${ baseName }-win32-v${ version }-${ currentCommit }-full.nupkg`, + url: `${ cdnURL }/${ baseName }-win32-v${ version }-dev${ commitCount }-full.nupkg`, size: windowsReleaseInfo.size, }; diff --git a/scripts/lib/git-utils.mjs b/scripts/lib/git-utils.mjs new file mode 100644 index 000000000..c02cb1f28 --- /dev/null +++ b/scripts/lib/git-utils.mjs @@ -0,0 +1,43 @@ +import * as child_process from 'child_process'; + +/** + * Get the most recent release tag (excluding pre-release tags) + * @returns {string} The latest release tag or empty string if none found + */ +export const getLatestTag = () => { + try { + // List all tags sorted by version, then filter for release tags only + const tags = child_process + .execSync( 'git tag --sort=-v:refname' ) + .toString() + .trim() + .split( '\n' ); + + // Find first tag that doesn't include '-' (no pre-release part) + const latestReleaseTag = tags.find( ( tag ) => ! tag.includes( '-' ) ); + return latestReleaseTag || ''; + } catch ( error ) { + // If no tags exist, return empty string + return ''; + } +}; + +/** + * Get commit count since the last tag + * @param {string} latestTag - The tag to count commits from + * @returns {number} Number of commits since the tag, or total commits if no tag + */ +export const getCommitCount = ( latestTag ) => { + try { + if ( latestTag ) { + return parseInt( + child_process.execSync( `git rev-list ${ latestTag }..HEAD --count` ).toString().trim(), + 10 + ); + } + // If no tags exist, count all commits + return parseInt( child_process.execSync( 'git rev-list --count HEAD' ).toString().trim(), 10 ); + } catch ( error ) { + throw new Error( 'Failed to get commit count: ' + error.message ); + } +}; diff --git a/scripts/prepare-dev-build-version.mjs b/scripts/prepare-dev-build-version.mjs index e3a3a0993..ced553007 100644 --- a/scripts/prepare-dev-build-version.mjs +++ b/scripts/prepare-dev-build-version.mjs @@ -1,27 +1,28 @@ // Rewrites the version in package.json so it includes the `-dev.abcd` style suffix of dev builds. -import * as child_process from 'child_process'; import fs from 'fs/promises'; import path from 'path'; import { fileURLToPath } from 'url'; +import { getLatestTag, getCommitCount } from './lib/git-utils.mjs'; const __dirname = path.dirname( fileURLToPath( import.meta.url ) ); -const currentCommit = child_process.execSync( 'git rev-parse --short HEAD' ).toString().trim(); +const latestTag = getLatestTag(); +const commitCount = getCommitCount( latestTag ); -if ( ! currentCommit ) { +if ( ! commitCount ) { // Are you trying to dev on the build scripts outside of CI? // You will need to define the GITHUB_SHA or BUILDKITE_COMMIT environment // variable before running build scripts. e.g. // GITHUB_SHA=abcdef1234567890 node ./scripts/prepare-dev-build-version.mjs - throw new Error( 'Missing commit hash' ); + throw new Error( 'Missing commit count' ); } const packageJsonPath = path.resolve( __dirname, '../package.json' ); const packageJsonText = await fs.readFile( packageJsonPath, 'utf-8' ); const packageJson = JSON.parse( packageJsonText ); -const devVersion = `${ packageJson.version.split( '-' )[ 0 ] }-dev.${ currentCommit }`; +const devVersion = `${ packageJson.version.split( '-' )[ 0 ] }-dev${ commitCount }`; packageJson.version = devVersion; diff --git a/src/index.ts b/src/index.ts index bd70379a3..572e5dc8d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -39,14 +39,14 @@ import { setupUpdates } from 'src/updates'; import packageJson from '../package.json'; if ( ! isCLI() && ! process.env.IS_DEV_BUILD ) { - const { release, environment } = getSentryReleaseInfo( app.getVersion() ); + const { sentryRelease, isDevEnvironment } = getSentryReleaseInfo( app.getVersion() ); Sentry.init( { dsn: 'https://97693275b2716fb95048c6d12f4318cf@o248881.ingest.sentry.io/4506612776501248', debug: true, - enabled: process.env.NODE_ENV !== 'development' && process.env.NODE_ENV !== 'test', - release, - environment, + enabled: ! isDevEnvironment, + release: sentryRelease, + environment: isDevEnvironment ? 'development' : 'production', } ); } diff --git a/src/lib/sentry-release.ts b/src/lib/sentry-release.ts index f4ed99b0d..2b5274c27 100644 --- a/src/lib/sentry-release.ts +++ b/src/lib/sentry-release.ts @@ -1,17 +1,17 @@ +import { isDevRelease } from './version-utils'; + /** - * Get the Sentry release information based on the version + * Get the Sentry release info for the current version * @param version The version string from package.json or app.getVersion() */ export function getSentryReleaseInfo( version: string ) { - const [ baseVersionWithBeta ] = version.split( '-dev.' ); + // Handle both -dev.HASH and -devN formats for backward compatibility + const baseVersionWithBeta = version.replace( /(-dev\..*|-dev\d+)$/, '' ); const isDevEnvironment = - version.includes( '-dev.' ) || + isDevRelease( version ) || !! process.env.IS_DEV_BUILD || process.env.NODE_ENV === 'development'; const sentryRelease = `studio@${ baseVersionWithBeta }`; - return { - release: sentryRelease, - environment: isDevEnvironment ? 'development' : 'production', - }; + return { sentryRelease, isDevEnvironment }; } diff --git a/src/lib/version-utils.ts b/src/lib/version-utils.ts new file mode 100644 index 000000000..8ad447d3d --- /dev/null +++ b/src/lib/version-utils.ts @@ -0,0 +1,9 @@ +/** + * Checks if a version string represents a development release + * Handles both old format (-dev.HASH) and new format (-devN) + * @param version The version string to check + * @returns boolean indicating if this is a dev release + */ +export function isDevRelease( version: string ): boolean { + return /-dev\..*|-dev\d+/.test( version ); +} diff --git a/src/updates.ts b/src/updates.ts index c63c79895..2633e9732 100644 --- a/src/updates.ts +++ b/src/updates.ts @@ -2,6 +2,7 @@ import { app, autoUpdater, dialog } from 'electron'; import * as Sentry from '@sentry/electron/main'; import { __ } from '@wordpress/i18n'; import { AUTO_UPDATE_INTERVAL_MS } from 'src/constants'; +import { isDevRelease } from './lib/version-utils'; type UpdpaterState = | 'init' @@ -18,7 +19,7 @@ let timeout: NodeJS.Timeout | null = null; let showManualCheckDialogs = false; const shouldPoll = - process.env.NODE_ENV === 'production' && app.isPackaged && ! app.getVersion().includes( '-dev.' ); + process.env.NODE_ENV === 'production' && app.isPackaged && ! isDevRelease( app.getVersion() ); export function setupUpdates() { if ( process.env.E2E ) { diff --git a/webpack.plugins.ts b/webpack.plugins.ts index c930718ef..e8275b6a3 100644 --- a/webpack.plugins.ts +++ b/webpack.plugins.ts @@ -5,9 +5,9 @@ import type { WebpackPluginInstance } from 'webpack'; const ForkTsCheckerWebpackPlugin: typeof IForkTsCheckerWebpackPlugin = require( 'fork-ts-checker-webpack-plugin' ); const version = process.env.npm_package_version || ''; -const { release: sentryRelease, environment } = getSentryReleaseInfo( version ); +const { sentryRelease, isDevEnvironment } = getSentryReleaseInfo( version ); console.log( 'Sentry release version:', sentryRelease ); -console.log( 'Sentry environment:', environment ); +console.log( 'Sentry environment:', isDevEnvironment ? 'development' : 'production' ); export const plugins: WebpackPluginInstance[] = [ new ForkTsCheckerWebpackPlugin( { @@ -19,7 +19,7 @@ export const plugins: WebpackPluginInstance[] = [ }, } ), // Sentry must be the last plugin - environment !== 'development' && + ! isDevEnvironment && !! process.env.SENTRY_AUTH_TOKEN && sentryWebpackPlugin( { authToken: process.env.SENTRY_AUTH_TOKEN,