diff --git a/.github/workflows/build-and-test-v3.yml b/.github/workflows/build-and-test-v3.yml index ad773fb68..54a0a280c 100644 --- a/.github/workflows/build-and-test-v3.yml +++ b/.github/workflows/build-and-test-v3.yml @@ -3,8 +3,12 @@ name: Build + Test v3 on: pull_request: types: [opened, synchronize, reopened, ready_for_review] + branches: + - v3-alpha pull_request_review: types: [submitted] + branches: + - v3-alpha jobs: check_approval: @@ -22,14 +26,72 @@ jobs: echo "approved=false" >> $GITHUB_OUTPUT fi - test_go: - name: Run Go Tests + test_js: + name: Run JS Tests needs: check_approval + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [20.x] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - name: Install Task + uses: arduino/setup-task@v2 + with: + version: 3.x + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Install dependencies + working-directory: v3/internal/runtime/desktop/@wailsio/runtime + run: | + npm ci + npx --yes esbuild@latest --version + + - name: Clean build artifacts + working-directory: v3/internal/runtime/desktop/@wailsio/runtime + run: npm run clean + + - name: Type-check runtime + working-directory: v3 + run: task runtime:check + + - name: Test runtime + working-directory: v3 + run: task runtime:test + + - name: Check that the bundled runtime builds + working-directory: v3 + run: task runtime:build + + - name: Check that the npm package builds + working-directory: v3/internal/runtime/desktop/@wailsio/runtime + run: npm run build + + - name: Store runtime build artifacts + uses: actions/upload-artifact@v4 + with: + name: runtime-build-artifacts + path: | + v3/internal/runtime/desktop/@wailsio/runtime/dist/ + v3/internal/runtime/desktop/@wailsio/runtime/types/ + v3/internal/runtime/desktop/@wailsio/runtime/tsconfig.tsbuildinfo + + test_go: + name: Run Go Tests v3 + needs: [check_approval, test_js] runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - os: [windows-latest, macos-latest, ubuntu-latest] + os: [warp-windows-latest-x64-2x, warp-macos-15-arm64-6x, warp-ubuntu-latest-x64-2x] go-version: [1.24] steps: @@ -55,25 +117,31 @@ jobs: version: 3.x repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Retrieve runtime build artifacts + uses: actions/download-artifact@v4 + with: + name: runtime-build-artifacts + path: v3/internal/runtime/desktop/@wailsio/runtime/ + - name: Build Examples - working-directory: ./v3 + working-directory: v3 run: task test:examples - name: Run tests (mac) if: matrix.os == 'macos-latest' env: CGO_LDFLAGS: -framework UniformTypeIdentifiers -mmacosx-version-min=10.13 - working-directory: ./v3 + working-directory: v3 run: go test -v ./... - name: Run tests (windows) if: matrix.os == 'windows-latest' - working-directory: ./v3 + working-directory: v3 run: go test -v ./... - name: Run tests (ubuntu) if: matrix.os == 'ubuntu-latest' - working-directory: ./v3 + working-directory: v3 run: > xvfb-run --auto-servernum sh -c ' @@ -82,33 +150,19 @@ jobs: ' - name: Typecheck binding generator output - working-directory: ./v3 + working-directory: v3 run: task generator:test:check - test_js: - name: Run JS Tests - needs: check_approval + cleanup: + name: Cleanup build artifacts + if: always() + needs: [test_js, test_go] runs-on: ubuntu-latest - strategy: - matrix: - node-version: [20.x] - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 + - uses: geekyeggo/delete-artifact@v5 with: - node-version: ${{ matrix.node-version }} - - - name: Install dependencies - run: npm install - working-directory: v2/internal/frontend/runtime - - - name: Run tests - run: npm test - working-directory: v2/internal/frontend/runtime + name: runtime-build-artifacts + failOnError: false test_templates: name: Test Templates @@ -156,7 +210,7 @@ jobs: repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Build Wails3 CLI - working-directory: ./v3 + working-directory: v3 run: | task install wails3 doctor @@ -168,3 +222,17 @@ jobs: wails3 init -n ${{ matrix.template }} -t ${{ matrix.template }} cd ${{ matrix.template }} wails3 build + + results: + if: ${{ always() }} + runs-on: ubuntu-latest + name: v3 Build Results + needs: [test_go, test_js, test_templates] + steps: + - run: | + result="${{ needs.build.result }}" + if [[ $result == "success" || $result == "skipped" ]]; then + exit 0 + else + exit 1 + fi \ No newline at end of file diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index c70050276..9e981bf1d 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -2,8 +2,12 @@ name: PR Checks on: pull_request: + branches: + - master pull_request_review: types: [submitted] + branches: + - master jobs: check_docs: diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 0db261abe..eb7275f30 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -1,62 +1,113 @@ on: push: branches: ['v3-alpha'] + workflow_dispatch: + +concurrency: + group: publish-npm-v3 + cancel-in-progress: true jobs: - publish: + detect: + name: Detect committed changes + if: github.event_name != 'workflow_dispatch' + outputs: + changed: ${{ steps.package-json-changes.outputs.any_modified == 'true' || steps.source-changes.outputs.any_modified == 'true' }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.sha }} + persist-credentials: 'true' + + - name: Detect committed package.json changes + id: package-json-changes + uses: tj-actions/changed-files@v45 + with: + files: | + v3/internal/runtime/desktop/@wailsio/runtime/package.json + + - name: Detect committed source changes + if: >- + steps.package-json-changes.outputs.any_modified != 'true' + id: source-changes + uses: tj-actions/changed-files@v45 + with: + files: | + v3/internal/runtime/Taskfile.yaml + v3/internal/runtime/desktop/@wailsio/compiled/main.js + v3/internal/runtime/desktop/@wailsio/runtime/tsconfig.json + v3/internal/runtime/desktop/@wailsio/runtime/src/** + v3/pkg/events/events.txt + v3/tasks/events/** + + rebuild_and_publish: + name: Rebuild and publish + needs: [detect] + if: >- + !failure() && !cancelled() + && (github.event_name == 'workflow_dispatch' || needs.detect.outputs.changed == 'true') + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 with: fetch-depth: 0 ref: 'v3-alpha' ssh-key: ${{ secrets.DEPLOY_KEY }} - - name: Configure git run: | git config --local user.email "github-actions@github.com" git config --local user.name "GitHub Actions" - - name: Setup go-task - uses: pnorton5432/setup-task@v1 + - name: Install Task + uses: arduino/setup-task@v2 with: - task-version: 3.29.1 + version: 3.x + repo-token: ${{ secrets.GITHUB_TOKEN }} - - uses: actions/setup-node@v3 + - name: Use Node.js 20 + uses: actions/setup-node@v4 with: node-version: "20" - - run: | - npm ci - npm run build:types - npm run build:docs + + - name: Install dependencies working-directory: v3/internal/runtime/desktop/@wailsio/runtime + run: | + npm ci + npx --yes esbuild@latest --version - - name: Verify Changed files - uses: tj-actions/verify-changed-files@v20 - id: verify-changed-files - with: - files: | - v3/internal/runtime/desktop/@wailsio/runtime/src/*.js - v3/internal/runtime/desktop/@wailsio/runtime/types/*.d.ts - v3/internal/runtime/desktop/@wailsio/runtime/docs/**/*.* + - name: Clean build artifacts + working-directory: v3/internal/runtime/desktop/@wailsio/runtime + run: npm run clean - - name: test action - if: steps.verify-changed-files.outputs.files_changed == 'true' - id: get-version - uses: beaconbrigade/package-json-version@v0.3.2 - with: - path: v3/internal/runtime/desktop/@wailsio/runtime + - name: Build bundled runtime + working-directory: v3 + run: task runtime:build + + - name: Test+Build npm package + working-directory: v3/internal/runtime/desktop/@wailsio/runtime + run: | + npm test + npm run build + + - name: Bump version + id: bump-version + working-directory: v3/internal/runtime/desktop/@wailsio/runtime + run: | + echo "version=$(npm --no-git-tag-version --force version prerelease)" >> "$GITHUB_OUTPUT" - name: Commit changes - if: steps.verify-changed-files.outputs.files_changed == 'true' run: | git add . - git commit -m "[skip ci] Publish @wailsio/runtime ${{ steps.get-version.outputs.version }}" + git commit -m "[skip ci] Publish @wailsio/runtime ${{ steps.bump-version.outputs.version }}" git push + fi - - uses: JS-DevTools/npm-publish@v3 - if: steps.verify-changed-files.outputs.files_changed == 'true' + - name: Publish npm package + uses: JS-DevTools/npm-publish@v3 with: package: v3/internal/runtime/desktop/@wailsio/runtime access: public diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx index 3974f344e..fd18b76c6 100644 --- a/docs/src/content/docs/changelog.mdx +++ b/docs/src/content/docs/changelog.mdx @@ -53,6 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `//wails:ignore` directive to prevent binding generation for chosen service methods by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) - Add `//wails:internal` directive on services and models to allow for types that are exported in Go but not in JS/TS by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) - Add binding generator support for constants of alias type to allow for weakly typed enums by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Add binding generator tests for Go 1.24 features by [@fbbdev](https://github.com/fbbdev) in [#4068](https://github.com/wailsapp/wails/pull/4068) - Add support for macOS 15 "Sequoia" to `OSInfo.Branding` for improved OS version detection in [#4065](https://github.com/wailsapp/wails/pull/4065) - Add `PostShutdown` hook for running custom code after the shutdown process completes by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) - Add `FatalError` struct to support detection of fatal errors in custom error handlers by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) @@ -60,6 +61,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add test harness for application startup/shutdown sequence and service startup/shutdown tests by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) - Add `RegisterService` method for registering services after the application has been created by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) - Add `MarshalError` field in application and service options for custom error handling in binding calls by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) +- Add cancellable promise wrapper that propagates cancellation requests through promise chains by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100) +- Add the ability to tie binding call cancellation to an `AbortSignal` by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100) +- Support `data-wml-*` attributes for WML alongside the usual `wml-*` attributes by [@leaanthony](https://github.com/leaanthony) - Add `Configure` method on all services for late configuration/dynamic reconfiguration by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) - `fileserver` service sends a 503 Service Unavailable response when unconfigured by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) - `kvstore` service provides an in-memory key-value store by default when unconfigured by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) @@ -100,6 +104,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed hidden menu items on macOS by [@leaanthony](https://github.com/leaanthony) - Fixed handling and formatting of errors in message processors by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) - Fixed skipped service shutdown when quitting application by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) +- Ensure menu updates occur on the main thread by [@leaanthony](https://github.com/leaanthony) +- The dragging and resizing mechanism is now more robust and matches expected platform behaviour more closely by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100) +- Fixed [#4097](https://github.com/wailsapp/wails/issues/4097) Webpack/angular discards runtime init code by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100) ### Changed @@ -120,6 +127,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `ServiceStartup` hooks are now invoked when `App.Run` is called, not in `application.New` by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) - `ServiceStartup` errors are now returned from `App.Run` instead of terminating the process by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) - Binding and dialog calls from JS now reject with error objects instead of strings by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) +- Improved systray menu positioning on Windows by [@leaanthony](https://github.com/leaanthony) +- The JS runtime has been ported to TypeScript by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100) +- The runtime initialises as soon as it is imported, no need to wait for the window to load by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100) +- The runtime does not export an init method anymore. A side effects import can be used to initialise it by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100) +- Bound methods now return a `CancellablePromise` that rejects with a `CancelError` if cancelled. The actual result of the call is discarded by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100) - Built-in service types are now consistently called `Service` by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) - Built-in service creation functions with options are now consistently called `NewWithConfig` by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) - `Select` method on `sqlite` service is now named `Query` for consistency with Go APIs by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) diff --git a/docs/src/content/docs/guides/menus.mdx b/docs/src/content/docs/guides/menus.mdx index 58a9cd313..50023ab4d 100644 --- a/docs/src/content/docs/guides/menus.mdx +++ b/docs/src/content/docs/guides/menus.mdx @@ -366,6 +366,10 @@ You can control when the default context menu appears using the `--default-conte ``` +:::note +This feature will only work as expected after the runtime [has been initialised](../../learn/runtime#initialisation). +::: + #### Nested Context Menu Behavior When using the `--default-contextmenu` property on nested elements, the following rules apply: diff --git a/docs/src/content/docs/learn/bindings.mdx b/docs/src/content/docs/learn/bindings.mdx index 1ca0ee590..d66de725f 100644 --- a/docs/src/content/docs/learn/bindings.mdx +++ b/docs/src/content/docs/learn/bindings.mdx @@ -437,8 +437,9 @@ func (s *MyService) WindowAwareMethod(ctx context.Context) (string, error) { ``` From the frontend, these methods can be called normally. If you need to cancel a -long-running operation, the Promise will be rejected with the cancellation -error: +long-running operation, you can call the special `cancel` method on the promise +and it will reject immediately with a special cancellation error; +the Go context will be cancelled and the actual result of the call will be discarded: ```javascript // Call the method @@ -449,6 +450,84 @@ const promise = MyService.LongRunningTask("input"); promise.cancel(); ``` +In fact, the runtime returns a special promise wrapper +that provides cancellation support for arbitrarily long promise chains. +For example: + +```javascript +import { CancelError } from "@wailsio/runtime"; + +// Call the method and process its output +const promise = MyService.LongRunningTask("input").then((result) => { + console.log(result); +}).catch((err) => { + if (err instanceof CancelError) { + console.log("Cancelled.", err.cause); + } else { + console.error("Failed.", err); + } +}); + +// Later... +// cancel() accepts an optional cause parameter +// that will be attached to the cancellation error: +promise.cancel("I'm tired of waiting!").then(() => { + // Cancellation has been requested successfully + // and all handlers attached above have run. + console.log("Ready for the next adventure!"); +}); +``` + +The `cancel` method returns a promise that fulfills always (and never rejects) +after the cancellation request has been submitted successfully +and all previously attached handlers have run. + +:::note +Calling the `cancel` method on a settled promise is safe and has no effect; +if the task completes before the call to `cancel`, the code above is going to log: + +``` +completed +Ready for the next adventure! +``` + +However, if `cancel` is called before the task finishes, the output will be: + +``` +Cancelled. I'm tired of waiting! +Ready for the next adventure! +``` +::: + +The approach discussed above requires storing and chaining promises manually, +which can be cumbersome for code written in `async`/`await` style. +If you target plaforms that support the `AbortController`/`AbortSignal` idiom, +you can call the `cancelOn` method and tie call cancellation to an `AbortSignal` instead: + +```javascript +async function callBinding(signal) { + try { + await MyService.LongRunningTask("input").cancelOn(signal); + } catch (err) { + if (err instanceof CancelError) { + console.log("Cancelled! Cause: ", err.cause); + } else { + console.error("Failed! Error: ", err); + } + } +} + +let controller = new AbortController(); +callBinding(controller.signal); + +// Later... +controller.abort("I'm tired of waiting!"); +``` + +:::caution +On the macOS platform, `AbortSignal` is only supported from macOS 10.15 Catalina onwards. +::: + ### Handling errors As you may have noticed above, bound methods can return errors, which are handled specially. diff --git a/docs/src/content/docs/learn/context-menu.mdx b/docs/src/content/docs/learn/context-menu.mdx index f56e9a763..ca57f182b 100644 --- a/docs/src/content/docs/learn/context-menu.mdx +++ b/docs/src/content/docs/learn/context-menu.mdx @@ -65,6 +65,10 @@ To associate a context menu with an HTML element, use the `--custom-contextmenu` - `--custom-contextmenu`: Specifies the menu ID (must match the ID used in `NewContextMenu`) - `--custom-contextmenu-data`: Optional data that will be passed to the click handlers +:::note +This feature will only work as expected after the runtime [has been initialised](../runtime#initialisation). +::: + ## Default Context Menu The default context menu is the webview's built-in context menu that provides system-level operations. You can control its visibility using the `--default-contextmenu` CSS property: diff --git a/docs/src/content/docs/learn/runtime.mdx b/docs/src/content/docs/learn/runtime.mdx index 3c2fbfc33..7b66a06b3 100644 --- a/docs/src/content/docs/learn/runtime.mdx +++ b/docs/src/content/docs/learn/runtime.mdx @@ -22,12 +22,12 @@ The runtime is required for integration between Go and the frontend. There are 2 ways to integrate the runtime: - Using the `@wailsio/runtime` package -- Using a pre-built version of the runtime +- Using a pre-built bundle ## Using the npm package The `@wailsio/runtime` package is a JavaScript package that provides access to -the Wails runtime from the frontend. It is used in by all the standard templates +the Wails runtime from the frontend. It is used by all standard templates and is the recommended way to integrate the runtime into your application. By using the `@wailsio/runtime` package, you will only include the parts of the runtime that you use. @@ -37,25 +37,55 @@ The package is available on npm and can be installed using: npm install --save @wailsio/runtime ``` -## Using a pre-built local version of the runtime +## Using a pre-built bundle Some projects will not use a Javascript bundler and may prefer to use a -pre-built version of the runtime. This is the default for the examples in -`v3/examples`. The pre-built version of the runtime can be generated using the -following command: +pre-built bundled version of the runtime. This version can be generated locally +using the following command: ```shell wails3 generate runtime ``` -This will generate a `runtime.js` (and `runtime.debug.js`) file in the current -directory. This file can be used by your application by adding it to your frontend project: +The command will output a `runtime.js` (and `runtime.debug.js`) file in the current +directory. This file is an ES module that can be imported by your application scripts +just like the npm package, but the API is also exported to the global window object, +so for simpler applications you can use it as follows: ```html
- + + -> + ``` + +:::caution +It is important to include the `type="module"` attribute on the ` +