Compare commits

..

7 Commits

Author SHA1 Message Date
bd5bfb0f1e
ci: remove unused remote_contract job from build-and-release workflow (#71)
The remote_contract job was effectively dead code — it only ran on
workflow_dispatch (excluded push and pull_request events) and was
configured with continue-on-error, so it never blocked releases.

Removing it simplifies the pipeline and eliminates the always()
workaround in the release job's if-condition.

Co-authored-by: Haitao Pan <manbuzhe2009@qq.com>
2026-06-30 13:59:47 +08:00
d130ea31e2
fix(macos): generate matching WebRTC framework dSYM (#68)
Co-authored-by: Haitao Pan <manbuzhe2009@qq.com>
2026-06-30 13:24:04 +08:00
1666cbabe7
chore: resolve merge conflict in pubspec.yaml (#67)
Co-authored-by: Haitao Pan <manbuzhe2009@qq.com>
2026-06-30 12:07:32 +08:00
2295960a74
chore: resolve merge conflict in pubspec.yaml (#66)
Co-authored-by: Haitao Pan <manbuzhe2009@qq.com>
2026-06-30 12:05:51 +08:00
08ba6e30f7
fix(macos): workaround App Store Connect dSYM validation bug (#62)
* fix(macos): workaround App Store Connect dSYM validation bug for App.framework

* test: mock device and package plugins and increase timeout

- Increase sync loop timeout in thread workspace binding test to avoid flakiness
- Mock device_info and package_info plugins for gateway runtime tests
- Update pubspec.yaml version

* test: fix missing plugin in runtime_controllers_settings_account_test

* build: make sync-version.sh auto-increment build number

---------

Co-authored-by: Haitao Pan <manbuzhe2009@qq.com>
2026-06-30 10:48:35 +08:00
6fb1441226
refactor: replace super_clipboard with pasteboard (drop cargokit/Rust) (#55)
* refactor: replace super_clipboard with pasteboard, drop cargokit/Rust

super_clipboard pulled in super_native_extensions (a Rust native layer
built via cargokit), whose precompiled-binary download from GitHub
release assets has been intermittently failing the build ("Connection
closed while receiving data"). It was used for exactly one feature -
reading a clipboard image into the composer - in a single file; the
other 12 imports were dead.

- Swap super_clipboard -> pasteboard (platform-channel, no Rust).
- Rewrite readClipboardImageAsXFileInternal() on Pasteboard.image
  (PNG bytes), collapsing three helpers into one.
- Remove 12 unused super_clipboard imports.
- Regenerated plugin registrants / lockfiles drop super_native_extensions.

Removes the Rust toolchain requirement and the flaky download entirely.
Text copy/paste already used Flutter's built-in Clipboard and is
unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* ci: keep TestFlight package release-only

---------

Co-authored-by: Haitao Pan <manbuzhe2009@qq.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 08:30:26 +08:00
01515f95ca
ci: TestFlight opt-in toggle + Xcode 27 build fixes (#54)
* ci: gate TestFlight behind opt-in toggle + Xcode 27 build fixes

TestFlight is now opt-in (default OFF). A workflow_dispatch boolean
`enable_testflight` (or the `ENABLE_TESTFLIGHT` repo variable) drives a
`prepare.outputs.testflight_enabled` flag that gates the macOS
app-store-pkg build leg and both testflight_ios/testflight_macos upload
legs. Missing Apple signing secrets no longer fail the normal DMG/IPA
release path (package-macos-app-store-pkg.sh hard-exits without them).

Xcode 27 build compatibility:
- Align Apple deployment targets so no pod sits below the app minimum
  (Xcode 27 rejects this): macOS pods + RunnerTests -> 15.6, iOS pods
  -> 15.5 to match the Runner targets.
- Add a `lipo` shim (scripts/xcode-tools/lipo) wired onto PATH in the
  iOS/macOS build phases; Xcode 27 only accepts one `-verify_arch`
  architecture per call while Flutter passes them all at once.
- macOS project hygiene: correct PrivacyInfo.xcprivacy path, set app
  display name + LSApplicationCategoryType.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test: make temp-dir cleanup resilient to concurrent-write races

The assistant execution target tests deleted their temp HOME/workspace
dirs with a raw recursive delete in addTearDown. A background flush
(e.g. controller dispose still persisting state) can keep writing into
the dir while the delete walks it, so the delete races and fails with
"Directory not empty" (errno 39), failing the test on CI.

Route all unguarded teardown deletes through the existing
_resilientDelete helper (re-check existence + retry), and harden that
helper so its final fallback never re-throws — a temp-dir cleanup
failure must never fail a test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Haitao Pan <manbuzhe2009@qq.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 07:27:09 +08:00
30 changed files with 167 additions and 223 deletions

View File

@ -35,6 +35,10 @@ on:
description: "Build & upload TestFlight (macOS/iOS App Store) artifacts"
type: boolean
default: false
enable_github_release:
description: "Upload assets to GitHub Release"
type: boolean
default: true
permissions:
contents: read
@ -54,6 +58,7 @@ jobs:
contents: write
outputs:
should_release: ${{ steps.flags.outputs.should_release }}
github_release_enabled: ${{ steps.flags.outputs.github_release_enabled }}
testflight_enabled: ${{ steps.flags.outputs.testflight_enabled }}
release_tag: ${{ steps.meta.outputs.release_tag }}
release_title: ${{ steps.meta.outputs.release_title }}
@ -68,6 +73,7 @@ jobs:
id: flags
shell: bash
env:
ENABLE_GITHUB_RELEASE_INPUT: ${{ github.event.inputs.enable_github_release }}
ENABLE_TESTFLIGHT_INPUT: ${{ github.event.inputs.enable_testflight }}
ENABLE_TESTFLIGHT_VAR: ${{ vars.ENABLE_TESTFLIGHT }}
run: |
@ -77,6 +83,12 @@ jobs:
echo "should_release=false" >> "$GITHUB_OUTPUT"
fi
if [[ "${GITHUB_EVENT_NAME:-}" == "workflow_dispatch" && "${ENABLE_GITHUB_RELEASE_INPUT:-}" == "false" ]]; then
echo "github_release_enabled=false" >> "$GITHUB_OUTPUT"
else
echo "github_release_enabled=true" >> "$GITHUB_OUTPUT"
fi
# TestFlight is opt-in (default OFF). Enabled only when explicitly
# requested via the workflow_dispatch input or the ENABLE_TESTFLIGHT
# repo/org variable. Keeps missing Apple signing secrets from failing
@ -144,7 +156,11 @@ jobs:
- platform: macos
arch: arm64
package: app-store-pkg
release_only: true
# Quoted so the `matrix.release_only != 'true'` guard compares
# string-to-string. A YAML boolean here coerces to a number in
# GitHub expressions (true -> 1, 'true' -> NaN), making the guard
# always true and building this release-only leg on every PR.
release_only: "true"
runs_on: macos-14
artifact_name: build-macos-arm64-pkg
artifact_paths: |
@ -288,46 +304,9 @@ jobs:
path: ${{ matrix.artifact_paths }}
if-no-files-found: error
remote_contract:
name: Test - remote provider contract
runs-on: ubuntu-22.04
needs:
- build
# Test-stage quality gate: runs between build and release.
# continue-on-error keeps it skippable so a failure never blocks release.
continue-on-error: true
if: ${{ github.event_name != 'push' && github.event_name != 'pull_request' }}
steps:
- name: Checkout source
uses: actions/checkout@v7
- name: Load Vault secrets
id: vault
uses: hashicorp/vault-action@v4
with:
url: ${{ env.VAULT_ADDR }}
method: jwt
role: github-actions-xworkmate-app
jwtGithubAudience: vault
ignoreNotFound: true
secrets: |
kv/data/github-actions/xworkmate-app REVIEW_ACCOUNT_LOGIN_PASSWORD | REVIEW_ACCOUNT_LOGIN_PASSWORD
- name: Export remote contract secrets
run: echo "REVIEW_ACCOUNT_LOGIN_PASSWORD=${{ steps.vault.outputs.REVIEW_ACCOUNT_LOGIN_PASSWORD }}" >> "$GITHUB_ENV"
- name: Verify accounts to bridge provider contract
shell: bash
env:
REVIEW_ACCOUNT_BASE_URL: ${{ vars.REVIEW_ACCOUNT_BASE_URL }}
REVIEW_ACCOUNT_LOGIN_NAME: ${{ vars.REVIEW_ACCOUNT_LOGIN_NAME }}
run: bash ./scripts/ci/verify_remote_provider_contract.sh
release:
# always() so release waits for the remote_contract gate to finish but is
# never blocked by it being skipped (e.g. push events) or failing.
# build/prepare must still genuinely succeed.
if: ${{ always() && needs.prepare.outputs.should_release == 'true' && needs.prepare.result == 'success' && needs.build.result == 'success' }}
if: ${{ needs.prepare.outputs.should_release == 'true' && needs.prepare.result == 'success' && needs.build.result == 'success' }}
strategy:
fail-fast: false
matrix:
@ -350,7 +329,6 @@ jobs:
needs:
- prepare
- build
- remote_contract
steps:
- name: Checkout source
uses: actions/checkout@v7

View File

@ -9,15 +9,13 @@ PODS:
- WebRTC-SDK (= 125.6422.06)
- integration_test (0.0.1):
- Flutter
- irondash_engine_context (0.0.1):
- Flutter
- package_info_plus (0.4.5):
- Flutter
- pasteboard (0.0.1):
- Flutter
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- super_native_extensions (0.0.1):
- Flutter
- WebRTC-SDK (125.6422.06)
DEPENDENCIES:
@ -26,10 +24,9 @@ DEPENDENCIES:
- Flutter (from `Flutter`)
- flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`)
- integration_test (from `.symlinks/plugins/integration_test/ios`)
- irondash_engine_context (from `.symlinks/plugins/irondash_engine_context/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- pasteboard (from `.symlinks/plugins/pasteboard/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- super_native_extensions (from `.symlinks/plugins/super_native_extensions/ios`)
SPEC REPOS:
trunk:
@ -46,14 +43,12 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_webrtc/ios"
integration_test:
:path: ".symlinks/plugins/integration_test/ios"
irondash_engine_context:
:path: ".symlinks/plugins/irondash_engine_context/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
pasteboard:
:path: ".symlinks/plugins/pasteboard/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
super_native_extensions:
:path: ".symlinks/plugins/super_native_extensions/ios"
SPEC CHECKSUMS:
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
@ -61,10 +56,9 @@ SPEC CHECKSUMS:
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_webrtc: 57f32415b8744e806f9c2a96ccdb60c6a627ba33
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
pasteboard: 3913b69d3f2be214970a8ae94e7e87fe76e47e98
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
super_native_extensions: b763c02dc3a8fd078389f410bf15149179020cb4
WebRTC-SDK: 79942c006ea64f6fb48d7da8a4786dfc820bc1db
PODFILE CHECKSUM: ca16f6ef66890e172b6528d5f0eb390e0410291e

View File

@ -10,7 +10,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:markdown/markdown.dart' as md;
import 'package:path_provider/path_provider.dart';
import 'package:super_clipboard/super_clipboard.dart';
import '../../app/app_controller.dart';
import '../../app/app_metadata.dart';
import '../../app/ui_feature_manifest.dart';

View File

@ -10,7 +10,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:markdown/markdown.dart' as md;
import 'package:path_provider/path_provider.dart';
import 'package:super_clipboard/super_clipboard.dart';
import '../../app/app_controller.dart';
import '../../app/app_metadata.dart';
import '../../app/ui_feature_manifest.dart';

View File

@ -9,7 +9,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:markdown/markdown.dart' as md;
import 'package:path_provider/path_provider.dart';
import 'package:super_clipboard/super_clipboard.dart';
import '../../app/app_controller.dart';
import '../../app/app_metadata.dart';
import '../../app/ui_feature_manifest.dart';

View File

@ -10,7 +10,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:markdown/markdown.dart' as md;
import 'package:path_provider/path_provider.dart';
import 'package:super_clipboard/super_clipboard.dart';
import 'package:pasteboard/pasteboard.dart';
import '../../app/app_controller.dart';
import '../../app/app_metadata.dart';
import '../../app/ui_feature_manifest.dart';
@ -82,88 +82,24 @@ class AssistantPasteIntent extends Intent {
}
Future<XFile?> readClipboardImageAsXFileInternal() async {
final clipboard = SystemClipboard.instance;
if (clipboard == null) {
// pasteboard normalizes clipboard images to PNG bytes across platforms.
Uint8List? bytes;
try {
bytes = await Pasteboard.image;
} catch (error, stackTrace) {
debugPrint('Error reading clipboard image: $error\n$stackTrace');
return null;
}
final reader = await clipboard.read();
return await readClipboardImageForFormatInternal(
reader,
format: Formats.png,
extension: 'png',
mimeType: 'image/png',
) ??
await readClipboardImageForFormatInternal(
reader,
format: Formats.jpeg,
extension: 'jpg',
mimeType: 'image/jpeg',
) ??
await readClipboardImageForFormatInternal(
reader,
format: Formats.gif,
extension: 'gif',
mimeType: 'image/gif',
) ??
await readClipboardImageForFormatInternal(
reader,
format: Formats.webp,
extension: 'webp',
mimeType: 'image/webp',
);
}
Future<XFile?> readClipboardImageForFormatInternal(
ClipboardReader reader, {
required FileFormat format,
required String extension,
required String mimeType,
}) async {
if (!reader.canProvide(format)) {
return null;
}
final bytes = await readClipboardFileBytesInternal(reader, format);
if (bytes == null || bytes.isEmpty) {
return null;
}
final temporaryDirectory =
await resolveClipboardAttachmentTempDirectoryInternal();
final fileName =
'clipboard-image-${DateTime.now().microsecondsSinceEpoch}.$extension';
'clipboard-image-${DateTime.now().microsecondsSinceEpoch}.png';
final file = File('${temporaryDirectory.path}/$fileName');
await file.writeAsBytes(bytes, flush: true);
return XFile(file.path, mimeType: mimeType, name: fileName);
}
Future<Uint8List?> readClipboardFileBytesInternal(
ClipboardReader reader,
FileFormat format,
) {
final completer = Completer<Uint8List?>();
final progress = reader.getFile(
format,
(file) async {
try {
final bytes = await file.readAll();
if (!completer.isCompleted) {
completer.complete(bytes);
}
} catch (error, stackTrace) {
if (!completer.isCompleted) {
completer.completeError(error, stackTrace);
}
}
},
onError: (error) {
if (!completer.isCompleted) {
completer.completeError(error);
}
},
);
if (progress == null) {
return Future<Uint8List?>.value(null);
}
return completer.future;
return XFile(file.path, mimeType: 'image/png', name: fileName);
}
Future<Directory> resolveClipboardAttachmentTempDirectoryInternal() async {

View File

@ -10,7 +10,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:markdown/markdown.dart' as md;
import 'package:path_provider/path_provider.dart';
import 'package:super_clipboard/super_clipboard.dart';
import '../../app/app_controller.dart';
import '../../app/app_metadata.dart';
import '../../app/ui_feature_manifest.dart';

View File

@ -10,7 +10,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:markdown/markdown.dart' as md;
import 'package:path_provider/path_provider.dart';
import 'package:super_clipboard/super_clipboard.dart';
import '../../app/app_controller.dart';
import '../../app/app_metadata.dart';
import '../../app/ui_feature_manifest.dart';

View File

@ -10,7 +10,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:markdown/markdown.dart' as md;
import 'package:path_provider/path_provider.dart';
import 'package:super_clipboard/super_clipboard.dart';
import '../../app/app_controller.dart';
import '../../app/app_metadata.dart';
import '../../app/ui_feature_manifest.dart';

View File

@ -10,7 +10,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:markdown/markdown.dart' as md;
import 'package:path_provider/path_provider.dart';
import 'package:super_clipboard/super_clipboard.dart';
import '../../app/app_controller.dart';
import '../../app/app_metadata.dart';
import '../../app/ui_feature_manifest.dart';

View File

@ -10,7 +10,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:markdown/markdown.dart' as md;
import 'package:path_provider/path_provider.dart';
import 'package:super_clipboard/super_clipboard.dart';
import '../../app/app_controller.dart';
import '../../app/app_metadata.dart';
import '../../app/ui_feature_manifest.dart';

View File

@ -8,7 +8,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:markdown/markdown.dart' as md;
import 'package:path_provider/path_provider.dart';
import 'package:super_clipboard/super_clipboard.dart';
import '../../app/app_controller.dart';
import '../../app/app_controller_desktop_thread_binding.dart';
import '../../app/app_metadata.dart';

View File

@ -10,7 +10,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:markdown/markdown.dart' as md;
import 'package:path_provider/path_provider.dart';
import 'package:super_clipboard/super_clipboard.dart';
import '../../app/app_controller.dart';
import '../../app/app_metadata.dart';
import '../../app/ui_feature_manifest.dart';

View File

@ -10,7 +10,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:markdown/markdown.dart' as md;
import 'package:path_provider/path_provider.dart';
import 'package:super_clipboard/super_clipboard.dart';
import '../../app/app_controller.dart';
import '../../app/app_metadata.dart';
import '../../app/ui_feature_manifest.dart';

View File

@ -10,7 +10,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:markdown/markdown.dart' as md;
import 'package:path_provider/path_provider.dart';
import 'package:super_clipboard/super_clipboard.dart';
import '../../app/app_controller.dart';
import '../../app/app_metadata.dart';
import '../../app/ui_feature_manifest.dart';

View File

@ -8,8 +8,7 @@
#include <file_selector_linux/file_selector_plugin.h>
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
#include <irondash_engine_context/irondash_engine_context_plugin.h>
#include <super_native_extensions/super_native_extensions_plugin.h>
#include <pasteboard/pasteboard_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
@ -18,10 +17,7 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) flutter_webrtc_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterWebRTCPlugin");
flutter_web_r_t_c_plugin_register_with_registrar(flutter_webrtc_registrar);
g_autoptr(FlPluginRegistrar) irondash_engine_context_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "IrondashEngineContextPlugin");
irondash_engine_context_plugin_register_with_registrar(irondash_engine_context_registrar);
g_autoptr(FlPluginRegistrar) super_native_extensions_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "SuperNativeExtensionsPlugin");
super_native_extensions_plugin_register_with_registrar(super_native_extensions_registrar);
g_autoptr(FlPluginRegistrar) pasteboard_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "PasteboardPlugin");
pasteboard_plugin_register_with_registrar(pasteboard_registrar);
}

View File

@ -5,8 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
file_selector_linux
flutter_webrtc
irondash_engine_context
super_native_extensions
pasteboard
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST

View File

@ -8,17 +8,15 @@ import Foundation
import device_info_plus
import file_selector_macos
import flutter_webrtc
import irondash_engine_context
import package_info_plus
import pasteboard
import shared_preferences_foundation
import super_native_extensions
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
IrondashEngineContextPlugin.register(with: registry.registrar(forPlugin: "IrondashEngineContextPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SuperNativeExtensionsPlugin.register(with: registry.registrar(forPlugin: "SuperNativeExtensionsPlugin"))
}

View File

@ -7,15 +7,13 @@ PODS:
- FlutterMacOS
- WebRTC-SDK (= 125.6422.06)
- FlutterMacOS (1.0.0)
- irondash_engine_context (0.0.1):
- FlutterMacOS
- package_info_plus (0.0.1):
- FlutterMacOS
- pasteboard (0.0.1):
- FlutterMacOS
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- super_native_extensions (0.0.1):
- FlutterMacOS
- WebRTC-SDK (125.6422.06)
DEPENDENCIES:
@ -23,10 +21,9 @@ DEPENDENCIES:
- file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`)
- flutter_webrtc (from `Flutter/ephemeral/.symlinks/plugins/flutter_webrtc/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- irondash_engine_context (from `Flutter/ephemeral/.symlinks/plugins/irondash_engine_context/macos`)
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
- pasteboard (from `Flutter/ephemeral/.symlinks/plugins/pasteboard/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- super_native_extensions (from `Flutter/ephemeral/.symlinks/plugins/super_native_extensions/macos`)
SPEC REPOS:
trunk:
@ -41,24 +38,21 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_webrtc/macos
FlutterMacOS:
:path: Flutter/ephemeral
irondash_engine_context:
:path: Flutter/ephemeral/.symlinks/plugins/irondash_engine_context/macos
package_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
pasteboard:
:path: Flutter/ephemeral/.symlinks/plugins/pasteboard/macos
shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
super_native_extensions:
:path: Flutter/ephemeral/.symlinks/plugins/super_native_extensions/macos
SPEC CHECKSUMS:
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
file_selector_macos: 9e9e068e90ebee155097d00e89ae91edb2374db7
flutter_webrtc: 377dbcebdde6fed0fc40de87bcaaa2bffcec9a88
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
irondash_engine_context: 893c7d96d20ce361d7e996f39d360c4c2f9869ba
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
pasteboard: b594eaf838d930b276d7a35a44a32b4f489170cb
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
super_native_extensions: c2795d6d9aedf4a79fae25cb6160b71b50549189
WebRTC-SDK: 79942c006ea64f6fb48d7da8a4786dfc820bc1db
PODFILE CHECKSUM: 7804cba3ecbc9953edc70dee53b2ce2b4aeaa013

View File

@ -348,22 +348,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.20.2"
irondash_engine_context:
dependency: transitive
description:
name: irondash_engine_context
sha256: "2bb0bc13dfda9f5aaef8dde06ecc5feb1379f5bb387d59716d799554f3f305d7"
url: "https://pub.dev"
source: hosted
version: "0.5.5"
irondash_message_channel:
dependency: transitive
description:
name: irondash_message_channel
sha256: b4101669776509c76133b8917ab8cfc704d3ad92a8c450b92934dd8884a2f060
url: "https://pub.dev"
source: hosted
version: "0.7.0"
js:
dependency: transitive
description:
@ -476,6 +460,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.2.1"
pasteboard:
dependency: "direct main"
description:
name: pasteboard
sha256: fedbe8da188d2f713aa8b01260737342e6e1087534a3ab26e1a719f8d3e8f32f
url: "https://pub.dev"
source: hosted
version: "0.5.0"
path:
dependency: transitive
description:
@ -540,14 +532,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.0"
pixel_snap:
dependency: transitive
description:
name: pixel_snap
sha256: "677410ea37b07cd37ecb6d5e6c0d8d7615a7cf3bd92ba406fd1ac57e937d1fb0"
url: "https://pub.dev"
source: hosted
version: "0.1.5"
platform:
dependency: transitive
description:
@ -689,22 +673,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.1"
super_clipboard:
dependency: "direct main"
description:
name: super_clipboard
sha256: e73f3bb7e66cc9260efa1dc507f979138e7e106c3521e2dda2d0311f6d728a16
url: "https://pub.dev"
source: hosted
version: "0.9.1"
super_native_extensions:
dependency: transitive
description:
name: super_native_extensions
sha256: b9611dcb68f1047d6f3ef11af25e4e68a21b1a705bbcc3eb8cb4e9f5c3148569
url: "https://pub.dev"
source: hosted
version: "0.9.1"
sync_http:
dependency: transitive
description:

View File

@ -2,9 +2,9 @@ name: xworkmate
description: "XWorkmate desktop-first AI workspace shell."
publish_to: 'none'
version: 1.1.5+1
build-date: 2026-06-28
build-id: 4e02107
version: 1.1.5+2
build-date: 2026-06-30
build-id: a876e3b0
environment:
sdk: ^3.11.0
@ -27,7 +27,7 @@ dependencies:
package_info_plus: ^8.3.1
path_provider: ^2.1.5
shared_preferences: ^2.5.3
super_clipboard: ^0.9.0
pasteboard: ^0.5.0
web_socket_channel: ^3.0.3
flutter_webrtc: ^0.12.3
yaml: ^3.1.3

View File

@ -1,8 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail
# Generate the dSYM that App Store validation expects for the vendored
# objective_c native-asset framework after Xcode/CocoaPods embed it.
# Generate dSYMs that App Store validation expects for embedded frameworks.
# Some prebuilt dependencies, including WebRTC, do not ship a dSYM even though
# their Mach-O binaries contain UUIDs that App Store Connect requires.
if [[ "${CONFIGURATION:-}" != "Release" && "${CONFIGURATION:-}" != "Profile" ]]; then
exit 0
fi
@ -22,6 +23,22 @@ fi
mkdir -p "${DWARF_DSYM_FOLDER_PATH}"
dsym_matches_binary() {
local binary_path="$1"
local dsym_path="$2"
local binary_uuids dsym_uuids uuid
[[ -d "${dsym_path}" ]] || return 1
binary_uuids="$(xcrun dwarfdump --uuid "${binary_path}" 2>/dev/null || true)"
dsym_uuids="$(xcrun dwarfdump --uuid "${dsym_path}" 2>/dev/null || true)"
[[ -n "${binary_uuids}" && -n "${dsym_uuids}" ]] || return 1
while read -r uuid; do
[[ -z "${uuid}" ]] || grep -Fq "${uuid}" <<<"${dsym_uuids}" || return 1
done < <(awk '/^UUID:/ { print $2 }' <<<"${binary_uuids}")
}
for framework_path in "${frameworks_dir}"/*.framework; do
[[ -d "${framework_path}" ]] || continue
@ -29,10 +46,8 @@ for framework_path in "${frameworks_dir}"/*.framework; do
binary_path="${framework_path}/${framework_name}"
[[ -f "${binary_path}" ]] || continue
[[ "${framework_name}" == "objective_c" ]] || continue
dsym_path="${DWARF_DSYM_FOLDER_PATH}/${framework_name}.framework.dSYM"
if [[ -d "${dsym_path}" ]]; then
if dsym_matches_binary "${binary_path}" "${dsym_path}"; then
continue
fi
@ -40,7 +55,8 @@ for framework_path in "${frameworks_dir}"/*.framework; do
continue
fi
echo "Generating missing dSYM for ${framework_name}.framework"
echo "Generating missing or mismatched dSYM for ${framework_name}.framework"
rm -rf "${dsym_path}"
if ! xcrun dsymutil "${binary_path}" -o "${dsym_path}" >/dev/null 2>&1; then
echo "warning: Failed to generate dSYM for ${framework_name}.framework" >&2
rm -rf "${dsym_path}" || true

View File

@ -40,7 +40,10 @@ app_build_commit="${GIT_BUILD_COMMIT:-${BUILD_ID_LINE:-unknown}}"
tmp_dir="$(mktemp -d "${RUNNER_TEMP:-/tmp}/xworkmate-macos-app-store.XXXXXX")"
cleanup() {
local status=$?
rm -rf "$tmp_dir"
apple_run_cleanup
return "$status"
}
trap cleanup EXIT
@ -80,12 +83,15 @@ xcodebuild archive \
-scheme Runner \
-configuration Release \
-archivePath "$archive_path" \
-allowProvisioningUpdates \
-allowProvisioningDeviceRegistration \
DEVELOPMENT_TEAM="N3G9T67W78"
xcodebuild -exportArchive \
-archivePath "$archive_path" \
-exportPath "$DIST_DIR" \
-exportOptionsPlist "$export_options_path"
-exportOptionsPlist "$export_options_path" \
-allowProvisioningUpdates
if ! compgen -G "$DIST_DIR/*.pkg" >/dev/null; then
echo "No macOS TestFlight pkg was produced under $DIST_DIR" >&2

29
scripts/sync-version.sh Executable file
View File

@ -0,0 +1,29 @@
#!/bin/bash
set -e
if [ -n "$1" ]; then
TARGET_VERSION="$1"
else
# Extract current version from pubspec.yaml
CURRENT_VERSION=$(grep "^version: " pubspec.yaml | awk '{print $2}')
if [[ "$CURRENT_VERSION" == *"+"* ]]; then
BASE_VERSION=$(echo "$CURRENT_VERSION" | cut -d'+' -f1)
BUILD_NUM=$(echo "$CURRENT_VERSION" | cut -d'+' -f2)
NEXT_BUILD_NUM=$((BUILD_NUM + 1))
TARGET_VERSION="${BASE_VERSION}+${NEXT_BUILD_NUM}"
else
TARGET_VERSION="${CURRENT_VERSION}+1"
fi
fi
DATE=$(date +%Y-%m-%d)
COMMIT=$(git rev-parse --short HEAD)
# Update version in pubspec.yaml
sed -i.bak -e "s/^version: .*/version: ${TARGET_VERSION}/" \
-e "s/^build-date: .*/build-date: ${DATE}/" \
-e "s/^build-id: .*/build-id: ${COMMIT}/" pubspec.yaml
rm -f pubspec.yaml.bak
echo "Updated pubspec.yaml to version=${TARGET_VERSION}, build-date=${DATE}, build-id=${COMMIT}"

41
test/mock_plugins.dart Normal file
View File

@ -0,0 +1,41 @@
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
void mockPlugins() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(
const MethodChannel('dev.fluttercommunity.plus/package_info'),
(MethodCall methodCall) async {
return {
'appName': 'XWorkmate',
'packageName': 'com.xevor.xworkmate',
'version': '1.1.5',
'buildNumber': '1',
};
},
);
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(
const MethodChannel('dev.fluttercommunity.plus/device_info'),
(MethodCall methodCall) async {
return {
'computerName': 'Test-Mac',
'hostName': 'Test-Mac',
'arch': 'arm64',
'model': 'MacBookPro18,1',
'kernelVersion': 'Darwin 21.4.0',
'osRelease': '21.4.0',
'activeCPUs': 10,
'memorySize': 34359738368,
'cpuFrequency': 3200000000,
};
},
);
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(
const MethodChannel('plugins.flutter.io/path_provider'),
(MethodCall methodCall) async {
return '/tmp';
},
);
}

View File

@ -1230,7 +1230,7 @@ void main() {
);
for (
var attempt = 0;
attempt < 300 &&
attempt < 1000 &&
controller
.requireTaskThreadForSessionInternal('unit-fixture-task-a')
.lastArtifactSyncStatus !=

View File

@ -1,3 +1,4 @@
import "../mock_plugins.dart";
import 'dart:convert';
import 'dart:io';
@ -11,6 +12,7 @@ import 'package:xworkmate/runtime/secure_config_store.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
mockPlugins();
HttpOverrides.global = null;
test(

View File

@ -1,3 +1,4 @@
import "../mock_plugins.dart";
import 'dart:convert';
import 'dart:io';
@ -9,6 +10,8 @@ import 'package:xworkmate/runtime/runtime_models.dart';
import 'package:xworkmate/runtime/secure_config_store.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
mockPlugins();
group('SettingsController account sync', () {
test(
'prefers managed bridge token over stale profile token for remote gateway auth',

View File

@ -8,16 +8,13 @@
#include <file_selector_windows/file_selector_windows.h>
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
#include <irondash_engine_context/irondash_engine_context_plugin_c_api.h>
#include <super_native_extensions/super_native_extensions_plugin_c_api.h>
#include <pasteboard/pasteboard_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows"));
FlutterWebRTCPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterWebRTCPlugin"));
IrondashEngineContextPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("IrondashEngineContextPluginCApi"));
SuperNativeExtensionsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SuperNativeExtensionsPluginCApi"));
PasteboardPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PasteboardPlugin"));
}

View File

@ -5,8 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
file_selector_windows
flutter_webrtc
irondash_engine_context
super_native_extensions
pasteboard
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST