tubeless 0.2.11 → 0.2.13

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 (196) hide show
  1. package/dist/core/child-execution.d.ts +1 -24
  2. package/dist/core/child-execution.js +11 -13
  3. package/dist/core/child-execution.js.map +1 -1
  4. package/dist/core/child-progress.js +7 -2
  5. package/dist/core/child-progress.js.map +1 -1
  6. package/dist/core/iteration.d.ts +27 -0
  7. package/dist/core/iteration.js +111 -0
  8. package/dist/core/iteration.js.map +1 -0
  9. package/dist/core/lifecycle.d.ts +7 -5
  10. package/dist/core/lifecycle.js +36 -4
  11. package/dist/core/lifecycle.js.map +1 -1
  12. package/dist/core/pipeline-artifacts.d.ts +11 -0
  13. package/dist/core/pipeline-artifacts.js +2 -0
  14. package/dist/core/pipeline-artifacts.js.map +1 -0
  15. package/dist/core/pipeline-compiler.d.ts +1 -0
  16. package/dist/core/pipeline-compiler.js +1 -0
  17. package/dist/core/pipeline-compiler.js.map +1 -1
  18. package/dist/core/pipeline-definition-identity.d.ts +2 -2
  19. package/dist/core/pipeline-definition-identity.js +28 -5
  20. package/dist/core/pipeline-definition-identity.js.map +1 -1
  21. package/dist/core/pipeline-execute.d.ts +1 -6
  22. package/dist/core/pipeline-execute.js +28 -215
  23. package/dist/core/pipeline-execute.js.map +1 -1
  24. package/dist/core/pipeline-execution-error.d.ts +35 -0
  25. package/dist/core/pipeline-execution-error.js +195 -0
  26. package/dist/core/pipeline-execution-error.js.map +1 -0
  27. package/dist/core/pipeline-identity.d.ts +1 -0
  28. package/dist/core/pipeline-identity.js +1 -0
  29. package/dist/core/pipeline-identity.js.map +1 -1
  30. package/dist/core/pipeline-mermaid.js +4 -1
  31. package/dist/core/pipeline-mermaid.js.map +1 -1
  32. package/dist/core/pipeline-run-state.d.ts +4 -4
  33. package/dist/core/pipeline-run-state.js +10 -16
  34. package/dist/core/pipeline-run-state.js.map +1 -1
  35. package/dist/core/pipeline-step-executor.d.ts +2 -0
  36. package/dist/core/pipeline-step-executor.js +11 -3
  37. package/dist/core/pipeline-step-executor.js.map +1 -1
  38. package/dist/core/pipeline-steps.d.ts +24 -1
  39. package/dist/core/pipeline-steps.js +62 -0
  40. package/dist/core/pipeline-steps.js.map +1 -1
  41. package/dist/core/pipeline-types.d.ts +28 -3
  42. package/dist/core/pipeline-types.js.map +1 -1
  43. package/dist/core/pipeline.d.ts +4 -1
  44. package/dist/core/pipeline.js +22 -4
  45. package/dist/core/pipeline.js.map +1 -1
  46. package/dist/project/project-compiler.d.ts +3 -31
  47. package/dist/project/project-compiler.js +11 -180
  48. package/dist/project/project-compiler.js.map +1 -1
  49. package/dist/project/project-registry.d.ts +44 -0
  50. package/dist/project/project-registry.js +107 -0
  51. package/dist/project/project-registry.js.map +1 -0
  52. package/dist/project/project-step-graph.d.ts +9 -0
  53. package/dist/project/project-step-graph.js +99 -0
  54. package/dist/project/project-step-graph.js.map +1 -0
  55. package/dist/render/render.js +1 -1
  56. package/dist/render/render.js.map +1 -1
  57. package/dist/reporter/interactive-reporter.js +6 -3
  58. package/dist/reporter/interactive-reporter.js.map +1 -1
  59. package/dist/reporter/live-ticker-frame.d.ts +21 -0
  60. package/dist/reporter/live-ticker-frame.js +196 -0
  61. package/dist/reporter/live-ticker-frame.js.map +1 -0
  62. package/dist/reporter/live-ticker-protocol.d.ts +41 -0
  63. package/dist/reporter/live-ticker-protocol.js +59 -0
  64. package/dist/reporter/live-ticker-protocol.js.map +1 -0
  65. package/dist/reporter/live-ticker-worker.js +11 -25
  66. package/dist/reporter/live-ticker-worker.js.map +1 -1
  67. package/dist/reporter/live-ticker.d.ts +0 -15
  68. package/dist/reporter/live-ticker.js +11 -216
  69. package/dist/reporter/live-ticker.js.map +1 -1
  70. package/dist/reporter/reporter.js +7 -6
  71. package/dist/reporter/reporter.js.map +1 -1
  72. package/dist/run-store/definition-projection.d.ts +8 -0
  73. package/dist/run-store/definition-projection.js +89 -0
  74. package/dist/run-store/definition-projection.js.map +1 -0
  75. package/dist/run-store/run-projection.d.ts +15 -0
  76. package/dist/run-store/run-projection.js +203 -0
  77. package/dist/run-store/run-projection.js.map +1 -0
  78. package/dist/run-store/run-store-codec.d.ts +2 -0
  79. package/dist/run-store/run-store-codec.js +11 -0
  80. package/dist/run-store/run-store-codec.js.map +1 -1
  81. package/dist/run-store/run-store-event-decoder.js +3 -0
  82. package/dist/run-store/run-store-event-decoder.js.map +1 -1
  83. package/dist/run-store/run-store-reader.d.ts +7 -0
  84. package/dist/run-store/run-store-reader.js +31 -0
  85. package/dist/run-store/run-store-reader.js.map +1 -0
  86. package/dist/run-store/run-store-sqlite.js +23 -7
  87. package/dist/run-store/run-store-sqlite.js.map +1 -1
  88. package/dist/run-store/run-store.d.ts +16 -4
  89. package/dist/run-store/run-store.js +14 -263
  90. package/dist/run-store/run-store.js.map +1 -1
  91. package/dist/studio/run-store-ui-api.d.ts +13 -0
  92. package/dist/studio/run-store-ui-api.js +163 -0
  93. package/dist/studio/run-store-ui-api.js.map +1 -0
  94. package/dist/studio/run-store-ui-client-browser.js +2 -2
  95. package/dist/studio/run-store-ui-client-transport.js +28 -3
  96. package/dist/studio/run-store-ui-client-transport.js.map +1 -1
  97. package/dist/studio/run-store-ui-client.d.ts +2 -1
  98. package/dist/studio/run-store-ui-client.js +15 -9
  99. package/dist/studio/run-store-ui-client.js.map +1 -1
  100. package/dist/studio/run-store-ui-http.d.ts +11 -0
  101. package/dist/studio/run-store-ui-http.js +52 -0
  102. package/dist/studio/run-store-ui-http.js.map +1 -1
  103. package/dist/studio/run-store-ui-state.d.ts +1 -1
  104. package/dist/studio/run-store-ui-state.js +10 -28
  105. package/dist/studio/run-store-ui-state.js.map +1 -1
  106. package/dist/studio/run-store-ui.d.ts +5 -11
  107. package/dist/studio/run-store-ui.js +5 -203
  108. package/dist/studio/run-store-ui.js.map +1 -1
  109. package/dist/testing/testing.d.ts +18 -2
  110. package/dist/testing/testing.js +35 -2
  111. package/dist/testing/testing.js.map +1 -1
  112. package/dist/tracing/artifact-metadata.d.ts +28 -0
  113. package/dist/tracing/artifact-metadata.js +105 -0
  114. package/dist/tracing/artifact-metadata.js.map +1 -0
  115. package/dist/tracing/tracing-codec.d.ts +1 -1
  116. package/dist/tracing/tracing-codec.js +1 -1
  117. package/dist/tracing/tracing-codec.js.map +1 -1
  118. package/dist/tracing/tracing-constants.d.ts +1 -1
  119. package/dist/tracing/tracing-constants.js +1 -1
  120. package/dist/tracing/tracing-constants.js.map +1 -1
  121. package/dist/tracing/tracing-contracts.d.ts +3 -1
  122. package/dist/tracing/tracing-contracts.js.map +1 -1
  123. package/dist/tracing/tracing-internal.d.ts +2 -0
  124. package/dist/tracing/tracing-internal.js +36 -13
  125. package/dist/tracing/tracing-internal.js.map +1 -1
  126. package/dist/tracing/tracing-schema.d.ts +330 -49
  127. package/dist/tracing/tracing-schema.js +102 -10
  128. package/dist/tracing/tracing-schema.js.map +1 -1
  129. package/dist/workbench/workbench-command-execution.d.ts +6 -0
  130. package/dist/workbench/workbench-command-execution.js +48 -0
  131. package/dist/workbench/workbench-command-execution.js.map +1 -0
  132. package/dist/workbench/workbench-history.js +11 -18
  133. package/dist/workbench/workbench-history.js.map +1 -1
  134. package/dist/workbench/workbench-launch-session.d.ts +2 -2
  135. package/dist/workbench/workbench-launch-session.js.map +1 -1
  136. package/dist/workbench/workbench-run.d.ts +0 -4
  137. package/dist/workbench/workbench-run.js +3 -47
  138. package/dist/workbench/workbench-run.js.map +1 -1
  139. package/dist/workbench/workbench-studio-launcher.d.ts +27 -0
  140. package/dist/workbench/workbench-studio-launcher.js +95 -0
  141. package/dist/workbench/workbench-studio-launcher.js.map +1 -0
  142. package/dist/workbench/workbench-ui.js +10 -83
  143. package/dist/workbench/workbench-ui.js.map +1 -1
  144. package/docs/README.md +13 -7
  145. package/docs/agent-guide.md +75 -6
  146. package/docs/airflow.md +187 -0
  147. package/docs/api-reference.md +87 -76
  148. package/docs/api-report.json +17 -6
  149. package/docs/artifacts.md +173 -0
  150. package/docs/child-pipeline-composition.md +87 -8
  151. package/docs/comparison.md +20 -0
  152. package/docs/concepts.md +79 -2
  153. package/docs/dagster.md +161 -0
  154. package/docs/inngest.md +163 -0
  155. package/docs/llms.txt +30 -1
  156. package/docs/recipes.md +82 -32
  157. package/docs/remote-step-composition.md +6 -0
  158. package/docs/step-functions.md +190 -0
  159. package/docs/studio.md +31 -12
  160. package/docs/temporal.md +183 -0
  161. package/examples/airflow/contract.ts +46 -0
  162. package/examples/airflow/hosted.ts +70 -0
  163. package/examples/airflow/remote.ts +175 -0
  164. package/examples/airflow/tubeless_example.py +49 -0
  165. package/examples/airflow/worker.ts +24 -0
  166. package/examples/artifact-lineage.ts +70 -0
  167. package/examples/dagster/definitions.py +59 -0
  168. package/examples/dagster/hosted.ts +78 -0
  169. package/examples/dagster/pipeline.ts +62 -0
  170. package/examples/dagster/test_definitions.py +47 -0
  171. package/examples/dagster/tsconfig.build.json +10 -0
  172. package/examples/dagster/worker.ts +22 -0
  173. package/examples/inherited-inputs.ts +28 -0
  174. package/examples/inngest/client.ts +4 -0
  175. package/examples/inngest/functions.ts +65 -0
  176. package/examples/inngest/pipeline.ts +41 -0
  177. package/examples/inngest/send.ts +11 -0
  178. package/examples/inngest/server.ts +17 -0
  179. package/examples/inngest/tsconfig.build.json +10 -0
  180. package/examples/iteration.ts +52 -0
  181. package/examples/parameterized-steps.ts +96 -0
  182. package/examples/project/tubeless.project.ts +50 -0
  183. package/examples/step-functions/handler.ts +110 -0
  184. package/examples/step-functions/input.json +1 -0
  185. package/examples/step-functions/pipeline.ts +41 -0
  186. package/examples/step-functions/state-machine.asl.json +41 -0
  187. package/examples/step-functions/template.json +42 -0
  188. package/examples/step-output-overrides.ts +41 -0
  189. package/examples/temporal/activities.ts +94 -0
  190. package/examples/temporal/client.ts +19 -0
  191. package/examples/temporal/pipeline.ts +41 -0
  192. package/examples/temporal/tsconfig.build.json +10 -0
  193. package/examples/temporal/worker.ts +21 -0
  194. package/examples/temporal/workflows.ts +16 -0
  195. package/package.json +13 -4
  196. package/skills/tubeless/SKILL.md +37 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live-ticker-frame.js","sourceRoot":"","sources":["../../src/reporter/live-ticker-frame.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjD,MAAM,CAAC,MAAM,aAAa,GAAG,QAAQ,CAAC;AACtC,MAAM,mBAAmB,GAAG,QAAQ,CAAC;AACrC,MAAM,iBAAiB,GAAG,QAAQ,CAAC;AACnC,MAAM,mBAAmB,GAAG,QAAQ,CAAC;AACrC,MAAM,iBAAiB,GAAG,QAAQ,CAAC;AAEnC,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,SAAS,EAAE,UAAU;IACrB,UAAU,EAAE,aAAa;IACzB,KAAK,EAAE,WAAW;IAClB,UAAU,EAAE,aAAa;CACjB,CAAC;AAEX,MAAM,oBAAoB,GAAG,CAAC,CAAC;AAC/B,MAAM,cAAc,GAAG,gBAAgB,CAAC;AACxC,MAAM,WAAW,GAAG,gBAAgB,CAAC;AACrC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAE5B,MAAM,UAAU,GAAG,mBAAmB,CAAC;AACvC,MAAM,iBAAiB,GAAG,mBAAmB,CAAC;AAC9C,MAAM,cAAc,GAAG,WAAW,CAAC;AACnC,MAAM,KAAK,GAAG,4BAA4B,CAAC;AAE3C,MAAM,QAAQ,GAAG;IACf,KAAK,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC;IAC5B,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC;CACnD,CAAC;AAEX,SAAS,cAAc,CAAC,SAAiB;IACvC,IAAI,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,SAAS,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC;IACvE,IAAI,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC;QAAE,OAAO,CAAC,CAAC;IACpC,MAAM,SAAS,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAChD,OAAO,SAAS,IAAI,MAAM;QACxB,CAAC,SAAS,IAAI,MAAM;YAClB,SAAS,KAAK,MAAM;YACpB,SAAS,KAAK,MAAM;YACpB,CAAC,SAAS,IAAI,MAAM,IAAI,SAAS,IAAI,MAAM,CAAC;YAC5C,CAAC,SAAS,IAAI,MAAM,IAAI,SAAS,IAAI,MAAM,CAAC;YAC5C,CAAC,SAAS,IAAI,MAAM,IAAI,SAAS,IAAI,MAAM,CAAC;YAC5C,CAAC,SAAS,IAAI,MAAM,IAAI,SAAS,IAAI,MAAM,CAAC;YAC5C,CAAC,SAAS,IAAI,MAAM,IAAI,SAAS,IAAI,MAAM,CAAC;YAC5C,CAAC,SAAS,IAAI,MAAM,IAAI,SAAS,IAAI,MAAM,CAAC;YAC5C,CAAC,SAAS,IAAI,OAAO,IAAI,SAAS,IAAI,OAAO,CAAC,CAAC;QACjD,CAAC,CAAC,CAAC;QACH,CAAC,CAAC,CAAC,CAAC;AACR,CAAC;AAED,SAAS,YAAY,CAAC,KAAa;IACjC,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,CAAC,KAAK,GAAG,cAAc,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;AACvF,CAAC;AAED,SAAS,OAAO,CAAC,KAAa,EAAE,OAA2B,EAAE,OAAgB;IAC3E,IAAI,CAAC,OAAO,IAAI,OAAO,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3C,MAAM,QAAQ,GAAG,OAAO,GAAG,CAAC,CAAC;IAC7B,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAC5C,IAAI,YAAY,CAAC,KAAK,CAAC,IAAI,QAAQ;QAAE,OAAO,KAAK,CAAC;IAClD,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;IACnE,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;IACnE,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,SAAS,GAAG,EAAE,CAAC;IACnB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,OAAO,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC/D,IAAI,KAAK,EAAE,CAAC;YACV,SAAS,IAAI,KAAK,CAAC;YACnB,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC;YACtB,QAAQ,GAAG,IAAI,CAAC;YAChB,SAAS;QACX,CAAC;QACD,MAAM,SAAS,GAAG,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QACtE,MAAM,SAAS,GAAG,KAAK,GAAG,cAAc,CAAC,SAAS,CAAC,CAAC;QACpD,IAAI,SAAS,GAAG,WAAW;YAAE,MAAM;QACnC,SAAS,IAAI,SAAS,CAAC;QACvB,KAAK,GAAG,SAAS,CAAC;QAClB,KAAK,IAAI,SAAS,CAAC,MAAM,CAAC;IAC5B,CAAC;IACD,OAAO,GAAG,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,CAAC;AACvE,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,WAAmB;IAC9C,OAAO,GAAG,mBAAmB,GAAG,WAAW,GAAG,iBAAiB,EAAE,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO,GAAG,mBAAmB,GAAG,KAAK,GAAG,iBAAiB,EAAE,CAAC;AAC9D,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAY,EAAE,KAAa;IACnD,MAAM,UAAU,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IAC7B,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACzC,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC9C,MAAM,UAAU,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,oBAAoB,CAAC,CAAC;IAC9D,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,gBAAgB,CAAC,GAAG,MAAM,CAAC;IAC3D,MAAM,SAAS,GAAG,IAAI,GAAG,oBAAoB,GAAG,CAAC,CAAC;IAClD,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC;IAC/B,IAAI,IAAkC,CAAC;IACvC,KAAK,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC;QACtD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACjC,MAAM,MAAM,GACV,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,IAAI,IAAI,IAAI,MAAM,GAAG,KAAK,GAAG,CAAC,IAAI,SAAS,CAAC;QACtF,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC;QACxC,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,WAAW,CAAC;YAC5D,IAAI,GAAG,KAAK,CAAC;QACf,CAAC;QACD,MAAM,IAAI,SAAS,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC;IAClB,CAAC;IACD,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC,KAAK,EAAE,CAAC;AACzC,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAY,EAAE,OAAe,EAAE,KAAa,EAAE,KAAc;IACrF,OAAO,IAAI;SACR,UAAU,CAAC,aAAa,EAAE,OAAO,CAAC;SAClC,OAAO,CACN,IAAI,MAAM,CAAC,GAAG,mBAAmB,SAAS,iBAAiB,EAAE,EAAE,GAAG,CAAC,EACnE,CAAC,MAAM,EAAE,KAAa,EAAE,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAChF;SACA,OAAO,CACN,IAAI,MAAM,CAAC,GAAG,mBAAmB,QAAQ,iBAAiB,EAAE,EAAE,GAAG,CAAC,EAClE,CAAC,MAAM,EAAE,IAAY,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CACzE,CAAC;AACN,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,KAAwB,EACxB,OAAe,EACf,KAAa,EACb,OAAgB,EAChB,KAAK,GAAG,KAAK,EACb,OAAO,GAAG,IAAI,EACd,OAA2B;IAE3B,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;IACpF,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,IAAI,OAAO,GAAG,GAAG,EAAE,CAAC;QAC1C,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IAChE,CAAC;IACD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC;IAC1D,MAAM,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,CAAC,CAAC;IAC1C,MAAM,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IACvC,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IACrC,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,KAAa,EAAU,EAAE;QAClD,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;QACjD,OAAO,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAChG,CAAC,CAAC;IACF,MAAM,IAAI,GAAG;QACX,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,UAAU,SAAS,UAAU,CAAC,MAAM,CAAC,SAAS,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE;QACpG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,QAAQ,IAAI,GAAG,CAAC,IAAI,EAAE,SAAS,GAAG,CAAC,CAAC,IAAI,QAAQ,EAAE,CAAC;QAC/E,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,SAAS,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE;KAClF,CAAC;IACF,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAChF,IAAI,CAAC,KAAK,CAAC,KAAK,SAAS;QACvB,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,SAAS,GAAG,CAAC,EAAE,OAAO,CAAC;QACvD,CAAC,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,SAAS,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,EAAE,CAC9D,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,WAAW;IAIH,KAAK;IACd,YAAY;IAJd,YAAY,CAAU;IAE9B,YACmB,KAA8B,EACvC,YAAY,GAAsB,EAAE;qBAD3B,KAAK;4BACd,YAAY;QAEpB,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC;IAC9C,CAAC;IAED,UAAU;QACR,IAAI,CAAC,IAAI,CAAC,YAAY;YAAE,OAAO;QAC/B,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;QACnC,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,OAAgB;QACpB,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC3C,wEAAwE;QACxE,+EAA+E;QAC/E,IAAI,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC;QACpC,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;YACzC,IAAI,GAAG,CAAC,CAAC;YACT,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;gBACrC,IAAI,IAAI,CAAC,CAAC;gBACV,IAAI,MAAM,GAAG,CAAC,CAAC;gBACf,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,EAAE,CAAC;oBACrD,MAAM,KAAK,GAAG,cAAc,CAAC,SAAS,CAAC,CAAC;oBACxC,IAAI,MAAM,GAAG,KAAK,GAAG,OAAO,EAAE,CAAC;wBAC7B,IAAI,IAAI,CAAC,CAAC;wBACV,MAAM,GAAG,CAAC,CAAC;oBACb,CAAC;oBACD,MAAM,IAAI,KAAK,CAAC;gBAClB,CAAC;YACH,CAAC;QACH,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,UAAU,IAAI,IAAI,WAAW,CAAC,SAAS,EAAE,CAAC,CAAC;QACtD,IAAI,CAAC,YAAY,GAAG,EAAE,CAAC;IACzB,CAAC;IAED,MAAM,CAAC,KAAwB,EAAE,OAAgB;QAC/C,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;YACnC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QAC3B,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACpB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC/B,IAAI,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;IAC5B,CAAC;CACF;AAED,MAAM,UAAU,cAAc,CAAC,OAAgB,EAAE,iBAAyB;IACxE,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;IAC3D,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,iBAAiB,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,CAAC,CAAE,CAAC;AAC1F,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAwB;IACvD,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC,CAAC;AAClG,CAAC","sourcesContent":["import { formatDurationMs } from \"./reporter.js\";\n\nexport const SPINNER_TOKEN = \"\\u0001\";\nconst ELAPSED_TOKEN_START = \"\\u0002\";\nconst ELAPSED_TOKEN_END = \"\\u0003\";\nconst SHIMMER_TOKEN_START = \"\\u0004\";\nconst SHIMMER_TOKEN_END = \"\\u0005\";\n\nexport const TICKER_ANSI = {\n clearDown: \"\\u001B[J\",\n hideCursor: \"\\u001B[?25l\",\n reset: \"\\u001B[0m\",\n showCursor: \"\\u001B[?25h\",\n} as const;\n\nconst SHIMMER_BAND_COLUMNS = 3;\nconst SHIMMER_BRIGHT = \"\\u001B[0;1;36m\";\nconst SHIMMER_DIM = \"\\u001B[0;2;36m\";\nconst SHIMMER_FRAME_MS = 80;\n\nconst ANSI_STYLE = /\\u001B\\[[0-9;]*m/g;\nconst ANSI_STYLE_PREFIX = /^\\u001B\\[[0-9;]*m/;\nconst COMBINING_MARK = /\\p{Mark}/u;\nconst EMOJI = /\\p{Extended_Pictographic}/u;\n\nconst SPINNERS = {\n ascii: [\"-\", \"\\\\\", \"|\", \"/\"],\n unicode: [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"],\n} as const;\n\nfunction characterWidth(character: string): number {\n if (COMBINING_MARK.test(character) || character === \"\\u200D\") return 0;\n if (EMOJI.test(character)) return 2;\n const codePoint = character.codePointAt(0) ?? 0;\n return codePoint >= 0x1100 &&\n (codePoint <= 0x115f ||\n codePoint === 0x2329 ||\n codePoint === 0x232a ||\n (codePoint >= 0x2e80 && codePoint <= 0xa4cf) ||\n (codePoint >= 0xac00 && codePoint <= 0xd7a3) ||\n (codePoint >= 0xf900 && codePoint <= 0xfaff) ||\n (codePoint >= 0xfe10 && codePoint <= 0xfe6f) ||\n (codePoint >= 0xff00 && codePoint <= 0xff60) ||\n (codePoint >= 0xffe0 && codePoint <= 0xffe6) ||\n (codePoint >= 0x20000 && codePoint <= 0x3fffd))\n ? 2\n : 1;\n}\n\nfunction visibleWidth(value: string): number {\n return [...value].reduce((width, character) => width + characterWidth(character), 0);\n}\n\nfunction fitLine(value: string, columns: number | undefined, unicode: boolean): string {\n if (!columns || columns <= 1) return value;\n const maxWidth = columns - 1;\n const plain = value.replace(ANSI_STYLE, \"\");\n if (visibleWidth(plain) <= maxWidth) return value;\n const ellipsis = unicode ? \"…\" : \".\".repeat(Math.min(3, maxWidth));\n const targetWidth = Math.max(0, maxWidth - visibleWidth(ellipsis));\n let width = 0;\n let truncated = \"\";\n let index = 0;\n let hasStyle = false;\n while (index < value.length) {\n const style = value.slice(index).match(ANSI_STYLE_PREFIX)?.[0];\n if (style) {\n truncated += style;\n index += style.length;\n hasStyle = true;\n continue;\n }\n const character = String.fromCodePoint(value.codePointAt(index) ?? 0);\n const nextWidth = width + characterWidth(character);\n if (nextWidth > targetWidth) break;\n truncated += character;\n width = nextWidth;\n index += character.length;\n }\n return `${truncated}${hasStyle ? TICKER_ANSI.reset : \"\"}${ellipsis}`;\n}\n\nexport function elapsedToken(startedAtMs: number): string {\n return `${ELAPSED_TOKEN_START}${startedAtMs}${ELAPSED_TOKEN_END}`;\n}\n\nexport function shimmerToken(value: string): string {\n return `${SHIMMER_TOKEN_START}${value}${SHIMMER_TOKEN_END}`;\n}\n\nfunction paintShimmerText(text: string, nowMs: number): string {\n const characters = [...text];\n if (characters.length === 0) return text;\n const widths = characters.map(characterWidth);\n const totalWidth = visibleWidth(text);\n const period = Math.max(1, totalWidth + SHIMMER_BAND_COLUMNS);\n const head = Math.floor(nowMs / SHIMMER_FRAME_MS) % period;\n const bandStart = head - SHIMMER_BAND_COLUMNS + 1;\n let column = 0;\n let output = TICKER_ANSI.reset;\n let last: \"bright\" | \"dim\" | undefined;\n for (const [index, character] of characters.entries()) {\n const width = widths[index] ?? 0;\n const inBand =\n width === 0 ? last === \"bright\" : column <= head && column + width - 1 >= bandStart;\n const style = inBand ? \"bright\" : \"dim\";\n if (style !== last) {\n output += style === \"bright\" ? SHIMMER_BRIGHT : SHIMMER_DIM;\n last = style;\n }\n output += character;\n column += width;\n }\n return `${output}${TICKER_ANSI.reset}`;\n}\n\nfunction replaceLiveTokens(line: string, spinner: string, nowMs: number, color: boolean): string {\n return line\n .replaceAll(SPINNER_TOKEN, spinner)\n .replace(\n new RegExp(`${ELAPSED_TOKEN_START}(\\\\d+)${ELAPSED_TOKEN_END}`, \"g\"),\n (_match, start: string) => formatDurationMs(Math.max(0, nowMs - Number(start)))\n )\n .replace(\n new RegExp(`${SHIMMER_TOKEN_START}(.*?)${SHIMMER_TOKEN_END}`, \"g\"),\n (_match, text: string) => (color ? paintShimmerText(text, nowMs) : text)\n );\n}\n\nexport function paintLiveLines(\n lines: readonly string[],\n spinner: string,\n nowMs: number,\n columns?: number,\n color = false,\n unicode = true,\n logPane?: readonly string[]\n): string[] {\n const painted = lines.map((line) => replaceLiveTokens(line, spinner, nowMs, color));\n if (!logPane || !columns || columns < 120) {\n return painted.map((line) => fitLine(line, columns, unicode));\n }\n const paneWidth = Math.min(60, Math.floor(columns * 0.4));\n const leftWidth = columns - paneWidth - 3;\n const horizontal = unicode ? \"─\" : \"-\";\n const vertical = unicode ? \"│\" : \"|\";\n const pad = (line: string, width: number): string => {\n const fitted = fitLine(line, width + 1, unicode);\n return fitted + \" \".repeat(Math.max(0, width - visibleWidth(fitted.replace(ANSI_STYLE, \"\"))));\n };\n const pane = [\n `${unicode ? \"╭\" : \"+\"}${horizontal} Logs ${horizontal.repeat(paneWidth - 9)}${unicode ? \"╮\" : \"+\"}`,\n ...logPane.map((line) => `${vertical} ${pad(line, paneWidth - 4)} ${vertical}`),\n `${unicode ? \"╰\" : \"+\"}${horizontal.repeat(paneWidth - 2)}${unicode ? \"╯\" : \"+\"}`,\n ];\n return Array.from({ length: Math.max(painted.length, pane.length) }, (_, index) =>\n pane[index] === undefined\n ? fitLine(painted[index] ?? \"\", leftWidth + 1, unicode)\n : `${pad(painted[index] ?? \"\", leftWidth)} ${pane[index]}`\n );\n}\n\nexport class TickerFrame {\n private cursorHidden: boolean;\n\n constructor(\n private readonly write: (chunk: string) => void,\n private paintedLines: readonly string[] = []\n ) {\n this.cursorHidden = paintedLines.length > 0;\n }\n\n showCursor(): void {\n if (!this.cursorHidden) return;\n this.write(TICKER_ANSI.showCursor);\n this.cursorHidden = false;\n }\n\n clear(columns?: number): void {\n if (this.paintedLines.length === 0) return;\n // Resizing can reflow each previously painted line across several rows.\n // Count those rows at the current width before moving back to the frame start.\n let rows = this.paintedLines.length;\n if (columns !== undefined && columns > 0) {\n rows = 0;\n for (const line of this.paintedLines) {\n rows += 1;\n let column = 0;\n for (const character of line.replace(ANSI_STYLE, \"\")) {\n const width = characterWidth(character);\n if (column + width > columns) {\n rows += 1;\n column = 0;\n }\n column += width;\n }\n }\n }\n this.write(`\\u001B[${rows}F${TICKER_ANSI.clearDown}`);\n this.paintedLines = [];\n }\n\n redraw(lines: readonly string[], columns?: number): void {\n if (!this.cursorHidden) {\n this.write(TICKER_ANSI.hideCursor);\n this.cursorHidden = true;\n }\n this.clear(columns);\n if (lines.length === 0) return;\n this.write(`${lines.join(\"\\n\")}\\n`);\n this.paintedLines = lines;\n }\n}\n\nexport function currentSpinner(unicode: boolean, refreshIntervalMs: number): string {\n const frames = unicode ? SPINNERS.unicode : SPINNERS.ascii;\n return frames[Math.floor(Date.now() / refreshIntervalMs) % frames.length] ?? frames[0]!;\n}\n\nexport function hasLiveAnimation(lines: readonly string[]): boolean {\n return lines.some((line) => line.includes(SPINNER_TOKEN) || line.includes(SHIMMER_TOKEN_START));\n}\n"]}
@@ -0,0 +1,41 @@
1
+ import type { MessagePort } from "node:worker_threads";
2
+ export interface LiveTickerWorkerData {
3
+ color: boolean;
4
+ columns?: number;
5
+ fd: number;
6
+ framePort: MessagePort;
7
+ refreshIntervalMs: number;
8
+ stateBuffer: SharedArrayBuffer;
9
+ unicode: boolean;
10
+ }
11
+ export type TickerWorkerMessage = {
12
+ columns?: number;
13
+ lines: string[];
14
+ logPane?: readonly string[];
15
+ type: "lines";
16
+ } | {
17
+ columns?: number;
18
+ text: string;
19
+ type: "log";
20
+ } | {
21
+ columns?: number;
22
+ lines: string[];
23
+ logPane?: readonly string[];
24
+ type: "stop";
25
+ };
26
+ /** Shared output ownership and acknowledgements, viewed from either thread. */
27
+ export declare class TickerWorkerState {
28
+ #private;
29
+ readonly buffer: SharedArrayBuffer;
30
+ constructor(buffer?: SharedArrayBuffer);
31
+ get acceptedLogs(): number;
32
+ acknowledgeLog(): void;
33
+ get inlineRequested(): boolean;
34
+ markStopped(): void;
35
+ waitForStop(timeoutMs: number): boolean;
36
+ withWorkerOutput(write: () => void): void;
37
+ /** Revoke worker writes, then acquire exclusive output ownership within the deadline. */
38
+ claimOutput(timeoutMs: number): boolean;
39
+ /** Only an exit event guarantees that an abandoned lock no longer protects a write. */
40
+ releaseOutputAfterWorkerExit(): void;
41
+ }
@@ -0,0 +1,59 @@
1
+ const STOPPED = 0;
2
+ const ACCEPTED_LOGS = 1;
3
+ const INLINE_REQUESTED = 2;
4
+ const OUTPUT_LOCK = 3;
5
+ /** Shared output ownership and acknowledgements, viewed from either thread. */
6
+ export class TickerWorkerState {
7
+ buffer;
8
+ #state;
9
+ constructor(buffer = new SharedArrayBuffer(4 * Int32Array.BYTES_PER_ELEMENT)) {
10
+ this.buffer = buffer;
11
+ this.#state = new Int32Array(buffer);
12
+ }
13
+ get acceptedLogs() {
14
+ return Atomics.load(this.#state, ACCEPTED_LOGS);
15
+ }
16
+ acknowledgeLog() {
17
+ Atomics.add(this.#state, ACCEPTED_LOGS, 1);
18
+ }
19
+ get inlineRequested() {
20
+ return Atomics.load(this.#state, INLINE_REQUESTED) === 1;
21
+ }
22
+ markStopped() {
23
+ Atomics.store(this.#state, STOPPED, 1);
24
+ Atomics.notify(this.#state, STOPPED);
25
+ }
26
+ waitForStop(timeoutMs) {
27
+ return Atomics.wait(this.#state, STOPPED, 0, timeoutMs) !== "timed-out";
28
+ }
29
+ withWorkerOutput(write) {
30
+ while (Atomics.compareExchange(this.#state, OUTPUT_LOCK, 0, 1) !== 0) {
31
+ Atomics.wait(this.#state, OUTPUT_LOCK, 1);
32
+ }
33
+ try {
34
+ if (!this.inlineRequested)
35
+ write();
36
+ }
37
+ finally {
38
+ Atomics.store(this.#state, OUTPUT_LOCK, 0);
39
+ Atomics.notify(this.#state, OUTPUT_LOCK);
40
+ }
41
+ }
42
+ /** Revoke worker writes, then acquire exclusive output ownership within the deadline. */
43
+ claimOutput(timeoutMs) {
44
+ Atomics.store(this.#state, INLINE_REQUESTED, 1);
45
+ const deadline = Date.now() + timeoutMs;
46
+ while (Atomics.compareExchange(this.#state, OUTPUT_LOCK, 0, 1) !== 0) {
47
+ const remainingMs = deadline - Date.now();
48
+ if (remainingMs <= 0)
49
+ return false;
50
+ Atomics.wait(this.#state, OUTPUT_LOCK, 1, remainingMs);
51
+ }
52
+ return true;
53
+ }
54
+ /** Only an exit event guarantees that an abandoned lock no longer protects a write. */
55
+ releaseOutputAfterWorkerExit() {
56
+ Atomics.store(this.#state, OUTPUT_LOCK, 0);
57
+ }
58
+ }
59
+ //# sourceMappingURL=live-ticker-protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live-ticker-protocol.js","sourceRoot":"","sources":["../../src/reporter/live-ticker-protocol.ts"],"names":[],"mappings":"AAiBA,MAAM,OAAO,GAAG,CAAC,CAAC;AAClB,MAAM,aAAa,GAAG,CAAC,CAAC;AACxB,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAC3B,MAAM,WAAW,GAAG,CAAC,CAAC;AAEtB,+EAA+E;AAC/E,MAAM,OAAO,iBAAiB;IAGP,MAAM;IAFlB,MAAM,CAAa;IAE5B,YAAqB,MAAM,GAAG,IAAI,iBAAiB,CAAC,CAAC,GAAG,UAAU,CAAC,iBAAiB,CAAC;sBAAhE,MAAM;QACzB,IAAI,CAAC,MAAM,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;IACvC,CAAC;IAED,IAAI,YAAY;QACd,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAClD,CAAC;IAED,cAAc;QACZ,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC;IAC7C,CAAC;IAED,IAAI,eAAe;QACjB,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAC3D,CAAC;IAED,WAAW;QACT,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;QACvC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,CAAC;IAED,WAAW,CAAC,SAAiB;QAC3B,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,SAAS,CAAC,KAAK,WAAW,CAAC;IAC1E,CAAC;IAED,gBAAgB,CAAC,KAAiB;QAChC,OAAO,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;YACrE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;QAC5C,CAAC;QACD,IAAI,CAAC;YACH,IAAI,CAAC,IAAI,CAAC,eAAe;gBAAE,KAAK,EAAE,CAAC;QACrC,CAAC;gBAAS,CAAC;YACT,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;YAC3C,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;IAED,yFAAyF;IACzF,WAAW,CAAC,SAAiB;QAC3B,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC;QAChD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;QACxC,OAAO,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;YACrE,MAAM,WAAW,GAAG,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC1C,IAAI,WAAW,IAAI,CAAC;gBAAE,OAAO,KAAK,CAAC;YACnC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,CAAC,CAAC;QACzD,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,uFAAuF;IACvF,4BAA4B;QAC1B,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;IAC7C,CAAC;CACF","sourcesContent":["import type { MessagePort } from \"node:worker_threads\";\n\nexport interface LiveTickerWorkerData {\n color: boolean;\n columns?: number;\n fd: number;\n framePort: MessagePort;\n refreshIntervalMs: number;\n stateBuffer: SharedArrayBuffer;\n unicode: boolean;\n}\n\nexport type TickerWorkerMessage =\n | { columns?: number; lines: string[]; logPane?: readonly string[]; type: \"lines\" }\n | { columns?: number; text: string; type: \"log\" }\n | { columns?: number; lines: string[]; logPane?: readonly string[]; type: \"stop\" };\n\nconst STOPPED = 0;\nconst ACCEPTED_LOGS = 1;\nconst INLINE_REQUESTED = 2;\nconst OUTPUT_LOCK = 3;\n\n/** Shared output ownership and acknowledgements, viewed from either thread. */\nexport class TickerWorkerState {\n readonly #state: Int32Array;\n\n constructor(readonly buffer = new SharedArrayBuffer(4 * Int32Array.BYTES_PER_ELEMENT)) {\n this.#state = new Int32Array(buffer);\n }\n\n get acceptedLogs(): number {\n return Atomics.load(this.#state, ACCEPTED_LOGS);\n }\n\n acknowledgeLog(): void {\n Atomics.add(this.#state, ACCEPTED_LOGS, 1);\n }\n\n get inlineRequested(): boolean {\n return Atomics.load(this.#state, INLINE_REQUESTED) === 1;\n }\n\n markStopped(): void {\n Atomics.store(this.#state, STOPPED, 1);\n Atomics.notify(this.#state, STOPPED);\n }\n\n waitForStop(timeoutMs: number): boolean {\n return Atomics.wait(this.#state, STOPPED, 0, timeoutMs) !== \"timed-out\";\n }\n\n withWorkerOutput(write: () => void): void {\n while (Atomics.compareExchange(this.#state, OUTPUT_LOCK, 0, 1) !== 0) {\n Atomics.wait(this.#state, OUTPUT_LOCK, 1);\n }\n try {\n if (!this.inlineRequested) write();\n } finally {\n Atomics.store(this.#state, OUTPUT_LOCK, 0);\n Atomics.notify(this.#state, OUTPUT_LOCK);\n }\n }\n\n /** Revoke worker writes, then acquire exclusive output ownership within the deadline. */\n claimOutput(timeoutMs: number): boolean {\n Atomics.store(this.#state, INLINE_REQUESTED, 1);\n const deadline = Date.now() + timeoutMs;\n while (Atomics.compareExchange(this.#state, OUTPUT_LOCK, 0, 1) !== 0) {\n const remainingMs = deadline - Date.now();\n if (remainingMs <= 0) return false;\n Atomics.wait(this.#state, OUTPUT_LOCK, 1, remainingMs);\n }\n return true;\n }\n\n /** Only an exit event guarantees that an abandoned lock no longer protects a write. */\n releaseOutputAfterWorkerExit(): void {\n Atomics.store(this.#state, OUTPUT_LOCK, 0);\n }\n}\n"]}
@@ -1,61 +1,48 @@
1
1
  import { writeSync } from "node:fs";
2
2
  import { parentPort, workerData } from "node:worker_threads";
3
- import { currentSpinner, paintLiveLines, SHIMMER_TOKEN_START, SPINNER_TOKEN, TickerFrame, } from "./live-ticker.js";
3
+ import { currentSpinner, paintLiveLines, hasLiveAnimation, TickerFrame, } from "./live-ticker-frame.js";
4
+ import { TickerWorkerState, } from "./live-ticker-protocol.js";
4
5
  const port = parentPort;
5
6
  if (port === null)
6
7
  throw new Error("live-ticker-worker must run in a worker thread");
7
8
  // SAFETY: createWorkerTicker owns the workerData shape.
8
9
  const data = workerData;
9
- // [0] stopped, [1] accepted logs, [2] inline owns output, [3] output lock
10
- const state = new Int32Array(data.stateBuffer);
10
+ const state = new TickerWorkerState(data.stateBuffer);
11
11
  const frame = new TickerFrame((chunk) => writeSync(data.fd, chunk));
12
12
  let columns = data.columns;
13
13
  let lines = [];
14
14
  let logPane;
15
- function withOutputLock(write) {
16
- while (Atomics.compareExchange(state, 3, 0, 1) !== 0) {
17
- Atomics.wait(state, 3, 1);
18
- }
19
- try {
20
- if (Atomics.load(state, 2) === 0)
21
- write();
22
- }
23
- finally {
24
- Atomics.store(state, 3, 0);
25
- Atomics.notify(state, 3);
26
- }
27
- }
28
15
  function paintFrame() {
29
16
  const painted = paintLiveLines(lines, currentSpinner(data.unicode, data.refreshIntervalMs), Date.now(), columns, data.color, data.unicode, logPane);
30
17
  frame.redraw(painted, columns);
31
18
  data.framePort.postMessage(painted);
32
19
  }
33
20
  function redraw() {
34
- withOutputLock(paintFrame);
21
+ state.withWorkerOutput(paintFrame);
35
22
  }
36
23
  const timer = setInterval(() => {
37
- if (Atomics.load(state, 2) === 1) {
24
+ if (state.inlineRequested) {
38
25
  clearInterval(timer);
39
26
  port.close();
40
27
  return;
41
28
  }
42
- if (lines.some((line) => line.includes(SPINNER_TOKEN) || line.includes(SHIMMER_TOKEN_START))) {
29
+ if (hasLiveAnimation(lines)) {
43
30
  redraw();
44
31
  }
45
32
  }, data.refreshIntervalMs);
46
33
  port.on("message", (message) => {
47
- if (Atomics.load(state, 2) === 1) {
34
+ if (state.inlineRequested) {
48
35
  clearInterval(timer);
49
36
  port.close();
50
37
  return;
51
38
  }
52
39
  columns = message.columns ?? columns;
53
40
  if (message.type === "log") {
54
- withOutputLock(() => {
41
+ state.withWorkerOutput(() => {
55
42
  frame.clear(columns);
56
43
  data.framePort.postMessage([]);
57
44
  writeSync(data.fd, message.text);
58
- Atomics.add(state, 1, 1);
45
+ state.acknowledgeLog();
59
46
  });
60
47
  return;
61
48
  }
@@ -67,12 +54,11 @@ port.on("message", (message) => {
67
54
  }
68
55
  clearInterval(timer);
69
56
  try {
70
- withOutputLock(() => {
57
+ state.withWorkerOutput(() => {
71
58
  paintFrame();
72
59
  frame.showCursor();
73
60
  });
74
- Atomics.store(state, 0, 1);
75
- Atomics.notify(state, 0);
61
+ state.markStopped();
76
62
  }
77
63
  finally {
78
64
  port.close();
@@ -1 +1 @@
1
- {"version":3,"file":"live-ticker-worker.js","sourceRoot":"","sources":["../../src/reporter/live-ticker-worker.ts"],"names":[],"mappings":"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","sourcesContent":["import { writeSync } from \"node:fs\";\nimport { parentPort, workerData, type MessagePort } from \"node:worker_threads\";\nimport {\n currentSpinner,\n paintLiveLines,\n SHIMMER_TOKEN_START,\n SPINNER_TOKEN,\n TickerFrame,\n} from \"./live-ticker.js\";\n\ninterface LiveTickerWorkerData {\n color: boolean;\n columns?: number;\n fd: number;\n framePort: MessagePort;\n refreshIntervalMs: number;\n stateBuffer: SharedArrayBuffer;\n unicode: boolean;\n}\n\ntype TickerWorkerMessage =\n | { columns?: number; lines: string[]; logPane?: readonly string[]; type: \"lines\" }\n | { columns?: number; text: string; type: \"log\" }\n | { columns?: number; lines: string[]; logPane?: readonly string[]; type: \"stop\" };\n\nconst port = parentPort;\nif (port === null) throw new Error(\"live-ticker-worker must run in a worker thread\");\n\n// SAFETY: createWorkerTicker owns the workerData shape.\nconst data = workerData as LiveTickerWorkerData;\n// [0] stopped, [1] accepted logs, [2] inline owns output, [3] output lock\nconst state = new Int32Array(data.stateBuffer);\nconst frame = new TickerFrame((chunk) => writeSync(data.fd, chunk));\nlet columns = data.columns;\nlet lines: string[] = [];\nlet logPane: readonly string[] | undefined;\n\nfunction withOutputLock(write: () => void): void {\n while (Atomics.compareExchange(state, 3, 0, 1) !== 0) {\n Atomics.wait(state, 3, 1);\n }\n try {\n if (Atomics.load(state, 2) === 0) write();\n } finally {\n Atomics.store(state, 3, 0);\n Atomics.notify(state, 3);\n }\n}\n\nfunction paintFrame(): void {\n const painted = paintLiveLines(\n lines,\n currentSpinner(data.unicode, data.refreshIntervalMs),\n Date.now(),\n columns,\n data.color,\n data.unicode,\n logPane\n );\n frame.redraw(painted, columns);\n data.framePort.postMessage(painted);\n}\n\nfunction redraw(): void {\n withOutputLock(paintFrame);\n}\n\nconst timer = setInterval(() => {\n if (Atomics.load(state, 2) === 1) {\n clearInterval(timer);\n port.close();\n return;\n }\n if (lines.some((line) => line.includes(SPINNER_TOKEN) || line.includes(SHIMMER_TOKEN_START))) {\n redraw();\n }\n}, data.refreshIntervalMs);\n\nport.on(\"message\", (message: TickerWorkerMessage) => {\n if (Atomics.load(state, 2) === 1) {\n clearInterval(timer);\n port.close();\n return;\n }\n columns = message.columns ?? columns;\n if (message.type === \"log\") {\n withOutputLock(() => {\n frame.clear(columns);\n data.framePort.postMessage([]);\n writeSync(data.fd, message.text);\n Atomics.add(state, 1, 1);\n });\n return;\n }\n lines = message.lines;\n logPane = message.logPane;\n if (message.type === \"lines\") {\n redraw();\n return;\n }\n clearInterval(timer);\n try {\n withOutputLock(() => {\n paintFrame();\n frame.showCursor();\n });\n Atomics.store(state, 0, 1);\n Atomics.notify(state, 0);\n } finally {\n port.close();\n }\n});\n"]}
1
+ {"version":3,"file":"live-ticker-worker.js","sourceRoot":"","sources":["../../src/reporter/live-ticker-worker.ts"],"names":[],"mappings":"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","sourcesContent":["import { writeSync } from \"node:fs\";\nimport { parentPort, workerData } from \"node:worker_threads\";\nimport {\n currentSpinner,\n paintLiveLines,\n hasLiveAnimation,\n TickerFrame,\n} from \"./live-ticker-frame.js\";\nimport {\n TickerWorkerState,\n type LiveTickerWorkerData,\n type TickerWorkerMessage,\n} from \"./live-ticker-protocol.js\";\n\nconst port = parentPort;\nif (port === null) throw new Error(\"live-ticker-worker must run in a worker thread\");\n\n// SAFETY: createWorkerTicker owns the workerData shape.\nconst data = workerData as LiveTickerWorkerData;\nconst state = new TickerWorkerState(data.stateBuffer);\nconst frame = new TickerFrame((chunk) => writeSync(data.fd, chunk));\nlet columns = data.columns;\nlet lines: string[] = [];\nlet logPane: readonly string[] | undefined;\n\nfunction paintFrame(): void {\n const painted = paintLiveLines(\n lines,\n currentSpinner(data.unicode, data.refreshIntervalMs),\n Date.now(),\n columns,\n data.color,\n data.unicode,\n logPane\n );\n frame.redraw(painted, columns);\n data.framePort.postMessage(painted);\n}\n\nfunction redraw(): void {\n state.withWorkerOutput(paintFrame);\n}\n\nconst timer = setInterval(() => {\n if (state.inlineRequested) {\n clearInterval(timer);\n port.close();\n return;\n }\n if (hasLiveAnimation(lines)) {\n redraw();\n }\n}, data.refreshIntervalMs);\n\nport.on(\"message\", (message: TickerWorkerMessage) => {\n if (state.inlineRequested) {\n clearInterval(timer);\n port.close();\n return;\n }\n columns = message.columns ?? columns;\n if (message.type === \"log\") {\n state.withWorkerOutput(() => {\n frame.clear(columns);\n data.framePort.postMessage([]);\n writeSync(data.fd, message.text);\n state.acknowledgeLog();\n });\n return;\n }\n lines = message.lines;\n logPane = message.logPane;\n if (message.type === \"lines\") {\n redraw();\n return;\n }\n clearInterval(timer);\n try {\n state.withWorkerOutput(() => {\n paintFrame();\n frame.showCursor();\n });\n state.markStopped();\n } finally {\n port.close();\n }\n});\n"]}
@@ -1,8 +1,3 @@
1
- export declare const SPINNER_TOKEN = "\u0001";
2
- export declare const SHIMMER_TOKEN_START = "\u0004";
3
- export declare function elapsedToken(startedAtMs: number): string;
4
- export declare function shimmerToken(value: string): string;
5
- export declare function paintLiveLines(lines: readonly string[], spinner: string, nowMs: number, columns?: number, color?: boolean, unicode?: boolean, logPane?: readonly string[]): string[];
6
1
  export interface LiveTicker {
7
2
  setLines(lines: readonly string[], logPane?: readonly string[]): void;
8
3
  writeLog(text: string): void;
@@ -18,14 +13,4 @@ export interface LiveTickerOptions {
18
13
  write(chunk: string): void;
19
14
  workerUrl?: URL;
20
15
  }
21
- export declare class TickerFrame {
22
- private readonly write;
23
- private paintedLines;
24
- private cursorHidden;
25
- constructor(write: (chunk: string) => void, paintedLines?: readonly string[]);
26
- showCursor(): void;
27
- clear(columns?: number): void;
28
- redraw(lines: readonly string[], columns?: number): void;
29
- }
30
- export declare function currentSpinner(unicode: boolean, refreshIntervalMs: number): string;
31
16
  export declare function createLiveTicker(options: LiveTickerOptions): LiveTicker;
@@ -1,202 +1,12 @@
1
1
  import { writeSync } from "node:fs";
2
2
  import { isatty } from "node:tty";
3
3
  import { MessageChannel, receiveMessageOnPort, Worker } from "node:worker_threads";
4
- import { formatDurationMs } from "./reporter.js";
5
- export const SPINNER_TOKEN = "\u0001";
6
- const ELAPSED_TOKEN_START = "\u0002";
7
- const ELAPSED_TOKEN_END = "\u0003";
8
- export const SHIMMER_TOKEN_START = "\u0004";
9
- const SHIMMER_TOKEN_END = "\u0005";
10
- const ANSI = {
11
- clearDown: "\u001B[J",
12
- hideCursor: "\u001B[?25l",
13
- reset: "\u001B[0m",
14
- showCursor: "\u001B[?25h",
15
- };
16
- const SHIMMER_BAND_COLUMNS = 3;
17
- const SHIMMER_BRIGHT = "\u001B[0;1;36m";
18
- const SHIMMER_DIM = "\u001B[0;2;36m";
19
- const SHIMMER_FRAME_MS = 80;
4
+ import { currentSpinner, hasLiveAnimation, paintLiveLines, TICKER_ANSI, TickerFrame, } from "./live-ticker-frame.js";
5
+ import { TickerWorkerState, } from "./live-ticker-protocol.js";
20
6
  const OUTPUT_LOCK_TIMEOUT_MS = 50;
21
- const ANSI_STYLE = /\u001B\[[0-9;]*m/g;
22
- const ANSI_STYLE_PREFIX = /^\u001B\[[0-9;]*m/;
23
- const COMBINING_MARK = /\p{Mark}/u;
24
- const EMOJI = /\p{Extended_Pictographic}/u;
25
- const SPINNERS = {
26
- ascii: ["-", "\\", "|", "/"],
27
- unicode: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
28
- };
29
- function characterWidth(character) {
30
- if (COMBINING_MARK.test(character) || character === "\u200D")
31
- return 0;
32
- if (EMOJI.test(character))
33
- return 2;
34
- const codePoint = character.codePointAt(0) ?? 0;
35
- return codePoint >= 0x1100 &&
36
- (codePoint <= 0x115f ||
37
- codePoint === 0x2329 ||
38
- codePoint === 0x232a ||
39
- (codePoint >= 0x2e80 && codePoint <= 0xa4cf) ||
40
- (codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
41
- (codePoint >= 0xf900 && codePoint <= 0xfaff) ||
42
- (codePoint >= 0xfe10 && codePoint <= 0xfe6f) ||
43
- (codePoint >= 0xff00 && codePoint <= 0xff60) ||
44
- (codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
45
- (codePoint >= 0x20000 && codePoint <= 0x3fffd))
46
- ? 2
47
- : 1;
48
- }
49
- function visibleWidth(value) {
50
- return [...value].reduce((width, character) => width + characterWidth(character), 0);
51
- }
52
- function fitLine(value, columns, unicode) {
53
- if (!columns || columns <= 1)
54
- return value;
55
- const maxWidth = columns - 1;
56
- const plain = value.replace(ANSI_STYLE, "");
57
- if (visibleWidth(plain) <= maxWidth)
58
- return value;
59
- const ellipsis = unicode ? "…" : ".".repeat(Math.min(3, maxWidth));
60
- const targetWidth = Math.max(0, maxWidth - visibleWidth(ellipsis));
61
- let width = 0;
62
- let truncated = "";
63
- let index = 0;
64
- let hasStyle = false;
65
- while (index < value.length) {
66
- const style = value.slice(index).match(ANSI_STYLE_PREFIX)?.[0];
67
- if (style) {
68
- truncated += style;
69
- index += style.length;
70
- hasStyle = true;
71
- continue;
72
- }
73
- const character = String.fromCodePoint(value.codePointAt(index) ?? 0);
74
- const nextWidth = width + characterWidth(character);
75
- if (nextWidth > targetWidth)
76
- break;
77
- truncated += character;
78
- width = nextWidth;
79
- index += character.length;
80
- }
81
- return `${truncated}${hasStyle ? ANSI.reset : ""}${ellipsis}`;
82
- }
83
- export function elapsedToken(startedAtMs) {
84
- return `${ELAPSED_TOKEN_START}${startedAtMs}${ELAPSED_TOKEN_END}`;
85
- }
86
- export function shimmerToken(value) {
87
- return `${SHIMMER_TOKEN_START}${value}${SHIMMER_TOKEN_END}`;
88
- }
89
- function paintShimmerText(text, nowMs) {
90
- const characters = [...text];
91
- if (characters.length === 0)
92
- return text;
93
- const widths = characters.map(characterWidth);
94
- const totalWidth = visibleWidth(text);
95
- const period = Math.max(1, totalWidth + SHIMMER_BAND_COLUMNS);
96
- const head = Math.floor(nowMs / SHIMMER_FRAME_MS) % period;
97
- const bandStart = head - SHIMMER_BAND_COLUMNS + 1;
98
- let column = 0;
99
- let output = ANSI.reset;
100
- let last;
101
- for (const [index, character] of characters.entries()) {
102
- const width = widths[index] ?? 0;
103
- const inBand = width === 0 ? last === "bright" : column <= head && column + width - 1 >= bandStart;
104
- const style = inBand ? "bright" : "dim";
105
- if (style !== last) {
106
- output += style === "bright" ? SHIMMER_BRIGHT : SHIMMER_DIM;
107
- last = style;
108
- }
109
- output += character;
110
- column += width;
111
- }
112
- return `${output}${ANSI.reset}`;
113
- }
114
- function replaceLiveTokens(line, spinner, nowMs, color) {
115
- return line
116
- .replaceAll(SPINNER_TOKEN, spinner)
117
- .replace(new RegExp(`${ELAPSED_TOKEN_START}(\\d+)${ELAPSED_TOKEN_END}`, "g"), (_match, start) => formatDurationMs(Math.max(0, nowMs - Number(start))))
118
- .replace(new RegExp(`${SHIMMER_TOKEN_START}(.*?)${SHIMMER_TOKEN_END}`, "g"), (_match, text) => (color ? paintShimmerText(text, nowMs) : text));
119
- }
120
- export function paintLiveLines(lines, spinner, nowMs, columns, color = false, unicode = true, logPane) {
121
- const painted = lines.map((line) => replaceLiveTokens(line, spinner, nowMs, color));
122
- if (!logPane || !columns || columns < 120) {
123
- return painted.map((line) => fitLine(line, columns, unicode));
124
- }
125
- const paneWidth = Math.min(60, Math.floor(columns * 0.4));
126
- const leftWidth = columns - paneWidth - 3;
127
- const horizontal = unicode ? "─" : "-";
128
- const vertical = unicode ? "│" : "|";
129
- const pad = (line, width) => {
130
- const fitted = fitLine(line, width + 1, unicode);
131
- return fitted + " ".repeat(Math.max(0, width - visibleWidth(fitted.replace(ANSI_STYLE, ""))));
132
- };
133
- const pane = [
134
- `${unicode ? "╭" : "+"}${horizontal} Logs ${horizontal.repeat(paneWidth - 9)}${unicode ? "╮" : "+"}`,
135
- ...logPane.map((line) => `${vertical} ${pad(line, paneWidth - 4)} ${vertical}`),
136
- `${unicode ? "╰" : "+"}${horizontal.repeat(paneWidth - 2)}${unicode ? "╯" : "+"}`,
137
- ];
138
- return Array.from({ length: Math.max(painted.length, pane.length) }, (_, index) => pane[index] === undefined
139
- ? fitLine(painted[index] ?? "", leftWidth + 1, unicode)
140
- : `${pad(painted[index] ?? "", leftWidth)} ${pane[index]}`);
141
- }
142
- export class TickerFrame {
143
- write;
144
- paintedLines;
145
- cursorHidden;
146
- constructor(write, paintedLines = []) {
147
- this.write = write;
148
- this.paintedLines = paintedLines;
149
- this.cursorHidden = paintedLines.length > 0;
150
- }
151
- showCursor() {
152
- if (!this.cursorHidden)
153
- return;
154
- this.write(ANSI.showCursor);
155
- this.cursorHidden = false;
156
- }
157
- clear(columns) {
158
- if (this.paintedLines.length === 0)
159
- return;
160
- // Resizing can reflow each previously painted line across several rows.
161
- // Count those rows at the current width before moving back to the frame start.
162
- let rows = this.paintedLines.length;
163
- if (columns !== undefined && columns > 0) {
164
- rows = 0;
165
- for (const line of this.paintedLines) {
166
- rows += 1;
167
- let column = 0;
168
- for (const character of line.replace(ANSI_STYLE, "")) {
169
- const width = characterWidth(character);
170
- if (column + width > columns) {
171
- rows += 1;
172
- column = 0;
173
- }
174
- column += width;
175
- }
176
- }
177
- }
178
- this.write(`\u001B[${rows}F${ANSI.clearDown}`);
179
- this.paintedLines = [];
180
- }
181
- redraw(lines, columns) {
182
- if (!this.cursorHidden) {
183
- this.write(ANSI.hideCursor);
184
- this.cursorHidden = true;
185
- }
186
- this.clear(columns);
187
- if (lines.length === 0)
188
- return;
189
- this.write(`${lines.join("\n")}\n`);
190
- this.paintedLines = lines;
191
- }
192
- }
193
7
  function resolveColumns(options) {
194
8
  return options.getColumns?.() ?? options.columns;
195
9
  }
196
- export function currentSpinner(unicode, refreshIntervalMs) {
197
- const frames = unicode ? SPINNERS.unicode : SPINNERS.ascii;
198
- return frames[Math.floor(Date.now() / refreshIntervalMs) % frames.length] ?? frames[0];
199
- }
200
10
  function createInlineTicker(options, adoptedPaintedLines = []) {
201
11
  let disposed = false;
202
12
  let lines = [];
@@ -209,8 +19,7 @@ function createInlineTicker(options, adoptedPaintedLines = []) {
209
19
  frame.redraw(paintLiveLines(lines, currentSpinner(options.unicode, options.refreshIntervalMs), Date.now(), columns, options.color === true, options.unicode, logPane), columns);
210
20
  };
211
21
  const timer = setInterval(() => {
212
- if (disposed ||
213
- !lines.some((line) => line.includes(SPINNER_TOKEN) || line.includes(SHIMMER_TOKEN_START))) {
22
+ if (disposed || !hasLiveAnimation(lines)) {
214
23
  return;
215
24
  }
216
25
  redraw();
@@ -258,9 +67,7 @@ function fileWorkerExecArgv(argv = process.execArgv) {
258
67
  return next;
259
68
  }
260
69
  function createWorkerTicker(options) {
261
- // [0] stopped, [1] accepted logs, [2] inline owns output, [3] output lock
262
- const stateBuffer = new SharedArrayBuffer(16);
263
- const state = new Int32Array(stateBuffer);
70
+ const state = new TickerWorkerState();
264
71
  const { port1: framePort, port2: workerFramePort } = new MessageChannel();
265
72
  let worker;
266
73
  try {
@@ -273,7 +80,7 @@ function createWorkerTicker(options) {
273
80
  fd: options.fd,
274
81
  framePort: workerFramePort,
275
82
  refreshIntervalMs: options.refreshIntervalMs,
276
- stateBuffer,
83
+ stateBuffer: state.buffer,
277
84
  unicode: options.unicode,
278
85
  },
279
86
  });
@@ -297,10 +104,10 @@ function createWorkerTicker(options) {
297
104
  const restoreCursor = () => {
298
105
  try {
299
106
  if (isatty(options.fd)) {
300
- writeSync(options.fd, ANSI.showCursor);
107
+ writeSync(options.fd, TICKER_ANSI.showCursor);
301
108
  }
302
109
  else {
303
- options.write(ANSI.showCursor);
110
+ options.write(TICKER_ANSI.showCursor);
304
111
  }
305
112
  }
306
113
  catch {
@@ -308,7 +115,7 @@ function createWorkerTicker(options) {
308
115
  }
309
116
  };
310
117
  const dropAcknowledgedLogs = () => {
311
- const accepted = Atomics.load(state, 1);
118
+ const accepted = state.acceptedLogs;
312
119
  pendingLogs.splice(0, accepted - acknowledgedLogs);
313
120
  acknowledgedLogs = accepted;
314
121
  };
@@ -319,19 +126,8 @@ function createWorkerTicker(options) {
319
126
  pendingLogs.length = 0;
320
127
  ticker.setLines([...lines], logPane);
321
128
  };
322
- const claimOutput = () => {
323
- Atomics.store(state, 2, 1);
324
- const deadline = Date.now() + OUTPUT_LOCK_TIMEOUT_MS;
325
- while (Atomics.compareExchange(state, 3, 0, 1) !== 0) {
326
- const remainingMs = deadline - Date.now();
327
- if (remainingMs <= 0)
328
- return false;
329
- Atomics.wait(state, 3, 1, remainingMs);
330
- }
331
- return true;
332
- };
333
129
  const createFallback = () => {
334
- if (!claimOutput())
130
+ if (!state.claimOutput(OUTPUT_LOCK_TIMEOUT_MS))
335
131
  return undefined;
336
132
  // dispose() blocks the event loop. Drain snapshots synchronously after taking
337
133
  // the output lock so adoption includes the worker's last completed paint.
@@ -347,8 +143,7 @@ function createWorkerTicker(options) {
347
143
  const failToInline = () => {
348
144
  if (disposed || inlineFallback)
349
145
  return;
350
- // The exit event guarantees that no worker write still owns this lock.
351
- Atomics.store(state, 3, 0);
146
+ state.releaseOutputAfterWorkerExit();
352
147
  inlineFallback = createFallback();
353
148
  };
354
149
  worker.on("error", () => void worker.terminate());
@@ -407,7 +202,7 @@ function createWorkerTicker(options) {
407
202
  logPane,
408
203
  type: "stop",
409
204
  });
410
- workerStopped = Atomics.wait(state, 0, 0, 500) !== "timed-out";
205
+ workerStopped = state.waitForStop(500);
411
206
  }
412
207
  catch {
413
208
  // The worker is unavailable; render its retained state below.