six-degrees 0.2.1 → 0.3.0

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 (470) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +10 -0
  3. package/.next/standalone/.next/build-manifest.json +3 -3
  4. package/.next/standalone/.next/prerender-manifest.json +39 -11
  5. package/.next/standalone/.next/required-server-files.json +4 -1
  6. package/.next/standalone/.next/routes-manifest.json +60 -0
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.rsc +10 -10
  18. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +10 -10
  19. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +9 -9
  20. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  21. package/.next/standalone/.next/server/app/api/admin-delete/route.js +3 -2
  22. package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/api/admin-update/route.js +3 -2
  24. package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/bridges/route.js +3 -2
  26. package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/bulk-import/route.js +3 -2
  28. package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/company-scores/legacy/route/app-paths-manifest.json +3 -0
  30. package/.next/standalone/.next/server/app/api/company-scores/legacy/route/build-manifest.json +12 -0
  31. package/.next/standalone/.next/server/app/api/company-scores/legacy/route/server-reference-manifest.json +4 -0
  32. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js +9 -0
  33. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.map +5 -0
  34. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -0
  35. package/.next/standalone/.next/server/app/api/company-scores/legacy/route_client-reference-manifest.js +3 -0
  36. package/.next/standalone/.next/server/app/api/company-scores/route.js +3 -2
  37. package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/connections/route.js +3 -2
  39. package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/data/export/route/app-paths-manifest.json +3 -0
  41. package/.next/standalone/.next/server/app/api/data/export/route/build-manifest.json +12 -0
  42. package/.next/standalone/.next/server/app/api/data/export/route/server-reference-manifest.json +4 -0
  43. package/.next/standalone/.next/server/app/api/data/export/route.js +7 -0
  44. package/.next/standalone/.next/server/app/api/data/export/route.js.map +5 -0
  45. package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -0
  46. package/.next/standalone/.next/server/app/api/data/export/route_client-reference-manifest.js +3 -0
  47. package/.next/standalone/.next/server/app/api/data/import/route/app-paths-manifest.json +3 -0
  48. package/.next/standalone/.next/server/app/api/data/import/route/build-manifest.json +12 -0
  49. package/.next/standalone/.next/server/app/api/data/import/route/server-reference-manifest.json +4 -0
  50. package/.next/standalone/.next/server/app/api/data/import/route.js +8 -0
  51. package/.next/standalone/.next/server/app/api/data/import/route.js.map +5 -0
  52. package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -0
  53. package/.next/standalone/.next/server/app/api/data/import/route_client-reference-manifest.js +3 -0
  54. package/.next/standalone/.next/server/app/api/data/restart/route/app-paths-manifest.json +3 -0
  55. package/.next/standalone/.next/server/app/api/data/restart/route/build-manifest.json +12 -0
  56. package/.next/standalone/.next/server/app/api/data/restart/route/server-reference-manifest.json +4 -0
  57. package/.next/standalone/.next/server/app/api/data/restart/route.js +8 -0
  58. package/.next/standalone/.next/server/app/api/data/restart/route.js.map +5 -0
  59. package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -0
  60. package/.next/standalone/.next/server/app/api/data/restart/route_client-reference-manifest.js +3 -0
  61. package/.next/standalone/.next/server/app/api/data/reveal/route/app-paths-manifest.json +3 -0
  62. package/.next/standalone/.next/server/app/api/data/reveal/route/build-manifest.json +12 -0
  63. package/.next/standalone/.next/server/app/api/data/reveal/route/server-reference-manifest.json +4 -0
  64. package/.next/standalone/.next/server/app/api/data/reveal/route.js +7 -0
  65. package/.next/standalone/.next/server/app/api/data/reveal/route.js.map +5 -0
  66. package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -0
  67. package/.next/standalone/.next/server/app/api/data/reveal/route_client-reference-manifest.js +3 -0
  68. package/.next/standalone/.next/server/app/api/data/route/app-paths-manifest.json +3 -0
  69. package/.next/standalone/.next/server/app/api/data/route/build-manifest.json +12 -0
  70. package/.next/standalone/.next/server/app/api/data/route/server-reference-manifest.json +4 -0
  71. package/.next/standalone/.next/server/app/api/data/route.js +7 -0
  72. package/.next/standalone/.next/server/app/api/data/route.js.map +5 -0
  73. package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -0
  74. package/.next/standalone/.next/server/app/api/data/route_client-reference-manifest.js +3 -0
  75. package/.next/standalone/.next/server/app/api/delete-cluster/route.js +3 -2
  76. package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
  77. package/.next/standalone/.next/server/app/api/ingest/route.js +2 -2
  78. package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
  79. package/.next/standalone/.next/server/app/api/network/route.js +3 -2
  80. package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
  81. package/.next/standalone/.next/server/app/api/notifications/route.js +3 -2
  82. package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  83. package/.next/standalone/.next/server/app/api/outreach/route.js +3 -2
  84. package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
  85. package/.next/standalone/.next/server/app/api/queue/route.js +3 -2
  86. package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
  87. package/.next/standalone/.next/server/app/api/scraper/route.js +3 -2
  88. package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
  89. package/.next/standalone/.next/server/app/api/settings/route/app-paths-manifest.json +3 -0
  90. package/.next/standalone/.next/server/app/api/settings/route/build-manifest.json +12 -0
  91. package/.next/standalone/.next/server/app/api/settings/route/server-reference-manifest.json +4 -0
  92. package/.next/standalone/.next/server/app/api/settings/route.js +9 -0
  93. package/.next/standalone/.next/server/app/api/settings/route.js.map +5 -0
  94. package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -0
  95. package/.next/standalone/.next/server/app/api/settings/route_client-reference-manifest.js +3 -0
  96. package/.next/standalone/.next/server/app/api/settings/sector-preview/route/app-paths-manifest.json +3 -0
  97. package/.next/standalone/.next/server/app/api/settings/sector-preview/route/build-manifest.json +12 -0
  98. package/.next/standalone/.next/server/app/api/settings/sector-preview/route/server-reference-manifest.json +4 -0
  99. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js +8 -0
  100. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.map +5 -0
  101. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -0
  102. package/.next/standalone/.next/server/app/api/settings/sector-preview/route_client-reference-manifest.js +3 -0
  103. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route/app-paths-manifest.json +3 -0
  104. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route/build-manifest.json +12 -0
  105. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route/server-reference-manifest.json +4 -0
  106. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js +8 -0
  107. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.map +5 -0
  108. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -0
  109. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route_client-reference-manifest.js +3 -0
  110. package/.next/standalone/.next/server/app/api/setup-profile/route.js +3 -2
  111. package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
  112. package/.next/standalone/.next/server/app/api/unlock/route.js +3 -2
  113. package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
  114. package/.next/standalone/.next/server/app/api/update/route.js +1 -1
  115. package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
  116. package/.next/standalone/.next/server/app/api/update-images/route.js +3 -2
  117. package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
  118. package/.next/standalone/.next/server/app/api/users/route.js +3 -2
  119. package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
  120. package/.next/standalone/.next/server/app/avatars/[file]/route.js +2 -1
  121. package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
  122. package/.next/standalone/.next/server/app/favicon.ico/route.js +2 -1
  123. package/.next/standalone/.next/server/app/favicon.ico/route.js.nft.json +1 -1
  124. package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
  125. package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
  126. package/.next/standalone/.next/server/app/import.html +1 -1
  127. package/.next/standalone/.next/server/app/import.rsc +12 -12
  128. package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +12 -12
  129. package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +1 -1
  130. package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +11 -11
  131. package/.next/standalone/.next/server/app/index.html +1 -1
  132. package/.next/standalone/.next/server/app/index.rsc +12 -12
  133. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +11 -11
  134. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +12 -12
  135. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  136. package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
  137. package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
  138. package/.next/standalone/.next/server/app/launch.html +2 -2
  139. package/.next/standalone/.next/server/app/launch.rsc +12 -12
  140. package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +12 -12
  141. package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +1 -1
  142. package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +11 -11
  143. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  144. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  145. package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
  146. package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
  147. package/.next/standalone/.next/server/app/paths.html +1 -1
  148. package/.next/standalone/.next/server/app/paths.rsc +12 -12
  149. package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +12 -12
  150. package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +1 -1
  151. package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +11 -11
  152. package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
  153. package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
  154. package/.next/standalone/.next/server/app/profile.html +1 -1
  155. package/.next/standalone/.next/server/app/profile.rsc +12 -12
  156. package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +12 -12
  157. package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +1 -1
  158. package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +11 -11
  159. package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
  160. package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
  161. package/.next/standalone/.next/server/app/queue.html +1 -1
  162. package/.next/standalone/.next/server/app/queue.rsc +12 -12
  163. package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +12 -12
  164. package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +1 -1
  165. package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +11 -11
  166. package/.next/standalone/.next/server/app/settings/page/app-paths-manifest.json +3 -0
  167. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +20 -0
  168. package/.next/standalone/.next/server/app/settings/page/next-font-manifest.json +11 -0
  169. package/.next/standalone/.next/server/app/settings/page/react-loadable-manifest.json +1 -0
  170. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -0
  171. package/.next/standalone/.next/server/app/settings/page.js +13 -0
  172. package/.next/standalone/.next/server/app/settings/page.js.map +5 -0
  173. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -0
  174. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +3 -0
  175. package/.next/standalone/.next/server/app/settings.html +1 -0
  176. package/.next/standalone/.next/server/app/settings.meta +26 -0
  177. package/.next/standalone/.next/server/app/settings.rsc +24 -0
  178. package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +24 -0
  179. package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +4 -0
  180. package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +28 -0
  181. package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
  182. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  183. package/.next/standalone/.next/server/app/setup.html +1 -1
  184. package/.next/standalone/.next/server/app/setup.rsc +12 -12
  185. package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +12 -12
  186. package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +1 -1
  187. package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +11 -11
  188. package/.next/standalone/.next/server/app-paths-manifest.json +10 -0
  189. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-rnla2._.js +3 -0
  190. package/.next/standalone/.next/server/chunks/[root-of-the-server]__00la_y0._.js +3 -0
  191. package/.next/standalone/.next/server/chunks/[root-of-the-server]__07t47cm._.js +3 -0
  192. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cj5jqc._.js +3 -0
  193. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e0fei3._.js +3 -0
  194. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0i5hdu4._.js +3 -0
  195. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljcioj._.js +3 -0
  196. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljnyho._.js +3 -0
  197. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o5-tcg._.js +3 -0
  198. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qrnous._.js +3 -0
  199. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t3v1a1._.js +3 -0
  200. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0uag-k5._.js +8 -0
  201. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0um1uey._.js +3 -0
  202. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wmexuc._.js +3 -0
  203. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xu34sz._.js +3 -0
  204. package/.next/standalone/.next/server/chunks/[root-of-the-server]__133knx1._.js +3 -0
  205. package/.next/standalone/.next/server/chunks/[root-of-the-server]__14ou7j5._.js +3 -0
  206. package/.next/standalone/.next/server/chunks/[root-of-the-server]__15-17hl._.js +3 -0
  207. package/.next/standalone/.next/server/chunks/[root-of-the-server]__16iu0hv._.js +3 -0
  208. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19b7mig._.js +3 -0
  209. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bbcwub._.js +8 -0
  210. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1eris29._.js +3 -0
  211. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1gjo_cq._.js +3 -0
  212. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +3 -0
  213. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qcm08_._.js +3 -0
  214. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rphsdk._.js +3 -0
  215. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1t3rugi._.js +3 -0
  216. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1w25q0m._.js +3 -0
  217. package/.next/standalone/.next/server/chunks/_072jl0y._.js +3 -0
  218. package/.next/standalone/.next/server/chunks/_0lw8_i0._.js +3 -0
  219. package/.next/standalone/.next/server/chunks/_1_fynia._.js +21 -3
  220. package/.next/standalone/.next/server/chunks/_1fn30_l._.js +3 -0
  221. package/.next/standalone/.next/server/chunks/_1itd1o_._.js +2 -1
  222. package/.next/standalone/.next/server/chunks/_1iwkh8t._.js +1 -1
  223. package/.next/standalone/.next/server/chunks/_1mxn_lb._.js +3 -0
  224. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_company-scores_legacy_route_actions_1ncspu_.js +3 -0
  225. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_export_route_actions_1lkimty.js +3 -0
  226. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_import_route_actions_1eji0kd.js +3 -0
  227. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_restart_route_actions_00_-u6o.js +3 -0
  228. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_reveal_route_actions_0-moqtn.js +3 -0
  229. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_route_actions_0j0w5ep.js +3 -0
  230. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_settings_route_actions_1bpm9ac.js +3 -0
  231. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_settings_sector-preview_route_actions_0w79454.js +3 -0
  232. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_settings_sector-suggestions_route_actions_1eoqugp.js +3 -0
  233. package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +155 -0
  234. package/.next/standalone/.next/server/chunks/{lib_rpc_0mc73lk.js → lib_rpc_00tx2sm.js} +1 -1
  235. package/.next/standalone/.next/server/chunks/lib_rpc_02-qa4d.js +3 -0
  236. package/.next/standalone/.next/server/chunks/lib_rpc_02ndsg2.js +3 -0
  237. package/.next/standalone/.next/server/chunks/lib_rpc_08q5vp6.js +3 -0
  238. package/.next/standalone/.next/server/chunks/lib_rpc_0_8i3ft.js +3 -0
  239. package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +5 -2
  240. package/.next/standalone/.next/server/chunks/lib_rpc_0huvw-3.js +3 -0
  241. package/.next/standalone/.next/server/chunks/lib_rpc_0ip2vdh.js +3 -0
  242. package/.next/standalone/.next/server/chunks/lib_rpc_0jzisgh.js +3 -0
  243. package/.next/standalone/.next/server/chunks/lib_rpc_0yi9xvd.js +3 -0
  244. package/.next/standalone/.next/server/chunks/lib_rpc_1jyhd0o.js +3 -0
  245. package/.next/standalone/.next/server/chunks/lib_rpc_1kn59-i.js +3 -0
  246. package/.next/standalone/.next/server/chunks/lib_rpc_1q2jkza.js +3 -0
  247. package/.next/standalone/.next/server/chunks/lib_rpc_1vpm1sk.js +3 -0
  248. package/.next/standalone/.next/server/chunks/lib_rpc_1x6p_31.js +3 -0
  249. package/.next/standalone/.next/server/chunks/node_modules_next_1zc5q0a._.js +158 -0
  250. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0mocm6d._.js → [root-of-the-server]__0an6blh._.js} +3 -3
  251. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m0255u._.js +3 -0
  252. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0bipr5s._.js → [root-of-the-server]__1z1j3ek._.js} +2 -2
  253. package/.next/standalone/.next/server/chunks/ssr/_07u8byv._.js +3 -0
  254. package/.next/standalone/.next/server/chunks/ssr/_08r_mlf._.js +3 -0
  255. package/.next/standalone/.next/server/chunks/ssr/{_0ggabjc._.js → _0942v8n._.js} +1 -1
  256. package/.next/standalone/.next/server/chunks/ssr/{_1djpk8t._.js → _0hl_t7r._.js} +2 -2
  257. package/.next/standalone/.next/server/chunks/ssr/_0tb83dw._.js +132 -0
  258. package/.next/standalone/.next/server/chunks/ssr/_1-87bs1._.js +3 -0
  259. package/.next/standalone/.next/server/chunks/ssr/_17gui6a._.js +1 -1
  260. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_settings_page_actions_11x4dq5.js +3 -0
  261. package/.next/standalone/.next/server/chunks/ssr/app_launch_page_19t0dyd.js +2 -2
  262. package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +4 -4
  263. package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
  264. package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +5 -5
  265. package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +3 -0
  266. package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +2 -2
  267. package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +1 -1
  268. package/.next/standalone/.next/server/chunks/ssr/node_modules_1f8t_rp._.js +23 -0
  269. package/.next/standalone/.next/server/chunks/ssr/node_modules_1nkgb45._.js +2 -2
  270. package/.next/standalone/.next/server/chunks/ssr/{node_modules_01xbj_9._.js → node_modules_1wax83z._.js} +3 -3
  271. package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_00nap8h._.js → node_modules_next_dist_1bunoni._.js} +2 -2
  272. package/.next/standalone/.next/server/edge/chunks/[root-of-the-server]__0pco7a6._.js +2 -2
  273. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  274. package/.next/standalone/.next/server/middleware-manifest.json +15 -7
  275. package/.next/standalone/.next/server/next-font-manifest.js +1 -1
  276. package/.next/standalone/.next/server/next-font-manifest.json +4 -0
  277. package/.next/standalone/.next/server/pages/404.html +1 -1
  278. package/.next/standalone/.next/server/pages/500.html +1 -1
  279. package/.next/standalone/.next/server/prefetch-hints.json +14 -0
  280. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  281. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  282. package/.next/standalone/.next/static/chunks/06x3jiiz96m5t.js +1 -0
  283. package/.next/standalone/.next/static/chunks/{34at5d2ifl62i.js → 0ngx9s6-zgshe.js} +5 -5
  284. package/.next/standalone/.next/static/chunks/0q-_khndfxubv.js +2 -0
  285. package/.next/standalone/.next/static/chunks/1c79veqedvubt.js +1 -0
  286. package/.next/standalone/.next/static/chunks/1chp3nx45meg5.js +44 -0
  287. package/.next/standalone/.next/static/chunks/1irqj0lf-fu3d.js +1 -0
  288. package/.next/standalone/.next/static/chunks/1uadqylrod5_v.js +1 -0
  289. package/.next/standalone/.next/static/chunks/23u0zclgwpc4d.js +1 -0
  290. package/.next/standalone/.next/static/chunks/289ktj97di6zg.js +1 -0
  291. package/.next/standalone/.next/static/chunks/2b3lmf8e6uj8m.js +16 -0
  292. package/.next/standalone/.next/static/chunks/2jz311xl1pp_8.js +1 -0
  293. package/.next/standalone/.next/static/chunks/3npq5yd7c4dmn.js +2 -0
  294. package/.next/standalone/.next/static/qqqcxvzBUizkECrT4swyK/_clientMiddlewareManifest.js +14 -0
  295. package/.next/standalone/CHANGELOG.md +298 -0
  296. package/.next/standalone/CONTRIBUTING.md +5 -2
  297. package/.next/standalone/README.md +138 -30
  298. package/.next/standalone/SECURITY.md +117 -10
  299. package/.next/standalone/app/api/company-scores/legacy/route.js +32 -0
  300. package/.next/standalone/app/api/company-scores/route.js +20 -11
  301. package/.next/standalone/app/api/data/export/route.js +49 -0
  302. package/.next/standalone/app/api/data/import/route.js +83 -0
  303. package/.next/standalone/app/api/data/restart/route.js +37 -0
  304. package/.next/standalone/app/api/data/reveal/route.js +14 -0
  305. package/.next/standalone/app/api/data/route.js +39 -0
  306. package/.next/standalone/app/api/ingest/route.js +15 -47
  307. package/.next/standalone/app/api/scraper/route.js +264 -125
  308. package/.next/standalone/app/api/settings/route.js +58 -0
  309. package/.next/standalone/app/api/settings/sector-preview/route.js +38 -0
  310. package/.next/standalone/app/api/settings/sector-suggestions/route.js +20 -0
  311. package/.next/standalone/app/api/update/route.js +188 -23
  312. package/.next/standalone/app/avatars/[file]/route.js +4 -2
  313. package/.next/standalone/app/components/CompanyScores.js +28 -7
  314. package/.next/standalone/app/components/EmptyState.js +8 -0
  315. package/.next/standalone/app/components/ForceGraph.js +39 -35
  316. package/.next/standalone/app/components/LegacyScoresCard.js +129 -0
  317. package/.next/standalone/app/components/Sidebar.js +11 -10
  318. package/.next/standalone/app/components/UpdatePanel.js +306 -119
  319. package/.next/standalone/app/components/UserProvider.js +4 -3
  320. package/.next/standalone/app/components/settings/DataSection.js +437 -0
  321. package/.next/standalone/app/components/settings/SectorSection.js +445 -0
  322. package/.next/standalone/app/components/ui.js +62 -0
  323. package/.next/standalone/app/launch/page.js +30 -20
  324. package/.next/standalone/app/page.js +13 -0
  325. package/.next/standalone/app/paths/page.js +12 -4
  326. package/.next/standalone/app/profile/page.js +41 -8
  327. package/.next/standalone/app/queue/page.js +8 -2
  328. package/.next/standalone/app/settings/page.js +96 -0
  329. package/.next/standalone/app/setup/page.js +32 -16
  330. package/.next/standalone/bin/args.mjs +44 -0
  331. package/.next/standalone/bin/six-degrees.mjs +9 -15
  332. package/.next/standalone/lib/companies.js +44 -119
  333. package/.next/standalone/lib/csv.js +8 -0
  334. package/.next/standalone/lib/data-export.js +170 -0
  335. package/.next/standalone/lib/data-folder.js +238 -0
  336. package/.next/standalone/lib/data-import.js +878 -0
  337. package/.next/standalone/lib/db-client.js +48 -3
  338. package/.next/standalone/lib/durable.js +73 -0
  339. package/.next/standalone/lib/gate.js +90 -1
  340. package/.next/standalone/lib/ingest.js +40 -0
  341. package/.next/standalone/lib/legacy-offer.js +185 -0
  342. package/.next/standalone/lib/legacy-scores.js +77 -0
  343. package/.next/standalone/lib/linkedin-limits.js +124 -1
  344. package/.next/standalone/lib/rpc.js +121 -13
  345. package/.next/standalone/lib/sample-stats.js +16 -0
  346. package/.next/standalone/lib/scan-progress.js +13 -0
  347. package/.next/standalone/lib/scan-state.js +53 -0
  348. package/.next/standalone/lib/scanner-python.js +685 -0
  349. package/.next/standalone/lib/scanner-setup.js +104 -0
  350. package/.next/standalone/lib/scoring.js +747 -138
  351. package/.next/standalone/lib/scraper-client.js +9 -5
  352. package/.next/standalone/lib/sector-directory.js +1532 -0
  353. package/.next/standalone/lib/sector-focus.js +163 -0
  354. package/.next/standalone/lib/sector-labels.js +81 -0
  355. package/.next/standalone/lib/settings-effects.js +59 -0
  356. package/.next/standalone/lib/settings.js +83 -0
  357. package/.next/standalone/lib/updater-job.js +838 -0
  358. package/.next/standalone/lib/updater.js +494 -0
  359. package/.next/standalone/middleware.js +25 -28
  360. package/.next/standalone/next.config.mjs +19 -3
  361. package/.next/standalone/package-lock.json +2 -2
  362. package/.next/standalone/package.json +1 -2
  363. package/.next/standalone/scripts/apply-update.sh +482 -0
  364. package/.next/standalone/scripts/build-app.mjs +251 -9
  365. package/.next/standalone/scripts/requirements.txt +106 -4
  366. package/.next/standalone/scripts/test-release-server.mjs +143 -0
  367. package/.next/standalone/server.js +1 -1
  368. package/.next/static/chunks/06x3jiiz96m5t.js +1 -0
  369. package/.next/static/chunks/{34at5d2ifl62i.js → 0ngx9s6-zgshe.js} +5 -5
  370. package/.next/static/chunks/0q-_khndfxubv.js +2 -0
  371. package/.next/static/chunks/1c79veqedvubt.js +1 -0
  372. package/.next/static/chunks/1chp3nx45meg5.js +44 -0
  373. package/.next/static/chunks/1irqj0lf-fu3d.js +1 -0
  374. package/.next/static/chunks/1uadqylrod5_v.js +1 -0
  375. package/.next/static/chunks/23u0zclgwpc4d.js +1 -0
  376. package/.next/static/chunks/289ktj97di6zg.js +1 -0
  377. package/.next/static/chunks/2b3lmf8e6uj8m.js +16 -0
  378. package/.next/static/chunks/2jz311xl1pp_8.js +1 -0
  379. package/.next/static/chunks/3npq5yd7c4dmn.js +2 -0
  380. package/.next/static/qqqcxvzBUizkECrT4swyK/_clientMiddlewareManifest.js +14 -0
  381. package/CHANGELOG.md +298 -0
  382. package/README.md +138 -30
  383. package/SECURITY.md +117 -10
  384. package/bin/args.mjs +44 -0
  385. package/bin/six-degrees.mjs +9 -15
  386. package/lib/companies.js +44 -119
  387. package/lib/csv.js +8 -0
  388. package/lib/data-export.js +170 -0
  389. package/lib/data-folder.js +238 -0
  390. package/lib/data-import.js +878 -0
  391. package/lib/db-client.js +48 -3
  392. package/lib/durable.js +73 -0
  393. package/lib/gate.js +90 -1
  394. package/lib/ingest.js +40 -0
  395. package/lib/legacy-offer.js +185 -0
  396. package/lib/legacy-scores.js +77 -0
  397. package/lib/linkedin-limits.js +124 -1
  398. package/lib/rpc.js +121 -13
  399. package/lib/sample-stats.js +16 -0
  400. package/lib/scan-progress.js +13 -0
  401. package/lib/scan-state.js +53 -0
  402. package/lib/scanner-python.js +685 -0
  403. package/lib/scanner-setup.js +104 -0
  404. package/lib/scoring.js +747 -138
  405. package/lib/scraper-client.js +9 -5
  406. package/lib/sector-directory.js +1532 -0
  407. package/lib/sector-focus.js +163 -0
  408. package/lib/sector-labels.js +81 -0
  409. package/lib/settings-effects.js +59 -0
  410. package/lib/settings.js +83 -0
  411. package/lib/updater-job.js +838 -0
  412. package/lib/updater.js +494 -0
  413. package/package.json +1 -2
  414. package/scripts/requirements.txt +106 -4
  415. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jrmmj-._.js +0 -150
  416. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qx548g._.js +0 -151
  417. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1an3leo._.js +0 -150
  418. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1m8zedk._.js +0 -155
  419. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1zor0ys._.js +0 -3
  420. package/.next/standalone/.next/server/chunks/_0_rp-01._.js +0 -3
  421. package/.next/standalone/.next/server/chunks/_0hzdk79._.js +0 -3
  422. package/.next/standalone/.next/server/chunks/_0ow7u5r._.js +0 -3
  423. package/.next/standalone/.next/server/chunks/_0t0zc2d._.js +0 -3
  424. package/.next/standalone/.next/server/chunks/_0wa631d._.js +0 -3
  425. package/.next/standalone/.next/server/chunks/_13ccnao._.js +0 -3
  426. package/.next/standalone/.next/server/chunks/_13es063._.js +0 -3
  427. package/.next/standalone/.next/server/chunks/_142872j._.js +0 -3
  428. package/.next/standalone/.next/server/chunks/_18wsb_a._.js +0 -8
  429. package/.next/standalone/.next/server/chunks/_1e-4h5h._.js +0 -3
  430. package/.next/standalone/.next/server/chunks/_1gvvd7n._.js +0 -158
  431. package/.next/standalone/.next/server/chunks/_1moda66._.js +0 -3
  432. package/.next/standalone/.next/server/chunks/_1p6ghzv._.js +0 -3
  433. package/.next/standalone/.next/server/chunks/_1xmmi_v._.js +0 -3
  434. package/.next/standalone/.next/server/chunks/_1z-yrlf._.js +0 -3
  435. package/.next/standalone/.next/server/chunks/ssr/_0bf3x4q._.js +0 -3
  436. package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +0 -4
  437. package/.next/standalone/.next/static/ZGTiDrZ5NHGETgGdlhssu/_clientMiddlewareManifest.js +0 -6
  438. package/.next/standalone/.next/static/chunks/0jk64vo17avsb.js +0 -1
  439. package/.next/standalone/.next/static/chunks/0s65dsrthylo6.js +0 -2
  440. package/.next/standalone/.next/static/chunks/0uyfdhi8dek85.js +0 -16
  441. package/.next/standalone/.next/static/chunks/220-ne82aus0c.js +0 -44
  442. package/.next/standalone/.next/static/chunks/229haorf0f19b.js +0 -2
  443. package/.next/standalone/.next/static/chunks/2g54uafnwuakc.js +0 -1
  444. package/.next/standalone/.next/static/chunks/2spwqs6b6dx13.js +0 -1
  445. package/.next/standalone/.next/static/chunks/38dlepuubiox0.js +0 -1
  446. package/.next/standalone/scripts/score-connections.sql +0 -80
  447. package/.next/standalone/scripts/score_new_connections.sql +0 -77
  448. package/.next/standalone/site/app.js +0 -84
  449. package/.next/standalone/site/index.html +0 -221
  450. package/.next/standalone/site/style.css +0 -155
  451. package/.next/standalone/site/video/install-mac-poster.jpg +0 -0
  452. package/.next/standalone/site/video/install-mac.mp4 +0 -0
  453. package/.next/standalone/site/video/linux-poster.jpg +0 -0
  454. package/.next/standalone/site/video/linux.mp4 +0 -0
  455. package/.next/standalone/site/video/scan-poster.jpg +0 -0
  456. package/.next/standalone/site/video/scan.mp4 +0 -0
  457. package/.next/static/ZGTiDrZ5NHGETgGdlhssu/_clientMiddlewareManifest.js +0 -6
  458. package/.next/static/chunks/0jk64vo17avsb.js +0 -1
  459. package/.next/static/chunks/0s65dsrthylo6.js +0 -2
  460. package/.next/static/chunks/0uyfdhi8dek85.js +0 -16
  461. package/.next/static/chunks/220-ne82aus0c.js +0 -44
  462. package/.next/static/chunks/229haorf0f19b.js +0 -2
  463. package/.next/static/chunks/2g54uafnwuakc.js +0 -1
  464. package/.next/static/chunks/2spwqs6b6dx13.js +0 -1
  465. package/.next/static/chunks/38dlepuubiox0.js +0 -1
  466. package/scripts/score_new_connections.sql +0 -77
  467. /package/.next/standalone/.next/static/{ZGTiDrZ5NHGETgGdlhssu → qqqcxvzBUizkECrT4swyK}/_buildManifest.js +0 -0
  468. /package/.next/standalone/.next/static/{ZGTiDrZ5NHGETgGdlhssu → qqqcxvzBUizkECrT4swyK}/_ssgManifest.js +0 -0
  469. /package/.next/static/{ZGTiDrZ5NHGETgGdlhssu → qqqcxvzBUizkECrT4swyK}/_buildManifest.js +0 -0
  470. /package/.next/static/{ZGTiDrZ5NHGETgGdlhssu → qqqcxvzBUizkECrT4swyK}/_ssgManifest.js +0 -0
@@ -0,0 +1,838 @@
1
+ // One-click updates for the Mac app: the work. The decisions are in
2
+ // lib/updater.js, and the swap after the app has quit is scripts/apply-update.sh.
3
+ //
4
+ // A click on "Install and restart" (Settings → Updates) starts one background
5
+ // job, which the page polls:
6
+ //
7
+ // checking read releases/latest again. The version is never taken from
8
+ // the page, and a pre-release is never installed.
9
+ // downloading SHA256SUMS, then the disk image for this Mac's chip
10
+ // verifying the image's SHA-256 against SHA256SUMS
11
+ // preparing mount it read-only and check the app inside: its code signature,
12
+ // bundle id, version, macOS minimum and chip, and that no link
13
+ // points outside it. Copy it beside the running app, check the
14
+ // copy the same way, and unmount.
15
+ // restarting start the helper, then end the server so the app quits
16
+ //
17
+ // Everything before "restarting" happens while the old version keeps running.
18
+ // If any of it fails, or the user cancels, the app stays exactly as it was.
19
+ //
20
+ // The system tools are called by absolute path, never through PATH, and never
21
+ // the developer-tools stubs (lipo, git) that ask to install Xcode's tools on a
22
+ // Mac that doesn't have them.
23
+
24
+ import { execFile, execFileSync, spawn } from 'node:child_process';
25
+ import { createHash, randomUUID } from 'node:crypto';
26
+ import fs from 'node:fs';
27
+ import fsp from 'node:fs/promises';
28
+ import os from 'node:os';
29
+ import path from 'node:path';
30
+ import {
31
+ APP_BUNDLE_NAME, BUNDLE_ID, CHIP_LABEL, GITHUB, UPDATE_HANDOFF_EXIT_CODE,
32
+ chipOf, digestsMatch, foreignAppProcesses, helperArgs, leftoversBeside, machoArchs, parseSha256Sums,
33
+ planFromRelease, relaunchArgs, stagingPath, versionAtLeast,
34
+ } from './updater.js';
35
+ import { runningNow } from './scan-state.js';
36
+
37
+ const TOOL = {
38
+ hdiutil: '/usr/bin/hdiutil',
39
+ codesign: '/usr/bin/codesign',
40
+ ditto: '/usr/bin/ditto',
41
+ plutil: '/usr/bin/plutil',
42
+ xattr: '/usr/bin/xattr',
43
+ du: '/usr/bin/du',
44
+ swVers: '/usr/bin/sw_vers',
45
+ sysctl: '/usr/sbin/sysctl',
46
+ ps: '/bin/ps',
47
+ };
48
+
49
+ const USER_AGENT = 'six-degrees-update';
50
+ const MB = 1024 * 1024;
51
+ const SPACE_MARGIN = 64 * MB;
52
+ const ACTIVE = new Set(['checking', 'downloading', 'verifying', 'preparing', 'restarting']);
53
+ const CANCELLABLE = new Set(['checking', 'downloading', 'verifying', 'preparing']);
54
+
55
+ /**
56
+ * A failure with a message fit to show on the Settings page as it is, and a
57
+ * code when the page does something particular about it ('stale-check': offer
58
+ * a new check rather than "Try again").
59
+ */
60
+ export class UpdateError extends Error {
61
+ constructor(message, code = null) {
62
+ super(message);
63
+ this.code = code;
64
+ }
65
+ }
66
+
67
+ const CANCELLED = 'cancelled';
68
+
69
+ /**
70
+ * Where the updater keeps things, none of them in the data folder (DESKTOP.md
71
+ * rule 3) and none in the log the app empties at every start:
72
+ * cacheDir ~/Library/Caches/Six Degrees: the previous version, zipped (one copy)
73
+ * statusFile …/last-update.json: how the last update went, for the next start
74
+ * confirmFile …/update-confirmed.json: the new version saying it has started,
75
+ * which the helper waits for before it lets go of the old one
76
+ * logFile $TMPDIR/six-degrees-update.log: the helper's own log
77
+ */
78
+ export function updatePaths({ home = os.homedir(), tmp = os.tmpdir() } = {}) {
79
+ const cacheDir = path.join(home, 'Library', 'Caches', 'Six Degrees');
80
+ return {
81
+ cacheDir,
82
+ statusFile: path.join(cacheDir, 'last-update.json'),
83
+ confirmFile: path.join(cacheDir, 'update-confirmed.json'),
84
+ logFile: path.join(tmp, 'six-degrees-update.log'),
85
+ };
86
+ }
87
+
88
+ /** The status file, or null when there is none or it can't be read. */
89
+ export function readStatusFile(file) {
90
+ try {
91
+ const value = JSON.parse(fs.readFileSync(file, 'utf8'));
92
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
93
+ } catch {
94
+ return null;
95
+ }
96
+ }
97
+
98
+ function writeStatusFile(file, value) {
99
+ fs.mkdirSync(path.dirname(file), { recursive: true });
100
+ const tmp = `${file}.${process.pid}.tmp`;
101
+ fs.writeFileSync(tmp, `${JSON.stringify(value)}\n`);
102
+ fs.renameSync(tmp, file);
103
+ }
104
+
105
+ /**
106
+ * Say, for a helper that is waiting on it, that this version has started
107
+ * (scripts/apply-update.sh keeps the previous version, whole, until it hears
108
+ * this, and puts it back if the new one closes first). Called from GET
109
+ * /api/update, which every page makes (StaleServerBanner), so the first page
110
+ * this version shows is the proof. Only while the status file says an update
111
+ * to exactly this version was handed over and hasn't finished. Returns whether
112
+ * it wrote.
113
+ */
114
+ export function confirmStarted({ statusFile, confirmFile }, version) {
115
+ const status = readStatusFile(statusFile);
116
+ if (!status || status.outcome !== 'started' || status.to !== version) return false;
117
+ if (readStatusFile(confirmFile)?.version === version) return false;
118
+ try {
119
+ writeStatusFile(confirmFile, { version, pid: process.pid, at: new Date().toISOString() });
120
+ return true;
121
+ } catch {
122
+ return false; // the helper then keeps waiting, and keeps the new version: nothing is lost
123
+ }
124
+ }
125
+
126
+ /** This Mac's chip and macOS version, asked of the system (lib/updater.js chipOf). */
127
+ export function macFacts() {
128
+ const ask = (file, args) => {
129
+ try { return execFileSync(file, args, { timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim(); }
130
+ catch { return ''; }
131
+ };
132
+ return {
133
+ chip: chipOf({ sysctlArm64: ask(TOOL.sysctl, ['-n', 'hw.optional.arm64']), processArch: process.arch }),
134
+ macosVersion: ask(TOOL.swVers, ['-productVersion']),
135
+ };
136
+ }
137
+
138
+ /** What lib/updater.js bundleRefusal needs to know about the running bundle. */
139
+ export function bundleFacts(bundle) {
140
+ if (!bundle) return { exists: false };
141
+ try {
142
+ const st = fs.statSync(bundle);
143
+ let parentWritable = true;
144
+ try { fs.accessSync(path.dirname(bundle), fs.constants.W_OK); } catch { parentWritable = false; }
145
+ return {
146
+ exists: st.isDirectory(),
147
+ parentWritable,
148
+ ownerUid: st.uid,
149
+ uid: typeof process.getuid === 'function' ? process.getuid() : undefined,
150
+ };
151
+ } catch {
152
+ return { exists: false };
153
+ }
154
+ }
155
+
156
+ // ── leftovers ────────────────────────────────────────────────────────────────
157
+ // An update cut off half-way (the server or the helper killed, the Mac shut
158
+ // down) can leave things behind that nothing will ever remove. The next check
159
+ // or install removes them: only this app's own names, only once nothing can
160
+ // still be using them, and never the previous version a failed update
161
+ // couldn't put back, which is the user's to rename.
162
+
163
+ const HOUR = 3600000;
164
+
165
+ function pidAlive(pid) {
166
+ try {
167
+ process.kill(pid, 0);
168
+ return true;
169
+ } catch (err) {
170
+ return err.code === 'EPERM'; // someone else's: alive
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Remove what interrupted updates left: beside the app, the old version moved
176
+ * aside and a new version taken out again, once their helper has ended and ten
177
+ * minutes have passed, and a staged copy after an hour; in the cache, the
178
+ * helper's half-made zips; in TMPDIR, a job's download folder after an hour
179
+ * (unmounting its image first). Returns what it removed. The caller makes sure
180
+ * no update is running in this server.
181
+ */
182
+ export function cleanLeftovers({
183
+ bundle, cacheDir, tmpRoot, statusFile, now = Date.now(), isAlive = pidAlive, asideAgeMs = 10 * 60000,
184
+ } = {}) {
185
+ const removed = [];
186
+ const age = (st) => now - Math.max(st.mtimeMs, st.ctimeMs);
187
+ const stat = (p) => { try { return fs.lstatSync(p); } catch { return null; } };
188
+ const list = (dir) => { try { return fs.readdirSync(dir); } catch { return []; } };
189
+ const remove = (p) => {
190
+ try {
191
+ fs.rmSync(p, { recursive: true, force: true });
192
+ removed.push(p);
193
+ } catch { /* next time */ }
194
+ };
195
+ const status = statusFile ? readStatusFile(statusFile) : null;
196
+ const theOnlyCopy = status?.outcome === 'failed' && typeof status.previous === 'string' ? path.resolve(status.previous) : null;
197
+
198
+ if (bundle) {
199
+ const dir = path.dirname(bundle);
200
+ for (const { name, kind, pid } of leftoversBeside(list(dir), path.basename(bundle))) {
201
+ const p = path.join(dir, name);
202
+ const st = stat(p);
203
+ if (!st || p === theOnlyCopy) continue;
204
+ if (kind === 'incoming') {
205
+ if (age(st) > HOUR) remove(p);
206
+ } else if (!isAlive(pid) && age(st) > asideAgeMs) {
207
+ remove(p);
208
+ }
209
+ }
210
+ }
211
+ if (cacheDir) {
212
+ for (const name of list(cacheDir)) {
213
+ if (!/^\.previous-[A-Za-z0-9]{6}$/.test(name) && !/\.zip\.part$/.test(name)
214
+ && !/^(last-update|update-confirmed)\.json\.\d+\.tmp$/.test(name)) continue;
215
+ const p = path.join(cacheDir, name);
216
+ const st = stat(p);
217
+ if (st && age(st) > HOUR) remove(p);
218
+ }
219
+ }
220
+ if (tmpRoot) {
221
+ for (const name of list(tmpRoot)) {
222
+ if (!/^six-degrees-update-[A-Za-z0-9]{6}$/.test(name)) continue;
223
+ const p = path.join(tmpRoot, name);
224
+ const st = stat(p);
225
+ if (!st || !st.isDirectory() || age(st) <= HOUR) continue;
226
+ const point = path.join(p, 'mnt');
227
+ if (mounted(point)) {
228
+ // A system tool on a mount point found at run time: nothing for the build to trace.
229
+ try { execFileSync(/*turbopackIgnore: true*/ TOOL.hdiutil, ['detach', point, '-force'], { stdio: 'ignore', timeout: 30000 }); } catch { /* checked below */ }
230
+ if (mounted(point)) continue; // never remove into a mounted image
231
+ }
232
+ remove(p);
233
+ }
234
+ }
235
+ return removed;
236
+ }
237
+
238
+ // ── the job ──────────────────────────────────────────────────────────────────
239
+ // One per server process, kept on globalThis so that every route bundle (and a
240
+ // reloaded module in development) sees the same one.
241
+
242
+ const STORE = Symbol.for('six-degrees.update-job');
243
+ function store() {
244
+ if (!globalThis[STORE]) globalThis[STORE] = { job: null };
245
+ return globalThis[STORE];
246
+ }
247
+
248
+ /** What the page sees of a job. */
249
+ function view(job) {
250
+ if (!job) return null;
251
+ const { id, phase, version, notes, received, total, error, code, startedAt, endedAt } = job;
252
+ return { id, phase, version, notes, received, total, error, code, startedAt, endedAt };
253
+ }
254
+
255
+ /** The current (or last) job, for update-status and the Settings page. */
256
+ export function currentUpdate() {
257
+ return view(store().job);
258
+ }
259
+
260
+ /** Is a job running (up to and including "restarting")? */
261
+ export function updateActive() {
262
+ const job = store().job;
263
+ return Boolean(job && ACTIVE.has(job.phase));
264
+ }
265
+
266
+ /**
267
+ * The version the last "Check for updates" offered, on this server. Install
268
+ * installs exactly that one: if a newer release has come out since, it stops
269
+ * and asks for a new check rather than install something nobody was shown.
270
+ * Kept here, never taken from the page.
271
+ */
272
+ export function rememberCheck(version) {
273
+ store().checked = version ? String(version) : null;
274
+ }
275
+ export function checkedVersion() {
276
+ return store().checked || null;
277
+ }
278
+
279
+ /**
280
+ * Start installing the newest release. Returns { job } at once; the work runs
281
+ * in the background. `done` settles when the job stops (tests wait for it).
282
+ * Refused with { error } while another job is running.
283
+ */
284
+ export function startUpdate(ctx) {
285
+ const s = store();
286
+ if (s.job && ACTIVE.has(s.job.phase)) {
287
+ return { error: 'An update is already in progress.', job: view(s.job) };
288
+ }
289
+ const job = {
290
+ id: randomUUID(),
291
+ phase: 'checking',
292
+ version: null,
293
+ notes: null,
294
+ received: 0,
295
+ total: null,
296
+ error: null,
297
+ code: null,
298
+ startedAt: new Date().toISOString(),
299
+ endedAt: null,
300
+ ctrl: new AbortController(),
301
+ };
302
+ s.job = job;
303
+ const done = runUpdate(job, ctx);
304
+ return { job: view(job), done };
305
+ }
306
+
307
+ /** Stop a job that hasn't reached "restarting". Nothing is left behind. */
308
+ export function cancelUpdate() {
309
+ const job = store().job;
310
+ if (job && CANCELLABLE.has(job.phase)) job.ctrl.abort(new UpdateError(CANCELLED));
311
+ return view(job);
312
+ }
313
+
314
+ /** Forget the job and the last check (tests only). */
315
+ export function _resetUpdateForTesting() {
316
+ store().job = null;
317
+ store().checked = null;
318
+ }
319
+
320
+ function run(file, args, { signal, timeoutMs = 120000 } = {}) {
321
+ return new Promise((resolve, reject) => {
322
+ execFile(file, args, { signal, timeout: timeoutMs, maxBuffer: 8 * MB }, (err, stdout, stderr) => {
323
+ if (err) {
324
+ const said = String(stderr || '').trim().split('\n').filter(Boolean).slice(-2).join(' ');
325
+ err.detail = said || err.message;
326
+ reject(err);
327
+ return;
328
+ }
329
+ resolve(String(stdout));
330
+ });
331
+ });
332
+ }
333
+
334
+ const mb = (bytes) => Math.max(1, Math.round(bytes / MB));
335
+
336
+ function ensureSpace(dir, needed, where) {
337
+ let free;
338
+ try {
339
+ const s = fs.statfsSync(dir);
340
+ free = s.bavail * s.bsize;
341
+ } catch {
342
+ return; // can't tell; the copy itself will fail plainly if the disk is full
343
+ }
344
+ if (free < needed) {
345
+ throw new UpdateError(`There isn't enough free space ${where}: the update needs about ${mb(needed)} MB, and ${mb(free)} MB is free.`);
346
+ }
347
+ }
348
+
349
+ async function fetchWithin(url, c, signal, ms, accept) {
350
+ return c.fetchImpl(url, {
351
+ headers: { Accept: accept, 'User-Agent': USER_AGENT },
352
+ signal: AbortSignal.any([signal, AbortSignal.timeout(ms)]),
353
+ cache: 'no-store',
354
+ redirect: 'follow',
355
+ });
356
+ }
357
+
358
+ /**
359
+ * Download to `file`, hashing as it arrives. It gives up when nothing has
360
+ * arrived for `idleMs`, not after a fixed total: the image is about 220 MB, and
361
+ * a slow connection that is still moving is fine.
362
+ */
363
+ async function download(url, file, { job, c, signal, expectedSize }) {
364
+ const idle = new AbortController();
365
+ const both = AbortSignal.any([signal, idle.signal]);
366
+ let timer = null;
367
+ const arm = () => {
368
+ clearTimeout(timer);
369
+ timer = setTimeout(() => idle.abort(new UpdateError(
370
+ `The download stopped for ${Math.round(c.idleMs / 1000)} seconds. Check your connection and try again.`,
371
+ )), c.idleMs);
372
+ };
373
+ arm();
374
+ const fh = await fsp.open(file, 'w');
375
+ const hash = createHash('sha256');
376
+ let received = 0;
377
+ try {
378
+ const res = await c.fetchImpl(url, {
379
+ headers: { Accept: 'application/octet-stream', 'User-Agent': USER_AGENT },
380
+ signal: both,
381
+ cache: 'no-store',
382
+ redirect: 'follow',
383
+ });
384
+ if (!res.ok || !res.body) throw new UpdateError(`GitHub answered ${res.status} to the download. Try again later.`);
385
+ const header = Number(res.headers.get('content-length')) || null;
386
+ if (expectedSize && header && header !== expectedSize) {
387
+ throw new UpdateError('The download is not the size GitHub listed for it, so it was stopped.');
388
+ }
389
+ const total = header || expectedSize || null;
390
+ job.total = total;
391
+ const limit = total || c.maxBytes;
392
+ for await (const chunk of res.body) {
393
+ arm();
394
+ received += chunk.byteLength;
395
+ if (received > limit) throw new UpdateError('The download is bigger than GitHub said it would be, so it was stopped.');
396
+ hash.update(chunk);
397
+ let offset = 0;
398
+ while (offset < chunk.byteLength) {
399
+ const { bytesWritten } = await fh.write(chunk, offset, chunk.byteLength - offset);
400
+ offset += bytesWritten;
401
+ }
402
+ job.received = received;
403
+ }
404
+ if (total && received !== total) throw new UpdateError('The download ended early. Try again.');
405
+ return hash.digest('hex');
406
+ } catch (err) {
407
+ // Say why it stopped (cancelled, or nothing arriving), not "aborted".
408
+ if (both.aborted && both.reason instanceof Error) throw both.reason;
409
+ throw err;
410
+ } finally {
411
+ clearTimeout(timer);
412
+ await fh.close();
413
+ }
414
+ }
415
+
416
+ /** Mount read-only, out of sight, at `mount.point`; notes the device for detach(). */
417
+ async function attach(image, mount, signal) {
418
+ await fsp.mkdir(mount.point, { recursive: true });
419
+ let out;
420
+ try {
421
+ out = await run(TOOL.hdiutil, ['attach', image, '-nobrowse', '-readonly', '-noautoopen', '-mountpoint', mount.point, '-plist'],
422
+ { signal, timeoutMs: 180000 });
423
+ } catch (err) {
424
+ if (signal.aborted) throw err;
425
+ throw new UpdateError(`The disk image wouldn't open (${err.detail}).`);
426
+ }
427
+ const devices = [...out.matchAll(/<key>dev-entry<\/key>\s*<string>([^<]+)<\/string>/g)].map((m) => m[1]);
428
+ mount.device = devices.find((d) => /^\/dev\/disk\d+$/.test(d)) || null;
429
+ }
430
+
431
+ function mounted(point) {
432
+ try {
433
+ return fs.statSync(point).dev !== fs.statSync(path.dirname(point)).dev;
434
+ } catch {
435
+ return false;
436
+ }
437
+ }
438
+
439
+ /**
440
+ * Unmount, patiently. Right after use, Spotlight or Finder can hold a volume
441
+ * for a moment and hdiutil says "Resource busy"; and once the volume is gone,
442
+ * only the disk device may be left to eject (TRAPS §28, build-app.mjs detach).
443
+ * Safe to call when nothing was mounted: an attach that was cancelled half-way
444
+ * may or may not have mounted the image, so this looks rather than assumes.
445
+ */
446
+ async function detach({ point, device }, sleep) {
447
+ const tries = [[], [], ['-force'], ['-force'], ['-force']];
448
+ let last = null;
449
+ for (let i = 0; i < tries.length; i++) {
450
+ const volume = mounted(point);
451
+ if (!volume && (!device || !fs.existsSync(device))) return;
452
+ try {
453
+ await run(TOOL.hdiutil, ['detach', volume ? point : device, ...tries[i]], { timeoutMs: 60000 });
454
+ return;
455
+ } catch (err) {
456
+ last = err;
457
+ await sleep(1000 * (i + 1));
458
+ }
459
+ }
460
+ if (!mounted(point) && (!device || !fs.existsSync(device))) return;
461
+ throw last;
462
+ }
463
+
464
+ /** The first link inside `root` that points outside it, or null (TRAPS §37). */
465
+ async function linkOutside(root) {
466
+ const entries = await fsp.readdir(root, { recursive: true, withFileTypes: true });
467
+ for (const e of entries) {
468
+ if (!e.isSymbolicLink()) continue;
469
+ const at = path.join(e.parentPath ?? e.path, e.name);
470
+ const to = await fsp.readlink(at);
471
+ const resolved = path.resolve(path.dirname(at), to);
472
+ if (path.isAbsolute(to) || (resolved !== root && !resolved.startsWith(root + path.sep))) {
473
+ return `${path.relative(root, at)} → ${to}`;
474
+ }
475
+ }
476
+ return null;
477
+ }
478
+
479
+ async function readHead(file, bytes = 4096) {
480
+ const fh = await fsp.open(file, 'r');
481
+ try {
482
+ const buf = Buffer.alloc(bytes);
483
+ const { bytesRead } = await fh.read(buf, 0, bytes, 0);
484
+ return buf.subarray(0, bytesRead);
485
+ } finally {
486
+ await fh.close();
487
+ }
488
+ }
489
+
490
+ /**
491
+ * Is this the app it should be? Its signature verifies strictly (what CI
492
+ * checks, release.yml), it is Six Degrees at exactly the release's version, it
493
+ * runs on this macOS and this chip, and every link stays inside it.
494
+ */
495
+ async function checkApp(app, { plan, c, signal, where }) {
496
+ const st = await fsp.lstat(app).catch(() => null);
497
+ if (!st || !st.isDirectory()) throw new UpdateError(`The disk image doesn't contain ${APP_BUNDLE_NAME}.`);
498
+ try {
499
+ await run(TOOL.codesign, ['--verify', '--deep', '--strict', app], { signal, timeoutMs: 300000 });
500
+ } catch (err) {
501
+ if (signal.aborted) throw err;
502
+ throw new UpdateError(`The new version's signature doesn't check out ${where} (${err.detail}), so it wasn't used.`);
503
+ }
504
+ const plist = path.join(app, 'Contents', 'Info.plist');
505
+ const read = (key) => run(TOOL.plutil, ['-extract', key, 'raw', '-o', '-', plist], { signal, timeoutMs: 15000 })
506
+ .then((s) => s.trim()).catch(() => '');
507
+ const [id, version, minimum, executable] = await Promise.all([
508
+ read('CFBundleIdentifier'), read('CFBundleShortVersionString'), read('LSMinimumSystemVersion'), read('CFBundleExecutable'),
509
+ ]);
510
+ if (id !== c.bundleId) {
511
+ throw new UpdateError(`The download isn't Six Degrees (it calls itself ${id || 'nothing'}), so it wasn't used.`);
512
+ }
513
+ if (version !== plan.version) {
514
+ throw new UpdateError(`The download says it is version ${version || '(none)'}, not ${plan.version}, so it wasn't used.`);
515
+ }
516
+ if (minimum && c.macosVersion && !versionAtLeast(c.macosVersion, minimum)) {
517
+ throw new UpdateError(`Version ${plan.version} needs macOS ${minimum} or later, and this Mac has ${c.macosVersion}.`);
518
+ }
519
+ // The chip, from the executable itself. The classic app's executable is a
520
+ // bash script, so for it the bundled Node binary is the one that tells.
521
+ const candidates = [
522
+ executable && !executable.includes('/') ? path.join(app, 'Contents', 'MacOS', executable) : null,
523
+ path.join(app, 'Contents', 'Resources', 'node'),
524
+ ].filter(Boolean);
525
+ for (const file of candidates) {
526
+ const archs = machoArchs(await readHead(file).catch(() => null));
527
+ if (!archs.size) continue;
528
+ if (!archs.has(c.chip)) {
529
+ throw new UpdateError(`The download is built for ${[...archs].map((a) => CHIP_LABEL[a] || a).join(' and ')} Macs, not this one, so it wasn't used.`);
530
+ }
531
+ break;
532
+ }
533
+ const escaping = await linkOutside(app);
534
+ if (escaping) throw new UpdateError(`The new version has a link that points outside it (${escaping}), so it wasn't used.`);
535
+ }
536
+
537
+ /**
538
+ * The helper's whole environment: nothing of this process's (test switches
539
+ * included) but the system's own tools, the user, and a UTF-8 locale. Without
540
+ * the locale, ps and lsof write a path that isn't plain ASCII as escapes, and
541
+ * the helper can't find the app's processes in, say, "Programmes Été" (TRAPS
542
+ * §40). tests/apply-update.test.mjs runs the helper with exactly this.
543
+ */
544
+ export function helperEnv() {
545
+ return {
546
+ PATH: '/usr/bin:/bin:/usr/sbin:/sbin',
547
+ HOME: os.homedir(),
548
+ TMPDIR: os.tmpdir(),
549
+ LANG: 'en_US.UTF-8',
550
+ LC_ALL: 'en_US.UTF-8',
551
+ ...(process.env.USER ? { USER: process.env.USER, LOGNAME: process.env.USER } : {}),
552
+ };
553
+ }
554
+
555
+ /**
556
+ * Other users' processes running the app at `bundle` (lib/updater.js
557
+ * foreignAppProcesses). An app someone else has open can't be stopped by this
558
+ * user, so it is never replaced from under them.
559
+ */
560
+ export function othersRunningApp(bundle) {
561
+ if (!bundle || typeof process.getuid !== 'function') return [];
562
+ let out = '';
563
+ try {
564
+ out = execFileSync(TOOL.ps, ['-axo', 'uid=,pid=,comm='], {
565
+ timeout: 10000, maxBuffer: 16 * MB, stdio: ['ignore', 'pipe', 'ignore'], env: helperEnv(),
566
+ }).toString();
567
+ } catch {
568
+ return [];
569
+ }
570
+ let real = bundle;
571
+ try { real = fs.realpathSync(bundle); } catch { /* the path as given */ }
572
+ return foreignAppProcesses(out, { bundles: [bundle, real], uid: process.getuid() });
573
+ }
574
+
575
+ /**
576
+ * Start the helper on its own, outside the app: its own session (so it
577
+ * outlives the app), its working folder in TMPDIR (so it is never inside the
578
+ * bundle it replaces), a clean environment (helperEnv), and its output in the
579
+ * update log. Resolves with the running child once it has started.
580
+ */
581
+ export function launchHelper({ helper, args, cwd, logFile }) {
582
+ const env = helperEnv();
583
+ const out = fs.openSync(logFile, 'w');
584
+ try {
585
+ const child = spawn('/bin/bash', [helper, ...args], { cwd, env, detached: true, stdio: ['ignore', out, out] });
586
+ return new Promise((resolve, reject) => {
587
+ child.once('error', reject);
588
+ child.once('spawn', () => { child.unref(); resolve(child); });
589
+ });
590
+ } finally {
591
+ fs.closeSync(out);
592
+ }
593
+ }
594
+
595
+ /**
596
+ * End this server with the code the Mac app reads as "quit quietly"
597
+ * (desktop/lib.mjs serverExitAction). Not a SIGTERM to itself: Next catches
598
+ * that and exits with 143, which says "stopped from outside" (TRAPS §39).
599
+ * The pause lets the page's next poll see "restarting". The helper is already
600
+ * running, and waits for the app to be gone before it touches anything.
601
+ */
602
+ function exitForUpdate() {
603
+ setTimeout(() => process.exit(UPDATE_HANDOFF_EXIT_CODE), 1200);
604
+ }
605
+
606
+ // Next wraps the global fetch to cache what pages fetch. This download is no
607
+ // page's business, outlives the click that started it, and is about 220 MB, so use
608
+ // the fetch underneath when Next says which one that is.
609
+ const plainFetch = (...args) => (globalThis.fetch?._nextOriginalFetch || globalThis.fetch)(...args);
610
+
611
+ const DEFAULTS = {
612
+ bundleId: BUNDLE_ID,
613
+ // GitHub, unless the route passes lib/updater.js releaseSource()'s loopback
614
+ // test address. Tests point both at a server on 127.0.0.1 the same way.
615
+ apiBase: GITHUB.apiBase,
616
+ downloadBase: GITHUB.downloadBase,
617
+ idleMs: 30000,
618
+ maxBytes: 1024 * MB,
619
+ waitSeconds: 30,
620
+ // How long the helper waits for the new version to say it has started before
621
+ // it lets go of the old one anyway (it only waits that long if the new
622
+ // version keeps running without a page loading).
623
+ confirmWaitSeconds: 600,
624
+ // How long the helper must stay up before the app quits for it. It spends
625
+ // its first seconds waiting for the app to go, so one that has already ended
626
+ // was given something it refused, and the app stays open to say so.
627
+ settleMs: 400,
628
+ // What the scanner's route is running (lib/scan-state.js scannerJob), or null.
629
+ scanRunning: () => null,
630
+ othersRunning: () => [],
631
+ launch: launchHelper,
632
+ exit: exitForUpdate,
633
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
634
+ };
635
+
636
+ // Another user of this Mac has the app open: this user can't stop their copy,
637
+ // and swapping the app from under it would break it. Asked before the download
638
+ // and again just before the hand-over.
639
+ function othersCheck(c) {
640
+ if (c.othersRunning().length) {
641
+ throw new UpdateError('Another user of this Mac has Six Degrees open, so it can\'t be replaced now. Try again once they have quit it.');
642
+ }
643
+ }
644
+
645
+ function plain(err) {
646
+ if (err instanceof UpdateError) return err.message;
647
+ if (err?.name === 'TimeoutError') return "GitHub didn't answer in time. Try again in a moment.";
648
+ if (err instanceof TypeError && /fetch failed/i.test(err.message)) {
649
+ return `Could not reach GitHub (${err.cause?.code || err.cause?.message || 'no connection'}). Check your connection and try again.`;
650
+ }
651
+ return `The update stopped: ${err?.detail || err?.message || err}.`;
652
+ }
653
+
654
+ /** The last few lines the helper wrote, to say why it stopped. */
655
+ function logTail(file) {
656
+ try {
657
+ return fs.readFileSync(file, 'utf8').trim().split('\n').slice(-2).join(' ').replace(/^\S+ \S+ {2}/, '');
658
+ } catch {
659
+ return '';
660
+ }
661
+ }
662
+
663
+ /**
664
+ * The job itself. `c` (the context) comes from the route, which works out
665
+ * every value on the server:
666
+ * slug, currentVersion, chip, macosVersion this copy and this Mac
667
+ * bundle the running app (lib/updater.js runningBundle)
668
+ * dataDir to reopen it on the same data
669
+ * helper scripts/apply-update.sh inside the running app
670
+ * tmpRoot, cacheDir, statusFile, confirmFile, lib/updater-job.js updatePaths
671
+ * logFile
672
+ * apiBase, downloadBase lib/updater.js releaseSource
673
+ * pids the app's processes the helper waits for
674
+ * expectVersion the version the last check offered
675
+ * othersRunning other users' processes running the app
676
+ * fetchImpl, scanRunning, launch, exit, … see DEFAULTS
677
+ */
678
+ async function runUpdate(job, ctx) {
679
+ const c = { fetchImpl: plainFetch, ...DEFAULTS, ...ctx };
680
+ const { signal } = job.ctrl;
681
+ const set = (patch) => Object.assign(job, patch);
682
+ let work = null;
683
+ let mount = null;
684
+ let staged = null;
685
+ let handedOver = false;
686
+ // Quitting the app mid-update ends this process mid-job (Next exits on the
687
+ // SIGTERM the app sends it). Leave nothing behind then either: not a mounted
688
+ // image, and not a half-copied app hidden beside the real one. Synchronous,
689
+ // because nothing asynchronous runs once a process is exiting.
690
+ const onExit = () => {
691
+ if (handedOver) return;
692
+ if (mount && mounted(mount.point)) {
693
+ // A system tool on a mount point made at run time: nothing for the build to trace.
694
+ try { execFileSync(/*turbopackIgnore: true*/ TOOL.hdiutil, ['detach', mount.point, '-force'], { stdio: 'ignore', timeout: 15000 }); } catch { /* best effort */ }
695
+ }
696
+ for (const dir of [staged, work]) {
697
+ if (dir) try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* best effort */ }
698
+ }
699
+ };
700
+ process.once('exit', onExit);
701
+ try {
702
+ // ── checking ──
703
+ const res = await fetchWithin(`${c.apiBase}/repos/${c.slug}/releases/latest`, c, signal, 15000, 'application/vnd.github+json');
704
+ if (res.status === 404) throw new UpdateError('No release has been published yet.');
705
+ if (!res.ok) throw new UpdateError(`GitHub answered ${res.status}. Try again in a moment.`);
706
+ const { plan, refusal } = planFromRelease(await res.json(), {
707
+ currentVersion: c.currentVersion, chip: c.chip, slug: c.slug, downloadBase: c.downloadBase,
708
+ });
709
+ if (refusal) throw new UpdateError(refusal.message);
710
+ // Exactly the version the page offered, or nothing: a release newer than
711
+ // that is something nobody has seen yet, not what they clicked to install.
712
+ if (c.expectVersion && plan.version !== c.expectVersion) {
713
+ throw new UpdateError(
714
+ `Version ${plan.version} has come out since you checked, and you were offered ${c.expectVersion}. Check again, then install.`,
715
+ 'stale-check',
716
+ );
717
+ }
718
+ set({ version: plan.version, notes: plan.notes, total: plan.dmg.size });
719
+ othersCheck(c);
720
+
721
+ work = await fsp.mkdtemp(path.join(c.tmpRoot, 'six-degrees-update-'));
722
+ // GitHub always lists the size; the fallback is well above any release (about
723
+ // 220 MB with the scanner's Python inside), so a missing one never lets a
724
+ // download start on a disk that can't hold it.
725
+ ensureSpace(c.tmpRoot, (plan.dmg.size || 512 * MB) + SPACE_MARGIN, 'for the download');
726
+
727
+ // ── downloading ──
728
+ set({ phase: 'downloading' });
729
+ const sumsRes = await fetchWithin(plan.sums.url, c, signal, 30000, 'text/plain, application/octet-stream');
730
+ if (!sumsRes.ok) throw new UpdateError(`GitHub answered ${sumsRes.status} for the checksum file. Try again later.`);
731
+ const tooBig = () => new UpdateError('The checksum file is not what was expected, so nothing was downloaded.');
732
+ if (Number(sumsRes.headers.get('content-length')) > 64 * 1024) throw tooBig();
733
+ const sumsText = await sumsRes.text();
734
+ if (sumsText.length > 64 * 1024) throw tooBig();
735
+ const want = parseSha256Sums(sumsText).get(plan.dmg.name);
736
+ if (!want) throw new UpdateError(`The checksum file doesn't list ${plan.dmg.name}, so its download can't be checked.`);
737
+ // A file made at run time, never one of the app's: nothing for the build to trace.
738
+ const image = path.join(/*turbopackIgnore: true*/ work, plan.dmg.name);
739
+ const got = await download(plan.dmg.url, `${image}.part`, { job, c, signal, expectedSize: plan.dmg.size });
740
+
741
+ // ── verifying ──
742
+ set({ phase: 'verifying' });
743
+ if (!digestsMatch(want, got)) {
744
+ throw new UpdateError("The download doesn't match the checksum published with it, so it wasn't used. Try again later.");
745
+ }
746
+ await fsp.rename(`${image}.part`, image);
747
+
748
+ // ── preparing ──
749
+ set({ phase: 'preparing' });
750
+ // Noted before attaching, so a cancel in the middle still unmounts it.
751
+ mount = { point: path.join(work, 'mnt'), device: null };
752
+ await attach(image, mount, signal);
753
+ const inImage = path.join(mount.point, APP_BUNDLE_NAME);
754
+ await checkApp(inImage, { plan, c, signal, where: 'in the disk image' });
755
+ const kb = Number.parseInt(await run(TOOL.du, ['-sk', inImage], { signal, timeoutMs: 120000 }), 10) || 0;
756
+ ensureSpace(path.dirname(c.bundle), kb * 1024 + SPACE_MARGIN, `in ${path.dirname(c.bundle)}`);
757
+ staged = stagingPath(c.bundle);
758
+ await fsp.rm(staged, { recursive: true, force: true }); // a copy left by an update that was interrupted
759
+ try {
760
+ // ditto, never cpSync, which rewrites Electron's links into absolute paths (TRAPS §37).
761
+ await run(TOOL.ditto, [inImage, staged], { signal, timeoutMs: 900000 });
762
+ } catch (err) {
763
+ if (signal.aborted) throw err;
764
+ throw new UpdateError(`Could not copy the new version into ${path.dirname(c.bundle)} (${err.detail}).`);
765
+ }
766
+ await checkApp(staged, { plan, c, signal, where: 'after copying' });
767
+ await run(TOOL.xattr, ['-dr', 'com.apple.quarantine', staged], { signal }).catch(() => {});
768
+ await detach(mount, c.sleep);
769
+ mount = null;
770
+ await fsp.rm(image, { force: true });
771
+
772
+ // ── restarting ──
773
+ // A last look: a scan, or Install on the Scan page, may have started while
774
+ // this ran. Quitting now would stop it half-way, so leave the app alone and
775
+ // say which one it was.
776
+ if (signal.aborted) throw signal.reason;
777
+ const busy = await c.scanRunning();
778
+ if (busy) {
779
+ throw new UpdateError(`${runningNow(busy)}, so the update was not applied. Try again when it has finished.`);
780
+ }
781
+ othersCheck(c);
782
+ if (signal.aborted) throw signal.reason;
783
+ set({ phase: 'restarting' }); // from here it can't be cancelled
784
+
785
+ const helper = path.join(work, 'apply-update.sh');
786
+ await fsp.copyFile(c.helper, helper); // run a copy: the original is inside the app it replaces
787
+ const args = helperArgs({
788
+ target: c.bundle,
789
+ staged,
790
+ keepDir: c.cacheDir,
791
+ statusFile: c.statusFile,
792
+ from: c.currentVersion,
793
+ to: plan.version,
794
+ pids: c.pids || [process.ppid, process.pid],
795
+ waitSeconds: c.waitSeconds,
796
+ logFile: c.logFile,
797
+ work,
798
+ confirmFile: c.confirmFile,
799
+ confirmWaitSeconds: c.confirmWaitSeconds,
800
+ relaunch: relaunchArgs({ dataDir: c.dataDir }),
801
+ });
802
+ // Written first: the helper replaces it with the outcome, and if it never
803
+ // gets that far, the next start says so instead of saying nothing.
804
+ const started = { outcome: 'started', from: c.currentVersion, to: plan.version, at: new Date().toISOString(), log: c.logFile };
805
+ try { writeStatusFile(c.statusFile, started); } catch { /* a courtesy; the update doesn't depend on it */ }
806
+ let child;
807
+ try {
808
+ child = await c.launch({ helper, args, cwd: c.tmpRoot, logFile: c.logFile });
809
+ await c.sleep(c.settleMs);
810
+ } catch (err) {
811
+ fs.rmSync(c.statusFile, { force: true });
812
+ throw new UpdateError(`The update helper wouldn't start (${err.message}).`);
813
+ }
814
+ if (child && (child.exitCode !== null || child.signalCode !== null)) {
815
+ fs.rmSync(c.statusFile, { force: true });
816
+ const said = logTail(c.logFile);
817
+ throw new UpdateError(`The update helper stopped before the app closed${said ? ` (${said})` : ''}.`);
818
+ }
819
+ // From here the helper waits for this app to go, so the app must quit.
820
+ handedOver = true;
821
+ c.exit();
822
+ } catch (err) {
823
+ const cancelled = signal.aborted && signal.reason instanceof UpdateError && signal.reason.message === CANCELLED;
824
+ set({
825
+ phase: cancelled ? 'cancelled' : 'failed',
826
+ error: cancelled ? null : plain(err),
827
+ code: !cancelled && err instanceof UpdateError ? err.code : null,
828
+ });
829
+ } finally {
830
+ if (mount) await detach(mount, c.sleep).catch(() => {});
831
+ if (!handedOver) {
832
+ if (staged) await fsp.rm(staged, { recursive: true, force: true }).catch(() => {});
833
+ if (work) await fsp.rm(work, { recursive: true, force: true }).catch(() => {});
834
+ }
835
+ process.removeListener('exit', onExit);
836
+ set({ endedAt: new Date().toISOString() });
837
+ }
838
+ }