@runeditrun/rer 0.0.0-stage → 0.1.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 (526) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -2
  3. package/bin/rer.mjs +3 -0
  4. package/cli/README.md +243 -0
  5. package/cli/adapter.mjs +10 -0
  6. package/cli/ai-local.mjs +65 -0
  7. package/cli/archive-path.mjs +52 -0
  8. package/cli/archive-vite.mjs +55 -0
  9. package/cli/archive.mjs +8 -0
  10. package/cli/auth-copy.mjs +80 -0
  11. package/cli/bounded.mjs +70 -0
  12. package/cli/cache-doctor.mjs +63 -0
  13. package/cli/container-transfer.mjs +35 -0
  14. package/cli/container.mjs +62 -0
  15. package/cli/contract.mjs +25 -0
  16. package/cli/dependencies.mjs +45 -0
  17. package/cli/dependency-approval.mjs +33 -0
  18. package/cli/dependency-doctor.mjs +34 -0
  19. package/cli/dependency-input.mjs +43 -0
  20. package/cli/deploy-config.mjs +118 -0
  21. package/cli/deploy.mjs +37 -0
  22. package/cli/dev-identity.mjs +80 -0
  23. package/cli/errors.mjs +14 -0
  24. package/cli/export.mjs +42 -0
  25. package/cli/font-cache.mjs +29 -0
  26. package/cli/hosted-output.mjs +110 -0
  27. package/cli/independent.mjs +29 -0
  28. package/cli/inspect.mjs +174 -0
  29. package/cli/jsonc.mjs +111 -0
  30. package/cli/lifecycle.mjs +148 -0
  31. package/cli/local.mjs +100 -0
  32. package/cli/main.mjs +57 -0
  33. package/cli/managed-server.mjs +143 -0
  34. package/cli/node-runtime-cache.mjs +68 -0
  35. package/cli/node-runtime-discovery.mjs +56 -0
  36. package/cli/node-runtime-download.mjs +59 -0
  37. package/cli/node-runtime.mjs +50 -0
  38. package/cli/output.mjs +22 -0
  39. package/cli/parse.mjs +77 -0
  40. package/cli/port.mjs +52 -0
  41. package/cli/portability-worker.mjs +56 -0
  42. package/cli/portability.mjs +66 -0
  43. package/cli/preview-cache.mjs +99 -0
  44. package/cli/project.mjs +48 -0
  45. package/cli/release.mjs +24 -0
  46. package/cli/runtime-front-door.mjs +46 -0
  47. package/cli/scheduled.mjs +81 -0
  48. package/cli/server-activity.mjs +12 -0
  49. package/cli/server-proxy.mjs +93 -0
  50. package/cli/server-registry.mjs +177 -0
  51. package/cli/server-resources.mjs +22 -0
  52. package/cli/settings.mjs +15 -0
  53. package/cli/setup-answers.mjs +29 -0
  54. package/cli/setup-input.mjs +113 -0
  55. package/cli/shared.mjs +7 -0
  56. package/cli/transfers.mjs +33 -0
  57. package/cli/version.mjs +36 -0
  58. package/composition/git-source.mjs +111 -0
  59. package/edition-authoring/files.mjs +81 -0
  60. package/edition-authoring/manifest.mjs +65 -0
  61. package/global/README.md +155 -0
  62. package/global/checkout.mjs +72 -0
  63. package/global/cli/command.mjs +13 -0
  64. package/global/cli/install.mjs +75 -0
  65. package/global/cli/layout.mjs +45 -0
  66. package/global/cli/status.mjs +54 -0
  67. package/global/cli/uninstall.mjs +36 -0
  68. package/global/delegate.mjs +13 -0
  69. package/global/launch/capacity.mjs +72 -0
  70. package/global/launch/dev.mjs +129 -0
  71. package/global/launch/facts.mjs +93 -0
  72. package/global/launch/failure.mjs +5 -0
  73. package/global/launch/local-defaults.mjs +4 -0
  74. package/global/launch/prepare.mjs +61 -0
  75. package/global/launch/render.mjs +44 -0
  76. package/global/launch/state.mjs +97 -0
  77. package/global/launch/steps.mjs +81 -0
  78. package/global/launch/worker.mjs +43 -0
  79. package/global/list.mjs +59 -0
  80. package/global/main.mjs +53 -0
  81. package/global/package-info.mjs +38 -0
  82. package/global/registry.mjs +24 -0
  83. package/global/rer.mjs +29 -0
  84. package/global/update/archive.mjs +89 -0
  85. package/global/update/command.mjs +90 -0
  86. package/global/update/latest.mjs +6 -0
  87. package/global/update/notice.mjs +58 -0
  88. package/global/update/registry.mjs +47 -0
  89. package/global/version.mjs +29 -0
  90. package/install.sh +144 -0
  91. package/modules/ai/LICENSE +21 -0
  92. package/modules/ai/README.md +90 -0
  93. package/modules/ai/client.mjs +71 -0
  94. package/modules/ai/config.mjs +39 -0
  95. package/modules/ai/dev/index.d.mts +6 -0
  96. package/modules/ai/dev/index.mjs +64 -0
  97. package/modules/ai/dev/outbox.mjs +33 -0
  98. package/modules/ai/errors.mjs +28 -0
  99. package/modules/ai/fake/index.d.mts +3 -0
  100. package/modules/ai/fake/index.mjs +82 -0
  101. package/modules/ai/index.d.mts +10 -0
  102. package/modules/ai/index.mjs +3 -0
  103. package/modules/ai/package.json +6 -0
  104. package/modules/container/README.md +156 -0
  105. package/modules/container/adapters/d1.mjs +34 -0
  106. package/modules/container/adapters/egress.mjs +40 -0
  107. package/modules/container/adapters/jobs.mjs +47 -0
  108. package/modules/container/adapters/objects.mjs +84 -0
  109. package/modules/container/adapters/retained.mjs +14 -0
  110. package/modules/container/adapters/sqlite-error.mjs +7 -0
  111. package/modules/container/adapters/sqlite-worker.mjs +26 -0
  112. package/modules/container/assets.mjs +14 -0
  113. package/modules/container/bindings.mjs +1 -0
  114. package/modules/container/boundary.mjs +38 -0
  115. package/modules/container/build.mjs +65 -0
  116. package/modules/container/deploy-input.mjs +17 -0
  117. package/modules/container/entrypoint.sh +5 -0
  118. package/modules/container/host.mjs +43 -0
  119. package/modules/container/ingress.mjs +23 -0
  120. package/modules/container/lock.mjs +11 -0
  121. package/modules/container/migrations.mjs +16 -0
  122. package/modules/container/node-engine.mjs +39 -0
  123. package/modules/container/portability-schema.mjs +14 -0
  124. package/modules/container/remote/deploy.py +76 -0
  125. package/modules/container/remote/files.py +275 -0
  126. package/modules/container/remote/resources.py +157 -0
  127. package/modules/container/runner.mjs +10 -0
  128. package/modules/container/ssh-plan.mjs +44 -0
  129. package/modules/container/ssh.mjs +63 -0
  130. package/modules/container/transfer.mjs +27 -0
  131. package/modules/container/worker-engine.mjs +32 -0
  132. package/modules/dependencies/README.md +211 -0
  133. package/modules/dependencies/approval.mjs +51 -0
  134. package/modules/dependencies/auth.mjs +40 -0
  135. package/modules/dependencies/authority.mjs +14 -0
  136. package/modules/dependencies/crypto.mjs +13 -0
  137. package/modules/dependencies/index.d.mts +23 -0
  138. package/modules/dependencies/index.mjs +113 -0
  139. package/modules/dependencies/logout.mjs +42 -0
  140. package/modules/dependencies/native.mjs +9 -0
  141. package/modules/dependencies/protocol.mjs +23 -0
  142. package/modules/dependencies/schema/approval-attempt.sql +3 -0
  143. package/modules/dependencies/schema/dependencies.sql +15 -0
  144. package/modules/dependencies/store.mjs +19 -0
  145. package/modules/deploy-kit/README.md +308 -0
  146. package/modules/deploy-kit/artifact.mjs +47 -0
  147. package/modules/deploy-kit/build-modules.mjs +21 -0
  148. package/modules/deploy-kit/check.mjs +13 -0
  149. package/modules/deploy-kit/cli.mjs +50 -0
  150. package/modules/deploy-kit/container-config.mjs +8 -0
  151. package/modules/deploy-kit/default-stage.mjs +14 -0
  152. package/modules/deploy-kit/deploy.mjs +80 -0
  153. package/modules/deploy-kit/health-request.mjs +86 -0
  154. package/modules/deploy-kit/health.mjs +99 -0
  155. package/modules/deploy-kit/index.mjs +12 -0
  156. package/modules/deploy-kit/local-only.mjs +10 -0
  157. package/modules/deploy-kit/migrations.mjs +48 -0
  158. package/modules/deploy-kit/owner-cli.mjs +63 -0
  159. package/modules/deploy-kit/owner.mjs +297 -0
  160. package/modules/deploy-kit/prepare.mjs +62 -0
  161. package/modules/deploy-kit/provider.mjs +99 -0
  162. package/modules/deploy-kit/queue-consumer.mjs +10 -0
  163. package/modules/deploy-kit/receipt.mjs +16 -0
  164. package/modules/deploy-kit/render.mjs +47 -0
  165. package/modules/deploy-kit/resources.mjs +106 -0
  166. package/modules/deploy-kit/service-grants.mjs +25 -0
  167. package/modules/deploy-kit/sql-safety.mjs +94 -0
  168. package/modules/deploy-kit/sql-tokens.mjs +49 -0
  169. package/modules/deploy-kit/teardown.mjs +100 -0
  170. package/modules/deploy-kit/templates.mjs +31 -0
  171. package/modules/deploy-kit/validation.mjs +58 -0
  172. package/modules/deploy-kit/worker.mjs +105 -0
  173. package/modules/erasure/CONTRACT.md +90 -0
  174. package/modules/erasure/README.md +221 -0
  175. package/modules/erasure/audit.mjs +23 -0
  176. package/modules/erasure/blobs.mjs +45 -0
  177. package/modules/erasure/cascade.mjs +91 -0
  178. package/modules/erasure/completion.mjs +62 -0
  179. package/modules/erasure/declaration.mjs +51 -0
  180. package/modules/erasure/fence.mjs +19 -0
  181. package/modules/erasure/index.d.mts +62 -0
  182. package/modules/erasure/index.mjs +85 -0
  183. package/modules/erasure/order.mjs +56 -0
  184. package/modules/erasure/plan.mjs +121 -0
  185. package/modules/erasure/rate.mjs +57 -0
  186. package/modules/erasure/receipt.mjs +32 -0
  187. package/modules/erasure/root.mjs +21 -0
  188. package/modules/erasure/schema/erasure-rates.sql +5 -0
  189. package/modules/erasure/schema/erasure.sql +27 -0
  190. package/modules/erasure/scope.mjs +29 -0
  191. package/modules/erasure/sql.mjs +23 -0
  192. package/modules/erasure/verification.mjs +69 -0
  193. package/modules/integrations/core/CONTRACT.md +50 -0
  194. package/modules/integrations/core/README.md +27 -0
  195. package/modules/integrations/core/crypto.ts +19 -0
  196. package/modules/integrations/core/database.ts +21 -0
  197. package/modules/integrations/core/delivery.ts +97 -0
  198. package/modules/integrations/core/destination.ts +13 -0
  199. package/modules/integrations/core/index.ts +7 -0
  200. package/modules/integrations/core/node-transport.ts +72 -0
  201. package/modules/integrations/core/public-address.ts +57 -0
  202. package/modules/integrations/core/repository.ts +74 -0
  203. package/modules/integrations/core/schema.sql +36 -0
  204. package/modules/integrations/core/sqlite.ts +20 -0
  205. package/modules/integrations/core/timestamped-body-signature.ts +38 -0
  206. package/modules/integrations/core/tokens.ts +71 -0
  207. package/modules/integrations/core/types.ts +52 -0
  208. package/modules/integrations/core/upgrade-token-usage-context.sql +5 -0
  209. package/modules/integrations/core/webhook.ts +73 -0
  210. package/modules/job-queue/README.md +173 -0
  211. package/modules/job-queue/cli.mjs +64 -0
  212. package/modules/job-queue/command.mjs +64 -0
  213. package/modules/job-queue/config.mjs +48 -0
  214. package/modules/job-queue/identity.mjs +48 -0
  215. package/modules/job-queue/index.mjs +135 -0
  216. package/modules/job-queue/membership.mjs +30 -0
  217. package/modules/job-queue/module.json +10 -0
  218. package/modules/job-queue/policy.mjs +29 -0
  219. package/modules/job-queue/processes.mjs +80 -0
  220. package/modules/job-queue/resources.mjs +37 -0
  221. package/modules/job-queue/storage.mjs +64 -0
  222. package/modules/job-queue/supervisor.mjs +30 -0
  223. package/modules/job-queue/waiting.mjs +16 -0
  224. package/modules/mail/LICENSE +21 -0
  225. package/modules/mail/README.md +336 -0
  226. package/modules/mail/doctor.mjs +81 -0
  227. package/modules/mail/index.mjs +8 -0
  228. package/modules/mail/live-proof/README.md +113 -0
  229. package/modules/mail/live-proof/run.mjs +55 -0
  230. package/modules/mail/package.json +19 -0
  231. package/modules/mail/pnpm-lock.yaml +537 -0
  232. package/modules/mail/src/adapters/cloudflare/README.md +84 -0
  233. package/modules/mail/src/adapters/cloudflare/headers.mjs +63 -0
  234. package/modules/mail/src/adapters/cloudflare/index.mjs +47 -0
  235. package/modules/mail/src/adapters/cloudflare/response.mjs +99 -0
  236. package/modules/mail/src/adapters/messaging/README.md +92 -0
  237. package/modules/mail/src/adapters/messaging/contract.mjs +66 -0
  238. package/modules/mail/src/adapters/messaging/event-schema.mjs +28 -0
  239. package/modules/mail/src/adapters/messaging/index.mjs +61 -0
  240. package/modules/mail/src/adapters/messaging/jws.mjs +68 -0
  241. package/modules/mail/src/adapters/messaging/request.mjs +52 -0
  242. package/modules/mail/src/adapters/messaging/serialize.mjs +21 -0
  243. package/modules/mail/src/adapters/resend/README.md +120 -0
  244. package/modules/mail/src/adapters/resend/index.mjs +22 -0
  245. package/modules/mail/src/adapters/resend/response.mjs +54 -0
  246. package/modules/mail/src/adapters/resend/serialize.mjs +46 -0
  247. package/modules/mail/src/adapters/resend/size.mjs +99 -0
  248. package/modules/mail/src/adapters/sendgrid/README.md +75 -0
  249. package/modules/mail/src/adapters/sendgrid/index.mjs +35 -0
  250. package/modules/mail/src/adapters/sendgrid/serialize.mjs +104 -0
  251. package/modules/mail/src/core/errors.mjs +9 -0
  252. package/modules/mail/src/core/transport.mjs +33 -0
  253. package/modules/mail/src/core/validation.mjs +172 -0
  254. package/modules/mail/src/development/index.d.mts +37 -0
  255. package/modules/mail/src/development/index.mjs +35 -0
  256. package/modules/mail/src/development/outbox.mjs +78 -0
  257. package/modules/mail/src/development/schema.sql +9 -0
  258. package/modules/mail/src/development/viewer-html.mjs +42 -0
  259. package/modules/mail/src/development/viewer.mjs +65 -0
  260. package/modules/mail/src/doctor/README.md +107 -0
  261. package/modules/mail/src/doctor/common.mjs +64 -0
  262. package/modules/mail/src/doctor/dns.mjs +102 -0
  263. package/modules/mail/src/doctor/index.mjs +28 -0
  264. package/modules/mail/src/doctor/probe.mjs +66 -0
  265. package/modules/mail/src/doctor/receive.mjs +81 -0
  266. package/modules/mail/src/doctor/records.mjs +45 -0
  267. package/modules/mail/src/events/README.md +268 -0
  268. package/modules/mail/src/events/d1-schema.sql +25 -0
  269. package/modules/mail/src/events/d1-storage.mjs +76 -0
  270. package/modules/mail/src/events/index.mjs +3 -0
  271. package/modules/mail/src/events/intake.mjs +201 -0
  272. package/modules/mail/src/events/storage.mjs +52 -0
  273. package/modules/mail/src/events/verify.mjs +92 -0
  274. package/modules/mail/src/inbound/README.md +137 -0
  275. package/modules/mail/src/inbound/bytes.mjs +91 -0
  276. package/modules/mail/src/inbound/headers.mjs +143 -0
  277. package/modules/mail/src/inbound/index.mjs +29 -0
  278. package/modules/mail/src/inbound/mime.mjs +36 -0
  279. package/modules/mail/src/inbound/multipart.mjs +27 -0
  280. package/modules/mail/src/inbound/resend-attachment.mjs +43 -0
  281. package/modules/mail/src/inbound/resend.mjs +54 -0
  282. package/modules/mail/src/inbound/sendgrid.mjs +60 -0
  283. package/modules/mail/src/sender.mjs +33 -0
  284. package/modules/mail/src/sendgrid-email/index.d.mts +33 -0
  285. package/modules/mail/src/sendgrid-email/index.mjs +86 -0
  286. package/modules/mail/src/unsubscribe/index.mjs +142 -0
  287. package/modules/operator-auth/README.md +599 -0
  288. package/modules/operator-auth/accounts/adapter.mjs +41 -0
  289. package/modules/operator-auth/accounts/admin.mjs +159 -0
  290. package/modules/operator-auth/accounts/credentials.mjs +27 -0
  291. package/modules/operator-auth/accounts/editor-owner.mjs +13 -0
  292. package/modules/operator-auth/accounts/index.d.mts +9 -0
  293. package/modules/operator-auth/accounts/index.mjs +9 -0
  294. package/modules/operator-auth/accounts/native.d.mts +38 -0
  295. package/modules/operator-auth/accounts/native.mjs +171 -0
  296. package/modules/operator-auth/accounts/oa.mjs +90 -0
  297. package/modules/operator-auth/accounts/provider-control.mjs +50 -0
  298. package/modules/operator-auth/accounts/provider-pin.mjs +14 -0
  299. package/modules/operator-auth/accounts/roles.mjs +26 -0
  300. package/modules/operator-auth/accounts/schema/dependency-setup.sql +17 -0
  301. package/modules/operator-auth/accounts/schema/dev-sessions.sql +22 -0
  302. package/modules/operator-auth/accounts/schema/domain-rules-step-up.sql +20 -0
  303. package/modules/operator-auth/accounts/schema/invitation-cancellation.sql +17 -0
  304. package/modules/operator-auth/accounts/schema/operator-auth.sql +142 -0
  305. package/modules/operator-auth/accounts/schema/provider-control.sql +30 -0
  306. package/modules/operator-auth/accounts/schema/provider-pins.sql +6 -0
  307. package/modules/operator-auth/accounts/settings-page.mjs +4 -0
  308. package/modules/operator-auth/accounts/store.mjs +246 -0
  309. package/modules/operator-auth/branding.mjs +66 -0
  310. package/modules/operator-auth/config.mjs +35 -0
  311. package/modules/operator-auth/conformance/README.md +81 -0
  312. package/modules/operator-auth/conformance/index.d.mts +43 -0
  313. package/modules/operator-auth/conformance/index.mjs +300 -0
  314. package/modules/operator-auth/conformance/presentation.mjs +57 -0
  315. package/modules/operator-auth/crypto.mjs +17 -0
  316. package/modules/operator-auth/dev/badge.mjs +62 -0
  317. package/modules/operator-auth/dev/index.d.mts +14 -0
  318. package/modules/operator-auth/dev/index.mjs +137 -0
  319. package/modules/operator-auth/dev/personas.mjs +17 -0
  320. package/modules/operator-auth/dev/sign-in-as.d.mts +3 -0
  321. package/modules/operator-auth/dev/sign-in-as.mjs +25 -0
  322. package/modules/operator-auth/dev-admission.d.mts +4 -0
  323. package/modules/operator-auth/dev-admission.mjs +10 -0
  324. package/modules/operator-auth/dev-gate.mjs +86 -0
  325. package/modules/operator-auth/docs/README.md +78 -0
  326. package/modules/operator-auth/docs/native-accounts.md +313 -0
  327. package/modules/operator-auth/docs/tanstack-start.md +218 -0
  328. package/modules/operator-auth/docs/vite-spa.md +144 -0
  329. package/modules/operator-auth/errors.mjs +9 -0
  330. package/modules/operator-auth/http.mjs +42 -0
  331. package/modules/operator-auth/identity/admission.mjs +105 -0
  332. package/modules/operator-auth/identity/after-confirm.mjs +18 -0
  333. package/modules/operator-auth/identity/email-link/confirmation.mjs +14 -0
  334. package/modules/operator-auth/identity/email-link/index.d.mts +48 -0
  335. package/modules/operator-auth/identity/email-link/index.mjs +128 -0
  336. package/modules/operator-auth/identity/email-link/message.mjs +16 -0
  337. package/modules/operator-auth/identity/index.d.mts +7 -0
  338. package/modules/operator-auth/identity/index.mjs +5 -0
  339. package/modules/operator-auth/identity/oidc/README.md +171 -0
  340. package/modules/operator-auth/identity/oidc/client-auth.mjs +16 -0
  341. package/modules/operator-auth/identity/oidc/config.mjs +102 -0
  342. package/modules/operator-auth/identity/oidc/crypto.mjs +29 -0
  343. package/modules/operator-auth/identity/oidc/errors.mjs +5 -0
  344. package/modules/operator-auth/identity/oidc/index.d.mts +33 -0
  345. package/modules/operator-auth/identity/oidc/index.mjs +287 -0
  346. package/modules/operator-auth/identity/oidc/jwt.mjs +102 -0
  347. package/modules/operator-auth/identity/oidc/network.mjs +93 -0
  348. package/modules/operator-auth/identity/oidc/presets.mjs +26 -0
  349. package/modules/operator-auth/identity/oidc/schema/oidc.sql +50 -0
  350. package/modules/operator-auth/identity/oidc/store.mjs +68 -0
  351. package/modules/operator-auth/identity/principal.mjs +28 -0
  352. package/modules/operator-auth/index.d.mts +223 -0
  353. package/modules/operator-auth/index.mjs +11 -0
  354. package/modules/operator-auth/input.mjs +55 -0
  355. package/modules/operator-auth/local-cli.mjs +16 -0
  356. package/modules/operator-auth/page.mjs +34 -0
  357. package/modules/operator-auth/policy.mjs +20 -0
  358. package/modules/operator-auth/routes.mjs +91 -0
  359. package/modules/operator-auth/service.mjs +152 -0
  360. package/modules/operator-auth/signin/index.mjs +67 -0
  361. package/modules/owner-claim/README.md +137 -0
  362. package/modules/owner-claim/idp.mjs +40 -0
  363. package/modules/owner-claim/index.d.mts +27 -0
  364. package/modules/owner-claim/index.mjs +39 -0
  365. package/modules/owner-claim/page.mjs +11 -0
  366. package/modules/owner-claim/schema/idp.sql +4 -0
  367. package/modules/owner-claim/schema/owner-claim.sql +18 -0
  368. package/modules/owner-claim/transaction.mjs +87 -0
  369. package/modules/owner-claim/verify.mjs +80 -0
  370. package/modules/portability/README.md +444 -0
  371. package/modules/portability/bridge.mjs +102 -0
  372. package/modules/portability/catalogue.mjs +118 -0
  373. package/modules/portability/cli-options.mjs +38 -0
  374. package/modules/portability/cli.mjs +56 -0
  375. package/modules/portability/copies.mjs +68 -0
  376. package/modules/portability/dev-data.mjs +32 -0
  377. package/modules/portability/diff.mjs +33 -0
  378. package/modules/portability/export.mjs +129 -0
  379. package/modules/portability/files.mjs +65 -0
  380. package/modules/portability/format.mjs +122 -0
  381. package/modules/portability/generated.mjs +60 -0
  382. package/modules/portability/hash.mjs +79 -0
  383. package/modules/portability/http.mjs +42 -0
  384. package/modules/portability/import.mjs +185 -0
  385. package/modules/portability/index.d.mts +58 -0
  386. package/modules/portability/index.mjs +22 -0
  387. package/modules/portability/journal.mjs +49 -0
  388. package/modules/portability/legacy-files.mjs +43 -0
  389. package/modules/portability/legacy-safety.mjs +27 -0
  390. package/modules/portability/lines.mjs +36 -0
  391. package/modules/portability/migration-history.mjs +18 -0
  392. package/modules/portability/migration-statements.mjs +23 -0
  393. package/modules/portability/payloads.mjs +52 -0
  394. package/modules/portability/provider-data.mjs +25 -0
  395. package/modules/portability/restore-safety.mjs +30 -0
  396. package/modules/portability/schema/portability.sql +25 -0
  397. package/modules/portability/storage.mjs +49 -0
  398. package/modules/release/README.md +212 -0
  399. package/modules/release/catalogue.mjs +79 -0
  400. package/modules/release/export.mjs +51 -0
  401. package/modules/release/produce.mjs +136 -0
  402. package/modules/release/public-child-transport-state.cjs +29 -0
  403. package/modules/release/public-child.mjs +71 -0
  404. package/modules/release/public-git.mjs +21 -0
  405. package/modules/release/publication-scan.mjs +22 -0
  406. package/modules/release/publish-handoff.mjs +37 -0
  407. package/modules/release/source.mjs +71 -0
  408. package/modules/release/test-summary.mjs +50 -0
  409. package/onboarding/README.md +163 -0
  410. package/onboarding/activity.mjs +31 -0
  411. package/onboarding/adapter-auth.mjs +88 -0
  412. package/onboarding/adapter-hosting.mjs +35 -0
  413. package/onboarding/adapter-labels.mjs +13 -0
  414. package/onboarding/adapter-settings.mjs +129 -0
  415. package/onboarding/adapter.mjs +238 -0
  416. package/onboarding/agent-handoff.mjs +9 -0
  417. package/onboarding/app-processes.mjs +21 -0
  418. package/onboarding/archive-output.d.mts +3 -0
  419. package/onboarding/archive-output.mjs +54 -0
  420. package/onboarding/command-custody.mjs +24 -0
  421. package/onboarding/command-recovery.mjs +8 -0
  422. package/onboarding/commands.mjs +166 -0
  423. package/onboarding/dependency-approval.mjs +36 -0
  424. package/onboarding/dev-identity.mjs +23 -0
  425. package/onboarding/email.mjs +24 -0
  426. package/onboarding/failures.mjs +42 -0
  427. package/onboarding/input-limits.mjs +7 -0
  428. package/onboarding/installer-inputs.mjs +24 -0
  429. package/onboarding/launcher.mjs +34 -0
  430. package/onboarding/local/build-files.mjs +64 -0
  431. package/onboarding/local/build.mjs +49 -0
  432. package/onboarding/local/environment.mjs +30 -0
  433. package/onboarding/local/files.mjs +35 -0
  434. package/onboarding/local/operation.mjs +166 -0
  435. package/onboarding/local/owner.mjs +34 -0
  436. package/onboarding/local/process-table.mjs +114 -0
  437. package/onboarding/local/recovery.mjs +79 -0
  438. package/onboarding/local/state.mjs +31 -0
  439. package/onboarding/metadata.mjs +7 -0
  440. package/onboarding/operator-install.mjs +45 -0
  441. package/onboarding/operator-installer.mjs +39 -0
  442. package/onboarding/operator-prerequisites.mjs +27 -0
  443. package/onboarding/package.json +6 -0
  444. package/onboarding/prefill.mjs +16 -0
  445. package/onboarding/private-files.mjs +97 -0
  446. package/onboarding/private.mjs +35 -0
  447. package/onboarding/process-cleanup.mjs +134 -0
  448. package/onboarding/process-diagnostics.mjs +91 -0
  449. package/onboarding/process-scope.mjs +118 -0
  450. package/onboarding/process-table.mjs +118 -0
  451. package/onboarding/process-tree.mjs +36 -0
  452. package/onboarding/production-modules.mjs +22 -0
  453. package/onboarding/production-options.mjs +40 -0
  454. package/onboarding/production-plan.mjs +8 -0
  455. package/onboarding/production-runtime.mjs +30 -0
  456. package/onboarding/project-vite.mjs +14 -0
  457. package/onboarding/providers.mjs +289 -0
  458. package/onboarding/queue.mjs +53 -0
  459. package/onboarding/queued-entry.mjs +34 -0
  460. package/onboarding/readiness.mjs +59 -0
  461. package/onboarding/redaction-jwt.mjs +32 -0
  462. package/onboarding/redaction.mjs +298 -0
  463. package/onboarding/schema/SETUP.schema.json +514 -0
  464. package/onboarding/schema/dependencies.mjs +35 -0
  465. package/onboarding/schema/json-schema.mjs +202 -0
  466. package/onboarding/schema/setup-validation.mjs +325 -0
  467. package/onboarding/secrets.mjs +21 -0
  468. package/onboarding/server.mjs +188 -0
  469. package/onboarding/setup-manifest.mjs +116 -0
  470. package/onboarding/state.mjs +46 -0
  471. package/onboarding/theme-files.mjs +37 -0
  472. package/onboarding/theme.mjs +118 -0
  473. package/onboarding/ui/activity.js +98 -0
  474. package/onboarding/ui/address-client.mjs +33 -0
  475. package/onboarding/ui/app.js +377 -0
  476. package/onboarding/ui/index.html +1 -0
  477. package/onboarding/ui/model-core.mjs +127 -0
  478. package/onboarding/ui/model.mjs +5 -0
  479. package/onboarding/ui/style.css +63 -0
  480. package/onboarding/ui/theme.css +37 -0
  481. package/onboarding/ui/workspace-art.js +30 -0
  482. package/onboarding/wrangler-auth.mjs +43 -0
  483. package/operations/README.md +246 -0
  484. package/operations/operations.mjs +168 -0
  485. package/operations/project-operations.mjs +157 -0
  486. package/package.json +11 -4
  487. package/scripts/publish-check/README.md +72 -0
  488. package/scripts/publish-check/checksum_maps.py +52 -0
  489. package/scripts/publish-check/cli.py +34 -0
  490. package/scripts/publish-check/command_arguments.py +51 -0
  491. package/scripts/publish-check/command_shell_quotes.py +41 -0
  492. package/scripts/publish-check/command_source_quotes.py +120 -0
  493. package/scripts/publish-check/content_identities.py +250 -0
  494. package/scripts/publish-check/copy_identities.py +41 -0
  495. package/scripts/publish-check/der.py +61 -0
  496. package/scripts/publish-check/destination.py +117 -0
  497. package/scripts/publish-check/env_values.py +74 -0
  498. package/scripts/publish-check/font_licence.py +23 -0
  499. package/scripts/publish-check/git_history.py +115 -0
  500. package/scripts/publish-check/github_refs.py +217 -0
  501. package/scripts/publish-check/gitleaks.py +72 -0
  502. package/scripts/publish-check/gitleaks.toml +40 -0
  503. package/scripts/publish-check/history_scan.py +176 -0
  504. package/scripts/publish-check/hit_location.py +18 -0
  505. package/scripts/publish-check/hygiene_config.py +42 -0
  506. package/scripts/publish-check/hygiene_text.py +11 -0
  507. package/scripts/publish-check/identity-recipe.mjs +54 -0
  508. package/scripts/publish-check/json_identity.py +79 -0
  509. package/scripts/publish-check/license-exceptions.json +11 -0
  510. package/scripts/publish-check/licenses.py +49 -0
  511. package/scripts/publish-check/policy.py +18 -0
  512. package/scripts/publish-check/project_identity.py +89 -0
  513. package/scripts/publish-check/provenance.py +91 -0
  514. package/scripts/publish-check/provenance_pins.py +33 -0
  515. package/scripts/publish-check/public_base.py +63 -0
  516. package/scripts/publish-check/public_hygiene.py +80 -0
  517. package/scripts/publish-check/publication-config.json +12 -0
  518. package/scripts/publish-check/publication_config.py +34 -0
  519. package/scripts/publish-check/report.py +52 -0
  520. package/scripts/publish-check/repository_commands.py +75 -0
  521. package/scripts/publish-check/repository_intent.py +52 -0
  522. package/scripts/publish-check/reviewed.py +41 -0
  523. package/scripts/publish-check/source_hashes.mjs +6 -0
  524. package/scripts/publish-check/tree_scan.py +88 -0
  525. package/scripts/publish-check/trust_bundle.py +87 -0
  526. package/scripts/publish-check.mjs +58 -0
package/cli/jsonc.mjs ADDED
@@ -0,0 +1,111 @@
1
+ function location(text, position) {
2
+ const lines = text.slice(0, position).split('\n');
3
+ return `position ${position} (line ${lines.length} column ${lines.at(-1).length + 1})`;
4
+ }
5
+
6
+ // Validate JSONC before JSON.parse so diagnostics do not depend on the Node
7
+ // version's error wording. An explicit stack also handles deeply nested input.
8
+ // Only comments and commas accepted by the grammar are masked; offsets stay in
9
+ // the original source and comment removal can never join separate tokens.
10
+ export function parseJsonc(text) {
11
+ const result = text.split(''), stack = [{ kind: 'root', state: 'value' }];
12
+ let index = 0;
13
+ const fail = (message, position = index) => {
14
+ throw new SyntaxError(`${position === text.length ? 'Unexpected end of JSON input' : message} at ${location(text, position)}`);
15
+ };
16
+ const unterminated = (kind, start) => {
17
+ throw new SyntaxError(`Unterminated ${kind} at ${location(text, start)}; close the ${kind}.`);
18
+ };
19
+ const blank = start => {
20
+ for (let i = start; i < index; i++) if (text[i] !== '\n' && text[i] !== '\r') result[i] = ' ';
21
+ };
22
+ const space = () => {
23
+ while (index < text.length) {
24
+ if (' \t\r\n'.includes(text[index])) { index++; continue; }
25
+ const start = index;
26
+ if (text[index] === '/' && text[index + 1] === '/') {
27
+ index += 2;
28
+ while (index < text.length && text[index] !== '\n' && text[index] !== '\r') index++;
29
+ } else if (text[index] === '/' && text[index + 1] === '*') {
30
+ const end = text.indexOf('*/', index + 2);
31
+ if (end === -1) unterminated('block comment', start);
32
+ index = end + 2;
33
+ } else break;
34
+ blank(start);
35
+ }
36
+ };
37
+ const string = () => {
38
+ const start = index++;
39
+ let end = index;
40
+ while (end < text.length && text[end] !== '"') end = Math.min(text.length, end + (text[end] === '\\' ? 2 : 1));
41
+ if (end === text.length) unterminated('string', start);
42
+ while (index < end) {
43
+ if (text.charCodeAt(index) < 32) fail('Unescaped control character in string');
44
+ if (text[index++] !== '\\') continue;
45
+ if (text[index] === 'u') {
46
+ index++;
47
+ for (let digit = 0; digit < 4; digit++, index++) {
48
+ if (index >= end || !/[0-9a-fA-F]/.test(text[index])) fail('Invalid Unicode escape');
49
+ }
50
+ } else {
51
+ if (!'"\\/bfnrt'.includes(text[index])) fail('Invalid string escape');
52
+ index++;
53
+ }
54
+ }
55
+ index++;
56
+ };
57
+ const digit = () => index < text.length && text[index] >= '0' && text[index] <= '9';
58
+ const digits = () => {
59
+ if (!digit()) fail('Expected a digit');
60
+ do { index++; } while (digit());
61
+ };
62
+ const number = () => {
63
+ if (text[index] === '-') index++;
64
+ if (text[index] === '0') index++;
65
+ else digits();
66
+ if (text[index] === '.') { index++; digits(); }
67
+ if (text[index] === 'e' || text[index] === 'E') {
68
+ index++;
69
+ if (text[index] === '+' || text[index] === '-') index++;
70
+ digits();
71
+ }
72
+ };
73
+ while (stack.length) {
74
+ space();
75
+ const frame = stack.at(-1), char = text[index], closing = frame.kind === 'object' ? '}' : ']';
76
+ if (frame.state === 'end') {
77
+ if (index < text.length) fail('Unexpected token after JSON value');
78
+ stack.pop();
79
+ } else if ((frame.state === 'keyOrEnd' || frame.state === 'valueOrEnd' || frame.state === 'comma') && char === closing) {
80
+ index++; stack.pop();
81
+ } else if (frame.state === 'keyOrEnd') {
82
+ if (char !== '"') fail('Expected a property name');
83
+ string(); frame.state = 'colon';
84
+ } else if (frame.state === 'colon') {
85
+ if (char !== ':') fail("Expected ':' after property name");
86
+ index++; frame.state = 'value';
87
+ } else if (frame.state === 'comma') {
88
+ if (char !== ',') fail(`Expected ',' or '${closing}'`);
89
+ const comma = index++;
90
+ space();
91
+ if (text[index] === closing) result[comma] = ' ';
92
+ frame.state = frame.kind === 'object' ? 'keyOrEnd' : 'valueOrEnd';
93
+ } else {
94
+ frame.state = frame.kind === 'root' ? 'end' : 'comma';
95
+ if (char === '{' || char === '[') {
96
+ index++;
97
+ stack.push({ kind: char === '{' ? 'object' : 'array', state: char === '{' ? 'keyOrEnd' : 'valueOrEnd' });
98
+ } else if (char === '"') string();
99
+ else if (char === '-' || digit()) number();
100
+ else {
101
+ const literal = { t: 'true', f: 'false', n: 'null' }[char];
102
+ if (!literal) fail('Unexpected token in JSON value');
103
+ for (const expected of literal) {
104
+ if (text[index] !== expected) fail(`Expected '${literal}'`);
105
+ index++;
106
+ }
107
+ }
108
+ }
109
+ }
110
+ return JSON.parse(result.join(''));
111
+ }
@@ -0,0 +1,148 @@
1
+ import { createHmac } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { dependencyInputs } from './dependency-input.mjs';
4
+ import { linkDependencies } from './dependencies.mjs';
5
+ import { savedSetupAnswers } from './setup-answers.mjs';
6
+ import { mkdtemp, rm } from 'node:fs/promises';
7
+ import { tmpdir } from 'node:os';
8
+ import { join } from 'node:path';
9
+ import { shared } from './shared.mjs';
10
+ import { tools, doctor } from './inspect.mjs';
11
+ import { availablePort, selectedOrigin, setupInput, stateSafety, refuseLegacyProcess } from './local.mjs';
12
+ import { missing, refuse, RerError, lifecycleError } from './errors.mjs';
13
+ import { executeDeclared } from './independent.mjs';
14
+ import { transfer } from './transfers.mjs';
15
+ import { runtimeFrontDoor } from './runtime-front-door.mjs';
16
+ import { localSettingKeys, requiredLocalSettings } from './settings.mjs';
17
+ import { rememberSetupAnswers } from './setup-answers.mjs';
18
+ import { ensureDevKey, readDevKey, refuseDevLayerOutput, openUrl, DEV_KEY_HEADER } from './dev-identity.mjs';
19
+
20
+ export async function runCommand(project, options, out) {
21
+ if(project.profile==='first-party'&&['dev','preview'].includes(options.verb))return (await import('./managed-server.mjs')).managedServer(project,options,out);
22
+ const cancellation = new AbortController(), signal = cancellation.signal;
23
+ let termination, operation, store, temporary, proxy;
24
+ const handlers = new Map(['SIGINT', 'SIGTERM'].map(name => [name, () => { if (!termination) { termination = name; cancellation.abort(name); } }]));
25
+ for (const [name, handler] of handlers) process.on(name, handler);
26
+ try {
27
+ let selected, input;
28
+ if (project.profile === 'first-party') {
29
+ if (project.adapter?.setup) {
30
+ const { loadInstallerSeed } = await shared('setup-manifest.mjs');
31
+ try { project.setupSeed = await loadInstallerSeed(project.root, project.adapter); }
32
+ catch (error) { throw new RerError('invalid_setup_manifest', `The declared setup manifest or presentation is invalid. ${String(error.message).replace(/\.?$/, '.')} Correct or restore its sources before retrying.`, 2); }
33
+ project.setupManifest = project.setupSeed.manifest;
34
+ }
35
+ }
36
+ if (project.profile === 'first-party' && options.verb !== 'doctor') {
37
+ if (!project.adapter?.run) throw refuse('The first-party command adapter is unavailable. Restore the shared lifecycle adapter.');
38
+ if (options.forwarded.length) throw refuse('First-party commands do not accept arbitrary forwarded arguments. Run rer VERB --help.');
39
+ await stateSafety(project);
40
+ if(options.verb==='setup')await refuseLegacyProcess(project);
41
+ if (['setup', 'outbox'].includes(options.verb)) {
42
+ selected = await selectedOrigin(project, options, { setup: options.verb === 'setup' });
43
+ if (options.verb === 'setup') {input = await setupInput(project, options, selected.vars, signal, { origin: selected.url });await dependencyInputs({project,input,origin:selected.url});}
44
+ else if (requiredLocalSettings(project, selected.vars).length) throw missing('Private owner settings are missing. Run rer setup --owner-email EMAIL first.');
45
+ if (options.verb !== 'outbox') await availablePort(selected.host, selected.port, { root: project.root, saved: !selected.explicitPort && selected.savedOrigin !== undefined });
46
+ if (options.verb === 'setup') announcePort(out, options, selected);
47
+ }
48
+ const { localOperation } = await shared('local/operation.mjs');
49
+ if (options.verb !== 'outbox') try { operation = await localOperation(project.root, process.env.RER_LOCAL_OPERATION); } catch (error) { throw refuse(error.message); }
50
+ const { recoverInterrupted } = await shared('local/recovery.mjs');
51
+ if (operation) await recoverInterrupted(project.root, operation);
52
+ }
53
+ const { createCommands } = await shared('commands.mjs');
54
+ const { privateDirectory } = await shared('private-files.mjs');
55
+ // Nested public commands stream into their caller's capture and keep their
56
+ // own log separate, including the browser install/setup/preview journey.
57
+ const capture = ['.seed', 'installer', ...(process.env.RER_LOCAL_OPERATION || process.env.RER_ONBOARDING_PROCESS_OWNER ? [`commands-${process.pid}`] : [])];
58
+ const directory = project.profile === 'first-party' && !['outbox', 'doctor'].includes(options.verb) ? await privateDirectory(project.root, capture) : (temporary = await mkdtemp(join(tmpdir(), 'rer-command-')));
59
+ const state = { logs: [] };
60
+ store = await createCommands(directory, state, { terminal: text => out.progress(text), onWaiting: options.json ? details => out.event('waiting', details) : undefined, queue: ['install','setup','build','verify','deploy','export','import'].includes(options.verb)?{verb:options.verb,originalCI:process.env.CI==='true'}:undefined });
61
+ store.state = state;
62
+ store.setupManifest = project.setupManifest;
63
+ store.step = async label => { state.step = store.redact(label); store.report(label); };
64
+ store.phase = async phase => { state.phase = phase; };
65
+ store.setCommandSignal(signal);
66
+ if (options.verb === 'doctor') return await doctor(project, store, signal);
67
+ if (project.profile === 'independent') return await executeDeclared(project, options, store, signal);
68
+ await tools(project.root, store, signal);
69
+ const adapter = project.adapter;
70
+ if (options.verb === 'install') {
71
+ await store.run('Installing frozen dependencies', 'pnpm', ['install', '--frozen-lockfile'], { cwd: project.root, signal, env: { ...process.env, CI: 'true' } }); return {};
72
+ }
73
+ if (options.verb === 'verify') {
74
+ const target = adapter.verify;
75
+ if (!Array.isArray(target)) throw refuse('A complete internal verification target is missing.');
76
+ await store.run('Verifying application', target[0], target[1], { cwd: project.root, signal, env: { ...process.env, RER_LOCAL_OPERATION: operation.token } });
77
+ await store.step('Serving the production build as rer deploy hosts it');
78
+ return { hosted: await (await import('./hosted-output.mjs')).checkHostedOutput(project.root, { declaration: adapter.deployDeclaration ?? adapter.deploy?.declaration }) };
79
+ }
80
+ if (['export', 'import'].includes(options.verb)) return await transfer(project, options, signal, { store, operationToken: operation.token });
81
+ if (options.verb === 'deploy') {
82
+ if (typeof adapter.deploy !== 'function') throw refuse('Owner-account deploy declaration is missing.');
83
+ const deployed = await adapter.deploy({ root: project.root, options, store, signal, operationToken: operation.token, localOnlyKeys: adapter.localOnlyKeys ?? [], checkBuild: () => refuseDevLayerOutput(project.root, adapter.devIdentity) });
84
+ if(deployed.deployed && !options.dryRun) await linkDependencies({project,input:await savedSetupAnswers(project,project.setupSeed.model),origin:deployed.origin,store,signal,appSetupToken:process.env[project.setupManifest?.steps.find(step=>step.type==='owner-claim')?.tokenField??'SETUP_TOKEN']});
85
+ return deployed;
86
+ }
87
+ // Setup writes the checkout's dev key; its temporary app and the CLI's own
88
+ // outbox reads present it. Captured logs redact it.
89
+ const devKey = adapter.devIdentity ? options.verb === 'setup' ? await ensureDevKey(project.root) : await readDevKey(project.root) : null;
90
+ if (devKey) store.addSecrets?.([devKey]);
91
+ let app;
92
+ if (options.verb === 'setup') proxy = await runtimeFrontDoor(selected);
93
+ const emailKey = localSettingKeys(project).email;
94
+ await adapter.run(project.root, input ?? { [emailKey]: options.ownerEmail ?? selected?.vars[emailKey] }, store,
95
+ child => { app = child; }, signal,
96
+ { verb: options.verb, ownerKey: emailKey, selectedOrigin: selected?.url, runtimePort: proxy?.runtimePort, operationToken: operation?.token, dev: adapter.dev, privateSettings: [...adapter.settings, '.rer/local/models.json', ...(project.privateInputs??[])], signInEmail: options.verb === 'outbox' ? options.ownerEmail : undefined, requestSignIn: Boolean(options.ownerEmail), devKey: devKey ?? undefined });
97
+ if (['setup', 'build'].includes(options.verb)) await refuseDevLayerOutput(project.root, adapter.devIdentity);
98
+ const url = selected?.url, signInUrl = url ? url + '/__dev/outbox' : undefined;
99
+ if (options.verb === 'setup') {
100
+ await linkDependencies({project,input,origin:url,store,signal});
101
+ if(project.setupManifest?.dependencies?.length) {
102
+ const { localVariables }=await shared('local/environment.mjs');
103
+ const values=localVariables(await readFile(new URL(project.adapter.hooks?.secrets?.file??'.dev.vars','file://'+project.root+'/'),'utf8'));
104
+ const proof=createHmac('sha256',values.SESSION_SECRET).update('local-cli\0sign-in').digest('hex');
105
+ store.addSecrets?.([proof]);
106
+ const completed=await fetch(url+'/auth/setup-complete',{method:'POST',headers:{Origin:url,'Content-Type':'application/json','x-rer-local-cli':proof},body:'{}',signal});
107
+ if(!completed.ok)throw missing('The dependency setup has not completed an owner claim.');
108
+ await completed.body?.cancel();
109
+ }
110
+ await store.stop(app);
111
+ await rememberSetupAnswers(project, input);
112
+ if (options.json) out.event('setup-ready', { url, signInUrl, setupUrl: url+'/setup' });
113
+ else if (devKey) { out.human(`RER_SETUP_READY ${url}`); out.human(`Next: rer dev opens ${url} signed in as the owner; first-run setup finishes at ${url}/setup.`); }
114
+ else { out.human(`RER_SETUP_READY ${url}`); signInInstructions(out, signInUrl); out.human(`Finish first-run setup at ${url}/setup after sign-in.`); out.human('Next: rer dev'); }
115
+ } else if (options.verb === 'outbox') {
116
+ const response = await fetch(signInUrl, { headers: devKey ? { [DEV_KEY_HEADER]: devKey } : {}, signal: AbortSignal.any([signal, AbortSignal.timeout(5000)]) });
117
+ if (!response.ok) throw new RerError('outbox_unavailable', 'The local application outbox is unavailable. Start rer dev first.', 1);
118
+ await response.body?.cancel();
119
+ const open = devKey ? openUrl(url, devKey, { path: '/__dev/key', next: '/__dev/outbox' }) : signInUrl;
120
+ if (options.json) out.event('outbox', { signInUrl, ...(devKey ? { openUrl: open } : {}) }); else { signInInstructions(out, signInUrl); if (devKey) out.human(`RER_OPEN ${open}`); }
121
+ if (options.open && !options.json && !options.nonInteractive) await store.run('Opening local outbox', process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'explorer.exe' : 'xdg-open', [open], { cwd: project.root, signal });
122
+ } else if (options.verb !== 'build') throw refuse('The requested first-party capability is unavailable.');
123
+ return {};
124
+ } catch (error) {
125
+ if (error instanceof RerError) throw error;
126
+ throw await lifecycleError(error, store);
127
+ } finally {
128
+ await store?.closeCommands(); await proxy?.close(); await operation?.release();
129
+ if (temporary) await rm(temporary, { recursive: true, force: true });
130
+ for (const [name, handler] of handlers) process.removeListener(name, handler);
131
+ if (termination) throw new RerError('cancelled', `Stopped by ${termination} after owned children were stopped.`, termination === 'SIGINT' ? 130 : 143);
132
+ }
133
+ }
134
+
135
+ // One plain line saying which port this checkout uses, and what moved.
136
+ function announcePort(out, options, selected) {
137
+ const { moved, skipped } = selected;
138
+ const line = moved ? `Moving this checkout from port ${moved.from} to port ${moved.to}. Its data is kept.`
139
+ : !selected.savedOrigin ? `Using port ${selected.port} for this checkout${skipped ? ` (${skipped} is in use)` : ''}.` : undefined;
140
+ if (!line) return;
141
+ if (options.json) out.event('port', { port: selected.port, ...(moved ? { movedFrom: moved.from } : {}) });
142
+ else out.human(line);
143
+ }
144
+
145
+ function signInInstructions(out, url) {
146
+ out.human(`RER_SIGN_IN ${url}`);
147
+ out.human('Open the outbox, choose the newest sign-in message and follow its link. No email was sent.');
148
+ }
package/cli/local.mjs ADDED
@@ -0,0 +1,100 @@
1
+ import { lstat, readdir, rm } from 'node:fs/promises';
2
+ import { resolve, relative } from 'node:path';
3
+ import { createServer } from 'node:net';
4
+ import { createInterface } from 'node:readline/promises';
5
+ import { contained } from './project.mjs';
6
+ import { shared } from './shared.mjs';
7
+ import { origin, loopback } from './parse.mjs';
8
+ import { refuse } from './errors.mjs';
9
+ import { defaultPort, firstFreePort, busyPortMessage } from './port.mjs';
10
+ import { localSettingKeys } from './settings.mjs';
11
+
12
+ export async function selectedOrigin(project, options, { setup = false } = {}) {
13
+ const { readSettings } = await shared('adapter-settings.mjs');
14
+ const vars = project.adapter?.hooks?.secrets ? (await readSettings(project.root, project.adapter.hooks.secrets)).values : {};
15
+ const { origin: originKey } = localSettingKeys(project);
16
+ let previous;
17
+ if (vars[originKey]) {
18
+ try { previous = new URL(vars[originKey]); } catch { throw refuse('Saved origin is malformed. Restore private settings or use confirmed reset.'); }
19
+ if (previous.origin !== vars[originKey] || previous.protocol !== 'http:' || !loopback(previous.hostname.replace(/^\[|\]$/g, ''))) throw refuse('Saved origin is unsafe. Restore private settings or use confirmed reset.');
20
+ }
21
+ const host = options.host ?? previous?.hostname.replace(/^\[|\]$/g, '') ?? '127.0.0.1';
22
+ const savedPort = previous ? Number(previous.port || 80) : undefined;
23
+ // A first setup with no port asked for takes the first free one, never a busy one.
24
+ let port = options.port ?? savedPort, taken;
25
+ if (port === undefined && setup) { port = await firstFreePort(host); taken = port !== defaultPort; }
26
+ port ??= defaultPort;
27
+ const url = origin(host, port);
28
+ // Setup on a saved checkout moves it to another port; the host stays.
29
+ let moved;
30
+ if (setup && previous && previous.origin !== url) {
31
+ if (previous.hostname.replace(/^\[|\]$/g, '') !== host) throw refuse('Setup origin conflicts with the saved installation. Reuse its origin or back up data and run rer reset --yes.');
32
+ moved = { from: savedPort, to: port };
33
+ }
34
+ return { url, host, port, vars, savedOrigin: previous?.origin, moved, explicitPort: options.port !== undefined, ...(taken ? { skipped: defaultPort } : {}) };
35
+ }
36
+
37
+ export async function availablePort(host, port, { root, saved = false } = {}) {
38
+ const socket = createServer();
39
+ try { await new Promise((resolve, reject) => { socket.once('error', reject); socket.listen(port, host, resolve); }); }
40
+ catch (error) {
41
+ if (error.code !== 'EADDRINUSE') throw refuse(`Port ${port} cannot be used on ${host}: ${error.code ?? error.message}.`);
42
+ throw refuse(await busyPortMessage({ port, host, root, saved }));
43
+ }
44
+ finally { if (socket.listening) await new Promise(resolve => socket.close(resolve)); }
45
+ // A server on every address (*:port) lets a bind to 127.0.0.1 succeed.
46
+ const { portListener } = await shared('process-diagnostics.mjs');
47
+ if (await portListener(port, host)) throw refuse(await busyPortMessage({ port, host, root, saved }));
48
+ }
49
+
50
+ export {setupInput} from './setup-input.mjs';
51
+
52
+ export async function stateSafety(project) {
53
+ const { selectLocalState } = await shared('local/state.mjs');
54
+ try { await selectLocalState(project.root, project.adapter.state); }
55
+ catch { throw refuse('Local state placement is unsafe or ambiguous. Preserve legacy state and recover it explicitly before retrying.'); }
56
+ }
57
+
58
+ async function safeTree(project, path) {
59
+ const absolute = await contained(project.root, path, { optional: true });
60
+ let status; try { status = await lstat(absolute); } catch (error) { if (error.code === 'ENOENT') return; throw error; }
61
+ if (status.isSymbolicLink() || (!status.isFile() && !status.isDirectory()) || (process.getuid && status.uid !== process.getuid()) || (status.isFile() && status.nlink !== 1)) throw refuse('Reset inventory contains unsafe or linked storage. Preserve it and resolve placement.');
62
+ if (status.isDirectory()) for (const entry of await readdir(absolute)) await safeTree(project, relative(project.root, resolve(absolute, entry)));
63
+ return absolute;
64
+ }
65
+
66
+ export async function reset(project, options, out) {
67
+ await stateSafety(project);
68
+ await refuseLegacyProcess(project,{reset:true});
69
+ const { strandedOriginals } = await shared('local/recovery.mjs');
70
+ if ((await strandedOriginals(project.root)).length) throw refuse('An interrupted build holds private settings. Run rer build to recover them before reviewing reset.');
71
+ const declared = project.adapter.settings ?? [];
72
+ for (const path of declared) if (!/^\.dev\.vars(?:\.previous)?$|^data\/[a-zA-Z0-9_-]+\.json$/.test(path)) throw refuse('Generated settings inventory is unsafe; declare specific private settings files.');
73
+ const paths = [...new Set(['.wrangler/state', '.rer/local', '.seed/installer', ...declared])];
74
+ const inventory = [];
75
+ for (const path of paths) if (await safeTree(project, path)) inventory.push(path);
76
+ if (options.json) out.event('inventory', { paths: inventory }); else out.human(`reset inventory: ${inventory.length ? inventory.join(', ') : '(empty)'}`);
77
+ let confirmed = options.yes;
78
+ if (!confirmed && !options.json && !options.nonInteractive && process.stdin.isTTY) {
79
+ const prompt = createInterface({ input: process.stdin, output: process.stderr });
80
+ try { confirmed = (await prompt.question('Remove exactly this local inventory? Type yes: ')) === 'yes'; } finally { prompt.close(); }
81
+ }
82
+ if (!confirmed) throw refuse('Reset requires explicit confirmation. Review the inventory, then pass --yes.');
83
+ // The shared lock lives outside every removal path and remains held throughout.
84
+ const { localOperation } = await shared('local/operation.mjs');
85
+ let operation;
86
+ try { operation = await localOperation(project.root); } catch (error) { throw refuse(error.message); }
87
+ try {
88
+ for (const path of inventory) { const absolute = await safeTree(project, path); if (absolute) await rm(absolute, { recursive: true, force: true }); }
89
+ } finally { await operation.release(); }
90
+ return { paths: inventory };
91
+ }
92
+
93
+ export async function refuseLegacyProcess(project,{reset=false}={}) {
94
+ let selected;try{selected=await selectedOrigin(project,{});}catch(error){if(!reset)throw error;}
95
+ // Confirmed reset can recover malformed settings; checkout/state custody is
96
+ // still checked even when there is no usable saved origin.
97
+ const {localProcessConflict}=await shared('process-diagnostics.mjs');
98
+ const conflict=await localProcessConflict(project.root,selected?.savedOrigin);
99
+ if(conflict)throw refuse(`A local process holds ${conflict.reason}: PID ${conflict.pid} (${conflict.command}). Stop it with kill -TERM ${conflict.pid}, then retry. Use rer dev --stop or rer preview --stop for a registered server.`);
100
+ }
package/cli/main.mjs ADDED
@@ -0,0 +1,57 @@
1
+ import { parse, help } from './parse.mjs';
2
+ import { output } from './output.mjs';
3
+ import { invalid, RerError } from './errors.mjs';
4
+ import { resolveProject, parsingProfile } from './project.mjs';
5
+ import { check, describe } from './inspect.mjs';
6
+ import { selectNodeRuntime } from './node-runtime.mjs';
7
+
8
+ export async function main(argv = process.argv.slice(2)) {
9
+ // Every runtime a seed's hooks start inherits this: Wrangler and the Cloudflare
10
+ // Vite plugin otherwise serve the Local Explorer, which reads and writes the
11
+ // store without the dev key.
12
+ process.env.X_LOCAL_EXPLORER = 'false';
13
+ let options = { verb: argv[0], json: argv.includes('--json') }, out = output(options);
14
+ try {
15
+ options = parse(argv,process.cwd(),{deferTail:true}); out = output(options);
16
+ if (options.help) return showHelp(options,out);
17
+ if (options.verb === 'archive') return (await import('./archive.mjs')).archiveCommand(options.archiveArgs,out);
18
+ if (options.verb === 'release') return (await import('./release.mjs')).dispatchRelease(options.releaseArgs);
19
+ const independent = await parsingProfile(options.cwd)==='independent'&&!['describe','check','doctor'].includes(options.verb);
20
+ options=parse(argv,process.cwd(),{independent});out=output(options);
21
+ if (options.help) return showHelp(options,out);
22
+ if (!independent && options.forwarded.length) throw invalid('First-party commands accept only the documented flags, without forwarded arguments.');
23
+ if(options.noQueue){const {shared}=await import('./shared.mjs');const {commandPolicy}=await shared('queue.mjs');const policy=await commandPolicy();try{if(!policy.ci)throw invalid('--no-queue requires the original caller CI=true.');}finally{await policy.close();}}
24
+ const runtime = await selectNodeRuntime(options, argv, out);
25
+ if (runtime) return runtime.exitCode;
26
+ const project = await resolveProject(options.cwd);
27
+ if (project.profile === 'independent' && !['describe', 'check', 'doctor'].includes(options.verb)) options = parse(argv, process.cwd(), { independent: true });
28
+ else if (options.forwarded.length) throw invalid('First-party commands accept only the documented flags, without forwarded arguments.');
29
+ out = output(options);
30
+ if (options.verb === 'describe') { const result = await describe(project); out.human(options.json ? JSON.stringify(result) : JSON.stringify(result, null, 2)); return 0; }
31
+ if (options.verb === 'check') {
32
+ const result = await check(project, options.profile);
33
+ if (!options.json) for (const clause of result.clauses.filter(c => !c.ok)) out.human(`${clause.id}: ${clause.message}`);
34
+ out.result(result.ok, result.ok ? 0 : 1, { clauses: result.clauses }); return result.ok ? 0 : 1;
35
+ }
36
+ if (options.verb === 'doctor') {
37
+ const result = await (await import('./lifecycle.mjs')).runCommand(project, options, out);
38
+ if (!options.json) out.human(JSON.stringify(result, null, 2));
39
+ out.result(result.ok, result.ok ? 0 : 3, { diagnostics: result }); return result.ok ? 0 : 3;
40
+ }
41
+ const bounded=await (await import('./bounded.mjs')).boundedCommand(project,options,argv,out);
42
+ if(bounded)return bounded.exitCode;
43
+ if(options.platform==='container' && !['export','import'].includes(options.verb)) { const result = await (await import('./container.mjs')).containerCommand(project,options,out); out.result(true,0,result); return 0; }
44
+ let result;
45
+ if (options.verb === 'reset' && project.profile === 'first-party') result = await (await import('./local.mjs')).reset(project, options, out);
46
+ else result = await (await import('./lifecycle.mjs')).runCommand(project, options, out);
47
+ out.result(true, 0, result); return 0;
48
+ } catch (error) {
49
+ const failure = error instanceof RerError ? error : new RerError('command_failed', 'Command failed; inspect the project operating documentation.', 1);
50
+ out.error(failure); out.result(false, failure.exitCode, failure.childExitCode === undefined ? {} : { childExitCode: failure.childExitCode, signal: failure.signal }); return failure.exitCode;
51
+ }
52
+ }
53
+
54
+ function showHelp(options,out) {
55
+ if(options.json){out.event('help',{usage:help(options.verb)});out.result(true,0);}else out.human(help(options.verb));
56
+ return 0;
57
+ }
@@ -0,0 +1,143 @@
1
+ import { prepareLiveModels } from './ai-local.mjs';
2
+ import {fileURLToPath} from 'node:url';
3
+ import { serverRegistry } from './server-registry.mjs';
4
+ import { activityClock } from './server-activity.mjs';
5
+ import { runtimeFrontDoor } from './runtime-front-door.mjs';
6
+ import {serverResources} from './server-resources.mjs';
7
+ import { productionBuild } from './preview-cache.mjs';
8
+ import {localSettingKeys} from './settings.mjs';
9
+ import { shared } from './shared.mjs';
10
+ import { availablePort,selectedOrigin,stateSafety,refuseLegacyProcess } from './local.mjs';
11
+ import { tools } from './inspect.mjs';
12
+ import { invalid,missing,refuse,RerError,lifecycleError } from './errors.mjs';
13
+ import { processInfo } from '../modules/job-queue/processes.mjs';
14
+ import { declaredCrons,runScheduled } from './scheduled.mjs';
15
+ import { readDevKey,requireDevKey,refuseManagedInstallation,resolvePersona,openUrl,DEV_KEY_HEADER } from './dev-identity.mjs';
16
+
17
+ // Seeds without devIdentity keep the outbox sign-in unchanged. With it, dev
18
+ // opens signed in (RER_SIGNED_IN, RER_OPEN); --login and preview keep the
19
+ // outbox, reached through RER_OPEN's one-time key exchange. A runtime that
20
+ // serves no outbox (a seed's preview without one) is not pointed at it.
21
+ export function ready(out,url,dev,json=false,outbox=true){
22
+ const signInUrl=url+'/__dev/outbox',open=dev&&(dev.identity||outbox);
23
+ if(json){out.event('ready',{url,...(outbox?{signInUrl}:{}),...(dev?{signedInAs:dev.signedInAs??null,...(open?{openUrl:dev.openUrl}:{})}:{})});return;}
24
+ out.human(`RER_READY ${url}`);
25
+ if(dev?.identity){if(dev.signedInAs)out.human(`RER_SIGNED_IN ${dev.signedInAs}`);out.human(`RER_OPEN ${dev.openUrl}`);out.human('Open that link on this computer. Its fragment holds this checkout\'s dev key, which the page keeps only as a cookie; never share the link.');return;}
26
+ if(!outbox)return;
27
+ out.human(`RER_SIGN_IN ${signInUrl}`);
28
+ if(dev)out.human(`RER_OPEN ${dev.openUrl}`);
29
+ out.human(`Open ${dev?'the last link on this computer (it carries the dev key; never share it)':'the outbox'}, choose the newest sign-in message and follow its link. No email was sent.`);
30
+ }
31
+ export async function managedServer(project,options,out,{clock={}}={}){
32
+ const registry=await serverRegistry(project.root),controller=new AbortController();
33
+ let store,operation,proxy,activity,app,termination,reason,cleanupPromise,finished,selected,cleaned;
34
+ const scheduling=new AbortController();
35
+ let activityWrites=Promise.resolve(),pendingActivity,activityWriting=false,infrastructureFailure;
36
+ function recordActivity(fields){
37
+ pendingActivity=fields;if(activityWriting)return;activityWriting=true;
38
+ activityWrites=(async()=>{while(pendingActivity){const latest=pendingActivity;pendingActivity=undefined;await registry.updateActivity(latest);}})().catch(error=>{infrastructureFailure=error;void stop('activity bookkeeping failed');}).finally(()=>{activityWriting=false;if(pendingActivity)recordActivity(pendingActivity);});
39
+ }
40
+ const stopped=new Promise(resolve=>{finished=resolve;});
41
+ const cleanupDone=new Promise(resolve=>{cleaned=resolve;});
42
+ const cleanup=()=>cleanupPromise??=(async()=>{scheduling.abort();activity?.close();await store?.closeCommands();await proxy?.close();await operation?.release();})();
43
+ const stop=async why=>{reason??=why;controller.abort();finished();await cleanupDone;return {};};
44
+ const handlers=new Map(['SIGINT','SIGTERM'].map(name=>[name,()=>{termination=name;void stop(name);} ]));
45
+ for(const [name,handler]of handlers)process.on(name,handler);
46
+ try{
47
+ if(options.stop){await registry.stop(options.verb,options);return {};}
48
+ controller.signal.throwIfAborted();
49
+ const devIdentity=project.adapter.devIdentity;
50
+ if(options.as!==undefined&&!(devIdentity&&options.verb==='dev'))throw invalid('--as needs a seed that declares devIdentity.');
51
+ let devKey=devIdentity?await readDevKey(project.root):null;
52
+ let current=await registry.existing(options.verb,options);
53
+ if(!current){
54
+ const reservation=await registry.reserve(options.verb,options);
55
+ current=reservation.existing;
56
+ }
57
+ if(current){
58
+ // Resolve a competing reservation through the same explicit-option check.
59
+ current=await registry.existing(options.verb,options);
60
+ const running=await registry.waitReady(current,controller.signal);
61
+ await registry.existing(options.verb,options);
62
+ if(options.verb==='dev' && (running.models??'fake')!==(options.models??'fake'))throw refuse('Stop the running dev server before changing --models mode.');
63
+ if(options.modelsService)throw refuse('Stop the running dev server before saving live-dev credentials.');
64
+ if(options.verb==='dev'&&Boolean(running.login)!==Boolean(options.login))throw refuse(`The running dev server ${running.login?'uses':'does not use'} --login. Stop it with rer dev --stop to change sign-in mode.`);
65
+ const response=await fetch(running.url+'/health',{headers:{'x-rer-probe':running.probe},signal:AbortSignal.timeout(5000)});
66
+ if(!response.ok || (await response.json()).status!=='ok')throw refuse('Registered application failed health verification. Stop it before retrying.');
67
+ if(devIdentity)devKey=await requireDevKey(project.root);
68
+ emitReady(out,options,running.url,devIdentity&&await devReady(running.url,devKey,options,controller.signal));return {reused:true};
69
+ }
70
+ await registry.serve(async action=>{
71
+ if(action==='status')return {...activity?.snapshot(),url:selected?.url,probe:proxy?.probe};
72
+ if(action==='stop')return stop('explicit stop');
73
+ throw Error('Unknown management operation');
74
+ });
75
+ await stateSafety(project);
76
+ await refuseLegacyProcess(project);
77
+ const {localOperation}=await shared('local/operation.mjs');
78
+ try{operation=await localOperation(project.root);}catch(error){throw refuse(error.message);}
79
+ const {recoverInterrupted}=await shared('local/recovery.mjs');await recoverInterrupted(project.root,operation);
80
+ const {loadInstallerSeed}=await shared('setup-manifest.mjs');
81
+ const seed=project.adapter.setup?await loadInstallerSeed(project.root,project.adapter):undefined;project.setupManifest=seed?.manifest;
82
+ const keys=localSettingKeys(project);
83
+ selected=await selectedOrigin(project,options);
84
+ await availablePort(selected.host,selected.port,{root:project.root,saved:!selected.explicitPort&&selected.savedOrigin!==undefined});
85
+ if(!selected.vars[keys.email])throw missing('Private owner settings are missing. Run rer setup first.');
86
+ if(devIdentity&&!devKey)devKey=await requireDevKey(project.root);
87
+ if(devIdentity&&options.verb==='dev'&&!options.login)refuseManagedInstallation(selected.vars,process.env);
88
+ if(project.adapter.localPrerequisites&&!await project.adapter.localPrerequisites({root:project.root,vars:selected.vars,email:selected.vars[keys.email]}))throw missing('Local migrations, owner or local authentication/mail settings are missing. Run rer setup first.');
89
+ await registry.update({url:selected.url,host:selected.host,port:selected.port,login:Boolean(options.login),models:options.models??'fake'});
90
+ const {privateDirectory}=await shared('private-files.mjs');
91
+ await privateDirectory(project.root,['.wrangler','state']);
92
+ const directory=await privateDirectory(project.root,['.seed','installer',...(process.env.RER_ONBOARDING_PROCESS_OWNER?[`commands-${process.pid}`]:[])]);
93
+ const {createCommands}=await shared('commands.mjs');const state={logs:[]};
94
+ store=await createCommands(directory,state,{terminal:text=>out.progress(text),onWaiting:options.json?details=>out.event('waiting',details):undefined,custodyToken:registry.custodyToken(),queue:{verb:options.verb,originalCI:process.env.CI==='true'}});
95
+ store.state=state;store.step=async label=>store.report(label);store.phase=async phase=>{state.phase=phase;};store.setCommandSignal(controller.signal);
96
+ store.setupManifest=seed?.manifest;if(devKey)store.addSecrets([devKey]);
97
+ await tools(project.root,store,controller.signal);
98
+ activity=activityClock({...clock,seconds:options.idleTimeout,onIdle:()=>void stop('idle timeout'),onChange:recordActivity});
99
+ const crons=project.adapter.scheduled!==false?await declaredCrons(project.root,project.adapter).catch(error=>{throw refuse(`The declared scheduled triggers cannot run locally: ${error.message}`);}):[];
100
+ proxy=await runtimeFrontDoor({host:selected.host,port:selected.port,activity});const internal=proxy.runtimePort;
101
+ const modelsLive = options.verb==='dev' ? await prepareLiveModels(project.root,options,store,controller.signal) : undefined;
102
+ const adapterOptions={modelsLive,selectedOrigin:selected.url,operationToken:operation.token,privateSettings:project.adapter.settings,runtimePort:internal,devKey:devKey??undefined,devLogin:Boolean(options.login),scheduled:crons.length>0};
103
+ const input={[keys.email]:selected.vars[keys.email]};
104
+ if(options.verb==='preview')await productionBuild(project,()=>store.run('Building fixed production output',process.execPath,[fileURLToPath(new URL('../bin/rer.mjs',import.meta.url)),'build','--cwd',project.root,'--json'],{cwd:project.root,phase:'build',signal:controller.signal,env:{...process.env,RER_LOCAL_OPERATION:operation.token},afterCommand:()=>recoverInterrupted(project.root,operation)}),text=>store.report(text));
105
+ await serverResources(project.root);
106
+ await project.adapter.run(project.root,input,store,async child=>{
107
+ app=child;
108
+ child.once('exit',code=>{if(!reason){reason='unexpected application exit';void cleanup().then(()=>finished({code:code||1}));}});
109
+ const identity=await processInfo(child.pid);
110
+ if(!identity)throw Error('Runtime exited before custody registration.');
111
+ await registry.update({runtimeProcess:{pid:identity.pid,start:identity.start}});
112
+ },controller.signal,{...adapterOptions,verb:options.verb,dev:project.adapter.dev,preview:project.adapter.preview});
113
+ const dev=devIdentity&&await devReady(selected.url,devKey,options,controller.signal);
114
+ const outbox=await outboxServed(selected.url,devKey,controller.signal);
115
+ activity.ready();emitReady(out,options,selected.url,dev,outbox);
116
+ if(crons.length){
117
+ out.progress(`Running ${crons.length===1?'the scheduled trigger':'the scheduled triggers'} locally: ${crons.join(' | ')}\n`);
118
+ void runScheduled({crons,url:`http://127.0.0.1:${internal}`,path:options.verb==='preview'?'/cdn-cgi/local/scheduled':undefined,signal:scheduling.signal,report:run=>reportScheduled(out,options,run)});
119
+ }
120
+ const result=await stopped;
121
+ if(termination)throw new RerError('cancelled',`Stopped by ${termination} after owned children were stopped.`,termination==='SIGINT'?130:143);
122
+ if(infrastructureFailure)throw new RerError('server_bookkeeping_failed','Server bookkeeping failed; owned serving was stopped.',1);
123
+ if(result?.code)throw new RerError('app_exited','The application stopped unexpectedly.',result.code);
124
+ out.progress(`Server stopped: ${reason}.\n`);return {};
125
+ }catch(error){if(termination)throw new RerError('cancelled',`Stopped by ${termination} after owned children were stopped.`,termination==='SIGINT'?130:143);if(['idle timeout','explicit stop'].includes(reason))return {};throw await lifecycleError(error,store);}finally{await cleanup();cleaned();while(activityWriting)await activityWrites;await registry.close();for(const [name,handler]of handlers)process.off(name,handler);}
126
+ }
127
+ function reportScheduled(out,options,run){
128
+ const at=new Date().toISOString().replace(/\.\d+Z$/,'Z'),outcome=run.skipped?`skipped (${run.error})`:run.ok?`ok ${run.ms}ms`:`failed (${run.status?`HTTP ${run.status}`:run.error}) ${run.ms}ms`;
129
+ if(options.json)out.event('scheduled',{at,cron:run.cron,ok:run.ok,skipped:Boolean(run.skipped),status:run.status,ms:run.ms});else out.human(`RER_SCHEDULED ${at} "${run.cron}" ${outcome}`);
130
+ }
131
+ // GET, not HEAD: a seed's outbox route need not answer HEAD. The body is never read.
132
+ async function outboxServed(url,devKey,signal){
133
+ const response=await fetch(url+'/__dev/outbox',{headers:devKey?{[DEV_KEY_HEADER]:devKey}:{},signal});
134
+ await response.body?.cancel();
135
+ return response.ok;
136
+ }
137
+ function emitReady(out,options,url,dev,outbox){ready(out,url,dev||undefined,options.json,outbox);}
138
+ async function devReady(url,key,options,signal){
139
+ if(options.verb==='preview')return {openUrl:openUrl(url,key,{path:'/__dev/key',next:'/__dev/outbox'})};
140
+ if(options.login)return {openUrl:openUrl(url,key,{next:'/__dev/outbox'})};
141
+ const as=options.as??'owner',persona=await resolvePersona(url,key,as,signal);
142
+ return {identity:true,signedInAs:persona.email??null,openUrl:openUrl(url,key,{path:'/__dev/key',as})};
143
+ }
@@ -0,0 +1,68 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { createReadStream } from 'node:fs';
3
+ import { lstat, mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';
4
+ import { homedir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { missing } from './errors.mjs';
7
+
8
+ export const nodeCache = () => join(homedir(), '.rer', 'node');
9
+ export const runtimeName = version => `node-v${version}-${process.platform}-${process.arch}`;
10
+ export const runtimeBinary = directory => join(directory, ...(process.platform === 'win32' ? ['node.exe'] : ['bin', 'node']));
11
+ export const shellQuote = value => `'${String(value).replaceAll("'", "'\\''")}'`;
12
+
13
+ export async function binaryDigest(path) {
14
+ const hash = createHash('sha256');
15
+ for await (const chunk of createReadStream(path)) hash.update(chunk);
16
+ return hash.digest('hex');
17
+ }
18
+ async function owned(path, directory) {
19
+ const stat = await lstat(path);
20
+ if (stat.isSymbolicLink() || (directory ? !stat.isDirectory() : !stat.isFile()) || (process.getuid && stat.uid !== process.getuid()) || (stat.mode & 0o022)) throw Error('Unsafe runtime cache placement.');
21
+ }
22
+ export async function prepareNodeCache() {
23
+ const cache = nodeCache();
24
+ for (const path of [join(homedir(), '.rer'), cache]) {
25
+ await mkdir(path, { mode: 0o700 }).catch(error => { if (error.code !== 'EEXIST') throw error; });
26
+ try { await owned(path, true); } catch { throw missing(`Node cache directory must be an owned, non-linked directory without group/other write access: ${path}. Correct its placement and permissions before retrying.`); }
27
+ }
28
+ return cache;
29
+ }
30
+ export async function cachedRuntime(version) {
31
+ const directory = join(nodeCache(), runtimeName(version));
32
+ try { await lstat(directory); } catch (error) { if (error.code === 'ENOENT') return; throw error; }
33
+ try {
34
+ await owned(join(homedir(), '.rer'), true); await owned(nodeCache(), true); await owned(directory, true);
35
+ const binary = runtimeBinary(directory), receipt = join(directory, '.rer-verified.json');
36
+ if (process.platform !== 'win32') await owned(join(directory, 'bin'), true);
37
+ await owned(binary, false); await owned(receipt, false);
38
+ const record = JSON.parse(await readFile(receipt, 'utf8'));
39
+ if (record.version !== version || !/^[a-f0-9]{64}$/.test(record.releaseSha256) || record.binarySha256 !== await binaryDigest(binary)) throw Error('Node cache integrity mismatch.');
40
+ return binary;
41
+ } catch {
42
+ throw missing(`The cached Node runtime is unsafe or incomplete. Preserve it for inspection with: mv ${shellQuote(directory)} ${shellQuote(directory + '.quarantine-' + Date.now())}. Then rerun the original rer command to acquire a verified release.`);
43
+ }
44
+ }
45
+ export async function recordRuntime(directory, version, releaseSha256) {
46
+ await writeFile(join(directory, '.rer-verified.json'), JSON.stringify({ version, releaseSha256, binarySha256: await binaryDigest(runtimeBinary(directory)) }) + '\n', { flag: 'wx', mode: 0o600 });
47
+ }
48
+
49
+ // Housekeeping for a runtime whose receipt and binary have just verified. Its
50
+ // quarantined predecessors (renamed aside by the recovery message below) are
51
+ // stale, and Node's C++ headers in include/ are read by nothing rer runs: the
52
+ // seeds' lockfiles hold no package that builds native code. Both cost disk on
53
+ // every Node pin, so they go. A failure here never blocks the command; doctor
54
+ // reports whatever is left.
55
+ export const quarantinePrefix = version => runtimeName(version) + '.quarantine-';
56
+ export async function tidyRuntime(version) {
57
+ const directory = join(nodeCache(), runtimeName(version));
58
+ const removable = async path => {
59
+ const info = await lstat(path).catch(() => undefined);
60
+ return info?.isDirectory() && !info.isSymbolicLink() && (!process.getuid || info.uid === process.getuid());
61
+ };
62
+ try {
63
+ for (const entry of await readdir(nodeCache())) {
64
+ if (entry.startsWith(quarantinePrefix(version)) && await removable(join(nodeCache(), entry))) await rm(join(nodeCache(), entry), { recursive: true, force: true });
65
+ }
66
+ if (await removable(join(directory, 'include'))) await rm(join(directory, 'include'), { recursive: true, force: true });
67
+ } catch {}
68
+ }