feat(v3): add experimental migrate command to CLI - #5895
Conversation
… migration wails3 migrate -d ./myv2project -o ./myv3project converts a Wails v2 project into a v3 project: - Parses wails.json and the declarative options.App literal passed to wails.Run (syntax-only, no module downloads needed) and generates an equivalent programmatic main.go via application.New() + app.Window.NewWithOptions(), preserving user code and comments through textual surgery on just the wails.Run statement and imports. - Maps v2 options to v3 (window geometry, start state, background colour, platform-specific options, single instance, asset server), converts Bind entries into v3 services and bridges the OnStartup/OnDomReady/OnShutdown/OnBeforeClose lifecycle callbacks. - Adds pkg/v2compat/runtime: the v2 runtime API (context-first functions) implemented on the v3 application API, so migrated code only needs its import path rewritten. Covers events, window control, dialogs, clipboard, browser, screens, logging and app control; every function documents its v3 replacement for incremental migration. - Migrates the frontend: regenerates wailsjs/ as a compatibility layer over @wailsio/runtime (runtime shim + Call.ByName binding shims from the parsed bound-struct methods) and adds the npm dependency. - Scaffolds the v3 build system via the init machinery: Taskfile, build/ assets and build/config.yml populated from the v2 metadata (product info, file associations, protocols, bundle id kept as com.wails.<name> so the app keeps its identity). - Transforms go.mod (wails/v2 -> wails/v3, go directive raised), preserving all other requires. - Writes MIGRATION.md documenting every mapped option and any manual steps (menus, custom loggers, EnumBind, ...). Docs: the v2-to-v3 migration guide now leads with the automated path.
The migrate command now announces its experimental status in the CLI output, the generated MIGRATION.md and the migration guide, with a pointer to the issue tracker for reports and contributions.
The bridge is no longer a public package in the v3 module: its source lives at internal/migrate/v2compat/runtime (compiled, vetted and tested in-repo but not importable) and wails3 migrate copies it into the output project as <module>/v2compat/runtime with a generated-code header explaining it is temporary. This means only migrated projects carry the v2-style API - nobody can adopt it for new code - and there is no sunset obligation on the v3 module: each project deletes its own bridge functions as call sites are ported to the v3 API, and removes the package when nothing imports it. Import rewriting now targets the project-local path, and the bridge is only emitted when the project actually needs it (v2 runtime imports or lifecycle hooks).
…at layers Half-migrated code helps nobody, and compatibility shims invite new code onto the old API. The migrate command now draws a hard line: Migrated fully (deterministic): - project scaffold, Taskfile, build assets, config.yml from wails.json - main.go rewritten around application.New()/NewWithOptions with the options mapped; lifecycle hooks wired natively (ApplicationStarted event, WindowRuntimeReady event, Options.OnShutdown, ShouldQuit) - go.mod v2 -> v3; frontend copied with @wailsio/runtime added Documented instead of migrated: - every call into the v2 runtime package, listed in MIGRATION.md by file:line with its concrete v3 replacement; the sources are copied untouched, so the compiler points at exactly the listed locations until they are ported - every frontend wailsjs import, with the @wailsio/runtime equivalent and the generate-bindings workflow; the generated wailsjs directory is not carried over (it is v2 build output and cannot work with v3) Removed: the v2compat runtime bridge and the generated wailsjs shims. go mod tidy is skipped (with a warning) while v2 call sites remain, since tidying would re-add the v2 dependency and let old calls compile only to fail at runtime; MIGRATION.md spells this out.
WalkthroughThis PR adds an experimental ChangesWails v2-to-v3 migration assistant
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant CLI as wails3 migrate
participant Parser as ParseV2Project
participant Mapper as MapOptions
participant Generator as GenerateMain
participant Report as Report
CLI->>Parser: parse v2 project source
Parser->>Mapper: pass parsed V2Project
Mapper->>Generator: pass V3Options
Generator->>Report: report manual steps and mapped options
Report->>CLI: render MIGRATION.md
Possibly related issues
Possibly related PRs
Suggested labels: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 golangci-lint (2.12.2)level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies" Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Adds an experimental wails3 migrate command to the v3 CLI that parses a Wails v2 project (wails.json, go.mod, and the wails.Run(&options.App{...}) site), scaffolds an equivalent v3 project into a separate output directory, and generates a MIGRATION.md checklist for manual follow-up work (notably remaining v2 runtime/wailsjs call sites).
Changes:
- Introduces a new
v3/internal/migrateengine (parse + map + generate + report) with unit and end-to-end tests. - Wires the new
migratecommand into the v3 CLI, adds flags, and implements project scaffolding/output behavior. - Updates docs (CLI reference, migration guide, blog post) to document the experimental migration workflow and reporting guidance.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| v3/internal/migrate/wailsjson.go | Parses v2 wails.json into a minimal config model with defaults used by the migrator. |
| v3/internal/migrate/report.go | Builds MIGRATION.md summarizing automatic mappings and manual porting steps. |
| v3/internal/migrate/parse.go | Syntax-parses a v2 module, locates wails.Run, discovers bound services, and enumerates call sites. |
| v3/internal/migrate/migrate.go | Defines core migrator data structures (V2Project, MainInfo, bound types). |
| v3/internal/migrate/migrate_test.go | Unit tests covering parsing, mapping, main generation, go.mod rewriting, and frontend migration behavior. |
| v3/internal/migrate/mapping.go | Maps v2 options.App fields to v3 application.Options / WebviewWindowOptions equivalents and records manual steps. |
| v3/internal/migrate/maingen.go | Rewrites main.go by replacing imports and the wails.Run(...) statement with v3 equivalents, then formats/prunes imports. |
| v3/internal/migrate/gomod.go | Rewrites go.mod from v2 to v3, bumps the Go directive minimum, and removes v2-specific replaces. |
| v3/internal/migrate/frontend.go | Copies frontend sources while excluding v2-generated wailsjs, ensures dist/ exists, and injects @wailsio/runtime. |
| v3/internal/migrate/copy.go | Copies remaining project files (excluding frontend/build and regenerated files) into the output directory. |
| v3/internal/migrate/advisor.go | Collects v2 runtime and wailsjs import call sites with concrete v3 migration advice. |
| v3/internal/flags/migrate.go | Adds CLI flags for wails3 migrate. |
| v3/internal/commands/migrate.go | Implements the CLI command orchestration (parsing, scaffolding, writing output, report generation, optional tidy). |
| v3/internal/commands/migrate_test.go | End-to-end test ensuring expected migrated outputs are generated and v2 artifacts are excluded. |
| v3/cmd/wails3/main.go | Registers the migrate subcommand in the CLI. |
| docs/src/content/docs/reference/cli.mdx | Documents the new wails3 migrate command in the CLI reference table. |
| docs/src/content/docs/migration/v2-to-v3.mdx | Adds an “Automated Migration (Experimental)” section and related guidance. |
| docs/src/content/docs/guides/cli.mdx | Adds a CLI guide section documenting the migrate workflow and reporting expectations. |
| docs/src/content/docs/blog/2026-08-02-wails-v3-beta.md | Updates the beta announcement to mention the experimental migration assistant. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| name := "" | ||
| if imp.Name != nil { | ||
| name = imp.Name.Name | ||
| } else { | ||
| name = path[strings.LastIndex(path, "/")+1:] | ||
| } |
| ast.Inspect(mainFile, func(n ast.Node) bool { | ||
| if typeName != "" { | ||
| return false | ||
| } | ||
| assign, ok := n.(*ast.AssignStmt) | ||
| if !ok || len(assign.Lhs) != 1 || len(assign.Rhs) != 1 { | ||
| return true | ||
| } | ||
| lhs, ok := assign.Lhs[0].(*ast.Ident) | ||
| if !ok || lhs.Name != e.Name { | ||
| return true | ||
| } | ||
| typeName = resolveElementType(fset, files, mainFile, assign.Rhs[0]) | ||
| return false | ||
| }) |
| if dependenciesRe.MatchString(content) { | ||
| content = dependenciesRe.ReplaceAllString(content, "$1\n \"@wailsio/runtime\": \"latest\",") | ||
| } else if idx := strings.Index(content, "{"); idx >= 0 { | ||
| content = content[:idx+1] + "\n \"dependencies\": {\n \"@wailsio/runtime\": \"latest\"\n }," + content[idx+1:] | ||
| } else { | ||
| proj.Report.Manual("frontend/package.json", "Could not add the `@wailsio/runtime` dependency automatically; add it and run your package manager's install.") | ||
| return nil | ||
| } |
| // Frontend: copy, regenerate wailsjs as a compatibility layer, add the | ||
| // @wailsio/runtime dependency. |
| for _, cs := range r.callSites { | ||
| sb.WriteString(cs + "\n") | ||
| } |
There was a problem hiding this comment.
Actionable comments posted: 17
🧹 Nitpick comments (10)
v3/internal/migrate/mapping.go (1)
336-341: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRecord a manual step when
Debugis not a literal.If
Debugis not a composite literal, or the literal has noOpenInspectorOnStartupkey, the option is dropped silently. The user gets no entry inMIGRATION.md. Add anm.manual("Debug", ...)call in the non-literal case.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@v3/internal/migrate/mapping.go` around lines 336 - 341, Update the “Debug” handling around compositeLit and litFields so a manual migration step is recorded when Debug is not a composite literal or lacks OpenInspectorOnStartup. Preserve the existing m.carry call when that key is present, and add m.manual("Debug", ...) only for the unsupported or missing-key path.v3/internal/migrate/maingen.go (1)
271-293: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueImport pruning drops the spec but keeps its comment.
The removal span covers only
imp.Pos()toimp.End(). A line comment or doc comment attached to that import stays in the file. The result is a dangling comment inside the import block. Extend the span to the spec'sDocandCommentpositions, or rebuild the import block from the AST withprinterinstead of cutting byte ranges.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@v3/internal/migrate/maingen.go` around lines 271 - 293, Update the import-pruning logic in the loop over gen.Specs to include comments attached to each ast.ImportSpec when calculating the removal span. Extend the start and end positions using imp.Doc and imp.Comment so associated comments are removed with the unused import, while preserving the existing handling for used and side-effect imports.docs/src/content/docs/blog/2026-08-02-wails-v3-beta.md (1)
137-141: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMatch the version casing used in this file.
Lines 124-135 use
v3andv2. The new paragraph usesV3andV2. Use the lowercase form for consistency.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/src/content/docs/blog/2026-08-02-wails-v3-beta.md` around lines 137 - 141, Update the migration assistant paragraph to use lowercase v3 and v2 consistently, including the project description, checklist reference, and API calls.v3/internal/commands/migrate.go (3)
106-107: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUpdate the stale comment.
The comment says the frontend step regenerates
wailsjsas a compatibility layer. The final behavior dropswailsjsand only copies the frontend plus adds the@wailsio/runtimedependency.TestMigrateFrontendasserts thatfrontend/wailsjsdoes not exist.♻️ Proposed fix
- // Frontend: copy, regenerate wailsjs as a compatibility layer, add the - // `@wailsio/runtime` dependency. + // Frontend: copy the sources (the v2 wailsjs output is dropped) and add + // the `@wailsio/runtime` dependency.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@v3/internal/commands/migrate.go` around lines 106 - 107, Update the frontend migration comment near TestMigrateFrontend to remove the claim that wailsjs is regenerated or retained as a compatibility layer; describe only copying the frontend and adding the `@wailsio/runtime` dependency, matching the asserted absence of frontend/wailsjs.
309-315: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winReport the icon copy only when the write succeeds.
Line 312 discards the
os.WriteFileerror. The note at Line 313 then tells the user thatbuild/appicon.pngwas copied even when the write failed. Check the error and warn instead.♻️ Proposed fix
if _, err := os.Stat(srcIcon); err == nil { data, err := os.ReadFile(srcIcon) if err == nil { - _ = os.WriteFile(filepath.Join(outDir, "build", "appicon.png"), data, 0o644) - proj.Report.Note("Copied build/appicon.png from the v2 project. Run `wails3 task common:generate:icons` to regenerate the platform icon files (icons.icns / icon.ico) from it.") + if werr := os.WriteFile(filepath.Join(outDir, "build", "appicon.png"), data, 0o644); werr != nil { + term.Warningf("Could not copy build/appicon.png (%v) - copy it manually.\n", werr) + } else { + proj.Report.Note("Copied build/appicon.png from the v2 project. Run `wails3 task common:generate:icons` to regenerate the platform icon files (icons.icns / icon.ico) from it.") + } } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@v3/internal/commands/migrate.go` around lines 309 - 315, Update the icon-copy block in the migration flow to check the error returned by os.WriteFile before reporting success. Only call proj.Report.Note after writing build/appicon.png succeeds; when the write fails, issue a warning through the existing reporting mechanism instead.
190-203: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winRestore stdout and the working directory with
defer.
templates.Installmutates process-global state. The restore ofos.Stdoutat Line 197 and theos.Chdir(wd)at Line 201 only run on the normal path. Iftemplates.Installpanics, the process keeps writing to/dev/nulland stays in the deleted scratch directory.defermakes the restore unconditional.♻️ Proposed fix
term.DisableOutput() - stdout := os.Stdout - if devnull, dnErr := os.OpenFile(os.DevNull, os.O_WRONLY, 0); dnErr == nil { - os.Stdout = devnull - defer devnull.Close() - } - err = templates.Install(initFlags) - os.Stdout = stdout - if !quiet { - term.EnableOutput() - } - if chdirErr := os.Chdir(wd); chdirErr != nil { - return chdirErr - } + stdout := os.Stdout + if devnull, dnErr := os.OpenFile(os.DevNull, os.O_WRONLY, 0); dnErr == nil { + os.Stdout = devnull + defer devnull.Close() + } + defer func() { + os.Stdout = stdout + if !quiet { + term.EnableOutput() + } + _ = os.Chdir(wd) + }() + err = templates.Install(initFlags)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@v3/internal/commands/migrate.go` around lines 190 - 203, In the migration flow surrounding templates.Install, defer restoration of os.Stdout immediately after redirecting it and defer returning to wd before invoking the installer, so both process-global state and the working directory are restored during panics or early returns. Remove the corresponding normal-path restoration calls while preserving quiet-mode output handling and existing error propagation.v3/internal/migrate/migrate_test.go (1)
370-383: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRemove the dead sentinel in the assertion loop.
Line 375 adds
"`src/main.js` should not appear"to thewantlist. Lines 377-379 then skip that exact entry. The loop asserts nothing for it. Either drop the entry and the guard, or write the intended negative assertion explicitly.♻️ Proposed fix
for _, want := range []string{ "## Port these to the v3 API", "`app.go:", // Go call site location "`runtime.WindowSetTitle`", "window.SetTitle(title)", - "`src/main.js` should not appear", } { - if want == "`src/main.js` should not appear" { - continue - } if !strings.Contains(md, want) { t.Errorf("report missing %q\n---\n%s", want, md) } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@v3/internal/migrate/migrate_test.go` around lines 370 - 383, Remove the unused "`src/main.js` should not appear" entry and its special-case guard from the assertion loop in the migration test, or replace them with an explicit negative assertion that verifies `md` does not contain that string.docs/src/content/docs/guides/cli.mdx (1)
56-77: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a
#### Flagstable for consistency.Every other command on this page documents its flags in a table (
initat Line 23,devat Line 87,buildat Line 107). Themigratesection points at--helpinstead. The flag set is small and stable inv3/internal/flags/migrate.go.♻️ Proposed addition
```bash wails3 migrate -d /path/to/v2-project -o /path/to/v3-project
+#### Flags
+| Flag | Description | Default |
+|-------------------|------------------------------------------------|---------|
+|-d| Path to the Wails v2 project to migrate |.|
+|-o| Directory to write the migrated v3 project to | |
+|-f| Write into a non-empty output directory |false|
+|-q| Suppress output |false|
+|-skipgomodtidy| Skip go mod tidy |false|</details> <details> <summary>🤖 Prompt for AI Agents</summary>Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.In
@docs/src/content/docs/guides/cli.mdxaround lines 56 - 77, Add a “####
Flags” table to themigratedocumentation section, listing-d,-o,-f,
-q, and-skipgomodtidywith their descriptions and defaults consistent with
v3/internal/flags/migrate.go; retain the existing migration guidance and
--helpreference.</details> <!-- cr-comment:v1:ccbb6802bc24f525259c197d --> </blockquote></details> <details> <summary>v3/internal/commands/migrate_test.go (1)</summary><blockquote> `223-235`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _⚡ Quick win_ **Add the reverse containment case.** `TestMigrateRefusesNestedOutput` covers only `outputDir` inside `v2Dir`. Add a case where `outputDir` is the parent of `v2Dir`. That case currently passes the path check and can overwrite the v2 sources. See the related comment on `v3/internal/commands/migrate.go` Line 54. <details> <summary>🤖 Prompt for AI Agents</summary>Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.In
@v3/internal/commands/migrate_test.goaround lines 223 - 235, Add a
reverse-containment test alongside TestMigrateRefusesNestedOutput that sets
OutputDir to a parent of v2Dir, invokes Migrate with the same options, and
asserts the same “must not be inside” error. Ensure the test verifies Migrate
rejects output paths that contain V2Dir and protects the existing v2 sources.</details> <!-- cr-comment:v1:70ed96ad24765bbf30bcaad5 --> </blockquote></details> <details> <summary>v3/internal/migrate/report.go (1)</summary><blockquote> `72-82`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _⚡ Quick win_ **Render call sites in a stable order.** `callSites` is appended during `adviseGoRuntimeCalls` from `range files`, and `Markdown()` renders them in that order. Copy and sort the slice before rendering to keep `MIGRATION.md` deterministic for equivalent inputs. <details> <summary>Proposed change</summary> ```diff - for _, cs := range r.callSites { + callSites := append([]string(nil), r.callSites...) + sort.Strings(callSites) + for _, cs := range callSites { sb.WriteString(cs + "\n") }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@v3/internal/migrate/report.go` around lines 72 - 82, Update Markdown() to copy r.callSites into a separate slice, sort the copy before iterating, and render the sorted values. Preserve r.callSites itself unchanged while ensuring equivalent inputs produce deterministic MIGRATION.md output.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/src/content/docs/guides/cli.mdx`:
- Around line 75-76: Update the Wails Enhancement Proposal link in the migration
guidance near generated MIGRATION.md so it points to the existing
/tree/master/v3/wep path, preserving the surrounding text and link purpose.
In `@v3/internal/commands/migrate.go`:
- Around line 54-59: The output containment validation only rejects outputs
nested inside the source; make it reject both directions so the source project
remains unchanged. In v3/internal/commands/migrate.go lines 54-59, update the
guards around isSubPath to also reject isSubPath(outDir, proj.Dir) with an
appropriate error. In v3/internal/commands/migrate_test.go lines 223-235, extend
TestMigrateRefusesNestedOutput with a case using the parent of v2Dir as
OutputDir and assert the new error.
- Around line 358-387: Update the YAML generation in the file-associations and
protocols migration blocks to serialize every emitted scalar safely, including
Ext, Name, Description, IconName, Role, and Scheme; use the project’s YAML
marshaller or quote each value so characters such as :, #, leading -, and quotes
remain parseable. Omit optional empty descriptions instead of emitting
null-valued fields, and preserve the existing migration notes and structure.
In `@v3/internal/migrate/advisor.go`:
- Line 155: Update wailsjsImportRe and the associated FindStringSubmatch
scanning loop to recognize side-effect static imports and dynamic import(...)
expressions in addition to existing from/require forms. Iterate through each
line’s matches so every wailsjs import, including multiple imports on one line,
is reported in MIGRATION.md before continuing to the next line.
- Around line 193-209: Update the scanner setup in adviseFrontendImports to
configure an explicit buffer limit appropriate for supported frontend files,
allowing valid long source lines beyond bufio.MaxScanTokenSize while retaining a
bounded maximum. Add a regression fixture containing an overlong frontend line
and verify migration advice completes without scanner.Err().
- Around line 111-151: Extend adviseGoRuntimeCalls to detect dot-imported
V2RuntimeImport entries, retaining an *ast.Ident reference for the v2 runtime
package and recording runtime.Updates when such an import is present. During AST
inspection, also match unqualified ast.CallExpr calls against the known runtime
advice names and report them using the v2 runtime reference, while preserving
existing SelectorExpr handling for aliased or named imports.
In `@v3/internal/migrate/frontend.go`:
- Around line 69-92: The addRuntimeDependency function must update package.json
as structured JSON instead of using regex or global text checks, which can
create trailing commas or skip required changes. Unmarshal the document, inspect
only the root dependencies object for `@wailsio/runtime`, add the latest entry
when absent, and marshal it back while preserving the existing missing-file
behavior; add tests covering empty root and empty dependencies objects.
In `@v3/internal/migrate/gomod.go`:
- Around line 10-12: Update the minGoVersion constant to 1.25 so generated Wails
v3 projects declare the supported minimum Go version, and update the
corresponding expectation in the migration test covering the Go directive to
assert 1.25.
In `@v3/internal/migrate/maingen.go`:
- Around line 89-93: Deduplicate field names in MapOptions before they reach
generation, ensuring repeated keys such as BackgroundType and Assets are emitted
only once. Also update writeFields to track already-written f.Name values and
skip duplicates, while preserving the existing field order and formatting for
the first occurrence.
In `@v3/internal/migrate/mapping.go`:
- Around line 296-304: Update the Assets mapping cases and mapAssetServer to use
set semantics like BackgroundType, ensuring only one application.Options.Assets
field remains when multiple v2 asset fields are configured. When a later asset
value replaces an earlier one, record the corresponding overwrite in
m.proj.Report.
- Around line 460-469: Update v3/internal/migrate/mapping.go lines 460-469 to
replace existing BackgroundType fields when mapping WebviewIsTransparent and
WindowIsTranslucent, and apply the same replacement behavior to
Mac.WebviewIsTransparent at line 547; update v3/internal/migrate/mapping.go
lines 296-304 so Assets, AssetsHandler, and mapAssetServer at line 429 replace
the Assets field and record a report note when overwriting; update
v3/internal/migrate/maingen.go lines 89-93 in writeFields to skip field names
already written as a defensive duplicate-key guard.
- Around line 280-286: Update the Fullscreen handling branch in the migration
mapping logic to distinguish the literal false identifier from other non-true
values. Skip m.manual for Fullscreen: false, while preserving the existing
fullscreen mapping for true and manual guidance for genuinely non-constant
values.
In `@v3/internal/migrate/migrate_test.go`:
- Around line 359-361: Align the v3 migration minimum Go version with the
toolchain by changing minGoVersion in gomod.go to 1.25, updating the
migrate_test.go expectation from “go 1.24” to “go 1.25”, and synchronizing
IMPLEMENTATION.md with the same v3 Go version decision.
In `@v3/internal/migrate/parse.go`:
- Around line 148-239: Update findRunCall to collect every wails.Run candidate
instead of stopping at the first match, preserving each call’s enclosing
statement context; reject multiple candidates and unsupported contexts before
GenerateMain writes output. Detect the documented if err := wails.Run(...); err
!= nil form and set RunStmt to the complete *ast.IfStmt only when its error
behavior can be preserved, otherwise reject it, so GenerateMain replaces a valid
rewrite range. Add fixtures covering multiple calls and both assignment/if
forms.
- Around line 309-448: Update resolveElementType, constructorReturnType, and
collectMethods to resolve each Bind entry from the wails.Run file’s lexical
scope and retain its package identity. Handle package-level and local var
declarations while selecting the nearest valid declaration, and restrict
constructor and receiver-method lookup to the resolved package instead of
matching names across all parsed files. Add fixtures covering package-level
vars, local shadowing, and duplicate symbols in a subpackage.
- Around line 58-90: Update the source-file selection in the filepath.WalkDir
callback to honor Go build constraints using a defined build context, excluding
files and directories such as testdata that are not part of the selected project
build. If migration supports multiple platforms, process each applicable
constrained entrypoint; otherwise reject or omit non-selected variants before
populating files and writing output. Add fixtures covering mutually exclusive
platform files and testdata.
In `@v3/internal/migrate/report.go`:
- Around line 60-70: Update the next-steps rendering in the report builder
around r.callSites so the call-site migration step and its continuation lines
are emitted only when callSites is non-empty. Renumber the subsequent go mod
tidy, dev, and manual-steps items based on whether that conditional step was
included, while preserving the existing guidance text.
---
Nitpick comments:
In `@docs/src/content/docs/blog/2026-08-02-wails-v3-beta.md`:
- Around line 137-141: Update the migration assistant paragraph to use lowercase
v3 and v2 consistently, including the project description, checklist reference,
and API calls.
In `@docs/src/content/docs/guides/cli.mdx`:
- Around line 56-77: Add a “#### Flags” table to the `migrate` documentation
section, listing `-d`, `-o`, `-f`, `-q`, and `-skipgomodtidy` with their
descriptions and defaults consistent with `v3/internal/flags/migrate.go`; retain
the existing migration guidance and `--help` reference.
In `@v3/internal/commands/migrate_test.go`:
- Around line 223-235: Add a reverse-containment test alongside
TestMigrateRefusesNestedOutput that sets OutputDir to a parent of v2Dir, invokes
Migrate with the same options, and asserts the same “must not be inside” error.
Ensure the test verifies Migrate rejects output paths that contain V2Dir and
protects the existing v2 sources.
In `@v3/internal/commands/migrate.go`:
- Around line 106-107: Update the frontend migration comment near
TestMigrateFrontend to remove the claim that wailsjs is regenerated or retained
as a compatibility layer; describe only copying the frontend and adding the
`@wailsio/runtime` dependency, matching the asserted absence of frontend/wailsjs.
- Around line 309-315: Update the icon-copy block in the migration flow to check
the error returned by os.WriteFile before reporting success. Only call
proj.Report.Note after writing build/appicon.png succeeds; when the write fails,
issue a warning through the existing reporting mechanism instead.
- Around line 190-203: In the migration flow surrounding templates.Install,
defer restoration of os.Stdout immediately after redirecting it and defer
returning to wd before invoking the installer, so both process-global state and
the working directory are restored during panics or early returns. Remove the
corresponding normal-path restoration calls while preserving quiet-mode output
handling and existing error propagation.
In `@v3/internal/migrate/maingen.go`:
- Around line 271-293: Update the import-pruning logic in the loop over
gen.Specs to include comments attached to each ast.ImportSpec when calculating
the removal span. Extend the start and end positions using imp.Doc and
imp.Comment so associated comments are removed with the unused import, while
preserving the existing handling for used and side-effect imports.
In `@v3/internal/migrate/mapping.go`:
- Around line 336-341: Update the “Debug” handling around compositeLit and
litFields so a manual migration step is recorded when Debug is not a composite
literal or lacks OpenInspectorOnStartup. Preserve the existing m.carry call when
that key is present, and add m.manual("Debug", ...) only for the unsupported or
missing-key path.
In `@v3/internal/migrate/migrate_test.go`:
- Around line 370-383: Remove the unused "`src/main.js` should not appear" entry
and its special-case guard from the assertion loop in the migration test, or
replace them with an explicit negative assertion that verifies `md` does not
contain that string.
In `@v3/internal/migrate/report.go`:
- Around line 72-82: Update Markdown() to copy r.callSites into a separate
slice, sort the copy before iterating, and render the sorted values. Preserve
r.callSites itself unchanged while ensuring equivalent inputs produce
deterministic MIGRATION.md output.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: a7a7af04-289f-4fb2-981b-2693cce22560
📒 Files selected for processing (19)
docs/src/content/docs/blog/2026-08-02-wails-v3-beta.mddocs/src/content/docs/guides/cli.mdxdocs/src/content/docs/migration/v2-to-v3.mdxdocs/src/content/docs/reference/cli.mdxv3/cmd/wails3/main.gov3/internal/commands/migrate.gov3/internal/commands/migrate_test.gov3/internal/flags/migrate.gov3/internal/migrate/advisor.gov3/internal/migrate/copy.gov3/internal/migrate/frontend.gov3/internal/migrate/gomod.gov3/internal/migrate/maingen.gov3/internal/migrate/mapping.gov3/internal/migrate/migrate.gov3/internal/migrate/migrate_test.gov3/internal/migrate/parse.gov3/internal/migrate/report.gov3/internal/migrate/wailsjson.go
| generated `MIGRATION.md`. Proposals to change migration behavior belong in a | ||
| [Wails Enhancement Proposal](https://github.com/wailsapp/wails/tree/master/wep). |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check that the wep directory exists on the default branch.
gh api repos/wailsapp/wails/contents/wep --jq '.[0].name' || echo "wep directory not found"Repository: wailsapp/wails
Length of output: 328
🌐 Web query:
Wails WEP GitHub wailsapp/wails Wails Enhancement Proposal
💡 Result:
The Wails Enhancement Proposal (WEP) is the formal process used by the Wails project to propose, discuss, and implement new features or changes to public behavior in Wails v3 [1][2][3]. This process is designed to ensure that major new capabilities are well-documented, reviewed by the community and maintainers, and integrated in a structured manner [1]. Key aspects of the WEP process include: Process Overview: Contributors who wish to propose a new capability or a change to public behavior should submit a draft pull request (PR) titled [WEP] instead of creating a standard feature-request issue [1][3][4]. The WEP PR serves as the canonical venue for discussion, allowing for feedback and community support to be gathered in a single, searchable record [5]. Workflow: 1. Submission: The proposer submits a draft PR containing the proposal documentation (often using a standard WEP template) and any necessary supporting materials [1][5]. 2. Discussion: The community and maintainers review the PR. Proposers are encouraged to share their proposal on platforms like the Wails Discord to gather feedback and support [1][5]. 3. Decision: Wails maintainers make a final decision based on the proposal's merits and community feedback. Accepted proposals are assigned a WEP number, and the PR is merged [1]. 4. Implementation: Once accepted, the focus shifts to the implementation phase, which involves developing the feature, conducting code reviews, and finalizing documentation before the feature is merged into the project [1]. Documentation: The official documentation and templates for the WEP process can be found within the Wails repository at v3/wep/README.md [1]. It includes details such as a WEP Index, status definitions, and guidelines for when a WEP is required [5]. This process is a key component of the Wails v3 ecosystem, helping the project manage growth while maintaining compatibility and clear governance [3].
Citations:
- 1: https://github.com/wailsapp/wails/blob/3ec88c12/v3/wep/README.md
- 2: https://pkg.go.dev/github.com/wailsapp/wails
- 3: https://v3.wails.io/blog/wails-v3-beta/
- 4: https://www.reddit.com/r/golang/comments/1vdizu5/wails_v3_beta_released_a_new_foundation_for_go/
- 5: chore(.github): accept v3 bug reports, add triage dropdowns, wire up auto-labelers #5754
🌐 Web query:
github.com/wailsapp/wails/tree/master/v3/wep
💡 Result:
The directory github.com/wailsapp/wails/tree/master/v3/wep contains the documentation and framework for the Wails Enhancement Proposal (WEP) process [1][2]. This process is the official mechanism for proposing, discussing, and implementing new features or changes to public behavior in Wails v3 [2][3]. Key details of the WEP process include: Purpose: It replaces traditional feature-request issues for significant changes, ensuring a structured approach to evolving the Wails codebase [1][4]. Workflow: 1. Submission: Proposers document their idea by creating a directory in v3/wep/proposals/ and filling out the official WEP template (v3/wep/WEP_TEMPLATE.md) [2][5]. 2. Proposal Review: Proposers submit a draft pull request (PR) titled [WEP] <title> containing the proposal file to gather community feedback [2][4]. 3. Decision: The Wails maintainers evaluate the proposal based on community feedback and project merits [2]. If accepted, the proposal is assigned a WEP number and merged [2]. 4. Implementation: Once accepted, the focus shifts to development, which involves submitting PRs for the implementation and iterating based on reviews [2]. This process is designed to help maintain clarity and stability as Wails v3 evolves during its beta phase [3][4].
Citations:
- 1: https://github.com/wailsapp/wails
- 2: https://github.com/wailsapp/wails/blob/3ec88c12/v3/wep/README.md
- 3: https://v3.wails.io/blog/wails-v3-beta/
- 4: https://newreleases.io/project/github/wailsapp/wails/release/v3.0.0-beta.0
- 5: https://github.com/wailsapp/wails/blob/3ec88c12/v3/wep/WEP_TEMPLATE.md
Update the broken Wails Enhancement Proposal link.
https://github.com/wailsapp/wails/tree/master/wep returns a repository 404. The WEP documentation is under v3/wep, so update the links to point to the existing https://github.com/wailsapp/wails/tree/master/v3/wep path.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/src/content/docs/guides/cli.mdx` around lines 75 - 76, Update the Wails
Enhancement Proposal link in the migration guidance near generated MIGRATION.md
so it points to the existing /tree/master/v3/wep path, preserving the
surrounding text and link purpose.
| if isSubPath(proj.Dir, outDir) { | ||
| return fmt.Errorf("the output directory must not be inside the v2 project directory") | ||
| } | ||
| if entries, err := os.ReadDir(outDir); err == nil && len(entries) > 0 && !options.Force { | ||
| return fmt.Errorf("output directory %s is not empty (use -f to write into it anyway)", outDir) | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
One-directional output containment check. isSubPath(proj.Dir, outDir) rejects an output directory inside the v2 project, but it does not reject an output directory that contains the v2 project. With -f, the migration then writes over the v2 sources, which contradicts the documented promise that the source project stays unchanged.
v3/internal/commands/migrate.go#L54-L59: add a second guard,isSubPath(outDir, proj.Dir), that returns an error when the output directory contains the v2 project directory.v3/internal/commands/migrate_test.go#L223-L235: add a case toTestMigrateRefusesNestedOutputthat setsOutputDirto the parent ofv2Dirand asserts the new error.
📍 Affects 2 files
v3/internal/commands/migrate.go#L54-L59(this comment)v3/internal/commands/migrate_test.go#L223-L235
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@v3/internal/commands/migrate.go` around lines 54 - 59, The output containment
validation only rejects outputs nested inside the source; make it reject both
directions so the source project remains unchanged. In
v3/internal/commands/migrate.go lines 54-59, update the guards around isSubPath
to also reject isSubPath(outDir, proj.Dir) with an appropriate error. In
v3/internal/commands/migrate_test.go lines 223-235, extend
TestMigrateRefusesNestedOutput with a case using the parent of v2Dir as
OutputDir and assert the new error.
| if fas := proj.Config.Info.FileAssociations; len(fas) > 0 { | ||
| var sb strings.Builder | ||
| sb.WriteString("fileAssociations:\n") | ||
| for _, fa := range fas { | ||
| sb.WriteString(fmt.Sprintf(" - ext: %s\n", fa.Ext)) | ||
| sb.WriteString(fmt.Sprintf(" name: %s\n", fa.Name)) | ||
| sb.WriteString(fmt.Sprintf(" description: %s\n", fa.Description)) | ||
| sb.WriteString(fmt.Sprintf(" iconName: %s\n", fa.IconName)) | ||
| sb.WriteString(fmt.Sprintf(" role: %s\n", fa.Role)) | ||
| } | ||
| if fileAssociationsRe.MatchString(content) { | ||
| content = fileAssociationsRe.ReplaceAllString(content, strings.TrimSuffix(sb.String(), "\n")) | ||
| } else { | ||
| content += "\n" + sb.String() | ||
| } | ||
| proj.Report.Note("File associations were moved to `build/config.yml`. Copy their icons into `build/` (macOS: `<iconName>.icns`, Windows: `<iconName>.ico`).") | ||
| } | ||
|
|
||
| if protos := proj.Config.Info.Protocols; len(protos) > 0 { | ||
| var sb strings.Builder | ||
| sb.WriteString("\nprotocols:\n") | ||
| for _, p := range protos { | ||
| sb.WriteString(fmt.Sprintf(" - scheme: %s\n", p.Scheme)) | ||
| if p.Description != "" { | ||
| sb.WriteString(fmt.Sprintf(" description: %s\n", p.Description)) | ||
| } | ||
| } | ||
| content += sb.String() | ||
| proj.Report.Note("Custom protocol schemes were moved to `build/config.yml`.") | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Quote the emitted YAML scalars.
appendAssociations writes v2 values into build/config.yml as bare YAML scalars. A description, name or scheme from wails.json can contain :, #, a leading -, or a quote character. The generated config.yml then fails to parse. UpdateBuildAssets at Line 302 reads that same file, so the migration aborts on a valid v2 project. Empty optional fields also emit description: with a null value instead of being omitted.
Emit the block with a YAML marshaller, or quote each scalar.
🐛 Proposed fix (minimal: quote each value)
+// yamlString renders s as a double-quoted YAML scalar.
+func yamlString(s string) string {
+ b, err := yaml.Marshal(s)
+ if err != nil {
+ return strconv.Quote(s)
+ }
+ return strings.TrimSpace(string(b))
+} for _, fa := range fas {
- sb.WriteString(fmt.Sprintf(" - ext: %s\n", fa.Ext))
- sb.WriteString(fmt.Sprintf(" name: %s\n", fa.Name))
- sb.WriteString(fmt.Sprintf(" description: %s\n", fa.Description))
- sb.WriteString(fmt.Sprintf(" iconName: %s\n", fa.IconName))
- sb.WriteString(fmt.Sprintf(" role: %s\n", fa.Role))
+ sb.WriteString(fmt.Sprintf(" - ext: %s\n", yamlString(fa.Ext)))
+ sb.WriteString(fmt.Sprintf(" name: %s\n", yamlString(fa.Name)))
+ sb.WriteString(fmt.Sprintf(" description: %s\n", yamlString(fa.Description)))
+ sb.WriteString(fmt.Sprintf(" iconName: %s\n", yamlString(fa.IconName)))
+ sb.WriteString(fmt.Sprintf(" role: %s\n", yamlString(fa.Role)))
} for _, p := range protos {
- sb.WriteString(fmt.Sprintf(" - scheme: %s\n", p.Scheme))
+ sb.WriteString(fmt.Sprintf(" - scheme: %s\n", yamlString(p.Scheme)))
if p.Description != "" {
- sb.WriteString(fmt.Sprintf(" description: %s\n", p.Description))
+ sb.WriteString(fmt.Sprintf(" description: %s\n", yamlString(p.Description)))
}
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@v3/internal/commands/migrate.go` around lines 358 - 387, Update the YAML
generation in the file-associations and protocols migration blocks to serialize
every emitted scalar safely, including Ext, Name, Description, IconName, Role,
and Scheme; use the project’s YAML marshaller or quote each value so characters
such as :, #, leading -, and quotes remain parseable. Omit optional empty
descriptions instead of emitting null-valued fields, and preserve the existing
migration notes and structure.
| func adviseGoRuntimeCalls(fset *token.FileSet, files map[string]*ast.File, proj *V2Project) { | ||
| for path, file := range files { | ||
| localName := "" | ||
| for name, ipath := range importMap(file) { | ||
| if ipath == V2RuntimeImport { | ||
| localName = name | ||
| } | ||
| } | ||
| if localName == "" { | ||
| continue | ||
| } | ||
| rel, err := filepath.Rel(proj.Dir, path) | ||
| if err != nil { | ||
| rel = path | ||
| } | ||
| ast.Inspect(file, func(n ast.Node) bool { | ||
| sel, ok := n.(*ast.SelectorExpr) | ||
| if !ok { | ||
| return true | ||
| } | ||
| ident, ok := sel.X.(*ast.Ident) | ||
| if !ok || ident.Name != localName { | ||
| return true | ||
| } | ||
| name := sel.Sel.Name | ||
| advice, ok := goRuntimeAdvice[name] | ||
| if !ok { | ||
| advice, ok = windowRuntimeAdvice[name] | ||
| if ok { | ||
| advice += " - get the window with `app.Window.Current()` or keep a reference to the one you create" | ||
| } | ||
| } | ||
| if !ok { | ||
| // Type references (runtime.OpenDialogOptions{...}) and | ||
| // anything unknown. | ||
| advice = "see the v3 application API and https://v3.wails.io/migration/v2-to-v3/" | ||
| } | ||
| pos := fset.Position(sel.Pos()) | ||
| proj.Report.CallSite(fmt.Sprintf("%s:%d", rel, pos.Line), "`runtime."+name+"`", advice) | ||
| return true | ||
| }) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the current selector-only implementation and dot-import coverage.
rg -n -C 8 --type go \
'adviseGoRuntimeCalls|SelectorExpr|CallExpr|V2RuntimeImport' \
v3/internal/migrate/advisor.go
rg -n --type go \
'(import\s+\.\s*|^\s*\.\s*)"github\.com/wailsapp/wails/v2/pkg/runtime"' \
v3 || trueRepository: wailsapp/wails
Length of output: 1647
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect full advisor.go and related import-map behavior.
wc -l v3/internal/migrate/advisor.go
sed -n '1,220p' v3/internal/migrate/advisor.go
# Probe Go AST for dot-imported calls and selector calls in an isolated Go source.
if command -v go >/dev/null 2>&1; then
tmp="$(mktemp -d)"
cat > "$tmp/main.go" <<'EOF'
package main
import . "github.com/wailsapp/wails/v2/pkg/runtime"
func f(ctx Context) {
Quit(ctx)
Quit(ctx)
}
EOF
go parser "$tmp/main.go" 2>/tmp/parser_err.log || { echo "go parser unavailable"; cat /tmp/parser_err.log; }
# AST dump without installing repo package; if parser unavailable, use parser package source if present.
cat >/tmp/astprobe.go <<'EOF'
package main
import (
"fmt"
"go/parser"
"go/token"
)
func main() {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "m.go", `package main
import . "github.com/wailsapp/wails/v2/pkg/runtime"
func f(ctx Context) {
Quit(ctx)
runtime.Quit(ctx)
}`, parser.ParseComments)
if err != nil {
panic(err)
}
ast.Inspect(f, func(n interface{}) bool {
if call, ok := n.(*ast.CallExpr); ok {
fmt.Printf("CALL pos=%s callee=%T: ", fset.Position(call.Pos()), call.Fun)
switch fn := call.Fun.(type) {
case *ast.Ident:
fmt.Printf("Ident.Name=%s\n", fn.Name)
case *ast.SelectorExpr:
fmt.Printf("Ident.Name=%s Sel.Name=%s\n", fn.X.(*ast.Ident).Name, fn.Sel.Name)
default:
fmt.Printf("%#v\n", fn)
}
}
return true
})
}
EOF
cat >/tmp/astprobe_m.go <<'EOF'
package main
import "github.com/wailsapp/wails/v2/pkg/runtime"
func f(ctx runtime.Context) {
runtime.Quit(ctx)
}
EOF
cd "$tmp"
go run astprobe.go
fi
# Search for importMap and report implementation.
rg -n -C 5 'func importMap|MIGRATION|Report|CallSite' v3/internal/migrateRepository: wailsapp/wails
Length of output: 10219
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate and inspect the import-map implementation and any dot-import handling.
rg -n -C 10 'func importMap|importMap\s*\(' --type go v3 || true
# Locate migration guidance files that advise dot-import handling if present.
rg -n -C 8 'dot import|dot imported|import \.|"github\.com/wailsapp/wails/v2/pkg/runtime"|runtime\.' --type go . || true
# Read-only Go parser probe without running repo code, using vendor std only if present.
tmp="$(mktemp -d)"
cat > "$tmp/astprobe.go" <<'EOF'
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
)
func main() {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "m.go", `package main
import . "github.com/wailsapp/wails/v2/pkg/runtime"
func f(ctx Context) {
Quit(ctx)
runtime.Quit(ctx)
}
const _ = "github.com/wailsapp/wails/v2/pkg/runtime".(string)
var _ = "github.com/wailsapp/wails/v2/pkg/runtime".(Context)
`, 0)
if err != nil {
panic(err)
}
ast.Inspect(f, func(n ast.Node) bool {
switch node := n.(type) {
case *ast.CallExpr:
switch fn := node.Fun.(type) {
case *ast.Ident:
fmt.Printf("call identifier: %s\n", fn.Name)
case *ast.SelectorExpr:
fmt.Printf("call selector: package=%s function=%s\n", fn.X.(*ast.Ident).Name, fn.Sel.Name)
}
case *ast.CompositeLit:
switch typ := node.Type.(type) {
case *ast.SelectorExpr:
fmt.Printf("type selector: package=%s type=%s\n", typ.X.(*ast.Ident).Name, typ.Sel.Name)
case *ast.Ident:
fmt.Printf("type identifier: %s\n", typ.Name)
}
}
return true
})
}
EOF
if [ -f "$tmp/astprobe.go" ]; then
cd "$tmp"
GOPATH=/tmp/gopath GOFLAGS="-mod=vendor" go run astprobe.go || true
fiRepository: wailsapp/wails
Length of output: 50371
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show the targeted advisor output for selector-only AST nodes.
rg -n -C 4 'func adviseGoRuntimeCalls|func reportCall|CallSite|SelectorExpr' v3/internal/migrate/advisor.go
# Check tracked migration docs for dot-import coverage in GTK/WebKit contexts.
for f in IMPLEMENTATION.md m3/IMPLEMENTATION.md v3/IMPLEMENTATION.md MIGRATION.md README.md; do
if [ -f "$f" ]; then
echo "== $f =="
rg -n -C 4 'dot import|dot imported|import \.|"github\.com/wailsapp/wails/v2/pkg/runtime"|Quit\(ctx\)|runtime\.' "$f" || true
fi
done
# Precise search under migrate tests/fixtures for dot imports if present.
rg -n -C 3 --type go 'import\s+\.\s*"github\.com/wailsapp/wails/v2/pkg/runtime"|dot import|CallExpr\(' v3/internal/migrate || trueRepository: wailsapp/wails
Length of output: 1793
Handle dot-imported v2 runtime calls.
With import . "github.com/wailsapp/wails/v2/pkg/runtime", calls such as Quit(ctx) appear as unqualified ast.CallExpr nodes, but adviseGoRuntimeCalls only records ast.SelectorExpr. Add dot-import handling and track matching unqualified calls so generated migration guidance does not omit those sites. Include runtime.Updates when a dot import is present and build an *ast.Ident reference to the v2 runtime package.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@v3/internal/migrate/advisor.go` around lines 111 - 151, Extend
adviseGoRuntimeCalls to detect dot-imported V2RuntimeImport entries, retaining
an *ast.Ident reference for the v2 runtime package and recording runtime.Updates
when such an import is present. During AST inspection, also match unqualified
ast.CallExpr calls against the known runtime advice names and report them using
the v2 runtime reference, while preserving existing SelectorExpr handling for
aliased or named imports.
| } | ||
| } | ||
|
|
||
| var wailsjsImportRe = regexp.MustCompile(`(?:from\s*|require\s*\(\s*)['"]([^'"]*wailsjs/(runtime|go)/[^'"]*)['"]`) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect extractor behavior and locate frontend import fixtures.
rg -n -C 8 --type go \
'wailsjsImportRe|FindStringSubmatch|FindAllStringSubmatch|adviseFrontendImports' \
v3/internal/migrate/advisor.go
rg -n \
-g '*.js' -g '*.jsx' -g '*.ts' -g '*.tsx' -g '*.svelte' -g '*.vue' \
-g '*.html' -g '*.mjs' -g '*.cjs' \
'wailsjs/(runtime|go)/' v3 || trueRepository: wailsapp/wails
Length of output: 2087
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import re
wailsjsImportRe = re.compile(r"(?:from\s*|require\s*\(\s*)['"]([^'"]*wailsjs/(runtime|go)/[^'"]*)['"]")
cases = {
"named_static": "import foo from 'wailsjs/go/my/svc';",
"side_effect": "import 'wailsjs/go/my/svc';",
"dynamic": "import('wailsjs/go/my/svc')",
"require_static": "require('wailsjs/runtime/foo');",
"require_side_effect": "require('wailsjs/go/my/svc');",
"two_line_imports": "import 'a';from 'b';",
"two_matching_static": "import a from 'wailsjs/go/svc'; import b from 'wailsjs/go/svc2';",
"two_matching_sideeffect": "import 'wailsjs/runtime/svc'; import 'wailsjs/go/svc2';",
"single_named": "from 'wailsjs/runtime/foo';",
}
for name, line in cases.items():
m = wailsjsImportRe.match(line)
allm = wailsjsImportRe.findall(line)
print(f"{name}")
print(" line:", line)
print(" match:", m.groups() if m else None)
print(" all:", allm)
PY
printf '\nRelevant advisor.go slice:\n'
sed -n '155,220p' v3/internal/migrate/advisor.goRepository: wailsapp/wails
Length of output: 428
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf 'Relevant advisor.go slice:\n'
sed -n '155,220p' v3/internal/migrate/advisor.go
printf '\nBehavioral probe for the exact regex and reporter logic:\n'
python3 - <<'PY'
from typing import Tuple
import re
# Exact form found in source, with matching quote class as in the Go source.
wailsjsImportRe = re.compile(r"(?:from\s*|require\s*\(\s*)['"'"'"]([^'"'"'"]*wailsjs/(runtime|go)/[^'"'"'"]*)['"'"'"]")
cases = {
"static_named": "import foo from 'wailsjs/go/my/svc';",
"side_effect_static": "import 'wailsjs/go/my/svc';",
"dynamic": "import('wailsjs/go/my/svc')",
"require_static": "require('wailsjs/runtime/foo');",
"require_side_effect": "require('wailsjs/go/my/svc');",
"two_static_same_line": "import a from 'wailsjs/go/svc'; import b from 'wailsjs/go/svc2';",
"two_side_effect_same_line": "import 'wailsjs/runtime/svc'; import 'wailsjs/go/svc2';",
"single_static_like": "from 'wailsjs/runtime/foo';",
}
for name, line in cases.items():
first = wailsjsImportRe.search(line)
all_matches = wailsjsImportRe.findall(line)
reports = []
m = first
while True:
if m is None:
break
reports.append(m.group(1))
m = wailsjsImportRe.search(line, pos=m.end())
print(f"{name}:")
print(f" line: {line}")
print(f" first_report: {first.group(1) if first else None}")
print(f" find_all_capture_groups: {all_matches}")
print(f" all_reports_after_end_search: {reports}")
PYRepository: wailsapp/wails
Length of output: 4060
Scan every wailsjs import.
The regex only sees from ... and require(...) imports. Side-effect static imports like import 'wailsjs/go/svc'; and dynamic imports like import('wailsjs/go/svc') are skipped. Also, FindStringSubmatch + the current loop reports only the first match on a line, so later same-line imports are left out of MIGRATION.md. Use the required import predicates and report every match before scanning further.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@v3/internal/migrate/advisor.go` at line 155, Update wailsjsImportRe and the
associated FindStringSubmatch scanning loop to recognize side-effect static
imports and dynamic import(...) expressions in addition to existing from/require
forms. Iterate through each line’s matches so every wailsjs import, including
multiple imports on one line, is reported in MIGRATION.md before continuing to
the next line.
| if !strings.Contains(src, "go 1.24") { | ||
| t.Errorf("go directive not raised:\n%s", src) | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Show minGoVersion and the v3 module's own go directive.
rg -n -C 3 'minGoVersion' --glob '*.go'
fd -H -t f '^go.mod$' | while IFS= read -r f; do
echo "=== $f ==="
rg -n '^(go|toolchain) ' "$f"
doneRepository: wailsapp/wails
Length of output: 152
🏁 Script executed:
#!/bin/bash
set -u
echo "Repository tracked files containing migrate_test.go or migrate/gomod.go:"
git ls-files | rg '(^|/)(migrate_test\.go|gomod\.go)$|(^|/)v3/internal/migrate/' || true
echo
echo "Search for minGoVersion and go directive text:"
rg -n -C 3 'minGoVersion|go 1\.2[0-9]|go \d+\.\d+|toolchain' . --glob '*.go' --glob '*.md' --glob '*.txt' || true
echo
echo "go.mod files and directives:"
git ls-files | rg '(^|/)go\.mod$' | while IFS= read -r f; do
echo "=== $f ==="
rg -n '^(go|toolchain) ' "$f" || true
done
echo
echo "IMPLEMENTATION.md references to Go:"
if git ls-files | rg -q '(^|/)IMPLEMENTATION\.md$'; then
rg -n -C 2 -i 'go|wails v3|minimum|toolchain' IMPLEMENTATION.md
fiRepository: wailsapp/wails
Length of output: 48708
Align migration minimum Go version with v3 toolchain.
Set v3/internal/migrate/gomod.go minGoVersion to 1.25, update v3/internal/migrate/migrate_test.go, and keep IMPLEMENTATION.md synchronized with the v3 Go decision.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@v3/internal/migrate/migrate_test.go` around lines 359 - 361, Align the v3
migration minimum Go version with the toolchain by changing minGoVersion in
gomod.go to 1.25, updating the migrate_test.go expectation from “go 1.24” to “go
1.25”, and synchronizing IMPLEMENTATION.md with the same v3 Go version decision.
Source: Learnings
| // Parse all Go files in the module (excluding frontend, build dir and | ||
| // hidden/vendor directories). | ||
| fset := token.NewFileSet() | ||
| files := map[string]*ast.File{} // abs path -> file | ||
| err = filepath.WalkDir(absDir, func(path string, d fs.DirEntry, err error) error { | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if d.IsDir() { | ||
| name := d.Name() | ||
| if path != absDir && (strings.HasPrefix(name, ".") || name == "vendor" || name == "node_modules") { | ||
| return filepath.SkipDir | ||
| } | ||
| if path == proj.FrontendDir || path == filepath.Join(absDir, cfg.BuildDir) { | ||
| return filepath.SkipDir | ||
| } | ||
| return nil | ||
| } | ||
| if !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") { | ||
| return nil | ||
| } | ||
| file, perr := parser.ParseFile(fset, path, nil, parser.ParseComments|parser.SkipObjectResolution) | ||
| if perr != nil { | ||
| return fmt.Errorf("could not parse %s: %w", path, perr) | ||
| } | ||
| for _, imp := range file.Imports { | ||
| if imp.Path.Value == strconv.Quote(V2RuntimeImport) { | ||
| proj.UsesV2Runtime = true | ||
| } | ||
| } | ||
| files[path] = file | ||
| return nil | ||
| }) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Respect build constraints when selecting project files.
Line 62 walks every .go file in the tree. Line 76 only skips _test.go. This includes files disabled by //go:build and fixture trees such as testdata. A project with mutually exclusive platform entrypoints can trigger the false “more than one wails.Run call” failure. It can also resolve methods from files that cannot coexist in one target build.
Build the parsed source set from a defined Go build context. If cross-platform migration is supported, migrate each build-constrained entrypoint. If it is not supported, reject non-selected variants before writing output. Add fixtures for mutually exclusive platform files and testdata.
Based on learnings, platform-specific behavior must be controlled by build tags.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@v3/internal/migrate/parse.go` around lines 58 - 90, Update the source-file
selection in the filepath.WalkDir callback to honor Go build constraints using a
defined build context, excluding files and directories such as testdata that are
not part of the selected project build. If migration supports multiple
platforms, process each applicable constrained entrypoint; otherwise reject or
omit non-selected variants before populating files and writing output. Add
fixtures covering mutually exclusive platform files and testdata.
Source: Learnings
| // findRunCall looks for a statement of the form | ||
| // | ||
| // err := wails.Run(&options.App{...}) | ||
| // err = wails.Run(...) | ||
| // wails.Run(...) | ||
| // if err := wails.Run(...); err != nil { ... } | ||
| // | ||
| // where "wails" is the local name of the github.com/wailsapp/wails/v2 import. | ||
| func findRunCall(fset *token.FileSet, path string, file *ast.File) *MainInfo { | ||
| imports := importMap(file) | ||
| wailsName := "" | ||
| for name, ipath := range imports { | ||
| if ipath == "github.com/wailsapp/wails/v2" { | ||
| wailsName = name | ||
| } | ||
| } | ||
| if wailsName == "" { | ||
| return nil | ||
| } | ||
|
|
||
| info := &MainInfo{Path: path, File: file, Fset: fset, Imports: imports} | ||
|
|
||
| isRunCall := func(n ast.Node) *ast.CallExpr { | ||
| call, ok := n.(*ast.CallExpr) | ||
| if !ok { | ||
| return nil | ||
| } | ||
| sel, ok := call.Fun.(*ast.SelectorExpr) | ||
| if !ok || sel.Sel.Name != "Run" { | ||
| return nil | ||
| } | ||
| ident, ok := sel.X.(*ast.Ident) | ||
| if !ok || ident.Name != wailsName { | ||
| return nil | ||
| } | ||
| return call | ||
| } | ||
|
|
||
| ast.Inspect(file, func(n ast.Node) bool { | ||
| if info.RunCall != nil { | ||
| return false | ||
| } | ||
| stmt, ok := n.(ast.Stmt) | ||
| if !ok { | ||
| return true | ||
| } | ||
| switch s := stmt.(type) { | ||
| case *ast.AssignStmt: | ||
| if len(s.Rhs) != 1 { | ||
| return true | ||
| } | ||
| call := isRunCall(s.Rhs[0]) | ||
| if call == nil { | ||
| return true | ||
| } | ||
| info.RunStmt = s | ||
| info.RunCall = call | ||
| if len(s.Lhs) == 1 { | ||
| if ident, ok := s.Lhs[0].(*ast.Ident); ok { | ||
| info.ErrIdent = ident.Name | ||
| info.AssignTok = s.Tok | ||
| } | ||
| } | ||
| return false | ||
| case *ast.ExprStmt: | ||
| call := isRunCall(s.X) | ||
| if call == nil { | ||
| return true | ||
| } | ||
| info.RunStmt = s | ||
| info.RunCall = call | ||
| return false | ||
| } | ||
| return true | ||
| }) | ||
|
|
||
| if info.RunCall == nil { | ||
| return nil | ||
| } | ||
|
|
||
| // Extract the &options.App{...} literal. | ||
| if len(info.RunCall.Args) == 1 { | ||
| arg := info.RunCall.Args[0] | ||
| if unary, ok := arg.(*ast.UnaryExpr); ok && unary.Op == token.AND { | ||
| arg = unary.X | ||
| } | ||
| if lit, ok := arg.(*ast.CompositeLit); ok { | ||
| info.AppLit = lit | ||
| } | ||
| } | ||
| return info | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Collect a single rewriteable wails.Run site.
Line 187 stops traversal after the first match. The parser therefore hides a second wails.Run in the same file, so the multiple-call guard at Line 104 cannot reject it. It also records the inner assignment of the documented if err := wails.Run(...); err != nil form as RunStmt. GenerateMain replaces that inner range with a v3 application/window block, but an if initializer accepts only one simple statement. The output is invalid Go.
Collect all candidates with their enclosing statement context. Reject multiple or unsupported contexts before writing output. For the if form, rewrite the complete *ast.IfStmt only when its error behavior can be preserved. Add fixtures for both forms.
Based on learnings from the supplied downstream code, GenerateMain replaces the exact MainInfo.RunStmt range.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@v3/internal/migrate/parse.go` around lines 148 - 239, Update findRunCall to
collect every wails.Run candidate instead of stopping at the first match,
preserving each call’s enclosing statement context; reject multiple candidates
and unsupported contexts before GenerateMain writes output. Detect the
documented if err := wails.Run(...); err != nil form and set RunStmt to the
complete *ast.IfStmt only when its error behavior can be preserved, otherwise
reject it, so GenerateMain replaces a valid rewrite range. Add fixtures covering
multiple calls and both assignment/if forms.
| // resolveBoundTypes maps each element of the Bind slice literal to its struct | ||
| // type and collects the exported methods of that type from the parsed files. | ||
| func resolveBoundTypes(fset *token.FileSet, files map[string]*ast.File, proj *V2Project, bind ast.Expr) []*BoundType { | ||
| lit, ok := bind.(*ast.CompositeLit) | ||
| if !ok { | ||
| proj.Report.Manual("Bind", "The Bind value is not a slice literal, so bound structs could not be discovered. The generated frontend/wailsjs shims may be incomplete; check frontend imports against the v3 bindings generated into frontend/bindings.") | ||
| return nil | ||
| } | ||
|
|
||
| mainFile := files[proj.Main.Path] | ||
| var result []*BoundType | ||
| for _, elt := range lit.Elts { | ||
| expr := printExpr(fset, elt) | ||
| typeName := resolveElementType(fset, files, mainFile, elt) | ||
| bt := &BoundType{Expr: expr, Name: typeName} | ||
| if typeName == "" { | ||
| proj.Report.Manual("Bind: "+expr, | ||
| "Could not statically determine the struct type of this Bind entry. It is still registered as a v3 service, but no frontend/wailsjs shim was generated for it.") | ||
| result = append(result, bt) | ||
| continue | ||
| } | ||
| pkgName, pkgPath, methods := collectMethods(fset, files, proj, typeName) | ||
| bt.PkgName = pkgName | ||
| bt.PkgPath = pkgPath | ||
| bt.Methods = methods | ||
| if len(methods) == 0 { | ||
| proj.Report.Note("Bind: " + expr + " (" + typeName + ") has no exported methods that could be discovered; no frontend/wailsjs shim was generated for it.") | ||
| } | ||
| result = append(result, bt) | ||
| } | ||
| return result | ||
| } | ||
|
|
||
| // resolveElementType attempts to resolve a Bind element expression to a | ||
| // struct type name using purely syntactic information: | ||
| // | ||
| // &App{...} -> App | ||
| // NewApp() -> return type of func NewApp | ||
| // app -> declaration of app in the same file (app := NewApp(), | ||
| // app := &App{}, var app = ...) | ||
| func resolveElementType(fset *token.FileSet, files map[string]*ast.File, mainFile *ast.File, expr ast.Expr) string { | ||
| switch e := expr.(type) { | ||
| case *ast.UnaryExpr: | ||
| if e.Op == token.AND { | ||
| if lit, ok := e.X.(*ast.CompositeLit); ok { | ||
| if ident, ok := lit.Type.(*ast.Ident); ok { | ||
| return ident.Name | ||
| } | ||
| } | ||
| } | ||
| case *ast.CallExpr: | ||
| if ident, ok := e.Fun.(*ast.Ident); ok { | ||
| return constructorReturnType(files, ident.Name) | ||
| } | ||
| case *ast.Ident: | ||
| var typeName string | ||
| ast.Inspect(mainFile, func(n ast.Node) bool { | ||
| if typeName != "" { | ||
| return false | ||
| } | ||
| assign, ok := n.(*ast.AssignStmt) | ||
| if !ok || len(assign.Lhs) != 1 || len(assign.Rhs) != 1 { | ||
| return true | ||
| } | ||
| lhs, ok := assign.Lhs[0].(*ast.Ident) | ||
| if !ok || lhs.Name != e.Name { | ||
| return true | ||
| } | ||
| typeName = resolveElementType(fset, files, mainFile, assign.Rhs[0]) | ||
| return false | ||
| }) | ||
| return typeName | ||
| } | ||
| return "" | ||
| } | ||
|
|
||
| // constructorReturnType finds `func Name(...) *T` in the parsed files and | ||
| // returns T. | ||
| func constructorReturnType(files map[string]*ast.File, name string) string { | ||
| for _, file := range files { | ||
| for _, decl := range file.Decls { | ||
| fn, ok := decl.(*ast.FuncDecl) | ||
| if !ok || fn.Recv != nil || fn.Name.Name != name { | ||
| continue | ||
| } | ||
| if fn.Type.Results == nil || len(fn.Type.Results.List) == 0 { | ||
| return "" | ||
| } | ||
| t := fn.Type.Results.List[0].Type | ||
| if star, ok := t.(*ast.StarExpr); ok { | ||
| t = star.X | ||
| } | ||
| if ident, ok := t.(*ast.Ident); ok { | ||
| return ident.Name | ||
| } | ||
| } | ||
| } | ||
| return "" | ||
| } | ||
|
|
||
| // collectMethods gathers the exported methods declared on *typeName or | ||
| // typeName across the parsed files. | ||
| func collectMethods(fset *token.FileSet, files map[string]*ast.File, proj *V2Project, typeName string) (pkgName, pkgPath string, methods []*BoundMethod) { | ||
| // Sort file paths for deterministic method order across map iteration. | ||
| paths := make([]string, 0, len(files)) | ||
| for path := range files { | ||
| paths = append(paths, path) | ||
| } | ||
| sort.Strings(paths) | ||
|
|
||
| for _, path := range paths { | ||
| file := files[path] | ||
| for _, decl := range file.Decls { | ||
| fn, ok := decl.(*ast.FuncDecl) | ||
| if !ok || fn.Recv == nil || len(fn.Recv.List) != 1 { | ||
| continue | ||
| } | ||
| recv := fn.Recv.List[0].Type | ||
| if star, ok := recv.(*ast.StarExpr); ok { | ||
| recv = star.X | ||
| } | ||
| ident, ok := recv.(*ast.Ident) | ||
| if !ok || ident.Name != typeName { | ||
| continue | ||
| } | ||
| if !fn.Name.IsExported() { | ||
| continue | ||
| } | ||
| if pkgName == "" { | ||
| pkgName = file.Name.Name | ||
| pkgPath = packagePath(proj, path, pkgName) | ||
| } | ||
| methods = append(methods, &BoundMethod{ | ||
| Name: fn.Name.Name, | ||
| Params: fieldListParams(fset, fn.Type.Params), | ||
| Results: fieldListParams(fset, fn.Type.Results), | ||
| }) | ||
| } | ||
| } | ||
| return pkgName, pkgPath, methods |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Resolve each Bind entry in lexical and package scope.
Line 364 searches assignments only by identifier text. An earlier app := ... in another function can be selected, and var app = ... is never inspected despite the documented form. Line 387 then searches every parsed package for NewApp. Lines 419-446 merge methods from every package with the same receiver spelling. Go identifiers and types are package-scoped. A duplicate NewApp or App in a subpackage can associate a Bind entry with an unrelated type and method list.
Resolve declarations relative to the wails.Run file and lexical scope. Preserve package identity with the resolved type. Restrict constructor and method lookup to that package. Add fixtures for a package-level var, a local shadow, and duplicate symbols in a subpackage.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@v3/internal/migrate/parse.go` around lines 309 - 448, Update
resolveElementType, constructorReturnType, and collectMethods to resolve each
Bind entry from the wails.Run file’s lexical scope and retain its package
identity. Handle package-level and local var declarations while selecting the
nearest valid declaration, and restrict constructor and receiver-method lookup
to the resolved package instead of matching names across all parsed files. Add
fixtures covering package-level vars, local shadowing, and duplicate symbols in
a subpackage.
| sb.WriteString("## Next steps\n\n") | ||
| sb.WriteString("1. Run `wails3 doctor` to check your environment.\n") | ||
| sb.WriteString("2. Port the call sites listed below to the v3 API. The compiler will point at\n") | ||
| sb.WriteString(" them: the project intentionally does not build until they are ported.\n") | ||
| sb.WriteString(" Do not run `go mod tidy` before this is done - it would re-add the v2\n") | ||
| sb.WriteString(" dependency and make the old calls compile, but they cannot work inside a\n") | ||
| sb.WriteString(" v3 application and will fail at runtime.\n") | ||
| sb.WriteString("3. Run `go mod tidy`, then `wails3 generate bindings` for the frontend bindings.\n") | ||
| sb.WriteString("4. Run `wails3 dev` to build and run the migrated app.\n") | ||
| sb.WriteString("5. Work through the *Manual steps* below, if any.\n") | ||
| sb.WriteString(" See https://v3.wails.io/migration/v2-to-v3/ for the full guide.\n\n") |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Make the call-site step conditional.
When r.callSites is empty, Lines 62-66 refer to call sites listed below and state that the project does not build. The report renders no call-site section in that case. Emit this step only when call sites exist, and renumber the remaining steps.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@v3/internal/migrate/report.go` around lines 60 - 70, Update the next-steps
rendering in the report builder around r.callSites so the call-site migration
step and its continuation lines are emitted only when callSites is non-empty.
Renumber the subsequent go mod tidy, dev, and manual-steps items based on
whether that conditional step was included, while preserving the existing
guidance text.
Summary
wails3 migrateto the released V3 CLIMIGRATION.mdbuild/config.ymlandTaskfile.ymllayoutValidation
go test ./internal/commands ./internal/migratego build ./cmd/wails3wails3 migrate --helppanic-recovery-testfixture into a fresh output directoryMIGRATION.md,Taskfile.yml, andbuild/config.ymlThe generated project remains intentionally non-zero-touch: users must review the report, port remaining V2 API calls, regenerate bindings, and test their application.
Summary by CodeRabbit
New Features
wails3 migratecommand to help convert Wails v2 projects to v3.MIGRATION.mdchecklist identifying API calls and steps requiring manual review.Documentation