mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 14:45:49 +01:00
Merge remote-tracking branch 'wails/v3-alpha' into v3/deeplink
This commit is contained in:
commit
101b09676d
15 changed files with 245 additions and 371 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
v3.0.0-alpha.11
|
||||
v3.0.0-alpha.12
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ type Window interface {
|
|||
ToggleFullscreen()
|
||||
ToggleMaximise()
|
||||
ToggleMenuBar()
|
||||
ToggleFrameless()
|
||||
UnFullscreen()
|
||||
UnMaximise()
|
||||
UnMinimise()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue