diff --git a/package.json b/package.json index 725bfb1..2f48556 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,9 @@ "keywords": [], "author": "", "license": "MIT", + "standard-version": { + "tagPrefix": "" + }, "devDependencies": { "@testing-library/dom": "^10.4.0", "@types/jest": "^29.5.14", diff --git a/release.ps1 b/release.ps1 index 116d61c..fcf3294 100644 --- a/release.ps1 +++ b/release.ps1 @@ -1,63 +1,211 @@ <# .SYNOPSIS - Bumps the plugin version and triggers an automated GitHub release. - -.DESCRIPTION - Runs type checking, bumps the version using standard-version (which also - updates CHANGELOG.md and creates a git tag), syncs the version to - manifest.json, then pushes. The tag push triggers GitHub Actions to build - the plugin and publish the release automatically. - -.PARAMETER Bump - Version bump type: patch, minor, or major (default: patch) - patch x.x.X Bug fixes, small improvements - minor x.X.0 New features (backward compatible) - major X.0.0 Breaking changes + Interactive release script for the Macros Obsidian plugin. + Stages changes, commits, bumps version, and pushes — triggering + an automated GitHub Actions release. .EXAMPLE .\release.ps1 - .\release.ps1 minor - .\release.ps1 -Bump major #> -param( - [ValidateSet("patch", "minor", "major")] - [string]$Bump = "patch" -) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" +Set-Location $PSScriptRoot + +# ── Helpers ───────────────────────────────────────────────────────────────── + +function Header([string]$msg) { + Write-Host "" + Write-Host " $msg" -ForegroundColor White + Write-Host (" " + ("─" * $msg.Length)) -ForegroundColor DarkGray +} function Step([string]$msg) { Write-Host "`n→ $msg" -ForegroundColor Cyan } +function Ok([string]$msg) { + Write-Host " ✓ $msg" -ForegroundColor Green +} + +function Warn([string]$msg) { + Write-Host " ! $msg" -ForegroundColor Yellow +} + function Fail([string]$msg) { - Write-Host "`n✗ $msg" -ForegroundColor Red + Write-Host "`n ✗ $msg" -ForegroundColor Red + Write-Host "" + Read-Host " Press Enter to exit" exit 1 } +function Prompt([string]$msg) { + Write-Host " $msg" -ForegroundColor Gray -NoNewline + return (Read-Host " ") +} + +function Menu([string]$title, [string[]]$options, [string[]]$descriptions, [int]$default = 0) { + Write-Host "" + Write-Host " $title" -ForegroundColor White + for ($i = 0; $i -lt $options.Length; $i++) { + $marker = if ($i -eq $default) { "▶" } else { " " } + $num = $i + 1 + Write-Host " $marker [$num] $($options[$i])" -ForegroundColor $(if ($i -eq $default) { "Cyan" } else { "Gray" }) + Write-Host " $($descriptions[$i])" -ForegroundColor DarkGray + } + Write-Host "" + $choice = Prompt "Choose (1-$($options.Length)) [default: $($default+1)]" + if ([string]::IsNullOrWhiteSpace($choice)) { return $default } + $idx = [int]$choice - 1 + if ($idx -lt 0 -or $idx -ge $options.Length) { return $default } + return $idx +} + +# ── Banner ─────────────────────────────────────────────────────────────────── + +Clear-Host +Write-Host "" +Write-Host " ╔══════════════════════════════════════╗" -ForegroundColor DarkCyan +Write-Host " ║ Macros Plugin Release Script ║" -ForegroundColor Cyan +Write-Host " ╚══════════════════════════════════════╝" -ForegroundColor DarkCyan + +# ── Check git status ───────────────────────────────────────────────────────── + +Header "Changed files" + +$status = git status --short +if ([string]::IsNullOrWhiteSpace($status)) { + Warn "No changes detected. Nothing to commit." + Write-Host "" + Read-Host " Press Enter to exit" + exit 0 +} + +$status -split "`n" | ForEach-Object { + if ($_.Trim()) { Write-Host " $_" -ForegroundColor DarkGray } +} + +# ── Commit type ────────────────────────────────────────────────────────────── + +$typeIdx = Menu "Commit type" ` + @("feat", "fix", "chore", "docs", "refactor", "perf", "test") ` + @( + "New feature or capability → bumps MINOR version", + "Bug fix or correction → bumps PATCH version", + "Maintenance, deps, tooling → bumps PATCH version", + "Documentation only → bumps PATCH version", + "Code restructure, no behaviour change → bumps PATCH version", + "Performance improvement → bumps PATCH version", + "Adding or fixing tests → bumps PATCH version" + ) + +$types = @("feat", "fix", "chore", "docs", "refactor", "perf", "test") +$commitType = $types[$typeIdx] + +# ── Commit description ─────────────────────────────────────────────────────── + +Header "Commit message" +$subject = "" +while ([string]::IsNullOrWhiteSpace($subject)) { + $subject = Prompt "Short description (e.g. 'add weekly summary view')" +} + +Write-Host "" +Write-Host " Optional: add a longer body (press Enter to skip)" -ForegroundColor Gray +Write-Host " You can paste multiple lines. Enter a blank line when done." -ForegroundColor DarkGray +Write-Host "" +$bodyLines = @() +while ($true) { + $line = Prompt " >" + if ([string]::IsNullOrWhiteSpace($line)) { break } + $bodyLines += $line +} + +$commitMsg = "${commitType}: ${subject}" +if ($bodyLines.Count -gt 0) { + $commitMsg += "`n`n" + ($bodyLines -join "`n") +} + +# ── Breaking change? ───────────────────────────────────────────────────────── + +$breaking = Prompt "Breaking change? (y/N)" +if ($breaking -match "^[Yy]") { + $breakingDesc = Prompt "Describe the breaking change" + $commitMsg += "`n`nBREAKING CHANGE: $breakingDesc" +} + +# ── Version bump ───────────────────────────────────────────────────────────── + +$defaultBump = if ($commitType -eq "feat" -or $breaking -match "^[Yy]") { 1 } else { 0 } +if ($breaking -match "^[Yy]") { $defaultBump = 2 } + +$bumpIdx = Menu "Version bump" ` + @("patch", "minor", "major") ` + @( + "x.x.X Bug fixes, maintenance, small improvements", + "x.X.0 New features (backward compatible)", + "X.0.0 Breaking changes" + ) ` + $defaultBump + +$bumps = @("patch", "minor", "major") +$bump = $bumps[$bumpIdx] + +# ── Confirm ─────────────────────────────────────────────────────────────────── + +$currentVersion = node -p "require('./package.json').version" +Header "Ready to release" +Write-Host " Commit : $commitMsg" -ForegroundColor White +Write-Host " Bump : $bump ($currentVersion → next $bump)" -ForegroundColor White +Write-Host "" +$confirm = Prompt "Proceed? (Y/n)" +if ($confirm -match "^[Nn]") { + Write-Host "`n Aborted." -ForegroundColor Yellow + exit 0 +} + +# ── Execute ─────────────────────────────────────────────────────────────────── + +Step "Staging all changes..." +git add . +Ok "Staged" + +Step "Committing..." +git commit -m $commitMsg +if ($LASTEXITCODE -ne 0) { Fail "Commit failed." } +Ok "Committed" + Step "Type checking..." npm run typecheck if ($LASTEXITCODE -ne 0) { Fail "Type check failed. Fix errors before releasing." } +Ok "Types OK" -Step "Bumping version ($Bump) and updating CHANGELOG.md..." -npx standard-version --release-as $Bump +Step "Bumping version ($bump) and updating CHANGELOG..." +npx standard-version --release-as $bump if ($LASTEXITCODE -ne 0) { Fail "Version bump failed." } Step "Syncing version to manifest.json..." node sync-version.js if ($LASTEXITCODE -ne 0) { Fail "Version sync failed." } - git add manifest.json -git commit -m "chore: sync version to manifest" --no-verify -if ($LASTEXITCODE -ne 0) { - Write-Host " (nothing to commit — manifest already in sync)" -ForegroundColor DarkGray +$syncResult = git commit -m "chore: sync version to manifest" --no-verify 2>&1 +if ($LASTEXITCODE -ne 0 -and $syncResult -notmatch "nothing to commit") { + Warn "manifest sync commit skipped (already in sync)" +} else { + Ok "manifest.json synced" } Step "Pushing to GitHub..." git push --follow-tags origin main if ($LASTEXITCODE -ne 0) { Fail "Push failed." } -$version = node -p "require('./package.json').version" -Write-Host "`n✓ v$version pushed. GitHub Actions will build and publish the release." -ForegroundColor Green -Write-Host " Track progress at: https://github.com/JamesCliffordSpratt/macros/actions" -ForegroundColor DarkGray +$newVersion = node -p "require('./package.json').version" +Write-Host "" +Write-Host " ╔══════════════════════════════════════╗" -ForegroundColor DarkGreen +Write-Host " ║ Release v$newVersion complete!$(if($newVersion.Length -lt 6){' '}elseif($newVersion.Length -lt 7){' '}else{' '})║" -ForegroundColor Green +Write-Host " ╚══════════════════════════════════════╝" -ForegroundColor DarkGreen +Write-Host "" +Write-Host " GitHub Actions is now building and publishing the release." -ForegroundColor Gray +Write-Host " Track: https://github.com/JamesCliffordSpratt/macros/actions" -ForegroundColor DarkGray +Write-Host "" +Read-Host " Press Enter to close" diff --git a/src/settings/StorageService.ts b/src/settings/StorageService.ts index 493b3c0..e689620 100644 --- a/src/settings/StorageService.ts +++ b/src/settings/StorageService.ts @@ -1276,7 +1276,8 @@ export class NutritionalSettingTab extends PluginSettingTab { .setName(t('settings.meals.create')) .setDesc(t('settings.meals.createDesc')) .addButton((btn) => { - btn.setButtonText(t('settings.meals.addButton')).onClick(() => { + btn.setButtonText(t('settings.meals.addButton')).onClick((evt) => { + evt.preventDefault(); new AddMealTemplateModal(this.plugin).open(); }); }); @@ -1296,7 +1297,8 @@ export class NutritionalSettingTab extends PluginSettingTab { editBtn .setButtonText(t('general.edit')) .setCta() - .onClick(() => { + .onClick((evt) => { + evt.preventDefault(); new EditMealTemplateModal(this.plugin, meal).open(); }); }) diff --git a/src/ui/modals/AddMealTemplateModal.ts b/src/ui/modals/AddMealTemplateModal.ts index a5005a6..224c486 100644 --- a/src/ui/modals/AddMealTemplateModal.ts +++ b/src/ui/modals/AddMealTemplateModal.ts @@ -262,6 +262,7 @@ export class AddMealTemplateModal extends Modal { const removeButton = buttonContainer.createEl('button', { text: t('meals.create.remove'), cls: 'remove-button', + attr: { type: 'button' }, }); // Edit serving button (if nutrition data available) @@ -269,6 +270,7 @@ export class AddMealTemplateModal extends Modal { const editButton = buttonContainer.createEl('button', { text: t('meals.create.editServing'), cls: 'edit-button', + attr: { type: 'button' }, }); this.component.registerDomEvent(editButton, 'click', () => { @@ -286,6 +288,7 @@ export class AddMealTemplateModal extends Modal { const addButton = buttonContainer.createEl('button', { text: t('meals.create.addToMeal'), cls: 'add-button', + attr: { type: 'button' }, }); this.component.registerDomEvent(addButton, 'click', () => { @@ -382,6 +385,7 @@ export class AddMealTemplateModal extends Modal { const removeButton = itemTag.createEl('button', { text: '×', cls: 'remove-item-button', + attr: { type: 'button' }, }); this.component.registerDomEvent(removeButton, 'click', () => { @@ -415,11 +419,13 @@ export class AddMealTemplateModal extends Modal { const cancelButton = buttonContainer.createEl('button', { text: t('general.cancel'), cls: 'cancel-button', + attr: { type: 'button' }, }); this.createButton = buttonContainer.createEl('button', { text: t('meals.create.create'), cls: 'create-button disabled', + attr: { type: 'button' }, }); (this.createButton as HTMLButtonElement).disabled = true; @@ -712,6 +718,7 @@ export class EditMealTemplateModal extends Modal { const removeButton = buttonContainer.createEl('button', { text: t('meals.create.remove'), cls: 'remove-button', + attr: { type: 'button' }, }); // Edit serving button (if nutrition data available) @@ -719,6 +726,7 @@ export class EditMealTemplateModal extends Modal { const editButton = buttonContainer.createEl('button', { text: t('meals.create.editServing'), cls: 'edit-button', + attr: { type: 'button' }, }); this.component.registerDomEvent(editButton, 'click', () => { @@ -736,6 +744,7 @@ export class EditMealTemplateModal extends Modal { const addButton = buttonContainer.createEl('button', { text: t('meals.create.addToMeal'), cls: 'add-button', + attr: { type: 'button' }, }); this.component.registerDomEvent(addButton, 'click', () => { @@ -857,6 +866,7 @@ export class EditMealTemplateModal extends Modal { const removeButton = itemTag.createEl('button', { text: '×', cls: 'remove-item-button', + attr: { type: 'button' }, }); this.component.registerDomEvent(removeButton, 'click', () => { @@ -886,11 +896,13 @@ export class EditMealTemplateModal extends Modal { const cancelButton = buttonContainer.createEl('button', { text: t('general.cancel'), cls: 'cancel-button', + attr: { type: 'button' }, }); this.saveButton = buttonContainer.createEl('button', { text: t('meals.edit.saveChanges'), cls: 'save-button', + attr: { type: 'button' }, }); this.component.registerDomEvent(cancelButton, 'click', () => { diff --git a/src/ui/modals/EditMealTemplateModal.ts b/src/ui/modals/EditMealTemplateModal.ts index 2c46339..757d988 100644 --- a/src/ui/modals/EditMealTemplateModal.ts +++ b/src/ui/modals/EditMealTemplateModal.ts @@ -240,6 +240,7 @@ export class EditMealTemplateModal extends Modal { const removeButton = buttonContainer.createEl('button', { text: t('meals.create.remove'), cls: 'remove-button', + attr: { type: 'button' }, }); // Edit serving button (if nutrition data available) @@ -247,7 +248,8 @@ export class EditMealTemplateModal extends Modal { const editButton = buttonContainer.createEl('button', { text: t('meals.create.editServing'), cls: 'edit-button', - }); + attr: { type: 'button' }, + }); this.component.registerDomEvent(editButton, 'click', () => { this.editFoodServing(food); @@ -264,6 +266,7 @@ export class EditMealTemplateModal extends Modal { const addButton = buttonContainer.createEl('button', { text: t('meals.create.addToMeal'), cls: 'add-button', + attr: { type: 'button' }, }); this.component.registerDomEvent(addButton, 'click', () => { @@ -385,6 +388,7 @@ export class EditMealTemplateModal extends Modal { const removeButton = itemTag.createEl('button', { text: '×', cls: 'remove-item-button', + attr: { type: 'button' }, }); this.component.registerDomEvent(removeButton, 'click', () => { @@ -414,12 +418,14 @@ export class EditMealTemplateModal extends Modal { const cancelButton = buttonContainer.createEl('button', { text: t('general.cancel'), cls: 'cancel-button', - }); + attr: { type: 'button' }, + }); this.saveButton = buttonContainer.createEl('button', { text: t('meals.edit.saveChanges'), cls: 'save-button', - }); + attr: { type: 'button' }, + }); this.component.registerDomEvent(cancelButton, 'click', () => { this.close();