@usine/server 0.2.1 → 0.4.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 (231) hide show
  1. package/LICENSE +661 -21
  2. package/README.md +54 -20
  3. package/dist/backend.d.ts +26 -5
  4. package/dist/backend.d.ts.map +1 -1
  5. package/dist/backend.js +41 -16
  6. package/dist/backend.js.map +1 -1
  7. package/dist/backup.d.ts +6 -3
  8. package/dist/backup.d.ts.map +1 -1
  9. package/dist/backup.js +26 -8
  10. package/dist/backup.js.map +1 -1
  11. package/dist/cli.d.ts +77 -11
  12. package/dist/cli.d.ts.map +1 -1
  13. package/dist/cli.js +280 -31
  14. package/dist/cli.js.map +1 -1
  15. package/dist/command.d.ts +22 -0
  16. package/dist/command.d.ts.map +1 -0
  17. package/dist/command.js +51 -0
  18. package/dist/command.js.map +1 -0
  19. package/dist/detach.d.ts +83 -0
  20. package/dist/detach.d.ts.map +1 -0
  21. package/dist/detach.js +320 -0
  22. package/dist/detach.js.map +1 -0
  23. package/dist/export-run.d.ts +1 -1
  24. package/dist/export-run.d.ts.map +1 -1
  25. package/dist/export-run.js +3 -3
  26. package/dist/export-run.js.map +1 -1
  27. package/dist/failure-message.d.ts +19 -0
  28. package/dist/failure-message.d.ts.map +1 -0
  29. package/dist/failure-message.js +127 -0
  30. package/dist/failure-message.js.map +1 -0
  31. package/dist/http.d.ts +14 -5
  32. package/dist/http.d.ts.map +1 -1
  33. package/dist/http.js +86 -33
  34. package/dist/http.js.map +1 -1
  35. package/dist/index.d.ts +1 -1
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +1 -1
  38. package/dist/index.js.map +1 -1
  39. package/dist/instance/environment.d.ts +17 -0
  40. package/dist/instance/environment.d.ts.map +1 -0
  41. package/dist/instance/environment.js +51 -0
  42. package/dist/instance/environment.js.map +1 -0
  43. package/dist/instance/id.d.ts +17 -0
  44. package/dist/instance/id.d.ts.map +1 -0
  45. package/dist/instance/id.js +35 -0
  46. package/dist/instance/id.js.map +1 -0
  47. package/dist/instance/lock.d.ts +27 -0
  48. package/dist/instance/lock.d.ts.map +1 -0
  49. package/dist/instance/lock.js +127 -0
  50. package/dist/instance/lock.js.map +1 -0
  51. package/dist/instance/manifest.d.ts +52 -0
  52. package/dist/instance/manifest.d.ts.map +1 -0
  53. package/dist/instance/manifest.js +131 -0
  54. package/dist/instance/manifest.js.map +1 -0
  55. package/dist/instance/operator-api.d.ts +30 -0
  56. package/dist/instance/operator-api.d.ts.map +1 -0
  57. package/dist/instance/operator-api.js +47 -0
  58. package/dist/instance/operator-api.js.map +1 -0
  59. package/dist/instance/ports.d.ts +23 -0
  60. package/dist/instance/ports.d.ts.map +1 -0
  61. package/dist/instance/ports.js +81 -0
  62. package/dist/instance/ports.js.map +1 -0
  63. package/dist/instance/record.d.ts +82 -0
  64. package/dist/instance/record.d.ts.map +1 -0
  65. package/dist/instance/record.js +116 -0
  66. package/dist/instance/record.js.map +1 -0
  67. package/dist/instance/state.d.ts +45 -0
  68. package/dist/instance/state.d.ts.map +1 -0
  69. package/dist/instance/state.js +79 -0
  70. package/dist/instance/state.js.map +1 -0
  71. package/dist/instance/systemd.d.ts +36 -0
  72. package/dist/instance/systemd.d.ts.map +1 -0
  73. package/dist/instance/systemd.js +61 -0
  74. package/dist/instance/systemd.js.map +1 -0
  75. package/dist/logger.d.ts +3 -0
  76. package/dist/logger.d.ts.map +1 -0
  77. package/dist/logger.js +27 -0
  78. package/dist/logger.js.map +1 -0
  79. package/dist/main.js +38 -18
  80. package/dist/main.js.map +1 -1
  81. package/dist/registry.d.ts +20 -0
  82. package/dist/registry.d.ts.map +1 -0
  83. package/dist/registry.js +37 -0
  84. package/dist/registry.js.map +1 -0
  85. package/dist/reload-signal.d.ts +8 -0
  86. package/dist/reload-signal.d.ts.map +1 -0
  87. package/dist/reload-signal.js +21 -0
  88. package/dist/reload-signal.js.map +1 -0
  89. package/dist/restart.d.ts +19 -0
  90. package/dist/restart.d.ts.map +1 -0
  91. package/dist/restart.js +105 -0
  92. package/dist/restart.js.map +1 -0
  93. package/dist/shutdown.d.ts +5 -2
  94. package/dist/shutdown.d.ts.map +1 -1
  95. package/dist/shutdown.js +11 -5
  96. package/dist/shutdown.js.map +1 -1
  97. package/dist/status.d.ts +12 -0
  98. package/dist/status.d.ts.map +1 -0
  99. package/dist/status.js +99 -0
  100. package/dist/status.js.map +1 -0
  101. package/dist/stop.d.ts +81 -0
  102. package/dist/stop.d.ts.map +1 -0
  103. package/dist/stop.js +195 -0
  104. package/dist/stop.js.map +1 -0
  105. package/dist/system-sleep.d.ts +9 -0
  106. package/dist/system-sleep.d.ts.map +1 -0
  107. package/dist/system-sleep.js +42 -0
  108. package/dist/system-sleep.js.map +1 -0
  109. package/dist/tracing.d.ts +22 -0
  110. package/dist/tracing.d.ts.map +1 -0
  111. package/dist/tracing.js +61 -0
  112. package/dist/tracing.js.map +1 -0
  113. package/dist/up.d.ts +2 -2
  114. package/dist/up.d.ts.map +1 -1
  115. package/dist/up.js +62 -24
  116. package/dist/up.js.map +1 -1
  117. package/dist/upgrade.d.ts +58 -0
  118. package/dist/upgrade.d.ts.map +1 -0
  119. package/dist/upgrade.js +237 -0
  120. package/dist/upgrade.js.map +1 -0
  121. package/dist/web/assets/CompositeItem-DU6IyNMY.js +1 -0
  122. package/dist/web/assets/ToolbarRootContext-Cytu6o2e.js +1 -0
  123. package/dist/web/assets/X.es-B7iqdxa_.js +1 -0
  124. package/dist/web/assets/_agent-BVKD5odk.js +1 -0
  125. package/dist/web/assets/_loop-BFLzamuo.js +1 -0
  126. package/dist/web/assets/_name-Bh4AQ9sR.js +6 -0
  127. package/dist/web/assets/_name-CMVsmWzq.js +1 -0
  128. package/dist/web/assets/_name-DCkgEma_.js +1 -0
  129. package/dist/web/assets/_name-Ds_pWi15.js +1 -0
  130. package/dist/web/assets/_number-B9aYwvcQ.js +1 -0
  131. package/dist/web/assets/_run-BZWt47QS.js +8 -0
  132. package/dist/web/assets/_worker-Dgfufm5p.js +1 -0
  133. package/dist/web/assets/action-rCd1FeAz.js +1 -0
  134. package/dist/web/assets/agents-Drbg6qpH.js +1 -0
  135. package/dist/web/assets/agents-_6-dEjrY.js +1 -0
  136. package/dist/web/assets/button-DVWXde__.js +1 -0
  137. package/dist/web/assets/collapsible-IOhZV1qg.js +1 -0
  138. package/dist/web/assets/core-wi-Hu_JV.js +12 -0
  139. package/dist/web/assets/drawer-BiqnR_8i.js +1 -0
  140. package/dist/web/assets/dropdown-menu-C9D1kRkO.js +1 -0
  141. package/dist/web/assets/edit-and-retry-BgOR6mvP.js +1 -0
  142. package/dist/web/assets/engine-javascript-D-6se_EF.js +141 -0
  143. package/dist/web/assets/error-alert-9tKpTE3v.js +1 -0
  144. package/dist/web/assets/file-error-SjG2jLJ9.js +1 -0
  145. package/dist/web/assets/fill-cell-ClM88KFD.js +1 -0
  146. package/dist/web/assets/heading-lX1a1LNN.js +1 -0
  147. package/dist/web/assets/index-BymNZU1Q.js +10 -0
  148. package/dist/web/assets/index-Dyjfp3ET.css +2 -0
  149. package/dist/web/assets/inter-cyrillic-ext-opsz-normal-CfYZpDVp.woff2 +0 -0
  150. package/dist/web/assets/inter-cyrillic-opsz-normal-BFQXbQ-f.woff2 +0 -0
  151. package/dist/web/assets/inter-greek-ext-opsz-normal-BmPQvyvo.woff2 +0 -0
  152. package/dist/web/assets/inter-greek-opsz-normal-BM0NlUE_.woff2 +0 -0
  153. package/dist/web/assets/inter-latin-ext-opsz-normal-DpK-iCPk.woff2 +0 -0
  154. package/dist/web/assets/inter-latin-opsz-normal-BwkfbSeq.woff2 +0 -0
  155. package/dist/web/assets/inter-vietnamese-opsz-normal-Ci9T9ieq.woff2 +0 -0
  156. package/dist/web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  157. package/dist/web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  158. package/dist/web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  159. package/dist/web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  160. package/dist/web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  161. package/dist/web/assets/label-badge-aIGT1Bry.js +1 -0
  162. package/dist/web/assets/list-v8WJp2Eq.js +1 -0
  163. package/dist/web/assets/loops-BFoFf_og.js +1 -0
  164. package/dist/web/assets/mark-CC3RwMy_.js +1 -0
  165. package/dist/web/assets/markdown-BnyfnuCL.js +29 -0
  166. package/dist/web/assets/matchContext-BquevSJS.js +2 -0
  167. package/dist/web/assets/model-BerBT6R0.js +1 -0
  168. package/dist/web/assets/moves-Dg9sshX5.js +1 -0
  169. package/dist/web/assets/overview-C_tFKGZ8.js +1 -0
  170. package/dist/web/assets/page-DWnGi6wA.js +301 -0
  171. package/dist/web/assets/project-not-found-CodaBVdu.js +1 -0
  172. package/dist/web/assets/pull-request-marks-DE00nqGW.js +1 -0
  173. package/dist/web/assets/queries-B1SHA_r4.js +1 -0
  174. package/dist/web/assets/reload-Dsl0zjJ7.js +1 -0
  175. package/dist/web/assets/repositories-BdxmD192.js +1 -0
  176. package/dist/web/assets/repositories-D4wYJfa_.js +1 -0
  177. package/dist/web/assets/route-B_gphTYG.js +1 -0
  178. package/dist/web/assets/route-Cc7dKnn5.js +1 -0
  179. package/dist/web/assets/routes-BLy5TGH_.js +65 -0
  180. package/dist/web/assets/run-again-BTKagSXR.js +1 -0
  181. package/dist/web/assets/section-B3pWIIBa.js +1 -0
  182. package/dist/web/assets/section-CD-MB5r3.js +1 -0
  183. package/dist/web/assets/section-Id99HktI.js +1 -0
  184. package/dist/web/assets/settings-YrDysQXW.js +1 -0
  185. package/dist/web/assets/slots-BraHmbNr.js +1 -0
  186. package/dist/web/assets/status-badge-DnvNo1tG.js +1 -0
  187. package/dist/web/assets/strings-CW2XaR5f.js +1 -0
  188. package/dist/web/assets/table-BjyI4fVI.js +1 -0
  189. package/dist/web/assets/tasks-DCL8kLmD.js +1 -0
  190. package/dist/web/assets/transcript-sxRJzx6t.js +5 -0
  191. package/dist/web/assets/usage-CLQJdX9-.js +1 -0
  192. package/dist/web/assets/use-now-0P8TUXeg.js +1 -0
  193. package/dist/web/assets/useAnchoredPopupScrollLock-CQ83Wy-r.js +1 -0
  194. package/dist/web/assets/useNavigate-BW1WdocJ.js +1 -0
  195. package/dist/web/assets/usePositioner-rNCxnHO-.js +1 -0
  196. package/dist/web/assets/useTimeout-_TcdZXP6.js +1 -0
  197. package/dist/web/assets/with-selector-DoElmmtd.js +1 -0
  198. package/dist/web/assets/workers-BH5CBOR3.js +1 -0
  199. package/dist/web/index.html +16 -10
  200. package/package.json +17 -12
  201. package/dist/web/assets/_name-DdyMEUN6.js +0 -6
  202. package/dist/web/assets/_name-eoobiMh0.js +0 -1
  203. package/dist/web/assets/_name-pUxlKZt5.js +0 -1
  204. package/dist/web/assets/badge-t7m0Vk1K.js +0 -146
  205. package/dist/web/assets/button-BoOukj77.js +0 -1
  206. package/dist/web/assets/core-CLXRiB63.js +0 -12
  207. package/dist/web/assets/empty-DbVYU7bi.js +0 -1
  208. package/dist/web/assets/engine-javascript-h-x4hGa4.js +0 -141
  209. package/dist/web/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  210. package/dist/web/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  211. package/dist/web/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  212. package/dist/web/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  213. package/dist/web/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  214. package/dist/web/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  215. package/dist/web/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  216. package/dist/web/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  217. package/dist/web/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  218. package/dist/web/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  219. package/dist/web/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  220. package/dist/web/assets/index-CVBBoW6r.css +0 -2
  221. package/dist/web/assets/index-CsW8KcJW.js +0 -10
  222. package/dist/web/assets/loops-DghpuoiG.js +0 -1
  223. package/dist/web/assets/operator-copy-DIsVXn17.js +0 -1
  224. package/dist/web/assets/overview-DmCIVPwN.js +0 -1
  225. package/dist/web/assets/rolldown-runtime-hePW80VL.js +0 -1
  226. package/dist/web/assets/routes-BDyP7vCr.js +0 -1
  227. package/dist/web/assets/run-drawer-B-xhTXe4.js +0 -157
  228. package/dist/web/assets/status-badge-yblcElE0.js +0 -1
  229. package/dist/web/assets/table-B8cLYNvq.js +0 -1
  230. package/dist/web/assets/useCompositeItem-HmBmWxUW.js +0 -1
  231. package/dist/web/assets/with-selector-N6m4X0X_.js +0 -1
@@ -0,0 +1,35 @@
1
+ import { InstanceId } from "@usine/core/instance";
2
+ import { Effect, FileSystem, Schema } from "effect";
3
+ /** `.usine/instance-id` couldn't be read or created, or doesn't contain an instance ID. */
4
+ export class InstanceIdError extends Schema.TaggedError("@usine/server/InstanceIdError")("InstanceIdError", {
5
+ operation: Schema.Literals(["read", "create"]),
6
+ path: Schema.String,
7
+ reason: Schema.String,
8
+ }) {
9
+ }
10
+ const InstanceIdFromText = Schema.Trim.pipe(Schema.decodeTo(InstanceId));
11
+ /**
12
+ * Returns the ID in `.usine/instance-id`, or writes a new one there if the file doesn't exist.
13
+ * Call it while holding the instance lock, so that no other process writes the file.
14
+ */
15
+ export const instanceIdOf = Effect.fn("Instance.id")(function* (files) {
16
+ const fileSystem = yield* FileSystem.FileSystem;
17
+ const failure = (operation) => (error) => new InstanceIdError({ operation, path: files.id, reason: error.message });
18
+ if (yield* fileSystem.exists(files.id).pipe(Effect.mapError(failure("read")))) {
19
+ const text = yield* fileSystem.readFileString(files.id).pipe(Effect.mapError(failure("read")));
20
+ const instanceId = yield* Schema.decodeUnknownEffect(InstanceIdFromText)(text).pipe(Effect.mapError(failure("read")));
21
+ yield* Effect.annotateCurrentSpan({ instanceId });
22
+ yield* Effect.logDebug("Read the instance ID", { instanceId, path: files.id });
23
+ return instanceId;
24
+ }
25
+ const instanceId = InstanceId.make(crypto.randomUUID());
26
+ yield* Effect.annotateCurrentSpan({ instanceId });
27
+ // A rename never leaves a partial file for the next start to refuse.
28
+ const staging = `${files.id}.${instanceId}`;
29
+ yield* fileSystem
30
+ .writeFileString(staging, `${instanceId}\n`)
31
+ .pipe(Effect.andThen(fileSystem.rename(staging, files.id)), Effect.mapError(failure("create")));
32
+ yield* Effect.logInfo("Created the instance ID", { instanceId, path: files.id });
33
+ return instanceId;
34
+ });
35
+ //# sourceMappingURL=id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"id.js","sourceRoot":"","sources":["../../src/instance/id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGpD,2FAA2F;AAC3F,MAAM,OAAO,eAAgB,SAAQ,MAAM,CAAC,WAAW,CACrD,+BAA+B,CAChC,CAAC,iBAAiB,EAAE;IACnB,SAAS,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC9C,IAAI,EAAE,MAAM,CAAC,MAAM;IACnB,MAAM,EAAE,MAAM,CAAC,MAAM;CACtB,CAAC;CAAG;AAEL,MAAM,kBAAkB,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC;AAEzE;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,QAAQ,CAAC,EAAE,KAAoB;IAClF,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC;IAChD,MAAM,OAAO,GACX,CAAC,SAAuC,EAAE,EAAE,CAAC,CAAC,KAAmC,EAAE,EAAE,CACnF,IAAI,eAAe,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IAC9E,IAAI,KAAK,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9E,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAC/F,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CACjF,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CACjC,CAAC;QACF,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC;QAClD,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,sBAAsB,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;QAC/E,OAAO,UAAU,CAAC;IACpB,CAAC;IACD,MAAM,UAAU,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;IACxD,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC;IAClD,qEAAqE;IACrE,MAAM,OAAO,GAAG,GAAG,KAAK,CAAC,EAAE,IAAI,UAAU,EAAE,CAAC;IAC5C,KAAK,CAAC,CAAC,UAAU;SACd,eAAe,CAAC,OAAO,EAAE,GAAG,UAAU,IAAI,CAAC;SAC3C,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IAClG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,yBAAyB,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;IACjF,OAAO,UAAU,CAAC;AACpB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { Effect, FileSystem, Schema } from "effect";
2
+ import { type InstanceFiles, type InstanceRecord } from "./record.ts";
3
+ declare const InstanceInUseError_base: Schema.Class<InstanceInUseError, Schema.TaggedStruct<"InstanceInUseError", {
4
+ readonly directory: Schema.String;
5
+ /** The PID of the lock holder, if it could be read. */
6
+ readonly holder: Schema.Option<Schema.brand<Schema.Int, "ProcessId">>;
7
+ }>, import("effect/Cause").YieldableError>;
8
+ /** Another `usine up` has the instance lock, possibly while still draining its Attempts. */
9
+ export declare class InstanceInUseError extends InstanceInUseError_base {
10
+ }
11
+ declare const InstanceLockError_base: Schema.Class<InstanceLockError, Schema.TaggedStruct<"InstanceLockError", {
12
+ readonly directory: Schema.String;
13
+ readonly reason: Schema.String;
14
+ }>, import("effect/Cause").YieldableError>;
15
+ /** The instance lock could not be created or acquired for a reason other than another holder. */
16
+ export declare class InstanceLockError extends InstanceLockError_base {
17
+ }
18
+ /** Returns whether a process holds the instance lock, without creating the lock file. */
19
+ export declare const lockHeld: (files: InstanceFiles) => Effect.Effect<boolean, InstanceLockError, FileSystem.FileSystem>;
20
+ /**
21
+ * Acquires the instance lock for the scope, so only one `usine up` opens the stores. The lock is an
22
+ * exclusive SQLite transaction on `.usine/instance.lock`, released by the OS on exit. Writes
23
+ * `.usine/instance.pid` and `.usine/instance.json`, and removes both when the scope closes.
24
+ */
25
+ export declare const holdInstance: (directory: string & import("effect/Brand").Brand<"InstanceDirectory">, record: InstanceRecord) => Effect.Effect<void, InstanceInUseError | InstanceLockError, FileSystem.FileSystem | import("effect/Scope").Scope>;
26
+ export {};
27
+ //# sourceMappingURL=lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../../src/instance/lock.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAU,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC5D,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,cAAc,EAMpB,MAAM,aAAa,CAAC;;;IAOnB,uDAAuD;;;AALzD,4FAA4F;AAC5F,qBAAa,kBAAmB,SAAQ,uBAMtC;CAAG;;;;;AAEL,iGAAiG;AACjG,qBAAa,iBAAkB,SAAQ,sBAEoC;CAAG;AAmB9E,yFAAyF;AACzF,eAAO,MAAM,QAAQ,4FAqBnB,CAAC;AAiDH;;;;GAIG;AACH,eAAO,MAAM,YAAY,sNA2CvB,CAAC"}
@@ -0,0 +1,127 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import { Effect, FileSystem, Option, Schema } from "effect";
3
+ import { ProcessId, instanceFiles, markStoppingSync, readPid, writeRecord, } from "./record.js";
4
+ /** Another `usine up` has the instance lock, possibly while still draining its Attempts. */
5
+ export class InstanceInUseError extends Schema.TaggedError("@usine/server/InstanceInUseError")("InstanceInUseError", {
6
+ directory: Schema.String,
7
+ /** The PID of the lock holder, if it could be read. */
8
+ holder: Schema.Option(ProcessId),
9
+ }) {
10
+ }
11
+ /** The instance lock could not be created or acquired for a reason other than another holder. */
12
+ export class InstanceLockError extends Schema.TaggedError("@usine/server/InstanceLockError")("InstanceLockError", { directory: Schema.String, reason: Schema.String }) {
13
+ }
14
+ /** The node:sqlite error when another connection has the lock (`SQLITE_BUSY`). */
15
+ const SqliteBusy = Schema.Struct({ errcode: Schema.Literal(5) });
16
+ const reasonOf = (cause) => cause instanceof Error ? cause.message : String(cause);
17
+ const takeLock = (file) => {
18
+ const database = new DatabaseSync(file, { timeout: 0 });
19
+ try {
20
+ database.exec("BEGIN EXCLUSIVE");
21
+ }
22
+ catch (cause) {
23
+ database.close();
24
+ throw cause;
25
+ }
26
+ return database;
27
+ };
28
+ /** Returns whether a process holds the instance lock, without creating the lock file. */
29
+ export const lockHeld = Effect.fn("Instance.lockHeld")(function* (files) {
30
+ const fileSystem = yield* FileSystem.FileSystem;
31
+ const lockError = (cause) => new InstanceLockError({ directory: files.folder, reason: reasonOf(cause) });
32
+ if (!(yield* fileSystem.exists(files.lock).pipe(Effect.mapError(lockError))))
33
+ return false;
34
+ return yield* Effect.try({
35
+ try: () => {
36
+ // The holder's exclusive transaction blocks readers, so a read fails with SQLITE_BUSY.
37
+ const database = new DatabaseSync(files.lock, { readOnly: true, timeout: 0 });
38
+ try {
39
+ database.prepare("SELECT count(*) FROM sqlite_master").get();
40
+ return false;
41
+ }
42
+ catch (cause) {
43
+ if (Schema.is(SqliteBusy)(cause))
44
+ return true;
45
+ throw cause;
46
+ }
47
+ finally {
48
+ database.close();
49
+ }
50
+ },
51
+ catch: lockError,
52
+ });
53
+ });
54
+ const removeFile = (fileSystem, path) => fileSystem.remove(path, { force: true }).pipe(Effect.tapError((error) => Effect.logWarning("Could not remove an instance file on exit", {
55
+ path,
56
+ message: error.message,
57
+ })), Effect.ignore);
58
+ /**
59
+ * Records the `stoppingSince` of the process on its first `SIGINT` or `SIGTERM`, so that `usine stop`
60
+ * doesn't send a second signal that would end the drain.
61
+ */
62
+ const markStoppingOnSignal = (files, record) => Effect.gen(function* () {
63
+ const context = yield* Effect.context();
64
+ yield* Effect.acquireRelease(Effect.sync(() => {
65
+ let marked = false;
66
+ const onSignal = () => {
67
+ if (marked)
68
+ return;
69
+ marked = true;
70
+ try {
71
+ markStoppingSync(files, record);
72
+ }
73
+ catch (cause) {
74
+ Effect.runForkWith(context)(Effect.logWarning("Could not record that the instance is stopping", {
75
+ path: files.record,
76
+ cause,
77
+ }));
78
+ }
79
+ };
80
+ process.prependListener("SIGINT", onSignal);
81
+ process.prependListener("SIGTERM", onSignal);
82
+ return onSignal;
83
+ }), (onSignal) => Effect.sync(() => {
84
+ process.removeListener("SIGINT", onSignal);
85
+ process.removeListener("SIGTERM", onSignal);
86
+ }));
87
+ });
88
+ /**
89
+ * Acquires the instance lock for the scope, so only one `usine up` opens the stores. The lock is an
90
+ * exclusive SQLite transaction on `.usine/instance.lock`, released by the OS on exit. Writes
91
+ * `.usine/instance.pid` and `.usine/instance.json`, and removes both when the scope closes.
92
+ */
93
+ export const holdInstance = Effect.fn("Server.holdInstance")(function* (directory, record) {
94
+ const fileSystem = yield* FileSystem.FileSystem;
95
+ const files = instanceFiles(directory);
96
+ const lockError = (cause) => new InstanceLockError({ directory, reason: reasonOf(cause) });
97
+ const refuse = Effect.fn("Server.refuseInstance")(function* (cause) {
98
+ if (!Schema.is(SqliteBusy)(cause))
99
+ return yield* lockError(cause);
100
+ const holder = yield* readPid(files);
101
+ yield* Effect.logWarning("Another usine process holds the instance folder", {
102
+ instanceDirectory: directory,
103
+ holder: Option.getOrUndefined(holder),
104
+ });
105
+ return yield* new InstanceInUseError({ directory, holder });
106
+ });
107
+ yield* fileSystem
108
+ .makeDirectory(files.folder, { recursive: true })
109
+ .pipe(Effect.mapError(lockError));
110
+ yield* Effect.acquireRelease(Effect.try({
111
+ try: () => takeLock(files.lock),
112
+ catch: (cause) => cause,
113
+ }), (database) => Effect.sync(() => database.close())).pipe(Effect.catch(refuse));
114
+ yield* Effect.acquireRelease(Effect.all([
115
+ fileSystem.writeFileString(files.pid, `${record.pid}\n`),
116
+ writeRecord(files, record),
117
+ ]).pipe(Effect.mapError(lockError)), () => Effect.all([removeFile(fileSystem, files.record), removeFile(fileSystem, files.pid)], {
118
+ discard: true,
119
+ }));
120
+ yield* markStoppingOnSignal(files, record);
121
+ yield* Effect.logInfo("Holding the instance folder", {
122
+ instanceDirectory: directory,
123
+ pid: record.pid,
124
+ version: record.version,
125
+ });
126
+ });
127
+ //# sourceMappingURL=lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.js","sourceRoot":"","sources":["../../src/instance/lock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ import { Effect, FileSystem, Option, Schema } from "effect";
2
+ /** The package manager that installs the instance's dependencies. */
3
+ export declare const PackageManager: Schema.Literals<readonly ["pnpm", "npm", "yarn", "bun"]>;
4
+ export type PackageManager = typeof PackageManager.Type;
5
+ declare const EnginePin_base: Schema.Class<EnginePin, Schema.Struct<{
6
+ readonly name: Schema.NonEmptyString;
7
+ readonly spec: Schema.NonEmptyString;
8
+ }>, {}>;
9
+ /** A `@usine/*` dependency of the instance with a registry spec. */
10
+ export declare class EnginePin extends EnginePin_base {
11
+ }
12
+ declare const InstanceManifest_base: Schema.Class<InstanceManifest, Schema.Struct<{
13
+ readonly path: Schema.String;
14
+ readonly text: Schema.String;
15
+ /** The `@usine/*` dependencies with a registry spec. */
16
+ readonly pins: Schema.$Array<typeof EnginePin>;
17
+ /** The spec of `@usine/server` if it points at a local folder instead of a registry version. */
18
+ readonly linkedServer: Schema.Option<Schema.String>;
19
+ readonly packageManager: Schema.Literals<readonly ["pnpm", "npm", "yarn", "bun"]>;
20
+ /** The lockfiles that exist next to `package.json`. */
21
+ readonly lockfiles: Schema.$Array<Schema.String>;
22
+ }>, {}>;
23
+ /** The instance folder's `package.json`, as `usine upgrade` reads it. */
24
+ export declare class InstanceManifest extends InstanceManifest_base {
25
+ }
26
+ declare const InstanceManifestError_base: Schema.Class<InstanceManifestError, Schema.TaggedStruct<"InstanceManifestError", {
27
+ readonly path: Schema.String;
28
+ readonly reason: Schema.String;
29
+ }>, import("effect/Cause").YieldableError>;
30
+ /** The instance's `package.json` couldn't be read or parsed. */
31
+ export declare class InstanceManifestError extends InstanceManifestError_base {
32
+ }
33
+ export declare const SERVER_PACKAGE = "@usine/server";
34
+ /** Parses `package.json` text. Fails with the parse error as `reason`. */
35
+ export declare const parseManifest: (path: string, text: string, lockfiles: ReadonlyArray<string>) => Effect.Effect<InstanceManifest, InstanceManifestError>;
36
+ /** Reads the instance's `package.json`, or returns `None` if the folder has none. */
37
+ export declare const readManifest: (directory: string) => Effect.Effect<Option.Option<InstanceManifest>, InstanceManifestError, FileSystem.FileSystem>;
38
+ /** The pin of `@usine/server`, if the manifest pins it to a registry version. */
39
+ export declare const serverPin: (manifest: InstanceManifest) => Option.Option<EnginePin>;
40
+ /** Returns `text` with each pin's spec replaced by its version in `versions`, formatting intact. */
41
+ export declare const bumpPins: (text: string, pins: ReadonlyArray<EnginePin>, versions: ReadonlyMap<string, string>) => string;
42
+ /** Whether `script` is inside the instance's `node_modules`. Both paths must be real paths. */
43
+ export declare const installedInInstance: (directory: string, script: string) => boolean;
44
+ /** Resolves `path` through symbolic links, or returns it unchanged if it can't. */
45
+ export declare const realPathOrSelf: (path: string) => Effect.Effect<string, never, FileSystem.FileSystem>;
46
+ /**
47
+ * Compares two semantic versions: negative if `left` is older, positive if newer, zero if equal or
48
+ * if either doesn't parse.
49
+ */
50
+ export declare const compareVersions: (left: string, right: string) => number;
51
+ export {};
52
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/instance/manifest.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE5D,qEAAqE;AACrE,eAAO,MAAM,cAAc,0DAAkD,CAAC;AAC9E,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAC;;;;;AAwBxD,oEAAoE;AACpE,qBAAa,SAAU,SAAQ,cAG7B;CAAG;;;;IAQH,wDAAwD;;IAExD,gGAAgG;;;IAGhG,uDAAuD;;;AAXzD,yEAAyE;AACzE,qBAAa,gBAAiB,SAAQ,qBAYpC;CAAG;;;;;AAEL,gEAAgE;AAChE,qBAAa,qBAAsB,SAAQ,0BAE+B;CAAG;AAE7E,eAAO,MAAM,cAAc,kBAAkB,CAAC;AAS9C,0EAA0E;AAC1E,eAAO,MAAM,aAAa,SAClB,MAAM,QACN,MAAM,aACD,aAAa,CAAC,MAAM,CAAC,KAC/B,MAAM,CAAC,MAAM,CAAC,gBAAgB,EAAE,qBAAqB,CAwBrD,CAAC;AAEJ,qFAAqF;AACrF,eAAO,MAAM,YAAY,qHAgBvB,CAAC;AAEH,iFAAiF;AACjF,eAAO,MAAM,SAAS,aAAc,gBAAgB,KAAG,MAAM,CAAC,MAAM,CAAC,SAAS,CACK,CAAC;AAIpF,oGAAoG;AACpG,eAAO,MAAM,QAAQ,SACb,MAAM,QACN,aAAa,CAAC,SAAS,CAAC,YACpB,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,KACpC,MAMO,CAAC;AAEX,+FAA+F;AAC/F,eAAO,MAAM,mBAAmB,cAAe,MAAM,UAAU,MAAM,KAAG,OAGvE,CAAC;AAEF,mFAAmF;AACnF,eAAO,MAAM,cAAc,SAAU,MAAM,wDAGxC,CAAC;AAIJ;;;GAGG;AACH,eAAO,MAAM,eAAe,SAAU,MAAM,SAAS,MAAM,KAAG,MAa7D,CAAC"}
@@ -0,0 +1,131 @@
1
+ import { isAbsolute, join, relative } from "node:path";
2
+ import { Effect, FileSystem, Option, Schema } from "effect";
3
+ /** The package manager that installs the instance's dependencies. */
4
+ export const PackageManager = Schema.Literals(["pnpm", "npm", "yarn", "bun"]);
5
+ const LOCKFILES = [
6
+ ["pnpm", "pnpm-lock.yaml"],
7
+ ["npm", "package-lock.json"],
8
+ ["yarn", "yarn.lock"],
9
+ ["bun", "bun.lock"],
10
+ ["bun", "bun.lockb"],
11
+ ];
12
+ const Dependencies = Schema.Record(Schema.String, Schema.String);
13
+ const PackageJson = Schema.fromJsonString(Schema.Struct({
14
+ packageManager: Schema.optionalKey(Schema.String),
15
+ dependencies: Schema.optionalKey(Dependencies),
16
+ devDependencies: Schema.optionalKey(Dependencies),
17
+ optionalDependencies: Schema.optionalKey(Dependencies),
18
+ }));
19
+ /** A dependency spec that doesn't resolve on a registry, such as `link:../usine/apps/server`. */
20
+ const LOCAL_SPEC = /^(?:link:|file:|workspace:|portal:|git\+|git:|github:|https?:|\.|\/|~)/u;
21
+ /** A `@usine/*` dependency of the instance with a registry spec. */
22
+ export class EnginePin extends Schema.Class("@usine/server/EnginePin")({
23
+ name: Schema.NonEmptyString,
24
+ spec: Schema.NonEmptyString,
25
+ }) {
26
+ }
27
+ /** The instance folder's `package.json`, as `usine upgrade` reads it. */
28
+ export class InstanceManifest extends Schema.Class("@usine/server/InstanceManifest")({
29
+ path: Schema.String,
30
+ text: Schema.String,
31
+ /** The `@usine/*` dependencies with a registry spec. */
32
+ pins: Schema.Array(EnginePin),
33
+ /** The spec of `@usine/server` if it points at a local folder instead of a registry version. */
34
+ linkedServer: Schema.Option(Schema.String),
35
+ packageManager: PackageManager,
36
+ /** The lockfiles that exist next to `package.json`. */
37
+ lockfiles: Schema.Array(Schema.String),
38
+ }) {
39
+ }
40
+ /** The instance's `package.json` couldn't be read or parsed. */
41
+ export class InstanceManifestError extends Schema.TaggedError("@usine/server/InstanceManifestError")("InstanceManifestError", { path: Schema.String, reason: Schema.String }) {
42
+ }
43
+ export const SERVER_PACKAGE = "@usine/server";
44
+ const packageManagerOf = (field, lockfiles) => {
45
+ const named = Schema.decodeUnknownOption(PackageManager)(field?.split("@")[0]);
46
+ if (Option.isSome(named))
47
+ return named.value;
48
+ const found = LOCKFILES.find(([, file]) => lockfiles.includes(file));
49
+ return found === undefined ? "npm" : found[0];
50
+ };
51
+ /** Parses `package.json` text. Fails with the parse error as `reason`. */
52
+ export const parseManifest = (path, text, lockfiles) => Schema.decodeUnknownEffect(PackageJson)(text).pipe(Effect.mapError((error) => new InstanceManifestError({ path, reason: error.message })), Effect.map((manifest) => {
53
+ const dependencies = [
54
+ ...Object.entries(manifest.dependencies ?? {}),
55
+ ...Object.entries(manifest.devDependencies ?? {}),
56
+ ...Object.entries(manifest.optionalDependencies ?? {}),
57
+ ].filter(([name]) => name.startsWith("@usine/"));
58
+ const server = dependencies.find(([name]) => name === SERVER_PACKAGE);
59
+ return new InstanceManifest({
60
+ path,
61
+ text,
62
+ pins: dependencies
63
+ .filter(([, spec]) => spec !== "" && !LOCAL_SPEC.test(spec))
64
+ .map(([name, spec]) => new EnginePin({ name, spec })),
65
+ linkedServer: server !== undefined && LOCAL_SPEC.test(server[1])
66
+ ? Option.some(server[1])
67
+ : Option.none(),
68
+ packageManager: packageManagerOf(manifest.packageManager, lockfiles),
69
+ lockfiles,
70
+ });
71
+ }));
72
+ /** Reads the instance's `package.json`, or returns `None` if the folder has none. */
73
+ export const readManifest = Effect.fn("Instance.readManifest")(function* (directory) {
74
+ const fileSystem = yield* FileSystem.FileSystem;
75
+ const path = join(directory, "package.json");
76
+ const failure = (error) => new InstanceManifestError({ path, reason: error.message });
77
+ if (!(yield* fileSystem.exists(path).pipe(Effect.mapError(failure)))) {
78
+ return Option.none();
79
+ }
80
+ const text = yield* fileSystem.readFileString(path).pipe(Effect.mapError(failure));
81
+ const lockfiles = [];
82
+ for (const [, file] of LOCKFILES) {
83
+ if (yield* fileSystem.exists(join(directory, file)).pipe(Effect.orElseSucceed(() => false))) {
84
+ lockfiles.push(file);
85
+ }
86
+ }
87
+ return Option.some(yield* parseManifest(path, text, lockfiles));
88
+ });
89
+ /** The pin of `@usine/server`, if the manifest pins it to a registry version. */
90
+ export const serverPin = (manifest) => Option.fromUndefinedOr(manifest.pins.find(({ name }) => name === SERVER_PACKAGE));
91
+ const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
92
+ /** Returns `text` with each pin's spec replaced by its version in `versions`, formatting intact. */
93
+ export const bumpPins = (text, pins, versions) => pins.reduce((current, { name, spec }) => {
94
+ const version = versions.get(name);
95
+ if (version === undefined)
96
+ return current;
97
+ const entry = new RegExp(`("${escapeRegExp(name)}"\\s*:\\s*)"${escapeRegExp(spec)}"`, "gu");
98
+ return current.replace(entry, `$1"${version}"`);
99
+ }, text);
100
+ /** Whether `script` is inside the instance's `node_modules`. Both paths must be real paths. */
101
+ export const installedInInstance = (directory, script) => {
102
+ const path = relative(join(directory, "node_modules"), script);
103
+ return path !== "" && !path.startsWith("..") && !isAbsolute(path);
104
+ };
105
+ /** Resolves `path` through symbolic links, or returns it unchanged if it can't. */
106
+ export const realPathOrSelf = (path) => Effect.flatMap(FileSystem.FileSystem, (fileSystem) => fileSystem.realPath(path)).pipe(Effect.orElseSucceed(() => path));
107
+ const VERSION = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+.*)?$/u;
108
+ /**
109
+ * Compares two semantic versions: negative if `left` is older, positive if newer, zero if equal or
110
+ * if either doesn't parse.
111
+ */
112
+ export const compareVersions = (left, right) => {
113
+ const a = VERSION.exec(left);
114
+ const b = VERSION.exec(right);
115
+ if (a === null || b === null)
116
+ return 0;
117
+ for (const index of [1, 2, 3]) {
118
+ const difference = Number(a[index]) - Number(b[index]);
119
+ if (difference !== 0)
120
+ return difference;
121
+ }
122
+ const [preA, preB] = [a[4], b[4]];
123
+ if (preA === preB)
124
+ return 0;
125
+ if (preA === undefined)
126
+ return 1;
127
+ if (preB === undefined)
128
+ return -1;
129
+ return preA.localeCompare(preB, "en", { numeric: true });
130
+ };
131
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/instance/manifest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import { Effect, Schema } from "effect";
2
+ import { HttpClient } from "effect/http";
3
+ import { type Port } from "../cli.ts";
4
+ declare const ListedRun_base: Schema.Class<ListedRun, Schema.Struct<{
5
+ readonly id: Schema.brand<Schema.NonEmptyString, "RunId">;
6
+ readonly state: Schema.String;
7
+ }>, {}>;
8
+ /** A Run as `GET /api/overview` lists it. */
9
+ export declare class ListedRun extends ListedRun_base {
10
+ }
11
+ declare const OperatorApiError_base: Schema.Class<OperatorApiError, Schema.TaggedStruct<"OperatorApiError", {
12
+ readonly url: Schema.String;
13
+ readonly reason: Schema.String;
14
+ }>, import("effect/Cause").YieldableError>;
15
+ /** The running instance's operator API didn't answer, or its answer didn't decode. */
16
+ export declare class OperatorApiError extends OperatorApiError_base {
17
+ }
18
+ /** Lists the Runs of the instance that listens on `port`, with one `GET /api/overview`. */
19
+ export declare const listRuns: (port: number & import("effect/Brand").Brand<"ServerPort">) => Effect.Effect<ListedRun[], OperatorApiError, HttpClient.HttpClient>;
20
+ /**
21
+ * Returns the environment variables with the gateways' admin tokens, which `usine up` reads at
22
+ * startup, from `GET /api/configuration`.
23
+ */
24
+ export declare const gatewayTokenVariables: (port: number & import("effect/Brand").Brand<"ServerPort">) => Effect.Effect<string[], OperatorApiError, HttpClient.HttpClient>;
25
+ /** Returns whether an HTTP server answers on `port`, with any status. */
26
+ export declare const answers: (port: Port) => Effect.Effect<boolean, never, HttpClient.HttpClient>;
27
+ /** Whether the Run has a live Attempt. */
28
+ export declare const hasLiveAttempt: (run: ListedRun) => boolean;
29
+ export {};
30
+ //# sourceMappingURL=operator-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operator-api.d.ts","sourceRoot":"","sources":["../../src/instance/operator-api.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,UAAU,EAAsB,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,KAAK,IAAI,EAAe,MAAM,WAAW,CAAC;;;;;AAOnD,6CAA6C;AAC7C,qBAAa,SAAU,SAAQ,cAG7B;CAAG;;;;;AAcL,sFAAsF;AACtF,qBAAa,gBAAiB,SAAQ,qBAE8B;CAAG;AAevE,2FAA2F;AAC3F,eAAO,MAAM,QAAQ,oIAGnB,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,qBAAqB,iIAKhC,CAAC;AAEH,yEAAyE;AACzE,eAAO,MAAM,OAAO,SAAU,IAAI,yDAM/B,CAAC;AAEJ,0CAA0C;AAC1C,eAAO,MAAM,cAAc,QAAS,SAAS,KAAG,OAAkC,CAAC"}
@@ -0,0 +1,47 @@
1
+ import { RunId } from "@usine/core/run";
2
+ import { Effect, Schema } from "effect";
3
+ import { HttpClient, HttpClientResponse } from "effect/http";
4
+ import { SERVER_HOST } from "../cli.js";
5
+ const OPERATOR_API_TIMEOUT = "5 seconds";
6
+ // The CLI decodes only the fields that it reads, so that it reads an instance of another engine
7
+ // version.
8
+ /** A Run as `GET /api/overview` lists it. */
9
+ export class ListedRun extends Schema.Class("@usine/server/ListedRun")({
10
+ id: RunId,
11
+ state: Schema.String,
12
+ }) {
13
+ }
14
+ const Overview = Schema.Struct({
15
+ projects: Schema.Array(Schema.Struct({ runs: Schema.Array(ListedRun) })),
16
+ });
17
+ const Configuration = Schema.Struct({
18
+ workers: Schema.Array(Schema.Struct({
19
+ gateway: Schema.NullOr(Schema.Struct({ adminTokenEnv: Schema.NonEmptyString })),
20
+ })),
21
+ });
22
+ /** The running instance's operator API didn't answer, or its answer didn't decode. */
23
+ export class OperatorApiError extends Schema.TaggedError("@usine/server/OperatorApiError")("OperatorApiError", { url: Schema.String, reason: Schema.String }) {
24
+ }
25
+ const apiUrl = (port, path) => `http://${SERVER_HOST}:${port}${path}`;
26
+ const getJson = (port, path, schema) => {
27
+ const url = apiUrl(port, path);
28
+ return Effect.flatMap(HttpClient.HttpClient, (client) => HttpClient.filterStatusOk(client).get(url)).pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(schema)), Effect.timeout(OPERATOR_API_TIMEOUT), Effect.mapError((error) => new OperatorApiError({ url, reason: error.message })));
29
+ };
30
+ /** Lists the Runs of the instance that listens on `port`, with one `GET /api/overview`. */
31
+ export const listRuns = Effect.fn("Instance.listRuns")(function* (port) {
32
+ const overview = yield* getJson(port, "/api/overview", Overview);
33
+ return overview.projects.flatMap(({ runs }) => runs);
34
+ });
35
+ /**
36
+ * Returns the environment variables with the gateways' admin tokens, which `usine up` reads at
37
+ * startup, from `GET /api/configuration`.
38
+ */
39
+ export const gatewayTokenVariables = Effect.fn("Instance.gatewayTokenVariables")(function* (port) {
40
+ const configuration = yield* getJson(port, "/api/configuration", Configuration);
41
+ return [...new Set(configuration.workers.flatMap(({ gateway }) => gateway?.adminTokenEnv ?? []))];
42
+ });
43
+ /** Returns whether an HTTP server answers on `port`, with any status. */
44
+ export const answers = (port) => Effect.flatMap(HttpClient.HttpClient, (client) => client.get(apiUrl(port, "/api/overview"))).pipe(Effect.flatMap((response) => response.text), Effect.timeout("2 seconds"), Effect.as(true), Effect.orElseSucceed(() => false));
45
+ /** Whether the Run has a live Attempt. */
46
+ export const hasLiveAttempt = (run) => run.state === "Running";
47
+ //# sourceMappingURL=operator-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operator-api.js","sourceRoot":"","sources":["../../src/instance/operator-api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { Effect, FileSystem, Schema } from "effect";
2
+ import { ChildProcessSpawner } from "effect/process";
3
+ declare const ListeningPortsError_base: Schema.Class<ListeningPortsError, Schema.TaggedStruct<"ListeningPortsError", {
4
+ readonly pid: Schema.brand<Schema.Int, "ProcessId">;
5
+ readonly reason: Schema.String;
6
+ }>, import("effect/Cause").YieldableError>;
7
+ /** The OS couldn't list the TCP ports that a process listens on. */
8
+ export declare class ListeningPortsError extends ListeningPortsError_base {
9
+ }
10
+ /**
11
+ * Lists the IPv4 TCP ports that the process listens on, from its file descriptors and its network
12
+ * namespace's socket table in `/proc`. Linux only. Fails if this user may not read them.
13
+ */
14
+ export declare const procListeningPorts: (pid: number & import("effect/Brand").Brand<"ProcessId">) => Effect.Effect<(number & import("effect/Brand").Brand<"ServerPort">)[], ListeningPortsError, FileSystem.FileSystem>;
15
+ /**
16
+ * Lists the IPv4 TCP ports that the process listens on, from lsof. Fails if lsof is missing or
17
+ * doesn't answer in time. Returns no ports for a process that lsof may not inspect.
18
+ */
19
+ export declare const lsofListeningPorts: (pid: number & import("effect/Brand").Brand<"ProcessId">) => Effect.Effect<(number & import("effect/Brand").Brand<"ServerPort">)[], ListeningPortsError, ChildProcessSpawner.ChildProcessSpawner>;
20
+ /** Lists the IPv4 TCP ports that the process listens on: from `/proc` on Linux, else from lsof. */
21
+ export declare const listeningPorts: (pid: number & import("effect/Brand").Brand<"ProcessId">) => Effect.Effect<(number & import("effect/Brand").Brand<"ServerPort">)[], ListeningPortsError, ChildProcessSpawner.ChildProcessSpawner | FileSystem.FileSystem>;
22
+ export {};
23
+ //# sourceMappingURL=ports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ports.d.ts","sourceRoot":"","sources":["../../src/instance/ports.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAA8B,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChF,OAAO,EAAgB,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;;;;;AASnE,oEAAoE;AACpE,qBAAa,mBAAoB,SAAQ,wBAE0B;CAAG;AAqBtE;;;GAGG;AACH,eAAO,MAAM,kBAAkB,iLA+B7B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,kBAAkB,mMA2B7B,CAAC;AAEH,mGAAmG;AACnG,eAAO,MAAM,cAAc,2NAKzB,CAAC"}
@@ -0,0 +1,81 @@
1
+ import { Effect, FileSystem, Option, Schema } from "effect";
2
+ import { ChildProcess, ChildProcessSpawner } from "effect/process";
3
+ import { Port } from "../cli.js";
4
+ import { ProcessId } from "./record.js";
5
+ const LSOF_TIMEOUT = "2 seconds";
6
+ /** The `st` column of a listening socket in `/proc/<pid>/net/tcp`. */
7
+ const TCP_LISTEN = "0A";
8
+ const SOCKET_LINK = /^socket:\[(\d+)\]$/u;
9
+ /** The OS couldn't list the TCP ports that a process listens on. */
10
+ export class ListeningPortsError extends Schema.TaggedError("@usine/server/ListeningPortsError")("ListeningPortsError", { pid: ProcessId, reason: Schema.String }) {
11
+ }
12
+ /** Returns the port after the last colon of a socket address, in base `radix`. */
13
+ const addressPort = (address, radix) => Option.toArray(Schema.decodeUnknownOption(Port)(Number.parseInt(address.slice(address.lastIndexOf(":") + 1), radix)));
14
+ /** Returns the inode and port of each listening socket in a `/proc/<pid>/net/tcp` table. */
15
+ const listeningSockets = (table) => table
16
+ .split("\n")
17
+ .slice(1)
18
+ .flatMap((line) => {
19
+ const [, local, , state, , , , , , inode] = line.trim().split(/\s+/u);
20
+ if (state !== TCP_LISTEN || local === undefined || inode === undefined)
21
+ return [];
22
+ return addressPort(local, 16).map((port) => ({ inode, port }));
23
+ });
24
+ /**
25
+ * Lists the IPv4 TCP ports that the process listens on, from its file descriptors and its network
26
+ * namespace's socket table in `/proc`. Linux only. Fails if this user may not read them.
27
+ */
28
+ export const procListeningPorts = Effect.fn("Instance.procListeningPorts")(function* (pid) {
29
+ const fileSystem = yield* FileSystem.FileSystem;
30
+ const failure = (error) => new ListeningPortsError({ pid, reason: error.message });
31
+ const descriptors = yield* fileSystem
32
+ .readDirectory(`/proc/${pid}/fd`)
33
+ .pipe(Effect.mapError(failure));
34
+ const inodes = new Set();
35
+ for (const descriptor of descriptors) {
36
+ const target = yield* fileSystem.readLink(`/proc/${pid}/fd/${descriptor}`).pipe(Effect.tapError((error) => Effect.logDebug("Skipped a descriptor that the process may have closed after the listing", {
37
+ pid,
38
+ descriptor,
39
+ reason: error.message,
40
+ })), Effect.option);
41
+ const inode = Option.flatMapNullishOr(target, (link) => SOCKET_LINK.exec(link)?.[1]);
42
+ if (Option.isSome(inode))
43
+ inodes.add(inode.value);
44
+ }
45
+ const table = yield* fileSystem
46
+ .readFileString(`/proc/${pid}/net/tcp`)
47
+ .pipe(Effect.mapError(failure));
48
+ const ports = listeningSockets(table)
49
+ .filter(({ inode }) => inodes.has(inode))
50
+ .map(({ port }) => port);
51
+ return [...new Set(ports)];
52
+ });
53
+ /**
54
+ * Lists the IPv4 TCP ports that the process listens on, from lsof. Fails if lsof is missing or
55
+ * doesn't answer in time. Returns no ports for a process that lsof may not inspect.
56
+ */
57
+ export const lsofListeningPorts = Effect.fn("Instance.lsofListeningPorts")(function* (pid) {
58
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
59
+ // -a selects the sockets that match every option, not any of them.
60
+ const lines = yield* spawner
61
+ .lines(ChildProcess.make("lsof", ["-a", "-nP", "-p", String(pid), "-i4TCP", "-sTCP:LISTEN", "-Fn"], {
62
+ stdin: "ignore",
63
+ stderr: "ignore",
64
+ }))
65
+ .pipe(Effect.timeoutOrElse({
66
+ duration: LSOF_TIMEOUT,
67
+ orElse: () => Effect.fail(new ListeningPortsError({ pid, reason: `lsof took over ${LSOF_TIMEOUT}` })),
68
+ }), Effect.catchTag("PlatformError", (error) => Effect.fail(new ListeningPortsError({ pid, reason: error.message }))));
69
+ // With -Fn, lsof names each socket on a line like `n127.0.0.1:7331`.
70
+ const ports = lines
71
+ .filter((line) => line.startsWith("n"))
72
+ .flatMap((line) => addressPort(line, 10));
73
+ return [...new Set(ports)];
74
+ });
75
+ /** Lists the IPv4 TCP ports that the process listens on: from `/proc` on Linux, else from lsof. */
76
+ export const listeningPorts = Effect.fn("Instance.listeningPorts")(function* (pid) {
77
+ const ports = process.platform === "linux" ? yield* procListeningPorts(pid) : yield* lsofListeningPorts(pid);
78
+ yield* Effect.annotateCurrentSpan({ pid, ports });
79
+ return ports;
80
+ });
81
+ //# sourceMappingURL=ports.js.map