@sun-asterisk/sungen 3.2.30 → 3.2.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/dist/cli/commands/audit.d.ts.map +1 -1
  2. package/dist/cli/commands/audit.js +3 -1
  3. package/dist/cli/commands/audit.js.map +1 -1
  4. package/dist/cli/commands/capability.d.ts.map +1 -1
  5. package/dist/cli/commands/capability.js +11 -0
  6. package/dist/cli/commands/capability.js.map +1 -1
  7. package/dist/cli/commands/init.d.ts.map +1 -1
  8. package/dist/cli/commands/init.js +2 -0
  9. package/dist/cli/commands/init.js.map +1 -1
  10. package/dist/cli/commands/setup-mobile-env.d.ts +15 -0
  11. package/dist/cli/commands/setup-mobile-env.d.ts.map +1 -0
  12. package/dist/cli/commands/setup-mobile-env.js +65 -0
  13. package/dist/cli/commands/setup-mobile-env.js.map +1 -0
  14. package/dist/cli/index.js +2 -0
  15. package/dist/cli/index.js.map +1 -1
  16. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-assertion.hbs +1 -2
  17. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-element.hbs +8 -5
  18. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/count-assertion.hbs +5 -1
  19. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-filter-assertion.hbs +1 -1
  20. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +1 -1
  21. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-filter-assertion.hbs +1 -1
  22. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +1 -1
  23. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/list-item-count-assertion.hbs +5 -2
  24. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/state-with-filter-assertion.hbs +1 -1
  25. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-filtered-assertion.hbs +1 -1
  26. package/dist/generators/test-generator/adapters/appium/templates/steps/partials/await-elements.hbs +12 -0
  27. package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +26 -0
  28. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/capture-variable.hbs +7 -1
  29. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/row-scoped-column-assertion.hbs +23 -1
  30. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +4 -4
  31. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/default.hbs +1 -1
  32. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/id.hbs +1 -1
  33. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +4 -4
  34. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +13 -2
  35. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +14 -0
  36. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  37. package/dist/generators/test-generator/code-generator.js +56 -13
  38. package/dist/generators/test-generator/code-generator.js.map +1 -1
  39. package/dist/generators/test-generator/diagnostics.d.ts +39 -2
  40. package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
  41. package/dist/generators/test-generator/diagnostics.js +38 -1
  42. package/dist/generators/test-generator/diagnostics.js.map +1 -1
  43. package/dist/generators/test-generator/patterns/index.d.ts +1 -1
  44. package/dist/generators/test-generator/patterns/index.js +1 -1
  45. package/dist/generators/test-generator/step-mapper.d.ts +35 -0
  46. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  47. package/dist/generators/test-generator/step-mapper.js +103 -4
  48. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  49. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  50. package/dist/generators/test-generator/template-engine.js +18 -0
  51. package/dist/generators/test-generator/template-engine.js.map +1 -1
  52. package/dist/generators/test-generator/utils/selector-resolver.d.ts +47 -0
  53. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  54. package/dist/generators/test-generator/utils/selector-resolver.js +17 -3
  55. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  56. package/dist/harness/audit.d.ts.map +1 -1
  57. package/dist/harness/audit.js +23 -4
  58. package/dist/harness/audit.js.map +1 -1
  59. package/dist/harness/next-step.d.ts.map +1 -1
  60. package/dist/harness/next-step.js +2 -1
  61. package/dist/harness/next-step.js.map +1 -1
  62. package/dist/harness/parse.d.ts.map +1 -1
  63. package/dist/harness/parse.js +42 -7
  64. package/dist/harness/parse.js.map +1 -1
  65. package/dist/harness/spec-branches.d.ts +7 -2
  66. package/dist/harness/spec-branches.d.ts.map +1 -1
  67. package/dist/harness/spec-branches.js +62 -11
  68. package/dist/harness/spec-branches.js.map +1 -1
  69. package/dist/harness/spec-coverage.d.ts +16 -0
  70. package/dist/harness/spec-coverage.d.ts.map +1 -1
  71. package/dist/harness/spec-coverage.js +119 -10
  72. package/dist/harness/spec-coverage.js.map +1 -1
  73. package/dist/orchestrator/appium-environment-setup.d.ts +24 -0
  74. package/dist/orchestrator/appium-environment-setup.d.ts.map +1 -0
  75. package/dist/orchestrator/appium-environment-setup.js +157 -0
  76. package/dist/orchestrator/appium-environment-setup.js.map +1 -0
  77. package/dist/orchestrator/mobile-runtime-scaffolder.d.ts +2 -0
  78. package/dist/orchestrator/mobile-runtime-scaffolder.d.ts.map +1 -1
  79. package/dist/orchestrator/mobile-runtime-scaffolder.js.map +1 -1
  80. package/dist/orchestrator/project-initializer.d.ts +1 -0
  81. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  82. package/dist/orchestrator/project-initializer.js +14 -0
  83. package/dist/orchestrator/project-initializer.js.map +1 -1
  84. package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +5 -1
  85. package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +55 -2
  86. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +20 -5
  87. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +3 -3
  88. package/dist/orchestrator/templates/appium-environment/README.md +83 -0
  89. package/dist/orchestrator/templates/appium-environment/scripts/common/install-appium.ps1 +44 -0
  90. package/dist/orchestrator/templates/appium-environment/scripts/common/install-appium.sh +50 -0
  91. package/dist/orchestrator/templates/appium-environment/scripts/common/lib.ps1 +108 -0
  92. package/dist/orchestrator/templates/appium-environment/scripts/common/lib.sh +141 -0
  93. package/dist/orchestrator/templates/appium-environment/scripts/common/verify-appium.ps1 +39 -0
  94. package/dist/orchestrator/templates/appium-environment/scripts/common/verify-appium.sh +43 -0
  95. package/dist/orchestrator/templates/appium-environment/scripts/common/verify-node.ps1 +13 -0
  96. package/dist/orchestrator/templates/appium-environment/scripts/common/verify-node.sh +16 -0
  97. package/dist/orchestrator/templates/appium-environment/scripts/macos/install-android.sh +110 -0
  98. package/dist/orchestrator/templates/appium-environment/scripts/macos/install-ios.sh +32 -0
  99. package/dist/orchestrator/templates/appium-environment/scripts/macos/verify-android.sh +32 -0
  100. package/dist/orchestrator/templates/appium-environment/scripts/macos/verify-ios.sh +15 -0
  101. package/dist/orchestrator/templates/appium-environment/scripts/windows/install-android.ps1 +153 -0
  102. package/dist/orchestrator/templates/appium-environment/scripts/windows/verify-android.ps1 +26 -0
  103. package/dist/orchestrator/templates/appium-environment/setup.ps1 +58 -0
  104. package/dist/orchestrator/templates/appium-environment/setup.sh +88 -0
  105. package/dist/orchestrator/templates/specs-checked-state.d.ts +1 -1
  106. package/dist/orchestrator/templates/specs-checked-state.d.ts.map +1 -1
  107. package/dist/orchestrator/templates/specs-checked-state.js +26 -3
  108. package/dist/orchestrator/templates/specs-checked-state.js.map +1 -1
  109. package/dist/orchestrator/templates/specs-checked-state.ts +31 -6
  110. package/dist/orchestrator/templates/specs-mock.d.ts +2 -1
  111. package/dist/orchestrator/templates/specs-mock.d.ts.map +1 -1
  112. package/dist/orchestrator/templates/specs-mock.js +10 -3
  113. package/dist/orchestrator/templates/specs-mock.js.map +1 -1
  114. package/dist/orchestrator/templates/specs-mock.ts +12 -4
  115. package/package.json +3 -3
  116. package/src/cli/commands/audit.ts +2 -1
  117. package/src/cli/commands/capability.ts +12 -1
  118. package/src/cli/commands/init.ts +3 -0
  119. package/src/cli/commands/setup-mobile-env.ts +64 -0
  120. package/src/cli/index.ts +2 -0
  121. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-assertion.hbs +1 -2
  122. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-element.hbs +8 -5
  123. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/count-assertion.hbs +5 -1
  124. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-filter-assertion.hbs +1 -1
  125. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +1 -1
  126. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-filter-assertion.hbs +1 -1
  127. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +1 -1
  128. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/list-item-count-assertion.hbs +5 -2
  129. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/state-with-filter-assertion.hbs +1 -1
  130. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-filtered-assertion.hbs +1 -1
  131. package/src/generators/test-generator/adapters/appium/templates/steps/partials/await-elements.hbs +12 -0
  132. package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +26 -0
  133. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/capture-variable.hbs +7 -1
  134. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/row-scoped-column-assertion.hbs +23 -1
  135. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +4 -4
  136. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/default.hbs +1 -1
  137. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/id.hbs +1 -1
  138. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +4 -4
  139. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +13 -2
  140. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +14 -0
  141. package/src/generators/test-generator/code-generator.ts +66 -17
  142. package/src/generators/test-generator/diagnostics.ts +39 -2
  143. package/src/generators/test-generator/patterns/index.ts +1 -1
  144. package/src/generators/test-generator/step-mapper.ts +106 -5
  145. package/src/generators/test-generator/template-engine.ts +18 -0
  146. package/src/generators/test-generator/utils/selector-resolver.ts +18 -3
  147. package/src/harness/audit.ts +24 -5
  148. package/src/harness/next-step.ts +2 -1
  149. package/src/harness/parse.ts +42 -7
  150. package/src/harness/spec-branches.ts +67 -12
  151. package/src/harness/spec-coverage.ts +121 -11
  152. package/src/orchestrator/appium-environment-setup.ts +127 -0
  153. package/src/orchestrator/mobile-runtime-scaffolder.ts +2 -0
  154. package/src/orchestrator/project-initializer.ts +15 -1
  155. package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +5 -1
  156. package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +55 -2
  157. package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +20 -5
  158. package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +3 -3
  159. package/src/orchestrator/templates/appium-environment/README.md +83 -0
  160. package/src/orchestrator/templates/appium-environment/scripts/common/install-appium.ps1 +44 -0
  161. package/src/orchestrator/templates/appium-environment/scripts/common/install-appium.sh +50 -0
  162. package/src/orchestrator/templates/appium-environment/scripts/common/lib.ps1 +108 -0
  163. package/src/orchestrator/templates/appium-environment/scripts/common/lib.sh +141 -0
  164. package/src/orchestrator/templates/appium-environment/scripts/common/verify-appium.ps1 +39 -0
  165. package/src/orchestrator/templates/appium-environment/scripts/common/verify-appium.sh +43 -0
  166. package/src/orchestrator/templates/appium-environment/scripts/common/verify-node.ps1 +13 -0
  167. package/src/orchestrator/templates/appium-environment/scripts/common/verify-node.sh +16 -0
  168. package/src/orchestrator/templates/appium-environment/scripts/macos/install-android.sh +110 -0
  169. package/src/orchestrator/templates/appium-environment/scripts/macos/install-ios.sh +32 -0
  170. package/src/orchestrator/templates/appium-environment/scripts/macos/verify-android.sh +32 -0
  171. package/src/orchestrator/templates/appium-environment/scripts/macos/verify-ios.sh +15 -0
  172. package/src/orchestrator/templates/appium-environment/scripts/windows/install-android.ps1 +153 -0
  173. package/src/orchestrator/templates/appium-environment/scripts/windows/verify-android.ps1 +26 -0
  174. package/src/orchestrator/templates/appium-environment/setup.ps1 +58 -0
  175. package/src/orchestrator/templates/appium-environment/setup.sh +88 -0
  176. package/src/orchestrator/templates/specs-checked-state.ts +31 -6
  177. package/src/orchestrator/templates/specs-mock.ts +12 -4
@@ -0,0 +1,83 @@
1
+ # appium-environment/ — one command to a working Appium machine
2
+
3
+ Emitted by `sungen init --mobile` (or `sungen capability add mobile`), which also runs it once.
4
+ It provisions everything **below** the Appium layer — the part QA used to do by hand — and ends with
5
+ `appium driver doctor`. Every step is **check → install → verify**: a satisfied step is skipped, so the
6
+ bundle is safe to re-run any time (a new machine, a teammate's clone, after an OS update).
7
+
8
+ ```bash
9
+ sungen setup-mobile-env # any OS — emits this folder if missing, then runs it
10
+ ./appium-environment/setup.sh # macOS, directly
11
+ powershell -NoProfile -ExecutionPolicy Bypass -File .\appium-environment\setup.ps1 # Windows, directly
12
+ ```
13
+
14
+ | Flag (macOS) | Flag (Windows) | Effect |
15
+ |---|---|---|
16
+ | `--check` | `-Check` | report what is missing, install nothing (side-effect free) |
17
+ | `--skip-android` | `-SkipAndroid` | skip JDK / Android SDK / uiautomator2 |
18
+ | `--skip-ios` | — | skip Xcode CLT / applesimutils / xcuitest (iOS needs a Mac) |
19
+ | `--no-profile` | `-NoProfile` | do not write env vars to your shell profile / User environment |
20
+
21
+ Exit code: `0` ready (or only warnings) · `1` something failed · `2` unsupported OS / bad flag.
22
+ `sungen init` treats a non-zero exit as a warning — the project scaffold is already complete.
23
+
24
+ ## What it installs (and what it only checks)
25
+
26
+ | Step | macOS (`setup.sh`) | Windows (`setup.ps1`) |
27
+ |---|---|---|
28
+ | Node.js ≥ 18 | check only (sungen needs it already) | check only |
29
+ | Package manager | Homebrew — installed if missing | winget — check only (App Installer, Microsoft Store) |
30
+ | JDK ≥ 17 | `brew install openjdk@21` (an existing JDK ≥ 17 is kept) | `winget install Microsoft.OpenJDK.21` (existing JDK ≥ 17 kept) |
31
+ | Android SDK root | existing `ANDROID_HOME` / Android Studio SDK / `brew install --cask android-commandlinetools` | existing / `%LOCALAPPDATA%\Android\Sdk` / Google's command-line tools zip |
32
+ | `platform-tools` (adb) + `emulator` | `sdkmanager` | `sdkmanager` |
33
+ | Env vars | marked block in `~/.zshrc` (or `~/.bash_profile`) | User environment (`ANDROID_HOME`, `ANDROID_SDK_ROOT`, `JAVA_HOME`, `Path`) |
34
+ | Android Studio | **check + hint only** (optional — AVD manager / on-screen emulator) | same |
35
+ | Xcode Command Line Tools | `xcode-select --install` (Apple's dialog — finish it, re-run) | — |
36
+ | Xcode.app | **check + hint only** (App Store; required for iOS) | — |
37
+ | Xcode license | `sudo xcodebuild -license accept` | — |
38
+ | `applesimutils` | `brew tap wix/brew && brew install applesimutils` | — |
39
+ | Appium | **project-local** (`node_modules/.bin/appium`, from `@sungen/driver-mobile`) — never global | same |
40
+ | Appium drivers | `uiautomator2` + `xcuitest` into `~/.appium` | `uiautomator2` |
41
+ | Health | `adb`, env vars, devices, Simulators, `appium driver doctor` | `adb`, env vars, devices, `appium driver doctor` |
42
+
43
+ Nothing is installed globally with npm. GUI apps (Xcode.app, Android Studio) are never installed for you —
44
+ the step tells you exactly where to get them and re-running the script picks up from there.
45
+
46
+ ## The env-var block
47
+
48
+ macOS: the lines live between `# >>> sungen mobile env >>>` and `# <<< sungen mobile env <<<` in your
49
+ shell profile; re-running rewrites only that block, and it is skipped entirely when your shell already
50
+ exports matching values. To undo, delete the block. Windows: plain User-scope variables — undo in
51
+ *System Properties → Environment Variables*.
52
+
53
+ **Open a new terminal after the first run** so `PATH` / `JAVA_HOME` / `ANDROID_HOME` apply.
54
+
55
+ ## Known limitations
56
+
57
+ - **Linux** is not provisioned (the entry point prints a notice and exits 0). Android on Linux: install a
58
+ JDK ≥ 17 and the Android command-line tools with your package manager, export the same variables, then
59
+ `sungen capability add mobile` for the project-local Appium + `uiautomator2`.
60
+ - **`sungen update` does not refresh this folder.** To pick up a newer bundle, delete `appium-environment/`
61
+ and run `sungen capability add mobile` again (all other files are skipped because they already exist).
62
+ - Windows downloads Google's command-line tools by a **pinned build number** (`$CmdlineToolsBuild` at the
63
+ top of `scripts/windows/install-android.ps1`). If the download 404s, bump it from
64
+ https://developer.android.com/studio#command-line-tools-only.
65
+ - No AVD / Simulator is created and no phone is paired — plug in a device (USB debugging on) or start an
66
+ emulator before `/sungen:run-test`.
67
+
68
+ ## Manual fallback (the steps this bundle automates)
69
+
70
+ - Android: https://sungen.sun-asterisk.vn/docs/mobile/appium-setup-android
71
+ - iOS: https://sungen.sun-asterisk.vn/docs/mobile/appium-setup-ios
72
+ - Overview + troubleshooting: https://sungen.sun-asterisk.vn/docs/mobile/appium-setup ·
73
+ https://sungen.sun-asterisk.vn/docs/mobile/mobile-troubleshooting
74
+
75
+ ## Layout
76
+
77
+ ```
78
+ appium-environment/
79
+ ├── setup.sh · setup.ps1 entry points (OS check, flags, step order, summary)
80
+ ├── scripts/common/ lib.{sh,ps1} step runner · verify-node · install-appium · verify-appium
81
+ ├── scripts/macos/ install-android · verify-android · install-ios · verify-ios
82
+ └── scripts/windows/ install-android · verify-android
83
+ ```
@@ -0,0 +1,44 @@
1
+ # install-appium.ps1 - Appium stays PROJECT-LOCAL (a devDependency pulled in by @sungen/driver-mobile),
2
+ # never `npm i -g appium`; the platform driver (uiautomator2) lives in ~\.appium and is installed
3
+ # through that local CLI. Mirrors what `sungen init --mobile` does in TypeScript, so this script also
4
+ # works on a fresh clone where a teammate ran init.
5
+ if (-not $script:SungenEnvLibLoaded) { . (Join-Path $PSScriptRoot 'lib.ps1') }
6
+
7
+ $AppiumCmd = $script:SungenEnvAppiumCmd
8
+
9
+ function Get-AppiumVersion { if (Test-Path $AppiumCmd) { ((& $AppiumCmd -v) 2>$null | Select-Object -First 1) } }
10
+ Invoke-Step -Title 'Appium (project-local)' -Check {
11
+ if (Test-Path $AppiumCmd) { "appium $(Get-AppiumVersion) (project-local: node_modules\.bin\appium)" }
12
+ } -Install {
13
+ # Only re-install when the project already declares the mobile driver - otherwise the fix is
14
+ # `sungen capability add mobile`, a consent-gated sungen command, not this script's call.
15
+ $pkg = Join-Path $script:ProjectDir 'package.json'
16
+ if ((Test-Path $pkg) -and ((Get-Content $pkg -Raw) -match '"@sungen/driver-mobile"')) {
17
+ Push-Location $script:ProjectDir; try { Invoke-Native npm @('install') } finally { Pop-Location }
18
+ } else { return 'needs-user' }
19
+ } -Verify {
20
+ if (Test-Path $AppiumCmd) { "appium $(Get-AppiumVersion)" }
21
+ } -Hint "Appium is project-local (never global). Add the mobile driver + runtime with:`n sungen capability add mobile"
22
+
23
+ # --- platform driver -------------------------------------------------------------------------------
24
+ function Get-InstalledDrivers {
25
+ $raw = (& $AppiumCmd driver list --installed --json) 2>$null | Out-String
26
+ if (-not $raw.Trim()) { return $null }
27
+ try { return ($raw | ConvertFrom-Json) } catch { return $null }
28
+ }
29
+ function Get-DriverDetail([string]$Name) {
30
+ $j = Get-InstalledDrivers
31
+ if ($j -and ($j.PSObject.Properties.Name -contains $Name)) { "$Name $($j.$Name.version) in ~\.appium" }
32
+ }
33
+
34
+ if (Test-Path $AppiumCmd) {
35
+ if (-not $script:SungenEnvSkipAndroid) {
36
+ $script:CurrentDriver = 'uiautomator2'
37
+ Invoke-Step -Title "Appium driver: $($script:CurrentDriver)" -Check { Get-DriverDetail $script:CurrentDriver } `
38
+ -Install { Invoke-Native $AppiumCmd @('driver', 'install', $script:CurrentDriver) } `
39
+ -Verify { Get-DriverDetail $script:CurrentDriver } `
40
+ -Hint "retry: npx appium driver install $($script:CurrentDriver)"
41
+ }
42
+ } else {
43
+ Write-Warn 'Appium platform driver (uiautomator2)' 'skipped - Appium is not installed in this project yet (see the step above)'
44
+ }
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env bash
2
+ # install-appium.sh — Appium stays PROJECT-LOCAL (a devDependency pulled in by @sungen/driver-mobile),
3
+ # never `npm i -g appium`; the platform drivers (uiautomator2 / xcuitest) live in ~/.appium and are
4
+ # installed through that local CLI. Mirrors what `sungen init --mobile` does in TypeScript, so this
5
+ # script also works on a fresh clone where a teammate ran init.
6
+ [ -n "${SUNGEN_ENV_LIB_LOADED:-}" ] || . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib.sh"
7
+
8
+ APPIUM_BIN=$SUNGEN_ENV_APPIUM_BIN
9
+
10
+ appium_check() {
11
+ [ -x "$APPIUM_BIN" ] || return 1
12
+ printf 'appium %s (project-local: node_modules/.bin/appium)' "$("$APPIUM_BIN" -v 2>/dev/null)"
13
+ }
14
+ appium_install() {
15
+ # Only re-install when the project already declares the mobile driver — otherwise the fix is
16
+ # `sungen capability add mobile`, which is a consent-gated sungen command, not this script's call.
17
+ if grep -q '"@sungen/driver-mobile"' "$PROJECT_DIR/package.json" 2>/dev/null; then
18
+ (cd "$PROJECT_DIR" && npm install)
19
+ else
20
+ return 2
21
+ fi
22
+ }
23
+ appium_verify() {
24
+ appium_check || { printf 'Appium is project-local (never global). Add the mobile driver + runtime with:\n sungen capability add mobile'; return 1; }
25
+ }
26
+ run_step "Appium (project-local)" appium_check appium_install appium_verify
27
+
28
+ # ── platform drivers — one run_step per driver, CURRENT_DRIVER carries the name (bash 3.2: no closures)
29
+ installed_drivers_json() { "$APPIUM_BIN" driver list --installed --json 2>/dev/null; }
30
+ drv_check() {
31
+ local json ver
32
+ json=$(installed_drivers_json) || return 1
33
+ printf '%s' "$json" | grep -q "\"$CURRENT_DRIVER\"" || return 1
34
+ ver=$(printf '%s' "$json" | node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{try{const j=JSON.parse(s);process.stdout.write((j['$CURRENT_DRIVER']||{}).version||'')}catch{}})" 2>/dev/null)
35
+ printf '%s %s in ~/.appium' "$CURRENT_DRIVER" "${ver:-}"
36
+ }
37
+ drv_install() { "$APPIUM_BIN" driver install "$CURRENT_DRIVER"; }
38
+ drv_verify() { drv_check || { printf 'retry: npx appium driver install %s' "$CURRENT_DRIVER"; return 1; }; }
39
+
40
+ if [ -x "$APPIUM_BIN" ]; then
41
+ APPIUM_DRIVERS="uiautomator2"
42
+ [ "$SUNGEN_ENV_SKIP_ANDROID" = 1 ] && APPIUM_DRIVERS=""
43
+ # xcuitest builds WebDriverAgent with Xcode at run time → only meaningful on a Mac.
44
+ if [ "$(uname -s)" = Darwin ] && [ "$SUNGEN_ENV_SKIP_IOS" != 1 ]; then APPIUM_DRIVERS="$APPIUM_DRIVERS xcuitest"; fi
45
+ for CURRENT_DRIVER in $APPIUM_DRIVERS; do
46
+ run_step "Appium driver: $CURRENT_DRIVER" drv_check drv_install drv_verify
47
+ done
48
+ else
49
+ log_warn "Appium platform drivers (uiautomator2 / xcuitest)" "skipped — Appium is not installed in this project yet (see the step above)"
50
+ fi
@@ -0,0 +1,108 @@
1
+ # lib.ps1 - shared step runner for the sungen appium-environment bundle (Windows side).
2
+ # Dot-sourced by setup.ps1 and by every scripts\**\*.ps1; never run directly.
3
+ # Windows PowerShell 5.1 compatible: no ternary, no ??, ASCII only (5.1 reads BOM-less files as ANSI).
4
+ #
5
+ # Step model (see docs: Mobile -> Appium setup -> "One command"):
6
+ # Check -> returns a detail string when already satisfied, $null otherwise (side-effect free)
7
+ # Install -> idempotent; output streams to the console; returns 'needs-user' when the user must finish it
8
+ # Verify -> returns a detail string when satisfied, $null otherwise; -Hint is shown on failure
9
+
10
+ if ($script:SungenEnvLibLoaded) { return }
11
+ $script:SungenEnvLibLoaded = $true
12
+ $ErrorActionPreference = 'Continue'
13
+
14
+ # Flags - set by setup.ps1's parameters, read by every step.
15
+ if ($null -eq $script:SungenEnvCheckOnly) { $script:SungenEnvCheckOnly = $false }
16
+ if ($null -eq $script:SungenEnvSkipAndroid) { $script:SungenEnvSkipAndroid = $false }
17
+ if ($null -eq $script:SungenEnvNoProfile) { $script:SungenEnvNoProfile = $false }
18
+ # Flipped to $true when the User environment actually changed.
19
+ $script:SungenEnvProfileChanged = $false
20
+
21
+ # Paths: <project>\appium-environment\scripts\common\lib.ps1 -> bundle = ..\.., project = bundle\..
22
+ $script:SungenEnvBundleDir = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
23
+ if (-not $script:ProjectDir) { $script:ProjectDir = (Resolve-Path (Join-Path $script:SungenEnvBundleDir '..')).Path }
24
+ # Appium is project-local by design (never `npm i -g appium`) - see install-appium.ps1.
25
+ $script:SungenEnvAppiumCmd = Join-Path $script:ProjectDir 'node_modules\.bin\appium.cmd'
26
+
27
+ $script:CountOk = 0; $script:CountSkip = 0; $script:CountWarn = 0; $script:CountFail = 0
28
+
29
+ function Write-Section([string]$Title) { Write-Host ''; Write-Host $Title -ForegroundColor White }
30
+ function Write-Detail([string]$Text) {
31
+ if (-not $Text) { return }
32
+ foreach ($line in ($Text -split "`r?`n")) { if ($line) { Write-Host " $line" -ForegroundColor DarkGray } }
33
+ }
34
+ function Write-Ok([string]$Title, [string]$Detail) { $script:CountOk++; Write-Host "[ok] $Title" -ForegroundColor Green; Write-Detail $Detail }
35
+ function Write-Skip([string]$Title, [string]$Detail) { $script:CountSkip++; Write-Host "[skip] $Title" -ForegroundColor DarkGray; Write-Detail $Detail }
36
+ function Write-Warn([string]$Title, [string]$Detail) { $script:CountWarn++; Write-Host "[warn] $Title" -ForegroundColor Yellow; Write-Detail $Detail }
37
+ function Write-Fail([string]$Title, [string]$Detail) { $script:CountFail++; Write-Host "[fail] $Title" -ForegroundColor Red; Write-Detail $Detail }
38
+ function Write-Install([string]$Title) { Write-Host "[install] $Title" -ForegroundColor Cyan }
39
+
40
+ # Invoke-Native <exe> <args...> - run a native command, throw on a non-zero exit code.
41
+ function Invoke-Native([string]$File, [string[]]$Arguments) {
42
+ & $File @Arguments
43
+ if ($LASTEXITCODE -ne 0) { throw "$File exited with code $LASTEXITCODE" }
44
+ }
45
+
46
+ # Invoke-Step - an install-type step.
47
+ function Invoke-Step {
48
+ param([string]$Title, [scriptblock]$Check, [scriptblock]$Install, [scriptblock]$Verify, [string]$Hint)
49
+ $found = $null; try { $found = & $Check } catch { $found = $null }
50
+ if ($found) { Write-Skip $Title "$found"; return }
51
+ if ($script:SungenEnvCheckOnly) { Write-Fail $Title 'not installed - run without -Check to install'; return }
52
+ Write-Install $Title
53
+ $needsUser = $false; $err = $null
54
+ try { $r = & $Install; if ("$r" -eq 'needs-user') { $needsUser = $true } } catch { $err = $_.Exception.Message }
55
+ $ok = $null; try { $ok = & $Verify } catch { $ok = $null }
56
+ if ($ok) { Write-Ok $Title "$ok"; return }
57
+ $msg = $Hint; if ($err) { $msg = "$err`n$Hint" }
58
+ if ($needsUser) { Write-Warn $Title $msg } else { Write-Fail $Title $msg }
59
+ }
60
+
61
+ # Invoke-Check - a verification-only step (ok / fail, or -Warn for informational checks).
62
+ function Invoke-Check {
63
+ param([string]$Title, [scriptblock]$Check, [string]$Hint, [switch]$Warn)
64
+ $out = $null; try { $out = & $Check } catch { $out = $null }
65
+ if ($out) { Write-Ok $Title "$out"; return }
66
+ if ($Warn) { Write-Warn $Title $Hint } else { Write-Fail $Title $Hint }
67
+ }
68
+
69
+ # Invoke-Guided - GUI installs this bundle never performs itself: skip when present, else warn with the next action.
70
+ function Invoke-Guided {
71
+ param([string]$Title, [scriptblock]$Check, [string]$Guidance)
72
+ $found = $null; try { $found = & $Check } catch { $found = $null }
73
+ if ($found) { Write-Skip $Title "$found" } else { Write-Warn $Title $Guidance }
74
+ }
75
+
76
+ # --- user environment (the Windows counterpart of the shell-profile block) ------------------------
77
+ # Update-SessionPath - pick up PATH changes made by installers (winget) without a new console.
78
+ function Update-SessionPath {
79
+ $env:Path = [Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' + [Environment]::GetEnvironmentVariable('Path', 'User')
80
+ }
81
+ # Set-UserEnv <name> <value> - user-scope variable (what `setx` does, without its 1024-char truncation). $true when changed.
82
+ function Set-UserEnv([string]$Name, [string]$Value) {
83
+ if ([Environment]::GetEnvironmentVariable($Name, 'User') -eq $Value) { Set-Item -Path "env:$Name" -Value $Value; return $false }
84
+ [Environment]::SetEnvironmentVariable($Name, $Value, 'User')
85
+ Set-Item -Path "env:$Name" -Value $Value
86
+ $script:SungenEnvProfileChanged = $true
87
+ return $true
88
+ }
89
+ # Add-UserPath <dir> - append to the user-scope Path once; also to this session. $true when changed.
90
+ function Add-UserPath([string]$Dir) {
91
+ $current = [Environment]::GetEnvironmentVariable('Path', 'User')
92
+ $parts = @(); if ($current) { $parts = @($current -split ';' | Where-Object { $_ }) }
93
+ if (-not (($env:Path -split ';') -contains $Dir)) { $env:Path = "$env:Path;$Dir" }
94
+ if ($parts -contains $Dir) { return $false }
95
+ [Environment]::SetEnvironmentVariable('Path', (($parts + $Dir) -join ';'), 'User')
96
+ $script:SungenEnvProfileChanged = $true
97
+ return $true
98
+ }
99
+
100
+ # Write-Summary - tallies + the "new terminal" notice. Returns 1 when anything failed, else 0.
101
+ function Write-Summary {
102
+ Write-Host ''
103
+ Write-Host "$($script:CountOk) ok, $($script:CountSkip) skipped, $($script:CountWarn) warned, $($script:CountFail) failed" -ForegroundColor White
104
+ if ($script:SungenEnvProfileChanged) { Write-Host '-> Open a NEW terminal so Path / JAVA_HOME / ANDROID_HOME apply.' -ForegroundColor Yellow }
105
+ if ($script:CountWarn -gt 0) { Write-Host ' warnings need a manual step - do it, then re-run this script.' }
106
+ if ($script:CountFail -gt 0) { return 1 }
107
+ return 0
108
+ }
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env bash
2
+ # lib.sh — shared step runner for the sungen appium-environment bundle (macOS / Linux side).
3
+ # Sourced by setup.sh and by every scripts/**/*.sh; never run directly.
4
+ # Bash 3.2 compatible (the /bin/bash that ships with macOS): no associative arrays, no ${var,,}.
5
+ #
6
+ # Step model (see docs: Mobile → Appium setup → "One command"):
7
+ # check → exit 0 when already satisfied (side-effect free — this is all `--check` runs)
8
+ # install → idempotent; output streams to the terminal; exit 2 = started but needs a user action
9
+ # verify → exit 0 when satisfied; stdout is the detail (ok) or the hint (fail)
10
+
11
+ [ -n "${SUNGEN_ENV_LIB_LOADED:-}" ] && return 0
12
+ SUNGEN_ENV_LIB_LOADED=1
13
+
14
+ # Flags — set by setup.sh's argument parser, read by every step.
15
+ SUNGEN_ENV_CHECK_ONLY=${SUNGEN_ENV_CHECK_ONLY:-0}
16
+ SUNGEN_ENV_SKIP_ANDROID=${SUNGEN_ENV_SKIP_ANDROID:-0}
17
+ SUNGEN_ENV_SKIP_IOS=${SUNGEN_ENV_SKIP_IOS:-0}
18
+ SUNGEN_ENV_NO_PROFILE=${SUNGEN_ENV_NO_PROFILE:-0}
19
+ # Flipped to 1 by write_profile_block when the shell profile actually changed.
20
+ SUNGEN_ENV_PROFILE_CHANGED=0
21
+
22
+ # Paths: <project>/appium-environment/scripts/common/lib.sh → bundle = ../.., project = bundle/..
23
+ SUNGEN_ENV_BUNDLE_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)
24
+ PROJECT_DIR=${PROJECT_DIR:-$(cd "$SUNGEN_ENV_BUNDLE_DIR/.." && pwd)}
25
+ # Appium is project-local by design (never `npm i -g appium`) — see install-appium.sh.
26
+ SUNGEN_ENV_APPIUM_BIN="$PROJECT_DIR/node_modules/.bin/appium"
27
+
28
+ # Environment as it was BEFORE this run — lets the profile step recognise a machine whose own
29
+ # shell profile already exports everything (then no block is written).
30
+ SUNGEN_ENV_INITIAL_ANDROID_HOME=${ANDROID_HOME:-}
31
+ SUNGEN_ENV_INITIAL_JAVA_HOME=${JAVA_HOME:-}
32
+ SUNGEN_ENV_INITIAL_ADB=$(command -v adb 2>/dev/null || true)
33
+
34
+ COUNT_OK=0; COUNT_SKIP=0; COUNT_WARN=0; COUNT_FAIL=0
35
+
36
+ if [ -t 1 ]; then
37
+ C_OK=$'\033[32m'; C_WARN=$'\033[33m'; C_FAIL=$'\033[31m'; C_DIM=$'\033[2m'; C_BOLD=$'\033[1m'; C_RESET=$'\033[0m'
38
+ else
39
+ C_OK=''; C_WARN=''; C_FAIL=''; C_DIM=''; C_BOLD=''; C_RESET=''
40
+ fi
41
+
42
+ have() { command -v "$1" >/dev/null 2>&1; }
43
+ # version_major "v22.17.1" → 22
44
+ version_major() { printf '%s' "$1" | sed -E 's/^[^0-9]*([0-9]+).*/\1/'; }
45
+ # java_major_of <java binary> → 21 (empty when not runnable). "1.8.0_x" → 1, so JDK 8 fails a ≥17 gate.
46
+ java_major_of() { "$1" -version 2>&1 | head -n1 | sed -E 's/^[^"]*"([0-9]+)(\.[0-9]+)*.*$/\1/' | grep -E '^[0-9]+$'; }
47
+ # first_line <multi-line string>
48
+ first_line() { printf '%s' "${1%%$'\n'*}"; }
49
+
50
+ section() { printf '\n%s%s%s\n' "$C_BOLD" "$1" "$C_RESET"; }
51
+ _detail() { [ -n "$1" ] && printf ' %s%s%s\n' "$C_DIM" "$1" "$C_RESET"; return 0; }
52
+ _detail_lines() { printf '%s\n' "$1" | while IFS= read -r line; do [ -n "$line" ] && _detail "$line"; done; return 0; }
53
+
54
+ log_ok() { COUNT_OK=$((COUNT_OK+1)); printf '%s✓ ok%s %s\n' "$C_OK" "$C_RESET" "$1"; _detail_lines "${2:-}"; }
55
+ log_skip() { COUNT_SKIP=$((COUNT_SKIP+1)); printf '%s– skip%s %s\n' "$C_DIM" "$C_RESET" "$1"; _detail_lines "${2:-}"; }
56
+ log_warn() { COUNT_WARN=$((COUNT_WARN+1)); printf '%s⚠ warn%s %s\n' "$C_WARN" "$C_RESET" "$1"; _detail_lines "${2:-}"; }
57
+ log_fail() { COUNT_FAIL=$((COUNT_FAIL+1)); printf '%s✗ fail%s %s\n' "$C_FAIL" "$C_RESET" "$1"; _detail_lines "${2:-}"; }
58
+ log_install() { printf '📦 install %s\n' "$1"; }
59
+
60
+ # run_step <title> <check_fn> <install_fn> <verify_fn> — an install-type step.
61
+ run_step() {
62
+ local title=$1 check_fn=$2 install_fn=$3 verify_fn=$4 found out rc
63
+ if found=$("$check_fn" 2>/dev/null); then log_skip "$title" "${found:-already installed}"; return 0; fi
64
+ if [ "$SUNGEN_ENV_CHECK_ONLY" = 1 ]; then log_fail "$title" "not installed — run without --check to install"; return 0; fi
65
+ log_install "$title"
66
+ "$install_fn"; rc=$?
67
+ if out=$("$verify_fn" 2>&1); then log_ok "$title" "$out"; return 0; fi
68
+ if [ "$rc" -eq 2 ]; then log_warn "$title" "${out:-needs a manual step — re-run setup.sh afterwards}"
69
+ else log_fail "$title" "${out:-install did not complete}"; fi
70
+ return 0
71
+ }
72
+
73
+ # run_check <title> <check_fn> <hint> [warn] — a verification-only step (ok / fail, or warn for informational checks).
74
+ run_check() {
75
+ local title=$1 check_fn=$2 hint=$3 level=${4:-fail} out msg
76
+ if out=$("$check_fn" 2>&1); then log_ok "$title" "$out"; return 0; fi
77
+ msg=$hint; [ -n "$out" ] && msg="$out"$'\n'"$hint"
78
+ if [ "$level" = warn ]; then log_warn "$title" "$msg"; else log_fail "$title" "$msg"; fi
79
+ return 0
80
+ }
81
+
82
+ # run_guided <title> <check_fn> <guidance> — GUI / App Store installs this bundle never performs itself.
83
+ run_guided() {
84
+ local title=$1 check_fn=$2 guidance=$3 found
85
+ if found=$("$check_fn" 2>/dev/null); then log_skip "$title" "${found:-already installed}"; return 0; fi
86
+ log_warn "$title" "$guidance"
87
+ return 0
88
+ }
89
+
90
+ # ── shell profile block ────────────────────────────────────────────────────────────────────────
91
+ PROFILE_BEGIN='# >>> sungen mobile env >>>'
92
+ PROFILE_END='# <<< sungen mobile env <<<'
93
+ # zsh → ~/.zshrc, bash → ~/.bash_profile, anything else → ~/.profile
94
+ profile_file() {
95
+ case "${SHELL:-}" in
96
+ *zsh) printf '%s' "$HOME/.zshrc" ;;
97
+ *bash) printf '%s' "$HOME/.bash_profile" ;;
98
+ *) printf '%s' "$HOME/.profile" ;;
99
+ esac
100
+ }
101
+ profile_block_content() {
102
+ local file; file=$(profile_file); [ -f "$file" ] || return 0
103
+ awk -v b="$PROFILE_BEGIN" -v e="$PROFILE_END" '$0==b{f=1;next} $0==e{f=0} f' "$file"
104
+ }
105
+ # write_profile_block <content> — append the marked block, replace it when different, keep it when identical.
106
+ # Returns 0 = unchanged, 3 = written. Sets PROFILE_MSG. Must NOT be called in a subshell (it sets globals).
107
+ write_profile_block() {
108
+ local content=$1 file tmp
109
+ file=$(profile_file); [ -f "$file" ] || : > "$file"
110
+ if [ "$(profile_block_content)" = "$content" ]; then PROFILE_MSG="block already up to date in $file"; return 0; fi
111
+ tmp=$(mktemp)
112
+ awk -v b="$PROFILE_BEGIN" -v e="$PROFILE_END" '$0==b{f=1;next} $0==e{f=0;next} !f' "$file" > "$tmp"
113
+ [ -s "$tmp" ] && [ -n "$(tail -c1 "$tmp")" ] && printf '\n' >> "$tmp"
114
+ { cat "$tmp"; printf '%s\n%s\n%s\n' "$PROFILE_BEGIN" "$content" "$PROFILE_END"; } > "$file"
115
+ rm -f "$tmp"
116
+ SUNGEN_ENV_PROFILE_CHANGED=1
117
+ PROFILE_MSG="written to $file — open a new terminal to apply"
118
+ return 3
119
+ }
120
+ # profile_step <title> <content> — the profile block as a step (honours --no-profile and --check).
121
+ profile_step() {
122
+ local title=$1 content=$2
123
+ if [ "$SUNGEN_ENV_NO_PROFILE" = 1 ]; then log_skip "$title" "--no-profile"; return 0; fi
124
+ if [ "$SUNGEN_ENV_CHECK_ONLY" = 1 ]; then
125
+ if [ "$(profile_block_content)" = "$content" ]; then log_skip "$title" "block already up to date in $(profile_file)"
126
+ else log_fail "$title" "block missing or outdated in $(profile_file) — run without --check to write it"; fi
127
+ return 0
128
+ fi
129
+ write_profile_block "$content"
130
+ case $? in 3) log_ok "$title" "$PROFILE_MSG" ;; *) log_skip "$title" "$PROFILE_MSG" ;; esac
131
+ return 0
132
+ }
133
+
134
+ # print_summary — tallies + the "new terminal" notice. Exit 1 when anything failed.
135
+ print_summary() {
136
+ printf '\n%s%d ok · %d skipped · %d warned · %d failed%s\n' "$C_BOLD" "$COUNT_OK" "$COUNT_SKIP" "$COUNT_WARN" "$COUNT_FAIL" "$C_RESET"
137
+ [ "$SUNGEN_ENV_PROFILE_CHANGED" = 1 ] && printf '%s→ Open a NEW terminal so PATH / JAVA_HOME / ANDROID_HOME apply.%s\n' "$C_WARN" "$C_RESET"
138
+ [ "$COUNT_WARN" -gt 0 ] && printf ' warnings need a manual step — do it, then re-run this script.\n'
139
+ [ "$COUNT_FAIL" -gt 0 ] && return 1
140
+ return 0
141
+ }
@@ -0,0 +1,39 @@
1
+ # verify-appium.ps1 - the final gate: `appium driver doctor uiautomator2` checks the whole chain the
2
+ # driver needs at run time (JAVA_HOME, ANDROID_HOME, adb). Doctor problems are reported as warnings
3
+ # with the doctor's own output - it names the missing piece.
4
+ if (-not $script:SungenEnvLibLoaded) { . (Join-Path $PSScriptRoot 'lib.ps1') }
5
+
6
+ $AppiumCmd = $script:SungenEnvAppiumCmd
7
+ # Doctor prints a required failure as a heavy-ballot-x line; the character is referenced by code point
8
+ # so this file stays ASCII (Windows PowerShell 5.1 reads BOM-less scripts as ANSI).
9
+ $RequiredFailMark = [string][char]0x2716
10
+
11
+ Invoke-Check -Title 'Appium CLI' -Check {
12
+ if (Test-Path $AppiumCmd) { "appium $((& $AppiumCmd -v) 2>$null | Select-Object -First 1)" }
13
+ } -Hint 'not installed in this project - sungen capability add mobile'
14
+
15
+ if ((Test-Path $AppiumCmd) -and -not $script:SungenEnvSkipAndroid) {
16
+ $installed = (& $AppiumCmd driver list --installed --json) 2>$null | Out-String
17
+ if ($installed -match '"uiautomator2"') {
18
+ # Inline (not Invoke-Check) so the failure detail prints AFTER the step title, like every other step.
19
+ $title = 'appium driver doctor uiautomator2'
20
+ $hint = 'fix the required items above (open a NEW terminal first if the environment was just written), then re-run'
21
+ $out = (& $AppiumCmd driver doctor uiautomator2 2>&1) | Out-String
22
+ $rc = $LASTEXITCODE
23
+ # Doctor marks BOTH required and optional problems with the same mark, so read its summary line:
24
+ # "### Diagnostic completed, N required fixes needed, M optional fixes possible. ###"
25
+ $req = $null; if ($out -match 'Diagnostic completed, (\d+) required fix') { $req = [int]$Matches[1] }
26
+ $opt = 0; if ($out -match ', (\d+) optional fix') { $opt = [int]$Matches[1] }
27
+ if ($null -eq $req) {
28
+ # No summary line (older driver) - fall back to the exit code.
29
+ if ($rc -eq 0) { Write-Ok $title 'doctor exited 0' } else { Write-Warn $title "$out`n$hint" }
30
+ } elseif ($req -gt 0) {
31
+ $marks = @(($out -split "`r?`n") | Where-Object { $_.Contains($RequiredFailMark) } | ForEach-Object { $_ -replace '^.*Doctor\s+', '' })
32
+ Write-Warn $title ("$req required fix(es) needed:`n" + ($marks -join "`n") + "`nfull report: npx appium driver doctor uiautomator2`n$hint")
33
+ } else {
34
+ Write-Ok $title "0 required fixes, $opt optional (extras like bundletool / gstreamer - see: npx appium driver doctor uiautomator2)"
35
+ }
36
+ } else {
37
+ Write-Warn 'appium driver doctor uiautomator2' 'driver not installed - see the Appium step above'
38
+ }
39
+ }
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env bash
2
+ # verify-appium.sh — the final gate: `appium driver doctor <driver>` checks the whole chain the
3
+ # driver needs at run time (JAVA_HOME, ANDROID_HOME, adb; Xcode + simctl for xcuitest).
4
+ # Doctor problems are reported as warnings with the doctor's own output — it names the missing piece.
5
+ [ -n "${SUNGEN_ENV_LIB_LOADED:-}" ] || . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib.sh"
6
+
7
+ APPIUM_BIN=$SUNGEN_ENV_APPIUM_BIN
8
+
9
+ appium_cli_check() { [ -x "$APPIUM_BIN" ] || return 1; printf 'appium %s' "$("$APPIUM_BIN" -v 2>/dev/null)"; }
10
+ run_check "Appium CLI" appium_cli_check "not installed in this project — sungen capability add mobile"
11
+
12
+ doctor_check() {
13
+ local out rc req opt
14
+ out=$("$APPIUM_BIN" driver doctor "$CURRENT_DRIVER" 2>&1); rc=$?
15
+ # Doctor marks BOTH required and optional problems with "✖", so read its own summary line instead:
16
+ # "### Diagnostic completed, N required fixes needed, M optional fixes possible. ###"
17
+ req=$(printf '%s\n' "$out" | sed -n -E 's/.*Diagnostic completed, ([0-9]+) required fix.*/\1/p' | tail -n1)
18
+ opt=$(printf '%s\n' "$out" | sed -n -E 's/.*, ([0-9]+) optional fix.*/\1/p' | tail -n1)
19
+ if [ -z "$req" ]; then
20
+ # No summary line (older driver) — fall back to the exit code.
21
+ [ "$rc" -eq 0 ] && { printf 'doctor exited 0'; return 0; }
22
+ printf '%s' "$out"; return 1
23
+ fi
24
+ if [ "$req" -gt 0 ]; then
25
+ printf '%s required fix(es) needed:\n%s\nfull report: npx appium driver doctor %s' "$req" \
26
+ "$(printf '%s\n' "$out" | grep '✖' | sed -E 's/^.*Doctor +//')" "$CURRENT_DRIVER"
27
+ return 1
28
+ fi
29
+ printf '0 required fixes · %s optional (extras like bundletool / gstreamer — see: npx appium driver doctor %s)' "${opt:-0}" "$CURRENT_DRIVER"
30
+ }
31
+
32
+ if [ -x "$APPIUM_BIN" ]; then
33
+ DOCTOR_DRIVERS=""
34
+ [ "$SUNGEN_ENV_SKIP_ANDROID" != 1 ] && DOCTOR_DRIVERS="uiautomator2"
35
+ if [ "$(uname -s)" = Darwin ] && [ "$SUNGEN_ENV_SKIP_IOS" != 1 ]; then DOCTOR_DRIVERS="$DOCTOR_DRIVERS xcuitest"; fi
36
+ for CURRENT_DRIVER in $DOCTOR_DRIVERS; do
37
+ if printf '%s' "$("$APPIUM_BIN" driver list --installed --json 2>/dev/null)" | grep -q "\"$CURRENT_DRIVER\""; then
38
+ run_check "appium driver doctor $CURRENT_DRIVER" doctor_check "fix the required items above (open a NEW terminal first if the profile block was just written), then re-run" warn
39
+ else
40
+ log_warn "appium driver doctor $CURRENT_DRIVER" "driver not installed — see the Appium step above"
41
+ fi
42
+ done
43
+ fi
@@ -0,0 +1,13 @@
1
+ # verify-node.ps1 - Node.js >= 18 (sungen's own engine floor). Never installs Node: sungen is already
2
+ # running on it when this bundle is invoked from `sungen init`; a standalone run gets the guide.
3
+ if (-not $script:SungenEnvLibLoaded) { . (Join-Path $PSScriptRoot 'lib.ps1') }
4
+
5
+ $NodeMin = 18
6
+ Invoke-Check -Title "Node.js >= $NodeMin" -Check {
7
+ if (-not (Get-Command node -ErrorAction SilentlyContinue)) { return $null }
8
+ $v = (& node -v) 2>$null
9
+ $major = 0; if ("$v" -match '(\d+)') { $major = [int]$Matches[1] }
10
+ if ($major -lt $NodeMin) { return $null }
11
+ $npm = (& npm -v) 2>$null
12
+ "node $v, npm $npm"
13
+ } -Hint 'install Node LTS: https://nodejs.org (or: winget install OpenJS.NodeJS.LTS), then open a new terminal'
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env bash
2
+ # verify-node.sh — Node.js ≥ 18 (sungen's own engine floor). Never installs Node: sungen is already
3
+ # running on it when this bundle is invoked from `sungen init`; a standalone run gets the guide.
4
+ [ -n "${SUNGEN_ENV_LIB_LOADED:-}" ] || . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib.sh"
5
+
6
+ NODE_MIN=18
7
+
8
+ node_check() {
9
+ local v major
10
+ have node || return 1
11
+ v=$(node -v 2>/dev/null) || return 1
12
+ major=$(version_major "$v")
13
+ [ "$major" -ge "$NODE_MIN" ] || { printf 'node %s is below %s' "$v" "$NODE_MIN"; return 1; }
14
+ printf 'node %s · npm %s' "$v" "$(npm -v 2>/dev/null || printf '?')"
15
+ }
16
+ run_check "Node.js ≥ $NODE_MIN" node_check "install Node LTS: https://nodejs.org (or: brew install node · nvm install --lts)"