Merge remote-tracking branch 'wails/v3-alpha' into v3/deeplink

This commit is contained in:
Atterpac 2025-07-15 08:54:46 -06:00
commit 101b09676d
15 changed files with 245 additions and 371 deletions

View file

@ -24,7 +24,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
*/
## [Unreleased]
## v3.0.0-alpha.12 - 2025-07-15
### Added
- `app.Env.GetAccentColor` to get the accent color of a user's system. Works on MacOS. by [@etesam913](https://github.com/etesam913)
- Add `window.ToggleFrameless()` api by [@atterpac](https://github.com/atterpac) in [#4137](https://github.com/wailsapp/wails/pull/4137)
### Fixed
- Fixed doctor command to check for Windows SDK dependencies by [@kodumulo](https://github.com/kodumulo) in [#4390](https://github.com/wailsapp/wails/issues/4390)
## v3.0.0-alpha.11 - 2025-07-12

View file

@ -34,6 +34,10 @@
closeButton.addEventListener('click', function(event) {
window.wails.Window.Close();
});
let toggleFramelessButton = document.querySelector('#toggle-frameless');
toggleFramelessButton.addEventListener('click', function(event) {
window.wails.Window.ToggleFrameless();
});
});
</script>
</head>
@ -42,6 +46,7 @@
<div class="quarter" style="background-color: lightblue; --wails-draggable: drag">Draggable</div>
<div class="quarter" style="background-color: lightgreen;"><div>Not Draggable</div><button id="min">Minimise for 3s</button></div>
<div class="quarter" style="background-color: lightpink;"><div>Not Draggable</div><button id="close">Close</button></div>
<div class="quarter" style="background-color: lightred;"><div>Not Draggable</div><button id="toggle-frameless">Toggle Frameless</button></div>
<div class="quarter" style="background-color: lightyellow; --wails-draggable: drag">Draggable</div>
</div>
</body>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -3,10 +3,11 @@
package doctor
import (
"github.com/samber/lo"
"github.com/wailsapp/go-webview2/webviewloader"
"os/exec"
"strings"
"github.com/samber/lo"
"github.com/wailsapp/go-webview2/webviewloader"
)
func getInfo() (map[string]string, bool) {
@ -43,12 +44,25 @@ func getMakeAppxVersion() string {
func getMSIXPackagingToolVersion() string {
// Check if MSIX Packaging Tool is installed
// Use PowerShell to check if the app is installed from Microsoft Store
cmd := exec.Command("powershell", "-Command", "Get-AppxPackage -Name Microsoft.MsixPackagingTool")
cmd := exec.Command("powershell", "-Command", "Get-AppxPackage -Name Microsoft.MSIXPackagingTool")
output, err := cmd.Output()
if err != nil || len(output) == 0 || !strings.Contains(string(output), "Microsoft.MsixPackagingTool") {
if err != nil || len(output) == 0 || !strings.Contains(string(output), "Microsoft.MSIXPackagingTool") {
return "Not Installed"
}
return "Installed"
if strings.Contains(string(output), "Version") {
lines := strings.Split(string(output), "\n")
for _, line := range lines {
if strings.Contains(line, "Version") {
parts := strings.Split(line, ":")
if len(parts) > 1 {
return strings.TrimSpace(parts[1])
}
}
}
}
return "Installed (Version Unknown)"
}
func getSignToolVersion() string {
@ -64,7 +78,7 @@ func checkPlatformDependencies(result map[string]string, ok *bool) {
checkCommonDependencies(result, ok)
// add nsis
result["NSIS"] = getNSISVersion()
// Add MSIX tooling checks
result["MakeAppx.exe (Windows SDK)"] = getMakeAppxVersion()
result["MSIX Packaging Tool"] = getMSIXPackagingToolVersion()

View file

@ -1,12 +1,12 @@
{
"name": "@wailsio/runtime",
"version": "3.0.0-alpha.67",
"version": "3.0.0-alpha.68",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@wailsio/runtime",
"version": "3.0.0-alpha.67",
"version": "3.0.0-alpha.68",
"license": "MIT",
"devDependencies": {
"happy-dom": "^17.1.1",

View file

@ -1,7 +1,7 @@
{
"name": "@wailsio/runtime",
"type": "module",
"version": "3.0.0-alpha.67",
"version": "3.0.0-alpha.68",
"description": "Wails Runtime",
"types": "types/index.d.ts",
"exports": {

View file

@ -218,6 +218,7 @@ export const Types = Object.freeze({
WindowLostFocus: "common:WindowLostFocus",
WindowMaximise: "common:WindowMaximise",
WindowMinimise: "common:WindowMinimise",
WindowToggleFrameless: "common:WindowToggleFrameless",
WindowRestore: "common:WindowRestore",
WindowRuntimeReady: "common:WindowRuntimeReady",
WindowShow: "common:WindowShow",

View file

@ -51,14 +51,15 @@ const ShowMethod = 36;
const SizeMethod = 37;
const ToggleFullscreenMethod = 38;
const ToggleMaximiseMethod = 39;
const UnFullscreenMethod = 40;
const UnMaximiseMethod = 41;
const UnMinimiseMethod = 42;
const WidthMethod = 43;
const ZoomMethod = 44;
const ZoomInMethod = 45;
const ZoomOutMethod = 46;
const ZoomResetMethod = 47;
const ToggleFramelessMethod = 40;
const UnFullscreenMethod = 41;
const UnMaximiseMethod = 42;
const UnMinimiseMethod = 43;
const WidthMethod = 44;
const ZoomMethod = 45;
const ZoomInMethod = 46;
const ZoomOutMethod = 47;
const ZoomResetMethod = 48;
/**
* A record describing the position of a window.
@ -453,6 +454,13 @@ class Window {
return this[callerSym](ToggleMaximiseMethod);
}
/**
* Toggles the window between frameless and normal.
*/
ToggleFrameless(): Promise<void> {
return this[callerSym](ToggleFramelessMethod);
}
/**
* Un-fullscreens the window.
*/

View file

@ -1 +1 @@
v3.0.0-alpha.11
v3.0.0-alpha.12

View file

@ -47,14 +47,15 @@ const (
WindowSize = 37
WindowToggleFullscreen = 38
WindowToggleMaximise = 39
WindowUnFullscreen = 40
WindowUnMaximise = 41
WindowUnMinimise = 42
WindowWidth = 43
WindowZoom = 44
WindowZoomIn = 45
WindowZoomOut = 46
WindowZoomReset = 47
WindowToggleFrameless = 40
WindowUnFullscreen = 41
WindowUnMaximise = 42
WindowUnMinimise = 43
WindowWidth = 44
WindowZoom = 45
WindowZoomIn = 46
WindowZoomOut = 47
WindowZoomReset = 48
)
var windowMethodNames = map[int]string{
@ -98,6 +99,7 @@ var windowMethodNames = map[int]string{
WindowSize: "Size",
WindowToggleFullscreen: "ToggleFullscreen",
WindowToggleMaximise: "ToggleMaximise",
WindowToggleFrameless: "ToggleFrameless",
WindowUnFullscreen: "UnFullscreen",
WindowUnMaximise: "UnMaximise",
WindowUnMinimise: "UnMinimise",
@ -108,7 +110,13 @@ var windowMethodNames = map[int]string{
WindowZoomReset: "ZoomReset",
}
func (m *MessageProcessor) processWindowMethod(method int, rw http.ResponseWriter, _ *http.Request, window Window, params QueryParams) {
func (m *MessageProcessor) processWindowMethod(
method int,
rw http.ResponseWriter,
_ *http.Request,
window Window,
params QueryParams,
) {
args, err := params.Args()
if err != nil {
m.httpError(rw, "Invalid window call:", fmt.Errorf("unable to parse arguments: %w", err))
@ -211,7 +219,11 @@ func (m *MessageProcessor) processWindowMethod(method int, rw http.ResponseWrite
case WindowSetAlwaysOnTop:
alwaysOnTop := args.Bool("alwaysOnTop")
if alwaysOnTop == nil {
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'alwaysOnTop'"))
m.httpError(
rw,
"Invalid window call:",
errors.New("missing or invalid argument 'alwaysOnTop'"),
)
return
}
window.SetAlwaysOnTop(*alwaysOnTop)
@ -247,7 +259,11 @@ func (m *MessageProcessor) processWindowMethod(method int, rw http.ResponseWrite
case WindowSetFrameless:
frameless := args.Bool("frameless")
if frameless == nil {
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'frameless'"))
m.httpError(
rw,
"Invalid window call:",
errors.New("missing or invalid argument 'frameless'"),
)
return
}
window.SetFrameless(*frameless)
@ -255,12 +271,20 @@ func (m *MessageProcessor) processWindowMethod(method int, rw http.ResponseWrite
case WindowSetMaxSize:
width := args.Int("width")
if width == nil {
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'width'"))
m.httpError(
rw,
"Invalid window call:",
errors.New("missing or invalid argument 'width'"),
)
return
}
height := args.Int("height")
if height == nil {
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'height'"))
m.httpError(
rw,
"Invalid window call:",
errors.New("missing or invalid argument 'height'"),
)
return
}
window.SetMaxSize(*width, *height)
@ -268,12 +292,20 @@ func (m *MessageProcessor) processWindowMethod(method int, rw http.ResponseWrite
case WindowSetMinSize:
width := args.Int("width")
if width == nil {
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'width'"))
m.httpError(
rw,
"Invalid window call:",
errors.New("missing or invalid argument 'width'"),
)
return
}
height := args.Int("height")
if height == nil {
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'height'"))
m.httpError(
rw,
"Invalid window call:",
errors.New("missing or invalid argument 'height'"),
)
return
}
window.SetMinSize(*width, *height)
@ -294,7 +326,11 @@ func (m *MessageProcessor) processWindowMethod(method int, rw http.ResponseWrite
case WindowSetResizable:
resizable := args.Bool("resizable")
if resizable == nil {
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'resizable'"))
m.httpError(
rw,
"Invalid window call:",
errors.New("missing or invalid argument 'resizable'"),
)
return
}
window.SetResizable(*resizable)
@ -302,12 +338,20 @@ func (m *MessageProcessor) processWindowMethod(method int, rw http.ResponseWrite
case WindowSetSize:
width := args.Int("width")
if width == nil {
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'width'"))
m.httpError(
rw,
"Invalid window call:",
errors.New("missing or invalid argument 'width'"),
)
return
}
height := args.Int("height")
if height == nil {
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'height'"))
m.httpError(
rw,
"Invalid window call:",
errors.New("missing or invalid argument 'height'"),
)
return
}
window.SetSize(*width, *height)
@ -323,7 +367,11 @@ func (m *MessageProcessor) processWindowMethod(method int, rw http.ResponseWrite
case WindowSetZoom:
zoom := args.Float64("zoom")
if zoom == nil {
m.httpError(rw, "Invalid window call:", errors.New("missing or invalid argument 'zoom'"))
m.httpError(
rw,
"Invalid window call:",
errors.New("missing or invalid argument 'zoom'"),
)
return
}
window.SetZoom(*zoom)
@ -346,6 +394,9 @@ func (m *MessageProcessor) processWindowMethod(method int, rw http.ResponseWrite
case WindowToggleMaximise:
window.ToggleMaximise()
m.ok(rw)
case WindowToggleFrameless:
window.ToggleFrameless()
m.ok(rw)
case WindowUnFullscreen:
window.UnFullscreen()
m.ok(rw)

View file

@ -288,7 +288,9 @@ func NewWindow(options WebviewWindowOptions) *WebviewWindow {
return result
}
func processKeyBindingOptions(keyBindings map[string]func(window *WebviewWindow)) map[string]func(window *WebviewWindow) {
func processKeyBindingOptions(
keyBindings map[string]func(window *WebviewWindow),
) map[string]func(window *WebviewWindow) {
result := make(map[string]func(window *WebviewWindow))
for key, callback := range keyBindings {
// Parse the key to an accelerator
@ -311,25 +313,51 @@ func (w *WebviewWindow) addCancellationFunction(canceller func()) {
func (w *WebviewWindow) CallError(callID string, result string, isJSON bool) {
if w.impl != nil {
w.impl.execJS(fmt.Sprintf("_wails.callErrorHandler('%s', '%s', %t);", callID, template.JSEscapeString(result), isJSON))
w.impl.execJS(
fmt.Sprintf(
"_wails.callErrorHandler('%s', '%s', %t);",
callID,
template.JSEscapeString(result),
isJSON,
),
)
}
}
func (w *WebviewWindow) CallResponse(callID string, result string) {
if w.impl != nil {
w.impl.execJS(fmt.Sprintf("_wails.callResultHandler('%s', '%s', true);", callID, template.JSEscapeString(result)))
w.impl.execJS(
fmt.Sprintf(
"_wails.callResultHandler('%s', '%s', true);",
callID,
template.JSEscapeString(result),
),
)
}
}
func (w *WebviewWindow) DialogError(dialogID string, result string) {
if w.impl != nil {
w.impl.execJS(fmt.Sprintf("_wails.dialogErrorCallback('%s', '%s');", dialogID, template.JSEscapeString(result)))
w.impl.execJS(
fmt.Sprintf(
"_wails.dialogErrorCallback('%s', '%s');",
dialogID,
template.JSEscapeString(result),
),
)
}
}
func (w *WebviewWindow) DialogResponse(dialogID string, result string, isJSON bool) {
if w.impl != nil {
w.impl.execJS(fmt.Sprintf("_wails.dialogResultCallback('%s', '%s', %t);", dialogID, template.JSEscapeString(result), isJSON))
w.impl.execJS(
fmt.Sprintf(
"_wails.dialogResultCallback('%s', '%s', %t);",
dialogID,
template.JSEscapeString(result),
isJSON,
),
)
}
}
@ -742,7 +770,10 @@ func (w *WebviewWindow) Center() {
}
// OnWindowEvent registers a callback for the given window event
func (w *WebviewWindow) OnWindowEvent(eventType events.WindowEventType, callback func(event *WindowEvent)) func() {
func (w *WebviewWindow) OnWindowEvent(
eventType events.WindowEventType,
callback func(event *WindowEvent),
) func() {
eventID := uint(eventType)
windowEventListener := &WindowEventListener{
callback: callback,
@ -763,7 +794,10 @@ func (w *WebviewWindow) OnWindowEvent(eventType events.WindowEventType, callback
}
// RegisterHook registers a hook for the given window event
func (w *WebviewWindow) RegisterHook(eventType events.WindowEventType, callback func(event *WindowEvent)) func() {
func (w *WebviewWindow) RegisterHook(
eventType events.WindowEventType,
callback func(event *WindowEvent),
) func() {
eventID := uint(eventType)
w.eventHooksLock.Lock()
defer w.eventHooksLock.Unlock()
@ -972,6 +1006,16 @@ func (w *WebviewWindow) ToggleMaximise() {
})
}
// ToggleFrameless toggles the window between frameless and normal
func (w *WebviewWindow) ToggleFrameless() {
if w.impl == nil || w.isDestroyed() {
return
}
InvokeSync(func() {
w.SetFrameless(!w.options.Frameless)
})
}
func (w *WebviewWindow) OpenDevTools() {
if w.impl == nil || w.isDestroyed() {
return

View file

@ -76,6 +76,7 @@ type Window interface {
ToggleFullscreen()
ToggleMaximise()
ToggleMenuBar()
ToggleFrameless()
UnFullscreen()
UnMaximise()
UnMinimise()

View file

@ -13,6 +13,7 @@ common:WindowHide
common:WindowLostFocus
common:WindowMaximise
common:WindowMinimise
common:WindowToggleFrameless
common:WindowRestore
common:WindowRuntimeReady
common:WindowShow
@ -206,4 +207,4 @@ windows:WindowZOrderChanged
windows:WindowMinimise
windows:WindowUnMinimise
windows:WindowMaximise
windows:WindowUnMaximise
windows:WindowUnMaximise

View file

@ -1,10 +1,7 @@
package main
import (
"fmt"
"os"
"os/exec"
"regexp"
"strconv"
"strings"
"time"
@ -21,203 +18,6 @@ func checkError(err error) {
}
}
func runCommand(name string, arg ...string) ([]byte, error) {
cmd := exec.Command(name, arg...)
return cmd.Output()
}
func hasReleaseTag() (bool, string) {
output, err := runCommand("git", "describe", "--tags", "--exact-match", "HEAD")
if err != nil {
return false, ""
}
tag := strings.TrimSpace(string(output))
matched, _ := regexp.MatchString(`^v3\.0\.0-alpha\.\d+(\.\d+)*$`, tag)
return matched, tag
}
func extractChangelogSinceTag(tag string) (string, error) {
output, err := runCommand("git", "log", "--pretty=format:- %s", tag+"..HEAD")
if err != nil {
return "", err
}
changelog := strings.TrimSpace(string(output))
if changelog == "" {
return "No changes since " + tag, nil
}
return changelog, nil
}
func extractUnreleasedChangelog() (string, error) {
// This function assumes we're in the project root
changelogData, err := os.ReadFile("docs/src/content/docs/changelog.mdx")
if err != nil {
return "", err
}
changelog := string(changelogData)
// Find the [Unreleased] section
unreleasedStart := strings.Index(changelog, "## [Unreleased]")
if unreleasedStart == -1 {
return "No unreleased changes found", nil
}
// Find the next version section
nextVersionStart := strings.Index(changelog[unreleasedStart+len("## [Unreleased]"):], "## ")
if nextVersionStart == -1 {
// No next version, take everything after [Unreleased]
content := changelog[unreleasedStart+len("## [Unreleased]"):]
return strings.TrimSpace(content), nil
}
// Extract content between [Unreleased] and next version
content := changelog[unreleasedStart+len("## [Unreleased]") : unreleasedStart+len("## [Unreleased]")+nextVersionStart]
return strings.TrimSpace(content), nil
}
func checkForChanges() (bool, string) {
// Get the latest v3 alpha tag
output, err := runCommand("git", "tag", "--list", "v3.0.0-alpha.*", "--sort=-version:refname")
if err != nil {
return true, "No previous tags found"
}
tags := strings.Split(strings.TrimSpace(string(output)), "\n")
if len(tags) == 0 || tags[0] == "" {
return true, "No previous tags found"
}
latestTag := tags[0]
// Check for commits since the latest tag
output, err = runCommand("git", "rev-list", latestTag+"..HEAD", "--count")
if err != nil {
return true, "Error checking commits: " + err.Error()
}
commitCount := strings.TrimSpace(string(output))
if commitCount == "0" {
return false, fmt.Sprintf("No changes since %s", latestTag)
}
// Get commit messages since the latest tag
output, err = runCommand("git", "log", "--pretty=format:- %s", latestTag+"..HEAD")
if err != nil {
return true, "Error getting commit messages: " + err.Error()
}
return true, fmt.Sprintf("Found %s commits since %s:\n%s", commitCount, latestTag, strings.TrimSpace(string(output)))
}
func validateChangelogUpdate(version string) (bool, string, error) {
// Check if we're in the right directory
changelogPath := "docs/src/content/docs/changelog.mdx"
if _, err := os.Stat(changelogPath); os.IsNotExist(err) {
// Try from project root
changelogPath = "../../../docs/src/content/docs/changelog.mdx"
}
changelogData, err := os.ReadFile(changelogPath)
if err != nil {
return false, "", fmt.Errorf("failed to read changelog: %v", err)
}
changelog := string(changelogData)
// Check if the version exists in changelog
versionHeader := "## " + version + " - "
if !strings.Contains(changelog, versionHeader) {
return false, "", fmt.Errorf("version %s not found in changelog", version)
}
// Extract the content for this version
versionStart := strings.Index(changelog, versionHeader)
if versionStart == -1 {
return false, "", fmt.Errorf("version header not found")
}
// Find the next version section - look for next ## followed by a version pattern
remainingContent := changelog[versionStart+len(versionHeader):]
nextVersionStart := strings.Index(remainingContent, "\n## v")
if nextVersionStart == -1 {
// This is the last version, take everything until end
content := changelog[versionStart:]
return true, strings.TrimSpace(content), nil
}
// Extract content between this version and next version
content := changelog[versionStart : versionStart+len(versionHeader)+nextVersionStart]
return true, strings.TrimSpace(content), nil
}
func outputReleaseMetadata(version, changelog string, hasChanges bool, changesSummary string) error {
fmt.Println("========================================")
fmt.Println("🧪 DRY RUN MODE - TESTING RELEASE SCRIPT")
fmt.Println("========================================")
// 1. Changes detection
fmt.Printf("1. CHANGES DETECTED: %t\n", hasChanges)
fmt.Printf(" SUMMARY: %s\n\n", changesSummary)
// 2. Changelog validation
fmt.Println("2. CHANGELOG VALIDATION:")
_, extractedChangelog, err := validateChangelogUpdate(version)
if err != nil {
fmt.Printf(" ❌ FAILED: %v\n\n", err)
return err
}
fmt.Printf(" ✅ PASSED: Version %s found in changelog\n\n", version)
// 3. Release notes in memory
fmt.Println("3. RELEASE NOTES EXTRACTED TO MEMORY:")
fmt.Printf(" LENGTH: %d characters\n", len(extractedChangelog))
fmt.Printf(" PREVIEW (first 200 chars): %s...\n\n", extractedChangelog[:min(200, len(extractedChangelog))])
// 4. Prerelease data
fmt.Println("4. GITHUB PRERELEASE DATA:")
fmt.Printf(" VERSION: %s\n", version)
fmt.Printf(" TAG: %s\n", version)
fmt.Printf(" TITLE: Wails v3 Alpha Release - %s\n", version)
fmt.Printf(" IS_PRERELEASE: true\n")
fmt.Printf(" IS_LATEST: false\n")
fmt.Printf(" DRAFT: false\n\n")
// Output environment variables for GitHub Actions
fmt.Println("5. ENVIRONMENT VARIABLES FOR GITHUB ACTIONS:")
fmt.Printf("RELEASE_VERSION=%s\n", version)
fmt.Printf("RELEASE_TAG=%s\n", version)
fmt.Printf("RELEASE_TITLE=Wails v3 Alpha Release - %s\n", version)
fmt.Printf("RELEASE_IS_PRERELEASE=true\n")
fmt.Printf("RELEASE_IS_LATEST=false\n")
fmt.Printf("RELEASE_DRAFT=false\n")
fmt.Printf("HAS_CHANGES=%t\n", hasChanges)
// Write changelog to file for GitHub Actions
err = os.WriteFile("release-notes.txt", []byte(extractedChangelog), 0o644)
if err != nil {
return fmt.Errorf("failed to write release notes: %v", err)
}
fmt.Printf("RELEASE_NOTES_FILE=release-notes.txt\n\n")
fmt.Println("========================================")
fmt.Println("✅ DRY RUN COMPLETED SUCCESSFULLY")
fmt.Println("========================================")
return nil
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
// TODO:This can be replaced with "https://github.com/coreos/go-semver/blob/main/semver/semver.go"
func updateVersion() string {
currentVersionData, err := os.ReadFile(versionFile)
@ -257,102 +57,34 @@ func updateVersion() string {
//}
func main() {
fmt.Println("🧪 STARTING DRY RUN RELEASE SCRIPT TEST")
fmt.Println("=======================================")
// Step 0: Ensure we have latest git data
fmt.Println("STEP 0: Fetching latest git data...")
_, gitErr := runCommand("git", "fetch", "--tags", "origin")
if gitErr != nil {
fmt.Printf("⚠️ Warning: Failed to fetch latest tags: %v\n", gitErr)
fmt.Println(" Continuing with local git state...")
} else {
fmt.Println(" ✅ Latest tags fetched successfully")
}
// Step 1: Check for changes since last release
fmt.Println("STEP 1: Checking for changes...")
hasChanges, changesSummary := checkForChanges()
// Step 2: Check if current commit has a release tag
hasTag, tag := hasReleaseTag()
var newVersion string
var releaseChangelog string
if hasTag {
// Current commit has a release tag - this is a nightly release scenario
fmt.Printf("Found release tag: %s\n", tag)
// Read version from version.txt
currentVersionData, err := os.ReadFile(versionFile)
if len(os.Args) > 1 {
newVersion = os.Args[1]
//currentVersion, err := os.ReadFile(versionFile)
//checkError(err)
err := os.WriteFile(versionFile, []byte(newVersion), 0o755)
checkError(err)
newVersion = strings.TrimSpace(string(currentVersionData))
// Extract changelog since the tag
changelog, err := extractChangelogSinceTag(tag)
checkError(err)
releaseChangelog = changelog
fmt.Printf("Nightly release scenario for tag: %s\n", newVersion)
//isPointRelease = IsPointRelease(string(currentVersion), newVersion)
} else {
// No release tag - normal release process
fmt.Println("STEP 2: No release tag found - proceeding with normal release process")
// Don't actually update files in dry run mode - just simulate
fmt.Println("🔄 SIMULATING VERSION UPDATE...")
if len(os.Args) > 1 {
newVersion = os.Args[1]
fmt.Printf(" Using provided version: %s\n", newVersion)
} else {
// Read current version and simulate increment
currentVersionData, err := os.ReadFile(versionFile)
checkError(err)
currentVersion := strings.TrimSpace(string(currentVersionData))
vsplit := strings.Split(currentVersion, ".")
minorVersion, err := strconv.Atoi(vsplit[len(vsplit)-1])
checkError(err)
minorVersion++
vsplit[len(vsplit)-1] = strconv.Itoa(minorVersion)
newVersion = strings.Join(vsplit, ".")
fmt.Printf(" Current version: %s\n", currentVersion)
fmt.Printf(" Next version would be: %s\n", newVersion)
}
newVersion = updateVersion()
}
fmt.Println("🔄 SIMULATING CHANGELOG UPDATE...")
// Simulate changelog update by checking current structure
s.CD("../../..")
changelogData, err := os.ReadFile("docs/src/content/docs/changelog.mdx")
checkError(err)
changelog := string(changelogData)
// Check if changelog structure is valid
if !strings.Contains(changelog, "## [Unreleased]") {
fmt.Println(" ❌ ERROR: Changelog missing [Unreleased] section")
os.Exit(1)
}
today := time.Now().Format("2006-01-02")
fmt.Printf(" Would add version section: ## %s - %s\n", newVersion, today)
// Simulate extracting unreleased content
unreleasedChangelog, err := extractUnreleasedChangelog()
checkError(err)
releaseChangelog = unreleasedChangelog
fmt.Printf(" ✅ Changelog structure validated\n")
fmt.Printf(" 📝 Extracted %d characters of unreleased content\n", len(releaseChangelog))
}
// Output comprehensive test results
fmt.Println("\nSTEP 3: Generating test results...")
err := outputReleaseMetadata(newVersion, releaseChangelog, hasChanges, changesSummary)
if err != nil {
fmt.Printf("❌ Test failed: %v\n", err)
os.Exit(1)
}
// Update ChangeLog
s.CD("../../..")
// Read in `src/pages/changelog.md`
changelogData, err := os.ReadFile("docs/src/content/docs/changelog.mdx")
checkError(err)
changelog := string(changelogData)
// Split on the line that has `## [Unreleased]`
changelogSplit := strings.Split(changelog, "## [Unreleased]")
// Get today's date in YYYY-MM-DD format
today := time.Now().Format("2006-01-02")
// Add the new version to the top of the changelog
newChangelog := changelogSplit[0] + "## [Unreleased]\n\n## " + newVersion + " - " + today + changelogSplit[1]
// Write the changelog back
err = os.WriteFile("docs/src/content/docs/changelog.mdx", []byte(newChangelog), 0o755)
checkError(err)
// TODO: Documentation Versioning and Translations