From 721cec7a326f21d572462da65ff06fddf31f5b01 Mon Sep 17 00:00:00 2001 From: Milky2018 <842376130@qq.com> Date: Thu, 13 Aug 2026 11:37:26 +0800 Subject: [PATCH 1/2] refactor(build): eliminate dedicated output directories --- .gitignore | 1 - AGENTS.md | 4 +- README.md | 4 +- cli/build_cmd/build_wbtest.mbt | 54 ++---- cli/cef/cef_platform_wbtest.mbt | 11 +- cli/dev/dev_wbtest.mbt | 38 ++-- cli/doctor/doctor_wbtest.mbt | 20 +- cli/fsutil/fsutil_wbtest.mbt | 8 +- cli/new/new_project_wbtest.mbt | 21 +- cli/new/templates.generated.mbt | 6 +- cli/new/templates.mbt | 6 +- cli/new/templates/todo/files/.gitignore.mtpl | 2 +- cli/new/templates/todo/files/README.md.mtpl | 4 +- cli/package/package.mbt | 64 +++---- cli/package/package_macos_dmg_wbtest.mbt | 4 +- cli/package/package_wbtest.mbt | 76 +++----- cli/package/package_windows_wbtest.mbt | 37 ++-- config/config_test.mbt | 102 ++++------ config/project_config.mbt | 2 +- config/project_decode.mbt | 2 +- .../backend/moon.mod | 4 + .../project-config-backend-metadata/moon.mod | 2 + e2e/test/app_commands_probe_wbtest.mbt | 8 +- e2e/test/dev_extension_process_wbtest.mbt | 119 +++++++----- e2e/test/dev_extension_scenario_wbtest.mbt | 8 +- e2e/test/orchestration_wbtest.mbt | 57 +++--- e2e/test/process_lifecycle_wbtest.mbt | 26 ++- e2e/test/scenario_validation_wbtest.mbt | 11 +- examples/44_project_config/main.mbt | 6 +- .../44_project_config/proton.project.json | 2 +- .../46_asset_sidecar_resources/commands.mbt | 6 - examples/46_asset_sidecar_resources/moon.pkg | 1 - .../47_dev_extension_js/proton.project.json | 6 +- .../app_commands_fixture/app_commands.mbt | 6 - examples/app_commands_fixture/moon.pkg | 1 - examples/e2e_fixtures/asset_sidecar.mbt | 9 - examples/e2e_fixtures/web_contents_view.mbt | 15 +- examples/moon.mod | 4 +- native/scripts/verify_link_config.mjs | 17 -- proton/bootstrap/config_paths_wbtest.mbt | 2 +- proton/bootstrap/config_test.mbt | 181 +++++++----------- proton/bootstrap/moon.pkg | 5 + proton/bootstrap/proton_project_config.mbt | 2 +- proton/catalog/catalog_test.mbt | 116 +---------- .../removed-field.proton.ext.json | 5 + .../self-dependency.proton.ext.json | 7 + .../unsupported-platform.proton.ext.json | 8 + .../proton-ext-module/fs/proton.ext.json | 8 + .../testdata/proton-ext-module/moon.mod | 7 + proton/facade_wbtest.mbt | 49 ++--- proton/moon.pkg | 1 + proton/native_link_config.mjs | 14 +- scripts/e2e_scaffold_source_smoke.mjs | 3 +- scripts/macos_package_smoke.mjs | 14 +- scripts/windows_package_smoke.ps1 | 2 +- 55 files changed, 511 insertions(+), 687 deletions(-) create mode 100644 config/testdata/project-config-backend-metadata/backend/moon.mod create mode 100644 config/testdata/project-config-backend-metadata/moon.mod create mode 100644 proton/catalog/testdata/proton-ext-invalid/removed-field.proton.ext.json create mode 100644 proton/catalog/testdata/proton-ext-invalid/self-dependency.proton.ext.json create mode 100644 proton/catalog/testdata/proton-ext-invalid/unsupported-platform.proton.ext.json create mode 100644 proton/catalog/testdata/proton-ext-module/fs/proton.ext.json create mode 100644 proton/catalog/testdata/proton-ext-module/moon.mod diff --git a/.gitignore b/.gitignore index fe076fc6..53e2f731 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ # MoonBit build artifacts _build/ -target/ .mooncakes/ .repos/ dist/ diff --git a/AGENTS.md b/AGENTS.md index 9ec7da40..6f6a7e30 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -65,8 +65,8 @@ developer must perform them. aligned with the actual examples. - `proton/prebuilt//`: shipped Proton-only native artifacts. Do not put CEF runtime files here. -- `lib/`, `build/`, `_build/`, `target/`, `native/build*`, `native/dist/`: - generated or vendored artifacts. +- `lib/`, `build/`, `_build/`, `native/build*`, `native/dist/`: generated or + vendored artifacts. Packaged application artifacts are written to `dist/`. - `.proton/`: generated project runtime cache created by `proton_cli cef setup`. ## Build And Test diff --git a/README.md b/README.md index b32b3dbe..a7af14dd 100644 --- a/README.md +++ b/README.md @@ -414,7 +414,7 @@ default targets and output directory: "role": "Editor" } ], - "output": "target/proton-dist" + "output": "dist" } } ``` @@ -439,7 +439,7 @@ proton_cli package --release The package command performs a debug build by default. Pass `--release` to use MoonBit's release build mode, or `--no-build` to reuse an existing build from -the selected mode. Package output is written to `target/proton-dist` by default. +the selected mode. Package output is written to `dist` by default. Icons, resources, output targets, signing, notarization, custom URL schemes, and macOS document types are configured through `proton.project.json` and package command options. diff --git a/cli/build_cmd/build_wbtest.mbt b/cli/build_cmd/build_wbtest.mbt index bf4b9742..1bb4dbdf 100644 --- a/cli/build_cmd/build_wbtest.mbt +++ b/cli/build_cmd/build_wbtest.mbt @@ -25,9 +25,6 @@ async fn[T] expect_build_config_error( ///| fn ensure_build_test_dir(path : String) -> Unit { - @mbfs.create_dir("target") catch { - _ => () - } @mbfs.create_dir(path) catch { _ => () } @@ -101,9 +98,9 @@ test "parse build args package and config" { ///| test "parse build args accepts an isolated Moon target directory" { let raw = parse_build_args([ - "--package", "app", "--moon-target-dir", "target/build-app", + "--package", "app", "--moon-target-dir", "custom-build", ]) - @debug.assert_eq(raw.moon_target_dir, Some("target/build-app")) + @debug.assert_eq(raw.moon_target_dir, Some("custom-build")) } ///| @@ -146,10 +143,10 @@ test "moon build args prepend an isolated target directory" { moon_build_args_with_target_dir( "app", ["--diagnostic-limit", "80"], - Some("target/build-app"), + Some("custom-build"), ), [ - "--target-dir", "target/build-app", "build", "app", "--diagnostic-limit", "80", + "--target-dir", "custom-build", "build", "app", "--diagnostic-limit", "80", "--target", "native", ], ) @@ -157,8 +154,7 @@ test "moon build args prepend an isolated target directory" { ///| async test "frontend path validation reports missing directory" { - let root = "target/proton-build-cwd" - ensure_build_test_dir(root) + let root = @async_fs.tmpdir(prefix="proton-build-cwd-") let error = try resolve_frontend_path(root, Some("missing"), "npm run build") catch { @@ -172,8 +168,7 @@ async test "frontend path validation reports missing directory" { ///| async test "backend path supports nested directories with spaces" { - let root = "target/proton build backend" - ensure_build_test_dir(root) + let root = @async_fs.tmpdir(prefix="proton-build-backend-") ensure_build_test_dir(@fsutil.path_join(root, "nested")) ensure_build_test_dir(@fsutil.path_join(root, "nested/backend app")) let path = resolve_backend_path(root, Some("nested/backend app")) @@ -182,8 +177,7 @@ async test "backend path supports nested directories with spaces" { ///| async test "build config discovery prefers package-local proton.project.json" { - let root = "target/proton-build-package-config" - ensure_build_test_dir(root) + let root = @async_fs.tmpdir(prefix="proton-build-config-") ensure_build_test_dir(@fsutil.path_join(root, "desktop")) let package_config = @fsutil.path_join( @fsutil.path_join(root, "desktop"), @@ -201,9 +195,8 @@ async test "build config discovery prefers package-local proton.project.json" { ///| async test "resolve path accepts Windows backslash relative input" { if @path.sep == '\\' { - let dir = "target/proton-build-path" - ensure_build_test_dir(dir) - let resolved = @fsutil.resolve_path(".", "target\\proton-build-path") + let dir = @async_fs.tmpdir(prefix="proton-build-path-") + let resolved = @fsutil.resolve_path(".", dir.replace_all(old="/", new="\\")) assert_true(resolved.contains(":/") || resolved.contains(":\\")) assert_true(@fsutil.path_exists(resolved)) let absolute = resolved.replace_all(old="/", new="\\") @@ -213,8 +206,7 @@ async test "resolve path accepts Windows backslash relative input" { ///| async test "project config validation rejects unknown top-level fields" { - let dir = "target/proton-build-config" - ensure_build_test_dir(dir) + let dir = @async_fs.tmpdir(prefix="proton-build-config-") let path = @fsutil.path_join(dir, "unknown.proton.project.json") write_build_test_file(path, build_config_with("legacy_runtime", true)) let error = expect_build_config_error(() => read_project_config(path)) @@ -231,8 +223,7 @@ async test "project config validation rejects unknown top-level fields" { ///| async test "project config validation requires window" { - let dir = "target/proton-build-config" - ensure_build_test_dir(dir) + let dir = @async_fs.tmpdir(prefix="proton-build-config-") let path = @fsutil.path_join(dir, "missing-window.proton.project.json") write_build_test_file( path, @@ -259,8 +250,7 @@ async test "project config validation requires window" { ///| async test "project config validation rejects unsupported size_hint" { - let dir = "target/proton-build-config" - ensure_build_test_dir(dir) + let dir = @async_fs.tmpdir(prefix="proton-build-config-") let path = @fsutil.path_join(dir, "bad-size-hint.proton.project.json") write_build_test_file( path, @@ -294,8 +284,7 @@ async test "project config validation rejects unsupported size_hint" { ///| async test "project config parsing keeps active bundle metadata out of build validation" { - let dir = "target/proton-build-config" - ensure_build_test_dir(dir) + let dir = @async_fs.tmpdir(prefix="proton-build-config-") let path = @fsutil.path_join(dir, "active-bundle.proton.project.json") write_build_test_file(path, build_config_with("bundle", { "active": true })) ignore(read_project_config(path)) @@ -303,9 +292,10 @@ async test "project config parsing keeps active bundle metadata out of build val ///| async test "production entry validation requires file-backed entry" { + let dir = @async_fs.tmpdir(prefix="proton-build-entry-") let error = try validate_production_entry_file( - @proton_config.ProjectEntry::Asset("target/proton-build-missing.html"), + @proton_config.ProjectEntry::Asset(@fsutil.path_join(dir, "missing.html")), ) catch { BuildPathError::Missing(..) as error => error.message() @@ -314,8 +304,6 @@ async test "production entry validation requires file-backed entry" { _ => fail("expected build error") } assert_true(error.contains("production entry does not exist")) - let dir = "target/proton-build-entry" - ensure_build_test_dir(dir) let file = @fsutil.path_join(dir, "index.html") @mbfs.write_string_to_file(file, "", encoding="utf8") catch { err => abort(@debug.render(Repr(err))) @@ -325,8 +313,7 @@ async test "production entry validation requires file-backed entry" { ///| async test "release validation checks secondary window entry files" { - let dir = "target/proton-build-secondary-entry" - ensure_build_test_dir(dir) + let dir = @async_fs.tmpdir(prefix="proton-build-secondary-") let main = @fsutil.path_join(dir, "main.html") let missing = @fsutil.path_join(dir, "secondary/missing.html") write_build_test_file(main, "") @@ -354,7 +341,7 @@ async test "release validation checks secondary window entry files" { test "frontend release rejects url production entry" { let frontend = Some( @proton_config.ProjectFrontendConfig::new( - dist=Some("target/proton-build-entry"), + dist=Some("fixtures/frontend-dist"), ), ) let error = expect_build_error_message(() => { @@ -374,7 +361,7 @@ test "frontend release config preflight rejects url entry" { backend: None, frontend: Some( @proton_config.ProjectFrontendConfig::new( - dist=Some("target/proton-build-entry"), + dist=Some("fixtures/frontend-dist"), ), ), } @@ -387,7 +374,7 @@ test "frontend release requires dist" { let config = BuildProjectConfig::{ base_dir: ".", entries: [ - @proton_config.ProjectEntry::Asset("target/proton-build-entry/index.html"), + @proton_config.ProjectEntry::Asset("fixtures/frontend-dist/index.html"), ], backend: None, frontend: Some( @@ -402,8 +389,7 @@ test "frontend release requires dist" { ///| async test "production entry validation rejects directories" { - let dir = "target/proton-build-entry-dir" - ensure_build_test_dir(dir) + let dir = @async_fs.tmpdir(prefix="proton-build-entry-dir-") let error = try validate_production_entry_file(@proton_config.ProjectEntry::Asset(dir)) catch { diff --git a/cli/cef/cef_platform_wbtest.mbt b/cli/cef/cef_platform_wbtest.mbt index 7cde5bf7..4f31db73 100644 --- a/cli/cef/cef_platform_wbtest.mbt +++ b/cli/cef/cef_platform_wbtest.mbt @@ -75,7 +75,7 @@ test "runtime manifests separate portable and active metadata" { ///| test "CEF cache paths are absolute and scoped by platform and archive" { - let project_root = @fsutil.resolve_path(".", "target/proton-cache-project") + let project_root = @fsutil.resolve_path(".", "fixtures/proton-cache-project") let home = @fsutil.resolve_path(project_root, "../proton-cache-home") let cache_root = resolve_global_cef_cache_root( project_root~, @@ -97,7 +97,7 @@ test "CEF cache paths are absolute and scoped by platform and archive" { ///| test "relative CEF cache overrides resolve from the project root" { - let project_root = @fsutil.resolve_path(".", "target/proton-cache-project") + let project_root = @fsutil.resolve_path(".", "fixtures/proton-cache-project") let actual = resolve_global_cef_cache_root( project_root~, configured_root=Some("../shared-cef"), @@ -116,10 +116,9 @@ test "relative CEF cache overrides resolve from the project root" { ///| async test "runtime metadata does not persist the active CEF cache path" { - let root = @fsutil.resolve_path(".", "target/proton-runtime-manifest") + let root = @async_fs.tmpdir(prefix="proton-runtime-manifest-") let proton_dir = @fsutil.path_join(root, ".proton") let runtime_dir = @fsutil.path_join(proton_dir, "runtime") - remove_tree(root) ensure_dir(runtime_dir) let cef_root = @fsutil.resolve_path( root, "../shared-cef/darwin-arm64/cef-test", @@ -306,8 +305,8 @@ test "generated runtime directories must stay under the project root" { ///| test "CEF installation never removes a project boundary or filesystem root" { - let root = @fsutil.resolve_path(".", "target/proton-cef-project") - let cache = @fsutil.resolve_path(".", "target/proton-cef-cache/runtime") + let root = @fsutil.resolve_path(".", "fixtures/proton-cef-project") + let cache = @fsutil.resolve_path(".", "fixtures/proton-cef-cache/runtime") assert_safe_install_dir(cache, root) let parent = @fsutil.path_parent(root) let mut filesystem_root = root diff --git a/cli/dev/dev_wbtest.mbt b/cli/dev/dev_wbtest.mbt index 451834e6..6c807d22 100644 --- a/cli/dev/dev_wbtest.mbt +++ b/cli/dev/dev_wbtest.mbt @@ -5,9 +5,6 @@ fn parse_dev_args(args : Array[String]) -> RawDevOptions raise { ///| fn ensure_dev_test_dir(path : String) -> Unit { - @mbfs.create_dir("target") catch { - _ => () - } @mbfs.create_dir(path) catch { _ => () } @@ -79,15 +76,14 @@ test "parse dev args package and frontend overrides" { ///| test "parse dev args accepts an isolated Moon target directory" { let raw = parse_dev_args([ - "--package", "desktop", "--moon-target-dir", "target/dev-app", + "--package", "desktop", "--moon-target-dir", "custom-dev-build", ]) - @debug.assert_eq(raw.moon_target_dir, Some("target/dev-app")) + @debug.assert_eq(raw.moon_target_dir, Some("custom-dev-build")) } ///| async test "read dev project config extracts module paths" { - let dir = "target/proton-dev-config" - ensure_dev_test_dir(dir) + let dir = @async_fs.tmpdir(prefix="proton-dev-config-") let path = @fsutil.path_join(dir, "proton.project.json") @mbfs.write_string_to_file( path, @@ -129,8 +125,7 @@ async test "read dev project config extracts module paths" { ///| async test "read dev frontend config validates full project config" { - let dir = "target/proton-dev-config" - ensure_dev_test_dir(dir) + let dir = @async_fs.tmpdir(prefix="proton-dev-config-") let path = @fsutil.path_join(dir, "bad-size-hint.proton.project.json") @mbfs.write_string_to_file( path, @@ -171,8 +166,7 @@ async test "read dev frontend config validates full project config" { ///| async test "frontend path is validated only when frontend starts" { - let root = "target/proton-dev-cwd" - ensure_dev_test_dir(root) + let root = @async_fs.tmpdir(prefix="proton-dev-cwd-") ensure_dev_test_dir(@fsutil.path_join(root, "frontend")) let resolved = resolve_frontend_path( root, @@ -206,8 +200,7 @@ async test "frontend path is validated only when frontend starts" { ///| async test "dev config discovery prefers package-local proton.project.json" { - let root = "target/proton-dev-package-config" - ensure_dev_test_dir(root) + let root = @async_fs.tmpdir(prefix="proton-dev-config-") ensure_dev_test_dir(@fsutil.path_join(root, "desktop")) let package_config = @fsutil.path_join( @fsutil.path_join(root, "desktop"), @@ -226,9 +219,8 @@ async test "dev config discovery prefers package-local proton.project.json" { ///| async test "backend path supports nested directories with spaces" { - let root = "target/proton dev backend" + let root = @async_fs.tmpdir(prefix="proton-dev-backend-") let backend = @fsutil.path_join(root, "modules/backend app") - ensure_dev_test_dir(root) ensure_dev_test_dir(@fsutil.path_join(root, "modules")) ensure_dev_test_dir(backend) let resolved = resolve_backend_path(root, Some("modules/backend app")) @@ -237,7 +229,7 @@ async test "backend path supports nested directories with spaces" { ///| async test "dev runtime layout validates helper" { - let root = "target/proton-dev-runtime-layout" + let root = @async_fs.tmpdir(prefix="proton-dev-runtime-") let runtime = create_fake_dev_runtime(root) let loaded = dev_runtime_from_root( @fsutil.resolve_path(".", runtime), @@ -277,7 +269,7 @@ async test "dev runtime layout validates helper" { ///| async test "dev process preloads an indirect Linux CEF runtime" { - let root = "target/proton-dev-runtime-preload" + let root = @async_fs.tmpdir(prefix="proton-dev-preload-") let runtime_path = create_fake_dev_runtime(root) let runtime = dev_runtime_from_root( @fsutil.resolve_path(".", runtime_path), @@ -377,7 +369,7 @@ test "dev app spawn env replaces inherited Windows variable spellings" { ///| async test "read dev runtime honors PROTON_NATIVE_DIST override" { - let root = "target/proton-dev-runtime-env" + let root = @async_fs.tmpdir(prefix="proton-dev-runtime-env-") let runtime = create_fake_dev_runtime(root) let previous = @env.get_env_var("PROTON_NATIVE_DIST") defer (match previous { @@ -396,7 +388,7 @@ async test "read dev runtime honors PROTON_NATIVE_DIST override" { @fsutil.path_join(loaded.bin_dir, helper_executable_name()), ), ) - let bad_root = "target/proton-dev-runtime-bad-env" + let bad_root = @fsutil.path_join(root, "bad") ensure_dev_test_dir(bad_root) @env.set_env_var("PROTON_NATIVE_DIST", "missing-runtime") try ensure_dev_runtime(@fsutil.resolve_path(".", bad_root), true) catch { @@ -413,9 +405,8 @@ async test "read dev runtime honors PROTON_NATIVE_DIST override" { ///| async test "resolve path accepts Windows backslash relative input" { if @path.sep == '\\' { - let dir = "target/proton-dev-path" - ensure_dev_test_dir(dir) - let resolved = @fsutil.resolve_path(".", "target\\proton-dev-path") + let dir = @async_fs.tmpdir(prefix="proton-dev-path-") + let resolved = @fsutil.resolve_path(".", dir.replace_all(old="/", new="\\")) assert_true(resolved.contains(":/") || resolved.contains(":\\")) assert_true(@fsutil.path_exists(resolved)) let absolute = resolved.replace_all(old="/", new="\\") @@ -510,8 +501,7 @@ async test "ready path validates curl probe availability" { ///| #cfg(not(platform="windows")) async test "frontend cancellation terminates child processes" { - let dir = "target/proton-dev-cancel-tree" - ensure_dev_test_dir(dir) + let dir = @async_fs.tmpdir(prefix="proton-dev-cancel-") let pid_path = @fsutil.path_join(dir, "child.pid") @mbfs.remove_file(pid_path) catch { _ => () diff --git a/cli/doctor/doctor_wbtest.mbt b/cli/doctor/doctor_wbtest.mbt index 72ba2cf1..315eaf6a 100644 --- a/cli/doctor/doctor_wbtest.mbt +++ b/cli/doctor/doctor_wbtest.mbt @@ -1,17 +1,6 @@ -///| -fn ensure_test_dir(path : String) -> Unit { - @mbfs.create_dir("target") catch { - _ => () - } - @mbfs.create_dir(path) catch { - _ => () - } -} - ///| fn write_test_project(path : String) -> String { let root = @path.Path(path).resolve().to_string() - ensure_test_dir(root) @mbfs.write_string_to_file( @fsutil.path_join(root, "proton.project.json"), ( @@ -37,7 +26,8 @@ fn write_test_project(path : String) -> String { ///| async test "doctor discovers proton.project.json from a nested directory" { - let root = write_test_project("target/proton-doctor-project") + let root = @async_fs.tmpdir(prefix="proton-doctor-project-") + ignore(write_test_project(root)) let nested = @fsutil.path_join(root, "nested") @mbfs.create_dir(nested) catch { _ => () @@ -56,8 +46,7 @@ async test "doctor discovers proton.project.json from a nested directory" { ///| async test "doctor skips project checks outside a Proton project" { - let root = @path.Path("target/proton-doctor-empty").resolve().to_string() - ensure_test_dir(root) + let root = @async_fs.tmpdir(prefix="proton-doctor-empty-") let report = build_doctor_report(root, verbose=false) assert_eq(report.sections.length(), 2) assert_false(report.has_errors()) @@ -68,7 +57,8 @@ async test "doctor skips project checks outside a Proton project" { ///| async test "doctor accepts the generated project configuration shape" { - let root = write_test_project("target/proton-doctor-valid-config") + let root = @async_fs.tmpdir(prefix="proton-doctor-valid-") + ignore(write_test_project(root)) let section = collect_project(discover_project(root)) assert_true( section.lines.any(fn(line) { diff --git a/cli/fsutil/fsutil_wbtest.mbt b/cli/fsutil/fsutil_wbtest.mbt index 940189cf..22871105 100644 --- a/cli/fsutil/fsutil_wbtest.mbt +++ b/cli/fsutil/fsutil_wbtest.mbt @@ -1,8 +1,5 @@ ///| fn ensure_fsutil_test_dir(path : String) -> Unit { - @mbfs.create_dir("target") catch { - _ => () - } @mbfs.create_dir(path) catch { _ => () } @@ -84,7 +81,7 @@ test "path_parent follows Windows dirname semantics" { ///| test "relative_path and containment use path components" { - let root = resolve_path(".", "target/proton-fsutil-containment") + let root = resolve_path("/fixture", "proton-fsutil-containment") assert_eq( relative_path(root, path_join(root, "nested/file")), path_join("nested", "file"), @@ -105,8 +102,7 @@ test "path_join joins with the platform separator" { ///| async test "find_project_root_from walks up to marker" { - let root = path_join("target", "proton-fsutil-project-root") - ensure_fsutil_test_dir(root) + let root = @async_fs.tmpdir(prefix="proton-fsutil-project-root-") ensure_fsutil_test_dir(path_join(root, "nested")) ensure_fsutil_test_dir(path_join(root, "nested/child")) write_fsutil_test_file(path_join(root, "moon.work"), "") diff --git a/cli/new/new_project_wbtest.mbt b/cli/new/new_project_wbtest.mbt index 165dfab8..326eea7f 100644 --- a/cli/new/new_project_wbtest.mbt +++ b/cli/new/new_project_wbtest.mbt @@ -329,7 +329,7 @@ test "templates receive final author title and size" { "bundle": { "active": true, "targets": ["app", "zip"], - "output": "target/proton-dist", + "output": "dist", .. }, .. @@ -445,8 +445,9 @@ test "registry dependency failures are classified separately" { ///| async test "project generation commits a complete staged tree" { + let workspace = @async_fs.tmpdir(prefix="proton-new-success-") let options = transaction_test_options( - "target/proton-new-transaction-success", + @fsutil.path_join(workspace, "project"), ) remove_new_test_tree(options.resolved_target_dir) let files = must_plan(options) @@ -471,7 +472,10 @@ async test "project generation commits a complete staged tree" { ///| async test "failed project writes leave no target or staging tree" { - let options = transaction_test_options("target/proton-new-transaction-failed") + let workspace = @async_fs.tmpdir(prefix="proton-new-failed-") + let options = transaction_test_options( + @fsutil.path_join(workspace, "project"), + ) remove_new_test_tree(options.resolved_target_dir) let files = [ PlannedFile::{ path: "blocked", content: "file" }, @@ -493,8 +497,9 @@ async test "failed project writes leave no target or staging tree" { ///| async test "failed project writes restore an existing empty target" { + let workspace = @async_fs.tmpdir(prefix="proton-new-empty-") let options = transaction_test_options( - "target/proton-new-transaction-empty-target", + @fsutil.path_join(workspace, "project"), ) remove_new_test_tree(options.resolved_target_dir) ensure_dir_recursive(options.resolved_target_dir) @@ -515,8 +520,9 @@ async test "failed project writes restore an existing empty target" { ///| async test "cancelled project generation removes its staging tree" { + let workspace = @async_fs.tmpdir(prefix="proton-new-cancelled-") let options = transaction_test_options( - "target/proton-new-transaction-cancelled", + @fsutil.path_join(workspace, "project"), ) remove_new_test_tree(options.resolved_target_dir) let outcome = @async.with_timeout_opt(0, () => { @@ -533,8 +539,11 @@ async test "cancelled project generation removes its staging tree" { ///| async test "moon check failure is rolled back before publication" { + let workspace = @async_fs.tmpdir(prefix="proton-new-check-failed-") let raw = must_parse([ - "target/proton-new-transaction-check-failed", "--check", "--no-git", + @fsutil.path_join(workspace, "project"), + "--check", + "--no-git", ]) let options = options_from_raw(".", raw) remove_new_test_tree(options.resolved_target_dir) diff --git a/cli/new/templates.generated.mbt b/cli/new/templates.generated.mbt index 753f9f06..b974015e 100644 --- a/cli/new/templates.generated.mbt +++ b/cli/new/templates.generated.mbt @@ -798,7 +798,7 @@ fn generated_template_specs() -> Array[TemplateSpec] { #| "bundle": { #| "active": true, #| "targets": ["app", "zip"], - #| "output": "target/proton-dist" + #| "output": "dist" #| } #|} #|``` @@ -819,7 +819,7 @@ fn generated_template_specs() -> Array[TemplateSpec] { #| prebuild rule. Ordinary `moon check`, `build`, and `run` commands keep it #| current through `moonx moonbit-community/proton_codegen`. #|- `backend/app/main.mbt` assembles and runs the application. - #|- `target/proton-dist/` contains packaged application artifacts. + #|- `dist/` contains packaged application artifacts. #| ), }, @@ -829,7 +829,7 @@ fn generated_template_specs() -> Array[TemplateSpec] { template_source: ( #|.DS_Store #|_build/ - #|target + #|dist/ #|.mooncakes/ #|.moonagent/ #|.proton/ diff --git a/cli/new/templates.mbt b/cli/new/templates.mbt index b572cb39..14a7c1f1 100644 --- a/cli/new/templates.mbt +++ b/cli/new/templates.mbt @@ -231,11 +231,7 @@ fn project_config_file(context : TemplateContext) -> PlannedFile { "entry": { "kind": "asset", "value": "frontend/dist/index.html" }, "permissions": [{ "window": "main", "origin": "entry", "extension": "app" }], "debug": true, - "bundle": { - "active": true, - "targets": ["app", "zip"], - "output": "target/proton-dist", - }, + "bundle": { "active": true, "targets": ["app", "zip"], "output": "dist" }, } { path: "proton.project.json", content: config.stringify(indent=2) + "\n" } } diff --git a/cli/new/templates/todo/files/.gitignore.mtpl b/cli/new/templates/todo/files/.gitignore.mtpl index a5384d7f..902ed5f0 100644 --- a/cli/new/templates/todo/files/.gitignore.mtpl +++ b/cli/new/templates/todo/files/.gitignore.mtpl @@ -1,6 +1,6 @@ .DS_Store _build/ -target +dist/ .mooncakes/ .moonagent/ .proton/ diff --git a/cli/new/templates/todo/files/README.md.mtpl b/cli/new/templates/todo/files/README.md.mtpl index f0ec246c..978f8a21 100644 --- a/cli/new/templates/todo/files/README.md.mtpl +++ b/cli/new/templates/todo/files/README.md.mtpl @@ -51,7 +51,7 @@ The `bundle` block in `proton.project.json` controls package creation: "bundle": { "active": true, "targets": ["app", "zip"], - "output": "target/proton-dist" + "output": "dist" } } ``` @@ -72,4 +72,4 @@ proton_cli package --release prebuild rule. Ordinary `moon check`, `build`, and `run` commands keep it current through `moonx moonbit-community/proton_codegen`. - `backend/app/main.mbt` assembles and runs the application. -- `target/proton-dist/` contains packaged application artifacts. +- `dist/` contains packaged application artifacts. diff --git a/cli/package/package.mbt b/cli/package/package.mbt index 356b8fe3..42b93288 100644 --- a/cli/package/package.mbt +++ b/cli/package/package.mbt @@ -21,7 +21,6 @@ struct RawPackageOptions { struct PackagePlan { root : String workspace_root : String - build_target_dir : String app_package : String config_path : String product_name : String @@ -382,7 +381,6 @@ async fn build_package_plan( PackagePlan::{ root: backend_root, workspace_root, - build_target_dir: package_build_target_dir(workspace_root, identifier), app_package, config_path, product_name, @@ -581,7 +579,6 @@ fn print_package_plan(plan : PackagePlan, dry_run : Bool) -> Unit { println(" update revision: " + plan.update_revision.to_string()) println(" targets: " + plan.targets.join(", ")) println(" output: " + plan.output) - println(" build target: " + plan.build_target_dir) let sign_binaries = if plan.sign_binaries.length() == 0 { "none" } else { @@ -616,7 +613,7 @@ async fn execute_package_plan(plan : PackagePlan) -> Unit { let executable = match find_package_executable(plan) { Some(path) => path None => - raise PackagePlanError::MissingExecutable(path=plan.build_target_dir) + raise PackagePlanError::MissingExecutable(path=package_build_root(plan)) } match current_package_host() { Windows => stage_windows_portable(plan, executable, runtime) @@ -638,29 +635,12 @@ async fn run_package_build(plan : PackagePlan) -> Unit { if plan.release { moon_args.push("--release") } - moon_args.push("--target-dir") - moon_args.push(plan.build_target_dir) - let old_rpath = @env.get_env_var("PROTON_PACKAGE_RPATH") - // Mach-O only. The link config reads this on darwin; the Linux branch - // ignores it and keeps the absolute runtime rpath, which is what a - // development run wants. Linux gets its relocatable rpath from patchelf - // during staging instead, so `$ORIGIN` never passes through the link - // config's quoting. - if is_macos_host() { - @env.set_env_var( - "PROTON_PACKAGE_RPATH", "@executable_path/../Resources/proton/lib", - ) - } - defer restore_package_env("PROTON_PACKAGE_RPATH", old_rpath) @build.run_release(plan.root, plan.app_package, plan.config_path, moon_args) } ///| -fn restore_package_env(name : String, value : String?) -> Unit { - match value { - Some(value) => @env.set_env_var(name, value) - None => @env.unset_env_var(name) - } +fn package_build_root(plan : PackagePlan) -> String { + @fsutil.resolve_path(plan.workspace_root, "_build") } ///| @@ -668,7 +648,7 @@ async fn find_package_executable(plan : PackagePlan) -> String? { // `--no-build` must reuse the same build mode selected for this command. let build_mode = if plan.release { "release" } else { "debug" } let root = @fsutil.resolve_path( - plan.build_target_dir, + package_build_root(plan), "native/" + build_mode + "/build", ) let package_name = path_basename(plan.app_package) @@ -756,17 +736,6 @@ async fn read_active_runtime(root : String) -> String { } } -///| -fn package_build_target_dir( - workspace_root : String, - identifier : String, -) -> String { - @fsutil.resolve_path( - workspace_root, - "target/proton-package-build/" + package_executable_name(identifier), - ) -} - ///| async fn require_runtime(path : String) -> String { if @fsutil.path_is_dir(path) { @@ -897,6 +866,11 @@ async fn stage_macos_app_impl( ) } require_launchable_macho(staged_executable) + replace_macos_rpath( + staged_executable, + @fsutil.resolve_path(runtime, "lib"), + "@executable_path/../Resources/proton/lib", + ) copy_tree(runtime, @fsutil.resolve_path(resources, "proton")) stage_release_project_config( plan.config_path, @@ -1619,6 +1593,26 @@ async fn add_macos_rpath(binary : String, rpath : String) -> Unit { } } +///| +async fn replace_macos_rpath( + binary : String, + current : String, + replacement : String, +) -> Unit { + let code = @process.run("install_name_tool", [ + "-rpath", current, replacement, binary, + ]) catch { + error => + raise PackageToolError::Start( + tool="install_name_tool", + detail=@debug.render(Repr(error)), + ) + } + if code != 0 { + raise PackageToolError::Exit(tool="install_name_tool", code~, detail=binary) + } +} + ///| fn required_macos_signing_identity() -> String raise MacosSigningError { match @env.get_env_var("PROTON_MACOS_SIGNING_IDENTITY") { diff --git a/cli/package/package_macos_dmg_wbtest.mbt b/cli/package/package_macos_dmg_wbtest.mbt index cd75ebeb..76e4d0ea 100644 --- a/cli/package/package_macos_dmg_wbtest.mbt +++ b/cli/package/package_macos_dmg_wbtest.mbt @@ -4,7 +4,6 @@ fn macos_dmg_test_plan(root : String) -> PackagePlan { PackagePlan::{ root, workspace_root: root, - build_target_dir: @fsutil.resolve_path(root, "build"), app_package: "app", config_path: @fsutil.resolve_path(root, "proton.project.json"), product_name: "Demo App", @@ -35,8 +34,7 @@ fn macos_dmg_test_plan(root : String) -> PackagePlan { #cfg(not(platform="windows")) async test "macOS dmg uses drag-to-install layout and a verified UDZO image" { guard is_macos_host() else { return } - let root = @fsutil.resolve_path("target", "proton-package-dmg-real") - remove_tree(root) + let root = @async_fs.tmpdir(prefix="proton-package-dmg-") let app = @fsutil.resolve_path(root, "Source.app") ensure_dir(@fsutil.resolve_path(app, "Contents")) write_text( diff --git a/cli/package/package_wbtest.mbt b/cli/package/package_wbtest.mbt index f8a8e8a5..146fc548 100644 --- a/cli/package/package_wbtest.mbt +++ b/cli/package/package_wbtest.mbt @@ -8,6 +8,14 @@ fn parse_package_matches(args : Array[String]) -> @argparse.Matches raise { command().parse(argv=args, env=Map([])) } +///| +fn restore_package_env(name : String, value : String?) -> Unit { + match value { + Some(value) => @env.set_env_var(name, value) + None => @env.unset_env_var(name) + } +} + ///| test "parse package args accepts repeated targets and overrides" { let raw = parse_package_args([ @@ -162,11 +170,6 @@ async test "package plan uses the host defaults" { assert_eq(plan.identifier, "dev.proton.demo") assert_eq(plan.version, "1.0.0") assert_false(plan.release) - assert_true( - normalize_slashes(plan.build_target_dir).has_suffix( - "/target/proton-package-build/dev-proton-demo", - ), - ) let release_plan = build_package_plan( parse_package_args(["--release"]), "proton.project.json", @@ -178,8 +181,7 @@ async test "package plan uses the host defaults" { ///| async test "package plan stages every window file entry" { - let root = @fsutil.resolve_path("target", "proton-package-window-entries") - remove_tree(root) + let root = @async_fs.tmpdir(prefix="proton-package-window-entries-") let main = @fsutil.resolve_path(root, "pages/main.html") let secondary = @fsutil.resolve_path(root, "standalone/settings.html") ensure_dir(@fsutil.path_parent(main)) @@ -226,8 +228,7 @@ async test "package plan stages every window file entry" { ///| async test "configured signing binaries resolve through staged resources" { - let root = @fsutil.resolve_path("target", "proton-package-sign-binaries") - remove_tree(root) + let root = @async_fs.tmpdir(prefix="proton-package-sign-binaries-") let binary = @fsutil.resolve_path(root, "bin/worker") ensure_dir(@fsutil.path_parent(binary)) write_text(binary, "binary") @@ -264,7 +265,7 @@ async test "configured signing binaries resolve through staged resources" { ///| async test "package plan uses configured backend module and package" { - let base_dir = @fsutil.resolve_path("target", "proton package module paths") + let base_dir = @async_fs.tmpdir(prefix="proton-package-module-paths-") let backend_path = @fsutil.resolve_path(base_dir, "modules/backend app") let project = @proton_config.ProjectConfig::new( base_dir, @@ -300,7 +301,7 @@ async test "package plan uses configured backend module and package" { ///| #cfg(not(platform="windows")) test "macOS package paths normalize absolute and relative inputs" { - let base = @fsutil.resolve_path("target", "proton package path base") + let base = @fsutil.resolve_path("fixtures", "proton package path base") assert_eq(@fsutil.resolve_path(base, "/"), "/") assert_eq( @fsutil.resolve_path(base, "/tmp/proton absolute path"), @@ -335,8 +336,7 @@ test "macOS package paths normalize absolute and relative inputs" { ///| #cfg(not(platform="windows")) async test "macOS ensure_dir recursively creates absolute paths with spaces" { - let root = @fsutil.resolve_path("target", "proton package ensure dir") - remove_tree(root) + let root = @async_fs.tmpdir(prefix="proton-package-ensure-dir-") let nested = @fsutil.resolve_path(root, "parent with spaces/child/grandchild") ensure_dir(nested) assert_true(@fsutil.path_is_dir(nested)) @@ -502,9 +502,8 @@ test "macOS CEF process signing preserves hardened runtime and identifier" { ///| async test "macOS signing rejects a missing required target" { - let missing = @fsutil.resolve_path( - "target", "proton-package-missing-signing-target", - ) + let root = @async_fs.tmpdir(prefix="proton-package-signing-target-") + let missing = @fsutil.resolve_path(root, "missing") remove_tree(missing) try require_macos_codesign_targets([ @@ -580,8 +579,7 @@ test "release project config preserves an already disabled or absent debug field ///| async test "macOS icon selection stages icns and rejects a missing source" { - let root = @fsutil.resolve_path("target", "proton-package-macos-icon") - remove_tree(root) + let root = @async_fs.tmpdir(prefix="proton-package-macos-icon-") let resources = @fsutil.resolve_path(root, "Resources") let icon = @fsutil.resolve_path(root, "Demo.ICNS") ensure_dir(resources) @@ -593,7 +591,6 @@ async test "macOS icon selection stages icns and rejects a missing source" { let plan = PackagePlan::{ root, workspace_root: root, - build_target_dir: @fsutil.resolve_path(root, "build"), app_package: "app", config_path: @fsutil.resolve_path(root, "proton.project.json"), product_name: "Demo", @@ -641,7 +638,6 @@ test "macOS plist contains bundle identity and executable" { let plan = PackagePlan::{ root: ".", workspace_root: ".", - build_target_dir: "target/proton-package-build/dev-proton-demo", app_package: "app", config_path: "proton.project.json", product_name: "Demo & App", @@ -651,7 +647,7 @@ test "macOS plist contains bundle identity and executable" { bundle_version: "1.2.3", update_revision: 42UL, targets: ["app"], - output: "target", + output: "dist", build: false, release: false, base_dir: ".", @@ -690,12 +686,10 @@ test "macOS plist contains bundle identity and executable" { ///| async test "package manifest records launch metadata" { - let root = "target/proton-package-launch-metadata" - ensure_dir(root) + let root = @async_fs.tmpdir(prefix="proton-package-launch-metadata-") let plan = PackagePlan::{ root: ".", workspace_root: ".", - build_target_dir: "target/proton-package-build/dev-proton-demo", app_package: "app", config_path: "proton.project.json", product_name: "Demo", @@ -705,7 +699,7 @@ async test "package manifest records launch metadata" { bundle_version: "1.2.3", update_revision: 0UL, targets: ["app"], - output: "target", + output: "dist", build: false, release: false, base_dir: ".", @@ -759,7 +753,6 @@ test "macOS helper plist matches its bundle identity" { let plan = PackagePlan::{ root: ".", workspace_root: ".", - build_target_dir: "target/proton-package-build/dev-proton-demo", app_package: "app", config_path: "proton.project.json", product_name: "Demo & App", @@ -769,7 +762,7 @@ test "macOS helper plist matches its bundle identity" { bundle_version: "42", update_revision: 42UL, targets: ["app"], - output: "target", + output: "dist", build: false, release: false, base_dir: ".", @@ -802,12 +795,10 @@ test "macOS helper plist matches its bundle identity" { ///| async test "macOS ZIP uses the final app name without metadata entries" { guard is_macos_host() else { return } - let root = @fsutil.resolve_path("target", "proton-package-macos-zip-layout") - remove_tree(root) + let root = @async_fs.tmpdir(prefix="proton-package-macos-zip-") let plan = PackagePlan::{ root, workspace_root: root, - build_target_dir: @fsutil.resolve_path(root, "build"), app_package: "app", config_path: @fsutil.resolve_path(root, "proton.project.json"), product_name: "Demo App", @@ -861,8 +852,7 @@ async test "macOS ZIP uses the final app name without metadata entries" { ///| async test "macOS staged app promotion rolls back when staging is missing" { - let root = "target/proton-package-macos-promotion" - remove_tree(root) + let root = @async_fs.tmpdir(prefix="proton-package-promotion-") let destination = @fsutil.resolve_path(root, "Demo.app") let marker = @fsutil.resolve_path(destination, "Contents/old.txt") ensure_dir(@fsutil.path_parent(marker)) @@ -885,8 +875,7 @@ async test "macOS staged app promotion rolls back when staging is missing" { ///| async test "macOS staged app promotion recovers an interrupted backup" { - let root = "target/proton-package-macos-stale-backup" - remove_tree(root) + let root = @async_fs.tmpdir(prefix="proton-package-stale-backup-") let destination = @fsutil.resolve_path(root, "Demo.app") let backup = destination + ".backup" let marker = @fsutil.resolve_path(backup, "Contents/old.txt") @@ -912,8 +901,7 @@ async test "macOS staged app promotion recovers an interrupted backup" { ///| async test "macOS artifact promotion rolls back the complete output set" { - let root = "target/proton-package-macos-transaction" - remove_tree(root) + let root = @async_fs.tmpdir(prefix="proton-package-transaction-") let app = @fsutil.resolve_path(root, "Demo.app") let app_backup = app + ".backup" let staged_app = @fsutil.resolve_path(root, ".demo.staging.app") @@ -955,8 +943,7 @@ async test "macOS artifact promotion rolls back the complete output set" { ///| async test "macOS notarization validates success and cleans failed upload" { guard is_macos_host() else { return } - let root = @fsutil.resolve_path("target", "proton-package-notarization-flow") - remove_tree(root) + let root = @async_fs.tmpdir(prefix="proton-package-notarization-") let tools = @fsutil.resolve_path(root, "tools") let app = @fsutil.resolve_path(root, "Demo.app") let log = @fsutil.resolve_path(root, "commands.log") @@ -979,7 +966,6 @@ async test "macOS notarization validates success and cleans failed upload" { let plan = PackagePlan::{ root, workspace_root: root, - build_target_dir: @fsutil.resolve_path(root, "build"), app_package: "app", config_path: @fsutil.resolve_path(root, "proton.project.json"), product_name: "Demo", @@ -1051,9 +1037,8 @@ async test "macOS notarization validates success and cleans failed upload" { ///| async test "package executable lookup ignores the dSYM mirror" { - let root = "target/proton-package-executable-lookup" - remove_tree(root) - let package_target = @fsutil.resolve_path(root, "package-target") + let root = @async_fs.tmpdir(prefix="proton-package-executable-") + let package_target = @fsutil.resolve_path(root, "workspace/_build") let unrelated = @fsutil.resolve_path( root, "workspace/native/release/build/other/app.exe", ) @@ -1062,7 +1047,6 @@ async test "package executable lookup ignores the dSYM mirror" { let plan = PackagePlan::{ root, workspace_root: @fsutil.resolve_path(root, "workspace"), - build_target_dir: package_target, app_package: "app", config_path: @fsutil.resolve_path(root, "proton.project.json"), product_name: "Demo", @@ -1107,8 +1091,7 @@ async test "package executable lookup ignores the dSYM mirror" { ///| async test "active runtime rejects another platform" { - let root = "target/proton-package-runtime-platform" - remove_tree(root) + let root = @async_fs.tmpdir(prefix="proton-package-runtime-") ensure_dir(@fsutil.resolve_path(root, ".proton")) let old_dist = @env.get_env_var("PROTON_NATIVE_DIST") @env.unset_env_var("PROTON_NATIVE_DIST") @@ -1136,7 +1119,7 @@ async test "active runtime rejects another platform" { ///| async test "Windows portable staging flattens runtime bin beside app executable" { - let root = "target/proton-package-windows-layout" + let root = @async_fs.tmpdir(prefix="proton-package-windows-") let output = @fsutil.resolve_path(root, "output") remove_tree(root) let runtime = prepare_windows_test_runtime(root) @@ -1149,7 +1132,6 @@ async test "Windows portable staging flattens runtime bin beside app executable" let plan = PackagePlan::{ root, workspace_root: root, - build_target_dir: @fsutil.resolve_path(root, "package-build"), app_package: "app", config_path: config, product_name: "Demo App", diff --git a/cli/package/package_windows_wbtest.mbt b/cli/package/package_windows_wbtest.mbt index 10bedb9e..b7821619 100644 --- a/cli/package/package_windows_wbtest.mbt +++ b/cli/package/package_windows_wbtest.mbt @@ -7,7 +7,6 @@ fn windows_test_plan( PackagePlan::{ root, workspace_root: root, - build_target_dir: @fsutil.resolve_path(root, "package-build"), app_package: "app", config_path: @fsutil.resolve_path(root, "proton.project.json"), product_name: "Demo App", @@ -154,7 +153,7 @@ test "Windows signtool args use SHA-256 and RFC3161 timestamp" { ///| test "Windows signing includes configured resource binaries" { - let root = "target/proton-package-windows-configured-signing" + let root = "fixtures/proton-package-windows-configured-signing" let plan = windows_test_plan(root, ["app"], true) plan.sign_binaries.push(@fsutil.resolve_path(root, "helpers/worker.exe")) let output = @fsutil.resolve_path(root, "portable") @@ -168,8 +167,7 @@ test "Windows signing includes configured resource binaries" { ///| #cfg(platform="windows") async test "Windows signtool signs and verifies configured resource binaries" { - let root = "target/proton-package-windows-configured-signtool" - remove_tree(root) + let root = @async_fs.tmpdir(prefix="proton-package-windows-signing-") let output = @fsutil.resolve_path(root, "portable") let plan = windows_test_plan(root, ["app"], true) let binary = @fsutil.resolve_path(root, "helpers/worker.exe") @@ -210,8 +208,7 @@ async test "Windows signtool signs and verifies configured resource binaries" { ///| async test "Windows icon selection accepts ico and rejects a missing source" { - let root = "target/proton-package-windows-icon" - remove_tree(root) + let root = @async_fs.tmpdir(prefix="proton-package-windows-icon-") let selected = @fsutil.resolve_path(root, "Demo.ICO") assert_eq( first_windows_icon(["icons/icon.png", selected, "icons/fallback.ico"]), @@ -245,14 +242,16 @@ test "Windows icon embedding requires the native CLI backend" { ///| test "Windows path resolution keeps dotted relative children under the base" { let path = normalize_slashes( - @fsutil.resolve_path("target/proton-path-base", ".proton/runtime.json"), + @fsutil.resolve_path("fixtures/proton-path-base", ".proton/runtime.json"), + ) + assert_true( + path.has_suffix("/fixtures/proton-path-base/.proton/runtime.json"), ) - assert_true(path.has_suffix("/target/proton-path-base/.proton/runtime.json")) } ///| async test "Windows runtime validation rejects a missing required file" { - let root = "target/proton-package-windows-missing-runtime" + let root = @async_fs.tmpdir(prefix="proton-package-windows-runtime-") ignore(prepare_windows_test_inputs(root)) let runtime = prepare_windows_test_runtime(root) let missing = @fsutil.resolve_path(runtime, "bin/chrome_elf.dll") @@ -272,7 +271,7 @@ async test "Windows runtime validation rejects a missing required file" { ///| async test "Windows portable staging is atomic and excludes build artifacts" { - let root = "target/proton package windows layout with spaces" + let root = @async_fs.tmpdir(prefix="proton package windows layout ") let (executable, _) = prepare_windows_test_inputs(root) let runtime = prepare_windows_test_runtime(root) let plan = windows_test_plan(root, ["app"], false) @@ -307,7 +306,7 @@ async test "Windows portable staging is atomic and excludes build artifacts" { ///| async test "Windows portable stages a configured signing binary from resources" { - let root = "target/proton-package-windows-resource-signing" + let root = @async_fs.tmpdir(prefix="proton-package-windows-resource-") let (executable, _) = prepare_windows_test_inputs(root) let binary = @fsutil.resolve_path(root, "helpers/worker.exe") write_windows_test_file(binary, "worker") @@ -332,7 +331,7 @@ async test "Windows portable stages a configured signing binary from resources" ///| #cfg(platform="windows") async test "Windows portable zip is atomic and extracts from paths with spaces" { - let root = "target/proton-package-windows-zip with spaces" + let root = @async_fs.tmpdir(prefix="proton package windows zip ") let (executable, _) = prepare_windows_test_inputs(root) let runtime = prepare_windows_test_runtime(root) let plan = windows_test_plan(root, ["app", "zip"], false) @@ -372,8 +371,7 @@ async test "Windows portable zip is atomic and extracts from paths with spaces" ///| async test "Windows promotion rolls back when staging is missing" { - let root = "target/proton-package-windows-rollback" - remove_tree(root) + let root = @async_fs.tmpdir(prefix="proton-package-windows-rollback-") let destination = @fsutil.resolve_path(root, "Demo App") let marker = @fsutil.resolve_path(destination, "old.txt") write_windows_test_file(marker, "old") @@ -396,8 +394,7 @@ async test "Windows promotion rolls back when staging is missing" { ///| async test "Windows promotion restores a stale backup before continuing" { - let root = "target/proton-package-windows-stale-backup" - remove_tree(root) + let root = @async_fs.tmpdir(prefix="proton-package-windows-backup-") let destination = @fsutil.resolve_path(root, "Demo App") let backup = destination + ".backup" let marker = @fsutil.resolve_path(backup, "old.txt") @@ -421,7 +418,7 @@ async test "Windows promotion restores a stale backup before continuing" { ///| async test "Windows signing rejects a missing target before invoking signtool" { - let root = "target/proton-package-windows-missing-sign-target" + let root = @async_fs.tmpdir(prefix="proton-package-windows-sign-target-") let (_, _) = prepare_windows_test_inputs(root) let output = @fsutil.resolve_path(root, "portable") for @@ -457,7 +454,7 @@ async test "Windows signing rejects a missing target before invoking signtool" { ///| #cfg(platform="windows") async test "Windows signing failure does not leak the certificate password" { - let root = "target/proton-package-windows-sign-failure" + let root = @async_fs.tmpdir(prefix="proton-package-windows-sign-failure-") let (_, _) = prepare_windows_test_inputs(root) let output = @fsutil.resolve_path(root, "portable") let plan = windows_test_plan(root, ["app"], true) @@ -492,7 +489,7 @@ async test "Windows signing failure does not leak the certificate password" { ///| #cfg(platform="windows") async test "Windows signing failure blocks promotion" { - let root = "target/proton-package-windows-sign-promotion-failure" + let root = @async_fs.tmpdir(prefix="proton-package-windows-promotion-") let (executable, _) = prepare_windows_test_inputs(root) let runtime = prepare_windows_test_runtime(root) let plan = windows_test_plan(root, ["app"], true) @@ -527,7 +524,7 @@ async test "Windows signing failure blocks promotion" { ///| #cfg(platform="windows") async test "Windows signature verification failure blocks promotion" { - let root = "target/proton-package-windows-verify-failure" + let root = @async_fs.tmpdir(prefix="proton-package-windows-verify-") let (executable, _) = prepare_windows_test_inputs(root) let runtime = prepare_windows_test_runtime(root) let plan = windows_test_plan(root, ["app"], true) diff --git a/config/config_test.mbt b/config/config_test.mbt index 56989070..8927ed57 100644 --- a/config/config_test.mbt +++ b/config/config_test.mbt @@ -14,14 +14,14 @@ test "project config reports malformed JSON at the text boundary" { let error = expect_project_config_error(() => { load_project_config_from_text( "{\"window\":", - "target/proton-config", - name="target/proton-config/proton.project.json", + "fixtures/proton-config", + name="fixtures/proton-config/proton.project.json", ) }) guard error is ProjectConfigError::Syntax(name~, diagnostics~) else { abort("expected project config JSON syntax error") } - assert_eq(name, "target/proton-config/proton.project.json") + assert_eq(name, "fixtures/proton-config/proton.project.json") assert_true(diagnostics != "") assert_true(error.message().has_prefix(name + ": ")) } @@ -38,7 +38,7 @@ test "project config rejects unsupported window size_hint" { "entry": { "kind": "url", "value": "http://localhost:5173" }, } let error = expect_project_config_error(() => { - load_project_config_from_json(source, "target/proton-config") + load_project_config_from_json(source, "fixtures/proton-config") }) assert_true(error.message().contains("unsupported window.size_hint")) } @@ -54,7 +54,7 @@ test "project config parses default titlebar style" { }, "entry": { "kind": "url", "value": "http://localhost:5173" }, } - let config = load_project_config_from_json(source, "target/proton-config").config() + let config = load_project_config_from_json(source, "fixtures/proton-config").config() assert_true(config.window.titlebar_style == TitlebarDefault) } @@ -69,7 +69,7 @@ test "project config parses overlay titlebar style" { }, "entry": { "kind": "url", "value": "http://localhost:5173" }, } - let config = load_project_config_from_json(source, "target/proton-config").config() + let config = load_project_config_from_json(source, "fixtures/proton-config").config() assert_true(config.window.titlebar_style == TitlebarOverlay) } @@ -81,7 +81,7 @@ test "project config requires an identifier for single instance mode" { "entry": { "kind": "html", "value": "
App
" }, } let error = expect_project_config_error(() => { - load_project_config_from_json(source, "target/proton-config") + load_project_config_from_json(source, "fixtures/proton-config") }) assert_true(error.message().contains("identifier")) assert_true(error.message().contains("single_instance")) @@ -95,7 +95,7 @@ test "project config decodes single instance mode" { "window": { "title": "App", "width": 900, "height": 700 }, "entry": { "kind": "html", "value": "
App
" }, } - let config = load_project_config_from_json(source, "target/proton-config").config() + let config = load_project_config_from_json(source, "fixtures/proton-config").config() assert_true(config.single_instance()) assert_eq(config.metadata().identifier, Some("dev.proton.single-instance")) } @@ -112,14 +112,14 @@ test "project config rejects unsupported titlebar style" { "entry": { "kind": "url", "value": "http://localhost:5173" }, } let error = expect_project_config_error(() => { - load_project_config_from_json(source, "target/proton-config") + load_project_config_from_json(source, "fixtures/proton-config") }) assert_true(error.message().contains("unsupported window.titlebar_style")) } ///| test "project config rebases file entry and frontend paths from config directory" { - let base_dir = "target/proton-config/subdir" + let base_dir = "fixtures/proton-config/subdir" let source : Json = { "window": { "title": "App", "width": 900, "height": 700 }, "entry": { "kind": "asset", "value": "frontend/dist/index.html" }, @@ -134,7 +134,7 @@ test "project config rebases file entry and frontend paths from config directory "icon": ["icons/icon.ico"], "resources": ["resources/**"], "sign": { "binaries": ["bin/worker"] }, - "output": "target/proton-dist", + "output": "custom-dist", }, } let config = load_project_config_from_json(source, base_dir).config() @@ -142,7 +142,7 @@ test "project config rebases file entry and frontend paths from config directory Asset(path) => assert_true( normalize_test_path(path).has_suffix( - "target/proton-config/subdir/frontend/dist/index.html", + "fixtures/proton-config/subdir/frontend/dist/index.html", ), ) _ => abort("expected asset entry") @@ -151,41 +151,41 @@ test "project config rebases file entry and frontend paths from config directory assert_eq(backend.app_package(), "app") assert_true( normalize_test_path(backend.path()).has_suffix( - "target/proton-config/subdir/backend module", + "fixtures/proton-config/subdir/backend module", ), ) let frontend = config.frontend().unwrap() assert_true( normalize_test_path(frontend.dist().unwrap()).has_suffix( - "target/proton-config/subdir/frontend module/dist", + "fixtures/proton-config/subdir/frontend module/dist", ), ) assert_true( normalize_test_path(frontend.path().unwrap()).has_suffix( - "target/proton-config/subdir/frontend module", + "fixtures/proton-config/subdir/frontend module", ), ) let bundle = config.bundle().unwrap() assert_eq(bundle.targets, ["app", "zip", "dmg"]) assert_true( normalize_test_path(bundle.icon[0]).has_suffix( - "target/proton-config/subdir/icons/icon.ico", + "fixtures/proton-config/subdir/icons/icon.ico", ), ) assert_true( normalize_test_path(bundle.resources[0]).has_suffix( - "target/proton-config/subdir/resources/**", + "fixtures/proton-config/subdir/resources/**", ), ) let sign = bundle.sign.unwrap() assert_true( normalize_test_path(sign.binaries()[0]).has_suffix( - "target/proton-config/subdir/bin/worker", + "fixtures/proton-config/subdir/bin/worker", ), ) assert_true( normalize_test_path(bundle.output()).has_suffix( - "target/proton-config/subdir/target/proton-dist", + "fixtures/proton-config/subdir/custom-dist", ), ) } @@ -279,14 +279,14 @@ test "project config rejects absolute project paths with field-specific errors" "bundle.output", project_config_for_path_test( html_entry, - extra=("bundle", { "output": "/target/proton-dist" }), + extra=("bundle", { "output": "/custom-dist" }), ), ), ] for _, item in cases { let (label, source) = item let error = expect_project_config_error(() => { - load_project_config_from_json(source, "target/proton-config") + load_project_config_from_json(source, "fixtures/proton-config") }) assert_eq( error.message(), @@ -304,7 +304,7 @@ test "project config rejects signing binary globs under the correct field" { extra=("bundle", { "sign": { "binaries": ["bin/*.exe"] } }), ) let error = expect_project_config_error(() => { - load_project_config_from_json(source, "target/proton-config") + load_project_config_from_json(source, "fixtures/proton-config") }) assert_eq( error.message(), @@ -319,7 +319,7 @@ test "project config rejects backslashes in portable project paths" { "entry": { "kind": "asset", "value": "frontend\\dist\\index.html" }, } let error = expect_project_config_error(() => { - load_project_config_from_json(source, "target/proton-config") + load_project_config_from_json(source, "fixtures/proton-config") }) assert_eq( error.message(), @@ -333,7 +333,7 @@ test "project config rebases portable paths into host path components" { "window": { "title": "App", "width": 900, "height": 700 }, "entry": { "kind": "asset", "value": "frontend/dist/index.html" }, } - let config = load_project_config_from_json(source, "target/proton-config").config() + let config = load_project_config_from_json(source, "fixtures/proton-config").config() match config.entry() { Asset(path) => { let entry : @mbpath.Path = path @@ -346,37 +346,7 @@ test "project config rebases portable paths into host path components" { ///| test "project config reads module metadata from configured backend" { - let project_dir = "target/project-config-backend-metadata" - let backend_dir = project_dir + "/backend" - @mbfs.create_dir("target") catch { - _ => () - } - @mbfs.create_dir(project_dir) catch { - _ => () - } - @mbfs.create_dir(backend_dir) catch { - _ => () - } - @mbfs.write_string_to_file( - project_dir + "/moon.mod", - "name = \"example/parent\"\nversion = \"9.9.9\"\n", - encoding="utf8", - ) catch { - error => abort(@debug.render(Repr(error))) - } - @mbfs.write_string_to_file( - backend_dir + "/moon.mod", - ( - #|name = "example/backend" - #|version = "1.2.3" - #|description = "Backend module metadata" - #|license = "Apache-2.0" - #| - ), - encoding="utf8", - ) catch { - error => abort(@debug.render(Repr(error))) - } + let project_dir = "testdata/project-config-backend-metadata" let source : Json = { "window": { "title": "App", "width": 900, "height": 700 }, "entry": { "kind": "asset", "value": "frontend/dist/index.html" }, @@ -388,11 +358,13 @@ test "project config reads module metadata from configured backend" { assert_eq(metadata.version, Some("1.2.3")) assert_eq(metadata.description, Some("Backend module metadata")) assert_eq(metadata.license, Some("Apache-2.0")) - @mbfs.remove_file(backend_dir + "/moon.mod") catch { - error => abort(@debug.render(Repr(error))) + let source_without_backend_manifest : Json = { + "window": { "title": "App", "width": 900, "height": 700 }, + "entry": { "kind": "asset", "value": "frontend/dist/index.html" }, + "backend": { "path": "missing-backend", "package": "app" }, } let metadata_without_backend_manifest = load_project_config_from_json( - source, project_dir, + source_without_backend_manifest, project_dir, ) .config() .metadata() @@ -407,7 +379,7 @@ test "project config rejects legacy frontend cwd" { "frontend": { "cwd": "frontend" }, } let error = expect_project_config_error(() => { - load_project_config_from_json(source, "target/proton-config") + load_project_config_from_json(source, "fixtures/proton-config") }) assert_eq( error.message(), @@ -426,7 +398,7 @@ test "project config aggregates unknown top-level fields" { "debug": false, } let error = expect_project_config_error(() => { - load_project_config_from_json(source, "target/proton-config") + load_project_config_from_json(source, "fixtures/proton-config") }) assert_eq( error.message(), @@ -496,7 +468,7 @@ test "project config rejects unknown object fields" { "entry": { "kind": "url", "value": "http://localhost:5173" }, } let error = expect_project_config_error(() => { - load_project_config_from_json(source, "target/proton-config") + load_project_config_from_json(source, "fixtures/proton-config") }) assert_eq( error.message(), @@ -510,7 +482,7 @@ test "project config load reports no warnings by default" { "window": { "title": "App", "width": 900, "height": 700 }, "entry": { "kind": "url", "value": "http://localhost:5173" }, } - let loaded = load_project_config_from_json(source, "target/proton-config") + let loaded = load_project_config_from_json(source, "fixtures/proton-config") assert_eq(loaded.warnings(), []) } @@ -520,7 +492,7 @@ test "loaded project config carries warnings" { "window": { "title": "App", "width": 900, "height": 700 }, "entry": { "kind": "url", "value": "http://localhost:5173" }, } - let loaded = load_project_config_from_json(source, "target/proton-config") + let loaded = load_project_config_from_json(source, "fixtures/proton-config") let with_warnings = LoadedProjectConfig::new(loaded.config(), warnings=[ "proton.project.json field `legacy` is deprecated", ]) @@ -555,7 +527,7 @@ test "project config decodes secondary windows and macOS launch metadata" { ], }, } - let config = load_project_config_from_json(source, "target/proton-config").config() + let config = load_project_config_from_json(source, "fixtures/proton-config").config() assert_eq(config.windows().length(), 1) assert_eq(config.windows()[0].id, "settings") assert_true(config.windows()[0].open_on_start) @@ -566,7 +538,7 @@ test "project config decodes secondary windows and macOS launch metadata" { ProjectEntry::File(path) => assert_true( normalize_test_path(path).has_suffix( - "target/proton-config/settings.html", + "fixtures/proton-config/settings.html", ), ) _ => abort("expected secondary file entry") diff --git a/config/project_config.mbt b/config/project_config.mbt index dbf1e05a..f22eac9b 100644 --- a/config/project_config.mbt +++ b/config/project_config.mbt @@ -238,7 +238,7 @@ pub fn ProjectBundleConfig::new( sign? : ProjectSignConfig? = None, url_schemes? : Array[String] = [], document_types? : Array[ProjectDocumentType] = [], - output? : String = "target/proton-dist", + output? : String = "dist", ) -> ProjectBundleConfig { ProjectBundleConfig::{ active, diff --git a/config/project_decode.mbt b/config/project_decode.mbt index 2a32b96b..3fbe34a4 100644 --- a/config/project_decode.mbt +++ b/config/project_decode.mbt @@ -573,7 +573,7 @@ fn optional_project_bundle( let document_types = optional_project_document_types(bundle_fields) let output = rebase_project_path( optional_string_config_field(bundle_fields, "output", "bundle.output").unwrap_or( - "target/proton-dist", + "dist", ), base_dir, "bundle.output", diff --git a/config/testdata/project-config-backend-metadata/backend/moon.mod b/config/testdata/project-config-backend-metadata/backend/moon.mod new file mode 100644 index 00000000..0b88840d --- /dev/null +++ b/config/testdata/project-config-backend-metadata/backend/moon.mod @@ -0,0 +1,4 @@ +name = "example/backend" +version = "1.2.3" +description = "Backend module metadata" +license = "Apache-2.0" diff --git a/config/testdata/project-config-backend-metadata/moon.mod b/config/testdata/project-config-backend-metadata/moon.mod new file mode 100644 index 00000000..f090eff0 --- /dev/null +++ b/config/testdata/project-config-backend-metadata/moon.mod @@ -0,0 +1,2 @@ +name = "example/parent" +version = "9.9.9" diff --git a/e2e/test/app_commands_probe_wbtest.mbt b/e2e/test/app_commands_probe_wbtest.mbt index a827a0b1..e71eef8d 100644 --- a/e2e/test/app_commands_probe_wbtest.mbt +++ b/e2e/test/app_commands_probe_wbtest.mbt @@ -75,7 +75,7 @@ async fn evaluate_json( ///| async fn run_app_commands_full_probe( - root : String, + output_root : String, target : String, timeout : Int, native_log_path : String, @@ -94,7 +94,7 @@ async fn run_app_commands_full_probe( let queue = run_queue_full_probe(page) run_reload_probe(page, target, timeout) page_open = false - run_non_proton_probe(target, root, timeout) + run_non_proton_probe(target, output_root, timeout) assert_native_bridge_guards(native_log_path) println( "MoonBit 41_app_commands full probe passed: screenshot=" + @@ -386,10 +386,10 @@ async fn run_reload_probe( ///| async fn run_non_proton_probe( target : String, - root : String, + output_root : String, timeout : Int, ) -> Unit { - let file_path = join_path(root, "target/non-proton-moonbit-probe.html") + let file_path = join_path(output_root, "non-proton-moonbit-probe.html") @mbfs.write_string_to_file( file_path, "Non Proton File Probe

file probe

", ) catch { diff --git a/e2e/test/dev_extension_process_wbtest.mbt b/e2e/test/dev_extension_process_wbtest.mbt index 24193876..3a8c6df4 100644 --- a/e2e/test/dev_extension_process_wbtest.mbt +++ b/e2e/test/dev_extension_process_wbtest.mbt @@ -22,7 +22,11 @@ fn dev_extension_frontend_state(root : String) -> DevExtensionFrontendState { } ///| -async fn ensure_dev_extension_frontend(root : String, timeout : Int) -> Unit { +async fn ensure_dev_extension_frontend( + root : String, + output_root : String, + timeout : Int, +) -> Unit { let frontend_dir = join_path(root, "examples/47_dev_extension_js/frontend") let vite = dev_extension_vite_binary(frontend_dir) if !@mbfs.path_exists(vite) { @@ -30,7 +34,7 @@ async fn ensure_dev_extension_frontend(root : String, timeout : Int) -> Unit { "ci", "--no-audit", "--no-fund", ]) run_dev_extension_command( - root, + output_root, "47-npm-ci", program, args, @@ -99,30 +103,30 @@ fn process_snapshot_suffix(snapshot : ProcessSnapshot) -> String { ///| fn dev_extension_cli_target_dir( - root : String, + output_root : String, phase : String, port : Int, ) -> String { - join_path(root, "target/proton-e2e-cli-" + phase + "-" + port.to_string()) + join_path(output_root, "cli-" + phase + "-" + port.to_string()) } ///| fn dev_extension_app_target_dir( - root : String, + output_root : String, phase : String, port : Int, ) -> String { - join_path(root, "target/proton-e2e-app-" + phase + "-" + port.to_string()) + join_path(output_root, "app-" + phase + "-" + port.to_string()) } ///| -fn dev_extension_example_target_dir(root : String, port : Int) -> String { - join_path(root, "target/proton-e2e-example-" + port.to_string()) +fn dev_extension_example_target_dir(output_root : String, port : Int) -> String { + join_path(output_root, "example-" + port.to_string()) } ///| async fn run_dev_extension_command( - root : String, + output_root : String, name : String, program : String, args : Array[String], @@ -130,10 +134,7 @@ async fn run_dev_extension_command( env : Map[String, String], timeout : Int, ) -> Unit { - @mbfs.create_dir(join_path(root, "target")) catch { - _ => () - } - let output_path = join_path(root, "target/proton-e2e-" + name + ".log") + let output_path = join_path(output_root, name + ".log") @async.with_task_group(group => { let output = @process.redirect_to_file( output_path, @@ -189,9 +190,10 @@ async fn run_dev_extension_command( ///| fn dev_extension_runtime_env( root : String, + output_root : String, cdp_port : Int, ) -> Map[String, String] raise { - let env = app_process_env(root, "47_dev_extension_js", cdp_port) + let env = app_process_env(root, output_root, "47_dev_extension_js", cdp_port) env["NO_COLOR"] = "1" env["FORCE_COLOR"] = "0" env["PROTON_NO_UPDATE_CHECK"] = "1" @@ -215,9 +217,10 @@ fn restore_dev_extension_parent_preload( ///| fn dev_extension_host_env( root : String, + output_root : String, cdp_port : Int, ) -> Map[String, String] raise { - let env = dev_extension_runtime_env(root, cdp_port) + let env = dev_extension_runtime_env(root, output_root, cdp_port) // app_process_env injects libcef.so for Linux application children. The // CLI/build hosts launch Moon or Node rather than CEF, so restore the // caller's preload instead of leaking the injected CEF wrapper into them. @@ -229,12 +232,13 @@ fn dev_extension_host_env( async fn spawn_dev_extension_cli_app( group : @async.TaskGroup[Unit], root : String, + output_root : String, cdp_port : Int, frontend_port : Int, ) -> SpawnedScenarioApp { let output_path = join_path( - root, - "target/proton-e2e-child-" + cdp_port.to_string() + ".log", + output_root, + "child-" + cdp_port.to_string() + ".log", ) let output = @process.redirect_to_file( output_path, @@ -248,7 +252,7 @@ async fn spawn_dev_extension_cli_app( "-C", join_path(root, "cli"), "--target-dir", - dev_extension_cli_target_dir(root, "dev", cdp_port), + dev_extension_cli_target_dir(output_root, "dev", cdp_port), "run", ".", "--", @@ -260,7 +264,7 @@ async fn spawn_dev_extension_cli_app( "--package", ".", "--moon-target-dir", - dev_extension_app_target_dir(root, "dev", cdp_port), + dev_extension_app_target_dir(output_root, "dev", cdp_port), "--url", frontend_url, "--command", @@ -276,7 +280,7 @@ async fn spawn_dev_extension_cli_app( program, args, cwd=root, - extra_env=dev_extension_host_env(root, cdp_port), + extra_env=dev_extension_host_env(root, output_root, cdp_port), stdout=output, stderr=output, cancel_handler=app_cancel_handler(), @@ -285,7 +289,7 @@ async fn spawn_dev_extension_cli_app( SpawnedScenarioApp::{ process, output_path, - native_log_path: scenario_native_log_path(root, cdp_port), + native_log_path: scenario_native_log_path(output_root, cdp_port), } } @@ -293,11 +297,12 @@ async fn spawn_dev_extension_cli_app( async fn spawn_dev_extension_production_app( group : @async.TaskGroup[Unit], root : String, + output_root : String, cdp_port : Int, ) -> SpawnedScenarioApp { let output_path = join_path( - root, - "target/proton-e2e-child-" + cdp_port.to_string() + ".log", + output_root, + "child-" + cdp_port.to_string() + ".log", ) let output = @process.redirect_to_file( output_path, @@ -307,7 +312,7 @@ async fn spawn_dev_extension_production_app( "-C", join_path(root, "examples"), "--target-dir", - dev_extension_example_target_dir(root, cdp_port), + dev_extension_example_target_dir(output_root, cdp_port), "run", "47_dev_extension_js", "--target", @@ -320,7 +325,7 @@ async fn spawn_dev_extension_production_app( program, args, cwd=root, - extra_env=dev_extension_runtime_env(root, cdp_port), + extra_env=dev_extension_runtime_env(root, output_root, cdp_port), stdout=output, stderr=output, cancel_handler=app_cancel_handler(), @@ -329,27 +334,31 @@ async fn spawn_dev_extension_production_app( SpawnedScenarioApp::{ process, output_path, - native_log_path: scenario_native_log_path(root, cdp_port), + native_log_path: scenario_native_log_path(output_root, cdp_port), } } ///| -async fn build_dev_extension_frontend(root : String, timeout : Int) -> Unit { +async fn build_dev_extension_frontend( + root : String, + output_root : String, + timeout : Int, +) -> Unit { let runtime_dist = active_runtime_dist(root) let has_linux_cef = @mbfs.path_exists( join_path(runtime_dist, "bin/libcef.so"), ) let timeout = dev_extension_build_timeout(timeout, has_linux_cef) let port = choose_cdp_port() - let env = dev_extension_host_env(root, port) - let cli_target_dir = dev_extension_cli_target_dir(root, "build", port) - let app_target_dir = dev_extension_app_target_dir(root, "build", port) + let env = dev_extension_host_env(root, output_root, port) + let cli_target_dir = dev_extension_cli_target_dir(output_root, "build", port) + let app_target_dir = dev_extension_app_target_dir(output_root, "build", port) defer { remove_dev_extension_tree(cli_target_dir) remove_dev_extension_tree(app_target_dir) } run_dev_extension_command( - root, + output_root, "47-proton-build", "moon", [ @@ -379,16 +388,23 @@ async fn build_dev_extension_frontend(root : String, timeout : Int) -> Unit { ///| async fn run_dev_extension_dev_probe_once( root : String, + output_root : String, frontend_port : Int, timeout : Int, ) -> Unit { let cdp_port = choose_cdp_port() let target = cdp_port.to_string() let frontend_url = "http://127.0.0.1:" + frontend_port.to_string() - let cli_target_dir = dev_extension_cli_target_dir(root, "dev", cdp_port) - let app_target_dir = dev_extension_app_target_dir(root, "dev", cdp_port) + let cli_target_dir = dev_extension_cli_target_dir( + output_root, "dev", cdp_port, + ) + let app_target_dir = dev_extension_app_target_dir( + output_root, "dev", cdp_port, + ) @async.with_task_group(group => { - let app = spawn_dev_extension_cli_app(group, root, cdp_port, frontend_port) + let app = spawn_dev_extension_cli_app( + group, root, output_root, cdp_port, frontend_port, + ) let mut stopped = false defer { if !stopped { @@ -400,7 +416,9 @@ async fn run_dev_extension_dev_probe_once( ignore( wait_for_spawned_app_cdp(app, cdp_http_endpoint_from(target), timeout), ) - run_dev_extension_page_probe(app, target, frontend_url, root, timeout) + run_dev_extension_page_probe( + app, target, frontend_url, output_root, timeout, + ) match app_exit_code(app.process) { Some(code) => fail( @@ -422,13 +440,18 @@ async fn run_dev_extension_dev_probe_once( ///| async fn run_dev_extension_production_probe_once( root : String, + output_root : String, timeout : Int, ) -> Unit { let cdp_port = choose_cdp_port() let target = cdp_port.to_string() - let example_target_dir = dev_extension_example_target_dir(root, cdp_port) + let example_target_dir = dev_extension_example_target_dir( + output_root, cdp_port, + ) @async.with_task_group(group => { - let app = spawn_dev_extension_production_app(group, root, cdp_port) + let app = spawn_dev_extension_production_app( + group, root, output_root, cdp_port, + ) let mut stopped = false defer { if !stopped { @@ -459,7 +482,11 @@ async fn run_dev_extension_production_probe_once( } ///| -async fn run_dev_extension_scenario_once(root : String, timeout : Int) -> Unit { +async fn run_dev_extension_scenario_once( + root : String, + output_root : String, + timeout : Int, +) -> Unit { let frontend = dev_extension_frontend_state(root) defer { if !frontend.had_node_modules { @@ -469,18 +496,22 @@ async fn run_dev_extension_scenario_once(root : String, timeout : Int) -> Unit { remove_dev_extension_tree(frontend.dist_dir) } } - ensure_dev_extension_frontend(root, timeout) + ensure_dev_extension_frontend(root, output_root, timeout) let frontend_port = choose_cdp_port() run_e2e_operation_with_timeout( timeout, "47_dev_extension_js development probe", - () => run_dev_extension_dev_probe_once(root, frontend_port, timeout), + () => { + run_dev_extension_dev_probe_once( + root, output_root, frontend_port, timeout, + ) + }, ) - build_dev_extension_frontend(root, timeout) + build_dev_extension_frontend(root, output_root, timeout) run_e2e_operation_with_timeout( timeout, "47_dev_extension_js production probe", - () => run_dev_extension_production_probe_once(root, timeout), + () => run_dev_extension_production_probe_once(root, output_root, timeout), ) } @@ -489,10 +520,12 @@ async fn run_dev_extension_scenario_with_retry( root : String, timeout : Int, ) -> Unit { + let output_root = @fs.tmpdir(prefix="proton-e2e-dev-extension-") + defer remove_dev_extension_tree(output_root) let mut attempt = 0 while attempt < 2 { let succeeded = try { - run_dev_extension_scenario_once(root, timeout) + run_dev_extension_scenario_once(root, output_root, timeout) true } catch { error => { diff --git a/e2e/test/dev_extension_scenario_wbtest.mbt b/e2e/test/dev_extension_scenario_wbtest.mbt index 81f95a57..c59d22df 100644 --- a/e2e/test/dev_extension_scenario_wbtest.mbt +++ b/e2e/test/dev_extension_scenario_wbtest.mbt @@ -58,7 +58,7 @@ async fn run_dev_extension_page_probe( app : SpawnedScenarioApp, target : String, frontend_url : String, - root : String, + output_root : String, timeout : Int, ) -> Unit { let page = connect_spawned_page(app, target, timeout) @@ -71,7 +71,7 @@ async fn run_dev_extension_page_probe( assert_dev_extension_probe(result, frontend_url, value) run_dev_extension_reload_probe(page, target, frontend_url, timeout) page_open = false - run_dev_extension_non_proton_probe(target, root, timeout) + run_dev_extension_non_proton_probe(target, output_root, timeout) assert_dev_extension_native_log(app.native_log_path) println("MoonBit 47_dev_extension_js dev probe passed") } @@ -337,10 +337,10 @@ fn dev_extension_reload_probe_expression() -> String { ///| async fn run_dev_extension_non_proton_probe( target : String, - root : String, + output_root : String, timeout : Int, ) -> Unit { - let file_path = join_path(root, "target/non-proton-dev-extension-probe.html") + let file_path = join_path(output_root, "non-proton-dev-extension-probe.html") @mbfs.write_string_to_file( file_path, "Non Proton File Probe

file probe

", ) catch { diff --git a/e2e/test/orchestration_wbtest.mbt b/e2e/test/orchestration_wbtest.mbt index 9bd22845..ff08c9b7 100644 --- a/e2e/test/orchestration_wbtest.mbt +++ b/e2e/test/orchestration_wbtest.mbt @@ -115,6 +115,7 @@ fn read_runtime_dist(manifest_path : String) -> Result[String, String] { ///| fn app_process_env( root : String, + output_root : String, scenario : String, cdp_port : Int, ) -> Map[String, String] raise { @@ -138,7 +139,7 @@ fn app_process_env( "PROTON_NATIVE_DIST": runtime_dist, "PROTON_RUNTIME_ROOT": runtime_dist, "PROTON_HELPER_PATH": helper_path, - "PROTON_NATIVE_LOG": scenario_native_log_path(root, cdp_port), + "PROTON_NATIVE_LOG": scenario_native_log_path(output_root, cdp_port), "PROTON_REMOTE_DEBUGGING_PORT": cdp_port.to_string(), "PROTON_HEADLESS": "1", "PROTON_E2E_ASSET_SIDECAR_ENTRY": join_path( @@ -191,8 +192,8 @@ fn helper_executable_name() -> String { } ///| -fn scenario_native_log_path(root : String, cdp_port : Int) -> String { - join_path(root, "target/proton-e2e-" + cdp_port.to_string() + ".log") +fn scenario_native_log_path(output_root : String, cdp_port : Int) -> String { + join_path(output_root, "native-" + cdp_port.to_string() + ".log") } ///| @@ -229,17 +230,14 @@ fn path_list_separator() -> String { async fn[X] spawn_scenario_app( group : @async.TaskGroup[X], root : String, + output_root : String, scenario : String, cdp_port : Int, ) -> SpawnedScenarioApp { - let (program, args) = scenario_app_command(root) - let output_dir = join_path(root, "target") - if !@mbfs.path_exists(output_dir) { - @mbfs.create_dir(output_dir) - } + let (program, args) = scenario_app_command(output_root) let output_path = join_path( - root, - "target/proton-e2e-child-" + cdp_port.to_string() + ".log", + output_root, + "child-" + cdp_port.to_string() + ".log", ) let output = @process.redirect_to_file( output_path, @@ -250,7 +248,7 @@ async fn[X] spawn_scenario_app( program, args, cwd=root, - extra_env=app_process_env(root, scenario, cdp_port), + extra_env=app_process_env(root, output_root, scenario, cdp_port), stdout=output, stderr=output, cancel_handler=app_cancel_handler(), @@ -259,18 +257,21 @@ async fn[X] spawn_scenario_app( SpawnedScenarioApp::{ process, output_path, - native_log_path: scenario_native_log_path(root, cdp_port), + native_log_path: scenario_native_log_path(output_root, cdp_port), } } ///| -async fn build_scenario_app_executable(root : String) -> Unit { +async fn build_scenario_app_executable( + root : String, + output_root : String, +) -> Unit { if @path.sep != '\\' { let (code, _) = @process.collect_output_merged( "moon", [ "--target-dir", - scenario_app_target_dir(root), + scenario_app_target_dir(output_root), "-C", root, "test", @@ -290,25 +291,25 @@ async fn build_scenario_app_executable(root : String) -> Unit { } ///| -fn scenario_app_command(root : String) -> (String, Array[String]) raise { +fn scenario_app_command(output_root : String) -> (String, Array[String]) raise { if @path.sep == '\\' { (current_test_executable(), [app_child_test_selector]) } else { - isolated_app_command(scenario_app_executable(root), [ + isolated_app_command(scenario_app_executable(output_root), [ app_child_test_selector, ]) } } ///| -fn scenario_app_target_dir(root : String) -> String { - join_path(root, "target/proton-e2e-app-child") +fn scenario_app_target_dir(output_root : String) -> String { + join_path(output_root, "app-build") } ///| -fn scenario_app_executable(root : String) -> String { +fn scenario_app_executable(output_root : String) -> String { join_path( - scenario_app_target_dir(root), + scenario_app_target_dir(output_root), "native/debug/test/moonbit-community/proton/e2e/test/test.whitebox_test.exe", ) } @@ -617,13 +618,14 @@ fn join_path(base : String, child : String) -> String { ///| async fn run_scenario_probe_once( root : String, + output_root : String, name : String, timeout : Int, ) -> Unit { let port = choose_cdp_port() let target = port.to_string() @async.with_task_group(group => { - let app = spawn_scenario_app(group, root, name, port) + let app = spawn_scenario_app(group, root, output_root, name, port) let mut stopped = false defer (if !stopped { app.process.cancel() }) let browser_web_socket_url = wait_for_spawned_app_cdp( @@ -651,7 +653,12 @@ async fn run_scenario_probe_once( finish_scenario_app(app, target) } else { if name == "41_app_commands" { - run_app_commands_full_probe(root, target, timeout, app.native_log_path) + run_app_commands_full_probe( + output_root, + target, + timeout, + app.native_log_path, + ) } else if name == "40_event_broadcast" { run_event_scenario_page_probe(app, target, timeout) } else if name == "46_asset_sidecar_resources" { @@ -692,7 +699,9 @@ async fn run_scenario_probe_with_retry( name : String, timeout : Int, ) -> Unit { - build_scenario_app_executable(root) + let output_root = @fs.tmpdir(prefix="proton-e2e-") + defer remove_dev_extension_tree(output_root) + build_scenario_app_executable(root, output_root) let operation_timeout = scenario_operation_timeout(name, timeout) let mut attempt = 0 while attempt < 2 { @@ -700,7 +709,7 @@ async fn run_scenario_probe_with_retry( run_e2e_operation_with_timeout( operation_timeout, "E2E scenario " + name, - () => run_scenario_probe_once(root, name, timeout), + () => run_scenario_probe_once(root, output_root, name, timeout), ) true } catch { diff --git a/e2e/test/process_lifecycle_wbtest.mbt b/e2e/test/process_lifecycle_wbtest.mbt index ab55b8ee..0db402ca 100644 --- a/e2e/test/process_lifecycle_wbtest.mbt +++ b/e2e/test/process_lifecycle_wbtest.mbt @@ -328,12 +328,16 @@ async fn wait_for_cdp_endpoint_stop(endpoint : String, timeout : Int) -> Unit { } ///| -async fn run_pending_lifecycle_probe_once(root : String, timeout : Int) -> Unit { +async fn run_pending_lifecycle_probe_once( + root : String, + output_root : String, + timeout : Int, +) -> Unit { let name = "41_app_commands" let port = choose_cdp_port() let target = port.to_string() @async.with_task_group(group => { - let app = spawn_scenario_app(group, root, name, port) + let app = spawn_scenario_app(group, root, output_root, name, port) let mut stopped = false defer (if !stopped { app.process.cancel() }) ignore( @@ -397,13 +401,16 @@ async fn run_pending_lifecycle_probe_once(root : String, timeout : Int) -> Unit ///| async fn run_pending_lifecycle_probe(root : String, timeout : Int) -> Unit { + let output_root = @fs.tmpdir(prefix="proton-e2e-lifecycle-") + defer remove_dev_extension_tree(output_root) + build_scenario_app_executable(root, output_root) let mut attempt = 0 while attempt < 2 { let succeeded = try { run_e2e_operation_with_timeout( timeout, "41_app_commands pending lifecycle", - () => run_pending_lifecycle_probe_once(root, timeout), + () => run_pending_lifecycle_probe_once(root, output_root, timeout), ) true } catch { @@ -424,12 +431,16 @@ async fn run_pending_lifecycle_probe(root : String, timeout : Int) -> Unit { } ///| -async fn run_event_lifecycle_probe_once(root : String, timeout : Int) -> Unit { +async fn run_event_lifecycle_probe_once( + root : String, + output_root : String, + timeout : Int, +) -> Unit { let name = "40_event_broadcast" let port = choose_cdp_port() let target = port.to_string() @async.with_task_group(group => { - let app = spawn_scenario_app(group, root, name, port) + let app = spawn_scenario_app(group, root, output_root, name, port) let mut stopped = false defer (if !stopped { app.process.cancel() }) ignore( @@ -491,13 +502,16 @@ async fn run_event_lifecycle_probe_once(root : String, timeout : Int) -> Unit { ///| async fn run_event_lifecycle_probe(root : String, timeout : Int) -> Unit { + let output_root = @fs.tmpdir(prefix="proton-e2e-lifecycle-") + defer remove_dev_extension_tree(output_root) + build_scenario_app_executable(root, output_root) let mut attempt = 0 while attempt < 2 { let succeeded = try { run_e2e_operation_with_timeout( timeout, "40_event_broadcast event lifecycle", - () => run_event_lifecycle_probe_once(root, timeout), + () => run_event_lifecycle_probe_once(root, output_root, timeout), ) true } catch { diff --git a/e2e/test/scenario_validation_wbtest.mbt b/e2e/test/scenario_validation_wbtest.mbt index 8defad43..0902cff4 100644 --- a/e2e/test/scenario_validation_wbtest.mbt +++ b/e2e/test/scenario_validation_wbtest.mbt @@ -105,14 +105,9 @@ test "Unix E2E cancellation targets the isolated process group first" { #cfg(not(platform="windows")) async test "Unix E2E cancellation terminates a real child process tree" { let root = repository_root() - let pid_path = join_path( - root, - "target/proton-e2e-cancel-child-" + choose_cdp_port().to_string() + ".pid", - ) - @mbfs.remove_file(pid_path) catch { - _ => () - } - defer (@mbfs.remove_file(pid_path) catch { _ => () }) + let output_root = @fs.tmpdir(prefix="proton-e2e-cancel-") + defer remove_dev_extension_tree(output_root) + let pid_path = join_path(output_root, "child.pid") @async.with_task_group(group => { let (program, args) = isolated_app_command("/bin/sh", [ "-c", "sleep 30 & child=$!; printf '%s' \"$child\" > \"$1\"; while :; do sleep 30; done", diff --git a/examples/44_project_config/main.mbt b/examples/44_project_config/main.mbt index bc97114e..ff3d654c 100644 --- a/examples/44_project_config/main.mbt +++ b/examples/44_project_config/main.mbt @@ -85,11 +85,7 @@ fn main { let bundle = require_some(config.bundle(), "bundle") assert_bool(bundle.active(), true, "bundle.active") assert_strings(bundle.targets(), ["app", "zip"], "bundle.targets") - assert_path_suffix( - bundle.output(), - "44_project_config/target/proton-dist", - "bundle.output", - ) + assert_path_suffix(bundle.output(), "44_project_config/dist", "bundle.output") match config.dev_manifest().manifest().entry { @manifest.AppEntry::Url(url) => diff --git a/examples/44_project_config/proton.project.json b/examples/44_project_config/proton.project.json index 1c575419..5ebfa90f 100644 --- a/examples/44_project_config/proton.project.json +++ b/examples/44_project_config/proton.project.json @@ -32,6 +32,6 @@ "resources": [ "resources/**" ], - "output": "target/proton-dist" + "output": "dist" } } diff --git a/examples/46_asset_sidecar_resources/commands.mbt b/examples/46_asset_sidecar_resources/commands.mbt index e184c810..98f2147b 100644 --- a/examples/46_asset_sidecar_resources/commands.mbt +++ b/examples/46_asset_sidecar_resources/commands.mbt @@ -41,12 +41,6 @@ async fn add_later(request : AddRequest) -> Int { #proton.command(contract=report_probe_command) fn report_probe(request : ReportProbeRequest) -> Unit { println("[asset-sidecar] " + request.report) - @mbfs.create_dir("target") catch { - _ => () - } - @mbfs.write_string_to_file("target/asset-sidecar.probe.json", request.report) catch { - _ => () - } } ///| diff --git a/examples/46_asset_sidecar_resources/moon.pkg b/examples/46_asset_sidecar_resources/moon.pkg index 123f6d0d..1b9cd07f 100644 --- a/examples/46_asset_sidecar_resources/moon.pkg +++ b/examples/46_asset_sidecar_resources/moon.pkg @@ -1,5 +1,4 @@ import { - "moonbitlang/x/fs" @mbfs, "moonbitlang/core/json", "moonbitlang/async", "moonbit-community/proton/extension" @proton_extension, diff --git a/examples/47_dev_extension_js/proton.project.json b/examples/47_dev_extension_js/proton.project.json index b4e61182..d71a5be5 100644 --- a/examples/47_dev_extension_js/proton.project.json +++ b/examples/47_dev_extension_js/proton.project.json @@ -12,6 +12,10 @@ "value": "frontend/dist/index.html" }, "debug": true, + "backend": { + "path": "..", + "package": "47_dev_extension_js" + }, "frontend": { "dev_url": "http://127.0.0.1:5173", "path": "frontend", @@ -25,6 +29,6 @@ "app", "zip" ], - "output": "target/proton-dist" + "output": "dist" } } diff --git a/examples/app_commands_fixture/app_commands.mbt b/examples/app_commands_fixture/app_commands.mbt index 3bed9ad7..a75c4e74 100644 --- a/examples/app_commands_fixture/app_commands.mbt +++ b/examples/app_commands_fixture/app_commands.mbt @@ -99,12 +99,6 @@ fn fail(payload : FailPayload) -> String raise AppCommandError { ///| fn report_probe(payload : ReportProbePayload) -> ReportProbeReply { println("[app-commands] " + payload.report) - @mbfs.create_dir("target") catch { - _ => () - } - @mbfs.write_string_to_file("target/app-commands.probe.json", payload.report) catch { - _ => () - } ReportProbeReply::{ ok: true } } diff --git a/examples/app_commands_fixture/moon.pkg b/examples/app_commands_fixture/moon.pkg index bb58ee10..07c3b3d9 100644 --- a/examples/app_commands_fixture/moon.pkg +++ b/examples/app_commands_fixture/moon.pkg @@ -1,5 +1,4 @@ import { - "moonbitlang/x/fs" @mbfs, "moonbitlang/core/json", "moonbitlang/async", "moonbit-community/proton/extension" @proton_extension, diff --git a/examples/e2e_fixtures/asset_sidecar.mbt b/examples/e2e_fixtures/asset_sidecar.mbt index c4b8b7ee..662d6442 100644 --- a/examples/e2e_fixtures/asset_sidecar.mbt +++ b/examples/e2e_fixtures/asset_sidecar.mbt @@ -52,15 +52,6 @@ fn asset_sidecar_extension() -> @proton_extension.Extension { }) registrar.bind(asset_sidecar_report_command, (_context, payload) => { println("[asset-sidecar-e2e] " + payload.report) - @mbfs.create_dir("target") catch { - _ => () - } - @mbfs.write_string_to_file( - "target/asset-sidecar-e2e.probe.json", - payload.report, - ) catch { - _ => () - } }) }, ) diff --git a/examples/e2e_fixtures/web_contents_view.mbt b/examples/e2e_fixtures/web_contents_view.mbt index e63e3971..6e565646 100644 --- a/examples/e2e_fixtures/web_contents_view.mbt +++ b/examples/e2e_fixtures/web_contents_view.mbt @@ -228,12 +228,15 @@ async fn run_web_contents_view_pump( ignore(wakeup.wait()) } } - let events_path = match @env.get_env_var("PROTON_NATIVE_LOG") { - Some(native_log) => native_log + ".view-events" - None => "target/proton-e2e-view-events.json" - } - @mbfs.write_string_to_file(events_path, view_events.join("\n")) catch { - _ => () + match @env.get_env_var("PROTON_NATIVE_LOG") { + Some(native_log) => + @mbfs.write_string_to_file( + native_log + ".view-events", + view_events.join("\n"), + ) catch { + _ => () + } + None => () } Ok(()) } diff --git a/examples/moon.mod b/examples/moon.mod index 13614c00..1e1f7aa0 100644 --- a/examples/moon.mod +++ b/examples/moon.mod @@ -24,12 +24,12 @@ rule(name: "embed", command: ":embed -i $input -o $output") rule( name: "proton_codegen", - command: "moon -C \"$mod_dir/../codegen\" run --target-dir \"$mod_dir/target/proton-codegen-moon\" --target wasm . -- -C \"$mod_dir\" \"$input\" -o \"$output\"", + command: "moon -C \"$mod_dir/../codegen\" run --target-dir \"$mod_dir/../codegen/_build\" --target wasm . -- -C \"$mod_dir\" \"$input\" -o \"$output\"", ) rule( name: "proton_extension_identity_codegen", - command: "moon -C \"$mod_dir/../codegen\" run --target-dir \"$mod_dir/target/proton-codegen-moon\" --target wasm . -- -C \"$mod_dir\" --extension-identity \"$input\" -o \"$output\"", + command: "moon -C \"$mod_dir/../codegen\" run --target-dir \"$mod_dir/../codegen/_build\" --target wasm . -- -C \"$mod_dir\" --extension-identity \"$input\" -o \"$output\"", ) source = "" diff --git a/native/scripts/verify_link_config.mjs b/native/scripts/verify_link_config.mjs index 1b50ab74..4ed30bd5 100644 --- a/native/scripts/verify_link_config.mjs +++ b/native/scripts/verify_link_config.mjs @@ -106,23 +106,6 @@ if (!linkFlags.includes(expectedLinkNeedle)) { ); } -if (process.platform === "darwin") { - const packageRpath = "@executable_path/../Resources/proton/lib"; - const packageConfig = readLinkConfig({ - env: { - PROTON_NATIVE_DIST: dist, - PROTON_PACKAGE_RPATH: packageRpath, - }, - }); - const packageFlags = packageConfig?.vars?.PROTON_NATIVE_LINK_FLAGS ?? ""; - if (!packageFlags.includes(packageRpath)) { - fail(`package rpath is missing from native link flags: ${packageFlags}`); - } - if (packageFlags.includes(`-rpath,\"${normalizedForLink(libDir)}\"`)) { - fail(`package link flags retain the absolute runtime rpath: ${packageFlags}`); - } -} - if (fs.existsSync(developmentDist)) { const isolatedCwd = fs.mkdtempSync( path.join(os.tmpdir(), "proton-link-config-"), diff --git a/proton/bootstrap/config_paths_wbtest.mbt b/proton/bootstrap/config_paths_wbtest.mbt index 089faa9c..bb655e9a 100644 --- a/proton/bootstrap/config_paths_wbtest.mbt +++ b/proton/bootstrap/config_paths_wbtest.mbt @@ -3,7 +3,7 @@ test "runtime config rebases portable paths into host path components" { let entry = build_app_entry_from_parts( "asset", "frontend/dist/index.html", - Some("target/proton-runtime-config"), + Some("fixtures/proton-runtime-config"), ) match entry { Asset(path) => { diff --git a/proton/bootstrap/config_test.mbt b/proton/bootstrap/config_test.mbt index 289600f5..f0d35f4b 100644 --- a/proton/bootstrap/config_test.mbt +++ b/proton/bootstrap/config_test.mbt @@ -51,17 +51,12 @@ fn[T] expect_bootstrap_error( ///| /// Writes a minimal proton.project.json with one extra top-level field and checks the error. fn expect_project_extra_field_error( + root : String, key : String, value : Json, expected : String, ) -> Unit { - @mbfs.create_dir("target") catch { - _ => () - } - @mbfs.create_dir("target/proton-extra-field") catch { - _ => () - } - let config_path = "target/proton-extra-field/proton.project.json" + let config_path = root + "/proton.project.json" let config : Json = { "window": { "title": "Invalid", "width": 900, "height": 700 }, "entry": { "kind": "html", "value": "" }, @@ -104,19 +99,14 @@ test "load_config_manifest_from_file rebases file entry and bool debug" { } ///| -test "load_config_manifest_from_file accepts explicit config filenames" { +async test "load_config_manifest_from_file accepts explicit config filenames" { let loaded = load_config_manifest_from_file( manifest_test_path("proton.project.json"), ) assert_eq(loaded.manifest().debug, 1) - @mbfs.create_dir("target") catch { - _ => () - } - @mbfs.create_dir("target/proton-alt-config") catch { - _ => () - } - let config_path = "target/proton-alt-config/proton.dev" + let root = @async_fs.tmpdir(prefix="proton-alt-config-") + let config_path = root + "/proton.dev" @mbfs.write_string_to_file( config_path, ( @@ -148,14 +138,9 @@ test "load_config_manifest_from_file accepts explicit config filenames" { } ///| -test "load_config_manifest_from_file rejects extension settings" { - @mbfs.create_dir("target") catch { - _ => () - } - @mbfs.create_dir("target/proton-unsupported") catch { - _ => () - } - let config_path = "target/proton-unsupported/proton.project.json" +async test "load_config_manifest_from_file rejects extension settings" { + let root = @async_fs.tmpdir(prefix="proton-unsupported-config-") + let config_path = root + "/proton.project.json" @mbfs.write_string_to_file( config_path, ( @@ -186,39 +171,37 @@ test "load_config_manifest_from_file rejects extension settings" { } ///| -test "load_config_manifest_from_file rejects unsupported top-level fields" { +async test "load_config_manifest_from_file rejects unsupported top-level fields" { + let root = @async_fs.tmpdir(prefix="proton-extra-field-") expect_project_extra_field_error( - "version", "1.0.0", "Unexpected key 'version' found in proton.project.json.", + root, "version", "1.0.0", "Unexpected key 'version' found in proton.project.json.", ) expect_project_extra_field_error( - "license", "MIT", "Unexpected key 'license' found in proton.project.json.", + root, "license", "MIT", "Unexpected key 'license' found in proton.project.json.", ) expect_project_extra_field_error( - "description", "App", "Unexpected key 'description' found in proton.project.json.", + root, "description", "App", "Unexpected key 'description' found in proton.project.json.", ) expect_project_extra_field_error( + root, "permissions", {}, "proton.project.json.permissions must be an array", ) expect_project_extra_field_error( - "debug", 1, "proton.project.json.debug must be a bool", + root, "debug", 1, "proton.project.json.debug must be a bool", ) } ///| -test "load_proton_project_config_from_file decodes tooling blocks and moon.mod metadata" { - @mbfs.create_dir("target") catch { - _ => () - } - @mbfs.create_dir("target/proton-project-config") catch { - _ => () - } - @mbfs.create_dir("target/proton-project-config/backend") catch { +async test "load_proton_project_config_from_file decodes tooling blocks and moon.mod metadata" { + let root = @async_fs.tmpdir(prefix="proton-project-config-") + let backend = root + "/backend" + @mbfs.create_dir(backend) catch { _ => () } @mbfs.write_string_to_file( - "target/proton-project-config/backend/moon.mod", + backend + "/moon.mod", ( #|name = "vendor/app" #|version = "1.2.3" @@ -230,7 +213,7 @@ test "load_proton_project_config_from_file decodes tooling blocks and moon.mod m ) catch { err => abort(@debug.render(Repr(err))) } - let config_path = "target/proton-project-config/proton.project.json" + let config_path = root + "/proton.project.json" @mbfs.write_string_to_file( config_path, ( @@ -301,7 +284,7 @@ test "load_proton_project_config_from_file decodes tooling blocks and moon.mod m #| "role": "Editor" #| } #| ], - #| "output": "target/proton-dist" + #| "output": "custom-dist" #| } #|} ), @@ -323,10 +306,9 @@ test "load_proton_project_config_from_file decodes tooling blocks and moon.mod m match project.backend() { None => abort("expected backend config") Some(backend) => { - assert_true( - normalize_test_slashes(backend.path()).has_suffix( - "target/proton-project-config/backend", - ), + assert_eq( + normalize_test_slashes(backend.path()), + normalize_test_slashes(root + "/backend"), ) assert_eq(backend.app_package(), "app") } @@ -336,15 +318,13 @@ test "load_proton_project_config_from_file decodes tooling blocks and moon.mod m None => abort("expected frontend config") Some(frontend) => { @debug.assert_eq(frontend.dev_url(), Some("http://localhost:5173")) - assert_true( - normalize_test_slashes(frontend.dist().unwrap()).has_suffix( - "target/proton-project-config/frontend/dist", - ), + assert_eq( + normalize_test_slashes(frontend.dist().unwrap()), + normalize_test_slashes(root + "/frontend/dist"), ) - assert_true( - normalize_test_slashes(frontend.path().unwrap()).has_suffix( - "target/proton-project-config/frontend", - ), + assert_eq( + normalize_test_slashes(frontend.path().unwrap()), + normalize_test_slashes(root + "/frontend"), ) @debug.assert_eq(frontend.before_dev(), Some("npm run dev")) @debug.assert_eq(frontend.before_build(), Some("npm run build")) @@ -356,29 +336,25 @@ test "load_proton_project_config_from_file decodes tooling blocks and moon.mod m Some(bundle) => { assert_true(bundle.active()) @debug.assert_eq(bundle.targets(), ["app", "zip"]) - assert_true( - normalize_test_slashes(bundle.icon()[0]).has_suffix( - "target/proton-project-config/icons/icon.ico", - ), + assert_eq( + normalize_test_slashes(bundle.icon()[0]), + normalize_test_slashes(root + "/icons/icon.ico"), ) - assert_true( - normalize_test_slashes(bundle.resources()[0]).has_suffix( - "target/proton-project-config/resources/**", - ), + assert_eq( + normalize_test_slashes(bundle.resources()[0]), + normalize_test_slashes(root + "/resources/**"), ) match bundle.sign() { Some(sign) => - assert_true( - normalize_test_slashes(sign.binaries()[0]).has_suffix( - "target/proton-project-config/bin/worker", - ), + assert_eq( + normalize_test_slashes(sign.binaries()[0]), + normalize_test_slashes(root + "/bin/worker"), ) None => fail("expected bundle sign config") } - assert_true( - normalize_test_slashes(bundle.output()).has_suffix( - "target/proton-project-config/target/proton-dist", - ), + assert_eq( + normalize_test_slashes(bundle.output()), + normalize_test_slashes(root + "/custom-dist"), ) let targets = bundle.targets() @@ -416,20 +392,17 @@ test "load_proton_project_config_from_file decodes tooling blocks and moon.mod m } else { abort("unexpected project summary JSON") } - assert_true( - normalize_test_slashes(entry_path).has_suffix( - "target/proton-project-config/frontend/dist/index.html", - ), + assert_eq( + normalize_test_slashes(entry_path), + normalize_test_slashes(root + "/frontend/dist/index.html"), ) - assert_true( - normalize_test_slashes(binary_path).has_suffix( - "target/proton-project-config/bin/worker", - ), + assert_eq( + normalize_test_slashes(binary_path), + normalize_test_slashes(root + "/bin/worker"), ) - assert_true( - normalize_test_slashes(output).has_suffix( - "target/proton-project-config/target/proton-dist", - ), + assert_eq( + normalize_test_slashes(output), + normalize_test_slashes(root + "/custom-dist"), ) let loaded_summary = load_proton_project_summary_from_file(config_path) @@ -462,14 +435,9 @@ test "proton project dev_manifest keeps entry when dev_url is absent" { } ///| -test "load_proton_project_config_from_file validates tooling blocks" { - @mbfs.create_dir("target") catch { - _ => () - } - @mbfs.create_dir("target/proton-project-invalid") catch { - _ => () - } - let config_path = "target/proton-project-invalid/proton.project.json" +async test "load_proton_project_config_from_file validates tooling blocks" { + let root = @async_fs.tmpdir(prefix="proton-project-invalid-") + let config_path = root + "/proton.project.json" @mbfs.write_string_to_file( config_path, ( @@ -503,14 +471,9 @@ test "load_proton_project_config_from_file validates tooling blocks" { } ///| -test "load_config_manifest_from_file validates project tooling blocks" { - @mbfs.create_dir("target") catch { - _ => () - } - @mbfs.create_dir("target/proton-config-manifest-invalid") catch { - _ => () - } - let config_path = "target/proton-config-manifest-invalid/proton.project.json" +async test "load_config_manifest_from_file validates project tooling blocks" { + let root = @async_fs.tmpdir(prefix="proton-manifest-invalid-") + let config_path = root + "/proton.project.json" @mbfs.write_string_to_file( config_path, ( @@ -544,15 +507,10 @@ test "load_config_manifest_from_file validates project tooling blocks" { } ///| -test "load_proton_project_config_from_file preserves active bundle without release metadata" { - @mbfs.create_dir("target") catch { - _ => () - } - @mbfs.create_dir("target/proton-project-bundle-metadata") catch { - _ => () - } +async test "load_proton_project_config_from_file preserves active bundle without release metadata" { + let root = @async_fs.tmpdir(prefix="proton-bundle-metadata-") @mbfs.write_string_to_file( - "target/proton-project-bundle-metadata/moon.mod", + root + "/moon.mod", ( #|name = "vendor/app" #|version = "1.0.0" @@ -563,7 +521,7 @@ test "load_proton_project_config_from_file preserves active bundle without relea err => abort(@debug.render(Repr(err))) } - let missing_identifier = "target/proton-project-bundle-metadata/missing-identifier.proton.project.json" + let missing_identifier = root + "/missing-identifier.proton.project.json" @mbfs.write_string_to_file( missing_identifier, ( @@ -594,14 +552,9 @@ test "load_proton_project_config_from_file preserves active bundle without relea } ///| -test "load_proton_project_config_from_file validates bundle targets" { - @mbfs.create_dir("target") catch { - _ => () - } - @mbfs.create_dir("target/proton-project-bundle-targets") catch { - _ => () - } - let supported_target = "target/proton-project-bundle-targets/supported.proton.project.json" +async test "load_proton_project_config_from_file validates bundle targets" { + let root = @async_fs.tmpdir(prefix="proton-bundle-targets-") + let supported_target = root + "/supported.proton.project.json" @mbfs.write_string_to_file( supported_target, ( @@ -631,7 +584,7 @@ test "load_proton_project_config_from_file validates bundle targets" { let supported = load_proton_project_config_from_file(supported_target) assert_eq(supported.bundle().unwrap().targets(), ["app", "zip", "dmg"]) - let unsupported_target = "target/proton-project-bundle-targets/unsupported.proton.project.json" + let unsupported_target = root + "/unsupported.proton.project.json" @mbfs.write_string_to_file( unsupported_target, ( @@ -661,7 +614,7 @@ test "load_proton_project_config_from_file validates bundle targets" { }) assert_eq(error.message(), "unsupported bundle.targets: msi") - let duplicate_target = "target/proton-project-bundle-targets/duplicate.proton.project.json" + let duplicate_target = root + "/duplicate.proton.project.json" @mbfs.write_string_to_file( duplicate_target, ( diff --git a/proton/bootstrap/moon.pkg b/proton/bootstrap/moon.pkg index 2478d005..8855adde 100644 --- a/proton/bootstrap/moon.pkg +++ b/proton/bootstrap/moon.pkg @@ -8,6 +8,11 @@ import { "moonbit-community/proton/manifest", } +import { + "moonbitlang/async", + "moonbitlang/async/fs" @async_fs, +} for "test" + warnings = "-29" supported_targets = "native" diff --git a/proton/bootstrap/proton_project_config.mbt b/proton/bootstrap/proton_project_config.mbt index 787a8fb4..0b1d2fcc 100644 --- a/proton/bootstrap/proton_project_config.mbt +++ b/proton/bootstrap/proton_project_config.mbt @@ -159,7 +159,7 @@ pub fn ProtonBundleConfig::new( sign? : ProtonSignConfig? = None, url_schemes? : Array[String] = [], document_types? : Array[@proton_config.ProjectDocumentType] = [], - output? : String = "target/proton-dist", + output? : String = "dist", ) -> ProtonBundleConfig { ProtonBundleConfig::{ active, diff --git a/proton/catalog/catalog_test.mbt b/proton/catalog/catalog_test.mbt index b6139ad8..337f62d5 100644 --- a/proton/catalog/catalog_test.mbt +++ b/proton/catalog/catalog_test.mbt @@ -110,61 +110,17 @@ test "discover_extension_descriptor_files can target one extension package" { ///| test "load_extension_descriptor_from_file requires proton.ext.json" { + let path = "fixtures/rejected/extension.json" let error = expect_descriptor_error(() => { - load_extension_descriptor_from_file("target/rejected/extension.json") + load_extension_descriptor_from_file(path) }) - assert_eq( - error, - ExtensionDescriptorError::InvalidMetadataFile( - path="target/rejected/extension.json", - ), - ) + assert_eq(error, ExtensionDescriptorError::InvalidMetadataFile(path~)) } ///| test "decode proton.ext.json infers MoonBit import path from nearest moon.mod" { - @mbfs.create_dir("target") catch { - _ => () - } - @mbfs.create_dir("target/proton-ext-module") catch { - _ => () - } - @mbfs.create_dir("target/proton-ext-module/fs") catch { - _ => () - } - @mbfs.write_string_to_file( - "target/proton-ext-module/moon.mod", - ( - #|name = "vendor/exts" - #|version = "2.3.4" - #|description = "Vendor extension pack" - #|license = "MIT" - #|repository = "https://example.com/vendor/exts" - #|readme = "README.md" - #|keywords = [ "desktop", "extension" ] - #| - ), - encoding="utf8", - ) catch { - err => abort(@debug.render(Repr(err))) - } - let metadata_path = "target/proton-ext-module/fs/proton.ext.json" - @mbfs.write_string_to_file( - metadata_path, - ( - #|{ - #| "id": "vendor/fs", - #| "namespace": "fs", - #| "package": "@vendor/proton-fs", - #| "dependencies": [ - #| "vendor/path" - #| ] - #|} - ), - encoding="utf8", - ) catch { - err => abort(@debug.render(Repr(err))) - } + let root = catalog_test_path("testdata/proton-ext-module") + let metadata_path = root + "/fs/proton.ext.json" let descriptor = load_extension_descriptor_from_file(metadata_path) assert_eq(descriptor.id(), "vendor/fs") assert_eq(descriptor.version(), "2.3.4") @@ -180,39 +136,15 @@ test "decode proton.ext.json infers MoonBit import path from nearest moon.mod" { @debug.assert_eq(descriptor.publish_package(), Some("@vendor/proton-fs")) @debug.assert_eq(descriptor.dependencies(), ["vendor/path"]) - let descriptor_paths = discover_extension_descriptor_files([ - "target/proton-ext-module/fs", - ]) + let descriptor_paths = discover_extension_descriptor_files([root + "/fs"]) assert_eq(descriptor_paths.length(), 1) assert_true(descriptor_paths[0].has_suffix("proton.ext.json")) } ///| test "decode proton.ext.json rejects invalid metadata" { - @mbfs.create_dir("target") catch { - _ => () - } - @mbfs.create_dir("target/proton-ext-invalid") catch { - _ => () - } - - let unsupported_platform = "target/proton-ext-invalid/unsupported-platform.proton.ext.json" - @mbfs.write_string_to_file( - unsupported_platform, - ( - #|{ - #| "id": "vendor/fs", - #| "namespace": "fs", - #| "platforms": [ - #| "windows", - #| "ios" - #| ] - #|} - ), - encoding="utf8", - ) catch { - err => abort(@debug.render(Repr(err))) - } + let root = catalog_test_path("testdata/proton-ext-invalid") + let unsupported_platform = root + "/unsupported-platform.proton.ext.json" let error = expect_descriptor_error(() => { load_extension_descriptor_from_file(unsupported_platform) }) @@ -224,22 +156,7 @@ test "decode proton.ext.json rejects invalid metadata" { ), ) - let self_dependency = "target/proton-ext-invalid/self-dependency.proton.ext.json" - @mbfs.write_string_to_file( - self_dependency, - ( - #|{ - #| "id": "vendor/fs", - #| "namespace": "fs", - #| "dependencies": [ - #| "vendor/fs" - #| ] - #|} - ), - encoding="utf8", - ) catch { - err => abort(@debug.render(Repr(err))) - } + let self_dependency = root + "/self-dependency.proton.ext.json" let error = expect_descriptor_error(() => { load_extension_descriptor_from_file(self_dependency) }) @@ -251,20 +168,7 @@ test "decode proton.ext.json rejects invalid metadata" { ), ) - let removed_field = "target/proton-ext-invalid/removed-field.proton.ext.json" - @mbfs.write_string_to_file( - removed_field, - ( - #|{ - #| "id": "vendor/fs", - #| "namespace": "fs", - #| "spec_symbol": "extension" - #|} - ), - encoding="utf8", - ) catch { - err => abort(@debug.render(Repr(err))) - } + let removed_field = root + "/removed-field.proton.ext.json" let error = expect_descriptor_error(() => { load_extension_descriptor_from_file(removed_field) }) diff --git a/proton/catalog/testdata/proton-ext-invalid/removed-field.proton.ext.json b/proton/catalog/testdata/proton-ext-invalid/removed-field.proton.ext.json new file mode 100644 index 00000000..d312a9f8 --- /dev/null +++ b/proton/catalog/testdata/proton-ext-invalid/removed-field.proton.ext.json @@ -0,0 +1,5 @@ +{ + "id": "vendor/fs", + "namespace": "fs", + "spec_symbol": "extension" +} diff --git a/proton/catalog/testdata/proton-ext-invalid/self-dependency.proton.ext.json b/proton/catalog/testdata/proton-ext-invalid/self-dependency.proton.ext.json new file mode 100644 index 00000000..592fb7ca --- /dev/null +++ b/proton/catalog/testdata/proton-ext-invalid/self-dependency.proton.ext.json @@ -0,0 +1,7 @@ +{ + "id": "vendor/fs", + "namespace": "fs", + "dependencies": [ + "vendor/fs" + ] +} diff --git a/proton/catalog/testdata/proton-ext-invalid/unsupported-platform.proton.ext.json b/proton/catalog/testdata/proton-ext-invalid/unsupported-platform.proton.ext.json new file mode 100644 index 00000000..8be68ebc --- /dev/null +++ b/proton/catalog/testdata/proton-ext-invalid/unsupported-platform.proton.ext.json @@ -0,0 +1,8 @@ +{ + "id": "vendor/fs", + "namespace": "fs", + "platforms": [ + "windows", + "ios" + ] +} diff --git a/proton/catalog/testdata/proton-ext-module/fs/proton.ext.json b/proton/catalog/testdata/proton-ext-module/fs/proton.ext.json new file mode 100644 index 00000000..ac905e27 --- /dev/null +++ b/proton/catalog/testdata/proton-ext-module/fs/proton.ext.json @@ -0,0 +1,8 @@ +{ + "id": "vendor/fs", + "namespace": "fs", + "package": "@vendor/proton-fs", + "dependencies": [ + "vendor/path" + ] +} diff --git a/proton/catalog/testdata/proton-ext-module/moon.mod b/proton/catalog/testdata/proton-ext-module/moon.mod new file mode 100644 index 00000000..0cdc0a1f --- /dev/null +++ b/proton/catalog/testdata/proton-ext-module/moon.mod @@ -0,0 +1,7 @@ +name = "vendor/exts" +version = "2.3.4" +description = "Vendor extension pack" +license = "MIT" +repository = "https://example.com/vendor/exts" +readme = "README.md" +keywords = [ "desktop", "extension" ] diff --git a/proton/facade_wbtest.mbt b/proton/facade_wbtest.mbt index cfa12e9b..73c86db3 100644 --- a/proton/facade_wbtest.mbt +++ b/proton/facade_wbtest.mbt @@ -1143,12 +1143,6 @@ test "entry HTML injection does not install the bridge" { ///| fn write_facade_dev_config(path : String, dev_url? : String) -> Unit { - @mbfs.create_dir("target") catch { - _ => () - } - @mbfs.create_dir("target/proton-facade-dev") catch { - _ => () - } let config : Json = { "window": { "title": "Dev Test", "width": 320, "height": 240 }, "entry": { "kind": "asset", "value": "app.html" }, @@ -1179,8 +1173,9 @@ fn restore_env_var(name : String, value : String?) -> Unit { } ///| -test "implicit config path honors PROTON_CONFIG_PATH" { - let config_path = "target/proton-facade-dev/implicit-env.proton.project.json" +async test "implicit config path honors PROTON_CONFIG_PATH" { + let root = @async_fs.tmpdir(prefix="proton-facade-config-") + let config_path = root + "/implicit-env.proton.project.json" write_facade_dev_config(config_path) let old_config = @env.get_env_var("PROTON_CONFIG_PATH") defer restore_env_var("PROTON_CONFIG_PATH", old_config) @@ -1204,8 +1199,9 @@ test "packaged config takes precedence over environment and working directory" { } ///| -test "default config API honors PROTON_CONFIG_PATH" { - let config_path = "target/proton-facade-dev/config-api-env.proton.project.json" +async test "default config API honors PROTON_CONFIG_PATH" { + let root = @async_fs.tmpdir(prefix="proton-facade-config-") + let config_path = root + "/config-api-env.proton.project.json" write_facade_dev_config(config_path) let old_config = @env.get_env_var("PROTON_CONFIG_PATH") let old_dev = @env.get_env_var("PROTON_DEV") @@ -1224,9 +1220,10 @@ test "default config API honors PROTON_CONFIG_PATH" { } ///| -test "config API keeps non-default paths explicit" { - let config_path = "target/proton-facade-dev/explicit.proton.project.json" - let env_path = "target/proton-facade-dev/explicit-env.proton.project.json" +async test "config API keeps non-default paths explicit" { + let root = @async_fs.tmpdir(prefix="proton-facade-config-") + let config_path = root + "/explicit.proton.project.json" + let env_path = root + "/explicit-env.proton.project.json" write_facade_dev_config(config_path) write_facade_dev_config(env_path) let old_config = @env.get_env_var("PROTON_CONFIG_PATH") @@ -1237,8 +1234,9 @@ test "config API keeps non-default paths explicit" { } ///| -test "config API overrides manifest fields and keeps base dir" { - let config_path = "target/proton-facade-dev/api-overrides.proton.project.json" +async test "config API overrides manifest fields and keeps base dir" { + let root = @async_fs.tmpdir(prefix="proton-facade-config-") + let config_path = root + "/api-overrides.proton.project.json" write_facade_dev_config(config_path) let old_dev = @env.get_env_var("PROTON_DEV") let old_mode = @env.get_env_var("PROTON_MODE") @@ -1273,8 +1271,9 @@ test "config API overrides manifest fields and keeps base dir" { } ///| -test "dev manifest uses env override or frontend dev_url" { - let override_path = "target/proton-facade-dev/env-override.proton.project.json" +async test "dev manifest uses env override or frontend dev_url" { + let root = @async_fs.tmpdir(prefix="proton-facade-config-") + let override_path = root + "/env-override.proton.project.json" write_facade_dev_config(override_path, dev_url="http://localhost:5173") let old_dev = @env.get_env_var("PROTON_DEV") let old_url = @env.get_env_var("PROTON_FRONTEND_URL") @@ -1292,7 +1291,7 @@ test "dev manifest uses env override or frontend dev_url" { _ => fail("expected dev URL entry") } - let config_path = "target/proton-facade-dev/config-dev-url.proton.project.json" + let config_path = root + "/config-dev-url.proton.project.json" write_facade_dev_config(config_path, dev_url="http://localhost:5173") @env.unset_env_var("PROTON_FRONTEND_URL") let config_manifest = unwrap_configuration(() => { @@ -1340,11 +1339,9 @@ test "single instance builder validates and resolves an inline identity" { } ///| -test "launch activation classifies URLs and existing documents" { - let document = "target/proton-facade-activation.txt" - @mbfs.create_dir("target") catch { - _ => () - } +async test "launch activation classifies URLs and existing documents" { + let root = @async_fs.tmpdir(prefix="proton-facade-activation-") + let document = root + "/activation.txt" @mbfs.write_string_to_file(document, "activation", encoding="utf8") catch { error => abort(@debug.render(Repr(error))) } @@ -1353,11 +1350,7 @@ test "launch activation classifies URLs and existing documents" { assert_false(launch_argument_is_url("custom:value", ["other"])) assert_true(launch_argument_is_document(document, ["txt"])) assert_false(launch_argument_is_document(document, ["md"])) - assert_false( - launch_argument_is_document("target/proton-facade-activation-missing.txt", [ - "txt", - ]), - ) + assert_false(launch_argument_is_document(root + "/missing.txt", ["txt"])) } ///| diff --git a/proton/moon.pkg b/proton/moon.pkg index d4f6495f..9e57f90e 100644 --- a/proton/moon.pkg +++ b/proton/moon.pkg @@ -21,6 +21,7 @@ import { } import { + "moonbitlang/async/fs" @async_fs, "moonbit-community/proton_contract", } for "wbtest" diff --git a/proton/native_link_config.mjs b/proton/native_link_config.mjs index a98558a2..52c80575 100644 --- a/proton/native_link_config.mjs +++ b/proton/native_link_config.mjs @@ -110,16 +110,15 @@ function linuxRpathLinkFlag(binDir, cc) { return cc.length > 0 ? `-Wl,-rpath-link,${quote(binDir)}` : ""; } -function linkFlags(dist, cc, rpathOverride) { +function linkFlags(dist, cc) { const libDir = path.join(dist, "lib"); const binDir = path.join(dist, "bin"); if (process.platform === "win32") { return quote(path.join(libDir, "proton.lib")); } if (process.platform === "darwin") { - const rpath = rpathOverride.length > 0 ? rpathOverride : libDir; return appendFlags( - `-L${quote(libDir)} -lproton -Wl,-rpath,${quote(rpath)}`, + `-L${quote(libDir)} -lproton -Wl,-rpath,${quote(libDir)}`, darwinWarningFlags(cc), ); } @@ -129,7 +128,7 @@ function linkFlags(dist, cc, rpathOverride) { ); } -function linkConfig(dist, packageName, cc, rpathOverride) { +function linkConfig(dist, packageName, cc) { const libDir = path.join(dist, "lib"); const binDir = path.join(dist, "bin"); if (process.platform === "win32") { @@ -144,7 +143,7 @@ function linkConfig(dist, packageName, cc, rpathOverride) { link_flags: process.platform === "darwin" ? appendFlags( - `-L${quote(libDir)} -Wl,-rpath,${quote(rpathOverride.length > 0 ? rpathOverride : libDir)}`, + `-L${quote(libDir)} -Wl,-rpath,${quote(libDir)}`, darwinWarningFlags(cc), ) : appendFlags( @@ -163,19 +162,18 @@ function helperPath(binDir) { export function createNativeLinkConfig(env = readPayloadEnv()) { const rawDist = envValue(env, "PROTON_NATIVE_DIST").trim(); const cc = envValue(env, "MOON_CC").trim(); - const rpathOverride = envValue(env, "PROTON_PACKAGE_RPATH").trim(); const dist = path.resolve(rawDist.length === 0 ? defaultDist() : rawDist); const binDir = path.join(dist, "bin"); return { vars: { - PROTON_NATIVE_LINK_FLAGS: linkFlags(dist, cc, rpathOverride), + PROTON_NATIVE_LINK_FLAGS: linkFlags(dist, cc), PROTON_NATIVE_STUB_CC_FLAGS: "", PROTON_NATIVE_RUNTIME_DIR: nativeLinkPath(binDir), PROTON_RUNTIME_ROOT: nativeLinkPath(dist), PROTON_HELPER_PATH: helperPath(binDir), }, link_configs: [ - linkConfig(dist, "moonbit-community/proton/native", cc, rpathOverride), + linkConfig(dist, "moonbit-community/proton/native", cc), ], }; } diff --git a/scripts/e2e_scaffold_source_smoke.mjs b/scripts/e2e_scaffold_source_smoke.mjs index a2ae3f45..2fb24fcb 100644 --- a/scripts/e2e_scaffold_source_smoke.mjs +++ b/scripts/e2e_scaffold_source_smoke.mjs @@ -241,8 +241,7 @@ function connectLocalSourceModules() { function verifyPackagedApp() { const appDir = path.join( projectDir, - "target", - "proton-dist", + "dist", "Todo E2E.app", ); assert(fs.existsSync(appDir), `packaged app is missing: ${appDir}`); diff --git a/scripts/macos_package_smoke.mjs b/scripts/macos_package_smoke.mjs index f4dd8f90..b9cb03f7 100644 --- a/scripts/macos_package_smoke.mjs +++ b/scripts/macos_package_smoke.mjs @@ -12,7 +12,15 @@ const exampleName = "47_dev_extension_js"; const productName = "Proton Dev Extension JS"; const executableName = "proton-dev-extension-js"; const exampleDir = path.join(repoRoot, "examples", exampleName); -const distDir = path.join(exampleDir, "target", "proton-dist"); +const exampleModule = fs.readFileSync( + path.join(repoRoot, "examples", "moon.mod"), + "utf8", +); +const exampleVersion = exampleModule.match(/^version\s*=\s*"([^"]+)"/m)?.[1]; +if (!exampleVersion) { + throw new Error("examples/moon.mod is missing version"); +} +const distDir = path.join(exampleDir, "dist"); const appPath = path.join(distDir, `${productName}.app`); const archivePath = `${appPath}.zip`; const helperNames = [ @@ -317,8 +325,8 @@ function verifyBundle() { const infoPlist = path.join(contents, "Info.plist"); const plistExpectations = new Map([ ["CFBundleIdentifier", "com.justjavac.proton.dev-extension-js"], - ["CFBundleShortVersionString", "0.1.0"], - ["CFBundleVersion", "0.1.0"], + ["CFBundleShortVersionString", exampleVersion], + ["CFBundleVersion", exampleVersion], ]); for (const [key, expected] of plistExpectations) { const actual = run( diff --git a/scripts/windows_package_smoke.ps1 b/scripts/windows_package_smoke.ps1 index 6fd2ade7..fe4e45e1 100644 --- a/scripts/windows_package_smoke.ps1 +++ b/scripts/windows_package_smoke.ps1 @@ -7,7 +7,7 @@ $ExampleName = "47_dev_extension_js" $ProductName = "Proton Dev Extension JS" $ExecutableName = "proton-dev-extension-js" $ExampleDir = Join-Path $RepoRoot (Join-Path "examples" $ExampleName) -$DistDir = Join-Path $ExampleDir "target\proton-dist" +$DistDir = Join-Path $ExampleDir "dist" $PortableDir = Join-Path $DistDir $ExecutableName $ArchivePath = Join-Path $DistDir ($ExecutableName + ".zip") $CdpTimeoutSeconds = 45 From 49529c2728bc18fbb436e98161e38ac084152376 Mon Sep 17 00:00:00 2001 From: Milky2018 <842376130@qq.com> Date: Thu, 13 Aug 2026 12:01:10 +0800 Subject: [PATCH 2/2] fix(e2e): use configured backend package --- e2e/test/dev_extension_process_wbtest.mbt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/e2e/test/dev_extension_process_wbtest.mbt b/e2e/test/dev_extension_process_wbtest.mbt index 3a8c6df4..cd74b928 100644 --- a/e2e/test/dev_extension_process_wbtest.mbt +++ b/e2e/test/dev_extension_process_wbtest.mbt @@ -261,8 +261,6 @@ async fn spawn_dev_extension_cli_app( "dev", "--config", "examples/47_dev_extension_js/proton.project.json", - "--package", - ".", "--moon-target-dir", dev_extension_app_target_dir(output_root, "dev", cdp_port), "--url", @@ -374,8 +372,6 @@ async fn build_dev_extension_frontend( "build", "--config", "examples/47_dev_extension_js/proton.project.json", - "--package", - ".", "--moon-target-dir", app_target_dir, ],