testdriverai 7.1.0 → 7.1.1

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 (325) hide show
  1. package/.env.example +2 -0
  2. package/.github/workflows/linux-tests.yml +28 -0
  3. package/agent/index.js +18 -45
  4. package/agent/interface.js +13 -2
  5. package/agent/lib/commands.js +1 -1
  6. package/agent/lib/redraw.js +1 -1
  7. package/agent/lib/sandbox.js +30 -2
  8. package/agent/lib/valid-version.js +2 -2
  9. package/debugger/index.html +1 -1
  10. package/docs/docs.json +86 -125
  11. package/docs/v6/getting-started/self-hosting.mdx +3 -2
  12. package/docs/v7/_drafts/agents.mdx +852 -0
  13. package/docs/v7/_drafts/auto-cache-key.mdx +167 -0
  14. package/docs/v7/{guides → _drafts}/caching-selectors.mdx +125 -17
  15. package/docs/v7/_drafts/dashcam-title-feature.mdx +89 -0
  16. package/docs/v7/_drafts/error-handling.mdx +501 -0
  17. package/docs/v7/_drafts/implementation-plan.mdx +994 -0
  18. package/docs/v7/_drafts/init-command.mdx +95 -0
  19. package/docs/v7/_drafts/optimal-sdk-design.mdx +1348 -0
  20. package/docs/v7/_drafts/plugin-migration.mdx +222 -0
  21. package/docs/v7/_drafts/prompt-cache.mdx +200 -0
  22. package/docs/{QUICK_START_TEST_RECORDING.md → v7/_drafts/quick-start-test-recording.mdx} +3 -3
  23. package/docs/v7/_drafts/sdk-logging.mdx +222 -0
  24. package/docs/v7/_drafts/sdk-migration.mdx +474 -0
  25. package/docs/v7/_drafts/sdk-v7-complete.mdx +345 -0
  26. package/docs/v7/{guides → _drafts}/self-hosting.mdx +1 -1
  27. package/docs/v7/{guides → _drafts}/troubleshooting.mdx +2 -2
  28. package/docs/v7/{guides → _drafts}/vitest-plugin.mdx +4 -4
  29. package/docs/v7/api/{ai.mdx → act.mdx} +24 -24
  30. package/docs/v7/api/client.mdx +1 -1
  31. package/docs/v7/api/dashcam.mdx +2 -2
  32. package/docs/v7/api/elements.mdx +143 -41
  33. package/docs/v7/api/find.mdx +258 -0
  34. package/docs/v7/api/type.mdx +51 -7
  35. package/docs/v7/features/ai-native.mdx +427 -0
  36. package/docs/v7/features/easy-to-write.mdx +351 -0
  37. package/docs/v7/features/enterprise.mdx +540 -0
  38. package/docs/v7/features/fast.mdx +424 -0
  39. package/docs/v7/features/observable.mdx +623 -0
  40. package/docs/v7/features/powerful.mdx +531 -0
  41. package/docs/v7/features/scalable.mdx +417 -0
  42. package/docs/v7/features/stable.mdx +514 -0
  43. package/docs/v7/getting-started/configuration.mdx +1 -1
  44. package/docs/v7/getting-started/generating-tests.mdx +525 -0
  45. package/docs/v7/getting-started/installation.mdx +486 -0
  46. package/docs/v7/getting-started/quickstart.mdx +51 -5
  47. package/docs/v7/getting-started/running-and-debugging.mdx +511 -0
  48. package/docs/v7/getting-started/setting-up-in-ci.mdx +612 -0
  49. package/docs/v7/getting-started/writing-tests.mdx +535 -0
  50. package/docs/v7/overview/what-is-testdriver.mdx +398 -0
  51. package/docs/v7/playwright.mdx +3 -3
  52. package/docs/v7/presets/chrome.mdx +16 -0
  53. package/docs/v7/presets/electron.mdx +18 -0
  54. package/docs/v7/presets/vscode.mdx +19 -0
  55. package/examples/run-tests-with-recording.sh +70 -0
  56. package/examples/screenshot-example.js +63 -0
  57. package/examples/sdk-awesome-logs-demo.js +177 -0
  58. package/examples/sdk-cache-thresholds.js +96 -0
  59. package/examples/sdk-element-properties.js +155 -0
  60. package/examples/sdk-simple-example.js +65 -0
  61. package/examples/test-recording-example.test.js +166 -0
  62. package/interfaces/cli/commands/init.js +358 -0
  63. package/interfaces/vitest-plugin.mjs +214 -10
  64. package/{src → lib}/core/Dashcam.js +41 -4
  65. package/{src → lib}/vitest/hooks.mjs +118 -100
  66. package/lib/vitest/setup.mjs +44 -0
  67. package/package.json +9 -10
  68. package/sdk.d.ts +15 -2
  69. package/sdk.js +70 -18
  70. package/{self-hosted.yml → setup/aws/self-hosted.yml} +1 -1
  71. package/{testdriver/acceptance-sdk → test/manual}/test-console-logs.test.mjs +1 -1
  72. package/test/manual/test-find-api.js +73 -0
  73. package/test/manual/test-init.sh +54 -0
  74. package/test/manual/test-prompt-cache.js +96 -0
  75. package/test/manual/test-provision-auth.mjs +22 -0
  76. package/test/manual/test-sandbox-render.js +28 -0
  77. package/test/manual/test-sdk-methods.js +15 -0
  78. package/test/manual/test-sdk-refactor.js +53 -0
  79. package/test/manual/test-stack-trace.mjs +57 -0
  80. package/test/testdriver/assert.test.mjs +41 -0
  81. package/{testdriver/acceptance-sdk → test/testdriver}/auto-cache-key-demo.test.mjs +1 -1
  82. package/{testdriver/acceptance-sdk → test/testdriver}/drag-and-drop.test.mjs +1 -1
  83. package/{testdriver/acceptance-sdk → test/testdriver}/element-not-found.test.mjs +1 -1
  84. package/{testdriver/acceptance-sdk → test/testdriver}/exec-js.test.mjs +1 -1
  85. package/{testdriver/acceptance-sdk → test/testdriver}/exec-output.test.mjs +3 -3
  86. package/{testdriver/acceptance-sdk → test/testdriver}/exec-pwsh.test.mjs +3 -3
  87. package/{testdriver/acceptance-sdk → test/testdriver}/focus-window.test.mjs +1 -1
  88. package/{testdriver/acceptance-sdk → test/testdriver}/formatted-logging.test.mjs +1 -1
  89. package/{testdriver/acceptance-sdk → test/testdriver}/hover-image.test.mjs +1 -1
  90. package/{testdriver/acceptance-sdk → test/testdriver}/hover-text-with-description.test.mjs +1 -1
  91. package/{testdriver/acceptance-sdk → test/testdriver}/hover-text.test.mjs +1 -1
  92. package/{testdriver/acceptance-sdk → test/testdriver}/match-image.test.mjs +1 -1
  93. package/{testdriver/acceptance-sdk → test/testdriver}/press-keys.test.mjs +1 -1
  94. package/{testdriver/acceptance-sdk → test/testdriver}/prompt.test.mjs +2 -2
  95. package/{testdriver/acceptance-sdk → test/testdriver}/scroll-keyboard.test.mjs +1 -1
  96. package/{testdriver/acceptance-sdk → test/testdriver}/scroll-until-image.test.mjs +1 -1
  97. package/{testdriver/acceptance-sdk → test/testdriver}/scroll-until-text.test.mjs +1 -1
  98. package/{testdriver/acceptance-sdk → test/testdriver}/scroll.test.mjs +1 -1
  99. package/{src/vitest/lifecycle.mjs → test/testdriver/setup/lifecycleHelpers.mjs} +84 -99
  100. package/test/testdriver/setup/testHelpers.mjs +653 -0
  101. package/{testdriver/acceptance-sdk → test/testdriver}/type.test.mjs +1 -1
  102. package/vitest.config.mjs +11 -57
  103. package/.github/dependabot.yml +0 -11
  104. package/.github/workflows/acceptance-linux.yml +0 -75
  105. package/.github/workflows/acceptance-sdk-tests.yml +0 -133
  106. package/.github/workflows/acceptance-tests.yml +0 -130
  107. package/.github/workflows/lint.yml +0 -27
  108. package/.github/workflows/publish-canary.yml +0 -40
  109. package/.github/workflows/publish-latest.yml +0 -61
  110. package/.github/workflows/test-install.yml +0 -29
  111. package/.vscode/extensions.json +0 -3
  112. package/.vscode/launch.json +0 -22
  113. package/.vscode/settings.json +0 -14
  114. package/AGENTS.md +0 -550
  115. package/CODEOWNERS +0 -2
  116. package/_testdriver/acceptance/assert.yaml +0 -7
  117. package/_testdriver/acceptance/dashcam.yaml +0 -9
  118. package/_testdriver/acceptance/drag-and-drop.yaml +0 -49
  119. package/_testdriver/acceptance/embed.yaml +0 -9
  120. package/_testdriver/acceptance/exec-js.yaml +0 -29
  121. package/_testdriver/acceptance/exec-output.yaml +0 -43
  122. package/_testdriver/acceptance/exec-shell.yaml +0 -40
  123. package/_testdriver/acceptance/focus-window.yaml +0 -16
  124. package/_testdriver/acceptance/hover-image.yaml +0 -18
  125. package/_testdriver/acceptance/hover-text-with-description.yaml +0 -29
  126. package/_testdriver/acceptance/hover-text.yaml +0 -14
  127. package/_testdriver/acceptance/if-else.yaml +0 -31
  128. package/_testdriver/acceptance/match-image.yaml +0 -15
  129. package/_testdriver/acceptance/press-keys.yaml +0 -35
  130. package/_testdriver/acceptance/prompt.yaml +0 -11
  131. package/_testdriver/acceptance/remember.yaml +0 -27
  132. package/_testdriver/acceptance/screenshots/cart.png +0 -0
  133. package/_testdriver/acceptance/scroll-keyboard.yaml +0 -34
  134. package/_testdriver/acceptance/scroll-until-image.yaml +0 -26
  135. package/_testdriver/acceptance/scroll-until-text.yaml +0 -20
  136. package/_testdriver/acceptance/scroll.yaml +0 -33
  137. package/_testdriver/acceptance/snippets/login.yaml +0 -29
  138. package/_testdriver/acceptance/snippets/match-cart.yaml +0 -8
  139. package/_testdriver/acceptance/type.yaml +0 -29
  140. package/_testdriver/behavior/failure.yaml +0 -7
  141. package/_testdriver/behavior/hover-text.yaml +0 -13
  142. package/_testdriver/behavior/lifecycle/postrun.yaml +0 -10
  143. package/_testdriver/behavior/lifecycle/prerun.yaml +0 -8
  144. package/_testdriver/behavior/lifecycle/provision.yaml +0 -8
  145. package/_testdriver/behavior/secrets.yaml +0 -7
  146. package/_testdriver/edge-cases/dashcam-chrome.yaml +0 -8
  147. package/_testdriver/edge-cases/exec-pwsh-multiline.yaml +0 -10
  148. package/_testdriver/edge-cases/js-exception.yaml +0 -8
  149. package/_testdriver/edge-cases/js-promise.yaml +0 -19
  150. package/_testdriver/edge-cases/lifecycle/postrun.yaml +0 -10
  151. package/_testdriver/edge-cases/prompt-in-middle.yaml +0 -23
  152. package/_testdriver/edge-cases/prompt-nested.yaml +0 -7
  153. package/_testdriver/edge-cases/success-test.yaml +0 -9
  154. package/_testdriver/examples/android/example.yaml +0 -12
  155. package/_testdriver/examples/android/lifecycle/postrun.yaml +0 -11
  156. package/_testdriver/examples/android/lifecycle/provision.yaml +0 -47
  157. package/_testdriver/examples/android/readme.md +0 -7
  158. package/_testdriver/examples/chrome-extension/lifecycle/provision.yaml +0 -74
  159. package/_testdriver/examples/desktop/lifecycle/prerun.yaml +0 -0
  160. package/_testdriver/examples/desktop/lifecycle/provision.yaml +0 -64
  161. package/_testdriver/examples/vscode-extension/lifecycle/provision.yaml +0 -73
  162. package/_testdriver/examples/web/lifecycle/postrun.yaml +0 -7
  163. package/_testdriver/examples/web/lifecycle/prerun.yaml +0 -22
  164. package/_testdriver/lifecycle/postrun.yaml +0 -8
  165. package/_testdriver/lifecycle/prerun.yaml +0 -15
  166. package/_testdriver/lifecycle/provision.yaml +0 -25
  167. package/docs/v7/guides/ci-cd/azure.mdx +0 -587
  168. package/docs/v7/guides/ci-cd/circleci.mdx +0 -523
  169. package/docs/v7/guides/ci-cd/github-actions.mdx +0 -457
  170. package/docs/v7/guides/ci-cd/gitlab.mdx +0 -498
  171. package/docs/v7/guides/ci-cd/jenkins.mdx +0 -664
  172. package/docs/v7/guides/ci-cd/travis.mdx +0 -438
  173. package/scripts/view-test-results.mjs +0 -96
  174. package/src/vitest/extended.mjs +0 -108
  175. package/src/vitest/index.mjs +0 -64
  176. package/src/vitest/utils.mjs +0 -150
  177. package/styles/.vale-config/2-MDX.ini +0 -5
  178. package/styles/Microsoft/AMPM.yml +0 -9
  179. package/styles/Microsoft/Accessibility.yml +0 -30
  180. package/styles/Microsoft/Acronyms.yml +0 -64
  181. package/styles/Microsoft/Adverbs.yml +0 -272
  182. package/styles/Microsoft/Auto.yml +0 -11
  183. package/styles/Microsoft/Avoid.yml +0 -14
  184. package/styles/Microsoft/Contractions.yml +0 -50
  185. package/styles/Microsoft/Dashes.yml +0 -13
  186. package/styles/Microsoft/DateFormat.yml +0 -8
  187. package/styles/Microsoft/DateNumbers.yml +0 -40
  188. package/styles/Microsoft/DateOrder.yml +0 -8
  189. package/styles/Microsoft/Ellipses.yml +0 -9
  190. package/styles/Microsoft/FirstPerson.yml +0 -16
  191. package/styles/Microsoft/Foreign.yml +0 -13
  192. package/styles/Microsoft/Gender.yml +0 -8
  193. package/styles/Microsoft/GenderBias.yml +0 -42
  194. package/styles/Microsoft/GeneralURL.yml +0 -11
  195. package/styles/Microsoft/HeadingAcronyms.yml +0 -7
  196. package/styles/Microsoft/HeadingColons.yml +0 -8
  197. package/styles/Microsoft/HeadingPunctuation.yml +0 -13
  198. package/styles/Microsoft/Headings.yml +0 -28
  199. package/styles/Microsoft/Hyphens.yml +0 -14
  200. package/styles/Microsoft/Negative.yml +0 -13
  201. package/styles/Microsoft/Ordinal.yml +0 -13
  202. package/styles/Microsoft/OxfordComma.yml +0 -8
  203. package/styles/Microsoft/Passive.yml +0 -183
  204. package/styles/Microsoft/Percentages.yml +0 -7
  205. package/styles/Microsoft/Plurals.yml +0 -7
  206. package/styles/Microsoft/Quotes.yml +0 -7
  207. package/styles/Microsoft/RangeTime.yml +0 -13
  208. package/styles/Microsoft/Semicolon.yml +0 -8
  209. package/styles/Microsoft/SentenceLength.yml +0 -6
  210. package/styles/Microsoft/Spacing.yml +0 -8
  211. package/styles/Microsoft/Suspended.yml +0 -7
  212. package/styles/Microsoft/Terms.yml +0 -42
  213. package/styles/Microsoft/URLFormat.yml +0 -9
  214. package/styles/Microsoft/Units.yml +0 -16
  215. package/styles/Microsoft/Vocab.yml +0 -25
  216. package/styles/Microsoft/We.yml +0 -11
  217. package/styles/Microsoft/Wordiness.yml +0 -127
  218. package/styles/Microsoft/meta.json +0 -4
  219. package/styles/alex/Ablist.yml +0 -274
  220. package/styles/alex/Condescending.yml +0 -16
  221. package/styles/alex/Gendered.yml +0 -110
  222. package/styles/alex/LGBTQ.yml +0 -55
  223. package/styles/alex/OCD.yml +0 -10
  224. package/styles/alex/Press.yml +0 -12
  225. package/styles/alex/ProfanityLikely.yml +0 -1289
  226. package/styles/alex/ProfanityMaybe.yml +0 -282
  227. package/styles/alex/ProfanityUnlikely.yml +0 -251
  228. package/styles/alex/README.md +0 -27
  229. package/styles/alex/Race.yml +0 -85
  230. package/styles/alex/Suicide.yml +0 -26
  231. package/styles/alex/meta.json +0 -4
  232. package/styles/config/vocabularies/Docs/accept.txt +0 -47
  233. package/styles/config/vocabularies/Docs/reject.txt +0 -4
  234. package/styles/proselint/Airlinese.yml +0 -8
  235. package/styles/proselint/AnimalLabels.yml +0 -48
  236. package/styles/proselint/Annotations.yml +0 -9
  237. package/styles/proselint/Apologizing.yml +0 -8
  238. package/styles/proselint/Archaisms.yml +0 -52
  239. package/styles/proselint/But.yml +0 -8
  240. package/styles/proselint/Cliches.yml +0 -782
  241. package/styles/proselint/CorporateSpeak.yml +0 -30
  242. package/styles/proselint/Currency.yml +0 -5
  243. package/styles/proselint/Cursing.yml +0 -15
  244. package/styles/proselint/DateCase.yml +0 -7
  245. package/styles/proselint/DateMidnight.yml +0 -7
  246. package/styles/proselint/DateRedundancy.yml +0 -10
  247. package/styles/proselint/DateSpacing.yml +0 -7
  248. package/styles/proselint/DenizenLabels.yml +0 -52
  249. package/styles/proselint/Diacritical.yml +0 -95
  250. package/styles/proselint/GenderBias.yml +0 -45
  251. package/styles/proselint/GroupTerms.yml +0 -39
  252. package/styles/proselint/Hedging.yml +0 -8
  253. package/styles/proselint/Hyperbole.yml +0 -6
  254. package/styles/proselint/Jargon.yml +0 -11
  255. package/styles/proselint/LGBTOffensive.yml +0 -13
  256. package/styles/proselint/LGBTTerms.yml +0 -15
  257. package/styles/proselint/Malapropisms.yml +0 -8
  258. package/styles/proselint/Needless.yml +0 -358
  259. package/styles/proselint/Nonwords.yml +0 -38
  260. package/styles/proselint/Oxymorons.yml +0 -22
  261. package/styles/proselint/P-Value.yml +0 -6
  262. package/styles/proselint/RASSyndrome.yml +0 -30
  263. package/styles/proselint/README.md +0 -12
  264. package/styles/proselint/Skunked.yml +0 -13
  265. package/styles/proselint/Spelling.yml +0 -17
  266. package/styles/proselint/Typography.yml +0 -11
  267. package/styles/proselint/Uncomparables.yml +0 -50
  268. package/styles/proselint/Very.yml +0 -6
  269. package/styles/proselint/meta.json +0 -15
  270. package/styles/write-good/Cliches.yml +0 -702
  271. package/styles/write-good/E-Prime.yml +0 -32
  272. package/styles/write-good/Illusions.yml +0 -11
  273. package/styles/write-good/Passive.yml +0 -183
  274. package/styles/write-good/README.md +0 -27
  275. package/styles/write-good/So.yml +0 -5
  276. package/styles/write-good/ThereIs.yml +0 -6
  277. package/styles/write-good/TooWordy.yml +0 -221
  278. package/styles/write-good/Weasel.yml +0 -29
  279. package/styles/write-good/meta.json +0 -4
  280. package/test/dashcam.test.js +0 -137
  281. package/test/mcp-example-test.yaml +0 -27
  282. package/test/test_parser.js +0 -47
  283. package/testdriver/acceptance-sdk/QUICK_REFERENCE.md +0 -61
  284. package/testdriver/acceptance-sdk/README.md +0 -128
  285. package/testdriver/acceptance-sdk/TEST_REPORTING.md +0 -245
  286. package/testdriver/acceptance-sdk/assert.test.mjs +0 -26
  287. package/testdriver/acceptance-sdk/hooks-example.test.mjs +0 -38
  288. package/testdriver/acceptance-sdk/presets-example.test.mjs +0 -87
  289. package/testdriver/acceptance-sdk/setup/testHelpers.mjs +0 -420
  290. package/testdriver/acceptance-sdk/sully-ai.test.mjs +0 -234
  291. package/testdriver/acceptance-sdk/type-checking-demo.js +0 -49
  292. package/vale.ini +0 -18
  293. package/vitest.config.example.js +0 -19
  294. package/vitest.config.mjs.bak +0 -44
  295. /package/docs/{ARCHITECTURE.md → v7/_drafts/architecture.mdx} +0 -0
  296. /package/docs/{AWESOME_LOGS_QUICK_REF.md → v7/_drafts/awesome-logs-quick-ref.mdx} +0 -0
  297. /package/docs/v7/{guides → _drafts}/best-practices.mdx +0 -0
  298. /package/docs/v7/{guides → _drafts}/caching-ai.mdx +0 -0
  299. /package/docs/v7/{guides → _drafts}/caching.mdx +0 -0
  300. /package/docs/{MIGRATION.md → v7/_drafts/cli-to-sdk-migration.mdx} +0 -0
  301. /package/{CONTRIBUTING.md → docs/v7/_drafts/contributing.mdx} +0 -0
  302. /package/docs/v7/{progressive-apis/CORE.md → _drafts/core.mdx} +0 -0
  303. /package/docs/v7/{guides → _drafts}/debugging.mdx +0 -0
  304. /package/docs/v7/{guides → _drafts}/faq.mdx +0 -0
  305. /package/docs/v7/{progressive-apis/HOOKS.md → _drafts/hooks.mdx} +0 -0
  306. /package/docs/v7/{guides → _drafts}/migration.mdx +0 -0
  307. /package/docs/v7/{guides → _drafts}/performance.mdx +0 -0
  308. /package/docs/{PRESETS.md → v7/_drafts/presets.mdx} +0 -0
  309. /package/docs/v7/{progressive-apis/PROGRESSIVE_DISCLOSURE.md → _drafts/progressive-disclosure.mdx} +0 -0
  310. /package/docs/v7/{progressive-apis/PROVISION.md → _drafts/provision.mdx} +0 -0
  311. /package/docs/{SDK_AWESOME_LOGS.md → v7/_drafts/sdk-awesome-logs.mdx} +0 -0
  312. /package/docs/{sdk-browser-rendering.md → v7/_drafts/sdk-browser-rendering.mdx} +0 -0
  313. /package/docs/{TEST_RECORDING.md → v7/_drafts/test-recording.mdx} +0 -0
  314. /package/docs/v7/{guides → _drafts}/vitest.mdx +0 -0
  315. /package/docs/v7/{README.md → overview/readme.mdx} +0 -0
  316. /package/{src → lib}/core/index.d.ts +0 -0
  317. /package/{src → lib}/core/index.js +0 -0
  318. /package/{src → lib}/presets/index.mjs +0 -0
  319. /package/{src → lib}/vitest/hooks.d.ts +0 -0
  320. /package/{debug-locate-response.js → test/manual/debug-locate-response.js} +0 -0
  321. /package/{verify-element-api.js → test/manual/verify-element-api.js} +0 -0
  322. /package/{verify-types.js → test/manual/verify-types.js} +0 -0
  323. /package/{testdriver/acceptance-sdk → test/testdriver}/chrome-extension.test.mjs +0 -0
  324. /package/{testdriver/acceptance-sdk → test/testdriver}/setup/globalTeardown.mjs +0 -0
  325. /package/{testdriver/acceptance-sdk → test/testdriver}/setup/vitestSetup.mjs +0 -0
@@ -0,0 +1,358 @@
1
+ const BaseCommand = require("../lib/base.js");
2
+ const { createCommandDefinitions } = require("../../../agent/interface.js");
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const chalk = require("chalk");
6
+ const { execSync } = require("child_process");
7
+ const readline = require("readline");
8
+
9
+ /**
10
+ * Init command - scaffolds Vitest SDK example tests for TestDriver
11
+ */
12
+ class InitCommand extends BaseCommand {
13
+ async run() {
14
+ await this.parse(InitCommand);
15
+
16
+ console.log(chalk.cyan("\n🚀 Initializing TestDriver project...\n"));
17
+
18
+ await this.setupPackageJson();
19
+ await this.createVitestExample();
20
+ await this.createGitHubWorkflow();
21
+ await this.createGitignore();
22
+ await this.installDependencies();
23
+ await this.promptForApiKey();
24
+
25
+ console.log(chalk.green("\n✅ Project initialized successfully!\n"));
26
+ this.printNextSteps();
27
+ }
28
+
29
+ /**
30
+ * Prompt user for API key and save to .env
31
+ */
32
+ async promptForApiKey() {
33
+ const envPath = path.join(process.cwd(), ".env");
34
+
35
+ // Check if .env already exists with TD_API_KEY
36
+ if (fs.existsSync(envPath)) {
37
+ const envContent = fs.readFileSync(envPath, "utf8");
38
+ if (envContent.includes("TD_API_KEY=")) {
39
+ console.log(chalk.gray("\n API key already configured in .env, skipping...\n"));
40
+ return;
41
+ }
42
+ }
43
+
44
+ console.log(chalk.cyan(" Setting up your TestDriver API key...\n"));
45
+ console.log(chalk.gray(" Get your API key from: https://console.testdriver.ai/team"));
46
+
47
+ // Ask if user wants to open the browser
48
+ const shouldOpen = await this.askYesNo(" Open API keys page in browser? (Y/n): ");
49
+ if (shouldOpen) {
50
+ try {
51
+ // Dynamic import for ES module
52
+ const open = (await import("open")).default;
53
+ await open("https://console.testdriver.ai/team");
54
+ console.log(chalk.gray(" Opening browser...\n"));
55
+ } catch (error) {
56
+ console.log(chalk.yellow(" ⚠️ Could not open browser automatically\n"));
57
+ }
58
+ }
59
+
60
+ // Prompt for API key with hidden input
61
+ const apiKey = await this.promptHidden(" Enter your API key (input will be hidden): ");
62
+
63
+ if (apiKey && apiKey.trim()) {
64
+ // Save to .env
65
+ const envContent = fs.existsSync(envPath)
66
+ ? fs.readFileSync(envPath, "utf8") + "\n"
67
+ : "";
68
+
69
+ fs.writeFileSync(envPath, envContent + `TD_API_KEY=${apiKey.trim()}\n`);
70
+ console.log(chalk.green("\n ✓ API key saved to .env\n"));
71
+ } else {
72
+ console.log(chalk.yellow("\n ⚠️ No API key entered. You can add it later to .env:\n"));
73
+ console.log(chalk.gray(" TD_API_KEY=your_api_key\n"));
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Prompt for hidden input (like password)
79
+ */
80
+ async promptHidden(question) {
81
+ return new Promise((resolve) => {
82
+ const rl = readline.createInterface({
83
+ input: process.stdin,
84
+ output: process.stdout,
85
+ });
86
+
87
+ // Mute output to hide the input
88
+ const stdin = process.stdin;
89
+ const muted = {
90
+ write: () => {},
91
+ };
92
+
93
+ rl.question(question, (answer) => {
94
+ rl.close();
95
+ stdin.removeListener("data", muted.write);
96
+ console.log(""); // New line after hidden input
97
+ resolve(answer);
98
+ });
99
+
100
+ // Mute stdin to hide input
101
+ stdin.on("data", (char) => {
102
+ // Don't write to output (hides the input)
103
+ });
104
+ });
105
+ }
106
+
107
+ /**
108
+ * Ask a yes/no question
109
+ */
110
+ async askYesNo(question) {
111
+ return new Promise((resolve) => {
112
+ const rl = readline.createInterface({
113
+ input: process.stdin,
114
+ output: process.stdout,
115
+ });
116
+
117
+ rl.question(question, (answer) => {
118
+ rl.close();
119
+ const normalized = answer.toLowerCase().trim();
120
+ resolve(normalized === "" || normalized === "y" || normalized === "yes");
121
+ });
122
+ });
123
+ }
124
+
125
+ /**
126
+ * Setup package.json if it doesn't exist
127
+ */
128
+ async setupPackageJson() {
129
+ const packageJsonPath = path.join(process.cwd(), "package.json");
130
+
131
+ if (!fs.existsSync(packageJsonPath)) {
132
+ console.log(chalk.gray(" Creating package.json..."));
133
+
134
+ const packageJson = {
135
+ name: path.basename(process.cwd()),
136
+ version: "1.0.0",
137
+ description: "TestDriver.ai test suite",
138
+ type: "module",
139
+ scripts: {
140
+ test: "vitest run",
141
+ "test:watch": "vitest",
142
+ "test:ui": "vitest --ui"
143
+ },
144
+ keywords: ["testdriver", "testing", "e2e"],
145
+ author: "",
146
+ license: "ISC"
147
+ };
148
+
149
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n");
150
+ console.log(chalk.green(` Created package.json`));
151
+ } else {
152
+ console.log(chalk.gray(" package.json already exists, skipping..."));
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Create a Vitest SDK example
158
+ */
159
+ async createVitestExample() {
160
+ const testDir = path.join(process.cwd(), "tests");
161
+ const testFile = path.join(testDir, "example.test.js");
162
+ const configFile = path.join(process.cwd(), "vitest.config.js");
163
+
164
+ // Create test directory if it doesn't exist
165
+ if (!fs.existsSync(testDir)) {
166
+ fs.mkdirSync(testDir, { recursive: true });
167
+ console.log(chalk.gray(` Created directory: ${testDir}`));
168
+ }
169
+
170
+ // Create example Vitest test
171
+ const vitestContent = `import { test, expect } from 'vitest';
172
+ import { chrome } from 'testdriverai/presets';
173
+
174
+ test('should navigate to example.com and find elements', async (context) => {
175
+ // The chrome preset handles connection, browser launch, and cleanup automatically
176
+ const { testdriver } = await chrome(context, {
177
+ url: 'https://example.com'
178
+ // apiKey automatically read from process.env.TD_API_KEY via .env file
179
+ });
180
+
181
+ // Find and verify elements
182
+ const heading = await testdriver.find('heading that says Example Domain');
183
+ expect(heading.found()).toBe(true);
184
+
185
+ const link = await testdriver.find('More information link');
186
+ expect(link.found()).toBe(true);
187
+ });
188
+ `;
189
+
190
+ fs.writeFileSync(testFile, vitestContent);
191
+ console.log(chalk.green(` Created test file: ${testFile}`));
192
+
193
+ // Create vitest config if it doesn't exist
194
+ if (!fs.existsSync(configFile)) {
195
+ const configContent = `import { defineConfig } from 'vitest/config';
196
+ import TestDriver from 'testdriverai/vitest';
197
+ import dotenv from 'dotenv';
198
+
199
+ // Load environment variables from .env file
200
+ dotenv.config();
201
+
202
+ export default defineConfig({
203
+ plugins: [TestDriver()],
204
+ test: {
205
+ testTimeout: 120000,
206
+ hookTimeout: 120000,
207
+ },
208
+ });
209
+ `;
210
+
211
+ fs.writeFileSync(configFile, configContent);
212
+ console.log(chalk.green(` Created config file: ${configFile}`));
213
+ }
214
+
215
+ }
216
+
217
+ /**
218
+ * Create or update .gitignore to include .env
219
+ */
220
+ async createGitignore() {
221
+ const gitignorePath = path.join(process.cwd(), ".gitignore");
222
+
223
+ let gitignoreContent = "";
224
+ if (fs.existsSync(gitignorePath)) {
225
+ gitignoreContent = fs.readFileSync(gitignorePath, "utf8");
226
+
227
+ // Check if .env is already in .gitignore
228
+ if (gitignoreContent.includes(".env")) {
229
+ console.log(chalk.gray(" .env already in .gitignore, skipping..."));
230
+ return;
231
+ }
232
+ }
233
+
234
+ // Add common ignores including .env
235
+ const ignoresToAdd = [
236
+ "",
237
+ "# TestDriver.ai",
238
+ ".env",
239
+ "node_modules/",
240
+ "test-results/",
241
+ "*.log",
242
+ ];
243
+
244
+ const newContent = gitignoreContent.trim()
245
+ ? gitignoreContent + "\n" + ignoresToAdd.join("\n") + "\n"
246
+ : ignoresToAdd.join("\n") + "\n";
247
+
248
+ fs.writeFileSync(gitignorePath, newContent);
249
+ console.log(chalk.green(" Updated .gitignore"));
250
+ }
251
+
252
+ /**
253
+ * Create GitHub Actions workflow
254
+ */
255
+ async createGitHubWorkflow() {
256
+ const workflowDir = path.join(process.cwd(), ".github", "workflows");
257
+ const workflowFile = path.join(workflowDir, "testdriver.yml");
258
+
259
+ // Create .github/workflows directory if it doesn't exist
260
+ if (!fs.existsSync(workflowDir)) {
261
+ fs.mkdirSync(workflowDir, { recursive: true });
262
+ console.log(chalk.gray(` Created directory: ${workflowDir}`));
263
+ }
264
+
265
+ if (!fs.existsSync(workflowFile)) {
266
+ const workflowContent = `name: TestDriver.ai Tests
267
+
268
+ on:
269
+ push:
270
+ branches: [ main, master ]
271
+ pull_request:
272
+ branches: [ main, master ]
273
+
274
+ jobs:
275
+ test:
276
+ runs-on: ubuntu-latest
277
+
278
+ steps:
279
+ - uses: actions/checkout@v4
280
+
281
+ - name: Setup Node.js
282
+ uses: actions/setup-node@v4
283
+ with:
284
+ node-version: '20'
285
+ cache: 'npm'
286
+
287
+ - name: Install dependencies
288
+ run: npm ci
289
+
290
+ - name: Run TestDriver.ai tests
291
+ env:
292
+ TD_API_KEY: \${{ secrets.TD_API_KEY }}
293
+ run: npm test
294
+
295
+ - name: Upload test results
296
+ if: always()
297
+ uses: actions/upload-artifact@v4
298
+ with:
299
+ name: test-results
300
+ path: test-results/
301
+ retention-days: 30
302
+ `;
303
+
304
+ fs.writeFileSync(workflowFile, workflowContent);
305
+ console.log(chalk.green(` Created GitHub workflow: ${workflowFile}`));
306
+ } else {
307
+ console.log(chalk.gray(" GitHub workflow already exists, skipping..."));
308
+ }
309
+ }
310
+
311
+ /**
312
+ * Install dependencies
313
+ */
314
+ async installDependencies() {
315
+ console.log(chalk.cyan("\n Installing dependencies...\n"));
316
+
317
+ try {
318
+ execSync("npm install -D vitest testdriverai && npm install dotenv", {
319
+ cwd: process.cwd(),
320
+ stdio: "inherit"
321
+ });
322
+ console.log(chalk.green("\n Dependencies installed successfully!"));
323
+ } catch (error) {
324
+ console.log(
325
+ chalk.yellow(
326
+ "\n⚠️ Failed to install dependencies automatically. Please run:",
327
+ ),
328
+ );
329
+ console.log(chalk.gray(" npm install -D vitest testdriverai"));
330
+ console.log(chalk.gray(" npm install dotenv\n"));
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Print next steps
336
+ */
337
+ printNextSteps() {
338
+ console.log(chalk.cyan("Next steps:\n"));
339
+ console.log(" 1. Run your tests:");
340
+ console.log(chalk.gray(" npm test\n"));
341
+ console.log(" 2. For CI/CD, add TD_API_KEY to your GitHub repository secrets");
342
+ console.log(chalk.gray(" Settings → Secrets → Actions → New repository secret\n"));
343
+ console.log(
344
+ chalk.cyan("Learn more at https://docs.testdriver.ai/getting-started\n"),
345
+ );
346
+ }
347
+ }
348
+
349
+ // Get command definition from interface.js
350
+ const tempAgent = { workingDir: process.cwd() };
351
+ const definitions = createCommandDefinitions(tempAgent);
352
+ const commandDef = definitions["init"];
353
+
354
+ InitCommand.description = commandDef?.description || "";
355
+ InitCommand.args = commandDef?.args || {};
356
+ InitCommand.flags = commandDef?.flags || {};
357
+
358
+ module.exports = InitCommand;
@@ -1,9 +1,14 @@
1
+ import { execSync } from "child_process";
1
2
  import crypto from "crypto";
2
3
  import fs from "fs";
4
+ import { createRequire } from "module";
3
5
  import os from "os";
4
6
  import path from "path";
5
7
  import { setTestRunInfo } from "./shared-test-state.mjs";
6
8
 
9
+ // Use createRequire to import CommonJS modules without esbuild processing
10
+ const require = createRequire(import.meta.url);
11
+
7
12
  /**
8
13
  * Simple logger for the vitest plugin
9
14
  * Supports log levels: debug, info, warn, error
@@ -218,6 +223,126 @@ export async function recordTestCaseDirect(token, apiRoot, testCaseData) {
218
223
  return await response.json();
219
224
  }
220
225
 
226
+ // Import TestDriverSDK using require to avoid esbuild transformation issues
227
+ const TestDriverSDK = require('../sdk.js');
228
+
229
+ /**
230
+ * Create a TestDriver client for use in beforeAll/beforeEach hooks
231
+ * This is for the shared instance pattern where one driver is used across multiple tests
232
+ *
233
+ * @param {object} options - TestDriver options
234
+ * @param {string} [options.apiKey] - TestDriver API key (defaults to process.env.TD_API_KEY)
235
+ * @param {boolean} [options.headless] - Run sandbox in headless mode
236
+ * @returns {Promise<TestDriver>} Connected TestDriver client instance
237
+ *
238
+ * @example
239
+ * let testdriver;
240
+ * beforeAll(async () => {
241
+ * testdriver = await createTestDriver({ headless: true });
242
+ * await testdriver.provision.chrome({ url: 'https://example.com' });
243
+ * });
244
+ */
245
+ export async function createTestDriver(options = {}) {
246
+ // Get global plugin options if available
247
+ const pluginOptions = globalThis.__testdriverPlugin?.state?.testDriverOptions || {};
248
+
249
+ // Merge options: plugin global options < test-specific options
250
+ const mergedOptions = { ...pluginOptions, ...options };
251
+
252
+ // Extract TestDriver-specific options
253
+ const apiKey = mergedOptions.apiKey || process.env.TD_API_KEY;
254
+
255
+ // Build config for TestDriverSDK constructor
256
+ const config = { ...mergedOptions };
257
+ delete config.apiKey;
258
+
259
+ // Use TD_API_ROOT from environment if not provided in config
260
+ if (!config.apiRoot && process.env.TD_API_ROOT) {
261
+ config.apiRoot = process.env.TD_API_ROOT;
262
+ }
263
+
264
+ const testdriver = new TestDriverSDK(apiKey, config);
265
+
266
+ // Connect to sandbox
267
+ console.log('[testdriver] Connecting to sandbox...');
268
+ await testdriver.auth();
269
+ await testdriver.connect();
270
+ console.log('[testdriver] ✅ Connected to sandbox');
271
+
272
+ return testdriver;
273
+ }
274
+
275
+ /**
276
+ * Register a test with a shared TestDriver instance
277
+ * Call this at the start of each test to associate the test context with the driver
278
+ *
279
+ * @param {TestDriver} testdriver - TestDriver client instance from createTestDriver
280
+ * @param {object} context - Vitest test context (from async (context) => {})
281
+ *
282
+ * @example
283
+ * it("step01: verify login", async (context) => {
284
+ * registerTest(testdriver, context);
285
+ * const result = await testdriver.assert("login form visible");
286
+ * });
287
+ */
288
+ export function registerTest(testdriver, context) {
289
+ if (!testdriver) {
290
+ throw new Error('registerTest() requires a TestDriver instance');
291
+ }
292
+ if (!context || !context.task) {
293
+ throw new Error('registerTest() requires Vitest context. Pass the context parameter from your test function.');
294
+ }
295
+
296
+ testdriver.__vitestContext = context.task;
297
+ logger.debug(`Registered test: ${context.task.name}`);
298
+ }
299
+
300
+ /**
301
+ * Clean up a TestDriver client created with createTestDriver
302
+ * Call this in afterAll to properly disconnect and stop recordings
303
+ *
304
+ * @param {TestDriver} testdriver - TestDriver client instance
305
+ *
306
+ * @example
307
+ * afterAll(async () => {
308
+ * await cleanupTestDriver(testdriver);
309
+ * });
310
+ */
311
+ export async function cleanupTestDriver(testdriver) {
312
+ if (!testdriver) {
313
+ return;
314
+ }
315
+
316
+ console.log('[testdriver] Cleaning up TestDriver client...');
317
+
318
+ try {
319
+ // Stop dashcam if it was started
320
+ if (testdriver._dashcam && testdriver._dashcam.recording) {
321
+ try {
322
+ const dashcamUrl = await testdriver.dashcam.stop();
323
+ console.log('🎥 Dashcam URL:', dashcamUrl);
324
+
325
+ // Register dashcam URL in memory for the reporter
326
+ if (dashcamUrl && globalThis.__testdriverPlugin?.registerDashcamUrl) {
327
+ const testId = testdriver.__vitestContext?.id || 'unknown';
328
+ const platform = testdriver.os || 'linux';
329
+ globalThis.__testdriverPlugin.registerDashcamUrl(testId, dashcamUrl, platform);
330
+ }
331
+ } catch (error) {
332
+ console.error('❌ Failed to stop dashcam:', error.message);
333
+ if (error.name === 'NotFoundError' || error.responseData?.error === 'NotFoundError') {
334
+ console.log(' ℹ️ Sandbox session already terminated - dashcam stop skipped');
335
+ }
336
+ }
337
+ }
338
+
339
+ await testdriver.disconnect();
340
+ console.log('✅ Client disconnected');
341
+ } catch (error) {
342
+ console.error('Error disconnecting client:', error);
343
+ }
344
+ }
345
+
221
346
  /**
222
347
  * Handle process termination and mark test run as cancelled
223
348
  */
@@ -292,10 +417,10 @@ function registerExitHandlers() {
292
417
  * This sets up global state and provides the registration API
293
418
  */
294
419
  export default function testDriverPlugin(options = {}) {
295
- // Initialize plugin state with options
296
- pluginState.apiKey = options.apiKey;
420
+ // Store options but don't read env vars yet - they may not be loaded
421
+ // Environment variables will be read in onInit after setupFiles run
297
422
  pluginState.apiRoot =
298
- options.apiRoot || process.env.TD_API_ROOT || "http://localhost:1337";
423
+ options.apiRoot || process.env.TD_API_ROOT || "https://testdriver-api.onrender.com";
299
424
  pluginState.ciProvider = detectCI();
300
425
  pluginState.gitInfo = getGitInfo();
301
426
 
@@ -307,7 +432,11 @@ export default function testDriverPlugin(options = {}) {
307
432
  registerExitHandlers();
308
433
 
309
434
  // Note: globalThis setup happens in vitestSetup.mjs for worker processes
310
- logger.debug("Initialized with API root:", pluginState.apiRoot);
435
+ logger.debug("TestDriver plugin initializing...");
436
+ logger.debug("API root:", pluginState.apiRoot);
437
+ logger.debug("API key from options:", !!options.apiKey);
438
+ logger.debug("API key from env (at config time):", !!process.env.TD_API_KEY);
439
+ logger.debug("CI Provider:", pluginState.ciProvider || "none");
311
440
  if (Object.keys(testDriverOptions).length > 0) {
312
441
  logger.debug("Global TestDriver options:", testDriverOptions);
313
442
  }
@@ -322,12 +451,22 @@ export default function testDriverPlugin(options = {}) {
322
451
  class TestDriverReporter {
323
452
  constructor(options = {}) {
324
453
  this.options = options;
325
- logger.debug("Reporter created");
454
+ logger.debug("Reporter created with options:", { hasApiKey: !!options.apiKey, hasApiRoot: !!options.apiRoot });
326
455
  }
327
456
 
328
457
  async onInit(ctx) {
329
458
  this.ctx = ctx;
330
- logger.debug("onInit called");
459
+ logger.debug("onInit called - UPDATED VERSION");
460
+
461
+ // NOW read the API key and API root (after setupFiles have run, including dotenv/config)
462
+ pluginState.apiKey = this.options.apiKey || process.env.TD_API_KEY;
463
+ pluginState.apiRoot = this.options.apiRoot || process.env.TD_API_ROOT || "https://testdriver-api.onrender.com";
464
+ logger.debug("API key from options:", !!this.options.apiKey);
465
+ logger.debug("API key from env (at onInit):", !!process.env.TD_API_KEY);
466
+ logger.debug("API root from options:", this.options.apiRoot);
467
+ logger.debug("API root from env (at onInit):", process.env.TD_API_ROOT);
468
+ logger.debug("Final API key set:", !!pluginState.apiKey);
469
+ logger.debug("Final API root set:", pluginState.apiRoot);
331
470
 
332
471
  // Initialize test run
333
472
  await this.initializeTestRun();
@@ -335,16 +474,23 @@ class TestDriverReporter {
335
474
 
336
475
  async initializeTestRun() {
337
476
  logger.debug("Initializing test run...");
477
+ logger.debug("Current API key in pluginState:", !!pluginState.apiKey);
478
+ logger.debug("Current API root in pluginState:", pluginState.apiRoot);
338
479
 
339
480
  // Check if we should enable the reporter
340
481
  if (!pluginState.apiKey) {
341
- logger.debug("No API key provided, skipping test recording");
482
+ logger.warn("No API key provided, skipping test recording");
483
+ logger.debug("API key sources - options:", !!this.options.apiKey, "env:", !!process.env.TD_API_KEY);
342
484
  return;
343
485
  }
344
486
 
487
+ logger.info("Starting test run initialization with API key...");
488
+
345
489
  try {
346
490
  // Exchange API key for JWT token
491
+ logger.debug("Authenticating with API...");
347
492
  await authenticate();
493
+ logger.debug("Authentication successful, token received");
348
494
 
349
495
  // Generate unique run ID
350
496
  pluginState.testRunId = generateRunId();
@@ -369,7 +515,9 @@ class TestDriverReporter {
369
515
  // Default to linux if no tests write platform info
370
516
  testRunData.platform = "linux";
371
517
 
518
+ logger.debug("Creating test run with data:", testRunData);
372
519
  pluginState.testRun = await createTestRun(testRunData);
520
+ logger.debug("Test run created successfully:", pluginState.testRun);
373
521
 
374
522
  // Store in environment variables for worker processes to access
375
523
  process.env.TD_TEST_RUN_ID = pluginState.testRunId;
@@ -396,17 +544,20 @@ class TestDriverReporter {
396
544
 
397
545
  async onTestRunEnd(testModules, unhandledErrors, reason) {
398
546
  logger.debug("Test run ending with reason:", reason);
547
+ logger.debug("Plugin state - API key present:", !!pluginState.apiKey, "Test run present:", !!pluginState.testRun);
399
548
 
400
549
  if (!pluginState.apiKey) {
401
- logger.debug("Skipping completion - no API key");
550
+ logger.warn("Skipping completion - no API key (was it cleared after init failure?)");
402
551
  return;
403
552
  }
404
553
 
405
554
  if (!pluginState.testRun) {
406
- logger.debug("Skipping completion - no test run created");
555
+ logger.warn("Skipping completion - no test run created (check initialization logs)");
407
556
  return;
408
557
  }
409
558
 
559
+ logger.info("Completing test run...");
560
+
410
561
  try {
411
562
  // Calculate statistics from testModules
412
563
  const stats = calculateStatsFromModules(testModules);
@@ -639,7 +790,7 @@ class TestDriverReporter {
639
790
  const testRunDbId = process.env.TD_TEST_RUN_DB_ID;
640
791
 
641
792
  logger.debug(`Reported test case to API${dashcamUrl ? " with dashcam URL" : ""}`);
642
- logger.info(`🔗 View test: ${pluginState.apiRoot.replace("testdriver-api.onrender.com", "app.testdriver.ai")}/runs/${testRunDbId}/${testCaseDbId}`);
793
+ logger.info(`🔗 View test: ${pluginState.apiRoot.replace("testdriver-api.onrender.com", "console.testdriver.ai")}/runs/${testRunDbId}/${testCaseDbId}`);
643
794
  } catch (error) {
644
795
  logger.error("Failed to report test case:", error.message);
645
796
  }
@@ -743,6 +894,59 @@ function getGitInfo() {
743
894
  if (process.env.CIRCLE_USERNAME) info.author = process.env.CIRCLE_USERNAME;
744
895
  }
745
896
 
897
+ // If not in CI or if commit info is missing, try to get it from local git
898
+ if (!info.commit) {
899
+ try {
900
+ info.commit = execSync("git rev-parse HEAD", {
901
+ encoding: "utf8",
902
+ stdio: ["pipe", "pipe", "ignore"]
903
+ }).trim();
904
+ } catch (e) {
905
+ // Git command failed, ignore
906
+ }
907
+ }
908
+
909
+ if (!info.branch) {
910
+ try {
911
+ info.branch = execSync("git rev-parse --abbrev-ref HEAD", {
912
+ encoding: "utf8",
913
+ stdio: ["pipe", "pipe", "ignore"]
914
+ }).trim();
915
+ } catch (e) {
916
+ // Git command failed, ignore
917
+ }
918
+ }
919
+
920
+ if (!info.author) {
921
+ try {
922
+ info.author = execSync("git config user.name", {
923
+ encoding: "utf8",
924
+ stdio: ["pipe", "pipe", "ignore"]
925
+ }).trim();
926
+ } catch (e) {
927
+ // Git command failed, ignore
928
+ }
929
+ }
930
+
931
+ if (!info.repo) {
932
+ try {
933
+ const remoteUrl = execSync("git config --get remote.origin.url", {
934
+ encoding: "utf8",
935
+ stdio: ["pipe", "pipe", "ignore"]
936
+ }).trim();
937
+
938
+ // Extract repo from git URL (supports both SSH and HTTPS)
939
+ // SSH: git@github.com:user/repo.git
940
+ // HTTPS: https://github.com/user/repo.git
941
+ const match = remoteUrl.match(/[:/]([^/:]+\/[^/:]+?)(\.git)?$/);
942
+ if (match) {
943
+ info.repo = match[1];
944
+ }
945
+ } catch (e) {
946
+ // Git command failed, ignore
947
+ }
948
+ }
949
+
746
950
  return info;
747
951
  }
748
952