@omnitron-dev/omnitron 0.3.5 → 0.4.6

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 (267) hide show
  1. package/README.md +296 -6
  2. package/bin/{pnpmfile-sdk.mjs → pnpmfile-sdk.js} +13 -0
  3. package/bin/sdk.d.ts +19 -0
  4. package/bin/sdk.js +932 -0
  5. package/dist/BUILD.json +2 -2
  6. package/dist/cli/omnitron.js +14 -6
  7. package/dist/cli/omnitron.js.map +1 -1
  8. package/dist/commands/stack.d.ts +2 -5
  9. package/dist/commands/stack.d.ts.map +1 -1
  10. package/dist/commands/stack.js +32 -0
  11. package/dist/commands/stack.js.map +1 -1
  12. package/dist/commands/status.d.ts.map +1 -1
  13. package/dist/commands/status.js +1 -0
  14. package/dist/commands/status.js.map +1 -1
  15. package/dist/daemon/daemon-client.d.ts +3 -15
  16. package/dist/daemon/daemon-client.d.ts.map +1 -1
  17. package/dist/daemon/daemon-client.js +4 -0
  18. package/dist/daemon/daemon-client.js.map +1 -1
  19. package/dist/daemon/daemon.d.ts +5 -11
  20. package/dist/daemon/daemon.d.ts.map +1 -1
  21. package/dist/daemon/daemon.js +28 -42
  22. package/dist/daemon/daemon.js.map +1 -1
  23. package/dist/daemon/daemon.rpc-service.d.ts +3 -15
  24. package/dist/daemon/daemon.rpc-service.d.ts.map +1 -1
  25. package/dist/daemon/daemon.rpc-service.js +27 -3
  26. package/dist/daemon/daemon.rpc-service.js.map +1 -1
  27. package/dist/daemon/darwin-owner-evidence.js +1 -1
  28. package/dist/daemon/darwin-owner-evidence.js.map +1 -1
  29. package/dist/daemon/execution-capabilities.d.ts +1 -1
  30. package/dist/daemon/execution-capabilities.d.ts.map +1 -1
  31. package/dist/daemon/execution-capabilities.js +3 -3
  32. package/dist/daemon/execution-capabilities.js.map +1 -1
  33. package/dist/daemon/execution-client.d.ts +4 -0
  34. package/dist/daemon/execution-client.d.ts.map +1 -1
  35. package/dist/daemon/execution-client.js +28 -6
  36. package/dist/daemon/execution-client.js.map +1 -1
  37. package/dist/daemon/execution-host.d.ts +4 -11
  38. package/dist/daemon/execution-host.d.ts.map +1 -1
  39. package/dist/daemon/execution-host.js +3 -9
  40. package/dist/daemon/execution-host.js.map +1 -1
  41. package/dist/daemon/execution-owner.d.ts.map +1 -1
  42. package/dist/daemon/execution-owner.js +2 -16
  43. package/dist/daemon/execution-owner.js.map +1 -1
  44. package/dist/daemon/execution-shutdown.d.ts +1 -1
  45. package/dist/daemon/execution-shutdown.d.ts.map +1 -1
  46. package/dist/daemon/managed-owner-build.d.ts +4 -0
  47. package/dist/daemon/managed-owner-build.d.ts.map +1 -1
  48. package/dist/daemon/managed-owner-build.js +14 -2
  49. package/dist/daemon/managed-owner-build.js.map +1 -1
  50. package/dist/daemon/managed-runtime.js +1 -1
  51. package/dist/daemon/managed-runtime.js.map +1 -1
  52. package/dist/daemon/owner-reconciliation.d.ts.map +1 -1
  53. package/dist/daemon/owner-reconciliation.js +1 -2
  54. package/dist/daemon/owner-reconciliation.js.map +1 -1
  55. package/dist/daemon/system-guardian-cgroup.js +1 -1
  56. package/dist/daemon/system-guardian-cgroup.js.map +1 -1
  57. package/dist/infrastructure/existing-containers.d.ts +25 -0
  58. package/dist/infrastructure/existing-containers.d.ts.map +1 -0
  59. package/dist/infrastructure/existing-containers.js +183 -0
  60. package/dist/infrastructure/existing-containers.js.map +1 -0
  61. package/dist/infrastructure/infrastructure.service.d.ts +14 -1
  62. package/dist/infrastructure/infrastructure.service.d.ts.map +1 -1
  63. package/dist/infrastructure/infrastructure.service.js +118 -9
  64. package/dist/infrastructure/infrastructure.service.js.map +1 -1
  65. package/dist/infrastructure/stack-infra-manager.d.ts +8 -2
  66. package/dist/infrastructure/stack-infra-manager.d.ts.map +1 -1
  67. package/dist/infrastructure/stack-infra-manager.js +46 -9
  68. package/dist/infrastructure/stack-infra-manager.js.map +1 -1
  69. package/dist/monitoring/log-journal.d.ts.map +1 -1
  70. package/dist/monitoring/log-journal.js +1 -2
  71. package/dist/monitoring/log-journal.js.map +1 -1
  72. package/dist/operations.d.ts +1 -0
  73. package/dist/operations.d.ts.map +1 -1
  74. package/dist/operations.js +1 -0
  75. package/dist/operations.js.map +1 -1
  76. package/dist/orchestrator/bootstrap-loader.d.ts +2 -0
  77. package/dist/orchestrator/bootstrap-loader.d.ts.map +1 -1
  78. package/dist/orchestrator/bootstrap-loader.js +2 -1
  79. package/dist/orchestrator/bootstrap-loader.js.map +1 -1
  80. package/dist/orchestrator/classic-launcher.d.ts.map +1 -1
  81. package/dist/orchestrator/classic-launcher.js +2 -1
  82. package/dist/orchestrator/classic-launcher.js.map +1 -1
  83. package/dist/orchestrator/execution-contract.d.ts +2 -3
  84. package/dist/orchestrator/execution-contract.d.ts.map +1 -1
  85. package/dist/orchestrator/file-watcher.d.ts +11 -23
  86. package/dist/orchestrator/file-watcher.d.ts.map +1 -1
  87. package/dist/orchestrator/file-watcher.js +57 -42
  88. package/dist/orchestrator/file-watcher.js.map +1 -1
  89. package/dist/orchestrator/orchestrator.service.d.ts +10 -5
  90. package/dist/orchestrator/orchestrator.service.d.ts.map +1 -1
  91. package/dist/orchestrator/orchestrator.service.js +117 -20
  92. package/dist/orchestrator/orchestrator.service.js.map +1 -1
  93. package/dist/project/bootstrap-metadata.d.ts +1 -1
  94. package/dist/project/bootstrap-metadata.d.ts.map +1 -1
  95. package/dist/project/bootstrap-metadata.js +3 -2
  96. package/dist/project/bootstrap-metadata.js.map +1 -1
  97. package/dist/project/node-stack-stop.d.ts +2 -1
  98. package/dist/project/node-stack-stop.d.ts.map +1 -1
  99. package/dist/project/node-stack-stop.js +5 -1
  100. package/dist/project/node-stack-stop.js.map +1 -1
  101. package/dist/release/closure-library-path.d.ts +8 -2
  102. package/dist/release/closure-library-path.d.ts.map +1 -1
  103. package/dist/release/closure-library-path.js +72 -28
  104. package/dist/release/closure-library-path.js.map +1 -1
  105. package/dist/release/local-installation.d.ts +2 -0
  106. package/dist/release/local-installation.d.ts.map +1 -1
  107. package/dist/release/local-installation.js +1 -1
  108. package/dist/release/local-installation.js.map +1 -1
  109. package/dist/release/portable-archive.d.ts +3 -0
  110. package/dist/release/portable-archive.d.ts.map +1 -1
  111. package/dist/release/portable-archive.js +13 -7
  112. package/dist/release/portable-archive.js.map +1 -1
  113. package/dist/release/registry-framework.d.ts +25 -0
  114. package/dist/release/registry-framework.d.ts.map +1 -1
  115. package/dist/release/registry-framework.js +51 -3
  116. package/dist/release/registry-framework.js.map +1 -1
  117. package/dist/release/target-runtime.d.ts +4 -0
  118. package/dist/release/target-runtime.d.ts.map +1 -1
  119. package/dist/release/target-runtime.js +9 -0
  120. package/dist/release/target-runtime.js.map +1 -1
  121. package/dist/services/host-upgrade.d.ts +2 -0
  122. package/dist/services/host-upgrade.d.ts.map +1 -1
  123. package/dist/services/host-upgrade.js +6 -2
  124. package/dist/services/host-upgrade.js.map +1 -1
  125. package/dist/services/infrastructure.rpc-service.d.ts +8 -0
  126. package/dist/services/infrastructure.rpc-service.d.ts.map +1 -1
  127. package/dist/services/infrastructure.rpc-service.js +90 -10
  128. package/dist/services/infrastructure.rpc-service.js.map +1 -1
  129. package/dist/services/kernel-rollout.service.d.ts.map +1 -1
  130. package/dist/services/kernel-rollout.service.js +1 -2
  131. package/dist/services/kernel-rollout.service.js.map +1 -1
  132. package/dist/services/migration-process.d.ts +1 -0
  133. package/dist/services/migration-process.d.ts.map +1 -1
  134. package/dist/services/migration-process.js +4 -2
  135. package/dist/services/migration-process.js.map +1 -1
  136. package/dist/services/pipeline-definition.d.ts.map +1 -1
  137. package/dist/services/pipeline-definition.js +32 -6
  138. package/dist/services/pipeline-definition.js.map +1 -1
  139. package/dist/services/pipeline.service.d.ts +7 -1
  140. package/dist/services/pipeline.service.d.ts.map +1 -1
  141. package/dist/services/pipeline.service.js +106 -13
  142. package/dist/services/pipeline.service.js.map +1 -1
  143. package/dist/services/project.rpc-service.d.ts +6 -5
  144. package/dist/services/project.rpc-service.d.ts.map +1 -1
  145. package/dist/services/project.rpc-service.js +13 -0
  146. package/dist/services/project.rpc-service.js.map +1 -1
  147. package/dist/services/project.service.d.ts +24 -6
  148. package/dist/services/project.service.d.ts.map +1 -1
  149. package/dist/services/project.service.js +327 -59
  150. package/dist/services/project.service.js.map +1 -1
  151. package/dist/services/remote-deployer.service.d.ts.map +1 -1
  152. package/dist/services/remote-deployer.service.js +3 -0
  153. package/dist/services/remote-deployer.service.js.map +1 -1
  154. package/dist/shared/dto/backend.d.ts +3 -0
  155. package/dist/shared/dto/backend.d.ts.map +1 -1
  156. package/dist/shared/dto/backend.js.map +1 -1
  157. package/dist/shared/dto/pipelines.d.ts +54 -5
  158. package/dist/shared/dto/pipelines.d.ts.map +1 -1
  159. package/dist/shared/dto/pipelines.js +0 -7
  160. package/dist/shared/dto/pipelines.js.map +1 -1
  161. package/dist/shared/dto/project.d.ts +60 -0
  162. package/dist/shared/dto/project.d.ts.map +1 -1
  163. package/dist/shared/dto/services.d.ts +26 -17
  164. package/dist/shared/dto/services.d.ts.map +1 -1
  165. package/dist/shared/dto/services.js.map +1 -1
  166. package/dist/workers/system-worker-manager.d.ts +1 -2
  167. package/dist/workers/system-worker-manager.d.ts.map +1 -1
  168. package/dist/workers/system-worker-manager.js +5 -3
  169. package/dist/workers/system-worker-manager.js.map +1 -1
  170. package/package.json +17 -16
  171. package/webapp/dist/BUILD.json +2 -2
  172. package/webapp/dist/assets/{access-DUjabwNX.js → access-B2gXuoMG.js} +1 -1
  173. package/webapp/dist/assets/{access-BvKK9dEv.js → access-I0f72P9m.js} +1 -1
  174. package/webapp/dist/assets/{admin-data-table-BKSDI8sU.js → admin-data-table-CAEL2f-m.js} +1 -1
  175. package/webapp/dist/assets/{alert-uBG6r9NW.js → alert-BdJ2uaY0.js} +1 -1
  176. package/webapp/dist/assets/{alerts-CzN6OqX5.js → alerts-DeVkMXWo.js} +1 -1
  177. package/webapp/dist/assets/{apps-G_Lm6m4F.js → apps-9SuDvyGQ.js} +1 -1
  178. package/webapp/dist/assets/{audit-CLMpdBzG.js → audit-DT8cY7Ys.js} +1 -1
  179. package/webapp/dist/assets/backend-status.store-BbBE9cn8.js +4 -0
  180. package/webapp/dist/assets/{card-DkH32gMY.js → card-DaUEmUI5.js} +1 -1
  181. package/webapp/dist/assets/{chart-BpGXlCJc.js → chart-BKXP2ter.js} +1 -1
  182. package/webapp/dist/assets/{client-DFnuv2z3.js → client-CgyTwq9g.js} +1 -1
  183. package/webapp/dist/assets/{code-editor-DK6Tmmxv.js → code-editor-JVuQ4m-z.js} +1 -1
  184. package/webapp/dist/assets/{comparison-bars-BtC0dHjF.js → comparison-bars-Ds5Dt1oj.js} +1 -1
  185. package/webapp/dist/assets/{confirm-dialog-BbGsAz7_.js → confirm-dialog-BayExVbF.js} +1 -1
  186. package/webapp/dist/assets/{console-keyboard-D3YCA0yU.js → console-keyboard-uSLt92dB.js} +1 -1
  187. package/webapp/dist/assets/{containers-BsFwprq4.js → containers-DkXTgcrY.js} +1 -1
  188. package/webapp/dist/assets/{controls-C5W8gHU4.js → controls-CqeGPnaF.js} +1 -1
  189. package/webapp/dist/assets/{dashboard-BN7rKE5n.js → dashboard-By5yPuOf.js} +1 -1
  190. package/webapp/dist/assets/{dashboard-builder-BXFxWjXP.js → dashboard-builder-C0hvBwkd.js} +1 -1
  191. package/webapp/dist/assets/{deploy-progress-CUUwlYtQ.js → deploy-progress-B39cvvCE.js} +1 -1
  192. package/webapp/dist/assets/{deploy-release-dialog-58lGZDNk.js → deploy-release-dialog-CnzaO_xL.js} +1 -1
  193. package/webapp/dist/assets/{deployments-DEzjHerg.js → deployments-BXP943hA.js} +1 -1
  194. package/webapp/dist/assets/{detail-pXPk4O0E.js → detail-B84RgiOb.js} +1 -1
  195. package/webapp/dist/assets/{detail-Dj3gxaH0.js → detail-CVXDks9-.js} +1 -1
  196. package/webapp/dist/assets/{detail-CBn9pjJU.js → detail-D9DixtaK.js} +1 -1
  197. package/webapp/dist/assets/{empty-content-7jOFhG4C.js → empty-content-eyDOuOVp.js} +1 -1
  198. package/webapp/dist/assets/{error-boundary-CLLI8PWw.js → error-boundary-CQmDr_HS.js} +1 -1
  199. package/webapp/dist/assets/{event-fields-CiyqrRO8.js → event-fields-C7p2a2HY.js} +1 -1
  200. package/webapp/dist/assets/{files-DigwXndm.js → files-BsfGT3t6.js} +2 -2
  201. package/webapp/dist/assets/{icons-OboZgWSt.js → icons-B9Ct8PJ9.js} +1 -1
  202. package/webapp/dist/assets/index-BEfv96EF.js +1 -0
  203. package/webapp/dist/assets/{label-B1SF0RTL.js → label-avMhe08g.js} +1 -1
  204. package/webapp/dist/assets/{log-level-filter-Bcn31D1F.js → log-level-filter-oaUGdbpk.js} +1 -1
  205. package/webapp/dist/assets/{logs-DrsBbLfb.js → logs-iC1OEDv3.js} +1 -1
  206. package/webapp/dist/assets/{metric-chart-M-kI-XOt.js → metric-chart-CSa_qv4F.js} +1 -1
  207. package/webapp/dist/assets/{metrics-D5cDZTEo.js → metrics-CKCmcGKw.js} +1 -1
  208. package/webapp/dist/assets/{mui-Cd711nOk.js → mui-B3fJdIMc.js} +1 -1
  209. package/webapp/dist/assets/{mui-CdB_p2lt.js → mui-Bdr6Ks5j.js} +1 -1
  210. package/webapp/dist/assets/{mui-Db5J-QUd.js → mui-CADnFGCx.js} +1 -1
  211. package/webapp/dist/assets/{mui-AnEgAVjz.js → mui-CMWGTMXv.js} +1 -1
  212. package/webapp/dist/assets/{mui-DyT2n1lx.js → mui-CN3dT7bb.js} +1 -1
  213. package/webapp/dist/assets/{mui-BwsIrivo.js → mui-CPEkFAPo.js} +1 -1
  214. package/webapp/dist/assets/{mui-dLCwWoMw.js → mui-D3qAWvfZ.js} +1 -1
  215. package/webapp/dist/assets/{mui-D5m7fDCh.js → mui-D7tBU80e.js} +1 -1
  216. package/webapp/dist/assets/{mui-59N0OVRa.js → mui-DaJavAoc.js} +1 -1
  217. package/webapp/dist/assets/{mui-BXSXx9c9.js → mui-SNRRJ_vi.js} +1 -1
  218. package/webapp/dist/assets/{nodes-CwwZZuu4.js → nodes-DtLyXH0V.js} +1 -1
  219. package/webapp/dist/assets/{operation-status-Bm1x1wTz.js → operation-status-DHyYOT7G.js} +1 -1
  220. package/webapp/dist/assets/{page-breadcrumbs-fR7Jmpp4.js → page-breadcrumbs-3R8RZ3nA.js} +1 -1
  221. package/webapp/dist/assets/pipelines-Do2NAAqK.js +3 -0
  222. package/webapp/dist/assets/{project.store-DuCgv6Eq.js → project.store-D5IR3fo9.js} +1 -1
  223. package/webapp/dist/assets/{projects-TBAdpCeX.js → projects-BqlGbzeG.js} +1 -1
  224. package/webapp/dist/assets/{realtime.store-hfxZD2DU.js → realtime.store-C8D235-B.js} +1 -1
  225. package/webapp/dist/assets/{release-bits-BJ6i23y0.js → release-bits-IehuBssK.js} +1 -1
  226. package/webapp/dist/assets/{release-build-panel-BNXkwNE1.js → release-build-panel-C48hP_9w.js} +1 -1
  227. package/webapp/dist/assets/{release-deployment-state-gN8ZT3aq.js → release-deployment-state-BfmxZP4Y.js} +1 -1
  228. package/webapp/dist/assets/{releases-CXXKUh-v.js → releases-Cm0RV5Xu.js} +1 -1
  229. package/webapp/dist/assets/{search-input-C0MHI8vO.js → search-input-SWD1dXxD.js} +1 -1
  230. package/webapp/dist/assets/settings-DwuEPQGg.js +1 -0
  231. package/webapp/dist/assets/sign-in-CQ-WeS5A.js +1 -0
  232. package/webapp/dist/assets/{skeleton-BmWDJWF9.js → skeleton-BnVxAoeX.js} +1 -1
  233. package/webapp/dist/assets/{snackbar-DSPmjMyi.js → snackbar-CRX6eqRT.js} +1 -1
  234. package/webapp/dist/assets/{stack-health-status-BIv2C10x.js → stack-health-status-DfErRNb1.js} +1 -1
  235. package/webapp/dist/assets/{stack-start-notice-D-AH_bzn.js → stack-start-notice-NkYDjR-f.js} +1 -1
  236. package/webapp/dist/assets/{stacks-WY_ynkON.js → stacks-BOGCfW90.js} +1 -1
  237. package/webapp/dist/assets/{store-bqUBn2A4.js → store-DtTtbc9H.js} +1 -1
  238. package/webapp/dist/assets/{system-info-DXMwo6gl.js → system-info-Cw-xywgI.js} +1 -1
  239. package/webapp/dist/assets/{table-BOWT6Y9d.js → table-D-so_B1p.js} +1 -1
  240. package/webapp/dist/assets/{table-viewport-Bp4pq-c_.js → table-viewport-DdZ1yAKm.js} +1 -1
  241. package/webapp/dist/assets/{tabs-k5NPGLnJ.js → tabs-DGqBkXh_.js} +1 -1
  242. package/webapp/dist/assets/{terminal-QWBau-Us.js → terminal-Bbl0XNeB.js} +1 -1
  243. package/webapp/dist/assets/{topology-C_Qp1RqW.js → topology-DmrRy7nN.js} +1 -1
  244. package/webapp/dist/assets/{topology-metadata-dialog-BivuU2y6.js → topology-metadata-dialog-Dpfp4YF1.js} +1 -1
  245. package/webapp/dist/assets/{traces-By0rdfDC.js → traces-CKHsOabJ.js} +1 -1
  246. package/webapp/dist/assets/{use-stack-context-8yaHaN8L.js → use-stack-context-BuraYaeY.js} +1 -1
  247. package/webapp/dist/assets/webapp-D8h_eabt.js +35 -0
  248. package/webapp/dist/console-version.json +1 -1
  249. package/webapp/dist/index.html +34 -34
  250. package/webapp/dist/logo.svg +1 -80
  251. package/bin/sdk.d.mts +0 -8
  252. package/bin/sdk.mjs +0 -580
  253. package/dist/monitoring/process-lifetime.d.ts +0 -11
  254. package/dist/monitoring/process-lifetime.d.ts.map +0 -1
  255. package/dist/monitoring/process-lifetime.js +0 -45
  256. package/dist/monitoring/process-lifetime.js.map +0 -1
  257. package/dist/shared/host-boot-identity.d.ts +0 -12
  258. package/dist/shared/host-boot-identity.d.ts.map +0 -1
  259. package/dist/shared/host-boot-identity.js +0 -89
  260. package/dist/shared/host-boot-identity.js.map +0 -1
  261. package/webapp/dist/assets/index-Bo9An3pJ.js +0 -1
  262. package/webapp/dist/assets/omnitron-logo-koMCbsCv.js +0 -1
  263. package/webapp/dist/assets/pipelines-BrfTjf6Y.js +0 -3
  264. package/webapp/dist/assets/settings-B3egRvbI.js +0 -1
  265. package/webapp/dist/assets/sign-in-9q-Nfy83.js +0 -1
  266. package/webapp/dist/assets/webapp-BO_WoB_z.js +0 -35
  267. package/webapp/dist/favicon.png +0 -0
package/bin/sdk.js ADDED
@@ -0,0 +1,932 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from 'node:child_process';
3
+ import { createHash, randomUUID } from 'node:crypto';
4
+ import fs from 'node:fs/promises';
5
+ import { watch } from 'node:fs';
6
+ import path from 'node:path';
7
+ import os from 'node:os';
8
+ import { fileURLToPath, pathToFileURL } from 'node:url';
9
+ import { setTimeout as delay } from 'node:timers/promises';
10
+ import { isDeepStrictEqual, parseArgs } from 'node:util';
11
+ import { parseAllDocuments } from 'yaml';
12
+ import { frameworkPackage } from './pnpmfile-sdk.js';
13
+
14
+ const help = `omnitron sdk init [--project-dir PATH]
15
+ omnitron sdk dev [SDK_CHECKOUT] [--project-dir PATH] [--stack NAME] [--no-watch]
16
+ omnitron sdk registry [--project-dir PATH]
17
+ omnitron sdk status [--project-dir PATH]
18
+
19
+ init installs the pnpm bridge without resolving registry dependencies.
20
+ dev selects the local SDK and watches its sources. Start again without a path to
21
+ reuse the selection. --no-watch builds the selected graph once without watching.
22
+ --stack confirms refresh of running applications in that local source stack.
23
+ Excluded or unavailable watchers are reported as failures, never as a ready SDK.
24
+ registry restores the frozen distribution dependencies. Releases always read Git.`;
25
+
26
+ const scope = '@omnitron-dev/';
27
+ // Released hook templates eligible for the explicit, idempotent init upgrade.
28
+ const previousBridgeHashes = new Set([
29
+ '28220745dd5d0643c4ac64ef75e7c466a9863741bd23473bbbfccee5c5f7858b',
30
+ '5d68d3ac4deb8f9d525b1db2e020dc1610d2ea49f3892232d982db20341c1735',
31
+ ]);
32
+ const statePath = (root) => path.join(root, '.omnitron', 'dev-sdk');
33
+ const sha256 = (value) => createHash('sha256').update(value).digest('hex');
34
+ const inside = (parent, child) => {
35
+ const rel = path.relative(parent, child);
36
+ return !path.isAbsolute(rel) && rel !== '..' && !rel.startsWith('..' + path.sep);
37
+ };
38
+ const readJson = async (file) => JSON.parse(await fs.readFile(file, 'utf8'));
39
+ async function optionalJson(file) {
40
+ try {
41
+ return await readJson(file);
42
+ } catch (error) {
43
+ if (error.code === 'ENOENT') return null;
44
+ throw error;
45
+ }
46
+ }
47
+ async function distributionLock(root) {
48
+ try { return await fs.readFile(path.join(root, 'pnpm-lock.yaml'), 'utf8'); }
49
+ catch (error) { if (error.code === 'ENOENT') return null; throw error; }
50
+ }
51
+ const lockIdentity = (distribution) => distribution === null ? null : sha256(distribution);
52
+ async function assertDistribution(root, identity) {
53
+ if (lockIdentity(await distributionLock(root)) !== identity)
54
+ throw Error('Distribution lock changed while preparing the development profile');
55
+ }
56
+ function lockDocuments(distribution) {
57
+ if (distribution === null) return [];
58
+ const documents = parseAllDocuments(distribution);
59
+ for (const document of documents)
60
+ if (document.errors.length) throw Error('Invalid pnpm-lock.yaml: ' + document.errors[0].message);
61
+ return documents;
62
+ }
63
+ const applicationDocument = (distribution) => lockDocuments(distribution).at(-1)?.toJS() ?? null;
64
+
65
+ async function restoreDistribution(root, original, owned) {
66
+ if (original === owned) return;
67
+ if (await distributionLock(root) !== owned)
68
+ throw Error('Distribution lock changed outside this SDK operation; its newer contents were preserved');
69
+ const file = path.join(root, 'pnpm-lock.yaml');
70
+ if (original === null) await fs.rm(file);
71
+ else await atomic(file, original);
72
+ }
73
+
74
+ async function bootstrapPackageManager(root, original) {
75
+ const application = applicationDocument(original);
76
+ await assertDistribution(root, lockIdentity(original));
77
+ let failure;
78
+ try { await command('pnpm', ['--version'], root, true); }
79
+ catch (error) { failure = error; }
80
+ const generated = await distributionLock(root);
81
+ if (!isDeepStrictEqual(applicationDocument(generated), application))
82
+ throw Error('Application distribution graph changed during package-manager bootstrap; its newer contents were preserved');
83
+ if (failure) {
84
+ await restoreDistribution(root, original, generated);
85
+ throw failure;
86
+ }
87
+ return generated;
88
+ }
89
+ async function atomic(file, value) {
90
+ const tmp = file + '.' + randomUUID() + '.tmp';
91
+ try {
92
+ await fs.writeFile(tmp, value, { flag: 'wx', mode: 0o600 });
93
+ await fs.rename(tmp, file);
94
+ } finally {
95
+ await fs.rm(tmp, { force: true });
96
+ }
97
+ }
98
+
99
+ function command(program, args, cwd, capture = false, signal) {
100
+ return new Promise((resolve, reject) => {
101
+ // pnpm and its lifecycle scripts must use the Node selected for this CLI,
102
+ // including when it was invoked by absolute path under a different shell.
103
+ const pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === 'path') ?? 'PATH';
104
+ const child = spawn(program, args, {
105
+ cwd,
106
+ env: { ...process.env, [pathKey]: path.dirname(process.execPath) + path.delimiter + (process.env[pathKey] ?? '') },
107
+ shell: false,
108
+ signal,
109
+ stdio: capture ? ['ignore', 'pipe', 'inherit'] : 'inherit',
110
+ });
111
+ let result = '';
112
+ child.stdout?.setEncoding('utf8').on('data', (chunk) => {
113
+ result += chunk;
114
+ });
115
+ child.on('error', reject);
116
+ child.on('close', (code, sig) =>
117
+ code === 0 ? resolve(result.trim()) : reject(new Error(`${program} ${args[0]} exited with ${sig ?? code}`))
118
+ );
119
+ });
120
+ }
121
+
122
+ async function inventory(root) {
123
+ const rows = JSON.parse(
124
+ await command(
125
+ 'pnpm',
126
+ ['--config.lockfile=false', '--config.ignore-pnpmfile=true', '-r', 'list', '--depth', '-1', '--json'],
127
+ root,
128
+ true
129
+ )
130
+ );
131
+ const entries = new Map();
132
+ for (const row of rows) {
133
+ const directory = await fs.realpath(row.path);
134
+ if (!inside(root, directory)) throw Error('Workspace package is outside the selected checkout: ' + directory);
135
+ const manifest = await readJson(path.join(directory, 'package.json'));
136
+ if (!manifest.name) continue;
137
+ if (entries.has(manifest.name)) throw Error('Duplicate workspace package: ' + manifest.name);
138
+ entries.set(manifest.name, { directory, manifest });
139
+ }
140
+ return entries;
141
+ }
142
+ function frameworkDependencies(manifest, development = false) {
143
+ const dependencies = { ...manifest.peerDependencies, ...manifest.dependencies,
144
+ ...manifest.optionalDependencies, ...(development && manifest.devDependencies) };
145
+ return Object.keys(dependencies).filter(name => name.startsWith(scope) &&
146
+ !(manifest.peerDependenciesMeta?.[name]?.optional && !manifest.dependencies?.[name] &&
147
+ !manifest.optionalDependencies?.[name] && !(development && manifest.devDependencies?.[name]))).sort();
148
+ }
149
+ async function moduleConfiguration(root) {
150
+ const settings = await Promise.all(['nodeLinker', 'virtualStoreDir'].map(async key => JSON.parse(await command(
151
+ 'pnpm', ['--config.lockfile=false', '--config.ignore-pnpmfile=true', 'config', 'get', key, '--json'], root, true
152
+ ))));
153
+ return { nodeLinker: settings[0] ?? 'isolated', virtualStoreDir: path.resolve(root, settings[1] ?? 'node_modules/.pnpm') };
154
+ }
155
+ async function desiredProfile(root, source, distribution) {
156
+ const packages = await inventory(source), consumers = await inventory(root);
157
+ for (const { directory } of consumers.values()) {
158
+ const modules = path.join(directory, 'node_modules');
159
+ const present = await fs.lstat(modules).catch(error => { if (error.code === 'ENOENT') return null; throw error; });
160
+ if (present && !inside(root, await fs.realpath(modules)))
161
+ throw Error('SDK installation requires project-owned node_modules; preserve the external dependency directory separately before selecting: ' + modules);
162
+ }
163
+ const links = {}, versions = {};
164
+ function select(name) {
165
+ if (Object.hasOwn(links, name)) return;
166
+ const pkg = packages.get(name);
167
+ if (!pkg) throw Error('SDK checkout does not provide ' + name);
168
+ const manifest = frameworkPackage(name, pkg.directory);
169
+ links[name] = pkg.directory;
170
+ versions[name] = manifest.version;
171
+ for (const dependency of frameworkDependencies(manifest)) select(dependency);
172
+ }
173
+ for (const { manifest } of consumers.values()) for (const name of frameworkDependencies(manifest, true)) {
174
+ if (consumers.has(name)) throw Error('A consumer workspace cannot shadow framework SDK package ' + name);
175
+ select(name);
176
+ }
177
+ if (!Object.keys(links).length) throw Error('The project declares no framework SDK dependencies');
178
+ const manifests = [...consumers.values()].map(({ directory, manifest }) => [path.relative(root, directory), manifest])
179
+ .sort(([left], [right]) => left.localeCompare(right));
180
+ const settings = [];
181
+ for (const file of ['pnpm-workspace.yaml', '.npmrc']) settings.push([file,
182
+ await fs.readFile(path.join(root, file), 'utf8').catch(error => { if (error.code === 'ENOENT') return null; throw error; })]);
183
+ // Environment interpolation and global pnpm configuration can change module
184
+ // placement without changing either workspace file. Bind the effective layout.
185
+ const installation = await moduleConfiguration(root);
186
+ const names = Object.keys(links).sort();
187
+ return { schema: 1, root, source, lockSha256: lockIdentity(distribution), inputSha256: sha256(JSON.stringify({ manifests, settings, installation })),
188
+ links: Object.fromEntries(names.map(name => [name, links[name]])), versions: Object.fromEntries(names.map(name => [name, versions[name]])) };
189
+ }
190
+
191
+ async function prepareState(root) {
192
+ const dir = statePath(root);
193
+ await fs.mkdir(dir, { recursive: true, mode: 0o700 });
194
+ if ((await fs.realpath(dir)) !== dir) throw Error('SDK state directory must be local to this project');
195
+ await fs.writeFile(path.join(dir, '.gitignore'), '*\n', { mode: 0o600 });
196
+ return dir;
197
+ }
198
+
199
+ // The OS releases the SQLite writer lock if the process dies, including on Windows.
200
+ async function exclusively(dir, body, waitSignal) {
201
+ const { DatabaseSync } = await import('node:sqlite');
202
+ const db = new DatabaseSync(path.join(dir, 'operation.sqlite'));
203
+ try {
204
+ for (;;) {
205
+ waitSignal?.throwIfAborted();
206
+ try {
207
+ db.exec('BEGIN IMMEDIATE');
208
+ break;
209
+ } catch (error) {
210
+ if (error.errcode !== 5) throw error;
211
+ if (!waitSignal) throw Error('Another development SDK operation is active in ' + dir);
212
+ await delay(200, undefined, { signal: waitSignal });
213
+ }
214
+ }
215
+ return await body();
216
+ } finally {
217
+ db.close();
218
+ }
219
+ }
220
+
221
+ async function seedLock(root, dir, distribution) {
222
+ if (applicationDocument(distribution) === null) {
223
+ await fs.rm(path.join(dir, 'pnpm-lock.yaml'), { force: true });
224
+ return;
225
+ }
226
+ const documents = lockDocuments(distribution);
227
+ const app = documents.at(-1);
228
+ const importers = app?.get('importers');
229
+ const prefix = path.relative(dir, root).split(path.sep).join('/');
230
+ for (const entry of importers?.items ?? []) entry.key.value = path.posix.normalize(path.posix.join(prefix, entry.key.value));
231
+ await atomic(path.join(dir, 'pnpm-lock.yaml'), documents.map((doc) => doc.toString()).join('---\n'));
232
+ }
233
+
234
+ async function installDevelopment(root, profile, expectedDistribution = profile.lockSha256) {
235
+ const dir = statePath(root);
236
+ const distribution = await distributionLock(root);
237
+ await assertDistribution(root, expectedDistribution);
238
+ await seedLock(root, dir, distribution);
239
+ // The workspace hook selects the same graph for normal pnpm install, run and exec.
240
+ // Keep an interrupted reinstall distinguishable from its last accepted profile.
241
+ await atomic(path.join(dir, 'install.json'), JSON.stringify({ ...profile, installing: true }, null, 2) + '\n');
242
+ // The package-manager boot document was prepared before capturing the
243
+ // baseline. Use ordinary hook discovery, also used by pnpm run/exec, so the
244
+ // installed pnpmfile receipt matches verifyDepsBeforeRun.
245
+ await command('pnpm', ['install', '--force', '--no-frozen-lockfile', '--config.confirm-modules-purge=false'], root);
246
+ await assertDistribution(root, expectedDistribution);
247
+ await verifyLinks(root, profile);
248
+ await atomic(path.join(dir, 'install.json'), JSON.stringify(profile, null, 2) + '\n');
249
+ }
250
+ // Development consumers need not be Omnitron applications. Registry restoration
251
+ // checks their own frozen importers; release archive admission remains separate.
252
+ async function registryBaseline(root, distribution, consumers) {
253
+ if (distribution === null)
254
+ throw Error('No distribution lock is available. Create a registry lock with pnpm install --lockfile-only --config.ignore-pnpmfile=true before sdk registry.');
255
+ if (applicationDocument(distribution) === null)
256
+ throw Error('The distribution lock contains package-manager metadata only; create a current registry application lock before sdk registry');
257
+ consumers ??= await inventory(root);
258
+ const importers = {}, packages = {};
259
+ for (const document of lockDocuments(distribution)) {
260
+ const graph = document.toJS();
261
+ Object.assign(importers, graph?.importers);
262
+ Object.assign(packages, graph?.packages);
263
+ }
264
+ const result = new Map(), sections = ['dependencies', 'devDependencies', 'optionalDependencies'];
265
+ for (const { directory, manifest } of consumers.values()) {
266
+ const key = path.relative(root, directory).split(path.sep).join('/') || '.';
267
+ const importer = importers[key];
268
+ if (!importer) throw Error('Distribution lock has no importer for ' + key);
269
+ for (const section of sections) {
270
+ const wanted = manifest[section] ?? {}, locked = importer[section] ?? {};
271
+ if (Object.entries(wanted).some(([name, specifier]) => locked[name]?.specifier !== specifier) ||
272
+ Object.keys(locked).some(name => !Object.hasOwn(wanted, name) && !Object.hasOwn(manifest.peerDependencies ?? {}, name)))
273
+ throw Error('Distribution lock is stale for ' + key + ' / ' + section + '; create a current registry lock before sdk registry');
274
+ }
275
+ for (const name of frameworkDependencies(manifest, true)) {
276
+ const entry = sections.map(section => importer[section]?.[name]).find(Boolean);
277
+ const version = typeof entry?.version === 'string' ? entry.version.split('(')[0] : '';
278
+ const archive = packages[name + '@' + version];
279
+ if (!archive || /^(?:link:|file:|workspace:)/.test(version) || archive.resolution?.type ||
280
+ !/^sha512-[A-Za-z0-9+/]{85}[AQgw]==$/.test(archive.resolution?.integrity ?? ''))
281
+ throw Error('Distribution lock must resolve a registry archive for ' + name);
282
+ result.set(directory + '\0' + name, { version, importer: key, entry, resolution: archive.resolution });
283
+ }
284
+ }
285
+ return result;
286
+ }
287
+
288
+ async function physicalInstallationDirectory(directory) {
289
+ const stat = await fs.lstat(directory);
290
+ if (!stat.isDirectory() || stat.isSymbolicLink() || await fs.realpath(directory) !== directory)
291
+ throw Error('pnpm installation requires a physical directory: ' + directory);
292
+ return directory;
293
+ }
294
+
295
+ async function readInstallationReceipt(file) {
296
+ const stat = await fs.lstat(file);
297
+ if (!stat.isFile() || stat.isSymbolicLink() || await fs.realpath(file) !== file)
298
+ throw Error('pnpm installation requires a physical receipt file: ' + file);
299
+ return fs.readFile(file, 'utf8');
300
+ }
301
+
302
+ async function registryInstallation(root) {
303
+ const modules = await physicalInstallationDirectory(path.join(root, 'node_modules'));
304
+ const { nodeLinker, virtualStoreDir: configured } = await moduleConfiguration(root);
305
+ const documents = lockDocuments(await readInstallationReceipt(path.join(modules, '.modules.yaml')));
306
+ const receipt = documents.length === 1 ? documents[0].toJS() : null;
307
+ if (!['isolated', 'hoisted'].includes(nodeLinker) || receipt?.nodeLinker !== nodeLinker ||
308
+ typeof receipt?.virtualStoreDir !== 'string' ||
309
+ path.resolve(modules, receipt.virtualStoreDir) !== configured)
310
+ throw Error('pnpm installation receipt does not match the configured nodeLinker and virtualStoreDir; run pnpm install for these settings');
311
+ const store = await physicalInstallationDirectory(configured);
312
+ const lock = applicationDocument(await readInstallationReceipt(path.join(store, 'lock.yaml')));
313
+ if (!lock?.importers || !lock?.packages) throw Error('pnpm installed lock receipt is missing its application graph');
314
+ return { nodeLinker, modules, store, lock };
315
+ }
316
+
317
+ async function verifyLinks(root, profile) {
318
+ const importers = await inventory(root);
319
+ const locked = profile ? null : await registryBaseline(root, await distributionLock(root), importers);
320
+ const installed = profile ? null : await registryInstallation(root);
321
+ let verified = 0;
322
+ for (const { directory, manifest } of importers.values()) {
323
+ for (const name of frameworkDependencies(manifest, true)) {
324
+ const actual = await fs.realpath(path.join(directory, 'node_modules', name));
325
+ if (!profile) {
326
+ const identity = await readJson(path.join(actual, 'package.json'));
327
+ const expected = locked.get(directory + '\0' + name);
328
+ const importer = installed.lock.importers[expected.importer];
329
+ const entry = ['dependencies', 'devDependencies', 'optionalDependencies'].map(section => importer?.[section]?.[name]).find(Boolean);
330
+ if (!isDeepStrictEqual(entry, expected.entry) || !isDeepStrictEqual(
331
+ installed.lock.packages[name + '@' + expected.version]?.resolution, expected.resolution
332
+ )) throw Error('pnpm installed lock receipt does not match the distribution lock for ' + name);
333
+ // Isolated pnpm stores may live on a separate volume. Their configured
334
+ // physical identity and installed lock must agree with pnpm's receipt.
335
+ // Hoisted packages remain in this workspace's real modules directories.
336
+ let contained = inside(installed.store, actual);
337
+ if (installed.nodeLinker === 'hoisted') {
338
+ contained = inside(installed.modules, actual);
339
+ if (!contained) {
340
+ const localModules = path.join(directory, 'node_modules');
341
+ if (inside(localModules, actual)) {
342
+ await physicalInstallationDirectory(localModules);
343
+ contained = true;
344
+ }
345
+ }
346
+ }
347
+ if (
348
+ identity.name !== name ||
349
+ identity.version !== expected.version || !contained
350
+ )
351
+ throw Error('Registry installation does not match the distribution lock for ' + name);
352
+ } else if (actual !== profile.links[name])
353
+ throw Error(`${manifest.name} resolves ${name} from ${actual}, expected ${profile.links[name]}`);
354
+ verified++;
355
+ }
356
+ }
357
+ if (profile) for (const [name, directory] of Object.entries(profile.links)) {
358
+ if (!inside(profile.source, directory) || await fs.realpath(directory) !== directory)
359
+ throw Error('Invalid Omnitron SDK package location for ' + name);
360
+ const manifest = frameworkPackage(name, directory, profile.versions?.[name]);
361
+ for (const dependency of frameworkDependencies(manifest)) {
362
+ const actual = await fs.realpath(path.join(directory, 'node_modules', dependency));
363
+ if (actual !== profile.links[dependency])
364
+ throw Error(`${name} resolves ${dependency} from ${actual}, expected ${profile.links[dependency]}`);
365
+ }
366
+ }
367
+ return verified;
368
+ }
369
+ async function frozen(root) {
370
+ await registryBaseline(root, await distributionLock(root));
371
+ await fs.rm(path.join(statePath(root), 'install.json'), { force: true });
372
+ await command('pnpm', ['install', '--force', '--frozen-lockfile', '--config.confirm-modules-purge=false'], root);
373
+ }
374
+
375
+ async function build(source, names, signal) {
376
+ if (!names.length) return;
377
+ const dir = await prepareState(source);
378
+ await exclusively(
379
+ dir,
380
+ () =>
381
+ command(
382
+ 'pnpm',
383
+ ['exec', 'turbo', 'run', 'build', ...names.map((n) => '--filter=' + n + '...')],
384
+ source,
385
+ false,
386
+ signal
387
+ ),
388
+ signal
389
+ );
390
+ }
391
+
392
+ async function profileStatus(root) {
393
+ const active = await optionalJson(path.join(statePath(root), 'active.json'));
394
+ if (active && (active.schema !== 1 || active.root !== root || typeof active.source !== 'string'))
395
+ throw Error('Invalid local SDK selection');
396
+ return active;
397
+ }
398
+
399
+ async function requireApplicationRuntime(client, refresh = false) {
400
+ const { assertExecutionCapabilities } = await import('@omnitron-dev/omnitron/operations');
401
+ const status = await client.status();
402
+ assertExecutionCapabilities(status.execution?.ownerCapabilities,
403
+ { applicationRuntimeResolution: 1, ...(refresh && { sourceRefresh: 1 }) }, 'Development SDK activation');
404
+ if (status.execution.connection?.status !== 'connected')
405
+ throw Error('Development SDK activation requires a current execution owner observation; restore the owner connection first');
406
+ const runtime = status.execution.ownerRuntime;
407
+ if (!runtime || ['node', 'abi', 'platform', 'arch'].some((key) => typeof runtime[key] !== 'string' || !runtime[key]))
408
+ throw Error('Execution owner did not report its Node runtime. Prepare an owner transition before switching the development SDK');
409
+ if (runtime.abi !== process.versions.modules || runtime.platform !== process.platform || runtime.arch !== process.arch)
410
+ throw Error(`Development SDK Node ${process.version} (ABI ${process.versions.modules}, ${process.platform}/${process.arch}) ` +
411
+ `is incompatible with owner Node ${runtime.node} (ABI ${runtime.abi}, ${runtime.platform}/${runtime.arch}). ` +
412
+ 'Run omnitron sdk with the owner-compatible Node before installing or building native dependencies');
413
+ }
414
+
415
+ async function localStack(root, stack) {
416
+ const { createDaemonClient, LONG_REQUEST_TIMEOUT } = await import('@omnitron-dev/omnitron/operations');
417
+ const client = createDaemonClient(undefined, LONG_REQUEST_TIMEOUT);
418
+ try {
419
+ const projects = await client.service('OmnitronProject');
420
+ const candidates = [];
421
+ for (const p of await projects.listProjects())
422
+ if ((await fs.realpath(p.path).catch(() => null)) === root) candidates.push(p.name);
423
+ if (candidates.length !== 1) throw Error('Expected exactly one registered project for ' + root);
424
+ const state = await projects.getStack({ project: candidates[0], stack });
425
+ if (state.type !== 'local' || state.selectedRelease || state.config.watch === false)
426
+ throw Error('SDK refresh requires an unpinned local development stack with watching enabled');
427
+ await requireApplicationRuntime(client, true);
428
+ return { client, state, project: candidates[0] };
429
+ } catch (error) {
430
+ await client.disconnect();
431
+ throw error;
432
+ }
433
+ }
434
+
435
+ async function requireStopped(root, stack) {
436
+ const socket = path.join(process.env.OMNITRON_HOME ?? path.join(os.homedir(), '.omnitron'), 'daemon.sock');
437
+ if (
438
+ !stack &&
439
+ !(await fs
440
+ .lstat(socket)
441
+ .then(() => true)
442
+ .catch((error) => {
443
+ if (error.code === 'ENOENT') return false;
444
+ throw error;
445
+ }))
446
+ )
447
+ return;
448
+ const { createDaemonClient, LONG_REQUEST_TIMEOUT } = await import('@omnitron-dev/omnitron/operations');
449
+ const client = createDaemonClient(undefined, LONG_REQUEST_TIMEOUT);
450
+ try {
451
+ const service = await client.service('OmnitronProject');
452
+ let found = false;
453
+ let sourceStack = false;
454
+ for (const project of await service.listProjects()) {
455
+ if ((await fs.realpath(project.path).catch(() => null)) !== root) continue;
456
+ for (const active of await service.listStacks({ project: project.name })) {
457
+ if (active.type !== 'local' || active.selectedRelease) continue;
458
+ sourceStack = true;
459
+ if (active.name === stack) found = true;
460
+ if (active.apps.some((app) => !['stopped', 'offline', 'not-started'].includes(app.status)))
461
+ throw Error(`Stop ${project.name}/${active.name} before switching its working-tree dependencies`);
462
+ }
463
+ }
464
+ if (stack && !found) throw Error('No local source stack named ' + stack + ' is registered for this checkout');
465
+ if (sourceStack) await requireApplicationRuntime(client);
466
+ } finally {
467
+ await client.disconnect();
468
+ }
469
+ }
470
+
471
+ function dependsOn(manifest, changed, packages, seen = new Set()) {
472
+ for (const name of Object.keys({
473
+ ...manifest.dependencies,
474
+ ...manifest.peerDependencies,
475
+ ...manifest.optionalDependencies,
476
+ })) {
477
+ if (changed.has(name)) return true;
478
+ if (seen.has(name)) continue;
479
+ seen.add(name);
480
+ const pkg = packages.get(name);
481
+ if (pkg && dependsOn(pkg.manifest, changed, packages, seen)) return true;
482
+ }
483
+ return false;
484
+ }
485
+ function applicationBuildIdentity(manifest, hashes, packages) {
486
+ // A local tool can execute a package declared as a development dependency.
487
+ // SDK package traversal still follows its normal runtime dependency graph.
488
+ const application = { ...manifest, dependencies: { ...manifest.devDependencies, ...manifest.dependencies } };
489
+ const dependencies = Object.entries(hashes)
490
+ .filter(([name]) => dependsOn(application, new Set([name]), packages))
491
+ .sort(([left], [right]) => left.localeCompare(right));
492
+ return dependencies.length ? sha256(JSON.stringify(dependencies)) : null;
493
+ }
494
+
495
+ async function refresh(root, stack, checkpoint, packages, save, signal) {
496
+ const { client, state, project } = await localStack(root, stack);
497
+ try {
498
+ const scopeName = project + '/' + stack;
499
+ const previous = checkpoint.applications[scopeName];
500
+ const applied = previous && typeof previous === 'object' && !Array.isArray(previous) ? previous : {};
501
+ checkpoint.applications[scopeName] = applied;
502
+ const inactive = new Set(['stopped', 'offline', 'not-started']);
503
+ // Reject known unavailable applications before refreshing any sibling.
504
+ for (const app of state.apps) {
505
+ if (inactive.has(app.status)) continue;
506
+ if (app.status !== 'online')
507
+ throw Error(`SDK refresh was not applied to ${scopeName}/${app.name}: application is ${app.status}`);
508
+ if (typeof app.handleKey !== 'string' || !app.handleKey)
509
+ throw Error(`SDK refresh was not applied to ${scopeName}/${app.name}: application has no owned handle`);
510
+ }
511
+ const membership = apps => JSON.stringify(apps.map(app => [app.name, app.handleKey, app.status])
512
+ .sort(([left], [right]) => String(left).localeCompare(String(right))));
513
+ const admittedMembership = membership(state.apps);
514
+ const confirmedSources = new Map();
515
+ const activeNames = new Set(state.apps.filter(app => !inactive.has(app.status)).map(app => app.handleKey));
516
+ for (const name of Object.keys(applied)) if (!activeNames.has(name)) delete applied[name];
517
+ await save();
518
+ const watched = await client.getWatchStatus();
519
+ if (activeNames.size && (!watched.enabled || !watched.watching))
520
+ throw Error('SDK refresh was not applied: ' + (watched.reason ?? 'application watching is unavailable'));
521
+ let confirmed = 0;
522
+ let skipped = 0;
523
+ for (const app of state.apps) {
524
+ signal?.throwIfAborted();
525
+ if (inactive.has(app.status)) {
526
+ console.log(`Inactive ${scopeName}/${app.name}: no SDK application receipt`);
527
+ skipped++;
528
+ continue;
529
+ }
530
+ const info = watched.apps.find((entry) => entry.name === app.handleKey);
531
+ if (!info) throw Error(`SDK refresh was not applied to ${app.handleKey}: the application has no active watcher. Check its watch policy and owner diagnostics`);
532
+ if (typeof info.generation !== 'string' || !info.generation || !Number.isSafeInteger(info.definitionRevision) || info.definitionRevision < 0)
533
+ throw Error(`Execution owner did not report a source binding for ${app.handleKey}`);
534
+ const directory = await fs.realpath(info.directory).catch(() => null);
535
+ if (!directory || !inside(root, directory)) throw Error(`Watched source for ${app.handleKey} is outside the selected project or unavailable`);
536
+ const pkg = [...packages.values()]
537
+ .filter((p) => inside(p.directory, directory))
538
+ .sort((a, b) => b.directory.length - a.directory.length)[0];
539
+ if (!pkg) throw Error(`No workspace package owns the watched source of ${app.handleKey}`);
540
+ const buildIdentity = applicationBuildIdentity(pkg.manifest, checkpoint.hashes, packages);
541
+ if (!buildIdentity) {
542
+ delete applied[app.handleKey];
543
+ await save();
544
+ confirmedSources.set(app.handleKey, info);
545
+ console.log(`Unaffected ${app.handleKey}: no dependency on the selected SDK`);
546
+ continue;
547
+ }
548
+ const current = await client.service('OmnitronProject').then((s) => s.getStack({ project, stack }));
549
+ const latestWatch = await client.getWatchStatus();
550
+ if (
551
+ !latestWatch.enabled ||
552
+ !latestWatch.watching ||
553
+ !latestWatch.apps.some((w) => w.name === app.handleKey && w.directory === info.directory &&
554
+ w.generation === info.generation && w.definitionRevision === info.definitionRevision)
555
+ )
556
+ throw Error(`Watch policy or source binding changed before refreshing ${app.handleKey}; SDK readiness is not confirmed`);
557
+ if (
558
+ current.selectedRelease ||
559
+ current.type !== 'local' ||
560
+ current.config.watch === false ||
561
+ !current.apps.some((a) => a.handleKey === app.handleKey && a.status === 'online' && a.generation === info.generation)
562
+ )
563
+ throw Error(`Stack changed before refreshing ${app.handleKey}; SDK readiness is not confirmed`);
564
+ const receipt = applied[app.handleKey];
565
+ if (receipt?.generation === info.generation && receipt.definitionRevision === info.definitionRevision &&
566
+ receipt.directory === directory && receipt.buildIdentity === buildIdentity) {
567
+ confirmedSources.set(app.handleKey, info);
568
+ confirmed++;
569
+ continue;
570
+ }
571
+ const refreshed = await client.restartAfterChange({ name: app.handleKey,
572
+ generation: info.generation, definitionRevision: info.definitionRevision });
573
+ const source = refreshed?.source;
574
+ if (refreshed?.process?.name !== app.handleKey || refreshed.process.status !== 'online' ||
575
+ typeof refreshed.process.generation !== 'string' || !refreshed.process.generation ||
576
+ refreshed.process.generation === info.generation || source?.name !== app.handleKey ||
577
+ source.generation !== refreshed.process.generation || source.directory !== info.directory ||
578
+ !Number.isSafeInteger(source.definitionRevision) || source.definitionRevision < 0)
579
+ throw Error(`Execution owner did not confirm a new online generation for ${app.handleKey}`);
580
+ applied[app.handleKey] = { generation: source.generation, definitionRevision: source.definitionRevision,
581
+ directory, buildIdentity };
582
+ // Persist each exact owner acknowledgement so a later application's
583
+ // failure does not cause successful applications to restart again.
584
+ await save();
585
+ confirmedSources.set(app.handleKey, source);
586
+ confirmed++;
587
+ console.log('Refreshed ' + app.handleKey);
588
+ }
589
+ signal?.throwIfAborted();
590
+ await requireApplicationRuntime(client, true);
591
+ const finalState = await client.service('OmnitronProject').then(service => service.getStack({ project, stack }));
592
+ const finalWatch = await client.getWatchStatus();
593
+ if (finalState.selectedRelease || finalState.type !== 'local' || finalState.config.watch === false ||
594
+ membership(finalState.apps) !== admittedMembership || finalWatch.enabled !== watched.enabled)
595
+ throw Error(`Stack changed while confirming ${scopeName}; SDK readiness is not confirmed`);
596
+ for (const [name, source] of confirmedSources) {
597
+ if (!finalWatch.enabled || !finalWatch.watching ||
598
+ !finalState.apps.some(app => app.handleKey === name && app.status === 'online' && app.generation === source.generation) ||
599
+ !finalWatch.apps.some(app => app.name === name && app.directory === source.directory &&
600
+ app.generation === source.generation && app.definitionRevision === source.definitionRevision))
601
+ throw Error(`Source binding changed while confirming ${name}; SDK readiness is not confirmed`);
602
+ }
603
+ await save();
604
+ console.log(`Development SDK refresh confirmed for ${scopeName}: ${confirmed} applications, ${skipped} inactive`);
605
+ } finally {
606
+ await client.disconnect();
607
+ }
608
+ }
609
+
610
+ async function buildDigest(directory) {
611
+ const root = path.join(directory, 'dist'),
612
+ hash = createHash('sha256');
613
+ async function visit(relative) {
614
+ const current = path.join(root, relative);
615
+ const stat = await fs.lstat(current);
616
+ if (!stat.isDirectory() || stat.isSymbolicLink())
617
+ throw Error('SDK build output must be a physical directory: ' + current);
618
+ const entries = await fs.readdir(current, { withFileTypes: true });
619
+ for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
620
+ const file = path.join(relative, e.name);
621
+ if (e.isDirectory()) await visit(file);
622
+ // Declarations and static assets are build inputs too. A type-only SDK
623
+ // change must invalidate a consumer's cached compiler result.
624
+ else if (e.isFile()) {
625
+ hash
626
+ .update(file)
627
+ .update('\0')
628
+ .update(
629
+ createHash('sha256')
630
+ .update(await fs.readFile(path.join(root, file)))
631
+ .digest()
632
+ );
633
+ } else throw Error('Unsupported SDK build output entry (expected a regular file or directory): ' + path.join(root, file));
634
+ }
635
+ }
636
+ await visit('');
637
+ return hash.digest('hex');
638
+ }
639
+
640
+ async function checkpointSdkBuild(profile, names, stack, signal, packages) {
641
+ const checkpoint = path.join(statePath(profile.root), 'built.json');
642
+ const prior = await optionalJson(checkpoint);
643
+ const hashes = prior?.source === profile.source ? prior.hashes : {};
644
+ const next = Object.fromEntries(Object.entries(hashes).filter(([name]) => Object.hasOwn(profile.links, name)));
645
+ const changed = new Set();
646
+ for (const name of names) {
647
+ next[name] = await buildDigest(profile.links[name]);
648
+ if (hashes[name] !== next[name]) changed.add(name);
649
+ }
650
+ signal?.throwIfAborted();
651
+ const nextCheckpoint = { schema: 2, source: profile.source, hashes: next,
652
+ applications: prior?.schema === 2 && prior.source === profile.source && prior.applications &&
653
+ typeof prior.applications === 'object' && !Array.isArray(prior.applications) ? prior.applications : {} };
654
+ const sourceState = await prepareState(profile.source);
655
+ const save = () => exclusively(sourceState, async () => {
656
+ // Other consumers can build the same SDK while this consumer refreshes.
657
+ // Recheck under the existing build mutex, without holding it across RPC.
658
+ for (const [name, digest] of Object.entries(next)) {
659
+ if (await buildDigest(profile.links[name]) !== digest)
660
+ throw Error(`SDK output changed while confirming ${name}; application readiness was not recorded. Run sdk dev again`);
661
+ }
662
+ signal?.throwIfAborted();
663
+ await atomic(checkpoint, JSON.stringify(nextCheckpoint) + '\n');
664
+ }, signal);
665
+ // A completed build is a separate fact from any application's loaded modules.
666
+ // Build-only use and another stack's receipt cannot certify this stack.
667
+ await save();
668
+ if (stack) {
669
+ packages ??= new Map([...(await inventory(profile.source)), ...(await inventory(profile.root))]);
670
+ await refresh(profile.root, stack, nextCheckpoint, packages, save, signal);
671
+ } else {
672
+ console.log(changed.size ? 'Development SDK build ready: ' + [...changed].join(', ') : 'Development SDK build is current');
673
+ }
674
+ }
675
+
676
+ export async function watchSdk(profile, stack, signal) {
677
+ if (stack) {
678
+ const { client } = await localStack(profile.root, stack);
679
+ await client.disconnect();
680
+ }
681
+ const packages = new Map([...(await inventory(profile.source)), ...(await inventory(profile.root))]);
682
+ const sdkPackages = new Map([...packages].filter(([name]) => Object.hasOwn(profile.links, name)));
683
+ const pending = new Set(Object.keys(profile.links));
684
+ const handles = [];
685
+ let timer, running;
686
+ async function rebuild() {
687
+ const edits = new Set(pending);
688
+ pending.clear();
689
+ const names = [...sdkPackages]
690
+ .filter(([name, pkg]) => edits.has(name) || dependsOn(pkg.manifest, edits, sdkPackages))
691
+ .map(([name]) => name);
692
+ await build(profile.source, names, signal);
693
+ await verifyLinks(profile.root, profile);
694
+ await checkpointSdkBuild(profile, names, stack, signal, packages);
695
+ }
696
+ const flush = async () => {
697
+ if (running || signal.aborted || !pending.size) return;
698
+ const attempted = [...pending];
699
+ running = rebuild();
700
+ let replay = true;
701
+ try {
702
+ await running;
703
+ } catch (error) {
704
+ if (!signal.aborted) {
705
+ replay = pending.size > 0;
706
+ for (const name of attempted) pending.add(name);
707
+ console.error('SDK build or refresh failed:', error.message);
708
+ }
709
+ } finally {
710
+ running = undefined;
711
+ if (!signal.aborted && pending.size && replay) queueMicrotask(flush);
712
+ }
713
+ };
714
+ function schedule(names) {
715
+ for (const name of names) pending.add(name);
716
+ clearTimeout(timer);
717
+ timer = setTimeout(flush, 150);
718
+ }
719
+ try {
720
+ for (const [name, dir] of Object.entries(profile.links)) {
721
+ handles.push(watch(path.join(dir, 'src'), { recursive: true }, () => schedule([name])));
722
+ handles.push(
723
+ watch(dir, (_event, file) => {
724
+ if (file && /^(?:package\.json|tsconfig.*\.json|(?:tsup|vite)\.config\..+)$/.test(String(file)))
725
+ schedule([name]);
726
+ })
727
+ );
728
+ }
729
+ handles.push(
730
+ watch(profile.source, (_event, file) => {
731
+ if (['pnpm-lock.yaml', 'pnpm-workspace.yaml', 'turbo.json', 'tsconfig.json'].includes(String(file)))
732
+ schedule(Object.keys(profile.links));
733
+ })
734
+ );
735
+ console.log('Watching SDK sources at ' + profile.source);
736
+ void flush();
737
+ if (!signal.aborted)
738
+ await new Promise((resolve, reject) => {
739
+ signal.addEventListener('abort', resolve, { once: true });
740
+ for (const handle of handles) handle.once('error', reject);
741
+ });
742
+ } finally {
743
+ clearTimeout(timer);
744
+ for (const handle of handles) handle.close();
745
+ await running?.catch(() => {});
746
+ }
747
+ }
748
+
749
+ export async function inspectSdk(projectDirectory = process.cwd()) {
750
+ const root = await fs.realpath(projectDirectory);
751
+ const dir = statePath(root);
752
+ const active = await profileStatus(root);
753
+ const distribution = await distributionLock(root);
754
+ const selected = await optionalJson(path.join(dir, 'install.json'));
755
+ if (JSON.stringify(selected) !== JSON.stringify(active))
756
+ throw Error('An SDK switch was interrupted. Run sdk dev to complete it, or sdk registry if a current registry lock is available.');
757
+ return active
758
+ ? {
759
+ mode: 'development',
760
+ source: active.source,
761
+ verifiedImports: await verifyLinks(root, active),
762
+ distributionLock: distribution === null ? 'absent' : applicationDocument(distribution) === null ? 'manager-only' : 'present',
763
+ distributionLockChanged: lockIdentity(distribution) !== active.lockSha256,
764
+ }
765
+ : { mode: 'registry', verifiedImports: await verifyLinks(root, null) };
766
+ }
767
+
768
+ export async function sdkCommand(argv) {
769
+ const { values, positionals } = parseArgs({
770
+ args: argv,
771
+ allowPositionals: true,
772
+ options: {
773
+ 'project-dir': { type: 'string' },
774
+ stack: { type: 'string' },
775
+ 'no-watch': { type: 'boolean' },
776
+ json: { type: 'boolean' },
777
+ help: { type: 'boolean', short: 'h' },
778
+ },
779
+ });
780
+ const [action, sourceArg] = positionals;
781
+ if (values.help || !action) {
782
+ console.log(help);
783
+ return;
784
+ }
785
+ if (!['init', 'dev', 'status', 'registry'].includes(action) || positionals.length > (action === 'dev' ? 2 : 1))
786
+ throw Error(help);
787
+ const root = await fs.realpath(values['project-dir'] ?? process.cwd()),
788
+ dir = statePath(root);
789
+ const bridge = path.join(root, '.pnpmfile.mjs');
790
+ const template = await fs.readFile(new URL('./pnpmfile-sdk.js', import.meta.url), 'utf8');
791
+ if (action === 'init') {
792
+ for (const existing of ['.pnpmfile.cjs', '.pnpmfile.js']) {
793
+ if (
794
+ await fs.stat(path.join(root, existing)).then(
795
+ () => true,
796
+ (error) => {
797
+ if (error.code === 'ENOENT') return false;
798
+ throw error;
799
+ }
800
+ )
801
+ )
802
+ throw Error('An existing pnpm hook owns ' + existing + ' in ' + root);
803
+ }
804
+ try {
805
+ await fs.writeFile(bridge, template, { flag: 'wx' });
806
+ } catch (error) {
807
+ if (error.code !== 'EEXIST') throw error;
808
+ const current = await fs.readFile(bridge, 'utf8');
809
+ if (previousBridgeHashes.has(sha256(current))) await atomic(bridge, template);
810
+ else if (current !== template)
811
+ throw Error(
812
+ 'An existing pnpm hook owns ' +
813
+ bridge +
814
+ '. Preserve that hook and integrate the SDK selection before enabling development.'
815
+ );
816
+ }
817
+ console.log('Commit the pnpm hook. Select a local SDK with omnitron sdk dev, or install published dependencies with pnpm install.');
818
+ console.log('Include .omnitron/dev-sdk/*.json in build cache inputs (Turbo: globalDependencies).');
819
+ return;
820
+ }
821
+ const distribution = await distributionLock(root);
822
+ if (action === 'status') {
823
+ console.log(JSON.stringify(await inspectSdk(root), null, 2));
824
+ return;
825
+ }
826
+ if (action === 'dev' && (await fs.readFile(bridge, 'utf8').catch(() => null)) !== template)
827
+ throw Error('Run omnitron sdk init to install or upgrade the project bridge, then commit the hook');
828
+ await prepareState(root);
829
+ await exclusively(dir, async () => {
830
+ const previous = await profileStatus(root);
831
+ const selection = path.join(dir, 'active.json');
832
+ const selected = await optionalJson(path.join(dir, 'install.json'));
833
+ const interrupted = JSON.stringify(selected) !== JSON.stringify(previous);
834
+ if (action === 'registry') {
835
+ if (!previous && !interrupted) {
836
+ await verifyLinks(root, null);
837
+ return;
838
+ }
839
+ await requireStopped(root, values.stack);
840
+ await registryBaseline(root, distribution);
841
+ try {
842
+ await frozen(root);
843
+ await verifyLinks(root, null);
844
+ await fs.rm(selection, { force: true });
845
+ } catch (error) {
846
+ try {
847
+ if (previous && JSON.stringify(await optionalJson(path.join(dir, 'install.json'))) !== JSON.stringify(selected))
848
+ await installDevelopment(root, previous, lockIdentity(distribution));
849
+ } catch (rollback) {
850
+ throw new AggregateError([error, rollback], 'Registry switch and recovery failed');
851
+ }
852
+ throw error;
853
+ }
854
+ return;
855
+ }
856
+ const source = await fs.realpath(
857
+ sourceArg ?? previous?.source ?? selected?.source ?? path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../..')
858
+ );
859
+ if (inside(root, source) || inside(source, root)) throw Error('Select separate project and SDK workspaces');
860
+ let desired = await desiredProfile(root, source, distribution);
861
+ const controller = new AbortController();
862
+ const stop = () => controller.abort();
863
+ process.once('SIGINT', stop);
864
+ process.once('SIGTERM', stop);
865
+ try {
866
+ if (interrupted || JSON.stringify(previous) !== JSON.stringify(desired)) {
867
+ await requireStopped(root, values.stack);
868
+ let registryAvailable = false;
869
+ if (!previous) {
870
+ try { await registryBaseline(root, distribution); registryAvailable = true; }
871
+ catch { /* A missing or stale distribution is not a rollback target. */ }
872
+ }
873
+ await build(source, Object.keys(desired.links), controller.signal);
874
+ if (!isDeepStrictEqual(await desiredProfile(root, source, distribution), desired))
875
+ throw Error('Consumer or SDK inputs changed while building; run sdk dev again');
876
+ let prepared = distribution;
877
+ let installationStarted = false;
878
+ try {
879
+ prepared = await bootstrapPackageManager(root, distribution);
880
+ desired = { ...desired, lockSha256: lockIdentity(prepared) };
881
+ installationStarted = true;
882
+ await installDevelopment(root, desired);
883
+ await checkpointSdkBuild(desired, Object.keys(desired.links), undefined, controller.signal);
884
+ await atomic(selection, JSON.stringify(desired, null, 2) + '\n');
885
+ if (prepared !== distribution)
886
+ console.log('Accepted pnpm package-manager metadata in the distribution lock; the application dependency graph is unchanged.');
887
+ } catch (error) {
888
+ if (!installationStarted || !previous && !registryAvailable) {
889
+ try { await restoreDistribution(root, distribution, prepared); }
890
+ catch (restore) { throw new AggregateError([error, restore], 'SDK switch failed; a changed distribution lock was preserved'); }
891
+ if (!installationStarted) throw error;
892
+ throw new Error(error.message + '. No verified registry baseline is available; the SDK installation is incomplete. Run sdk dev again to finish it.', { cause: error });
893
+ }
894
+ let recovery;
895
+ try {
896
+ if (previous) await installDevelopment(root, previous, lockIdentity(prepared));
897
+ else if (registryAvailable) { await frozen(root); await verifyLinks(root, null); }
898
+ } catch (rollback) { recovery = rollback; }
899
+ try { await restoreDistribution(root, distribution, prepared); }
900
+ catch (restore) { throw new AggregateError([error, ...(recovery ? [recovery] : []), restore], 'SDK switch failed; a changed distribution lock was preserved'); }
901
+ if (recovery) throw new AggregateError([error, recovery], 'SDK switch and recovery failed');
902
+ throw error;
903
+ }
904
+ } else {
905
+ await verifyLinks(root, desired);
906
+ if (values['no-watch']) {
907
+ if (values.stack) {
908
+ const { client } = await localStack(root, values.stack);
909
+ await client.disconnect();
910
+ }
911
+ // Profile equality says nothing about source or built output. Let the
912
+ // normal Turbo graph decide which tasks need rebuilding on every dev.
913
+ await build(source, Object.keys(desired.links), controller.signal);
914
+ if (!isDeepStrictEqual(await desiredProfile(root, source, distribution), desired))
915
+ throw Error('Consumer or SDK inputs changed while building; run sdk dev again');
916
+ await verifyLinks(root, desired);
917
+ await checkpointSdkBuild(desired, Object.keys(desired.links), values.stack, controller.signal);
918
+ }
919
+ }
920
+ if (!values['no-watch']) await watchSdk(desired, values.stack, controller.signal);
921
+ } finally {
922
+ process.off('SIGINT', stop);
923
+ process.off('SIGTERM', stop);
924
+ }
925
+ });
926
+ }
927
+
928
+ if (process.argv[1] && pathToFileURL(path.resolve(process.argv[1])).href === import.meta.url)
929
+ sdkCommand(process.argv.slice(2)).catch((error) => {
930
+ console.error(error.message);
931
+ process.exitCode = 1;
932
+ });