From 31e0407ae2520693c17aa5ca4bb0f0686170dc1d Mon Sep 17 00:00:00 2001 From: gapmiss <98914514+gapmiss@users.noreply.github.com> Date: Mon, 5 May 2025 06:09:50 -0500 Subject: [PATCH] README, release.mjs --- README.md | 11 +- package.json | 7 +- release.mjs | 384 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 390 insertions(+), 12 deletions(-) create mode 100644 release.mjs diff --git a/README.md b/README.md index c2ebc2e..14f16b3 100644 --- a/README.md +++ b/README.md @@ -132,18 +132,9 @@ Via BRAT (Beta Reviewer's Auto-update Tool): 3. Enter this repository, gapmiss/inline-callouts 4. Enable Vault Name plugin in the community plugin list - Manually: -1. download the latest [release archive](https://github.com/gapmiss/inline-callouts/releases/download/0.1.0/inline-callouts-v0.1.0.zip) -2. uncompress the downloaded archive -3. move the `inline-callouts` folder to `/path/to/vault/.obsidian/plugins/` -4. Settings > Community plugins > reload **Installed plugins** -5. enable plugin - -or: - -1. download `main.js`, `manifest.json` & `styles.css` +1. download `main.js`, `manifest.json` & `styles.css` from the latest [release](https://github.com/gapmiss/inline-callouts/releases/) 2. create a new folder `/path/to/vault/.obsidian/plugins/inline-callouts` 3. move all 3 files to `/path/to/vault/.obsidian/plugins/inline-callouts` 4. Settings > Community plugins > reload **Installed plugins** diff --git a/package.json b/package.json index 923549a..1b013fe 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,15 @@ { "name": "inline-callouts", "version": "0.1.0", - "description": "Inline callouts plugin for Obsidian (https://obsidian.md)", + "description": "Inline Callouts: Add inline callouts/badges to notes", "main": "main.js", "scripts": { "dev": "node esbuild.config.mjs", "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", - "version": "node version-bump.mjs && git add manifest.json versions.json" + "version": "node version-bump.mjs && git add manifest.json versions.json", + "release": "node release.mjs", + "release:minor": "node release.mjs minor", + "release:major": "node release.mjs major" }, "keywords": [], "author": "", diff --git a/release.mjs b/release.mjs new file mode 100644 index 0000000..617c92e --- /dev/null +++ b/release.mjs @@ -0,0 +1,384 @@ +import { readFileSync, writeFileSync } from 'fs'; +import { execSync } from 'child_process'; +import path from 'path'; + +/** + * Version Update and Deployment Automation Script + * + * 1. Update version in package.json + * 2. Synchronize version in manifest.json, versions.json + * 3. Run build + * 4. Create Git commit + * 5. Create Git tag + * 6. Push changes to GitHub (REQUIRES Github CLI: https://cli.github.com) + */ + +// Version type definitions +const VERSION_TYPES = { + PATCH: 'patch', + MINOR: 'minor', + MAJOR: 'major' +}; + +// Default settings +const DEFAULT_VERSION_TYPE = VERSION_TYPES.PATCH; +const RELEASE_FILES = ['main.js', 'manifest.json', 'styles.css']; +const MIN_APP_VERSION = "0.15.0" + +// Store original versions for rollback if needed +let originalPackageVersion = ''; +let originalManifestVersion = ''; + +/** + * Updates version value from the version string. + * @param {string} version - Current version (e.g., '0.2.2') + * @param {string} type - Update type ('patch', 'minor', 'major') + * @returns {string} Updated version + */ +const updateVersion = (version, type = DEFAULT_VERSION_TYPE) => { + const [major, minor, patch] = version.split('.').map(Number); + + switch (type) { + case VERSION_TYPES.MAJOR: + return `${major + 1}.0.0`; + case VERSION_TYPES.MINOR: + return `${major}.${minor + 1}.0`; + case VERSION_TYPES.PATCH: + default: + return `${major}.${minor}.${patch + 1}`; + } +}; + +/** + * Gets version information before any changes are made. + * @returns {string} Previous version + */ +const getPreviousVersion = () => { + const manifestPath = path.resolve(process.cwd(), 'manifest.json'); + const manifestData = JSON.parse(readFileSync(manifestPath, 'utf8')); + return manifestData.version; +} + +/** + * Updates version information in package.json file. + * @param {string} versionType - Version type to update + * @returns {string} New version + */ +const updatePackageVersion = (versionType) => { + const packagePath = path.resolve(process.cwd(), 'package.json'); + const packageData = JSON.parse(readFileSync(packagePath, 'utf8')); + + const currentVersion = packageData.version; + originalPackageVersion = currentVersion; // Store original version for possible rollback + const newVersion = updateVersion(currentVersion, versionType); + + packageData.version = newVersion; + writeFileSync(packagePath, JSON.stringify(packageData, null, '\t') + '\n'); + + console.log(`šŸ“¦ Updated package.json version: ${currentVersion} → ${newVersion}`); + return newVersion; +}; + +/** + * Updates version information in manifest.json file. + * @param {string} newVersion - Version to update + */ +const updateManifestVersion = (newVersion) => { + const manifestPath = path.resolve(process.cwd(), 'manifest.json'); + const manifestData = JSON.parse(readFileSync(manifestPath, 'utf8')); + + const currentVersion = manifestData.version; + originalManifestVersion = currentVersion; // Store original version for possible rollback + manifestData.version = newVersion; + + writeFileSync(manifestPath, JSON.stringify(manifestData, null, '\t') + '\n'); + console.log(`šŸ“‹ Updated manifest.json version: ${currentVersion} → ${newVersion}`); +}; + +/** + * Updates version information in versions.json file. + * @param {string} previousVersion - Previous version + * @param {string} newVersion - Version to update + */ +const updateVersionsVersion = (previousVersion, newVersion, minAppVersion) => { + const versionsPath = path.resolve(process.cwd(), 'versions.json'); + const versionsData = JSON.parse(readFileSync(versionsPath, 'utf8')); + + const manifestPath = path.resolve(process.cwd(), 'manifest.json'); + const manifestData = JSON.parse(readFileSync(manifestPath, 'utf8')); + + const currentVersion = manifestData.version; + versionsData[newVersion] = minAppVersion; + + writeFileSync(versionsPath, JSON.stringify(versionsData, null, '\t') + '\n'); + console.log(`šŸ“‹ Updated versions.json version: ${previousVersion} → ${newVersion}`); +}; + +/** + * Run project build + */ +const buildProject = () => { + try { + console.log('šŸ”Ø Starting project build...'); + execSync('npm run build', { stdio: 'inherit' }); + console.log('āœ… Build completed'); + return true; + } catch (error) { + console.error('āŒ Build failed:', error.message); + return false; + } +}; + +/** + * Rollback version changes if the release process fails + */ +const rollbackVersions = () => { + if (originalPackageVersion) { + try { + const packagePath = path.resolve(process.cwd(), 'package.json'); + const packageData = JSON.parse(readFileSync(packagePath, 'utf8')); + const currentVersion = packageData.version; + + packageData.version = originalPackageVersion; + writeFileSync(packagePath, JSON.stringify(packageData, null, '\t') + '\n'); + console.log(`ā™»ļø Rolled back package.json version: ${currentVersion} → ${originalPackageVersion}`); + } catch (error) { + console.error('āŒ Failed to rollback package.json version:', error.message); + } + } + + if (originalManifestVersion) { + try { + const manifestPath = path.resolve(process.cwd(), 'manifest.json'); + const manifestData = JSON.parse(readFileSync(manifestPath, 'utf8')); + const currentVersion = manifestData.version; + + const versionsPath = path.resolve(process.cwd(), 'versions.json'); + const versionsData = JSON.parse(readFileSync(versionsPath, 'utf8')); + + // remove last version from JSON + let keys = Object.keys(versionsData) + delete versionsData[keys[keys.length-1]] + + writeFileSync(versionsPath, JSON.stringify(versionsData, null, '\t') + '\n'); + console.log(`ā™»ļø Rolled back versions.json version: ${currentVersion} → ${originalManifestVersion}`); + } catch (error) { + console.error('āŒ Failed to rollback versions.json version:', error.message); + } + } + + if (originalManifestVersion) { + try { + const manifestPath = path.resolve(process.cwd(), 'manifest.json'); + const manifestData = JSON.parse(readFileSync(manifestPath, 'utf8')); + const currentVersion = manifestData.version; + + manifestData.version = originalManifestVersion; + writeFileSync(manifestPath, JSON.stringify(manifestData, null, '\t') + '\n'); + console.log(`ā™»ļø Rolled back manifest.json version: ${currentVersion} → ${originalManifestVersion}`); + } catch (error) { + console.error('āŒ Failed to rollback manifest.json version:', error.message); + } + } + + +}; + +/** + * Create Git commit and tag + * @param {string} version - New version + */ +const createGitCommitAndTag = (version) => { + try { + // Stage changed files + try { + execSync('git add package.json manifest.json versions.json', { stdio: 'inherit' }); + } catch (error) { + console.error('āŒ Failed to stage package.json, versions.json and manifest.json:', error.message); + return false; + } + + // Try to stage release files + try { + execSync(`git add ${RELEASE_FILES.join(' ')}`, { stdio: 'inherit' }); + } catch (error) { + console.warn('āš ļø Note: Some release files could not be staged. This may be normal if they are gitignored.'); + } + + // Create commit + const commitMessage = `chore: release ${version}`; + execSync(`git commit -m "${commitMessage}"`, { stdio: 'inherit' }); + console.log(`āœ… Commit created: ${commitMessage}`); + + // Create tag + const tagName = `${version}`; + execSync(`git tag -a ${tagName} -m "Release ${tagName}"`, { stdio: 'inherit' }); + console.log(`šŸ·ļø Tag created: ${tagName}`); + + // Push changes + execSync('git push', { stdio: 'inherit' }); + execSync('git push --tags', { stdio: 'inherit' }); + console.log('šŸš€ Changes pushed to GitHub'); + + // Create GitHub Release + try { + console.log('šŸ“¦ Creating GitHub Release...'); + + // First check if GitHub CLI is installed + try { + execSync('gh --version', { stdio: 'pipe' }); + } catch (error) { + console.log('āš ļø GitHub CLI not found. Skipping GitHub Release creation.'); + console.log('ā„¹ļø To create GitHub Releases, install GitHub CLI: https://cli.github.com/'); + return; + } + + // Get previous tag for changelog link + let previousTag = ''; + try { + // Get all tags sorted by version (newest first) + const tagsOutput = execSync('git tag --sort=-v:refname', { encoding: 'utf-8' }); + // Split by line and remove empty lines + const tags = tagsOutput.split('\n').filter(tag => tag.trim() !== ''); + + // If current tag is in the list, get the next one (which is the previous release) + const currentTagIndex = tags.indexOf(tagName); + if (currentTagIndex >= 0 && currentTagIndex < tags.length - 1) { + previousTag = tags[currentTagIndex + 1]; + } else if (tags.length > 1 && currentTagIndex < 0) { + // If new tag is not yet in the list, take the first one + previousTag = tags[0]; + } + } catch (error) { + console.warn('āš ļø Could not determine previous tag:', error.message); + } + + // Create release notes with changelog link if previous tag exists + let releaseNotes = `${tagName}`; + if (previousTag) { + const repoUrl = execSync('git config --get remote.origin.url', { encoding: 'utf-8' }) + .trim() + .replace(/\.git$/, '') + .replace(/^git@github\.com:/, 'https://github.com/'); + + releaseNotes += `\n\n**Full Changelog**: ${repoUrl}/compare/${previousTag}...${tagName}`; + } + + // Create GitHub Release using GitHub CLI + const releaseCommand = `gh release create ${tagName} ${RELEASE_FILES.join(' ')} --title "Release ${tagName}" --notes "${releaseNotes}"`; + execSync(releaseCommand, { stdio: 'inherit' }); + console.log(`āœ… GitHub Release created: ${tagName}`); + } catch (releaseError) { + console.error('āš ļø Failed to create GitHub Release:', releaseError.message); + console.log('Release files were still committed and pushed to GitHub.'); + } + return true; // Successfully created commit, tag, and pushed + } catch (error) { + console.error('āŒ Error during Git operations:', error.message); + return false; // Failed to complete Git operations + } +}; + +/** + * Check if Git working tree is clean + * @returns {boolean} Whether the working tree is clean + */ +const isGitWorkingTreeClean = () => { + try { + // Check for uncommitted changes + const output = execSync('git status --porcelain', { encoding: 'utf-8' }); + return output.trim() === ''; + } catch (error) { + console.error('āŒ Error checking Git status:', error.message); + return false; + } +}; + +/** + * Main function + */ +const main = () => { + let success = true; + let newVersion = ''; + + try { + // Check for uncommitted changes + if (!isGitWorkingTreeClean()) { + console.error('āŒ Cannot proceed with release: You have uncommitted changes.'); + console.log('Please commit or stash your changes before running the release script.'); + process.exit(1); + } + + // Check command line arguments + const args = process.argv.slice(2); + const versionType = args[0] || DEFAULT_VERSION_TYPE; + + if (!Object.values(VERSION_TYPES).includes(versionType)) { + console.error(`āŒ Invalid version type: ${versionType}`); + console.log(`Valid options: ${Object.values(VERSION_TYPES).join(', ')}`); + process.exit(1); + } + + let previousVersion = getPreviousVersion() + + // Step 1: Update package.json version + try { + newVersion = updatePackageVersion(versionType); + } catch (error) { + console.error('āŒ Failed to update package.json version:', error.message); + success = false; + } + + // Step 2: Update manifest.json version + if (success) { + try { + updateManifestVersion(newVersion); + } catch (error) { + console.error('āŒ Failed to update manifest.json version:', error.message); + success = false; + } + } + + // Step 2.5: Update versions.json version + if (success) { + try { + updateVersionsVersion(previousVersion, newVersion, MIN_APP_VERSION); + } catch (error) { + console.error('āŒ Failed to update versions.json version:', error.message); + success = false; + } + } + + // Step 3: Build the project + if (success) { + if (!buildProject()) { + console.error('āŒ Build process failed.'); + success = false; + } + } + + // Step 4: Git operations + if (success) { + if (!createGitCommitAndTag(newVersion)) { + console.error('āŒ Git operations failed.'); + success = false; + } + } + + // Check overall success + if (success) { + console.log(`\nšŸŽ‰ Release ${newVersion} completed successfully!`); + } else { + console.error('āŒ Release process failed. Rolling back version changes...'); + rollbackVersions(); + process.exit(1); + } + } catch (error) { + console.error('āŒ Unexpected error during release process:', error.message); + rollbackVersions(); + process.exit(1); + } +}; + +// Run script +main();