@invarn/cibuild 2.9.4 → 2.9.7

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 (547) hide show
  1. package/dist/chunk-35SI42CD.mjs +1 -0
  2. package/dist/chunk-IC45KVLJ.mjs +1 -0
  3. package/dist/chunk-XDY4V23V.mjs +1 -0
  4. package/dist/cli.cjs +14 -14
  5. package/dist/commands.mjs +1 -0
  6. package/dist/lib.mjs +3934 -0
  7. package/package.json +6 -10
  8. package/dist/src/cli.d.ts +0 -3
  9. package/dist/src/cli.d.ts.map +0 -1
  10. package/dist/src/cli.js +0 -399
  11. package/dist/src/commands/a-credential-target-follows-the-flavor-source-set.test.d.ts +0 -38
  12. package/dist/src/commands/a-credential-target-follows-the-flavor-source-set.test.d.ts.map +0 -1
  13. package/dist/src/commands/a-credential-target-follows-the-flavor-source-set.test.js +0 -222
  14. package/dist/src/commands/a-credential-two-workflows-cannot-share-is-declared-per-workflow.test.d.ts +0 -41
  15. package/dist/src/commands/a-credential-two-workflows-cannot-share-is-declared-per-workflow.test.d.ts.map +0 -1
  16. package/dist/src/commands/a-credential-two-workflows-cannot-share-is-declared-per-workflow.test.js +0 -324
  17. package/dist/src/commands/a-properties-stand-in-declares-the-keys-the-build-reads.test.d.ts +0 -2
  18. package/dist/src/commands/a-properties-stand-in-declares-the-keys-the-build-reads.test.d.ts.map +0 -1
  19. package/dist/src/commands/a-properties-stand-in-declares-the-keys-the-build-reads.test.js +0 -382
  20. package/dist/src/commands/an-already-stored-value-does-not-answer-the-target-question.test.d.ts +0 -29
  21. package/dist/src/commands/an-already-stored-value-does-not-answer-the-target-question.test.d.ts.map +0 -1
  22. package/dist/src/commands/an-already-stored-value-does-not-answer-the-target-question.test.js +0 -160
  23. package/dist/src/commands/android-gms-guard.test.d.ts +0 -9
  24. package/dist/src/commands/android-gms-guard.test.d.ts.map +0 -1
  25. package/dist/src/commands/android-gms-guard.test.js +0 -78
  26. package/dist/src/commands/android-java-version.test.d.ts +0 -12
  27. package/dist/src/commands/android-java-version.test.d.ts.map +0 -1
  28. package/dist/src/commands/android-java-version.test.js +0 -209
  29. package/dist/src/commands/android-scanner-java.test.d.ts +0 -2
  30. package/dist/src/commands/android-scanner-java.test.d.ts.map +0 -1
  31. package/dist/src/commands/android-scanner-java.test.js +0 -130
  32. package/dist/src/commands/android-scanner.d.ts +0 -250
  33. package/dist/src/commands/android-scanner.d.ts.map +0 -1
  34. package/dist/src/commands/android-scanner.js +0 -1639
  35. package/dist/src/commands/build.d.ts +0 -215
  36. package/dist/src/commands/build.d.ts.map +0 -1
  37. package/dist/src/commands/build.js +0 -1433
  38. package/dist/src/commands/build.test.d.ts +0 -13
  39. package/dist/src/commands/build.test.d.ts.map +0 -1
  40. package/dist/src/commands/build.test.js +0 -121
  41. package/dist/src/commands/detect-platform.d.ts +0 -13
  42. package/dist/src/commands/detect-platform.d.ts.map +0 -1
  43. package/dist/src/commands/detect-platform.js +0 -51
  44. package/dist/src/commands/detect-project.d.ts +0 -7
  45. package/dist/src/commands/detect-project.d.ts.map +0 -1
  46. package/dist/src/commands/detect-project.js +0 -12
  47. package/dist/src/commands/each-flavor-is-asked-for-its-own-properties-file.test.d.ts +0 -26
  48. package/dist/src/commands/each-flavor-is-asked-for-its-own-properties-file.test.d.ts.map +0 -1
  49. package/dist/src/commands/each-flavor-is-asked-for-its-own-properties-file.test.js +0 -196
  50. package/dist/src/commands/edit.d.ts +0 -12
  51. package/dist/src/commands/edit.d.ts.map +0 -1
  52. package/dist/src/commands/edit.js +0 -652
  53. package/dist/src/commands/edit.test.d.ts +0 -6
  54. package/dist/src/commands/edit.test.d.ts.map +0 -1
  55. package/dist/src/commands/edit.test.js +0 -45
  56. package/dist/src/commands/fidelity-trim.d.ts +0 -13
  57. package/dist/src/commands/fidelity-trim.d.ts.map +0 -1
  58. package/dist/src/commands/fidelity-trim.js +0 -21
  59. package/dist/src/commands/fidelity-trim.test.d.ts +0 -8
  60. package/dist/src/commands/fidelity-trim.test.d.ts.map +0 -1
  61. package/dist/src/commands/fidelity-trim.test.js +0 -79
  62. package/dist/src/commands/file-secret-collector.d.ts +0 -50
  63. package/dist/src/commands/file-secret-collector.d.ts.map +0 -1
  64. package/dist/src/commands/file-secret-collector.js +0 -217
  65. package/dist/src/commands/flavors-a-convention-plugin-creates-are-still-found.test.d.ts +0 -38
  66. package/dist/src/commands/flavors-a-convention-plugin-creates-are-still-found.test.d.ts.map +0 -1
  67. package/dist/src/commands/flavors-a-convention-plugin-creates-are-still-found.test.js +0 -300
  68. package/dist/src/commands/github-workflow.d.ts +0 -5
  69. package/dist/src/commands/github-workflow.d.ts.map +0 -1
  70. package/dist/src/commands/github-workflow.js +0 -45
  71. package/dist/src/commands/index.d.ts +0 -33
  72. package/dist/src/commands/index.d.ts.map +0 -1
  73. package/dist/src/commands/index.js +0 -33
  74. package/dist/src/commands/init.d.ts +0 -20
  75. package/dist/src/commands/init.d.ts.map +0 -1
  76. package/dist/src/commands/init.js +0 -264
  77. package/dist/src/commands/ios-scanner.d.ts +0 -104
  78. package/dist/src/commands/ios-scanner.d.ts.map +0 -1
  79. package/dist/src/commands/ios-scanner.js +0 -492
  80. package/dist/src/commands/ios-scheme-ranking.test.d.ts +0 -2
  81. package/dist/src/commands/ios-scheme-ranking.test.d.ts.map +0 -1
  82. package/dist/src/commands/ios-scheme-ranking.test.js +0 -264
  83. package/dist/src/commands/local-properties-reaches-every-workflow.test.d.ts +0 -30
  84. package/dist/src/commands/local-properties-reaches-every-workflow.test.d.ts.map +0 -1
  85. package/dist/src/commands/local-properties-reaches-every-workflow.test.js +0 -146
  86. package/dist/src/commands/reset.d.ts +0 -7
  87. package/dist/src/commands/reset.d.ts.map +0 -1
  88. package/dist/src/commands/reset.js +0 -81
  89. package/dist/src/commands/run.d.ts +0 -22
  90. package/dist/src/commands/run.d.ts.map +0 -1
  91. package/dist/src/commands/run.js +0 -141
  92. package/dist/src/commands/secrets-sync-workflow.d.ts +0 -15
  93. package/dist/src/commands/secrets-sync-workflow.d.ts.map +0 -1
  94. package/dist/src/commands/secrets-sync-workflow.js +0 -255
  95. package/dist/src/commands/secrets-upload.d.ts +0 -21
  96. package/dist/src/commands/secrets-upload.d.ts.map +0 -1
  97. package/dist/src/commands/secrets-upload.js +0 -177
  98. package/dist/src/commands/secrets-upload.test.d.ts +0 -5
  99. package/dist/src/commands/secrets-upload.test.d.ts.map +0 -1
  100. package/dist/src/commands/secrets-upload.test.js +0 -60
  101. package/dist/src/commands/validate.d.ts +0 -10
  102. package/dist/src/commands/validate.d.ts.map +0 -1
  103. package/dist/src/commands/validate.js +0 -46
  104. package/dist/src/config.d.ts +0 -3
  105. package/dist/src/config.d.ts.map +0 -1
  106. package/dist/src/config.js +0 -47
  107. package/dist/src/envman/cli.d.ts +0 -21
  108. package/dist/src/envman/cli.d.ts.map +0 -1
  109. package/dist/src/envman/cli.js +0 -240
  110. package/dist/src/envman/envman.d.ts +0 -83
  111. package/dist/src/envman/envman.d.ts.map +0 -1
  112. package/dist/src/envman/envman.js +0 -361
  113. package/dist/src/envman/envman.test.d.ts +0 -5
  114. package/dist/src/envman/envman.test.d.ts.map +0 -1
  115. package/dist/src/envman/envman.test.js +0 -236
  116. package/dist/src/envman/envstore-round-trip.test.d.ts +0 -27
  117. package/dist/src/envman/envstore-round-trip.test.d.ts.map +0 -1
  118. package/dist/src/envman/envstore-round-trip.test.js +0 -130
  119. package/dist/src/envman/index.d.ts +0 -23
  120. package/dist/src/envman/index.d.ts.map +0 -1
  121. package/dist/src/envman/index.js +0 -23
  122. package/dist/src/envman/shim.d.ts +0 -16
  123. package/dist/src/envman/shim.d.ts.map +0 -1
  124. package/dist/src/envman/shim.js +0 -66
  125. package/dist/src/envman/types.d.ts +0 -55
  126. package/dist/src/envman/types.d.ts.map +0 -1
  127. package/dist/src/envman/types.js +0 -12
  128. package/dist/src/lib.d.ts +0 -33
  129. package/dist/src/lib.d.ts.map +0 -1
  130. package/dist/src/lib.js +0 -37
  131. package/dist/src/pipeline.d.ts +0 -3
  132. package/dist/src/pipeline.d.ts.map +0 -1
  133. package/dist/src/pipeline.js +0 -57
  134. package/dist/src/runner-node-step.test.d.ts +0 -12
  135. package/dist/src/runner-node-step.test.d.ts.map +0 -1
  136. package/dist/src/runner-node-step.test.js +0 -61
  137. package/dist/src/runner.d.ts +0 -31
  138. package/dist/src/runner.d.ts.map +0 -1
  139. package/dist/src/runner.js +0 -240
  140. package/dist/src/shared/detect-project.d.ts +0 -57
  141. package/dist/src/shared/detect-project.d.ts.map +0 -1
  142. package/dist/src/shared/detect-project.js +0 -219
  143. package/dist/src/shared/detect-project.test.d.ts +0 -2
  144. package/dist/src/shared/detect-project.test.d.ts.map +0 -1
  145. package/dist/src/shared/detect-project.test.js +0 -161
  146. package/dist/src/shared/git.d.ts +0 -25
  147. package/dist/src/shared/git.d.ts.map +0 -1
  148. package/dist/src/shared/git.js +0 -53
  149. package/dist/src/shared/git.test.d.ts +0 -9
  150. package/dist/src/shared/git.test.d.ts.map +0 -1
  151. package/dist/src/shared/git.test.js +0 -66
  152. package/dist/src/shared/gitignore.d.ts +0 -6
  153. package/dist/src/shared/gitignore.d.ts.map +0 -1
  154. package/dist/src/shared/gitignore.js +0 -26
  155. package/dist/src/shared/gradle-settings.d.ts +0 -49
  156. package/dist/src/shared/gradle-settings.d.ts.map +0 -1
  157. package/dist/src/shared/gradle-settings.js +0 -138
  158. package/dist/src/shared/gradle-settings.test.d.ts +0 -2
  159. package/dist/src/shared/gradle-settings.test.d.ts.map +0 -1
  160. package/dist/src/shared/gradle-settings.test.js +0 -65
  161. package/dist/src/shared/prompts.d.ts +0 -30
  162. package/dist/src/shared/prompts.d.ts.map +0 -1
  163. package/dist/src/shared/prompts.js +0 -121
  164. package/dist/src/shared/xcode-container.test.d.ts +0 -2
  165. package/dist/src/shared/xcode-container.test.d.ts.map +0 -1
  166. package/dist/src/shared/xcode-container.test.js +0 -123
  167. package/dist/src/step-marker.d.ts +0 -12
  168. package/dist/src/step-marker.d.ts.map +0 -1
  169. package/dist/src/step-marker.js +0 -29
  170. package/dist/src/step-marker.test.d.ts +0 -11
  171. package/dist/src/step-marker.test.d.ts.map +0 -1
  172. package/dist/src/step-marker.test.js +0 -45
  173. package/dist/src/types.d.ts +0 -58
  174. package/dist/src/types.d.ts.map +0 -1
  175. package/dist/src/types.js +0 -2
  176. package/dist/src/yaml/a-cache-path-must-not-swallow-a-credential.test.d.ts +0 -28
  177. package/dist/src/yaml/a-cache-path-must-not-swallow-a-credential.test.d.ts.map +0 -1
  178. package/dist/src/yaml/a-cache-path-must-not-swallow-a-credential.test.js +0 -223
  179. package/dist/src/yaml/a-declared-secret-reaches-the-file-step.test.d.ts +0 -20
  180. package/dist/src/yaml/a-declared-secret-reaches-the-file-step.test.d.ts.map +0 -1
  181. package/dist/src/yaml/a-declared-secret-reaches-the-file-step.test.js +0 -201
  182. package/dist/src/yaml/a-declared-slot-may-name-the-workflow-it-belongs-to.test.d.ts +0 -37
  183. package/dist/src/yaml/a-declared-slot-may-name-the-workflow-it-belongs-to.test.d.ts.map +0 -1
  184. package/dist/src/yaml/a-declared-slot-may-name-the-workflow-it-belongs-to.test.js +0 -255
  185. package/dist/src/yaml/a-missing-credential-is-loud.test.d.ts +0 -32
  186. package/dist/src/yaml/a-missing-credential-is-loud.test.d.ts.map +0 -1
  187. package/dist/src/yaml/a-missing-credential-is-loud.test.js +0 -301
  188. package/dist/src/yaml/a-runtime-requirement-names-a-real-provider.test.d.ts +0 -18
  189. package/dist/src/yaml/a-runtime-requirement-names-a-real-provider.test.d.ts.map +0 -1
  190. package/dist/src/yaml/a-runtime-requirement-names-a-real-provider.test.js +0 -66
  191. package/dist/src/yaml/a-scoped-slot-must-name-a-workflow-the-pipeline-has.test.d.ts +0 -34
  192. package/dist/src/yaml/a-scoped-slot-must-name-a-workflow-the-pipeline-has.test.d.ts.map +0 -1
  193. package/dist/src/yaml/a-scoped-slot-must-name-a-workflow-the-pipeline-has.test.js +0 -218
  194. package/dist/src/yaml/a-value-this-instance-stored-is-a-value-it-has.test.d.ts +0 -27
  195. package/dist/src/yaml/a-value-this-instance-stored-is-a-value-it-has.test.d.ts.map +0 -1
  196. package/dist/src/yaml/a-value-this-instance-stored-is-a-value-it-has.test.js +0 -126
  197. package/dist/src/yaml/bitrise-compat.d.ts +0 -65
  198. package/dist/src/yaml/bitrise-compat.d.ts.map +0 -1
  199. package/dist/src/yaml/bitrise-compat.js +0 -206
  200. package/dist/src/yaml/bitrise-compat.test.d.ts +0 -5
  201. package/dist/src/yaml/bitrise-compat.test.d.ts.map +0 -1
  202. package/dist/src/yaml/bitrise-compat.test.js +0 -347
  203. package/dist/src/yaml/cache-key-missing-file.test.d.ts +0 -12
  204. package/dist/src/yaml/cache-key-missing-file.test.d.ts.map +0 -1
  205. package/dist/src/yaml/cache-key-missing-file.test.js +0 -116
  206. package/dist/src/yaml/converter.d.ts +0 -103
  207. package/dist/src/yaml/converter.d.ts.map +0 -1
  208. package/dist/src/yaml/converter.js +0 -420
  209. package/dist/src/yaml/converter.test.d.ts +0 -5
  210. package/dist/src/yaml/converter.test.d.ts.map +0 -1
  211. package/dist/src/yaml/converter.test.js +0 -548
  212. package/dist/src/yaml/e2e.test.d.ts +0 -6
  213. package/dist/src/yaml/e2e.test.d.ts.map +0 -1
  214. package/dist/src/yaml/e2e.test.js +0 -446
  215. package/dist/src/yaml/env-resolver.d.ts +0 -218
  216. package/dist/src/yaml/env-resolver.d.ts.map +0 -1
  217. package/dist/src/yaml/env-resolver.js +0 -591
  218. package/dist/src/yaml/env-resolver.test.d.ts +0 -5
  219. package/dist/src/yaml/env-resolver.test.d.ts.map +0 -1
  220. package/dist/src/yaml/env-resolver.test.js +0 -502
  221. package/dist/src/yaml/every-build-log-witnesses-the-commit-on-disk.test.d.ts +0 -16
  222. package/dist/src/yaml/every-build-log-witnesses-the-commit-on-disk.test.d.ts.map +0 -1
  223. package/dist/src/yaml/every-build-log-witnesses-the-commit-on-disk.test.js +0 -126
  224. package/dist/src/yaml/fidelity-input-digest.d.ts +0 -37
  225. package/dist/src/yaml/fidelity-input-digest.d.ts.map +0 -1
  226. package/dist/src/yaml/fidelity-input-digest.js +0 -208
  227. package/dist/src/yaml/fidelity-input-digest.test.d.ts +0 -2
  228. package/dist/src/yaml/fidelity-input-digest.test.d.ts.map +0 -1
  229. package/dist/src/yaml/fidelity-input-digest.test.js +0 -167
  230. package/dist/src/yaml/fidelity-manifest.d.ts +0 -126
  231. package/dist/src/yaml/fidelity-manifest.d.ts.map +0 -1
  232. package/dist/src/yaml/fidelity-manifest.js +0 -134
  233. package/dist/src/yaml/fidelity-manifest.test.d.ts +0 -12
  234. package/dist/src/yaml/fidelity-manifest.test.d.ts.map +0 -1
  235. package/dist/src/yaml/fidelity-manifest.test.js +0 -251
  236. package/dist/src/yaml/interactive-prompts.d.ts +0 -71
  237. package/dist/src/yaml/interactive-prompts.d.ts.map +0 -1
  238. package/dist/src/yaml/interactive-prompts.js +0 -260
  239. package/dist/src/yaml/meta-helpers.d.ts +0 -28
  240. package/dist/src/yaml/meta-helpers.d.ts.map +0 -1
  241. package/dist/src/yaml/meta-helpers.js +0 -46
  242. package/dist/src/yaml/missing-env-handler.d.ts +0 -45
  243. package/dist/src/yaml/missing-env-handler.d.ts.map +0 -1
  244. package/dist/src/yaml/missing-env-handler.js +0 -64
  245. package/dist/src/yaml/parser.d.ts +0 -33
  246. package/dist/src/yaml/parser.d.ts.map +0 -1
  247. package/dist/src/yaml/parser.js +0 -148
  248. package/dist/src/yaml/parser.test.d.ts +0 -5
  249. package/dist/src/yaml/parser.test.d.ts.map +0 -1
  250. package/dist/src/yaml/parser.test.js +0 -83
  251. package/dist/src/yaml/pipeline-with-secrets.d.ts +0 -25
  252. package/dist/src/yaml/pipeline-with-secrets.d.ts.map +0 -1
  253. package/dist/src/yaml/pipeline-with-secrets.js +0 -76
  254. package/dist/src/yaml/platform-detector.d.ts +0 -83
  255. package/dist/src/yaml/platform-detector.d.ts.map +0 -1
  256. package/dist/src/yaml/platform-detector.js +0 -189
  257. package/dist/src/yaml/platform-detector.test.d.ts +0 -5
  258. package/dist/src/yaml/platform-detector.test.d.ts.map +0 -1
  259. package/dist/src/yaml/platform-detector.test.js +0 -525
  260. package/dist/src/yaml/preflight-validation.d.ts +0 -29
  261. package/dist/src/yaml/preflight-validation.d.ts.map +0 -1
  262. package/dist/src/yaml/preflight-validation.js +0 -21
  263. package/dist/src/yaml/secrets-manager.d.ts +0 -87
  264. package/dist/src/yaml/secrets-manager.d.ts.map +0 -1
  265. package/dist/src/yaml/secrets-manager.js +0 -230
  266. package/dist/src/yaml/shell-assignments.d.ts +0 -40
  267. package/dist/src/yaml/shell-assignments.d.ts.map +0 -1
  268. package/dist/src/yaml/shell-assignments.js +0 -77
  269. package/dist/src/yaml/step-validator-check-failure.test.d.ts +0 -24
  270. package/dist/src/yaml/step-validator-check-failure.test.d.ts.map +0 -1
  271. package/dist/src/yaml/step-validator-check-failure.test.js +0 -130
  272. package/dist/src/yaml/step-validator-self-assigned.test.d.ts +0 -23
  273. package/dist/src/yaml/step-validator-self-assigned.test.d.ts.map +0 -1
  274. package/dist/src/yaml/step-validator-self-assigned.test.js +0 -191
  275. package/dist/src/yaml/step-validator.d.ts +0 -67
  276. package/dist/src/yaml/step-validator.d.ts.map +0 -1
  277. package/dist/src/yaml/step-validator.js +0 -466
  278. package/dist/src/yaml/step-validator.test.d.ts +0 -18
  279. package/dist/src/yaml/step-validator.test.d.ts.map +0 -1
  280. package/dist/src/yaml/step-validator.test.js +0 -140
  281. package/dist/src/yaml/steps/android-sign.d.ts +0 -35
  282. package/dist/src/yaml/steps/android-sign.d.ts.map +0 -1
  283. package/dist/src/yaml/steps/android-sign.js +0 -147
  284. package/dist/src/yaml/steps/android-variant-resolution.test.d.ts +0 -2
  285. package/dist/src/yaml/steps/android-variant-resolution.test.d.ts.map +0 -1
  286. package/dist/src/yaml/steps/android-variant-resolution.test.js +0 -260
  287. package/dist/src/yaml/steps/android-version.d.ts +0 -26
  288. package/dist/src/yaml/steps/android-version.d.ts.map +0 -1
  289. package/dist/src/yaml/steps/android-version.js +0 -128
  290. package/dist/src/yaml/steps/android-version.test.d.ts +0 -5
  291. package/dist/src/yaml/steps/android-version.test.d.ts.map +0 -1
  292. package/dist/src/yaml/steps/android-version.test.js +0 -196
  293. package/dist/src/yaml/steps/android.d.ts +0 -156
  294. package/dist/src/yaml/steps/android.d.ts.map +0 -1
  295. package/dist/src/yaml/steps/android.js +0 -1084
  296. package/dist/src/yaml/steps/app-store-deploy.d.ts +0 -48
  297. package/dist/src/yaml/steps/app-store-deploy.d.ts.map +0 -1
  298. package/dist/src/yaml/steps/app-store-deploy.js +0 -167
  299. package/dist/src/yaml/steps/base.d.ts +0 -263
  300. package/dist/src/yaml/steps/base.d.ts.map +0 -1
  301. package/dist/src/yaml/steps/base.js +0 -380
  302. package/dist/src/yaml/steps/bitrise-android-tools.d.ts +0 -26
  303. package/dist/src/yaml/steps/bitrise-android-tools.d.ts.map +0 -1
  304. package/dist/src/yaml/steps/bitrise-android-tools.js +0 -200
  305. package/dist/src/yaml/steps/bitrise-android-tools.test.d.ts +0 -5
  306. package/dist/src/yaml/steps/bitrise-android-tools.test.d.ts.map +0 -1
  307. package/dist/src/yaml/steps/bitrise-android-tools.test.js +0 -280
  308. package/dist/src/yaml/steps/bitrise-apk-info.d.ts +0 -22
  309. package/dist/src/yaml/steps/bitrise-apk-info.d.ts.map +0 -1
  310. package/dist/src/yaml/steps/bitrise-apk-info.js +0 -144
  311. package/dist/src/yaml/steps/bitrise-apk-info.test.d.ts +0 -5
  312. package/dist/src/yaml/steps/bitrise-apk-info.test.d.ts.map +0 -1
  313. package/dist/src/yaml/steps/bitrise-apk-info.test.js +0 -331
  314. package/dist/src/yaml/steps/bitrise-slack.d.ts +0 -49
  315. package/dist/src/yaml/steps/bitrise-slack.d.ts.map +0 -1
  316. package/dist/src/yaml/steps/bitrise-slack.js +0 -280
  317. package/dist/src/yaml/steps/bitrise-slack.test.d.ts +0 -5
  318. package/dist/src/yaml/steps/bitrise-slack.test.d.ts.map +0 -1
  319. package/dist/src/yaml/steps/bitrise-slack.test.js +0 -484
  320. package/dist/src/yaml/steps/bitrise-ssh.d.ts +0 -27
  321. package/dist/src/yaml/steps/bitrise-ssh.d.ts.map +0 -1
  322. package/dist/src/yaml/steps/bitrise-ssh.js +0 -134
  323. package/dist/src/yaml/steps/bitrise-ssh.test.d.ts +0 -5
  324. package/dist/src/yaml/steps/bitrise-ssh.test.d.ts.map +0 -1
  325. package/dist/src/yaml/steps/bitrise-ssh.test.js +0 -205
  326. package/dist/src/yaml/steps/cache-pull-daemon.test.d.ts +0 -27
  327. package/dist/src/yaml/steps/cache-pull-daemon.test.d.ts.map +0 -1
  328. package/dist/src/yaml/steps/cache-pull-daemon.test.js +0 -212
  329. package/dist/src/yaml/steps/cache-pull-restore-roots.test.d.ts +0 -42
  330. package/dist/src/yaml/steps/cache-pull-restore-roots.test.d.ts.map +0 -1
  331. package/dist/src/yaml/steps/cache-pull-restore-roots.test.js +0 -209
  332. package/dist/src/yaml/steps/cache-push-daemon.test.d.ts +0 -27
  333. package/dist/src/yaml/steps/cache-push-daemon.test.d.ts.map +0 -1
  334. package/dist/src/yaml/steps/cache-push-daemon.test.js +0 -171
  335. package/dist/src/yaml/steps/cache.d.ts +0 -59
  336. package/dist/src/yaml/steps/cache.d.ts.map +0 -1
  337. package/dist/src/yaml/steps/cache.js +0 -1027
  338. package/dist/src/yaml/steps/cocoapods-deployment-floor.d.ts +0 -42
  339. package/dist/src/yaml/steps/cocoapods-deployment-floor.d.ts.map +0 -1
  340. package/dist/src/yaml/steps/cocoapods-deployment-floor.js +0 -140
  341. package/dist/src/yaml/steps/cocoapods-deployment-floor.test.d.ts +0 -11
  342. package/dist/src/yaml/steps/cocoapods-deployment-floor.test.d.ts.map +0 -1
  343. package/dist/src/yaml/steps/cocoapods-deployment-floor.test.js +0 -176
  344. package/dist/src/yaml/steps/cocoapods-install-emits-valid-bash.test.d.ts +0 -22
  345. package/dist/src/yaml/steps/cocoapods-install-emits-valid-bash.test.d.ts.map +0 -1
  346. package/dist/src/yaml/steps/cocoapods-install-emits-valid-bash.test.js +0 -97
  347. package/dist/src/yaml/steps/cocoapods-locked-bundler.test.d.ts +0 -17
  348. package/dist/src/yaml/steps/cocoapods-locked-bundler.test.d.ts.map +0 -1
  349. package/dist/src/yaml/steps/cocoapods-locked-bundler.test.js +0 -282
  350. package/dist/src/yaml/steps/cocoapods-skip-if-absent.test.d.ts +0 -17
  351. package/dist/src/yaml/steps/cocoapods-skip-if-absent.test.d.ts.map +0 -1
  352. package/dist/src/yaml/steps/cocoapods-skip-if-absent.test.js +0 -132
  353. package/dist/src/yaml/steps/envstore-loader.test.d.ts +0 -15
  354. package/dist/src/yaml/steps/envstore-loader.test.d.ts.map +0 -1
  355. package/dist/src/yaml/steps/envstore-loader.test.js +0 -143
  356. package/dist/src/yaml/steps/fastlane.d.ts +0 -27
  357. package/dist/src/yaml/steps/fastlane.d.ts.map +0 -1
  358. package/dist/src/yaml/steps/fastlane.js +0 -79
  359. package/dist/src/yaml/steps/fidelity-aspect-guard.d.ts +0 -53
  360. package/dist/src/yaml/steps/fidelity-aspect-guard.d.ts.map +0 -1
  361. package/dist/src/yaml/steps/fidelity-aspect-guard.js +0 -72
  362. package/dist/src/yaml/steps/fidelity-aspect-guard.test.d.ts +0 -11
  363. package/dist/src/yaml/steps/fidelity-aspect-guard.test.d.ts.map +0 -1
  364. package/dist/src/yaml/steps/fidelity-aspect-guard.test.js +0 -50
  365. package/dist/src/yaml/steps/fidelity-reference-provenance.d.ts +0 -87
  366. package/dist/src/yaml/steps/fidelity-reference-provenance.d.ts.map +0 -1
  367. package/dist/src/yaml/steps/fidelity-reference-provenance.js +0 -111
  368. package/dist/src/yaml/steps/fidelity-reference-provenance.test.d.ts +0 -22
  369. package/dist/src/yaml/steps/fidelity-reference-provenance.test.d.ts.map +0 -1
  370. package/dist/src/yaml/steps/fidelity-reference-provenance.test.js +0 -118
  371. package/dist/src/yaml/steps/file.d.ts +0 -52
  372. package/dist/src/yaml/steps/file.d.ts.map +0 -1
  373. package/dist/src/yaml/steps/file.js +0 -108
  374. package/dist/src/yaml/steps/flutter.d.ts +0 -63
  375. package/dist/src/yaml/steps/flutter.d.ts.map +0 -1
  376. package/dist/src/yaml/steps/flutter.js +0 -215
  377. package/dist/src/yaml/steps/git-clone-names-the-inputs-it-ignores.test.d.ts +0 -16
  378. package/dist/src/yaml/steps/git-clone-names-the-inputs-it-ignores.test.d.ts.map +0 -1
  379. package/dist/src/yaml/steps/git-clone-names-the-inputs-it-ignores.test.js +0 -127
  380. package/dist/src/yaml/steps/git-clone.d.ts +0 -32
  381. package/dist/src/yaml/steps/git-clone.d.ts.map +0 -1
  382. package/dist/src/yaml/steps/git-clone.js +0 -171
  383. package/dist/src/yaml/steps/git-fetch-retry-exec.test.d.ts +0 -10
  384. package/dist/src/yaml/steps/git-fetch-retry-exec.test.d.ts.map +0 -1
  385. package/dist/src/yaml/steps/git-fetch-retry-exec.test.js +0 -162
  386. package/dist/src/yaml/steps/git-fetch-retry.d.ts +0 -62
  387. package/dist/src/yaml/steps/git-fetch-retry.d.ts.map +0 -1
  388. package/dist/src/yaml/steps/git-fetch-retry.js +0 -155
  389. package/dist/src/yaml/steps/git-fetch-retry.test.d.ts +0 -5
  390. package/dist/src/yaml/steps/git-fetch-retry.test.d.ts.map +0 -1
  391. package/dist/src/yaml/steps/git-fetch-retry.test.js +0 -86
  392. package/dist/src/yaml/steps/google-play-deploy.d.ts +0 -37
  393. package/dist/src/yaml/steps/google-play-deploy.d.ts.map +0 -1
  394. package/dist/src/yaml/steps/google-play-deploy.js +0 -193
  395. package/dist/src/yaml/steps/google-play-deploy.test.d.ts +0 -5
  396. package/dist/src/yaml/steps/google-play-deploy.test.d.ts.map +0 -1
  397. package/dist/src/yaml/steps/google-play-deploy.test.js +0 -310
  398. package/dist/src/yaml/steps/gradle-memory.d.ts +0 -61
  399. package/dist/src/yaml/steps/gradle-memory.d.ts.map +0 -1
  400. package/dist/src/yaml/steps/gradle-memory.js +0 -311
  401. package/dist/src/yaml/steps/gradle-memory.test.d.ts +0 -2
  402. package/dist/src/yaml/steps/gradle-memory.test.d.ts.map +0 -1
  403. package/dist/src/yaml/steps/gradle-memory.test.js +0 -297
  404. package/dist/src/yaml/steps/gradle-sdk-cmake.d.ts +0 -28
  405. package/dist/src/yaml/steps/gradle-sdk-cmake.d.ts.map +0 -1
  406. package/dist/src/yaml/steps/gradle-sdk-cmake.js +0 -107
  407. package/dist/src/yaml/steps/gradle-sdk-cmake.test.d.ts +0 -2
  408. package/dist/src/yaml/steps/gradle-sdk-cmake.test.d.ts.map +0 -1
  409. package/dist/src/yaml/steps/gradle-sdk-cmake.test.js +0 -183
  410. package/dist/src/yaml/steps/index.d.ts +0 -10
  411. package/dist/src/yaml/steps/index.d.ts.map +0 -1
  412. package/dist/src/yaml/steps/index.js +0 -1461
  413. package/dist/src/yaml/steps/ios-deps.d.ts +0 -78
  414. package/dist/src/yaml/steps/ios-deps.d.ts.map +0 -1
  415. package/dist/src/yaml/steps/ios-deps.js +0 -279
  416. package/dist/src/yaml/steps/ios-deps.test.d.ts +0 -5
  417. package/dist/src/yaml/steps/ios-deps.test.d.ts.map +0 -1
  418. package/dist/src/yaml/steps/ios-deps.test.js +0 -90
  419. package/dist/src/yaml/steps/ios-signing.d.ts +0 -31
  420. package/dist/src/yaml/steps/ios-signing.d.ts.map +0 -1
  421. package/dist/src/yaml/steps/ios-signing.js +0 -144
  422. package/dist/src/yaml/steps/ios-version.d.ts +0 -47
  423. package/dist/src/yaml/steps/ios-version.d.ts.map +0 -1
  424. package/dist/src/yaml/steps/ios-version.js +0 -151
  425. package/dist/src/yaml/steps/java-home-version-spec.test.d.ts +0 -10
  426. package/dist/src/yaml/steps/java-home-version-spec.test.d.ts.map +0 -1
  427. package/dist/src/yaml/steps/java-home-version-spec.test.js +0 -58
  428. package/dist/src/yaml/steps/linting.d.ts +0 -47
  429. package/dist/src/yaml/steps/linting.d.ts.map +0 -1
  430. package/dist/src/yaml/steps/linting.js +0 -148
  431. package/dist/src/yaml/steps/phase2.test.d.ts +0 -6
  432. package/dist/src/yaml/steps/phase2.test.d.ts.map +0 -1
  433. package/dist/src/yaml/steps/phase2.test.js +0 -197
  434. package/dist/src/yaml/steps/phase3.test.d.ts +0 -5
  435. package/dist/src/yaml/steps/phase3.test.d.ts.map +0 -1
  436. package/dist/src/yaml/steps/phase3.test.js +0 -144
  437. package/dist/src/yaml/steps/phase4.test.d.ts +0 -5
  438. package/dist/src/yaml/steps/phase4.test.d.ts.map +0 -1
  439. package/dist/src/yaml/steps/phase4.test.js +0 -166
  440. package/dist/src/yaml/steps/phase5.test.d.ts +0 -6
  441. package/dist/src/yaml/steps/phase5.test.d.ts.map +0 -1
  442. package/dist/src/yaml/steps/phase5.test.js +0 -263
  443. package/dist/src/yaml/steps/registry.d.ts +0 -88
  444. package/dist/src/yaml/steps/registry.d.ts.map +0 -1
  445. package/dist/src/yaml/steps/registry.js +0 -125
  446. package/dist/src/yaml/steps/registry.test.d.ts +0 -5
  447. package/dist/src/yaml/steps/registry.test.d.ts.map +0 -1
  448. package/dist/src/yaml/steps/registry.test.js +0 -235
  449. package/dist/src/yaml/steps/release.d.ts +0 -50
  450. package/dist/src/yaml/steps/release.d.ts.map +0 -1
  451. package/dist/src/yaml/steps/release.js +0 -154
  452. package/dist/src/yaml/steps/render-post-processor.d.ts +0 -100
  453. package/dist/src/yaml/steps/render-post-processor.d.ts.map +0 -1
  454. package/dist/src/yaml/steps/render-post-processor.js +0 -270
  455. package/dist/src/yaml/steps/render-post-processor.test.d.ts +0 -13
  456. package/dist/src/yaml/steps/render-post-processor.test.d.ts.map +0 -1
  457. package/dist/src/yaml/steps/render-post-processor.test.js +0 -220
  458. package/dist/src/yaml/steps/script.d.ts +0 -23
  459. package/dist/src/yaml/steps/script.d.ts.map +0 -1
  460. package/dist/src/yaml/steps/script.js +0 -63
  461. package/dist/src/yaml/steps/spec-validation.test.d.ts +0 -6
  462. package/dist/src/yaml/steps/spec-validation.test.d.ts.map +0 -1
  463. package/dist/src/yaml/steps/spec-validation.test.js +0 -130
  464. package/dist/src/yaml/steps/steps.test.d.ts +0 -6
  465. package/dist/src/yaml/steps/steps.test.d.ts.map +0 -1
  466. package/dist/src/yaml/steps/steps.test.js +0 -881
  467. package/dist/src/yaml/steps/structural-facts.d.ts +0 -992
  468. package/dist/src/yaml/steps/structural-facts.d.ts.map +0 -1
  469. package/dist/src/yaml/steps/structural-facts.js +0 -3276
  470. package/dist/src/yaml/steps/structural-facts.test.d.ts +0 -12
  471. package/dist/src/yaml/steps/structural-facts.test.d.ts.map +0 -1
  472. package/dist/src/yaml/steps/structural-facts.test.js +0 -3039
  473. package/dist/src/yaml/steps/test-config.d.ts +0 -5
  474. package/dist/src/yaml/steps/test-config.d.ts.map +0 -1
  475. package/dist/src/yaml/steps/test-config.js +0 -25
  476. package/dist/src/yaml/steps/trim-align-corpus.test.d.ts +0 -28
  477. package/dist/src/yaml/steps/trim-align-corpus.test.d.ts.map +0 -1
  478. package/dist/src/yaml/steps/trim-align-corpus.test.js +0 -383
  479. package/dist/src/yaml/steps/trim-align.d.ts +0 -87
  480. package/dist/src/yaml/steps/trim-align.d.ts.map +0 -1
  481. package/dist/src/yaml/steps/trim-align.js +0 -367
  482. package/dist/src/yaml/steps/trim-align.test.d.ts +0 -11
  483. package/dist/src/yaml/steps/trim-align.test.d.ts.map +0 -1
  484. package/dist/src/yaml/steps/trim-align.test.js +0 -261
  485. package/dist/src/yaml/steps/ui-fidelity-preview-android.d.ts +0 -66
  486. package/dist/src/yaml/steps/ui-fidelity-preview-android.d.ts.map +0 -1
  487. package/dist/src/yaml/steps/ui-fidelity-preview-android.js +0 -251
  488. package/dist/src/yaml/steps/ui-fidelity-preview-android.test.d.ts +0 -13
  489. package/dist/src/yaml/steps/ui-fidelity-preview-android.test.d.ts.map +0 -1
  490. package/dist/src/yaml/steps/ui-fidelity-preview-android.test.js +0 -302
  491. package/dist/src/yaml/steps/ui-fidelity-preview.d.ts +0 -134
  492. package/dist/src/yaml/steps/ui-fidelity-preview.d.ts.map +0 -1
  493. package/dist/src/yaml/steps/ui-fidelity-preview.js +0 -173
  494. package/dist/src/yaml/steps/ui-fidelity-preview.test.d.ts +0 -11
  495. package/dist/src/yaml/steps/ui-fidelity-preview.test.d.ts.map +0 -1
  496. package/dist/src/yaml/steps/ui-fidelity-preview.test.js +0 -210
  497. package/dist/src/yaml/steps/ui-fidelity-render-android.d.ts +0 -155
  498. package/dist/src/yaml/steps/ui-fidelity-render-android.d.ts.map +0 -1
  499. package/dist/src/yaml/steps/ui-fidelity-render-android.js +0 -1389
  500. package/dist/src/yaml/steps/ui-fidelity-render-android.test.d.ts +0 -2
  501. package/dist/src/yaml/steps/ui-fidelity-render-android.test.d.ts.map +0 -1
  502. package/dist/src/yaml/steps/ui-fidelity-render-android.test.js +0 -1106
  503. package/dist/src/yaml/steps/ui-fidelity-render.d.ts +0 -230
  504. package/dist/src/yaml/steps/ui-fidelity-render.d.ts.map +0 -1
  505. package/dist/src/yaml/steps/ui-fidelity-render.js +0 -1597
  506. package/dist/src/yaml/steps/ui-fidelity-render.test.d.ts +0 -18
  507. package/dist/src/yaml/steps/ui-fidelity-render.test.d.ts.map +0 -1
  508. package/dist/src/yaml/steps/ui-fidelity-render.test.js +0 -1871
  509. package/dist/src/yaml/steps/xcode-app-product.d.ts +0 -90
  510. package/dist/src/yaml/steps/xcode-app-product.d.ts.map +0 -1
  511. package/dist/src/yaml/steps/xcode-app-product.js +0 -247
  512. package/dist/src/yaml/steps/xcode-app-product.test.d.ts +0 -2
  513. package/dist/src/yaml/steps/xcode-app-product.test.d.ts.map +0 -1
  514. package/dist/src/yaml/steps/xcode-app-product.test.js +0 -202
  515. package/dist/src/yaml/steps/xcode-derived-data.test.d.ts +0 -30
  516. package/dist/src/yaml/steps/xcode-derived-data.test.d.ts.map +0 -1
  517. package/dist/src/yaml/steps/xcode-derived-data.test.js +0 -253
  518. package/dist/src/yaml/steps/xcode-destination.d.ts +0 -47
  519. package/dist/src/yaml/steps/xcode-destination.d.ts.map +0 -1
  520. package/dist/src/yaml/steps/xcode-destination.js +0 -202
  521. package/dist/src/yaml/steps/xcode-destination.test.d.ts +0 -9
  522. package/dist/src/yaml/steps/xcode-destination.test.d.ts.map +0 -1
  523. package/dist/src/yaml/steps/xcode-destination.test.js +0 -227
  524. package/dist/src/yaml/steps/xcode-new.test.d.ts +0 -5
  525. package/dist/src/yaml/steps/xcode-new.test.d.ts.map +0 -1
  526. package/dist/src/yaml/steps/xcode-new.test.js +0 -211
  527. package/dist/src/yaml/steps/xcode-resolve-retry.test.d.ts +0 -9
  528. package/dist/src/yaml/steps/xcode-resolve-retry.test.d.ts.map +0 -1
  529. package/dist/src/yaml/steps/xcode-resolve-retry.test.js +0 -146
  530. package/dist/src/yaml/steps/xcode-simulator-archs.d.ts +0 -53
  531. package/dist/src/yaml/steps/xcode-simulator-archs.d.ts.map +0 -1
  532. package/dist/src/yaml/steps/xcode-simulator-archs.js +0 -98
  533. package/dist/src/yaml/steps/xcode-simulator-archs.test.d.ts +0 -21
  534. package/dist/src/yaml/steps/xcode-simulator-archs.test.d.ts.map +0 -1
  535. package/dist/src/yaml/steps/xcode-simulator-archs.test.js +0 -155
  536. package/dist/src/yaml/steps/xcode.d.ts +0 -230
  537. package/dist/src/yaml/steps/xcode.d.ts.map +0 -1
  538. package/dist/src/yaml/steps/xcode.js +0 -1340
  539. package/dist/src/yaml/types.d.ts +0 -76
  540. package/dist/src/yaml/types.d.ts.map +0 -1
  541. package/dist/src/yaml/types.js +0 -5
  542. package/dist/src/yaml/validation-types.d.ts +0 -102
  543. package/dist/src/yaml/validation-types.d.ts.map +0 -1
  544. package/dist/src/yaml/validation-types.js +0 -8
  545. package/dist/src/yaml/yaml-updater.d.ts +0 -24
  546. package/dist/src/yaml/yaml-updater.d.ts.map +0 -1
  547. package/dist/src/yaml/yaml-updater.js +0 -128
@@ -1,1106 +0,0 @@
1
- import { spawn, spawnSync } from 'node:child_process';
2
- import { chmodSync, cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync, } from 'node:fs';
3
- import { gzipSync } from 'node:zlib';
4
- import { tmpdir } from 'node:os';
5
- import { dirname, join, resolve } from 'node:path';
6
- import { fileURLToPath } from 'node:url';
7
- import { ANDROID_RENDERER, DEFAULT_GRADLE_TASK, UiFidelityRenderAndroidStepExecutor, } from './ui-fidelity-render-android.js';
8
- import { DEFAULT_SCALE } from './ui-fidelity-render.js';
9
- import { clearRegistry, getStepMetadata, hasStep } from './registry.js';
10
- import { initializeStepRegistry } from './index.js';
11
- import { testConfig } from './test-config.js';
12
- const PNG_MAGIC = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
13
- const tempDirs = [];
14
- afterAll(() => {
15
- for (const dir of tempDirs) {
16
- try {
17
- rmSync(dir, { recursive: true, force: true });
18
- }
19
- catch {
20
- // best effort
21
- }
22
- }
23
- });
24
- // ---- fake toolchain ----------------------------------------------------------
25
- // Fake `gradlew`, shipped inside the archive at the project root. With
26
- // FAKE_GRADLE_FAIL set it prints a compiler-style diagnostic and exits 1
27
- // (the project-does-not-build path). Otherwise it writes one PNG per screen
28
- // named in FAKE_ROBORAZZI_SCREENS (comma-separated) to
29
- // proof/build/outputs/roborazzi/<Screen>.png, mimicking a Roborazzi record.
30
- const FAKE_GRADLEW_LINES = [
31
- '#!/bin/bash',
32
- '[ -n "$FAKE_GRADLE_LOG" ] && echo "$*" >> "$FAKE_GRADLE_LOG"',
33
- 'if [ -n "$FAKE_GRADLE_FAIL" ]; then',
34
- ' echo "> Task :proof:compileDebugKotlin FAILED" >&2',
35
- ' echo "e: XProof.kt:7:20 unresolved reference: Missing" >&2',
36
- ' exit 1',
37
- 'fi',
38
- 'out="proof/build/outputs/roborazzi"',
39
- 'mkdir -p "$out"',
40
- 'IFS=","',
41
- 'for s in $FAKE_ROBORAZZI_SCREENS; do',
42
- ' [ -z "$s" ] && continue',
43
- " printf '\\x89PNG\\r\\n\\x1a\\n' > \"$out/$s.png\"",
44
- ' printf \'roborazzi:%s\' "$s" >> "$out/$s.png"',
45
- // When FAKE_ROBORAZZI_LAYOUT is set, also emit the layout-dump sidecar next
46
- // to the PNG (matching basename, .layout.json) — a clipping card with an
47
- // overflowing tag, so the deriver yields one clipped-by-parent fact.
48
- ' if [ -n "$FAKE_ROBORAZZI_LAYOUT" ]; then',
49
- // Layout sidecar basename: flat "<screen>.layout.json" by default, or an
50
- // explicit golden-style dotted name via FAKE_ROBORAZZI_LAYOUT_NAME to prove
51
- // the consumer finds it by dot-segment match, decoupled from the PNG stem.
52
- ' lname="${FAKE_ROBORAZZI_LAYOUT_NAME:-$s.layout.json}"',
53
- // The tag node carries the resolved-typeface enrichment (a Roboto fallback)
54
- // so a run with reference_typeface can prove the params → fact plumbing.
55
- ` printf '%s' '{"renderSdk":34,"canvas":{"left":0,"top":0,"right":200,"bottom":400},"nodes":[{"id":"card","bounds":{"left":20,"top":20,"right":180,"bottom":200},"parentId":null,"clipsChildren":true},{"id":"tag","bounds":{"left":60,"top":150,"right":140,"bottom":224},"parentId":"card","clipsChildren":false,"text":"TAG","resolvedTypeface":{"family":"Roboto-Regular","weight":400}}]}' > "$out/$lname"`,
56
- ' fi',
57
- 'done',
58
- 'exit 0',
59
- ];
60
- // Fake `ci`, providing only the branch the shared trim stage uses:
61
- // `ci fidelity-trim <in> <out> <aligned> <ref>`. Overwrites <out> with a
62
- // "trimmed:<in basename>" PNG, writes an "aligned:<in basename>" PNG, and
63
- // emits a fixed TRIMDIMS line. FAKE_TRIM_FAIL exercises warn-and-continue.
64
- const FAKE_CI_LINES = [
65
- '#!/bin/bash',
66
- 'case "$1" in',
67
- ' fidelity-trim)',
68
- ' if [ -n "$FAKE_TRIM_FAIL" ]; then echo "trim: simulated failure" >&2; exit 1; fi',
69
- ' in_png="$2"; out_png="$3"; aligned_png="$4"',
70
- " printf '\\x89PNG\\r\\n\\x1a\\n' > \"$out_png\"",
71
- ' printf \'trimmed:%s\' "$(basename "$in_png")" >> "$out_png"',
72
- ' if [ -n "$aligned_png" ]; then',
73
- " printf '\\x89PNG\\r\\n\\x1a\\n' > \"$aligned_png\"",
74
- ' printf \'aligned:%s\' "$(basename "$in_png")" >> "$aligned_png"',
75
- ' fi',
76
- ' echo "TRIMDIMS 320 640 160 320 400 800 40 77"',
77
- ' exit 0 ;;',
78
- ' *) exit 0 ;;',
79
- 'esac',
80
- ];
81
- function makeProject(params) {
82
- const dir = mkdtempSync(join(tmpdir(), 'ui-fidelity-android-'));
83
- tempDirs.push(dir);
84
- mkdirSync(join(dir, '.ci', 'inputs'), { recursive: true });
85
- const binDir = join(dir, 'bin');
86
- mkdirSync(binDir, { recursive: true });
87
- const ciPath = join(binDir, 'ci');
88
- writeFileSync(ciPath, FAKE_CI_LINES.join('\n') + '\n');
89
- chmodSync(ciPath, 0o755);
90
- if (params !== undefined) {
91
- writeFileSync(join(dir, '.ci', 'inputs', 'params.json'), typeof params === 'string' ? params : JSON.stringify(params));
92
- }
93
- return { dir, binDir };
94
- }
95
- function writeReference(project, basename, label = basename) {
96
- writeFileSync(join(project.dir, '.ci', 'inputs', basename), Buffer.concat([PNG_MAGIC, Buffer.from('ref:' + label)]));
97
- }
98
- function isPng(filePath) {
99
- try {
100
- return readFileSync(filePath).subarray(0, 8).equals(PNG_MAGIC);
101
- }
102
- catch {
103
- return false;
104
- }
105
- }
106
- function artifact(project, relative) {
107
- return join(project.dir, '.ci', 'artifacts', relative);
108
- }
109
- function tarHeader(name, size, typeflag, mode) {
110
- const header = Buffer.alloc(512);
111
- header.write(name, 0, 100, 'utf-8');
112
- header.write(mode.toString(8).padStart(7, '0') + '\0', 100, 8, 'ascii');
113
- header.write('0000000\0', 108, 8, 'ascii');
114
- header.write('0000000\0', 116, 8, 'ascii');
115
- header.write(size.toString(8).padStart(11, '0') + '\0', 124, 12, 'ascii');
116
- header.write('00000000000\0', 136, 12, 'ascii');
117
- header.write(' ', 148, 8, 'ascii');
118
- header.write(typeflag, 156, 1, 'ascii');
119
- header.write('ustar\0' + '00', 257, 8, 'ascii');
120
- let checksum = 0;
121
- for (const byte of header)
122
- checksum += byte;
123
- header.write(checksum.toString(8).padStart(6, '0') + '\0 ', 148, 8, 'ascii');
124
- return header;
125
- }
126
- function makeTarGz(entries) {
127
- const blocks = [];
128
- for (const entry of entries) {
129
- const isDir = entry.name.endsWith('/');
130
- const content = Buffer.isBuffer(entry.content)
131
- ? entry.content
132
- : Buffer.from(entry.content ?? '', 'utf-8');
133
- const mode = entry.mode ?? (isDir ? 0o755 : 0o644);
134
- blocks.push(tarHeader(entry.name, isDir ? 0 : content.length, isDir ? '5' : '0', mode));
135
- if (!isDir && content.length > 0) {
136
- const padded = Buffer.alloc(Math.ceil(content.length / 512) * 512);
137
- content.copy(padded);
138
- blocks.push(padded);
139
- }
140
- }
141
- blocks.push(Buffer.alloc(1024));
142
- return gzipSync(Buffer.concat(blocks));
143
- }
144
- /** Entries for a well-formed shipped Gradle project under the given root. */
145
- function gradleProjectEntries(root = 'project') {
146
- const prefix = root === '' ? '' : root + '/';
147
- const entries = root === '' ? [] : [{ name: prefix }];
148
- entries.push({ name: prefix + 'settings.gradle.kts', content: 'rootProject.name = "p"\ninclude(":proof")\n' }, { name: prefix + 'gradlew', content: FAKE_GRADLEW_LINES.join('\n') + '\n', mode: 0o755 }, { name: prefix + 'proof/build.gradle.kts', content: '// module\n' });
149
- return entries;
150
- }
151
- function stageArchive(project, archive) {
152
- writeFileSync(join(project.dir, '.ci', 'inputs', 'package.tar.gz'), Buffer.isBuffer(archive) ? archive : makeTarGz(archive));
153
- }
154
- // ---- script build + run ------------------------------------------------------
155
- async function buildScript(inputs = {}) {
156
- const executor = new UiFidelityRenderAndroidStepExecutor();
157
- const step = await executor.execute(inputs, {}, testConfig);
158
- return step.script;
159
- }
160
- function runScript(project, script, env = {}) {
161
- return spawnSync('node', ['-e', script], {
162
- cwd: project.dir,
163
- encoding: 'utf-8',
164
- env: {
165
- ...process.env,
166
- PATH: project.binDir + ':' + (process.env.PATH ?? ''),
167
- ...env,
168
- },
169
- });
170
- }
171
- function readResult(project) {
172
- return JSON.parse(readFileSync(join(project.dir, '.ci', 'artifacts', 'protocol-result.json'), 'utf-8'));
173
- }
174
- describe('registry integration', () => {
175
- test('ui-fidelity-render-android is registered with its input schema', () => {
176
- clearRegistry();
177
- initializeStepRegistry();
178
- expect(hasStep('ui-fidelity-render-android')).toBe(true);
179
- const metadata = getStepMetadata('ui-fidelity-render-android');
180
- expect(metadata?.platform).toBe('android');
181
- const inputs = metadata?.inputs ?? {};
182
- expect(Object.keys(inputs).sort()).toEqual([
183
- 'gradle_task',
184
- 'package_path',
185
- 'package_source',
186
- 'reference_source',
187
- 'scale',
188
- ]);
189
- expect(inputs.package_source.required).toBe(false);
190
- expect(inputs.package_source.default).toBe('inputs');
191
- expect(inputs.reference_source.required).toBe(false);
192
- expect(inputs.reference_source.default).toBe('inputs');
193
- // package_path is enforced by the executor only in repo mode; the default
194
- // (inputs) config is valid without it, so it is not unconditionally required.
195
- expect(inputs.package_path.required).toBe(false);
196
- expect(inputs.scale.required).toBe(false);
197
- expect(inputs.scale.default).toBe(DEFAULT_SCALE);
198
- expect(inputs.gradle_task.required).toBe(false);
199
- expect(inputs.gradle_task.default).toBe(DEFAULT_GRADLE_TASK);
200
- });
201
- });
202
- describe('default byte compatibility', () => {
203
- // Snapshot of the script generated for the default inputs (no package_source,
204
- // no reference_source), captured from the executor with everything else at its
205
- // default. Omitting both *_source inputs must keep the generated script
206
- // byte-identical to this snapshot, so existing protocols are provably
207
- // untouched. Update the snapshot only for a deliberate, reviewed change to the
208
- // default runtime.
209
- const DEFAULT_SCRIPT_SNAPSHOT = resolve(dirname(fileURLToPath(import.meta.url)), '../../../test/fixtures/ui-fidelity-render-android-v1-script.txt');
210
- test('omitting package_source and reference_source generates a byte-identical script', async () => {
211
- const executor = new UiFidelityRenderAndroidStepExecutor();
212
- const step = await executor.execute({}, {}, testConfig);
213
- expect(step.script).toBe(readFileSync(DEFAULT_SCRIPT_SNAPSHOT, 'utf-8'));
214
- });
215
- });
216
- describe('UiFidelityRenderAndroidStepExecutor', () => {
217
- test('returns a node StepDef named ui-fidelity-render-android', async () => {
218
- const executor = new UiFidelityRenderAndroidStepExecutor();
219
- const step = await executor.execute({}, {}, testConfig);
220
- expect(step.kind).toBe('node');
221
- expect(step.name).toBe('ui-fidelity-render-android');
222
- expect(step.script.length).toBeGreaterThan(0);
223
- });
224
- });
225
- describe('android render runtime — params validation', () => {
226
- test('writes a contract-shaped, empty-screens result and fails when params.json is absent', async () => {
227
- const project = makeProject();
228
- const script = await buildScript();
229
- const run = runScript(project, script);
230
- expect(run.status).toBe(1);
231
- const doc = readResult(project);
232
- expect(doc.renderer).toBe(ANDROID_RENDERER);
233
- expect(doc.error).toBeNull();
234
- expect(doc.screens).toEqual([]);
235
- });
236
- test('fails when params.json is malformed', async () => {
237
- const project = makeProject('{ not json');
238
- const run = runScript(project, await buildScript());
239
- expect(run.status).toBe(1);
240
- expect(readResult(project).screens).toEqual([]);
241
- });
242
- });
243
- describe('android render runtime — reference setup', () => {
244
- test('records REFERENCE_MISSING for a screen whose reference is absent', async () => {
245
- const project = makeProject({ screens: { XProof: 'x.png' } });
246
- const run = runScript(project, await buildScript());
247
- expect(run.status).toBe(1);
248
- const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
249
- expect(screen?.error?.code).toBe('REFERENCE_MISSING');
250
- expect(screen?.reference_image_path).toBeNull();
251
- });
252
- test('copies a present reference into the artifacts and records its path', async () => {
253
- const project = makeProject({ screens: { XProof: 'x.png' } });
254
- writeReference(project, 'x.png');
255
- const run = runScript(project, await buildScript());
256
- const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
257
- expect(screen?.reference_image_path).toBe('ui-fidelity/references/XProof.png');
258
- expect(isPng(artifact(project, 'ui-fidelity/references/XProof.png'))).toBe(true);
259
- });
260
- });
261
- describe('android render runtime — archive validation', () => {
262
- function projectWithRef() {
263
- const project = makeProject({ screens: { XProof: 'x.png' } });
264
- writeReference(project, 'x.png');
265
- return project;
266
- }
267
- test('missing archive yields ANDROID_ARCHIVE_MISSING with every screen entry present', async () => {
268
- const project = projectWithRef();
269
- const run = runScript(project, await buildScript());
270
- expect(run.status).toBe(1);
271
- const doc = readResult(project);
272
- expect(doc.error?.code).toBe('ANDROID_ARCHIVE_MISSING');
273
- expect(doc.screens.map((s) => s.screen)).toEqual(['XProof']);
274
- });
275
- test('a corrupt archive yields ANDROID_ARCHIVE_INVALID', async () => {
276
- const project = projectWithRef();
277
- stageArchive(project, Buffer.from('not a gzip'));
278
- const run = runScript(project, await buildScript());
279
- expect(readResult(project).error?.code).toBe('ANDROID_ARCHIVE_INVALID');
280
- expect(run.status).toBe(1);
281
- });
282
- test('an archive with dot-dot members yields ANDROID_ARCHIVE_INVALID', async () => {
283
- const project = projectWithRef();
284
- stageArchive(project, [{ name: '../escape.txt', content: 'x' }]);
285
- runScript(project, await buildScript());
286
- expect(readResult(project).error?.code).toBe('ANDROID_ARCHIVE_INVALID');
287
- });
288
- test('an extraction larger than the size bound yields ANDROID_ARCHIVE_TOO_LARGE', async () => {
289
- const project = projectWithRef();
290
- stageArchive(project, gradleProjectEntries());
291
- runScript(project, await buildScript(), { UI_FIDELITY_MAX_PACKAGE_BYTES: '10' });
292
- expect(readResult(project).error?.code).toBe('ANDROID_ARCHIVE_TOO_LARGE');
293
- });
294
- test('an archive without a Gradle settings file yields ANDROID_PROJECT_ROOT_MISSING', async () => {
295
- const project = projectWithRef();
296
- stageArchive(project, [
297
- { name: 'project/' },
298
- { name: 'project/build.gradle.kts', content: '// no settings\n' },
299
- ]);
300
- runScript(project, await buildScript());
301
- expect(readResult(project).error?.code).toBe('ANDROID_PROJECT_ROOT_MISSING');
302
- });
303
- test('archive validation failures fail the step even with zero screens', async () => {
304
- const project = makeProject({ screens: {} });
305
- const run = runScript(project, await buildScript());
306
- expect(run.status).toBe(1);
307
- expect(readResult(project).error?.code).toBe('ANDROID_ARCHIVE_MISSING');
308
- });
309
- });
310
- describe('android render runtime — render + trim (happy path)', () => {
311
- test('renders each screen, trims via the shared post-processor, and records the contract shape', async () => {
312
- const project = makeProject({ screens: { XProof: 'x.png' } });
313
- writeReference(project, 'x.png');
314
- stageArchive(project, gradleProjectEntries());
315
- const run = runScript(project, await buildScript(), { FAKE_ROBORAZZI_SCREENS: 'XProof' });
316
- expect(run.status).toBe(0);
317
- const doc = readResult(project);
318
- expect(doc.error).toBeNull();
319
- const screen = doc.screens.find((s) => s.screen === 'XProof');
320
- expect(screen?.status).toBe('rendered');
321
- expect(screen?.error).toBeNull();
322
- expect(screen?.rendered_image_path).toBe('ui-fidelity/rendered/XProof.png');
323
- expect(screen?.aligned_image_path).toBe('ui-fidelity/aligned/XProof.png');
324
- // dimensions from the (fake) trim helper's TRIMDIMS, logical_pt = px / scale 2.
325
- expect(screen?.dimensions).toEqual({
326
- rendered_px: [320, 640],
327
- logical_pt: [160, 320],
328
- reference_px: [160, 320],
329
- // the fake helper's TRIMDIMS pre-trim source px (fields 5-6)
330
- source_px: [400, 800],
331
- // …and the crop origin (fields 7-8): where the trimmed image sits in the
332
- // source, so geometry captured in window coordinates can be placed on
333
- // the trimmed bytes (the 2026-08-01 spotlight diagnosis).
334
- trim_offset_px: [40, 77],
335
- });
336
- expect(isPng(artifact(project, 'ui-fidelity/rendered/XProof.png'))).toBe(true);
337
- expect(isPng(artifact(project, 'ui-fidelity/aligned/XProof.png'))).toBe(true);
338
- // The rendered file holds the trim helper's output, not the raw render.
339
- expect(readFileSync(artifact(project, 'ui-fidelity/rendered/XProof.png'), 'utf-8')).toContain('trimmed:XProof.png');
340
- });
341
- test('derives structural facts from the layout dump and records layout_path + facts_path', async () => {
342
- const project = makeProject({ screens: { XProof: 'x.png' } });
343
- writeReference(project, 'x.png');
344
- stageArchive(project, gradleProjectEntries());
345
- const run = runScript(project, await buildScript(), {
346
- FAKE_ROBORAZZI_SCREENS: 'XProof',
347
- FAKE_ROBORAZZI_LAYOUT: '1',
348
- });
349
- expect(run.status).toBe(0);
350
- const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
351
- expect(screen?.status).toBe('rendered');
352
- expect(screen?.layout_path).toBe('ui-fidelity/layout/XProof.json');
353
- expect(screen?.facts_path).toBe('ui-fidelity/facts/XProof.json');
354
- // The layout dump is copied verbatim as a build artifact.
355
- expect(existsSync(artifact(project, 'ui-fidelity/layout/XProof.json'))).toBe(true);
356
- // The facts artifact surfaces the bottom clip the eye would miss.
357
- const facts = JSON.parse(readFileSync(artifact(project, 'ui-fidelity/facts/XProof.json'), 'utf-8'));
358
- expect(facts).toEqual({
359
- screen: 'XProof',
360
- facts: [
361
- {
362
- kind: 'clipped_by_parent',
363
- nodeId: 'tag',
364
- clipperId: 'card',
365
- edges: ['bottom'],
366
- clippedPx: { bottom: 24 },
367
- },
368
- ],
369
- });
370
- });
371
- test('emits a source_digest content key on a rendered screen entry', async () => {
372
- const project = makeProject({ screens: { XProof: 'x.png' } });
373
- writeReference(project, 'x.png');
374
- stageArchive(project, gradleProjectEntries());
375
- const run = runScript(project, await buildScript(), {
376
- FAKE_ROBORAZZI_SCREENS: 'XProof',
377
- });
378
- expect(run.status).toBe(0);
379
- const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
380
- expect(screen?.status).toBe('rendered');
381
- expect(screen?.source_digest).toMatch(/^[0-9a-f]{64}$/);
382
- });
383
- test('omits source_digest for a non-hermetic package (Gradle script escapes the root)', async () => {
384
- const project = makeProject({ screens: { XProof: 'x.png' } });
385
- writeReference(project, 'x.png');
386
- // A settings script that pulls in a sibling build via a parent path — the
387
- // package is no longer its own dependency closure, so it must not be
388
- // cacheable: it still renders, but emits no content key.
389
- const entries = gradleProjectEntries().map((e) => e.name.endsWith('settings.gradle.kts')
390
- ? { ...e, content: 'rootProject.name = "p"\ninclude(":proof")\nincludeBuild("../design-system")\n' }
391
- : e);
392
- stageArchive(project, entries);
393
- const run = runScript(project, await buildScript(), { FAKE_ROBORAZZI_SCREENS: 'XProof' });
394
- expect(run.status).toBe(0);
395
- const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
396
- expect(screen?.status).toBe('rendered');
397
- expect(screen?.source_digest == null).toBe(true);
398
- });
399
- test('assembles the inline structural block (facts) onto the screen entry', async () => {
400
- const project = makeProject({ screens: { XProof: 'x.png' } });
401
- writeReference(project, 'x.png');
402
- stageArchive(project, gradleProjectEntries());
403
- const run = runScript(project, await buildScript(), {
404
- FAKE_ROBORAZZI_SCREENS: 'XProof',
405
- FAKE_ROBORAZZI_LAYOUT: '1',
406
- });
407
- expect(run.status).toBe(0);
408
- const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
409
- // Signal A surfaces inline; max deltas stay null (no comparison ran).
410
- expect(screen?.structural?.summary).toEqual({ clean: false, factCount: 1, maxSizeDeltaDp: null, maxClipDeltaDp: null });
411
- expect(screen?.structural?.facts).toHaveLength(1);
412
- // Description vs comparison: with no reference_geometry the geometry block
413
- // still persists the authored-tag nodes' rendered boxes — and nothing else
414
- // (no design/deltas/sizeFlag).
415
- expect(screen?.structural?.geometry).toEqual({
416
- unit: 'dp',
417
- density: 2,
418
- elements: [
419
- { id: 'card', rendered: { w: 80, h: 90, x: 10, y: 10 } },
420
- { id: 'tag', rendered: { w: 40, h: 37, x: 30, y: 75 } },
421
- ],
422
- });
423
- // The verdict obligation is baked onto the run result by the runner: the
424
- // clip fact (node 'tag') becomes a must-explain row with a null verdict.
425
- expect(screen?.structural?.review?.verdict).toBeNull();
426
- expect(screen?.structural?.review?.mustExplain).toEqual(['tag']);
427
- expect(screen?.structural?.review?.rows.every((r) => r.verdict === null)).toBe(true);
428
- });
429
- test('adds Signal-B geometry deltas to the structural block when reference_geometry is given', async () => {
430
- // The fake layout dump: card bounds 160x180px, tag 80x74px (density 2 → dp
431
- // card 80x90, tag 40x37). Declare design numbers so the card reads +10dp tall.
432
- const project = makeProject({
433
- screens: { XProof: 'x.png' },
434
- reference_geometry: { XProof: { card: { w: 80, h: 80 }, tag: { w: 40, h: 37 } } },
435
- });
436
- writeReference(project, 'x.png');
437
- stageArchive(project, gradleProjectEntries());
438
- const run = runScript(project, await buildScript(), {
439
- FAKE_ROBORAZZI_SCREENS: 'XProof',
440
- FAKE_ROBORAZZI_LAYOUT: '1',
441
- });
442
- expect(run.status).toBe(0);
443
- const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
444
- expect(screen?.structural?.summary).toEqual({ clean: false, factCount: 1, maxSizeDeltaDp: 10, maxClipDeltaDp: 0 });
445
- const geometry = screen?.structural?.geometry;
446
- expect(geometry?.unit).toBe('dp');
447
- expect(geometry?.density).toBe(2);
448
- const card = geometry?.elements.find((e) => e.id === 'card');
449
- expect(card).toEqual({
450
- id: 'card',
451
- // rendered now carries x/y (dp); design stays w/h-only (no x/y in ref).
452
- rendered: { w: 80, h: 90, x: 10, y: 10 },
453
- design: { w: 80, h: 80 },
454
- deltas: [{ dimension: 'h', delta: 10 }],
455
- sizeFlag: true,
456
- // Render ancestry (icon-ink-ratio 05), measured by the EMBEDDED port that
457
- // actually runs on the runner: `card` is a root in this dump, so its chain
458
- // is empty. Empty is a measurement — the judge reads an ABSENT field as
459
- // "this render predates ancestry" and stays silent on it.
460
- ancestors: [],
461
- });
462
- const tag = geometry?.elements.find((e) => e.id === 'tag');
463
- expect(tag?.sizeFlag).toBe(false);
464
- expect(tag?.deltas).toEqual([]);
465
- // ...and `tag` is nested inside `card`, so the chain the judge compares
466
- // against the design tree is non-trivial here.
467
- expect(tag?.ancestors).toEqual(['card']);
468
- });
469
- test('derives a flagged typeface fact when reference_typeface is given and the resolved font diverges (typeface issue 04)', async () => {
470
- // The fake dump resolved Roboto-Regular 400 on `tag`; the design declares
471
- // Poppins 600 → exactly one typeface fact on the run result. A matching
472
- // declaration on a second run produces none.
473
- const project = makeProject({
474
- screens: { XProof: 'x.png' },
475
- reference_typeface: { XProof: { tag: { family: 'Poppins', weight: 600 } } },
476
- });
477
- writeReference(project, 'x.png');
478
- stageArchive(project, gradleProjectEntries());
479
- const run = runScript(project, await buildScript(), {
480
- FAKE_ROBORAZZI_SCREENS: 'XProof',
481
- FAKE_ROBORAZZI_LAYOUT: '1',
482
- });
483
- expect(run.status).toBe(0);
484
- const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
485
- const typefaceFacts = (screen?.structural?.appearance?.facts ?? []).filter((f) => f.kind === 'typeface');
486
- expect(typefaceFacts).toEqual([
487
- {
488
- kind: 'typeface',
489
- elementId: 'tag',
490
- channel: 'text',
491
- rendered: { family: 'Roboto-Regular', weight: 400 },
492
- reference: { family: 'Poppins', weight: 600 },
493
- },
494
- ]);
495
- // The resolved font is persisted as the next drift baseline.
496
- expect(screen?.structural?.appearance?.rendered?.typeface).toEqual({
497
- tag: { family: 'Roboto-Regular', weight: 400 },
498
- });
499
- // Matching declaration → no typeface fact (and no advisory).
500
- const clean = makeProject({
501
- screens: { XProof: 'x.png' },
502
- reference_typeface: { XProof: { tag: { family: 'Roboto-Regular', weight: 400 } } },
503
- });
504
- writeReference(clean, 'x.png');
505
- stageArchive(clean, gradleProjectEntries());
506
- const cleanRun = runScript(clean, await buildScript(), {
507
- FAKE_ROBORAZZI_SCREENS: 'XProof',
508
- FAKE_ROBORAZZI_LAYOUT: '1',
509
- });
510
- expect(cleanRun.status).toBe(0);
511
- const cleanScreen = readResult(clean).screens.find((s) => s.screen === 'XProof');
512
- expect((cleanScreen?.structural?.appearance?.facts ?? []).filter((f) => f.kind === 'typeface')).toEqual([]);
513
- expect(cleanScreen?.structural?.appearance?.advisories).toBeUndefined();
514
- });
515
- test('finds the layout dump by dot-segment match even when its name differs from the PNG stem', async () => {
516
- const project = makeProject({ screens: { XProof: 'x.png' } });
517
- writeReference(project, 'x.png');
518
- stageArchive(project, gradleProjectEntries());
519
- // Golden-style dotted layout name (as real Roborazzi would name a sidecar),
520
- // distinct from the flat PNG the fake writes — the consumer must still find it.
521
- const run = runScript(project, await buildScript(), {
522
- FAKE_ROBORAZZI_SCREENS: 'XProof',
523
- FAKE_ROBORAZZI_LAYOUT: '1',
524
- FAKE_ROBORAZZI_LAYOUT_NAME: 'com.example.XProofTest.renderXProof.layout.json',
525
- });
526
- expect(run.status).toBe(0);
527
- const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
528
- expect(screen?.facts_path).toBe('ui-fidelity/facts/XProof.json');
529
- const facts = JSON.parse(readFileSync(artifact(project, 'ui-fidelity/facts/XProof.json'), 'utf-8'));
530
- expect(facts.facts).toHaveLength(1);
531
- expect(facts.facts[0].kind).toBe('clipped_by_parent');
532
- });
533
- test('degrades cleanly when no layout dump is emitted (no facts, render still succeeds)', async () => {
534
- const project = makeProject({ screens: { XProof: 'x.png' } });
535
- writeReference(project, 'x.png');
536
- stageArchive(project, gradleProjectEntries());
537
- // No FAKE_ROBORAZZI_LAYOUT → the render emits a PNG but no sidecar.
538
- const run = runScript(project, await buildScript(), { FAKE_ROBORAZZI_SCREENS: 'XProof' });
539
- expect(run.status).toBe(0);
540
- const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
541
- expect(screen?.status).toBe('rendered');
542
- expect(screen?.layout_path ?? null).toBeNull();
543
- expect(screen?.facts_path ?? null).toBeNull();
544
- expect(existsSync(artifact(project, 'ui-fidelity/facts/XProof.json'))).toBe(false);
545
- });
546
- test('renders multiple screens independently in one Gradle run', async () => {
547
- const project = makeProject({ screens: { XProof: 'x.png', YProof: 'y.png' } });
548
- writeReference(project, 'x.png');
549
- writeReference(project, 'y.png');
550
- stageArchive(project, gradleProjectEntries());
551
- const run = runScript(project, await buildScript(), { FAKE_ROBORAZZI_SCREENS: 'XProof,YProof' });
552
- expect(run.status).toBe(0);
553
- const doc = readResult(project);
554
- expect(doc.screens.map((s) => s.status)).toEqual(['rendered', 'rendered']);
555
- });
556
- });
557
- describe('android render runtime — reference provenance', () => {
558
- const GOOD_PROV = {
559
- figmaNode: '12:345',
560
- locale: 'en',
561
- resolved: true,
562
- exportedAt: '2026-06-18T10:00:00Z',
563
- };
564
- // Stage a valid project so per-build archive validation passes and the only
565
- // signal under test is the per-screen provenance verdict.
566
- function provProject(params) {
567
- const project = makeProject(params);
568
- stageArchive(project, gradleProjectEntries());
569
- return project;
570
- }
571
- test('a reference with no provenance stays unverifiable when a render locale is declared', async () => {
572
- const project = provProject({
573
- screens: { XProof: 'x.png' },
574
- render_locale: 'en',
575
- provenance: {},
576
- });
577
- writeReference(project, 'x.png');
578
- runScript(project, await buildScript());
579
- const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
580
- expect(screen?.status).toBe('unverifiable');
581
- expect(screen?.error?.code).toBe('NO_PROVENANCE');
582
- });
583
- test('a placeholder reference (resolved=false) renders and downgrades only the copy channel', async () => {
584
- const project = provProject({
585
- screens: { XProof: 'x.png' },
586
- render_locale: 'en',
587
- provenance: { XProof: { ...GOOD_PROV, resolved: false } },
588
- });
589
- writeReference(project, 'x.png');
590
- const run = runScript(project, await buildScript(), { FAKE_ROBORAZZI_SCREENS: 'XProof' });
591
- expect(run.status).toBe(0);
592
- const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
593
- expect(screen?.status).toBe('rendered');
594
- expect(screen?.error).toBeNull();
595
- expect(screen?.copy_advisory?.code).toBe('copy_unverifiable');
596
- expect(screen?.copy_advisory?.reason).toBe('unresolved_placeholders');
597
- });
598
- test('the placeholder downgrade is identical without a render_locale (same input, same verdict)', async () => {
599
- // Live evidence bld_5f8dc6330c9e61f578297f00 / bld_c92bc240ab8aac7dfe258b27:
600
- // the same resolved:false reference was judged in one run and refused in
601
- // the other purely because of the optional render_locale param.
602
- const project = provProject({
603
- screens: { XProof: 'x.png' },
604
- provenance: { XProof: { ...GOOD_PROV, resolved: false } },
605
- });
606
- writeReference(project, 'x.png');
607
- const run = runScript(project, await buildScript(), { FAKE_ROBORAZZI_SCREENS: 'XProof' });
608
- expect(run.status).toBe(0);
609
- const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
610
- expect(screen?.status).toBe('rendered');
611
- expect(screen?.copy_advisory?.reason).toBe('unresolved_placeholders');
612
- });
613
- test('an Italian reference against an English render downgrades the copy channel', async () => {
614
- const project = provProject({
615
- screens: { XProof: 'x.png' },
616
- render_locale: 'en',
617
- provenance: { XProof: { ...GOOD_PROV, locale: 'it' } },
618
- });
619
- writeReference(project, 'x.png');
620
- const run = runScript(project, await buildScript(), { FAKE_ROBORAZZI_SCREENS: 'XProof' });
621
- expect(run.status).toBe(0);
622
- const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
623
- expect(screen?.status).toBe('rendered');
624
- expect(screen?.copy_advisory?.code).toBe('copy_unverifiable');
625
- expect(screen?.copy_advisory?.reason).toBe('locale_mismatch');
626
- });
627
- test('locale comparison requires a declared render_locale — its absence skips only that', async () => {
628
- const project = provProject({
629
- screens: { XProof: 'x.png' },
630
- provenance: { XProof: { ...GOOD_PROV, locale: 'it' } },
631
- });
632
- writeReference(project, 'x.png');
633
- runScript(project, await buildScript(), { FAKE_ROBORAZZI_SCREENS: 'XProof' });
634
- const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
635
- expect(screen?.status).toBe('rendered');
636
- expect(screen?.copy_advisory ?? null).toBeNull();
637
- });
638
- test('an absent reference with good provenance stays fatal (REFERENCE_ABSENT)', async () => {
639
- const project = provProject({
640
- screens: { XProof: 'x.png' },
641
- render_locale: 'en',
642
- provenance: { XProof: GOOD_PROV },
643
- });
644
- // No writeReference — the file is absent.
645
- runScript(project, await buildScript());
646
- const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
647
- expect(screen?.status).toBe('unverifiable');
648
- expect(screen?.error?.code).toBe('REFERENCE_ABSENT');
649
- });
650
- test('an absent reference is fatal even without a render_locale (armed by provenance presence)', async () => {
651
- const project = provProject({
652
- screens: { XProof: 'x.png' },
653
- provenance: { XProof: GOOD_PROV },
654
- });
655
- runScript(project, await buildScript());
656
- const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
657
- expect(screen?.status).toBe('unverifiable');
658
- expect(screen?.error?.code).toBe('REFERENCE_ABSENT');
659
- });
660
- test('a good provenanced reference renders normally with no copy advisory', async () => {
661
- const project = provProject({
662
- screens: { XProof: 'x.png' },
663
- render_locale: 'en',
664
- provenance: { XProof: GOOD_PROV },
665
- });
666
- writeReference(project, 'x.png');
667
- const run = runScript(project, await buildScript(), { FAKE_ROBORAZZI_SCREENS: 'XProof' });
668
- expect(run.status).toBe(0);
669
- const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
670
- expect(screen?.status).toBe('rendered');
671
- expect(screen?.error).toBeNull();
672
- expect(screen?.copy_advisory ?? null).toBeNull();
673
- });
674
- test('independent verdicts: a clean sibling carries no advisory while the placeholder state does', async () => {
675
- const project = provProject({
676
- screens: { XProof: 'x.png', YProof: 'y.png' },
677
- render_locale: 'en',
678
- provenance: { XProof: GOOD_PROV, YProof: { ...GOOD_PROV, resolved: false } },
679
- });
680
- writeReference(project, 'x.png');
681
- writeReference(project, 'y.png');
682
- runScript(project, await buildScript(), { FAKE_ROBORAZZI_SCREENS: 'XProof,YProof' });
683
- const doc = readResult(project);
684
- const x = doc.screens.find((s) => s.screen === 'XProof');
685
- expect(x?.status).toBe('rendered');
686
- expect(x?.copy_advisory ?? null).toBeNull();
687
- const y = doc.screens.find((s) => s.screen === 'YProof');
688
- expect(y?.status).toBe('rendered');
689
- expect(y?.copy_advisory?.reason).toBe('unresolved_placeholders');
690
- });
691
- test('without provenance and without a render_locale, nothing is enforced (legacy behaviour)', async () => {
692
- const project = provProject({ screens: { XProof: 'x.png' } });
693
- writeReference(project, 'x.png');
694
- runScript(project, await buildScript(), { FAKE_ROBORAZZI_SCREENS: 'XProof' });
695
- const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
696
- expect(screen?.status).toBe('rendered');
697
- expect(screen?.copy_advisory ?? null).toBeNull();
698
- });
699
- test('a downgraded copy channel derives no copy facts while geometry facts still hold', async () => {
700
- // The fake layout dump carries a text node ("tag": "TAG") that clips its
701
- // parent. With reference text that mismatches, a clean run derives a
702
- // text_mismatch fact — a downgraded run must derive NONE while keeping
703
- // the clipped_by_parent geometry fact.
704
- const factKinds = async (resolved) => {
705
- const project = provProject({
706
- screens: { XProof: 'x.png' },
707
- render_locale: 'en',
708
- provenance: { XProof: { ...GOOD_PROV, resolved } },
709
- reference_text: { XProof: { tag: 'DIFFERENT' } },
710
- });
711
- writeReference(project, 'x.png');
712
- runScript(project, await buildScript(), {
713
- FAKE_ROBORAZZI_SCREENS: 'XProof',
714
- FAKE_ROBORAZZI_LAYOUT: '1',
715
- });
716
- const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
717
- const structural = (screen?.structural?.facts ?? []).map((f) => f.kind);
718
- const appearance = (screen?.structural?.appearance?.facts ?? []).map((f) => f.kind);
719
- return [...structural, ...appearance];
720
- };
721
- const clean = await factKinds(true);
722
- expect(clean).toContain('text_mismatch');
723
- expect(clean).toContain('clipped_by_parent');
724
- const downgraded = await factKinds(false);
725
- expect(downgraded).not.toContain('text_mismatch');
726
- expect(downgraded).not.toContain('text_missing');
727
- expect(downgraded).toContain('clipped_by_parent');
728
- });
729
- });
730
- describe('android render runtime — error isolation', () => {
731
- function twoScreenProject() {
732
- const project = makeProject({ screens: { XProof: 'x.png', YProof: 'y.png' } });
733
- writeReference(project, 'x.png');
734
- writeReference(project, 'y.png');
735
- stageArchive(project, gradleProjectEntries());
736
- return project;
737
- }
738
- test('a Gradle build failure marks every screen RENDER_UNSUPPORTED', async () => {
739
- const project = twoScreenProject();
740
- const run = runScript(project, await buildScript(), { FAKE_GRADLE_FAIL: '1' });
741
- expect(run.status).toBe(1);
742
- const doc = readResult(project);
743
- // Build-level failure is reported per screen (not an archive-level error).
744
- expect(doc.error).toBeNull();
745
- for (const screen of doc.screens) {
746
- expect(screen.status).toBe('render_failed');
747
- expect(screen.error?.code).toBe('RENDER_UNSUPPORTED');
748
- expect(screen.error?.message).toContain('did not build');
749
- }
750
- });
751
- test('a screen whose PNG is not produced is RENDER_FAILED while others render', async () => {
752
- const project = twoScreenProject();
753
- // Only XProof records a PNG; YProof's is absent.
754
- const run = runScript(project, await buildScript(), { FAKE_ROBORAZZI_SCREENS: 'XProof' });
755
- expect(run.status).toBe(1);
756
- const doc = readResult(project);
757
- const x = doc.screens.find((s) => s.screen === 'XProof');
758
- const y = doc.screens.find((s) => s.screen === 'YProof');
759
- expect(x?.status).toBe('rendered');
760
- expect(y?.status).toBe('render_failed');
761
- expect(y?.error?.code).toBe('RENDER_FAILED');
762
- });
763
- });
764
- /** Materialize a well-formed Gradle render project (fake gradlew) on disk. */
765
- function writeGradleProjectDir(dir) {
766
- mkdirSync(join(dir, 'proof'), { recursive: true });
767
- writeFileSync(join(dir, 'settings.gradle.kts'), 'rootProject.name = "p"\ninclude(":proof")\n');
768
- const gradlew = join(dir, 'gradlew');
769
- writeFileSync(gradlew, FAKE_GRADLEW_LINES.join('\n') + '\n');
770
- chmodSync(gradlew, 0o755);
771
- writeFileSync(join(dir, 'proof', 'build.gradle.kts'), '// module\n');
772
- }
773
- describe('package_source: repo', () => {
774
- test('rejects an unknown package_source value', async () => {
775
- const executor = new UiFidelityRenderAndroidStepExecutor();
776
- await expect(executor.execute({ package_source: 'cloud' }, {}, testConfig)).rejects.toThrow(/package_source/);
777
- });
778
- test('package_source repo requires package_path', async () => {
779
- const executor = new UiFidelityRenderAndroidStepExecutor();
780
- await expect(executor.execute({ package_source: 'repo' }, {}, testConfig)).rejects.toThrow(/package_path/);
781
- });
782
- test('omitting package_source bakes no packageSource/packagePath into the config', async () => {
783
- const script = await buildScript();
784
- const configLine = script.split('\n').find((l) => l.startsWith('var CONFIG = '));
785
- expect(configLine).toBeDefined();
786
- expect(configLine).not.toContain('packageSource');
787
- expect(configLine).not.toContain('packagePath');
788
- });
789
- test('renders the checked-out Gradle project at package_path (no archive)', async () => {
790
- const project = makeProject({ screens: { XProof: 'x.png' } });
791
- writeReference(project, 'x.png');
792
- // The committed project lives at a repo-relative path; no package.tar.gz.
793
- writeGradleProjectDir(join(project.dir, 'android-app'));
794
- const run = runScript(project, await buildScript({ package_source: 'repo', package_path: 'android-app' }), { FAKE_ROBORAZZI_SCREENS: 'XProof' });
795
- expect(run.status).toBe(0);
796
- const doc = readResult(project);
797
- expect(doc.package_source).toBe('repo');
798
- expect(doc.error).toBeNull();
799
- const screen = doc.screens.find((s) => s.screen === 'XProof');
800
- expect(screen?.status).toBe('rendered');
801
- expect(screen?.rendered_image_path).toBe('ui-fidelity/rendered/XProof.png');
802
- // The render came from the checkout, not an extracted tarball.
803
- expect(existsSync(join(project.dir, '.ci', 'inputs', 'package.tar.gz'))).toBe(false);
804
- });
805
- test('a missing package_path directory fails cleanly with a per-build error', async () => {
806
- const project = makeProject({ screens: { XProof: 'x.png' } });
807
- writeReference(project, 'x.png');
808
- // No project at the path.
809
- const run = runScript(project, await buildScript({ package_source: 'repo', package_path: 'nope' }), { FAKE_ROBORAZZI_SCREENS: 'XProof' });
810
- expect(run.status).toBe(1);
811
- const doc = readResult(project);
812
- expect(doc.package_source).toBe('repo');
813
- expect(doc.error?.code).toBe('ANDROID_PROJECT_MISSING');
814
- });
815
- test('a package_path without a settings script fails cleanly', async () => {
816
- const project = makeProject({ screens: { XProof: 'x.png' } });
817
- writeReference(project, 'x.png');
818
- // A directory that is not a Gradle project root.
819
- mkdirSync(join(project.dir, 'not-a-project'), { recursive: true });
820
- writeFileSync(join(project.dir, 'not-a-project', 'README.md'), '# nope\n');
821
- const run = runScript(project, await buildScript({ package_source: 'repo', package_path: 'not-a-project' }), { FAKE_ROBORAZZI_SCREENS: 'XProof' });
822
- expect(run.status).toBe(1);
823
- expect(readResult(project).error?.code).toBe('ANDROID_PROJECT_ROOT_MISSING');
824
- });
825
- test('composes with reference_source repo (project + reference both from the checkout)', async () => {
826
- const project = makeProject({ screens: { XProof: 'refs/x.png' } });
827
- mkdirSync(join(project.dir, 'refs'), { recursive: true });
828
- writeFileSync(join(project.dir, 'refs', 'x.png'), Buffer.concat([PNG_MAGIC, Buffer.from('ref:x')]));
829
- writeGradleProjectDir(join(project.dir, 'android-app'));
830
- const run = runScript(project, await buildScript({
831
- package_source: 'repo',
832
- package_path: 'android-app',
833
- reference_source: 'repo',
834
- }), { FAKE_ROBORAZZI_SCREENS: 'XProof' });
835
- expect(run.status).toBe(0);
836
- const doc = readResult(project);
837
- expect(doc.package_source).toBe('repo');
838
- const screen = doc.screens.find((s) => s.screen === 'XProof');
839
- expect(screen?.status).toBe('rendered');
840
- expect(screen?.reference_image_path).toBe('ui-fidelity/references/XProof.png');
841
- });
842
- });
843
- // Clone-free package delivery (fidelity-linux 03): the dispatched YAML carries
844
- // a signed package_url; the runtime downloads it to .ci/inputs/package.tar.gz
845
- // and proceeds byte-for-byte like inputs mode, resolving each screen's
846
- // committed reference INSIDE the extracted package (repo-relative path minus
847
- // the package_path prefix) — there is no checkout on a clone-free worker.
848
- describe('package_source: url', () => {
849
- /**
850
- * Serve one tar.gz (or a 404) from a loopback HTTP server IN A CHILD
851
- * PROCESS. runScript executes the generated script via spawnSync, which
852
- * blocks this process's event loop — an in-process server would deadlock
853
- * against the script's curl. The child prints its port once listening.
854
- */
855
- const SERVE_PACKAGE_SRC = [
856
- "var http = require('http');",
857
- "var fs = require('fs');",
858
- 'var filePath = process.argv[1];',
859
- 'var server = http.createServer(function (req, res) {',
860
- " if (filePath === '-') { res.statusCode = 404; res.end('gone'); return; }",
861
- " res.setHeader('content-type', 'application/gzip');",
862
- ' res.end(fs.readFileSync(filePath));',
863
- '});',
864
- "server.listen(0, '127.0.0.1', function () {",
865
- " console.log('PORT ' + server.address().port);",
866
- '});',
867
- ].join('\n');
868
- async function servePackage(archive) {
869
- let filePath = '-';
870
- if (archive !== null) {
871
- const dir = mkdtempSync(join(tmpdir(), 'ui-fidelity-url-serve-'));
872
- tempDirs.push(dir);
873
- filePath = join(dir, 'package.tar.gz');
874
- writeFileSync(filePath, archive);
875
- }
876
- const child = spawn('node', ['-e', SERVE_PACKAGE_SRC, filePath], {
877
- stdio: ['ignore', 'pipe', 'inherit'],
878
- });
879
- const port = await new Promise((resolvePort, rejectPort) => {
880
- let buffered = '';
881
- const timer = setTimeout(() => rejectPort(new Error('package server did not start')), 10_000);
882
- child.stdout.on('data', (chunk) => {
883
- buffered += chunk.toString();
884
- const match = /PORT (\d+)/.exec(buffered);
885
- if (match) {
886
- clearTimeout(timer);
887
- resolvePort(Number(match[1]));
888
- }
889
- });
890
- child.on('exit', () => rejectPort(new Error('package server exited early')));
891
- });
892
- return {
893
- url: `http://127.0.0.1:${port}/api/fidelity/packages/blob1?exp=1&sig=ab`,
894
- close: () => new Promise((resolveClose) => {
895
- child.once('exit', () => resolveClose(undefined));
896
- child.kill('SIGKILL');
897
- }),
898
- };
899
- }
900
- /** Archive shaped like the dashboard's assembly: package-dir contents at the
901
- * archive root, committed references under references/. */
902
- function packagedProject() {
903
- return makeTarGz([
904
- ...gradleProjectEntries(''),
905
- { name: 'references/x.png', content: Buffer.concat([PNG_MAGIC, Buffer.from('ref:url-x')]) },
906
- ]);
907
- }
908
- test('package_source url requires package_url and package_path', async () => {
909
- const executor = new UiFidelityRenderAndroidStepExecutor();
910
- await expect(executor.execute({ package_source: 'url', package_path: 'android-app' }, {}, testConfig)).rejects.toThrow(/package_url/);
911
- await expect(executor.execute({ package_source: 'url', package_url: 'https://x.test/p' }, {}, testConfig)).rejects.toThrow(/package_path/);
912
- });
913
- test('rejects reference_source alongside package_source url (URL mode owns references)', async () => {
914
- const executor = new UiFidelityRenderAndroidStepExecutor();
915
- await expect(executor.execute({
916
- package_source: 'url',
917
- package_url: 'https://x.test/p',
918
- package_path: 'android-app',
919
- reference_source: 'repo',
920
- }, {}, testConfig)).rejects.toThrow(/reference_source/);
921
- });
922
- test('url mode requires curl on the runner (validation requirement)', () => {
923
- const executor = new UiFidelityRenderAndroidStepExecutor();
924
- const urlReqs = executor.getValidationRequirements({ package_source: 'url' }, {}, testConfig);
925
- expect(urlReqs.some((r) => r.name === 'curl')).toBe(true);
926
- // Default inputs mode keeps tar only — no new requirement for existing runs.
927
- const defaultReqs = executor.getValidationRequirements({}, {}, testConfig);
928
- expect(defaultReqs.some((r) => r.name === 'curl')).toBe(false);
929
- });
930
- test('downloads the package, renders, and resolves references inside it', async () => {
931
- const project = makeProject({ screens: { XProof: 'android-app/references/x.png' } });
932
- const served = await servePackage(packagedProject());
933
- try {
934
- const run = runScript(project, await buildScript({
935
- package_source: 'url',
936
- package_url: served.url,
937
- package_path: 'android-app',
938
- }), { FAKE_ROBORAZZI_SCREENS: 'XProof' });
939
- expect(run.status).toBe(0);
940
- const doc = readResult(project);
941
- expect(doc.package_source).toBe('url');
942
- const screen = doc.screens.find((s) => s.screen === 'XProof');
943
- expect(screen?.status).toBe('rendered');
944
- expect(screen?.reference_image_path).toBe('ui-fidelity/references/XProof.png');
945
- expect(isPng(artifact(project, 'ui-fidelity/references/XProof.png'))).toBe(true);
946
- }
947
- finally {
948
- await served.close();
949
- }
950
- });
951
- test('a failed download is one structured build error, not per-screen noise', async () => {
952
- const project = makeProject({ screens: { XProof: 'android-app/references/x.png' } });
953
- const served = await servePackage(null); // 404
954
- try {
955
- const run = runScript(project, await buildScript({
956
- package_source: 'url',
957
- package_url: served.url,
958
- package_path: 'android-app',
959
- }));
960
- expect(run.status).not.toBe(0);
961
- const doc = readResult(project);
962
- expect(doc.error?.code).toBe('ANDROID_PACKAGE_DOWNLOAD_FAILED');
963
- const screen = doc.screens.find((s) => s.screen === 'XProof');
964
- // The failure is the build's, not the screen's — no bogus
965
- // REFERENCE_MISSING masking the real cause.
966
- expect(screen?.error).toBeNull();
967
- }
968
- finally {
969
- await served.close();
970
- }
971
- });
972
- test('a reference outside the package directory fails that screen only', async () => {
973
- const project = makeProject({
974
- screens: {
975
- XProof: 'android-app/references/x.png',
976
- Stray: 'design/elsewhere/stray.png',
977
- },
978
- });
979
- const served = await servePackage(packagedProject());
980
- try {
981
- const run = runScript(project, await buildScript({
982
- package_source: 'url',
983
- package_url: served.url,
984
- package_path: 'android-app',
985
- }), { FAKE_ROBORAZZI_SCREENS: 'XProof' });
986
- expect(run.status).not.toBe(0);
987
- const doc = readResult(project);
988
- expect(doc.screens.find((s) => s.screen === 'XProof')?.status).toBe('rendered');
989
- const stray = doc.screens.find((s) => s.screen === 'Stray');
990
- expect(stray?.status).toBe('render_failed');
991
- expect(stray?.error?.code).toBe('REFERENCE_MISSING');
992
- }
993
- finally {
994
- await served.close();
995
- }
996
- });
997
- });
998
- describe('reference_source: repo', () => {
999
- test('rejects an unknown reference_source value', async () => {
1000
- const executor = new UiFidelityRenderAndroidStepExecutor();
1001
- await expect(executor.execute({ reference_source: 'artifact' }, {}, testConfig)).rejects.toThrow(/reference_source/);
1002
- });
1003
- test('omitting reference_source bakes no referenceSource into the script config', async () => {
1004
- const script = await buildScript();
1005
- const configLine = script.split('\n').find((l) => l.startsWith('var CONFIG = '));
1006
- expect(configLine).toBeDefined();
1007
- expect(configLine).not.toContain('referenceSource');
1008
- });
1009
- test('reads each screen reference from its repo-relative path in the checkout', async () => {
1010
- const project = makeProject({
1011
- screens: { XProof: 'refs/x.png', YProof: 'design/y.png' },
1012
- });
1013
- // Committed references live at repo-relative paths (resolved against cwd),
1014
- // NOT in .ci/inputs.
1015
- mkdirSync(join(project.dir, 'refs'), { recursive: true });
1016
- mkdirSync(join(project.dir, 'design'), { recursive: true });
1017
- writeFileSync(join(project.dir, 'refs', 'x.png'), Buffer.concat([PNG_MAGIC, Buffer.from('ref:x')]));
1018
- writeFileSync(join(project.dir, 'design', 'y.png'), Buffer.concat([PNG_MAGIC, Buffer.from('ref:y')]));
1019
- stageArchive(project, gradleProjectEntries());
1020
- const run = runScript(project, await buildScript({ reference_source: 'repo' }), {
1021
- FAKE_ROBORAZZI_SCREENS: 'XProof,YProof',
1022
- });
1023
- expect(run.status).toBe(0);
1024
- const doc = readResult(project);
1025
- expect(doc.screens.map((s) => s.status)).toEqual(['rendered', 'rendered']);
1026
- expect(doc.screens.map((s) => s.reference_image_path)).toEqual([
1027
- 'ui-fidelity/references/XProof.png',
1028
- 'ui-fidelity/references/YProof.png',
1029
- ]);
1030
- expect(isPng(artifact(project, 'ui-fidelity/references/XProof.png'))).toBe(true);
1031
- expect(isPng(artifact(project, 'ui-fidelity/references/YProof.png'))).toBe(true);
1032
- });
1033
- test('fails one screen when its committed reference is missing, renders the rest', async () => {
1034
- const project = makeProject({
1035
- screens: { XProof: 'refs/x.png', GhostProof: 'refs/ghost.png' },
1036
- });
1037
- mkdirSync(join(project.dir, 'refs'), { recursive: true });
1038
- writeFileSync(join(project.dir, 'refs', 'x.png'), Buffer.concat([PNG_MAGIC, Buffer.from('ref:x')]));
1039
- // refs/ghost.png intentionally absent.
1040
- stageArchive(project, gradleProjectEntries());
1041
- const run = runScript(project, await buildScript({ reference_source: 'repo' }), {
1042
- FAKE_ROBORAZZI_SCREENS: 'XProof',
1043
- });
1044
- expect(run.status).not.toBe(0);
1045
- const doc = readResult(project);
1046
- expect(doc.screens.find((s) => s.screen === 'XProof')?.status).toBe('rendered');
1047
- const ghost = doc.screens.find((s) => s.screen === 'GhostProof');
1048
- expect(ghost?.status).toBe('render_failed');
1049
- expect(ghost?.error?.code).toBe('REFERENCE_MISSING');
1050
- });
1051
- });
1052
- // The fake gradlew/swift cannot exercise the real Roborazzi/Robolectric render
1053
- // or the CoreGraphics trim. This end-to-end guard ships the committed Roborazzi
1054
- // fixture as the render package and runs the whole step with the real
1055
- // toolchain, proving the tracer bullet: ship a lean Gradle project, get a
1056
- // paired result back. Gated behind CIBUILD_UI_FIDELITY_REAL_ANDROID=1 (the
1057
- // Android analog of the iOS real-Swift gate); needs the Android toolchain +
1058
- // warm Roborazzi caches on the dev machine.
1059
- describe('android render runtime — real toolchain', () => {
1060
- const realAndroidTest = process.env.CIBUILD_UI_FIDELITY_REAL_ANDROID === '1' ? test : test.skip;
1061
- // A real 1x1 PNG so the trim helper's reference load/align step succeeds.
1062
- const ONE_BY_ONE_PNG = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==', 'base64');
1063
- const FIXTURE_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '../../../../cibuild-runner/scripts/roborazzi-fixture');
1064
- realAndroidTest('renders the Roborazzi fixture at 720x1280 and returns a paired result', async () => {
1065
- const dir = mkdtempSync(join(tmpdir(), 'ui-fidelity-android-real-'));
1066
- tempDirs.push(dir);
1067
- const project = { dir, binDir: '' };
1068
- mkdirSync(join(dir, '.ci', 'inputs'), { recursive: true });
1069
- writeFileSync(join(dir, '.ci', 'inputs', 'params.json'), JSON.stringify({ screens: { XProof: 'ref.png' } }));
1070
- writeFileSync(join(dir, '.ci', 'inputs', 'ref.png'), ONE_BY_ONE_PNG);
1071
- // Archive the committed fixture (minus build/.gradle noise) as the
1072
- // shipped render package, with the real system tar.
1073
- const stage = mkdtempSync(join(tmpdir(), 'ui-fidelity-android-fixture-'));
1074
- tempDirs.push(stage);
1075
- cpSync(FIXTURE_DIR, join(stage, 'project'), { recursive: true });
1076
- const archive = join(dir, '.ci', 'inputs', 'package.tar.gz');
1077
- const tar = spawnSync('tar', ['-czf', archive, '-C', stage, '--exclude', '*/build', '--exclude', '*/.gradle', 'project'], { encoding: 'utf-8' });
1078
- expect(tar.stderr).toBe('');
1079
- expect(tar.status).toBe(0);
1080
- const script = await buildScript();
1081
- const run = spawnSync('node', ['-e', script], {
1082
- cwd: dir,
1083
- encoding: 'utf-8',
1084
- env: { ...process.env },
1085
- maxBuffer: 64 * 1024 * 1024,
1086
- });
1087
- if (run.status !== 0) {
1088
- // eslint-disable-next-line no-console
1089
- console.error('STDOUT\n' + run.stdout + '\nSTDERR\n' + run.stderr);
1090
- }
1091
- expect(run.status).toBe(0);
1092
- const doc = readResult(project);
1093
- expect(doc.error).toBeNull();
1094
- const screen = doc.screens.find((s) => s.screen === 'XProof');
1095
- expect(screen?.status).toBe('rendered');
1096
- expect(screen?.error).toBeNull();
1097
- // The raw render before trimming is the fixture's full 720x1280 canvas.
1098
- expect(run.stdout).toContain('XProof: 720x1280');
1099
- // Paired, contract-shaped artifacts are produced.
1100
- expect(isPng(artifact(project, 'ui-fidelity/rendered/XProof.png'))).toBe(true);
1101
- expect(isPng(artifact(project, 'ui-fidelity/aligned/XProof.png'))).toBe(true);
1102
- expect(screen?.dimensions?.rendered_px[0]).toBeGreaterThan(0);
1103
- expect(screen?.dimensions?.rendered_px[1]).toBeGreaterThan(0);
1104
- }, 600_000);
1105
- });
1106
- //# sourceMappingURL=ui-fidelity-render-android.test.js.map